Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 90 additions & 1 deletion Scripts/demo_renderer.gd
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ extends Node

const ProxyClass = preload("res://Scripts/demo_player_proxy.gd")
const FALLBACK_HEALTH_TEXTURE = preload("res://Shapes/feather.svg")
const ENERGY_TEXT_SCENE = preload("res://Scenes/energy_text.tscn")
const PLAYER_SHADOW_TEXTURE = preload("res://Shapes/Circle.svg")

@export var stage_a: Node2D
@export var stage_b: Node2D
Expand Down Expand Up @@ -77,9 +79,11 @@ func _ready() -> void:
{
"ghosts": _ghosts_a,
"balls": _balls_a,
"energy_collect_seq": {},
"stage": stage_a,
"high_stage": high_stage_a,
"player": _player_a,
"shadow": null,
"prev_player": {},
"proxy": null,
"loaded_skin_id": "",
Expand All @@ -94,9 +98,11 @@ func _ready() -> void:
{
"ghosts": _ghosts_b,
"balls": _balls_b,
"energy_collect_seq": {},
"stage": stage_b,
"high_stage": high_stage_b,
"player": _player_b,
"shadow": null,
"prev_player": {},
"proxy": null,
"loaded_skin_id": "",
Expand Down Expand Up @@ -308,6 +314,14 @@ func _update_energy_balls(screen: Dictionary, stage: Node2D, balls_array: Array)
if ball_id == -1:
continue

var collect_seq_by_id: Dictionary = screen["energy_collect_seq"]
var collect_seq: int = ball_data.get("collect_seq", 0)
if collect_seq_by_id.has(ball_id):
var previous_seq: int = collect_seq_by_id.get(ball_id, 0)
if collect_seq > previous_seq:
_spawn_demo_energy_text(screen, ball_data)
collect_seq_by_id[ball_id] = collect_seq

var collected: bool = ball_data.get("collected", false)
var combo: int = ball_data.get("combo", 0)
if collected:
Expand All @@ -325,6 +339,7 @@ func _update_energy_balls(screen: Dictionary, stage: Node2D, balls_array: Array)
var ghost: Node2D = balls[ball_id]
if is_instance_valid(ghost):
ghost.global_position = pos
ghost.scale = ball_data.get("scale", ghost.scale)
ghost.visible = true
ghost.now_combo = combo
else:
Expand All @@ -335,6 +350,7 @@ func _update_energy_balls(screen: Dictionary, stage: Node2D, balls_array: Array)
stage.add_child(ghost)
_suppress_energy_ball(ghost)
ghost.global_position = pos
ghost.scale = ball_data.get("scale", Vector2.ONE)
ghost.visible = true
balls[ball_id] = ghost

Expand All @@ -345,6 +361,52 @@ func _update_energy_balls(screen: Dictionary, stage: Node2D, balls_array: Array)
if is_instance_valid(old):
old.queue_free()
balls.erase(ball_id)
screen["energy_collect_seq"].erase(ball_id)


func _spawn_demo_energy_text(screen: Dictionary, ball_data: Dictionary) -> void:
var gain: int = ball_data.get("last_collect_gain", 0)
if gain <= 0:
return
var combo: int = ball_data.get("last_collect_combo", 0)
combo = mini(maxi(combo, 0), 5)
var pos: Vector2 = ball_data.get(
"last_collect_position", ball_data.get("position", Vector2.ZERO)
)

var text := ENERGY_TEXT_SCENE.instantiate() as Label
if not text:
return
if stage_layer:
stage_layer.add_child(text)
else:
var stage: Node2D = screen.get("stage", null) as Node2D
if not stage:
text.queue_free()
return
stage.add_child(text)
text.text = "+%d" % gain
text.z_index = Util.LAYERS.get("EnergyBall", 0) + 1
var text_offset: Vector2 = Vector2(text.size.x / 2.0, 0.0)
if stage_layer:
text.global_position = _world_to_demo_canvas(screen, pos) - text_offset
else:
text.global_position = pos - text_offset
if text.has_method("initialize"):
text.initialize(combo)


func _world_to_demo_canvas(screen: Dictionary, world_position: Vector2) -> Vector2:
var stage: Node2D = screen.get("stage", null) as Node2D
if not stage:
return world_position

var viewport := stage.get_viewport()
var canvas_position: Vector2 = viewport.canvas_transform * world_position
var viewport_parent := viewport.get_parent()
if viewport_parent is Control:
canvas_position += (viewport_parent as Control).get_global_rect().position
return canvas_position


## Lightweight suppression for energy ball ghosts: keeps _physics_process
Expand Down Expand Up @@ -414,6 +476,7 @@ func _apply_player(screen: Dictionary, stage: Node2D, player_state: Dictionary)
# Update position
var pos: Vector2 = player_state.get("position", Vector2.ZERO)
var sprite_y: float = player_state.get("sprite_y", 0.0)
_update_player_shadow(screen, stage, pos, sprite_y, player_state.get("is_jumping", false))
ghost.global_position = Vector2(pos.x, pos.y - sprite_y)

# Update velocity for skin _process() animations
Expand Down Expand Up @@ -460,6 +523,32 @@ func _apply_player(screen: Dictionary, stage: Node2D, player_state: Dictionary)
}


func _update_player_shadow(
screen: Dictionary, stage: Node2D, pos: Vector2, sprite_y: float, is_jumping: bool
) -> void:
if not stage:
return
var shadow: Sprite2D = screen.get("shadow", null)
if not is_instance_valid(shadow):
shadow = Sprite2D.new()
shadow.name = "PlayerShadowGhost"
shadow.texture = PLAYER_SHADOW_TEXTURE
shadow.z_index = Util.LAYERS.get("Player/ShadowSprite", 0)
shadow.modulate = Color(0, 0, 0, 0.5)
stage.add_child(shadow)
screen["shadow"] = shadow

shadow.global_position = pos
var should_show: bool = is_jumping or sprite_y > 0.0
shadow.visible = should_show
if not should_show:
return

var jump_ratio: float = clamp(sprite_y / 120.0, 0.0, 1.0)
shadow.scale = Vector2.ONE * lerp(0.2, 0.1, jump_ratio)
shadow.modulate.a = lerp(0.5, 0.15, jump_ratio)


## Updates the energy HUD for [param screen] from [param energy] and [param max_energy].
func _update_energy_hud(screen: Dictionary, energy: int, max_energy: int) -> void:
var label = screen["energy_label"]
Expand Down Expand Up @@ -561,7 +650,7 @@ func _load_skin(skin_id: String) -> Node2D:
func _create_player_ghost(stage: Node2D) -> Node2D:
var ghost := Sprite2D.new()
ghost.name = "PlayerGhost"
ghost.texture = preload("res://Shapes/Circle.svg")
ghost.texture = PLAYER_SHADOW_TEXTURE
ghost.scale = Vector2(0.2, 0.2)
ghost.z_index = Util.LAYERS["Player/BodySprite"]
ghost.modulate = Color(0.2, 0.6, 1.0, 1.0)
Expand Down
11 changes: 10 additions & 1 deletion Scripts/energy_ball.gd
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ var now_combo := 0
var time_elapsed := 0.0
var energy_gain: Array
var energy_ball_phase: int = 0
var collect_seq := 0
var last_collect_gain := 0
var last_collect_combo := 0
var last_collect_position := Vector2.ZERO

var rng := RandomNumberGenerator.new()

Expand Down Expand Up @@ -52,8 +56,13 @@ func _reload_from_game_data() -> void:
func _on_body_entered(body: Node2D) -> void:
if collision_shape.disabled or body != player_node:
return
var gain: int = energy_gain[energy_ball_phase][now_combo]
collect_seq += 1
last_collect_gain = gain
last_collect_combo = now_combo
last_collect_position = global_position
collision_shape.set_deferred("disabled", true)
Global.energyball_collected.emit(energy_gain[energy_ball_phase][now_combo])
Global.energyball_collected.emit(gain)
Audio.play_coconut_sfx(now_combo)
_spawn_energy_text()
now_combo = min(now_combo + 1, energy_gain[energy_ball_phase].size() - 1)
Expand Down
8 changes: 7 additions & 1 deletion Scripts/energy_label.gd
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ var time: float = 0
func _ready() -> void:
scale = Vector2(0.8, 0.8)
energy_label.text = "0"
_refresh_limitation_position()


func _process(delta: float) -> void:
Expand All @@ -18,8 +19,13 @@ func _process(delta: float) -> void:

func _update_energy(energy_amount: int) -> void:
energy_label.text = str(energy_amount)
limitation_label.position = Vector2(energy_label.text.length() * 32 + 52, 0)
_refresh_limitation_position()


func _update_max_energy(max_energy: int) -> void:
limitation_label.text = "/%d" % max_energy
_refresh_limitation_position()


func _refresh_limitation_position() -> void:
limitation_label.position = Vector2(energy_label.text.length() * 32 + 52, 0)
12 changes: 12 additions & 0 deletions Scripts/network/state_serializer.gd
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,20 @@ func _serialize_energy_balls() -> Array[Dictionary]:
{
"id": child.get_instance_id(),
"position": child.global_position if child is Node2D else Vector2.ZERO,
"scale": child.scale if child is Node2D else Vector2.ONE,
"collected": not child.visible if "visible" in child else false,
"combo": child.get("now_combo") if "now_combo" in child else 0,
"collect_seq": child.get("collect_seq") if "collect_seq" in child else 0,
"last_collect_gain":
child.get("last_collect_gain") if "last_collect_gain" in child else 0,
"last_collect_combo":
child.get("last_collect_combo") if "last_collect_combo" in child else 0,
"last_collect_position":
(
child.get("last_collect_position")
if "last_collect_position" in child
else Vector2.ZERO
),
}
)
)
Expand Down
117 changes: 106 additions & 11 deletions Scripts/traps/trap9-mortar.gd
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ var explosion_radius: float = 0.0
var shell_vertical_speed: float = 0.0
var my_shell_rotate_speed: float
var is_demo := false
var demo_shell: Sprite2D
var demo_shadow: Sprite2D

