update
This commit is contained in:
Vendored
+1
-1
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -138,10 +138,22 @@ const MESSAGE_DURATION := 4.0
|
|||||||
@onready var message_bar: PanelContainer = $MessageBar
|
@onready var message_bar: PanelContainer = $MessageBar
|
||||||
@onready var message_container: VBoxContainer = $MessageBar/MarginContainer/MessageContainer
|
@onready var message_container: VBoxContainer = $MessageBar/MarginContainer/MessageContainer
|
||||||
|
|
||||||
|
var last_messages = {} # {player_name: {text: String, time: int}}
|
||||||
|
|
||||||
# Message types for different styling
|
# Message types for different styling
|
||||||
enum MessageType {NORMAL, POWERUP, GOAL, CYCLE, WARNING}
|
enum MessageType {NORMAL, POWERUP, GOAL, CYCLE, WARNING}
|
||||||
|
|
||||||
func add_message_to_bar(player_name: String, message: String, type: int = MessageType.NORMAL):
|
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:
|
if not message_container:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
+7
-7
@@ -122,7 +122,7 @@ const AVAILABLE_CHARACTERS: Array[String] = ["Bob", "Masbro", "Gatot", "Oldpop"]
|
|||||||
set(value):
|
set(value):
|
||||||
is_my_turn = value
|
is_my_turn = value
|
||||||
if is_my_turn and is_multiplayer_authority():
|
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
|
@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])
|
print("Player %s staggered for %.1f seconds" % [name, duration])
|
||||||
|
|
||||||
if is_multiplayer_authority():
|
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()
|
drop_random_item()
|
||||||
|
|
||||||
# Grant "Smashed" Bonus (1 bar, max 2)
|
# 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)
|
var cell = Vector3i(drop_pos.x, 0, drop_pos.y)
|
||||||
rpc("sync_grid_item", cell.x, cell.y, cell.z, item_id)
|
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])
|
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:
|
if dropped_count > 0:
|
||||||
rpc("sync_playerboard", playerboard)
|
rpc("sync_playerboard", playerboard)
|
||||||
rpc("trigger_screen_shake", "targeted")
|
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])
|
print("Player %s dropped %d tiles due to Super Push" % [name, dropped_count])
|
||||||
|
|
||||||
func _find_valid_drop_position() -> Vector2i:
|
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.
|
# So we might fail here if not wired up.
|
||||||
# For now, let's look for "PowerUpInventoryUI" in CanvasLayer.
|
# 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.
|
# Or check if ui_manager tracks it.
|
||||||
# Note: We haven't instantiated it yet in UIManager. We will need to do that.
|
# 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)
|
if target_player == self and effect != 4: # 4 = INVISIBLE (Self)
|
||||||
# Trying to target self with harmful effect?
|
# 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
|
return
|
||||||
|
|
||||||
# 3. Activate Effect
|
# 3. Activate Effect
|
||||||
@@ -1141,7 +1141,7 @@ func start_turn():
|
|||||||
has_performed_action = false
|
has_performed_action = false
|
||||||
is_my_turn = true
|
is_my_turn = true
|
||||||
if is_multiplayer_authority():
|
if is_multiplayer_authority():
|
||||||
rpc("display_message", "It's your turn!")
|
# rpc("display_message", "It's your turn!") # Handled by setter
|
||||||
_after_action_completed()
|
_after_action_completed()
|
||||||
|
|
||||||
func end_turn():
|
func end_turn():
|
||||||
|
|||||||
@@ -188,9 +188,7 @@ func _try_use_powerup() -> bool:
|
|||||||
var success = powerup_manager.use_special_effect()
|
var success = powerup_manager.use_special_effect()
|
||||||
if success:
|
if success:
|
||||||
print("[BotController] %s used power-up (reason: %s)" % [actor.name, eval.reason])
|
print("[BotController] %s used power-up (reason: %s)" % [actor.name, eval.reason])
|
||||||
var main = get_tree().get_root().get_node_or_null("Main")
|
NotificationManager.send_message(actor, NotificationManager.MESSAGES.USED_SPECIAL_POWER, NotificationManager.MessageType.POWERUP)
|
||||||
if main and main.has_method("broadcast_message"):
|
|
||||||
main.rpc("broadcast_message", actor.display_name, "Used a special power!")
|
|
||||||
|
|
||||||
await _wait_with_variance(action_delay)
|
await _wait_with_variance(action_delay)
|
||||||
if not is_instance_valid(self): return true # Early exit if deleted
|
if not is_instance_valid(self): return true # Early exit if deleted
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -700,7 +700,7 @@ func _check_goal_completion():
|
|||||||
goals_cycle_manager.on_goal_completed(player, goals_cycle_manager.get_time_remaining())
|
goals_cycle_manager.on_goal_completed(player, goals_cycle_manager.get_time_remaining())
|
||||||
else:
|
else:
|
||||||
# Fallback if manager not initialized yet
|
# 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:
|
func clear_and_convert_to_score() -> int:
|
||||||
"""Clear playerboard and return score for matching tiles."""
|
"""Clear playerboard and return score for matching tiles."""
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ func _process(delta):
|
|||||||
func _on_boost_full():
|
func _on_boost_full():
|
||||||
player.is_attack_mode = true
|
player.is_attack_mode = true
|
||||||
emit_signal("bar_filled")
|
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)
|
print("[PowerUp] Player %s Boost Full! Entering Attack Mode." % player.name)
|
||||||
|
|
||||||
if player.is_multiplayer_authority():
|
if player.is_multiplayer_authority():
|
||||||
|
|||||||
@@ -66,10 +66,10 @@ func initialize(p_player: Node3D, p_gridmap: Node):
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
func get_effect_from_item(item_id: int) -> int:
|
func get_effect_from_item(item_id: int) -> int:
|
||||||
match item_id:
|
match item_id:
|
||||||
7: return SpecialEffect.BLOCK_FLOOR # Heart
|
7: return SpecialEffect.BLOCK_FLOOR # Heart
|
||||||
8: return SpecialEffect.FREEZE_PLAYER # Diamond
|
8: return SpecialEffect.FREEZE_PLAYER # Diamond
|
||||||
9: return SpecialEffect.INVISIBLE_MODE # Star
|
9: return SpecialEffect.INVISIBLE_MODE # Star
|
||||||
10: return SpecialEffect.BURN_TILES # Coin (Handles Burn or Spawn)
|
10: return SpecialEffect.BURN_TILES # Coin (Handles Burn or Spawn)
|
||||||
_: return -1
|
_: return -1
|
||||||
|
|
||||||
func add_powerup_from_item(item_id: int):
|
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)
|
_execute_burn_tiles(target_player)
|
||||||
else:
|
else:
|
||||||
# Spawn tiles around SELF (as per user request "around activating player")
|
# Spawn tiles around SELF (as per user request "around activating player")
|
||||||
_execute_spawn_tiles(player)
|
_execute_spawn_tiles(player)
|
||||||
|
|
||||||
SpecialEffect.BLOCK_FLOOR:
|
SpecialEffect.BLOCK_FLOOR:
|
||||||
if target_player:
|
if target_player:
|
||||||
@@ -189,7 +189,7 @@ func _execute_burn_tiles(target: Node3D):
|
|||||||
board_indices.append(i)
|
board_indices.append(i)
|
||||||
|
|
||||||
if board_indices.is_empty():
|
if board_indices.is_empty():
|
||||||
return
|
return
|
||||||
|
|
||||||
var burn_count = rng.randi_range(3, 6)
|
var burn_count = rng.randi_range(3, 6)
|
||||||
board_indices.shuffle()
|
board_indices.shuffle()
|
||||||
@@ -208,7 +208,7 @@ func _execute_burn_tiles(target: Node3D):
|
|||||||
if main:
|
if main:
|
||||||
main.rpc("sync_playerboard", target.name.to_int(), target.playerboard)
|
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):
|
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).
|
# 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.
|
# "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):
|
func _execute_freeze_player(target: Node3D):
|
||||||
if not target:
|
if not target:
|
||||||
@@ -233,7 +233,7 @@ func _execute_freeze_player(target: Node3D):
|
|||||||
target.set("is_frozen", true)
|
target.set("is_frozen", true)
|
||||||
_create_unfreeze_timer(target, FREEZE_DURATION)
|
_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):
|
func _execute_block_floor(target: Node3D):
|
||||||
# Make nearby tile non-walkable for 9 seconds
|
# Make nearby tile non-walkable for 9 seconds
|
||||||
@@ -262,14 +262,14 @@ func _execute_block_floor(target: Node3D):
|
|||||||
"timer": BLOCK_DURATION
|
"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):
|
func _execute_invisible_mode(target: Node3D):
|
||||||
target.is_invisible = true
|
target.is_invisible = true
|
||||||
# Auto-disable after duration handled in Player._process or here?
|
# Auto-disable after duration handled in Player._process or here?
|
||||||
# SpecialTilesManager seems to handle effect timers.
|
# SpecialTilesManager seems to handle effect timers.
|
||||||
invisible_timer = INVISIBLE_DURATION
|
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):
|
func spawn_powerups_around(center: Vector2i, force_powerups: bool = true):
|
||||||
# "spawn / replace your nearby tiles into power up ( special tiles )"
|
# "spawn / replace your nearby tiles into power up ( special tiles )"
|
||||||
# PowerUp Tiles are 7, 8, 9, 10 (Heart, Diamond, Star, Coin)
|
# PowerUp Tiles are 7, 8, 9, 10 (Heart, Diamond, Star, Coin)
|
||||||
|
|
||||||
var radius = 2
|
var radius = 2
|
||||||
for x in range(-radius, radius + 1):
|
for x in range(-radius, radius + 1):
|
||||||
for y 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
|
invisible_timer = 0
|
||||||
if is_instance_valid(player):
|
if is_instance_valid(player):
|
||||||
player.is_invisible = false
|
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
|
invisible_timer = 0 # Cancel timer
|
||||||
if player.get("original_movement_range"):
|
if player.get("original_movement_range"):
|
||||||
player.movement_range = player.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 true
|
||||||
return false
|
return false
|
||||||
|
|
||||||
@@ -377,4 +376,4 @@ func _create_unfreeze_timer(target_player: Node3D, duration: float):
|
|||||||
# Reset visuals
|
# Reset visuals
|
||||||
if target_player.has_method("sync_modulate"):
|
if target_player.has_method("sync_modulate"):
|
||||||
target_player.rpc("sync_modulate", Color.WHITE)
|
target_player.rpc("sync_modulate", Color.WHITE)
|
||||||
target_player.rpc("display_message", "Unfrozen!")
|
NotificationManager.send_message(target_player, NotificationManager.MESSAGES.UNFROZEN, NotificationManager.MessageType.NORMAL)
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user