sci2-ts 0.17.0 → 0.18.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,12 @@ 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.18.0](https://github.com/rlueder/sci-ts/compare/v0.17.0...v0.18.0) (2026-10-03)
8
+
9
+ ### Added
10
+
11
+ * **lib:** painted game menu with lit rows, arrow keys, Escape and unavailable choices ([#21](https://github.com/rlueder/sci-ts/issues/21)) ([17dfd28](https://github.com/rlueder/sci-ts/commit/17dfd28c0f16379714a058869b7b698dc5338344))
12
+
7
13
  ## [0.17.0](https://github.com/rlueder/sci-ts/compare/v0.16.0...v0.17.0) (2026-10-02)
8
14
 
9
15
  ### Added
package/lib/992.sc CHANGED
@@ -51,14 +51,18 @@
51
51
  field 0 ; the description being typed
52
52
  slot 0) ; the save it goes into
53
53
 
54
+ ;; Painted with the textStyle's menuSkin, if it has one; Restore is unavailable there until
55
+ ;; there's a save.
54
56
  (method (showMenu &tmp menu)
55
57
  (= mode SR_MENU)
56
- (= menu (Menu new:))
58
+ (self load:)
59
+ (= menu ((Menu new:) painted: TRUE yourself:))
57
60
  (menu
58
61
  add: SR_SAVE "Save the game"
59
62
  add: SR_RESTORE "Restore a game"
60
63
  add: SR_RESTART "Start again"
61
64
  add: SR_TEXT (String STRING_FORMAT "Text speed: %s" (self textSpeedName:)))
65
+ (if (not count) ((menu valued: SR_RESTORE) disabled: TRUE))
62
66
  ;; Only a game with recorded lines asks how to have them.
63
67
  (if (DoAudio 9)
64
68
  (menu add: SR_SPEECH (String STRING_FORMAT "Speech: %s" (self speechName:))))
package/lib/995.sc CHANGED
@@ -76,9 +76,10 @@
76
76
  (if (and iconBar (iconBar box?)) (iconBar step:))
77
77
  (while (GetEvent EV_ALL theEvent)
78
78
  (self handleEvent: theEvent))
79
- ;; theEvent now says where the pointer is: at the top edge, the icon bar comes down.
79
+ ;; theEvent now says where the pointer is: an open menu or the icon bar follows it, and at
80
+ ;; the top edge the icon bar comes down.
80
81
  (cond
81
- ((== dialog iconBar) (iconBar pointerAt: (theEvent x?) (theEvent y?)))
82
+ ((and dialog (dialog respondsTo: #pointerAt)) (dialog pointerAt: (theEvent x?) (theEvent y?)))
82
83
  ((and canInput (not dialog) (not talking) (<= (theEvent y?) ICON_BAR_EDGE)) (iconBar show:))))
83
84
 
84
85
  ;; The verb right-click goes to: walk, do, look, talk, the item picked (if any), walk.
package/lib/996.sc CHANGED
@@ -10,6 +10,14 @@
10
10
  ;; A frame is a view whose loop 0 has eight cels, each anchored at its top-left corner: the
11
11
  ;; corners (top-left, top-right, bottom-left, bottom-right), then the edges (top, bottom, left,
12
12
  ;; right). The edges are tiled between the corners; transparent pixels show the paper.
13
+ ;;
14
+ ;; The game menu can be painted instead (the casebook, say): `menuSkin` is a view whose loop
15
+ ;; 0, cel 0 is the panel, its title painted in, loop 1 the row behind a choice and loop 2 the
16
+ ;; bullet before it, each with a cel for a choice as it is, under the pointer or keyboard
17
+ ;; focus, pressed and unavailable; all anchored at their top-left corners. The panel goes at
18
+ ;; menuX, menuY (-1: centred), the rows from menuRowX, menuRowY down by menuRowStep, the
19
+ ;; bullet and the text at their offsets in the row. The text is in the four menu colours.
20
+ ;; (textStyle menuSkin: 269 menuX: 38 menuY: 11 menuRowX: 10 menuRowY: 29 menuRowStep: 22)
13
21
  (class TextStyle of Obj
14
22
  (properties
15
23
  font 0
@@ -21,7 +29,16 @@
21
29
  portraitFrame -1 ; a view around portraits: loop 0 behind the face, loop 1 over its edges
22
30
  portraitX 8 ; where a portrait's face goes on the left (mirrored on the right)
23
31
  portraitY 8 ; its top; below 0, its bottom that far above the screen's, its text with it
24
- nameFont -1)) ; the font for a speaker's name before their line (-1: the text's)
32
+ nameFont -1 ; the font for a speaker's name before their line (-1: the text's)
33
+ menuSkin -1 ; the game menu's painted panel, rows and bullets, or -1 (above)
34
+ menuX -1 menuY -1
35
+ menuRowX 10 menuRowY 29 menuRowStep 22
36
+ menuBulletX 5 menuBulletY 5
37
+ menuTextX 25 menuTextY 4
38
+ menuFore -1 ; the text of a choice, under the pointer, pressed, unavailable (-1: fore)
39
+ menuHover -1
40
+ menuPressed -1
41
+ menuDisabled -1))
25
42
 
26
43
  ;; A box of text over the room: drawn by the interpreter into a bitmap, shown as a screen
27
44
  ;; item in the UI plane. Set text (and width, x, y), then init; it's as tall as the text
@@ -44,6 +61,7 @@
44
61
  borderColor -2 ; -1: none
45
62
  frame -2 ; -1: none
46
63
  textLeft 0 textTop 0 textRight -1 textBottom -1
64
+ margin -1 ; between the text and the edge, or the frame (-1: the textStyle's)
47
65
  width 200
48
66
  height 0
49
67
  bottom -1) ; -1: placed at y
@@ -55,7 +73,7 @@
55
73
  (if (== frame -2) (= frame (textStyle frame?)))
56
74
  (if (== borderColor -2) (= borderColor (if (!= frame -1) -1 else (textStyle border?))))
57
75
  ;; As tall as the text needs, inside the margin and the frame.
58
- (= m (textStyle margin?))
76
+ (= m (if (== margin -1) (textStyle margin?) else margin))
59
77
  (= padL m) (= padT m) (= padR m) (= padB m)
60
78
  (if (!= frame -1)
61
79
  (+= padL (CelWide frame 0 6))
@@ -389,19 +407,57 @@
389
407
  (method (cue)
390
408
  (self sayNext:)))
391
409
 
392
- ;; One choice in a Menu.
410
+ ;; One choice in a Menu. In a painted menu it has a row and a bullet behind it, and is in one
411
+ ;; of the skin's states (MENU_NORMAL, MENU_HOVER, MENU_PRESSED, MENU_DISABLED).
393
412
  (class MenuItem of TextItem
394
413
  (properties
395
- value 0)
414
+ value 0
415
+ disabled 0 ; can't be chosen: shown unavailable in a painted menu (a plain one ignores it)
416
+ row 0
417
+ bullet 0
418
+ state -1)
419
+
420
+ ;; Anywhere on its row, if it has one.
421
+ (method (onMe theX theY &tmp v)
422
+ (if (not row) (return (super onMe: theX theY)))
423
+ (= v (row view?))
424
+ (return
425
+ (and (>= theX (row x?)) (< theX (+ (row x?) (CelWide v 1 0)))
426
+ (>= theY (row y?)) (< theY (+ (row y?) (CelHigh v 1 0))))))
427
+
428
+ ;; Its row, bullet and text as they look in state st.
429
+ (method (setState st &tmp c)
430
+ (if (or (not row) (== st state)) (return))
431
+ (= state st)
432
+ (row setCel: st)
433
+ (bullet setCel: st)
434
+ (= c
435
+ (switch st
436
+ (MENU_HOVER (textStyle menuHover?))
437
+ (MENU_PRESSED (textStyle menuPressed?))
438
+ (MENU_DISABLED (textStyle menuDisabled?))
439
+ (else (textStyle menuFore?))))
440
+ (= fore (if (== c -1) (textStyle fore?) else c))
441
+ (if bitmap
442
+ (Bitmap BITMAP_DISPOSE bitmap)
443
+ (= bitmap (CreateTextBitmap 0 width height self))
444
+ (UpdateScreenItem self)))
396
445
 
397
446
  (method (dispose)
447
+ (if row (row dispose:) (= row 0))
448
+ (if bullet (bullet dispose:) (= bullet 0))
398
449
  ;; Its text was made for it (a literal string is left alone).
399
450
  (String ARRAY_FREE text)
400
451
  (super dispose:)))
