PluginBench
Skill
Pass
Audit score 90

msw-combat-system

msw-git/msw-ai-coding-plugins-official

Complete MSW combat pipeline: attacks, damage, hit reactions, game feel, state management, events, and AI FSM with native API support.

What is msw-combat-system?

A comprehensive integration guide for the MSW combat system covering the full Attack→Hit pipeline, damage calculation, knockback, hit stop, camera shake, sprite flash, SFX, death/revive mechanics, damage skins, hit effects, avatar animations, and AI finite state machines. Use this when building combat-capable entities, monsters, bosses, or projectile systems in MSW.

  • Attack resolution with Box/Circle/Polygon collision shapes and target filtering via IsAttackTarget/IsHitTarget
  • Damage model with CalcDamage, CalcCritical hooks, and HitEvent.Extra for custom data propagation
  • Hit reactions including per-body knockback, i-frame management via IsHitTarget, and state transitions
  • Six native game-feel systems: Hit Stop, Camera Shake, Zoom, Sprite Flash, VFX spawning, and SFX playback
  • Combat state management with StateComponent FSM, DeadEvent/ReviveEvent, and PlayerComponent HP/revive
  • Event bus with HitEvent, AttackEvent, StateChangeEvent, PlayerActionEvent, and custom @Event decorators

How to install msw-combat-system

npx skills add https://github.com/msw-git/msw-ai-coding-plugins-official --skill msw-combat-system
Prerequisites
  • MSW environment with access to NativeScripts and .d.mlua type definitions
  • Understanding of MSW component architecture (AttackComponent, HitComponent, StateComponent, AIComponent)
  • Familiarity with MSW event system and custom @Event decorators
  • Knowledge of Lua scripting and MSW's mlua syntax for method overrides
Claude Code
Cursor
Windsurf
Cline

How to use msw-combat-system

  1. 1.Review the coverage matrix (§0) to identify which combat layers you need to implement
  2. 2.Read the relevant references/* files: monster.md for entity setup, projectile.md for ranged attacks, ai-bt.md for behavior trees, hp-gauge.md for UI
  3. 3.Implement attack shapes using CircleShape, BoxShape, or PolygonShape with appropriate offsets and sizes
  4. 4.Override IsAttackTarget on AttackComponent and IsHitTarget on HitComponent for faction/immunity filtering
  5. 5.Hook CalcDamage and CalcCritical for custom damage formulas and critical hit logic
  6. 6.Attach game-feel components (Hit Stop, Shake, Flash, SFX) to trigger on HitEvent
  7. 7.Set up StateComponent FSM for combat states (idle, attacking, hit, dead) and wire DeadEvent/ReviveEvent
  8. 8.Use AIComponent with behavior trees or StateComponent for monster decision-making, leveraging AIChaseComponent/AIWanderComponent for movement

Use cases

Good for
  • Building a melee combat monster with frontal box attacks, knockback, and overhead HP bar
  • Creating a ranged attack system with projectiles that pierce multiple targets and trigger hit effects
  • Implementing boss AI with behavior trees, state transitions, and multi-layer decision making
  • Setting up game-feel feedback (hit stop, screen shake, sprite flash, sound) on successful hits
  • Designing a damage system with critical hits, element affinity, and custom damage calculations
Who it's for
  • Game designers building combat-capable monsters and bosses
  • Programmers implementing attack resolution and hit detection systems
  • AI/gameplay engineers designing FSM-based or behavior tree-driven enemy behavior
  • Combat systems designers working with damage models, knockback, and hit reactions
  • Developers creating projectile systems, ranged attacks, or area-of-effect abilities

msw-combat-system FAQ

What collision shapes are supported for attacks?

Only Box, Circle, and Polygon shapes have native MSW API support. Other shapes like Capsule, Cone, or Ray must be approximated by composition or custom logic.

How do I prevent the same target from being hit multiple times by one attack?

Duplicate-hit prevention is not native. Manage it in script by caching hit targets in a table<Entity, boolean> and checking it before applying damage.

Can I use a timer-based movement system for monsters and projectiles?

Not recommended. Timer-based movement (0.1–0.15s intervals) produces choppy 6–10Hz teleportation. Use OnUpdate(delta)-based movement instead: MovementComponent:MoveToDirection for monsters, TransformComponent:Translate for body-less projectiles.

What is the difference between Attack and AttackFast?

Attack returns a table of hit components for further processing. AttackFast skips table building and is optimized for mass resolution scenarios like bullet hell, offering better performance.

Where do I find full implementation examples?

This SKILL.md covers system flow and API surface. Full code, model JSON, and variation patterns are in references/* files: monster.md for entity setup, projectile.md for ranged attacks, ai-bt.md for behavior trees, and hp-gauge.md for HP bars.

Full instructions (SKILL.md)

Source of truth, from msw-git/msw-ai-coding-plugins-official.


name: msw-combat-system description: "MSW combat system integration guide. Covers the Attack→Hit pipeline, damage model, i-frame, knockback, Hit Stop, Camera Shake, Sprite Flash, SFX, death/revive, damage skin, hit effect, avatar combat motion, custom events, and AI FSM — all based on MSW native APIs for 2D multi-genre coverage. Keywords: attack, hit, damage, combat, monster, hit effect, critical, projectile, damage skin, knockback, hit stop, combo, HP bar."

msw-combat-system

The full MSW combat pipeline. Covers only items in the common 2D combat layer that have MSW native API support, regardless of genre. Excludes formulas/theory. API signatures are based on Environment/NativeScripts/**/*.d.mlua.


