From 1d07cb51104bb5177e04693379d8f6b58d5bd503 Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Sat, 30 Nov 2024 12:12:12 +0000 Subject: [PATCH 001/152] pathfinding_improvement --- libopenage/pathfinding/pathfinder.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libopenage/pathfinding/pathfinder.cpp b/libopenage/pathfinding/pathfinder.cpp index e46919e62b..6a6b81632d 100644 --- a/libopenage/pathfinding/pathfinder.cpp +++ b/libopenage/pathfinding/pathfinder.cpp @@ -231,8 +231,8 @@ const Pathfinder::portal_star_t Pathfinder::portal_a_star(const PathRequest &req auto portal_pos = portal->get_exit_center(start_sector->get_id()); auto portal_abs_pos = sector_pos + portal_pos; auto heuristic_cost = Pathfinder::heuristic_cost(portal_abs_pos, request.target); - - portal_node->current_cost = 0; + std::cout << portal->get_id() << ": " << heuristic_cost << std::endl; + portal_node->current_cost = Pathfinder::heuristic_cost(portal_abs_pos, request.start); portal_node->heuristic_cost = heuristic_cost; portal_node->future_cost = portal_node->current_cost + heuristic_cost; From 7f7d5e222b7db4730cc86e0c74d503d300609e7b Mon Sep 17 00:00:00 2001 From: jere8184 Date: Sat, 30 Nov 2024 12:13:36 +0000 Subject: [PATCH 002/152] Update pathfinder.cpp --- libopenage/pathfinding/pathfinder.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libopenage/pathfinding/pathfinder.cpp b/libopenage/pathfinding/pathfinder.cpp index 6a6b81632d..2fef36053a 100644 --- a/libopenage/pathfinding/pathfinder.cpp +++ b/libopenage/pathfinding/pathfinder.cpp @@ -231,7 +231,6 @@ const Pathfinder::portal_star_t Pathfinder::portal_a_star(const PathRequest &req auto portal_pos = portal->get_exit_center(start_sector->get_id()); auto portal_abs_pos = sector_pos + portal_pos; auto heuristic_cost = Pathfinder::heuristic_cost(portal_abs_pos, request.target); - std::cout << portal->get_id() << ": " << heuristic_cost << std::endl; portal_node->current_cost = Pathfinder::heuristic_cost(portal_abs_pos, request.start); portal_node->heuristic_cost = heuristic_cost; portal_node->future_cost = portal_node->current_cost + heuristic_cost; From 5ad3d4c2ce0305390caff15f212cf6213ccb75df Mon Sep 17 00:00:00 2001 From: ZhuohaoHe Date: Wed, 27 Nov 2024 13:49:27 -0500 Subject: [PATCH 003/152] renderer: implement `is_complete()` to check if all uniforms have been set --- copying.md | 1 + libopenage/renderer/demo/demo_0.cpp | 7 ++++++- libopenage/renderer/demo/demo_1.cpp | 6 ++++++ libopenage/renderer/demo/demo_2.cpp | 5 +++++ libopenage/renderer/demo/demo_4.cpp | 5 +++++ libopenage/renderer/demo/demo_5.cpp | 5 +++++ libopenage/renderer/demo/demo_6.cpp | 12 ++++++++++++ libopenage/renderer/opengl/uniform_input.cpp | 9 +++++++++ libopenage/renderer/opengl/uniform_input.h | 5 +++++ libopenage/renderer/uniform_input.h | 2 ++ 10 files changed, 56 insertions(+), 1 deletion(-) diff --git a/copying.md b/copying.md index 61153fb9cf..3d97be2f8b 100644 --- a/copying.md +++ b/copying.md @@ -157,6 +157,7 @@ _the openage authors_ are: | Edvin Lindholm | EdvinLndh | edvinlndh à gmail dawt com | | Jeremiah Morgan | jere8184 | jeremiahmorgan dawt bham à outlook dawt com | | Tobias Alam | alamt22 | tobiasal à umich dawt edu | +| Alex Zhuohao He | ZzzhHe | zhuohao dawt he à outlook dawt com | If you're a first-time committer, add yourself to the above list. This is not just for legal reasons, but also to keep an overview of all those nicknames. diff --git a/libopenage/renderer/demo/demo_0.cpp b/libopenage/renderer/demo/demo_0.cpp index 38fd467d0b..16ce725aef 100644 --- a/libopenage/renderer/demo/demo_0.cpp +++ b/libopenage/renderer/demo/demo_0.cpp @@ -43,8 +43,13 @@ void renderer_demo_0(const util::Path &path) { auto display_shader = renderer->add_shader({display_vshader_src, display_fshader_src}); auto quad = renderer->add_mesh_geometry(resources::MeshData::make_quad()); + auto display_unif = display_shader->create_empty_input(); + /* Check if all uniform values for uniform inputs have been set */ + if (!display_unif->is_complete()) { + log::log(WARN << "Some Uniform values have not been set."); + } Renderable display_stuff{ - display_shader->create_empty_input(), + display_unif, quad, false, false, diff --git a/libopenage/renderer/demo/demo_1.cpp b/libopenage/renderer/demo/demo_1.cpp index 23dfdfa67b..65da534b2a 100644 --- a/libopenage/renderer/demo/demo_1.cpp +++ b/libopenage/renderer/demo/demo_1.cpp @@ -172,6 +172,12 @@ void renderer_demo_1(const util::Path &path) { auto pass2 = renderer->add_render_pass({display_obj}, renderer->get_display_target()); + /* Check if all uniform values for uniform inputs have been set */ + if (!obj1_unifs->is_complete() || !obj2_unifs->is_complete() || !obj3_unifs->is_complete() + || !proj_unif->is_complete() || !color_texture_unif->is_complete()) { + log::log(WARN << "Some Uniform values have not been set."); + } + /* Data retrieved from the object index texture. */ resources::Texture2dData id_texture_data = id_texture->into_data(); bool texture_data_valid = false; diff --git a/libopenage/renderer/demo/demo_2.cpp b/libopenage/renderer/demo/demo_2.cpp index e615d238ba..c5392f8590 100644 --- a/libopenage/renderer/demo/demo_2.cpp +++ b/libopenage/renderer/demo/demo_2.cpp @@ -224,6 +224,11 @@ void renderer_demo_2(const util::Path &path) { auto pass2 = renderer->add_render_pass({display_obj}, renderer->get_display_target()); + /* Check if all uniform values for uniform inputs have been set */ + if (!obj1_unifs->is_complete() || !proj_unif->is_complete() || !color_texture_unif->is_complete()) { + log::log(WARN << "Some Uniform values have not been set."); + } + /* Data retrieved from the object index texture. */ resources::Texture2dData id_texture_data = id_texture->into_data(); bool texture_data_valid = false; diff --git a/libopenage/renderer/demo/demo_4.cpp b/libopenage/renderer/demo/demo_4.cpp index fb39057e5d..515de4933b 100644 --- a/libopenage/renderer/demo/demo_4.cpp +++ b/libopenage/renderer/demo/demo_4.cpp @@ -165,6 +165,11 @@ void renderer_demo_4(const util::Path &path) { auto pass2 = renderer->add_render_pass({display_obj}, renderer->get_display_target()); + /* Check if all uniform values for uniform inputs have been set */ + if (!obj1_unifs->is_complete() || !proj_unif->is_complete() || !color_texture_unif->is_complete()) { + log::log(WARN << "Some Uniform values have not been set."); + } + window.add_resize_callback([&](size_t w, size_t h, double /*scale*/) { /* Calculate a projection matrix for the new screen size. */ float aspectRatio = float(w) / float(h); diff --git a/libopenage/renderer/demo/demo_5.cpp b/libopenage/renderer/demo/demo_5.cpp index 85752db18c..1361d274fa 100644 --- a/libopenage/renderer/demo/demo_5.cpp +++ b/libopenage/renderer/demo/demo_5.cpp @@ -134,6 +134,11 @@ void renderer_demo_5(const util::Path &path) { "tex", gltex); + /* Check if all uniform values for uniform inputs have been set */ + if (!transform_unifs->is_complete()) { + log::log(WARN << "Some Uniform values have not been set."); + } + // Move around the scene with WASD window.add_key_callback([&](const QKeyEvent &ev) { bool cam_update = false; diff --git a/libopenage/renderer/demo/demo_6.cpp b/libopenage/renderer/demo/demo_6.cpp index 9844503e64..1c80e50150 100644 --- a/libopenage/renderer/demo/demo_6.cpp +++ b/libopenage/renderer/demo/demo_6.cpp @@ -179,6 +179,10 @@ const std::vector RenderManagerDemo6::create_2d_obj() { this->obj_2d_texture, "tile_params", tile_params); + /* Check if all uniform values for uniform inputs have been set */ + if (!animation_2d_unifs->is_complete()) { + log::log(WARN << "Some Uniform values have not been set."); + } auto quad = this->renderer->add_mesh_geometry(resources::MeshData::make_quad()); Renderable animation_2d_obj{ animation_2d_unifs, @@ -198,6 +202,11 @@ const renderer::Renderable RenderManagerDemo6::create_3d_obj() { auto terrain_unifs = this->obj_3d_shader->new_uniform_input( "tex", this->obj_3d_texture); + /* Check if all uniform values for uniform inputs have been set */ + if (!terrain_unifs->is_complete()) { + log::log(WARN << "Some Uniform values have not been set."); + } + std::vector terrain_pos{}; terrain_pos.push_back({-25, -25, 0}); terrain_pos.push_back({25, -25, 0}); @@ -260,6 +269,9 @@ const std::vector RenderManagerDemo6::create_frame_obj() { frame_size, "incol", Eigen::Vector4f{0.0f, 0.0f, 1.0f, 1.0f}); + if (!frame_unifs->is_complete()) { + log::log(WARN << "Some Uniform values have not been set."); + } Renderable frame_obj{ frame_unifs, frame_geometry, diff --git a/libopenage/renderer/opengl/uniform_input.cpp b/libopenage/renderer/opengl/uniform_input.cpp index 38c63f2d66..8c021959b7 100644 --- a/libopenage/renderer/opengl/uniform_input.cpp +++ b/libopenage/renderer/opengl/uniform_input.cpp @@ -50,4 +50,13 @@ GlUniformBufferInput::GlUniformBufferInput(const std::shared_ptr this->update_data.resize(offset); } +bool GlUniformInput::is_complete() const { + for (const auto& uniform : this->update_offs) { + if (!uniform.used) { + return false; + } + } + return true; +} + } // namespace openage::renderer::opengl diff --git a/libopenage/renderer/opengl/uniform_input.h b/libopenage/renderer/opengl/uniform_input.h index 961e12a2a5..d635836b05 100644 --- a/libopenage/renderer/opengl/uniform_input.h +++ b/libopenage/renderer/opengl/uniform_input.h @@ -35,6 +35,11 @@ class GlUniformInput final : public UniformInput { public: GlUniformInput(const std::shared_ptr &prog); + /** + * Check if all uniforms have been set. + */ + bool is_complete() const override; + /** * Store the IDs of the uniforms from the shader set by this uniform input. */ diff --git a/libopenage/renderer/uniform_input.h b/libopenage/renderer/uniform_input.h index 6f2536459b..39bb4ea983 100644 --- a/libopenage/renderer/uniform_input.h +++ b/libopenage/renderer/uniform_input.h @@ -66,6 +66,8 @@ class UniformInput : public DataInput { public: virtual ~UniformInput() = default; + virtual bool is_complete() const = 0; + void update() override; void update(const char *unif, int32_t val) override; void update(const char *unif, uint32_t val) override; From a107605d80c9c931fd92f048a597ee24cfef6ee8 Mon Sep 17 00:00:00 2001 From: Alex Zhuohao He Date: Fri, 29 Nov 2024 20:57:54 -0500 Subject: [PATCH 004/152] add util function in demo to check all uniforms --- libopenage/renderer/demo/demo_0.cpp | 12 ++++++------ libopenage/renderer/demo/demo_1.cpp | 7 +++---- libopenage/renderer/demo/demo_2.cpp | 6 +++--- libopenage/renderer/demo/demo_4.cpp | 6 +++--- libopenage/renderer/demo/demo_5.cpp | 6 +++--- libopenage/renderer/demo/demo_6.cpp | 16 +++++----------- libopenage/renderer/demo/util.cpp | 14 ++++++++++++++ libopenage/renderer/demo/util.h | 10 ++++++++++ libopenage/renderer/opengl/uniform_input.cpp | 12 ++++++------ 9 files changed, 53 insertions(+), 36 deletions(-) diff --git a/libopenage/renderer/demo/demo_0.cpp b/libopenage/renderer/demo/demo_0.cpp index 16ce725aef..d1d679c0e4 100644 --- a/libopenage/renderer/demo/demo_0.cpp +++ b/libopenage/renderer/demo/demo_0.cpp @@ -9,6 +9,7 @@ #include "renderer/resources/mesh_data.h" #include "renderer/resources/shader_source.h" #include "renderer/shader_program.h" +#include "renderer/demo/util.h" namespace openage::renderer::tests { @@ -43,18 +44,17 @@ void renderer_demo_0(const util::Path &path) { auto display_shader = renderer->add_shader({display_vshader_src, display_fshader_src}); auto quad = renderer->add_mesh_geometry(resources::MeshData::make_quad()); - auto display_unif = display_shader->create_empty_input(); - /* Check if all uniform values for uniform inputs have been set */ - if (!display_unif->is_complete()) { - log::log(WARN << "Some Uniform values have not been set."); - } Renderable display_stuff{ - display_unif, + display_shader->create_empty_input(), quad, false, false, }; + if (!check_uniform_completeness({display_stuff})) { + log::log(WARN << "Uniforms not complete."); + } + auto pass = renderer->add_render_pass({display_stuff}, renderer->get_display_target()); while (not window.should_close()) { diff --git a/libopenage/renderer/demo/demo_1.cpp b/libopenage/renderer/demo/demo_1.cpp index 65da534b2a..2fb3ce6201 100644 --- a/libopenage/renderer/demo/demo_1.cpp +++ b/libopenage/renderer/demo/demo_1.cpp @@ -15,6 +15,7 @@ #include "renderer/shader_program.h" #include "renderer/texture.h" #include "util/math_constants.h" +#include "renderer/demo/util.h" namespace openage::renderer::tests { @@ -172,10 +173,8 @@ void renderer_demo_1(const util::Path &path) { auto pass2 = renderer->add_render_pass({display_obj}, renderer->get_display_target()); - /* Check if all uniform values for uniform inputs have been set */ - if (!obj1_unifs->is_complete() || !obj2_unifs->is_complete() || !obj3_unifs->is_complete() - || !proj_unif->is_complete() || !color_texture_unif->is_complete()) { - log::log(WARN << "Some Uniform values have not been set."); + if (!check_uniform_completeness({obj1, obj2, obj3, proj_update, display_obj})) { + log::log(WARN << "Uniforms not complete."); } /* Data retrieved from the object index texture. */ diff --git a/libopenage/renderer/demo/demo_2.cpp b/libopenage/renderer/demo/demo_2.cpp index c5392f8590..863ea8c979 100644 --- a/libopenage/renderer/demo/demo_2.cpp +++ b/libopenage/renderer/demo/demo_2.cpp @@ -18,6 +18,7 @@ #include "renderer/resources/texture_data.h" #include "renderer/shader_program.h" #include "renderer/texture.h" +#include "renderer/demo/util.h" namespace openage::renderer::tests { @@ -224,9 +225,8 @@ void renderer_demo_2(const util::Path &path) { auto pass2 = renderer->add_render_pass({display_obj}, renderer->get_display_target()); - /* Check if all uniform values for uniform inputs have been set */ - if (!obj1_unifs->is_complete() || !proj_unif->is_complete() || !color_texture_unif->is_complete()) { - log::log(WARN << "Some Uniform values have not been set."); + if (!check_uniform_completeness({proj_update, obj1, display_obj})) { + log::log(WARN << "Uniforms not complete."); } /* Data retrieved from the object index texture. */ diff --git a/libopenage/renderer/demo/demo_4.cpp b/libopenage/renderer/demo/demo_4.cpp index 515de4933b..35c795c427 100644 --- a/libopenage/renderer/demo/demo_4.cpp +++ b/libopenage/renderer/demo/demo_4.cpp @@ -16,6 +16,7 @@ #include "renderer/resources/shader_source.h" #include "renderer/resources/texture_data.h" #include "renderer/shader_program.h" +#include "renderer/demo/util.h" #include "time/clock.h" @@ -165,9 +166,8 @@ void renderer_demo_4(const util::Path &path) { auto pass2 = renderer->add_render_pass({display_obj}, renderer->get_display_target()); - /* Check if all uniform values for uniform inputs have been set */ - if (!obj1_unifs->is_complete() || !proj_unif->is_complete() || !color_texture_unif->is_complete()) { - log::log(WARN << "Some Uniform values have not been set."); + if (!check_uniform_completeness({proj_update, obj1, display_obj})) { + log::log(WARN << "Uniforms not complete."); } window.add_resize_callback([&](size_t w, size_t h, double /*scale*/) { diff --git a/libopenage/renderer/demo/demo_5.cpp b/libopenage/renderer/demo/demo_5.cpp index 1361d274fa..744c1b9fc7 100644 --- a/libopenage/renderer/demo/demo_5.cpp +++ b/libopenage/renderer/demo/demo_5.cpp @@ -17,6 +17,7 @@ #include "renderer/shader_program.h" #include "renderer/uniform_buffer.h" #include "renderer/uniform_input.h" +#include "renderer/demo/util.h" namespace openage::renderer::tests { @@ -134,9 +135,8 @@ void renderer_demo_5(const util::Path &path) { "tex", gltex); - /* Check if all uniform values for uniform inputs have been set */ - if (!transform_unifs->is_complete()) { - log::log(WARN << "Some Uniform values have not been set."); + if (!check_uniform_completeness({terrain_obj})) { + log::log(WARN << "Uniforms not complete."); } // Move around the scene with WASD diff --git a/libopenage/renderer/demo/demo_6.cpp b/libopenage/renderer/demo/demo_6.cpp index 1c80e50150..016d9d41a3 100644 --- a/libopenage/renderer/demo/demo_6.cpp +++ b/libopenage/renderer/demo/demo_6.cpp @@ -23,6 +23,7 @@ #include "renderer/shader_program.h" #include "renderer/texture.h" #include "renderer/uniform_buffer.h" +#include "renderer/demo/util.h" #include "time/clock.h" #include "util/path.h" #include "util/vector.h" @@ -179,10 +180,6 @@ const std::vector RenderManagerDemo6::create_2d_obj() { this->obj_2d_texture, "tile_params", tile_params); - /* Check if all uniform values for uniform inputs have been set */ - if (!animation_2d_unifs->is_complete()) { - log::log(WARN << "Some Uniform values have not been set."); - } auto quad = this->renderer->add_mesh_geometry(resources::MeshData::make_quad()); Renderable animation_2d_obj{ animation_2d_unifs, @@ -202,10 +199,6 @@ const renderer::Renderable RenderManagerDemo6::create_3d_obj() { auto terrain_unifs = this->obj_3d_shader->new_uniform_input( "tex", this->obj_3d_texture); - /* Check if all uniform values for uniform inputs have been set */ - if (!terrain_unifs->is_complete()) { - log::log(WARN << "Some Uniform values have not been set."); - } std::vector terrain_pos{}; terrain_pos.push_back({-25, -25, 0}); @@ -269,9 +262,6 @@ const std::vector RenderManagerDemo6::create_frame_obj() { frame_size, "incol", Eigen::Vector4f{0.0f, 0.0f, 1.0f, 1.0f}); - if (!frame_unifs->is_complete()) { - log::log(WARN << "Some Uniform values have not been set."); - } Renderable frame_obj{ frame_unifs, frame_geometry, @@ -499,6 +489,10 @@ void RenderManagerDemo6::create_render_passes() { this->display_pass = renderer->add_render_pass( {display_obj_3d, display_obj_2d, display_obj_frame}, renderer->get_display_target()); + + if (!check_uniform_completeness({display_obj_3d, display_obj_2d, display_obj_frame})) { + log::log(WARN << "Uniforms not complete."); + } } } // namespace openage::renderer::tests diff --git a/libopenage/renderer/demo/util.cpp b/libopenage/renderer/demo/util.cpp index aafffebf8d..b160e9251c 100644 --- a/libopenage/renderer/demo/util.cpp +++ b/libopenage/renderer/demo/util.cpp @@ -1,8 +1,22 @@ // Copyright 2015-2023 the openage authors. See copying.md for legal info. #include "util.h" +#include "renderer/uniform_input.h" namespace openage::renderer::tests { +bool check_uniform_completeness(const std::vector &renderables) { + bool all_complete = true; + + // Iterate over each renderable object + for (const auto &renderable : renderables) { + if (renderable.uniform && !renderable.uniform->is_complete()) { + all_complete = false; + } + } + + return all_complete; +} + } // namespace openage::renderer::tests diff --git a/libopenage/renderer/demo/util.h b/libopenage/renderer/demo/util.h index 9899867e2b..cfb19f665a 100644 --- a/libopenage/renderer/demo/util.h +++ b/libopenage/renderer/demo/util.h @@ -2,6 +2,8 @@ #pragma once +#include +#include "renderer/renderable.h" namespace openage::renderer::tests { @@ -11,4 +13,12 @@ namespace openage::renderer::tests { opengl::GlContext::check_error(); \ printf("after %s\n", txt); +/** + * Check if all uniform values for the given renderables have been set. + * + * @param renderables The list of renderable objects to check. + * @return true if all uniforms have been set, false otherwise. + */ +bool check_uniform_completeness(const std::vector &renderables); + } // namespace openage::renderer::tests diff --git a/libopenage/renderer/opengl/uniform_input.cpp b/libopenage/renderer/opengl/uniform_input.cpp index 8c021959b7..4a8a4b0496 100644 --- a/libopenage/renderer/opengl/uniform_input.cpp +++ b/libopenage/renderer/opengl/uniform_input.cpp @@ -51,12 +51,12 @@ GlUniformBufferInput::GlUniformBufferInput(const std::shared_ptr } bool GlUniformInput::is_complete() const { - for (const auto& uniform : this->update_offs) { - if (!uniform.used) { - return false; - } - } - return true; + for (const auto& uniform : this->update_offs) { + if (!uniform.used) { + return false; + } + } + return true; } } // namespace openage::renderer::opengl From 1ea8aefd79d41e5adfa0225880720bd76ce89afd Mon Sep 17 00:00:00 2001 From: Alex Zhuohao He Date: Fri, 29 Nov 2024 21:52:27 -0500 Subject: [PATCH 005/152] Minor code cleanup and formatting fixes --- libopenage/renderer/demo/util.cpp | 2 +- libopenage/renderer/demo/util.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libopenage/renderer/demo/util.cpp b/libopenage/renderer/demo/util.cpp index b160e9251c..d94a0d6209 100644 --- a/libopenage/renderer/demo/util.cpp +++ b/libopenage/renderer/demo/util.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #include "util.h" #include "renderer/uniform_input.h" diff --git a/libopenage/renderer/demo/util.h b/libopenage/renderer/demo/util.h index cfb19f665a..a584d94af9 100644 --- a/libopenage/renderer/demo/util.h +++ b/libopenage/renderer/demo/util.h @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #pragma once From 006cdf27cd37129933a2c9f7f25479682f878754 Mon Sep 17 00:00:00 2001 From: Alex Zhuohao He Date: Sat, 30 Nov 2024 11:07:26 -0500 Subject: [PATCH 006/152] format code using clang-format and simplify the check loop --- libopenage/renderer/demo/demo_0.cpp | 4 ++-- libopenage/renderer/demo/demo_1.cpp | 4 ++-- libopenage/renderer/demo/demo_2.cpp | 4 ++-- libopenage/renderer/demo/demo_4.cpp | 4 ++-- libopenage/renderer/demo/demo_5.cpp | 4 ++-- libopenage/renderer/demo/demo_6.cpp | 4 ++-- libopenage/renderer/demo/util.cpp | 9 ++++----- libopenage/renderer/demo/util.h | 1 + libopenage/renderer/opengl/uniform_input.cpp | 2 +- libopenage/renderer/opengl/uniform_input.h | 8 ++++---- 10 files changed, 22 insertions(+), 22 deletions(-) diff --git a/libopenage/renderer/demo/demo_0.cpp b/libopenage/renderer/demo/demo_0.cpp index d1d679c0e4..d027c2d1e0 100644 --- a/libopenage/renderer/demo/demo_0.cpp +++ b/libopenage/renderer/demo/demo_0.cpp @@ -2,6 +2,7 @@ #include "demo_0.h" +#include "renderer/demo/util.h" #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" #include "renderer/render_pass.h" @@ -9,7 +10,6 @@ #include "renderer/resources/mesh_data.h" #include "renderer/resources/shader_source.h" #include "renderer/shader_program.h" -#include "renderer/demo/util.h" namespace openage::renderer::tests { @@ -51,7 +51,7 @@ void renderer_demo_0(const util::Path &path) { false, }; - if (!check_uniform_completeness({display_stuff})) { + if (not check_uniform_completeness({display_stuff})) { log::log(WARN << "Uniforms not complete."); } diff --git a/libopenage/renderer/demo/demo_1.cpp b/libopenage/renderer/demo/demo_1.cpp index 2fb3ce6201..baa471b7b2 100644 --- a/libopenage/renderer/demo/demo_1.cpp +++ b/libopenage/renderer/demo/demo_1.cpp @@ -6,6 +6,7 @@ #include #include +#include "renderer/demo/util.h" #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" #include "renderer/render_pass.h" @@ -15,7 +16,6 @@ #include "renderer/shader_program.h" #include "renderer/texture.h" #include "util/math_constants.h" -#include "renderer/demo/util.h" namespace openage::renderer::tests { @@ -173,7 +173,7 @@ void renderer_demo_1(const util::Path &path) { auto pass2 = renderer->add_render_pass({display_obj}, renderer->get_display_target()); - if (!check_uniform_completeness({obj1, obj2, obj3, proj_update, display_obj})) { + if (not check_uniform_completeness({obj1, obj2, obj3, proj_update, display_obj})) { log::log(WARN << "Uniforms not complete."); } diff --git a/libopenage/renderer/demo/demo_2.cpp b/libopenage/renderer/demo/demo_2.cpp index 863ea8c979..1c92469bb5 100644 --- a/libopenage/renderer/demo/demo_2.cpp +++ b/libopenage/renderer/demo/demo_2.cpp @@ -6,6 +6,7 @@ #include #include +#include "renderer/demo/util.h" #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" #include "renderer/render_pass.h" @@ -18,7 +19,6 @@ #include "renderer/resources/texture_data.h" #include "renderer/shader_program.h" #include "renderer/texture.h" -#include "renderer/demo/util.h" namespace openage::renderer::tests { @@ -225,7 +225,7 @@ void renderer_demo_2(const util::Path &path) { auto pass2 = renderer->add_render_pass({display_obj}, renderer->get_display_target()); - if (!check_uniform_completeness({proj_update, obj1, display_obj})) { + if (not check_uniform_completeness({proj_update, obj1, display_obj})) { log::log(WARN << "Uniforms not complete."); } diff --git a/libopenage/renderer/demo/demo_4.cpp b/libopenage/renderer/demo/demo_4.cpp index 35c795c427..7f4704a38a 100644 --- a/libopenage/renderer/demo/demo_4.cpp +++ b/libopenage/renderer/demo/demo_4.cpp @@ -5,6 +5,7 @@ #include #include +#include "renderer/demo/util.h" #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" #include "renderer/render_pass.h" @@ -16,7 +17,6 @@ #include "renderer/resources/shader_source.h" #include "renderer/resources/texture_data.h" #include "renderer/shader_program.h" -#include "renderer/demo/util.h" #include "time/clock.h" @@ -166,7 +166,7 @@ void renderer_demo_4(const util::Path &path) { auto pass2 = renderer->add_render_pass({display_obj}, renderer->get_display_target()); - if (!check_uniform_completeness({proj_update, obj1, display_obj})) { + if (not check_uniform_completeness({proj_update, obj1, display_obj})) { log::log(WARN << "Uniforms not complete."); } diff --git a/libopenage/renderer/demo/demo_5.cpp b/libopenage/renderer/demo/demo_5.cpp index 744c1b9fc7..34e8f4db22 100644 --- a/libopenage/renderer/demo/demo_5.cpp +++ b/libopenage/renderer/demo/demo_5.cpp @@ -7,6 +7,7 @@ #include #include "renderer/camera/camera.h" +#include "renderer/demo/util.h" #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" #include "renderer/render_pass.h" @@ -17,7 +18,6 @@ #include "renderer/shader_program.h" #include "renderer/uniform_buffer.h" #include "renderer/uniform_input.h" -#include "renderer/demo/util.h" namespace openage::renderer::tests { @@ -135,7 +135,7 @@ void renderer_demo_5(const util::Path &path) { "tex", gltex); - if (!check_uniform_completeness({terrain_obj})) { + if (not check_uniform_completeness({terrain_obj})) { log::log(WARN << "Uniforms not complete."); } diff --git a/libopenage/renderer/demo/demo_6.cpp b/libopenage/renderer/demo/demo_6.cpp index 016d9d41a3..e8beaafc6c 100644 --- a/libopenage/renderer/demo/demo_6.cpp +++ b/libopenage/renderer/demo/demo_6.cpp @@ -9,6 +9,7 @@ #include "renderer/camera/camera.h" #include "renderer/camera/frustum_2d.h" #include "renderer/camera/frustum_3d.h" +#include "renderer/demo/util.h" #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" #include "renderer/render_pass.h" @@ -23,7 +24,6 @@ #include "renderer/shader_program.h" #include "renderer/texture.h" #include "renderer/uniform_buffer.h" -#include "renderer/demo/util.h" #include "time/clock.h" #include "util/path.h" #include "util/vector.h" @@ -490,7 +490,7 @@ void RenderManagerDemo6::create_render_passes() { {display_obj_3d, display_obj_2d, display_obj_frame}, renderer->get_display_target()); - if (!check_uniform_completeness({display_obj_3d, display_obj_2d, display_obj_frame})) { + if (not check_uniform_completeness({display_obj_3d, display_obj_2d, display_obj_frame})) { log::log(WARN << "Uniforms not complete."); } } diff --git a/libopenage/renderer/demo/util.cpp b/libopenage/renderer/demo/util.cpp index d94a0d6209..04f7ad6b7d 100644 --- a/libopenage/renderer/demo/util.cpp +++ b/libopenage/renderer/demo/util.cpp @@ -1,22 +1,21 @@ // Copyright 2015-2024 the openage authors. See copying.md for legal info. #include "util.h" + #include "renderer/uniform_input.h" namespace openage::renderer::tests { bool check_uniform_completeness(const std::vector &renderables) { - bool all_complete = true; - // Iterate over each renderable object for (const auto &renderable : renderables) { - if (renderable.uniform && !renderable.uniform->is_complete()) { - all_complete = false; + if (renderable.uniform && not renderable.uniform->is_complete()) { + return false; } } - return all_complete; + return true; } } // namespace openage::renderer::tests diff --git a/libopenage/renderer/demo/util.h b/libopenage/renderer/demo/util.h index a584d94af9..8d8130a306 100644 --- a/libopenage/renderer/demo/util.h +++ b/libopenage/renderer/demo/util.h @@ -3,6 +3,7 @@ #pragma once #include + #include "renderer/renderable.h" namespace openage::renderer::tests { diff --git a/libopenage/renderer/opengl/uniform_input.cpp b/libopenage/renderer/opengl/uniform_input.cpp index 4a8a4b0496..2be81e287f 100644 --- a/libopenage/renderer/opengl/uniform_input.cpp +++ b/libopenage/renderer/opengl/uniform_input.cpp @@ -51,7 +51,7 @@ GlUniformBufferInput::GlUniformBufferInput(const std::shared_ptr } bool GlUniformInput::is_complete() const { - for (const auto& uniform : this->update_offs) { + for (const auto &uniform : this->update_offs) { if (!uniform.used) { return false; } diff --git a/libopenage/renderer/opengl/uniform_input.h b/libopenage/renderer/opengl/uniform_input.h index d635836b05..c46c235414 100644 --- a/libopenage/renderer/opengl/uniform_input.h +++ b/libopenage/renderer/opengl/uniform_input.h @@ -35,10 +35,10 @@ class GlUniformInput final : public UniformInput { public: GlUniformInput(const std::shared_ptr &prog); - /** - * Check if all uniforms have been set. - */ - bool is_complete() const override; + /** + * Check if all uniforms have been set. + */ + bool is_complete() const override; /** * Store the IDs of the uniforms from the shader set by this uniform input. From c15fe6fd9af25d4188558808f5c86e5d2fb875c3 Mon Sep 17 00:00:00 2001 From: Alex Zhuohao He Date: Sat, 30 Nov 2024 14:26:10 -0500 Subject: [PATCH 007/152] update `!` to `not` --- libopenage/renderer/demo/demo_1.cpp | 2 +- libopenage/renderer/demo/demo_2.cpp | 2 +- libopenage/renderer/opengl/uniform_input.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libopenage/renderer/demo/demo_1.cpp b/libopenage/renderer/demo/demo_1.cpp index baa471b7b2..c28b7dc296 100644 --- a/libopenage/renderer/demo/demo_1.cpp +++ b/libopenage/renderer/demo/demo_1.cpp @@ -193,7 +193,7 @@ void renderer_demo_1(const util::Path &path) { ssize_t y = qpos.y(); log::log(INFO << "Clicked at location (" << x << ", " << y << ")"); - if (!texture_data_valid) { + if (not texture_data_valid) { id_texture_data = id_texture->into_data(); texture_data_valid = true; } diff --git a/libopenage/renderer/demo/demo_2.cpp b/libopenage/renderer/demo/demo_2.cpp index 1c92469bb5..c2a223d51b 100644 --- a/libopenage/renderer/demo/demo_2.cpp +++ b/libopenage/renderer/demo/demo_2.cpp @@ -248,7 +248,7 @@ void renderer_demo_2(const util::Path &path) { ssize_t y = qpos.y(); log::log(INFO << "Clicked at location (" << x << ", " << y << ")"); - if (!texture_data_valid) { + if (not texture_data_valid) { id_texture_data = id_texture->into_data(); texture_data_valid = true; } diff --git a/libopenage/renderer/opengl/uniform_input.cpp b/libopenage/renderer/opengl/uniform_input.cpp index 2be81e287f..e6caed4ca8 100644 --- a/libopenage/renderer/opengl/uniform_input.cpp +++ b/libopenage/renderer/opengl/uniform_input.cpp @@ -52,7 +52,7 @@ GlUniformBufferInput::GlUniformBufferInput(const std::shared_ptr bool GlUniformInput::is_complete() const { for (const auto &uniform : this->update_offs) { - if (!uniform.used) { + if (not uniform.used) { return false; } } From 89719a9e882b9189375bebcaa02c21ba2b183ec7 Mon Sep 17 00:00:00 2001 From: Christoph Heine Date: Tue, 3 Dec 2024 04:38:23 +0100 Subject: [PATCH 008/152] Change '&&' to 'and'. --- libopenage/renderer/demo/util.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libopenage/renderer/demo/util.cpp b/libopenage/renderer/demo/util.cpp index 04f7ad6b7d..0f39691cdb 100644 --- a/libopenage/renderer/demo/util.cpp +++ b/libopenage/renderer/demo/util.cpp @@ -10,7 +10,7 @@ namespace openage::renderer::tests { bool check_uniform_completeness(const std::vector &renderables) { // Iterate over each renderable object for (const auto &renderable : renderables) { - if (renderable.uniform && not renderable.uniform->is_complete()) { + if (renderable.uniform and not renderable.uniform->is_complete()) { return false; } } From 8f396007b88a3b3f883fd9d877af883e580b9ad1 Mon Sep 17 00:00:00 2001 From: jere8184 Date: Mon, 2 Dec 2024 23:55:27 +0000 Subject: [PATCH 009/152] Update curves.md --- doc/code/curves.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/code/curves.md b/doc/code/curves.md index 376c4c938c..ff4bbb248d 100644 --- a/doc/code/curves.md +++ b/doc/code/curves.md @@ -42,7 +42,7 @@ directly invalidating the state, making curves more reliable in async scenarios resolving dependencies for keyframes in the past can still be challenging). The usage of curves has a few downsides though. They are less space efficient due to the -keyframe storage, interpolation are more costly more costly than incremental changes, and +keyframe storage, interpolation are more costly than incremental changes, and their integration is more complex than the usage of simpler data structures. However, in situations where operations are predictable, long-lasting, and easy to calculate - which is the case for most RTS games - the positives may outweigh the downsides. From b0346085291c180dcfb4fa3123703ba85acb3827 Mon Sep 17 00:00:00 2001 From: Alex Zhuohao He Date: Tue, 3 Dec 2024 11:45:49 -0500 Subject: [PATCH 010/152] update .py and .pyx by adding new window arguments --- openage/game/main.py | 21 +++++++++++++++++++++ openage/game/main_cpp.pyx | 8 +++++++- openage/main/main.py | 21 +++++++++++++++++++++ openage/main/main_cpp.pyx | 8 +++++++- 4 files changed, 56 insertions(+), 2 deletions(-) diff --git a/openage/game/main.py b/openage/game/main.py index 0fdf007259..44b74ee058 100644 --- a/openage/game/main.py +++ b/openage/game/main.py @@ -36,6 +36,19 @@ def init_subparser(cli: ArgumentParser) -> None: help="Check if the assets are up to date" ) + cli.add_argument( + "--window-size", nargs=2, type=int, default=[1024, 768], + metavar=('WIDTH', 'HEIGHT'), + help="initial window size in pixels, e.g., --window-size 1024 768") + + cli.add_argument( + "--vsync", action='store_true', + help="enable vertical synchronization") + + cli.add_argument( + "--window-mode", choices=["fullscreen", "borderless", "windowed"], default="windowed", + help="set the window mode: fullscreen, borderless, or windowed (default)") + def main(args, error): """ @@ -98,5 +111,13 @@ def main(args, error): # encode modpacks as bytes for the C++ interface args.modpacks = [modpack.encode('utf-8') for modpack in args.modpacks] + # Pass window parameters to engine + args.window_args = { + "width": args.window_size[0], + "height": args.window_size[1], + "vsync": args.vsync, + "window_mode": args.window_mode, + } + # start the game, continue in main_cpp.pyx! return run_game(args, root) diff --git a/openage/game/main_cpp.pyx b/openage/game/main_cpp.pyx index e7697b0ddb..d37545f87c 100644 --- a/openage/game/main_cpp.pyx +++ b/openage/game/main_cpp.pyx @@ -1,4 +1,4 @@ -# Copyright 2015-2023 the openage authors. See copying.md for legal info. +# Copyright 2015-2024 the openage authors. See copying.md for legal info. from cpython.ref cimport PyObject from libcpp.string cimport string @@ -37,6 +37,12 @@ def run_game(args, root_path): else: args_cpp.mods = vector[string]() + # window + args_cpp.window_args.width = args.window_args["width"] + args_cpp.window_args.height = args.window_args["height"] + args_cpp.window_args.vsync = args.window_args["vsync"] + args_cpp.window_args.mode = args.window_args["window_mode"].encode('utf-8') + # run the game! with nogil: result = run_game_cpp(args_cpp) diff --git a/openage/main/main.py b/openage/main/main.py index fbeb527ee4..5fa628c3e5 100644 --- a/openage/main/main.py +++ b/openage/main/main.py @@ -28,6 +28,19 @@ def init_subparser(cli: ArgumentParser): "--modpacks", nargs="+", type=str, help="list of modpacks to load") + cli.add_argument( + "--window-size", nargs=2, type=int, default=[1024, 768], + metavar=('WIDTH', 'HEIGHT'), + help="initial window size in pixels, e.g., --window-size 1024 768") + + cli.add_argument( + "--vsync", action='store_true', + help="enable vertical synchronization") + + cli.add_argument( + "--window-mode", choices=["fullscreen", "borderless", "windowed"], default="windowed", + help="set the window mode: fullscreen, borderless, or windowed (default)") + def main(args, error): """ @@ -106,5 +119,13 @@ def main(args, error): else: args.modpacks = [query_modpack(list(available_modpacks.keys())).encode("utf-8")] + # Pass window parameters to engine + args.window_args = { + "width": args.window_size[0], + "height": args.window_size[1], + "vsync": args.vsync, + "window_mode": args.window_mode, + } + # start the game, continue in main_cpp.pyx! return run_game(args, root) diff --git a/openage/main/main_cpp.pyx b/openage/main/main_cpp.pyx index e7697b0ddb..d37545f87c 100644 --- a/openage/main/main_cpp.pyx +++ b/openage/main/main_cpp.pyx @@ -1,4 +1,4 @@ -# Copyright 2015-2023 the openage authors. See copying.md for legal info. +# Copyright 2015-2024 the openage authors. See copying.md for legal info. from cpython.ref cimport PyObject from libcpp.string cimport string @@ -37,6 +37,12 @@ def run_game(args, root_path): else: args_cpp.mods = vector[string]() + # window + args_cpp.window_args.width = args.window_args["width"] + args_cpp.window_args.height = args.window_args["height"] + args_cpp.window_args.vsync = args.window_args["vsync"] + args_cpp.window_args.mode = args.window_args["window_mode"].encode('utf-8') + # run the game! with nogil: result = run_game_cpp(args_cpp) From a4335bef262429aa43fb75e0af0d9daee2bb47a0 Mon Sep 17 00:00:00 2001 From: Alex Zhuohao He Date: Wed, 4 Dec 2024 16:24:20 -0500 Subject: [PATCH 011/152] update main, engine, window and presenter to get window arguments passed from py --- libopenage/engine/engine.cpp | 7 ++++--- libopenage/engine/engine.h | 6 +++++- libopenage/main.cpp | 22 ++++++++++++++++++++-- libopenage/main.h | 22 +++++++++++++++++++++- libopenage/presenter/presenter.cpp | 13 ++++--------- libopenage/presenter/presenter.h | 8 ++++++-- libopenage/renderer/opengl/window.cpp | 15 +++++++++++++++ libopenage/renderer/window.h | 11 +++++++++++ 8 files changed, 86 insertions(+), 18 deletions(-) diff --git a/libopenage/engine/engine.cpp b/libopenage/engine/engine.cpp index fa2c251b4d..dc787ebcf1 100644 --- a/libopenage/engine/engine.cpp +++ b/libopenage/engine/engine.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #include "engine.h" @@ -16,7 +16,8 @@ namespace openage::engine { Engine::Engine(mode mode, const util::Path &root_dir, const std::vector &mods, - bool debug_graphics) : + bool debug_graphics, + const renderer::window_settings &window_settings) : running{true}, run_mode{mode}, root_dir{root_dir}, @@ -56,7 +57,7 @@ Engine::Engine(mode mode, // if presenter is used, run it in a separate thread if (this->run_mode == mode::FULL) { this->threads.emplace_back([&, debug_graphics]() { - this->presenter->run(debug_graphics); + this->presenter->run(debug_graphics, window_settings); // Make sure that the presenter gets destructed in the same thread // otherwise OpenGL complains about missing contexts diff --git a/libopenage/engine/engine.h b/libopenage/engine/engine.h index 1fe4298061..d324fe4fda 100644 --- a/libopenage/engine/engine.h +++ b/libopenage/engine/engine.h @@ -9,6 +9,8 @@ #include "util/path.h" +#include + // TODO: Remove custom jthread definition when clang/libc++ finally supports it #if __llvm__ #if !__cpp_lib_jthread @@ -72,11 +74,13 @@ class Engine { * @param root_dir openage root directory. * @param mods The mods to load. * @param debug_graphics If true, enable OpenGL debug logging. + * @param window_settings window display setting */ Engine(mode mode, const util::Path &root_dir, const std::vector &mods, - bool debug_graphics = false); + bool debug_graphics = false, + const renderer::window_settings &window_settings = {}); // engine should not be copied or moved Engine(const Engine &) = delete; diff --git a/libopenage/main.cpp b/libopenage/main.cpp index e7794be40c..f8e6b45f3f 100644 --- a/libopenage/main.cpp +++ b/libopenage/main.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #include "main.h" @@ -31,7 +31,25 @@ int run_game(const main_arguments &args) { run_mode = openage::engine::Engine::mode::HEADLESS; } - openage::engine::Engine engine{run_mode, args.root_path, args.mods, args.gl_debug}; + // convert window arguments to window settings + renderer::window_settings win_settings = {}; + win_settings.width = args.window_args.width; + win_settings.height = args.window_args.height; + win_settings.vsync = args.window_args.vsync; + + renderer::window_mode wmode; + if (args.window_args.mode == "fullscreen") { + wmode = renderer::window_mode::FULLSCREEN; + } + else if (args.window_args.mode == "borderless") { + wmode = renderer::window_mode::BORDERLESS; + } + else { + wmode = renderer::window_mode::WINDOWED; + } + win_settings.mode = wmode; + + openage::engine::Engine engine{run_mode, args.root_path, args.mods, args.gl_debug, win_settings}; engine.loop(); diff --git a/libopenage/main.h b/libopenage/main.h index 0e60c386cc..e99b374642 100644 --- a/libopenage/main.h +++ b/libopenage/main.h @@ -1,4 +1,4 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -16,6 +16,24 @@ namespace openage { +/** + * Window parameters struct. + * + * pxd: + * + * cppclass window_arguments: + * int width + * int height + * bool vsync + * string mode + */ +struct window_arguments { + int width; + int height; + bool vsync; + std::string mode; +}; + /** * Used for passing arguments to run_game. * @@ -26,12 +44,14 @@ namespace openage { * bool gl_debug * bool headless * vector[string] mods + * window_arguments window_args */ struct main_arguments { util::Path root_path; bool gl_debug; bool headless; std::vector mods; + window_arguments window_args; }; diff --git a/libopenage/presenter/presenter.cpp b/libopenage/presenter/presenter.cpp index a37362be95..6d77a72710 100644 --- a/libopenage/presenter/presenter.cpp +++ b/libopenage/presenter/presenter.cpp @@ -32,7 +32,6 @@ #include "renderer/stages/skybox/render_stage.h" #include "renderer/stages/terrain/render_stage.h" #include "renderer/stages/world/render_stage.h" -#include "renderer/window.h" #include "time/time_loop.h" #include "util/path.h" @@ -48,10 +47,10 @@ Presenter::Presenter(const util::Path &root_dir, time_loop{time_loop} {} -void Presenter::run(bool debug_graphics) { +void Presenter::run(bool debug_graphics, const renderer::window_settings &window_settings) { log::log(INFO << "Presenter: Launching subsystems..."); - this->init_graphics(debug_graphics); + this->init_graphics(debug_graphics, window_settings); this->init_input(); @@ -93,18 +92,14 @@ std::shared_ptr Presenter::init_window_system() { return std::make_shared(); } -void Presenter::init_graphics(bool debug) { +void Presenter::init_graphics(bool debug, const renderer::window_settings &window_settings) { log::log(INFO << "Presenter: Initializing graphics subsystems..."); // Start up rendering framework this->gui_app = this->init_window_system(); // Window and renderer - renderer::window_settings settings; - settings.width = 1024; - settings.height = 768; - settings.debug = debug; - this->window = renderer::Window::create("openage presenter test", settings); + this->window = renderer::Window::create("openage presenter test", window_settings); this->renderer = this->window->make_renderer(); // Asset mangement diff --git a/libopenage/presenter/presenter.h b/libopenage/presenter/presenter.h index b43d4fc60f..6f45800a32 100644 --- a/libopenage/presenter/presenter.h +++ b/libopenage/presenter/presenter.h @@ -7,6 +7,9 @@ #include "util/path.h" +#include + + namespace qtgui { class GuiApplication; } @@ -88,8 +91,9 @@ class Presenter { * Start the presenter and initialize subsystems. * * @param debug_graphics If true, enable OpenGL debug logging. + * @param window_settings window display setting */ - void run(bool debug_graphics = false); + void run(bool debug_graphics = false, const renderer::window_settings &window_settings = {}); /** * Set the game simulation controlled by this presenter. @@ -120,7 +124,7 @@ class Presenter { * - main renderer * - component renderers (Terrain, Game Entities, GUI) */ - void init_graphics(bool debug = false); + void init_graphics(bool debug = false, const renderer::window_settings &window_settings = {}); /** * Initialize the GUI. diff --git a/libopenage/renderer/opengl/window.cpp b/libopenage/renderer/opengl/window.cpp index 5f75717540..d832c49337 100644 --- a/libopenage/renderer/opengl/window.cpp +++ b/libopenage/renderer/opengl/window.cpp @@ -57,6 +57,21 @@ GlWindow::GlWindow(const std::string &title, this->window->setFormat(format); this->window->create(); + // set display mode + switch (settings.mode) { + case window_mode::FULLSCREEN: + this->window->showFullScreen(); + break; + case window_mode::BORDERLESS: + this->window->setFlags(this->window->flags() | Qt::FramelessWindowHint); + this->window->show(); + break; + case window_mode::WINDOWED: + default: + this->window->showNormal(); + break; + } + this->context = std::make_shared(this->window, settings.debug); if (not this->context->get_raw_context()->isValid()) { throw Error{MSG(err) << "Failed to create Qt OpenGL context."}; diff --git a/libopenage/renderer/window.h b/libopenage/renderer/window.h index 71960e2d17..6f34aa58e3 100644 --- a/libopenage/renderer/window.h +++ b/libopenage/renderer/window.h @@ -21,6 +21,15 @@ namespace openage::renderer { class WindowEventHandler; +/** + * Modes for window display. + */ +enum class window_mode { + FULLSCREEN, + BORDERLESS, + WINDOWED +}; + /** * Settings for creating a window. */ @@ -35,6 +44,8 @@ struct window_settings { bool vsync = true; // If true, enable debug logging for the selected backend. bool debug = false; + // Display mode for the window. + window_mode mode = window_mode::FULLSCREEN; }; From 87da14ecafb6f043c62e230b00900251b763e725 Mon Sep 17 00:00:00 2001 From: Alex Zhuohao He Date: Fri, 6 Dec 2024 10:20:13 -0500 Subject: [PATCH 012/152] Address review feedback: fix formatting, remove redundant debug arg, and change display mode setting method. --- libopenage/engine/engine.cpp | 5 ++--- libopenage/engine/engine.h | 6 ++---- libopenage/main.cpp | 8 ++++++-- libopenage/presenter/presenter.cpp | 6 +++--- libopenage/presenter/presenter.h | 10 ++++------ libopenage/renderer/opengl/window.cpp | 14 ++++++++------ libopenage/renderer/window.h | 2 +- openage/game/main.py | 6 +++--- openage/main/main.py | 6 +++--- 9 files changed, 32 insertions(+), 31 deletions(-) diff --git a/libopenage/engine/engine.cpp b/libopenage/engine/engine.cpp index dc787ebcf1..02c0dd87d3 100644 --- a/libopenage/engine/engine.cpp +++ b/libopenage/engine/engine.cpp @@ -16,7 +16,6 @@ namespace openage::engine { Engine::Engine(mode mode, const util::Path &root_dir, const std::vector &mods, - bool debug_graphics, const renderer::window_settings &window_settings) : running{true}, run_mode{mode}, @@ -56,8 +55,8 @@ Engine::Engine(mode mode, // if presenter is used, run it in a separate thread if (this->run_mode == mode::FULL) { - this->threads.emplace_back([&, debug_graphics]() { - this->presenter->run(debug_graphics, window_settings); + this->threads.emplace_back([&]() { + this->presenter->run(window_settings); // Make sure that the presenter gets destructed in the same thread // otherwise OpenGL complains about missing contexts diff --git a/libopenage/engine/engine.h b/libopenage/engine/engine.h index d324fe4fda..0231054628 100644 --- a/libopenage/engine/engine.h +++ b/libopenage/engine/engine.h @@ -7,9 +7,9 @@ #include #include +#include "renderer/window.h" #include "util/path.h" -#include // TODO: Remove custom jthread definition when clang/libc++ finally supports it #if __llvm__ @@ -73,13 +73,11 @@ class Engine { * @param mode The run mode to use. * @param root_dir openage root directory. * @param mods The mods to load. - * @param debug_graphics If true, enable OpenGL debug logging. - * @param window_settings window display setting + * @param window_settings The settings to customize the display window (e.g. size, display mode, vsync). */ Engine(mode mode, const util::Path &root_dir, const std::vector &mods, - bool debug_graphics = false, const renderer::window_settings &window_settings = {}); // engine should not be copied or moved diff --git a/libopenage/main.cpp b/libopenage/main.cpp index f8e6b45f3f..2147cbe4e0 100644 --- a/libopenage/main.cpp +++ b/libopenage/main.cpp @@ -44,12 +44,16 @@ int run_game(const main_arguments &args) { else if (args.window_args.mode == "borderless") { wmode = renderer::window_mode::BORDERLESS; } - else { + else if (args.window_args.mode == "windowed") { wmode = renderer::window_mode::WINDOWED; } + else { + throw Error(MSG(err) << "Invalid window mode: " << args.window_args.mode); + } win_settings.mode = wmode; + win_settings.debug = args.gl_debug; - openage::engine::Engine engine{run_mode, args.root_path, args.mods, args.gl_debug, win_settings}; + openage::engine::Engine engine{run_mode, args.root_path, args.mods, win_settings}; engine.loop(); diff --git a/libopenage/presenter/presenter.cpp b/libopenage/presenter/presenter.cpp index 6d77a72710..6db1e23136 100644 --- a/libopenage/presenter/presenter.cpp +++ b/libopenage/presenter/presenter.cpp @@ -47,10 +47,10 @@ Presenter::Presenter(const util::Path &root_dir, time_loop{time_loop} {} -void Presenter::run(bool debug_graphics, const renderer::window_settings &window_settings) { +void Presenter::run(const renderer::window_settings &window_settings) { log::log(INFO << "Presenter: Launching subsystems..."); - this->init_graphics(debug_graphics, window_settings); + this->init_graphics(window_settings); this->init_input(); @@ -92,7 +92,7 @@ std::shared_ptr Presenter::init_window_system() { return std::make_shared(); } -void Presenter::init_graphics(bool debug, const renderer::window_settings &window_settings) { +void Presenter::init_graphics(const renderer::window_settings &window_settings) { log::log(INFO << "Presenter: Initializing graphics subsystems..."); // Start up rendering framework diff --git a/libopenage/presenter/presenter.h b/libopenage/presenter/presenter.h index 6f45800a32..a4b5dec68e 100644 --- a/libopenage/presenter/presenter.h +++ b/libopenage/presenter/presenter.h @@ -5,10 +5,9 @@ #include #include +#include "renderer/window.h" #include "util/path.h" -#include - namespace qtgui { class GuiApplication; @@ -90,10 +89,9 @@ class Presenter { /** * Start the presenter and initialize subsystems. * - * @param debug_graphics If true, enable OpenGL debug logging. - * @param window_settings window display setting + * @param window_settings The settings to customize the display window (e.g. size, display mode, vsync). */ - void run(bool debug_graphics = false, const renderer::window_settings &window_settings = {}); + void run(const renderer::window_settings &window_settings = {}); /** * Set the game simulation controlled by this presenter. @@ -124,7 +122,7 @@ class Presenter { * - main renderer * - component renderers (Terrain, Game Entities, GUI) */ - void init_graphics(bool debug = false, const renderer::window_settings &window_settings = {}); + void init_graphics(const renderer::window_settings &window_settings = {}); /** * Initialize the GUI. diff --git a/libopenage/renderer/opengl/window.cpp b/libopenage/renderer/opengl/window.cpp index d832c49337..46772e14d9 100644 --- a/libopenage/renderer/opengl/window.cpp +++ b/libopenage/renderer/opengl/window.cpp @@ -58,18 +58,20 @@ GlWindow::GlWindow(const std::string &title, this->window->create(); // set display mode + // Reset to a known state + this->window->setWindowState(Qt::WindowNoState); switch (settings.mode) { - case window_mode::FULLSCREEN: - this->window->showFullScreen(); + case window_mode::WINDOWED: + this->window->setFlags(this->window->flags() & ~Qt::FramelessWindowHint); break; case window_mode::BORDERLESS: this->window->setFlags(this->window->flags() | Qt::FramelessWindowHint); - this->window->show(); break; - case window_mode::WINDOWED: - default: - this->window->showNormal(); + case window_mode::FULLSCREEN: + this->window->setWindowState(Qt::WindowFullScreen); break; + default: + throw Error{MSG(err) << "Invalid window mode."}; } this->context = std::make_shared(this->window, settings.debug); diff --git a/libopenage/renderer/window.h b/libopenage/renderer/window.h index 6f34aa58e3..e3465bef4d 100644 --- a/libopenage/renderer/window.h +++ b/libopenage/renderer/window.h @@ -45,7 +45,7 @@ struct window_settings { // If true, enable debug logging for the selected backend. bool debug = false; // Display mode for the window. - window_mode mode = window_mode::FULLSCREEN; + window_mode mode = window_mode::WINDOWED; }; diff --git a/openage/game/main.py b/openage/game/main.py index 44b74ee058..1ad1378426 100644 --- a/openage/game/main.py +++ b/openage/game/main.py @@ -39,15 +39,15 @@ def init_subparser(cli: ArgumentParser) -> None: cli.add_argument( "--window-size", nargs=2, type=int, default=[1024, 768], metavar=('WIDTH', 'HEIGHT'), - help="initial window size in pixels, e.g., --window-size 1024 768") + help="Initial window size in pixels") cli.add_argument( "--vsync", action='store_true', - help="enable vertical synchronization") + help="Enable vertical synchronization") cli.add_argument( "--window-mode", choices=["fullscreen", "borderless", "windowed"], default="windowed", - help="set the window mode: fullscreen, borderless, or windowed (default)") + help="Set the window mode") def main(args, error): diff --git a/openage/main/main.py b/openage/main/main.py index 5fa628c3e5..05266ec744 100644 --- a/openage/main/main.py +++ b/openage/main/main.py @@ -31,15 +31,15 @@ def init_subparser(cli: ArgumentParser): cli.add_argument( "--window-size", nargs=2, type=int, default=[1024, 768], metavar=('WIDTH', 'HEIGHT'), - help="initial window size in pixels, e.g., --window-size 1024 768") + help="Initial window size in pixels") cli.add_argument( "--vsync", action='store_true', - help="enable vertical synchronization") + help="Enable vertical synchronization") cli.add_argument( "--window-mode", choices=["fullscreen", "borderless", "windowed"], default="windowed", - help="set the window mode: fullscreen, borderless, or windowed (default)") + help="Set the window mode") def main(args, error): From 8094dc6ab19affc544200804f65feb0a9e25c0ae Mon Sep 17 00:00:00 2001 From: Alex Zhuohao He Date: Sat, 7 Dec 2024 11:39:32 -0500 Subject: [PATCH 013/152] remove redundant window.setFlags() and pass window_settings to presenter by value --- libopenage/presenter/presenter.cpp | 2 +- libopenage/presenter/presenter.h | 2 +- libopenage/renderer/opengl/window.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libopenage/presenter/presenter.cpp b/libopenage/presenter/presenter.cpp index 6db1e23136..9775b62d9b 100644 --- a/libopenage/presenter/presenter.cpp +++ b/libopenage/presenter/presenter.cpp @@ -47,7 +47,7 @@ Presenter::Presenter(const util::Path &root_dir, time_loop{time_loop} {} -void Presenter::run(const renderer::window_settings &window_settings) { +void Presenter::run(const renderer::window_settings window_settings) { log::log(INFO << "Presenter: Launching subsystems..."); this->init_graphics(window_settings); diff --git a/libopenage/presenter/presenter.h b/libopenage/presenter/presenter.h index a4b5dec68e..2059daeed0 100644 --- a/libopenage/presenter/presenter.h +++ b/libopenage/presenter/presenter.h @@ -91,7 +91,7 @@ class Presenter { * * @param window_settings The settings to customize the display window (e.g. size, display mode, vsync). */ - void run(const renderer::window_settings &window_settings = {}); + void run(const renderer::window_settings window_settings = {}); /** * Set the game simulation controlled by this presenter. diff --git a/libopenage/renderer/opengl/window.cpp b/libopenage/renderer/opengl/window.cpp index 46772e14d9..548575dd2d 100644 --- a/libopenage/renderer/opengl/window.cpp +++ b/libopenage/renderer/opengl/window.cpp @@ -62,7 +62,7 @@ GlWindow::GlWindow(const std::string &title, this->window->setWindowState(Qt::WindowNoState); switch (settings.mode) { case window_mode::WINDOWED: - this->window->setFlags(this->window->flags() & ~Qt::FramelessWindowHint); + // nothing to do because it's the default break; case window_mode::BORDERLESS: this->window->setFlags(this->window->flags() | Qt::FramelessWindowHint); From e04eafa25fa1f1f48351f459fbca4ea7d2d60693 Mon Sep 17 00:00:00 2001 From: fabiobarkoski Date: Wed, 7 Aug 2024 00:22:52 -0300 Subject: [PATCH 014/152] convert: Speed up converter texture packing cythonized more the texture packing, also changed the list passed to factor(), before was a list of frame objects, now a list of only width, height and index of the frames. --- .../processor/export/texture_merge.pyx | 22 ++--- .../convert/service/export/png/binpack.pxd | 34 +++++--- .../convert/service/export/png/binpack.pyx | 87 +++++++++---------- 3 files changed, 77 insertions(+), 66 deletions(-) diff --git a/openage/convert/processor/export/texture_merge.pyx b/openage/convert/processor/export/texture_merge.pyx index f726d0af02..51ce4830ed 100644 --- a/openage/convert/processor/export/texture_merge.pyx +++ b/openage/convert/processor/export/texture_merge.pyx @@ -1,4 +1,4 @@ -# Copyright 2014-2023 the openage authors. See copying.md for legal info. +# Copyright 2014-2024 the openage authors. See copying.md for legal info. # # cython: infer_types=True # pylint: disable=too-many-locals @@ -9,15 +9,16 @@ a terrain texture. import numpy from enum import Enum +cimport cython +cimport numpy + from ....log import spam +from ...service.export.png.binpack cimport block from ...entity_object.export.texture import TextureImage -from ...service.export.png.binpack cimport Packer, DeterministicPacker, RowPacker, ColumnPacker, BinaryTreePacker, BestPacker +from ...service.export.png.binpack cimport DeterministicPacker, RowPacker, ColumnPacker, BinaryTreePacker, BestPacker from ...value_object.read.media.hardcoded.texture import (MAX_TEXTURE_DIMENSION, MARGIN, TERRAIN_ASPECT_RATIO) -cimport cython -cimport numpy - class PackerType(Enum): """ Packer types @@ -57,6 +58,7 @@ cdef void cmerge_frames(texture, packer_type=PackerType.BINPACK, cache=None) exc :type cache: list """ cdef list frames = texture.frames + cdef list blocks = [block(idx, frame.width, frame.height) for idx, frame in enumerate(frames)] if len(frames) == 0: raise ValueError("cannot create texture with empty input frame list") @@ -64,7 +66,7 @@ cdef void cmerge_frames(texture, packer_type=PackerType.BINPACK, cache=None) exc cdef BestPacker packer if cache: - packer = BestPacker([DeterministicPacker(margin=MARGIN,hints=cache)]) + packer = BestPacker([DeterministicPacker(margin=MARGIN, hints=cache)]) else: if packer_type == PackerType.ROW: @@ -81,7 +83,7 @@ cdef void cmerge_frames(texture, packer_type=PackerType.BINPACK, cache=None) exc RowPacker(margin=MARGIN), ColumnPacker(margin=MARGIN)]) - packer.pack(frames) + packer.pack(blocks) cdef int width = packer.width() cdef int height = packer.height() @@ -106,11 +108,11 @@ cdef void cmerge_frames(texture, packer_type=PackerType.BINPACK, cache=None) exc cdef int sub_h cdef list drawn_frames_meta = [] - for sub_frame in frames: + for index, sub_frame in enumerate(frames): sub_w = sub_frame.width sub_h = sub_frame.height - pos_x, pos_y = packer.pos(sub_frame) + pos_x, pos_y = packer.pos(index) spam("drawing frame %03d on atlas at %d x %d...", len(drawn_frames_meta), pos_x, pos_y) @@ -143,4 +145,4 @@ cdef void cmerge_frames(texture, packer_type=PackerType.BINPACK, cache=None) exc if isinstance(packer, BestPacker): # Only generate these values if no custom packer was used # TODO: It might make sense to do it anyway for debugging purposes - texture.best_packer_hints = packer.get_mapping_hints(frames) + texture.best_packer_hints = packer.get_mapping_hints(blocks) diff --git a/openage/convert/service/export/png/binpack.pxd b/openage/convert/service/export/png/binpack.pxd index e13725a367..5560441143 100644 --- a/openage/convert/service/export/png/binpack.pxd +++ b/openage/convert/service/export/png/binpack.pxd @@ -1,15 +1,19 @@ -# Copyright 2021-2021 the openage authors. See copying.md for legal info. +# Copyright 2021-2024 the openage authors. See copying.md for legal info. -from libcpp.memory cimport shared_ptr +from libcpp.unordered_map cimport unordered_map + +ctypedef (unsigned int, unsigned int, (unsigned int, unsigned int)) mapping_value cdef class Packer: cdef unsigned int margin - cdef dict mapping + cdef unordered_map[int, mapping_value] mapping cdef void pack(self, list blocks) - cdef (unsigned int, unsigned int) pos(self, block) + cdef (unsigned int, unsigned int) pos(self, int index) cdef unsigned int width(self) cdef unsigned int height(self) + cdef list get_mapping_hints(self, list blocks) + cdef (unsigned int) get_packer_settings(self) cdef class DeterministicPacker(Packer): pass @@ -20,9 +24,11 @@ cdef class BestPacker: cdef void pack(self, list blocks) cdef Packer best_packer(self) - cdef (unsigned int, unsigned int) pos(self, block) + cdef (unsigned int, unsigned int) pos(self, int index) cdef unsigned int width(self) cdef unsigned int height(self) + cdef list get_mapping_hints(self, list blocks) + cdef (unsigned int) get_packer_settings(self) cdef class RowPacker(Packer): pass @@ -34,12 +40,13 @@ cdef class BinaryTreePacker(Packer): cdef unsigned int aspect_ratio cdef packer_node *root - cdef void fit(self, block) - cdef packer_node *find_node(self, packer_node *root, unsigned int width, unsigned int height) - cdef packer_node *split_node(self, packer_node *node, unsigned int width, unsigned int height) - cdef packer_node *grow_node(self, unsigned int width, unsigned int height) - cdef packer_node *grow_right(self, unsigned int width, unsigned int height) - cdef packer_node *grow_down(self, unsigned int width, unsigned int height) + cdef void fit(self, block block) + cdef (unsigned int) get_packer_settings(self) + cdef packer_node *find_node(self, packer_node *root, unsigned int width, unsigned int height) noexcept + cdef packer_node *split_node(self, packer_node *node, unsigned int width, unsigned int height) noexcept + cdef packer_node *grow_node(self, unsigned int width, unsigned int height) noexcept + cdef packer_node *grow_right(self, unsigned int width, unsigned int height) noexcept + cdef packer_node *grow_down(self, unsigned int width, unsigned int height) noexcept cdef struct packer_node: unsigned int x @@ -49,3 +56,8 @@ cdef struct packer_node: bint used packer_node *down packer_node *right + +cdef struct block: + unsigned int index + unsigned int width + unsigned int height diff --git a/openage/convert/service/export/png/binpack.pyx b/openage/convert/service/export/png/binpack.pyx index 30e39cd95f..291be2a526 100644 --- a/openage/convert/service/export/png/binpack.pyx +++ b/openage/convert/service/export/png/binpack.pyx @@ -1,4 +1,4 @@ -# Copyright 2016-2023 the openage authors. See copying.md for legal info. +# Copyright 2016-2024 the openage authors. See copying.md for legal info. # # cython: infer_types=True,profile=False # TODO pylint: disable=C,R @@ -7,14 +7,10 @@ Routines for 2D binpacking """ -from enum import Enum - cimport cython -from libc.stdint cimport uintptr_t -from libc.stdlib cimport malloc - from libc.math cimport sqrt - +from libc.stdlib cimport malloc +from libcpp.unordered_map cimport unordered_map @cython.boundscheck(False) @cython.wraparound(False) @@ -24,6 +20,7 @@ cdef inline (unsigned int, unsigned int) factor(unsigned int n): Return two (preferable close) factors of n. """ cdef unsigned int a = sqrt(n) + cdef int num for num in range(a, 0, -1): if n % num == 0: return num, n // num @@ -33,9 +30,8 @@ cdef class Packer: """ Packs blocks. """ - def __init__(self, margin): + def __init__(self, int margin): self.margin = margin - self.mapping = {} cdef void pack(self, list blocks): """ @@ -45,31 +41,33 @@ cdef class Packer: """ raise NotImplementedError - cdef (unsigned int, unsigned int) pos(self, block): - return self.mapping[block] + cdef (unsigned int, unsigned int) pos(self, int index): + node = self.mapping[index] + return node[0], node[1] cdef unsigned int width(self): """ Gets the total width of the packing. """ - return max(self.pos(block)[0] + block.width for block in self.mapping) + return max(self.pos(idx)[0] + block[2][0] for idx, block in self.mapping) cdef unsigned int height(self): """ Gets the total height of the packing. """ - return max(self.pos(block)[1] + block.height for block in self.mapping) + return max(self.pos(idx)[1] + block[2][1] for idx, block in self.mapping) - def get_packer_settings(self): + cdef (unsigned int) get_packer_settings(self): """ Get the init parameters set for the packer. """ return (self.margin,) - def get_mapping_hints(self, blocks): - hints = [] + cdef list get_mapping_hints(self, list blocks): + cdef list hints = [] + cdef block block for block in blocks: - hints.append(self.pos(block)) + hints.append(self.pos(block.index)) return hints @@ -79,13 +77,13 @@ cdef class DeterministicPacker(Packer): Packs blocks based on predetermined settings. """ - def __init__(self, margin, hints): + def __init__(self, int margin, list hints): super().__init__(margin) self.hints = hints cdef void pack(self, list blocks): for idx, block in enumerate(blocks): - self.mapping[block] = self.hints[idx] + self.mapping[block.index] = self.hints[idx] cdef class BestPacker: @@ -97,18 +95,17 @@ cdef class BestPacker: self.current_best = None cdef void pack(self, list blocks): - cdef Packer p + cdef Packer packer for packer in self.packers: - p = packer - p.pack(blocks) + packer.pack(blocks) self.current_best = self.best_packer() cdef Packer best_packer(self): return min(self.packers, key=lambda Packer p: p.width() * p.height()) - cdef (unsigned int, unsigned int) pos(self, block): - return self.current_best.pos(block) + cdef (unsigned int, unsigned int) pos(self, int index): + return self.current_best.pos(index) cdef unsigned int width(self): return self.current_best.width() @@ -116,10 +113,10 @@ cdef class BestPacker: cdef unsigned int height(self): return self.current_best.height() - def get_packer_settings(self): + cdef (unsigned int) get_packer_settings(self): return self.current_best.get_packer_settings() - def get_mapping_hints(self, blocks): + cdef list get_mapping_hints(self, list blocks): return self.current_best.get_mapping_hints(blocks) @@ -129,8 +126,6 @@ cdef class RowPacker(Packer): """ cdef void pack(self, list blocks): - self.mapping = {} - cdef unsigned int num_rows cdef list rows @@ -148,7 +143,7 @@ cdef class RowPacker(Packer): x = 0 for block in row: - self.mapping[block] = (x, y) + self.mapping[block.index] = (x, y, (block.width, block.height)) x += block.width + self.margin y += max(block.height for block in row) + self.margin @@ -160,8 +155,6 @@ cdef class ColumnPacker(Packer): """ cdef void pack(self, list blocks): - self.mapping = {} - num_columns, _ = factor(len(blocks)) columns = [[] for _ in range(num_columns)] @@ -176,13 +169,13 @@ cdef class ColumnPacker(Packer): y = 0 for block in column: - self.mapping[block] = (x, y) + self.mapping[block.index] = (x, y, (block.width, block.height)) y += block.height + self.margin x += max(block.width for block in column) + self.margin -cdef inline (unsigned int, unsigned int, unsigned int, unsigned int) maxside_heuristic(block): +cdef inline (unsigned int, unsigned int, unsigned int, unsigned int) maxside_heuristic(block block): """ Heuristic: Order blocks by maximum side. """ @@ -202,27 +195,26 @@ cdef class BinaryTreePacker(Packer): textures. """ - def __init__(self, margin, aspect_ratio=1): + def __init__(self, int margin, int aspect_ratio=1): # ASF: what about heuristic=max_heuristic? super().__init__(margin) self.aspect_ratio = aspect_ratio self.root = NULL cdef void pack(self, list blocks): - self.mapping = {} self.root = NULL for block in sorted(blocks, key=maxside_heuristic, reverse=True): self.fit(block) - cdef (unsigned int, unsigned int) pos(self, block): - node = self.mapping[block] + cdef (unsigned int, unsigned int) pos(self, int index): + node = self.mapping[index] return node[0], node[1] - def get_packer_settings(self): + cdef (unsigned int) get_packer_settings(self): return (self.margin,) - cdef void fit(self, block): + cdef void fit(self, block block): cdef packer_node *node if self.root == NULL: self.root = malloc(sizeof(packer_node)) @@ -246,9 +238,9 @@ cdef class BinaryTreePacker(Packer): node = self.grow_node(block.width + self.margin, block.height + self.margin) - self.mapping[block] = (node.x, node.y) + self.mapping[block.index] = (node.x, node.y, (block.width, block.height)) - cdef packer_node *find_node(self, packer_node *root, unsigned int width, unsigned int height): + cdef packer_node *find_node(self, packer_node *root, unsigned int width, unsigned int height) noexcept: if root.used: return (self.find_node(root.right, width, height) or self.find_node(root.down, width, height)) @@ -256,7 +248,7 @@ cdef class BinaryTreePacker(Packer): elif width <= root.width and height <= root.height: return root - cdef packer_node *split_node(self, packer_node *node, unsigned int width, unsigned int height): + cdef packer_node *split_node(self, packer_node *node, unsigned int width, unsigned int height) noexcept: node.used = True node.down = malloc(sizeof(packer_node)) @@ -280,7 +272,7 @@ cdef class BinaryTreePacker(Packer): return node @cython.cdivision(True) - cdef packer_node *grow_node(self, unsigned int width, unsigned int height): + cdef packer_node *grow_node(self, unsigned int width, unsigned int height) noexcept: cdef bint can_grow_down = width <= self.root.width cdef bint can_grow_right = height <= self.root.height # assert can_grow_down or can_grow_right, "Bad block ordering heuristic" @@ -302,7 +294,7 @@ cdef class BinaryTreePacker(Packer): else: return self.grow_down(width, height) - cdef packer_node *grow_right(self, unsigned int width, unsigned int height): + cdef packer_node *grow_right(self, unsigned int width, unsigned int height) noexcept: old_root = self.root self.root = malloc(sizeof(packer_node)) @@ -327,7 +319,7 @@ cdef class BinaryTreePacker(Packer): if node != NULL: return self.split_node(node, width, height) - cdef packer_node *grow_down(self, unsigned int width, unsigned int height): + cdef packer_node *grow_down(self, unsigned int width, unsigned int height) noexcept: old_root = self.root self.root = malloc(sizeof(packer_node)) @@ -361,3 +353,8 @@ cdef struct packer_node: bint used packer_node *down packer_node *right + +cdef struct block: + unsigned int index + unsigned int width + unsigned int height From 2b39bf79882f545b615804bf4345c5bf8671771a Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 8 Dec 2024 03:10:50 +0100 Subject: [PATCH 015/152] etc: Update Ubuntu docker file. --- .../{Dockerfile.ubuntu.2204 => Dockerfile.ubuntu.2404} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename packaging/docker/devenv/{Dockerfile.ubuntu.2204 => Dockerfile.ubuntu.2404} (95%) diff --git a/packaging/docker/devenv/Dockerfile.ubuntu.2204 b/packaging/docker/devenv/Dockerfile.ubuntu.2404 similarity index 95% rename from packaging/docker/devenv/Dockerfile.ubuntu.2204 rename to packaging/docker/devenv/Dockerfile.ubuntu.2404 index 4976e0c693..d3d492f6af 100644 --- a/packaging/docker/devenv/Dockerfile.ubuntu.2204 +++ b/packaging/docker/devenv/Dockerfile.ubuntu.2404 @@ -1,4 +1,4 @@ -FROM ubuntu:23.04 +FROM ubuntu:24.04 RUN apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y sudo \ && sudo apt-get update \ @@ -8,8 +8,8 @@ RUN apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y sudo \ cmake \ cython3 \ flex \ - gcc-11 \ - g++-11 \ + gcc \ + g++ \ git \ libeigen3-dev \ libepoxy-dev \ From 6d370952b4d805dc1ead0b9844f48055571a084f Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 8 Dec 2024 03:12:08 +0100 Subject: [PATCH 016/152] etc: Update GitHub actions for Ubuntu workflow. --- .github/workflows/{ubuntu-22.04.yml => ubuntu-24.04.yml} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename .github/workflows/{ubuntu-22.04.yml => ubuntu-24.04.yml} (94%) diff --git a/.github/workflows/ubuntu-22.04.yml b/.github/workflows/ubuntu-24.04.yml similarity index 94% rename from .github/workflows/ubuntu-22.04.yml rename to .github/workflows/ubuntu-24.04.yml index 311524f799..6fa9145279 100644 --- a/.github/workflows/ubuntu-22.04.yml +++ b/.github/workflows/ubuntu-24.04.yml @@ -1,14 +1,14 @@ -name: Ubuntu 22.04 CI +name: Ubuntu 24.04 CI on: [push, workflow_dispatch] jobs: build-devenv: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - name: Build the Docker image - run: sudo DOCKER_BUILDKIT=1 docker build ./packaging/docker/devenv --file ./packaging/docker/devenv/Dockerfile.ubuntu.2204 --tag openage-devenv:latest + run: sudo DOCKER_BUILDKIT=1 docker build ./packaging/docker/devenv --file ./packaging/docker/devenv/Dockerfile.ubuntu.2404 --tag openage-devenv:latest shell: bash - name: Save the Docker image run: | @@ -24,7 +24,7 @@ jobs: retention-days: 30 build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 needs: build-devenv steps: - uses: actions/checkout@v4 From 0bd54ce39a7a24a263a82b14f7dff04a9f770362 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 8 Dec 2024 03:19:20 +0100 Subject: [PATCH 017/152] doc: Update reference to Ubuntu 24.04 in README. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 266cdfc5fa..4cd98b9070 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ If you're interested, we wrote detailed explanations on our blog: [Part 1](https | Operating System | Build status | | :-----------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | | Debian Sid | [Todo: Kevin #11] | -| Ubuntu 22.04 LTS | [![Ubuntu 22.04 build status](https://github.com/SFTTech/openage/actions/workflows/ubuntu-22.04.yml/badge.svg?branch=master)](https://github.com/SFTtech/openage/actions/workflows/ubuntu-22.04.yml) | +| Ubuntu 24.04 LTS | [![Ubuntu 24.04 build status](https://github.com/SFTTech/openage/actions/workflows/ubuntu-24.04.yml/badge.svg?branch=master)](https://github.com/SFTtech/openage/actions/workflows/ubuntu-24.04.yml) | | macOS | [![macOS build status](https://github.com/SFTtech/openage/workflows/macOS-CI/badge.svg)](https://github.com/SFTtech/openage/actions?query=workflow%3AmacOS-CI) | | Windows Server 2019 | [![Windows Server 2019 build status](https://github.com/SFTtech/openage/actions/workflows/windows-server-2019.yml/badge.svg?branch=master)](https://github.com/SFTtech/openage/actions/workflows/windows-server-2019.yml) | | Windows Server 2022 | [![Windows Server 2022 build status](https://github.com/SFTtech/openage/actions/workflows/windows-server-2022.yml/badge.svg?branch=master)](https://github.com/SFTtech/openage/actions/workflows/windows-server-2022.yml) | From b306c76cd8f9a7844cc0363e148be27e7f7ad205 Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Tue, 3 Dec 2024 00:54:52 +0000 Subject: [PATCH 018/152] curve: common element wrapper --- libopenage/curve/element_wrapper.h | 54 ++++++++++++++++++++++++++ libopenage/curve/map.h | 22 +++-------- libopenage/curve/map_filter_iterator.h | 6 +-- libopenage/curve/queue.h | 33 ++-------------- 4 files changed, 66 insertions(+), 49 deletions(-) create mode 100644 libopenage/curve/element_wrapper.h diff --git a/libopenage/curve/element_wrapper.h b/libopenage/curve/element_wrapper.h new file mode 100644 index 0000000000..79756f0e5c --- /dev/null +++ b/libopenage/curve/element_wrapper.h @@ -0,0 +1,54 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include "time/time.h" + +namespace openage::curve { + +/** + * wrapper class for elements of a curve, store the insertion time and erase time of the element + * aswell the data of the element. + */ +template +struct element_wrapper { + // Insertion time of the element + time::time_t _alive; + // Erase time of the element + time::time_t _dead; + // Element value + T value; + + /** + * construct new element, set its start time and value + * its end time will be set to time::TIME_MAX + */ + element_wrapper(const time::time_t &time, const T &value) : + _alive{time}, + _dead{time::TIME_MAX}, + value{value} {} + + // construct new element, set its start time, end time and value + element_wrapper(const T &value, const time::time_t &alive, const time::time_t &dead) : + _alive{alive}, + _dead{dead}, + value{value} {} + + + // start time of this element + const time::time_t &alive() const { + return _alive; + } + + // end time of this element + const time::time_t &dead() const { + return _dead; + } + + // set the end time of this element + void set_dead(const time::time_t &time) { + _dead = time; + } +}; + +} // namespace openage::curve diff --git a/libopenage/curve/map.h b/libopenage/curve/map.h index 9712c89470..d996095d7c 100644 --- a/libopenage/curve/map.h +++ b/libopenage/curve/map.h @@ -1,4 +1,4 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -8,6 +8,7 @@ #include #include "curve/map_filter_iterator.h" +#include "curve/element_wrapper.h" #include "time/time.h" #include "util/fixed_point.h" @@ -20,26 +21,15 @@ namespace openage::curve { */ template class UnorderedMap { - /** Internal container to access all data and metadata */ - struct map_element { - map_element(const val_t &v, const time::time_t &a, const time::time_t &d) : - value(v), - alive(a), - dead(d) {} - - val_t value; - time::time_t alive; - time::time_t dead; - }; /** * Data holder. Maps keys to map elements. * Map elements themselves store when they are valid. */ - std::unordered_map container; + std::unordered_map> container; public: - using const_iterator = typename std::unordered_map::const_iterator; + using const_iterator = typename std::unordered_map>::const_iterator; std::optional> operator()(const time::time_t &, const key_t &) const; @@ -95,7 +85,7 @@ std::optional>> UnorderedMap::at(const time::time_t &time, const key_t &key) const { auto e = this->container.find(key); - if (e != this->container.end() and e->second.alive <= time and e->second.dead > time) { + if (e != this->container.end() and e->second.alive() <= time and e->second.dead() > time) { return MapFilterIterator>( e, this, @@ -160,7 +150,7 @@ UnorderedMap::insert(const time::time_t &alive, const time::time_t &dead, const key_t &key, const val_t &value) { - map_element e(value, alive, dead); + element_wrapper e(value, alive, dead); auto it = this->container.insert(std::make_pair(key, e)); return MapFilterIterator>( it.first, diff --git a/libopenage/curve/map_filter_iterator.h b/libopenage/curve/map_filter_iterator.h index 5e71c0789f..e60f762c26 100644 --- a/libopenage/curve/map_filter_iterator.h +++ b/libopenage/curve/map_filter_iterator.h @@ -1,4 +1,4 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -35,8 +35,8 @@ class MapFilterIterator : public CurveIterator { using CurveIterator::operator=; virtual bool valid() const override { - return (this->get_base()->second.alive >= this->from - and this->get_base()->second.dead < this->to); + return (this->get_base()->second.alive() >= this->from + and this->get_base()->second.dead() < this->to); } /** diff --git a/libopenage/curve/queue.h b/libopenage/curve/queue.h index 9604a76308..210670ee7b 100644 --- a/libopenage/curve/queue.h +++ b/libopenage/curve/queue.h @@ -13,6 +13,7 @@ #include "curve/iterator.h" #include "curve/queue_filter_iterator.h" +#include "curve/element_wrapper.h" #include "event/evententity.h" #include "time/time.h" #include "util/fixed_point.h" @@ -32,39 +33,11 @@ namespace curve { */ template class Queue : public event::EventEntity { - struct queue_wrapper { - // Insertion time of the element - time::time_t _alive; - // Erase time of the element - // TODO: this has to be mutable because erase() will complain otherwise - mutable time::time_t _dead; - // Element value - T value; - - queue_wrapper(const time::time_t &time, const T &value) : - _alive{time}, - _dead{time::TIME_MAX}, - value{value} {} - - const time::time_t &alive() const { - return _alive; - } - - const time::time_t &dead() const { - return _dead; - } - - // TODO: this has to be const because erase() will complain otherwise - void set_dead(const time::time_t &time) const { - _dead = time; - } - }; - public: /** * The underlaying container type. */ - using container_t = typename std::vector; + using container_t = typename std::vector>; /** * The index type to access elements in the container @@ -412,7 +385,7 @@ QueueFilterIterator> Queue::insert(const time::time_t &time, // Get the iterator to the insertion point iterator insertion_point = std::next(this->container.begin(), at); - insertion_point = this->container.insert(insertion_point, queue_wrapper{time, e}); + insertion_point = this->container.insert(insertion_point, element_wrapper{time, e}); // TODO: Inserting before any dead elements shoud reset their death time // since by definition, they cannot be popped before the new element From 775b3f63a5798f32be690262143208e4c1fd3dc3 Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Wed, 11 Dec 2024 18:39:47 +0000 Subject: [PATCH 019/152] change portalNode::entry_sector to std::optional --- libopenage/pathfinding/pathfinder.cpp | 11 ++++++----- libopenage/pathfinding/pathfinder.h | 3 ++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/libopenage/pathfinding/pathfinder.cpp b/libopenage/pathfinding/pathfinder.cpp index 2fef36053a..c99afd5b9d 100644 --- a/libopenage/pathfinding/pathfinder.cpp +++ b/libopenage/pathfinding/pathfinder.cpp @@ -269,11 +269,12 @@ const Pathfinder::portal_star_t Pathfinder::portal_a_star(const PathRequest &req } // get the exits of the current node - const auto &exits = current_node->get_exits(current_node->entry_sector); + ENSURE(current_node->entry_sector != std::nullopt, "Entry sector not set for portal node."); + const auto &exits = current_node->get_exits(current_node->entry_sector.value()); // evaluate all neighbors of the current candidate for further progress for (auto &[exit, distance_cost] : exits) { - exit->entry_sector = current_node->portal->get_exit_sector(current_node->entry_sector); + exit->entry_sector = current_node->portal->get_exit_sector(current_node->entry_sector.value()); bool not_visited = !visited_portals.contains(exit->portal->get_id()); if (not_visited) { @@ -289,9 +290,9 @@ const Pathfinder::portal_star_t Pathfinder::portal_a_star(const PathRequest &req if (not_visited or tentative_cost < exit->current_cost) { if (not_visited) { // Get heuristic cost (from exit node to target cell) - auto exit_sector = grid->get_sector(exit->portal->get_exit_sector(exit->entry_sector)); + auto exit_sector = grid->get_sector(exit->portal->get_exit_sector(exit->entry_sector.value())); auto exit_sector_pos = exit_sector->get_position().to_tile(sector_size); - auto exit_portal_pos = exit->portal->get_exit_center(exit->entry_sector); + auto exit_portal_pos = exit->portal->get_exit_center(exit->entry_sector.value()); exit->heuristic_cost = Pathfinder::heuristic_cost( exit_sector_pos + exit_portal_pos, request.target); @@ -469,7 +470,7 @@ int Pathfinder::distance_cost(const coord::tile_delta &portal1_pos, PortalNode::PortalNode(const std::shared_ptr &portal) : portal{portal}, - entry_sector{NULL}, + entry_sector{std::nullopt}, future_cost{std::numeric_limits::max()}, current_cost{std::numeric_limits::max()}, heuristic_cost{std::numeric_limits::max()}, diff --git a/libopenage/pathfinding/pathfinder.h b/libopenage/pathfinding/pathfinder.h index e2529987b7..8d569d78d8 100644 --- a/libopenage/pathfinding/pathfinder.h +++ b/libopenage/pathfinding/pathfinder.h @@ -4,6 +4,7 @@ #include #include +#include #include #include "coord/tile.h" @@ -209,7 +210,7 @@ class PortalNode : public std::enable_shared_from_this { /** * Sector where the portal is entered. */ - sector_id_t entry_sector; + std::optional entry_sector; /** * Future cost estimation value for this node. From 54b2191c6c97346943b54bab08d48f78b2a0f259 Mon Sep 17 00:00:00 2001 From: jere8184 Date: Mon, 16 Dec 2024 20:18:25 +0000 Subject: [PATCH 020/152] Update windows-server CI to run tests --- .github/workflows/windows-server-2019.yml | 1 + .github/workflows/windows-server-2022.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/windows-server-2019.yml b/.github/workflows/windows-server-2019.yml index df9d96284b..7cd34e49b5 100644 --- a/.github/workflows/windows-server-2019.yml +++ b/.github/workflows/windows-server-2019.yml @@ -76,6 +76,7 @@ jobs: $DLL_PATH = Join-Path package dll | Resolve-Path cd package python -m openage --add-dll-search-path $DLL_PATH --version + ./run.exe test -a shell: pwsh - name: Publish build artifacts uses: actions/upload-artifact@v4 diff --git a/.github/workflows/windows-server-2022.yml b/.github/workflows/windows-server-2022.yml index bbc73b78c2..327c23584a 100644 --- a/.github/workflows/windows-server-2022.yml +++ b/.github/workflows/windows-server-2022.yml @@ -76,6 +76,7 @@ jobs: $DLL_PATH = Join-Path package dll | Resolve-Path cd package python -m openage --add-dll-search-path $DLL_PATH --version + ./run.exe test -a shell: pwsh - name: Publish build artifacts uses: actions/upload-artifact@v4 From 727fd328ce6ae94e9c9a9da268cb052d5a711faf Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 25 Dec 2024 14:41:39 +0100 Subject: [PATCH 021/152] curve: Add empty .cpp file for cmake. --- libopenage/curve/CMakeLists.txt | 1 + libopenage/curve/element_wrapper.cpp | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 libopenage/curve/element_wrapper.cpp diff --git a/libopenage/curve/CMakeLists.txt b/libopenage/curve/CMakeLists.txt index 1aa1efb55b..623a22a25a 100644 --- a/libopenage/curve/CMakeLists.txt +++ b/libopenage/curve/CMakeLists.txt @@ -3,6 +3,7 @@ add_sources(libopenage continuous.cpp discrete.cpp discrete_mod.cpp + element_wrapper.cpp interpolated.cpp iterator.cpp keyframe.cpp diff --git a/libopenage/curve/element_wrapper.cpp b/libopenage/curve/element_wrapper.cpp new file mode 100644 index 0000000000..5d2eaa08af --- /dev/null +++ b/libopenage/curve/element_wrapper.cpp @@ -0,0 +1,9 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "element_wrapper.h" + +namespace openage::curve { + +// This file is intended to be empty + +} // namespace openage::curve From 71cdc50ce8cfbcafbfdf898d3189cb5dffc79f0a Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 25 Dec 2024 14:43:43 +0100 Subject: [PATCH 022/152] curve: Fix comments of element wrapper. --- libopenage/curve/element_wrapper.h | 46 ++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/libopenage/curve/element_wrapper.h b/libopenage/curve/element_wrapper.h index 79756f0e5c..a0cfcd2109 100644 --- a/libopenage/curve/element_wrapper.h +++ b/libopenage/curve/element_wrapper.h @@ -7,45 +7,67 @@ namespace openage::curve { /** - * wrapper class for elements of a curve, store the insertion time and erase time of the element - * aswell the data of the element. + * Wrapper for elements in a curve container. + * + * Stores the lifetime of the element (insertion time and erasure time) alongside the value. */ template struct element_wrapper { - // Insertion time of the element + /// Time of insertion of the element into the container time::time_t _alive; - // Erase time of the element + /// Time of erasure of the element from the container time::time_t _dead; - // Element value + /// Element value T value; /** - * construct new element, set its start time and value - * its end time will be set to time::TIME_MAX + * Create a new element with insertion time \p time and a given value. + * + * Erasure time is set to time::TIME_MAX, i.e. the element is alive indefinitely. + * + * @param time Insertion time of the element. + * @param value Element value. */ element_wrapper(const time::time_t &time, const T &value) : _alive{time}, _dead{time::TIME_MAX}, value{value} {} - // construct new element, set its start time, end time and value + /** + * Create a new element with insertion time \p alive and erasure time \p dead and a given value. + * + * @param alive Insertion time of the element. + * @param dead Erasure time of the element. + * @param value Element value. + */ element_wrapper(const T &value, const time::time_t &alive, const time::time_t &dead) : _alive{alive}, _dead{dead}, value{value} {} - - // start time of this element + /** + * Get the insertion time of this element. + * + * @return Time when the element was inserted into the container. + */ const time::time_t &alive() const { return _alive; } - // end time of this element + /** + * Get the erasure time of this element. + * + * @return Time when the element was erased from the container. + */ const time::time_t &dead() const { return _dead; } - // set the end time of this element + /** + * Set the erasure time of this element. + * + * @param time Time when the element was erased from the container. + */ void set_dead(const time::time_t &time) { _dead = time; } From 41596023a1efc3e37ea682cb1ab8cbb4a3ce2843 Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 25 Dec 2024 14:46:16 +0100 Subject: [PATCH 023/152] curve: Order arguments of both constructors the same way. --- libopenage/curve/element_wrapper.h | 2 +- libopenage/curve/map.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libopenage/curve/element_wrapper.h b/libopenage/curve/element_wrapper.h index a0cfcd2109..498ee550cc 100644 --- a/libopenage/curve/element_wrapper.h +++ b/libopenage/curve/element_wrapper.h @@ -40,7 +40,7 @@ struct element_wrapper { * @param dead Erasure time of the element. * @param value Element value. */ - element_wrapper(const T &value, const time::time_t &alive, const time::time_t &dead) : + element_wrapper(const time::time_t &alive, const time::time_t &dead, const T &value) : _alive{alive}, _dead{dead}, value{value} {} diff --git a/libopenage/curve/map.h b/libopenage/curve/map.h index d996095d7c..5a5e0a4d9b 100644 --- a/libopenage/curve/map.h +++ b/libopenage/curve/map.h @@ -150,7 +150,7 @@ UnorderedMap::insert(const time::time_t &alive, const time::time_t &dead, const key_t &key, const val_t &value) { - element_wrapper e(value, alive, dead); + element_wrapper e{alive, dead, value}; auto it = this->container.insert(std::make_pair(key, e)); return MapFilterIterator>( it.first, From d6bf5b910ce0425d9cf9d9f1a991ebef1c937abc Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 25 Dec 2024 14:56:03 +0100 Subject: [PATCH 024/152] curve: Make element wrapper member access private. Prevents unexpected manipulation. --- libopenage/curve/element_wrapper.h | 39 ++++++++++++++++++------ libopenage/curve/map_filter_iterator.h | 2 +- libopenage/curve/queue.h | 4 +-- libopenage/curve/queue_filter_iterator.h | 2 +- 4 files changed, 33 insertions(+), 14 deletions(-) diff --git a/libopenage/curve/element_wrapper.h b/libopenage/curve/element_wrapper.h index 498ee550cc..764b61d5cd 100644 --- a/libopenage/curve/element_wrapper.h +++ b/libopenage/curve/element_wrapper.h @@ -12,14 +12,8 @@ namespace openage::curve { * Stores the lifetime of the element (insertion time and erasure time) alongside the value. */ template -struct element_wrapper { - /// Time of insertion of the element into the container - time::time_t _alive; - /// Time of erasure of the element from the container - time::time_t _dead; - /// Element value - T value; - +class element_wrapper { +public: /** * Create a new element with insertion time \p time and a given value. * @@ -31,7 +25,7 @@ struct element_wrapper { element_wrapper(const time::time_t &time, const T &value) : _alive{time}, _dead{time::TIME_MAX}, - value{value} {} + _value{value} {} /** * Create a new element with insertion time \p alive and erasure time \p dead and a given value. @@ -43,7 +37,7 @@ struct element_wrapper { element_wrapper(const time::time_t &alive, const time::time_t &dead, const T &value) : _alive{alive}, _dead{dead}, - value{value} {} + _value{value} {} /** * Get the insertion time of this element. @@ -71,6 +65,31 @@ struct element_wrapper { void set_dead(const time::time_t &time) { _dead = time; } + + /** + * Get the value of this element. + * + * @return Value of the element. + */ + const T &value() const { + return _value; + } + +private: + /** + * Time of insertion of the element into the container + */ + time::time_t _alive; + + /** + * Time of erasure of the element from the container + */ + time::time_t _dead; + + /** + * Element value + */ + T _value; }; } // namespace openage::curve diff --git a/libopenage/curve/map_filter_iterator.h b/libopenage/curve/map_filter_iterator.h index e60f762c26..3aec2a899e 100644 --- a/libopenage/curve/map_filter_iterator.h +++ b/libopenage/curve/map_filter_iterator.h @@ -44,7 +44,7 @@ class MapFilterIterator : public CurveIterator { * Nicer way of accessing it beside operator *. */ val_t const &value() const override { - return this->get_base()->second.value; + return this->get_base()->second.value(); } /** diff --git a/libopenage/curve/queue.h b/libopenage/curve/queue.h index 210670ee7b..9314dd3a0e 100644 --- a/libopenage/curve/queue.h +++ b/libopenage/curve/queue.h @@ -277,7 +277,7 @@ const T &Queue::front(const time::time_t &time) const { << ", container size: " << this->container.size() << ")"); - return this->container.at(at).value; + return this->container.at(at).value(); } @@ -303,7 +303,7 @@ const T &Queue::pop_front(const time::time_t &time) { this->changes(time); - return this->container.at(at).value; + return this->container.at(at).value(); } diff --git a/libopenage/curve/queue_filter_iterator.h b/libopenage/curve/queue_filter_iterator.h index 248abb44ad..cf6bc5aa2c 100644 --- a/libopenage/curve/queue_filter_iterator.h +++ b/libopenage/curve/queue_filter_iterator.h @@ -40,7 +40,7 @@ class QueueFilterIterator : public CurveIteratorget_base(); - return a.value; + return a.value(); } }; From 0a03e2d33c22b0e07950bec8e25f45ece35839ab Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Sun, 29 Dec 2024 20:40:48 +0000 Subject: [PATCH 025/152] suppress add-dll-search-path in main subparser --- openage/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openage/__main__.py b/openage/__main__.py index 970643bb51..60c1c1bb4f 100644 --- a/openage/__main__.py +++ b/openage/__main__.py @@ -127,7 +127,7 @@ def main(argv=None): "codegen", parents=[global_cli])) - args = cli.parse_args(argv) + args, remaining_args = cli.parse_known_args(argv) dll_manager = None if sys.platform == 'win32': @@ -139,7 +139,7 @@ def main(argv=None): if not args.subcommand: # the user didn't specify a subcommand. default to 'main'. - args = main_cli.parse_args(argv) + args = main_cli.parse_args(remaining_args) args.dll_manager = dll_manager From 26c79cb50b9cd2d34737e70e20e69a19b2ea67e1 Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Sun, 29 Dec 2024 18:17:51 +0000 Subject: [PATCH 026/152] specify dll path --- .github/workflows/windows-server-2019.yml | 2 +- .github/workflows/windows-server-2022.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows-server-2019.yml b/.github/workflows/windows-server-2019.yml index 7cd34e49b5..bf3ff7856b 100644 --- a/.github/workflows/windows-server-2019.yml +++ b/.github/workflows/windows-server-2019.yml @@ -76,7 +76,7 @@ jobs: $DLL_PATH = Join-Path package dll | Resolve-Path cd package python -m openage --add-dll-search-path $DLL_PATH --version - ./run.exe test -a + python -m openage --add-dll-search-path $DLL_PATH test -a shell: pwsh - name: Publish build artifacts uses: actions/upload-artifact@v4 diff --git a/.github/workflows/windows-server-2022.yml b/.github/workflows/windows-server-2022.yml index 327c23584a..a721db5a52 100644 --- a/.github/workflows/windows-server-2022.yml +++ b/.github/workflows/windows-server-2022.yml @@ -76,7 +76,7 @@ jobs: $DLL_PATH = Join-Path package dll | Resolve-Path cd package python -m openage --add-dll-search-path $DLL_PATH --version - ./run.exe test -a + python -m openage --add-dll-search-path $DLL_PATH test -a shell: pwsh - name: Publish build artifacts uses: actions/upload-artifact@v4 From 21fbaa91849ddb5dcac177ade4c64c38c8f67de7 Mon Sep 17 00:00:00 2001 From: Alex Zhuohao He Date: Thu, 2 Jan 2025 15:05:53 -0500 Subject: [PATCH 027/152] bind empty VAO to bufferless quad rendering --- libopenage/renderer/opengl/geometry.cpp | 3 ++- libopenage/renderer/opengl/renderer.cpp | 13 ++++++++++--- libopenage/renderer/opengl/renderer.h | 11 ++++++++++- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/libopenage/renderer/opengl/geometry.cpp b/libopenage/renderer/opengl/geometry.cpp index 0e2c930a2a..e0898edbe8 100644 --- a/libopenage/renderer/opengl/geometry.cpp +++ b/libopenage/renderer/opengl/geometry.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2024 the openage authors. See copying.md for legal info. +// Copyright 2015-2025 the openage authors. See copying.md for legal info. #include "geometry.h" @@ -54,6 +54,7 @@ void GlGeometry::update_verts_offset(std::vector const &verts, size_t o void GlGeometry::draw() const { switch (this->get_type()) { case geometry_t::bufferless_quad: + // any VAO must be bound before this call glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); break; diff --git a/libopenage/renderer/opengl/renderer.cpp b/libopenage/renderer/opengl/renderer.cpp index 33b734790f..6d8d909a5b 100644 --- a/libopenage/renderer/opengl/renderer.cpp +++ b/libopenage/renderer/opengl/renderer.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2024 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #include "renderer.h" @@ -15,6 +15,7 @@ #include "renderer/opengl/texture.h" #include "renderer/opengl/uniform_buffer.h" #include "renderer/opengl/uniform_input.h" +#include "renderer/opengl/vertex_array.h" #include "renderer/opengl/window.h" #include "renderer/resources/buffer_info.h" @@ -26,7 +27,8 @@ GlRenderer::GlRenderer(const std::shared_ptr &ctx, gl_context{ctx}, display{std::make_shared(ctx, viewport_size[0], - viewport_size[1])} { + viewport_size[1])}, + shared_quad_vao{std::make_shared(ctx)} { // color used to clear the color buffers glClearColor(0.0f, 0.0f, 0.0f, 0.0f); @@ -100,7 +102,7 @@ std::shared_ptr GlRenderer::add_uniform_buffer(resources::Uniform resources::UniformBufferInfo::get_size(input, info.get_layout()), resources::UniformBufferInfo::get_stride_size(input.type, info.get_layout()), input.count, - input.name}); + input.name}); offset += size; } @@ -169,6 +171,11 @@ void GlRenderer::render(const std::shared_ptr &pass) { auto gl_target = std::dynamic_pointer_cast(pass->get_target()); gl_target->bind_write(); + // ensure that an (empty) VAO is bound before rendering geometry + // a bound VAO is required to render bufferless quad geometries by OpenGL + // see https://www.khronos.org/opengl/wiki/Vertex_Rendering#Causes_of_rendering_failure + shared_quad_vao->bind(); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // TODO: Option for face culling diff --git a/libopenage/renderer/opengl/renderer.h b/libopenage/renderer/opengl/renderer.h index 458af24358..751af7cacc 100644 --- a/libopenage/renderer/opengl/renderer.h +++ b/libopenage/renderer/opengl/renderer.h @@ -1,4 +1,4 @@ -// Copyright 2017-2024 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #pragma once @@ -17,6 +17,7 @@ namespace opengl { class GlContext; class GlRenderPass; class GlRenderTarget; +class GlVertexArray; class GlWindow; /// The OpenGL specialization of the rendering interface. @@ -67,6 +68,14 @@ class GlRenderer final : public Renderer { /// The main screen surface as a render target. std::shared_ptr display; + + /// An empty vertex array object (VAO). + /// + /// This VAO has to be bound at the start of a render pass to ensure + /// that bufferless quad geometry can be drawn without errors. Drawing a + /// bufferless quad requires any VAO to be bound + /// see https://www.khronos.org/opengl/wiki/Vertex_Rendering#Causes_of_rendering_failure + std::shared_ptr shared_quad_vao; }; } // namespace opengl From b424f672e3ec2c5104011b3a222a9f1925b57367 Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Thu, 9 Jan 2025 01:14:15 +0000 Subject: [PATCH 028/152] remove msvc class or struct prefix in util::demangle --- libopenage/util/compiler.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libopenage/util/compiler.cpp b/libopenage/util/compiler.cpp index 2fd850920d..71100d3f92 100644 --- a/libopenage/util/compiler.cpp +++ b/libopenage/util/compiler.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2024 the openage authors. See copying.md for legal info. +// Copyright 2015-2025 the openage authors. See copying.md for legal info. #include "compiler.h" @@ -24,10 +24,10 @@ namespace util { std::string demangle(const char *symbol) { #ifdef _WIN32 - // TODO: demangle names for MSVC; Possibly using UnDecorateSymbolName - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms681400(v=vs.85).aspx - // Could it be that MSVC's typeid(T).name() already returns a demangled name? It seems that .raw_name() returns the mangled name - return symbol; + // MSVC's typeid(T).name() already returns a demangled name + // unlike clang and gcc the MSVC demangled name is prefixed with "class " or "stuct " + // we remove the prefix to match the format of clang and gcc + return strchr(symbol, ' ') + 1; #else int status; char *buf = abi::__cxa_demangle(symbol, nullptr, nullptr, &status); From 251985936eee1dbacc78a45928efb1fd8a0c6fc6 Mon Sep 17 00:00:00 2001 From: Manuel <17874544+MKoesters@users.noreply.github.com> Date: Mon, 13 Jan 2025 22:59:30 +0100 Subject: [PATCH 029/152] Remove additional quotes in build instructions Remove additional " in configure command --- doc/build_instructions/macos.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/build_instructions/macos.md b/doc/build_instructions/macos.md index c9946f2d39..e38dee9932 100644 --- a/doc/build_instructions/macos.md +++ b/doc/build_instructions/macos.md @@ -38,7 +38,7 @@ We advise against using the clang version that comes with macOS (Apple Clang) as ``` # on Intel macOS, llvm is by default in /usr/local/Cellar/llvm/bin/ # on ARM macOS, llvm is by default in /opt/homebrew/Cellar/llvm/bin/ -./configure --compiler="$(brew --prefix llvm)/bin/clang"" --download-nyan +./configure --compiler="$(brew --prefix llvm)/bin/clang" --download-nyan ``` Afterwards, trigger the build using `make`: From 64dd525de649e4c36a30e7ec05bad74e62475164 Mon Sep 17 00:00:00 2001 From: dmwever <56411717+dmwever@users.noreply.github.com> Date: Tue, 3 Dec 2024 21:47:26 -0600 Subject: [PATCH 030/152] Add dirty flag and integrate --- libopenage/pathfinding/CMakeLists.txt | 1 + libopenage/pathfinding/cost_field.cpp | 22 +++++++-- libopenage/pathfinding/cost_field.h | 28 +++++++++-- libopenage/pathfinding/demo/demo_0.cpp | 20 ++++---- libopenage/pathfinding/demo/demo_1.cpp | 16 ++++++- libopenage/pathfinding/field_cache.cpp | 32 +++++++++++++ libopenage/pathfinding/field_cache.h | 66 ++++++++++++++++++++++++++ libopenage/pathfinding/integrator.cpp | 64 +++++++++++++++++-------- libopenage/pathfinding/integrator.h | 34 ++++--------- libopenage/pathfinding/path.h | 5 ++ libopenage/pathfinding/pathfinder.cpp | 3 +- libopenage/pathfinding/sector.cpp | 4 ++ libopenage/pathfinding/sector.h | 8 ++++ libopenage/pathfinding/tests.cpp | 4 +- 14 files changed, 242 insertions(+), 65 deletions(-) create mode 100644 libopenage/pathfinding/field_cache.cpp create mode 100644 libopenage/pathfinding/field_cache.h diff --git a/libopenage/pathfinding/CMakeLists.txt b/libopenage/pathfinding/CMakeLists.txt index 7f85264d01..33c2844ace 100644 --- a/libopenage/pathfinding/CMakeLists.txt +++ b/libopenage/pathfinding/CMakeLists.txt @@ -1,6 +1,7 @@ add_sources(libopenage cost_field.cpp definitions.cpp + field_cache.cpp flow_field.cpp grid.cpp integration_field.cpp diff --git a/libopenage/pathfinding/cost_field.cpp b/libopenage/pathfinding/cost_field.cpp index 3299084548..4ad1318874 100644 --- a/libopenage/pathfinding/cost_field.cpp +++ b/libopenage/pathfinding/cost_field.cpp @@ -13,7 +13,8 @@ namespace openage::path { CostField::CostField(size_t size) : size{size}, - cells(this->size * this->size, COST_MIN) { + cells(this->size * this->size, COST_MIN), + changed{time::time_t::min_value()} { log::log(DBG << "Created cost field with size " << this->size << "x" << this->size); } @@ -33,29 +34,42 @@ cost_t CostField::get_cost(size_t idx) const { return this->cells.at(idx); } -void CostField::set_cost(const coord::tile_delta &pos, cost_t cost) { +void CostField::set_cost(const coord::tile_delta &pos, cost_t cost, time::time_t &changed) { this->cells[pos.ne + pos.se * this->size] = cost; + this->changed = changed; } -void CostField::set_cost(size_t x, size_t y, cost_t cost) { +void CostField::set_cost(size_t x, size_t y, cost_t cost, time::time_t &changed) { this->cells[x + y * this->size] = cost; + this->changed = changed; } void CostField::set_cost(size_t idx, cost_t cost) { this->cells[idx] = cost; + this->changed = time::TIME_ZERO; +} + +void CostField::set_cost(size_t idx, cost_t cost,time::time_t &changed) { + this->cells[idx] = cost; + this->changed = changed; } const std::vector &CostField::get_costs() const { return this->cells; } -void CostField::set_costs(std::vector &&cells) { +void CostField::set_costs(std::vector &&cells, time::time_t &changed) { ENSURE(cells.size() == this->cells.size(), "cells vector has wrong size: " << cells.size() << "; expected: " << this->cells.size()); this->cells = std::move(cells); + this->changed = changed; +} + +bool CostField::is_dirty(time::time_t &time) { + return time <= this->changed; } } // namespace openage::path diff --git a/libopenage/pathfinding/cost_field.h b/libopenage/pathfinding/cost_field.h index a1fc87b046..99dd16306c 100644 --- a/libopenage/pathfinding/cost_field.h +++ b/libopenage/pathfinding/cost_field.h @@ -6,6 +6,7 @@ #include #include "pathfinding/types.h" +#include "time/time.h" namespace openage { @@ -64,8 +65,9 @@ class CostField { * * @param pos Coordinates of the cell (relative to field origin). * @param cost Cost to set. + * @param changed Time cost is set. */ - void set_cost(const coord::tile_delta &pos, cost_t cost); + void set_cost(const coord::tile_delta &pos, cost_t cost, time::time_t &changed); /** * Set the cost at a specified position. @@ -73,8 +75,9 @@ class CostField { * @param x X-coordinate of the cell. * @param y Y-coordinate of the cell. * @param cost Cost to set. + * @param changed Time cost is set. */ - void set_cost(size_t x, size_t y, cost_t cost); + void set_cost(size_t x, size_t y, cost_t cost, time::time_t &changed); /** * Set the cost at a specified position. @@ -84,6 +87,15 @@ class CostField { */ void set_cost(size_t idx, cost_t cost); + /** + * Set the cost at a specified position. + * + * @param idx Index of the cell. + * @param cost Cost to set. + * @param changed Time cost is set. + */ + void set_cost(size_t idx, cost_t cost, time::time_t &changed); + /** * Get the cost field values. * @@ -95,8 +107,16 @@ class CostField { * Set the cost field values. * * @param cells Cost field values. + * @param changed Time cost is set. */ - void set_costs(std::vector &&cells); + void set_costs(std::vector &&cells, time::time_t &changed); + + /** + * Check if the cost field is dirty at the specified time. + * + * @param time Specified time to check. + */ + bool is_dirty(time::time_t &time); private: /** @@ -104,6 +124,8 @@ class CostField { */ size_t size; + time::time_t changed; + /** * Cost field values. */ diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index 483261ebaf..fbe022e8ef 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -33,15 +33,17 @@ void path_demo_0(const util::Path &path) { // Cost field with some obstacles auto cost_field = std::make_shared(field_length); - cost_field->set_cost(coord::tile_delta{0, 0}, COST_IMPASSABLE); - cost_field->set_cost(coord::tile_delta{1, 0}, 254); - cost_field->set_cost(coord::tile_delta{4, 3}, 128); - cost_field->set_cost(coord::tile_delta{5, 3}, 128); - cost_field->set_cost(coord::tile_delta{6, 3}, 128); - cost_field->set_cost(coord::tile_delta{4, 4}, 128); - cost_field->set_cost(coord::tile_delta{5, 4}, 128); - cost_field->set_cost(coord::tile_delta{6, 4}, 128); - cost_field->set_cost(coord::tile_delta{1, 7}, COST_IMPASSABLE); + + time::time_t time = time::TIME_ZERO; + cost_field->set_cost(coord::tile_delta{0, 0}, COST_IMPASSABLE, time); + cost_field->set_cost(coord::tile_delta{1, 0}, 254, time); + cost_field->set_cost(coord::tile_delta{4, 3}, 128, time); + cost_field->set_cost(coord::tile_delta{5, 3}, 128, time); + cost_field->set_cost(coord::tile_delta{6, 3}, 128, time); + cost_field->set_cost(coord::tile_delta{4, 4}, 128, time); + cost_field->set_cost(coord::tile_delta{5, 4}, 128, time); + cost_field->set_cost(coord::tile_delta{6, 4}, 128, time); + cost_field->set_cost(coord::tile_delta{1, 7}, COST_IMPASSABLE, time); log::log(INFO << "Created cost field"); // Create an integration field from the cost field diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp index 1edeba52a1..ecb0e4cb63 100644 --- a/libopenage/pathfinding/demo/demo_1.cpp +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -11,6 +11,8 @@ #include "pathfinding/portal.h" #include "pathfinding/sector.h" #include "util/timer.h" +#include "time/time_loop.h" +#include "time/clock.h" #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" @@ -25,13 +27,17 @@ namespace openage::path::tests { void path_demo_1(const util::Path &path) { + auto time_loop = std::make_shared(); + time_loop->run(); + auto clock = time_loop->get_clock(); auto grid = std::make_shared(0, util::Vector2s{4, 3}, 10); + time::time_t time = clock->get_time(); // Initialize the cost field for each sector. for (auto §or : grid->get_sectors()) { auto cost_field = sector->get_cost_field(); std::vector sector_cost = sectors_cost.at(sector->get_id()); - cost_field->set_costs(std::move(sector_cost)); + cost_field->set_costs(std::move(sector_cost), time); } // Initialize portals between sectors. @@ -87,16 +93,20 @@ void path_demo_1(const util::Path &path) { coord::tile start{2, 26}; coord::tile target{36, 2}; + time::time_t request_time = clock->get_time(); + PathRequest path_request{ grid->get_id(), start, target, + request_time }; + + log::log(INFO << "Pathfinding request at " << request_time); grid->init_portal_nodes(); timer.start(); Path path_result = pathfinder->get_path(path_request); timer.stop(); - log::log(INFO << "Pathfinding took " << timer.getval() / 1000 << " us"); // Create a renderer to display the grid and path @@ -127,6 +137,7 @@ void path_demo_1(const util::Path &path) { grid->get_id(), start, target, + clock->get_time() }; timer.reset(); @@ -147,6 +158,7 @@ void path_demo_1(const util::Path &path) { grid->get_id(), start, target, + clock->get_time() }; timer.reset(); diff --git a/libopenage/pathfinding/field_cache.cpp b/libopenage/pathfinding/field_cache.cpp new file mode 100644 index 0000000000..9d144e1cc4 --- /dev/null +++ b/libopenage/pathfinding/field_cache.cpp @@ -0,0 +1,32 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "field_cache.h" + +namespace openage::path { + +void FieldCache::add(std::pair cache_key, + std::shared_ptr &integration_field, + std::shared_ptr &flow_field) { + this->cache[cache_key] = std::make_pair(integration_field, flow_field); +} + +void FieldCache::evict(std::pair cache_key) { + this->cache.erase(cache_key); +} + +bool FieldCache::is_cached(std::pair cache_key) { + auto cached = this->cache.find(cache_key); + return cached != this->cache.end(); +} + +std::shared_ptr FieldCache::get_integration_field(std::pair cache_key) { + auto cached = this->cache.find(cache_key); + return cached->second.first; +} + +std::shared_ptr FieldCache::get_flow_field(std::pair cache_key) { + auto cached = this->cache.find(cache_key); + return cached->second.second; +} + +} // namespace openage::path \ No newline at end of file diff --git a/libopenage/pathfinding/field_cache.h b/libopenage/pathfinding/field_cache.h new file mode 100644 index 0000000000..7b98c4d957 --- /dev/null +++ b/libopenage/pathfinding/field_cache.h @@ -0,0 +1,66 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include "pathfinding/types.h" +#include "util/hash.h" + +namespace openage { +namespace coord { +struct tile_delta; +} // namespace coord + +namespace path { +class IntegrationField; +class FlowField; + +class FieldCache +{ +public: + FieldCache() = default; + ~FieldCache() = default; + + void add(std::pair cache_key, + std::shared_ptr &integration_field, + std::shared_ptr &flow_field); + + void evict(std::pair cache_key); + + bool is_cached(std::pair cache_key); + + std::shared_ptr get_integration_field(std::pair cache_key); + + std::shared_ptr get_flow_field(std::pair cache_key); + + using get_return_t = std::pair, std::shared_ptr>; + +private: + /** + * Hash function for the field cache. + */ + struct pair_hash { + template + std::size_t operator()(const std::pair &pair) const { + return util::hash_combine(std::hash{}(pair.first), std::hash{}(pair.second)); + } + }; + + /** + * Cache for already computed fields. + * + * Key is the portal ID and the sector ID from which the field was entered. Fields that are cached are + * cleared of dynamic flags, i.e. wavefront or LOS flags. These have to be recalculated + * when the field is reused. + */ + + std::unordered_map, + get_return_t, + pair_hash> + cache; +}; + +} // namespace path +} // namespace openage \ No newline at end of file diff --git a/libopenage/pathfinding/integrator.cpp b/libopenage/pathfinding/integrator.cpp index 265c5285cc..d91ac9ecb7 100644 --- a/libopenage/pathfinding/integrator.cpp +++ b/libopenage/pathfinding/integrator.cpp @@ -8,6 +8,7 @@ #include "pathfinding/flow_field.h" #include "pathfinding/integration_field.h" #include "pathfinding/portal.h" +#include "pathfinding/field_cache.h" namespace openage::path { @@ -36,24 +37,32 @@ std::shared_ptr Integrator::integrate(const std::shared_ptr &portal, const coord::tile_delta &target, - bool with_los) { + bool with_los, + bool evict_cache) { auto cache_key = std::make_pair(portal->get_id(), other_sector_id); - auto cached = this->field_cache.find(cache_key); - if (cached != this->field_cache.end()) { + if (evict_cache) { + this->field_cache->evict(cache_key); + } + if (this->field_cache->is_cached(cache_key)) { log::log(DBG << "Using cached integration field for portal " << portal->get_id() << " from sector " << other_sector_id); + + + //retrieve cached integration field + auto cached_integration_field = this->field_cache->get_integration_field(cache_key); + if (with_los) { log::log(SPAM << "Performing LOS pass on cached field"); // Make a copy of the cached field to avoid modifying the cached field - auto integration_field = std::make_shared(*cached->second.first); + auto integration_field = std::make_shared(*cached_integration_field); // Only integrate LOS; leave the rest of the field as is integration_field->integrate_los(cost_field, other, other_sector_id, portal, target); return integration_field; } - return cached->second.first; + return cached_integration_field; } log::log(DBG << "Integrating cost field for portal " << portal->get_id() @@ -97,17 +106,24 @@ std::shared_ptr Integrator::build(const std::shared_ptr &other, sector_id_t other_sector_id, const std::shared_ptr &portal, - bool with_los) { + bool with_los, + bool evict_cache) { auto cache_key = std::make_pair(portal->get_id(), other_sector_id); - auto cached = this->field_cache.find(cache_key); - if (cached != this->field_cache.end()) { + if (evict_cache) { + this->field_cache->evict(cache_key); + } + if (this->field_cache->is_cached(cache_key)) { log::log(DBG << "Using cached flow field for portal " << portal->get_id() << " from sector " << other_sector_id); + + //retrieve cached flow field + auto cached_flow_field = this->field_cache->get_flow_field(cache_key); + if (with_los) { log::log(SPAM << "Transferring LOS flags to cached flow field"); // Make a copy of the cached flow field - auto flow_field = std::make_shared(*cached->second.second); + auto flow_field = std::make_shared(*cached_flow_field); // Transfer the LOS flags to the flow field flow_field->transfer_dynamic_flags(integration_field); @@ -115,7 +131,7 @@ std::shared_ptr Integrator::build(const std::shared_ptrsecond.second; + return cached_flow_field; } log::log(DBG << "Building flow field for portal " << portal->get_id() @@ -140,17 +156,27 @@ Integrator::get_return_t Integrator::get(const std::shared_ptr &cost_ sector_id_t other_sector_id, const std::shared_ptr &portal, const coord::tile_delta &target, - bool with_los) { + bool with_los, + bool evict_cache) { auto cache_key = std::make_pair(portal->get_id(), other_sector_id); - auto cached = this->field_cache.find(cache_key); - if (cached != this->field_cache.end()) { + if (evict_cache) { + this->field_cache->evict(cache_key); + } + if (this->field_cache->is_cached(cache_key)) { log::log(DBG << "Using cached integration and flow fields for portal " << portal->get_id() << " from sector " << other_sector_id); + + + //retrieve cached fields + auto cached_integration_field = this->field_cache->get_integration_field(cache_key); + auto cached_flow_field = this->field_cache->get_flow_field(cache_key); + + if (with_los) { log::log(SPAM << "Performing LOS pass on cached field"); // Make a copy of the cached integration field - auto integration_field = std::make_shared(*cached->second.first); + auto integration_field = std::make_shared(*cached_integration_field); // Only integrate LOS; leave the rest of the field as is integration_field->integrate_los(cost_field, other, other_sector_id, portal, target); @@ -158,7 +184,7 @@ Integrator::get_return_t Integrator::get(const std::shared_ptr &cost_ log::log(SPAM << "Transferring LOS flags to cached flow field"); // Make a copy of the cached flow field - auto flow_field = std::make_shared(*cached->second.second); + auto flow_field = std::make_shared(*cached_flow_field); // Transfer the LOS flags to the flow field flow_field->transfer_dynamic_flags(integration_field); @@ -166,11 +192,11 @@ Integrator::get_return_t Integrator::get(const std::shared_ptr &cost_ return std::make_pair(integration_field, flow_field); } - return cached->second; + return std::make_pair(cached_integration_field, cached_flow_field); } - auto integration_field = this->integrate(cost_field, other, other_sector_id, portal, target, with_los); - auto flow_field = this->build(integration_field, other, other_sector_id, portal); + auto integration_field = this->integrate(cost_field, other, other_sector_id, portal, target, with_los, evict_cache); + auto flow_field = this->build(integration_field, other, other_sector_id, portal, evict_cache); log::log(DBG << "Caching integration and flow fields for portal ID: " << portal->get_id() << ", sector ID: " << other_sector_id); @@ -182,7 +208,7 @@ Integrator::get_return_t Integrator::get(const std::shared_ptr &cost_ std::shared_ptr cached_flow_field = std::make_shared(*flow_field); cached_flow_field->reset_dynamic_flags(); - this->field_cache[cache_key] = std::make_pair(cached_integration_field, cached_flow_field); + this->field_cache->add(cache_key, cached_integration_field, cached_flow_field); return std::make_pair(integration_field, flow_field); } diff --git a/libopenage/pathfinding/integrator.h b/libopenage/pathfinding/integrator.h index c27d3c1eb1..690c525ad7 100644 --- a/libopenage/pathfinding/integrator.h +++ b/libopenage/pathfinding/integrator.h @@ -2,9 +2,7 @@ #pragma once -#include #include -#include #include "pathfinding/types.h" #include "util/hash.h" @@ -20,6 +18,7 @@ class CostField; class FlowField; class IntegrationField; class Portal; +class FieldCache; /** * Integrator for the flow field pathfinding algorithm. @@ -65,7 +64,8 @@ class Integrator { sector_id_t other_sector_id, const std::shared_ptr &portal, const coord::tile_delta &target, - bool with_los = true); + bool with_los = true, + bool evict_cache = false); /** * Build the flow field from an integration field. @@ -91,7 +91,8 @@ class Integrator { const std::shared_ptr &other, sector_id_t other_sector_id, const std::shared_ptr &portal, - bool with_los = true); + bool with_los = true, + bool evict_cache = false); using get_return_t = std::pair, std::shared_ptr>; @@ -123,30 +124,11 @@ class Integrator { sector_id_t other_sector_id, const std::shared_ptr &portal, const coord::tile_delta &target, - bool with_los = true); + bool with_los = true, + bool evict_cache = false); private: - /** - * Hash function for the field cache. - */ - struct pair_hash { - template - std::size_t operator()(const std::pair &pair) const { - return util::hash_combine(std::hash{}(pair.first), std::hash{}(pair.second)); - } - }; - - /** - * Cache for already computed fields. - * - * Key is the portal ID and the sector ID from which the field was entered. Fields that are cached are - * cleared of dynamic flags, i.e. wavefront or LOS flags. These have to be recalculated - * when the field is reused. - */ - std::unordered_map, - get_return_t, - pair_hash> - field_cache; + std::shared_ptr field_cache; }; } // namespace path diff --git a/libopenage/pathfinding/path.h b/libopenage/pathfinding/path.h index d1c61ac325..cfeb7bc503 100644 --- a/libopenage/pathfinding/path.h +++ b/libopenage/pathfinding/path.h @@ -6,6 +6,7 @@ #include "coord/tile.h" #include "pathfinding/types.h" +#include "time/time.h" namespace openage::path { @@ -20,6 +21,8 @@ struct PathRequest { coord::tile start; /// Target position of the path. coord::tile target; + /// Time request was made. + time::time_t time; }; /** @@ -34,6 +37,8 @@ struct Path { /// First waypoint is the start position of the path request. /// Last waypoint is the target position of the path request. std::vector waypoints; + /// Time path was created. + time::time_t time; }; } // namespace openage::path diff --git a/libopenage/pathfinding/pathfinder.cpp b/libopenage/pathfinding/pathfinder.cpp index c99afd5b9d..8aec42a514 100644 --- a/libopenage/pathfinding/pathfinder.cpp +++ b/libopenage/pathfinding/pathfinder.cpp @@ -155,7 +155,8 @@ const Path Pathfinder::get_path(const PathRequest &request) { prev_sector_id, portal, target_delta, - with_los); + with_los, + next_sector->is_dirty(request.time)); flow_fields.push_back(std::make_pair(next_sector_id, sector_fields.second)); prev_integration_field = sector_fields.first; diff --git a/libopenage/pathfinding/sector.cpp b/libopenage/pathfinding/sector.cpp index f50dc209bf..08843fe016 100644 --- a/libopenage/pathfinding/sector.cpp +++ b/libopenage/pathfinding/sector.cpp @@ -249,4 +249,8 @@ void Sector::connect_exits() { } } +bool Sector::is_dirty(time::time_t time) { + return this->cost_field->is_dirty(time); +} + } // namespace openage::path diff --git a/libopenage/pathfinding/sector.h b/libopenage/pathfinding/sector.h index a4ba94fa52..cc2950f25e 100644 --- a/libopenage/pathfinding/sector.h +++ b/libopenage/pathfinding/sector.h @@ -9,6 +9,7 @@ #include "coord/chunk.h" #include "pathfinding/portal.h" #include "pathfinding/types.h" +#include "time/time.h" namespace openage::path { @@ -103,6 +104,13 @@ class Sector { */ void connect_exits(); + /** + * Check if the cost field is dirty at the specified time. + * + * @param time Specified time to check. + */ + bool is_dirty(time::time_t time); + private: /** * ID of the sector. diff --git a/libopenage/pathfinding/tests.cpp b/libopenage/pathfinding/tests.cpp index 5f92496ff8..6ec26b392d 100644 --- a/libopenage/pathfinding/tests.cpp +++ b/libopenage/pathfinding/tests.cpp @@ -10,6 +10,7 @@ #include "pathfinding/integration_field.h" #include "pathfinding/integrator.h" #include "pathfinding/types.h" +#include "time/time.h" namespace openage { @@ -23,7 +24,8 @@ void flow_field() { // | 1 | 1 | 1 | // | 1 | X | 1 | // | 1 | 1 | 1 | - cost_field->set_costs({1, 1, 1, 1, 255, 1, 1, 1, 1}); + time::time_t time = time::TIME_ZERO; + cost_field->set_costs({1, 1, 1, 1, 255, 1, 1, 1, 1}, time); // Test the different field types { From 2110ff21c568314987df89cd5df5d70772f38465 Mon Sep 17 00:00:00 2001 From: dmwever <56411717+dmwever@users.noreply.github.com> Date: Thu, 5 Dec 2024 19:50:20 -0600 Subject: [PATCH 031/152] Update libopenage/pathfinding/cost_field.cpp Co-authored-by: Christoph Heine <6852422+heinezen@users.noreply.github.com> --- libopenage/pathfinding/cost_field.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libopenage/pathfinding/cost_field.cpp b/libopenage/pathfinding/cost_field.cpp index 4ad1318874..d4ae7f8464 100644 --- a/libopenage/pathfinding/cost_field.cpp +++ b/libopenage/pathfinding/cost_field.cpp @@ -14,7 +14,7 @@ namespace openage::path { CostField::CostField(size_t size) : size{size}, cells(this->size * this->size, COST_MIN), - changed{time::time_t::min_value()} { + changed{time::TIME_MIN} { log::log(DBG << "Created cost field with size " << this->size << "x" << this->size); } From 5dcc8a82cdf8b354fb3126ae1237cd47f262308d Mon Sep 17 00:00:00 2001 From: dmwever <56411717+dmwever@users.noreply.github.com> Date: Thu, 5 Dec 2024 20:26:51 -0600 Subject: [PATCH 032/152] Add const to time_t and improve docstrings --- libopenage/pathfinding/cost_field.cpp | 10 +++++----- libopenage/pathfinding/cost_field.h | 25 ++++++++++++++----------- libopenage/pathfinding/demo/demo_0.cpp | 2 +- libopenage/pathfinding/demo/demo_1.cpp | 6 +++--- libopenage/pathfinding/field_cache.cpp | 2 +- libopenage/pathfinding/field_cache.h | 2 +- libopenage/pathfinding/path.h | 4 ++-- libopenage/pathfinding/sector.cpp | 2 +- libopenage/pathfinding/sector.h | 2 +- libopenage/pathfinding/tests.cpp | 2 +- 10 files changed, 30 insertions(+), 27 deletions(-) diff --git a/libopenage/pathfinding/cost_field.cpp b/libopenage/pathfinding/cost_field.cpp index d4ae7f8464..c211d169c1 100644 --- a/libopenage/pathfinding/cost_field.cpp +++ b/libopenage/pathfinding/cost_field.cpp @@ -34,12 +34,12 @@ cost_t CostField::get_cost(size_t idx) const { return this->cells.at(idx); } -void CostField::set_cost(const coord::tile_delta &pos, cost_t cost, time::time_t &changed) { +void CostField::set_cost(const coord::tile_delta &pos, cost_t cost, const time::time_t &changed) { this->cells[pos.ne + pos.se * this->size] = cost; this->changed = changed; } -void CostField::set_cost(size_t x, size_t y, cost_t cost, time::time_t &changed) { +void CostField::set_cost(size_t x, size_t y, cost_t cost, const time::time_t &changed) { this->cells[x + y * this->size] = cost; this->changed = changed; } @@ -49,7 +49,7 @@ void CostField::set_cost(size_t idx, cost_t cost) { this->changed = time::TIME_ZERO; } -void CostField::set_cost(size_t idx, cost_t cost,time::time_t &changed) { +void CostField::set_cost(size_t idx, cost_t cost, const time::time_t &changed) { this->cells[idx] = cost; this->changed = changed; } @@ -58,7 +58,7 @@ const std::vector &CostField::get_costs() const { return this->cells; } -void CostField::set_costs(std::vector &&cells, time::time_t &changed) { +void CostField::set_costs(std::vector &&cells, const time::time_t &changed) { ENSURE(cells.size() == this->cells.size(), "cells vector has wrong size: " << cells.size() << "; expected: " @@ -68,7 +68,7 @@ void CostField::set_costs(std::vector &&cells, time::time_t &changed) { this->changed = changed; } -bool CostField::is_dirty(time::time_t &time) { +bool CostField::is_dirty(const time::time_t &time) { return time <= this->changed; } diff --git a/libopenage/pathfinding/cost_field.h b/libopenage/pathfinding/cost_field.h index 99dd16306c..e62496acf1 100644 --- a/libopenage/pathfinding/cost_field.h +++ b/libopenage/pathfinding/cost_field.h @@ -65,9 +65,9 @@ class CostField { * * @param pos Coordinates of the cell (relative to field origin). * @param cost Cost to set. - * @param changed Time cost is set. + * @param changed Time at which the cost value is changed. */ - void set_cost(const coord::tile_delta &pos, cost_t cost, time::time_t &changed); + void set_cost(const coord::tile_delta &pos, cost_t cost, const time::time_t &changed); /** * Set the cost at a specified position. @@ -75,9 +75,9 @@ class CostField { * @param x X-coordinate of the cell. * @param y Y-coordinate of the cell. * @param cost Cost to set. - * @param changed Time cost is set. + * @param changed Time at which the cost value is changed. */ - void set_cost(size_t x, size_t y, cost_t cost, time::time_t &changed); + void set_cost(size_t x, size_t y, cost_t cost, const time::time_t &changed); /** * Set the cost at a specified position. @@ -92,9 +92,9 @@ class CostField { * * @param idx Index of the cell. * @param cost Cost to set. - * @param changed Time cost is set. + * @param changed Time at which the cost value is changed. */ - void set_cost(size_t idx, cost_t cost, time::time_t &changed); + void set_cost(size_t idx, cost_t cost, const time::time_t &changed); /** * Get the cost field values. @@ -107,16 +107,16 @@ class CostField { * Set the cost field values. * * @param cells Cost field values. - * @param changed Time cost is set. + * @param changed Time at which the cost value is changed. */ - void set_costs(std::vector &&cells, time::time_t &changed); + void set_costs(std::vector &&cells, const time::time_t &changed); /** * Check if the cost field is dirty at the specified time. * - * @param time Specified time to check. + * @param time Cost field is dirty if the cost field is accessed before the latest change to the cost field. */ - bool is_dirty(time::time_t &time); + bool is_dirty(const time::time_t &time); private: /** @@ -124,7 +124,10 @@ class CostField { */ size_t size; - time::time_t changed; + /** + * Time the cost field was last changed. + */ + const time::time_t changed; /** * Cost field values. diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index fbe022e8ef..2dfb825fa0 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -34,7 +34,7 @@ void path_demo_0(const util::Path &path) { // Cost field with some obstacles auto cost_field = std::make_shared(field_length); - time::time_t time = time::TIME_ZERO; + const time::time_t time = time::TIME_ZERO; cost_field->set_cost(coord::tile_delta{0, 0}, COST_IMPASSABLE, time); cost_field->set_cost(coord::tile_delta{1, 0}, 254, time); cost_field->set_cost(coord::tile_delta{4, 3}, 128, time); diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp index ecb0e4cb63..a31b9b786b 100644 --- a/libopenage/pathfinding/demo/demo_1.cpp +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -32,7 +32,7 @@ void path_demo_1(const util::Path &path) { auto clock = time_loop->get_clock(); auto grid = std::make_shared(0, util::Vector2s{4, 3}, 10); - time::time_t time = clock->get_time(); + const time::time_t time = clock->get_time(); // Initialize the cost field for each sector. for (auto §or : grid->get_sectors()) { auto cost_field = sector->get_cost_field(); @@ -93,7 +93,7 @@ void path_demo_1(const util::Path &path) { coord::tile start{2, 26}; coord::tile target{36, 2}; - time::time_t request_time = clock->get_time(); + const time::time_t request_time = clock->get_time(); PathRequest path_request{ grid->get_id(), @@ -102,11 +102,11 @@ void path_demo_1(const util::Path &path) { request_time }; - log::log(INFO << "Pathfinding request at " << request_time); grid->init_portal_nodes(); timer.start(); Path path_result = pathfinder->get_path(path_request); timer.stop(); + log::log(INFO << "Pathfinding request at " << request_time); log::log(INFO << "Pathfinding took " << timer.getval() / 1000 << " us"); // Create a renderer to display the grid and path diff --git a/libopenage/pathfinding/field_cache.cpp b/libopenage/pathfinding/field_cache.cpp index 9d144e1cc4..a798e9fbd7 100644 --- a/libopenage/pathfinding/field_cache.cpp +++ b/libopenage/pathfinding/field_cache.cpp @@ -29,4 +29,4 @@ std::shared_ptr FieldCache::get_flow_field(std::pairsecond.second; } -} // namespace openage::path \ No newline at end of file +} // namespace openage::path diff --git a/libopenage/pathfinding/field_cache.h b/libopenage/pathfinding/field_cache.h index 7b98c4d957..a1f43a563e 100644 --- a/libopenage/pathfinding/field_cache.h +++ b/libopenage/pathfinding/field_cache.h @@ -63,4 +63,4 @@ class FieldCache }; } // namespace path -} // namespace openage \ No newline at end of file +} // namespace openage diff --git a/libopenage/pathfinding/path.h b/libopenage/pathfinding/path.h index cfeb7bc503..ca6391bf67 100644 --- a/libopenage/pathfinding/path.h +++ b/libopenage/pathfinding/path.h @@ -22,7 +22,7 @@ struct PathRequest { /// Target position of the path. coord::tile target; /// Time request was made. - time::time_t time; + const time::time_t time; }; /** @@ -38,7 +38,7 @@ struct Path { /// Last waypoint is the target position of the path request. std::vector waypoints; /// Time path was created. - time::time_t time; + const time::time_t time; }; } // namespace openage::path diff --git a/libopenage/pathfinding/sector.cpp b/libopenage/pathfinding/sector.cpp index 08843fe016..4556fd42c3 100644 --- a/libopenage/pathfinding/sector.cpp +++ b/libopenage/pathfinding/sector.cpp @@ -249,7 +249,7 @@ void Sector::connect_exits() { } } -bool Sector::is_dirty(time::time_t time) { +bool Sector::is_dirty(const time::time_t time) { return this->cost_field->is_dirty(time); } diff --git a/libopenage/pathfinding/sector.h b/libopenage/pathfinding/sector.h index cc2950f25e..e0f751b70d 100644 --- a/libopenage/pathfinding/sector.h +++ b/libopenage/pathfinding/sector.h @@ -109,7 +109,7 @@ class Sector { * * @param time Specified time to check. */ - bool is_dirty(time::time_t time); + bool is_dirty(const time::time_t time); private: /** diff --git a/libopenage/pathfinding/tests.cpp b/libopenage/pathfinding/tests.cpp index 6ec26b392d..2ac0960c34 100644 --- a/libopenage/pathfinding/tests.cpp +++ b/libopenage/pathfinding/tests.cpp @@ -24,7 +24,7 @@ void flow_field() { // | 1 | 1 | 1 | // | 1 | X | 1 | // | 1 | 1 | 1 | - time::time_t time = time::TIME_ZERO; + const time::time_t time = time::TIME_ZERO; cost_field->set_costs({1, 1, 1, 1, 255, 1, 1, 1, 1}, time); // Test the different field types From 04211fac4990272578ca70e8b5d5725a9e56edeb Mon Sep 17 00:00:00 2001 From: dmwever <56411717+dmwever@users.noreply.github.com> Date: Thu, 5 Dec 2024 20:28:14 -0600 Subject: [PATCH 033/152] change sector is_dirty to a reference --- libopenage/pathfinding/sector.cpp | 2 +- libopenage/pathfinding/sector.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libopenage/pathfinding/sector.cpp b/libopenage/pathfinding/sector.cpp index 4556fd42c3..2da73690c0 100644 --- a/libopenage/pathfinding/sector.cpp +++ b/libopenage/pathfinding/sector.cpp @@ -249,7 +249,7 @@ void Sector::connect_exits() { } } -bool Sector::is_dirty(const time::time_t time) { +bool Sector::is_dirty(const time::time_t &time) { return this->cost_field->is_dirty(time); } diff --git a/libopenage/pathfinding/sector.h b/libopenage/pathfinding/sector.h index e0f751b70d..a8aad24c01 100644 --- a/libopenage/pathfinding/sector.h +++ b/libopenage/pathfinding/sector.h @@ -109,7 +109,7 @@ class Sector { * * @param time Specified time to check. */ - bool is_dirty(const time::time_t time); + bool is_dirty(const time::time_t &time); private: /** From 7f75f5309c452ac8ad7c836f0d5862aec7fb35e8 Mon Sep 17 00:00:00 2001 From: dmwever <56411717+dmwever@users.noreply.github.com> Date: Thu, 5 Dec 2024 22:56:07 -0600 Subject: [PATCH 034/152] Change fieldCache to unique_ptr and add docs --- libopenage/pathfinding/field_cache.h | 1 - libopenage/pathfinding/integrator.h | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/libopenage/pathfinding/field_cache.h b/libopenage/pathfinding/field_cache.h index a1f43a563e..87dbdf60f9 100644 --- a/libopenage/pathfinding/field_cache.h +++ b/libopenage/pathfinding/field_cache.h @@ -55,7 +55,6 @@ class FieldCache * cleared of dynamic flags, i.e. wavefront or LOS flags. These have to be recalculated * when the field is reused. */ - std::unordered_map, get_return_t, pair_hash> diff --git a/libopenage/pathfinding/integrator.h b/libopenage/pathfinding/integrator.h index 690c525ad7..7d09c00a73 100644 --- a/libopenage/pathfinding/integrator.h +++ b/libopenage/pathfinding/integrator.h @@ -128,7 +128,11 @@ class Integrator { bool evict_cache = false); private: - std::shared_ptr field_cache; + + /** + * Cache for already computed fields. + */ + std::unique_ptr field_cache; }; } // namespace path From 219431d9b29b9af2661fa5675bce6a3ed2e99989 Mon Sep 17 00:00:00 2001 From: dmwever <56411717+dmwever@users.noreply.github.com> Date: Thu, 5 Dec 2024 22:57:25 -0600 Subject: [PATCH 035/152] Grammar Co-authored-by: Christoph Heine <6852422+heinezen@users.noreply.github.com> --- libopenage/pathfinding/path.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libopenage/pathfinding/path.h b/libopenage/pathfinding/path.h index ca6391bf67..8be7f802f7 100644 --- a/libopenage/pathfinding/path.h +++ b/libopenage/pathfinding/path.h @@ -37,7 +37,7 @@ struct Path { /// First waypoint is the start position of the path request. /// Last waypoint is the target position of the path request. std::vector waypoints; - /// Time path was created. + /// Time the path was created. const time::time_t time; }; From 305b41264d48904f4dead1bae19f7c0cbc311f9e Mon Sep 17 00:00:00 2001 From: dmwever <56411717+dmwever@users.noreply.github.com> Date: Thu, 5 Dec 2024 22:57:41 -0600 Subject: [PATCH 036/152] Grammar Co-authored-by: Christoph Heine <6852422+heinezen@users.noreply.github.com> --- libopenage/pathfinding/path.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libopenage/pathfinding/path.h b/libopenage/pathfinding/path.h index 8be7f802f7..2d1f1cd593 100644 --- a/libopenage/pathfinding/path.h +++ b/libopenage/pathfinding/path.h @@ -21,7 +21,7 @@ struct PathRequest { coord::tile start; /// Target position of the path. coord::tile target; - /// Time request was made. + /// Time the request was made. const time::time_t time; }; From 94101cbcd6edc1d73fd0284e848dd7fdb88eecae Mon Sep 17 00:00:00 2001 From: dmwever <56411717+dmwever@users.noreply.github.com> Date: Thu, 5 Dec 2024 23:03:37 -0600 Subject: [PATCH 037/152] Use contains() Co-authored-by: Christoph Heine <6852422+heinezen@users.noreply.github.com> --- libopenage/pathfinding/field_cache.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libopenage/pathfinding/field_cache.cpp b/libopenage/pathfinding/field_cache.cpp index a798e9fbd7..81b21c8264 100644 --- a/libopenage/pathfinding/field_cache.cpp +++ b/libopenage/pathfinding/field_cache.cpp @@ -15,8 +15,7 @@ void FieldCache::evict(std::pair cache_k } bool FieldCache::is_cached(std::pair cache_key) { - auto cached = this->cache.find(cache_key); - return cached != this->cache.end(); + return this->cache.contains(cache_key); } std::shared_ptr FieldCache::get_integration_field(std::pair cache_key) { From 3cfdb11893650e3e7465476d32d555df86469aba Mon Sep 17 00:00:00 2001 From: dmwever <56411717+dmwever@users.noreply.github.com> Date: Sat, 7 Dec 2024 12:04:36 -0600 Subject: [PATCH 038/152] several changes --- libopenage/gamestate/map.cpp | 2 +- libopenage/pathfinding/cost_field.cpp | 31 ++++++++--------- libopenage/pathfinding/cost_field.h | 21 +++++------- libopenage/pathfinding/field_cache.cpp | 27 ++++++++------- libopenage/pathfinding/field_cache.h | 46 +++++++++++++++++--------- libopenage/pathfinding/integrator.cpp | 22 ++++++------ libopenage/pathfinding/types.h | 15 +++++++++ 7 files changed, 92 insertions(+), 72 deletions(-) diff --git a/libopenage/gamestate/map.cpp b/libopenage/gamestate/map.cpp index 8300ce273d..88578fabdb 100644 --- a/libopenage/gamestate/map.cpp +++ b/libopenage/gamestate/map.cpp @@ -49,7 +49,7 @@ Map::Map(const std::shared_ptr &state, auto sector = grid->get_sector(chunk_idx); auto cost_field = sector->get_cost_field(); - cost_field->set_cost(tile_idx, path_cost.second); + cost_field->set_cost(tile_idx, path_cost.second, time::TIME_ZERO); } } } diff --git a/libopenage/pathfinding/cost_field.cpp b/libopenage/pathfinding/cost_field.cpp index c211d169c1..d737f550c0 100644 --- a/libopenage/pathfinding/cost_field.cpp +++ b/libopenage/pathfinding/cost_field.cpp @@ -14,7 +14,7 @@ namespace openage::path { CostField::CostField(size_t size) : size{size}, cells(this->size * this->size, COST_MIN), - changed{time::TIME_MIN} { + valid_until{time::TIME_MIN} { log::log(DBG << "Created cost field with size " << this->size << "x" << this->size); } @@ -34,42 +34,39 @@ cost_t CostField::get_cost(size_t idx) const { return this->cells.at(idx); } -void CostField::set_cost(const coord::tile_delta &pos, cost_t cost, const time::time_t &changed) { - this->cells[pos.ne + pos.se * this->size] = cost; - this->changed = changed; +void CostField::set_cost(const coord::tile_delta &pos, cost_t cost, const time::time_t &valid_until) { + this->set_cost(pos.ne + pos.se * this->size, cost, valid_until); } -void CostField::set_cost(size_t x, size_t y, cost_t cost, const time::time_t &changed) { - this->cells[x + y * this->size] = cost; - this->changed = changed; +void CostField::set_cost(size_t x, size_t y, cost_t cost, const time::time_t &valid_until) { + this->set_cost(x + y * this->size, cost, valid_until); } -void CostField::set_cost(size_t idx, cost_t cost) { +inline void CostField::set_cost(size_t idx, cost_t cost, const time::time_t &valid_until) { this->cells[idx] = cost; - this->changed = time::TIME_ZERO; -} - -void CostField::set_cost(size_t idx, cost_t cost, const time::time_t &changed) { - this->cells[idx] = cost; - this->changed = changed; + this->valid_until = valid_until; } const std::vector &CostField::get_costs() const { return this->cells; } -void CostField::set_costs(std::vector &&cells, const time::time_t &changed) { +void CostField::set_costs(std::vector &&cells, const time::time_t &valid_until) { ENSURE(cells.size() == this->cells.size(), "cells vector has wrong size: " << cells.size() << "; expected: " << this->cells.size()); this->cells = std::move(cells); - this->changed = changed; + this->valid_until = valid_until; } bool CostField::is_dirty(const time::time_t &time) { - return time <= this->changed; + return time >= this->valid_until; +} + +void CostField::clean() { + this->valid_until = time::TIME_MAX; } } // namespace openage::path diff --git a/libopenage/pathfinding/cost_field.h b/libopenage/pathfinding/cost_field.h index e62496acf1..f70adf9251 100644 --- a/libopenage/pathfinding/cost_field.h +++ b/libopenage/pathfinding/cost_field.h @@ -79,14 +79,6 @@ class CostField { */ void set_cost(size_t x, size_t y, cost_t cost, const time::time_t &changed); - /** - * Set the cost at a specified position. - * - * @param idx Index of the cell. - * @param cost Cost to set. - */ - void set_cost(size_t idx, cost_t cost); - /** * Set the cost at a specified position. * @@ -94,7 +86,7 @@ class CostField { * @param cost Cost to set. * @param changed Time at which the cost value is changed. */ - void set_cost(size_t idx, cost_t cost, const time::time_t &changed); + inline void set_cost(size_t idx, cost_t cost, const time::time_t &changed); /** * Get the cost field values. @@ -113,11 +105,16 @@ class CostField { /** * Check if the cost field is dirty at the specified time. - * - * @param time Cost field is dirty if the cost field is accessed before the latest change to the cost field. + * + * @param time Cost field is dirty if the cost field is accessed after the time given in valid_until. */ bool is_dirty(const time::time_t &time); + /** + * Cleans the dirty flag by setting it to time_MAX. + */ + void clean(); + private: /** * Side length of the field. @@ -127,7 +124,7 @@ class CostField { /** * Time the cost field was last changed. */ - const time::time_t changed; + time::time_t &valid_until; /** * Cost field values. diff --git a/libopenage/pathfinding/field_cache.cpp b/libopenage/pathfinding/field_cache.cpp index 81b21c8264..95276b0818 100644 --- a/libopenage/pathfinding/field_cache.cpp +++ b/libopenage/pathfinding/field_cache.cpp @@ -4,28 +4,27 @@ namespace openage::path { -void FieldCache::add(std::pair cache_key, - std::shared_ptr &integration_field, - std::shared_ptr &flow_field) { - this->cache[cache_key] = std::make_pair(integration_field, flow_field); +void FieldCache::add(cache_key_t cache_key, + field_cache_t cache_entry) { + this->cache[cache_key] = cache_entry; } -void FieldCache::evict(std::pair cache_key) { - this->cache.erase(cache_key); +void FieldCache::evict(cache_key_t cache_key) { + this->cache.erase(cache_key); } -bool FieldCache::is_cached(std::pair cache_key) { - return this->cache.contains(cache_key); +bool FieldCache::is_cached(cache_key_t cache_key) { + return this->cache.contains(cache_key); } -std::shared_ptr FieldCache::get_integration_field(std::pair cache_key) { - auto cached = this->cache.find(cache_key); - return cached->second.first; +std::shared_ptr FieldCache::get_integration_field(cache_key_t cache_key) { + auto cached = this->cache.find(cache_key); + return cached->second.first; } -std::shared_ptr FieldCache::get_flow_field(std::pair cache_key) { - auto cached = this->cache.find(cache_key); - return cached->second.second; +std::shared_ptr FieldCache::get_flow_field(cache_key_t cache_key) { + auto cached = this->cache.find(cache_key); + return cached->second.second; } } // namespace openage::path diff --git a/libopenage/pathfinding/field_cache.h b/libopenage/pathfinding/field_cache.h index 87dbdf60f9..23374e5123 100644 --- a/libopenage/pathfinding/field_cache.h +++ b/libopenage/pathfinding/field_cache.h @@ -17,25 +17,39 @@ namespace path { class IntegrationField; class FlowField; -class FieldCache -{ +/** + * Cache to store already calculated flow and integration fields for the pathfinding algorithm. + */ +class FieldCache { public: - FieldCache() = default; - ~FieldCache() = default; + FieldCache() = default; + ~FieldCache() = default; - void add(std::pair cache_key, - std::shared_ptr &integration_field, - std::shared_ptr &flow_field); + /** + * Adds a new field cache entry to the cache with a given portal and sector cache key. + */ + void add(cache_key_t cache_key, + field_cache_t cache_entry); - void evict(std::pair cache_key); + /** + * Evicts a given field cache entry from the cache at the given cache key. + */ + void evict(cache_key_t cache_key); - bool is_cached(std::pair cache_key); + /** + * Checks if there is a cached entry at a specific cache key. + */ + bool is_cached(cache_key_t cache_key); - std::shared_ptr get_integration_field(std::pair cache_key); - - std::shared_ptr get_flow_field(std::pair cache_key); - - using get_return_t = std::pair, std::shared_ptr>; + /** + * Gets the integration field from a given cache entry. + */ + std::shared_ptr get_integration_field(cache_key_t cache_key); + + /** + * Gets the flow field from a given cache entry. + */ + std::shared_ptr get_flow_field(cache_key_t cache_key); private: /** @@ -55,8 +69,8 @@ class FieldCache * cleared of dynamic flags, i.e. wavefront or LOS flags. These have to be recalculated * when the field is reused. */ - std::unordered_map, - get_return_t, + std::unordered_map cache; }; diff --git a/libopenage/pathfinding/integrator.cpp b/libopenage/pathfinding/integrator.cpp index d91ac9ecb7..e164f3a094 100644 --- a/libopenage/pathfinding/integrator.cpp +++ b/libopenage/pathfinding/integrator.cpp @@ -5,10 +5,10 @@ #include "log/log.h" #include "pathfinding/cost_field.h" +#include "pathfinding/field_cache.h" #include "pathfinding/flow_field.h" #include "pathfinding/integration_field.h" #include "pathfinding/portal.h" -#include "pathfinding/field_cache.h" namespace openage::path { @@ -38,17 +38,16 @@ std::shared_ptr Integrator::integrate(const std::shared_ptr &portal, const coord::tile_delta &target, bool with_los, - bool evict_cache) { + bool evict_cache) { auto cache_key = std::make_pair(portal->get_id(), other_sector_id); if (evict_cache) { this->field_cache->evict(cache_key); } - if (this->field_cache->is_cached(cache_key)) { + else if (this->field_cache->is_cached(cache_key)) { log::log(DBG << "Using cached integration field for portal " << portal->get_id() << " from sector " << other_sector_id); - - //retrieve cached integration field + // retrieve cached integration field auto cached_integration_field = this->field_cache->get_integration_field(cache_key); if (with_los) { @@ -107,16 +106,16 @@ std::shared_ptr Integrator::build(const std::shared_ptr &portal, bool with_los, - bool evict_cache) { + bool evict_cache) { auto cache_key = std::make_pair(portal->get_id(), other_sector_id); if (evict_cache) { this->field_cache->evict(cache_key); } - if (this->field_cache->is_cached(cache_key)) { + else if (this->field_cache->is_cached(cache_key)) { log::log(DBG << "Using cached flow field for portal " << portal->get_id() << " from sector " << other_sector_id); - //retrieve cached flow field + // retrieve cached flow field auto cached_flow_field = this->field_cache->get_flow_field(cache_key); if (with_los) { @@ -157,17 +156,16 @@ Integrator::get_return_t Integrator::get(const std::shared_ptr &cost_ const std::shared_ptr &portal, const coord::tile_delta &target, bool with_los, - bool evict_cache) { + bool evict_cache) { auto cache_key = std::make_pair(portal->get_id(), other_sector_id); if (evict_cache) { this->field_cache->evict(cache_key); } - if (this->field_cache->is_cached(cache_key)) { + else if (this->field_cache->is_cached(cache_key)) { log::log(DBG << "Using cached integration and flow fields for portal " << portal->get_id() << " from sector " << other_sector_id); - - //retrieve cached fields + // retrieve cached fields auto cached_integration_field = this->field_cache->get_integration_field(cache_key); auto cached_flow_field = this->field_cache->get_flow_field(cache_key); diff --git a/libopenage/pathfinding/types.h b/libopenage/pathfinding/types.h index 1b48a49654..3229010a91 100644 --- a/libopenage/pathfinding/types.h +++ b/libopenage/pathfinding/types.h @@ -4,6 +4,8 @@ #include #include +#include +#include namespace openage::path { @@ -109,4 +111,17 @@ using sector_id_t = size_t; */ using portal_id_t = size_t; +class FlowField; +class IntegrationField; + +/** + * Cache key for accessing the field cache using a portal id and a sector id. + */ +using cache_key_t = std::pair; + +/** + * Returnable field cache entry pair containing an integration field and a flow field. + */ +using field_cache_t = std::pair, std::shared_ptr>; + } // namespace openage::path From 5c4150505e2743780c8f409ea78a94ade4a6b411 Mon Sep 17 00:00:00 2001 From: dmwever <56411717+dmwever@users.noreply.github.com> Date: Fri, 20 Dec 2024 18:34:01 -0600 Subject: [PATCH 039/152] next round of changes --- libopenage/pathfinding/cost_field.cpp | 5 ----- libopenage/pathfinding/cost_field.h | 12 ++++++++---- libopenage/pathfinding/integrator.cpp | 4 +++- libopenage/pathfinding/integrator.h | 2 +- libopenage/pathfinding/path.h | 2 -- 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/libopenage/pathfinding/cost_field.cpp b/libopenage/pathfinding/cost_field.cpp index d737f550c0..3ba37a6242 100644 --- a/libopenage/pathfinding/cost_field.cpp +++ b/libopenage/pathfinding/cost_field.cpp @@ -42,11 +42,6 @@ void CostField::set_cost(size_t x, size_t y, cost_t cost, const time::time_t &va this->set_cost(x + y * this->size, cost, valid_until); } -inline void CostField::set_cost(size_t idx, cost_t cost, const time::time_t &valid_until) { - this->cells[idx] = cost; - this->valid_until = valid_until; -} - const std::vector &CostField::get_costs() const { return this->cells; } diff --git a/libopenage/pathfinding/cost_field.h b/libopenage/pathfinding/cost_field.h index f70adf9251..f214e30f96 100644 --- a/libopenage/pathfinding/cost_field.h +++ b/libopenage/pathfinding/cost_field.h @@ -67,7 +67,7 @@ class CostField { * @param cost Cost to set. * @param changed Time at which the cost value is changed. */ - void set_cost(const coord::tile_delta &pos, cost_t cost, const time::time_t &changed); + void set_cost(const coord::tile_delta &pos, cost_t cost, const time::time_t &valid_until); /** * Set the cost at a specified position. @@ -77,7 +77,7 @@ class CostField { * @param cost Cost to set. * @param changed Time at which the cost value is changed. */ - void set_cost(size_t x, size_t y, cost_t cost, const time::time_t &changed); + void set_cost(size_t x, size_t y, cost_t cost, const time::time_t &valid_until); /** * Set the cost at a specified position. @@ -86,7 +86,11 @@ class CostField { * @param cost Cost to set. * @param changed Time at which the cost value is changed. */ - inline void set_cost(size_t idx, cost_t cost, const time::time_t &changed); + + inline void set_cost(size_t idx, cost_t cost, const time::time_t &until) { + cells[idx] = cost; + valid_until = until; + } /** * Get the cost field values. @@ -124,7 +128,7 @@ class CostField { /** * Time the cost field was last changed. */ - time::time_t &valid_until; + time::time_t valid_until; /** * Cost field values. diff --git a/libopenage/pathfinding/integrator.cpp b/libopenage/pathfinding/integrator.cpp index e164f3a094..a71845cffb 100644 --- a/libopenage/pathfinding/integrator.cpp +++ b/libopenage/pathfinding/integrator.cpp @@ -206,7 +206,9 @@ Integrator::get_return_t Integrator::get(const std::shared_ptr &cost_ std::shared_ptr cached_flow_field = std::make_shared(*flow_field); cached_flow_field->reset_dynamic_flags(); - this->field_cache->add(cache_key, cached_integration_field, cached_flow_field); + field_cache_t field_cache = field_cache_t(cached_integration_field, cached_flow_field); + + this->field_cache->add(cache_key, field_cache); return std::make_pair(integration_field, flow_field); } diff --git a/libopenage/pathfinding/integrator.h b/libopenage/pathfinding/integrator.h index 7d09c00a73..daa0e226fd 100644 --- a/libopenage/pathfinding/integrator.h +++ b/libopenage/pathfinding/integrator.h @@ -5,6 +5,7 @@ #include #include "pathfinding/types.h" +#include "pathfinding/field_cache.h" #include "util/hash.h" @@ -18,7 +19,6 @@ class CostField; class FlowField; class IntegrationField; class Portal; -class FieldCache; /** * Integrator for the flow field pathfinding algorithm. diff --git a/libopenage/pathfinding/path.h b/libopenage/pathfinding/path.h index 2d1f1cd593..df1b34a599 100644 --- a/libopenage/pathfinding/path.h +++ b/libopenage/pathfinding/path.h @@ -37,8 +37,6 @@ struct Path { /// First waypoint is the start position of the path request. /// Last waypoint is the target position of the path request. std::vector waypoints; - /// Time the path was created. - const time::time_t time; }; } // namespace openage::path From ced50928e08b09c0a80023a8fcbf827129743df4 Mon Sep 17 00:00:00 2001 From: dmwever <56411717+dmwever@users.noreply.github.com> Date: Tue, 24 Dec 2024 20:48:29 -0600 Subject: [PATCH 040/152] Update libopenage/pathfinding/cost_field.h Co-authored-by: Christoph Heine <6852422+heinezen@users.noreply.github.com> --- libopenage/pathfinding/cost_field.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libopenage/pathfinding/cost_field.h b/libopenage/pathfinding/cost_field.h index f214e30f96..fe31c8945e 100644 --- a/libopenage/pathfinding/cost_field.h +++ b/libopenage/pathfinding/cost_field.h @@ -65,7 +65,7 @@ class CostField { * * @param pos Coordinates of the cell (relative to field origin). * @param cost Cost to set. - * @param changed Time at which the cost value is changed. + * @param valid_until Time at which the cost value is changed. */ void set_cost(const coord::tile_delta &pos, cost_t cost, const time::time_t &valid_until); From 300b12360e6de07746ccbcedc55ce08e84f05a96 Mon Sep 17 00:00:00 2001 From: dmwever <56411717+dmwever@users.noreply.github.com> Date: Tue, 24 Dec 2024 21:39:59 -0600 Subject: [PATCH 041/152] - more fixes --- libopenage/pathfinding/cost_field.cpp | 2 +- libopenage/pathfinding/cost_field.h | 8 ++++---- libopenage/pathfinding/demo/demo_1.cpp | 13 ++++++------- libopenage/pathfinding/integrator.cpp | 25 +++++++++++-------------- libopenage/pathfinding/integrator.h | 14 ++++++++------ libopenage/pathfinding/pathfinder.cpp | 4 ++-- 6 files changed, 32 insertions(+), 34 deletions(-) diff --git a/libopenage/pathfinding/cost_field.cpp b/libopenage/pathfinding/cost_field.cpp index 3ba37a6242..7e9f206a59 100644 --- a/libopenage/pathfinding/cost_field.cpp +++ b/libopenage/pathfinding/cost_field.cpp @@ -60,7 +60,7 @@ bool CostField::is_dirty(const time::time_t &time) { return time >= this->valid_until; } -void CostField::clean() { +void CostField::clear_dirty() { this->valid_until = time::TIME_MAX; } diff --git a/libopenage/pathfinding/cost_field.h b/libopenage/pathfinding/cost_field.h index fe31c8945e..6e731a0f92 100644 --- a/libopenage/pathfinding/cost_field.h +++ b/libopenage/pathfinding/cost_field.h @@ -87,9 +87,9 @@ class CostField { * @param changed Time at which the cost value is changed. */ - inline void set_cost(size_t idx, cost_t cost, const time::time_t &until) { - cells[idx] = cost; - valid_until = until; + inline void set_cost(size_t idx, cost_t cost, const time::time_t &valid_until) { + this->cells[idx] = cost; + this->valid_until = valid_until; } /** @@ -117,7 +117,7 @@ class CostField { /** * Cleans the dirty flag by setting it to time_MAX. */ - void clean(); + void clear_dirty(); private: /** diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp index a31b9b786b..2af57b481f 100644 --- a/libopenage/pathfinding/demo/demo_1.cpp +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -27,12 +27,11 @@ namespace openage::path::tests { void path_demo_1(const util::Path &path) { - auto time_loop = std::make_shared(); - time_loop->run(); - auto clock = time_loop->get_clock(); auto grid = std::make_shared(0, util::Vector2s{4, 3}, 10); - const time::time_t time = clock->get_time(); + time::Clock clock = time::Clock(); + clock.start(); + const time::time_t time = clock.get_time(); // Initialize the cost field for each sector. for (auto §or : grid->get_sectors()) { auto cost_field = sector->get_cost_field(); @@ -93,7 +92,7 @@ void path_demo_1(const util::Path &path) { coord::tile start{2, 26}; coord::tile target{36, 2}; - const time::time_t request_time = clock->get_time(); + const time::time_t request_time = clock.get_time(); PathRequest path_request{ grid->get_id(), @@ -137,7 +136,7 @@ void path_demo_1(const util::Path &path) { grid->get_id(), start, target, - clock->get_time() + clock.get_time() }; timer.reset(); @@ -158,7 +157,7 @@ void path_demo_1(const util::Path &path) { grid->get_id(), start, target, - clock->get_time() + clock.get_time() }; timer.reset(); diff --git a/libopenage/pathfinding/integrator.cpp b/libopenage/pathfinding/integrator.cpp index a71845cffb..5bac928fe9 100644 --- a/libopenage/pathfinding/integrator.cpp +++ b/libopenage/pathfinding/integrator.cpp @@ -9,6 +9,7 @@ #include "pathfinding/flow_field.h" #include "pathfinding/integration_field.h" #include "pathfinding/portal.h" +#include "time/time.h" namespace openage::path { @@ -37,10 +38,10 @@ std::shared_ptr Integrator::integrate(const std::shared_ptr &portal, const coord::tile_delta &target, - bool with_los, - bool evict_cache) { + const time::time_t &time, + bool with_los) { auto cache_key = std::make_pair(portal->get_id(), other_sector_id); - if (evict_cache) { + if (cost_field->is_dirty(time)) { this->field_cache->evict(cache_key); } else if (this->field_cache->is_cached(cache_key)) { @@ -105,13 +106,9 @@ std::shared_ptr Integrator::build(const std::shared_ptr &other, sector_id_t other_sector_id, const std::shared_ptr &portal, - bool with_los, - bool evict_cache) { + bool with_los) { auto cache_key = std::make_pair(portal->get_id(), other_sector_id); - if (evict_cache) { - this->field_cache->evict(cache_key); - } - else if (this->field_cache->is_cached(cache_key)) { + if (this->field_cache->is_cached(cache_key)) { log::log(DBG << "Using cached flow field for portal " << portal->get_id() << " from sector " << other_sector_id); @@ -155,10 +152,10 @@ Integrator::get_return_t Integrator::get(const std::shared_ptr &cost_ sector_id_t other_sector_id, const std::shared_ptr &portal, const coord::tile_delta &target, - bool with_los, - bool evict_cache) { + const time::time_t &time, + bool with_los) { auto cache_key = std::make_pair(portal->get_id(), other_sector_id); - if (evict_cache) { + if (cost_field->is_dirty(time)) { this->field_cache->evict(cache_key); } else if (this->field_cache->is_cached(cache_key)) { @@ -193,8 +190,8 @@ Integrator::get_return_t Integrator::get(const std::shared_ptr &cost_ return std::make_pair(cached_integration_field, cached_flow_field); } - auto integration_field = this->integrate(cost_field, other, other_sector_id, portal, target, with_los, evict_cache); - auto flow_field = this->build(integration_field, other, other_sector_id, portal, evict_cache); + auto integration_field = this->integrate(cost_field, other, other_sector_id, portal, target, time, with_los); + auto flow_field = this->build(integration_field, other, other_sector_id, portal); log::log(DBG << "Caching integration and flow fields for portal ID: " << portal->get_id() << ", sector ID: " << other_sector_id); diff --git a/libopenage/pathfinding/integrator.h b/libopenage/pathfinding/integrator.h index daa0e226fd..e8181a1ac3 100644 --- a/libopenage/pathfinding/integrator.h +++ b/libopenage/pathfinding/integrator.h @@ -7,6 +7,7 @@ #include "pathfinding/types.h" #include "pathfinding/field_cache.h" #include "util/hash.h" +#include "time/time.h" namespace openage { @@ -55,6 +56,7 @@ class Integrator { * @param other_sector_id Sector ID of the other side of the portal. * @param portal Portal. * @param target Coordinates of the target cell, relative to the integration field origin. + * @param time The time to check is the cached cost field is dirty. * @param with_los If true an LOS pass is performed before cost integration. * * @return Integration field. @@ -64,8 +66,8 @@ class Integrator { sector_id_t other_sector_id, const std::shared_ptr &portal, const coord::tile_delta &target, - bool with_los = true, - bool evict_cache = false); + const time::time_t &time, + bool with_los = true); /** * Build the flow field from an integration field. @@ -91,8 +93,7 @@ class Integrator { const std::shared_ptr &other, sector_id_t other_sector_id, const std::shared_ptr &portal, - bool with_los = true, - bool evict_cache = false); + bool with_los = true); using get_return_t = std::pair, std::shared_ptr>; @@ -115,6 +116,7 @@ class Integrator { * @param other_sector_id Sector ID of the other side of the portal. * @param portal Portal. * @param target Coordinates of the target cell, relative to the integration field origin. + * @param time The time to check is the cached cost field is dirty. * @param with_los If true an LOS pass is performed before cost integration. * * @return Integration field and flow field. @@ -124,8 +126,8 @@ class Integrator { sector_id_t other_sector_id, const std::shared_ptr &portal, const coord::tile_delta &target, - bool with_los = true, - bool evict_cache = false); + const time::time_t &time, + bool with_los = true); private: diff --git a/libopenage/pathfinding/pathfinder.cpp b/libopenage/pathfinding/pathfinder.cpp index 8aec42a514..5a9c785f0c 100644 --- a/libopenage/pathfinding/pathfinder.cpp +++ b/libopenage/pathfinding/pathfinder.cpp @@ -155,8 +155,8 @@ const Path Pathfinder::get_path(const PathRequest &request) { prev_sector_id, portal, target_delta, - with_los, - next_sector->is_dirty(request.time)); + request.time, + with_los); flow_fields.push_back(std::make_pair(next_sector_id, sector_fields.second)); prev_integration_field = sector_fields.first; From 3cef612e94db0b90dab792544fb507bc2d37fc5d Mon Sep 17 00:00:00 2001 From: dmwever <56411717+dmwever@users.noreply.github.com> Date: Thu, 2 Jan 2025 17:20:58 -0600 Subject: [PATCH 042/152] Update cost_field.h --- libopenage/pathfinding/cost_field.h | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/libopenage/pathfinding/cost_field.h b/libopenage/pathfinding/cost_field.h index 6e731a0f92..a25bdaadf8 100644 --- a/libopenage/pathfinding/cost_field.h +++ b/libopenage/pathfinding/cost_field.h @@ -65,7 +65,7 @@ class CostField { * * @param pos Coordinates of the cell (relative to field origin). * @param cost Cost to set. - * @param valid_until Time at which the cost value is changed. + * @param valid_until Time at which the cost value expires. */ void set_cost(const coord::tile_delta &pos, cost_t cost, const time::time_t &valid_until); @@ -75,7 +75,7 @@ class CostField { * @param x X-coordinate of the cell. * @param y Y-coordinate of the cell. * @param cost Cost to set. - * @param changed Time at which the cost value is changed. + * @param valid_until Time at which the cost value expires. */ void set_cost(size_t x, size_t y, cost_t cost, const time::time_t &valid_until); @@ -84,9 +84,8 @@ class CostField { * * @param idx Index of the cell. * @param cost Cost to set. - * @param changed Time at which the cost value is changed. + * @param valid_until Time at which the cost value expires. */ - inline void set_cost(size_t idx, cost_t cost, const time::time_t &valid_until) { this->cells[idx] = cost; this->valid_until = valid_until; @@ -103,7 +102,7 @@ class CostField { * Set the cost field values. * * @param cells Cost field values. - * @param changed Time at which the cost value is changed. + * @param valid_until Time at which the cost value expires. */ void set_costs(std::vector &&cells, const time::time_t &changed); @@ -126,7 +125,7 @@ class CostField { size_t size; /** - * Time the cost field was last changed. + * Time the cost field expires. */ time::time_t valid_until; From 4ed876bb4aecc01d0b3bc9236896734351195a76 Mon Sep 17 00:00:00 2001 From: dmwever <56411717+dmwever@users.noreply.github.com> Date: Thu, 2 Jan 2025 17:22:25 -0600 Subject: [PATCH 043/152] Update libopenage/pathfinding/cost_field.h Co-authored-by: Christoph Heine <6852422+heinezen@users.noreply.github.com> --- libopenage/pathfinding/cost_field.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libopenage/pathfinding/cost_field.h b/libopenage/pathfinding/cost_field.h index a25bdaadf8..f32cc2f2b7 100644 --- a/libopenage/pathfinding/cost_field.h +++ b/libopenage/pathfinding/cost_field.h @@ -114,7 +114,7 @@ class CostField { bool is_dirty(const time::time_t &time); /** - * Cleans the dirty flag by setting it to time_MAX. + * Clear the dirty flag. */ void clear_dirty(); From e0fc329bf29b68abca84a0a660cd1ef1bf21de7a Mon Sep 17 00:00:00 2001 From: dmwever <56411717+dmwever@users.noreply.github.com> Date: Thu, 2 Jan 2025 17:22:42 -0600 Subject: [PATCH 044/152] Update libopenage/pathfinding/cost_field.h Co-authored-by: Christoph Heine <6852422+heinezen@users.noreply.github.com> --- libopenage/pathfinding/cost_field.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libopenage/pathfinding/cost_field.h b/libopenage/pathfinding/cost_field.h index f32cc2f2b7..967d4880f6 100644 --- a/libopenage/pathfinding/cost_field.h +++ b/libopenage/pathfinding/cost_field.h @@ -109,7 +109,7 @@ class CostField { /** * Check if the cost field is dirty at the specified time. * - * @param time Cost field is dirty if the cost field is accessed after the time given in valid_until. + * @param time Time of access to the cost field. */ bool is_dirty(const time::time_t &time); From ba7aec4551469df4d0e150354ae779d1291d945d Mon Sep 17 00:00:00 2001 From: dmwever <56411717+dmwever@users.noreply.github.com> Date: Thu, 2 Jan 2025 17:23:31 -0600 Subject: [PATCH 045/152] Update libopenage/pathfinding/integrator.h Co-authored-by: Christoph Heine <6852422+heinezen@users.noreply.github.com> --- libopenage/pathfinding/integrator.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libopenage/pathfinding/integrator.h b/libopenage/pathfinding/integrator.h index e8181a1ac3..ee0d605758 100644 --- a/libopenage/pathfinding/integrator.h +++ b/libopenage/pathfinding/integrator.h @@ -56,7 +56,7 @@ class Integrator { * @param other_sector_id Sector ID of the other side of the portal. * @param portal Portal. * @param target Coordinates of the target cell, relative to the integration field origin. - * @param time The time to check is the cached cost field is dirty. + * @param time Time of the path request. * @param with_los If true an LOS pass is performed before cost integration. * * @return Integration field. From f0d923c5f61ce69d9d969629e8294780259b43e0 Mon Sep 17 00:00:00 2001 From: dmwever <56411717+dmwever@users.noreply.github.com> Date: Thu, 2 Jan 2025 17:23:45 -0600 Subject: [PATCH 046/152] Update libopenage/pathfinding/integrator.h Co-authored-by: Christoph Heine <6852422+heinezen@users.noreply.github.com> --- libopenage/pathfinding/integrator.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libopenage/pathfinding/integrator.h b/libopenage/pathfinding/integrator.h index ee0d605758..29a5cf4717 100644 --- a/libopenage/pathfinding/integrator.h +++ b/libopenage/pathfinding/integrator.h @@ -116,7 +116,7 @@ class Integrator { * @param other_sector_id Sector ID of the other side of the portal. * @param portal Portal. * @param target Coordinates of the target cell, relative to the integration field origin. - * @param time The time to check is the cached cost field is dirty. + * @param time Time of the path request. * @param with_los If true an LOS pass is performed before cost integration. * * @return Integration field and flow field. From d839bc532fe833db075fe6e843dfc6c914fd1810 Mon Sep 17 00:00:00 2001 From: dmwever <56411717+dmwever@users.noreply.github.com> Date: Thu, 2 Jan 2025 19:22:05 -0600 Subject: [PATCH 047/152] Update Demo --- libopenage/pathfinding/cost_field.h | 4 +++- libopenage/pathfinding/demo/demo_1.cpp | 17 ++++++----------- libopenage/pathfinding/integrator.h | 2 +- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/libopenage/pathfinding/cost_field.h b/libopenage/pathfinding/cost_field.h index 967d4880f6..fbdbbb6d94 100644 --- a/libopenage/pathfinding/cost_field.h +++ b/libopenage/pathfinding/cost_field.h @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #pragma once @@ -110,6 +110,8 @@ class CostField { * Check if the cost field is dirty at the specified time. * * @param time Time of access to the cost field. + * + * @return Whether the cost field is dirty. */ bool is_dirty(const time::time_t &time); diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp index 2af57b481f..6f594b1a78 100644 --- a/libopenage/pathfinding/demo/demo_1.cpp +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #include "demo_1.h" @@ -29,14 +29,12 @@ namespace openage::path::tests { void path_demo_1(const util::Path &path) { auto grid = std::make_shared(0, util::Vector2s{4, 3}, 10); - time::Clock clock = time::Clock(); - clock.start(); - const time::time_t time = clock.get_time(); // Initialize the cost field for each sector. for (auto §or : grid->get_sectors()) { auto cost_field = sector->get_cost_field(); std::vector sector_cost = sectors_cost.at(sector->get_id()); - cost_field->set_costs(std::move(sector_cost), time); + cost_field->set_costs(std::move(sector_cost), 0); + cost_field->clear_dirty(); } // Initialize portals between sectors. @@ -92,20 +90,17 @@ void path_demo_1(const util::Path &path) { coord::tile start{2, 26}; coord::tile target{36, 2}; - const time::time_t request_time = clock.get_time(); - PathRequest path_request{ grid->get_id(), start, target, - request_time + 0 }; grid->init_portal_nodes(); timer.start(); Path path_result = pathfinder->get_path(path_request); timer.stop(); - log::log(INFO << "Pathfinding request at " << request_time); log::log(INFO << "Pathfinding took " << timer.getval() / 1000 << " us"); // Create a renderer to display the grid and path @@ -136,7 +131,7 @@ void path_demo_1(const util::Path &path) { grid->get_id(), start, target, - clock.get_time() + 0 }; timer.reset(); @@ -157,7 +152,7 @@ void path_demo_1(const util::Path &path) { grid->get_id(), start, target, - clock.get_time() + 0 }; timer.reset(); diff --git a/libopenage/pathfinding/integrator.h b/libopenage/pathfinding/integrator.h index 29a5cf4717..eebe55c266 100644 --- a/libopenage/pathfinding/integrator.h +++ b/libopenage/pathfinding/integrator.h @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #pragma once From 5249d778a2ab8b397773b02d3b71b965bc31ddcf Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 13 Jan 2025 03:54:45 +0100 Subject: [PATCH 048/152] gamestate: Add request time to pathfinding request. --- libopenage/gamestate/system/move.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libopenage/gamestate/system/move.cpp b/libopenage/gamestate/system/move.cpp index 3da45ceab3..fac4f16412 100644 --- a/libopenage/gamestate/system/move.cpp +++ b/libopenage/gamestate/system/move.cpp @@ -37,7 +37,8 @@ namespace openage::gamestate::system { std::vector find_path(const std::shared_ptr &pathfinder, path::grid_id_t grid_id, const coord::phys3 &start, - const coord::phys3 &end) { + const coord::phys3 &end, + const time::time_t &start_time) { auto start_tile = start.to_tile(); auto end_tile = end.to_tile(); @@ -46,6 +47,7 @@ std::vector find_path(const std::shared_ptr &pat grid_id, start_tile, end_tile, + start_time, }; auto tile_path = pathfinder->get_path(request); @@ -122,7 +124,7 @@ const time::time_t Move::move_default(const std::shared_ptrget_map(); auto pathfinder = map->get_pathfinder(); auto grid_id = map->get_grid_id(move_path_grid->get_name()); - auto waypoints = find_path(pathfinder, grid_id, current_pos, destination); + auto waypoints = find_path(pathfinder, grid_id, current_pos, destination, start_time); // use waypoints for movement double total_time = 0; From 1fec0c9b7a1922fd7c10ba96db2d84cc5e277f5b Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 13 Jan 2025 03:55:06 +0100 Subject: [PATCH 049/152] path: Fix warning for cost field member ordering. --- libopenage/pathfinding/cost_field.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libopenage/pathfinding/cost_field.cpp b/libopenage/pathfinding/cost_field.cpp index 7e9f206a59..0afac1a07a 100644 --- a/libopenage/pathfinding/cost_field.cpp +++ b/libopenage/pathfinding/cost_field.cpp @@ -13,8 +13,8 @@ namespace openage::path { CostField::CostField(size_t size) : size{size}, - cells(this->size * this->size, COST_MIN), - valid_until{time::TIME_MIN} { + valid_until{time::TIME_MIN}, + cells(this->size * this->size, COST_MIN) { log::log(DBG << "Created cost field with size " << this->size << "x" << this->size); } From 87776b6fa9a85c8970df79c3e462d4c07ef42330 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 13 Jan 2025 03:57:57 +0100 Subject: [PATCH 050/152] path: Fix missing initialization of integrator's field cache. --- libopenage/pathfinding/integrator.cpp | 5 +++++ libopenage/pathfinding/integrator.h | 7 +++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/libopenage/pathfinding/integrator.cpp b/libopenage/pathfinding/integrator.cpp index 5bac928fe9..db573f480d 100644 --- a/libopenage/pathfinding/integrator.cpp +++ b/libopenage/pathfinding/integrator.cpp @@ -14,6 +14,11 @@ namespace openage::path { +Integrator::Integrator() : + field_cache{std::make_unique()} { +} + + std::shared_ptr Integrator::integrate(const std::shared_ptr &cost_field, const coord::tile_delta &target, bool with_los) { diff --git a/libopenage/pathfinding/integrator.h b/libopenage/pathfinding/integrator.h index eebe55c266..490d5eaacb 100644 --- a/libopenage/pathfinding/integrator.h +++ b/libopenage/pathfinding/integrator.h @@ -26,7 +26,11 @@ class Portal; */ class Integrator { public: - Integrator() = default; + /** + * Create a new integrator. + */ + Integrator(); + ~Integrator() = default; /** @@ -130,7 +134,6 @@ class Integrator { bool with_los = true); private: - /** * Cache for already computed fields. */ From c5a31302f0a1f3763ce7ac12122a8a4b08f651d7 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 13 Jan 2025 03:59:44 +0100 Subject: [PATCH 051/152] Fix sanity check complaints. --- libopenage/gamestate/system/move.cpp | 2 +- libopenage/pathfinding/cost_field.cpp | 2 +- libopenage/pathfinding/cost_field.h | 2 +- libopenage/pathfinding/demo/demo_0.cpp | 2 +- libopenage/pathfinding/integrator.cpp | 2 +- libopenage/pathfinding/sector.h | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libopenage/gamestate/system/move.cpp b/libopenage/gamestate/system/move.cpp index fac4f16412..5a44baf113 100644 --- a/libopenage/gamestate/system/move.cpp +++ b/libopenage/gamestate/system/move.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #include "move.h" diff --git a/libopenage/pathfinding/cost_field.cpp b/libopenage/pathfinding/cost_field.cpp index 0afac1a07a..0cb217093a 100644 --- a/libopenage/pathfinding/cost_field.cpp +++ b/libopenage/pathfinding/cost_field.cpp @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #include "cost_field.h" diff --git a/libopenage/pathfinding/cost_field.h b/libopenage/pathfinding/cost_field.h index fbdbbb6d94..0c5f18dabb 100644 --- a/libopenage/pathfinding/cost_field.h +++ b/libopenage/pathfinding/cost_field.h @@ -110,7 +110,7 @@ class CostField { * Check if the cost field is dirty at the specified time. * * @param time Time of access to the cost field. - * + * * @return Whether the cost field is dirty. */ bool is_dirty(const time::time_t &time); diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index 2dfb825fa0..70d4f8be1f 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -33,7 +33,7 @@ void path_demo_0(const util::Path &path) { // Cost field with some obstacles auto cost_field = std::make_shared(field_length); - + const time::time_t time = time::TIME_ZERO; cost_field->set_cost(coord::tile_delta{0, 0}, COST_IMPASSABLE, time); cost_field->set_cost(coord::tile_delta{1, 0}, 254, time); diff --git a/libopenage/pathfinding/integrator.cpp b/libopenage/pathfinding/integrator.cpp index db573f480d..6befb014b3 100644 --- a/libopenage/pathfinding/integrator.cpp +++ b/libopenage/pathfinding/integrator.cpp @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #include "integrator.h" diff --git a/libopenage/pathfinding/sector.h b/libopenage/pathfinding/sector.h index a8aad24c01..0a8172f996 100644 --- a/libopenage/pathfinding/sector.h +++ b/libopenage/pathfinding/sector.h @@ -106,7 +106,7 @@ class Sector { /** * Check if the cost field is dirty at the specified time. - * + * * @param time Specified time to check. */ bool is_dirty(const time::time_t &time); From 8dba1e118a018ad4d26a6822c1e8fecc770140b8 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 13 Jan 2025 04:07:39 +0100 Subject: [PATCH 052/152] path: Make is_dirty check const. --- libopenage/pathfinding/cost_field.cpp | 2 +- libopenage/pathfinding/cost_field.h | 2 +- libopenage/pathfinding/demo/demo_0.cpp | 2 +- libopenage/pathfinding/sector.cpp | 2 +- libopenage/pathfinding/sector.h | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libopenage/pathfinding/cost_field.cpp b/libopenage/pathfinding/cost_field.cpp index 0cb217093a..12889663eb 100644 --- a/libopenage/pathfinding/cost_field.cpp +++ b/libopenage/pathfinding/cost_field.cpp @@ -56,7 +56,7 @@ void CostField::set_costs(std::vector &&cells, const time::time_t &valid this->valid_until = valid_until; } -bool CostField::is_dirty(const time::time_t &time) { +bool CostField::is_dirty(const time::time_t &time) const { return time >= this->valid_until; } diff --git a/libopenage/pathfinding/cost_field.h b/libopenage/pathfinding/cost_field.h index 0c5f18dabb..c03b494a84 100644 --- a/libopenage/pathfinding/cost_field.h +++ b/libopenage/pathfinding/cost_field.h @@ -113,7 +113,7 @@ class CostField { * * @return Whether the cost field is dirty. */ - bool is_dirty(const time::time_t &time); + bool is_dirty(const time::time_t &time) const; /** * Clear the dirty flag. diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index 70d4f8be1f..31f66bb51f 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #include "demo_0.h" diff --git a/libopenage/pathfinding/sector.cpp b/libopenage/pathfinding/sector.cpp index 2da73690c0..d41bcaefef 100644 --- a/libopenage/pathfinding/sector.cpp +++ b/libopenage/pathfinding/sector.cpp @@ -249,7 +249,7 @@ void Sector::connect_exits() { } } -bool Sector::is_dirty(const time::time_t &time) { +bool Sector::is_dirty(const time::time_t &time) const { return this->cost_field->is_dirty(time); } diff --git a/libopenage/pathfinding/sector.h b/libopenage/pathfinding/sector.h index 0a8172f996..4749ac62b1 100644 --- a/libopenage/pathfinding/sector.h +++ b/libopenage/pathfinding/sector.h @@ -109,7 +109,7 @@ class Sector { * * @param time Specified time to check. */ - bool is_dirty(const time::time_t &time); + bool is_dirty(const time::time_t &time) const; private: /** From 9b20825500a2654e4d4796842a92bd94e08b0e51 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 13 Jan 2025 04:32:08 +0100 Subject: [PATCH 053/152] path: Make demo 1 comments more verbose. --- libopenage/pathfinding/demo/demo_1.cpp | 43 +++++++++++++++++++------- libopenage/pathfinding/demo/demo_1.h | 4 +-- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp index 6f594b1a78..ed1b1ebf84 100644 --- a/libopenage/pathfinding/demo/demo_1.cpp +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -11,8 +11,6 @@ #include "pathfinding/portal.h" #include "pathfinding/sector.h" #include "util/timer.h" -#include "time/time_loop.h" -#include "time/clock.h" #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" @@ -32,21 +30,30 @@ void path_demo_1(const util::Path &path) { // Initialize the cost field for each sector. for (auto §or : grid->get_sectors()) { auto cost_field = sector->get_cost_field(); - std::vector sector_cost = sectors_cost.at(sector->get_id()); - cost_field->set_costs(std::move(sector_cost), 0); - cost_field->clear_dirty(); + + // Read the data from the preconfigured table + std::vector sector_cost = SECTORS_COST.at(sector->get_id()); + + // Set the cost field for the sector + cost_field->set_costs(std::move(sector_cost), time::TIME_ZERO); } - // Initialize portals between sectors. + // Initialize portals between sectors util::Vector2s grid_size = grid->get_size(); portal_id_t portal_id = 0; for (size_t y = 0; y < grid_size[1]; y++) { for (size_t x = 0; x < grid_size[0]; x++) { + // For each sector on the grid, we will seatch for portals to the east and south + // sectors and connect them + auto sector = grid->get_sector(x, y); if (x < grid_size[0] - 1) { + // Check the sector to the east auto neighbor = grid->get_sector(x + 1, y); auto portals = sector->find_portals(neighbor, PortalDirection::EAST_WEST, portal_id); + + // Add the portals to the sector and the neighbor for (auto &portal : portals) { sector->add_portal(portal); neighbor->add_portal(portal); @@ -54,8 +61,11 @@ void path_demo_1(const util::Path &path) { portal_id += portals.size(); } if (y < grid_size[1] - 1) { + // Check the sector to the south auto neighbor = grid->get_sector(x, y + 1); auto portals = sector->find_portals(neighbor, PortalDirection::NORTH_SOUTH, portal_id); + + // Add the portals to the sector and the neighbor for (auto &portal : portals) { sector->add_portal(portal); neighbor->add_portal(portal); @@ -65,7 +75,8 @@ void path_demo_1(const util::Path &path) { } } - // Connect portals inside sectors. + // Connect portals that can mutually reach each other + // within the same sector for (auto sector : grid->get_sectors()) { sector->connect_exits(); @@ -84,23 +95,28 @@ void path_demo_1(const util::Path &path) { auto pathfinder = std::make_shared(); pathfinder->add_grid(grid); + // Add a timer to measure the pathfinding speed util::Timer timer; - // Create a path request and get the path + // Create a path request from one end of the grid to the other coord::tile start{2, 26}; coord::tile target{36, 2}; - PathRequest path_request{ grid->get_id(), start, target, - 0 + time::TIME_ZERO, }; + // Initialize the portal nodes of the grid + // This is used for the A* pathfinding search at the beginning grid->init_portal_nodes(); + timer.start(); + // Let the pathfinder search for a path Path path_result = pathfinder->get_path(path_request); timer.stop(); + log::log(INFO << "Pathfinding took " << timer.getval() / 1000 << " us"); // Create a renderer to display the grid and path @@ -113,8 +129,11 @@ void path_demo_1(const util::Path &path) { auto render_manager = std::make_shared(qtapp, window, path, grid); log::log(INFO << "Created render manager for pathfinding demo"); + // Callbacks for mouse button events + // Used to set the start and target cells for pathfinding window->add_mouse_button_callback([&](const QMouseEvent &ev) { if (ev.type() == QEvent::MouseButtonRelease) { + // From the mouse position, calculate the position/cell on the grid auto cell_count_x = grid->get_size()[0] * grid->get_sector_size(); auto cell_count_y = grid->get_size()[1] * grid->get_sector_size(); auto window_size = window->get_size(); @@ -131,7 +150,7 @@ void path_demo_1(const util::Path &path) { grid->get_id(), start, target, - 0 + time::TIME_ZERO, }; timer.reset(); @@ -152,7 +171,7 @@ void path_demo_1(const util::Path &path) { grid->get_id(), start, target, - 0 + time::TIME_ZERO, }; timer.reset(); diff --git a/libopenage/pathfinding/demo/demo_1.h b/libopenage/pathfinding/demo/demo_1.h index db70efe084..b2d3037eb8 100644 --- a/libopenage/pathfinding/demo/demo_1.h +++ b/libopenage/pathfinding/demo/demo_1.h @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #pragma once @@ -182,7 +182,7 @@ class RenderManager1 { // Cost for the sectors in the grid // taken from Figure 23.1 in "Crowd Pathfinding and Steering Using Flow Field Tiles" -const std::vector> sectors_cost = { +const std::vector> SECTORS_COST = { { // clang-format off 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, From b43113adcfd2f8f409bc1fb091bcf362dba45f17 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 13 Jan 2025 04:38:19 +0100 Subject: [PATCH 054/152] path: Activate cache usage in demo 1. --- libopenage/pathfinding/demo/demo_1.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp index ed1b1ebf84..61df3e8995 100644 --- a/libopenage/pathfinding/demo/demo_1.cpp +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -35,7 +35,7 @@ void path_demo_1(const util::Path &path) { std::vector sector_cost = SECTORS_COST.at(sector->get_id()); // Set the cost field for the sector - cost_field->set_costs(std::move(sector_cost), time::TIME_ZERO); + cost_field->set_costs(std::move(sector_cost), time::TIME_MAX); } // Initialize portals between sectors From 62889e643fd9db2e6efe6985b979e047015d9a08 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 13 Jan 2025 04:42:22 +0100 Subject: [PATCH 055/152] patg: Remove 'is_dirty' method from sector code. It should only be checked from the cost field. --- libopenage/pathfinding/sector.cpp | 4 ---- libopenage/pathfinding/sector.h | 8 -------- 2 files changed, 12 deletions(-) diff --git a/libopenage/pathfinding/sector.cpp b/libopenage/pathfinding/sector.cpp index d41bcaefef..f50dc209bf 100644 --- a/libopenage/pathfinding/sector.cpp +++ b/libopenage/pathfinding/sector.cpp @@ -249,8 +249,4 @@ void Sector::connect_exits() { } } -bool Sector::is_dirty(const time::time_t &time) const { - return this->cost_field->is_dirty(time); -} - } // namespace openage::path diff --git a/libopenage/pathfinding/sector.h b/libopenage/pathfinding/sector.h index 4749ac62b1..a4ba94fa52 100644 --- a/libopenage/pathfinding/sector.h +++ b/libopenage/pathfinding/sector.h @@ -9,7 +9,6 @@ #include "coord/chunk.h" #include "pathfinding/portal.h" #include "pathfinding/types.h" -#include "time/time.h" namespace openage::path { @@ -104,13 +103,6 @@ class Sector { */ void connect_exits(); - /** - * Check if the cost field is dirty at the specified time. - * - * @param time Specified time to check. - */ - bool is_dirty(const time::time_t &time) const; - private: /** * ID of the sector. From ec72342fc42d0aeaf741dd488d022a0c3d0a1de2 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 13 Jan 2025 05:02:42 +0100 Subject: [PATCH 056/152] path: Cleanup field cache code. - Make read-only methods const - Add get(..) method for getting entire field entry to avoid two lookups - Simplify lookup of entries - Add more docstrings --- libopenage/pathfinding/field_cache.cpp | 24 +++++------ libopenage/pathfinding/field_cache.h | 55 ++++++++++++++++++++------ 2 files changed, 56 insertions(+), 23 deletions(-) diff --git a/libopenage/pathfinding/field_cache.cpp b/libopenage/pathfinding/field_cache.cpp index 95276b0818..417c8a2bbd 100644 --- a/libopenage/pathfinding/field_cache.cpp +++ b/libopenage/pathfinding/field_cache.cpp @@ -4,27 +4,29 @@ namespace openage::path { -void FieldCache::add(cache_key_t cache_key, - field_cache_t cache_entry) { +void FieldCache::add(const cache_key_t cache_key, + const field_cache_t cache_entry) { this->cache[cache_key] = cache_entry; } -void FieldCache::evict(cache_key_t cache_key) { - this->cache.erase(cache_key); +bool FieldCache::evict(const cache_key_t cache_key) { + return this->cache.erase(cache_key) != 0; } -bool FieldCache::is_cached(cache_key_t cache_key) { +bool FieldCache::is_cached(const cache_key_t cache_key) const { return this->cache.contains(cache_key); } -std::shared_ptr FieldCache::get_integration_field(cache_key_t cache_key) { - auto cached = this->cache.find(cache_key); - return cached->second.first; +std::shared_ptr FieldCache::get_integration_field(const cache_key_t cache_key) const { + return this->cache.at(cache_key).first; } -std::shared_ptr FieldCache::get_flow_field(cache_key_t cache_key) { - auto cached = this->cache.find(cache_key); - return cached->second.second; +std::shared_ptr FieldCache::get_flow_field(const cache_key_t cache_key) const { + return this->cache.at(cache_key).second; +} + +field_cache_t FieldCache::get(const cache_key_t cache_key) const { + return this->cache.at(cache_key); } } // namespace openage::path diff --git a/libopenage/pathfinding/field_cache.h b/libopenage/pathfinding/field_cache.h index 23374e5123..b5c39b44de 100644 --- a/libopenage/pathfinding/field_cache.h +++ b/libopenage/pathfinding/field_cache.h @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #pragma once @@ -8,6 +8,7 @@ #include "pathfinding/types.h" #include "util/hash.h" + namespace openage { namespace coord { struct tile_delta; @@ -26,30 +27,60 @@ class FieldCache { ~FieldCache() = default; /** - * Adds a new field cache entry to the cache with a given portal and sector cache key. + * Adds a new field entry to the cache. + * + * @param cache_key Cache key for the field entry. + * @param cache_entry Field entry (integration field, flow field). + */ + void add(const cache_key_t cache_key, + const field_cache_t cache_entry); + + /** + * Evict field entry from the cache. + * + * @param cache_key Cache key for the field entry to evict. + * + * @return true if the cache key was found and evicted, false otherwise. */ - void add(cache_key_t cache_key, - field_cache_t cache_entry); + bool evict(const cache_key_t cache_key); /** - * Evicts a given field cache entry from the cache at the given cache key. + * Check if there is a cached entry for a specific cache key. + * + * @param cache_key Cache key to check. + * + * @return true if a field entry is found for the cache key, false otherwise. */ - void evict(cache_key_t cache_key); + bool is_cached(const cache_key_t cache_key) const; /** - * Checks if there is a cached entry at a specific cache key. + * Get a cached integration field. + * + * @param cache_key Cache key for the field entry. + * + * @return Integration field. */ - bool is_cached(cache_key_t cache_key); + std::shared_ptr get_integration_field(const cache_key_t cache_key) const; /** - * Gets the integration field from a given cache entry. + * Get a cached flow field. + * + * @param cache_key Cache key for the field entry. + * + * @return Flow field. */ - std::shared_ptr get_integration_field(cache_key_t cache_key); + std::shared_ptr get_flow_field(const cache_key_t cache_key) const; /** - * Gets the flow field from a given cache entry. + * Get a cached field entry. + * + * Contains both integration field and flow field. + * + * @param cache_key Cache key for the field entry. + * + * @return Field entry (integration field, flow field). */ - std::shared_ptr get_flow_field(cache_key_t cache_key); + field_cache_t get(const cache_key_t cache_key) const; private: /** From 863ba6c1ad8188bd180e9634c910703daa3b6f5e Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 13 Jan 2025 05:06:45 +0100 Subject: [PATCH 057/152] path: Avoid duplicate lookups for field cache in integrator. --- libopenage/pathfinding/integrator.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/libopenage/pathfinding/integrator.cpp b/libopenage/pathfinding/integrator.cpp index 6befb014b3..f4dcf324b1 100644 --- a/libopenage/pathfinding/integrator.cpp +++ b/libopenage/pathfinding/integrator.cpp @@ -18,7 +18,6 @@ Integrator::Integrator() : field_cache{std::make_unique()} { } - std::shared_ptr Integrator::integrate(const std::shared_ptr &cost_field, const coord::tile_delta &target, bool with_los) { @@ -47,6 +46,8 @@ std::shared_ptr Integrator::integrate(const std::shared_ptrget_id(), other_sector_id); if (cost_field->is_dirty(time)) { + log::log(DBG << "Evicting cached integration and flow fields for portal " << portal->get_id() + << " from sector " << other_sector_id); this->field_cache->evict(cache_key); } else if (this->field_cache->is_cached(cache_key)) { @@ -161,6 +162,8 @@ Integrator::get_return_t Integrator::get(const std::shared_ptr &cost_ bool with_los) { auto cache_key = std::make_pair(portal->get_id(), other_sector_id); if (cost_field->is_dirty(time)) { + log::log(DBG << "Evicting cached integration and flow fields for portal " << portal->get_id() + << " from sector " << other_sector_id); this->field_cache->evict(cache_key); } else if (this->field_cache->is_cached(cache_key)) { @@ -168,9 +171,9 @@ Integrator::get_return_t Integrator::get(const std::shared_ptr &cost_ << " from sector " << other_sector_id); // retrieve cached fields - auto cached_integration_field = this->field_cache->get_integration_field(cache_key); - auto cached_flow_field = this->field_cache->get_flow_field(cache_key); - + auto cached_fields = this->field_cache->get(cache_key); + auto cached_integration_field = cached_fields.first; + auto cached_flow_field = cached_fields.second; if (with_los) { log::log(SPAM << "Performing LOS pass on cached field"); From eb8f73df790d56661908dddb67b9f1e2246e6ebd Mon Sep 17 00:00:00 2001 From: dmwever Date: Mon, 13 Jan 2025 09:11:19 -0600 Subject: [PATCH 058/152] Update copying.md --- copying.md | 1 + 1 file changed, 1 insertion(+) diff --git a/copying.md b/copying.md index 3d97be2f8b..d1434fe4a7 100644 --- a/copying.md +++ b/copying.md @@ -158,6 +158,7 @@ _the openage authors_ are: | Jeremiah Morgan | jere8184 | jeremiahmorgan dawt bham à outlook dawt com | | Tobias Alam | alamt22 | tobiasal à umich dawt edu | | Alex Zhuohao He | ZzzhHe | zhuohao dawt he à outlook dawt com | +| David Wever | dmwever | dmwever à crimson dawt ua dawt edu | If you're a first-time committer, add yourself to the above list. This is not just for legal reasons, but also to keep an overview of all those nicknames. From dfa93447192857fd5423e3bd231551dd9535d938 Mon Sep 17 00:00:00 2001 From: dmwever Date: Thu, 16 Jan 2025 12:54:35 -0600 Subject: [PATCH 059/152] - Sanity Fixes --- libopenage/pathfinding/field_cache.cpp | 2 +- libopenage/pathfinding/sector.cpp | 2 +- libopenage/pathfinding/sector.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libopenage/pathfinding/field_cache.cpp b/libopenage/pathfinding/field_cache.cpp index 417c8a2bbd..86f985e3ba 100644 --- a/libopenage/pathfinding/field_cache.cpp +++ b/libopenage/pathfinding/field_cache.cpp @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #include "field_cache.h" diff --git a/libopenage/pathfinding/sector.cpp b/libopenage/pathfinding/sector.cpp index f50dc209bf..5897bbef84 100644 --- a/libopenage/pathfinding/sector.cpp +++ b/libopenage/pathfinding/sector.cpp @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #include "sector.h" diff --git a/libopenage/pathfinding/sector.h b/libopenage/pathfinding/sector.h index a4ba94fa52..d684a399b6 100644 --- a/libopenage/pathfinding/sector.h +++ b/libopenage/pathfinding/sector.h @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #pragma once From 378f69c07377d811aba32307c384580fd5bba4be Mon Sep 17 00:00:00 2001 From: dmwever Date: Thu, 16 Jan 2025 13:02:59 -0600 Subject: [PATCH 060/152] Add old email for sanity checks --- .mailmap | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index fbf10ad63f..c9a2ee2afd 100644 --- a/.mailmap +++ b/.mailmap @@ -20,4 +20,5 @@ Tobias Feldballe Tobias Feldballe Jonas Borchelt Derek Frogget <114030121+derekfrogget@users.noreply.github.com> -Nikhil Ghosh \ No newline at end of file +Nikhil Ghosh +David Wever <56411717+dmwever@users.noreply.github.com> \ No newline at end of file From 06454693c182fc5f24b3b119782823daf50db133 Mon Sep 17 00:00:00 2001 From: dmwever Date: Thu, 16 Jan 2025 13:11:09 -0600 Subject: [PATCH 061/152] Update .mailmap --- .mailmap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index c9a2ee2afd..8a1a8727b1 100644 --- a/.mailmap +++ b/.mailmap @@ -21,4 +21,4 @@ Tobias Feldballe Jonas Borchelt Derek Frogget <114030121+derekfrogget@users.noreply.github.com> Nikhil Ghosh -David Wever <56411717+dmwever@users.noreply.github.com> \ No newline at end of file +David Wever <56411717+dmwever@users.noreply.github.com> \ No newline at end of file From 3fca4ad4dca7859ff4bacff4b4d1140688688d2d Mon Sep 17 00:00:00 2001 From: Michael Lynch Date: Sat, 18 Jan 2025 07:51:40 -0500 Subject: [PATCH 062/152] Add Nix build result folder to gitignore The nix build step creates an output folder called /result. I've added this to the .gitignore file so that git doesn't try to add generated files/binaries to the source tree. --- .gitignore | 3 +++ copying.md | 1 + 2 files changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 1910b8d6ac..06263991e6 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,9 @@ __pycache__ /.ccls-cache /.cache +# Nix build output +/result + # root dir run script /run /run.cpp diff --git a/copying.md b/copying.md index d1434fe4a7..53ced52632 100644 --- a/copying.md +++ b/copying.md @@ -159,6 +159,7 @@ _the openage authors_ are: | Tobias Alam | alamt22 | tobiasal à umich dawt edu | | Alex Zhuohao He | ZzzhHe | zhuohao dawt he à outlook dawt com | | David Wever | dmwever | dmwever à crimson dawt ua dawt edu | +| Michael Lynch | mtlynch | git à mtlynch dawt io | If you're a first-time committer, add yourself to the above list. This is not just for legal reasons, but also to keep an overview of all those nicknames. From 9ba478ede2945c64a3e3cc8436d1695d4463cf34 Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Fri, 13 Dec 2024 16:17:45 +0000 Subject: [PATCH 063/152] impliment curve::array --- libopenage/curve/CMakeLists.txt | 1 + libopenage/curve/array.cpp | 10 + libopenage/curve/array.h | 244 +++++++++++++++++++++++++ libopenage/curve/keyframe.h | 9 + libopenage/curve/keyframe_container.h | 2 - libopenage/curve/tests/curve_types.cpp | 87 ++++++++- 6 files changed, 348 insertions(+), 5 deletions(-) create mode 100644 libopenage/curve/array.cpp create mode 100644 libopenage/curve/array.h diff --git a/libopenage/curve/CMakeLists.txt b/libopenage/curve/CMakeLists.txt index 623a22a25a..174498e23b 100644 --- a/libopenage/curve/CMakeLists.txt +++ b/libopenage/curve/CMakeLists.txt @@ -1,4 +1,5 @@ add_sources(libopenage + array.cpp base_curve.cpp continuous.cpp discrete.cpp diff --git a/libopenage/curve/array.cpp b/libopenage/curve/array.cpp new file mode 100644 index 0000000000..97e07033ce --- /dev/null +++ b/libopenage/curve/array.cpp @@ -0,0 +1,10 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + + +#include "array.h" + +namespace openage::curve { + +// This file is intended to be empty + +} // openage::curve diff --git a/libopenage/curve/array.h b/libopenage/curve/array.h new file mode 100644 index 0000000000..7076082459 --- /dev/null +++ b/libopenage/curve/array.h @@ -0,0 +1,244 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "curve/keyframe_container.h" +#include "event/evententity.h" + + +// remember to update docs +namespace openage { +namespace curve { + +template +class Array : event::EventEntity { +public: + Array(const std::shared_ptr &loop, + size_t id, + const std::string &idstr = "", + const EventEntity::single_change_notifier ¬ifier = nullptr) : + EventEntity(loop, notifier), _id{id}, _idstr{idstr}, loop{loop}{} + + Array(const Array &) = delete; + + + /** + * Get the last element with elem->time <= time from + * the keyfram container at a given index + */ + T get(const time::time_t &t, const size_t index) const; + + + /** + * Get an array of the last elements with elem->time <= time from + * all keyfram containers contained within this array curve + */ + std::array get_all(const time::time_t &t) const; + + + consteval size_t size() const; + + /** + * Get the last element and associated time which has elem->time <= time from + * the keyfram container at a given index + */ + std::pair frame(const time::time_t &t, const size_t index) const; + + + /** + * Get the first element and associated time which has elem->time >= time from + * the keyfram container at a given index + */ + std::pair next_frame(const time::time_t &t, const size_t index) const; + + + /** + * Insert a new keyframe value at time t at index + */ + void set_insert(const time::time_t &t, const size_t index, T value); + + + /** + * Insert a new keyframe value at time t; delete all keyframes after time t at index + */ + void set_last(const time::time_t &t, const size_t index, T value); + + + /** + * Insert a new keyframe value at time t at index i; remove all other keyframes with time t at index i + */ + void set_replace(const time::time_t &t, const size_t index, T value); + + + /** + * Copy keyframes from another container to this container. + * + * Replaces all keyframes beginning at t >= start with keyframes from \p other. + * + * @param other Curve that keyframes are copied from. + * @param start Start time at which keyframes are replaced (default = -INF). + * Using the default value replaces ALL keyframes of \p this with + * the keyframes of \p other. + */ + void sync(const Array &other, const time::time_t &start); + + + /** + * Get the identifier of this curve. + * + * @return Identifier. + */ + size_t id() const override { + return this->_id; + } + + /** + * Get the human-readable identifier of this curve. + * + * @return Human-readable identifier. + */ + std::string idstr() const override { + if (this->_idstr.size() == 0) { + return std::to_string(this->id()); + } + return this->_idstr; + } + + + KeyframeContainer& operator[] (size_t index) + { + return this->container[index]; + } + + KeyframeContainer operator[] (size_t index) const + { + return this->container[index]; + } + + class Iterator { + public: + Iterator(Array *curve, const time::time_t &time = time::TIME_MAX, size_t offset = 0) : + curve(curve), time(time), offset(offset) {}; + + const T operator*() { + return curve->frame(this->time, this->offset).second; + } + + void operator++() { + this->offset++; + } + + bool operator!=(const Array::Iterator &rhs) const { + return this->offset != rhs.offset; + } + + + private: + size_t offset; + Array *curve; + time::time_t time; + }; + + + Iterator begin(const time::time_t &time = time::TIME_MAX); + + Iterator end(const time::time_t &time = time::TIME_MAX); + + +private: + std::array, Size> container; + + //hint for KeyframeContainer operations + mutable std::array last_element = {}; + + /** + * Identifier for the container + */ + const size_t _id; + + /** + * Human-readable identifier for the container + */ + const std::string _idstr; + + /** + * The eventloop this curve was registered to + */ + const std::shared_ptr loop; +}; + + +template +std::pair Array::frame(const time::time_t &t, const size_t index) const { + size_t frmae_index = container[index].last(t, this->last_element[index]); + this->last_element[index] = frmae_index; + return container[index].get(frmae_index).make_pair(); +} + +template +std::pair Array::next_frame(const time::time_t &t, const size_t index) const { + size_t frmae_index = container[index].last(t, this->last_element[index]); + this->last_element[index] = frmae_index; + return container[index].get(frmae_index + 1).make_pair(); +} + +template +T Array::get(const time::time_t &t, const size_t index) const { + return this->frame(t, index).second; +} + +template +std::array Array::get_all(const time::time_t &t) const { + return [&](std::index_sequence) { + return std::array{this->get(t, I)...}; + }(std::make_index_sequence{}); +} + +template +consteval size_t Array::size() const { + return Size; +} + + +template +void Array::set_insert(const time::time_t &t, const size_t index, T value) { + this->last_element[index] = this->container[index].insert_after(Keyframe(t, value), this->last_element[index]); +} + + +template +void Array::set_last(const time::time_t &t, const size_t index, T value) { + + size_t frame_index = this->container[index].insert_after(Keyframe(t, value), this->last_element[index]); + this->last_element[index] = frame_index; + this->container[index].erase_after(frame_index); +} + + +template +void Array::set_replace(const time::time_t &t, const size_t index, T value) { + this->container[index].insert_overwrite(Keyframe(t, value), this->last_element[index]); +} + +template +void Array::sync(const Array &other, const time::time_t &start) { + for (int i = 0; i < Size; i++) { + this->container[i].sync(other.container[i], start); + } +} + +template +typename Array::Iterator Array::begin(const time::time_t &time) { + return Array::Iterator(this, time); +} + + +template +typename Array::Iterator Array::end(const time::time_t &time) { + return Array::Iterator(this, time, this->container.size()); +} + +} // namespace curve +} // namespace openage diff --git a/libopenage/curve/keyframe.h b/libopenage/curve/keyframe.h index e868783cbb..a34b0c6262 100644 --- a/libopenage/curve/keyframe.h +++ b/libopenage/curve/keyframe.h @@ -54,6 +54,15 @@ class Keyframe { return this->value; } + /** + * Get the value and timestamp of the keyframe in form of std::pair + * @return keyframe pair + */ + std::pair make_pair() const + { + return {time(), val()}; + } + public: /** * Value of the keyframe. diff --git a/libopenage/curve/keyframe_container.h b/libopenage/curve/keyframe_container.h index d9bb9a0bbd..8434942058 100644 --- a/libopenage/curve/keyframe_container.h +++ b/libopenage/curve/keyframe_container.h @@ -279,8 +279,6 @@ class KeyframeContainer { * Replaces all keyframes beginning at t >= start with keyframes from \p other. * * @param other Curve that keyframes are copied from. - * @param converter Function that converts the value type of \p other to the - * value type of \p this. * @param start Start time at which keyframes are replaced (default = -INF). * Using the default value replaces ALL keyframes of \p this with * the keyframes of \p other. diff --git a/libopenage/curve/tests/curve_types.cpp b/libopenage/curve/tests/curve_types.cpp index 421d3fb4d7..6c754e5df9 100644 --- a/libopenage/curve/tests/curve_types.cpp +++ b/libopenage/curve/tests/curve_types.cpp @@ -5,6 +5,7 @@ #include #include +#include "curve/array.h" #include "curve/continuous.h" #include "curve/discrete.h" #include "curve/discrete_mod.h" @@ -232,7 +233,7 @@ void curve_types() { TESTEQUALS(c.get(8), 4); } - //Check the discrete type + // Check the discrete type { auto f = std::make_shared(); Discrete c(f, 0); @@ -257,7 +258,7 @@ void curve_types() { TESTEQUALS(complex.get(10), "Test 10"); } - //Check the discrete mod type + // Check the discrete mod type { auto f = std::make_shared(); DiscreteMod c(f, 0); @@ -290,7 +291,7 @@ void curve_types() { TESTEQUALS(c.get_mod(15, 0), 0); } - //check set_last + // check set_last { auto f = std::make_shared(); Discrete c(f, 0); @@ -386,6 +387,86 @@ void curve_types() { TESTEQUALS(c.get(1), 0); TESTEQUALS(c.get(5), 0); } + + { // array + auto f = std::make_shared(); + + Array a(f,0); + a.set_insert(time::time_t(1), 0, 0); + a.set_insert(time::time_t(1), 1, 1); + a.set_insert(time::time_t(1), 2, 2); + a.set_insert(time::time_t(1), 3, 3); + //a = [[0:0, 1:0],[0:0, 1:1],[0:0, 1:2],[0:0, 1:3]] + + auto res = a.get_all(time::time_t(1)); + TESTEQUALS(res[0], 0); + TESTEQUALS(res[1], 1); + TESTEQUALS(res[2], 2); + TESTEQUALS(res[3], 3); + + Array other(f,0); + other[0].last(999); + other[1].last(999); + other[2].last(999); + other[3].last(999); + other.set_insert(time::time_t(1), 0, 4); + other.set_insert(time::time_t(1), 1, 5); + other.set_insert(time::time_t(1), 2, 6); + other.set_insert(time::time_t(1), 3, 7); + //other = [[0:999, 1:4],[0:999, 1:5],[0:999, 1:6],[0:999, 1:7]] + + + a.sync(other, time::time_t(1)); + //a = [[0:0, 1:4],[0:0, 1:5],[0:0, 1:6],[0:0, 1:7]] + + res = a.get_all(time::time_t(0)); + TESTEQUALS(res[0], 0); + TESTEQUALS(res[1], 0); + TESTEQUALS(res[2], 0); + TESTEQUALS(res[3], 0); + res = a.get_all(time::time_t(1)); + TESTEQUALS(res[0], 4); + TESTEQUALS(res[1], 5); + TESTEQUALS(res[2], 6); + TESTEQUALS(res[3], 7); + + // Additional tests + a.set_insert(time::time_t(2), 0, 15); + a.set_insert(time::time_t(2), 0, 20); + a.set_replace(time::time_t(2), 0, 25); + TESTEQUALS(a.get(time::time_t(2), 0), 25); + // a = [[0:0, 1:4, 2:25],[0:0, 1:5],[0:0, 1:6],[0:0, 1:7]] + + a.set_insert(time::time_t(3), 0, 30); + a.set_insert(time::time_t(4), 0, 40); + a.set_last(time::time_t(3), 0, 35); + TESTEQUALS(a.get(time::time_t(3), 0), 35); + // a = [[0:0, 1:4, 2:25, 3:35],[0:0, 1:5],[0:0, 1:6],[0:0, 1:7]] + + auto frame = a.frame(time::time_t(1), 2); + TESTEQUALS(frame.second, 6); + TESTEQUALS(frame.first, time::time_t(1)); + + a.set_insert(time::time_t(5), 3, 40); + auto next_frame = a.next_frame(time::time_t(1), 3); + TESTEQUALS(next_frame.second, 40); + TESTEQUALS(next_frame.first, time::time_t(5)); + + // Test operator[] + TESTEQUALS(a[0].get(a[0].last(time::time_t(2))).val(), 25); + TESTEQUALS(a[1].get(a[1].last(time::time_t(2))).val(), 5); + + // Test begin and end + auto it = a.begin(time::time_t(1)); + TESTEQUALS(*it, 4); + ++it; + TESTEQUALS(*it, 5); + ++it; + TESTEQUALS(*it, 6); + ++it; + TESTEQUALS(*it, 7); + ++it; + } } } // namespace openage::curve::tests From 8647a7bc98ef652c5b1e96ddfc3ea3c2cef74f1d Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Thu, 26 Dec 2024 15:44:48 +0000 Subject: [PATCH 064/152] address comments --- libopenage/curve/array.h | 94 +++++++++++++++----------- libopenage/curve/base_curve.h | 4 +- libopenage/curve/tests/container.cpp | 84 ++++++++++++++++++++++- libopenage/curve/tests/curve_types.cpp | 81 ---------------------- 4 files changed, 139 insertions(+), 124 deletions(-) diff --git a/libopenage/curve/array.h b/libopenage/curve/array.h index 7076082459..34109365db 100644 --- a/libopenage/curve/array.h +++ b/libopenage/curve/array.h @@ -16,10 +16,10 @@ template class Array : event::EventEntity { public: Array(const std::shared_ptr &loop, - size_t id, - const std::string &idstr = "", - const EventEntity::single_change_notifier ¬ifier = nullptr) : - EventEntity(loop, notifier), _id{id}, _idstr{idstr}, loop{loop}{} + size_t id, + const std::string &idstr = "", + const EventEntity::single_change_notifier ¬ifier = nullptr) : + EventEntity(loop, notifier), _id{id}, _idstr{idstr}, loop{loop} {} Array(const Array &) = delete; @@ -28,16 +28,16 @@ class Array : event::EventEntity { * Get the last element with elem->time <= time from * the keyfram container at a given index */ - T get(const time::time_t &t, const size_t index) const; + T at(const time::time_t &t, const size_t index) const; /** * Get an array of the last elements with elem->time <= time from * all keyfram containers contained within this array curve */ - std::array get_all(const time::time_t &t) const; - + std::array get(const time::time_t &t) const; + // Get the amount of KeyframeContainers in array curve consteval size_t size() const; /** @@ -107,92 +107,107 @@ class Array : event::EventEntity { } - KeyframeContainer& operator[] (size_t index) - { - return this->container[index]; - } - - KeyframeContainer operator[] (size_t index) const - { - return this->container[index]; + // get a copy to the KeyframeContainer at index + KeyframeContainer operator[](size_t index) const { + return this->container.at(index); } + // Array::Iterator is used to iterate over KeyframeContainers contained in a curve at a given time. class Iterator { public: Iterator(Array *curve, const time::time_t &time = time::TIME_MAX, size_t offset = 0) : curve(curve), time(time), offset(offset) {}; + // returns a copy of the keyframe at the current offset and time const T operator*() { - return curve->frame(this->time, this->offset).second; + return this->curve->frame(this->time, this->offset).second; } + // increments the Iterator to point at the next KeyframeContainer void operator++() { this->offset++; } + // Compare two Iterators by their offset bool operator!=(const Array::Iterator &rhs) const { return this->offset != rhs.offset; } private: + // used to index the Curve::Array pointed to by this iterator size_t offset; + + // curve::Array that this iterator is iterating over Array *curve; + + // time at which this iterator is iterating over time::time_t time; }; - Iterator begin(const time::time_t &time = time::TIME_MAX); + // iterator pointing to a keyframe of the first KeyframeContainer in the curve at a given time + Iterator begin(const time::time_t &time = time::TIME_MIN); - Iterator end(const time::time_t &time = time::TIME_MAX); + // iterator pointing after the last KeyframeContainer in the curve at a given time + Iterator end(const time::time_t &time = time::TIME_MIN); private: std::array, Size> container; - //hint for KeyframeContainer operations + /** + * hints for KeyframeContainer operations, mutable as hints + * are updated by const read functions. + * This is used to speed up the search for next keyframe to be accessed + */ mutable std::array last_element = {}; /** - * Identifier for the container - */ + * Identifier for the container + */ const size_t _id; /** - * Human-readable identifier for the container - */ + * Human-readable identifier for the container + */ const std::string _idstr; /** - * The eventloop this curve was registered to - */ + * The eventloop this curve was registered to + */ const std::shared_ptr loop; }; template std::pair Array::frame(const time::time_t &t, const size_t index) const { - size_t frmae_index = container[index].last(t, this->last_element[index]); - this->last_element[index] = frmae_index; - return container[index].get(frmae_index).make_pair(); + size_t &hint = this->last_element[index]; + size_t frame_index = this->container.at(index).last(t, hint); + hint = frame_index; + return this->container.at(index).get(frame_index).make_pair(); } template std::pair Array::next_frame(const time::time_t &t, const size_t index) const { - size_t frmae_index = container[index].last(t, this->last_element[index]); - this->last_element[index] = frmae_index; - return container[index].get(frmae_index + 1).make_pair(); + size_t &hint = this->last_element[index]; + size_t frame_index = this->container.at(index).last(t, hint); + hint = frame_index; + return this->container.at(index).get(frame_index + 1).make_pair(); } template -T Array::get(const time::time_t &t, const size_t index) const { - return this->frame(t, index).second; +T Array::at(const time::time_t &t, const size_t index) const { + size_t &hint = this->last_element[index]; + size_t frame_index = this->container.at(index).last(t, hint); + hint = frame_index; + return this->container.at(index).get(frame_index).val(); } template -std::array Array::get_all(const time::time_t &t) const { +std::array Array::get(const time::time_t &t) const { return [&](std::index_sequence) { - return std::array{this->get(t, I)...}; + return std::array{this->at(t, I)...}; }(std::make_index_sequence{}); } @@ -204,22 +219,21 @@ consteval size_t Array::size() const { template void Array::set_insert(const time::time_t &t, const size_t index, T value) { - this->last_element[index] = this->container[index].insert_after(Keyframe(t, value), this->last_element[index]); + this->last_element[index] = this->container.at(index).insert_after(Keyframe{t, value}, this->last_element[index]); } template void Array::set_last(const time::time_t &t, const size_t index, T value) { - - size_t frame_index = this->container[index].insert_after(Keyframe(t, value), this->last_element[index]); + size_t frame_index = this->container.at(index).insert_after(Keyframe{t, value}, this->last_element[index]); this->last_element[index] = frame_index; - this->container[index].erase_after(frame_index); + this->container.at(index).erase_after(frame_index); } template void Array::set_replace(const time::time_t &t, const size_t index, T value) { - this->container[index].insert_overwrite(Keyframe(t, value), this->last_element[index]); + this->container.at(index).insert_overwrite(Keyframe{t, value}, this->last_element[index]); } template diff --git a/libopenage/curve/base_curve.h b/libopenage/curve/base_curve.h index dc58885b98..3d2ff20343 100644 --- a/libopenage/curve/base_curve.h +++ b/libopenage/curve/base_curve.h @@ -245,7 +245,7 @@ template std::pair BaseCurve::frame(const time::time_t &time) const { auto e = this->container.last(time, this->container.size()); auto elem = this->container.get(e); - return std::make_pair(elem.time(), elem.val()); + return elem.make_pair(); } @@ -254,7 +254,7 @@ std::pair BaseCurve::next_frame(const time::time_t &ti auto e = this->container.last(time, this->container.size()); e++; auto elem = this->container.get(e); - return std::make_pair(elem.time(), elem.val()); + return elem.make_pair(); } template diff --git a/libopenage/curve/tests/container.cpp b/libopenage/curve/tests/container.cpp index 020e2aad99..51a5e4bee2 100644 --- a/libopenage/curve/tests/container.cpp +++ b/libopenage/curve/tests/container.cpp @@ -9,6 +9,7 @@ #include #include +#include "curve/array.h" #include "curve/iterator.h" #include "curve/map.h" #include "curve/map_filter_iterator.h" @@ -56,7 +57,7 @@ void test_map() { // Basic tests test lookup in the middle of the range. { - auto t = map.at(2, 0); //At timestamp 2 element 0 + auto t = map.at(2, 0); // At timestamp 2 element 0 TESTEQUALS(t.has_value(), true); TESTEQUALS(t.value().value(), 0); t = map.at(20, 5); @@ -242,11 +243,92 @@ void test_queue() { TESTEQUALS(q.empty(100001), false); } +void test_array() { + auto f = std::make_shared(); + + Array a(f, 0); + a.set_insert(1, 0, 0); + a.set_insert(1, 1, 1); + a.set_insert(1, 2, 2); + a.set_insert(1, 3, 3); + // a = [[0:0, 1:0],[0:0, 1:1],[0:0, 1:2],[0:0, 1:3]] + + auto res = a.get(1); + TESTEQUALS(res.at(0), 0); + TESTEQUALS(res.at(1), 1); + TESTEQUALS(res.at(2), 2); + TESTEQUALS(res.at(3), 3); + + Array other(f, 0); + other.set_last(0, 0, 999); + other.set_last(0, 1, 999); + other.set_last(0, 2, 999); + other.set_last(0, 3, 999); + + other.set_insert(1, 0, 4); + other.set_insert(1, 1, 5); + other.set_insert(1, 2, 6); + other.set_insert(1, 3, 7); + // other = [[0:999, 1:4],[0:999, 1:5],[0:999, 1:6],[0:999, 1:7]] + + + a.sync(other, 1); + // a = [[0:0, 1:4],[0:0, 1:5],[0:0, 1:6],[0:0, 1:7]] + + res = a.get(0); + TESTEQUALS(res.at(0), 0); + TESTEQUALS(res.at(1), 0); + TESTEQUALS(res.at(2), 0); + TESTEQUALS(res.at(3), 0); + res = a.get(1); + TESTEQUALS(res.at(0), 4); + TESTEQUALS(res.at(1), 5); + TESTEQUALS(res.at(2), 6); + TESTEQUALS(res.at(3), 7); + + // Additional tests + a.set_insert(2, 0, 15); + a.set_insert(2, 0, 20); + a.set_replace(2, 0, 25); + TESTEQUALS(a.at(2, 0), 25); + // a = [[0:0, 1:4, 2:25],[0:0, 1:5],[0:0, 1:6],[0:0, 1:7]] + + a.set_insert(3, 0, 30); + a.set_insert(4, 0, 40); + a.set_last(3, 0, 35); + TESTEQUALS(a.at(4, 0), 35); + // a = [[0:0, 1:4, 2:25, 3:35],[0:0, 1:5],[0:0, 1:6],[0:0, 1:7]] + + auto frame = a.frame(1, 2); + TESTEQUALS(frame.second, 6); + TESTEQUALS(frame.first, 1); + + a.set_insert(5, 3, 40); + auto next_frame = a.next_frame(1, 3); + TESTEQUALS(next_frame.second, 40); + TESTEQUALS(next_frame.first, 5); + + // Test operator[] + TESTEQUALS(a[0].get(a[0].last(2)).val(), 25); + TESTEQUALS(a[1].get(a[1].last(2)).val(), 5); + + // Test begin and end + auto it = a.begin(1); + TESTEQUALS(*it, 4); + ++it; + TESTEQUALS(*it, 5); + ++it; + TESTEQUALS(*it, 6); + ++it; + TESTEQUALS(*it, 7); +} + void container() { test_map(); test_list(); test_queue(); + test_array(); } diff --git a/libopenage/curve/tests/curve_types.cpp b/libopenage/curve/tests/curve_types.cpp index 6c754e5df9..935aa7141d 100644 --- a/libopenage/curve/tests/curve_types.cpp +++ b/libopenage/curve/tests/curve_types.cpp @@ -5,7 +5,6 @@ #include #include -#include "curve/array.h" #include "curve/continuous.h" #include "curve/discrete.h" #include "curve/discrete_mod.h" @@ -387,86 +386,6 @@ void curve_types() { TESTEQUALS(c.get(1), 0); TESTEQUALS(c.get(5), 0); } - - { // array - auto f = std::make_shared(); - - Array a(f,0); - a.set_insert(time::time_t(1), 0, 0); - a.set_insert(time::time_t(1), 1, 1); - a.set_insert(time::time_t(1), 2, 2); - a.set_insert(time::time_t(1), 3, 3); - //a = [[0:0, 1:0],[0:0, 1:1],[0:0, 1:2],[0:0, 1:3]] - - auto res = a.get_all(time::time_t(1)); - TESTEQUALS(res[0], 0); - TESTEQUALS(res[1], 1); - TESTEQUALS(res[2], 2); - TESTEQUALS(res[3], 3); - - Array other(f,0); - other[0].last(999); - other[1].last(999); - other[2].last(999); - other[3].last(999); - other.set_insert(time::time_t(1), 0, 4); - other.set_insert(time::time_t(1), 1, 5); - other.set_insert(time::time_t(1), 2, 6); - other.set_insert(time::time_t(1), 3, 7); - //other = [[0:999, 1:4],[0:999, 1:5],[0:999, 1:6],[0:999, 1:7]] - - - a.sync(other, time::time_t(1)); - //a = [[0:0, 1:4],[0:0, 1:5],[0:0, 1:6],[0:0, 1:7]] - - res = a.get_all(time::time_t(0)); - TESTEQUALS(res[0], 0); - TESTEQUALS(res[1], 0); - TESTEQUALS(res[2], 0); - TESTEQUALS(res[3], 0); - res = a.get_all(time::time_t(1)); - TESTEQUALS(res[0], 4); - TESTEQUALS(res[1], 5); - TESTEQUALS(res[2], 6); - TESTEQUALS(res[3], 7); - - // Additional tests - a.set_insert(time::time_t(2), 0, 15); - a.set_insert(time::time_t(2), 0, 20); - a.set_replace(time::time_t(2), 0, 25); - TESTEQUALS(a.get(time::time_t(2), 0), 25); - // a = [[0:0, 1:4, 2:25],[0:0, 1:5],[0:0, 1:6],[0:0, 1:7]] - - a.set_insert(time::time_t(3), 0, 30); - a.set_insert(time::time_t(4), 0, 40); - a.set_last(time::time_t(3), 0, 35); - TESTEQUALS(a.get(time::time_t(3), 0), 35); - // a = [[0:0, 1:4, 2:25, 3:35],[0:0, 1:5],[0:0, 1:6],[0:0, 1:7]] - - auto frame = a.frame(time::time_t(1), 2); - TESTEQUALS(frame.second, 6); - TESTEQUALS(frame.first, time::time_t(1)); - - a.set_insert(time::time_t(5), 3, 40); - auto next_frame = a.next_frame(time::time_t(1), 3); - TESTEQUALS(next_frame.second, 40); - TESTEQUALS(next_frame.first, time::time_t(5)); - - // Test operator[] - TESTEQUALS(a[0].get(a[0].last(time::time_t(2))).val(), 25); - TESTEQUALS(a[1].get(a[1].last(time::time_t(2))).val(), 5); - - // Test begin and end - auto it = a.begin(time::time_t(1)); - TESTEQUALS(*it, 4); - ++it; - TESTEQUALS(*it, 5); - ++it; - TESTEQUALS(*it, 6); - ++it; - TESTEQUALS(*it, 7); - ++it; - } } } // namespace openage::curve::tests From 5f5c0f4b4d9011f9f4d8bc0ff8c937a5aa3d4be1 Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Fri, 27 Dec 2024 11:12:18 +0000 Subject: [PATCH 065/152] libopenage/curve/base_curve.h --- libopenage/curve/base_curve.h | 1 + 1 file changed, 1 insertion(+) diff --git a/libopenage/curve/base_curve.h b/libopenage/curve/base_curve.h index 3d2ff20343..e5e265b5a0 100644 --- a/libopenage/curve/base_curve.h +++ b/libopenage/curve/base_curve.h @@ -47,6 +47,7 @@ class BaseCurve : public event::EventEntity { // registration. If you need to copy a curve, use the sync() method. // TODO: if copying is enabled again, these members have to be reassigned: _id, _idstr, last_element BaseCurve(const BaseCurve &) = delete; + BaseCurve &operator=(const BaseCurve &) = delete; BaseCurve(BaseCurve &&) = default; From a5600f44d2f5450c636b3352a978c7f4b7b1f11f Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Wed, 8 Jan 2025 23:55:27 +0000 Subject: [PATCH 066/152] address new comments --- libopenage/curve/array.h | 74 ++++++++++++++++++++-------- libopenage/curve/tests/container.cpp | 5 +- 2 files changed, 54 insertions(+), 25 deletions(-) diff --git a/libopenage/curve/array.h b/libopenage/curve/array.h index 34109365db..fb5be284cd 100644 --- a/libopenage/curve/array.h +++ b/libopenage/curve/array.h @@ -1,25 +1,34 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #pragma once #include +#include "curve/iterator.h" #include "curve/keyframe_container.h" #include "event/evententity.h" -// remember to update docs +// ASDF: remember to update docs namespace openage { namespace curve { template class Array : event::EventEntity { public: + /** + * The underlaying container type. + */ + using container_t = std::array, Size>; + Array(const std::shared_ptr &loop, size_t id, + const T &default_val = T(), const std::string &idstr = "", const EventEntity::single_change_notifier ¬ifier = nullptr) : - EventEntity(loop, notifier), _id{id}, _idstr{idstr}, loop{loop} {} + EventEntity(loop, notifier), + _id{id}, _idstr{idstr}, loop{loop}, container{KeyframeContainer(default_val)} { + } Array(const Array &) = delete; @@ -37,7 +46,9 @@ class Array : event::EventEntity { */ std::array get(const time::time_t &t) const; - // Get the amount of KeyframeContainers in array curve + /** + * Get the amount of KeyframeContainers in array curve + */ consteval size_t size() const; /** @@ -107,59 +118,80 @@ class Array : event::EventEntity { } - // get a copy to the KeyframeContainer at index - KeyframeContainer operator[](size_t index) const { - return this->container.at(index); - } - - // Array::Iterator is used to iterate over KeyframeContainers contained in a curve at a given time. + /** + * Array::Iterator is used to iterate over KeyframeContainers contained in a curve at a given time. + */ class Iterator { public: Iterator(Array *curve, const time::time_t &time = time::TIME_MAX, size_t offset = 0) : curve(curve), time(time), offset(offset) {}; - // returns a copy of the keyframe at the current offset and time + /** + * returns a copy of the keyframe at the current offset and time + */ const T operator*() { return this->curve->frame(this->time, this->offset).second; } - // increments the Iterator to point at the next KeyframeContainer + /** + * increments the Iterator to point at the next KeyframeContainer + */ void operator++() { this->offset++; } - // Compare two Iterators by their offset + /** + * Compare two Iterators by their offset + */ bool operator!=(const Array::Iterator &rhs) const { return this->offset != rhs.offset; } private: - // used to index the Curve::Array pointed to by this iterator + /** + * used to index the Curve::Array pointed to by this iterator + */ size_t offset; - // curve::Array that this iterator is iterating over + /** + * the curve object that this iterator, iterates over + */ Array *curve; - // time at which this iterator is iterating over + /** + * time at which this iterator is iterating over + */ time::time_t time; }; - // iterator pointing to a keyframe of the first KeyframeContainer in the curve at a given time + /** + * iterator pointing to a keyframe of the first KeyframeContainer in the curve at a given time + */ Iterator begin(const time::time_t &time = time::TIME_MIN); - // iterator pointing after the last KeyframeContainer in the curve at a given time + /** + * iterator pointing after the last KeyframeContainer in the curve at a given time + */ Iterator end(const time::time_t &time = time::TIME_MIN); private: + /** + * get a copy to the KeyframeContainer at index + */ + KeyframeContainer operator[](size_t index) const { + return this->container.at(index); + } + + std::array, Size> container; /** - * hints for KeyframeContainer operations, mutable as hints - * are updated by const read functions. - * This is used to speed up the search for next keyframe to be accessed + * hints for KeyframeContainer operations + * hints is used to speed up the search for next keyframe to be accessed + * mutable as hints are updated by const read-only functions. */ mutable std::array last_element = {}; diff --git a/libopenage/curve/tests/container.cpp b/libopenage/curve/tests/container.cpp index 51a5e4bee2..8aed3c8d05 100644 --- a/libopenage/curve/tests/container.cpp +++ b/libopenage/curve/tests/container.cpp @@ -247,6 +247,7 @@ void test_array() { auto f = std::make_shared(); Array a(f, 0); + const std::array &default_val = std::array(); a.set_insert(1, 0, 0); a.set_insert(1, 1, 1); a.set_insert(1, 2, 2); @@ -308,10 +309,6 @@ void test_array() { TESTEQUALS(next_frame.second, 40); TESTEQUALS(next_frame.first, 5); - // Test operator[] - TESTEQUALS(a[0].get(a[0].last(2)).val(), 25); - TESTEQUALS(a[1].get(a[1].last(2)).val(), 5); - // Test begin and end auto it = a.begin(1); TESTEQUALS(*it, 4); From 2f3573236af59c0a5ada1226860bb31c6e84d5ae Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Thu, 9 Jan 2025 16:04:55 +0000 Subject: [PATCH 067/152] update curves.md --- doc/code/curves.md | 35 ++++++++++++++++++++++++++++++++++- libopenage/curve/array.h | 1 - 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/doc/code/curves.md b/doc/code/curves.md index ff4bbb248d..372a4b0153 100644 --- a/doc/code/curves.md +++ b/doc/code/curves.md @@ -198,7 +198,7 @@ e.g. angles between 0 and 360 degrees. ### Container Container curves are intended for storing changes to collections and containers. -The currently supported containers are `Queue` and `UnorderedMap`. +The currently supported containers are `Queue`, `UnorderedMap` and `Array`. The most important distinction between regular C++ containers and curve containers is that curve containers track the *lifespan* of each element, i.e. their insertion time, @@ -253,3 +253,36 @@ Unordered map curve containers store key-value pairs while additionally keeping track of element insertion time. Requests for a key `k` at time `t` will return the value of `k` at that time. The unordered map can also be iterated over for a specific time `t` which allows access to all key-value pairs that were in the map at time `t`. + + +#### Array +Array curve containers are the equivalent to the `std::array` C++ containers. Unlike other curve containers, +each element of the underlying std::array is a `keyframecontainer` and when a value is added to the `Array curve` +at a given index they are added to the respective `keyframecontainer` at that index as a keyframe. + +**Read** + +Read operations retrieve values for a specific point in time. + +| Method | Description | +| ----------------- | --------------------------------------- ---------------------------------| +| `get(t, i)` | Get value of index `i` at time <= `t` | +| `get(t)` | Get array of keyframes at time <= `t` | +| `size()` | Get size of the array | +| `frame(t, i)` | Get the time and value of the keyframe at index `i` with time <= `t` | +| `next_frame(t, i)`| Get the time and value of the first keyframe at index `i` with time > `t`| + +**Modify** + +Modify operations insert values for a specific point in time. + +| Method | Description | +| ------------------------- | ------------------------------------------------------------------------------------------| +| `set_insert(t, i, value)` | Insert a new keyframe(`t`, `value`) at index `i` | +| `set_last(t, i, value)` | Insert a new keyframe(`t`, `value`) at index `i`; delete all keyframes after time `t` | +| `set_replace(t, i, value)`| Insert a new keyframe(`t`, `value`) at index `i`; remove all other keyframes with time `t`| + +**Copy** +| Method | Description | +| ---------------- | ------------------------------------------------------------------------------------------------ | +| `sync(Curve, t)` | Replace all keyframes from self after time `t` with keyframes from source `Curve` after time `t` | diff --git a/libopenage/curve/array.h b/libopenage/curve/array.h index fb5be284cd..8b8ca00e77 100644 --- a/libopenage/curve/array.h +++ b/libopenage/curve/array.h @@ -9,7 +9,6 @@ #include "event/evententity.h" -// ASDF: remember to update docs namespace openage { namespace curve { From bb9b9673c74c467b6a5cd8a66bdd53b04c961a0c Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 16 Jan 2025 03:48:16 +0100 Subject: [PATCH 068/152] curve: Add more docstrings to array container. --- libopenage/curve/array.h | 176 ++++++++++++++++++++++++++------------- 1 file changed, 117 insertions(+), 59 deletions(-) diff --git a/libopenage/curve/array.h b/libopenage/curve/array.h index 8b8ca00e77..4e39ff28ab 100644 --- a/libopenage/curve/array.h +++ b/libopenage/curve/array.h @@ -20,68 +20,111 @@ class Array : event::EventEntity { */ using container_t = std::array, Size>; + /** + * Create a new array curve container. + * + * @param loop Event loop this curve is registered on for notifications. + * @param id Unique identifier for this curve. + * @param idstr Human-readable identifier for this curve. + * @param notifier Function to call when this curve changes. + * @param default_val Default value for all elements in the array. + */ Array(const std::shared_ptr &loop, size_t id, - const T &default_val = T(), const std::string &idstr = "", - const EventEntity::single_change_notifier ¬ifier = nullptr) : + const EventEntity::single_change_notifier ¬ifier = nullptr, + const T &default_val = T()) : EventEntity(loop, notifier), - _id{id}, _idstr{idstr}, loop{loop}, container{KeyframeContainer(default_val)} { - } - + containers{KeyframeContainer(default_val)}, + _id{id}, + _idstr{idstr}, + loop{loop}, + last_element{} {} + + // prevent copying because it invalidates the usage of unique ids and event + // registration. If you need to copy a curve, use the sync() method. Array(const Array &) = delete; + Array &operator=(const Array &) = delete; + Array(Array &&) = default; /** - * Get the last element with elem->time <= time from - * the keyfram container at a given index + * Get the last element with elem->time <= time. + * + * @param t Time of access. + * @param index Index of the array element. + * + * @return Value of the last element with time <= t. */ T at(const time::time_t &t, const size_t index) const; - /** - * Get an array of the last elements with elem->time <= time from - * all keyfram containers contained within this array curve + * Get all elements at time t. + * + * @param t Time of access. + * + * @return Array of values at time t. */ std::array get(const time::time_t &t) const; /** - * Get the amount of KeyframeContainers in array curve + * Get the size of the array. + * + * @return Array size. */ consteval size_t size() const; /** - * Get the last element and associated time which has elem->time <= time from - * the keyfram container at a given index + * Get the last keyframe value and time with elem->time <= time. + * + * @param t Time of access. + * @param index Index of the array element. + * + * @return Time-value pair of the last keyframe with time <= t. */ std::pair frame(const time::time_t &t, const size_t index) const; - /** - * Get the first element and associated time which has elem->time >= time from - * the keyfram container at a given index + * Get the first keyframe value and time with elem->time > time. + * + * If there is no keyframe with time > t, the behavior is undefined. + * + * @param t Time of access. + * @param index Index of the array element. + * + * @return Time-value pair of the first keyframe with time > t. */ std::pair next_frame(const time::time_t &t, const size_t index) const; - /** - * Insert a new keyframe value at time t at index + * Insert a new keyframe value at time t. + * + * If there is already a keyframe at time t, the new keyframe is inserted after the existing one. + * + * @param t Time of insertion. + * @param index Index of the array element. + * @param value Keyframe value. */ void set_insert(const time::time_t &t, const size_t index, T value); - /** - * Insert a new keyframe value at time t; delete all keyframes after time t at index + * Insert a new keyframe value at time t. Erase all other keyframes with elem->time > t. + * + * @param t Time of insertion. + * @param index Index of the array element. + * @param value Keyframe value. */ void set_last(const time::time_t &t, const size_t index, T value); - /** - * Insert a new keyframe value at time t at index i; remove all other keyframes with time t at index i + * Replace all keyframes at elem->time == t with a new keyframe value. + * + * @param t Time of insertion. + * @param index Index of the array element. + * @param value Keyframe value. */ void set_replace(const time::time_t &t, const size_t index, T value); - /** * Copy keyframes from another container to this container. * @@ -94,7 +137,6 @@ class Array : event::EventEntity { */ void sync(const Array &other, const time::time_t &start); - /** * Get the identifier of this curve. * @@ -116,7 +158,6 @@ class Array : event::EventEntity { return this->_idstr; } - /** * Array::Iterator is used to iterate over KeyframeContainers contained in a curve at a given time. */ @@ -146,7 +187,6 @@ class Array : event::EventEntity { return this->offset != rhs.offset; } - private: /** * used to index the Curve::Array pointed to by this iterator @@ -164,7 +204,6 @@ class Array : event::EventEntity { time::time_t time; }; - /** * iterator pointing to a keyframe of the first KeyframeContainer in the curve at a given time */ @@ -175,24 +214,20 @@ class Array : event::EventEntity { */ Iterator end(const time::time_t &time = time::TIME_MIN); - private: /** * get a copy to the KeyframeContainer at index */ KeyframeContainer operator[](size_t index) const { - return this->container.at(index); + return this->containers.at(index); } - - std::array, Size> container; - /** - * hints for KeyframeContainer operations - * hints is used to speed up the search for next keyframe to be accessed - * mutable as hints are updated by const read-only functions. + * Containers for each array element. + * + * Each element is managed by a KeyframeContainer. */ - mutable std::array last_element = {}; + std::array, Size> containers; /** * Identifier for the container @@ -208,31 +243,43 @@ class Array : event::EventEntity { * The eventloop this curve was registered to */ const std::shared_ptr loop; + + /** + * Cache hints for containers. Stores the index of the last keyframe accessed in each container. + * + * hints is used to speed up the search for keyframes. + * + * mutable as hints are updated by const read-only functions. + */ + mutable std::array::elem_ptr, Size> last_element = {}; }; template -std::pair Array::frame(const time::time_t &t, const size_t index) const { +std::pair Array::frame(const time::time_t &t, + const size_t index) const { size_t &hint = this->last_element[index]; - size_t frame_index = this->container.at(index).last(t, hint); + size_t frame_index = this->containers.at(index).last(t, hint); hint = frame_index; - return this->container.at(index).get(frame_index).make_pair(); + return this->containers.at(index).get(frame_index).make_pair(); } template -std::pair Array::next_frame(const time::time_t &t, const size_t index) const { +std::pair Array::next_frame(const time::time_t &t, + const size_t index) const { size_t &hint = this->last_element[index]; - size_t frame_index = this->container.at(index).last(t, hint); + size_t frame_index = this->containers.at(index).last(t, hint); hint = frame_index; - return this->container.at(index).get(frame_index + 1).make_pair(); + return this->containers.at(index).get(frame_index + 1).make_pair(); } template -T Array::at(const time::time_t &t, const size_t index) const { +T Array::at(const time::time_t &t, + const size_t index) const { size_t &hint = this->last_element[index]; - size_t frame_index = this->container.at(index).last(t, hint); + size_t frame_index = this->containers.at(index).last(t, hint); hint = frame_index; - return this->container.at(index).get(frame_index).val(); + return this->containers.at(index).get(frame_index).val(); } template @@ -247,31 +294,43 @@ consteval size_t Array::size() const { return Size; } - template -void Array::set_insert(const time::time_t &t, const size_t index, T value) { - this->last_element[index] = this->container.at(index).insert_after(Keyframe{t, value}, this->last_element[index]); -} +void Array::set_insert(const time::time_t &t, + const size_t index, + T value) { + this->last_element[index] = this->containers.at(index).insert_after(Keyframe{t, value}, this->last_element[index]); + // ASDF: Change notification +} template -void Array::set_last(const time::time_t &t, const size_t index, T value) { - size_t frame_index = this->container.at(index).insert_after(Keyframe{t, value}, this->last_element[index]); +void Array::set_last(const time::time_t &t, + const size_t index, + T value) { + size_t frame_index = this->containers.at(index).insert_after(Keyframe{t, value}, this->last_element[index]); this->last_element[index] = frame_index; - this->container.at(index).erase_after(frame_index); -} + this->containers.at(index).erase_after(frame_index); + // ASDF: Change notification +} template -void Array::set_replace(const time::time_t &t, const size_t index, T value) { - this->container.at(index).insert_overwrite(Keyframe{t, value}, this->last_element[index]); +void Array::set_replace(const time::time_t &t, + const size_t index, + T value) { + this->containers.at(index).insert_overwrite(Keyframe{t, value}, this->last_element[index]); + + // ASDF: Change notification } template -void Array::sync(const Array &other, const time::time_t &start) { +void Array::sync(const Array &other, + const time::time_t &start) { for (int i = 0; i < Size; i++) { - this->container[i].sync(other.container[i], start); + this->containers[i].sync(other.containers[i], start); } + + // ASDF: Change notification } template @@ -279,10 +338,9 @@ typename Array::Iterator Array::begin(const time::time_t &time return Array::Iterator(this, time); } - template typename Array::Iterator Array::end(const time::time_t &time) { - return Array::Iterator(this, time, this->container.size()); + return Array::Iterator(this, time, this->containers.size()); } } // namespace curve From e36e94fc47242be79cf821042c91c529df6531e1 Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 16 Jan 2025 03:49:13 +0100 Subject: [PATCH 069/152] curve: Add event notification function call to modify operations. --- libopenage/curve/array.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libopenage/curve/array.h b/libopenage/curve/array.h index 4e39ff28ab..2bcf52522d 100644 --- a/libopenage/curve/array.h +++ b/libopenage/curve/array.h @@ -300,7 +300,7 @@ void Array::set_insert(const time::time_t &t, T value) { this->last_element[index] = this->containers.at(index).insert_after(Keyframe{t, value}, this->last_element[index]); - // ASDF: Change notification + this->changes(t); } template @@ -311,7 +311,7 @@ void Array::set_last(const time::time_t &t, this->last_element[index] = frame_index; this->containers.at(index).erase_after(frame_index); - // ASDF: Change notification + this->changes(t); } template @@ -320,7 +320,7 @@ void Array::set_replace(const time::time_t &t, T value) { this->containers.at(index).insert_overwrite(Keyframe{t, value}, this->last_element[index]); - // ASDF: Change notification + this->changes(t); } template @@ -330,7 +330,7 @@ void Array::sync(const Array &other, this->containers[i].sync(other.containers[i], start); } - // ASDF: Change notification + this->changes(start); } template From 4b12a991fdbc1db7d39d18922e5408ce266b71bc Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 16 Jan 2025 04:28:09 +0100 Subject: [PATCH 070/152] curve: Allow array default values to be different for each element. --- libopenage/curve/array.h | 49 ++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/libopenage/curve/array.h b/libopenage/curve/array.h index 2bcf52522d..857638f5e0 100644 --- a/libopenage/curve/array.h +++ b/libopenage/curve/array.h @@ -12,13 +12,22 @@ namespace openage { namespace curve { +template +constexpr std::array, Size> init_default_vals(const std::array &default_vals) { + std::array, Size> containers; + for (size_t i = 0; i < Size; i++) { + containers[i] = KeyframeContainer(default_vals[i]); + } + return containers; +} + template class Array : event::EventEntity { public: - /** - * The underlaying container type. - */ + /// Underlying container type. using container_t = std::array, Size>; + /// Index type to access elements in the container. + using index_t = typename container_t::size_type; /** * Create a new array curve container. @@ -27,15 +36,15 @@ class Array : event::EventEntity { * @param id Unique identifier for this curve. * @param idstr Human-readable identifier for this curve. * @param notifier Function to call when this curve changes. - * @param default_val Default value for all elements in the array. + * @param default_vals Default values for the array elements. */ Array(const std::shared_ptr &loop, size_t id, const std::string &idstr = "", const EventEntity::single_change_notifier ¬ifier = nullptr, - const T &default_val = T()) : + const std::array &default_vals = {}) : EventEntity(loop, notifier), - containers{KeyframeContainer(default_val)}, + containers{init_default_vals(default_vals)}, _id{id}, _idstr{idstr}, loop{loop}, @@ -56,7 +65,7 @@ class Array : event::EventEntity { * * @return Value of the last element with time <= t. */ - T at(const time::time_t &t, const size_t index) const; + T at(const time::time_t &t, const index_t index) const; /** * Get all elements at time t. @@ -82,7 +91,7 @@ class Array : event::EventEntity { * * @return Time-value pair of the last keyframe with time <= t. */ - std::pair frame(const time::time_t &t, const size_t index) const; + std::pair frame(const time::time_t &t, const index_t index) const; /** * Get the first keyframe value and time with elem->time > time. @@ -94,7 +103,7 @@ class Array : event::EventEntity { * * @return Time-value pair of the first keyframe with time > t. */ - std::pair next_frame(const time::time_t &t, const size_t index) const; + std::pair next_frame(const time::time_t &t, const index_t index) const; /** * Insert a new keyframe value at time t. @@ -105,7 +114,7 @@ class Array : event::EventEntity { * @param index Index of the array element. * @param value Keyframe value. */ - void set_insert(const time::time_t &t, const size_t index, T value); + void set_insert(const time::time_t &t, const index_t index, T value); /** * Insert a new keyframe value at time t. Erase all other keyframes with elem->time > t. @@ -114,7 +123,7 @@ class Array : event::EventEntity { * @param index Index of the array element. * @param value Keyframe value. */ - void set_last(const time::time_t &t, const size_t index, T value); + void set_last(const time::time_t &t, const index_t index, T value); /** * Replace all keyframes at elem->time == t with a new keyframe value. @@ -123,7 +132,7 @@ class Array : event::EventEntity { * @param index Index of the array element. * @param value Keyframe value. */ - void set_replace(const time::time_t &t, const size_t index, T value); + void set_replace(const time::time_t &t, const index_t index, T value); /** * Copy keyframes from another container to this container. @@ -218,7 +227,7 @@ class Array : event::EventEntity { /** * get a copy to the KeyframeContainer at index */ - KeyframeContainer operator[](size_t index) const { + KeyframeContainer operator[](index_t index) const { return this->containers.at(index); } @@ -227,7 +236,7 @@ class Array : event::EventEntity { * * Each element is managed by a KeyframeContainer. */ - std::array, Size> containers; + container_t containers; /** * Identifier for the container @@ -257,7 +266,7 @@ class Array : event::EventEntity { template std::pair Array::frame(const time::time_t &t, - const size_t index) const { + const index_t index) const { size_t &hint = this->last_element[index]; size_t frame_index = this->containers.at(index).last(t, hint); hint = frame_index; @@ -266,7 +275,7 @@ std::pair Array::frame(const time::time_t &t, template std::pair Array::next_frame(const time::time_t &t, - const size_t index) const { + const index_t index) const { size_t &hint = this->last_element[index]; size_t frame_index = this->containers.at(index).last(t, hint); hint = frame_index; @@ -275,7 +284,7 @@ std::pair Array::next_frame(const time::time_t &t, template T Array::at(const time::time_t &t, - const size_t index) const { + const index_t index) const { size_t &hint = this->last_element[index]; size_t frame_index = this->containers.at(index).last(t, hint); hint = frame_index; @@ -296,7 +305,7 @@ consteval size_t Array::size() const { template void Array::set_insert(const time::time_t &t, - const size_t index, + const index_t index, T value) { this->last_element[index] = this->containers.at(index).insert_after(Keyframe{t, value}, this->last_element[index]); @@ -305,7 +314,7 @@ void Array::set_insert(const time::time_t &t, template void Array::set_last(const time::time_t &t, - const size_t index, + const index_t index, T value) { size_t frame_index = this->containers.at(index).insert_after(Keyframe{t, value}, this->last_element[index]); this->last_element[index] = frame_index; @@ -316,7 +325,7 @@ void Array::set_last(const time::time_t &t, template void Array::set_replace(const time::time_t &t, - const size_t index, + const index_t index, T value) { this->containers.at(index).insert_overwrite(Keyframe{t, value}, this->last_element[index]); From e8c124294386b836c36e81727996bb871fc50f59 Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 16 Jan 2025 04:31:39 +0100 Subject: [PATCH 071/152] curve: Fix gcc compiler warnings. --- libopenage/curve/array.h | 12 ++++++------ libopenage/curve/tests/container.cpp | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libopenage/curve/array.h b/libopenage/curve/array.h index 857638f5e0..ea1cb71724 100644 --- a/libopenage/curve/array.h +++ b/libopenage/curve/array.h @@ -197,11 +197,6 @@ class Array : event::EventEntity { } private: - /** - * used to index the Curve::Array pointed to by this iterator - */ - size_t offset; - /** * the curve object that this iterator, iterates over */ @@ -211,6 +206,11 @@ class Array : event::EventEntity { * time at which this iterator is iterating over */ time::time_t time; + + /** + * used to index the Curve::Array pointed to by this iterator + */ + size_t offset; }; /** @@ -335,7 +335,7 @@ void Array::set_replace(const time::time_t &t, template void Array::sync(const Array &other, const time::time_t &start) { - for (int i = 0; i < Size; i++) { + for (index_t i = 0; i < Size; i++) { this->containers[i].sync(other.containers[i], start); } diff --git a/libopenage/curve/tests/container.cpp b/libopenage/curve/tests/container.cpp index 8aed3c8d05..48a3b19202 100644 --- a/libopenage/curve/tests/container.cpp +++ b/libopenage/curve/tests/container.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2024 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #include #include @@ -247,7 +247,7 @@ void test_array() { auto f = std::make_shared(); Array a(f, 0); - const std::array &default_val = std::array(); + // const std::array &default_val = std::array(); a.set_insert(1, 0, 0); a.set_insert(1, 1, 1); a.set_insert(1, 2, 2); From 599f7bd8645b3ec1db64b7f06e467c7c3543b467 Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 16 Jan 2025 04:52:33 +0100 Subject: [PATCH 072/152] curve: Fix elem_ptr caching for array. --- libopenage/curve/array.h | 77 ++++++++++++++++++++++++++--------- libopenage/curve/base_curve.h | 6 +-- libopenage/curve/discrete.h | 6 +-- libopenage/curve/keyframe.h | 12 +++--- 4 files changed, 70 insertions(+), 31 deletions(-) diff --git a/libopenage/curve/array.h b/libopenage/curve/array.h index ea1cb71724..9f9bacd09c 100644 --- a/libopenage/curve/array.h +++ b/libopenage/curve/array.h @@ -48,7 +48,7 @@ class Array : event::EventEntity { _id{id}, _idstr{idstr}, loop{loop}, - last_element{} {} + last_elements{} {} // prevent copying because it invalidates the usage of unique ids and event // registration. If you need to copy a curve, use the sync() method. @@ -260,35 +260,50 @@ class Array : event::EventEntity { * * mutable as hints are updated by const read-only functions. */ - mutable std::array::elem_ptr, Size> last_element = {}; + mutable std::array::elem_ptr, Size> last_elements = {}; }; template std::pair Array::frame(const time::time_t &t, const index_t index) const { - size_t &hint = this->last_element[index]; - size_t frame_index = this->containers.at(index).last(t, hint); - hint = frame_index; - return this->containers.at(index).get(frame_index).make_pair(); + // find elem_ptr in container to get the last keyframe + auto hint = this->last_elements[index]; + auto frame_index = this->containers.at(index).last(t, hint); + + // update the hint + this->last_elements[index] = frame_index; + + return this->containers.at(index).get(frame_index).as_pair(); } template std::pair Array::next_frame(const time::time_t &t, const index_t index) const { - size_t &hint = this->last_element[index]; - size_t frame_index = this->containers.at(index).last(t, hint); - hint = frame_index; - return this->containers.at(index).get(frame_index + 1).make_pair(); + // find elem_ptr in container to get the last keyframe with time <= t + auto hint = this->last_elements[index]; + auto frame_index = this->containers.at(index).last(t, hint); + + // increment the index to get the next keyframe + frame_index++; + + // update the hint + this->last_elements[index] = frame_index; + + return this->containers.at(index).get(frame_index).as_pair(); } template T Array::at(const time::time_t &t, const index_t index) const { - size_t &hint = this->last_element[index]; - size_t frame_index = this->containers.at(index).last(t, hint); - hint = frame_index; - return this->containers.at(index).get(frame_index).val(); + // find elem_ptr in container to get the last keyframe with time <= t + auto hint = this->last_elements[index]; + auto e = this->containers.at(index).last(t, hint); + + // update the hint + this->last_elements[index] = e; + + return this->containers.at(index).get(e).val(); } template @@ -307,7 +322,12 @@ template void Array::set_insert(const time::time_t &t, const index_t index, T value) { - this->last_element[index] = this->containers.at(index).insert_after(Keyframe{t, value}, this->last_element[index]); + // find elem_ptr in container to get the last keyframe with time <= t + auto hint = this->last_elements[index]; + auto e = this->containers.at(index).insert_after(Keyframe{t, value}, hint); + + // update the hint + this->last_elements[index] = e; this->changes(t); } @@ -316,9 +336,23 @@ template void Array::set_last(const time::time_t &t, const index_t index, T value) { - size_t frame_index = this->containers.at(index).insert_after(Keyframe{t, value}, this->last_element[index]); - this->last_element[index] = frame_index; - this->containers.at(index).erase_after(frame_index); + // find elem_ptr in container to get the last keyframe with time <= t + auto hint = this->last_elements[index]; + auto e = this->containers.at(index).last(t, hint); + + // erase max one same-time value + if (this->containers.at(index).get(e).time() == t) { + e--; + } + + // erase all keyframes with time > t + this->containers.at(index).erase_after(e); + + // insert the new keyframe at the end + this->containers.at(index).insert_before(Keyframe{t, value}, e); + + // update the hint + this->last_elements[index] = hint; this->changes(t); } @@ -327,7 +361,12 @@ template void Array::set_replace(const time::time_t &t, const index_t index, T value) { - this->containers.at(index).insert_overwrite(Keyframe{t, value}, this->last_element[index]); + // find elem_ptr in container to get the last keyframe with time <= t + auto hint = this->last_elements[index]; + auto e = this->containers.at(index).insert_overwrite(Keyframe{t, value}, hint); + + // update the hint + this->last_elements[index] = e; this->changes(t); } diff --git a/libopenage/curve/base_curve.h b/libopenage/curve/base_curve.h index e5e265b5a0..de5c14201d 100644 --- a/libopenage/curve/base_curve.h +++ b/libopenage/curve/base_curve.h @@ -1,4 +1,4 @@ -// Copyright 2017-2024 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #pragma once @@ -246,7 +246,7 @@ template std::pair BaseCurve::frame(const time::time_t &time) const { auto e = this->container.last(time, this->container.size()); auto elem = this->container.get(e); - return elem.make_pair(); + return elem.as_pair(); } @@ -255,7 +255,7 @@ std::pair BaseCurve::next_frame(const time::time_t &ti auto e = this->container.last(time, this->container.size()); e++; auto elem = this->container.get(e); - return elem.make_pair(); + return elem.as_pair(); } template diff --git a/libopenage/curve/discrete.h b/libopenage/curve/discrete.h index 44fe132de6..b9f9b6b00c 100644 --- a/libopenage/curve/discrete.h +++ b/libopenage/curve/discrete.h @@ -1,4 +1,4 @@ -// Copyright 2017-2024 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #pragma once @@ -80,7 +80,7 @@ std::pair Discrete::get_time(const time::time_t &time) const this->last_element = e; auto elem = this->container.get(e); - return std::make_pair(elem.time, elem.value); + return elem.as_pair(); } @@ -97,7 +97,7 @@ std::optional> Discrete::get_previous(const time:: e--; auto elem = this->container.get(e); - return std::make_pair(elem.time(), elem.val()); + return elem.as_pair(); } } // namespace openage::curve diff --git a/libopenage/curve/keyframe.h b/libopenage/curve/keyframe.h index a34b0c6262..cd2ebf6ced 100644 --- a/libopenage/curve/keyframe.h +++ b/libopenage/curve/keyframe.h @@ -1,4 +1,4 @@ -// Copyright 2019-2024 the openage authors. See copying.md for legal info. +// Copyright 2019-2025 the openage authors. See copying.md for legal info. #pragma once @@ -55,12 +55,12 @@ class Keyframe { } /** - * Get the value and timestamp of the keyframe in form of std::pair - * @return keyframe pair + * Get a time-value pair of this keyframe. + * + * @return Keyframe time-value pair. */ - std::pair make_pair() const - { - return {time(), val()}; + std::pair as_pair() const { + return {this->timestamp, this->value}; } public: From 156d009daa69df970e8aeaa36d5560a7b411a6aa Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 19 Jan 2025 15:49:28 +0100 Subject: [PATCH 073/152] curve: Add more comments to tests and check additional methods. --- libopenage/curve/tests/container.cpp | 56 ++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/libopenage/curve/tests/container.cpp b/libopenage/curve/tests/container.cpp index 48a3b19202..92b885320d 100644 --- a/libopenage/curve/tests/container.cpp +++ b/libopenage/curve/tests/container.cpp @@ -244,35 +244,58 @@ void test_queue() { } void test_array() { - auto f = std::make_shared(); + auto loop = std::make_shared(); - Array a(f, 0); - // const std::array &default_val = std::array(); + Array a(loop, 0); a.set_insert(1, 0, 0); a.set_insert(1, 1, 1); a.set_insert(1, 2, 2); a.set_insert(1, 3, 3); // a = [[0:0, 1:0],[0:0, 1:1],[0:0, 1:2],[0:0, 1:3]] + // test size + TESTEQUALS(a.size(), 4); + + // extracting array at time t == 1 auto res = a.get(1); + auto expected = std::array{0, 1, 2, 3}; + TESTEQUALS(res.at(0), expected.at(0)); + TESTEQUALS(res.at(1), expected.at(1)); + TESTEQUALS(res.at(2), expected.at(2)); + TESTEQUALS(res.at(3), expected.at(3)); + TESTEQUALS(res.size(), expected.size()); + + // extracting array at time t == 0 + // array should have default values (== 0) for all keyframes + res = a.get(0); TESTEQUALS(res.at(0), 0); - TESTEQUALS(res.at(1), 1); - TESTEQUALS(res.at(2), 2); - TESTEQUALS(res.at(3), 3); + TESTEQUALS(res.at(1), 0); + TESTEQUALS(res.at(2), 0); + TESTEQUALS(res.at(3), 0); - Array other(f, 0); + Array other(loop, 0); other.set_last(0, 0, 999); other.set_last(0, 1, 999); other.set_last(0, 2, 999); other.set_last(0, 3, 999); + // inserting keyframes at time t == 1 other.set_insert(1, 0, 4); other.set_insert(1, 1, 5); other.set_insert(1, 2, 6); other.set_insert(1, 3, 7); // other = [[0:999, 1:4],[0:999, 1:5],[0:999, 1:6],[0:999, 1:7]] + TESTEQUALS(other.at(0, 0), 999); + TESTEQUALS(other.at(0, 1), 999); + TESTEQUALS(other.at(0, 2), 999); + TESTEQUALS(other.at(0, 3), 999); + TESTEQUALS(other.at(1, 0), 4); + TESTEQUALS(other.at(1, 1), 5); + TESTEQUALS(other.at(1, 2), 6); + TESTEQUALS(other.at(1, 3), 7); + // sync keyframes from other to a a.sync(other, 1); // a = [[0:0, 1:4],[0:0, 1:5],[0:0, 1:6],[0:0, 1:7]] @@ -287,27 +310,30 @@ void test_array() { TESTEQUALS(res.at(2), 6); TESTEQUALS(res.at(3), 7); - // Additional tests + // replace keyframes at time t == 2 a.set_insert(2, 0, 15); a.set_insert(2, 0, 20); a.set_replace(2, 0, 25); TESTEQUALS(a.at(2, 0), 25); // a = [[0:0, 1:4, 2:25],[0:0, 1:5],[0:0, 1:6],[0:0, 1:7]] - a.set_insert(3, 0, 30); - a.set_insert(4, 0, 40); - a.set_last(3, 0, 35); + // set last keyframe at time t == 3 + a.set_insert(3, 0, 30); // a = [[0:0, 1:4, 2:25, 3:30], ... + a.set_insert(4, 0, 40); // a = [[0:0, 1:4, 2:25, 3:30, 4:40], ... + a.set_last(3, 0, 35); // a = [[0:0, 1:4, 2:25, 3:35],... TESTEQUALS(a.at(4, 0), 35); // a = [[0:0, 1:4, 2:25, 3:35],[0:0, 1:5],[0:0, 1:6],[0:0, 1:7]] + // test frame and next_frame auto frame = a.frame(1, 2); - TESTEQUALS(frame.second, 6); - TESTEQUALS(frame.first, 1); + TESTEQUALS(frame.first, 1); // time + TESTEQUALS(frame.second, 6); // value a.set_insert(5, 3, 40); + // a = [[0:0, 1:4, 2:25, 3:35],[0:0, 1:5],[0:0, 1:6],[0:0, 1:7, 5:40]] auto next_frame = a.next_frame(1, 3); - TESTEQUALS(next_frame.second, 40); - TESTEQUALS(next_frame.first, 5); + TESTEQUALS(next_frame.first, 5); // time + TESTEQUALS(next_frame.second, 40); // value // Test begin and end auto it = a.begin(1); From ad747a6bc0fb71689cc540a4e0fef978e2a053cb Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 19 Jan 2025 16:03:41 +0100 Subject: [PATCH 074/152] doc: Fix formatting of curve docs and reformulate array docs a bit. --- doc/code/curves.md | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/doc/code/curves.md b/doc/code/curves.md index 372a4b0153..e914f8bcfe 100644 --- a/doc/code/curves.md +++ b/doc/code/curves.md @@ -17,6 +17,7 @@ Curves are an integral part of openage's event-based game simulation. 2. [Container](#container) 1. [Queue](#queue) 2. [Unordered Map](#unordered-map) + 3. [Array](#array) ## Motivation @@ -256,33 +257,38 @@ allows access to all key-value pairs that were in the map at time `t`. #### Array -Array curve containers are the equivalent to the `std::array` C++ containers. Unlike other curve containers, -each element of the underlying std::array is a `keyframecontainer` and when a value is added to the `Array curve` -at a given index they are added to the respective `keyframecontainer` at that index as a keyframe. + +Array curve containers store a fixed number of `n` elements where `n` is determined at compile-time. +They are the curve equivalent to the `std::array` C++ containers. In comparison to `std::array` each +element in the array curve container is tracked individually over time. Hence, each index is associated +with its own `KeyframeContainer` whose keyframes can be updated independent from other indices. +When a value is added to the `Array` curve at a given index, a new keyframe is added to the respective +`KeyframeContainer` stored at that index. **Read** Read operations retrieve values for a specific point in time. -| Method | Description | -| ----------------- | --------------------------------------- ---------------------------------| -| `get(t, i)` | Get value of index `i` at time <= `t` | -| `get(t)` | Get array of keyframes at time <= `t` | -| `size()` | Get size of the array | -| `frame(t, i)` | Get the time and value of the keyframe at index `i` with time <= `t` | -| `next_frame(t, i)`| Get the time and value of the first keyframe at index `i` with time > `t`| +| Method | Description | +| ------------------ | ------------------------------------------------------------------------ | +| `get(t, i)` | Get value of element at index `i` at time <= `t` | +| `get(t)` | Get array of values at time <= `t` | +| `size()` | Get the number of elements in the array | +| `frame(t, i)` | Get the previous keyframe (time and value) at index `i` before or at `t` | +| `next_frame(t, i)` | Get the next keyframe (time and value) at index `i` after `t` | **Modify** Modify operations insert values for a specific point in time. -| Method | Description | -| ------------------------- | ------------------------------------------------------------------------------------------| -| `set_insert(t, i, value)` | Insert a new keyframe(`t`, `value`) at index `i` | -| `set_last(t, i, value)` | Insert a new keyframe(`t`, `value`) at index `i`; delete all keyframes after time `t` | -| `set_replace(t, i, value)`| Insert a new keyframe(`t`, `value`) at index `i`; remove all other keyframes with time `t`| +| Method | Description | +| -------------------------- | ------------------------------------------------------------------------------------------ | +| `set_insert(t, i, value)` | Insert a new keyframe(`t`, `value`) at index `i` | +| `set_last(t, i, value)` | Insert a new keyframe(`t`, `value`) at index `i`; delete all keyframes after time `t` | +| `set_replace(t, i, value)` | Insert a new keyframe(`t`, `value`) at index `i`; remove all other keyframes with time `t` | **Copy** + | Method | Description | -| ---------------- | ------------------------------------------------------------------------------------------------ | +| ---------------- | ------------------------------------------------------------------------------------------------ | | `sync(Curve, t)` | Replace all keyframes from self after time `t` with keyframes from source `Curve` after time `t` | From d571822ef9f341be22b7b3c93e5c599a7dbbb8ae Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 19 Jan 2025 16:10:58 +0100 Subject: [PATCH 075/152] curve: Refactor and move curve containers to subfolder. --- libopenage/curve/CMakeLists.txt | 8 +------- libopenage/curve/container/CMakeLists.txt | 9 +++++++++ libopenage/curve/{ => container}/array.cpp | 2 +- libopenage/curve/{ => container}/array.h | 2 +- libopenage/curve/{ => container}/element_wrapper.cpp | 2 +- libopenage/curve/{ => container}/element_wrapper.h | 2 +- libopenage/curve/{ => container}/iterator.cpp | 2 +- libopenage/curve/{ => container}/iterator.h | 2 +- libopenage/curve/{ => container}/map.cpp | 2 +- libopenage/curve/{ => container}/map.h | 7 +++---- .../curve/{ => container}/map_filter_iterator.cpp | 2 +- .../curve/{ => container}/map_filter_iterator.h | 4 ++-- libopenage/curve/{ => container}/queue.cpp | 2 +- libopenage/curve/{ => container}/queue.h | 8 ++++---- .../curve/{ => container}/queue_filter_iterator.cpp | 2 +- .../curve/{ => container}/queue_filter_iterator.h | 4 ++-- libopenage/curve/tests/container.cpp | 12 ++++++------ libopenage/gamestate/component/api/live.cpp | 6 +++--- libopenage/gamestate/component/api/live.h | 4 ++-- .../gamestate/component/internal/command_queue.h | 4 ++-- libopenage/gamestate/entity_factory.cpp | 4 ++-- 21 files changed, 46 insertions(+), 44 deletions(-) create mode 100644 libopenage/curve/container/CMakeLists.txt rename libopenage/curve/{ => container}/array.cpp (65%) rename libopenage/curve/{ => container}/array.h (99%) rename libopenage/curve/{ => container}/element_wrapper.cpp (68%) rename libopenage/curve/{ => container}/element_wrapper.h (96%) rename libopenage/curve/{ => container}/iterator.cpp (67%) rename libopenage/curve/{ => container}/iterator.h (97%) rename libopenage/curve/{ => container}/map.cpp (66%) rename libopenage/curve/{ => container}/map.h (97%) rename libopenage/curve/{ => container}/map_filter_iterator.cpp (68%) rename libopenage/curve/{ => container}/map_filter_iterator.h (93%) rename libopenage/curve/{ => container}/queue.cpp (58%) rename libopenage/curve/{ => container}/queue.h (98%) rename libopenage/curve/{ => container}/queue_filter_iterator.cpp (69%) rename libopenage/curve/{ => container}/queue_filter_iterator.h (93%) diff --git a/libopenage/curve/CMakeLists.txt b/libopenage/curve/CMakeLists.txt index 174498e23b..eb52858f43 100644 --- a/libopenage/curve/CMakeLists.txt +++ b/libopenage/curve/CMakeLists.txt @@ -1,19 +1,13 @@ add_sources(libopenage - array.cpp base_curve.cpp continuous.cpp discrete.cpp discrete_mod.cpp - element_wrapper.cpp interpolated.cpp - iterator.cpp keyframe.cpp keyframe_container.cpp - map.cpp - map_filter_iterator.cpp - queue.cpp - queue_filter_iterator.cpp segmented.cpp ) +add_subdirectory("container") add_subdirectory("tests") diff --git a/libopenage/curve/container/CMakeLists.txt b/libopenage/curve/container/CMakeLists.txt new file mode 100644 index 0000000000..a187e01429 --- /dev/null +++ b/libopenage/curve/container/CMakeLists.txt @@ -0,0 +1,9 @@ +add_sources(libopenage + array.cpp + element_wrapper.cpp + iterator.cpp + map.cpp + map_filter_iterator.cpp + queue.cpp + queue_filter_iterator.cpp +) diff --git a/libopenage/curve/array.cpp b/libopenage/curve/container/array.cpp similarity index 65% rename from libopenage/curve/array.cpp rename to libopenage/curve/container/array.cpp index 97e07033ce..609ee117a3 100644 --- a/libopenage/curve/array.cpp +++ b/libopenage/curve/container/array.cpp @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #include "array.h" diff --git a/libopenage/curve/array.h b/libopenage/curve/container/array.h similarity index 99% rename from libopenage/curve/array.h rename to libopenage/curve/container/array.h index 9f9bacd09c..e980d77915 100644 --- a/libopenage/curve/array.h +++ b/libopenage/curve/container/array.h @@ -4,7 +4,7 @@ #include -#include "curve/iterator.h" +#include "curve/container/iterator.h" #include "curve/keyframe_container.h" #include "event/evententity.h" diff --git a/libopenage/curve/element_wrapper.cpp b/libopenage/curve/container/element_wrapper.cpp similarity index 68% rename from libopenage/curve/element_wrapper.cpp rename to libopenage/curve/container/element_wrapper.cpp index 5d2eaa08af..bdbca7346f 100644 --- a/libopenage/curve/element_wrapper.cpp +++ b/libopenage/curve/container/element_wrapper.cpp @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #include "element_wrapper.h" diff --git a/libopenage/curve/element_wrapper.h b/libopenage/curve/container/element_wrapper.h similarity index 96% rename from libopenage/curve/element_wrapper.h rename to libopenage/curve/container/element_wrapper.h index 764b61d5cd..0032e4c6fe 100644 --- a/libopenage/curve/element_wrapper.h +++ b/libopenage/curve/container/element_wrapper.h @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/curve/iterator.cpp b/libopenage/curve/container/iterator.cpp similarity index 67% rename from libopenage/curve/iterator.cpp rename to libopenage/curve/container/iterator.cpp index b449d93812..594df3a5b8 100644 --- a/libopenage/curve/iterator.cpp +++ b/libopenage/curve/container/iterator.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2018 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #include "iterator.h" diff --git a/libopenage/curve/iterator.h b/libopenage/curve/container/iterator.h similarity index 97% rename from libopenage/curve/iterator.h rename to libopenage/curve/container/iterator.h index d0346e1b5d..7a4fb82d6b 100644 --- a/libopenage/curve/iterator.h +++ b/libopenage/curve/container/iterator.h @@ -1,4 +1,4 @@ -// Copyright 2017-2024 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/curve/map.cpp b/libopenage/curve/container/map.cpp similarity index 66% rename from libopenage/curve/map.cpp rename to libopenage/curve/container/map.cpp index 82b26f9ecc..a2469bfa5f 100644 --- a/libopenage/curve/map.cpp +++ b/libopenage/curve/container/map.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2018 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #include "map.h" diff --git a/libopenage/curve/map.h b/libopenage/curve/container/map.h similarity index 97% rename from libopenage/curve/map.h rename to libopenage/curve/container/map.h index 5a5e0a4d9b..4997824a6d 100644 --- a/libopenage/curve/map.h +++ b/libopenage/curve/container/map.h @@ -1,4 +1,4 @@ -// Copyright 2017-2024 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #pragma once @@ -7,8 +7,8 @@ #include #include -#include "curve/map_filter_iterator.h" -#include "curve/element_wrapper.h" +#include "curve/container/element_wrapper.h" +#include "curve/container/map_filter_iterator.h" #include "time/time.h" #include "util/fixed_point.h" @@ -21,7 +21,6 @@ namespace openage::curve { */ template class UnorderedMap { - /** * Data holder. Maps keys to map elements. * Map elements themselves store when they are valid. diff --git a/libopenage/curve/map_filter_iterator.cpp b/libopenage/curve/container/map_filter_iterator.cpp similarity index 68% rename from libopenage/curve/map_filter_iterator.cpp rename to libopenage/curve/container/map_filter_iterator.cpp index da10b01774..bba3ffa478 100644 --- a/libopenage/curve/map_filter_iterator.cpp +++ b/libopenage/curve/container/map_filter_iterator.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2018 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #include "map_filter_iterator.h" diff --git a/libopenage/curve/map_filter_iterator.h b/libopenage/curve/container/map_filter_iterator.h similarity index 93% rename from libopenage/curve/map_filter_iterator.h rename to libopenage/curve/container/map_filter_iterator.h index 3aec2a899e..c9afceee88 100644 --- a/libopenage/curve/map_filter_iterator.h +++ b/libopenage/curve/container/map_filter_iterator.h @@ -1,8 +1,8 @@ -// Copyright 2017-2024 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #pragma once -#include "curve/iterator.h" +#include "curve/container/iterator.h" #include "time/time.h" diff --git a/libopenage/curve/queue.cpp b/libopenage/curve/container/queue.cpp similarity index 58% rename from libopenage/curve/queue.cpp rename to libopenage/curve/container/queue.cpp index d994b6c82e..842a140045 100644 --- a/libopenage/curve/queue.cpp +++ b/libopenage/curve/container/queue.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2018 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #include "queue.h" diff --git a/libopenage/curve/queue.h b/libopenage/curve/container/queue.h similarity index 98% rename from libopenage/curve/queue.h rename to libopenage/curve/container/queue.h index 9314dd3a0e..fb32a53cbb 100644 --- a/libopenage/curve/queue.h +++ b/libopenage/curve/container/queue.h @@ -1,4 +1,4 @@ -// Copyright 2017-2024 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #pragma once @@ -11,9 +11,9 @@ #include "error/error.h" -#include "curve/iterator.h" -#include "curve/queue_filter_iterator.h" -#include "curve/element_wrapper.h" +#include "curve/container/element_wrapper.h" +#include "curve/container/iterator.h" +#include "curve/container/queue_filter_iterator.h" #include "event/evententity.h" #include "time/time.h" #include "util/fixed_point.h" diff --git a/libopenage/curve/queue_filter_iterator.cpp b/libopenage/curve/container/queue_filter_iterator.cpp similarity index 69% rename from libopenage/curve/queue_filter_iterator.cpp rename to libopenage/curve/container/queue_filter_iterator.cpp index fa0b3ad15a..b4ceb2b7e6 100644 --- a/libopenage/curve/queue_filter_iterator.cpp +++ b/libopenage/curve/container/queue_filter_iterator.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2018 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #include "queue_filter_iterator.h" diff --git a/libopenage/curve/queue_filter_iterator.h b/libopenage/curve/container/queue_filter_iterator.h similarity index 93% rename from libopenage/curve/queue_filter_iterator.h rename to libopenage/curve/container/queue_filter_iterator.h index cf6bc5aa2c..6b2fa471f2 100644 --- a/libopenage/curve/queue_filter_iterator.h +++ b/libopenage/curve/container/queue_filter_iterator.h @@ -1,8 +1,8 @@ -// Copyright 2017-2024 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #pragma once -#include "curve/iterator.h" +#include "curve/container/iterator.h" #include "time/time.h" diff --git a/libopenage/curve/tests/container.cpp b/libopenage/curve/tests/container.cpp index 92b885320d..16da2476e9 100644 --- a/libopenage/curve/tests/container.cpp +++ b/libopenage/curve/tests/container.cpp @@ -9,12 +9,12 @@ #include #include -#include "curve/array.h" -#include "curve/iterator.h" -#include "curve/map.h" -#include "curve/map_filter_iterator.h" -#include "curve/queue.h" -#include "curve/queue_filter_iterator.h" +#include "curve/container/array.h" +#include "curve/container/iterator.h" +#include "curve/container/map.h" +#include "curve/container/map_filter_iterator.h" +#include "curve/container/queue.h" +#include "curve/container/queue_filter_iterator.h" #include "event/event_loop.h" #include "testing/testing.h" diff --git a/libopenage/gamestate/component/api/live.cpp b/libopenage/gamestate/component/api/live.cpp index 01fafde5ea..f7a1f17d98 100644 --- a/libopenage/gamestate/component/api/live.cpp +++ b/libopenage/gamestate/component/api/live.cpp @@ -1,12 +1,12 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2025 the openage authors. See copying.md for legal info. #include "live.h" #include +#include "curve/container/iterator.h" +#include "curve/container/map_filter_iterator.h" #include "curve/discrete.h" -#include "curve/iterator.h" -#include "curve/map_filter_iterator.h" #include "gamestate/component/types.h" diff --git a/libopenage/gamestate/component/api/live.h b/libopenage/gamestate/component/api/live.h index 2e1f5e41d5..4916713cdc 100644 --- a/libopenage/gamestate/component/api/live.h +++ b/libopenage/gamestate/component/api/live.h @@ -1,4 +1,4 @@ -// Copyright 2021-2024 the openage authors. See copying.md for legal info. +// Copyright 2021-2025 the openage authors. See copying.md for legal info. #pragma once @@ -7,7 +7,7 @@ #include -#include "curve/map.h" +#include "curve/container/map.h" #include "gamestate/component/api_component.h" #include "gamestate/component/types.h" #include "time/time.h" diff --git a/libopenage/gamestate/component/internal/command_queue.h b/libopenage/gamestate/component/internal/command_queue.h index a7905c4d24..fb3179b470 100644 --- a/libopenage/gamestate/component/internal/command_queue.h +++ b/libopenage/gamestate/component/internal/command_queue.h @@ -1,10 +1,10 @@ -// Copyright 2021-2024 the openage authors. See copying.md for legal info. +// Copyright 2021-2025 the openage authors. See copying.md for legal info. #pragma once #include -#include "curve/queue.h" +#include "curve/container/queue.h" #include "gamestate/component/internal/commands/base_command.h" #include "gamestate/component/internal_component.h" #include "gamestate/component/types.h" diff --git a/libopenage/gamestate/entity_factory.cpp b/libopenage/gamestate/entity_factory.cpp index f2f489206c..6e1a4c825f 100644 --- a/libopenage/gamestate/entity_factory.cpp +++ b/libopenage/gamestate/entity_factory.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #include "entity_factory.h" @@ -10,8 +10,8 @@ #include "error/error.h" +#include "curve/container/queue.h" #include "curve/discrete.h" -#include "curve/queue.h" #include "event/event_loop.h" #include "gamestate/activity/activity.h" #include "gamestate/activity/condition/command_in_queue.h" From 5f8690944a46c2435182c7a843b970966214962b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ng=C3=B4=20Xu=C3=A2n=20Minh?= Date: Thu, 28 Oct 2021 00:18:49 +0700 Subject: [PATCH 076/152] Add generic function fox SMX file --- .../convert/value_object/read/media/smx.pyx | 319 ++++++++++++++++++ 1 file changed, 319 insertions(+) diff --git a/openage/convert/value_object/read/media/smx.pyx b/openage/convert/value_object/read/media/smx.pyx index d1f61a2735..10ad0c3364 100644 --- a/openage/convert/value_object/read/media/smx.pyx +++ b/openage/convert/value_object/read/media/smx.pyx @@ -59,6 +59,325 @@ cdef public dict LAYER_TYPES = { 1: SMXLayerType.SHADOW, 2: SMXLayerType.OUTLINE, } +cdef class SMXMainLayer8to5Variant: + pass + +cdef class SMXMainLayer4plus1Variant: + pass + +cdef class SMXOutlineLayerVariant: + pass + +cdef class SMXShadowLayerVariant: + pass + + +ctypedef fused SMXLayerVariant: + SMXMainLayer8to5Variant + SMXMainLayer4plus1Variant + SMXOutlineLayerVariant + SMXShadowLayerVariant + + +cdef process_drawing_cmds(SMXLayerVariant variant, + const uint8_t[:] &data_raw, + vector[pixel] &row_data, + Py_ssize_t rowid, + Py_ssize_t first_cmd_offset, + Py_ssize_t first_color_offset, + int chunk_pos, + size_t expected_size): + """ + TODO: docstring + """ + + # position in the command array, we start at the first command of this row + cdef Py_ssize_t dpos_cmd = first_cmd_offset + + # is the end of the current row reached? + cdef bool eor = False + + cdef uint8_t cmd = 0 + cdef uint8_t lower_crumb = 0 + cdef int pixel_count = 0 + + if SMXLayerVariant is SMXMainLayer8to5Variant + # Position in the pixel data array + cdef Py_ssize_t dpos_color = first_color_offset + + # Position in the compression chunk. + cdef bool odd = chunk_pos + cdef int px_dpos = 0 # For loop iterator + + cdef vector[uint8_t] pixel_data + pixel_data.reserve(4) + + # Pixel data temporary values that need further decompression + cdef uint8_t pixel_data_odd_0 = 0 + cdef uint8_t pixel_data_odd_1 = 0 + cdef uint8_t pixel_data_odd_2 = 0 + cdef uint8_t pixel_data_odd_3 = 0 + + # Mask for even indices + # cdef uint8_t pixel_mask_even_0 = 0xFF + cdef uint8_t pixel_mask_even_1 = 0b00000011 + cdef uint8_t pixel_mask_even_2 = 0b11110000 + cdef uint8_t pixel_mask_even_3 = 0b00111111 + + if SMXLayerVariant is SMXMainLayer4plus1Variant: + # Position in the pixel data array + cdef Py_ssize_t dpos_color = first_color_offset + + # Position in the compression chunk + cdef uint8_t dpos_chunk = chunk_pos + + cdef uint8_t palette_section_block = 0 + cdef uint8_t palette_section = 0 + + if SMXLayerVariant in (SMXShadowLayerVariant, SMXOutlineLayerVariant): + cdef uint8_t nextbyte = 0 + + # work through commands till end of row. + while not eor: + if row_data.size() > expected_size: + raise Exception( + f"Only {expected_size:d} pixels should be drawn in row {rowid:d} " + f"with layer type {self.info.layer_type:#x}, but we have {row_data.size():d} " + f"already!" + ) + + # fetch drawing instruction + cmd = data_raw[dpos_cmd] + + # Last 2 bits store command type + lower_crumb = 0b00000011 & cmd + + if lower_crumb == 0b00000011: + # eor (end of row) command, this row is finished now. + eor = True + dpos_cmd += 1 + + if is SMXShadowLayerVariant: + # shadows sometimes need an extra pixel at + # the end + if row_data.size() < expected_size: + # copy the last drawn pixel + # (still stored in nextbyte) + # + # TODO: confirm that this is the + # right way to do it + row_data.push_back(pixel(color_shadow, + nextbyte, 0, 0, 0)) + continue + + elif lower_crumb == 0b00000000: + # skip command + # draw 'count' transparent pixels + # count = (cmd >> 2) + 1 + + pixel_count = (cmd >> 2) + 1 + + for _ in range(pixel_count): + row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) + + elif lower_crumb == 0b00000001: + # color_list command + # draw the following 'count' pixels + # pixels are stored in 5 byte chunks + # even pixel indices have their info stored + # in byte[0] - byte[3]. odd pixel indices have + # their info stored in byte[1] - byte[4]. + # count = (cmd >> 2) + 1 + + pixel_count = (cmd >> 2) + 1 + + if is SMXMainLayer8to5Variant: + for _ in range(pixel_count): + # Start fetching pixel data + if odd: + # Odd indices require manual extraction of each of the 4 values + + # Palette index. Essentially a rotation of (byte[1]byte[2]) + # by 6 to the left, then masking with 0x00FF. + pixel_data_odd_0 = data_raw[dpos_color + 1] + pixel_data_odd_1 = data_raw[dpos_color + 2] + pixel_data.push_back((pixel_data_odd_0 >> 2) | (pixel_data_odd_1 << 6)) + + # Palette section. Described in byte[2] in bits 4-5. + pixel_data.push_back((pixel_data_odd_1 >> 2) & 0x03) + + # Damage mask 1. Essentially a rotation of (byte[3]byte[4]) + # by 6 to the left, then masking with 0x00F0. + pixel_data_odd_2 = data_raw[dpos_color + 3] + pixel_data_odd_3 = data_raw[dpos_color + 4] + pixel_data.push_back(((pixel_data_odd_2 >> 2) | (pixel_data_odd_3 << 6)) & 0xF0) + + # Damage mask 2. Described in byte[4] in bits 0-5. + pixel_data.push_back((pixel_data_odd_3 >> 2) & 0x3F) + + row_data.push_back(pixel(color_standard, + pixel_data[0], + pixel_data[1], + pixel_data[2], + pixel_data[3])) + + # Go to next pixel + dpos_color += 5 + + else: + # Even indices can be read "as is". They just have to be masked. + for px_dpos in range(4): + pixel_data.push_back(data_raw[dpos_color + px_dpos]) + + row_data.push_back(pixel(color_standard, + pixel_data[0], + pixel_data[1] & pixel_mask_even_1, + pixel_data[2] & pixel_mask_even_2, + pixel_data[3] & pixel_mask_even_3)) + + odd = not odd + pixel_data.clear() + + if is SMXMainLayer4plus1Variant: + palette_section_block = data_raw[dpos_color + (4 - dpos_chunk)] + + for _ in range(pixel_count): + # Start fetching pixel data + palette_section = (palette_section_block >> (2 * dpos_chunk)) & 0x03 + row_data.push_back(pixel(color_standard, + data_raw[dpos_color], + palette_section, + 0, + 0)) + + dpos_color += 1 + dpos_chunk += 1 + + # Skip to next chunk + if dpos_chunk > 3: + dpos_chunk = 0 + dpos_color += 1 # Skip palette section block + palette_section_block = data_raw[dpos_color + 4] + + if SMXLayerVariant is SMXShadowLayerVariant: + for _ in range(pixel_count): + dpos_color += 1 + nextbyte = data_raw[dpos_color] + + row_data.push_back(pixel(color_shadow, + nextbyte, 0, 0, 0)) + + if SMXLayerVariant is SMXOutlineLayerVariant: + # we don't know the color the game wants + # so we just draw index 0 + row_data.push_back(pixel(color_outline, + 0, 0, 0, 0)) + + + elif lower_crumb == 0b00000010: + if SMXLayerVariant is SMXMainLayer8to5Variant: + # player_color command + # draw the following 'count' pixels + # pixels are stored in 5 byte chunks + # even pixel indices have their info stored + # in byte[0] - byte[3]. odd pixel indices have + # their info stored in byte[1] - byte[4]. + # count = (cmd >> 2) + 1 + + pixel_count = (cmd >> 2) + 1 + + for _ in range(pixel_count): + # Start fetching pixel data + if odd: + # Odd indices require manual extraction of each of the 4 values + + # Palette index. Essentially a rotation of (byte[1]byte[2]) + # by 6 to the left, then masking with 0x00FF. + pixel_data_odd_0 = data_raw[dpos_color + 1] + pixel_data_odd_1 = data_raw[dpos_color + 2] + pixel_data.push_back((pixel_data_odd_0 >> 2) | (pixel_data_odd_1 << 6)) + + # Palette section. Described in byte[2] in bits 4-5. + pixel_data.push_back((pixel_data_odd_1 >> 2) & 0x03) + + # Damage modifier 1. Essentially a rotation of (byte[3]byte[4]) + # by 6 to the left, then masking with 0x00F0. + pixel_data_odd_2 = data_raw[dpos_color + 3] + pixel_data_odd_3 = data_raw[dpos_color + 4] + pixel_data.push_back(((pixel_data_odd_2 >> 2) | (pixel_data_odd_3 << 6)) & 0xF0) + + # Damage modifier 2. Described in byte[4] in bits 0-5. + pixel_data.push_back((pixel_data_odd_3 >> 2) & 0x3F) + + row_data.push_back(pixel(color_player, + pixel_data[0], + pixel_data[1], + pixel_data[2], + pixel_data[3])) + + # Go to next pixel + dpos_color += 5 + + else: + # Even indices can be read "as is". They just have to be masked. + for px_dpos in range(4): + pixel_data.push_back(data_raw[dpos_color + px_dpos]) + + row_data.push_back(pixel(color_player, + pixel_data[0], + pixel_data[1] & pixel_mask_even_1, + pixel_data[2] & pixel_mask_even_2, + pixel_data[3] & pixel_mask_even_3)) + + odd = not odd + pixel_data.clear() + + + elif lower_crumb == 0b00000010: + if SMXLayerVariant is SMXMainLayer4plus1Variant: + # player_color command + # draw the following 'count' pixels + # 4 pixels are stored in every 5 byte chunk. + # palette indices are contained in byte[0] - byte[3] + # palette sections are stored in byte[4] + # count = (cmd >> 2) + 1 + + pixel_count = (cmd >> 2) + 1 + + for _ in range(pixel_count): + # Start fetching pixel data + palette_section = (palette_section_block >> (2 * dpos_chunk)) & 0x03 + row_data.push_back(pixel(color_player, + data_raw[dpos_color], + palette_section, + 0, + 0)) + + dpos_color += 1 + dpos_chunk += 1 + + # Skip to next chunk + if dpos_chunk > 3: + dpos_chunk = 0 + dpos_color += 1 # Skip palette section block + palette_section_block = data_raw[dpos_color + 4] + + if SMXLayerVariant is (SMXOutlineLayerVariant, SMXShadowLayerVariant): + pass + + else: + raise Exception( + f"unknown smx main graphics layer drawing command: " + + f"{cmd:#x} in row {rowid:d}" + ) + + # Process next command + dpos_cmd += 1 + + if SMXLayerVariant in (SMXMainLayer8to5Variant, SMXMainLayer4plus1Variant): + return dpos_cmd, dpos_color, odd, row_data + if SMXLayerVariant in (SMXOutlineLayerVariant, SMXShadowLayerVariant): + return dpos_cmd, dpos_cmd, chunk_pos, row_data class SMX: From b421336fe8a70460cf7ce45955beed6a2e8449e0 Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Fri, 27 Dec 2024 15:23:22 +0000 Subject: [PATCH 077/152] compiling code --- .../convert/value_object/read/media/smx.pyx | 688 ++---------------- 1 file changed, 55 insertions(+), 633 deletions(-) diff --git a/openage/convert/value_object/read/media/smx.pyx b/openage/convert/value_object/read/media/smx.pyx index 10ad0c3364..b4cee859dc 100644 --- a/openage/convert/value_object/read/media/smx.pyx +++ b/openage/convert/value_object/read/media/smx.pyx @@ -59,28 +59,29 @@ cdef public dict LAYER_TYPES = { 1: SMXLayerType.SHADOW, 2: SMXLayerType.OUTLINE, } -cdef class SMXMainLayer8to5Variant: +cdef class SMXMainLayer8to5: pass -cdef class SMXMainLayer4plus1Variant: +cdef class SMXMainLayer4plus1: pass -cdef class SMXOutlineLayerVariant: +cdef class SMXOutlineLayer: pass -cdef class SMXShadowLayerVariant: +cdef class SMXShadowLayer: pass ctypedef fused SMXLayerVariant: - SMXMainLayer8to5Variant - SMXMainLayer4plus1Variant - SMXOutlineLayerVariant - SMXShadowLayerVariant + SMXLayer + SMXMainLayer8to5 + SMXMainLayer4plus1 + SMXOutlineLayer + SMXShadowLayer - -cdef process_drawing_cmds(SMXLayerVariant variant, - const uint8_t[:] &data_raw, +@cython.boundscheck(False) +cdef inline (int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant variant, + const uint8_t[::1] &data_raw, vector[pixel] &row_data, Py_ssize_t rowid, Py_ssize_t first_cmd_offset, @@ -100,49 +101,34 @@ cdef process_drawing_cmds(SMXLayerVariant variant, cdef uint8_t cmd = 0 cdef uint8_t lower_crumb = 0 cdef int pixel_count = 0 - - if SMXLayerVariant is SMXMainLayer8to5Variant + cdef Py_ssize_t dpos_color = 0 + cdef vector[uint8_t] pixel_data + # Pixel data temporary values that need further decompression + cdef uint8_t pixel_data_odd_0 = 0 + cdef uint8_t pixel_data_odd_1 = 0 + cdef uint8_t pixel_data_odd_2 = 0 + cdef uint8_t pixel_data_odd_3 = 0 + # Mask for even indices + # cdef uint8_t pixel_mask_even_0 = 0xFF + cdef uint8_t pixel_mask_even_1 = 0b00000011 + cdef uint8_t pixel_mask_even_2 = 0b11110000 + cdef uint8_t pixel_mask_even_3 = 0b00111111 + + if variant in (SMXMainLayer8to5, SMXMainLayer4plus1): # Position in the pixel data array - cdef Py_ssize_t dpos_color = first_color_offset + dpos_color = first_color_offset - # Position in the compression chunk. - cdef bool odd = chunk_pos - cdef int px_dpos = 0 # For loop iterator - - cdef vector[uint8_t] pixel_data - pixel_data.reserve(4) - - # Pixel data temporary values that need further decompression - cdef uint8_t pixel_data_odd_0 = 0 - cdef uint8_t pixel_data_odd_1 = 0 - cdef uint8_t pixel_data_odd_2 = 0 - cdef uint8_t pixel_data_odd_3 = 0 - - # Mask for even indices - # cdef uint8_t pixel_mask_even_0 = 0xFF - cdef uint8_t pixel_mask_even_1 = 0b00000011 - cdef uint8_t pixel_mask_even_2 = 0b11110000 - cdef uint8_t pixel_mask_even_3 = 0b00111111 - - if SMXLayerVariant is SMXMainLayer4plus1Variant: - # Position in the pixel data array - cdef Py_ssize_t dpos_color = first_color_offset - # Position in the compression chunk - cdef uint8_t dpos_chunk = chunk_pos - - cdef uint8_t palette_section_block = 0 - cdef uint8_t palette_section = 0 - - if SMXLayerVariant in (SMXShadowLayerVariant, SMXOutlineLayerVariant): - cdef uint8_t nextbyte = 0 + cdef uint8_t palette_section_block = 0 + cdef uint8_t palette_section = 0 + cdef uint8_t nextbyte = 0 # work through commands till end of row. while not eor: if row_data.size() > expected_size: raise Exception( f"Only {expected_size:d} pixels should be drawn in row {rowid:d} " - f"with layer type {self.info.layer_type:#x}, but we have {row_data.size():d} " + f"with layer type {variant:#x}, but we have {row_data.size():d} " f"already!" ) @@ -157,7 +143,7 @@ cdef process_drawing_cmds(SMXLayerVariant variant, eor = True dpos_cmd += 1 - if is SMXShadowLayerVariant: + if variant is SMXShadowLayer: # shadows sometimes need an extra pixel at # the end if row_data.size() < expected_size: @@ -191,10 +177,11 @@ cdef process_drawing_cmds(SMXLayerVariant variant, pixel_count = (cmd >> 2) + 1 - if is SMXMainLayer8to5Variant: + if variant is SMXMainLayer8to5: + pixel_data.reserve(4) for _ in range(pixel_count): # Start fetching pixel data - if odd: + if chunk_pos: # Odd indices require manual extraction of each of the 4 values # Palette index. Essentially a rotation of (byte[1]byte[2]) @@ -226,8 +213,8 @@ cdef process_drawing_cmds(SMXLayerVariant variant, else: # Even indices can be read "as is". They just have to be masked. - for px_dpos in range(4): - pixel_data.push_back(data_raw[dpos_color + px_dpos]) + for i in range(4): + pixel_data.push_back(data_raw[dpos_color + i]) row_data.push_back(pixel(color_standard, pixel_data[0], @@ -238,12 +225,12 @@ cdef process_drawing_cmds(SMXLayerVariant variant, odd = not odd pixel_data.clear() - if is SMXMainLayer4plus1Variant: - palette_section_block = data_raw[dpos_color + (4 - dpos_chunk)] + if variant is SMXMainLayer4plus1: + palette_section_block = data_raw[dpos_color + (4 - chunk_pos)] for _ in range(pixel_count): # Start fetching pixel data - palette_section = (palette_section_block >> (2 * dpos_chunk)) & 0x03 + palette_section = (palette_section_block >> (2 * chunk_pos)) & 0x03 row_data.push_back(pixel(color_standard, data_raw[dpos_color], palette_section, @@ -251,15 +238,15 @@ cdef process_drawing_cmds(SMXLayerVariant variant, 0)) dpos_color += 1 - dpos_chunk += 1 + chunk_pos += 1 # Skip to next chunk - if dpos_chunk > 3: - dpos_chunk = 0 + if chunk_pos > 3: + chunk_pos = 0 dpos_color += 1 # Skip palette section block palette_section_block = data_raw[dpos_color + 4] - if SMXLayerVariant is SMXShadowLayerVariant: + if variant is SMXShadowLayer: for _ in range(pixel_count): dpos_color += 1 nextbyte = data_raw[dpos_color] @@ -267,7 +254,7 @@ cdef process_drawing_cmds(SMXLayerVariant variant, row_data.push_back(pixel(color_shadow, nextbyte, 0, 0, 0)) - if SMXLayerVariant is SMXOutlineLayerVariant: + if variant is SMXOutlineLayer: # we don't know the color the game wants # so we just draw index 0 row_data.push_back(pixel(color_outline, @@ -275,7 +262,7 @@ cdef process_drawing_cmds(SMXLayerVariant variant, elif lower_crumb == 0b00000010: - if SMXLayerVariant is SMXMainLayer8to5Variant: + if variant is SMXMainLayer8to5: # player_color command # draw the following 'count' pixels # pixels are stored in 5 byte chunks @@ -334,7 +321,7 @@ cdef process_drawing_cmds(SMXLayerVariant variant, elif lower_crumb == 0b00000010: - if SMXLayerVariant is SMXMainLayer4plus1Variant: + if variant is SMXMainLayer4plus1: # player_color command # draw the following 'count' pixels # 4 pixels are stored in every 5 byte chunk. @@ -346,7 +333,7 @@ cdef process_drawing_cmds(SMXLayerVariant variant, for _ in range(pixel_count): # Start fetching pixel data - palette_section = (palette_section_block >> (2 * dpos_chunk)) & 0x03 + palette_section = (palette_section_block >> (2 * chunk_pos)) & 0x03 row_data.push_back(pixel(color_player, data_raw[dpos_color], palette_section, @@ -354,15 +341,15 @@ cdef process_drawing_cmds(SMXLayerVariant variant, 0)) dpos_color += 1 - dpos_chunk += 1 + chunk_pos += 1 # Skip to next chunk - if dpos_chunk > 3: - dpos_chunk = 0 + if chunk_pos > 3: + chunk_pos = 0 dpos_color += 1 # Skip palette section block palette_section_block = data_raw[dpos_color + 4] - if SMXLayerVariant is (SMXOutlineLayerVariant, SMXShadowLayerVariant): + if variant is (SMXOutlineLayer, SMXShadowLayer): pass else: @@ -374,9 +361,9 @@ cdef process_drawing_cmds(SMXLayerVariant variant, # Process next command dpos_cmd += 1 - if SMXLayerVariant in (SMXMainLayer8to5Variant, SMXMainLayer4plus1Variant): + if variant in (SMXMainLayer8to5, SMXMainLayer4plus1): return dpos_cmd, dpos_color, odd, row_data - if SMXLayerVariant in (SMXOutlineLayerVariant, SMXShadowLayerVariant): + if variant in (SMXOutlineLayer, SMXShadowLayer): return dpos_cmd, dpos_cmd, chunk_pos, row_data @@ -729,7 +716,7 @@ cdef class SMXLayer: # process the drawing commands for this row. next_cmd_offset, next_color_offset, chunk_pos, row_data = \ - self.process_drawing_cmds( + process_drawing_cmds(self, data_raw, row_data, rowid, @@ -757,26 +744,6 @@ cdef class SMXLayer: return next_cmd_offset, next_color_offset, chunk_pos, row_data - cdef (int, int, int, vector[pixel]) process_drawing_cmds(self, - const uint8_t[::1] &data_raw, - vector[pixel] &row_data, - Py_ssize_t rowid, - Py_ssize_t first_cmd_offset, - Py_ssize_t first_color_offset, - int chunk_pos, - size_t expected_size): - """ - Extracts pixel data from the layer data. Every layer type uses - its own implementation for better optimization. - - :param row_data: Pixel data is appended to this array. - :param rowid: Index of the current row in the layer. - :param first_cmd_offset: Offset of the first command of the current row. - :param first_color_offset: Offset of the first pixel data value of the current row. - :param chunk_pos: Current position in the compressed chunk. - :param expected_size: Expected length of row_data after encountering the EOR command. - """ - pass def get_picture_data(self, palette): """ @@ -811,551 +778,6 @@ cdef class SMXLayer: return repr(self.info) -cdef class SMXMainLayer8to5(SMXLayer): - """ - Compressed SMP layer (compression type 8to5) for the main graphics sprite. - """ - - def __init__(self, layer_header, data): - super().__init__(layer_header, data) - - @cython.boundscheck(False) - cdef inline (int, int, int, vector[pixel]) process_drawing_cmds(self, - const uint8_t[::1] &data_raw, - vector[pixel] &row_data, - Py_ssize_t rowid, - Py_ssize_t first_cmd_offset, - Py_ssize_t first_color_offset, - int chunk_pos, - size_t expected_size): - """ - extract colors (pixels) for the drawing commands that were - compressed with 8to5 compression. - """ - # position in the command array, we start at the first command of this row - cdef Py_ssize_t dpos_cmd = first_cmd_offset - - # Position in the pixel data array - cdef Py_ssize_t dpos_color = first_color_offset - - # Position in the compression chunk. - cdef bool odd = chunk_pos - cdef int px_dpos = 0 # For loop iterator - - # is the end of the current row reached? - cdef bool eor = False - - cdef uint8_t cmd = 0 - cdef uint8_t lower_crumb = 0 - cdef int pixel_count = 0 - cdef vector[uint8_t] pixel_data - pixel_data.reserve(4) - - # Pixel data temporary values that need further decompression - cdef uint8_t pixel_data_odd_0 = 0 - cdef uint8_t pixel_data_odd_1 = 0 - cdef uint8_t pixel_data_odd_2 = 0 - cdef uint8_t pixel_data_odd_3 = 0 - - # Mask for even indices - # cdef uint8_t pixel_mask_even_0 = 0xFF - cdef uint8_t pixel_mask_even_1 = 0b00000011 - cdef uint8_t pixel_mask_even_2 = 0b11110000 - cdef uint8_t pixel_mask_even_3 = 0b00111111 - - # work through commands till end of row. - while not eor: - if row_data.size() > expected_size: - raise Exception( - f"Only {expected_size:d} pixels should be drawn in row {rowid:d} " - f"with layer type {self.info.layer_type:#x}, but we have {row_data.size():d} " - f"already!" - ) - - # fetch drawing instruction - cmd = data_raw[dpos_cmd] - - # Last 2 bits store command type - lower_crumb = 0b00000011 & cmd - - if lower_crumb == 0b00000011: - # eor (end of row) command, this row is finished now. - eor = True - dpos_cmd += 1 - - continue - - elif lower_crumb == 0b00000000: - # skip command - # draw 'count' transparent pixels - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - for _ in range(pixel_count): - row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) - - elif lower_crumb == 0b00000001: - # color_list command - # draw the following 'count' pixels - # pixels are stored in 5 byte chunks - # even pixel indices have their info stored - # in byte[0] - byte[3]. odd pixel indices have - # their info stored in byte[1] - byte[4]. - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - for _ in range(pixel_count): - # Start fetching pixel data - if odd: - # Odd indices require manual extraction of each of the 4 values - - # Palette index. Essentially a rotation of (byte[1]byte[2]) - # by 6 to the left, then masking with 0x00FF. - pixel_data_odd_0 = data_raw[dpos_color + 1] - pixel_data_odd_1 = data_raw[dpos_color + 2] - pixel_data.push_back((pixel_data_odd_0 >> 2) | (pixel_data_odd_1 << 6)) - - # Palette section. Described in byte[2] in bits 4-5. - pixel_data.push_back((pixel_data_odd_1 >> 2) & 0x03) - - # Damage mask 1. Essentially a rotation of (byte[3]byte[4]) - # by 6 to the left, then masking with 0x00F0. - pixel_data_odd_2 = data_raw[dpos_color + 3] - pixel_data_odd_3 = data_raw[dpos_color + 4] - pixel_data.push_back(((pixel_data_odd_2 >> 2) | (pixel_data_odd_3 << 6)) & 0xF0) - - # Damage mask 2. Described in byte[4] in bits 0-5. - pixel_data.push_back((pixel_data_odd_3 >> 2) & 0x3F) - - row_data.push_back(pixel(color_standard, - pixel_data[0], - pixel_data[1], - pixel_data[2], - pixel_data[3])) - - # Go to next pixel - dpos_color += 5 - - else: - # Even indices can be read "as is". They just have to be masked. - for px_dpos in range(4): - pixel_data.push_back(data_raw[dpos_color + px_dpos]) - - row_data.push_back(pixel(color_standard, - pixel_data[0], - pixel_data[1] & pixel_mask_even_1, - pixel_data[2] & pixel_mask_even_2, - pixel_data[3] & pixel_mask_even_3)) - - odd = not odd - pixel_data.clear() - - elif lower_crumb == 0b00000010: - # player_color command - # draw the following 'count' pixels - # pixels are stored in 5 byte chunks - # even pixel indices have their info stored - # in byte[0] - byte[3]. odd pixel indices have - # their info stored in byte[1] - byte[4]. - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - for _ in range(pixel_count): - # Start fetching pixel data - if odd: - # Odd indices require manual extraction of each of the 4 values - - # Palette index. Essentially a rotation of (byte[1]byte[2]) - # by 6 to the left, then masking with 0x00FF. - pixel_data_odd_0 = data_raw[dpos_color + 1] - pixel_data_odd_1 = data_raw[dpos_color + 2] - pixel_data.push_back((pixel_data_odd_0 >> 2) | (pixel_data_odd_1 << 6)) - - # Palette section. Described in byte[2] in bits 4-5. - pixel_data.push_back((pixel_data_odd_1 >> 2) & 0x03) - - # Damage modifier 1. Essentially a rotation of (byte[3]byte[4]) - # by 6 to the left, then masking with 0x00F0. - pixel_data_odd_2 = data_raw[dpos_color + 3] - pixel_data_odd_3 = data_raw[dpos_color + 4] - pixel_data.push_back(((pixel_data_odd_2 >> 2) | (pixel_data_odd_3 << 6)) & 0xF0) - - # Damage modifier 2. Described in byte[4] in bits 0-5. - pixel_data.push_back((pixel_data_odd_3 >> 2) & 0x3F) - - row_data.push_back(pixel(color_player, - pixel_data[0], - pixel_data[1], - pixel_data[2], - pixel_data[3])) - - # Go to next pixel - dpos_color += 5 - - else: - # Even indices can be read "as is". They just have to be masked. - for px_dpos in range(4): - pixel_data.push_back(data_raw[dpos_color + px_dpos]) - - row_data.push_back(pixel(color_player, - pixel_data[0], - pixel_data[1] & pixel_mask_even_1, - pixel_data[2] & pixel_mask_even_2, - pixel_data[3] & pixel_mask_even_3)) - - odd = not odd - pixel_data.clear() - - else: - raise Exception( - f"unknown smx main graphics layer drawing command: " + - f"{cmd:#x} in row {rowid:d}" - ) - - # Process next command - dpos_cmd += 1 - - return dpos_cmd, dpos_color, odd, row_data - - -cdef class SMXMainLayer4plus1(SMXLayer): - """ - Compressed SMP layer (compression type 4plus1) for the main graphics sprite. - """ - - def __init__(self, layer_header, data): - super().__init__(layer_header, data) - - @cython.boundscheck(False) - cdef inline (int, int, int, vector[pixel]) process_drawing_cmds(self, - const uint8_t[::1] &data_raw, - vector[pixel] &row_data, - Py_ssize_t rowid, - Py_ssize_t first_cmd_offset, - Py_ssize_t first_color_offset, - int chunk_pos, - size_t expected_size): - """ - extract colors (pixels) for the drawing commands that were - compressed with 4plus1 compression. - """ - # position in the data blob, we start at the first command of this row - cdef Py_ssize_t dpos_cmd = first_cmd_offset - - # Position in the pixel data array - cdef Py_ssize_t dpos_color = first_color_offset - - # Position in the compression chunk - cdef uint8_t dpos_chunk = chunk_pos - - # is the end of the current row reached? - cdef bool eor = False - - cdef uint8_t cmd = 0 - cdef uint8_t lower_crumb = 0 - cdef int pixel_count = 0 - cdef uint8_t palette_section_block = 0 - cdef uint8_t palette_section = 0 - - # work through commands till end of row. - while not eor: - if row_data.size() > expected_size: - raise Exception( - f"Only {expected_size:d} pixels should be drawn in row {rowid:d} " + - f"with layer type {self.info.layer_type:#x}, but we have {row_data.size():d} " + - f"already!" - ) - - # fetch drawing instruction - cmd = data_raw[dpos_cmd] - - # Last 2 bits store command type - lower_crumb = 0b00000011 & cmd - - if lower_crumb == 0b00000011: - # eor (end of row) command, this row is finished now. - eor = True - dpos_cmd += 1 - - continue - - elif lower_crumb == 0b00000000: - # skip command - # draw 'count' transparent pixels - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - for _ in range(pixel_count): - row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) - - elif lower_crumb == 0b00000001: - # color_list command - # draw the following 'count' pixels - # 4 pixels are stored in every 5 byte chunk. - # palette indices are contained in byte[0] - byte[3] - # palette sections are stored in byte[4] - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - palette_section_block = data_raw[dpos_color + (4 - dpos_chunk)] - - for _ in range(pixel_count): - # Start fetching pixel data - palette_section = (palette_section_block >> (2 * dpos_chunk)) & 0x03 - row_data.push_back(pixel(color_standard, - data_raw[dpos_color], - palette_section, - 0, - 0)) - - dpos_color += 1 - dpos_chunk += 1 - - # Skip to next chunk - if dpos_chunk > 3: - dpos_chunk = 0 - dpos_color += 1 # Skip palette section block - palette_section_block = data_raw[dpos_color + 4] - - elif lower_crumb == 0b00000010: - # player_color command - # draw the following 'count' pixels - # 4 pixels are stored in every 5 byte chunk. - # palette indices are contained in byte[0] - byte[3] - # palette sections are stored in byte[4] - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - for _ in range(pixel_count): - # Start fetching pixel data - palette_section = (palette_section_block >> (2 * dpos_chunk)) & 0x03 - row_data.push_back(pixel(color_player, - data_raw[dpos_color], - palette_section, - 0, - 0)) - - dpos_color += 1 - dpos_chunk += 1 - - # Skip to next chunk - if dpos_chunk > 3: - dpos_chunk = 0 - dpos_color += 1 # Skip palette section block - palette_section_block = data_raw[dpos_color + 4] - - else: - raise Exception( - f"unknown smx main graphics layer drawing command: " + - f"{cmd:#x} in row {rowid:d}" - ) - - # Process next command - dpos_cmd += 1 - - return dpos_cmd, dpos_color, dpos_chunk, row_data - - -cdef class SMXShadowLayer(SMXLayer): - """ - Compressed SMP layer for the shadow graphics. - """ - - def __init__(self, layer_header, data): - super().__init__(layer_header, data) - - @cython.boundscheck(False) - cdef inline (int, int, int, vector[pixel]) process_drawing_cmds(self, - const uint8_t[::1] &data_raw, - vector[pixel] &row_data, - Py_ssize_t rowid, - Py_ssize_t first_cmd_offset, - Py_ssize_t first_color_offset, - int chunk_pos, - size_t expected_size): - """ - extract colors (pixels) for the drawing commands - found for this row in the SMX layer. - """ - # position in the data blob, we start at the first command of this row - cdef Py_ssize_t dpos = first_cmd_offset - - # is the end of the current row reached? - cdef bool eor = False - - cdef uint8_t cmd = 0 - cdef uint8_t nextbyte = 0 - cdef uint8_t lower_crumb = 0 - cdef int pixel_count = 0 - - # work through commands till end of row. - while not eor: - if row_data.size() > expected_size: - raise Exception( - f"Only {expected_size:d} pixels should be drawn ifn row {rowid:d} " + - f"with layer type {self.info.layer_type:#x}, but we have {row_data.size():d} " + - f"already!" - ) - - # fetch drawing instruction - cmd = data_raw[dpos] - - # Last 2 bits store command type - lower_crumb = 0b00000011 & cmd - - if lower_crumb == 0b00000011: - # eol (end of line) command, this row is finished now. - eor = True - dpos += 1 - - # shadows sometimes need an extra pixel at - # the end - if row_data.size() < expected_size: - # copy the last drawn pixel - # (still stored in nextbyte) - # - # TODO: confirm that this is the - # right way to do it - row_data.push_back(pixel(color_shadow, - nextbyte, 0, 0, 0)) - - continue - - elif lower_crumb == 0b00000000: - # skip command - # draw 'count' transparent pixels - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - for _ in range(pixel_count): - row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) - - elif lower_crumb == 0b00000001: - # color_list command - # draw the following 'count' pixels - # pixels are stored as 1 byte alpha values - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - for _ in range(pixel_count): - dpos += 1 - nextbyte = data_raw[dpos] - - row_data.push_back(pixel(color_shadow, - nextbyte, 0, 0, 0)) - - else: - raise Exception( - f"unknown smp shadow layer drawing command: " + - f"{cmd:#x} in row {rowid}" - ) - - # process next command - dpos += 1 - - # end of row reached, return the created pixel array. - return dpos, dpos, chunk_pos, row_data - - -cdef class SMXOutlineLayer(SMXLayer): - """ - Compressed SMP layer for the outline graphics. - """ - - def __init__(self, layer_header, data): - super().__init__(layer_header, data) - - @cython.boundscheck(False) - cdef inline (int, int, int, vector[pixel]) process_drawing_cmds(self, - const uint8_t[::1] &data_raw, - vector[pixel] &row_data, - Py_ssize_t rowid, - Py_ssize_t first_cmd_offset, - Py_ssize_t first_color_offset, - int chunk_pos, - size_t expected_size): - """ - extract colors (pixels) for the drawing commands - found for this row in the SMX layer. - """ - # position in the data blob, we start at the first command of this row - cdef Py_ssize_t dpos = first_cmd_offset - - # is the end of the current row reached? - cdef bool eor = False - - cdef uint8_t cmd = 0 - cdef uint8_t nextbyte = 0 - cdef uint8_t lower_crumb = 0 - cdef int pixel_count = 0 - - # work through commands till end of row. - while not eor: - if row_data.size() > expected_size: - raise Exception( - f"Only {expected_size:d} pixels should be drawn in row {rowid:d} " + - f"with layer type {self.info.layer_type:#x}, but we have {row_data.size():d} " + - f"already!" - ) - - # fetch drawing instruction - cmd = data_raw[dpos] - - # Last 2 bits store command type - lower_crumb = 0b00000011 & cmd - - # opcode: cmd, rowid: rowid - - if lower_crumb == 0b00000011: - # eol (end of line) command, this row is finished now. - eor = True - dpos += 1 - - continue - - elif lower_crumb == 0b00000000: - # skip command - # draw 'count' transparent pixels - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - for _ in range(pixel_count): - row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) - - elif lower_crumb == 0b00000001: - # color_list command - # draw the following 'count' pixels - # as player outline colors. - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - for _ in range(pixel_count): - # we don't know the color the game wants - # so we just draw index 0 - row_data.push_back(pixel(color_outline, - 0, 0, 0, 0)) - - else: - raise Exception( - f"unknown smp outline layer drawing command: " + - f"{cmd:#x} in row {rowid}" - ) - - # process next command - dpos += 1 - - # end of row reached, return the created pixel array. - return dpos, dpos, chunk_pos, row_data @cython.boundscheck(False) From 7177fd2e9a72b0364c401fdc33fd4a87e6784e6e Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Fri, 27 Dec 2024 19:30:30 +0000 Subject: [PATCH 078/152] format --- .../convert/value_object/read/media/smx.pyx | 181 +++++++++--------- 1 file changed, 95 insertions(+), 86 deletions(-) diff --git a/openage/convert/value_object/read/media/smx.pyx b/openage/convert/value_object/read/media/smx.pyx index b4cee859dc..bcf9ec0e33 100644 --- a/openage/convert/value_object/read/media/smx.pyx +++ b/openage/convert/value_object/read/media/smx.pyx @@ -2,6 +2,9 @@ # # cython: infer_types=True +from libcpp.vector cimport vector +from libcpp cimport bool +from libc.stdint cimport uint8_t, uint16_t from enum import Enum import numpy from struct import Struct, unpack_from @@ -12,11 +15,6 @@ from .....log import spam, dbg cimport cython cimport numpy -from libc.stdint cimport uint8_t, uint16_t -from libcpp cimport bool -from libcpp.vector cimport vector - - # SMX files have little endian byte order endianness = "< " @@ -49,8 +47,8 @@ class SMXLayerType(Enum): """ SMX layer types. """ - MAIN = "main" - SHADOW = "shadow" + MAIN = "main" + SHADOW = "shadow" OUTLINE = "outline" @@ -79,15 +77,16 @@ ctypedef fused SMXLayerVariant: SMXOutlineLayer SMXShadowLayer + @cython.boundscheck(False) -cdef inline (int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant variant, - const uint8_t[::1] &data_raw, - vector[pixel] &row_data, - Py_ssize_t rowid, - Py_ssize_t first_cmd_offset, - Py_ssize_t first_color_offset, - int chunk_pos, - size_t expected_size): +cdef inline(int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant variant, + const uint8_t[::1] & data_raw, + vector[pixel] & row_data, + Py_ssize_t rowid, + Py_ssize_t first_cmd_offset, + Py_ssize_t first_color_offset, + int chunk_pos, + size_t expected_size): """ TODO: docstring """ @@ -118,11 +117,10 @@ cdef inline (int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant # Position in the pixel data array dpos_color = first_color_offset - cdef uint8_t palette_section_block = 0 cdef uint8_t palette_section = 0 cdef uint8_t nextbyte = 0 - + # work through commands till end of row. while not eor: if row_data.size() > expected_size: @@ -176,7 +174,7 @@ cdef inline (int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant # count = (cmd >> 2) + 1 pixel_count = (cmd >> 2) + 1 - + if variant is SMXMainLayer8to5: pixel_data.reserve(4) for _ in range(pixel_count): @@ -188,7 +186,8 @@ cdef inline (int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant # by 6 to the left, then masking with 0x00FF. pixel_data_odd_0 = data_raw[dpos_color + 1] pixel_data_odd_1 = data_raw[dpos_color + 2] - pixel_data.push_back((pixel_data_odd_0 >> 2) | (pixel_data_odd_1 << 6)) + pixel_data.push_back( + (pixel_data_odd_0 >> 2) | (pixel_data_odd_1 << 6)) # Palette section. Described in byte[2] in bits 4-5. pixel_data.push_back((pixel_data_odd_1 >> 2) & 0x03) @@ -197,16 +196,17 @@ cdef inline (int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant # by 6 to the left, then masking with 0x00F0. pixel_data_odd_2 = data_raw[dpos_color + 3] pixel_data_odd_3 = data_raw[dpos_color + 4] - pixel_data.push_back(((pixel_data_odd_2 >> 2) | (pixel_data_odd_3 << 6)) & 0xF0) + pixel_data.push_back( + ((pixel_data_odd_2 >> 2) | (pixel_data_odd_3 << 6)) & 0xF0) # Damage mask 2. Described in byte[4] in bits 0-5. pixel_data.push_back((pixel_data_odd_3 >> 2) & 0x3F) row_data.push_back(pixel(color_standard, - pixel_data[0], - pixel_data[1], - pixel_data[2], - pixel_data[3])) + pixel_data[0], + pixel_data[1], + pixel_data[2], + pixel_data[3])) # Go to next pixel dpos_color += 5 @@ -217,10 +217,10 @@ cdef inline (int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant pixel_data.push_back(data_raw[dpos_color + i]) row_data.push_back(pixel(color_standard, - pixel_data[0], - pixel_data[1] & pixel_mask_even_1, - pixel_data[2] & pixel_mask_even_2, - pixel_data[3] & pixel_mask_even_3)) + pixel_data[0], + pixel_data[1] & pixel_mask_even_1, + pixel_data[2] & pixel_mask_even_2, + pixel_data[3] & pixel_mask_even_3)) odd = not odd pixel_data.clear() @@ -230,7 +230,8 @@ cdef inline (int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant for _ in range(pixel_count): # Start fetching pixel data - palette_section = (palette_section_block >> (2 * chunk_pos)) & 0x03 + palette_section = ( + palette_section_block >> (2 * chunk_pos)) & 0x03 row_data.push_back(pixel(color_standard, data_raw[dpos_color], palette_section, @@ -243,7 +244,7 @@ cdef inline (int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant # Skip to next chunk if chunk_pos > 3: chunk_pos = 0 - dpos_color += 1 # Skip palette section block + dpos_color += 1 # Skip palette section block palette_section_block = data_raw[dpos_color + 4] if variant is SMXShadowLayer: @@ -258,9 +259,8 @@ cdef inline (int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant # we don't know the color the game wants # so we just draw index 0 row_data.push_back(pixel(color_outline, - 0, 0, 0, 0)) + 0, 0, 0, 0)) - elif lower_crumb == 0b00000010: if variant is SMXMainLayer8to5: # player_color command @@ -282,7 +282,8 @@ cdef inline (int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant # by 6 to the left, then masking with 0x00FF. pixel_data_odd_0 = data_raw[dpos_color + 1] pixel_data_odd_1 = data_raw[dpos_color + 2] - pixel_data.push_back((pixel_data_odd_0 >> 2) | (pixel_data_odd_1 << 6)) + pixel_data.push_back( + (pixel_data_odd_0 >> 2) | (pixel_data_odd_1 << 6)) # Palette section. Described in byte[2] in bits 4-5. pixel_data.push_back((pixel_data_odd_1 >> 2) & 0x03) @@ -291,16 +292,17 @@ cdef inline (int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant # by 6 to the left, then masking with 0x00F0. pixel_data_odd_2 = data_raw[dpos_color + 3] pixel_data_odd_3 = data_raw[dpos_color + 4] - pixel_data.push_back(((pixel_data_odd_2 >> 2) | (pixel_data_odd_3 << 6)) & 0xF0) + pixel_data.push_back( + ((pixel_data_odd_2 >> 2) | (pixel_data_odd_3 << 6)) & 0xF0) # Damage modifier 2. Described in byte[4] in bits 0-5. pixel_data.push_back((pixel_data_odd_3 >> 2) & 0x3F) row_data.push_back(pixel(color_player, - pixel_data[0], - pixel_data[1], - pixel_data[2], - pixel_data[3])) + pixel_data[0], + pixel_data[1], + pixel_data[2], + pixel_data[3])) # Go to next pixel dpos_color += 5 @@ -308,18 +310,18 @@ cdef inline (int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant else: # Even indices can be read "as is". They just have to be masked. for px_dpos in range(4): - pixel_data.push_back(data_raw[dpos_color + px_dpos]) + pixel_data.push_back( + data_raw[dpos_color + px_dpos]) row_data.push_back(pixel(color_player, - pixel_data[0], - pixel_data[1] & pixel_mask_even_1, - pixel_data[2] & pixel_mask_even_2, - pixel_data[3] & pixel_mask_even_3)) + pixel_data[0], + pixel_data[1] & pixel_mask_even_1, + pixel_data[2] & pixel_mask_even_2, + pixel_data[3] & pixel_mask_even_3)) odd = not odd pixel_data.clear() - elif lower_crumb == 0b00000010: if variant is SMXMainLayer4plus1: # player_color command @@ -333,12 +335,13 @@ cdef inline (int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant for _ in range(pixel_count): # Start fetching pixel data - palette_section = (palette_section_block >> (2 * chunk_pos)) & 0x03 + palette_section = ( + palette_section_block >> (2 * chunk_pos)) & 0x03 row_data.push_back(pixel(color_player, - data_raw[dpos_color], - palette_section, - 0, - 0)) + data_raw[dpos_color], + palette_section, + 0, + 0)) dpos_color += 1 chunk_pos += 1 @@ -346,7 +349,7 @@ cdef inline (int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant # Skip to next chunk if chunk_pos > 3: chunk_pos = 0 - dpos_color += 1 # Skip palette section block + dpos_color += 1 # Skip palette section block palette_section_block = data_raw[dpos_color + 4] if variant is (SMXOutlineLayer, SMXShadowLayer): @@ -356,7 +359,7 @@ cdef inline (int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant raise Exception( f"unknown smx main graphics layer drawing command: " + f"{cmd:#x} in row {rowid:d}" - ) + ) # Process next command dpos_cmd += 1 @@ -409,14 +412,16 @@ class SMX: """ smx_header = SMX.smx_header.unpack_from(data) - self.smp_type, version, frame_count, file_size_comp,\ + self.smp_type, version, frame_count, file_size_comp, \ file_size_uncomp, comment = smx_header dbg("SMX") dbg(" version: %s", version) dbg(" frame count: %s", frame_count) - dbg(" file size compressed: %s B", file_size_comp + 0x20) # 0x20 = SMX header size - dbg(" file size uncompressed: %s B", file_size_uncomp + 0x40) # 0x80 = SMP header size + # 0x20 = SMX header size + dbg(" file size compressed: %s B", file_size_comp + 0x20) + # 0x80 = SMP header size + dbg(" file size uncompressed: %s B", file_size_uncomp + 0x40) dbg(" comment: %s", comment.decode('ascii')) # SMX graphic frames are created from overlaying @@ -434,7 +439,7 @@ class SMX: frame_header = SMX.smx_frame_header.unpack_from( data, current_offset) - frame_type , palette_number, _ = frame_header + frame_type, palette_number, _ = frame_header current_offset += SMX.smx_frame_header.size @@ -453,7 +458,7 @@ class SMX: layer_header_data = SMX.smx_layer_header.unpack_from( data, current_offset) - width, height, hotspot_x, hotspot_y,\ + width, height, hotspot_x, hotspot_y, \ distance_next_frame, _ = layer_header_data current_offset += SMX.smx_layer_header.size @@ -463,12 +468,14 @@ class SMX: # Skip outline table current_offset += 4 * height - qdl_command_array_size = Struct("< I").unpack_from(data, current_offset)[0] + qdl_command_array_size = Struct( + "< I").unpack_from(data, current_offset)[0] current_offset += 4 # Read length of color table if layer_type is SMXLayerType.MAIN: - qdl_color_table_size = Struct("< I").unpack_from(data, current_offset)[0] + qdl_color_table_size = Struct( + "< I").unpack_from(data, current_offset)[0] current_offset += 4 qdl_color_table_offset = current_offset + qdl_command_array_size @@ -488,16 +495,20 @@ class SMX: if layer_type is SMXLayerType.MAIN: if layer_header.compression_type == 0x08: - self.main_frames.append(SMXMainLayer8to5(layer_header, data)) + self.main_frames.append( + SMXMainLayer8to5(layer_header, data)) elif layer_header.compression_type == 0x00: - self.main_frames.append(SMXMainLayer4plus1(layer_header, data)) + self.main_frames.append( + SMXMainLayer4plus1(layer_header, data)) elif layer_type is SMXLayerType.SHADOW: - self.shadow_frames.append(SMXShadowLayer(layer_header, data)) + self.shadow_frames.append( + SMXShadowLayer(layer_header, data)) elif layer_type is SMXLayerType.OUTLINE: - self.outline_frames.append(SMXOutlineLayer(layer_header, data)) + self.outline_frames.append( + SMXOutlineLayer(layer_header, data)) def get_frames(self, layer: int = 0): """ @@ -673,16 +684,17 @@ cdef class SMXLayer: # process cmd table for i in range(row_count): cmd_offset, color_offset, chunk_pos, row_data = \ - self.create_color_row(data_raw, i, cmd_offset, color_offset, chunk_pos) + self.create_color_row( + data_raw, i, cmd_offset, color_offset, chunk_pos) self.pcolor.push_back(row_data) - cdef inline (int, int, int, vector[pixel]) create_color_row(self, - const uint8_t[::1] &data_raw, - Py_ssize_t rowid, - int cmd_offset, - int color_offset, - int chunk_pos): + cdef inline(int, int, int, vector[pixel]) create_color_row(self, + const uint8_t[::1] & data_raw, + Py_ssize_t rowid, + int cmd_offset, + int color_offset, + int chunk_pos): """ Extract colors (pixels) for the given rowid. @@ -717,14 +729,14 @@ cdef class SMXLayer: # process the drawing commands for this row. next_cmd_offset, next_color_offset, chunk_pos, row_data = \ process_drawing_cmds(self, - data_raw, - row_data, - rowid, - first_cmd_offset, - first_color_offset, - chunk_pos, - pixel_count - bounds.right - ) + data_raw, + row_data, + rowid, + first_cmd_offset, + first_color_offset, + chunk_pos, + pixel_count - bounds.right + ) # finish by filling up the right transparent space for i in range(bounds.right): @@ -736,7 +748,7 @@ cdef class SMXLayer: summary = "%d/%d -> row %d, layer type %d, offset %d / %#x" % ( got, pixel_count, rowid, self.info.layer_type, first_cmd_offset, first_cmd_offset - ) + ) txt = "got %%s pixels than expected: %s, missing: %d" % ( summary, abs(pixel_count - got)) @@ -744,7 +756,6 @@ cdef class SMXLayer: return next_cmd_offset, next_color_offset, chunk_pos, row_data - def get_picture_data(self, palette): """ Convert the palette index matrix to a RGBA image. @@ -778,12 +789,10 @@ cdef class SMXLayer: return repr(self.info) - - @cython.boundscheck(False) @cython.wraparound(False) -cdef numpy.ndarray determine_rgba_matrix(vector[vector[pixel]] &image_matrix, - numpy.ndarray[numpy.uint8_t, ndim=2, mode="c"] palette): +cdef numpy.ndarray determine_rgba_matrix(vector[vector[pixel]] & image_matrix, + numpy.ndarray[numpy.uint8_t, ndim = 2, mode = "c"] palette): """ converts a palette index image matrix to an rgba matrix. @@ -794,7 +803,7 @@ cdef numpy.ndarray determine_rgba_matrix(vector[vector[pixel]] &image_matrix, cdef size_t height = image_matrix.size() cdef size_t width = image_matrix[0].size() - cdef numpy.ndarray[numpy.uint8_t, ndim=3, mode="c"] array_data = \ + cdef numpy.ndarray[numpy.uint8_t, ndim = 3, mode = "c"] array_data = \ numpy.zeros((height, width, 4), dtype=numpy.uint8) cdef uint8_t[:, ::1] m_lookup = palette @@ -883,7 +892,7 @@ cdef numpy.ndarray determine_rgba_matrix(vector[vector[pixel]] &image_matrix, @cython.boundscheck(False) @cython.wraparound(False) -cdef numpy.ndarray determine_damage_matrix(vector[vector[pixel]] &image_matrix): +cdef numpy.ndarray determine_damage_matrix(vector[vector[pixel]] & image_matrix): """ converts the damage modifier values to an image using the RG values. @@ -893,7 +902,7 @@ cdef numpy.ndarray determine_damage_matrix(vector[vector[pixel]] &image_matrix): cdef size_t height = image_matrix.size() cdef size_t width = image_matrix[0].size() - cdef numpy.ndarray[numpy.uint8_t, ndim=3, mode="c"] array_data = \ + cdef numpy.ndarray[numpy.uint8_t, ndim = 3, mode = "c"] array_data = \ numpy.zeros((height, width, 4), dtype=numpy.uint8) cdef uint8_t r = 0 From 74cb7d0cd7802c0b3fabe6c33ff7a221253d701b Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Fri, 27 Dec 2024 20:42:22 +0000 Subject: [PATCH 079/152] class defs --- .../convert/value_object/read/media/smx.pyx | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/openage/convert/value_object/read/media/smx.pyx b/openage/convert/value_object/read/media/smx.pyx index bcf9ec0e33..2cb4749091 100644 --- a/openage/convert/value_object/read/media/smx.pyx +++ b/openage/convert/value_object/read/media/smx.pyx @@ -57,17 +57,21 @@ cdef public dict LAYER_TYPES = { 1: SMXLayerType.SHADOW, 2: SMXLayerType.OUTLINE, } -cdef class SMXMainLayer8to5: - pass +cdef class SMXMainLayer8to5(SMXLayer): + def __init__(self, layer_header, data): + super().__init__(layer_header, data) -cdef class SMXMainLayer4plus1: - pass +cdef class SMXMainLayer4plus1(SMXLayer): + def __init__(self, layer_header, data): + super().__init__(layer_header, data) -cdef class SMXOutlineLayer: - pass +cdef class SMXOutlineLayer(SMXLayer): + def __init__(self, layer_header, data): + super().__init__(layer_header, data) -cdef class SMXShadowLayer: - pass +cdef class SMXShadowLayer(SMXLayer): + def __init__(self, layer_header, data): + super().__init__(layer_header, data) ctypedef fused SMXLayerVariant: From b5427b57667a46223372946e5dc5f1a17f2ff0af Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Fri, 27 Dec 2024 22:42:20 +0000 Subject: [PATCH 080/152] smp fused --- .../convert/value_object/read/media/smp.pyx | 499 ++++++------------ 1 file changed, 169 insertions(+), 330 deletions(-) diff --git a/openage/convert/value_object/read/media/smp.pyx b/openage/convert/value_object/read/media/smp.pyx index 49a2122175..9454bd9c80 100644 --- a/openage/convert/value_object/read/media/smp.pyx +++ b/openage/convert/value_object/read/media/smp.pyx @@ -253,6 +253,170 @@ class SMPLayerHeader: ) return "".join(ret) + +cdef class SMPMainLayer(SMPLayer): + def __init__(self, layer_header, data): + super().__init__(layer_header, data) + + def get_damage_mask(self): + """ + Convert the 4th pixel byte to a mask used for damaged units. + """ + return determine_damage_matrix(self.pcolor) + +cdef class SMPShadowLayer(SMPLayer): + def __init__(self, layer_header, data): + super().__init__(layer_header, data) + +cdef class SMPOutlineLayer(SMPLayer): + def __init__(self, layer_header, data): + super().__init__(layer_header, data) + + +ctypedef fused SMPLayerVariant: + SMPLayer + SMPMainLayer + SMPShadowLayer + SMPOutlineLayer + + +@cython.boundscheck(False) +cdef void process_drawing_cmds(SMPLayerVariant variant, + const uint8_t[::1] &data_raw, + vector[pixel] &row_data, + Py_ssize_t rowid, + Py_ssize_t first_cmd_offset, + size_t expected_size): + + """ + extract colors (pixels) for the drawing commands + found for this row in the SMP frame. + """ + + # position in the data blob, we start at the first command of this row + cdef Py_ssize_t dpos = first_cmd_offset + + # is the end of the current row reached? + cdef bool eor = False + + cdef uint8_t cmd = 0 + cdef uint8_t nextbyte = 0 + cdef uint8_t lower_crumb = 0 + cdef int pixel_count = 0 + + cdef vector[uint8_t] pixel_data + pixel_data.reserve(4) + + # work through commands till end of row. + while not eor: + if row_data.size() > expected_size: + raise Exception( + f"Only {expected_size:d} pixels should be drawn in row {rowid:d} " + + f"with layer type {variant.info.layer_type:#x}, but we have {row_data.size():d}" + ) + + # fetch drawing instruction + cmd = data_raw[dpos] + + # Last 2 bits store command type + lower_crumb = 0b00000011 & cmd + + # opcode: cmd, rowid: rowid + + if lower_crumb == 0b00000011: + # eol (end of line) command, this row is finished now. + eor = True + + if variant is SMPShadowLayer and row_data.size() < expected_size: + # copy the last drawn pixel + # (still stored in nextbyte) + # + # TODO: confirm that this is the + # right way to do it + row_data.push_back(pixel(color_shadow, nextbyte, 0, 0, 0)) + + continue + + elif lower_crumb == 0b00000000: + # skip command + # draw 'count' transparent pixels + # count = (cmd >> 2) + 1 + + pixel_count = (cmd >> 2) + 1 + + for _ in range(pixel_count): + row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) + + elif lower_crumb == 0b00000001: + # color_list command + # draw the following 'count' pixels + # pixels are stored as 4 byte palette and meta infos + # count = (cmd >> 2) + 1 + + pixel_count = (cmd >> 2) + 1 + + for _ in range(pixel_count): + if variant is SMPShadowLayer: + dpos += 1 + nextbyte = data_raw[dpos] + row_data.push_back(pixel(color_shadow, + nextbyte, 0, 0, 0)) + elif variant is SMPOutlineLayer: + # we don't know the color the game wants + # so we just draw index 0 + row_data.push_back(pixel(color_outline, + 0, 0, 0, 0)) + elif variant is SMPMainLayer: + for _ in range(4): + dpos += 1 + pixel_data.push_back(data_raw[dpos]) + + row_data.push_back(pixel(color_standard, + pixel_data[0], + pixel_data[1], + pixel_data[2], + pixel_data[3] & 0x1F)) # remove "usage" bit here + pixel_data.clear() + + elif lower_crumb == 0b00000010 and variant in (SMPMainLayer, SMPShadowLayer): + # player_color command + # draw the following 'count' pixels + # pixels are stored as 4 byte palette and meta infos + # count = (cmd >> 2) + 1 + + pixel_count = (cmd >> 2) + 1 + + for _ in range(pixel_count): + if variant is SMPShadowLayer: + dpos += 1 + nextbyte = data_raw[dpos] + row_data.push_back(pixel(color_shadow, + nextbyte, 0, 0, 0)) + else: + for _ in range(4): + dpos += 1 + pixel_data.push_back(data_raw[dpos]) + + row_data.push_back(pixel(color_player, + pixel_data[0], + pixel_data[1], + pixel_data[2], + pixel_data[3] & 0x1F)) # remove "usage" bit here + pixel_data.clear() + + else: + raise Exception( + f"unknown smp {variant.info.layer_type} layer drawing command: " + + f"{cmd:#x} in row {rowid:d}" + ) + + # process next command + dpos += 1 + + # end of row reached, return the created pixel array. + return + + cdef class SMPLayer: """ one layer inside the SMP. you can imagine it as a frame of a video. @@ -357,10 +521,11 @@ cdef class SMPLayer: row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) # process the drawing commands for this row. - self.process_drawing_cmds(data_raw, - row_data, rowid, - first_cmd_offset, - pixel_count - bounds.right) + process_drawing_cmds(self, + data_raw, + row_data, rowid, + first_cmd_offset, + pixel_count - bounds.right) # finish by filling up the right transparent space for i in range(bounds.right): @@ -383,15 +548,6 @@ cdef class SMPLayer: return row_data - @cython.boundscheck(False) - cdef void process_drawing_cmds(self, - const uint8_t[::1] &data_raw, - vector[pixel] &row_data, - Py_ssize_t rowid, - Py_ssize_t first_cmd_offset, - size_t expected_size): - pass - def get_picture_data(self, palette): """ Convert the palette index matrix to a colored image. @@ -417,323 +573,6 @@ cdef class SMPLayer: return repr(self.info) -cdef class SMPMainLayer(SMPLayer): - """ - SMPLayer for the main graphics sprite. - """ - - def __init__(self, layer_header, data): - super().__init__(layer_header, data) - - @cython.boundscheck(False) - cdef void process_drawing_cmds(self, - const uint8_t[::1] &data_raw, - vector[pixel] &row_data, - Py_ssize_t rowid, - Py_ssize_t first_cmd_offset, - size_t expected_size): - """ - extract colors (pixels) for the drawing commands - found for this row in the SMP frame. - """ - - # position in the data blob, we start at the first command of this row - cdef Py_ssize_t dpos = first_cmd_offset - - # is the end of the current row reached? - cdef bool eor = False - - cdef uint8_t cmd - cdef uint8_t nextbyte - cdef uint8_t lower_crumb - cdef int pixel_count - - cdef vector[uint8_t] pixel_data - pixel_data.reserve(4) - - # work through commands till end of row. - while not eor: - if row_data.size() > expected_size: - raise Exception( - f"Only {expected_size:d} pixels should be drawn in row {rowid:d} " + - f"with layer type {self.info.layer_type:#x}, but we have {row_data.size():d}" - ) - - # fetch drawing instruction - cmd = data_raw[dpos] - - # Last 2 bits store command type - lower_crumb = 0b00000011 & cmd - - # opcode: cmd, rowid: rowid - - if lower_crumb == 0b00000011: - # eol (end of line) command, this row is finished now. - eor = True - - continue - - elif lower_crumb == 0b00000000: - # skip command - # draw 'count' transparent pixels - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - for _ in range(pixel_count): - row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) - - elif lower_crumb == 0b00000001: - # color_list command - # draw the following 'count' pixels - # pixels are stored as 4 byte palette and meta infos - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - for _ in range(pixel_count): - for _ in range(4): - dpos += 1 - pixel_data.push_back(data_raw[dpos]) - - row_data.push_back(pixel(color_standard, - pixel_data[0], - pixel_data[1], - pixel_data[2], - pixel_data[3] & 0x1F)) # remove "usage" bit here - - pixel_data.clear() - - elif lower_crumb == 0b00000010: - # player_color command - # draw the following 'count' pixels - # pixels are stored as 4 byte palette and meta infos - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - for _ in range(pixel_count): - for _ in range(4): - dpos += 1 - pixel_data.push_back(data_raw[dpos]) - - row_data.push_back(pixel(color_player, - pixel_data[0], - pixel_data[1], - pixel_data[2], - pixel_data[3] & 0x1F)) # remove "usage" bit here - - pixel_data.clear() - - else: - raise Exception( - f"unknown smp main graphics layer drawing command: " + - f"{cmd:#x} in row {rowid:d}" - ) - - # process next command - dpos += 1 - - # end of row reached, return the created pixel array. - return - - def get_damage_mask(self): - """ - Convert the 4th pixel byte to a mask used for damaged units. - """ - return determine_damage_matrix(self.pcolor) - - -cdef class SMPShadowLayer(SMPLayer): - """ - SMPLayer for the shadow graphics. - """ - - def __init__(self, layer_header, data): - super().__init__(layer_header, data) - - @cython.boundscheck(False) - cdef void process_drawing_cmds(self, - const uint8_t[::1] &data_raw, - vector[pixel] &row_data, - Py_ssize_t rowid, - Py_ssize_t first_cmd_offset, - size_t expected_size): - """ - extract colors (pixels) for the drawing commands - found for this row in the SMP frame. - """ - - # position in the data blob, we start at the first command of this row - cdef Py_ssize_t dpos = first_cmd_offset - - # is the end of the current row reached? - cdef bool eor = False - - cdef uint8_t cmd - cdef uint8_t nextbyte - cdef uint8_t lower_crumb - cdef int pixel_count - - # work through commands till end of row. - while not eor: - if row_data.size() > expected_size: - raise Exception( - f"Only {expected_size:d} pixels should be drawn in row {rowid:d} " + - f"with layer type {self.info.layer_type:#x}, but we have {row_data.size():d} " + - f"already!" - ) - - # fetch drawing instruction - cmd = data_raw[dpos] - - # Last 2 bits store command type - lower_crumb = 0b00000011 & cmd - - # opcode: cmd, rowid: rowid - - if lower_crumb == 0b00000011: - # eol (end of line) command, this row is finished now. - eor = True - - # shadows sometimes need an extra pixel at - # the end - if row_data.size() < expected_size: - # copy the last drawn pixel - # (still stored in nextbyte) - # - # TODO: confirm that this is the - # right way to do it - row_data.push_back(pixel(color_shadow, - nextbyte, 0, 0, 0)) - - continue - - elif lower_crumb == 0b00000000: - # skip command - # draw 'count' transparent pixels - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - for _ in range(pixel_count): - row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) - - elif lower_crumb == 0b00000001: - # color_list command - # draw the following 'count' pixels - # pixels are stored as 1 byte alpha values - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - for _ in range(pixel_count): - - dpos += 1 - nextbyte = data_raw[dpos] - - row_data.push_back(pixel(color_shadow, - nextbyte, 0, 0, 0)) - - else: - raise Exception( - f"unknown smp shadow layer drawing command: " + - f"{cmd:#x} in row {rowid:d}") - - # process next command - dpos += 1 - - # end of row reached, return the created pixel array. - return - - -cdef class SMPOutlineLayer(SMPLayer): - """ - SMPLayer for the outline graphics. - """ - - def __init__(self, layer_header, data): - super().__init__(layer_header, data) - - @cython.boundscheck(False) - cdef void process_drawing_cmds(self, - const uint8_t[::1] &data_raw, - vector[pixel] &row_data, - Py_ssize_t rowid, - Py_ssize_t first_cmd_offset, - size_t expected_size): - """ - extract colors (pixels) for the drawing commands - found for this row in the SMP frame. - """ - - # position in the data blob, we start at the first command of this row - cdef Py_ssize_t dpos = first_cmd_offset - - # is the end of the current row reached? - cdef bool eor = False - - cdef uint8_t cmd - cdef uint8_t nextbyte - cdef uint8_t lower_crumb - cdef int pixel_count - - # work through commands till end of row. - while not eor: - if row_data.size() > expected_size: - raise Exception( - f"Only {expected_size:d} pixels should be drawn in row {rowid:d} " + - f"with layer type {self.info.layer_type:#x}, but we have {row_data.size():d} " - f"already!" - ) - - # fetch drawing instruction - cmd = data_raw[dpos] - - # Last 2 bits store command type - lower_crumb = 0b00000011 & cmd - - # opcode: cmd, rowid: rowid - - if lower_crumb == 0b00000011: - # eol (end of line) command, this row is finished now. - eor = True - - continue - - elif lower_crumb == 0b00000000: - # skip command - # draw 'count' transparent pixels - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - for _ in range(pixel_count): - row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) - - elif lower_crumb == 0b00000001: - # color_list command - # draw the following 'count' pixels - # as player outline colors. - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - for _ in range(pixel_count): - # we don't know the color the game wants - # so we just draw index 0 - row_data.push_back(pixel(color_outline, - 0, 0, 0, 0)) - - else: - raise Exception( - f"unknown smp outline layer drawing command: " + - f"{cmd:#x} in row {rowid:d}") - # process next command - dpos += 1 - - # end of row reached, return the created pixel array. - return @cython.boundscheck(False) From 536c6b8a30e862926709f6081822c19658f80f03 Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Fri, 27 Dec 2024 22:42:44 +0000 Subject: [PATCH 081/152] formatting --- .../convert/value_object/read/media/smp.pyx | 71 +++++++++---------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/openage/convert/value_object/read/media/smp.pyx b/openage/convert/value_object/read/media/smp.pyx index 9454bd9c80..942ac8e028 100644 --- a/openage/convert/value_object/read/media/smp.pyx +++ b/openage/convert/value_object/read/media/smp.pyx @@ -1,7 +1,10 @@ -# Copyright 2013-2023 the openage authors. See copying.md for legal info. +# Copyright 2013-2024 the openage authors. See copying.md for legal info. # # cython: infer_types=True +from libcpp.vector cimport vector +from libcpp cimport bool +from libc.stdint cimport uint8_t, uint16_t from enum import Enum import numpy from struct import Struct, unpack_from @@ -12,11 +15,6 @@ from .....log import spam, dbg cimport cython cimport numpy -from libc.stdint cimport uint8_t, uint16_t -from libcpp cimport bool -from libcpp.vector cimport vector - - # SMP files have little endian byte order endianness = "< " @@ -50,8 +48,8 @@ class SMPLayerType(Enum): """ SMP layer types. """ - MAIN = "main" - SHADOW = "shadow" + MAIN = "main" + SHADOW = "shadow" OUTLINE = "outline" @@ -106,7 +104,7 @@ class SMP: def __init__(self, data): smp_header = SMP.smp_header.unpack_from(data) - signature, version, frame_count, facet_count, frames_per_facet,\ + signature, version, frame_count, facet_count, frames_per_facet, \ checksum, file_size, source_format, comment = smp_header dbg("SMP") @@ -159,12 +157,14 @@ class SMP: elif layer_header.layer_type == 0x04: # layer that stores a shadow - self.shadow_frames.append(SMPShadowLayer(layer_header, data)) + self.shadow_frames.append( + SMPShadowLayer(layer_header, data)) elif layer_header.layer_type == 0x08 or \ - layer_header.layer_type == 0x10: + layer_header.layer_type == 0x10: # layer that stores an outline - self.outline_frames.append(SMPOutlineLayer(layer_header, data)) + self.outline_frames.append( + SMPOutlineLayer(layer_header, data)) else: raise Exception( @@ -282,11 +282,11 @@ ctypedef fused SMPLayerVariant: @cython.boundscheck(False) cdef void process_drawing_cmds(SMPLayerVariant variant, - const uint8_t[::1] &data_raw, - vector[pixel] &row_data, - Py_ssize_t rowid, - Py_ssize_t first_cmd_offset, - size_t expected_size): + const uint8_t[::1] & data_raw, + vector[pixel] & row_data, + Py_ssize_t rowid, + Py_ssize_t first_cmd_offset, + size_t expected_size): """ extract colors (pixels) for the drawing commands @@ -360,22 +360,22 @@ cdef void process_drawing_cmds(SMPLayerVariant variant, dpos += 1 nextbyte = data_raw[dpos] row_data.push_back(pixel(color_shadow, - nextbyte, 0, 0, 0)) + nextbyte, 0, 0, 0)) elif variant is SMPOutlineLayer: # we don't know the color the game wants # so we just draw index 0 row_data.push_back(pixel(color_outline, - 0, 0, 0, 0)) + 0, 0, 0, 0)) elif variant is SMPMainLayer: for _ in range(4): dpos += 1 pixel_data.push_back(data_raw[dpos]) row_data.push_back(pixel(color_standard, - pixel_data[0], - pixel_data[1], - pixel_data[2], - pixel_data[3] & 0x1F)) # remove "usage" bit here + pixel_data[0], + pixel_data[1], + pixel_data[2], + pixel_data[3] & 0x1F)) # remove "usage" bit here pixel_data.clear() elif lower_crumb == 0b00000010 and variant in (SMPMainLayer, SMPShadowLayer): @@ -391,17 +391,17 @@ cdef void process_drawing_cmds(SMPLayerVariant variant, dpos += 1 nextbyte = data_raw[dpos] row_data.push_back(pixel(color_shadow, - nextbyte, 0, 0, 0)) + nextbyte, 0, 0, 0)) else: for _ in range(4): dpos += 1 pixel_data.push_back(data_raw[dpos]) row_data.push_back(pixel(color_player, - pixel_data[0], - pixel_data[1], - pixel_data[2], - pixel_data[3] & 0x1F)) # remove "usage" bit here + pixel_data[0], + pixel_data[1], + pixel_data[2], + pixel_data[3] & 0x1F)) # remove "usage" bit here pixel_data.clear() else: @@ -493,7 +493,7 @@ cdef class SMPLayer: self.pcolor.push_back(self.create_color_row(data_raw, i)) cdef vector[pixel] create_color_row(self, - const uint8_t[::1] &data_raw, + const uint8_t[::1] & data_raw, Py_ssize_t rowid): """ extract colors (pixels) for the given rowid. @@ -573,12 +573,10 @@ cdef class SMPLayer: return repr(self.info) - - @cython.boundscheck(False) @cython.wraparound(False) -cdef numpy.ndarray determine_rgba_matrix(vector[vector[pixel]] &image_matrix, - numpy.ndarray[numpy.uint8_t, ndim=2, mode="c"] palette): +cdef numpy.ndarray determine_rgba_matrix(vector[vector[pixel]] & image_matrix, + numpy.ndarray[numpy.uint8_t, ndim = 2, mode = "c"] palette): """ converts a palette index image matrix to an rgba matrix. """ @@ -586,7 +584,7 @@ cdef numpy.ndarray determine_rgba_matrix(vector[vector[pixel]] &image_matrix, cdef size_t height = image_matrix.size() cdef size_t width = image_matrix[0].size() - cdef numpy.ndarray[numpy.uint8_t, ndim=3, mode="c"] array_data = \ + cdef numpy.ndarray[numpy.uint8_t, ndim = 3, mode = "c"] array_data = \ numpy.zeros((height, width, 4), dtype=numpy.uint8) cdef uint8_t[:, ::1] m_lookup = palette @@ -681,9 +679,10 @@ cdef numpy.ndarray determine_rgba_matrix(vector[vector[pixel]] &image_matrix, return array_data + @cython.boundscheck(False) @cython.wraparound(False) -cdef numpy.ndarray determine_damage_matrix(vector[vector[pixel]] &image_matrix): +cdef numpy.ndarray determine_damage_matrix(vector[vector[pixel]] & image_matrix): """ converts the damage modifier values to an image using the RG values. @@ -693,7 +692,7 @@ cdef numpy.ndarray determine_damage_matrix(vector[vector[pixel]] &image_matrix): cdef size_t height = image_matrix.size() cdef size_t width = image_matrix[0].size() - cdef numpy.ndarray[numpy.uint8_t, ndim=3, mode="c"] array_data = \ + cdef numpy.ndarray[numpy.uint8_t, ndim = 3, mode = "c"] array_data = \ numpy.zeros((height, width, 4), dtype=numpy.uint8) cdef uint8_t r From 11e2177984e99da3f97274934bf48a90fde89609 Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Mon, 30 Dec 2024 02:11:25 +0000 Subject: [PATCH 082/152] adjustments --- .../convert/value_object/read/media/smp.pyx | 162 +++++++------ .../convert/value_object/read/media/smx.pyx | 222 ++++++++++-------- 2 files changed, 211 insertions(+), 173 deletions(-) diff --git a/openage/convert/value_object/read/media/smp.pyx b/openage/convert/value_object/read/media/smp.pyx index 942ac8e028..80d2844fce 100644 --- a/openage/convert/value_object/read/media/smp.pyx +++ b/openage/convert/value_object/read/media/smp.pyx @@ -258,6 +258,9 @@ cdef class SMPMainLayer(SMPLayer): def __init__(self, layer_header, data): super().__init__(layer_header, data) + for i in range(self.row_count): + self.pcolor.push_back(create_color_row(self, i)) + def get_damage_mask(self): """ Convert the 4th pixel byte to a mask used for damaged units. @@ -268,18 +271,78 @@ cdef class SMPShadowLayer(SMPLayer): def __init__(self, layer_header, data): super().__init__(layer_header, data) + for i in range(self.row_count): + self.pcolor.push_back(create_color_row(self, i)) + cdef class SMPOutlineLayer(SMPLayer): def __init__(self, layer_header, data): super().__init__(layer_header, data) + for i in range(self.row_count): + self.pcolor.push_back(create_color_row(self, i)) + ctypedef fused SMPLayerVariant: - SMPLayer SMPMainLayer SMPShadowLayer SMPOutlineLayer +@cython.boundscheck(False) +cdef vector[pixel] create_color_row(SMPLayerVariant variant, + Py_ssize_t rowid): + """ + extract colors (pixels) for the given rowid. + """ + cdef vector[pixel] row_data + cdef Py_ssize_t i + + cdef Py_ssize_t first_cmd_offset = variant.cmd_offsets[rowid] # redudent + cdef boundary_def bounds = variant.boundaries[rowid] + cdef size_t pixel_count = variant.info.size[0] + + # preallocate memory + row_data.reserve(pixel_count) + + # row is completely transparent + if bounds.full_row: + for _ in range(pixel_count): + row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) + + return row_data + + # start drawing the left transparent space + for i in range(bounds.left): + row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) + + # process the drawing commands for this row. + process_drawing_cmds(variant, variant.data_raw, + row_data, rowid, + first_cmd_offset, + pixel_count - bounds.right) + + # finish by filling up the right transparent space + for i in range(bounds.right): + row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) + + # verify size of generated row + if row_data.size() != pixel_count: + got = row_data.size() + summary = ( + f"{got:d}/{pixel_count:d} -> row {rowid:d}, " + f"layer type {variant.info.layer_type:x}, " + f"offset {first_cmd_offset:d} / {first_cmd_offset:#x}" + ) + message = ( + f"got {'LESS' if got < pixel_count else 'MORE'} pixels than expected: {summary}, " + f"missing: {abs(pixel_count - got):d}" + ) + + raise Exception(message) + + return row_data + + @cython.boundscheck(False) cdef void process_drawing_cmds(SMPLayerVariant variant, const uint8_t[::1] & data_raw, @@ -312,7 +375,7 @@ cdef void process_drawing_cmds(SMPLayerVariant variant, if row_data.size() > expected_size: raise Exception( f"Only {expected_size:d} pixels should be drawn in row {rowid:d} " + - f"with layer type {variant.info.layer_type:#x}, but we have {row_data.size():d}" + f"with layer type {variant.info.layer_type:#x}, but we have {row_data.size():d} already!" ) # fetch drawing instruction @@ -320,14 +383,13 @@ cdef void process_drawing_cmds(SMPLayerVariant variant, # Last 2 bits store command type lower_crumb = 0b00000011 & cmd - # opcode: cmd, rowid: rowid if lower_crumb == 0b00000011: # eol (end of line) command, this row is finished now. eor = True - if variant is SMPShadowLayer and row_data.size() < expected_size: + if SMPLayerVariant is SMPShadowLayer and row_data.size() < expected_size: # copy the last drawn pixel # (still stored in nextbyte) # @@ -356,17 +418,17 @@ cdef void process_drawing_cmds(SMPLayerVariant variant, pixel_count = (cmd >> 2) + 1 for _ in range(pixel_count): - if variant is SMPShadowLayer: + if SMPLayerVariant is SMPShadowLayer: dpos += 1 nextbyte = data_raw[dpos] row_data.push_back(pixel(color_shadow, nextbyte, 0, 0, 0)) - elif variant is SMPOutlineLayer: + elif SMPLayerVariant is SMPOutlineLayer: # we don't know the color the game wants # so we just draw index 0 row_data.push_back(pixel(color_outline, 0, 0, 0, 0)) - elif variant is SMPMainLayer: + elif SMPLayerVariant is SMPMainLayer: for _ in range(4): dpos += 1 pixel_data.push_back(data_raw[dpos]) @@ -378,7 +440,7 @@ cdef void process_drawing_cmds(SMPLayerVariant variant, pixel_data[3] & 0x1F)) # remove "usage" bit here pixel_data.clear() - elif lower_crumb == 0b00000010 and variant in (SMPMainLayer, SMPShadowLayer): + elif lower_crumb == 0b00000010 and (SMPLayerVariant is SMPMainLayer or SMPLayerVariant is SMPShadowLayer): # player_color command # draw the following 'count' pixels # pixels are stored as 4 byte palette and meta infos @@ -387,7 +449,7 @@ cdef void process_drawing_cmds(SMPLayerVariant variant, pixel_count = (cmd >> 2) + 1 for _ in range(pixel_count): - if variant is SMPShadowLayer: + if SMPLayerVariant is SMPShadowLayer: dpos += 1 nextbyte = data_raw[dpos] row_data.push_back(pixel(color_shadow, @@ -446,15 +508,20 @@ cdef class SMPLayer: # pixel matrix representing the final image cdef vector[vector[pixel]] pcolor + # memory pointer + cdef const uint8_t[::1] data_raw + + # rows of image + cdef size_t row_count + def __init__(self, layer_header, data): self.info = layer_header if not (isinstance(data, bytes) or isinstance(data, bytearray)): raise ValueError("Layer data must be some bytes object") - # memory pointer # convert the bytes obj to char* - cdef const uint8_t[::1] data_raw = data + self.data_raw = data cdef unsigned short left cdef unsigned short right @@ -462,11 +529,11 @@ cdef class SMPLayer: cdef size_t i cdef int cmd_offset - cdef size_t row_count = self.info.size[1] - self.pcolor.reserve(row_count) + self.row_count = self.info.size[1] + self.pcolor.reserve(self.row_count) # process bondary table - for i in range(row_count): + for i in range(self.row_count): outline_entry_position = (self.info.outline_table_offset + i * SMPLayer.smp_frame_row_edge.size) @@ -481,7 +548,7 @@ cdef class SMPLayer: self.boundaries.push_back(boundary_def(left, right, False)) # process cmd table - for i in range(row_count): + for i in range(self.row_count): cmd_table_position = (self.info.qdl_table_offset + i * SMPLayer.smp_command_offset.size) @@ -489,65 +556,6 @@ cdef class SMPLayer: data, cmd_table_position)[0] + self.info.frame_offset self.cmd_offsets.push_back(cmd_offset) - for i in range(row_count): - self.pcolor.push_back(self.create_color_row(data_raw, i)) - - cdef vector[pixel] create_color_row(self, - const uint8_t[::1] & data_raw, - Py_ssize_t rowid): - """ - extract colors (pixels) for the given rowid. - """ - - cdef vector[pixel] row_data - cdef Py_ssize_t i - - first_cmd_offset = self.cmd_offsets[rowid] - cdef boundary_def bounds = self.boundaries[rowid] - cdef size_t pixel_count = self.info.size[0] - - # preallocate memory - row_data.reserve(pixel_count) - - # row is completely transparent - if bounds.full_row: - for _ in range(pixel_count): - row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) - - return row_data - - # start drawing the left transparent space - for i in range(bounds.left): - row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) - - # process the drawing commands for this row. - process_drawing_cmds(self, - data_raw, - row_data, rowid, - first_cmd_offset, - pixel_count - bounds.right) - - # finish by filling up the right transparent space - for i in range(bounds.right): - row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) - - # verify size of generated row - if row_data.size() != pixel_count: - got = row_data.size() - summary = ( - f"{got:d}/{pixel_count:d} -> row {rowid:d}, " - f"layer type {self.info.layer_type:x}, " - f"offset {first_cmd_offset:d} / {first_cmd_offset:#x}" - ) - message = ( - f"got {'LESS' if got < pixel_count else 'MORE'} pixels than expected: {summary}, " - f"missing: {abs(pixel_count - got):d}" - ) - - raise Exception(message) - - return row_data - def get_picture_data(self, palette): """ Convert the palette index matrix to a colored image. @@ -576,7 +584,7 @@ cdef class SMPLayer: @cython.boundscheck(False) @cython.wraparound(False) cdef numpy.ndarray determine_rgba_matrix(vector[vector[pixel]] & image_matrix, - numpy.ndarray[numpy.uint8_t, ndim = 2, mode = "c"] palette): + numpy.ndarray[numpy.uint8_t, ndim= 2, mode = "c"] palette): """ converts a palette index image matrix to an rgba matrix. """ @@ -584,7 +592,7 @@ cdef numpy.ndarray determine_rgba_matrix(vector[vector[pixel]] & image_matrix, cdef size_t height = image_matrix.size() cdef size_t width = image_matrix[0].size() - cdef numpy.ndarray[numpy.uint8_t, ndim = 3, mode = "c"] array_data = \ + cdef numpy.ndarray[numpy.uint8_t, ndim= 3, mode = "c"] array_data = \ numpy.zeros((height, width, 4), dtype=numpy.uint8) cdef uint8_t[:, ::1] m_lookup = palette @@ -692,7 +700,7 @@ cdef numpy.ndarray determine_damage_matrix(vector[vector[pixel]] & image_matrix) cdef size_t height = image_matrix.size() cdef size_t width = image_matrix[0].size() - cdef numpy.ndarray[numpy.uint8_t, ndim = 3, mode = "c"] array_data = \ + cdef numpy.ndarray[numpy.uint8_t, ndim= 3, mode = "c"] array_data = \ numpy.zeros((height, width, 4), dtype=numpy.uint8) cdef uint8_t r diff --git a/openage/convert/value_object/read/media/smx.pyx b/openage/convert/value_object/read/media/smx.pyx index 2cb4749091..fd12a20ee7 100644 --- a/openage/convert/value_object/read/media/smx.pyx +++ b/openage/convert/value_object/read/media/smx.pyx @@ -60,28 +60,116 @@ cdef public dict LAYER_TYPES = { cdef class SMXMainLayer8to5(SMXLayer): def __init__(self, layer_header, data): super().__init__(layer_header, data) + # process cmd table + for i in range(self.row_count): + cmd_offset, color_offset, chunk_pos, row_data = create_color_row( + self, i) + + self.pcolor.push_back(row_data) cdef class SMXMainLayer4plus1(SMXLayer): def __init__(self, layer_header, data): super().__init__(layer_header, data) + # process cmd table + for i in range(self.row_count): + cmd_offset, color_offset, chunk_pos, row_data = create_color_row( + self, i) + + self.pcolor.push_back(row_data) cdef class SMXOutlineLayer(SMXLayer): def __init__(self, layer_header, data): super().__init__(layer_header, data) + # process cmd table + for i in range(self.row_count): + cmd_offset, color_offset, chunk_pos, row_data = create_color_row( + self, i) + + self.pcolor.push_back(row_data) cdef class SMXShadowLayer(SMXLayer): def __init__(self, layer_header, data): super().__init__(layer_header, data) + # process cmd table + for i in range(self.row_count): + cmd_offset, color_offset, chunk_pos, row_data = create_color_row( + self, i) + + self.pcolor.push_back(row_data) ctypedef fused SMXLayerVariant: - SMXLayer SMXMainLayer8to5 SMXMainLayer4plus1 SMXOutlineLayer SMXShadowLayer +cdef inline(int, int, int, vector[pixel]) create_color_row(SMXLayerVariant variant, + Py_ssize_t rowid): + """ + Extract colors (pixels) for the given rowid. + + :param rowid: Index of the current row in the layer. + :param cmd_offset: Offset of the command table of the layer. + :param color_offset: Offset of the color table of the layer. + :param chunk_pos: Current position in the compressed chunk. + """ + + cdef vector[pixel] row_data + cdef Py_ssize_t i + + cdef int first_cmd_offset = variant.cmd_offset + cdef int first_color_offset = variant.color_offset + cdef int first_chunk_pos = variant.chunk_pos + cdef boundary_def bounds = variant.boundaries[rowid] + cdef size_t pixel_count = variant.info.size[0] + + # preallocate memory + row_data.reserve(pixel_count) + + # row is completely transparent + if bounds.full_row: + for _ in range(pixel_count): + row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) + + return variant.cmd_offset, variant.color_offset, variant.chunk_pos, row_data + + # start drawing the left transparent space + for i in range(bounds.left): + row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) + + # process the drawing commands for this row. + next_cmd_offset, next_color_offset, next_chunk_pos, row_data = \ + process_drawing_cmds(variant, + variant.data_raw, + row_data, + rowid, + first_cmd_offset, + first_color_offset, + first_chunk_pos, + pixel_count - bounds.right + ) + + # finish by filling up the right transparent space + for i in range(bounds.right): + row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) + + # verify size of generated row + if row_data.size() != pixel_count: + got = row_data.size() + summary = "%d/%d -> row %d, layer type %s, offset %d / %#x" % ( + got, pixel_count, rowid, variant.info.layer_type, + first_cmd_offset, first_cmd_offset + ) + txt = "got %%s pixels than expected: %s, missing: %d" % ( + summary, abs(pixel_count - got)) + + raise Exception(txt % ("LESS" if got < pixel_count else "MORE")) + + return next_cmd_offset, next_color_offset, next_chunk_pos, row_data + + @cython.boundscheck(False) cdef inline(int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant variant, const uint8_t[::1] & data_raw, @@ -117,7 +205,9 @@ cdef inline(int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant v cdef uint8_t pixel_mask_even_2 = 0b11110000 cdef uint8_t pixel_mask_even_3 = 0b00111111 - if variant in (SMXMainLayer8to5, SMXMainLayer4plus1): + print(type(variant)) + + if SMXLayerVariant is SMXMainLayer8to5 or SMXLayerVariant is SMXMainLayer4plus1: # Position in the pixel data array dpos_color = first_color_offset @@ -139,13 +229,14 @@ cdef inline(int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant v # Last 2 bits store command type lower_crumb = 0b00000011 & cmd + print(lower_crumb) if lower_crumb == 0b00000011: # eor (end of row) command, this row is finished now. eor = True dpos_cmd += 1 - if variant is SMXShadowLayer: + if SMXLayerVariant is SMXShadowLayer: # shadows sometimes need an extra pixel at # the end if row_data.size() < expected_size: @@ -179,7 +270,7 @@ cdef inline(int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant v pixel_count = (cmd >> 2) + 1 - if variant is SMXMainLayer8to5: + if SMXLayerVariant is SMXMainLayer8to5: pixel_data.reserve(4) for _ in range(pixel_count): # Start fetching pixel data @@ -229,7 +320,7 @@ cdef inline(int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant v odd = not odd pixel_data.clear() - if variant is SMXMainLayer4plus1: + if SMXLayerVariant is SMXMainLayer4plus1: palette_section_block = data_raw[dpos_color + (4 - chunk_pos)] for _ in range(pixel_count): @@ -251,7 +342,7 @@ cdef inline(int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant v dpos_color += 1 # Skip palette section block palette_section_block = data_raw[dpos_color + 4] - if variant is SMXShadowLayer: + if SMXLayerVariant is SMXShadowLayer: for _ in range(pixel_count): dpos_color += 1 nextbyte = data_raw[dpos_color] @@ -259,14 +350,14 @@ cdef inline(int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant v row_data.push_back(pixel(color_shadow, nextbyte, 0, 0, 0)) - if variant is SMXOutlineLayer: + if SMXLayerVariant is SMXOutlineLayer: # we don't know the color the game wants # so we just draw index 0 row_data.push_back(pixel(color_outline, 0, 0, 0, 0)) elif lower_crumb == 0b00000010: - if variant is SMXMainLayer8to5: + if SMXLayerVariant is SMXMainLayer8to5: # player_color command # draw the following 'count' pixels # pixels are stored in 5 byte chunks @@ -327,7 +418,7 @@ cdef inline(int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant v pixel_data.clear() elif lower_crumb == 0b00000010: - if variant is SMXMainLayer4plus1: + if SMXLayerVariant is SMXMainLayer4plus1: # player_color command # draw the following 'count' pixels # 4 pixels are stored in every 5 byte chunk. @@ -356,7 +447,7 @@ cdef inline(int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant v dpos_color += 1 # Skip palette section block palette_section_block = data_raw[dpos_color + 4] - if variant is (SMXOutlineLayer, SMXShadowLayer): + if SMXLayerVariant is SMXOutlineLayer or SMXLayerVariant is SMXShadowLayer: pass else: @@ -368,9 +459,9 @@ cdef inline(int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant v # Process next command dpos_cmd += 1 - if variant in (SMXMainLayer8to5, SMXMainLayer4plus1): - return dpos_cmd, dpos_color, odd, row_data - if variant in (SMXOutlineLayer, SMXShadowLayer): + if SMXLayerVariant is SMXMainLayer8to5 or SMXLayerVariant is SMXMainLayer4plus1: + return dpos_cmd, dpos_color, chunk_pos, row_data + if SMXLayerVariant is SMXOutlineLayer or SMXLayerVariant is SMXShadowLayer: return dpos_cmd, dpos_cmd, chunk_pos, row_data @@ -640,6 +731,21 @@ cdef class SMXLayer: # pixel matrix representing the final image cdef vector[vector[pixel]] pcolor + # memory pointer + cdef const uint8_t[::1] data_raw + + # current command + cdef int cmd_offset + + # current color + cdef int color_offset + + # current chunk position + cdef int chunk_pos + + # rows + cdef size_t row_count + def __init__(self, layer_header, data): """ SMX layer definition superclass. There can be various types of @@ -655,19 +761,18 @@ cdef class SMXLayer: if not (isinstance(data, bytes) or isinstance(data, bytearray)): raise ValueError("Layer data must be some bytes object") - # memory pointer # convert the bytes obj to char* - cdef const uint8_t[::1] data_raw = data + self.data_raw = data cdef unsigned short left cdef unsigned short right cdef size_t i - cdef size_t row_count = self.info.size[1] - self.pcolor.reserve(row_count) + self.row_count = self.info.size[1] + self.pcolor.reserve(self.row_count) # process bondary table - for i in range(row_count): + for i in range(self.row_count): outline_entry_position = (self.info.outline_table_offset + i * SMXLayer.smp_layer_row_edge.size) @@ -681,84 +786,9 @@ cdef class SMXLayer: else: self.boundaries.push_back(boundary_def(left, right, False)) - cdef int cmd_offset = self.info.qdl_command_table_offset - cdef int color_offset = self.info.qdl_color_table_offset - cdef int chunk_pos = 0 - - # process cmd table - for i in range(row_count): - cmd_offset, color_offset, chunk_pos, row_data = \ - self.create_color_row( - data_raw, i, cmd_offset, color_offset, chunk_pos) - - self.pcolor.push_back(row_data) - - cdef inline(int, int, int, vector[pixel]) create_color_row(self, - const uint8_t[::1] & data_raw, - Py_ssize_t rowid, - int cmd_offset, - int color_offset, - int chunk_pos): - """ - Extract colors (pixels) for the given rowid. - - :param rowid: Index of the current row in the layer. - :param cmd_offset: Offset of the command table of the layer. - :param color_offset: Offset of the color table of the layer. - :param chunk_pos: Current position in the compressed chunk. - """ - - cdef vector[pixel] row_data - cdef Py_ssize_t i - - cdef int first_cmd_offset = cmd_offset - cdef int first_color_offset = color_offset - cdef boundary_def bounds = self.boundaries[rowid] - cdef size_t pixel_count = self.info.size[0] - - # preallocate memory - row_data.reserve(pixel_count) - - # row is completely transparent - if bounds.full_row: - for _ in range(pixel_count): - row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) - - return cmd_offset, color_offset, chunk_pos, row_data - - # start drawing the left transparent space - for i in range(bounds.left): - row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) - - # process the drawing commands for this row. - next_cmd_offset, next_color_offset, chunk_pos, row_data = \ - process_drawing_cmds(self, - data_raw, - row_data, - rowid, - first_cmd_offset, - first_color_offset, - chunk_pos, - pixel_count - bounds.right - ) - - # finish by filling up the right transparent space - for i in range(bounds.right): - row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) - - # verify size of generated row - if row_data.size() != pixel_count: - got = row_data.size() - summary = "%d/%d -> row %d, layer type %d, offset %d / %#x" % ( - got, pixel_count, rowid, self.info.layer_type, - first_cmd_offset, first_cmd_offset - ) - txt = "got %%s pixels than expected: %s, missing: %d" % ( - summary, abs(pixel_count - got)) - - raise Exception(txt % ("LESS" if got < pixel_count else "MORE")) - - return next_cmd_offset, next_color_offset, chunk_pos, row_data + self.cmd_offset = self.info.qdl_command_table_offset + self.color_offset = self.info.qdl_color_table_offset + self.chunk_pos = 0 def get_picture_data(self, palette): """ From 21af7181cb4593031f067d7861f7d8ceac6969b4 Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Mon, 30 Dec 2024 04:20:01 +0000 Subject: [PATCH 083/152] adjustments --- .../convert/value_object/read/media/smp.pyx | 2 +- .../convert/value_object/read/media/smx.pyx | 57 ++++++++++--------- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/openage/convert/value_object/read/media/smp.pyx b/openage/convert/value_object/read/media/smp.pyx index 80d2844fce..15cb55873e 100644 --- a/openage/convert/value_object/read/media/smp.pyx +++ b/openage/convert/value_object/read/media/smp.pyx @@ -297,7 +297,7 @@ cdef vector[pixel] create_color_row(SMPLayerVariant variant, cdef vector[pixel] row_data cdef Py_ssize_t i - cdef Py_ssize_t first_cmd_offset = variant.cmd_offsets[rowid] # redudent + cdef Py_ssize_t first_cmd_offset = variant.cmd_offsets[rowid] cdef boundary_def bounds = variant.boundaries[rowid] cdef size_t pixel_count = variant.info.size[0] diff --git a/openage/convert/value_object/read/media/smx.pyx b/openage/convert/value_object/read/media/smx.pyx index fd12a20ee7..a058c22399 100644 --- a/openage/convert/value_object/read/media/smx.pyx +++ b/openage/convert/value_object/read/media/smx.pyx @@ -1,4 +1,4 @@ -# Copyright 2019-2023 the openage authors. See copying.md for legal info. +# Copyright 2019-2024 the openage authors. See copying.md for legal info. # # cython: infer_types=True @@ -64,6 +64,9 @@ cdef class SMXMainLayer8to5(SMXLayer): for i in range(self.row_count): cmd_offset, color_offset, chunk_pos, row_data = create_color_row( self, i) + self.cmd_offset = cmd_offset + self.color_offset = color_offset + self.chunk_pos = chunk_pos self.pcolor.push_back(row_data) @@ -74,6 +77,9 @@ cdef class SMXMainLayer4plus1(SMXLayer): for i in range(self.row_count): cmd_offset, color_offset, chunk_pos, row_data = create_color_row( self, i) + self.cmd_offset = cmd_offset + self.color_offset = color_offset + self.chunk_pos = chunk_pos self.pcolor.push_back(row_data) @@ -84,6 +90,9 @@ cdef class SMXOutlineLayer(SMXLayer): for i in range(self.row_count): cmd_offset, color_offset, chunk_pos, row_data = create_color_row( self, i) + self.cmd_offset = cmd_offset + self.color_offset = color_offset + self.chunk_pos = chunk_pos self.pcolor.push_back(row_data) @@ -94,6 +103,9 @@ cdef class SMXShadowLayer(SMXLayer): for i in range(self.row_count): cmd_offset, color_offset, chunk_pos, row_data = create_color_row( self, i) + self.cmd_offset = cmd_offset + self.color_offset = color_offset + self.chunk_pos = chunk_pos self.pcolor.push_back(row_data) @@ -182,7 +194,6 @@ cdef inline(int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant v """ TODO: docstring """ - # position in the command array, we start at the first command of this row cdef Py_ssize_t dpos_cmd = first_cmd_offset @@ -205,8 +216,6 @@ cdef inline(int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant v cdef uint8_t pixel_mask_even_2 = 0b11110000 cdef uint8_t pixel_mask_even_3 = 0b00111111 - print(type(variant)) - if SMXLayerVariant is SMXMainLayer8to5 or SMXLayerVariant is SMXMainLayer4plus1: # Position in the pixel data array dpos_color = first_color_offset @@ -229,24 +238,22 @@ cdef inline(int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant v # Last 2 bits store command type lower_crumb = 0b00000011 & cmd - print(lower_crumb) - if lower_crumb == 0b00000011: # eor (end of row) command, this row is finished now. + eor = True dpos_cmd += 1 - if SMXLayerVariant is SMXShadowLayer: - # shadows sometimes need an extra pixel at - # the end - if row_data.size() < expected_size: - # copy the last drawn pixel - # (still stored in nextbyte) - # - # TODO: confirm that this is the - # right way to do it - row_data.push_back(pixel(color_shadow, - nextbyte, 0, 0, 0)) + # shadows sometimes need an extra pixel at + # the end + if SMXLayerVariant is SMXShadowLayer and row_data.size() < expected_size: + # copy the last drawn pixel + # (still stored in nextbyte) + # + # TODO: confirm that this is the + # right way to do it + row_data.push_back(pixel(color_shadow, + nextbyte, 0, 0, 0)) continue elif lower_crumb == 0b00000000: @@ -344,8 +351,8 @@ cdef inline(int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant v if SMXLayerVariant is SMXShadowLayer: for _ in range(pixel_count): - dpos_color += 1 - nextbyte = data_raw[dpos_color] + dpos_cmd += 1 + nextbyte = data_raw[dpos_cmd] row_data.push_back(pixel(color_shadow, nextbyte, 0, 0, 0)) @@ -417,8 +424,7 @@ cdef inline(int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant v odd = not odd pixel_data.clear() - elif lower_crumb == 0b00000010: - if SMXLayerVariant is SMXMainLayer4plus1: + elif SMXLayerVariant is SMXMainLayer4plus1: # player_color command # draw the following 'count' pixels # 4 pixels are stored in every 5 byte chunk. @@ -447,9 +453,6 @@ cdef inline(int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant v dpos_color += 1 # Skip palette section block palette_section_block = data_raw[dpos_color + 4] - if SMXLayerVariant is SMXOutlineLayer or SMXLayerVariant is SMXShadowLayer: - pass - else: raise Exception( f"unknown smx main graphics layer drawing command: " + @@ -826,7 +829,7 @@ cdef class SMXLayer: @cython.boundscheck(False) @cython.wraparound(False) cdef numpy.ndarray determine_rgba_matrix(vector[vector[pixel]] & image_matrix, - numpy.ndarray[numpy.uint8_t, ndim = 2, mode = "c"] palette): + numpy.ndarray[numpy.uint8_t, ndim= 2, mode = "c"] palette): """ converts a palette index image matrix to an rgba matrix. @@ -837,7 +840,7 @@ cdef numpy.ndarray determine_rgba_matrix(vector[vector[pixel]] & image_matrix, cdef size_t height = image_matrix.size() cdef size_t width = image_matrix[0].size() - cdef numpy.ndarray[numpy.uint8_t, ndim = 3, mode = "c"] array_data = \ + cdef numpy.ndarray[numpy.uint8_t, ndim= 3, mode = "c"] array_data = \ numpy.zeros((height, width, 4), dtype=numpy.uint8) cdef uint8_t[:, ::1] m_lookup = palette @@ -936,7 +939,7 @@ cdef numpy.ndarray determine_damage_matrix(vector[vector[pixel]] & image_matrix) cdef size_t height = image_matrix.size() cdef size_t width = image_matrix[0].size() - cdef numpy.ndarray[numpy.uint8_t, ndim = 3, mode = "c"] array_data = \ + cdef numpy.ndarray[numpy.uint8_t, ndim= 3, mode = "c"] array_data = \ numpy.zeros((height, width, 4), dtype=numpy.uint8) cdef uint8_t r = 0 From f8d35e074179b561245898596cb4620de09a2e87 Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Thu, 2 Jan 2025 16:19:12 +0000 Subject: [PATCH 084/152] copying.md --- .mailmap | 6 +++++- copying.md | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index 8a1a8727b1..3d8b8ec12e 100644 --- a/.mailmap +++ b/.mailmap @@ -21,4 +21,8 @@ Tobias Feldballe Jonas Borchelt Derek Frogget <114030121+derekfrogget@users.noreply.github.com> Nikhil Ghosh -David Wever <56411717+dmwever@users.noreply.github.com> \ No newline at end of file +<<<<<<< HEAD +David Wever <56411717+dmwever@users.noreply.github.com> +======= +Ngô Xuân Minh +>>>>>>> e8251ff6 (copying.md) diff --git a/copying.md b/copying.md index 53ced52632..f873817f29 100644 --- a/copying.md +++ b/copying.md @@ -160,6 +160,7 @@ _the openage authors_ are: | Alex Zhuohao He | ZzzhHe | zhuohao dawt he à outlook dawt com | | David Wever | dmwever | dmwever à crimson dawt ua dawt edu | | Michael Lynch | mtlynch | git à mtlynch dawt io | +| Ngô Xuân Minh | | xminh dawt ngo dawt 00 à gmail dawt com | If you're a first-time committer, add yourself to the above list. This is not just for legal reasons, but also to keep an overview of all those nicknames. From e6228a2e4326c5654e7968b74c1c4142da91e99b Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Thu, 9 Jan 2025 13:08:08 +0000 Subject: [PATCH 085/152] addressing comments --- .../convert/value_object/read/media/smp.pyx | 235 +++++++++--------- .../convert/value_object/read/media/smx.pyx | 182 +++++++------- 2 files changed, 211 insertions(+), 206 deletions(-) diff --git a/openage/convert/value_object/read/media/smp.pyx b/openage/convert/value_object/read/media/smp.pyx index 15cb55873e..c52aa1a194 100644 --- a/openage/convert/value_object/read/media/smp.pyx +++ b/openage/convert/value_object/read/media/smp.pyx @@ -1,10 +1,7 @@ -# Copyright 2013-2024 the openage authors. See copying.md for legal info. +# Copyright 2013-2025 the openage authors. See copying.md for legal info. # # cython: infer_types=True -from libcpp.vector cimport vector -from libcpp cimport bool -from libc.stdint cimport uint8_t, uint16_t from enum import Enum import numpy from struct import Struct, unpack_from @@ -15,6 +12,11 @@ from .....log import spam, dbg cimport cython cimport numpy +from libc.stdint cimport uint8_t, uint16_t +from libcpp cimport bool +from libcpp.vector cimport vector + + # SMP files have little endian byte order endianness = "< " @@ -48,8 +50,8 @@ class SMPLayerType(Enum): """ SMP layer types. """ - MAIN = "main" - SHADOW = "shadow" + MAIN = "main" + SHADOW = "shadow" OUTLINE = "outline" @@ -104,7 +106,7 @@ class SMP: def __init__(self, data): smp_header = SMP.smp_header.unpack_from(data) - signature, version, frame_count, facet_count, frames_per_facet, \ + signature, version, frame_count, facet_count, frames_per_facet,\ checksum, file_size, source_format, comment = smp_header dbg("SMP") @@ -157,14 +159,12 @@ class SMP: elif layer_header.layer_type == 0x04: # layer that stores a shadow - self.shadow_frames.append( - SMPShadowLayer(layer_header, data)) + self.shadow_frames.append(SMPShadowLayer(layer_header, data)) elif layer_header.layer_type == 0x08 or \ - layer_header.layer_type == 0x10: + layer_header.layer_type == 0x10: # layer that stores an outline - self.outline_frames.append( - SMPOutlineLayer(layer_header, data)) + self.outline_frames.append(SMPOutlineLayer(layer_header, data)) else: raise Exception( @@ -254,6 +254,108 @@ class SMPLayerHeader: return "".join(ret) +cdef class SMPLayer: + """ + one layer inside the SMP. you can imagine it as a frame of a video. + """ + + # struct smp_frame_row_edge { + # unsigned short left_space; + # unsigned short right_space; + # }; + smp_frame_row_edge = Struct(endianness + "H H") + + # struct smp_command_offset { + # unsigned int offset; + # } + smp_command_offset = Struct(endianness + "I") + + # layer and frame information + cdef object info + + # for each row: + # contains (left, right, full_row) number of boundary pixels + cdef vector[boundary_def] boundaries + + # stores the file offset for the first drawing command + cdef vector[int] cmd_offsets + + # pixel matrix representing the final image + cdef vector[vector[pixel]] pcolor + + # memory pointer + cdef const uint8_t[::1] data_raw + + # rows of image + cdef size_t row_count + + def __init__(self, layer_header, data): + self.info = layer_header + + if not (isinstance(data, bytes) or isinstance(data, bytearray)): + raise ValueError("Layer data must be some bytes object") + + # convert the bytes obj to char* + self.data_raw = data + + cdef unsigned short left + cdef unsigned short right + + cdef size_t i + cdef int cmd_offset + + self.row_count = self.info.size[1] + self.pcolor.reserve(self.row_count) + + # process bondary table + for i in range(self.row_count): + outline_entry_position = (self.info.outline_table_offset + + i * SMPLayer.smp_frame_row_edge.size) + + left, right = SMPLayer.smp_frame_row_edge.unpack_from( + data, outline_entry_position + ) + + # is this row completely transparent? + if left == 0xFFFF or right == 0xFFFF: + self.boundaries.push_back(boundary_def(0, 0, True)) + else: + self.boundaries.push_back(boundary_def(left, right, False)) + + # process cmd table + for i in range(self.row_count): + cmd_table_position = (self.info.qdl_table_offset + + i * SMPLayer.smp_command_offset.size) + + cmd_offset = SMPLayer.smp_command_offset.unpack_from( + data, cmd_table_position)[0] + self.info.frame_offset + self.cmd_offsets.push_back(cmd_offset) + + def get_picture_data(self, palette): + """ + Convert the palette index matrix to a colored image. + """ + return determine_rgba_matrix(self.pcolor, palette) + + def get_hotspot(self): + """ + Return the layer's hotspot (the "center" of the image) + """ + return self.info.hotspot + + def get_palette_number(self): + """ + Return the layer's palette number. + + :return: Palette number of the layer. + :rtype: int + """ + return self.pcolor[0][0].palette & 0b00111111 + + def __repr__(self): + return repr(self.info) + + cdef class SMPMainLayer(SMPLayer): def __init__(self, layer_header, data): super().__init__(layer_header, data) @@ -479,112 +581,13 @@ cdef void process_drawing_cmds(SMPLayerVariant variant, return -cdef class SMPLayer: - """ - one layer inside the SMP. you can imagine it as a frame of a video. - """ - - # struct smp_frame_row_edge { - # unsigned short left_space; - # unsigned short right_space; - # }; - smp_frame_row_edge = Struct(endianness + "H H") - - # struct smp_command_offset { - # unsigned int offset; - # } - smp_command_offset = Struct(endianness + "I") - - # layer and frame information - cdef object info - - # for each row: - # contains (left, right, full_row) number of boundary pixels - cdef vector[boundary_def] boundaries - - # stores the file offset for the first drawing command - cdef vector[int] cmd_offsets - - # pixel matrix representing the final image - cdef vector[vector[pixel]] pcolor - - # memory pointer - cdef const uint8_t[::1] data_raw - - # rows of image - cdef size_t row_count - - def __init__(self, layer_header, data): - self.info = layer_header - - if not (isinstance(data, bytes) or isinstance(data, bytearray)): - raise ValueError("Layer data must be some bytes object") - - # convert the bytes obj to char* - self.data_raw = data - - cdef unsigned short left - cdef unsigned short right - - cdef size_t i - cdef int cmd_offset - - self.row_count = self.info.size[1] - self.pcolor.reserve(self.row_count) - - # process bondary table - for i in range(self.row_count): - outline_entry_position = (self.info.outline_table_offset + - i * SMPLayer.smp_frame_row_edge.size) - - left, right = SMPLayer.smp_frame_row_edge.unpack_from( - data, outline_entry_position - ) - - # is this row completely transparent? - if left == 0xFFFF or right == 0xFFFF: - self.boundaries.push_back(boundary_def(0, 0, True)) - else: - self.boundaries.push_back(boundary_def(left, right, False)) - - # process cmd table - for i in range(self.row_count): - cmd_table_position = (self.info.qdl_table_offset + - i * SMPLayer.smp_command_offset.size) - - cmd_offset = SMPLayer.smp_command_offset.unpack_from( - data, cmd_table_position)[0] + self.info.frame_offset - self.cmd_offsets.push_back(cmd_offset) - - def get_picture_data(self, palette): - """ - Convert the palette index matrix to a colored image. - """ - return determine_rgba_matrix(self.pcolor, palette) - - def get_hotspot(self): - """ - Return the layer's hotspot (the "center" of the image) - """ - return self.info.hotspot - - def get_palette_number(self): - """ - Return the layer's palette number. - - :return: Palette number of the layer. - :rtype: int - """ - return self.pcolor[0][0].palette & 0b00111111 - def __repr__(self): - return repr(self.info) @cython.boundscheck(False) @cython.wraparound(False) -cdef numpy.ndarray determine_rgba_matrix(vector[vector[pixel]] & image_matrix, - numpy.ndarray[numpy.uint8_t, ndim= 2, mode = "c"] palette): +cdef numpy.ndarray determine_rgba_matrix(vector[vector[pixel]] &image_matrix, + numpy.ndarray[numpy.uint8_t, ndim=2, mode="c"] palette): """ converts a palette index image matrix to an rgba matrix. """ @@ -592,7 +595,7 @@ cdef numpy.ndarray determine_rgba_matrix(vector[vector[pixel]] & image_matrix, cdef size_t height = image_matrix.size() cdef size_t width = image_matrix[0].size() - cdef numpy.ndarray[numpy.uint8_t, ndim= 3, mode = "c"] array_data = \ + cdef numpy.ndarray[numpy.uint8_t, ndim=3, mode="c"] array_data = \ numpy.zeros((height, width, 4), dtype=numpy.uint8) cdef uint8_t[:, ::1] m_lookup = palette @@ -690,7 +693,7 @@ cdef numpy.ndarray determine_rgba_matrix(vector[vector[pixel]] & image_matrix, @cython.boundscheck(False) @cython.wraparound(False) -cdef numpy.ndarray determine_damage_matrix(vector[vector[pixel]] & image_matrix): +cdef numpy.ndarray determine_damage_matrix(vector[vector[pixel]] &image_matrix): """ converts the damage modifier values to an image using the RG values. @@ -700,7 +703,7 @@ cdef numpy.ndarray determine_damage_matrix(vector[vector[pixel]] & image_matrix) cdef size_t height = image_matrix.size() cdef size_t width = image_matrix[0].size() - cdef numpy.ndarray[numpy.uint8_t, ndim= 3, mode = "c"] array_data = \ + cdef numpy.ndarray[numpy.uint8_t, ndim=3, mode="c"] array_data = \ numpy.zeros((height, width, 4), dtype=numpy.uint8) cdef uint8_t r diff --git a/openage/convert/value_object/read/media/smx.pyx b/openage/convert/value_object/read/media/smx.pyx index a058c22399..52e17428c8 100644 --- a/openage/convert/value_object/read/media/smx.pyx +++ b/openage/convert/value_object/read/media/smx.pyx @@ -1,10 +1,7 @@ -# Copyright 2019-2024 the openage authors. See copying.md for legal info. +# Copyright 2019-2025 the openage authors. See copying.md for legal info. # # cython: infer_types=True -from libcpp.vector cimport vector -from libcpp cimport bool -from libc.stdint cimport uint8_t, uint16_t from enum import Enum import numpy from struct import Struct, unpack_from @@ -15,6 +12,11 @@ from .....log import spam, dbg cimport cython cimport numpy +from libc.stdint cimport uint8_t, uint16_t +from libcpp cimport bool +from libcpp.vector cimport vector + + # SMX files have little endian byte order endianness = "< " @@ -47,8 +49,8 @@ class SMXLayerType(Enum): """ SMX layer types. """ - MAIN = "main" - SHADOW = "shadow" + MAIN = "main" + SHADOW = "shadow" OUTLINE = "outline" @@ -57,57 +59,6 @@ cdef public dict LAYER_TYPES = { 1: SMXLayerType.SHADOW, 2: SMXLayerType.OUTLINE, } -cdef class SMXMainLayer8to5(SMXLayer): - def __init__(self, layer_header, data): - super().__init__(layer_header, data) - # process cmd table - for i in range(self.row_count): - cmd_offset, color_offset, chunk_pos, row_data = create_color_row( - self, i) - self.cmd_offset = cmd_offset - self.color_offset = color_offset - self.chunk_pos = chunk_pos - - self.pcolor.push_back(row_data) - -cdef class SMXMainLayer4plus1(SMXLayer): - def __init__(self, layer_header, data): - super().__init__(layer_header, data) - # process cmd table - for i in range(self.row_count): - cmd_offset, color_offset, chunk_pos, row_data = create_color_row( - self, i) - self.cmd_offset = cmd_offset - self.color_offset = color_offset - self.chunk_pos = chunk_pos - - self.pcolor.push_back(row_data) - -cdef class SMXOutlineLayer(SMXLayer): - def __init__(self, layer_header, data): - super().__init__(layer_header, data) - # process cmd table - for i in range(self.row_count): - cmd_offset, color_offset, chunk_pos, row_data = create_color_row( - self, i) - self.cmd_offset = cmd_offset - self.color_offset = color_offset - self.chunk_pos = chunk_pos - - self.pcolor.push_back(row_data) - -cdef class SMXShadowLayer(SMXLayer): - def __init__(self, layer_header, data): - super().__init__(layer_header, data) - # process cmd table - for i in range(self.row_count): - cmd_offset, color_offset, chunk_pos, row_data = create_color_row( - self, i) - self.cmd_offset = cmd_offset - self.color_offset = color_offset - self.chunk_pos = chunk_pos - - self.pcolor.push_back(row_data) ctypedef fused SMXLayerVariant: @@ -246,15 +197,16 @@ cdef inline(int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant v # shadows sometimes need an extra pixel at # the end - if SMXLayerVariant is SMXShadowLayer and row_data.size() < expected_size: - # copy the last drawn pixel - # (still stored in nextbyte) - # - # TODO: confirm that this is the - # right way to do it - row_data.push_back(pixel(color_shadow, - nextbyte, 0, 0, 0)) - continue + if SMXLayerVariant is SMXShadowLayer: + if row_data.size() < expected_size: + # copy the last drawn pixel + # (still stored in nextbyte) + # + # TODO: confirm that this is the + # right way to do it + row_data.push_back(pixel(color_shadow, + nextbyte, 0, 0, 0)) + continue elif lower_crumb == 0b00000000: # skip command @@ -510,16 +462,14 @@ class SMX: """ smx_header = SMX.smx_header.unpack_from(data) - self.smp_type, version, frame_count, file_size_comp, \ + self.smp_type, version, frame_count, file_size_comp,\ file_size_uncomp, comment = smx_header dbg("SMX") dbg(" version: %s", version) dbg(" frame count: %s", frame_count) - # 0x20 = SMX header size - dbg(" file size compressed: %s B", file_size_comp + 0x20) - # 0x80 = SMP header size - dbg(" file size uncompressed: %s B", file_size_uncomp + 0x40) + dbg(" file size compressed: %s B", file_size_comp + 0x20) # 0x20 = SMX header size + dbg(" file size uncompressed: %s B", file_size_uncomp + 0x40) # 0x80 = SMP header size dbg(" comment: %s", comment.decode('ascii')) # SMX graphic frames are created from overlaying @@ -537,7 +487,7 @@ class SMX: frame_header = SMX.smx_frame_header.unpack_from( data, current_offset) - frame_type, palette_number, _ = frame_header + frame_type , palette_number, _ = frame_header current_offset += SMX.smx_frame_header.size @@ -556,7 +506,7 @@ class SMX: layer_header_data = SMX.smx_layer_header.unpack_from( data, current_offset) - width, height, hotspot_x, hotspot_y, \ + width, height, hotspot_x, hotspot_y,\ distance_next_frame, _ = layer_header_data current_offset += SMX.smx_layer_header.size @@ -566,14 +516,12 @@ class SMX: # Skip outline table current_offset += 4 * height - qdl_command_array_size = Struct( - "< I").unpack_from(data, current_offset)[0] + qdl_command_array_size = Struct("< I").unpack_from(data, current_offset)[0] current_offset += 4 # Read length of color table if layer_type is SMXLayerType.MAIN: - qdl_color_table_size = Struct( - "< I").unpack_from(data, current_offset)[0] + qdl_color_table_size = Struct("< I").unpack_from(data, current_offset)[0] current_offset += 4 qdl_color_table_offset = current_offset + qdl_command_array_size @@ -593,20 +541,16 @@ class SMX: if layer_type is SMXLayerType.MAIN: if layer_header.compression_type == 0x08: - self.main_frames.append( - SMXMainLayer8to5(layer_header, data)) + self.main_frames.append(SMXMainLayer8to5(layer_header, data)) elif layer_header.compression_type == 0x00: - self.main_frames.append( - SMXMainLayer4plus1(layer_header, data)) + self.main_frames.append(SMXMainLayer4plus1(layer_header, data)) elif layer_type is SMXLayerType.SHADOW: - self.shadow_frames.append( - SMXShadowLayer(layer_header, data)) + self.shadow_frames.append(SMXShadowLayer(layer_header, data)) elif layer_type is SMXLayerType.OUTLINE: - self.outline_frames.append( - SMXOutlineLayer(layer_header, data)) + self.outline_frames.append(SMXOutlineLayer(layer_header, data)) def get_frames(self, layer: int = 0): """ @@ -826,10 +770,68 @@ cdef class SMXLayer: return repr(self.info) +cdef class SMXMainLayer8to5(SMXLayer): + """ + Compressed SMP layer (compression type 8to5) for the main graphics sprite. + """ + + def __init__(self, layer_header, data): + super().__init__(layer_header, data) + # process cmd table + for i in range(self.row_count): + cmd_offset, color_offset, chunk_pos, row_data = create_color_row( + self, i) + self.cmd_offset = cmd_offset + self.color_offset = color_offset + self.chunk_pos = chunk_pos + + self.pcolor.push_back(row_data) + +cdef class SMXMainLayer4plus1(SMXLayer): + def __init__(self, layer_header, data): + super().__init__(layer_header, data) + # process cmd table + for i in range(self.row_count): + cmd_offset, color_offset, chunk_pos, row_data = create_color_row( + self, i) + self.cmd_offset = cmd_offset + self.color_offset = color_offset + self.chunk_pos = chunk_pos + + self.pcolor.push_back(row_data) + +cdef class SMXOutlineLayer(SMXLayer): + def __init__(self, layer_header, data): + super().__init__(layer_header, data) + # process cmd table + for i in range(self.row_count): + cmd_offset, color_offset, chunk_pos, row_data = create_color_row( + self, i) + self.cmd_offset = cmd_offset + self.color_offset = color_offset + self.chunk_pos = chunk_pos + + self.pcolor.push_back(row_data) + +cdef class SMXShadowLayer(SMXLayer): + def __init__(self, layer_header, data): + super().__init__(layer_header, data) + # process cmd table + for i in range(self.row_count): + cmd_offset, color_offset, chunk_pos, row_data = create_color_row( + self, i) + self.cmd_offset = cmd_offset + self.color_offset = color_offset + self.chunk_pos = chunk_pos + + self.pcolor.push_back(row_data) + + + @cython.boundscheck(False) @cython.wraparound(False) -cdef numpy.ndarray determine_rgba_matrix(vector[vector[pixel]] & image_matrix, - numpy.ndarray[numpy.uint8_t, ndim= 2, mode = "c"] palette): +cdef numpy.ndarray determine_rgba_matrix(vector[vector[pixel]] &image_matrix, + numpy.ndarray[numpy.uint8_t, ndim=2, mode="c"] palette): """ converts a palette index image matrix to an rgba matrix. @@ -840,7 +842,7 @@ cdef numpy.ndarray determine_rgba_matrix(vector[vector[pixel]] & image_matrix, cdef size_t height = image_matrix.size() cdef size_t width = image_matrix[0].size() - cdef numpy.ndarray[numpy.uint8_t, ndim= 3, mode = "c"] array_data = \ + cdef numpy.ndarray[numpy.uint8_t, ndim=3, mode="c"] array_data = \ numpy.zeros((height, width, 4), dtype=numpy.uint8) cdef uint8_t[:, ::1] m_lookup = palette @@ -929,7 +931,7 @@ cdef numpy.ndarray determine_rgba_matrix(vector[vector[pixel]] & image_matrix, @cython.boundscheck(False) @cython.wraparound(False) -cdef numpy.ndarray determine_damage_matrix(vector[vector[pixel]] & image_matrix): +cdef numpy.ndarray determine_damage_matrix(vector[vector[pixel]] &image_matrix): """ converts the damage modifier values to an image using the RG values. @@ -939,7 +941,7 @@ cdef numpy.ndarray determine_damage_matrix(vector[vector[pixel]] & image_matrix) cdef size_t height = image_matrix.size() cdef size_t width = image_matrix[0].size() - cdef numpy.ndarray[numpy.uint8_t, ndim= 3, mode = "c"] array_data = \ + cdef numpy.ndarray[numpy.uint8_t, ndim=3, mode="c"] array_data = \ numpy.zeros((height, width, 4), dtype=numpy.uint8) cdef uint8_t r = 0 From c8b98a2719f5198cf9d678dbd2d95921a6528069 Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Thu, 16 Jan 2025 23:26:07 +0000 Subject: [PATCH 086/152] restructure --- .../convert/value_object/read/media/smp.pyx | 423 +++++----- .../convert/value_object/read/media/smx.pyx | 794 +++++++++--------- 2 files changed, 592 insertions(+), 625 deletions(-) diff --git a/openage/convert/value_object/read/media/smp.pyx b/openage/convert/value_object/read/media/smp.pyx index c52aa1a194..9ad253c5c3 100644 --- a/openage/convert/value_object/read/media/smp.pyx +++ b/openage/convert/value_object/read/media/smp.pyx @@ -253,6 +253,11 @@ class SMPLayerHeader: ) return "".join(ret) +ctypedef fused SMPLayerVariant: + SMPMainLayer + SMPShadowLayer + SMPOutlineLayer + cdef class SMPLayer: """ @@ -283,20 +288,15 @@ cdef class SMPLayer: # pixel matrix representing the final image cdef vector[vector[pixel]] pcolor - # memory pointer - cdef const uint8_t[::1] data_raw - - # rows of image - cdef size_t row_count - - def __init__(self, layer_header, data): + def init(self, SMPLayerVariant variant, layer_header, data): self.info = layer_header if not (isinstance(data, bytes) or isinstance(data, bytearray)): raise ValueError("Layer data must be some bytes object") + # memory pointer # convert the bytes obj to char* - self.data_raw = data + cdef const uint8_t[::1] data_raw = data cdef unsigned short left cdef unsigned short right @@ -304,11 +304,11 @@ cdef class SMPLayer: cdef size_t i cdef int cmd_offset - self.row_count = self.info.size[1] - self.pcolor.reserve(self.row_count) + cdef size_t row_count = self.info.size[1] + self.pcolor.reserve(row_count) # process bondary table - for i in range(self.row_count): + for i in range(row_count): outline_entry_position = (self.info.outline_table_offset + i * SMPLayer.smp_frame_row_edge.size) @@ -323,7 +323,7 @@ cdef class SMPLayer: self.boundaries.push_back(boundary_def(left, right, False)) # process cmd table - for i in range(self.row_count): + for i in range(row_count): cmd_table_position = (self.info.qdl_table_offset + i * SMPLayer.smp_command_offset.size) @@ -331,257 +331,253 @@ cdef class SMPLayer: data, cmd_table_position)[0] + self.info.frame_offset self.cmd_offsets.push_back(cmd_offset) - def get_picture_data(self, palette): - """ - Convert the palette index matrix to a colored image. - """ - return determine_rgba_matrix(self.pcolor, palette) + for i in range(row_count): + self.pcolor.push_back(self.create_color_row(variant, data_raw, i)) - def get_hotspot(self): + cdef vector[pixel] create_color_row(self, + SMPLayerVariant variant, + const uint8_t[::1] &data_raw, + Py_ssize_t rowid): """ - Return the layer's hotspot (the "center" of the image) + extract colors (pixels) for the given rowid. """ - return self.info.hotspot - def get_palette_number(self): - """ - Return the layer's palette number. + cdef vector[pixel] row_data + cdef Py_ssize_t i - :return: Palette number of the layer. - :rtype: int - """ - return self.pcolor[0][0].palette & 0b00111111 + first_cmd_offset = self.cmd_offsets[rowid] + cdef boundary_def bounds = self.boundaries[rowid] + cdef size_t pixel_count = self.info.size[0] - def __repr__(self): - return repr(self.info) + # preallocate memory + row_data.reserve(pixel_count) + # row is completely transparent + if bounds.full_row: + for _ in range(pixel_count): + row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) -cdef class SMPMainLayer(SMPLayer): - def __init__(self, layer_header, data): - super().__init__(layer_header, data) + return row_data - for i in range(self.row_count): - self.pcolor.push_back(create_color_row(self, i)) + # start drawing the left transparent space + for i in range(bounds.left): + row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) - def get_damage_mask(self): - """ - Convert the 4th pixel byte to a mask used for damaged units. - """ - return determine_damage_matrix(self.pcolor) + # process the drawing commands for this row. + self.process_drawing_cmds(variant, + data_raw, + row_data, rowid, + first_cmd_offset, + pixel_count - bounds.right) -cdef class SMPShadowLayer(SMPLayer): - def __init__(self, layer_header, data): - super().__init__(layer_header, data) + # finish by filling up the right transparent space + for i in range(bounds.right): + row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) - for i in range(self.row_count): - self.pcolor.push_back(create_color_row(self, i)) + # verify size of generated row + if row_data.size() != pixel_count: + got = row_data.size() + summary = ( + f"{got:d}/{pixel_count:d} -> row {rowid:d}, " + f"layer type {self.info.layer_type:x}, " + f"offset {first_cmd_offset:d} / {first_cmd_offset:#x}" + ) + message = ( + f"got {'LESS' if got < pixel_count else 'MORE'} pixels than expected: {summary}, " + f"missing: {abs(pixel_count - got):d}" + ) -cdef class SMPOutlineLayer(SMPLayer): - def __init__(self, layer_header, data): - super().__init__(layer_header, data) + raise Exception(message) - for i in range(self.row_count): - self.pcolor.push_back(create_color_row(self, i)) + return row_data -ctypedef fused SMPLayerVariant: - SMPMainLayer - SMPShadowLayer - SMPOutlineLayer + @cython.boundscheck(False) + cdef void process_drawing_cmds(self, + SMPLayerVariant variant, + const uint8_t[::1] &data_raw, + vector[pixel] &row_data, + Py_ssize_t rowid, + Py_ssize_t first_cmd_offset, + size_t expected_size): + """ + extract colors (pixels) for the drawing commands + found for this row in the SMP frame. + """ + # position in the data blob, we start at the first command of this row + cdef Py_ssize_t dpos = first_cmd_offset -@cython.boundscheck(False) -cdef vector[pixel] create_color_row(SMPLayerVariant variant, - Py_ssize_t rowid): - """ - extract colors (pixels) for the given rowid. - """ - cdef vector[pixel] row_data - cdef Py_ssize_t i + # is the end of the current row reached? + cdef bool eor = False - cdef Py_ssize_t first_cmd_offset = variant.cmd_offsets[rowid] - cdef boundary_def bounds = variant.boundaries[rowid] - cdef size_t pixel_count = variant.info.size[0] + cdef uint8_t cmd + cdef uint8_t nextbyte + cdef uint8_t lower_crumb + cdef int pixel_count - # preallocate memory - row_data.reserve(pixel_count) + cdef vector[uint8_t] pixel_data + pixel_data.reserve(4) - # row is completely transparent - if bounds.full_row: - for _ in range(pixel_count): - row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) + # work through commands till end of row. + while not eor: + if row_data.size() > expected_size: + raise Exception( + f"Only {expected_size:d} pixels should be drawn in row {rowid:d} " + + f"with layer type {self.info.layer_type:#x}, but we have {row_data.size():d}" + ) - return row_data + # fetch drawing instruction + cmd = data_raw[dpos] - # start drawing the left transparent space - for i in range(bounds.left): - row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) - - # process the drawing commands for this row. - process_drawing_cmds(variant, variant.data_raw, - row_data, rowid, - first_cmd_offset, - pixel_count - bounds.right) - - # finish by filling up the right transparent space - for i in range(bounds.right): - row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) - - # verify size of generated row - if row_data.size() != pixel_count: - got = row_data.size() - summary = ( - f"{got:d}/{pixel_count:d} -> row {rowid:d}, " - f"layer type {variant.info.layer_type:x}, " - f"offset {first_cmd_offset:d} / {first_cmd_offset:#x}" - ) - message = ( - f"got {'LESS' if got < pixel_count else 'MORE'} pixels than expected: {summary}, " - f"missing: {abs(pixel_count - got):d}" - ) + # Last 2 bits store command type + lower_crumb = 0b00000011 & cmd - raise Exception(message) + # opcode: cmd, rowid: rowid - return row_data + if lower_crumb == 0b00000011: + # eol (end of line) command, this row is finished now. + eor = True + if SMPLayerVariant is SMPShadowLayer and row_data.size() < expected_size: + # copy the last drawn pixel + # (still stored in nextbyte) + # + # TODO: confirm that this is the + # right way to do it + row_data.push_back(pixel(color_shadow, nextbyte, 0, 0, 0)) -@cython.boundscheck(False) -cdef void process_drawing_cmds(SMPLayerVariant variant, - const uint8_t[::1] & data_raw, - vector[pixel] & row_data, - Py_ssize_t rowid, - Py_ssize_t first_cmd_offset, - size_t expected_size): + continue - """ - extract colors (pixels) for the drawing commands - found for this row in the SMP frame. - """ - - # position in the data blob, we start at the first command of this row - cdef Py_ssize_t dpos = first_cmd_offset - - # is the end of the current row reached? - cdef bool eor = False - - cdef uint8_t cmd = 0 - cdef uint8_t nextbyte = 0 - cdef uint8_t lower_crumb = 0 - cdef int pixel_count = 0 - - cdef vector[uint8_t] pixel_data - pixel_data.reserve(4) - - # work through commands till end of row. - while not eor: - if row_data.size() > expected_size: - raise Exception( - f"Only {expected_size:d} pixels should be drawn in row {rowid:d} " + - f"with layer type {variant.info.layer_type:#x}, but we have {row_data.size():d} already!" - ) + elif lower_crumb == 0b00000000: + # skip command + # draw 'count' transparent pixels + # count = (cmd >> 2) + 1 - # fetch drawing instruction - cmd = data_raw[dpos] + pixel_count = (cmd >> 2) + 1 - # Last 2 bits store command type - lower_crumb = 0b00000011 & cmd - # opcode: cmd, rowid: rowid + for _ in range(pixel_count): + row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) - if lower_crumb == 0b00000011: - # eol (end of line) command, this row is finished now. - eor = True + elif lower_crumb == 0b00000001: + # color_list command + # draw the following 'count' pixels + # pixels are stored as 4 byte palette and meta infos + # count = (cmd >> 2) + 1 - if SMPLayerVariant is SMPShadowLayer and row_data.size() < expected_size: - # copy the last drawn pixel - # (still stored in nextbyte) - # - # TODO: confirm that this is the - # right way to do it - row_data.push_back(pixel(color_shadow, nextbyte, 0, 0, 0)) + pixel_count = (cmd >> 2) + 1 - continue + for _ in range(pixel_count): + if SMPLayerVariant is SMPShadowLayer: + dpos += 1 + nextbyte = data_raw[dpos] + row_data.push_back(pixel(color_shadow, nextbyte, 0, 0, 0)) + elif SMPLayerVariant is SMPOutlineLayer: + # we don't know the color the game wants + # so we just draw index 0 + row_data.push_back(pixel(color_outline, 0, 0, 0, 0)) + elif SMPLayerVariant is SMPMainLayer: + for _ in range(4): + dpos += 1 + pixel_data.push_back(data_raw[dpos]) + + row_data.push_back(pixel(color_standard, + pixel_data[0], + pixel_data[1], + pixel_data[2], + pixel_data[3] & 0x1F)) # remove "usage" bit here + pixel_data.clear() + + elif lower_crumb == 0b00000010 and (SMPLayerVariant is SMPMainLayer or SMPLayerVariant is SMPShadowLayer): + # player_color command + # draw the following 'count' pixels + # pixels are stored as 4 byte palette and meta infos + # count = (cmd >> 2) + 1 + + pixel_count = (cmd >> 2) + 1 + + for _ in range(pixel_count): + if SMPLayerVariant is SMPShadowLayer: + dpos += 1 + nextbyte = data_raw[dpos] + row_data.push_back(pixel(color_shadow, + nextbyte, 0, 0, 0)) + else: + for _ in range(4): + dpos += 1 + pixel_data.push_back(data_raw[dpos]) + + row_data.push_back(pixel(color_player, + pixel_data[0], + pixel_data[1], + pixel_data[2], + pixel_data[3] & 0x1F)) # remove "usage" bit here + pixel_data.clear() - elif lower_crumb == 0b00000000: - # skip command - # draw 'count' transparent pixels - # count = (cmd >> 2) + 1 + else: + raise Exception( + f"unknown smp {self.info.layer_type} layer drawing command: " + + f"{cmd:#x} in row {rowid:d}" + ) - pixel_count = (cmd >> 2) + 1 + # process next command + dpos += 1 - for _ in range(pixel_count): - row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) + # end of row reached, return the created pixel array. + return - elif lower_crumb == 0b00000001: - # color_list command - # draw the following 'count' pixels - # pixels are stored as 4 byte palette and meta infos - # count = (cmd >> 2) + 1 - pixel_count = (cmd >> 2) + 1 + def get_picture_data(self, palette): + """ + Convert the palette index matrix to a colored image. + """ + return determine_rgba_matrix(self.pcolor, palette) - for _ in range(pixel_count): - if SMPLayerVariant is SMPShadowLayer: - dpos += 1 - nextbyte = data_raw[dpos] - row_data.push_back(pixel(color_shadow, - nextbyte, 0, 0, 0)) - elif SMPLayerVariant is SMPOutlineLayer: - # we don't know the color the game wants - # so we just draw index 0 - row_data.push_back(pixel(color_outline, - 0, 0, 0, 0)) - elif SMPLayerVariant is SMPMainLayer: - for _ in range(4): - dpos += 1 - pixel_data.push_back(data_raw[dpos]) + def get_hotspot(self): + """ + Return the layer's hotspot (the "center" of the image) + """ + return self.info.hotspot - row_data.push_back(pixel(color_standard, - pixel_data[0], - pixel_data[1], - pixel_data[2], - pixel_data[3] & 0x1F)) # remove "usage" bit here - pixel_data.clear() + def get_palette_number(self): + """ + Return the layer's palette number. - elif lower_crumb == 0b00000010 and (SMPLayerVariant is SMPMainLayer or SMPLayerVariant is SMPShadowLayer): - # player_color command - # draw the following 'count' pixels - # pixels are stored as 4 byte palette and meta infos - # count = (cmd >> 2) + 1 + :return: Palette number of the layer. + :rtype: int + """ + return self.pcolor[0][0].palette & 0b00111111 - pixel_count = (cmd >> 2) + 1 + def __repr__(self): + return repr(self.info) - for _ in range(pixel_count): - if SMPLayerVariant is SMPShadowLayer: - dpos += 1 - nextbyte = data_raw[dpos] - row_data.push_back(pixel(color_shadow, - nextbyte, 0, 0, 0)) - else: - for _ in range(4): - dpos += 1 - pixel_data.push_back(data_raw[dpos]) - row_data.push_back(pixel(color_player, - pixel_data[0], - pixel_data[1], - pixel_data[2], - pixel_data[3] & 0x1F)) # remove "usage" bit here - pixel_data.clear() +cdef class SMPMainLayer(SMPLayer): + """ + SMPLayer for the main graphics sprite. + """ - else: - raise Exception( - f"unknown smp {variant.info.layer_type} layer drawing command: " + - f"{cmd:#x} in row {rowid:d}" - ) + def __init__(self, layer_header, data): + self.init(self ,layer_header, data) - # process next command - dpos += 1 + def get_damage_mask(self): + """ + Convert the 4th pixel byte to a mask used for damaged units. + """ + return determine_damage_matrix(self.pcolor) - # end of row reached, return the created pixel array. - return +cdef class SMPShadowLayer(SMPLayer): + """ + SMPLayer for the shadow graphics. + """ + def __init__(self, layer_header, data): + self.init(self ,layer_header, data) +cdef class SMPOutlineLayer(SMPLayer): + def __init__(self, layer_header, data): + self.init(self, layer_header, data) @cython.boundscheck(False) @@ -690,7 +686,6 @@ cdef numpy.ndarray determine_rgba_matrix(vector[vector[pixel]] &image_matrix, return array_data - @cython.boundscheck(False) @cython.wraparound(False) cdef numpy.ndarray determine_damage_matrix(vector[vector[pixel]] &image_matrix): diff --git a/openage/convert/value_object/read/media/smx.pyx b/openage/convert/value_object/read/media/smx.pyx index 52e17428c8..2e60a84a00 100644 --- a/openage/convert/value_object/read/media/smx.pyx +++ b/openage/convert/value_object/read/media/smx.pyx @@ -68,357 +68,6 @@ ctypedef fused SMXLayerVariant: SMXShadowLayer -cdef inline(int, int, int, vector[pixel]) create_color_row(SMXLayerVariant variant, - Py_ssize_t rowid): - """ - Extract colors (pixels) for the given rowid. - - :param rowid: Index of the current row in the layer. - :param cmd_offset: Offset of the command table of the layer. - :param color_offset: Offset of the color table of the layer. - :param chunk_pos: Current position in the compressed chunk. - """ - - cdef vector[pixel] row_data - cdef Py_ssize_t i - - cdef int first_cmd_offset = variant.cmd_offset - cdef int first_color_offset = variant.color_offset - cdef int first_chunk_pos = variant.chunk_pos - cdef boundary_def bounds = variant.boundaries[rowid] - cdef size_t pixel_count = variant.info.size[0] - - # preallocate memory - row_data.reserve(pixel_count) - - # row is completely transparent - if bounds.full_row: - for _ in range(pixel_count): - row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) - - return variant.cmd_offset, variant.color_offset, variant.chunk_pos, row_data - - # start drawing the left transparent space - for i in range(bounds.left): - row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) - - # process the drawing commands for this row. - next_cmd_offset, next_color_offset, next_chunk_pos, row_data = \ - process_drawing_cmds(variant, - variant.data_raw, - row_data, - rowid, - first_cmd_offset, - first_color_offset, - first_chunk_pos, - pixel_count - bounds.right - ) - - # finish by filling up the right transparent space - for i in range(bounds.right): - row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) - - # verify size of generated row - if row_data.size() != pixel_count: - got = row_data.size() - summary = "%d/%d -> row %d, layer type %s, offset %d / %#x" % ( - got, pixel_count, rowid, variant.info.layer_type, - first_cmd_offset, first_cmd_offset - ) - txt = "got %%s pixels than expected: %s, missing: %d" % ( - summary, abs(pixel_count - got)) - - raise Exception(txt % ("LESS" if got < pixel_count else "MORE")) - - return next_cmd_offset, next_color_offset, next_chunk_pos, row_data - - -@cython.boundscheck(False) -cdef inline(int, int, int, vector[pixel]) process_drawing_cmds(SMXLayerVariant variant, - const uint8_t[::1] & data_raw, - vector[pixel] & row_data, - Py_ssize_t rowid, - Py_ssize_t first_cmd_offset, - Py_ssize_t first_color_offset, - int chunk_pos, - size_t expected_size): - """ - TODO: docstring - """ - # position in the command array, we start at the first command of this row - cdef Py_ssize_t dpos_cmd = first_cmd_offset - - # is the end of the current row reached? - cdef bool eor = False - - cdef uint8_t cmd = 0 - cdef uint8_t lower_crumb = 0 - cdef int pixel_count = 0 - cdef Py_ssize_t dpos_color = 0 - cdef vector[uint8_t] pixel_data - # Pixel data temporary values that need further decompression - cdef uint8_t pixel_data_odd_0 = 0 - cdef uint8_t pixel_data_odd_1 = 0 - cdef uint8_t pixel_data_odd_2 = 0 - cdef uint8_t pixel_data_odd_3 = 0 - # Mask for even indices - # cdef uint8_t pixel_mask_even_0 = 0xFF - cdef uint8_t pixel_mask_even_1 = 0b00000011 - cdef uint8_t pixel_mask_even_2 = 0b11110000 - cdef uint8_t pixel_mask_even_3 = 0b00111111 - - if SMXLayerVariant is SMXMainLayer8to5 or SMXLayerVariant is SMXMainLayer4plus1: - # Position in the pixel data array - dpos_color = first_color_offset - - cdef uint8_t palette_section_block = 0 - cdef uint8_t palette_section = 0 - cdef uint8_t nextbyte = 0 - - # work through commands till end of row. - while not eor: - if row_data.size() > expected_size: - raise Exception( - f"Only {expected_size:d} pixels should be drawn in row {rowid:d} " - f"with layer type {variant:#x}, but we have {row_data.size():d} " - f"already!" - ) - - # fetch drawing instruction - cmd = data_raw[dpos_cmd] - - # Last 2 bits store command type - lower_crumb = 0b00000011 & cmd - if lower_crumb == 0b00000011: - # eor (end of row) command, this row is finished now. - - eor = True - dpos_cmd += 1 - - # shadows sometimes need an extra pixel at - # the end - if SMXLayerVariant is SMXShadowLayer: - if row_data.size() < expected_size: - # copy the last drawn pixel - # (still stored in nextbyte) - # - # TODO: confirm that this is the - # right way to do it - row_data.push_back(pixel(color_shadow, - nextbyte, 0, 0, 0)) - continue - - elif lower_crumb == 0b00000000: - # skip command - # draw 'count' transparent pixels - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - for _ in range(pixel_count): - row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) - - elif lower_crumb == 0b00000001: - # color_list command - # draw the following 'count' pixels - # pixels are stored in 5 byte chunks - # even pixel indices have their info stored - # in byte[0] - byte[3]. odd pixel indices have - # their info stored in byte[1] - byte[4]. - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - if SMXLayerVariant is SMXMainLayer8to5: - pixel_data.reserve(4) - for _ in range(pixel_count): - # Start fetching pixel data - if chunk_pos: - # Odd indices require manual extraction of each of the 4 values - - # Palette index. Essentially a rotation of (byte[1]byte[2]) - # by 6 to the left, then masking with 0x00FF. - pixel_data_odd_0 = data_raw[dpos_color + 1] - pixel_data_odd_1 = data_raw[dpos_color + 2] - pixel_data.push_back( - (pixel_data_odd_0 >> 2) | (pixel_data_odd_1 << 6)) - - # Palette section. Described in byte[2] in bits 4-5. - pixel_data.push_back((pixel_data_odd_1 >> 2) & 0x03) - - # Damage mask 1. Essentially a rotation of (byte[3]byte[4]) - # by 6 to the left, then masking with 0x00F0. - pixel_data_odd_2 = data_raw[dpos_color + 3] - pixel_data_odd_3 = data_raw[dpos_color + 4] - pixel_data.push_back( - ((pixel_data_odd_2 >> 2) | (pixel_data_odd_3 << 6)) & 0xF0) - - # Damage mask 2. Described in byte[4] in bits 0-5. - pixel_data.push_back((pixel_data_odd_3 >> 2) & 0x3F) - - row_data.push_back(pixel(color_standard, - pixel_data[0], - pixel_data[1], - pixel_data[2], - pixel_data[3])) - - # Go to next pixel - dpos_color += 5 - - else: - # Even indices can be read "as is". They just have to be masked. - for i in range(4): - pixel_data.push_back(data_raw[dpos_color + i]) - - row_data.push_back(pixel(color_standard, - pixel_data[0], - pixel_data[1] & pixel_mask_even_1, - pixel_data[2] & pixel_mask_even_2, - pixel_data[3] & pixel_mask_even_3)) - - odd = not odd - pixel_data.clear() - - if SMXLayerVariant is SMXMainLayer4plus1: - palette_section_block = data_raw[dpos_color + (4 - chunk_pos)] - - for _ in range(pixel_count): - # Start fetching pixel data - palette_section = ( - palette_section_block >> (2 * chunk_pos)) & 0x03 - row_data.push_back(pixel(color_standard, - data_raw[dpos_color], - palette_section, - 0, - 0)) - - dpos_color += 1 - chunk_pos += 1 - - # Skip to next chunk - if chunk_pos > 3: - chunk_pos = 0 - dpos_color += 1 # Skip palette section block - palette_section_block = data_raw[dpos_color + 4] - - if SMXLayerVariant is SMXShadowLayer: - for _ in range(pixel_count): - dpos_cmd += 1 - nextbyte = data_raw[dpos_cmd] - - row_data.push_back(pixel(color_shadow, - nextbyte, 0, 0, 0)) - - if SMXLayerVariant is SMXOutlineLayer: - # we don't know the color the game wants - # so we just draw index 0 - row_data.push_back(pixel(color_outline, - 0, 0, 0, 0)) - - elif lower_crumb == 0b00000010: - if SMXLayerVariant is SMXMainLayer8to5: - # player_color command - # draw the following 'count' pixels - # pixels are stored in 5 byte chunks - # even pixel indices have their info stored - # in byte[0] - byte[3]. odd pixel indices have - # their info stored in byte[1] - byte[4]. - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - for _ in range(pixel_count): - # Start fetching pixel data - if odd: - # Odd indices require manual extraction of each of the 4 values - - # Palette index. Essentially a rotation of (byte[1]byte[2]) - # by 6 to the left, then masking with 0x00FF. - pixel_data_odd_0 = data_raw[dpos_color + 1] - pixel_data_odd_1 = data_raw[dpos_color + 2] - pixel_data.push_back( - (pixel_data_odd_0 >> 2) | (pixel_data_odd_1 << 6)) - - # Palette section. Described in byte[2] in bits 4-5. - pixel_data.push_back((pixel_data_odd_1 >> 2) & 0x03) - - # Damage modifier 1. Essentially a rotation of (byte[3]byte[4]) - # by 6 to the left, then masking with 0x00F0. - pixel_data_odd_2 = data_raw[dpos_color + 3] - pixel_data_odd_3 = data_raw[dpos_color + 4] - pixel_data.push_back( - ((pixel_data_odd_2 >> 2) | (pixel_data_odd_3 << 6)) & 0xF0) - - # Damage modifier 2. Described in byte[4] in bits 0-5. - pixel_data.push_back((pixel_data_odd_3 >> 2) & 0x3F) - - row_data.push_back(pixel(color_player, - pixel_data[0], - pixel_data[1], - pixel_data[2], - pixel_data[3])) - - # Go to next pixel - dpos_color += 5 - - else: - # Even indices can be read "as is". They just have to be masked. - for px_dpos in range(4): - pixel_data.push_back( - data_raw[dpos_color + px_dpos]) - - row_data.push_back(pixel(color_player, - pixel_data[0], - pixel_data[1] & pixel_mask_even_1, - pixel_data[2] & pixel_mask_even_2, - pixel_data[3] & pixel_mask_even_3)) - - odd = not odd - pixel_data.clear() - - elif SMXLayerVariant is SMXMainLayer4plus1: - # player_color command - # draw the following 'count' pixels - # 4 pixels are stored in every 5 byte chunk. - # palette indices are contained in byte[0] - byte[3] - # palette sections are stored in byte[4] - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - for _ in range(pixel_count): - # Start fetching pixel data - palette_section = ( - palette_section_block >> (2 * chunk_pos)) & 0x03 - row_data.push_back(pixel(color_player, - data_raw[dpos_color], - palette_section, - 0, - 0)) - - dpos_color += 1 - chunk_pos += 1 - - # Skip to next chunk - if chunk_pos > 3: - chunk_pos = 0 - dpos_color += 1 # Skip palette section block - palette_section_block = data_raw[dpos_color + 4] - - else: - raise Exception( - f"unknown smx main graphics layer drawing command: " + - f"{cmd:#x} in row {rowid:d}" - ) - - # Process next command - dpos_cmd += 1 - - if SMXLayerVariant is SMXMainLayer8to5 or SMXLayerVariant is SMXMainLayer4plus1: - return dpos_cmd, dpos_color, chunk_pos, row_data - if SMXLayerVariant is SMXOutlineLayer or SMXLayerVariant is SMXShadowLayer: - return dpos_cmd, dpos_cmd, chunk_pos, row_data - class SMX: """ @@ -678,22 +327,7 @@ cdef class SMXLayer: # pixel matrix representing the final image cdef vector[vector[pixel]] pcolor - # memory pointer - cdef const uint8_t[::1] data_raw - - # current command - cdef int cmd_offset - - # current color - cdef int color_offset - - # current chunk position - cdef int chunk_pos - - # rows - cdef size_t row_count - - def __init__(self, layer_header, data): + def init(self, SMXLayerVariant variant, layer_header, data): """ SMX layer definition superclass. There can be various types of layers inside an SMX frame. @@ -708,18 +342,19 @@ cdef class SMXLayer: if not (isinstance(data, bytes) or isinstance(data, bytearray)): raise ValueError("Layer data must be some bytes object") + # memory pointer # convert the bytes obj to char* - self.data_raw = data + cdef const uint8_t[::1] data_raw = data cdef unsigned short left cdef unsigned short right cdef size_t i - self.row_count = self.info.size[1] - self.pcolor.reserve(self.row_count) + cdef size_t row_count = self.info.size[1] + self.pcolor.reserve(row_count) # process bondary table - for i in range(self.row_count): + for i in range(row_count): outline_entry_position = (self.info.outline_table_offset + i * SMXLayer.smp_layer_row_edge.size) @@ -733,9 +368,379 @@ cdef class SMXLayer: else: self.boundaries.push_back(boundary_def(left, right, False)) - self.cmd_offset = self.info.qdl_command_table_offset - self.color_offset = self.info.qdl_color_table_offset - self.chunk_pos = 0 + cdef int cmd_offset = self.info.qdl_command_table_offset + cdef int color_offset = self.info.qdl_color_table_offset + cdef int chunk_pos = 0 + + # process cmd table + for i in range(row_count): + cmd_offset, color_offset, chunk_pos, row_data = \ + self.create_color_row(variant, data_raw, i, cmd_offset, color_offset, chunk_pos) + + self.pcolor.push_back(row_data) + + + cdef inline (int, int, int, vector[pixel]) create_color_row(self, + SMXLayerVariant variant, + const uint8_t[::1] &data_raw, + Py_ssize_t rowid, + int cmd_offset, + int color_offset, + int chunk_pos): + """ + Extract colors (pixels) for the given rowid. + + :param rowid: Index of the current row in the layer. + :param cmd_offset: Offset of the command table of the layer. + :param color_offset: Offset of the color table of the layer. + :param chunk_pos: Current position in the compressed chunk. + """ + + cdef vector[pixel] row_data + cdef Py_ssize_t i + + cdef int first_cmd_offset = cmd_offset + cdef int first_color_offset = color_offset + cdef boundary_def bounds = self.boundaries[rowid] + cdef size_t pixel_count = self.info.size[0] + + # preallocate memory + row_data.reserve(pixel_count) + + # row is completely transparent + if bounds.full_row: + for _ in range(pixel_count): + row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) + + return cmd_offset, color_offset, chunk_pos, row_data + + # start drawing the left transparent space + for i in range(bounds.left): + row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) + + # process the drawing commands for this row. + next_cmd_offset, next_color_offset, chunk_pos, row_data = \ + self.process_drawing_cmds( + variant, + data_raw, + row_data, + rowid, + first_cmd_offset, + first_color_offset, + chunk_pos, + pixel_count - bounds.right + ) + + # finish by filling up the right transparent space + for i in range(bounds.right): + row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) + + # verify size of generated row + if row_data.size() != pixel_count: + got = row_data.size() + summary = "%d/%d -> row %d, layer type %d, offset %d / %#x" % ( + got, pixel_count, rowid, self.info.layer_type, + first_cmd_offset, first_cmd_offset + ) + txt = "got %%s pixels than expected: %s, missing: %d" % ( + summary, abs(pixel_count - got)) + + raise Exception(txt % ("LESS" if got < pixel_count else "MORE")) + + return next_cmd_offset, next_color_offset, chunk_pos, row_data + + + @cython.boundscheck(False) + cdef inline (int, int, int, vector[pixel]) process_drawing_cmds(self, + SMXLayerVariant variant, + const uint8_t[::1] &data_raw, + vector[pixel] &row_data, + Py_ssize_t rowid, + Py_ssize_t first_cmd_offset, + Py_ssize_t first_color_offset, + int chunk_pos, + size_t expected_size): + """ + extract colors (pixels) for the drawing commands that were + compressed with 8to5 compression. + """ + # position in the command array, we start at the first command of this row + cdef Py_ssize_t dpos_cmd = first_cmd_offset + + # Position in the pixel data array + cdef Py_ssize_t dpos_color = first_color_offset + + # Position in the compression chunk. + cdef bool odd = chunk_pos + cdef int px_dpos = 0 # For loop iterator + + # is the end of the current row reached? + cdef bool eor = False + + cdef uint8_t cmd = 0 + cdef uint8_t lower_crumb = 0 + cdef int pixel_count = 0 + cdef vector[uint8_t] pixel_data + pixel_data.reserve(4) + + # Pixel data temporary values that need further decompression + cdef uint8_t pixel_data_odd_0 = 0 + cdef uint8_t pixel_data_odd_1 = 0 + cdef uint8_t pixel_data_odd_2 = 0 + cdef uint8_t pixel_data_odd_3 = 0 + + # Mask for even indices + # cdef uint8_t pixel_mask_even_0 = 0xFF + cdef uint8_t pixel_mask_even_1 = 0b00000011 + cdef uint8_t pixel_mask_even_2 = 0b11110000 + cdef uint8_t pixel_mask_even_3 = 0b00111111 + + if SMXLayerVariant is SMXMainLayer8to5 or SMXLayerVariant is SMXMainLayer4plus1: + # Position in the pixel data array + dpos_color = first_color_offset + + cdef uint8_t palette_section_block = 0 + cdef uint8_t palette_section = 0 + cdef uint8_t nextbyte = 0 + + # work through commands till end of row. + while not eor: + if row_data.size() > expected_size: + raise Exception( + f"Only {expected_size:d} pixels should be drawn in row {rowid:d} " + f"with layer type {self.info.layer_type:#x}, but we have {row_data.size():d} " + f"already!" + ) + + # fetch drawing instruction + cmd = data_raw[dpos_cmd] + + # Last 2 bits store command type + lower_crumb = 0b00000011 & cmd + + if lower_crumb == 0b00000011: + # eor (end of row) command, this row is finished now. + eor = True + dpos_cmd += 1 + + # shadows sometimes need an extra pixel at + # the end + if SMXLayerVariant is SMXShadowLayer: + if row_data.size() < expected_size: + # copy the last drawn pixel + # (still stored in nextbyte) + # + # TODO: confirm that this is the + # right way to do it + row_data.push_back(pixel(color_shadow, + nextbyte, 0, 0, 0)) + continue + + elif lower_crumb == 0b00000000: + # skip command + # draw 'count' transparent pixels + # count = (cmd >> 2) + 1 + + pixel_count = (cmd >> 2) + 1 + + for _ in range(pixel_count): + row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) + + elif lower_crumb == 0b00000001: + # color_list command + # draw the following 'count' pixels + # pixels are stored in 5 byte chunks + # even pixel indices have their info stored + # in byte[0] - byte[3]. odd pixel indices have + # their info stored in byte[1] - byte[4]. + # count = (cmd >> 2) + 1 + + pixel_count = (cmd >> 2) + 1 + + if SMXLayerVariant is SMXMainLayer8to5: + pixel_data.reserve(4) + for _ in range(pixel_count): + # Start fetching pixel data + if odd: + # Odd indices require manual extraction of each of the 4 values + + # Palette index. Essentially a rotation of (byte[1]byte[2]) + # by 6 to the left, then masking with 0x00FF. + pixel_data_odd_0 = data_raw[dpos_color + 1] + pixel_data_odd_1 = data_raw[dpos_color + 2] + pixel_data.push_back((pixel_data_odd_0 >> 2) | (pixel_data_odd_1 << 6)) + + # Palette section. Described in byte[2] in bits 4-5. + pixel_data.push_back((pixel_data_odd_1 >> 2) & 0x03) + + # Damage mask 1. Essentially a rotation of (byte[3]byte[4]) + # by 6 to the left, then masking with 0x00F0. + pixel_data_odd_2 = data_raw[dpos_color + 3] + pixel_data_odd_3 = data_raw[dpos_color + 4] + pixel_data.push_back(((pixel_data_odd_2 >> 2) | (pixel_data_odd_3 << 6)) & 0xF0) + + # Damage mask 2. Described in byte[4] in bits 0-5. + pixel_data.push_back((pixel_data_odd_3 >> 2) & 0x3F) + + row_data.push_back(pixel(color_standard, + pixel_data[0], + pixel_data[1], + pixel_data[2], + pixel_data[3])) + + # Go to next pixel + dpos_color += 5 + + else: + # Even indices can be read "as is". They just have to be masked. + for i in range(4): + pixel_data.push_back(data_raw[dpos_color + i]) + + row_data.push_back(pixel(color_standard, + pixel_data[0], + pixel_data[1] & pixel_mask_even_1, + pixel_data[2] & pixel_mask_even_2, + pixel_data[3] & pixel_mask_even_3)) + + odd = not odd + pixel_data.clear() + + if SMXLayerVariant is SMXMainLayer4plus1: + palette_section_block = data_raw[dpos_color + (4 - chunk_pos)] + + for _ in range(pixel_count): + # Start fetching pixel data + palette_section = ( + palette_section_block >> (2 * chunk_pos)) & 0x03 + row_data.push_back(pixel(color_standard, + data_raw[dpos_color], + palette_section, + 0, + 0)) + + dpos_color += 1 + chunk_pos += 1 + + # Skip to next chunk + if chunk_pos > 3: + chunk_pos = 0 + dpos_color += 1 # Skip palette section block + palette_section_block = data_raw[dpos_color + 4] + + if SMXLayerVariant is SMXShadowLayer: + for _ in range(pixel_count): + dpos_cmd += 1 + nextbyte = data_raw[dpos_cmd] + + row_data.push_back(pixel(color_shadow, + nextbyte, 0, 0, 0)) + + if SMXLayerVariant is SMXOutlineLayer: + # we don't know the color the game wants + # so we just draw index 0 + row_data.push_back(pixel(color_outline, + 0, 0, 0, 0)) + + elif lower_crumb == 0b00000010: + if SMXLayerVariant is SMXMainLayer8to5: + # player_color command + # draw the following 'count' pixels + # pixels are stored in 5 byte chunks + # even pixel indices have their info stored + # in byte[0] - byte[3]. odd pixel indices have + # their info stored in byte[1] - byte[4]. + # count = (cmd >> 2) + 1 + + pixel_count = (cmd >> 2) + 1 + + for _ in range(pixel_count): + # Start fetching pixel data + if odd: + # Odd indices require manual extraction of each of the 4 values + + # Palette index. Essentially a rotation of (byte[1]byte[2]) + # by 6 to the left, then masking with 0x00FF. + pixel_data_odd_0 = data_raw[dpos_color + 1] + pixel_data_odd_1 = data_raw[dpos_color + 2] + pixel_data.push_back((pixel_data_odd_0 >> 2) | (pixel_data_odd_1 << 6)) + + # Palette section. Described in byte[2] in bits 4-5. + pixel_data.push_back((pixel_data_odd_1 >> 2) & 0x03) + + # Damage modifier 1. Essentially a rotation of (byte[3]byte[4]) + # by 6 to the left, then masking with 0x00F0. + pixel_data_odd_2 = data_raw[dpos_color + 3] + pixel_data_odd_3 = data_raw[dpos_color + 4] + pixel_data.push_back(((pixel_data_odd_2 >> 2) | (pixel_data_odd_3 << 6)) & 0xF0) + + # Damage modifier 2. Described in byte[4] in bits 0-5. + pixel_data.push_back((pixel_data_odd_3 >> 2) & 0x3F) + + row_data.push_back(pixel(color_player, + pixel_data[0], + pixel_data[1], + pixel_data[2], + pixel_data[3])) + + # Go to next pixel + dpos_color += 5 + + else: + # Even indices can be read "as is". They just have to be masked. + for px_dpos in range(4): + pixel_data.push_back(data_raw[dpos_color + px_dpos]) + + row_data.push_back(pixel(color_player, + pixel_data[0], + pixel_data[1] & pixel_mask_even_1, + pixel_data[2] & pixel_mask_even_2, + pixel_data[3] & pixel_mask_even_3)) + + odd = not odd + pixel_data.clear() + + elif SMXLayerVariant is SMXMainLayer4plus1: + # player_color command + # draw the following 'count' pixels + # 4 pixels are stored in every 5 byte chunk. + # palette indices are contained in byte[0] - byte[3] + # palette sections are stored in byte[4] + # count = (cmd >> 2) + 1 + + pixel_count = (cmd >> 2) + 1 + + for _ in range(pixel_count): + # Start fetching pixel data + palette_section = (palette_section_block >> (2 * chunk_pos)) & 0x03 + row_data.push_back(pixel(color_player, + data_raw[dpos_color], + palette_section, + 0, + 0)) + + dpos_color += 1 + chunk_pos += 1 + + # Skip to next chunk + if chunk_pos > 3: + chunk_pos = 0 + dpos_color += 1 # Skip palette section block + palette_section_block = data_raw[dpos_color + 4] + + else: + raise Exception( + f"unknown smx main graphics layer drawing command: " + + f"{cmd:#x} in row {rowid:d}" + ) + + # Process next command + dpos_cmd += 1 + + if SMXLayerVariant is SMXMainLayer8to5 or SMXLayerVariant is SMXMainLayer4plus1: + return dpos_cmd, dpos_color, chunk_pos, row_data + elif SMXLayerVariant is SMXOutlineLayer or SMXLayerVariant is SMXShadowLayer: + return dpos_cmd, dpos_cmd, chunk_pos, row_data + def get_picture_data(self, palette): """ @@ -776,55 +781,22 @@ cdef class SMXMainLayer8to5(SMXLayer): """ def __init__(self, layer_header, data): - super().__init__(layer_header, data) - # process cmd table - for i in range(self.row_count): - cmd_offset, color_offset, chunk_pos, row_data = create_color_row( - self, i) - self.cmd_offset = cmd_offset - self.color_offset = color_offset - self.chunk_pos = chunk_pos - - self.pcolor.push_back(row_data) + self.init(self, layer_header, data) cdef class SMXMainLayer4plus1(SMXLayer): def __init__(self, layer_header, data): - super().__init__(layer_header, data) - # process cmd table - for i in range(self.row_count): - cmd_offset, color_offset, chunk_pos, row_data = create_color_row( - self, i) - self.cmd_offset = cmd_offset - self.color_offset = color_offset - self.chunk_pos = chunk_pos + self.init(self, layer_header, data) - self.pcolor.push_back(row_data) cdef class SMXOutlineLayer(SMXLayer): def __init__(self, layer_header, data): - super().__init__(layer_header, data) - # process cmd table - for i in range(self.row_count): - cmd_offset, color_offset, chunk_pos, row_data = create_color_row( - self, i) - self.cmd_offset = cmd_offset - self.color_offset = color_offset - self.chunk_pos = chunk_pos + self.init(self, layer_header, data) - self.pcolor.push_back(row_data) cdef class SMXShadowLayer(SMXLayer): def __init__(self, layer_header, data): - super().__init__(layer_header, data) - # process cmd table - for i in range(self.row_count): - cmd_offset, color_offset, chunk_pos, row_data = create_color_row( - self, i) - self.cmd_offset = cmd_offset - self.color_offset = color_offset - self.chunk_pos = chunk_pos + self.init(self, layer_header, data) - self.pcolor.push_back(row_data) From 64782e68bbc14c99b2b82bed5d370ce031178d6d Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Sun, 19 Jan 2025 22:06:07 +0000 Subject: [PATCH 087/152] split conditionals --- .../convert/value_object/read/media/smp.pyx | 65 ++++++++++--------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/openage/convert/value_object/read/media/smp.pyx b/openage/convert/value_object/read/media/smp.pyx index 9ad253c5c3..40787250ec 100644 --- a/openage/convert/value_object/read/media/smp.pyx +++ b/openage/convert/value_object/read/media/smp.pyx @@ -439,13 +439,14 @@ cdef class SMPLayer: # eol (end of line) command, this row is finished now. eor = True - if SMPLayerVariant is SMPShadowLayer and row_data.size() < expected_size: - # copy the last drawn pixel - # (still stored in nextbyte) - # - # TODO: confirm that this is the - # right way to do it - row_data.push_back(pixel(color_shadow, nextbyte, 0, 0, 0)) + if SMPLayerVariant is SMPShadowLayer: + if row_data.size() < expected_size: + # copy the last drawn pixel + # (still stored in nextbyte) + # + # TODO: confirm that this is the + # right way to do it + row_data.push_back(pixel(color_shadow, nextbyte, 0, 0, 0)) continue @@ -488,31 +489,37 @@ cdef class SMPLayer: pixel_data[3] & 0x1F)) # remove "usage" bit here pixel_data.clear() - elif lower_crumb == 0b00000010 and (SMPLayerVariant is SMPMainLayer or SMPLayerVariant is SMPShadowLayer): - # player_color command - # draw the following 'count' pixels - # pixels are stored as 4 byte palette and meta infos - # count = (cmd >> 2) + 1 + elif lower_crumb == 0b00000010: + if (SMPLayerVariant is SMPMainLayer or SMPLayerVariant is SMPShadowLayer): + # player_color command + # draw the following 'count' pixels + # pixels are stored as 4 byte palette and meta infos + # count = (cmd >> 2) + 1 - pixel_count = (cmd >> 2) + 1 + pixel_count = (cmd >> 2) + 1 - for _ in range(pixel_count): - if SMPLayerVariant is SMPShadowLayer: - dpos += 1 - nextbyte = data_raw[dpos] - row_data.push_back(pixel(color_shadow, - nextbyte, 0, 0, 0)) - else: - for _ in range(4): + for _ in range(pixel_count): + if SMPLayerVariant is SMPShadowLayer: dpos += 1 - pixel_data.push_back(data_raw[dpos]) - - row_data.push_back(pixel(color_player, - pixel_data[0], - pixel_data[1], - pixel_data[2], - pixel_data[3] & 0x1F)) # remove "usage" bit here - pixel_data.clear() + nextbyte = data_raw[dpos] + row_data.push_back(pixel(color_shadow, + nextbyte, 0, 0, 0)) + else: + for _ in range(4): + dpos += 1 + pixel_data.push_back(data_raw[dpos]) + + row_data.push_back(pixel(color_player, + pixel_data[0], + pixel_data[1], + pixel_data[2], + pixel_data[3] & 0x1F)) # remove "usage" bit here + pixel_data.clear() + else: + raise Exception( + f"unknown smp {self.info.layer_type} layer drawing command: " + + f"{cmd:#x} in row {rowid:d}" + ) else: raise Exception( From 71391d3ff2f63554e9bdf2bc686c859885a5c3b1 Mon Sep 17 00:00:00 2001 From: jere8184 Date: Fri, 24 Jan 2025 21:00:46 +0000 Subject: [PATCH 088/152] Update .mailmap --- .mailmap | 3 --- 1 file changed, 3 deletions(-) diff --git a/.mailmap b/.mailmap index 3d8b8ec12e..5acbd1b581 100644 --- a/.mailmap +++ b/.mailmap @@ -21,8 +21,5 @@ Tobias Feldballe Jonas Borchelt Derek Frogget <114030121+derekfrogget@users.noreply.github.com> Nikhil Ghosh -<<<<<<< HEAD David Wever <56411717+dmwever@users.noreply.github.com> -======= Ngô Xuân Minh ->>>>>>> e8251ff6 (copying.md) From 68e500b65de7416b051cc030c9d437948d32b2e6 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 27 Jan 2025 03:22:14 +0100 Subject: [PATCH 089/152] convert: Make fused type work without inheritance. --- .../convert/value_object/read/media/smx.pyx | 58 ++++++++----------- 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/openage/convert/value_object/read/media/smx.pyx b/openage/convert/value_object/read/media/smx.pyx index 2e60a84a00..7fb6a39f7f 100644 --- a/openage/convert/value_object/read/media/smx.pyx +++ b/openage/convert/value_object/read/media/smx.pyx @@ -61,12 +61,24 @@ cdef public dict LAYER_TYPES = { } +cdef class SMXMainLayer8to5: + pass + +cdef class SMXMainLayer4plus1: + pass + +cdef class SMXShadowLayer: + pass + +cdef class SMXOutlineLayer: + pass + + ctypedef fused SMXLayerVariant: SMXMainLayer8to5 SMXMainLayer4plus1 - SMXOutlineLayer SMXShadowLayer - + SMXOutlineLayer class SMX: @@ -190,16 +202,17 @@ class SMX: if layer_type is SMXLayerType.MAIN: if layer_header.compression_type == 0x08: - self.main_frames.append(SMXMainLayer8to5(layer_header, data)) + self.main_frames.append(SMXLayer(SMXMainLayer8to5(), layer_header, data)) elif layer_header.compression_type == 0x00: - self.main_frames.append(SMXMainLayer4plus1(layer_header, data)) + self.main_frames.append(SMXLayer(SMXMainLayer4plus1(), layer_header, data)) elif layer_type is SMXLayerType.SHADOW: - self.shadow_frames.append(SMXShadowLayer(layer_header, data)) + self.shadow_frames.append(SMXLayer(SMXShadowLayer(), layer_header, data)) elif layer_type is SMXLayerType.OUTLINE: - self.outline_frames.append(SMXOutlineLayer(layer_header, data)) + # self.outline_frames.append(SMXLayer(SMXOutlineLayer(), layer_header, data)) + pass def get_frames(self, layer: int = 0): """ @@ -327,6 +340,9 @@ cdef class SMXLayer: # pixel matrix representing the final image cdef vector[vector[pixel]] pcolor + def __init__(self, variant, layer_header, data) -> None: + self.init(variant, layer_header, data) + def init(self, SMXLayerVariant variant, layer_header, data): """ SMX layer definition superclass. There can be various types of @@ -376,7 +392,6 @@ cdef class SMXLayer: for i in range(row_count): cmd_offset, color_offset, chunk_pos, row_data = \ self.create_color_row(variant, data_raw, i, cmd_offset, color_offset, chunk_pos) - self.pcolor.push_back(row_data) @@ -438,8 +453,8 @@ cdef class SMXLayer: # verify size of generated row if row_data.size() != pixel_count: got = row_data.size() - summary = "%d/%d -> row %d, layer type %d, offset %d / %#x" % ( - got, pixel_count, rowid, self.info.layer_type, + summary = "%d/%d -> row %d, layer type %s, offset %d / %#x" % ( + got, pixel_count, rowid, repr(self.info.layer_type), first_cmd_offset, first_cmd_offset ) txt = "got %%s pixels than expected: %s, missing: %d" % ( @@ -775,31 +790,6 @@ cdef class SMXLayer: return repr(self.info) -cdef class SMXMainLayer8to5(SMXLayer): - """ - Compressed SMP layer (compression type 8to5) for the main graphics sprite. - """ - - def __init__(self, layer_header, data): - self.init(self, layer_header, data) - -cdef class SMXMainLayer4plus1(SMXLayer): - def __init__(self, layer_header, data): - self.init(self, layer_header, data) - - -cdef class SMXOutlineLayer(SMXLayer): - def __init__(self, layer_header, data): - self.init(self, layer_header, data) - - -cdef class SMXShadowLayer(SMXLayer): - def __init__(self, layer_header, data): - self.init(self, layer_header, data) - - - - @cython.boundscheck(False) @cython.wraparound(False) cdef numpy.ndarray determine_rgba_matrix(vector[vector[pixel]] &image_matrix, From 09cfc3d20b08a442e46d67a11ee956f931ed9143 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 27 Jan 2025 03:47:49 +0100 Subject: [PATCH 090/152] convert: Fix missing loop in outline layer parsing. --- openage/convert/value_object/read/media/smx.pyx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openage/convert/value_object/read/media/smx.pyx b/openage/convert/value_object/read/media/smx.pyx index 7fb6a39f7f..565dcb611e 100644 --- a/openage/convert/value_object/read/media/smx.pyx +++ b/openage/convert/value_object/read/media/smx.pyx @@ -211,8 +211,7 @@ class SMX: self.shadow_frames.append(SMXLayer(SMXShadowLayer(), layer_header, data)) elif layer_type is SMXLayerType.OUTLINE: - # self.outline_frames.append(SMXLayer(SMXOutlineLayer(), layer_header, data)) - pass + self.outline_frames.append(SMXLayer(SMXOutlineLayer(), layer_header, data)) def get_frames(self, layer: int = 0): """ @@ -653,8 +652,9 @@ cdef class SMXLayer: if SMXLayerVariant is SMXOutlineLayer: # we don't know the color the game wants # so we just draw index 0 - row_data.push_back(pixel(color_outline, - 0, 0, 0, 0)) + for _ in range(pixel_count): + row_data.push_back(pixel(color_outline, + 0, 0, 0, 0)) elif lower_crumb == 0b00000010: if SMXLayerVariant is SMXMainLayer8to5: From 3b82ce2d37aa1d5b80779ffa5c303669c0fc01ea Mon Sep 17 00:00:00 2001 From: Leo Peng Date: Fri, 31 Jan 2025 11:54:47 +0800 Subject: [PATCH 091/152] doc: fix incorrect fontconfig path in Windows build guide --- doc/build_instructions/windows_msvc.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/build_instructions/windows_msvc.md b/doc/build_instructions/windows_msvc.md index af889d8b99..e31bcf79cd 100644 --- a/doc/build_instructions/windows_msvc.md +++ b/doc/build_instructions/windows_msvc.md @@ -82,7 +82,7 @@ _Note:_ If you want to download and build Nyan automatically add `-DDOWNLOAD_NYA - Select all `ttf\DejaVuSerif*.ttf` files, right click and click `Install for all users`. _Note:_ This will require administrator rights. - - Set the `FONTCONFIG_PATH` environment variable to `\installed\\tools\fontconfig\fonts\`. + - Set the `FONTCONFIG_PATH` environment variable to `\installed\\tools\fontconfig\fonts\` or `\installed\\etc\fonts\`. - Copy `fontconfig\57-dejavu-serif.conf` to `%FONTCONFIG_PATH%\conf.d`. - [Optional] Set the `AGE2DIR` environment variable to the AoE 2 installation directory. - Set `QML2_IMPORT_PATH` to `\installed\\qml` or for prebuilt Qt `\\\qml` From 64206f07597b454b594be89cd99c738ff03ba318 Mon Sep 17 00:00:00 2001 From: Jonas Jelten Date: Thu, 6 Feb 2025 22:09:22 +0100 Subject: [PATCH 092/152] readme: add kevin badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4cd98b9070..c0bcf4a02c 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ If you're interested, we wrote detailed explanations on our blog: [Part 1](https | Operating System | Build status | | :-----------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| Debian Sid | [Todo: Kevin #11] | +| Debian Sid | [![Kevin CI status](https://cidata.sft.lol/openage/branches/master/status.svg)](/kevinfile) | | Ubuntu 24.04 LTS | [![Ubuntu 24.04 build status](https://github.com/SFTTech/openage/actions/workflows/ubuntu-24.04.yml/badge.svg?branch=master)](https://github.com/SFTtech/openage/actions/workflows/ubuntu-24.04.yml) | | macOS | [![macOS build status](https://github.com/SFTtech/openage/workflows/macOS-CI/badge.svg)](https://github.com/SFTtech/openage/actions?query=workflow%3AmacOS-CI) | | Windows Server 2019 | [![Windows Server 2019 build status](https://github.com/SFTtech/openage/actions/workflows/windows-server-2019.yml/badge.svg?branch=master)](https://github.com/SFTtech/openage/actions/workflows/windows-server-2019.yml) | From cb5f61e3b25c269bf54c69e163c2faccf8487f7b Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 9 Feb 2025 21:31:49 +0100 Subject: [PATCH 093/152] convert: Make fused type work without inheritance. --- .../convert/value_object/read/media/smp.pyx | 66 +++++++++---------- 1 file changed, 30 insertions(+), 36 deletions(-) diff --git a/openage/convert/value_object/read/media/smp.pyx b/openage/convert/value_object/read/media/smp.pyx index 40787250ec..a67efd87bb 100644 --- a/openage/convert/value_object/read/media/smp.pyx +++ b/openage/convert/value_object/read/media/smp.pyx @@ -62,6 +62,24 @@ cdef public dict LAYER_TYPES = { } +cdef class SMPMainLayer: + pass + + +cdef class SMPShadowLayer: + pass + + +cdef class SMPOutlineLayer: + pass + + +ctypedef fused SMPLayerVariant: + SMPMainLayer + SMPShadowLayer + SMPOutlineLayer + + class SMP: """ Class for reading/converting the SMP image format (successor of SLP). @@ -155,16 +173,16 @@ class SMP: if layer_header.layer_type == 0x02: # layer that store the main graphic - self.main_frames.append(SMPMainLayer(layer_header, data)) + self.main_frames.append(SMPLayer(SMPMainLayer(), layer_header, data)) elif layer_header.layer_type == 0x04: # layer that stores a shadow - self.shadow_frames.append(SMPShadowLayer(layer_header, data)) + self.shadow_frames.append(SMPLayer(SMPShadowLayer(), layer_header, data)) elif layer_header.layer_type == 0x08 or \ layer_header.layer_type == 0x10: # layer that stores an outline - self.outline_frames.append(SMPOutlineLayer(layer_header, data)) + self.outline_frames.append(SMPLayer(SMPOutlineLayer(), layer_header, data)) else: raise Exception( @@ -253,11 +271,6 @@ class SMPLayerHeader: ) return "".join(ret) -ctypedef fused SMPLayerVariant: - SMPMainLayer - SMPShadowLayer - SMPOutlineLayer - cdef class SMPLayer: """ @@ -288,6 +301,9 @@ cdef class SMPLayer: # pixel matrix representing the final image cdef vector[vector[pixel]] pcolor + def __init__(self, variant, layer_header, data) -> None: + self.init(variant, layer_header, data) + def init(self, SMPLayerVariant variant, layer_header, data): self.info = layer_header @@ -540,6 +556,12 @@ cdef class SMPLayer: """ return determine_rgba_matrix(self.pcolor, palette) + def get_damage_mask(self): + """ + Convert the 4th pixel byte to a mask used for damaged units. + """ + return determine_damage_matrix(self.pcolor) + def get_hotspot(self): """ Return the layer's hotspot (the "center" of the image) @@ -559,34 +581,6 @@ cdef class SMPLayer: return repr(self.info) -cdef class SMPMainLayer(SMPLayer): - """ - SMPLayer for the main graphics sprite. - """ - - def __init__(self, layer_header, data): - self.init(self ,layer_header, data) - - def get_damage_mask(self): - """ - Convert the 4th pixel byte to a mask used for damaged units. - """ - return determine_damage_matrix(self.pcolor) - - -cdef class SMPShadowLayer(SMPLayer): - """ - SMPLayer for the shadow graphics. - """ - - def __init__(self, layer_header, data): - self.init(self ,layer_header, data) - -cdef class SMPOutlineLayer(SMPLayer): - def __init__(self, layer_header, data): - self.init(self, layer_header, data) - - @cython.boundscheck(False) @cython.wraparound(False) cdef numpy.ndarray determine_rgba_matrix(vector[vector[pixel]] &image_matrix, From 6f8689d617ce6e08e30c4838db28b6586f5a98ca Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 9 Feb 2025 21:56:20 +0100 Subject: [PATCH 094/152] convert: Add comments to SMP/SMX code. --- .../convert/value_object/read/media/smp.pyx | 137 ++++++++++++++---- .../convert/value_object/read/media/smx.pyx | 111 ++++++++------ 2 files changed, 180 insertions(+), 68 deletions(-) diff --git a/openage/convert/value_object/read/media/smp.pyx b/openage/convert/value_object/read/media/smp.pyx index a67efd87bb..03de7dd200 100644 --- a/openage/convert/value_object/read/media/smp.pyx +++ b/openage/convert/value_object/read/media/smp.pyx @@ -22,6 +22,7 @@ from libcpp.vector cimport vector endianness = "< " +# Boundary of a row in a SMP layer. cdef struct boundary_def: Py_ssize_t left Py_ssize_t right @@ -63,17 +64,27 @@ cdef public dict LAYER_TYPES = { cdef class SMPMainLayer: + """ + Main graphic layer of an SMP. Stores the color information. + """ pass cdef class SMPShadowLayer: + """ + Shadow layer of an SMP. + """ pass cdef class SMPOutlineLayer: + """ + Outline layer of an SMP. + """ pass +# fused type for the layer variants ctypedef fused SMPLayerVariant: SMPMainLayer SMPShadowLayer @@ -83,7 +94,7 @@ ctypedef fused SMPLayerVariant: class SMP: """ Class for reading/converting the SMP image format (successor of SLP). - This format is used to store all graphics within AoE2: Definitive Edition. + This format is used to store all graphics within AoE2: Definitive Edition (Beta). """ # struct smp_header { @@ -122,15 +133,23 @@ class SMP: # }; smp_layer_header = Struct(endianness + "i i i i I I I I") - def __init__(self, data): + def __init__(self, data: bytes) -> None: + """ + Read the SMP file and store the frames in the object. + + :param data: SMP file data. + """ smp_header = SMP.smp_header.unpack_from(data) signature, version, frame_count, facet_count, frames_per_facet,\ checksum, file_size, source_format, comment = smp_header dbg("SMP") + spam(" signature: %s", signature.decode('ascii')) + spam(" version: %s", version) dbg(" frame count: %s", frame_count) dbg(" facet count: %s", facet_count) dbg(" facets per animation: %s", frames_per_facet) + spam(" checksum: %s", checksum) dbg(" file size: %s B", file_size) dbg(" source format: %s", source_format) dbg(" comment: %s", comment.decode('ascii')) @@ -188,9 +207,10 @@ class SMP: raise Exception( f"unknown layer type: {layer_header.layer_type:#x} at offset {layer_header_offset:#x}" ) + spam(layer_header) - def get_frames(self, layer: int = 0): + def get_frames(self, layer: int = 0) -> list[SMPLayer]: """ Get the frames in the SMP. @@ -198,7 +218,6 @@ class SMP: - 0 = main graphics - 1 = shadow graphics - 2 = outline - :type layer: int """ cdef list frames @@ -221,7 +240,7 @@ class SMP: return frames - def __str__(self): + def __str__(self) -> str: ret = list() ret.extend([repr(self), "\n", SMPLayerHeader.repr_header(), "\n"]) @@ -229,15 +248,39 @@ class SMP: ret.extend([repr(frame), "\n"]) return "".join(ret) - def __repr__(self): + def __repr__(self) -> str: return f"SMP image<{len(self.main_frames):d} frames>" class SMPLayerHeader: - def __init__(self, width, height, hotspot_x, - hotspot_y, layer_type, outline_table_offset, - qdl_table_offset, flags, - frame_offset): + """ + Header of a layer in the SMP file. + """ + def __init__( + self, + width: int, + height: int, + hotspot_x: int, + hotspot_y: int, + layer_type: int, + outline_table_offset: int, + qdl_table_offset: int, + flags: int, + frame_offset: int + ) -> None: + """ + Create a SMP layer header. + + :param width: Width of the layer sprites. + :param height: Height of the layer sprites. + :param hotspot_x: X coordinate of the anchor point. + :param hotspot_y: Y coordinate of the anchor point. + :param layer_type: Type of the layer. + :param outline_table_offset: Offset of the outline table. + :param qdl_table_offset: Offset of the pixel command table. + :param flags: Flags of the layer. + :param frame_offset: Offset of the frame. + """ self.size = (width, height) self.hotspot = (hotspot_x, hotspot_y) @@ -252,16 +295,19 @@ class SMPLayerHeader: # the absolute offset of the frame self.frame_offset = frame_offset + # flags + self.flags = flags + self.palette_number = -1 @staticmethod - def repr_header(): + def repr_header() -> str: return ("width x height | hotspot x/y | " "layer type | " "offset (outline table|qdl table)" ) - def __repr__(self): + def __repr__(self) -> str: ret = ( "% 5d x% 7d | " % self.size, "% 4d /% 5d | " % self.hotspot, @@ -274,7 +320,7 @@ class SMPLayerHeader: cdef class SMPLayer: """ - one layer inside the SMP. you can imagine it as a frame of a video. + Layer inside the SMP. you can imagine it as a frame of an animation. """ # struct smp_frame_row_edge { @@ -301,10 +347,29 @@ cdef class SMPLayer: # pixel matrix representing the final image cdef vector[vector[pixel]] pcolor - def __init__(self, variant, layer_header, data) -> None: + def __init__( + self, + variant, # this argument must not be typed because cython can't handle it + layer_header: SMPLayerHeader, + data: bytes + ) -> None: + """ + Create a SMP layer. + + :param variant: Type of the layer. + :param layer_header: Header information of the layer. + :param data: Layer data. + """ self.init(variant, layer_header, data) - def init(self, SMPLayerVariant variant, layer_header, data): + def init(self, SMPLayerVariant variant, layer_header: SMPLayerHeader, data: bytes) -> None: + """ + Read the pixel information of the SMP layer. + + :param variant: Type of the layer. + :param layer_header: Header information of the layer. + :param data: Layer data. + """ self.info = layer_header if not (isinstance(data, bytes) or isinstance(data, bytearray)): @@ -355,7 +420,11 @@ cdef class SMPLayer: const uint8_t[::1] &data_raw, Py_ssize_t rowid): """ - extract colors (pixels) for the given rowid. + Extract colors (pixels) for a pixel row in the layer. + + :param variant: Type of the layer. + :param data_raw: Raw data of the layer. + :param rowid: Index of the current row in the layer. """ cdef vector[pixel] row_data @@ -417,8 +486,14 @@ cdef class SMPLayer: Py_ssize_t first_cmd_offset, size_t expected_size): """ - extract colors (pixels) for the drawing commands - found for this row in the SMP frame. + Extract colors (pixels) from the drawing commands for a row in the layer. + + :param variant: Type of the layer. + :param data_raw: Raw data of the layer. + :param row_data: Stores the extracted pixels. May be prefilled with transparent pixels. + :param rowid: Index of the current row in the layer. + :param first_cmd_offset: Offset of the first drawing command in the data. + :param expected_size: Expected number of pixels in the row. """ # position in the data blob, we start at the first command of this row @@ -550,34 +625,41 @@ cdef class SMPLayer: return - def get_picture_data(self, palette): + def get_picture_data(self, palette) -> numpy.ndarray: """ - Convert the palette index matrix to a colored image. + Convert the palette index matrix to a RGBA image. + + :param palette: Color palette used for pixels in the sprite. + :type palette: .colortable.ColorTable + :return: Array of RGBA values. """ return determine_rgba_matrix(self.pcolor, palette) - def get_damage_mask(self): + def get_damage_mask(self) -> numpy.ndarray: """ Convert the 4th pixel byte to a mask used for damaged units. + + :return: Damage mask of the layer. """ return determine_damage_matrix(self.pcolor) - def get_hotspot(self): + def get_hotspot(self) -> tuple[int, int]: """ - Return the layer's hotspot (the "center" of the image) + Return the layer's hotspot (the "center" of the image). + + :return: Hotspot of the layer. """ return self.info.hotspot - def get_palette_number(self): + def get_palette_number(self) -> int: """ Return the layer's palette number. :return: Palette number of the layer. - :rtype: int """ return self.pcolor[0][0].palette & 0b00111111 - def __repr__(self): + def __repr__(self) -> str: return repr(self.info) @@ -587,6 +669,9 @@ cdef numpy.ndarray determine_rgba_matrix(vector[vector[pixel]] &image_matrix, numpy.ndarray[numpy.uint8_t, ndim=2, mode="c"] palette): """ converts a palette index image matrix to an rgba matrix. + + :param image_matrix: A 2-dimensional array of SMP pixels. + :param palette: Color palette used for normal pixels in the sprite. """ cdef size_t height = image_matrix.size() diff --git a/openage/convert/value_object/read/media/smx.pyx b/openage/convert/value_object/read/media/smx.pyx index 565dcb611e..887440cf7b 100644 --- a/openage/convert/value_object/read/media/smx.pyx +++ b/openage/convert/value_object/read/media/smx.pyx @@ -21,6 +21,8 @@ from libcpp.vector cimport vector # SMX files have little endian byte order endianness = "< " + +# Boundary of a row in a SMX layer. cdef struct boundary_def: Py_ssize_t left Py_ssize_t right @@ -62,18 +64,31 @@ cdef public dict LAYER_TYPES = { cdef class SMXMainLayer8to5: + """ + Main graphics layer of an SMX (compressed with 8to5). + """ pass cdef class SMXMainLayer4plus1: + """ + Main graphics layer of an SMX (compressed with 4plus1). + """ pass cdef class SMXShadowLayer: + """ + Shadow layer of an SMX. + """ pass cdef class SMXOutlineLayer: + """ + Outline layer of an SMX. + """ pass +# fused type for SMX layer variants ctypedef fused SMXLayerVariant: SMXMainLayer8to5 SMXMainLayer4plus1 @@ -114,14 +129,12 @@ class SMX: # }; smx_layer_header = Struct(endianness + "H H h h I i") - def __init__(self, data): + def __init__(self, data: bytes): """ - Read an SMX image file. + Read an SMX image file and store the frames in the object. - :param data: File content as bytes. - :type data: bytes, bytearray + :param data: SMX file data. """ - smx_header = SMX.smx_header.unpack_from(data) self.smp_type, version, frame_count, file_size_comp,\ file_size_uncomp, comment = smx_header @@ -213,7 +226,7 @@ class SMX: elif layer_type is SMXLayerType.OUTLINE: self.outline_frames.append(SMXLayer(SMXOutlineLayer(), layer_header, data)) - def get_frames(self, layer: int = 0): + def get_frames(self, layer: int = 0) -> list[SMXLayer]: """ Get the frames in the SMX. @@ -221,7 +234,6 @@ class SMX: - 0 = main graphics - 1 = shadow graphics - 2 = outline - :type layer: int """ cdef list frames @@ -257,16 +269,23 @@ class SMX: class SMXLayerHeader: - def __init__(self, layer_type, frame_type, - palette_number, - width, height, hotspot_x, hotspot_y, - outline_table_offset, - qdl_command_table_offset, - qdl_color_table_offset): + def __init__( + self, + layer_type: SMXLayerType, + frame_type: int, + palette_number: int, + width: int, + height: int, + hotspot_x: int, + hotspot_y: int, + outline_table_offset: int, + qdl_command_table_offset: int, + qdl_color_table_offset: int + ) -> None: """ Stores the header of a layer including additional info about its frame. - :param layer_type: Type of layer. Either main. shadow or outline. + :param layer_type: Type of layer. :param frame_type: Type of the frame the layer belongs to. :param palette_number: Palette number used for pixels in the frame. :param width: Width of layer in pixels. @@ -276,16 +295,6 @@ class SMXLayerHeader: :param outline_table_offset: Absolute position of the layer's outline table in the file. :param qdl_command_table_offset: Absolute position of the layer's command table in the file. :param qdl_color_table_offset: Absolute position of the layer's pixel data table in the file. - :type layer_type: str - :type frame_type: int - :type palette_number: int - :type width: int - :type height: int - :type hotspot_x: int - :type hotspot_y: int - :type outline_table_offset: int - :type qdl_command_table_offset: int - :type qdl_color_table_offset: int """ self.size = (width, height) @@ -301,12 +310,12 @@ class SMXLayerHeader: self.qdl_color_table_offset = qdl_color_table_offset @staticmethod - def repr_header(): + def repr_header() -> str: return ("layer type | width x height | " "hotspot x/y | " ) - def __repr__(self): + def __repr__(self) -> str: ret = ( "% s | " % self.layer_type, "% 5d x% 7d | " % self.size, @@ -320,7 +329,7 @@ class SMXLayerHeader: cdef class SMXLayer: """ - one layer inside the compressed SMP. + Layer inside the compressed SMX. """ # struct smp_layer_row_edge { @@ -339,18 +348,28 @@ cdef class SMXLayer: # pixel matrix representing the final image cdef vector[vector[pixel]] pcolor - def __init__(self, variant, layer_header, data) -> None: + def __init__( + self, + variant, # this argument must not be typed because cython can't handle it + layer_header: SMXLayerHeader, + data: bytes + ) -> None: + """ + Create a SMX layer. + + :param variant: Type of the layer. + :param layer_header: Header information of the layer. + :param data: Layer data. + """ self.init(variant, layer_header, data) - def init(self, SMXLayerVariant variant, layer_header, data): + def init(self, SMXLayerVariant variant, layer_header: SMXLayerHeader, data: bytes) -> None: """ - SMX layer definition superclass. There can be various types of - layers inside an SMX frame. + SMX layer definition. There can be various types of layers inside an SMX frame. + :param variant: Type of the layer. :param layer_header: Header definition of the layer. :param data: File content as bytes. - :type layer_header: SMXLayerHeader - :type data: bytes, bytearray """ self.info = layer_header @@ -402,8 +421,10 @@ cdef class SMXLayer: int color_offset, int chunk_pos): """ - Extract colors (pixels) for the given rowid. + Extract colors (pixels) for a pixel row in the layer. + :param variant: Type of the layer. + :param data_raw: Raw data of the layer. :param rowid: Index of the current row in the layer. :param cmd_offset: Offset of the command table of the layer. :param color_offset: Offset of the color table of the layer. @@ -477,6 +498,15 @@ cdef class SMXLayer: """ extract colors (pixels) for the drawing commands that were compressed with 8to5 compression. + + :param variant: Type of the layer. + :param data_raw: Raw data of the layer. + :param row_data: Stores the extracted pixels. May be prefilled with transparent pixels. + :param rowid: Row index. + :param first_cmd_offset: Offset of the first drawing command in the data. + :param first_color_offset: Offset of the first color command in the data. + :param chunk_pos: Current position in the compressed chunk. + :param expected_size: Expected number of pixels in the row. """ # position in the command array, we start at the first command of this row cdef Py_ssize_t dpos_cmd = first_cmd_offset @@ -757,32 +787,29 @@ cdef class SMXLayer: return dpos_cmd, dpos_cmd, chunk_pos, row_data - def get_picture_data(self, palette): + def get_picture_data(self, palette) -> numpy.ndarray: """ Convert the palette index matrix to a RGBA image. - :param main_palette: Color palette used for pixels in the sprite. - :type main_palette: .colortable.ColorTable + :param palette: Color palette used for pixels in the sprite. + :type palette: .colortable.ColorTable :return: Array of RGBA values. - :rtype: numpy.ndarray """ return determine_rgba_matrix(self.pcolor, palette) - def get_hotspot(self): + def get_hotspot(self) -> tuple[int, int]: """ Return the layer's hotspot (the "center" of the image). :return: Hotspot of the layer. - :rtype: tuple """ return self.info.hotspot - def get_palette_number(self): + def get_palette_number(self) -> int: """ Return the layer's palette number. :return: Palette number of the layer. - :rtype: int """ return self.info.palette_number From cfc21ff1f2369c7e6f53d26cdd8c5f92ae3b293b Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Wed, 22 Jan 2025 19:13:06 +0000 Subject: [PATCH 095/152] remove OOAPI specifier from enum to allow windows debug builds to link successfully --- libopenage/util/enum.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libopenage/util/enum.h b/libopenage/util/enum.h index 394db95b93..c6a9b4666b 100644 --- a/libopenage/util/enum.h +++ b/libopenage/util/enum.h @@ -1,4 +1,4 @@ -// Copyright 2015-2024 the openage authors. See copying.md for legal info. +// Copyright 2015-2025 the openage authors. See copying.md for legal info. #pragma once @@ -44,7 +44,7 @@ namespace util { * NumericType numeric */ template -class OAAPI EnumValue { +class EnumValue { public: constexpr EnumValue(const char *value_name, NumericType numeric_value) : name(value_name), numeric(numeric_value) {} @@ -124,7 +124,7 @@ class OAAPI EnumValue { * bool operator >=(Enum[DerivedType] other) except + */ template -class OAAPI Enum { +class Enum { using this_type = Enum; public: From 423cfed131040ccf278eb55872d2cf5da9c041eb Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Fri, 24 Jan 2025 12:26:48 +0000 Subject: [PATCH 096/152] working wondows debug build --- buildsystem/cythonize.py | 19 +++++++++++++++++++ buildsystem/python.cmake | 6 +++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/buildsystem/cythonize.py b/buildsystem/cythonize.py index 9e8ba8e234..89b499a29f 100755 --- a/buildsystem/cythonize.py +++ b/buildsystem/cythonize.py @@ -88,13 +88,32 @@ def cythonize_wrapper(modules, **kwargs): with redirect_stdout(cython_filter): if src_modules: cythonize(src_modules, **kwargs) + windows_include_python_debug_build_wrapper(src_modules, bin_dir) if bin_modules: os.chdir(bin_dir) cythonize(bin_modules, **kwargs) + windows_include_python_debug_build_wrapper(bin_modules, bin_dir) os.chdir(src_dir) +def windows_include_python_debug_build_wrapper(modules, path): + for module in modules: + module = str(module) + if (path): + module = path + "\\" + module + module = module.removesuffix(".py").removesuffix(".pyx") + module = module + ".cpp" + with open(module, "r") as file: + text = file.read() + text = text.replace("#include \"Python.h\"", + "#ifdef _DEBUG\n#define _DEBUG_WAS_DEFINED\n#undef _DEBUG\n#endif\n\ + #include \"Python.h\"\n#ifdef _DEBUG_WAS_DEFINED\n#define _DEBUG\n\ + #undef _DEBUG_WAS_DEFINED\n#endif", 1) + with open(module, "w") as file: + file.write(text) + + def main(): """ CLI entry point """ cli = argparse.ArgumentParser() diff --git a/buildsystem/python.cmake b/buildsystem/python.cmake index f5c4d84de5..a8e1fed72e 100644 --- a/buildsystem/python.cmake +++ b/buildsystem/python.cmake @@ -128,8 +128,7 @@ function(add_cython_modules) if(MINGW) set_target_properties("${TARGETNAME}" PROPERTIES LINK_FLAGS "-municode") endif() - - target_link_libraries("${TARGETNAME}" PRIVATE ${PYEXT_LIBRARY}) + target_link_libraries("${TARGETNAME}" PRIVATE C:/vcpkg/installed/x64-windows/lib/python311.lib) else() set_property(GLOBAL APPEND PROPERTY SFT_CYTHON_MODULES "${source}") add_library("${TARGETNAME}" MODULE "${CPPNAME}") @@ -140,7 +139,7 @@ function(add_cython_modules) ) if(WIN32) - target_link_libraries("${TARGETNAME}" PRIVATE ${PYEXT_LIBRARY}) + target_link_libraries("${TARGETNAME}" PRIVATE C:/vcpkg/installed/x64-windows/lib/python311.lib) endif() endif() @@ -514,6 +513,7 @@ function(python_finalize) ${INPLACEMODULES_LISTFILE} "$" ) + message(hello jeremiah, ${INPLACEMODULES_LISTFILE}) set(INPLACEMODULES_TIMEFILE "${CMAKE_BINARY_DIR}/py/inplacemodules_timefile") add_custom_command(OUTPUT "${INPLACEMODULES_TIMEFILE}" COMMAND ${INPLACEMODULES_INVOCATION} From 7eca2beb3a3d622b3150fdfd517c7491b48f09f9 Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Fri, 24 Jan 2025 12:33:16 +0000 Subject: [PATCH 097/152] working windows debug build --- buildsystem/HandlePythonOptions.cmake | 13 ++++++++++- buildsystem/cythonize.py | 33 +++++++++++++++++++-------- buildsystem/python.cmake | 12 ++++++---- 3 files changed, 43 insertions(+), 15 deletions(-) diff --git a/buildsystem/HandlePythonOptions.cmake b/buildsystem/HandlePythonOptions.cmake index ef7da71f9b..d6888c0673 100644 --- a/buildsystem/HandlePythonOptions.cmake +++ b/buildsystem/HandlePythonOptions.cmake @@ -1,4 +1,4 @@ -# Copyright 2015-2023 the openage authors. See copying.md for legal info. +# Copyright 2015-2025 the openage authors. See copying.md for legal info. # finds the python interpreter, install destination and extension flags. @@ -39,6 +39,17 @@ if(PYTHON_VER VERSION_GREATER_EQUAL 3.8 AND PYTHON_VERSION VERSION_LESS 3.9) endif() set(PYEXT_LIBRARY "${PYTHON_LIBRARIES}") + +#Windows always uses optimized version of Python lib +if(WIN32 AND "${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + #get index of string "optimized" and increment it by 1 so index points at the path of the optimized lib + list (FIND PYEXT_LIBRARY "optimized" _index) + if (${_index} GREATER -1) + MATH(EXPR _index "${_index}+1") + list(GET PYEXT_LIBRARY ${_index} PYEXT_LIBRARY) + endif() +endif() + set(PYEXT_INCLUDE_DIRS "${PYTHON_INCLUDE_DIRS};${NUMPY_INCLUDE_DIR}") if(NOT CMAKE_PY_INSTALL_PREFIX) diff --git a/buildsystem/cythonize.py b/buildsystem/cythonize.py index 89b499a29f..5d7cd492fe 100755 --- a/buildsystem/cythonize.py +++ b/buildsystem/cythonize.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright 2015-2021 the openage authors. See copying.md for legal info. +# Copyright 2015-2025 the openage authors. See copying.md for legal info. """ Runs Cython on all modules that were listed via add_cython_module. @@ -73,7 +73,7 @@ def remove_if_exists(filename): filename.unlink() -def cythonize_wrapper(modules, **kwargs): +def cythonize_wrapper(modules, force_optimized_lib = False, **kwargs): """ Calls cythonize, filtering useless warnings """ bin_dir, bin_modules = kwargs['build_dir'], [] src_dir, src_modules = Path.cwd(), [] @@ -88,29 +88,40 @@ def cythonize_wrapper(modules, **kwargs): with redirect_stdout(cython_filter): if src_modules: cythonize(src_modules, **kwargs) - windows_include_python_debug_build_wrapper(src_modules, bin_dir) + if sys.platform == 'win32' and force_optimized_lib: + windows_use_optimized_lib_python(src_modules, bin_dir) if bin_modules: os.chdir(bin_dir) cythonize(bin_modules, **kwargs) - windows_include_python_debug_build_wrapper(bin_modules, bin_dir) + if sys.platform == 'win32' and force_optimized_lib: + windows_use_optimized_lib_python(bin_modules, bin_dir) os.chdir(src_dir) -def windows_include_python_debug_build_wrapper(modules, path): +def windows_use_optimized_lib_python(modules, path): + """ + Add an #ifdef statement in cythonized .cpp files to temporarily undefine _DEBUG before + #include "Python.h" + + This function modifies the generated C++ files from Cython to prevent linking to + the debug version of the Python library on Windows. The debug version of the + Python library cannot import Python libraries that contain extension modules. + """ + for module in modules: module = str(module) - if (path): + if path: module = path + "\\" + module module = module.removesuffix(".py").removesuffix(".pyx") module = module + ".cpp" - with open(module, "r") as file: + with open(module, "r", encoding='utf8') as file: text = file.read() text = text.replace("#include \"Python.h\"", "#ifdef _DEBUG\n#define _DEBUG_WAS_DEFINED\n#undef _DEBUG\n#endif\n\ #include \"Python.h\"\n#ifdef _DEBUG_WAS_DEFINED\n#define _DEBUG\n\ #undef _DEBUG_WAS_DEFINED\n#endif", 1) - with open(module, "w") as file: + with open(module, "w", encoding='utf8') as file: file.write(text) @@ -144,6 +155,8 @@ def main(): )) cli.add_argument("--threads", type=int, default=cpu_count(), help="number of compilation threads to use") + cli.add_argument("--force_optimized_lib", action="store_true", + help= "edit compiled .cpp files to link to optimized version of python libary") args = cli.parse_args() # cython emits warnings on using absolute paths to modules @@ -178,12 +191,12 @@ def main(): # writing funny lines at the head of each file. cythonize_args['language'] = 'c++' - cythonize_wrapper(modules, **cythonize_args) + cythonize_wrapper(modules, args.force_optimized_lib, **cythonize_args) # build standalone executables that embed the py interpreter Options.embed = "main" - cythonize_wrapper(embedded_modules, **cythonize_args) + cythonize_wrapper(embedded_modules, args.force_optimized_lib, **cythonize_args) # verify depends from Cython.Build.Dependencies import _dep_tree diff --git a/buildsystem/python.cmake b/buildsystem/python.cmake index a8e1fed72e..a46f7106f3 100644 --- a/buildsystem/python.cmake +++ b/buildsystem/python.cmake @@ -1,4 +1,4 @@ -# Copyright 2014-2020 the openage authors. See copying.md for legal info. +# Copyright 2014-2025 the openage authors. See copying.md for legal info. # provides macros for defining python extension modules and pxdgen sources. # and a 'finalize' function that must be called in the end. @@ -128,7 +128,8 @@ function(add_cython_modules) if(MINGW) set_target_properties("${TARGETNAME}" PROPERTIES LINK_FLAGS "-municode") endif() - target_link_libraries("${TARGETNAME}" PRIVATE C:/vcpkg/installed/x64-windows/lib/python311.lib) + + target_link_libraries("${TARGETNAME}" PRIVATE ${PYEXT_LIBRARY}) else() set_property(GLOBAL APPEND PROPERTY SFT_CYTHON_MODULES "${source}") add_library("${TARGETNAME}" MODULE "${CPPNAME}") @@ -139,7 +140,7 @@ function(add_cython_modules) ) if(WIN32) - target_link_libraries("${TARGETNAME}" PRIVATE C:/vcpkg/installed/x64-windows/lib/python311.lib) + target_link_libraries("${TARGETNAME}" PRIVATE ${PYEXT_LIBRARY}) endif() endif() @@ -374,6 +375,9 @@ function(python_finalize) # cythonize (.pyx -> .cpp) + if(WIN32 AND "${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + set(force_optimized_lib "--force_optimized_lib") + endif() get_property(cython_modules GLOBAL PROPERTY SFT_CYTHON_MODULES) write_on_change("${CMAKE_BINARY_DIR}/py/cython_modules" "${cython_modules}") @@ -392,6 +396,7 @@ function(python_finalize) "${CMAKE_BINARY_DIR}/py/cython_modules" "${CMAKE_BINARY_DIR}/py/cython_modules_embed" "${CMAKE_BINARY_DIR}/py/pxd_list" + ${force_optimized_lib} "--build-dir" "${CMAKE_BINARY_DIR}" COMMAND "${CMAKE_COMMAND}" -E touch "${CYTHONIZE_TIMEFILE}" DEPENDS @@ -513,7 +518,6 @@ function(python_finalize) ${INPLACEMODULES_LISTFILE} "$" ) - message(hello jeremiah, ${INPLACEMODULES_LISTFILE}) set(INPLACEMODULES_TIMEFILE "${CMAKE_BINARY_DIR}/py/inplacemodules_timefile") add_custom_command(OUTPUT "${INPLACEMODULES_TIMEFILE}" COMMAND ${INPLACEMODULES_INVOCATION} From de9ab1db44d7abbb32408754f3476d95f8b8432a Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Fri, 24 Jan 2025 18:10:33 +0000 Subject: [PATCH 098/152] CI-CD:use debug config for CI --- .github/workflows/windows-server-2019.yml | 2 +- .github/workflows/windows-server-2022.yml | 2 +- buildsystem/HandlePythonOptions.cmake | 1 + buildsystem/cythonize.py | 30 +++++++++++++++++------ buildsystem/python.cmake | 5 +--- 5 files changed, 27 insertions(+), 13 deletions(-) diff --git a/.github/workflows/windows-server-2019.yml b/.github/workflows/windows-server-2019.yml index bf3ff7856b..406d303f27 100644 --- a/.github/workflows/windows-server-2019.yml +++ b/.github/workflows/windows-server-2019.yml @@ -51,7 +51,7 @@ jobs: mkdir build cd build cmake -DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN_FILE" -DCMAKE_BUILD_TYPE=Release -DCMAKE_TRY_COMPILE_CONFIGURATION=Release -DCMAKE_CXX_FLAGS='/Zc:__cplusplus /permissive- /EHsc' -DCMAKE_EXE_LINKER_FLAGS='' -DCMAKE_MODULE_LINKER_FLAGS='' -DCMAKE_SHARED_LINKER_FLAGS='' -DDOWNLOAD_NYAN=YES -DCXX_OPTIMIZATION_LEVEL=auto -DCXX_SANITIZE_FATAL=False -DCXX_SANITIZE_MODE=none -DWANT_BACKTRACE=if_available -DWANT_GPERFTOOLS_PROFILER=if_available -DWANT_GPERFTOOLS_TCMALLOC=False -DWANT_INOTIFY=if_available -DWANT_NCURSES=if_available -DWANT_OPENGL=if_available -DWANT_VULKAN=if_available -DFLEX_EXECUTABLE="$FLEX_PATH" -G "Visual Studio 16 2019" -A x64 ../source - cmake --build . --config RelWithDebInfo -- -nologo -maxCpuCount + cmake --build . --config Debug -- -nologo -maxCpuCount shell: pwsh - name: Package run: | diff --git a/.github/workflows/windows-server-2022.yml b/.github/workflows/windows-server-2022.yml index a721db5a52..10a6a52c59 100644 --- a/.github/workflows/windows-server-2022.yml +++ b/.github/workflows/windows-server-2022.yml @@ -51,7 +51,7 @@ jobs: mkdir build cd build cmake -DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN_FILE" -DCMAKE_BUILD_TYPE=Release -DCMAKE_TRY_COMPILE_CONFIGURATION=Release -DCMAKE_CXX_FLAGS='/Zc:__cplusplus /permissive- /EHsc' -DCMAKE_EXE_LINKER_FLAGS='' -DCMAKE_MODULE_LINKER_FLAGS='' -DCMAKE_SHARED_LINKER_FLAGS='' -DDOWNLOAD_NYAN=YES -DCXX_OPTIMIZATION_LEVEL=auto -DCXX_SANITIZE_FATAL=False -DCXX_SANITIZE_MODE=none -DWANT_BACKTRACE=if_available -DWANT_GPERFTOOLS_PROFILER=if_available -DWANT_GPERFTOOLS_TCMALLOC=False -DWANT_INOTIFY=if_available -DWANT_NCURSES=if_available -DWANT_OPENGL=if_available -DWANT_VULKAN=if_available -DFLEX_EXECUTABLE="$FLEX_PATH" -G "Visual Studio 17 2022" -A x64 ../source - cmake --build . --config RelWithDebInfo -- -nologo -maxCpuCount + cmake --build . --config Debug -- -nologo -maxCpuCount shell: pwsh - name: Package run: | diff --git a/buildsystem/HandlePythonOptions.cmake b/buildsystem/HandlePythonOptions.cmake index d6888c0673..20a35f3d61 100644 --- a/buildsystem/HandlePythonOptions.cmake +++ b/buildsystem/HandlePythonOptions.cmake @@ -47,6 +47,7 @@ if(WIN32 AND "${CMAKE_BUILD_TYPE}" STREQUAL "Debug") if (${_index} GREATER -1) MATH(EXPR _index "${_index}+1") list(GET PYEXT_LIBRARY ${_index} PYEXT_LIBRARY) + set(force_optimized_lib_flag "--force_optimized_lib") endif() endif() diff --git a/buildsystem/cythonize.py b/buildsystem/cythonize.py index 5d7cd492fe..e7ac86ae6f 100755 --- a/buildsystem/cythonize.py +++ b/buildsystem/cythonize.py @@ -89,17 +89,17 @@ def cythonize_wrapper(modules, force_optimized_lib = False, **kwargs): if src_modules: cythonize(src_modules, **kwargs) if sys.platform == 'win32' and force_optimized_lib: - windows_use_optimized_lib_python(src_modules, bin_dir) + win_use_optimized_lib_python(src_modules, bin_dir) if bin_modules: os.chdir(bin_dir) cythonize(bin_modules, **kwargs) if sys.platform == 'win32' and force_optimized_lib: - windows_use_optimized_lib_python(bin_modules, bin_dir) + win_use_optimized_lib_python(bin_modules, bin_dir) os.chdir(src_dir) -def windows_use_optimized_lib_python(modules, path): +def win_use_optimized_lib_python(modules, path): """ Add an #ifdef statement in cythonized .cpp files to temporarily undefine _DEBUG before #include "Python.h" @@ -107,6 +107,8 @@ def windows_use_optimized_lib_python(modules, path): This function modifies the generated C++ files from Cython to prevent linking to the debug version of the Python library on Windows. The debug version of the Python library cannot import Python libraries that contain extension modules. + see: https://github.com/python/cpython/issues/127619 (To unserstand the problem) + see: https://stackoverflow.com/a/59566420 (To understand the soloution) """ for module in modules: @@ -117,10 +119,24 @@ def windows_use_optimized_lib_python(modules, path): module = module + ".cpp" with open(module, "r", encoding='utf8') as file: text = file.read() - text = text.replace("#include \"Python.h\"", - "#ifdef _DEBUG\n#define _DEBUG_WAS_DEFINED\n#undef _DEBUG\n#endif\n\ - #include \"Python.h\"\n#ifdef _DEBUG_WAS_DEFINED\n#define _DEBUG\n\ - #undef _DEBUG_WAS_DEFINED\n#endif", 1) + if not text.count("OPENAGE: UNDEF_DEBUG_INSERTED"): + text = text.replace( + '#include "Python.h"', + ( + "\n\n// OPENAGE: UNDEF_DEBUG_INSERTED\n" + "// Avoid linking to the debug version of the Python library on Windows\n\n" + "#ifdef _DEBUG\n" + "#define _DEBUG_WAS_DEFINED\n" + "#undef _DEBUG\n#endif\n" + "#include \"Python.h\"\n" + "#ifdef _DEBUG_WAS_DEFINED\n" + "#define _DEBUG\n" + "#undef _DEBUG_WAS_DEFINED\n" + "#endif\n\n" + "// OPENAGE: UNDEF_DEBUG_INSERTED\n\n" + ), + 1 + ) with open(module, "w", encoding='utf8') as file: file.write(text) diff --git a/buildsystem/python.cmake b/buildsystem/python.cmake index a46f7106f3..90117baadc 100644 --- a/buildsystem/python.cmake +++ b/buildsystem/python.cmake @@ -375,9 +375,6 @@ function(python_finalize) # cythonize (.pyx -> .cpp) - if(WIN32 AND "${CMAKE_BUILD_TYPE}" STREQUAL "Debug") - set(force_optimized_lib "--force_optimized_lib") - endif() get_property(cython_modules GLOBAL PROPERTY SFT_CYTHON_MODULES) write_on_change("${CMAKE_BINARY_DIR}/py/cython_modules" "${cython_modules}") @@ -396,7 +393,7 @@ function(python_finalize) "${CMAKE_BINARY_DIR}/py/cython_modules" "${CMAKE_BINARY_DIR}/py/cython_modules_embed" "${CMAKE_BINARY_DIR}/py/pxd_list" - ${force_optimized_lib} + ${force_optimized_lib_flag} "--build-dir" "${CMAKE_BINARY_DIR}" COMMAND "${CMAKE_COMMAND}" -E touch "${CYTHONIZE_TIMEFILE}" DEPENDS From 11bdc78f487ad1d4ad43c000370aed2d003a10a9 Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Mon, 20 Jan 2025 16:06:22 +0000 Subject: [PATCH 099/152] ArrayIterator inherit from CurveIterator --- libopenage/curve/container/array.h | 78 +++++++++++++++------------- libopenage/curve/tests/container.cpp | 3 +- 2 files changed, 43 insertions(+), 38 deletions(-) diff --git a/libopenage/curve/container/array.h b/libopenage/curve/container/array.h index e980d77915..e3d69af553 100644 --- a/libopenage/curve/container/array.h +++ b/libopenage/curve/container/array.h @@ -26,6 +26,10 @@ class Array : event::EventEntity { public: /// Underlying container type. using container_t = std::array, Size>; + + /// Uderlying iterator type + using const_iterator = typename std::array, Size>::const_iterator; + /// Index type to access elements in the container. using index_t = typename container_t::size_type; @@ -167,61 +171,60 @@ class Array : event::EventEntity { return this->_idstr; } - /** - * Array::Iterator is used to iterate over KeyframeContainers contained in a curve at a given time. - */ - class Iterator { - public: - Iterator(Array *curve, const time::time_t &time = time::TIME_MAX, size_t offset = 0) : - curve(curve), time(time), offset(offset) {}; + class ArrayIterator : public CurveIterator> { + public: /** - * returns a copy of the keyframe at the current offset and time + * Construct the iterator from its boundary conditions: time and container */ - const T operator*() { - return this->curve->frame(this->time, this->offset).second; + ArrayIterator(const const_iterator &base, + const Array *base_container, + const time::time_t &to) : + CurveIterator>(base, base_container, -time::TIME_MAX, to) { } - /** - * increments the Iterator to point at the next KeyframeContainer - */ - void operator++() { - this->offset++; - } - /** - * Compare two Iterators by their offset - */ - bool operator!=(const Array::Iterator &rhs) const { - return this->offset != rhs.offset; + virtual bool valid() const override { + if (this->container->end().get_base() != this->get_base() && this->get_base()->begin()->time() <= this->to) { + return true; + } + return false; } - private: /** - * the curve object that this iterator, iterates over + * Get the keyFrame with a time <= this->to from the KeyframeContainer + * that this iterator currently points at */ - Array *curve; - - /** - * time at which this iterator is iterating over - */ - time::time_t time; + const T &value() const override { + const auto &key_frame_container = *this->get_base(); + size_t hint_index = std::distance(this->container->begin().get_base(), this->get_base()); + size_t e = key_frame_container.last(this->to, last_elements[hint_index]); + this->last_elements[hint_index] = e; + return key_frame_container.get(e).val(); + } /** - * used to index the Curve::Array pointed to by this iterator + * Cache hints for containers. Stores the index of the last keyframe accessed in each container. + * + * hints is used to speed up the search for keyframes. + * + * mutable as hints are updated by const read-only functions. */ - size_t offset; + mutable std::array::elem_ptr, Size> last_elements = {}; }; + /** * iterator pointing to a keyframe of the first KeyframeContainer in the curve at a given time */ - Iterator begin(const time::time_t &time = time::TIME_MIN); + ArrayIterator begin(const time::time_t &time = time::TIME_MIN) const; + /** * iterator pointing after the last KeyframeContainer in the curve at a given time */ - Iterator end(const time::time_t &time = time::TIME_MIN); + ArrayIterator end(const time::time_t &time = time::TIME_MAX) const; + private: /** @@ -382,13 +385,14 @@ void Array::sync(const Array &other, } template -typename Array::Iterator Array::begin(const time::time_t &time) { - return Array::Iterator(this, time); +auto Array::begin(const time::time_t &t) const -> ArrayIterator { + return ArrayIterator(this->containers.begin(), this, t); } + template -typename Array::Iterator Array::end(const time::time_t &time) { - return Array::Iterator(this, time, this->containers.size()); +auto Array::end(const time::time_t &t) const -> ArrayIterator { + return ArrayIterator(this->containers.end(), this, t); } } // namespace curve diff --git a/libopenage/curve/tests/container.cpp b/libopenage/curve/tests/container.cpp index 16da2476e9..a19542dd8c 100644 --- a/libopenage/curve/tests/container.cpp +++ b/libopenage/curve/tests/container.cpp @@ -336,7 +336,8 @@ void test_array() { TESTEQUALS(next_frame.second, 40); // value // Test begin and end - auto it = a.begin(1); + + auto it = a.begin(openage::time::time_t(1)); TESTEQUALS(*it, 4); ++it; TESTEQUALS(*it, 5); From f1ca4190ca20fa14ea404cf0df5a963a624f2594 Mon Sep 17 00:00:00 2001 From: jere8184 Date: Tue, 21 Jan 2025 03:18:50 +0000 Subject: [PATCH 100/152] Update container.cpp --- libopenage/curve/tests/container.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libopenage/curve/tests/container.cpp b/libopenage/curve/tests/container.cpp index a19542dd8c..d7d6825de0 100644 --- a/libopenage/curve/tests/container.cpp +++ b/libopenage/curve/tests/container.cpp @@ -337,7 +337,7 @@ void test_array() { // Test begin and end - auto it = a.begin(openage::time::time_t(1)); + auto it = a.begin(1); TESTEQUALS(*it, 4); ++it; TESTEQUALS(*it, 5); From 768bd99fc64f7ce224112b1f4198cf0faa16eb8d Mon Sep 17 00:00:00 2001 From: jere8184 Date: Tue, 21 Jan 2025 03:20:10 +0000 Subject: [PATCH 101/152] Update container.cpp --- libopenage/curve/tests/container.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libopenage/curve/tests/container.cpp b/libopenage/curve/tests/container.cpp index d7d6825de0..16da2476e9 100644 --- a/libopenage/curve/tests/container.cpp +++ b/libopenage/curve/tests/container.cpp @@ -336,7 +336,6 @@ void test_array() { TESTEQUALS(next_frame.second, 40); // value // Test begin and end - auto it = a.begin(1); TESTEQUALS(*it, 4); ++it; From 03a5f8257e423fbcc92411b77808cc30d6436371 Mon Sep 17 00:00:00 2001 From: haytham918 Date: Mon, 18 Nov 2024 17:32:50 -0500 Subject: [PATCH 102/152] Add and Test Clang-tidy --- buildsystem/codecompliance/__main__.py | 11 ++++ buildsystem/codecompliance/clangtidy.py | 67 +++++++++++++++++++++++++ copying.md | 1 + 3 files changed, 79 insertions(+) create mode 100644 buildsystem/codecompliance/clangtidy.py diff --git a/buildsystem/codecompliance/__main__.py b/buildsystem/codecompliance/__main__.py index 2a4f8b4084..a8373478b9 100644 --- a/buildsystem/codecompliance/__main__.py +++ b/buildsystem/codecompliance/__main__.py @@ -57,6 +57,8 @@ def parse_args(): help="increase program verbosity") cli.add_argument("-q", "--quiet", action="count", default=0, help="decrease program verbosity") + cli.add_argument("--clangtidy", action="store_true", + help="Check the C++ code with clang-tidy.") args = cli.parse_args() process_args(args, cli.error) @@ -92,6 +94,7 @@ def process_args(args, error): args.pystyle = True args.pylint = True args.test_git_change_years = True + args.clangtidy = True if not any((args.headerguards, args.legal, args.authors, args.pystyle, args.cppstyle, args.cython, args.test_git_change_years, @@ -127,6 +130,10 @@ def process_args(args, error): if args.pylint: if not importlib.util.find_spec('pylint'): error("pylint python module required for linting") + + if args.clangtidy: + if not shutil.which('clang-tidy'): + error("--clang-tidy requires clang-tidy to be installed") def get_changed_files(gitref): @@ -264,6 +271,10 @@ def find_all_issues(args, check_files=None): from .modes import find_issues yield from find_issues(check_files, ('openage', 'buildsystem', 'libopenage', 'etc/gdb_pretty')) + + if args.clangtidy: + from .clangtidy import find_issues + yield from find_issues(check_files, ('libopenage', )) if __name__ == '__main__': diff --git a/buildsystem/codecompliance/clangtidy.py b/buildsystem/codecompliance/clangtidy.py new file mode 100644 index 0000000000..affa76bd9e --- /dev/null +++ b/buildsystem/codecompliance/clangtidy.py @@ -0,0 +1,67 @@ +# Copyright 2024-2024 the openage authors. See copying.md for legal info. + +""" +Checks clang-tidy errors on cpp files +""" + +import subprocess +from .cppstyle import filter_file_list +from .util import findfiles + + +def find_issues(check_files, dirnames): + """ + Invoke clang-tidy to check C++ files for issues. + Yields issues found by clang-tidy in real-time. + """ + # Specify the checks to include + checks_to_include = [ + 'clang-analyzer-*', + 'bugprone-*', + 'concurrency-*', + 'performance-*' + ] + # Create the checks string + checks = ','.join(checks_to_include) + + # Invocation command + invocation = ['clang-tidy', f'-checks=-*,{checks}'] + + if check_files is not None: + filenames = list(filter_file_list(check_files, dirnames)) + else: + filenames = list(filter_file_list(findfiles(dirnames), dirnames)) + + if not filenames: + print("No files to check.") + return # No files to check + + for filename in filenames: + # Run clang-tidy for each file + print(f"Starting clang-tidy check on file: {filename}") + try: + process = subprocess.Popen( + invocation + [filename], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + + # Stream output in real-time + while True: + output = process.stdout.readline() + if output: + yield ("clang-tidy output", output.strip(), None) + elif process.poll() is not None: + break + + # Capture remaining errors (if any) + for error_line in process.stderr: + yield ("clang-tidy error", error_line.strip(), None) + + except Exception as exc: + yield ( + "clang-tidy error", + f"An error occurred while running clang-tidy on {filename}: {str(exc)}", + None + ) \ No newline at end of file diff --git a/copying.md b/copying.md index f873817f29..3938eaa8e0 100644 --- a/copying.md +++ b/copying.md @@ -161,6 +161,7 @@ _the openage authors_ are: | David Wever | dmwever | dmwever à crimson dawt ua dawt edu | | Michael Lynch | mtlynch | git à mtlynch dawt io | | Ngô Xuân Minh | | xminh dawt ngo dawt 00 à gmail dawt com | +| Haytham Tang | haytham918 | yunxuant à umich dawt edu | If you're a first-time committer, add yourself to the above list. This is not just for legal reasons, but also to keep an overview of all those nicknames. From cb9b76220059d286b389fd79c1efbc46487c513a Mon Sep 17 00:00:00 2001 From: haytham918 Date: Mon, 18 Nov 2024 18:11:54 -0500 Subject: [PATCH 103/152] Some style fix for clangtidy.py --- buildsystem/codecompliance/__main__.py | 8 +++---- buildsystem/codecompliance/clangtidy.py | 31 ++++++++++++------------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/buildsystem/codecompliance/__main__.py b/buildsystem/codecompliance/__main__.py index a8373478b9..b72edcd2ef 100644 --- a/buildsystem/codecompliance/__main__.py +++ b/buildsystem/codecompliance/__main__.py @@ -57,8 +57,8 @@ def parse_args(): help="increase program verbosity") cli.add_argument("-q", "--quiet", action="count", default=0, help="decrease program verbosity") - cli.add_argument("--clangtidy", action="store_true", - help="Check the C++ code with clang-tidy.") + cli.add_argument("--clangtidy", action="store_true", + help="Check the C++ code with clang-tidy.") args = cli.parse_args() process_args(args, cli.error) @@ -98,7 +98,7 @@ def process_args(args, error): if not any((args.headerguards, args.legal, args.authors, args.pystyle, args.cppstyle, args.cython, args.test_git_change_years, - args.pylint, args.filemodes, args.textfiles)): + args.pylint, args.filemodes, args.textfiles, args.clangtidy)): error("no checks were specified") has_git = bool(shutil.which('git')) @@ -130,7 +130,6 @@ def process_args(args, error): if args.pylint: if not importlib.util.find_spec('pylint'): error("pylint python module required for linting") - if args.clangtidy: if not shutil.which('clang-tidy'): error("--clang-tidy requires clang-tidy to be installed") @@ -271,7 +270,6 @@ def find_all_issues(args, check_files=None): from .modes import find_issues yield from find_issues(check_files, ('openage', 'buildsystem', 'libopenage', 'etc/gdb_pretty')) - if args.clangtidy: from .clangtidy import find_issues yield from find_issues(check_files, ('libopenage', )) diff --git a/buildsystem/codecompliance/clangtidy.py b/buildsystem/codecompliance/clangtidy.py index affa76bd9e..7eda5cd72a 100644 --- a/buildsystem/codecompliance/clangtidy.py +++ b/buildsystem/codecompliance/clangtidy.py @@ -22,7 +22,7 @@ def find_issues(check_files, dirnames): 'performance-*' ] # Create the checks string - checks = ','.join(checks_to_include) + checks = ', '.join(checks_to_include) # Invocation command invocation = ['clang-tidy', f'-checks=-*,{checks}'] @@ -40,28 +40,27 @@ def find_issues(check_files, dirnames): # Run clang-tidy for each file print(f"Starting clang-tidy check on file: {filename}") try: - process = subprocess.Popen( + with subprocess.Popen( invocation + [filename], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True - ) - - # Stream output in real-time - while True: - output = process.stdout.readline() - if output: - yield ("clang-tidy output", output.strip(), None) - elif process.poll() is not None: - break + ) as process: + # Stream output in real-time + while True: + output = process.stdout.readline() + if output: + yield ("clang-tidy output", output.strip(), None) + elif process.poll() is not None: + break - # Capture remaining errors (if any) - for error_line in process.stderr: - yield ("clang-tidy error", error_line.strip(), None) + # Capture remaining errors (if any) + for error_line in process.stderr: + yield ("clang-tidy error", error_line.strip(), None) - except Exception as exc: + except subprocess.SubprocessError as exc: yield ( "clang-tidy error", f"An error occurred while running clang-tidy on {filename}: {str(exc)}", None - ) \ No newline at end of file + ) From 6306530cece676969c857b644c413ade29d1fb70 Mon Sep 17 00:00:00 2001 From: haytham918 Date: Mon, 18 Nov 2024 18:24:49 -0500 Subject: [PATCH 104/152] Add clang-tidy to dependency list of doc/buildings.md --- doc/building.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/building.md b/doc/building.md index 619ad7a423..8c11b8d967 100644 --- a/doc/building.md +++ b/doc/building.md @@ -57,6 +57,7 @@ Dependency list: CR qt6 >=6.2 (Core, Quick, QuickControls, Multimedia modules) CR toml11 CR O vulkan + S clang-tidy A An installed version of any of the following (wine is your friend). Other versions _might_ work: From 401abc2cc2ab2f233ba2f8e4d0f478f2337cf023 Mon Sep 17 00:00:00 2001 From: haytham918 Date: Tue, 19 Nov 2024 13:23:35 -0500 Subject: [PATCH 105/152] Clang-tidy follows alphabetical sorting in building.md, I think? --- doc/building.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/building.md b/doc/building.md index 8c11b8d967..6ed51cb4a6 100644 --- a/doc/building.md +++ b/doc/building.md @@ -39,6 +39,7 @@ Dependency list: CR opengl >=3.3 CR libepoxy CR libpng + S clang-tidy R dejavu font CR eigen >=3 CR freetype2 @@ -57,7 +58,6 @@ Dependency list: CR qt6 >=6.2 (Core, Quick, QuickControls, Multimedia modules) CR toml11 CR O vulkan - S clang-tidy A An installed version of any of the following (wine is your friend). Other versions _might_ work: From a8ee46e99281440a0e5a8ddd736eacac70f86e9b Mon Sep 17 00:00:00 2001 From: haytham918 Date: Wed, 27 Nov 2024 04:44:24 -0500 Subject: [PATCH 106/152] Add comments in clang-tidy.py --- buildsystem/codecompliance/clangtidy.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/buildsystem/codecompliance/clangtidy.py b/buildsystem/codecompliance/clangtidy.py index 7eda5cd72a..d4e1b5586a 100644 --- a/buildsystem/codecompliance/clangtidy.py +++ b/buildsystem/codecompliance/clangtidy.py @@ -15,6 +15,7 @@ def find_issues(check_files, dirnames): Yields issues found by clang-tidy in real-time. """ # Specify the checks to include + # 4 checks we focus on checks_to_include = [ 'clang-analyzer-*', 'bugprone-*', @@ -27,6 +28,7 @@ def find_issues(check_files, dirnames): # Invocation command invocation = ['clang-tidy', f'-checks=-*,{checks}'] + # Use utility functions from util.py and cppstyle.py if check_files is not None: filenames = list(filter_file_list(check_files, dirnames)) else: @@ -58,6 +60,7 @@ def find_issues(check_files, dirnames): for error_line in process.stderr: yield ("clang-tidy error", error_line.strip(), None) + # Handle exception except subprocess.SubprocessError as exc: yield ( "clang-tidy error", From 8debe0c2cce93cec23515dce7f207b2a11f989be Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 10 Feb 2025 00:49:52 +0100 Subject: [PATCH 107/152] buildsys: Add new sanity check category 'checkmerge' in makefile. --- Makefile | 14 +++++---- buildsystem/codecompliance/__main__.py | 39 ++++++++++++++++---------- kevinfile | 2 +- 3 files changed, 34 insertions(+), 21 deletions(-) diff --git a/Makefile b/Makefile index ef481521b2..d0dc0585f8 100644 --- a/Makefile +++ b/Makefile @@ -128,22 +128,26 @@ mrproperer: mrproper checkfast: python3 -m buildsystem.codecompliance --fast -.PHONY: checkall -checkall: - python3 -m buildsystem.codecompliance --all +.PHONY: checkmerge +checkmerge: + python3 -m buildsystem.codecompliance --merge .PHONY: checkchanged checkchanged: - python3 -m buildsystem.codecompliance --all --only-changed-files=origin/master + python3 -m buildsystem.codecompliance --merge --only-changed-files=origin/master .PHONY: checkuncommited checkuncommited: - python3 -m buildsystem.codecompliance --all --only-changed-files=HEAD + python3 -m buildsystem.codecompliance --merge --only-changed-files=HEAD .PHONY: checkpy checkpy: python3 -m buildsystem.codecompliance --pystyle --pylint +.PHONY: checkall +checkall: + python3 -m buildsystem.codecompliance --all + .PHONY: help help: $(BUILDDIR)/Makefile @echo "openage Makefile" diff --git a/buildsystem/codecompliance/__main__.py b/buildsystem/codecompliance/__main__.py index b72edcd2ef..4d3237e36a 100644 --- a/buildsystem/codecompliance/__main__.py +++ b/buildsystem/codecompliance/__main__.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 the openage authors. See copying.md for legal info. +# Copyright 2014-2025 the openage authors. See copying.md for legal info. """ Entry point for the code compliance checker. @@ -18,16 +18,24 @@ def parse_args(): """ Returns the raw argument namespace. """ cli = argparse.ArgumentParser() - cli.add_argument("--fast", action="store_true", - help="do all checks that can be performed quickly") - cli.add_argument("--all", action="store_true", - help="do all checks, even the really slow ones") + check_types = cli.add_mutually_exclusive_group() + check_types.add_argument("--fast", action="store_true", + help="do all checks that can be performed quickly") + check_types.add_argument("--merge", action="store_true", + help="do all checks that are required before merges to master") + check_types.add_argument("--all", action="store_true", + help="do all checks, even the really slow ones") + cli.add_argument("--only-changed-files", metavar='GITREF', help=("slow checks are only done on files that have " "changed since GITREF.")) cli.add_argument("--authors", action="store_true", help=("check whether all git authors are in copying.md. " "repo must be a git repository.")) + cli.add_argument("--clang-tidy", action="store_true", + help=("Check the C++ code with clang-tidy. Make sure you have build the " + "project with ./configure --clang-tidy or have set " + "CMAKE_CXX_CLANG_TIDY for your CMake build.")) cli.add_argument("--cppstyle", action="store_true", help="check the cpp code style") cli.add_argument("--cython", action="store_true", @@ -57,8 +65,6 @@ def parse_args(): help="increase program verbosity") cli.add_argument("-q", "--quiet", action="count", default=0, help="decrease program verbosity") - cli.add_argument("--clangtidy", action="store_true", - help="Check the C++ code with clang-tidy.") args = cli.parse_args() process_args(args, cli.error) @@ -78,7 +84,7 @@ def process_args(args, error): # set up log level log_setup(args.verbose - args.quiet) - if args.fast or args.all: + if args.fast or args.merge or args.all: # enable "fast" tests args.authors = True args.cppstyle = True @@ -88,17 +94,19 @@ def process_args(args, error): args.filemodes = True args.textfiles = True - if args.all: - # enable tests that take a bit longer - + if args.merge or args.all: + # enable tests that are required before merging to master args.pystyle = True args.pylint = True args.test_git_change_years = True - args.clangtidy = True + + if args.all: + # enable tests that take a bit longer + args.clang_tidy = True if not any((args.headerguards, args.legal, args.authors, args.pystyle, args.cppstyle, args.cython, args.test_git_change_years, - args.pylint, args.filemodes, args.textfiles, args.clangtidy)): + args.pylint, args.filemodes, args.textfiles, args.clang_tidy)): error("no checks were specified") has_git = bool(shutil.which('git')) @@ -130,7 +138,8 @@ def process_args(args, error): if args.pylint: if not importlib.util.find_spec('pylint'): error("pylint python module required for linting") - if args.clangtidy: + + if args.clang_tidy: if not shutil.which('clang-tidy'): error("--clang-tidy requires clang-tidy to be installed") @@ -270,7 +279,7 @@ def find_all_issues(args, check_files=None): from .modes import find_issues yield from find_issues(check_files, ('openage', 'buildsystem', 'libopenage', 'etc/gdb_pretty')) - if args.clangtidy: + if args.clang_tidy: from .clangtidy import find_issues yield from find_issues(check_files, ('libopenage', )) diff --git a/kevinfile b/kevinfile index 7a7353bbcd..e4acd0e036 100644 --- a/kevinfile +++ b/kevinfile @@ -6,7 +6,7 @@ sanity_check: - skip (? if job != "debian" ?) - make checkall + make checkmerge # Various optimisation options can affect warnings compiler generates. # Make sure both release and debug are tested. Arch job has more checks, From e9543ab8ec9daff327abda6ba11211c66148391f Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Fri, 24 Jan 2025 18:13:43 +0000 Subject: [PATCH 108/152] fix paring heap UB --- libopenage/datastructure/pairing_heap.h | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/libopenage/datastructure/pairing_heap.h b/libopenage/datastructure/pairing_heap.h index dd348d8c55..8cf1731713 100644 --- a/libopenage/datastructure/pairing_heap.h +++ b/libopenage/datastructure/pairing_heap.h @@ -1,4 +1,4 @@ -// Copyright 2014-2024 the openage authors. See copying.md for legal info. +// Copyright 2014-2025 the openage authors. See copying.md for legal info. #pragma once @@ -404,7 +404,7 @@ class PairingHeap final { * erase all elements on the heap. */ void clear() { - auto delete_node = [](element_t node) { delete node; }; + auto delete_node = [](element_t& node) { delete node; node = nullptr; }; this->iter_all(delete_node); this->root_node = nullptr; this->node_count = 0; @@ -586,7 +586,7 @@ class PairingHeap final { * @param func Function to apply to each node. */ template - void iter_all(const std::function &func) const { + void iter_all(const std::function &func) { this->walk_tree(this->root_node, func); } @@ -599,21 +599,18 @@ class PairingHeap final { * @param func Function to apply to each node. */ template - void walk_tree(const element_t &start, - const std::function &func) const { + void walk_tree(element_t &start, + const std::function &func) { if constexpr (not reverse) { func(start); } if (start) { auto node = start->first_child; - while (true) { - if (not node) { - break; - } - + while (node) { this->walk_tree(node, func); - node = node->next_sibling; + if (node) + node = node->next_sibling; } if constexpr (reverse) { func(start); From aef6cf01ecf64bb1c9e3b10f4ed9b1621dcf0fd0 Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Sun, 26 Jan 2025 16:05:47 +0000 Subject: [PATCH 109/152] add iterator to paring heap --- libopenage/datastructure/pairing_heap.h | 206 +++++++++++++++++++++++- libopenage/datastructure/tests.cpp | 10 +- 2 files changed, 207 insertions(+), 9 deletions(-) diff --git a/libopenage/datastructure/pairing_heap.h b/libopenage/datastructure/pairing_heap.h index 8cf1731713..df8d1ad66d 100644 --- a/libopenage/datastructure/pairing_heap.h +++ b/libopenage/datastructure/pairing_heap.h @@ -17,6 +17,7 @@ */ #include +#include #include #include #include @@ -36,6 +37,9 @@ template class PairingHeap; +template +class PairingHeapIterator; + template > class PairingHeapNode { @@ -43,6 +47,7 @@ class PairingHeapNode { using this_type = PairingHeapNode; friend PairingHeap; + friend PairingHeapIterator; T data; compare cmp; @@ -186,6 +191,166 @@ class PairingHeapNode { }; +/** + * @brief Iterator class for PairingHeap. + * + * This class provides a bidirectional iterator for the PairingHeap data structure. + * It allows traversal of the heap in both forward and backward directions. + * It is depth-first traversal. + * + * @tparam T The type of elements stored in the heap. + * @tparam compare The comparison functor used to order the elements. + */ +template > +class PairingHeapIterator { +public: + using iterator_category = std::bidirectional_iterator_tag; + using value_type = T; + using difference_type = std::ptrdiff_t; + using pointer = T *; + using reference = T &; + + /** + * @brief Constructs an iterator starting at the given node. + * + * @param node The starting node for the iterator. + */ + PairingHeapIterator(PairingHeapNode *node) : + current(node) {} + + /** + * @brief Dereference operator. + * + * @return A reference to the data stored in the current node. + */ + reference operator*() const { + return current->data; + } + + /** + * @brief Member access operator. + * + * @return A pointer to the data stored in the current node. + */ + pointer operator->() const { + return &(current->data); + } + + + /** + * @brief Get current node. + * + * @return The current node. + */ + PairingHeapNode *node() { + return current; + } + + + /** + * @brief Pre-increment operator. + * + * Moves the iterator to the next node in the heap. + * + * @return A reference to the incremented iterator. + */ + PairingHeapIterator &operator++() { + if (current->first_child) { + current = current->first_child; + } + else if (current->next_sibling) { + current = current->next_sibling; + } + else { + while (current->parent && !current->parent->next_sibling) { + current = current->parent; + } + if (current->parent) { + current = current->parent->next_sibling; + } + else { + current = nullptr; + } + } + return *this; + } + + /** + * @brief Post-increment operator. + * + * Moves the iterator to the next node in the heap. + * + * @return A copy of the iterator before incrementing. + */ + PairingHeapIterator operator++(int) { + PairingHeapIterator tmp = *this; + ++(*this); + return tmp; + } + + /** + * @brief Pre-decrement operator. + * + * Moves the iterator to the previous node in the heap. + * + * @return A reference to the decremented iterator. + */ + PairingHeapIterator &operator--() { + if (current->prev_sibling) { + current = current->prev_sibling; + while (current->first_child) { + current = current->first_child; + while (current->next_sibling) { + current = current->next_sibling; + } + } + } + else if (current->parent) { + current = current->parent; + } + return *this; + } + + /** + * @brief Post-decrement operator. + * + * Moves the iterator to the previous node in the heap. + * + * @return A copy of the iterator before decrementing. + */ + PairingHeapIterator operator--(int) { + PairingHeapIterator tmp = *this; + --(*this); + return tmp; + } + + /** + * @brief Equality comparison operator. + * + * @param a The first iterator to compare. + * @param b The second iterator to compare. + * @return True if both iterators point to the same node, false otherwise. + */ + friend bool operator==(const PairingHeapIterator &a, const PairingHeapIterator &b) { + return a.current == b.current; + } + + /** + * @brief Inequality comparison operator. + * + * @param a The first iterator to compare. + * @param b The second iterator to compare. + * @return True if the iterators point to different nodes, false otherwise. + */ + friend bool operator!=(const PairingHeapIterator &a, const PairingHeapIterator &b) { + return a.current != b.current; + } + +private: + PairingHeapNode *current; ///< Pointer to the current node in the heap. +}; + + /** * (Quite) efficient heap implementation. */ @@ -196,6 +361,7 @@ class PairingHeap final { public: using element_t = heapnode_t *; using this_type = PairingHeap; + using iterator = PairingHeapIterator; /** * create a empty heap. @@ -404,11 +570,24 @@ class PairingHeap final { * erase all elements on the heap. */ void clear() { - auto delete_node = [](element_t& node) { delete node; node = nullptr; }; - this->iter_all(delete_node); + std::vector to_delete; + to_delete.reserve(this->size()); + + // collect all node pointers to delete + for (iterator it = this->begin(); it != this->end(); it++) { + to_delete.push_back(it.node()); + } + + // delete all nodes + for (element_t node : to_delete) { + delete node; + } + + // reset heap state to empty this->root_node = nullptr; this->node_count = 0; #if OPENAGE_PAIRINGHEAP_DEBUG + // clear the node set for debugging this->nodes.clear(); #endif } @@ -586,10 +765,18 @@ class PairingHeap final { * @param func Function to apply to each node. */ template - void iter_all(const std::function &func) { + void iter_all(const std::function &func) const { this->walk_tree(this->root_node, func); } + iterator begin() const { + return iterator(this->root_node); + } + + iterator end() const { + return iterator(nullptr); + } + private: /** * Apply the given function to all nodes in the tree. @@ -599,18 +786,21 @@ class PairingHeap final { * @param func Function to apply to each node. */ template - void walk_tree(element_t &start, - const std::function &func) { + void walk_tree(const element_t &start, + const std::function &func) const { if constexpr (not reverse) { func(start); } if (start) { auto node = start->first_child; - while (node) { + while (true) { + if (not node) { + break; + } + this->walk_tree(node, func); - if (node) - node = node->next_sibling; + node = node->next_sibling; } if constexpr (reverse) { func(start); diff --git a/libopenage/datastructure/tests.cpp b/libopenage/datastructure/tests.cpp index c375f78c17..40aa516951 100644 --- a/libopenage/datastructure/tests.cpp +++ b/libopenage/datastructure/tests.cpp @@ -1,4 +1,4 @@ -// Copyright 2014-2024 the openage authors. See copying.md for legal info. +// Copyright 2014-2025 the openage authors. See copying.md for legal info. #include "tests.h" @@ -135,6 +135,14 @@ void pairing_heap_3() { heap.push(heap_elem{3}); heap.push(heap_elem{4}); heap.push(heap_elem{5}); + + size_t i = 0; + std::array expected{0, 5, 4, 3, 2, 1}; + for (auto &elem : heap) { + TESTEQUALS(elem.data, expected.at(i)); + i++; + } + heap.pop(); // trigger pairing heap.clear(); From 961d18717bf64c8c094137fb76ed118bb267860d Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 2 Mar 2025 22:45:40 +0100 Subject: [PATCH 110/152] doc: Change mentions of 'checkall' to 'checkmerge'. --- .github/PULL_REQUEST_TEMPLATES/pull_request_template.md | 2 +- Makefile | 1 + doc/buildsystem.md | 2 +- doc/contributing.md | 4 ++-- doc/ide/configs/vscode/tasks.json | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATES/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATES/pull_request_template.md index ed3dfa558f..537cbfb04a 100644 --- a/.github/PULL_REQUEST_TEMPLATES/pull_request_template.md +++ b/.github/PULL_REQUEST_TEMPLATES/pull_request_template.md @@ -5,7 +5,7 @@ - [ ] I have read the [contribution guide](doc/contributing.md) - [ ] I have added my info to [copying.md](copying.md) (only first time contributors) -- [ ] I have run `make checkall` and fixed all mentioned problems +- [ ] I have run `make checkmerge` and fixed all mentioned problems ### Description diff --git a/Makefile b/Makefile index d0dc0585f8..4fd6e2fe5a 100644 --- a/Makefile +++ b/Makefile @@ -179,6 +179,7 @@ help: $(BUILDDIR)/Makefile @echo "tests -> run the tests (py + cpp)" @echo "" @echo "checkall -> full code compliance check" + @echo "checkmerge -> code compliance check for merging to master" @echo "checkfast -> fast checks only" @echo "checkchanged -> full check for all files changed since origin/master" @echo "checkuncommited -> full check for all currently uncommited files" diff --git a/doc/buildsystem.md b/doc/buildsystem.md index 57dcdaf1d7..65b9acba5b 100644 --- a/doc/buildsystem.md +++ b/doc/buildsystem.md @@ -42,7 +42,7 @@ Additional recipes: - `doc` (generate docs via Doxygen) - `test` (runs the various tests) - various cleaning recipes: `cleanelf`, `cleancodegen`, `cleancython`, `cleaninsourcebuild`, `cleanpxdgen`, `cleanbuilddirs`, `mrproper`, `mrproperer` - - various compliance checkers: `checkfast`, `checkall`, ... + - various compliance checkers: `checkfast`, `checkmerge`, `checkall`, ... Phases diff --git a/doc/contributing.md b/doc/contributing.md index 6498b10f95..326c1d1e98 100644 --- a/doc/contributing.md +++ b/doc/contributing.md @@ -52,7 +52,7 @@ tl;dr - Add yourself to `copying.md` - `git add libopenage/unit/tentacle_monster.cpp` - `git commit -m "engine: fixed vomiting animation of tentacle monster"` -- `make checkall` +- `make checkmerge` - `make test` - `git push origin tentacle-monster-fix` - Create a pull request and look at the CI output @@ -101,7 +101,7 @@ Before making a pull request, it's good to review these things: - Run `make test` to check whether any functionality has been broken - [Check your whitespaces](https://github.com/SFTtech/openage/blob/master/doc/code_style/tabs_n_spaces.md) - [Read all the codestyle docs]( https://github.com/SFTtech/openage/tree/master/doc/code_style) -- Before pushing, run `make checkall`. If that fails, the automatic buildbot will reject your code. +- Before pushing, run `make checkmerge`. If that fails, the automatic buildbot will reject your code. - If this is your first contribution, add yourself to the authors list in [copying.md](/copying.md). - Commit messages should be meaningful, they should say in a sentence (or very little text) what changes it has without requiring to read the entire diff. [tpope knows this very well!](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) diff --git a/doc/ide/configs/vscode/tasks.json b/doc/ide/configs/vscode/tasks.json index 9ac35659b0..2a43dbf10e 100644 --- a/doc/ide/configs/vscode/tasks.json +++ b/doc/ide/configs/vscode/tasks.json @@ -51,7 +51,7 @@ "type": "shell", "command": "make", "args": [ - "checkall" + "checkmerge" ], "group": "build", "presentation": { From 7a81292421409b67b3a918ed1780c5697f43088b Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Wed, 5 Mar 2025 22:39:18 +0000 Subject: [PATCH 111/152] ci-cd: always force link to release python lib when configuring windows debug build --- buildsystem/HandlePythonOptions.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/buildsystem/HandlePythonOptions.cmake b/buildsystem/HandlePythonOptions.cmake index 20a35f3d61..7ab80d3bdc 100644 --- a/buildsystem/HandlePythonOptions.cmake +++ b/buildsystem/HandlePythonOptions.cmake @@ -39,7 +39,7 @@ if(PYTHON_VER VERSION_GREATER_EQUAL 3.8 AND PYTHON_VERSION VERSION_LESS 3.9) endif() set(PYEXT_LIBRARY "${PYTHON_LIBRARIES}") - +message("PYTHON_LIBRARIES: " "${PYTHON_LIBRARIES}") #Windows always uses optimized version of Python lib if(WIN32 AND "${CMAKE_BUILD_TYPE}" STREQUAL "Debug") #get index of string "optimized" and increment it by 1 so index points at the path of the optimized lib @@ -47,8 +47,10 @@ if(WIN32 AND "${CMAKE_BUILD_TYPE}" STREQUAL "Debug") if (${_index} GREATER -1) MATH(EXPR _index "${_index}+1") list(GET PYEXT_LIBRARY ${_index} PYEXT_LIBRARY) - set(force_optimized_lib_flag "--force_optimized_lib") + message("force linking to python release lib + instead of debug lib when cythonising") endif() + set(force_optimized_lib_flag "--force_optimized_lib") endif() set(PYEXT_INCLUDE_DIRS "${PYTHON_INCLUDE_DIRS};${NUMPY_INCLUDE_DIR}") From 4d138bcc370571d5095f21700d3abf6d678172bf Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Wed, 5 Mar 2025 22:39:18 +0000 Subject: [PATCH 112/152] ci-cd: always force link to release python lib when configuring windows debug build --- .github/workflows/windows-server-2019.yml | 2 +- .github/workflows/windows-server-2022.yml | 2 +- buildsystem/HandlePythonOptions.cmake | 3 +-- buildsystem/modules/FindPython.cmake | 13 ++++++++++++- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/workflows/windows-server-2019.yml b/.github/workflows/windows-server-2019.yml index 406d303f27..06f9fa0df4 100644 --- a/.github/workflows/windows-server-2019.yml +++ b/.github/workflows/windows-server-2019.yml @@ -50,7 +50,7 @@ jobs: $FLEX_PATH = (Get-ChildItem ./download -Recurse -Force -Filter 'win_flex.exe')[0].FullName mkdir build cd build - cmake -DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN_FILE" -DCMAKE_BUILD_TYPE=Release -DCMAKE_TRY_COMPILE_CONFIGURATION=Release -DCMAKE_CXX_FLAGS='/Zc:__cplusplus /permissive- /EHsc' -DCMAKE_EXE_LINKER_FLAGS='' -DCMAKE_MODULE_LINKER_FLAGS='' -DCMAKE_SHARED_LINKER_FLAGS='' -DDOWNLOAD_NYAN=YES -DCXX_OPTIMIZATION_LEVEL=auto -DCXX_SANITIZE_FATAL=False -DCXX_SANITIZE_MODE=none -DWANT_BACKTRACE=if_available -DWANT_GPERFTOOLS_PROFILER=if_available -DWANT_GPERFTOOLS_TCMALLOC=False -DWANT_INOTIFY=if_available -DWANT_NCURSES=if_available -DWANT_OPENGL=if_available -DWANT_VULKAN=if_available -DFLEX_EXECUTABLE="$FLEX_PATH" -G "Visual Studio 16 2019" -A x64 ../source + cmake -DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN_FILE" -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TRY_COMPILE_CONFIGURATION=Debug -DCMAKE_CXX_FLAGS='/Zc:__cplusplus /permissive- /EHsc' -DCMAKE_EXE_LINKER_FLAGS='' -DCMAKE_MODULE_LINKER_FLAGS='' -DCMAKE_SHARED_LINKER_FLAGS='' -DDOWNLOAD_NYAN=YES -DCXX_OPTIMIZATION_LEVEL=auto -DCXX_SANITIZE_FATAL=False -DCXX_SANITIZE_MODE=none -DWANT_BACKTRACE=if_available -DWANT_GPERFTOOLS_PROFILER=if_available -DWANT_GPERFTOOLS_TCMALLOC=False -DWANT_INOTIFY=if_available -DWANT_NCURSES=if_available -DWANT_OPENGL=if_available -DWANT_VULKAN=if_available -DFLEX_EXECUTABLE="$FLEX_PATH" -G "Visual Studio 16 2019" -A x64 ../source cmake --build . --config Debug -- -nologo -maxCpuCount shell: pwsh - name: Package diff --git a/.github/workflows/windows-server-2022.yml b/.github/workflows/windows-server-2022.yml index 10a6a52c59..0ee700e8f3 100644 --- a/.github/workflows/windows-server-2022.yml +++ b/.github/workflows/windows-server-2022.yml @@ -50,7 +50,7 @@ jobs: $FLEX_PATH = (Get-ChildItem ./download -Recurse -Force -Filter 'win_flex.exe')[0].FullName mkdir build cd build - cmake -DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN_FILE" -DCMAKE_BUILD_TYPE=Release -DCMAKE_TRY_COMPILE_CONFIGURATION=Release -DCMAKE_CXX_FLAGS='/Zc:__cplusplus /permissive- /EHsc' -DCMAKE_EXE_LINKER_FLAGS='' -DCMAKE_MODULE_LINKER_FLAGS='' -DCMAKE_SHARED_LINKER_FLAGS='' -DDOWNLOAD_NYAN=YES -DCXX_OPTIMIZATION_LEVEL=auto -DCXX_SANITIZE_FATAL=False -DCXX_SANITIZE_MODE=none -DWANT_BACKTRACE=if_available -DWANT_GPERFTOOLS_PROFILER=if_available -DWANT_GPERFTOOLS_TCMALLOC=False -DWANT_INOTIFY=if_available -DWANT_NCURSES=if_available -DWANT_OPENGL=if_available -DWANT_VULKAN=if_available -DFLEX_EXECUTABLE="$FLEX_PATH" -G "Visual Studio 17 2022" -A x64 ../source + cmake -DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN_FILE" -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TRY_COMPILE_CONFIGURATION=Debug -DCMAKE_CXX_FLAGS='/Zc:__cplusplus /permissive- /EHsc' -DCMAKE_EXE_LINKER_FLAGS='' -DCMAKE_MODULE_LINKER_FLAGS='' -DCMAKE_SHARED_LINKER_FLAGS='' -DDOWNLOAD_NYAN=YES -DCXX_OPTIMIZATION_LEVEL=auto -DCXX_SANITIZE_FATAL=False -DCXX_SANITIZE_MODE=none -DWANT_BACKTRACE=if_available -DWANT_GPERFTOOLS_PROFILER=if_available -DWANT_GPERFTOOLS_TCMALLOC=False -DWANT_INOTIFY=if_available -DWANT_NCURSES=if_available -DWANT_OPENGL=if_available -DWANT_VULKAN=if_available -DFLEX_EXECUTABLE="$FLEX_PATH" -G "Visual Studio 17 2022" -A x64 ../source cmake --build . --config Debug -- -nologo -maxCpuCount shell: pwsh - name: Package diff --git a/buildsystem/HandlePythonOptions.cmake b/buildsystem/HandlePythonOptions.cmake index 7ab80d3bdc..13ac8a62b8 100644 --- a/buildsystem/HandlePythonOptions.cmake +++ b/buildsystem/HandlePythonOptions.cmake @@ -47,9 +47,8 @@ if(WIN32 AND "${CMAKE_BUILD_TYPE}" STREQUAL "Debug") if (${_index} GREATER -1) MATH(EXPR _index "${_index}+1") list(GET PYEXT_LIBRARY ${_index} PYEXT_LIBRARY) - message("force linking to python release lib - instead of debug lib when cythonising") endif() + message("force linking to python release lib, instead of debug lib when cythonising") set(force_optimized_lib_flag "--force_optimized_lib") endif() diff --git a/buildsystem/modules/FindPython.cmake b/buildsystem/modules/FindPython.cmake index c2e03011d0..61d5494191 100644 --- a/buildsystem/modules/FindPython.cmake +++ b/buildsystem/modules/FindPython.cmake @@ -1,4 +1,4 @@ -# Copyright 2015-2023 the openage authors. See copying.md for legal info. +# Copyright 2015-2025 the openage authors. See copying.md for legal info. # Find Python # ~~~~~~~~~~~ @@ -78,6 +78,12 @@ set(PYTHON_MIN_VERSION_HEX "${BIT_SHIFT_HEX}") # there's a static_assert that tests the Python version. # that way, we verify the interpreter and the library version. # (the interpreter provided us the library location) + +if(WIN32 AND "${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + set(TEMP_CMAKE_TRY_COMPILE_CONFIGURATION ${CMAKE_TRY_COMPILE_CONFIGURATION}) + set(CMAKE_TRY_COMPILE_CONFIGURATION "Release") +endif() + try_compile(PYTHON_TEST_RESULT "${CMAKE_BINARY_DIR}" SOURCES "${CMAKE_CURRENT_LIST_DIR}/FindPython_test.cpp" @@ -87,6 +93,11 @@ try_compile(PYTHON_TEST_RESULT OUTPUT_VARIABLE PYTHON_TEST_OUTPUT ) +if(WIN32 AND "${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + set(CMAKE_TRY_COMPILE_CONFIGURATION ${TEMP_CMAKE_TRY_COMPILE_CONFIGURATION}) +endif() + + if(NOT PYTHON_TEST_RESULT) message(STATUS "!! No suitable Python interpreter was found !! From 81b0443ee23cd983bec510513d88e24803e58aa4 Mon Sep 17 00:00:00 2001 From: anatriaslabella Date: Mon, 17 Feb 2025 21:10:51 -0500 Subject: [PATCH 113/152] Added sin, cos, and tan operations to fixed-point implementation --- copying.md | 1 + libopenage/util/fixed_point.h | 29 ++++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/copying.md b/copying.md index 3938eaa8e0..dd71a8dc82 100644 --- a/copying.md +++ b/copying.md @@ -162,6 +162,7 @@ _the openage authors_ are: | Michael Lynch | mtlynch | git à mtlynch dawt io | | Ngô Xuân Minh | | xminh dawt ngo dawt 00 à gmail dawt com | | Haytham Tang | haytham918 | yunxuant à umich dawt edu | +| Ana Trias-Labellarte | anatriaslabella | ana dawt triaslabella à ufl dawt edu | If you're a first-time committer, add yourself to the above list. This is not just for legal reasons, but also to keep an overview of all those nicknames. diff --git a/libopenage/util/fixed_point.h b/libopenage/util/fixed_point.h index 4e97e77323..e9f86ce0ef 100644 --- a/libopenage/util/fixed_point.h +++ b/libopenage/util/fixed_point.h @@ -1,4 +1,4 @@ -// Copyright 2015-2024 the openage authors. See copying.md for legal info. +// Copyright 2015-2025 the openage authors. See copying.md for legal info. #pragma once @@ -451,6 +451,18 @@ class FixedPoint { constexpr double atan2(const FixedPoint &n) { return std::atan2(this->to_double(), n.to_double()); } + + constexpr double sin() { + return std::sin(this->to_double()); + } + + constexpr double cos() { + return std::cos(this->to_double()); + } + + constexpr double tan() { + return std::tan(this->to_double()); + } }; @@ -567,6 +579,21 @@ constexpr double atan2(openage::util::FixedPoint x, openage::util::FixedPo return x.atan2(y); } +template +constexpr double sin(openage::util::FixedPoint n) { + return n.sin(); +} + +template +constexpr double cos(openage::util::FixedPoint n) { + return n.cos(); +} + +template +constexpr double tan(openage::util::FixedPoint n) { + return n.tan(); +} + template constexpr openage::util::FixedPoint min(openage::util::FixedPoint x, openage::util::FixedPoint y) { return openage::util::FixedPoint::from_raw_value( From 247910482b937bd39369efe92c66919e53d81bef Mon Sep 17 00:00:00 2001 From: Eelco Empting Date: Wed, 12 Mar 2025 20:34:26 +0100 Subject: [PATCH 114/152] Add new template parameter & tests; prettier test failures --- libopenage/testing/testing.h | 37 +++++-- libopenage/util/fixed_point.h | 149 ++++++++++++++------------- libopenage/util/fixed_point_test.cpp | 36 ++++++- 3 files changed, 142 insertions(+), 80 deletions(-) diff --git a/libopenage/testing/testing.h b/libopenage/testing/testing.h index 50e03338e0..83f0efaab9 100644 --- a/libopenage/testing/testing.h +++ b/libopenage/testing/testing.h @@ -1,4 +1,4 @@ -// Copyright 2014-2024 the openage authors. See copying.md for legal info. +// Copyright 2014-2025 the openage authors. See copying.md for legal info. #pragma once @@ -51,7 +51,9 @@ bool fail(const log::message &msg); try { \ auto &&test_result_left = (left); \ if (test_result_left != (right)) { \ - TESTFAILMSG("unexpected value: " << (test_result_left)); \ + TESTFAILMSG(__FILE__ << ":" << __LINE__ << ": Expected " \ + << test_result_left << " and " \ + << (right) << " to be equal"); \ } \ } \ catch (::openage::testing::TestError & e) { \ @@ -63,6 +65,28 @@ bool fail(const log::message &msg); } \ while (0) +/** + * Asserts that the left expression does not equal the right expression, + * and that no exception is thrown. + */ +#define TESTNOTEQUALS(left, right) \ + do { \ + try { \ + auto &&test_result_left = (left); \ + if (test_result_left == (right)) { \ + TESTFAILMSG(__FILE__ << ":" << __LINE__ << ": Expected " \ + << test_result_left << " and " \ + << (right) << " to be NOT equal"); \ + } \ + } \ + catch (::openage::testing::TestError & e) { \ + throw; \ + } \ + catch (::openage::error::Error & e) { \ + TESTFAILMSG("unexpected exception: " << e); \ + } \ + } \ + while (0) /** * Asserts that the left expression equals the right expression, @@ -72,8 +96,9 @@ bool fail(const log::message &msg); do { \ try { \ auto &&test_result_left = (left); \ - if ((test_result_left < (right - epsilon)) or (test_result_left > (right + epsilon))) { \ - TESTFAILMSG("unexpected value: " << (test_result_left)); \ + auto &&test_result_right = (right); \ + if ((test_result_left < (test_result_right - epsilon)) or (test_result_left > (test_result_right + epsilon))) { \ + TESTFAILMSG(__FILE__ << ":" << __LINE__ << ": Expected " << (test_result_left) << " and " << (test_result_right) << " to be equal"); \ } \ } \ catch (::openage::testing::TestError & e) { \ @@ -99,7 +124,7 @@ bool fail(const log::message &msg); expr_has_thrown = true; \ } \ if (not expr_has_thrown) { \ - TESTFAILMSG("no exception"); \ + TESTFAILMSG(__FILE__ << ":" << __LINE__ << ": no exception"); \ } \ } \ while (0) @@ -114,7 +139,7 @@ bool fail(const log::message &msg); expression; \ } \ catch (::openage::error::Error & e) { \ - TESTFAILMSG("unexpected exception"); \ + TESTFAILMSG(__FILE__ << ":" << __LINE__ << ": unexpected exception"); \ } \ } \ while (0) diff --git a/libopenage/util/fixed_point.h b/libopenage/util/fixed_point.h index e9f86ce0ef..2d6a7eb084 100644 --- a/libopenage/util/fixed_point.h +++ b/libopenage/util/fixed_point.h @@ -85,14 +85,16 @@ constexpr static * If you change this class, remember to update the gdb pretty printers * in etc/gdb_pretty/printers.py. */ -template +template class FixedPoint { public: using raw_type = int_type; - using this_type = FixedPoint; + using this_type = FixedPoint; using unsigned_int_type = typename std::make_unsigned::type; + using unsigned_intermediate_type = typename std::make_unsigned::type; + using same_type_but_unsigned = FixedPoint; + fractional_bits, typename FixedPoint::unsigned_intermediate_type>; private: // Helper function to create the scaling factors that are used below. @@ -264,14 +266,14 @@ class FixedPoint { /** * Factory function to get a fixed-point number from a fixed-point number of different type. */ - template other_fractional_bits)>::type * = nullptr> - static constexpr FixedPoint from_fixedpoint(const FixedPoint &other) { + template other_fractional_bits)>::type * = nullptr> + static constexpr FixedPoint from_fixedpoint(const FixedPoint &other) { return FixedPoint::from_raw_value( safe_shift(static_cast(other.get_raw_value()))); } - template ::type * = nullptr> - static constexpr FixedPoint from_fixedpoint(const FixedPoint &other) { + template ::type * = nullptr> + static constexpr FixedPoint from_fixedpoint(const FixedPoint &other) { return FixedPoint::from_raw_value( static_cast(other.get_raw_value() / safe_shiftleft(1))); } @@ -380,14 +382,14 @@ class FixedPoint { return FixedPoint::this_type::from_raw_value(-this->raw_value); } - template - constexpr double hypot(const FixedPoint rhs) { + template + constexpr double hypot(const FixedPoint rhs) { return std::hypot(this->to_double(), rhs.to_double()); } - template - constexpr FixedPoint hypotfp(const FixedPoint rhs) { - return FixedPoint(this->hypot(rhs)); + template + constexpr FixedPoint hypotfp(const FixedPoint rhs) { + return FixedPoint(this->hypot(rhs)); } // Basic operators @@ -471,42 +473,42 @@ class FixedPoint { /** * FixedPoint + FixedPoint */ -template -constexpr FixedPoint operator+(const FixedPoint &lhs, const FixedPoint &rhs) { - return FixedPoint::from_raw_value(lhs.get_raw_value() + rhs.get_raw_value()); +template +constexpr FixedPoint operator+(const FixedPoint &lhs, const FixedPoint &rhs) { + return FixedPoint::from_raw_value(lhs.get_raw_value() + rhs.get_raw_value()); } /** * FixedPoint + double */ -template -constexpr FixedPoint operator+(const FixedPoint &lhs, const double &rhs) { - return FixedPoint{lhs} + FixedPoint::from_double(rhs); +template +constexpr FixedPoint operator+(const FixedPoint &lhs, const double &rhs) { + return FixedPoint{lhs} + FixedPoint::from_double(rhs); } /** * FixedPoint - FixedPoint */ -template -constexpr FixedPoint operator-(const FixedPoint &lhs, const FixedPoint &rhs) { - return FixedPoint::from_raw_value(lhs.get_raw_value() - rhs.get_raw_value()); +template +constexpr FixedPoint operator-(const FixedPoint &lhs, const FixedPoint &rhs) { + return FixedPoint::from_raw_value(lhs.get_raw_value() - rhs.get_raw_value()); } /** * FixedPoint - double */ -template -constexpr FixedPoint operator-(const FixedPoint &lhs, const double &rhs) { - return FixedPoint{lhs} - FixedPoint::from_double(rhs); +template +constexpr FixedPoint operator-(const FixedPoint &lhs, const double &rhs) { + return FixedPoint{lhs} - FixedPoint::from_double(rhs); } /** * FixedPoint * N */ -template -typename std::enable_if::value, FixedPoint>::type constexpr operator*(const FixedPoint lhs, const N &rhs) { - return FixedPoint::from_raw_value(lhs.get_raw_value() * rhs); +template +typename std::enable_if::value, FixedPoint>::type constexpr operator*(const FixedPoint lhs, const N &rhs) { + return FixedPoint::from_raw_value(lhs.get_raw_value() * rhs); } /* @@ -523,40 +525,41 @@ typename std::enable_if::value, FixedPoint>::type co * => a * a will overflow because: * a.rawvalue == 2^(16+16) == 2^32 * -> a.rawvalue * a.rawvalue == 2^64 => pwnt + * + * Use a larger intermediate type to prevent overflow */ -// template -// constexpr FixedPoint operator*(const FixedPoint lhs, const FixedPoint rhs) { -// I ret = 0; -// if (not __builtin_mul_overflow(lhs.get_raw_value(), rhs.get_raw_value(), &ret)) { -// throw std::overflow_error("FixedPoint multiplication overflow"); -// } +template +constexpr FixedPoint operator*(const FixedPoint lhs, const FixedPoint rhs) { + Inter ret = static_cast(lhs.get_raw_value()) * static_cast(rhs.get_raw_value()); + ret >>= F; -// return FixedPoint::from_raw_value(ret); -// } + return FixedPoint::from_raw_value(static_cast(ret)); +} /** * FixedPoint / FixedPoint */ -template -constexpr FixedPoint operator/(const FixedPoint lhs, const FixedPoint rhs) { - return FixedPoint::from_raw_value(div(lhs.get_raw_value(), rhs.get_raw_value()) << F); +template +constexpr FixedPoint operator/(const FixedPoint lhs, const FixedPoint rhs) { + Inter ret = div((static_cast(lhs.get_raw_value()) << F), static_cast(rhs.get_raw_value())); + return FixedPoint::from_raw_value(static_cast(ret)); } /** * FixedPoint / N */ -template -constexpr FixedPoint operator/(const FixedPoint lhs, const N &rhs) { - return FixedPoint::from_raw_value(div(lhs.get_raw_value(), static_cast(rhs))); +template +constexpr FixedPoint operator/(const FixedPoint lhs, const N &rhs) { + return FixedPoint::from_raw_value(div(lhs.get_raw_value(), static_cast(rhs))); } /** * FixedPoint % FixedPoint (modulo) */ -template -constexpr FixedPoint operator%(const FixedPoint lhs, const FixedPoint rhs) { +template +constexpr FixedPoint operator%(const FixedPoint lhs, const FixedPoint rhs) { auto div = (lhs / rhs); auto n = div.to_int(); return lhs - (rhs * n); @@ -569,71 +572,71 @@ constexpr FixedPoint operator%(const FixedPoint lhs, const FixedPoin // std function overloads namespace std { -template -constexpr double sqrt(openage::util::FixedPoint n) { +template +constexpr double sqrt(openage::util::FixedPoint n) { return n.sqrt(); } -template -constexpr double atan2(openage::util::FixedPoint x, openage::util::FixedPoint y) { +template +constexpr double atan2(openage::util::FixedPoint x, openage::util::FixedPoint y) { return x.atan2(y); } -template -constexpr double sin(openage::util::FixedPoint n) { +template +constexpr double sin(openage::util::FixedPoint n) { return n.sin(); } -template -constexpr double cos(openage::util::FixedPoint n) { +template +constexpr double cos(openage::util::FixedPoint n) { return n.cos(); } -template -constexpr double tan(openage::util::FixedPoint n) { +template +constexpr double tan(openage::util::FixedPoint n) { return n.tan(); } -template -constexpr openage::util::FixedPoint min(openage::util::FixedPoint x, openage::util::FixedPoint y) { - return openage::util::FixedPoint::from_raw_value( +template +constexpr openage::util::FixedPoint min(openage::util::FixedPoint x, openage::util::FixedPoint y) { + return openage::util::FixedPoint::from_raw_value( std::min(x.get_raw_value(), y.get_raw_value())); } -template -constexpr openage::util::FixedPoint max(openage::util::FixedPoint x, openage::util::FixedPoint y) { - return openage::util::FixedPoint::from_raw_value( +template +constexpr openage::util::FixedPoint max(openage::util::FixedPoint x, openage::util::FixedPoint y) { + return openage::util::FixedPoint::from_raw_value( std::max(x.get_raw_value(), y.get_raw_value())); } -template -constexpr openage::util::FixedPoint abs(openage::util::FixedPoint n) { - return openage::util::FixedPoint::from_raw_value( +template +constexpr openage::util::FixedPoint abs(openage::util::FixedPoint n) { + return openage::util::FixedPoint::from_raw_value( std::abs(n.get_raw_value())); } -template -constexpr double hypot(openage::util::FixedPoint x, openage::util::FixedPoint y) { +template +constexpr double hypot(openage::util::FixedPoint x, openage::util::FixedPoint y) { return x.hypot(y); } -template -struct hash> { - constexpr size_t operator()(const openage::util::FixedPoint &n) const { +template +struct hash> { + constexpr size_t operator()(const openage::util::FixedPoint &n) const { return std::hash{}(n.raw_value); } }; -template -struct numeric_limits> { - constexpr static openage::util::FixedPoint min() { - return openage::util::FixedPoint::min_value(); +template +struct numeric_limits> { + constexpr static openage::util::FixedPoint min() { + return openage::util::FixedPoint::min_value(); } - constexpr static openage::util::FixedPoint max() { - return openage::util::FixedPoint::max_value(); + constexpr static openage::util::FixedPoint max() { + return openage::util::FixedPoint::max_value(); } }; diff --git a/libopenage/util/fixed_point_test.cpp b/libopenage/util/fixed_point_test.cpp index 7a7fb2bc0c..21fd788259 100644 --- a/libopenage/util/fixed_point_test.cpp +++ b/libopenage/util/fixed_point_test.cpp @@ -1,4 +1,4 @@ -// Copyright 2016-2023 the openage authors. See copying.md for legal info. +// Copyright 2016-2025 the openage authors. See copying.md for legal info. #include "fixed_point.h" @@ -122,6 +122,40 @@ void fixed_point() { TESTEQUALS_FLOAT(TestTypeShort::e().to_double(), math::E, 1e-3); TESTEQUALS_FLOAT(TestTypeShort::pi().to_double(), math::PI, 1e-3); + { + using S = FixedPoint; + using T = FixedPoint; + + auto a = S::from_int(16U); + TESTNOTEQUALS((a*a).to_int(), 256U); + + auto b = T::from_int(16U); + TESTEQUALS((b*b).to_int(), 256U); + + auto c = T::from_int(17U); + TESTEQUALS((c*c).to_int(), 289U); + } + { + using S = FixedPoint; + auto a = S::from_int(256); + auto b = S::from_int(8); + TESTNOTEQUALS((a/b).to_int(), 32); + + + using T = FixedPoint; + auto c = T::from_int(256); + auto d = T::from_int(8); + TESTEQUALS((c/d).to_int(), 32); + } + { + using T = FixedPoint; + auto a = T::from_double(4.75); + auto b = T::from_double(3.5); + auto c = -a; + TESTEQUALS_FLOAT((a/b).to_double(), 4.75/3.5, 0.1); + TESTEQUALS_FLOAT((c/b).to_double(), -4.75/3.5, 0.1); + } + } }}} // openage::util::tests From 5ad6d7282d857c4ce149cd802a5311f8050119e3 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 15 Mar 2025 11:34:04 +0100 Subject: [PATCH 115/152] Added name to copying.md --- copying.md | 1 + 1 file changed, 1 insertion(+) diff --git a/copying.md b/copying.md index dd71a8dc82..862f18bb39 100644 --- a/copying.md +++ b/copying.md @@ -163,6 +163,7 @@ _the openage authors_ are: | Ngô Xuân Minh | | xminh dawt ngo dawt 00 à gmail dawt com | | Haytham Tang | haytham918 | yunxuant à umich dawt edu | | Ana Trias-Labellarte | anatriaslabella | ana dawt triaslabella à ufl dawt edu | +| Eelco Empting | Eeelco | me à eelco dawt de | If you're a first-time committer, add yourself to the above list. This is not just for legal reasons, but also to keep an overview of all those nicknames. From cc6bddfe300c2526f66d99a04ba3aaf9a41f7f69 Mon Sep 17 00:00:00 2001 From: Jordan Sutton Date: Mon, 17 Mar 2025 06:10:05 -0600 Subject: [PATCH 116/152] Update missing CMAKE_CXX_COMPILER env in ubuntu dockerfile --- copying.md | 1 + packaging/docker/devenv/Dockerfile.ubuntu.2404 | 2 ++ 2 files changed, 3 insertions(+) diff --git a/copying.md b/copying.md index 862f18bb39..8daf89e0a9 100644 --- a/copying.md +++ b/copying.md @@ -164,6 +164,7 @@ _the openage authors_ are: | Haytham Tang | haytham918 | yunxuant à umich dawt edu | | Ana Trias-Labellarte | anatriaslabella | ana dawt triaslabella à ufl dawt edu | | Eelco Empting | Eeelco | me à eelco dawt de | +| Jordan Sutton | jsutCodes | jsutcodes à gmial dawt com | If you're a first-time committer, add yourself to the above list. This is not just for legal reasons, but also to keep an overview of all those nicknames. diff --git a/packaging/docker/devenv/Dockerfile.ubuntu.2404 b/packaging/docker/devenv/Dockerfile.ubuntu.2404 index d3d492f6af..5a135fe2a2 100644 --- a/packaging/docker/devenv/Dockerfile.ubuntu.2404 +++ b/packaging/docker/devenv/Dockerfile.ubuntu.2404 @@ -37,3 +37,5 @@ RUN apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y sudo \ qml6-module-qtquick3d-spatialaudio \ && sudo apt-get clean \ && truncate -s 0 ~/.bash_history + +ENV CMAKE_CXX_COMPILER=/usr/bin/g++ From 36727b6c64a9b18ccab06691d05213a5e4468641 Mon Sep 17 00:00:00 2001 From: Jordan Sutton Date: Mon, 17 Mar 2025 08:56:17 -0600 Subject: [PATCH 117/152] Remove -11 argument Ubuntu 24.04 LTS installs with base of g++-13 and gcc-13. Unsure if this is the correct path. Could either move forward with 13 or can change to manually install the 11 version in the Docker Build proccess. --- .github/workflows/ubuntu-24.04.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ubuntu-24.04.yml b/.github/workflows/ubuntu-24.04.yml index 6fa9145279..2fd07b7030 100644 --- a/.github/workflows/ubuntu-24.04.yml +++ b/.github/workflows/ubuntu-24.04.yml @@ -41,7 +41,7 @@ jobs: - name: Build openage run: | sudo docker run --rm -v "$(pwd)":/mnt/openage -w /mnt/openage openage-devenv:latest \ - bash -c 'mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=$(which gcc-11) -DCMAKE_CXX_COMPILER=$(which g++-11) -DCMAKE_CXX_FLAGS='' -DCMAKE_EXE_LINKER_FLAGS='' -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_MODULE_LINKER_FLAGS='' -DCMAKE_SHARED_LINKER_FLAGS='' -DDOWNLOAD_NYAN=YES -DCXX_OPTIMIZATION_LEVEL=auto -DCXX_SANITIZE_FATAL=False -DCXX_SANITIZE_MODE=none -DWANT_BACKTRACE=if_available -DWANT_GPERFTOOLS_PROFILER=if_available -DWANT_GPERFTOOLS_TCMALLOC=False -DWANT_INOTIFY=if_available -DWANT_NCURSES=if_available -DWANT_OPENGL=if_available -DWANT_VULKAN=if_available -G Ninja .. && cmake --build . --parallel $(nproc) -- -k1' + bash -c 'mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=$(which gcc) -DCMAKE_CXX_COMPILER=$(which g++) -DCMAKE_CXX_FLAGS='' -DCMAKE_EXE_LINKER_FLAGS='' -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_MODULE_LINKER_FLAGS='' -DCMAKE_SHARED_LINKER_FLAGS='' -DDOWNLOAD_NYAN=YES -DCXX_OPTIMIZATION_LEVEL=auto -DCXX_SANITIZE_FATAL=False -DCXX_SANITIZE_MODE=none -DWANT_BACKTRACE=if_available -DWANT_GPERFTOOLS_PROFILER=if_available -DWANT_GPERFTOOLS_TCMALLOC=False -DWANT_INOTIFY=if_available -DWANT_NCURSES=if_available -DWANT_OPENGL=if_available -DWANT_VULKAN=if_available -G Ninja .. && cmake --build . --parallel $(nproc) -- -k1' - name: Compress build artifacts run: | mkdir -p /tmp/openage From 9b9cf3ee81abedd1f0f6d6eee1c26251f5a4b7d9 Mon Sep 17 00:00:00 2001 From: Jordan Sutton Date: Mon, 17 Mar 2025 09:14:29 -0600 Subject: [PATCH 118/152] Undo unecessary ENV command --- packaging/docker/devenv/Dockerfile.ubuntu.2404 | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packaging/docker/devenv/Dockerfile.ubuntu.2404 b/packaging/docker/devenv/Dockerfile.ubuntu.2404 index 5a135fe2a2..3ca06676f1 100644 --- a/packaging/docker/devenv/Dockerfile.ubuntu.2404 +++ b/packaging/docker/devenv/Dockerfile.ubuntu.2404 @@ -36,6 +36,4 @@ RUN apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y sudo \ qt6-multimedia-dev \ qml6-module-qtquick3d-spatialaudio \ && sudo apt-get clean \ - && truncate -s 0 ~/.bash_history - -ENV CMAKE_CXX_COMPILER=/usr/bin/g++ + && truncate -s 0 ~/.bash_history \ No newline at end of file From 11fb72283b0c9a5be449c17c3aa629da5bb22ed2 Mon Sep 17 00:00:00 2001 From: Jordan Sutton Date: Mon, 17 Mar 2025 09:23:27 -0600 Subject: [PATCH 119/152] Update copying.md --- copying.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/copying.md b/copying.md index 8daf89e0a9..9d8f6703e1 100644 --- a/copying.md +++ b/copying.md @@ -164,7 +164,7 @@ _the openage authors_ are: | Haytham Tang | haytham918 | yunxuant à umich dawt edu | | Ana Trias-Labellarte | anatriaslabella | ana dawt triaslabella à ufl dawt edu | | Eelco Empting | Eeelco | me à eelco dawt de | -| Jordan Sutton | jsutCodes | jsutcodes à gmial dawt com | +| Jordan Sutton | jsutCodes | jsutcodes à gmail dawt com | If you're a first-time committer, add yourself to the above list. This is not just for legal reasons, but also to keep an overview of all those nicknames. From d255e7ec598b542137e744448ba835c8caa204a7 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 31 Mar 2025 02:27:13 +0200 Subject: [PATCH 120/152] doc: Add 'vulkan' package to Windows vcpkg install instructions. --- doc/build_instructions/windows_msvc.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/build_instructions/windows_msvc.md b/doc/build_instructions/windows_msvc.md index e31bcf79cd..43def4cbd0 100644 --- a/doc/build_instructions/windows_msvc.md +++ b/doc/build_instructions/windows_msvc.md @@ -47,7 +47,11 @@ _Note:_ Also ensure that `python` and `python3` both point to the correct and th vcpkg install dirent eigen3 fontconfig freetype harfbuzz libepoxy libogg libpng opus opusfile qtbase qtdeclarative qtmultimedia toml11 - _Note:_ The `qt6` port in vcpkg has been split into multiple packages, build times are acceptable now. +If you also want Vulkan graphics support, you should also run this command: + + vcpkg install vulkan + + _Note:_ The `qt` port in vcpkg has been split into multiple packages, build times are acceptable now. If you want, you can still use [the prebuilt version](https://www.qt.io/download-open-source/) instead. If you do so, include `-DCMAKE_PREFIX_PATH=` in the cmake configure command. From 932358c39c0b95990f649ca1073a8d3bebfb12e7 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 31 Mar 2025 02:40:48 +0200 Subject: [PATCH 121/152] doc: Reformat Windows build docs Markdown. --- doc/build_instructions/windows_msvc.md | 122 +++++++++++++------------ 1 file changed, 65 insertions(+), 57 deletions(-) diff --git a/doc/build_instructions/windows_msvc.md b/doc/build_instructions/windows_msvc.md index 43def4cbd0..9dc072db5a 100644 --- a/doc/build_instructions/windows_msvc.md +++ b/doc/build_instructions/windows_msvc.md @@ -17,104 +17,112 @@ __NOTE:__ You need to manually make sure and doublecheck if the system you are b --> ## Setting up the build environment - You will need to download and install the following manually. - Those who already have the latest stable versions of these programs can skip this: - - [Visual Studio Buildtools](https://aka.ms/vs/17/release/vs_BuildTools.exe) - - With the "Visual C++ Buildtools" workload. +You will need to download and install the following manually. +Those who already have the latest stable versions of these programs can skip this: - _NOTE:_ If you are searching for an IDE for development you can get an overview [here](https://en.wikipedia.org/wiki/Comparison_of_integrated_development_environments#C/C++). - We've also written some [instructions for developing with different IDEs](/doc/ide/README.md). +- [Visual Studio Buildtools](https://aka.ms/vs/17/release/vs_BuildTools.exe) + - With the "Visual C++ Buildtools" workload. + _NOTE:_ If you are searching for an IDE for development you can get an overview [here](https://en.wikipedia.org/wiki/Comparison_of_integrated_development_environments#C/C++). We've also written some [instructions for developing with different IDEs](/doc/ide/README.md). - - [Python 3](https://www.python.org/downloads/windows/) - - With the "pip" option enabled. We use `pip` to install other dependencies. - - With the "Precompile standard library" option enabled. - - With the "Download debug binaries (...)" option enabled. - - If in doubt, run the installer again and choose "Modify". - - You are going to need the 64-bit version of python if you are planning to build the 64-bit version of openage, and vice versa. +- [Python 3](https://www.python.org/downloads/windows/) + - With the *pip* option enabled. We use `pip` to install other dependencies. + - With the *Precompile standard library* option enabled. + - With the *Download debug binaries (...)* option enabled. + - If in doubt, run the installer again and choose *Modify*. + - You are going to need the 64-bit version of python if you are planning to build the 64-bit version of openage, and vice versa. - [CMake](https://cmake.org/download/) ### Python Modules - Open a command prompt at `/Scripts` - - pip install cython numpy lz4 toml pillow pygments pyreadline3 mako +Open a command prompt at `/Scripts` +```ps +pip install cython numpy lz4 toml pillow pygments pyreadline3 mako +``` _Note:_ Make sure the Python 3 instance you're installing these scripts for is the one you call `python` in CMD + _Note:_ Also ensure that `python` and `python3` both point to the correct and the same version of Python 3 ### vcpkg packages - Set up [vcpkg](https://github.com/Microsoft/vcpkg#quick-start). Open a command prompt at `` +Set up [vcpkg](https://github.com/Microsoft/vcpkg#quick-start). Open a command prompt at `` - vcpkg install dirent eigen3 fontconfig freetype harfbuzz libepoxy libogg libpng opus opusfile qtbase qtdeclarative qtmultimedia toml11 +```ps +vcpkg install dirent eigen3 fontconfig freetype harfbuzz libepoxy libogg libpng opus opusfile qtbase qtdeclarative qtmultimedia toml11 +``` If you also want Vulkan graphics support, you should also run this command: - vcpkg install vulkan +```ps +vcpkg install vulkan +``` - _Note:_ The `qt` port in vcpkg has been split into multiple packages, build times are acceptable now. - If you want, you can still use [the prebuilt version](https://www.qt.io/download-open-source/) instead. - If you do so, include `-DCMAKE_PREFIX_PATH=` in the cmake configure command. +_Note:_ The `qt` port in vcpkg has been split into multiple packages, build times are acceptable now. If you want, you can still use [the prebuilt version](https://www.qt.io/download-open-source/) instead. If you do so, include `-DCMAKE_PREFIX_PATH=` in the cmake configure command. - _Note:_ If you are planning to build the 64-bit version of openage, you are going to need 64-bit libraries. - Add command line option `--triplet x64-windows` to the above command or add the environment variable `VCPKG_DEFAULT_TRIPLET=x64-windows` to build x64 libraries. [See here](https://github.com/Microsoft/vcpkg/issues/1254) +_Note:_ If you are planning to build the 64-bit version of openage, you are going to need 64-bit libraries. Add command line option `--triplet x64-windows` to the above command or add the environment variable `VCPKG_DEFAULT_TRIPLET=x64-windows` to build x64 libraries. [See here](https://github.com/Microsoft/vcpkg/issues/1254) ## Building openage - Note that openage doesn't support completely out-of-source-tree builds yet. - We will, however, use a separate `build` directory to build the binaries. +Note that openage doesn't support completely out-of-source-tree builds yet. We will, however, use a separate `build` directory to build the binaries. -_Note:_ You will also need to set up [the dependencies for Nyan](https://github.com/SFTtech/nyan/blob/master/doc/building.md#windows), which is mainly [flex](https://sourceforge.net/projects/winflexbison/) +_Note:_ You will also need to set up [the dependencies for nyan](https://github.com/SFTtech/nyan/blob/master/doc/building.md#windows), which is mainly [flex](https://sourceforge.net/projects/winflexbison/). Open a command prompt at ``: - mkdir build - cd build - cmake -DCMAKE_TOOLCHAIN_FILE=\scripts\buildsystems\vcpkg.cmake .. - cmake --build . --config RelWithDebInfo -- /nologo /m /v:m +```ps +mkdir build +cd build +cmake -DCMAKE_TOOLCHAIN_FILE=\scripts\buildsystems\vcpkg.cmake .. +cmake --build . --config RelWithDebInfo -- /nologo /m /v:m +``` _Note:_ If you want to build the x64 version, please add `-G "Visual Studio 17 2022" -A x64` (for VS2022) to the first cmake command. _Note:_ If you want to download and build Nyan automatically add `-DDOWNLOAD_NYAN=YES -DFLEX_EXECUTABLE=` to the first cmake command. ## Running openage (in devmode) - While this is straightforward on other platforms, there is still stuff to do to run openage on Windows: - - Install the [DejaVu Book Font](https://dejavu-fonts.github.io/Download.html). - - Download and extract the latest `dejavu-fonts-ttf` tarball/zip file. - - Select all `ttf\DejaVuSerif*.ttf` files, right click and click `Install for all users`. - - _Note:_ This will require administrator rights. - - Set the `FONTCONFIG_PATH` environment variable to `\installed\\tools\fontconfig\fonts\` or `\installed\\etc\fonts\`. - - Copy `fontconfig\57-dejavu-serif.conf` to `%FONTCONFIG_PATH%\conf.d`. - - [Optional] Set the `AGE2DIR` environment variable to the AoE 2 installation directory. - - Set `QML2_IMPORT_PATH` to `\installed\\qml` or for prebuilt Qt `\\\qml` - - openage needs these DLL files to run: - - `openage.dll` (Usually in `\build\libopenage\`.) - - `nyan.dll` (The location depends on the procedure chosen to get nyan.) - - DLLs from vcpkg-installed dependencies. Normally, these DLLs should be copied to `\build\libopenage\` during the build process. If they are not, you can find them in `\installed\\bin`. - - If prebuilt QT6 was installed, the original location of QT6 DLLs is `\bin`. - - - Now, to run the openage: - - Open a CMD window in `\build\` and run `python -m openage main` - - Execute`\build\run.exe` every time after that and enjoy! +While this is straightforward on other platforms, there is still stuff to do to run openage on Windows: + +- Install the [DejaVu Book Font](https://dejavu-fonts.github.io/Download.html). + - Download and extract the latest `dejavu-fonts-ttf` tarball/zip file. + - Select all `ttf\DejaVuSerif*.ttf` files, right click and click `Install for all users`. + _Note:_ This will require administrator rights. + + - Set the `FONTCONFIG_PATH` environment variable to `\installed\\tools\fontconfig\fonts\` or `\installed\\etc\fonts\`. + - Copy `fontconfig\57-dejavu-serif.conf` to `%FONTCONFIG_PATH%\conf.d`. +- [Optional] Set the `AGE2DIR` environment variable to the AoE 2 installation directory. +- Set `QML2_IMPORT_PATH` to `\installed\\qml` or for prebuilt Qt `\\\qml` +- openage needs these DLL files to run: + - `openage.dll` (Usually in `\build\libopenage\`.) + - `nyan.dll` (The location depends on the procedure chosen to get nyan.) + - DLLs from vcpkg-installed dependencies. Normally, these DLLs should be copied to `\build\libopenage\` during the build process. If they are not, you can find them in `\installed\\bin`. + - If prebuilt QT6 was installed, the original location of QT6 DLLs is `\bin`. + +Now, to run openage: + +- Open a CMD window in `\build\` and run `python -m openage main` +- Execute`\build\run.exe` every time after that and enjoy! ## Packaging - - Install [NSIS](https://sourceforge.net/projects/nsis/files/latest/download). - - Depending on the way you installed Qt (vcpkg/pre-built) you need to edit the following line in `\buildsystem\templates\ForwardVariables.cmake.in`: +- Install [NSIS](https://sourceforge.net/projects/nsis/files/latest/download). +- Depending on the way you installed Qt (vcpkg/pre-built) you need to edit the following line in `\buildsystem\templates\ForwardVariables.cmake.in`: + ``` - # Use windeploy for packaging qt-prebuilt, standard value '1' for windeploy, '0' for vcpkg - set(use_windeployqt 1) +# Use windeploy for packaging qt-prebuilt, standard value '1' for windeploy, '0' for vcpkg +set(use_windeployqt 1) ``` - Open a command prompt at `\build` (or use the one from the building step): +Open a command prompt at `\build` (or use the one from the building step): +```ps cpack -C RelWithDebInfo +``` - The installer (`openage--.exe`) will be generated in the same directory.
+The installer (`openage--.exe`) will be generated in the same directory.
- _Hint:_ Append `-V` to the `cpack` command for verbose output (it takes time to package all dependencies). +_Note:_ Append `-V` to the `cpack` command for verbose output (it takes time to package all dependencies). - _Hint:_ you can set with the environment variable `TARGET_PLATFORM` (e.g. amd64, x86). +_Note:_ you can set with the environment variable `TARGET_PLATFORM` (e.g. amd64, x86). From 777163d8535b6c8cd21a3a23bd5aac79b149631e Mon Sep 17 00:00:00 2001 From: danielwieczorek Date: Sun, 6 Apr 2025 14:37:17 +0200 Subject: [PATCH 122/152] Update new contributor Add new contributor into copying.md and mailmap Issue: https://github.com/SFTtech/openage/issues/1766 --- copying.md | 1 + 1 file changed, 1 insertion(+) diff --git a/copying.md b/copying.md index 9d8f6703e1..d8430fd215 100644 --- a/copying.md +++ b/copying.md @@ -165,6 +165,7 @@ _the openage authors_ are: | Ana Trias-Labellarte | anatriaslabella | ana dawt triaslabella à ufl dawt edu | | Eelco Empting | Eeelco | me à eelco dawt de | | Jordan Sutton | jsutCodes | jsutcodes à gmail dawt com | +| Daniel Wieczorek | Danio | danielwieczorek96 à gmail dawt com | If you're a first-time committer, add yourself to the above list. This is not just for legal reasons, but also to keep an overview of all those nicknames. From 4cf5f6fd5588cd2fbb0f45e73ea878749919a6c6 Mon Sep 17 00:00:00 2001 From: danielwieczorek Date: Sun, 6 Apr 2025 14:16:51 +0200 Subject: [PATCH 123/152] Fix assets conversion bug. Add running with docker docs Using Cython < 3.0.10 leads to the runtime errors on assets conversion. Cython >= 3.0.10 shall be used. Update docker build file and add chapter in documentation about running image with Docker. Issue: https://github.com/SFTtech/openage/issues/1766 --- CMakeLists.txt | 4 +- README.md | 4 +- buildsystem/HandlePythonOptions.cmake | 16 ++++- doc/build_instructions/docker.md | 70 +++++++++++++++++++ doc/build_instructions/ubuntu.md | 14 ++-- doc/building.md | 3 +- .../docker/devenv/Dockerfile.ubuntu.2404 | 6 +- 7 files changed, 104 insertions(+), 13 deletions(-) create mode 100644 doc/build_instructions/docker.md diff --git a/CMakeLists.txt b/CMakeLists.txt index 47484acb06..7a6a785bf4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,7 +39,9 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) # Python and Cython requirements set(PYTHON_MIN_VERSION 3.9) -set(CYTHON_MIN_VERSION 0.29.31) +set(CYTHON_MIN_VERSION 3.0.10) +set(CYTHON_MIN_VERSION_FALLBACK 0.29.31) +set(CYTHON_MAX_VERSION_FALLBACK 3.0.7) # CMake policies foreach(pol diff --git a/README.md b/README.md index c0bcf4a02c..8666083783 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,9 @@ Quickstart make ``` +**Alternative approach:** +You can build and run the project using Docker. See [Running with docker](/doc/build_instructions/docker.md) for more details. + * **I compiled everything. Now how do I run it?** * Execute `cd bin && ./run main`. * [The convert script](/doc/media_convert.md) will transform original assets into openage formats, which are a lot saner and more moddable. @@ -145,7 +148,6 @@ To turn them off, use `./bin/run --dont-segfault --no-errors --dont-eat-dog`. If this still does not help, try our [troubleshooting guide](/doc/troubleshooting.md), the [contact section](#contact) or the [bug tracker](https://github.com/SFTtech/openage/issues). - Contributing ============ diff --git a/buildsystem/HandlePythonOptions.cmake b/buildsystem/HandlePythonOptions.cmake index 13ac8a62b8..4c5988efad 100644 --- a/buildsystem/HandlePythonOptions.cmake +++ b/buildsystem/HandlePythonOptions.cmake @@ -4,7 +4,17 @@ # the Python version number requirement is in modules/FindPython_test.cpp find_package(Python ${PYTHON_MIN_VERSION} REQUIRED) -find_package(Cython ${CYTHON_MIN_VERSION} REQUIRED) + +find_package(Cython ${CYTHON_MIN_VERSION}) +if(NOT CYTHON_FOUND) + message("Checking for alternative Cython fallback version (>=${CYTHON_MIN_VERSION_FALLBACK} AND <=${CYTHON_MAX_VERSION_FALLBACK})") + find_package(Cython ${CYTHON_MIN_VERSION_FALLBACK} QUIET) + if(CYTHON_VERSION VERSION_LESS ${CYTHON_MIN_VERSION} AND CYTHON_VERSION VERSION_GREATER ${CYTHON_MAX_VERSION_FALLBACK}) + message(FATAL_ERROR "Cython version ${CYTHON_VERSION} is not compatible") + else() + message("Compatible Cython version ${CYTHON_VERSION} found") + endif() +endif() py_get_config_var(EXT_SUFFIX PYEXT_SUFFIX) if(MINGW) @@ -43,8 +53,8 @@ message("PYTHON_LIBRARIES: " "${PYTHON_LIBRARIES}") #Windows always uses optimized version of Python lib if(WIN32 AND "${CMAKE_BUILD_TYPE}" STREQUAL "Debug") #get index of string "optimized" and increment it by 1 so index points at the path of the optimized lib - list (FIND PYEXT_LIBRARY "optimized" _index) - if (${_index} GREATER -1) + list(FIND PYEXT_LIBRARY "optimized" _index) + if(${_index} GREATER -1) MATH(EXPR _index "${_index}+1") list(GET PYEXT_LIBRARY ${_index} PYEXT_LIBRARY) endif() diff --git a/doc/build_instructions/docker.md b/doc/build_instructions/docker.md new file mode 100644 index 0000000000..6a431f0c63 --- /dev/null +++ b/doc/build_instructions/docker.md @@ -0,0 +1,70 @@ +### Running with Docker + +Docker simplifies the setup process by providing a consistent development environment. It allows you to build and run the project without manually installing dependencies. Currently provided [Dockerfile.ubuntu.2404](../../packaging/docker/devenv/Dockerfile.ubuntu.2404) supports Ubuntu 24.04 as the base operating system. + +#### Prerequisites + +- **Docker**: Ensure Docker is installed on your machine. Follow the [official documentation](https://docs.docker.com/) for installation instructions. +- **Display Server**: This guide supports both **X11** and **Wayland** display servers for GUI applications. +- **Tested Configuration**: These instructions were tested on Ubuntu 24.04 running on WSL2 on Windows 11. + +#### Steps to Build and Run + +1. **Enable GUI Support** + + Depending on your display server, follow the appropriate steps: + + - **For X11**: + Allow the Docker container to access your X server: + ```bash + xhost +local:root + ``` + + - **For Wayland**: + Allow the Docker container to access your Wayland socket: + ```bash + sudo chmod a+rw /run/user/$(id -u)/wayland-0 + ``` + +2. **Build the Docker Image** + + Build the Docker image using the provided Dockerfile: + ```bash + sudo docker build -t openage -f packaging/docker/devenv/Dockerfile.ubuntu.2404 . + ``` + +3. **Run the Docker Container** + + Start the Docker container with the appropriate configuration for your display server: + + - **For X11**: + ```bash + docker run -it \ + -e DISPLAY=$DISPLAY \ + -v /tmp/.X11-unix:/tmp/.X11-unix \ + -v $HOME/.Xauthority:/root/.Xauthority \ + --network host openage + ``` + + - **For Wayland**: + ```bash + docker run -it \ + -e XDG_RUNTIME_DIR=/tmp \ + -e QT_QPA_PLATFORM=wayland \ + -e WAYLAND_DISPLAY=$WAYLAND_DISPLAY \ + -v $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY:/tmp/$WAYLAND_DISPLAY \ + --user=$(id -u):$(id -g) \ + --network host openage + ``` + +4. **Follow the Regular Setup** + +Once inside the container, follow the regular setup described in the [Development](../building.md#development) chapter. You can skip dependency installation since the Docker image already includes all required dependencies. + +#### Notes + +- **X11 vs. Wayland**: Ensure you know which display server your system is using. Most modern Linux distributions default to Wayland, but X11 is still widely used. +- **Permissions**: For Wayland, you may need to adjust permissions for the Wayland socket (`/run/user/$(id -u)/wayland-0`) to allow Docker access. +- **GUI Applications**: These configurations enable GUI applications to run inside the Docker container. + +By following these steps, you can build and run the `openage` project in a Dockerized environment with support X11 or Wayland display servers. diff --git a/doc/build_instructions/ubuntu.md b/doc/build_instructions/ubuntu.md index d46ebd159f..46cd975fb3 100644 --- a/doc/build_instructions/ubuntu.md +++ b/doc/build_instructions/ubuntu.md @@ -2,19 +2,23 @@ Run the following commands: - - `sudo apt-get update` - - `sudo apt-get install g++ cmake cython3 libeigen3-dev libepoxy-dev libfontconfig1-dev libfreetype-dev libharfbuzz-dev libogg-dev libopus-dev libopusfile-dev libpng-dev libtoml11-dev python3-dev python3-mako python3-numpy python3-lz4 python3-pil python3-pip python3-pygments python3-toml qml6-module-qtquick-controls qt6-declarative-dev qt6-multimedia-dev qml6-module-qtquick3d-spatialaudio` +```bash + sudo apt-get update + sudo apt-get install g++ cmake cython3 libeigen3-dev libepoxy-dev libfontconfig1-dev libfreetype-dev libharfbuzz-dev libogg-dev libopus-dev libopusfile-dev libpng-dev libtoml11-dev python3-dev python3-mako python3-numpy python3-lz4 python3-pil python3-pip python3-pygments python3-toml qml6-module-qtquick-controls qt6-declarative-dev qt6-multimedia-dev qml6-module-qtquick3d-spatialaudio + ``` You will also need [nyan](https://github.com/SFTtech/nyan/blob/master/doc/building.md) and its dependencies. -# Additional steps for Ubuntu 22.04 LTS +# Additional steps for Ubuntu 22.04 LTS & 24.04 LTS -The available system version of Cython is too old in Ubuntu 22.04. You have to get the correct version +The available system version of Cython is too old in Ubuntu 22.04 & 24.04. You have to get the correct version from pip: -``` +```bash pip3 install cython --break-system-packages ``` +Please note that project requires at least **Cython 3.0.10**. + # Linux Mint Issue Linux Mint has a [problem with `toml11`](https://github.com/SFTtech/openage/issues/1601), since CMake can't find it. To solve this, download the [toml11.zip](https://github.com/SFTtech/openage/files/13401192/toml11.zip), after, put the files in the `/usr/lib/x86_64-linux-gnu/cmake/toml11` path. (if the `toml11` directory doesn't exist, create it) diff --git a/doc/building.md b/doc/building.md index 6ed51cb4a6..923fde5507 100644 --- a/doc/building.md +++ b/doc/building.md @@ -29,7 +29,7 @@ Dependency list: C gcc >=10 or clang >=10 CRA python >=3.9 - C cython >=0.29.31 + C cython >=3.0.10 OR (>=0.29.31 AND <=3.0.7) C cmake >=3.16 A numpy A lz4 @@ -161,7 +161,6 @@ The reference package is [created for Gentoo](https://github.com/SFTtech/gentoo- - Use `make install DESTDIR=/tmp/your_temporary_packaging_dir`, which will then be packed/installed by your package manager. - ### Troubleshooting - I wanna see compiler invocations diff --git a/packaging/docker/devenv/Dockerfile.ubuntu.2404 b/packaging/docker/devenv/Dockerfile.ubuntu.2404 index 3ca06676f1..f57dd43321 100644 --- a/packaging/docker/devenv/Dockerfile.ubuntu.2404 +++ b/packaging/docker/devenv/Dockerfile.ubuntu.2404 @@ -36,4 +36,8 @@ RUN apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y sudo \ qt6-multimedia-dev \ qml6-module-qtquick3d-spatialaudio \ && sudo apt-get clean \ - && truncate -s 0 ~/.bash_history \ No newline at end of file + && truncate -s 0 ~/.bash_history + +# At least cython >= 3.0.10 < 4.0.0 is required to avoid runtime errors +# TODO: Remove this line once cython is upgraded in Ubuntu 24.04.3 (expected around August 2025) +RUN pip install "cython>=3.0.10,<4.0.0" --break-system-packages \ No newline at end of file From 6d5ee2bb253f897bd1c1b88a75132f9567ec1221 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 20 Apr 2025 02:01:46 +0200 Subject: [PATCH 124/152] etc: Fix import of gdb printing Python module. --- etc/gdb_pretty/printers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/etc/gdb_pretty/printers.py b/etc/gdb_pretty/printers.py index fe6d43e397..9ae29539ce 100644 --- a/etc/gdb_pretty/printers.py +++ b/etc/gdb_pretty/printers.py @@ -1,4 +1,4 @@ -# Copyright 2024-2024 the openage authors. See copying.md for legal info. +# Copyright 2024-2025 the openage authors. See copying.md for legal info. """ Pretty printers for GDB. @@ -6,6 +6,7 @@ import re import gdb # type: ignore +import gdb.printing # type: ignore # TODO: Printers should inherit from gdb.ValuePrinter when gdb 14.1 is available in all distros. From 8ae9de699784defb318cc4f6250eccaed9b44983 Mon Sep 17 00:00:00 2001 From: bytegrrrl Date: Sun, 13 Apr 2025 08:19:45 -0400 Subject: [PATCH 125/152] Add pure FixedPoint sqrt implementation and tests --- libopenage/util/fixed_point.h | 32 ++++++++++++++++++++++- libopenage/util/fixed_point_test.cpp | 38 ++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/libopenage/util/fixed_point.h b/libopenage/util/fixed_point.h index 2d6a7eb084..337c3ceb4e 100644 --- a/libopenage/util/fixed_point.h +++ b/libopenage/util/fixed_point.h @@ -3,6 +3,7 @@ #pragma once #include +#include #include #include #include @@ -446,8 +447,37 @@ class FixedPoint { return is; } + /** + * Pure FixedPoint sqrt implementation using Heron's Algorithm. + * + * Note that this function is undefined for negative values. + */ constexpr double sqrt() { - return std::sqrt(this->to_double()); + // Zero can cause issues later, so deal with now. + if (this->raw_value == 0) { + return 0.0; + } + + // A greater shift = more precision, but can overflow the intermediate type if too large. + size_t max_shift = std::countl_zero((unsigned_intermediate_type)this->raw_value) - 1; + size_t shift = max_shift > fractional_bits ? fractional_bits : max_shift; + shift &= ~1; + + // We can't use the safe shift since the shift value is unknown at compile time. + intermediate_type n = (intermediate_type)this->raw_value << shift; + intermediate_type guess = (intermediate_type)1 << fractional_bits; + + for (size_t _i = 0; _i < fractional_bits; _i++) { + intermediate_type prev = guess; + guess = (guess + n / guess) / 2; + if (guess == prev) + break; + } + + // The sqrt operation halves the number of bits, so we'll we'll have to calculate a shift back + size_t unshift = fractional_bits - (shift + fractional_bits) / 2; + + return from_raw_value(guess << unshift).to_double(); } constexpr double atan2(const FixedPoint &n) { diff --git a/libopenage/util/fixed_point_test.cpp b/libopenage/util/fixed_point_test.cpp index 21fd788259..f5951ac267 100644 --- a/libopenage/util/fixed_point_test.cpp +++ b/libopenage/util/fixed_point_test.cpp @@ -156,6 +156,44 @@ void fixed_point() { TESTEQUALS_FLOAT((c/b).to_double(), -4.75/3.5, 0.1); } + // Pure FixedPoint sqrt tests + { + using T = FixedPoint; + TESTEQUALS_FLOAT(T(41231.131).sqrt(), 203.0545025356, 1e-7); + TESTEQUALS_FLOAT(T(547965.116).sqrt(), 740.2466588915, 1e-7); + + TESTEQUALS_FLOAT(T(2).sqrt(), T::sqrt_2(), 1e-9); + TESTEQUALS_FLOAT(2 / T::pi().sqrt(), T::inv2_sqrt_pi(), 1e-9); + + // Powers of two (anything over 2^15 will overflow (2^16)^2 = 2^32 >). + for (size_t i = 0; i < 15; i++) { + int64_t value = 1 << i; + TESTEQUALS_FLOAT(T(value * value).sqrt(), value, 1e-7); + } + + for (size_t i = 0; i < 100; i++) { + double value = 14.25 * i; + TESTEQUALS_FLOAT(T(value * value).sqrt(), value, 1e-7); + } + + // This one can go up to 2^63, but that would take years. + for (uint32_t i = 0; i < (1u << 16); i++) { + T value = T::from_raw_value(i * i); + TESTEQUALS_FLOAT(value.sqrt(), std::sqrt(value.to_double()), 1e-7); + } + + // We lose some precision when raw_type == intermediate_type + for (uint64_t i = 1; i < (1ul << 63); i = (i << 1) ^ i) { + T value = T::from_raw_value(i * i); + TESTEQUALS_FLOAT(value.sqrt(), std::sqrt(value.to_double()), 1e-4); + } + + using FP16_16 = FixedPoint; + for (uint32_t i = 0; i < (1u << 16); i++) { + FP16_16 value = FP16_16::from_raw_value(i); + TESTEQUALS_FLOAT(value.sqrt(), std::sqrt(value.to_double()), 1e-4); + } + } } }}} // openage::util::tests From 9890d12319de2c4a5425895d00a06f1b2fc511c0 Mon Sep 17 00:00:00 2001 From: bytegrrrl Date: Sun, 13 Apr 2025 19:26:52 -0400 Subject: [PATCH 126/152] Update copying.md --- copying.md | 1 + 1 file changed, 1 insertion(+) diff --git a/copying.md b/copying.md index d8430fd215..8c61b294e2 100644 --- a/copying.md +++ b/copying.md @@ -166,6 +166,7 @@ _the openage authors_ are: | Eelco Empting | Eeelco | me à eelco dawt de | | Jordan Sutton | jsutCodes | jsutcodes à gmail dawt com | | Daniel Wieczorek | Danio | danielwieczorek96 à gmail dawt com | +| | bytegrrrl | bytegrrrl à proton dawt me | If you're a first-time committer, add yourself to the above list. This is not just for legal reasons, but also to keep an overview of all those nicknames. From a51a90e3b1ef56c7167e4fa1ac19f14b3be0bda4 Mon Sep 17 00:00:00 2001 From: bytegrrrl Date: Mon, 21 Apr 2025 03:13:39 -0400 Subject: [PATCH 127/152] Update FixedPoint::sqrt() to return a FixedPoint --- libopenage/util/fixed_point.h | 32 ++++++++++++++++++++-------- libopenage/util/fixed_point_test.cpp | 18 +++++++++++----- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/libopenage/util/fixed_point.h b/libopenage/util/fixed_point.h index 337c3ceb4e..6cab0167f7 100644 --- a/libopenage/util/fixed_point.h +++ b/libopenage/util/fixed_point.h @@ -451,33 +451,47 @@ class FixedPoint { * Pure FixedPoint sqrt implementation using Heron's Algorithm. * * Note that this function is undefined for negative values. + * + * There's a small loss in precision depending on the value of fractional_bits and the position of + * the most significant bit: if the integer portion is very large, we won't have as much (absolute) + * precision. Ideally you would want the intermediate_type to be twice the size of raw_type to avoid + * any losses. */ - constexpr double sqrt() { + constexpr FixedPoint sqrt() { // Zero can cause issues later, so deal with now. if (this->raw_value == 0) { return 0.0; } + // Check for negative values + ENSURE(std::is_unsigned() or std::is_signed() and this->raw_value > 0, "FixedPoint::sqrt() is undefined for negative values."); + // A greater shift = more precision, but can overflow the intermediate type if too large. - size_t max_shift = std::countl_zero((unsigned_intermediate_type)this->raw_value) - 1; + size_t max_shift = std::countl_zero(static_cast(this->raw_value)) - 1; size_t shift = max_shift > fractional_bits ? fractional_bits : max_shift; - shift &= ~1; + + // shift + fractional bits must be an even number + if ((shift + fractional_bits) % 2) { + shift -= 1; + } // We can't use the safe shift since the shift value is unknown at compile time. - intermediate_type n = (intermediate_type)this->raw_value << shift; - intermediate_type guess = (intermediate_type)1 << fractional_bits; + intermediate_type n = static_cast(this->raw_value) << shift; + intermediate_type guess = static_cast(1) << fractional_bits; - for (size_t _i = 0; _i < fractional_bits; _i++) { + for (size_t i = 0; i < fractional_bits; i++) { intermediate_type prev = guess; guess = (guess + n / guess) / 2; - if (guess == prev) + if (guess == prev) { break; + + } } // The sqrt operation halves the number of bits, so we'll we'll have to calculate a shift back size_t unshift = fractional_bits - (shift + fractional_bits) / 2; - return from_raw_value(guess << unshift).to_double(); + return from_raw_value(guess << unshift); } constexpr double atan2(const FixedPoint &n) { @@ -604,7 +618,7 @@ namespace std { template constexpr double sqrt(openage::util::FixedPoint n) { - return n.sqrt(); + return static_cast(n.sqrt()); } template diff --git a/libopenage/util/fixed_point_test.cpp b/libopenage/util/fixed_point_test.cpp index f5951ac267..64ba8f6b2c 100644 --- a/libopenage/util/fixed_point_test.cpp +++ b/libopenage/util/fixed_point_test.cpp @@ -163,7 +163,7 @@ void fixed_point() { TESTEQUALS_FLOAT(T(547965.116).sqrt(), 740.2466588915, 1e-7); TESTEQUALS_FLOAT(T(2).sqrt(), T::sqrt_2(), 1e-9); - TESTEQUALS_FLOAT(2 / T::pi().sqrt(), T::inv2_sqrt_pi(), 1e-9); + TESTEQUALS_FLOAT(2 / std::sqrt(T::pi()), T::inv2_sqrt_pi(), 1e-9); // Powers of two (anything over 2^15 will overflow (2^16)^2 = 2^32 >). for (size_t i = 0; i < 15; i++) { @@ -177,23 +177,31 @@ void fixed_point() { } // This one can go up to 2^63, but that would take years. - for (uint32_t i = 0; i < (1u << 16); i++) { + for (uint32_t i = 0; i < 65536; i++) { T value = T::from_raw_value(i * i); TESTEQUALS_FLOAT(value.sqrt(), std::sqrt(value.to_double()), 1e-7); } // We lose some precision when raw_type == intermediate_type - for (uint64_t i = 1; i < (1ul << 63); i = (i << 1) ^ i) { + for (uint64_t i = 1; i < std::numeric_limits::max(); i = (i * 2) ^ i) { T value = T::from_raw_value(i * i); + if (value < 0) { + value = -value; + } TESTEQUALS_FLOAT(value.sqrt(), std::sqrt(value.to_double()), 1e-4); } using FP16_16 = FixedPoint; - for (uint32_t i = 0; i < (1u << 16); i++) { + for (uint32_t i = 0; i < 65536; i++) { FP16_16 value = FP16_16::from_raw_value(i); TESTEQUALS_FLOAT(value.sqrt(), std::sqrt(value.to_double()), 1e-4); } + + + // Test with negative number + TESTTHROWS((FixedPoint::from_float(-3.25).sqrt())); + TESTNOEXCEPT((FixedPoint::from_float(3.25).sqrt())); + TESTNOEXCEPT((FixedPoint::from_float(-3.25).sqrt())); } } - }}} // openage::util::tests From 4a7d664ce0104ef3b966f15c5b1f90329b7099dc Mon Sep 17 00:00:00 2001 From: bytegrrrl Date: Mon, 21 Apr 2025 04:55:08 -0400 Subject: [PATCH 128/152] Fix sqrt test cases --- libopenage/util/fixed_point.h | 6 +++--- libopenage/util/fixed_point_test.cpp | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/libopenage/util/fixed_point.h b/libopenage/util/fixed_point.h index 6cab0167f7..2b3a97797a 100644 --- a/libopenage/util/fixed_point.h +++ b/libopenage/util/fixed_point.h @@ -460,11 +460,11 @@ class FixedPoint { constexpr FixedPoint sqrt() { // Zero can cause issues later, so deal with now. if (this->raw_value == 0) { - return 0.0; + return zero(); } - // Check for negative values - ENSURE(std::is_unsigned() or std::is_signed() and this->raw_value > 0, "FixedPoint::sqrt() is undefined for negative values."); + // Check for negative values + ENSURE(std::is_unsigned() or std::is_signed() and this->raw_value > 0, "FixedPoint::sqrt() is undefined for negative values."); // A greater shift = more precision, but can overflow the intermediate type if too large. size_t max_shift = std::countl_zero(static_cast(this->raw_value)) - 1; diff --git a/libopenage/util/fixed_point_test.cpp b/libopenage/util/fixed_point_test.cpp index 64ba8f6b2c..b3690b8d15 100644 --- a/libopenage/util/fixed_point_test.cpp +++ b/libopenage/util/fixed_point_test.cpp @@ -192,7 +192,7 @@ void fixed_point() { } using FP16_16 = FixedPoint; - for (uint32_t i = 0; i < 65536; i++) { + for (uint32_t i = 1; i < 65536; i++) { FP16_16 value = FP16_16::from_raw_value(i); TESTEQUALS_FLOAT(value.sqrt(), std::sqrt(value.to_double()), 1e-4); } @@ -204,4 +204,5 @@ void fixed_point() { TESTNOEXCEPT((FixedPoint::from_float(-3.25).sqrt())); } } + }}} // openage::util::tests From 55196f1f92492794a12423ece919d6153f66a852 Mon Sep 17 00:00:00 2001 From: bytegrrrl Date: Tue, 22 Apr 2025 00:34:11 -0400 Subject: [PATCH 129/152] Fix formatting --- libopenage/util/fixed_point.h | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/libopenage/util/fixed_point.h b/libopenage/util/fixed_point.h index 2b3a97797a..d8d54e3550 100644 --- a/libopenage/util/fixed_point.h +++ b/libopenage/util/fixed_point.h @@ -451,11 +451,11 @@ class FixedPoint { * Pure FixedPoint sqrt implementation using Heron's Algorithm. * * Note that this function is undefined for negative values. - * - * There's a small loss in precision depending on the value of fractional_bits and the position of - * the most significant bit: if the integer portion is very large, we won't have as much (absolute) - * precision. Ideally you would want the intermediate_type to be twice the size of raw_type to avoid - * any losses. + * + * There's a small loss in precision depending on the value of fractional_bits and the position of + * the most significant bit: if the integer portion is very large, we won't have as much (absolute) + * precision. Ideally you would want the intermediate_type to be twice the size of raw_type to avoid + * any losses. */ constexpr FixedPoint sqrt() { // Zero can cause issues later, so deal with now. @@ -464,16 +464,16 @@ class FixedPoint { } // Check for negative values - ENSURE(std::is_unsigned() or std::is_signed() and this->raw_value > 0, "FixedPoint::sqrt() is undefined for negative values."); + ENSURE(std::is_unsigned() or (std::is_signed() and this->raw_value > 0), "FixedPoint::sqrt() is undefined for negative values."); // A greater shift = more precision, but can overflow the intermediate type if too large. size_t max_shift = std::countl_zero(static_cast(this->raw_value)) - 1; size_t shift = max_shift > fractional_bits ? fractional_bits : max_shift; - // shift + fractional bits must be an even number - if ((shift + fractional_bits) % 2) { - shift -= 1; - } + // shift + fractional bits must be an even number + if ((shift + fractional_bits) % 2) { + shift -= 1; + } // We can't use the safe shift since the shift value is unknown at compile time. intermediate_type n = static_cast(this->raw_value) << shift; @@ -484,8 +484,7 @@ class FixedPoint { guess = (guess + n / guess) / 2; if (guess == prev) { break; - - } + } } // The sqrt operation halves the number of bits, so we'll we'll have to calculate a shift back From 292d6dadd5ecb7977aad6f4ba0b615ac4cc0fac4 Mon Sep 17 00:00:00 2001 From: bytegrrrl Date: Wed, 23 Apr 2025 18:20:19 -0400 Subject: [PATCH 130/152] Optimize FixedPoint::sqrt() signedness check Co-authored-by: Christoph Heine <6852422+heinezen@users.noreply.github.com> --- libopenage/util/fixed_point.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libopenage/util/fixed_point.h b/libopenage/util/fixed_point.h index d8d54e3550..c751238cdf 100644 --- a/libopenage/util/fixed_point.h +++ b/libopenage/util/fixed_point.h @@ -464,7 +464,9 @@ class FixedPoint { } // Check for negative values - ENSURE(std::is_unsigned() or (std::is_signed() and this->raw_value > 0), "FixedPoint::sqrt() is undefined for negative values."); + if constexpr (std::is_signed()) { + ENSURE(this->raw_value > 0, "FixedPoint::sqrt() is undefined for negative values."); + } // A greater shift = more precision, but can overflow the intermediate type if too large. size_t max_shift = std::countl_zero(static_cast(this->raw_value)) - 1; From 8586c3239091dfe7a31b6fbcc213ed3e37f1cedb Mon Sep 17 00:00:00 2001 From: Andreas Date: Tue, 13 May 2025 19:53:20 +0200 Subject: [PATCH 131/152] Fix configuration step for macos clang is mainly for building c files, while it can build c++ it does not link c++ libraries. --- doc/build_instructions/macos.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/build_instructions/macos.md b/doc/build_instructions/macos.md index e38dee9932..72458f6269 100644 --- a/doc/build_instructions/macos.md +++ b/doc/build_instructions/macos.md @@ -38,7 +38,7 @@ We advise against using the clang version that comes with macOS (Apple Clang) as ``` # on Intel macOS, llvm is by default in /usr/local/Cellar/llvm/bin/ # on ARM macOS, llvm is by default in /opt/homebrew/Cellar/llvm/bin/ -./configure --compiler="$(brew --prefix llvm)/bin/clang" --download-nyan +./configure --compiler="$(brew --prefix llvm)/bin/clang++" --download-nyan ``` Afterwards, trigger the build using `make`: From 85298c11f75da40ed07e2ed46b631234e5059694 Mon Sep 17 00:00:00 2001 From: Alex Zhuohao He Date: Sun, 29 Dec 2024 15:25:11 -0500 Subject: [PATCH 132/152] add world shader commands implementation --- .../renderer/stages/world/CMakeLists.txt | 1 + .../stages/world/world_shader_commands.cpp | 73 ++++++++++++ .../stages/world/world_shader_commands.h | 108 ++++++++++++++++++ 3 files changed, 182 insertions(+) create mode 100644 libopenage/renderer/stages/world/world_shader_commands.cpp create mode 100644 libopenage/renderer/stages/world/world_shader_commands.h diff --git a/libopenage/renderer/stages/world/CMakeLists.txt b/libopenage/renderer/stages/world/CMakeLists.txt index 811fd929ed..d86d7aa44c 100644 --- a/libopenage/renderer/stages/world/CMakeLists.txt +++ b/libopenage/renderer/stages/world/CMakeLists.txt @@ -2,4 +2,5 @@ add_sources(libopenage object.cpp render_entity.cpp render_stage.cpp + world_shader_commands.cpp ) diff --git a/libopenage/renderer/stages/world/world_shader_commands.cpp b/libopenage/renderer/stages/world/world_shader_commands.cpp new file mode 100644 index 0000000000..e7b31f87c1 --- /dev/null +++ b/libopenage/renderer/stages/world/world_shader_commands.cpp @@ -0,0 +1,73 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "world_shader_commands.h" + +#include "error/error.h" +#include "log/log.h" + +namespace openage::renderer::world { + +bool WorldShaderCommands::add_command(uint8_t alpha, const std::string &code, const std::string &description) { + if (!validate_alpha(alpha)) { + log::log(ERR << "Invalid alpha value: " << int(alpha)); + return false; + } + if (!validate_code(code)) { + log::log(ERR << "Invalid command code"); + return false; + } + + commands_map[alpha] = {alpha, code, description}; + return true; +} + +bool WorldShaderCommands::remove_command(uint8_t alpha) { + if (!validate_alpha(alpha)) { + return false; + } + commands_map.erase(alpha); + return true; +} + +bool WorldShaderCommands::has_command(uint8_t alpha) const { + return commands_map.contains(alpha); +} + +std::string WorldShaderCommands::integrate_command(const std::string &base_shader) { + std::string final_shader = base_shader; + std::string commands_code = generate_command_code(); + + // Find the insertion point + size_t insert_point = final_shader.find(COMMAND_MARKER); + if (insert_point == std::string::npos) { + throw Error(MSG(err) << "Failed to find command insertion point in shader."); + } + + // Replace the insertion point with the generated command code + final_shader.replace(insert_point, strlen(COMMAND_MARKER), commands_code); + + return final_shader; +} + +std::string WorldShaderCommands::generate_command_code() const { + std::string result = ""; + + for (const auto &[alpha, command] : commands_map) { + result += " case " + std::to_string(alpha) + ":\n"; + result += " // " + command.description + "\n"; + result += " " + command.code + "\n"; + result += " break;\n\n"; + } + + return result; +} + +bool WorldShaderCommands::validate_alpha(uint8_t alpha) const { + return alpha % 2 == 0 && alpha >= 0 && alpha <= 254; +} + +bool WorldShaderCommands::validate_code(const std::string &code) const { + return !code.empty(); +} + +} // namespace openage::renderer::world diff --git a/libopenage/renderer/stages/world/world_shader_commands.h b/libopenage/renderer/stages/world/world_shader_commands.h new file mode 100644 index 0000000000..d025735db2 --- /dev/null +++ b/libopenage/renderer/stages/world/world_shader_commands.h @@ -0,0 +1,108 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include + +namespace openage { +namespace renderer { +namespace world { + +/** + * Represents a single shader command that can be used in the world fragment shader. + * Commands are identified by their alpha values and contain GLSL code snippets + * that define custom rendering behavior. + */ +struct ShaderCommand { + // Command identifier ((must be even, range 0-254)) + uint8_t alpha; + // GLSL code snippet that defines the command's behavior + std::string code; + // Documentation (optional) + std::string description; +}; + +/** + * Manages shader commands for the world fragment shader. + * Provides functionality to add, remove, and integrate commands into the base shader. + * Commands are inserted at a predefined marker in the shader code. + */ +class WorldShaderCommands { +public: + // Marker in shader code where commands will be inserted + static constexpr const char *COMMAND_MARKER = "//@INSERT_COMMANDS@"; + + /** + * Add a new shader command. + * + * @param alpha Command identifier (must be even, range 0-254) + * @param code GLSL code snippet defining the command's behavior + * @param description Human-readable description of the command's purpose + * + * @return true if command was added successfully, false if validation failed + */ + bool add_command(uint8_t alpha, const std::string &code, const std::string &description = ""); + + /** + * Remove a command. + * + * @param alpha Command identifier (even values 0-254) + */ + bool remove_command(uint8_t alpha); + + /** + * Check if a command is registered. + * + * @param alpha Command identifier to check + * + * @return true if command is registered + */ + bool has_command(uint8_t alpha) const; + + /** + * Integrate registered commands into the base shader code. + * + * @param base_shader Original shader code containing the command marker + * + * @return Complete shader code with commands integrated at the marker position + * + * @throws Error if command marker is not found in the base shader + */ + std::string integrate_command(const std::string &base_shader); + +private: + /** + * Generate GLSL code for all registered commands. + * + * @return String containing case statements for each command + */ + std::string generate_command_code() const; + + /** + * Validate a command identifier. + * + * @param alpha Command identifier to validate + * + * @return true if alpha is even and within valid range (0-254) + */ + bool validate_alpha(uint8_t alpha) const; + + /** + * Validate command GLSL code. + * + * @param code GLSL code snippet to validate + * + * @return true if code is not empty (additional validation could be added) + */ + + bool validate_code(const std::string &code) const; + + // Map of command identifiers to their respective commands + std::map commands_map; +}; + +} // namespace world +} // namespace renderer +} // namespace openage From 856b901ce907db8d191f54f71f6e15f31ae9f916 Mon Sep 17 00:00:00 2001 From: Alex Zhuohao He Date: Sun, 29 Dec 2024 15:26:41 -0500 Subject: [PATCH 133/152] use world shader commands in render_stage & update world2d.frag --- assets/shaders/world2d.frag.glsl | 10 +------ .../renderer/stages/world/render_stage.cpp | 28 +++++++++++++++++-- .../renderer/stages/world/render_stage.h | 13 +++++++++ 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/assets/shaders/world2d.frag.glsl b/assets/shaders/world2d.frag.glsl index 98d323e0f6..f9de0354ba 100644 --- a/assets/shaders/world2d.frag.glsl +++ b/assets/shaders/world2d.frag.glsl @@ -26,15 +26,7 @@ void main() { // do not save the ID return; - case 254: - col = vec4(1.0f, 0.0f, 0.0f, 1.0f); - break; - case 252: - col = vec4(0.0f, 1.0f, 0.0f, 1.0f); - break; - case 250: - col = vec4(0.0f, 0.0f, 1.0f, 1.0f); - break; + //@INSERT_COMMANDS@ default: col = tex_val; break; diff --git a/libopenage/renderer/stages/world/render_stage.cpp b/libopenage/renderer/stages/world/render_stage.cpp index d8d93279af..3962daa170 100644 --- a/libopenage/renderer/stages/world/render_stage.cpp +++ b/libopenage/renderer/stages/world/render_stage.cpp @@ -127,12 +127,18 @@ void WorldRenderStage::initialize_render_pass(size_t width, vert_shader_file.read()); vert_shader_file.close(); + // Initialize shader command system before loading fragment shader + this->shader_commands = std::make_unique(); + this->init_shader_commands(); + auto frag_shader_file = (shaderdir / "world2d.frag.glsl").open(); + auto base_shader = frag_shader_file.read(); + frag_shader_file.close(); + auto frag_shader_src = renderer::resources::ShaderSource( resources::shader_lang_t::glsl, resources::shader_stage_t::fragment, - frag_shader_file.read()); - frag_shader_file.close(); + this->shader_commands->integrate_command(base_shader)); this->output_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::rgba8)); this->depth_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::depth24)); @@ -156,4 +162,22 @@ void WorldRenderStage::init_uniform_ids() { WorldObject::anchor_offset = this->display_shader->get_uniform_id("anchor_offset"); } +void WorldRenderStage::init_shader_commands() { + // Register default shader commands + this->shader_commands->add_command( + 254, + "col = vec4(1.0f, 0.0f, 0.0f, 1.0f);", + "Red tint command"); + this->shader_commands->add_command( + 252, + "col = vec4(0.0f, 1.0f, 0.0f, 1.0f);", + "Green tint command"); + this->shader_commands->add_command( + 250, + "col = vec4(0.0f, 0.0f, 1.0f, 1.0f);", + "Blue tint command"); + + // Additional commands can be added here +} + } // namespace openage::renderer::world diff --git a/libopenage/renderer/stages/world/render_stage.h b/libopenage/renderer/stages/world/render_stage.h index f1256f3b57..f048a1cbee 100644 --- a/libopenage/renderer/stages/world/render_stage.h +++ b/libopenage/renderer/stages/world/render_stage.h @@ -7,6 +7,7 @@ #include #include "util/path.h" +#include "world_shader_commands.h" namespace openage { @@ -111,6 +112,12 @@ class WorldRenderStage { */ void init_uniform_ids(); + /** + * Initialize the shader command system and register default commands. + * This must be called before initializing the shader program. + */ + void init_shader_commands(); + /** * Reference to the openage renderer. */ @@ -174,6 +181,12 @@ class WorldRenderStage { * Mutex for protecting threaded access. */ std::shared_mutex mutex; + + /** + * Shader command system for the world fragment shader. + * Manages custom rendering behaviors through alpha channel commands. + */ + std::unique_ptr shader_commands; }; } // namespace world } // namespace renderer From 72921d00481cceac079169bce1af67f14e48be4e Mon Sep 17 00:00:00 2001 From: Alex Zhuohao He Date: Sun, 29 Dec 2024 16:03:08 -0500 Subject: [PATCH 134/152] fix a cstring error --- libopenage/renderer/stages/world/world_shader_commands.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libopenage/renderer/stages/world/world_shader_commands.cpp b/libopenage/renderer/stages/world/world_shader_commands.cpp index e7b31f87c1..b734d8d86a 100644 --- a/libopenage/renderer/stages/world/world_shader_commands.cpp +++ b/libopenage/renderer/stages/world/world_shader_commands.cpp @@ -2,6 +2,8 @@ #include "world_shader_commands.h" +#include + #include "error/error.h" #include "log/log.h" @@ -44,7 +46,7 @@ std::string WorldShaderCommands::integrate_command(const std::string &base_shade } // Replace the insertion point with the generated command code - final_shader.replace(insert_point, strlen(COMMAND_MARKER), commands_code); + final_shader.replace(insert_point, std::strlen(COMMAND_MARKER), commands_code); return final_shader; } From 78e4a1fd99fd9b551dece6ad70c71d3b2a7dca93 Mon Sep 17 00:00:00 2001 From: Alex Zhuohao He Date: Sun, 19 Jan 2025 20:08:28 -0500 Subject: [PATCH 135/152] reset world2d shader and render_entity --- assets/shaders/world2d.frag.glsl | 12 ++++++-- .../renderer/stages/world/render_stage.cpp | 30 ++----------------- .../renderer/stages/world/render_stage.h | 15 +--------- 3 files changed, 14 insertions(+), 43 deletions(-) diff --git a/assets/shaders/world2d.frag.glsl b/assets/shaders/world2d.frag.glsl index f9de0354ba..143d3f0980 100644 --- a/assets/shaders/world2d.frag.glsl +++ b/assets/shaders/world2d.frag.glsl @@ -26,10 +26,18 @@ void main() { // do not save the ID return; - //@INSERT_COMMANDS@ + case 254: + col = vec4(1.0f, 0.0f, 0.0f, 1.0f); + break; + case 252: + col = vec4(0.0f, 1.0f, 0.0f, 1.0f); + break; + case 250: + col = vec4(0.0f, 0.0f, 1.0f, 1.0f); + break; default: col = tex_val; break; } id = u_id; -} +} \ No newline at end of file diff --git a/libopenage/renderer/stages/world/render_stage.cpp b/libopenage/renderer/stages/world/render_stage.cpp index 3962daa170..e5b0774788 100644 --- a/libopenage/renderer/stages/world/render_stage.cpp +++ b/libopenage/renderer/stages/world/render_stage.cpp @@ -127,18 +127,12 @@ void WorldRenderStage::initialize_render_pass(size_t width, vert_shader_file.read()); vert_shader_file.close(); - // Initialize shader command system before loading fragment shader - this->shader_commands = std::make_unique(); - this->init_shader_commands(); - auto frag_shader_file = (shaderdir / "world2d.frag.glsl").open(); - auto base_shader = frag_shader_file.read(); - frag_shader_file.close(); - auto frag_shader_src = renderer::resources::ShaderSource( resources::shader_lang_t::glsl, resources::shader_stage_t::fragment, - this->shader_commands->integrate_command(base_shader)); + frag_shader_file.read()); + frag_shader_file.close(); this->output_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::rgba8)); this->depth_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::depth24)); @@ -162,22 +156,4 @@ void WorldRenderStage::init_uniform_ids() { WorldObject::anchor_offset = this->display_shader->get_uniform_id("anchor_offset"); } -void WorldRenderStage::init_shader_commands() { - // Register default shader commands - this->shader_commands->add_command( - 254, - "col = vec4(1.0f, 0.0f, 0.0f, 1.0f);", - "Red tint command"); - this->shader_commands->add_command( - 252, - "col = vec4(0.0f, 1.0f, 0.0f, 1.0f);", - "Green tint command"); - this->shader_commands->add_command( - 250, - "col = vec4(0.0f, 0.0f, 1.0f, 1.0f);", - "Blue tint command"); - - // Additional commands can be added here -} - -} // namespace openage::renderer::world +} // namespace openage::renderer::world \ No newline at end of file diff --git a/libopenage/renderer/stages/world/render_stage.h b/libopenage/renderer/stages/world/render_stage.h index f048a1cbee..894ab52ff0 100644 --- a/libopenage/renderer/stages/world/render_stage.h +++ b/libopenage/renderer/stages/world/render_stage.h @@ -7,7 +7,6 @@ #include #include "util/path.h" -#include "world_shader_commands.h" namespace openage { @@ -112,12 +111,6 @@ class WorldRenderStage { */ void init_uniform_ids(); - /** - * Initialize the shader command system and register default commands. - * This must be called before initializing the shader program. - */ - void init_shader_commands(); - /** * Reference to the openage renderer. */ @@ -181,13 +174,7 @@ class WorldRenderStage { * Mutex for protecting threaded access. */ std::shared_mutex mutex; - - /** - * Shader command system for the world fragment shader. - * Manages custom rendering behaviors through alpha channel commands. - */ - std::unique_ptr shader_commands; }; } // namespace world } // namespace renderer -} // namespace openage +} // namespace openage \ No newline at end of file From 26c60ac7efeb847eb615f042c4c8ea9d8ffde482 Mon Sep 17 00:00:00 2001 From: Alex Zhuohao He Date: Thu, 23 Jan 2025 17:08:08 -0500 Subject: [PATCH 136/152] implement more controllable shader command template --- .../renderer/stages/world/render_stage.cpp | 60 +++++- .../renderer/stages/world/render_stage.h | 39 +++- .../stages/world/world_shader_commands.cpp | 173 +++++++++++++----- .../stages/world/world_shader_commands.h | 102 ++++------- 4 files changed, 269 insertions(+), 105 deletions(-) diff --git a/libopenage/renderer/stages/world/render_stage.cpp b/libopenage/renderer/stages/world/render_stage.cpp index e5b0774788..cb2c863870 100644 --- a/libopenage/renderer/stages/world/render_stage.cpp +++ b/libopenage/renderer/stages/world/render_stage.cpp @@ -1,4 +1,4 @@ -// Copyright 2022-2024 the openage authors. See copying.md for legal info. +// Copyright 2022-2025 the openage authors. See copying.md for legal info. #include "render_stage.h" @@ -12,6 +12,7 @@ #include "renderer/resources/texture_info.h" #include "renderer/shader_program.h" #include "renderer/stages/world/object.h" +#include "renderer/stages/world/world_shader_commands.h" #include "renderer/texture.h" #include "renderer/window.h" #include "time/clock.h" @@ -46,6 +47,30 @@ WorldRenderStage::WorldRenderStage(const std::shared_ptr &window, log::log(INFO << "Created render stage 'World'"); } +WorldRenderStage::WorldRenderStage(const std::shared_ptr &window, + const std::shared_ptr &renderer, + const std::shared_ptr &camera, + const util::Path &shaderdir, + const util::Path &configdir, + const std::shared_ptr &asset_manager, + const std::shared_ptr clock) : + renderer{renderer}, + camera{camera}, + asset_manager{asset_manager}, + render_objects{}, + clock{clock}, + default_geometry{this->renderer->add_mesh_geometry(WorldObject::get_mesh())} { + auto size = window->get_size(); + this->initialize_render_pass_with_shader_commands(size[0], size[1], shaderdir, configdir); + this->init_uniform_ids(); + + window->add_resize_callback([this](size_t width, size_t height, double /*scale*/) { + this->resize(width, height); + }); + + log::log(INFO << "Created render stage 'World' with shader command"); +} + std::shared_ptr WorldRenderStage::get_render_pass() { return this->render_pass; } @@ -156,4 +181,37 @@ void WorldRenderStage::init_uniform_ids() { WorldObject::anchor_offset = this->display_shader->get_uniform_id("anchor_offset"); } +void WorldRenderStage::initialize_render_pass_with_shader_commands(size_t width, size_t height, const util::Path &shaderdir, const util::Path &config_path) { + auto vert_shader_file = (shaderdir / "demo_7_world.vert.glsl").open(); + auto vert_shader_src = renderer::resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::vertex, + vert_shader_file.read()); + vert_shader_file.close(); + + auto frag_shader_file = (shaderdir / "demo_7_world.frag.glsl").open(); + log::log(INFO << "Loading shader commands config from: " << (shaderdir / "demo_7_display.frag.glsl")); + this->shader_template = std::make_shared(frag_shader_file.read()); + if (not this->shader_template->load_commands(config_path / "world_commands.config")) { + log::log(ERR << "Failed to load shader commands configuration for world stage"); + return; + } + + auto frag_shader_src = renderer::resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::fragment, + this->shader_template->generate_source()); + frag_shader_file.close(); + + this->output_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::rgba8)); + this->depth_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::depth24)); + this->id_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::r32ui)); + + this->display_shader = this->renderer->add_shader({vert_shader_src, frag_shader_src}); + this->display_shader->bind_uniform_buffer("camera", this->camera->get_uniform_buffer()); + + auto fbo = this->renderer->create_texture_target({this->output_texture, this->depth_texture, this->id_texture}); + this->render_pass = this->renderer->add_render_pass({}, fbo); +} + } // namespace openage::renderer::world \ No newline at end of file diff --git a/libopenage/renderer/stages/world/render_stage.h b/libopenage/renderer/stages/world/render_stage.h index 894ab52ff0..2c751be50d 100644 --- a/libopenage/renderer/stages/world/render_stage.h +++ b/libopenage/renderer/stages/world/render_stage.h @@ -1,4 +1,4 @@ -// Copyright 2022-2024 the openage authors. See copying.md for legal info. +// Copyright 2022-2025 the openage authors. See copying.md for legal info. #pragma once @@ -33,6 +33,7 @@ class AssetManager; namespace world { class RenderEntity; class WorldObject; +class ShaderCommandTemplate; /** * Renderer for drawing and displaying entities in the game world (units, buildings, etc.) @@ -60,6 +61,26 @@ class WorldRenderStage { const util::Path &shaderdir, const std::shared_ptr &asset_manager, const std::shared_ptr clock); + + /** + * Create a new render stage for the game world with shader command. + * + * @param window openage window targeted for rendering. + * @param renderer openage low-level renderer. + * @param camera Camera used for the rendered scene. + * @param shaderdir Directory containing the shader source files. + * @param configdir Directory containing the config for shader command. + * @param asset_manager Asset manager for loading resources. + * @param clock Simulation clock for timing animations. + */ + WorldRenderStage(const std::shared_ptr &window, + const std::shared_ptr &renderer, + const std::shared_ptr &camera, + const util::Path &shaderdir, + const util::Path &configdir, + const std::shared_ptr &asset_manager, + const std::shared_ptr clock); + ~WorldRenderStage() = default; /** @@ -111,6 +132,17 @@ class WorldRenderStage { */ void init_uniform_ids(); + /** + * Initialize render pass with shader commands. + * This is an alternative to initialize_render_pass() that uses configurable shader commands. + * + * @param width Width of the FBO. + * @param height Height of the FBO. + * @param shaderdir Directory containing shader files. + * @param configdir Directory containing configuration file. + */ + void initialize_render_pass_with_shader_commands(size_t width, size_t height, const util::Path &shaderdir, const util::Path &config_path); + /** * Reference to the openage renderer. */ @@ -131,6 +163,11 @@ class WorldRenderStage { */ std::shared_ptr render_pass; + /** + * Template for the world shader program. + */ + std::shared_ptr shader_template; + /** * Render entities requested by the game world. */ diff --git a/libopenage/renderer/stages/world/world_shader_commands.cpp b/libopenage/renderer/stages/world/world_shader_commands.cpp index b734d8d86a..9cc10f5f87 100644 --- a/libopenage/renderer/stages/world/world_shader_commands.cpp +++ b/libopenage/renderer/stages/world/world_shader_commands.cpp @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #include "world_shader_commands.h" @@ -9,67 +9,158 @@ namespace openage::renderer::world { -bool WorldShaderCommands::add_command(uint8_t alpha, const std::string &code, const std::string &description) { - if (!validate_alpha(alpha)) { - log::log(ERR << "Invalid alpha value: " << int(alpha)); +ShaderCommandTemplate::ShaderCommandTemplate(const std::string &template_code) : + template_code{template_code} {} + +bool ShaderCommandTemplate::load_commands(const util::Path &config_path) { + try { + log::log(INFO << "Loading shader commands config from: " << config_path); + auto config_file = config_path.open(); + std::string line; + std::stringstream ss(config_file.read()); + + ShaderCommandConfig current_command; + // if true, we are reading the code block for the current command. + bool reading_code = false; + std::string code_block; + + while (std::getline(ss, line)) { + if (!line.empty() && line.back() == '\r') { + line.pop_back(); + } + // Trim whitespace from line + line = trim(line); + log::log(INFO << "Parsing line: " << line); + + // Skip empty lines and comments + if (line.empty() || line[0] == '#') { + continue; + } + + if (reading_code) { + if (line == "}") { + reading_code = false; + current_command.code = code_block; + + // Generate and add snippet + std::string snippet = generate_snippet(current_command); + add_snippet(current_command.placeholder_id, snippet); + commands.push_back(current_command); + + // Reset for next command + code_block.clear(); + } + else { + code_block += line + "\n"; + } + continue; + } + + if (line == "[COMMAND]") { + current_command = ShaderCommandConfig{}; + continue; + } + + // Parse key-value pairs + size_t pos = line.find('='); + if (pos != std::string::npos) { + std::string key = trim(line.substr(0, pos)); + std::string value = trim(line.substr(pos + 1)); + + if (key == "placeholder") { + current_command.placeholder_id = value; + } + else if (key == "alpha") { + uint8_t alpha = static_cast(std::stoi(value)); + if (alpha % 2 == 0 && alpha >= 0 && alpha <= 254) { + current_command.alpha = alpha; + } + else { + log::log(ERR << "Invalid alpha value for command: " << alpha); + return false; + } + } + else if (key == "description") { + current_command.description = value; + } + else if (key == "code") { + if (value == "{") { + reading_code = true; + code_block.clear(); + } + } + } + } + + return true; + } + catch (const std::exception &e) { + log::log(ERR << "Failed to load shader commands: " << e.what()); return false; } - if (!validate_code(code)) { - log::log(ERR << "Invalid command code"); +} + +bool ShaderCommandTemplate::add_snippet(const std::string &placeholder_id, const std::string &snippet) { + if (snippet.empty()) { + log::log(ERR << "Empty snippet for placeholder: " << placeholder_id); return false; } - commands_map[alpha] = {alpha, code, description}; - return true; -} + if (placeholder_id.empty()) { + log::log(ERR << "Empty placeholder ID for snippet"); + return false; + } -bool WorldShaderCommands::remove_command(uint8_t alpha) { - if (!validate_alpha(alpha)) { + // Check if the placeholder exists in the template + std::string placeholder = "//@" + placeholder_id + "@"; + if (template_code.find(placeholder) == std::string::npos) { + log::log(ERR << "Placeholder not found in template: " << placeholder_id); return false; } - commands_map.erase(alpha); + + // Store the snippet + snippets[placeholder_id].push_back(snippet); return true; } -bool WorldShaderCommands::has_command(uint8_t alpha) const { - return commands_map.contains(alpha); +std::string ShaderCommandTemplate::generate_snippet(const ShaderCommandConfig &command) { + return "case " + std::to_string(command.alpha) + ":\n" + + "\t\t// " + command.description + "\n" + + "\t\t" + command.code + "\t\tbreak;\n"; } -std::string WorldShaderCommands::integrate_command(const std::string &base_shader) { - std::string final_shader = base_shader; - std::string commands_code = generate_command_code(); +std::string ShaderCommandTemplate::generate_source() const { + std::string result = template_code; - // Find the insertion point - size_t insert_point = final_shader.find(COMMAND_MARKER); - if (insert_point == std::string::npos) { - throw Error(MSG(err) << "Failed to find command insertion point in shader."); - } - - // Replace the insertion point with the generated command code - final_shader.replace(insert_point, std::strlen(COMMAND_MARKER), commands_code); + // Process each placeholder + for (const auto &[placeholder_id, snippet_list] : snippets) { + std::string combined_snippets; - return final_shader; -} + // Combine all snippets for this placeholder + for (const auto &snippet : snippet_list) { + combined_snippets += snippet; + } -std::string WorldShaderCommands::generate_command_code() const { - std::string result = ""; + // Find and replace the placeholder + std::string placeholder = "//@" + placeholder_id + "@"; + size_t pos = result.find(placeholder); + if (pos == std::string::npos) { + throw Error(MSG(err) << "Placeholder disappeared from template: " << placeholder_id); + } - for (const auto &[alpha, command] : commands_map) { - result += " case " + std::to_string(alpha) + ":\n"; - result += " // " + command.description + "\n"; - result += " " + command.code + "\n"; - result += " break;\n\n"; + // Replace placeholder with combined snippets + result.replace(pos, placeholder.length(), combined_snippets); } return result; } -bool WorldShaderCommands::validate_alpha(uint8_t alpha) const { - return alpha % 2 == 0 && alpha >= 0 && alpha <= 254; -} - -bool WorldShaderCommands::validate_code(const std::string &code) const { - return !code.empty(); +std::string ShaderCommandTemplate::trim(const std::string &str) const { + size_t first = str.find_first_not_of(" \t"); + if (first == std::string::npos) { + return ""; + } + size_t last = str.find_last_not_of(" \t"); + return str.substr(first, (last - first + 1)); } - } // namespace openage::renderer::world diff --git a/libopenage/renderer/stages/world/world_shader_commands.h b/libopenage/renderer/stages/world/world_shader_commands.h index d025735db2..9f6a158a25 100644 --- a/libopenage/renderer/stages/world/world_shader_commands.h +++ b/libopenage/renderer/stages/world/world_shader_commands.h @@ -1,11 +1,14 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #pragma once #include #include +#include #include +#include "util/path.h" + namespace openage { namespace renderer { namespace world { @@ -15,94 +18,69 @@ namespace world { * Commands are identified by their alpha values and contain GLSL code snippets * that define custom rendering behavior. */ -struct ShaderCommand { - // Command identifier ((must be even, range 0-254)) +struct ShaderCommandConfig { + /// ID of the placeholder where this snippet should be inserted + std::string placeholder_id; + /// Command identifier ((must be even, range 0-254)) uint8_t alpha; - // GLSL code snippet that defines the command's behavior + /// GLSL code snippet that defines the command's behavior std::string code; - // Documentation (optional) + /// Documentation (optional) std::string description; }; /** - * Manages shader commands for the world fragment shader. - * Provides functionality to add, remove, and integrate commands into the base shader. - * Commands are inserted at a predefined marker in the shader code. + * Manages shader templates and their code snippets. + * Allows loading configurable shader commands and generating + * complete shader source code. */ -class WorldShaderCommands { +class ShaderCommandTemplate { public: - // Marker in shader code where commands will be inserted - static constexpr const char *COMMAND_MARKER = "//@INSERT_COMMANDS@"; - /** - * Add a new shader command. - * - * @param alpha Command identifier (must be even, range 0-254) - * @param code GLSL code snippet defining the command's behavior - * @param description Human-readable description of the command's purpose + * Create a shader template from source code of shader. * - * @return true if command was added successfully, false if validation failed + * @param template_code Source code containing placeholders. */ - bool add_command(uint8_t alpha, const std::string &code, const std::string &description = ""); + explicit ShaderCommandTemplate(const std::string &template_code); /** - * Remove a command. + * Load commands from a configuration file. * - * @param alpha Command identifier (even values 0-254) + * @param config_path Path to the command configuration file. + * @return true if commands were loaded successfully. */ - bool remove_command(uint8_t alpha); + bool load_commands(const util::Path &config_path); /** - * Check if a command is registered. + * Add a single code snippet to the template. * - * @param alpha Command identifier to check - * - * @return true if command is registered + * @param placeholder_id Where to insert the snippet. + * @param snippet Code to insert. + * @return true if snippet was added successfully. */ - bool has_command(uint8_t alpha) const; + bool add_snippet(const std::string &placeholder_id, const std::string &snippet); /** - * Integrate registered commands into the base shader code. - * - * @param base_shader Original shader code containing the command marker - * - * @return Complete shader code with commands integrated at the marker position + * Generate final shader source code with all snippets inserted. * - * @throws Error if command marker is not found in the base shader + * @return Complete shader code. + * @throws Error if any required placeholders are missing snippets. */ - std::string integrate_command(const std::string &base_shader); + std::string generate_source() const; private: - /** - * Generate GLSL code for all registered commands. - * - * @return String containing case statements for each command - */ - std::string generate_command_code() const; + // Generate a single code snippet for a command. + std::string generate_snippet(const ShaderCommandConfig &command); + // Helper function to trim whitespace from a string + std::string trim(const std::string &str) const; - /** - * Validate a command identifier. - * - * @param alpha Command identifier to validate - * - * @return true if alpha is even and within valid range (0-254) - */ - bool validate_alpha(uint8_t alpha) const; - - /** - * Validate command GLSL code. - * - * @param code GLSL code snippet to validate - * - * @return true if code is not empty (additional validation could be added) - */ - - bool validate_code(const std::string &code) const; - - // Map of command identifiers to their respective commands - std::map commands_map; + // Original template code with placeholders + std::string template_code; + // Mapping of placeholder IDs to their code snippets + std::map> snippets; + // Loaded command configurations + std::vector commands; }; - } // namespace world } // namespace renderer } // namespace openage From 474a713d22bca29ae158225e849de983acd1140a Mon Sep 17 00:00:00 2001 From: Alex Zhuohao He Date: Thu, 23 Jan 2025 17:08:55 -0500 Subject: [PATCH 137/152] add demo7 to test shader command --- assets/test/shaders/demo_7_world.frag.glsl | 34 +++++++ assets/test/shaders/demo_7_world.vert.glsl | 101 ++++++++++++++++++++ assets/test/shaders/world_commands.config | 23 +++++ libopenage/renderer/demo/CMakeLists.txt | 1 + libopenage/renderer/demo/demo_7.cpp | 106 +++++++++++++++++++++ libopenage/renderer/demo/demo_7.h | 22 +++++ libopenage/renderer/demo/tests.cpp | 7 +- 7 files changed, 293 insertions(+), 1 deletion(-) create mode 100644 assets/test/shaders/demo_7_world.frag.glsl create mode 100644 assets/test/shaders/demo_7_world.vert.glsl create mode 100644 assets/test/shaders/world_commands.config create mode 100644 libopenage/renderer/demo/demo_7.cpp create mode 100644 libopenage/renderer/demo/demo_7.h diff --git a/assets/test/shaders/demo_7_world.frag.glsl b/assets/test/shaders/demo_7_world.frag.glsl new file mode 100644 index 0000000000..d6ffd22029 --- /dev/null +++ b/assets/test/shaders/demo_7_world.frag.glsl @@ -0,0 +1,34 @@ +#version 330 + +in vec2 vert_uv; + +layout(location = 0) out vec4 col; +layout(location = 1) out uint id; + +uniform sampler2D tex; +uniform uint u_id; + +// position (top left corner) and size: (x, y, width, height) +uniform vec4 tile_params; + +vec2 uv = vec2( + vert_uv.x * tile_params.z + tile_params.x, + vert_uv.y *tile_params.w + tile_params.y); + +void main() { + vec4 tex_val = texture(tex, uv); + int alpha = int(round(tex_val.a * 255)); + switch (alpha) { + case 0: + col = tex_val; + discard; + + // do not save the ID + return; + //@COMMAND_SWITCH@ + default: + col = tex_val; + break; + } + id = u_id; +} \ No newline at end of file diff --git a/assets/test/shaders/demo_7_world.vert.glsl b/assets/test/shaders/demo_7_world.vert.glsl new file mode 100644 index 0000000000..7987d40ec3 --- /dev/null +++ b/assets/test/shaders/demo_7_world.vert.glsl @@ -0,0 +1,101 @@ +#version 330 + +layout(location=0) in vec2 v_position; +layout(location=1) in vec2 uv; + +out vec2 vert_uv; + +// camera parameters for transforming the object position +// and scaling the subtex to the correct size +layout (std140) uniform camera { + // view matrix (world to view space) + mat4 view; + // projection matrix (view to clip space) + mat4 proj; + // inverse zoom factor (1.0 / zoom) + // high zoom = upscale subtex + // low zoom = downscale subtex + float inv_zoom; + // inverse viewport size (1.0 / viewport size) + vec2 inv_viewport_size; +}; + +// can be used to move the object position in world space _before_ +// it's transformed to clip space +// this is usually unnecessary because we want to draw the +// subtex where the object is, so this can be set to the identity matrix +uniform mat4 model; + +// position of the object in world space +uniform vec3 obj_world_position; + +// flip the subtexture horizontally/vertically +uniform bool flip_x; +uniform bool flip_y; + +// parameters for scaling and moving the subtex +// to the correct position in clip space + +// animation scalefactor +// scales the vertex positions so that they +// match the subtex dimensions +// +// high animation scale = downscale subtex +// low animation scale = upscale subtex +uniform float scale; + +// size of the subtex (in pixels) +uniform vec2 subtex_size; + +// offset of the subtex anchor point +// from the subtex center (in pixels) +// used to move the subtex so that the anchor point +// is at the object position +uniform vec2 anchor_offset; + +void main() { + // translate the position of the object from world space to clip space + // this is the position where we want to draw the subtex in 2D + vec4 obj_clip_pos = proj * view * model * vec4(obj_world_position, 1.0); + + // subtex has to be scaled to account for the zoom factor + // and the animation scale factor. essentially this is (animation scale / zoom). + float zoom_scale = scale * inv_zoom; + + // Scale the subtex vertices + // we have to account for the viewport size to get the correct dimensions + // and then scale the subtex to the zoom factor to get the correct size + vec2 vert_scale = zoom_scale * subtex_size * inv_viewport_size; + + // Scale the anchor offset with the same method as above + // to get the correct anchor position in the viewport + vec2 anchor_scale = zoom_scale * anchor_offset * inv_viewport_size; + + // if the subtex is flipped, we also need to flip the anchor offset + // essentially, we invert the coordinates for the flipped axis + float anchor_x = float(flip_x) * -1.0 * anchor_scale.x + float(!flip_x) * anchor_scale.x; + float anchor_y = float(flip_y) * -1.0 * anchor_scale.y + float(!flip_y) * anchor_scale.y; + + // offset the clip position by the offset of the subtex anchor + // imagine this as pinning the subtex to the object position at the subtex anchor point + obj_clip_pos += vec4(anchor_x, anchor_y, 0.0, 0.0); + + // create a move matrix for positioning the vertices + // uses the vert scale and the transformed object position in clip space + mat4 move = mat4(vert_scale.x, 0.0, 0.0, 0.0, + 0.0, vert_scale.y, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + obj_clip_pos.x, obj_clip_pos.y, obj_clip_pos.z, 1.0); + + // calculate the final vertex position + gl_Position = move * vec4(v_position, 0.0, 1.0); + + // if the subtex is flipped, we also need to flip the uv tex coordinates + // essentially, we invert the coordinates for the flipped axis + + // !flip_x is default because OpenGL uses bottom-left as its origin + float uv_x = float(!flip_x) * uv.x + float(flip_x) * (1.0 - uv.x); + float uv_y = float(flip_y) * uv.y + float(!flip_y) * (1.0 - uv.y); + + vert_uv = vec2(uv_x, uv_y); +} diff --git a/assets/test/shaders/world_commands.config b/assets/test/shaders/world_commands.config new file mode 100644 index 0000000000..16b6a9d399 --- /dev/null +++ b/assets/test/shaders/world_commands.config @@ -0,0 +1,23 @@ +[COMMAND] +placeholder=COMMAND_SWITCH +alpha=254 +description=Red tint +code={ +col = vec4(1.0, 0.0, 0.0, 1.0) * tex_val; +} + +[COMMAND] +placeholder=COMMAND_SWITCH +alpha=252 +description=Green tint +code={ +col = vec4(0.0, 1.0, 0.0, 1.0) * tex_val; +} + +[COMMAND] +placeholder=COMMAND_SWITCH +alpha=250 +description=Blue tint +code={ +col = vec4(0.0, 0.0, 1.0, 1.0) * tex_val; +} \ No newline at end of file diff --git a/libopenage/renderer/demo/CMakeLists.txt b/libopenage/renderer/demo/CMakeLists.txt index fb93e5279b..7567731afd 100644 --- a/libopenage/renderer/demo/CMakeLists.txt +++ b/libopenage/renderer/demo/CMakeLists.txt @@ -6,6 +6,7 @@ add_sources(libopenage demo_4.cpp demo_5.cpp demo_6.cpp + demo_7.cpp stresstest_0.cpp stresstest_1.cpp tests.cpp diff --git a/libopenage/renderer/demo/demo_7.cpp b/libopenage/renderer/demo/demo_7.cpp new file mode 100644 index 0000000000..a3a21bf94d --- /dev/null +++ b/libopenage/renderer/demo/demo_7.cpp @@ -0,0 +1,106 @@ +// demo_shader_commands.h +#pragma once + +#include "util/path.h" + +#include +#include + +#include "coord/tile.h" +#include "renderer/camera/camera.h" +#include "renderer/gui/integration/public/gui_application_with_logger.h" +#include "renderer/opengl/window.h" +#include "renderer/render_factory.h" +#include "renderer/render_pass.h" +#include "renderer/render_target.h" +#include "renderer/resources/assets/asset_manager.h" +#include "renderer/resources/shader_source.h" +#include "renderer/stages/camera/manager.h" +#include "renderer/stages/screen/render_stage.h" +#include "renderer/stages/skybox/render_stage.h" +#include "renderer/stages/terrain/render_entity.h" +#include "renderer/stages/terrain/render_stage.h" +#include "renderer/stages/world/render_entity.h" +#include "renderer/stages/world/render_stage.h" +#include "renderer/uniform_buffer.h" +#include "time/clock.h" + +namespace openage::renderer::tests { + +void renderer_demo_7(const util::Path &path) { + // Basic setup + auto qtapp = std::make_shared(); + window_settings settings; + settings.width = 800; + settings.height = 600; + settings.debug = true; + + auto window = std::make_shared("Shader Commands Demo", settings); + auto renderer = window->make_renderer(); + auto camera = std::make_shared(renderer, window->get_size()); + auto clock = std::make_shared(); + auto asset_manager = std::make_shared( + renderer, + path["assets"]["test"]); + auto cam_manager = std::make_shared(camera); + + auto shaderdir = path / "assets" / "test" / "shaders"; + + std::vector> + render_passes{}; + + // Initialize world renderer with shader commands + auto world_renderer = std::make_shared( + window, + renderer, + camera, + shaderdir, + shaderdir, // Temporarily, Shader commands config has the same path with shaders for this demo + asset_manager, + clock); + + render_passes.push_back(world_renderer->get_render_pass()); + + auto screen_renderer = std::make_shared( + window, + renderer, + path["assets"]["shaders"]); + std::vector> targets{}; + for (auto &pass : render_passes) { + targets.push_back(pass->get_target()); + } + screen_renderer->set_render_targets(targets); + + render_passes.push_back(screen_renderer->get_render_pass()); + + auto render_factory = std::make_shared(nullptr, world_renderer); + + auto entity1 = render_factory->add_world_render_entity(); + entity1->update(0, coord::phys3(0.0f, 0.0f, 0.0f), "./textures/test_gaben.sprite"); + + auto entity2 = render_factory->add_world_render_entity(); + entity2->update(1, coord::phys3(3.0f, 0.0f, 0.0f), "./textures/test_gaben.sprite"); + + auto entity3 = render_factory->add_world_render_entity(); + entity3->update(2, coord::phys3(-3.0f, 0.0f, 0.0f), "./textures/test_gaben.sprite"); + + // Main loop + while (not window->should_close()) { + qtapp->process_events(); + + // Update camera matrices + cam_manager->update(); + + world_renderer->update(); + + for (auto &pass : render_passes) { + renderer->render(pass); + } + + renderer->check_error(); + + window->update(); + } +} + +} // namespace openage::renderer::tests \ No newline at end of file diff --git a/libopenage/renderer/demo/demo_7.h b/libopenage/renderer/demo/demo_7.h new file mode 100644 index 0000000000..45c192523d --- /dev/null +++ b/libopenage/renderer/demo/demo_7.h @@ -0,0 +1,22 @@ +// Copyright 2025-2025 the openage authors. See copying.md for legal info. + +#pragma once + +#include "util/path.h" + +namespace openage::renderer::tests { + +/** + * Show off the render stages in the level 2 renderer and the camera + * system. + * - Window creation + * - Creating a camera + * - Initializing the level 2 render stages: skybox, terrain, world, screen + * - Adding renderables to the render stages via the render factory + * - Moving camera with mouse/keyboard callbacks + * + * @param path Path to the project rootdir. + */ +void renderer_demo_7(const util::Path &path); + +} // namespace openage::renderer::tests diff --git a/libopenage/renderer/demo/tests.cpp b/libopenage/renderer/demo/tests.cpp index d3fb0e3c21..bab82af2e3 100644 --- a/libopenage/renderer/demo/tests.cpp +++ b/libopenage/renderer/demo/tests.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2024 the openage authors. See copying.md for legal info. +// Copyright 2015-2025 the openage authors. See copying.md for legal info. #include "tests.h" @@ -12,6 +12,7 @@ #include "renderer/demo/demo_4.h" #include "renderer/demo/demo_5.h" #include "renderer/demo/demo_6.h" +#include "renderer/demo/demo_7.h" #include "renderer/demo/stresstest_0.h" #include "renderer/demo/stresstest_1.h" @@ -47,6 +48,10 @@ void renderer_demo(int demo_id, const util::Path &path) { renderer_demo_6(path); break; + case 7: + renderer_demo_7(path); + break; + default: log::log(MSG(err) << "Unknown renderer demo requested: " << demo_id << "."); break; From 720f07541459016dad8f6cc0d7893655e991c1e0 Mon Sep 17 00:00:00 2001 From: Alex Zhuohao He Date: Thu, 23 Jan 2025 17:20:42 -0500 Subject: [PATCH 138/152] update to pass sanity_check --- libopenage/renderer/demo/demo_7.cpp | 7 ++++--- libopenage/renderer/stages/world/render_stage.cpp | 2 +- libopenage/renderer/stages/world/render_stage.h | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/libopenage/renderer/demo/demo_7.cpp b/libopenage/renderer/demo/demo_7.cpp index a3a21bf94d..470ded48f1 100644 --- a/libopenage/renderer/demo/demo_7.cpp +++ b/libopenage/renderer/demo/demo_7.cpp @@ -1,5 +1,6 @@ -// demo_shader_commands.h -#pragma once +// Copyright 2025-2025 the openage authors. See copying.md for legal info. + +#include "demo_7.h" #include "util/path.h" @@ -103,4 +104,4 @@ void renderer_demo_7(const util::Path &path) { } } -} // namespace openage::renderer::tests \ No newline at end of file +} // namespace openage::renderer::tests diff --git a/libopenage/renderer/stages/world/render_stage.cpp b/libopenage/renderer/stages/world/render_stage.cpp index cb2c863870..7514f2ca7e 100644 --- a/libopenage/renderer/stages/world/render_stage.cpp +++ b/libopenage/renderer/stages/world/render_stage.cpp @@ -214,4 +214,4 @@ void WorldRenderStage::initialize_render_pass_with_shader_commands(size_t width, this->render_pass = this->renderer->add_render_pass({}, fbo); } -} // namespace openage::renderer::world \ No newline at end of file +} // namespace openage::renderer::world diff --git a/libopenage/renderer/stages/world/render_stage.h b/libopenage/renderer/stages/world/render_stage.h index 2c751be50d..9fc4cdbf06 100644 --- a/libopenage/renderer/stages/world/render_stage.h +++ b/libopenage/renderer/stages/world/render_stage.h @@ -214,4 +214,4 @@ class WorldRenderStage { }; } // namespace world } // namespace renderer -} // namespace openage \ No newline at end of file +} // namespace openage From 1f507077a6dfeb9b26b95edcb87faf0098f11e83 Mon Sep 17 00:00:00 2001 From: Alex Zhuohao He Date: Mon, 27 Jan 2025 20:03:29 -0500 Subject: [PATCH 139/152] Revert file(s) to pre-PR state --- assets/shaders/world2d.frag.glsl | 2 +- assets/test/shaders/world_commands.config | 23 ------- .../renderer/stages/world/render_stage.cpp | 60 +------------------ .../renderer/stages/world/render_stage.h | 39 +----------- 4 files changed, 3 insertions(+), 121 deletions(-) delete mode 100644 assets/test/shaders/world_commands.config diff --git a/assets/shaders/world2d.frag.glsl b/assets/shaders/world2d.frag.glsl index 143d3f0980..98d323e0f6 100644 --- a/assets/shaders/world2d.frag.glsl +++ b/assets/shaders/world2d.frag.glsl @@ -40,4 +40,4 @@ void main() { break; } id = u_id; -} \ No newline at end of file +} diff --git a/assets/test/shaders/world_commands.config b/assets/test/shaders/world_commands.config deleted file mode 100644 index 16b6a9d399..0000000000 --- a/assets/test/shaders/world_commands.config +++ /dev/null @@ -1,23 +0,0 @@ -[COMMAND] -placeholder=COMMAND_SWITCH -alpha=254 -description=Red tint -code={ -col = vec4(1.0, 0.0, 0.0, 1.0) * tex_val; -} - -[COMMAND] -placeholder=COMMAND_SWITCH -alpha=252 -description=Green tint -code={ -col = vec4(0.0, 1.0, 0.0, 1.0) * tex_val; -} - -[COMMAND] -placeholder=COMMAND_SWITCH -alpha=250 -description=Blue tint -code={ -col = vec4(0.0, 0.0, 1.0, 1.0) * tex_val; -} \ No newline at end of file diff --git a/libopenage/renderer/stages/world/render_stage.cpp b/libopenage/renderer/stages/world/render_stage.cpp index 7514f2ca7e..d8d93279af 100644 --- a/libopenage/renderer/stages/world/render_stage.cpp +++ b/libopenage/renderer/stages/world/render_stage.cpp @@ -1,4 +1,4 @@ -// Copyright 2022-2025 the openage authors. See copying.md for legal info. +// Copyright 2022-2024 the openage authors. See copying.md for legal info. #include "render_stage.h" @@ -12,7 +12,6 @@ #include "renderer/resources/texture_info.h" #include "renderer/shader_program.h" #include "renderer/stages/world/object.h" -#include "renderer/stages/world/world_shader_commands.h" #include "renderer/texture.h" #include "renderer/window.h" #include "time/clock.h" @@ -47,30 +46,6 @@ WorldRenderStage::WorldRenderStage(const std::shared_ptr &window, log::log(INFO << "Created render stage 'World'"); } -WorldRenderStage::WorldRenderStage(const std::shared_ptr &window, - const std::shared_ptr &renderer, - const std::shared_ptr &camera, - const util::Path &shaderdir, - const util::Path &configdir, - const std::shared_ptr &asset_manager, - const std::shared_ptr clock) : - renderer{renderer}, - camera{camera}, - asset_manager{asset_manager}, - render_objects{}, - clock{clock}, - default_geometry{this->renderer->add_mesh_geometry(WorldObject::get_mesh())} { - auto size = window->get_size(); - this->initialize_render_pass_with_shader_commands(size[0], size[1], shaderdir, configdir); - this->init_uniform_ids(); - - window->add_resize_callback([this](size_t width, size_t height, double /*scale*/) { - this->resize(width, height); - }); - - log::log(INFO << "Created render stage 'World' with shader command"); -} - std::shared_ptr WorldRenderStage::get_render_pass() { return this->render_pass; } @@ -181,37 +156,4 @@ void WorldRenderStage::init_uniform_ids() { WorldObject::anchor_offset = this->display_shader->get_uniform_id("anchor_offset"); } -void WorldRenderStage::initialize_render_pass_with_shader_commands(size_t width, size_t height, const util::Path &shaderdir, const util::Path &config_path) { - auto vert_shader_file = (shaderdir / "demo_7_world.vert.glsl").open(); - auto vert_shader_src = renderer::resources::ShaderSource( - resources::shader_lang_t::glsl, - resources::shader_stage_t::vertex, - vert_shader_file.read()); - vert_shader_file.close(); - - auto frag_shader_file = (shaderdir / "demo_7_world.frag.glsl").open(); - log::log(INFO << "Loading shader commands config from: " << (shaderdir / "demo_7_display.frag.glsl")); - this->shader_template = std::make_shared(frag_shader_file.read()); - if (not this->shader_template->load_commands(config_path / "world_commands.config")) { - log::log(ERR << "Failed to load shader commands configuration for world stage"); - return; - } - - auto frag_shader_src = renderer::resources::ShaderSource( - resources::shader_lang_t::glsl, - resources::shader_stage_t::fragment, - this->shader_template->generate_source()); - frag_shader_file.close(); - - this->output_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::rgba8)); - this->depth_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::depth24)); - this->id_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::r32ui)); - - this->display_shader = this->renderer->add_shader({vert_shader_src, frag_shader_src}); - this->display_shader->bind_uniform_buffer("camera", this->camera->get_uniform_buffer()); - - auto fbo = this->renderer->create_texture_target({this->output_texture, this->depth_texture, this->id_texture}); - this->render_pass = this->renderer->add_render_pass({}, fbo); -} - } // namespace openage::renderer::world diff --git a/libopenage/renderer/stages/world/render_stage.h b/libopenage/renderer/stages/world/render_stage.h index 9fc4cdbf06..f1256f3b57 100644 --- a/libopenage/renderer/stages/world/render_stage.h +++ b/libopenage/renderer/stages/world/render_stage.h @@ -1,4 +1,4 @@ -// Copyright 2022-2025 the openage authors. See copying.md for legal info. +// Copyright 2022-2024 the openage authors. See copying.md for legal info. #pragma once @@ -33,7 +33,6 @@ class AssetManager; namespace world { class RenderEntity; class WorldObject; -class ShaderCommandTemplate; /** * Renderer for drawing and displaying entities in the game world (units, buildings, etc.) @@ -61,26 +60,6 @@ class WorldRenderStage { const util::Path &shaderdir, const std::shared_ptr &asset_manager, const std::shared_ptr clock); - - /** - * Create a new render stage for the game world with shader command. - * - * @param window openage window targeted for rendering. - * @param renderer openage low-level renderer. - * @param camera Camera used for the rendered scene. - * @param shaderdir Directory containing the shader source files. - * @param configdir Directory containing the config for shader command. - * @param asset_manager Asset manager for loading resources. - * @param clock Simulation clock for timing animations. - */ - WorldRenderStage(const std::shared_ptr &window, - const std::shared_ptr &renderer, - const std::shared_ptr &camera, - const util::Path &shaderdir, - const util::Path &configdir, - const std::shared_ptr &asset_manager, - const std::shared_ptr clock); - ~WorldRenderStage() = default; /** @@ -132,17 +111,6 @@ class WorldRenderStage { */ void init_uniform_ids(); - /** - * Initialize render pass with shader commands. - * This is an alternative to initialize_render_pass() that uses configurable shader commands. - * - * @param width Width of the FBO. - * @param height Height of the FBO. - * @param shaderdir Directory containing shader files. - * @param configdir Directory containing configuration file. - */ - void initialize_render_pass_with_shader_commands(size_t width, size_t height, const util::Path &shaderdir, const util::Path &config_path); - /** * Reference to the openage renderer. */ @@ -163,11 +131,6 @@ class WorldRenderStage { */ std::shared_ptr render_pass; - /** - * Template for the world shader program. - */ - std::shared_ptr shader_template; - /** * Render entities requested by the game world. */ From 32a9d670bf29385a4d1e8ccc2fa88601c67a5e70 Mon Sep 17 00:00:00 2001 From: Alex Zhuohao He Date: Tue, 28 Jan 2025 17:20:12 -0500 Subject: [PATCH 140/152] Rename files --- .../{demo_7_world.frag.glsl => demo_7_shader_command.frag.glsl} | 0 .../{demo_7_world.vert.glsl => demo_7_shader_command.vert.glsl} | 0 .../world/{world_shader_commands.cpp => shader_template.cpp} | 0 .../stages/world/{world_shader_commands.h => shader_template.h} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename assets/test/shaders/{demo_7_world.frag.glsl => demo_7_shader_command.frag.glsl} (100%) rename assets/test/shaders/{demo_7_world.vert.glsl => demo_7_shader_command.vert.glsl} (100%) rename libopenage/renderer/stages/world/{world_shader_commands.cpp => shader_template.cpp} (100%) rename libopenage/renderer/stages/world/{world_shader_commands.h => shader_template.h} (100%) diff --git a/assets/test/shaders/demo_7_world.frag.glsl b/assets/test/shaders/demo_7_shader_command.frag.glsl similarity index 100% rename from assets/test/shaders/demo_7_world.frag.glsl rename to assets/test/shaders/demo_7_shader_command.frag.glsl diff --git a/assets/test/shaders/demo_7_world.vert.glsl b/assets/test/shaders/demo_7_shader_command.vert.glsl similarity index 100% rename from assets/test/shaders/demo_7_world.vert.glsl rename to assets/test/shaders/demo_7_shader_command.vert.glsl diff --git a/libopenage/renderer/stages/world/world_shader_commands.cpp b/libopenage/renderer/stages/world/shader_template.cpp similarity index 100% rename from libopenage/renderer/stages/world/world_shader_commands.cpp rename to libopenage/renderer/stages/world/shader_template.cpp diff --git a/libopenage/renderer/stages/world/world_shader_commands.h b/libopenage/renderer/stages/world/shader_template.h similarity index 100% rename from libopenage/renderer/stages/world/world_shader_commands.h rename to libopenage/renderer/stages/world/shader_template.h From 06bbfd12eb79b694ad2fda50113aad4e83f8d03b Mon Sep 17 00:00:00 2001 From: Alex Zhuohao He Date: Tue, 28 Jan 2025 17:34:46 -0500 Subject: [PATCH 141/152] Simplify shader template system with file-based snippets --- .../shaders/demo_7_shader_command.frag.glsl | 36 +--- .../shaders/demo_7_shader_command.vert.glsl | 100 +--------- .../shaders/demo_7_snippets/alpha.snippet | 1 + .../shaders/demo_7_snippets/color.snippet | 3 + libopenage/renderer/demo/demo_7.cpp | 112 +++++------- libopenage/renderer/demo/demo_7.h | 12 +- .../renderer/stages/world/CMakeLists.txt | 2 +- .../renderer/stages/world/shader_template.cpp | 171 ++++-------------- .../renderer/stages/world/shader_template.h | 48 ++--- 9 files changed, 114 insertions(+), 371 deletions(-) create mode 100644 assets/test/shaders/demo_7_snippets/alpha.snippet create mode 100644 assets/test/shaders/demo_7_snippets/color.snippet diff --git a/assets/test/shaders/demo_7_shader_command.frag.glsl b/assets/test/shaders/demo_7_shader_command.frag.glsl index d6ffd22029..76e50ffd78 100644 --- a/assets/test/shaders/demo_7_shader_command.frag.glsl +++ b/assets/test/shaders/demo_7_shader_command.frag.glsl @@ -1,34 +1,12 @@ #version 330 -in vec2 vert_uv; +in vec2 tex_coord; +out vec4 frag_color; -layout(location = 0) out vec4 col; -layout(location = 1) out uint id; - -uniform sampler2D tex; -uniform uint u_id; - -// position (top left corner) and size: (x, y, width, height) -uniform vec4 tile_params; - -vec2 uv = vec2( - vert_uv.x * tile_params.z + tile_params.x, - vert_uv.y *tile_params.w + tile_params.y); +uniform float time; void main() { - vec4 tex_val = texture(tex, uv); - int alpha = int(round(tex_val.a * 255)); - switch (alpha) { - case 0: - col = tex_val; - discard; - - // do not save the ID - return; - //@COMMAND_SWITCH@ - default: - col = tex_val; - break; - } - id = u_id; -} \ No newline at end of file + // PLACEHOLDER: color + // PLACEHOLDER: alpha + frag_color = vec4(r, g, b, alpha); +} diff --git a/assets/test/shaders/demo_7_shader_command.vert.glsl b/assets/test/shaders/demo_7_shader_command.vert.glsl index 7987d40ec3..caefe64535 100644 --- a/assets/test/shaders/demo_7_shader_command.vert.glsl +++ b/assets/test/shaders/demo_7_shader_command.vert.glsl @@ -1,101 +1,9 @@ #version 330 -layout(location=0) in vec2 v_position; -layout(location=1) in vec2 uv; - -out vec2 vert_uv; - -// camera parameters for transforming the object position -// and scaling the subtex to the correct size -layout (std140) uniform camera { - // view matrix (world to view space) - mat4 view; - // projection matrix (view to clip space) - mat4 proj; - // inverse zoom factor (1.0 / zoom) - // high zoom = upscale subtex - // low zoom = downscale subtex - float inv_zoom; - // inverse viewport size (1.0 / viewport size) - vec2 inv_viewport_size; -}; - -// can be used to move the object position in world space _before_ -// it's transformed to clip space -// this is usually unnecessary because we want to draw the -// subtex where the object is, so this can be set to the identity matrix -uniform mat4 model; - -// position of the object in world space -uniform vec3 obj_world_position; - -// flip the subtexture horizontally/vertically -uniform bool flip_x; -uniform bool flip_y; - -// parameters for scaling and moving the subtex -// to the correct position in clip space - -// animation scalefactor -// scales the vertex positions so that they -// match the subtex dimensions -// -// high animation scale = downscale subtex -// low animation scale = upscale subtex -uniform float scale; - -// size of the subtex (in pixels) -uniform vec2 subtex_size; - -// offset of the subtex anchor point -// from the subtex center (in pixels) -// used to move the subtex so that the anchor point -// is at the object position -uniform vec2 anchor_offset; +in vec2 position; +out vec2 tex_coord; void main() { - // translate the position of the object from world space to clip space - // this is the position where we want to draw the subtex in 2D - vec4 obj_clip_pos = proj * view * model * vec4(obj_world_position, 1.0); - - // subtex has to be scaled to account for the zoom factor - // and the animation scale factor. essentially this is (animation scale / zoom). - float zoom_scale = scale * inv_zoom; - - // Scale the subtex vertices - // we have to account for the viewport size to get the correct dimensions - // and then scale the subtex to the zoom factor to get the correct size - vec2 vert_scale = zoom_scale * subtex_size * inv_viewport_size; - - // Scale the anchor offset with the same method as above - // to get the correct anchor position in the viewport - vec2 anchor_scale = zoom_scale * anchor_offset * inv_viewport_size; - - // if the subtex is flipped, we also need to flip the anchor offset - // essentially, we invert the coordinates for the flipped axis - float anchor_x = float(flip_x) * -1.0 * anchor_scale.x + float(!flip_x) * anchor_scale.x; - float anchor_y = float(flip_y) * -1.0 * anchor_scale.y + float(!flip_y) * anchor_scale.y; - - // offset the clip position by the offset of the subtex anchor - // imagine this as pinning the subtex to the object position at the subtex anchor point - obj_clip_pos += vec4(anchor_x, anchor_y, 0.0, 0.0); - - // create a move matrix for positioning the vertices - // uses the vert scale and the transformed object position in clip space - mat4 move = mat4(vert_scale.x, 0.0, 0.0, 0.0, - 0.0, vert_scale.y, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - obj_clip_pos.x, obj_clip_pos.y, obj_clip_pos.z, 1.0); - - // calculate the final vertex position - gl_Position = move * vec4(v_position, 0.0, 1.0); - - // if the subtex is flipped, we also need to flip the uv tex coordinates - // essentially, we invert the coordinates for the flipped axis - - // !flip_x is default because OpenGL uses bottom-left as its origin - float uv_x = float(!flip_x) * uv.x + float(flip_x) * (1.0 - uv.x); - float uv_y = float(flip_y) * uv.y + float(!flip_y) * (1.0 - uv.y); - - vert_uv = vec2(uv_x, uv_y); + tex_coord = position.xy * 0.5 + 0.5; + gl_Position = vec4(position.xy, 0.0, 1.0); } diff --git a/assets/test/shaders/demo_7_snippets/alpha.snippet b/assets/test/shaders/demo_7_snippets/alpha.snippet new file mode 100644 index 0000000000..603c275311 --- /dev/null +++ b/assets/test/shaders/demo_7_snippets/alpha.snippet @@ -0,0 +1 @@ +float alpha = (sin(time) + 1.0) * 0.5; diff --git a/assets/test/shaders/demo_7_snippets/color.snippet b/assets/test/shaders/demo_7_snippets/color.snippet new file mode 100644 index 0000000000..4c0204e773 --- /dev/null +++ b/assets/test/shaders/demo_7_snippets/color.snippet @@ -0,0 +1,3 @@ +float r = 0.5 + 0.5 * sin(time); +float g = 0.5 + 0.5 * sin(time + 2.0); +float b = 0.5 + 0.5 * sin(time + 4.0); diff --git a/libopenage/renderer/demo/demo_7.cpp b/libopenage/renderer/demo/demo_7.cpp index 470ded48f1..5f964b1034 100644 --- a/libopenage/renderer/demo/demo_7.cpp +++ b/libopenage/renderer/demo/demo_7.cpp @@ -4,27 +4,15 @@ #include "util/path.h" -#include -#include - -#include "coord/tile.h" -#include "renderer/camera/camera.h" +#include "renderer/demo/util.h" #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" -#include "renderer/render_factory.h" #include "renderer/render_pass.h" #include "renderer/render_target.h" -#include "renderer/resources/assets/asset_manager.h" +#include "renderer/resources/mesh_data.h" #include "renderer/resources/shader_source.h" -#include "renderer/stages/camera/manager.h" -#include "renderer/stages/screen/render_stage.h" -#include "renderer/stages/skybox/render_stage.h" -#include "renderer/stages/terrain/render_entity.h" -#include "renderer/stages/terrain/render_stage.h" -#include "renderer/stages/world/render_entity.h" -#include "renderer/stages/world/render_stage.h" -#include "renderer/uniform_buffer.h" -#include "time/clock.h" +#include "renderer/shader_program.h" +#include "renderer/stages/world/shader_template.h" namespace openage::renderer::tests { @@ -36,72 +24,62 @@ void renderer_demo_7(const util::Path &path) { settings.height = 600; settings.debug = true; - auto window = std::make_shared("Shader Commands Demo", settings); - auto renderer = window->make_renderer(); - auto camera = std::make_shared(renderer, window->get_size()); - auto clock = std::make_shared(); - auto asset_manager = std::make_shared( - renderer, - path["assets"]["test"]); - auto cam_manager = std::make_shared(camera); + opengl::GlWindow window("Shader Commands Demo", settings); + auto renderer = window.make_renderer(); auto shaderdir = path / "assets" / "test" / "shaders"; - std::vector> - render_passes{}; - - // Initialize world renderer with shader commands - auto world_renderer = std::make_shared( - window, - renderer, - camera, - shaderdir, - shaderdir, // Temporarily, Shader commands config has the same path with shaders for this demo - asset_manager, - clock); - - render_passes.push_back(world_renderer->get_render_pass()); - - auto screen_renderer = std::make_shared( - window, - renderer, - path["assets"]["shaders"]); - std::vector> targets{}; - for (auto &pass : render_passes) { - targets.push_back(pass->get_target()); - } - screen_renderer->set_render_targets(targets); + // Initialize shader templlalte + world::ShaderTemplate frag_template(shaderdir / "demo_7_shader_command.frag.glsl"); - render_passes.push_back(screen_renderer->get_render_pass()); + // Load snippets from a snippet directory + frag_template.load_snippets(shaderdir / "demo_7_snippets"); - auto render_factory = std::make_shared(nullptr, world_renderer); + auto vert_shader_file = (shaderdir / "demo_7_shader_command.vert.glsl").open(); + auto vert_shader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::vertex, + vert_shader_file.read()); + vert_shader_file.close(); - auto entity1 = render_factory->add_world_render_entity(); - entity1->update(0, coord::phys3(0.0f, 0.0f, 0.0f), "./textures/test_gaben.sprite"); + auto frag_shader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::fragment, + frag_template.generate_source()); - auto entity2 = render_factory->add_world_render_entity(); - entity2->update(1, coord::phys3(3.0f, 0.0f, 0.0f), "./textures/test_gaben.sprite"); + auto shader = renderer->add_shader({vert_shader_src, frag_shader_src}); - auto entity3 = render_factory->add_world_render_entity(); - entity3->update(2, coord::phys3(-3.0f, 0.0f, 0.0f), "./textures/test_gaben.sprite"); + // Create a simple quad for rendering + auto quad = renderer->add_mesh_geometry(resources::MeshData::make_quad()); - // Main loop - while (not window->should_close()) { - qtapp->process_events(); + auto uniforms = shader->new_uniform_input("time", 0.0f); + + Renderable display_obj{ + uniforms, + quad, + false, + false, + }; - // Update camera matrices - cam_manager->update(); + if (not check_uniform_completeness({display_obj})) { + log::log(WARN << "Uniforms not complete."); + } - world_renderer->update(); + auto pass = renderer->add_render_pass({display_obj}, renderer->get_display_target()); - for (auto &pass : render_passes) { - renderer->render(pass); - } + // Main loop + float time = 0.0f; + while (not window.should_close()) { + time += 0.016f; + uniforms->update("time", time); - renderer->check_error(); + renderer->render(pass); + window.update(); + qtapp->process_events(); - window->update(); + renderer->check_error(); } + window.close(); } } // namespace openage::renderer::tests diff --git a/libopenage/renderer/demo/demo_7.h b/libopenage/renderer/demo/demo_7.h index 45c192523d..7642fe06c8 100644 --- a/libopenage/renderer/demo/demo_7.h +++ b/libopenage/renderer/demo/demo_7.h @@ -7,13 +7,13 @@ namespace openage::renderer::tests { /** - * Show off the render stages in the level 2 renderer and the camera - * system. + * Demonstrate the shader template system for shader generation. * - Window creation - * - Creating a camera - * - Initializing the level 2 render stages: skybox, terrain, world, screen - * - Adding renderables to the render stages via the render factory - * - Moving camera with mouse/keyboard callbacks + * - Create a shader template + * - Load shader snippets (command) from files + * - Generate shader sources from the template + * - Creating a render pass + * - Creating a renderable from a mesh * * @param path Path to the project rootdir. */ diff --git a/libopenage/renderer/stages/world/CMakeLists.txt b/libopenage/renderer/stages/world/CMakeLists.txt index d86d7aa44c..c43ac917b9 100644 --- a/libopenage/renderer/stages/world/CMakeLists.txt +++ b/libopenage/renderer/stages/world/CMakeLists.txt @@ -2,5 +2,5 @@ add_sources(libopenage object.cpp render_entity.cpp render_stage.cpp - world_shader_commands.cpp + shader_template.cpp ) diff --git a/libopenage/renderer/stages/world/shader_template.cpp b/libopenage/renderer/stages/world/shader_template.cpp index 9cc10f5f87..04adbfbcab 100644 --- a/libopenage/renderer/stages/world/shader_template.cpp +++ b/libopenage/renderer/stages/world/shader_template.cpp @@ -1,6 +1,6 @@ // Copyright 2024-2025 the openage authors. See copying.md for legal info. -#include "world_shader_commands.h" +#include "shader_template.h" #include @@ -9,158 +9,57 @@ namespace openage::renderer::world { -ShaderCommandTemplate::ShaderCommandTemplate(const std::string &template_code) : - template_code{template_code} {} - -bool ShaderCommandTemplate::load_commands(const util::Path &config_path) { - try { - log::log(INFO << "Loading shader commands config from: " << config_path); - auto config_file = config_path.open(); - std::string line; - std::stringstream ss(config_file.read()); - - ShaderCommandConfig current_command; - // if true, we are reading the code block for the current command. - bool reading_code = false; - std::string code_block; - - while (std::getline(ss, line)) { - if (!line.empty() && line.back() == '\r') { - line.pop_back(); - } - // Trim whitespace from line - line = trim(line); - log::log(INFO << "Parsing line: " << line); - - // Skip empty lines and comments - if (line.empty() || line[0] == '#') { - continue; - } - - if (reading_code) { - if (line == "}") { - reading_code = false; - current_command.code = code_block; - - // Generate and add snippet - std::string snippet = generate_snippet(current_command); - add_snippet(current_command.placeholder_id, snippet); - commands.push_back(current_command); - - // Reset for next command - code_block.clear(); - } - else { - code_block += line + "\n"; - } - continue; - } - - if (line == "[COMMAND]") { - current_command = ShaderCommandConfig{}; - continue; - } - - // Parse key-value pairs - size_t pos = line.find('='); - if (pos != std::string::npos) { - std::string key = trim(line.substr(0, pos)); - std::string value = trim(line.substr(pos + 1)); +ShaderTemplate::ShaderTemplate(const util::Path &template_path) { + auto file = template_path.open(); + this->template_code = file.read(); + file.close(); +} - if (key == "placeholder") { - current_command.placeholder_id = value; - } - else if (key == "alpha") { - uint8_t alpha = static_cast(std::stoi(value)); - if (alpha % 2 == 0 && alpha >= 0 && alpha <= 254) { - current_command.alpha = alpha; - } - else { - log::log(ERR << "Invalid alpha value for command: " << alpha); - return false; - } - } - else if (key == "description") { - current_command.description = value; - } - else if (key == "code") { - if (value == "{") { - reading_code = true; - code_block.clear(); - } - } - } +void ShaderTemplate::load_snippets(const util::Path &snippet_path) { + // load config here + util::Path snippet_path_copy = snippet_path; + for (const auto &entry : snippet_path_copy.iterdir()) { + if (entry.get_name().ends_with(".snippet")) { + add_snippet(entry); } - - return true; - } - catch (const std::exception &e) { - log::log(ERR << "Failed to load shader commands: " << e.what()); - return false; } } -bool ShaderCommandTemplate::add_snippet(const std::string &placeholder_id, const std::string &snippet) { - if (snippet.empty()) { - log::log(ERR << "Empty snippet for placeholder: " << placeholder_id); - return false; - } - - if (placeholder_id.empty()) { - log::log(ERR << "Empty placeholder ID for snippet"); - return false; - } - - // Check if the placeholder exists in the template - std::string placeholder = "//@" + placeholder_id + "@"; - if (template_code.find(placeholder) == std::string::npos) { - log::log(ERR << "Placeholder not found in template: " << placeholder_id); - return false; - } +void ShaderTemplate::add_snippet(const util::Path &snippet_path) { + std::string file_name = snippet_path.get_name(); + std::string name = file_name.substr(0, file_name.find_last_of('.')); + auto file = snippet_path.open(); + std::string content = file.read(); + file.close(); - // Store the snippet - snippets[placeholder_id].push_back(snippet); - return true; + snippets[name] = content; } -std::string ShaderCommandTemplate::generate_snippet(const ShaderCommandConfig &command) { - return "case " + std::to_string(command.alpha) + ":\n" - + "\t\t// " + command.description + "\n" - + "\t\t" + command.code + "\t\tbreak;\n"; -} - -std::string ShaderCommandTemplate::generate_source() const { +std::string ShaderTemplate::generate_source() const { std::string result = template_code; // Process each placeholder - for (const auto &[placeholder_id, snippet_list] : snippets) { - std::string combined_snippets; + for (const auto &[name, snippet_code] : snippets) { + std::string placeholder = "// PLACEHOLDER: " + name; + size_t pos = result.find(placeholder); - // Combine all snippets for this placeholder - for (const auto &snippet : snippet_list) { - combined_snippets += snippet; + if (pos != std::string::npos) { + result.replace(pos, placeholder.length(), snippet_code); } - - // Find and replace the placeholder - std::string placeholder = "//@" + placeholder_id + "@"; - size_t pos = result.find(placeholder); - if (pos == std::string::npos) { - throw Error(MSG(err) << "Placeholder disappeared from template: " << placeholder_id); + else { + log::log(WARN << "Placeholder not found in template: " << name); } + } - // Replace placeholder with combined snippets - result.replace(pos, placeholder.length(), combined_snippets); + // Check if all placeholders were replaced + size_t placeholder_pos = result.find("// PLACEHOLDER:"); + if (placeholder_pos != std::string::npos) { + size_t line_end = result.find('\n', placeholder_pos); + std::string missing = result.substr(placeholder_pos, + line_end - placeholder_pos); + throw Error(MSG(err) << "Missing snippet for placeholder: " << missing); } return result; } - -std::string ShaderCommandTemplate::trim(const std::string &str) const { - size_t first = str.find_first_not_of(" \t"); - if (first == std::string::npos) { - return ""; - } - size_t last = str.find_last_not_of(" \t"); - return str.substr(first, (last - first + 1)); -} } // namespace openage::renderer::world diff --git a/libopenage/renderer/stages/world/shader_template.h b/libopenage/renderer/stages/world/shader_template.h index 9f6a158a25..0b5dc89f80 100644 --- a/libopenage/renderer/stages/world/shader_template.h +++ b/libopenage/renderer/stages/world/shader_template.h @@ -13,52 +13,35 @@ namespace openage { namespace renderer { namespace world { -/** - * Represents a single shader command that can be used in the world fragment shader. - * Commands are identified by their alpha values and contain GLSL code snippets - * that define custom rendering behavior. - */ -struct ShaderCommandConfig { - /// ID of the placeholder where this snippet should be inserted - std::string placeholder_id; - /// Command identifier ((must be even, range 0-254)) - uint8_t alpha; - /// GLSL code snippet that defines the command's behavior - std::string code; - /// Documentation (optional) - std::string description; -}; - /** * Manages shader templates and their code snippets. * Allows loading configurable shader commands and generating * complete shader source code. */ -class ShaderCommandTemplate { +class ShaderTemplate { public: /** * Create a shader template from source code of shader. * - * @param template_code Source code containing placeholders. + * @param template_path Path to the template file. */ - explicit ShaderCommandTemplate(const std::string &template_code); + explicit ShaderTemplate(const util::Path &template_path); /** - * Load commands from a configuration file. + * Load all snippets from a JSON config file. * - * @param config_path Path to the command configuration file. - * @return true if commands were loaded successfully. + * @param config_path Path to JSON config file. + * @param base_path Base path for resolving relative snippet paths. */ - bool load_commands(const util::Path &config_path); + void load_snippets(const util::Path &snippet_path); /** - * Add a single code snippet to the template. + * Add a single code snippet to snippets map. * - * @param placeholder_id Where to insert the snippet. - * @param snippet Code to insert. - * @return true if snippet was added successfully. + * @param name Snippet identifier. + * @param snippet_path Path to the snippet file. */ - bool add_snippet(const std::string &placeholder_id, const std::string &snippet); + void add_snippet(const util::Path &snippet_path); /** * Generate final shader source code with all snippets inserted. @@ -69,17 +52,10 @@ class ShaderCommandTemplate { std::string generate_source() const; private: - // Generate a single code snippet for a command. - std::string generate_snippet(const ShaderCommandConfig &command); - // Helper function to trim whitespace from a string - std::string trim(const std::string &str) const; - // Original template code with placeholders std::string template_code; // Mapping of placeholder IDs to their code snippets - std::map> snippets; - // Loaded command configurations - std::vector commands; + std::map snippets; }; } // namespace world } // namespace renderer From b07e6bbabfb5ceed41dfff9c1d13227aa44fdfed Mon Sep 17 00:00:00 2001 From: Alex Zhuohao He Date: Sun, 20 Apr 2025 15:26:43 -0400 Subject: [PATCH 142/152] update according to review --- libopenage/renderer/demo/demo_7.cpp | 7 ++--- .../renderer/stages/world/shader_template.cpp | 26 ++++++++++++------- .../renderer/stages/world/shader_template.h | 7 ++--- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/libopenage/renderer/demo/demo_7.cpp b/libopenage/renderer/demo/demo_7.cpp index 5f964b1034..9377053bdc 100644 --- a/libopenage/renderer/demo/demo_7.cpp +++ b/libopenage/renderer/demo/demo_7.cpp @@ -29,7 +29,7 @@ void renderer_demo_7(const util::Path &path) { auto shaderdir = path / "assets" / "test" / "shaders"; - // Initialize shader templlalte + // Initialize shader template world::ShaderTemplate frag_template(shaderdir / "demo_7_shader_command.frag.glsl"); // Load snippets from a snippet directory @@ -42,10 +42,7 @@ void renderer_demo_7(const util::Path &path) { vert_shader_file.read()); vert_shader_file.close(); - auto frag_shader_src = resources::ShaderSource( - resources::shader_lang_t::glsl, - resources::shader_stage_t::fragment, - frag_template.generate_source()); + auto frag_shader_src = frag_template.generate_source(); auto shader = renderer->add_shader({vert_shader_src, frag_shader_src}); diff --git a/libopenage/renderer/stages/world/shader_template.cpp b/libopenage/renderer/stages/world/shader_template.cpp index 04adbfbcab..641e312f1e 100644 --- a/libopenage/renderer/stages/world/shader_template.cpp +++ b/libopenage/renderer/stages/world/shader_template.cpp @@ -26,25 +26,25 @@ void ShaderTemplate::load_snippets(const util::Path &snippet_path) { } void ShaderTemplate::add_snippet(const util::Path &snippet_path) { - std::string file_name = snippet_path.get_name(); - std::string name = file_name.substr(0, file_name.find_last_of('.')); auto file = snippet_path.open(); std::string content = file.read(); file.close(); + std::string name = snippet_path.get_stem(); + snippets[name] = content; } -std::string ShaderTemplate::generate_source() const { - std::string result = template_code; +renderer::resources::ShaderSource ShaderTemplate::generate_source() const { + std::string result_src = template_code; // Process each placeholder for (const auto &[name, snippet_code] : snippets) { std::string placeholder = "// PLACEHOLDER: " + name; - size_t pos = result.find(placeholder); + size_t pos = result_src.find(placeholder); if (pos != std::string::npos) { - result.replace(pos, placeholder.length(), snippet_code); + result_src.replace(pos, placeholder.length(), snippet_code); } else { log::log(WARN << "Placeholder not found in template: " << name); @@ -52,14 +52,20 @@ std::string ShaderTemplate::generate_source() const { } // Check if all placeholders were replaced - size_t placeholder_pos = result.find("// PLACEHOLDER:"); + size_t placeholder_pos = result_src.find("// PLACEHOLDER:"); if (placeholder_pos != std::string::npos) { - size_t line_end = result.find('\n', placeholder_pos); - std::string missing = result.substr(placeholder_pos, - line_end - placeholder_pos); + size_t line_end = result_src.find('\n', placeholder_pos); + std::string missing = result_src.substr(placeholder_pos, + line_end - placeholder_pos); throw Error(MSG(err) << "Missing snippet for placeholder: " << missing); } + auto result = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::fragment, + std::move(result_src)); + return result; } + } // namespace openage::renderer::world diff --git a/libopenage/renderer/stages/world/shader_template.h b/libopenage/renderer/stages/world/shader_template.h index 0b5dc89f80..117236f51c 100644 --- a/libopenage/renderer/stages/world/shader_template.h +++ b/libopenage/renderer/stages/world/shader_template.h @@ -7,6 +7,7 @@ #include #include +#include "renderer/resources/shader_source.h" #include "util/path.h" namespace openage { @@ -49,12 +50,12 @@ class ShaderTemplate { * @return Complete shader code. * @throws Error if any required placeholders are missing snippets. */ - std::string generate_source() const; + renderer::resources::ShaderSource generate_source() const; private: - // Original template code with placeholders + /// Original template code with placeholders std::string template_code; - // Mapping of placeholder IDs to their code snippets + /// Mapping of placeholder IDs to their code snippets std::map snippets; }; } // namespace world From baabe59d1e4627ada0e55f115ef2c371df573c76 Mon Sep 17 00:00:00 2001 From: Alex Zhuohao He Date: Sat, 3 May 2025 18:19:51 -0400 Subject: [PATCH 143/152] Optimize ShaderTemplate by precomputing placeholder positions --- .../renderer/stages/world/shader_template.cpp | 38 ++++++++++--------- .../renderer/stages/world/shader_template.h | 14 +++++-- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/libopenage/renderer/stages/world/shader_template.cpp b/libopenage/renderer/stages/world/shader_template.cpp index 641e312f1e..6514204162 100644 --- a/libopenage/renderer/stages/world/shader_template.cpp +++ b/libopenage/renderer/stages/world/shader_template.cpp @@ -13,6 +13,20 @@ ShaderTemplate::ShaderTemplate(const util::Path &template_path) { auto file = template_path.open(); this->template_code = file.read(); file.close(); + + std::string marker = "// PLACEHOLDER: "; + size_t pos = 0; + + while ((pos = this->template_code.find(marker, pos)) != std::string::npos) { + size_t name_start = pos + marker.length(); + size_t line_end = this->template_code.find('\n', name_start); + std::string name = this->template_code.substr(name_start, line_end - name_start); + // Trim trailing whitespace (space, tab, carriage return, etc.) + name.erase(name.find_last_not_of(" \t\r\n") + 1); + + this->placeholders.push_back({name, pos, line_end - pos}); + pos = line_end; + } } void ShaderTemplate::load_snippets(const util::Path &snippet_path) { @@ -38,28 +52,18 @@ void ShaderTemplate::add_snippet(const util::Path &snippet_path) { renderer::resources::ShaderSource ShaderTemplate::generate_source() const { std::string result_src = template_code; - // Process each placeholder - for (const auto &[name, snippet_code] : snippets) { - std::string placeholder = "// PLACEHOLDER: " + name; - size_t pos = result_src.find(placeholder); - - if (pos != std::string::npos) { - result_src.replace(pos, placeholder.length(), snippet_code); + // Replace placeholders in reverse order (to avoid offset issues) + for (auto it = placeholders.rbegin(); it != placeholders.rend(); ++it) { + const auto &ph = *it; + auto snippet_it = snippets.find(ph.name); + if (snippet_it != snippets.end()) { + result_src.replace(ph.position, ph.length, snippet_it->second); } else { - log::log(WARN << "Placeholder not found in template: " << name); + throw Error(MSG(err) << "Missing snippet for placeholder: " << ph.name); } } - // Check if all placeholders were replaced - size_t placeholder_pos = result_src.find("// PLACEHOLDER:"); - if (placeholder_pos != std::string::npos) { - size_t line_end = result_src.find('\n', placeholder_pos); - std::string missing = result_src.substr(placeholder_pos, - line_end - placeholder_pos); - throw Error(MSG(err) << "Missing snippet for placeholder: " << missing); - } - auto result = resources::ShaderSource( resources::shader_lang_t::glsl, resources::shader_stage_t::fragment, diff --git a/libopenage/renderer/stages/world/shader_template.h b/libopenage/renderer/stages/world/shader_template.h index 117236f51c..542ca8ed08 100644 --- a/libopenage/renderer/stages/world/shader_template.h +++ b/libopenage/renderer/stages/world/shader_template.h @@ -29,10 +29,9 @@ class ShaderTemplate { explicit ShaderTemplate(const util::Path &template_path); /** - * Load all snippets from a JSON config file. + * Load all snippets from a directory of .snippet files. * - * @param config_path Path to JSON config file. - * @param base_path Base path for resolving relative snippet paths. + * @param snippet_path Path to directory containing snippet files. */ void load_snippets(const util::Path &snippet_path); @@ -57,6 +56,15 @@ class ShaderTemplate { std::string template_code; /// Mapping of placeholder IDs to their code snippets std::map snippets; + + /// Info about a placeholder found in the template + struct Placeholder { + std::string name; + size_t position; + size_t length; + }; + + std::vector placeholders; }; } // namespace world } // namespace renderer From 55af0a50070f31910b12d67f5d7f0931fcef8fb4 Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 14 May 2025 07:45:23 +0200 Subject: [PATCH 144/152] renderer: Move shader template to resources namespace. --- libopenage/renderer/demo/demo_7.cpp | 5 +++-- libopenage/renderer/resources/CMakeLists.txt | 1 + .../renderer/{stages/world => resources}/shader_template.cpp | 2 +- .../renderer/{stages/world => resources}/shader_template.h | 4 ++-- libopenage/renderer/stages/world/CMakeLists.txt | 1 - 5 files changed, 7 insertions(+), 6 deletions(-) rename libopenage/renderer/{stages/world => resources}/shader_template.cpp (98%) rename libopenage/renderer/{stages/world => resources}/shader_template.h (95%) diff --git a/libopenage/renderer/demo/demo_7.cpp b/libopenage/renderer/demo/demo_7.cpp index 9377053bdc..8c804a52d9 100644 --- a/libopenage/renderer/demo/demo_7.cpp +++ b/libopenage/renderer/demo/demo_7.cpp @@ -11,8 +11,9 @@ #include "renderer/render_target.h" #include "renderer/resources/mesh_data.h" #include "renderer/resources/shader_source.h" +#include "renderer/resources/shader_template.h" #include "renderer/shader_program.h" -#include "renderer/stages/world/shader_template.h" + namespace openage::renderer::tests { @@ -30,7 +31,7 @@ void renderer_demo_7(const util::Path &path) { auto shaderdir = path / "assets" / "test" / "shaders"; // Initialize shader template - world::ShaderTemplate frag_template(shaderdir / "demo_7_shader_command.frag.glsl"); + resources::ShaderTemplate frag_template(shaderdir / "demo_7_shader_command.frag.glsl"); // Load snippets from a snippet directory frag_template.load_snippets(shaderdir / "demo_7_snippets"); diff --git a/libopenage/renderer/resources/CMakeLists.txt b/libopenage/renderer/resources/CMakeLists.txt index c5946910e5..16a0e5eb5f 100644 --- a/libopenage/renderer/resources/CMakeLists.txt +++ b/libopenage/renderer/resources/CMakeLists.txt @@ -4,6 +4,7 @@ add_sources(libopenage mesh_data.cpp palette_info.cpp shader_source.cpp + shader_template.cpp texture_data.cpp texture_info.cpp texture_subinfo.cpp diff --git a/libopenage/renderer/stages/world/shader_template.cpp b/libopenage/renderer/resources/shader_template.cpp similarity index 98% rename from libopenage/renderer/stages/world/shader_template.cpp rename to libopenage/renderer/resources/shader_template.cpp index 6514204162..c328ba82bc 100644 --- a/libopenage/renderer/stages/world/shader_template.cpp +++ b/libopenage/renderer/resources/shader_template.cpp @@ -7,7 +7,7 @@ #include "error/error.h" #include "log/log.h" -namespace openage::renderer::world { +namespace openage::renderer::resources { ShaderTemplate::ShaderTemplate(const util::Path &template_path) { auto file = template_path.open(); diff --git a/libopenage/renderer/stages/world/shader_template.h b/libopenage/renderer/resources/shader_template.h similarity index 95% rename from libopenage/renderer/stages/world/shader_template.h rename to libopenage/renderer/resources/shader_template.h index 542ca8ed08..210061ef0c 100644 --- a/libopenage/renderer/stages/world/shader_template.h +++ b/libopenage/renderer/resources/shader_template.h @@ -12,7 +12,7 @@ namespace openage { namespace renderer { -namespace world { +namespace resources { /** * Manages shader templates and their code snippets. @@ -57,7 +57,7 @@ class ShaderTemplate { /// Mapping of placeholder IDs to their code snippets std::map snippets; - /// Info about a placeholder found in the template + /// Info about a placeholder found in the template struct Placeholder { std::string name; size_t position; diff --git a/libopenage/renderer/stages/world/CMakeLists.txt b/libopenage/renderer/stages/world/CMakeLists.txt index c43ac917b9..811fd929ed 100644 --- a/libopenage/renderer/stages/world/CMakeLists.txt +++ b/libopenage/renderer/stages/world/CMakeLists.txt @@ -2,5 +2,4 @@ add_sources(libopenage object.cpp render_entity.cpp render_stage.cpp - shader_template.cpp ) From e92676677605346269730fa45058e8e25136cba7 Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 14 May 2025 08:53:32 +0200 Subject: [PATCH 145/152] doc: Add documentation for shader templates. --- doc/code/renderer/demos.md | 13 +++ doc/code/renderer/images/demo_7.mp4 | Bin 0 -> 1009185 bytes doc/code/renderer/level1.md | 76 ++++++++++++++++++ .../renderer/resources/shader_template.h | 2 + 4 files changed, 91 insertions(+) create mode 100644 doc/code/renderer/images/demo_7.mp4 diff --git a/doc/code/renderer/demos.md b/doc/code/renderer/demos.md index 3bb7b266f3..3be974d628 100644 --- a/doc/code/renderer/demos.md +++ b/doc/code/renderer/demos.md @@ -144,6 +144,19 @@ This demo shows how to use [frustum culling](level2.md#frustum-culling) in the r ![Demo 6](/doc/code/renderer/images/demo_6.png) +### Demo 6 + +This demo shows how to use [shader templating](level1.md#shader-templates) in the renderer. + +```bash +./bin/run test --demo renderer.tests.renderer_demo 6 +``` + +**Result:** + +![Demo 7](/doc/code/renderer/images/demo_7.mp4) + + ## Stresstests ### Stresstest 0 diff --git a/doc/code/renderer/images/demo_7.mp4 b/doc/code/renderer/images/demo_7.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..fa26db0f866371a6c0d4a93cd781640039332f2d GIT binary patch literal 1009185 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

KY;#PN__?XyW!nbDj|LO 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�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

}P`erBRgQzL=l`z#HLkDcyLB2To%ook!@U2vO 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

m-I6^2ZWf$Rv{Rb#in}DPEfLN@Ii{7Z% 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

$aGh;0d?Mq)$HVPB#uunWIYS-M`?)P9 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�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 buffer = renderer->add_uniform_buffer(ubo_info); ``` +### Shader Templates + +Shader templates allow the definition of a shader source code with placeholders that can be replaced at +load- or run-time. Templates are help if only specific parts of the shader code are supposed to +be configurable. This may be used, for example, to alter certain code path of the built-in shaders +without recompiling the engine. + +An example shader template looks like this: + +```glsl +#version 330 + +in vec2 tex_coord; +out vec4 frag_color; + +// PLACEHOLDER: uniform_color + +void main() { + // PLACEHOLDER: alpha + frag_color = vec4(col.xyz, alpha); +} +``` + +Placeholders are inserted as comments into the GLSL source. Every placeholder has an ID for +referencing. For these IDs, *snippets* can be defined to insert code in place of the placeholder +comment. Placeholders can be placed at any position in the template, so they can be used to insert +other statements than the control code, including unforms, input/output variables, functions and more. + +The snippets for the above example may look like this: + +`uniform_color.snippet` + +```glsl +uniform vec4 col; +``` + +`alpha.snippet` + +```glsl +float alpha = 0.5; +``` + +In the renderer, shader templates can be created using the `renderer::resources::ShaderTemplate` class. + +```cpp +util::Path template_path = shaderdir / "example_template.frag.glsl"; +resources::ShaderTemplate template(template_path); +``` + +After loading the template, snippets for the placeholders can be added. These are either loaded +as single files or from a directory. + +```cpp +util::Path snippets_path = shaderdir / "example_snippets"; +template.load_snippets(snippets_path); +``` + +or + +```cpp +util::Path snippets_path = shaderdir / "example_snippets"; +template.add_snippet(snippets_path / "uniform_color.snippet"); +template.add_snippet(snippets_path / "alpha.snippet"); +``` + +If snippets have been loaded for all placeholder IDs, the shader template can generate the final +shader source code as `renderer::resources::ShaderSource`. This can then be used to create the actual +`renderer::ShaderProgram` uploaded to the GPU. + +```cpp +resources::ShaderSource source = template.generate_source(); +ShaderProgram prog = = renderer->add_shader({source}); +``` + + ## Thread-safety This level might or might not be threadsafe depending on the concrete backend. The OpenGL version is, in typical GL fashion, so not-threadsafe it's almost anti-threadsafe. All code must be executed sequentially on a dedicated window thread, the same one on which the window and renderer were initially created. The plan for the Vulkan version is to make it at least independent of thread-local storage and hopefully completely threadsafe. diff --git a/libopenage/renderer/resources/shader_template.h b/libopenage/renderer/resources/shader_template.h index 210061ef0c..3e6656629e 100644 --- a/libopenage/renderer/resources/shader_template.h +++ b/libopenage/renderer/resources/shader_template.h @@ -64,6 +64,8 @@ class ShaderTemplate { size_t length; }; + /// All placeholders found in the template + /// precomputed on creation std::vector placeholders; }; } // namespace world From e4e0916f4f719cbf087010ffc365f484f7807edb Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 14 May 2025 08:59:22 +0200 Subject: [PATCH 146/152] renderer: Fix CI complaints. --- libopenage/renderer/stages/world/render_stage.cpp | 2 +- libopenage/renderer/stages/world/render_stage.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libopenage/renderer/stages/world/render_stage.cpp b/libopenage/renderer/stages/world/render_stage.cpp index d8d93279af..eec13b602b 100644 --- a/libopenage/renderer/stages/world/render_stage.cpp +++ b/libopenage/renderer/stages/world/render_stage.cpp @@ -1,4 +1,4 @@ -// Copyright 2022-2024 the openage authors. See copying.md for legal info. +// Copyright 2022-2025 the openage authors. See copying.md for legal info. #include "render_stage.h" diff --git a/libopenage/renderer/stages/world/render_stage.h b/libopenage/renderer/stages/world/render_stage.h index f1256f3b57..7a0fe02a4c 100644 --- a/libopenage/renderer/stages/world/render_stage.h +++ b/libopenage/renderer/stages/world/render_stage.h @@ -1,4 +1,4 @@ -// Copyright 2022-2024 the openage authors. See copying.md for legal info. +// Copyright 2022-2025 the openage authors. See copying.md for legal info. #pragma once From 927f547d4985cba8e172c9492273b34537571c56 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 15 Sep 2024 06:26:21 +0200 Subject: [PATCH 147/152] doc: UML changes for nyan data API v0.6.0. doc: UML changes for ApplyEffect. doc: UML changes for Task nodes. doc: UMl changes for NextCommand condition. doc: UMl changes for Ranged property. doc: UMl changes for ApplyEffect. --- doc/nyan/aoe2_nyan_tree.svg | 7602 ++++++++++++++++++----------------- doc/nyan/aoe2_nyan_tree.uxf | 6937 +++++++++++++++++--------------- 2 files changed, 7502 insertions(+), 7037 deletions(-) diff --git a/doc/nyan/aoe2_nyan_tree.svg b/doc/nyan/aoe2_nyan_tree.svg index 27360da9cf..ba6d22e8d4 100644 --- a/doc/nyan/aoe2_nyan_tree.svg +++ b/doc/nyan/aoe2_nyan_tree.svg @@ -1,7 +1,7 @@ -AbilityUsableability : AbilityMoveToTargetPathTypeClearCommandQueueNextCommandMovePopCommandQueueNextCommandIdleTaskTasknext : Nodetask : TaskTargetInRangeability : AbilityRangedmin_range : floatmax_range : floatApplyEffectMoveIdleCommandNextCommandnext : dict(Command, Node)SwitchConditionXORSwitchGateswitch : SwitchConditiondefault : NodePathTypeNextCommandcommand : CommandCommandInQueueConditionnext : NodeCommandInQueueWaitAbilityWaittime : floatEventUnit behaviour graphActivitygraph : ActivityAbilitynext : Nodeability : abstract(Ability)ability : AbilityXOREventGatenext : dict(Event, Node)XORGatenext : orderedset(Condition)default : NodeEndActivitystart : StartStartnext : NodeNodeConstructablestarting_progress : intconstruction_progress : set(Progress)TransformCarryRestockHarvestConstructAll ingame objectsare game entitiesProjectileBarracksSwordsmanRelicTreeFalseTruePatchPropertyPrioritypriority : intChainedBatchOrderedBatchUnorderedBatchChancechance : floatPrioritypriority : intBatchPropertyEffectBatcheffects : set(DiscreteEffect)properties : dict(BatchProperty, BatchProperty) = {}OwnsGameEntitygame_entity : GameEntityStateChangeActivestate_change : StateChangerResetResetProgressPropertyResistancePropertyAreaEffectrange : floatdropoff : DropoffTypeDiplomaticstances : set(DiplomaticStance)Costcost : CostEffectPropertyflacflacresistanceeffectMultipliermultiplier : floatModifierPropertyLockPoolslots : intLocklock_pools : set(LockPool)Locklock_pool : LockPoolAbilityPropertyMULTIXORXORNOTSUBSETMAXsize : intLogicGateinputs : set(LogicElement)BuySellExchangeModefee_multiplier : floatExchangeResourcesresource_a : Resourceresource_b : Resourceexchange_rate : ExchangeRateexchange_modes : set(ExchangeMode)ExchangeRatebase_price : floatprice_adjust : optional(dict(ExchangeMode, PriceMode)) = Noneprice_pool : optional(PricePool) = NoneAnyTransformPoolInternalDropSiteupdate_time : floatResourceContainerresource : Resourcemax_amount : intcarry_progress : set(Progress)ResourceStoragecontainers : set(ResourceContainer)MiscVariantElevationDifferenceHighmin_elevation_difference : optional(float) = NoneElevationDifferenceHighmin_elevation_difference : optional(float) = NoneAttributeAboveValueattribute : Attributethreshold : floatAttributeBelowPercentageattribute : Attributethreshold : floatAnimationOverlayoverlays : set(Animation)AnyAnyAnyTechTypeReplacegame_entities : set(GameEntity)Guardrange : floatPalettepalette : fileAttributeAbovePercentageattribute : Attributethreshold : floatNyanPatchStackedstack_limit : intSelectionBoxMatchToSpriteRectanglewidth : floatheight : floatMeanDistributionTypeDetectCloak (SWGB)range : floatallowed_types : set(GameEntityType)blacklisted_entities : set(GameEntity)Hitboxradius_x : floatradius_y : floatradius_z : floatProjectileHitTerrainProjectilePassThroughpass_through_range : intAttributeBelowValueattribute : Attributethreshold : floatTimertime : floatResourceSpotsDepletedonly_enabled : boolSelfAnyLiteralScopestances : set(DiplomaticStance)SUBSETMINsize : intORANDLogicElementonly_once : boolResearchablesexclude : set(ResearchableTech)Creatablesexclude : set(CreatableGameEntity)ProductionModeProductionQueuesize : intproduction_modes : set(ProductionMode)OwnStoragecontainer : EntityContainerNoStackLinearshift_x : intshift_y : intscale_factor : floatHyperbolicshift_x : intshift_y : intscale_factor : floatCalculationTypeStackedstack_limit : intcalculation_type : CalculationTypedistribution_type : DistributionTypeTerrainTypeMostHerdingLongestTimeInRangeClosestHerdingHerdableModeShadowTimeRelativeProgressIncreaseTimeRelativeProgressDecreaseTimeRelativeProgressIncreaseTimeRelativeProgressDecreaseAttributeChangeAdaptiveArrearAdvancePaymentModeResearchAttributeCostattributes : set(Attribute)researchables : set(ResearchableTech)CreationAttributeCostattributes : set(Attribute)creatables : set(CreatableGameEntity)AttributeCostamount : set(AttributeAmount)ResourceCostamount : set(ResourceAmount)Costpayment_mode : PaymentModeUnconditionalUnconditionalTimeRelativeProgressChangeTimeRelativeAttributeChangePricePoolDynamicchange_value : floatmin_price : floatmax_price : floatFixedPriceModeDepositResourcesOnProgressprogress_type : ProgressTyperesources : set(Resource)affected_types : set(GameEntityType)blacklisted_entities : set(GameEntity)Attributename : TranslatedStringabbreviation : TranslatedStringOverlayTerrainterrain_overlay : TerrainTerrainRequirementallowed_types : set(TerrainType)blacklisted_terrains : set(Terrain)Terrainterrain : TerrainStateChangestate_change : StateChangerAoE1TradeRouteexchange_resources : set(Resource)trade_amount : intProgressTypeTimeRelativeProgressChangetype : ProgressTypeFlatAttributeIncreaseFlatAttributeDecreaseTimeRelativeAttributeChangetype : AttributeChangeTypeTimeRelativeProgressChangetype : ProgressTypetotal_change_time : floatTimeRelativeAttributeIncreaseTimeRelativeAttributeDecreaseTimeRelativeAttributeChangetype : AttributeChangeTypetotal_change_time : floatignore_protection : set(ProtectingAttribute)ProgressStatusprogress_type : ProgressTypeprogress : floatRefundOnConditioncondition : set(LogicElement)refund_amount : set(ResourceAmount)AnimationOverrideoverrides : set(AnimationOverride)ExecutionSoundsounds : set(Sound)InverseLinearAdjacentTilesVariantnorth : optional(GameEntity)north_east : optional(GameEntity)east : optional(GameEntity)south_east : optional(GameEntity)south : optional(GameEntity)south_west : optional(GameEntity)west : optional(GameEntity)north_west : optional(GameEntity)Placetile_snap_distance : floatclearance_size_x : floatclearance_size_y : floatallow_rotation : boolmax_elevation_difference : intEjectPlacementModeSendToContainerTypeLureTypeDiplomaticLineOfSightdiplomatic_stance : DiplomaticStanceTerrainterrain : TerrainTerrainterrain : TerrainNormalInContainerDiscreteEffectcontainers : set(EntityContainer)ability : ApplyDiscreteEffectInContainerContinuousEffectcontainers : set(EntityContainer)ability : ApplyContinuousEffectStateChangerenable_abilities : set(Ability)disable_abilities : set(Ability)enable_modifiers : set(Modifier)disable_modifiers : set(Modifier)transform_pool : optional(TransformPool) = Nonepriority : intopenage nyan data API v0.5.0openage nyan data API v0.6.0GameEntityScopeaffected_types : set(GameEntityType)blacklisted_entities : set(GameEntity)StandardRegenerateResourceSpotrate : ResourceRateresource_spot : ResourceSpotAoE2ProjectileAmountprovider_abilities : set(ApplyDiscreteEffect)receiver_abilities : set(ApplyDiscreteEffect)change_types : set(AttributeChangeType)Orange elements:Effects/Resistances that canbe applied on other gameentitiesRevealline_of_sight : floataffected_types : set(GameEntityType)blacklisted_entities : set(GameEntity)ResearchTimeresearchables : set(ResearchableTech)StorageElementCapacitystorage_element : StorageElementDefinitionEntityContainerCapacitycontainer : EntityContainerHerdrange : floatstrength : intallowed_types : set(GameEntityType)blacklisted_entities : set(GameEntity)InstantTechResearchtech : Techcondition : set(LogicElement)ResearchResourceCostresources : set(Resource)researchables : set(ResearchableTech)CreationResourceCostresources : set(Resource)creatables : set(CreatableGameEntity)CreationTimecreatables : set(CreatableGameEntity)ReloadTimeGatheringEfficiencyresource_spot : ResourceSpotAbsoluteProjectileAmountamount : floatStrayMoveModeAttackMoveModifierScopeExpectedPositionCurrentPositionTargetModeSendToContainertype : SendToContainerTypesearch_range : floatignore_containers : set(EntityContainer)SendToContainertype : SendToContainerTypestorages : set(EntityContainer)Scopedstances : set(DiplomaticStance)scope : ModifierScopeGameEntityFormationformation : Formationsubformation : SubformationSubformationordering_priority : intFormationsubformations : set(Subformation)FlatAttributeIncreaseFlatAttributeDecreaseFlatAttributeIncreaseFlatAttributeDecreaseFlatAttributeIncreaseFlatAttributeDecreaseFlatAttributeIncreaseFlatAttributeDecreaseAoE2TradeRouteTradeRoutetrade_resource : Resourcestart_trade_post : GameEntityend_trade_post : GameEntityTechtypes : set(TechType)name : TranslatedStringdescription : TranslatedMarkupFilelong_description : TranslatedMarkupFileupdates : orderedset(Patch)FallbackLinearNoDropoffDropoffTypeDiplomaticstances : set(DiplomaticStance)AnimationOverrideability : AnimatedAbilityanimations : set(Animation)priority : intCostcost : CostEntityContainerallowed_types : set(GameEntityType)blacklisted_entities : set(GameEntity)storage_element_defs : set(StorageElementDefinition)slots : intcarry_progress : set(Progress)Visibilityvisible_in_fog : boolTurnturn_speed : floatLanguageietf_string : textLanguageSoundPairlanguage : Languagesound : SoundLanguageMarkupPairlanguage : Languagemarkup_file : fileLanguageTextPairlanguage : Languagestring : textLuretype : LureTypeLuretype : LureTypedestination : set(GameEntity)min_distance_to_destination : floatElevationDifferenceLowmin_elevation_difference : optional(float) = NoneSelfDiplomaticstances : set(DiplomaticStance)DiplomaticStanceExitContainerallowed_containers : set(EntityContainer)EnterContainerallowed_containers : set(EntityContainer)allowed_types : set(GameEntityType)blacklisted_entities : set(GameEntity)RangedDiscreteEffectmin_range : intmax_range : intHuntConvertTypeConvertSelfDestructConvert (Ranged)RangedContinuousEffectmin_range : intmax_range : intSelfDestructMakeHarvestableresource_spot : ResourceSpotresist_condition : set(LogicElement)MakeHarvestableresource_spot : ResourceSpotGatherauto_resume : boolresume_search_range : floattargets : set(ResourceSpot)gather_rate : ResourceRatecontainer : ResourceContainerRepairMonkHealMonkHeal (Ranged)ApplyContinuousEffecteffects : set(ContinuousEffect)application_delay : floatallowed_types : set(GameEntityType)blacklisted_entities : set(GameEntity)ApplyDiscreteEffectbatches : set(EffectBatch)reload_time : floatapplication_delay : floatallowed_types : set(GameEntityType)blacklisted_entities : set(GameEntity)FlatAttributeChangetype : AttributeChangeTypeblock_rate : AttributeRateContinuousResistanceAttributeRatetype : Attributerate : floatResourceRatetype : Resourcerate : floatDiscreteResistanceDiscreteEffectFlatAttributeChangeignore_protection : set(ProtectingAttribute)ContinuousEffectAoE2Convertprotection_round_recharge_time : floatAoE2Convertskip_guaranteed_rounds : intskip_protected_rounds : intConvertchance_resist : floatConvertcost_fail : optional(Cost) = NoneAttributeChangeTypeFlatAttributeChangetype : AttributeChangeTypeblock_value : AttributeAmountFlatAttributeChangetype : AttributeChangeTypemin_change_value : optional(AttributeAmount) = Nonemax_change_value : optional(AttributeAmount) = Nonechange_value : AttributeAmountignore_protection : set(ProtectingAttribute)Effectors' sideResistors' sideEffectproperties : dict(EffectProperty, EffectProperty) = {}Resistanceproperties : dict(ResistanceProperty, ResistanceProperty) = {}HealthGameEntityTypeAttributeAmounttype : Attributeamount : intAccuracyaccuracy : floataccuracy_dispersion : floatdispersion_dropoff : DropOffTypetarget_types : set(GameEntityType)blacklisted_entities : set(GameEntity)Cloak (SWGB)interrupted_by : set(Ability)interrupt_cooldown : floatLineOfSightrange : floatFaithShield (SWGB)ProtectingAttributeprotects : AttributeAttributeSettingstarting_value : intTerrainOverlayterrain_overlay : TerrainAnimatedoverrides : set(AnimationOverride)Terrainsprite : fileCreatableGameEntityplacement_modes : set(PlacementMode)UseContingentamount : set(ResourceAmount)ProvideContingentamount : set(ResourceAmount)ResourceContingentmin_amount : intmax_amount : intLiteralscope : LiteralScopeProjectileunignore_entities : set(GameEntity)AttributeChangeTrackerattribute : Attributechange_progress : set(Progress)Collisionhitbox : HitboxNamedlong_description : TranslatedMarkupFileDropResourcescontainers : set(ResourceContainer)search_range : floatallowed_types : set(GameEntityType)blacklisted_entities : set(GameEntity)Herdableadjacent_discover_range : floatmode : HerdableModeStopPathablehitbox : Hitboxpath_costs : dict(PathType, int)Foundationfoundation_terrain : TerrainPerspectiveVariantangle : intRestockamount : intPassiveStandGroundDefensiveAggressiveTradePosttrade_routes : set(TradeRoute)Followrange : floatPatrolGameEntityStancetype_preference : orderedset(GameEntityType)GameEntityStancestances: set(GameEntityStance)SendBackToTaskblacklisted_entities : set(GameEntity)TransferStoragestorage_element : GameEntitysource_container : EntityContainertarget_container : EntityContainerGameEntityProgressstatus : ProgressStatusTechResearchedtech : TechVariantchanges : orderedset(Patch)priority : intActiveTransformTotransform_progress : set(Progress)Selectableselection_box : SelectionBoxRallyPointAoE2 specific objectsimplementation)implementation)Basic nyan API objectsElevationDifferenceLowmin_elevation_difference : optional(float) = NoneFlyoverrelative_angle : floatflyover_types : set(GameEntityType)blacklisted_entities : set(GameEntity)Animationsprite : fileAnimationOverridesanimation.If min_projectiles is greater thanthe number of Projectiles inprojectiles, the last projectilein the orderedset should be usedbut AoM had more.Stores what happens aftera percentage ofconstruction, damage,transformation, etc. isreachedProgressright_boundary : floatgame_setup patches the uniquefeatures into the objects(graphics, techs, boni, abilities,etc.)RemoveStoragestorage_elements : set(GameEntity)CollectStoragestorage_elements : set(GameEntity)StorageElementDefinitionstorage_element : GameEntityelements_per_slot : intconflicts : set(StorageElementDefinition)state_change : optional(StateChanger) = NoneStoragecontainer : EntityContainerempty_condition : set(LogicElement)RandomVariantchance_share : floatResearchresearchables : set(ResearchableTech)Tradetrade_routes : set(TradeRoute)container : ResourceContainerCreatecreatables : set(CreatableGameEntity)RelicBonusGatheringRateresource_spot : ResourceSpotMoveSpeedAttributeSettingsValueattribute : AttributeFeitoriaBonusFoodAmountWoodAmountStoneAmountGoldAmountResourceAmounttype : Resourceamount : intContinuousResourcerates : set(ResourceRate)patched.IdlePlayerSetupgame_setup : orderedset(Patch)Despawnstate_change : optional(StateChanger) = NoneTauntactivation_message : textdisplay_message : TranslatedStringsound : SoundLiveattributes : set(AttributeSetting)Harvestableharvestable_by_default : boolPassiveTransformTotransform_progress : set(Progress)Cheatactivation_message : textchanges : orderedset(Patch)Flyheight : floatResistanceresistances : set(Resistance)ShootProjectilemax_projectiles : intmin_range : intmax_range : intreload_time : floatspawn_delay : floatprojectile_delay : floatrequire_turning : boolmanual_aiming_allowed : boolspawning_area_offset_x : floatspawning_area_offset_y : floatspawning_area_offset_z : floatspawning_area_width : floatspawning_area_height : floatspawning_area_randomness : floatallowed_types : set(GameEntityType)blacklisted_entities : set(GameEntity)RegenerateAttributerate : AttributeRateFormationformations : set(GameEntityFormation)Movepath_type : PathTypeCommandSoundsounds : set(Sound)Animatedanimations : set(Animation)Abilityproperties : dict(AbilityProperty, AbilityProperty) = {}Modpriority : intpatches : orderedset(Patch)Patchproperties : dict(PatchProperty, PatchProperty) = {}patch : NyanPatchModifierproperties : dict(ModifierProperty, ModifierProperty) = {}Soundplay_delay : floatsounds : orderedset(file)TerrainAmbientobject : GameEntitymax_density : intTerrainpath_costs : dict(PathType, int)ResearchableTechcondition : set(LogicElement)TranslatedSoundtranslations : set(LanguageSoundPair)TranslatedMarkupFiletranslations : set(LanguageMarkupPair)TranslatedObjectTranslatedStringtranslations : set(LanguageTextPair)Resourcename : TranslatedStringmax_storage : intResourceSpotdecay_rate : floatDropSiteaccepts_from : set(ResourceContainer)GameEntityvariants : set(Variant)Object - 10 + 9 UMLClass - 1190 - 3600 - 100 - 60 + 1080 + 3249 + 90 + 54 *Object* @@ -41,10 +41,10 @@ bg=red UMLClass - 410 - 3580 - 300 - 120 + 378 + 3231 + 270 + 108 *GameEntity* @@ -59,10 +59,10 @@ variants : set(Variant) UMLClass - 4930 - 3130 - 290 - 80 + 4446 + 2826 + 261 + 72 *DropSite* bg=green @@ -74,10 +74,10 @@ accepts_from : set(ResourceContainer) Relation - 700 - 3620 - 510 - 30 + 639 + 3267 + 459 + 27 lt=<<- 490.0;10.0;10.0;10.0 @@ -85,10 +85,10 @@ accepts_from : set(ResourceContainer) UMLClass - 850 - 3120 - 300 - 120 + 774 + 2817 + 270 + 108 *ResourceSpot* @@ -103,10 +103,10 @@ decay_rate : float UMLClass - 910 - 3300 - 180 - 80 + 828 + 2979 + 162 + 72 *Resource* bg=pink @@ -119,10 +119,10 @@ max_storage : int UMLClass - 0 - 2670 - 270 - 80 + 9 + 2412 + 243 + 72 *TranslatedString* bg=pink @@ -134,10 +134,10 @@ translations : set(LanguageTextPair) UMLClass - 370 - 2830 - 150 - 60 + 342 + 2556 + 135 + 54 *TranslatedObject* @@ -147,10 +147,10 @@ bg=pink UMLClass - 290 - 2670 - 290 - 80 + 270 + 2412 + 261 + 72 *TranslatedMarkupFile* bg=pink @@ -162,10 +162,10 @@ translations : set(LanguageMarkupPair) UMLClass - 600 - 2670 - 280 - 80 + 549 + 2412 + 252 + 72 *TranslatedSound* bg=pink @@ -177,10 +177,10 @@ translations : set(LanguageSoundPair) Relation - 430 - 2740 - 30 - 110 + 396 + 2475 + 27 + 99 lt=<<- 10.0;90.0;10.0;10.0 @@ -188,10 +188,10 @@ translations : set(LanguageSoundPair) Relation - 120 - 2740 - 340 - 60 + 117 + 2475 + 306 + 54 lt=- 320.0;40.0;10.0;40.0;10.0;10.0 @@ -199,10 +199,10 @@ translations : set(LanguageSoundPair) Relation - 430 - 2740 - 330 - 60 + 396 + 2475 + 297 + 54 lt=- 10.0;40.0;310.0;40.0;310.0;10.0 @@ -210,10 +210,10 @@ translations : set(LanguageSoundPair) UMLClass - 3260 - 2420 - 320 - 130 + 2943 + 2187 + 288 + 117 *ResearchableTech* bg=pink @@ -229,10 +229,10 @@ condition : set(LogicElement) UMLClass - 1330 - 3710 - 320 - 150 + 1206 + 3348 + 288 + 135 *Terrain* bg=pink @@ -249,10 +249,10 @@ path_costs : dict(PathType, int) UMLClass - 1530 - 3900 - 190 - 80 + 1386 + 3519 + 171 + 72 *TerrainAmbient* bg=pink @@ -265,10 +265,10 @@ max_density : int UMLClass - 1820 - 3250 - 190 - 80 + 1647 + 2934 + 171 + 72 *Sound* bg=pink @@ -281,10 +281,10 @@ sounds : orderedset(file) UMLClass - 1600 - 1250 - 360 - 80 + 1449 + 1134 + 324 + 72 *Modifier* bg=pink @@ -296,10 +296,10 @@ properties : dict(ModifierProperty, ModifierProperty) = {} UMLClass - 1330 - 3530 - 340 - 80 + 1206 + 3186 + 306 + 72 *Patch* bg=pink @@ -312,10 +312,10 @@ patch : NyanPatch UMLClass - 1820 - 3510 - 210 - 80 + 1647 + 3168 + 189 + 72 *Mod* bg=pink @@ -328,10 +328,10 @@ patches : orderedset(Patch) Relation - 1280 - 3620 - 2550 - 30 + 1161 + 3267 + 2295 + 27 lt=<<- 10.0;10.0;2530.0;10.0 @@ -339,10 +339,10 @@ patches : orderedset(Patch) UMLClass - 3810 - 3600 - 340 - 90 + 3438 + 3249 + 306 + 81 *Ability* bg=pink @@ -354,10 +354,10 @@ properties : dict(AbilityProperty, AbilityProperty) = {} UMLClass - 3620 - 4010 - 180 - 80 + 3267 + 3618 + 162 + 72 *Animated* bg=pink @@ -369,10 +369,10 @@ animations : set(Animation) UMLClass - 3620 - 3920 - 180 - 80 + 3267 + 3537 + 162 + 72 *CommandSound* bg=pink @@ -385,10 +385,10 @@ sounds : set(Sound) UMLClass - 4310 - 3840 - 180 - 100 + 3888 + 3465 + 162 + 90 *Move* bg=green @@ -402,10 +402,10 @@ path_type : PathType UMLClass - 4070 - 4280 - 290 - 80 + 3672 + 3861 + 261 + 72 *Formation* bg=green @@ -417,10 +417,10 @@ formations : set(GameEntityFormation) UMLClass - 4860 - 4320 - 180 - 80 + 4383 + 3897 + 162 + 72 *RegenerateAttribute* bg=green @@ -432,10 +432,10 @@ rate : AttributeRate UMLClass - 5960 - 1950 - 320 - 340 + 5373 + 1764 + 288 + 279 *ShootProjectile* bg=green @@ -444,8 +444,6 @@ bg=green projectiles : orderedset(GameEntity) min_projectiles : int max_projectiles : int -min_range : int -max_range : int // time until the ability can be used again reload_time : float // time to wait between the start of the ability @@ -469,10 +467,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 4830 - 4520 - 210 - 80 + 4356 + 4077 + 189 + 72 *Resistance* bg=green @@ -484,10 +482,10 @@ resistances : set(Resistance) UMLClass - 4090 - 3840 - 160 - 80 + 3690 + 3465 + 144 + 72 *Fly* bg=green @@ -499,10 +497,10 @@ height : float Relation - 510 - 2850 - 750 - 770 + 468 + 2574 + 675 + 693 lt=<<- 730.0;750.0;730.0;10.0;10.0;10.0 @@ -510,10 +508,10 @@ height : float UMLClass - 2110 - 3060 - 190 - 80 + 1908 + 2763 + 171 + 72 *Cheat* bg=pink @@ -526,10 +524,10 @@ changes : orderedset(Patch) Relation - 1080 - 3340 - 180 - 30 + 981 + 3015 + 162 + 27 lt=- 160.0;10.0;10.0;10.0 @@ -537,10 +535,10 @@ changes : orderedset(Patch) Relation - 990 - 3230 - 30 - 90 + 900 + 2916 + 27 + 81 lt=<. 10.0;70.0;10.0;10.0 @@ -548,10 +546,10 @@ changes : orderedset(Patch) Relation - 1140 - 3150 - 120 - 30 + 1035 + 2844 + 108 + 27 lt=- 10.0;10.0;100.0;10.0 @@ -559,10 +557,10 @@ changes : orderedset(Patch) Relation - 1580 - 3850 - 30 - 70 + 1431 + 3474 + 27 + 63 lt=<. 10.0;50.0;10.0;10.0 @@ -570,10 +568,10 @@ changes : orderedset(Patch) Relation - 2040 - 3180 - 90 - 30 + 1845 + 2871 + 81 + 27 lt=- 10.0;10.0;70.0;10.0 @@ -581,10 +579,10 @@ changes : orderedset(Patch) Relation - 1470 - 3620 - 30 - 110 + 1332 + 3267 + 27 + 99 lt=- 10.0;10.0;10.0;90.0 @@ -592,10 +590,10 @@ changes : orderedset(Patch) UMLClass - 6560 - 3590 - 290 - 110 + 5913 + 3240 + 261 + 99 *PassiveTransformTo* bg=green @@ -610,10 +608,10 @@ transform_progress : set(Progress) UMLClass - 4570 - 2890 - 300 - 140 + 4122 + 2610 + 270 + 126 *Harvestable* bg=green @@ -629,10 +627,10 @@ harvestable_by_default : bool UMLClass - 4950 - 4630 - 240 - 80 + 4464 + 4176 + 216 + 72 *Live* bg=green @@ -644,10 +642,10 @@ attributes : set(AttributeSetting) UMLClass - 2110 - 3150 - 250 - 100 + 1908 + 2844 + 225 + 90 *Taunt* bg=pink @@ -661,10 +659,10 @@ sound : Sound Relation - 1910 - 3580 - 30 - 70 + 1728 + 3231 + 27 + 63 lt=- 10.0;10.0;10.0;50.0 @@ -672,10 +670,10 @@ sound : Sound Relation - 1770 - 1320 - 30 - 2330 + 1602 + 1197 + 27 + 2097 lt=- 10.0;10.0;10.0;2310.0 @@ -683,10 +681,10 @@ sound : Sound Relation - 1470 - 3600 - 30 - 50 + 1332 + 3249 + 27 + 45 lt=- 10.0;10.0;10.0;30.0 @@ -694,10 +692,10 @@ sound : Sound Relation - 2040 - 3090 - 90 - 30 + 1845 + 2790 + 81 + 27 lt=- 70.0;10.0;10.0;10.0 @@ -705,10 +703,10 @@ sound : Sound UMLClass - 6420 - 3740 - 310 - 110 + 5787 + 3375 + 279 + 99 *Despawn* bg=green @@ -723,10 +721,10 @@ state_change : optional(StateChanger) = None UMLClass - 630 - 4380 - 310 - 160 + 576 + 3951 + 279 + 144 *PlayerSetup* bg=pink @@ -744,10 +742,10 @@ game_setup : orderedset(Patch) Relation - 770 - 3620 - 30 - 780 + 702 + 3267 + 27 + 702 lt=- 10.0;760.0;10.0;10.0 @@ -755,10 +753,10 @@ game_setup : orderedset(Patch) Relation - 1720 - 3620 - 30 - 420 + 1557 + 3267 + 27 + 378 lt=- 10.0;10.0;10.0;400.0 @@ -766,10 +764,10 @@ game_setup : orderedset(Patch) UMLClass - 6420 - 3670 - 120 - 60 + 5787 + 3312 + 108 + 54 *Idle* @@ -779,10 +777,10 @@ bg=green UMLNote - 1550 - 1350 - 210 - 160 + 1404 + 1224 + 189 + 144 Modifier should only be used in cases where Patches don't @@ -798,10 +796,10 @@ bg=blue UMLClass - 1660 - 1100 - 220 - 80 + 1503 + 999 + 198 + 72 *ContinuousResource* bg=yellow @@ -813,10 +811,10 @@ rates : set(ResourceRate) UMLClass - 910 - 3430 - 180 - 80 + 828 + 3096 + 162 + 72 *ResourceAmount* bg=pink @@ -829,10 +827,10 @@ amount : int UMLClass - 530 - 3410 - 120 - 60 + 486 + 3078 + 108 + 54 *GoldAmount* @@ -841,10 +839,10 @@ amount : int UMLClass - 530 - 3480 - 120 - 60 + 486 + 3141 + 108 + 54 *StoneAmount* @@ -853,10 +851,10 @@ amount : int UMLClass - 530 - 3340 - 120 - 60 + 486 + 3015 + 108 + 54 *WoodAmount* @@ -865,10 +863,10 @@ amount : int UMLClass - 530 - 3270 - 120 - 60 + 486 + 2952 + 108 + 54 *FoodAmount* @@ -877,10 +875,10 @@ amount : int Relation - 1080 - 3460 - 180 - 30 + 981 + 3123 + 162 + 27 lt=- 160.0;10.0;10.0;10.0 @@ -888,10 +886,10 @@ amount : int Relation - 990 - 3370 - 30 - 80 + 900 + 3042 + 27 + 72 lt=<. 10.0;10.0;10.0;60.0 @@ -899,10 +897,10 @@ amount : int Relation - 660 - 3460 - 270 - 30 + 603 + 3123 + 243 + 27 lt=<<- 250.0;10.0;10.0;10.0 @@ -910,10 +908,10 @@ amount : int Relation - 640 - 3290 - 50 - 100 + 585 + 2970 + 45 + 90 lt=- 10.0;10.0;30.0;10.0;30.0;80.0 @@ -921,10 +919,10 @@ amount : int Relation - 640 - 3360 - 50 - 100 + 585 + 3033 + 45 + 90 lt=- 10.0;10.0;30.0;10.0;30.0;80.0 @@ -932,10 +930,10 @@ amount : int Relation - 640 - 3450 - 50 - 80 + 585 + 3114 + 45 + 72 lt=- 10.0;60.0;30.0;60.0;30.0;10.0 @@ -943,10 +941,10 @@ amount : int Relation - 640 - 3430 - 50 - 50 + 585 + 3096 + 45 + 45 lt=- 10.0;10.0;30.0;10.0;30.0;30.0 @@ -954,10 +952,10 @@ amount : int Relation - 1770 - 1170 - 30 - 100 + 1602 + 1062 + 27 + 90 lt=<<- 10.0;80.0;10.0;10.0 @@ -965,10 +963,10 @@ amount : int UMLClass - 1630 - 980 - 120 - 60 + 1476 + 891 + 108 + 54 *FeitoriaBonus* @@ -977,10 +975,10 @@ amount : int Relation - 1680 - 1030 - 120 - 90 + 1521 + 936 + 108 + 81 lt=<<- 100.0;70.0;100.0;40.0;10.0;40.0;10.0;10.0 @@ -988,10 +986,10 @@ amount : int UMLClass - 970 - 1010 - 190 - 80 + 882 + 918 + 171 + 72 *AttributeSettingsValue* bg=yellow @@ -1003,10 +1001,10 @@ attribute : Attribute UMLClass - 1020 - 940 - 140 - 60 + 927 + 855 + 126 + 54 *MoveSpeed* @@ -1016,10 +1014,10 @@ bg=yellow UMLClass - 940 - 690 - 220 - 80 + 855 + 630 + 198 + 72 *GatheringRate* bg=yellow @@ -1031,10 +1029,10 @@ resource_spot : ResourceSpot Relation - 1200 - 480 - 600 - 760 + 1089 + 441 + 540 + 684 lt=- 580.0;740.0;10.0;740.0;10.0;10.0 @@ -1042,10 +1040,10 @@ resource_spot : ResourceSpot Relation - 1150 - 960 - 80 - 30 + 1044 + 873 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -1053,10 +1051,10 @@ resource_spot : ResourceSpot Relation - 1150 - 720 - 80 - 30 + 1044 + 657 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -1064,10 +1062,10 @@ resource_spot : ResourceSpot UMLClass - 1810 - 980 - 120 - 60 + 1638 + 891 + 108 + 54 *RelicBonus* @@ -1076,10 +1074,10 @@ resource_spot : ResourceSpot Relation - 1770 - 1030 - 120 - 60 + 1602 + 936 + 108 + 54 lt=- 10.0;40.0;100.0;40.0;100.0;10.0 @@ -1087,10 +1085,10 @@ resource_spot : ResourceSpot UMLClass - 3610 - 2580 - 280 - 80 + 3258 + 2331 + 252 + 72 *Create* bg=green @@ -1102,10 +1100,10 @@ creatables : set(CreatableGameEntity) UMLClass - 4370 - 2670 - 230 - 80 + 3942 + 2412 + 207 + 72 *Trade* bg=green @@ -1119,10 +1117,10 @@ container : ResourceContainer UMLClass - 3610 - 2420 - 280 - 80 + 3258 + 2187 + 252 + 72 *Research* bg=green @@ -1134,10 +1132,10 @@ researchables : set(ResearchableTech) UMLClass - 370 - 3770 - 150 - 80 + 342 + 3402 + 135 + 72 *RandomVariant* @@ -1149,10 +1147,10 @@ chance_share : float Relation - 750 - 3800 - 50 - 30 + 684 + 3429 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -1160,10 +1158,10 @@ chance_share : float UMLClass - 4620 - 1640 - 260 - 90 + 4167 + 1485 + 234 + 81 *Storage* bg=green @@ -1177,10 +1175,10 @@ empty_condition : set(LogicElement) UMLClass - 4980 - 1480 - 310 - 120 + 4491 + 1341 + 279 + 108 *StorageElementDefinition* bg=pink @@ -1195,10 +1193,10 @@ state_change : optional(StateChanger) = None Relation - 4740 - 1600 - 30 - 60 + 4275 + 1449 + 27 + 54 lt=<. 10.0;10.0;10.0;40.0 @@ -1206,10 +1204,10 @@ state_change : optional(StateChanger) = None UMLClass - 4420 - 1970 - 300 - 90 + 3987 + 1782 + 270 + 81 *CollectStorage* bg=green @@ -1222,10 +1220,10 @@ storage_elements : set(GameEntity) UMLClass - 4420 - 1870 - 300 - 90 + 3987 + 1692 + 270 + 81 *RemoveStorage* bg=green @@ -1238,10 +1236,10 @@ storage_elements : set(GameEntity) UMLNote - 720 - 4550 - 220 - 80 + 657 + 4104 + 198 + 72 game_setup patches the unique features into the objects @@ -1253,10 +1251,10 @@ bg=blue UMLClass - 2030 - 4780 - 370 - 110 + 1836 + 4311 + 333 + 99 *Progress* bg=pink @@ -1272,10 +1270,10 @@ right_boundary : float Relation - 2140 - 3620 - 30 - 1180 + 1935 + 3267 + 27 + 1062 lt=- 10.0;10.0;10.0;1160.0 @@ -1283,10 +1281,10 @@ right_boundary : float UMLNote - 2160 - 4680 - 190 - 90 + 1953 + 4221 + 171 + 81 Stores what happens after a percentage of @@ -1299,10 +1297,10 @@ bg=blue UMLNote - 2580 - 4220 - 200 - 90 + 2331 + 3807 + 180 + 81 In AoE2 there is only one HarvestProgress State @@ -1314,10 +1312,10 @@ bg=blue UMLNote - 6290 - 2050 - 240 - 90 + 5670 + 1854 + 216 + 81 If min_projectiles is greater than the number of Projectiles in @@ -1329,10 +1327,10 @@ bg=blue UMLNote - 1770 - 3760 - 130 - 120 + 1602 + 3393 + 117 + 108 Abilities and StorageElements @@ -1347,10 +1345,10 @@ bg=blue UMLNote - 4930 - 3460 - 210 - 100 + 4446 + 3123 + 189 + 90 Villager Gather abilities can override the graphics of @@ -1363,10 +1361,10 @@ bg=blue UMLClass - 2110 - 3330 - 190 - 80 + 1908 + 3006 + 171 + 72 *Animation* bg=pink @@ -1378,10 +1376,10 @@ sprite : file Relation - 2000 - 3280 - 70 - 30 + 1809 + 2961 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -1389,10 +1387,10 @@ sprite : file UMLClass - 440 - 240 - 250 - 90 + 405 + 225 + 225 + 81 *Flyover* bg=yellow @@ -1406,10 +1404,10 @@ blacklisted_entities : set(GameEntity) Relation - 710 - 320 - 370 - 30 + 648 + 297 + 333 + 27 lt=- 350.0;10.0;10.0;10.0 @@ -1417,10 +1415,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 370 - 340 - 320 - 70 + 342 + 315 + 288 + 63 *ElevationDifferenceLow* bg=yellow @@ -1432,10 +1430,10 @@ min_elevation_difference : optional(float) = None Relation - 680 - 270 - 60 - 30 + 621 + 252 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -1443,10 +1441,10 @@ min_elevation_difference : optional(float) = None Relation - 680 - 370 - 60 - 30 + 621 + 342 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -1454,10 +1452,10 @@ min_elevation_difference : optional(float) = None UMLNote - 0 - 100 - 190 - 70 + 9 + 99 + 171 + 63 Pink elements: @@ -1469,10 +1467,10 @@ bg=pink UMLNote - 0 - 180 - 190 - 70 + 9 + 171 + 171 + 63 Green elements: @@ -1484,10 +1482,10 @@ bg=green UMLNote - 0 - 260 - 190 - 70 + 9 + 243 + 171 + 63 Yellow elements: @@ -1499,10 +1497,10 @@ bg=yellow UMLNote - 0 - 440 - 190 - 70 + 9 + 405 + 171 + 63 White elements: @@ -1512,10 +1510,10 @@ AoE2 specific objects UMLClass - 3770 - 2730 - 120 - 60 + 3402 + 2466 + 108 + 54 *RallyPoint* @@ -1525,10 +1523,10 @@ bg=green UMLClass - 6130 - 3860 - 230 - 90 + 5526 + 3483 + 207 + 81 *Selectable* bg=green @@ -1540,10 +1538,10 @@ selection_box : SelectionBox UMLClass - 3770 - 4490 - 330 - 100 + 3402 + 4050 + 297 + 90 *ActiveTransformTo* bg=green @@ -1557,10 +1555,10 @@ transform_progress : set(Progress) UMLClass - 550 - 3770 - 210 - 80 + 504 + 3402 + 189 + 72 *Variant* bg=pink @@ -1573,10 +1571,10 @@ priority : int Relation - 510 - 3800 - 60 - 30 + 468 + 3429 + 54 + 27 lt=<<- 40.0;10.0;10.0;10.0 @@ -1584,10 +1582,10 @@ priority : int UMLClass - 1570 - 4260 - 210 - 80 + 1422 + 3843 + 189 + 72 *TechResearched* bg=pink @@ -1599,10 +1597,10 @@ tech : Tech UMLClass - 1550 - 4350 - 230 - 90 + 1404 + 3924 + 207 + 81 *GameEntityProgress* bg=pink @@ -1615,10 +1613,10 @@ status : ProgressStatus Relation - 1790 - 4220 - 30 - 900 + 1620 + 3807 + 27 + 810 lt=<<- 10.0;10.0;10.0;880.0 @@ -1626,10 +1624,10 @@ status : ProgressStatus UMLClass - 4460 - 1760 - 260 - 100 + 4023 + 1593 + 234 + 90 *TransferStorage* bg=green @@ -1643,10 +1641,10 @@ target_container : EntityContainer Relation - 1720 - 3680 - 70 - 30 + 1557 + 3321 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -1654,10 +1652,10 @@ target_container : EntityContainer UMLClass - 4780 - 1970 - 320 - 90 + 4311 + 1782 + 288 + 81 *SendBackToTask* bg=green @@ -1670,10 +1668,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 4070 - 4370 - 250 - 80 + 3672 + 3942 + 225 + 72 *GameEntityStance* bg=green @@ -1685,10 +1683,10 @@ stances: set(GameEntityStance) UMLClass - 4400 - 4380 - 340 - 90 + 3969 + 3951 + 306 + 81 *GameEntityStance* bg=pink @@ -1702,10 +1700,10 @@ type_preference : orderedset(GameEntityType) Relation - 4310 - 4400 - 110 - 30 + 3888 + 3969 + 99 + 27 lt=<. 90.0;10.0;10.0;10.0 @@ -1713,10 +1711,10 @@ type_preference : orderedset(GameEntityType) UMLClass - 4600 - 3840 - 110 - 60 + 4149 + 3465 + 99 + 54 *Patrol* @@ -1726,10 +1724,10 @@ bg=pink UMLClass - 4600 - 3910 - 160 - 80 + 4149 + 3528 + 144 + 72 *Follow* bg=pink @@ -1741,10 +1739,10 @@ range : float Relation - 4550 - 3800 - 30 - 410 + 4104 + 3429 + 27 + 369 lt=<<- 10.0;10.0;10.0;390.0 @@ -1752,10 +1750,10 @@ range : float Relation - 4550 - 3940 - 70 - 30 + 4104 + 3555 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -1763,10 +1761,10 @@ range : float UMLClass - 4370 - 2580 - 290 - 80 + 3942 + 2331 + 261 + 72 *TradePost* bg=green @@ -1778,10 +1776,10 @@ trade_routes : set(TradeRoute) UMLClass - 4360 - 4490 - 100 - 60 + 3933 + 4050 + 90 + 54 *Aggressive* @@ -1791,10 +1789,10 @@ bg=pink UMLClass - 4360 - 4560 - 100 - 60 + 3933 + 4113 + 90 + 54 *Defensive* @@ -1804,10 +1802,10 @@ bg=pink UMLClass - 4340 - 4630 - 120 - 60 + 3915 + 4176 + 108 + 54 *StandGround* @@ -1817,10 +1815,10 @@ bg=pink UMLClass - 4360 - 4700 - 100 - 60 + 3933 + 4239 + 90 + 54 *Passive* @@ -1830,10 +1828,10 @@ bg=pink Relation - 4450 - 4580 - 60 - 30 + 4014 + 4131 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -1841,10 +1839,10 @@ bg=pink Relation - 4480 - 4460 - 30 - 290 + 4041 + 4023 + 27 + 261 lt=<<- 10.0;10.0;10.0;270.0 @@ -1852,10 +1850,10 @@ bg=pink Relation - 4450 - 4650 - 60 - 30 + 4014 + 4194 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -1863,10 +1861,10 @@ bg=pink Relation - 4450 - 4720 - 60 - 30 + 4014 + 4257 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -1874,10 +1872,10 @@ bg=pink UMLClass - 4600 - 3040 - 270 - 140 + 4149 + 2745 + 243 + 126 *Restock* bg=green @@ -1894,10 +1892,10 @@ amount : int UMLClass - 370 - 3860 - 170 - 80 + 342 + 3483 + 153 + 72 *PerspectiveVariant* @@ -1909,10 +1907,10 @@ angle : int Relation - 530 - 3840 - 140 - 80 + 486 + 3465 + 126 + 72 lt=<<- 120.0;10.0;120.0;60.0;10.0;60.0 @@ -1920,10 +1918,10 @@ angle : int UMLClass - 6010 - 4760 - 220 - 80 + 5418 + 4293 + 198 + 72 *Foundation* bg=green @@ -1935,10 +1933,10 @@ foundation_terrain : Terrain UMLClass - 5100 - 4320 - 220 - 80 + 4599 + 3897 + 198 + 72 *Pathable* bg=green @@ -1951,10 +1949,10 @@ path_costs : dict(PathType, int) UMLClass - 6420 - 3530 - 120 - 60 + 5787 + 3186 + 108 + 54 // Returns unit to Idle state @@ -1965,10 +1963,10 @@ bg=green UMLClass - 6420 - 3930 - 320 - 100 + 5787 + 3546 + 288 + 90 *Herdable* bg=green @@ -1981,10 +1979,10 @@ mode : HerdableMode UMLClass - 4930 - 3220 - 260 - 110 + 4446 + 2907 + 234 + 99 *DropResources* bg=green @@ -2000,10 +1998,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 4750 - 4210 - 290 - 100 + 4284 + 3798 + 261 + 90 *Named* bg=green @@ -2017,10 +2015,10 @@ long_description : TranslatedMarkupFile UMLClass - 5100 - 4420 - 220 - 80 + 4599 + 3987 + 198 + 72 *Collision* bg=green @@ -2032,10 +2030,10 @@ hitbox : Hitbox UMLClass - 5700 - 4760 - 250 - 80 + 5139 + 4293 + 225 + 72 *AttributeChangeTracker* bg=green @@ -2048,10 +2046,10 @@ change_progress : set(Progress) UMLClass - 5960 - 1780 - 320 - 150 + 5373 + 1611 + 288 + 135 *Projectile* bg=green @@ -2070,10 +2068,10 @@ unignore_entities : set(GameEntity) UMLClass - 1610 - 4150 - 210 - 80 + 1458 + 3744 + 189 + 72 *Literal* bg=pink @@ -2085,10 +2083,10 @@ scope : LiteralScope UMLClass - 700 - 3300 - 180 - 80 + 639 + 2979 + 162 + 72 *ResourceContingent* bg=pink @@ -2101,10 +2099,10 @@ max_amount : int Relation - 870 - 3330 - 60 - 30 + 792 + 3006 + 54 + 27 lt=<<- 40.0;10.0;10.0;10.0 @@ -2112,10 +2110,10 @@ max_amount : int UMLClass - 4930 - 2950 - 250 - 80 + 4446 + 2664 + 225 + 72 *ProvideContingent* bg=green @@ -2127,10 +2125,10 @@ amount : set(ResourceAmount) UMLClass - 4930 - 3040 - 250 - 80 + 4446 + 2745 + 225 + 72 *UseContingent* bg=green @@ -2142,10 +2140,10 @@ amount : set(ResourceAmount) UMLClass - 3260 - 2560 - 320 - 160 + 2943 + 2313 + 288 + 144 *CreatableGameEntity* bg=pink @@ -2164,10 +2162,10 @@ placement_modes : set(PlacementMode) Relation - 3570 - 2610 - 60 - 30 + 3222 + 2358 + 54 + 27 lt=<. 10.0;10.0;40.0;10.0 @@ -2175,10 +2173,10 @@ placement_modes : set(PlacementMode) UMLClass - 2110 - 3510 - 190 - 80 + 1908 + 3168 + 171 + 72 *Terrain* bg=pink @@ -2190,10 +2188,10 @@ sprite : file Relation - 2040 - 3090 - 30 - 560 + 1845 + 2790 + 27 + 504 lt=- 10.0;10.0;10.0;540.0 @@ -2201,10 +2199,10 @@ sprite : file Relation - 2040 - 3360 - 90 - 30 + 1845 + 3033 + 81 + 27 lt=- 70.0;10.0;10.0;10.0 @@ -2212,10 +2210,10 @@ sprite : file UMLClass - 2460 - 4660 - 230 - 80 + 2223 + 4203 + 207 + 72 *Animated* bg=pink @@ -2228,10 +2226,10 @@ overrides : set(AnimationOverride) UMLClass - 2460 - 4840 - 190 - 80 + 2223 + 4365 + 171 + 72 *TerrainOverlay* bg=pink @@ -2245,10 +2243,10 @@ terrain_overlay : Terrain Relation - 2420 - 4690 - 60 - 30 + 2187 + 4230 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -2256,10 +2254,10 @@ terrain_overlay : Terrain Relation - 2420 - 4870 - 60 - 30 + 2187 + 4392 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -2267,10 +2265,10 @@ terrain_overlay : Terrain UMLClass - 4960 - 4770 - 220 - 110 + 4473 + 4302 + 198 + 99 *AttributeSetting* bg=pink @@ -2285,10 +2283,10 @@ starting_value : int UMLClass - 4960 - 5050 - 220 - 80 + 4473 + 4554 + 198 + 72 *ProtectingAttribute* bg=pink @@ -2300,10 +2298,10 @@ protects : Attribute Relation - 5060 - 4700 - 30 - 90 + 4563 + 4239 + 27 + 81 lt=<. 10.0;70.0;10.0;10.0 @@ -2311,10 +2309,10 @@ protects : Attribute Relation - 5060 - 5010 - 30 - 60 + 4563 + 4518 + 27 + 54 lt=<<- 10.0;10.0;10.0;40.0 @@ -2322,10 +2320,10 @@ protects : Attribute UMLClass - 5000 - 5170 - 150 - 60 + 4509 + 4662 + 135 + 54 *Shield (SWGB)* @@ -2334,10 +2332,10 @@ protects : Attribute Relation - 5060 - 5120 - 30 - 70 + 4563 + 4617 + 27 + 63 lt=<<- 10.0;10.0;10.0;50.0 @@ -2345,10 +2343,10 @@ protects : Attribute UMLClass - 5240 - 4940 - 100 - 60 + 4725 + 4455 + 90 + 54 *Faith* @@ -2357,10 +2355,10 @@ protects : Attribute Relation - 5180 - 4950 - 80 - 30 + 4671 + 4464 + 72 + 27 lt=<<- 10.0;10.0;60.0;10.0 @@ -2368,10 +2366,10 @@ protects : Attribute UMLClass - 4860 - 4410 - 180 - 80 + 4383 + 3978 + 162 + 72 *LineOfSight* bg=green @@ -2383,10 +2381,10 @@ range : float UMLClass - 5100 - 4210 - 210 - 80 + 4599 + 3798 + 189 + 72 *Cloak (SWGB)* bg=green @@ -2399,10 +2397,10 @@ interrupt_cooldown : float UMLClass - 6330 - 1740 - 270 - 130 + 5706 + 1575 + 243 + 117 *Accuracy* bg=pink @@ -2419,10 +2417,10 @@ blacklisted_entities : set(GameEntity) Relation - 6270 - 1780 - 80 - 30 + 5652 + 1611 + 72 + 27 lt=<. 60.0;10.0;10.0;10.0 @@ -2430,10 +2428,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 4720 - 4890 - 160 - 80 + 4257 + 4410 + 144 + 72 *AttributeAmount* bg=pink @@ -2446,10 +2444,10 @@ amount : int Relation - 4870 - 4940 - 100 - 30 + 4392 + 4455 + 90 + 27 lt=<. 80.0;10.0;10.0;10.0 @@ -2457,10 +2455,10 @@ amount : int UMLClass - 750 - 3550 - 140 - 60 + 684 + 3204 + 126 + 54 *GameEntityType* @@ -2470,10 +2468,10 @@ bg=pink Relation - 700 - 3580 - 70 - 30 + 639 + 3231 + 63 + 27 lt=<. 50.0;10.0;10.0;10.0 @@ -2481,10 +2479,10 @@ bg=pink UMLClass - 5240 - 5010 - 100 - 60 + 4725 + 4518 + 90 + 54 *Health* @@ -2493,10 +2491,10 @@ bg=pink Relation - 5210 - 4950 - 50 - 110 + 4698 + 4464 + 45 + 99 lt=- 10.0;10.0;10.0;90.0;30.0;90.0 @@ -2504,10 +2502,10 @@ bg=pink Relation - 810 - 3600 - 30 - 50 + 738 + 3249 + 27 + 45 lt=- 10.0;30.0;10.0;10.0 @@ -2515,10 +2513,10 @@ bg=pink Relation - 960 - 3620 - 30 - 2130 + 873 + 3267 + 27 + 1917 lt=- 10.0;2110.0;10.0;10.0 @@ -2526,10 +2524,10 @@ bg=pink Relation - 960 - 5720 - 420 - 60 + 873 + 5157 + 378 + 54 lt=- 10.0;10.0;400.0;10.0;400.0;40.0 @@ -2537,10 +2535,10 @@ bg=pink UMLClass - 1180 - 5760 - 390 - 80 + 1071 + 5193 + 351 + 72 *Resistance* bg=pink @@ -2552,10 +2550,10 @@ properties : dict(ResistanceProperty, ResistanceProperty) = {} UMLClass - 400 - 5760 - 330 - 80 + 369 + 5193 + 297 + 72 *Effect* bg=pink @@ -2567,10 +2565,10 @@ properties : dict(EffectProperty, EffectProperty) = {} Relation - 560 - 5720 - 430 - 60 + 513 + 5157 + 387 + 54 lt=- 410.0;10.0;10.0;10.0;10.0;40.0 @@ -2578,10 +2576,10 @@ properties : dict(EffectProperty, EffectProperty) = {} UMLNote - 1210 - 5680 - 140 - 40 + 1098 + 5121 + 126 + 36 Resistors' side bg=blue @@ -2590,10 +2588,10 @@ bg=blue UMLNote - 570 - 5680 - 140 - 40 + 522 + 5121 + 126 + 36 Effectors' side bg=blue @@ -2602,10 +2600,10 @@ bg=blue UMLClass - 670 - 5990 - 350 - 130 + 612 + 5400 + 315 + 117 *FlatAttributeChange* bg=pink @@ -2621,10 +2619,10 @@ ignore_protection : set(ProtectingAttribute) UMLClass - 1480 - 5990 - 250 - 90 + 1341 + 5400 + 225 + 81 *FlatAttributeChange* bg=pink @@ -2637,10 +2635,10 @@ block_value : AttributeAmount UMLClass - 1040 - 4770 - 190 - 60 + 945 + 4302 + 171 + 54 *AttributeChangeType* @@ -2650,10 +2648,10 @@ bg=pink UMLClass - 670 - 6390 - 300 - 130 + 612 + 5760 + 270 + 117 *Convert* bg=orange @@ -2669,10 +2667,10 @@ cost_fail : optional(Cost) = None UMLClass - 1480 - 6390 - 290 - 130 + 1341 + 5760 + 261 + 117 *Convert* bg=orange @@ -2685,10 +2683,10 @@ chance_resist : float UMLClass - 740 - 6530 - 240 - 90 + 675 + 5886 + 216 + 81 *AoE2Convert* bg=orange @@ -2701,10 +2699,10 @@ skip_protected_rounds : int UMLClass - 1530 - 6530 - 290 - 100 + 1386 + 5886 + 261 + 90 *AoE2Convert* bg=orange @@ -2723,10 +2721,10 @@ protection_round_recharge_time : float UMLClass - 320 - 5890 - 180 - 60 + 297 + 5310 + 162 + 54 *ContinuousEffect* @@ -2737,10 +2735,10 @@ bg=orange UMLClass - 170 - 5990 - 330 - 130 + 162 + 5400 + 297 + 117 *FlatAttributeChange* bg=pink @@ -2756,10 +2754,10 @@ ignore_protection : set(ProtectingAttribute) UMLClass - 670 - 5890 - 170 - 60 + 612 + 5310 + 153 + 54 *DiscreteEffect* @@ -2769,10 +2767,10 @@ bg=orange UMLClass - 1480 - 5890 - 180 - 60 + 1341 + 5310 + 162 + 54 *DiscreteResistance* @@ -2782,10 +2780,10 @@ bg=orange UMLClass - 910 - 3520 - 180 - 80 + 828 + 3177 + 162 + 72 *ResourceRate* bg=pink @@ -2798,10 +2796,10 @@ rate : float Relation - 1080 - 3550 - 180 - 30 + 981 + 3204 + 162 + 27 lt=- 160.0;10.0;10.0;10.0 @@ -2809,10 +2807,10 @@ rate : float UMLClass - 4720 - 4980 - 160 - 80 + 4257 + 4491 + 144 + 72 *AttributeRate* bg=pink @@ -2825,10 +2823,10 @@ rate : float Relation - 560 - 5830 - 30 - 60 + 513 + 5256 + 27 + 54 lt=<<- 10.0;10.0;10.0;40.0 @@ -2836,10 +2834,10 @@ rate : float Relation - 400 - 5860 - 190 - 50 + 369 + 5283 + 171 + 45 lt=- 170.0;10.0;10.0;10.0;10.0;30.0 @@ -2847,10 +2845,10 @@ rate : float Relation - 560 - 5860 - 210 - 50 + 513 + 5283 + 189 + 45 lt=- 10.0;10.0;190.0;10.0;190.0;30.0 @@ -2858,10 +2856,10 @@ rate : float Relation - 1170 - 5860 - 200 - 50 + 1062 + 5283 + 180 + 45 lt=- 180.0;10.0;10.0;10.0;10.0;30.0 @@ -2869,10 +2867,10 @@ rate : float Relation - 1350 - 5830 - 30 - 60 + 1224 + 5256 + 27 + 54 lt=<<- 10.0;10.0;10.0;40.0 @@ -2880,10 +2878,10 @@ rate : float Relation - 1340 - 5860 - 240 - 50 + 1215 + 5283 + 216 + 45 lt=- 10.0;10.0;220.0;10.0;220.0;30.0 @@ -2891,10 +2889,10 @@ rate : float UMLClass - 1090 - 5890 - 180 - 60 + 990 + 5310 + 162 + 54 *ContinuousResistance* @@ -2904,10 +2902,10 @@ bg=orange Relation - 960 - 4790 - 100 - 30 + 873 + 4320 + 90 + 27 lt=- 10.0;10.0;80.0;10.0 @@ -2915,10 +2913,10 @@ bg=orange UMLClass - 1060 - 5990 - 210 - 90 + 963 + 5400 + 189 + 81 *FlatAttributeChange* bg=pink @@ -2931,10 +2929,10 @@ block_rate : AttributeRate Relation - 630 - 5910 - 60 - 140 + 576 + 5328 + 54 + 126 lt=<<- 40.0;10.0;10.0;10.0;10.0;120.0;40.0;120.0 @@ -2942,10 +2940,10 @@ block_rate : AttributeRate Relation - 490 - 5910 - 60 - 140 + 450 + 5328 + 54 + 126 lt=<<- 10.0;10.0;40.0;10.0;40.0;120.0;10.0;120.0 @@ -2953,10 +2951,10 @@ block_rate : AttributeRate Relation - 630 - 6320 - 60 - 130 + 576 + 5697 + 54 + 117 lt=- 10.0;10.0;10.0;110.0;40.0;110.0 @@ -2964,10 +2962,10 @@ block_rate : AttributeRate Relation - 710 - 6510 - 50 - 80 + 648 + 5868 + 45 + 72 lt=<<- 10.0;10.0;10.0;60.0;30.0;60.0 @@ -2975,10 +2973,10 @@ block_rate : AttributeRate Relation - 1500 - 6510 - 50 - 80 + 1359 + 5868 + 45 + 72 lt=<<- 10.0;10.0;10.0;60.0;30.0;60.0 @@ -2986,10 +2984,10 @@ block_rate : AttributeRate Relation - 1440 - 6020 - 60 - 330 + 1305 + 5427 + 54 + 297 lt=- 10.0;10.0;10.0;310.0;40.0;310.0 @@ -2997,10 +2995,10 @@ block_rate : AttributeRate Relation - 1440 - 5910 - 60 - 140 + 1305 + 5328 + 54 + 126 lt=<<- 40.0;10.0;10.0;10.0;10.0;120.0;40.0;120.0 @@ -3008,10 +3006,10 @@ block_rate : AttributeRate Relation - 1260 - 5910 - 60 - 140 + 1143 + 5328 + 54 + 126 lt=<<- 10.0;10.0;40.0;10.0;40.0;120.0;10.0;120.0 @@ -3019,10 +3017,10 @@ block_rate : AttributeRate UMLClass - 6220 - 2740 - 320 - 140 + 5607 + 2475 + 288 + 126 *ApplyDiscreteEffect* bg=green @@ -3042,10 +3040,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 6220 - 2560 - 320 - 120 + 5607 + 2313 + 288 + 108 *ApplyContinuousEffect* bg=green @@ -3062,22 +3060,22 @@ blacklisted_entities : set(GameEntity) UMLClass - 6600 - 2460 - 140 - 60 + 5634 + 2214 + 135 + 54 -*MonkHeal* +*MonkHeal (Ranged)* UMLClass - 6600 - 2590 - 110 - 60 + 5949 + 2340 + 99 + 54 *Repair* @@ -3086,10 +3084,10 @@ blacklisted_entities : set(GameEntity) Relation - 630 - 6020 - 60 - 330 + 576 + 5427 + 54 + 297 lt=- 10.0;10.0;10.0;310.0;40.0;310.0 @@ -3097,10 +3095,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 4600 - 3190 - 270 - 130 + 4149 + 2880 + 243 + 117 *Gather* bg=green @@ -3116,10 +3114,10 @@ container : ResourceContainer UMLClass - 670 - 6290 - 260 - 80 + 612 + 5670 + 234 + 72 *MakeHarvestable* bg=orange @@ -3131,10 +3129,10 @@ resource_spot : ResourceSpot UMLClass - 1480 - 6290 - 340 - 80 + 1341 + 5670 + 306 + 72 *MakeHarvestable* bg=orange @@ -3147,37 +3145,21 @@ resist_condition : set(LogicElement) Relation - 6440 - 2480 - 180 - 30 + 5688 + 2259 + 27 + 72 lt=<<- - 10.0;10.0;160.0;10.0 - - - UMLClass - - 6220 - 2450 - 230 - 80 - - *RangedContinuousEffect* -bg=green - --- -min_range : int -max_range : int - + 10.0;60.0;10.0;10.0 Relation - 6530 - 2610 - 90 - 30 + 5886 + 2358 + 81 + 27 lt=<<- 10.0;10.0;70.0;10.0 @@ -3185,10 +3167,10 @@ max_range : int UMLClass - 6600 - 2750 - 140 - 60 + 5949 + 2484 + 126 + 54 *SelfDestruct* @@ -3198,10 +3180,10 @@ max_range : int Relation - 6530 - 2770 - 90 - 30 + 5886 + 2502 + 81 + 27 lt=<<- 10.0;10.0;70.0;10.0 @@ -3209,22 +3191,22 @@ max_range : int UMLClass - 6260 - 3040 - 130 - 60 + 5634 + 2646 + 135 + 54 -*Convert* +*Convert (Ranged)* Relation - 6310 - 2980 - 30 - 80 + 5688 + 2592 + 27 + 72 lt=<<- 10.0;10.0;10.0;60.0 @@ -3232,10 +3214,10 @@ max_range : int UMLClass - 1040 - 4840 - 120 - 60 + 945 + 4365 + 108 + 54 *ConvertType* @@ -3245,10 +3227,10 @@ bg=pink Relation - 960 - 4860 - 100 - 30 + 873 + 4383 + 90 + 27 lt=- 10.0;10.0;80.0;10.0 @@ -3256,10 +3238,10 @@ bg=pink Relation - 1440 - 6320 - 60 - 140 + 1305 + 5697 + 54 + 126 lt=- 10.0;10.0;10.0;120.0;40.0;120.0 @@ -3267,10 +3249,10 @@ bg=pink UMLClass - 6600 - 2820 - 120 - 60 + 5949 + 2547 + 108 + 54 *Hunt* @@ -3279,37 +3261,10 @@ bg=pink UMLClass - 6220 - 2910 - 200 - 80 - - *RangedDiscreteEffect* -bg=green - --- -min_range : int -max_range : int - - - - Relation - - 6310 - 2870 - 30 - 60 - - lt=<<- - 10.0;10.0;10.0;40.0 - - - UMLClass - - 4780 - 1760 - 320 - 100 + 4311 + 1593 + 288 + 90 *EnterContainer* bg=green @@ -3323,10 +3278,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 4780 - 1870 - 260 - 90 + 4311 + 1692 + 234 + 81 *ExitContainer* bg=green @@ -3338,10 +3293,10 @@ allowed_containers : set(EntityContainer) UMLClass - 2110 - 3260 - 160 - 60 + 1908 + 2943 + 144 + 54 *DiplomaticStance* @@ -3351,10 +3306,10 @@ bg=pink Relation - 2040 - 3280 - 90 - 30 + 1845 + 2961 + 81 + 27 lt=- 70.0;10.0;10.0;10.0 @@ -3362,10 +3317,10 @@ bg=pink UMLClass - 3620 - 4100 - 220 - 80 + 3267 + 3699 + 198 + 72 *Diplomatic* bg=pink @@ -3377,10 +3332,10 @@ stances : set(DiplomaticStance) Relation - 1050 - 30 - 180 - 480 + 954 + 36 + 162 + 432 lt=- 160.0;460.0;10.0;460.0;10.0;10.0 @@ -3388,10 +3343,10 @@ stances : set(DiplomaticStance) UMLClass - 2480 - 3320 - 100 - 60 + 2241 + 2997 + 90 + 54 *Self* @@ -3401,10 +3356,10 @@ bg=pink Relation - 2260 - 3280 - 290 - 30 + 2043 + 2961 + 261 + 27 lt=<<- 10.0;10.0;270.0;10.0 @@ -3412,10 +3367,10 @@ bg=pink Relation - 1200 - 200 - 320 - 310 + 1089 + 189 + 288 + 279 lt=- 300.0;10.0;170.0;10.0;170.0;290.0;10.0;290.0 @@ -3423,10 +3378,10 @@ bg=pink UMLClass - 1530 - 180 - 320 - 80 + 1386 + 171 + 288 + 72 *ElevationDifferenceLow* bg=yellow @@ -3438,10 +3393,10 @@ min_elevation_difference : optional(float) = None UMLClass - 230 - 6290 - 270 - 100 + 216 + 5670 + 243 + 90 *Lure* bg=orange @@ -3457,10 +3412,10 @@ min_distance_to_destination : float Relation - 490 - 6020 - 60 - 320 + 450 + 5427 + 54 + 288 lt=- 40.0;10.0;40.0;300.0;10.0;300.0 @@ -3468,10 +3423,10 @@ min_distance_to_destination : float UMLClass - 1020 - 6290 - 250 - 100 + 927 + 5670 + 225 + 90 *Lure* bg=orange @@ -3484,10 +3439,10 @@ type : LureType Relation - 1260 - 6020 - 60 - 320 + 1143 + 5427 + 54 + 288 lt=- 40.0;10.0;40.0;300.0;10.0;300.0 @@ -3495,10 +3450,10 @@ type : LureType UMLClass - 50 - 2540 - 160 - 80 + 54 + 2295 + 144 + 72 *LanguageTextPair* bg=pink @@ -3511,10 +3466,10 @@ string : text UMLClass - 340 - 2540 - 200 - 80 + 315 + 2295 + 180 + 72 *LanguageMarkupPair* bg=pink @@ -3527,10 +3482,10 @@ markup_file : file UMLClass - 660 - 2540 - 180 - 80 + 603 + 2295 + 162 + 72 *LanguageSoundPair* bg=pink @@ -3543,10 +3498,10 @@ sound : Sound Relation - 120 - 2610 - 30 - 80 + 117 + 2358 + 27 + 72 lt=<. 10.0;60.0;10.0;10.0 @@ -3554,10 +3509,10 @@ sound : Sound Relation - 430 - 2610 - 30 - 80 + 396 + 2358 + 27 + 72 lt=<. 10.0;60.0;10.0;10.0 @@ -3565,10 +3520,10 @@ sound : Sound Relation - 730 - 2610 - 30 - 80 + 666 + 2358 + 27 + 72 lt=<. 10.0;60.0;10.0;10.0 @@ -3576,10 +3531,10 @@ sound : Sound UMLClass - 570 - 2900 - 170 - 80 + 522 + 2619 + 153 + 72 *Language* bg=pink @@ -3591,10 +3546,10 @@ ietf_string : text Relation - 640 - 2850 - 30 - 70 + 585 + 2574 + 27 + 63 lt=- 10.0;50.0;10.0;10.0 @@ -3602,10 +3557,10 @@ ietf_string : text UMLClass - 4090 - 3930 - 160 - 80 + 3690 + 3546 + 144 + 72 *Turn* bg=green @@ -3617,10 +3572,10 @@ turn_speed : float UMLClass - 5100 - 4520 - 160 - 80 + 4599 + 4077 + 144 + 72 *Visibility* bg=green @@ -3632,10 +3587,10 @@ visible_in_fog : bool UMLClass - 4570 - 1480 - 350 - 130 + 4122 + 1341 + 315 + 117 *EntityContainer* bg=pink @@ -3651,10 +3606,10 @@ carry_progress : set(Progress) Relation - 4910 - 1530 - 90 - 30 + 4428 + 1386 + 81 + 27 lt=<. 70.0;10.0;10.0;10.0 @@ -3662,10 +3617,10 @@ carry_progress : set(Progress) UMLClass - 1230 - 5280 - 220 - 80 + 1116 + 4761 + 198 + 72 *Cost* bg=pink @@ -3677,10 +3632,10 @@ cost : Cost Relation - 1180 - 5400 - 70 - 30 + 1071 + 4869 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -3688,10 +3643,10 @@ cost : Cost UMLClass - 1770 - 3650 - 240 - 100 + 1602 + 3294 + 216 + 90 *AnimationOverride* bg=pink @@ -3705,10 +3660,10 @@ priority : int UMLClass - 1530 - 3400 - 220 - 80 + 1386 + 3069 + 198 + 72 *Diplomatic* bg=pink @@ -3720,10 +3675,10 @@ stances : set(DiplomaticStance) UMLClass - 740 - 4700 - 160 - 60 + 675 + 4239 + 144 + 54 *DropoffType* @@ -3733,10 +3688,10 @@ bg=pink Relation - 670 - 4720 - 90 - 30 + 612 + 4257 + 81 + 27 lt=<<- 70.0;10.0;10.0;10.0 @@ -3744,10 +3699,10 @@ bg=pink Relation - 670 - 4720 - 60 - 100 + 612 + 4257 + 54 + 90 lt=- 40.0;10.0;40.0;80.0;10.0;80.0 @@ -3755,10 +3710,10 @@ bg=pink Relation - 670 - 4650 - 60 - 100 + 612 + 4194 + 54 + 90 lt=- 40.0;80.0;40.0;10.0;10.0;10.0 @@ -3766,10 +3721,10 @@ bg=pink UMLClass - 520 - 4630 - 160 - 60 + 477 + 4176 + 144 + 54 *NoDropoff* @@ -3779,10 +3734,10 @@ bg=pink UMLClass - 520 - 4700 - 160 - 60 + 477 + 4239 + 144 + 54 *Linear* @@ -3792,10 +3747,10 @@ bg=pink UMLClass - 1280 - 4770 - 120 - 60 + 1161 + 4302 + 108 + 54 // This type is _only_ evaluated if all FlatAttributeChange Effects with other types are outside of the // range defined in the FlatAttributeChange Effect with type Fallback. @@ -3809,10 +3764,10 @@ bg=pink Relation - 1220 - 4790 - 80 - 30 + 1107 + 4320 + 72 + 27 lt=<<- 10.0;10.0;60.0;10.0 @@ -3820,10 +3775,10 @@ bg=pink Relation - 3570 - 2440 - 60 - 30 + 3222 + 2205 + 54 + 27 lt=<. 10.0;10.0;40.0;10.0 @@ -3831,10 +3786,10 @@ bg=pink UMLClass - 1040 - 4100 - 270 - 130 + 945 + 3699 + 243 + 117 *Tech* bg=pink @@ -3850,10 +3805,10 @@ updates : orderedset(Patch) Relation - 1230 - 3650 - 30 - 470 + 1116 + 3294 + 27 + 423 lt=<<- 10.0;10.0;10.0;450.0 @@ -3861,10 +3816,10 @@ updates : orderedset(Patch) UMLClass - 4730 - 2590 - 240 - 100 + 4266 + 2340 + 216 + 90 // Determines traded resource and resource amount *TradeRoute* @@ -3879,10 +3834,10 @@ end_trade_post : GameEntity Relation - 4590 - 2680 - 210 - 60 + 4140 + 2421 + 189 + 54 lt=<. 190.0;10.0;190.0;40.0;10.0;40.0 @@ -3890,10 +3845,10 @@ end_trade_post : GameEntity UMLClass - 5040 - 2590 - 170 - 60 + 4545 + 2340 + 153 + 54 *AoE2TradeRoute* @@ -3904,10 +3859,10 @@ bg=pink UMLClass - 720 - 6140 - 220 - 60 + 657 + 5535 + 198 + 54 *FlatAttributeDecrease* @@ -3917,10 +3872,10 @@ bg=orange UMLClass - 720 - 6210 - 220 - 60 + 657 + 5598 + 198 + 54 *FlatAttributeIncrease* @@ -3930,10 +3885,10 @@ bg=orange Relation - 690 - 6110 - 50 - 80 + 630 + 5508 + 45 + 72 lt=->> 30.0;60.0;10.0;60.0;10.0;10.0 @@ -3941,10 +3896,10 @@ bg=orange Relation - 690 - 6160 - 50 - 100 + 630 + 5553 + 45 + 90 lt=- 30.0;80.0;10.0;80.0;10.0;10.0 @@ -3952,10 +3907,10 @@ bg=orange UMLClass - 1540 - 6140 - 220 - 60 + 1395 + 5535 + 198 + 54 *FlatAttributeDecrease* @@ -3965,10 +3920,10 @@ bg=orange UMLClass - 1540 - 6210 - 220 - 60 + 1395 + 5598 + 198 + 54 *FlatAttributeIncrease* @@ -3978,10 +3933,10 @@ bg=orange Relation - 1500 - 6070 - 30 - 190 + 1359 + 5472 + 27 + 171 lt=<<- 10.0;10.0;10.0;170.0 @@ -3989,10 +3944,10 @@ bg=orange Relation - 1500 - 6160 - 60 - 30 + 1359 + 5553 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4000,10 +3955,10 @@ bg=orange UMLClass - 230 - 6140 - 220 - 60 + 216 + 5535 + 198 + 54 *FlatAttributeDecrease* @@ -4013,10 +3968,10 @@ bg=orange UMLClass - 230 - 6210 - 220 - 60 + 216 + 5598 + 198 + 54 *FlatAttributeIncrease* @@ -4026,10 +3981,10 @@ bg=orange Relation - 440 - 6160 - 50 - 100 + 405 + 5553 + 45 + 90 lt=- 10.0;80.0;30.0;80.0;30.0;10.0 @@ -4037,10 +3992,10 @@ bg=orange Relation - 440 - 6110 - 50 - 80 + 405 + 5508 + 45 + 72 lt=->> 10.0;60.0;30.0;60.0;30.0;10.0 @@ -4048,10 +4003,10 @@ bg=orange UMLClass - 1020 - 6140 - 220 - 60 + 927 + 5535 + 198 + 54 *FlatAttributeDecrease* @@ -4061,10 +4016,10 @@ bg=orange UMLClass - 1020 - 6210 - 220 - 60 + 927 + 5598 + 198 + 54 *FlatAttributeIncrease* @@ -4074,10 +4029,10 @@ bg=orange Relation - 1230 - 6160 - 50 - 100 + 1116 + 5553 + 45 + 90 lt=- 10.0;80.0;30.0;80.0;30.0;10.0 @@ -4085,10 +4040,10 @@ bg=orange Relation - 1230 - 6070 - 50 - 120 + 1116 + 5472 + 45 + 108 lt=->> 10.0;100.0;30.0;100.0;30.0;10.0 @@ -4096,10 +4051,10 @@ bg=orange UMLClass - 1490 - 3160 - 250 - 80 + 1350 + 2853 + 225 + 72 *Formation* bg=pink @@ -4111,10 +4066,10 @@ subformations : set(Subformation) Relation - 1730 - 3190 - 70 - 30 + 1566 + 2880 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -4122,10 +4077,10 @@ subformations : set(Subformation) UMLClass - 1540 - 3270 - 180 - 80 + 1395 + 2952 + 162 + 72 *Subformation* bg=pink @@ -4137,10 +4092,10 @@ ordering_priority : int UMLClass - 4400 - 4280 - 220 - 80 + 3969 + 3861 + 198 + 72 *GameEntityFormation* bg=pink @@ -4153,10 +4108,10 @@ subformation : Subformation Relation - 4350 - 4310 - 70 - 30 + 3924 + 3888 + 63 + 27 lt=<. 50.0;10.0;10.0;10.0 @@ -4164,10 +4119,10 @@ subformation : Subformation Relation - 1710 - 3300 - 90 - 30 + 1548 + 2979 + 81 + 27 lt=- 10.0;10.0;70.0;10.0 @@ -4175,10 +4130,10 @@ subformation : Subformation Relation - 1620 - 3230 - 30 - 60 + 1467 + 2916 + 27 + 54 lt=<. 10.0;40.0;10.0;10.0 @@ -4186,10 +4141,10 @@ subformation : Subformation UMLClass - 1400 - 1660 - 210 - 90 + 1269 + 1503 + 189 + 81 *Scoped* bg=pink @@ -4203,24 +4158,13 @@ stances : set(DiplomaticStance) scope : ModifierScope - - Relation - - 6330 - 2520 - 30 - 60 - - lt=<<- - 10.0;40.0;10.0;10.0 - UMLClass - 670 - 6640 - 250 - 100 + 612 + 5985 + 225 + 90 *SendToContainer* bg=orange @@ -4233,10 +4177,10 @@ storages : set(EntityContainer) Relation - 630 - 6420 - 60 - 280 + 576 + 5787 + 54 + 252 lt=- 10.0;10.0;10.0;260.0;40.0;260.0 @@ -4244,10 +4188,10 @@ storages : set(EntityContainer) UMLClass - 1480 - 6640 - 260 - 100 + 1341 + 5985 + 234 + 90 *SendToContainer* bg=orange @@ -4261,10 +4205,10 @@ ignore_containers : set(EntityContainer) Relation - 1440 - 6430 - 60 - 270 + 1305 + 5796 + 54 + 243 lt=- 10.0;10.0;10.0;250.0;40.0;250.0 @@ -4272,10 +4216,10 @@ ignore_containers : set(EntityContainer) Relation - 3880 - 2750 - 60 - 870 + 3501 + 2484 + 54 + 783 lt=<<- 40.0;850.0;40.0;10.0;10.0;10.0 @@ -4283,10 +4227,10 @@ ignore_containers : set(EntityContainer) Relation - 3880 - 2610 - 60 - 170 + 3501 + 2358 + 54 + 153 lt=- 40.0;150.0;40.0;10.0;10.0;10.0 @@ -4294,10 +4238,10 @@ ignore_containers : set(EntityContainer) Relation - 3880 - 2450 - 60 - 190 + 3501 + 2214 + 54 + 171 lt=- 40.0;170.0;40.0;10.0;10.0;10.0 @@ -4305,10 +4249,10 @@ ignore_containers : set(EntityContainer) Relation - 4140 - 3620 - 2440 - 30 + 3735 + 3267 + 2196 + 27 lt=<<- 10.0;10.0;2420.0;10.0 @@ -4316,21 +4260,21 @@ ignore_containers : set(EntityContainer) Relation - 3570 - 3700 - 30 - 550 + 3222 + 3339 + 27 + 576 lt=<<- - 10.0;10.0;10.0;530.0 + 10.0;10.0;10.0;620.0 Relation - 3570 - 3950 - 70 - 30 + 3222 + 3564 + 63 + 27 lt=- 50.0;10.0;10.0;10.0 @@ -4338,10 +4282,10 @@ ignore_containers : set(EntityContainer) Relation - 3570 - 4040 - 70 - 30 + 3222 + 3645 + 63 + 27 lt=- 50.0;10.0;10.0;10.0 @@ -4349,10 +4293,10 @@ ignore_containers : set(EntityContainer) Relation - 3910 - 3680 - 30 - 830 + 3528 + 3321 + 27 + 747 lt=<<- 10.0;10.0;10.0;810.0 @@ -4360,10 +4304,10 @@ ignore_containers : set(EntityContainer) Relation - 4020 - 4310 - 70 - 120 + 3627 + 3888 + 63 + 108 lt=- 10.0;10.0;10.0;100.0;50.0;100.0 @@ -4371,10 +4315,10 @@ ignore_containers : set(EntityContainer) Relation - 3910 - 4310 - 180 - 30 + 3528 + 3888 + 162 + 27 lt=- 10.0;10.0;160.0;10.0 @@ -4382,10 +4326,10 @@ ignore_containers : set(EntityContainer) Relation - 4240 - 3870 - 60 - 120 + 3825 + 3492 + 54 + 108 lt=- 10.0;100.0;40.0;100.0;40.0;10.0 @@ -4393,10 +4337,10 @@ ignore_containers : set(EntityContainer) Relation - 4240 - 3620 - 60 - 280 + 3825 + 3267 + 54 + 252 lt=- 10.0;260.0;40.0;260.0;40.0;10.0 @@ -4404,10 +4348,10 @@ ignore_containers : set(EntityContainer) Relation - 4270 - 3870 - 60 - 30 + 3852 + 3492 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4415,10 +4359,10 @@ ignore_containers : set(EntityContainer) Relation - 4650 - 2610 - 100 - 30 + 4194 + 2358 + 90 + 27 lt=<. 80.0;10.0;10.0;10.0 @@ -4426,10 +4370,10 @@ ignore_containers : set(EntityContainer) Relation - 4960 - 2610 - 100 - 30 + 4473 + 2358 + 90 + 27 lt=<<- 10.0;10.0;80.0;10.0 @@ -4437,10 +4381,10 @@ ignore_containers : set(EntityContainer) Relation - 5000 - 2610 - 30 - 120 + 4509 + 2358 + 27 + 108 lt=- 10.0;10.0;10.0;100.0 @@ -4448,10 +4392,10 @@ ignore_containers : set(EntityContainer) Relation - 3910 - 2500 - 480 - 30 + 3528 + 2259 + 432 + 27 lt=- 460.0;10.0;10.0;10.0 @@ -4459,10 +4403,10 @@ ignore_containers : set(EntityContainer) Relation - 4330 - 2500 - 60 - 140 + 3906 + 2259 + 54 + 126 lt=- 40.0;120.0;10.0;120.0;10.0;10.0 @@ -4470,10 +4414,10 @@ ignore_containers : set(EntityContainer) Relation - 4330 - 2610 - 60 - 120 + 3906 + 2358 + 54 + 108 lt=- 40.0;100.0;10.0;100.0;10.0;10.0 @@ -4481,10 +4425,10 @@ ignore_containers : set(EntityContainer) Relation - 4870 - 4990 - 100 - 30 + 4392 + 4500 + 90 + 27 lt=<. 80.0;10.0;10.0;10.0 @@ -4492,10 +4436,10 @@ ignore_containers : set(EntityContainer) Relation - 5060 - 3620 - 30 - 1030 + 4563 + 3267 + 27 + 927 lt=- 10.0;10.0;10.0;1010.0 @@ -4503,10 +4447,10 @@ ignore_containers : set(EntityContainer) Relation - 5030 - 4240 - 60 - 30 + 4536 + 3825 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4514,10 +4458,10 @@ ignore_containers : set(EntityContainer) Relation - 5030 - 4350 - 60 - 30 + 4536 + 3924 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4525,10 +4469,10 @@ ignore_containers : set(EntityContainer) Relation - 5060 - 4450 - 60 - 30 + 4563 + 4014 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4536,10 +4480,10 @@ ignore_containers : set(EntityContainer) Relation - 5030 - 4550 - 60 - 30 + 4536 + 4104 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4547,10 +4491,10 @@ ignore_containers : set(EntityContainer) Relation - 5060 - 4550 - 60 - 30 + 4563 + 4104 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4558,10 +4502,10 @@ ignore_containers : set(EntityContainer) Relation - 5030 - 4440 - 60 - 30 + 4536 + 4005 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4569,10 +4513,10 @@ ignore_containers : set(EntityContainer) Relation - 5060 - 4240 - 60 - 30 + 4563 + 3825 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4580,10 +4524,10 @@ ignore_containers : set(EntityContainer) Relation - 5970 - 3620 - 30 - 1200 + 5382 + 3267 + 27 + 1080 lt=- 10.0;10.0;10.0;1180.0 @@ -4591,10 +4535,10 @@ ignore_containers : set(EntityContainer) Relation - 5940 - 4790 - 60 - 30 + 5355 + 4320 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4602,10 +4546,10 @@ ignore_containers : set(EntityContainer) Relation - 5970 - 4790 - 60 - 30 + 5382 + 4320 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4613,10 +4557,10 @@ ignore_containers : set(EntityContainer) Relation - 5060 - 4350 - 60 - 30 + 4563 + 3924 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4624,10 +4568,10 @@ ignore_containers : set(EntityContainer) Relation - 6380 - 3550 - 60 - 100 + 5751 + 3204 + 54 + 90 lt=- 40.0;10.0;10.0;10.0;10.0;80.0 @@ -4635,10 +4579,10 @@ ignore_containers : set(EntityContainer) Relation - 6380 - 3620 - 30 - 370 + 5751 + 3267 + 27 + 333 lt=- 10.0;350.0;10.0;10.0 @@ -4646,10 +4590,10 @@ ignore_containers : set(EntityContainer) Relation - 6380 - 3960 - 60 - 30 + 5751 + 3573 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4657,10 +4601,10 @@ ignore_containers : set(EntityContainer) Relation - 6180 - 2670 - 210 - 980 + 5571 + 2412 + 189 + 882 lt=- 190.0;10.0;190.0;40.0;10.0;40.0;10.0;960.0 @@ -4668,10 +4612,10 @@ ignore_containers : set(EntityContainer) Relation - 6530 - 2840 - 90 - 30 + 5886 + 2565 + 81 + 27 lt=<<- 10.0;10.0;70.0;10.0 @@ -4679,10 +4623,10 @@ ignore_containers : set(EntityContainer) Relation - 6360 - 2700 - 30 - 60 + 5733 + 2439 + 27 + 54 lt=- 10.0;10.0;10.0;40.0 @@ -4690,10 +4634,10 @@ ignore_containers : set(EntityContainer) Relation - 4890 - 2950 - 30 - 700 + 4410 + 2664 + 27 + 630 lt=- 10.0;680.0;10.0;10.0 @@ -4701,10 +4645,10 @@ ignore_containers : set(EntityContainer) Relation - 4890 - 2980 - 60 - 30 + 4410 + 2691 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4712,10 +4656,10 @@ ignore_containers : set(EntityContainer) Relation - 4860 - 2950 - 60 - 30 + 4383 + 2664 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4723,10 +4667,10 @@ ignore_containers : set(EntityContainer) Relation - 4860 - 3070 - 60 - 30 + 4383 + 2772 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4734,10 +4678,10 @@ ignore_containers : set(EntityContainer) Relation - 4890 - 3070 - 60 - 30 + 4410 + 2772 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4745,10 +4689,10 @@ ignore_containers : set(EntityContainer) Relation - 4890 - 3160 - 60 - 30 + 4410 + 2853 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4756,10 +4700,10 @@ ignore_containers : set(EntityContainer) Relation - 4890 - 3250 - 60 - 30 + 4410 + 2934 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4767,10 +4711,10 @@ ignore_containers : set(EntityContainer) Relation - 4860 - 3220 - 60 - 30 + 4383 + 2907 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4778,10 +4722,10 @@ ignore_containers : set(EntityContainer) Relation - 4740 - 1720 - 1000 - 1930 + 4275 + 1557 + 900 + 1737 lt=- 980.0;1910.0;980.0;360.0;10.0;360.0;10.0;10.0 @@ -4789,10 +4733,10 @@ ignore_containers : set(EntityContainer) Relation - 5710 - 2070 - 270 - 30 + 5148 + 1872 + 243 + 27 lt=- 10.0;10.0;250.0;10.0 @@ -4800,10 +4744,10 @@ ignore_containers : set(EntityContainer) Relation - 5920 - 1870 - 60 - 230 + 5337 + 1692 + 54 + 207 lt=- 40.0;10.0;10.0;10.0;10.0;210.0 @@ -4811,10 +4755,10 @@ ignore_containers : set(EntityContainer) Relation - 4740 - 1790 - 60 - 30 + 4275 + 1620 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4822,10 +4766,10 @@ ignore_containers : set(EntityContainer) Relation - 4740 - 1900 - 60 - 30 + 4275 + 1719 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4833,10 +4777,10 @@ ignore_containers : set(EntityContainer) Relation - 4740 - 1990 - 60 - 30 + 4275 + 1800 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4844,10 +4788,10 @@ ignore_containers : set(EntityContainer) Relation - 4710 - 1990 - 60 - 30 + 4248 + 1800 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4855,10 +4799,10 @@ ignore_containers : set(EntityContainer) Relation - 4710 - 1900 - 60 - 30 + 4248 + 1719 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4866,10 +4810,10 @@ ignore_containers : set(EntityContainer) Relation - 4710 - 1790 - 60 - 30 + 4248 + 1620 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4877,10 +4821,10 @@ ignore_containers : set(EntityContainer) UMLClass - 6330 - 1890 - 140 - 60 + 5706 + 1710 + 126 + 54 *TargetMode* @@ -4890,10 +4834,10 @@ bg=pink Relation - 6270 - 1910 - 80 - 30 + 5652 + 1728 + 72 + 27 lt=<. 60.0;10.0;10.0;10.0 @@ -4901,10 +4845,10 @@ bg=pink UMLClass - 6540 - 1890 - 160 - 60 + 5895 + 1710 + 144 + 54 *CurrentPosition* @@ -4914,10 +4858,10 @@ bg=pink Relation - 6460 - 1910 - 100 - 30 + 5823 + 1728 + 90 + 27 lt=<<- 10.0;10.0;80.0;10.0 @@ -4925,10 +4869,10 @@ bg=pink UMLClass - 6540 - 1960 - 160 - 60 + 5895 + 1773 + 144 + 54 *ExpectedPosition* @@ -4938,10 +4882,10 @@ bg=pink Relation - 6500 - 1910 - 60 - 100 + 5859 + 1728 + 54 + 90 lt=- 10.0;10.0;10.0;80.0;40.0;80.0 @@ -4949,10 +4893,10 @@ bg=pink UMLClass - 1200 - 1670 - 140 - 60 + 1089 + 1512 + 126 + 54 *ModifierScope* @@ -4962,10 +4906,10 @@ bg=pink Relation - 1330 - 1690 - 90 - 30 + 1206 + 1530 + 81 + 27 lt=<. 10.0;10.0;70.0;10.0 @@ -4973,10 +4917,10 @@ bg=pink UMLClass - 4600 - 4090 - 110 - 60 + 4149 + 3690 + 99 + 54 *AttackMove* @@ -4986,10 +4930,10 @@ bg=pink Relation - 4550 - 4110 - 70 - 30 + 4104 + 3708 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -4997,10 +4941,10 @@ bg=pink UMLClass - 4510 - 3750 - 120 - 60 + 4068 + 3384 + 108 + 54 *MoveMode* @@ -5010,10 +4954,10 @@ bg=pink Relation - 4390 - 3770 - 140 - 90 + 3960 + 3402 + 126 + 81 lt=<. 120.0;10.0;10.0;10.0;10.0;70.0 @@ -5021,10 +4965,10 @@ bg=pink Relation - 1490 - 290 - 60 - 30 + 1350 + 270 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -5032,10 +4976,10 @@ bg=pink UMLClass - 1530 - 270 - 130 - 60 + 1386 + 252 + 117 + 54 *Stray* @@ -5045,10 +4989,10 @@ bg=yellow UMLClass - 2390 - 770 - 300 - 80 + 2160 + 702 + 270 + 72 *AbsoluteProjectileAmount* bg=yellow @@ -5060,10 +5004,10 @@ amount : float Relation - 1770 - 440 - 590 - 800 + 1602 + 405 + 531 + 720 lt=- 10.0;780.0;570.0;780.0;570.0;10.0 @@ -5071,10 +5015,10 @@ amount : float UMLClass - 920 - 780 - 240 - 80 + 837 + 711 + 216 + 72 *GatheringEfficiency* bg=yellow @@ -5086,10 +5030,10 @@ resource_spot : ResourceSpot Relation - 1150 - 810 - 80 - 30 + 1044 + 738 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5097,10 +5041,10 @@ resource_spot : ResourceSpot UMLClass - 1020 - 870 - 140 - 60 + 927 + 792 + 126 + 54 *ReloadTime* @@ -5110,10 +5054,10 @@ bg=yellow Relation - 1150 - 890 - 80 - 30 + 1044 + 810 + 72 + 27 lt=- 10.0;10.0;60.0;10.0 @@ -5121,10 +5065,10 @@ bg=yellow UMLClass - 1260 - 1010 - 280 - 80 + 1143 + 918 + 252 + 72 *CreationTime* bg=yellow @@ -5136,10 +5080,10 @@ creatables : set(CreatableGameEntity) Relation - 1200 - 1040 - 80 - 30 + 1089 + 945 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5147,10 +5091,10 @@ creatables : set(CreatableGameEntity) UMLClass - 1260 - 920 - 300 - 80 + 1143 + 837 + 270 + 72 *CreationResourceCost* bg=yellow @@ -5164,10 +5108,10 @@ creatables : set(CreatableGameEntity) Relation - 1200 - 960 - 80 - 30 + 1089 + 873 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5175,10 +5119,10 @@ creatables : set(CreatableGameEntity) UMLClass - 1260 - 650 - 290 - 80 + 1143 + 594 + 261 + 72 *ResearchResourceCost* bg=yellow @@ -5192,10 +5136,10 @@ researchables : set(ResearchableTech) Relation - 1200 - 680 - 80 - 30 + 1089 + 621 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5203,10 +5147,10 @@ researchables : set(ResearchableTech) Relation - 2330 - 800 - 80 - 30 + 2106 + 729 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5214,10 +5158,10 @@ researchables : set(ResearchableTech) UMLClass - 2390 - 680 - 220 - 80 + 2160 + 621 + 198 + 72 // Immediately unlocks a Tech as soon as the requirements are fulfilled *InstantTechResearch* @@ -5231,10 +5175,10 @@ condition : set(LogicElement) Relation - 2330 - 710 - 80 - 30 + 2106 + 648 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5242,16 +5186,15 @@ condition : set(LogicElement) UMLClass - 4930 - 3340 - 250 - 110 + 4446 + 3015 + 225 + 99 *Herd* bg=green -- -range : float strength : int allowed_types : set(GameEntityType) blacklisted_entities : set(GameEntity) @@ -5260,10 +5203,10 @@ blacklisted_entities : set(GameEntity) Relation - 4890 - 3370 - 60 - 30 + 4410 + 3042 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -5271,10 +5214,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 880 - 510 - 280 - 80 + 801 + 468 + 252 + 72 *EntityContainerCapacity* bg=yellow @@ -5287,10 +5230,10 @@ container : EntityContainer UMLClass - 870 - 600 - 290 - 80 + 792 + 549 + 261 + 72 *StorageElementCapacity* bg=yellow @@ -5303,10 +5246,10 @@ storage_element : StorageElementDefinition Relation - 1150 - 540 - 80 - 30 + 1044 + 495 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5314,10 +5257,10 @@ storage_element : StorageElementDefinition Relation - 1150 - 630 - 80 - 30 + 1044 + 576 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5325,10 +5268,10 @@ storage_element : StorageElementDefinition UMLClass - 1260 - 740 - 290 - 80 + 1143 + 675 + 261 + 72 *ResearchTime* bg=yellow @@ -5341,10 +5284,10 @@ researchables : set(ResearchableTech) Relation - 1200 - 770 - 80 - 30 + 1089 + 702 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5352,10 +5295,10 @@ researchables : set(ResearchableTech) UMLClass - 2390 - 500 - 280 - 100 + 2160 + 459 + 252 + 90 // Reveal area around listed units *Reveal* @@ -5370,10 +5313,10 @@ blacklisted_entities : set(GameEntity) Relation - 2330 - 530 - 80 - 30 + 2106 + 486 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5381,10 +5324,10 @@ blacklisted_entities : set(GameEntity) UMLNote - 0 - 340 - 190 - 90 + 9 + 315 + 171 + 81 Orange elements: @@ -5397,10 +5340,10 @@ bg=orange UMLClass - 2390 - 860 - 320 - 100 + 2160 + 783 + 288 + 90 // The change values and fire rate of the provider and receiver are compared (divided) // with the result being added to the projectile amount of the receiver @@ -5419,10 +5362,10 @@ change_types : set(AttributeChangeType) UMLClass - 4640 - 3420 - 230 - 80 + 4185 + 3087 + 207 + 72 *RegenerateResourceSpot* bg=green @@ -5435,10 +5378,10 @@ resource_spot : ResourceSpot Relation - 4860 - 3450 - 60 - 30 + 4383 + 3114 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -5446,10 +5389,10 @@ resource_spot : ResourceSpot UMLClass - 1150 - 1760 - 120 - 60 + 1044 + 1593 + 108 + 54 // Only affect yourself (default for modifiers in GameEntity) @@ -5460,10 +5403,10 @@ bg=pink Relation - 1260 - 1720 - 70 - 100 + 1143 + 1557 + 63 + 90 lt=<<- 50.0;10.0;50.0;80.0;10.0;80.0 @@ -5471,10 +5414,10 @@ bg=pink UMLClass - 1020 - 1830 - 250 - 80 + 927 + 1656 + 225 + 72 // Affect all game entities in the list *GameEntityScope* @@ -5488,10 +5431,10 @@ blacklisted_entities : set(GameEntity) Relation - 1260 - 1790 - 70 - 90 + 1143 + 1620 + 63 + 81 lt=- 10.0;70.0;50.0;70.0;50.0;10.0 @@ -5499,21 +5442,21 @@ blacklisted_entities : set(GameEntity) Text - 0 - 0 - 230 - 30 + 9 + 9 + 207 + 27 - openage nyan data API v0.5.0 + openage nyan data API v0.6.0 UMLClass - 1420 - 3010 - 320 - 140 + 1287 + 2718 + 288 + 126 *StateChanger* bg=pink @@ -5531,10 +5474,10 @@ priority : int Relation - 1730 - 3060 - 70 - 30 + 1566 + 2763 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -5542,10 +5485,10 @@ priority : int UMLClass - 1980 - 500 - 310 - 90 + 1791 + 459 + 279 + 81 // Apply effects when in a container *InContainerContinuousEffect* @@ -5559,10 +5502,10 @@ ability : ApplyContinuousEffect Relation - 2280 - 530 - 80 - 30 + 2061 + 486 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5570,10 +5513,10 @@ ability : ApplyContinuousEffect UMLClass - 1980 - 610 - 310 - 90 + 1791 + 558 + 279 + 81 // Apply effects when in a container *InContainerDiscreteEffect* @@ -5587,10 +5530,10 @@ ability : ApplyDiscreteEffect Relation - 2280 - 640 - 80 - 30 + 2061 + 585 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5598,10 +5541,10 @@ ability : ApplyDiscreteEffect UMLClass - 4600 - 4160 - 110 - 60 + 4149 + 3753 + 99 + 54 *Normal* @@ -5611,10 +5554,10 @@ bg=pink Relation - 4550 - 4180 - 70 - 30 + 4104 + 3771 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -5622,10 +5565,10 @@ bg=pink UMLClass - 520 - 150 - 170 - 80 + 477 + 144 + 153 + 72 *Terrain* bg=yellow @@ -5637,10 +5580,10 @@ terrain : Terrain Relation - 680 - 180 - 60 - 30 + 621 + 171 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -5648,10 +5591,10 @@ terrain : Terrain UMLClass - 1530 - 0 - 170 - 80 + 1386 + 9 + 153 + 72 *Terrain* bg=yellow @@ -5663,10 +5606,10 @@ terrain : Terrain Relation - 1490 - 30 - 30 - 360 + 1350 + 36 + 27 + 324 lt=- 10.0;340.0;10.0;10.0 @@ -5674,10 +5617,10 @@ terrain : Terrain UMLClass - 2390 - 410 - 280 - 80 + 2160 + 378 + 252 + 72 // Reveal area around listed units *DiplomaticLineOfSight* @@ -5690,10 +5633,10 @@ diplomatic_stance : DiplomaticStance Relation - 2330 - 440 - 80 - 30 + 2106 + 405 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5701,10 +5644,10 @@ diplomatic_stance : DiplomaticStance UMLClass - 1040 - 4910 - 120 - 60 + 945 + 4428 + 108 + 54 *LureType* @@ -5714,10 +5657,10 @@ bg=pink Relation - 960 - 4930 - 100 - 30 + 873 + 4446 + 90 + 27 lt=- 10.0;10.0;80.0;10.0 @@ -5725,10 +5668,10 @@ bg=pink UMLClass - 1040 - 4980 - 200 - 60 + 945 + 4491 + 180 + 54 *SendToContainerType* @@ -5738,10 +5681,10 @@ bg=pink Relation - 960 - 5000 - 100 - 30 + 873 + 4509 + 90 + 27 lt=- 10.0;10.0;80.0;10.0 @@ -5749,10 +5692,10 @@ bg=pink UMLClass - 3450 - 2760 - 140 - 60 + 3114 + 2493 + 126 + 54 *PlacementMode* @@ -5762,10 +5705,10 @@ bg=pink Relation - 3490 - 2710 - 30 - 70 + 3150 + 2448 + 27 + 63 lt=<. 10.0;50.0;10.0;10.0 @@ -5773,10 +5716,10 @@ bg=pink UMLClass - 3570 - 3070 - 110 - 60 + 3222 + 2772 + 99 + 54 *Eject* @@ -5786,10 +5729,10 @@ bg=pink UMLClass - 3570 - 2840 - 210 - 130 + 3222 + 2565 + 189 + 117 *Place* bg=pink @@ -5805,10 +5748,10 @@ max_elevation_difference : int Relation - 3520 - 2810 - 30 - 390 + 3177 + 2538 + 27 + 351 lt=<<- 10.0;10.0;10.0;370.0 @@ -5816,10 +5759,10 @@ max_elevation_difference : int Relation - 3520 - 2870 - 70 - 30 + 3177 + 2592 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -5827,10 +5770,10 @@ max_elevation_difference : int UMLClass - 370 - 3950 - 260 - 180 + 342 + 3564 + 234 + 162 *AdjacentTilesVariant* @@ -5849,10 +5792,10 @@ north_west : optional(GameEntity) Relation - 620 - 3890 - 50 - 120 + 567 + 3510 + 45 + 108 lt=- 30.0;10.0;30.0;100.0;10.0;100.0 @@ -5860,10 +5803,10 @@ north_west : optional(GameEntity) UMLClass - 520 - 4770 - 160 - 60 + 477 + 4302 + 144 + 54 *InverseLinear* @@ -5873,10 +5816,10 @@ bg=pink UMLClass - 3620 - 3830 - 180 - 80 + 3267 + 3456 + 162 + 72 *ExecutionSound* bg=pink @@ -5889,10 +5832,10 @@ sounds : set(Sound) Relation - 3570 - 3860 - 70 - 30 + 3222 + 3483 + 63 + 27 lt=- 50.0;10.0;10.0;10.0 @@ -5900,10 +5843,10 @@ sounds : set(Sound) Relation - 3570 - 3770 - 70 - 30 + 3222 + 3402 + 63 + 27 lt=- 50.0;10.0;10.0;10.0 @@ -5911,10 +5854,10 @@ sounds : set(Sound) UMLClass - 3620 - 3740 - 230 - 80 + 3267 + 3375 + 207 + 72 *AnimationOverride* bg=pink @@ -5926,10 +5869,10 @@ overrides : set(AnimationOverride) UMLClass - 1980 - 410 - 310 - 80 + 1791 + 378 + 279 + 72 *RefundOnCondition* bg=yellow @@ -5942,10 +5885,10 @@ refund_amount : set(ResourceAmount) Relation - 2280 - 440 - 80 - 30 + 2061 + 405 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5953,10 +5896,10 @@ refund_amount : set(ResourceAmount) UMLClass - 1290 - 4410 - 230 - 80 + 1170 + 3978 + 207 + 72 *ProgressStatus* bg=pink @@ -5969,10 +5912,10 @@ progress : float Relation - 1510 - 4420 - 60 - 30 + 1368 + 3987 + 54 + 27 lt=<. 10.0;10.0;40.0;10.0 @@ -5980,10 +5923,10 @@ progress : float UMLClass - 170 - 6410 - 330 - 110 + 162 + 5778 + 297 + 99 *TimeRelativeAttributeChange* bg=pink @@ -5997,10 +5940,10 @@ ignore_protection : set(ProtectingAttribute) Relation - 490 - 6310 - 60 - 170 + 450 + 5688 + 54 + 153 lt=- 40.0;10.0;40.0;150.0;10.0;150.0 @@ -6008,10 +5951,10 @@ ignore_protection : set(ProtectingAttribute) UMLClass - 200 - 6540 - 260 - 60 + 189 + 5895 + 234 + 54 *TimeRelativeAttributeDecrease* @@ -6021,10 +5964,10 @@ bg=orange UMLClass - 200 - 6610 - 260 - 60 + 189 + 5958 + 234 + 54 *TimeRelativeAttributeIncrease* @@ -6034,10 +5977,10 @@ bg=orange Relation - 450 - 6560 - 50 - 100 + 414 + 5913 + 45 + 90 lt=- 10.0;80.0;30.0;80.0;30.0;10.0 @@ -6045,10 +5988,10 @@ bg=orange Relation - 450 - 6510 - 50 - 80 + 414 + 5868 + 45 + 72 lt=->> 10.0;60.0;30.0;60.0;30.0;10.0 @@ -6056,10 +5999,10 @@ bg=orange UMLClass - 250 - 6690 - 250 - 90 + 234 + 6030 + 225 + 81 *TimeRelativeProgressChange* bg=pink @@ -6072,10 +6015,10 @@ total_change_time : float Relation - 490 - 6450 - 60 - 300 + 450 + 5814 + 54 + 270 lt=- 40.0;10.0;40.0;280.0;10.0;280.0 @@ -6083,10 +6026,10 @@ total_change_time : float UMLClass - 1020 - 6410 - 250 - 90 + 927 + 5778 + 225 + 81 *TimeRelativeAttributeChange* bg=pink @@ -6098,10 +6041,10 @@ type : AttributeChangeType Relation - 1260 - 6310 - 60 - 160 + 1143 + 5688 + 54 + 144 lt=- 40.0;10.0;40.0;140.0;10.0;140.0 @@ -6109,10 +6052,10 @@ type : AttributeChangeType UMLClass - 1020 - 6540 - 220 - 60 + 927 + 5895 + 198 + 54 *FlatAttributeDecrease* @@ -6122,10 +6065,10 @@ bg=orange UMLClass - 1020 - 6610 - 220 - 60 + 927 + 5958 + 198 + 54 *FlatAttributeIncrease* @@ -6135,10 +6078,10 @@ bg=orange Relation - 1230 - 6560 - 50 - 100 + 1116 + 5913 + 45 + 90 lt=- 10.0;80.0;30.0;80.0;30.0;10.0 @@ -6146,10 +6089,10 @@ bg=orange Relation - 1230 - 6490 - 50 - 100 + 1116 + 5850 + 45 + 90 lt=->> 10.0;80.0;30.0;80.0;30.0;10.0 @@ -6157,10 +6100,10 @@ bg=orange UMLClass - 1020 - 6690 - 250 - 90 + 927 + 6030 + 225 + 81 *TimeRelativeProgressChange* bg=pink @@ -6173,10 +6116,10 @@ type : ProgressType Relation - 1260 - 6440 - 60 - 310 + 1143 + 5805 + 54 + 279 lt=- 40.0;10.0;40.0;290.0;10.0;290.0 @@ -6184,10 +6127,10 @@ type : ProgressType UMLClass - 2220 - 4180 - 120 - 60 + 2007 + 3771 + 108 + 54 *ProgressType* @@ -6197,10 +6140,10 @@ bg=pink Relation - 2140 - 4200 - 100 - 30 + 1935 + 3789 + 90 + 27 lt=- 10.0;10.0;80.0;10.0 @@ -6208,10 +6151,10 @@ bg=pink UMLClass - 5040 - 2670 - 270 - 80 + 4545 + 2412 + 243 + 72 *AoE1TradeRoute* bg=pink @@ -6224,10 +6167,10 @@ trade_amount : int UMLClass - 2460 - 4930 - 210 - 80 + 2223 + 4446 + 189 + 72 *StateChange* bg=pink @@ -6239,10 +6182,10 @@ state_change : StateChanger Relation - 2420 - 4960 - 60 - 30 + 2187 + 4473 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -6250,10 +6193,10 @@ state_change : StateChanger UMLClass - 2460 - 4750 - 150 - 80 + 2223 + 4284 + 135 + 72 *Terrain* bg=pink @@ -6267,10 +6210,10 @@ terrain : Terrain Relation - 2420 - 4780 - 60 - 30 + 2187 + 4311 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -6278,10 +6221,10 @@ terrain : Terrain Relation - 5970 - 4700 - 60 - 30 + 5382 + 4239 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -6289,10 +6232,10 @@ terrain : Terrain UMLClass - 6010 - 4670 - 260 - 80 + 5418 + 4212 + 234 + 72 *TerrainRequirement* bg=green @@ -6305,10 +6248,10 @@ blacklisted_terrains : set(Terrain) UMLClass - 6010 - 4570 - 250 - 90 + 5418 + 4122 + 225 + 81 *OverlayTerrain* bg=green @@ -6320,10 +6263,10 @@ terrain_overlay : Terrain UMLClass - 4950 - 4930 - 240 - 90 + 4464 + 4446 + 216 + 81 *Attribute* bg=pink @@ -6337,10 +6280,10 @@ abbreviation : TranslatedString Relation - 5060 - 4870 - 30 - 80 + 4563 + 4392 + 27 + 72 lt=<. 10.0;60.0;10.0;10.0 @@ -6348,10 +6291,10 @@ abbreviation : TranslatedString UMLClass - 1980 - 720 - 310 - 120 + 1791 + 657 + 279 + 108 *DepositResourcesOnProgress* bg=yellow @@ -6366,10 +6309,10 @@ blacklisted_entities : set(GameEntity) Relation - 2280 - 750 - 80 - 30 + 2061 + 684 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -6377,10 +6320,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 4410 - 2130 - 140 - 60 + 3978 + 1926 + 126 + 54 *PriceMode* @@ -6390,10 +6333,10 @@ bg=pink Relation - 4470 - 2400 - 30 - 80 + 4032 + 2169 + 27 + 72 lt=<. 10.0;10.0;10.0;60.0 @@ -6401,10 +6344,10 @@ bg=pink Relation - 4540 - 2150 - 170 - 30 + 4095 + 1944 + 153 + 27 lt=<<- 10.0;10.0;150.0;10.0 @@ -6412,10 +6355,10 @@ bg=pink UMLClass - 4690 - 2130 - 110 - 60 + 4230 + 1926 + 99 + 54 *Fixed* @@ -6425,10 +6368,10 @@ bg=pink Relation - 3520 - 3090 - 70 - 30 + 3177 + 2790 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -6436,10 +6379,10 @@ bg=pink Relation - 5000 - 2700 - 60 - 30 + 4509 + 2439 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -6447,10 +6390,10 @@ bg=pink Relation - 4650 - 2150 - 30 - 100 + 4194 + 1944 + 27 + 90 lt=- 10.0;10.0;10.0;80.0 @@ -6458,10 +6401,10 @@ bg=pink Relation - 4650 - 2220 - 60 - 30 + 4194 + 2007 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -6469,10 +6412,10 @@ bg=pink UMLClass - 4690 - 2200 - 150 - 100 + 4230 + 1989 + 135 + 90 *Dynamic* bg=pink @@ -6486,10 +6429,10 @@ max_price : float UMLClass - 4770 - 2330 - 120 - 60 + 4302 + 2106 + 108 + 54 *PricePool* @@ -6499,10 +6442,10 @@ bg=pink Relation - 4700 - 2350 - 90 - 30 + 4239 + 2124 + 81 + 27 lt=<. 70.0;10.0;10.0;10.0 @@ -6510,10 +6453,10 @@ bg=pink UMLClass - 750 - 80 - 250 - 60 + 684 + 81 + 225 + 54 *TimeRelativeAttributeChange* @@ -6523,10 +6466,10 @@ bg=yellow Relation - 990 - 100 - 90 - 30 + 900 + 99 + 81 + 27 lt=- 70.0;10.0;10.0;10.0 @@ -6534,10 +6477,10 @@ bg=yellow UMLClass - 770 - 10 - 230 - 60 + 702 + 18 + 207 + 54 *TimeRelativeProgressChange* @@ -6547,10 +6490,10 @@ bg=yellow Relation - 990 - 30 - 90 - 30 + 900 + 36 + 81 + 27 lt=- 70.0;10.0;10.0;10.0 @@ -6558,10 +6501,10 @@ bg=yellow UMLClass - 540 - 500 - 150 - 60 + 495 + 459 + 135 + 54 *Unconditional* @@ -6571,10 +6514,10 @@ bg=yellow Relation - 710 - 180 - 30 - 370 + 648 + 171 + 27 + 333 lt=- 10.0;10.0;10.0;350.0 @@ -6582,10 +6525,10 @@ bg=yellow UMLClass - 1530 - 340 - 170 - 60 + 1386 + 315 + 153 + 54 *Unconditional* @@ -6595,10 +6538,10 @@ bg=yellow Relation - 1490 - 360 - 60 - 30 + 1350 + 333 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -6606,10 +6549,10 @@ bg=yellow UMLClass - 1010 - 2740 - 240 - 80 + 918 + 2475 + 216 + 72 *Cost* bg=pink @@ -6621,10 +6564,10 @@ payment_mode : PaymentMode UMLClass - 1310 - 2690 - 230 - 80 + 1188 + 2430 + 207 + 72 *ResourceCost* bg=pink @@ -6636,10 +6579,10 @@ amount : set(ResourceAmount) UMLClass - 1310 - 2790 - 230 - 80 + 1188 + 2520 + 207 + 72 *AttributeCost* bg=pink @@ -6651,10 +6594,10 @@ amount : set(AttributeAmount) Relation - 1240 - 2770 - 70 - 30 + 1125 + 2502 + 63 + 27 lt=<<- 10.0;10.0;50.0;10.0 @@ -6662,10 +6605,10 @@ amount : set(AttributeAmount) Relation - 1280 - 2720 - 50 - 70 + 1161 + 2457 + 45 + 63 lt=- 10.0;50.0;10.0;10.0;30.0;10.0 @@ -6673,10 +6616,10 @@ amount : set(AttributeAmount) Relation - 1280 - 2760 - 50 - 90 + 1161 + 2493 + 45 + 81 lt=- 10.0;10.0;10.0;70.0;30.0;70.0 @@ -6684,10 +6627,10 @@ amount : set(AttributeAmount) UMLClass - 1260 - 830 - 300 - 80 + 1143 + 756 + 270 + 72 *CreationAttributeCost* bg=yellow @@ -6701,10 +6644,10 @@ creatables : set(CreatableGameEntity) Relation - 1200 - 860 - 80 - 30 + 1089 + 783 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -6712,10 +6655,10 @@ creatables : set(CreatableGameEntity) Relation - 1150 - 1040 - 80 - 30 + 1044 + 945 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -6723,10 +6666,10 @@ creatables : set(CreatableGameEntity) UMLClass - 1260 - 560 - 290 - 80 + 1143 + 513 + 261 + 72 *ResearchAttributeCost* bg=yellow @@ -6740,10 +6683,10 @@ researchables : set(ResearchableTech) Relation - 1200 - 590 - 80 - 30 + 1089 + 540 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -6751,10 +6694,10 @@ researchables : set(ResearchableTech) Relation - 1120 - 2810 - 30 - 70 + 1017 + 2538 + 27 + 63 lt=- 10.0;50.0;10.0;10.0 @@ -6762,10 +6705,10 @@ researchables : set(ResearchableTech) UMLClass - 1060 - 2640 - 140 - 60 + 963 + 2385 + 126 + 54 *PaymentMode* @@ -6775,10 +6718,10 @@ bg=pink Relation - 1120 - 2690 - 30 - 70 + 1017 + 2430 + 27 + 63 lt=<. 10.0;10.0;10.0;50.0 @@ -6786,10 +6729,10 @@ bg=pink UMLClass - 1000 - 2420 - 110 - 60 + 909 + 2187 + 99 + 54 *Advance* @@ -6799,10 +6742,10 @@ bg=pink UMLClass - 1000 - 2560 - 110 - 60 + 909 + 2313 + 99 + 54 *Arrear* @@ -6812,10 +6755,10 @@ bg=pink UMLClass - 1000 - 2490 - 110 - 60 + 909 + 2250 + 99 + 54 *Adaptive* @@ -6825,10 +6768,10 @@ bg=pink Relation - 1140 - 2370 - 30 - 290 + 1035 + 2142 + 27 + 261 lt=<<- 10.0;270.0;10.0;10.0 @@ -6836,10 +6779,10 @@ bg=pink Relation - 1100 - 2580 - 70 - 30 + 999 + 2331 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -6847,10 +6790,10 @@ bg=pink Relation - 1100 - 2510 - 70 - 30 + 999 + 2268 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -6858,10 +6801,10 @@ bg=pink Relation - 1100 - 2440 - 70 - 30 + 999 + 2205 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -6869,10 +6812,10 @@ bg=pink UMLClass - 2450 - 4010 - 160 - 60 + 2214 + 3618 + 144 + 54 *AttributeChange* @@ -6882,10 +6825,10 @@ bg=pink Relation - 2330 - 4200 - 110 - 30 + 2106 + 3789 + 99 + 27 lt=<<- 10.0;10.0;90.0;10.0 @@ -6893,10 +6836,10 @@ bg=pink UMLClass - 200 - 6800 - 260 - 60 + 189 + 6129 + 234 + 54 *TimeRelativeProgressDecrease* @@ -6906,10 +6849,10 @@ bg=orange UMLClass - 200 - 6870 - 260 - 60 + 189 + 6192 + 234 + 54 *TimeRelativeProgressIncrease* @@ -6919,10 +6862,10 @@ bg=orange Relation - 450 - 6820 - 50 - 100 + 414 + 6147 + 45 + 90 lt=- 10.0;80.0;30.0;80.0;30.0;10.0 @@ -6930,10 +6873,10 @@ bg=orange Relation - 450 - 6770 - 50 - 80 + 414 + 6102 + 45 + 72 lt=->> 10.0;60.0;30.0;60.0;30.0;10.0 @@ -6941,10 +6884,10 @@ bg=orange UMLClass - 980 - 6800 - 260 - 60 + 891 + 6129 + 234 + 54 *TimeRelativeProgressDecrease* @@ -6954,10 +6897,10 @@ bg=orange UMLClass - 980 - 6870 - 260 - 60 + 891 + 6192 + 234 + 54 *TimeRelativeProgressIncrease* @@ -6967,10 +6910,10 @@ bg=orange Relation - 1230 - 6820 - 50 - 100 + 1116 + 6147 + 45 + 90 lt=- 10.0;80.0;30.0;80.0;30.0;10.0 @@ -6978,10 +6921,10 @@ bg=orange Relation - 1230 - 6770 - 50 - 80 + 1116 + 6102 + 45 + 72 lt=->> 10.0;60.0;30.0;60.0;30.0;10.0 @@ -6989,10 +6932,10 @@ bg=orange UMLClass - 1000 - 2350 - 110 - 60 + 909 + 2124 + 99 + 54 *Shadow* @@ -7002,10 +6945,10 @@ bg=pink Relation - 1100 - 2370 - 70 - 30 + 999 + 2142 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -7013,10 +6956,10 @@ bg=pink UMLClass - 6510 - 4060 - 160 - 60 + 5868 + 3663 + 144 + 54 *HerdableMode* @@ -7026,10 +6969,10 @@ bg=pink Relation - 6580 - 4020 - 30 - 60 + 5931 + 3627 + 27 + 54 lt=<. 10.0;40.0;10.0;10.0 @@ -7037,10 +6980,10 @@ bg=pink UMLClass - 6600 - 4150 - 160 - 60 + 5949 + 3744 + 144 + 54 *ClosestHerding* @@ -7050,10 +6993,10 @@ bg=pink Relation - 6550 - 4110 - 30 - 230 + 5904 + 3708 + 27 + 207 lt=<<- 10.0;10.0;10.0;210.0 @@ -7061,10 +7004,10 @@ bg=pink Relation - 6550 - 4170 - 70 - 30 + 5904 + 3762 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -7072,10 +7015,10 @@ bg=pink UMLClass - 6600 - 4220 - 200 - 60 + 5949 + 3807 + 180 + 54 *LongestTimeInRange* @@ -7085,10 +7028,10 @@ bg=pink UMLClass - 6600 - 4290 - 160 - 60 + 5949 + 3870 + 144 + 54 *MostHerding* @@ -7098,10 +7041,10 @@ bg=pink Relation - 6550 - 4240 - 70 - 30 + 5904 + 3825 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -7109,10 +7052,10 @@ bg=pink Relation - 6550 - 4310 - 70 - 30 + 5904 + 3888 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -7120,10 +7063,10 @@ bg=pink UMLClass - 1400 - 3900 - 120 - 60 + 1269 + 3519 + 108 + 54 *TerrainType* @@ -7133,10 +7076,10 @@ bg=pink Relation - 1450 - 3850 - 30 - 70 + 1314 + 3474 + 27 + 63 lt=<. 10.0;50.0;10.0;10.0 @@ -7144,10 +7087,10 @@ bg=pink UMLClass - 1230 - 5370 - 260 - 90 + 1116 + 4842 + 234 + 81 *Stacked* bg=pink @@ -7162,10 +7105,10 @@ distribution_type : DistributionType Relation - 1180 - 5310 - 70 - 30 + 1071 + 4788 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -7173,10 +7116,10 @@ distribution_type : DistributionType UMLClass - 1620 - 5390 - 160 - 60 + 1467 + 4860 + 144 + 54 *CalculationType* @@ -7190,10 +7133,10 @@ bg=pink Relation - 1480 - 5410 - 160 - 30 + 1341 + 4878 + 144 + 27 lt=<. 140.0;10.0;10.0;10.0 @@ -7201,10 +7144,10 @@ bg=pink UMLClass - 1700 - 5650 - 160 - 100 + 1539 + 5094 + 144 + 90 *Hyperbolic* bg=pink @@ -7221,10 +7164,10 @@ scale_factor : float Relation - 1660 - 5440 - 30 - 260 + 1503 + 4905 + 27 + 234 lt=<<- 10.0;10.0;10.0;240.0 @@ -7232,10 +7175,10 @@ scale_factor : float Relation - 1500 - 6230 - 60 - 30 + 1359 + 5616 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -7243,10 +7186,10 @@ scale_factor : float Relation - 1660 - 5670 - 60 - 30 + 1503 + 5112 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -7254,10 +7197,10 @@ scale_factor : float UMLClass - 1700 - 5540 - 160 - 100 + 1539 + 4995 + 144 + 90 *Linear* bg=pink @@ -7274,10 +7217,10 @@ scale_factor : float Relation - 1660 - 5560 - 60 - 30 + 1503 + 5013 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -7285,10 +7228,10 @@ scale_factor : float UMLClass - 1700 - 5470 - 120 - 60 + 1539 + 4932 + 108 + 54 *NoStack* @@ -7301,10 +7244,10 @@ bg=pink Relation - 1660 - 5490 - 60 - 30 + 1503 + 4950 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -7312,10 +7255,10 @@ bg=pink UMLClass - 3570 - 3140 - 190 - 80 + 3222 + 2835 + 171 + 72 *OwnStorage* bg=pink @@ -7327,10 +7270,10 @@ container : EntityContainer Relation - 3520 - 3170 - 70 - 30 + 3177 + 2862 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -7338,10 +7281,10 @@ container : EntityContainer UMLClass - 3580 - 2330 - 310 - 80 + 3231 + 2106 + 279 + 72 *ProductionQueue* bg=green @@ -7354,10 +7297,10 @@ production_modes : set(ProductionMode) Relation - 3880 - 2360 - 60 - 120 + 3501 + 2133 + 54 + 108 lt=- 40.0;100.0;40.0;10.0;10.0;10.0 @@ -7365,10 +7308,10 @@ production_modes : set(ProductionMode) UMLClass - 3670 - 2230 - 160 - 60 + 3312 + 2016 + 144 + 54 *ProductionMode* @@ -7378,10 +7321,10 @@ bg=pink Relation - 3740 - 2280 - 30 - 70 + 3375 + 2061 + 27 + 63 lt=<. 10.0;10.0;10.0;50.0 @@ -7389,10 +7332,10 @@ bg=pink UMLClass - 3940 - 2310 - 270 - 80 + 3555 + 2088 + 243 + 72 *Creatables* bg=pink @@ -7404,10 +7347,10 @@ exclude : set(CreatableGameEntity) Relation - 3820 - 2250 - 140 - 30 + 3447 + 2034 + 126 + 27 lt=<<- 10.0;10.0;120.0;10.0 @@ -7415,10 +7358,10 @@ exclude : set(CreatableGameEntity) UMLClass - 3940 - 2220 - 240 - 80 + 3555 + 2007 + 216 + 72 *Researchables* bg=pink @@ -7430,10 +7373,10 @@ exclude : set(ResearchableTech) Relation - 3900 - 2250 - 60 - 120 + 3519 + 2034 + 54 + 108 lt=- 10.0;10.0;10.0;100.0;40.0;100.0 @@ -7441,10 +7384,10 @@ exclude : set(ResearchableTech) UMLClass - 1640 - 4020 - 200 - 80 + 1485 + 3627 + 180 + 72 *LogicElement* bg=pink @@ -7456,10 +7399,10 @@ only_once : bool UMLClass - 1990 - 4120 - 100 - 60 + 1800 + 3717 + 90 + 54 *AND* @@ -7469,10 +7412,10 @@ bg=pink Relation - 1960 - 4090 - 30 - 320 + 1773 + 3690 + 27 + 288 lt=<<- 10.0;10.0;10.0;300.0 @@ -7480,10 +7423,10 @@ bg=pink UMLClass - 1990 - 4190 - 100 - 60 + 1800 + 3780 + 90 + 54 *OR* @@ -7493,10 +7436,10 @@ bg=pink UMLClass - 1990 - 4260 - 140 - 80 + 1800 + 3843 + 126 + 72 *SUBSETMIN* bg=pink @@ -7508,10 +7451,10 @@ size : int Relation - 1940 - 4210 - 50 - 30 + 1755 + 3798 + 45 + 27 lt=- 30.0;10.0;10.0;10.0 @@ -7519,10 +7462,10 @@ size : int Relation - 1960 - 4210 - 50 - 30 + 1773 + 3798 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7530,10 +7473,10 @@ size : int Relation - 1960 - 4140 - 50 - 30 + 1773 + 3735 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7541,10 +7484,10 @@ size : int Relation - 6350 - 3890 - 60 - 30 + 5724 + 3510 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -7552,10 +7495,10 @@ size : int Relation - 6380 - 3790 - 60 - 30 + 5751 + 3420 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -7563,10 +7506,10 @@ size : int Relation - 6380 - 3690 - 60 - 30 + 5751 + 3330 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -7574,10 +7517,10 @@ size : int UMLClass - 1340 - 4150 - 210 - 80 + 1215 + 3744 + 189 + 72 *LiteralScope* bg=pink @@ -7589,10 +7532,10 @@ stances : set(DiplomaticStance) Relation - 1540 - 4180 - 90 - 30 + 1395 + 3771 + 81 + 27 lt=<. 10.0;10.0;70.0;10.0 @@ -7600,10 +7543,10 @@ stances : set(DiplomaticStance) UMLClass - 1310 - 4250 - 100 - 60 + 1188 + 3834 + 90 + 54 *Any* @@ -7614,10 +7557,10 @@ bg=pink Relation - 1420 - 4220 - 30 - 150 + 1287 + 3807 + 27 + 135 lt=<<- 10.0;10.0;10.0;130.0 @@ -7625,10 +7568,10 @@ bg=pink UMLClass - 1310 - 4320 - 100 - 60 + 1188 + 3897 + 90 + 54 *Self* @@ -7638,10 +7581,10 @@ bg=pink Relation - 1400 - 4340 - 50 - 30 + 1269 + 3915 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7649,10 +7592,10 @@ bg=pink Relation - 1400 - 4270 - 50 - 30 + 1269 + 3852 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7660,10 +7603,10 @@ bg=pink Relation - 1770 - 4290 - 50 - 30 + 1602 + 3870 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7671,10 +7614,10 @@ bg=pink Relation - 1770 - 4380 - 50 - 30 + 1602 + 3951 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7682,10 +7625,10 @@ bg=pink UMLClass - 1560 - 4450 - 220 - 80 + 1413 + 4014 + 198 + 72 *ResourceSpotsDepleted* bg=pink @@ -7697,10 +7640,10 @@ only_enabled : bool UMLClass - 1560 - 4540 - 220 - 80 + 1413 + 4095 + 198 + 72 *Timer* bg=pink @@ -7712,10 +7655,10 @@ time : float Relation - 1770 - 4480 - 50 - 30 + 1602 + 4041 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7723,10 +7666,10 @@ time : float Relation - 1770 - 4570 - 50 - 30 + 1602 + 4122 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7734,10 +7677,10 @@ time : float UMLClass - 1560 - 4630 - 220 - 80 + 1413 + 4176 + 198 + 72 *AttributeBelowValue* bg=pink @@ -7750,10 +7693,10 @@ threshold : float UMLClass - 1560 - 4900 - 220 - 80 + 1413 + 4419 + 198 + 72 *ProjectilePassThrough* bg=pink @@ -7765,10 +7708,10 @@ pass_through_range : int UMLClass - 1590 - 4990 - 190 - 60 + 1440 + 4500 + 171 + 54 *ProjectileHitTerrain* @@ -7778,10 +7721,10 @@ bg=pink Relation - 1770 - 4660 - 50 - 30 + 1602 + 4203 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7789,10 +7732,10 @@ bg=pink Relation - 1770 - 4930 - 50 - 30 + 1602 + 4446 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7800,10 +7743,10 @@ bg=pink Relation - 1770 - 5010 - 50 - 30 + 1602 + 4518 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7811,10 +7754,10 @@ bg=pink UMLClass - 5380 - 4420 - 140 - 100 + 4851 + 3987 + 126 + 90 *Hitbox* bg=pink @@ -7828,10 +7771,10 @@ radius_z : float Relation - 5310 - 4450 - 90 - 30 + 4788 + 4014 + 81 + 27 lt=<. 70.0;10.0;10.0;10.0 @@ -7839,16 +7782,15 @@ radius_z : float UMLClass - 5100 - 4100 - 270 - 100 + 4599 + 3699 + 243 + 90 *DetectCloak (SWGB)* bg=green -- -range : float allowed_types : set(GameEntityType) blacklisted_entities : set(GameEntity) @@ -7856,10 +7798,10 @@ blacklisted_entities : set(GameEntity) Relation - 5060 - 4130 - 60 - 30 + 4563 + 3726 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -7867,10 +7809,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 1420 - 5490 - 160 - 60 + 1287 + 4950 + 144 + 54 *DistributionType* @@ -7886,10 +7828,10 @@ bg=pink Relation - 1360 - 5450 - 80 - 90 + 1233 + 4914 + 72 + 81 lt=<. 60.0;70.0;10.0;70.0;10.0;10.0 @@ -7897,10 +7839,10 @@ bg=pink UMLClass - 1510 - 5570 - 100 - 60 + 1368 + 5022 + 90 + 54 *Mean* @@ -7910,10 +7852,10 @@ bg=pink Relation - 1470 - 5540 - 30 - 80 + 1332 + 4995 + 27 + 72 lt=<<- 10.0;10.0;10.0;60.0 @@ -7921,10 +7863,10 @@ bg=pink Relation - 1470 - 5590 - 60 - 30 + 1332 + 5040 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -7932,10 +7874,10 @@ bg=pink UMLClass - 6250 - 4080 - 140 - 90 + 5634 + 3681 + 126 + 81 *Rectangle* bg=pink @@ -7948,10 +7890,10 @@ height : float UMLClass - 6070 - 4080 - 140 - 60 + 5472 + 3681 + 126 + 54 *MatchToSprite* @@ -7961,10 +7903,10 @@ bg=pink UMLClass - 6160 - 3980 - 140 - 60 + 5553 + 3591 + 126 + 54 *SelectionBox* @@ -7974,10 +7916,10 @@ bg=pink Relation - 6220 - 3940 - 30 - 60 + 5607 + 3555 + 27 + 54 lt=<. 10.0;40.0;10.0;10.0 @@ -7985,10 +7927,10 @@ bg=pink Relation - 6260 - 4030 - 30 - 70 + 5643 + 3636 + 27 + 63 lt=<<- 10.0;10.0;10.0;50.0 @@ -7996,10 +7938,10 @@ bg=pink Relation - 6180 - 4030 - 30 - 70 + 5571 + 3636 + 27 + 63 lt=<<- 10.0;10.0;10.0;50.0 @@ -8007,10 +7949,10 @@ bg=pink UMLClass - 1440 - 1760 - 170 - 80 + 1305 + 1593 + 153 + 72 *Stacked* bg=pink @@ -8022,10 +7964,10 @@ stack_limit : int Relation - 1650 - 1620 - 30 - 290 + 1494 + 1467 + 27 + 261 lt=<<- 10.0;10.0;10.0;270.0 @@ -8033,10 +7975,10 @@ stack_limit : int Relation - 5970 - 4600 - 60 - 30 + 5382 + 4149 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -8044,10 +7986,10 @@ stack_limit : int UMLClass - 1290 - 3320 - 120 - 60 + 1170 + 2997 + 108 + 54 *NyanPatch* @@ -8057,10 +7999,10 @@ bg=pink Relation - 1230 - 3340 - 80 - 30 + 1116 + 3015 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -8068,10 +8010,10 @@ bg=pink UMLClass - 1820 - 4630 - 220 - 80 + 1647 + 4176 + 198 + 72 *AttributeAbovePercentage* bg=pink @@ -8084,10 +8026,10 @@ threshold : float Relation - 1770 - 4750 - 50 - 30 + 1602 + 4284 + 45 + 27 lt=- 30.0;10.0;10.0;10.0 @@ -8095,10 +8037,10 @@ threshold : float Relation - 2040 - 3540 - 90 - 30 + 1845 + 3195 + 81 + 27 lt=- 70.0;10.0;10.0;10.0 @@ -8106,10 +8048,10 @@ threshold : float UMLClass - 2110 - 3420 - 190 - 80 + 1908 + 3087 + 171 + 72 *Palette* bg=pink @@ -8121,10 +8063,10 @@ palette : file Relation - 2040 - 3450 - 90 - 30 + 1845 + 3114 + 81 + 27 lt=- 70.0;10.0;10.0;10.0 @@ -8132,10 +8074,10 @@ palette : file Relation - 4550 - 3860 - 70 - 30 + 4104 + 3483 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -8143,10 +8085,10 @@ palette : file UMLClass - 4600 - 4000 - 160 - 80 + 4149 + 3609 + 144 + 72 *Guard* bg=pink @@ -8158,10 +8100,10 @@ range : float Relation - 4550 - 4030 - 70 - 30 + 4104 + 3636 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -8169,10 +8111,10 @@ range : float UMLClass - 3570 - 2980 - 210 - 80 + 3222 + 2691 + 189 + 72 *Replace* bg=pink @@ -8184,10 +8126,10 @@ game_entities : set(GameEntity) Relation - 3520 - 3010 - 70 - 30 + 3177 + 2718 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -8195,10 +8137,10 @@ game_entities : set(GameEntity) UMLClass - 1070 - 4000 - 120 - 60 + 972 + 3609 + 108 + 54 *TechType* @@ -8208,10 +8150,10 @@ bg=pink Relation - 1180 - 4020 - 80 - 30 + 1071 + 3627 + 72 + 27 lt=- 10.0;10.0;60.0;10.0 @@ -8219,10 +8161,10 @@ bg=pink Relation - 1120 - 4050 - 30 - 70 + 1017 + 3654 + 27 + 63 lt=<. 10.0;10.0;10.0;50.0 @@ -8230,10 +8172,10 @@ bg=pink UMLClass - 1090 - 3910 - 100 - 60 + 990 + 3528 + 90 + 54 *Any* @@ -8243,10 +8185,10 @@ bg=pink Relation - 1130 - 3960 - 30 - 60 + 1026 + 3573 + 27 + 54 lt=<<- 10.0;40.0;10.0;10.0 @@ -8254,10 +8196,10 @@ bg=pink UMLClass - 1410 - 3990 - 100 - 60 + 1278 + 3600 + 90 + 54 *Any* @@ -8267,10 +8209,10 @@ bg=pink Relation - 1450 - 3950 - 30 - 60 + 1314 + 3564 + 27 + 54 lt=<<- 10.0;10.0;10.0;40.0 @@ -8278,10 +8220,10 @@ bg=pink UMLClass - 720 - 3480 - 100 - 60 + 657 + 3141 + 90 + 54 *Any* @@ -8291,10 +8233,10 @@ bg=pink Relation - 810 - 3500 - 70 - 70 + 738 + 3159 + 63 + 63 lt=<<- 50.0;50.0;50.0;10.0;10.0;10.0 @@ -8302,10 +8244,10 @@ bg=pink UMLClass - 2460 - 4570 - 230 - 80 + 2223 + 4122 + 207 + 72 *AnimationOverlay* bg=pink @@ -8318,10 +8260,10 @@ overlays : set(Animation) Relation - 2420 - 4600 - 60 - 30 + 2187 + 4149 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -8329,10 +8271,10 @@ overlays : set(Animation) UMLClass - 1820 - 4540 - 220 - 80 + 1647 + 4095 + 198 + 72 *AttributeBelowPercentage* bg=pink @@ -8345,10 +8287,10 @@ threshold : float UMLClass - 1560 - 4720 - 220 - 80 + 1413 + 4257 + 198 + 72 *AttributeAboveValue* bg=pink @@ -8361,10 +8303,10 @@ threshold : float Relation - 1790 - 4570 - 50 - 30 + 1620 + 4122 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -8372,10 +8314,10 @@ threshold : float Relation - 1790 - 4660 - 50 - 30 + 1620 + 4203 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -8383,10 +8325,10 @@ threshold : float UMLClass - 370 - 420 - 320 - 70 + 342 + 387 + 288 + 63 *ElevationDifferenceHigh* bg=yellow @@ -8398,10 +8340,10 @@ min_elevation_difference : optional(float) = None Relation - 680 - 450 - 60 - 30 + 621 + 414 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -8409,10 +8351,10 @@ min_elevation_difference : optional(float) = None Relation - 680 - 520 - 60 - 30 + 621 + 477 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -8420,10 +8362,10 @@ min_elevation_difference : optional(float) = None UMLClass - 1530 + 1386 90 - 320 - 80 + 288 + 72 *ElevationDifferenceHigh* bg=yellow @@ -8435,10 +8377,10 @@ min_elevation_difference : optional(float) = None Relation - 1490 - 120 - 60 - 30 + 1350 + 117 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -8446,10 +8388,10 @@ min_elevation_difference : optional(float) = None UMLClass - 370 - 4140 - 130 - 60 + 342 + 3735 + 117 + 54 *MiscVariant* @@ -8459,10 +8401,10 @@ bg=pink Relation - 490 - 3970 - 180 - 220 + 450 + 3582 + 162 + 198 lt=- 160.0;10.0;160.0;200.0;10.0;200.0 @@ -8470,10 +8412,10 @@ bg=pink UMLClass - 4630 - 3330 - 240 - 80 + 4176 + 3006 + 216 + 72 *ResourceStorage* bg=green @@ -8485,10 +8427,10 @@ containers : set(ResourceContainer) Relation - 4860 - 3360 - 60 - 30 + 4383 + 3033 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -8496,10 +8438,10 @@ containers : set(ResourceContainer) UMLClass - 4360 - 3330 - 240 - 100 + 3933 + 3006 + 216 + 90 *ResourceContainer* bg=pink @@ -8513,10 +8455,10 @@ carry_progress : set(Progress) Relation - 4590 - 3360 - 60 - 30 + 4140 + 3033 + 54 + 27 lt=<. 10.0;10.0;40.0;10.0 @@ -8524,10 +8466,10 @@ carry_progress : set(Progress) UMLClass - 4390 - 3460 - 180 - 80 + 3960 + 3123 + 162 + 72 *InternalDropSite* bg=pink @@ -8539,10 +8481,10 @@ update_time : float Relation - 4470 - 3420 - 30 - 60 + 4032 + 3087 + 27 + 54 lt=<<- 10.0;10.0;10.0;40.0 @@ -8550,10 +8492,10 @@ update_time : float UMLClass - 1260 - 3010 - 140 - 60 + 1143 + 2718 + 126 + 54 *TransformPool* @@ -8563,10 +8505,10 @@ bg=pink Relation - 1390 - 3030 - 50 - 30 + 1260 + 2736 + 45 + 27 lt=<. 10.0;10.0;30.0;10.0 @@ -8574,10 +8516,10 @@ bg=pink Relation - 4450 - 4510 - 60 - 30 + 4014 + 4068 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -8585,10 +8527,10 @@ bg=pink UMLClass - 2360 - 3320 - 100 - 60 + 2133 + 2997 + 90 + 54 *Any* @@ -8598,10 +8540,10 @@ bg=pink Relation - 2400 - 3280 - 30 - 60 + 2169 + 2961 + 27 + 54 lt=- 10.0;40.0;10.0;10.0 @@ -8609,10 +8551,10 @@ bg=pink Relation - 2520 - 3280 - 30 - 60 + 2277 + 2961 + 27 + 54 lt=- 10.0;40.0;10.0;10.0 @@ -8620,10 +8562,10 @@ bg=pink UMLClass - 4300 - 2310 - 410 - 100 + 3879 + 2088 + 369 + 90 *ExchangeRate* bg=pink @@ -8637,10 +8579,10 @@ price_pool : optional(PricePool) = None UMLClass - 4370 - 2460 - 260 - 110 + 3942 + 2223 + 234 + 99 *ExchangeResources* bg=green @@ -8655,10 +8597,10 @@ exchange_modes : set(ExchangeMode) Relation - 4470 - 2180 - 30 - 150 + 4032 + 1971 + 27 + 135 lt=<. 10.0;10.0;10.0;130.0 @@ -8666,10 +8608,10 @@ exchange_modes : set(ExchangeMode) UMLClass - 4670 - 2470 - 160 - 80 + 4212 + 2232 + 144 + 72 *ExchangeMode* bg=pink @@ -8681,10 +8623,10 @@ fee_multiplier : float Relation - 4620 - 2500 - 70 - 30 + 4167 + 2259 + 63 + 27 lt=<. 50.0;10.0;10.0;10.0 @@ -8692,10 +8634,10 @@ fee_multiplier : float UMLClass - 4910 - 2440 - 100 - 60 + 4428 + 2205 + 90 + 54 *Sell* @@ -8705,10 +8647,10 @@ bg=pink UMLClass - 4910 - 2510 - 100 - 60 + 4428 + 2268 + 90 + 54 *Buy* @@ -8718,10 +8660,10 @@ bg=pink Relation - 4820 - 2480 - 110 - 30 + 4347 + 2241 + 99 + 27 lt=<<- 10.0;10.0;90.0;10.0 @@ -8729,10 +8671,10 @@ bg=pink Relation - 4820 - 2510 - 110 - 30 + 4347 + 2268 + 99 + 27 lt=<<- 10.0;10.0;90.0;10.0 @@ -8740,10 +8682,10 @@ bg=pink Relation - 1710 - 4090 - 30 - 80 + 1548 + 3690 + 27 + 72 lt=<<- 10.0;10.0;10.0;60.0 @@ -8751,10 +8693,10 @@ bg=pink Relation - 1830 - 4050 - 70 - 30 + 1656 + 3654 + 63 + 27 lt=<<- 10.0;10.0;50.0;10.0 @@ -8762,10 +8704,10 @@ bg=pink UMLClass - 1880 - 4020 - 190 - 80 + 1701 + 3627 + 171 + 72 *LogicGate* bg=pink @@ -8777,10 +8719,10 @@ inputs : set(LogicElement) UMLClass - 1990 - 4350 - 140 - 80 + 1800 + 3924 + 126 + 72 *SUBSETMAX* bg=pink @@ -8792,10 +8734,10 @@ size : int Relation - 1960 - 4380 - 50 - 30 + 1773 + 3951 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -8803,10 +8745,10 @@ size : int UMLClass - 1850 - 4120 - 100 - 60 + 1674 + 3717 + 90 + 54 *NOT* @@ -8816,10 +8758,10 @@ bg=pink Relation - 1940 - 4140 - 50 - 30 + 1755 + 3735 + 45 + 27 lt=- 30.0;10.0;10.0;10.0 @@ -8827,10 +8769,10 @@ bg=pink UMLClass - 1850 - 4190 - 100 - 60 + 1674 + 3780 + 90 + 54 *XOR* @@ -8840,10 +8782,10 @@ bg=pink Relation - 1960 - 4290 - 50 - 30 + 1773 + 3870 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -8851,10 +8793,10 @@ bg=pink UMLClass - 1850 - 4260 - 100 - 60 + 1674 + 3843 + 90 + 54 *MULTIXOR* @@ -8864,10 +8806,10 @@ bg=pink Relation - 1940 - 4280 - 50 - 30 + 1755 + 3861 + 45 + 27 lt=- 30.0;10.0;10.0;10.0 @@ -8875,10 +8817,10 @@ bg=pink UMLClass - 3510 - 3650 - 150 - 60 + 3168 + 3294 + 135 + 54 *AbilityProperty* @@ -8888,10 +8830,10 @@ bg=pink Relation - 3570 - 3620 - 30 - 50 + 3222 + 3267 + 27 + 45 lt=- 10.0;10.0;10.0;30.0 @@ -8899,10 +8841,10 @@ bg=pink Relation - 3570 - 4130 - 70 - 30 + 3222 + 3726 + 63 + 27 lt=- 50.0;10.0;10.0;10.0 @@ -8910,10 +8852,10 @@ bg=pink UMLClass - 3620 - 4190 - 220 - 80 + 3267 + 3780 + 180 + 72 *Lock* bg=pink @@ -8925,10 +8867,10 @@ lock_pool : LockPool Relation - 3570 - 4220 - 70 - 30 + 3222 + 3807 + 63 + 27 lt=- 50.0;10.0;10.0;10.0 @@ -8936,10 +8878,10 @@ lock_pool : LockPool UMLClass - 5100 - 4010 - 190 - 80 + 4599 + 3618 + 171 + 72 *Lock* bg=green @@ -8951,10 +8893,10 @@ lock_pools : set(LockPool) Relation - 5060 - 4040 - 60 - 30 + 4563 + 3645 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -8962,10 +8904,10 @@ lock_pools : set(LockPool) UMLClass - 5330 - 4010 - 150 - 80 + 4806 + 3618 + 135 + 72 *LockPool* bg=pink @@ -8977,10 +8919,10 @@ slots : int Relation - 5280 - 4040 - 70 - 30 + 4761 + 3645 + 63 + 27 lt=<. 50.0;10.0;10.0;10.0 @@ -8988,10 +8930,10 @@ slots : int UMLClass - 1550 - 1570 - 160 - 60 + 1404 + 1422 + 144 + 54 *ModifierProperty* @@ -9001,10 +8943,10 @@ bg=pink Relation - 1700 - 1590 - 100 - 30 + 1539 + 1440 + 90 + 27 lt=- 80.0;10.0;10.0;10.0 @@ -9012,10 +8954,10 @@ bg=pink Relation - 1600 - 1690 - 80 - 30 + 1449 + 1530 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -9023,10 +8965,10 @@ bg=pink Relation - 1600 - 1790 - 80 - 30 + 1449 + 1620 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -9034,10 +8976,10 @@ bg=pink UMLClass - 1440 - 1850 - 170 - 80 + 1305 + 1674 + 153 + 72 *Multiplier* bg=pink @@ -9049,10 +8991,10 @@ multiplier : float Relation - 1600 - 1880 - 80 - 30 + 1449 + 1701 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -9060,10 +9002,10 @@ multiplier : float Relation - 2330 - 890 - 80 - 30 + 2106 + 810 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -9071,10 +9013,10 @@ multiplier : float UMLNote - 1080 - 450 - 60 - 30 + 981 + 414 + 54 + 27 effect bg=blue @@ -9083,10 +9025,10 @@ bg=blue UMLNote - 1270 - 450 - 90 - 30 + 1152 + 414 + 81 + 27 resistance bg=blue @@ -9095,10 +9037,10 @@ bg=blue UMLNote - 730 - 290 - 60 - 30 + 666 + 270 + 54 + 27 flac bg=blue @@ -9107,10 +9049,10 @@ bg=blue UMLNote - 1390 - 170 - 60 - 30 + 1260 + 162 + 54 + 27 flac bg=blue @@ -9119,10 +9061,10 @@ bg=blue UMLClass - 690 - 5490 - 140 - 60 + 630 + 4950 + 126 + 54 *EffectProperty* @@ -9132,10 +9074,10 @@ bg=pink Relation - 820 - 5510 - 170 - 30 + 747 + 4968 + 153 + 27 lt=- 10.0;10.0;150.0;10.0 @@ -9143,10 +9085,10 @@ bg=pink Relation - 750 - 5130 - 30 - 380 + 684 + 4626 + 27 + 342 lt=<<- 10.0;360.0;10.0;10.0 @@ -9154,10 +9096,10 @@ bg=pink UMLClass - 520 - 5370 - 200 - 80 + 477 + 4842 + 180 + 72 *Cost* bg=pink @@ -9169,10 +9111,10 @@ cost : Cost UMLClass - 480 - 5280 - 240 - 80 + 441 + 4761 + 216 + 72 *Diplomatic* bg=pink @@ -9184,10 +9126,10 @@ stances : set(DiplomaticStance) Relation - 890 - 4720 - 100 - 30 + 810 + 4257 + 90 + 27 lt=- 10.0;10.0;80.0;10.0 @@ -9195,10 +9137,10 @@ stances : set(DiplomaticStance) UMLClass - 550 - 5190 - 170 - 80 + 504 + 4680 + 153 + 72 *AreaEffect* bg=pink @@ -9211,10 +9153,10 @@ dropoff : DropoffType Relation - 710 - 5220 - 70 - 30 + 648 + 4707 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -9222,10 +9164,10 @@ dropoff : DropoffType Relation - 710 - 5310 - 70 - 30 + 648 + 4788 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -9233,10 +9175,10 @@ dropoff : DropoffType Relation - 710 - 5400 - 70 - 30 + 648 + 4869 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -9244,10 +9186,10 @@ dropoff : DropoffType UMLClass - 1120 - 5490 - 150 - 60 + 1017 + 4950 + 135 + 54 *ResistanceProperty* @@ -9257,10 +9199,10 @@ bg=pink Relation - 960 - 5510 - 180 - 30 + 873 + 4968 + 162 + 27 lt=- 10.0;10.0;160.0;10.0 @@ -9268,10 +9210,10 @@ bg=pink Relation - 1180 - 5310 - 30 - 200 + 1071 + 4788 + 27 + 180 lt=<<- 10.0;180.0;10.0;10.0 @@ -9279,10 +9221,10 @@ bg=pink Relation - 2420 - 4530 - 30 - 460 + 2187 + 4086 + 27 + 414 lt=<<- 10.0;10.0;10.0;440.0 @@ -9290,10 +9232,10 @@ bg=pink UMLClass - 2320 - 4480 - 160 - 60 + 2097 + 4041 + 144 + 54 *ProgressProperty* @@ -9304,10 +9246,10 @@ bg=pink Relation - 2140 - 4500 - 200 - 30 + 1935 + 4059 + 180 + 27 lt=- 10.0;10.0;180.0;10.0 @@ -9315,10 +9257,10 @@ bg=pink Relation - 1960 - 3740 - 30 - 80 + 1773 + 3375 + 27 + 72 lt=<<- 10.0;10.0;10.0;60.0 @@ -9326,10 +9268,10 @@ bg=pink UMLClass - 1920 - 3800 - 100 - 60 + 1737 + 3429 + 90 + 54 *Reset* @@ -9339,10 +9281,10 @@ bg=pink UMLClass - 1280 - 3080 - 100 - 60 + 1161 + 2781 + 90 + 54 *Reset* @@ -9352,10 +9294,10 @@ bg=pink Relation - 1370 - 3100 - 70 - 30 + 1242 + 2799 + 63 + 27 lt=<<- 50.0;10.0;10.0;10.0 @@ -9363,10 +9305,10 @@ bg=pink UMLClass - 1590 - 5060 - 190 - 80 + 1440 + 4563 + 171 + 72 *StateChangeActive* bg=pink @@ -9378,10 +9320,10 @@ state_change : StateChanger Relation - 1770 - 5090 - 50 - 30 + 1602 + 4590 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -9389,10 +9331,10 @@ state_change : StateChanger UMLClass - 1560 - 4810 - 220 - 80 + 1413 + 4338 + 198 + 72 *OwnsGameEntity* bg=pink @@ -9404,10 +9346,10 @@ game_entity : GameEntity Relation - 1770 - 4840 - 50 - 30 + 1602 + 4365 + 45 + 27 lt=- 30.0;10.0;10.0;10.0 @@ -9415,10 +9357,10 @@ game_entity : GameEntity UMLClass - 6520 - 2910 - 330 - 80 + 5877 + 2628 + 297 + 72 *EffectBatch* bg=pink @@ -9432,10 +9374,10 @@ properties : dict(BatchProperty, BatchProperty) = {} UMLClass - 6910 - 2920 - 150 - 60 + 6228 + 2637 + 135 + 54 *BatchProperty* @@ -9446,10 +9388,10 @@ bg=pink UMLClass - 6980 - 3010 - 170 - 80 + 6291 + 2718 + 153 + 72 *Priority* bg=pink @@ -9462,10 +9404,10 @@ priority : int Relation - 6940 - 2970 - 30 - 190 + 6255 + 2682 + 27 + 171 lt=<<- 10.0;10.0;10.0;170.0 @@ -9473,10 +9415,10 @@ priority : int Relation - 6940 - 3040 - 60 - 30 + 6255 + 2745 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9484,10 +9426,10 @@ priority : int UMLClass - 6980 - 3100 - 170 - 80 + 6291 + 2799 + 153 + 72 *Chance* bg=pink @@ -9500,10 +9442,10 @@ chance : float Relation - 6940 - 3130 - 60 - 30 + 6255 + 2826 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9511,10 +9453,10 @@ chance : float Relation - 6840 - 2940 - 90 - 30 + 6165 + 2655 + 81 + 27 lt=<. 70.0;10.0;10.0;10.0 @@ -9522,10 +9464,10 @@ chance : float Relation - 6460 - 2870 - 80 - 100 + 5823 + 2592 + 72 + 90 lt=<. 60.0;80.0;10.0;80.0;10.0;10.0 @@ -9533,10 +9475,10 @@ chance : float UMLClass - 6620 - 3010 - 150 - 60 + 5967 + 2718 + 135 + 54 *UnorderedBatch* @@ -9547,10 +9489,10 @@ bg=pink Relation - 6580 - 2980 - 30 - 220 + 5931 + 2691 + 27 + 198 lt=<<- 10.0;10.0;10.0;200.0 @@ -9558,10 +9500,10 @@ bg=pink Relation - 6580 - 3030 - 60 - 30 + 5931 + 2736 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9569,10 +9511,10 @@ bg=pink UMLClass - 6620 - 3080 - 150 - 60 + 5967 + 2781 + 135 + 54 *OrderedBatch* @@ -9584,10 +9526,10 @@ bg=pink Relation - 6580 - 3100 - 60 - 30 + 5931 + 2799 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9595,10 +9537,10 @@ bg=pink UMLClass - 6620 - 3150 - 150 - 60 + 5967 + 2844 + 135 + 54 *ChainedBatch* @@ -9609,10 +9551,10 @@ bg=pink Relation - 6580 - 3170 - 60 - 30 + 5931 + 2862 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9620,10 +9562,10 @@ bg=pink UMLClass - 520 - 5100 - 200 - 80 + 477 + 4599 + 180 + 72 *Priority* bg=pink @@ -9635,10 +9577,10 @@ priority : int Relation - 710 - 5130 - 70 - 30 + 648 + 4626 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -9646,10 +9588,10 @@ priority : int UMLClass - 1380 - 3410 - 120 - 60 + 1251 + 3078 + 108 + 54 *PatchProperty* @@ -9659,10 +9601,10 @@ bg=pink Relation - 1490 - 3430 - 60 - 30 + 1350 + 3096 + 54 + 27 lt=<<- 10.0;10.0;40.0;10.0 @@ -9670,10 +9612,10 @@ bg=pink Relation - 1350 - 3370 - 30 - 180 + 1224 + 3042 + 27 + 162 lt=<. 10.0;10.0;10.0;160.0 @@ -9681,10 +9623,10 @@ bg=pink Relation - 1430 - 3460 - 30 - 90 + 1296 + 3123 + 27 + 81 lt=<. 10.0;10.0;10.0;70.0 @@ -9692,10 +9634,10 @@ bg=pink Relation - 1610 - 4040 - 50 - 30 + 1458 + 3645 + 45 + 27 lt=<<- 30.0;10.0;10.0;10.0 @@ -9703,10 +9645,10 @@ bg=pink UMLClass - 1520 - 4070 - 100 - 60 + 1377 + 3672 + 90 + 54 *True* @@ -9716,10 +9658,10 @@ bg=pink UMLClass - 1520 - 4000 - 100 - 60 + 1377 + 3609 + 90 + 54 *False* @@ -9729,10 +9671,10 @@ bg=pink Relation - 1610 - 4070 - 50 - 30 + 1458 + 3672 + 45 + 27 lt=<<- 30.0;10.0;10.0;10.0 @@ -9740,10 +9682,10 @@ bg=pink Relation - 1490 - 30 - 60 - 30 + 1350 + 36 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9751,10 +9693,10 @@ bg=pink Relation - 1490 - 210 - 60 - 30 + 1350 + 198 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9762,10 +9704,10 @@ bg=pink UMLClass - 160 - 3630 - 120 - 60 + 153 + 3276 + 108 + 54 *Tree* @@ -9774,10 +9716,10 @@ bg=pink UMLClass - 160 - 3700 - 120 - 60 + 153 + 3339 + 108 + 54 *Relic* @@ -9786,10 +9728,10 @@ bg=pink Relation - 300 - 3620 - 130 - 30 + 279 + 3267 + 117 + 27 lt=<<- 110.0;10.0;10.0;10.0 @@ -9797,10 +9739,10 @@ bg=pink Relation - 300 - 3510 - 30 - 310 + 279 + 3168 + 27 + 279 lt=- 10.0;10.0;10.0;290.0 @@ -9808,10 +9750,10 @@ bg=pink Relation - 270 - 3720 - 60 - 30 + 252 + 3357 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9819,10 +9761,10 @@ bg=pink UMLClass - 160 - 3560 - 120 - 60 + 153 + 3213 + 108 + 54 *Swordsman* @@ -9831,10 +9773,10 @@ bg=pink UMLClass - 160 - 3490 - 120 - 60 + 153 + 3150 + 108 + 54 *Barracks* @@ -9843,10 +9785,10 @@ bg=pink Relation - 270 - 3650 - 60 - 30 + 252 + 3294 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9854,10 +9796,10 @@ bg=pink Relation - 270 - 3580 - 60 - 30 + 252 + 3231 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9865,10 +9807,10 @@ bg=pink Relation - 270 - 3510 - 60 - 30 + 252 + 3168 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9876,10 +9818,10 @@ bg=pink UMLClass - 160 - 3770 - 120 - 60 + 153 + 3402 + 108 + 54 *Projectile* @@ -9888,10 +9830,10 @@ bg=pink Relation - 270 - 3790 - 60 - 30 + 252 + 3420 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9899,10 +9841,10 @@ bg=pink UMLNote - 10 - 3490 - 140 - 70 + 18 + 3150 + 126 + 63 All ingame objects are game entities @@ -9912,10 +9854,10 @@ bg=blue UMLClass - 2450 - 4150 - 120 - 60 + 2214 + 3744 + 108 + 54 *Construct* @@ -9925,10 +9867,10 @@ bg=pink UMLClass - 2450 - 4220 - 120 - 60 + 2214 + 3807 + 108 + 54 *Harvest* @@ -9938,10 +9880,10 @@ bg=pink UMLClass - 2450 - 4290 - 120 - 60 + 2214 + 3870 + 108 + 54 *Restock* @@ -9951,10 +9893,10 @@ bg=pink UMLClass - 2450 - 4080 - 120 - 60 + 2214 + 3681 + 108 + 54 *Carry* @@ -9964,10 +9906,10 @@ bg=pink UMLClass - 2450 - 4360 - 120 - 60 + 2214 + 3933 + 108 + 54 *Transform* @@ -9977,10 +9919,10 @@ bg=pink Relation - 2410 - 4030 - 30 - 380 + 2178 + 3636 + 27 + 342 lt=- 10.0;10.0;10.0;360.0 @@ -9988,10 +9930,10 @@ bg=pink Relation - 2410 - 4030 - 60 - 30 + 2178 + 3636 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9999,10 +9941,10 @@ bg=pink Relation - 2410 - 4100 - 60 - 30 + 2178 + 3699 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -10010,10 +9952,10 @@ bg=pink Relation - 2410 - 4170 - 60 - 30 + 2178 + 3762 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -10021,10 +9963,10 @@ bg=pink Relation - 2410 - 4240 - 60 - 30 + 2178 + 3825 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -10032,10 +9974,10 @@ bg=pink Relation - 2410 - 4310 - 60 - 30 + 2178 + 3888 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -10043,10 +9985,10 @@ bg=pink Relation - 2410 - 4380 - 60 - 30 + 2178 + 3951 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -10054,10 +9996,10 @@ bg=pink UMLClass - 5700 - 4670 - 250 - 80 + 5139 + 4212 + 225 + 72 *Constructable* bg=green @@ -10070,10 +10012,10 @@ construction_progress : set(Progress) Relation - 5940 - 4700 - 60 - 30 + 5355 + 4239 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -10081,10 +10023,10 @@ construction_progress : set(Progress) UMLClass - 2200 - 2230 - 110 - 60 + 1989 + 2016 + 99 + 54 *Node* @@ -10094,21 +10036,21 @@ bg=pink Relation - 2250 - 2280 - 30 - 280 + 2034 + 2061 + 27 + 342 lt=<<- - 10.0;10.0;10.0;260.0 + 10.0;10.0;10.0;360.0 UMLClass - 2070 - 2320 - 160 - 80 + 1872 + 2097 + 144 + 72 *Start* bg=pink @@ -10120,10 +10062,10 @@ next : Node Relation - 2220 - 2350 - 60 - 30 + 2007 + 2124 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -10131,10 +10073,10 @@ next : Node UMLClass - 1850 - 2320 - 150 - 80 + 1674 + 2097 + 135 + 72 *Activity* bg=pink @@ -10146,10 +10088,10 @@ start : Start Relation - 1990 - 2350 - 100 - 30 + 1800 + 2124 + 90 + 27 lt=<. 80.0;10.0;10.0;10.0 @@ -10157,10 +10099,10 @@ start : Start UMLClass - 2120 - 2420 - 110 - 60 + 1917 + 2187 + 99 + 54 *End* @@ -10170,10 +10112,10 @@ bg=pink Relation - 2220 - 2440 - 60 - 30 + 2007 + 2205 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -10181,10 +10123,10 @@ bg=pink UMLClass - 2290 - 2320 - 220 - 80 + 2070 + 2097 + 198 + 72 *XORGate* bg=pink @@ -10197,10 +10139,10 @@ default : Node UMLClass - 2290 - 2410 - 220 - 80 + 2070 + 2259 + 198 + 72 *XOREventGate* bg=pink @@ -10212,26 +10154,26 @@ next : dict(Event, Node) UMLClass - 2060 - 2500 - 170 - 90 + 1863 + 2259 + 153 + 81 *Ability* bg=pink -- next : Node -ability : abstract(Ability) +ability : Ability Relation - 2250 - 2350 - 60 - 30 + 2034 + 2124 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -10239,10 +10181,10 @@ ability : abstract(Ability) Relation - 2250 - 2440 - 60 - 30 + 2034 + 2286 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -10250,10 +10192,10 @@ ability : abstract(Ability) Relation - 2220 - 2530 - 60 - 30 + 2007 + 2286 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -10261,10 +10203,10 @@ ability : abstract(Ability) UMLClass - 3710 - 3360 - 180 - 90 + 3348 + 3033 + 162 + 81 *Activity* bg=green @@ -10276,10 +10218,10 @@ graph : Activity Relation - 3880 - 3390 - 60 - 30 + 3501 + 3060 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -10287,10 +10229,10 @@ graph : Activity UMLNote - 1790 - 2270 - 160 - 30 + 1620 + 2052 + 144 + 27 Unit behaviour graph bg=blue @@ -10299,10 +10241,10 @@ bg=blue UMLClass - 2560 - 2420 - 110 - 60 + 2349 + 2268 + 99 + 54 *Event* @@ -10312,21 +10254,21 @@ bg=pink Relation - 2500 - 2440 - 80 - 30 + 2259 + 2286 + 108 + 27 lt=<. - 60.0;10.0;10.0;10.0 + 100.0;10.0;10.0;10.0 Relation - 2580 - 2470 - 30 - 240 + 2367 + 2313 + 27 + 216 lt=<<- 10.0;10.0;10.0;220.0 @@ -10334,10 +10276,10 @@ bg=pink UMLClass - 2610 - 2500 - 160 - 80 + 2394 + 2340 + 144 + 72 *Wait* bg=pink @@ -10349,10 +10291,10 @@ time : float UMLClass - 2610 - 2590 - 120 - 60 + 2394 + 2421 + 108 + 54 *WaitAbility* @@ -10362,10 +10304,10 @@ bg=pink Relation - 2580 - 2530 - 50 - 30 + 2367 + 2367 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -10373,10 +10315,10 @@ bg=pink Relation - 2580 - 2610 - 50 - 30 + 2367 + 2439 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -10384,10 +10326,10 @@ bg=pink UMLClass - 2610 - 2660 - 160 - 60 + 2394 + 2484 + 144 + 54 *CommandInQueue* @@ -10397,10 +10339,10 @@ bg=pink Relation - 2580 - 2680 - 50 - 30 + 2367 + 2502 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -10408,10 +10350,10 @@ bg=pink UMLClass - 2520 - 2070 - 140 - 80 + 2196 + 1665 + 126 + 72 *Condition* bg=pink @@ -10423,21 +10365,21 @@ next : Node Relation - 2450 - 2100 - 90 - 240 + 2142 + 1692 + 72 + 423 lt=<. - 70.0;10.0;10.0;10.0;10.0;220.0 + 60.0;10.0;10.0;10.0;10.0;450.0 UMLClass - 2610 - 2170 - 160 - 60 + 2277 + 1755 + 144 + 54 *CommandInQueue* @@ -10447,21 +10389,21 @@ bg=pink Relation - 2580 - 2140 - 30 - 220 + 2250 + 1728 + 27 + 306 lt=<<- - 10.0;10.0;10.0;200.0 + 10.0;10.0;10.0;320.0 Relation - 2580 - 2190 - 50 - 30 + 2250 + 1773 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -10469,23 +10411,25 @@ bg=pink UMLClass - 2610 - 2240 - 160 - 60 + 2277 + 1818 + 144 + 72 - -*NextCommandIdle* -bg=pink + *NextCommand* +bg=pink + +-- +command : Command Relation - 2580 - 2260 - 50 - 30 + 2250 + 1836 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -10493,60 +10437,433 @@ bg=pink Relation - 1770 - 2350 - 100 - 30 + 1602 + 2124 + 90 + 27 lt=- 10.0;10.0;80.0;10.0 + + Relation + + 2250 + 1926 + 45 + 27 + + lt=- + 10.0;10.0;30.0;10.0 + UMLClass - 2610 - 2310 - 160 - 60 + 3915 + 3591 + 108 + 54 -*NextCommandMove* +*PathType* bg=pink Relation - 2580 - 2330 - 50 - 30 + 3960 + 3546 + 27 + 63 + + lt=<. + 10.0;50.0;10.0;10.0 + + + UMLClass + + 2070 + 2178 + 225 + 72 + + *XORSwitchGate* +bg=pink + +-- +switch : SwitchCondition +default : Node + + + + Relation + + 2034 + 2205 + 54 + 27 lt=- - 10.0;10.0;30.0;10.0 + 40.0;10.0;10.0;10.0 UMLClass - 4340 - 3980 - 120 - 60 + 2349 + 2187 + 126 + 54 -*PathType* +*SwitchCondition* bg=pink Relation - 4390 - 3930 - 30 - 70 + 2286 + 2205 + 81 + 27 + + lt=<. + 70.0;10.0;10.0;10.0 + + + UMLClass + + 2529 + 2178 + 180 + 72 + + *NextCommand* +bg=pink + +-- +next : dict(Command, Node) + + + + Relation + + 2466 + 2205 + 81 + 27 + + lt=<<- + 70.0;10.0;10.0;10.0 + + + UMLClass + + 1656 + 2466 + 99 + 54 + + +*Command* +bg=pink + + + + Relation + + 1602 + 2484 + 72 + 27 + + lt=- + 10.0;10.0;60.0;10.0 + + + UMLClass + + 1719 + 2601 + 99 + 54 + + +*Idle* +bg=pink + + + + UMLClass + + 1719 + 2664 + 99 + 54 + + +*Move* +bg=pink + + + + UMLClass + + 1719 + 2538 + 99 + 54 + + +*ApplyEffect* +bg=pink + + + + Relation + + 1683 + 2511 + 27 + 198 + + lt=<<- + 10.0;10.0;10.0;200.0 + + + Relation + + 1683 + 2556 + 54 + 27 + + lt=- + 10.0;10.0;40.0;10.0 + + + Relation + + 1683 + 2619 + 54 + 27 + + lt=- + 40.0;10.0;10.0;10.0 + + + Relation + + 1683 + 2682 + 54 + 27 + + lt=- + 10.0;10.0;40.0;10.0 + + + UMLClass + + 3267 + 3861 + 144 + 72 + + *Ranged* +bg=pink + +-- +min_range : float +max_range : float + + + + Relation + + 3222 + 3888 + 63 + 27 + + lt=- + 50.0;10.0;10.0;10.0 + + + UMLClass + + 2277 + 1899 + 162 + 72 + + *TargetInRange* +bg=pink + +-- +ability : Ability + + + + UMLClass + + 1863 + 2349 + 153 + 81 + + *Task* +bg=pink + +-- +next : Node +task : Task + + + + Relation + + 2007 + 2376 + 54 + 27 + + lt=- + 40.0;10.0;10.0;10.0 + + + UMLClass + + 1926 + 2466 + 90 + 54 + + +*Task* +bg=pink + + + + Relation + + 1962 + 2421 + 27 + 63 lt=<. 10.0;50.0;10.0;10.0 + + Relation + + 1962 + 2511 + 27 + 198 + + lt=<<- + 10.0;10.0;10.0;200.0 + + + UMLClass + + 1989 + 2538 + 162 + 54 + + +*PopCommandQueue* +bg=pink + + + + Relation + + 1962 + 2556 + 45 + 27 + + lt=- + 10.0;10.0;30.0;10.0 + + + UMLClass + + 1989 + 2601 + 162 + 54 + + +*ClearCommandQueue* +bg=pink + + + + Relation + + 1962 + 2619 + 45 + 27 + + lt=- + 10.0;10.0;30.0;10.0 + + + UMLClass + + 1989 + 2664 + 126 + 54 + + +*MoveToTarget* +bg=pink + + + + Relation + + 1962 + 2682 + 45 + 27 + + lt=- + 10.0;10.0;30.0;10.0 + + + UMLClass + + 2277 + 1980 + 162 + 72 + + *AbilityUsable* +bg=pink + +-- +ability : Ability + + + + Relation + + 2250 + 2007 + 45 + 27 + + lt=- + 10.0;10.0;30.0;10.0 + From fb8971e8d86575c261251ccb8439a715c7dcf07a Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 15 Sep 2024 06:26:43 +0200 Subject: [PATCH 148/152] doc: Document API changes for nyan data API v0.6.0. doc: Document API changes for ApplyEffect. doc: Document API changes for Task nodes. doc: Document API changes for Ranged property. doc: Document API changes for NextCommand condition. --- doc/nyan/api_reference/reference_ability.md | 68 +++----- doc/nyan/api_reference/reference_util.md | 169 ++++++++++++++++++-- 2 files changed, 178 insertions(+), 59 deletions(-) diff --git a/doc/nyan/api_reference/reference_ability.md b/doc/nyan/api_reference/reference_ability.md index dfddc1c82f..2bb20fa6f2 100644 --- a/doc/nyan/api_reference/reference_ability.md +++ b/doc/nyan/api_reference/reference_ability.md @@ -127,6 +127,26 @@ If the lock pool the ability with this property cannot become active. +## ability.property.type.Ranged + +```python +Ranged(AbilityProperty): + min_range : float + max_range : float +``` + +Abilities with this property can only be used within a specified range around the game entity. The property mostly affects abilities that are *targeted*, i.e. that are used on other game entities or locations in the game world. + +If the target of the ability is another game entity and said game entity has a `Collision` ability, the range check factors in the `Hitbox` boundaries of the targeted game entity when calculating the distance. + +Without this property, abilities behave as if `min_range` and `max_range` are `0.0`. + +**min_range** +Minimum distance to the target of the ability. + +**max_range** +Maximum distance to the target of the ability. + ## ability.type.ActiveTransformTo ```python @@ -327,16 +347,12 @@ Alters the abilities and modifiers of a game entity after the despawn condition ```python DetectCloak(Ability): - range : float allowed_types : set(children(GameEntityType)) blacklisted_entities : set(GameEntity) ``` Enables the game entity to decloak other game entities which use the `Cloak` ability. -**range** -Range around the game entity in which other game entities will be decloaked. - **allowed_types** Whitelist of game entity types that can be decloaked. @@ -543,7 +559,6 @@ Determines whether the resource spot is harvestable when it is created. If `True ```python Herd(Ability): - range : float strength : int allowed_types : set(children(GameEntityType)) blacklisted_entities : set(GameEntity) @@ -551,9 +566,6 @@ Herd(Ability): Allows a game entity to change the ownership of other game entities with the `Herdable` ability. -**range** -Minimum distance to a herdable game entity to make it change ownership. - **strength** Comparison value for situations when the game entity competes with other game entities for a herdable. The game entity with the highest `strength` value will always be prefered, even if other game entities fulfill the condition set by `mode` in `Herdable` better. @@ -787,38 +799,6 @@ RallyPoint(Ability): Allows a game entity to set a rally point on the map. Game entities spawned by the `Create` ability or ejected from a container will move to the rally point location. The rally point can be placed on another game entity. In that case, the game entities moving there will try to use an appropriate ability on it. -## ability.type.RangedContinuousEffect - -```python -RangedContinuousEffect(ApplyContinuousEffect): - min_range : int - max_range : int -``` - -Applies continuous effects on another game entity. This specialization of `ApplyContinuousEffect` allows ranged application. - -**min_range** -Minimum distance to target. - -**max_range** -Maximum distance to the target. - -## ability.type.RangedDiscreteEffect - -```python -RangedDiscreteEffect(ApplyDiscreteEffect): - min_range : int - max_range : int -``` - -Applies batches of discrete effects on another game entity. This specialization of `ApplyDiscreteEffect` allows ranged application. - -**min_range** -Minimum distance to target. - -**max_range** -Maximum distance to the target. - ## ability.type.RegenerateAttribute ```python @@ -966,8 +946,6 @@ ShootProjectile(Ability): projectiles : orderedset(GameEntity) min_projectiles : int max_projectiles : int - min_range : int - max_range : int reload_time : float spawn_delay : float projectile_delay : float @@ -994,12 +972,6 @@ Minimum amount of projectiles spawned. **max_projectiles** Maximum amount of projectiles spawned. -**min_range** -Minimum distance to the targeted game entity. - -**max_range** -Maximum distance to the targeted game entity. - **reload_time** Time until the ability can be used again in seconds. The timer starts after the *last* projectile has been fired. diff --git a/doc/nyan/api_reference/reference_util.md b/doc/nyan/api_reference/reference_util.md index dd9646e862..7667a1a10a 100644 --- a/doc/nyan/api_reference/reference_util.md +++ b/doc/nyan/api_reference/reference_util.md @@ -54,6 +54,18 @@ Generalization object for conditions that can be used in `XORGate` nodes. **node** Node that is visited when the condition is true. +## util.activity.condition.type.AbilityUsable + +```python +AbilityUsable(Condition): + ability : abstract(Ability) +``` + +Is true when an ability can be used by the game entity when the node is visited. + +**ability** +Ability definition used for the usability check. This can reference a specific ability of the game entity or an abstract API object from the `engine.ability.type` namespace. If a specific ability is referenced, the ability must be assigned to the game entity **and** be enabled for the check to pass. If an API object is referenced, at least one ability of the same type must be enabled for the check to pass. + ## util.activity.condition.type.CommandInQueue ```python @@ -61,25 +73,35 @@ CommandInQueue(Condition): pass ``` -Is true when the command queue is not empty when the node is visited. +Is true when the game entity's command queue is not empty when the node is visited. -## util.activity.condition.type.NextCommandIdle +## util.activity.condition.type.NextCommand ```python -NextCommandIdle(Condition): - pass +NextCommand(Condition): + command : children(Command) ``` -Is true when the next command in the queue is of type `Idle`. +Is true when the next command in the game entity's command queue is of a specific type. -## util.activity.condition.type.NextCommandMove +**command** +Command type checked by the condition. + +## util.activity.condition.type.TargetInRange ```python -NextCommandMove(Condition): - pass +TargetInRange(Condition): + ability : abstract(Ability) ``` -Is true when the next command in the queue is of type `Move`. +Is true when the target of the next command in the game entity's command queue is in range of an ability. + +**ability** +Ability definition used for the range check. + +This can reference a specific ability of the game entity or an abstract API object from the `engine.ability.type` namespace. If a specific ability is referenced, the ability must be assigned to the game entity and must be enabled. Otherwise, the range check fails. If an API object is referenced, the first active ability with the same type as the API object is executed. + +If the ability has the property `Ranged`, the attributes of this property are utilized for the range check calculations. If the ability does not have a `Ranged` property, the condition is only true when the game entity is at the same position as the target. ## util.activity.event.Event @@ -149,7 +171,7 @@ Next node in the activity graph. **ability** Ability that is executed. -This can reference a specific ability of the game entity or an abstract API object from the `engine.ability.type` namespace. If a specific ability is referenced, the ability must be assigned to the game entity and must not be disabled. Otherwise, the ability is not executed. If an API object is referenced, the first active ability with the same type as the API object is executed. +This can reference a specific ability of the game entity or an abstract API object from the `engine.ability.type` namespace. If a specific ability is referenced, the ability must be assigned to the game entity and must be enabled. Otherwise, the ability is not executed. If an API object is referenced, the first active ability with the same type as the API object is executed. ## util.activity.node.type.End @@ -172,6 +194,22 @@ Start of an activity. Does nothing but pointing to the next node. **next** Next node in the activity graph. +## util.activity.node.type.Task + +```python +Task(Node): + next : Node + task : children(Task) +``` + +Executes a task on the game entity when the node is visited. + +**next** +Next node in the activity graph. + +**task** +Task that is executed. + ## util.activity.node.type.XOREventGate ```python @@ -195,11 +233,84 @@ XORGate(Node): Gateway that branches the activity graph depending on the result of conditional queries. Queries are executed immediately when the node is visited. **next** -Mapping of conditional queries to the next node in the activity graph. The first query that evaluates to true is used to determine the next node. If no query evaluates to true, the `default` node is used. +Mapping of conditional queries to the next node in the activity graph. The first query that evaluates to true is used to determine the next node. If no query evaluates to true, the `default` node is used as fallback. **default** Default node that is used if no query evaluates to true. +## util.activity.node.type.XORSwitchGate + +```python +XORSwitchGate(Node): + switch : children(SwitchCondition) + default : Node +``` + +Gateway that branches the activity graph depending on the value of a runtime parameter. In comparison to `XORGate`, only one conditional query is done based on the value (similar to the behaviour of a [switch statement](https://en.wikipedia.org/wiki/Switch_statement)). The query is executed immediately when the node is visited. + +**switch** +Defines which runtime parameter is checked as well as the mapping of parameter value to the next node in the activity graph. If a value is encountered at query execution time that is not associated with a node, the `default` node is used as fallback. + +**default** +Default node that is used if a value does not have an associated node. + +## util.activity.switch_condition.SwitchCondition + +```python +SwitchCondition(Object): + pass +``` + +Generalization object for conditions that can be used in `XORSwitchGate` nodes. + +## util.activity.switch_condition.type.NextCommand + +```python +NextCommand(SwitchCondition): + next : dict(children(Command), Node) +``` + +Switches branches based on the type of command that is in the queue of the game entity. + +**next** +Mapping of command types to the next node in the activity graph. + +## util.activity.task.Task + +```python +Task(Object): + pass +``` + +Generalization object for tasks that can be used in `Task` nodes. + +## util.activity.task.type.ClearCommandQueue + +```python +ClearCommandQueue(Task): + pass +``` + +Clear the command queue of the game entity executing the activity. + +## util.activity.task.type.MoveToTarget + +```python +MoveToTarget(Task): + pass +``` + +Move to the current target of the game entity. The target may be a position or another game entity. If the game entity has no target at time of execution, the task is skipped. + +## util.activity.task.type.PopCommandQueue + +```python +PopCommandQueue(Task): + pass +``` + +Pop the front command from the command queue of the game entity executing the activity. + ## util.animation_override.AnimationOverride ```python @@ -420,6 +531,42 @@ The activation message that has to be typed into the chat console. **changes** Changes to API objects. +## util.command.Command + +```python +Command(Object): + pass +``` + +Generalization object for commands of a game entity. + +## util.command.type.ApplyEffect + +```python +ApplyEffect(Command): + pass +``` + +Game entity command for using the `ApplyEffect` ability. + +## util.command.type.Idle + +```python +Idle(Command): + pass +``` + +Game entity command for using the `Idle` ability. + +## util.command.type.Move + +```python +Move(Command): + pass +``` + +Game entity command for using the `Move` ability. + ## util.container_type.SendToContainerType ```python From 05ad1e772a7b53c21d12db867b781e281ec70085 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 May 2025 17:08:36 +0200 Subject: [PATCH 149/152] doc: Changelog for nyan data API v0.6.0. --- doc/changelogs/nyan_api/v0.6.0.md | 43 +++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 doc/changelogs/nyan_api/v0.6.0.md diff --git a/doc/changelogs/nyan_api/v0.6.0.md b/doc/changelogs/nyan_api/v0.6.0.md new file mode 100644 index 0000000000..1372c5ef83 --- /dev/null +++ b/doc/changelogs/nyan_api/v0.6.0.md @@ -0,0 +1,43 @@ +# [0.6.0] - 2025-05-18 +All notable changes for version [v0.6.0] are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Added +### Ability module +- Add `Ranged(Property)` object; defines range of an ability; replaces various range members in `Ability` objects + +### Utility module +- Add `AbilityUsable(Condition)` object; checks if an ability can be used by an entity +- Add `NextCommand(Condition)` object; checks if a specific command is next in the command queue; replaces individual objects checking for specific commands +- Add `TargetInRange(Condition)` object; checks if the target of an entity is in range of a specific ability +- Add `Task(Node)` object; execute internal task when visiting node +- Add `Task(Object)` object +- Add `ClearCommandQueue(Task)` object; clears the command queue when visiting task node +- Add `PopCommandQueue(Task)` object; pops the front command in the command queue when visiting task node +- Add `MoveToTarget(Task)` object; move to the current target of the entity when visiting task node +- Add `XORSwitchGate(Node)` object; switch branches based on evaluating single value +- Add `SwitchCondition(Object)` object +- Add `NextCommand(SwitchCondition)` object; switch branches based on the next command in the command queue +- Add `Command(Object)` object; references internal commands of the engine +- Add `ApplyEffect(Command)` object +- Add `Idle(Command)` object +- Add `Move(Command)` object + +### Removed +### Ability module +- Remove `RangedContinuousEffect(Ability)` object; functionality superceded by `Ranged(Property)` object +- Remove `RangedDiscreteEffect(Ability)` object; functionality superceded by `Ranged(Property)` object +- Remove `range` member from `DetectCloak(Ability)`; functionality superceded by `Ranged(Property)` object +- Remove `range` member from `Herd(Ability)`; functionality superceded by `Ranged(Property)` object +- Remove `min_range` member from `ShootProjectile(Ability)`; functionality superceded by `Ranged(Property)` object +- Remove `max_range` member from `ShootProjectile(Ability)`; functionality superceded by `Ranged(Property)` object + +### Utility module +- Remove `NextCommandIdle(Condition)` object; functionality superceded by `NextCommand(Condition)` object +- Remove `NextCommandMove(Condition)` object; functionality superceded by `NextCommand(Condition)` object + +## Reference visualization + +* [Gamedata](https://github.com/SFTtech/openage/blob/927f547d4985cba8e172c9492273b34537571c56/doc/nyan/aoe2_nyan_tree.svg) From f8763a1d4df4c794b64b463c1a0e26ed3cd95349 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 19 May 2025 21:32:30 +0200 Subject: [PATCH 150/152] doc: Fix renderer demo run instructions. --- doc/code/renderer/demos.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/doc/code/renderer/demos.md b/doc/code/renderer/demos.md index 3be974d628..82de8c4e86 100644 --- a/doc/code/renderer/demos.md +++ b/doc/code/renderer/demos.md @@ -24,7 +24,7 @@ The demo mostly follows the steps described in the [Level 1 Renderer - Basic Usa documentation. ```bash -./bin/run test --demo renderer.tests.renderer_demo 0 +cd bin && ./run test --demo renderer.tests.renderer_demo 0 ``` **Result:** @@ -38,7 +38,7 @@ This demo shows how simple *textured* meshes can be created and rendered. It als how to interact with the window and the renderer using window callbacks. ```bash -./bin/run test --demo renderer.tests.renderer_demo 1 +cd bin && ./run test --demo renderer.tests.renderer_demo 1 ``` **Controls:** @@ -56,7 +56,7 @@ In this demo, we show how animation and texture metadata files are parsed and us load and render the correct textures and animations for a mesh. ```bash -./bin/run test --demo renderer.tests.renderer_demo 2 +cd bin && ./run test --demo renderer.tests.renderer_demo 2 ``` **Controls:** @@ -76,7 +76,7 @@ This demo shows a minimal setup for the [Level 2 Renderer](level2.md) and how to with it. The demo also introduces the camera system and how to interact with it. ```bash -./bin/run test --demo renderer.tests.renderer_demo 3 +cd bin && ./run test --demo renderer.tests.renderer_demo 3 ``` **Controls:** @@ -95,7 +95,7 @@ This demos shows how animation frame timing works and how to control the animati with the engine's internal clock. ```bash -./bin/run test --demo renderer.tests.renderer_demo 4 +cd bin && ./run test --demo renderer.tests.renderer_demo 4 ``` **Controls:** @@ -115,7 +115,7 @@ This demo shows how to create [uniform buffers](level1.md#uniform-buffers) and h Additionally, uniform buffer usage for the camera system is demonstrated. ```bash -./bin/run test --demo renderer.tests.renderer_demo 5 +cd bin && ./run test --demo renderer.tests.renderer_demo 5 ``` **Controls:** @@ -132,7 +132,7 @@ Additionally, uniform buffer usage for the camera system is demonstrated. This demo shows how to use [frustum culling](level2.md#frustum-culling) in the renderer. ```bash -./bin/run test --demo renderer.tests.renderer_demo 6 +cd bin && ./run test --demo renderer.tests.renderer_demo 6 ``` **Controls:** @@ -144,12 +144,12 @@ This demo shows how to use [frustum culling](level2.md#frustum-culling) in the r ![Demo 6](/doc/code/renderer/images/demo_6.png) -### Demo 6 +### Demo 7 This demo shows how to use [shader templating](level1.md#shader-templates) in the renderer. ```bash -./bin/run test --demo renderer.tests.renderer_demo 6 +cd bin && ./run test --demo renderer.tests.renderer_demo 7 ``` **Result:** @@ -164,7 +164,7 @@ This demo shows how to use [shader templating](level1.md#shader-templates) in th This stresstest tests the performance when rendering an increasingly larger number of objects. ```bash -./bin/run test --demo renderer.tests.stresstest 0 +cd bin && ./run test --demo renderer.tests.renderer_stresstest 0 ``` **Result:** @@ -177,7 +177,7 @@ This stresstest tests the performance when [frustum culling](level2.md#frustum-c number of objects is rendered on the screen. ```bash -./bin/run test --demo renderer.tests.stresstest 1 +cd bin && ./run test --demo renderer.tests.renderer_stresstest 1 ``` **Result:** From 906e14bdcee9651d53a4110198aa7334b752a317 Mon Sep 17 00:00:00 2001 From: Jeremiah Morgan Date: Sat, 24 May 2025 17:44:45 +0100 Subject: [PATCH 151/152] build system: remove STANDALONE from cabchecksum.pyx --- openage/cabextract/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openage/cabextract/CMakeLists.txt b/openage/cabextract/CMakeLists.txt index 9a545259af..87f4fd36eb 100644 --- a/openage/cabextract/CMakeLists.txt +++ b/openage/cabextract/CMakeLists.txt @@ -1,6 +1,6 @@ add_cython_modules( lzxd.pyx - STANDALONE cabchecksum.pyx + cabchecksum.pyx ) add_py_modules( From b671dde2a118402dba911b2304b9fad01c1a0049 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 14 Jun 2025 00:21:35 +0200 Subject: [PATCH 152/152] buildsys: Renames 'codegen' target to 'cppgen'. Fixes a CMake warning because 'codegen' is a reserved name. --- Makefile | 6 +++++- buildsystem/codegen.cmake | 4 ++-- libopenage/CMakeLists.txt | 4 ++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 4fd6e2fe5a..ad8ad7adf0 100644 --- a/Makefile +++ b/Makefile @@ -45,7 +45,11 @@ libopenage: $(BUILDDIR) .PHONY: codegen codegen: $(BUILDDIR) - $(MAKE) $(MAKEARGS) -C $(BUILDDIR) codegen + $(MAKE) $(MAKEARGS) -C $(BUILDDIR) cppgen + +.PHONY: cppgen +cppgen: $(BUILDDIR) + $(MAKE) $(MAKEARGS) -C $(BUILDDIR) cppgen .PHONY: pxdgen pxdgen: $(BUILDDIR) diff --git a/buildsystem/codegen.cmake b/buildsystem/codegen.cmake index 068078bf6d..685dbf0463 100644 --- a/buildsystem/codegen.cmake +++ b/buildsystem/codegen.cmake @@ -1,4 +1,4 @@ -# Copyright 2014-2019 the openage authors. See copying.md for legal info. +# Copyright 2014-2025 the openage authors. See copying.md for legal info. # set CODEGEN_SCU_FILE to the absolute path to SCU file macro(get_codegen_scu_file) @@ -52,7 +52,7 @@ function(codegen_run) COMMENT "openage.codegen: generating c++ code" ) - add_custom_target(codegen + add_custom_target(cppgen DEPENDS "${CODEGEN_TIMEFILE}" ) diff --git a/libopenage/CMakeLists.txt b/libopenage/CMakeLists.txt index 030231baf7..30b7b23b01 100644 --- a/libopenage/CMakeLists.txt +++ b/libopenage/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright 2014-2019 the openage authors. See copying.md for legal info. +# Copyright 2014-2025 the openage authors. See copying.md for legal info. # main C++ library definitions. # dependency and source file setup for the resulting library. @@ -13,7 +13,7 @@ declare_binary(libopenage openage library allow_no_undefined) set_target_properties(libopenage PROPERTIES VERSION 0 AUTOMOC ON - AUTOGEN_TARGET_DEPENDS "codegen" + AUTOGEN_TARGET_DEPENDS "cppgen" ) ##################################################

X*^D=dU{~ZmAb2tx_lpWjXJ_FUF!09 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⩝!ud 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⊤gK_U?*6ZL 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 z�Vqc6CHBPN>HDTj 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%0A*zSgw_~n8?>BBJS&UJw^uQW3 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