- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
550 lines
18 KiB
GDScript
550 lines
18 KiB
GDScript
# ChatUI (P3) —— 聊天窗:多标签 + 输入行 + 频道前缀 + 系统/战斗日志。
|
||
#
|
||
# var chat := preload("res://ui/chat_ui.gd").new()
|
||
# add_child(chat)
|
||
# chat.setup(m2client, canvas_parent) # canvas_parent: 一个 CanvasLayer/Control
|
||
# chat.focus_input() # Enter 键调
|
||
#
|
||
# 输入行前缀: /w <名> <话> 私聊 · /g 公会 · /p 队伍 · /s 喊话 · 其它 普通。
|
||
# 消费 M2Client:chat / whisper / item_picked_up。按类型分到 全部 / 私聊 / 系统 / 战斗 标签。
|
||
extends Node
|
||
|
||
const Locale = preload("res://locale.gd")
|
||
const WhisperChatSystem = preload("res://whisper_chat_system.gd")
|
||
|
||
# EChatType (wire.h)
|
||
const T_TALKING := 0
|
||
const T_INFO := 1
|
||
const T_NOTICE := 2
|
||
const T_PARTY := 3
|
||
const T_GUILD := 4
|
||
const T_SHOUT := 6
|
||
const T_WHISPER := 7
|
||
# PythonChat.h::EWhisperType (40250). Keep these distinct from EChatType:
|
||
# 1..4 are server error modes, 5 is GM, and 0xff is a system message.
|
||
const WHISPER_TYPE_CHAT := 0
|
||
const WHISPER_TYPE_NOT_EXIST := 1
|
||
const WHISPER_TYPE_TARGET_BLOCKED := 2
|
||
const WHISPER_TYPE_SENDER_BLOCKED := 3
|
||
const WHISPER_TYPE_ERROR := 4
|
||
const WHISPER_TYPE_GM := 5
|
||
const WHISPER_TYPE_SYSTEM := 255
|
||
const FISHING_SUCCESS := 3
|
||
const FISHING_FAIL := 4
|
||
const FISHING_FISH := 5
|
||
|
||
const TABS := ["全部", "私聊", "系统", "战斗"]
|
||
const COLOR := {
|
||
0: Color(0.92, 0.92, 0.92), # talking
|
||
1: Color(0.55, 0.85, 1.0), # info
|
||
2: Color(1.0, 0.85, 0.3), # notice
|
||
3: Color(0.6, 0.85, 1.0), # party
|
||
4: Color(0.5, 1.0, 0.6), # guild
|
||
6: Color(1.0, 0.7, 0.4), # shout
|
||
7: Color(1.0, 0.6, 0.95), # whisper
|
||
}
|
||
|
||
signal line_added(tab: int, bbcode: String)
|
||
|
||
var client: Node
|
||
var _root: Control
|
||
var _log := {} # tab_index -> RichTextLabel
|
||
var _input: LineEdit
|
||
var _tab := 0
|
||
var _tab_btns := []
|
||
var _max_lines := 200
|
||
var _buffers := {} # tab -> Array[String]
|
||
var _locale: RefCounted
|
||
var _proto: Node
|
||
var _fishing_active := false
|
||
var _whisper_dialog: Panel
|
||
var _whisper_target: LineEdit
|
||
var _whisper_message: LineEdit
|
||
var _whisper_system: RefCounted
|
||
var _mobile_mode := false
|
||
var _tab_row: HBoxContainer
|
||
|
||
func setup(m2client: Node, parent: Node, assets_root := "") -> void:
|
||
client = m2client
|
||
var player_name := "Hero"
|
||
if client and client.has_method("get_main_vid") and client.has_method("get_entity"):
|
||
var main: Dictionary = client.get_entity(int(client.get_main_vid()))
|
||
if not main.is_empty() and not String(main.get("name", "")).is_empty():
|
||
player_name = String(main.get("name", "Hero"))
|
||
_whisper_system = WhisperChatSystem.new(player_name)
|
||
if assets_root != "":
|
||
_locale = Locale.new()
|
||
_locale.setup(assets_root, "en")
|
||
_build(parent)
|
||
if client:
|
||
if client.has_signal("chat"):
|
||
client.chat.connect(_on_chat)
|
||
if client.has_signal("whisper_received"):
|
||
client.whisper_received.connect(_on_whisper)
|
||
if client.has_signal("item_picked_up"):
|
||
client.item_picked_up.connect(_on_pickup)
|
||
if client.has_signal("fishing_event"):
|
||
client.fishing_event.connect(_on_fishing_event)
|
||
|
||
func set_proto(item_proto: Node) -> void:
|
||
_proto = item_proto
|
||
|
||
func _loc(key: String, fallback: String, args: Array = []) -> String:
|
||
if _locale and _locale.has(key):
|
||
return _locale.t(key, args)
|
||
return fallback % args if not args.is_empty() else fallback
|
||
|
||
func on_fishing_feedback(code: String) -> void:
|
||
if code == "FISHING_WRONG_PLACE":
|
||
var s := _loc("FISHING_WRONG_PLACE", "You cannot go fishing here.")
|
||
_append(2, T_INFO, s)
|
||
_append(3, T_INFO, s)
|
||
|
||
func focus_input() -> void:
|
||
if _input:
|
||
_input.grab_focus()
|
||
|
||
func set_mobile_mode(enabled: bool) -> void:
|
||
_mobile_mode = enabled
|
||
if _root:
|
||
_apply_mobile_layout()
|
||
_apply_mobile_whisper_layout()
|
||
if enabled and _root:
|
||
# Chat is an on-demand full-screen page on mobile; the landscape HUD has
|
||
# no permanent bottom chat strip.
|
||
_root.visible = false
|
||
|
||
func close() -> void:
|
||
if _root:
|
||
_root.visible = false
|
||
_close_whisper()
|
||
|
||
func get_mobile_windows() -> Array[Control]:
|
||
var result: Array[Control] = []
|
||
if _root:
|
||
result.append(_root)
|
||
if _whisper_dialog:
|
||
result.append(_whisper_dialog)
|
||
return result
|
||
|
||
# 预填 "/w <名> " 并聚焦(好友列表点名字时调)。
|
||
func start_whisper(name: String) -> void:
|
||
if _whisper_dialog == null or name.strip_edges() == "":
|
||
return
|
||
_whisper_target.text = name.strip_edges()
|
||
if _whisper_system:
|
||
_whisper_system.open_conversation(_whisper_target.text)
|
||
_whisper_message.clear()
|
||
_whisper_dialog.visible = true
|
||
if _whisper_message.is_inside_tree():
|
||
_whisper_message.grab_focus()
|
||
|
||
func is_whisper_open() -> bool:
|
||
return _whisper_dialog != null and _whisper_dialog.visible
|
||
|
||
func whisper_target() -> String:
|
||
return _whisper_target.text if _whisper_target != null else ""
|
||
|
||
func whisper_system() -> RefCounted:
|
||
return _whisper_system
|
||
|
||
func toggle_whisper_ignore(name: String) -> bool:
|
||
if _whisper_system == null:
|
||
return false
|
||
return _whisper_system.toggle_ignore_target(name.strip_edges())
|
||
|
||
func is_typing() -> bool:
|
||
return _input != null and _input.has_focus()
|
||
|
||
# game.py ToggleChatLogWindow hides/shows the complete chat log while keeping
|
||
# the accumulated channel buffers. Enter can still focus the input afterward.
|
||
func toggle_log() -> void:
|
||
if _root:
|
||
_root.visible = not _root.visible
|
||
|
||
func is_log_visible() -> bool:
|
||
return _root != null and _root.visible
|
||
|
||
func set_view_chat(enabled: bool) -> void:
|
||
if _root:
|
||
_root.visible = enabled
|
||
|
||
func set_log_visible(v: bool) -> void:
|
||
if _root:
|
||
_root.visible = v
|
||
|
||
# --- 建 UI --------------------------------------------------------------
|
||
|
||
func _build(parent: Node) -> void:
|
||
_root = Control.new()
|
||
_root.set_anchors_preset(Control.PRESET_BOTTOM_LEFT)
|
||
_root.position = Vector2(12, -232)
|
||
_root.size = Vector2(460, 220)
|
||
parent.add_child(_root)
|
||
|
||
var panel := Panel.new()
|
||
panel.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
var sb := StyleBoxFlat.new()
|
||
sb.bg_color = Color(0.05, 0.06, 0.08, 0.72)
|
||
sb.set_corner_radius_all(4)
|
||
panel.add_theme_stylebox_override("panel", sb)
|
||
_root.add_child(panel)
|
||
|
||
_tab_row = HBoxContainer.new()
|
||
_tab_row.position = Vector2(6, 4)
|
||
_root.add_child(_tab_row)
|
||
for i in TABS.size():
|
||
var b := Button.new()
|
||
b.text = TABS[i]
|
||
b.toggle_mode = true
|
||
b.button_pressed = (i == 0)
|
||
b.add_theme_font_size_override("font_size", 11)
|
||
var idx := i
|
||
b.pressed.connect(func(): _select_tab(idx))
|
||
_tab_row.add_child(b)
|
||
_tab_btns.append(b)
|
||
|
||
for i in TABS.size():
|
||
var rt := RichTextLabel.new()
|
||
rt.bbcode_enabled = true
|
||
rt.scroll_following = true
|
||
rt.selection_enabled = true
|
||
rt.position = Vector2(6, 30)
|
||
rt.size = Vector2(448, 158)
|
||
rt.visible = (i == 0)
|
||
rt.add_theme_font_size_override("normal_font_size", 12)
|
||
_root.add_child(rt)
|
||
_log[i] = rt
|
||
_buffers[i] = []
|
||
|
||
_input = LineEdit.new()
|
||
_input.placeholder_text = "回车发送 · /w 名 私聊 · /g 公会 · /p 队伍 · /s 喊话"
|
||
_input.position = Vector2(6, 192)
|
||
_input.size = Vector2(448, 24)
|
||
_input.add_theme_font_size_override("font_size", 12)
|
||
_input.text_submitted.connect(_on_submit)
|
||
_root.add_child(_input)
|
||
_build_whisper_dialog(parent)
|
||
_apply_mobile_layout()
|
||
_apply_mobile_whisper_layout()
|
||
|
||
func _apply_mobile_layout() -> void:
|
||
if _root == null:
|
||
return
|
||
if not _mobile_mode:
|
||
_root.set_anchors_preset(Control.PRESET_BOTTOM_LEFT)
|
||
_root.position = Vector2(12, -232)
|
||
_root.size = Vector2(460, 220)
|
||
if _tab_row:
|
||
_tab_row.position = Vector2(6, 4)
|
||
for rt in _log.values():
|
||
(rt as RichTextLabel).position = Vector2(6, 30)
|
||
(rt as RichTextLabel).size = Vector2(448, 158)
|
||
if _input:
|
||
_input.position = Vector2(6, 192)
|
||
_input.size = Vector2(448, 24)
|
||
return
|
||
# One landscape page with a large text area and a keyboard-friendly input
|
||
# row. The HUD remains clear until the player explicitly opens chat.
|
||
_root.set_anchors_preset(Control.PRESET_TOP_LEFT)
|
||
_root.position = Vector2.ZERO
|
||
_root.size = Vector2(760, 340)
|
||
_root.set_meta("mobile_title", "聊天")
|
||
if _tab_row:
|
||
_tab_row.position = Vector2(14, 48)
|
||
_tab_row.size = Vector2(732, 40)
|
||
_tab_row.add_theme_constant_override("separation", 6)
|
||
for child in _tab_row.get_children():
|
||
child.custom_minimum_size = Vector2(112, 38)
|
||
child.add_theme_font_size_override("font_size", 12)
|
||
for rt in _log.values():
|
||
(rt as RichTextLabel).position = Vector2(14, 94)
|
||
(rt as RichTextLabel).size = Vector2(732, 190)
|
||
(rt as RichTextLabel).add_theme_font_size_override("normal_font_size", 14)
|
||
if _input:
|
||
_input.position = Vector2(14, 294)
|
||
_input.size = Vector2(732, 38)
|
||
_input.add_theme_font_size_override("font_size", 13)
|
||
|
||
func _apply_mobile_whisper_layout() -> void:
|
||
if _whisper_dialog == null:
|
||
return
|
||
if not _mobile_mode:
|
||
_whisper_dialog.set_anchors_preset(Control.PRESET_CENTER)
|
||
_whisper_dialog.position = Vector2(-170, -82)
|
||
_whisper_dialog.size = Vector2(340, 164)
|
||
return
|
||
_whisper_dialog.set_anchors_preset(Control.PRESET_TOP_LEFT)
|
||
_whisper_dialog.position = Vector2.ZERO
|
||
_whisper_dialog.size = Vector2(480, 260)
|
||
_whisper_dialog.set_meta("mobile_title", "私聊")
|
||
var title := _whisper_dialog.get_child(0) as Label
|
||
if title:
|
||
title.position = Vector2(18, 50)
|
||
title.add_theme_font_size_override("font_size", 15)
|
||
var target_label := _whisper_dialog.get_child(1) as Label
|
||
if target_label:
|
||
target_label.position = Vector2(18, 94)
|
||
var message_label := _whisper_dialog.get_child(3) as Label
|
||
if message_label:
|
||
message_label.position = Vector2(18, 148)
|
||
if _whisper_target:
|
||
_whisper_target.position = Vector2(86, 90)
|
||
_whisper_target.size = Vector2(370, 42)
|
||
if _whisper_message:
|
||
_whisper_message.position = Vector2(86, 144)
|
||
_whisper_message.size = Vector2(370, 42)
|
||
_whisper_message.virtual_keyboard_type = LineEdit.KEYBOARD_TYPE_DEFAULT
|
||
var cancel := _whisper_dialog.get_node_or_null("Button") as Button
|
||
var send := _whisper_dialog.get_node_or_null("Button2") as Button
|
||
if cancel:
|
||
cancel.position = Vector2(174, 202)
|
||
cancel.size = Vector2(130, 44)
|
||
if send:
|
||
send.position = Vector2(316, 202)
|
||
send.size = Vector2(130, 44)
|
||
|
||
func _build_whisper_dialog(parent: Node) -> void:
|
||
# root/game.py 的 OpenWhisperDialog 是独立窗口,不应把私聊目标写回公共
|
||
# 聊天输入行。这里用 UiManager 同级的独立 Panel,保留自己的焦点和发送键。
|
||
_whisper_dialog = Panel.new()
|
||
_whisper_dialog.name = "WhisperDialog"
|
||
_whisper_dialog.set_anchors_preset(Control.PRESET_CENTER)
|
||
_whisper_dialog.position = Vector2(-170, -82)
|
||
_whisper_dialog.size = Vector2(340, 164)
|
||
_whisper_dialog.visible = false
|
||
_whisper_dialog.mouse_filter = Control.MOUSE_FILTER_STOP
|
||
var bg := StyleBoxFlat.new()
|
||
bg.bg_color = Color(0.04, 0.05, 0.08, 0.97)
|
||
bg.border_color = Color(0.62, 0.36, 0.72, 0.95)
|
||
bg.set_border_width_all(1)
|
||
bg.set_corner_radius_all(5)
|
||
_whisper_dialog.add_theme_stylebox_override("panel", bg)
|
||
parent.add_child(_whisper_dialog)
|
||
|
||
var title := Label.new()
|
||
title.text = "私聊"
|
||
title.position = Vector2(14, 10)
|
||
title.add_theme_font_size_override("font_size", 16)
|
||
_whisper_dialog.add_child(title)
|
||
var target_label := Label.new()
|
||
target_label.text = "对象"
|
||
target_label.position = Vector2(14, 45)
|
||
_whisper_dialog.add_child(target_label)
|
||
_whisper_target = LineEdit.new()
|
||
_whisper_target.position = Vector2(66, 41)
|
||
_whisper_target.size = Vector2(256, 26)
|
||
_whisper_target.editable = false
|
||
_whisper_target.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
_whisper_dialog.add_child(_whisper_target)
|
||
var message_label := Label.new()
|
||
message_label.text = "内容"
|
||
message_label.position = Vector2(14, 80)
|
||
_whisper_dialog.add_child(message_label)
|
||
_whisper_message = LineEdit.new()
|
||
_whisper_message.position = Vector2(66, 76)
|
||
_whisper_message.size = Vector2(256, 26)
|
||
_whisper_message.placeholder_text = "输入私聊内容"
|
||
_whisper_message.text_submitted.connect(_on_whisper_submit)
|
||
_whisper_dialog.add_child(_whisper_message)
|
||
var cancel := Button.new()
|
||
cancel.text = "取消"
|
||
cancel.position = Vector2(170, 119)
|
||
cancel.size = Vector2(70, 28)
|
||
cancel.pressed.connect(_close_whisper)
|
||
_whisper_dialog.add_child(cancel)
|
||
var send := Button.new()
|
||
send.text = "发送"
|
||
send.position = Vector2(248, 119)
|
||
send.size = Vector2(74, 28)
|
||
send.pressed.connect(func(): _on_whisper_submit(_whisper_message.text))
|
||
_whisper_dialog.add_child(send)
|
||
|
||
func _close_whisper() -> void:
|
||
if _whisper_dialog:
|
||
_whisper_dialog.visible = false
|
||
if _whisper_message and _whisper_message.is_inside_tree():
|
||
_whisper_message.release_focus()
|
||
|
||
func _on_whisper_submit(text: String) -> void:
|
||
var to := _whisper_target.text.strip_edges() if _whisper_target else ""
|
||
var msg := text.strip_edges()
|
||
if to == "" or msg == "" or client == null:
|
||
return
|
||
client.whisper(to, msg)
|
||
if _whisper_system:
|
||
_whisper_system.send_whisper(to, msg)
|
||
_append(1, T_WHISPER, "→ %s: %s" % [to, msg])
|
||
_append(0, T_WHISPER, "→ %s: %s" % [to, msg])
|
||
_close_whisper()
|
||
|
||
func _unhandled_input(event: InputEvent) -> void:
|
||
if not is_whisper_open() or not (event is InputEventKey):
|
||
return
|
||
var key := event as InputEventKey
|
||
if not key.pressed:
|
||
return
|
||
if key.keycode == KEY_ESCAPE:
|
||
_close_whisper()
|
||
get_viewport().set_input_as_handled()
|
||
elif key.keycode not in [KEY_ENTER, KEY_KP_ENTER]:
|
||
# 独立私聊窗口打开时,WASD / 快捷栏键不应穿透到世界。
|
||
get_viewport().set_input_as_handled()
|
||
|
||
func _select_tab(i: int) -> void:
|
||
_tab = i
|
||
for t in _log:
|
||
_log[t].visible = (t == i)
|
||
for k in _tab_btns.size():
|
||
_tab_btns[k].button_pressed = (k == i)
|
||
|
||
# --- 输入 -------------------------------------------------------------
|
||
|
||
func _on_submit(text: String) -> void:
|
||
_input.clear()
|
||
if _input.is_inside_tree():
|
||
_input.release_focus()
|
||
var s := text.strip_edges()
|
||
if s == "" or client == null:
|
||
return
|
||
if s.begins_with("/w ") or s.begins_with("/whisper "):
|
||
var rest := s.substr(s.find(" ") + 1).strip_edges()
|
||
var sp := rest.find(" ")
|
||
if sp > 0:
|
||
var to := rest.substr(0, sp)
|
||
var msg := rest.substr(sp + 1).strip_edges()
|
||
client.whisper(to, msg)
|
||
if _whisper_system:
|
||
_whisper_system.send_whisper(to, msg)
|
||
_append(1, T_WHISPER, "→ %s: %s" % [to, msg])
|
||
return
|
||
var map := {"/g ": T_GUILD, "/p ": T_PARTY, "/s ": T_SHOUT}
|
||
for pre in map:
|
||
if s.begins_with(pre):
|
||
client.say(map[pre], s.substr(3).strip_edges())
|
||
return
|
||
client.say(T_TALKING, s)
|
||
|
||
# --- 收消息 --------------------------------------------------------------
|
||
|
||
func _on_chat(type: int, vid: int, text: String) -> void:
|
||
var who := ""
|
||
if client and client.has_method("get_entity"):
|
||
var e: Dictionary = client.get_entity(vid)
|
||
if not e.is_empty():
|
||
who = String(e.get("name", ""))
|
||
var body := ("%s: %s" % [who, text]) if who != "" else text
|
||
var tab := 0
|
||
if type == T_INFO or type == T_NOTICE:
|
||
tab = 2
|
||
_append(tab, type, body)
|
||
if tab != 0:
|
||
_append(0, type, body) # 全部标签也收一份
|
||
|
||
func _on_whisper(sub: int, from: String, text: String) -> void:
|
||
var tag := from
|
||
var line := text
|
||
if sub == WHISPER_TYPE_GM:
|
||
tag = "[GM] " + from
|
||
if _whisper_system:
|
||
var gm_result: Dictionary = _whisper_system.receive_whisper(from, text, true)
|
||
if not bool(gm_result.get("delivered", false)):
|
||
return
|
||
elif sub == WHISPER_TYPE_SYSTEM:
|
||
tag = "[系统]"
|
||
elif sub == WHISPER_TYPE_CHAT:
|
||
if _whisper_system:
|
||
var chat_result: Dictionary = _whisper_system.receive_whisper(from, text)
|
||
if not bool(chat_result.get("delivered", false)):
|
||
return
|
||
elif sub != WHISPER_TYPE_CHAT:
|
||
# game.py::OnRecvWhisperError converts modes 1..4 through
|
||
# localeInfo.WHISPER_ERROR and appends them as a system whisper. The
|
||
# native signal carries the wire text as a best-effort fallback; use a
|
||
# stable localized fallback when the server sends an empty error body.
|
||
tag = "[系统]"
|
||
if line == "":
|
||
line = _whisper_error_text(sub, from)
|
||
_append(1, T_WHISPER, "%s: %s" % [tag, line])
|
||
_append(0, T_WHISPER, "%s: %s" % [tag, line])
|
||
|
||
func _whisper_error_text(sub: int, from: String) -> String:
|
||
match sub:
|
||
WHISPER_TYPE_NOT_EXIST:
|
||
return _loc("WHISPER_NOT_EXIST", "无法私聊:%s 不在线。", [from])
|
||
WHISPER_TYPE_TARGET_BLOCKED:
|
||
return _loc("WHISPER_TARGET_BLOCKED", "无法私聊:%s 拒绝了私聊。", [from])
|
||
WHISPER_TYPE_SENDER_BLOCKED:
|
||
return _loc("WHISPER_SENDER_BLOCKED", "无法私聊:%s 屏蔽了你。", [from])
|
||
WHISPER_TYPE_ERROR:
|
||
return _loc("WHISPER_ERROR", "私聊失败。")
|
||
_:
|
||
return _loc("WHISPER_UNKNOWN_ERROR", "未知私聊错误(模式 %d,目标 %s)。", [sub, from])
|
||
|
||
func _on_pickup(vnum: int, count: int, from: String) -> void:
|
||
var s := "拾取 #%d x%d" % [vnum, count]
|
||
if from != "":
|
||
s += "(来自 %s)" % from
|
||
_append(2, T_INFO, s)
|
||
_append(3, T_INFO, s)
|
||
|
||
func _on_fishing_event(subheader: int, info: int, _dir: int) -> void:
|
||
# RecvFishing only calls a UI callback for FAIL and FISH. SUCCESS is an
|
||
# animation transition; the later FISH event decides notify vs success
|
||
# from the actor's IsFishing() state.
|
||
if subheader == 0: # FISHING_START
|
||
_fishing_active = true
|
||
return
|
||
if subheader == 2: # FISHING_REACT
|
||
_fishing_active = true
|
||
return
|
||
if subheader == FISHING_FAIL:
|
||
_fishing_active = false
|
||
var fail := _loc("FISHING_FAILURE", "You lost the Bait.")
|
||
_append(2, T_INFO, fail)
|
||
_append(3, T_INFO, fail)
|
||
return
|
||
if subheader != FISHING_FISH:
|
||
if subheader == FISHING_SUCCESS:
|
||
_fishing_active = false
|
||
elif subheader == 1: # FISHING_STOP
|
||
_fishing_active = false
|
||
return
|
||
if info == 0:
|
||
var unknown := _loc("FISHING_UNKNOWN", "Something has taken the bait but you can't see what it is.")
|
||
_append(2, T_INFO, unknown)
|
||
_append(3, T_INFO, unknown)
|
||
return
|
||
var item: Dictionary = _proto.item(info) if _proto and _proto.has_method("item") else {}
|
||
# ClientVS22 silently ignores a fish event when item_proto has no record.
|
||
if item.is_empty():
|
||
return
|
||
var name := String(item.get("locale_name", item.get("name", "")))
|
||
if name == "":
|
||
name = "#%d" % info
|
||
var is_fish := int(item.get("type", -1)) == 12 # CItemData::ITEM_TYPE_FISH
|
||
var key := ""
|
||
var fallback := ""
|
||
if _fishing_active:
|
||
key = "FISHING_NOTIFY1" if is_fish else "FISHING_NOTIFY2"
|
||
fallback = "It looks like %s is hooked." if is_fish else "It looks like %s is on the hook."
|
||
else:
|
||
key = "FISHING_SUCCESS1" if is_fish else "FISHING_SUCCESS2"
|
||
fallback = "You captured %s!" if is_fish else "You have pulled %s out of the water!"
|
||
var s := _loc(key, fallback, [name])
|
||
_append(2, T_INFO, s)
|
||
_append(3, T_INFO, s)
|
||
|
||
# --- 追加行 --------------------------------------------------------------
|
||
|
||
func _append(tab: int, type: int, body: String) -> void:
|
||
var c: Color = COLOR.get(type, COLOR[0])
|
||
var line := "[color=#%s]%s[/color]" % [c.to_html(false), body]
|
||
var buf: Array = _buffers[tab]
|
||
buf.append(line)
|
||
if buf.size() > _max_lines:
|
||
buf.pop_front()
|
||
var rt: RichTextLabel = _log[tab]
|
||
rt.text = "\n".join(buf)
|
||
line_added.emit(tab, line)
|