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
+9 -3
View File
@@ -47,7 +47,6 @@ func _process(delta):
movement_manager.simple_move_to(target_position)
# Targeting Mode Preview
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:
@@ -69,7 +68,7 @@ func _process(delta):
# Choose highlight color/mesh based on skill
# User Request: Use default hover item (1)
var highlight_id = 1
var highlight_id = 1
player.highlight_cells_if_authorized(area, highlight_id)
@@ -104,7 +103,7 @@ func handle_unhandled_input(event):
if player.powerup_manager:
# Attack Mode (formerly Special)
player.powerup_manager.use_special_effect()
KEY_E:
KEY_E:
if player.powerup_manager:
# Spawn Boost
if player.powerup_manager.has_method("spawn_boost_reward"):
@@ -112,6 +111,13 @@ func handle_unhandled_input(event):
else:
# Fallback if method missing
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
+11 -7
View File
@@ -7,15 +7,15 @@ const HOLO_TILES = [11, 12, 13, 14]
enum SpecialEffect {
FASTER_SPEED, # ID 11
AREA_FREEZE, # ID 12
BLOCK_FLOOR, # ID 13
AREA_FREEZE, # ID 12
BLOCK_FLOOR, # ID 13
INVISIBLE_MODE # ID 14
}
# 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 active_buffs: Dictionary = {} # EffectEnum -> float (Duration Running)
var active_buffs: Dictionary = {} # EffectEnum -> float (Duration Running)
# Cooldown Constants (Level 1 / Level 8)
const COOLDOWN_L1 = 15.0
@@ -203,6 +203,11 @@ func activate_effect(effect: int, target_player: Node3D = null):
if powerup_cooldowns.get(effect, 0.0) > 0:
print("PowerUp %s on cooldown." % SpecialEffect.keys()[effect])
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
var level = powerup_levels.get(effect, 1)
@@ -241,7 +246,7 @@ func activate_effect(effect: int, target_player: Node3D = null):
NotificationManager.send_message(player, msg, NotificationManager.MessageType.NORMAL)
# Do NOT set cooldown yet. Cooldown sets on execution.
# Revert the cooldown set above (hacky but handles the split flow)
powerup_cooldowns[effect] = 0.0
powerup_cooldowns[effect] = 0.0
emit_signal("cooldown_updated", effect, 0.0, 0.0)
print("[SpecialTiles] Entered Targeting Mode for %s" % SpecialEffect.keys()[effect])
return # Exit, wait for input
@@ -406,7 +411,6 @@ func _execute_invisible_mode(target: Node3D):
NotificationManager.send_message(target, "Invisible Mode!", NotificationManager.MessageType.POWERUP)
# =============================================================================
# Helper: Spawn Powerups (For Super Push)
# =============================================================================
@@ -460,7 +464,7 @@ func _update_freeze_zones(delta: float):
if dx <= zone.radius and dy <= zone.radius:
# Apply slow effect repeatedly
# We use a short duration so it expires quickly if they leave
p.rpc("apply_slow_effect", 0.5)
p.rpc("apply_slow_effect", 0.5)
if zone.timer <= 0:
zones_to_remove.append(i)
+96 -51
View File
@@ -11,48 +11,60 @@ signal movement_finished
var enhanced_gridmap: Node
var is_moving: bool = false
var is_carried: bool = false
var carrier: Node3D = null
var tween: Tween
const SIDE_OFFSET = 0.35 # Distance from center
func initialize(start_pos: Vector2i, p_gridmap: Node):
current_position = start_pos
enhanced_gridmap = p_gridmap
# Snap to grid visual (Center on tile)
# User wanted Z=1.0. X should ideally be 0.5 (center).
# Ensuring consistent offset across Init, Sync, and Move.
position = Vector3(current_position.x + 0.5, 1.0, current_position.y + 1.0)
# NO RPC HERE - Spawn RPC handles initial position sync
# Grounded and Side-positioned
# We use a consistent side offset (e.g. North-West corner of the tile)
update_visual_position()
@rpc("any_peer", "call_local", "reliable")
func sync_position(pos: Vector2i):
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):
if is_moving: return
if is_moving or is_carried: return
# Validate
if not enhanced_gridmap.is_position_valid(target_pos):
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
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
var dir = world_pos - position
if dir.length_squared() > 0.1:
var dir = target_world_pos - position
if dir.length_squared() > 0.01:
var target_rot = atan2(dir.x, dir.z)
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.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():
current_position = target_pos
is_moving = false
@@ -69,16 +81,43 @@ func sync_movement(target_pos: Vector2i):
# --- COMBAT / INTERACTION ---
func on_hit(attacker: Node = null):
"""Called when hit by a player attack."""
print("[Tekton] Hit by %s!" % (attacker.name if attacker else "Unknown"))
func on_hit(attacker: Node = null, intensity: float = 1.0):
"""Called when hit by a player attack or knock.
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)
_flash_damage()
# Spawn Tiles
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():
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.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."""
if not enhanced_gridmap: return
@@ -98,35 +137,41 @@ func spawn_tiles_around():
var rng = RandomNumberGenerator.new()
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):
for y in range(-radius, radius + 1):
var pos = current_position + Vector2i(x, y)
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)
var pos = current_position + Vector2i(x, y)
# Don't overwrite the Tekton's own cell? Or do?
# Maybe avoid center.
if x == 0 and y == 0: continue
if enhanced_gridmap.is_position_valid(pos):
# 50% chance to spawn something
if rng.randf() > 0.5: continue
# Don't overwrite the Tekton's own cell? Or do?
# Maybe avoid center.
if x == 0 and y == 0: continue
# Determine Type
var item_id: int
var roll = rng.randf()
if enhanced_gridmap.is_position_valid(pos):
# 50% chance to spawn something
if rng.randf() > 0.5: continue
if roll < 0.6:
# 60% Normal Tile (7-10)
item_id = rng.randi_range(7, 10)
elif roll < 0.9:
# 30% PowerUp (11-14)
item_id = rng.randi_range(11, 14)
else:
# 10% Obstacle/Trap (optional)
item_id = -1 # Clear?
# Determine Type
var item_id: int
var roll = rng.randf()
if roll < 0.6:
# 60% Normal Tile (7-10)
item_id = rng.randi_range(7, 10)
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)
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