forked from xiangwang25/whale-town-front-v2
286 lines
12 KiB
GDScript
286 lines
12 KiB
GDScript
class_name CafeInterior
|
|
extends Node2D
|
|
|
|
# ============================================================================
|
|
# CafeInterior.gd - 鲸鱼咖啡馆室内场景控制器
|
|
# ============================================================================
|
|
# 承载咖啡馆挂机服务大厅,负责玩家出生点、相机边界和返回工作区。
|
|
# ============================================================================
|
|
|
|
const CAMERA_ZOOM: Vector2 = Vector2(1.65, 1.65)
|
|
const CAMERA_LIMIT_LEFT: int = -768
|
|
const CAMERA_LIMIT_TOP: int = -512
|
|
const CAMERA_LIMIT_RIGHT: int = 768
|
|
const CAMERA_LIMIT_BOTTOM: int = 512
|
|
const WORK_ZONE_CAFE_RETURN_POSITION: Vector2 = Vector2(-1093, 560)
|
|
const CAFE_DOOR_POSITION: Vector2 = Vector2(0, 392)
|
|
const WORLD_TEXT_THEME = preload("res://assets/ui/world_text_theme.tres")
|
|
const COMPANION_NAMEPLATE_RENDER_SCALE: float = 0.5
|
|
const COMPANION_NAMEPLATE_FONT_SIZE: int = 24
|
|
const COMPANION_NAMEPLATE_VISUAL_HEIGHT: int = 22
|
|
const COMPANION_NAMEPLATE_VISUAL_MIN_WIDTH: int = 76
|
|
const COMPANION_NAMEPLATE_VISUAL_MAX_WIDTH: int = 118
|
|
const COMPANION_NAMEPLATE_VISUAL_CHAR_WIDTH: int = 12
|
|
const NPC_NAMEPLATE_OFFSET_Y: float = -136.0
|
|
const HIRED_PLAYER_NAMEPLATE_OFFSET_Y: float = -96.0
|
|
|
|
@onready var player: PlayerController = $YSortWorld/Characters/Players/Player
|
|
@onready var playerCamera: Camera2D = $YSortWorld/Characters/Players/Player/Camera2D
|
|
@onready var hiredPlayerCompanionTarget: CafeCompanionTarget = $YSortWorld/Characters/Players/Player/CafeHiredCompanionTarget
|
|
@onready var hiredPlayerNameplate: Label = $YSortWorld/Characters/Players/Player/CafeHiredCompanionNameplate
|
|
@onready var exitToWorkZoneArea: Area2D = $InteractionAreas/ExitToWorkZoneArea
|
|
@onready var cafeRecruitmentLogoArea: Area2D = $InteractionAreas/CafeRecruitmentLogoArea
|
|
@onready var serviceIdlePoint01: Marker2D = $Markers/ServiceIdlePoints/ServiceIdlePoint01
|
|
@onready var cafeWhaleBaristaNpc: NPCController = $YSortWorld/Characters/CafeWhaleBaristaNpc
|
|
@onready var cafeCompanionNameplate: Label = $YSortWorld/Characters/CafeWhaleBaristaNpc/CafeCompanionNameplate
|
|
|
|
var _isChangingScene: bool = false
|
|
var _lastRecruitmentClickMsec: int = 0
|
|
|
|
func _ready() -> void:
|
|
_align_service_occupants()
|
|
_configure_static_companion_nameplates()
|
|
_apply_spawn_point()
|
|
_configure_camera()
|
|
_connect_exit_area()
|
|
_connect_recruitment_area()
|
|
_connect_cafe_companion_events()
|
|
|
|
func _exit_tree() -> void:
|
|
var eventSystem := get_node_or_null("/root/EventSystem")
|
|
if eventSystem != null:
|
|
eventSystem.call("disconnect_event", EventNames.CAFE_COMPANION_AGENT_REGISTERED, _on_cafe_companion_agent_registered, self)
|
|
eventSystem.call("disconnect_event", EventNames.CAFE_COMPANION_EMPLOYMENT_RESIGNED, _on_cafe_companion_employment_resigned, self)
|
|
|
|
func _align_service_occupants() -> void:
|
|
cafeWhaleBaristaNpc.global_position = serviceIdlePoint01.global_position
|
|
|
|
func _apply_spawn_point() -> void:
|
|
var spawnName: String = SceneManager.get_next_spawn_name()
|
|
var markerName: String = spawnName if not spawnName.is_empty() else "DefaultSpawn"
|
|
var marker := $Markers.get_node_or_null(markerName) as Marker2D
|
|
if marker == null:
|
|
marker = $Markers/DefaultSpawn
|
|
player.global_position = marker.global_position
|
|
|
|
func _configure_camera() -> void:
|
|
playerCamera.enabled = true
|
|
playerCamera.make_current()
|
|
playerCamera.zoom = CAMERA_ZOOM
|
|
playerCamera.position_smoothing_enabled = true
|
|
playerCamera.limit_left = CAMERA_LIMIT_LEFT
|
|
playerCamera.limit_top = CAMERA_LIMIT_TOP
|
|
playerCamera.limit_right = CAMERA_LIMIT_RIGHT
|
|
playerCamera.limit_bottom = CAMERA_LIMIT_BOTTOM
|
|
playerCamera.limit_smoothed = true
|
|
|
|
func _connect_exit_area() -> void:
|
|
if not exitToWorkZoneArea.body_entered.is_connected(_on_exit_area_body_entered):
|
|
exitToWorkZoneArea.body_entered.connect(_on_exit_area_body_entered)
|
|
|
|
func _connect_recruitment_area() -> void:
|
|
cafeRecruitmentLogoArea.input_pickable = true
|
|
if not cafeRecruitmentLogoArea.input_event.is_connected(_on_recruitment_area_input_event):
|
|
cafeRecruitmentLogoArea.input_event.connect(_on_recruitment_area_input_event)
|
|
set_process_unhandled_input(true)
|
|
|
|
func _connect_cafe_companion_events() -> void:
|
|
var eventSystem := get_node_or_null("/root/EventSystem")
|
|
if eventSystem == null:
|
|
return
|
|
eventSystem.call("connect_event", EventNames.CAFE_COMPANION_AGENT_REGISTERED, _on_cafe_companion_agent_registered, self)
|
|
eventSystem.call("connect_event", EventNames.CAFE_COMPANION_EMPLOYMENT_RESIGNED, _on_cafe_companion_employment_resigned, self)
|
|
|
|
func _on_exit_area_body_entered(body: Node2D) -> void:
|
|
if _isChangingScene or body != player:
|
|
return
|
|
_isChangingScene = true
|
|
SceneManager.set_next_scene_position(WORK_ZONE_CAFE_RETURN_POSITION)
|
|
SceneManager.change_scene("work_zone")
|
|
|
|
func _on_recruitment_area_input_event(_viewport: Viewport, event: InputEvent, _shapeIdx: int) -> void:
|
|
if not _is_primary_mouse_click(event):
|
|
return
|
|
get_viewport().set_input_as_handled()
|
|
_try_emit_recruitment_selected()
|
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
|
if not _is_primary_mouse_click(event):
|
|
return
|
|
if not _area_contains_global_point(cafeRecruitmentLogoArea, get_global_mouse_position()):
|
|
return
|
|
get_viewport().set_input_as_handled()
|
|
_try_emit_recruitment_selected()
|
|
|
|
func _try_emit_recruitment_selected() -> void:
|
|
var now := Time.get_ticks_msec()
|
|
if _lastRecruitmentClickMsec > 0 and now - _lastRecruitmentClickMsec < 120:
|
|
return
|
|
_lastRecruitmentClickMsec = now
|
|
_emit_recruitment_selected()
|
|
|
|
func _emit_recruitment_selected() -> void:
|
|
var eventSystem := get_node_or_null("/root/EventSystem")
|
|
if eventSystem == null:
|
|
return
|
|
eventSystem.call("emit_event", EventNames.CAFE_COMPANION_RECRUITMENT_SELECTED, {
|
|
"cafe_id": "whale_cafe",
|
|
"terminal_id": "front_reception_whale_logo",
|
|
"title": "咖啡店陪伴招聘",
|
|
"anchor_position": cafeRecruitmentLogoArea.global_position,
|
|
})
|
|
|
|
func _is_primary_mouse_click(event: InputEvent) -> bool:
|
|
if not (event is InputEventMouseButton):
|
|
return false
|
|
var mouseEvent := event as InputEventMouseButton
|
|
return mouseEvent.button_index == MOUSE_BUTTON_LEFT and mouseEvent.pressed
|
|
|
|
func _area_contains_global_point(area: Area2D, globalPoint: Vector2) -> bool:
|
|
for child in area.get_children():
|
|
var shapeNode := child as CollisionShape2D
|
|
if shapeNode == null or shapeNode.disabled or shapeNode.shape == null:
|
|
continue
|
|
if _shape_contains_point(shapeNode, globalPoint):
|
|
return true
|
|
return false
|
|
|
|
func _shape_contains_point(shapeNode: CollisionShape2D, globalPoint: Vector2) -> bool:
|
|
var localPoint := shapeNode.to_local(globalPoint)
|
|
var shape := shapeNode.shape
|
|
if shape is RectangleShape2D:
|
|
var rectangle := shape as RectangleShape2D
|
|
return Rect2(rectangle.size * -0.5, rectangle.size).has_point(localPoint)
|
|
if shape is CircleShape2D:
|
|
var circle := shape as CircleShape2D
|
|
return localPoint.length() <= circle.radius
|
|
return false
|
|
|
|
func _on_cafe_companion_agent_registered(data: Dictionary) -> void:
|
|
var companionData := _extract_companion_data(data)
|
|
if companionData.is_empty():
|
|
return
|
|
|
|
var servicePointId := str(companionData.get("service_point_id", "")).strip_edges()
|
|
var servicePointMarker := _service_point_marker(servicePointId)
|
|
if servicePointMarker == null:
|
|
push_warning("CafeInterior: 服务点不存在,无法摆放被雇佣玩家: %s" % servicePointId)
|
|
return
|
|
|
|
var personaName := str(companionData.get("persona_name", "陪伴机器人")).strip_edges()
|
|
player.global_position = servicePointMarker.global_position
|
|
player.velocity = Vector2.ZERO
|
|
player.lastDirection = "down"
|
|
player.set_movement_locked(true)
|
|
player.call("_play_idle_animation")
|
|
player.call("_update_world_sort_z")
|
|
_configure_hired_player_companion_target(companionData, servicePointId, personaName)
|
|
_configure_hired_player_nameplate(personaName)
|
|
|
|
func _on_cafe_companion_employment_resigned(_data: Dictionary) -> void:
|
|
player.set_movement_locked(false)
|
|
_disable_hired_player_companion_target()
|
|
hiredPlayerNameplate.visible = false
|
|
player.global_position = CAFE_DOOR_POSITION
|
|
player.velocity = Vector2.ZERO
|
|
player.lastDirection = "down"
|
|
player.call("_play_idle_animation")
|
|
player.call("_update_world_sort_z")
|
|
|
|
func _extract_companion_data(data: Dictionary) -> Dictionary:
|
|
var companionVariant: Variant = data.get("companion", {})
|
|
if companionVariant is Dictionary:
|
|
return companionVariant
|
|
if data.has("service_point_id"):
|
|
return data
|
|
return {}
|
|
|
|
func _service_point_marker(servicePointId: String) -> Marker2D:
|
|
if servicePointId.is_empty():
|
|
return null
|
|
return $Markers/ServiceIdlePoints.get_node_or_null(servicePointId) as Marker2D
|
|
|
|
func _configure_hired_player_companion_target(companionData: Dictionary, servicePointId: String, personaName: String) -> void:
|
|
hiredPlayerCompanionTarget.servicePointId = servicePointId
|
|
hiredPlayerCompanionTarget.companionId = str(companionData.get("id", "")).strip_edges()
|
|
if hiredPlayerCompanionTarget.companionId.is_empty():
|
|
hiredPlayerCompanionTarget.companionId = str(companionData.get("companion_id", "")).strip_edges()
|
|
hiredPlayerCompanionTarget.companionType = str(companionData.get("companion_type", "hired_player")).strip_edges()
|
|
hiredPlayerCompanionTarget.personaName = personaName
|
|
hiredPlayerCompanionTarget.ownerUserId = str(companionData.get("owner_user_id", "")).strip_edges()
|
|
hiredPlayerCompanionTarget.employmentEndsAt = str(companionData.get("employment_ends_at", "")).strip_edges()
|
|
hiredPlayerCompanionTarget.selfTarget = true
|
|
hiredPlayerCompanionTarget.input_pickable = true
|
|
|
|
var shapeNode := hiredPlayerCompanionTarget.get_node_or_null("CollisionShape2D") as CollisionShape2D
|
|
if shapeNode != null:
|
|
shapeNode.disabled = false
|
|
|
|
func _disable_hired_player_companion_target() -> void:
|
|
hiredPlayerCompanionTarget.input_pickable = false
|
|
hiredPlayerCompanionTarget.selfTarget = false
|
|
var shapeNode := hiredPlayerCompanionTarget.get_node_or_null("CollisionShape2D") as CollisionShape2D
|
|
if shapeNode != null:
|
|
shapeNode.disabled = true
|
|
|
|
func _configure_hired_player_nameplate(personaName: String) -> void:
|
|
_configure_companion_nameplate(hiredPlayerNameplate, personaName, HIRED_PLAYER_NAMEPLATE_OFFSET_Y)
|
|
hiredPlayerNameplate.visible = true
|
|
|
|
func _configure_static_companion_nameplates() -> void:
|
|
_configure_companion_nameplate(cafeCompanionNameplate, cafeWhaleBaristaNpc.npcName, NPC_NAMEPLATE_OFFSET_Y)
|
|
cafeCompanionNameplate.visible = true
|
|
|
|
func _configure_companion_nameplate(label: Label, personaName: String, offsetY: float) -> void:
|
|
var displayName := personaName.strip_edges()
|
|
if displayName.is_empty():
|
|
displayName = "陪伴机器人"
|
|
var visualWidth := _companion_nameplate_visual_width(displayName)
|
|
var renderWidth := ceili(float(visualWidth) / COMPANION_NAMEPLATE_RENDER_SCALE)
|
|
var renderHeight := ceili(float(COMPANION_NAMEPLATE_VISUAL_HEIGHT) / COMPANION_NAMEPLATE_RENDER_SCALE)
|
|
|
|
label.theme = WORLD_TEXT_THEME
|
|
label.text = displayName
|
|
label.visible = true
|
|
label.z_index = 30
|
|
label.scale = Vector2.ONE * COMPANION_NAMEPLATE_RENDER_SCALE
|
|
label.position = Vector2(float(visualWidth) * -0.5, offsetY)
|
|
label.custom_minimum_size = Vector2(renderWidth, renderHeight)
|
|
label.size = label.custom_minimum_size
|
|
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
label.clip_text = true
|
|
label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
|
label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
label.add_theme_color_override("font_color", Color(0.12, 0.20, 0.24, 1.0))
|
|
label.add_theme_color_override("font_shadow_color", Color(1.0, 1.0, 1.0, 0.85))
|
|
label.add_theme_constant_override("shadow_offset_x", 0)
|
|
label.add_theme_constant_override("shadow_offset_y", 1)
|
|
label.add_theme_font_size_override("font_size", COMPANION_NAMEPLATE_FONT_SIZE)
|
|
label.add_theme_stylebox_override("normal", _create_companion_nameplate_style())
|
|
|
|
func _companion_nameplate_visual_width(displayName: String) -> int:
|
|
var estimatedWidth := displayName.length() * COMPANION_NAMEPLATE_VISUAL_CHAR_WIDTH + 28
|
|
return clampi(estimatedWidth, COMPANION_NAMEPLATE_VISUAL_MIN_WIDTH, COMPANION_NAMEPLATE_VISUAL_MAX_WIDTH)
|
|
|
|
func _create_companion_nameplate_style() -> StyleBoxFlat:
|
|
var style := StyleBoxFlat.new()
|
|
style.bg_color = Color(1.0, 0.988, 0.955, 0.96)
|
|
style.border_color = Color(0.18, 0.34, 0.38, 0.92)
|
|
style.border_width_left = 2
|
|
style.border_width_top = 2
|
|
style.border_width_right = 2
|
|
style.border_width_bottom = 2
|
|
style.corner_radius_top_left = 12
|
|
style.corner_radius_top_right = 12
|
|
style.corner_radius_bottom_left = 12
|
|
style.corner_radius_bottom_right = 12
|
|
style.content_margin_left = 12
|
|
style.content_margin_top = 4
|
|
style.content_margin_right = 12
|
|
style.content_margin_bottom = 4
|
|
style.shadow_color = Color(0.05, 0.08, 0.09, 0.18)
|
|
style.shadow_size = 4
|
|
style.shadow_offset = Vector2(0, 2)
|
|
return style
|