feat: Implement core player character logic, including state management, network synchronization, character selection, and manager initialization.

This commit is contained in:
Yogi Wiguna
2026-02-12 11:02:30 +08:00
parent 786e73dbaf
commit da858c12aa
7 changed files with 206 additions and 20 deletions
+92 -9
View File
@@ -975,6 +975,8 @@ func _physics_process(delta):
func _unhandled_input(event):
# Handle power-up usage
if event.is_action_pressed("use_powerup") and is_multiplayer_authority():
if is_frozen:
return
if powerup_manager and powerup_manager.can_use_special():
powerup_manager.use_special_effect()
return
@@ -1834,16 +1836,34 @@ func sync_grab_tekton(tekton_path: NodePath):
print("[Player %s] Grabbed Tekton %s" % [name, tekton.name])
func throw_tekton():
if not is_multiplayer_authority() or not is_carrying_tekton:
if not is_multiplayer_authority() or not is_carrying_tekton or is_frozen:
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()
# NOTE: Movement manager uses atan2(x, z) which implies 0 rotation = +Z facing.
# So we must use +basis.z (Positive Z) as forward, not standard -basis.z.
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
# Calculate distance (5 to 7 tiles)
var rng = RandomNumberGenerator.new()
rng.randomize()
var distance = rng.randi_range(5, 7)
var target_pos = current_position + (throw_dir * distance)
# Clamp to grid bounds if possible, or just check validity
if enhanced_gridmap:
# Simple clamp assuming 0-based indexing and knowing size would be better,
# but if we don't have size, we can just raycast or check validity step by step?
# Let's just try the target. If invalid, maybe pull back?
# Or just let it land "off map" and handle it?
# Better: Clamp to grid dimensions if known.
# EnhancedGridMap usually has columns/rows.
if "columns" in enhanced_gridmap and "rows" in enhanced_gridmap:
target_pos.x = clamp(target_pos.x, 0, enhanced_gridmap.columns - 1)
target_pos.y = clamp(target_pos.y, 0, enhanced_gridmap.rows - 1)
if is_multiplayer_authority():
rpc("sync_throw_tekton", target_pos)
@@ -1856,13 +1876,76 @@ func sync_throw_tekton(target_pos: Vector2i):
is_carrying_tekton = false
tekton.set_carried(false)
# Move Tekton to target pos
tekton.current_position = target_pos
# Visual Arc Tween
var start_pos = tekton.global_position
# Target world position
var end_world_pos = Vector3(
target_pos.x * cell_size.x + cell_size.x * 0.5,
cell_size.y, # Floor Y
target_pos.y * cell_size.z + cell_size.z * 0.5
) + cell_offset
# Intensity 0.5 for throw (drops 50% tiles)
tekton.on_hit(self , 0.5)
var mid_pos = (start_pos + end_world_pos) / 2.0
mid_pos.y += 4.0 # Arc height
print("[Player %s] Threw Tekton to %s" % [name, target_pos])
var tween = create_tween()
tween.set_parallel(true)
# We can use a curve or just simple jump logic.
# For a nice arc in 3D: Tween X/Z linearly, Tween Y with ease out/in (bounce-like)?
# Or use a method that interpolates a curve.
# Simple approach: Tween 'position' effectively? No, linear pos is straight line.
# Let's use a value tween and update position manually, or just use a parabolic path helper?
# Easiest readable way: likely just tweening horizontal and vertical separately if we could.
# Let's stick to a simple "Jump" tween:
# 1. Move X/Z linearly to target
tween.tween_property(tekton, "global_position:x", end_world_pos.x, 0.6).set_trans(Tween.TRANS_LINEAR)
tween.tween_property(tekton, "global_position:z", end_world_pos.z, 0.6).set_trans(Tween.TRANS_LINEAR)
# 2. Move Y up then down
var jump_tween = create_tween()
jump_tween.tween_property(tekton, "global_position:y", mid_pos.y, 0.3).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
jump_tween.tween_property(tekton, "global_position:y", end_world_pos.y, 0.3).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN).set_delay(0.3)
# Landing Callback
jump_tween.tween_callback(func():
tekton.current_position = target_pos
# Impact Effects!
# 1. Stun nearby players (Radius 2?)
# "if there's a player around that floor they will got stunned" -> "around that floor" implies radius
var impact_center = target_pos
var stun_radius = 1.5
var players = get_tree().get_nodes_in_group("Players")
print("[Throw] Checking stun impact at %s. Found %d players." % [impact_center, players.size()])
for p in players:
if p == self: continue
# Check distance
var dist = Vector2(p.current_position.x, p.current_position.y).distance_to(Vector2(impact_center.x, impact_center.y))
print("[Throw] Player %s at %s. Dist: %.2f (Radius: %.1f)" % [p.name, p.current_position, dist, stun_radius])
if dist <= stun_radius:
if p.has_method("apply_stagger"):
print("[Throw] Applying stagger to %s" % p.name)
p.apply_stagger(3.0)
NotificationManager.send_message(self , "Stunned " + p.name + "!", NotificationManager.MessageType.WARNING)
# 2. Tekton drops tiles (Spawn tiles around) AND shrinks
if tekton.has_method("on_thrown_landing"):
tekton.on_thrown_landing(self )
else:
# Fallback
tekton.on_hit(self , 1.0)
print("[Player %s] Tekton landed at %s" % [name, target_pos])
).set_delay(0.6)
print("[Player %s] Threw Tekton to %s (Dist: %s)" % [name, target_pos, target_pos.distance_to(tekton.current_position)])
func knock_tekton():
if not is_multiplayer_authority() or is_frozen: