stepstead-client/scripts/vendor_shop.gd
jts 6a1706051d İlk commit: Stepstead Godot istemci (4.6 Mobile)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 11:31:35 +03:00

236 lines
7.7 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.

extends PanelContainer
## Satıcı dükkanı — bir NPC vendor'ın stoğu. Şehir haritasındaki pine tıklayınca açılır.
## İki sekme: 🛒 Satın Al (sabit stok + oyuncu konsinyeleri, ömür rozetli) / 💰 Sat.
## Kod ile kurulur. Node adı "VendorShop". Alt-sheet.
const PANEL_HEIGHT := 640.0
const RARITY_COLOR := {
"common": Color(0.8, 0.8, 0.8), "uncommon": Color(0.4, 0.85, 0.4),
"rare": Color(0.35, 0.6, 1.0), "epic": Color(0.7, 0.45, 0.95),
"legendary": Color(1.0, 0.65, 0.2),
}
var _npc_id := 0
var _npc_name := ""
var _tab := "buy" # "buy" | "sell"
var _title: Label
var _info: Label
var _tab_buy: Button
var _tab_sell: Button
var _list: VBoxContainer
func _ready() -> void:
name = "VendorShop"
visible = false
_build_ui()
func _build_ui() -> void:
set_anchors_preset(Control.PRESET_BOTTOM_WIDE)
offset_top = -PANEL_HEIGHT
offset_left = 8
offset_right = -8
offset_bottom = -8
var sb := StyleBoxFlat.new()
sb.bg_color = Color(0.12, 0.11, 0.09, 0.98)
sb.set_corner_radius_all(10)
add_theme_stylebox_override("panel", sb)
var margin := MarginContainer.new()
margin.add_theme_constant_override("margin_left", 12)
margin.add_theme_constant_override("margin_right", 12)
margin.add_theme_constant_override("margin_top", 8)
margin.add_theme_constant_override("margin_bottom", 8)
add_child(margin)
var vbox := VBoxContainer.new()
vbox.add_theme_constant_override("separation", 6)
margin.add_child(vbox)
var top := HBoxContainer.new()
_title = Label.new()
_title.add_theme_font_size_override("font_size", 18)
_title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
top.add_child(_title)
var close_btn := Button.new()
close_btn.text = ""
close_btn.pressed.connect(func() -> void: visible = false)
top.add_child(close_btn)
vbox.add_child(top)
_info = Label.new()
_info.add_theme_font_size_override("font_size", 13)
_info.add_theme_color_override("font_color", Color(0.95, 0.8, 0.35))
vbox.add_child(_info)
var tabs := HBoxContainer.new()
tabs.add_theme_constant_override("separation", 6)
_tab_buy = _tab_btn(tabs, "🛒 Satın Al", "buy")
_tab_sell = _tab_btn(tabs, "💰 Sat", "sell")
vbox.add_child(tabs)
var scroll := ScrollContainer.new()
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
vbox.add_child(scroll)
_list = VBoxContainer.new()
_list.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_list.add_theme_constant_override("separation", 6)
scroll.add_child(_list)
func _tab_btn(parent: HBoxContainer, label: String, key: String) -> Button:
var b := Button.new()
b.text = label
b.toggle_mode = true
b.size_flags_horizontal = Control.SIZE_EXPAND_FILL
b.pressed.connect(func() -> void:
_tab = key
_sync_tabs()
refresh())
parent.add_child(b)
return b
func _sync_tabs() -> void:
_tab_buy.button_pressed = _tab == "buy"
_tab_sell.button_pressed = _tab == "sell"
func open(npc_id: int, npc_name: String = "") -> void:
_npc_id = npc_id
_npc_name = npc_name
_tab = "buy"
_sync_tabs()
visible = true
refresh()
func refresh() -> void:
_info.text = "🪙 %d" % int(GameState.character.get("gold", 0))
for c in _list.get_children():
c.queue_free()
if _tab == "buy":
await _render_buy()
else:
await _render_sell()
func _muted(txt: String) -> Label:
var l := Label.new()
l.text = txt
l.add_theme_color_override("font_color", Color(0.6, 0.6, 0.6))
return l
func _render_buy() -> void:
var res: Dictionary = await GameState.npc_shop(_npc_id)
if not res.ok or not (res.body is Dictionary):
_list.add_child(_muted("Dükkan yüklenemedi."))
return
var body: Dictionary = res.body
_title.text = "🏪 %s" % str(body.get("npc_name", _npc_name))
var role := str(body.get("role", ""))
if role != "":
_title.text += " (%s)" % role
var items: Array = body.get("items", [])
if items.is_empty():
_list.add_child(_muted("Bu satıcının stoğu boş."))
return
var gold := int(GameState.character.get("gold", 0))
for it in items:
if it is Dictionary:
_list.add_child(_buy_row(it, gold))
func _buy_row(it: Dictionary, gold: int) -> HBoxContainer:
var row := HBoxContainer.new()
row.add_theme_constant_override("separation", 8)
var info := VBoxContainer.new()
info.size_flags_horizontal = Control.SIZE_EXPAND_FILL
var name_lbl := Label.new()
name_lbl.text = str(it.get("item_name", ""))
name_lbl.add_theme_font_size_override("font_size", 15)
name_lbl.add_theme_color_override("font_color", RARITY_COLOR.get(str(it.get("rarity", "common")), Color.WHITE))
info.add_child(name_lbl)
var meta := Label.new()
var price := int(it.get("unit_price", 0))
var src := str(it.get("source", "base"))
if src == "player":
var exp := int(it.get("expires_in_h", 0))
var qty := int(it.get("qty", 0))
meta.text = "🪙 %d • kalan %d • ⏳ %ds (oyuncu malı)" % [price, qty, exp]
meta.add_theme_color_override("font_color", Color(0.7, 0.75, 0.85))
else:
meta.text = "🪙 %d • sınırsız" % price
meta.add_theme_color_override("font_color", Color(0.6, 0.62, 0.58))
meta.add_theme_font_size_override("font_size", 12)
info.add_child(meta)
row.add_child(info)
var qty_spin := SpinBox.new()
qty_spin.min_value = 1
qty_spin.max_value = maxi(1, int(it.get("qty", 10))) if src == "player" else 10
qty_spin.value = 1
qty_spin.custom_minimum_size = Vector2(70, 0)
row.add_child(qty_spin)
var btn := Button.new()
if gold < price:
btn.text = "🪙 az"
btn.disabled = true
else:
btn.text = "Al"
btn.pressed.connect(_on_buy.bind(btn, str(it.get("item_code", "")), qty_spin))
row.add_child(btn)
return row
func _render_sell() -> void:
var inv: Array = GameState.inventory
var sellable := inv.filter(func(r: Variant) -> bool: return r is Dictionary and int(r.get("qty", 0)) > 0)
if sellable.is_empty():
_list.add_child(_muted("Envanterin boş."))
return
_list.add_child(_muted("Satıcıya sat → gold. (Satıcının normalde satmadığı ürün 24s dükkanına düşer.)"))
for r in sellable:
_list.add_child(_sell_row(r))
func _sell_row(r: Dictionary) -> HBoxContainer:
var row := HBoxContainer.new()
row.add_theme_constant_override("separation", 8)
var name_lbl := Label.new()
name_lbl.text = "%s ×%d" % [str(r.get("item_name", "")), int(r.get("qty", 0))]
name_lbl.add_theme_font_size_override("font_size", 15)
name_lbl.add_theme_color_override("font_color", RARITY_COLOR.get(str(r.get("rarity", "common")), Color.WHITE))
name_lbl.size_flags_horizontal = Control.SIZE_EXPAND_FILL
row.add_child(name_lbl)
var qty_spin := SpinBox.new()
qty_spin.min_value = 1
qty_spin.max_value = maxi(1, int(r.get("qty", 1)))
qty_spin.value = 1
qty_spin.custom_minimum_size = Vector2(70, 0)
row.add_child(qty_spin)
var btn := Button.new()
btn.text = "Sat"
btn.pressed.connect(_on_sell.bind(btn, str(r.get("item_code", "")), qty_spin))
row.add_child(btn)
return row
func _on_buy(btn: Button, code: String, qty_spin: SpinBox) -> void:
btn.disabled = true
var res: Dictionary = await GameState.npc_buy(_npc_id, code, int(qty_spin.value))
_toast(res, "✅ Satın alındı", "🚫 Alınamadı")
await refresh()
func _on_sell(btn: Button, code: String, qty_spin: SpinBox) -> void:
btn.disabled = true
var res: Dictionary = await GameState.npc_sell(_npc_id, code, int(qty_spin.value))
_toast(res, "✅ Satıldı", "🚫 Satılamadı")
await refresh()
func _toast(res: Dictionary, ok_msg: String, fail_prefix: String) -> void:
var toast := get_tree().get_first_node_in_group("toast")
var msg := ok_msg
var col := Color(0.6, 0.85, 0.5)
if not res.ok:
col = Color(0.95, 0.5, 0.4)
msg = fail_prefix
if res.body is Dictionary and res.body.has("detail"):
msg = "%s: %s" % [fail_prefix, str(res.body["detail"])]
if toast != null and toast.has_method("show_toast"):
toast.call("show_toast", msg, col)