sci2-ts 0.2.2 → 0.4.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +24 -5
  2. package/apps/viewer/src/session.js +12 -2
  3. package/apps/viewer/src/session.ts +12 -2
  4. package/lib/992.sc +173 -0
  5. package/lib/993.sc +135 -0
  6. package/lib/994.sc +13 -0
  7. package/lib/995.sc +64 -5
  8. package/lib/996.sc +88 -26
  9. package/lib/997.sc +26 -0
  10. package/lib/998.sc +43 -5
  11. package/lib/999.sc +4 -1
  12. package/lib/resources.js +33 -3
  13. package/lib/resources.ts +34 -3
  14. package/lib/system.sh +38 -1
  15. package/package.json +1 -1
  16. package/packages/content/src/room.js +13 -1
  17. package/packages/content/src/room.ts +11 -1
  18. package/packages/content/src/target.d.ts +5 -0
  19. package/packages/content/src/target.ts +5 -0
  20. package/packages/sci/src/vm/kernels/index.d.ts +1 -1
  21. package/packages/sci/src/vm/kernels/index.js +1 -1
  22. package/packages/sci/src/vm/kernels/index.ts +1 -1
  23. package/packages/sci/src/vm/kernels/saves.d.ts +8 -0
  24. package/packages/sci/src/vm/kernels/saves.js +24 -3
  25. package/packages/sci/src/vm/kernels/saves.ts +30 -3
  26. package/packages/sci/src/vm/kernels/text.js +27 -2
  27. package/packages/sci/src/vm/kernels/text.ts +23 -2
  28. package/tools/art/build.d.ts +10 -0
  29. package/tools/art/build.js +32 -2
  30. package/tools/art/build.ts +28 -2
  31. package/tools/art/font.d.ts +24 -0
  32. package/tools/art/font.js +40 -0
  33. package/tools/art/font.ts +56 -0
  34. package/tools/game/build.d.ts +4 -0
  35. package/tools/game/build.js +27 -1
  36. package/tools/game/build.ts +24 -1
  37. package/tools/game/kit.d.ts +1 -0
  38. package/tools/game/kit.js +1 -0
  39. package/tools/game/kit.ts +1 -0
  40. package/tools/game/target.d.ts +1 -1
  41. package/tools/game/target.js +5 -3
  42. package/tools/game/target.ts +5 -3
package/lib/996.sc CHANGED
@@ -3,8 +3,25 @@
3
3
  (script 996)
4
4
  (include "system.sh")
5
5
 
6
+ ;; How text boxes and menus look, for the whole game (the `textStyle` global): the font, the
7
+ ;; ink, paper and border colours, and a frame drawn around each box instead of the border.
8
+ ;; The game sets it in its init, after (super init:):
9
+ ;; (textStyle font: 1 fore: 3 back: 12 frame: 260)
10
+ ;; A frame is a view whose loop 0 has eight cels, each anchored at its top-left corner: the
11
+ ;; corners (top-left, top-right, bottom-left, bottom-right), then the edges (top, bottom, left,
12
+ ;; right). The edges are tiled between the corners; transparent pixels show the paper.
13
+ (class TextStyle of Obj
14
+ (properties
15
+ font 0
16
+ fore 0 ; ink
17
+ back 255 ; paper
18
+ border 0 ; a one-pixel border's colour; -1: none
19
+ frame -1 ; a frame view, or -1
20
+ margin 4)) ; between the text and the border or frame
21
+
6
22
  ;; A box of text over the room: drawn by the interpreter into a bitmap, shown as a screen
