Compare commits
3
Commits
gui
...
v.2.43-rollback
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
968808df6d | ||
|
|
472c49944b | ||
|
|
5c789cb37f |
+19
-19
@@ -3,7 +3,7 @@ name: Release
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- "v*"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
@@ -20,29 +20,29 @@ jobs:
|
|||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
run: |
|
run: |
|
||||||
git config --global credential.helper store
|
git config --global credential.helper store
|
||||||
echo "http://god:${{ secrets.TEKTON_RELEASE_TOKEN }}@52.74.133.55:3000" > ~/.git-credentials
|
echo "http://god:${{ secrets.TEKTON_RELEASE_TOKEN }}@git.klud.top" > ~/.git-credentials
|
||||||
git clone http://52.74.133.55:3000/danchie/tekton.git .
|
git clone http://git.klud.top/danchie/tekton.git .
|
||||||
git checkout $TAG_NAME
|
git checkout $TAG_NAME
|
||||||
|
|
||||||
- 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
|
||||||
@@ -85,7 +85,7 @@ jobs:
|
|||||||
- name: Create Gitea Release
|
- name: Create Gitea Release
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
API="http://52.74.133.55:3000/api/v1/repos/danchie/tekton/releases"
|
API="http://git.klud.top/api/v1/repos/danchie/tekton/releases"
|
||||||
TAG="$TAG_NAME"
|
TAG="$TAG_NAME"
|
||||||
echo "Checking existing release for $TAG..."
|
echo "Checking existing release for $TAG..."
|
||||||
RELEASE_JSON=$(curl -s -H "Authorization: token $GITEA_TOKEN" "$API/tags/$TAG" 2>/dev/null || echo "")
|
RELEASE_JSON=$(curl -s -H "Authorization: token $GITEA_TOKEN" "$API/tags/$TAG" 2>/dev/null || echo "")
|
||||||
@@ -116,7 +116,7 @@ jobs:
|
|||||||
-H "Authorization: token $GITEA_TOKEN" \
|
-H "Authorization: token $GITEA_TOKEN" \
|
||||||
-H "Content-Type: multipart/form-data" \
|
-H "Content-Type: multipart/form-data" \
|
||||||
-F "attachment=@build/tekton_armageddon_windows_${TAG_NAME}.zip" \
|
-F "attachment=@build/tekton_armageddon_windows_${TAG_NAME}.zip" \
|
||||||
"http://52.74.133.55:3000/api/v1/repos/danchie/tekton/releases/$RELEASE_ID/assets"
|
"http://git.klud.top/api/v1/repos/danchie/tekton/releases/$RELEASE_ID/assets"
|
||||||
echo "Windows uploaded"
|
echo "Windows uploaded"
|
||||||
|
|
||||||
- name: Upload Linux asset
|
- name: Upload Linux asset
|
||||||
@@ -126,7 +126,7 @@ jobs:
|
|||||||
-H "Authorization: token $GITEA_TOKEN" \
|
-H "Authorization: token $GITEA_TOKEN" \
|
||||||
-H "Content-Type: multipart/form-data" \
|
-H "Content-Type: multipart/form-data" \
|
||||||
-F "attachment=@build/tekton_armageddon_linux_${TAG_NAME}.zip" \
|
-F "attachment=@build/tekton_armageddon_linux_${TAG_NAME}.zip" \
|
||||||
"http://52.74.133.55:3000/api/v1/repos/danchie/tekton/releases/$RELEASE_ID/assets"
|
"http://git.klud.top/api/v1/repos/danchie/tekton/releases/$RELEASE_ID/assets"
|
||||||
echo "Linux uploaded"
|
echo "Linux uploaded"
|
||||||
|
|
||||||
- name: Upload macOS asset
|
- name: Upload macOS asset
|
||||||
@@ -137,7 +137,7 @@ jobs:
|
|||||||
-H "Authorization: token $GITEA_TOKEN" \
|
-H "Authorization: token $GITEA_TOKEN" \
|
||||||
-H "Content-Type: multipart/form-data" \
|
-H "Content-Type: multipart/form-data" \
|
||||||
-F "attachment=@build/tekton_armageddon_macos_${TAG_NAME}.zip" \
|
-F "attachment=@build/tekton_armageddon_macos_${TAG_NAME}.zip" \
|
||||||
"http://52.74.133.55:3000/api/v1/repos/danchie/tekton/releases/$RELEASE_ID/assets"
|
"http://git.klud.top/api/v1/repos/danchie/tekton/releases/$RELEASE_ID/assets"
|
||||||
echo "macOS uploaded"
|
echo "macOS uploaded"
|
||||||
else
|
else
|
||||||
echo "macOS asset not built, skipping"
|
echo "macOS asset not built, skipping"
|
||||||
@@ -150,5 +150,5 @@ jobs:
|
|||||||
-H "Authorization: token $GITEA_TOKEN" \
|
-H "Authorization: token $GITEA_TOKEN" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"draft":false}' \
|
-d '{"draft":false}' \
|
||||||
"http://52.74.133.55:3000/api/v1/repos/danchie/tekton/releases/$RELEASE_ID"
|
"http://git.klud.top/api/v1/repos/danchie/tekton/releases/$RELEASE_ID"
|
||||||
echo "Published: https://git.klud.top/danchie/tekton/releases/tag/$TAG_NAME"
|
echo "Published: https://git.klud.top/danchie/tekton/releases/tag/$TAG_NAME"
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
version:
|
version:
|
||||||
description: 'Patch version (e.g., 2.4.2)'
|
description: "Patch version (e.g., 2.4.4)"
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
notes:
|
notes:
|
||||||
description: 'Release notes'
|
description: "Release notes"
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
@@ -22,20 +22,20 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITEA_TOKEN: ${{ secrets.TEKTON_RELEASE_TOKEN }}
|
GITEA_TOKEN: ${{ secrets.TEKTON_RELEASE_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
git clone --depth 1 http://god:$GITEA_TOKEN@52.74.133.55:3000/danchie/tekton.git .
|
git clone --depth 1 http://god:$GITEA_TOKEN@git.klud.top/danchie/tekton.git .
|
||||||
git config user.name "god"
|
git config user.name "god"
|
||||||
git config user.email "god@noreply.git.klud.top"
|
git config user.email "god@noreply.git.klud.top"
|
||||||
|
|
||||||
- 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
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ jobs:
|
|||||||
git init
|
git init
|
||||||
git config user.name "god"
|
git config user.name "god"
|
||||||
git config user.email "god@noreply.git.klud.top"
|
git config user.email "god@noreply.git.klud.top"
|
||||||
git remote add origin http://god:$GITEA_TOKEN@52.74.133.55:3000/danchie/tekton.git
|
git remote add origin http://god:$GITEA_TOKEN@git.klud.top/danchie/tekton.git
|
||||||
git checkout -b patches
|
git checkout -b patches
|
||||||
git add .
|
git add .
|
||||||
git commit -m "patch ${{ github.event.inputs.version }}"
|
git commit -m "patch ${{ github.event.inputs.version }}"
|
||||||
|
|||||||
+1
-1
@@ -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
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user