feat: Add main game scene with core logic, manager initialization, UI setup, and an in-game message bar.
This commit is contained in:
@@ -269,10 +269,17 @@ func spawn_powerups_around(center: Vector2i, force_powerups: bool = true):
|
||||
for y in range(-radius, radius + 1):
|
||||
var pos = center + Vector2i(x, y)
|
||||
if enhanced_gridmap.is_position_valid(pos):
|
||||
# Random chance
|
||||
if rng.randf() > 0.4: continue
|
||||
# Random chance to spawn ANYTHING at this spot (keep density reasonable)
|
||||
if rng.randf() > 0.5: continue
|
||||
|
||||
var item_id: int
|
||||
# 70% Chance for PowerUp (11-14)
|
||||
if rng.randf() < 0.7:
|
||||
item_id = rng.randi_range(11, 14)
|
||||
else:
|
||||
# 30% Chance for Normal Tile (7-10)
|
||||
item_id = rng.randi_range(7, 10)
|
||||
|
||||
var item_id = rng.randi_range(11, 14) # 11-14 are the new powerups
|
||||
var cell = Vector3i(pos.x, 1, pos.y)
|
||||
|
||||
if player.is_multiplayer_authority():
|
||||
|
||||
+142
-131
@@ -3,8 +3,7 @@ extends Control
|
||||
# PowerUpInventoryUI - Displays stored powerups and handles selection
|
||||
|
||||
# UI References
|
||||
var icon_containers: Dictionary = {} # { EffectEnum: TextureRect }
|
||||
var selection_indicators: Dictionary = {} # { EffectEnum: Control }
|
||||
var icon_containers: Dictionary = {} # { EffectEnum: Button }
|
||||
|
||||
# Local State
|
||||
var selected_effect: int = -1
|
||||
@@ -13,159 +12,171 @@ var special_manager_ref: Node = null # Reference to SpecialTilesManager
|
||||
signal effect_selected(effect: int)
|
||||
|
||||
func _ready():
|
||||
# Wait for children to be ready
|
||||
await get_tree().process_frame
|
||||
|
||||
print("[PowerUpUI] _ready called")
|
||||
# Map Effect Enum to UI Nodes (Assumes specific names in main.tscn)
|
||||
# Default structure: HBoxContainer > CoinIcon, HeartIcon, etc.
|
||||
# We try to get them immediately. If they are children, they should be accessible.
|
||||
var container = get_node_or_null("HBoxContainer")
|
||||
if not container:
|
||||
print("PowerUpUI: Container not found")
|
||||
print("[PowerUpUI] ERROR: HBoxContainer not found in ", get_path())
|
||||
return
|
||||
|
||||
# ID 11 = Faster (Coin Icon?) User said: "Coin : random between two" originally,
|
||||
# but now "11 (Faster)". Let's use CoinIcon for Speed? Or Star?
|
||||
# User Request: "CoinIcon, HeartIcon, DiamondIcon and StarIcon"
|
||||
# Let's map:
|
||||
# 11 (Faster) -> CoinIcon
|
||||
# 12 (Freeze) -> DiamondIcon
|
||||
# 13 (Block) -> HeartIcon
|
||||
# 14 (Invisible) -> StarIcon
|
||||
# Mapping based on User Request
|
||||
# 11: FASTER_SPEED (0) -> SpeedBtn
|
||||
# 12: AREA_FREEZE (1) -> FreezeAreaBtn
|
||||
# 13: BLOCK_FLOOR (2) -> WallBtn
|
||||
# 14: INVISIBLE_MODE (3) -> GhostBtn
|
||||
|
||||
_setup_icon(11, container.get_node_or_null("CoinIcon"))
|
||||
_setup_icon(12, container.get_node_or_null("DiamondIcon"))
|
||||
_setup_icon(13, container.get_node_or_null("HeartIcon"))
|
||||
_setup_icon(14, container.get_node_or_null("StarIcon"))
|
||||
# We use 0, 1, 2, 3 to match SpecialTilesManager.SpecialEffect enum
|
||||
_setup_btn(0, container.get_node_or_null("SpeedBtn"))
|
||||
_setup_btn(1, container.get_node_or_null("FreezeAreaBtn"))
|
||||
_setup_btn(2, container.get_node_or_null("WallBtn"))
|
||||
_setup_btn(3, container.get_node_or_null("GhostBtn"))
|
||||
|
||||
# Note: SpecialEffect enum values from SpecialTilesManager:
|
||||
# BURN_TILES = 0
|
||||
# SPAWN_TILES = 1 (we map Coin to 0, merged)
|
||||
# FREEZE_PLAYER = 2
|
||||
# BLOCK_FLOOR = 3
|
||||
# INVISIBLE_MODE = 4
|
||||
print("[PowerUpUI] UI Initialization Complete. Mapped %d buttons." % icon_containers.size())
|
||||
|
||||
func _setup_icon(effect_id: int, node: Control):
|
||||
if not node: return
|
||||
func _setup_btn(effect_id: int, btn: Button):
|
||||
if not btn:
|
||||
print("[PowerUpUI] Warning: Button for effect %d is null" % effect_id)
|
||||
return
|
||||
|
||||
icon_containers[effect_id] = node
|
||||
icon_containers[effect_id] = btn
|
||||
|
||||
# Assume node has specific children for selection state?
|
||||
# "SelectRect" child?
|
||||
var select_rect = node.get_node_or_null("SelectRect")
|
||||
if select_rect:
|
||||
selection_indicators[effect_id] = select_rect
|
||||
select_rect.visible = false
|
||||
# Start DISABLED
|
||||
btn.disabled = true
|
||||
btn.modulate = Color(0.5, 0.5, 0.5, 0.5) # Grayed out
|
||||
btn.focus_mode = Control.FOCUS_NONE
|
||||
|
||||
# Add Level Label
|
||||
if not btn.has_node("LevelLabel"):
|
||||
var lvl_lbl = Label.new()
|
||||
lvl_lbl.name = "LevelLabel"
|
||||
lvl_lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
lvl_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||
lvl_lbl.vertical_alignment = VERTICAL_ALIGNMENT_BOTTOM
|
||||
lvl_lbl.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
lvl_lbl.add_theme_font_size_override("font_size", 16)
|
||||
lvl_lbl.add_theme_color_override("font_outline_color", Color.BLACK)
|
||||
lvl_lbl.add_theme_constant_override("outline_size", 4)
|
||||
lvl_lbl.text = "" # Hidden initially
|
||||
btn.add_child(lvl_lbl)
|
||||
|
||||
# Add Cooldown Label if missing
|
||||
if not node.has_node("CooldownLabel"):
|
||||
var lbl = Label.new()
|
||||
lbl.name = "CooldownLabel"
|
||||
lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE # Ensure input passes to icon
|
||||
# Style the label
|
||||
lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
lbl.set_anchors_preset(Control.PRESET_FULL_RECT) # Cover icon
|
||||
lbl.add_theme_color_override("font_shadow_color", Color.BLACK)
|
||||
lbl.add_theme_constant_override("shadow_offset_x", 1)
|
||||
lbl.add_theme_constant_override("shadow_offset_y", 1)
|
||||
lbl.add_theme_font_size_override("font_size", 20) # Big text
|
||||
lbl.text = ""
|
||||
lbl.hide()
|
||||
node.add_child(lbl)
|
||||
|
||||
# Connect click event
|
||||
if not node.gui_input.is_connected(_on_icon_input):
|
||||
node.gui_input.connect(_on_icon_input.bind(effect_id))
|
||||
# Add Cooldown Label
|
||||
if not btn.has_node("CooldownLabel"):
|
||||
var cd_lbl = Label.new()
|
||||
cd_lbl.name = "CooldownLabel"
|
||||
cd_lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
cd_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
cd_lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
cd_lbl.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
cd_lbl.add_theme_font_size_override("font_size", 20)
|
||||
cd_lbl.add_theme_color_override("font_outline_color", Color.BLACK)
|
||||
cd_lbl.add_theme_constant_override("outline_size", 4)
|
||||
cd_lbl.text = ""
|
||||
btn.add_child(cd_lbl)
|
||||
|
||||
func _on_icon_input(event, effect_id: int):
|
||||
if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
print("[PowerUpUI] Clicked Icon %d. Manager: %s" % [effect_id, special_manager_ref])
|
||||
# User Request: "Click the CoinIcon for activate"
|
||||
# Instead of just selecting, we ACTIVATE it immediately.
|
||||
if special_manager_ref:
|
||||
special_manager_ref.activate_effect(effect_id)
|
||||
else:
|
||||
print("[PowerUpUI] ERROR: SpecialManagerRef is null!")
|
||||
|
||||
# Visual feedback (still useful)
|
||||
select_effect(effect_id)
|
||||
|
||||
# Auto-deselect after short delay?
|
||||
# For now, keep selection highlight as feedback of "last used" or "active"?
|
||||
# Or just flash it?
|
||||
|
||||
# If it's a "selection for target" ability (like Block Floor on Target?),
|
||||
# we might need selection state first, then clicking target.
|
||||
# But user said "CoinIcon ... activate faster speed". Speed is self-cast.
|
||||
# Freeze is 3x3 self-centered.
|
||||
# Invisible is self.
|
||||
# Block is "Wall Block"... maybe checks target?
|
||||
# SpecialTilesManager.activate_effect defaults target to self if null.
|
||||
# So direct activation is safe for IsInstant effects.
|
||||
# Connect click
|
||||
if not btn.pressed.is_connected(_on_btn_pressed):
|
||||
btn.pressed.connect(_on_btn_pressed.bind(effect_id))
|
||||
|
||||
func _on_btn_pressed(effect_id: int):
|
||||
print("[PowerUpUI] Clicked Button %d" % effect_id)
|
||||
if special_manager_ref:
|
||||
special_manager_ref.activate_effect(effect_id)
|
||||
else:
|
||||
print("[PowerUpUI] ERROR: special_manager_ref is null during click")
|
||||
|
||||
func setup(player_node):
|
||||
print("[PowerUpUI] Setup called for player: ", player_node.name)
|
||||
|
||||
var special_manager = player_node.get_node_or_null("SpecialTilesManager")
|
||||
if special_manager:
|
||||
special_manager_ref = special_manager # Store reference for activation logic
|
||||
if not special_manager.is_connected("inventory_updated", _on_inventory_updated):
|
||||
special_manager.connect("inventory_updated", _on_inventory_updated)
|
||||
if not special_manager.is_connected("cooldown_updated", _on_cooldown_updated):
|
||||
special_manager.connect("cooldown_updated", _on_cooldown_updated)
|
||||
|
||||
_on_inventory_updated(special_manager.inventory)
|
||||
_connect_special_manager(special_manager)
|
||||
else:
|
||||
print("[PowerUpUI] SpecialTilesManager not found on %s. Waiting for it..." % player_node.name)
|
||||
if not player_node.child_entered_tree.is_connected(_on_player_child_entered):
|
||||
player_node.child_entered_tree.connect(_on_player_child_entered.bind(player_node))
|
||||
|
||||
func _on_player_child_entered(node: Node, player_node: Node):
|
||||
if node.name == "SpecialTilesManager":
|
||||
print("[PowerUpUI] SpecialTilesManager appeared on %s!" % player_node.name)
|
||||
_connect_special_manager(node)
|
||||
# Disconnect to avoid checking every child forever
|
||||
if player_node.child_entered_tree.is_connected(_on_player_child_entered):
|
||||
player_node.child_entered_tree.disconnect(_on_player_child_entered)
|
||||
|
||||
func _connect_special_manager(special_manager):
|
||||
special_manager_ref = special_manager
|
||||
print("[PowerUpUI] Connected to SpecialTilesManager")
|
||||
|
||||
# Connect signals if not already connected
|
||||
if not special_manager.is_connected("powerup_unlocked", _on_powerup_unlocked):
|
||||
special_manager.connect("powerup_unlocked", _on_powerup_unlocked)
|
||||
|
||||
if not special_manager.is_connected("cooldown_updated", _on_cooldown_updated):
|
||||
special_manager.connect("cooldown_updated", _on_cooldown_updated)
|
||||
|
||||
if not special_manager.is_connected("inventory_updated", _on_inventory_updated):
|
||||
special_manager.connect("inventory_updated", _on_inventory_updated)
|
||||
|
||||
# Initial State Sync
|
||||
if icon_containers.is_empty():
|
||||
print("[PowerUpUI] Warning: Icon containers empty during setup. Attempting _ready logic now...")
|
||||
var container = get_node_or_null("HBoxContainer")
|
||||
if container:
|
||||
# Fix: Use correct IDs 0-3 here too
|
||||
_setup_btn(0, container.get_node_or_null("SpeedBtn"))
|
||||
_setup_btn(1, container.get_node_or_null("FreezeAreaBtn"))
|
||||
_setup_btn(2, container.get_node_or_null("WallBtn"))
|
||||
_setup_btn(3, container.get_node_or_null("GhostBtn"))
|
||||
|
||||
# Sync Inventory
|
||||
_on_inventory_updated(special_manager.inventory)
|
||||
|
||||
# Sync Levels for owned items
|
||||
for effect in special_manager.inventory:
|
||||
if special_manager.inventory[effect]:
|
||||
var lvl = special_manager.powerup_levels.get(effect, 1)
|
||||
_on_powerup_unlocked(effect, lvl)
|
||||
|
||||
func _on_powerup_unlocked(effect: int, level: int):
|
||||
# Enable button and set level
|
||||
if icon_containers.has(effect):
|
||||
var btn = icon_containers[effect]
|
||||
print("[PowerUpUI] Enabling button for Effect %d (Node: %s)" % [effect, btn.name])
|
||||
btn.disabled = false
|
||||
btn.modulate = Color.WHITE # Restore color
|
||||
btn.visible = true # Ensure visible
|
||||
|
||||
# Update Level
|
||||
var lvl_lbl = btn.get_node_or_null("LevelLabel")
|
||||
if lvl_lbl:
|
||||
lvl_lbl.text = "Lvl %d" % level
|
||||
else:
|
||||
print("[PowerUpUI] ERROR: Unlocked Effect %d but no UI button found! Keys: %s" % [effect, icon_containers.keys()])
|
||||
|
||||
func _on_cooldown_updated(effect: int, time_left: float, max_time: float):
|
||||
if icon_containers.has(effect):
|
||||
var node = icon_containers[effect]
|
||||
var lbl = node.get_node_or_null("CooldownLabel")
|
||||
if lbl:
|
||||
var btn = icon_containers[effect]
|
||||
var cd_lbl = btn.get_node_or_null("CooldownLabel")
|
||||
if cd_lbl:
|
||||
if time_left > 0:
|
||||
lbl.text = "%.1fs" % time_left
|
||||
lbl.show()
|
||||
node.modulate = Color(0.5, 0.5, 0.5, 1.0) # Dimmed
|
||||
cd_lbl.text = "%.1f" % time_left
|
||||
btn.disabled = true
|
||||
btn.modulate = Color(0.7, 0.7, 0.7, 0.8)
|
||||
else:
|
||||
lbl.hide()
|
||||
# Check inventory to restore proper modulate
|
||||
var has_item = false # Need ref to inventory... or rely on next inventory update?
|
||||
# Inventory update might not fire when cooldown ends.
|
||||
# Let's restore bright if we have it?
|
||||
# We don't have inventory reference here easily without storing it.
|
||||
node.modulate = Color.WHITE # Assume we have it if we used it?
|
||||
# Or better, just restore to WHITE and let inventory logic handle "not owned" graying.
|
||||
# But wait, logic in _on_inventory_updated sets GRAY if not owned.
|
||||
# If we set WHITE here, we might un-gray an unowned item?
|
||||
# The only way cooldown runs is if we ACTIVATED it, so we HAD it.
|
||||
# And we treat it as infinite consumable now. So we still have it.
|
||||
pass
|
||||
|
||||
# Better modulate handling:
|
||||
# If cooldown > 0, DIM.
|
||||
# If cooldown == 0, check owned state?
|
||||
# For now, simplistic: if cooldown ends, set white.
|
||||
if time_left <= 0:
|
||||
node.modulate = Color.WHITE
|
||||
cd_lbl.text = ""
|
||||
# Re-enable if we own it
|
||||
if special_manager_ref and special_manager_ref.inventory.get(effect, false):
|
||||
btn.disabled = false
|
||||
btn.modulate = Color.WHITE
|
||||
|
||||
func _on_inventory_updated(inventory: Dictionary):
|
||||
# Update UI icons (Dimmed vs Lit)
|
||||
# Update UI icons (Dimmed vs Lit) and Enablement
|
||||
print("[PowerUpUI] Inventory Updated: ", inventory)
|
||||
for effect in icon_containers:
|
||||
if inventory.has(effect):
|
||||
var has_item = inventory[effect]
|
||||
icon_containers[effect].modulate = Color.WHITE if has_item else Color(0.3, 0.3, 0.3, 0.5)
|
||||
var btn = icon_containers[effect]
|
||||
|
||||
if not has_item and selected_effect == effect:
|
||||
deselect()
|
||||
|
||||
func select_effect(effect: int):
|
||||
# Check if we own it first? The UI click handler should check.
|
||||
selected_effect = effect
|
||||
emit_signal("effect_selected", effect)
|
||||
_update_selection_visuals()
|
||||
|
||||
func deselect():
|
||||
selected_effect = -1
|
||||
emit_signal("effect_selected", -1)
|
||||
_update_selection_visuals()
|
||||
|
||||
func _update_selection_visuals():
|
||||
for effect in selection_indicators:
|
||||
selection_indicators[effect].visible = (effect == selected_effect)
|
||||
btn.modulate = Color.WHITE if has_item else Color(0.5, 0.5, 0.5, 0.5)
|
||||
btn.disabled = !has_item
|
||||
|
||||
Reference in New Issue
Block a user