From 004c845af7d2942a930339938b45b5fda964e6f9 Mon Sep 17 00:00:00 2001 From: Jamie Greunbaum Date: Mon, 7 Sep 2026 13:49:13 -0400 Subject: [PATCH] - Increased minimum version to 4.3 for compatibility with physics bones. - Added an interface for nodes that can be damaged. - Character3D implements the above interface to make itself sliceable. - Added a hurtbox base class. - MeshEditingLibrary::slice_mesh now uses Godot references when possible. - Removed semicolon from print macros to enforce placing them in code. --- orng.gdextension | 2 +- src/SConstruct | 1 + src/godot-cpp | 2 +- src/liborng/interfaces/i_damageable.cpp | 8 ++ src/liborng/interfaces/i_damageable.h | 18 ++++ .../nodes/character_nodes/character_3d.cpp | 48 ++++++++- .../nodes/character_nodes/character_3d.h | 38 ++++++-- src/liborng/nodes/hurtbox/hurtbox_base.cpp | 97 +++++++++++++++++++ src/liborng/nodes/hurtbox/hurtbox_base.h | 65 +++++++++++++ src/liborng/nodes/mesh_editing_library.cpp | 8 +- src/liborng/nodes/mesh_editing_library.h | 2 +- src/liborng/orng_macros.h | 6 +- src/liborng/register_types.cpp | 4 + 13 files changed, 277 insertions(+), 22 deletions(-) create mode 100644 src/liborng/interfaces/i_damageable.cpp create mode 100644 src/liborng/interfaces/i_damageable.h create mode 100644 src/liborng/nodes/hurtbox/hurtbox_base.cpp create mode 100644 src/liborng/nodes/hurtbox/hurtbox_base.h diff --git a/orng.gdextension b/orng.gdextension index 09a3e7a..6fa5173 100644 --- a/orng.gdextension +++ b/orng.gdextension @@ -1,7 +1,7 @@ [configuration] entry_symbol = "orng_library_init" -compatibility_minimum = "4.2" +compatibility_minimum = "4.3" reloadable = true [libraries] diff --git a/src/SConstruct b/src/SConstruct index 4ff0b69..0896346 100644 --- a/src/SConstruct +++ b/src/SConstruct @@ -1,6 +1,7 @@ env = SConscript("godot-cpp/SConstruct") env.Append(CPPPATH="liborng/") env.Append(CPPPATH="liborng/data_assets") +env.Append(CPPPATH="liborng/nodes/interfaces") env.Append(CPPPATH="liborng/nodes") env.Append(CPPPATH="liborng/nodes/character_nodes") env.Append(CPPPATH="liborng/resources") diff --git a/src/godot-cpp b/src/godot-cpp index 4bc6e67..d5cc777 160000 --- a/src/godot-cpp +++ b/src/godot-cpp @@ -1 +1 @@ -Subproject commit 4bc6e67d51df4c12e150fe4769911e239c627a47 +Subproject commit d5cc777a89d899665fb61f1650ef0dc0cf6488c4 diff --git a/src/liborng/interfaces/i_damageable.cpp b/src/liborng/interfaces/i_damageable.cpp new file mode 100644 index 0000000..8d705d6 --- /dev/null +++ b/src/liborng/interfaces/i_damageable.cpp @@ -0,0 +1,8 @@ +/* + * ©2023 Batty Bovine Productions, LLC. All Rights Reserved. + * + * If this source code makes it to the public internet, this software can be + * considered to be protected by the MIT licence. Have fun with it. + */ + +#include "i_damageable.h" diff --git a/src/liborng/interfaces/i_damageable.h b/src/liborng/interfaces/i_damageable.h new file mode 100644 index 0000000..05f1295 --- /dev/null +++ b/src/liborng/interfaces/i_damageable.h @@ -0,0 +1,18 @@ +/* + * ©2023 Batty Bovine Productions, LLC. All Rights Reserved. + * + * If this source code makes it to the public internet, this software can be + * considered to be protected by the MIT licence. Have fun with it. + */ + +#pragma once + +#include +using namespace godot; + + +class IDamageable +{ +public: + virtual void take_damage(const Dictionary &hit_data) = 0; +}; diff --git a/src/liborng/nodes/character_nodes/character_3d.cpp b/src/liborng/nodes/character_nodes/character_3d.cpp index 37e6b44..7cae688 100644 --- a/src/liborng/nodes/character_nodes/character_3d.cpp +++ b/src/liborng/nodes/character_nodes/character_3d.cpp @@ -9,7 +9,10 @@ #include "orng_macros.h" +#include "nodes/mesh_editing_library.h" + #include +#include #include using namespace godot; @@ -17,14 +20,51 @@ using namespace godot; void Character3D::_ready() { - set_animation_tree_path(this->animation_tree_path); + TypedArray child_mesh_candidates = this->skeleton->get_children(); + const uint32_t num_children = child_mesh_candidates.size(); + for (uint32_t i = 0; i < num_children; i++) + { + if (MeshInstance3D *mesh = cast_to(child_mesh_candidates[i])) + { + this->meshes.emplace_back(mesh); + } + } } -void Character3D::set_animation_tree_path(NodePath at) +void Character3D::take_damage(const Dictionary &hit_data) { - this->animation_tree_path = at; - this->animation_tree = cast_to(Node::get_node_or_null(this->animation_tree_path)); + PRINT_LOG(CHARACTER3D_TAG, "Taking damage: ", hit_data["hit_points"]); + PRINT_LOG(CHARACTER3D_TAG, "Hit bones: ", hit_data["bone_hits"]); + + const Transform3D &global_hit_transform = (Transform3D)hit_data["hit_transform"]; + const Plane &slice_plane = this->skeleton->get_global_transform().inverse().xform(Plane(global_hit_transform.get_basis().get_column(1), global_hit_transform.origin)); + const uint32_t num_meshes = this->meshes.size(); + for (uint32_t i = 0; i < num_meshes; i++) + { + MeshInstance3D *original_mesh_instance = this->meshes[i]; + Ref first_half; first_half.instantiate(); + Ref other_half; other_half.instantiate(); + PackedVector3Array &impact_points = MeshEditingLibrary::slice_mesh(original_mesh_instance, this->skeleton, slice_plane, first_half, other_half, MeshEditingLibrary::CAP_UV_FILL_MESH_BOUNDS, original_mesh_instance->get_active_material(0)); + + if (other_half.is_valid()) + original_mesh_instance->set_mesh(other_half); + } +} + + +void Character3D::_activate_ragdoll() +{ + const TypedArray &physical_bones = this->physical_bone_simulator->get_children(); + const uint32_t num_physical_bones = physical_bones.size(); + for (uint32_t i = 0; i < num_physical_bones; i++) + { + if (PhysicalBone3D *bone = cast_to(physical_bones[i])) + { + bone->set_collision_layer(0); + } + } + this->physical_bone_simulator->physical_bones_start_simulation(); } diff --git a/src/liborng/nodes/character_nodes/character_3d.h b/src/liborng/nodes/character_nodes/character_3d.h index 8f41054..8420d68 100644 --- a/src/liborng/nodes/character_nodes/character_3d.h +++ b/src/liborng/nodes/character_nodes/character_3d.h @@ -9,33 +9,49 @@ #include "orng_macros.h" +#include "interfaces/i_damageable.h" + #include #include #include +#include +#include +#include +#include using namespace godot; #define CHARACTER3D_TAG "Character3D" -class Character3D : public CharacterBody3D +class Character3D : public CharacterBody3D, public IDamageable { GDCLASS(Character3D, CharacterBody3D); public: virtual void _ready() override; + virtual void take_damage(const Dictionary &hit_data) override; + + Skeleton3D *get_skeleton() const { return this->skeleton; } + PhysicalBoneSimulator3D *get_physical_bone_simulator() const { return this->physical_bone_simulator; } + AnimationTree *get_animation_tree() const { return this->animation_tree; } + virtual void _on_fell_out_of_world(); protected: - void set_animation_tree_path(NodePath at); - NodePath get_animation_tree_path() const { return this->animation_tree_path; } + void set_skeleton(Skeleton3D *s) { this->skeleton = s; } + void set_physical_bone_simulator(PhysicalBoneSimulator3D *pbs) { this->physical_bone_simulator = pbs; } + void set_animation_tree(AnimationTree *at) { this->animation_tree = at; } private: - AnimationTree *get_animation_tree() const { return this->animation_tree; } + void _activate_ragdoll(); - NodePath animation_tree_path; + Skeleton3D *skeleton = nullptr; + PhysicalBoneSimulator3D *physical_bone_simulator = nullptr; AnimationTree *animation_tree = nullptr; + std::vector meshes; + // Godot boilerplate below protected: static void _bind_methods() @@ -44,10 +60,16 @@ protected: ADD_SIGNAL(MethodInfo("fell_out_of_world")); - ClassDB::bind_method(D_METHOD("get_animation_tree_path"), &Character3D::get_animation_tree_path); - ClassDB::bind_method(D_METHOD("set_animation_tree_path", "animation_tree"), &Character3D::set_animation_tree_path); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "animation_tree", PROPERTY_HINT_NODE_TYPE, "AnimationTree"), "set_animation_tree_path", "get_animation_tree_path"); + ClassDB::bind_method(D_METHOD("get_skeleton"), &Character3D::get_skeleton); + ClassDB::bind_method(D_METHOD("set_skeleton", "skeleton"), &Character3D::set_skeleton); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "skeleton", PROPERTY_HINT_NODE_TYPE, "Skeleton3D"), "set_skeleton", "get_skeleton"); + + ClassDB::bind_method(D_METHOD("get_physical_bone_simulator"), &Character3D::get_physical_bone_simulator); + ClassDB::bind_method(D_METHOD("set_physical_bone_simulator", "physical_bone_simulator"), &Character3D::set_physical_bone_simulator); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physical_bone_simulator", PROPERTY_HINT_NODE_TYPE, "PhysicalBoneSimulator3D"), "set_physical_bone_simulator", "get_physical_bone_simulator"); ClassDB::bind_method(D_METHOD("get_animation_tree"), &Character3D::get_animation_tree); + ClassDB::bind_method(D_METHOD("set_animation_tree", "animation_tree"), &Character3D::set_animation_tree); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "animation_tree", PROPERTY_HINT_NODE_TYPE, "AnimationTree"), "set_animation_tree", "get_animation_tree"); } }; diff --git a/src/liborng/nodes/hurtbox/hurtbox_base.cpp b/src/liborng/nodes/hurtbox/hurtbox_base.cpp new file mode 100644 index 0000000..fa5329c --- /dev/null +++ b/src/liborng/nodes/hurtbox/hurtbox_base.cpp @@ -0,0 +1,97 @@ +/* + * ©2023 Batty Bovine Productions, LLC. All Rights Reserved. + * + * If this source code makes it to the public internet, this software can be + * considered to be protected by the MIT licence. Have fun with it. + */ + +#include "hurtbox_base.h" +#include "orng_macros.h" + +#include "character_3d.h" + +#include +#include +#include +#include +using namespace godot; + + +void HurtboxBase::_ready() +{ + this->set_process(false); + this->remaining_life = this->lifetime; + + CALL_NEXT_FRAME(callable_mp(this, &HurtboxBase::_send_next_in_queue)); +} + +void HurtboxBase::_physics_process(double delta) +{ + if (this->remaining_life <= 0.0f) + { + this->collision_shape->set_disabled(true); + } + + TypedArray characters_hit_this_frame; + + const TypedArray &bodies = this->get_overlapping_bodies(); + const uint32_t num_bodies = bodies.size(); + + for (uint32_t i = 0; i < num_bodies; i++) + { + if (Character3D *body = cast_to(bodies[i])) + { + if (!this->hit_character_bodies.has(body)) + { + characters_hit_this_frame.append(body); + TypedArray bone_hits; + + for (uint32_t bone_index = 0; bone_index < num_bodies; bone_index++) + { + if (PhysicalBone3D *bone = cast_to(bodies[bone_index])) + { + if (String(bone->get_path()).begins_with(String(body->get_path()))) + { + bone_hits.append(bone->get_bone_id()); + PRINT_LOG(HURTBOX_BASE_TAG, "Bone hit: ", bone->get_name(), " (", bone->get_class(), " : ", bone->get_rid(), ")"); + } + } + } + + PRINT_LOG(HURTBOX_BASE_TAG, "Character hit: ", body->get_name(), " (", body->get_class(), " : ", body->get_rid(), ")"); + + Dictionary hit_data; + hit_data["body"] = body; + hit_data["bone_hits"] = bone_hits; + hit_data["hit_points"] = this->hit_points; + hit_data["hit_transform"] = this->get_global_transform(); + + this->hit_data_queue.emplace_back(hit_data); + } + } + } + + this->hit_character_bodies.append_array(characters_hit_this_frame); + + this->remaining_life -= delta; +} + +void HurtboxBase::_send_next_in_queue() +{ + if (!this->hit_data_queue.empty()) + { + const Dictionary &hit_data = this->hit_data_queue.front(); + Character3D *body = cast_to(hit_data["body"]); + body->take_damage(hit_data); + this->hit_data_queue.erase(this->hit_data_queue.begin()); + } + + if (this->remaining_life > 0.0f) + { + CALL_NEXT_FRAME(callable_mp(this, &HurtboxBase::_send_next_in_queue)); + } + else if (this->hit_data_queue.empty()) + { + this->queue_free(); + } +} diff --git a/src/liborng/nodes/hurtbox/hurtbox_base.h b/src/liborng/nodes/hurtbox/hurtbox_base.h new file mode 100644 index 0000000..be6f4f4 --- /dev/null +++ b/src/liborng/nodes/hurtbox/hurtbox_base.h @@ -0,0 +1,65 @@ +/* + * ©2023 Batty Bovine Productions, LLC. All Rights Reserved. + * + * If this source code makes it to the public internet, this software can be + * considered to be protected by the MIT licence. Have fun with it. + */ + +#pragma once + +#include "orng_macros.h" + +#include "character_3d.h" + +#include +#include +using namespace godot; + +#define HURTBOX_BASE_TAG "HurtboxBase" + + +class HurtboxBase : public Area3D +{ + GDCLASS(HurtboxBase, Area3D); + +public: + virtual void _ready() override; + virtual void _physics_process(double delta) override; + + void set_collision_shape(CollisionShape3D *cs) { this->collision_shape = cs; } + CollisionShape3D *get_collision_shape() const { return this->collision_shape; } + + void set_lifetime(const float lt) { this->lifetime = lt; } + float get_lifetime() const { return this->lifetime; } + + void set_hit_points(const int32_t points) { this->hit_points = points; } + int32_t get_hit_points() const { return this->hit_points; } + +protected: + std::vector hit_data_queue; + +private: + Dictionary _create_hit_data_struct() const; + void _send_next_in_queue(); + + CollisionShape3D *collision_shape = nullptr; + + float lifetime = 0.05f; + float remaining_life = 0.0f; + int32_t hit_points = 0; + + TypedArray hit_character_bodies; + + +// Godot boilerplate below +protected: + static void _bind_methods() + { + ADD_GETTER_SETTER_HINTED(HurtboxBase, collision_shape, Variant::OBJECT, PROPERTY_HINT_NODE_TYPE, "CollisionShape3D"); + + ADD_GETTER_SETTER(HurtboxBase, lifetime, Variant::FLOAT); + ADD_GETTER_SETTER(HurtboxBase, hit_points, Variant::INT); + + ClassDB::bind_method(D_METHOD("_send_next_in_queue"), &HurtboxBase::_send_next_in_queue); + } +}; diff --git a/src/liborng/nodes/mesh_editing_library.cpp b/src/liborng/nodes/mesh_editing_library.cpp index 9c289ea..29a91a2 100644 --- a/src/liborng/nodes/mesh_editing_library.cpp +++ b/src/liborng/nodes/mesh_editing_library.cpp @@ -24,7 +24,7 @@ MeshEditingLibrary::~MeshEditingLibrary() { } -PackedVector3Array MeshEditingLibrary::slice_mesh(const MeshInstance3D *original_mesh_instance, const Skeleton3D *original_skeleton, const Plane &local_plane, ArrayMesh *out_first_half, ArrayMesh *out_other_half, const MeshEditCapUV cap_option, const Ref cap_material) +PackedVector3Array MeshEditingLibrary::slice_mesh(const MeshInstance3D *original_mesh_instance, const Skeleton3D *original_skeleton, const Plane &local_plane, Ref out_first_half, Ref out_other_half, const MeshEditCapUV cap_option, const Ref cap_material) { const Time *time = Time::get_singleton(); @@ -535,6 +535,9 @@ PackedVector3Array MeshEditingLibrary::slice_mesh(const MeshInstance3D *original polygon_centroid += position; } + polygon_centroid /= polygon_set[polygon_index].vertices.size(); + impact_points.append(polygon_centroid); + earcut_polygon.push_back(sub_polygon); std::vector indices = mapbox::earcut(earcut_polygon); for (uint32_t i = 0; (i+2) < indices.size(); i += 3) @@ -543,9 +546,6 @@ PackedVector3Array MeshEditingLibrary::slice_mesh(const MeshInstance3D *original cap_section_indices.append(indices[i] + polygon_vertex_base); cap_section_indices.append(indices[i+2] + polygon_vertex_base); } - - polygon_centroid /= polygon_set[polygon_index].vertices.size(); - impact_points.append(polygon_centroid); } } diff --git a/src/liborng/nodes/mesh_editing_library.h b/src/liborng/nodes/mesh_editing_library.h index e7c6877..270628c 100644 --- a/src/liborng/nodes/mesh_editing_library.h +++ b/src/liborng/nodes/mesh_editing_library.h @@ -68,7 +68,7 @@ public: CAP_UV_TILED }; - static PackedVector3Array slice_mesh(const MeshInstance3D *original_mesh_instance, const Skeleton3D *original_skeleton, const Plane &local_plane, ArrayMesh *out_first_half, ArrayMesh *out_other_half, const MeshEditCapUV cap_option, const Ref cap_material); + static PackedVector3Array slice_mesh(const MeshInstance3D *original_mesh_instance, const Skeleton3D *original_skeleton, const Plane &local_plane, Ref out_first_half, Ref out_other_half, const MeshEditCapUV cap_option, const Ref cap_material); protected: enum MeshSlicePlaneOrientation diff --git a/src/liborng/orng_macros.h b/src/liborng/orng_macros.h index fb27c9f..2c50b97 100644 --- a/src/liborng/orng_macros.h +++ b/src/liborng/orng_macros.h @@ -52,9 +52,9 @@ * Logging helpers */ #ifdef DEBUG_ENABLED - #define PRINT_LOG(tag, ...) UtilityFunctions::print("[" tag "] " __VA_ARGS__); - #define PRINT_WARNING(tag, ...) UtilityFunctions::push_warning("[" tag "] " __VA_ARGS__); - #define PRINT_ERROR(tag, ...) UtilityFunctions::printerr("[" tag "] " __VA_ARGS__); + #define PRINT_LOG(tag, ...) UtilityFunctions::print("[" tag "] ", __VA_ARGS__) + #define PRINT_WARNING(tag, ...) UtilityFunctions::push_warning("[" tag "] ", __VA_ARGS__) + #define PRINT_ERROR(tag, ...) UtilityFunctions::printerr("[" tag "] ", __VA_ARGS__) #else #define PRINT_LOG(tag, ...) #define PRINT_WARNING(tag, ...) diff --git a/src/liborng/register_types.cpp b/src/liborng/register_types.cpp index 729eda2..df383ab 100644 --- a/src/liborng/register_types.cpp +++ b/src/liborng/register_types.cpp @@ -23,6 +23,8 @@ #include "nodes/sublevel_scene.h" #include "nodes/character_nodes/character_3d.h" +#include "nodes/hurtbox/hurtbox_base.h" + #include "resources/level_metadata_map.h" #include "resources/level_metadata_resource.h" @@ -68,6 +70,8 @@ void initialize_orng_module(ModuleInitializationLevel p_level) { ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); + + ClassDB::register_class(); ClassDB::register_class();