481 lines
22 KiB
GDScript
481 lines
22 KiB
GDScript
extends Node
|
|
|
|
# 社区资料、通知与旅行 API 的客户端协调器。
|
|
const ACCENT := Color("58c7db")
|
|
const PANEL_COLOR := Color(0.035, 0.071, 0.106, 0.97)
|
|
const DISCOVERY_DISTANCE: float = 180.0
|
|
const DISCOVERY_POINTS: Array[Dictionary] = [
|
|
{"id": "square_center", "mapId": "whale_port", "position": Vector2(0, 30)},
|
|
{"id": "square_dock", "mapId": "whale_port", "position": Vector2(-870, -202)},
|
|
{"id": "square_headquarters", "mapId": "whale_port", "position": Vector2(13, -600)},
|
|
{"id": "square_cottage", "mapId": "whale_port", "position": Vector2(845, -192)},
|
|
{"id": "square_workshop", "mapId": "whale_port", "position": Vector2(645, 500)},
|
|
{"id": "square_notice", "mapId": "whale_port", "position": Vector2(-542, 648)},
|
|
{"id": "square_work_zone_gate", "mapId": "whale_port", "position": Vector2(0, 775)},
|
|
{"id": "work_entrance", "mapId": "work_zone", "position": Vector2(0, 755)},
|
|
{"id": "work_mall", "mapId": "work_zone", "position": Vector2(0, -614)},
|
|
{"id": "work_cafe_gate", "mapId": "work_zone", "position": Vector2(-1044, 222)},
|
|
{"id": "work_jobs", "mapId": "work_zone", "position": Vector2(-502, 192)},
|
|
{"id": "work_courses", "mapId": "work_zone", "position": Vector2(496, 0)},
|
|
{"id": "work_ai", "mapId": "work_zone", "position": Vector2(452, 548)},
|
|
{"id": "work_exchange", "mapId": "work_zone", "position": Vector2(1075, 548)},
|
|
{"id": "cafe_entrance", "mapId": "whale_cafe", "position": Vector2(0, 363)},
|
|
{"id": "cafe_counter", "mapId": "whale_cafe", "position": Vector2(0, -37)},
|
|
{"id": "cafe_companion", "mapId": "whale_cafe", "position": Vector2(-398, -226)},
|
|
{"id": "personal_room", "mapId": "personal_space", "position": Vector2.ZERO},
|
|
]
|
|
|
|
var _canvas: CanvasLayer
|
|
var _profile_panel: PanelContainer
|
|
var _profile_content: VBoxContainer
|
|
var _notification_panel: PanelContainer
|
|
var _notification_content: VBoxContainer
|
|
var _current_profile: Dictionary = {}
|
|
var _discoveryRequests: Dictionary = {}
|
|
var _discoveredDestinations: Dictionary = {}
|
|
|
|
func _ready() -> void:
|
|
add_to_group("whaletown_escape_dismissible")
|
|
_build_ui()
|
|
var event_system := get_node_or_null("/root/EventSystem")
|
|
if event_system != null:
|
|
event_system.call("connect_event", EventNames.SOCIAL_NOTIFICATION_RECEIVED, _on_social_realtime, self)
|
|
|
|
func _exit_tree() -> void:
|
|
var event_system := get_node_or_null("/root/EventSystem")
|
|
if event_system != null:
|
|
event_system.call("disconnect_event", EventNames.SOCIAL_NOTIFICATION_RECEIVED, _on_social_realtime, self)
|
|
|
|
func show_profile(user_id: String) -> void:
|
|
var normalized := user_id.strip_edges()
|
|
if normalized.is_empty():
|
|
return
|
|
_request_get("/social/profiles/%s" % normalized, func(success: bool, response: Dictionary, error: Dictionary) -> void:
|
|
if not success:
|
|
_show_status("无法查看名片:%s" % str(error.get("message", "请求失败")))
|
|
return
|
|
var data_variant: Variant = response.get("data", {})
|
|
if data_variant is Dictionary:
|
|
_current_profile = data_variant as Dictionary
|
|
_render_profile()
|
|
)
|
|
|
|
func show_own_profile() -> void:
|
|
_request_get("/social/profile", func(success: bool, response: Dictionary, error: Dictionary) -> void:
|
|
if not success:
|
|
_show_status("无法读取个人名片:%s" % str(error.get("message", "请求失败")))
|
|
return
|
|
var data_variant: Variant = response.get("data", {})
|
|
if data_variant is Dictionary:
|
|
_current_profile = data_variant as Dictionary
|
|
_render_profile()
|
|
)
|
|
|
|
func request_friend(user_id: String, username: String = "") -> void:
|
|
var chat := get_node_or_null("/root/ChatManager")
|
|
if chat != null and chat.has_method("request_friend"):
|
|
chat.call("request_friend", user_id, username)
|
|
|
|
func open_private_chat(user_id: String, username: String) -> void:
|
|
var event_system := get_node_or_null("/root/EventSystem")
|
|
if event_system != null:
|
|
event_system.call("emit_event", EventNames.CHAT_PRIVATE_TARGET_SELECTED, {"userId": user_id, "username": username})
|
|
_close_profile()
|
|
|
|
func visit_room(user_id: String, nickname: String = "") -> void:
|
|
var owner_id: String = user_id.strip_edges()
|
|
if owner_id.is_empty():
|
|
return
|
|
_request_get("/social/profiles/%s" % owner_id.uri_encode(), func(success: bool, response: Dictionary, error: Dictionary) -> void:
|
|
if not success:
|
|
_show_status("无法访问房间:%s" % str(error.get("message", "请求失败")))
|
|
return
|
|
var profile_variant: Variant = response.get("data", {})
|
|
if not (profile_variant is Dictionary) or not bool((profile_variant as Dictionary).get("room_visitable", false)):
|
|
_show_status("该玩家当前不开放个人空间访问")
|
|
return
|
|
var profile: Dictionary = profile_variant as Dictionary
|
|
_begin_room_visit(owner_id, str(profile.get("nickname", nickname)))
|
|
)
|
|
|
|
func _begin_room_visit(owner_id: String, nickname: String) -> void:
|
|
var current_scene: Node = get_tree().current_scene
|
|
var return_scene: String = SceneManager.get_current_scene_name().strip_edges()
|
|
if return_scene.is_empty() and current_scene != null:
|
|
return_scene = _scene_name_to_id(current_scene.name)
|
|
if return_scene.is_empty() or return_scene == "personal_space":
|
|
return_scene = "square"
|
|
var return_position := Vector2.ZERO
|
|
if current_scene != null:
|
|
var player_node := current_scene.get_node_or_null("YSortWorld/Characters/Players/Player") as Node2D
|
|
if player_node != null:
|
|
return_position = player_node.global_position
|
|
SceneManager.begin_room_visit(owner_id, nickname, return_scene, return_position)
|
|
_close_profile()
|
|
SceneManager.change_scene("personal_space")
|
|
|
|
func request_travel(destination_id: String, completion: Callable = Callable()) -> void:
|
|
_request_post("/world/travel-destinations/%s/travel" % destination_id.uri_encode(), {}, func(success: bool, response: Dictionary, error: Dictionary) -> void:
|
|
if not success:
|
|
_show_status(str(error.get("message", "该地点尚未解锁")))
|
|
if completion.is_valid(): completion.call(false, {})
|
|
return
|
|
var data_variant: Variant = response.get("data", {})
|
|
var data: Dictionary = data_variant if data_variant is Dictionary else {}
|
|
if completion.is_valid(): completion.call(true, data)
|
|
)
|
|
|
|
func discover_destination(destination_id: String) -> void:
|
|
var normalized := destination_id.strip_edges()
|
|
if normalized.is_empty() or _discoveredDestinations.has(normalized) or _discoveryRequests.has(normalized):
|
|
return
|
|
_discoveryRequests[normalized] = true
|
|
_request_post("/world/travel-destinations/%s/discover" % normalized.uri_encode(), {}, func(success: bool, _response: Dictionary, _error: Dictionary) -> void:
|
|
_discoveryRequests.erase(normalized)
|
|
if success:
|
|
_discoveredDestinations[normalized] = true
|
|
)
|
|
|
|
func discover_nearby_destinations(map_id: String, position: Vector2) -> void:
|
|
for point in DISCOVERY_POINTS:
|
|
if str(point.get("mapId", "")) != map_id:
|
|
continue
|
|
var target := point.get("position", Vector2.ZERO) as Vector2
|
|
if position.distance_to(target) <= DISCOVERY_DISTANCE:
|
|
discover_destination(str(point.get("id", "")))
|
|
|
|
func toggle_notifications() -> void:
|
|
_notification_panel.visible = not _notification_panel.visible
|
|
if _notification_panel.visible:
|
|
_refresh_notifications()
|
|
|
|
func is_escape_dismissible() -> bool:
|
|
return (is_instance_valid(_profile_panel) and _profile_panel.visible) or (is_instance_valid(_notification_panel) and _notification_panel.visible)
|
|
|
|
func get_escape_priority() -> int:
|
|
return 800
|
|
|
|
func request_escape_close() -> void:
|
|
if is_instance_valid(_profile_panel) and _profile_panel.visible:
|
|
_close_profile()
|
|
return
|
|
if is_instance_valid(_notification_panel):
|
|
_notification_panel.visible = false
|
|
|
|
func _on_social_realtime(_payload: Dictionary) -> void:
|
|
if _notification_panel.visible:
|
|
_refresh_notifications()
|
|
|
|
func _refresh_notifications() -> void:
|
|
_request_get("/social/notifications?limit=30", func(success: bool, response: Dictionary, error: Dictionary) -> void:
|
|
if not success:
|
|
_show_notification_rows(["通知暂时无法读取:%s" % str(error.get("message", "请求失败"))])
|
|
return
|
|
var data_variant: Variant = response.get("data", {})
|
|
var data: Dictionary = data_variant if data_variant is Dictionary else {}
|
|
var rows: Array[String] = []
|
|
var notifications_variant: Variant = data.get("notifications", [])
|
|
if notifications_variant is Array:
|
|
for item in notifications_variant as Array:
|
|
if item is Dictionary:
|
|
var notification: Dictionary = item
|
|
rows.append("%s\n%s" % [str(notification.get("title", "通知")), str(notification.get("content", ""))])
|
|
if rows.is_empty(): rows.append("暂无通知")
|
|
_show_notification_rows(rows)
|
|
)
|
|
|
|
func _render_profile() -> void:
|
|
_profile_panel.visible = true
|
|
for child in _profile_content.get_children():
|
|
child.queue_free()
|
|
var nickname := str(_current_profile.get("nickname", "玩家"))
|
|
var username := str(_current_profile.get("username", ""))
|
|
var online := bool(_current_profile.get("online", false))
|
|
var area := str(_current_profile.get("currentArea", ""))
|
|
var identity := HBoxContainer.new()
|
|
identity.add_theme_constant_override("separation", 12)
|
|
identity.add_child(_avatar_badge(nickname))
|
|
var identity_text := VBoxContainer.new()
|
|
identity_text.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
identity_text.add_child(_label(nickname, 26, ACCENT))
|
|
identity_text.add_child(_label("@%s · %s" % [username, "在线" if online else "离线"], 15, Color(0.72, 0.82, 0.87, 1)))
|
|
identity.add_child(identity_text)
|
|
_profile_content.add_child(identity)
|
|
_profile_content.add_child(_label("当前区域:%s" % _area_label(area), 16, Color(0.88, 0.94, 0.96, 1)))
|
|
var skin_id := str(_current_profile.get("skinId", "")).strip_edges()
|
|
_profile_content.add_child(_label("角色:%s" % (skin_id if not skin_id.is_empty() else "默认角色"), 14, Color(0.50, 0.76, 0.80, 1)))
|
|
var bio := str(_current_profile.get("bio", "")).strip_edges()
|
|
_profile_content.add_child(_label(bio if not bio.is_empty() else "这个玩家还没有留下简介。", 16, Color(0.84, 0.90, 0.92, 1)))
|
|
var interests_variant: Variant = _current_profile.get("interests", [])
|
|
var interests: Array[String] = []
|
|
if interests_variant is Array:
|
|
for interest in interests_variant as Array: interests.append(str(interest))
|
|
_profile_content.add_child(_label("兴趣:%s" % (" · ".join(interests) if not interests.is_empty() else "未设置"), 14, Color(0.50, 0.76, 0.80, 1)))
|
|
var user_id := str(_current_profile.get("id", ""))
|
|
var self_profile := user_id == _current_user_id()
|
|
if self_profile:
|
|
_profile_content.add_child(_label("编辑社区资料", 17, ACCENT))
|
|
var nickname_input := LineEdit.new()
|
|
nickname_input.placeholder_text = "昵称(首次可立即修改,此后每 7 天一次)"
|
|
nickname_input.text = nickname
|
|
_profile_content.add_child(nickname_input)
|
|
var bio_input := TextEdit.new()
|
|
bio_input.placeholder_text = "简介(最多 160 字)"
|
|
bio_input.text = bio
|
|
bio_input.custom_minimum_size = Vector2(0, 82)
|
|
_profile_content.add_child(bio_input)
|
|
var current_interests: Array[String] = []
|
|
for interest in interests: current_interests.append(interest)
|
|
var interest_row := HBoxContainer.new()
|
|
interest_row.add_theme_constant_override("separation", 6)
|
|
var interest_selects: Array[OptionButton] = []
|
|
for slot in 3:
|
|
var selector := OptionButton.new()
|
|
selector.custom_minimum_size = Vector2(145, 34)
|
|
selector.add_item("兴趣标签")
|
|
for tag in _interest_catalog():
|
|
selector.add_item(str(tag.get("label", "")))
|
|
selector.set_item_metadata(selector.item_count - 1, str(tag.get("id", "")))
|
|
if slot < current_interests.size():
|
|
for item_index in range(1, selector.item_count):
|
|
if str(selector.get_item_metadata(item_index)) == current_interests[slot]:
|
|
selector.select(item_index)
|
|
break
|
|
interest_selects.append(selector)
|
|
interest_row.add_child(selector)
|
|
_profile_content.add_child(interest_row)
|
|
_profile_content.add_child(_button("保存资料", func() -> void: _save_own_profile(nickname_input.text, bio_input.text, interest_selects)))
|
|
else:
|
|
var actions := HBoxContainer.new()
|
|
actions.add_theme_constant_override("separation", 8)
|
|
actions.add_child(_button("私聊", func() -> void: open_private_chat(user_id, nickname)))
|
|
actions.add_child(_button("加好友", func() -> void: request_friend(user_id, nickname)))
|
|
if bool(_current_profile.get("room_visitable", false)):
|
|
actions.add_child(_button("访问房间", func() -> void: visit_room(user_id, nickname)))
|
|
_profile_content.add_child(actions)
|
|
var safety := HBoxContainer.new()
|
|
safety.add_theme_constant_override("separation", 8)
|
|
safety.add_child(_button("拉黑", func() -> void: _block_user(user_id)))
|
|
var report_form := _report_form(user_id)
|
|
var report_button := _button("举报", func() -> void: report_form.visible = not report_form.visible)
|
|
safety.add_child(report_button)
|
|
_profile_content.add_child(safety)
|
|
_profile_content.add_child(report_form)
|
|
_profile_content.add_child(_button("关闭", _close_profile))
|
|
|
|
func _block_user(user_id: String) -> void:
|
|
_request_post("/social/blocks", {"userId": user_id}, func(success: bool, _response: Dictionary, error: Dictionary) -> void:
|
|
_show_status("已拉黑该玩家" if success else str(error.get("message", "拉黑失败")))
|
|
if success: _close_profile()
|
|
)
|
|
|
|
func _save_own_profile(nickname: String, bio: String, interest_selects: Array[OptionButton]) -> void:
|
|
var interests: Array[String] = []
|
|
for selector in interest_selects:
|
|
var index := selector.selected
|
|
if index > 0:
|
|
var value := str(selector.get_item_metadata(index)).strip_edges()
|
|
if not value.is_empty() and not interests.has(value): interests.append(value)
|
|
_request_patch("/social/profile", {"nickname": nickname.strip_edges(), "bio": bio.strip_edges(), "interests": interests}, func(success: bool, response: Dictionary, error: Dictionary) -> void:
|
|
if not success:
|
|
_show_status(str(error.get("message", "资料保存失败")))
|
|
return
|
|
var data_variant: Variant = response.get("data", {})
|
|
if data_variant is Dictionary:
|
|
_current_profile = data_variant as Dictionary
|
|
_render_profile()
|
|
var event_system := get_node_or_null("/root/EventSystem")
|
|
if event_system != null: event_system.call("emit_event", EventNames.SOCIAL_PROFILE_UPDATED, _current_profile)
|
|
)
|
|
|
|
func _report_form(user_id: String) -> VBoxContainer:
|
|
var form := VBoxContainer.new()
|
|
form.visible = false
|
|
form.add_theme_constant_override("separation", 6)
|
|
form.add_theme_stylebox_override("panel", _subpanel_style())
|
|
form.add_child(_label("举报原因", 15, ACCENT))
|
|
var reason := OptionButton.new()
|
|
for item in [
|
|
{"id": "harassment", "label": "骚扰或辱骂"},
|
|
{"id": "spam", "label": "垃圾信息"},
|
|
{"id": "inappropriate_content", "label": "不当内容"},
|
|
{"id": "impersonation", "label": "冒充他人"},
|
|
{"id": "other", "label": "其他"},
|
|
]:
|
|
reason.add_item(str(item.get("label", "其他")))
|
|
reason.set_item_metadata(reason.item_count - 1, str(item.get("id", "other")))
|
|
form.add_child(reason)
|
|
var note := TextEdit.new()
|
|
note.placeholder_text = "补充说明(可选,最多 500 字)"
|
|
note.custom_minimum_size = Vector2(0, 68)
|
|
form.add_child(note)
|
|
var block_also := CheckBox.new()
|
|
block_also.text = "同时拉黑此玩家"
|
|
block_also.button_pressed = false
|
|
form.add_child(block_also)
|
|
form.add_child(_button("提交举报", func() -> void:
|
|
_submit_report(user_id, str(reason.get_item_metadata(reason.selected)), note.text, block_also.button_pressed)
|
|
))
|
|
return form
|
|
|
|
func _submit_report(user_id: String, reason: String, note: String, block_also: bool) -> void:
|
|
_request_post("/social/reports", {
|
|
"userId": user_id,
|
|
"reason": reason,
|
|
"note": note.strip_edges(),
|
|
"blockAlso": block_also,
|
|
}, func(success: bool, _response: Dictionary, error: Dictionary) -> void:
|
|
_show_status("举报已提交" if success else str(error.get("message", "举报失败")))
|
|
if success and block_also:
|
|
_close_profile()
|
|
)
|
|
|
|
func _build_ui() -> void:
|
|
_canvas = CanvasLayer.new()
|
|
_canvas.layer = 88
|
|
add_child(_canvas)
|
|
_profile_panel = _panel(Vector2(540, 610))
|
|
_profile_panel.set_anchors_preset(Control.PRESET_CENTER)
|
|
_profile_panel.position = Vector2(-270, -305)
|
|
_canvas.add_child(_profile_panel)
|
|
var profile_margin := _margin(_profile_panel)
|
|
var profile_scroll := ScrollContainer.new()
|
|
profile_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
|
profile_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
|
profile_margin.add_child(profile_scroll)
|
|
_profile_content = VBoxContainer.new()
|
|
_profile_content.add_theme_constant_override("separation", 12)
|
|
_profile_content.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
profile_scroll.add_child(_profile_content)
|
|
_notification_panel = _panel(Vector2(390, 430))
|
|
_notification_panel.set_anchors_preset(Control.PRESET_TOP_RIGHT)
|
|
_notification_panel.position = Vector2(-414, 110)
|
|
_canvas.add_child(_notification_panel)
|
|
var notification_margin := _margin(_notification_panel)
|
|
_notification_content = VBoxContainer.new()
|
|
_notification_content.add_theme_constant_override("separation", 9)
|
|
notification_margin.add_child(_notification_content)
|
|
_notification_panel.visible = false
|
|
_profile_panel.visible = false
|
|
|
|
func _show_notification_rows(rows: Array[String]) -> void:
|
|
for child in _notification_content.get_children(): child.queue_free()
|
|
_notification_content.add_child(_label("通知中心", 22, ACCENT))
|
|
for row in rows:
|
|
_notification_content.add_child(_label(row, 15, Color(0.87, 0.93, 0.95, 1)))
|
|
_notification_content.add_child(_button("全部标为已读", func() -> void:
|
|
_request_patch("/social/notifications/read-all", {}, func(_success: bool, _response: Dictionary, _error: Dictionary) -> void: _refresh_notifications())
|
|
))
|
|
|
|
func _panel(panel_size: Vector2) -> PanelContainer:
|
|
var panel := PanelContainer.new()
|
|
panel.size = panel_size
|
|
panel.mouse_filter = Control.MOUSE_FILTER_STOP
|
|
var style := StyleBoxFlat.new()
|
|
style.bg_color = PANEL_COLOR
|
|
style.border_color = Color(0.20, 0.52, 0.61, 0.9)
|
|
style.set_border_width_all(1)
|
|
style.set_corner_radius_all(12)
|
|
style.shadow_color = Color(0, 0, 0, 0.45)
|
|
style.shadow_size = 16
|
|
panel.add_theme_stylebox_override("panel", style)
|
|
return panel
|
|
|
|
func _margin(parent: Control) -> MarginContainer:
|
|
var margin := MarginContainer.new()
|
|
margin.add_theme_constant_override("margin_left", 20)
|
|
margin.add_theme_constant_override("margin_top", 18)
|
|
margin.add_theme_constant_override("margin_right", 20)
|
|
margin.add_theme_constant_override("margin_bottom", 18)
|
|
parent.add_child(margin)
|
|
return margin
|
|
|
|
func _label(text: String, font_size: int, color: Color) -> Label:
|
|
var label := Label.new()
|
|
label.text = text
|
|
label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
label.add_theme_font_size_override("font_size", font_size)
|
|
label.add_theme_color_override("font_color", color)
|
|
return label
|
|
|
|
func _button(text: String, callback: Callable) -> Button:
|
|
var button := Button.new()
|
|
button.text = text
|
|
button.custom_minimum_size = Vector2(92, 34)
|
|
button.pressed.connect(callback)
|
|
return button
|
|
|
|
func _avatar_badge(nickname: String) -> Label:
|
|
var badge := Label.new()
|
|
badge.text = nickname.left(1).to_upper() if not nickname.is_empty() else "玩"
|
|
badge.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
badge.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
badge.custom_minimum_size = Vector2(52, 52)
|
|
badge.add_theme_font_size_override("font_size", 22)
|
|
badge.add_theme_color_override("font_color", Color(0.92, 0.99, 1.0, 1.0))
|
|
var style := StyleBoxFlat.new()
|
|
style.bg_color = Color(0.12, 0.40, 0.48, 1.0)
|
|
style.set_corner_radius_all(26)
|
|
style.border_color = ACCENT
|
|
style.set_border_width_all(1)
|
|
badge.add_theme_stylebox_override("normal", style)
|
|
return badge
|
|
|
|
func _subpanel_style() -> StyleBoxFlat:
|
|
var style := StyleBoxFlat.new()
|
|
style.bg_color = Color(0.06, 0.13, 0.18, 0.88)
|
|
style.set_border_width_all(1)
|
|
style.border_color = Color(0.18, 0.45, 0.52, 0.75)
|
|
style.set_corner_radius_all(8)
|
|
style.content_margin_left = 10
|
|
style.content_margin_right = 10
|
|
style.content_margin_top = 8
|
|
style.content_margin_bottom = 8
|
|
return style
|
|
|
|
func _close_profile() -> void:
|
|
_profile_panel.visible = false
|
|
|
|
func _show_status(message: String) -> void:
|
|
push_warning("Social: %s" % message)
|
|
|
|
func _request_get(endpoint: String, callback: Callable) -> void:
|
|
var api := get_node_or_null("/root/ApiClient")
|
|
if api != null: api.call("get_json", endpoint, callback, true)
|
|
|
|
func _request_post(endpoint: String, payload: Dictionary, callback: Callable) -> void:
|
|
var api := get_node_or_null("/root/ApiClient")
|
|
if api != null: api.call("post_json", endpoint, payload, callback, true)
|
|
|
|
func _request_patch(endpoint: String, payload: Dictionary, callback: Callable) -> void:
|
|
var api := get_node_or_null("/root/ApiClient")
|
|
if api != null: api.call("patch_json", endpoint, payload, callback, true)
|
|
|
|
func _current_user_id() -> String:
|
|
var auth := get_node_or_null("/root/AuthManager")
|
|
if auth != null and auth.has_method("get_current_user"):
|
|
var user_variant: Variant = auth.call("get_current_user")
|
|
if user_variant is Dictionary: return str((user_variant as Dictionary).get("id", ""))
|
|
return ""
|
|
|
|
func _area_label(map_id: String) -> String:
|
|
return {"whale_port": "中心广场", "work_zone": "打工区", "whale_cafe": "鲸鱼咖啡馆", "personal_space": "个人空间"}.get(map_id, map_id)
|
|
|
|
func _scene_name_to_id(scene_name: String) -> String:
|
|
return {
|
|
"Square": "square",
|
|
"WorkZone": "work_zone",
|
|
"CafeInterior": "cafe_interior",
|
|
"PersonalSpace": "personal_space",
|
|
}.get(scene_name, "")
|
|
|
|
func _interest_catalog() -> Array[Dictionary]:
|
|
return [
|
|
{"id": "ai", "label": "AI/大模型"}, {"id": "programming", "label": "编程开发"},
|
|
{"id": "data_science", "label": "数据科学"}, {"id": "open_source", "label": "开源协作"},
|
|
{"id": "product", "label": "产品"}, {"id": "design", "label": "设计"},
|
|
{"id": "game_dev", "label": "游戏开发"}, {"id": "content_creation", "label": "内容创作"},
|
|
{"id": "community", "label": "社区活动"}, {"id": "learning_partner", "label": "学习搭子"},
|
|
{"id": "career", "label": "职业成长"}, {"id": "casual_chat", "label": "轻松闲聊"},
|
|
]
|