sci2-ts 0.6.1 → 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/CHANGELOG.md +12 -0
- package/lib/992.sc +17 -1
- package/lib/996.sc +19 -7
- package/lib/998.sc +44 -4
- package/lib/999.sc +2 -1
- package/lib/system.sh +11 -0
- package/package.json +1 -1
- package/packages/content/room.schema.json +5 -0
- package/packages/content/src/room.d.ts +2 -0
- package/packages/content/src/room.js +18 -3
- package/packages/content/src/room.ts +18 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@ 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.0](https://github.com/rlueder/sci-ts/compare/v0.7.0...v0.8.0) (2026-10-02)
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
* **content:** poses, idles and prop behaviours ([#7](https://github.com/rlueder/sci-ts/issues/7)) ([1413ca3](https://github.com/rlueder/sci-ts/commit/1413ca334a29255d9a67ca136f01607187e7bee6))
|
|
12
|
+
|
|
13
|
+
## [0.7.0](https://github.com/rlueder/sci-ts/compare/v0.6.1...v0.7.0) (2026-10-01)
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
* **lib:** text speed, and dismissing a line from the keyboard ([#6](https://github.com/rlueder/sci-ts/issues/6)) ([3ab244b](https://github.com/rlueder/sci-ts/commit/3ab244b0d96a34dd0c3e7d13b1f52404fdceccfe))
|
|
18
|
+
|
|
7
19
|
## [0.6.1](https://github.com/rlueder/sci-ts/compare/v0.6.0...v0.6.1) (2026-10-01)
|
|
8
20
|
|
|
9
21
|
### Fixed
|
package/lib/992.sc
CHANGED
|
@@ -58,9 +58,17 @@
|
|
|
58
58
|
add: SR_SAVE "Save the game"
|
|
59
59
|
add: SR_RESTORE "Restore a game"
|
|
60
60
|
add: SR_RESTART "Start again"
|
|
61
|
+
add: SR_TEXT (String STRING_FORMAT "Text speed: %s" (self textSpeedName:))
|
|
61
62
|
add: 0 "Carry on"
|
|
62
63
|
show: self))
|
|
63
64
|
|
|
65
|
+
(method (textSpeedName)
|
|
66
|
+
(switch textSpeed
|
|
67
|
+
(TEXT_SLOW (return "slow"))
|
|
68
|
+
(TEXT_FAST (return "fast"))
|
|
69
|
+
(TEXT_CLICK (return "until I click"))
|
|
70
|
+
(else (return "normal"))))
|
|
71
|
+
|
|
64
72
|
;; The saves there are now.
|
|
65
73
|
(method (load)
|
|
66
74
|
(self release:)
|
|
@@ -117,7 +125,15 @@
|
|
|
117
125
|
(switch v
|
|
118
126
|
(SR_SAVE (self save:))
|
|
119
127
|
(SR_RESTORE (self restore:))
|
|
120
|
-
(SR_RESTART (RestartGame))
|
|
128
|
+
(SR_RESTART (RestartGame))
|
|
129
|
+
;; The next speed, slowest first; the menu again, showing it.
|
|
130
|
+
(SR_TEXT
|
|
131
|
+
(switch textSpeed
|
|
132
|
+
(TEXT_SLOW (= textSpeed TEXT_NORMAL))
|
|
133
|
+
(TEXT_NORMAL (= textSpeed TEXT_FAST))
|
|
134
|
+
(TEXT_FAST (= textSpeed TEXT_CLICK))
|
|
135
|
+
(else (= textSpeed TEXT_SLOW)))
|
|
136
|
+
(self showMenu:))))
|
|
121
137
|
(SR_SAVE (if v (self askDescription: (- v 1))))
|
|
122
138
|
(SR_RESTORE
|
|
123
139
|
(if v
|
package/lib/996.sc
CHANGED
|
@@ -93,8 +93,9 @@
|
|
|
93
93
|
(if bitmap (Bitmap BITMAP_DISPOSE bitmap) (= bitmap 0))
|
|
94
94
|
(super dispose:)))
|
|
95
95
|
|
|
96
|
-
;; Shows a line until it's been there long enough to read
|
|
97
|
-
;;
|
|
96
|
+
;; Shows a line until it's been there long enough to read (as long as `textSpeed` says), or
|
|
97
|
+
;; the player clicks or presses Enter, Space or "."; then cues whoever asked. The narrator
|
|
98
|
+
;; speaks for no one in particular.
|
|
98
99
|
(class Narrator of Obj
|
|
99
100
|
(properties
|
|
100
101
|
caller 0
|
|
@@ -119,15 +120,26 @@
|
|
|
119
120
|
yourself:))
|
|
120
121
|
(box init:)
|
|
121
122
|
(= talking self)
|
|
122
|
-
|
|
123
|
-
|
|
123
|
+
(= until (self readingTime: (String STRING_LENGTH txt))))
|
|
124
|
+
|
|
125
|
+
;; Game time to read a line of n characters: normally two seconds, and more for longer
|
|
126
|
+
;; lines; -1 is until the player dismisses it.
|
|
127
|
+
(method (readingTime n)
|
|
128
|
+
(switch textSpeed
|
|
129
|
+
(TEXT_SLOW (return (+ gameTime 180 (* 5 n))))
|
|
130
|
+
(TEXT_FAST (return (+ gameTime 70 (* 2 n))))
|
|
131
|
+
(TEXT_CLICK (return -1))
|
|
132
|
+
(else (return (+ gameTime 120 (* 3 n))))))
|
|
124
133
|
|
|
125
134
|
(method (doit)
|
|
126
|
-
(if (and box (>= gameTime until)) (self done:)))
|
|
135
|
+
(if (and box (!= until -1) (>= gameTime until)) (self done:)))
|
|
127
136
|
|
|
128
|
-
;; A click while a line is up dismisses it (and nothing else).
|
|
137
|
+
;; A click, Enter, Space or "." while a line is up dismisses it (and nothing else).
|
|
129
138
|
(method (handleEvent event)
|
|
130
|
-
(if (and box
|
|
139
|
+
(if (and box
|
|
140
|
+
(or (== (event type?) EV_MOUSE_DOWN)
|
|
141
|
+
(and (== (event type?) EV_KEY_DOWN)
|
|
142
|
+
(or (== (event message?) KEY_ENTER) (== (event message?) KEY_SPACE) (== (event message?) KEY_PERIOD)))))
|
|
131
143
|
(event claimed: TRUE)
|
|
132
144
|
(self done:)
|
|
133
145
|
(return TRUE))
|
package/lib/998.sc
CHANGED
|
@@ -207,9 +207,11 @@
|
|
|
207
207
|
(if (not (and noun (messager say: noun verb 0 0 0 modNum)))
|
|
208
208
|
(curRoom doVerb: verb))))
|
|
209
209
|
|
|
210
|
-
;; A Feature drawn with a view: a screen item in the room's plane.
|
|
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
212
|
(class View of Feature
|
|
212
213
|
(properties
|
|
214
|
+
clickable 1
|
|
213
215
|
view -1 loop 0 cel 0
|
|
214
216
|
z 0
|
|
215
217
|
priority 0 fixPriority 0
|
|
@@ -230,7 +232,7 @@
|
|
|
230
232
|
(if (not (& signal SIG_HIDDEN)) (UpdateScreenItem self)))
|
|
231
233
|
|
|
232
234
|
(method (onMe theX theY)
|
|
233
|
-
(return (and (not (& signal SIG_HIDDEN)) (IsOnMe theX theY self))))
|
|
235
|
+
(return (and clickable (not (& signal SIG_HIDDEN)) (IsOnMe theX theY self))))
|
|
234
236
|
|
|
235
237
|
(method (hide)
|
|
236
238
|
(|= signal SIG_HIDDEN)
|
|
@@ -364,13 +366,51 @@
|
|
|
364
366
|
(if scaler (scaler dispose:) (= scaler 0))
|
|
365
367
|
(super dispose:)))
|
|
366
368
|
|
|
367
|
-
;; The hero: walks where the player clicks, and goes from room to room.
|
|
369
|
+
;; The hero: walks where the player clicks, and goes from room to room. Standing still
|
|
370
|
+
;; for idleAfter seconds while the player can act, he plays a loop of idleView once (a
|
|
371
|
+
;; puff on the pipe, a glance at a watch), chosen at random, then stands again.
|
|
368
372
|
(class Ego of Actor
|
|
369
|
-
|
|
373
|
+
(properties
|
|
374
|
+
normalView -1 ; the walking view (the first view he's normalized with)
|
|
375
|
+
idleView -1 ; -1: no idles
|
|
376
|
+
idleAfter 8
|
|
377
|
+
stillSince 0 ; the game time he was last busy
|
|
378
|
+
idling 0)
|
|
379
|
+
|
|
380
|
+
;; Back to walking: his walking view, cycling as he moves, turning as he goes, seen.
|
|
370
381
|
(method (normalize)
|
|
382
|
+
(if (== normalView -1) (= normalView view) else (= view normalView))
|
|
383
|
+
(= idling 0)
|
|
371
384
|
(self setLoop: -1 setCycle: Walk show:)
|
|
372
385
|
(return self))
|
|
373
386
|
|
|
387
|
+
(method (doit)
|
|
388
|
+
(super doit:)
|
|
389
|
+
(self checkIdle:))
|
|
390
|
+
|
|
391
|
+
(method (checkIdle)
|
|
392
|
+
(cond
|
|
393
|
+
((== idleView -1) 0)
|
|
394
|
+
;; Busy, or someone else is in charge: no idle, and one under way stops.
|
|
395
|
+
((or mover talking dialog (not (user canInput?)) (and (not idling) (!= view normalView)))
|
|
396
|
+
(if idling (self normalize:))
|
|
397
|
+
(= stillSince gameTime))
|
|
398
|
+
(idling 0)
|
|
399
|
+
((>= (- gameTime stillSince) (* idleAfter 60))
|
|
400
|
+
(= idling 1)
|
|
401
|
+
(= view idleView)
|
|
402
|
+
(self setLoop: (Random 0 (- (NumLoops self) 1)) setCel: 0 setCycle: End self))))
|
|
403
|
+
|
|
404
|
+
;; An idle has played through.
|
|
405
|
+
(method (cue)
|
|
406
|
+
(if idling
|
|
407
|
+
(self normalize:)
|
|
408
|
+
(= stillSince gameTime)))
|
|
409
|
+
|
|
410
|
+
(method (setMotion)
|
|
411
|
+
(if idling (self normalize:))
|
|
412
|
+
(super setMotion: &rest))
|
|
413
|
+
|
|
374
414
|
(method (roomDisposed)
|
|
375
415
|
(self setMotion: 0)
|
|
376
416
|
(DeleteScreenItem self)
|
package/lib/999.sc
CHANGED
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
textStyle ; how text boxes and menus look (a TextStyle)
|
|
32
32
|
inventory ; what the hero carries (an Inventory)
|
|
33
33
|
theItem ; the item picked to use, or 0
|
|
34
|
-
iconBar
|
|
34
|
+
iconBar ; the icon bar (an IconBar)
|
|
35
|
+
textSpeed) ; how long a line stays up: TEXT_NORMAL, TEXT_SLOW, TEXT_FAST, TEXT_CLICK
|
|
35
36
|
|
|
36
37
|
(procedure (SetFlag n)
|
|
37
38
|
(|= [gameFlags (/ n 16)] (<< 1 (mod n 16))))
|
package/lib/system.sh
CHANGED
|
@@ -44,6 +44,14 @@
|
|
|
44
44
|
(define SR_RESTORE 3)
|
|
45
45
|
(define SR_RESTART 4)
|
|
46
46
|
(define SR_NAME 5)
|
|
47
|
+
(define SR_TEXT 6)
|
|
48
|
+
|
|
49
|
+
;; How long a line stays up (the textSpeed global): normal (the default), slow, fast, or
|
|
50
|
+
;; until the player clicks.
|
|
51
|
+
(define TEXT_NORMAL 0)
|
|
52
|
+
(define TEXT_SLOW 1)
|
|
53
|
+
(define TEXT_FAST 2)
|
|
54
|
+
(define TEXT_CLICK 3)
|
|
47
55
|
|
|
48
56
|
;; Event types, as GetEvent reports them.
|
|
49
57
|
(define EV_NULL 0)
|
|
@@ -54,6 +62,8 @@
|
|
|
54
62
|
;; Keys (an event's message).
|
|
55
63
|
(define KEY_TAB 9)
|
|
56
64
|
(define KEY_ENTER 13)
|
|
65
|
+
(define KEY_SPACE 32)
|
|
66
|
+
(define KEY_PERIOD 46)
|
|
57
67
|
(define KEY_ESCAPE 27)
|
|
58
68
|
(define KEY_F5 $3f00)
|
|
59
69
|
(define KEY_F7 $4100)
|
|
@@ -78,6 +88,7 @@
|
|
|
78
88
|
(define ARRAY_AT_PUT 3)
|
|
79
89
|
(define ARRAY_FREE 4)
|
|
80
90
|
(define STRING_LENGTH 10)
|
|
91
|
+
(define STRING_FORMAT 11)
|
|
81
92
|
(define STRING_FORMAT_INTO 12)
|
|
82
93
|
(define ARRAY_COPY 6)
|
|
83
94
|
|
package/package.json
CHANGED
|
@@ -222,6 +222,11 @@
|
|
|
222
222
|
"moves": {
|
|
223
223
|
"type": "boolean",
|
|
224
224
|
"description": "It walks or floats about in cutscenes (<<walk name x y>>): an Actor instead of a Prop"
|
|
225
|
+
},
|
|
226
|
+
"script": {
|
|
227
|
+
"type": "string",
|
|
228
|
+
"pattern": "^[A-Z][A-Za-z0-9]*$",
|
|
229
|
+
"description": "A Script class of the game's: an instance of it runs on the prop, e.g. an ambient behaviour"
|
|
225
230
|
}
|
|
226
231
|
}
|
|
227
232
|
}
|
|
@@ -82,6 +82,8 @@ interface PropSpec {
|
|
|
82
82
|
/** forward: loop the animation forever. */
|
|
83
83
|
cycle?: "forward";
|
|
84
84
|
sightAngle?: number;
|
|
85
|
+
/** A Script class of the game's: an instance of it runs on the prop (ambient behaviour). */
|
|
86
|
+
script?: string;
|
|
85
87
|
}
|
|
86
88
|
export interface CompiledRoom {
|
|
87
89
|
number: number;
|
|
@@ -83,7 +83,9 @@ class SpecChecker {
|
|
|
83
83
|
names.add(name);
|
|
84
84
|
const t = this.map(value, path);
|
|
85
85
|
if (group === "props") {
|
|
86
|
-
this.known(t, path, ["view", "at", "loop", "cel", "cycle", "sightAngle", "moves"]);
|
|
86
|
+
this.known(t, path, ["view", "at", "loop", "cel", "cycle", "sightAngle", "moves", "script"]);
|
|
87
|
+
if (t.script !== undefined && (typeof t.script !== "string" || !/^[A-Z][A-Za-z0-9]*$/.test(t.script)))
|
|
88
|
+
this.fail([...path, "script"], "expected a class name, e.g. Scurry");
|
|
87
89
|
if (t.moves !== undefined && typeof t.moves !== "boolean")
|
|
88
90
|
this.fail([...path, "moves"], "expected true or false");
|
|
89
91
|
this.int(t.view, [...path, "view"], 0, 65535);
|
|
@@ -522,6 +524,14 @@ class RoomCompiler {
|
|
|
522
524
|
return [{ op: "code", code: send(to, [["setCycle", [{ cls: this.target.forwardCycle }]]]) }];
|
|
523
525
|
return fail("expected once or forever");
|
|
524
526
|
}
|
|
527
|
+
case "view":
|
|
528
|
+
arity(2, "hero|<prop> view");
|
|
529
|
+
return [{ op: "code", code: send(who(args[0], false), [["view", [int(args[1], "view", 0, 65535)]], ["setCel", [0]]]) }];
|
|
530
|
+
case "normal":
|
|
531
|
+
arity(1, "hero");
|
|
532
|
+
if (args[0] !== "hero")
|
|
533
|
+
fail("only the hero goes back to normal (walking, turning as he goes)");
|
|
534
|
+
return [{ op: "code", code: send({ global: g.ego }, [["normalize", []]]) }];
|
|
525
535
|
case "stop":
|
|
526
536
|
arity(1, "<prop>");
|
|
527
537
|
return [{ op: "code", code: send(who(args[0], false), [["setCycle", [0]]]) }];
|
|
@@ -564,7 +574,7 @@ class RoomCompiler {
|
|
|
564
574
|
arity(1, "number");
|
|
565
575
|
return [{ op: "code", code: send({ global: g.curRoom }, [["newRoom", [int(args[0], "room", 0, 65535)]]]) }];
|
|
566
576
|
default:
|
|
567
|
-
return fail("unknown command (known: walk, face, wait, hide, show, loop, cel, animate, stop, music, sound, closeup, get, drop, room; and set, if)");
|
|
577
|
+
return fail("unknown command (known: walk, face, wait, hide, show, view, loop, cel, animate, normal, stop, music, sound, closeup, get, drop, room; and set, if)");
|
|
568
578
|
}
|
|
569
579
|
}
|
|
570
580
|
/** Loop music n, unless it's what's playing already. */
|
|
@@ -728,7 +738,12 @@ class RoomCompiler {
|
|
|
728
738
|
for (const name of [...Object.keys(spec.features ?? {}), ...Object.keys(spec.exits ?? {})])
|
|
729
739
|
c.push(...send({ obj: name }, [["init", []]]));
|
|
730
740
|
for (const [name, p] of Object.entries(spec.props ?? {})) {
|
|
731
|
-
c.push(...send({ obj: name }, [
|
|
741
|
+
c.push(...send({ obj: name }, [
|
|
742
|
+
["init", []],
|
|
743
|
+
...(p.cycle === "forward" ? [["setCycle", [{ cls: this.target.forwardCycle }]]] : []),
|
|
744
|
+
// A new instance of the game's Script class, running on the prop.
|
|
745
|
+
...(p.script ? [["setScript", [{ code: ["pushi #new", "push0", `class ${p.script}`, "send 4"] }]]] : []),
|
|
746
|
+
]));
|
|
732
747
|
}
|
|
733
748
|
const polygons = [
|
|
734
749
|
...(spec.walkable ? [[3, spec.walkable]] : []),
|
|
@@ -85,6 +85,8 @@ interface PropSpec {
|
|
|
85
85
|
/** forward: loop the animation forever. */
|
|
86
86
|
cycle?: "forward";
|
|
87
87
|
sightAngle?: number;
|
|
88
|
+
/** A Script class of the game's: an instance of it runs on the prop (ambient behaviour). */
|
|
89
|
+
script?: string;
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
export interface CompiledRoom {
|
|
@@ -186,7 +188,8 @@ class SpecChecker {
|
|
|
186
188
|
names.add(name);
|
|
187
189
|
const t = this.map(value, path);
|
|
188
190
|
if (group === "props") {
|
|
189
|
-
this.known(t, path, ["view", "at", "loop", "cel", "cycle", "sightAngle", "moves"]);
|
|
191
|
+
this.known(t, path, ["view", "at", "loop", "cel", "cycle", "sightAngle", "moves", "script"]);
|
|
192
|
+
if (t.script !== undefined && (typeof t.script !== "string" || !/^[A-Z][A-Za-z0-9]*$/.test(t.script))) this.fail([...path, "script"], "expected a class name, e.g. Scurry");
|
|
190
193
|
if (t.moves !== undefined && typeof t.moves !== "boolean") this.fail([...path, "moves"], "expected true or false");
|
|
191
194
|
this.int(t.view, [...path, "view"], 0, 65535);
|
|
192
195
|
this.point(t.at, [...path, "at"]);
|
|
@@ -622,6 +625,13 @@ class RoomCompiler {
|
|
|
622
625
|
if (args[1] === "forever") return [{ op: "code", code: send(to, [["setCycle", [{ cls: this.target.forwardCycle }]]]) }];
|
|
623
626
|
return fail("expected once or forever");
|
|
624
627
|
}
|
|
628
|
+
case "view":
|
|
629
|
+
arity(2, "hero|<prop> view");
|
|
630
|
+
return [{ op: "code", code: send(who(args[0], false), [["view", [int(args[1], "view", 0, 65535)]], ["setCel", [0]]]) }];
|
|
631
|
+
case "normal":
|
|
632
|
+
arity(1, "hero");
|
|
633
|
+
if (args[0] !== "hero") fail("only the hero goes back to normal (walking, turning as he goes)");
|
|
634
|
+
return [{ op: "code", code: send({ global: g.ego }, [["normalize", []]]) }];
|
|
625
635
|
case "stop":
|
|
626
636
|
arity(1, "<prop>");
|
|
627
637
|
return [{ op: "code", code: send(who(args[0], false), [["setCycle", [0]]]) }];
|
|
@@ -661,7 +671,7 @@ class RoomCompiler {
|
|
|
661
671
|
arity(1, "number");
|
|
662
672
|
return [{ op: "code", code: send({ global: g.curRoom }, [["newRoom", [int(args[0], "room", 0, 65535)]]]) }];
|
|
663
673
|
default:
|
|
664
|
-
return fail("unknown command (known: walk, face, wait, hide, show, loop, cel, animate, stop, music, sound, closeup, get, drop, room; and set, if)");
|
|
674
|
+
return fail("unknown command (known: walk, face, wait, hide, show, view, loop, cel, animate, normal, stop, music, sound, closeup, get, drop, room; and set, if)");
|
|
665
675
|
}
|
|
666
676
|
}
|
|
667
677
|
|
|
@@ -831,7 +841,12 @@ class RoomCompiler {
|
|
|
831
841
|
if (spec.music !== undefined) c.push(...this.playMusic(spec.music));
|
|
832
842
|
for (const name of [...Object.keys(spec.features ?? {}), ...Object.keys(spec.exits ?? {})]) c.push(...send({ obj: name }, [["init", []]]));
|
|
833
843
|
for (const [name, p] of Object.entries(spec.props ?? {})) {
|
|
834
|
-
c.push(...send({ obj: name }, [
|
|
844
|
+
c.push(...send({ obj: name }, [
|
|
845
|
+
["init", []],
|
|
846
|
+
...(p.cycle === "forward" ? [["setCycle", [{ cls: this.target.forwardCycle }]] as [string, Val[]]] : []),
|
|
847
|
+
// A new instance of the game's Script class, running on the prop.
|
|
848
|
+
...(p.script ? [["setScript", [{ code: ["pushi #new", "push0", `class ${p.script}`, "send 4"] }]] as [string, Val[]]] : []),
|
|
849
|
+
]));
|
|
835
850
|
}
|
|
836
851
|
const polygons: [number, [number, number][]][] = [
|
|
837
852
|
...(spec.walkable ? [[3, spec.walkable] as [number, [number, number][]]] : []),
|