@onready var shell: Sprite2D = $ShellShadow/Shell
@onready var shell_shadow: Node2D = $ShellShadow
Expand All @@ -41,12 +43,85 @@ var is_demo := false


func _ready() -> void:
_setup_layers()
if is_demo:
_setup_demo_display()
return
$ShellShadow/Shell.z_index = Util.LAYERS["Trap9Mortar/Shell"]
$ShellShadow/Shadow.z_index = Util.LAYERS["Trap9Mortar/Shadow"]
$Explosion.z_index = Util.LAYERS["Trap9Mortar/Explosion"]
shell.reparent(Global.high_stage)
_reparent_shell_to_high_stage()


func _exit_tree() -> void:
if is_instance_valid(shell) and shell.get_parent() != shell_shadow:
if not shell.is_queued_for_deletion():
shell.queue_free()


func _setup_layers() -> void:
shell.z_index = Util.LAYERS["Trap9Mortar/Shell"]
shadow.z_index = Util.LAYERS["Trap9Mortar/Shadow"]
explosion.z_index = Util.LAYERS["Trap9Mortar/Explosion"]


func _reparent_shell_to_high_stage() -> void:
if Global.high_stage and shell.get_parent() != Global.high_stage:
shell.reparent(Global.high_stage)


func _setup_demo_display() -> void:
shell_shadow.visible = false
shell.visible = false
shadow.visible = false


