sci2-ts 0.4.0 → 0.5.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.5.0](https://github.com/rlueder/sci-ts/compare/v0.4.0...v0.5.0) (2026-10-01)
8
+
9
+ ### Added
10
+
11
+ * **lib:** an icon bar for players without a right button ([#3](https://github.com/rlueder/sci-ts/issues/3)) ([a28d5b2](https://github.com/rlueder/sci-ts/commit/a28d5b244417af6985c0c82c825a3af29ad68067))
12
+
7
13
  ## [0.4.0](https://github.com/rlueder/sci-ts/compare/v0.3.0...v0.4.0) (2026-10-01)
8
14
 
9
15
  ### Added
package/lib/991.sc ADDED
@@ -0,0 +1,102 @@
1
+ ;;; The icon bar: the verbs, the item, the inventory and the game menu, for players without
2
+ ;;; a right mouse button (touch screens, trackpads).
3
+ (script 991)
4
+ (include "system.sh")
5
+
6
+ ;; An icon: a View hit anywhere in its square, not only where it's drawn. Its `actions`
7
+ ;; says what it stands for.
8
+ (class Icon of View
9
+ (properties
10
+ size ICON_SIZE)
11
+
12
+ (method (onMe theX theY)
13
+ (return (and (>= theX x) (< theX (+ x size)) (>= theY y) (< theY (+ y size))))))
14
+
15
+ ;; Slides down when the pointer reaches the top edge of the screen, or on a tap there. Its
16
+ ;; icons are the cels of `view`, loop 0 as they are and loop 1 picked, each ICON_SIZE
17
+ ;; square and anchored at the top-left corner: walk, look, do, talk, the inventory, the
18
+ ;; game menu (the item in use shows its own icon). A game with its own sets view in its
19
+ ;; init: (iconBar view: 266). Picking one, or a click below the bar, closes it.
20
+ (class IconBar of Obj
21
+ (properties
22
+ view ICON_BAR_VIEW
23
+ box 0
24
+ icons 0
25
+ height 0)
26
+
27
+ (method (show &tmp x)
28
+ (if box (return))
29
+ (= box
30
+ ((TextItem new:)
31
+ text: ""
32
+ width: SCREEN_WIDTH
33
+ height: (+ ICON_SIZE (* 2 ICON_GAP) (* 2 (textStyle margin?)))
34
+ x: 0 y: 0
35
+ yourself:))
36
+ (if (!= (textStyle frame?) -1)
37
+ (box height: (+ (box height?) (CelHigh (textStyle frame?) 0 4) (CelHigh (textStyle frame?) 0 5))))
38
+ (box init:)
39
+ (= height (box height?))
40
+ (= icons (List new:))
41
+ (= x (+ (box textLeft?) ICON_GAP))
42
+ ;; The verbs, then the item, the inventory and the menu.
43
+ (self addIcon: x 0 V_WALK)
44
+ (+= x (+ ICON_SIZE ICON_GAP))
45
+ (self addIcon: x 1 V_LOOK)
46
+ (+= x (+ ICON_SIZE ICON_GAP))
47
+ (self addIcon: x 2 V_DO)
48
+ (+= x (+ ICON_SIZE ICON_GAP))
49
+ (self addIcon: x 3 V_TALK)
50
+ (+= x (+ ICON_SIZE ICON_GAP))
51
+ (self addIcon: x -1 V_ITEM)
52
+ (+= x (+ ICON_SIZE ICON_GAP))
53
+ (self addIcon: x 4 ICON_INVENTORY)
54
+ (+= x (+ ICON_SIZE ICON_GAP))
55
+ (self addIcon: x 5 ICON_MENU)
56
+ (= dialog self))
57
+
58
+ ;; An icon at x: cel c of the bar's view (-1: the item in use), standing for `what`.
59
+ (method (addIcon theX c what &tmp icon)
60
+ (if (and (== c -1) (not theItem)) (return))
61
+ (= icon
62
+ ((Icon new:)
63
+ view: (if (== c -1) (theItem view?) else view)
64
+ loop: (if (and (!= c -1) (== what (user verb?))) 1 else 0)
65
+ cel: (if (== c -1) 0 else c)
66
+ actions: what
67
+ x: theX
68
+ y: (+ (box textTop?) ICON_GAP)
69
+ yourself:))
70
+ (icon init:)
71
+ (icon plane: uiPlane setPri: (+ (box priority?) 1))
72
+ (icons add: icon))
73
+
74
+ (method (hide)
75
+ (if (== dialog self) (= dialog 0))
76
+ (if icons
77
+ (icons eachElementDo: #dispose)
78
+ (icons dispose:)
79
+ (= icons 0))
80
+ (if box (box dispose:) (= box 0)))
81
+
82
+ (method (dismiss)
83
+ (self hide:))
84
+
85
+ ;; While it's open, the pointer going well below it closes it too.
86
+ (method (pointerAt theX theY)
87
+ (if (and box (> theY (+ height ICON_BAR_SLACK))) (self hide:)))
88
+
89
+ (method (handleEvent event &tmp icon what)
90
+ (event claimed: TRUE)
91
+ (if (!= (event type?) EV_MOUSE_DOWN) (return TRUE))
92
+ (= icon (icons firstTrue: #onMe (event x?) (event y?)))
93
+ (if (not icon)
94
+ (self hide:)
95
+ (return TRUE))
96
+ (= what (icon actions?))
97
+ (self hide:)
98
+ (switch what
99
+ (ICON_INVENTORY (inventory showSelf:))
100
+ (ICON_MENU (game showMenu:))
101
+ (else (user setVerb: what)))
102
+ (return TRUE)))
package/lib/993.sc CHANGED
@@ -47,7 +47,7 @@
47
47
  (for ((= node (FirstNode elements))) node ((= node (NextNode node)))
48
48
  (= item (NodeValue node))
49
49
  ;; The icon keeps its item in `actions`.
50
- (= icon ((View new:) view: (item view?) loop: 0 cel: 0 actions: item x: x y: (+ (window y?) (window textTop?) INV_GAP) yourself:))
50
+ (= icon ((Icon new:) view: (item view?) loop: 0 cel: 0 actions: item x: x y: (+ (window y?) (window textTop?) INV_GAP) yourself:))
51
51
  (icon init:)
52
52
  (icon plane: uiPlane setPri: (+ (window priority?) 1))
53
53
  (icons add: icon)
package/lib/995.sc CHANGED
@@ -70,7 +70,11 @@
70
70
  (method (doit)
71
71
  (self showCursor:)
72
72
  (while (GetEvent EV_ALL theEvent)
73
- (self handleEvent: theEvent)))
73
+ (self handleEvent: theEvent))
74
+ ;; theEvent now says where the pointer is: at the top edge, the icon bar comes down.
75
+ (cond
76
+ ((== dialog iconBar) (iconBar pointerAt: (theEvent x?) (theEvent y?)))
77
+ ((and canInput (not dialog) (not talking) (<= (theEvent y?) ICON_BAR_EDGE)) (iconBar show:))))
74
78
 
75
79
  ;; The verb right-click goes to: walk, do, look, talk, the item picked (if any), walk.
76
80
  (method (nextVerb)
@@ -101,6 +105,11 @@
101
105
  (if (& (event modifiers?) MOD_RIGHT)
102
106
  (self setVerb: (self nextVerb:))
103
107
  (return))
108
+ ;; A tap at the top edge (touch has no pointer to hover there) opens the icon bar.
109
+ (if (<= (event y?) (* 3 ICON_BAR_EDGE))
110
+ (event claimed: TRUE)
111
+ (iconBar show:)
112
+ (return))
104
113
  (= ex (event x?))
105
114
  (= ey (event y?))
106
115
  (if (== verb V_WALK)
package/lib/998.sc CHANGED
@@ -33,6 +33,7 @@
33
33
  (= narrator (Narrator new:))
34
34
  (= textStyle (TextStyle new:))
35
35
  (= inventory (Inventory new:))
36
+ (= iconBar (IconBar new:))
36
37
  (= music (Sound new:))
37
38
  (= sfx (Sound new:))
38
39
  (= uiPlane ((Plane new:) priority: 200 picture: -2 yourself:))
package/lib/999.sc CHANGED
@@ -30,7 +30,8 @@
30
30
  [gameFlags 64] ; story flags, a bit each
31
31
  textStyle ; how text boxes and menus look (a TextStyle)
32
32
  inventory ; what the hero carries (an Inventory)
33
- theItem) ; the item picked to use, or 0
33
+ theItem ; the item picked to use, or 0
34
+ iconBar) ; the icon bar (an IconBar)
34
35
 
35
36
  (procedure (SetFlag n)
36
37
  (|= [gameFlags (/ n 16)] (<< 1 (mod n 16))))
package/lib/resources.js CHANGED
@@ -106,9 +106,50 @@ function shade() {
106
106
  const cel = { width, height, displaceX: width >> 1, displaceY: height - 1, skipColor: Colour.Transparent, pixels: new Uint8Array(width * height).fill(253) };
107
107
  return writeView({ flags: 1, loops: [{ link: -1, mirror: false, cels: [cel] }], palette: undefined });
108
108
  }
109
+ /**
110
+ * The icon bar's icons (ICON_BAR_VIEW): walk, look, do, talk, the inventory and the game
111
+ * menu, each 24 pixels square and anchored at the top-left corner; loop 0 as they are,
112
+ * loop 1 picked (inverted). The verbs reuse their cursors' drawings.
113
+ */
114
+ const ICON_BAR_VIEW = 990;
115
+ const ICON = 24;
116
+ const BAG = [
117
+ "...XXXXX...",
118
+ "..X.....X..",
119
+ "..X.....X..",
120
+ "XXXXXXXXXXX",
121
+ "XWWWWWWWWWX",
122
+ "XWWWWWWWWWX",
123
+ "XWWWXXXWWWX",
124
+ "XWWWWWWWWWX",
125
+ "XWWWWWWWWWX",
126
+ "XXXXXXXXXXX",
127
+ ];
128
+ const MENU = ["XXXXXXXXXXX", "", "", "XXXXXXXXXXX", "", "", "XXXXXXXXXXX"].map((r) => r || "...........");
129
+ function iconBar() {
130
+ const drawings = [CURSORS[3].art, CURSORS[1].art, CURSORS[4].art, CURSORS[2].art, BAG, MENU];
131
+ const icon = (art, picked) => {
132
+ const [ink, paper] = picked ? [Colour.White, Colour.Black] : [Colour.Black, Colour.White];
133
+ const pixels = new Uint8Array(ICON * ICON);
134
+ for (let y = 0; y < ICON; y++)
135
+ for (let x = 0; x < ICON; x++)
136
+ pixels[y * ICON + x] = x === 0 || y === 0 || x === ICON - 1 || y === ICON - 1 ? Colour.Black : paper;
137
+ const top = (ICON - art.length) >> 1, left = (ICON - art[0].length) >> 1;
138
+ art.forEach((row, y) => [...row].forEach((c, x) => {
139
+ if (c === "X")
140
+ pixels[(top + y) * ICON + left + x] = ink;
141
+ else if (c === "W")
142
+ pixels[(top + y) * ICON + left + x] = paper;
143
+ }));
144
+ return { width: ICON, height: ICON, displaceX: ICON >> 1, displaceY: ICON - 1, skipColor: Colour.Transparent, pixels };
145
+ };
146
+ const loop = (picked) => ({ link: -1, mirror: false, cels: drawings.map((art) => icon(art, picked)) });
147
+ return writeView({ flags: 1, loops: [loop(false), loop(true)], palette: undefined });
148
+ }
109
149
  export default function resources() {
110
150
  return [
111
151
  ...Object.entries(CURSORS).map(([verb, c]) => ({ type: ResourceType.View, number: CURSOR_BASE + Number(verb), data: cursor(c.art, c.hotspot) })),
112
152
  { type: ResourceType.View, number: CLOSE_UP_SHADE, data: shade() },
153
+ { type: ResourceType.View, number: ICON_BAR_VIEW, data: iconBar() },
113
154
  ];
114
155
  }
package/lib/resources.ts CHANGED
@@ -111,9 +111,47 @@ function shade() {
111
111
  return writeView({ flags: 1, loops: [{ link: -1, mirror: false, cels: [cel] }], palette: undefined });
112
112
  }
113
113
 
114
+ /**
115
+ * The icon bar's icons (ICON_BAR_VIEW): walk, look, do, talk, the inventory and the game
116
+ * menu, each 24 pixels square and anchored at the top-left corner; loop 0 as they are,
117
+ * loop 1 picked (inverted). The verbs reuse their cursors' drawings.
118
+ */
119
+ const ICON_BAR_VIEW = 990;
120
+ const ICON = 24;
121
+ const BAG = [
122
+ "...XXXXX...",
123
+ "..X.....X..",
124
+ "..X.....X..",
125
+ "XXXXXXXXXXX",
126
+ "XWWWWWWWWWX",
127
+ "XWWWWWWWWWX",
128
+ "XWWWXXXWWWX",
129
+ "XWWWWWWWWWX",
130
+ "XWWWWWWWWWX",
131
+ "XXXXXXXXXXX",
132
+ ];
133
+ const MENU = ["XXXXXXXXXXX", "", "", "XXXXXXXXXXX", "", "", "XXXXXXXXXXX"].map((r) => r || "...........");
134
+ function iconBar() {
135
+ const drawings = [CURSORS[3]!.art, CURSORS[1]!.art, CURSORS[4]!.art, CURSORS[2]!.art, BAG, MENU];
136
+ const icon = (art: string[], picked: boolean) => {
137
+ const [ink, paper] = picked ? [Colour.White, Colour.Black] : [Colour.Black, Colour.White];
138
+ const pixels = new Uint8Array(ICON * ICON);
139
+ for (let y = 0; y < ICON; y++) for (let x = 0; x < ICON; x++) pixels[y * ICON + x] = x === 0 || y === 0 || x === ICON - 1 || y === ICON - 1 ? Colour.Black : paper;
140
+ const top = (ICON - art.length) >> 1, left = (ICON - art[0]!.length) >> 1;
141
+ art.forEach((row, y) => [...row].forEach((c, x) => {
142
+ if (c === "X") pixels[(top + y) * ICON + left + x] = ink;
143
+ else if (c === "W") pixels[(top + y) * ICON + left + x] = paper;
144
+ }));
145
+ return { width: ICON, height: ICON, displaceX: ICON >> 1, displaceY: ICON - 1, skipColor: Colour.Transparent, pixels };
146
+ };
147
+ const loop = (picked: boolean) => ({ link: -1, mirror: false, cels: drawings.map((art) => icon(art, picked)) });
148
+ return writeView({ flags: 1, loops: [loop(false), loop(true)], palette: undefined });
149
+ }
150
+
114
151
  export default function resources(): ResourceData[] {
115
152
  return [
116
153
  ...Object.entries(CURSORS).map(([verb, c]) => ({ type: ResourceType.View, number: CURSOR_BASE + Number(verb), data: cursor(c.art, c.hotspot) })),
117
154
  { type: ResourceType.View, number: CLOSE_UP_SHADE, data: shade() },
155
+ { type: ResourceType.View, number: ICON_BAR_VIEW, data: iconBar() },
118
156
  ];
119
157
  }
package/lib/system.sh CHANGED
@@ -26,6 +26,17 @@
26
26
 
27
27
  ;; Saved games: a description is up to SAVE_DESCRIPTION - 1 characters; the menus show the
28
28
  ;; newest SAVES_SHOWN. What SaveRestore's open menu is for:
29
+ ;; The icon bar: icons are ICON_SIZE pixels square, ICON_GAP apart; it opens when the
30
+ ;; pointer is within ICON_BAR_EDGE pixels of the top and closes ICON_BAR_SLACK below it.
31
+ ;; Besides verbs, an icon can stand for the inventory or the game menu.
32
+ (define ICON_SIZE 24)
33
+ (define ICON_GAP 4)
34
+ (define ICON_BAR_VIEW 990)
35
+ (define ICON_BAR_EDGE 3)
36
+ (define ICON_BAR_SLACK 12)
37
+ (define ICON_INVENTORY 100)
38
+ (define ICON_MENU 101)
39
+
29
40
  (define SAVE_DESCRIPTION 36)
30
41
  (define SAVES_SHOWN 8)
31
42
  (define SR_MENU 1)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sci2-ts",
3
- "version": "0.4.0",
3
+ "version": "0.5.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",