7
- ;; item in the UI plane. Set text (and font, colours, width, x, y), then init.
23
+ ;; item in the UI plane. Set text (and width, x, y), then init; it's as tall as the text
24
+ ;; unless height is set. Font, colours and frame come from the textStyle unless set here.
8
25
  (class TextItem of Obj
9
26
  (properties
10
27
  x 0 y 0 z 0
@@ -14,36 +31,66 @@
14
31
  bitmap 0
15
32
  scaleSignal 0 scaleX 128 scaleY 128
16
33
  text 0
17
- font 0
18
- fore 0
19
- back 255
34
+ font -1 ; -1 here and below: the textStyle's
35
+ fore -1
36
+ back -1
20
37
  skip 254
21
38
  mode 0 ; 0 left, 1 centred
22
- borderColor 0
39
+ borderColor -2 ; -1: none
40
+ frame -2 ; -1: none
23
41
  textLeft 0 textTop 0 textRight -1 textBottom -1
24
42
  width 200
25
43
  height 0)
26
44
 
27
- (method (init &tmp r)
28
- ;; As tall as the text needs, with a 4-pixel margin inside the border.
29
- (= r (Array ARRAY_NEW 4 0))
30
- (TextSize r text font (- width 8))
31
- (= height (+ (Array ARRAY_AT r 3) 9))
32
- (Array ARRAY_FREE r)
33
- (= textLeft 4)
34
- (= textTop 4)
35
- (= textRight (- width 5))
36
- (= textBottom (- height 5))
45
+ (method (init &tmp r m padL padT padR padB)
46
+ (if (== font -1) (= font (textStyle font?)))
47
+ (if (== fore -1) (= fore (textStyle fore?)))
48
+ (if (== back -1) (= back (textStyle back?)))
49
+ (if (== frame -2) (= frame (textStyle frame?)))
50
+ (if (== borderColor -2) (= borderColor (if (!= frame -1) -1 else (textStyle border?))))
51
+ ;; As tall as the text needs, inside the margin and the frame.
52
+ (= m (textStyle margin?))
53
+ (= padL m) (= padT m) (= padR m) (= padB m)
54
+ (if (!= frame -1)
55
+ (+= padL (CelWide frame 0 6))
56
+ (+= padR (CelWide frame 0 7))
57
+ (+= padT (CelHigh frame 0 4))
58
+ (+= padB (CelHigh frame 0 5)))
59
+ (if (not height)
60
+ (= r (Array ARRAY_NEW 4 0))
61
+ (TextSize r text font (- width (+ padL padR)))
62
+ (= height (+ (Array ARRAY_AT r 3) 1 padT padB))
63
+ (Array ARRAY_FREE r))
64
+ (= textLeft padL)
65
+ (= textTop padT)
66
+ (= textRight (- width (+ padR 1)))
67
+ (= textBottom (- height (+ padB 1)))
37
68
  (= plane uiPlane)
38
69
  (= bitmap (CreateTextBitmap 0 width height self))
70
+ (if (!= frame -1) (self drawFrame:))
39
71
  (AddScreenItem self))
40
72
 
73
+ ;; The edges, tiled, then the corners over their ends.
74
+ (method (drawFrame &tmp i step)
75
+ (if (< (= step (CelWide frame 0 4)) 1) (= step 1))
76
+ (for ((= i (CelWide frame 0 0))) (< i width) ((+= i step))
77
+ (Bitmap BITMAP_DRAW_VIEW bitmap frame 0 4 i 0)
78
+ (Bitmap BITMAP_DRAW_VIEW bitmap frame 0 5 i (- height (CelHigh frame 0 5))))
79
+ (if (< (= step (CelHigh frame 0 6)) 1) (= step 1))
80
+ (for ((= i (CelHigh frame 0 0))) (< i height) ((+= i step))
81
+ (Bitmap BITMAP_DRAW_VIEW bitmap frame 0 6 0 i)
82
+ (Bitmap BITMAP_DRAW_VIEW bitmap frame 0 7 (- width (CelWide frame 0 7)) i))
83
+ (Bitmap BITMAP_DRAW_VIEW bitmap frame 0 0 0 0)
84
+ (Bitmap BITMAP_DRAW_VIEW bitmap frame 0 1 (- width (CelWide frame 0 1)) 0)
85
+ (Bitmap BITMAP_DRAW_VIEW bitmap frame 0 2 0 (- height (CelHigh frame 0 2)))
86
+ (Bitmap BITMAP_DRAW_VIEW bitmap frame 0 3 (- width (CelWide frame 0 3)) (- height (CelHigh frame 0 3))))
87
+
41
88
  (method (onMe theX theY)
42
89
  (return (and (>= theX x) (< theX (+ x width)) (>= theY y) (< theY (+ y height)))))
43
90
 
44
91
  (method (dispose)
45
92
  (DeleteScreenItem self)
46
- (if bitmap (Bitmap 1 bitmap) (= bitmap 0))
93
+ (if bitmap (Bitmap BITMAP_DISPOSE bitmap) (= bitmap 0))
47
94
  (super dispose:)))
48
95
 
49
96
  ;; Shows a line until it's been there long enough to read, or the player clicks; then
@@ -53,7 +100,7 @@
53
100
  caller 0
54
101
  box 0
55
102
  until 0 ; the game time it goes
56
- font 0
103
+ font -1 ; -1: the textStyle's
57
104
  x -1 ; -1: centred
58
105
  y 16
59
106
  width 220)
