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
- Adventure combat by default: three-hit player combo, charged attack, and dodge invulnerability
- Reuses the standard RPGJS HUD and graphic-bound entity components
- Impact visual preset with particles, animated combat typography, and camera shake
- State machine AI with
Idle,Alert,Combat,Flee, andStunned - 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
onBeforeHitandonAfterHit
Installation
Quick Start
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.
Factions and targets
BattleAi can target players, other battle events, every combat entity, hostile
factions, or an explicit list of factions.
players: target all players.events: target action-battle events.all: target players and action-battle events.hostile: target entities with a different faction.["guards", "bandits"]: target only these factions.(context) => boolean: fully custom target rule.
Enable the module
Register the module on the server:Adventure player combat
Action Battle now uses theadventure preset by default. Press the action
control for a buffered three-hit combo, hold E and release it for a
charged attack, and use the normal dash control (Shift by default)
to dodge. Damage, charge duration, cooldowns, and invulnerability remain
server-authoritative.
Use preset: "classic" to retain the previous single-attack behavior and UI:
hud.ce component so HP, SP, face, and level remain consistent with the
rest of the game.
For enemies, BattleAi reuses the regular server Components.hpBar() and
merges it into componentsTop. That native component is positioned from the
rendered graphic bounds, not only from the entity hitbox, so it remains above
tall or scaled sprites. Generated four-direction Studio spritesheets also
exclude transparent frame padding from those bounds. Customize it through
presentation.healthBar:
presentation.healthBar: false for entities that should not display it.
No additional HUD or custom HP-bar component is registered by Action Battle.
Mobile games can expose the charged attack through withMobile({ buttons: { heavy: true } }); the heavy button dispatches the same authoritative charge
start/release controls as the keyboard.
Studio skills and keyboard shortcuts
Skills loaded from RPGJS Studio can define their complete Action Battle presentation and gameplay contract. The client only requests a skill use; the server verifies that the skill is learned, that SP is available, that its cooldown has elapsed, and that the selected target is valid.action.visual controls client-only particle presentation and never changes
server gameplay:
castFxis a short preset attached to the caster.trailFxis a continuous preset attached to the moving projectile.impactFxis a short preset attached to the target on impact.
"auto" to retain Action Battle defaults and "none" to disable that phase.
The legacy visual.fx field remains supported as an impact alias, but
impactFx takes precedence.
key accepts the same keyboard names as project input controls. If it is
omitted, learned skills use the numeric slots 1 through 0. The Adventure
preset reserves its charged-attack and guard keys, so Studio reports E and
F conflicts without blocking the save.
targeting.range is expressed in map tiles. A projectile can override its
travel range in pixels; otherwise Action Battle derives it from the targeting
range and tile width. When action.mode is projectile and no custom
projectile type is supplied, the built-in action-battle-skill CanvasEngine
renderer displays projectile.graphic, applies scale, and optionally rotates
the graphic along its trajectory. Damage and the impact animation/sound occur
only when the authoritative projectile collides.
Area masks use # for affected tiles and . for empty tiles. Studio exposes
this as a visual grid and canonicalizes legacy binary masks by treating 1 as
affected and 0 as empty.
Use action.target to select enemy, ally, self, or any. Ranged ally
skills and area shapes enter the targeting overlay; a single-target enemy
projectile can use the soft target in front of the player.
animations is optional. If you omit it, attacks keep using the default
attack animation and no extra hurt, death, or skill-cast animation is played.
Adventure attacks lock movement and facing through their active frames by
default, then allow movement or dodge to cancel recovery. Control locks are
leased independently, so a hurt, guard, dodge, or follow-up attack cannot let
an older timer restore stale animation or direction flags.
lockMovement to false if you want players to keep moving while
attacking. The client stops local predicted movement as soon as the action
input is pressed and shows a short slash preview by default. Disable
showPreview when you provide your own client-side attack effect.
For precise attack-by-attack control, use the profile’s control block:
impact visual preset adds CanvasEngine particles, anchored floating
damage typography, screen shake, and a short render-only hit-stop.
Accessibility controls can disable these independently without changing server
combat:
Recommended composable DX
New action-battle configuration is grouped by responsibility:combatowns gameplay rules: player attack profile, damage, knockback, and hit hooks.visualowns temporary combat feedback: sprite animations, flashes, damage text, CanvasEngine effects, and previews.uiowns client components: action bar, targeting overlay, attack preview, and custom GUI or sprite components.aiowns reusable AI behavior functions.skills.targetingowns action targeting metadata for skills.
visual preset on the server and client when your project splits
configuration by runtime. The server still decides when authoritative hit, hurt,
skill, and enemy attack feedback should happen, but it sends one compact
action-battle client visual event. The client resolves that visual locally and
groups the flash, hit text, sound, component animation, or sprite animation.
This keeps gameplay authority on the server while avoiding several visual
packets for one combat moment. The client also triggers local input feedback
such as the attack preview.
@rpgjs/action-battle.
Visual composition
createActionBattleVisual() accepts a preset or a map of visual parts. A visual
part is a function receiving the current combat context and helper methods.
impact preset selects an official CanvasEngine preset from the combat
context: hitSpark for normal hits, slashSpark for combo finishers,
impactBurst for charged or critical attacks, magicBurst for skills, and
healPulse for healing. Its world-space damage popup changes font size, color,
outline, movement, and duration for the same contexts. The built-in component
animations are registered as action-battle-hit-fx and
action-battle-damage.
A skill or weapon action can override its impact presentation with serializable
metadata:
fx.component(entity, id, params).
Action-battle visual composition runs through the general
Client Visuals mechanism for server-triggered combat
feedback. Use direct server visual APIs for isolated one-off effects, and use
visual here when you want action-battle to group combat presentation on the
client.
The legacy animations option still works for sprite animation names and
temporary graphics. New orchestration should go through visual, while
animations remains useful as the data source used by
fx.graphic(entity, "attack"), fx.graphic(entity, "hurt"), and
fx.graphic(entity, "castSkill").
Composable UI
Action Battle uses RPGJS’s generic, server-authoritative hotbar. Enable it on the server withui.hotbar; targeting and attack-preview components remain
client-owned and replaceable.
Compatibility
The legacyattack, systems.combat, systems.ai, and skills.getTargeting
options are still supported. New code should prefer combat, ai, and
skills.targeting so each part of the action-battle module stays independently
replaceable.
Attack profile model
Useattack.profile to describe the timing model of a player attack in one
typed object. A profile separates the attack into startup, active, and recovery
phases so combat systems can share the same vocabulary.
350ms. The player attack runtime uses
startupMs before creating the hitbox, activeMs to keep the hitbox active,
and totalDurationMs for movement and direction locks.
Weapons can override the player attack profile from their database entry:
Skill and weapon actions
Skills and weapons can define anaction block for action-battle selection,
while their effect stays automatic by default.
BattleAi evaluates every learned skill and uses attackSkill as a priority
hint. A skill that is cooling down, too expensive, out of range, or unable to
cover the target does not block a normal attack. Player hotbar skills, enemy
skills, and configured equipped weapons use the same executor, so onUse
receives the same context in every case.
onUse, action-battle applies the standard RPGJS skill effect:
SP cost, hit rate, states, and damage formulas. For weapons, the default effect
is a physical hit using the equipped weapon stats and action-battle hit hooks.
action.target can be "enemy", "ally", "self", or "any"; enemy
resolution uses the attacker’s action-battle faction and targets selector.
Projectile direction uses the same generic projectile options as
map.projectiles.emit(), including spreadDegrees and accuracy.
Use onUse(user, target, ctx) only when the action needs custom logic:
ctx.defaultEffect():
Plugin-first extension points
Action battle is structured as replaceable systems. You can keep the default Zelda-like sword attack and only replace the pieces your game needs.@rpgjs/action-battle/server:
ActionBattleCombatSystem, ActionBattleAiBehavior,
ActionBattleHitHooks, and ActionBattleHitResult.
For data-driven enemies, use createActionEnemy():
BattleAi, the server lets the
event handle onAction and does not create the combat hitbox. Enemy events
with BattleAi still trigger the A-RPG attack.
Configure stats with the standard RPGJS API
The AI uses the event’s existing data.Health and resources
Parameters
Skills
Items and equipment
Classes
States
AI configuration
All AI options are optional:animations override the global provideActionBattle() animations.
Use a string for a simple animation name, an object to temporarily switch
graphics, or a resolver function for data-driven events. Return null or
undefined from a resolver to skip the animation.
attackProfiles lets enemies telegraph attacks with startupMs, keep hitboxes
active for activeMs, and apply hit reactions. poise controls interruption:
an incoming hit only stuns the enemy when its reaction.staggerPower is greater
than or equal to the enemy’s poise.
rewards are awarded once to the player who lands the killing blow. On defeat,
Action Battle calls event.remove({ reason: "defeated", transition }). The
server removes collision immediately while clients keep the sprite long enough
to play its Studio die animation and a configurable CanvasEngine death effect.
Use presentation.death to tune the effect, scale, shake, and duration, or set
it to false for immediate removal. The legacy onDefeated(event, attacker)
signature remains supported for two-argument callbacks.
When combat spritesheets come from RPGJS Studio media fields, convert the media
ids with createStudioActionBattleAnimations(). Studio-generated combat
spritesheets are played with setGraphicAnimation("attack", graphic, 1) by
default:
provideStudioGame(). You can still pass a static
object when you want to override the media ids manually. Animation values may be
media ids or media objects returned by the Studio game API.
Studio four-direction attack spritesheets play in 350ms by default so their
visual timing matches the default Adventure attack profile without changing the
walk animation speed. A media record may override this client-side presentation
with metadata.attackDurationMs; gameplay startup, active, and recovery windows
remain server-authoritative and independent.
For Studio enemies, the runtime reads enemy.animations automatically when an
enemy is created from the Studio database. The supported Studio fields are
attack, hurt, die, castSpell, guard, parry, and stagger;
castSkill is also accepted when you configure action-battle directly.
stagger falls back to the Studio hurt animation. These values are resolved
on the server and sent to the client as plain animation data, so media IDs
remain usable without transferring resolver functions.
Combat sounds and dynamic music
Action Battle can play local action cues and crossfade the current map BGM into a looping battle track:{ id, volume, cooldownMs }. Basic attacks are predicted locally and suppress
the matching authoritative cue for 250ms. A skill’s own sound field takes
priority over the generic skill cue. Cue cooldowns prevent repeated area hits
from producing an unusable wall of sound.
Combat threat is authoritative on the server and isolated per player. Alert,
combat, fleeing, and stunned enemies keep that player in combat. On equal
priority the currently selected enemy stays stable; bosses default to priority
100 and ordinary enemies to 0. Configure an enemy-specific track with:
combatAudio, map combatMusic, and enemy combatMusic
plus combatMusicPriority. createStudioActionBattleAudio(config) can provide
the same settings statically before these fields are available in a project.
Studio media ids are resolved lazily; resolving a new effect does not stop
music that is already playing.
The Adventure AI director limits how many enemies attack one target
simultaneously. Other enemies keep repositioning instead of stacking the same
attack:
Composable AI behaviors
Action Battle has three AI layers. They all run on the server and end in the same authoritative runtime for movement, attacks, skills, cooldowns, hit reactions, and rewards.- Presets are the fastest path for common enemies.
- Simplified behaviors are readable rule lists that return intentions.
- Behavior trees are the advanced API for bosses and custom enemy logic.
Presets
Use a built-in preset and override only what changes:aggressive, defensive, ranged, tank, and
berserker. A preset is only behavior configuration. Stats still come from the
event itself.
Register project presets through provideActionBattle():
preset: "name". Local options passed to new BattleAi()
override the preset values.
Simplified behaviors
UsesimpleBehavior when you want expressive rules without writing a full
behavior tree. Each rule checks a condition and returns an intent:
Common intention helpers:
Behavior trees
UsebehaviorTree when you need explicit tree control. A tree node returns
success, failure, or running, optionally with an intent or decision.
Dynamic behavior and memory
Intent functions receive the AI context and can usememory for per-enemy
state:
Boss phases and delayed sequences
Phase helpers keep scripted behavior in the existing behavior-tree runtime. Their state is stored per AI instance inmemory, so a preset can safely be
shared by several enemies:
phase(key, hpRatio, action) completes once after HP falls below the ratio.
once(key, action) provides the same one-time behavior without an HP
condition. cooldown(key, ms, action) starts its cooldown only after the
wrapped action succeeds. sequenceWithDelay(key, steps) advances across AI
ticks, and wait(ms) uses the authoritative server clock without creating
client-owned gameplay timers.
Use run(callback) when project logic is local to the tree. For reusable or
module-provided behavior, register a named action:
callAction() returns failure when its name is not registered, allowing a
selector to continue to a fallback branch.
Server-driven AI visuals
visual() sends a JSON-shaped cue through the existing Action Battle client
visual packet. The server decides when it happens; the client handler only
renders it:
once() or cooldown() around cues selected by a tree branch so they are not
sent on every 100 ms AI tick. Visual handlers must never apply damage, change
stats, select targets, or otherwise own gameplay state.
Movement and server actions
The advanced intent helpers are thin wrappers over the controlled event:
Teleport helpers deliberately use the normal RPGJS teleport primitive and do
not search for a collision-free position. Projects that need safe placement
should resolve it in
run() or a registered action before calling
teleportTo().
Sample project
samples/sample-dev contains four AI demo enemies on center-map:
Preset Rusheruses a named preset.Simple KiterusessimpleBehaviorand distance control.Tree Eliteuses a directbehaviorTree.Phase Bossuses delayed phases, generic visuals, speed changes, teleportation, and a registered action.
Enemy types
Enemy types affect behavior, not stats:Attack patterns
Use skills for attacks
attackSkill makes one skill
the preferred opener without excluding the others. Melee skills require contact,
projectiles use their configured travel range, and instant area skills use
targeting.range plus aoeMask. While no action is ready, the enemy approaches,
retreats, or strafes toward the useful range of its next action.
Self-targeted healing and support skills are used automatically below 60% HP.
Ally-targeted skills are outside the current automatic planner.
Debug enemy decisions
Decision tracing is disabled by default. Enable it on the authoritative server and optionally filter one enemy or a set of categories:decision logs include the distance, global cooldown, evaluated skills,
effective ranges, rejection reasons, and the selected attack or repositioning
request. Common rejection reasons are cooldown, insufficientSp,
outOfRange, invalidTarget, maskMiss, and notUseful.
For increasing levels of control, start with learned skills only, add
attackSkill for an explicit priority, use defineActionBattleAiPreset() for
reusable typed defaults, then use behaviorKey or a behavior tree for fully
custom decisions.
Examples
Basic enemy
Mage with skills
Patrol guard
Player combat
The module handles player attacks via theaction input:
Knockback system
Knockback force is driven by the equipped weapon’sknockbackForce property: