457 lines
13 KiB
GDScript
457 lines
13 KiB
GDScript
class_name HomeDecorController
|
|
extends Node
|
|
|
|
const DecorCatalogType = preload("res://homes/decor_catalog.gd")
|
|
const DRAG_RESPONSE: float = 22.0
|
|
const DRAG_POSITION_EPSILON: float = 0.005
|
|
|
|
var _player: Player
|
|
var _hotbar: PlayerHotbar
|
|
var _bag: PlayerBag
|
|
var _home_state: PlayerHomeState
|
|
var _network_home: NetworkHomeService
|
|
var _home_world: HomeWorldService
|
|
var _fishing_spot: FishingSpot
|
|
var _toolbar: HomeDecorToolbar
|
|
var _mode: PlayerHomeState.PlacementMode = PlayerHomeState.PlacementMode.GRID
|
|
var _selected_instance_id: String = ""
|
|
var _drag_instance_id: String = ""
|
|
var _drag_owner_fingerprint: String = ""
|
|
var _drag_pointer_offset: Vector3 = Vector3.ZERO
|
|
var _drag_original_position: Vector3 = Vector3.ZERO
|
|
var _drag_original_yaw: float = 0.0
|
|
var _drag_target_position: Vector3 = Vector3.ZERO
|
|
var _drag_target_yaw: float = 0.0
|
|
var _drag_target_valid: bool = false
|
|
|
|
|
|
func setup(
|
|
player: Player,
|
|
hotbar: PlayerHotbar,
|
|
bag: PlayerBag,
|
|
home_state: PlayerHomeState,
|
|
network_home: NetworkHomeService,
|
|
home_world: HomeWorldService,
|
|
fishing_spot: FishingSpot,
|
|
toolbar: HomeDecorToolbar,
|
|
) -> void:
|
|
_player = player
|
|
_hotbar = hotbar
|
|
_bag = bag
|
|
_home_state = home_state
|
|
_network_home = network_home
|
|
_home_world = home_world
|
|
_fishing_spot = fishing_spot
|
|
_toolbar = toolbar
|
|
if not _network_home.local_space_changed.is_connected(
|
|
_on_local_space_changed
|
|
):
|
|
_network_home.local_space_changed.connect(_on_local_space_changed)
|
|
if not _home_state.changed.is_connected(_on_home_changed):
|
|
_home_state.changed.connect(_on_home_changed)
|
|
if not _toolbar.placement_mode_requested.is_connected(
|
|
_on_placement_mode_requested
|
|
):
|
|
_toolbar.placement_mode_requested.connect(
|
|
_on_placement_mode_requested
|
|
)
|
|
_toolbar.rotate_requested.connect(_rotate_selected)
|
|
_toolbar.store_requested.connect(_store_selected)
|
|
_toolbar.cancel_requested.connect(_clear_selection)
|
|
_toolbar.set_mode(_mode)
|
|
_refresh_toolbar()
|
|
|
|
|
|
func begin_decorating() -> void:
|
|
_refresh_toolbar()
|
|
if _toolbar.visible:
|
|
_toolbar.report_status(
|
|
"use held decor to place it; click or drag placed decor"
|
|
)
|
|
|
|
|
|
func _input(event: InputEvent) -> void:
|
|
if _drag_instance_id.is_empty():
|
|
return
|
|
var pointer_motion := event as InputEventMouseMotion
|
|
if pointer_motion != null:
|
|
_update_drag(pointer_motion.position)
|
|
get_viewport().set_input_as_handled()
|
|
return
|
|
var pointer_button := event as InputEventMouseButton
|
|
if (
|
|
pointer_button != null
|
|
and pointer_button.button_index == MOUSE_BUTTON_LEFT
|
|
and not pointer_button.pressed
|
|
):
|
|
_finish_drag()
|
|
get_viewport().set_input_as_handled()
|
|
|
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
|
var pointer_button := event as InputEventMouseButton
|
|
if (
|
|
pointer_button != null
|
|
and pointer_button.button_index == MOUSE_BUTTON_LEFT
|
|
):
|
|
if not pointer_button.pressed or not _can_decorate():
|
|
return
|
|
if _begin_drag(pointer_button.position):
|
|
get_viewport().set_input_as_handled()
|
|
return
|
|
var pointer_decor_id: StringName = _get_selected_decor_id()
|
|
if (
|
|
not pointer_decor_id.is_empty()
|
|
and _bag.get_quantity(pointer_decor_id) > 0
|
|
):
|
|
_place_carried_decor(pointer_decor_id)
|
|
get_viewport().set_input_as_handled()
|
|
return
|
|
if not _can_decorate() or not event.is_action_pressed("fish_primary"):
|
|
return
|
|
if event is InputEventKey and event.echo:
|
|
return
|
|
var decor_id: StringName = _get_selected_decor_id()
|
|
if not decor_id.is_empty() and _bag.get_quantity(decor_id) > 0:
|
|
_place_carried_decor(decor_id)
|
|
get_viewport().set_input_as_handled()
|
|
|
|
|
|
func _process(delta: float) -> void:
|
|
if _drag_instance_id.is_empty():
|
|
return
|
|
if not Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
|
|
_finish_drag()
|
|
return
|
|
if not _drag_target_valid:
|
|
return
|
|
if not _can_decorate():
|
|
_cancel_drag()
|
|
return
|
|
var current: Dictionary = _home_world.get_decor_preview_transform(
|
|
_drag_owner_fingerprint, _drag_instance_id
|
|
)
|
|
if current.is_empty():
|
|
_cancel_drag()
|
|
return
|
|
var current_position: Vector3 = current["position"]
|
|
var current_yaw: float = float(current["yaw"])
|
|
var blend: float = 1.0 - exp(-DRAG_RESPONSE * delta)
|
|
_home_world.set_decor_preview_transform(
|
|
_drag_owner_fingerprint,
|
|
_drag_instance_id,
|
|
current_position.lerp(_drag_target_position, blend),
|
|
lerp_angle(current_yaw, _drag_target_yaw, blend),
|
|
)
|
|
|
|
|
|
func _begin_drag(screen_position: Vector2) -> bool:
|
|
var owner_fingerprint: String = (
|
|
_network_home.get_local_home_owner_fingerprint()
|
|
)
|
|
var camera: Camera3D = _player.get_active_gameplay_camera()
|
|
var clicked_instance: String = _home_world.find_decor_instance_at_screen(
|
|
owner_fingerprint, camera, screen_position
|
|
)
|
|
if clicked_instance.is_empty():
|
|
return false
|
|
var placement: Dictionary = _home_state.get_placement(clicked_instance)
|
|
if placement.is_empty():
|
|
return false
|
|
var original_position: Vector3 = _placement_position(placement)
|
|
if not original_position.is_finite():
|
|
return false
|
|
if not _home_world.set_decor_manipulation_active(
|
|
owner_fingerprint, clicked_instance, true
|
|
):
|
|
return false
|
|
var pointer_position: Vector3 = _home_world.get_floor_position_from_screen(
|
|
owner_fingerprint, camera, screen_position
|
|
)
|
|
_drag_instance_id = clicked_instance
|
|
_drag_owner_fingerprint = owner_fingerprint
|
|
_drag_original_position = original_position
|
|
_drag_original_yaw = float(placement.get("yaw", 0.0))
|
|
_drag_target_position = original_position
|
|
_drag_target_yaw = _drag_original_yaw
|
|
_drag_target_valid = true
|
|
_drag_pointer_offset = (
|
|
original_position - pointer_position
|
|
if pointer_position.is_finite()
|
|
else Vector3.ZERO
|
|
)
|
|
_drag_pointer_offset.y = 0.0
|
|
_select_instance(clicked_instance)
|
|
_toolbar.report_status("drag decor, then release to place it")
|
|
return true
|
|
|
|
|
|
func _update_drag(screen_position: Vector2) -> void:
|
|
if not _can_decorate():
|
|
_cancel_drag()
|
|
return
|
|
var camera: Camera3D = _player.get_active_gameplay_camera()
|
|
var pointer_position: Vector3 = _home_world.get_floor_position_from_screen(
|
|
_drag_owner_fingerprint, camera, screen_position
|
|
)
|
|
if not pointer_position.is_finite():
|
|
# Keep the most recent valid target when the pointer briefly leaves the
|
|
# RV floor. Releasing there should place at that target, not throw the
|
|
# decor all the way back to where the drag began.
|
|
_toolbar.report_status("keep the pointer over the RV floor")
|
|
return
|
|
var placement: Dictionary = _home_state.get_placement(_drag_instance_id)
|
|
if placement.is_empty():
|
|
_clear_selection()
|
|
return
|
|
var candidate: Vector3 = pointer_position + _drag_pointer_offset
|
|
candidate.y = 0.0
|
|
var normalized: Dictionary = _network_home.get_valid_local_decor_transform(
|
|
_drag_instance_id,
|
|
candidate,
|
|
float(placement.get("yaw", _drag_original_yaw)),
|
|
_mode,
|
|
)
|
|
if normalized.is_empty():
|
|
# A densely furnished area may have no nearby solution. Preserve the
|
|
# last valid preview so the interaction remains stable while the player
|
|
# moves the pointer toward open space.
|
|
_toolbar.report_status("holding at the nearest available position")
|
|
return
|
|
_drag_target_position = normalized["position"]
|
|
_drag_target_yaw = float(normalized["yaw"])
|
|
_drag_target_valid = true
|
|
_toolbar.report_status("release to place decor")
|
|
|
|
|
|
func _finish_drag() -> void:
|
|
var instance_id: String = _drag_instance_id
|
|
var owner_fingerprint: String = _drag_owner_fingerprint
|
|
var original_position: Vector3 = _drag_original_position
|
|
var original_yaw: float = _drag_original_yaw
|
|
var target_position: Vector3 = _drag_target_position
|
|
var target_yaw: float = _drag_target_yaw
|
|
var target_valid: bool = _drag_target_valid
|
|
var placement: Dictionary = _home_state.get_placement(instance_id)
|
|
var decor_id := StringName(str(placement.get("decor_id", "")))
|
|
if (
|
|
not target_valid
|
|
or decor_id.is_empty()
|
|
or not _home_world.is_decor_placement_clear_of_players(
|
|
owner_fingerprint, decor_id, target_position, target_yaw
|
|
)
|
|
):
|
|
_home_world.set_decor_preview_transform(
|
|
owner_fingerprint,
|
|
instance_id,
|
|
original_position,
|
|
original_yaw,
|
|
)
|
|
_home_world.set_decor_manipulation_active(
|
|
owner_fingerprint, instance_id, false
|
|
)
|
|
_reset_drag_state()
|
|
_toolbar.report_status(
|
|
"a player is in the way"
|
|
if target_valid
|
|
else "that space is not available"
|
|
)
|
|
return
|
|
_home_world.set_decor_preview_transform(
|
|
owner_fingerprint, instance_id, target_position, target_yaw
|
|
)
|
|
if (
|
|
original_position.distance_to(target_position)
|
|
<= DRAG_POSITION_EPSILON
|
|
and is_zero_approx(angle_difference(original_yaw, target_yaw))
|
|
):
|
|
_home_world.set_decor_manipulation_active(
|
|
owner_fingerprint, instance_id, false
|
|
)
|
|
_reset_drag_state()
|
|
_select_instance(instance_id)
|
|
return
|
|
if _network_home.update_local_decor(
|
|
instance_id, target_position, target_yaw, _mode
|
|
):
|
|
_home_world.set_decor_manipulation_active(
|
|
owner_fingerprint, instance_id, false
|
|
)
|
|
_reset_drag_state()
|
|
_toolbar.report_status("decor moved")
|
|
return
|
|
_home_world.set_decor_preview_transform(
|
|
owner_fingerprint,
|
|
instance_id,
|
|
original_position,
|
|
original_yaw,
|
|
)
|
|
_home_world.set_decor_manipulation_active(
|
|
owner_fingerprint, instance_id, false
|
|
)
|
|
_reset_drag_state()
|
|
_toolbar.report_status("that space is not available")
|
|
|
|
|
|
func _cancel_drag() -> void:
|
|
if _drag_instance_id.is_empty():
|
|
return
|
|
_home_world.set_decor_preview_transform(
|
|
_drag_owner_fingerprint,
|
|
_drag_instance_id,
|
|
_drag_original_position,
|
|
_drag_original_yaw,
|
|
)
|
|
_home_world.set_decor_manipulation_active(
|
|
_drag_owner_fingerprint, _drag_instance_id, false
|
|
)
|
|
_reset_drag_state()
|
|
|
|
|
|
func _reset_drag_state() -> void:
|
|
_drag_instance_id = ""
|
|
_drag_owner_fingerprint = ""
|
|
_drag_pointer_offset = Vector3.ZERO
|
|
_drag_original_position = Vector3.ZERO
|
|
_drag_original_yaw = 0.0
|
|
_drag_target_position = Vector3.ZERO
|
|
_drag_target_yaw = 0.0
|
|
_drag_target_valid = false
|
|
|
|
|
|
static func _placement_position(placement: Dictionary) -> Vector3:
|
|
if placement.is_empty():
|
|
return Vector3.INF
|
|
var values: Array = placement.get("position", [])
|
|
if values.size() != 3:
|
|
return Vector3.INF
|
|
return Vector3(
|
|
float(values[0]), float(values[1]), float(values[2])
|
|
)
|
|
|
|
|
|
func _place_carried_decor(decor_id: StringName) -> void:
|
|
var owner_fingerprint: String = (
|
|
_network_home.get_local_home_owner_fingerprint()
|
|
)
|
|
var forward: Vector3 = -_player.global_basis.z
|
|
forward.y = 0.0
|
|
forward = forward.normalized()
|
|
var global_position: Vector3 = _player.global_position + forward * 1.45
|
|
var local_position: Vector3 = _home_world.interior_global_to_local(
|
|
owner_fingerprint, global_position
|
|
)
|
|
var instance_id: String = _network_home.place_local_decor(
|
|
decor_id, local_position, _player.global_rotation.y, _mode
|
|
)
|
|
if instance_id.is_empty():
|
|
_toolbar.report_status("make room in front of you to place this decor")
|
|
return
|
|
_select_instance(instance_id)
|
|
_toolbar.report_status("decor placed")
|
|
|
|
|
|
func _select_instance(instance_id: String) -> void:
|
|
var placement: Dictionary = _home_state.get_placement(instance_id)
|
|
if placement.is_empty():
|
|
_clear_selection()
|
|
return
|
|
_selected_instance_id = instance_id
|
|
var decor_id := StringName(str(placement.get("decor_id", "")))
|
|
_toolbar.set_selection(
|
|
instance_id, DecorCatalogType.get_display_name(decor_id)
|
|
)
|
|
|
|
|
|
func _rotate_selected() -> void:
|
|
if not _can_decorate() or _selected_instance_id.is_empty():
|
|
return
|
|
var placement: Dictionary = _home_state.get_placement(
|
|
_selected_instance_id
|
|
)
|
|
if placement.is_empty():
|
|
_clear_selection()
|
|
return
|
|
var values: Array = placement.get("position", [])
|
|
if values.size() != 3:
|
|
return
|
|
var position := Vector3(
|
|
float(values[0]), float(values[1]), float(values[2])
|
|
)
|
|
if _network_home.update_local_decor(
|
|
_selected_instance_id,
|
|
position,
|
|
float(placement.get("yaw", 0.0)) + PI * 0.5,
|
|
_mode,
|
|
):
|
|
_toolbar.report_status("decor rotated")
|
|
else:
|
|
_toolbar.report_status("not enough room to rotate here")
|
|
|
|
|
|
func _store_selected() -> void:
|
|
if _selected_instance_id.is_empty():
|
|
return
|
|
if _network_home.remove_local_decor(_selected_instance_id):
|
|
_clear_selection()
|
|
_toolbar.report_status("decor packed into storage")
|
|
else:
|
|
_toolbar.report_status("inventory and storage are full")
|
|
|
|
|
|
func _clear_selection() -> void:
|
|
_cancel_drag()
|
|
_selected_instance_id = ""
|
|
if _toolbar != null:
|
|
_toolbar.set_selection("")
|
|
|
|
|
|
func _on_placement_mode_requested(mode: int) -> void:
|
|
_mode = mode as PlayerHomeState.PlacementMode
|
|
|
|
|
|
func _on_local_space_changed(
|
|
_space_id: StringName,
|
|
_owner_fingerprint: String,
|
|
) -> void:
|
|
_clear_selection()
|
|
_refresh_toolbar()
|
|
|
|
|
|
func _on_home_changed() -> void:
|
|
if (
|
|
not _selected_instance_id.is_empty()
|
|
and _home_state.get_placement(_selected_instance_id).is_empty()
|
|
):
|
|
_clear_selection()
|
|
_refresh_toolbar()
|
|
|
|
|
|
func _refresh_toolbar() -> void:
|
|
if _toolbar == null:
|
|
return
|
|
_toolbar.set_active(_can_decorate())
|
|
if not _toolbar.visible:
|
|
_clear_selection()
|
|
|
|
|
|
func _can_decorate() -> bool:
|
|
return (
|
|
_player != null
|
|
and _network_home != null
|
|
and _network_home.can_local_decorate()
|
|
and _fishing_spot != null
|
|
and _fishing_spot.can_open_fishing_shop()
|
|
and _player.is_movement_enabled()
|
|
)
|
|
|
|
|
|
func _get_selected_decor_id() -> StringName:
|
|
if (
|
|
_hotbar == null
|
|
or _hotbar.get_selected_assignment_kind()
|
|
!= PlayerHotbar.AssignmentKind.ITEM
|
|
):
|
|
return StringName()
|
|
var item_id: StringName = _hotbar.get_selected_item_id()
|
|
return item_id if DecorCatalogType.has_product(item_id) else StringName()
|