Files
Bullosseum/systems/combat/damage_event.gd
T
richard 7b6a141767 Reorganize the codebase into role-based folders (snake_case)
Move the 40-odd root-level scripts and scenes into a tree that groups by role,
per the approved roadmap (WS2/WS3): actors/{player,matador,bear}, systems/{combat,
fx,input,debug,render,crowd,config}, ui/, props/ (was tscn_s/). Rename the shell
scene.tscn -> levels/game_shell.tscn and every PascalCase scene to snake_case
(Player.tscn -> player.tscn, MainMenu.tscn -> main_menu.tscn, etc.) so the
case-insensitive web/macOS filesystems can't alias two files together.

Done as a scripted move: each file travels with its .uid; every quoted res:// path
across scenes, scripts, .tres, and project.godot's 9 autoloads is rewritten; the
.godot uid/class cache is rebuilt from scratch. Scenes still resolve by uid, so the
rewrite is belt-and-suspenders. Lint globs (run_tests.sh + both CI workflows) now
walk the new dirs; an orphan animation_tree.gd.uid and 7 pre-existing trailing-
whitespace lints (barrel/torch, never covered by the old root-only glob) are cleaned.

Full suite green on both sides. Asset consolidation (Assets/Animations/fonts ->
art/) is deferred — moving imported .glb/.png/.ttf is safer done in the editor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017KoYBYQgRyY1zDHXMsXY3V
2026-09-06 00:04:41 +03:00

81 lines
3.5 KiB
GDScript

class_name DamageEvent
extends RefCounted
## The one typed payload for every hit in the game — a bull goring a matador, a bear slam popping
## the bull, a thrown spear landing. It replaces the scattered, stringly Object.call(&"take_sword_hit",
## "gored") paths: an emitter (a Hitbox) fills one of these and hands it to the target's
## receive_damage(event). One shape to read, log and type-check, instead of four call conventions.
##
## It serves both victim models the game already has: the bull, which dies on one clean hit
## (lethal + cause + hit_position), and an enemy, which is launched/ragdolled by an impulse
## (knockback_dir · knockback_strength + knock_up). A given hit just leaves the fields it doesn't
## use at their defaults.
enum Cause { GENERIC, GORED, MAULED, THROWN, SLAMMED, KICKED, DASHED }
## Who dealt the hit — for attribution and for ignoring self-overlap. May be null.
var source: Node = null
## What kind of hit; drives the victim's reaction (blood/vignette/FX) and any future resistances.
var cause: Cause = Cause.GENERIC
## World contact point, so impact FX spawn where the hit actually landed.
var hit_position: Vector3 = Vector3.ZERO
## Planar push direction (need not be normalised). Vector3.ZERO = no horizontal knockback.
var knockback_dir: Vector3 = Vector3.ZERO
## Impulse magnitude behind knockback_dir — the enemy launch/ragdoll strength.
var knockback_strength: float = 0.0
## Upward launch in m/s (an overhead smash pop, a leap-slam outer ring). 0 = a grounded hit.
var knock_up: float = 0.0
## One clean hit ends the run for the bull; most enemy-dealt hits are lethal to it. Enemies that
## are merely launched (not killed) by the bull's abilities set this false.
var lethal: bool = true
static func make(kind: Cause, at: Vector3, src: Node = null) -> DamageEvent:
var e := DamageEvent.new()
e.cause = kind
e.hit_position = at
e.source = src
return e
## The single entry point every attacker uses to wound a target: build the typed event and hand it
## to receive_damage(). Falls back to the legacy string call for any target not yet migrated, so a
## caller never has to branch on which contract the victim speaks. `knock_up_v` is the already-shaped
## pop (0 = grounded); callers that scale it by distance compute it before calling.
static func deal(target: Node, kind: Cause, at: Vector3, src: Node = null, knock_up_v: float = 0.0) -> void:
if target == null:
return
if target.has_method(&"receive_damage"):
var event := make(kind, at, src)
event.knock_up = knock_up_v
target.call(&"receive_damage", event)
return
if target.has_method(&"take_sword_hit"):
target.call(&"take_sword_hit", make(kind, at, src).cause_name(), at)
if knock_up_v > 0.0 and target.has_method(&"apply_knock_up"):
target.call(&"apply_knock_up", knock_up_v)
## Lower-case name of the cause, for logs and for the bull's existing cause-keyed reactions
## ("gored" / "mauled" / "thrown") during the migration off string causes.
func cause_name() -> String:
return String(Cause.keys()[cause]).to_lower()
## Maps a legacy cause string onto the enum, so a Hitbox's exported damage_cause round-trips
## exactly (gored/mauled/thrown are the only ones in use). Unknown strings fall back to GENERIC.
static func cause_from(text: String) -> Cause:
match text.to_lower():
"gored":
return Cause.GORED
"mauled":
return Cause.MAULED
"thrown":
return Cause.THROWN
"slammed":
return Cause.SLAMMED
"kicked":
return Cause.KICKED
"dashed":
return Cause.DASHED
return Cause.GENERIC