Files
transcience/src/actors/enemies/enemy_def.gd
T
claude cb2c1e7840 Scale health and damage by ten so percentages have somewhere to land
A shot is 60, a fresh character has 1000, the Warden has 36000. Enemy health,
boss health and every emitter's damage were scaled with them, so every ratio
is unchanged and time to kill is exactly what it was -- tests/unit/test_content
now pins shots-to-kill per enemy and hits-to-kill a player so a careless edit
to either side shows up as a number a designer recognises.

The reason is rounding rather than balance. Damage is an integer, and at a base
of 6 the +5% every upgrade carries computed to 6.3 and rounded straight back to
6: a player took their first upgrade, was told it made them stronger, and
nothing happened. diag_upgrades used to print "damage matches the formula
(6 -> 6)"; it now prints (60 -> 63). The boss's per-phase armour multiplier had
the same problem, turning 1.15 into an effective 1.17.

The practice dummy stopped relying on a huge health pool at the same time. Its
old 100000 was already past the u16 the snapshot sends enemy health in, and
once a shot did 60 a patient player could have destroyed the hub's only
practice target for everyone until the next restart. EnemyDef.indestructible
says what was actually meant, and a test asserts nothing else uses it.

check.sh clean, 364 tests, SMOKE PASS, all four diagnostics green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 16:16:38 +02:00

47 lines
1.9 KiB
GDScript

class_name EnemyDef
extends Resource
## Data-only description of a trash enemy. Behaviour is picked from a small set
## of readable, predictable movements -- players should be able to learn an
## enemy in one encounter.
enum Move {
## Never moves. Pure turret.
STATIC,
## Slides along a fixed heading, bouncing off the arena edges.
DRIFT,
## Circles its spawn point at a fixed radius.
ORBIT,
## Walks straight at the nearest player, slowly.
APPROACH,
## Keeps a preferred distance from the nearest player.
STRAFE,
}
@export var id: StringName = &"drone"
@export var display_name: String = "Drone"
@export var max_hp: int = 40
@export var radius: float = 14.0
@export var move: Move = Move.DRIFT
@export var speed: float = 60.0
## ORBIT radius, or STRAFE preferred distance.
@export var move_param: float = 120.0
## Ticks between direction re-evaluations for APPROACH/STRAFE, so enemies read
## as deliberate rather than twitchy.
@export var retarget_interval: int = 30
## How far this enemy notices a player, in world units. It also needs line of
## sight -- range alone would mean shooting through walls.
@export var aggro_range: float = 420.0
## Index the client renderer uses to pick a shape/colour.
@export var visual: int = 0
@export var emitters: Array[BulletEmitter] = []
## The emitter timeline wraps at this many ticks.
@export var pattern_loop_ticks: int = 240
## Never takes damage. Exists for the hub's practice target, whose whole job is
## to still be there tomorrow -- expressing that as a flag rather than as a
## large health pool means it cannot be worn down by a patient player, and
## keeps its health inside the u16 the snapshot sends.
@export var indestructible: bool = false
## What this enemy may leave behind. Rolled once per entry on death, against
## the world's own RNG. Empty for anything that should drop nothing.
@export var loot: Array[LootDrop] = []