feat: 2.3.2

This commit is contained in:
2026-05-19 17:30:29 +08:00
parent 7ca11c6534
commit 8430d1054e
39 changed files with 6581 additions and 738 deletions
+67 -67
View File
@@ -6,43 +6,43 @@ extends Control
## Open scenes/tools/skin_catalog_editor.tscn in the editor, then press F6 (Run Current Scene).
## Edit skins in the form, then click "💾 Save & Generate" to rewrite:
## • scripts/managers/skin_manager.gd (SKIN_CATALOG block)
## • server/nakama/tekton_admin.js (SHOP_CATALOG_DEFS block)
## • server/nakama/economy.js (SHOP_CATALOG_DEFS block)
const DATA_PATH := "res://assets/data/skin_catalog_data.json"
const DATA_PATH := "res://assets/data/skin_catalog_data.json"
const SKIN_MANAGER_PATH := "res://scripts/managers/skin_manager.gd"
const ADMIN_JS_PATH := "res://server/nakama/tekton_admin.js"
const ADMIN_JS_PATH := "res://server/nakama/economy.js"
const CATEGORIES := ["head", "costume", "glove", "accessory"]
const RARITIES := ["Common", "Rare", "Epic", "Legendary"]
const MODES := ["override", "overlay"]
const RARITIES := ["Common", "Rare", "Epic", "Legendary"]
const MODES := ["override", "overlay"]
# Sentinel markers — must match what's in the target files
const BEGIN_SKIN := "## [BEGIN_SKIN_CATALOG]"
const END_SKIN := "## [END_SKIN_CATALOG]"
const END_SKIN := "## [END_SKIN_CATALOG]"
const BEGIN_SHOP := "// [BEGIN_SHOP_CATALOG_DEFS]"
const END_SHOP := "// [END_SHOP_CATALOG_DEFS]"
const END_SHOP := "// [END_SHOP_CATALOG_DEFS]"
# ─── State ───────────────────────────────────────────────────────────────────
var _data: Array = []
var _selected_idx: int = -1
var _dirty: bool = false
var _data: Array = []
var _selected_idx: int = -1
var _dirty: bool = false
# ─── UI refs (built in code) ─────────────────────────────────────────────────
var _skin_list_vbox: VBoxContainer
var _status_label: Label
var _form_panel: PanelContainer
var _no_sel_label: Label
var _form_item_id: LineEdit
var _form_name: LineEdit
var _form_character: LineEdit
var _form_gold: SpinBox
var _form_star: SpinBox
var _form_category: OptionButton
var _form_rarity: OptionButton
var _slots_vbox: VBoxContainer
var _duplicate_btn: Button
var _delete_btn: Button
var _save_btn: Button
var _skin_list_vbox: VBoxContainer
var _status_label: Label
var _form_panel: PanelContainer
var _no_sel_label: Label
var _form_item_id: LineEdit
var _form_name: LineEdit
var _form_character: LineEdit
var _form_gold: SpinBox
var _form_star: SpinBox
var _form_category: OptionButton
var _form_rarity: OptionButton
var _slots_vbox: VBoxContainer
var _duplicate_btn: Button
var _delete_btn: Button
var _save_btn: Button
# ─────────────────────────────────────────────────────────────────────────────
func _ready() -> void:
@@ -54,7 +54,7 @@ func _ready() -> void:
# UI Construction
# ─────────────────────────────────────────────────────────────────────────────
func _build_ui() -> void:
anchor_right = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
# Root VBox
@@ -137,13 +137,13 @@ func _build_ui() -> void:
# Form fields
form_vbox.add_child(_section_label("── Item Info ───────────────────────────"))
_form_item_id = _field(form_vbox, "Item ID", "e.g. oldpop_hat1")
_form_name = _field(form_vbox, "Display Name", "e.g. Oldpop Hat I")
_form_character = _field(form_vbox, "Character (node)", "e.g. Oldpop, Masbro, Bob")
_form_gold = _spinbox(form_vbox, "Gold Price", 0, 99999)
_form_star = _spinbox(form_vbox, "Star Price", 0, 99999)
_form_category = _option(form_vbox, "Category", CATEGORIES)
_form_rarity = _option(form_vbox, "Rarity", RARITIES)
_form_item_id = _field(form_vbox, "Item ID", "e.g. oldpop_hat1")
_form_name = _field(form_vbox, "Display Name", "e.g. Oldpop Hat I")
_form_character = _field(form_vbox, "Character (node)", "e.g. Oldpop, Masbro, Bob")
_form_gold = _spinbox(form_vbox, "Gold Price", 0, 99999)
_form_star = _spinbox(form_vbox, "Star Price", 0, 99999)
_form_category = _option(form_vbox, "Category", CATEGORIES)
_form_rarity = _option(form_vbox, "Rarity", RARITIES)
# ── Slots section ─────────────────────────────────────────────────────────
var slots_hdr := HBoxContainer.new()
@@ -280,23 +280,23 @@ func _on_list_item_pressed(idx: int) -> void:
# ─────────────────────────────────────────────────────────────────────────────
func _populate_form() -> void:
if _selected_idx < 0 or _selected_idx >= _data.size():
_form_panel.visible = false
_form_panel.visible = false
_no_sel_label.visible = true
_duplicate_btn.disabled = true
_delete_btn.disabled = true
_delete_btn.disabled = true
return
_form_panel.visible = true
_form_panel.visible = true
_no_sel_label.visible = false
_duplicate_btn.disabled = false
_delete_btn.disabled = false
_delete_btn.disabled = false
var e: Dictionary = _data[_selected_idx]
_form_item_id.text = e.get("item_id", "")
_form_name.text = e.get("name", "")
_form_character.text = e.get("character", "")
_form_gold.value = e.get("gold", 0)
_form_star.value = e.get("star", 0)
_form_item_id.text = e.get("item_id", "")
_form_name.text = e.get("name", "")
_form_character.text = e.get("character", "")
_form_gold.value = e.get("gold", 0)
_form_star.value = e.get("star", 0)
var cat_idx := CATEGORIES.find(e.get("category", "head"))
_form_category.selected = max(0, cat_idx)
@@ -355,13 +355,13 @@ func _commit_form() -> void:
if _selected_idx < 0 or _selected_idx >= _data.size():
return
var e: Dictionary = _data[_selected_idx]
e["item_id"] = _form_item_id.text.strip_edges()
e["name"] = _form_name.text.strip_edges()
e["item_id"] = _form_item_id.text.strip_edges()
e["name"] = _form_name.text.strip_edges()
e["character"] = _form_character.text.strip_edges()
e["gold"] = int(_form_gold.value)
e["star"] = int(_form_star.value)
e["category"] = CATEGORIES[_form_category.selected]
e["rarity"] = RARITIES[_form_rarity.selected]
e["gold"] = int(_form_gold.value)
e["star"] = int(_form_star.value)
e["category"] = CATEGORIES[_form_category.selected]
e["rarity"] = RARITIES[_form_rarity.selected]
# Read slots
var slots: Array = []
for row in _slots_vbox.get_children():
@@ -371,8 +371,8 @@ func _commit_form() -> void:
if ch.size() < 3:
continue
slots.append({
"mesh": (ch[0] as LineEdit).text.strip_edges(),
"mode": MODES[(ch[1] as OptionButton).selected],
"mesh": (ch[0] as LineEdit).text.strip_edges(),
"mode": MODES[(ch[1] as OptionButton).selected],
"material": (ch[2] as LineEdit).text.strip_edges(),
})
e["slots"] = slots
@@ -384,14 +384,14 @@ func _on_add_pressed() -> void:
if _selected_idx >= 0:
_commit_form()
_data.append({
"item_id": "new_skin_%d" % _data.size(),
"name": "New Skin",
"category": "head",
"item_id": "new_skin_%d" % _data.size(),
"name": "New Skin",
"category": "head",
"character": "",
"gold": 0,
"star": 0,
"rarity": "Common",
"slots": [],
"gold": 0,
"star": 0,
"rarity": "Common",
"slots": [],
})
_selected_idx = _data.size() - 1
_refresh_list()
@@ -454,7 +454,7 @@ func _on_save_pressed() -> void:
var err_gd := _generate_skin_manager()
var err_js := _generate_admin_js()
if err_gd == OK and err_js == OK:
_set_status("✓ skin_manager.gd and tekton_admin.js updated successfully!", Color(0.4, 1.0, 0.4))
_set_status("✓ skin_manager.gd and economy.js updated successfully!", Color(0.4, 1.0, 0.4))
else:
_set_status("⚠ Some files could not be updated — check the Output log.", Color.YELLOW)
@@ -465,7 +465,7 @@ func _generate_skin_manager() -> int:
if not FileAccess.file_exists(SKIN_MANAGER_PATH):
push_error("[SkinCatalogEditor] File not found: " + SKIN_MANAGER_PATH)
return ERR_FILE_NOT_FOUND
var f := FileAccess.open(SKIN_MANAGER_PATH, FileAccess.READ)
var f := FileAccess.open(SKIN_MANAGER_PATH, FileAccess.READ)
var src: String = f.get_as_text()
f.close()
@@ -516,20 +516,20 @@ func _generate_skin_manager() -> int:
return OK
# ─────────────────────────────────────────────────────────────────────────────
# Code Generation — tekton_admin.js
# Code Generation — economy.js
# ─────────────────────────────────────────────────────────────────────────────
func _generate_admin_js() -> int:
if not FileAccess.file_exists(ADMIN_JS_PATH):
push_error("[SkinCatalogEditor] File not found: " + ADMIN_JS_PATH)
return ERR_FILE_NOT_FOUND
var f := FileAccess.open(ADMIN_JS_PATH, FileAccess.READ)
var f := FileAccess.open(ADMIN_JS_PATH, FileAccess.READ)
var src: String = f.get_as_text()
f.close()
var b := src.find(BEGIN_SHOP)
var e := src.find(END_SHOP)
if b == -1 or e == -1:
push_error("[SkinCatalogEditor] Sentinel markers not found in tekton_admin.js")
push_error("[SkinCatalogEditor] Sentinel markers not found in economy.js")
return ERR_INVALID_DATA
var lines: PackedStringArray = []
@@ -545,12 +545,12 @@ func _generate_admin_js() -> int:
var char_val: String = entry.get("character", "")
var char_part: String = (", character: \"%s\"" % char_val) if not char_val.is_empty() else ""
lines.append(" { id: \"%s\", name: \"%s\", category: \"%s\", gold: %d, star: %d, rarity: \"%s\"%s }," % [
entry.get("item_id", ""),
entry.get("name", ""),
entry.get("item_id", ""),
entry.get("name", ""),
cat,
entry.get("gold", 0),
entry.get("star", 0),
entry.get("rarity", "Common"),
entry.get("gold", 0),
entry.get("star", 0),
entry.get("rarity", "Common"),
char_part,
])