forked from xiangwang25/whale-town-front-v2
feat: add nearby social interactions
This commit is contained in:
@@ -37,13 +37,13 @@ const MAP_CONFIGS: Dictionary = {
|
||||
"YSortWorld",
|
||||
],
|
||||
"markers": [
|
||||
{"label": "码头", "icon": "anchor", "pos": Vector2(0.160, 0.395), "side": "right"},
|
||||
{"label": "总部", "icon": "home", "pos": Vector2(0.505, 0.188), "side": "right"},
|
||||
{"label": "广场", "icon": "whale", "pos": Vector2(0.505, 0.515), "side": "right"},
|
||||
{"label": "小屋", "icon": "home", "pos": Vector2(0.830, 0.400), "side": "right"},
|
||||
{"label": "工坊", "icon": "tool", "pos": Vector2(0.752, 0.760), "side": "left"},
|
||||
{"label": "公告", "icon": "notice", "pos": Vector2(0.288, 0.838), "side": "right"},
|
||||
{"label": "入口", "icon": "gate", "pos": Vector2(0.500, 0.900), "side": "right"},
|
||||
{"label": "码头", "destinationId": "square_dock", "icon": "anchor", "pos": Vector2(0.160, 0.395), "side": "right"},
|
||||
{"label": "总部", "destinationId": "square_headquarters", "icon": "home", "pos": Vector2(0.505, 0.188), "side": "right"},
|
||||
{"label": "广场", "destinationId": "square_center", "icon": "whale", "pos": Vector2(0.505, 0.515), "side": "right"},
|
||||
{"label": "小屋", "destinationId": "square_cottage", "icon": "home", "pos": Vector2(0.830, 0.400), "side": "right"},
|
||||
{"label": "工坊", "destinationId": "square_workshop", "icon": "tool", "pos": Vector2(0.752, 0.760), "side": "left"},
|
||||
{"label": "公告", "destinationId": "square_notice", "icon": "notice", "pos": Vector2(0.288, 0.838), "side": "right"},
|
||||
{"label": "入口", "destinationId": "square_work_zone_gate", "icon": "gate", "pos": Vector2(0.500, 0.900), "side": "right"},
|
||||
],
|
||||
},
|
||||
"WorkZone": {
|
||||
@@ -57,13 +57,13 @@ const MAP_CONFIGS: Dictionary = {
|
||||
"YSortWorld",
|
||||
],
|
||||
"markers": [
|
||||
{"label": "商城", "icon": "home", "pos": Vector2(0.500, 0.180), "side": "right"},
|
||||
{"label": "咖啡店", "icon": "home", "pos": Vector2(0.092, 0.620), "side": "right"},
|
||||
{"label": "任务", "icon": "notice", "pos": Vector2(0.304, 0.600), "side": "right"},
|
||||
{"label": "课程", "icon": "notice", "pos": Vector2(0.694, 0.500), "side": "left"},
|
||||
{"label": "AI站", "icon": "tool", "pos": Vector2(0.676, 0.785), "side": "left"},
|
||||
{"label": "鲸币", "icon": "whale", "pos": Vector2(0.920, 0.785), "side": "left"},
|
||||
{"label": "入口", "icon": "gate", "pos": Vector2(0.500, 0.900), "side": "right"},
|
||||
{"label": "商城", "destinationId": "work_mall", "icon": "home", "pos": Vector2(0.500, 0.180), "side": "right"},
|
||||
{"label": "咖啡店", "destinationId": "work_cafe_gate", "icon": "home", "pos": Vector2(0.092, 0.620), "side": "right"},
|
||||
{"label": "任务", "destinationId": "work_jobs", "icon": "notice", "pos": Vector2(0.304, 0.600), "side": "right"},
|
||||
{"label": "课程", "destinationId": "work_courses", "icon": "notice", "pos": Vector2(0.694, 0.500), "side": "left"},
|
||||
{"label": "AI站", "destinationId": "work_ai", "icon": "tool", "pos": Vector2(0.676, 0.785), "side": "left"},
|
||||
{"label": "鲸币", "destinationId": "work_exchange", "icon": "whale", "pos": Vector2(0.920, 0.785), "side": "left"},
|
||||
{"label": "入口", "destinationId": "work_entrance", "icon": "gate", "pos": Vector2(0.500, 0.900), "side": "right"},
|
||||
],
|
||||
},
|
||||
"CafeInterior": {
|
||||
@@ -74,11 +74,19 @@ const MAP_CONFIGS: Dictionary = {
|
||||
"CafeServiceHallBase",
|
||||
],
|
||||
"markers": [
|
||||
{"label": "服务台", "icon": "whale", "pos": Vector2(0.500, 0.465), "side": "right"},
|
||||
{"label": "陪伴区", "icon": "notice", "pos": Vector2(0.240, 0.280), "side": "right"},
|
||||
{"label": "出口", "icon": "gate", "pos": Vector2(0.500, 0.855), "side": "right"},
|
||||
{"label": "服务台", "destinationId": "cafe_counter", "icon": "whale", "pos": Vector2(0.500, 0.465), "side": "right"},
|
||||
{"label": "陪伴区", "destinationId": "cafe_companion", "icon": "notice", "pos": Vector2(0.240, 0.280), "side": "right"},
|
||||
{"label": "出口", "destinationId": "cafe_entrance", "icon": "gate", "pos": Vector2(0.500, 0.855), "side": "right"},
|
||||
],
|
||||
},
|
||||
"PersonalSpace": {
|
||||
"title": "我的房间",
|
||||
"worldSize": Vector2(1024, 768),
|
||||
"sourceNodes": [
|
||||
"RoomBase",
|
||||
],
|
||||
"markers": [],
|
||||
},
|
||||
}
|
||||
|
||||
var _panel: PanelContainer
|
||||
@@ -90,8 +98,13 @@ var _mapCamera: Camera2D
|
||||
var _titleLabel: Label
|
||||
var _markerNodes: Array[Control] = []
|
||||
var _isOpen: bool = false
|
||||
var _travelDestinations: Dictionary = {}
|
||||
var _travelLocked: bool = false
|
||||
var _destinationMenu: PopupMenu
|
||||
var _destinationMenuIds: Dictionary = {}
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("whaletown_escape_dismissible")
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_build_ui()
|
||||
set_panel_open(false)
|
||||
@@ -107,6 +120,8 @@ func _notification(what: int) -> void:
|
||||
_anchor_panel()
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if get_viewport().is_input_handled():
|
||||
return
|
||||
if not _isOpen:
|
||||
return
|
||||
if event is InputEventKey:
|
||||
@@ -123,10 +138,20 @@ func set_panel_open(open: bool) -> void:
|
||||
_panel.visible = _isOpen
|
||||
if _isOpen:
|
||||
_rebuild_minimap_world()
|
||||
_load_travel_destinations()
|
||||
|
||||
func is_panel_open() -> bool:
|
||||
return _isOpen
|
||||
|
||||
func is_escape_dismissible() -> bool:
|
||||
return _isOpen
|
||||
|
||||
func get_escape_priority() -> int:
|
||||
return 600
|
||||
|
||||
func request_escape_close() -> void:
|
||||
set_panel_open(false)
|
||||
|
||||
func toggle_panel() -> void:
|
||||
set_panel_open(not _isOpen)
|
||||
|
||||
@@ -152,6 +177,9 @@ func _build_ui() -> void:
|
||||
|
||||
content.add_child(_build_header())
|
||||
content.add_child(_build_map_view())
|
||||
_destinationMenu = PopupMenu.new()
|
||||
_destinationMenu.id_pressed.connect(_on_destination_menu_selected)
|
||||
add_child(_destinationMenu)
|
||||
|
||||
func _build_header() -> Control:
|
||||
var header := HBoxContainer.new()
|
||||
@@ -173,6 +201,20 @@ func _build_header() -> Control:
|
||||
title.add_theme_font_size_override("font_size", 24)
|
||||
header.add_child(title)
|
||||
|
||||
var destinationButton := Button.new()
|
||||
destinationButton.text = "目的地"
|
||||
destinationButton.tooltip_text = "查看全部快速传送目的地"
|
||||
destinationButton.custom_minimum_size = Vector2(76, 38)
|
||||
destinationButton.focus_mode = Control.FOCUS_NONE
|
||||
destinationButton.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||||
destinationButton.add_theme_font_size_override("font_size", 14)
|
||||
destinationButton.add_theme_color_override("font_color", ACCENT_COLOR)
|
||||
destinationButton.add_theme_stylebox_override("normal", _create_round_style(Color(0.902, 0.962, 0.995, 1.0), 14))
|
||||
destinationButton.add_theme_stylebox_override("hover", _create_round_style(Color(0.818, 0.925, 0.980, 1.0), 14))
|
||||
destinationButton.add_theme_stylebox_override("focus", StyleBoxEmpty.new())
|
||||
destinationButton.pressed.connect(func() -> void: _show_destination_menu(destinationButton))
|
||||
header.add_child(destinationButton)
|
||||
|
||||
var closeButton := Button.new()
|
||||
closeButton.text = "×"
|
||||
closeButton.tooltip_text = "关闭地图"
|
||||
@@ -263,6 +305,16 @@ func _create_marker(marker: Dictionary) -> Control:
|
||||
button.add_theme_stylebox_override("hover", _create_marker_style(Color(0.925, 0.966, 0.996, 0.98), ACCENT_COLOR))
|
||||
button.add_theme_stylebox_override("pressed", _create_marker_style(Color(0.858, 0.925, 0.980, 0.98), ACCENT_COLOR.darkened(0.05)))
|
||||
button.add_theme_stylebox_override("focus", StyleBoxEmpty.new())
|
||||
var destinationId := str(marker.get("destinationId", "")).strip_edges()
|
||||
var destination: Dictionary = _travelDestinations.get(destinationId, {})
|
||||
var unlocked := destinationId.is_empty() or _travelDestinations.is_empty() or bool(destination.get("unlocked", false))
|
||||
button.disabled = not unlocked or _travelLocked
|
||||
button.tooltip_text = "点击快速传送" if unlocked else "首次到访后解锁"
|
||||
if not unlocked:
|
||||
button.modulate = Color(0.66, 0.69, 0.72, 0.72)
|
||||
button.pressed.connect(func() -> void: return)
|
||||
else:
|
||||
button.pressed.connect(func() -> void: _request_travel(destinationId))
|
||||
|
||||
var row := HBoxContainer.new()
|
||||
row.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
@@ -281,7 +333,7 @@ func _create_marker(marker: Dictionary) -> Control:
|
||||
|
||||
var label := Label.new()
|
||||
label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
label.text = str(marker.get("label", "地点"))
|
||||
label.text = ("🔒 " if not unlocked else "") + str(marker.get("label", "地点"))
|
||||
label.add_theme_color_override("font_color", TEXT_COLOR)
|
||||
label.add_theme_font_size_override("font_size", 14)
|
||||
row.add_child(label)
|
||||
@@ -295,6 +347,117 @@ func _create_marker(marker: Dictionary) -> Control:
|
||||
)
|
||||
return button
|
||||
|
||||
func _load_travel_destinations() -> void:
|
||||
var api := get_node_or_null("/root/ApiClient")
|
||||
if api == null:
|
||||
return
|
||||
api.call("get_json", "/world/travel-destinations", func(success: bool, response: Dictionary, _error: Dictionary) -> void:
|
||||
if not success:
|
||||
return
|
||||
var data_variant: Variant = response.get("data", [])
|
||||
if not (data_variant is Array):
|
||||
return
|
||||
_travelDestinations.clear()
|
||||
for entry in data_variant as Array:
|
||||
if entry is Dictionary:
|
||||
var destination: Dictionary = entry
|
||||
_travelDestinations[str(destination.get("id", ""))] = destination
|
||||
_render_markers()
|
||||
, true)
|
||||
|
||||
func _show_destination_menu(origin: Control) -> void:
|
||||
if not is_instance_valid(_destinationMenu):
|
||||
return
|
||||
_destinationMenu.clear()
|
||||
_destinationMenuIds.clear()
|
||||
if _travelDestinations.is_empty():
|
||||
_destinationMenu.add_item("正在读取目的地…", 0)
|
||||
_destinationMenu.set_item_disabled(0, true)
|
||||
else:
|
||||
var destinations: Array[Dictionary] = []
|
||||
for destination_variant in _travelDestinations.values():
|
||||
if destination_variant is Dictionary:
|
||||
destinations.append(destination_variant as Dictionary)
|
||||
destinations.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
|
||||
var map_order := {"whale_port": 0, "work_zone": 1, "whale_cafe": 2, "personal_space": 3}
|
||||
var order_a := int(map_order.get(str(a.get("mapId", "")), 9))
|
||||
var order_b := int(map_order.get(str(b.get("mapId", "")), 9))
|
||||
if order_a != order_b:
|
||||
return order_a < order_b
|
||||
return str(a.get("label", "")).naturalnocasecmp_to(str(b.get("label", ""))) < 0
|
||||
)
|
||||
var current_map := ""
|
||||
var menu_id := 1
|
||||
for destination in destinations:
|
||||
var map_id := str(destination.get("mapId", ""))
|
||||
if map_id != current_map:
|
||||
if not current_map.is_empty():
|
||||
_destinationMenu.add_separator()
|
||||
_destinationMenu.add_item("— %s —" % _map_label(map_id), menu_id)
|
||||
_destinationMenu.set_item_disabled(_destinationMenu.item_count - 1, true)
|
||||
menu_id += 1
|
||||
current_map = map_id
|
||||
var unlocked := bool(destination.get("unlocked", false))
|
||||
_destinationMenu.add_item(("" if unlocked else "🔒 ") + str(destination.get("label", "地点")), menu_id)
|
||||
_destinationMenu.set_item_disabled(_destinationMenu.item_count - 1, not unlocked or _travelLocked)
|
||||
_destinationMenuIds[menu_id] = str(destination.get("id", ""))
|
||||
menu_id += 1
|
||||
_destinationMenu.reset_size()
|
||||
_destinationMenu.position = Vector2i(origin.get_screen_position() + Vector2(0.0, origin.size.y))
|
||||
_destinationMenu.popup()
|
||||
|
||||
func _on_destination_menu_selected(menu_id: int) -> void:
|
||||
var destination_id := str(_destinationMenuIds.get(menu_id, ""))
|
||||
if not destination_id.is_empty():
|
||||
_request_travel(destination_id)
|
||||
|
||||
func _request_travel(destinationId: String) -> void:
|
||||
if destinationId.is_empty() or _travelLocked:
|
||||
return
|
||||
var socialManager := get_node_or_null("/root/SocialManager")
|
||||
if socialManager == null or not socialManager.has_method("request_travel"):
|
||||
return
|
||||
_travelLocked = true
|
||||
_render_markers()
|
||||
socialManager.call("request_travel", destinationId, Callable(self, "_on_travel_authorized"))
|
||||
|
||||
func _on_travel_authorized(success: bool, destination: Dictionary) -> void:
|
||||
if not success:
|
||||
await get_tree().create_timer(1.0).timeout
|
||||
_travelLocked = false
|
||||
_render_markers()
|
||||
return
|
||||
var mapId := str(destination.get("mapId", ""))
|
||||
var position := _scene_position_for_destination(mapId, Vector2(float(destination.get("x", 0.0)), float(destination.get("y", 0.0))))
|
||||
var sceneName: String = str({"whale_port": "square", "work_zone": "work_zone", "whale_cafe": "cafe_interior", "personal_space": "personal_space"}.get(mapId, ""))
|
||||
if sceneName.is_empty():
|
||||
await get_tree().create_timer(1.0).timeout
|
||||
_travelLocked = false
|
||||
_render_markers()
|
||||
return
|
||||
set_panel_open(false)
|
||||
SceneManager.set_next_destination_id(str(destination.get("id", "")))
|
||||
SceneManager.set_next_scene_position(position)
|
||||
SceneManager.change_scene(sceneName)
|
||||
|
||||
func _scene_position_for_destination(map_id: String, map_position: Vector2) -> Vector2:
|
||||
# 服务端旅行目录使用小地图坐标;游戏场景则以地图中心为原点。
|
||||
var origin := {
|
||||
"whale_port": Vector2(1280, 960),
|
||||
"work_zone": Vector2(1280, 960),
|
||||
"whale_cafe": Vector2(768, 512),
|
||||
"personal_space": Vector2(768, 512),
|
||||
}.get(map_id, Vector2.ZERO) as Vector2
|
||||
return map_position - origin
|
||||
|
||||
func _map_label(map_id: String) -> String:
|
||||
return {
|
||||
"whale_port": "中心广场",
|
||||
"work_zone": "打工区",
|
||||
"whale_cafe": "鲸鱼咖啡馆",
|
||||
"personal_space": "我的房间",
|
||||
}.get(map_id, map_id)
|
||||
|
||||
func _anchor_panel() -> void:
|
||||
_panel.set_anchors_preset(Control.PRESET_TOP_RIGHT)
|
||||
_panel.offset_left = -PANEL_SIZE.x - PANEL_MARGIN_RIGHT
|
||||
|
||||
Reference in New Issue
Block a user