Split Station into WorkStation abstract script
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
class_name Station
|
||||
extends Node3D
|
||||
|
||||
### Abstract 'class', should never be instantiated ###
|
||||
# - Keep all logic all stations share.
|
||||
# - Contain as much of the networking code syncing stations as possible.
|
||||
|
||||
## Sounds
|
||||
@export var pickup_sound: AudioStream
|
||||
@export var drop_sound: AudioStream
|
||||
|
||||
## Node references. These are all required snd should be set in the inspector
|
||||
@export var snap_zone: XRToolsSnapZone
|
||||
@export var synchronizer: MultiplayerSynchronizer
|
||||
|
||||
var sync_config: SceneReplicationConfig
|
||||
|
||||
var enabled: bool: # When disabled the station only updated display and sounds.
|
||||
get: return snap_zone.enabled
|
||||
set(p_enabled):
|
||||
SweetLogger.info("Set enabled {0} on station {1} on client {2}", [p_enabled, name, multiplayer.get_unique_id()])
|
||||
snap_zone.enabled = p_enabled
|
||||
snap_zone.set_process(p_enabled)
|
||||
|
||||
|
||||
# The child station has to call super.ready() for this to be called
|
||||
func ready() -> void:
|
||||
SweetLogger.debug("Station {0} ready", [name])
|
||||
if not snap_zone:
|
||||
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.
|
||||
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:
|
||||
refresh_display()
|
||||
|
||||
|
||||
## Signal handles ##
|
||||
func _on_object_picked_up_handler(item: Node3D) -> void:
|
||||
if not NetworkManager.owns_world():
|
||||
return
|
||||
_clients_on_object_picked_up_handler.rpc(item.get_path())
|
||||
_apply_object_picked_up(item)
|
||||
|
||||
|
||||
func _on_object_dropped_handler(item: Node3D) -> void:
|
||||
if not NetworkManager.owns_world():
|
||||
return
|
||||
_clients_on_object_dropped_handler.rpc(item.get_path())
|
||||
_apply_object_dropped(item)
|
||||
|
||||
|
||||
func _apply_object_picked_up(item: Node3D) -> void:
|
||||
SweetLogger.debug("Pickup: {0}", [item.name])
|
||||
on_object_picked_up(item)
|
||||
var food_item = Helper.find_food_item(item)
|
||||
if food_item:
|
||||
on_food_item_picked_up(food_item)
|
||||
|
||||
|
||||
func _apply_object_dropped(item: Node3D) -> void:
|
||||
SweetLogger.debug("Drop: {0}", [item.name])
|
||||
on_object_dropped(item)
|
||||
var food_item = Helper.find_food_item(item)
|
||||
if food_item:
|
||||
on_food_item_dropped(food_item)
|
||||
|
||||
|
||||
# Propagate events to clients
|
||||
@rpc("authority", "call_remote", "reliable")
|
||||
func _clients_on_object_picked_up_handler(item_path: NodePath):
|
||||
var item := Helper.get_node_from_path(self, item_path) as Node3D
|
||||
if not item:
|
||||
return
|
||||
_apply_object_picked_up(item)
|
||||
|
||||
@rpc("authority", "call_remote", "reliable")
|
||||
func _clients_on_object_dropped_handler(item_path: NodePath):
|
||||
var item := Helper.get_node_from_path(self, item_path) as Node3D
|
||||
if not item:
|
||||
return
|
||||
_apply_object_dropped(item)
|
||||
|
||||
|
||||
|
||||
## Virutal methods
|
||||
# The child station implements these if it cares about them.
|
||||
func on_object_picked_up(_item: Node3D) -> void:
|
||||
SweetLogger.debug("Not implemented in {0}", [name])
|
||||
|
||||
func on_object_dropped(_item: Node3D) -> void:
|
||||
SweetLogger.debug("Not implemented in {0}", [name])
|
||||
|
||||
func on_food_item_picked_up(_food_item: FoodItem) -> void:
|
||||
SweetLogger.debug("Not implemented in {0}", [name])
|
||||
|
||||
func on_food_item_dropped(_food_item: FoodItem) -> void:
|
||||
SweetLogger.debug("Not implemented in {0}", [name])
|
||||
|
||||
|
||||
func refresh_display() -> void:
|
||||
pass
|
||||
@@ -0,0 +1 @@
|
||||
uid://1ii5ov17p2p6
|
||||
@@ -0,0 +1,23 @@
|
||||
[gd_scene format=3 uid="uid://cbs8jiqe8rcmn"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://cvucwxibtol5f" path="res://stations/StationMovement.tscn" id="1_yuo1j"]
|
||||
[ext_resource type="PackedScene" uid="uid://bxbocxdaayvwx" path="res://UI/progress_bar.tscn" id="2_be8yi"]
|
||||
[ext_resource type="PackedScene" uid="uid://ce7vysyvondf8" path="res://addons/godot-xr-tools/objects/snap_zone.tscn" id="3_be8yi"]
|
||||
|
||||
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_0o2ds"]
|
||||
|
||||
[node name="Station" type="Node3D" unique_id=707500902]
|
||||
|
||||
[node name="StationMovement" parent="." unique_id=946873975 instance=ExtResource("1_yuo1j")]
|
||||
|
||||
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." unique_id=1536666704]
|
||||
replication_config = SubResource("SceneReplicationConfig_0o2ds")
|
||||
|
||||
[node name="AudioStreamPlayer3DPulse" type="AudioStreamPlayer3D" parent="." unique_id=1976975302]
|
||||
|
||||
[node name="AudioStreamPlayer3DContinous" type="AudioStreamPlayer3D" parent="." unique_id=925832848]
|
||||
|
||||
[node name="ProgressBar3D" parent="." unique_id=654673176 instance=ExtResource("2_be8yi")]
|
||||
|
||||
[node name="SnapZone" parent="." unique_id=1315859105 groups=["station_zone"] instance=ExtResource("3_be8yi")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
||||
@@ -0,0 +1,128 @@
|
||||
class_name WorkStation
|
||||
extends Station
|
||||
|
||||
### Abstract 'class', should never be instantiated ###
|
||||
# This script serves two main jobs:
|
||||
# - Keep all logic all workstations (sink, hob...) share.
|
||||
# - Contain as much of the networking code syncing stations as possible.
|
||||
|
||||
## Sounds
|
||||
@export var process_sound: AudioStream
|
||||
@export var complete_sound: AudioStream
|
||||
|
||||
## Node references. These are all required snd should be set in the inspector
|
||||
@export var notification_audio: AudioStreamPlayer3D # Short sounds like pickup or complete
|
||||
@export var ambient_audio: AudioStreamPlayer3D # Releating sounds like a cooking noise
|
||||
@export var progress_bar: ProgressBar3D
|
||||
|
||||
|
||||
## Synced by MultiplayerSyncronizer ##
|
||||
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.ready() for this to be called
|
||||
func ready() -> void:
|
||||
super.ready()
|
||||
if not notification_audio:
|
||||
SweetLogger.warning("{0} missing notification_audio reference", [name])
|
||||
if not ambient_audio:
|
||||
SweetLogger.warning("{0} missing ambient_audio reference", [name])
|
||||
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:
|
||||
super.process(delta)
|
||||
#SweetLogger.debug("station process result_id: {0}, work: {1}, max_work: {2}", [result_id, current_work, max_work])
|
||||
refresh_display()
|
||||
|
||||
|
||||
# Propagate events to clients
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func _everyone_on_work_complete():
|
||||
on_work_complete()
|
||||
|
||||
|
||||
## Virutal methods ##
|
||||
# The child station implements these if it cares about them.
|
||||
func on_work_complete() -> void:
|
||||
SweetLogger.debug("Not implemented in {0}", [name])
|
||||
|
||||
|
||||
# The child station has to call super.refresh_display for this to be called
|
||||
func refresh_display() -> void:
|
||||
super.refresh_display()
|
||||
var is_active: bool = not result_id.is_empty()
|
||||
progress_bar.set_bar_visible(is_active)
|
||||
progress_bar.set_progress((current_work / max_work) if is_active else 0.0)
|
||||
|
||||
|
||||
## Public methods ##
|
||||
# Will be called by clients to the server, and server to server
|
||||
func add_work(work: float) -> void:
|
||||
SweetLogger.debug("Add work: {0} current: {1} max_work: {2}", [snappedf(work, 0.01), snappedf(current_work, 0.01), snappedf(max_work, 0.01)])
|
||||
if work <= 0:
|
||||
SweetLogger.warning("Work to add must be positive, work: {0}", [work])
|
||||
return
|
||||
if not NetworkManager.owns_world:
|
||||
server_add_work(work)
|
||||
return
|
||||
if not result_id or result_id.is_empty():
|
||||
SweetLogger.warning("Work can not be added when result_id is empty")
|
||||
return
|
||||
current_work += work
|
||||
if current_work >= max_work:
|
||||
_everyone_on_work_complete()
|
||||
|
||||
|
||||
@rpc("any_peer", "call_remote", "reliable")
|
||||
func server_add_work(work: float) -> void:
|
||||
add_work(work)
|
||||
|
||||
|
||||
# Converts the currenly held item into result_id, by despawning it and instantiating a new item
|
||||
func convert_item() -> Node3D:
|
||||
if not NetworkManager.owns_world():
|
||||
return
|
||||
SweetLogger.info("Convert item, result_id: {0}", [result_id])
|
||||
if not NetworkManager.owns_world:
|
||||
SweetLogger.warning("Only the server should ever call this!")
|
||||
return null
|
||||
if not result_id or result_id.is_empty():
|
||||
SweetLogger.warning("Station tried to convert item, but result_id is null or empty!")
|
||||
return
|
||||
var old_pickable = snap_zone.picked_up_object
|
||||
if not old_pickable:
|
||||
SweetLogger.warning("Station tried to convert item, but item is missing!")
|
||||
return null
|
||||
var target_id := result_id
|
||||
var original_transform = old_pickable.global_transform
|
||||
var new_scene_instance = NetworkManager.spawn_item(RecipeManager.get_item_scene(target_id).resource_path, original_transform)
|
||||
|
||||
reset()
|
||||
|
||||
# Drop and free the old item and pick up the new one
|
||||
SweetLogger.debug("Freeing old_pickable {0}", [old_pickable])
|
||||
snap_zone.drop_object()
|
||||
NetworkManager.despawn_item(old_pickable)
|
||||
snap_zone.pick_up_object(new_scene_instance)
|
||||
SweetLogger.info("Convert item into id: {0}, node name: {1}", [target_id, new_scene_instance.name])
|
||||
return new_scene_instance
|
||||
|
||||
|
||||
func reset() -> void:
|
||||
SweetLogger.debug("->[]")
|
||||
current_work = 0
|
||||
max_work = 0
|
||||
result_id = ""
|
||||
@@ -0,0 +1 @@
|
||||
uid://b25s3bhslrlxc
|
||||
Reference in New Issue
Block a user