NPC scripting: Difference between revisions
Document new NPC/player-mode API and events available from 0.1.5 |
Release G1R:MP 0.1.5 BUILD144 / Protocol 37; update availability and migration |
||
| (One intermediate revision by the same user not shown) | |||
| Line 1: | Line 1: | ||
= NPC scripting = | = NPC scripting = | ||
'''Available from update 0.1.5.''' | '''Available from update 0.1.5.''' Included in the public 0.1.5 release (Protocol 37). See [[Update 0.1.5]] for the complete function/event index. | ||
== Ownership, units and defaults == | == Ownership, units and defaults == | ||
| Line 303: | Line 303: | ||
[[Category:NPC Functions]] | [[Category:NPC Functions]] | ||
[[Category:Lua Examples]] | [[Category:Lua Examples]] | ||
<!-- npc-appearance015:start --> | |||
== NPC appearance since 0.1.5 == | |||
The following are server-side functions for human NPCs created by [[spawnNpc]]. Only the owning resource can change them. They use the existing remote-character appearance pipeline, not a second body mesh or player inventory. Authoritative values are replayed on relevance entry and late join. | |||
* [[setNpcIdentity]] / [[getNpcIdentity]] choose/read the matched identity profile and head. [[getNpcIdentityNames]] lists the same named identities as the player API. Use "default" to explicitly return to the Nameless Hero identity. Armor and independent color/tattoo overrides are preserved. | |||
* [[setNpcSkinColor]] / [[getNpcSkinColor]] / [[resetNpcSkinColor]] control independent skin tint. RGB channels are integer sRGB bytes, 0..255. The getter returns {r,g,b}, or false when no override is enabled or the NPC is unavailable. | |||
* [[setNpcTattoo]] / [[getNpcTattoo]] / [[resetNpcTattoo]] control the tattoo override. [[getNpcTattooNames]] returns the fixed catalog from [[Player tattoo catalog]]. "none" explicitly removes tattoos; reset restores inherited materials. These are different operations. | |||
Existing [[setNpcSkin]] remains a legacy surface preset, not an alias for setNpcIdentity. NPCs that have never called setNpcIdentity keep the old skin behaviour. After an explicit identity is selected, setNpcSkin does not replace that identity. [[getNpcData]] adds identity and identityExplicit to distinguish these states. getNpcIdentity returns "default" before the first explicit identity selection; this does not describe any legacy skin fallback. | |||
[[setNpcArmor]] changes the independent garment; [[setNpcWeapon]] changes the equipped weapon. Neither new cosmetic setters nor their resets change authoritative HP, equipment, patrol or combat configuration. Appearance rebuilding is asynchronous: true confirms acceptance, not that the render has already finished. Tattoos are visible only on exposed body regions supported by the chosen preset; no new geometry, outfit replacement or arbitrary textures are loaded from scripts. | |||
<syntaxhighlight lang="lua"> | |||
local npc = spawnNpc("Guide", "default", x, y, z, 0) | |||
if npc then | |||
setNpcIdentity(npc, "milten") | |||
setNpcArmor(npc, "water_robe") | |||
setNpcSkinColor(npc, 90, 60, 40) | |||
setNpcTattoo(npc, "templar1") | |||
-- Later, independently: | |||
setNpcIdentity(npc, "default") -- keeps armor, tint and tattoo | |||
resetNpcSkinColor(npc) | |||
resetNpcTattoo(npc) | |||
end | |||
</syntaxhighlight> | |||
State lasts for this NPC's lifetime. For persistence across a server restart, store the keys/RGB in your resource database and reapply after spawning a new NPC. No client event is automatically trusted or exposed by these APIs. | |||
<!-- npc-appearance015:end --> | |||
Latest revision as of 07:33, 26 September 2026
NPC scripting
Available from update 0.1.5. Included in the public 0.1.5 release (Protocol 37). See Update 0.1.5 for the complete function/event index.
Ownership, units and defaults
These are server-side resource APIs. Use spawnNpc to create a human NPC. Mutations require its owning resource; never authorize an incoming client event merely because it contains a valid NPC ID. Validate getEventClient, permissions and gameplay conditions in any remote handler. NPC IDs and player IDs are not interchangeable. Invalid typed values/settings normally return false; wrong Lua argument types can raise binding errors and should be avoided.
Positions/ranges use metres, speeds metres per second, yaw/FOV degrees, animation blending seconds, projectile timing milliseconds. IDs remain opaque identifiers.
Defaults: health/maxHealth=100, invulnerable=true, weapon mode=none, combatTarget=0, melee damage=10; mana=0/maxMana=100, arrows=0, bolts=0. Merely spawning, equipping or setting HP does not start combat. No automatic retaliation, faction hostility or resurrection is installed.
Equipment and animation
- setNpcWeapon selects an item; setNpcWeaponMode draws it. Modes: none, fist, melee, bow, crossbow, magic. getNpcWeaponMode reads the mode. Changing to a different item holsters; repeating the same item does not.
- startNpcAnim uses Player animation catalog aliases (not raw asset paths); stopNpcAnim stops them. Weapons must be holstered. Drawing cancels scripted animation. Animations suspend routes/goals/waits rather than discarding them; one-shots finish automatically, loops need a stop.
- setNpcRotation sets yaw once. setNpcLookAt turns the whole NPC toward the player's current position once. Neither implements head-only tracking; movement may later update yaw.
- getPlayerWeaponMode is a separate player observation API: none/fist/melee/bow/crossbow/magic or false if unavailable. onPlayerWeaponModeChange reports changes after the initial baseline, not inventory slot changes.
Health and optional melee combat
Use setNpcMaxHealth, setNpcHealth, getNpcHealth, getNpcMaxHealth and setNpcInvulnerable. Increasing maximum HP does not heal. HP=0 causes death; positive HP revives without restoring old combat targeting. Invulnerability blocks damage but not authorized script setters.
setNpcAttackDamage sets base melee-only damage. Zero is explicitly non-damaging. setNpcCombatTarget selects a live player for continuous combat; pass false to stop. Draw a supported mode first. No automatic aggression is enabled. npcAttack is for one ranged/magic attempt, not melee.
-- Run in the resource owning the NPC; playerId/x/y/z must be validated.
local npc = spawnNpc("Guard", "milten", x, y, z, 0)
if npc then
setNpcMaxHealth(npc, 250)
setNpcHealth(npc, 250)
setNpcInvulnerable(npc, false)
setNpcWeapon(npc, "1h_sword_short_02")
setNpcWeaponMode(npc, "melee")
setNpcAttackDamage(npc, 10)
setNpcCombatMovement(npc, {mode="run", chase=true})
setNpcCombatTarget(npc, playerId)
end
-- Later: setNpcCombatTarget(npc, false)
Combat temporarily suspends script routes/goals; they can resume after stopping. Target loss, death, pause, weapon changes and damage reactions cancel pending preparation. Already released projectiles can still hit. NPC armor protection follows the verified server garment catalog; unknown bonuses are not guessed. Incoming player hits are validated server-side. onNpcDamage, onNpcDeath and onNpcAttack report committed results; do not apply the same damage again in handlers.
Combat movement
setNpcCombatMovement accepts a partial table; unknown fields/types or an invalid combination reject the entire change. getNpcCombatMovement returns {chase, mode, speed}.
| Field | Values | Default |
|---|---|---|
| chase | boolean; false holds position while permitting in-range attacks | true |
| mode | walk or run | walk |
| speed | walk: 0.1..2.5 m/s; run: 2.6..8 m/s | 1.8 |
Specifying mode without speed chooses walk=1.8 or run=4.5. Changing chase alone preserves mode/speed. This configuration does not start combat, replace a route speed or replay equipment/aim preparation. Use setNpcPaused for a full pause.
setNpcCombatMovement(npc, {mode="run", speed=4.5, chase=true})
setNpcCombatMovement(npc, {chase=false}) -- hold, but may attack in range
setNpcCombatMovement(npc, {chase=true}) -- resume pursuit
Pursuit still follows server navigation and dynamic capsule checks. Combat targeting is abandoned beyond 50 metres horizontally or 5 metres vertically; this is distinct from projectile attack range and the configurable perception query. A blocked route is not bypassed with a teleport.
Spells and projectiles
Supported spell keys: fire_bolt, fire_ball, fire_ball_milten, ice_bolt, ice_block, ball_lightning, fist_of_wind. Fire Ball, Milten Fire Ball and Ball Lightning support levels 1..3; the others accept level 1. Catalog-only spells such as sleep/heal are not supported by this NPC combat API.
setNpcSpell selects the spell/level; none clears it. It stops targeting and holsters magic. Draw with setNpcWeaponMode(npc,"magic"). getNpcSpell returns the key, not the level. Use setNpcMana, setNpcMaxMana, getNpcMana, getNpcMaxMana, setNpcAmmo and getNpcAmmo for finite resources. Initial mana and ammo must be supplied explicitly.
-- Existing NPC owned by this resource; attacks cause real gameplay damage.
setNpcSpell(npc, "fire_ball", 1)
setNpcMaxMana(npc, 200)
setNpcMana(npc, 200)
setNpcWeaponMode(npc, "magic")
setNpcCombatConfig(npc, {range=3, consumeMana=true})
setNpcCombatMovement(npc, {mode="run", chase=true})
setNpcCombatTarget(npc, playerId)
-- Runs into range, stops to cast; when target leaves range, pursues again.
-- For one attempt instead of continuous combat: npcAttack(npc, playerId)
setNpcCombatTarget(npc, false)
setNpcWeapon(npc, "bow_small_01")
setNpcAmmo(npc, "arrows", 20)
setNpcWeaponMode(npc, "bow")
npcAttack(npc, playerId)
-- Crossbow alternative: crossbow_01, bolts, mode crossbow.
setNpcCombatConfig / getNpcCombatConfig:
| Field | Allowed values | Default |
|---|---|---|
| range | 1..40 metres; spell range can further constrain it | 15 |
| dexterity | 0..100000 | 10 |
| skillTier | integer 0 untrained / 1 trained / 2 master | 0 |
| aimTimeMs | integer 150..30000 | 600 |
| cooldownMs | integer 360..60000; spell cooldown/recovery still apply | 1000 |
| consumeAmmo, consumeMana | boolean | true |
Changing resource/config settings can cancel preparation; do not reapply them every frame. Disabling consumption preserves resources after release, but sufficient initial resources are still required. Ranged damage uses dexterity/skill; spell damage uses the existing spell catalog, not setNpcAttackDamage. Server [ranged] enabled and [magic] enabled switches are respected.
Visibility and collision
isNpcSeeingPlayer tests distance, horizontal cone and custom server sight blockers. Defaults: 15 metres / 120 degrees. setNpcLookAt is independent: it only faces once.
This is not complete native-world physics. Server-authoritative NPC projectiles consider actor capsules and enabled custom navigation prisms with blocksSight=true. They stop on the first blocker; area/cone spell effects check per-target occlusion. Terrain/buildings/doors/native meshes without matching server blocker geometry are not automatically covered. A map-editor object's collision=true does not create a server sight blocker.
Create a prism using createAiNavigationBlocker (default blocksSight=true). Use setAiNavigationBlockerBlocksSight to make it navigation-only; use setAiNavigationBlockerEnabled, updateAiNavigationBlocker and destroyAiNavigationBlocker for lifecycle changes. Disabling/destroying is respected without reloading. For a scripted moving door, synchronize its matching blocker rather than treating it as permanently closed. Ownership/normal navigation limits still apply.
Combat status
getNpcCombatState returns {phase, rejectReason, target, mode, mana, maxMana, arrows, bolts}. phase is a controller string: idle, blocked-or-out-of-range, casting, prepare, draw-or-loaded, aim, recovery or rejected. It is not a complete melee animation state machine. rejectReason is a numeric code for the current ranged/magic subsystem.
onNpcProjectileState sends numeric phase/result/rejectReason and a domain string (ranged or magic). The tables below list the exact protocol enum mapping; they are not new Lua global constants.
SpellCastPhase
| Value | Meaning |
|---|---|
| 0 | None |
| 1 | Equip |
| 2 | CastStart |
| 3 | Charging |
| 4 | Channeling |
| 5 | Release |
| 6 | Recovery |
| 7 | Cancel |
| 8 | Completed |
SpellCastResult
| Value | Meaning |
|---|---|
| 0 | Pending |
| 1 | Accepted |
| 2 | Rejected |
| 3 | Completed |
| 4 | Cancelled |
SpellRejectReason
| Value | Meaning |
|---|---|
| 0 | None |
| 1 | ServerDisabled |
| 2 | CatalogMismatch |
| 3 | InvalidSpell |
| 4 | NotImplemented |
| 5 | NotGranted |
| 6 | CircleTooLow |
| 7 | NotEnoughMana |
| 8 | Cooldown |
| 9 | InvalidPhase |
| 10 | DuplicateCast |
| 11 | RateLimited |
| 12 | InvalidOrigin |
| 13 | InvalidAim |
| 14 | InvalidTarget |
| 15 | OutOfRange |
| 16 | StaleSnapshot |
RangedPhase
| Value | Meaning |
|---|---|
| 0 | None |
| 1 | Equipped |
| 2 | Relaxed |
| 3 | AimStart |
| 4 | Aiming |
| 5 | NotchStart |
| 6 | Notched |
| 7 | DrawStart |
| 8 | Drawing |
| 9 | DrawHold |
| 10 | Release |
| 11 | QuickRelease |
| 12 | Recovery |
| 13 | ReloadStart |
| 14 | Reloading |
| 15 | Loaded |
| 16 | Cancelled |
| 17 | Unequipped |
RangedActionResult
| Value | Meaning |
|---|---|
| 0 | Pending |
| 1 | Accepted |
| 2 | Rejected |
| 3 | Completed |
| 4 | Cancelled |
RangedRejectReason
| Value | Meaning |
|---|---|
| 0 | None |
| 1 | ServerDisabled |
| 2 | InvalidPlayer |
| 3 | InvalidSequence |
| 4 | InvalidWeapon |
| 5 | WeaponNotEquipped |
| 6 | WeaponNotInHand |
| 7 | InvalidPhase |
| 8 | DuplicateShot |
| 9 | RateLimited |
| 10 | InvalidOrigin |
| 11 | InvalidAim |
| 12 | InvalidTarget |
| 13 | OutOfRange |
| 14 | StaleSnapshot |
| 15 | NoAmmo |
| 16 | NotLoaded |
| 17 | ChargeTooShort |
| 18 | ReloadIncomplete |
Synchronization and cleanup
Current appearance, equipment/drawn state, life state, ongoing action/cast timing and current projectiles are replayed on relevance entry/late join, without replaying historical damage/releases. Each NPC keeps independent state. Stopping the owner resource/destroying the NPC cleans its state and projectile subsystem; death alone does not necessarily remove projectiles already released.
The update also improves NPC melee hit presentation, bow visibility and return to locomotion after ended attacks. Human crouch/sneak replication and remote attack recovery are automatic presentation changes, not new Lua functions. No local inventory manipulation, head-only gaze or full-world collision expansion is promised by these APIs.
NPC appearance since 0.1.5
The following are server-side functions for human NPCs created by spawnNpc. Only the owning resource can change them. They use the existing remote-character appearance pipeline, not a second body mesh or player inventory. Authoritative values are replayed on relevance entry and late join.
- setNpcIdentity / getNpcIdentity choose/read the matched identity profile and head. getNpcIdentityNames lists the same named identities as the player API. Use "default" to explicitly return to the Nameless Hero identity. Armor and independent color/tattoo overrides are preserved.
- setNpcSkinColor / getNpcSkinColor / resetNpcSkinColor control independent skin tint. RGB channels are integer sRGB bytes, 0..255. The getter returns {r,g,b}, or false when no override is enabled or the NPC is unavailable.
- setNpcTattoo / getNpcTattoo / resetNpcTattoo control the tattoo override. getNpcTattooNames returns the fixed catalog from Player tattoo catalog. "none" explicitly removes tattoos; reset restores inherited materials. These are different operations.
Existing setNpcSkin remains a legacy surface preset, not an alias for setNpcIdentity. NPCs that have never called setNpcIdentity keep the old skin behaviour. After an explicit identity is selected, setNpcSkin does not replace that identity. getNpcData adds identity and identityExplicit to distinguish these states. getNpcIdentity returns "default" before the first explicit identity selection; this does not describe any legacy skin fallback.
setNpcArmor changes the independent garment; setNpcWeapon changes the equipped weapon. Neither new cosmetic setters nor their resets change authoritative HP, equipment, patrol or combat configuration. Appearance rebuilding is asynchronous: true confirms acceptance, not that the render has already finished. Tattoos are visible only on exposed body regions supported by the chosen preset; no new geometry, outfit replacement or arbitrary textures are loaded from scripts.
local npc = spawnNpc("Guide", "default", x, y, z, 0)
if npc then
setNpcIdentity(npc, "milten")
setNpcArmor(npc, "water_robe")
setNpcSkinColor(npc, 90, 60, 40)
setNpcTattoo(npc, "templar1")
-- Later, independently:
setNpcIdentity(npc, "default") -- keeps armor, tint and tattoo
resetNpcSkinColor(npc)
resetNpcTattoo(npc)
end
State lasts for this NPC's lifetime. For persistence across a server restart, store the keys/RGB in your resource database and reapply after spawning a new NPC. No client event is automatically trusted or exposed by these APIs.