236 lines
7.7 KiB
GDScript
236 lines
7.7 KiB
GDScript
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)
|