@@ -106,14 +153,16 @@
106
153
  (= line (String 11 "%s: %s" name txt))
107
154
  (super say: line &rest whoCares)))
108
155
 
109
- ;; Someone with a portrait: a view whose loop 0 is the bust, loop 1 the mouth (moving while
110
- ;; they talk) and loop 2 the eyes, every cel the same size and placed at one point (the
111
- ;; bottom middle). The room's talker hands its parts to init: mouth bust eyes frame, where
112
- ;; frame is drawn first (the bust). The text goes beside the portrait.
156
+ ;; Someone with a portrait: a view whose loop 0 is the bust, loop 1 the mouth and loop 2 the
157
+ ;; eyes, every cel the same size and anchored at its top-left corner. The room's talker hands
158
+ ;; its parts to init: mouth bust eyes frame, where frame is drawn first (the bust), placed
159
+ ;; where the portrait goes (the top left of the screen). The text goes to its right. While
160
+ ;; the line is said the mouth moves, then closes; the eyes blink.
113
161
  (class PortraitTalker of Talker
114
162
  (properties
115
163
  mouth 0 bust 0 eyes 0 frame 0
116
- priority 150)
164
+ priority 150
165
+ mouthUntil 0) ; the game time the mouth stops
117
166
 
118
167
  (method (init theMouth theBust theEyes theFrame)
119
168
  (= mouth theMouth)
@@ -125,15 +174,24 @@
125
174
  (self init:)
126
175
  (= portrait (if frame frame else bust))
127
176
  (if portrait
128
- (= x (+ (portrait x?) (/ (CelWide (portrait view?) 0 0) 2) 6))
129
- (= y (+ (- (portrait y?) (CelHigh (portrait view?) 0 0)) 1))
130
- (= width (- (- SCREEN_WIDTH x) 8)))
177
+ (= x (+ (portrait x?) (CelWide (portrait view?) (portrait loop?) (portrait cel?)) 6))
178
+ (= y (portrait y?))
179
+ (= width (- SCREEN_WIDTH (+ x 6))))
131
180
  (super say: txt &rest whoCares)
132
181
  (self showPart: frame priority)
133
182
  (self showPart: bust priority)
134
183
  (self showPart: eyes (+ priority 1))
135
184
  (self showPart: mouth (+ priority 1))
136
- (if mouth (mouth setCycle: Forward)))
185
+ (if eyes (eyes setCycle: Blink))
186
+ (if mouth
187
+ (mouth setCycle: Forward)
188
+ ;; About as long as it takes to say: half a second, and two cycles a letter.
189
+ (= mouthUntil (+ gameTime 30 (* 2 (String STRING_LENGTH txt))))))
190
+
191
+ (method (doit)
192
+ (if (and mouth (mouth cycler?) (>= gameTime mouthUntil))
193
+ (mouth setCycle: 0 setCel: 0))
194
+ (super doit:))
137
195
 
138
196
  (method (showPart part pri)
139
197
  (if part
@@ -232,6 +290,10 @@
232
290
  (c choose: v))
233
291
  (return TRUE))
234
292
 
293
+ ;; Closed without a choice.
294
+ (method (dismiss)
295
+ (self dispose:))
296
+
235
297
  (method (dispose)
236
298
  (if (== dialog self) (= dialog 0))
237
299
  (if items
package/lib/997.sc CHANGED
@@ -169,6 +169,32 @@
169
169
  else
170
170
  (client cel: c))))
171
171
 
172
+ ;; Eyes: cel 0 (open) most of the time; every two or three seconds, the other cels in turn
173
+ ;; (shut) and back to 0.
174
+ (class Blink of Cycle
175
+ (properties
176
+ nextBlink 0)
177
+
178
+ (method (init who)
179
+ (super init: who)
180
+ (self rest:))
181
+
182
+ (method (rest)
183
+ (= nextBlink (+ gameTime 90 (Random 0 90))))
184
+
185
+ (method (doit &tmp c)
186
+ (if (== (client cel?) 0)
187
+ (if (and (>= gameTime nextBlink) (> (self lastCel:) 0))
188
+ (= cycleCnt gameTime)
189
+ (client cel: 1))
190
+ else
191
+ (= c (self nextCel:))
192
+ (if (> c (self lastCel:))
193
+ (client cel: 0)
194
+ (self rest:)
195
+ else
196
+ (client cel: c)))))
197
+
172
198
  ;; Sizes its client by how far up the screen it stands: frontSize percent at frontY,