0. Coverage matrix

#LayerNativeCustom required
1Attack ResolutionAttackComponent + HitComponent (Box/Circle/Polygon)Capsule/Cone/Ray, pierce count
2Damage ModelCalcDamage/CalcCritical/GetCriticalDamageRate/GetDisplayHitCount hooks + HitEvent.Extra:anyElement affinity, composite formulas
3Hit ReactionPer-Body knockback API, IsHitTarget-based i-frameStagger level, status effects
4Game FeelAll 6 native (Hit Stop, Shake, Zoom, Flash, VFX, SFX)
5Combat StateStateComponent + DeadEvent/ReviveEvent, PlayerComponent HP/reviveMP/Stamina/Rage, aggro
6Event BusHitEvent/AttackEvent/StateChangeEvent/PlayerActionEvent + custom @EventOnKill/OnBlocked
7AIStateComponent (FSM) + AIComponent (BT, 4 Composite types native) + AIChaseComponent/AIWanderComponent, _UserService.UserEntitiesDecorator/Memory(Blackboard), Threat Table
+Damage Skin3 DamageSkin* components + DamageSkinService
+Hit EffectHitEffectSpawnerComponent (auto)
+Avatar MotionAvatarStateAnimationComponent (State→MapleAvatarBodyActionState)

0.5 References — where to go

