extends PanelContainer ## Sohbet paneli — kod ile kurulur (tscn'de yok), WsClient chat sinyallerini dinler. ## hud.gd oluşturup HUD CanvasLayer'ına ekler. Node adı "ChatPanel" (world.gd blokaj kontrolü). const MAX_ROWS := 60 const PANEL_HEIGHT := 480.0 var _list: VBoxContainer var _scroll: ScrollContainer var _input: LineEdit var _tab_global: Button var _tab_local: Button ## Aktif kanal: "global" | "local". Mesajlar kanal başına bellekte tutulur, ## sekme değişince yeniden çizilir. var _channel := "global" var _msgs := {"global": [], "local": []} func _ready() -> void: name = "ChatPanel" visible = false _build_ui() WsClient.chat_received.connect(_on_chat) WsClient.chat_history.connect(_on_history) WsClient.local_chat_history.connect(_on_local_history) # Lokasyon değişince yerel geçmiş bayatlar GameState.traveled.connect(func(_p: Dictionary) -> void: _msgs["local"] = [] if visible and _channel == "local": WsClient.request_local_history()) 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.08, 0.1, 0.13, 0.95) 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 = "💬 Sohbet" title.add_theme_font_size_override("font_size", 18) title.size_flags_horizontal = Control.SIZE_EXPAND_FILL top.add_child(title) _tab_global = Button.new() _tab_global.text = "🌍 Genel" _tab_global.toggle_mode = true _tab_global.button_pressed = true _tab_global.pressed.connect(func() -> void: _switch_channel("global")) top.add_child(_tab_global) _tab_local = Button.new() _tab_local.text = "📍 Burada" _tab_local.toggle_mode = true _tab_local.pressed.connect(func() -> void: _switch_channel("local")) top.add_child(_tab_local) 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) _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", 4) _scroll.add_child(_list) var bottom := HBoxContainer.new() bottom.add_theme_constant_override("separation", 8) _input = LineEdit.new() _input.placeholder_text = "Mesaj yaz…" _input.max_length = 300 _input.size_flags_horizontal = Control.SIZE_EXPAND_FILL _input.text_submitted.connect(func(_t: String) -> void: _send()) bottom.add_child(_input) var send_btn := Button.new() send_btn.text = "Gönder" send_btn.pressed.connect(_send) bottom.add_child(send_btn) vbox.add_child(bottom) func open() -> void: visible = true if _channel == "local" and (_msgs["local"] as Array).is_empty(): WsClient.request_local_history() _scroll_to_bottom() _input.grab_focus() func _switch_channel(ch: String) -> void: _channel = ch _tab_global.button_pressed = ch == "global" _tab_local.button_pressed = ch == "local" _input.placeholder_text = "Mesaj yaz…" if ch == "global" else "Buradakilere yaz…" if ch == "local": WsClient.request_local_history() _rerender() func _send() -> void: var msg := _input.text.strip_edges() if msg == "": return WsClient.send_chat(msg, _channel) _input.text = "" _input.grab_focus() func _on_history(messages: Array) -> void: _msgs["global"] = messages.duplicate() if _channel == "global": _rerender() func _on_local_history(messages: Array) -> void: _msgs["local"] = messages.duplicate() if _channel == "local": _rerender() func _on_chat(msg: Dictionary) -> void: var ch := str(msg.get("channel", "global")) if not _msgs.has(ch): return var arr: Array = _msgs[ch] arr.append(msg) while arr.size() > MAX_ROWS: arr.pop_front() if ch == _channel: _append_row(msg) while _list.get_child_count() > MAX_ROWS: var oldest := _list.get_child(0) _list.remove_child(oldest) oldest.queue_free() _scroll_to_bottom() func _rerender() -> void: for c in _list.get_children(): c.queue_free() for m in (_msgs[_channel] as Array): if m is Dictionary: _append_row(m) _scroll_to_bottom() func _append_row(msg: Dictionary) -> void: var own: bool = int(msg.get("char_id", 0)) == int(GameState.character.get("id", -1)) var row := RichTextLabel.new() row.bbcode_enabled = true row.fit_content = true row.scroll_active = false row.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART row.size_flags_horizontal = Control.SIZE_EXPAND_FILL var name_color := "#7ee08a" if own else "#e0c060" row.text = "[color=%s]%s:[/color] %s" % [ name_color, str(msg.get("name", "?")).replace("[", "["), str(msg.get("text", "")).replace("[", "["), ] _list.add_child(row) func _scroll_to_bottom() -> void: if not visible: return await get_tree().process_frame _scroll.scroll_vertical = int(_scroll.get_v_scroll_bar().max_value)