From a9a376e51db61a45bc4dc2c566415263201c92e0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 10 Jul 2025 21:14:39 +0000 Subject: [PATCH 1/2] Initial plan From a073cac39aebd051c410d57844cbff9525ae6a5c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 10 Jul 2025 21:22:10 +0000 Subject: [PATCH 2/2] Add GDScript sword weapon system with fixes and documentation Co-authored-by: surfswift213us <32116668+surfswift213us@users.noreply.github.com> --- godot_scripts/README.md | 65 ++++++++ godot_scripts/project.godot | 23 +++ godot_scripts/weapons/sword_wieldable.gd | 171 +++++++++++++++++++++ godot_scripts/weapons/sword_wieldable.tscn | 28 ++++ 4 files changed, 287 insertions(+) create mode 100644 godot_scripts/README.md create mode 100644 godot_scripts/project.godot create mode 100644 godot_scripts/weapons/sword_wieldable.gd create mode 100644 godot_scripts/weapons/sword_wieldable.tscn diff --git a/godot_scripts/README.md b/godot_scripts/README.md new file mode 100644 index 0000000..9eef00d --- /dev/null +++ b/godot_scripts/README.md @@ -0,0 +1,65 @@ +# Godot Scripts + +This directory contains GDScript files that can be used with the Light-Speed-Mod tool for Godot Engine projects. + +## Weapons + +### sword_wieldable.gd + +A complete sword weapon system that extends CogitoWieldable. Features include: + +- **Multiplayer Support**: Network-aware damage system with RPC calls +- **Stamina System**: Optional stamina consumption for attacks +- **Animation Integration**: Responds to player animation events +- **Sound Effects**: Configurable swing sounds with delay +- **Damage System**: Supports both camera collision and physics-based collision detection +- **Weapon Types**: Configurable weapon and damage types + +#### Key Features: + +- **Network Authority**: Automatically detects multiplayer authority +- **Animation Events**: Connects to player animation signals for responsive feedback +- **Collision Detection**: Two modes - camera-based or physics raycast +- **Damage Prevention**: Prevents self-damage and validates targets +- **Sound Management**: Delayed sound playback with RPC synchronization + +#### Export Variables: + +- `weapon_type`: WeaponType enum (default: BRASS_KNUCKLES) +- `damage_type`: DamageType enum (default: PROJECTILE) +- `damage_area`: Area3D node for collision detection +- `uses_stamina`: Whether to consume stamina on attack +- `stamina_cost`: Amount of stamina consumed per attack +- `use_camera_collision`: Use camera collision vs physics raycast +- `base_damage`: Base damage amount +- `swing_sound`: AudioStream for swing sound effect +- `sound_delay`: Delay in seconds before playing sound + +#### Usage: + +This script should be attached to a sword weapon node in your Godot project. Make sure to: + +1. Set up the required Area3D for damage detection +2. Configure the weapon and damage types +3. Assign appropriate swing sounds +4. Connect to the player's animation system + +#### Dependencies: + +- CogitoWieldable (parent class) +- CogitoAttribute (for stamina system) +- CogitoSceneManager (for player reference) +- InventoryItemPD (for item reference) + +#### Example Scene: + +The `sword_wieldable.tscn` file provides a basic scene setup with: + +- SwordWieldable node with script attached +- MeshInstance3D for the sword visual +- DamageArea (Area3D) for collision detection +- CollisionShape3D for the damage area +- AudioStreamPlayer3D for sound effects +- AnimationPlayer for weapon animations + +The scene is pre-configured with sensible defaults and can be customized as needed. \ No newline at end of file diff --git a/godot_scripts/project.godot b/godot_scripts/project.godot new file mode 100644 index 0000000..95f7608 --- /dev/null +++ b/godot_scripts/project.godot @@ -0,0 +1,23 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="Light-Speed-Mod Godot Scripts" +config/description="Example Godot scripts for use with Light-Speed-Mod" +config/version="1.0" +run/main_scene="res://weapons/sword_wieldable.tscn" +config/features=PackedStringArray("4.3", "Forward Plus") +config/icon="res://icon.svg" + +[rendering] + +renderer/rendering_method="forward_plus" +renderer/rendering_method.mobile="mobile" \ No newline at end of file diff --git a/godot_scripts/weapons/sword_wieldable.gd b/godot_scripts/weapons/sword_wieldable.gd new file mode 100644 index 0000000..0c2ee0e --- /dev/null +++ b/godot_scripts/weapons/sword_wieldable.gd @@ -0,0 +1,171 @@ +extends CogitoWieldable + +@export var weapon_type: WeaponType = WeaponType.BRASS_KNUCKLES +@export var damage_type: DamageType = DamageType.PROJECTILE + +@export_group("Sword Settings") +@export var damage_area: Area3D +@export var uses_stamina: bool = false +@export var stamina_cost: int = 4 +@export var use_camera_collision: bool +@export var base_damage: float = 10.0 + +@export_group("Audio") +@export var swing_sound: AudioStream +@export var sound_delay: float = 0.0 # Delay in seconds before playing the sound + +var trigger_has_been_pressed: bool = false +var player_stamina: CogitoAttribute = null +var network_authority: bool = false +var last_anim = "" + +func _ready(): + if wieldable_mesh: + wieldable_mesh.show() + + damage_area.body_entered.connect(_on_body_entered) + + if uses_stamina: + player_stamina = grab_player_stamina_attribute() + + # Set up network authority + network_authority = is_multiplayer_authority() + print("[Sword] Ready on peer: ", multiplayer.get_unique_id()) + print("[Sword] Network authority: ", network_authority) + + # Connect to player's animation event signal + if player_interaction_component: + var player = player_interaction_component.get_parent() + if player: + player.animation_event.connect(_on_animation_event) + print("[Sword] Connected to player animation events") + +func _process(_delta): + if not player_interaction_component: + return + + var player = player_interaction_component.get_parent() + if not player or not player.anim_player: + return + + var current_anim = player.anim_player.current_animation + if current_anim != last_anim: # Only check when animation changes + if current_anim == "Sword/Block" or \ + current_anim == "Sword/RBlock" or \ + current_anim == "Sword/BasicAttack" or \ + current_anim == "Sword/CenterAttack" or \ + current_anim == "Sword/LHook" or \ + current_anim == "Sword/RHook" or \ + current_anim == "Sword/MMAKick": + play_swing_sound.rpc() + print("[Sword] Playing swing sound for: ", current_anim) + last_anim = current_anim + +func _on_animation_event(event_name: String, event_data: Dictionary): + if event_name == "Sword/Block" or \ + event_name == "Sword/RBlock" or \ + event_name == "Sword/BasicAttack" or \ + event_name == "Sword/CenterAttack" or \ + event_name == "Sword/LHook" or \ + event_name == "Sword/RHook" or \ + event_name == "Sword/MMAKick": + play_swing_sound.rpc() + print("[Sword] Playing swing sound for animation event: ", event_name) + +func grab_player_stamina_attribute() -> CogitoAttribute: + if CogitoSceneManager._current_player_node.stamina_attribute: + return CogitoSceneManager._current_player_node.stamina_attribute + else: + print("Wieldable: No player stamina attribute found.") + return null + +func action_primary(_passed_item_reference:InventoryItemPD, _is_released: bool): + if wieldable_mesh: + wieldable_mesh.show() + + if _is_released: + return + + if animation_player.is_playing(): + return + + if uses_stamina: + if player_stamina.value_current < stamina_cost: + print("Wieldable: Not enough stamina!") + return + else: + player_stamina.subtract(stamina_cost) + + animation_player.play(anim_action_primary) + play_swing_sound.rpc() + +@rpc("any_peer", "call_local") +func play_swing_sound(): + if audio_stream_player_3d and swing_sound: + if sound_delay > 0: + await get_tree().create_timer(sound_delay).timeout + audio_stream_player_3d.stream = swing_sound + audio_stream_player_3d.play() + +func _on_body_entered(collider): + print("[Sword] Collision on peer:", multiplayer.get_unique_id(), "Collider:", collider.name) + + # Check if the collider is a valid target + if not collider.has_signal("damage_received"): + print("[Sword] Not a valid target") + return + + var player = player_interaction_component.get_parent() + + # Prevent self-damage + if collider == player: + print("[Sword] Prevented self-damage!") + return + + # Simulate attack hitting + var hit_position: Vector3 + var hit_direction: Vector3 + + if use_camera_collision: + hit_position = player_interaction_component.Get_Camera_Collision() + hit_direction = (hit_position - player.get_global_transform().origin).normalized() + else: + var space_state = damage_area.get_world_3d().direct_space_state + var hitbox_origin = damage_area.global_transform.origin + var ray_params = PhysicsRayQueryParameters3D.new() + ray_params.from = hitbox_origin + ray_params.to = collider.global_transform.origin + var result = space_state.intersect_ray(ray_params) + if result.size() > 0: + hit_position = result.position + hit_direction = (hit_position - hitbox_origin).normalized() + + # Force the RPC call + print("[Sword] 🔥 FORCING DAMAGE RPC from peer:", multiplayer.get_unique_id()) + send_damage.rpc(base_damage, hit_direction, hit_position, collider.get_path()) + +@rpc("any_peer", "call_local") +func send_damage(damage: float, direction: Vector3, position: Vector3, target_path: String): + var target = get_node_or_null(target_path) + if not target: + print("[Sword] Target not found") + return + + print("[Sword] Processing damage on peer:", multiplayer.get_unique_id()) + + # If target has the damage_received signal, emit it. + if target.has_signal("damage_received"): + print("[Sword] Emitting damage signal") + target.damage_received.emit(damage, direction, position) + + # Traverse upward until we find a node that actually implements receive_damage. + var player_node = target + while player_node and not player_node.has_method("receive_damage"): + player_node = player_node.get_parent() + + if player_node and player_node.has_method("receive_damage"): + print("[Sword] Sending RPC to target for damage") + # Using rpc here so that the target processes its own damage. + player_node.rpc("receive_damage", damage) + else: + print("[Sword] No node with receive_damage found in hierarchy!") \ No newline at end of file diff --git a/godot_scripts/weapons/sword_wieldable.tscn b/godot_scripts/weapons/sword_wieldable.tscn new file mode 100644 index 0000000..8c7885d --- /dev/null +++ b/godot_scripts/weapons/sword_wieldable.tscn @@ -0,0 +1,28 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://godot_scripts/weapons/sword_wieldable.gd" id="1"] + +[node name="SwordWieldable" type="Node3D"] +script = ExtResource("1") +weapon_type = 1 +damage_type = 1 +uses_stamina = true +stamina_cost = 4 +use_camera_collision = false +base_damage = 10.0 +sound_delay = 0.1 + +[node name="MeshInstance3D" type="MeshInstance3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0) + +[node name="DamageArea" type="Area3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0) + +[node name="CollisionShape3D" type="CollisionShape3D" parent="DamageArea"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0) + +[node name="AudioStreamPlayer3D" type="AudioStreamPlayer3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0) + +[node name="AnimationPlayer" type="AnimationPlayer" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0) \ No newline at end of file