extends PanelContainer ## Pazar paneli — oyuncu ilanları + NPC vendor. Kod ile kurulur, hud.gd ekler. ## Node adı "MarketPanel" (world.gd blokaj kontrolü). ## Görüntüleme her yerden; ilan verme / satın alma / vendor satışı sadece pazar'da ## (sunucu zaten doğrular, UI da butonları kilitler). const PANEL_HEIGHT := 640.0 const MARKET_LOCATION := "pazar" var _tab_listings: Button var _tab_sell: Button var _tab_stock: Button var _list: VBoxContainer var _scroll: ScrollContainer var _search: LineEdit var _info: Label var _tab := "listings" # "listings" | "sell" | "stock" func _ready() -> void: name = "MarketPanel" 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.13, 0.11, 0.08, 0.97) 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() var title := Label.new() title.text = "🏪 Pazar" title.add_theme_font_size_override("font_size", 18) title.size_flags_horizontal = Control.SIZE_EXPAND_FILL top.add_child(title) _tab_listings = Button.new() _tab_listings.text = "📜 İlanlar" _tab_listings.toggle_mode = true _tab_listings.button_pressed = true _tab_listings.pressed.connect(func() -> void: _switch("listings")) top.add_child(_tab_listings) _tab_sell = Button.new() _tab_sell.text = "🎒 Sat" _tab_sell.toggle_mode = true _tab_sell.pressed.connect(func() -> void: _switch("sell")) top.add_child(_tab_sell) _tab_stock = Button.new() _tab_stock.text = "🧰 Vendor" _tab_stock.toggle_mode = true _tab_stock.pressed.connect(func() -> void: _switch("stock")) top.add_child(_tab_stock) 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.75, 0.7, 0.55)) vbox.add_child(_info) _search = LineEdit.new() _search.placeholder_text = "🔍 Ara…" _search.text_submitted.connect(func(_t: String) -> void: refresh()) vbox.add_child(_search) _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 _at_market() -> bool: return str(GameState.character.get("location_code", "")) == MARKET_LOCATION func open() -> void: visible = true refresh() func _switch(tab: String) -> void: _tab = tab _tab_listings.button_pressed = tab == "listings" _tab_sell.button_pressed = tab == "sell" _tab_stock.button_pressed = tab == "stock" _search.visible = tab == "listings" refresh() func refresh() -> void: _info.text = ("🪙 %d" % int(GameState.character.get("gold", 0))) + \ ("" if _at_market() else " • ⚠ İşlem için Pazar'a git (sadece vitrin)") for c in _list.get_children(): c.queue_free() if _tab == "listings": await _render_listings() elif _tab == "sell": await _render_sell() else: await _render_stock() func _render_listings() -> void: var rows: Array = await GameState.market_listings(_search.text.strip_edges()) if rows.is_empty(): _list.add_child(_muted("Açık ilan yok.")) return for r in rows: if r is Dictionary: _list.add_child(_listing_row(r)) func _listing_row(r: Dictionary) -> HBoxContainer: var row := HBoxContainer.new() row.add_theme_constant_override("separation", 10) var info := VBoxContainer.new() info.size_flags_horizontal = Control.SIZE_EXPAND_FILL 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) info.add_child(name_lbl) var meta := Label.new() meta.text = "🪙 %d/adet • %s" % [int(r.get("unit_price", 0)), str(r.get("seller_name", ""))] meta.add_theme_font_size_override("font_size", 12) meta.add_theme_color_override("font_color", Color(0.6, 0.58, 0.5)) info.add_child(meta) row.add_child(info) var btn := Button.new() if bool(r.get("mine", false)): btn.text = "İptal" btn.pressed.connect(_on_cancel.bind(btn, int(r.get("id", 0)))) else: var total: int = int(r.get("qty", 0)) * int(r.get("unit_price", 0)) btn.text = "Al (🪙 %d)" % total if not _at_market(): btn.disabled = true elif int(GameState.character.get("gold", 0)) < total: btn.text = "🪙 yetersiz" btn.disabled = true else: btn.pressed.connect(_on_buy.bind(btn, int(r.get("id", 0)))) row.add_child(btn) return row func _render_sell() -> void: var rows: Array = await GameState.vendor_prices() if rows.is_empty(): _list.add_child(_muted("Envanterin boş.")) return var at_market := _at_market() for r in rows: if r is Dictionary: _list.add_child(_sell_row(r, at_market)) func _sell_row(r: Dictionary, at_market: bool) -> VBoxContainer: var box := VBoxContainer.new() 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.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 = int(r.get("qty", 1)) qty_spin.value = 1 row.add_child(qty_spin) var vend_btn := Button.new() vend_btn.text = "Vendor 🪙 %d" % int(r.get("unit_price", 0)) vend_btn.disabled = not at_market vend_btn.pressed.connect(_on_vendor_sell.bind(vend_btn, str(r.get("item_code", "")), qty_spin)) row.add_child(vend_btn) box.add_child(row) var row2 := HBoxContainer.new() row2.add_theme_constant_override("separation", 8) var spacer := Control.new() spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL row2.add_child(spacer) var price_lbl := Label.new() price_lbl.text = "İlan fiyatı:" price_lbl.add_theme_font_size_override("font_size", 12) row2.add_child(price_lbl) var price_spin := SpinBox.new() price_spin.min_value = 1 price_spin.max_value = 1000000 price_spin.value = maxi(1, int(r.get("unit_price", 1)) * 2) row2.add_child(price_spin) var list_btn := Button.new() list_btn.text = "📜 İlan Ver" list_btn.disabled = not at_market list_btn.pressed.connect(_on_list.bind(list_btn, str(r.get("item_code", "")), qty_spin, price_spin)) row2.add_child(list_btn) box.add_child(row2) return box func _render_stock() -> void: var rows: Array = await GameState.vendor_stock() if rows.is_empty(): _list.add_child(_muted("Vendor stoku yüklenemedi.")) return var at_market := _at_market() var gold := int(GameState.character.get("gold", 0)) for r in rows: if r is Dictionary: _list.add_child(_stock_row(r, at_market, gold)) func _stock_row(r: Dictionary, at_market: bool, gold: int) -> HBoxContainer: var row := HBoxContainer.new() row.add_theme_constant_override("separation", 8) var name_lbl := Label.new() name_lbl.text = str(r.get("item_name", "")) name_lbl.add_theme_font_size_override("font_size", 15) 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 = 10 qty_spin.value = 1 row.add_child(qty_spin) var price := int(r.get("unit_price", 0)) var btn := Button.new() btn.text = "Al 🪙 %d" % price if not at_market: btn.disabled = true elif gold < price: btn.text = "🪙 yetersiz" btn.disabled = true else: btn.pressed.connect(_on_vendor_buy.bind(btn, str(r.get("item_code", "")), qty_spin)) row.add_child(btn) return row func _on_vendor_buy(btn: Button, code: String, qty_spin: SpinBox) -> void: btn.disabled = true var res: Dictionary = await GameState.vendor_buy(code, int(qty_spin.value)) _report(res) refresh() func _on_buy(btn: Button, listing_id: int) -> void: btn.disabled = true var res: Dictionary = await GameState.market_buy(listing_id) _report(res) refresh() func _on_cancel(btn: Button, listing_id: int) -> void: btn.disabled = true var res: Dictionary = await GameState.market_cancel(listing_id) _report(res) refresh() func _on_vendor_sell(btn: Button, code: String, qty_spin: SpinBox) -> void: btn.disabled = true var res: Dictionary = await GameState.vendor_sell(code, int(qty_spin.value)) _report(res) refresh() func _on_list(btn: Button, code: String, qty_spin: SpinBox, price_spin: SpinBox) -> void: btn.disabled = true var res: Dictionary = await GameState.market_list(code, int(qty_spin.value), int(price_spin.value)) _report(res) refresh() func _report(res: Dictionary) -> void: if not res.ok and res.body is Dictionary and res.body.has("detail"): _info.text = "⚠ " + str(res.body["detail"]) func _muted(text: String) -> Label: var l := Label.new() l.text = text l.add_theme_color_override("font_color", Color(0.6, 0.6, 0.6)) return l