the start of a simple context menu for dropping items#25
Conversation
devmoreir4
left a comment
There was a problem hiding this comment.
Thanks for working on this.
I tested it and the game does run, but Godot still reports some warnings/errors. I also noticed a few changes in the diff that seem unrelated to this feature, probably from local editor/project settings.
Could you please go through the PR again, fix the warnings/errors Godot reports, and remove anything that wasn't meant to be part of the pickup/drop change?
The main things to check are the project config changes, deleted UID/import files, item scene paths/data, and the multiplayer pickup/drop validation.
After that, I'll take another look.
| @@ -1,7 +1,2 @@ | |||
| # Normalize EOL for all files that Git considers text files. | |||
| ; Format: | ||
| ; [section] ; section goes between [] | ||
| ; param=value ; assign values to parameters | ||
|
|
There was a problem hiding this comment.
this looks like unrelated local/editor config churn. Please revert the project name back from TemplateTest, restore the description/tags, and keep only project settings that are required for pickup/drop.
|
I'll take a look at this. Tell me what you want with the UID files?
Version Control Filtering
If you don’t want .uid files in your repository but still need them locally:
*
Keep them generated locally (required for UID references to work).
*
Add them to .gitignore:
*.uid
________________________________
From: Copilot ***@***.***>
Sent: May 28, 2026 7:09 PM
To: devmoreir4/godot-3d-multiplayer-template ***@***.***>
Cc: Curtis ***@***.***>; Author ***@***.***>
Subject: Re: [devmoreir4/godot-3d-multiplayer-template] the start of a simple context menu for dropping items (PR #25)
@Copilot commented on this pull request.
Pull request overview
This PR introduces the first iteration of “world items” that can be picked up into the server-authoritative inventory, plus a right-click context menu in the inventory UI to drop items back into the world (with new item scenes/spawners to replicate dropped items in multiplayer).
Changes:
* Add pickup flow using an in-front Area3D to detect nearby world items and add them to inventory.
* Add inventory right-click context menu (initially focused on “Drop”) and item metadata to support context actions + scene spawning.
* Add replicated RigidBody3D item scenes and spawners/containers in the level to support networked world items.
Reviewed changes
Copilot reviewed 35 out of 48 changed files in this pull request and generated 16 comments.
Show a summary per file
File Description
scripts/player.gd Adds pickup handling, item spawning RPCs, and rigidbody collision interaction.
scripts/network.gd Adds a client connection debug print.
scripts/multiplayer_chat_ui.gd.uid Removes stored UID file for the chat UI script.
scripts/main_menu_ui.gd.uid Removes stored UID file for the main menu UI script.
scripts/level.gd Minor quit handler edits and debug item list change.
scripts/item.gd Adds context menu options + scene path data and drop/eat/etc stubs.
scripts/item_database.gd Sets scene_path for sample items so they can spawn as world scenes.
scripts/inventory_ui.gd Adds right-click context menu and “drop” action wiring.
scripts/3d_godot_robot.gd Adds pickup animation helper (play_pickup).
scenes/ui/main_menu_ui.tscn Godot scene re-serialization/unique_id updates.
scenes/ui/inventory_ui.tscn Adds MenuBar node (used to parent popup menu) and re-serialization updates.
scenes/ui/inventory_slot_ui.tscn Visual/background changes (gradient texture) and re-serialization updates.
scenes/level/level.tscn Adds item/monster containers and MultiplayerSpawner nodes for replicated spawns.
scenes/items/sword.tscn New replicated sword world item scene (RigidBody3D + synchronizer).
scenes/items/sword.gd.uid UID for sword script.
scenes/items/sword.gd Sword rigidbody script with item_id.
scenes/items/ruby_red_potion.tscn New replicated potion world item scene (RigidBody3D + synchronizer).
scenes/items/ruby_red_potion.gd.uid UID for potion script.
scenes/items/ruby_red_potion.gd Potion rigidbody script with item_id.
scenes/items/magic_gem.tscn New replicated gem world item scene (RigidBody3D + synchronizer).
scenes/items/magic_gem.gd.uid UID for gem script.
scenes/items/magic_gem.gd Gem rigidbody script with item_id.
scenes/items/leather_armor.tscn New replicated armor world item scene (RigidBody3D + synchronizer).
scenes/items/leaather_armor.gd.uid UID for armor script (typo in filename).
scenes/items/leaather_armor.gd Armor rigidbody script with item_id (typo in filename).
scenes/items/base_rigid_body_3d.gd.uid UID for base rigidbody script.
scenes/items/base_rigid_body_3d.gd Base rigidbody class for item rigidbodies.
scenes/items/base_item.tscn Base item rigidbody scene template.
scenes/items/apple.tscn New replicated apple world item scene (used as placeholder).
scenes/items/apple_rigid_body3d.gd.uid UID for apple script.
scenes/items/apple_rigid_body3d.gd Apple rigidbody script with item_id.
README.md Minor edit (currently includes an accidental stray character).
project.godot Updates project config, input mappings, rendering settings, and autoload references.
assets/characters/player/3DGodotRobot.glb.import Removes generated import metadata file.
.gitignore Reduces ignore rules to only .godot/ and /android/.
.gitattributes Removes export-ignore entries.
________________________________
💡 Add Copilot custom instructions</devmoreir4/godot-3d-multiplayer-template/new/main?filename=.github/instructions/*.instructions.md> for smarter, more guided reviews. Learn how to get started<https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot>.
________________________________
In scripts/player.gd<#25 (comment)>:
+ if Input.is_action_just_pressed("pickup") || get_node("3DGodotRobot/AnimationPlayer").current_animation == "Emote2" :
+ _body.play_pickup()
+ return
The pickup handling will repeatedly call play_pickup() every physics frame while the animation is "Emote2", which can effectively restart the animation and keep the player stuck returning early (no movement/gravity updates). Consider triggering pickup only on the input edge, and track pickup state via an animation_finished signal (or a timer) so you block movement only while the animation is actually playing, without re-playing it each frame.
________________________________
In scripts/player.gd<#25 (comment)>:
***@***.***("any_peer", "call_local", "reliable")
+func request_add_single_item(item_id: String) -> bool:
+ print("Debug: request_add_item called on player ", name, " (authority: ", get_multiplayer_authority(), ") by client ", multiplayer.get_remote_sender_id())
+ var item = ItemDatabase.get_item(item_id)
+ if not item:
+ push_warning("Item not found: " + item_id)
+ return false
+
+ var remaining = player_inventory.add_item(item, 1)
+
+ if remaining == 0:
+ var owner_id = get_multiplayer_authority()
+ if owner_id != 1:
+ sync_inventory_to_owner.rpc_id(owner_id, player_inventory.to_dict())
+ else:
+ var level_scene = get_tree().get_current_scene()
+ if level_scene and level_scene.has_method("update_local_inventory_display"):
+ level_scene.update_local_inventory_display()
+ return true
+ else:
+ return false
request_add_single_item() mutates player_inventory but does not enforce server-only execution (no multiplayer.is_server() guard / sender validation). Because server_pickup() is marked call_local, clients will run this path too and can desync or locally “grant” themselves items. Make server_pickup() server-only (remove call_local) and/or add a server guard + sender check before modifying inventory.
________________________________
In scripts/player.gd<#25 (comment)>:
***@***.***("authority", "call_local", "reliable")
+func add_world_item( scene_path:String, player_position:Vector3) -> void:
+ var instance_item = load( scene_path ).instantiate()
+ instance_item.position = player_position
+
+ get_node("/root/Level/Environment/ItemContainer").add_child( instance_item, 1 )
+
add_world_item() is marked @rpc("authority", "call_local", ...) and loads/instantiates an arbitrary scene_path provided by the caller. With call_local, the client will also spawn a local copy when calling rpc_id(1, ...), and a malicious client could request spawning arbitrary scenes/positions. Make this server-only (remove call_local, require multiplayer.is_server()), validate the sender matches the player authority, and whitelist/validate scene_path (e.g., via ItemDatabase). Also handle failed load() gracefully.
________________________________
In scripts/player.gd<#25 (comment)>:
***@***.***("any_peer", "call_local", "reliable")
+func applyForceToServerObject( nameOfObject : String, normal : Vector3 ):
+ # originally get_tree().get_nodes_in_group("objects")
+ var object_node = get_node("/root/Level/Environment/ItemContainer")
+ if object_node != null:
+ for n in object_node.get_children():
+ if n.name == nameOfObject:
+ n.apply_force( normal * 100)
+ pass
applyForceToServerObject() is exposed as @rpc("any_peer", "call_local", ...) and selects targets by node name. This lets any client request forces on arbitrary rigid bodies (and with call_local it applies locally too), which is both a cheating vector and can lead to client/server divergence. Restrict this to server execution, validate the sender/target, and prefer a stable identifier (e.g., get_instance_id() or a server-issued ID) over name.
________________________________
In scripts/inventory_ui.gd<#25 (comment)>:
+ _hide_tooltip()
+ var context_menu = PopupMenu.new()
+ menubar.add_child( context_menu )
+ context_menu.id_pressed.connect( _on_item_selected )
+ for item_option in current_item.context_options:
+ context_menu.add_item( _get_context_menu_string(item_option), item_option )
+
+ context_menu.set_position( get_viewport().get_mouse_position() )
+ context_menu.popup()
+
Right-click currently creates a new PopupMenu every time and adds it as a child of $MenuBar, but it’s never reused or freed. Over time this will accumulate nodes and signal connections. Consider keeping a single PopupMenu instance (created in _ready()), clearing/repopulating its items on each right-click, and reusing it.
________________________________
In scripts/inventory_ui.gd<#25 (comment)>:
+ elif index == Item.ContextOptions.DROP:
+ print( "attempting to drop item " )
+
+ current_player.add_world_item.rpc_id( 1, current_item.scene_path, current_player.get_node("3DGodotRobot/InfrontArea3D").global_position )
+ slot.remove_item(1)
+ refresh_display()
+ pass
Dropping an item updates the slot locally via slot.remove_item(1) but does not send a server-authoritative inventory change (request_remove_item / similar). In multiplayer this will desync the client from the server (and can be exploited). The drop action should: (1) request the server to remove the item from the inventory, and (2) have the server spawn the world item only after a successful removal.
________________________________
In scripts/item.gd<#25 (comment)>:
+# an array of integers from an enum
***@***.*** var context_options: Array[int] = [ContextOptions.DROP]
+# not sure we need the whole scene, just a path should be enough
***@***.*** var scene_path: String = "res://scenes/objects/apple.tscn"
scene_path defaults to res://scenes/objects/apple.tscn, but there is no scenes/objects/ directory in this repo and from_dict() defaults to a different path (res://scenes/items/apple.tscn). Use a single valid default (likely under scenes/items/) to avoid broken drops/spawns and inconsistent serialization defaults.
________________________________
In scripts/item.gd<#25 (comment)>:
+func drop( drop_node : Node3D ):
+ var player = drop_node.get_node("..")._get_local_player()
+ var marker_position = player.get_child("Marker3D").position
+
+ # drop at player position into "environment
+ var player_position = player.position + marker_position
+
+ player.add_world_item.rpc_id( 1, scene_path, player_position )
player.get_child("Marker3D") is invalid: get_child() takes an index, not a node name, so this will throw at runtime. Use get_node("Marker3D") (or get_node_or_null) to fetch the marker node safely.
________________________________
In scripts/item.gd<#25 (comment)>:
+func read():
+ print("defaul read called")
Typo in log message: "defaul read called" → "default read called".
________________________________
In scripts/item_database.gd<#25 (comment)>:
@@ -80,6 +84,7 @@ func _create_sample_items():
pickaxe.stackable = false
pickaxe.value = 100
pickaxe.icon = placeholder_icon
+ pickaxe.scene_path = "res://scenes/items/apple.tscn"
items[pickaxe.id] = pickaxe
iron_pickaxe.scene_path points to res://scenes/items/apple.tscn, but the apple scene’s script sets item_id = "apple" (and there is no "apple" item in ItemDatabase). As a result, pickup logic that looks up ItemDatabase.get_item(item_id) will fail for spawned pickaxes. Either create a pickaxe scene/script with item_id = "iron_pickaxe", or add an "apple" item entry and use that ID consistently.
________________________________
In scenes/items/leather_armor.tscn<#25 (comment)>:
@@ -0,0 +1,33 @@
+[gd_scene format=3 uid="uid://bwudvnrsyts07"]
+
+[ext_resource type="Script" uid="uid://d08vmi4wiiy2y" path="res://scenes/items/leaather_armor.gd" id="1_ewbxo"]
The armor scene references res://scenes/items/leaather_armor.gd (double “a”). This typo makes the resource naming inconsistent and easy to miss when searching/maintaining. Consider renaming the script file to leather_armor.gd (and updating the reference/uid) for consistency.
________________________________
In README.md<#25 (comment)>:
@@ -49,7 +49,7 @@ Ensure the script has execution permissions (`chmod +x run_headless_server.sh`)
* <kbd>B</kbd> to toggle inventory.
* <kbd>F1</kbd> to add random test item (debug).
* <kbd>F2</kbd> to print inventory contents (debug).
-
+w
There’s an extraneous w character under the Controls list. This looks accidental and should be removed.
________________________________
In project.godot<#25 (comment)>:
[application]
-config/name="TemplateOnlineMultiplayer"
-config/description="This is a foundational template for a 3D multiplayer game, developed in Godot Engine."
-config/tags=PackedStringArray("3d", "network")
-run/main_scene="res://scenes/level/level.tscn"
-config/features=PackedStringArray("4.6", "Forward Plus")
-config/icon="res://icon.png"
+config/name="TemplateTest"
+run/main_scene="uid://dugaivbj1o66n"
+config/features=PackedStringArray("4.6", "GL Compatibility")
+config/icon="res://icon.svg"
config/icon is set to res://icon.svg, but this repository doesn’t include an icon.svg file. This will break project loading/export until the icon path is corrected (e.g., keep using res://icon.png or add the SVG to the repo).
________________________________
In project.godot<#25 (comment)>:
+[editor]
-window/size/viewport_width=1280
-window/size/viewport_height=720
+movie_writer/movie_file="C:/Users/curti/downloads/godot-3d-multiplayer-template-main/godot-3d-multiplayer-template-main/output.avi"
+movie_writer/fps=30
The [editor] movie_writer/movie_file setting is committed with a machine-specific absolute Windows path. This is not portable and will cause confusing editor settings for other contributors/CI. Consider removing the [editor] movie_writer entries from version control (or moving them to per-user editor settings).
________________________________
In project.godot<#25 (comment)>:
inventory={
"deadzone": 0.2,
-"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":66,"key_label":0,"unicode":98,"location":0,"echo":false,"script":null)
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":73,"key_label":0,"unicode":105,"location":0,"echo":false,"script":null)
+]
+}
+pickup={
+"deadzone": 0.2,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
]
}
The inventory input action is now bound to physical keycode 73 (I key). README (Controls) and Level._notification() currently document B for inventory, so controls/docs are out of sync. Either revert the binding to B or update the docs/UI hints to match the new key.
________________________________
In scripts/player.gd<#25 (comment)>:
+# client calls this
+func pickup():
+ server_pickup.rpc_id(1)
+
***@***.***("any_peer", "call_local", "reliable")
+func server_pickup():
+ var array_of_items = get_node("3DGodotRobot/InfrontArea3D").get_overlapping_bodies()
+ for item in array_of_items:
+ if item.get("item_id") != null:
+ var result = request_add_single_item(item.get("item_id"))
+ if result:
+ print( "item addded to inventory")
+ item.queue_free()
+ else:
+ print("unable to add item to inventory")
server_pickup() is declared as @rpc("any_peer", "call_local", ...). With call_local, the caller client will execute pickup logic locally (including queue_free()), which can cause client/server divergence and makes cheating easier. This RPC should run only on the server; remove call_local and add a multiplayer.is_server() guard + sender validation before granting items / freeing world items.
—
Reply to this email directly, view it on GitHub<#25?email_source=notifications&email_token=AACKZZYCG36R7ES2XXTAGM345DBJLA5CNFSNUABKM5UWIORPF5TWS5BNNB2WEL2QOVWGYUTFOF2WK43UKJSXM2LFO4XTIMZYGUZTSNZQHAYKM4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2KYZTPN52GK4S7MNWGSY3L#pullrequestreview-4385397080>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AACKZZ3NAFNPENEDEII44FT45DBJLAVCNFSM6AAAAACZOLZZQ6VHI2DSMVQWIX3LMV43YUDVNRWFEZLROVSXG5CSMV3GSZLXHM2DGOBVGM4TOMBYGA>.
Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS<https://github.com/notifications/mobile/ios/AACKZZYMCCCBGTU7G4JQLAT45DBJLA5CNFSNUABKM5UWIORPF5TWS5BNNB2WEL2QOVWGYUTFOF2WK43UKJSXM2LFO4XTIMZYGUZTSNZQHAYKM4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2KUZTPN52GK4S7NFXXG> and Android<https://github.com/notifications/mobile/android/AACKZZ7BMMI6RL5X5TZHHPL45DBJLA5CNFSNUABKM5UWIORPF5TWS5BNNB2WEL2QOVWGYUTFOF2WK43UKJSXM2LFO4XTIMZYGUZTSNZQHAYKM4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2K4ZTPN52GK4S7MFXGI4TPNFSA>. Download it today!
You are receiving this because you authored the thread.Message ID: ***@***.***>
|
|
I'd keep them committed. The scenes still reference those scripts by UID, so deleting/ignoring the The best fix here is to restore the missing UID files, or re-save/update the scenes in Godot so the references are clean. |
unecessary Revert "Merge pull request devmoreir4#23 from devmoreir4/dev" This reverts commit 05092e5, reversing changes made to 9445587.
|
I think this reverted a bit too much. The PR now only changes the README. You can check what's currently changed in the Files changed tab. |
attack, damage and death for character, platforms, world items gems, pickaxe
|
I've checked in a bunch of changes that gives you some more "game" features where the players can hit each other and pick up items in the world. Also i put some blocks in the game so I have something to jump on. This code should be useful to add to your template. It may need some refining / networking fixes as I wasn't focused on the I would post a video but the godot recording doesn't seem to work very well with multiple instances running. todo: |
|
Maybe i should start a new pull request so I could get the AI summary again? |
client. Not sure if this bug always existed.
|
noticed a serious problem picking up items. |
…ssue. Removed objects from ItemsContainer and added them with code
ground too often as a rectange/square.
fix: ipaddress works better in the binary client if it's not localhost/127.0.0.1 with 127.0.0.1 i was not able to connect with a client
|
No need to start a new PR just for the AI summary. You can keep working in this one. I can run the summary again after the next update, but don't worry too much about it. I'll review the actual diff. The extra gameplay ideas sound cool too. |
|
Here's a video of the game solo player.
https://youtu.be/X8JP5vjSqLw
I wanted to use this as a template / basis for a game after everything is working pretty good.
Curtis.
…________________________________
From: Carlos Moreira ***@***.***>
Sent: June 3, 2026 5:15 PM
To: devmoreir4/godot-3d-multiplayer-template ***@***.***>
Cc: Curtis ***@***.***>; Author ***@***.***>
Subject: Re: [devmoreir4/godot-3d-multiplayer-template] the start of a simple context menu for dropping items (PR #25)
[https://avatars.githubusercontent.com/u/75152235?s=20&v=4]devmoreir4 left a comment (devmoreir4/godot-3d-multiplayer-template#25)<#25 (comment)>
No need to start a new PR just for the AI summary. You can keep working in this one.
I can run the summary again after the next update, but don't worry too much about it. I'll review the actual diff.
The extra gameplay ideas sound cool too.
—
Reply to this email directly, view it on GitHub<#25?email_source=notifications&email_token=AACKZZZ3W36C7SO4OZZHMOT46CIOHA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTINRRGY3TONJQGA4KM4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2KYZTPN52GK4S7MNWGSY3L#issuecomment-4616775008>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AACKZZ26NTO3KJXRYS7BCML46CIOHAVCNFSM6AAAAACZOLZZQ6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHM2DMMJWG43TKMBQHA>.
Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS<https://github.com/notifications/mobile/ios/AACKZZ7IGASQVD3TAEMWSX346CIOHA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTINRRGY3TONJQGA4KM4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2KUZTPN52GK4S7NFXXG> and Android<https://github.com/notifications/mobile/android/AACKZZ2HYT3PQMBEXXGVXAD46CIOHA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTINRRGY3TONJQGA4KM4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2K4ZTPN52GK4S7MFXGI4TPNFSA>. Download it today!
You are receiving this because you authored the thread.Message ID: ***@***.***>
|
|
I see "1 requested change" but don't see it. Also seems like my video cuts out half way through probably because i tabbed in and out of the application and for whatever reason the recording application doesn't register that that host/join node is no longer visible. |
|
the "1 requested change" is from my previous review. I'll update/clear it when I review the PR again. I did a quick multiplayer test and found a few issues: the skin color doesn't seem to update, gravity breaks after jumping + attacking, and the items being too large. |
|
caused it so the texture could no longer be set as a null was being returned in player.set_mesh_texture. The color changes but not how you want it to. It changes color of the other players when you want it to only change / remember your own color.
introduced. It was fixed by instantiating a new material for each player instead of using the existing material. Somehow the material got shared between each instance.
|
color problems are fixed. |
I've added a right click context to drop items.
I've added the ability to pickup items in the world and put them in the inventory.
Using emote2 animation for pickups and have a little area in front of the character that is used to determine what items
are nearby to pickup.
I really like your server and have tested the --headless mode with 3 run instances and it works fine,
also tested this change in host/join with 2 run instances.
I think NAT punch to get this working through firewalls might be something to consider in the fucture.
In the player I've added the collision function for the rigidbody3d object, I think this the wrong way of doing this.
I was very close to having this working without doing this and just relying on the godot physics.
Sorry if this is a little messy this is my first time doing a "pull request"