Post Game jam commit

This commit is contained in:
Daniel Kauss Serna 2026-02-03 21:06:49 +01:00
commit 6db2131520
164 changed files with 172524 additions and 0 deletions

24
scripts/AnimSprite.gd Normal file
View file

@ -0,0 +1,24 @@
extends AnimatedSprite2D
var time = 0.0
@export var float_str = 10.0
@export var float_time = 2.0
@export var float_offset = -12.0
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
time += delta
# Calculate the new Y position using a sine wave
position.y = sin(time * float_time) * float_str + float_offset
queue_redraw()
func switch_mask(new : Types.mask_types):
$MeleeMask.visible = false
$RangedMask.visible = false
$SpitMask.visible = false
if (new == Types.mask_types.Melee):
$MeleeMask.visible = true
elif (new == Types.mask_types.Ranged):
$RangedMask.visible = true
elif (new == Types.mask_types.Spit):
$SpitMask.visible = true

View file

@ -0,0 +1 @@
uid://c0e4uacrlcrk8

110
scripts/camera.gd Normal file
View file

@ -0,0 +1,110 @@
extends Camera2D
@export var target_res : Vector2 = Vector2(480, 270)
@export var target_zoom : float = 1
@export var cam_speed : float = 5.0
@export var min_speed : float = 0.1
@export var ui_layer : CanvasLayer
# --- NEW VARIABLE ---
# 0.0 = no movement, 0.5 = camera moves halfway to mouse, 1.0 = camera centers on mouse
@export_range(0.0, 1.0) var mouse_look_strength : float = 0.15
# --------------------
@export var shake_decay : float = 5.0
@export var max_offset : Vector2 = Vector2(10, 10)
var pixel_material : ShaderMaterial
var scaling : Vector2
var actual_pos : Vector2
var shake_trauma : float = 0.0
func _ready():
# Safety check to prevent crash if node is missing
if has_node("Pixelator"):
pixel_material = get_node("Pixelator").material
get_viewport().size_changed.connect(_on_viewport_resized)
EventBus.player_dmg.connect(_on_player_dmg)
EventBus.screenshake.connect(_on_screenshake)
_on_viewport_resized()
func _fract(x : Vector2) -> Vector2:
return x - floor(x)
func _on_player_dmg():
# Apply a default punchy shake when hit
add_shake(0.5)
func _on_screenshake(intensity: float):
add_shake(intensity)
func add_shake(amount: float):
shake_trauma = clamp(shake_trauma + amount, 0.0, 1.0)
func _apply_shake():
# Squaring trauma makes the shake feel more organic (stronger at start, tapers off)
var amount = pow(shake_trauma, 2)
var offset = Vector2(
max_offset.x * amount * randf_range(-1, 1),
max_offset.y * amount * randf_range(-1, 1)
)
# Apply floor to keep the shake aligned with your pixel grid
global_position = floor(actual_pos + offset)
func _on_viewport_resized():
var viewport_res : Vector2 = get_viewport().get_visible_rect().size
scaling = Vector2(viewport_res.x / target_res.x, viewport_res.y / target_res.y)
if has_node("Pixelator"):
var pixel_rect : ColorRect = get_node("Pixelator")
pixel_rect.position = -target_res / 2.
pixel_rect.size = target_res
zoom = scaling * target_zoom
if ui_layer:
ui_layer.scale = zoom
EventBus.debug_print.emit("new scaling: " + str(scaling))
func _process(delta : float):
var parent_pos : Vector2 = get_parent().global_position
# --- NEW MOUSE LOOK LOGIC ---
var mouse_pos = get_viewport().get_mouse_position()
var vp_size = get_viewport().get_visible_rect().size
var center = vp_size / 2.0
# Calculate how far mouse is from center
var offset_from_center = mouse_pos - center
# Apply strength factor
offset_from_center *= mouse_look_strength
# Convert screen pixels to world units by dividing by the current scaling/zoom
# (We guard against division by zero just in case)
if scaling.x != 0 and scaling.y != 0:
var world_offset = offset_from_center / scaling
parent_pos += world_offset
# ---------------------------
var dist := actual_pos.distance_to(parent_pos)
var speed : float = max(min_speed, dist * cam_speed) * delta
actual_pos = actual_pos.move_toward(parent_pos, speed)
if shake_trauma > 0:
shake_trauma = max(shake_trauma - shake_decay * delta, 0)
_apply_shake()
else:
global_position = floor(actual_pos)
var sub_pixels : Vector2 = floor(_fract(actual_pos) * scaling)
if pixel_material:
pixel_material.set_shader_parameter("cam_pos", sub_pixels)
pixel_material.set_shader_parameter("scaling", scaling)

1
scripts/camera.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://b5wg73yvuksot

22
scripts/debug_ui.gd Normal file
View file

@ -0,0 +1,22 @@
extends Control
var lines : Array[String] = []
func _ready() -> void:
EventBus.debug_print.connect(_on_debug_print)
func _process(delta: float) -> void:
$Label.text = "FPS: " + str(Engine.get_frames_per_second()) + "\n"
for l in lines:
$Label.text += l + "\n"
func _on_debug_print(msg : String):
lines.append(msg)
if len(lines) > 10:
lines.pop_front()
func _shortcut_input(event: InputEvent) -> void:
if (event.is_action_pressed("debug")):
visible = !visible

1
scripts/debug_ui.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://da2hsusvv3rxg

12
scripts/dmg_flash.gd Normal file
View file

@ -0,0 +1,12 @@
extends ColorRect
func _ready():
EventBus.player_dmg.connect(flash_red)
func flash_red():
var tween = create_tween()
color.a = 0.3
tween.tween_property(self, "color:a", 0.0, 0.2).set_trans(Tween.TRANS_SINE)

1
scripts/dmg_flash.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://dgxs6odxyi6s4

View file

@ -0,0 +1,5 @@
extends GPUParticles2D
func _on_finished() -> void:
queue_free()

View file

@ -0,0 +1 @@
uid://b6f7w4uftych8

36
scripts/enemy_spawn.gd Normal file
View file

@ -0,0 +1,36 @@
extends Node2D
@export var enemies: Array[PackedScene] # Drag your enemy .tscn here
@export var num_circles: int = 3 # How many rings of enemies
@export var enemies_per_circle: int = 8 # Base number of enemies
@export var radius_step: float = 100.0 # Distance between each ring
@export var initial_radius: float = 150.0
func _ready() -> void:
spawn_concentric_horde()
func spawn_concentric_horde() -> void:
for i in range(num_circles):
# We calculate the radius for this specific ring
var current_radius = initial_radius + (i * radius_step)
for j in range(enemies_per_circle):
spawn_enemy_at_random_angle(current_radius)
func spawn_enemy_at_random_angle(radius: float) -> void:
var random_angle = randf_range(0, 2 * PI)
var spawn_pos = Vector2(
radius * cos(random_angle),
radius * sin(random_angle)
)
var idx1 = randi() % len(enemies)
var idx2 = randi() % len(enemies)
var idx = min(idx1, idx2)
print(idx)
var enemy_instance = enemies[idx].instantiate()
add_child(enemy_instance)
enemy_instance.position = spawn_pos

View file

@ -0,0 +1 @@
uid://kyh2wu0jwdwd

134
scripts/grass.gd Normal file
View file

