Install
$ agentstack add skill-bullish0x-gamestudio-health-combat-system ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Health and Combat System
When to Use
Use this skill when:
- Implementing health and damage
- Creating combat mechanics
- Adding status effects
- Building armor/defense systems
- Implementing healing
- Creating damage types
Core Principles
- Event-Driven: Use events for damage/healing
- Extensible: Support multiple damage types
- Balanced: Clear combat formulas
- Feedback: Visual/audio damage feedback
- Data-Driven: Configure via components
- Invulnerability: Temporary damage immunity
Health and Combat Implementation
1. Health Components
// components/Health.ts
export class Health {
current: number;
max: number;
regenRate: number = 0; // HP per second
isInvulnerable: boolean = false;
invulnerabilityDuration: number = 0;
constructor(max: number, current?: number, regenRate: number = 0) {
this.max = max;
this.current = current ?? max;
this.regenRate = regenRate;
}
isDead(): boolean {
return this.current = this.max;
}
getHealthPercent(): number {
return this.current / this.max;
}
damage(amount: number): void {
if (this.isInvulnerable) return;
this.current = Math.max(0, this.current - amount);
}
heal(amount: number): void {
this.current = Math.min(this.max, this.current + amount);
}
setInvulnerable(duration: number): void {
this.isInvulnerable = true;
this.invulnerabilityDuration = duration;
}
}
// components/Armor.ts
export class Armor {
value: number; // Flat damage reduction
percentage: number = 0; // 0-1, percentage reduction
constructor(value: number = 0, percentage: number = 0) {
this.value = value;
this.percentage = percentage;
}
calculateReduction(damage: number): number {
// Apply percentage reduction first
let reduced = damage * (1 - this.percentage);
// Then flat reduction
reduced = Math.max(0, reduced - this.value);
return reduced;
}
}
// components/Attack.ts
export class Attack {
damage: number;
damageType: string = 'physical';
range: number;
cooldown: number = 0;
cooldownTime: number;
attackSpeed: number = 1; // Attacks per second
constructor(
damage: number,
range: number,
cooldownTime: number = 1,
damageType: string = 'physical'
) {
this.damage = damage;
this.range = range;
this.cooldownTime = cooldownTime;
this.damageType = damageType;
}
canAttack(): boolean {
return this.cooldown 0;
}
shouldTick(): boolean {
return this.timeSinceLastTick >= this.tickInterval;
}
tick(): void {
this.timeSinceLastTick = 0;
}
addStack(): void {
if (this.stacks ();
add(effect: StatusEffect): void {
const existing = this.effects.get(effect.type);
if (existing) {
// Refresh duration and add stack
existing.remainingTime = Math.max(existing.remainingTime, effect.duration);
existing.addStack();
} else {
this.effects.set(effect.type, effect);
}
}
remove(type: StatusEffectType): void {
this.effects.delete(type);
}
has(type: StatusEffectType): boolean {
return this.effects.has(type);
}
get(type: StatusEffectType): StatusEffect | undefined {
return this.effects.get(type);
}
getAll(): StatusEffect[] {
return Array.from(this.effects.values());
}
clear(): void {
this.effects.clear();
}
}
3. Combat System
// systems/CombatSystem.ts
export class CombatSystem extends UpdateSystem {
priority = 35;
private eventBus: EventBus;
constructor(eventBus: EventBus) {
super();
this.eventBus = eventBus;
}
update(world: World, deltaTime: number): void {
// Update attack cooldowns
this.updateCooldowns(world, deltaTime);
// Update invulnerability
this.updateInvulnerability(world, deltaTime);
// Process attacks
this.processAttacks(world);
// Update status effects
this.updateStatusEffects(world, deltaTime);
// Health regeneration
this.updateHealthRegen(world, deltaTime);
// Check for deaths
this.checkDeaths(world);
}
private updateCooldowns(world: World, deltaTime: number): void {
const entities = world.query([Attack]);
entities.iterate((entity, [attack]) => {
if (attack.cooldown > 0) {
attack.cooldown = Math.max(0, attack.cooldown - deltaTime);
}
});
}
private updateInvulnerability(world: World, deltaTime: number): void {
const entities = world.query([Health]);
entities.iterate((entity, [health]) => {
if (health.isInvulnerable) {
health.invulnerabilityDuration -= deltaTime;
if (health.invulnerabilityDuration ([Transform, Attack]);
const targets = world.query([Transform, Health]);
attackers.iterate((attacker, [attackerTransform, attack]) => {
if (!attack.canAttack()) return;
// Find targets in range
targets.iterate((target, [targetTransform, health]) => {
if (target === attacker) return;
const distance = attackerTransform.position.distanceTo(targetTransform.position);
if (distance ([Health, StatusEffects]);
entities.iterate((entity, [health, statusEffects]) => {
const effects = statusEffects.getAll();
for (const effect of effects) {
// Update effect duration
const stillActive = effect.update(deltaTime);
if (!stillActive) {
statusEffects.remove(effect.type);
continue;
}
// Apply effect
if (effect.shouldTick()) {
this.applyStatusEffect(entity, health, effect);
effect.tick();
}
}
});
}
private applyStatusEffect(entity: Entity, health: Health, effect: StatusEffect): void {
const value = effect.getEffectiveValue();
switch (effect.type) {
case StatusEffectType.Poison:
case StatusEffectType.Burn:
health.damage(value);
break;
case StatusEffectType.Regeneration:
health.heal(value);
break;
case StatusEffectType.Stun:
// Handled by movement/AI systems
break;
case StatusEffectType.Slow:
// Handled by movement system
break;
}
}
private updateHealthRegen(world: World, deltaTime: number): void {
const entities = world.query([Health]);
entities.iterate((entity, [health]) => {
if (health.regenRate > 0 && !health.isFullHealth()) {
health.heal(health.regenRate * deltaTime);
}
});
}
private checkDeaths(world: World): void {
const entities = world.query([Health]);
entities.iterate((entity, [health]) => {
if (health.isDead() && entity.active) {
this.eventBus.emit(new DeathEvent(entity, null));
entity.active = false;
}
});
}
}
4. Damage Types System
// combat/DamageTypeSystem.ts
export interface DamageResistance {
physical: number; // 0-1 (0 = immune, 1 = full damage)
fire: number;
ice: number;
poison: number;
lightning: number;
}
export class DamageResistances {
resistances: DamageResistance = {
physical: 1,
fire: 1,
ice: 1,
poison: 1,
lightning: 1,
};
getResistance(damageType: string): number {
return this.resistances[damageType as keyof DamageResistance] ?? 1;
}
setResistance(damageType: string, value: number): void {
if (damageType in this.resistances) {
this.resistances[damageType as keyof DamageResistance] = Math.max(0, Math.min(1, value));
}
}
calculateDamage(baseDamage: number, damageType: string): number {
return baseDamage * this.getResistance(damageType);
}
}
Usage Examples
// Example 1: Basic combat setup
const player = world.createEntity();
player.addComponent(new Health(100, 100, 5)); // 100 HP, 5 HP/s regen
player.addComponent(new Armor(10, 0.2)); // 10 flat reduction, 20% reduction
player.addComponent(new Attack(15, 2, 0.5)); // 15 damage, 2 range, 0.5s cooldown
const enemy = world.createEntity();
enemy.addComponent(new Health(50));
enemy.addComponent(new Attack(10, 1.5, 1));
// Example 2: Apply status effect
const statusEffects = entity.getComponent(StatusEffects) ||
entity.addComponent(new StatusEffects());
// Apply poison
statusEffects.add(new StatusEffect({
type: StatusEffectType.Poison,
duration: 5,
tickInterval: 1,
value: 2, // 2 damage per second
}));
// Apply regeneration
statusEffects.add(new StatusEffect({
type: StatusEffectType.Regeneration,
duration: 10,
tickInterval: 1,
value: 5, // 5 HP per second
}));
// Example 3: Damage resistances
const resistances = enemy.addComponent(new DamageResistances());
resistances.setResistance('fire', 0.5); // 50% fire resistance
resistances.setResistance('ice', 1.5); // 150% ice damage (weakness)
resistances.setResistance('physical', 0); // Immune to physical
// Example 4: Healing potion
function useHealthPotion(entity: Entity): void {
const health = entity.getComponent(Health);
if (health) {
health.heal(50);
eventBus.emit(new HealEvent(entity, 50));
}
}
// Example 5: Critical hits
class CriticalHitSystem extends UpdateSystem {
private critChance = 0.2; // 20%
private critMultiplier = 2;
update(world: World, deltaTime: number): void {
// Listen to damage events
eventBus.on(DamageEvent, (event) => {
if (Math.random() ();
private comboWindow = 2; // 2 seconds
update(world: World, deltaTime: number): void {
eventBus.on(DamageEvent, (event) => {
if (!event.source) return;
const now = performance.now() / 1000;
const combo = this.combos.get(event.source) ?? { count: 0, lastHitTime: 0 };
// Check if within combo window
if (now - combo.lastHitTime 1) {
const multiplier = 1 + (combo.count - 1) * 0.1; // +10% per combo
const bonusDamage = event.amount * (multiplier - 1);
const health = event.target.getComponent(Health);
if (health) {
health.damage(bonusDamage);
}
}
});
}
}
// Example 7: Lifesteal
class LifestealSystem extends UpdateSystem {
private lifestealPercent = 0.2; // 20%
update(world: World, deltaTime: number): void {
eventBus.on(DamageEvent, (event) => {
if (!event.source) return;
const sourceHealth = event.source.getComponent(Health);
if (sourceHealth) {
const healAmount = event.amount * this.lifestealPercent;
sourceHealth.heal(healAmount);
}
});
}
}
Checklist
- [ ] Add Health components
- [ ] Implement Attack components
- [ ] Create combat system
- [ ] Add armor/resistance system
- [ ] Implement status effects
- [ ] Add healing mechanics
- [ ] Create damage events
- [ ] Add visual feedback
- [ ] Test combat balance
- [ ] Profile performance
Common Pitfalls
- No invulnerability: Hit multiple times per frame
- Wrong damage calculation: Armor broken
- No death check: Entities with negative HP
- Forgotten cooldowns: Instant attacks
- Status effect stacking: Unlimited stacks
- No resistance system: All damage same
- Missing events: No damage feedback
Performance Tips
Combat Optimization
- Update cooldowns once per frame
- Cache damage calculations
- Limit status effect checks
- Use spatial queries for range checks
- Batch damage events
Memory Optimization
- Pool damage events
- Reuse status effect objects
- Limit active status effects
- Clear expired effects immediately
Mobile Considerations
- Simpler damage calculations
- Fewer status effects
- Lower tick rates for DoT
- Simpler resistance system
- Fewer damage types
Related Skills
ecs-events- Combat eventsecs-system-patterns- System implementationaudio-system- Combat soundsui-system- Health barscollision-system- Hit detection
References
- Combat system design
- Damage calculation formulas
- Status effect patterns
- RPG mechanics design
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: bullish0x
- Source: bullish0x/GameStudio
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.