func _create_demo_sprite(sprite_name: String, source: Sprite2D, layer: int) -> Sprite2D:
var sprite := Sprite2D.new()
sprite.name = sprite_name
sprite.texture = source.texture
sprite.scale = source.scale
sprite.centered = source.centered
sprite.offset = source.offset
sprite.top_level = true
sprite.z_as_relative = false
sprite.z_index = layer
sprite.visible = false
add_child(sprite)
return sprite


func _ensure_demo_visuals() -> void:
if not is_instance_valid(demo_shadow):
demo_shadow = _create_demo_sprite(
"DemoMortarShadow", shadow, Util.LAYERS["Trap9Mortar/Shadow"]
)
if not is_instance_valid(demo_shell):
demo_shell = _create_demo_sprite("DemoMortarShell", shell, Util.LAYERS["Trap9Mortar/Shell"])


func _apply_demo_visuals(
data: Dictionary,
shadow_position: Vector2,
shell_position: Vector2,
shell_rotation: float,
explosion_visible: bool
) -> void:
_ensure_demo_visuals()
_setup_demo_display()

var show_shadow: bool = data.get("shadow_visible", not explosion_visible)
show_shadow = show_shadow and data.get("shell_shadow_visible", not explosion_visible)
show_shadow = show_shadow and not explosion_visible
var show_shell: bool = data.get("shell_visible", not explosion_visible)
show_shell = show_shell and not explosion_visible

