From d6c84dd30d7e1d7e7b6d4a8977905429e70abb57 Mon Sep 17 00:00:00 2001 From: adtpdn Date: Mon, 2 Feb 2026 00:21:32 +0800 Subject: [PATCH] update --- .vscode/settings.json | 2 +- scenes/main.gd | 12 +++++ scenes/player.gd | 14 +++--- scripts/bot_controller.gd | 4 +- scripts/managers/notification_manager.gd | 55 ++++++++++++++++++++++ scripts/managers/playerboard_manager.gd | 2 +- scripts/managers/powerup_manager.gd | 2 +- scripts/managers/special_tiles_manager.gd | 29 ++++++------ tiles_armagedon_a1.res | Bin 11552 -> 0 bytes 9 files changed, 92 insertions(+), 28 deletions(-) create mode 100644 scripts/managers/notification_manager.gd delete mode 100644 tiles_armagedon_a1.res diff --git a/.vscode/settings.json b/.vscode/settings.json index ac1d800..fd1f493 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "godotTools.editorPath.godot4": "c:\\Users\\beng\\Godot\\Editors\\4.5.1-stable\\Godot_v4.5.1-stable_win64.exe" + "godotTools.editorPath.godot4": "/home/beng/Godot/Editors/4.5.1-stable/Godot_v4.5.1-stable_linux.x86_64" } \ No newline at end of file diff --git a/scenes/main.gd b/scenes/main.gd index fa24d62..8bb4a80 100644 --- a/scenes/main.gd +++ b/scenes/main.gd @@ -138,10 +138,22 @@ const MESSAGE_DURATION := 4.0 @onready var message_bar: PanelContainer = $MessageBar @onready var message_container: VBoxContainer = $MessageBar/MarginContainer/MessageContainer +var last_messages = {} # {player_name: {text: String, time: int}} + # Message types for different styling enum MessageType {NORMAL, POWERUP, GOAL, CYCLE, WARNING} func add_message_to_bar(player_name: String, message: String, type: int = MessageType.NORMAL): + # Deduplication check + var current_time = Time.get_ticks_msec() + if player_name in last_messages: + var last = last_messages[player_name] + # Ignore if same message within 2 seconds + if last.text == message and current_time - last.time < 2000: + return + + last_messages[player_name] = {"text": message, "time": current_time} + if not message_container: return diff --git a/scenes/player.gd b/scenes/player.gd index 14d53e1..3b8d16d 100644 --- a/scenes/player.gd +++ b/scenes/player.gd @@ -122,7 +122,7 @@ const AVAILABLE_CHARACTERS: Array[String] = ["Bob", "Masbro", "Gatot", "Oldpop"] set(value): is_my_turn = value if is_my_turn and is_multiplayer_authority(): - rpc("display_message", "It's your turn!") + NotificationManager.send_message(self, NotificationManager.MESSAGES.TURN_START, NotificationManager.MessageType.NORMAL) @export var has_moved_this_turn = false @@ -629,7 +629,7 @@ func apply_stagger(duration: float = 1.5): print("Player %s staggered for %.1f seconds" % [name, duration]) if is_multiplayer_authority(): - rpc("display_message", "C R U S H E D !", 4) # MessageType.WARNING + NotificationManager.send_message(self, NotificationManager.MESSAGES.CRUSHED, NotificationManager.MessageType.WARNING) drop_random_item() # Grant "Smashed" Bonus (1 bar, max 2) @@ -680,7 +680,7 @@ func drop_random_item(): var cell = Vector3i(drop_pos.x, 0, drop_pos.y) rpc("sync_grid_item", cell.x, cell.y, cell.z, item_id) - rpc("display_message", "Dropped item!", 4) + NotificationManager.send_message(self, NotificationManager.MESSAGES.DROPPED_ITEM, NotificationManager.MessageType.WARNING) print("Player %s dropped item %d at %s" % [name, item_id, drop_pos]) @@ -706,7 +706,7 @@ func drop_all_tiles(): if dropped_count > 0: rpc("sync_playerboard", playerboard) rpc("trigger_screen_shake", "targeted") - rpc("display_message", "CRITICALLY HIT!", 4) + NotificationManager.send_message(self, NotificationManager.MESSAGES.CRITICALLY_HIT, NotificationManager.MessageType.WARNING) print("Player %s dropped %d tiles due to Super Push" % [name, dropped_count]) func _find_valid_drop_position() -> Vector2i: @@ -741,7 +741,7 @@ func attempt_target_action(target_index: int): # So we might fail here if not wired up. # For now, let's look for "PowerUpInventoryUI" in CanvasLayer. - var inventory_ui = main.ui_manager.get_node_or_null("PowerUpInventoryUI") + var inventory_ui = main.ui_manager.get_node_or_null("PowerUpInventoryUI") # Or check if ui_manager tracks it. # Note: We haven't instantiated it yet in UIManager. We will need to do that. @@ -766,7 +766,7 @@ func attempt_target_action(target_index: int): if target_player == self and effect != 4: # 4 = INVISIBLE (Self) # Trying to target self with harmful effect? - rpc("display_message", "Can't target self!", 3) + NotificationManager.send_message(self, NotificationManager.MESSAGES.CANT_TARGET_SELF, NotificationManager.MessageType.WARNING) return # 3. Activate Effect @@ -1141,7 +1141,7 @@ func start_turn(): has_performed_action = false is_my_turn = true if is_multiplayer_authority(): - rpc("display_message", "It's your turn!") + # rpc("display_message", "It's your turn!") # Handled by setter _after_action_completed() func end_turn(): diff --git a/scripts/bot_controller.gd b/scripts/bot_controller.gd index 2a36840..816614b 100644 --- a/scripts/bot_controller.gd +++ b/scripts/bot_controller.gd @@ -188,9 +188,7 @@ func _try_use_powerup() -> bool: var success = powerup_manager.use_special_effect() if success: print("[BotController] %s used power-up (reason: %s)" % [actor.name, eval.reason]) - var main = get_tree().get_root().get_node_or_null("Main") - if main and main.has_method("broadcast_message"): - main.rpc("broadcast_message", actor.display_name, "Used a special power!") + NotificationManager.send_message(actor, NotificationManager.MESSAGES.USED_SPECIAL_POWER, NotificationManager.MessageType.POWERUP) await _wait_with_variance(action_delay) if not is_instance_valid(self): return true # Early exit if deleted diff --git a/scripts/managers/notification_manager.gd b/scripts/managers/notification_manager.gd new file mode 100644 index 0000000..8624e47 --- /dev/null +++ b/scripts/managers/notification_manager.gd @@ -0,0 +1,55 @@ +class_name NotificationManager +extends RefCounted + +# Centralized usage of display_message RPC +# Ensures consistent message types and easy refactoring + +enum MessageType { + NORMAL = 0, + POWERUP = 1, + GOAL = 2, + CYCLE = 3, + WARNING = 4 +} + +const MESSAGES = { + # Turn / Game Flow + "TURN_START": "It's your turn!", + "GOAL_COMPLETED": "Goal completed!", + + # Attack / Damage + "CRUSHED": "C R U S H E D !", + "CRITICALLY_HIT": "CRITICALLY HIT!", + "DROPPED_ITEM": "Dropped item!", + "CANT_TARGET_SELF": "Can't target self!", + + # Special Effects (Format strings) + "BURNED_BY": "Burned by %s!", + "TILES_SPAWNED": "Tiles Spawned!", + "FROZEN_BY": "Frozen by %s!", + "FLOOR_BLOCKED": "Floor Blocked!", + "INVISIBLE": "Invisible!", + "INVISIBILITY_ENDED": "Invisibility Ended!", # Changed slightly to be consistent if needed, or keep original + "SHIELD_BLOCKED": "Shield blocked an attack!", + "UNFROZEN": "Unfrozen!", + + # Powerups + "ATTACK_MODE_READY": "ATTACK MODE READY!", + "USED_SPECIAL_POWER": "Used a special power!" +} + +static func send_message(target: Node, message: String, type: int = MessageType.NORMAL): + if is_instance_valid(target) and target.has_method("rpc"): + # Check if the text is empty, do nothing + if message.is_empty(): + return + + # Call the RPC on the target (usually a Player node) + # "any_peer" allows any client to send this message to the target + target.rpc("display_message", message, type) + +# Helper for broadcasting to all players (if needed in future) +static func broadcast_to_all(tree: SceneTree, message: String, type: int = MessageType.NORMAL): + var players = tree.get_nodes_in_group("Players") + for player in players: + send_message(player, message, type) diff --git a/scripts/managers/playerboard_manager.gd b/scripts/managers/playerboard_manager.gd index a7e0b53..fe8d9bc 100644 --- a/scripts/managers/playerboard_manager.gd +++ b/scripts/managers/playerboard_manager.gd @@ -700,7 +700,7 @@ func _check_goal_completion(): goals_cycle_manager.on_goal_completed(player, goals_cycle_manager.get_time_remaining()) else: # Fallback if manager not initialized yet - player.rpc("display_message", "Goal completed!") + NotificationManager.send_message(player, NotificationManager.MESSAGES.GOAL_COMPLETED, NotificationManager.MessageType.GOAL) func clear_and_convert_to_score() -> int: """Clear playerboard and return score for matching tiles.""" diff --git a/scripts/managers/powerup_manager.gd b/scripts/managers/powerup_manager.gd index add459e..b665a40 100644 --- a/scripts/managers/powerup_manager.gd +++ b/scripts/managers/powerup_manager.gd @@ -61,7 +61,7 @@ func _process(delta): func _on_boost_full(): player.is_attack_mode = true emit_signal("bar_filled") - player.rpc("display_message", "ATTACK MODE READY!", 1) + NotificationManager.send_message(player, NotificationManager.MESSAGES.ATTACK_MODE_READY, NotificationManager.MessageType.POWERUP) print("[PowerUp] Player %s Boost Full! Entering Attack Mode." % player.name) if player.is_multiplayer_authority(): diff --git a/scripts/managers/special_tiles_manager.gd b/scripts/managers/special_tiles_manager.gd index 126b301..fcdbafa 100644 --- a/scripts/managers/special_tiles_manager.gd +++ b/scripts/managers/special_tiles_manager.gd @@ -66,10 +66,10 @@ func initialize(p_player: Node3D, p_gridmap: Node): # ============================================================================= func get_effect_from_item(item_id: int) -> int: match item_id: - 7: return SpecialEffect.BLOCK_FLOOR # Heart - 8: return SpecialEffect.FREEZE_PLAYER # Diamond - 9: return SpecialEffect.INVISIBLE_MODE # Star - 10: return SpecialEffect.BURN_TILES # Coin (Handles Burn or Spawn) + 7: return SpecialEffect.BLOCK_FLOOR # Heart + 8: return SpecialEffect.FREEZE_PLAYER # Diamond + 9: return SpecialEffect.INVISIBLE_MODE # Star + 10: return SpecialEffect.BURN_TILES # Coin (Handles Burn or Spawn) _: return -1 func add_powerup_from_item(item_id: int): @@ -120,7 +120,7 @@ func activate_effect(effect: int, target_player: Node3D = null): _execute_burn_tiles(target_player) else: # Spawn tiles around SELF (as per user request "around activating player") - _execute_spawn_tiles(player) + _execute_spawn_tiles(player) SpecialEffect.BLOCK_FLOOR: if target_player: @@ -189,7 +189,7 @@ func _execute_burn_tiles(target: Node3D): board_indices.append(i) if board_indices.is_empty(): - return + return var burn_count = rng.randi_range(3, 6) board_indices.shuffle() @@ -208,7 +208,7 @@ func _execute_burn_tiles(target: Node3D): if main: main.rpc("sync_playerboard", target.name.to_int(), target.playerboard) - target.rpc("display_message", "Burned by %s!" % player.display_name, 3) + NotificationManager.send_message(target, NotificationManager.MESSAGES.BURNED_BY % player.display_name, NotificationManager.MessageType.WARNING) func _execute_spawn_tiles(target: Node3D): @@ -220,7 +220,7 @@ func _execute_spawn_tiles(target: Node3D): # So random number tiles (7-10 are powerups, 1-6 are normal? No, 7-10 are patterns in this game). # "Spawn 3x3 pattern tiles" -> Tiles with ID 7,8,9,10 are the goal tiles. - target.rpc("display_message", "Tiles Spawned!", 2) + NotificationManager.send_message(target, NotificationManager.MESSAGES.TILES_SPAWNED, NotificationManager.MessageType.POWERUP) func _execute_freeze_player(target: Node3D): if not target: @@ -233,7 +233,7 @@ func _execute_freeze_player(target: Node3D): target.set("is_frozen", true) _create_unfreeze_timer(target, FREEZE_DURATION) - target.rpc("display_message", "Frozen by %s!" % player.display_name, 3) + NotificationManager.send_message(target, NotificationManager.MESSAGES.FROZEN_BY % player.display_name, NotificationManager.MessageType.WARNING) func _execute_block_floor(target: Node3D): # Make nearby tile non-walkable for 9 seconds @@ -262,14 +262,14 @@ func _execute_block_floor(target: Node3D): "timer": BLOCK_DURATION }) - target.rpc("display_message", "Floor Blocked!", 3) + NotificationManager.send_message(target, NotificationManager.MESSAGES.FLOOR_BLOCKED, NotificationManager.MessageType.WARNING) func _execute_invisible_mode(target: Node3D): target.is_invisible = true # Auto-disable after duration handled in Player._process or here? # SpecialTilesManager seems to handle effect timers. invisible_timer = INVISIBLE_DURATION - target.rpc("display_message", "Invisible!", 2) + NotificationManager.send_message(target, NotificationManager.MESSAGES.INVISIBLE, NotificationManager.MessageType.POWERUP) # ============================================================================= @@ -279,7 +279,6 @@ func _execute_invisible_mode(target: Node3D): func spawn_powerups_around(center: Vector2i, force_powerups: bool = true): # "spawn / replace your nearby tiles into power up ( special tiles )" # PowerUp Tiles are 7, 8, 9, 10 (Heart, Diamond, Star, Coin) - var radius = 2 for x in range(-radius, radius + 1): for y in range(-radius, radius + 1): @@ -308,7 +307,7 @@ func _update_invisible_timer(delta: float): invisible_timer = 0 if is_instance_valid(player): player.is_invisible = false - player.rpc("display_message", "Invisibility Ended") + NotificationManager.send_message(player, NotificationManager.MESSAGES.INVISIBILITY_ENDED, NotificationManager.MessageType.NORMAL) # ============================================================================= @@ -366,7 +365,7 @@ func check_shield_and_cancel_effect() -> bool: invisible_timer = 0 # Cancel timer if player.get("original_movement_range"): player.movement_range = player.original_movement_range - player.rpc("display_message", "Shield blocked an attack!") + NotificationManager.send_message(player, NotificationManager.MESSAGES.SHIELD_BLOCKED, NotificationManager.MessageType.POWERUP) return true return false @@ -377,4 +376,4 @@ func _create_unfreeze_timer(target_player: Node3D, duration: float): # Reset visuals if target_player.has_method("sync_modulate"): target_player.rpc("sync_modulate", Color.WHITE) - target_player.rpc("display_message", "Unfrozen!") + NotificationManager.send_message(target_player, NotificationManager.MESSAGES.UNFROZEN, NotificationManager.MessageType.NORMAL) diff --git a/tiles_armagedon_a1.res b/tiles_armagedon_a1.res deleted file mode 100644 index 32d3e43fd314e9860ef666c32bdebc99b0a5e53d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11552 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 Kv4`n@qyG;N65=HQ