MSW combat system integration guide. Covers the Attack→Hit pipeline, damage model, i-frame, knockback, Hit Stop, Camera Shake, Sprite Flash, SFX, death/revive,…
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
#
Layer
Native
Custom required
1
Attack Resolution
AttackComponent + HitComponent (Box/Circle/Polygon)
Capsule/Cone/Ray, pierce count
2
Damage Model
CalcDamage/CalcCritical/GetCriticalDamageRate/GetDisplayHitCount hooks + HitEvent.Extra:any
Element affinity, composite formulas
3
Hit Reaction
Per-Body knockback API, IsHitTarget-based i-frame
Stagger level, status effects
4
Game Feel
All 6 native (Hit Stop, Shake, Zoom, Flash, VFX, SFX)
—
5
Combat State
StateComponent + DeadEvent/ReviveEvent, PlayerComponent HP/revive
MP/Stamina/Rage, aggro
6
Event Bus
HitEvent/AttackEvent/StateChangeEvent/PlayerActionEvent + custom @Event
OnKill/OnBlocked
7
AI
StateComponent (FSM) + AIComponent (BT, 4 Composite types native) + AIChaseComponent/AIWanderComponent, _UserService.UserEntities
Decorator/Memory(Blackboard), Threat Table
+
Damage Skin
3 DamageSkin* components + DamageSkinService
—
+
Hit Effect
HitEffectSpawnerComponent (auto)
—
+
Avatar Motion
AvatarStateAnimationComponent (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.
File
Scope
When to read
../msw-general/references/monster.md
Monster .model component assembly + ActionSheet + AI choice + canonical Pattern A scripts (Soldier-style) + HP/Respawn + spawn + verification
When building a combat-capable monster
references/hp-gauge.md
Full implementation of an overhead HP bar based on PixelRendererComponent
When attaching an overhead HP bar
references/projectile.md
Projectile (Body-less entity + OnUpdate Translate) + homing/pierce/splash variants
When building ranged attacks like arrows, bullets, magic bolts
references/ai-bt.md
BehaviourTree — AIComponent + 4 Composite types + @BTNode + custom Decorator/Memory/Threat
When 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
Side
Override
Purpose
Attacker
AttackComponent:IsAttackTarget(defender, attackInfo) → boolean
Faction / distance / state
Defender
HitComponent:IsHitTarget(attackInfo) → boolean
Invincibility / 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
Form
Shape construction
Frontal melee Box
BoxShape(pos + LookDirectionX*offset, size, 0) — see DefaultPlayer PlayerAttack
Circular AoE
CircleShape(self.WorldPos, radius)
Projectile
Spawn 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
Target
Body?
Movement API
Rationale
Monster / NPC / AI
Yes (map-type Body + MovementComponent)
MovementComponent:MoveToDirection(dir, 0) + MovementComponent.InputSpeed
MovementComponent.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 / effect
No (Sprite+Transform+Trigger)
self.Entity.TransformComponent:Translate(speed*delta, 0) every frame
Direct Transform manipulation is safe without a Body. The pattern from the official "Create a Long-Range Projectile" tutorial.
Direct Rigidbody control (advanced)
Yes
body:AddForce(...) — sustained acceleration / impulse
RigidbodyComponent.d.mlua:71 — MoveVelocity 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 movement
6~10Hz teleport, no frame interpolation → jerky
body:SetPosition(...) / MovementComponent:SetPosition(...) inside OnUpdate
Both 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.
Component
Notes
MOD.Core.TransformComponent
Default
MOD.Core.SpriteRendererComponent
Renderer
Body (map type)
RigidbodyComponent(MapleTile) / KinematicbodyComponent(RectTile) / SideviewbodyComponent(SideViewRectTile)
MOD.Core.MovementComponent
E.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
Element
API
ExecSpace
Hit Stop (global)
_UtilLogic:SetClientTimeScale(float) — 0~100
ClientOnly
Hit Stop (individual)
renderer.PlayRate = 0 (Sprite/Skeleton/Avatar)
@Sync
Slow Motion
_UtilLogic:SetClientTimeScale(0.3) + timer to restore
ClientOnly
Camera Shake
cameraComp:ShakeCamera(intensity, duration, targetUserId?)
Client
Camera Zoom
cameraComp:SetZoomTo(percent, duration, targetUserId?) · requires IsAllowZoomInOut=true first
Client
Hit Flash
spriteRenderer.Color = Color(r,g,b,a) → timer to restore
@Sync
Color HDR overbright
Color.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 loop
PlayLoopSound / PlayLoopSoundAtPos
Client
SFX attached
SoundComponent:Play() · pitch randomization via Pitch 0~3
Client
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
Family
Name
Description
Explosion/impact
SparkExplosion
Sparks (one-shot) — general-purpose hit
SparkLoop
Continuous sparks
SparkRadialExplosion
Sparks scattering radially
SmallExplosion
Small explosion + smoke
BigExplosion
Big explosion + smoke
TinyExplosion
Very small explosion (Color option ignored)
DustExplosion
Circular shockwave + smoke (Color option ignored)
EnergyExplosion
Circular shockwave then center convergence
CircleBurst
Circular light burst
PillarBurst
Circular light burst + directional light
Fire/flame
FireField
Cartoon flames
FireFieldIntense
Intense cartoon flames
FireBall
Flame at a single point
FlameThrower
Flamethrower stream
LargeFlames
Large flames from the floor
MediumFlames
Medium flames from the floor
TinyFlames
Tiny flames from the floor
WildFire
Giant pillar of flame (Color option ignored)
Lightning/electric
LightningOrbSharp
Spherical electric particles
LightningStrikeSharp
Lightning bolt
LightningStrikeSharpTall
Tall lightning bolt
LightningOrbSoft
Electric wave emission
LightningBlast
Periodic electric waves
LightningStrike
Periodic lightning
LightningStrikeTall
Periodic tall lightning
Buff/magic
Aura
Aurora light from the floor
Buff
Strong light rising from the floor
Charge
Large particles converging on one point
ChargeOrb
Particles converging on one point
Enchant
Large light with light/particles around it
SpinField
Particles around a rotating circle
StarVortex
Starlight converging to the center
Nova
Wide circular wave
UpperCylinder
Rising pillar from the floor
Misc
Firework
Fireworks
FireworkCluster
Multiple fireworks at once
FireFlies
Fireflies
GoopSpray
Liquid spray to the side
GoopSprayEffect
Liquid spray downwards
DustStorm
Wide dust storm
RisingSteam
Rising white mist from the floor
BigSplash
Large water splash
Shower
Water poured on one spot
Full SpriteParticleType list (8)
Name
Description
BurstBig
Sprite emerges in a radial pattern
SpawnField
Particles + sprite emerge in a circular area
BurstNova
Particles + sprite burst in a circular pattern
SimpleSpawn
Simple particle + sprite appearance
Burst
Particles + sprite scatter
Stream
Generated while moving in a specific direction
StreamSharp
Thin line moving in a specific direction
AdditiveColor
Color effect applied to the sprite
Full AreaParticleType list (12)
Name
Description
Rain
Rain
Snow
Snow
FogCalm
Fog
FogHeavy
Heavy descending fog
FogLively
Rising fog
CalmStarField
Rising star cluster
StarFieldSimple
Twinkling star cluster
StarFog
Star + nebula particles (stationary)
StarFogFlow
Star + nebula particles (rising)
Windlines
Thin lines
WindlinesBig
Thin lines + thick lines
WindlinesSpeedy
Fast straight lines
Choosing between EffectService and ParticleService
Situation
Recommended
MapleStory skill / hit animations (specific imagery)
EffectService (specify RUID)
General hit/explosion (fast to implement)
ParticleService.BasicParticle
Scatter a custom image as particles
ParticleService.SpriteParticle
Environmental ambience like rain/snow/fog
ParticleService.AreaParticle
Sustained effects like buff auras
Either with isLoop=true
Rich, layered effects
Combine EffectService and ParticleService
Standard pattern for server event → client effect: @Sync property change → detected in OnSyncProperty(ClientOnly) → call EffectService/ParticleService.
5. Death / Revive
Event
Emission condition
Payload
DeadEvent
Auto on StateComponent:ChangeState("DEAD")
none
ReviveEvent
Auto on PlayerComponent:Respawn() (players only)
none
StateChangeEvent
Auto on every state transition
CurrentStateName, 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 event
MSW implementation
OnAttackStart
OnAttack hook or custom AttackStartEvent
OnAttackHit / OnDamageTaken
Native HitEvent
OnAttackMiss
Custom — SendEvent when IsAttackTarget returns false
OnCriticalHit
Covered by the HitEvent.IsCritical flag
OnDeath / OnRevive
Native DeadEvent/ReviveEvent
OnStateChange
Native StateChangeEvent
OnKill / OnBlocked / OnParry / OnStatusApplied
Custom @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
Pattern
Fit
Reference
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 IDLE ↔ DEAD 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
UI
API
HP bar (screen-fixed)
SliderComponent (MinValue/MaxValue/Value/FillRectColor/FillRectImageRUID/Direction/UseHandle) + SliderValueChangedEvent. ⚠ UI entities only
Damage numbers
3 DamageSkin* components + DamageSkinService — §11
Crosshair
SpriteGUIRendererComponent in .ui
Combo counter / buff icons
TextComponent + SpriteGUIRendererComponent
Worldspace HP bar (overhead): no native support. Two implementation options:
Option
Approach
Fit
Lightweight
Adjust LocalScale.x = hp/maxHp on a child entity's SpriteRendererComponent or use TiledSize.x (with SpriteDrawMode.Tiled)
Quick prototype, simple gauge
Full
Based on PixelRendererComponent — full implementation references/hp-gauge.md
Production-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
Item
Usage
HP decrement
self.Entity.PlayerComponent.Hp -= event.TotalDamage
Death check
PlayerComponent:IsDead()
Revive
PlayerComponent: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 override
ActionAttack / ActionJump / ActionInteraction(key, isKeyDown) etc.
Action event reception
EmitPlayerActionEvent(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:
File
Role
Key points
PlayerAttack.mlua
Frontal Box attack
LookDirectionX for direction, AttackFast + CollisionGroups.Monster, CalcDamage=50, 30% crit
PlayerHit.mlua
i-frame
ImmuneCooldown property, _UtilLogic.ElapsedSeconds deadline, IsHitTarget override
Monster.mlua
Monster HP
Custom @Sync Hp (no PlayerComponent), HandleHitEvent → Dead/Respawn
MonsterAttack.mlua
Sprite-size-based melee
isvalid(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
Constant
Purpose
CollisionGroups.Player
Monster → Player attack
CollisionGroups.Monster
Player → Monster attack
CollisionGroups.HitBox
Default 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
Purpose
RUID
Used on
Hit
3271c3e79bf04ecba9a107d55495970d
Default for attacker's DamageSkinSettingComponent.DamageSkinId
Taken hit
02c22d93421b4038b3c413b3e40b57ec
Defender-side display — call _DamageSkinService:Play manually
Heal
d58b67cf0f3a4eaf9fe1ad87c0ffac8a
Heal/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:
Side
Component
Role
Attacker
DamageSkinSettingComponent
Which skin/style to display
Defender
DamageSkinSpawnerComponent
Display position offset
Defender
DamageSkinComponent
Damage number body (over the entity)
Include all 3 in the .model and damage numbers appear with zero script code.
DamageSkinSettingComponent (attacker)
Property
Type
Default
Description
DamageSkinId
DataRef
hit RUID (table above)
Damage number skin RUID
DamageSkinScale
Vector2
(1, 1)
Number size
Alpha
float
1
Opacity
PlayRate
float
1
Playback speed
DelayPerAttack
float
0.05
Delay between multi-hits (seconds)
TweenType
DamageSkinTweenType
Default
Animation style
LitMode
LitMode
Default
Lighting influence
DamageSkinTweenType: Default (popup) / Volcano (fan) / Blade (overlap) / each *Mini (75% scale)
DamageSkinSpawnerComponent (defender)
Property
Type
Default
DamageSkinOffset
Vector2
(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
TweenType
Recommended situation
Default
Normal hits
Volcano
Critical / area hits (upward scatter)
Blade
Continuous slashes / combos (overlapping numbers)
*Mini
Small 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-generaldon't have the plugin yet? install it then click "run inline in claude" again.