@ -0,0 +1,134 @@
@tool
class_name GrassMultiMesh extends MultiMeshInstance2D
@export_group("Visuals")
@export var texture_sheet : Texture2D
@export var blade_size : Vector2 = Vector2(16, 16)
@export var texture_sheet_size : Vector2 = Vector2(64, 16)
@export var grass_tint : Color
@export_group("Editor")
@export var editing : bool = false
@export var brush_radius : float = 50.0
@export var brush_density : int = 5
@export var grass_data : Array[Dictionary] = []
enum GrassState {NORMAL = 0, CUT = 1}
func _ready() -> void:
if not multimesh:
multimesh = MultiMesh.new()
multimesh.transform_format = MultiMesh.TRANSFORM_2D
multimesh.use_colors = true
multimesh.use_custom_data = true
multimesh.mesh = _create_mesh_rect()
if material is ShaderMaterial:
material.set_shader_parameter("frame_size", blade_size)
material.set_shader_parameter("sheet_size", texture_sheet_size)
_rebuild_multimesh()
func _process(_delta):
# move_grass_away(%Player.global_position, 25)
if editing and Engine.is_editor_hint():
if Input.is_key_pressed(KEY_K):
_brush_paint()
if Input.is_key_pressed(KEY_E):
_brush_erase()
queue_redraw()
func cut_grass_at(pos: Vector2, radius: float):
_interact_with_grass(pos, radius, GrassState.CUT)
func move_grass_away(pos: Vector2, radius: float):
var half = radius / 2
var local_target = to_local(pos)
for i in range(multimesh.instance_count):
var t = multimesh.get_instance_transform_2d(i)
var dist = t.origin.distance_squared_to(local_target)
if dist < half * half:
var custom_data = multimesh.get_instance_custom_data(i)
custom_data.r = (t.origin.x - local_target.x) * 2
multimesh.set_instance_custom_data(i, custom_data)
elif dist < radius * radius:
var custom_data = multimesh.get_instance_custom_data(i)
custom_data.r = grass_data[i]["rot"]
multimesh.set_instance_custom_data(i, custom_data)
func _interact_with_grass(global_target_pos: Vector2, radius: float, new_state: int):
var local_target = to_local(global_target_pos)
for i in range(multimesh.instance_count):
var t = multimesh.get_instance_transform_2d(i)
if t.origin.distance_squared_to(local_target) < radius * radius:
var custom_data = multimesh.get_instance_custom_data(i)
if int(custom_data.b) == new_state:
continue
custom_data.b = float(new_state)
multimesh.set_instance_custom_data(i, custom_data)
grass_data[i]["state"] = new_state
func _create_mesh_rect() -> QuadMesh:
var mesh = QuadMesh.new()
mesh.size = blade_size
return mesh
func _rebuild_multimesh() -> void:
multimesh.instance_count = grass_data.size()
var pivot_offset = Vector2(0, -blade_size.y / 2.0)
for i in range(grass_data.size()):
var d = grass_data[i]
var xform = Transform2D().translated(d.pos).rotated(d.get("rot", 0.0)).translated(pivot_offset)
multimesh.set_instance_transform_2d(i, xform)
multimesh.set_instance_color(i, grass_tint)
var custom = Color(
randf(),
float(d.get("tex_idx", 0)),
float(d.get("state", 0)),
0.0
)
multimesh.set_instance_custom_data(i, custom)
func _brush_paint() -> void:
var s = Time.get_ticks_msec()
print(s)
seed(s)
var mouse_pos = get_local_mouse_position()
for i in range(brush_density):
var angle = randf() * TAU
var rad = sqrt(randf()) * brush_radius
var pos = mouse_pos + Vector2(cos(angle), sin(angle)) * rad
var new_blade = {
"pos": pos,
"tex_idx": int((randf() ** (1.0/3.0)) * 8),
"state": GrassState.NORMAL,
"rot": randf_range(-0.1, 0.1)
}
grass_data.append(new_blade)
_rebuild_multimesh()
func _brush_erase() -> void:
var mouse_pos = get_local_mouse_position()
for i in range(grass_data.size() - 1, -1, -1):
if grass_data[i].pos.distance_to(mouse_pos) < brush_radius:
grass_data.remove_at(i)
_rebuild_multimesh()
func _draw() -> void:
if editing and Engine.is_editor_hint():
draw_arc(get_local_mouse_position(), brush_radius, 0, TAU, 32, Color.YELLOW, 2.0)

1
scripts/grass.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://b5o4ky21bvg66

11
scripts/health_bar.gd Normal file
View file

@ -0,0 +1,11 @@
extends TextureRect
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
EventBus.health_changed.connect(_on_health_changed)
pass # Replace with function body.
func _on_health_changed(new : int):
texture.region = Rect2(0, new * 32, 0, 32)

View file

@ -0,0 +1 @@
uid://cj6pb1828dcfr

20
scripts/hearts.gd Normal file
View file

@ -0,0 +1,20 @@
extends CPUParticles2D
@export var speed: float = 600.0
func _physics_process(delta: float) -> void:
position += transform.x * speed * delta
func set_from_player(val):
$Hitbox.from_player = val
func _on_hitbox_onhit() -> void:
$Hitbox.queue_free()
$Explosion.emitting = true
emitting = false
await get_tree().create_timer(1.2).timeout
queue_free()
func _on_timer_timeout() -> void:
queue_free()

1
scripts/hearts.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://dyyqm52cwjimy

35
scripts/hitbox.gd Normal file
View file

@ -0,0 +1,35 @@
class_name Hitbox extends Area2D
var from_player = true
signal onhit;
var hitplayer = false
#func _process(delta: float) -> void:
#EventBus.cut_grass_at.emit(global_position, 14)
func _ready() -> void:
body_entered.connect(_on_body_entered)
func _on_body_entered(body: Node) -> void:
print(body)
if body is Enemy and from_player:
var bname := str(body.name)
if body.has_method("hit"):
body.hit()
onhit.emit()
body.knockback()
EventBus.debug_print.emit("Hit: " + bname)
else:
EventBus.debug_print.emit("Hitbox touched " + bname + " but it lacks 'reduce_health' method.")
if body is Player and not from_player and not hitplayer:
var bname := str(body.name)
if body.has_method("hit"):
hitplayer = true
body.hit()
onhit.emit()
EventBus.debug_print.emit("Hit: " + bname)
else:
EventBus.debug_print.emit("Hitbox touched " + bname + " but it lacks 'reduce_health' method.")
EventBus.debug_print.emit(str(body))
if body is TileMapLayer:
onhit.emit()

