594 lines
18 KiB
GDScript
594 lines
18 KiB
GDScript
extends Node
|
|
class_name MektonBullsManager
|
|
|
|
class Blueprint3x3 extends RefCounted:
|
|
var anchor: Vector2i
|
|
var color: int
|
|
var cells: Array[Vector2i] = []
|
|
var progress: int = 0
|
|
|
|
signal phase_changed(phase_index: int)
|
|
signal player_eliminated(player_id: int)
|
|
|
|
# Nodes
|
|
var main_scene: Node
|
|
var gridmap: Node
|
|
|
|
# Phase State
|
|
var current_phase: int = 1
|
|
var arena_size: Vector2i = Vector2i(20, 20)
|
|
|
|
var round_duration: float = 120.0
|
|
var phase_interval: float = 30.0
|
|
var round_timer: float = 120.0
|
|
var phase_timer: float = 30.0
|
|
|
|
signal time_remaining_changed(remaining: float)
|
|
|
|
var bull_node: Node3D = null
|
|
const BULL_SCENE = preload("res://scenes/npcs/mekton_bull.tscn")
|
|
|
|
enum CellState {
|
|
SAFE,
|
|
WATER,
|
|
BLOCKED
|
|
}
|
|
var arena_cells: Dictionary = {}
|
|
|
|
var is_active: bool = false
|
|
var flood_cooldown: float = 0.0
|
|
|
|
|
|
var player_blueprints: Dictionary = {} # { player_id: Blueprint3x3 }
|
|
var player_powers: Dictionary = {} # { player_id: { "FREEZE": 0, "KNOCK": 0 } }
|
|
|
|
var player_cooldowns: Dictionary = {} # { player_id: float }
|
|
enum PowerType { FREEZE, KNOCK }
|
|
|
|
# Placement Tracking
|
|
var player_placement: Dictionary = {} # { pid: placement_rank } 1=first out
|
|
var elimination_order: Array = [] # List of pids
|
|
|
|
var candy_tick_timer: float = 0.0
|
|
const GOAL_COLORS = [7, 8, 9, 10]
|
|
|
|
|
|
const TILE_WALKABLE: int = 0
|
|
const TILE_WATER: int = 24 # Water tile
|
|
const TILE_OBSTACLE: int = 4 # Wall/obstacle
|
|
|
|
func initialize(main: Node, grid: Node) -> void:
|
|
main_scene = main
|
|
gridmap = grid
|
|
|
|
|
|
func start_game_mode() -> void:
|
|
print("[MektonBulls] Starting Mekton Bulls game mode...")
|
|
|
|
round_duration = float(LobbyManager.mekton_bulls_round_duration)
|
|
phase_interval = float(LobbyManager.mekton_bulls_phase_interval)
|
|
round_timer = round_duration
|
|
phase_timer = phase_interval
|
|
|
|
is_active = true
|
|
_setup_arena()
|
|
|
|
if multiplayer.has_multiplayer_peer() and multiplayer.is_server():
|
|
_spawn_bull()
|
|
_assign_initial_blueprints()
|
|
|
|
|
|
func _ready():
|
|
player_eliminated.connect(_on_player_eliminated)
|
|
|
|
func _setup_arena() -> void:
|
|
if not gridmap:
|
|
gridmap = get_parent().get_node_or_null("EnhancedGridMap")
|
|
if not gridmap:
|
|
gridmap = get_node_or_null("/root/Main/EnhancedGridMap")
|
|
if not gridmap:
|
|
push_error("[MektonBulls] No EnhancedGridMap found!")
|
|
return
|
|
|
|
print("[MektonBulls] Setting up Phase 1 Arena...")
|
|
|
|
if multiplayer.has_multiplayer_peer() and multiplayer.is_server():
|
|
rpc("sync_arena_setup")
|
|
_apply_arena_setup()
|
|
|
|
@rpc("authority", "call_remote", "reliable")
|
|
func sync_arena_setup() -> void:
|
|
_apply_arena_setup()
|
|
|
|
func _apply_arena_setup() -> void:
|
|
if not gridmap:
|
|
gridmap = get_parent().get_node_or_null("EnhancedGridMap")
|
|
if not gridmap: return
|
|
|
|
current_phase = 1
|
|
arena_size = arena_size_for_phase(current_phase)
|
|
|
|
gridmap.set("columns", 20)
|
|
gridmap.set("rows", 20)
|
|
gridmap.clear()
|
|
|
|
# Initial build 20x20
|
|
for x in range(20):
|
|
for z in range(20):
|
|
var pos = Vector2i(x, z)
|
|
|
|
if _is_boundary(pos):
|
|
# Perimeter
|
|
gridmap.set_cell_item(Vector3i(x, 0, z), TILE_WALKABLE)
|
|
gridmap.set_cell_item(Vector3i(x, 1, z), -1)
|
|
arena_cells[pos] = CellState.SAFE
|
|
else:
|
|
# Walkable floor
|
|
gridmap.set_cell_item(Vector3i(x, 0, z), TILE_WALKABLE)
|
|
gridmap.set_cell_item(Vector3i(x, 1, z), -1)
|
|
arena_cells[pos] = CellState.SAFE
|
|
|
|
gridmap.set_cell_item(Vector3i(x, 2, z), -1)
|
|
|
|
gridmap.diagonal_movement = true
|
|
gridmap.update_grid_data()
|
|
gridmap.initialize_astar()
|
|
|
|
_reposition_npc()
|
|
_validate_blueprint_after_shrink()
|
|
|
|
|
|
func arena_size_for_phase(phase: int) -> Vector2i:
|
|
match phase:
|
|
1: return Vector2i(20, 20)
|
|
2: return Vector2i(19, 19)
|
|
3: return Vector2i(18, 18)
|
|
4: return Vector2i(17, 17)
|
|
_: return Vector2i(17, 17) # Final phase
|
|
|
|
func _is_boundary(pos: Vector2i) -> bool:
|
|
var bounds = arena_size_for_phase(current_phase)
|
|
# Grid shrinks symmetrically towards (0,0) by bounds.
|
|
return pos.x == 0 or pos.x == bounds.x - 1 or pos.y == 0 or pos.y == bounds.y - 1
|
|
|
|
func _shrink_arena() -> void:
|
|
if current_phase >= 4:
|
|
return
|
|
|
|
current_phase += 1
|
|
var new_bounds = arena_size_for_phase(current_phase)
|
|
var old_bounds = arena_size_for_phase(current_phase - 1)
|
|
|
|
print("[MektonBulls] Shrinking arena to Phase %d (%dx%d)" % [current_phase, new_bounds.x, new_bounds.y])
|
|
|
|
# Apply locally first
|
|
_apply_ring_shrink(old_bounds, new_bounds)
|
|
|
|
if multiplayer.has_multiplayer_peer() and multiplayer.is_server():
|
|
rpc("sync_shrink_arena", current_phase)
|
|
|
|
emit_signal("phase_changed", current_phase)
|
|
|
|
@rpc("authority", "call_remote", "reliable")
|
|
func sync_shrink_arena(new_phase: int) -> void:
|
|
var old_bounds = arena_size_for_phase(current_phase)
|
|
current_phase = new_phase
|
|
var new_bounds = arena_size_for_phase(current_phase)
|
|
_apply_ring_shrink(old_bounds, new_bounds)
|
|
emit_signal("phase_changed", current_phase)
|
|
|
|
func _apply_ring_shrink(old_bounds: Vector2i, new_bounds: Vector2i) -> void:
|
|
for x in range(20):
|
|
for z in range(20):
|
|
var pos = Vector2i(x, z)
|
|
|
|
if x >= new_bounds.x or z >= new_bounds.y:
|
|
# It is now outside the new bounds -> WATER
|
|
gridmap.set_cell_item(Vector3i(x, 0, z), TILE_WATER)
|
|
gridmap.set_cell_item(Vector3i(x, 1, z), -1)
|
|
arena_cells[pos] = CellState.WATER
|
|
elif _is_boundary(pos):
|
|
# New boundary -> No wall, just the edge of SAFE
|
|
gridmap.set_cell_item(Vector3i(x, 1, z), -1)
|
|
arena_cells[pos] = CellState.SAFE
|
|
|
|
gridmap.update_grid_data()
|
|
gridmap.initialize_astar()
|
|
|
|
_reposition_npc()
|
|
_validate_blueprint_after_shrink()
|
|
|
|
|
|
func _reposition_npc() -> void:
|
|
# Reposition Mekton Bull to the center of the current bounds
|
|
if not bull_node:
|
|
return
|
|
|
|
var bounds = arena_size_for_phase(current_phase)
|
|
# Center in world units
|
|
var cx = (bounds.x / 2.0) * gridmap.cell_size.x
|
|
var cz = (bounds.y / 2.0) * gridmap.cell_size.z
|
|
|
|
bull_node.position = Vector3(cx, 0, cz)
|
|
|
|
func get_spawn_points(player_count: int) -> Array[Vector2i]:
|
|
var spawns: Array[Vector2i] = []
|
|
var bounds = arena_size_for_phase(current_phase)
|
|
# 4 players: inner corners
|
|
spawns.append(Vector2i(1, 1))
|
|
if bounds.x > 2: spawns.append(Vector2i(bounds.x - 2, 1))
|
|
if bounds.y > 2: spawns.append(Vector2i(1, bounds.y - 2))
|
|
if bounds.x > 2 and bounds.y > 2: spawns.append(Vector2i(bounds.x - 2, bounds.y - 2))
|
|
|
|
if player_count > 4:
|
|
if bounds.x > 4: spawns.append(Vector2i(bounds.x / 2, 1))
|
|
if bounds.x > 4 and bounds.y > 2: spawns.append(Vector2i(bounds.x / 2, bounds.y - 2))
|
|
if player_count > 6:
|
|
if bounds.y > 4: spawns.append(Vector2i(1, bounds.y / 2))
|
|
if bounds.x > 2 and bounds.y > 4: spawns.append(Vector2i(bounds.x - 2, bounds.y / 2))
|
|
|
|
return spawns.slice(0, player_count)
|
|
|
|
func _spawn_bull() -> void:
|
|
if bull_node == null:
|
|
bull_node = BULL_SCENE.instantiate()
|
|
bull_node.name = "MektonBull"
|
|
# Use multiplayer spawner if appropriate, else just add child
|
|
main_scene.add_child(bull_node)
|
|
|
|
var bounds = arena_size_for_phase(current_phase)
|
|
var cx = (bounds.x / 2.0) * gridmap.cell_size.x
|
|
var cz = (bounds.y / 2.0) * gridmap.cell_size.z
|
|
var start_pos = Vector3(cx, 0, cz)
|
|
|
|
if bull_node.has_method("initialize"):
|
|
bull_node.initialize(self, gridmap, start_pos)
|
|
|
|
|
|
func _process(delta: float) -> void:
|
|
if not multiplayer.has_multiplayer_peer() or multiplayer.multiplayer_peer == null: return
|
|
if not is_active: return
|
|
|
|
if multiplayer.is_server():
|
|
round_timer -= delta
|
|
time_remaining_changed.emit(round_timer)
|
|
rpc("sync_time_remaining", round_timer)
|
|
|
|
if round_timer <= 0:
|
|
_on_round_time_expired()
|
|
|
|
phase_timer -= delta
|
|
if phase_timer <= 0:
|
|
phase_timer = phase_interval
|
|
_shrink_arena()
|
|
|
|
if flood_cooldown > 0:
|
|
|
|
flood_cooldown -= delta
|
|
|
|
if multiplayer.is_server():
|
|
if flood_cooldown <= 0 and bull_node:
|
|
var bull_pos_3d = gridmap.local_to_map(bull_node.position)
|
|
var bull_pos_2d = Vector2i(bull_pos_3d.x, bull_pos_3d.z)
|
|
if _is_boundary(bull_pos_2d):
|
|
_trigger_water_flood()
|
|
|
|
|
|
candy_tick_timer -= delta
|
|
if candy_tick_timer <= 0:
|
|
candy_tick_timer = 0.1
|
|
_process_candy_tick()
|
|
|
|
for pid in player_cooldowns.keys():
|
|
if player_cooldowns[pid] > 0:
|
|
player_cooldowns[pid] -= delta
|
|
|
|
|
|
|
|
|
|
@rpc("authority", "call_local", "reliable")
|
|
func sync_time_remaining(time: float) -> void:
|
|
round_timer = time
|
|
time_remaining_changed.emit(round_timer)
|
|
|
|
func _on_round_time_expired() -> void:
|
|
is_active = false
|
|
var players = get_tree().get_nodes_in_group("Players")
|
|
for p in players:
|
|
if p.is_in_group("Players"):
|
|
var pid = p.name.to_int()
|
|
if not elimination_order.has(pid):
|
|
elimination_order.append(pid)
|
|
player_placement[pid] = elimination_order.size()
|
|
|
|
_end_round()
|
|
|
|
func _trigger_water_flood() -> void:
|
|
flood_cooldown = 3.0
|
|
print("[MektonBulls] Bull is on boundary! Flooding outer ring!")
|
|
|
|
# Network sync
|
|
rpc("sync_water_flood")
|
|
_apply_water_flood()
|
|
|
|
@rpc("authority", "call_local", "reliable")
|
|
func sync_water_flood() -> void:
|
|
_apply_water_flood()
|
|
|
|
func _apply_water_flood() -> void:
|
|
# 1. Eliminate any player whose cell is currently _is_boundary
|
|
# (which is the outermost ring of the current arena_size)
|
|
var players = get_tree().get_nodes_in_group("Players")
|
|
var bounds = arena_size_for_phase(current_phase)
|
|
|
|
for p in players:
|
|
if p.is_in_group("Players") and p.has_method("is_eliminated") and not p.is_eliminated():
|
|
var p_cell_3d = gridmap.local_to_map(p.position)
|
|
var p_cell_2d = Vector2i(p_cell_3d.x, p_cell_3d.z)
|
|
if _is_boundary(p_cell_2d):
|
|
if multiplayer.is_server():
|
|
if p.has_method("eliminate"):
|
|
p.eliminate()
|
|
else:
|
|
player_eliminated.emit(p.name.to_int())
|
|
|
|
# 2. Play VFX / SFX (placeholder print if no actual scene yet)
|
|
if main_scene and main_scene.get("vfx_manager") and main_scene.vfx_manager.has_method("play_splash"):
|
|
var cx = (bounds.x / 2.0) * gridmap.cell_size.x
|
|
var cz = (bounds.y / 2.0) * gridmap.cell_size.z
|
|
main_scene.vfx_manager.play_splash(Vector3(cx, 0, cz))
|
|
|
|
if has_node("/root/SfxManager"):
|
|
get_node("/root/SfxManager").play_rpc("water_flood")
|
|
else:
|
|
print("[MektonBulls] WHOOSH! Water flood VFX played.")
|
|
|
|
# 3. Set those cells to TILE_WATER
|
|
for x in range(20):
|
|
for z in range(20):
|
|
var pos = Vector2i(x, z)
|
|
if _is_boundary(pos):
|
|
gridmap.set_cell_item(Vector3i(x, 0, z), TILE_WATER)
|
|
gridmap.set_cell_item(Vector3i(x, 1, z), -1)
|
|
arena_cells[pos] = CellState.WATER
|
|
|
|
gridmap.update_grid_data()
|
|
|
|
func _assign_initial_blueprints() -> void:
|
|
var players = get_tree().get_nodes_in_group("Players")
|
|
for p in players:
|
|
if p.is_in_group("Players"):
|
|
_reroll_blueprint(p.name.to_int())
|
|
|
|
func _reroll_blueprint(player_id: int) -> void:
|
|
var bp = Blueprint3x3.new()
|
|
bp.color = GOAL_COLORS[randi() % GOAL_COLORS.size()]
|
|
bp.anchor = _get_valid_3x3_anchor()
|
|
|
|
# Generate 3x3 cells
|
|
for dx in range(3):
|
|
for dz in range(3):
|
|
var cpos = Vector2i(bp.anchor.x + dx, bp.anchor.y + dz)
|
|
bp.cells.append(cpos)
|
|
# Paint it
|
|
gridmap.set_cell_item(Vector3i(cpos.x, 0, cpos.y), bp.color)
|
|
|
|
player_blueprints[player_id] = bp
|
|
gridmap.update_grid_data()
|
|
rpc("sync_painted_cells", bp.cells, bp.color)
|
|
|
|
@rpc("authority", "call_local", "reliable")
|
|
func sync_painted_cells(cells: Array, color: int) -> void:
|
|
for c in cells:
|
|
gridmap.set_cell_item(Vector3i(c.x, 0, c.y), color)
|
|
|
|
func _get_valid_3x3_anchor() -> Vector2i:
|
|
var bounds = arena_size_for_phase(current_phase)
|
|
# boundary is 0 and bounds-1
|
|
# inner is 1 to bounds-2
|
|
# 3x3 needs x to x+2 -> x+2 <= bounds-2 -> x <= bounds-4
|
|
var max_x = bounds.x - 4
|
|
var max_y = bounds.y - 4
|
|
|
|
if max_x < 1: max_x = 1
|
|
if max_y < 1: max_y = 1
|
|
|
|
return Vector2i(randi_range(1, max_x), randi_range(1, max_y))
|
|
|
|
func _process_candy_tick() -> void:
|
|
var players = get_tree().get_nodes_in_group("Players")
|
|
for p in players:
|
|
if not p.is_in_group("Players") or (p.has_method("is_eliminated") and p.is_eliminated()):
|
|
continue
|
|
|
|
var pid = p.name.to_int()
|
|
if not player_blueprints.has(pid):
|
|
continue
|
|
|
|
var bp: Blueprint3x3 = player_blueprints[pid]
|
|
var pos_3d = gridmap.local_to_map(p.position)
|
|
var pos = Vector2i(pos_3d.x, pos_3d.z)
|
|
|
|
if pos in bp.cells:
|
|
var item = gridmap.get_cell_item(Vector3i(pos.x, 0, pos.y))
|
|
if item == bp.color:
|
|
# Pickup!
|
|
bp.progress += 1
|
|
# Remove color local
|
|
gridmap.set_cell_item(Vector3i(pos.x, 0, pos.y), TILE_WALKABLE)
|
|
rpc("sync_painted_cells", [pos], TILE_WALKABLE)
|
|
|
|
if bp.progress >= 9:
|
|
_grant_power(pid)
|
|
_reroll_blueprint(pid)
|
|
|
|
|
|
func _grant_power(player_id: int) -> void:
|
|
print("[MektonBulls] Blueprint complete! Prompting power picker for ", player_id)
|
|
# Safely call rpc_id
|
|
if multiplayer.has_multiplayer_peer() and multiplayer.get_peers().has(player_id) or player_id == multiplayer.get_unique_id():
|
|
rpc_id(player_id, "prompt_power_picker")
|
|
|
|
@rpc("authority", "call_remote", "reliable")
|
|
func prompt_power_picker() -> void:
|
|
if hud_node and hud_node.has_method("show_power_picker"):
|
|
hud_node.show_power_picker()
|
|
|
|
var hud_node: Node = null
|
|
|
|
@rpc("authority", "call_local", "reliable")
|
|
func sync_score_completed(placements: Dictionary) -> void:
|
|
if hud_node and hud_node.has_method("show_placement"):
|
|
hud_node.show_placement(placements)
|
|
|
|
@rpc("any_peer", "call_local", "reliable")
|
|
func try_pick_power(power_type_str: String) -> void:
|
|
var sender = multiplayer.get_remote_sender_id()
|
|
if not multiplayer.is_server(): return
|
|
|
|
if not player_powers.has(sender):
|
|
player_powers[sender] = { "FREEZE": 0, "KNOCK": 0 }
|
|
|
|
if power_type_str == "FREEZE" or power_type_str == "KNOCK":
|
|
player_powers[sender][power_type_str] += 1
|
|
rpc("sync_player_powers", sender, player_powers[sender])
|
|
|
|
@rpc("authority", "call_local", "reliable")
|
|
func sync_player_powers(pid: int, powers: Dictionary) -> void:
|
|
player_powers[pid] = powers
|
|
|
|
@rpc("any_peer", "call_local", "reliable")
|
|
func try_use_freeze() -> void:
|
|
var sender = multiplayer.get_remote_sender_id()
|
|
if not multiplayer.is_server(): return
|
|
|
|
if player_cooldowns.get(sender, 0.0) > 0: return
|
|
if not player_powers.has(sender) or player_powers[sender]["FREEZE"] <= 0: return
|
|
|
|
player_powers[sender]["FREEZE"] -= 1
|
|
player_cooldowns[sender] = 1.0
|
|
rpc("sync_player_powers", sender, player_powers[sender])
|
|
|
|
if bull_node and bull_node.has_method("apply_slow"):
|
|
bull_node.apply_slow(3.0)
|
|
if has_node("/root/SfxManager"):
|
|
get_node("/root/SfxManager").play_rpc("freeze_burst")
|
|
|
|
@rpc("any_peer", "call_local", "reliable")
|
|
func try_use_knock(target_id: int, dir: Vector3) -> void:
|
|
var sender = multiplayer.get_remote_sender_id()
|
|
if not multiplayer.is_server(): return
|
|
|
|
if player_cooldowns.get(sender, 0.0) > 0: return
|
|
if not player_powers.has(sender) or player_powers[sender]["KNOCK"] <= 0: return
|
|
|
|
player_powers[sender]["KNOCK"] -= 1
|
|
player_cooldowns[sender] = 1.0
|
|
rpc("sync_player_powers", sender, player_powers[sender])
|
|
|
|
rpc("sync_apply_knock", target_id, dir)
|
|
|
|
|
|
@rpc("authority", "call_local", "reliable")
|
|
func sync_apply_knock(target_id: int, dir: Vector3) -> void:
|
|
var players = get_tree().get_nodes_in_group("Players")
|
|
for p in players:
|
|
if p.name == str(target_id):
|
|
# Translate position 1 cell in dir
|
|
var move_dist = gridmap.cell_size.x
|
|
# Normalize to 4 directions
|
|
var dx = 0
|
|
var dz = 0
|
|
if abs(dir.x) > abs(dir.z):
|
|
dx = sign(dir.x)
|
|
else:
|
|
dz = sign(dir.z)
|
|
|
|
p.position += Vector3(dx * move_dist, 0, dz * move_dist)
|
|
|
|
# Play a smack sound if available
|
|
if has_node("/root/SfxManager"):
|
|
get_node("/root/SfxManager").play_rpc("knock_burst")
|
|
elif main_scene and main_scene.get("audio_manager") and main_scene.audio_manager.has_method("play_sfx"):
|
|
main_scene.audio_manager.play_sfx("smack")
|
|
|
|
|
|
func _validate_blueprint_after_shrink() -> void:
|
|
if not multiplayer.is_server(): return
|
|
var bounds = arena_size_for_phase(current_phase)
|
|
|
|
for pid in player_blueprints.keys():
|
|
var bp: Blueprint3x3 = player_blueprints[pid]
|
|
var outside = false
|
|
for c in bp.cells:
|
|
if c.x <= 0 or c.x >= bounds.x - 1 or c.y <= 0 or c.y >= bounds.y - 1:
|
|
outside = true
|
|
break
|
|
|
|
if outside:
|
|
# Clear old ones
|
|
rpc("sync_painted_cells", bp.cells, TILE_WALKABLE)
|
|
_reroll_blueprint(pid)
|
|
|
|
func _on_player_eliminated(player_id: int) -> void:
|
|
if not elimination_order.has(player_id):
|
|
elimination_order.append(player_id)
|
|
print("[MektonBulls] Player %d eliminated. Rank: %d" % [player_id, elimination_order.size()])
|
|
# Placement rank: 1 is first out
|
|
player_placement[player_id] = elimination_order.size()
|
|
|
|
if multiplayer.is_server():
|
|
# Check if only 1 player remains
|
|
var players = get_tree().get_nodes_in_group("Players")
|
|
var alive_count = 0
|
|
var last_alive: int = -1
|
|
for p in players:
|
|
if p.is_in_group("Players"):
|
|
var pid = p.name.to_int()
|
|
if not elimination_order.has(pid):
|
|
alive_count += 1
|
|
last_alive = pid
|
|
|
|
if alive_count <= 1:
|
|
if last_alive != -1 and not elimination_order.has(last_alive):
|
|
elimination_order.append(last_alive)
|
|
player_placement[last_alive] = elimination_order.size()
|
|
is_active = false
|
|
_end_round()
|
|
|
|
func _end_round() -> void:
|
|
if not multiplayer.is_server(): return
|
|
print("[MektonBulls] Round ended. Computing placement scores...")
|
|
|
|
var total_players = elimination_order.size()
|
|
if total_players == 0: return
|
|
|
|
var min_pts = LobbyManager.mekton_bulls_min_points
|
|
var max_pts = LobbyManager.mekton_bulls_max_points
|
|
|
|
var scores = {}
|
|
for i in range(total_players):
|
|
var pid = elimination_order[i]
|
|
var rank = i + 1 # 1 = first out
|
|
|
|
var pts = min_pts
|
|
if total_players > 1:
|
|
var t = float(rank - 1) / float(total_players - 1)
|
|
pts = int(lerp(float(min_pts), float(max_pts), t))
|
|
|
|
scores[pid] = pts
|
|
print("[MektonBulls] Player %d finished rank %d -> %d pts" % [pid, rank, pts])
|
|
|
|
# In the real game, we'd sync this to the scores manager
|
|
# main_scene.rpc("sync_score_updated", scores) - wait, is there a direct scoreboard in Mekton Bulls?
|
|
# Typically GoalsCycleManager tracks scores, or main.gd
|
|
if main_scene and main_scene.get("goals_cycle_manager"):
|
|
for pid in scores.keys():
|
|
main_scene.goals_cycle_manager.add_score(pid, scores[pid])
|
|
|
|
rpc("sync_score_completed", player_placement)
|
|
|
|
# End the goal cycle match if it hasn't already
|
|
if main_scene and main_scene.get("goals_cycle_manager") and main_scene.goals_cycle_manager.is_active:
|
|
main_scene.goals_cycle_manager.end_match()
|