Repository navigation
On Hit Effect Component
Some enemies should have a chance to apply a bonus effect — poison, stun, or similar — when they successfully land a hit, on top of the base damage already dealt. OnHitEffectComponent provides this: it subscribes to one or more configured attack events (e.g. meleeAttack and/or a future rangedAttackHit), rolls a probability on each hit, and on success applies a health-over-time (or instant) effect to the target. This mirrors the pattern used by Status Effects and Melee Attacks — a single configurable component reacting to an event, decoupled from the component that fired it.
public OnHitEffectComponent(List<String> hitEventNames, float triggerChance, int effectDuration, int effectHealthChange) {}
OnHitEffectComponent extends Component directly. It is responsible for:
- Listening for every event name in
hitEventNameson the owning entity. - Rolling a random value against
triggerChanceon each configured event. - On success, applying
effectHealthChangeto the target'sCombatStatsComponent— instantly ifeffectDurationis0, or gradually overeffectDurationticks otherwise. - Tracking any in-progress over-time effect itself and advancing it each
update(), applying the exact remaining amount on the final tick so the total is precise even wheneffectHealthChangedoesn't divide evenly across ticks.
Providing a null or empty hitEventNames list is invalid. Providing a triggerChance outside (0, 1] is invalid. Providing an effectDuration less than 0 is invalid.
This can be used for:
- A base enemy's guaranteed-ish poison-on-hit, e.g.
triggerChance = 0.3fover a handful of ticks - A rarer bonus effect, e.g.
triggerChance = 0.1f, on the same or a different enemy - Enemies that should apply the same bonus effect on both melee and ranged hits, via a single instance configured with both event names
new OnHitEffectComponent(List.of("meleeAttack"), 0.3f, 5, -10)
(providing hitEventNames = ["meleeAttack"], triggerChance = 0.3f, effectDuration = 5, and effectHealthChange = -10) creates an on-hit effect with a 30% chance per hit to apply -10 health to the target, spread across 5 ticks.
OnHitEffectComponent does not attach a HealthEffectComponent to the target — Entity.addComponent() rejects additions made after Entity.create(), and a hit target has always already been created. Health changes are instead applied directly via CombatStatsComponent.addHealth(int), with the per-tick math mirroring HealthEffectComponent's own approach. A consequence worth knowing: because the component lives on the attacker rather than the target, an in-progress effect only keeps ticking while the attacker's own update() runs — if the attacker is removed mid-effect, the target will not receive the remaining ticks.
An entity can only carry one component of a given class, so an enemy needing two distinct on-hit effects (e.g. a common poison and a rare stun) cannot currently express that with two OnHitEffectComponent instances on the same entity.
OnHitEffectComponent does not fire an event of its own — it only listens. Any event listed in hitEventNames (e.g. meleeAttack, fired by MeleeAttackComponent after a successful hit) triggers onAttackHit(Entity target).
Not currently constructed from an entity config field the way MeleeAttackComponent is — attached directly at spawn time with the desired hitEventNames/triggerChance/effectDuration/effectHealthChange values. Could be extended to a config-driven approach (e.g. an OnHitEffectConfig composition block, following the same nullable-composition pattern as MeleeAttackConfig) if per-enemy tuning via config becomes necessary.
Testing was done to ensure OnHitEffectComponent correctly reacts to a successful melee hit and applies its configured effect according to its trigger probability.
To set up these tests, an owner entity (carrying the OnHitEffectComponent under test) and a target entity (carrying the effect-receiving component relevant to the effectType under test, e.g. CombatStatsComponent) were created. The "meleeAttackHit" event was triggered directly on the owner in each test, bypassing MeleeAttackComponent entirely, so only OnHitEffectComponent's own logic was exercised.
Tests were used to ensure the following situations were working:
- A
triggerChanceoutside the valid0.0–1.0range is rejected - An unrecognised
effectTypeis rejected or falls back to the documented default behaviour - Valid
effectType,triggerChance, andmagnitudevalues construct the component successfully
Tests were used to ensure the following situations were working:
- A forced-success roll applies the configured effect to the target
- A forced-failure roll does not apply any effect to the target
- Poison-type effects are probability-gated the same as any other effect type (not guaranteed on every hit)
Tests were used to ensure the following situations were working:
- Two or more
OnHitEffectComponentinstances with differenteffectTypes attached to the same owner react independently to a single"meleeAttackHit"event - One effect triggering does not prevent or interfere with another effect's own roll
Tests were used to ensure the following situations were working:
-
create()correctly subscribes to"meleeAttackHit" -
dispose()correctly unsubscribes, so a disposed component no longer reacts to further hits
The Trigger Probability Tests above assume the component's implementation exposes an injectable/seedable source of randomness, so that "forced success" and "forced failure" can be tested deterministically rather than through repeated statistical runs. This is a design assumption flagged for confirmation against the actual implementation before these tests are written.