feat: take_powerup VFX, rank fix, admin chat management
- Wire take_powerup AnimatedSprite3D on powerup pickup via add_powerup_from_item() - Make take_powerup animation one-shot (loop: false) - Fix rank Position label hidden at game start (visible = false, only shows when score > 0) - Competition ranking for tied scores in main.gd - Lobby Chat admin tab: system prefix, max messages, wipe, purge old, save config - Chat Storage admin tab: list/browse/delete individual channel messages - Backend RPCs: admin_get_chat_config, admin_set_chat_config, admin_purge_old_messages, admin_list_channel_messages, admin_delete_channel_message - Chat config applied on lobby join (max_messages, prefix injection)
This commit is contained in:
@@ -153,6 +153,12 @@ func add_powerup_from_item(item_id: int):
|
||||
var effect = get_effect_from_item(item_id)
|
||||
if effect == -1: return
|
||||
|
||||
# VFX: show pickup burst on all peers (mirrors skill VFX pattern)
|
||||
if player.is_multiplayer_authority() and player.has_method("can_rpc") and player.can_rpc():
|
||||
player.rpc("play_skill_vfx", "take_powerup")
|
||||
elif player.has_method("play_skill_vfx"):
|
||||
player.play_skill_vfx("take_powerup")
|
||||
|
||||
# 1-PowerUp Rule: If this is a DIFFERENT power-up, clear the old one
|
||||
var is_different = not inventory.get(effect, false)
|
||||
var already_has_any = false
|
||||
|
||||
@@ -149,6 +149,21 @@ func _parse_error_msg(msg: String) -> String:
|
||||
func admin_clear_global_chat(payload: String) -> Dictionary:
|
||||
return await api_rpc_async("admin_clear_global_chat", payload)
|
||||
|
||||
func admin_get_chat_config() -> Dictionary:
|
||||
return await api_rpc_async("admin_get_chat_config", "{}")
|
||||
|
||||
func admin_set_chat_config(config: Dictionary) -> Dictionary:
|
||||
return await api_rpc_async("admin_set_chat_config", JSON.stringify(config))
|
||||
|
||||
func admin_purge_old_messages(channel_id: String, max_age_days: int) -> Dictionary:
|
||||
return await api_rpc_async("admin_purge_old_messages", JSON.stringify({"channel_id": channel_id, "max_age_days": max_age_days}))
|
||||
|
||||
func admin_list_channel_messages(channel_id: String, limit: int = 50, cursor: String = "", forward: bool = true) -> Dictionary:
|
||||
return await api_rpc_async("admin_list_channel_messages", JSON.stringify({"channel_id": channel_id, "limit": limit, "cursor": cursor, "forward": forward}))
|
||||
|
||||
func admin_delete_channel_message(channel_id: String, message_id: String) -> Dictionary:
|
||||
return await api_rpc_async("admin_delete_channel_message", JSON.stringify({"channel_id": channel_id, "message_id": message_id}))
|
||||
|
||||
func send_friend_request(target_id: String) -> Dictionary:
|
||||
var payload = JSON.stringify({"target_user_id": target_id})
|
||||
return await api_rpc_async("send_friend_request", payload)
|
||||
|
||||
@@ -69,6 +69,27 @@ var _all_server_mails: Array = []
|
||||
@onready var load_banners_btn := %LoadBannersBtn as Button
|
||||
@onready var save_banners_btn := %SaveBannersBtn as Button
|
||||
|
||||
# Tab: Lobby Chat
|
||||
@onready var chat_prefix_edit := %PrefixEdit as LineEdit
|
||||
@onready var chat_max_msg_spin := %MaxMsgSpin as SpinBox
|
||||
@onready var chat_max_age_spin := %MaxAgeSpin as SpinBox
|
||||
@onready var chat_wipe_btn := %WipeChatBtn as Button
|
||||
@onready var chat_purge_btn := %PurgeOldBtn as Button
|
||||
@onready var chat_save_btn := %SaveConfigBtn as Button
|
||||
@onready var chat_status_label := %ChatStatusLabel as Label
|
||||
|
||||
# Tab: Chat Storage
|
||||
@onready var chat_channel_id_edit := %ChannelIdEdit as LineEdit
|
||||
@onready var load_messages_btn := %LoadMessagesBtn as Button
|
||||
@onready var chat_tree := %ChatTree as Tree
|
||||
@onready var refresh_chat_btn := %RefreshChatBtn as Button
|
||||
@onready var delete_selected_btn := %DeleteSelectedBtn as Button
|
||||
|
||||
var _chat_tree_root: TreeItem
|
||||
var _chat_channel_id: String = ""
|
||||
var _chat_cursor: String = ""
|
||||
var _chat_messages_data: Array = []
|
||||
|
||||
const MONTH_NAMES = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
|
||||
|
||||
# -- Data --
|
||||
@@ -175,6 +196,17 @@ func _setup_columns() -> void:
|
||||
mail_tree.set_column_expand(5, false)
|
||||
_mail_root = mail_tree.create_item()
|
||||
|
||||
# Chat Storage
|
||||
chat_tree.set_column_title(0, "Sender")
|
||||
chat_tree.set_column_title(1, "Content")
|
||||
chat_tree.set_column_title(2, "Date")
|
||||
chat_tree.set_column_title(3, "ID")
|
||||
chat_tree.set_column_custom_minimum_width(0, 100)
|
||||
chat_tree.set_column_expand(1, true)
|
||||
chat_tree.set_column_custom_minimum_width(2, 120)
|
||||
chat_tree.set_column_custom_minimum_width(3, 100)
|
||||
_chat_tree_root = chat_tree.create_item()
|
||||
|
||||
func _connect_signals() -> void:
|
||||
close_btn.pressed.connect(_on_close)
|
||||
refresh_btn.pressed.connect(_on_refresh)
|
||||
@@ -217,6 +249,16 @@ func _connect_signals() -> void:
|
||||
load_banners_btn.pressed.connect(func(): await _load_featured_banners())
|
||||
save_banners_btn.pressed.connect(func(): await _save_featured_banners())
|
||||
|
||||
# Chat actions
|
||||
chat_wipe_btn.pressed.connect(_on_wipe_chat)
|
||||
chat_purge_btn.pressed.connect(_on_purge_old_chat)
|
||||
chat_save_btn.pressed.connect(_on_save_chat_config)
|
||||
|
||||
# Chat Storage actions
|
||||
load_messages_btn.pressed.connect(_on_load_chat_messages)
|
||||
refresh_chat_btn.pressed.connect(_on_load_chat_messages)
|
||||
delete_selected_btn.pressed.connect(_on_delete_chat_message)
|
||||
|
||||
# =============================================================================
|
||||
# Core Panel Logic
|
||||
# =============================================================================
|
||||
@@ -243,6 +285,10 @@ func _on_tab_changed(tab_index: int) -> void:
|
||||
await _load_mail()
|
||||
elif tab_index == 5:
|
||||
await _load_featured_banners()
|
||||
elif tab_index == 6:
|
||||
await _load_chat_config()
|
||||
elif tab_index == 7:
|
||||
await _on_load_chat_messages()
|
||||
|
||||
# =============================================================================
|
||||
# RPC Helper
|
||||
@@ -1193,3 +1239,159 @@ func _save_featured_banners() -> void:
|
||||
_set_status("Save failed: " + str(res.error), CLR_STATUS_ERR)
|
||||
elif res.has("success"):
|
||||
_set_status("Banners saved! (%d slots)" % banners.size(), CLR_STATUS_OK)
|
||||
|
||||
# =============================================================================
|
||||
# TAB 7: LOBBY CHAT
|
||||
# =============================================================================
|
||||
func _load_chat_config() -> void:
|
||||
chat_status_label.text = "Loading config..."
|
||||
var res := await _rpc("admin_get_chat_config", {})
|
||||
if res.has("error"):
|
||||
chat_status_label.text = "Failed: " + str(res.error)
|
||||
return
|
||||
|
||||
var config: Dictionary = res.get("config", {})
|
||||
chat_prefix_edit.text = config.get("prefix", "")
|
||||
chat_max_msg_spin.value = config.get("max_messages", 50)
|
||||
chat_max_age_spin.value = config.get("max_age_days", 0)
|
||||
chat_status_label.text = ""
|
||||
|
||||
func _on_wipe_chat() -> void:
|
||||
var confirm := ConfirmationDialog.new()
|
||||
confirm.title = "Wipe Entire Lobby Chat?"
|
||||
confirm.dialog_text = "This will delete ALL messages in the global lobby chat for everyone. Continue?"
|
||||
add_child(confirm)
|
||||
confirm.popup_centered()
|
||||
confirm.confirmed.connect(func():
|
||||
chat_status_label.text = "Wiping chat..."
|
||||
var lobby = get_tree().get_first_node_in_group("Lobby")
|
||||
if lobby and lobby.has_method("admin_wipe_chat"):
|
||||
lobby.admin_wipe_chat()
|
||||
chat_status_label.text = "Chat wiped!"
|
||||
else:
|
||||
chat_status_label.text = "Lobby not found — cannot wipe."
|
||||
confirm.queue_free()
|
||||
)
|
||||
|
||||
func _on_purge_old_chat() -> void:
|
||||
var max_age: int = int(chat_max_age_spin.value)
|
||||
if max_age <= 0:
|
||||
chat_status_label.text = "Set 'Delete older than' to > 0 days first."
|
||||
return
|
||||
|
||||
var confirm := ConfirmationDialog.new()
|
||||
confirm.title = "Purge Old Messages?"
|
||||
confirm.dialog_text = "Delete all messages older than %d days?" % max_age
|
||||
add_child(confirm)
|
||||
confirm.popup_centered()
|
||||
confirm.confirmed.connect(func():
|
||||
chat_status_label.text = "Purging old messages..."
|
||||
var lobby = get_tree().get_first_node_in_group("Lobby")
|
||||
if lobby and lobby.has_method("admin_purge_chat"):
|
||||
var deleted: int = await lobby.admin_purge_chat(max_age)
|
||||
chat_status_label.text = "Purged %d old messages." % deleted
|
||||
else:
|
||||
chat_status_label.text = "Lobby not found — cannot purge."
|
||||
confirm.queue_free()
|
||||
)
|
||||
|
||||
func _on_save_chat_config() -> void:
|
||||
chat_status_label.text = "Saving..."
|
||||
var config := {
|
||||
"prefix": chat_prefix_edit.text.strip_edges(),
|
||||
"max_messages": int(chat_max_msg_spin.value),
|
||||
"max_age_days": int(chat_max_age_spin.value)
|
||||
}
|
||||
var res := await _rpc("admin_set_chat_config", config)
|
||||
if res.has("error"):
|
||||
chat_status_label.text = "Failed: " + str(res.error)
|
||||
else:
|
||||
chat_status_label.text = "Chat config saved!"
|
||||
|
||||
# =============================================================================
|
||||
# TAB 8: CHAT STORAGE
|
||||
# =============================================================================
|
||||
func _on_load_chat_messages() -> void:
|
||||
var channel_id := chat_channel_id_edit.text.strip_edges()
|
||||
if channel_id.is_empty():
|
||||
_set_status("Enter a Channel ID first.", CLR_STATUS_ERR)
|
||||
return
|
||||
|
||||
_chat_channel_id = channel_id
|
||||
_chat_cursor = ""
|
||||
_chat_messages_data.clear()
|
||||
_clear_tree(chat_tree, _chat_tree_root)
|
||||
|
||||
await _fetch_chat_messages_batch()
|
||||
|
||||
func _fetch_chat_messages_batch() -> void:
|
||||
_set_status("Loading messages...")
|
||||
var payload := {
|
||||
"channel_id": _chat_channel_id,
|
||||
"limit": 50,
|
||||
"cursor": _chat_cursor,
|
||||
"forward": false
|
||||
}
|
||||
var res := await _rpc("admin_list_channel_messages", payload)
|
||||
|
||||
if res.has("error"):
|
||||
_set_status("Failed: " + str(res.error), CLR_STATUS_ERR)
|
||||
return
|
||||
|
||||
var msgs = res.get("messages", [])
|
||||
var next_cursor = res.get("next_cursor", "")
|
||||
|
||||
for msg in msgs:
|
||||
_chat_messages_data.append(msg)
|
||||
var item := _chat_tree_root.create_child()
|
||||
item.set_text(0, msg.get("username", msg.get("sender_id", "?").substr(0, 8)))
|
||||
item.set_text(1, msg.get("content", ""))
|
||||
item.set_text(2, msg.get("create_time", "").substr(0, 19).replace("T", " "))
|
||||
var mid = msg.get("message_id", "")
|
||||
item.set_text(3, mid)
|
||||
item.set_tooltip_text(3, mid)
|
||||
item.set_metadata(0, msg)
|
||||
|
||||
count_label.text = "%d messages loaded" % _chat_messages_data.size()
|
||||
|
||||
if not next_cursor.is_empty():
|
||||
_chat_cursor = next_cursor
|
||||
_set_status("Loaded page. Click Refresh to load more.", CLR_STATUS_OK)
|
||||
else:
|
||||
_chat_cursor = ""
|
||||
_set_status("All messages loaded.", CLR_STATUS_OK)
|
||||
|
||||
func _on_delete_chat_message() -> void:
|
||||
var item = chat_tree.get_selected()
|
||||
if not item:
|
||||
_set_status("Select a message to delete.", CLR_STATUS_ERR)
|
||||
return
|
||||
|
||||
var msg = item.get_metadata(0)
|
||||
if not msg:
|
||||
return
|
||||
|
||||
var msg_id = msg.get("message_id", "")
|
||||
if msg_id.is_empty():
|
||||
return
|
||||
|
||||
var confirm := ConfirmationDialog.new()
|
||||
confirm.title = "Delete Message?"
|
||||
confirm.dialog_text = "Permanently delete message from " + msg.get("username", "?") + "?"
|
||||
add_child(confirm)
|
||||
confirm.popup_centered()
|
||||
confirm.confirmed.connect(func():
|
||||
_set_status("Deleting message...")
|
||||
var res = await _rpc("admin_delete_channel_message", {
|
||||
"channel_id": _chat_channel_id,
|
||||
"message_id": msg_id
|
||||
})
|
||||
if res.get("success", false):
|
||||
_set_status("Message deleted!", CLR_STATUS_OK)
|
||||
chat_tree.get_root().remove_child(item)
|
||||
item.free()
|
||||
count_label.text = "%d messages loaded" % _chat_messages_data.size()
|
||||
else:
|
||||
_set_status("Failed: " + str(res.get("error", "")), CLR_STATUS_ERR)
|
||||
confirm.queue_free()
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user