feat: Implement core game systems including settings management, player input, and initial gameplay mechanics with associated UI.

This commit is contained in:
Yogi Wiguna
2026-03-12 10:35:26 +08:00
parent 4f6783b468
commit 93eda69ad6
10 changed files with 218 additions and 155 deletions
+17 -46
View File
@@ -82,7 +82,7 @@ func _process(delta):
player.highlight_cells_if_authorized(area, highlight_id)
func handle_unhandled_input(event):
# Early return if not authorized human playersa
# Early return if not authorized human player
if not player.is_multiplayer_authority() or player.is_bot or player.is_in_group("Bots"):
player.set_process_unhandled_input(false)
return
@@ -97,66 +97,37 @@ func handle_unhandled_input(event):
# --- Keyboard Shortcuts (Event-based) ---
if event is InputEventKey and event.pressed and not event.echo:
var mode = LobbyManager.get_game_mode()
var is_sng = mode == GameMode.Mode.STOP_N_GO
# Safety check for SettingsManager
if not SettingsManager:
return
# 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:
# Single Slot Logic: Find whatever powerup is currently set to 'true' and activate it
var active_effect = -1
if player.special_tiles_manager:
for effect_key in player.special_tiles_manager.inventory:
if player.special_tiles_manager.inventory[effect_key]:
active_effect = effect_key
break
if active_effect != -1:
player.activate_powerup(active_effect)
else:
print("No powerup in slot 1 to activate.")
# KP Fallback
elif ek == KEY_KP_1:
var active_effect = -1
if player.special_tiles_manager:
for effect_key in player.special_tiles_manager.inventory:
if player.special_tiles_manager.inventory[effect_key]:
active_effect = effect_key
break
if active_effect != -1:
player.activate_powerup(active_effect)
# Action Buttons (Remappable)
elif ek == SettingsManager.get_control_keycode("attack_mode"):
# 1. Unified check for POWER-UP Activation
if event.is_action_pressed("use_powerup"):
player.activate_held_powerup()
get_viewport().set_input_as_handled()
return
# 3. Action Buttons (Remappable via InputMap)
if event.is_action_pressed("action_knock_tekton"):
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()
get_viewport().set_input_as_handled()
elif ek == SettingsManager.get_control_keycode("spawn_boost"):
elif event.is_action_pressed("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()
get_viewport().set_input_as_handled()
elif ek == SettingsManager.get_control_keycode("tekton_grab"):
elif event.is_action_pressed("action_grab_tekton"):
if not player.is_carrying_tekton and player.powerup_manager:
if player.powerup_manager.can_use_special():
if player.powerup_manager.has_method("can_use_special"): # Corrected method name
player.grab_tekton()
# Handle spawn point selection if not yet selected
get_viewport().set_input_as_handled()
# Handle spawn point selection if not yet selected
if not player.spawn_point_selected and player.highlighted_spawn_points.size() > 0:
if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
+70 -6
View File
@@ -31,23 +31,33 @@ var settings = {
},
"controls": {
"use_controller": false,
# Movement (Keybinds are typically fixed to WASD/Arrows in Input Map, but we show them)
# Movement
"move_up": KEY_W,
"move_up_alt": KEY_UP,
"move_down": KEY_S,
"move_down_alt": KEY_DOWN,
"move_left": KEY_A,
"move_left_alt": KEY_LEFT,
"move_right": KEY_D,
"move_right_alt": KEY_RIGHT,
# Actions
"grab": KEY_SPACE,
"grab_alt": KEY_J,
"put": KEY_R,
"put_alt": KEY_K,
"tekton_grab": KEY_G,
"tekton_grab_alt": KEY_L,
# Power-Up Controls
"powerup_1": KEY_1,
"powerup_2": KEY_2,
"powerup_3": KEY_3,
"powerup_4": KEY_4,
"use_powerup": KEY_F,
"use_powerup_alt": KEY_SHIFT,
# Power Bar Controls / Special
"attack_mode": KEY_Q,
"spawn_boost": KEY_E
"attack_mode_alt": KEY_U,
"spawn_boost": KEY_E,
"spawn_boost_alt": KEY_I
}
}
@@ -80,6 +90,7 @@ func save_settings():
func apply_all_settings():
apply_video_settings()
apply_audio_settings()
apply_control_settings()
emit_signal("settings_applied")
func apply_video_settings():
@@ -149,9 +160,62 @@ func set_bus_volume(bus_name: String, volume_linear: float):
AudioServer.set_bus_volume_db(bus_idx, linear_to_db(volume_linear))
AudioServer.set_bus_mute(bus_idx, volume_linear <= 0.001)
func apply_control_settings():
# Sync custom settings with InputMap
var mapping = {
"move_up": "move_north",
"move_down": "move_south",
"move_left": "move_west",
"move_right": "move_east",
"grab": "action_grab",
"put": "action_put",
"use_powerup": "use_powerup",
"tekton_grab": "action_grab_tekton",
"attack_mode": "action_knock_tekton",
"spawn_boost": "spawn_boost"
}
for setting_key in mapping.keys():
var action_name = mapping[setting_key]
if not InputMap.has_action(action_name):
InputMap.add_action(action_name)
InputMap.action_erase_events(action_name)
# Add Primary
var primary_key = settings.controls.get(setting_key)
if primary_key:
var event = InputEventKey.new()
event.keycode = primary_key
InputMap.action_add_event(action_name, event)
# Add Secondary
var secondary_key = settings.controls.get(setting_key + "_alt")
if secondary_key:
var event = InputEventKey.new()
event.keycode = secondary_key
InputMap.action_add_event(action_name, event)
# Add Joypad defaults for movement
if action_name.begins_with("move_"):
var joy_axis = -1
var axis_val = 0.0
match action_name:
"move_north": joy_axis = JOY_AXIS_LEFT_Y; axis_val = -1.0
"move_south": joy_axis = JOY_AXIS_LEFT_Y; axis_val = 1.0
"move_west": joy_axis = JOY_AXIS_LEFT_X; axis_val = -1.0
"move_east": joy_axis = JOY_AXIS_LEFT_X; axis_val = 1.0
if joy_axis != -1:
var joy_event = InputEventJoypadMotion.new()
joy_event.axis = joy_axis
joy_event.axis_value = axis_val
InputMap.action_add_event(action_name, joy_event)
func set_control(action_name: String, keycode: int):
if settings.controls.has(action_name):
settings.controls[action_name] = keycode
apply_control_settings() # Apply immediately
emit_signal("control_remapped", action_name, keycode)
save_settings()
+1 -27
View File
@@ -206,11 +206,6 @@ func activate_effect(effect: int, target_player: Node3D = null):
print("PowerUp %s not found in inventory or false. Inventory: %s" % [effect, inventory])
return
# Check Cooldown
if powerup_cooldowns.get(effect, 0.0) > 0:
print("PowerUp %s on cooldown." % SpecialEffect.keys()[effect])
return
# Check Attack Mode Restriction
if player.get("is_attack_mode") and effect == SpecialEffect.INVISIBLE_MODE:
NotificationManager.send_message(player, "Cannot enter Ghost mode while in Attack Mode!", NotificationManager.MessageType.WARNING)
@@ -221,16 +216,8 @@ func activate_effect(effect: int, target_player: Node3D = null):
NotificationManager.send_message(player, "Cannot use this power while carrying a Tekton!", NotificationManager.MessageType.WARNING)
return
# Calculate Cooldown based on Level
var level = powerup_levels.get(effect, 1)
# Linear Interp: Lvl 1 = 15s, Lvl 8 = 5s
# Slope = (5 - 15) / (8 - 1) = -10 / 7 = -1.428...
var cooldown_time = COOLDOWN_L1 + ((level - 1) * (COOLDOWN_L8 - COOLDOWN_L1) / 7.0)
powerup_cooldowns[effect] = cooldown_time
emit_signal("cooldown_updated", effect, cooldown_time, cooldown_time)
print("[SpecialTiles] Player %s activated %s (Lvl %d). Cooldown: %.1fs" % [player.name, SpecialEffect.keys()[effect], level, cooldown_time])
print("[SpecialTiles] Player %s activated %s (Lvl %d). No cooldown applied." % [player.name, SpecialEffect.keys()[effect], level])
match effect:
SpecialEffect.FASTER_SPEED:
@@ -555,19 +542,6 @@ func _check_for_icy_floor():
pass
func _process(delta):
# Update Cooldowns
for effect in powerup_cooldowns.keys():
if powerup_cooldowns[effect] > 0:
powerup_cooldowns[effect] -= delta
# Emit signal occasionally or only on change? Every frame might be too much for UI?
# Optimization: Emit every 0.1s or if diff is significant?
# For snappy UI text, frame sync is okay for local player.
emit_signal("cooldown_updated", effect, powerup_cooldowns[effect], 0.0) # max unused for tick
if powerup_cooldowns[effect] <= 0:
powerup_cooldowns[effect] = 0
emit_signal("cooldown_updated", effect, 0, 0)
# Update Active Buffs (Speed)
if active_buffs.has(SpecialEffect.FASTER_SPEED):
active_buffs[SpecialEffect.FASTER_SPEED] -= delta
+15 -1
View File
@@ -15,6 +15,7 @@ var attack_mode_button: Button # Renamed from special_button
var spawn_boost_button: Button
var settings_button: Button
var tekton_grab_button: Button
@onready var SettingsManager = get_node_or_null("/root/SettingsManager")
# Settings - persisted to config file
@@ -30,6 +31,7 @@ var button_positions: Dictionary = {
"attack_mode": Vector2(-200, -80), # Renamed
"spawn_boost": Vector2(-120, -80)
}
var button_scale: float = 1.0
# Reference to main scene and player
@@ -163,6 +165,7 @@ func _create_touch_ui():
put_button = _find_or_create_action_button(actions_container, "Put", "📦", button_positions.put)
tekton_grab_button = _find_or_create_action_button(actions_container, "TektonGrab", "👋", Vector2(-280, -80))
# Order: AttackMode, SpawnBoost, Grab, TektonGrab
if attack_mode_button:
@@ -184,6 +187,7 @@ func _create_touch_ui():
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:
@@ -306,6 +310,7 @@ func _ensure_shortcut_label(btn: Button, button_name: String):
"AttackMode": existing_lbl.text = SettingsManager.get_control_text("attack_mode")
"SpawnBoost": existing_lbl.text = SettingsManager.get_control_text("spawn_boost")
"TektonGrab": existing_lbl.text = SettingsManager.get_control_text("tekton_grab")
print("[TouchControls] Updated %s shortcut label to: %s" % [button_name, existing_lbl.text])
@@ -338,6 +343,7 @@ func _ensure_shortcut_label(btn: Button, button_name: String):
"AttackMode": shortcut_lbl.text = SettingsManager.get_control_text("attack_mode") if SettingsManager else "Q"
"SpawnBoost": shortcut_lbl.text = SettingsManager.get_control_text("spawn_boost") if SettingsManager else "E"
"TektonGrab": shortcut_lbl.text = SettingsManager.get_control_text("tekton_grab") if SettingsManager else "G"
btn.add_child(shortcut_lbl)
@@ -358,6 +364,7 @@ func _on_button_pressed(button_name: String):
"AttackMode": btn = attack_mode_button
"SpawnBoost": btn = spawn_boost_button
"TektonGrab": btn = tekton_grab_button
if btn:
var tween = create_tween()
@@ -396,6 +403,7 @@ func _on_button_pressed(button_name: String):
if local_player.has_method("grab_tekton"):
local_player.grab_tekton()
func _on_button_released(button_name: String):
var btn: Button
match button_name:
@@ -404,6 +412,7 @@ func _on_button_released(button_name: String):
"AttackMode": btn = attack_mode_button
"SpawnBoost": btn = spawn_boost_button
"TektonGrab": btn = tekton_grab_button
if btn:
var tween = create_tween()
@@ -445,7 +454,12 @@ func _load_settings():
attack_mode_pos = config.get_value("touch_controls", "special_position", Vector2(-200, -80))
var spawn_boost_pos = config.get_value("touch_controls", "spawn_boost_position", Vector2(-120, -80))
button_positions = {"grab": grab_pos, "put": put_pos, "attack_mode": attack_mode_pos, "spawn_boost": spawn_boost_pos}
button_positions = {
"grab": grab_pos,
"put": put_pos,
"attack_mode": attack_mode_pos,
"spawn_boost": spawn_boost_pos
}
# Apply loaded settings
_apply_settings()