1
scripts/hitbox.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://ejd25ul4j5pp

119
scripts/magic_circle.gd Normal file
View file

@ -0,0 +1,119 @@
@tool
class_name MagicCircle extends Node2D
@export var base_rad : float = 20;
@export var layer_rad_inc : float = 1.1;
@export var layers : int = 10
@export var vis_layers : float = 10.0
@export var main_color : Color = Color(1, 1, 1, 1)
@export var fixed_seed : int = 1234;
@export var squish : float = 0.7
@export var rot_speed : float = 0.1;
@export var line_width : float = 0.6
var runes : Texture2D = load("res://assets/runes.png");
var rot = 0;
var time = 0;
# Called when the node enters the scene tree for the first time.
func _ready():
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(_delta):
queue_redraw()
# absloute angle, NO rot
func _draw_arc(radius, start_angle, end_angle, color, segments=10):
var segment_size : float = (end_angle - start_angle) / segments
var last_pos = Vector2(cos(start_angle), sin(start_angle)) * radius
for i in range(segments+1):
var angle = start_angle + (i * segment_size)
var next_pos = Vector2(cos(angle), sin(angle)) * radius
draw_line(last_pos, next_pos, color, line_width)
last_pos = next_pos
# yes rot
func _draw_circle(radius, small_circles, small_radius, color, segments = 50):
if (small_circles == 0):
_draw_arc(radius, 0, 2*PI, color, segments)
return
var arc_segments = segments / small_circles
var arc_size = (2*PI) / small_circles
# arc_length = angle * radius
# angle = arc_length / radius
var small_circle_angle = (small_radius / radius)
for i in range(small_circles):
var small_angle = i * arc_size;
var start_angle = i * arc_size + small_circle_angle
var end_angle = (i + 1) * arc_size - small_circle_angle
_draw_arc(radius, start_angle, end_angle, color, arc_segments)
var circle_pos = Vector2(cos(small_angle), sin(small_angle)) * radius
draw_arc(circle_pos, small_radius, 0, 2*PI, 10, color, line_width)
_draw_rand_rune(circle_pos, color)
func _draw_text_circle(radius, rune_num, color, segments = 50):
_draw_circle(radius + 10, 0, 0, color, segments)
_draw_circle(radius - 10, 0, 0, color, segments)
var rune_diff = (2 * PI) / rune_num
for i in range(rune_num):
var angle = i * rune_diff
var pos = Vector2(cos(angle), sin(angle)) * radius
_draw_rand_rune(pos, color)
# yes rot
func _draw_polygon(radius, segments, skip, color):
var segment_angle = (2*PI) / segments
var last_pos = Vector2(1, 0) * radius
for i in range(segments +1):
var angle = (i * skip) * segment_angle
var next_pos = Vector2(cos(angle), sin(angle)) * radius
draw_line(last_pos, next_pos, color, line_width)
last_pos = next_pos
func _draw_rand_rune(draw_pos, color):
var pos = draw_pos
pos.x -= 5;#
pos.y -= 5
draw_texture_rect_region(runes, Rect2(pos.x, pos.y, 10, 10), Rect2(rand_r(0, 4) * 10, 0, 10, 10), color)
# inclusive
func rand_r(min_v, max_v):
return randi() % (max_v - min_v + 1) + min_v
func _draw():
seed(fixed_seed)
for i in range(1, layers):
var layer_type = randf();
var layer_counter = float(i) / layers # 0 - 1
var layer_inv_counter = 1 - layer_counter # 1 - 0
var layer_dir = -1 if rand_r(0, 1) else 1;
var layer_rot_mult = rand_r(1, 12) * rot_speed * layer_dir
var layer_rot = Time.get_ticks_msec() * layer_inv_counter * layer_rot_mult * 0.001;
var layer_radius = i * pow(base_rad, layer_rad_inc)
var layer_color = main_color
layer_color.a = clamp(vis_layers - i, 0, 1)
draw_set_transform(Vector2.ZERO, layer_rot, Vector2(1, squish))
if layer_type < 0.2:
var small_circles = rand_r(0, 5)
var small_radius = layer_radius * 0.1
_draw_circle(layer_radius, small_circles, small_radius, layer_color)
elif layer_type < 0.7:
var vertices = rand_r(5, 8)
var skip = rand_r(1, 2)
_draw_polygon(layer_radius, vertices, skip, layer_color)
elif layer_type <= 1:
var rune_num = round(4 + layer_counter * 32)
_draw_text_circle(layer_radius, rune_num, layer_color)

