extends Area2D class_name CafeCompanionTarget # ============================================================================ # CafeCompanionTarget.gd - 咖啡店陪伴机器人点击组件 # ============================================================================ # 挂在咖啡店 NPC 或被雇佣玩家节点下,负责把鼠标点击转成 # “选择陪伴机器人”事件;真正聊天必须由付费时长购买后开启。 # ============================================================================ @export var servicePointId: String = "" @export var companionId: String = "" @export var companionType: String = "npc" @export var personaName: String = "" @export var ownerUserId: String = "" @export var employmentEndsAt: String = "" @export var selfTarget: bool = false var _lastClickMsec: int = 0 func _ready() -> void: input_pickable = true set_process_unhandled_input(true) func _input_event(_viewport: Viewport, event: InputEvent, _shapeIdx: int) -> void: if not (event is InputEventMouseButton): return var mouseEvent := event as InputEventMouseButton if mouseEvent.button_index != MOUSE_BUTTON_LEFT or not mouseEvent.pressed: return get_viewport().set_input_as_handled() _try_emit_target_selected() func _unhandled_input(event: InputEvent) -> void: if not (event is InputEventMouseButton): return var mouseEvent := event as InputEventMouseButton if mouseEvent.button_index != MOUSE_BUTTON_LEFT or not mouseEvent.pressed: return if not _contains_global_point(get_global_mouse_position()): return get_viewport().set_input_as_handled() _try_emit_target_selected() func _try_emit_target_selected() -> void: var now := Time.get_ticks_msec() if _lastClickMsec > 0 and now - _lastClickMsec < 120: return _lastClickMsec = now _emit_target_selected() func _emit_target_selected() -> void: var eventSystem := get_node_or_null("/root/EventSystem") if eventSystem == null: return var payload := { "cafe_id": "whale_cafe", "service_point_id": servicePointId, "companion_id": companionId, "companion_type": companionType, "persona_name": _resolved_persona_name(), "owner_user_id": ownerUserId, "employment_ends_at": employmentEndsAt, "target_path": str(get_parent().get_path()) if get_parent() != null else "", } eventSystem.call("emit_event", EventNames.CAFE_COMPANION_SELF_SELECTED if selfTarget else EventNames.CAFE_COMPANION_SELECTED, payload) func _resolved_persona_name() -> String: var normalizedName := personaName.strip_edges() if not normalizedName.is_empty(): return normalizedName var parent := get_parent() if parent != null and parent.get("npcName") != null: return str(parent.get("npcName")).strip_edges() return companionId func _contains_global_point(globalPoint: Vector2) -> bool: for child in 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 if shape is CapsuleShape2D: var capsule := shape as CapsuleShape2D var halfHeight: float = maxf(0.0, capsule.height * 0.5 - capsule.radius) var clampedY: float = clampf(localPoint.y, -halfHeight, halfHeight) return Vector2(localPoint.x, localPoint.y - clampedY).length() <= capsule.radius return false