sci2-ts 0.10.0 → 0.12.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 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.12.0](https://github.com/rlueder/sci-ts/compare/v0.11.0...v0.12.0) (2026-10-02)
8
+
9
+ ### Added
10
+
11
+ * **lib:** portraits that face the middle from their character's side, framed, and one for the hero ([#14](https://github.com/rlueder/sci-ts/issues/14)) ([e7be055](https://github.com/rlueder/sci-ts/commit/e7be0555af2c49743c539e7231cf979ac274617e))
12
+
13
+ ## [0.11.0](https://github.com/rlueder/sci-ts/compare/v0.10.0...v0.11.0) (2026-10-02)
14
+
15
+ ### Added
16
+
17
+ * **lib:** menus as one framed box, and text boxes that sit on the bottom of the screen ([#13](https://github.com/rlueder/sci-ts/issues/13)) ([83cf03d](https://github.com/rlueder/sci-ts/commit/83cf03d0bb4e044d24cbff769a102294e3a66a03))
18
+
7
19
  ## [0.10.0](https://github.com/rlueder/sci-ts/compare/v0.9.0...v0.10.0) (2026-10-02)
8
20
 
9
21
  ### Added
package/lib/996.sc CHANGED
@@ -17,11 +17,15 @@
17
17
  back 255 ; paper
18
18
  border 0 ; a one-pixel border's colour; -1: none
19
19
  frame -1 ; a frame view, or -1
20
- margin 4)) ; between the text and the border or frame
20
+ margin 4 ; between the text and the border or frame
21
+ portraitFrame -1 ; a view around portraits: loop 0 behind the face, loop 1 over its edges
22
+ portraitX 8 ; where a portrait's face goes on the left (mirrored on the right)
23
+ portraitY 8))
21
24
 
22
25
  ;; A box of text over the room: drawn by the interpreter into a bitmap, shown as a screen
23
26
  ;; item in the UI plane. Set text (and width, x, y), then init; it's as tall as the text
24
27
  ;; unless height is set. Font, colours and frame come from the textStyle unless set here.
28
+ ;; With bottom set, y is worked out so the box ends there: it grows upwards with the text.
25
29
  (class TextItem of Obj
26
30
  (properties
27
31
  x 0 y 0 z 0
@@ -40,7 +44,8 @@
40
44
  frame -2 ; -1: none
41
45
  textLeft 0 textTop 0 textRight -1 textBottom -1
42
46
  width 200
43
- height 0)
47
+ height 0
48
+ bottom -1) ; -1: placed at y
44
49
 
45
50
  (method (init &tmp r m padL padT padR padB)
46
51
  (if (== font -1) (= font (textStyle font?)))
@@ -65,6 +70,7 @@
65
70
  (= textTop padT)
66
71
  (= textRight (- width (+ padR 1)))
67
72
  (= textBottom (- height (+ padB 1)))
73
+ (if (!= bottom -1) (= y (- bottom height)))
68
74
  (= plane uiPlane)
69
75
  (= bitmap (CreateTextBitmap 0 width height self))
70
76
  (if (!= frame -1) (self drawFrame:))
@@ -111,7 +117,7 @@
111
117
  noun 0 verb 0 cond 0 seq 0
112
118
  font -1 ; -1: the textStyle's
113
119
  x -1 ; -1: centred
114
- y 16
120
+ y 16 ; below 0: that far above the bottom of the screen, growing upwards
115
121
  width 220)
116
122
 
117
123
  (method (say txt whoCares)
@@ -128,7 +134,8 @@
128
134
  font: font
129
135
  width: width
130
136
  x: (if (== x -1) (/ (- SCREEN_WIDTH width) 2) else x)
131
- y: y
137
+ y: (if (< y 0) 0 else y)
138
+ bottom: (if (< y 0) (+ SCREEN_HEIGHT y) else -1)
132
139
  yourself:))
133
140
  (box init:))
134
141
  (= talking self)
@@ -187,34 +194,80 @@
187
194
  (super say: line &rest whoCares)))
188
195
 
189
196
  ;; Someone with a portrait: a view whose loop 0 is the bust, loop 1 the mouth and loop 2 the
