rpgjs-solo-action-battle
v5.0.0-beta.29.solo.2
Published
Transport-free deterministic action combat for RPGJS Solo
Readme
rpgjs-solo-action-battle
Deterministic, transport-free action combat for RPGJS Solo. It adapts RPGJS v5's attack-profile and hit-policy vocabulary to the Solo fixed-tick runtime; it does not import the upstream server, client, rooms, projectiles, timers, or AI runtime.
The package owns combat execution, not combat decisions. Human input, replay,
and Yuka governors all dispatch the same public combat:use and
combat:guard actions. Startup, active, recovery, cooldown, resource,
targeting, guard, projectile, status, damage, and defeat state lives on the
authoritative Solo entities and therefore participates in normal snapshots.
Projectiles travel as non-body-blocking sensors: map collision still stops a
shot, while invalid targets such as neutral NPCs and defeated bodies cannot
silently absorb it before the combat target filter runs.
When a command includes targetId but omits direction, Solo aims from the
attacker toward that authoritative target. An explicit direction still wins.
This keeps targeted melee, projectile, and governed class actions correct after
kiting or knockback changes the actor's old facing.
import { SoloRuntime } from 'rpgjs-solo'
import { SoloActionBattle } from 'rpgjs-solo-action-battle'
const runtime = new SoloRuntime()
const combat = new SoloActionBattle(runtime)
combat.registerAction({
id: 'knight-slash',
name: 'Crown Slash',
mode: 'melee',
range: 42,
arcDegrees: 110,
damage: { powerScale: 1.1 },
profile: { startupTicks: 5, activeTicks: 4, recoveryTicks: 11 }
})
combat.registerCombatant('hero', {
faction: 'crown',
actions: ['knight-slash'],
power: 18,
guard: { damageReduction: 0.7 }
})
runtime.dispatch({
type: 'action',
entityId: 'hero',
action: 'combat:use',
payload: { actionId: 'knight-slash', targetId: 'slime' },
source: 'human'
})Human UI and Yuka observation adapters can query the exact same validation without issuing speculative commands:
const shot = combat.canUseAction('hero', {
actionId: 'hunter:longbow',
targetId: 'slime'
}, 'ai')
const movement = combat.canMove('hero')
const guard = combat.canGuard('hero')These queries include pause, defeat, stun/root, active/recovery, cooldown,
learned-action, resource, target, status, and game-authored canUse rules. They
never spend resources, start actions, or append commands.
Game-authored class kits, enemy tuning, encounter eligibility, rewards, and story consequences remain in the game. The reusable fixed-tick mechanics and telemetry remain here.
