This commit is contained in:
2026-02-12 06:53:26 +08:00
parent c37b317eb5
commit 5ec559c4c9
5 changed files with 254 additions and 84 deletions
+10
View File
@@ -120,3 +120,13 @@ use_powerup={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":70,"key_label":0,"unicode":102,"location":0,"echo":false,"script":null) "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":70,"key_label":0,"unicode":102,"location":0,"echo":false,"script":null)
] ]
} }
action_grab_tekton={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":71,"physical_keycode":0,"key_label":0,"unicode":103,"location":0,"echo":false,"script":null)
]
}
action_knock_tekton={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":66,"physical_keycode":0,"key_label":0,"unicode":98,"location":0,"echo":false,"script":null)
]
}
+127 -22
View File
@@ -33,6 +33,15 @@ var is_frozen: bool = false
var is_invisible: bool = false var is_invisible: bool = false
var original_movement_range: int = 1 var original_movement_range: int = 1
# Tekton Interaction
var carried_tekton: Node3D = null
var is_carrying_tekton: bool:
get: return is_carried_tekton
set(value):
is_carried_tekton = value
# Visual/Logic side effects if any
var is_carried_tekton: bool = false
var is_attack_mode: bool = false: var is_attack_mode: bool = false:
set(value): set(value):
if is_attack_mode == value: if is_attack_mode == value:
@@ -41,9 +50,9 @@ var is_attack_mode: bool = false:
is_attack_mode = value is_attack_mode = value
# Visual feedback for attack mode (Red Tint) # Visual feedback for attack mode (Red Tint)
if is_attack_mode: if is_attack_mode:
_apply_tint_recursive(self, Color(1.0, 0.5, 0.5)) _apply_tint_recursive(self , Color(1.0, 0.5, 0.5))
else: else:
_apply_tint_recursive(self, Color.WHITE) _apply_tint_recursive(self , Color.WHITE)
# Sync to others if we are the authority # Sync to others if we are the authority
if is_multiplayer_authority(): if is_multiplayer_authority():
@@ -148,7 +157,7 @@ const AVAILABLE_CHARACTERS: Array[String] = ["Bob", "Masbro", "Gatot", "Oldpop"]
set(value): set(value):
is_my_turn = value is_my_turn = value
if is_my_turn and is_multiplayer_authority(): if is_my_turn and is_multiplayer_authority():
NotificationManager.send_message(self, NotificationManager.MESSAGES.TURN_START, NotificationManager.MessageType.NORMAL) NotificationManager.send_message(self , NotificationManager.MESSAGES.TURN_START, NotificationManager.MessageType.NORMAL)
@export var has_moved_this_turn = false @export var has_moved_this_turn = false
@@ -287,39 +296,39 @@ func _init_managers():
movement_manager = load("res://scripts/managers/player_movement_manager.gd").new() movement_manager = load("res://scripts/managers/player_movement_manager.gd").new()
movement_manager.name = "MovementManager" movement_manager.name = "MovementManager"
add_child(movement_manager) add_child(movement_manager)
movement_manager.initialize(self, enhanced_gridmap) movement_manager.initialize(self , enhanced_gridmap)
race_manager = load("res://scripts/managers/player_race_manager.gd").new() race_manager = load("res://scripts/managers/player_race_manager.gd").new()
race_manager.name = "RaceManager" race_manager.name = "RaceManager"
add_child(race_manager) add_child(race_manager)
race_manager.initialize(self, enhanced_gridmap) race_manager.initialize(self , enhanced_gridmap)
# Skip InputManager for bots # Skip InputManager for bots
if not (is_bot or is_in_group("Bots")): if not (is_bot or is_in_group("Bots")):
input_manager = load("res://scripts/managers/player_input_manager.gd").new() input_manager = load("res://scripts/managers/player_input_manager.gd").new()
input_manager.name = "InputManager" input_manager.name = "InputManager"
add_child(input_manager) add_child(input_manager)
input_manager.initialize(self, movement_manager, race_manager) input_manager.initialize(self , movement_manager, race_manager)
playerboard_manager = load("res://scripts/managers/playerboard_manager.gd").new() playerboard_manager = load("res://scripts/managers/playerboard_manager.gd").new()
playerboard_manager.name = "PlayerboardManager" playerboard_manager.name = "PlayerboardManager"
add_child(playerboard_manager) add_child(playerboard_manager)
playerboard_manager.initialize(self, enhanced_gridmap) playerboard_manager.initialize(self , enhanced_gridmap)
action_manager = load("res://scripts/managers/player_action_manager.gd").new() action_manager = load("res://scripts/managers/player_action_manager.gd").new()
action_manager.name = "ActionManager" action_manager.name = "ActionManager"
add_child(action_manager) add_child(action_manager)
action_manager.initialize(self, enhanced_gridmap) action_manager.initialize(self , enhanced_gridmap)
special_tiles_manager = load("res://scripts/managers/special_tiles_manager.gd").new() special_tiles_manager = load("res://scripts/managers/special_tiles_manager.gd").new()
special_tiles_manager.name = "SpecialTilesManager" special_tiles_manager.name = "SpecialTilesManager"
add_child(special_tiles_manager) add_child(special_tiles_manager)
special_tiles_manager.initialize(self, enhanced_gridmap) special_tiles_manager.initialize(self , enhanced_gridmap)
powerup_manager = load("res://scripts/managers/powerup_manager.gd").new() powerup_manager = load("res://scripts/managers/powerup_manager.gd").new()
powerup_manager.name = "PowerUpManager" powerup_manager.name = "PowerUpManager"
add_child(powerup_manager) add_child(powerup_manager)
powerup_manager.initialize(self, enhanced_gridmap) powerup_manager.initialize(self , enhanced_gridmap)
# ============================================================================= # =============================================================================
# Character Selection # Character Selection
@@ -689,7 +698,7 @@ func apply_stagger(duration: float = 1.5):
return # Already staggered return # Already staggered
is_frozen = true is_frozen = true
_apply_tint_recursive(self, Color.BLUE) # Visual feedback _apply_tint_recursive(self , Color.BLUE) # Visual feedback
# Set immunity (3 seconds as requested) # Set immunity (3 seconds as requested)
immunity_timer = 3.0 immunity_timer = 3.0
@@ -697,7 +706,7 @@ func apply_stagger(duration: float = 1.5):
print("Player %s staggered for %.1f seconds" % [name, duration]) print("Player %s staggered for %.1f seconds" % [name, duration])
if is_multiplayer_authority(): if is_multiplayer_authority():
NotificationManager.send_message(self, NotificationManager.MESSAGES.CRUSHED, NotificationManager.MessageType.WARNING) NotificationManager.send_message(self , NotificationManager.MESSAGES.CRUSHED, NotificationManager.MessageType.WARNING)
drop_random_item() drop_random_item()
# Grant "Smashed" Bonus (1 bar, max 2) # Grant "Smashed" Bonus (1 bar, max 2)
@@ -709,15 +718,15 @@ func apply_stagger(duration: float = 1.5):
is_frozen = false is_frozen = false
# If still immune, show immunity tint (Green?), otherwise White # If still immune, show immunity tint (Green?), otherwise White
if immunity_timer > 0: if immunity_timer > 0:
_apply_tint_recursive(self, Color(0.5, 1.0, 0.5)) # Light Green for immunity _apply_tint_recursive(self , Color(0.5, 1.0, 0.5)) # Light Green for immunity
else: else:
_apply_tint_recursive(self, Color.WHITE) # Remove tint _apply_tint_recursive(self , Color.WHITE) # Remove tint
@rpc("any_peer", "call_local") @rpc("any_peer", "call_local")
func apply_slow_effect(duration: float = 3.0): func apply_slow_effect(duration: float = 3.0):
# "area with blue like wall... Player who cross on that area will got slowed and freeze effect" # "area with blue like wall... Player who cross on that area will got slowed and freeze effect"
# Visual: Blue Tint # Visual: Blue Tint
_apply_tint_recursive(self, Color(0.6, 0.8, 1.0)) # Icy Blue _apply_tint_recursive(self , Color(0.6, 0.8, 1.0)) # Icy Blue
# Logic: Slow Movement speed # Logic: Slow Movement speed
if movement_manager: if movement_manager:
@@ -740,9 +749,9 @@ func apply_slow_effect(duration: float = 3.0):
movement_manager.set_speed_multiplier(1.0) movement_manager.set_speed_multiplier(1.0)
if immunity_timer > 0: if immunity_timer > 0:
_apply_tint_recursive(self, Color(0.5, 1.0, 0.5)) _apply_tint_recursive(self , Color(0.5, 1.0, 0.5))
else: else:
_apply_tint_recursive(self, Color.WHITE) _apply_tint_recursive(self , Color.WHITE)
func playerboard_is_empty() -> bool: func playerboard_is_empty() -> bool:
for item in playerboard: for item in playerboard:
@@ -779,7 +788,7 @@ func drop_random_item():
var cell = Vector3i(drop_pos.x, 0, drop_pos.y) var cell = Vector3i(drop_pos.x, 0, drop_pos.y)
rpc("sync_grid_item", cell.x, cell.y, cell.z, item_id) rpc("sync_grid_item", cell.x, cell.y, cell.z, item_id)
NotificationManager.send_message(self, NotificationManager.MESSAGES.DROPPED_ITEM, NotificationManager.MessageType.WARNING) NotificationManager.send_message(self , NotificationManager.MESSAGES.DROPPED_ITEM, NotificationManager.MessageType.WARNING)
print("Player %s dropped item %d at %s" % [name, item_id, drop_pos]) print("Player %s dropped item %d at %s" % [name, item_id, drop_pos])
@@ -805,7 +814,7 @@ func drop_all_tiles():
if dropped_count > 0: if dropped_count > 0:
rpc("sync_playerboard", playerboard) rpc("sync_playerboard", playerboard)
rpc("trigger_screen_shake", "targeted") rpc("trigger_screen_shake", "targeted")
NotificationManager.send_message(self, NotificationManager.MESSAGES.CRITICALLY_HIT, NotificationManager.MessageType.WARNING) NotificationManager.send_message(self , NotificationManager.MESSAGES.CRITICALLY_HIT, NotificationManager.MessageType.WARNING)
print("Player %s dropped %d tiles due to Super Push" % [name, dropped_count]) print("Player %s dropped %d tiles due to Super Push" % [name, dropped_count])
func _find_valid_drop_position() -> Vector2i: func _find_valid_drop_position() -> Vector2i:
@@ -865,7 +874,7 @@ func attempt_target_action(target_index: int):
if target_player == self and effect != 4: # 4 = INVISIBLE (Self) if target_player == self and effect != 4: # 4 = INVISIBLE (Self)
# Trying to target self with harmful effect? # Trying to target self with harmful effect?
NotificationManager.send_message(self, NotificationManager.MESSAGES.CANT_TARGET_SELF, NotificationManager.MessageType.WARNING) NotificationManager.send_message(self , NotificationManager.MESSAGES.CANT_TARGET_SELF, NotificationManager.MessageType.WARNING)
return return
# 3. Activate Effect # 3. Activate Effect
@@ -941,7 +950,7 @@ func _process(delta):
immunity_timer -= delta immunity_timer -= delta
if immunity_timer <= 0: if immunity_timer <= 0:
immunity_timer = 0 immunity_timer = 0
_apply_tint_recursive(self, Color.WHITE) # Remove immunity tint _apply_tint_recursive(self , Color.WHITE) # Remove immunity tint
@rpc("any_peer", "call_local") @rpc("any_peer", "call_local")
func ping_existence(): func ping_existence():
@@ -1179,13 +1188,16 @@ func start_movement_along_path(path: Array, clear_visual: bool = true):
for point in path: for point in path:
# Use global_position for consistency # Use global_position for consistency
tween.tween_property(self, "global_position", grid_to_world(Vector2i(point.x, point.y)), step_duration) tween.tween_property(self , "global_position", grid_to_world(Vector2i(point.x, point.y)), step_duration)
tween.tween_callback(func(): tween.tween_callback(func():
current_position = Vector2i(path[-1].x, path[-1].y) current_position = Vector2i(path[-1].x, path[-1].y)
is_player_moving = false is_player_moving = false
target_position = Vector2i(-1, -1) target_position = Vector2i(-1, -1)
if is_carrying_tekton and is_instance_valid(carried_tekton):
carried_tekton.current_position = current_position
# FORCE SNAP: Update target visual position to the perfect grid center # FORCE SNAP: Update target visual position to the perfect grid center
# This ensures that when interpolation resumes (in _process), it pulls to the correct spot # This ensures that when interpolation resumes (in _process), it pulls to the correct spot
target_visual_position = grid_to_world(current_position) target_visual_position = grid_to_world(current_position)
@@ -1797,3 +1809,96 @@ func set_spawn_position(pos: Vector2i):
func complete_race(final_position: int): func complete_race(final_position: int):
if race_manager: if race_manager:
race_manager.on_race_completed(final_position) race_manager.on_race_completed(final_position)
# =============================================================================
# Tekton Interaction Logic
# =============================================================================
func grab_tekton():
if not is_multiplayer_authority() or is_carrying_tekton or is_frozen:
return
# Find nearby Tekton
var tekton = _find_nearby_tekton()
if tekton:
if is_multiplayer_authority():
rpc("sync_grab_tekton", tekton.get_path())
@rpc("any_peer", "call_local", "reliable")
func sync_grab_tekton(tekton_path: NodePath):
var tekton = get_node_or_null(tekton_path)
if tekton:
carried_tekton = tekton
is_carrying_tekton = true
tekton.set_carried(true, self )
print("[Player %s] Grabbed Tekton %s" % [name, tekton.name])
func throw_tekton():
if not is_multiplayer_authority() or not is_carrying_tekton:
return
# Determine throw direction (where player is facing)
# For simplicity, we use the player's current rotation to find the target tile
var forward = - global_transform.basis.z.normalized()
var throw_dir = Vector2i(round(forward.x), round(forward.z))
if throw_dir == Vector2i.ZERO: throw_dir = Vector2i(1, 0) # Fallback
var target_pos = current_position + throw_dir
if is_multiplayer_authority():
rpc("sync_throw_tekton", target_pos)
@rpc("any_peer", "call_local", "reliable")
func sync_throw_tekton(target_pos: Vector2i):
if carried_tekton:
var tekton = carried_tekton
carried_tekton = null
is_carrying_tekton = false
tekton.set_carried(false)
# Move Tekton to target pos
tekton.current_position = target_pos
# Intensity 0.5 for throw (drops 50% tiles)
tekton.on_hit(self , 0.5)
print("[Player %s] Threw Tekton to %s" % [name, target_pos])
func knock_tekton():
if not is_multiplayer_authority() or is_frozen:
return
# Requirement: Full Powerup Bar
if not powerup_manager or not powerup_manager.can_use_special():
NotificationManager.send_message(self , "Need Full Boost to Knock!", NotificationManager.MessageType.WARNING)
return
var tekton = _find_nearby_tekton()
if tekton:
# Consume Boost
powerup_manager.consume_boost(100.0)
if is_multiplayer_authority():
rpc("sync_knock_tekton", tekton.get_path())
@rpc("any_peer", "call_local", "reliable")
func sync_knock_tekton(tekton_path: NodePath):
var tekton = get_node_or_null(tekton_path)
if tekton:
# Intensity 2.0 for knock (drops 200% tiles)
tekton.on_hit(self , 2.0)
print("[Player %s] Knocked Tekton %s" % [name, tekton.name])
# Visual feedback (Juice)
if is_multiplayer_authority():
rpc("trigger_screen_shake", "heavy")
func _find_nearby_tekton() -> Node3D:
var tektons = get_tree().get_nodes_in_group("Tektons")
for tekton in tektons:
if tekton.is_carried: continue
var dist = (tekton.current_position - current_position).length()
if dist <= 1.5: # Adjacent (1.0 or 1.41)
return tekton
return null
+7 -1
View File
@@ -47,7 +47,6 @@ func _process(delta):
movement_manager.simple_move_to(target_position) movement_manager.simple_move_to(target_position)
# Targeting Mode Preview # Targeting Mode Preview
var main = player.get_node_or_null("/root/Main") var main = player.get_node_or_null("/root/Main")
if main and main.ui_manager and main.ui_manager.current_action_state == main.ui_manager.ActionState.TARGETING: if main and main.ui_manager and main.ui_manager.current_action_state == main.ui_manager.ActionState.TARGETING:
@@ -112,6 +111,13 @@ func handle_unhandled_input(event):
else: else:
# Fallback if method missing # Fallback if method missing
player.powerup_manager.use_special_effect() player.powerup_manager.use_special_effect()
KEY_G:
if player.is_carrying_tekton:
player.throw_tekton()
else:
player.grab_tekton()
KEY_B:
player.knock_tekton()
# Handle spawn point selection if not yet selected # Handle spawn point selection if not yet selected
+9 -5
View File
@@ -7,15 +7,15 @@ const HOLO_TILES = [11, 12, 13, 14]
enum SpecialEffect { enum SpecialEffect {
FASTER_SPEED, # ID 11 FASTER_SPEED, # ID 11
AREA_FREEZE, # ID 12 AREA_FREEZE, # ID 12
BLOCK_FLOOR, # ID 13 BLOCK_FLOOR, # ID 13
INVISIBLE_MODE # ID 14 INVISIBLE_MODE # ID 14
} }
# Levels & Cooldowns # Levels & Cooldowns
var powerup_levels: Dictionary = {} # EffectEnum -> int (1 to 8) var powerup_levels: Dictionary = {} # EffectEnum -> int (1 to 8)
var powerup_cooldowns: Dictionary = {} # EffectEnum -> float (Time Remaining) var powerup_cooldowns: Dictionary = {} # EffectEnum -> float (Time Remaining)
var active_buffs: Dictionary = {} # EffectEnum -> float (Duration Running) var active_buffs: Dictionary = {} # EffectEnum -> float (Duration Running)
# Cooldown Constants (Level 1 / Level 8) # Cooldown Constants (Level 1 / Level 8)
const COOLDOWN_L1 = 15.0 const COOLDOWN_L1 = 15.0
@@ -204,6 +204,11 @@ func activate_effect(effect: int, target_player: Node3D = null):
print("PowerUp %s on cooldown." % SpecialEffect.keys()[effect]) print("PowerUp %s on cooldown." % SpecialEffect.keys()[effect])
return return
# Check Carrying Restriction
if player.get("is_carrying_tekton") and effect != SpecialEffect.FASTER_SPEED:
NotificationManager.send_message(player, "Cannot use this power while carrying a Tekton!", NotificationManager.MessageType.WARNING)
return
# Calculate Cooldown based on Level # Calculate Cooldown based on Level
var level = powerup_levels.get(effect, 1) var level = powerup_levels.get(effect, 1)
# Linear Interp: Lvl 1 = 15s, Lvl 8 = 5s # Linear Interp: Lvl 1 = 15s, Lvl 8 = 5s
@@ -406,7 +411,6 @@ func _execute_invisible_mode(target: Node3D):
NotificationManager.send_message(target, "Invisible Mode!", NotificationManager.MessageType.POWERUP) NotificationManager.send_message(target, "Invisible Mode!", NotificationManager.MessageType.POWERUP)
# ============================================================================= # =============================================================================
# Helper: Spawn Powerups (For Super Push) # Helper: Spawn Powerups (For Super Push)
# ============================================================================= # =============================================================================
+94 -49
View File
@@ -11,48 +11,60 @@ signal movement_finished
var enhanced_gridmap: Node var enhanced_gridmap: Node
var is_moving: bool = false var is_moving: bool = false
var is_carried: bool = false
var carrier: Node3D = null
var tween: Tween var tween: Tween
const SIDE_OFFSET = 0.35 # Distance from center
func initialize(start_pos: Vector2i, p_gridmap: Node): func initialize(start_pos: Vector2i, p_gridmap: Node):
current_position = start_pos current_position = start_pos
enhanced_gridmap = p_gridmap enhanced_gridmap = p_gridmap
# Snap to grid visual (Center on tile) # Grounded and Side-positioned
# User wanted Z=1.0. X should ideally be 0.5 (center). # We use a consistent side offset (e.g. North-West corner of the tile)
# Ensuring consistent offset across Init, Sync, and Move. update_visual_position()
position = Vector3(current_position.x + 0.5, 1.0, current_position.y + 1.0)
# NO RPC HERE - Spawn RPC handles initial position sync
@rpc("any_peer", "call_local", "reliable") @rpc("any_peer", "call_local", "reliable")
func sync_position(pos: Vector2i): func sync_position(pos: Vector2i):
current_position = pos current_position = pos
position = Vector3(current_position.x + 0.5, 1.0, current_position.y + 1.0) update_visual_position()
func update_visual_position():
if is_carried: return
# Align with floor height (matching player's typical grounded height)
var floor_y = 0.05
if enhanced_gridmap and "cell_size" in enhanced_gridmap:
floor_y = enhanced_gridmap.cell_size.y
# Side offset: place it near the edge
# Using NW corner (+0.2, +0.2) instead of center (+0.5, +0.5)
position = Vector3(current_position.x + 0.2, floor_y, current_position.y + 0.2)
func move_to(target_pos: Vector2i): func move_to(target_pos: Vector2i):
if is_moving: return if is_moving or is_carried: return
# Validate # Validate
if not enhanced_gridmap.is_position_valid(target_pos): if not enhanced_gridmap.is_position_valid(target_pos):
return return
# Check simple collision (optional, can be expanded)
# For now, Tekton walks through things or we check elsewhere?
# Controller should check validity.
is_moving = true is_moving = true
var world_pos = Vector3(target_pos.x + 0.5, 1.0, target_pos.y + 1.0)
var floor_y = 0.05
if enhanced_gridmap and "cell_size" in enhanced_gridmap:
floor_y = enhanced_gridmap.cell_size.y
var target_world_pos = Vector3(target_pos.x + 0.2, floor_y, target_pos.y + 0.2)
# Rotation # Rotation
var dir = world_pos - position var dir = target_world_pos - position
if dir.length_squared() > 0.1: if dir.length_squared() > 0.01:
var target_rot = atan2(dir.x, dir.z) var target_rot = atan2(dir.x, dir.z)
rotation.y = target_rot rotation.y = target_rot
# Tween Movement (Match Z+0.5 offset for symmetry during movement as requested)
var final_world_pos = Vector3(target_pos.x + 0.5, 1.0, target_pos.y + 0.5)
tween = create_tween() tween = create_tween()
tween.tween_property(self, "position", final_world_pos, movement_speed).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) tween.tween_property(self , "position", target_world_pos, movement_speed).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
tween.tween_callback(func(): tween.tween_callback(func():
current_position = target_pos current_position = target_pos
is_moving = false is_moving = false
@@ -69,16 +81,43 @@ func sync_movement(target_pos: Vector2i):
# --- COMBAT / INTERACTION --- # --- COMBAT / INTERACTION ---
func on_hit(attacker: Node = null): func on_hit(attacker: Node = null, intensity: float = 1.0):
"""Called when hit by a player attack.""" """Called when hit by a player attack or knock.
print("[Tekton] Hit by %s!" % (attacker.name if attacker else "Unknown")) Intensity: 0.5 for throw, 1.0+ for knock."""
print("[Tekton] Hit by %s! Intensity: %.1f" % [attacker.name if attacker else "Unknown", intensity])
# Visual Reaction (Flash red) # Visual Reaction (Flash red)
_flash_damage() _flash_damage()
# Spawn Tiles # Spawn Tiles
if is_multiplayer_authority(): if is_multiplayer_authority():
spawn_tiles_around() var tile_count = int(8 * intensity) # Base 8 tiles for 1.0 intensity
spawn_tiles_around(tile_count)
@rpc("any_peer", "call_local")
func set_carried(state: bool, p_carrier: Node3D = null):
is_carried = state
carrier = p_carrier
is_moving = false
if tween: tween.kill()
# Disable/Enable controller
var controller = get_node_or_null("TektonController")
if controller:
controller.set_physics_process(not state)
if state:
controller.get_node("Timer").stop()
else:
controller.call("_start_timer")
if not state:
update_visual_position()
func _process(delta):
if is_carried and is_instance_valid(carrier):
# Carry on head: offset Y by approx carrier height (e.g. 1.25)
global_position = carrier.global_position + Vector3(0, 1.5, 0)
rotation = carrier.rotation
func _flash_damage(): func _flash_damage():
var meshes = find_children("*", "MeshInstance3D", true) var meshes = find_children("*", "MeshInstance3D", true)
@@ -90,7 +129,7 @@ func _flash_damage():
t.tween_property(mesh, "scale", Vector3(1.2, 1.2, 1.2), 0.1) t.tween_property(mesh, "scale", Vector3(1.2, 1.2, 1.2), 0.1)
t.tween_property(mesh, "scale", Vector3(1.0, 1.0, 1.0), 0.1) t.tween_property(mesh, "scale", Vector3(1.0, 1.0, 1.0), 0.1)
func spawn_tiles_around(): func spawn_tiles_around(count: int = 4):
"""Spawns a mix of normal and special tiles in a radius.""" """Spawns a mix of normal and special tiles in a radius."""
if not enhanced_gridmap: return if not enhanced_gridmap: return
@@ -98,35 +137,41 @@ func spawn_tiles_around():
var rng = RandomNumberGenerator.new() var rng = RandomNumberGenerator.new()
rng.randomize() rng.randomize()
print("[Tekton] Spawning tiles around %s" % current_position) print("[Tekton] Spawning %d tiles around %s" % [count, current_position])
for x in range(-radius, radius + 1): var spawned = 0
for y in range(-radius, radius + 1): var attempts = 0
var pos = current_position + Vector2i(x, y) while spawned < count and attempts < 25:
attempts += 1
var x = rng.randi_range(-radius, radius)
var y = rng.randi_range(-radius, radius)
# Don't overwrite the Tekton's own cell? Or do? var pos = current_position + Vector2i(x, y)
# Maybe avoid center.
if x == 0 and y == 0: continue
if enhanced_gridmap.is_position_valid(pos): # Don't overwrite the Tekton's own cell? Or do?
# 50% chance to spawn something # Maybe avoid center.
if rng.randf() > 0.5: continue if x == 0 and y == 0: continue
# Determine Type if enhanced_gridmap.is_position_valid(pos):
var item_id: int # 50% chance to spawn something
var roll = rng.randf() if rng.randf() > 0.5: continue
if roll < 0.6: # Determine Type
# 60% Normal Tile (7-10) var item_id: int
item_id = rng.randi_range(7, 10) var roll = rng.randf()
elif roll < 0.9:
# 30% PowerUp (11-14)
item_id = rng.randi_range(11, 14)
else:
# 10% Obstacle/Trap (optional)
item_id = -1 # Clear?
if item_id != -1: if roll < 0.6:
var main = get_tree().get_root().get_node_or_null("Main") # 60% Normal Tile (7-10)
if main: item_id = rng.randi_range(7, 10)
main.rpc("sync_grid_item", pos.x, 1, pos.y, item_id) elif roll < 0.9:
# 30% PowerUp (11-14)
item_id = rng.randi_range(11, 14)
else:
# 10% Obstacle/Trap (optional)
item_id = -1 # Clear?
if item_id != -1:
var main = get_tree().get_root().get_node_or_null("Main")
if main:
main.rpc("sync_grid_item", pos.x, 1, pos.y, item_id)
spawned += 1