7 Commits

Author SHA1 Message Date
JonShard 7648ecbac9 Add Sweetlogger 2026-08-10 13:47:01 +02:00
JonShard bf31f85b02 Fix StationMovement in Multiplayer, still works singleplayer 2026-08-10 13:02:19 +02:00
JonShard dbaad35e50 Fix gamemanager state syncing 2026-08-10 10:23:38 +02:00
JonShard 513ad23e09 Warning hunt 2026-08-09 14:04:49 +02:00
JonShard 9cdf72dc47 Add MultiplayerSyncronizers to stations 2026-08-09 13:15:30 +02:00
JonShard cf4b4ac32e Change GameManager to use RPCs to sync state 2026-08-09 10:10:04 +02:00
JonShard f156379ff3 Notes 2026-08-09 08:17:47 +02:00
59 changed files with 1184 additions and 601 deletions
+7 -7
View File
@@ -29,13 +29,13 @@ func _ready() -> void:
func _on_body_entered(body: Node3D) -> void:
if not NetworkManager.owns_world():
return
print("Container enabled: ", enabled)
SweetLogger.debug("enabled: {0}", [enabled], "container.gd", "_on_body_entered")
if not enabled:
print("Container disabled in _on_body_entered body")
SweetLogger.debug("disabled in _on_body_entered body", [], "container.gd", "_on_body_entered")
return
if not body.is_in_group(target_group):
return
print("Container _on_body_entered body: ", body)
SweetLogger.debug("body: {0}", [body], "container.gd", "_on_body_entered")
# If one of us is in a station
@@ -46,14 +46,14 @@ func _on_body_entered(body: Node3D) -> void:
# If enough space, add item
var food_item = body.get_node("FoodItem")
print("Container in station found %s of type %s: %s" % [target_group, FoodItem.Type.keys()[food_item.type], body.name])
SweetLogger.debug("in station found {0} of type {1}: {2}", [target_group, FoodItem.Type.keys()[food_item.type], body.name], "container.gd", "_on_body_entered")
var meal_count := contained_items.filter(func(f): return f.type == FoodItem.Type.MEAL).size()
var side_count := contained_items.filter(func(f): return f.type == FoodItem.Type.SIDE).size()
if food_item.type == FoodItem.Type.MEAL and meal_count < meal_positions.size():
print("Container adding meal")
SweetLogger.debug("adding meal", [], "container.gd", "_on_body_entered")
_add_item(body)
if food_item.type == FoodItem.Type.SIDE and side_count < side_positions.size():
print("Container adding side")
SweetLogger.debug("adding side", [], "container.gd", "_on_body_entered")
_add_item(body)
@@ -93,7 +93,7 @@ func _add_item(item: Node3D) -> void:
NetworkManager.despawn_item(item)
# In case the container is on a table that needs to register this addition,
# ask all table in scene to absorb any new items.
print("Container group call absorb_items()")
SweetLogger.debug("group call absorb_items()", [], "container.gd", "_add_item")
get_tree().call_group("table", "absorb_items")
+2 -2
View File
@@ -112,8 +112,8 @@ func _populate_world_if_owner() -> void:
func _init_recipes():
GameManager.meals_in_play = ["hamburger"]
GameManager.sides_in_play = ["cube"]
GameManager.set_meals_in_play(["hamburger"])
GameManager.set_sides_in_play(["cube"])
# Free the authored template nodes. Done immediately rather than with
+14 -33
View File
@@ -18,7 +18,7 @@ var _pickable: XRToolsPickable
# RigidBody3D default of STATIC) — captured once so it can be restored when
# this peer regains ownership, instead of getting stuck on whatever
# apply_held_state() last forced it to while non-authority.
var _original_freeze_mode: int
var _original_freeze_mode: RigidBody3D.FreezeMode
# Same idea for the pickable's authored `enabled` flag, which the non-authority
# branch of apply_held_state() clears while someone else is holding the item.
@@ -51,9 +51,7 @@ func _set_net_held_by(value: int) -> void:
var old := net_held_by
net_held_by = value
if old != value and NetworkManager.is_online():
print("%s net_held_by: %d -> %d (local state: %s, authority=%d)" % [
_pickable.name, old, value, _holder_desc(), get_multiplayer_authority()
])
SweetLogger.debug("{0} net_held_by: {1} -> {2} (local state: {3}, authority={4})", [_pickable.name, old, value, _holder_desc(), get_multiplayer_authority()], "net_pickable.gd", "_set_net_held_by")
apply_held_state()
@@ -71,7 +69,7 @@ func _set_net_held_by(value: int) -> void:
func apply_held_state() -> void:
if not _pickable:
return
if not NetworkManager.is_online() or is_multiplayer_authority():
if not NetworkManager.is_online() or (is_inside_tree() and is_multiplayer_authority()) :
_grab_race_logged = false
# We own this item's simulation (offline, loose+server, or currently
# holding it). If it's not actively in our own hand right now, make
@@ -84,12 +82,7 @@ func apply_held_state() -> void:
or _pickable.collision_mask != _pickable.original_collision_mask
if changed:
if NetworkManager.is_online():
print(
"%s: reclaiming ownership, restoring freeze_mode %d->%d collision_mask %d->%d" % [
_pickable.name, _pickable.freeze_mode, _original_freeze_mode,
_pickable.collision_mask, _pickable.original_collision_mask
]
)
SweetLogger.debug("{0}: reclaiming ownership, restoring freeze_mode {1}->{2} collision_mask {3}->{4}", [_pickable.name, _pickable.freeze_mode, _original_freeze_mode, _pickable.collision_mask, _pickable.original_collision_mask], "net_pickable.gd", "apply_held_state")
_pickable.freeze_mode = _original_freeze_mode
_pickable.collision_mask = _pickable.original_collision_mask
# Unlike freeze/collision (which XRToolsPickable manages itself while
@@ -104,9 +97,7 @@ func apply_held_state() -> void:
# driver that then fell out of the station.
if _pickable.enabled != _original_enabled:
if NetworkManager.is_online():
print("%s: reclaiming ownership, restoring enabled %s->%s" % [
_pickable.name, _pickable.enabled, _original_enabled
])
SweetLogger.debug("{0}: reclaiming ownership, restoring enabled {1}->{2}", [_pickable.name, _pickable.enabled, _original_enabled], "net_pickable.gd", "apply_held_state")
_pickable.enabled = _original_enabled
return
# A net_held_by/position sync update can race ahead of the
@@ -119,21 +110,13 @@ func apply_held_state() -> void:
# Log once per grab, not once per tick.
if NetworkManager.is_online() and not _grab_race_logged:
_grab_race_logged = true
print(
"%s: ignoring non-authority sync (net_held_by=%d) — still actively held by our own hand (grab-race guard)" % [
_pickable.name, net_held_by
]
)
SweetLogger.debug("{0}: ignoring non-authority sync (net_held_by={1}) — still actively held by our own hand (grab-race guard)", [_pickable.name, net_held_by], "net_pickable.gd", "apply_held_state")
return
_grab_race_logged = false
# Someone else owns it: stop simulating locally, just follow the sync.
if _pickable.is_picked_up():
if NetworkManager.is_online():
print(
"%s: was held by %s on this peer, but authority now says peer %d owns it — force-dropping" % [
_pickable.name, _holder_desc(), net_held_by
]
)
SweetLogger.debug("{0}: was held by {1} on this peer, but authority now says peer {2} owns it — force-dropping", [_pickable.name, _holder_desc(), net_held_by], "net_pickable.gd", "apply_held_state")
_pickable.drop()
# Bail out when we're already in the follow-the-sync state. Without this the
# writes below (and the line logged with them) repeated every tick for every
@@ -147,8 +130,8 @@ func apply_held_state() -> void:
and _pickable.collision_mask == 0 \
and _pickable.enabled == want_enabled:
return
if NetworkManager.is_online():
print("%s: freezing (non-authority, owner=peer %d)" % [_pickable.name, net_held_by])
# if NetworkManager.is_online(): # This was spamming that Knife was frozen every frame.
# print("%s: freezing (non-authority, owner=peer %d)" % [_pickable.name, net_held_by])
_pickable.freeze = true
_pickable.freeze_mode = RigidBody3D.FREEZE_MODE_KINEMATIC
_pickable.collision_mask = 0
@@ -165,10 +148,10 @@ func _on_picked_up(_p) -> void:
# addon's own "grab out of a snap zone" shortcut mid-cascade) — not a
# player-initiated hand grab, so no authority request from here.
if NetworkManager.is_online():
print("%s picked up by %s (not a hand) — no authority request" % [_pickable.name, _holder_desc()])
SweetLogger.debug("{0} picked up by {1} (not a hand) — no authority request", [_pickable.name, _holder_desc()], "net_pickable.gd", "_on_picked_up")
return
if NetworkManager.is_online():
print("%s grabbed by hand (authority was peer %d), requesting authority" % [_pickable.name, net_held_by])
SweetLogger.debug("{0} grabbed by hand (authority was peer {1}), requesting authority", [_pickable.name, net_held_by], "net_pickable.gd", "_on_picked_up")
NetworkManager.request_item_authority_from(_pickable.get_path())
@@ -177,12 +160,10 @@ func _on_dropped(_p) -> void:
# apply_held_state() losing authority (see above) must not re-report.
if not is_multiplayer_authority():
if NetworkManager.is_online():
print("%s dropped locally, but we aren't its authority (peer %d is) — not reporting" % [_pickable.name, net_held_by])
SweetLogger.debug("{0} dropped locally, but we aren't its authority (peer {1} is) — not reporting", [_pickable.name, net_held_by], "net_pickable.gd", "_on_dropped")
return
if NetworkManager.is_online():
print("%s dropped, reporting release to server (lin=%s ang=%s)" % [
_pickable.name, _pickable.linear_velocity, _pickable.angular_velocity
])
SweetLogger.debug("{0} dropped, reporting release to server (lin={1} ang={2})", [_pickable.name, _pickable.linear_velocity, _pickable.angular_velocity], "net_pickable.gd", "_on_dropped")
# Send our own final transform too: we were the authority until now, and the
# server's copy may not have received our last position sync yet.
NetworkManager.release_item_authority_from(
@@ -205,5 +186,5 @@ func _holder_desc() -> String:
return "hand(%s)" % by.get_path()
if by is XRToolsSnapZone:
var station := by.get_parent()
return "zone(%s)" % (station.name if station else str(by.get_path()))
return "zone(%s)" % (str(station.name) if station else str(by.get_path()))
return "other(%s: %s)" % [by.get_class(), by.get_path()]
+4 -3
View File
@@ -331,7 +331,7 @@ func _try_snap_into_station(item: Node) -> void:
var by: Node = null
if item.has_method("get_picked_up_by"):
by = item.get_picked_up_by()
log_line("skipped snapping %s: already held by %s (grab-race guard)" % [item.name, by.get_path() if by else "?"])
log_line("skipped snapping %s: already held by %s (grab-race guard)" % [item.name, str(by.get_path()) if by else "?"])
return
for zone in _station_snap_zones():
if is_instance_valid(zone.picked_up_object):
@@ -522,7 +522,8 @@ func _on_player_absent(peer_id: int) -> void:
# --- Command-line driven test bootstrap -----------------------------------
func _handle_cmdline() -> void:
var args := OS.get_cmdline_user_args()
SweetLogger.info("Networkmanager _handle_cmdline()", [], "network_manager.gd", "_handle_cmdline")
var args := OS.get_cmdline_args()
if args.has("--server"):
log_line("cmdline: --server")
host()
@@ -553,7 +554,7 @@ func log_line(s: String) -> void:
if p != null and p.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED:
id = multiplayer.get_unique_id()
var line := "[NET %d] %s" % [id, s]
print(line)
SweetLogger.info(line, [], "network_manager.gd", "log_line")
if _log_file:
_log_file.store_line(line)
_log_file.flush()
+2 -2
View File
@@ -151,5 +151,5 @@ static func get_items_old() -> Array[Dictionary]:
return items
static func _item(scene: String, name: String, pos: Vector3) -> Dictionary:
return {"scene": scene, "name": name, "xform": Transform3D(Basis(), pos), "props": {}}
static func _item(scene: String, item_name: String, pos: Vector3) -> Dictionary:
return {"scene": scene, "name": item_name, "xform": Transform3D(Basis(), pos), "props": {}}
+8 -9
View File
@@ -1,6 +1,6 @@
[gd_scene load_steps=5 format=3]
[gd_scene format=3 uid="uid://c58ns7csdahjy"]
[ext_resource type="Script" path="res://Player/net_player.gd" id="1_np001"]
[ext_resource type="Script" uid="uid://bncuxh7j7ix5q" path="res://Player/net_player.gd" id="1_np001"]
[ext_resource type="PackedScene" uid="uid://bq86r4yll8po" path="res://addons/godot-xr-tools/hands/scenes/lowpoly/left_fullglove_low.tscn" id="2_np002"]
[ext_resource type="PackedScene" uid="uid://xqimcf20s2jp" path="res://addons/godot-xr-tools/hands/scenes/lowpoly/right_fullglove_low.tscn" id="3_np003"]
@@ -28,17 +28,16 @@ properties/5/path = NodePath("RightHand:quaternion")
properties/5/spawn = false
properties/5/replication_mode = 1
[node name="NetPlayer" type="Node3D"]
[node name="NetPlayer" type="Node3D" unique_id=70287099]
script = ExtResource("1_np001")
[node name="Head" type="MeshInstance3D" parent="."]
[node name="Head" type="MeshInstance3D" parent="." unique_id=56588259]
mesh = SubResource("CapsuleMesh_np004")
[node name="LeftHand" parent="." instance=ExtResource("2_np002")]
[node name="LeftHand" parent="." unique_id=1703673184 instance=ExtResource("2_np002")]
[node name="RightHand" parent="." instance=ExtResource("3_np003")]
[node name="RightHand" parent="." unique_id=325341465 instance=ExtResource("3_np003")]
[node name="Sync" type="MultiplayerSynchronizer" parent="."]
root_path = NodePath("..")
replication_config = SubResource("SceneReplicationConfig_np005")
[node name="Sync" type="MultiplayerSynchronizer" parent="." unique_id=529459963]
replication_interval = 0.033
replication_config = SubResource("SceneReplicationConfig_np005")
+2 -2
View File
@@ -7,7 +7,7 @@ func _ready() -> void:
func _on_game_state_changed(new_state: GameManager.GameState) -> void:
print("BuildModeController: game_state_changed to %s" % GameManager.GameState.keys()[new_state])
SweetLogger.info("game_state_changed to {0}", [GameManager.GameState.keys()[new_state]], "build_mode_controller.gd", "_on_game_state_changed")
if new_state == GameManager.GameState.BUILDING:
despawn_unheld_pickables()
@@ -25,7 +25,7 @@ func despawn_unheld_pickables():
NetworkManager.despawn_item(pickable)
continue
if not held_by.is_in_group("persistent_inventory"):
print("BuildModeController despawn_unheld_pickables held_by: %s, pickable: %s " % [held_by, pickable])
SweetLogger.debug("despawn_unheld_pickables held_by: {0}, pickable: {1}", [held_by, pickable], "build_mode_controller.gd", "despawn_unheld_pickables")
NetworkManager.despawn_item(pickable)
+7 -7
View File
@@ -10,7 +10,7 @@ var _food_item: FoodItem
func _ready() -> void:
print("CombinableItem _ready(): ", _pickable.name)
SweetLogger.debug("_ready(): {0}", [_pickable.name], "combinable_item.gd", "_ready")
_food_item = get_parent().get_node_or_null("FoodItem") as FoodItem
if not _food_item:
push_error("CombinableItem is missing FoodItem reference. must be a sibling of a FoodItem on ", get_parent().name, ".")
@@ -40,8 +40,8 @@ func _ready() -> void:
# Enable the trigger only while snapped into a snap zone (not hand-held).
func _on_item_picked_up(_item: Node3D) -> void:
var by := _pickable.get_picked_up_by()
var snapped: bool = by != null and by.has_method("is_xr_class") and by.is_xr_class("XRToolsSnapZone")
set_deferred("monitoring", snapped)
var is_snapped: bool = by != null and by.has_method("is_xr_class") and by.is_xr_class("XRToolsSnapZone")
set_deferred("monitoring", is_snapped)
func _on_item_dropped(_item: Node3D) -> void:
set_deferred("monitoring", false)
@@ -52,24 +52,24 @@ func _on_body_entered(body: Node3D) -> void:
if not NetworkManager.owns_world():
return
if _combining or body == _pickable:
print("CombinableItem _on_body_entered: other is our own pickable")
SweetLogger.debug("other is our own pickable", [], "combinable_item.gd", "_on_body_entered")
return
var other := Helper.find_food_item(body)
if not other:
print("CombinableItem _on_body_entered: other is not a foodItem")
SweetLogger.debug("other is not a foodItem", [], "combinable_item.gd", "_on_body_entered")
return
var result: PackedScene = RecipeManager.get_combination_result(_food_item.id, other.id)
if not result:
print("CombinableItem _on_body_entered: There is no recipe for %s + %s" % [_food_item.id, other.id])
SweetLogger.debug("There is no recipe for {0} + {1}", [_food_item.id, other.id], "combinable_item.gd", "_on_body_entered")
return
_combine(body, result)
# Instantiate the result of combination and free the two ingredient items
func _combine(other_body: Node3D, result: PackedScene) -> void:
print("CombinableItem _combine: combining %s + %s into %s" % [_food_item.id, Helper.find_food_item(other_body).id, result.resource_path])
SweetLogger.debug("combining {0} + {1} into {2}", [_food_item.id, Helper.find_food_item(other_body).id, result.resource_path], "combinable_item.gd", "_combine")
# Get Snapzone
var snap_zone := _pickable.get_picked_up_by()
+1 -1
View File
@@ -41,7 +41,7 @@ func _process(delta: float) -> void:
# Count down to zero and despawn
_time_left -= delta
if _time_left <= 0:
print("DespawningItem despawning!" , get_parent())
SweetLogger.debug("despawning {0}", [get_parent()], "despawning_item.gd", "_process")
NetworkManager.despawn_item(get_parent())
# Stop counting: despawn_item() only queues the free, so without this we
# keep re-reporting the same item every frame until it actually goes.
+2 -2
View File
@@ -9,10 +9,10 @@ func _ready() -> void:
if not kitchen_scene:
push_error("StationSpawner missing kitchen_scene")
print("Kitchen init")
SweetLogger.debug("initializing", [], "kitchen_instantiator.gd", "_ready")
if NetworkManager.owns_world():
print("Kitchen init is_server")
SweetLogger.debug("initializing on server", [], "kitchen_instantiator.gd", "_ready")
# Later we will do procedural generation here.
# For now we just load a scene.
NetworkManager.call_deferred("spawn_item", kitchen_scene.resource_path, transform)
+7 -7
View File
@@ -9,7 +9,7 @@ var customers_served: int = 0
func _ready() -> void:
GameManager.game_state = GameManager.GameState.BUILDING
GameManager.set_game_state(GameManager.GameState.BUILDING)
Signals.game_state_changed.connect(_on_game_state_changed)
@@ -30,14 +30,14 @@ func _reset_values() -> void:
func start_next_day() -> void:
print("DayController: start_next_day")
SweetLogger.info("starting next day", [], "day_controller.gd", "start_next_day")
_reset_values()
GameManager.customers_per_day += GameManager.customers_count_increase_per_day
GameManager.day_number += 1
GameManager.set_customers_per_day(GameManager.customers_per_day + GameManager.customers_count_increase_per_day)
GameManager.set_day_number(GameManager.day_number + 1)
func finish_current_day() -> void:
print("DayController: finish_current_day")
SweetLogger.info("finishing current day", [], "day_controller.gd", "finish_current_day")
_reset_values()
@@ -50,9 +50,9 @@ func _process(delta: float) -> void:
#print("DayController: customers_at_this_time: ", customers_at_this_time)
if customers_spawned < customers_at_this_time:
customers_spawned += 1
print("DayController: spawning customer")
SweetLogger.debug("spawning customer", [], "day_controller.gd", "_process")
Signals.request_customer_spawn.emit()
# If day complete
if GameManager.customers_per_day == customers_served and customers_spawned == GameManager.customers_per_day:
print("DayController: Day complete")
SweetLogger.info("day complete", [], "day_controller.gd", "_process")
finish_current_day()
+1 -1
View File
@@ -32,7 +32,7 @@ func spawn_customer() -> void:
func _rebuild_table_list() -> void:
_tables.clear()
_tables.append_array(get_tree().get_nodes_in_group("table"))
print("QueueController _rebuild_table_list complete: ", _tables)
SweetLogger.debug("_rebuild_table_list complete: {0}", [_tables], "queue_controller.gd", "_rebuild_table_list")
func _try_to_assign_customers() -> void:
+10 -10
View File
@@ -114,14 +114,14 @@ func get_next(size, walls):
var offset = [-1,-1]
walls.shuffle()
print(size)
SweetLogger.debug("get_next size: {0}", [size], "tile_map_layer.gd", "get_next")
for wall in walls:
print(wall)
SweetLogger.debug("get_next wall: {0}", [wall], "tile_map_layer.gd", "get_next")
if wall["der"][1] == 0:
print("horisontal")
SweetLogger.debug("get_next horizontal wall", [], "tile_map_layer.gd", "get_next")
if size[0]<wall["len"]-6:
print("length 6 lees can go anywere")
SweetLogger.debug("TileMapLayer get_next length 6 less can go anywhere", [], "tile_map_layer.gd", "get_next")
if wall["der"][0] == 1:offset[1] = wall["pos"][1]
else: offset[1] = wall["pos"][1]-size[1]
@@ -130,16 +130,16 @@ func get_next(size, walls):
if size[0]+offset[0]>wall["pos"][0]+wall["len"]-3:offset[0] = wall["len"]-(3+size[0])
elif size[0]<=wall["len"]-3:
print("3 less needs to be on a corner")
SweetLogger.debug("TileMapLayer get_next 3 less needs to be on a corner", [], "tile_map_layer.gd", "get_next")
if wall["der"][0] == 1:offset[1] = wall["pos"][1]
else: offset[1] = wall["pos"][1]-size[1]
if randi_range(0, 1):offset[0]=wall["pos"][0]+wall["len"]-size[0]
else:offset[0] = wall["pos"][0]
else:
print("virtical")
SweetLogger.debug("get_next vertical wall", [], "tile_map_layer.gd", "get_next")
if size[1]<wall["len"]-6:
print("length 6 lees can go anywere")
SweetLogger.debug("TileMapLayer get_next length 6 less can go anywhere", [], "tile_map_layer.gd", "get_next")
if wall["der"][1] == 1:offset[0] = wall["pos"][0]
else: offset[0] = wall["pos"][0]-size[0]
@@ -148,10 +148,10 @@ func get_next(size, walls):
if size[1]+offset[1]>wall["len"]-3:offset[1] = wall["len"]-(3+size[1])
elif size[1]<=wall["len"]-3:
print("3 less needs to be on a corner")
SweetLogger.debug("TileMapLayer get_next 3 less needs to be on a corner", [], "tile_map_layer.gd", "get_next")
if wall["der"][1] == 1:offset[0] = wall["pos"][0]
else: offset[0] = wall["pos"][0]-size[0]
print(offset)
SweetLogger.debug("get_next offset: {0}", [offset], "tile_map_layer.gd", "get_next")
if randi() % 2:offset[1]=wall["pos"][1]+wall["len"]-size[1]
else:offset[1] = wall["pos"][1]
@@ -165,7 +165,7 @@ func get_next(size, walls):
func draw_room(Size, offset=Vector2i(0, 0)):
var walls = []
offset = Vector2i(offset[0], offset[1])
print("draw: ", Size, " offset: ", offset)
SweetLogger.debug("draw_room size: {0} offset: {1}", [Size, offset], "tile_map_layer.gd", "draw_room")
for i in range(Size[0]):
tile_map.set_cell(Vector2i(i+offset[0],offset[1]), 0, Vector2i(1,1))
+15
View File
@@ -6,6 +6,17 @@
[ext_resource type="PackedScene" uid="uid://c0lknik4noobs" path="res://items/BurgerBuns.tscn" id="2_1q74o"]
[ext_resource type="PackedScene" uid="uid://mpapt5mkbuao" path="res://prefabs/slide_off_dome.tscn" id="4_rf1b2"]
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_u2p81"]
properties/0/path = NodePath(".:position")
properties/0/spawn = false
properties/0/replication_mode = 1
properties/1/path = NodePath(".:rotation")
properties/1/spawn = false
properties/1/replication_mode = 1
properties/2/path = NodePath(".:visible")
properties/2/spawn = false
properties/2/replication_mode = 1
[sub_resource type="SphereShape3D" id="SphereShape3D_xmbo2"]
radius = 0.3
@@ -21,6 +32,10 @@ albedo_color = Color(0.6784191, 0.54546416, 0.016760282, 1)
[node name="StationMovement" parent="." unique_id=946873975 node_paths=PackedStringArray("station") instance=ExtResource("1_u2p81")]
station = NodePath("../BurgerBunsDispenser")
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." unique_id=904562637]
root_path = NodePath("../BurgerBunsDispenser")
replication_config = SubResource("SceneReplicationConfig_u2p81")
[node name="BurgerBunsDispenser" type="StaticBody3D" parent="." unique_id=1720683779]
script = ExtResource("1_myl3s")
item_scene = ExtResource("2_1q74o")
+18
View File
@@ -9,6 +9,24 @@
[ext_resource type="PackedScene" uid="uid://bxbocxdaayvwx" path="res://UI/progress_bar.tscn" id="6_racgh"]
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_3jbl6"]
properties/0/path = NodePath("Counter:position")
properties/0/spawn = false
properties/0/replication_mode = 1
properties/1/path = NodePath("Counter:rotation")
properties/1/spawn = false
properties/1/replication_mode = 1
properties/2/path = NodePath("Counter:process_result")
properties/2/spawn = false
properties/2/replication_mode = 1
properties/3/path = NodePath("Counter:process_result_work")
properties/3/spawn = false
properties/3/replication_mode = 1
properties/4/path = NodePath("Counter:work_progress")
properties/4/spawn = false
properties/4/replication_mode = 1
properties/5/path = NodePath("Counter:visible")
properties/5/spawn = false
properties/5/replication_mode = 1
[sub_resource type="BoxShape3D" id="BoxShape3D_24d3s"]
size = Vector3(0.48521727, 0.040039063, 0.41994628)
+14 -12
View File
@@ -8,14 +8,6 @@
[ext_resource type="Material" uid="uid://d1djvskv4n4m3" path="res://textures/hob_off.tres" id="4_8qpxk"]
[ext_resource type="Material" uid="uid://db7c7w4apwti7" path="res://textures/hob_on.tres" id="6_m7l4u"]
[sub_resource type="BoxShape3D" id="BoxShape3D_kdxnr"]
size = Vector3(0.6, 1, 0.6)
[sub_resource type="BoxShape3D" id="BoxShape3D_7uuqv"]
size = Vector3(0.6, 0.12802735, 0.6)
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_gkb3v"]
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_np_hob"]
properties/0/path = NodePath("Hob:time_cooked")
properties/0/spawn = false
@@ -32,6 +24,17 @@ properties/3/replication_mode = 1
properties/4/path = NodePath("Hob:rotation")
properties/4/spawn = false
properties/4/replication_mode = 1
properties/5/path = NodePath("Hob:visible")
properties/5/spawn = false
properties/5/replication_mode = 1
[sub_resource type="BoxShape3D" id="BoxShape3D_kdxnr"]
size = Vector3(0.6, 1, 0.6)
[sub_resource type="BoxShape3D" id="BoxShape3D_7uuqv"]
size = Vector3(0.6, 0.12802735, 0.6)
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_gkb3v"]
[sub_resource type="Animation" id="Animation_7uuqv"]
length = 0.001
@@ -171,6 +174,9 @@ _data = {
[node name="StationMovement" parent="." unique_id=946873975 node_paths=PackedStringArray("station") instance=ExtResource("1_pydjp")]
station = NodePath("../Hob")
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." unique_id=768992259]
replication_config = SubResource("SceneReplicationConfig_np_hob")
[node name="Hob" type="StaticBody3D" parent="." unique_id=1332123047]
script = ExtResource("1_7jc4g")
@@ -279,10 +285,6 @@ size = Vector3(0.55, 0.835, 0.072)
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.003479004, -0.45215607, 0.24655426)
size = Vector3(0.55, 0.096, 0.14)
[node name="Sync" type="MultiplayerSynchronizer" parent="Hob" unique_id=768992259]
root_path = NodePath("../..")
replication_config = SubResource("SceneReplicationConfig_np_hob")
[node name="AnimationPlayer" type="AnimationPlayer" parent="Hob" unique_id=1525460719]
root_node = NodePath("../..")
libraries/ = SubResource("AnimationLibrary_ac5f3")
+2 -5
View File
@@ -36,9 +36,6 @@ properties/0/replication_mode = 1
properties/1/path = NodePath(".:quaternion")
properties/1/spawn = false
properties/1/replication_mode = 1
properties/2/path = NodePath("NetPickable:net_held_by")
properties/2/spawn = true
properties/2/replication_mode = 1
[sub_resource type="BoxShape3D" id="BoxShape3D_vlqg6"]
size = Vector3(0.5, 0.87231445, 0.5)
@@ -51,10 +48,10 @@ albedo_color = Color(1, 0.78999996, 0.39999998, 0.4627451)
properties/0/path = NodePath(".:visible")
properties/0/spawn = false
properties/0/replication_mode = 1
properties/1/path = NodePath("../MoveHandle:position")
properties/1/path = NodePath(".:position")
properties/1/spawn = false
properties/1/replication_mode = 1
properties/2/path = NodePath("../MoveHandle:rotation")
properties/2/path = NodePath(".:rotation")
properties/2/spawn = false
properties/2/replication_mode = 1
+16 -16
View File
@@ -11,9 +11,9 @@ extends StaticBody3D
# Chopping state. Follow the hob/sink pattern so the display refreshes when
# values change; only the world owner runs the station's process in practice.
var process_result: String = "": set = _set_result
var process_result_work: float = 0.0: set = _set_result_work
var work_progress: float = 0.0: set = _set_work_progress
@export var process_result: String = "": set = _set_result
@export var process_result_work: float = 0.0: set = _set_result_work
@export var work_progress: float = 0.0: set = _set_work_progress
func _ready() -> void:
@@ -47,7 +47,7 @@ func _set_result(value: String) -> void:
if not process_result.is_empty():
knife.visible = true
knife.enabled = true
print("Counter: chopping recipe set: ", process_result)
SweetLogger.debug("chopping recipe set: {0}", [process_result], "counter.gd", "_set_result")
else:
_hide_all_tools()
@@ -71,7 +71,7 @@ func _refresh_progress_bar() -> void:
# Add work from gesture area (knife hits). This increments the chopping progress.
func add_work(work: float) -> void:
print("Counter add_work: ", work)
SweetLogger.debug("add_work: {0}", [work], "counter.gd", "add_work")
# Only the world owner should drive the authoritative state. Guarding is
# handled by higher-level NetworkManager logic elsewhere, mirror hob's
# convert_held_to_item which checks ownership before spawning.
@@ -82,16 +82,16 @@ func add_work(work: float) -> void:
audio.play()
# If we've reached the required time, convert the held item
if process_result != "" and process_result_work > 0 and work_progress >= process_result_work:
print("Counter: chopping complete, converting item to: ", process_result)
SweetLogger.debug("chopping complete, converting item to: {0}", [process_result], "counter.gd", "add_work")
work_progress = 0
convert_held_to_item(process_result)
func _on_gesture_area_body_entered(body: Node3D) -> void:
print("Counter: gesture area entered: ", body)
SweetLogger.debug("gesture area entered: {0}", [body], "counter.gd", "_on_gesture_area_body_entered")
if body.is_in_group("chopping_tool"):
if process_result == "":
print("Counter: knife entered but nothing to chop")
SweetLogger.debug("knife entered but nothing to chop", [], "counter.gd", "_on_gesture_area_body_entered")
return
var speed := 0.0
@@ -101,31 +101,31 @@ func _on_gesture_area_body_entered(body: Node3D) -> void:
speed = body.velocity.length()
if speed < chop_min_speed:
print("Counter: knife too slow for chopping: ", speed)
SweetLogger.debug("knife too slow for chopping: {0}", [speed], "counter.gd", "_on_gesture_area_body_entered")
return
add_work(chop_work_steps)
func _on_object_picked_up(_item: Variant) -> void:
print("Counter: object picked up: ", _item)
SweetLogger.debug("object picked up: {0}", [_item], "counter.gd", "_on_object_picked_up")
var food_item = Helper.find_food_item(_item)
if not food_item:
print("Counter: held object is not a FoodItem")
SweetLogger.debug("held object is not a FoodItem", [], "counter.gd", "_on_object_picked_up")
return
var result = RecipeManager.get_chopping_result(food_item.id)
if not result:
print("Counter: held a FoodItem that is not choppable, id: %s process_result: %s" % [food_item.id, result])
SweetLogger.debug("held a FoodItem that is not choppable, id: {0} process_result: {1}", [food_item.id, result], "counter.gd", "_on_object_picked_up")
return
process_result = result
process_result_work = RecipeManager.get_chopping_work(food_item.id)
print("Counter: set process_result ", process_result, " work: ", process_result_work)
SweetLogger.debug("set process_result {0} work: {1}", [process_result, process_result_work], "counter.gd", "_on_object_picked_up")
func _on_object_dropped(_item: Variant) -> void:
print("Counter: object drop ")
SweetLogger.debug("object drop", [], "counter.gd", "_on_object_dropped")
work_progress = 0
process_result = ""
process_result_work = 0
@@ -135,7 +135,7 @@ func _on_object_dropped(_item: Variant) -> void:
func convert_held_to_item(_item: String) -> void:
if not NetworkManager.owns_world():
return
print("Counter converting ", _item)
SweetLogger.debug("converting {0}", [_item], "counter.gd", "convert_held_to_item")
var old_pickable = snap_zone.picked_up_object
if not old_pickable:
@@ -145,7 +145,7 @@ func convert_held_to_item(_item: String) -> void:
var original_transform = old_pickable.global_transform
var new_scene_instance = NetworkManager.spawn_item(RecipeManager.get_item_scene(_item).resource_path, original_transform)
print("Counter freeing old_pickable ", old_pickable)
SweetLogger.debug("freeing old_pickable {0}", [old_pickable], "counter.gd", "convert_held_to_item")
snap_zone.drop_object()
NetworkManager.despawn_item(old_pickable)
snap_zone.pick_up_object(new_scene_instance)
+14
View File
@@ -6,6 +6,17 @@
[ext_resource type="Script" uid="uid://cquqe4f1m1sw6" path="res://addons/godot-xr-tools/objects/snap_zone.gd" id="3_ahn1e"]
[ext_resource type="PackedScene" uid="uid://mpapt5mkbuao" path="res://prefabs/slide_off_dome.tscn" id="4_nn8tr"]
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_0wtc7"]
properties/0/path = NodePath("CubeSideDispenser:position")
properties/0/spawn = false
properties/0/replication_mode = 1
properties/1/path = NodePath("CubeSideDispenser:rotation")
properties/1/spawn = false
properties/1/replication_mode = 1
properties/2/path = NodePath("CubeSideDispenser:visible")
properties/2/spawn = false
properties/2/replication_mode = 1
[sub_resource type="SphereShape3D" id="SphereShape3D_xmbo2"]
radius = 0.3
@@ -25,6 +36,9 @@ size = Vector3(0.1, 0.1, 0.1)
[node name="StationMovement" parent="." unique_id=946873975 node_paths=PackedStringArray("station") instance=ExtResource("1_qooyy")]
station = NodePath("../CubeSideDispenser")
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." unique_id=821487160]
replication_config = SubResource("SceneReplicationConfig_0wtc7")
[node name="CubeSideDispenser" type="StaticBody3D" parent="." unique_id=200950572]
script = ExtResource("1_4m60m")
item_scene = ExtResource("2_nn8tr")
+1 -1
View File
@@ -11,7 +11,7 @@ func _makeDirty(item) -> void:
return
var plate: PlateController = item.get_node_or_null("PlateController") as PlateController
if not plate:
print("Dirt Station: held object is not a Plate")
SweetLogger.debug("held object is not a Plate", [], "dirt_station.gd", "_makeDirty")
return
if not plate.is_dirty:
plate.is_dirty = true
+29 -7
View File
@@ -1,8 +1,20 @@
[gd_scene format=3 uid="uid://cnjwtnhwh0i8q"]
[ext_resource type="Script" uid="uid://q7tpwmrbmu1m" path="res://stations/dirt_station.gd" id="1_hc1d4"]
[ext_resource type="PackedScene" uid="uid://cvucwxibtol5f" path="res://stations/StationMovement.tscn" id="1_ucg2q"]
[ext_resource type="Script" uid="uid://cquqe4f1m1sw6" path="res://addons/godot-xr-tools/objects/snap_zone.gd" id="2_v0ytd"]
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_ku4h3"]
properties/0/path = NodePath(".:position")
properties/0/spawn = false
properties/0/replication_mode = 1
properties/1/path = NodePath(".:rotation")
properties/1/spawn = false
properties/1/replication_mode = 1
properties/2/path = NodePath(".:visible")
properties/2/spawn = false
properties/2/replication_mode = 1
[sub_resource type="BoxShape3D" id="BoxShape3D_24d3s"]
size = Vector3(0.5, 1, 0.5)
@@ -16,28 +28,38 @@ size = Vector3(0.5, 1, 0.5)
[sub_resource type="BoxShape3D" id="BoxShape3D_mep2a"]
size = Vector3(0.81640625, 0.5635376, 0.79351807)
[node name="DirtStation" type="StaticBody3D" unique_id=160842153 groups=["station"]]
[node name="DirtStation" type="Node3D" unique_id=784427347]
[node name="StationMovement" parent="." unique_id=946873975 node_paths=PackedStringArray("station") instance=ExtResource("1_ucg2q")]
station = NodePath("../DirtStation")
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." unique_id=1485614825]
root_path = NodePath("../DirtStation")
replication_config = SubResource("SceneReplicationConfig_ku4h3")
[node name="DirtStation" type="StaticBody3D" parent="." unique_id=160842153 groups=["station"]]
script = ExtResource("1_hc1d4")
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1400366312]
[node name="CollisionShape3D" type="CollisionShape3D" parent="DirtStation" unique_id=1400366312]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
shape = SubResource("BoxShape3D_24d3s")
[node name="MeshInstance3D" type="MeshInstance3D" parent="CollisionShape3D" unique_id=55726639]
[node name="MeshInstance3D" type="MeshInstance3D" parent="DirtStation/CollisionShape3D" unique_id=55726639]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.008318901, 0, -0.022509098)
mesh = SubResource("BoxMesh_ay2w6")
[node name="Label3D" type="Label3D" parent="CollisionShape3D" unique_id=1251766743]
[node name="Label3D" type="Label3D" parent="DirtStation/CollisionShape3D" unique_id=1251766743]
transform = Transform3D(0.9999992, 0, 0, 0, 0.99999946, 0, 0, 0, 0.9999992, 0.002797456, 0.6309581, -0.2812457)
text = "Dirt"
[node name="XRToolsSnapZone" type="Area3D" parent="." unique_id=333161027 groups=["station"]]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5270121, 0)
[node name="XRToolsSnapZone" type="Area3D" parent="DirtStation" unique_id=333161027 groups=["station"]]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.0313971, 0)
collision_layer = 65536
collision_mask = 65540
script = ExtResource("2_v0ytd")
snap_mode = 1
metadata/_custom_type_script = "uid://cquqe4f1m1sw6"
[node name="CollisionShape3D2" type="CollisionShape3D" parent="XRToolsSnapZone" unique_id=1398712192]
[node name="CollisionShape3D2" type="CollisionShape3D" parent="DirtStation/XRToolsSnapZone" unique_id=1398712192]
transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0.008666992, -0.11009073, -0.009353638)
shape = SubResource("BoxShape3D_mep2a")
+7 -7
View File
@@ -67,26 +67,26 @@ func _refresh_display() -> void:
progress_bar.override_fill_color(Color.RED if cooking_result == "charcoal" else Color.GREEN)
func _on_object_picked_up(_item) -> void:
print("Hob: object picked up: ", _item)
SweetLogger.debug("object picked up: {0}", [_item], "hob.gd", "_on_object_picked_up")
# Find the CookableItem in held object
var _food_item = _item.get_node_or_null("FoodItem") as FoodItem
if not _food_item:
print("Hob: held object is not a FoodItem")
SweetLogger.debug("held object is not a FoodItem", [], "hob.gd", "_on_object_picked_up")
return
var result = RecipeManager.get_cooking_result(_food_item.id)
if not result:
print("Hob: held a FoodItem that is not cookable, id: %s result: %s" % [_food_item.id, result])
SweetLogger.debug("held a FoodItem that is not cookable, id: {0} result: {1}", [_food_item.id, result], "hob.gd", "_on_object_picked_up")
return
cooking_result = result
cooking_result_time = RecipeManager.get_cooking_time(_food_item.id)
print("Hob: set cooking_result ", cooking_result)
SweetLogger.debug("set cooking_result {0}", [cooking_result], "hob.gd", "_on_object_picked_up")
# The setters above already refresh the display and start the flames.
func _on_object_dropped(_item) -> void:
print("Hob: object drop ")
SweetLogger.debug("object drop", [], "hob.gd", "_on_object_dropped")
time_cooked = 0
cooking_result = ""
cooking_result_time = 0
@@ -95,7 +95,7 @@ func _on_object_dropped(_item) -> void:
func convert_held_to_item(_item: String) -> void:
if not NetworkManager.owns_world():
return
print("Hob converting ", _item)
SweetLogger.debug("converting {0}", [_item], "hob.gd", "convert_held_to_item")
var old_pickable = snap_zone.picked_up_object
if not old_pickable:
@@ -108,7 +108,7 @@ func convert_held_to_item(_item: String) -> void:
var new_scene_instance = NetworkManager.spawn_item(RecipeManager.get_item_scene(_item).resource_path, original_transform)
# Drop and free the old item and pick up the new one
print("Hob freeing old_pickable ", old_pickable)
SweetLogger.debug("freeing old_pickable {0}", [old_pickable], "hob.gd", "convert_held_to_item")
snap_zone.drop_object()
NetworkManager.despawn_item(old_pickable)
snap_zone.pick_up_object(new_scene_instance)
+2 -2
View File
@@ -10,7 +10,7 @@ func _ready() -> void:
push_error("Item dispenser is missing a reference to its snap zone child")
func _process(delta: float) -> void:
func _process(_delta: float) -> void:
if not NetworkManager.owns_world():
return
#if snap_zone.picked_up_object: # Player picked up this station
@@ -21,6 +21,6 @@ func _process(delta: float) -> void:
#return
#
if not snap_zone.picked_up_object:
print("Item dispenser is missing item, spawning new item")
SweetLogger.debug("missing item, spawning new item", [], "item_dispenser.gd", "_process")
var new_item = NetworkManager.spawn_item(item_scene.resource_path, snap_zone.global_transform)
snap_zone.pick_up_object(new_item)
+14
View File
@@ -6,6 +6,17 @@
[ext_resource type="Script" uid="uid://cquqe4f1m1sw6" path="res://addons/godot-xr-tools/objects/snap_zone.gd" id="3_lr065"]
[ext_resource type="PackedScene" uid="uid://mpapt5mkbuao" path="res://prefabs/slide_off_dome.tscn" id="4_tfo2i"]
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_jgjsq"]
properties/0/path = NodePath("PlateDispenser:position")
properties/0/spawn = false
properties/0/replication_mode = 1
properties/1/path = NodePath("PlateDispenser:rotation")
properties/1/spawn = false
properties/1/replication_mode = 1
properties/2/path = NodePath("PlateDispenser:visible")
properties/2/spawn = false
properties/2/replication_mode = 1
[sub_resource type="SphereShape3D" id="SphereShape3D_xmbo2"]
radius = 0.3
@@ -18,6 +29,9 @@ radius = 0.3
[node name="StationMovement" parent="." unique_id=946873975 node_paths=PackedStringArray("station") instance=ExtResource("1_jgjsq")]
station = NodePath("../PlateDispenser")
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." unique_id=901684173]
replication_config = SubResource("SceneReplicationConfig_jgjsq")
[node name="PlateDispenser" type="StaticBody3D" parent="." unique_id=710538846]
script = ExtResource("1_jm0ik")
item_scene = ExtResource("2_tfo2i")
+14
View File
@@ -6,6 +6,17 @@
[ext_resource type="Script" uid="uid://cquqe4f1m1sw6" path="res://addons/godot-xr-tools/objects/snap_zone.gd" id="4_fx1kw"]
[ext_resource type="PackedScene" uid="uid://mpapt5mkbuao" path="res://prefabs/slide_off_dome.tscn" id="5_ur331"]
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_8orw3"]
properties/0/path = NodePath("PotatoDispenser:position")
properties/0/spawn = false
properties/0/replication_mode = 1
properties/1/path = NodePath("PotatoDispenser:rotation")
properties/1/spawn = false
properties/1/replication_mode = 1
properties/2/path = NodePath("PotatoDispenser:visible")
properties/2/spawn = false
properties/2/replication_mode = 1
[sub_resource type="SphereShape3D" id="SphereShape3D_xmbo2"]
radius = 0.3
@@ -21,6 +32,9 @@ albedo_color = Color(0.48, 0.34336, 0.1872, 1)
[node name="StationMovement" parent="." unique_id=946873975 node_paths=PackedStringArray("station") instance=ExtResource("1_8orw3")]
station = NodePath("../PotatoDispenser")
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." unique_id=41449367]
replication_config = SubResource("SceneReplicationConfig_8orw3")
[node name="PotatoDispenser" type="StaticBody3D" parent="." unique_id=1720683779]
script = ExtResource("2_30d2d")
item_scene = ExtResource("3_mjqsn")
+14
View File
@@ -7,6 +7,17 @@
[ext_resource type="PackedScene" uid="uid://mpapt5mkbuao" path="res://prefabs/slide_off_dome.tscn" id="5_mep2a"]
[ext_resource type="PackedScene" uid="uid://cvucwxibtol5f" path="res://stations/StationMovement.tscn" id="6_8ou8n"]
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_8ou8n"]
properties/0/path = NodePath("RawBurgerDispenser:position")
properties/0/spawn = false
properties/0/replication_mode = 1
properties/1/path = NodePath("RawBurgerDispenser:rotation")
properties/1/spawn = false
properties/1/replication_mode = 1
properties/2/path = NodePath("RawBurgerDispenser:visible")
properties/2/spawn = false
properties/2/replication_mode = 1
[sub_resource type="SphereShape3D" id="SphereShape3D_xmbo2"]
radius = 0.3
@@ -22,6 +33,9 @@ albedo_texture = ExtResource("4_1f5le")
[node name="StationMovement" parent="." unique_id=946873975 node_paths=PackedStringArray("station") instance=ExtResource("6_8ou8n")]
station = NodePath("../RawBurgerDispenser")
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." unique_id=1886082284]
replication_config = SubResource("SceneReplicationConfig_8ou8n")
[node name="RawBurgerDispenser" type="StaticBody3D" parent="." unique_id=235630131]
script = ExtResource("1_gkb3v")
item_scene = ExtResource("2_mep2a")
+7 -7
View File
@@ -10,9 +10,9 @@ const plate_wash_time: float = 3.0
## display when they change. Only the world owner runs this station's _process
## and snap zone, so without this a client never updates its bar — it stayed
## frozen on screen long after the plate came out clean.
var time_washed: float = 0.0: set = _set_time_washed
var is_washing: bool = false: set = _set_is_washing
var plate: PlateController = null
@export var time_washed: float = 0.0: set = _set_time_washed
@export var is_washing: bool = false: set = _set_is_washing
@export var plate: PlateController = null
func _set_time_washed(value: float) -> void:
@@ -59,7 +59,7 @@ func _refresh_progress_bar() -> void:
func _on_object_picked_up(_item) -> void:
print("Sink: object picked up: ", _item)
SweetLogger.debug("object picked up: {0}", [_item], "sink.gd", "_on_object_picked_up")
plate = _item.get_node_or_null("PlateController") as PlateController
if plate:
# The setter starts the effects and shows the bar, here and on clients.
@@ -67,7 +67,7 @@ func _on_object_picked_up(_item) -> void:
func _on_object_dropped(_item) -> void:
print("Sink: object drop ")
SweetLogger.debug("object drop", [], "sink.gd", "_on_object_dropped")
_reset_sink()
@@ -81,14 +81,14 @@ func complete_washing():
return
plate.is_dirty = false
_reset_sink()
print("Sink washing complete!")
SweetLogger.debug("washing complete!", [], "sink.gd", "complete_washing")
func _process(delta: float) -> void:
if is_washing:
time_washed += wash_speed * delta
_refresh_progress_bar()
print("Sink washing plate: ", time_washed)
SweetLogger.debug("washing plate: {0}", [time_washed], "sink.gd", "_process")
if time_washed >= plate_wash_time:
complete_washing()
+21 -11
View File
@@ -5,6 +5,23 @@
[ext_resource type="Script" uid="uid://cquqe4f1m1sw6" path="res://addons/godot-xr-tools/objects/snap_zone.gd" id="2_3ocod"]
[ext_resource type="PackedScene" uid="uid://bxbocxdaayvwx" path="res://UI/progress_bar.tscn" id="4_1pinr"]
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_np_sink"]
properties/0/path = NodePath(".:position")
properties/0/spawn = false
properties/0/replication_mode = 1
properties/1/path = NodePath(".:rotation")
properties/1/spawn = false
properties/1/replication_mode = 1
properties/2/path = NodePath(".:time_washed")
properties/2/spawn = false
properties/2/replication_mode = 1
properties/3/path = NodePath(".:is_washing")
properties/3/spawn = false
properties/3/replication_mode = 1
properties/4/path = NodePath(".:visible")
properties/4/spawn = false
properties/4/replication_mode = 1
[sub_resource type="BoxShape3D" id="BoxShape3D_l02yb"]
size = Vector3(0.6, 1.0024658, 0.6)
@@ -16,14 +33,6 @@ stereo = true
[sub_resource type="BoxShape3D" id="BoxShape3D_ai6d4"]
size = Vector3(0.5, 0.128, 0.5)
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_np_sink"]
properties/0/path = NodePath(".:time_washed")
properties/0/spawn = true
properties/0/replication_mode = 1
properties/1/path = NodePath(".:is_washing")
properties/1/spawn = true
properties/1/replication_mode = 1
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_0gbf8"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_bvwq1"]
@@ -187,6 +196,10 @@ height = 0.04
[node name="StationMovement" parent="." unique_id=946873975 node_paths=PackedStringArray("station") instance=ExtResource("1_uluvy")]
station = NodePath("../Sink")
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." unique_id=1601242574]
root_path = NodePath("../Sink")
replication_config = SubResource("SceneReplicationConfig_np_sink")
[node name="Sink" type="StaticBody3D" parent="." unique_id=2055277359 groups=["station"]]
script = ExtResource("1_7hh4b")
@@ -241,9 +254,6 @@ transform = Transform3D(1, 0, 0, 0, 0.9367828, -0.34991136, 0, 0.34991136, 0.936
operation = 2
size = Vector3(1, 1.2053223, 0.352417)
[node name="Sync" type="MultiplayerSynchronizer" parent="Sink" unique_id=1601242574]
replication_config = SubResource("SceneReplicationConfig_np_sink")
[node name="ProgressBar3D" parent="Sink" unique_id=654673176 instance=ExtResource("4_1pinr")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.3229032, -0.035980098)
+101 -23
View File
@@ -7,17 +7,21 @@ extends Node
var move_handle_rigid: RigidBody3D
var original_collision_layer: int = 0
var original_y_pos: float = 0.0
var original_handle_y_pos: float = 0.0
var original_station_transform: Transform3D = Transform3D.IDENTITY
var moving: bool = false
@export var moving: bool = false
var ghost_materials: Array[StandardMaterial3D] = []
# Ghost update throttling (clients send unreliable RPCs to server at this rate)
var ghost_send_interval: float = 0.1 # seconds (10 Hz)
var _ghost_send_accum: float = 0.0
const GHOST_COLOR_VALID: Color = Color(0, 1, 0, 0.35)
const GHOST_COLOR_INVALID: Color = Color(1, 0, 0, 0.35)
func set_move_handle_enabled(enabled: bool) -> void:
print("StationMovement set_move_handle_enabled: ", enabled)
SweetLogger.debug("set_move_handle_enabled: {0}", [enabled], "station_movement.gd", "set_move_handle_enabled")
move_handle.visible = enabled
move_handle.enabled = enabled
if enabled:
@@ -28,7 +32,7 @@ func _on_game_state_changed(new_state: GameManager.GameState) -> void:
set_move_handle_enabled(new_state == GameManager.GameState.BUILDING)
func _on_station_bought(_instance: Node3D):
print("StationMovement _on_station_bought")
SweetLogger.info("_on_station_bought")
if GameManager.game_state == GameManager.GameState.BUILDING:
set_move_handle_enabled(true)
@@ -42,7 +46,7 @@ func _ready() -> void:
move_handle_rigid = move_handle as RigidBody3D
original_collision_layer = station.collision_layer
original_y_pos = move_handle.transform.origin.y
original_handle_y_pos = move_handle.transform.origin.y
original_station_transform = station.global_transform
move_handle.picked_up.connect(_handle_pickup)
move_handle.dropped.connect(_handle_drop)
@@ -89,51 +93,75 @@ func _set_ghost_color(color: Color) -> void:
func _handle_pickup(_by: Node) -> void:
print("StationMovement handle pickup")
SweetLogger.info("handle pickup")
if move_handle.get_picked_up_by() is XRToolsSnapZone:
print("StationMovement handle pickup by snap zone, dropping and resetting position")
SweetLogger.info("handle pickup by snap zone, dropping and resetting position")
move_handle.drop()
move_handle.global_position = station.global_position + Vector3(0, original_y_pos, 0)
move_handle.global_position = station.global_position + Vector3(0, original_handle_y_pos, 0)
move_handle.rotation = station.rotation
return
moving = true
move_ghost.visible = true
station.visible = false
station.collision_layer = 0
original_station_transform = station.global_transform
move_ghost.global_transform = original_station_transform
if NetworkManager.is_online():
# Ask the server to update visibility (reliable)
server_update_visibility.rpc_id(1, true, false)
else:
# If not connected to server (single-player or test), apply locally
move_ghost.visible = true
station.visible = false
func _handle_drop(_by: Node) -> void:
print("StationMovement handle drop")
SweetLogger.info("handle drop")
moving = false
move_ghost.visible = false
station.visible = true
station.collision_layer = original_collision_layer
if _is_move_position_valid():
station.global_transform = move_ghost.global_transform
else:
station.global_transform = original_station_transform
move_handle.global_position = station.global_position + Vector3(0, original_y_pos, 0)
move_handle.rotation = station.rotation
var is_valid: bool = _is_move_position_valid()
if NetworkManager.is_online():
server_handle_drop.rpc_id(1, move_ghost.global_transform, is_valid)
else:
# If not connected to server (single-player or test), apply locally only when valid
if is_valid:
station.global_transform = move_ghost.global_transform
move_handle.global_transform = move_ghost.global_transform.translated(Vector3(0, original_handle_y_pos, 0))
SweetLogger.debug("handle drop (local apply), station authority: {0} move_handle authority: {1}", [station.get_multiplayer_authority(), move_handle.get_multiplayer_authority()], "station_movement.gd", "_handle_drop")
else:
station.global_transform = original_station_transform
move_handle.global_transform = original_station_transform.translated(Vector3(0, original_handle_y_pos, 0))
SweetLogger.debug("handle drop (local reject), restoring original position", [], "station_movement.gd", "_handle_drop")
func _process(_delta: float) -> void:
if not moving:
return
# Update the ghost locally for instant feedback and send throttled unreliable RPCs to the server
move_ghost.visible = true
station.visible = false
move_ghost.global_transform = Helper.get_snapped_transform(move_handle)
move_ghost.global_transform.origin.y = original_station_transform.origin.y
if _is_move_position_valid():
_set_ghost_color(GHOST_COLOR_VALID)
else:
_set_ghost_color(GHOST_COLOR_INVALID)
var ghost_color = GHOST_COLOR_VALID if _is_move_position_valid() else GHOST_COLOR_INVALID
_set_ghost_color(ghost_color)
# Throttle and send ghost transform updates to server so other clients see the preview
_ghost_send_accum += _delta
if _ghost_send_accum >= ghost_send_interval:
_ghost_send_accum = 0.0
# Unreliable RPC to the server to update the server-side ghost; server will replicate to clients
# Use rpc_unreliable_id to avoid blocking traffic
if NetworkManager.is_online():
server_update_move_ghost_transform.rpc_id(1, move_ghost.global_transform, ghost_color)
func _is_move_position_valid() -> bool:
for body in move_ghost.get_overlapping_bodies():
print("Overlapping body: ", body)
SweetLogger.debug("Overlapping body: {0}", [body], "station_movement.gd", "_is_move_position_valid")
if body == move_handle:
continue
if body == station:
@@ -146,9 +174,59 @@ func _is_move_position_valid() -> bool:
return false
for area in move_ghost.get_overlapping_areas():
print("Overlapping area: ", area)
#print("Overlapping area: ", area)
if area == move_ghost:
continue
return false
return true
@rpc("any_peer", "call_remote", "reliable")
func server_handle_drop(new_transform: Transform3D, client_thinks_valid: bool):
if not multiplayer.is_server():
return
if not (client_thinks_valid):
# Reset state for everyone, including server
var original_trans = original_station_transform # Make a copy to avoid server_update_station_transform setting it before move handle uses it
server_update_station_transform.rpc(original_trans)
server_update_move_ghost_transform.rpc(original_trans)
server_update_handle_transform.rpc(original_trans.translated(Vector3(0, original_handle_y_pos, 0)))
server_update_visibility.rpc(false, true)
SweetLogger.debug("server_handle_drop: transform rejected (invalid)", [], "station_movement.gd", "server_handle_drop")
return
server_update_station_transform.rpc(new_transform)
server_update_move_ghost_transform.rpc(new_transform)
server_update_handle_transform.rpc(new_transform.translated(Vector3(0, original_handle_y_pos, 0)))
server_update_visibility.rpc(false, true)
SweetLogger.debug("server_handle_drop: transform applied", [], "station_movement.gd", "server_handle_drop")
@rpc("any_peer", "call_local", "reliable") # Server updates clients
func server_update_station_transform(new_transform: Transform3D) -> void:
SweetLogger.debug("server_update_station_transform", [], "station_movement.gd", "server_update_station_transform")
station.global_transform = new_transform
original_station_transform = new_transform
@rpc("any_peer", "call_local", "reliable") # Server updates clients
func server_update_handle_transform(new_transform: Transform3D) -> void:
SweetLogger.debug("server_update_handle_transform", [], "station_movement.gd", "server_update_handle_transform")
move_handle.global_transform = new_transform
@rpc("any_peer", "call_local", "reliable")
func server_update_visibility(ghost_visible: bool, station_visible: bool) -> void:
SweetLogger.debug("server_update_visibility", [], "station_movement.gd", "server_update_visibility")
move_ghost.visible = ghost_visible
station.visible = station_visible
@rpc("any_peer", "call_local", "unreliable")
func server_update_move_ghost_transform(new_transform: Transform3D, new_color: Color = Color.YELLOW) -> void:
SweetLogger.debug("server_update_move_ghost_transform", [], "station_movement.gd", "server_update_move_ghost_transform")
move_ghost.global_transform = new_transform
_set_ghost_color(new_color)
+23 -23
View File
@@ -47,7 +47,7 @@ var _players_count: int = 0
func place_order() -> void:
print("Table place_order()")
SweetLogger.debug("place_order()", [], "table.gd", "place_order")
var new_orders: Array[String] = _unsatisfied_orders.duplicate()
# for _i in range(0, randi_range(1, 2)):
# new_orders.append(GameManager.get_random_meal())
@@ -59,7 +59,7 @@ func place_order() -> void:
func absorb_items():
print("Table: absorb_items()")
SweetLogger.debug("absorb_items()", [], "table.gd", "absorb_items")
for snap_zone_node in snap_zones:
var held_object = snap_zone_node.picked_up_object
if not held_object:
@@ -69,7 +69,7 @@ func absorb_items():
func satisfyAllOrders() -> void:
print("Table: satisfyAllOrders()")
SweetLogger.debug("satisfyAllOrders()", [], "table.gd", "satisfyAllOrders")
_unsatisfied_orders.clear()
_original_orders.clear()
clearAllFood()
@@ -77,7 +77,7 @@ func satisfyAllOrders() -> void:
func clearAllFood() -> void:
print("Table: clearAllFood()")
SweetLogger.debug("clearAllFood()", [], "table.gd", "clearAllFood")
for zone in snap_zones:
var held_object = zone.picked_up_object
if not held_object:
@@ -98,7 +98,7 @@ func clearAllFood() -> void:
# Group called from Queue when trying to assign customers to tables
func try_consume_customer() -> bool:
if _state == TableState.EMPTY:
print("Table try_consume_customer, consumed a customer")
SweetLogger.debug("try_consume_customer, consumed a customer", [], "table.gd", "try_consume_customer")
_state_end(TableState.EMPTY)
return true
return false
@@ -137,14 +137,14 @@ func _ready() -> void:
func _on_object_picked_up(_item) -> void:
print("Table: object picked up: ", _item)
SweetLogger.debug("object picked up: {0}", [_item], "table.gd", "_on_object_picked_up")
if _state == TableState.EATING:
return
_absorb_item_if_correct(_item)
func _on_object_dropped(_item) -> void:
print("Table: object dropped, item: ", _item)
SweetLogger.debug("object dropped, item: {0}", [_item], "table.gd", "_on_object_dropped")
# If then player picks up a food_item (side) the table has already registerd, unregister
var food_item: FoodItem = Helper.find_food_item(_item)
if food_item and food_item.type == FoodItem.Type.SIDE:
@@ -161,12 +161,12 @@ func _on_object_dropped(_item) -> void:
# There is no on_stay signal, we have to track player with bool
func _on_player_enter(_body):
_players_count += 1
print("Table _on_player_enter() pleryers_count: ", _players_count)
SweetLogger.debug("_on_player_enter() players_count: {0}", [_players_count], "table.gd", "_on_player_enter")
func _on_player_exit(_body):
_players_count -= 1
print("Table _on_player_exit() pleryers_count: ", _players_count)
SweetLogger.debug("_on_player_exit() players_count: {0}", [_players_count], "table.gd", "_on_player_exit")
func _place_order_if_player():
@@ -183,20 +183,20 @@ func _place_order_if_player():
func _get_plate_controller_from_item(_item: Node) -> PlateController:
if not _item:
print("_item is null")
SweetLogger.debug("_item is null", [], "table.gd", "_get_plate_controller_from_item")
return null
for child in _item.get_children():
if child is PlateController:
print("found child")
SweetLogger.debug("found child", [], "table.gd", "_get_plate_controller_from_item")
return child
print("no match")
SweetLogger.debug("no match", [], "table.gd", "_get_plate_controller_from_item")
return null
func _absorb_item_if_correct(_item: Node) -> void:
print("Table _absorb_item_if_correct, item: ", _item)
SweetLogger.debug("_absorb_item_if_correct, item: {0}", [_item], "table.gd", "_absorb_item_if_correct")
if not _item:
return
if not (_state == TableState.WAITING_PRIMARY or _state == TableState.WAITING_FRIEND):
@@ -204,14 +204,14 @@ func _absorb_item_if_correct(_item: Node) -> void:
# Plate: Get plate controller in child of _item (hopefully a plate XRpickable)
var plate_controller = _get_plate_controller_from_item(_item)
print("Table _absorb_item_if_correct plate_controller ref: ", plate_controller)
SweetLogger.debug("_absorb_item_if_correct plate_controller ref: {0}", [plate_controller], "table.gd", "_absorb_item_if_correct")
_item.print_tree_pretty()
if plate_controller:
# Absorm items from the plate we want
for food_item: FoodItem in plate_controller.container.contained_items: # TOOD: handle registering the meal again when a side is added to the plate
if food_item.id in _unsatisfied_orders and not food_item.is_absorbed:
print("Table: _absorb_item_if_correct held a plate with FoodItem that is in unsatisfied orders, removing it")
SweetLogger.debug("_absorb_item_if_correct held a plate with FoodItem that is in unsatisfied orders, removing it", [], "table.gd", "_absorb_item_if_correct")
(plate_controller.get_parent() as XRToolsPickable).get_picked_up_by().enabled = false # Lock meal that is deliverd
_unsatisfied_orders.erase(food_item.id)
food_item.is_absorbed = true
@@ -221,17 +221,17 @@ func _absorb_item_if_correct(_item: Node) -> void:
# Side pickable item, no container
var food_item: FoodItem = Helper.find_food_item(_item)
if food_item and food_item.type == FoodItem.Type.SIDE:
print("Table: _absorb_item_if_correct held a side that is in unsatisfied orders, removing it")
SweetLogger.debug("_absorb_item_if_correct held a side that is in unsatisfied orders, removing it", [], "table.gd", "_absorb_item_if_correct")
# Do not lock snap zone here. Problem if table want many meals, but to many sides have filled up slots, so can't place plates.
_unsatisfied_orders.erase(food_item.id)
_update_state_from_orders()
return
print("Table: absorb_item_if_correct() held object is not a Plate or Side")
SweetLogger.debug("absorb_item_if_correct() held object is not a Plate or Side", [], "table.gd", "_absorb_item_if_correct")
func _update_state_from_orders():
print("Table _update_state_from_orders: unsatisfied_orders: ", _unsatisfied_orders)
SweetLogger.debug("_update_state_from_orders: unsatisfied_orders: {0}", [_unsatisfied_orders], "table.gd", "_update_state_from_orders")
if _unsatisfied_orders.size() > 0 and _state != TableState.EATING:
_set_state(TableState.WAITING_FRIEND)
@@ -250,11 +250,11 @@ func _collect_money_from_food():
var food_item: FoodItem = Helper.find_food_item(held_object)
if plate:
for fi: FoodItem in plate.get_food_items():
GameManager.money += fi.sell_value
GameManager.set_money(GameManager.money + fi.sell_value)
elif food_item:
GameManager.money += food_item.sell_value
GameManager.set_money(GameManager.money + food_item.sell_value)
print("Table _collect_money_from_food(), money: ", GameManager.money)
SweetLogger.debug("_collect_money_from_food(), money: {0}", [GameManager.money], "table.gd", "_collect_money_from_food")
func _set_snap_zones_enabled(value: bool) -> void:
@@ -265,7 +265,7 @@ func _set_snap_zones_enabled(value: bool) -> void:
## Server-only state transition: sets the new state's timer and assigns
## _state (whose setter refreshes the display on every peer).
func _set_state(newState: TableState) -> void:
print("Table State START: ", TableState.keys()[newState])
SweetLogger.debug("State START: {0}", [TableState.keys()[newState]], "table.gd", "_set_state")
match newState:
TableState.IDLE:
@@ -307,7 +307,7 @@ func _set_state(newState: TableState) -> void:
# When state timer is finished, do this stuff before moving to next state
func _state_end(oldState: TableState):
print("Table State END : ", TableState.keys()[oldState])
SweetLogger.debug("State END: {0}", [TableState.keys()[oldState]], "table.gd", "_state_end")
match oldState:
TableState.EMPTY:
customers.visible = true
+24 -14
View File
@@ -7,6 +7,26 @@
[ext_resource type="AudioStream" uid="uid://daiv8ubwdbidf" path="res://sounds/220195__gameaudio__click-wooden-1.wav" id="3_0s6ir"]
[ext_resource type="PackedScene" uid="uid://bxbocxdaayvwx" path="res://UI/progress_bar.tscn" id="3_kjf1i"]
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_np_table"]
properties/0/path = NodePath(".:_state")
properties/0/spawn = false
properties/0/replication_mode = 1
properties/1/path = NodePath(".:_state_time")
properties/1/spawn = false
properties/1/replication_mode = 1
properties/2/path = NodePath(".:_unsatisfied_orders")
properties/2/spawn = false
properties/2/replication_mode = 1
properties/3/path = NodePath(".:position")
properties/3/spawn = false
properties/3/replication_mode = 1
properties/4/path = NodePath(".:rotation")
properties/4/spawn = false
properties/4/replication_mode = 1
properties/5/path = NodePath(".:visible")
properties/5/spawn = false
properties/5/replication_mode = 1
[sub_resource type="BoxShape3D" id="BoxShape3D_24d3s"]
size = Vector3(0.7983472, 0.060000002, 0.59873295)
@@ -16,17 +36,6 @@ radius = 0.6
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_24d3s"]
albedo_color = Color(0.31, 0.21576, 0.1333, 1)
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_np_table"]
properties/0/path = NodePath(".:_state")
properties/0/spawn = true
properties/0/replication_mode = 1
properties/1/path = NodePath(".:_state_time")
properties/1/spawn = true
properties/1/replication_mode = 1
properties/2/path = NodePath(".:_unsatisfied_orders")
properties/2/spawn = true
properties/2/replication_mode = 1
[sub_resource type="BoxShape3D" id="BoxShape3D_vlqg6"]
size = Vector3(2.1, 0.5, 2.1)
@@ -35,6 +44,10 @@ size = Vector3(2.1, 0.5, 2.1)
[node name="StationMovement" parent="." unique_id=946873975 node_paths=PackedStringArray("station") instance=ExtResource("1_5mi7y")]
station = NodePath("../Table")
[node name="MultiplayerSyncronizer" type="MultiplayerSynchronizer" parent="." unique_id=2000411505]
root_path = NodePath("../Table")
replication_config = SubResource("SceneReplicationConfig_np_table")
[node name="Table" type="StaticBody3D" parent="." unique_id=1863572470 groups=["station", "table"]]
script = ExtResource("1_2vcpj")
money_sound = ExtResource("2_jslhm")
@@ -154,9 +167,6 @@ pixel_size = 0.003
billboard = 2
text = "20.1s"
[node name="Sync" type="MultiplayerSynchronizer" parent="Table" unique_id=2000411505]
replication_config = SubResource("SceneReplicationConfig_np_table")
[node name="ProgressBar3D" parent="Table" unique_id=654673176 instance=ExtResource("3_kjf1i")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.8692299, 0)
y_billboard = true
+1 -1
View File
@@ -1,6 +1,6 @@
[gd_resource type="StandardMaterial3D" format=3 uid="uid://cmia50cfqxxo4"]
[ext_resource type="Texture2D" uid="uid://eqp04wybmg" path="res://Textures/devTex.svg" id="1_3jxsn"]
[ext_resource type="Texture2D" uid="uid://eqp04wybmg" path="res://textures/devTex.svg" id="1_3jxsn"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_0gbf8"]
transparency = 1
+1 -1
View File
@@ -1,6 +1,6 @@
[gd_resource type="Sky" format=3 uid="uid://c1abtpwhdm2d5"]
[ext_resource type="Texture2D" uid="uid://ci0hjoabnf8nu" path="res://Textures/sky/NightSkyHDRI009_16K_HDR.exr" id="acg_mft3ic9d"]
[ext_resource type="Texture2D" uid="uid://ci0hjoabnf8nu" path="res://textures/sky/NightSkyHDRI009_16K_HDR.exr" id="acg_mft3ic9d"]
[sub_resource type="PanoramaSkyMaterial" id="acg_025sfqr4"]
panorama = ExtResource("acg_mft3ic9d")
+1 -1
View File
@@ -48,7 +48,7 @@ func _ready() -> void:
## --server / --join <ip> on the command line skip straight to the multiplayer
## scene, same as the previously headless-tested main.tscn flow.
func _bypass_menu_for_cmdline() -> bool:
var args := OS.get_cmdline_user_args()
var args := OS.get_cmdline_args()
if args.has("--server"):
NetworkManager.request_host()
get_tree().change_scene_to_file.call_deferred(MULTIPLAYER_SCENE)
+4 -3
View File
@@ -17,12 +17,13 @@ func _ready() -> void:
func _on_button_pressed() -> void:
print("ShopStationButton buy ", station_name)
SweetLogger.debug("buy {0}", [station_name], "shop_station_button.gd", "_on_button_pressed")
_buy_station()
func _buy_station() -> void:
GameManager.money -= cost
SweetLogger.debug("_buy_station: cost={0} current money={1}", [cost, GameManager.money], "shop_station_button.gd", "_buy_station")
GameManager.set_money(GameManager.money - cost)
var transform = Helper.get_snapped_transform(XRHelpers.get_xr_origin(self).get_node_or_null("PlayerBody"))
var forward: Vector3 = -transform.basis.z.normalized()
transform.origin += (forward * 0.6)
@@ -33,7 +34,7 @@ func _buy_station() -> void:
Signals.station_bought.emit(instance)
@rpc("reliable")
@rpc("any_peer", "call_remote", "reliable")
func _ask_server_to_spawn(station_scene_path, transform):
NetworkManager.spawn_item(station_scene_path, transform)
+9 -6
View File
@@ -32,8 +32,10 @@ func _process(_delta: float) -> void:
func toggle_shop():
if GameManager.game_state == GameManager.GameState.BUILDING:
print("Shop UI toggle shop")
SweetLogger.debug("toggle shop", [], "shop_ui.gd", "toggle_shop")
enabled = !enabled
SweetLogger.debug("_process: money={0} label text={1}", [GameManager.money, money_label.text], "shop_ui.gd", "toggle_shop")
func _set_enabled(value: bool) -> void:
@@ -52,15 +54,14 @@ func _set_poke_enabled_for_controller(controller_node: XRController3D, value: bo
var poke_node = Helper.find_first_child_of_type(controller_node, XRToolsPoke)
if poke_node:
print("Shop UI _set_poke_enabled_for_controller: ", poke_node.get_path(), " poke enabled: ", value)
SweetLogger.debug("_set_poke_enabled_for_controller: {0} poke enabled: {1}", [poke_node.get_path(), value], "shop_ui.gd", "_set_poke_enabled_for_controller")
poke_node.enabled = value
poke_node.visible = value
else:
print("Shop UI _set_poke_enabled_for_controller: ", controller_node.name, " poke not found")
SweetLogger.debug("_set_poke_enabled_for_controller: {0} poke not found", [controller_node.name], "shop_ui.gd", "_set_poke_enabled_for_controller")
# For some reason, this is called every frame the button is down. So we need our own timer.
func _on_controller_button_pressed(button_name: String) -> void:
print("Shop _on_controller_button_pressed, button: ", button_name)
var now := Time.get_ticks_msec() / 1000.0
if now - _last_toggle_time < INPUT_TOGGLE_COOLDOWN:
return
@@ -76,8 +77,10 @@ func _on_other_controller_button_pressed(button_name: String) -> void:
enabled = false
func _on_station_bought(instance) -> void:
func _on_station_bought(_instance) -> void:
enabled = false
money_label.text = str(GameManager.money) + "$"
SweetLogger.debug("_on_station_bought: new money: {0}", [GameManager.money], "shop_ui.gd", "_on_station_bought")
func detect_hand_from_xr_ancestor() -> void:
@@ -93,4 +96,4 @@ func detect_hand_from_xr_ancestor() -> void:
other_controller = right_controller
elif controller == right_controller:
other_controller = left_controller
print("Shop UI detected hand: ", controller.get_tracker_hand())
SweetLogger.debug("detected hand: {0}", [controller.get_tracker_hand()], "shop_ui.gd", "detect_hand_from_xr_ancestor")
+1 -37
View File
@@ -1,4 +1,4 @@
[gd_resource type="ShaderMaterial" load_steps=7 format=3 uid="uid://dyuaw57o8y3i"]
[gd_resource type="ShaderMaterial" format=3 uid="uid://dyuaw57o8y3i"]
[sub_resource type="VisualShaderNodeColorParameter" id="VisualShaderNodeColorParameter_nl6jr"]
parameter_name = "Color"
@@ -16,42 +16,6 @@ constant = 0.1
operator = 2
[sub_resource type="VisualShader" id="VisualShader_wb0u4"]
code = "shader_type spatial;
render_mode blend_mix, depth_draw_opaque, cull_back, diffuse_lambert, specular_schlick_ggx;
uniform vec4 Color : source_color;
void fragment() {
// ColorParameter:2
vec4 n_out2p0 = Color;
// FloatConstant:4
float n_out4p0 = 0.100000;
// VectorOp:3
vec3 n_out3p0 = vec3(n_out2p0.xyz) * vec3(n_out4p0);
// Fresnel:5
float n_in5p3 = 1.00000;
float n_out5p0 = pow(1.0 - clamp(dot(NORMAL, VIEW), 0.0, 1.0), n_in5p3);
// VectorOp:6
vec3 n_out6p0 = vec3(n_out2p0.xyz) * vec3(n_out5p0);
// Output:0
ALBEDO = n_out3p0;
EMISSION = n_out6p0;
}
"
nodes/fragment/0/position = Vector2(660, 60)
nodes/fragment/2/node = SubResource("VisualShaderNodeColorParameter_nl6jr")
nodes/fragment/2/position = Vector2(40, 40)
@@ -1,4 +1,4 @@
[gd_resource type="VisualShader" load_steps=28 format=3 uid="uid://c6okm2ay0fkjf"]
[gd_resource type="VisualShader" format=3 uid="uid://c6okm2ay0fkjf"]
[sub_resource type="VisualShaderNodeFloatOp" id="1"]
output_port_for_preview = 0
@@ -101,159 +101,6 @@ output_port_for_preview = 0
function = 12
[resource]
code = "shader_type spatial;
render_mode blend_mix, depth_draw_opaque, cull_back, diffuse_lambert, specular_schlick_ggx, unshaded;
uniform vec4 albedo : source_color;
uniform float value;
uniform float fade;
uniform float radius;
uniform float width;
void fragment() {
// ColorParameter:2
vec4 n_out2p0 = albedo;
// Input:3
vec2 n_out3p0 = UV;
// VectorOp:4
vec3 n_in4p1 = vec3(2.00000, 2.00000, 0.00000);
vec3 n_out4p0 = vec3(n_out3p0, 0.0) * n_in4p1;
// VectorOp:5
vec3 n_in5p1 = vec3(-1.00000, -1.00000, 0.00000);
vec3 n_out5p0 = n_out4p0 + n_in5p1;
// VectorFunc:17
vec3 n_out17p0 = normalize(n_out5p0);
// VectorOp:19
vec3 n_in19p1 = vec3(0.00000, -1.00000, 0.00000);
vec3 n_out19p0 = cross(n_out17p0, n_in19p1);
// VectorDecompose:20
float n_out20p0 = n_out19p0.x;
float n_out20p1 = n_out19p0.y;
float n_out20p2 = n_out19p0.z;
// DotProduct:18
vec3 n_in18p1 = vec3(0.00000, -1.00000, 0.00000);
float n_out18p0 = dot(n_out17p0, n_in18p1);
// FloatFunc:24
float n_out24p0 = acos(n_out18p0);
// FloatOp:23
float n_in23p0 = 6.28319;
float n_out23p0 = n_in23p0 - n_out24p0;
vec3 n_out22p0;
// If:22
float n_in22p1 = 0.00000;
float n_in22p2 = 0.00001;
if(abs(n_out20p2 - n_in22p1) < n_in22p2)
{
n_out22p0 = vec3(n_out24p0);
}
else if(n_out20p2 < n_in22p1)
{
n_out22p0 = vec3(n_out24p0);
}
else
{
n_out22p0 = vec3(n_out23p0);
}
// FloatOp:25
float n_in25p1 = 6.28319;
float n_out25p0 = n_out22p0.x / n_in25p1;
// FloatParameter:26
float n_out26p0 = value;
// FloatOp:27
float n_out27p0 = n_out25p0 - n_out26p0;
// FloatParameter:14
float n_out14p0 = fade;
// FloatOp:30
float n_in30p1 = 6.28319;
float n_out30p0 = n_out14p0 / n_in30p1;
// FloatOp:28
float n_out28p0 = n_out27p0 / n_out30p0;
// VectorLen:6
float n_out6p0 = length(n_out5p0);
// FloatParameter:8
float n_out8p0 = radius;
// FloatOp:7
float n_out7p0 = n_out6p0 - n_out8p0;
// FloatFunc:9
float n_out9p0 = abs(n_out7p0);
// FloatParameter:11
float n_out11p0 = width;
// FloatOp:15
float n_in15p1 = 2.00000;
float n_out15p0 = n_out11p0 / n_in15p1;
// FloatOp:13
float n_out13p0 = n_out9p0 - n_out15p0;
// FloatOp:10
float n_out10p0 = n_out13p0 / n_out14p0;
// FloatOp:29
float n_out29p0 = max(n_out28p0, n_out10p0);
// FloatOp:12
float n_in12p0 = 1.00000;
float n_out12p0 = n_in12p0 - n_out29p0;
// Output:0
ALBEDO = vec3(n_out2p0.xyz);
ALPHA = n_out12p0;
}
"
graph_offset = Vector2(652.664, 119.317)
flags/unshaded = true
nodes/fragment/0/position = Vector2(1800, -40)
nodes/fragment/2/node = SubResource("3")
@@ -1,4 +1,4 @@
[gd_resource type="VisualShader" load_steps=16 format=3 uid="uid://4i0pwdtfmtsv"]
[gd_resource type="VisualShader" format=3 uid="uid://4i0pwdtfmtsv"]
[sub_resource type="VisualShaderNodeCompare" id="5"]
output_port_for_preview = 0
@@ -62,95 +62,6 @@ function = 2
condition = 1
[resource]
code = "shader_type spatial;
render_mode blend_mix, depth_draw_opaque, cull_back, diffuse_lambert, specular_schlick_ggx, unshaded;
uniform vec4 bar_color : source_color;
uniform sampler2D bar_texture : source_color;
uniform vec3 cutout = vec3(4.850000, 0.330000, 0.000000);
uniform float progress;
void fragment() {
// ColorParameter:15
vec4 n_out15p0 = bar_color;
vec4 n_out19p0;
// Texture2D:19
n_out19p0 = texture(bar_texture, UV);
float n_out19p4 = n_out19p0.a;
// VectorOp:17
vec3 n_out17p0 = vec3(n_out15p0.xyz) * vec3(n_out19p0.xyz);
// Input:3
vec2 n_out3p0 = UV;
// VectorOp:4
vec3 n_in4p1 = vec3(10.00000, 1.00000, 0.00000);
vec3 n_out4p0 = vec3(n_out3p0, 0.0) * n_in4p1;
// VectorOp:5
vec3 n_in5p1 = vec3(5.00000, 0.50000, 0.00000);
vec3 n_out5p0 = n_out4p0 - n_in5p1;
// VectorFunc:6
vec3 n_out6p0 = abs(n_out5p0);
// Vector3Parameter:20
vec3 n_out20p0 = cutout;
bool n_out8p0;
// Compare:8
{
bvec2 _bv = greaterThan(vec2(n_out6p0.xy), vec2(n_out20p0.xy));
n_out8p0 = any(_bv);
}
// FloatParameter:12
float n_out12p0 = progress;
// VectorCompose:13
float n_in13p1 = 0.00000;
float n_in13p2 = 0.00000;
vec3 n_out13p0 = vec3(n_out12p0, n_in13p1, n_in13p2);
bool n_out11p0;
// Compare:11
{
bvec2 _bv = lessThan(n_out3p0, vec2(n_out13p0.xy));
n_out11p0 = any(_bv);
}
// FloatOp:14
float n_out14p0 = max((n_out8p0 ? 1.0 : 0.0), (n_out11p0 ? 1.0 : 0.0));
// FloatOp:18
float n_out18p0 = n_out19p4 * n_out14p0;
// Output:0
ALBEDO = n_out17p0;
ALPHA = n_out18p0;
}
"
graph_offset = Vector2(-744.269, 307.118)
flags/unshaded = true
nodes/fragment/0/position = Vector2(1220, 440)
nodes/fragment/3/node = SubResource("10")
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 sweetpea
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+105
View File
@@ -0,0 +1,105 @@
## hey - sweet logger you got there!
---
A Godot 4 addon that makes reading logs and debugging sane when you run multiple game instances at once.
It's meant as a drop-in/stand-in for `print()`: same habit of sprinkling logs through your systems, but every line carries more context - which peer printed it, what kind of log it is, which script and function it came from. That makes it much easier to follow what each client (and the server) is doing when several instances share one console.
When you're testing multiplayer locally (host + clients in separate run instances), the editor console fills with interleaved output from every peer. Sweet Logger formats each line the same way so you can tell at a glance **what kind of log it is**, **which peer it came from**, **when it happened**, and **which script/function** produced it.
---
## Features
- **Peer-aware labels** - tags each line as `SERVER`, a client peer ID, or `DISCONNECTED`
- **Color-coded peers** - consistent colors per peer so interleaved console output is easy to scan
- **Log levels** - `log`, `info`, `warning`, `error`, `debug` with distinct colors
- **Script context** - optional script and function name columns
- **Local timestamps** - `mm:ss:ms` by default (optional hours)
- **Rich console output** - uses `print_rich` with aligned columns
---
## Installation
### From the Godot Asset Library
1. In Godot, open the **AssetLib** tab.
2. Search for **Sweet Logger** and download it.
3. Open **Project → Project Settings → Plugins** and enable **Sweet Logger**.
4. The plugin registers a `SweetLogger` autoload automatically.
### Manual
1. Copy the `addons/sweet-logger` folder into your project's `addons/` directory.
2. In Godot, open **Project → Project Settings → Plugins** and enable **Sweet Logger**.
3. The plugin registers a `SweetLogger` autoload automatically.
---
## Usage
Call the autoload from anywhere:
```gdscript
SweetLogger.info("Player joined", [], "lobby.gd", "on_peer_connected")
SweetLogger.warning("High latency: {0}ms", [rtt], "net.gd", "_process")
SweetLogger.error("RPC failed", [], "sync.gd", "apply_state")
SweetLogger.debug("Tick {0}", [tick], "game.gd", "_physics_process")
SweetLogger.log("Hello from peer")
```
### Message formatting
Pass optional args and use `{0}`, `{1}`, … placeholders:
```gdscript
SweetLogger.info("Spawned {0} at {1}", [entity_name, position], "spawner.gd", "spawn")
```
### API
| Method | Level |
| --------------------------------------------------------------------- | ------- |
| `SweetLogger.log(message, args=[], script_name="", function_name="")` | General |
| `SweetLogger.info(...)` | Info |
| `SweetLogger.warning(...)` | Warning |
| `SweetLogger.error(...)` | Error |
| `SweetLogger.debug(...)` | Debug |
All methods share the same signature: message, optional format args, optional script name, optional function name.
## Configuration
On the `SweetLogger` autoload (or in the inspector when selected):
| Export | Default | Description |
| ---------------------- | ------- | --------------------------------------- |
| `SHOW_SCRIPT_NAME` | `true` | Include the script name column |
| `SHOW_FUNCTION_NAME` | `true` | Include the function name column |
| `SHOW_TIMESTAMP_HOURS` | `false` | Use `hh:mm:ss:ms` instead of `mm:ss:ms` |
## Log line layout
Each line is roughly:
```
[LEVEL][timestamp][PEER][script::function] message
```
- **LEVEL** - color by type (info blue, warning yellow, error red, etc.)
- **timestamp** - local time for correlating events across instances
- **PEER** - `SERVER`, numeric client ID, or `DISCONNECTED`, each with a stable color
- **script::function** - where the log was emitted (when you pass those args)
---
## Why this exists
Godot's "Run Multiple Instances" is great for testing multiplayer games on one machine, but plain `print()` output from every peer lands in one console with no clear ownership. Sweet Logger keeps a fixed column layout and peer coloring so you can follow one client's story (or the server's) without losing the others.
## License
MIT - see [LICENSE](addons/sweet-logger/LICENSE).
+7
View File
@@ -0,0 +1,7 @@
[plugin]
name="Sweet Logger"
description="Rich console logging with peer-aware prefixes. Registers the SweetLogger autoload."
author="sweetpea"
version="1.0.0"
script="plugin.gd"
+13
View File
@@ -0,0 +1,13 @@
@tool
extends EditorPlugin
const AUTOLOAD_NAME := "SweetLogger"
const AUTOLOAD_PATH := "res://addons/sweet-logger/sweet_logger.gd"
func _enter_tree() -> void:
var key := "autoload/%s" % AUTOLOAD_NAME
if not ProjectSettings.has_setting(key):
add_autoload_singleton(AUTOLOAD_NAME, AUTOLOAD_PATH)
func _exit_tree() -> void:
remove_autoload_singleton(AUTOLOAD_NAME)
+1
View File
@@ -0,0 +1 @@
uid://cm16x8s6cox4w
+289
View File
@@ -0,0 +1,289 @@
extends Node
## Global Logger singleton for consistent logging across the game.
## All logs are prefixed with [peer_id]: to identify which instance is logging.
## Uses print_rich with colorized backgrounds for log type, local time, peer IDs, and script context.
# COLORS
#===================================================================================#
## Log type configuration with background color, message background color, and contrast text color
const LOG_TYPES = {
"log": {
"bg_color": "#2d2d2d",
"message_bg_color": "#3d3d3d",
"text_color": "#ffffff"
},
"info": {
"bg_color": "#2563eb",
"message_bg_color": "#3b82f6",
"text_color": "#ffffff"
},
"warning": {
"bg_color": "#fbbf24",
"message_bg_color": "#fcd34d",
"text_color": "#ffffff"
},
"error": {
"bg_color": "#dc2626",
"message_bg_color": "#ef4444",
"text_color": "#ffffff"
},
"debug": {
"bg_color": "#7c3aed",
"message_bg_color": "#a78bfa",
"text_color": "#ffffff"
}
}
## Contrast text color that works on all backgrounds
const CONTRAST_TEXT = "#ffffff"
## Predefined colors for special peer IDs
const SPECIAL_PEER_COLORS = {
"SERVER": {
"bg_color": "#7c3aed",
"text_color": "#ffffff"
},
"DISCONNECTED": {
"bg_color": "#6b7280",
"text_color": "#ffffff"
}
}
const PEER_COLOR_PALETTE = [
{"bg_color": "#10b981", "text_color": "#ffffff"}, # emerald green
{"bg_color": "#06b6d4", "text_color": "#ffffff"}, # cyan
{"bg_color": "#ec4899", "text_color": "#ffffff"}, # fuchsia pink
{"bg_color": "#f97316", "text_color": "#ffffff"}, # orange
{"bg_color": "#14b8a6", "text_color": "#ffffff"}, # teal
{"bg_color": "#84cc16", "text_color": "#000000"}, # lime green
{"bg_color": "#0ea5e9", "text_color": "#ffffff"}, # sky blue
{"bg_color": "#22c55e", "text_color": "#ffffff"}, # green
{"bg_color": "#e11d48", "text_color": "#ffffff"}, # rose red
{"bg_color": "#d946ef", "text_color": "#ffffff"}, # magenta
]
## Background color for the entire log line
const LOG_LINE_BG_COLOR = "#1a1a1a"
## Background color for the script/function name column
const SCRIPT_FUNCTION_BG_COLOR = "#4a5a6a"
## Background color for the local time column (between log type and peer id)
const TIMESTAMP_BG_COLOR = "#1e3a5f"
#===================================================================================#
# WIDTHS
#===================================================================================#
## Column widths for alignment (in characters)
const PEER_ID_COLUMN_WIDTH = 10
const LOG_TYPE_COLUMN_WIDTH = 8
## Default mm:ss:ms; with SHOW_TIMESTAMP_HOURS, hh:mm:ss:ms
const TIMESTAMP_COLUMN_WIDTH_MMSSMS = 9
const TIMESTAMP_COLUMN_WIDTH_HHMMSSMS = 12
const SCRIPT_FUNCTION_COLUMN_WIDTH = 40
#===================================================================================#
# CONFIGURATION
#===================================================================================#
## Cache for peer ID colors to ensure consistency
var _peer_color_cache: Dictionary = {}
## Enable/disable showing script name and function name in logs
@export var SHOW_SCRIPT_NAME = true
@export var SHOW_FUNCTION_NAME = true
## When false (default), timestamp is local mm:ss:ms. When true, local hh:mm:ss:ms (wider column).
@export var SHOW_TIMESTAMP_HOURS = false
#===================================================================================#
# GETTERS
#===================================================================================#
func _get_peer_id() -> String:
"""Get the current peer ID, or return a default identifier if not connected."""
if multiplayer == null:
return "DISCONNECTED"
# Check if we're the server (server always has ID 1, but we check is_server() for clarity)
if multiplayer.is_server():
return "SERVER"
# Get the unique ID (will be 1 for server, or random positive int > 1 for clients)
var peer_id = multiplayer.get_unique_id()
return str(peer_id)
func _get_peer_color_config(peer_id_str: String) -> Dictionary:
"""Get color configuration for a peer ID string."""
# Check special peer IDs first
if SPECIAL_PEER_COLORS.has(peer_id_str):
return SPECIAL_PEER_COLORS[peer_id_str]
# Check cache
if _peer_color_cache.has(peer_id_str):
return _peer_color_cache[peer_id_str]
# Generate consistent color based on peer ID
# Try to parse the peer_id_str as a number
var peer_id_num = peer_id_str.to_int()
var color_index: int
if peer_id_num > 0:
color_index = peer_id_num % PEER_COLOR_PALETTE.size()
else:
# If parsing fails, use hash of the string for consistency
var hash_value = peer_id_str.hash()
color_index = abs(hash_value) % PEER_COLOR_PALETTE.size()
var color_config = PEER_COLOR_PALETTE[color_index].duplicate()
_peer_color_cache[peer_id_str] = color_config
return color_config
#===================================================================================#
# FORMATTERS
#===================================================================================#
func _format_message(message: String, args: Array = []) -> String:
"""Format message with optional arguments."""
if args.is_empty():
return message
var formatted = message
for i in range(args.size()):
formatted = formatted.replace("{" + str(i) + "}", str(args[i]))
return formatted
func _format_rich_text(text: String, bg_color: String, text_color: String) -> String:
"""Format text with background and text colors using BBCode."""
return "[bgcolor=%s][color=%s]%s[/color][/bgcolor]" % [bg_color, text_color, text]
func _format_space_with_bg(bg_color: String) -> String:
"""Format a space character with a background color."""
return "[bgcolor=%s] [/bgcolor]" % bg_color
func _get_padding(text_length: int, width: int) -> String:
"""Get padding string for a given text length and target width."""
if text_length >= width:
return ""
var padding = ""
for i in range(width - text_length):
padding += " "
return padding
func _pad_text(text: String, width: int) -> String:
"""Pad text to a specific width for column alignment."""
return text + _get_padding(text.length(), width)
func _truncate_text(text: String, max_length: int) -> String:
"""Truncate text to a maximum length."""
if text.length() <= max_length:
return text
return text.substr(0, max_length)
func _get_local_timestamp_string() -> String:
"""Local time: mm:ss:ms by default, or hh:mm:ss:ms when SHOW_TIMESTAMP_HOURS is true."""
var unix = Time.get_unix_time_from_system()
var ms = clampi(int(floor(fmod(unix, 1.0) * 1000.0)), 0, 999)
var t = Time.get_time_dict_from_system()
if SHOW_TIMESTAMP_HOURS:
return "%02d:%02d:%02d:%03d" % [t.hour, t.minute, t.second, ms]
return "%02d:%02d:%03d" % [t.minute, t.second, ms]
#===================================================================================#
# PRINT
#===================================================================================#
func _print_rich_log(peer_id_str: String, log_type: String, message: String, script_name: String = "", function_name: String = "") -> void:
"""Print a rich formatted log with peer ID and log type colors."""
var peer_color = _get_peer_color_config(peer_id_str)
var log_config = LOG_TYPES.get(log_type, LOG_TYPES["log"])
# Use peer ID directly without "Peer" prefix
var peer_label = peer_id_str
var log_type_label = log_type.to_upper()
# Pad peer ID column for alignment
var peer_padded = _pad_text(peer_label, PEER_ID_COLUMN_WIDTH)
# Format peer ID with its background color
var peer_formatted = _format_rich_text(peer_padded, peer_color.bg_color, peer_color.text_color)
# Format log type with its background color
var log_type_padded = _pad_text(log_type_label, LOG_TYPE_COLUMN_WIDTH)
var log_type_formatted = _format_rich_text(log_type_padded, log_config.bg_color, log_config.text_color)
# Local time column (between log type and peer id)
var ts_width = TIMESTAMP_COLUMN_WIDTH_HHMMSSMS if SHOW_TIMESTAMP_HOURS else TIMESTAMP_COLUMN_WIDTH_MMSSMS
var time_padded = _pad_text(_get_local_timestamp_string(), ts_width)
var time_formatted = _format_rich_text(time_padded, TIMESTAMP_BG_COLOR, CONTRAST_TEXT)
# Build the context string (script name and function name) combined in one column
var context_string = ""
var script_function_parts: Array[String] = []
if SHOW_SCRIPT_NAME and script_name != "":
var script_truncated = _truncate_text(script_name, 18)
script_function_parts.append(script_truncated)
if SHOW_FUNCTION_NAME and function_name != "":
var function_truncated = _truncate_text(function_name, 20)
script_function_parts.append(function_truncated)
if not script_function_parts.is_empty():
# Combine script and function names with a separator
var combined = "::".join(script_function_parts)
# Pad, then format with custom background color (so whitespace has background too)
var combined_padded = _pad_text(combined, SCRIPT_FUNCTION_COLUMN_WIDTH)
var combined_formatted = _format_rich_text(combined_padded, SCRIPT_FUNCTION_BG_COLOR, log_config.text_color)
context_string = combined_formatted
# Format message with text color only (no background)
var message_formatted = "[color=%s]%s[/color]" % [log_config.text_color, message]
# Format spaces with peer ID background color
var peer_space = _format_space_with_bg(peer_color.bg_color)
# Combine log type, context, and message with peer-colored spaces
var peer_and_message = peer_formatted
if context_string != "":
peer_and_message += peer_space + context_string
peer_and_message += peer_space + message_formatted
# Create the full log line: log type, timestamp, then peer id and message
var log_line = log_type_formatted + time_formatted + peer_and_message
var wrapped_line = "[bgcolor=%s]%s[/bgcolor]" % [LOG_LINE_BG_COLOR, log_line]
print_rich(wrapped_line)
#===================================================================================#
# CALLERS
#===================================================================================#
func log(message: String, args: Array = [], script_name: String = "", function_name: String = "") -> void:
"""Basic log function with peer_id prefix."""
var formatted = _format_message(message, args)
var peer_id_str = _get_peer_id()
_print_rich_log(peer_id_str, "log", formatted, script_name, function_name)
func info(message: String, args: Array = [], script_name: String = "", function_name: String = "") -> void:
"""Log an informational message."""
var formatted = _format_message(message, args)
var peer_id_str = _get_peer_id()
_print_rich_log(peer_id_str, "info", formatted, script_name, function_name)
func warning(message: String, args: Array = [], script_name: String = "", function_name: String = "") -> void:
"""Log a warning message."""
var formatted = _format_message(message, args)
var peer_id_str = _get_peer_id()
_print_rich_log(peer_id_str, "warning", formatted, script_name, function_name)
func error(message: String, args: Array = [], script_name: String = "", function_name: String = "") -> void:
"""Log an error message."""
var formatted = _format_message(message, args)
var peer_id_str = _get_peer_id()
_print_rich_log(peer_id_str, "error", formatted, script_name, function_name)
func debug(message: String, args: Array = [], script_name: String = "", function_name: String = "") -> void:
"""Log a debug message."""
var formatted = _format_message(message, args)
var peer_id_str = _get_peer_id()
_print_rich_log(peer_id_str, "debug", formatted, script_name, function_name)
#===================================================================================#
+1
View File
@@ -0,0 +1 @@
uid://cdfw054e8nhxv
+4 -3
View File
@@ -88,6 +88,7 @@ Shared INventory
## NEtwork TOTO
- Make gamemanager a node with syncroniser
- All stations
- Client purchases don't spawn
- All stations need syncronizers
- Pickable not synced cooked burger on hob example
- Client purchases don't spawn items from shop. RPC adventure
- Client can not move stations including hobs, on_move handle drop is called, network pickable authority is given. but station not moved
+224 -25
View File
@@ -1,19 +1,201 @@
class_name GameManager
extends Node
const STAR_DAY_FREQUENCY: int = 3
static var money: int = 1000
static var day_number: int = 1
static var meals_in_play: Array[String]
static var sides_in_play: Array[String]
var money: int = 1000:
get: return money
func set_money(value):
if multiplayer.is_server():
money = max(0,value)
client_sync_money.rpc(money)
SweetLogger.info("set_money money set to {0}", [money], "GameManager.gd", "set_money")
else:
SweetLogger.info("set_money client/(peer_id={0}) attempted to set money directly, sending request to server", [multiplayer.get_unique_id()], "GameManager.gd", "set_money")
server_set_money.rpc_id(1, value)
@rpc("any_peer", "call_remote", "reliable") # CLIENT -> SERVER: Request a change
func server_set_money(value: int) -> void: set_money(value)
@rpc("authority", "call_remote", "reliable") # SERVER -> CLIENTS: Replicate new change
func client_sync_money(value: int) -> void: money = value
var day_number: int = 1:
get:
return day_number
func set_day_number(value: int) -> void:
if multiplayer.is_server():
day_number = value
client_sync_day_number.rpc(day_number)
SweetLogger.info("set_day_number day_number set to {0}", [day_number], "GameManager.gd", "set_day_number")
else:
SweetLogger.info("set_day_number client/(peer_id={0}) attempted to set day_number directly, sending request to server", [multiplayer.get_unique_id()], "GameManager.gd", "set_day_number")
server_set_day_number.rpc_id(1, value)
@rpc("any_peer", "call_remote", "reliable")
func server_set_day_number(value: int) -> void:
set_day_number(value)
@rpc("authority", "call_remote", "reliable")
func client_sync_day_number(value: int) -> void:
day_number = value
var meals_in_play: Array[String] = []:
get:
return meals_in_play
func set_meals_in_play(value: Array[String]) -> void:
if multiplayer.is_server():
meals_in_play = value
client_sync_meals_in_play.rpc(meals_in_play)
SweetLogger.info("set_meals_in_play meals_in_play set to {0}", [meals_in_play], "GameManager.gd", "set_meals_in_play")
else:
SweetLogger.info("set_meals_in_play client/(peer_id={0}) attempted to set meals_in_play directly, sending request to server", [multiplayer.get_unique_id()], "GameManager.gd", "set_meals_in_play")
server_set_meals_in_play.rpc_id(1, value)
@rpc("any_peer", "call_remote", "reliable")
func server_set_meals_in_play(value: Array[String]) -> void:
set_meals_in_play(value)
@rpc("authority", "call_remote", "reliable")
func client_sync_meals_in_play(value: Array[String]) -> void:
meals_in_play = value
var sides_in_play: Array[String] = []:
get:
return sides_in_play
func set_sides_in_play(value: Array[String]) -> void:
if multiplayer.is_server():
sides_in_play = value
client_sync_sides_in_play.rpc(sides_in_play)
SweetLogger.info("set_sides_in_play sides_in_play set to {0}", [sides_in_play], "GameManager.gd", "set_sides_in_play")
else:
SweetLogger.info("set_sides_in_play client/(peer_id={0}) attempted to set sides_in_play directly, sending request to server", [multiplayer.get_unique_id()], "GameManager.gd", "set_sides_in_play")
server_set_sides_in_play.rpc_id(1, value)
@rpc("any_peer", "call_remote", "reliable")
func server_set_sides_in_play(value: Array[String]) -> void:
set_sides_in_play(value)
@rpc("authority", "call_remote", "reliable")
func client_sync_sides_in_play(value: Array[String]) -> void:
sides_in_play = value
# GameConfig - difficulty settings
static var day_length_seconds: float = 60.0
static var customers_per_day: float = 5.0
static var customers_count_increase_per_day: float = 1.0
static var group_min_size: int = 1
static var group_max_size: int = 2
var day_length_seconds: float = 60.0:
get:
return day_length_seconds
func set_day_length_seconds(value: float) -> void:
if multiplayer.is_server():
day_length_seconds = value
client_sync_day_length_seconds.rpc(day_length_seconds)
SweetLogger.info("set_day_length_seconds day_length_seconds set to {0}", [day_length_seconds], "GameManager.gd", "set_day_length_seconds")
else:
SweetLogger.info("set_day_length_seconds client/(peer_id={0}) attempted to set day_length_seconds directly, sending request to server", [multiplayer.get_unique_id()], "GameManager.gd", "set_day_length_seconds")
server_set_day_length_seconds.rpc_id(1, value)
@rpc("any_peer", "call_remote", "reliable")
func server_set_day_length_seconds(value: float) -> void:
set_day_length_seconds(value)
@rpc("authority", "call_remote", "reliable")
func client_sync_day_length_seconds(value: float) -> void:
day_length_seconds = value
var customers_per_day: float = 5.0:
get:
return customers_per_day
func set_customers_per_day(value: float) -> void:
if multiplayer.is_server():
customers_per_day = value
client_sync_customers_per_day.rpc(customers_per_day)
SweetLogger.info("set_customers_per_day customers_per_day set to {0}", [customers_per_day], "GameManager.gd", "set_customers_per_day")
else:
SweetLogger.info("set_customers_per_day client/(peer_id={0}) attempted to set customers_per_day directly, sending request to server", [multiplayer.get_unique_id()], "GameManager.gd", "set_customers_per_day")
server_set_customers_per_day.rpc_id(1, value)
@rpc("any_peer", "call_remote", "reliable")
func server_set_customers_per_day(value: float) -> void:
set_customers_per_day(value)
@rpc("authority", "call_remote", "reliable")
func client_sync_customers_per_day(value: float) -> void:
customers_per_day = value
var customers_count_increase_per_day: float = 1.0:
get:
return customers_count_increase_per_day
func set_customers_count_increase_per_day(value: float) -> void:
if multiplayer.is_server():
customers_count_increase_per_day = value
client_sync_customers_count_increase_per_day.rpc(customers_count_increase_per_day)
SweetLogger.info("set_customers_count_increase_per_day customers_count_increase_per_day set to {0}", [customers_count_increase_per_day], "GameManager.gd", "set_customers_count_increase_per_day")
else:
SweetLogger.info("set_customers_count_increase_per_day client/(peer_id={0}) attempted to set customers_count_increase_per_day directly, sending request to server", [multiplayer.get_unique_id()], "GameManager.gd", "set_customers_count_increase_per_day")
server_set_customers_count_increase_per_day.rpc_id(1, value)
@rpc("any_peer", "call_remote", "reliable")
func server_set_customers_count_increase_per_day(value: float) -> void:
set_customers_count_increase_per_day(value)
@rpc("authority", "call_remote", "reliable")
func client_sync_customers_count_increase_per_day(value: float) -> void:
customers_count_increase_per_day = value
var group_min_size: int = 1:
get:
return group_min_size
func set_group_min_size(value: int) -> void:
if multiplayer.is_server():
group_min_size = value
client_sync_group_min_size.rpc(group_min_size)
SweetLogger.info("set_group_min_size group_min_size set to {0}", [group_min_size], "GameManager.gd", "set_group_min_size")
else:
SweetLogger.info("set_group_min_size client/(peer_id={0}) attempted to set group_min_size directly, sending request to server", [multiplayer.get_unique_id()], "GameManager.gd", "set_group_min_size")
server_set_group_min_size.rpc_id(1, value)
@rpc("any_peer", "call_remote", "reliable")
func server_set_group_min_size(value: int) -> void:
set_group_min_size(value)
@rpc("authority", "call_remote", "reliable")
func client_sync_group_min_size(value: int) -> void:
group_min_size = value
var group_max_size: int = 2:
get:
return group_max_size
func set_group_max_size(value: int) -> void:
if multiplayer.is_server():
group_max_size = value
client_sync_group_max_size.rpc(group_max_size)
SweetLogger.info("set_group_max_size group_max_size set to {0}", [group_max_size], "GameManager.gd", "set_group_max_size")
else:
SweetLogger.info("set_group_max_size client/(peer_id={0}) attempted to set group_max_size directly, sending request to server", [multiplayer.get_unique_id()], "GameManager.gd", "set_group_max_size")
server_set_group_max_size.rpc_id(1, value)
@rpc("any_peer", "call_remote", "reliable")
func server_set_group_max_size(value: int) -> void:
set_group_max_size(value)
@rpc("authority", "call_remote", "reliable")
func client_sync_group_max_size(value: int) -> void:
group_max_size = value
enum GameState {
@@ -22,38 +204,55 @@ enum GameState {
BUILDING
}
static var game_state: GameState = GameState.BUILDING: set = _set_game_state
var game_state: GameState = GameState.BUILDING:
get:
return game_state
static func _set_game_state(value: GameState):
func set_game_state(value: GameState) -> void:
if multiplayer.is_server():
game_state = value
client_sync_game_state.rpc(game_state)
Signals.game_state_changed.emit(game_state)
SweetLogger.info("set_game_state game_state set to {0}", [game_state], "GameManager.gd", "set_game_state")
else:
SweetLogger.info("set_game_state client/(peer_id={0}) attempted to set game_state directly, sending request to server", [multiplayer.get_unique_id()], "GameManager.gd", "set_game_state")
server_set_game_state.rpc_id(1, value)
@rpc("any_peer", "call_remote", "reliable")
func server_set_game_state(value: GameState) -> void:
set_game_state(value)
@rpc("authority", "call_remote", "reliable")
func client_sync_game_state(value: GameState) -> void:
game_state = value
Signals.game_state_changed.emit(value)
Signals.game_state_changed.emit(game_state)
static func _restart():
print("restart Game")
game_state = GameState.RUNNING
static func get_random_meal() -> String:
func get_random_meal() -> String:
if meals_in_play.size() == 0:
push_error("GameManager: get_random_meal() called but meals_in_play is empty")
return ""
var rand_index = randi() % meals_in_play.size()
print("GameManager: get_random_meal() returning ", meals_in_play[rand_index])
SweetLogger.debug("get_random_meal() returning {0}", [meals_in_play[rand_index]], "GameManager.gd", "get_random_meal")
return meals_in_play[rand_index]
static func get_random_side() -> String:
print("GameManager: get_random_side() ")
func get_random_side() -> String:
SweetLogger.debug("get_random_side()", [], "GameManager.gd", "get_random_side")
if sides_in_play.size() == 0:
push_error("GameManager: get_random_side() called but sides_in_play is empty")
return ""
var rand_index = randi() % sides_in_play.size()
print("GameManager: get_random_side() list=%s returning %s" % [sides_in_play, sides_in_play[rand_index]])
SweetLogger.debug("get_random_side() list={0} returning {1}", [sides_in_play, sides_in_play[rand_index]], "GameManager.gd", "get_random_side")
return sides_in_play[rand_index]
static func game_over():
print("Game Over")
func _restart():
SweetLogger.info("restart Game", [], "GameManager.gd", "_restart")
game_state = GameState.RUNNING
func game_over():
SweetLogger.info("Game Over", [], "GameManager.gd", "game_over")
game_state = GameState.GAME_OVER
Signals.game_over.emit()
+14 -14
View File
@@ -111,22 +111,22 @@ static func get_item_scene(item_id: StringName) -> PackedScene:
static func print_all_recipes() -> void:
load_recipes()
if not _loaded:
print("RecipeManager: could not load recipes.yaml; no recipes printed.")
SweetLogger.warning("could not load recipes.yaml; no recipes printed.", [], "RecipeManager.gd", "print_all_recipes")
return
if _combining_map.is_empty():
print("RecipeManager: no combining recipes found")
SweetLogger.warning("no combining recipes found", [], "RecipeManager.gd", "print_all_recipes")
return
print("#### RecipeManager: Loaded recipes ####")
SweetLogger.info("#### RecipeManager: Loaded recipes ####", [], "RecipeManager.gd", "print_all_recipes")
_print_combining_recipes()
print("")
SweetLogger.info("", [], "RecipeManager.gd", "print_all_recipes")
_print_cooking_recipes()
print("")
SweetLogger.info("", [], "RecipeManager.gd", "print_all_recipes")
_print_chopping_recipes()
print("")
SweetLogger.info("", [], "RecipeManager.gd", "print_all_recipes")
_print_rolling_recipes()
print("")
SweetLogger.info("", [], "RecipeManager.gd", "print_all_recipes")
_print_augmenting_recipes()
static func _print_combining_recipes() -> void:
@@ -135,12 +135,12 @@ static func _print_combining_recipes() -> void:
var key_str = str(pair_key)
var separator_index = key_str.find("|")
if separator_index == -1:
print("RecipeManager: invalid combining map key '%s'" % key_str)
SweetLogger.warning("invalid combining map key '{0}'", [key_str], "RecipeManager.gd", "_print_combining_recipes")
continue
var first = key_str.substr(0, separator_index)
var second = key_str.substr(separator_index + 1, key_str.length() - separator_index - 1)
print(" %s <-combine-- %s + %s" % [result_id, first, second])
SweetLogger.info(" {0} <-combine-- {1} + {2}", [result_id, first, second], "RecipeManager.gd", "_print_combining_recipes")
static func _print_cooking_recipes() -> void:
@@ -148,22 +148,22 @@ static func _print_cooking_recipes() -> void:
var cook_defs = _cooking_map[cooked_id]
if typeof(cook_defs) == TYPE_ARRAY:
for cook_def in cook_defs:
print(" %s <-cook-- %s - time: %.1f" % [cooked_id, cook_def["ingredient"], cook_def["time"]])
SweetLogger.info(" {0} <-cook-- {1} - time: {2}", [cooked_id, cook_def["ingredient"], cook_def["time"]], "RecipeManager.gd", "_print_cooking_recipes")
else:
var cook_def = cook_defs
print(" %s <-cook-- %s - time: %.1f" % [cooked_id, cook_def["ingredient"], cook_def["time"]])
SweetLogger.info(" {0} <-cook-- {1} - time: {2}", [cooked_id, cook_def["ingredient"], cook_def["time"]], "RecipeManager.gd", "_print_cooking_recipes")
static func _print_chopping_recipes() -> void:
for chopped_id in _chopping_map.keys():
var chop_def = _chopping_map[chopped_id]
print(" %s <-chop-- %s - work: %.1f" % [chopped_id, chop_def["ingredient"], chop_def["work"]])
SweetLogger.info(" {0} <-chop-- {1} - work: {2}", [chopped_id, chop_def["ingredient"], chop_def["work"]], "RecipeManager.gd", "_print_chopping_recipes")
static func _print_rolling_recipes() -> void:
for rolled_id in _rolling_map.keys():
var roll_def = _rolling_map[rolled_id]
print(" %s <-roll-- %s - work: %.1f" % [rolled_id, roll_def["ingredient"], roll_def["work"]])
SweetLogger.info(" {0} <-roll-- {1} - work: {2}", [rolled_id, roll_def["ingredient"], roll_def["work"]], "RecipeManager.gd", "_print_rolling_recipes")
static func _print_augmenting_recipes() -> void:
@@ -171,7 +171,7 @@ static func _print_augmenting_recipes() -> void:
var augment_def = _augmenting_map[target_id]
for ingredient_id in augment_def.keys():
var attr_key = augment_def[ingredient_id]
print(" %s <-augment-- %s adds attribute '%s'" % [target_id, ingredient_id, attr_key])
SweetLogger.info(" {0} <-augment-- {1} adds attribute '{2}'", [target_id, ingredient_id, attr_key], "RecipeManager.gd", "_print_augmenting_recipes")
static func _build_scene_paths() -> void:
+5
View File
@@ -1,7 +1,12 @@
extends Node
@warning_ignore("unused_signal")
signal game_over
@warning_ignore("unused_signal")
signal restart_game
@warning_ignore("unused_signal")
signal game_state_changed(new_state: GameManager.GameState)
@warning_ignore("unused_signal")
signal station_bought(instance: Node3D)
@warning_ignore("unused_signal")
signal request_customer_spawn()
+19
View File
@@ -0,0 +1,19 @@
extends Node
func _ready() -> void:
if OS.has_feature("editor"):
# Force subwindows to be native OS windows instead of embedded viewports
get_tree().root.gui_embed_subwindows = false
get_tree().create_timer(0.05).timeout.connect(_position_window)
func _position_window() -> void:
var is_server: bool = OS.get_cmdline_args().has("--server")
var screen_rect: Rect2i = DisplayServer.screen_get_usable_rect(DisplayServer.window_get_current_screen())
var window_width: int = screen_rect.size.x / 2
var window_height: int = int(screen_rect.size.y * (2.0 / 3.0))
DisplayServer.window_set_size(Vector2i(window_width - 10, window_height))
var x_pos: int = screen_rect.position.x + window_width if is_server else screen_rect.position.x
DisplayServer.window_set_position(Vector2i(x_pos, screen_rect.position.y))
+1
View File
@@ -0,0 +1 @@
uid://de8j52uus1pbv
+11 -7
View File
@@ -2,7 +2,7 @@ extends Node
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("quit_game"):
get_tree().quit()
all_quit_game.rpc()
if event.is_action_pressed("satisfy_table_orders"):
satisfy_table_orders()
if event.is_action_pressed("set_building_mode"):
@@ -12,6 +12,10 @@ func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("set_game_over"):
set_game_over()
# Kill all clients
@rpc("any_peer", "call_local", "reliable")
func all_quit_game():
get_tree().quit()
# Finds any node of class/type Table and calls satisfyAllOrders()
func satisfy_table_orders() -> void:
@@ -24,14 +28,14 @@ func satisfy_table_orders() -> void:
func set_buidling_mode():
print("Global key event: Setting building mode")
GameManager.game_state = GameManager.GameState.BUILDING
SweetLogger.info("Setting building mode", [], "global_key_events.gd", "set_buidling_mode")
GameManager.set_game_state(GameManager.GameState.BUILDING)
func set_running_mode():
print("Global key event: Setting running mode")
GameManager.game_state = GameManager.GameState.RUNNING
SweetLogger.info("Setting running mode", [], "global_key_events.gd", "set_running_mode")
GameManager.set_game_state(GameManager.GameState.RUNNING)
func set_game_over():
print("Global key event: Setting game over")
GameManager.game_state = GameManager.GameState.GAME_OVER
SweetLogger.info("Setting game over", [], "global_key_events.gd", "set_game_over")
GameManager.set_game_state(GameManager.GameState.GAME_OVER)
+3 -3
View File
@@ -25,15 +25,15 @@ static func find_first_child_of_type(node: Node, type: Variant) -> Node:
# Returns true if decendant is a decendant node of root, false otherwise. Returns false if either node is null.
static func is_node_decendant_of(decendant: Node, root: Node) -> bool:
if not decendant or not root:
print("Helper.is_node_decendant_of: decendant or root is null")
SweetLogger.debug("is_node_decendant_of: decendant or root is null", [], "helper.gd", "is_node_decendant_of")
return false
var current: Node = decendant
while current:
if current == root:
print("Helper.is_node_decendant_of: decendant %s is a descendant of root %s" % [decendant.name, root.name])
SweetLogger.debug("is_node_decendant_of: decendant {0} is a descendant of root {1}", [decendant.name, root.name], "helper.gd", "is_node_decendant_of")
return true
current = current.get_parent()
print("Helper.is_node_decendant_of: decendant %s is NOT a descendant of root %s" % [decendant.name, root.name])
SweetLogger.debug("is_node_decendant_of: decendant {0} is NOT a descendant of root {1}", [decendant.name, root.name], "helper.gd", "is_node_decendant_of")
return false
+5 -2
View File
@@ -22,6 +22,9 @@ XRToolsRumbleManager="*uid://by853dk86g1qw"
NetworkManager="*res://Net/network_manager.gd"
GlobalKeyEvents="*uid://c60unagog5oi1"
Signals="*uid://fuux6cxfjwdc"
GameManager="*uid://y4sol8l8vih1"
SweetLogger="*uid://cdfw054e8nhxv"
MoveWindows="*uid://de8j52uus1pbv"
[debug]
@@ -31,13 +34,13 @@ file_logging/enable_file_logging=true
window/size/viewport_width=1000
window/size/viewport_height=650
window/size/mode=2
window/size/initial_position_type=0
window/stretch/mode="canvas_items"
window/stretch/aspect="expand"
[editor_plugins]
enabled=PackedStringArray("res://addons/godot-xr-tools/plugin.cfg", "res://addons/kanban_tasks/plugin.cfg", "res://addons/yaml/plugin.cfg")
enabled=PackedStringArray("res://addons/godot-xr-tools/plugin.cfg", "res://addons/kanban_tasks/plugin.cfg", "res://addons/sweet-logger/plugin.cfg", "res://addons/yaml/plugin.cfg")
[filesystem]
-1
View File
@@ -1 +0,0 @@
uid://be8o8m6xpp3kc
+30 -30
View File
@@ -86,7 +86,7 @@ var _frame_index := 0
func _ready() -> void:
var args := OS.get_cmdline_user_args()
var args := OS.get_cmdline_args()
_auto_mode = "--mptest" in args
# Opt-in only. This node also sits in the real multiplayer scene (so a client
# joining a test session has a driver), and must be completely inert during
@@ -176,10 +176,10 @@ var _despawn_timers_seen := {}
# Items live either baked in the scene root or, once spawned at runtime, under
# WorldContent. Look in both.
func _find(name: String) -> Node3D:
var n := _world.get_node_or_null(name)
func _find(node_name: String) -> Node3D:
var n := _world.get_node_or_null(node_name)
if not n:
n = _world.get_node_or_null("WorldContent/" + name)
n = _world.get_node_or_null("WorldContent/" + node_name)
return n as Node3D
@@ -788,7 +788,7 @@ func _setup_frames() -> void:
_frames_enabled = false
_log("frame capture disabled: a headless run has no rendered output to grab")
return
var role := "server" if "--server" in OS.get_cmdline_user_args() else "client"
var role := "server" if "--server" in OS.get_cmdline_args() else "client"
_frames_dir = "res://logs/mptest_frames_%s" % role
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(_frames_dir))
# Clear out a previous run's frames, or the GIF would splice the two together.
@@ -965,10 +965,10 @@ func _snapshot() -> Dictionary:
for child in root.get_children():
if child is XRToolsPickable and not child.is_queued_for_deletion():
out[str(child.name)] = _describe(child)
for name in WATCHED_STATIONS:
var station := _find(name)
for station_name in WATCHED_STATIONS:
var station := _find(station_name)
if station:
out["station:" + name] = _describe_station(station)
out["station:" + station_name] = _describe_station(station)
return out
@@ -1016,37 +1016,37 @@ func _fetch_client_snapshot() -> Dictionary:
# Compare the server's view with the client's, returning a list of differences.
func _compare(server: Dictionary, client: Dictionary) -> Array[String]:
var problems: Array[String] = []
for name in server:
if not client.has(name):
problems.append("'%s' exists on the server but NOT on the client" % name)
for name in client:
if not server.has(name):
problems.append("'%s' exists on the client but NOT on the server (ghost copy)" % name)
for name in server:
if not client.has(name):
for entity_name in server:
if not client.has(entity_name):
problems.append("'%s' exists on the server but NOT on the client" % entity_name)
for entity_name in client:
if not server.has(entity_name):
problems.append("'%s' exists on the client but NOT on the server (ghost copy)" % entity_name)
for entity_name in server:
if not client.has(entity_name):
continue
var s: Dictionary = server[name]
var c: Dictionary = client[name]
var s: Dictionary = server[entity_name]
var c: Dictionary = client[entity_name]
# Stations are compared on their displayed state, not a position.
if s.has("pos") and c.has("pos"):
var dist: float = (s["pos"] as Vector3).distance_to(c["pos"])
if dist > SYNC_POS_TOLERANCE:
problems.append("%s is %.3fm apart (server %s vs client %s)" % [name, dist, s["pos"], c["pos"]])
problems.append("%s is %.3fm apart (server %s vs client %s)" % [entity_name, dist, s["pos"], c["pos"]])
for key in s:
# "_" keys are per-peer diagnostics, not things that must match.
if key == "pos" or key.begins_with("_"):
continue
if s[key] != c[key]:
problems.append("%s.%s: server=%s client=%s" % [name, key, s[key], c[key]])
problems.append("%s.%s: server=%s client=%s" % [entity_name, key, s[key], c[key]])
# Absolute invariants, checked per peer. A cross-peer diff can't catch a
# fault that happens identically on both sides.
for peer_name in ["server", "client"]:
var snap: Dictionary = server if peer_name == "server" else client
for name in snap:
var d: Dictionary = snap[name]
for entity_name in snap:
var d: Dictionary = snap[entity_name]
if d.has("visuals_attached") and not d["visuals_attached"]:
problems.append("on the %s, %s's food has come off the plate (%.3fm from it, limit %.2f)"
% [peer_name, name, d.get("_visual_offset", -1.0), MAX_VISUAL_OFFSET])
% [peer_name, entity_name, d.get("_visual_offset", -1.0), MAX_VISUAL_OFFSET])
return problems
@@ -1368,17 +1368,17 @@ func _assert_debug_camera() -> void:
func _check_framing() -> void:
var size := get_viewport().get_visible_rect().size
var offscreen: Array[String] = []
for name in ["Counter2", "Counter", "Hob", "Sink", "DirtStation", "Plate"]:
var n := _find(name)
for station_name in ["Counter2", "Counter", "Hob", "Sink", "DirtStation", "Plate"]:
var n := _find(station_name)
if not n:
continue
var p := _debug_cam.unproject_position(n.global_position)
var frac := Vector2(p.x / size.x, p.y / size.y)
var on := not _debug_cam.is_position_behind(n.global_position) \
and frac.x > 0.02 and frac.x < 0.98 and frac.y > 0.02 and frac.y < 0.98
_log(" framing: %-12s at %.2f,%.2f of frame%s" % [name, frac.x, frac.y, "" if on else " <-- OFF SCREEN"])
_log(" framing: %-12s at %.2f,%.2f of frame%s" % [station_name, frac.x, frac.y, "" if on else " <-- OFF SCREEN"])
if not on:
offscreen.append(name)
offscreen.append(station_name)
if offscreen.is_empty():
_log(" framing: all test objects are in view")
else:
@@ -1410,7 +1410,7 @@ func _banner(s: String) -> void:
# would otherwise fight over one file (the engine's own godot.log has exactly
# that problem when two peers run at once — it rotates per process).
func _open_log() -> void:
var role := "server" if "--server" in OS.get_cmdline_user_args() else "client"
var role := "server" if "--server" in OS.get_cmdline_args() else "client"
var path := "res://logs/mptest_%s.log" % role
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path("res://logs"))
_log_file = FileAccess.open(path, FileAccess.WRITE)
@@ -1419,11 +1419,11 @@ func _open_log() -> void:
path = OS.get_environment("TEMP").path_join("vryhungry_mptest_%s.log" % role)
_log_file = FileAccess.open(path, FileAccess.WRITE)
_log_path = ProjectSettings.globalize_path(path)
print("[MPTEST] step log -> %s" % _log_path)
SweetLogger.info("[MPTEST] step log -> {0}", [_log_path], "mp_test_driver.gd", "_open_log")
func _log(s: String) -> void:
print("[MPTEST %s] %s" % [_role, s])
SweetLogger.info("[MPTEST {0}] {1}", [_role, s], "mp_test_driver.gd", "_log")
if _log_file:
_log_file.store_line(s)
_log_file.flush()
+5 -5
View File
@@ -42,7 +42,7 @@ func _run() -> void:
_clear_previous_results(logs_dir)
print_rich("[b]Running the multiplayer test suite...[/b]")
print(" the editor will be unresponsive until it finishes (~2 minutes)")
SweetLogger.info("the editor will be unresponsive until it finishes (~2 minutes)", [], "run_tests_in_editor.gd", "_run")
var server_pid := _launch(exe, project_dir, ["--server"], SERVER_SCENE)
if server_pid <= 0:
@@ -61,7 +61,7 @@ func _run() -> void:
OS.delay_msec(500)
waited += 0.5
if OS.is_process_running(server_pid):
print(" timed out after %ds, stopping the instances" % TIMEOUT_SEC)
SweetLogger.warning("timed out after {0}s, stopping the instances", [TIMEOUT_SEC], "run_tests_in_editor.gd", "_run")
OS.kill(server_pid)
if OS.is_process_running(client_pid):
OS.kill(client_pid)
@@ -108,7 +108,7 @@ func _print_report(logs_dir: String) -> void:
push_error("No report at %s — the run did not finish. Check logs/mptest_server.log" % path)
return
var text := FileAccess.get_file_as_string(path)
print("")
SweetLogger.info("", [], "run_tests_in_editor.gd", "_print_report")
# Colour the summary so a failure is obvious in the Output panel.
for line in text.split("\n"):
if line.begins_with("FAIL") or line.contains("RESULT: FAILED"):
@@ -118,8 +118,8 @@ func _print_report(logs_dir: String) -> void:
elif line.begins_with("PASS"):
print_rich("[color=gray]%s[/color]" % line)
else:
print(line)
print("report: %s" % path)
SweetLogger.info("{0}", [line], "run_tests_in_editor.gd", "_print_report")
SweetLogger.info("report: {0}", [path], "run_tests_in_editor.gd", "_print_report")
func _build_gif(project_dir: String, logs_dir: String) -> void:
+2 -2
View File
@@ -3,8 +3,8 @@ extends Node
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
print(get_stations())
print(get_items())
SweetLogger.debug("stations: {0}", [get_stations()], "testworldLoad.gd", "_ready")
SweetLogger.debug("items: {0}", [get_items()], "testworldLoad.gd", "_ready")