190
- ;; eyes, every cel the same size and anchored at its top-left corner. The room's talker hands
191
- ;; its parts to init: mouth bust eyes frame, where frame is drawn first (the bust), placed
192
- ;; where the portrait goes (the top left of the screen). The text goes to its right. While
193
- ;; the line is said the mouth moves, then closes; the eyes blink.
197
+ ;; eyes, every cel the same size and anchored at its top-left corner; loops 3 to 5, if there
198
+ ;; are any, are the same facing left. A room's talker is handed its parts (init: mouth bust
199
+ ;; eyes frame, where frame is drawn as the bust); one with a view of its own (the hero's)
200
+ ;; makes them. While the line is said the mouth moves, then closes; the eyes blink.
201
+ ;;
202
+ ;; The portrait goes at the top left facing right, or the top right facing left, with the
203
+ ;; text beside it on the side towards the middle. A character takes the side of the hero
204
+ ;; it stands on (who: is its body in the room; without one, the right); the hero takes the
205
+ ;; side opposite whoever spoke last. The textStyle places it (portraitX, portraitY) and can
206
+ ;; frame it (portraitFrame).
194
207
  (class PortraitTalker of Talker
195
208
  (properties
196
209
  mouth 0 bust 0 eyes 0 frame 0
210
+ view -1 ; the portrait's view, for a talker that makes its own parts
211
+ who 0 ; the character's body in the room
212
+ side 0 ; where the portrait is: 0 the left, 1 the right
213
+ otherSide 1 ; the hero's: the side of whoever spoke last
214
+ back 0 front 0 ; the portraitFrame, behind the face and over its edges
197
215
  priority 150
198
216
  mouthFor 0) ; cycles the mouth moves for
199
217
 
200
218
  (method (init theMouth theBust theEyes theFrame)
201
- (= mouth theMouth)
202
- (= bust theBust)
203
- (= eyes theEyes)
204
- (= frame theFrame))
205
-
206
- (method (say txt whoCares &tmp portrait)
207
- (self init:)
208
- (= portrait (if frame frame else bust))
209
- (if portrait
210
- (= x (+ (portrait x?) (CelWide (portrait view?) (portrait loop?) (portrait cel?)) 6))
211
- (= y (portrait y?))
212
- (= width (- SCREEN_WIDTH (+ x 6))))
219
+ (if argc
220
+ (= mouth theMouth)
221
+ (= bust theBust)
222
+ (= eyes theEyes)
223
+ (= frame theFrame)))
224
+
225
+ (method (makeParts)
226
+ (if (and (!= view -1) (not bust) (not frame))
227
+ (= bust ((View new:) view: view yourself:))
228
+ (= mouth ((Prop new:) view: view yourself:))
229
+ (= eyes ((Prop new:) view: view yourself:))))
230
+
231
+ ;; This line's side: see above.
232
+ (method (pickSide &tmp s)
233
+ (if (== self heroTalker) (return (- 1 otherSide)))
234
+ (= s (if (and who ego (< (who x?) (ego x?))) 0 else 1))
235
+ (if (and heroTalker (!= heroTalker self) (heroTalker respondsTo: #otherSide)) (heroTalker otherSide: s))
236
+ (return s))
237
+
238
+ (method (say txt whoCares &tmp face l pw f fx fy over)
239
+ (self init: makeParts:)
240
+ (= face (if frame frame else bust))
241
+ (if face
242
+ (= side (self pickSide:))
243
+ (= l (if (and side (>= (NumLoops face) 6)) 3 else 0))
244
+ (= pw (CelWide (face view?) l 0))
245
+ (= f (textStyle portraitFrame?))
246
+ (= over (if (!= f -1) (/ (- (CelWide f 0 0) pw) 2) else 0))
247
+ (= fx (if side (- SCREEN_WIDTH (+ (textStyle portraitX?) pw)) else (textStyle portraitX?)))
248
+ (= fy (textStyle portraitY?))
249
+ (face x: fx y: fy loop: l cel: 0)
250
+ (if mouth (mouth x: fx y: fy loop: (+ l 1) cel: 0))
251
+ (if eyes (eyes x: fx y: fy loop: (+ l 2) cel: 0))
252
+ (if (!= f -1)
253
+ (if (not back) (= back ((View new:) view: f loop: 0 cel: 0 yourself:)))
254
+ (back x: fx y: fy)
255
+ (if (and (not front) (>= (NumLoops back) 2)) (= front ((View new:) view: f loop: 1 cel: 0 yourself:)))
256
+ (if front (front x: fx y: fy)))
257
+ ;; The text beside it, towards the middle.
258
+ (= y fy)
259
+ (if side
260
+ (= x 6)
261
+ (= width (- fx (+ over 12)))
262
+ else
263
+ (= x (+ fx pw over 6))
264
+ (= width (- SCREEN_WIDTH (+ x 6)))))
213
265
  (super say: txt &rest whoCares)
214
- (self showPart: frame priority)
215
- (self showPart: bust priority)
216
- (self showPart: eyes (+ priority 1))
266
+ (self showPart: back (- priority 1))
267
+ (self showPart: face priority)
217
268
  (self showPart: mouth (+ priority 1))
269
+ (self showPart: eyes (+ priority 1))
270
+ (self showPart: front (+ priority 2))
218
271
  (if eyes (eyes setCycle: Blink))
219
272
  (if mouth
220
273
  (mouth setCycle: Forward)
@@ -232,11 +285,19 @@
232
285
  (part init:)
233
286
  (part plane: uiPlane setPri: pri)))
234
287
 
288
+ ;; Off the screen, kept for the next line.
289
+ (method (hidePart part)
290
+ (if part
291
+ (DeleteScreenItem part)
292
+ (cast delete: part)))
293
+
235
294
  (method (clear)
236
- (if frame (frame dispose:))
237
- (if bust (bust dispose:))
238
- (if eyes (eyes dispose:))
239
- (if mouth (mouth dispose:))
295
+ (self hidePart: front)
296
+ (self hidePart: back)
297
+ (self hidePart: frame)
298
+ (self hidePart: bust)
299
+ (self hidePart: eyes)
300
+ (self hidePart: mouth)
240
301
  (super clear:)))
241
302
 
242
303
  ;; Says the lines of a message file for a noun, verb and condition, in sequence:
@@ -294,25 +355,53 @@
294
355
  (String ARRAY_FREE text)
295
356
  (super dispose:)))
296
357
 
297
- ;; Choices in boxes, one under another; the first click on one ends it. While it's open it
298
- ;; gets every click (it's the `dialog`). The caller hears the choice by `choose: value`.
358
+ ;; Choices one under another in a box (the textStyle's frame around them all); the first
359
+ ;; click on one ends it. While it's open it gets every click (it's the `dialog`). The caller
360
+ ;; hears the choice by `choose: value`.
299
361
  (class Menu of Obj
300
362
  (properties
301
363
  items 0
302
364
  caller 0
303
- y 20
304
- width 240)
305
-
306
- (method (add v txt &tmp item)
307
- (if (not items) (= items (List new:)))
308
- (= item ((MenuItem new:) value: v text: txt width: width x: (/ (- SCREEN_WIDTH width) 2) y: y yourself:))
365
+ panel 0
366
+ y 20 ; the top of the box
367
+ width 240
368
+ next 0) ; where the next choice goes
369
+
370
+ (method (add v txt &tmp item f)
371
+ (= f (textStyle frame?))
372
+ (if (not items)
373
+ (= items (List new:))
374
+ (= next (+ y (if (!= f -1) (CelHigh f 0 4) else 1))))
375
+ (= item
376
+ ((MenuItem new:)
377
+ value: v
378
+ text: txt
379
+ frame: -1
380
+ borderColor: -1
381
+ width: (- width (if (!= f -1) (+ (CelWide f 0 6) (CelWide f 0 7)) else 2))
382
+ x: (+ (/ (- SCREEN_WIDTH width) 2) (if (!= f -1) (CelWide f 0 6) else 1))
383
+ y: next
384
+ yourself:))
309
385
  (item init:)
310
- (+= y (+ (item height?) 2))
386
+ (+= next (item height?))
311
387
  (items add: item)
312
388
  (return self))
313
389
 
314
- (method (show whoCares)
390
+ ;; The box behind the choices, then it waits for one.
391
+ (method (show whoCares &tmp f)
392
+ (= f (textStyle frame?))
315
393
  (= caller whoCares)
394
+ (if items
395
+ (= panel
396
+ ((TextItem new:)
397
+ text: ""
398
+ x: (/ (- SCREEN_WIDTH width) 2)
399
+ y: y
400
+ width: width
401
+ height: (+ (- next y) (if (!= f -1) (CelHigh f 0 5) else 1))
402
+ priority: 99
403
+ yourself:))
404
+ (panel init:))
316
405
  (= dialog self))
317
406
 
318
407
  (method (handleEvent event &tmp item v c)
@@ -336,6 +425,7 @@
336
425
  (items eachElementDo: #dispose)
337
426
  (items dispose:)
338
427
  (= items 0))
428
+ (if panel (panel dispose:) (= panel 0))
339
429
  (super dispose:)))
340
430
 
341
431
  ;; A conversation with something: talking to it opens a menu of topics, choosing one says
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sci2-ts",
3
- "version": "0.10.0",
3
+ "version": "0.12.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",
@@ -837,6 +837,8 @@ class RoomCompiler {
837
837
  "&rest 1", `super ${p.class} 12`,
838
838
  "pushi #setPri", "push1", "pTos priority", "ldi 1", "add", "push", `lofsa @${key}Mouth`, "send 6",
839
839
  "pushi #setPri", "push1", "pTos priority", "ldi 1", "add", "push", `lofsa @${key}Eyes`, "send 6",
840
+ // Its body in the room, if a prop has its name: where it stands picks the portrait's side.
841
+ ...(p.who && this.spec.props?.[key] ? send("self", [["who", [{ obj: key }]]]) : []),
840
842
  "ret",
841
843
  ], "");
842
844
  }
@@ -975,6 +975,8 @@ class RoomCompiler {
975
975
  "&rest 1", `super ${p.class} 12`,
976
976
  "pushi #setPri", "push1", "pTos priority", "ldi 1", "add", "push", `lofsa @${key}Mouth`, "send 6",
977
977
  "pushi #setPri", "push1", "pTos priority", "ldi 1", "add", "push", `lofsa @${key}Eyes`, "send 6",
978
+ // Its body in the room, if a prop has its name: where it stands picks the portrait's side.
979
+ ...(p.who && this.spec.props?.[key] ? send("self", [["who", [{ obj: key }]]]) : []),
978
980
  "ret",
979
981
  ],
980
982
  "",
@@ -40,7 +40,8 @@ export interface Target {
40
40
  /**
41
41
  * A character with a portrait view: loop 0 the bust, loop 1 mouth shapes, loop 2 eyes
42
42
  * (cel 0 open), all cels the same size so the parts line up. The talker class gets
43
- * `init: mouth bust eyes frame`; parts are a View (bust) and Props at `at`.
43
+ * `init: mouth bust eyes frame`; parts are a View (bust) and Props at `at`. With `who`,
44
+ * the talker is also told the character's prop in the room, if it has one (`who:`).
44
45
  */
45
46
  portrait: {
46
47
  class: string;
@@ -48,6 +49,7 @@ export interface Target {
48
49
  at: [number, number];
49
50
  frameSignal: number;
50
51
  partSignal: number;
52
+ who?: boolean;
51
53
  };
52
54
  /**
53
55
  * Conversations: an instance of this class runs a menu of topics for a thing (e.g. a
@@ -37,9 +37,10 @@ export interface Target {
37
37
  /**
38
38
  * A character with a portrait view: loop 0 the bust, loop 1 mouth shapes, loop 2 eyes
39
39
  * (cel 0 open), all cels the same size so the parts line up. The talker class gets
40
- * `init: mouth bust eyes frame`; parts are a View (bust) and Props at `at`.
40
+ * `init: mouth bust eyes frame`; parts are a View (bust) and Props at `at`. With `who`,
41
+ * the talker is also told the character's prop in the room, if it has one (`who:`).
41
42
  */
42
- portrait: { class: string; props: Record<string, number>; at: [number, number]; frameSignal: number; partSignal: number };
43
+ portrait: { class: string; props: Record<string, number>; at: [number, number]; frameSignal: number; partSignal: number; who?: boolean };
43
44
  /**
44
45
  * Conversations: an instance of this class runs a menu of topics for a thing (e.g. a
45
46
  * Teller with `init: client modNum sayNoun verb rootNoun`; topic labels are messages at
@@ -31,8 +31,8 @@ export function libraryTarget(globals, items = {}) {
31
31
  heroTalker: 98,
32
32
  firstRoomTalker: 200,
33
33
  voice: { class: "Talker", props: {} },
34
- // Portraits stand at the top left, the text beside them.
35
- portrait: { class: "PortraitTalker", props: {}, at: [8, 8], frameSignal: 0, partSignal: 0 },
34
+ // Portraits stand at the top, left or right by where the character is (who:).
35
+ portrait: { class: "PortraitTalker", props: {}, at: [8, 8], frameSignal: 0, partSignal: 0, who: true },
36
36
  teller: { class: "Teller", props: {}, verb: 2 },
37
37
  forwardCycle: "Forward",
38
38
  closeUp: "CloseUp",
@@ -33,8 +33,8 @@ export function libraryTarget(globals: readonly string[], items: Record<string,
33
33
  heroTalker: 98,
34
34
  firstRoomTalker: 200,
35
35
  voice: { class: "Talker", props: {} },
36
- // Portraits stand at the top left, the text beside them.
37
- portrait: { class: "PortraitTalker", props: {}, at: [8, 8], frameSignal: 0, partSignal: 0 },
36
+ // Portraits stand at the top, left or right by where the character is (who:).
37
+ portrait: { class: "PortraitTalker", props: {}, at: [8, 8], frameSignal: 0, partSignal: 0, who: true },
38
38
  teller: { class: "Teller", props: {}, verb: 2 },
39
39
  forwardCycle: "Forward",
40
40
  closeUp: "CloseUp",