View file

@ -0,0 +1 @@
uid://pyblew2m6xc3

30
scripts/mask_bar.gd Normal file
View file

@ -0,0 +1,30 @@
extends TextureProgressBar
@export var threshold: float = 0.5
@export var shake_intensity: float = 5.0
var original_x: float
func _ready() -> void:
EventBus.mask_time_changed.connect(_on_time_changed)
original_x = position.x
func _process(_delta: float) -> void:
if value / max_value <= threshold:
apply_effects()
else:
position.x = original_x
modulate = Color.WHITE
func _on_time_changed(new: float) -> void:
value = new
func apply_effects() -> void:
var danger_factor = 1.0 - (value / (max_value * threshold))
danger_factor = clamp(danger_factor, 0.0, 1.0)
var flash = (sin(Time.get_ticks_msec() * 0.02) + 1.0) / 2.0
modulate = Color.WHITE * (1 + flash * danger_factor)
var shake = sin(Time.get_ticks_msec() * 0.05) * shake_intensity * danger_factor
position.x = original_x + shake

1
scripts/mask_bar.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://cqrqqn2p0h0kb

65
scripts/mask_drop.gd Normal file
View file

@ -0,0 +1,65 @@
class_name MaskDrop extends Node2D
@export var mask_type : Types.mask_types
@onready var popup := $Popup
@export var time_to_live = 14
@export var initial_blink_speed: float = 0.7
var alive_time = 0
var target_position : Vector2
@onready var blink_timer = $Timer
func _ready() -> void:
blink_timer.wait_time = initial_blink_speed
blink_timer.timeout.connect(_on_timeout)
blink_timer.start()
blink_timer.paused = true
$MeleeMask.rotate(randf() * PI - (PI / 2))
$RangedMask.rotate(randf() * PI - (PI / 2))
func _process(delta: float) -> void:
alive_time += delta
$MeleeMask.visible = false
$RangedMask.visible = false
$SpitMask.visible = false
if (mask_type == Types.mask_types.Melee):
$MeleeMask.visible = true
elif (mask_type == Types.mask_types.Ranged):
$RangedMask.visible = true
elif (mask_type == Types.mask_types.Spit):
$SpitMask.visible = true
var progress = alive_time / time_to_live
if progress > 0.5:
blink_timer.paused = false
blink_timer.wait_time = lerp(initial_blink_speed, 0.02, progress)
if alive_time >= time_to_live:
queue_free()
func show_popup():
var tween = create_tween()
tween.tween_property(popup, "modulate:a", 1, 0.2)
tween.parallel().tween_property(popup, "position:y", -23, 0.2)
func hide_popup():
var tween = create_tween()
tween.tween_property(popup, "modulate:a", 0, 0.2)
tween.parallel().tween_property(popup, "position:y", -16, 0.2)
func collect(target : Vector2):
target_position = target
var tween = create_tween()
tween.set_trans(Tween.TRANS_EXPO)
tween.tween_method(_lerp_to_target, 0.0, 1.0, 0.3)
func _lerp_to_target(progression:float):
global_position = lerp(global_position, target_position, progression)
if global_position.distance_to(target_position)<=10.0:
queue_free()
func _on_timeout():
visible = !visible

1
scripts/mask_drop.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://c576b1a6s5vw

39
scripts/mask_ui.gd Normal file
View file

@ -0,0 +1,39 @@
extends Label
var cur_mask : Types.mask_types
var uses : int
var health : int
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
EventBus.mask_changed.connect(_on_mask_changed)
EventBus.mask_uses_changed.connect(_on_uses_changed)
EventBus.health_changed.connect(_on_health_changed)
func _process(delta: float) -> void:
update_text()
func update_text():
var enemies = len(get_tree().get_nodes_in_group("enemy"))
text = "Mask: " + _get_mask_name(cur_mask) + "\n" + \
"Uses: " + str(uses) + "\n" + \
"Health: " + str(health) + "\n" + \
"Enemies: " + str(enemies)
func _get_mask_name(type : Types.mask_types):
if (type == 0): return "Melee"
if (type == 1): return "Ranged"
if (type == 2): return "Spit"
return ""
func _on_mask_changed(new : Types.mask_types):
cur_mask = new
update_text()
func _on_uses_changed(new : int):
uses = new
update_text()
func _on_health_changed(new : int):
health = new
update_text()

