NPC scripting: Difference between revisions

From Wiki G1R-MP G1 Remake Multiplayer
Jump to navigation Jump to search
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.''' Upcoming-release documentation; these additions are not included in public 0.1.4. See [[Update 0.1.5]] for the complete function/event index.
'''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.

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.