Files

425 lines
15 KiB
GDScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SkillUI (P6) —— 技能窗:主动 / 辅助(被动·支援) / 坐骑 三个分类页 + 等级 + 加点。
#
# var sk := preload("res://ui/skill_ui.gd").new()
# add_child(sk)
# sk.setup(m2client, skill_table, ui_manager)
# sk.set_job("WARRIOR")
# sk.toggle() # K 键
#
# 每行:名字 + Lv X(/M/G/P) + [](被动 / CANNOT_LEVEL_UP 无加点)+ [被动]/[切换] 标签。
# 点名字 -> drag_skill_idquickbar 落点读)。`skills_changed` 刷新。
# §3.10[] 的显隐逐行对齐 uicharacter.py RefreshSkillPlusButtonList /
# __RefreshSkillPlusButton / CanShowPlusButton —— 技能组门(get_skill_group()==0)、
# 三个技能点来源分开取、skillGrade==0、马术页 skillLevel<20、主动页 CanLevelUpSkill、
# 辅助页 HIDE_SUPPORT_SKILL_POINT 整页隐藏。升级结果仍由 GC_SKILL_LEVEL 回包驱动。
extends Node
const MobileTouchButton = preload("res://ui/mobile/mobile_touch_button.gd")
const TABS := ["主动", "辅助", "坐骑"]
const MASTER_SUFFIX := ["", " M", " G", " P"] # master_type 0..3
# uicharacter.py:21 HIDE_SUPPORT_SKILL_POINT —— 本 migration 资源为 True,辅助页整页不出加号。
const HIDE_SUPPORT_SKILL_POINT := true
# uicharacter.py __RefreshSkillPlusButton 的 HORSE 分支硬编码 `if skillLevel < 20`。
const RIDING_SKILL_MAX_LEVEL := 20
var client: Node
var table: RefCounted # SkillTable
var ui: CanvasLayer # UiManager
var job := "WARRIOR"
var drag_skill_id := 0
var _tab := 0
var _mobile_mode := false
var _mobile_quickbar: Node
var _mobile_hint: Label
var _mobile_drop_slots: Array[MobileTouchButton] = []
var _mobile_drag_quickbar_slot := -1
var _win: Control
var _rows := {} # skill_id -> {lv, up}
func setup(m2client: Node, skill_table: RefCounted, ui_manager: CanvasLayer) -> void:
client = m2client
table = skill_table
ui = ui_manager
if client.has_signal("skills_changed"):
client.skills_changed.connect(refresh)
func set_job(j: String) -> void:
job = j
if is_open():
_rebuild()
func set_mobile_mode(enabled: bool) -> void:
_mobile_mode = enabled
if _mobile_mode and _win:
close()
func set_mobile_quickbar(quickbar: Node) -> void:
_mobile_quickbar = quickbar
_refresh_mobile_quickbar_targets()
func is_open() -> bool:
return _win != null and is_instance_valid(_win)
func toggle() -> void:
if is_open(): close()
else: open()
func close() -> void:
if is_open():
ui.close(_win)
_win = null
_rows.clear()
_mobile_drop_slots.clear()
_mobile_drag_quickbar_slot = -1
func _cat_key() -> String:
match _tab:
1: return "SUPPORT"
2: return "HORSE"
_: return job
func open() -> void:
if is_open() or table == null:
return
_win = Control.new()
_win.name = "MobileSkillWindow" if _mobile_mode else "SkillWindow"
if _mobile_mode:
_win.set_anchors_preset(Control.PRESET_TOP_LEFT)
_win.position = Vector2.ZERO
_win.size = Vector2(760, 340)
_win.set_meta("mobile_title", "技能")
else:
_win.set_anchors_preset(Control.PRESET_CENTER_RIGHT)
_win.position = Vector2(-300, -240)
_win.size = Vector2(290, 470)
_win.set_meta("is_titlebar", true)
var panel := Panel.new()
panel.set_anchors_preset(Control.PRESET_FULL_RECT)
var sb := StyleBoxFlat.new()
sb.bg_color = Color(0.08, 0.09, 0.12, 0.95)
sb.set_corner_radius_all(4)
panel.add_theme_stylebox_override("panel", sb)
_win.add_child(panel)
var title := Label.new()
title.text = "技能"
title.position = Vector2(12, 8) if not _mobile_mode else Vector2(14, 48)
title.visible = not _mobile_mode
if _mobile_mode:
title.add_theme_font_size_override("font_size", 15)
_win.add_child(title)
# 分类页签
var tabrow := HBoxContainer.new()
tabrow.name = "tabs"
tabrow.position = Vector2(10, 30) if not _mobile_mode else Vector2(14, 48)
if _mobile_mode:
tabrow.size = Vector2(360, 40)
tabrow.add_theme_constant_override("separation", 6)
_win.add_child(tabrow)
for i in TABS.size():
var b := Button.new()
b.text = TABS[i]
b.toggle_mode = true
b.button_pressed = (i == _tab)
if _mobile_mode:
b.custom_minimum_size = Vector2(110, 38)
b.add_theme_font_size_override("font_size", 12)
var ti: int = i
b.pressed.connect(func(): _switch_tab(ti))
tabrow.add_child(b)
var vb := VBoxContainer.new()
vb.name = "list"
vb.position = Vector2(10, 60) if not _mobile_mode else Vector2(0, 0)
vb.custom_minimum_size = Vector2(270, 0) if not _mobile_mode else Vector2(720, 0)
vb.add_theme_constant_override("separation", 2)
if _mobile_mode:
var scroll := ScrollContainer.new()
scroll.name = "SkillScroll"
scroll.position = Vector2(14, 92)
scroll.size = Vector2(732, 214)
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
scroll.mouse_filter = Control.MOUSE_FILTER_STOP
scroll.add_child(vb)
_win.add_child(scroll)
_mobile_hint = Label.new()
_mobile_hint.position = Vector2(14, 310)
_mobile_hint.size = Vector2(720, 22)
_mobile_hint.add_theme_font_size_override("font_size", 10)
_mobile_hint.add_theme_color_override("font_color", Color(0.62, 0.72, 0.84))
_mobile_hint.text = "可用点数与技能等级由服务器实时确认"
_win.add_child(_mobile_hint)
_build_mobile_quickbar_targets()
else:
_win.add_child(vb)
ui.open(_win)
_rebuild()
func _switch_tab(i: int) -> void:
_tab = i
if is_open():
var tabrow: HBoxContainer = _win.get_node("tabs")
for j in tabrow.get_child_count():
(tabrow.get_child(j) as Button).button_pressed = (j == i)
_rebuild()
func _rebuild() -> void:
var vb: VBoxContainer = _win.get_node_or_null("list") as VBoxContainer
if vb == null and _mobile_mode:
vb = _win.get_node_or_null("SkillScroll/list") as VBoxContainer
if vb == null:
return
for c in vb.get_children():
c.queue_free()
_rows.clear()
var ids: Array = table.for_category(_cat_key())
if ids.is_empty():
var e := Label.new()
e.text = "(无)"
vb.add_child(e)
return
for id in ids:
var row := HBoxContainer.new()
row.custom_minimum_size = Vector2(720, 46) if _mobile_mode else Vector2(268, 26)
var nm: Control
if _mobile_mode:
var skill_name := MobileTouchButton.new()
skill_name.setup(table.name_of(id), Color(0.34, 0.58, 0.88, 0.88))
skill_name.set_rect_style()
skill_name.set_gesture_enabled(true)
skill_name.custom_minimum_size = Vector2(300, 42)
skill_name.size = Vector2(300, 42)
var skill_button := skill_name
skill_button.long_pressed.connect(func(): _mobile_begin_skill_drag(id, skill_button))
skill_button.drag_changed.connect(func(_delta: Vector2):
_mobile_update_skill_drag(id, skill_button))
skill_button.drag_released.connect(func(_delta: Vector2):
_mobile_end_skill_drag(id, skill_button))
nm = skill_name
else:
var skill_label := Label.new()
skill_label.text = table.name_of(id)
skill_label.custom_minimum_size = Vector2(140, 0)
skill_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
skill_label.add_theme_font_size_override("font_size", 12)
nm = skill_label
row.add_child(nm)
var lv := Label.new()
lv.name = "lv"
lv.custom_minimum_size = Vector2(100, 42) if _mobile_mode else Vector2(58, 0)
lv.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
lv.add_theme_font_size_override("font_size", 13 if _mobile_mode else 12)
row.add_child(lv)
var tag := Label.new()
tag.custom_minimum_size = Vector2(76, 42) if _mobile_mode else Vector2(40, 0)
tag.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
tag.add_theme_font_size_override("font_size", 10)
if table.is_passive(id):
tag.text = "被动"
tag.modulate = Color(0.6, 0.8, 1.0)
elif table.is_toggle(id):
tag.text = "切换"
tag.modulate = Color(0.9, 0.8, 0.5)
row.add_child(tag)
var sid: int = id
if table.can_level_up(id) and not table.is_passive(id):
var up := Button.new()
up.text = ""
up.custom_minimum_size = Vector2(48, 42) if _mobile_mode else Vector2(28, 22)
if _mobile_mode:
up.add_theme_font_size_override("font_size", 18)
up.pressed.connect(func(): client.skill_up(sid))
row.add_child(up)
_rows[id] = {"lv": lv, "up": up}
else:
_rows[id] = {"lv": lv, "up": null}
if not _mobile_mode:
nm.gui_input.connect(func(e: InputEvent):
if e is InputEventMouseButton and e.pressed and e.button_index == MOUSE_BUTTON_LEFT:
drag_skill_id = sid
if _mobile_hint: _mobile_hint.text = "已选择:%s;点击快捷栏按钮自动放入空位" % table.name_of(sid)
nm.accept_event())
if _mobile_mode:
var assign := Button.new()
assign.text = "快捷栏"
assign.custom_minimum_size = Vector2(92, 42)
assign.add_theme_font_size_override("font_size", 11)
assign.pressed.connect(func(): _mobile_assign_skill(sid))
row.add_child(assign)
vb.add_child(row)
refresh()
_refresh_mobile_quickbar_targets()
func _build_mobile_quickbar_targets() -> void:
var title := Label.new()
title.text = "快捷栏"
title.position = Vector2(390, 48)
title.size = Vector2(60, 38)
title.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
title.add_theme_font_size_override("font_size", 12)
title.add_theme_color_override("font_color", Color(0.95, 0.82, 0.54))
title.mouse_filter = Control.MOUSE_FILTER_IGNORE
_win.add_child(title)
for i in 8:
var target := MobileTouchButton.new()
target.setup("—", Color(0.42, 0.65, 0.92, 0.9))
target.set_rect_style()
target.set_gesture_enabled(true)
target.position = Vector2(448 + i * 38, 48)
target.size = Vector2(34, 38)
target.custom_minimum_size = Vector2(34, 38)
var slot := i
target.pressed.connect(func(): _mobile_drop_on_quickbar(slot))
target.long_pressed.connect(func(): _mobile_begin_quickbar_drag(slot))
target.drag_released.connect(func(_delta: Vector2): _mobile_end_quickbar_drag(slot, target))
_win.add_child(target)
_mobile_drop_slots.append(target)
func _mobile_begin_skill_drag(skill_id: int, _button: MobileTouchButton) -> void:
drag_skill_id = skill_id
if _mobile_hint:
_mobile_hint.text = "拖到上方快捷栏槽位,松手放入"
func _mobile_update_skill_drag(skill_id: int, _button: MobileTouchButton) -> void:
if drag_skill_id == skill_id and _mobile_hint:
_mobile_hint.text = "拖到上方快捷栏槽位,松手放入"
func _mobile_end_skill_drag(skill_id: int, button: MobileTouchButton) -> void:
if drag_skill_id != skill_id:
return
var target := _mobile_drop_slot_at(button.drag_end_position())
if target >= 0 and _mobile_quickbar and _mobile_quickbar.has_method("assign"):
if bool(_mobile_quickbar.assign(target, "skill", skill_id)):
drag_skill_id = 0
_refresh_mobile_quickbar_targets()
if _mobile_hint: _mobile_hint.text = "已放入快捷栏第 %d 格" % (target + 1)
return
if _mobile_hint:
_mobile_hint.text = "已选择:%s;可拖到快捷栏" % table.name_of(skill_id)
func _mobile_drop_on_quickbar(slot: int) -> void:
if _mobile_quickbar == null or not _mobile_quickbar.has_method("assign"):
return
if drag_skill_id != 0:
if bool(_mobile_quickbar.assign(slot, "skill", drag_skill_id)):
drag_skill_id = 0
_refresh_mobile_quickbar_targets()
if _mobile_hint: _mobile_hint.text = "已放入快捷栏第 %d 格" % (slot + 1)
func _mobile_begin_quickbar_drag(slot: int) -> void:
_mobile_drag_quickbar_slot = slot
if _mobile_hint:
_mobile_hint.text = "拖到另一个快捷栏槽位以交换"
func _mobile_end_quickbar_drag(slot: int, button: MobileTouchButton) -> void:
if _mobile_drag_quickbar_slot != slot:
return
var target := _mobile_drop_slot_at(button.drag_end_position())
if target >= 0 and target != slot and _mobile_quickbar \
and _mobile_quickbar.has_method("mobile_swap"):
_mobile_quickbar.mobile_swap(slot, target)
_refresh_mobile_quickbar_targets()
if _mobile_hint: _mobile_hint.text = "已交换快捷栏位置"
_mobile_drag_quickbar_slot = -1
func _mobile_drop_slot_at(position: Vector2) -> int:
for i in _mobile_drop_slots.size():
var slot := _mobile_drop_slots[i]
if slot and slot.get_global_rect().has_point(position):
return i
return -1
func _refresh_mobile_quickbar_targets() -> void:
if not _mobile_mode:
return
for i in _mobile_drop_slots.size():
var slot := _mobile_drop_slots[i]
if slot == null or _mobile_quickbar == null:
continue
var state: Dictionary = _mobile_quickbar.mobile_slot_state(i) \
if _mobile_quickbar.has_method("mobile_slot_state") else {}
var label := "—"
if state.get("kind", "") == "skill" and table:
label = table.name_of(int(state.get("id", 0))).substr(0, 4)
elif state.get("kind", "") == "item":
label = "物"
elif state.get("kind", "") == "emote":
label = "表"
slot.set_caption(label)
func _mobile_assign_skill(skill_id: int) -> void:
if _mobile_quickbar == null or not _mobile_quickbar.has_method("assign_mobile"):
if _mobile_hint: _mobile_hint.text = "快捷栏暂不可用"
return
if bool(_mobile_quickbar.assign_mobile("skill", skill_id)):
drag_skill_id = 0
_refresh_mobile_quickbar_targets()
if _mobile_hint: _mobile_hint.text = "已放入快捷栏"
else:
if _mobile_hint: _mobile_hint.text = "快捷栏已满,请先移除一个技能"
func refresh() -> void:
if not is_open():
return
var lv := {}
var ms := {}
for s in client.get_skills():
lv[int(s["id"])] = int(s["level"])
ms[int(s["id"])] = int(s.get("master", 0))
# RefreshSkillPlusButtonListuicharacter.py:984)→ __RefreshSkillPlusButton:937):
# 每页的技能点来源分开取,技能组未选时整列隐藏加号。
var page := _cat_key()
var can_use_now := _can_use_skill_now()
var stat_point := _stat_point_for(page)
var plv := _player_level()
for id in _rows:
var lvl: int = lv.get(id, 0)
var m: int = clampi(ms.get(id, 0), 0, 3)
_rows[id]["lv"].text = "Lv %d%s" % [lvl, MASTER_SUFFIX[m]]
var up: Button = _rows[id]["up"]
if up != null:
up.visible = _can_show_plus(id, lvl, m, page, can_use_now, stat_point, plv)
# __CanUseSkillNowuicharacter.py:1270):net.GetMainActorSkillGroup()==0 -> False。
func _can_use_skill_now() -> bool:
if client.has_method("get_skill_group"):
return int(client.get_skill_group()) != 0
return true
# skillPageStatDictuicharacter.py:225):SUPPORT=POINT_SUB_SKILL(27) /
# ACTIVE=POINT_SKILL(28) / HORSE=POINT_HORSE_SKILL(113)。三个来源分开取,不合并成一个。
func _stat_point_for(page: String) -> int:
if not client.has_method("get_points"):
return 1
var p: Dictionary = client.get_points()
match page:
"SUPPORT": return int(p.get("skill_support", 0))
"HORSE": return int(p.get("skill_horse", 0))
_: return int(p.get("skill_active", 0))
func _player_level() -> int:
if not client.has_method("get_points"):
return 1
return int(client.get_points().get("level", 1))
# __RefreshSkillPlusButton 每槽体(uicharacter.py:937+ CanShowPlusButton:925):
# 技能组未选 -> 全隐藏;辅助页 HIDE_SUPPORT_SKILL_POINT -> 整页隐藏;
# statPoint <= 0 -> 不显示;skillGrade != 0(已过 0 级,走大师书)-> 跳过;
# HORSE 页:GetStatus(LEVEL) >= GetSkillLevelLimit(idx) 时,skillLevel < 20 才显示;
# 其余页:skill.CanLevelUpSkill(idx, level)。
func _can_show_plus(id: int, level: int, grade: int, page: String,
can_use_now: bool, stat_point: int, plv: int) -> bool:
if not can_use_now:
return false
if page == "SUPPORT" and HIDE_SUPPORT_SKILL_POINT:
return false
if stat_point <= 0:
return false
if grade != 0:
return false
if page == "HORSE":
if plv >= table.skill_level_limit(id):
return level < RIDING_SKILL_MAX_LEVEL
return false
return table.can_level_up_skill(id, level)