Skip to main content

Action Battle System

Advanced real-time action combat AI system for RPGJS. The AI controller manages behavior only. All stats, HP, SP, skills, items, classes, and states are configured with the standard RPGJS API.

Features

  • State machine AI with Idle, Alert, Combat, Flee, and Stunned
  • Multiple enemy types: Aggressive, Defensive, Ranged, Tank, Berserker
  • Attack patterns: Melee, Combo, Charged, Zone, DashAttack
  • Skill support with standard RPGJS skills
  • Dodge and counter-attack behaviors
  • Group behavior and waypoint patrols
  • Knockback driven by weapon configuration
  • Hook system with onBeforeHit and onAfterHit

Installation

npm install @rpgjs/action-battle

Quick Start

import { EventMode, ATK, PDEF, MAXHP, type EventDefinition } from "@rpgjs/server";
import { provideActionBattle, BattleAi, EnemyType } from "@rpgjs/action-battle/server";

function GoblinEnemy(): EventDefinition {
  return {
    name: "Goblin",
    mode: EventMode.Scenario,
    onInit() {
      this.setGraphic("goblin");

      this.hp = 80;
      this.param[MAXHP] = 80;
      this.param[ATK] = 15;
      this.param[PDEF] = 5;

      new BattleAi(this, {
        enemyType: EnemyType.Aggressive
      });
    }
  };
}
When you build an object-based event for a map, type the factory as EventDefinition. The returned object only describes the event behavior. Placement data such as id, x, and y still belongs to the outer maps[].events wrapper.

Enable the module

Register the module on the server:
import { createServer } from "@rpgjs/server";
import { provideActionBattle } from "@rpgjs/action-battle/server";

export default createServer({
  providers: [
    provideActionBattle()
  ]
});

Configure stats with the standard RPGJS API

The AI uses the event’s existing data.

Health and resources

this.hp = 100;
this.param[MAXHP] = 100;
this.sp = 50;
this.param[MAXSP] = 50;

Parameters

import { ATK, PDEF, SDEF } from "@rpgjs/server";

this.param[ATK] = 20;
this.param[PDEF] = 10;
this.param[SDEF] = 8;

Skills

import { Fireball, Heal } from "./database/skills";

this.learnSkill(Fireball);
this.learnSkill(Heal);

Items and equipment

import { Sword, Shield, Potion } from "./database/items";

this.addItem(Potion, 3);
this.equip(Sword);
this.equip(Shield);

Classes

import { WarriorClass } from "./database/classes";

this.setClass(WarriorClass);

States

import { PoisonState } from "./database/states";

this.addState(PoisonState);

AI configuration

All AI options are optional:
new BattleAi(event, {
  enemyType: EnemyType.Aggressive,
  attackSkill: Fireball,
  attackCooldown: 1000,
  visionRange: 150,
  attackRange: 60,
  dodgeChance: 0.2,
  dodgeCooldown: 2000,
  fleeThreshold: 0.2,
  attackPatterns: [
    AttackPattern.Melee,
    AttackPattern.Combo,
    AttackPattern.DashAttack
  ],
  patrolWaypoints: [
    { x: 100, y: 100 },
    { x: 300, y: 100 }
  ],
  groupBehavior: true,
  onDefeated: (event, attacker) => {
    const name = attacker?.name?.() ?? "Unknown";
    console.log(`${event.name()} was defeated by ${name}!`);
  }
});

Enemy types

Enemy types affect behavior, not stats:
TypeAttack SpeedDodgeBehavior
AggressiveFastLowRushes player
DefensiveSlowHighCounter-attacks
RangedMediumMediumKeeps distance
TankSlowNoneStands ground
BerserkerVariableLowFaster when hurt

Attack patterns

PatternDescription
MeleeSingle attack
Combo2-3 rapid attacks
ChargedWind-up, stronger attack
Zone360° area attack
DashAttackRush toward target then attack

Use skills for attacks

import { Skill } from "@rpgjs/database";

@Skill({
  name: "Slash",
  spCost: 5,
  power: 25,
  hitRate: 0.95
})
export class Slash {}

onInit() {
  this.hp = 100;
  this.sp = 50;
  this.learnSkill(Slash);

  new BattleAi(this, {
    attackSkill: Slash
  });
}

Examples

Basic enemy

import { type EventDefinition, ATK, MAXHP } from "@rpgjs/server";

function Goblin(): EventDefinition {
  return {
    name: "Goblin",
    onInit() {
      this.setGraphic("goblin");
      this.hp = 50;
      this.param[MAXHP] = 50;
      this.param[ATK] = 10;

      new BattleAi(this);
    }
  };
}

Mage with skills

import { ATK, MAXHP, MAXSP, type EventDefinition } from "@rpgjs/server";

function DarkMage(): EventDefinition {
  return {
    name: "Dark Mage",
    onInit() {
      this.setGraphic("mage");
      this.hp = 60;
      this.sp = 100;
      this.param[MAXHP] = 60;
      this.param[MAXSP] = 100;
      this.param[ATK] = 25;

      this.learnSkill(Fireball);

      new BattleAi(this, {
        enemyType: EnemyType.Ranged,
        attackSkill: Fireball,
        visionRange: 200
      });
    }
  };
}

Patrol guard

import { ATK, MAXHP, type EventDefinition } from "@rpgjs/server";

function PatrolGuard(): EventDefinition {
  return {
    name: "Guard",
    onInit() {
      this.setGraphic("guard");
      this.hp = 80;
      this.param[MAXHP] = 80;
      this.param[ATK] = 15;

      new BattleAi(this, {
        enemyType: EnemyType.Defensive,
        patrolWaypoints: [
          { x: 100, y: 150 },
          { x: 300, y: 150 },
          { x: 300, y: 350 },
          { x: 100, y: 350 }
        ]
      });
    }
  };
}

Player combat

The module handles player attacks via the action input:
// Player presses action key -> attack animation + hitbox
// Hitbox detects enemy -> applyPlayerHitToEvent(player, event)
// Damage uses RPGJS formula: target.applyDamage(attacker)

Knockback system

Knockback force is driven by the equipped weapon’s knockbackForce property:
const Warhammer = {
  id: "warhammer",
  name: "War Hammer",
  atk: 30,
  knockbackForce: 100,
  _type: "weapon" as const
};

Reference source

This guide is based on the package documentation in packages/action-battle/README.md.