1
scripts/mask_ui.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://cdm0kcipvrxyo

25
scripts/portal.gd Normal file
View file

@ -0,0 +1,25 @@
extends Sprite2D
@export var nextScene : String
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
var enemies = len(get_tree().get_nodes_in_group("enemy"))
var player = get_tree().get_first_node_in_group("player")
if enemies == 0 and player:
material.set_shader_parameter("intensity", 1.7)
$MagicCircle.visible = true
$Text.visible = true
var player_dist = player.global_position.distance_to(global_position)
if Input.is_action_just_pressed("interact") && player_dist < 70:
SceneTransition.change_scene(nextScene)
else:
material.set_shader_parameter("intensity", 0)
$MagicCircle.visible = false
$Text.visible = false

1
scripts/portal.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://cioob0euvug4e

20
scripts/proximity.gd Normal file
View file

@ -0,0 +1,20 @@
extends Node2D
@export var range : int = 50
func _process(_delta: float) -> void:
if has_node("%Player"):
if %Player.global_position.distance_to(global_position) < range:
show_ins()
else:
hide_ins()
func show_ins():
var tween = create_tween()
tween.tween_property(self, "modulate:a", 1, 0.2)
tween.parallel().tween_property(self, "position:y", -23, 0.2)
func hide_ins():
var tween = create_tween()
tween.tween_property(self, "modulate:a", 0, 0.2)
tween.parallel().tween_property(self, "position:y", -16, 0.2)

1
scripts/proximity.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://c0wn3w7q2j470

6
scripts/ranged_enemy.gd Normal file
View file

@ -0,0 +1,6 @@
extends Enemy
func _draw():
if charge > 0.1:
var color = Color.RED.lerp(Color.WHITE, charge / charge_time)
draw_dashed_line(Vector2.ZERO, to_local(player.global_position), color, 1, 5)

View file

@ -0,0 +1 @@
uid://buamdls133c2e

8
scripts/scithe_attack.gd Normal file
View file

@ -0,0 +1,8 @@
extends Node2D
func set_from_player(val):
$Anchor/Scithe/Hitbox.from_player = val
func _on_animation_player_animation_finished(_anim_name: StringName) -> void:
queue_free()

View file

@ -0,0 +1 @@
uid://c1anp8np0aw0

51
scripts/settings_menu.gd Normal file
View file

@ -0,0 +1,51 @@
extends NinePatchRect
@export var volume_slider : HSlider
@export var fullscreen_check : CheckBox
@export var vsync_check : CheckBox
func _ready():
_update_settings()
Settings.apply_all()
func _update_settings():
volume_slider.value = Settings.master_volume
fullscreen_check.button_pressed = Settings.fullscreen
vsync_check.button_pressed = Settings.vsync
func _input(event: InputEvent) -> void:
_update_settings()
if (event.is_action_pressed("ui_cancel")):
get_viewport().set_input_as_handled()
visible = !visible
get_tree().paused = visible
elif (event.is_action_pressed("fullscreen")):
get_viewport().set_input_as_handled()
Settings.fullscreen = !Settings.fullscreen
Settings.apply_fullscreen()
func _on_volume_changed(value: float):
Settings.master_volume = value
Settings.apply_volume()
func _on_fullscreen_toggled(enabled: bool):
Settings.fullscreen = enabled
Settings.apply_fullscreen()
func _on_max_fps_changed(value: float):
Settings.max_fps = int(value)
Settings.apply_max_fps()
func _on_vsync_toggled(enabled : bool):
Settings.vsync = enabled
Settings.apply_vsync()
func _on_exit_button_pressed() -> void:
get_tree().quit()
func _on_continue_button_pressed() -> void:
visible = false
get_tree().paused = visible

View file

@ -0,0 +1 @@
uid://c62xd44e27oki

10
scripts/shadow.gd Normal file
View file

@ -0,0 +1,10 @@
@tool
class_name Shadow extends Node2D
@export var shadow_color: Color = Color(0, 0, 0, 0.3) # Semi-transparent black
@export var shadow_size: float = 12
func _draw():
draw_set_transform(Vector2.ZERO, 0, Vector2(1, 0.7))
draw_circle(Vector2.ZERO, shadow_size, shadow_color, true)

1
scripts/shadow.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://synocbtvgrf4

