Refactor Table to use new SharedInventoryStation
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
class_name SharedInventoryStation
|
||||
extends Station
|
||||
|
||||
### Abstract 'class', should never be instantiated ###
|
||||
# Backbone for stations that need to know about neighboring stations of the
|
||||
# same kind (tables, belts).
|
||||
|
||||
## Path to one Area3D probe per direction this station should watch for a
|
||||
## same-kind neighbor. Wired explicitly per concrete scene (e.g. a belt
|
||||
## wires one path, a table wires four).
|
||||
@export var probe_paths: Array[NodePath] = []
|
||||
|
||||
## Resolved from probe_paths in ready().
|
||||
var probes: Array[Area3D] = []
|
||||
|
||||
## Area3D probe -> the SharedInventoryStation neighbor detected through it
|
||||
## (or null if that direction is currently unoccupied).
|
||||
var neighbors: Dictionary = {}
|
||||
|
||||
|
||||
# The child station has to call super._enter_tree() for this to be called
|
||||
func _enter_tree() -> void:
|
||||
super._enter_tree()
|
||||
var properties: Array[NodePath] = [".:position", ".:rotation", ".:visible"]
|
||||
for property_path in properties:
|
||||
sync_config.add_property(property_path)
|
||||
sync_config.property_set_spawn(property_path, false)
|
||||
sync_config.property_set_replication_mode(property_path, SceneReplicationConfig.REPLICATION_MODE_ON_CHANGE)
|
||||
|
||||
|
||||
# The child station has to call super.ready() for this to be called
|
||||
func ready() -> void:
|
||||
super.ready()
|
||||
# Find probes
|
||||
for path in probe_paths:
|
||||
var probe := get_node_or_null(path) as Area3D
|
||||
if probe:
|
||||
probes.append(probe)
|
||||
else:
|
||||
SweetLogger.warning("{0} could not resolve neighbor probe at path {1}", [name, path])
|
||||
if probes.is_empty():
|
||||
SweetLogger.warning("{0} has no neighbor probes wired", [name])
|
||||
for probe in probes:
|
||||
neighbors[probe] = null
|
||||
probe.body_entered.connect(_on_probe_body_entered.bind(probe))
|
||||
probe.body_exited.connect(_on_probe_body_exited.bind(probe))
|
||||
# Init leader state before any probe signal has a chance to fire
|
||||
_broadcast_group_changed.call_deferred()
|
||||
|
||||
|
||||
## Cycle-safe search of the neighbor graph (handles circular belt/table loops)
|
||||
func get_connected_group() -> Array[SharedInventoryStation]:
|
||||
var visited: Dictionary = {get_instance_id(): true}
|
||||
var queue: Array[SharedInventoryStation] = [self]
|
||||
var group: Array[SharedInventoryStation] = []
|
||||
while not queue.is_empty():
|
||||
var current: SharedInventoryStation = queue.pop_front()
|
||||
group.append(current)
|
||||
for neighbor in current.neighbors.values():
|
||||
if neighbor and not visited.has(neighbor.get_instance_id()):
|
||||
visited[neighbor.get_instance_id()] = true
|
||||
queue.append(neighbor)
|
||||
return group
|
||||
|
||||
|
||||
## Deterministic leader: smallest grid position, computed the same on every peer
|
||||
func is_group_leader() -> bool:
|
||||
var leader: SharedInventoryStation = self
|
||||
for member in get_connected_group():
|
||||
if _grid_sort_key(member) < _grid_sort_key(leader):
|
||||
leader = member
|
||||
return leader == self
|
||||
|
||||
|
||||
static func _grid_sort_key(station: SharedInventoryStation) -> Vector2i:
|
||||
var pos := station.global_position
|
||||
return Vector2i(roundi(pos.x / Helper.SNAP_GRID_SIZE), roundi(pos.z / Helper.SNAP_GRID_SIZE))
|
||||
|
||||
|
||||
## Food items this station holds, exposed for neighbors to read; override for multi-zone stations
|
||||
func get_exposed_food_items() -> Array[FoodItem]:
|
||||
var items: Array[FoodItem] = []
|
||||
if snap_zone and snap_zone.picked_up_object:
|
||||
var food_item := Helper.find_food_item(snap_zone.picked_up_object)
|
||||
if food_item:
|
||||
items.append(food_item)
|
||||
return items
|
||||
|
||||
|
||||
## Every food item held anywhere in this station's connected group
|
||||
func get_group_food_items() -> Array[FoodItem]:
|
||||
var items: Array[FoodItem] = []
|
||||
for member in get_connected_group():
|
||||
items.append_array(member.get_exposed_food_items())
|
||||
return items
|
||||
|
||||
|
||||
func _on_probe_body_entered(body: Node3D, probe: Area3D) -> void:
|
||||
var station := body as SharedInventoryStation
|
||||
if not station or station == self:
|
||||
return
|
||||
neighbors[probe] = station
|
||||
_broadcast_group_changed()
|
||||
|
||||
|
||||
func _on_probe_body_exited(body: Node3D, probe: Area3D) -> void:
|
||||
var former: SharedInventoryStation = neighbors.get(probe)
|
||||
if not former or body != former:
|
||||
return
|
||||
neighbors[probe] = null
|
||||
# former is no longer reachable from self, so it needs telling separately
|
||||
former._broadcast_group_changed()
|
||||
_broadcast_group_changed()
|
||||
|
||||
|
||||
## Notifies every member of this station's current connected group (not just the two that changed)
|
||||
func _broadcast_group_changed() -> void:
|
||||
for member in get_connected_group():
|
||||
member._on_group_changed()
|
||||
|
||||
|
||||
## Virtual hook: override to react to this station's group membership/leadership changing
|
||||
func _on_group_changed() -> void:
|
||||
pass
|
||||
@@ -0,0 +1 @@
|
||||
uid://bciln4f4tjwgy
|
||||
+12
-7
@@ -25,6 +25,17 @@ var enabled: bool: # When disabled the station only updated display and sounds.
|
||||
snap_zone.set_process(p_enabled)
|
||||
|
||||
|
||||
# Godot requires replication_config to be fully built before _ready. So in _enter_tree
|
||||
# The child station has to call super._enter_tree() for this to be called.
|
||||
func _enter_tree() -> void:
|
||||
# Always a fresh config - abstract/station.tscn's config sub-resource is shared
|
||||
# in memory across every station scene that instances it, so reusing it here
|
||||
# would pile every station type's properties onto the same shared object.
|
||||
synchronizer.root_path = get_path()
|
||||
synchronizer.replication_config = SceneReplicationConfig.new()
|
||||
sync_config = synchronizer.replication_config
|
||||
|
||||
|
||||
# The child station has to call super.ready() for this to be called
|
||||
func ready() -> void:
|
||||
SweetLogger.debug("Station {0} ready", [name])
|
||||
@@ -36,18 +47,12 @@ func ready() -> void:
|
||||
SweetLogger.warning("{0} missing snap_zone reference", [name])
|
||||
if not synchronizer:
|
||||
SweetLogger.warning("{0} missing synchronizer reference", [name])
|
||||
# Disable if we're not the server.
|
||||
# Disable if we're not the server.
|
||||
if not NetworkManager.owns_world():
|
||||
enabled = false
|
||||
|
||||
snap_zone.has_picked_up.connect(_on_object_picked_up_handler)
|
||||
snap_zone.has_dropped.connect(_on_object_dropped_handler)
|
||||
|
||||
# Configure Multiplayer Syncronizer
|
||||
# This overwrites any changes made in the inspector.
|
||||
synchronizer.root_path = get_path()
|
||||
synchronizer.replication_config = SceneReplicationConfig.new()
|
||||
sync_config = synchronizer.replication_config
|
||||
|
||||
# The child station has to call super.process(delta) for this to be called
|
||||
func process(_delta: float) -> void:
|
||||
|
||||
@@ -19,21 +19,22 @@ var current_work: float # How far a FoodItem conversion is toward completion
|
||||
var max_work: float # How much work has to be acheived to trigger conversion
|
||||
var result_id: String # Station active if not empty. What FoodItem id the conversion turns the current FoodItem into.
|
||||
|
||||
# The child station has to call super._enter_tree() for this to be called
|
||||
func _enter_tree() -> void:
|
||||
super._enter_tree()
|
||||
var properties: Array[NodePath] = [".:current_work", ".:max_work", ".:result_id"]
|
||||
for property_path in properties:
|
||||
sync_config.add_property(property_path)
|
||||
sync_config.property_set_spawn(property_path, false)
|
||||
sync_config.property_set_replication_mode(property_path, SceneReplicationConfig.REPLICATION_MODE_ON_CHANGE)
|
||||
|
||||
|
||||
# The child station has to call super.ready() for this to be called
|
||||
func ready() -> void:
|
||||
super.ready()
|
||||
if not progress_bar:
|
||||
SweetLogger.warning("{0} missing progress_bar reference", [name])
|
||||
|
||||
# Configure Multiplayer Syncronizer
|
||||
# Base Station creates the config, here we append to it:
|
||||
var properties: Array[NodePath] = [ ".:current_work", ".:max_work", ".:result_id"]
|
||||
for property_path in properties:
|
||||
sync_config.add_property(property_path)
|
||||
sync_config.property_set_spawn(property_path, false)
|
||||
sync_config.property_set_replication_mode(property_path, SceneReplicationConfig.REPLICATION_MODE_ON_CHANGE)
|
||||
|
||||
|
||||
|
||||
# The child station has to call super.process(delta) for this to be called
|
||||
func process(delta: float) -> void:
|
||||
|
||||
Reference in New Issue
Block a user