sci2-ts 0.7.0 → 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 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.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
+
7
13
  ## [0.7.0](https://github.com/rlueder/sci-ts/compare/v0.6.1...v0.7.0) (2026-10-01)
8
14
 
9
15
  ### Added
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
- ;; Back to walking: cycling as he moves, turning as he goes, seen.
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sci2-ts",
3
- "version": "0.7.0",
3
+ "version": "0.8.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",
@@ -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 }, [["init", []], ...(p.cycle === "forward" ? [["setCycle", [{ cls: this.target.forwardCycle }]]] : [])]));
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 }, [["init", []], ...(p.cycle === "forward" ? [["setCycle", [{ cls: this.target.forwardCycle }]] as [string, Val[]]] : [])]));
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][]]] : []),