This SKILL.md covers only the system flow and native API surface. Actual model JSON, full script code, and variation patterns are in the references/* files below — Read them directly.

FileScopeWhen to read
../msw-general/references/monster.mdMonster .model component assembly + ActionSheet + AI choice + canonical Pattern A scripts (Soldier-style) + HP/Respawn + spawn + verificationWhen building a combat-capable monster
references/hp-gauge.mdFull implementation of an overhead HP bar based on PixelRendererComponentWhen attaching an overhead HP bar
references/projectile.mdProjectile (Body-less entity + OnUpdate Translate) + homing/pierce/splash variantsWhen building ranged attacks like arrows, bullets, magic bolts
references/ai-bt.mdBehaviourTree — AIComponent + 4 Composite types + @BTNode + custom Decorator/Memory/ThreatWhen you need BT-based monster/boss AI and multi-layer decision making

Priority: *this SKILL.md (concepts + API tables) → the relevant references/ (full implementation)**.


1. Attack Resolution

1-1. Shape & Attack trigger

HitComponent.ColliderType supports only Box / Circle / Polygon. Other shapes must be approximated by composition.

AttackComponent:
  Attack(Vector2 size, Vector2 offset, string attackInfo, CollisionGroup? cg)    → table<Component>
  Attack(Shape shape, string attackInfo, CollisionGroup? cg)                     → table<Component>
  AttackFast(Shape shape, string attackInfo, CollisionGroup? cg)                 → void   (for mass resolution, bullet hell)
  AttackFrom(Vector2 size, Vector2 position, string attackInfo, CollisionGroup? cg) → table<Component>
  emitter EmitAttackEvent(AttackEvent)
  • Shapes: CircleShape(position, radius) / BoxShape(position, size, angle) / PolygonShape(position, points, angle). For an axis-aligned rectangle, use BoxShape(center, size, 0) — there is no RectangleShape type. Pass angle = 0 to BoxShape / PolygonShape when no rotation is needed.
  • Polygon hit surface: HitComponent.PolygonPoints: SyncList<Vector2>
  • AttackFast does not build a hit table → better performance for bullet hell / mass resolution

1-2. Target filter

SideOverridePurpose
AttackerAttackComponent:IsAttackTarget(defender, attackInfo) → booleanFaction / distance / state
DefenderHitComponent:IsHitTarget(attackInfo) → booleanInvincibility / immunity

If either returns false → the hit is excluded. The super call is __base:IsAttackTarget(...) (mlua-specific).

Do not add @ExecSpace when overriding — both IsAttackTarget and IsHitTarget have an unspecified ExecSpace (=All) on the parent. Adding an annotation like @ExecSpace("ServerOnly") in the child triggers LEA-3014 SignatureMismatch at runtime. Even without the annotation, the call path runs through the server-side hit pipeline, so actual execution happens on the server. Details: msw-scripting/SKILL.md §9 "Method override".

  • HitComponent.CollisionGroup defaults to CollisionGroups.HitBox. The last argument of Attack(..., cg) specifies the target group.
  • Duplicate-hit prevention / pierce / max hits: not native. Manage in script via the table returned by Attack + a table<Entity, boolean> cache.

1-3. attackInfo tagging

A string extension point that propagates into CalcDamage/IsHitTarget/GetDisplayHitCount. Value conventions are up to the project. A namespace style such as "melee.light", "dot.poison" is recommended.

1-4. ⚠️ IsLegacy

ColliderType/ColliderOffset/PolygonPoints are only valid when HitComponent.IsLegacy = false. BoxOffset/ColliderName are deprecated.

1-5. Shape mapping per attack form

FormShape construction
Frontal melee BoxBoxShape(pos + LookDirectionX*offset, size, 0) — see DefaultPlayer PlayerAttack
Circular AoECircleShape(self.WorldPos, radius)
ProjectileSpawn a Body-less model (Sprite+Transform only) + in OnUpdate(delta) call TransformComponent:Translate(speed*delta, 0) + distance-based hit check + _EntityService:Destroy. Movement rules in §1-6, full implementation → references/projectile.md

There is no MSW-specific projectile system — implemented as an entity + AttackComponent combo.

1-6. Continuous movement — common rules for projectiles, monsters, and AI

Continuous movement (chase, flight, auto-move) is per-frame OnUpdate(delta)-based. Moving via a timer (SetTimerRepeat(0.1~0.15s)) produces 6~10Hz teleportation that looks choppy.

Recommended API per target

TargetBody?Movement APIRationale
Monster / NPC / AIYes (map-type Body + MovementComponent)MovementComponent:MoveToDirection(dir, 0) + MovementComponent.InputSpeedMovementComponent.d.mlua:1 — controls all three of Rigid/Kinematic/Sideview. InputSpeed belongs to MovementComponent (.d.mlua:7), so it is not Player-only. The second arg 0 — deltaTime is applied only on ladders (.d.mlua:32). Official BT examples ActionFollow/ActionMoveRandom also use 0.
Projectile / gem / drop item / effectNo (Sprite+Transform+Trigger)self.Entity.TransformComponent:Translate(speed*delta, 0) every frameDirect Transform manipulation is safe without a Body. The pattern from the official "Create a Long-Range Projectile" tutorial.
Direct Rigidbody control (advanced)Yesbody:AddForce(...) — sustained acceleration / impulseRigidbodyComponent.d.mlua:71MoveVelocity is "mainly controlled by MovementComponent", so prefer routing through MovementComponent instead of writing it directly.

For the actual velocity conversion of MovementComponent.InputSpeed per map type, see msw-general/references/platform.md §10 (MapleTile=×1, RectTile=÷1.2, SideView=×1.5).

Forbidden patterns

Reason
_TimerService:SetTimerRepeat(move, 0.1~0.15) for movement6~10Hz teleport, no frame interpolation → jerky
body:SetPosition(...) / MovementComponent:SetPosition(...) inside OnUpdateBoth are teleport methods (MovementComponent.d.mlua:37, SetPosition on each Body's .d.mlua). Using them for continuous movement is choppy. Use only for one-shot spawn/respawn/snap.
self.Entity.TransformComponent.Position = newPos (entity with active Body)The physics engine overwrites it next frame and network sync is blocked.
Constant-step move without delta, e.g. Translate(0.009, 0)Frame-rate dependent. Speed differs between 60FPS and 30FPS.

⚠ Be cautious with the official msw-search antipattern

mlua_Document_Retriever returns a high-scoring "Entity Movement Control Using MovementComponent" document, but the body is an antipattern that moves every frame inside OnUpdate with MovementComponent:SetPosition(...)ignore that document and use MoveToDirection / Translate from the table above. FlappyFish Remake, Stopping the Taxi, and Making a Moving Foothold also show missing-delta or direct-Position assignment, so be careful when referring to them.

MovementComponent attachment — monsters / NPCs

Not included in the monster model by default. The .model must contain all of the following components.

ComponentNotes
MOD.Core.TransformComponentDefault
MOD.Core.SpriteRendererComponentRenderer
Body (map type)RigidbodyComponent(MapleTile) / KinematicbodyComponent(RectTile) / SideviewbodyComponent(SideViewRectTile)
MOD.Core.MovementComponentE.g. InputSpeed = 2.0 — required for movement APIs

Cross references

  • Knockback (1-shot impulse) is not continuous movement, so use §3-1 directly.
  • Body selection per map type / InputSpeed conversion: msw-general/references/platform.md §4·§10

2. Damage Model

AttackComponent:
  method integer CalcDamage(attacker, defender, attackInfo)        -- default 1     (ExecSpace=All)
  method boolean CalcCritical(attacker, defender, attackInfo)      -- default false (ExecSpace=All)
  method float   GetCriticalDamageRate()                           -- default 2.0   (ExecSpace=All)
  method int32   GetDisplayHitCount(attackInfo)                    -- default 1     (ExecSpace=All)
  method void    OnAttack(defender)                                                 -- (ExecSpace=All)

HitComponent:
  method void OnHit(Entity attacker, integer damage, boolean isCritical, string attackInfo, int32 hitCount)
  emitter EmitHitEvent(HitEvent)

⚠ All hooks above have an unspecified ExecSpace (=All) on the parent. Adding @ExecSpace("ServerOnly") etc. when overriding triggers LEA-3014 SignatureMismatch. Drop the annotation and declare just method .... Details: msw-scripting/SKILL.md §9 "Method override".

2-1. HitEvent payload

AttackCenter:   Vector2
AttackerEntity: Entity (nilable)
Damages:        List<integer>    -- multi-hit split
Extra:          any              -- ★ extension slot (knockback/stun/element/tags)
IsCritical:     boolean
TotalDamage:    integer
FeedbackAction: HitFeedbackAction  -- ⚠ entire enum deprecated

Carry auxiliary info (knockback vector, stagger time, element) on the Extra table.

2-2. AttackEvent payload

A single field, DefenderEntity: Entity. The attacker is the handler's self.

2-3. ⚠️ Antipattern: direct HP subtraction — do not bypass HitEvent

Subtracting the defender's HP directly, such as monster.Hp -= damage / target.MonsterAI.HP -= damage, does not emit HitEvent — damage skin, hit effect, IsHitTarget immunity, and OnHit overrides all silently skip.

For player-side custom damage (channel / aura / DoT), the bypass also breaks avatar animation: AvatarStateAnimationComponent only reacts to StateChangeEvent, so the player avatar stays in idle even though the HP bar drops. If you must apply damage without HitEvent, also manually call StateComponent:ChangeState("HIT") (UPPERCASE key — see §10) and, for death/revive, PlayerComponent:ProcessDead() / ProcessRevive(). Otherwise hit/dead motions silently miss with no error.


3. Hit Reaction

3-1. Knockback — API per Body

Body (map type)Implementation
Rigidbody (MapleTile)body:AddForce(Vector2(dir*5, 3)) ★recommended · SetForce · JustJump(Vector2(0, 4)) (vertical)
Kinematicbody (RectTile / top-down)body.MoveVelocity = Vector2(dir*5, 0) — no AddForce
Sideviewbody (SideViewRectTile)body.MoveVelocity + body.JumpSpeed
  • Rigidbody is auto-damped by the engine. Kinematic/Sideview must be damped manually inside OnUpdate (MoveVelocity *= 0.9).
  • Wall bounce: subscribe to FootholdCollisionEvent and flip the velocity.
  • Knockback is a 1-shot impulse — do not confuse it with continuous movement (chase, flight). For continuous movement see §1-6.
  • ⚠ Forbidden: assigning TransformComponent.Position directly on an entity with an active Body → network sync is blocked. body:SetPosition(...) is a teleport method, so do not call it inside an OnUpdate loop (§1-6).

3-2. i-frame

Standard pattern: deadline check based on _UtilLogic.ElapsedSeconds + returning false from HitComponent:IsHitTarget. The DefaultPlayer default PlayerHit.mlua provides this pattern as-is (§9-4).

Alternative: while invincible, swap HitComponent.CollisionGroup to a separate group → the resolution itself is excluded. This is better for frame-accurate precision.

3-3. Status effects (Buff/Debuff)

No native support. Implement a @Component BuffComponent directly + tick with _TimerService:SetTimerRepeat + broadcast custom StatusAppliedEvent/StatusExpiredEvent.

For a single simple stun, StateComponent:ChangeState("STUN") + input/AI block flags is enough.


4. Game Feel — all native

ElementAPIExecSpace
Hit Stop (global)_UtilLogic:SetClientTimeScale(float) — 0~100ClientOnly
Hit Stop (individual)renderer.PlayRate = 0 (Sprite/Skeleton/Avatar)@Sync
Slow Motion_UtilLogic:SetClientTimeScale(0.3) + timer to restoreClientOnly
Camera ShakecameraComp:ShakeCamera(intensity, duration, targetUserId?)Client
Camera ZoomcameraComp:SetZoomTo(percent, duration, targetUserId?) · requires IsAllowZoomInOut=true firstClient
Hit FlashspriteRenderer.Color = Color(r,g,b,a) → timer to restore@Sync
Color HDR overbrightColor.HSVToRGB(h, s, v, hdr=true) — values > 1.0 allowed
VFX fixed_EffectService:PlayEffect(clipRUID, instigator, pos, zRot, scale, isLoop?, options?) → serial
VFX attached_EffectService:PlayEffectAttached(clipRUID, parent, localPos, localZRot, localScale, isLoop?, options?)
VFX remove_EffectService:RemoveEffect(serial)
SFX 2D_SoundService:PlaySound(id, volume, targetUserId?)Client
SFX 3D_SoundService:PlaySoundAtPos(id, pos, listener, volume)Client
SFX loopPlayLoopSound / PlayLoopSoundAtPosClient
SFX attachedSoundComponent:Play() · pitch randomization via Pitch 0~3Client
BGM_SoundService:PlayBGM(id, volume) / StopBGM(immediately)Client
Preload_SoundService:LoadSound(id)ClientOnly

PlayEffect options keys: FlipX, FlipY, SortingLayer, OrderInLayer, Alpha, StartFrameIndex, EndFrameIndex, PlayRate, SyncFlip, Color, MaterialID, IgnoreMapLayerCheck, LitMode

Get the current camera: _CameraService:GetCurrentCameraComponent().

ParticleService — built-in particles

General-purpose particle effects driven by enum values only, no RUID. 3 categories:

-- BasicParticle: general-purpose presets (no RUID needed)
integer _ParticleService:PlayBasicParticle(BasicParticleType, Entity instigator, Vector3 pos, number zRot, Vector3 scale, boolean isLoop, Dictionary options)
integer _ParticleService:PlayBasicParticleAttached(BasicParticleType, Entity parent, Vector3 localPos, number localZRot, Vector3 localScale, boolean isLoop, Dictionary options)

-- SpriteParticle: custom sprite as a particle (spriteRUID required)
integer _ParticleService:PlaySpriteParticle(SpriteParticleType, string spriteRUID, Entity instigator, Vector3 pos, number zRot, Vector3 scale, boolean isLoop, Dictionary options)
integer _ParticleService:PlaySpriteParticleAttached(SpriteParticleType, string spriteRUID, Entity parent, Vector3 localPos, number localZRot, Vector3 localScale, boolean isLoop, Dictionary options)

-- AreaParticle: environmental particles over a wide area (areaSize added)
integer _ParticleService:PlayAreaParticle(AreaParticleType, Vector2 areaSize, Entity instigator, Vector3 pos, number zRot, Vector3 scale, boolean isLoop, Dictionary options)

void _ParticleService:RemoveParticle(integer serial)

options keys: Color, SortingLayer, OrderInLayer, ParticleSize, ParticleCount

Looping particles (isLoop=true) must be cleaned up via RemoveParticle(serial). Store the serial in self._T so it can be removed later.

Full BasicParticleType list

FamilyNameDescription
Explosion/impactSparkExplosionSparks (one-shot) — general-purpose hit
SparkLoopContinuous sparks
SparkRadialExplosionSparks scattering radially
SmallExplosionSmall explosion + smoke
BigExplosionBig explosion + smoke
TinyExplosionVery small explosion (Color option ignored)
DustExplosionCircular shockwave + smoke (Color option ignored)
EnergyExplosionCircular shockwave then center convergence
CircleBurstCircular light burst
PillarBurstCircular light burst + directional light
Fire/flameFireFieldCartoon flames
FireFieldIntenseIntense cartoon flames
FireBallFlame at a single point
FlameThrowerFlamethrower stream
LargeFlamesLarge flames from the floor
MediumFlamesMedium flames from the floor
TinyFlamesTiny flames from the floor
WildFireGiant pillar of flame (Color option ignored)
Lightning/electricLightningOrbSharpSpherical electric particles
LightningStrikeSharpLightning bolt
LightningStrikeSharpTallTall lightning bolt
LightningOrbSoftElectric wave emission
LightningBlastPeriodic electric waves
LightningStrikePeriodic lightning
LightningStrikeTallPeriodic tall lightning
Buff/magicAuraAurora light from the floor
BuffStrong light rising from the floor
ChargeLarge particles converging on one point
ChargeOrbParticles converging on one point
EnchantLarge light with light/particles around it
SpinFieldParticles around a rotating circle
StarVortexStarlight converging to the center
NovaWide circular wave
UpperCylinderRising pillar from the floor
MiscFireworkFireworks
FireworkClusterMultiple fireworks at once
FireFliesFireflies
GoopSprayLiquid spray to the side
GoopSprayEffectLiquid spray downwards
DustStormWide dust storm
RisingSteamRising white mist from the floor
BigSplashLarge water splash
ShowerWater poured on one spot

Full SpriteParticleType list (8)

NameDescription
BurstBigSprite emerges in a radial pattern
SpawnFieldParticles + sprite emerge in a circular area
BurstNovaParticles + sprite burst in a circular pattern
SimpleSpawnSimple particle + sprite appearance
BurstParticles + sprite scatter
StreamGenerated while moving in a specific direction
StreamSharpThin line moving in a specific direction
AdditiveColorColor effect applied to the sprite

Full AreaParticleType list (12)

NameDescription
RainRain
SnowSnow
FogCalmFog
FogHeavyHeavy descending fog
FogLivelyRising fog
CalmStarFieldRising star cluster
StarFieldSimpleTwinkling star cluster
StarFogStar + nebula particles (stationary)
StarFogFlowStar + nebula particles (rising)
WindlinesThin lines
WindlinesBigThin lines + thick lines
WindlinesSpeedyFast straight lines

Choosing between EffectService and ParticleService

SituationRecommended
MapleStory skill / hit animations (specific imagery)EffectService (specify RUID)
General hit/explosion (fast to implement)ParticleService.BasicParticle
Scatter a custom image as particlesParticleService.SpriteParticle
Environmental ambience like rain/snow/fogParticleService.AreaParticle
Sustained effects like buff aurasEither with isLoop=true
Rich, layered effectsCombine EffectService and ParticleService

Standard pattern for server event → client effect: @Sync property change → detected in OnSyncProperty(ClientOnly) → call EffectService/ParticleService.


5. Death / Revive

EventEmission conditionPayload
DeadEventAuto on StateComponent:ChangeState("DEAD")none
ReviveEventAuto on PlayerComponent:Respawn() (players only)none
StateChangeEventAuto on every state transitionCurrentStateName, PrevStateName

Tracking the killer: DeadEvent has no payload → cache self.LastAttacker = event.AttackerEntity in HandleHitEvent and use it in HandleDeadEvent.

For player-specific death/revive, prefer §9-1 PlayerComponent.Respawn/ProcessDead/ProcessRevive.


6. Event Bus

Logical eventMSW implementation
OnAttackStartOnAttack hook or custom AttackStartEvent
OnAttackHit / OnDamageTakenNative HitEvent
OnAttackMissCustom — SendEvent when IsAttackTarget returns false
OnCriticalHitCovered by the HitEvent.IsCritical flag
OnDeath / OnReviveNative DeadEvent/ReviveEvent
OnStateChangeNative StateChangeEvent
OnKill / OnBlocked / OnParry / OnStatusAppliedCustom @Event

6-1. Custom event rules

  • Definition: @Event script XxxEvent extends EventType + property declarations
  • Receiving: the handler keyword (not method), @EventSender("Self" | "Service","XxxService" | "Logic","XxxLogic")
  • Connect/disconnect: entity:ConnectEvent(XxxEvent, self.Handler) / call DisconnectEvent in OnEndPlay (the engine does not auto-disconnect)
  • Global: @Logic CombatEventBusLogic singleton + @EventSender("Logic","CombatEventBusLogic")

7. AI — FSM(StateComponent) + BT(AIComponent) + custom-script (Pattern A), all native-compatible

PatternFitReference
FSM (StateComponent + @State)Simple enemies (3~5 states), player IDLE/HIT/DEAD, boss phases, animation sync (AvatarStateAnimationComponent auto mapping §10). Requires StateComponent.IsLegacy=false if you want StateAnimationComponent to auto-swap clips.../msw-general/references/animation-state.md (state-machine + animation pipeline unified)
BT (AIComponent + 4 Composite types + @BTNode)Patrol + chase + attack combos, varied boss patterns, Composite/Decorator reuse, probability-weighted actions. Requires StateComponent.IsLegacy=false.references/ai-bt.md
Custom script with self-state (@Component holding CurrentAIState plus direct SpriteRUID assignment — Soldier-style pattern)Behaviors that don't fit AIChase/AIWander (roam ↔ stand ↔ say ↔ attack, range-gated attacks, talking idle). No AIChaseComponent/AIWanderComponent, no IsLegacy=false needed — the script bypasses the ActionSheet pipeline. Reserve StateComponent for IDLEDEAD only.../msw-general/references/monster.md §7 "Canonical Pattern A Scripts (Soldier)"

7-1. FSM — StateComponent (summary)

StateComponent + @State script XxxStateType extends StateType (lifecycle OnEnter/OnUpdate/OnExit/OnConditionCheck). The only auto-registered states are IDLE/DEAD (+ HIT if a HitComponent exists, MOVE if an AIChase/AIWander exists) — ATTACK/PATROL/STUN/PHASE2 etc. must all be pre-registered via AddState("name", XxxStateType) in OnBeginPlay. Auto transitions use AddCondition(from, to) + per-frame OnConditionCheck().

State names must be UPPERCASE; unregistered names immediately produce [LEA-3005] InvalidArgument : 'stateName'. Registering a key in AvatarStateAnimationComponent.StateToAvatarBodyActionSheet does not auto-register it in StateComponent — the two are separate.

Full implementation → ../msw-general/references/animation-state.md (FSM authoring, ChangeState failure matrix, standard PATROL/CHASE/ATTACK/HIT/DEAD monster pattern, and the state→animation pipeline live together — the two are the same underlying system viewed from two angles)

7-2. BT — AIComponent (summary)

AIComponent + SequenceNode/SelectorNode/RandomSelectorNode/ParallelNode + @BTNode Action Nodes + native AIChaseComponent/AIWanderComponent. All 4 Composite types are native; Decorator/Memory(Blackboard)/Threat Table must be implemented by hand.

⚠ When using custom BT, remove AIChaseComponent/AIWanderComponent from the .model.

Full implementation → references/ai-bt.md

Full monster entity composition → ../msw-general/references/monster.md

This SKILL.md only covers combat-specific aspects (ATTACK/HIT/DEAD + DeadEvent/ReviveEvent + BT entry point). For general mlua state machine / scripting patterns see msw-scripting.


8. UI natives

UIAPI
HP bar (screen-fixed)SliderComponent (MinValue/MaxValue/Value/FillRectColor/FillRectImageRUID/Direction/UseHandle) + SliderValueChangedEvent. ⚠ UI entities only
Damage numbers3 DamageSkin* components + DamageSkinService — §11
CrosshairSpriteGUIRendererComponent in .ui
Combo counter / buff iconsTextComponent + SpriteGUIRendererComponent

Worldspace HP bar (overhead): no native support. Two implementation options:

OptionApproachFit
LightweightAdjust LocalScale.x = hp/maxHp on a child entity's SpriteRendererComponent or use TiledSize.x (with SpriteDrawMode.Tiled)Quick prototype, simple gauge
FullBased on PixelRendererComponentfull implementation references/hp-gauge.mdProduction-grade, many monsters shown at once

9. DefaultPlayer combat natives

The player entity has HP, revive, and input natively. Do not create custom Hp/MaxHp properties — use PlayerComponent.

The full property/method tables for PlayerComponent / PlayerControllerComponent are in msw-defaultplayer/SKILL.md. Only combat essentials here.

9-1. Core combat APIs

ItemUsage
HP decrementself.Entity.PlayerComponent.Hp -= event.TotalDamage
Death checkPlayerComponent:IsDead()
RevivePlayerComponent:Respawn()RespawnPosition → SpawnLocation → map entry point. DeadEvent/ReviveEvent auto-emitted
Client-only death processing@ExecSpace("Client") ProcessDead(targetUserId?) / ProcessRevive(targetUserId?)
Direction check ★PlayerControllerComponent.LookDirectionX (+1 right, -1 left). Do not use TransformComponent.Scale.x
Action hook overrideActionAttack / ActionJump / ActionInteraction(key, isKeyDown) etc.
Action event receptionEmitPlayerActionEvent(PlayerActionEvent) → §9-3

9-3. PlayerActionEvent

property string ActionName       -- "Attack" / "Jump" / "Crouch" / ...
property Entity PlayerEntity

The default pattern is PlayerAttack extends AttackComponent that receives @EventSender("Self") handler HandlePlayerActionEvent(...) and branches on event.ActionName == "Attack".

9-4. Default templates (RootDesk/MyDesk/)

Copy-paste without modification. Override as needed:

FileRoleKey points
PlayerAttack.mluaFrontal Box attackLookDirectionX for direction, AttackFast + CollisionGroups.Monster, CalcDamage=50, 30% crit
PlayerHit.mluai-frameImmuneCooldown property, _UtilLogic.ElapsedSeconds deadline, IsHitTarget override
Monster.mluaMonster HPCustom @Sync Hp (no PlayerComponent), HandleHitEventDead/Respawn
MonsterAttack.mluaSprite-size-based meleeisvalid(defender.PlayerComponent) + __base:IsAttackTarget(...) super in IsAttackTarget

9-5. Time reference

_UtilLogic.ElapsedSeconds is recommended (world clock, consistent across pause/restore). Do not use os.clock().

9-6. Standard CollisionGroups

ConstantPurpose
CollisionGroups.PlayerMonster → Player attack
CollisionGroups.MonsterPlayer → Monster attack
CollisionGroups.HitBoxDefault for HitComponent.CollisionGroup

10. Avatar motion — AvatarStateAnimationComponent

Auto-links StateComponent transitions to avatar animations.

@Sync property SyncDictionary<string, AvatarBodyActionElement> StateToAvatarBodyActionSheet  -- IsLegacy=false
@Sync property SyncDictionary<string, string>                  ActionSheet                    -- IsLegacy=true (deprecated)

method void   SetActionSheet(string key, string animationClipRuid)
method void   RemoveActionSheet(string key)
method string StateStringToAnimationKey(string stateName)
emitter EmitBodyActionStateChangeEvent(BodyActionStateChangeEvent)
  • ChangeState("HIT") → the mapped MapleAvatarBodyActionState.Hit plays automatically
  • Combat-relevant state values: Attack=3, Hit=14, Dead=10, Alert=4, Heal=13
  • IsLegacy=false fixed; use only StateToAvatarBodyActionSheet

The full avatar component coverage (AvatarRendererComponent etc.) is in msw-defaultplayer. This section covers only combat motion mapping.


11. Damage skin (number display)

Default RUIDs

PurposeRUIDUsed on
Hit3271c3e79bf04ecba9a107d55495970dDefault for attacker's DamageSkinSettingComponent.DamageSkinId
Taken hit02c22d93421b4038b3c413b3e40b57ecDefender-side display — call _DamageSkinService:Play manually
Heald58b67cf0f3a4eaf9fe1ad87c0ffac8aHeal/potion — call _DamageSkinService:Play manually

11-1. Auto mode (component-based)

On Attack/AttackFast, if all 3 components below are present, damage numbers are displayed automatically:

SideComponentRole
AttackerDamageSkinSettingComponentWhich skin/style to display
DefenderDamageSkinSpawnerComponentDisplay position offset
DefenderDamageSkinComponentDamage number body (over the entity)

Include all 3 in the .model and damage numbers appear with zero script code.

DamageSkinSettingComponent (attacker)

PropertyTypeDefaultDescription
DamageSkinIdDataRefhit RUID (table above)Damage number skin RUID
DamageSkinScaleVector2(1, 1)Number size
Alphafloat1Opacity
PlayRatefloat1Playback speed
DelayPerAttackfloat0.05Delay between multi-hits (seconds)
TweenTypeDamageSkinTweenTypeDefaultAnimation style
LitModeLitModeDefaultLighting influence

DamageSkinTweenType: Default (popup) / Volcano (fan) / Blade (overlap) / each *Mini (75% scale)

DamageSkinSpawnerComponent (defender)

PropertyTypeDefault
DamageSkinOffsetVector2(0,0)

11-2. Manual mode — DamageSkinService

Cases not caught by auto mode (heal, Miss/Guard, non-standard damage sources) call _DamageSkinService directly.

_DamageSkinService:Play(targetEntity, skinRuid, delay, damages:List<int>, tweenType, isCritical, offset, scale, playRate, alpha, litMode)
_DamageSkinService:PlayTextDamage(targetEntity, skinRuid, textType, tweenType)
_DamageSkinService:PreloadAsync(skinRuid, callback(success))    -- ClientOnly

DamageSkinTextType: Miss / Guard / Resist / Shot / Counter

_DamageSkinService:Play is in the Client space — to call it from server logic (HP subtraction, etc.) wrap it in an @ExecSpace("Client") method or change a @Sync property and trigger from OnSyncProperty.

Play() has 6 required parameters. Passing only some of the 5 optional ones triggers LEA-3005 InvalidArgument.

11-3. Recipes

(a) Critical emphasis — auto mode + dynamic scale

Auto mode renders red font automatically when IsCritical=true. To emphasize further, temporarily increase the attacker-side scale:

-- ⚠ AttackComponent hooks (CalcDamage/CalcCritical/GetCriticalDamageRate/GetDisplayHitCount/
--   IsAttackTarget/IsHitTarget/OnAttack) have an unspecified ExecSpace (=All) on the parent.
--   Adding @ExecSpace when overriding triggers LEA-3014 SignatureMismatch.
--   Details: msw-scripting/SKILL.md §9 "Method override → LEA-3014"
method integer CalcDamage(Entity attacker, Entity defender, string attackInfo)
    return 100
end

method boolean CalcCritical(Entity attacker, Entity defender, string attackInfo)
    return math.random() < 0.3
end

method float GetCriticalDamageRate()
    return 2.5     -- 100 → 250
end

Differentiate criticals visually with DamageSkinSettingComponent.TweenType = Volcano (fan scatter) or Blade (overlap).

(b) Heal / recovery — manual call

local HEAL_RUID = "d58b67cf0f3a4eaf9fe1ad87c0ffac8a"

@ExecSpace("Client")
method void ShowHeal(Entity target, integer amount)
    _DamageSkinService:Play(
        target, HEAL_RUID, 0,
        { amount },                            -- damages
        DamageSkinTweenType.Default,
        false,                                 -- isCritical
        Vector2(0, 0.5),                       -- offset (above head)
        Vector2(1, 1), 1.0, 1.0, LitMode.Default
    )
end

(c) Miss / Guard / Resist text

local HIT_RUID = "02c22d93421b4038b3c413b3e40b57ec"

@ExecSpace("Client")
method void ShowMiss(Entity target)
    _DamageSkinService:PlayTextDamage(
        target, HIT_RUID, DamageSkinTextType.Miss, DamageSkinTweenType.Default
    )
end

Call this when AttackComponent:IsAttackTarget returned false → "miss animation + damage 0".

(d) Multi-hit — split into N with a single call

If you pass a List as the damages argument of _DamageSkinService:Play, the numbers are shown sequentially at DelayPerAttack (attacker component value) intervals:

_DamageSkinService:Play(target, ATTACK_RUID, 0, { 12, 8, 14, 11, 9 },
    DamageSkinTweenType.Default, false, Vector2(0,0), Vector2(1,1), 1, 1, LitMode.Default)

Auto mode behaves identically with HitEvent.Damages (List) — override GetDisplayHitCount(attackInfo) to control the split count.

(e) Preload — prevent first-display stutter

The first use of a skin RUID may have texture loading lag. Preload on map entry:

@ExecSpace("ClientOnly")
method void OnBeginPlay()
    _DamageSkinService:PreloadAsync("3271c3e79bf04ecba9a107d55495970d", function(ok) end)
    _DamageSkinService:PreloadAsync("02c22d93421b4038b3c413b3e40b57ec", function(ok) end)
    _DamageSkinService:PreloadAsync("d58b67cf0f3a4eaf9fe1ad87c0ffac8a", function(ok) end)
end

(f) TweenType use cases

TweenTypeRecommended situation
DefaultNormal hits
VolcanoCritical / area hits (upward scatter)
BladeContinuous slashes / combos (overlapping numbers)
*MiniSmall damage like DoT (poison/burn) — less screen clutter

(g) Faction-specific skins

To use different skin RUIDs per side (player vs enemy, PvP factions, etc.), swap DamageSkinSettingComponent.DamageSkinId at runtime:

self.Entity.DamageSkinSettingComponent.DamageSkinId = MY_TEAM_SKIN_RUID

12. Hit effect — HitEffectSpawnerComponent

Attach to the defender and a hit effect plays automatically on HitEvent. No properties — just add the component to the .model.


13. Full combat checklist

  • Attacker model: an AttackComponent-derived script (+ optional: DamageSkinSettingComponent)
  • Defender model: HitComponent + HitEffectSpawnerComponent + (optional: DamageSkinSpawnerComponent + DamageSkinComponent)
  • HitComponent: IsLegacy=false, set ColliderType/BoxSize/CircleRadius, set CollisionGroup
  • State motions: register ATTACK/HIT/DEAD in StateComponent + AvatarStateAnimationComponent.StateToAvatarBodyActionSheet
  • HP handling: player uses PlayerComponent.Hp; monster uses custom @Sync Hp
  • Direction check: LookDirectionX (no Scale.x)
  • Time reference: _UtilLogic.ElapsedSeconds (no os.clock)
  • Event cleanup: explicit DisconnectEvent in OnEndPlay
  • Body rule: do not assign TransformComponent.Position directly on an entity with an active Body

14. Custom implementation is required

Buff/Debuff · BT Decorator/Memory(Blackboard) · Aggro/Threat Table · projectile pooling · pierce/max-hits · stagger-level system · resources (MP/Stamina/Rage) · combo/cancel windows · guard/parry · world→screen coordinate conversion · worldspace HP bar


Out of scope

  • General player topics (HP/movement/camera/costume aside): msw-defaultplayer
  • General mlua grammar/lifecycle: msw-scripting
  • .model authoring rules/templates: msw-general