demo_shadow.global_position = shadow_position
demo_shadow.rotation = shell_rotation
demo_shadow.self_modulate = data.get("shadow_modulate", Color.BLACK)
demo_shadow.visible = show_shadow

demo_shell.global_position = shell_position
demo_shell.rotation = shell_rotation
demo_shell.self_modulate = Color.WHITE
demo_shell.visible = show_shell


static func initialize(start_pos: Vector2, end_pos: Vector2, air_time: float) -> Trap9Mortar:
Expand Down Expand Up @@ -139,23 +214,43 @@ func serialize_state() -> Dictionary:
"type": "trap9-mortar",
"position": global_position,
"shadow_position": shell_shadow.global_position,
"demo_shell_position": shell_shadow.global_position + Vector2(0.0, remote_shell.position.y),
"shell_y": remote_shell.position.y,
"shell_rotation": remote_shell.rotation,
"shadow_modulate": shadow.self_modulate,
"shadow_visible": shadow.visible,
"shell_visible": shell.visible,
"shell_shadow_visible": shell_shadow.visible,
"explosion_visibility": explosion.visible,
"modulate": modulate,
"particle_emitting": effect.emitting
}


func apply_demo_state(data: Dictionary) -> void:
shell_shadow.global_position = data.get("shadow_position", Vector2.ZERO)
var shadow_position: Vector2 = data.get("shadow_position", Vector2.ZERO)
var shell_position: Vector2 = data.get(
"demo_shell_position", shadow_position + Vector2(0.0, data.get("shell_y", 0.0))
)
var shell_rotation: float = data.get("shell_rotation", 0.0)
var explosion_visible: bool = data.get("explosion_visibility", false)

global_position = data.get("position", Vector2.ZERO)
shell.position.y = data.get("shell_y", 0.0)
shell.rotation = data.get("shell_rotation", 0.0)
shadow.rotation = shell.rotation
shadow.self_modulate = data.get("shadow_modulate", Color.BLACK)
explosion.visible = data.get("explosion_visibility", false)
shell_shadow.visible = !explosion.visible
explosion.visible = explosion_visible
modulate = data.get("modulate", Color.WHITE)
effect.emitting = data.get("particle_emitting", false)

if is_demo:
_apply_demo_visuals(
data, shadow_position, shell_position, shell_rotation, explosion_visible
)
return

shell_shadow.global_position = shadow_position
shell.global_position = shell_position
shell.rotation = shell_rotation
shadow.rotation = shell_rotation
shadow.self_modulate = data.get("shadow_modulate", Color.BLACK)
shell_shadow.visible = data.get("shell_shadow_visible", not explosion_visible)
shadow.visible = data.get("shadow_visible", not explosion_visible)
shell.visible = data.get("shell_visible", not explosion_visible)
Loading