sci2-ts 0.8.0 → 0.8.2
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/CHANGELOG.md +16 -0
- package/lib/996.sc +14 -11
- package/lib/997.sc +5 -3
- package/lib/998.sc +11 -3
- package/package.json +1 -1
- package/packages/sci/src/script/compiler.js +21 -0
- package/packages/sci/src/script/compiler.ts +18 -0
- package/packages/sci/src/vm/kernels/audio.js +4 -0
- package/packages/sci/src/vm/kernels/audio.ts +3 -0
- package/packages/sci/src/vm/kernels/graphics.js +11 -3
- package/packages/sci/src/vm/kernels/graphics.ts +9 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ All notable changes to this project. Versions follow [Semantic Versioning](https
|
|
|
4
4
|
and the entries are generated from commit messages
|
|
5
5
|
([Conventional Commits](https://www.conventionalcommits.org/)) when a release is made.
|
|
6
6
|
|
|
7
|
+
## [0.8.2](https://github.com/rlueder/sci-ts/compare/v0.8.1...v0.8.2) (2026-10-02)
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
* **lib:** actors walk past each other and are clicked only where they're drawn ([#10](https://github.com/rlueder/sci-ts/issues/10)) ([99deb4a](https://github.com/rlueder/sci-ts/commit/99deb4a1e63bb5170ab0742942041ce3fde1801d))
|
|
12
|
+
|
|
13
|
+
## [0.8.1](https://github.com/rlueder/sci-ts/compare/v0.8.0...v0.8.1) (2026-10-02)
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
* timers past 32767 cycles, waits on missing sounds, methods hidden by properties ([#9](https://github.com/rlueder/sci-ts/issues/9)) ([7ec09d7](https://github.com/rlueder/sci-ts/commit/7ec09d765ab2818cf0171d5e96d9fa1b6744b576))
|
|
18
|
+
|
|
19
|
+
### Documentation
|
|
20
|
+
|
|
21
|
+
* AGENTS.md for people and agents working here ([#8](https://github.com/rlueder/sci-ts/issues/8)) ([42bec1f](https://github.com/rlueder/sci-ts/commit/42bec1fafbb2798b26539923608882fef0c5118d))
|
|
22
|
+
|
|
7
23
|
## [0.8.0](https://github.com/rlueder/sci-ts/compare/v0.7.0...v0.8.0) (2026-10-02)
|
|
8
24
|
|
|
9
25
|
### Added
|
package/lib/996.sc
CHANGED
|
@@ -100,7 +100,8 @@
|
|
|
100
100
|
(properties
|
|
101
101
|
caller 0
|
|
102
102
|
box 0
|
|
103
|
-
|
|
103
|
+
shownAt 0 ; the game time it went up
|
|
104
|
+
readFor 0 ; how long it stays (-1: until dismissed)
|
|
104
105
|
font -1 ; -1: the textStyle's
|
|
105
106
|
x -1 ; -1: centred
|
|
106
107
|
y 16
|
|
@@ -120,19 +121,21 @@
|
|
|
120
121
|
yourself:))
|
|
121
122
|
(box init:)
|
|
122
123
|
(= talking self)
|
|
123
|
-
(=
|
|
124
|
+
(= shownAt gameTime)
|
|
125
|
+
(= readFor (self readingTime: (String STRING_LENGTH txt))))
|
|
124
126
|
|
|
125
|
-
;;
|
|
126
|
-
;;
|
|
127
|
+
;; Cycles to read a line of n characters: normally two seconds, and more for longer lines;
|
|
128
|
+
;; -1 is until the player dismisses it.
|
|
127
129
|
(method (readingTime n)
|
|
128
130
|
(switch textSpeed
|
|
129
|
-
(TEXT_SLOW (return (+
|
|
130
|
-
(TEXT_FAST (return (+
|
|
131
|
+
(TEXT_SLOW (return (+ 180 (* 5 n))))
|
|
132
|
+
(TEXT_FAST (return (+ 70 (* 2 n))))
|
|
131
133
|
(TEXT_CLICK (return -1))
|
|
132
|
-
(else (return (+
|
|
134
|
+
(else (return (+ 120 (* 3 n))))))
|
|
133
135
|
|
|
136
|
+
;; Time is measured as cycles since, which stays right when gameTime wraps past 32767.
|
|
134
137
|
(method (doit)
|
|
135
|
-
(if (and box (!=
|
|
138
|
+
(if (and box (!= readFor -1) (>= (- gameTime shownAt) readFor)) (self done:)))
|
|
136
139
|
|
|
137
140
|
;; A click, Enter, Space or "." while a line is up dismisses it (and nothing else).
|
|
138
141
|
(method (handleEvent event)
|
|
@@ -174,7 +177,7 @@
|
|
|
174
177
|
(properties
|
|
175
178
|
mouth 0 bust 0 eyes 0 frame 0
|
|
176
179
|
priority 150
|
|
177
|
-
|
|
180
|
+
mouthFor 0) ; cycles the mouth moves for
|
|
178
181
|
|
|
179
182
|
(method (init theMouth theBust theEyes theFrame)
|
|
180
183
|
(= mouth theMouth)
|
|
@@ -198,10 +201,10 @@
|
|
|
198
201
|
(if mouth
|
|
199
202
|
(mouth setCycle: Forward)
|
|
200
203
|
;; About as long as it takes to say: half a second, and two cycles a letter.
|
|
201
|
-
(=
|
|
204
|
+
(= mouthFor (+ 30 (* 2 (String STRING_LENGTH txt))))))
|
|
202
205
|
|
|
203
206
|
(method (doit)
|
|
204
|
-
(if (and mouth (mouth cycler?) (>= gameTime
|
|
207
|
+
(if (and mouth (mouth cycler?) (>= (- gameTime shownAt) mouthFor))
|
|
205
208
|
(mouth setCycle: 0 setCel: 0))
|
|
206
209
|
(super doit:))
|
|
207
210
|
|
package/lib/997.sc
CHANGED
|
@@ -173,18 +173,20 @@
|
|
|
173
173
|
;; (shut) and back to 0.
|
|
174
174
|
(class Blink of Cycle
|
|
175
175
|
(properties
|
|
176
|
-
|
|
176
|
+
restSince 0
|
|
177
|
+
restFor 0)
|
|
177
178
|
|
|
178
179
|
(method (init who)
|
|
179
180
|
(super init: who)
|
|
180
181
|
(self rest:))
|
|
181
182
|
|
|
182
183
|
(method (rest)
|
|
183
|
-
(=
|
|
184
|
+
(= restSince gameTime)
|
|
185
|
+
(= restFor (+ 90 (Random 0 90))))
|
|
184
186
|
|
|
185
187
|
(method (doit &tmp c)
|
|
186
188
|
(if (== (client cel?) 0)
|
|
187
|
-
(if (and (>= gameTime
|
|
189
|
+
(if (and (>= (- gameTime restSince) restFor) (> (self lastCel:) 0))
|
|
188
190
|
(= cycleCnt gameTime)
|
|
189
191
|
(client cel: 1))
|
|
190
192
|
else
|
package/lib/998.sc
CHANGED
|
@@ -208,10 +208,13 @@
|
|
|
208
208
|
(curRoom doVerb: verb))))
|
|
209
209
|
|
|
210
210
|
;; A Feature drawn with a view: a screen item in the room's plane. One that's only there to
|
|
211
|
-
;; look at (rain on a window, a sky) has clickable 0, and clicks go to what's behind it.
|
|
211
|
+
;; look at (rain on a window, a sky) has clickable 0, and clicks go to what's behind it. With
|
|
212
|
+
;; checkPixels, it's hit only where it's drawn, not anywhere in its cel (Actors: people are
|
|
213
|
+
;; often drawn on canvases much wider than they are).
|
|
212
214
|
(class View of Feature
|
|
213
215
|
(properties
|
|
214
216
|
clickable 1
|
|
217
|
+
checkPixels 0
|
|
215
218
|
view -1 loop 0 cel 0
|
|
216
219
|
z 0
|
|
217
220
|
priority 0 fixPriority 0
|
|
@@ -232,7 +235,7 @@
|
|
|
232
235
|
(if (not (& signal SIG_HIDDEN)) (UpdateScreenItem self)))
|
|
233
236
|
|
|
234
237
|
(method (onMe theX theY)
|
|
235
|
-
(return (and clickable (not (& signal SIG_HIDDEN)) (IsOnMe theX theY self))))
|
|
238
|
+
(return (and clickable (not (& signal SIG_HIDDEN)) (IsOnMe theX theY self checkPixels))))
|
|
236
239
|
|
|
237
240
|
(method (hide)
|
|
238
241
|
(|= signal SIG_HIDDEN)
|
|
@@ -315,9 +318,13 @@
|
|
|
315
318
|
(if script (script dispose:))
|
|
316
319
|
(super dispose:)))
|
|
317
320
|
|
|
318
|
-
;; A Prop that moves (a mover), facing where it goes.
|
|
321
|
+
;; A Prop that moves (a mover), facing where it goes. Actors walk through each other unless
|
|
322
|
+
;; ignoreActors is 0: the interpreter measures an actor by its whole cel, and characters are
|
|
323
|
+
;; often drawn on canvases much wider than they are, so blocking would stop them far apart.
|
|
319
324
|
(class Actor of Prop
|
|
320
325
|
(properties
|
|
326
|
+
ignoreActors 1
|
|
327
|
+
checkPixels 1
|
|
321
328
|
mover 0
|
|
322
329
|
moveSpeed 2 ; cycles between steps
|
|
323
330
|
heading 0
|
|
@@ -359,6 +366,7 @@
|
|
|
359
366
|
|
|
360
367
|
;; Whether another actor is in the way (DoBresen asks).
|
|
361
368
|
(method (cantBeHere)
|
|
369
|
+
(if ignoreActors (return 0))
|
|
362
370
|
(return (CantBeHere self (cast elements?))))
|
|
363
371
|
|
|
364
372
|
(method (dispose)
|
package/package.json
CHANGED
|
@@ -95,6 +95,27 @@ export function compileScripts(sources, ctx) {
|
|
|
95
95
|
defined.add(m.name);
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
+
// A method named like a property of its object: `x:` sets the property and the method
|
|
99
|
+
// never runs.
|
|
100
|
+
const propertiesOf = (name, seen = new Set()) => {
|
|
101
|
+
if (!name || seen.has(name))
|
|
102
|
+
return new Set(OBJECT_HEADER);
|
|
103
|
+
seen.add(name);
|
|
104
|
+
const c = classes.get(name);
|
|
105
|
+
if (c)
|
|
106
|
+
return new Set([...c.obj.props.map((p) => p.name), ...propertiesOf(c.obj.superName, seen)]);
|
|
107
|
+
return new Set([...OBJECT_HEADER, ...(ctx.externalClass?.(name)?.properties.map((p) => p.name) ?? [])]);
|
|
108
|
+
};
|
|
109
|
+
for (const u of units) {
|
|
110
|
+
for (const o of u.objects) {
|
|
111
|
+
const props = new Set([...o.props.map((p) => p.name), ...propertiesOf(o.superName)]);
|
|
112
|
+
for (const m of o.methods) {
|
|
113
|
+
if (!props.has(m.name))
|
|
114
|
+
continue;
|
|
115
|
+
ctx.onWarning({ message: `method ${m.name}: ${o.name} has a property called ${m.name}, so ${m.name}: sets it and this never runs`, file: u.file, line: m.line });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
98
119
|
const reported = new Set();
|
|
99
120
|
for (const s of project.sent) {
|
|
100
121
|
if (defined.has(s.name) || reported.has(s.name))
|
|
@@ -195,6 +195,24 @@ export function compileScripts(sources: readonly CompileSource[], ctx: CompileCo
|
|
|
195
195
|
for (const m of o.methods) defined.add(m.name);
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
|
+
// A method named like a property of its object: `x:` sets the property and the method
|
|
199
|
+
// never runs.
|
|
200
|
+
const propertiesOf = (name: string | undefined, seen = new Set<string>()): Set<string> => {
|
|
201
|
+
if (!name || seen.has(name)) return new Set(OBJECT_HEADER);
|
|
202
|
+
seen.add(name);
|
|
203
|
+
const c = classes.get(name);
|
|
204
|
+
if (c) return new Set([...c.obj.props.map((p) => p.name), ...propertiesOf(c.obj.superName, seen)]);
|
|
205
|
+
return new Set([...OBJECT_HEADER, ...(ctx.externalClass?.(name)?.properties.map((p) => p.name) ?? [])]);
|
|
206
|
+
};
|
|
207
|
+
for (const u of units) {
|
|
208
|
+
for (const o of u.objects) {
|
|
209
|
+
const props = new Set([...o.props.map((p) => p.name), ...propertiesOf(o.superName)]);
|
|
210
|
+
for (const m of o.methods) {
|
|
211
|
+
if (!props.has(m.name)) continue;
|
|
212
|
+
ctx.onWarning({ message: `method ${m.name}: ${o.name} has a property called ${m.name}, so ${m.name}: sets it and this never runs`, file: u.file, line: m.line });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
198
216
|
const reported = new Set<string>();
|
|
199
217
|
for (const s of project.sent) {
|
|
200
218
|
if (defined.has(s.name) || reported.has(s.name)) continue;
|
|
@@ -266,6 +266,10 @@ export const audioKernels = {
|
|
|
266
266
|
a.play(soundKey(obj), clip, loop, volume);
|
|
267
267
|
else
|
|
268
268
|
a.startSong(obj, number, loop, volume);
|
|
269
|
+
// Neither an effect nor a song by that number: it has ended already, so the next
|
|
270
|
+
// update reports it and whoever waits for it carries on.
|
|
271
|
+
if (!clip && !a.songs.has(obj))
|
|
272
|
+
vm.setProp(obj, "signal", NOT_PLAYING);
|
|
269
273
|
return 0;
|
|
270
274
|
}
|
|
271
275
|
case 9: // stop
|
|
@@ -274,6 +274,9 @@ export const audioKernels: Record<string, KernelFn> = {
|
|
|
274
274
|
const clip = a.index.effects.get(number);
|
|
275
275
|
if (clip) a.play(soundKey(obj), clip, loop, volume);
|
|
276
276
|
else a.startSong(obj, number, loop, volume);
|
|
277
|
+
// Neither an effect nor a song by that number: it has ended already, so the next
|
|
278
|
+
// update reports it and whoever waits for it carries on.
|
|
279
|
+
if (!clip && !a.songs.has(obj)) vm.setProp(obj, "signal", NOT_PLAYING);
|
|
277
280
|
return 0;
|
|
278
281
|
}
|
|
279
282
|
case 9: // stop
|
|
@@ -409,14 +409,22 @@ export const graphicsKernels = {
|
|
|
409
409
|
vm.setProp(obj, "brBottom", fromInt(bottom));
|
|
410
410
|
vm.setProp(obj, "brTop", fromInt(bottom - g.prop(obj, "yStep")));
|
|
411
411
|
},
|
|
412
|
-
IsOnMe
|
|
412
|
+
// IsOnMe(x, y, obj, checkPixels): with checkPixels, only where the cel isn't transparent.
|
|
413
|
+
IsOnMe: (vm, [x = 0, y = 0, obj = 0, checkPixels = 0]) => {
|
|
413
414
|
const g = graphics(vm);
|
|
414
415
|
const px = toSigned(x), py = toSigned(y);
|
|
415
416
|
// Like SSCI, a screen item is hit where its cel is actually drawn (scaled, current
|
|
416
417
|
// position): its ns* props can be stale, since only SetNowSeen updates them.
|
|
417
418
|
const r = g.items.has(obj) ? g.viewRect(obj) : undefined;
|
|
418
|
-
if (r)
|
|
419
|
-
|
|
419
|
+
if (r) {
|
|
420
|
+
if (!(px >= r.x && px < r.x + r.width && py >= r.y && py < r.y + r.height))
|
|
421
|
+
return 0;
|
|
422
|
+
if (!checkPixels)
|
|
423
|
+
return 1;
|
|
424
|
+
const cx = Math.min(r.cel.width - 1, Math.floor((px - r.x) / r.sx));
|
|
425
|
+
const cy = Math.min(r.cel.height - 1, Math.floor((py - r.y) / r.sy));
|
|
426
|
+
return bool(r.cel.pixels[cy * r.cel.width + (r.mirror ? r.cel.width - 1 - cx : cx)] !== r.cel.skipColor);
|
|
427
|
+
}
|
|
420
428
|
return bool(px >= g.prop(obj, "nsLeft") && px <= g.prop(obj, "nsRight") && py >= g.prop(obj, "nsTop") && py <= g.prop(obj, "nsBottom"));
|
|
421
429
|
},
|
|
422
430
|
IsHiRes: () => 0,
|
|
@@ -448,13 +448,20 @@ export const graphicsKernels: Record<string, KernelFn> = {
|
|
|
448
448
|
vm.setProp(obj, "brBottom", fromInt(bottom));
|
|
449
449
|
vm.setProp(obj, "brTop", fromInt(bottom - g.prop(obj, "yStep")));
|
|
450
450
|
},
|
|
451
|
-
IsOnMe
|
|
451
|
+
// IsOnMe(x, y, obj, checkPixels): with checkPixels, only where the cel isn't transparent.
|
|
452
|
+
IsOnMe: (vm, [x = 0, y = 0, obj = 0, checkPixels = 0]) => {
|
|
452
453
|
const g = graphics(vm);
|
|
453
454
|
const px = toSigned(x), py = toSigned(y);
|
|
454
455
|
// Like SSCI, a screen item is hit where its cel is actually drawn (scaled, current
|
|
455
456
|
// position): its ns* props can be stale, since only SetNowSeen updates them.
|
|
456
457
|
const r = g.items.has(obj) ? g.viewRect(obj) : undefined;
|
|
457
|
-
if (r)
|
|
458
|
+
if (r) {
|
|
459
|
+
if (!(px >= r.x && px < r.x + r.width && py >= r.y && py < r.y + r.height)) return 0;
|
|
460
|
+
if (!checkPixels) return 1;
|
|
461
|
+
const cx = Math.min(r.cel.width - 1, Math.floor((px - r.x) / r.sx));
|
|
462
|
+
const cy = Math.min(r.cel.height - 1, Math.floor((py - r.y) / r.sy));
|
|
463
|
+
return bool(r.cel.pixels[cy * r.cel.width + (r.mirror ? r.cel.width - 1 - cx : cx)] !== r.cel.skipColor);
|
|
464
|
+
}
|
|
458
465
|
return bool(px >= g.prop(obj, "nsLeft") && px <= g.prop(obj, "nsRight") && py >= g.prop(obj, "nsTop") && py <= g.prop(obj, "nsBottom"));
|
|
459
466
|
},
|
|
460
467
|
IsHiRes: () => 0,
|