feat: Implement powerup inventory UI, settings menu, and foundational managers for input and game settings.
This commit is contained in:
@@ -3,6 +3,7 @@ extends Node
|
||||
var player: Node3D
|
||||
var movement_manager: Node
|
||||
var race_manager: Node
|
||||
@onready var SettingsManager = get_node_or_null("/root/SettingsManager")
|
||||
|
||||
func initialize(p_player: Node3D, p_movement_manager: Node, p_race_manager: Node):
|
||||
player = p_player
|
||||
@@ -87,48 +88,49 @@ func handle_unhandled_input(event):
|
||||
var mode = LobbyManager.get_game_mode()
|
||||
var is_sng = mode == GameMode.Mode.STOP_N_GO
|
||||
|
||||
match event.keycode:
|
||||
KEY_KP_1, KEY_1, KEY_KP_2, KEY_2, KEY_KP_3, KEY_3, KEY_KP_4, KEY_4:
|
||||
if is_sng:
|
||||
# Stop n Go Mapping: 1-Speed, 2-Ghost
|
||||
match event.keycode:
|
||||
KEY_KP_1, KEY_1:
|
||||
player.activate_powerup(0) # FASTER_SPEED
|
||||
KEY_KP_2, KEY_2:
|
||||
player.activate_powerup(3) # INVISIBLE_MODE
|
||||
else:
|
||||
# Unified Mapping for all modes: 1-Speed, 2-Wall, 3-Freeze, 4-Ghost
|
||||
match event.keycode:
|
||||
KEY_KP_1, KEY_1:
|
||||
player.activate_powerup(0) # FASTER_SPEED
|
||||
KEY_KP_2, KEY_2:
|
||||
player.activate_powerup(2) # BLOCK_FLOOR
|
||||
KEY_KP_3, KEY_3:
|
||||
player.activate_powerup(1) # AREA_FREEZE
|
||||
KEY_KP_4, KEY_4:
|
||||
player.activate_powerup(3) # INVISIBLE_MODE
|
||||
# KEY_R:
|
||||
# player.auto_put_item()
|
||||
KEY_Q:
|
||||
if player.powerup_manager:
|
||||
# Attack Mode (formerly Special)
|
||||
# Now we want "Straight to Attack Mode" style
|
||||
player.powerup_manager.use_special_effect()
|
||||
|
||||
# Force visual update / mutual exclusivity manually if powerup manager doesn't do it yet
|
||||
if player.is_attack_mode and player.has_method("enter_attack_mode"):
|
||||
# Re-triggering enter_attack_mode might be redundant but safely ensures visuals/Knock=False
|
||||
player.enter_attack_mode()
|
||||
# Get dynamic keybinds
|
||||
var key_p1 = SettingsManager.get_control_keycode("powerup_1")
|
||||
var key_p2 = SettingsManager.get_control_keycode("powerup_2")
|
||||
var key_p3 = SettingsManager.get_control_keycode("powerup_3")
|
||||
var key_p4 = SettingsManager.get_control_keycode("powerup_4")
|
||||
|
||||
var ek = event.keycode
|
||||
|
||||
# Unified check for PowerUp keys
|
||||
if ek == key_p1:
|
||||
player.activate_powerup(0) # FASTER_SPEED
|
||||
elif ek == key_p2:
|
||||
if is_sng:
|
||||
player.activate_powerup(3) # Ghost for SNG P2
|
||||
else:
|
||||
player.activate_powerup(2) # Wall for Normal P2
|
||||
elif ek == key_p3 and not is_sng:
|
||||
player.activate_powerup(1) # AREA_FREEZE
|
||||
elif ek == key_p4 and not is_sng:
|
||||
player.activate_powerup(3) # INVISIBLE_MODE
|
||||
|
||||
# Non-Remappable variants (Numpad) - Optional fallback
|
||||
elif ek == KEY_KP_1:
|
||||
player.activate_powerup(0)
|
||||
elif ek == KEY_KP_2:
|
||||
player.activate_powerup(3 if is_sng else 2)
|
||||
|
||||
# Action Buttons (Remappable)
|
||||
elif ek == SettingsManager.get_control_keycode("attack_mode"):
|
||||
if player.powerup_manager:
|
||||
player.powerup_manager.use_special_effect()
|
||||
if player.is_attack_mode and player.has_method("enter_attack_mode"):
|
||||
player.enter_attack_mode()
|
||||
|
||||
KEY_E:
|
||||
if player.is_carrying_tekton and player.powerup_manager:
|
||||
# Spawn Boost (Now uses Tekton)
|
||||
if player.powerup_manager.has_method("spawn_boost_reward"):
|
||||
player.powerup_manager.spawn_boost_reward()
|
||||
KEY_G:
|
||||
if not player.is_carrying_tekton and player.powerup_manager:
|
||||
if player.powerup_manager.can_use_special():
|
||||
player.grab_tekton()
|
||||
elif ek == SettingsManager.get_control_keycode("spawn_boost"):
|
||||
if player.is_carrying_tekton and player.powerup_manager:
|
||||
if player.powerup_manager.has_method("spawn_boost_reward"):
|
||||
player.powerup_manager.spawn_boost_reward()
|
||||
|
||||
elif ek == SettingsManager.get_control_keycode("tekton_grab"):
|
||||
if not player.is_carrying_tekton and player.powerup_manager:
|
||||
if player.powerup_manager.can_use_special():
|
||||
player.grab_tekton()
|
||||
|
||||
# Handle spawn point selection if not yet selected
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
extends Node
|
||||
|
||||
# SettingsManager - Global singleton for handling game settings persistence
|
||||
# Autoloaded as "SettingsManager"
|
||||
|
||||
const SETTINGS_FILE = "user://game_settings.cfg"
|
||||
|
||||
# Default values
|
||||
var settings = {
|
||||
"video": {
|
||||
"fullscreen": true,
|
||||
"vsync": true,
|
||||
"resolution_idx": 1
|
||||
},
|
||||
"audio": {
|
||||
"master_volume": 0.8,
|
||||
"music_volume": 0.7,
|
||||
"sfx_volume": 0.9
|
||||
},
|
||||
"controls": {
|
||||
"powerup_1": KEY_1,
|
||||
"powerup_2": KEY_2,
|
||||
"powerup_3": KEY_3,
|
||||
"powerup_4": KEY_4,
|
||||
"grab": KEY_SPACE,
|
||||
"put": KEY_R,
|
||||
"attack_mode": KEY_Q,
|
||||
"spawn_boost": KEY_E,
|
||||
"tekton_grab": KEY_G
|
||||
}
|
||||
}
|
||||
|
||||
signal settings_applied
|
||||
signal control_remapped(action: String, key: int)
|
||||
|
||||
func _ready():
|
||||
load_settings()
|
||||
apply_all_settings()
|
||||
|
||||
func load_settings():
|
||||
var config = ConfigFile.new()
|
||||
var err = config.load(SETTINGS_FILE)
|
||||
|
||||
if err == OK:
|
||||
for section in settings.keys():
|
||||
for key in settings[section].keys():
|
||||
settings[section][key] = config.get_value(section, key, settings[section][key])
|
||||
print("[Settings] Loaded.")
|
||||
else:
|
||||
print("[Settings] Using defaults.")
|
||||
|
||||
func save_settings():
|
||||
var config = ConfigFile.new()
|
||||
for section in settings.keys():
|
||||
for key in settings[section].keys():
|
||||
config.set_value(section, key, settings[section][key])
|
||||
config.save(SETTINGS_FILE)
|
||||
|
||||
func apply_all_settings():
|
||||
apply_video_settings()
|
||||
apply_audio_settings()
|
||||
emit_signal("settings_applied")
|
||||
|
||||
func apply_video_settings():
|
||||
var video = settings.video
|
||||
if video.fullscreen:
|
||||
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
|
||||
else:
|
||||
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
|
||||
|
||||
if video.vsync:
|
||||
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_ENABLED)
|
||||
else:
|
||||
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)
|
||||
|
||||
func apply_audio_settings():
|
||||
var audio = settings.audio
|
||||
set_bus_volume("Master", audio.master_volume)
|
||||
set_bus_volume("Music", audio.music_volume)
|
||||
set_bus_volume("SFX", audio.sfx_volume)
|
||||
|
||||
func set_bus_volume(bus_name: String, volume_linear: float):
|
||||
var bus_idx = AudioServer.get_bus_index(bus_name)
|
||||
if bus_idx != -1:
|
||||
AudioServer.set_bus_volume_db(bus_idx, linear_to_db(volume_linear))
|
||||
AudioServer.set_bus_mute(bus_idx, volume_linear <= 0.001)
|
||||
|
||||
func set_control(action_name: String, keycode: int):
|
||||
if settings.controls.has(action_name):
|
||||
settings.controls[action_name] = keycode
|
||||
emit_signal("control_remapped", action_name, keycode)
|
||||
save_settings()
|
||||
|
||||
func get_control_keycode(action_name: String) -> int:
|
||||
return settings.controls.get(action_name, -1)
|
||||
|
||||
func get_control_text(action_name: String) -> String:
|
||||
var code = get_control_keycode(action_name)
|
||||
if code == -1: return "Unbound"
|
||||
return OS.get_keycode_string(code)
|
||||
@@ -0,0 +1 @@
|
||||
uid://c1ouaaqnn0lrc
|
||||
@@ -142,11 +142,26 @@ func _create_touch_ui():
|
||||
|
||||
tekton_grab_button = _find_or_create_action_button(actions_container, "TektonGrab", "👋", Vector2(-280, -80))
|
||||
|
||||
# Order: AttackMode, SpawnBoost, Grab
|
||||
if attack_mode_button: actions_container.move_child(attack_mode_button, 0)
|
||||
if spawn_boost_button: actions_container.move_child(spawn_boost_button, 1)
|
||||
if grab_button: actions_container.move_child(grab_button, 2)
|
||||
if tekton_grab_button: actions_container.move_child(tekton_grab_button, 3)
|
||||
# Order: AttackMode, SpawnBoost, Grab, TektonGrab
|
||||
if attack_mode_button:
|
||||
actions_container.move_child(attack_mode_button, 0)
|
||||
attack_mode_button.icon = load("res://assets/graphics/touch_control/attack_mode.png")
|
||||
attack_mode_button.expand_icon = true
|
||||
|
||||
if spawn_boost_button:
|
||||
actions_container.move_child(spawn_boost_button, 1)
|
||||
spawn_boost_button.icon = load("res://assets/graphics/touch_control/spawn_tile.png")
|
||||
spawn_boost_button.expand_icon = true
|
||||
|
||||
if grab_button:
|
||||
actions_container.move_child(grab_button, 2)
|
||||
grab_button.icon = load("res://assets/graphics/touch_control/take_tile.png")
|
||||
grab_button.expand_icon = true
|
||||
|
||||
if tekton_grab_button:
|
||||
actions_container.move_child(tekton_grab_button, 3)
|
||||
tekton_grab_button.icon = load("res://assets/graphics/touch_control/grab_tekton.png")
|
||||
tekton_grab_button.expand_icon = true
|
||||
|
||||
# Hide Put Button
|
||||
if put_button:
|
||||
@@ -391,6 +406,7 @@ func _load_settings():
|
||||
var err = config.load(CONFIG_PATH)
|
||||
if err != OK:
|
||||
print("[TouchControls] No saved settings found, using defaults")
|
||||
_apply_settings() # Apply default settings immediately
|
||||
return
|
||||
|
||||
# Load settings values
|
||||
@@ -439,46 +455,55 @@ func _apply_settings():
|
||||
# Apply joystick visibility
|
||||
if virtual_joystick:
|
||||
virtual_joystick.visible = joystick_enabled
|
||||
|
||||
# Apply touch buttons visibility - FORCED ON per request to "just show them"
|
||||
if actions_container:
|
||||
actions_container.visible = true
|
||||
actions_container.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
actions_container.mouse_filter = Control.MOUSE_FILTER_PASS
|
||||
|
||||
# Apply touch buttons visibility - dependent on master joystick_enabled switch
|
||||
# If joystick is disabled, ALL touch controls are hidden
|
||||
# Note: We ignore touch_buttons_enabled here to ensure "Enable Virtual Joystick" shows EVERYTHING as requested
|
||||
var buttons_visible = joystick_enabled
|
||||
var buttons_visible = true
|
||||
|
||||
print("[TouchControls] Applying settings: JoystickEnabled=", joystick_enabled, " ButtonsVisible=", buttons_visible)
|
||||
print("[TouchControls] Applying settings: ButtonsVisible=", buttons_visible)
|
||||
|
||||
if grab_button:
|
||||
grab_button.visible = buttons_visible
|
||||
grab_button.visible = false
|
||||
grab_button.scale = Vector2(button_scale, button_scale)
|
||||
# Use offsets for anchored controls, not position
|
||||
# grab_button.offset_left = button_positions.grab.x
|
||||
# grab_button.offset_top = button_positions.grab.y
|
||||
# grab_button.offset_right = button_positions.grab.x + button_size
|
||||
# grab_button.offset_bottom = button_positions.grab.y + button_size
|
||||
grab_button.set_anchors_preset(Control.PRESET_BOTTOM_RIGHT)
|
||||
grab_button.offset_left = button_positions.grab.x
|
||||
grab_button.offset_top = button_positions.grab.y
|
||||
grab_button.offset_right = button_positions.grab.x + button_size
|
||||
grab_button.offset_bottom = button_positions.grab.y + button_size
|
||||
|
||||
if put_button:
|
||||
put_button.visible = false # Always INTENTIONALLY HIDDEN per request
|
||||
# put_button.scale = Vector2(button_scale, button_scale)
|
||||
# put_button.offset_left = button_positions.put.x
|
||||
# put_button.offset_top = button_positions.put.y
|
||||
# put_button.offset_right = button_positions.put.x + button_size
|
||||
# put_button.offset_bottom = button_positions.put.y + button_size
|
||||
|
||||
if attack_mode_button:
|
||||
attack_mode_button.visible = buttons_visible
|
||||
attack_mode_button.visible = true
|
||||
attack_mode_button.scale = Vector2(button_scale, button_scale)
|
||||
# attack_mode_button.offset_left = button_positions.attack_mode.x
|
||||
# attack_mode_button.offset_top = button_positions.attack_mode.y
|
||||
# attack_mode_button.offset_right = button_positions.attack_mode.x + button_size
|
||||
# attack_mode_button.offset_bottom = button_positions.attack_mode.y + button_size
|
||||
attack_mode_button.set_anchors_preset(Control.PRESET_BOTTOM_RIGHT)
|
||||
attack_mode_button.offset_left = button_positions.attack_mode.x
|
||||
attack_mode_button.offset_top = button_positions.attack_mode.y
|
||||
attack_mode_button.offset_right = button_positions.attack_mode.x + button_size
|
||||
attack_mode_button.offset_bottom = button_positions.attack_mode.y + button_size
|
||||
|
||||
if spawn_boost_button:
|
||||
spawn_boost_button.visible = buttons_visible
|
||||
spawn_boost_button.visible = true
|
||||
spawn_boost_button.scale = Vector2(button_scale, button_scale)
|
||||
# spawn_boost_button.offset_left = button_positions.spawn_boost.x
|
||||
# spawn_boost_button.offset_top = button_positions.spawn_boost.y
|
||||
# spawn_boost_button.offset_right = button_positions.spawn_boost.x + button_size
|
||||
# spawn_boost_button.offset_bottom = button_positions.spawn_boost.y + button_size
|
||||
spawn_boost_button.set_anchors_preset(Control.PRESET_BOTTOM_RIGHT)
|
||||
spawn_boost_button.offset_left = button_positions.spawn_boost.x
|
||||
spawn_boost_button.offset_top = button_positions.spawn_boost.y
|
||||
spawn_boost_button.offset_right = button_positions.spawn_boost.x + button_size
|
||||
spawn_boost_button.offset_bottom = button_positions.spawn_boost.y + button_size
|
||||
|
||||
if tekton_grab_button:
|
||||
tekton_grab_button.visible = true
|
||||
tekton_grab_button.scale = Vector2(button_scale, button_scale)
|
||||
tekton_grab_button.set_anchors_preset(Control.PRESET_BOTTOM_RIGHT)
|
||||
tekton_grab_button.offset_left = -280
|
||||
tekton_grab_button.offset_top = -80
|
||||
tekton_grab_button.offset_right = -280 + button_size
|
||||
tekton_grab_button.offset_bottom = -80 + button_size
|
||||
|
||||
# Force layer update
|
||||
visible = true
|
||||
|
||||
Reference in New Issue
Block a user