173
199
  ;; backSize at backY, in between in proportion.
174
200
  (class Scaler of Obj
package/lib/998.sc CHANGED
@@ -20,7 +20,8 @@
20
20
  ;; The game: sets everything up, then runs one cycle after another until `quit`.
21
21
  (class Game of Obj
22
22
  (properties
23
- script 0)
23
+ script 0
24
+ saver 0) ; the SaveRestore behind the game menu
24
25
 
25
26
  (method (init)
26
27
  (= game self)
@@ -30,6 +31,8 @@
30
31
  (= theEvent (Event new:))
31
32
  (= messager (Messager new:))
32
33
  (= narrator (Narrator new:))
34
+ (= textStyle (TextStyle new:))
35
+ (= inventory (Inventory new:))
33
36
  (= music (Sound new:))
34
37
  (= sfx (Sound new:))
35
38
  (= uiPlane ((Plane new:) priority: 200 picture: -2 yourself:))
@@ -42,6 +45,38 @@
42
45
  (while (not quit)
43
46
  (self doit:)))
44
47
 
48
+ ;; After a restore (RestoreGame), the interpreter starts the game here, not at play: the
49
+ ;; game is as it was saved, but nothing is on screen and nothing is playing. Put the
50
+ ;; planes and the cast back and start looping sounds again, then carry on.
51
+ (method (replay)
52
+ (uiPlane init:)
53
+ (if curRoom ((curRoom plane?) init:))
54
+ (cast eachElementDo: #replay)
55
+ (sounds eachElementDo: #replay)
56
+ (user cursor: -1)
57
+ (while (not quit)
58
+ (self doit:)))
59
+
60
+ ;; The game menu (Escape): save, restore, start again.
61
+ (method (showMenu)
62
+ (self clearText:)
63
+ ((self saveRestore:) showMenu:))
64
+
65
+ (method (save)
66
+ ((self saveRestore:) save:))
67
+
68
+ (method (restore)
69
+ ((self saveRestore:) restore:))
70
+
71
+ (method (saveRestore)
72
+ (if (not saver) (= saver (SaveRestore new:)))
73
+ (return saver))
74
+
75
+ ;; Takes down a line being shown and whatever is open (a menu, the inventory, a close-up).
76
+ (method (clearText)
77
+ (if talking (talking clear:))
78
+ (if dialog (dialog dismiss:)))
79
+
45
80
  ;; One cycle: scripts, the room, everything on screen, text, drawing, then input.
46
81
  (method (doit)
47
82
  (++ gameTime)
@@ -57,8 +92,7 @@
57
92
  ;; Leaves the current room for room n (its script's export 0).
58
93
  (method (newRoom n &tmp old)
59
94
  (= newRoomNum 0)
60
- (if talking (talking clear:))
61
- (if dialog (dialog dispose:))
95
+ (self clearText:)
62
96
  (if curRoom
63
97
  (= old curRoomNum)
64
98
  (curRoom dispose:)
@@ -77,10 +111,10 @@
77
111
 
78
112
  ;; Clicks do nothing (but dismiss text) until handsOn: for cutscenes.
79
113
  (method (handsOff)
80
- (user canInput: FALSE))
114
+ (user canInput: FALSE showCursor:))
81
115
 
82
116
  (method (handsOn)
83
- (user canInput: TRUE)))
117
+ (user canInput: TRUE showCursor:)))
84
118
 
85
119
  ;; A room: its picture, where you can walk, its exits, and what's said about it.
86
120
  (class Room of Obj
@@ -237,6 +271,10 @@
237
271
  (method (roomDisposed)
238
272
  (self dispose:))
239
273
 
274
+ ;; Back on screen after a restore.
275
+ (method (replay)
276
+ (if (not (& signal SIG_HIDDEN)) (AddScreenItem self)))
277
+
240
278
  (method (dispose)
241
279
  (if (not (& signal SIG_HIDDEN)) (DeleteScreenItem self))
242
280
  (cast delete: self)
package/lib/999.sc CHANGED
@@ -27,7 +27,10 @@
27
27
  uiPlane ; the plane over the room, for text
28
28
  gameTime ; cycles since the game started
29
29
  quit ; set to end the game
30
- [gameFlags 64]) ; story flags, a bit each
30
+ [gameFlags 64] ; story flags, a bit each
31
+ textStyle ; how text boxes and menus look (a TextStyle)
32
+ inventory ; what the hero carries (an Inventory)
33
+ theItem) ; the item picked to use, or 0
31
34
 
32
35
  (procedure (SetFlag n)
33
36
  (|= [gameFlags (/ n 16)] (<< 1 (mod n 16))))
package/lib/resources.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import { Colour, ResourceType, writeView } from "../tools/game/kit.js";
2
2
  /**
3
- * The class library's own resources: a cursor for each verb, view CURSOR_BASE + verb
4
- * (system.sh). X is black, W white, . transparent.
3
+ * The class library's own resources: a cursor for each verb, view CURSOR_BASE + verb, and
4
+ * one while the player waits, CURSOR_WAIT (system.sh). X is black, W white, . transparent.
5
5
  */
6
6
  const CURSOR_BASE = 990;
7
+ const CURSOR_WAIT = 995;
7
8
  const CURSORS = {
8
9
  // V_LOOK: an eye.
9
10
  1: {
@@ -70,6 +71,22 @@ const CURSORS = {
70
71
  "...XXXXXX..",
71
72
  ],
72
73
  },
74
+ // Waiting: an hourglass.
75
+ [CURSOR_WAIT - CURSOR_BASE]: {
76
+ hotspot: "centre",
77
+ art: [
78
+ "XXXXXXXXX",
79
+ "XWWWWWWWX",
80
+ ".XWWWWWX.",
81
+ "..XWWWX..",
82
+ "...XWX...",
83
+ "...XWX...",
84
+ "..XWWWX..",
85
+ ".XWWWWWX.",
86
+ "XWWWWWWWX",
87
+ "XXXXXXXXX",
88
+ ],
89
+ },
73
90
  };
74
91
  function cursor(art, hotspot) {
75
92
  const width = art[0].length, height = art.length;
@@ -79,6 +96,19 @@ function cursor(art, hotspot) {
79
96
  const cel = { width, height, displaceX: (width >> 1) - hx, displaceY: height - 1 - hy, skipColor: Colour.Transparent, pixels };
80
97
  return writeView({ flags: 1, loops: [{ link: -1, mirror: false, cels: [cel] }], palette: undefined });
81
98
  }
99
+ /**
100
+ * What a close-up dims the room with (CLOSE_UP_SHADE): the whole screen in colour 253, which
101
+ * CloseUp makes a remap colour that darkens what's under it.
102
+ */
103
+ const CLOSE_UP_SHADE = 996;
104
+ function shade() {
105
+ const [width, height] = [320, 200];
106
+ const cel = { width, height, displaceX: width >> 1, displaceY: height - 1, skipColor: Colour.Transparent, pixels: new Uint8Array(width * height).fill(253) };
107
+ return writeView({ flags: 1, loops: [{ link: -1, mirror: false, cels: [cel] }], palette: undefined });
108
+ }
82
109
  export default function resources() {
83
- return Object.entries(CURSORS).map(([verb, c]) => ({ type: ResourceType.View, number: CURSOR_BASE + Number(verb), data: cursor(c.art, c.hotspot) }));
110
+ return [
111
+ ...Object.entries(CURSORS).map(([verb, c]) => ({ type: ResourceType.View, number: CURSOR_BASE + Number(verb), data: cursor(c.art, c.hotspot) })),
112
+ { type: ResourceType.View, number: CLOSE_UP_SHADE, data: shade() },
113
+ ];
84
114
  }
package/lib/resources.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  import { Colour, ResourceType, writeView, type ResourceData } from "../tools/game/kit.ts";
2
2
 
3
3
  /**
4
- * The class library's own resources: a cursor for each verb, view CURSOR_BASE + verb
5
- * (system.sh). X is black, W white, . transparent.
4
+ * The class library's own resources: a cursor for each verb, view CURSOR_BASE + verb, and
5
+ * one while the player waits, CURSOR_WAIT (system.sh). X is black, W white, . transparent.
6
6
  */
7
7
  const CURSOR_BASE = 990;
8
+ const CURSOR_WAIT = 995;
8
9
 
9
10
  const CURSORS: Record<number, { art: string[]; hotspot: "tip" | "centre" }> = {
10
11
  // V_LOOK: an eye.
@@ -72,6 +73,22 @@ const CURSORS: Record<number, { art: string[]; hotspot: "tip" | "centre" }> = {
72
73
  "...XXXXXX..",
73
74
  ],
74
75
  },
76
+ // Waiting: an hourglass.
77
+ [CURSOR_WAIT - CURSOR_BASE]: {
78
+ hotspot: "centre",
79
+ art: [
80
+ "XXXXXXXXX",
81
+ "XWWWWWWWX",
82
+ ".XWWWWWX.",
83
+ "..XWWWX..",
84
+ "...XWX...",
85
+ "...XWX...",
86
+ "..XWWWX..",
87
+ ".XWWWWWX.",
88
+ "XWWWWWWWX",
89
+ "XXXXXXXXX",
90
+ ],
91
+ },
75
92
  };
76
93
 
77
94
  function cursor(art: string[], hotspot: "tip" | "centre") {
@@ -83,6 +100,20 @@ function cursor(art: string[], hotspot: "tip" | "centre") {
83
100
  return writeView({ flags: 1, loops: [{ link: -1, mirror: false, cels: [cel] }], palette: undefined });
84
101
  }
85
102
 
103
+ /**
104
+ * What a close-up dims the room with (CLOSE_UP_SHADE): the whole screen in colour 253, which
105
+ * CloseUp makes a remap colour that darkens what's under it.
106
+ */
107
+ const CLOSE_UP_SHADE = 996;
108
+ function shade() {
109
+ const [width, height] = [320, 200];
110
+ const cel = { width, height, displaceX: width >> 1, displaceY: height - 1, skipColor: Colour.Transparent, pixels: new Uint8Array(width * height).fill(253) };
111
+ return writeView({ flags: 1, loops: [{ link: -1, mirror: false, cels: [cel] }], palette: undefined });
112
+ }
113
+
86
114
  export default function resources(): ResourceData[] {
87
- return Object.entries(CURSORS).map(([verb, c]) => ({ type: ResourceType.View, number: CURSOR_BASE + Number(verb), data: cursor(c.art, c.hotspot) }));
115
+ return [
116
+ ...Object.entries(CURSORS).map(([verb, c]) => ({ type: ResourceType.View, number: CURSOR_BASE + Number(verb), data: cursor(c.art, c.hotspot) })),
117
+ { type: ResourceType.View, number: CLOSE_UP_SHADE, data: shade() },
118
+ ];
88
119
  }
package/lib/system.sh CHANGED
@@ -8,15 +8,46 @@
8
8
  V_WALK
9
9
  V_DO)
10
10
  (define VERB_COUNT 4)
11
+ (define V_ITEM 5) ; using the item picked in the inventory (theItem)
11
12
 
12
- ;; Cursors: one view per verb, CURSOR_BASE + verb.
13
+ ;; Cursors: the library's are views CURSOR_BASE + verb, and CURSOR_WAIT while the player
14
+ ;; can't act (a game can bring its own: User).
13
15
  (define CURSOR_BASE 990)
16
+ (define CURSOR_WAIT 995)
17
+
18
+ ;; The inventory window: icons are INV_ICON pixels square, INV_GAP apart.
19
+ (define INV_ICON 24)
20
+ (define INV_GAP 4)
21
+
22
+ ;; Close-ups: the room is dimmed by this view, drawn in a remap colour.
23
+ (define CLOSE_UP_SHADE 996)
24
+ (define REMAP_OFF 0)
25
+ (define REMAP_BY_PERCENT 2)
26
+
27
+ ;; Saved games: a description is up to SAVE_DESCRIPTION - 1 characters; the menus show the
28
+ ;; newest SAVES_SHOWN. What SaveRestore's open menu is for:
29
+ (define SAVE_DESCRIPTION 36)
30
+ (define SAVES_SHOWN 8)
31
+ (define SR_MENU 1)
32
+ (define SR_SAVE 2)
33
+ (define SR_RESTORE 3)
34
+ (define SR_RESTART 4)
35
+ (define SR_NAME 5)
14
36
 
15
37
  ;; Event types, as GetEvent reports them.
16
38
  (define EV_NULL 0)
17
39
  (define EV_MOUSE_DOWN 1)
18
40
  (define EV_MOUSE_UP 2)
19
41
  (define EV_KEY_DOWN 4)
42
+
43
+ ;; Keys (an event's message).
44
+ (define KEY_TAB 9)
45
+ (define KEY_ENTER 13)
46
+ (define KEY_ESCAPE 27)
47
+ (define KEY_F5 $3f00)
48
+ (define KEY_F7 $4100)
49
+ (define KEY_I 73)
50
+ (define KEY_i 105)
20
51
  (define EV_ALL $7fff)
21
52
  ;; The right button arrives as a click with shift held.
22
53
  (define MOD_RIGHT 3)
@@ -36,6 +67,12 @@
36
67
  (define ARRAY_AT_PUT 3)
37
68
  (define ARRAY_FREE 4)
38
69
  (define STRING_LENGTH 10)
70
+ (define STRING_FORMAT_INTO 12)
71
+ (define ARRAY_COPY 6)
72
+
73
+ ;; Bitmap subfunctions.
74
+ (define BITMAP_DISPOSE 1)
75
+ (define BITMAP_DRAW_VIEW 3)
39
76
 
40
77
  ;; DoSound kernel calls.
41
78
  (define SND_INIT 6)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sci2-ts",
3
- "version": "0.2.2",
3
+ "version": "0.4.0",
4
4
  "description": "An SCI2 adventure game engine in TypeScript, and the tools to make games with it",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -537,11 +537,23 @@ class RoomCompiler {
537
537
  ? [{ op: "async", code: send({ global: g.sound }, [["number", [n]], ["loop", [1]], ["play", ["self"]]]) }]
538
538
  : [{ op: "code", code: send({ global: g.sound }, [["number", [n]], ["loop", [1]], ["play", []]]) }];
539
539
  }
540
+ case "closeup": {
541
+ if (args.length < 1 || args.length > 3)
542
+ fail("expected <<closeup view [loop [cel]]>>");
543
+ if (!this.target.closeUp)
544
+ fail("this game has no close-ups");
545
+ const [view, loop = "0", cel = "0"] = args;
546
+ // A new close-up, shown until the player clicks; then it cues the script.
547
+ return [{ op: "async", code: [
548
+ ...send({ cls: this.target.closeUp }, [["new", []]]),
549
+ "pushi #show", "pushi 4", ...pushVal(int(view, "view", 0, 65535)), ...pushVal(int(loop, "loop", 0, 255)), ...pushVal(int(cel, "cel", 0, 255)), "pushSelf", "send 12",
550
+ ] }];
551
+ }
540
552
  case "room":
541
553
  arity(1, "number");
542
554
  return [{ op: "code", code: send({ global: g.curRoom }, [["newRoom", [int(args[0], "room", 0, 65535)]]]) }];
543
555
  default:
544
- return fail("unknown command (known: walk, face, wait, hide, show, loop, cel, animate, stop, music, sound, room; and set, if)");
556
+ return fail("unknown command (known: walk, face, wait, hide, show, loop, cel, animate, stop, music, sound, closeup, room; and set, if)");
545
557
  }
546
558
  }
547
559
  /** Loop music n, unless it's what's playing already. */
@@ -636,11 +636,21 @@ class RoomCompiler {
636
636
  ? [{ op: "async", code: send({ global: g.sound }, [["number", [n]], ["loop", [1]], ["play", ["self"]]]) }]
637
637
  : [{ op: "code", code: send({ global: g.sound }, [["number", [n]], ["loop", [1]], ["play", []]]) }];
638
638
  }
639
+ case "closeup": {
640
+ if (args.length < 1 || args.length > 3) fail("expected <<closeup view [loop [cel]]>>");
641
+ if (!this.target.closeUp) fail("this game has no close-ups");
642
+ const [view, loop = "0", cel = "0"] = args;
643
+ // A new close-up, shown until the player clicks; then it cues the script.
644
+ return [{ op: "async", code: [
645
+ ...send({ cls: this.target.closeUp! }, [["new", []]]),
646
+ "pushi #show", "pushi 4", ...pushVal(int(view, "view", 0, 65535)), ...pushVal(int(loop, "loop", 0, 255)), ...pushVal(int(cel, "cel", 0, 255)), "pushSelf", "send 12",
647
+ ] }];
648
+ }
639
649
  case "room":
640
650
  arity(1, "number");
641
651
  return [{ op: "code", code: send({ global: g.curRoom }, [["newRoom", [int(args[0], "room", 0, 65535)]]]) }];
642
652
  default:
643
- return fail("unknown command (known: walk, face, wait, hide, show, loop, cel, animate, stop, music, sound, room; and set, if)");
653
+ return fail("unknown command (known: walk, face, wait, hide, show, loop, cel, animate, stop, music, sound, closeup, room; and set, if)");
644
654
  }
645
655
  }
