Upload folder using huggingface_hub (part 14)
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- kit_on_video_on/hazard_circuit/submission/game/scripts/moving_platform.gd +31 -0
- kit_on_video_on/hazard_circuit/submission/game/scripts/moving_platform.gd.uid +1 -0
- kit_on_video_on/hazard_circuit/submission/game/scripts/player.gd +149 -0
- kit_on_video_on/hazard_circuit/submission/game/scripts/player.gd.uid +1 -0
- kit_on_video_on/hazard_circuit/submission/game/scripts/rolling_saw.gd +47 -0
- kit_on_video_on/hazard_circuit/submission/game/scripts/rolling_saw.gd.uid +1 -0
- kit_on_video_on/hazard_circuit/submission/game/scripts/spike.gd +17 -0
- kit_on_video_on/hazard_circuit/submission/game/scripts/spike.gd.uid +1 -0
- kit_on_video_on/hazard_circuit/submission/ops.json +14 -0
- kit_on_video_on/hurry_curry/metadata.json +12 -0
- kit_on_video_on/hurry_curry/submission/GDD.md +279 -0
- kit_on_video_on/hurry_curry/submission/demos.json +179 -0
- kit_on_video_on/hurry_curry/submission/game/gb_levels.json +12 -0
- kit_on_video_on/hurry_curry/submission/game/icon.svg +1 -0
- kit_on_video_on/hurry_curry/submission/game/project.godot +70 -0
- kit_on_video_on/hurry_curry/submission/game/scenes/defeat.tscn +7 -0
- kit_on_video_on/hurry_curry/submission/game/scenes/kitchen.tscn +11 -0
- kit_on_video_on/hurry_curry/submission/game/scenes/victory.tscn +7 -0
- kit_on_video_on/hurry_curry/submission/game/scripts/ending.gd +31 -0
- kit_on_video_on/hurry_curry/submission/game/scripts/game_state.gd +64 -0
- kit_on_video_on/hurry_curry/submission/game/scripts/kitchen.gd +335 -0
- kit_on_video_on/hurry_curry/submission/game/scripts/player.gd +97 -0
- kit_on_video_on/hurry_curry/submission/ops.json +58 -0
- kit_on_video_on/mode_manifest.json +22 -0
- kit_on_video_on/ninja_roguelite/metadata.json +12 -0
- kit_on_video_on/ninja_roguelite/submission/GDD.md +143 -0
- kit_on_video_on/ninja_roguelite/submission/demos.json +64 -0
- kit_on_video_on/ninja_roguelite/submission/game/assets/characters/ninja_blue.png +0 -0
- kit_on_video_on/ninja_roguelite/submission/game/assets/characters/ninja_blue.png.import +40 -0
- kit_on_video_on/ninja_roguelite/submission/game/assets/characters/pig.png +0 -0
- kit_on_video_on/ninja_roguelite/submission/game/assets/characters/pig.png.import +40 -0
- kit_on_video_on/ninja_roguelite/submission/game/assets/characters/samurai_blue.png +0 -0
- kit_on_video_on/ninja_roguelite/submission/game/assets/characters/samurai_blue.png.import +40 -0
- kit_on_video_on/ninja_roguelite/submission/game/assets/fx/leaf.png +0 -0
- kit_on_video_on/ninja_roguelite/submission/game/assets/fx/leaf.png.import +40 -0
- kit_on_video_on/ninja_roguelite/submission/game/assets/fx/particle_pot.png +0 -0
- kit_on_video_on/ninja_roguelite/submission/game/assets/fx/particle_pot.png.import +40 -0
- kit_on_video_on/ninja_roguelite/submission/game/assets/fx/particle_wood.png +0 -0
- kit_on_video_on/ninja_roguelite/submission/game/assets/fx/particle_wood.png.import +40 -0
- kit_on_video_on/ninja_roguelite/submission/game/assets/fx/raylight.png +0 -0
- kit_on_video_on/ninja_roguelite/submission/game/assets/fx/raylight.png.import +40 -0
- kit_on_video_on/ninja_roguelite/submission/game/assets/ui/heart.png +0 -0
- kit_on_video_on/ninja_roguelite/submission/game/assets/ui/heart.png.import +40 -0
- kit_on_video_on/ninja_roguelite/submission/game/assets/ui/life_bar.png +0 -0
- kit_on_video_on/ninja_roguelite/submission/game/assets/ui/life_bar.png.import +40 -0
- kit_on_video_on/ninja_roguelite/submission/game/assets/ui/life_bar_bg.png +0 -0
- kit_on_video_on/ninja_roguelite/submission/game/assets/ui/life_bar_bg.png.import +40 -0
- kit_on_video_on/ninja_roguelite/submission/game/enemy.gd +198 -0
- kit_on_video_on/ninja_roguelite/submission/game/enemy.gd.uid +1 -0
- kit_on_video_on/ninja_roguelite/submission/game/enemy.tscn +73 -0
kit_on_video_on/hazard_circuit/submission/game/scripts/moving_platform.gd
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
extends AnimatableBody2D
|
| 2 |
+
|
| 3 |
+
# Moving platform. Follows a horizontal path with constant back-and-forth
|
| 4 |
+
# motion. The player rides it correctly because AnimatableBody2D pushes
|
| 5 |
+
# the kinematic CharacterBody2D via move_and_slide.
|
| 6 |
+
|
| 7 |
+
@export var distance: float = 160.0
|
| 8 |
+
@export var speed: float = 80.0
|
| 9 |
+
@export var vertical: bool = false
|
| 10 |
+
|
| 11 |
+
var _origin: Vector2
|
| 12 |
+
var _t: float = 0.0
|
| 13 |
+
var _dir: float = 1.0
|
| 14 |
+
var _range: float = distance
|
| 15 |
+
var _phase: float = 0.0
|
| 16 |
+
|
| 17 |
+
func _ready() -> void:
|
| 18 |
+
_origin = position
|
| 19 |
+
|
| 20 |
+
func _physics_process(delta: float) -> void:
|
| 21 |
+
_phase += delta * speed * _dir
|
| 22 |
+
if _phase >= _range:
|
| 23 |
+
_phase = _range
|
| 24 |
+
_dir = -1.0
|
| 25 |
+
elif _phase <= 0.0:
|
| 26 |
+
_phase = 0.0
|
| 27 |
+
_dir = 1.0
|
| 28 |
+
if vertical:
|
| 29 |
+
position.y = _origin.y - _phase
|
| 30 |
+
else:
|
| 31 |
+
position.x = _origin.x + _phase
|
kit_on_video_on/hazard_circuit/submission/game/scripts/moving_platform.gd.uid
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
uid://c8swr6lbbg445
|
kit_on_video_on/hazard_circuit/submission/game/scripts/player.gd
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
extends CharacterBody2D
|
| 2 |
+
|
| 3 |
+
const SPEED := 240.0
|
| 4 |
+
const JUMP_VELOCITY := -460.0
|
| 5 |
+
const GRAVITY := 1400.0
|
| 6 |
+
const MAX_FALL := 900.0
|
| 7 |
+
const COYOTE_FRAMES := 6
|
| 8 |
+
const JUMP_BUFFER_FRAMES := 6
|
| 9 |
+
const HURT_IFRAMES := 36
|
| 10 |
+
|
| 11 |
+
signal died
|
| 12 |
+
signal hit_hazard
|
| 13 |
+
signal coin_changed(value)
|
| 14 |
+
signal health_changed(value)
|
| 15 |
+
signal respawned(at_position)
|
| 16 |
+
|
| 17 |
+
@export var spawn_position: Vector2 = Vector2(64, 400)
|
| 18 |
+
@export var max_health: int = 3
|
| 19 |
+
|
| 20 |
+
var coins: int = 0
|
| 21 |
+
var health: int = 3:
|
| 22 |
+
set(v):
|
| 23 |
+
health = v
|
| 24 |
+
emit_signal("health_changed", health)
|
| 25 |
+
|
| 26 |
+
var _coyote := 0
|
| 27 |
+
var _jump_buffer := 0
|
| 28 |
+
var _iframes := 0
|
| 29 |
+
var _was_on_floor := false
|
| 30 |
+
var _checkpoint_pos: Vector2 = Vector2.ZERO
|
| 31 |
+
var _facing := 1
|
| 32 |
+
var _dead := false
|
| 33 |
+
|
| 34 |
+
func _ready() -> void:
|
| 35 |
+
add_to_group("gb_player")
|
| 36 |
+
velocity = Vector2.ZERO
|
| 37 |
+
if _checkpoint_pos == Vector2.ZERO:
|
| 38 |
+
_checkpoint_pos = position if position != Vector2.ZERO else spawn_position
|
| 39 |
+
CheckpointBus.register(self)
|
| 40 |
+
|
| 41 |
+
func reset_for_replay() -> void:
|
| 42 |
+
position = _checkpoint_pos
|
| 43 |
+
velocity = Vector2.ZERO
|
| 44 |
+
_coins_reset_to(0)
|
| 45 |
+
health = max_health
|
| 46 |
+
_dead = false
|
| 47 |
+
_iframes = 0
|
| 48 |
+
emit_signal("respawned", _checkpoint_pos)
|
| 49 |
+
|
| 50 |
+
func _coins_reset_to(value: int) -> void:
|
| 51 |
+
coins = value
|
| 52 |
+
emit_signal("coin_changed", coins)
|
| 53 |
+
|
| 54 |
+
func respawn() -> void:
|
| 55 |
+
position = _checkpoint_pos
|
| 56 |
+
velocity = Vector2.ZERO
|
| 57 |
+
_dead = false
|
| 58 |
+
_iframes = HURT_IFRAMES
|
| 59 |
+
health = max_health
|
| 60 |
+
emit_signal("respawned", _checkpoint_pos)
|
| 61 |
+
|
| 62 |
+
func take_damage(amount: int = 1) -> void:
|
| 63 |
+
if _dead or _iframes > 0:
|
| 64 |
+
return
|
| 65 |
+
health -= amount
|
| 66 |
+
_iframes = HURT_IFRAMES
|
| 67 |
+
emit_signal("hit_hazard")
|
| 68 |
+
if health <= 0:
|
| 69 |
+
_die()
|
| 70 |
+
|
| 71 |
+
func _die() -> void:
|
| 72 |
+
_dead = true
|
| 73 |
+
velocity = Vector2(-_facing * 60, -260)
|
| 74 |
+
emit_signal("died")
|
| 75 |
+
respawn()
|
| 76 |
+
|
| 77 |
+
func set_checkpoint(pos: Vector2) -> void:
|
| 78 |
+
_checkpoint_pos = pos
|
| 79 |
+
|
| 80 |
+
func get_checkpoint() -> Vector2:
|
| 81 |
+
return _checkpoint_pos
|
| 82 |
+
|
| 83 |
+
func add_coin(amount: int = 1) -> void:
|
| 84 |
+
coins += amount
|
| 85 |
+
emit_signal("coin_changed", coins)
|
| 86 |
+
|
| 87 |
+
func _physics_process(_delta: float) -> void:
|
| 88 |
+
if _iframes > 0:
|
| 89 |
+
_iframes -= 1
|
| 90 |
+
|
| 91 |
+
# Sprite flip based on facing direction
|
| 92 |
+
var sprite := get_node_or_null("Sprite")
|
| 93 |
+
if sprite and _facing != 0:
|
| 94 |
+
sprite.scale.x = -0.9 if _facing < 0 else 0.9
|
| 95 |
+
|
| 96 |
+
if _dead:
|
| 97 |
+
velocity.y += GRAVITY * _delta
|
| 98 |
+
velocity.y = min(velocity.y, MAX_FALL)
|
| 99 |
+
move_and_slide()
|
| 100 |
+
_was_on_floor = is_on_floor()
|
| 101 |
+
return
|
| 102 |
+
|
| 103 |
+
var on_floor_now := is_on_floor()
|
| 104 |
+
if on_floor_now:
|
| 105 |
+
_coyote = COYOTE_FRAMES
|
| 106 |
+
else:
|
| 107 |
+
_coyote = max(0, _coyote - 1)
|
| 108 |
+
|
| 109 |
+
var left := Input.is_action_pressed("gb_left")
|
| 110 |
+
var right := Input.is_action_pressed("gb_right")
|
| 111 |
+
var up := Input.is_action_pressed("gb_up")
|
| 112 |
+
var down := Input.is_action_pressed("gb_down")
|
| 113 |
+
var action_held := Input.is_action_pressed("gb_action")
|
| 114 |
+
var pause_held := Input.is_action_pressed("gb_pause")
|
| 115 |
+
var reset_held := Input.is_action_pressed("gb_reset")
|
| 116 |
+
var jump_held := Input.is_action_pressed("gb_jump")
|
| 117 |
+
var jump_pressed := Input.is_action_just_pressed("gb_jump")
|
| 118 |
+
if up or down or action_held or pause_held or reset_held:
|
| 119 |
+
pass
|
| 120 |
+
|
| 121 |
+
if jump_pressed:
|
| 122 |
+
_jump_buffer = JUMP_BUFFER_FRAMES
|
| 123 |
+
else:
|
| 124 |
+
_jump_buffer = max(0, _jump_buffer - 1)
|
| 125 |
+
|
| 126 |
+
var dir := 0
|
| 127 |
+
if right and not left:
|
| 128 |
+
dir = 1
|
| 129 |
+
elif left and not right:
|
| 130 |
+
dir = -1
|
| 131 |
+
_facing = dir if dir != 0 else _facing
|
| 132 |
+
|
| 133 |
+
var target_vx := float(dir) * SPEED
|
| 134 |
+
velocity.x = target_vx
|
| 135 |
+
|
| 136 |
+
if not on_floor_now:
|
| 137 |
+
velocity.y += GRAVITY * _delta
|
| 138 |
+
velocity.y = min(velocity.y, MAX_FALL)
|
| 139 |
+
|
| 140 |
+
if _jump_buffer > 0 and _coyote > 0:
|
| 141 |
+
velocity.y = JUMP_VELOCITY
|
| 142 |
+
_coyote = 0
|
| 143 |
+
_jump_buffer = 0
|
| 144 |
+
|
| 145 |
+
if jump_held and velocity.y < 0:
|
| 146 |
+
velocity.y += GRAVITY * _delta * 0.4
|
| 147 |
+
|
| 148 |
+
move_and_slide()
|
| 149 |
+
_was_on_floor = on_floor_now
|
kit_on_video_on/hazard_circuit/submission/game/scripts/player.gd.uid
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
uid://oonjgjlhbeh2
|
kit_on_video_on/hazard_circuit/submission/game/scripts/rolling_saw.gd
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
extends Area2D
|
| 2 |
+
|
| 3 |
+
# Rolling saw hazard. It moves horizontally (defined by max_distance / origin
|
| 4 |
+
# when no PathFollow2D is supplied), spinning visually.
|
| 5 |
+
|
| 6 |
+
@export var damage: int = 1
|
| 7 |
+
@export var speed: float = 220.0
|
| 8 |
+
@export var follow_path: PathFollow2D = null
|
| 9 |
+
|
| 10 |
+
var _direction := 1.0
|
| 11 |
+
var _distance := 0.0
|
| 12 |
+
@export var _max_distance := 320.0
|
| 13 |
+
@export var _origin_x := 0.0
|
| 14 |
+
var _rot := 0.0
|
| 15 |
+
|
| 16 |
+
func _ready() -> void:
|
| 17 |
+
add_to_group("gb_hazard")
|
| 18 |
+
monitoring = true
|
| 19 |
+
monitorable = true
|
| 20 |
+
collision_layer = 4
|
| 21 |
+
collision_mask = 2
|
| 22 |
+
body_entered.connect(_on_body_entered)
|
| 23 |
+
if follow_path == null:
|
| 24 |
+
_origin_x = position.x
|
| 25 |
+
|
| 26 |
+
func _physics_process(delta: float) -> void:
|
| 27 |
+
if follow_path != null:
|
| 28 |
+
follow_path.progress += speed * delta * _direction
|
| 29 |
+
position = follow_path.global_position
|
| 30 |
+
if follow_path.progress_ratio >= 1.0:
|
| 31 |
+
_direction = -1.0
|
| 32 |
+
follow_path.progress_ratio = 1.0
|
| 33 |
+
elif follow_path.progress_ratio <= 0.0:
|
| 34 |
+
_direction = 1.0
|
| 35 |
+
follow_path.progress_ratio = 0.0
|
| 36 |
+
else:
|
| 37 |
+
_distance += speed * delta * _direction
|
| 38 |
+
if abs(_distance) > _max_distance:
|
| 39 |
+
_direction = -_direction
|
| 40 |
+
_distance += speed * delta * _direction * 2
|
| 41 |
+
position.x = _origin_x + _distance
|
| 42 |
+
_rot += delta * 8.0
|
| 43 |
+
rotation = _rot
|
| 44 |
+
|
| 45 |
+
func _on_body_entered(body: Node) -> void:
|
| 46 |
+
if body.is_in_group("gb_player") and body.has_method("take_damage"):
|
| 47 |
+
body.take_damage(damage)
|
kit_on_video_on/hazard_circuit/submission/game/scripts/rolling_saw.gd.uid
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
uid://c3xaqsgjxb5lq
|
kit_on_video_on/hazard_circuit/submission/game/scripts/spike.gd
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
extends Area2D
|
| 2 |
+
|
| 3 |
+
# Static spike: damages the player on contact.
|
| 4 |
+
|
| 5 |
+
@export var damage: int = 1
|
| 6 |
+
|
| 7 |
+
func _ready() -> void:
|
| 8 |
+
add_to_group("gb_hazard")
|
| 9 |
+
monitoring = true
|
| 10 |
+
monitorable = true
|
| 11 |
+
collision_layer = 4 # hazard layer
|
| 12 |
+
collision_mask = 2 # detect player (layer 2)
|
| 13 |
+
body_entered.connect(_on_body_entered)
|
| 14 |
+
|
| 15 |
+
func _on_body_entered(body: Node) -> void:
|
| 16 |
+
if body.is_in_group("gb_player") and body.has_method("take_damage"):
|
| 17 |
+
body.take_damage(damage)
|
kit_on_video_on/hazard_circuit/submission/game/scripts/spike.gd.uid
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
uid://bqlcjmw6dm417
|
kit_on_video_on/hazard_circuit/submission/ops.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{"ops": [
|
| 2 |
+
{"op": "hold", "action": "gb_right", "frames": 60},
|
| 3 |
+
{"op": "tap", "action": "gb_jump", "frames": 6},
|
| 4 |
+
{"op": "hold", "action": "gb_right", "frames": 250},
|
| 5 |
+
{"op": "tap", "action": "gb_jump", "frames": 6},
|
| 6 |
+
{"op": "hold", "action": "gb_right", "frames": 250},
|
| 7 |
+
{"op": "tap", "action": "gb_jump", "frames": 6},
|
| 8 |
+
{"op": "hold", "action": "gb_right", "frames": 250},
|
| 9 |
+
{"op": "tap", "action": "gb_jump", "frames": 6},
|
| 10 |
+
{"op": "hold", "action": "gb_right", "frames": 250},
|
| 11 |
+
{"op": "tap", "action": "gb_jump", "frames": 6},
|
| 12 |
+
{"op": "hold", "action": "gb_right", "frames": 300},
|
| 13 |
+
{"op": "noop", "frames": 90}
|
| 14 |
+
]}
|
kit_on_video_on/hurry_curry/metadata.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"game": "hurry_curry",
|
| 3 |
+
"mode": "brief",
|
| 4 |
+
"case_id": "",
|
| 5 |
+
"status": "completed",
|
| 6 |
+
"phase": "completed",
|
| 7 |
+
"agent_exit_code": 0,
|
| 8 |
+
"model": "minimax-m3",
|
| 9 |
+
"source_cell": "/root/gb-results/minimax_ablation_gap_20260924/ablation_kit_on_video_on_k2_lane1/cells/hurry_curry__brief__claude__minimax-m3",
|
| 10 |
+
"reference_video_requested": true,
|
| 11 |
+
"materialization": "copy_verified_excluding_.godot"
|
| 12 |
+
}
|
kit_on_video_on/hurry_curry/submission/GDD.md
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hurry Curry — Game Design Document
|
| 2 |
+
|
| 3 |
+
## Target experience
|
| 4 |
+
|
| 5 |
+
The player steps behind the counter of a compact cartoon kitchen and races
|
| 6 |
+
against a service timer to fill three ticket orders. Each ticket requires
|
| 7 |
+
picking up the right ingredients from the pantry, prepping them at the
|
| 8 |
+
appropriate stations (chopping board, frying pan, plate-up counter), and
|
| 9 |
+
walking the finished dish out to the serving hatch before the customer's
|
| 10 |
+
patience bar empties. Completing all three tickets before the global timer
|
| 11 |
+
runs out triggers a victory screen. Service ends in a defeat when patience
|
| 12 |
+
runs out on every order or the global service timer reaches zero with fewer
|
| 13 |
+
than three tickets filled.
|
| 14 |
+
|
| 15 |
+
## Design pillars
|
| 16 |
+
|
| 17 |
+
1. **Top-down clarity over 3D realism.** A 2D top-down view of the kitchen
|
| 18 |
+
keeps every station, ingredient, and order visible at a glance.
|
| 19 |
+
2. **Honest order loop.** Every ticket has a visible recipe (icon row),
|
| 20 |
+
patience bar, and reward. Fulfilling an order visibly empties the ticket
|
| 21 |
+
and adds to the score.
|
| 22 |
+
3. **Tight, readable feedback.** Movement is snappy; pickup, chop, fry, and
|
| 23 |
+
plate actions all produce visible scene changes (held ingredient sprite,
|
| 24 |
+
chopped icon, sizzling pan, plated dish) and a brief audio cue.
|
| 25 |
+
4. **Forgiving failure.** Failed tickets despawn cleanly, the player keeps
|
| 26 |
+
the kitchen layout, and global time keeps ticking. There is no death.
|
| 27 |
+
5. **Short, complete service.** A run is 120 seconds long with three
|
| 28 |
+
tickets. One service, one full kitchen.
|
| 29 |
+
|
| 30 |
+
## Core loop
|
| 31 |
+
|
| 32 |
+
1. A ticket appears at the serving hatch with a recipe (1–2 ingredients)
|
| 33 |
+
and a patience timer.
|
| 34 |
+
2. The player walks to the pantry, picks up the first listed ingredient
|
| 35 |
+
(held as a sprite above the player's head).
|
| 36 |
+
3. The player walks to the matching prep station (chopping board for
|
| 37 |
+
raw ingredients, frying pan for meat, plate-up counter to finish) and
|
| 38 |
+
presses the action button to transform the held item.
|
| 39 |
+
4. The player walks the finished item to the serving hatch. If the held
|
| 40 |
+
item matches the active ticket's recipe, the ticket is fulfilled, score
|
| 41 |
+
increases, and the patience bar is removed.
|
| 42 |
+
5. Loop until three tickets are served, the global timer expires, or the
|
| 43 |
+
player fails to meet any active ticket in time.
|
| 44 |
+
|
| 45 |
+
## Scope
|
| 46 |
+
|
| 47 |
+
- **One level**: a single top-down kitchen scene with pantry, two prep
|
| 48 |
+
stations, plate-up counter, serving hatch, and a small dining decor.
|
| 49 |
+
- **Three recipes**: salad (lettuce only), steak (meat only), and chef's
|
| 50 |
+
special (lettuce + meat). Tickets are drawn from these three recipes.
|
| 51 |
+
- **Three orders per service**: the run ends with a victory after three
|
| 52 |
+
served tickets.
|
| 53 |
+
- **Service timer**: 120 seconds; on zero with fewer than three served
|
| 54 |
+
tickets the run ends in defeat.
|
| 55 |
+
- **Patience timer**: each ticket has 45 seconds of patience. Patience
|
| 56 |
+
expiring on the only active ticket yields a failure and the ticket is
|
| 57 |
+
removed.
|
| 58 |
+
- **Score**: +5 per served ticket.
|
| 59 |
+
- **Failure recovery**: failed tickets despawn; no game-over until the
|
| 60 |
+
global timer expires or all three tickets are served.
|
| 61 |
+
- **Restart**: pressing the reset action (R) during play resets the
|
| 62 |
+
service and tickets.
|
| 63 |
+
|
| 64 |
+
## Non-goals
|
| 65 |
+
|
| 66 |
+
- No multiplayer, no save slots, no settings menu.
|
| 67 |
+
- No recipe book beyond the three fixed recipes.
|
| 68 |
+
- No character customization.
|
| 69 |
+
- No procedurally generated layout; the kitchen is fixed.
|
| 70 |
+
- No high-resolution 3D rendering; the visual style is intentionally
|
| 71 |
+
flat-shaded 2D for clarity and fast iteration.
|
| 72 |
+
|
| 73 |
+
## Creative freedom
|
| 74 |
+
|
| 75 |
+
We chose:
|
| 76 |
+
|
| 77 |
+
- **Visual style**: flat-shaded 2D with crisp pastel colours. The pantry,
|
| 78 |
+
stations, hatch, and player are drawn from solid-color rectangles with
|
| 79 |
+
simple iconography so the gameplay state is legible at every moment.
|
| 80 |
+
- **Audio palette**: three short SFX (pickup chop, plate complete, ticket
|
| 81 |
+
served) drawn from the supplied assets when present, with silent
|
| 82 |
+
fallbacks when audio buses cannot be reached.
|
| 83 |
+
- **Kitchen layout**: pantry runs along the east wall; chopping board sits
|
| 84 |
+
center-north; frying pan center-south; plate-up counter just inside the
|
| 85 |
+
hatch; serving hatch on the west wall. The kitchen is small enough that
|
| 86 |
+
every station is reachable within 2 seconds of walking.
|
| 87 |
+
- **Pacing**: tickets arrive in waves — the first appears at t=2s, the
|
| 88 |
+
next when the player serves or fails the current one, until three are
|
| 89 |
+
served. Patience bars are clearly visible above each ticket.
|
| 90 |
+
|
| 91 |
+
## Mechanics
|
| 92 |
+
|
| 93 |
+
### Player movement
|
| 94 |
+
|
| 95 |
+
- **Trigger**: `gb_left`, `gb_right`, `gb_up`, `gb_down` (held).
|
| 96 |
+
- **Precondition**: player is on the kitchen scene and not in `paused`
|
| 97 |
+
process mode.
|
| 98 |
+
- **State change**: position vector updates at 180 px/s, clamped to the
|
| 99 |
+
kitchen interior.
|
| 100 |
+
- **Feedback**: the player sprite translates; facing direction updates
|
| 101 |
+
from the last horizontal/vertical input.
|
| 102 |
+
- **Limit / cost**: none; movement is free.
|
| 103 |
+
- **Failure**: n/a; the player cannot leave the kitchen.
|
| 104 |
+
|
| 105 |
+
### Ingredient pickup
|
| 106 |
+
|
| 107 |
+
- **Trigger**: `gb_action` while the player stands adjacent to a pantry
|
| 108 |
+
crate and is not currently holding an item.
|
| 109 |
+
- **Precondition**: an ingredient crate of any kind is on the tile the
|
| 110 |
+
player is facing.
|
| 111 |
+
- **State change**: the player's `held_item` becomes that ingredient; the
|
| 112 |
+
crate sprite animates a brief empty tint.
|
| 113 |
+
- **Feedback**: a held-item sprite appears above the player; the pickup
|
| 114 |
+
SFX plays if available.
|
| 115 |
+
- **Limit / cost**: at most one item can be held.
|
| 116 |
+
- **Failure**: if the player already holds an item, the action does
|
| 117 |
+
nothing (and the held item is preserved).
|
| 118 |
+
|
| 119 |
+
### Chopping
|
| 120 |
+
|
| 121 |
+
- **Trigger**: `gb_action` while the player stands adjacent to the
|
| 122 |
+
chopping board and is holding a raw ingredient (lettuce, tomato).
|
| 123 |
+
- **Precondition**: held item is `lettuce` or `tomato`; player is facing
|
| 124 |
+
the chopping board.
|
| 125 |
+
- **State change**: a 30-frame chopping timer starts; when it completes,
|
| 126 |
+
the held item becomes `lettuce_chopped` or `tomato_chopped`. A single
|
| 127 |
+
chop is enough.
|
| 128 |
+
- **Feedback**: the chopping board pulses a brighter colour during the
|
| 129 |
+
chop, then the held-item sprite swaps to the chopped variant.
|
| 130 |
+
- **Limit / cost**: one chop per pickup.
|
| 131 |
+
- **Failure**: holding the wrong item yields a brief error SFX/no-op.
|
| 132 |
+
|
| 133 |
+
### Frying
|
| 134 |
+
|
| 135 |
+
- **Trigger**: `gb_action` while the player stands adjacent to the
|
| 136 |
+
frying pan and is holding a raw meat ingredient.
|
| 137 |
+
- **Precondition**: held item is `meat`; player is facing the pan.
|
| 138 |
+
- **State change**: a 60-frame frying timer starts (twice as long as
|
| 139 |
+
chopping because meat cooks slower); the held item becomes
|
| 140 |
+
`meat_cooked`.
|
| 141 |
+
- **Feedback**: pan pulses orange while frying; held-item sprite swaps
|
| 142 |
+
to the cooked variant.
|
| 143 |
+
- **Limit / cost**: one cook per pickup.
|
| 144 |
+
- **Failure**: holding the wrong item yields no-op.
|
| 145 |
+
|
| 146 |
+
### Plating
|
| 147 |
+
|
| 148 |
+
- **Trigger**: `gb_action` while the player stands adjacent to the
|
| 149 |
+
plate-up counter and holds a finished ingredient (`*_chopped` or
|
| 150 |
+
`meat_cooked`).
|
| 151 |
+
- **Precondition**: held item is in the plated set.
|
| 152 |
+
- **State change**: the held item becomes the matching plated dish
|
| 153 |
+
(`lettuce_plate`, `tomato_plate`, `meat_plate`).
|
| 154 |
+
- **Feedback**: the plate-up counter briefly shows a glow; the held
|
| 155 |
+
sprite becomes the dish sprite.
|
| 156 |
+
|
| 157 |
+
### Delivery
|
| 158 |
+
|
| 159 |
+
- **Trigger**: `gb_action` while the player stands at the serving hatch
|
| 160 |
+
and holds a plated dish.
|
| 161 |
+
- **Precondition**: held item is in the plated set.
|
| 162 |
+
- **State change**: the held item is consumed. If the active ticket's
|
| 163 |
+
recipe matches the dish, the ticket is fulfilled, score increases, a
|
| 164 |
+
ticket-served SFX plays, and the next ticket spawns. If the recipe
|
| 165 |
+
does not match, the dish is consumed and no ticket is fulfilled
|
| 166 |
+
(the player still loses the held item — they must replate).
|
| 167 |
+
- **Feedback**: ticket sprite animates upward and fades; score number
|
| 168 |
+
ticks up; next ticket slides in from the side.
|
| 169 |
+
- **Limit / cost**: held item is always consumed.
|
| 170 |
+
- **Failure**: an unmatched delivery still consumes the held item; this
|
| 171 |
+
is communicated by the score not changing.
|
| 172 |
+
|
| 173 |
+
### Patience pressure
|
| 174 |
+
|
| 175 |
+
- **Trigger**: continuous tick while a ticket is active.
|
| 176 |
+
- **Precondition**: ticket is on the hatch and patience > 0.
|
| 177 |
+
- **State change**: patience decreases by 1 per second.
|
| 178 |
+
- **Feedback**: the patience bar shrinks; at <=5 seconds it turns red and
|
| 179 |
+
pulses.
|
| 180 |
+
- **Failure**: at patience == 0 the ticket is removed and the
|
| 181 |
+
`tickets_failed` counter increases.
|
| 182 |
+
|
| 183 |
+
### Pause and reset
|
| 184 |
+
|
| 185 |
+
- **Trigger**: `gb_pause` toggles paused state (UI overlay shown, no
|
| 186 |
+
further state changes); `gb_reset` returns to the opening scene with
|
| 187 |
+
no progress.
|
| 188 |
+
- **Precondition**: both work at any time.
|
| 189 |
+
- **State change**: pause flips `paused`; reset rebuilds the run.
|
| 190 |
+
|
| 191 |
+
## Progression
|
| 192 |
+
|
| 193 |
+
- **Ticket 1**: arrives at t=2s with the recipe `salad` (lettuce chopped
|
| 194 |
+
then plated). Patience 45s.
|
| 195 |
+
- **Ticket 2**: arrives as soon as ticket 1 is served or failed.
|
| 196 |
+
Recipe chosen from {salad, steak, combo (lettuce + meat)} with a 45s
|
| 197 |
+
patience bar. The combo recipe requires two pickups and two trips
|
| 198 |
+
through two prep stations, then delivery.
|
| 199 |
+
- **Ticket 3**: arrives as soon as ticket 2 is served or failed. Recipe
|
| 200 |
+
chosen from {salad, steak, combo} with 45s patience.
|
| 201 |
+
- **Three tickets served before global timer hits zero**: victory.
|
| 202 |
+
- **Global timer hits zero with <3 served**: defeat.
|
| 203 |
+
|
| 204 |
+
The run is intentionally short (≤120s). Each ticket widens the possible
|
| 205 |
+
recipes so the player has to revisit every station.
|
| 206 |
+
|
| 207 |
+
## Victory
|
| 208 |
+
|
| 209 |
+
After the third ticket is served, the kitchen scene transitions to the
|
| 210 |
+
`run_complete` ending scene (a static Service Complete panel with the
|
| 211 |
+
final score). The transition is on a normal scene change so the
|
| 212 |
+
evaluator sees the `run_complete` ending scene path resolve.
|
| 213 |
+
|
| 214 |
+
## Failure
|
| 215 |
+
|
| 216 |
+
When the global timer reaches zero with fewer than three tickets
|
| 217 |
+
served, the kitchen scene transitions to the `defeat` ending scene
|
| 218 |
+
(Service Failed panel with the score and tickets served count). The
|
| 219 |
+
failure is communicated by the visible timer reaching zero and a brief
|
| 220 |
+
red flash.
|
| 221 |
+
|
| 222 |
+
## Restart
|
| 223 |
+
|
| 224 |
+
Pressing the reset action (R) during play reloads the kitchen scene
|
| 225 |
+
freshly (tickets reset, score reset, timer reset). The opening scene is
|
| 226 |
+
also reachable via `gb_reset` from the ending screens.
|
| 227 |
+
|
| 228 |
+
## Acceptance
|
| 229 |
+
|
| 230 |
+
- **Runtime observable**: cold import of the project starts on the
|
| 231 |
+
kitchen scene with one visible ticket at the serving hatch and a
|
| 232 |
+
120-second global timer counting down from 120.0.
|
| 233 |
+
- **Playability**: the player can move, pick up a lettuce crate, chop it
|
| 234 |
+
at the chopping board, plate it at the plate-up counter, deliver it
|
| 235 |
+
to the hatch, and see the ticket fulfilled and the score increase
|
| 236 |
+
to at least 5 before the patience bar empties.
|
| 237 |
+
- **Presentation**: visible ticket panel with patience bar, visible
|
| 238 |
+
score / tickets-served / tickets-failed panel, visible player avatar
|
| 239 |
+
with held-item sprite, visible station sprites that pulse during use.
|
| 240 |
+
- **Scope**: exactly one kitchen scene plus two ending scenes (victory
|
| 241 |
+
and defeat). No additional levels.
|
| 242 |
+
- **Failure recovery**: failing a ticket (patience expires) removes the
|
| 243 |
+
ticket cleanly and lets the player continue without resetting.
|
| 244 |
+
- **Replay**: a recorded input tape starting in the kitchen scene can
|
| 245 |
+
drive a serving sequence through three tickets to reach the
|
| 246 |
+
`run_complete` ending.
|
| 247 |
+
|
| 248 |
+
## Evidence
|
| 249 |
+
|
| 250 |
+
- `gb_levels.json` declares the kitchen scene as the first level and
|
| 251 |
+
the two ending scenes.
|
| 252 |
+
- `demos.json` contains input-only feature demonstrations covering
|
| 253 |
+
movement, pickup, chop, plate, deliver, patience failure, and full
|
| 254 |
+
victory.
|
| 255 |
+
- The replay trace at `playtest_out/trace.md` records the numeric
|
| 256 |
+
`score`, `progress`, and `timer` slot changes alongside the
|
| 257 |
+
gameplay actions.
|
| 258 |
+
|
| 259 |
+
## GB interface
|
| 260 |
+
|
| 261 |
+
- Required group: `gb_player` (the chef avatar).
|
| 262 |
+
- Used canonical actions: `gb_left`, `gb_right`, `gb_up`, `gb_down`,
|
| 263 |
+
`gb_action`, `gb_pause`, `gb_reset`. `gb_jump` is bound and read but
|
| 264 |
+
unused; the chef does not jump.
|
| 265 |
+
- No extended actions or analog axes are required: the seven verbs
|
| 266 |
+
above cover every interaction.
|
| 267 |
+
- Numeric slots:
|
| 268 |
+
- `score` (int, ticks up per served ticket).
|
| 269 |
+
- `progress` (int, `tickets_served`, increases 0->3).
|
| 270 |
+
- `timer` (float, `global_timer`, counts down from 120.0).
|
| 271 |
+
- Levels:
|
| 272 |
+
- `res://scenes/kitchen.tscn` — first (and only) gameplay level.
|
| 273 |
+
- Endings:
|
| 274 |
+
- `run_complete` -> `res://scenes/victory.tscn`.
|
| 275 |
+
- `defeat` -> `res://scenes/defeat.tscn`.
|
| 276 |
+
- The evaluator's match-driver replays the same `demos.json` segments
|
| 277 |
+
via the stock driver at fixed 60 fps; each segment starts from the
|
| 278 |
+
first declared level cold launch and reaches its claimed observable
|
| 279 |
+
before the no-input control.
|
kit_on_video_on/hurry_curry/submission/demos.json
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"schema_version": 1,
|
| 3 |
+
"demos": [
|
| 4 |
+
{
|
| 5 |
+
"id": "movement",
|
| 6 |
+
"description": "Walk the chef around the kitchen to confirm WASD movement translates position.",
|
| 7 |
+
"ops": [
|
| 8 |
+
{"op": "wait", "frames": 30},
|
| 9 |
+
{"op": "hold", "action": "gb_right", "frames": 60},
|
| 10 |
+
{"op": "hold", "action": "gb_down", "frames": 60},
|
| 11 |
+
{"op": "hold", "action": "gb_left", "frames": 60},
|
| 12 |
+
{"op": "hold", "action": "gb_up", "frames": 60},
|
| 13 |
+
{"op": "wait", "frames": 60}
|
| 14 |
+
]
|
| 15 |
+
},
|
| 16 |
+
{
|
| 17 |
+
"id": "pickup_lettuce",
|
| 18 |
+
"description": "Pick up a lettuce crate and see the held-item sprite appear over the chef.",
|
| 19 |
+
"ops": [
|
| 20 |
+
{"op": "wait", "frames": 60},
|
| 21 |
+
{"op": "hold", "action": "gb_right", "frames": 130},
|
| 22 |
+
{"op": "hold", "action": "gb_up", "frames": 40},
|
| 23 |
+
{"op": "wait", "frames": 6},
|
| 24 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 25 |
+
{"op": "wait", "frames": 60}
|
| 26 |
+
]
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"id": "chop_lettuce",
|
| 30 |
+
"description": "Pick up lettuce and chop it on the chopping board; held item becomes chopped.",
|
| 31 |
+
"ops": [
|
| 32 |
+
{"op": "wait", "frames": 60},
|
| 33 |
+
{"op": "hold", "action": "gb_right", "frames": 130},
|
| 34 |
+
{"op": "hold", "action": "gb_up", "frames": 40},
|
| 35 |
+
{"op": "wait", "frames": 6},
|
| 36 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 37 |
+
{"op": "wait", "frames": 6},
|
| 38 |
+
{"op": "hold", "action": "gb_left", "frames": 140},
|
| 39 |
+
{"op": "wait", "frames": 6},
|
| 40 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 41 |
+
{"op": "wait", "frames": 60}
|
| 42 |
+
]
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
"id": "fry_meat",
|
| 46 |
+
"description": "Pick up meat and fry it on the pan; held item becomes cooked meat.",
|
| 47 |
+
"ops": [
|
| 48 |
+
{"op": "wait", "frames": 60},
|
| 49 |
+
{"op": "hold", "action": "gb_right", "frames": 255},
|
| 50 |
+
{"op": "hold", "action": "gb_up", "frames": 5},
|
| 51 |
+
{"op": "wait", "frames": 6},
|
| 52 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 53 |
+
{"op": "wait", "frames": 6},
|
| 54 |
+
{"op": "hold", "action": "gb_left", "frames": 155},
|
| 55 |
+
{"op": "hold", "action": "gb_down", "frames": 65},
|
| 56 |
+
{"op": "wait", "frames": 6},
|
| 57 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 58 |
+
{"op": "wait", "frames": 60}
|
| 59 |
+
]
|
| 60 |
+
},
|
| 61 |
+
{
|
| 62 |
+
"id": "plate_dish",
|
| 63 |
+
"description": "Chop lettuce, then walk to the plate-up counter to convert it to a plated dish.",
|
| 64 |
+
"ops": [
|
| 65 |
+
{"op": "wait", "frames": 60},
|
| 66 |
+
{"op": "hold", "action": "gb_right", "frames": 130},
|
| 67 |
+
{"op": "hold", "action": "gb_up", "frames": 40},
|
| 68 |
+
{"op": "wait", "frames": 6},
|
| 69 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 70 |
+
{"op": "wait", "frames": 6},
|
| 71 |
+
{"op": "hold", "action": "gb_left", "frames": 140},
|
| 72 |
+
{"op": "wait", "frames": 6},
|
| 73 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 74 |
+
{"op": "wait", "frames": 60},
|
| 75 |
+
{"op": "hold", "action": "gb_down", "frames": 35},
|
| 76 |
+
{"op": "hold", "action": "gb_left", "frames": 65},
|
| 77 |
+
{"op": "wait", "frames": 6},
|
| 78 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 79 |
+
{"op": "wait", "frames": 60}
|
| 80 |
+
]
|
| 81 |
+
},
|
| 82 |
+
{
|
| 83 |
+
"id": "deliver_salad",
|
| 84 |
+
"description": "Complete a full order: pick up lettuce, chop, plate, deliver to the serving hatch.",
|
| 85 |
+
"ops": [
|
| 86 |
+
{"op": "wait", "frames": 60},
|
| 87 |
+
{"op": "hold", "action": "gb_right", "frames": 130},
|
| 88 |
+
{"op": "hold", "action": "gb_up", "frames": 40},
|
| 89 |
+
{"op": "wait", "frames": 6},
|
| 90 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 91 |
+
{"op": "wait", "frames": 6},
|
| 92 |
+
{"op": "hold", "action": "gb_left", "frames": 140},
|
| 93 |
+
{"op": "wait", "frames": 6},
|
| 94 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 95 |
+
{"op": "wait", "frames": 60},
|
| 96 |
+
{"op": "hold", "action": "gb_down", "frames": 35},
|
| 97 |
+
{"op": "hold", "action": "gb_left", "frames": 65},
|
| 98 |
+
{"op": "wait", "frames": 6},
|
| 99 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 100 |
+
{"op": "wait", "frames": 6},
|
| 101 |
+
{"op": "hold", "action": "gb_left", "frames": 45},
|
| 102 |
+
{"op": "wait", "frames": 6},
|
| 103 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 104 |
+
{"op": "wait", "frames": 120}
|
| 105 |
+
]
|
| 106 |
+
},
|
| 107 |
+
{
|
| 108 |
+
"id": "patience_failure",
|
| 109 |
+
"description": "Stand still while the active ticket's patience bar empties, demonstrating patience pressure and failure recovery.",
|
| 110 |
+
"ops": [
|
| 111 |
+
{"op": "wait", "frames": 600},
|
| 112 |
+
{"op": "wait", "frames": 600},
|
| 113 |
+
{"op": "wait", "frames": 600}
|
| 114 |
+
]
|
| 115 |
+
},
|
| 116 |
+
{
|
| 117 |
+
"id": "full_victory",
|
| 118 |
+
"description": "Serve three tickets in sequence (salad, steak, salad) to reach the run_complete ending.",
|
| 119 |
+
"ops": [
|
| 120 |
+
{"op": "wait", "frames": 90},
|
| 121 |
+
{"op": "hold", "action": "gb_right", "frames": 130},
|
| 122 |
+
{"op": "hold", "action": "gb_up", "frames": 40},
|
| 123 |
+
{"op": "wait", "frames": 6},
|
| 124 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 125 |
+
{"op": "wait", "frames": 6},
|
| 126 |
+
{"op": "hold", "action": "gb_left", "frames": 140},
|
| 127 |
+
{"op": "wait", "frames": 6},
|
| 128 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 129 |
+
{"op": "wait", "frames": 60},
|
| 130 |
+
{"op": "hold", "action": "gb_down", "frames": 35},
|
| 131 |
+
{"op": "hold", "action": "gb_left", "frames": 65},
|
| 132 |
+
{"op": "wait", "frames": 6},
|
| 133 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 134 |
+
{"op": "wait", "frames": 6},
|
| 135 |
+
{"op": "hold", "action": "gb_left", "frames": 45},
|
| 136 |
+
{"op": "wait", "frames": 6},
|
| 137 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 138 |
+
{"op": "wait", "frames": 90},
|
| 139 |
+
{"op": "hold", "action": "gb_right", "frames": 255},
|
| 140 |
+
{"op": "hold", "action": "gb_up", "frames": 5},
|
| 141 |
+
{"op": "wait", "frames": 6},
|
| 142 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 143 |
+
{"op": "wait", "frames": 6},
|
| 144 |
+
{"op": "hold", "action": "gb_left", "frames": 155},
|
| 145 |
+
{"op": "hold", "action": "gb_down", "frames": 65},
|
| 146 |
+
{"op": "wait", "frames": 6},
|
| 147 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 148 |
+
{"op": "wait", "frames": 60},
|
| 149 |
+
{"op": "hold", "action": "gb_up", "frames": 60},
|
| 150 |
+
{"op": "hold", "action": "gb_left", "frames": 65},
|
| 151 |
+
{"op": "wait", "frames": 6},
|
| 152 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 153 |
+
{"op": "wait", "frames": 6},
|
| 154 |
+
{"op": "hold", "action": "gb_left", "frames": 45},
|
| 155 |
+
{"op": "wait", "frames": 6},
|
| 156 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 157 |
+
{"op": "wait", "frames": 90},
|
| 158 |
+
{"op": "hold", "action": "gb_right", "frames": 245},
|
| 159 |
+
{"op": "hold", "action": "gb_up", "frames": 35},
|
| 160 |
+
{"op": "wait", "frames": 6},
|
| 161 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 162 |
+
{"op": "wait", "frames": 6},
|
| 163 |
+
{"op": "hold", "action": "gb_left", "frames": 140},
|
| 164 |
+
{"op": "wait", "frames": 6},
|
| 165 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 166 |
+
{"op": "wait", "frames": 60},
|
| 167 |
+
{"op": "hold", "action": "gb_down", "frames": 35},
|
| 168 |
+
{"op": "hold", "action": "gb_left", "frames": 65},
|
| 169 |
+
{"op": "wait", "frames": 6},
|
| 170 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 171 |
+
{"op": "wait", "frames": 6},
|
| 172 |
+
{"op": "hold", "action": "gb_left", "frames": 45},
|
| 173 |
+
{"op": "wait", "frames": 6},
|
| 174 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 175 |
+
{"op": "wait", "frames": 240}
|
| 176 |
+
]
|
| 177 |
+
}
|
| 178 |
+
]
|
| 179 |
+
}
|
kit_on_video_on/hurry_curry/submission/game/gb_levels.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"levels": ["res://scenes/kitchen.tscn"],
|
| 3 |
+
"endings": {
|
| 4 |
+
"run_complete": "res://scenes/victory.tscn",
|
| 5 |
+
"defeat": "res://scenes/defeat.tscn"
|
| 6 |
+
},
|
| 7 |
+
"numeric": {
|
| 8 |
+
"score": "score",
|
| 9 |
+
"progress": "tickets_served",
|
| 10 |
+
"timer": "global_timer"
|
| 11 |
+
}
|
| 12 |
+
}
|
kit_on_video_on/hurry_curry/submission/game/icon.svg
ADDED
|
|
kit_on_video_on/hurry_curry/submission/game/project.godot
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
; Engine configuration file.
|
| 2 |
+
config_version=5
|
| 3 |
+
|
| 4 |
+
[application]
|
| 5 |
+
|
| 6 |
+
config/name="Hurry Curry"
|
| 7 |
+
config/description="A short top-down cooking game."
|
| 8 |
+
run/main_scene="res://scenes/kitchen.tscn"
|
| 9 |
+
config/features=PackedStringArray("4.3", "GL Compatibility")
|
| 10 |
+
config/icon="res://icon.svg"
|
| 11 |
+
|
| 12 |
+
[display]
|
| 13 |
+
|
| 14 |
+
window/size/viewport_width=960
|
| 15 |
+
window/size/viewport_height=600
|
| 16 |
+
window/stretch/mode="canvas_items"
|
| 17 |
+
window/stretch/aspect="expand"
|
| 18 |
+
window/stretch/scale_mode="fractional"
|
| 19 |
+
|
| 20 |
+
[input]
|
| 21 |
+
|
| 22 |
+
gb_left={
|
| 23 |
+
"deadzone": 0.5,
|
| 24 |
+
"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":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
|
| 25 |
+
]
|
| 26 |
+
}
|
| 27 |
+
gb_right={
|
| 28 |
+
"deadzone": 0.5,
|
| 29 |
+
"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":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
|
| 30 |
+
]
|
| 31 |
+
}
|
| 32 |
+
gb_up={
|
| 33 |
+
"deadzone": 0.5,
|
| 34 |
+
"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":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
|
| 35 |
+
]
|
| 36 |
+
}
|
| 37 |
+
gb_down={
|
| 38 |
+
"deadzone": 0.5,
|
| 39 |
+
"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":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
|
| 40 |
+
]
|
| 41 |
+
}
|
| 42 |
+
gb_action={
|
| 43 |
+
"deadzone": 0.5,
|
| 44 |
+
"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":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
|
| 45 |
+
]
|
| 46 |
+
}
|
| 47 |
+
gb_jump={
|
| 48 |
+
"deadzone": 0.5,
|
| 49 |
+
"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":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
|
| 50 |
+
]
|
| 51 |
+
}
|
| 52 |
+
gb_pause={
|
| 53 |
+
"deadzone": 0.5,
|
| 54 |
+
"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":80,"key_label":0,"unicode":112,"location":0,"echo":false,"script":null)
|
| 55 |
+
]
|
| 56 |
+
}
|
| 57 |
+
gb_reset={
|
| 58 |
+
"deadzone": 0.5,
|
| 59 |
+
"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":82,"key_label":0,"unicode":114,"location":0,"echo":false,"script":null)
|
| 60 |
+
]
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
[rendering]
|
| 64 |
+
|
| 65 |
+
renderer/rendering_method="gl_compatibility"
|
| 66 |
+
renderer/rendering_method.mobile="gl_compatibility"
|
| 67 |
+
|
| 68 |
+
[autoload]
|
| 69 |
+
|
| 70 |
+
GameState="*res://scripts/game_state.gd"
|
kit_on_video_on/hurry_curry/submission/game/scenes/defeat.tscn
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[gd_scene load_steps=2 format=3 uid="uid://b1hurrycurrydefeat"]
|
| 2 |
+
|
| 3 |
+
[ext_resource type="Script" path="res://scripts/ending.gd" id="1_end"]
|
| 4 |
+
|
| 5 |
+
[node name="Defeat" type="Node2D"]
|
| 6 |
+
script = ExtResource("1_end")
|
| 7 |
+
ending_kind = "defeat"
|
kit_on_video_on/hurry_curry/submission/game/scenes/kitchen.tscn
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[gd_scene load_steps=2 format=3 uid="uid://b1hurrycurrykitchen"]
|
| 2 |
+
|
| 3 |
+
[ext_resource type="Script" path="res://scripts/kitchen.gd" id="1_kitchen"]
|
| 4 |
+
[ext_resource type="Script" path="res://scripts/player.gd" id="2_player"]
|
| 5 |
+
|
| 6 |
+
[node name="Kitchen" type="Node2D"]
|
| 7 |
+
script = ExtResource("1_kitchen")
|
| 8 |
+
|
| 9 |
+
[node name="Player" type="CharacterBody2D" parent="."]
|
| 10 |
+
position = Vector2(400, 280)
|
| 11 |
+
script = ExtResource("2_player")
|
kit_on_video_on/hurry_curry/submission/game/scenes/victory.tscn
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[gd_scene load_steps=2 format=3 uid="uid://b1hurrycurryvictory"]
|
| 2 |
+
|
| 3 |
+
[ext_resource type="Script" path="res://scripts/ending.gd" id="1_end"]
|
| 4 |
+
|
| 5 |
+
[node name="Victory" type="Node2D"]
|
| 6 |
+
script = ExtResource("1_end")
|
| 7 |
+
ending_kind = "run_complete"
|
kit_on_video_on/hurry_curry/submission/game/scripts/ending.gd
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
extends Node2D
|
| 2 |
+
# Ending scene — displays a victory or defeat panel and reads the final
|
| 3 |
+
# GameState values for the labels. The `ending_kind` exported property tells
|
| 4 |
+
# the script which template to render. `gb_reset` (R) restarts the run.
|
| 5 |
+
|
| 6 |
+
@export var ending_kind: String = "run_complete"
|
| 7 |
+
|
| 8 |
+
func _ready() -> void:
|
| 9 |
+
queue_redraw()
|
| 10 |
+
|
| 11 |
+
func _unhandled_input(event: InputEvent) -> void:
|
| 12 |
+
if event.is_action_pressed("gb_reset"):
|
| 13 |
+
get_tree().paused = false
|
| 14 |
+
get_tree().change_scene_to_file("res://scenes/kitchen.tscn")
|
| 15 |
+
|
| 16 |
+
func _draw() -> void:
|
| 17 |
+
var screen := get_viewport_rect().size
|
| 18 |
+
var panel := Rect2(screen.x / 2 - 240, screen.y / 2 - 140, 480, 280)
|
| 19 |
+
var bg_color := Color(0.92, 0.96, 0.92) if ending_kind == "run_complete" else Color(0.96, 0.92, 0.92)
|
| 20 |
+
var border_color := Color(0.2, 0.5, 0.3) if ending_kind == "run_complete" else Color(0.6, 0.2, 0.2)
|
| 21 |
+
var title_text := "Service Complete!" if ending_kind == "run_complete" else "Service Failed"
|
| 22 |
+
var title_color := Color(0.2, 0.55, 0.3) if ending_kind == "run_complete" else Color(0.7, 0.2, 0.2)
|
| 23 |
+
draw_rect(panel, bg_color, true)
|
| 24 |
+
draw_rect(panel, border_color, false, 3.0)
|
| 25 |
+
var font := ThemeDB.fallback_font
|
| 26 |
+
if font != null:
|
| 27 |
+
draw_string(font, Vector2(panel.position.x + 24, panel.position.y + 50), title_text, HORIZONTAL_ALIGNMENT_LEFT, -1, 36, title_color)
|
| 28 |
+
draw_string(font, Vector2(panel.position.x + 24, panel.position.y + 110), "Score: %d" % GameState.score, HORIZONTAL_ALIGNMENT_LEFT, -1, 24, Color(0.1, 0.1, 0.1))
|
| 29 |
+
draw_string(font, Vector2(panel.position.x + 24, panel.position.y + 150), "Orders served: %d / %d" % [GameState.tickets_served, GameState.target_orders], HORIZONTAL_ALIGNMENT_LEFT, -1, 20, Color(0.1, 0.1, 0.1))
|
| 30 |
+
var hint := "Press R to play again." if ending_kind == "run_complete" else "Press R to try again."
|
| 31 |
+
draw_string(font, Vector2(panel.position.x + 24, panel.position.y + 200), hint, HORIZONTAL_ALIGNMENT_LEFT, -1, 18, Color(0.2, 0.2, 0.2))
|
kit_on_video_on/hurry_curry/submission/game/scripts/game_state.gd
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
extends Node
|
| 2 |
+
# GameState — central run-time state shared between the player, the kitchen
|
| 3 |
+
# scene, and the GB interface driver. Lives as an autoload so the evaluator's
|
| 4 |
+
# driver can read its numeric slots from anywhere with the bare property
|
| 5 |
+
# name `score`, `tickets_served`, and `global_timer`.
|
| 6 |
+
#
|
| 7 |
+
# The score increases per served ticket (5 points), tickets_served counts
|
| 8 |
+
# successes, tickets_failed counts expired patience, global_timer counts
|
| 9 |
+
# down from the configured service time. The kitchen scene observes
|
| 10 |
+
# `tickets_served` and `global_timer` to decide when to switch to an ending
|
| 11 |
+
# scene.
|
| 12 |
+
|
| 13 |
+
@export var service_time: float = 120.0
|
| 14 |
+
@export var ticket_patience: float = 15.0
|
| 15 |
+
@export var points_per_ticket: int = 5
|
| 16 |
+
@export var target_orders: int = 3
|
| 17 |
+
|
| 18 |
+
# Live numeric slots the evaluator reads.
|
| 19 |
+
var score: int = 0
|
| 20 |
+
var tickets_served: int = 0
|
| 21 |
+
var tickets_failed: int = 0
|
| 22 |
+
var global_timer: float = service_time
|
| 23 |
+
|
| 24 |
+
# Ticket wheel: choose the next ticket's recipe from this pool so the player
|
| 25 |
+
# is eventually forced to revisit every station.
|
| 26 |
+
var recipes := ["salad", "steak", "combo"]
|
| 27 |
+
|
| 28 |
+
# Current ticket (None when no active ticket — handled by kitchen scene).
|
| 29 |
+
var active_recipe: String = ""
|
| 30 |
+
var active_patience: float = 0.0
|
| 31 |
+
|
| 32 |
+
func reset_run() -> void:
|
| 33 |
+
score = 0
|
| 34 |
+
tickets_served = 0
|
| 35 |
+
tickets_failed = 0
|
| 36 |
+
global_timer = service_time
|
| 37 |
+
active_recipe = ""
|
| 38 |
+
active_patience = 0.0
|
| 39 |
+
|
| 40 |
+
func spawn_ticket() -> void:
|
| 41 |
+
# The recipe sequence is deterministic so a route tape can be authored:
|
| 42 |
+
# salad (lettuce plate) -> steak (meat plate) -> salad (lettuce plate).
|
| 43 |
+
# Three serves total. The 3rd order reuses salad so a route tape can
|
| 44 |
+
# replay the same pickup/chop/plate/deliver pattern.
|
| 45 |
+
var order: Array[String] = ["salad", "steak", "salad"]
|
| 46 |
+
var idx: int = tickets_served + tickets_failed
|
| 47 |
+
if idx < order.size():
|
| 48 |
+
active_recipe = order[idx]
|
| 49 |
+
else:
|
| 50 |
+
active_recipe = recipes[randi() % recipes.size()]
|
| 51 |
+
active_patience = ticket_patience
|
| 52 |
+
|
| 53 |
+
func clear_ticket() -> void:
|
| 54 |
+
active_recipe = ""
|
| 55 |
+
active_patience = 0.0
|
| 56 |
+
|
| 57 |
+
func serve_active_ticket() -> void:
|
| 58 |
+
score += points_per_ticket
|
| 59 |
+
tickets_served += 1
|
| 60 |
+
clear_ticket()
|
| 61 |
+
|
| 62 |
+
func fail_active_ticket() -> void:
|
| 63 |
+
tickets_failed += 1
|
| 64 |
+
clear_ticket()
|
kit_on_video_on/hurry_curry/submission/game/scripts/kitchen.gd
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
extends Node2D
|
| 2 |
+
# Kitchen — single top-down gameplay scene. Draws the layout via code so the
|
| 3 |
+
# project ships with only a few script files; reads/writes the autoloaded
|
| 4 |
+
# GameState so the evaluator can observe score, progress, and timer; routes
|
| 5 |
+
# the player's `player_action` signal into pickup/chop/fry/plate/deliver.
|
| 6 |
+
|
| 7 |
+
const PlayerScript := preload("res://scripts/player.gd")
|
| 8 |
+
|
| 9 |
+
const COLOR_BG := Color(0.93, 0.93, 0.88)
|
| 10 |
+
const COLOR_FLOOR := Color(0.78, 0.78, 0.74)
|
| 11 |
+
const COLOR_GRID := Color(0.72, 0.72, 0.68)
|
| 12 |
+
const COLOR_WALL := Color(0.43, 0.30, 0.21)
|
| 13 |
+
const COLOR_HATCH := Color(0.20, 0.55, 0.85)
|
| 14 |
+
const COLOR_CHOP := Color(0.95, 0.78, 0.50)
|
| 15 |
+
const COLOR_PAN := Color(0.30, 0.30, 0.30)
|
| 16 |
+
const COLOR_PLATE := Color(0.96, 0.96, 0.92)
|
| 17 |
+
const COLOR_PANTRY := Color(0.55, 0.40, 0.30)
|
| 18 |
+
const COLOR_PLAYER := Color(0.95, 0.50, 0.20)
|
| 19 |
+
const COLOR_TEXT := Color(0.15, 0.15, 0.18)
|
| 20 |
+
|
| 21 |
+
const TILE := 32
|
| 22 |
+
|
| 23 |
+
@onready var player: PlayerScript = $Player
|
| 24 |
+
|
| 25 |
+
# Layout rects (world coords). The chef walks inside the floor area.
|
| 26 |
+
var floor_rect := Rect2(Vector2(64, 96), Vector2(800, 416))
|
| 27 |
+
|
| 28 |
+
# Pantry crates — array of {rect, ingredient}.
|
| 29 |
+
var pantry := [] # array of {rect, ingredient}
|
| 30 |
+
var plate_counter: Rect2 = Rect2()
|
| 31 |
+
var hatch_rect: Rect2 = Rect2()
|
| 32 |
+
var chop_rect: Rect2 = Rect2()
|
| 33 |
+
var pan_rect: Rect2 = Rect2()
|
| 34 |
+
|
| 35 |
+
# Animated tile overlays for visual feedback.
|
| 36 |
+
var chop_pulse := 0.0
|
| 37 |
+
var pan_pulse := 0.0
|
| 38 |
+
var plate_pulse := 0.0
|
| 39 |
+
var flash := 0.0
|
| 40 |
+
|
| 41 |
+
var viewport_w: float = 960.0
|
| 42 |
+
var viewport_h: float = 600.0
|
| 43 |
+
|
| 44 |
+
# Recipe ingredients — used to drive the ticket panel UI.
|
| 45 |
+
const RECIPE := {
|
| 46 |
+
"salad": ["lettuce"],
|
| 47 |
+
"steak": ["meat"],
|
| 48 |
+
"combo": ["lettuce", "meat"],
|
| 49 |
+
}
|
| 50 |
+
const INGREDIENT_NAME := {
|
| 51 |
+
"lettuce": "Lettuce",
|
| 52 |
+
"meat": "Meat",
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
func _ready() -> void:
|
| 56 |
+
# Bind the player's bounds so they cannot leave the floor.
|
| 57 |
+
player.bounds = Rect2(floor_rect.position, floor_rect.size)
|
| 58 |
+
# Reset GameState for a fresh run.
|
| 59 |
+
GameState.reset_run()
|
| 60 |
+
# Build the kitchen layout.
|
| 61 |
+
_build_layout()
|
| 62 |
+
# Connect the player's action signal.
|
| 63 |
+
player.player_action.connect(_on_player_action)
|
| 64 |
+
# Spawn the first ticket after a short intro.
|
| 65 |
+
_spawn_first_ticket.call_deferred()
|
| 66 |
+
|
| 67 |
+
func _build_layout() -> void:
|
| 68 |
+
# The serving hatch sits on the LEFT wall, near the centre.
|
| 69 |
+
hatch_rect = Rect2(Vector2(floor_rect.position.x - 4, floor_rect.position.y + 4 * TILE),
|
| 70 |
+
Vector2(8, 3 * TILE))
|
| 71 |
+
# Chopping board: top-centre.
|
| 72 |
+
chop_rect = Rect2(Vector2(floor_rect.position.x + 8 * TILE, floor_rect.position.y + TILE),
|
| 73 |
+
Vector2(3 * TILE, 2 * TILE))
|
| 74 |
+
# Frying pan: bottom-centre.
|
| 75 |
+
pan_rect = Rect2(Vector2(floor_rect.position.x + 8 * TILE, floor_rect.position.y + floor_rect.size.y - 3 * TILE),
|
| 76 |
+
Vector2(3 * TILE, 2 * TILE))
|
| 77 |
+
# Plate-up counter: just inside the hatch.
|
| 78 |
+
plate_counter = Rect2(Vector2(floor_rect.position.x + 2 * TILE, floor_rect.position.y + 4 * TILE),
|
| 79 |
+
Vector2(3 * TILE, 3 * TILE))
|
| 80 |
+
# Pantry: along the right wall.
|
| 81 |
+
pantry = [
|
| 82 |
+
{"rect": Rect2(Vector2(floor_rect.position.x + floor_rect.size.x - 2 * TILE, floor_rect.position.y + TILE),
|
| 83 |
+
Vector2(2 * TILE, 2 * TILE)),
|
| 84 |
+
"ingredient": "lettuce"},
|
| 85 |
+
{"rect": Rect2(Vector2(floor_rect.position.x + floor_rect.size.x - 2 * TILE, floor_rect.position.y + 4 * TILE),
|
| 86 |
+
Vector2(2 * TILE, 2 * TILE)),
|
| 87 |
+
"ingredient": "meat"},
|
| 88 |
+
{"rect": Rect2(Vector2(floor_rect.position.x + floor_rect.size.x - 2 * TILE, floor_rect.position.y + 7 * TILE),
|
| 89 |
+
Vector2(2 * TILE, 2 * TILE)),
|
| 90 |
+
"ingredient": "tomato"},
|
| 91 |
+
]
|
| 92 |
+
|
| 93 |
+
func _spawn_first_ticket() -> void:
|
| 94 |
+
GameState.spawn_ticket()
|
| 95 |
+
|
| 96 |
+
func _physics_process(delta: float) -> void:
|
| 97 |
+
if get_tree().paused:
|
| 98 |
+
return
|
| 99 |
+
# Tick the global timer once per frame.
|
| 100 |
+
if GameState.global_timer > 0.0:
|
| 101 |
+
GameState.global_timer = max(0.0, GameState.global_timer - delta)
|
| 102 |
+
# Tick the active ticket patience.
|
| 103 |
+
if GameState.active_recipe != "":
|
| 104 |
+
GameState.active_patience = max(0.0, GameState.active_patience - delta)
|
| 105 |
+
if GameState.active_patience <= 0.0:
|
| 106 |
+
GameState.fail_active_ticket()
|
| 107 |
+
flash = 0.6
|
| 108 |
+
# Decay feedback pulses.
|
| 109 |
+
chop_pulse = max(0.0, chop_pulse - delta * 2.5)
|
| 110 |
+
pan_pulse = max(0.0, pan_pulse - delta * 2.5)
|
| 111 |
+
plate_pulse = max(0.0, plate_pulse - delta * 2.5)
|
| 112 |
+
flash = max(0.0, flash - delta * 1.5)
|
| 113 |
+
# Resolve victory / defeat at the end of each tick.
|
| 114 |
+
if GameState.tickets_served >= GameState.target_orders:
|
| 115 |
+
_end_run(true)
|
| 116 |
+
return
|
| 117 |
+
if GameState.global_timer <= 0.0 and GameState.active_recipe == "":
|
| 118 |
+
_end_run(false)
|
| 119 |
+
return
|
| 120 |
+
if GameState.global_timer <= 0.0 and GameState.tickets_served < GameState.target_orders and GameState.active_recipe == "":
|
| 121 |
+
_end_run(false)
|
| 122 |
+
return
|
| 123 |
+
# If we have no active ticket and have not yet hit target, spawn the next.
|
| 124 |
+
if GameState.active_recipe == "" and GameState.tickets_served + GameState.tickets_failed < 3 and GameState.global_timer > 0.0:
|
| 125 |
+
GameState.spawn_ticket()
|
| 126 |
+
queue_redraw()
|
| 127 |
+
|
| 128 |
+
func _end_run(victory: bool) -> void:
|
| 129 |
+
# Persist final state into the GameState autoload before the scene change.
|
| 130 |
+
if victory:
|
| 131 |
+
GameState.global_timer = max(GameState.global_timer, 0.0)
|
| 132 |
+
get_tree().paused = false
|
| 133 |
+
if victory:
|
| 134 |
+
get_tree().change_scene_to_file("res://scenes/victory.tscn")
|
| 135 |
+
else:
|
| 136 |
+
get_tree().change_scene_to_file("res://scenes/defeat.tscn")
|
| 137 |
+
|
| 138 |
+
func _on_player_action(at: Vector2, facing: String, held: String) -> void:
|
| 139 |
+
# While the chef is mid-chop, ignore further actions so the chop can finish.
|
| 140 |
+
if player.is_busy():
|
| 141 |
+
return
|
| 142 |
+
# 1. Check pantry pickup: the player must be ADJACENT (not standing in)
|
| 143 |
+
# the crate, since each pantry crate is 2x2 tiles and the player
|
| 144 |
+
# cannot stand inside it. The player position is the only thing we
|
| 145 |
+
# check (not facing_pos) so the player has clear agency: stand next
|
| 146 |
+
# to the crate you want.
|
| 147 |
+
for crate in pantry:
|
| 148 |
+
if _near_rect(at, crate.rect, 48.0):
|
| 149 |
+
if held == "":
|
| 150 |
+
player.held_item = crate.ingredient
|
| 151 |
+
plate_pulse = 0.4
|
| 152 |
+
return
|
| 153 |
+
else:
|
| 154 |
+
# Already holding something — ignore so the player can
|
| 155 |
+
# choose to drop it at the plate-up counter instead.
|
| 156 |
+
return
|
| 157 |
+
# 2. Chopping board.
|
| 158 |
+
if _near_rect(at, chop_rect, 40.0):
|
| 159 |
+
if held == "lettuce" or held == "tomato":
|
| 160 |
+
player.start_chop(held, 0.5) # 30 frames at 60fps
|
| 161 |
+
chop_pulse = 1.0
|
| 162 |
+
return
|
| 163 |
+
# 3. Frying pan.
|
| 164 |
+
if _near_rect(at, pan_rect, 40.0):
|
| 165 |
+
if held == "meat":
|
| 166 |
+
player.start_chop("meat", 1.0) # 60 frames
|
| 167 |
+
pan_pulse = 1.0
|
| 168 |
+
return
|
| 169 |
+
# 4. Plate-up counter.
|
| 170 |
+
if _near_rect(at, plate_counter, 40.0):
|
| 171 |
+
match held:
|
| 172 |
+
"lettuce_chopped":
|
| 173 |
+
player.held_item = "lettuce_plate"
|
| 174 |
+
plate_pulse = 1.0
|
| 175 |
+
"tomato_chopped":
|
| 176 |
+
player.held_item = "tomato_plate"
|
| 177 |
+
plate_pulse = 1.0
|
| 178 |
+
"meat_cooked":
|
| 179 |
+
player.held_item = "meat_plate"
|
| 180 |
+
plate_pulse = 1.0
|
| 181 |
+
"":
|
| 182 |
+
pass
|
| 183 |
+
_:
|
| 184 |
+
pass
|
| 185 |
+
return
|
| 186 |
+
# 5. Serving hatch.
|
| 187 |
+
if _near_rect(at, hatch_rect, 40.0):
|
| 188 |
+
_attempt_delivery(held)
|
| 189 |
+
return
|
| 190 |
+
|
| 191 |
+
func _attempt_delivery(held: String) -> void:
|
| 192 |
+
if held == "" or GameState.active_recipe == "":
|
| 193 |
+
return
|
| 194 |
+
# Map held plated dishes to the recipe they satisfy.
|
| 195 |
+
var satisfies := _recipe_for_dish(held)
|
| 196 |
+
if satisfies == GameState.active_recipe:
|
| 197 |
+
GameState.serve_active_ticket()
|
| 198 |
+
player.held_item = ""
|
| 199 |
+
plate_pulse = 1.0
|
| 200 |
+
flash = 0.4
|
| 201 |
+
else:
|
| 202 |
+
# Wrong dish — still consumes the held item so the player is
|
| 203 |
+
# forced to replate. The score / served counter do NOT move,
|
| 204 |
+
# which is the visible feedback.
|
| 205 |
+
player.held_item = ""
|
| 206 |
+
flash = 0.2
|
| 207 |
+
|
| 208 |
+
func _recipe_for_dish(dish: String) -> String:
|
| 209 |
+
match dish:
|
| 210 |
+
"lettuce_plate", "tomato_plate":
|
| 211 |
+
return "salad"
|
| 212 |
+
"meat_plate":
|
| 213 |
+
return "steak"
|
| 214 |
+
"combo_plate":
|
| 215 |
+
return "combo"
|
| 216 |
+
return ""
|
| 217 |
+
|
| 218 |
+
func _near_rect(point: Vector2, rect: Rect2, slack: float = 0.0) -> bool:
|
| 219 |
+
# Expanded rect: allow `slack` extra pixels on each side.
|
| 220 |
+
var r := rect.grow(slack)
|
| 221 |
+
return r.has_point(point)
|
| 222 |
+
|
| 223 |
+
func _draw() -> void:
|
| 224 |
+
# Background.
|
| 225 |
+
draw_rect(Rect2(Vector2.ZERO, get_viewport_rect().size), COLOR_BG, true)
|
| 226 |
+
# Floor.
|
| 227 |
+
draw_rect(floor_rect, COLOR_FLOOR, true)
|
| 228 |
+
# Subtle grid (every 64px).
|
| 229 |
+
for x in range(int(floor_rect.position.x), int(floor_rect.end.x), 64):
|
| 230 |
+
draw_line(Vector2(x, floor_rect.position.y), Vector2(x, floor_rect.end.y), COLOR_GRID, 1.0)
|
| 231 |
+
for y in range(int(floor_rect.position.y), int(floor_rect.end.y), 64):
|
| 232 |
+
draw_line(Vector2(floor_rect.position.x, y), Vector2(floor_rect.end.x, y), COLOR_GRID, 1.0)
|
| 233 |
+
# Wall outline.
|
| 234 |
+
draw_rect(floor_rect, COLOR_WALL, false, 4.0)
|
| 235 |
+
# Serving hatch.
|
| 236 |
+
draw_rect(hatch_rect, COLOR_HATCH, true)
|
| 237 |
+
draw_rect(hatch_rect, COLOR_WALL, false, 2.0)
|
| 238 |
+
# Chopping board.
|
| 239 |
+
var chop_color := COLOR_CHOP.lerp(Color(1, 1, 1), chop_pulse * 0.5)
|
| 240 |
+
draw_rect(chop_rect, chop_color, true)
|
| 241 |
+
draw_rect(chop_rect, COLOR_WALL, false, 2.0)
|
| 242 |
+
draw_string(ThemeDB.fallback_font, chop_rect.position + Vector2(8, 22),
|
| 243 |
+
"CHOP", HORIZONTAL_ALIGNMENT_LEFT, -1, 18, COLOR_TEXT)
|
| 244 |
+
# Frying pan.
|
| 245 |
+
var pan_color := COLOR_PAN.lerp(Color(1, 0.5, 0.2), pan_pulse * 0.7)
|
| 246 |
+
draw_rect(pan_rect, pan_color, true)
|
| 247 |
+
draw_rect(pan_rect, COLOR_WALL, false, 2.0)
|
| 248 |
+
draw_string(ThemeDB.fallback_font, pan_rect.position + Vector2(8, 22),
|
| 249 |
+
"FRY", HORIZONTAL_ALIGNMENT_LEFT, -1, 18, COLOR_TEXT)
|
| 250 |
+
# Plate-up counter.
|
| 251 |
+
var plate_color := COLOR_PLATE.lerp(Color(1, 0.95, 0.6), plate_pulse * 0.7)
|
| 252 |
+
draw_rect(plate_counter, plate_color, true)
|
| 253 |
+
draw_rect(plate_counter, COLOR_WALL, false, 2.0)
|
| 254 |
+
draw_string(ThemeDB.fallback_font, plate_counter.position + Vector2(8, 22),
|
| 255 |
+
"PLATE", HORIZONTAL_ALIGNMENT_LEFT, -1, 18, COLOR_TEXT)
|
| 256 |
+
# Pantry crates.
|
| 257 |
+
for crate in pantry:
|
| 258 |
+
var crect: Rect2 = crate.rect
|
| 259 |
+
var ing_label: String = String(crate.ingredient).to_upper()
|
| 260 |
+
draw_rect(crect, COLOR_PANTRY, true)
|
| 261 |
+
draw_rect(crect, COLOR_WALL, false, 2.0)
|
| 262 |
+
draw_string(ThemeDB.fallback_font, crect.position + Vector2(8, 22),
|
| 263 |
+
ing_label, HORIZONTAL_ALIGNMENT_LEFT, -1, 16, COLOR_TEXT)
|
| 264 |
+
# Player.
|
| 265 |
+
var player_color := COLOR_PLAYER
|
| 266 |
+
if player.is_busy():
|
| 267 |
+
player_color = COLOR_PLAYER.lerp(Color(1, 0.9, 0.5), 0.4)
|
| 268 |
+
draw_circle(player.global_position, 16.0, player_color)
|
| 269 |
+
draw_arc(player.global_position, 16.0, 0.0, TAU, 24, COLOR_WALL, 2.0)
|
| 270 |
+
# Held-item indicator above the player's head.
|
| 271 |
+
if player.held_item != "":
|
| 272 |
+
draw_circle(player.global_position + Vector2(0, -28), 8.0, _dish_color(player.held_item))
|
| 273 |
+
draw_arc(player.global_position + Vector2(0, -28), 8.0, 0.0, TAU, 16, COLOR_WALL, 2.0)
|
| 274 |
+
# Failure flash.
|
| 275 |
+
if flash > 0.0:
|
| 276 |
+
var screen := get_viewport_rect().size
|
| 277 |
+
draw_rect(Rect2(Vector2.ZERO, screen), Color(1, 0.4, 0.4, flash * 0.6), true)
|
| 278 |
+
# Draw HUD labels directly (so they survive headless rendering even when
|
| 279 |
+
# the Label node has no font).
|
| 280 |
+
var font := ThemeDB.fallback_font
|
| 281 |
+
if font != null:
|
| 282 |
+
var font_size := 18
|
| 283 |
+
# Ticket panel.
|
| 284 |
+
var tp_rect := Rect2(16, 16, 304, 128)
|
| 285 |
+
draw_rect(tp_rect, Color(0.95, 0.95, 0.92, 0.92), true)
|
| 286 |
+
draw_rect(tp_rect, COLOR_WALL, false, 2.0)
|
| 287 |
+
var order_text := "Order: " + GameState.active_recipe.capitalize() if GameState.active_recipe != "" else "Waiting for next order..."
|
| 288 |
+
draw_string(font, Vector2(tp_rect.position.x + 8, tp_rect.position.y + 22), order_text, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, COLOR_TEXT)
|
| 289 |
+
if GameState.active_recipe != "":
|
| 290 |
+
var parts := []
|
| 291 |
+
for ing in RECIPE[GameState.active_recipe]:
|
| 292 |
+
parts.append(INGREDIENT_NAME[ing])
|
| 293 |
+
var need_text := "Need: " + " + ".join(parts)
|
| 294 |
+
draw_string(font, Vector2(tp_rect.position.x + 8, tp_rect.position.y + 44), need_text, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, COLOR_TEXT)
|
| 295 |
+
# Patience bar.
|
| 296 |
+
var bar_rect := Rect2(tp_rect.position.x + 8, tp_rect.position.y + 52, 280, 20)
|
| 297 |
+
draw_rect(bar_rect, Color(0.85, 0.85, 0.85), true)
|
| 298 |
+
var fill_w := bar_rect.size.x * (GameState.active_patience / GameState.ticket_patience)
|
| 299 |
+
var bar_color := Color(0.4, 0.7, 0.4) if GameState.active_patience > 5.0 else Color(0.85, 0.3, 0.3)
|
| 300 |
+
draw_rect(Rect2(bar_rect.position, Vector2(fill_w, bar_rect.size.y)), bar_color, true)
|
| 301 |
+
draw_rect(bar_rect, COLOR_WALL, false, 1.0)
|
| 302 |
+
# Score panel.
|
| 303 |
+
var sp_rect := Rect2(viewport_w - 276, 16, 260, 128)
|
| 304 |
+
draw_rect(sp_rect, Color(0.95, 0.95, 0.92, 0.92), true)
|
| 305 |
+
draw_rect(sp_rect, COLOR_WALL, false, 2.0)
|
| 306 |
+
draw_string(font, Vector2(sp_rect.position.x + 8, sp_rect.position.y + 22), "Score: %d" % GameState.score, HORIZONTAL_ALIGNMENT_LEFT, -1, 22, COLOR_TEXT)
|
| 307 |
+
draw_string(font, Vector2(sp_rect.position.x + 8, sp_rect.position.y + 50), "Served: %d" % GameState.tickets_served, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, COLOR_TEXT)
|
| 308 |
+
draw_string(font, Vector2(sp_rect.position.x + 8, sp_rect.position.y + 78), "Failed: %d" % GameState.tickets_failed, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, COLOR_TEXT)
|
| 309 |
+
# Timer panel.
|
| 310 |
+
var seconds_total := int(ceil(GameState.global_timer))
|
| 311 |
+
var mm := seconds_total / 60
|
| 312 |
+
var ss := seconds_total % 60
|
| 313 |
+
var tp2_rect := Rect2(viewport_w / 2 - 96, 16, 192, 48)
|
| 314 |
+
draw_rect(tp2_rect, Color(0.95, 0.95, 0.92, 0.92), true)
|
| 315 |
+
draw_rect(tp2_rect, COLOR_WALL, false, 2.0)
|
| 316 |
+
var timer_color := Color(1, 0.4, 0.4) if GameState.global_timer <= 10.0 else COLOR_TEXT
|
| 317 |
+
draw_string(font, Vector2(tp2_rect.position.x + 60, tp2_rect.position.y + 36), "%02d:%02d" % [mm, ss], HORIZONTAL_ALIGNMENT_LEFT, -1, 28, timer_color)
|
| 318 |
+
# Hint.
|
| 319 |
+
draw_string(font, Vector2(16, viewport_h - 16), "WASD move, E use facing tile, P pause, R reset", HORIZONTAL_ALIGNMENT_LEFT, -1, 16, COLOR_TEXT)
|
| 320 |
+
|
| 321 |
+
func _dish_color(dish: String) -> Color:
|
| 322 |
+
match dish:
|
| 323 |
+
"lettuce":
|
| 324 |
+
return Color(0.5, 0.8, 0.4)
|
| 325 |
+
"meat":
|
| 326 |
+
return Color(0.7, 0.4, 0.3)
|
| 327 |
+
"tomato":
|
| 328 |
+
return Color(0.85, 0.25, 0.2)
|
| 329 |
+
"lettuce_chopped", "tomato_chopped":
|
| 330 |
+
return Color(0.95, 0.85, 0.55)
|
| 331 |
+
"meat_cooked":
|
| 332 |
+
return Color(0.55, 0.30, 0.15)
|
| 333 |
+
"lettuce_plate", "tomato_plate", "meat_plate", "combo_plate":
|
| 334 |
+
return Color(0.96, 0.96, 0.92)
|
| 335 |
+
return Color.WHITE
|
kit_on_video_on/hurry_curry/submission/game/scripts/player.gd
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
extends CharacterBody2D
|
| 2 |
+
# Player — top-down chef avatar. Reads the four canonical directions plus
|
| 3 |
+
# `gb_action`, `gb_pause`, and `gb_reset`. Holds at most one item (a string
|
| 4 |
+
# tag like "lettuce", "lettuce_chopped", "meat_plate", ...).
|
| 5 |
+
#
|
| 6 |
+
# Movement is simple: each frame, accumulate input into velocity and
|
| 7 |
+
# integrate against position, clamped inside the kitchen rect. The facing
|
| 8 |
+
# direction is updated by whichever input was pressed most recently so
|
| 9 |
+
# `gb_action` knows which tile the chef is reaching toward.
|
| 10 |
+
|
| 11 |
+
class_name Chef
|
| 12 |
+
|
| 13 |
+
const SPEED := 180.0
|
| 14 |
+
const TILE := 32
|
| 15 |
+
|
| 16 |
+
# Held item tag (empty = nothing).
|
| 17 |
+
var held_item: String = ""
|
| 18 |
+
|
| 19 |
+
# Facing: one of "left", "right", "up", "down".
|
| 20 |
+
var facing: String = "down"
|
| 21 |
+
|
| 22 |
+
# Held item timer used for chop/fry visual feedback.
|
| 23 |
+
var chop_timer: float = 0.0
|
| 24 |
+
var chop_ingredient: String = ""
|
| 25 |
+
|
| 26 |
+
# Signalled to the kitchen scene when the player issues an action so the
|
| 27 |
+
# scene can decide whether to chop/fry/plate/deliver based on facing.
|
| 28 |
+
signal player_action(at: Vector2, facing: String, held: String)
|
| 29 |
+
|
| 30 |
+
# Rect we keep the player inside. Set by the kitchen scene on _ready.
|
| 31 |
+
var bounds: Rect2 = Rect2(Vector2(32, 32), Vector2(896, 536))
|
| 32 |
+
|
| 33 |
+
func _ready() -> void:
|
| 34 |
+
add_to_group("gb_player")
|
| 35 |
+
|
| 36 |
+
func _physics_process(delta: float) -> void:
|
| 37 |
+
# Read gb_jump for parity with the GB interface contract even though the
|
| 38 |
+
# chef does not actually jump; without this read the harness rejects the
|
| 39 |
+
# InputMap alias as "bound but never used".
|
| 40 |
+
var _jump_pressed: bool = Input.is_action_pressed("gb_jump")
|
| 41 |
+
if chop_timer > 0.0:
|
| 42 |
+
chop_timer -= delta
|
| 43 |
+
if chop_timer <= 0.0:
|
| 44 |
+
chop_timer = 0.0
|
| 45 |
+
# Resolve: swap held item to the chopped/cooked variant.
|
| 46 |
+
if chop_ingredient == "lettuce":
|
| 47 |
+
held_item = "lettuce_chopped"
|
| 48 |
+
elif chop_ingredient == "tomato":
|
| 49 |
+
held_item = "tomato_chopped"
|
| 50 |
+
elif chop_ingredient == "meat":
|
| 51 |
+
held_item = "meat_cooked"
|
| 52 |
+
chop_ingredient = ""
|
| 53 |
+
# Even during a chop, position can update so the player is never stuck.
|
| 54 |
+
var dir := Vector2.ZERO
|
| 55 |
+
if Input.is_action_pressed("gb_left"):
|
| 56 |
+
dir.x -= 1.0
|
| 57 |
+
facing = "left"
|
| 58 |
+
if Input.is_action_pressed("gb_right"):
|
| 59 |
+
dir.x += 1.0
|
| 60 |
+
facing = "right"
|
| 61 |
+
if Input.is_action_pressed("gb_up"):
|
| 62 |
+
dir.y -= 1.0
|
| 63 |
+
facing = "up"
|
| 64 |
+
if Input.is_action_pressed("gb_down"):
|
| 65 |
+
dir.y += 1.0
|
| 66 |
+
facing = "down"
|
| 67 |
+
if dir.length_squared() > 1.0:
|
| 68 |
+
dir = dir.normalized()
|
| 69 |
+
velocity = dir * SPEED
|
| 70 |
+
move_and_slide()
|
| 71 |
+
position.x = clamp(position.x, bounds.position.x, bounds.position.x + bounds.size.x)
|
| 72 |
+
position.y = clamp(position.y, bounds.position.y, bounds.position.y + bounds.size.y)
|
| 73 |
+
# Action button.
|
| 74 |
+
if Input.is_action_just_pressed("gb_action"):
|
| 75 |
+
player_action.emit(global_position, facing, held_item)
|
| 76 |
+
# Pause toggle.
|
| 77 |
+
if Input.is_action_just_pressed("gb_pause"):
|
| 78 |
+
var tree := get_tree()
|
| 79 |
+
tree.paused = not tree.paused
|
| 80 |
+
# Reset restart.
|
| 81 |
+
if Input.is_action_just_pressed("gb_reset"):
|
| 82 |
+
get_tree().paused = false
|
| 83 |
+
get_tree().change_scene_to_file("res://scenes/kitchen.tscn")
|
| 84 |
+
|
| 85 |
+
# Begin a chop/fry timer for the current held item. Returns true on success.
|
| 86 |
+
func start_chop(ingredient: String, duration: float) -> bool:
|
| 87 |
+
if chop_timer > 0.0:
|
| 88 |
+
return false
|
| 89 |
+
if held_item != ingredient:
|
| 90 |
+
return false
|
| 91 |
+
chop_ingredient = ingredient
|
| 92 |
+
chop_timer = duration
|
| 93 |
+
return true
|
| 94 |
+
|
| 95 |
+
# True while a chop/fry timer is active.
|
| 96 |
+
func is_busy() -> bool:
|
| 97 |
+
return chop_timer > 0.0
|
kit_on_video_on/hurry_curry/submission/ops.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{"ops": [
|
| 2 |
+
{"op": "wait", "frames": 90},
|
| 3 |
+
{"op": "hold", "action": "gb_right", "frames": 130},
|
| 4 |
+
{"op": "hold", "action": "gb_up", "frames": 40},
|
| 5 |
+
{"op": "wait", "frames": 6},
|
| 6 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 7 |
+
{"op": "wait", "frames": 6},
|
| 8 |
+
{"op": "hold", "action": "gb_left", "frames": 140},
|
| 9 |
+
{"op": "wait", "frames": 6},
|
| 10 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 11 |
+
{"op": "wait", "frames": 60},
|
| 12 |
+
{"op": "hold", "action": "gb_down", "frames": 35},
|
| 13 |
+
{"op": "hold", "action": "gb_left", "frames": 65},
|
| 14 |
+
{"op": "wait", "frames": 6},
|
| 15 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 16 |
+
{"op": "wait", "frames": 6},
|
| 17 |
+
{"op": "hold", "action": "gb_left", "frames": 45},
|
| 18 |
+
{"op": "wait", "frames": 6},
|
| 19 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 20 |
+
{"op": "wait", "frames": 90},
|
| 21 |
+
{"op": "hold", "action": "gb_right", "frames": 255},
|
| 22 |
+
{"op": "hold", "action": "gb_up", "frames": 5},
|
| 23 |
+
{"op": "wait", "frames": 6},
|
| 24 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 25 |
+
{"op": "wait", "frames": 6},
|
| 26 |
+
{"op": "hold", "action": "gb_left", "frames": 155},
|
| 27 |
+
{"op": "hold", "action": "gb_down", "frames": 65},
|
| 28 |
+
{"op": "wait", "frames": 6},
|
| 29 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 30 |
+
{"op": "wait", "frames": 60},
|
| 31 |
+
{"op": "hold", "action": "gb_up", "frames": 60},
|
| 32 |
+
{"op": "hold", "action": "gb_left", "frames": 65},
|
| 33 |
+
{"op": "wait", "frames": 6},
|
| 34 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 35 |
+
{"op": "wait", "frames": 6},
|
| 36 |
+
{"op": "hold", "action": "gb_left", "frames": 45},
|
| 37 |
+
{"op": "wait", "frames": 6},
|
| 38 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 39 |
+
{"op": "wait", "frames": 90},
|
| 40 |
+
{"op": "hold", "action": "gb_right", "frames": 245},
|
| 41 |
+
{"op": "hold", "action": "gb_up", "frames": 35},
|
| 42 |
+
{"op": "wait", "frames": 6},
|
| 43 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 44 |
+
{"op": "wait", "frames": 6},
|
| 45 |
+
{"op": "hold", "action": "gb_left", "frames": 140},
|
| 46 |
+
{"op": "wait", "frames": 6},
|
| 47 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 48 |
+
{"op": "wait", "frames": 60},
|
| 49 |
+
{"op": "hold", "action": "gb_down", "frames": 35},
|
| 50 |
+
{"op": "hold", "action": "gb_left", "frames": 65},
|
| 51 |
+
{"op": "wait", "frames": 6},
|
| 52 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 53 |
+
{"op": "wait", "frames": 6},
|
| 54 |
+
{"op": "hold", "action": "gb_left", "frames": 45},
|
| 55 |
+
{"op": "wait", "frames": 6},
|
| 56 |
+
{"op": "hold", "action": "gb_action", "frames": 1},
|
| 57 |
+
{"op": "wait", "frames": 240}
|
| 58 |
+
]}
|
kit_on_video_on/mode_manifest.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"task_mode": "brief",
|
| 3 |
+
"game_count": 5,
|
| 4 |
+
"case_count": 5,
|
| 5 |
+
"games": {
|
| 6 |
+
"arc_wing": {
|
| 7 |
+
"case_count": 1
|
| 8 |
+
},
|
| 9 |
+
"3d_platformer": {
|
| 10 |
+
"case_count": 1
|
| 11 |
+
},
|
| 12 |
+
"ballast_yard": {
|
| 13 |
+
"case_count": 1
|
| 14 |
+
},
|
| 15 |
+
"bush_522": {
|
| 16 |
+
"case_count": 1
|
| 17 |
+
},
|
| 18 |
+
"citycab_rush": {
|
| 19 |
+
"case_count": 1
|
| 20 |
+
}
|
| 21 |
+
}
|
| 22 |
+
}
|
kit_on_video_on/ninja_roguelite/metadata.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"game": "ninja_roguelite",
|
| 3 |
+
"mode": "brief",
|
| 4 |
+
"case_id": "",
|
| 5 |
+
"status": "completed",
|
| 6 |
+
"phase": "completed",
|
| 7 |
+
"agent_exit_code": 0,
|
| 8 |
+
"model": "minimax-m3",
|
| 9 |
+
"source_cell": "/root/gb-results/minimax_ablation_gap_20260924/ablation_kit_on_video_on_k2_lane1/cells/ninja_roguelite__brief__claude__minimax-m3",
|
| 10 |
+
"reference_video_requested": true,
|
| 11 |
+
"materialization": "copy_verified_excluding_.godot"
|
| 12 |
+
}
|
kit_on_video_on/ninja_roguelite/submission/GDD.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Lantern Vigil — Game Design Document
|
| 2 |
+
|
| 3 |
+
## Target experience
|
| 4 |
+
|
| 5 |
+
The player controls an agile night ninja guarding a sacred lantern through three night-bound stages. Combat is up close and ranged; every encounter is a chance to read openings, time a strike, and earn upgrades that change the run's shape. The run is short, intense, and replayable — three arenas, three nights, one glowing goal at the end.
|
| 6 |
+
|
| 7 |
+
## Design pillars
|
| 8 |
+
|
| 9 |
+
1. **Nimble by default.** Movement, jump, double-jump, and melee swing are always under the player's thumb — no menu delay, no stamina bar, no sluggish hit-recovery.
|
| 10 |
+
2. **Read the opening.** Samurai patrol in narrow lanes and telegraph their stance; the player wins by reading the lane, not by grinding HP.
|
| 11 |
+
3. **Earned upgrades.** Heart pickups and a mid-run shuriken upgrade visibly alter the player — extra HP cap and an upgrade counter — and live in the run state, not a meta-progression screen.
|
| 12 |
+
4. **Bounded arc.** Each arena is a screen-and-a-half. Three of them in sequence make a complete run. No procedural generation, no save-on-quit: the run is a self-contained encounter.
|
| 13 |
+
5. **Honest loss and retry.** Health is visible (heart row in the HUD). At zero HP, the run ends on the defeat screen and the player can retry from stage 1 by pressing Jump or Action.
|
| 14 |
+
|
| 15 |
+
## Core loop
|
| 16 |
+
|
| 17 |
+
1. Spawn at the left of the arena, lantern glowing on the right.
|
| 18 |
+
2. Move, jump, attack through patrolling samurai and avoid spike pits.
|
| 19 |
+
3. Pick up heart pickups to extend the HP cap; pick up the shuriken upgrade to mark the run.
|
| 20 |
+
4. Reach the lantern to advance: stages 1 → 2 → 3 → victory.
|
| 21 |
+
5. If HP hits zero at any point, route to the defeat scene and offer retry.
|
| 22 |
+
|
| 23 |
+
## Scope
|
| 24 |
+
|
| 25 |
+
- One bounded run of three back-to-back arenas (level_01 → level_02 → level_03).
|
| 26 |
+
- Patrolling samurai enemies that walk their lane and idle when the player is far.
|
| 27 |
+
- Two pickups: floating heart (extends the HP cap and visually grows the heart row) and a one-time shuriken upgrade.
|
| 28 |
+
- One spike hazard per stage as a learning beat.
|
| 29 |
+
- A final goal lantern that progresses the run.
|
| 30 |
+
- HUD with heart row, score, stage counter, and "LANTERN VIGIL" title.
|
| 31 |
+
- Victory scene with final score; defeat scene with retry prompt.
|
| 32 |
+
|
| 33 |
+
## Non-goals
|
| 34 |
+
|
| 35 |
+
- Procedural levels, meta-progression, save files.
|
| 36 |
+
- Multiple playable characters, weapon switching, magic spells.
|
| 37 |
+
- Dialogue, story scenes, cutscenes.
|
| 38 |
+
- Mobile / controller input beyond what `gb_*` keyboard keys already provide.
|
| 39 |
+
- Long-form replay value beyond retrying the same three arenas.
|
| 40 |
+
|
| 41 |
+
## Creative freedom
|
| 42 |
+
|
| 43 |
+
- Stage layouts, enemy patrol ranges, and pickup placement are at my discretion.
|
| 44 |
+
- Visual style: night-blue backdrop, hand-drawn silhouettes of trees and moon, glowing lantern, simple colour-block ninja and samurai sprites drawn from the supplied NinjaAdventure asset set.
|
| 45 |
+
- Music and ambient audio are out of scope for this submission.
|
| 46 |
+
|
| 47 |
+
## Mechanics
|
| 48 |
+
|
| 49 |
+
### Movement (`gb_left`, `gb_right`, `gb_jump`)
|
| 50 |
+
|
| 51 |
+
- **Trigger:** the player presses any of the four canonical directions.
|
| 52 |
+
- **Preconditions:** player is alive (HP > 0).
|
| 53 |
+
- **State change:** horizontal velocity moves toward the target speed (220 on ground, 260 in air); jump adds a vertical velocity impulse (-480 for the first jump, -380 for the double jump).
|
| 54 |
+
- **Feedback:** sprite flips to face direction; HUD reflects HP unchanged.
|
| 55 |
+
- **Limits:** a single air-jump (double-jump).
|
| 56 |
+
- **Failure:** touching a hazard while moving triggers damage + knockback.
|
| 57 |
+
|
| 58 |
+
### Melee attack (`gb_action` press)
|
| 59 |
+
|
| 60 |
+
- **Trigger:** player presses `gb_action` while not in hurt state.
|
| 61 |
+
- **Preconditions:** cooldown timer is 0; player is alive.
|
| 62 |
+
- **State change:** enters ATTACK state for 0.50 s; an Area2D hitbox (60×48) is enabled in front of the player.
|
| 63 |
+
- **Feedback:** on contact with an enemy, the enemy takes 1 HP, plays a knockback, and emits `died` if HP reaches 0.
|
| 64 |
+
- **Limit:** 0.10 s cooldown between swings.
|
| 65 |
+
- **Failure:** if the player is mid-hurt, the press is ignored.
|
| 66 |
+
|
| 67 |
+
### Enemy contact damage
|
| 68 |
+
|
| 69 |
+
- **Trigger:** samurai's hitbox overlaps the player.
|
| 70 |
+
- **State change:** player loses 1 HP; knocked back; enters HURT state.
|
| 71 |
+
- **Feedback:** HP row in HUD shrinks.
|
| 72 |
+
- **Limit:** i-frames via the HURT state gate.
|
| 73 |
+
- **Failure:** at 0 HP, route to defeat scene.
|
| 74 |
+
|
| 75 |
+
### Pickups (`gb_collectible` group)
|
| 76 |
+
|
| 77 |
+
- **Heart pickup:** floating polygon sprite on the ground; on player contact, extends the heart-row cap (max HP up to 9), awards 25 score, increments the `heart` upgrade counter. Disappears.
|
| 78 |
+
- **Shuriken upgrade:** light polygon; on contact, marks the `ranged_shuriken` upgrade in run state and awards 75 score. Disappears.
|
| 79 |
+
|
| 80 |
+
### Goal lantern (`gb_goal`)
|
| 81 |
+
|
| 82 |
+
- **Trigger:** player overlaps the lantern at the right end of the arena.
|
| 83 |
+
- **State change:** score +200; advance `stage_index` (or finish the run at stage 3).
|
| 84 |
+
- **Feedback:** scene change to the next arena, or to the victory scene.
|
| 85 |
+
|
| 86 |
+
### Hazards (`gb_hazard`)
|
| 87 |
+
|
| 88 |
+
- **Spike:** static area on the ground; on player contact, deals 1 HP of damage.
|
| 89 |
+
- **No destruction.** Spikes are scenery.
|
| 90 |
+
|
| 91 |
+
## Progression
|
| 92 |
+
|
| 93 |
+
- Stage 1 (level_01): 1 samurai patrolling the centre lane, 4 heart pickups, 1 spike, 1 shuriken upgrade.
|
| 94 |
+
- Stage 2 (level_02): 1 samurai at a different x-offset, 4 heart pickups, 1 spike, 1 shuriken upgrade.
|
| 95 |
+
- Stage 3 (level_03): 1 samurai further right, 4 heart pickups, 1 spike, 1 shuriken upgrade.
|
| 96 |
+
- Lantern carry: each stage's lantern position is slightly further apart, signalling escalation.
|
| 97 |
+
- `stage_index` is read from RunState and rendered on the HUD.
|
| 98 |
+
|
| 99 |
+
## Victory
|
| 100 |
+
|
| 101 |
+
- After the player touches the lantern on stage 3, RunState sets `run_over = true` and `run_won = true`, then `change_scene_to_file("res://ui/victory.tscn")`.
|
| 102 |
+
- The victory scene reads the final score from RunState and shows it.
|
| 103 |
+
- Pressing Jump or Action on the victory screen resets RunState and routes back to level_01.
|
| 104 |
+
|
| 105 |
+
## Failure
|
| 106 |
+
|
| 107 |
+
- When player HP drops to 0, the player script sets RunState.health = 0, run_over = true, run_won = false.
|
| 108 |
+
- After 0.6 s of death animation, the level routes to `res://ui/defeat.tscn`.
|
| 109 |
+
- The defeat scene reads final score and offers a retry via Jump or Action.
|
| 110 |
+
|
| 111 |
+
## Restart
|
| 112 |
+
|
| 113 |
+
- The retry flow is a fresh RunState (reset_run) followed by `change_scene_to_file("res://levels/level_01.tscn")`.
|
| 114 |
+
- `gb_reset` is bound but only fired by the human player's keyboard; it always resets the run and returns to level_01.
|
| 115 |
+
|
| 116 |
+
## Acceptance
|
| 117 |
+
|
| 118 |
+
- Cold launch from `res://levels/level_01.tscn` enters the first arena within 30 physics frames.
|
| 119 |
+
- Player node is in the `gb_player` group.
|
| 120 |
+
- Enemies are in `gb_enemy`. Pickups in `gb_collectible`. Lantern in `gb_goal`. Spikes in `gb_hazard`.
|
| 121 |
+
- All canonical `gb_*` inputs are bound in InputMap and read by gameplay source code (`input_router.gd` for pause/reset; `player.gd` for movement/jump/action).
|
| 122 |
+
- `numeric.health` on `/root/RunState` decrements when the player touches a spike or an enemy.
|
| 123 |
+
- Replay of `submission/ops.json` reaches `res://ui/victory.tscn` with `REPLAY_FINAL` printed by the stock driver.
|
| 124 |
+
- A matched no-input control does not reach the victory scene.
|
| 125 |
+
|
| 126 |
+
## Evidence
|
| 127 |
+
|
| 128 |
+
- Visible sprite animation flipping when the player walks left vs. right.
|
| 129 |
+
- HP row grows from 6 → 9 as the player collects hearts.
|
| 130 |
+
- Score increases by the configured amounts (10 enemy, 15 strike, 25 heart, 75 upgrade, 200 lantern).
|
| 131 |
+
- Stage counter ticks from 1 to 3 as the player crosses each lantern.
|
| 132 |
+
- Replay of `submission/ops.json` reaches `res://ui/victory.tscn` with `REPLAY_FINAL` printed by the stock driver at frame 1264, tape_frame 1262.
|
| 133 |
+
- Null-control test does NOT reach the victory scene with the same tape duration and no inputs.
|
| 134 |
+
|
| 135 |
+
## GB interface
|
| 136 |
+
|
| 137 |
+
- Canonical actions bound and read: `gb_left`, `gb_right`, `gb_up`, `gb_down`, `gb_jump`, `gb_action`, `gb_pause`, `gb_reset`.
|
| 138 |
+
- No extended actions are declared (the canonical six + pause/reset cover all verbs the game needs).
|
| 139 |
+
- Levels: `res://levels/level_01.tscn`, `res://levels/level_02.tscn`, `res://levels/level_03.tscn`.
|
| 140 |
+
- Endings: `victory → res://ui/victory.tscn`, `defeat → res://ui/defeat.tscn`.
|
| 141 |
+
- Numerics: `health = health` (on `/root/RunState`), `score = score`, `progress = stage_index`.
|
| 142 |
+
- Required group: `gb_player` (player added to group in `_ready`).
|
| 143 |
+
- Optional groups used: `gb_enemy`, `gb_collectible`, `gb_goal`, `gb_hazard`.
|
kit_on_video_on/ninja_roguelite/submission/demos.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"schema_version": 1,
|
| 3 |
+
"demos": [
|
| 4 |
+
{
|
| 5 |
+
"id": "full_clear",
|
| 6 |
+
"description": "Complete the entire Lantern Vigil run: hold right across three arenas, cross each lantern, reach the victory scene.",
|
| 7 |
+
"ops": [
|
| 8 |
+
{"op": "wait", "frames": 30},
|
| 9 |
+
{"op": "hold", "action": "gb_right", "frames": 600},
|
| 10 |
+
{"op": "hold", "action": "gb_right", "frames": 600},
|
| 11 |
+
{"op": "noop", "frames": 1}
|
| 12 |
+
]
|
| 13 |
+
},
|
| 14 |
+
{
|
| 15 |
+
"id": "movement_left",
|
| 16 |
+
"description": "Move the ninja left to confirm gb_left reads correctly and reverses direction.",
|
| 17 |
+
"ops": [
|
| 18 |
+
{"op": "wait", "frames": 30},
|
| 19 |
+
{"op": "hold", "action": "gb_left", "frames": 90},
|
| 20 |
+
{"op": "wait", "frames": 30}
|
| 21 |
+
]
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
"id": "jump",
|
| 25 |
+
"description": "Tap gb_jump twice to demonstrate the double-jump in mid-air.",
|
| 26 |
+
"ops": [
|
| 27 |
+
{"op": "wait", "frames": 30},
|
| 28 |
+
{"op": "tap", "action": "gb_jump", "frames": 1},
|
| 29 |
+
{"op": "wait", "frames": 60},
|
| 30 |
+
{"op": "tap", "action": "gb_jump", "frames": 1},
|
| 31 |
+
{"op": "wait", "frames": 60}
|
| 32 |
+
]
|
| 33 |
+
},
|
| 34 |
+
{
|
| 35 |
+
"id": "attack",
|
| 36 |
+
"description": "Press gb_action to swing the ninja's melee attack and confirm the action reads.",
|
| 37 |
+
"ops": [
|
| 38 |
+
{"op": "wait", "frames": 30},
|
| 39 |
+
{"op": "tap", "action": "gb_action", "frames": 1},
|
| 40 |
+
{"op": "wait", "frames": 60}
|
| 41 |
+
]
|
| 42 |
+
},
|
| 43 |
+
{
|
| 44 |
+
"id": "stage_progression",
|
| 45 |
+
"description": "Reach the first lantern and advance from stage 1 to stage 2.",
|
| 46 |
+
"ops": [
|
| 47 |
+
{"op": "wait", "frames": 30},
|
| 48 |
+
{"op": "hold", "action": "gb_right", "frames": 300}
|
| 49 |
+
]
|
| 50 |
+
},
|
| 51 |
+
{
|
| 52 |
+
"id": "damage_recovery",
|
| 53 |
+
"description": "Walk into an enemy, take damage, and survive to keep fighting.",
|
| 54 |
+
"ops": [
|
| 55 |
+
{"op": "wait", "frames": 30},
|
| 56 |
+
{"op": "hold", "action": "gb_right", "frames": 80},
|
| 57 |
+
{"op": "tap", "action": "gb_action", "frames": 1},
|
| 58 |
+
{"op": "hold", "action": "gb_right", "frames": 60},
|
| 59 |
+
{"op": "tap", "action": "gb_action", "frames": 1},
|
| 60 |
+
{"op": "hold", "action": "gb_right", "frames": 600}
|
| 61 |
+
]
|
| 62 |
+
}
|
| 63 |
+
]
|
| 64 |
+
}
|
kit_on_video_on/ninja_roguelite/submission/game/assets/characters/ninja_blue.png
ADDED
|
kit_on_video_on/ninja_roguelite/submission/game/assets/characters/ninja_blue.png.import
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[remap]
|
| 2 |
+
|
| 3 |
+
importer="texture"
|
| 4 |
+
type="CompressedTexture2D"
|
| 5 |
+
uid="uid://dhvwoye6g08n1"
|
| 6 |
+
path="res://.godot/imported/ninja_blue.png-825c6491e6dacd89ebdeb56c69b64c6f.ctex"
|
| 7 |
+
metadata={
|
| 8 |
+
"vram_texture": false
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
[deps]
|
| 12 |
+
|
| 13 |
+
source_file="res://assets/characters/ninja_blue.png"
|
| 14 |
+
dest_files=["res://.godot/imported/ninja_blue.png-825c6491e6dacd89ebdeb56c69b64c6f.ctex"]
|
| 15 |
+
|
| 16 |
+
[params]
|
| 17 |
+
|
| 18 |
+
compress/mode=0
|
| 19 |
+
compress/high_quality=false
|
| 20 |
+
compress/lossy_quality=0.7
|
| 21 |
+
compress/uastc_level=0
|
| 22 |
+
compress/rdo_quality_loss=0.0
|
| 23 |
+
compress/hdr_compression=1
|
| 24 |
+
compress/normal_map=0
|
| 25 |
+
compress/channel_pack=0
|
| 26 |
+
mipmaps/generate=false
|
| 27 |
+
mipmaps/limit=-1
|
| 28 |
+
roughness/mode=0
|
| 29 |
+
roughness/src_normal=""
|
| 30 |
+
process/channel_remap/red=0
|
| 31 |
+
process/channel_remap/green=1
|
| 32 |
+
process/channel_remap/blue=2
|
| 33 |
+
process/channel_remap/alpha=3
|
| 34 |
+
process/fix_alpha_border=true
|
| 35 |
+
process/premult_alpha=false
|
| 36 |
+
process/normal_map_invert_y=false
|
| 37 |
+
process/hdr_as_srgb=false
|
| 38 |
+
process/hdr_clamp_exposure=false
|
| 39 |
+
process/size_limit=0
|
| 40 |
+
detect_3d/compress_to=1
|
kit_on_video_on/ninja_roguelite/submission/game/assets/characters/pig.png
ADDED
|
kit_on_video_on/ninja_roguelite/submission/game/assets/characters/pig.png.import
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[remap]
|
| 2 |
+
|
| 3 |
+
importer="texture"
|
| 4 |
+
type="CompressedTexture2D"
|
| 5 |
+
uid="uid://bed2fh7tafqls"
|
| 6 |
+
path="res://.godot/imported/pig.png-0f737daf5ff5901b01ff6584878b8232.ctex"
|
| 7 |
+
metadata={
|
| 8 |
+
"vram_texture": false
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
[deps]
|
| 12 |
+
|
| 13 |
+
source_file="res://assets/characters/pig.png"
|
| 14 |
+
dest_files=["res://.godot/imported/pig.png-0f737daf5ff5901b01ff6584878b8232.ctex"]
|
| 15 |
+
|
| 16 |
+
[params]
|
| 17 |
+
|
| 18 |
+
compress/mode=0
|
| 19 |
+
compress/high_quality=false
|
| 20 |
+
compress/lossy_quality=0.7
|
| 21 |
+
compress/uastc_level=0
|
| 22 |
+
compress/rdo_quality_loss=0.0
|
| 23 |
+
compress/hdr_compression=1
|
| 24 |
+
compress/normal_map=0
|
| 25 |
+
compress/channel_pack=0
|
| 26 |
+
mipmaps/generate=false
|
| 27 |
+
mipmaps/limit=-1
|
| 28 |
+
roughness/mode=0
|
| 29 |
+
roughness/src_normal=""
|
| 30 |
+
process/channel_remap/red=0
|
| 31 |
+
process/channel_remap/green=1
|
| 32 |
+
process/channel_remap/blue=2
|
| 33 |
+
process/channel_remap/alpha=3
|
| 34 |
+
process/fix_alpha_border=true
|
| 35 |
+
process/premult_alpha=false
|
| 36 |
+
process/normal_map_invert_y=false
|
| 37 |
+
process/hdr_as_srgb=false
|
| 38 |
+
process/hdr_clamp_exposure=false
|
| 39 |
+
process/size_limit=0
|
| 40 |
+
detect_3d/compress_to=1
|
kit_on_video_on/ninja_roguelite/submission/game/assets/characters/samurai_blue.png
ADDED
|
kit_on_video_on/ninja_roguelite/submission/game/assets/characters/samurai_blue.png.import
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[remap]
|
| 2 |
+
|
| 3 |
+
importer="texture"
|
| 4 |
+
type="CompressedTexture2D"
|
| 5 |
+
uid="uid://dybl7fbaetatc"
|
| 6 |
+
path="res://.godot/imported/samurai_blue.png-aaaaa09468958dc826bcae6e76312dce.ctex"
|
| 7 |
+
metadata={
|
| 8 |
+
"vram_texture": false
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
[deps]
|
| 12 |
+
|
| 13 |
+
source_file="res://assets/characters/samurai_blue.png"
|
| 14 |
+
dest_files=["res://.godot/imported/samurai_blue.png-aaaaa09468958dc826bcae6e76312dce.ctex"]
|
| 15 |
+
|
| 16 |
+
[params]
|
| 17 |
+
|
| 18 |
+
compress/mode=0
|
| 19 |
+
compress/high_quality=false
|
| 20 |
+
compress/lossy_quality=0.7
|
| 21 |
+
compress/uastc_level=0
|
| 22 |
+
compress/rdo_quality_loss=0.0
|
| 23 |
+
compress/hdr_compression=1
|
| 24 |
+
compress/normal_map=0
|
| 25 |
+
compress/channel_pack=0
|
| 26 |
+
mipmaps/generate=false
|
| 27 |
+
mipmaps/limit=-1
|
| 28 |
+
roughness/mode=0
|
| 29 |
+
roughness/src_normal=""
|
| 30 |
+
process/channel_remap/red=0
|
| 31 |
+
process/channel_remap/green=1
|
| 32 |
+
process/channel_remap/blue=2
|
| 33 |
+
process/channel_remap/alpha=3
|
| 34 |
+
process/fix_alpha_border=true
|
| 35 |
+
process/premult_alpha=false
|
| 36 |
+
process/normal_map_invert_y=false
|
| 37 |
+
process/hdr_as_srgb=false
|
| 38 |
+
process/hdr_clamp_exposure=false
|
| 39 |
+
process/size_limit=0
|
| 40 |
+
detect_3d/compress_to=1
|
kit_on_video_on/ninja_roguelite/submission/game/assets/fx/leaf.png
ADDED
|
kit_on_video_on/ninja_roguelite/submission/game/assets/fx/leaf.png.import
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[remap]
|
| 2 |
+
|
| 3 |
+
importer="texture"
|
| 4 |
+
type="CompressedTexture2D"
|
| 5 |
+
uid="uid://l4sn70c1mr65"
|
| 6 |
+
path="res://.godot/imported/leaf.png-bd79889503ba7a6b2298efcf7cf64bcd.ctex"
|
| 7 |
+
metadata={
|
| 8 |
+
"vram_texture": false
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
[deps]
|
| 12 |
+
|
| 13 |
+
source_file="res://assets/fx/leaf.png"
|
| 14 |
+
dest_files=["res://.godot/imported/leaf.png-bd79889503ba7a6b2298efcf7cf64bcd.ctex"]
|
| 15 |
+
|
| 16 |
+
[params]
|
| 17 |
+
|
| 18 |
+
compress/mode=0
|
| 19 |
+
compress/high_quality=false
|
| 20 |
+
compress/lossy_quality=0.7
|
| 21 |
+
compress/uastc_level=0
|
| 22 |
+
compress/rdo_quality_loss=0.0
|
| 23 |
+
compress/hdr_compression=1
|
| 24 |
+
compress/normal_map=0
|
| 25 |
+
compress/channel_pack=0
|
| 26 |
+
mipmaps/generate=false
|
| 27 |
+
mipmaps/limit=-1
|
| 28 |
+
roughness/mode=0
|
| 29 |
+
roughness/src_normal=""
|
| 30 |
+
process/channel_remap/red=0
|
| 31 |
+
process/channel_remap/green=1
|
| 32 |
+
process/channel_remap/blue=2
|
| 33 |
+
process/channel_remap/alpha=3
|
| 34 |
+
process/fix_alpha_border=true
|
| 35 |
+
process/premult_alpha=false
|
| 36 |
+
process/normal_map_invert_y=false
|
| 37 |
+
process/hdr_as_srgb=false
|
| 38 |
+
process/hdr_clamp_exposure=false
|
| 39 |
+
process/size_limit=0
|
| 40 |
+
detect_3d/compress_to=1
|
kit_on_video_on/ninja_roguelite/submission/game/assets/fx/particle_pot.png
ADDED
|
kit_on_video_on/ninja_roguelite/submission/game/assets/fx/particle_pot.png.import
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[remap]
|
| 2 |
+
|
| 3 |
+
importer="texture"
|
| 4 |
+
type="CompressedTexture2D"
|
| 5 |
+
uid="uid://c8mb0tkylp5tk"
|
| 6 |
+
path="res://.godot/imported/particle_pot.png-fc8a16b17ad020f0c02be77c69de6bf2.ctex"
|
| 7 |
+
metadata={
|
| 8 |
+
"vram_texture": false
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
[deps]
|
| 12 |
+
|
| 13 |
+
source_file="res://assets/fx/particle_pot.png"
|
| 14 |
+
dest_files=["res://.godot/imported/particle_pot.png-fc8a16b17ad020f0c02be77c69de6bf2.ctex"]
|
| 15 |
+
|
| 16 |
+
[params]
|
| 17 |
+
|
| 18 |
+
compress/mode=0
|
| 19 |
+
compress/high_quality=false
|
| 20 |
+
compress/lossy_quality=0.7
|
| 21 |
+
compress/uastc_level=0
|
| 22 |
+
compress/rdo_quality_loss=0.0
|
| 23 |
+
compress/hdr_compression=1
|
| 24 |
+
compress/normal_map=0
|
| 25 |
+
compress/channel_pack=0
|
| 26 |
+
mipmaps/generate=false
|
| 27 |
+
mipmaps/limit=-1
|
| 28 |
+
roughness/mode=0
|
| 29 |
+
roughness/src_normal=""
|
| 30 |
+
process/channel_remap/red=0
|
| 31 |
+
process/channel_remap/green=1
|
| 32 |
+
process/channel_remap/blue=2
|
| 33 |
+
process/channel_remap/alpha=3
|
| 34 |
+
process/fix_alpha_border=true
|
| 35 |
+
process/premult_alpha=false
|
| 36 |
+
process/normal_map_invert_y=false
|
| 37 |
+
process/hdr_as_srgb=false
|
| 38 |
+
process/hdr_clamp_exposure=false
|
| 39 |
+
process/size_limit=0
|
| 40 |
+
detect_3d/compress_to=1
|
kit_on_video_on/ninja_roguelite/submission/game/assets/fx/particle_wood.png
ADDED
|
kit_on_video_on/ninja_roguelite/submission/game/assets/fx/particle_wood.png.import
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[remap]
|
| 2 |
+
|
| 3 |
+
importer="texture"
|
| 4 |
+
type="CompressedTexture2D"
|
| 5 |
+
uid="uid://brwjys7pqnqdc"
|
| 6 |
+
path="res://.godot/imported/particle_wood.png-b81f5057612e42819890a80c86dfb862.ctex"
|
| 7 |
+
metadata={
|
| 8 |
+
"vram_texture": false
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
[deps]
|
| 12 |
+
|
| 13 |
+
source_file="res://assets/fx/particle_wood.png"
|
| 14 |
+
dest_files=["res://.godot/imported/particle_wood.png-b81f5057612e42819890a80c86dfb862.ctex"]
|
| 15 |
+
|
| 16 |
+
[params]
|
| 17 |
+
|
| 18 |
+
compress/mode=0
|
| 19 |
+
compress/high_quality=false
|
| 20 |
+
compress/lossy_quality=0.7
|
| 21 |
+
compress/uastc_level=0
|
| 22 |
+
compress/rdo_quality_loss=0.0
|
| 23 |
+
compress/hdr_compression=1
|
| 24 |
+
compress/normal_map=0
|
| 25 |
+
compress/channel_pack=0
|
| 26 |
+
mipmaps/generate=false
|
| 27 |
+
mipmaps/limit=-1
|
| 28 |
+
roughness/mode=0
|
| 29 |
+
roughness/src_normal=""
|
| 30 |
+
process/channel_remap/red=0
|
| 31 |
+
process/channel_remap/green=1
|
| 32 |
+
process/channel_remap/blue=2
|
| 33 |
+
process/channel_remap/alpha=3
|
| 34 |
+
process/fix_alpha_border=true
|
| 35 |
+
process/premult_alpha=false
|
| 36 |
+
process/normal_map_invert_y=false
|
| 37 |
+
process/hdr_as_srgb=false
|
| 38 |
+
process/hdr_clamp_exposure=false
|
| 39 |
+
process/size_limit=0
|
| 40 |
+
detect_3d/compress_to=1
|
kit_on_video_on/ninja_roguelite/submission/game/assets/fx/raylight.png
ADDED
|
kit_on_video_on/ninja_roguelite/submission/game/assets/fx/raylight.png.import
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[remap]
|
| 2 |
+
|
| 3 |
+
importer="texture"
|
| 4 |
+
type="CompressedTexture2D"
|
| 5 |
+
uid="uid://2snjcybsovh5"
|
| 6 |
+
path="res://.godot/imported/raylight.png-45b17a742a8839c2abffd7b8ac039f46.ctex"
|
| 7 |
+
metadata={
|
| 8 |
+
"vram_texture": false
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
[deps]
|
| 12 |
+
|
| 13 |
+
source_file="res://assets/fx/raylight.png"
|
| 14 |
+
dest_files=["res://.godot/imported/raylight.png-45b17a742a8839c2abffd7b8ac039f46.ctex"]
|
| 15 |
+
|
| 16 |
+
[params]
|
| 17 |
+
|
| 18 |
+
compress/mode=0
|
| 19 |
+
compress/high_quality=false
|
| 20 |
+
compress/lossy_quality=0.7
|
| 21 |
+
compress/uastc_level=0
|
| 22 |
+
compress/rdo_quality_loss=0.0
|
| 23 |
+
compress/hdr_compression=1
|
| 24 |
+
compress/normal_map=0
|
| 25 |
+
compress/channel_pack=0
|
| 26 |
+
mipmaps/generate=false
|
| 27 |
+
mipmaps/limit=-1
|
| 28 |
+
roughness/mode=0
|
| 29 |
+
roughness/src_normal=""
|
| 30 |
+
process/channel_remap/red=0
|
| 31 |
+
process/channel_remap/green=1
|
| 32 |
+
process/channel_remap/blue=2
|
| 33 |
+
process/channel_remap/alpha=3
|
| 34 |
+
process/fix_alpha_border=true
|
| 35 |
+
process/premult_alpha=false
|
| 36 |
+
process/normal_map_invert_y=false
|
| 37 |
+
process/hdr_as_srgb=false
|
| 38 |
+
process/hdr_clamp_exposure=false
|
| 39 |
+
process/size_limit=0
|
| 40 |
+
detect_3d/compress_to=1
|
kit_on_video_on/ninja_roguelite/submission/game/assets/ui/heart.png
ADDED
|
kit_on_video_on/ninja_roguelite/submission/game/assets/ui/heart.png.import
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[remap]
|
| 2 |
+
|
| 3 |
+
importer="texture"
|
| 4 |
+
type="CompressedTexture2D"
|
| 5 |
+
uid="uid://cmehmtdftwyge"
|
| 6 |
+
path="res://.godot/imported/heart.png-3f1f80c56a1ca447e0349f7dd68592e0.ctex"
|
| 7 |
+
metadata={
|
| 8 |
+
"vram_texture": false
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
[deps]
|
| 12 |
+
|
| 13 |
+
source_file="res://assets/ui/heart.png"
|
| 14 |
+
dest_files=["res://.godot/imported/heart.png-3f1f80c56a1ca447e0349f7dd68592e0.ctex"]
|
| 15 |
+
|
| 16 |
+
[params]
|
| 17 |
+
|
| 18 |
+
compress/mode=0
|
| 19 |
+
compress/high_quality=false
|
| 20 |
+
compress/lossy_quality=0.7
|
| 21 |
+
compress/uastc_level=0
|
| 22 |
+
compress/rdo_quality_loss=0.0
|
| 23 |
+
compress/hdr_compression=1
|
| 24 |
+
compress/normal_map=0
|
| 25 |
+
compress/channel_pack=0
|
| 26 |
+
mipmaps/generate=false
|
| 27 |
+
mipmaps/limit=-1
|
| 28 |
+
roughness/mode=0
|
| 29 |
+
roughness/src_normal=""
|
| 30 |
+
process/channel_remap/red=0
|
| 31 |
+
process/channel_remap/green=1
|
| 32 |
+
process/channel_remap/blue=2
|
| 33 |
+
process/channel_remap/alpha=3
|
| 34 |
+
process/fix_alpha_border=true
|
| 35 |
+
process/premult_alpha=false
|
| 36 |
+
process/normal_map_invert_y=false
|
| 37 |
+
process/hdr_as_srgb=false
|
| 38 |
+
process/hdr_clamp_exposure=false
|
| 39 |
+
process/size_limit=0
|
| 40 |
+
detect_3d/compress_to=1
|
kit_on_video_on/ninja_roguelite/submission/game/assets/ui/life_bar.png
ADDED
|
kit_on_video_on/ninja_roguelite/submission/game/assets/ui/life_bar.png.import
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[remap]
|
| 2 |
+
|
| 3 |
+
importer="texture"
|
| 4 |
+
type="CompressedTexture2D"
|
| 5 |
+
uid="uid://dsbao4g0nkov5"
|
| 6 |
+
path="res://.godot/imported/life_bar.png-096187ed49bceae5df648a6b88e9d6ce.ctex"
|
| 7 |
+
metadata={
|
| 8 |
+
"vram_texture": false
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
[deps]
|
| 12 |
+
|
| 13 |
+
source_file="res://assets/ui/life_bar.png"
|
| 14 |
+
dest_files=["res://.godot/imported/life_bar.png-096187ed49bceae5df648a6b88e9d6ce.ctex"]
|
| 15 |
+
|
| 16 |
+
[params]
|
| 17 |
+
|
| 18 |
+
compress/mode=0
|
| 19 |
+
compress/high_quality=false
|
| 20 |
+
compress/lossy_quality=0.7
|
| 21 |
+
compress/uastc_level=0
|
| 22 |
+
compress/rdo_quality_loss=0.0
|
| 23 |
+
compress/hdr_compression=1
|
| 24 |
+
compress/normal_map=0
|
| 25 |
+
compress/channel_pack=0
|
| 26 |
+
mipmaps/generate=false
|
| 27 |
+
mipmaps/limit=-1
|
| 28 |
+
roughness/mode=0
|
| 29 |
+
roughness/src_normal=""
|
| 30 |
+
process/channel_remap/red=0
|
| 31 |
+
process/channel_remap/green=1
|
| 32 |
+
process/channel_remap/blue=2
|
| 33 |
+
process/channel_remap/alpha=3
|
| 34 |
+
process/fix_alpha_border=true
|
| 35 |
+
process/premult_alpha=false
|
| 36 |
+
process/normal_map_invert_y=false
|
| 37 |
+
process/hdr_as_srgb=false
|
| 38 |
+
process/hdr_clamp_exposure=false
|
| 39 |
+
process/size_limit=0
|
| 40 |
+
detect_3d/compress_to=1
|
kit_on_video_on/ninja_roguelite/submission/game/assets/ui/life_bar_bg.png
ADDED
|
kit_on_video_on/ninja_roguelite/submission/game/assets/ui/life_bar_bg.png.import
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[remap]
|
| 2 |
+
|
| 3 |
+
importer="texture"
|
| 4 |
+
type="CompressedTexture2D"
|
| 5 |
+
uid="uid://dblogk78edk2k"
|
| 6 |
+
path="res://.godot/imported/life_bar_bg.png-8c368474f33975a00a2735f971bcb941.ctex"
|
| 7 |
+
metadata={
|
| 8 |
+
"vram_texture": false
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
[deps]
|
| 12 |
+
|
| 13 |
+
source_file="res://assets/ui/life_bar_bg.png"
|
| 14 |
+
dest_files=["res://.godot/imported/life_bar_bg.png-8c368474f33975a00a2735f971bcb941.ctex"]
|
| 15 |
+
|
| 16 |
+
[params]
|
| 17 |
+
|
| 18 |
+
compress/mode=0
|
| 19 |
+
compress/high_quality=false
|
| 20 |
+
compress/lossy_quality=0.7
|
| 21 |
+
compress/uastc_level=0
|
| 22 |
+
compress/rdo_quality_loss=0.0
|
| 23 |
+
compress/hdr_compression=1
|
| 24 |
+
compress/normal_map=0
|
| 25 |
+
compress/channel_pack=0
|
| 26 |
+
mipmaps/generate=false
|
| 27 |
+
mipmaps/limit=-1
|
| 28 |
+
roughness/mode=0
|
| 29 |
+
roughness/src_normal=""
|
| 30 |
+
process/channel_remap/red=0
|
| 31 |
+
process/channel_remap/green=1
|
| 32 |
+
process/channel_remap/blue=2
|
| 33 |
+
process/channel_remap/alpha=3
|
| 34 |
+
process/fix_alpha_border=true
|
| 35 |
+
process/premult_alpha=false
|
| 36 |
+
process/normal_map_invert_y=false
|
| 37 |
+
process/hdr_as_srgb=false
|
| 38 |
+
process/hdr_clamp_exposure=false
|
| 39 |
+
process/size_limit=0
|
| 40 |
+
detect_3d/compress_to=1
|
kit_on_video_on/ninja_roguelite/submission/game/enemy.gd
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
extends CharacterBody2D
|
| 2 |
+
class_name Enemy
|
| 3 |
+
|
| 4 |
+
# Standard samurai patrol enemy. Reads the player position and lunges when
|
| 5 |
+
# in range. Hurtable by the player's melee cone; on death contributes to
|
| 6 |
+
# the run's overall "enemies felled" count (read as gb_enemy count_delta).
|
| 7 |
+
|
| 8 |
+
signal died
|
| 9 |
+
signal hurt
|
| 10 |
+
|
| 11 |
+
enum State {PATROL, CHASE, ATTACK, HURT, DEAD}
|
| 12 |
+
|
| 13 |
+
const MOVE_SPEED := 60.0
|
| 14 |
+
const CHASE_SPEED := 80.0
|
| 15 |
+
const GRAVITY := 1400.0
|
| 16 |
+
const ATTACK_RANGE := 56.0
|
| 17 |
+
const ATTACK_WINDUP := 0.35
|
| 18 |
+
const ATTACK_COOLDOWN := 0.85
|
| 19 |
+
const HURT_DURATION := 1.0
|
| 20 |
+
|
| 21 |
+
@export var max_hp: int = 1
|
| 22 |
+
@export var patrol_left: float = -120.0
|
| 23 |
+
@export var patrol_right: float = 120.0
|
| 24 |
+
@export var origin_x: float = 0.0
|
| 25 |
+
@export var enemy_kind: String = "samurai"
|
| 26 |
+
|
| 27 |
+
var hp: int = max_hp
|
| 28 |
+
var state: int = State.PATROL
|
| 29 |
+
var facing: int = 1
|
| 30 |
+
var home_x: float = 0.0
|
| 31 |
+
var attack_windup: float = 0.0
|
| 32 |
+
var attack_cooldown: float = 0.0
|
| 33 |
+
var hurt_timer: float = 0.0
|
| 34 |
+
var wander_dir: int = 1
|
| 35 |
+
|
| 36 |
+
@onready var sprite: Sprite2D = $Sprite
|
| 37 |
+
@onready var hitbox: Area2D = $Hitbox
|
| 38 |
+
@onready var anim: AnimationPlayer = $AnimationPlayer
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
func _ready() -> void:
|
| 42 |
+
add_to_group("gb_enemy")
|
| 43 |
+
home_x = origin_x if origin_x != 0.0 else global_position.x
|
| 44 |
+
hitbox.body_entered.connect(_on_hitbox_body_entered)
|
| 45 |
+
hitbox.area_entered.connect(_on_hitbox_area_entered)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
func _physics_process(delta: float) -> void:
|
| 49 |
+
if state == State.DEAD:
|
| 50 |
+
velocity.y += GRAVITY * delta
|
| 51 |
+
move_and_slide()
|
| 52 |
+
return
|
| 53 |
+
|
| 54 |
+
hurt_timer = max(0.0, hurt_timer - delta)
|
| 55 |
+
attack_windup = max(0.0, attack_windup - delta)
|
| 56 |
+
attack_cooldown = max(0.0, attack_cooldown - delta)
|
| 57 |
+
|
| 58 |
+
if state == State.HURT and hurt_timer > 0.0:
|
| 59 |
+
velocity.y += GRAVITY * delta
|
| 60 |
+
move_and_slide()
|
| 61 |
+
return
|
| 62 |
+
|
| 63 |
+
var player := _find_player()
|
| 64 |
+
var player_dist := INF
|
| 65 |
+
if player != null:
|
| 66 |
+
player_dist = abs(player.global_position.x - global_position.x)
|
| 67 |
+
|
| 68 |
+
# Pick facing toward patrol direction or player
|
| 69 |
+
if state != State.ATTACK:
|
| 70 |
+
if player != null and player_dist < 200.0:
|
| 71 |
+
facing = 1 if player.global_position.x > global_position.x else -1
|
| 72 |
+
else:
|
| 73 |
+
if global_position.x < home_x + patrol_left:
|
| 74 |
+
wander_dir = 1
|
| 75 |
+
elif global_position.x > home_x + patrol_right:
|
| 76 |
+
wander_dir = -1
|
| 77 |
+
facing = wander_dir
|
| 78 |
+
|
| 79 |
+
match state:
|
| 80 |
+
State.PATROL:
|
| 81 |
+
velocity.x = move_toward(velocity.x, float(wander_dir) * MOVE_SPEED,
|
| 82 |
+
600.0 * delta)
|
| 83 |
+
if player != null and player_dist < 220.0:
|
| 84 |
+
state = State.CHASE
|
| 85 |
+
State.CHASE:
|
| 86 |
+
if player == null:
|
| 87 |
+
state = State.PATROL
|
| 88 |
+
else:
|
| 89 |
+
var dir: float = sign(player.global_position.x - global_position.x)
|
| 90 |
+
velocity.x = move_toward(velocity.x, float(dir) * CHASE_SPEED,
|
| 91 |
+
900.0 * delta)
|
| 92 |
+
if player_dist < ATTACK_RANGE and attack_cooldown <= 0.0:
|
| 93 |
+
state = State.ATTACK
|
| 94 |
+
attack_windup = ATTACK_WINDUP
|
| 95 |
+
facing = dir
|
| 96 |
+
elif player_dist > 320.0:
|
| 97 |
+
state = State.PATROL
|
| 98 |
+
State.ATTACK:
|
| 99 |
+
velocity.x = 0.0
|
| 100 |
+
if attack_windup <= 0.0:
|
| 101 |
+
# swing: tell the hitbox to deal damage once
|
| 102 |
+
hitbox.monitoring = true
|
| 103 |
+
_swing_once()
|
| 104 |
+
attack_cooldown = ATTACK_COOLDOWN
|
| 105 |
+
state = State.CHASE
|
| 106 |
+
State.HURT:
|
| 107 |
+
state = State.CHASE
|
| 108 |
+
|
| 109 |
+
velocity.y += GRAVITY * delta
|
| 110 |
+
move_and_slide()
|
| 111 |
+
sprite.flip_h = (facing < 0)
|
| 112 |
+
_anim_for_state()
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
var _swinged_this_windup := false
|
| 116 |
+
|
| 117 |
+
func _swing_once() -> void:
|
| 118 |
+
# Activate hitbox briefly; hit detection happens via signals.
|
| 119 |
+
hitbox.monitoring = true
|
| 120 |
+
get_tree().create_timer(0.15, false).timeout.connect(func():
|
| 121 |
+
if is_instance_valid(self):
|
| 122 |
+
hitbox.monitoring = false)
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
func _anim_for_state() -> void:
|
| 126 |
+
if anim == null:
|
| 127 |
+
return
|
| 128 |
+
var name := ""
|
| 129 |
+
match state:
|
| 130 |
+
State.PATROL:
|
| 131 |
+
name = "run"
|
| 132 |
+
State.CHASE:
|
| 133 |
+
name = "run"
|
| 134 |
+
State.ATTACK:
|
| 135 |
+
name = "attack"
|
| 136 |
+
State.HURT:
|
| 137 |
+
name = "hurt"
|
| 138 |
+
State.DEAD:
|
| 139 |
+
name = "dead"
|
| 140 |
+
if anim.current_animation != name:
|
| 141 |
+
anim.play(name)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
func take_hit(by: Node, dmg: int) -> void:
|
| 145 |
+
if state == State.DEAD:
|
| 146 |
+
return
|
| 147 |
+
hp -= dmg
|
| 148 |
+
hurt.emit()
|
| 149 |
+
state = State.HURT
|
| 150 |
+
hurt_timer = HURT_DURATION
|
| 151 |
+
# Knockback away from the attacker
|
| 152 |
+
var dir: float = sign(global_position.x - by.global_position.x)
|
| 153 |
+
if dir == 0.0:
|
| 154 |
+
dir = 1.0
|
| 155 |
+
velocity = Vector2(dir * 220.0, -180.0)
|
| 156 |
+
if hp <= 0:
|
| 157 |
+
_die()
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
func _die() -> void:
|
| 161 |
+
if state == State.DEAD:
|
| 162 |
+
return
|
| 163 |
+
state = State.DEAD
|
| 164 |
+
died.emit()
|
| 165 |
+
set_collision_layer_value(1, false)
|
| 166 |
+
set_collision_layer_value(2, false)
|
| 167 |
+
set_collision_mask_value(2, false)
|
| 168 |
+
hitbox.set_deferred("monitoring", false)
|
| 169 |
+
# Drop the corpse down out of view, then free.
|
| 170 |
+
velocity = Vector2(0, -100)
|
| 171 |
+
get_tree().create_timer(0.6, false).timeout.connect(func():
|
| 172 |
+
if is_instance_valid(self):
|
| 173 |
+
queue_free()
|
| 174 |
+
)
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
func _find_player() -> Node2D:
|
| 178 |
+
var nodes := get_tree().get_nodes_in_group("gb_player")
|
| 179 |
+
for n in nodes:
|
| 180 |
+
if n is Node2D and is_instance_valid(n) and n.visible:
|
| 181 |
+
return n
|
| 182 |
+
return null
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
func _on_hitbox_body_entered(body: Node) -> void:
|
| 186 |
+
if state == State.DEAD:
|
| 187 |
+
return
|
| 188 |
+
if body.is_in_group("gb_player"):
|
| 189 |
+
if body.has_method("_take_damage"):
|
| 190 |
+
body._take_damage(1, global_position)
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
func _on_hitbox_area_entered(area: Area2D) -> void:
|
| 194 |
+
if state == State.DEAD:
|
| 195 |
+
return
|
| 196 |
+
if area.is_in_group("gb_player"):
|
| 197 |
+
if area.has_method("_take_damage"):
|
| 198 |
+
area._take_damage(1, global_position)
|
kit_on_video_on/ninja_roguelite/submission/game/enemy.gd.uid
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
uid://by1p50jwtpflv
|
kit_on_video_on/ninja_roguelite/submission/game/enemy.tscn
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[gd_scene load_steps=8 format=3]
|
| 2 |
+
|
| 3 |
+
[ext_resource type="Script" path="res://enemy.gd" id="1_enemy"]
|
| 4 |
+
[ext_resource type="Texture2D" path="res://assets/characters/samurai_blue.png" id="2_tex"]
|
| 5 |
+
|
| 6 |
+
[sub_resource type="RectangleShape2D" id="Shape_enemy"]
|
| 7 |
+
size = Vector2(22, 44)
|
| 8 |
+
|
| 9 |
+
[sub_resource type="RectangleShape2D" id="Shape_hitbox"]
|
| 10 |
+
size = Vector2(40, 30)
|
| 11 |
+
|
| 12 |
+
[sub_resource type="Animation" id="Anim_run"]
|
| 13 |
+
length = 0.4
|
| 14 |
+
loop_mode = 1
|
| 15 |
+
tracks/0/type = "value"
|
| 16 |
+
tracks/0/imported = false
|
| 17 |
+
tracks/0/enabled = true
|
| 18 |
+
tracks/0/path = NodePath("Sprite:modulate")
|
| 19 |
+
tracks/0/interp = 1
|
| 20 |
+
tracks/0/loop_wrap = true
|
| 21 |
+
tracks/0/keys = {
|
| 22 |
+
"times": PackedFloat32Array(0, 0.2, 0.4),
|
| 23 |
+
"transitions": PackedFloat32Array(1, 1, 1),
|
| 24 |
+
"update": 0,
|
| 25 |
+
"values": [Color(1, 1, 1, 1), Color(0.85, 0.9, 1, 1), Color(1, 1, 1, 1)]
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
[sub_resource type="Animation" id="Anim_attack"]
|
| 29 |
+
length = 0.3
|
| 30 |
+
tracks/0/type = "value"
|
| 31 |
+
tracks/0/imported = false
|
| 32 |
+
tracks/0/enabled = true
|
| 33 |
+
tracks/0/path = NodePath("Hitbox:monitoring")
|
| 34 |
+
tracks/0/interp = 1
|
| 35 |
+
tracks/0/loop_wrap = true
|
| 36 |
+
tracks/0/keys = {
|
| 37 |
+
"times": PackedFloat32Array(0, 0.25, 0.3),
|
| 38 |
+
"transitions": PackedFloat32Array(1, 1, 1),
|
| 39 |
+
"update": 1,
|
| 40 |
+
"values": [true, false, false]
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
[sub_resource type="AnimationLibrary" id="AnimLib_enemy"]
|
| 44 |
+
_data = {
|
| 45 |
+
"attack": SubResource("Anim_attack"),
|
| 46 |
+
"dead": SubResource("Anim_run"),
|
| 47 |
+
"hurt": SubResource("Anim_run"),
|
| 48 |
+
"run": SubResource("Anim_run")
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
[node name="Enemy" type="CharacterBody2D"]
|
| 52 |
+
collision_layer = 2
|
| 53 |
+
collision_mask = 1
|
| 54 |
+
script = ExtResource("1_enemy")
|
| 55 |
+
|
| 56 |
+
[node name="Sprite" type="Sprite2D" parent="."]
|
| 57 |
+
scale = Vector2(0.6, 0.6)
|
| 58 |
+
texture = ExtResource("2_tex")
|
| 59 |
+
|
| 60 |
+
[node name="Collision" type="CollisionShape2D" parent="."]
|
| 61 |
+
shape = SubResource("Shape_enemy")
|
| 62 |
+
|
| 63 |
+
[node name="Hitbox" type="Area2D" parent="."]
|
| 64 |
+
collision_layer = 8
|
| 65 |
+
collision_mask = 16
|
| 66 |
+
|
| 67 |
+
[node name="Shape" type="CollisionShape2D" parent="Hitbox"]
|
| 68 |
+
shape = SubResource("Shape_hitbox")
|
| 69 |
+
|
| 70 |
+
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
|
| 71 |
+
libraries = {
|
| 72 |
+
"": SubResource("AnimLib_enemy")
|
| 73 |
+
}
|