View file

@ -0,0 +1,12 @@
extends Node
signal dialogue_requested(text: String)
signal dialogue_finished()
signal debug_print(text : String)
signal mask_changed(new : Types.mask_types)
signal mask_uses_changed(new : int)
signal health_changed(new : int)
signal player_dmg()
signal screenshake(intensity : float)
signal mask_time_changed(time : float)
signal cut_grass_at(p, r)

View file

@ -0,0 +1 @@
uid://b31uw84k0w7x3

View file

@ -0,0 +1,71 @@
extends CanvasLayer
@onready var rect = $ColorRect
@onready var log_display = $RichTextLabel
func _ready():
rect.modulate.a = 0
log_display.text = ""
log_display.visible = false
# Ensure the rect doesn't block clicks while invisible
rect.mouse_filter = Control.MOUSE_FILTER_IGNORE
## Combined function to handle both success and error transitions
func change_scene(target_path: String, is_error: bool = false):
rect.mouse_filter = Control.MOUSE_FILTER_STOP
# Set color based on status
if is_error:
rect.color = Color.CRIMSON
else:
rect.color = Color.from_string("#364388", Color.BLUE)
# Fade In
var tween_in = create_tween()
await tween_in.tween_property(rect, "modulate:a", 1.0, 0.3).finished
# Display Log
log_display.visible = true
var log_file = "res://hex.txt" if is_error else "res://log.txt"
await display_log_file(log_file)
await get_tree().create_timer(0.5).timeout
# Change Scene
var error = get_tree().change_scene_to_file(target_path)
if error != OK:
push_error("Failed to load scene: " + target_path)
# Cleanup and Fade Out
log_display.visible = false
log_display.text = ""
var tween_out = create_tween()
await tween_out.tween_property(rect, "modulate:a", 0.0, 0.5).finished
rect.mouse_filter = Control.MOUSE_FILTER_IGNORE
func display_log_file(file_path: String):
if not FileAccess.file_exists(file_path):
log_display.add_text("ERROR: " + file_path + " not found in build.\n")
# Check Project -> Export -> Resources -> Filters to include non-resource files
return
var file = FileAccess.open(file_path, FileAccess.READ)
if not file:
log_display.add_text("ERROR: Could not open file.\n")
return
# Reading line by line for that "hacker" effect
while not file.eof_reached():
var line = file.get_line()
if line.strip_edges() == "": continue # Skip empty lines if desired
log_display.add_text(line + "\n")
# Auto-scroll to bottom
log_display.scroll_to_line(log_display.get_line_count())
var delay = 0#randf_range(0.0001, 0.00015)
#if randf() > 0.95:
#delay += 0.2 # Random "loading" hitch for realism
await get_tree().create_timer(delay).timeout

View file

@ -0,0 +1 @@
uid://dktk18oihl6xf

View file

@ -0,0 +1,36 @@
extends Node
var master_volume: float = 1.0
var fullscreen: bool = false
var vsync : bool = false
var max_fps: int = 60
func apply_volume():
var bus_index := AudioServer.get_bus_index("Master")
AudioServer.set_bus_volume_db(
bus_index,
linear_to_db(master_volume)
)
func apply_fullscreen():
DisplayServer.window_set_mode(
DisplayServer.WINDOW_MODE_FULLSCREEN if fullscreen
else DisplayServer.WINDOW_MODE_WINDOWED
)
if not fullscreen:
get_window().size = Vector2i(480*3, 270*3);
func apply_vsync():
DisplayServer.window_set_vsync_mode(
DisplayServer.VSYNC_ENABLED if vsync
else DisplayServer.VSYNC_DISABLED
)
func apply_max_fps():
Engine.max_fps = max_fps
func apply_all():
apply_volume()
apply_fullscreen()
apply_vsync()
apply_max_fps()

View file

@ -0,0 +1 @@
uid://cyoitmcbh2sw5

View file