646
656
 
@@ -59,6 +59,11 @@ export interface Target {
59
59
  };
60
60
  /** The class that cycles a prop's cels forever (<<animate x forever>>, cycle: forward). */
61
61
  forwardCycle: string;
62
+ /**
63
+ * Close-ups (<<closeup view [loop [cel]]>>), if the target has them: a class whose new
64
+ * instance takes `show: view loop cel caller` and cues the caller when it's dismissed.
65
+ */
66
+ closeUp?: string;
62
67
  /** Verb number for "said on arrival" messages. */
63
68
  narrationVerb: number;
64
69
  /** Verb names as used in Yarn node titles (lower case) -> verb numbers. */
@@ -46,6 +46,11 @@ export interface Target {
46
46
  teller: { class: string; props: Record<string, number>; verb: number };
47
47
  /** The class that cycles a prop's cels forever (<<animate x forever>>, cycle: forward). */
48
48
  forwardCycle: string;
49
+ /**
50
+ * Close-ups (<<closeup view [loop [cel]]>>), if the target has them: a class whose new
51
+ * instance takes `show: view loop cel caller` and cues the caller when it's dismissed.
52
+ */
53
+ closeUp?: string;
49
54
  /** Verb number for "said on arrival" messages. */
50
55
  narrationVerb: number;
51
56
  /** Verb names as used in Yarn node titles (lower case) -> verb numbers. */
@@ -3,6 +3,6 @@ export { graphics, Graphics, type Frame, SCREEN_WIDTH, SCREEN_HEIGHT, ShowStyle,
3
3
  export { input, Input, EventType, SciKey, hostFiles, type SciEvent } from "./system.ts";
4
4
  export { audio, AudioState, readingTime } from "./audio.ts";
5
5
  export { paletteEffects, PaletteEffects } from "./palette.ts";
6
- export { saves, restoreGame, MemorySaveStore, type SaveStore, type SaveInfo } from "./saves.ts";
6
+ export { saves, restoreGame, scriptsFingerprint, MemorySaveStore, type SaveStore, type SaveInfo } from "./saves.ts";
7
7
  export declare const allKernels: Record<string, KernelFn>;
8
8
  export { stringHelpers } from "./arrays.ts";
@@ -12,7 +12,7 @@ export { graphics, Graphics, SCREEN_WIDTH, SCREEN_HEIGHT, ShowStyle, frameRgb }
12
12
  export { input, Input, EventType, SciKey, hostFiles } from "./system.js";
13
13
  export { audio, AudioState, readingTime } from "./audio.js";
14
14
  export { paletteEffects, PaletteEffects } from "./palette.js";
15
- export { saves, restoreGame, MemorySaveStore } from "./saves.js";
15
+ export { saves, restoreGame, scriptsFingerprint, MemorySaveStore } from "./saves.js";
16
16
  export const allKernels = {
17
17
  ...coreKernels,
18
18
  ...mathKernels,
@@ -14,7 +14,7 @@ export { graphics, Graphics, type Frame, SCREEN_WIDTH, SCREEN_HEIGHT, ShowStyle,
14
14
  export { input, Input, EventType, SciKey, hostFiles, type SciEvent } from "./system.ts";
15
15
  export { audio, AudioState, readingTime } from "./audio.ts";
16
16
  export { paletteEffects, PaletteEffects } from "./palette.ts";
17
- export { saves, restoreGame, MemorySaveStore, type SaveStore, type SaveInfo } from "./saves.ts";
17
+ export { saves, restoreGame, scriptsFingerprint, MemorySaveStore, type SaveStore, type SaveInfo } from "./saves.ts";
18
18
 
19
19
  export const allKernels: Record<string, KernelFn> = {
20
20
  ...coreKernels,
@@ -5,6 +5,11 @@ export interface SaveInfo {
5
5
  description: string;
6
6
  version: string;
7
7
  date: number;
8
+ /**
9
+ * The scripts it was made with (scriptsFingerprint). A save is a snapshot of running
10
+ * scripts, so it only restores into the same ones; saves from before this was kept have none.
11
+ */
12
+ scripts?: string;
8
13
  }
9
14
  /** Where saves live. Synchronous because kernels are; hosts preload and write through. */
10
15
  export interface SaveStore {
@@ -32,7 +37,10 @@ export declare class MemorySaveStore implements SaveStore {
32
37
  declare class SaveState {
33
38
  store: SaveStore;
34
39
  restarting: number;
40
+ fingerprint: string | undefined;
35
41
  }
42
+ /** A hash (FNV-1a) of every script and heap resource: which scripts a save belongs to. */
43
+ export declare function scriptsFingerprint(vm: Vm): string;
36
44
  export declare const saves: (vm: Vm) => SaveState;
37
45
  /**
38
46
  * Loads a saved game the way SSCI's RestoreGame does: host state (planes, screen items,