$npx -y skills add thedivergentai/GD-Agentic-Skills --skill godot-combat-systemExpert patterns for combat systems including hitbox/hurtbox architecture, damage calculation (DamageData class), health components, combat state machines, combo systems, ability cooldowns, and damage popups. Use for action games, RPGs, or fighting games. Trigger keywords: Hitbox,
| 1 | ## Godot 4.7 Baseline |
| 2 | |
| 3 | - Expert patterns in this skill target **Godot 4.7+** (stable, 2026-06-18). |
| 4 | - Consult the [Godot 4.7 migration guide](https://docs.godotengine.org/en/4.7/tutorials/migrating/upgrading_to_godot_4.7.html) when upgrading projects from 4.6. |
| 5 | - **NEVER** assume 4.6 defaults (stretch mode, audio area_mask, RichTextLabel percent flags) without checking 4.7 migration notes. |
| 6 | |
| 7 | # Combat System |
| 8 | |
| 9 | Expert guidance for building flexible, component-based combat systems. |
| 10 | |
| 11 | ## NEVER Do |
| 12 | |
| 13 | - **NEVER use direct damage references (`target.health -= 10`)** — This bypasses armor, resistances, and invincibility logic. Always use a `DamageData` + `HealthComponent` pattern for consistent results. |
| 14 | - **NEVER forget invincibility frames (i-frames)** — Without them, multi-hit attacks deal damage every single frame. Always apply a brief invincibility period (0.1–0.5s) after taking a hit. |
| 15 | - **NEVER keep hitboxes active permanently** — This causes unintended "ghost" damage. Enable and disable hitboxes precisely using `AnimationPlayer` tracks or code-timed triggers. |
| 16 | - **NEVER use groups for physics-based hit filtering** — Collision layers are evaluated in C++ and are significantly faster. Groups don't restrict physics intersections adequately for high-performance combat. |
| 17 | - **NEVER emit damage signals without a DamageData object** — A raw number loses critical context like damage type, source, and knockback direction. |
| 18 | - **NEVER use try/catch blocks with validate targets** — GDScript does not support exceptions. Use `has_method(&"take_damage")` or the `is` operator for safe type checking. |
| 19 | - **NEVER hardcode hitstun pauses using OS.delay_msec()** — This blocks the entire OS thread and freezes the game. Use `create_tween()` or `Engine.time_scale` for visual hit-stop effects. |
| 20 | - **NEVER apply massive impulses to a RigidBody inside _process()** — Physics-altering impulses must happen in `_physics_process()` or `_integrate_forces()` to remain deterministic and stable. |
| 21 | - **NEVER couple UI lifebars directly inside the Player script** — Use a `health_changed` signal. This keeps your combat logic clean and independent of UI implementation details. |
| 22 | - **NEVER leave CollisionShapes active on dead entities** — Corpses will block players and towers. Disable them immediately using `set_deferred("disabled", true)`. |
| 23 | - **NEVER scale CollisionShapes non-uniformly** — Non-uniform scaling breaks the physics engine's collision math. Always scale the internal resource (e.g., `CircleShape2D.radius`) instead. |
| 24 | - **NEVER use instanced Nodes for base stat data** — Nodes carry unnecessary overhead. Use Godot's `Resource` class for lightweight, efficient, and inspectable stat containers. |
| 25 | - **NEVER use raw strings for elemental damage types** — Strings are slow and error-prone. Use `enum` flags (optionally with `@export_flags`) to manage multi-type damage efficiently. |
| 26 | - **NEVER use standard strings for state names in high-frequency loops** — Use `StringName` (&"attacking", &"stunned") to drastically improve dictionary lookups and hash comparison speeds. |
| 27 | - **NEVER forget to duplicate() a shared Resource stats block** — If you don't call `duplicate()` when instancing a mob, all enemies of that type will share the same health pool. |
| 28 | |
| 29 | --- |
| 30 | |
| 31 | ## Available Scripts |
| 32 | |
| 33 | > **MANDATORY**: Read the appropriate script before implementing the corresponding pattern. |
| 34 | |
| 35 | ### [combat_system_patterns.gd](scripts/combat_system_patterns.gd) |
| 36 | 10 Expert patterns: Safe duck-typing, hitstun tweens, nodeless AoE shape casting, and frame-perfect sync. |
| 37 | |
| 38 | ### [hitbox_hurtbox.gd](scripts/hitbox_hurtbox.gd) |
| 39 | Component-based hitbox with hit-stop and knockback logic. |
| 40 | |
| 41 | --- |
| 42 | |
| 43 | ## Damage System |
| 44 | |
| 45 | ```gdscript |
| 46 | # damage_data.gd |
| 47 | class_name DamageData |
| 48 | extends RefCounted |
| 49 | |
| 50 | var amount: float |
| 51 | var source: Node |
| 52 | var damage_type: String = "physical" |
| 53 | var knockback: Vector2 = Vector2.ZERO |
| 54 | var is_critical: bool = false |
| 55 | |
| 56 | func _init(dmg: float, src: Node = null) -> void: |
| 57 | amount = dmg |
| 58 | source = src |
| 59 | ``` |
| 60 | |
| 61 | ## Hurtbox/Hitbox Pattern |
| 62 | |
| 63 | ```gdscript |
| 64 | # hurtbox.gd |
| 65 | extends Area2D |
| 66 | class_name Hurtbox |
| 67 | |
| 68 | signal damage_received(data: DamageData) |
| 69 | |
| 70 | @export var health_component: Node |
| 71 | |
| 72 | func _ready() -> void: |
| 73 | area_entered.connect(_on_area_entered) |
| 74 | |
| 75 | func _on_area_entered(area: Area2D) -> void: |
| 76 | if area is Hitbox: |
| 77 | var damage := area.get_damage() |
| 78 | damage_received.emit(damage) |
| 79 | |
| 80 | if health_component: |
| 81 | health_component.take_damage(damage) |
| 82 | ``` |
| 83 | |
| 84 | ```gdscript |
| 85 | # hitbox.gd |
| 86 | extends Area2D |
| 87 | class_name Hitbox |
| 88 | |
| 89 | @export var damage: float = 10.0 |
| 90 | @export var damage_ty |