@ -0,0 +1,72 @@
extends Node
const SFX_DEATH = preload("res://assets/sfx/death.mp3")
const SFX_HEART = preload("res://assets/sfx/heart.mp3")
const SFX_SWING = preload("res://assets/sfx/swing.mp3")
const SFX_CLICK = preload("res://assets/sfx/click.mp3")
const MUSIC_TITLE = preload("res://assets/music/title.wav")
# Configuration
var pool_size = 10
var sfx_dict = {}
var music_dict = {}
var sfx_pool = []
var current_pool_index = 0
@onready var music_player_1 := AudioStreamPlayer.new()
@onready var music_player_2 := AudioStreamPlayer.new()
func _ready():
process_mode = Node.PROCESS_MODE_ALWAYS
sfx_dict = {
"death": SFX_DEATH,
"heart": SFX_HEART,
"swing": SFX_SWING,
"click": SFX_CLICK
}
music_dict = {
"title": MUSIC_TITLE
}
for i in range(pool_size):
var asp = AudioStreamPlayer.new()
asp.bus = "SFX"
add_child(asp)
sfx_pool.append(asp)
add_child(music_player_1)
add_child(music_player_2)
music_player_1.bus = "Music"
music_player_2.bus = "Music"
func play_sfx(sound_name: String):
if sfx_dict.has(sound_name):
var asp : AudioStreamPlayer = sfx_pool[current_pool_index]
asp.stream = sfx_dict[sound_name]
asp.play()
current_pool_index = (current_pool_index + 1) % pool_size
func play_music(music_name: String, fade_duration: float = 1.0):
if not music_dict.has(music_name): return
var next_track = music_dict[music_name]
var active = music_player_1 if music_player_1.playing else music_player_2
var idle = music_player_2 if music_player_1.playing else music_player_1
if active.stream == next_track: return
idle.stream = next_track
idle.volume_db = -80
idle.play()
var tween = create_tween().set_parallel(true)
tween.tween_property(active, "volume_db", -80, fade_duration)
tween.tween_property(idle, "volume_db", 0, fade_duration)
await tween.finished
active.stop()

View file

@ -0,0 +1 @@
uid://buunplrqixmky

View file

@ -0,0 +1,7 @@
extends Node
enum mask_types {
Melee,
Ranged,
Spit
}

View file

@ -0,0 +1 @@
uid://ixakrcerxwlj

View file

@ -0,0 +1,3 @@
extends Node
var done_tuto = false

View file

@ -0,0 +1 @@
uid://csi5hjjkyps56

9
scripts/slash.gd Normal file
View file

@ -0,0 +1,9 @@
extends AnimatedSprite2D
var from_player := false
func _process(delta: float) -> void:
$Hitbox.from_player = from_player
func _on_animation_finished() -> void:
queue_free()

1
scripts/slash.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://8it3jqarby3f

45
scripts/spit.gd Normal file
View file

@ -0,0 +1,45 @@
extends Node2D
@export var speed: float = 400.0
@export var max_height: float = 100.0
@export var payload : PackedScene
var target_position: Vector2
@onready var sprite = $Bomb
var start_position: Vector2
var distance_total: float
var distance_covered: float = 0.0
var from_player = true
func set_from_player(value):
from_player = value
func _ready():
start_position = global_position
distance_total = 150
func _process(delta):
if distance_covered < distance_total:
distance_covered += speed * delta
var t = clamp(distance_covered / distance_total, 0.0, 1.0)
global_position = start_position.lerp(target_position, t)
var arc_y = 4 * max_height * t * (1 - t)
sprite.position.y = -arc_y
else:
global_position = target_position
sprite.position.y = 0
set_process(false)
on_arrival()
func on_arrival():
EventBus.screenshake.emit(1)
var ins = payload.instantiate()
ins.set_from_player(from_player)
ins.global_position = global_position
get_parent().add_child(ins)
queue_free()

1
scripts/spit.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://ctninfm8hkbp2

14
scripts/spit_dragon.gd Normal file
View file

@ -0,0 +1,14 @@
extends Enemy
func _draw():
if charge > 0.1:
var color = Color.RED.lerp(Color.WHITE, charge / charge_time)
if player:
draw_dashed_line(Vector2.ZERO, to_local(player.global_position), color, 1, 5)
func _process(delta: float) -> void:
super._process(delta)
if (charge > 0.1):
$Anim.play("attack")
else:
$Anim.play("default")

View file

@ -0,0 +1 @@
uid://bpabecs0hf676

10
scripts/spit_payload.gd Normal file
View file

@ -0,0 +1,10 @@
extends CPUParticles2D
func _ready() -> void:
emitting = true
func set_from_player(value):
$Hitbox.from_player = value
func _on_finished() -> void:
queue_free()

View file

@ -0,0 +1 @@
uid://8bsq7kvqky3d

15
scripts/world_logic.gd Normal file
View file

@ -0,0 +1,15 @@
extends Node2D
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
var player = get_tree().get_first_node_in_group("player")
if player:
if player.global_position.distance_to(global_position) > 600:
player.hit()
pass

View file

@ -0,0 +1 @@
uid://dihbkd2fiwb8b