--- name: msw-defaultplayer description: "MSW DefaultPlayer (character) management. Use the msw-general ModelBuilder to inspect/patch DefaultPlayer.model and Player.model, add/remove components, configure movement speed / jump force / HP / camera, and per-map-mode movement components. Use for DefaultPlayer model, player components, movement speed, jump force, HP, camera, physics. Keywords: player, DefaultPlayer, speed, jump, HP, camera, gravity, revive, respawn, character." --- # MSW DefaultPlayer Use the `msw-general` ModelBuilder to inspect/patch the DefaultPlayer model file, manage components, and configure movement / physics / HP / camera. > For **costume / avatar equipment**, see the `msw-avatar` skill. Costumes apply not only to DefaultPlayer but to any entity, so they live in a separate skill. --- ## DefaultPlayer overview ### What is DefaultPlayer? The **player character model** provided by default in the MapleStory Worlds Maker workspace. - When any user enters a world, a player entity is created based on this model. - The model ID to use is specified by the `PlayerUri` property of `DefaultUserEnterLeaveLogic`. ### File location and structure DefaultPlayer is made up of **two .model files**: | File | Path | Role | |------|------|------| | **Player.model** | `./Global/Player.model` | Base model. Defines the Components list and Properties links | | **DefaultPlayer.model** | `./Global/DefaultPlayer.model` | Inherits Player (`BaseModelId: "player"`). Overrides Values | > **Important**: both files are located in `./Global/`. Custom script files are created under `./RootDesk/MyDesk/`. ### DefaultPlayer is patched through ModelBuilder DefaultPlayer is managed through the sibling `msw-general/scripts/model/msw_model_builder.cjs`, not raw JSON edits. - Change a property value: `ModelBuilder.read("./Global/DefaultPlayer.model").value(...)` - Add/remove a component: `component()` / `removeComponent()` - Check the base component list: `ModelBuilder.snapshot("./Global/Player.model")` --- ## File structure detail ### Player.model (base) ``` Path: ./Global/Player.model EntryKey: model://player ``` - `Components`: the full list of default components on the player (MOD.Core.* native components) - `Properties`: model property → component property link definitions (properties editable from the inspector) - `Values`: empty (defaults are provided by the engine) ### DefaultPlayer.model (override) ``` Path: ./Global/DefaultPlayer.model EntryKey: model://defaultplayer BaseModelId: "player" ``` - `Components`: only the components **added** on DefaultPlayer (e.g. `script.PlayerHit`, `script.PlayerAttack`) - `Values`: the array of overridden setting values --- ## DefaultPlayer default component list Native components inherited from Player.model: | Component | Role | |-----------|------| | **TransformComponent** | Position, rotation, scale | | **MovementComponent** | Movement speed and jump force control | | **RigidbodyComponent** | Physics (gravity, footholds), MapleStory-style movement | | **KinematicbodyComponent** | Up/down/left/right movement on a RectTileMap | | **SideviewbodyComponent** | Side-scrolling movement on a SideViewRectTileMap | | **StateComponent** | State machine (Walk, Jump, Dead, etc.) | | **AvatarRendererComponent** | Avatar rendering, color, emotion | | **AvatarStateAnimationComponent** | State → avatar animation mapping | | **CostumeManagerComponent** | Equipment / costume management → **see the `msw-avatar` skill for details** | | **CameraComponent** | Camera tracking settings | | **PlayerControllerComponent** | Input-to-action mapping, condition handling | | **PlayerComponent** | HP, death/revive, PVP, map travel | | **ChatBalloonComponent** | Chat balloon | | **NameTagComponent** | Name tag | | **DamageSkinSettingComponent** | Damage skin | | **DamageSkinSpawnerComponent** | Damage skin spawn | | **HitEffectSpawnerComponent** | Hit effect spawn | | **TriggerComponent** | Collision detection | | **InventoryComponent** | Inventory | Script components added in DefaultPlayer.model: | Component | Role | |-----------|------| | **script.PlayerHit** | Player hit-handling logic | | **script.PlayerAttack** | Player attack logic | --- ## Quick reference — key components | Component | Role | Key properties / methods | |-----------|------|--------------------------| | **PlayerComponent** | HP, death/revive, PVP, map travel | `Hp`, `MaxHp`, `PVPMode`, `RespawnDuration`, `RespawnPosition`, `UserId`, `IsDead()`, `ProcessDead()`, `ProcessRevive()`, `MoveToMapPosition()` | | **PlayerControllerComponent** | Input → action mapping, conditional control | `SetActionKey(key, actionName)`, `ActionAttack()`, `ActionJump()`, `LookDirectionX` | | **MovementComponent** | High-level movement speed / jump interface | `InputSpeed` (default 1.0), `JumpForce` (default 1), `Jump()`, `Stop()` | | **RigidbodyComponent** | Maple side-view physics (gravity / footholds) | `Gravity`, `WalkAcceleration`, `WalkSpeed`, `AddForce()`, `IsOnGround()` | | **KinematicbodyComponent** | Top-view up/down/left/right movement on RectTile | (RectTile map-mode only) | | **SideviewbodyComponent** | Side-view on SideViewRectTile | (SideViewRectTile map-mode only) | | **AvatarRendererComponent** | Avatar rendering / color / emotion | `SetColor()`, `SetAlpha()`, `PlayEmotion()`, `PlayRate` | | **StateComponent** | State machine (Walk/Jump/Dead) | `CurrentStateName`, `ChangeState()`, DeadEvent/ReviveEvent | | **NameTagComponent** | Name tag | `Name`, `FontSize`, `FontColor`, `NameTagRUID` | | **ChatBalloonComponent** | Chat balloon | `Message`, `ChatModeEnabled`, `ShowDuration` | | **CameraComponent** | Camera tracking | `DeadZone`, `SoftZone`, `Damping`, `ScreenOffset` | | **TriggerComponent** | Collision detection | `BoxSize`, `Offset`, CollisionGroup | --- ## DefaultPlayer Values structure Format of each entry in the `Values` array of `DefaultPlayer.model`: ```json { "TargetType": " or null", "Name": "", "ValueType": { "$type": "MODNativeType", "type": "" }, "Value": } ``` ### TargetType rules - `null`: a model property defined in Player.model's Properties (linked through Properties to the actual component property) - `"MOD.Core."`: directly override a property on a specific native component - `"script."`: a property of a custom script component ### Model properties (TargetType: null) Mapped to actual component properties through the links defined in Player.model's Properties. | Model property name | Source component.property | Description | Default | |---------------------|---------------------------|-------------|---------| | speed | MovementComponent.InputSpeed | Movement speed | 1.0 | | jumpForce | MovementComponent.JumpForce | Jump height | 1.0 | | walkAcceleration | RigidbodyComponent.WalkAcceleration | Acceleration / deceleration | 1.0 | | gravity | RigidbodyComponent.Gravity | Gravity | 1.0 | | cameraDeadZone | CameraComponent.DeadZone | Camera dead zone | `{x: 0.052, y: 0.08}` | | cameraSoftZone | CameraComponent.SoftZone | Camera soft zone | `{x: 0.268, y: 0.7}` | | cameraDamping | CameraComponent.Damping | Camera smooth-follow | `{x: 2.5, y: 3.9}` | | cameraScreen | CameraComponent.ScreenOffset | Dead-zone center point | `{x: 0.5, y: 0.655}` | | cameraDutch | CameraComponent.DutchAngle | Camera rotation | 0.0 | | cameraOffset | CameraComponent.CameraOffset | Camera position offset | `{x: 0.0, y: 0.0}` | | message | ChatBalloonComponent.Message | Chat balloon message | `""` | | chatModeEnabled | ChatBalloonComponent.ChatModeEnabled | Whether chat is processed (e.g. balloon shown) | `true` | | nameTag | NameTagComponent.Name | Name tag | `""` | | damageSkinId | DamageSkinSettingComponent.DamageSkinId | Damage skin type | DataRef | | damageDelayPerAttack | DamageSkinSettingComponent.DelayPerAttack | Damage delay | 0.05 | | triggerBodyBoxSize | TriggerComponent.BoxSize | Collision detection area size | `{x: 0.66, y: 0.7}` | | triggerBodyBoxOffset | TriggerComponent.BoxOffset | Collision detection area offset | `{x: 0.0, y: 0.35}` | | triggerBodyColliderOffset | TriggerComponent.ColliderOffset | Collider offset | `{x: 0.0, y: 0.35}` | | maxHp | PlayerComponent.MaxHp | Max HP | 1000 | ### Direct component override (TargetType: specific component) Values that directly override a component property rather than going through a model-property link: | TargetType | Name | Description | Default | |------------|------|-------------|---------| | MOD.Core.CameraComponent | ZoomRatioMax | Camera max zoom ratio | 500.0 | | MOD.Core.MovementComponent | JumpForce | Jump force (direct override) | 1.0 | | MOD.Core.MovementComponent | InputSpeed | Movement speed (direct override) | 1.0 | | script.PlayerHit | CollisionGroup | Hit collision group | CollisionGroup ID | | script.PlayerHit | BoxSize | Hit collision area size | `{x: 0.45, y: 0.7}` | | script.PlayerHit | ColliderOffset | Hit collision offset | `{x: 0.0, y: 0.35}` | --- ## Movement components per map mode > See [`msw-general/references/platform.md`](../msw-general/references/platform.md) §4 for the TileMapMode↔Body mapping table. Depending on the map mode, one of RigidbodyComponent / KinematicbodyComponent / SideviewbodyComponent is active. --- ## Identifying a player (for script reference) - `entity.PlayerComponent ~= nil` → whether the entity is a player - `_UserService.LocalPlayer` → my player entity (client-only) - `_UserService:GetUserEntityByUserId(userId)` → player entity for a specific user --- ## Player entity runtime structure (root vs children) A spawned player is **not** a single flat entity. At runtime the engine builds a small hierarchy under the player root, and **avatar action selectors live on grandchild entities** — not on the root. This affects how you look up components and how you trigger avatar poses. ### Component → entity mapping | Component | Where it lives | Lookup | |---|---|---| | `PlayerComponent` | Root | `player:GetComponent("PlayerComponent")` (or `player.PlayerComponent`) | | `StateComponent` | Root | `player:GetComponent("StateComponent")` | | `MovementComponent` | Root | `player:GetComponent("MovementComponent")` | | `AvatarRendererComponent` | Root | `player.AvatarRendererComponent` | | `AvatarStateAnimationComponent` | Root | `player:GetComponent("AvatarStateAnimationComponent")` | | `PlayerControllerComponent` | Root | `player.PlayerControllerComponent` | | **`AvatarBodyActionSelectorComponent`** | **Body grandchild** of root (under the avatar root) | not reachable via `player:GetComponent(...)` — see below | | **`AvatarFaceActionSelectorComponent`** | **Face grandchild** of root | not reachable via `player:GetComponent(...)` — see below | `player:GetComponent("AvatarBodyActionSelectorComponent")` returns `nil` and the LSP does not warn (the signature is `Component`, not nilable). The failure is only visible at runtime. ### How to reach the selectors ```lua -- ClientOnly context (recommended — the direct API) local body = self.Entity.AvatarRendererComponent:GetBodyEntity() local face = self.Entity.AvatarRendererComponent:GetFaceEntity() local bodySelector = body and body:GetComponent("AvatarBodyActionSelectorComponent") local faceSelector = face and face:GetComponent("AvatarFaceActionSelectorComponent") -- Server or "either side" context (GetBodyEntity / GetFaceEntity are ClientOnly) local bodySelector = self.Entity:GetFirstChildComponentByTypeName( "AvatarBodyActionSelectorComponent", true) ``` > `AvatarRendererComponent:GetBodyEntity()` and `GetFaceEntity()` are declared `@ExecSpace("ClientOnly")` — calling them from a server-side method returns `nil`. Use `GetFirstChildComponentByTypeName(name, recursive=true)` as the cross-side fallback. --- ## Triggering avatar poses — use `StateComponent`, never write the selector directly A live player has a state machine running every tick: `PlayerControllerComponent` evaluates input/movement → `StateComponent` transitions (IDLE / MOVE / etc.) → `AvatarStateAnimationComponent.ReceiveStateChangeEvent` translates that into `BodyActionStateChangeEvent` → the selector's `ActionState` is rewritten. This means **writing `AvatarBodyActionSelectorComponent.ActionState` directly is a silent overwrite**: the value applies for one frame, then the next state-machine tick maps the current `StateComponent` state back onto the selector and your write is gone. Logs print `ActionState=Attack` immediately after the assignment, but in play mode the pose flickers for one frame and disappears. ### Correct entry point ```lua local state = self.Entity:GetComponent("StateComponent") state:ChangeState("ATTACK") -- ActionSheet keys are UPPERCASE ``` State keys come from the player's ActionSheet. The DefaultPlayer ships with **11 UPPERCASE keys**, each mapped to a default animation: | `StateComponent:ChangeState(...)` key | Default animation | |---|---| | `"IDLE"` | `stand` | | `"MOVE"` | `walk` | | `"ATTACK"` | `attack` | | `"HIT"` | `hit` | | `"CROUCH"` | `crouch` | | `"FALL"` | `fall` | | `"JUMP"` | `fall` | | `"CLIMB"` | `rope` | | `"LADDER"` | `ladder` | | `"DEAD"` | `dead` | | `"SIT"` | `sit` | The state machine auto-returns to `IDLE` once the action finishes — no manual restore timer needed. > **Casing pitfall**: the **string key** passed to `ChangeState` is **UPPERCASE** (`"ATTACK"`). The **enum value** written to `selector.ActionState` directly (NPC path — see below) is `MapleAvatarBodyActionState.Attack` — **PascalCase**, separate API surface, same underlying state. Wrong casing on the string side (`"attack"` / `"Attack"`) misses the ActionSheet mapping silently — no warning. ### When *can* you write the selector directly? Only on entities **without** a running `PlayerControllerComponent` + `StateComponent` + `AvatarStateAnimationComponent` stack — e.g. NPCs / monsters that use the avatar renderer for visuals but have no input controller driving a state machine. On those, `selector.ActionState = MapleAvatarBodyActionState.` sticks. On DefaultPlayer-shaped entities, route through `StateComponent:ChangeState(...)` instead. ### Key services at a glance (for script reference) | Service | Role | Key API | |---------|------|---------| | **_UserService** | User management, enter/leave | `LocalPlayer`, `UserEntities`, `GetUserEntityByUserId()`, UserEnterEvent/UserLeaveEvent | | **_TeleportService** | Teleport / map travel | `TeleportToEntity()`, `TeleportToMapPosition()`, `WarpUserToWorldAsync()` | | **_CameraService** | Camera control | `SwitchCameraTo()`, `ZoomTo()`, `ZoomReset()` | | **DefaultUserEnterLeaveLogic** | User enter/leave logic | `PlayerUri` (player model ID), `StartPoint` (starting map) | --- ## How to modify DefaultPlayer ### Changing property values (Values) Load `./Global/DefaultPlayer.model` with `ModelBuilder.read()`, then update values with `value()`. **Example: set movement speed to 2.0** ```javascript const { ModelBuilder } = require("../msw-general/scripts/model/msw_model_builder.cjs"); const b = ModelBuilder.read("./Global/DefaultPlayer.model"); b.value(null, "speed", 2.0, "float") .value("MovementComponent", "InputSpeed", 2.0, "float") .write("./Global/DefaultPlayer.model"); ``` > **Note**: both the model property (`TargetType: null`, `Name: "speed"`) and the direct component override (`TargetType: "MOD.Core.MovementComponent"`, `Name: "InputSpeed"`) can exist. Set both consistently through `value()`. **Example: jump force 1.5 + HP 2000** ```javascript const b = ModelBuilder.read("./Global/DefaultPlayer.model"); b.value(null, "jumpForce", 1.5, "float") .value("MovementComponent", "JumpForce", 1.5, "float") .value(null, "maxHp", 2000, "int") .write("./Global/DefaultPlayer.model"); ``` ### Adding a new Values entry Use `ModelBuilder.value(targetType, name, value, typeKey)`. The builder generates the `ValueType` descriptor; do not hand-write type strings. Common `typeKey` values: `bool`, `int`, `float`, `double`, `string`, `vector2`, `vector3`, `data_ref`, `collision_group`. ### Adding a component Use `component()` on `./Global/DefaultPlayer.model`. Only add components that are not inherited from `Player.model`. For inherited native components (`PlayerComponent`, `MovementComponent`, `CameraComponent`, `StateComponent`, etc.), override values with `value(...)` instead of redeclaring the component. `ModelBuilder` warns (`M040`) when a component already inherited from `BaseModelId: "player"` is added locally. **Adding a custom script component**: ```javascript const b = ModelBuilder.read("./Global/DefaultPlayer.model"); b.component("script.MyCustomComponent") .write("./Global/DefaultPlayer.model"); ``` > Custom scripts (.mlua) must be created under `./RootDesk/MyDesk/`. Write the script first, Maker `refresh`, then add `"script."` with the builder. **Adding a non-inherited native component**: ```javascript const b = ModelBuilder.read("./Global/DefaultPlayer.model"); b.component("SpriteRendererComponent") .write("./Global/DefaultPlayer.model"); ``` ### Removing a component Use `removeComponent()` on `DefaultPlayer.model`. Related `Values` entries are removed by the builder. > **Caution**: components inherited from Player.model (the base) are not in DefaultPlayer.model's Components. Removing base components requires patching `Player.model` through `ModelBuilder`, and is generally not recommended. --- ## Pitfalls (Common Pitfalls) Common pitfalls when **adding to Components** and **changing Values** in DefaultPlayer.model. ### Component list pitfalls | # | Pitfall | Symptom | Resolution | |---|---------|---------|------------| | C1 | A `script.XXX` you added disappears or doesn't apply | A script component that isn't registered in the matching `.codeblock` metadata in the same directory is silently dropped at load time; if saved in that state, it's lost permanently | After writing the `.mlua`, use **Maker Refresh** so the `.codeblock` is auto-generated. Or attach at runtime right after spawn via `entity:AddComponent("Name")` | | C2 | Duplicate-adding a native component already on Player.model (e.g. `MOD.Core.MovementComponent`) | Only a duplicate-component warning is emitted, not blocked → workspace warnings accumulate, behavior becomes non-deterministic | Check the base component list (§65-89) before adding. If it's already there, just change settings via Values | | C3 | Removing `script.PlayerHit` / `script.PlayerAttack` | These are the only extra scripts DefaultPlayer ships with. Removing them eliminates hit immunity / attack logic | If the goal is to disable, toggle the logic inside the script, or use Enable=false in Values | | C4 | Disabling `AvatarRendererComponent` and adding `SpriteRendererComponent` (or other renderer swap) | If Avatar and Sprite renderers are active at the same time, you get z-fighting / costumes not applied | Only add Sprite after disabling Avatar (see the pattern at §395) | | C5 | Custom damage path only decrements an HP property (or only broadcasts a damage-skin event) — the engine hit/death pipeline never fires | The avatar state machine reacts to `StateChangeEvent`, not to property writes. Damage numbers / particles render fine but the avatar stays idle through hit / dead / revive — silent visual miss with no error log | **First choice**: route damage through `HitComponent:OnHit(attacker, damage, isCrit, attackInfo, hitCount)`. `script.PlayerHit` then handles the hit / death / respawn transitions automatically. **When you must drive damage manually** (channeled / aura / scripted): pair `StateComponent:ChangeState("HIT")` for the hit pose with `PlayerComponent:ProcessDead()` / `ProcessRevive()` for the death and revive cycle. Don't only edit HP — the avatar won't react. | | C6 | Setting `SpriteRendererComponent.Color` / `FlipX` on an entity where `AvatarRendererComponent` is active (e.g. DefaultPlayer) | The component is present and `GetComponent` returns non-nil, but the sprite output is hidden behind the avatar renderer's own pipeline — color/flip writes are silently no-op. The same pattern works on plain sprite-rendered entities, so first-try copy-paste fails without any warning | Use `AvatarRendererComponent:SetColor(r, g, b, a)` (0–1 floats) for tint and `:SetAlpha(a)` for fade. Both are `ClientOnly`. For facing, drive the character through `MovementComponent.MoveDirection` (or the facing API of your game logic) instead of `sprite.FlipX` | ### Values change pitfall — only `jumpForce` / `speed` need extra care Most Values entries are in the `TargetType=null` (alias) form and can be set through `ModelBuilder.value(null, ...)`. **Only the two fields below are exceptions** — both an alias and a native entry (`TargetType="MOD.Core.MovementComponent"`) exist at the same time: | Field | alias entry | native entry | |-------|-------------|--------------| | Jump force | `jumpForce` (`TargetType=null`) | `JumpForce` (`TargetType="MOD.Core.MovementComponent"`) | | Movement speed | `speed` (`TargetType=null`) | `InputSpeed` (`TargetType="MOD.Core.MovementComponent"`) | On entity spawn, Values are applied in array order and both write to the same native field; **the native entry appears later in the array, so the native value wins**. - Wrong: editing only the alias side (`jumpForce` / `speed`) → overwritten by the later native entry and ignored - Right: **edit both consistently to the same value**, or edit only the native side (`JumpForce` / `InputSpeed`) Other alias-only entries (`walkAcceleration`, `gravity`, camera-related except `cameraDeadZone`, `nameTag`, `damageSkinId`, `damageDelayPerAttack`, `triggerBody*`, `maxHp`, etc.) can be modified through the alias as-is. --- ## Hiding DefaultPlayer DefaultPlayer's components are inherited from the base model, so they **cannot be deleted**. Disabling them via `Enable=false` is the only option. ### Component keep/disable classification | Component | Fully hide | Hide avatar only | Notes | |-----------|:----------:|:----------------:|-------| | TransformComponent | **keep** | **keep** | Required | | PlayerComponent | **keep** | **keep** | Required — disabling causes enter failures | | StateComponent | **keep** | **keep** | Disabling causes other components to error | | MovementComponent | **keep** | **keep** | Keep when movement is needed | | CameraComponent | **keep** | **keep** | Keep when camera is needed | | AvatarRendererComponent | **disable** | **disable** | Key — disabling this alone hides it | | AvatarStateAnimationComponent | **disable** | **disable** | | | CostumeManagerComponent | **disable** | **disable** | | | PlayerControllerComponent | **disable** | keep | Depends on whether movement should be blocked | | ChatBalloonComponent | **disable** | **disable** | | | NameTagComponent | **disable** | **disable** | | | DamageSkinSettingComponent | **disable** | **disable** | | | DamageSkinSpawnerComponent | **disable** | **disable** | | | HitComponent | **disable** | **disable** | | | HitEffectSpawnerComponent | **disable** | **disable** | | | TriggerComponent | **disable** | **disable** | | | InventoryComponent | **disable** | **disable** | | | RigidbodyComponent | **disable** | keep per map mode | When on a MapleTile map | | SideviewbodyComponent | **disable** | keep per map mode | When on a SideViewRectTile map | | KinematicbodyComponent | EnableShadow=false | EnableShadow=false | Removes the shadow only | ### Builder edit — add Enable=false to Values Set the component values through `ModelBuilder.value()`. For components that don't yet have an Enable entry, the builder adds one. ```javascript const { ModelBuilder } = require("../msw-general/scripts/model/msw_model_builder.cjs"); const b = ModelBuilder.read("./Global/DefaultPlayer.model"); b.enable("AvatarRendererComponent", false) .value("KinematicbodyComponent", "EnableShadow", false, "bool") .write("./Global/DefaultPlayer.model"); ``` After saving, **Maker Refresh** is required. --- ## DefaultPlayer component extension patterns - **Non-avatar player**: disable AvatarRendererComponent → add SpriteRendererComponent → set SpriteRUID. - **Collision setup**: tweak ColliderType and CollisionGroup in TriggerComponent's Values. - **SpawnLocation**: place a Special → SpawnLocation in the map (.map) file (revive position). --- ## Workflows ### Modifying basic player properties (movement speed, jump force, HP, etc.) ``` 1. Load ./Global/DefaultPlayer.model with ModelBuilder.read() 2. Update values with value(targetType, name, value, typeKey) 3. If both the model property (TargetType: null) and the direct component override exist, set both consistently 4. write("./Global/DefaultPlayer.model") ``` ### Adding a custom script to the player ``` 1. Write a new .mlua script under ./RootDesk/MyDesk/ (see the msw-scripting skill) 2. Maker refresh so the script type is registered 3. Load ./Global/DefaultPlayer.model with ModelBuilder.read() 4. Add "script." with component() 5. If needed, add default property values for the script with value() 6. write("./Global/DefaultPlayer.model") 7. Request Maker Refresh ``` ### Changing camera settings ``` 1. Load ./Global/DefaultPlayer.model with ModelBuilder.read() 2. Set cameraDeadZone, cameraSoftZone, cameraDamping, cameraScreen, cameraDutch, cameraOffset with value() 3. write("./Global/DefaultPlayer.model") ``` --- ## Boundaries and caveats ### In scope - Inspect/patch the DefaultPlayer/Player model files through ModelBuilder - Add/remove components through `component()` / `removeComponent()` - Movement / physics / HP / camera settings through `value()` ### Out of scope - Costume / avatar equipment → `msw-avatar` skill - UI editor → .ui files under `./ui/` (dedicated skill) - Map editing → .map files under `./map/` (dedicated skill, including NPC/monster spawn) - General scripts / resources → each dedicated skill ### Constraints 1. **Careful with Global/**: DefaultPlayer.model and Player.model live in `./Global/`. This folder is reserved for engine default templates, so creating new files here is not recommended. 2. **Custom script location**: new script files must be created under `./RootDesk/MyDesk/`. 3. **Map mode caveat**: the active movement component differs depending on the map mode (MapleTile/RectTile/SideViewRectTile). 4. **ValueType correctness**: when adding a Values entry, use `ModelBuilder.value()` with an explicit `typeKey`; do not hand-write `ValueType`. 5. **Maker Refresh**: after adding/modifying scripts, Maker Refresh is required (.codeblock is auto-generated).