401
452
 
402
- ;; Choices one under another in a box (the textStyle's frame around them all); the first
403
- ;; click on one ends it. While it's open it gets every click (it's the `dialog`). The caller
404
- ;; hears the choice by `choose: value`.
453
+ ;; Choices one under another in a box (the textStyle's frame around them all); a click on one
454
+ ;; ends it, and Escape chooses the one worth 0 (Cancel, Carry on, Goodbye). While it's open it
455
+ ;; gets every event (it's the `dialog`). The caller hears the choice by `choose: value`.
456
+ ;;
457
+ ;; A `painted` menu is drawn with the textStyle's menuSkin instead, if it has one and the
458
+ ;; choices fit on its panel: each on its row, lit under the pointer, pressed while the button
459
+ ;; is down; the arrow keys move between them and Enter or Space chooses, and an unavailable
460
+ ;; one is passed over.
405
461
  (class Menu of Obj
406
462
  (properties
407
463
  items 0
@@ -409,56 +465,175 @@
409
465
  panel 0
410
466
  y 20 ; the top of the box
411
467
  width 240
412
- next 0) ; where the next choice goes
468
+ next 0 ; where the next choice goes
469
+ painted 0 ; drawn with the textStyle's menuSkin, if it fits
470
+ skin -1 ; the skin it's drawn with, or -1
471
+ focus 0 ; the choice that's lit
472
+ pressed 0 ; the choice the button went down on
473
+ pointerX -1 pointerY -1)
474
+
475
+ (method (add v txt &tmp item)
476
+ (if (not items) (= items (List new:)))
477
+ (= item ((MenuItem new:) value: v text: txt yourself:))
478
+ (items add: item)
479
+ (return self))
413
480
 
414
- (method (add v txt &tmp item f)
481
+ ;; The choices on their box, then it waits for one.
482
+ (method (show whoCares &tmp s)
483
+ (= caller whoCares)
484
+ (if (not items) (return))
485
+ (= s (textStyle menuSkin?))
486
+ (if (and painted (!= s -1)
487
+ (<= (+ (textStyle menuRowY?) (* (items size?) (textStyle menuRowStep?))) (CelHigh s 0 0)))
488
+ (= skin s)
489
+ (self showPainted:)
490
+ else
491
+ (self showPlain:))
492
+ (= dialog self))
493
+
494
+ (method (showPlain &tmp f node item)
415
495
  (= f (textStyle frame?))
416
- (if (not items)
417
- (= items (List new:))
418
- (= next (+ y (if (!= f -1) (CelHigh f 0 4) else 1))))
419
- (= item
420
- ((MenuItem new:)
421
- value: v
422
- text: txt
496
+ (= next (+ y (if (!= f -1) (CelHigh f 0 4) else 1)))
497
+ (for ((= node (FirstNode (items elements?)))) node ((= node (NextNode node)))
498
+ (= item (NodeValue node))
499
+ (item
423
500
  frame: -1
424
501
  borderColor: -1
425
502
  width: (- width (if (!= f -1) (+ (CelWide f 0 6) (CelWide f 0 7)) else 2))
426
503
  x: (+ (/ (- SCREEN_WIDTH width) 2) (if (!= f -1) (CelWide f 0 6) else 1))
427
504
  y: next
505
+ disabled: 0)
506
+ (item init:)
507
+ (+= next (item height?)))
508
+ (= panel
509
+ ((TextItem new:)
510
+ text: ""
511
+ x: (/ (- SCREEN_WIDTH width) 2)
512
+ y: y
513
+ width: width
514
+ height: (+ (- next y) (if (!= f -1) (CelHigh f 0 5) else 1))
515
+ priority: 99
428
516
  yourself:))
429
- (item init:)
430
- (+= next (item height?))
431
- (items add: item)
432
- (return self))
433
-
434
- ;; The box behind the choices, then it waits for one.
435
- (method (show whoCares &tmp f)
436
- (= f (textStyle frame?))
437
- (= caller whoCares)
438
- (if items
439
- (= panel
440
- ((TextItem new:)
441
- text: ""
442
- x: (/ (- SCREEN_WIDTH width) 2)
443
- y: y
444
- width: width
445
- height: (+ (- next y) (if (!= f -1) (CelHigh f 0 5) else 1))
446
- priority: 99
447
- yourself:))
448
- (panel init:))
449
- (= dialog self))
450
-
451
- (method (handleEvent event &tmp item v c)
517
+ (panel init:))
518
+
519
+ (method (showPainted &tmp px py rx ry node item)
520
+ (= px (if (== (textStyle menuX?) -1) (/ (- SCREEN_WIDTH (CelWide skin 0 0)) 2) else (textStyle menuX?)))
521
+ (= py (if (== (textStyle menuY?) -1) (/ (- SCREEN_HEIGHT (CelHigh skin 0 0)) 2) else (textStyle menuY?)))
522
+ (= panel (self part: 0 0 px py 99))
523
+ (= rx (+ px (textStyle menuRowX?)))
524
+ (= ry (+ py (textStyle menuRowY?)))
525
+ (for ((= node (FirstNode (items elements?)))) node ((= node (NextNode node)))
526
+ (= item (NodeValue node))
527
+ (item
528
+ row: (self part: 1 0 rx ry 100)
529
+ bullet: (self part: 2 0 (+ rx (textStyle menuBulletX?)) (+ ry (textStyle menuBulletY?)) 101)
530
+ frame: -1
531
+ borderColor: -1
532
+ margin: 0
533
+ back: 254
534
+ x: (+ rx (textStyle menuTextX?))
535
+ y: (+ ry (textStyle menuTextY?))
536
+ width: (- (CelWide skin 1 0) (textStyle menuTextX?))
537
+ height: (- (CelHigh skin 1 0) (textStyle menuTextY?))
538
+ priority: 102)
539
+ (item setState: (if (item disabled?) MENU_DISABLED else MENU_NORMAL))
540
+ (item init:)
541
+ (+= ry (textStyle menuRowStep?))))
542
+
543
+ ;; A cel of the skin on the screen, over the room whether or not there is one.
544
+ (method (part l c theX theY pri &tmp p)
545
+ (= p
546
+ ((View new:)
547
+ view: skin loop: l cel: c x: theX y: theY
548
+ plane: uiPlane priority: pri fixPriority: 1
549
+ yourself:))
550
+ (cast add: p)
551
+ (AddScreenItem p)
552
+ (return p))
553
+
554
+ ;; Lights choice it (0: none), and only it.
555
+ (method (light it &tmp node item)
556
+ (= focus it)
557
+ (for ((= node (FirstNode (items elements?)))) node ((= node (NextNode node)))
558
+ (= item (NodeValue node))
559
+ (item setState:
560
+ (cond
561
+ ((item disabled?) MENU_DISABLED)
562
+ ((and (== item pressed) (== item it)) MENU_PRESSED)
563
+ ((== item it) MENU_HOVER)
564
+ (else MENU_NORMAL)))))
565
+
566
+ ;; The User says where the pointer is each cycle: when it moves, what's under it is lit.
567
+ (method (pointerAt theX theY &tmp item)
568
+ (if (or (== skin -1) (and (== theX pointerX) (== theY pointerY))) (return))
569
+ (= pointerX theX)
570
+ (= pointerY theY)
571
+ (= item (items firstTrue: #onMe theX theY))
572
+ (if (and item (item disabled?)) (= item 0))
573
+ (if (!= item focus) (self light: item)))
574
+
575
+ ;; The i-th choice, from 0.
576
+ (method (nth i &tmp node)
577
+ (for ((= node (FirstNode (items elements?)))) (and node i) ((= node (NextNode node)))
578
+ (-- i))
579
+ (return (if node (NodeValue node) else 0)))
580
+
581
+ ;; The choice worth v, or 0.
582
+ (method (valued v &tmp node)
583
+ (for ((= node (FirstNode (items elements?)))) node ((= node (NextNode node)))
584
+ (if (== ((NodeValue node) value?) v) (return (NodeValue node))))
585
+ (return 0))
586
+
587
+ ;; Lights the next choice that's available, dir 1 down or -1 up, round from the end.
588
+ (method (step dir &tmp i n k node item)
589
+ (= n (items size?))
590
+ (= i (if (> dir 0) -1 else n))
591
+ (= k 0)
592
+ (for ((= node (FirstNode (items elements?)))) node ((= node (NextNode node)))
593
+ (if (== (NodeValue node) focus) (= i k))
594
+ (++ k))
595
+ (for ((= k 0)) (< k n) ((++ k))
596
+ (= i (mod (+ i dir n) n))
597
+ (= item (self nth: i))
598
+ (if (not (item disabled?))
599
+ (self light: item)
600
+ (return))))
601
+
602
+ (method (handleEvent event &tmp item)
452
603
  (event claimed: TRUE)
453
- (if (!= (event type?) EV_MOUSE_DOWN) (return TRUE))
454
- (= item (items firstTrue: #onMe (event x?) (event y?)))
455
- (if item
456
- (= v (item value?))
457
- (= c caller)
458
- (self dispose:)
459
- (c choose: v))
604
+ (switch (event type?)
605
+ (EV_KEY_DOWN
606
+ (switch (event message?)
607
+ (KEY_ESCAPE (self pick: (self valued: 0)))
608
+ (KEY_UP (if (!= skin -1) (self step: -1)))
609
+ (KEY_DOWN (if (!= skin -1) (self step: 1)))
610
+ (KEY_ENTER (if focus (self pick: focus)))
611
+ (KEY_SPACE (if focus (self pick: focus)))))
612
+ (EV_MOUSE_DOWN
613
+ (= item (items firstTrue: #onMe (event x?) (event y?)))
614
+ (cond
615
+ ((== skin -1) (self pick: item))
616
+ ((and item (not (item disabled?)))
617
+ (= pressed item)
618
+ (self light: item))))
619
+ (EV_MOUSE_UP
620
+ (if pressed
621
+ (= item (items firstTrue: #onMe (event x?) (event y?)))
622
+ (if (== item pressed)
623
+ (self pick: item)
624
+ else
625
+ (= pressed 0)
626
+ (self light: (if (and item (not (item disabled?))) item else 0))))))
460
627
  (return TRUE))
461
628
 
629
+ ;; Ends it with that choice (none: it stays open).
630
+ (method (pick item &tmp v c)
631
+ (if (or (not item) (item disabled?)) (return))
632
+ (= v (item value?))
633
+ (= c caller)
634
+ (self dispose:)
635
+ (c choose: v))
636
+
462
637
  ;; Closed without a choice.
463
638
  (method (dismiss)
464
639
  (self dispose:))
package/lib/system.sh CHANGED
@@ -39,6 +39,12 @@
39
39
 
40
40
  (define SAVE_DESCRIPTION 36)
41
41
  (define SAVES_SHOWN 8)
42
+ ;; A painted menu choice's look: the cels of the skin's row and bullet loops.
43
+ (define MENU_NORMAL 0)
44
+ (define MENU_HOVER 1)
45
+ (define MENU_PRESSED 2)
46
+ (define MENU_DISABLED 3)
47
+
42
48
  (define SR_MENU 1)
43
49
  (define SR_SAVE 2)
44
50
  (define SR_RESTORE 3)
@@ -74,6 +80,8 @@
74
80
  (define KEY_SPACE 32)
75
81
  (define KEY_PERIOD 46)
76
82
  (define KEY_ESCAPE 27)
83
+ (define KEY_UP $4800)
84
+ (define KEY_DOWN $5000)
77
85
  (define KEY_F5 $3f00)
78
86
  (define KEY_F7 $4100)
79
87
  (define KEY_I 73)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sci2-ts",
3
- "version": "0.17.0",
3
+ "version": "0.18.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",