update
This commit is contained in:
@@ -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)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|||||||
@@ -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:
|
||||||
@@ -1186,6 +1195,9 @@ func start_movement_along_path(path: Array, clear_visual: bool = true):
|
|||||||
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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|||||||
+71
-26
@@ -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,10 +137,15 @@ 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])
|
||||||
|
|
||||||
|
var spawned = 0
|
||||||
|
var attempts = 0
|
||||||
|
while spawned < count and attempts < 25:
|
||||||
|
attempts += 1
|
||||||
|
var x = rng.randi_range(-radius, radius)
|
||||||
|
var y = rng.randi_range(-radius, radius)
|
||||||
|
|
||||||
for x in range(-radius, radius + 1):
|
|
||||||
for y in range(-radius, radius + 1):
|
|
||||||
var pos = current_position + Vector2i(x, y)
|
var pos = current_position + Vector2i(x, y)
|
||||||
|
|
||||||
# Don't overwrite the Tekton's own cell? Or do?
|
# Don't overwrite the Tekton's own cell? Or do?
|
||||||
@@ -130,3 +174,4 @@ func spawn_tiles_around():
|
|||||||
var main = get_tree().get_root().get_node_or_null("Main")
|
var main = get_tree().get_root().get_node_or_null("Main")
|
||||||
if main:
|
if main:
|
||||||
main.rpc("sync_grid_item", pos.x, 1, pos.y, item_id)
|
main.rpc("sync_grid_item", pos.x, 1, pos.y, item_id)
|
||||||
|
spawned += 1
|
||||||
|
|||||||
Reference in New Issue
Block a user