Files
VRyHungry1/abstract/shared_inventory_station.gd
2026-08-17 14:40:59 +02:00

125 lines
4.3 KiB
GDScript

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