netfishing/gathering/gathering_controller.gd

247 lines
6.6 KiB
GDScript

class_name GatheringController
extends Node3D
const FishingShopStockType = preload("res://economy/fishing_shop_stock.gd")
const FishingSpotType = preload("res://fishing/fishing_spot.gd")
const NetworkWorldSpawnServiceType = preload(
"res://network/network_world_spawn_service.gd"
)
signal status_changed(message: String)
@export_range(0.5, 5.0, 0.05) var marker_distance: float = 1.7
@export_range(0.1, 2.0, 0.05) var marker_radius: float = 0.7
@export_flags_3d_physics var terrain_collision_mask: int = 1
@export_range(0.5, 20.0, 0.5) var ray_height: float = 5.0
@export_range(0.5, 20.0, 0.5) var ray_depth: float = 8.0
var _player: Player
var _bag: PlayerBag
var _hotbar: PlayerHotbar
var _fishing_spot: FishingSpotType
var _service: NetworkWorldSpawnServiceType
var _marker: MeshInstance3D
var _marker_invalid_material: StandardMaterial3D
var _marker_valid_material: StandardMaterial3D
var _marker_position: Vector3
var _marker_has_surface: bool = false
var _target_entity_id: String = ""
var _charging: bool = false
var _charge_elapsed: float = 0.0
var _charge_duration: float = 2.0
var _request_id: String = ""
var _gameplay_input_enabled: bool = false
func _ready() -> void:
_build_marker()
set_process_unhandled_input(true)
func setup(
player: Player,
bag: PlayerBag,
hotbar: PlayerHotbar,
fishing_spot: FishingSpotType,
service: NetworkWorldSpawnServiceType,
) -> void:
_player = player
_bag = bag
_hotbar = hotbar
_fishing_spot = fishing_spot
_service = service
_service.local_interaction_finished.connect(
_on_interaction_finished
)
func set_gameplay_input_enabled(enabled: bool) -> void:
_gameplay_input_enabled = enabled
if not enabled:
_cancel_charge()
func is_net_selected() -> bool:
return (
_bag != null
and _hotbar != null
and _hotbar.get_selected_item_id()
== FishingShopStockType.CRAB_NET_ID
and _bag.owns_item(FishingShopStockType.CRAB_NET_ID)
)
func _process(delta: float) -> void:
var input_available: bool = (
_gameplay_input_enabled
and is_net_selected()
and _player != null
and _player.is_local_control_enabled()
and _fishing_spot != null
and _fishing_spot.can_change_hotbar_selection()
)
if not input_available:
if _charging:
_cancel_charge()
_set_marker_visible(false)
return
if not _charging:
_set_marker_visible(false)
return
_charge_elapsed += delta
_update_marker_target()
_update_target_entity()
var target_entry: GatherableData = _service.get_entry_for_entity(
_target_entity_id
)
if target_entry != null:
_charge_duration = target_entry.charge_duration
var fully_charged: bool = _charge_elapsed >= _charge_duration
var valid_target: bool = (
fully_charged
and not _target_entity_id.is_empty()
and _player.is_sneaking()
)
_marker.material_override = (
_marker_valid_material if valid_target else _marker_invalid_material
)
_set_marker_visible(_marker_has_surface)
func _unhandled_input(event: InputEvent) -> void:
if not event.is_action("fish_primary"):
return
if event.is_pressed():
if (
_charging
or not _gameplay_input_enabled
or not is_net_selected()
or _fishing_spot == null
or not _fishing_spot.can_change_hotbar_selection()
):
return
_request_id = _service.begin_local_interaction()
if _request_id.is_empty():
return
_charging = true
_charge_elapsed = 0.0
_charge_duration = maxf(
_service.get_charge_duration_for_tool(
FishingShopStockType.CRAB_NET_ID
),
0.1,
)
_update_marker_target()
_update_target_entity()
get_viewport().set_input_as_handled()
return
if not _charging:
return
var fully_charged: bool = _charge_elapsed >= _charge_duration
if (
fully_charged
and _marker_has_surface
and not _target_entity_id.is_empty()
and _player.is_sneaking()
):
_service.finish_local_interaction(
_request_id,
_target_entity_id,
_marker_position,
)
else:
_service.cancel_local_interaction(_request_id)
status_changed.emit(
"Sneak close, pull the net all the way back, and line up the marker."
)
_reset_charge_state()
get_viewport().set_input_as_handled()
func _update_marker_target() -> void:
_marker_has_surface = false
if _player == null or get_world_3d() == null:
return
var facing: Vector3 = _player.get_facing_direction()
facing.y = 0.0
if facing.is_zero_approx():
facing = -_player.global_basis.z
facing.y = 0.0
if facing.is_zero_approx():
return
facing = facing.normalized()
var query_center: Vector3 = _player.global_position + facing * marker_distance
var query := PhysicsRayQueryParameters3D.create(
query_center + Vector3.UP * ray_height,
query_center - Vector3.UP * ray_depth,
terrain_collision_mask,
)
query.collide_with_areas = false
query.collide_with_bodies = true
var hit: Dictionary = get_world_3d().direct_space_state.intersect_ray(query)
if hit.is_empty():
return
var position: Variant = hit.get("position")
if typeof(position) != TYPE_VECTOR3:
return
_marker_position = (position as Vector3) + Vector3.UP * 0.045
_marker.global_position = _marker_position
_marker_has_surface = true
func _update_target_entity() -> void:
_target_entity_id = (
_service.find_capture_target(
_marker_position,
FishingShopStockType.CRAB_NET_ID,
)
if _marker_has_surface and _service != null
else ""
)
func _cancel_charge() -> void:
if _charging and not _request_id.is_empty() and _service != null:
_service.cancel_local_interaction(_request_id)
_reset_charge_state()
func _reset_charge_state() -> void:
_charging = false
_charge_elapsed = 0.0
_request_id = ""
_target_entity_id = ""
_marker_has_surface = false
_set_marker_visible(false)
func _on_interaction_finished(accepted: bool, message: String) -> void:
_reset_charge_state()
if not accepted or not message.is_empty():
status_changed.emit(message)
func _build_marker() -> void:
_marker_invalid_material = StandardMaterial3D.new()
_marker_invalid_material.albedo_color = Color(0.86, 0.24, 0.19, 0.9)
_marker_invalid_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
_marker_valid_material = StandardMaterial3D.new()
_marker_valid_material.albedo_color = Color(0.25, 0.9, 0.36, 0.95)
_marker_valid_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
var ring := TorusMesh.new()
ring.inner_radius = marker_radius * 0.78
ring.outer_radius = marker_radius
ring.rings = 16
ring.ring_segments = 24
_marker = MeshInstance3D.new()
_marker.name = "GatheringTargetMarker"
_marker.mesh = ring
_marker.material_override = _marker_invalid_material
_marker.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
_marker.visible = false
add_child(_marker)
func _set_marker_visible(value: bool) -> void:
if _marker != null:
_marker.visible = value