feat: Add initial player character, movement, network synchronization, bot AI, and game managers.

This commit is contained in:
Yogi Wiguna
2026-02-24 16:54:45 +08:00
parent aa6d6dcec2
commit e31973dfab
7 changed files with 367 additions and 127 deletions
+18 -14
View File
@@ -55,7 +55,7 @@ func simple_move_to(grid_position: Vector2i) -> bool:
return false
if not player.is_multiplayer_authority():
# print("[Move] Failed: Not authority for ", player.name)
print("[Move] Failed: Not authority for %s (Authority: %d, My Peer: %d)" % [player.name, player.get_multiplayer_authority(), player.multiplayer.get_unique_id()])
return false
if player.get("is_frozen") or player.get("is_stop_frozen"):
@@ -144,7 +144,7 @@ func try_push(target_pos: Vector2i, direction: Vector2i) -> bool:
# === NEW LOGIC: Only allow push if in ATTACK MODE ===
if not player.get("is_attack_mode"):
# Standard bumping effect or nothing?
# User said "Remove standard push", so we just do nothing or small shake
print("[Move] Push blocked: Not in attack mode (%s trying to push)" % player.name)
return false
# === SUPER PUSH (Attack Mode) ===
@@ -196,19 +196,23 @@ func try_push(target_pos: Vector2i, direction: Vector2i) -> bool:
# Consume all available boost to force a full recharge cycle
player.powerup_manager.consume_boost(100.0)
# SCORING: 200 Points for successful attack
# SCORING: 200 Points for successful attack (ONLY in Free Mode)
if player.is_multiplayer_authority():
var main = player.get_tree().get_root().get_node_or_null("Main")
if main:
var gcm = main.get_node_or_null("GoalsCycleManager")
if gcm:
if multiplayer.is_server():
# Server/Bot: Directly add score to specific player ID
gcm.add_score(player.name.to_int(), 200)
else:
# Client: Request score add (sender ID used)
gcm.rpc("request_add_score", 200)
NotificationManager.send_message(player, "Successful Attack! +200 Pts", NotificationManager.MessageType.GOAL)
var is_sng = LobbyManager.game_mode == "Stop n Go"
if not is_sng:
var main = player.get_tree().get_root().get_node_or_null("Main")
if main:
var gcm = main.get_node_or_null("GoalsCycleManager")
if gcm:
if multiplayer.is_server():
# Server/Bot: Directly add score to specific player ID
gcm.add_score(player.name.to_int(), 200)
else:
# Client: Request score add (sender ID used)
gcm.rpc("request_add_score", 200)
NotificationManager.send_message(player, "Successful Attack! +200 Pts", NotificationManager.MessageType.GOAL)
else:
NotificationManager.send_message(player, "Successful Attack!", NotificationManager.MessageType.GOAL)
# 5. Attack Mode Persistence
# logic moved to consume_boost: checks if <= 0 then disables.
+12 -2
View File
@@ -31,10 +31,15 @@ func _normalize_tile(tile: int) -> int:
func grab_item(grid_position: Vector2i) -> bool:
var has_ap = player.action_points > 0 if TurnManager.turn_based_mode else true
if not enhanced_gridmap or not has_ap:
if not enhanced_gridmap:
print("[Grab] Failed for %s: enhanced_gridmap is null" % player.name)
return false
if not has_ap:
print("[Grab] Failed for %s: no AP (%d)" % [player.name, player.action_points])
return false
if player.get("is_frozen"):
print("[Grab] Failed for %s: player is frozen" % player.name)
return false
var cell = Vector3i(grid_position.x, 1, grid_position.y)
@@ -49,9 +54,11 @@ func grab_item(grid_position: Vector2i) -> bool:
is_adjacent = true
break
if not is_adjacent:
print("[Grab] Failed for %s: %s is not adjacent to current %s" % [player.name, grid_position, player.current_position])
return false
if item == -1:
print("[Grab] Failed for %s: no item at %s Layer 1" % [player.name, grid_position])
return false
# === AUTO-ARRANGE LOGIC (Client-side pre-check) ===
@@ -62,12 +69,15 @@ func grab_item(grid_position: Vector2i) -> bool:
if not is_powerup:
target_slot = find_best_goal_slot_for_item(item)
if target_slot == -1:
print("Player: No valid slot found for item.")
print("[Grab] Failed for %s: No valid slot found for item %d." % [player.name, item])
return false # no space
if not player.is_multiplayer_authority():
print("[Grab] Failed for %s: not authority" % player.name)
return false
print("[Grab] %s SUCCESS! Grabbing item %d at %s into slot %d" % [player.name, item, grid_position, target_slot])
# Play pickup animation (synced across network)
if player.is_multiplayer_authority() and player.has_method("sync_pickup_animation"):
player.rpc("sync_pickup_animation")
+12 -8
View File
@@ -315,14 +315,18 @@ func _execute_area_freeze(center_pos: Vector2i = Vector2i.ZERO):
hit_count += 1
if hit_count > 0 and player.is_multiplayer_authority():
var points = hit_count * 50
var main = player.get_tree().get_root().get_node_or_null("Main")
if main:
var gcm = main.get_node_or_null("GoalsCycleManager")
if gcm:
gcm.rpc("request_add_score", points)
NotificationManager.send_message(player, "Hit %d Players! +%d Pts" % [hit_count, points], NotificationManager.MessageType.GOAL)
var is_sng = LobbyManager.game_mode == "Stop n Go"
if not is_sng:
var points = hit_count * 50
var main = player.get_tree().get_root().get_node_or_null("Main")
if main:
var gcm = main.get_node_or_null("GoalsCycleManager")
if gcm:
gcm.rpc("request_add_score", points)
NotificationManager.send_message(player, "Hit %d Players! +%d Pts" % [hit_count, points], NotificationManager.MessageType.GOAL)
else:
NotificationManager.send_message(player, "Hit %d Players!" % hit_count, NotificationManager.MessageType.GOAL)
# Visual Feedback (Turn Floor Blue - Item 12 on Layer 0)
if player.is_multiplayer_authority():
+16 -13
View File
@@ -11,7 +11,7 @@ enum Phase {GO, STOP}
const GO_DURATION: float = 8.0
const STOP_DURATION: float = 4.0
const REQUIRED_GOALS: int = 2
const REQUIRED_GOALS: int = 5
var current_phase: Phase = Phase.GO
var phase_timer: float = GO_DURATION
@@ -416,12 +416,7 @@ func sync_mission_progress(_player_id: int, _mission_index: int, _current: int):
# Deprecated
pass
func check_win_condition(player_id: int, position: Vector2i) -> bool:
# 1. Must reach the finish line (Column 21)
if position.x < finish_line_x:
return false
# 2. Must have enough Goal Completions (tracked by GoalsCycleManager)
func is_mission_complete(player_id: int) -> bool:
var main = get_node_or_null("/root/Main")
if not main: return false
@@ -429,15 +424,23 @@ func check_win_condition(player_id: int, position: Vector2i) -> bool:
if not goals_cycle_manager: return false
var completed_count = goals_cycle_manager.player_goal_counts.get(player_id, 0)
if completed_count >= REQUIRED_GOALS:
print("[StopNGo] Player %d REACHED FINISH with %d goals complete!" % [player_id, completed_count])
return completed_count >= REQUIRED_GOALS
func check_win_condition(player_id: int, position: Vector2i) -> bool:
# 1. Must reach the finish line (Column 21)
if position.x < finish_line_x:
return false
# 2. Must have enough Goal Completions
if is_mission_complete(player_id):
print("[StopNGo] Player %d REACHED FINISH with goals complete!" % player_id)
return true
else:
# Inform the player locally if they reach the end without goals
var player_node = main.get_node_or_null(str(player_id))
var main = get_node_or_null("/root/Main")
var player_node = main.get_node_or_null(str(player_id)) if main else null
if player_node:
NotificationManager.send_message(player_node, "Incomplete! Achieve %d goals (x%d) to win!" % [REQUIRED_GOALS, REQUIRED_GOALS], NotificationManager.MessageType.WARNING)
NotificationManager.send_message(player_node, "Incomplete! Achieve %d goals to win!" % REQUIRED_GOALS, NotificationManager.MessageType.WARNING)
print("[StopNGo] Player %d reached finish but goal count too low: %d/%d" % [player_id, completed_count, REQUIRED_GOALS])
print("[StopNGo] Player %d reached finish but goals incomplete." % player_id)
return false