diff --git a/WEAPON_SYSTEM.md b/WEAPON_SYSTEM.md index 46de776..65813ce 100644 --- a/WEAPON_SYSTEM.md +++ b/WEAPON_SYSTEM.md @@ -62,27 +62,54 @@ pickup_radius = 1.5 weight = 2.0 ``` -### Step 3: Create a WorldWeapon Scene (Optional) - -If you want to place weapons in the level: - +### Step 3: Spawn Weapons in the Level + +There are two ways to add weapons to your level: + +#### Option A: Manual Placement (Recommended) + +1. Open your level scene (e.g., `level/scenes/level.tscn`) +2. Drag a WorldWeapon scene into `WeaponsContainer`: + - From FileSystem panel: `level/scenes/weapons/world_weapon_sword.tscn` + - Or: `level/scenes/weapons/world_weapon_shield.tscn` + - Drop it as a child of `WeaponsContainer` +3. Position the weapon in 3D space where you want it to spawn +4. Save the scene + +**How it works:** +- The server automatically detects manually placed WorldWeapon nodes on startup +- Each weapon is assigned a unique ID and tracked for multiplayer +- Clients automatically receive synced copies via RPC +- No code changes needed - just drag and drop! + +#### Option B: Dynamic Spawning + +Spawn weapons via code in `level.gd`: + +```gdscript +# In _spawn_initial_weapons() or wherever you need +_weapon_spawn_counter += 1 +rpc("spawn_world_weapon", + "res://level/resources/weapon_sword.tres", + Vector3(5, 1, 0), # spawn position + Vector3.ZERO, # initial velocity + _weapon_spawn_counter +) ``` -[RigidBody3D] - WorldWeaponYourWeapon - └─ [CollisionShape3D] - Approximate weapon shape -``` - -Set the script to `world_weapon.gd` and assign your WeaponData resource. -Example: `level/scenes/weapons/world_weapon_sword.tscn` +This is useful for: +- Spawning weapons at runtime +- Procedural weapon placement +- Loot drops from enemies ## Testing ### In Godot Editor 1. Open `level/scenes/level.tscn` -2. Drag a WorldWeapon scene (e.g., `world_weapon_sword.tscn`) into the level -3. Position it somewhere visible -4. Run the game (F5) +2. Add a manually placed weapon (see Option A above) or modify `_spawn_initial_weapons()` in `level.gd` +3. Run the game (F5) +4. Walk near the weapon and press **E** to pick it up ### Controls diff --git a/level/resources/weapon_testsword.tres b/level/resources/weapon_testsword.tres index 4a311bd..6b05c27 100644 --- a/level/resources/weapon_testsword.tres +++ b/level/resources/weapon_testsword.tres @@ -1,6 +1,7 @@ -[gd_resource type="Resource" script_class="WeaponData" load_steps=2 format=3 uid="uid://pqoldmf2q3t6"] +[gd_resource type="Resource" script_class="WeaponData" load_steps=3 format=3 uid="uid://pqoldmf2q3t6"] [ext_resource type="Script" uid="uid://d2homvlmrg6xs" path="res://level/scripts/weapon_data.gd" id="1"] +[ext_resource type="PackedScene" uid="uid://rkvkbxlweo60" path="res://level/scenes/weapons/TestSwordMesh.tscn" id="1_gdc1w"] [resource] script = ExtResource("1") @@ -10,4 +11,5 @@ damage = 20.0 attack_range = 3.5 attack_cooldown = 0.6 knockback_force = 12.0 +mesh_scene = ExtResource("1_gdc1w") weight = 2.0 diff --git a/level/scenes/level.tscn b/level/scenes/level.tscn index 688d209..be288e1 100644 --- a/level/scenes/level.tscn +++ b/level/scenes/level.tscn @@ -1,9 +1,10 @@ -[gd_scene load_steps=18 format=3 uid="uid://dugaivbj1o66n"] +[gd_scene load_steps=19 format=3 uid="uid://dugaivbj1o66n"] [ext_resource type="Script" uid="uid://d0dgljwwl463n" path="res://level/scripts/level.gd" id="1_e1sh7"] [ext_resource type="PackedScene" uid="uid://db06e8q8f8bdq" path="res://level/scenes/Player_Lilguy.tscn" id="1_uvcbi"] [ext_resource type="FontFile" uid="uid://diapabmalpcrj" path="res://assets/fonts/Kurland.ttf" id="3_icc4p"] [ext_resource type="PackedScene" uid="uid://b48oxbcgxu3d8" path="res://assets/Objects/Colosseum_10.fbx" id="4_u750a"] +[ext_resource type="PackedScene" uid="uid://hd6pq287rgye" path="res://level/scenes/weapons/world_weapon_testsword.tscn" id="5_cwx4m"] [sub_resource type="PlaneMesh" id="PlaneMesh_r5xs5"] size = Vector2(90, 90) @@ -346,6 +347,11 @@ offset_bottom = 40.0 [node name="Colosseum_10" parent="." instance=ExtResource("4_u750a")] transform = Transform3D(15, 0, 0, 0, 15, 0, 0, 0, 15, 1.301034, -1.2294581, 2.0630608) +[node name="WeaponsContainer" type="Node3D" parent="."] + +[node name="WorldWeaponSword" parent="WeaponsContainer" instance=ExtResource("5_cwx4m")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.0268106, 2.6057472, 8.836907) + [connection signal="pressed" from="Menu/MainContainer/MainMenu/Buttons/Host" to="." method="_on_host_pressed"] [connection signal="pressed" from="Menu/MainContainer/MainMenu/Buttons/Join" to="." method="_on_join_pressed"] [connection signal="pressed" from="Menu/MainContainer/MainMenu/Option4/Quit" to="." method="_on_quit_pressed"] diff --git a/level/scripts/level.gd b/level/scripts/level.gd index 48e4e70..8cf90e4 100644 --- a/level/scripts/level.gd +++ b/level/scripts/level.gd @@ -39,15 +39,81 @@ func _ready(): add_child(weapons_container) print("Created WeaponsContainer") + # Clients: Remove manually placed weapons (server will sync them via RPC) if not multiplayer.is_server(): + _cleanup_manual_weapons_on_client() return Network.connect("player_connected", Callable(self, "_on_player_connected")) multiplayer.peer_disconnected.connect(_remove_player) + # Initialize any manually placed weapons in the scene + _initialize_manual_weapons() + # Spawn initial weapons when server starts _spawn_initial_weapons() +func _cleanup_manual_weapons_on_client(): + """Remove manually placed weapons on clients (server will sync them via RPC)""" + if not weapons_container: + return + + var weapons_to_remove = [] + for child in weapons_container.get_children(): + if child is WorldWeapon and child.weapon_id == -1: + weapons_to_remove.append(child) + + for weapon in weapons_to_remove: + print("[Client] Removing manually placed weapon: ", weapon.name) + weapon.queue_free() + +func _initialize_manual_weapons(): + """Initialize any WorldWeapon nodes manually placed in the level scene""" + if not multiplayer.is_server(): + return + + if not weapons_container: + return + + # Find all WorldWeapon nodes in the weapons container + var manual_weapons = [] + for child in weapons_container.get_children(): + if child is WorldWeapon: + manual_weapons.append(child) + + if manual_weapons.is_empty(): + print("[Server] No manually placed weapons found") + return + + print("[Server] Found ", manual_weapons.size(), " manually placed weapon(s)") + + # Initialize each manually placed weapon + for weapon in manual_weapons: + # Skip if already initialized (weapon_id != -1) + if weapon.weapon_id != -1: + continue + + # Assign unique ID + _weapon_spawn_counter += 1 + weapon.weapon_id = _weapon_spawn_counter + + # Set deterministic name for networking + var old_name = weapon.name + weapon.name = "WorldWeapon_" + str(weapon.weapon_id) + + # Track in active weapons + _active_weapons[weapon.weapon_id] = weapon + + # Connect cleanup signal + weapon.tree_exiting.connect(_on_weapon_removed.bind(weapon.weapon_id)) + + print("[Server] Initialized manual weapon '", old_name, "' with ID: ", weapon.weapon_id, " at position: ", weapon.global_position) + + # Verify weapon_data is set + if not weapon.weapon_data: + push_error("Manual weapon '", old_name, "' has no WeaponData assigned!") + continue + func _spawn_initial_weapons(): if not multiplayer.is_server(): return diff --git a/level/scripts/world_weapon.gd b/level/scripts/world_weapon.gd index 4c81cfb..a1dde0a 100644 --- a/level/scripts/world_weapon.gd +++ b/level/scripts/world_weapon.gd @@ -14,6 +14,8 @@ var _pickup_area: Area3D = null var _is_being_picked_up: bool = false func _ready(): + print("[WorldWeapon ", name, "] _ready() called. weapon_id=", weapon_id, ", weapon_data=", weapon_data) + # Set collision layer to "weapon" (layer 3 = bit 4) collision_layer = 4 collision_mask = 2 # Collide with world @@ -21,10 +23,14 @@ func _ready(): # Set up physics if weapon_data: mass = weapon_data.weight + print("[WorldWeapon ", name, "] Set mass to ", mass) # Spawn mesh if weapon_data and weapon_data.mesh_scene: + print("[WorldWeapon ", name, "] Spawning mesh from ", weapon_data.mesh_scene) _spawn_mesh() + else: + print("[WorldWeapon ", name, "] ERROR: Cannot spawn mesh. weapon_data=", weapon_data, ", mesh_scene=", weapon_data.mesh_scene if weapon_data else "null") # Create collision shape if not exists _setup_collision() @@ -44,6 +50,7 @@ func _spawn_mesh(): # Instantiate mesh _mesh_instance = weapon_data.mesh_scene.instantiate() add_child(_mesh_instance) + print("[WorldWeapon ", name, "] Mesh spawned successfully: ", _mesh_instance) func _setup_collision(): # Check if we already have a collision shape