Combat without death in Godot 4: shadows that warn before they charge
A lantern, three shadows that telegraph their attacks and a firefly that lets you glide. How I built Gotic's first quest with enemies where nobody can lose anything.

At a camp with bunting near the entrance to Brumacosta there's a note and a lantern. Take it, follow its trail north and you reach some ruins: three stone arches, each with an unlit brazier, a sealed chest in the middle and three shadows drifting between the standing stones. This is El farol de las luciérnagas ("the firefly lantern"), Gotic's first quest with something like enemies.
With Q (or LUZ, "light", on a phone) the lantern sends out a 6 m flash. The first one stuns a shadow; the second dispels it and leaves a spark the colour of its brazier (Ámbar, Marea or Violeta). Collect all three, light the braziers and the chest opens. Inside is the reward: a firefly companion that follows you around and lets you glide by holding Space (or SALTAR, "jump") while falling.

How it's built
I wanted combat, not punishment. Each shadow is a small state machine in its own script (enemigo.gd): it patrols near its spot, chases you if you come within 12 m and, at about 5 m, telegraphs: it shows "¡APÁRTATE!" ("get out of the way!") above itself and draws a reddish ring on the ground that grows for 0.9 s. Then it charges in a straight line at 10 m/s without correcting its course. Sidestep and it misses. It rests for a second and starts over.
The flash doesn't go through walls: before hurting a shadow a ray is cast from the player, and if it hits anything solid, it doesn't count. The cooldown is 1.3 s, and the touch button shows the countdown instead of "LUZ".
The lantern takes three hits, with 1.8 s of protection after each. When it runs out, it sends you back to camp keeping your sparks and lit braziers, and walking back to camp also refills it. Nothing is ever lost for good.
Gliding is the shortest piece of code in the whole mod. If the quest is done, you're holding jump, you're not on the broom or swimming, and you're falling faster than 3.2 m/s, your fall is capped at that speed:
func apply_glide(held: bool) -> void:
if completed and held and not world.is_blocked() and world.player.mover == null and not world.player.swimming and world.player.velocity.y < -GLIDE_SPEED:
world.player.velocity.y = -GLIDE_SPEED
Shadows don't attack while you're on the broom, and the flash doesn't work from it either. Everything pauses while the map or a window is open. The save keeps the lantern, which shadows are gone, the sparks, the braziers and the reward; cooldowns and shadow positions reset on load. And like every Gotic mod, it stands alone: it needs neither the village nor the broom.
Measured on PC: 54 checks of its own (483 in total that round), including a full playthrough with the engine's physics: sidestepping a charge, taking a hit by standing still, dispelling all three shadows while respecting the cooldown, lighting the braziers, opening the chest and gliding. LUZ is also tested pressed with a second finger, the way Android sends it.
What went wrong
When the inventory arrived, the lantern became an item you equip and unequip (it goes in your hand). That exposed a bug: loading a save where you didn't have the lantern left the one hanging from your hand in place. The fishing rod, the courier's satchel and the parrot from other mods had the same problem, and all four were fixed together.
Still unverified
Performance on a phone hasn't been measured, and the charges and the flash have only been tried with simulated fingers, not real ones. I also haven't checked what happens if you hold Space to glide while looking through the photo camera's viewfinder, which came later.