@rezless/vm 0.6.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builtins/agents.d.ts +14 -0
- package/dist/builtins/agents.d.ts.map +1 -0
- package/dist/builtins/agents.js +117 -0
- package/dist/builtins/agents.js.map +1 -0
- package/dist/builtins/animation.d.ts +46 -0
- package/dist/builtins/animation.d.ts.map +1 -0
- package/dist/builtins/animation.js +316 -0
- package/dist/builtins/animation.js.map +1 -0
- package/dist/builtins/attachments.d.ts +9 -0
- package/dist/builtins/attachments.d.ts.map +1 -0
- package/dist/builtins/attachments.js +127 -0
- package/dist/builtins/attachments.js.map +1 -0
- package/dist/builtins/base64.d.ts +13 -2
- package/dist/builtins/base64.d.ts.map +1 -1
- package/dist/builtins/base64.js +8 -6
- package/dist/builtins/base64.js.map +1 -1
- package/dist/builtins/camera.d.ts +35 -0
- package/dist/builtins/camera.d.ts.map +1 -0
- package/dist/builtins/camera.js +104 -0
- package/dist/builtins/camera.js.map +1 -0
- package/dist/builtins/collision.d.ts +21 -0
- package/dist/builtins/collision.d.ts.map +1 -0
- package/dist/builtins/collision.js +44 -0
- package/dist/builtins/collision.js.map +1 -0
- package/dist/builtins/controls.d.ts +19 -0
- package/dist/builtins/controls.d.ts.map +1 -0
- package/dist/builtins/controls.js +27 -0
- package/dist/builtins/controls.js.map +1 -0
- package/dist/builtins/damage.d.ts +24 -0
- package/dist/builtins/damage.d.ts.map +1 -0
- package/dist/builtins/damage.js +166 -0
- package/dist/builtins/damage.js.map +1 -0
- package/dist/builtins/detected.d.ts +22 -1
- package/dist/builtins/detected.d.ts.map +1 -1
- package/dist/builtins/detected.js +28 -2
- package/dist/builtins/detected.js.map +1 -1
- package/dist/builtins/dialog.d.ts +23 -0
- package/dist/builtins/dialog.d.ts.map +1 -0
- package/dist/builtins/dialog.js +69 -0
- package/dist/builtins/dialog.js.map +1 -0
- package/dist/builtins/email.d.ts +61 -0
- package/dist/builtins/email.d.ts.map +1 -0
- package/dist/builtins/email.js +121 -0
- package/dist/builtins/email.js.map +1 -0
- package/dist/builtins/environment.d.ts +27 -0
- package/dist/builtins/environment.d.ts.map +1 -0
- package/dist/builtins/environment.js +126 -0
- package/dist/builtins/environment.js.map +1 -0
- package/dist/builtins/experience.d.ts +51 -0
- package/dist/builtins/experience.d.ts.map +1 -0
- package/dist/builtins/experience.js +202 -0
- package/dist/builtins/experience.js.map +1 -0
- package/dist/builtins/game-control.d.ts +17 -0
- package/dist/builtins/game-control.d.ts.map +1 -0
- package/dist/builtins/game-control.js +21 -0
- package/dist/builtins/game-control.js.map +1 -0
- package/dist/builtins/give.d.ts +16 -0
- package/dist/builtins/give.d.ts.map +1 -0
- package/dist/builtins/give.js +137 -0
- package/dist/builtins/give.js.map +1 -0
- package/dist/builtins/ground.d.ts +18 -0
- package/dist/builtins/ground.d.ts.map +1 -0
- package/dist/builtins/ground.js +83 -0
- package/dist/builtins/ground.js.map +1 -0
- package/dist/builtins/hash.d.ts +1 -0
- package/dist/builtins/hash.d.ts.map +1 -1
- package/dist/builtins/hash.js +12 -2
- package/dist/builtins/hash.js.map +1 -1
- package/dist/builtins/http.d.ts +99 -4
- package/dist/builtins/http.d.ts.map +1 -1
- package/dist/builtins/http.js +216 -6
- package/dist/builtins/http.js.map +1 -1
- package/dist/builtins/index.d.ts.map +1 -1
- package/dist/builtins/index.js +250 -14
- package/dist/builtins/index.js.map +1 -1
- package/dist/builtins/inventory.d.ts +8 -5
- package/dist/builtins/inventory.d.ts.map +1 -1
- package/dist/builtins/inventory.js +37 -15
- package/dist/builtins/inventory.js.map +1 -1
- package/dist/builtins/json.d.ts.map +1 -1
- package/dist/builtins/json.js +45 -34
- package/dist/builtins/json.js.map +1 -1
- package/dist/builtins/land.d.ts +29 -0
- package/dist/builtins/land.d.ts.map +1 -0
- package/dist/builtins/land.js +339 -0
- package/dist/builtins/land.js.map +1 -0
- package/dist/builtins/link-info.js +2 -2
- package/dist/builtins/link-info.js.map +1 -1
- package/dist/builtins/links.d.ts +20 -0
- package/dist/builtins/links.d.ts.map +1 -0
- package/dist/builtins/links.js +53 -0
- package/dist/builtins/links.js.map +1 -0
- package/dist/builtins/linksetdata.d.ts +5 -0
- package/dist/builtins/linksetdata.d.ts.map +1 -1
- package/dist/builtins/linksetdata.js +22 -9
- package/dist/builtins/linksetdata.js.map +1 -1
- package/dist/builtins/lists.d.ts +21 -0
- package/dist/builtins/lists.d.ts.map +1 -1
- package/dist/builtins/lists.js +137 -17
- package/dist/builtins/lists.js.map +1 -1
- package/dist/builtins/math.d.ts +18 -0
- package/dist/builtins/math.d.ts.map +1 -1
- package/dist/builtins/math.js +104 -5
- package/dist/builtins/math.js.map +1 -1
- package/dist/builtins/media.d.ts +8 -0
- package/dist/builtins/media.d.ts.map +1 -0
- package/dist/builtins/media.js +167 -0
- package/dist/builtins/media.js.map +1 -0
- package/dist/builtins/motion.d.ts +79 -0
- package/dist/builtins/motion.d.ts.map +1 -0
- package/dist/builtins/motion.js +396 -0
- package/dist/builtins/motion.js.map +1 -0
- package/dist/builtins/notecard-search.d.ts +15 -0
- package/dist/builtins/notecard-search.d.ts.map +1 -0
- package/dist/builtins/notecard-search.js +374 -0
- package/dist/builtins/notecard-search.js.map +1 -0
- package/dist/builtins/object-animation.d.ts +21 -0
- package/dist/builtins/object-animation.d.ts.map +1 -0
- package/dist/builtins/object-animation.js +193 -0
- package/dist/builtins/object-animation.js.map +1 -0
- package/dist/builtins/object-reads.d.ts +39 -0
- package/dist/builtins/object-reads.d.ts.map +1 -0
- package/dist/builtins/object-reads.js +332 -0
- package/dist/builtins/object-reads.js.map +1 -0
- package/dist/builtins/object.d.ts +11 -2
- package/dist/builtins/object.d.ts.map +1 -1
- package/dist/builtins/object.js +32 -8
- package/dist/builtins/object.js.map +1 -1
- package/dist/builtins/particles.d.ts +4 -0
- package/dist/builtins/particles.d.ts.map +1 -0
- package/dist/builtins/particles.js +65 -0
- package/dist/builtins/particles.js.map +1 -0
- package/dist/builtins/pathfinding.d.ts +39 -0
- package/dist/builtins/pathfinding.d.ts.map +1 -0
- package/dist/builtins/pathfinding.js +83 -0
- package/dist/builtins/pathfinding.js.map +1 -0
- package/dist/builtins/permissions.d.ts.map +1 -1
- package/dist/builtins/permissions.js +6 -0
- package/dist/builtins/permissions.js.map +1 -1
- package/dist/builtins/physics.d.ts +49 -0
- package/dist/builtins/physics.d.ts.map +1 -0
- package/dist/builtins/physics.js +47 -0
- package/dist/builtins/physics.js.map +1 -0
- package/dist/builtins/prim-accessors.d.ts +4 -0
- package/dist/builtins/prim-accessors.d.ts.map +1 -1
- package/dist/builtins/prim-accessors.js +75 -28
- package/dist/builtins/prim-accessors.js.map +1 -1
- package/dist/builtins/prim-params.d.ts.map +1 -1
- package/dist/builtins/prim-params.js +6 -2
- package/dist/builtins/prim-params.js.map +1 -1
- package/dist/builtins/raycast.d.ts +3 -0
- package/dist/builtins/raycast.d.ts.map +1 -0
- package/dist/builtins/raycast.js +65 -0
- package/dist/builtins/raycast.js.map +1 -0
- package/dist/builtins/region.d.ts +31 -0
- package/dist/builtins/region.d.ts.map +1 -0
- package/dist/builtins/region.js +125 -0
- package/dist/builtins/region.js.map +1 -0
- package/dist/builtins/remote-load.d.ts +3 -0
- package/dist/builtins/remote-load.d.ts.map +1 -0
- package/dist/builtins/remote-load.js +53 -0
- package/dist/builtins/remote-load.js.map +1 -0
- package/dist/builtins/rez.d.ts +10 -0
- package/dist/builtins/rez.d.ts.map +1 -0
- package/dist/builtins/rez.js +255 -0
- package/dist/builtins/rez.js.map +1 -0
- package/dist/builtins/rsa.d.ts +4 -0
- package/dist/builtins/rsa.d.ts.map +1 -0
- package/dist/builtins/rsa.js +44 -0
- package/dist/builtins/rsa.js.map +1 -0
- package/dist/builtins/script-control.d.ts +8 -3
- package/dist/builtins/script-control.d.ts.map +1 -1
- package/dist/builtins/script-control.js +27 -8
- package/dist/builtins/script-control.js.map +1 -1
- package/dist/builtins/sensor.d.ts +11 -0
- package/dist/builtins/sensor.d.ts.map +1 -0
- package/dist/builtins/sensor.js +145 -0
- package/dist/builtins/sensor.js.map +1 -0
- package/dist/builtins/sit.d.ts +23 -0
- package/dist/builtins/sit.d.ts.map +1 -0
- package/dist/builtins/sit.js +150 -0
- package/dist/builtins/sit.js.map +1 -0
- package/dist/builtins/sound.d.ts +19 -0
- package/dist/builtins/sound.d.ts.map +1 -0
- package/dist/builtins/sound.js +113 -0
- package/dist/builtins/sound.js.map +1 -0
- package/dist/builtins/strings.d.ts +6 -0
- package/dist/builtins/strings.d.ts.map +1 -1
- package/dist/builtins/strings.js +47 -6
- package/dist/builtins/strings.js.map +1 -1
- package/dist/builtins/targets.d.ts +12 -0
- package/dist/builtins/targets.d.ts.map +1 -0
- package/dist/builtins/targets.js +121 -0
- package/dist/builtins/targets.js.map +1 -0
- package/dist/builtins/time.d.ts +15 -5
- package/dist/builtins/time.d.ts.map +1 -1
- package/dist/builtins/time.js +36 -11
- package/dist/builtins/time.js.map +1 -1
- package/dist/builtins/vehicles.d.ts +22 -0
- package/dist/builtins/vehicles.d.ts.map +1 -0
- package/dist/builtins/vehicles.js +78 -0
- package/dist/builtins/vehicles.js.map +1 -0
- package/dist/builtins/viewer.d.ts +39 -0
- package/dist/builtins/viewer.d.ts.map +1 -0
- package/dist/builtins/viewer.js +74 -0
- package/dist/builtins/viewer.js.map +1 -0
- package/dist/clock.d.ts +45 -7
- package/dist/clock.d.ts.map +1 -1
- package/dist/clock.js +125 -14
- package/dist/clock.js.map +1 -1
- package/dist/dispatch.d.ts +3 -2
- package/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +15 -10
- package/dist/dispatch.js.map +1 -1
- package/dist/experience.d.ts +39 -0
- package/dist/experience.d.ts.map +1 -0
- package/dist/experience.js +38 -0
- package/dist/experience.js.map +1 -0
- package/dist/index.d.ts +20 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/interpreter.d.ts +5 -10
- package/dist/interpreter.d.ts.map +1 -1
- package/dist/interpreter.js +143 -70
- package/dist/interpreter.js.map +1 -1
- package/dist/inventory.d.ts +2 -0
- package/dist/inventory.d.ts.map +1 -1
- package/dist/inventory.js +2 -0
- package/dist/inventory.js.map +1 -1
- package/dist/linkset.d.ts +147 -11
- package/dist/linkset.d.ts.map +1 -1
- package/dist/linkset.js +401 -28
- package/dist/linkset.js.map +1 -1
- package/dist/memory.d.ts +5 -2
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +5 -2
- package/dist/memory.js.map +1 -1
- package/dist/prim-params.d.ts +27 -10
- package/dist/prim-params.d.ts.map +1 -1
- package/dist/prim-params.js +209 -125
- package/dist/prim-params.js.map +1 -1
- package/dist/prim.d.ts +95 -5
- package/dist/prim.d.ts.map +1 -1
- package/dist/prim.js +96 -7
- package/dist/prim.js.map +1 -1
- package/dist/runtime.d.ts +52 -3
- package/dist/runtime.d.ts.map +1 -1
- package/dist/script.d.ts +171 -12
- package/dist/script.d.ts.map +1 -1
- package/dist/script.js +480 -73
- package/dist/script.js.map +1 -1
- package/dist/typecheck.d.ts +3 -1
- package/dist/typecheck.d.ts.map +1 -1
- package/dist/typecheck.js +25 -5
- package/dist/typecheck.js.map +1 -1
- package/dist/values/coerce.d.ts +1 -1
- package/dist/values/coerce.d.ts.map +1 -1
- package/dist/values/coerce.js +10 -6
- package/dist/values/coerce.js.map +1 -1
- package/dist/values/format.d.ts +3 -1
- package/dist/values/format.d.ts.map +1 -1
- package/dist/values/format.js +10 -6
- package/dist/values/format.js.map +1 -1
- package/dist/values/ops.d.ts +3 -0
- package/dist/values/ops.d.ts.map +1 -1
- package/dist/values/ops.js +2 -2
- package/dist/values/ops.js.map +1 -1
- package/dist/world.d.ts +247 -1
- package/dist/world.d.ts.map +1 -1
- package/dist/world.js +318 -6
- package/dist/world.js.map +1 -1
- package/package.json +2 -2
package/dist/script.js
CHANGED
|
@@ -1,23 +1,37 @@
|
|
|
1
|
+
import { clearTargetMotion } from './builtins/motion.js';
|
|
2
|
+
import { copyGameControl, gameControlAvatars } from './builtins/game-control.js';
|
|
3
|
+
import { PERMISSION_GAME_CONTROL } from './generated/constants.js';
|
|
4
|
+
import { clearTargets } from './builtins/targets.js';
|
|
5
|
+
import { withCollisionFilter } from './builtins/collision.js';
|
|
6
|
+
import { stopSensors } from './builtins/sensor.js';
|
|
7
|
+
import { enterDamageEvent, leaveDamageEvent, combatAvatars, } from './builtins/damage.js';
|
|
8
|
+
import { sendIncomingHttp } from './builtins/http.js';
|
|
9
|
+
import { deliverEmail, emailQueue } from './builtins/email.js';
|
|
10
|
+
import { characterFor, pathUpdate, setNavigationResults } from './builtins/pathfinding.js';
|
|
1
11
|
import { truncateUtf8 } from './values/utf8.js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
12
|
+
import { agentPresence } from './builtins/viewer.js';
|
|
13
|
+
import { defaultCamera } from './builtins/camera.js';
|
|
14
|
+
import { animationAvatars, playingAnimations, setAnimationState } from './builtins/animation.js';
|
|
15
|
+
import { HaltSignal, ResetScriptSignal } from './builtins/object.js';
|
|
4
16
|
import { Mulberry32 } from './random.js';
|
|
5
17
|
import { inventoryItemName } from './inventory.js';
|
|
18
|
+
import { typedList, listElementTypes } from './values/list-types.js';
|
|
6
19
|
import { NULL_KEY } from './values/types.js';
|
|
7
20
|
import { execHandler, StateChangeSignal } from './interpreter.js';
|
|
8
21
|
import { buildCoveragePlan, CoverageCollector } from './coverage.js';
|
|
9
22
|
import { defaultEvalFor, rot, toFloat32Value, vec } from './values/types.js';
|
|
10
|
-
import { typedList } from './values/list-types.js';
|
|
11
23
|
import { Env } from './env.js';
|
|
12
|
-
import { ScriptClockView } from './clock.js';
|
|
24
|
+
import { DELAYED_EVENTS, ScriptClockView } from './clock.js';
|
|
13
25
|
import { Linkset } from './linkset.js';
|
|
14
26
|
import { Prim } from './prim.js';
|
|
15
27
|
import { EVENT_SPECS } from './generated/events.js';
|
|
16
28
|
import { CONSTANT_TABLE } from './generated/constants_table.js';
|
|
17
|
-
import { DEBUG_CHANNEL, HTTP_BODY_TRUNCATED } from './generated/constants.js';
|
|
29
|
+
import { DEBUG_CHANNEL, HTTP_BODY_TRUNCATED, PERMISSION_TAKE_CONTROLS, XP_ERROR_NOT_PERMITTED, } from './generated/constants.js';
|
|
18
30
|
import { own } from './own.js';
|
|
19
31
|
import { deterministicKey } from './keys.js';
|
|
20
|
-
import { Memory,
|
|
32
|
+
import { Memory, RuntimeError, codeBytes } from './memory.js';
|
|
33
|
+
import { invalidPrograms } from './typecheck.js';
|
|
34
|
+
import { grantExperience } from './builtins/experience.js';
|
|
21
35
|
/**
|
|
22
36
|
* Public handle for a loaded LSL script. Tests interact almost entirely
|
|
23
37
|
* with this surface: drive events via fire(), inspect chat / calls,
|
|
@@ -25,11 +39,13 @@ import { Memory, StackHeapCollision, codeBytes } from './memory.js';
|
|
|
25
39
|
*/
|
|
26
40
|
export class Script {
|
|
27
41
|
ast;
|
|
42
|
+
options;
|
|
28
43
|
state;
|
|
29
44
|
mocks = Object.create(null);
|
|
30
45
|
globals;
|
|
31
46
|
userFunctions;
|
|
32
47
|
functions;
|
|
48
|
+
invalidPrograms;
|
|
33
49
|
handlersByState;
|
|
34
50
|
started = false;
|
|
35
51
|
stubs;
|
|
@@ -37,20 +53,28 @@ export class Script {
|
|
|
37
53
|
/** Per-script clock facade (timer state + time reference). */
|
|
38
54
|
clockView;
|
|
39
55
|
host;
|
|
40
|
-
linkset;
|
|
41
56
|
scriptName;
|
|
42
57
|
/** When false, drainQueue parks events instead of delivering them. */
|
|
43
58
|
running = true;
|
|
44
59
|
/** Events parked while `running === false`; replayed when re-enabled. */
|
|
45
60
|
parkedEvents = [];
|
|
61
|
+
/** llGetStartParameter: the linkset's start parameter until the script resets, then 0. */
|
|
62
|
+
startParameter;
|
|
63
|
+
/** The handler chain suspended in llSleep or a builtin delay, if any. */
|
|
64
|
+
task = null;
|
|
65
|
+
/** Virtual time this script last started a handler or woke from a sleep. */
|
|
66
|
+
resumedAt = 0;
|
|
46
67
|
constructor(ast, options = {}) {
|
|
47
68
|
this.ast = ast;
|
|
69
|
+
this.options = options;
|
|
48
70
|
// Auto-wrap: if no host prim was provided, create a default 1-prim linkset.
|
|
49
71
|
let host = options.host;
|
|
50
72
|
if (!host) {
|
|
51
73
|
const linkset = new Linkset({
|
|
52
74
|
owner: options.owner ?? NULL_KEY,
|
|
53
75
|
startUnixTime: options.startUnixTime ?? 0,
|
|
76
|
+
startParameter: options.startParameter ?? 0,
|
|
77
|
+
startString: options.startString ?? '',
|
|
54
78
|
});
|
|
55
79
|
const prim = new Prim({
|
|
56
80
|
key: options.objectKey ??
|
|
@@ -64,15 +88,14 @@ export class Script {
|
|
|
64
88
|
if (!host.linkset) {
|
|
65
89
|
throw new Error('Script host prim must belong to a Linkset');
|
|
66
90
|
}
|
|
67
|
-
this.
|
|
91
|
+
this.startParameter = this.linkset.startParameter;
|
|
68
92
|
this.scriptName = inventoryItemName(options.scriptName ?? deriveScriptName(options.filename));
|
|
69
93
|
this.clockView = new ScriptClockView(this.linkset.clock, () => this);
|
|
70
|
-
const linkset = this.linkset;
|
|
71
94
|
const prim = host;
|
|
72
95
|
const scriptName = this.scriptName;
|
|
73
96
|
const identity = {
|
|
74
97
|
get owner() {
|
|
75
|
-
return linkset.owner;
|
|
98
|
+
return prim.linkset.owner;
|
|
76
99
|
},
|
|
77
100
|
get objectKey() {
|
|
78
101
|
return prim.key;
|
|
@@ -99,6 +122,13 @@ export class Script {
|
|
|
99
122
|
clock: this.clockView,
|
|
100
123
|
httpRequests: [],
|
|
101
124
|
httpKeyCounter: 0,
|
|
125
|
+
urls: [],
|
|
126
|
+
urlPolicy: 'grant',
|
|
127
|
+
urlCounter: 0,
|
|
128
|
+
incomingHttp: [],
|
|
129
|
+
httpInCounter: 0,
|
|
130
|
+
emails: [],
|
|
131
|
+
targetedEmails: [],
|
|
102
132
|
listens: [],
|
|
103
133
|
listenHandleCounter: 0,
|
|
104
134
|
random: new Mulberry32(options.randomSeed ?? 1),
|
|
@@ -107,11 +137,29 @@ export class Script {
|
|
|
107
137
|
dataserverRequests: [],
|
|
108
138
|
dataserverKeyCounter: 0,
|
|
109
139
|
detectedStack: [],
|
|
110
|
-
linksetData
|
|
140
|
+
get linksetData() {
|
|
141
|
+
return prim.linkset.linksetData;
|
|
142
|
+
},
|
|
111
143
|
appearance: this.host.appearance,
|
|
144
|
+
gameControlSnapshot: null,
|
|
112
145
|
permissions: { granted: 0, agent: NULL_KEY, requests: [] },
|
|
146
|
+
experience: options.experience ?? null,
|
|
147
|
+
experienceRequests: [],
|
|
148
|
+
controls: { taken: null, level: 0 },
|
|
149
|
+
camera: defaultCamera(),
|
|
150
|
+
cameraParams: new Map(),
|
|
151
|
+
get animations() {
|
|
152
|
+
return animationAvatars(prim.linkset);
|
|
153
|
+
},
|
|
154
|
+
inventoryOffers: [],
|
|
155
|
+
viewerRequests: [],
|
|
156
|
+
teleportRequests: [],
|
|
157
|
+
agentPresence: agentPresence(prim.linkset),
|
|
158
|
+
dialogs: [],
|
|
113
159
|
lifecycle: {
|
|
114
160
|
dead: false,
|
|
161
|
+
dying: false,
|
|
162
|
+
removed: false,
|
|
115
163
|
},
|
|
116
164
|
filename,
|
|
117
165
|
source,
|
|
@@ -127,6 +175,7 @@ export class Script {
|
|
|
127
175
|
initGlobals(this.globals, ast.globals);
|
|
128
176
|
this.userFunctions = new Map(ast.functions.map((f) => [f.name, f]));
|
|
129
177
|
this.functions = options.functions ?? {};
|
|
178
|
+
this.invalidPrograms = invalidPrograms(ast, this.functions);
|
|
130
179
|
this.stubs = options.stubs ?? 'allow';
|
|
131
180
|
this.handlersByState = indexHandlers(ast);
|
|
132
181
|
if (!this.handlersByState.has('default')) {
|
|
@@ -138,7 +187,21 @@ export class Script {
|
|
|
138
187
|
host.addScript(this);
|
|
139
188
|
}
|
|
140
189
|
}
|
|
190
|
+
/** A state transition leaves on_damage before lifecycle handlers execute. */
|
|
191
|
+
clearDamageAdjustment() {
|
|
192
|
+
const context = this.state.detectedStack.at(-1);
|
|
193
|
+
if (context?.event === 'on_damage')
|
|
194
|
+
context.event = '';
|
|
195
|
+
}
|
|
196
|
+
/** Configure an avatar's combat health, parcel and attachment scripts. */
|
|
197
|
+
setCombatAvatar(id, avatar) {
|
|
198
|
+
combatAvatars(this).set(id.toLowerCase(), { ...avatar, region: this.linkset.region });
|
|
199
|
+
}
|
|
141
200
|
/** Current virtual time in milliseconds since linkset construction. */
|
|
201
|
+
/** The linkset holding this script's prim; it changes when the prim is linked or unlinked. */
|
|
202
|
+
get linkset() {
|
|
203
|
+
return this.host.linkset;
|
|
204
|
+
}
|
|
142
205
|
get now() {
|
|
143
206
|
return this.linkset.clock.now;
|
|
144
207
|
}
|
|
@@ -181,6 +244,18 @@ export class Script {
|
|
|
181
244
|
callsOf(name) {
|
|
182
245
|
return this.state.calls.filter((c) => c.name === name);
|
|
183
246
|
}
|
|
247
|
+
/** The object's character, or null before creation and after deletion. */
|
|
248
|
+
get character() {
|
|
249
|
+
return characterFor(this);
|
|
250
|
+
}
|
|
251
|
+
/** Set fixed navigation query answers. These fixtures survive script resets. */
|
|
252
|
+
setNavigationResults(results) {
|
|
253
|
+
setNavigationResults(this, results);
|
|
254
|
+
}
|
|
255
|
+
/** Raise path_update on every script in the character's root prim. */
|
|
256
|
+
pathUpdate(type, reserved = []) {
|
|
257
|
+
pathUpdate(this, type, reserved);
|
|
258
|
+
}
|
|
184
259
|
/** Captured outgoing HTTP requests from `llHTTPRequest`. */
|
|
185
260
|
get httpRequests() {
|
|
186
261
|
return this.state.httpRequests;
|
|
@@ -206,9 +281,10 @@ export class Script {
|
|
|
206
281
|
: typedList([HTTP_BODY_TRUNCATED, req.bodyMaxLength], ['integer', 'integer']),
|
|
207
282
|
body: cut,
|
|
208
283
|
};
|
|
284
|
+
const started = this.host.scripts.filter((s) => s.handles('http_response'));
|
|
209
285
|
for (const s of this.host.scripts)
|
|
210
286
|
this.clockView.scheduleOn(s, this.now, 'http_response', payload);
|
|
211
|
-
this.linkset.
|
|
287
|
+
this.linkset.settle(started);
|
|
212
288
|
}
|
|
213
289
|
/** Convenience: respond to the most recent HTTP request. */
|
|
214
290
|
respondToLastHttp(response) {
|
|
@@ -217,6 +293,46 @@ export class Script {
|
|
|
217
293
|
throw new Error('no HTTP request to respond to');
|
|
218
294
|
this.respondToHttp(req.key, response);
|
|
219
295
|
}
|
|
296
|
+
/** URLs the script holds from llRequestURL / llRequestSecureURL, oldest first. */
|
|
297
|
+
get urls() {
|
|
298
|
+
return this.state.urls;
|
|
299
|
+
}
|
|
300
|
+
/** Grant later llRequestURL / llRequestSecureURL calls (the default). */
|
|
301
|
+
grantUrls() {
|
|
302
|
+
this.state.urlPolicy = 'grant';
|
|
303
|
+
}
|
|
304
|
+
/** Deny later llRequestURL / llRequestSecureURL calls with URL_REQUEST_DENIED. */
|
|
305
|
+
denyUrls() {
|
|
306
|
+
this.state.urlPolicy = 'deny';
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Send a request to one of the script's URLs, with a path and query after
|
|
310
|
+
* it if you like, and fire `http_request`. The returned request's
|
|
311
|
+
* `response` holds what the script answered with llHTTPResponse.
|
|
312
|
+
*/
|
|
313
|
+
sendHttpRequest(url, init = {}) {
|
|
314
|
+
return sendIncomingHttp(this, this.state, url, init);
|
|
315
|
+
}
|
|
316
|
+
/** Mail the script sent with llEmail, oldest first. */
|
|
317
|
+
get emails() {
|
|
318
|
+
return this.state.emails;
|
|
319
|
+
}
|
|
320
|
+
/** Mail the script sent with llTargetedEmail, oldest first. */
|
|
321
|
+
get targetedEmails() {
|
|
322
|
+
return this.state.targetedEmails;
|
|
323
|
+
}
|
|
324
|
+
/** Mail queued on the script's prim, oldest first, waiting for llGetNextEmail. */
|
|
325
|
+
get inbox() {
|
|
326
|
+
return emailQueue(this.host);
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Queue mail on the script's prim. It fires `email` only when a script in
|
|
330
|
+
* the prim asks for it with llGetNextEmail. Returns false, queuing
|
|
331
|
+
* nothing, when the prim already holds 101 mails.
|
|
332
|
+
*/
|
|
333
|
+
deliverEmail(init) {
|
|
334
|
+
return deliverEmail(this, init);
|
|
335
|
+
}
|
|
220
336
|
/** Currently active listen registrations (from `llListen`). */
|
|
221
337
|
get listens() {
|
|
222
338
|
return this.state.listens;
|
|
@@ -244,9 +360,10 @@ export class Script {
|
|
|
244
360
|
throw new Error(`unknown dataserver request key: ${key}`);
|
|
245
361
|
req.fulfilled = true;
|
|
246
362
|
const payload = { queryid: key, data: value };
|
|
363
|
+
const started = this.host.scripts.filter((s) => s.handles('dataserver'));
|
|
247
364
|
for (const s of this.host.scripts)
|
|
248
365
|
this.clockView.scheduleOn(s, this.now, 'dataserver', payload);
|
|
249
|
-
this.linkset.
|
|
366
|
+
this.linkset.settle(started);
|
|
250
367
|
}
|
|
251
368
|
/** Convenience: respond to the most recent dataserver request. */
|
|
252
369
|
respondToLastDataserver(value) {
|
|
@@ -272,17 +389,180 @@ export class Script {
|
|
|
272
389
|
const granted = req.permissions & (permissions ?? req.permissions);
|
|
273
390
|
this.state.permissions.granted = granted;
|
|
274
391
|
this.state.permissions.agent = req.agent;
|
|
392
|
+
if (!(granted & PERMISSION_TAKE_CONTROLS))
|
|
393
|
+
this.state.controls.taken = null;
|
|
394
|
+
const started = this.handles('run_time_permissions') ? [this] : [];
|
|
275
395
|
this.clockView.schedule(this.now, 'run_time_permissions', { perm: granted });
|
|
276
|
-
this.linkset.
|
|
396
|
+
this.linkset.settle(started);
|
|
277
397
|
}
|
|
278
398
|
/** Answer the most recent unanswered permission request with No. */
|
|
279
399
|
denyPermissions() {
|
|
280
400
|
this.grantPermissions(0);
|
|
281
401
|
}
|
|
402
|
+
/** The experience the script was compiled with, or null. */
|
|
403
|
+
get experience() {
|
|
404
|
+
return this.state.experience;
|
|
405
|
+
}
|
|
406
|
+
/** llRequestExperiencePermissions calls waiting for an answer, oldest first. */
|
|
407
|
+
get experienceRequests() {
|
|
408
|
+
return this.state.experienceRequests;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Accept the most recent unanswered llRequestExperiencePermissions: the
|
|
412
|
+
* agent joins the experience, the script holds the experience permissions
|
|
413
|
+
* for that agent, and `experience_permissions` fires.
|
|
414
|
+
*/
|
|
415
|
+
grantExperiencePermissions() {
|
|
416
|
+
const req = this.answerExperienceRequest();
|
|
417
|
+
const started = this.handles('experience_permissions') ? [this] : [];
|
|
418
|
+
grantExperience(this.state, req.agent);
|
|
419
|
+
this.linkset.settle(started);
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Decline the most recent unanswered llRequestExperiencePermissions:
|
|
423
|
+
* `experience_permissions_denied` fires with `reason`
|
|
424
|
+
* (XP_ERROR_NOT_PERMITTED by default).
|
|
425
|
+
*/
|
|
426
|
+
denyExperiencePermissions(reason = XP_ERROR_NOT_PERMITTED) {
|
|
427
|
+
const req = this.answerExperienceRequest();
|
|
428
|
+
this.fireAndSettle('experience_permissions_denied', { agent_id: req.agent, reason });
|
|
429
|
+
}
|
|
430
|
+
answerExperienceRequest() {
|
|
431
|
+
const req = [...this.state.experienceRequests].reverse().find((r) => !r.answered);
|
|
432
|
+
if (!req)
|
|
433
|
+
throw new Error('no pending experience permission request');
|
|
434
|
+
req.answered = true;
|
|
435
|
+
return req;
|
|
436
|
+
}
|
|
437
|
+
fireAndSettle(event, payload) {
|
|
438
|
+
const started = this.handles(event) ? [this] : [];
|
|
439
|
+
this.clockView.schedule(this.now, event, payload);
|
|
440
|
+
this.linkset.settle(started);
|
|
441
|
+
}
|
|
442
|
+
/** Controls taken with llTakeControls, or null when none are. */
|
|
443
|
+
get controls() {
|
|
444
|
+
return this.state.controls.taken;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Have the permission agent hold exactly the controls in `level` (CONTROL_*
|
|
448
|
+
* bits; 0 lets go of all). Fires `control` with the held and just-changed
|
|
449
|
+
* bits among those taken, if the script took controls with accept TRUE and
|
|
450
|
+
* any of them is held or changed.
|
|
451
|
+
*/
|
|
452
|
+
sendControl(level) {
|
|
453
|
+
const edge = level ^ this.state.controls.level;
|
|
454
|
+
this.state.controls.level = level;
|
|
455
|
+
const taken = this.state.controls.taken;
|
|
456
|
+
if (!taken?.accept)
|
|
457
|
+
return;
|
|
458
|
+
const held = level & taken.controls;
|
|
459
|
+
const changed = edge & taken.controls;
|
|
460
|
+
if (held === 0 && changed === 0)
|
|
461
|
+
return;
|
|
462
|
+
const started = this.handles('control') ? [this] : [];
|
|
463
|
+
this.clockView.schedule(this.now, 'control', { id: this.state.permissions.agent, level: held, edge: changed });
|
|
464
|
+
this.linkset.settle(started);
|
|
465
|
+
}
|
|
466
|
+
/** Set an avatar's semantic input fixture, shared by scripts in this World. */
|
|
467
|
+
setGameControl(agent, input) {
|
|
468
|
+
const avatars = gameControlAvatars(this.linkset);
|
|
469
|
+
if (input === null)
|
|
470
|
+
avatars.delete(agent.toLowerCase());
|
|
471
|
+
else
|
|
472
|
+
avatars.set(agent.toLowerCase(), copyGameControl(input));
|
|
473
|
+
}
|
|
474
|
+
/** Queue raw input and a copy of the avatar's semantic fixture for this script. */
|
|
475
|
+
sendGameControl(agent, raw = {}) {
|
|
476
|
+
if (!(this.state.permissions.granted & PERMISSION_GAME_CONTROL)
|
|
477
|
+
|| agent.toLowerCase() !== this.state.permissions.agent.toLowerCase())
|
|
478
|
+
return;
|
|
479
|
+
const input = gameControlAvatars(this.linkset).get(agent.toLowerCase());
|
|
480
|
+
const snapshot = input ? { ...copyGameControl(input), agent: agent.toLowerCase() } : null;
|
|
481
|
+
this.fire('game_control', {
|
|
482
|
+
id: agent, button_levels: (raw.buttons ?? 0) | 0,
|
|
483
|
+
axes: typedList((raw.axes ?? []).map(Math.fround), (raw.axes ?? []).map(() => 'float')),
|
|
484
|
+
gameControlSnapshot: snapshot,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
/** Set the permission agent's camera that llGetCameraPos / Rot / FOV / Aspect report. */
|
|
488
|
+
setCamera(camera) {
|
|
489
|
+
const { pos, rot: r, fov, aspect } = { ...this.state.camera, ...camera };
|
|
490
|
+
this.state.camera = {
|
|
491
|
+
pos: vec(pos.x, pos.y, pos.z),
|
|
492
|
+
rot: rot(r.x, r.y, r.z, r.s),
|
|
493
|
+
fov: Math.fround(fov),
|
|
494
|
+
aspect: Math.fround(aspect),
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
/** Camera rules set with llSetCameraParams, by rule. */
|
|
498
|
+
get cameraParams() {
|
|
499
|
+
return this.state.cameraParams;
|
|
500
|
+
}
|
|
501
|
+
/** Register an avatar in this region and set its default locomotion animation. */
|
|
502
|
+
setAnimationState(agent, state) {
|
|
503
|
+
setAnimationState(this.state.animations, agent, state);
|
|
504
|
+
}
|
|
505
|
+
/** Playing animation UUIDs for an avatar, in UUID order. Returns a fresh array. */
|
|
506
|
+
getPlayingAnimations(agent) {
|
|
507
|
+
return playingAnimations(this.state.animations, agent);
|
|
508
|
+
}
|
|
509
|
+
/** Inventory offers and completed prim deliveries, oldest first. */
|
|
510
|
+
get inventoryOffers() {
|
|
511
|
+
return this.state.inventoryOffers;
|
|
512
|
+
}
|
|
513
|
+
/** Teleport attempts made by this script, oldest first. */
|
|
514
|
+
get teleportRequests() {
|
|
515
|
+
return this.state.teleportRequests;
|
|
516
|
+
}
|
|
517
|
+
/** URL/map requests and rejected floater calls, oldest first. */
|
|
518
|
+
get viewerRequests() {
|
|
519
|
+
return this.state.viewerRequests;
|
|
520
|
+
}
|
|
521
|
+
/** Override whether an avatar is present in this region, for every script sharing it. */
|
|
522
|
+
setAgentInRegion(agent, present = true) {
|
|
523
|
+
this.state.agentPresence.set(agent.toLowerCase(), present);
|
|
524
|
+
}
|
|
525
|
+
/** Dialogs and text boxes this script showed with llDialog / llTextBox, oldest first. */
|
|
526
|
+
get dialogs() {
|
|
527
|
+
return this.state.dialogs;
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Answer a dialog as its avatar: the most recent unanswered one, or
|
|
531
|
+
* `opts.dialog`. The avatar says `text` on the dialog's channel, heard by
|
|
532
|
+
* listens within 20 m of the prim that showed it. A llDialog answer must
|
|
533
|
+
* be one of its buttons; a text box takes any text.
|
|
534
|
+
*/
|
|
535
|
+
answerDialog(text, opts = {}) {
|
|
536
|
+
const dialog = opts.dialog ?? [...this.state.dialogs].reverse().find((d) => !d.answered);
|
|
537
|
+
if (!dialog)
|
|
538
|
+
throw new Error('no unanswered dialog');
|
|
539
|
+
if (!this.state.dialogs.includes(dialog))
|
|
540
|
+
throw new Error('that dialog was not shown by this script');
|
|
541
|
+
if (dialog.answered)
|
|
542
|
+
throw new Error('that dialog was already answered');
|
|
543
|
+
if (dialog.kind === 'dialog' && !dialog.buttons.includes(text)) {
|
|
544
|
+
throw new Error(`'${text}' is not a button on the dialog: ${dialog.buttons.join(', ')}`);
|
|
545
|
+
}
|
|
546
|
+
dialog.answered = true;
|
|
547
|
+
const chat = { channel: dialog.channel, name: opts.name ?? '', key: dialog.agent, message: text };
|
|
548
|
+
this.linkset.settle(this.linkset.routeAvatarChat(this.host, chat).filter((s) => s.handles('listen')));
|
|
549
|
+
}
|
|
282
550
|
/** Currently displayed floating text (from llSetText). null if unset. */
|
|
283
551
|
get text() {
|
|
284
552
|
return this.state.appearance.text;
|
|
285
553
|
}
|
|
554
|
+
/** The prim's llSetTouchText text, '' if unset. */
|
|
555
|
+
get touchText() {
|
|
556
|
+
return this.host.touchText;
|
|
557
|
+
}
|
|
558
|
+
/** The prim's llSetSitText text, '' if unset. */
|
|
559
|
+
get sitText() {
|
|
560
|
+
return this.host.sitText;
|
|
561
|
+
}
|
|
562
|
+
/** The prim's llSetPayPrice arguments, null if unset. */
|
|
563
|
+
get payPrice() {
|
|
564
|
+
return this.host.payPrice;
|
|
565
|
+
}
|
|
286
566
|
/** Object description from llSetObjectDesc. */
|
|
287
567
|
get objectDesc() {
|
|
288
568
|
return this.state.appearance.description;
|
|
@@ -303,13 +583,17 @@ export class Script {
|
|
|
303
583
|
this.state.linksetData.set(k, { value: v.value, password: v.password ?? '' });
|
|
304
584
|
}
|
|
305
585
|
}
|
|
586
|
+
/** The script's memory limit in bytes, as llGetMemoryLimit reads it. */
|
|
587
|
+
get memoryLimit() {
|
|
588
|
+
return this.state.memory.limit;
|
|
589
|
+
}
|
|
306
590
|
/** True once `llDie()` has been called. Subsequent fire() calls are no-ops. */
|
|
307
591
|
get dead() {
|
|
308
592
|
return this.state.lifecycle.dead;
|
|
309
593
|
}
|
|
310
594
|
/**
|
|
311
|
-
* Match chat against this script's listens and queue `listen`
|
|
312
|
-
*
|
|
595
|
+
* Match chat against this script's listens and queue one `listen` event
|
|
596
|
+
* if any of them match; returns whether one did. Does not drain — caller does that. Used by Linkset.deliverChat
|
|
313
597
|
* for cross-script delivery.
|
|
314
598
|
*/
|
|
315
599
|
matchChatListens(opts) {
|
|
@@ -330,15 +614,16 @@ export class Script {
|
|
|
330
614
|
id: opts.key,
|
|
331
615
|
msg: opts.message,
|
|
332
616
|
});
|
|
617
|
+
return true;
|
|
333
618
|
}
|
|
619
|
+
return false;
|
|
334
620
|
}
|
|
335
621
|
/**
|
|
336
622
|
* Deliver chat to this script only. Linkset-wide delivery (every listening
|
|
337
623
|
* script in the linkset) is `linkset.deliverChat`.
|
|
338
624
|
*/
|
|
339
625
|
deliverChat(opts) {
|
|
340
|
-
this.matchChatListens(opts);
|
|
341
|
-
this.linkset.drainQueue();
|
|
626
|
+
this.linkset.settle(this.matchChatListens(opts) && this.handles('listen') ? [this] : []);
|
|
342
627
|
}
|
|
343
628
|
/**
|
|
344
629
|
* Override an `ll*` function for the lifetime of this Script. Replaces
|
|
@@ -375,7 +660,10 @@ export class Script {
|
|
|
375
660
|
*
|
|
376
661
|
* Synchronous: runs the handler to completion before returning, including
|
|
377
662
|
* any state transitions triggered by `state foo;` (state_exit / change /
|
|
378
|
-
* state_entry chain runs before fire() returns).
|
|
663
|
+
* state_entry chain runs before fire() returns). A handler that sleeps
|
|
664
|
+
* moves the clock to its wake time, running every other script's due
|
|
665
|
+
* events on the way. A script already sleeping handles the event after
|
|
666
|
+
* its current handler.
|
|
379
667
|
*
|
|
380
668
|
* If the current state has no handler for `eventName`, this is a no-op
|
|
381
669
|
* — matches LSL behavior of silently dropping unhandled events.
|
|
@@ -384,23 +672,26 @@ export class Script {
|
|
|
384
672
|
if (this.state.lifecycle.dead)
|
|
385
673
|
return;
|
|
386
674
|
if (!this.running) {
|
|
387
|
-
this.parkedEvents.push({ at: this.now, event: eventName, payload });
|
|
675
|
+
withCollisionFilter(this, eventName, payload, p => this.parkedEvents.push({ at: this.now, event: eventName, payload: p }));
|
|
388
676
|
return;
|
|
389
677
|
}
|
|
390
678
|
if (!this.started) {
|
|
391
679
|
this.started = true;
|
|
392
680
|
const entry = this.handlersByState.get(this.state.currentState)?.get('state_entry');
|
|
393
681
|
if (entry && eventName !== 'state_entry') {
|
|
394
|
-
this.
|
|
395
|
-
this.linkset.
|
|
682
|
+
this.begin(entry, [], {});
|
|
683
|
+
this.linkset.settle([this]);
|
|
396
684
|
}
|
|
397
685
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
686
|
+
withCollisionFilter(this, eventName, payload, p => {
|
|
687
|
+
const started = this.handles(eventName) ? [this] : [];
|
|
688
|
+
if (this.task || (DELAYED_EVENTS.has(eventName) && this.clockView.delayedUntilMs > this.now)) {
|
|
689
|
+
this.clockView.schedule(this.now, eventName, p);
|
|
690
|
+
}
|
|
691
|
+
else
|
|
692
|
+
this.deliver(eventName, p);
|
|
693
|
+
this.linkset.settle(started);
|
|
694
|
+
});
|
|
404
695
|
}
|
|
405
696
|
/**
|
|
406
697
|
* Run state_entry of the default state. If the script has been auto-started
|
|
@@ -411,38 +702,106 @@ export class Script {
|
|
|
411
702
|
return;
|
|
412
703
|
if (this.started)
|
|
413
704
|
return;
|
|
705
|
+
this.boot();
|
|
706
|
+
this.linkset.settle([this]);
|
|
707
|
+
}
|
|
708
|
+
/** Queue a fresh inventory script's first event without waiting for its handler. */
|
|
709
|
+
queueStart() {
|
|
710
|
+
if (this.started || this.dead)
|
|
711
|
+
return;
|
|
414
712
|
this.started = true;
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
713
|
+
this.clockView.schedule(this.now, 'state_entry');
|
|
714
|
+
}
|
|
715
|
+
/** Copy inventory code; fresh copies omit saved runtime state for remote loading. */
|
|
716
|
+
copyTo(host, opts = {}) {
|
|
717
|
+
const copy = new Script(this.ast, { ...this.options, host, coverage: false });
|
|
718
|
+
Object.assign(copy.mocks, this.mocks);
|
|
719
|
+
copy.state.coverage = this.state.coverage;
|
|
720
|
+
if (opts.fresh)
|
|
721
|
+
return copy;
|
|
722
|
+
for (const g of this.ast.globals) {
|
|
723
|
+
const value = this.global(g.name);
|
|
724
|
+
const saved = Array.isArray(value)
|
|
725
|
+
? typedList(structuredClone(value), listElementTypes(value) ?? [])
|
|
726
|
+
: structuredClone(value);
|
|
727
|
+
copy.setGlobal(g.name, saved);
|
|
728
|
+
}
|
|
729
|
+
copy.state.memory.limit = this.state.memory.limit;
|
|
730
|
+
copy.state.currentState = this.state.currentState;
|
|
731
|
+
copy.started = this.started;
|
|
732
|
+
copy.running = this.running && !this.dead;
|
|
733
|
+
Object.assign(copy.state.random, this.state.random);
|
|
734
|
+
copy.state.listens.push(...structuredClone(this.state.listens));
|
|
735
|
+
copy.state.listenHandleCounter = this.state.listenHandleCounter;
|
|
736
|
+
copy.clockView.timerIntervalMs = this.clockView.timerIntervalMs;
|
|
737
|
+
copy.clockView.timerNextFireMs = copy.now + Math.max(0, this.clockView.timerNextFireMs - this.now);
|
|
738
|
+
copy.clockView.timeReferenceMs = copy.now - (this.now - this.clockView.timeReferenceMs);
|
|
739
|
+
copy.clockView.minEventDelayMs = this.clockView.minEventDelayMs;
|
|
740
|
+
for (const kind of ['move', 'look']) {
|
|
741
|
+
const request = host.linkset.prims[0].targetMotion[kind];
|
|
742
|
+
if (request?.owner.prim === this.host.key && request.owner.script === this.scriptName)
|
|
743
|
+
request.owner.prim = host.key;
|
|
744
|
+
}
|
|
745
|
+
return copy;
|
|
746
|
+
}
|
|
747
|
+
/** Queue rez lifecycle events after restoring the inventory script's saved data. */
|
|
748
|
+
queueRez() {
|
|
749
|
+
if (!this.running)
|
|
750
|
+
return;
|
|
751
|
+
if (!this.started) {
|
|
752
|
+
this.started = true;
|
|
753
|
+
this.clockView.schedule(this.now, 'state_entry');
|
|
418
754
|
}
|
|
419
|
-
this.
|
|
755
|
+
this.clockView.schedule(this.now, 'on_rez', { start_param: this.startParameter });
|
|
756
|
+
}
|
|
757
|
+
boot() {
|
|
758
|
+
this.started = true;
|
|
759
|
+
const entry = this.handlersByState.get('default')?.get('state_entry');
|
|
760
|
+
if (entry)
|
|
761
|
+
this.begin(entry, [], {});
|
|
420
762
|
}
|
|
421
763
|
/**
|
|
422
764
|
* Reset the script as if `llResetScript` had been called: clear globals,
|
|
423
765
|
* reseed them from the AST initializers, return to the default state,
|
|
424
766
|
* cancel timers, and run state_entry. Used internally when llResetScript
|
|
425
767
|
* is invoked from inside a handler; tests can also call it directly.
|
|
768
|
+
* A sleeping handler is abandoned.
|
|
426
769
|
*/
|
|
427
770
|
reset() {
|
|
771
|
+
this.restart();
|
|
772
|
+
this.linkset.settle([this]);
|
|
773
|
+
}
|
|
774
|
+
restart() {
|
|
775
|
+
this.abandon();
|
|
428
776
|
this.state.memory.releaseStack();
|
|
429
777
|
this.globals.clear();
|
|
430
778
|
try {
|
|
431
779
|
initGlobals(this.globals, this.ast.globals);
|
|
432
780
|
}
|
|
433
781
|
catch (e) {
|
|
434
|
-
if (e instanceof
|
|
782
|
+
if (e instanceof RuntimeError)
|
|
435
783
|
this.crash(e);
|
|
436
784
|
throw e;
|
|
437
785
|
}
|
|
438
786
|
this.state.currentState = 'default';
|
|
439
787
|
this.state.lifecycle.dead = false;
|
|
788
|
+
this.state.lifecycle.dying = false;
|
|
789
|
+
this.state.lifecycle.removed = false;
|
|
440
790
|
this.state.listens.length = 0;
|
|
791
|
+
this.state.urls.length = 0;
|
|
441
792
|
this.state.permissions.granted = 0;
|
|
442
793
|
this.state.permissions.agent = NULL_KEY;
|
|
443
794
|
for (const r of this.state.permissions.requests)
|
|
444
795
|
r.answered = true;
|
|
796
|
+
for (const r of this.state.experienceRequests)
|
|
797
|
+
r.answered = true;
|
|
798
|
+
this.state.controls.taken = null;
|
|
799
|
+
stopSensors(this);
|
|
800
|
+
clearTargets(this);
|
|
801
|
+
clearTargetMotion(this);
|
|
445
802
|
this.clockView.cancelTimer();
|
|
803
|
+
this.clockView.setMinEventDelay(0);
|
|
804
|
+
this.startParameter = 0;
|
|
446
805
|
this.clockView.resetReference();
|
|
447
806
|
this.linkset.clock.purgeTarget(this);
|
|
448
807
|
this.parkedEvents.length = 0;
|
|
@@ -454,10 +813,9 @@ export class Script {
|
|
|
454
813
|
if (this.state.coverage)
|
|
455
814
|
this.state.coverage.hitState('default');
|
|
456
815
|
// Per LSL: if the script is stopped, reset just clears state — no
|
|
457
|
-
// state_entry runs until it's restarted.
|
|
458
|
-
// state_entry now via start().
|
|
816
|
+
// state_entry runs until it's restarted.
|
|
459
817
|
if (this.running)
|
|
460
|
-
this.
|
|
818
|
+
this.boot();
|
|
461
819
|
}
|
|
462
820
|
/**
|
|
463
821
|
* Internal: invoked by `Linkset.drainQueue` to deliver one queued event.
|
|
@@ -468,33 +826,64 @@ export class Script {
|
|
|
468
826
|
if (this.state.lifecycle.dead)
|
|
469
827
|
return;
|
|
470
828
|
if (event === '__reset') {
|
|
471
|
-
this.
|
|
829
|
+
this.restart();
|
|
472
830
|
return;
|
|
473
831
|
}
|
|
474
832
|
const handler = this.handlersByState.get(this.state.currentState)?.get(event);
|
|
475
833
|
if (!handler)
|
|
476
834
|
return;
|
|
477
|
-
|
|
478
|
-
|
|
835
|
+
if (DELAYED_EVENTS.has(event))
|
|
836
|
+
this.clockView.delayedUntilMs = this.now + this.clockView.minEventDelayMs;
|
|
837
|
+
this.begin(handler, bindPayload(event, payload), payload);
|
|
479
838
|
}
|
|
480
|
-
/**
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
839
|
+
/** Whether the current state has a handler for `event`. */
|
|
840
|
+
handles(event) {
|
|
841
|
+
return this.handlersByState.get(this.state.currentState).has(event);
|
|
842
|
+
}
|
|
843
|
+
/** Virtual time a sleeping handler wakes at, or undefined when the script is idle. */
|
|
844
|
+
get wakeAt() {
|
|
845
|
+
return this.task?.wakeAt;
|
|
846
|
+
}
|
|
847
|
+
/** Internal: invoked by `Linkset.drainQueue` when a sleeping handler's wake time comes. */
|
|
848
|
+
wake() {
|
|
849
|
+
const task = this.task;
|
|
850
|
+
if (!task)
|
|
489
851
|
return;
|
|
490
|
-
|
|
491
|
-
this.
|
|
852
|
+
this.task = null;
|
|
853
|
+
this.step(task);
|
|
854
|
+
}
|
|
855
|
+
begin(handler, args, payload) {
|
|
856
|
+
this.step({ run: this.runHandler(handler, args, payload), startedAt: this.now, wakeAt: 0 });
|
|
857
|
+
}
|
|
858
|
+
step(task) {
|
|
859
|
+
this.resumedAt = this.now;
|
|
860
|
+
let r;
|
|
492
861
|
try {
|
|
493
|
-
|
|
862
|
+
r = task.run.next();
|
|
494
863
|
}
|
|
495
|
-
|
|
496
|
-
this.
|
|
864
|
+
catch (e) {
|
|
865
|
+
this.finish(task);
|
|
866
|
+
throw e;
|
|
867
|
+
}
|
|
868
|
+
if (!r.done) {
|
|
869
|
+
this.task = { ...task, wakeAt: r.value.until };
|
|
870
|
+
return;
|
|
497
871
|
}
|
|
872
|
+
this.finish(task);
|
|
873
|
+
this.clockView.finishHandler(r.value);
|
|
874
|
+
if (r.value)
|
|
875
|
+
this.restart();
|
|
876
|
+
}
|
|
877
|
+
finish(task) {
|
|
878
|
+
if (this.now === task.startedAt)
|
|
879
|
+
return;
|
|
880
|
+
this.clockView.busyUntilMs = this.now;
|
|
881
|
+
this.linkset.clock.requeue(this);
|
|
882
|
+
}
|
|
883
|
+
abandon() {
|
|
884
|
+
const task = this.task;
|
|
885
|
+
this.task = null;
|
|
886
|
+
task?.run.return(false);
|
|
498
887
|
}
|
|
499
888
|
/**
|
|
500
889
|
* Run a handler and process any state-change signal it raises.
|
|
@@ -505,34 +894,43 @@ export class Script {
|
|
|
505
894
|
* with a small loop so that a `state_exit` or `state_entry` that itself
|
|
506
895
|
* does `state foo;` continues the chain correctly.
|
|
507
896
|
*/
|
|
508
|
-
runHandler(handler, args) {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
897
|
+
*runHandler(handler, args, payload) {
|
|
898
|
+
this.state.gameControlSnapshot = handler.name === 'game_control'
|
|
899
|
+
? payload['gameControlSnapshot'] ?? null : null;
|
|
900
|
+
enterDamageEvent(payload);
|
|
901
|
+
const detected = payload['detected'];
|
|
902
|
+
const pushed = Array.isArray(detected) && detected.length > 0;
|
|
903
|
+
if (pushed)
|
|
904
|
+
this.state.detectedStack.push({
|
|
905
|
+
entries: detected, event: handler.name,
|
|
906
|
+
});
|
|
512
907
|
try {
|
|
513
908
|
this.state.memory.startEvent();
|
|
514
|
-
this.runHandlerChain(handler, args);
|
|
909
|
+
return yield* this.runHandlerChain(handler, args);
|
|
515
910
|
}
|
|
516
911
|
catch (e) {
|
|
517
|
-
if (e instanceof
|
|
912
|
+
if (e instanceof RuntimeError)
|
|
518
913
|
this.crash(e);
|
|
519
914
|
throw e;
|
|
520
915
|
}
|
|
521
916
|
finally {
|
|
522
|
-
if (
|
|
523
|
-
this.
|
|
917
|
+
if (pushed)
|
|
918
|
+
this.state.detectedStack.pop();
|
|
919
|
+
this.state.gameControlSnapshot = null;
|
|
920
|
+
leaveDamageEvent(payload);
|
|
524
921
|
}
|
|
525
922
|
}
|
|
526
|
-
|
|
923
|
+
/** Returns true when the chain ended in llResetScript. */
|
|
924
|
+
*runHandlerChain(handler, args) {
|
|
527
925
|
const ctx = {
|
|
528
926
|
state: this.state,
|
|
529
927
|
mocks: this.mocks,
|
|
530
928
|
globals: this.globals,
|
|
531
929
|
userFunctions: this.userFunctions,
|
|
532
930
|
functions: this.functions,
|
|
931
|
+
invalidPrograms: this.invalidPrograms,
|
|
533
932
|
script: this,
|
|
534
933
|
prim: this.host,
|
|
535
|
-
linkset: this.linkset,
|
|
536
934
|
};
|
|
537
935
|
let pending = {
|
|
538
936
|
handler,
|
|
@@ -542,28 +940,30 @@ export class Script {
|
|
|
542
940
|
const { handler: h, args: a } = pending;
|
|
543
941
|
pending = null;
|
|
544
942
|
try {
|
|
545
|
-
execHandler(ctx, h, a);
|
|
943
|
+
yield* execHandler(ctx, h, a);
|
|
546
944
|
}
|
|
547
945
|
catch (e) {
|
|
548
|
-
if (e instanceof ResetScriptSignal)
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
if (e instanceof ScriptRemovedSignal)
|
|
553
|
-
return;
|
|
946
|
+
if (e instanceof ResetScriptSignal)
|
|
947
|
+
return true;
|
|
948
|
+
if (e instanceof HaltSignal)
|
|
949
|
+
return false;
|
|
554
950
|
if (!(e instanceof StateChangeSignal))
|
|
555
951
|
throw e;
|
|
952
|
+
this.state.gameControlSnapshot = null;
|
|
953
|
+
this.clearDamageAdjustment();
|
|
954
|
+
if (this.state.lifecycle.removed)
|
|
955
|
+
return false;
|
|
556
956
|
const target = e.target;
|
|
557
957
|
if (target === this.state.currentState || this.state.lifecycle.dead)
|
|
558
958
|
continue;
|
|
559
959
|
const exit = this.handlersByState.get(this.state.currentState)?.get('state_exit');
|
|
560
960
|
if (exit) {
|
|
561
961
|
try {
|
|
562
|
-
execHandler(ctx, exit, []);
|
|
962
|
+
yield* execHandler(ctx, exit, []);
|
|
563
963
|
}
|
|
564
964
|
catch (e2) {
|
|
565
|
-
if (e2 instanceof
|
|
566
|
-
return;
|
|
965
|
+
if (e2 instanceof HaltSignal)
|
|
966
|
+
return false;
|
|
567
967
|
if (e2 instanceof StateChangeSignal) {
|
|
568
968
|
// Discard further changes inside state_exit per LSL convention.
|
|
569
969
|
}
|
|
@@ -575,6 +975,7 @@ export class Script {
|
|
|
575
975
|
if (!this.handlersByState.has(target)) {
|
|
576
976
|
throw new Error(`unknown state '${target}' in state change`);
|
|
577
977
|
}
|
|
978
|
+
stopSensors(this);
|
|
578
979
|
this.state.listens.length = 0;
|
|
579
980
|
this.linkset.clock.purgeTarget(this, this.now, '__reset');
|
|
580
981
|
this.state.currentState = target;
|
|
@@ -586,6 +987,7 @@ export class Script {
|
|
|
586
987
|
}
|
|
587
988
|
}
|
|
588
989
|
}
|
|
990
|
+
return false;
|
|
589
991
|
}
|
|
590
992
|
/** Stop the script after a fatal run-time error, reporting it on DEBUG_CHANNEL. */
|
|
591
993
|
crash(error) {
|
|
@@ -604,7 +1006,12 @@ export class Script {
|
|
|
604
1006
|
*/
|
|
605
1007
|
markDead() {
|
|
606
1008
|
this.state.lifecycle.dead = true;
|
|
1009
|
+
this.state.urls.length = 0;
|
|
1010
|
+
stopSensors(this);
|
|
1011
|
+
clearTargets(this);
|
|
1012
|
+
clearTargetMotion(this);
|
|
607
1013
|
this.clockView.cancelTimer();
|
|
1014
|
+
this.abandon();
|
|
608
1015
|
}
|
|
609
1016
|
/**
|
|
610
1017
|
* Replay any events parked while this script was disabled. Caller is
|