fix: downgrade workflow godot version to 4.6.3 to match system, fix godot compilation errors

This commit is contained in:
2026-07-06 12:09:23 +08:00
parent 9747f91b20
commit 5c789cb37f
8 changed files with 368 additions and 354 deletions
+11 -11
View File
@@ -27,22 +27,22 @@ jobs:
- name: Setup Godot (Cached) - name: Setup Godot (Cached)
run: | run: |
apt-get update -qq && apt-get install -y -qq unzip curl apt-get update -qq && apt-get install -y -qq unzip curl
if [ ! -f /cache/godot_4.7 ]; then if [ ! -f /cache/godot_4.6.3 ]; then
echo "Downloading Godot 4.7..." echo "Downloading Godot 4.6.3..."
curl -sL -o /tmp/godot.zip "https://github.com/godotengine/godot-builds/releases/download/4.7-stable/Godot_v4.7-stable_linux.x86_64.zip" curl -sL -o /tmp/godot.zip "https://github.com/godotengine/godot-builds/releases/download/4.6.3-stable/Godot_v4.6.3-stable_linux.x86_64.zip"
unzip -q -o /tmp/godot.zip -d /cache/ unzip -q -o /tmp/godot.zip -d /cache/
mv /cache/Godot_v4.7-stable_linux.x86_64 /cache/godot_4.7 mv /cache/Godot_v4.6.3-stable_linux.x86_64 /cache/godot_4.6.3
fi fi
cp /cache/godot_4.7 /usr/local/bin/godot cp /cache/godot_4.6.3 /usr/local/bin/godot
chmod +x /usr/local/bin/godot chmod +x /usr/local/bin/godot
mkdir -p ~/.local/share/godot/export_templates/4.7.stable mkdir -p ~/.local/share/godot/export_templates/4.6.3.stable
if [ ! -f /cache/Godot_v4.7-stable_export_templates.tpz ]; then if [ ! -f /cache/Godot_v4.6.3-stable_export_templates.tpz ]; then
echo "Downloading templates..." echo "Downloading templates..."
curl -sL -o /cache/Godot_v4.7-stable_export_templates.tpz \ curl -sL -o /cache/Godot_v4.6.3-stable_export_templates.tpz \
"https://github.com/godotengine/godot-builds/releases/download/4.7-stable/Godot_v4.7-stable_export_templates.tpz" "https://github.com/godotengine/godot-builds/releases/download/4.6.3-stable/Godot_v4.6.3-stable_export_templates.tpz"
fi fi
cd ~/.local/share/godot/export_templates/4.7.stable cd ~/.local/share/godot/export_templates/4.6.3.stable
unzip -q -o /cache/Godot_v4.7-stable_export_templates.tpz unzip -q -o /cache/Godot_v4.6.3-stable_export_templates.tpz
mv templates/* . mv templates/* .
rm -rf templates rm -rf templates
cd $GITHUB_WORKSPACE cd $GITHUB_WORKSPACE
+5 -5
View File
@@ -28,14 +28,14 @@ jobs:
- name: Setup Godot (Cached) - name: Setup Godot (Cached)
run: | run: |
if [ ! -f /cache/godot_4.7 ]; then if [ ! -f /cache/godot_4.6.3 ]; then
echo "Downloading Godot 4.7..." echo "Downloading Godot 4.6.3..."
apt-get update -qq && apt-get install -y -qq unzip curl apt-get update -qq && apt-get install -y -qq unzip curl
curl -sL -o /tmp/godot.zip "https://github.com/godotengine/godot-builds/releases/download/4.7-stable/Godot_v4.7-stable_linux.x86_64.zip" curl -sL -o /tmp/godot.zip "https://github.com/godotengine/godot-builds/releases/download/4.6.3-stable/Godot_v4.6.3-stable_linux.x86_64.zip"
unzip -q -o /tmp/godot.zip -d /cache/ unzip -q -o /tmp/godot.zip -d /cache/
mv /cache/Godot_v4.7-stable_linux.x86_64 /cache/godot_4.7 mv /cache/Godot_v4.6.3-stable_linux.x86_64 /cache/godot_4.6.3
fi fi
cp /cache/godot_4.7 /usr/local/bin/godot cp /cache/godot_4.6.3 /usr/local/bin/godot
chmod +x /usr/local/bin/godot chmod +x /usr/local/bin/godot
mkdir -p build mkdir -p build
+1 -1
View File
@@ -17,7 +17,7 @@ compatibility/default_parent_skeleton_in_mesh_instance_3d=true
config/name="Tekton Dash Armageddon" config/name="Tekton Dash Armageddon"
config/version="2.4.3" config/version="2.4.3"
run/main_scene="res://scenes/ui/boot_screen.tscn" run/main_scene="res://scenes/ui/boot_screen.tscn"
config/features=PackedStringArray("4.7", "Forward Plus") config/features=PackedStringArray("4.6", "Forward Plus")
boot_splash/bg_color=Color(0.16470589, 0.6745098, 0.9372549, 1) boot_splash/bg_color=Color(0.16470589, 0.6745098, 0.9372549, 1)
boot_splash/stretch_mode=0 boot_splash/stretch_mode=0
boot_splash/image="uid://b10e6kr508642" boot_splash/image="uid://b10e6kr508642"
+2 -1
View File
@@ -204,7 +204,8 @@ func _setup_effect_elevation():
print("[Main] MeshLibrary elevation applied: Wall(4) and Freeze(5) at Y=0.8") print("[Main] MeshLibrary elevation applied: Wall(4) and Freeze(5) at Y=0.8")
# Force gridmap cell size to match player logic (1, 0.05, 1) - >0.001 to avoid errors # Force gridmap cell size to match player logic (1, 0.05, 1) - >0.001 to avoid errors
var em = $EnhancedGridMap if not em:
em = get_node_or_null("EnhancedGridMap")
if em: if em:
em.cell_size = Vector3(1, 0.05, 1) em.cell_size = Vector3(1, 0.05, 1)
+16 -6
View File
@@ -218,17 +218,27 @@ func _run_ai_tick():
# Priority 0.7: Candy Survival — Deliver candies to Mekton NPC # Priority 0.7: Candy Survival — Deliver candies to Mekton NPC
if strategic_planner and strategic_planner.is_candy_survival_mode(): if strategic_planner and strategic_planner.is_candy_survival_mode():
var gm = strategic_planner._get_candy_survival_manager() var gm = strategic_planner._get_candy_survival_manager()
if gm and gm.is_active: if gm and gm.active:
var pid = actor.get("peer_id") if "peer_id" in actor else actor.name.to_int() var pid = actor.get("peer_id") if "peer_id" in actor else actor.name.to_int()
var held_color = gm.player_candy_color.get(pid, -1) var held_color = gm.player_candy_color.get(pid, -1)
if held_color != -1 and gm.player_candies.get(pid, 0) > 0: if held_color != -1 and gm.player_candies.get(pid, 0) > 0:
if held_color == gm.current_face: if held_color == gm.current_face:
# Face matches! Run to the NPC Center! # Face matches! Run to the NPC Center!
var path = strategic_planner.find_path(actor.current_position, Vector2i(8, 8)) var path = enhanced_gridmap.find_path(
if path.size() > 0: Vector2(actor.current_position),
_execute_move(path[0]) Vector2(8, 8),
print("[BotController] Action Taken: Delivering candies to Mekton!") 0, false, false
return )
if path.size() >= 2:
var next_step = Vector2i(path[1].x, path[1].y)
if actor.movement_manager.simple_move_to(next_step):
_is_processing_action = true
_current_action = "delivering_candies"
while is_instance_valid(actor) and actor.is_player_moving:
await get_tree().process_frame
_is_processing_action = false
print("[BotController] Action Taken: Delivering candies to Mekton!")
return
# Priority 1: Tekton Management (Grab Tekton if full boost, or spawn if carrying) # Priority 1: Tekton Management (Grab Tekton if full boost, or spawn if carrying)
# Spawning while carrying is high priority; Hunting is medium priority. # Spawning while carrying is high priority; Hunting is medium priority.
+331 -328
View File
@@ -56,38 +56,34 @@ var hud_delivery_indicator: Label
var _candy_survival_hud_scene: PackedScene = preload("res://scenes/candy_survival_hud.tscn") var _candy_survival_hud_scene: PackedScene = preload("res://scenes/candy_survival_hud.tscn")
func _ready(): func _ready():
set_process(false) set_process(false)
_setup_hud() _setup_hud()
func _setup_hud() -> void: func _setup_hud() -> void:
var hud_instance = _candy_survival_hud_scene.instantiate() var hud_instance = _candy_survival_hud_scene.instantiate()
hud_layer = hud_instance hud_layer = hud_instance
hud_layer.visible = false hud_layer.visible = false
add_child(hud_layer) add_child(hud_layer)
var bot_box = hud_layer.get_node_or_null("BottomContainer/HBoxContainer") var bot_box = hud_layer.get_node_or_null("BottomContainer/HBoxContainer")
if bot_box: if bot_box:
hud_knock_label = bot_box.get_node_or_null("SabotagePanel/KnockChargesLabel") hud_knock_label = bot_box.get_node_or_null("SabotagePanel/KnockChargesLabel")
hud_ghost_label = bot_box.get_node_or_null("SabotagePanel/GhostChargesLabel") hud_ghost_label = bot_box.get_node_or_null("SabotagePanel/GhostChargesLabel")
hud_stack_badge = bot_box.get_node_or_null("ScorePanel/CandyStackBadge") hud_stack_badge = bot_box.get_node_or_null("ScorePanel/CandyStackBadge")
hud_points_label = bot_box.get_node_or_null("ScorePanel/PointsLabel") hud_points_label = bot_box.get_node_or_null("ScorePanel/PointsLabel")
hud_delivery_indicator = bot_box.get_node_or_null("ScorePanel/DeliveryIndicator") hud_delivery_indicator = bot_box.get_node_or_null("ScorePanel/DeliveryIndicator")
var top_box = hud_layer.get_node_or_null("TopContainer") var top_box = hud_layer.get_node_or_null("TopContainer")
if top_box: if top_box:
hud_rush_bar = top_box.get_node_or_null("SugarRushBar") hud_rush_bar = top_box.get_node_or_null("SugarRushBar")
if hud_rush_bar: if hud_rush_bar:
hud_rush_label = hud_rush_bar.get_node_or_null("SugarRushLabel") hud_rush_label = hud_rush_bar.get_node_or_null("SugarRushLabel")
func start_game_mode() -> void:
if multiplayer.is_server():
activate_client_side()
func activate_client_side() -> void: func activate_client_side() -> void:
active = true active = true
if hud_layer: if hud_layer:
hud_layer.visible = true hud_layer.visible = true
set_process(true) set_process(true)
# Per-player state # Per-player state
var player_candies: Dictionary = {} # pid -> int (stack count) var player_candies: Dictionary = {} # pid -> int (stack count)
@@ -110,410 +106,417 @@ const TILE_IDS: Dictionary = {CandyColor.HEART: 7, CandyColor.DIAMOND: 8, CandyC
# ── Initialization ── # ── Initialization ──
func initialize(main, grid) -> void: func initialize(main, grid) -> void:
main_scene = main main_scene = main
gridmap = grid gridmap = grid
_define_sticky_walls() _define_sticky_walls()
print("[CandySurvival] Initialized") print("[CandySurvival] Initialized")
func _define_sticky_walls() -> void: func _define_sticky_walls() -> void:
# Arena boundary walls (rows/cols 0 and 17) # Arena boundary walls (rows/cols 0 and 17)
for x in range(ARENA_COLS): for x in range(ARENA_COLS):
sticky_cells[Vector2i(x, 0)] = true sticky_cells[Vector2i(x, 0)] = true
sticky_cells[Vector2i(x, ARENA_ROWS - 1)] = true sticky_cells[Vector2i(x, ARENA_ROWS - 1)] = true
for y in range(ARENA_ROWS): for y in range(ARENA_ROWS):
sticky_cells[Vector2i(0, y)] = true sticky_cells[Vector2i(0, y)] = true
sticky_cells[Vector2i(ARENA_COLS - 1, y)] = true sticky_cells[Vector2i(ARENA_COLS - 1, y)] = true
# Mekton NPC zone (3x3 center) # Mekton NPC zone (3x3 center)
for dx in range(NPC_CELLS): for dx in range(NPC_CELLS):
for dy in range(NPC_CELLS): for dy in range(NPC_CELLS):
sticky_cells[Vector2i(NPC_CENTER.x + dx, NPC_CENTER.y + dy)] = true sticky_cells[Vector2i(NPC_CENTER.x + dx, NPC_CENTER.y + dy)] = true
func start_game_mode() -> void: func start_game_mode() -> void:
active = true if multiplayer.is_server():
current_face = CandyColor.HEART activate_client_side()
face_timer = 0.0 active = true
game_elapsed = 0.0 current_face = CandyColor.HEART
player_candies.clear() face_timer = 0.0
player_candy_color.clear() game_elapsed = 0.0
player_knocks.clear() player_candies.clear()
player_ghosts.clear() player_candy_color.clear()
player_ghost_active.clear() player_knocks.clear()
player_ghost_timer.clear() player_ghosts.clear()
player_score.clear() player_ghost_active.clear()
player_blueprints.clear() player_ghost_timer.clear()
player_sugar_rush.clear() player_score.clear()
player_last_knocked_by.clear() player_blueprints.clear()
player_sugar_rush.clear()
player_last_knocked_by.clear()
# Init players from lobby # Init players from lobby
var pids = _get_player_ids() var pids = _get_player_ids()
for pid in pids: for pid in pids:
player_candies[pid] = 0 player_candies[pid] = 0
player_candy_color[pid] = -1 player_candy_color[pid] = -1
player_knocks[pid] = START_KNOCK player_knocks[pid] = START_KNOCK
player_ghosts[pid] = START_GHOST player_ghosts[pid] = START_GHOST
player_ghost_active[pid] = false player_ghost_active[pid] = false
player_ghost_timer[pid] = 0.0 player_ghost_timer[pid] = 0.0
player_score[pid] = 0 player_score[pid] = 0
player_blueprints[pid] = 0 player_blueprints[pid] = 0
player_sugar_rush[pid] = 0.0 player_sugar_rush[pid] = 0.0
player_last_knocked_by[pid] = 0 player_last_knocked_by[pid] = 0
print("[CandySurvival] Started with %d players" % pids.size()) print("[CandySurvival] Started with %d players" % pids.size())
func _enter_tree() -> void: func _enter_tree() -> void:
set_process(true) set_process(true)
func _process(delta: float) -> void: func _process(delta: float) -> void:
if not active: if not active:
return return
game_elapsed += delta game_elapsed += delta
# Mekton face color cycle # Mekton face color cycle
face_timer += delta face_timer += delta
if face_timer >= COLOR_CYCLE_TIME: if face_timer >= COLOR_CYCLE_TIME:
face_timer = 0.0 face_timer = 0.0
current_face = CandyColor.values()[(current_face + 1) % CandyColor.size()] current_face = CandyColor.values()[(current_face + 1) % CandyColor.size()]
# Sync color to the Mekton NPC # Sync color to the Mekton NPC
if mekton_node and mekton_node.has_method("set_face_color_rpc"): if mekton_node and mekton_node.has_method("set_face_color_rpc"):
mekton_node.rpc("set_face_color_rpc", current_face) mekton_node.rpc("set_face_color_rpc", current_face)
# Update delivery indicators for all players based on new face # Update delivery indicators for all players based on new face
for pid in player_candies: for pid in player_candies:
_update_candy_badge(pid) _update_candy_badge(pid)
# Sugar Rush timers # Sugar Rush timers
var any_rush = false var any_rush = false
for pid in player_sugar_rush.keys(): for pid in player_sugar_rush.keys():
if player_sugar_rush[pid] > 0: if player_sugar_rush[pid] > 0:
player_sugar_rush[pid] -= delta player_sugar_rush[pid] -= delta
if player_sugar_rush[pid] <= 0: if player_sugar_rush[pid] <= 0:
player_sugar_rush[pid] = 0.0 player_sugar_rush[pid] = 0.0
_restore_time_scale() _restore_time_scale()
sugar_rush_ended.emit(pid) sugar_rush_ended.emit(pid)
if player_sugar_rush[pid] > 0: if player_sugar_rush[pid] > 0:
any_rush = true any_rush = true
if any_rush: if any_rush:
Engine.time_scale = SR_SPEED Engine.time_scale = SR_SPEED
# Ghost timers # Ghost timers
for pid in player_ghost_active.keys(): for pid in player_ghost_active.keys():
if player_ghost_active[pid]: if player_ghost_active[pid]:
player_ghost_timer[pid] -= delta player_ghost_timer[pid] -= delta
if player_ghost_timer[pid] <= 0: if player_ghost_timer[pid] <= 0:
player_ghost_active[pid] = false player_ghost_active[pid] = false
player_ghost_timer[pid] = 0.0 player_ghost_timer[pid] = 0.0
rpc("sync_ghost_active", pid, false) rpc("sync_ghost_active", pid, false)
# Turn off ghost visual/state # Turn off ghost visual/state
var all_players = get_tree().get_nodes_in_group("Players") var all_players = get_tree().get_nodes_in_group("Players")
for player in all_players: for player in all_players:
var curr_pid = player.get("peer_id") if "peer_id" in player else player.name.to_int() var curr_pid = player.get("peer_id") if "peer_id" in player else player.name.to_int()
if curr_pid == pid: if curr_pid == pid:
player.set("is_invisible", false) player.set("is_invisible", false)
if player.has_method("sync_modulate"): if player.has_method("sync_modulate"):
if multiplayer.is_server() and player.has_method("can_rpc") and player.can_rpc(): if multiplayer.is_server() and player.has_method("can_rpc") and player.can_rpc():
player.rpc("sync_modulate", Color.WHITE) player.rpc("sync_modulate", Color.WHITE)
else: else:
player.sync_modulate(Color.WHITE) player.sync_modulate(Color.WHITE)
break break
# Candy stack: points per second per candy # Candy stack: points per second per candy
for pid in player_candies.keys(): for pid in player_candies.keys():
var count = player_candies.get(pid, 0) var count = player_candies.get(pid, 0)
if count > 0: if count > 0:
var points = count * CANDY_PPS * delta var points = count * CANDY_PPS * delta
_add_score(pid, int(points)) _add_score(pid, int(points))
# ── Blueprint ── # ── Blueprint ──
func on_blueprint_completed(pid: int, primary_tile_id: int, off_color: bool) -> void: func on_blueprint_completed(pid: int, primary_tile_id: int, off_color: bool) -> void:
var base_points = 1000 var base_points = 1000
var points = base_points if not off_color else int(base_points * OFF_COLOR_PENALTY) var points = base_points if not off_color else int(base_points * OFF_COLOR_PENALTY)
_add_score(pid, points) _add_score(pid, points)
player_blueprints[pid] = player_blueprints.get(pid, 0) + 1 player_blueprints[pid] = player_blueprints.get(pid, 0) + 1
var candy_color = CandyColor.HEART var candy_color = CandyColor.HEART
for key in TILE_IDS: for key in TILE_IDS:
if TILE_IDS[key] == primary_tile_id: if TILE_IDS[key] == primary_tile_id:
candy_color = key candy_color = key
break break
_give_candy(pid, candy_color) _give_candy(pid, candy_color)
func can_finish_with_off_color(pid: int, primary_tile_id: int) -> bool: func can_finish_with_off_color(pid: int, primary_tile_id: int) -> bool:
return not _grid_has_color_tiles(primary_tile_id) return not _grid_has_color_tiles(primary_tile_id)
func _grid_has_color_tiles(target_tile_id: int) -> bool: func _grid_has_color_tiles(target_tile_id: int) -> bool:
if not gridmap: if not gridmap:
return true return true
for x in range(1, ARENA_COLS - 1): for x in range(1, ARENA_COLS - 1):
for y in range(1, ARENA_ROWS - 1): for y in range(1, ARENA_ROWS - 1):
var v = Vector3i(x, 1, y) var v = Vector3i(x, 1, y)
if gridmap.get_cell_item(v) == target_tile_id: if gridmap.get_cell_item(v) == target_tile_id:
return true return true
return false return false
# ── Candy Stack ── # ── Candy Stack ──
func _give_candy(pid: int, color: int) -> void: func _give_candy(pid: int, color: int) -> void:
player_candies[pid] = player_candies.get(pid, 0) + 1 player_candies[pid] = player_candies.get(pid, 0) + 1
player_candy_color[pid] = color player_candy_color[pid] = color
_update_candy_badge(pid) _update_candy_badge(pid)
func _update_candy_badge(pid: int) -> void: func _update_candy_badge(pid: int) -> void:
var count = player_candies.get(pid, 0) var count = player_candies.get(pid, 0)
var mult = 1.0 + count * MULTI_STEP var mult = 1.0 + count * MULTI_STEP
var color = player_candy_color.get(pid, -1) var color = player_candy_color.get(pid, -1)
var face_match = (color != -1 and color == current_face) var face_match = (color != -1 and color == current_face)
rpc("sync_candy_badge", pid, count, mult, face_match) rpc("sync_candy_badge", pid, count, mult, face_match)
# Update the 3D meshes on the player's head across all clients # Update the 3D meshes on the player's head across all clients
var all_players = get_tree().get_nodes_in_group("Players") var all_players = get_tree().get_nodes_in_group("Players")
for player in all_players: for player in all_players:
var curr_pid = player.get("peer_id") if "peer_id" in player else player.name.to_int() var curr_pid = player.get("peer_id") if "peer_id" in player else player.name.to_int()
if curr_pid == pid: if curr_pid == pid:
if player.has_method("sync_candy_stack"): if player.has_method("sync_candy_stack"):
player.rpc("sync_candy_stack", count, color) player.rpc("sync_candy_stack", count, color)
break break
func get_multiplier(pid: int) -> float: func get_multiplier(pid: int) -> float:
var count = player_candies.get(pid, 0) var count = player_candies.get(pid, 0)
return 1.0 + count * MULTI_STEP return 1.0 + count * MULTI_STEP
# ── Mekton Delivery ── # ── Mekton Delivery ──
@rpc("any_peer", "call_local", "reliable") @rpc("any_peer", "call_local", "reliable")
func try_deliver(pid: int) -> bool: func try_deliver(pid: int) -> bool:
if not multiplayer.is_server(): if not multiplayer.is_server():
return false return false
var color = player_candy_color.get(pid, -1) var color = player_candy_color.get(pid, -1)
if color == -1 or player_candies.get(pid, 0) == 0: if color == -1 or player_candies.get(pid, 0) == 0:
return false return false
if color != current_face: if color != current_face:
return false return false
var count = player_candies[pid] var count = player_candies[pid]
var mult = get_multiplier(pid) var mult = get_multiplier(pid)
_add_score(pid, count * 500) _add_score(pid, count * 500)
player_candies[pid] = 0 player_candies[pid] = 0
player_candy_color[pid] = -1 player_candy_color[pid] = -1
_update_candy_badge(pid) _update_candy_badge(pid)
_trigger_sugar_rush(pid, count, mult) _trigger_sugar_rush(pid, count, mult)
return true return true
func _trigger_sugar_rush(pid: int, candies: int, mult: float) -> void: func _trigger_sugar_rush(pid: int, candies: int, mult: float) -> void:
var duration = SR_BASE_TIME + candies * SR_PER_CANDY * mult var duration = SR_BASE_TIME + candies * SR_PER_CANDY * mult
player_sugar_rush[pid] = duration player_sugar_rush[pid] = duration
Engine.time_scale = SR_SPEED Engine.time_scale = SR_SPEED
sugar_rush_started.emit(pid) sugar_rush_started.emit(pid)
func _restore_time_scale() -> void: func _restore_time_scale() -> void:
var any_rush = false var any_rush = false
for pid in player_sugar_rush.keys(): for pid in player_sugar_rush.keys():
if player_sugar_rush[pid] > 0: if player_sugar_rush[pid] > 0:
any_rush = true any_rush = true
break break
if not any_rush: if not any_rush:
Engine.time_scale = 1.0 Engine.time_scale = 1.0
# ── Knock / Ghost ── # ── Knock / Ghost ──
@rpc("any_peer", "call_local", "reliable") @rpc("any_peer", "call_local", "reliable")
func try_knock(attacker: int, target: int) -> bool: func try_knock(attacker: int, target: int) -> bool:
if not multiplayer.is_server(): if not multiplayer.is_server():
return false return false
if player_ghost_active.get(target, false): if player_ghost_active.get(target, false):
return false return false
if player_knocks.get(attacker, 0) <= 0: if player_knocks.get(attacker, 0) <= 0:
return false return false
var target_candies = player_candies.get(target, 0) var target_candies = player_candies.get(target, 0)
if target_candies == 0: if target_candies == 0:
# Backfire: both lose a charge # Backfire: both lose a charge
player_knocks[attacker] = player_knocks.get(attacker, 0) - 1 player_knocks[attacker] = player_knocks.get(attacker, 0) - 1
# Attacker is also knocked down briefly (visual handled by caller) # Attacker is also knocked down briefly (visual handled by caller)
rpc("sync_knock_result", attacker, target, 0) rpc("sync_knock_result", attacker, target, 0)
return false return false
# Steal all candies # Steal all candies
player_candies[attacker] = player_candies.get(attacker, 0) + target_candies player_candies[attacker] = player_candies.get(attacker, 0) + target_candies
player_candies[target] = 0 player_candies[target] = 0
player_candy_color[target] = -1 player_candy_color[target] = -1
player_candy_color[attacker] = player_candy_color.get(target, -1) player_candy_color[attacker] = player_candy_color.get(target, -1)
player_knocks[attacker] = player_knocks.get(attacker, 0) - 1 player_knocks[attacker] = player_knocks.get(attacker, 0) - 1
player_last_knocked_by[target] = attacker player_last_knocked_by[target] = attacker
_add_score(attacker, target_candies * 100) _add_score(attacker, target_candies * 100)
_update_candy_badge(attacker) _update_candy_badge(attacker)
_update_candy_badge(target) _update_candy_badge(target)
rpc("sync_knock_result", attacker, target, target_candies) rpc("sync_knock_result", attacker, target, target_candies)
return true return true
func try_activate_ghost(pid: int) -> bool: func try_activate_ghost(pid: int) -> bool:
if player_ghost_active.get(pid, false): if player_ghost_active.get(pid, false):
return false return false
if player_ghosts.get(pid, 0) <= 0: if player_ghosts.get(pid, 0) <= 0:
return false return false
player_ghosts[pid] = player_ghosts.get(pid, 0) - 1 player_ghosts[pid] = player_ghosts.get(pid, 0) - 1
player_ghost_active[pid] = true player_ghost_active[pid] = true
player_ghost_timer[pid] = GHOST_DURATION player_ghost_timer[pid] = GHOST_DURATION
rpc("sync_ghost_active", pid, true) rpc("sync_ghost_active", pid, true)
# Core powerup integration: set is_invisible on the player node # Core powerup integration: set is_invisible on the player node
var all_players = get_tree().get_nodes_in_group("Players") var all_players = get_tree().get_nodes_in_group("Players")
for player in all_players: for player in all_players:
var curr_pid = player.get("peer_id") if "peer_id" in player else player.name.to_int() var curr_pid = player.get("peer_id") if "peer_id" in player else player.name.to_int()
if curr_pid == pid: if curr_pid == pid:
player.set("is_invisible", true) player.set("is_invisible", true)
if player.has_method("sync_modulate"): if player.has_method("sync_modulate"):
if multiplayer.is_server() and player.has_method("can_rpc") and player.can_rpc(): if multiplayer.is_server() and player.has_method("can_rpc") and player.can_rpc():
player.rpc("sync_modulate", Color(1.0, 1.0, 1.0, 0.4)) player.rpc("sync_modulate", Color(1.0, 1.0, 1.0, 0.4))
else: else:
player.sync_modulate(Color(1.0, 1.0, 1.0, 0.4)) player.sync_modulate(Color(1.0, 1.0, 1.0, 0.4))
break break
return true return true
func is_ghost_active(pid: int) -> bool: func is_ghost_active(pid: int) -> bool:
return player_ghost_active.get(pid, false) return player_ghost_active.get(pid, false)
# ── Arena ── # ── Arena ──
func _setup_arena() -> void: func _setup_arena() -> void:
if not gridmap: if not gridmap:
return return
gridmap.clear_layer(0) gridmap.set("columns", ARENA_COLS)
gridmap.clear_layer(1) gridmap.set("rows", ARENA_ROWS)
gridmap.clear_layer(2) gridmap.clear()
# Build floor # Build floor
for x in range(ARENA_COLS): for x in range(ARENA_COLS):
for y in range(ARENA_ROWS): for y in range(ARENA_ROWS):
gridmap.set_cell_item(Vector3i(x, 0, y), 0) gridmap.set_cell_item(Vector3i(x, 0, y), 0)
# Apply sticky walls on layer 2 # Apply sticky walls on layer 2
for pos in sticky_cells.keys(): for pos in sticky_cells.keys():
gridmap.set_cell_item(Vector3i(pos.x, 2, pos.y), 14) gridmap.set_cell_item(Vector3i(pos.x, 2, pos.y), 14)
# Spawn Mekton NPC at center # Spawn Mekton NPC at center
if mekton_node: if mekton_node:
mekton_node.queue_free() mekton_node.queue_free()
mekton_node = _spawn_mekton_npc(NPC_CENTER) mekton_node = _spawn_mekton_npc(NPC_CENTER)
print("[CandySurvival] Arena setup 18x18") print("[CandySurvival] Arena setup 18x18")
func _spawn_mekton_npc(center: Vector2i) -> Node: func _spawn_mekton_npc(center: Vector2i) -> Node:
var path = "res://scenes/candy_survival_npc.tscn" var path = "res://scenes/candy_survival_npc.tscn"
if ResourceLoader.exists(path): if ResourceLoader.exists(path):
var npc = load(path).instantiate() var npc = load(path).instantiate()
npc.position = Vector3(center.x + 1, 0, center.y + 1) npc.position = Vector3(center.x + 1, 0, center.y + 1)
add_child(npc) add_child(npc)
if npc.has_method("set_face_color_rpc"): if npc.has_method("set_face_color_rpc"):
npc.set_face_color_rpc(current_face) npc.set_face_color_rpc(current_face)
return npc return npc
return null return null
func _apply_arena_setup() -> void: func _apply_arena_setup() -> void:
pass pass
func setup_mission_tiles() -> void: func setup_mission_tiles() -> void:
if not gridmap: if not gridmap:
return return
randomize() randomize()
var tile_ids = [7, 8, 9, 10] var tile_ids = [7, 8, 9, 10]
var count = 0 var count = 0
for x in range(1, ARENA_COLS - 1): for x in range(1, ARENA_COLS - 1):
for y in range(1, ARENA_ROWS - 1): for y in range(1, ARENA_ROWS - 1):
if sticky_cells.has(Vector2i(x, y)): if sticky_cells.has(Vector2i(x, y)):
continue continue
if randf() < 0.6: if randf() < 0.6:
var tile = tile_ids[randi() % tile_ids.size()] var tile = tile_ids[randi() % tile_ids.size()]
gridmap.set_cell_item(Vector3i(x, 1, y), tile) gridmap.set_cell_item(Vector3i(x, 1, y), tile)
count += 1 count += 1
print("[CandySurvival] Spawned %d mission tiles" % count) print("[CandySurvival] Spawned %d mission tiles" % count)
func get_spawn_points(count: int) -> Array: func get_spawn_points(count: int) -> Array:
var points = [] var points = []
var corners = [ var corners = [
Vector2i(2, 2), Vector2i(ARENA_COLS - 2, 2), Vector2i(2, 2), Vector2i(ARENA_COLS - 2, 2),
Vector2i(2, ARENA_ROWS - 2), Vector2i(ARENA_COLS - 2, ARENA_ROWS - 2) Vector2i(2, ARENA_ROWS - 2), Vector2i(ARENA_COLS - 2, ARENA_ROWS - 2)
] ]
for i in range(count): for i in range(count):
points.append(corners[i % corners.size()]) points.append(corners[i % corners.size()])
return points return points
func get_arena_positions() -> Dictionary: func get_arena_positions() -> Dictionary:
return {"columns": ARENA_COLS, "rows": ARENA_ROWS} return {"columns": ARENA_COLS, "rows": ARENA_ROWS}
func get_arena_bounds() -> Dictionary: func get_arena_bounds() -> Dictionary:
return {"min_x": -1.0, "max_x": float(ARENA_COLS), "min_z": -1.0, "max_z": float(ARENA_ROWS)} return {"min_x": -1.0, "max_x": float(ARENA_COLS), "min_z": -1.0, "max_z": float(ARENA_ROWS)}
# ── Sticky (pure wall collision) ── # ── Sticky (pure wall collision) ──
func is_sticky_cell(pos: Vector2i) -> bool: func is_sticky_cell(pos: Vector2i) -> bool:
return sticky_cells.has(pos) return sticky_cells.has(pos)
func _is_npc_zone(pos: Vector2i) -> bool: func _is_npc_zone(pos: Vector2i) -> bool:
var cx = NPC_CENTER.x var cx = NPC_CENTER.x
var cy = NPC_CENTER.y var cy = NPC_CENTER.y
return pos.x >= cx and pos.x < cx + NPC_CELLS and pos.y >= cy and pos.y < cy + NPC_CELLS return pos.x >= cx and pos.x < cx + NPC_CELLS and pos.y >= cy and pos.y < cy + NPC_CELLS
# ── Scoring ── # ── Scoring ──
func _add_score(pid: int, points: int) -> void: func _add_score(pid: int, points: int) -> void:
player_score[pid] = player_score.get(pid, 0) + points player_score[pid] = player_score.get(pid, 0) + points
if main_scene and main_scene.has_method("add_points"): if main_scene and main_scene.has_method("add_points"):
main_scene.add_points(pid, points) main_scene.add_points(pid, points)
# ── Helpers ── # ── Helpers ──
func _get_player_ids() -> Array: func _get_player_ids() -> Array:
if main_scene and main_scene.has_method("get_player_ids"): if main_scene and main_scene.has_method("get_player_ids"):
return main_scene.get_player_ids() return main_scene.get_player_ids()
return [] return []
func candy_survival_round_duration() -> int: func candy_survival_round_duration() -> int:
return 180 return 180
func get_score(pid: int) -> int: func get_score(pid: int) -> int:
return player_score.get(pid, 0) return player_score.get(pid, 0)
# ── RPCs (stubs — HUD client handles visuals) ── # ── RPCs (stubs — HUD client handles visuals) ──
@rpc("authority", "call_remote", "unreliable") @rpc("authority", "call_remote", "unreliable")
func sync_candy_badge(pid: int, count: int, mult: float, face_match: bool) -> void: func sync_candy_badge(pid: int, count: int, mult: float, face_match: bool) -> void:
if pid == multiplayer.get_unique_id(): if not active:
if hud_stack_badge: activate_client_side()
hud_stack_badge.text = "Stack: %d (x%.1f)" % [count, mult] if pid == multiplayer.get_unique_id():
if hud_delivery_indicator: if hud_stack_badge:
if count > 0: hud_stack_badge.text = "Stack: %d (x%.1f)" % [count, mult]
if face_match: if hud_delivery_indicator:
hud_delivery_indicator.text = "READY TO DELIVER!" if count > 0:
hud_delivery_indicator.add_theme_color_override("font_color", Color(0.4, 1.0, 0.4)) if face_match:
else: hud_delivery_indicator.text = "READY TO DELIVER!"
hud_delivery_indicator.text = "Waiting for match..." hud_delivery_indicator.add_theme_color_override("font_color", Color(0.4, 1.0, 0.4))
hud_delivery_indicator.add_theme_color_override("font_color", Color(1.0, 0.4, 0.4)) else:
else: hud_delivery_indicator.text = "Waiting for match..."
hud_delivery_indicator.text = "" hud_delivery_indicator.add_theme_color_override("font_color", Color(1.0, 0.4, 0.4))
else:
hud_delivery_indicator.text = ""
@rpc("authority", "call_remote", "unreliable") @rpc("authority", "call_remote", "unreliable")
func sync_knock_result(attacker: int, target: int, candies: int) -> void: func sync_knock_result(attacker: int, target: int, candies: int) -> void:
var local = multiplayer.get_unique_id() if not active:
if attacker == local or target == local: activate_client_side()
if hud_knock_label and player_knocks.has(local): var local = multiplayer.get_unique_id()
hud_knock_label.text = "🥊 Knocks: %d" % player_knocks[local] if attacker == local or target == local:
if hud_knock_label and player_knocks.has(local):
hud_knock_label.text = "🥊 Knocks: %d" % player_knocks[local]
@rpc("authority", "call_remote", "unreliable") @rpc("authority", "call_remote", "unreliable")
func sync_ghost_active(pid: int, active_on: bool) -> void: func sync_ghost_active(pid: int, active_on: bool) -> void:
if pid == multiplayer.get_unique_id(): if not active:
if hud_ghost_label and player_ghosts.has(pid): activate_client_side()
hud_ghost_label.text = "👻 Ghosts: %d" % player_ghosts[pid] if pid == multiplayer.get_unique_id():
if hud_ghost_label and player_ghosts.has(pid):
hud_ghost_label.text = "👻 Ghosts: %d" % player_ghosts[pid]
+1 -1
View File
@@ -122,7 +122,7 @@ func simple_move_to(grid_position: Vector2i) -> bool:
return false return false
# Candy Survival explicit wall overrides (since we visually removed the wall blocks) # Candy Survival explicit wall overrides (since we visually removed the wall blocks)
if gm and gm.is_active: if gm and gm.active:
if gm._is_npc_zone(grid_position): if gm._is_npc_zone(grid_position):
print("[Move] Failed: Blocked by Candy Survival NPC center at %s" % grid_position) print("[Move] Failed: Blocked by Candy Survival NPC center at %s" % grid_position)
return false return false
+1 -1
View File
@@ -558,7 +558,7 @@ func spawn_powerups_around(center: Vector2i, force_powerups: bool = true, only_c
item_id = rng.randi_range(7, 10) item_id = rng.randi_range(7, 10)
else: else:
# 20% Chance for PowerUp # 20% Chance for PowerUp
item_id = rng.randi_range(11, 14) item_id = rng.randi_range(11, 14)
var cell = Vector3i(pos.x, 1, pos.y) var cell = Vector3i(pos.x, 1, pos.y)