@lewismoten/gamemaker-sandbox-engine 3.1.0 → 3.3.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 +42 -0
- package/NOTICE.md +58 -753
- package/README.md +94 -10
- package/dist/assets/{music.worklet-NqOeNo6T.js → music.worklet-aD2x7RRM.js} +23 -21
- package/dist/audio/cmfLoad.js +10 -0
- package/dist/audio/cmfPlayer.js +201 -0
- package/dist/audio/context.js +75 -0
- package/dist/audio/engine.js +173 -0
- package/dist/audio/mix.js +40 -0
- package/dist/audio/opl.js +245 -0
- package/dist/audio/output.js +40 -0
- package/dist/core/gameFiles.js +89 -0
- package/dist/engine/character.js +187 -0
- package/dist/engine/funcKeys.js +164 -0
- package/dist/engine/game.js +133 -0
- package/dist/engine/keys.js +68 -0
- package/dist/engine/machine.js +192 -0
- package/dist/engine/menus.js +135 -0
- package/dist/engine/monsters.js +259 -0
- package/dist/engine/playGain.js +10 -0
- package/dist/engine/playerLook.js +48 -0
- package/dist/engine/recorder.js +127 -0
- package/dist/engine/render.js +45 -0
- package/dist/engine/scene.js +324 -0
- package/dist/engine/scroll.js +79 -0
- package/dist/engine/showGraphic.js +107 -0
- package/dist/engine/slowPc.js +98 -0
- package/dist/engine/slowPcMusic.js +46 -0
- package/dist/engine/touch.js +61 -0
- package/dist/engine/world.js +205 -0
- package/dist/formats/binary.js +120 -0
- package/dist/formats/blocks.js +103 -0
- package/dist/formats/character.js +83 -0
- package/dist/formats/cmf.js +37 -0
- package/dist/formats/cmfEdit.js +154 -0
- package/dist/formats/cmfPreview.js +89 -0
- package/dist/formats/cmfSong.js +190 -0
- package/dist/formats/fli.js +340 -0
- package/dist/formats/fliFrames.js +34 -0
- package/dist/formats/fliLimits.js +54 -0
- package/dist/formats/game.js +198 -0
- package/dist/formats/gif.js +125 -0
- package/dist/formats/gifProgressive.js +99 -0
- package/dist/formats/graphicScripts.js +44 -0
- package/dist/formats/highscore.js +30 -0
- package/dist/formats/jpeg.js +359 -0
- package/dist/formats/map.js +38 -0
- package/dist/formats/monster.js +69 -0
- package/dist/formats/palette.js +58 -0
- package/dist/formats/recording.js +17 -0
- package/dist/formats/sound.js +44 -0
- package/dist/formats/voc.js +303 -0
- package/dist/gfx/colorVision.js +45 -0
- package/dist/gfx/cp437Glyphs.js +337 -0
- package/dist/gfx/font8x8.js +10 -0
- package/dist/gfx/screen.js +151 -0
- package/dist/index.js +15 -0
- package/dist/input/chords.js +46 -0
- package/dist/input/deskMouse.js +24 -0
- package/dist/input/keyboard.js +82 -0
- package/dist/input/scancodes.js +143 -0
- package/dist/player/fileExtensions.js +118 -0
- package/dist/player/patchRules.js +129 -0
- package/dist/player/patchTable.js +1753 -0
- package/dist/text/cp437.js +44 -0
- package/dist/types/audio/cmfPlayer.d.ts +1 -1
- package/dist/types/audio/engine.d.ts +21 -0
- package/dist/types/audio/mix.d.ts +14 -0
- package/dist/types/audio/music.worklet.d.ts +8 -0
- package/dist/types/engine/character.d.ts +3 -3
- package/dist/types/engine/funcKeys.d.ts +8 -6
- package/dist/types/engine/game.d.ts +9 -4
- package/dist/types/engine/keys.d.ts +2 -2
- package/dist/types/engine/machine.d.ts +8 -4
- package/dist/types/engine/menus.d.ts +4 -4
- package/dist/types/engine/monsters.d.ts +1 -1
- package/dist/types/engine/playerLook.d.ts +20 -2
- package/dist/types/engine/recorder.d.ts +8 -1
- package/dist/types/engine/render.d.ts +2 -2
- package/dist/types/engine/scene.d.ts +41 -9
- package/dist/types/engine/scroll.d.ts +9 -1
- package/dist/types/engine/showGraphic.d.ts +2 -2
- package/dist/types/engine/slowPc.d.ts +64 -2
- package/dist/types/engine/slowPcMusic.d.ts +18 -0
- package/dist/types/engine/touch.d.ts +2 -2
- package/dist/types/engine/world.d.ts +28 -6
- package/dist/types/formats/cmfEdit.d.ts +53 -0
- package/dist/types/formats/cmfPreview.d.ts +19 -0
- package/dist/types/formats/cmfSong.d.ts +74 -0
- package/dist/types/formats/fli.d.ts +11 -0
- package/dist/types/formats/fliFrames.d.ts +18 -0
- package/dist/types/formats/fliLimits.d.ts +13 -0
- package/dist/types/formats/game.d.ts +7 -0
- package/dist/types/formats/gif.d.ts +1 -1
- package/dist/types/formats/gifProgressive.d.ts +46 -0
- package/dist/types/formats/jpeg.d.ts +20 -0
- package/dist/types/gfx/colorVision.d.ts +26 -0
- package/dist/types/gfx/screen.d.ts +4 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/input/keyboard.d.ts +1 -1
- package/dist/types/{dosPlayer → player}/patchRules.d.ts +1 -1
- package/dist/types/{dosPlayer → player}/patchTable.d.ts +8 -5
- package/dist/types/text/cp437.d.ts +6 -0
- package/package.json +322 -9
- package/dist/engine.js +0 -5436
- package/dist/types/engine/package.d.ts +0 -4
- /package/dist/types/{project → core}/gameFiles.d.ts +0 -0
- /package/dist/types/{dosPlayer → player}/fileExtensions.d.ts +0 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const r = "\0☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ ", c = /* @__PURE__ */ new Map();
|
|
2
|
+
for (let t = 1; t < 256; t++) (t < 32 || t > 126) && c.set(r[t], t);
|
|
3
|
+
for (const [t, o] of [
|
|
4
|
+
["β", 225],
|
|
5
|
+
// β beta for ß
|
|
6
|
+
["μ", 230],
|
|
7
|
+
// μ mu for µ
|
|
8
|
+
["Ω", 234],
|
|
9
|
+
// Ω ohm for Ω
|
|
10
|
+
["∑", 228],
|
|
11
|
+
// ∑ sum for Σ
|
|
12
|
+
["ϕ", 237],
|
|
13
|
+
// ϕ for φ
|
|
14
|
+
["∈", 238]
|
|
15
|
+
// ∈ for ε
|
|
16
|
+
])
|
|
17
|
+
c.set(t, o);
|
|
18
|
+
const l = { "–": "-", "—": "--", "‘": "'", "’": "'", "“": '"', "”": '"', "…": "...", "×": "x" };
|
|
19
|
+
function C(t) {
|
|
20
|
+
let o = "";
|
|
21
|
+
for (const e of t) {
|
|
22
|
+
const n = c.get(e);
|
|
23
|
+
o += n !== void 0 ? String.fromCharCode(n) : l[e] ?? e;
|
|
24
|
+
}
|
|
25
|
+
return o;
|
|
26
|
+
}
|
|
27
|
+
const f = /* @__PURE__ */ new Map(), i = new TextDecoder("latin1");
|
|
28
|
+
for (let t = 128; t < 160; t++) f.set(i.decode(Uint8Array.of(t)), t);
|
|
29
|
+
function s(t) {
|
|
30
|
+
let o = "";
|
|
31
|
+
for (let e = 0; e < t.length; e++) {
|
|
32
|
+
const n = f.get(t[e]) ?? t.charCodeAt(e);
|
|
33
|
+
o += n < 256 ? r[n] : t[e];
|
|
34
|
+
}
|
|
35
|
+
return o;
|
|
36
|
+
}
|
|
37
|
+
function a(t) {
|
|
38
|
+
return i.decode(t).replace(/[^\x00-\x7f]/g, (o) => s(o));
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
a as cp437Text,
|
|
42
|
+
s as fromCp437,
|
|
43
|
+
C as toCp437
|
|
44
|
+
};
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
export declare class AudioEngine {
|
|
2
2
|
private ctx?;
|
|
3
3
|
private master?;
|
|
4
|
+
/** Music and effects each go through their own gain, set by the visitor's sound settings (mix.ts). */
|
|
5
|
+
private musicGain?;
|
|
6
|
+
private sfxGain?;
|
|
7
|
+
/** Music and effects of a game being played also pass the Games setting (setGame). */
|
|
8
|
+
private gameGain?;
|
|
9
|
+
private game;
|
|
10
|
+
private level;
|
|
11
|
+
private unmix?;
|
|
4
12
|
private music?;
|
|
5
13
|
private musicReady?;
|
|
6
14
|
private speaker?;
|
|
@@ -8,12 +16,23 @@ export declare class AudioEngine {
|
|
|
8
16
|
private vocCache;
|
|
9
17
|
musicEnabled: boolean;
|
|
10
18
|
soundEnabled: boolean;
|
|
19
|
+
private cardEnabled;
|
|
20
|
+
private requestedSong?;
|
|
21
|
+
private loadedSong?;
|
|
22
|
+
get soundCardEnabled(): boolean;
|
|
23
|
+
/** Optional live host control: card removal pauses FM and stops digital effects, leaving the PC speaker alone. */
|
|
24
|
+
setSoundCard(enabled: boolean): void;
|
|
11
25
|
/**
|
|
12
26
|
* Starts the sound. The first time, call it from a user gesture's handler (iOS: inside
|
|
13
27
|
* the touchend/click itself, before any await); the page's one AudioContext (context.ts)
|
|
14
28
|
* is resumed there.
|
|
15
29
|
*/
|
|
16
30
|
resume(): Promise<void>;
|
|
31
|
+
/** Marks this engine as a game's: its sound also follows the Games setting (live). */
|
|
32
|
+
setGame(game: boolean): void;
|
|
33
|
+
/** A fixed trim (0 to 1) under the visitor's settings, for sound meant to sit quietly in the background. */
|
|
34
|
+
setLevel(level: number): void;
|
|
35
|
+
private gameLevel;
|
|
17
36
|
/** Where this engine's sound leaves it (for recording just this game's sound). */
|
|
18
37
|
get node(): AudioNode | undefined;
|
|
19
38
|
/**
|
|
@@ -28,6 +47,8 @@ export declare class AudioEngine {
|
|
|
28
47
|
/** Counts songs asked for, so one still waiting for the worklet doesn't start after a newer one (or a stop). */
|
|
29
48
|
private songs;
|
|
30
49
|
stopSong(): void;
|
|
50
|
+
/** The song stands still for `ms` (its notes keep sounding), then goes on from where it was: a slow PC's disk read. */
|
|
51
|
+
holdMusic(ms: number): void;
|
|
31
52
|
pauseMusic(paused: boolean): void;
|
|
32
53
|
/** DoSound's PC speaker path: one frequency per 1/145.6 s tick; 0 = silence. */
|
|
33
54
|
playSpeaker(freqs: ArrayLike<number>): void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type Channel = 'music' | 'voice' | 'sfx' | 'games';
|
|
2
|
+
export interface ChannelMix {
|
|
3
|
+
on: boolean;
|
|
4
|
+
/** 0 to 100. */
|
|
5
|
+
volume: number;
|
|
6
|
+
}
|
|
7
|
+
export type Mix = Record<Channel, ChannelMix>;
|
|
8
|
+
export declare const CHANNELS: readonly Channel[];
|
|
9
|
+
export declare const getMix: () => Mix;
|
|
10
|
+
/** The gain (0 to 1) a channel plays at now: 0 when it is off. */
|
|
11
|
+
export declare const channelGain: (c: Channel) => number;
|
|
12
|
+
export declare function setChannel(c: Channel, change: Partial<ChannelMix>): void;
|
|
13
|
+
/** Calls `f` whenever a setting changes; returns a function that stops it. */
|
|
14
|
+
export declare function onMix(f: (m: Mix) => void): () => void;
|
|
@@ -8,6 +8,14 @@ export type MusicMessage = {
|
|
|
8
8
|
type: 'pause';
|
|
9
9
|
paused: boolean;
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* An emulated slow PC is reading the disk with the timer held (engine/slowPc.ts readHoldsMusic): PlayIt
|
|
13
|
+
* gets no interrupts for `ms`, so the song stands still while the FM chip keeps sounding its notes.
|
|
14
|
+
*/
|
|
15
|
+
| {
|
|
16
|
+
type: 'hold';
|
|
17
|
+
ms: number;
|
|
18
|
+
}
|
|
11
19
|
/** The engine is done with this node (the page's context lives on): stop processing. */
|
|
12
20
|
| {
|
|
13
21
|
type: 'end';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type Frame } from '../formats/character';
|
|
2
|
-
import { type World } from './world';
|
|
1
|
+
import { type Frame } from '../formats/character.js';
|
|
2
|
+
import { type World } from './world.js';
|
|
3
3
|
/** Asks the player which inventory slot to drop; resolves -1 for Esc. */
|
|
4
4
|
type DropPicker = () => Promise<number>;
|
|
5
5
|
/**
|
|
@@ -8,7 +8,7 @@ type DropPicker = () => Promise<number>;
|
|
|
8
8
|
*/
|
|
9
9
|
export declare function currentFrame(w: World): Frame;
|
|
10
10
|
/**
|
|
11
|
-
* MoveCharGrav: every GRAVSPEED ticks, accelerate by one pixel a tick in each
|
|
11
|
+
* MoveCharGrav: every GRAVSPEED ticks (w.tune), accelerate by one pixel a tick in each
|
|
12
12
|
* direction the block's grav bits name (1 up, 2 right, 4 down, 8 left), capped at
|
|
13
13
|
* MAXGRAV * 4. Returns true while there is any gravity momentum.
|
|
14
14
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { Machine } from './machine';
|
|
2
|
-
import type { Screen } from '../gfx/screen';
|
|
3
|
-
import { type PlayerLook } from './playerLook';
|
|
4
|
-
import { type World } from './world';
|
|
1
|
+
import type { Machine } from './machine.js';
|
|
2
|
+
import type { Screen } from '../gfx/screen.js';
|
|
3
|
+
import { type PlayerLook } from './playerLook.js';
|
|
4
|
+
import { type World } from './world.js';
|
|
5
5
|
/** What the status and drop screens read of a level: its palette, blocks, inventory and clock. */
|
|
6
6
|
export type InventoryWorld = Pick<World, 'palette' | 'bg' | 'clock'> & {
|
|
7
7
|
ci: Pick<World['ci'], 'inv' | 'score' | 'meter' | 'hitpts' | 'lives'>;
|
|
@@ -16,8 +16,10 @@ export declare function helpScreen(m: Machine, w: World, instructions: string |
|
|
|
16
16
|
export declare function drawStatus(screen: Screen, w: InventoryWorld, look: PlayerLook): void;
|
|
17
17
|
/** F2: Display_Invent */
|
|
18
18
|
export declare function statusScreen(m: Machine, w: World, look?: PlayerLook): Promise<void>;
|
|
19
|
-
/**
|
|
20
|
-
export declare
|
|
19
|
+
/** How many pages GMabout shows (two, as the DOS player does). */
|
|
20
|
+
export declare const aboutPages: (look: PlayerLook) => number;
|
|
21
|
+
/** One page of GMabout (0: the first, as F10 opens it), framed; returns the text color. */
|
|
22
|
+
export declare function drawAbout(screen: Screen, palette: Uint8Array, look: PlayerLook, page?: number): number;
|
|
21
23
|
/** F10: GMabout */
|
|
22
24
|
export declare function aboutScreen(m: Machine, w: World, look?: PlayerLook): Promise<void>;
|
|
23
25
|
/** DropBloc's picker screen, with the selection box on slot (j, k); returns the box's color. */
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { Machine } from './machine';
|
|
2
|
-
import { type Session, type SpeedSetting } from './scene';
|
|
3
|
-
import type
|
|
4
|
-
import {
|
|
1
|
+
import { Machine } from './machine.js';
|
|
2
|
+
import { type Session, type SpeedSetting } from './scene.js';
|
|
3
|
+
import { type PlayerAbout } from './playerLook.js';
|
|
4
|
+
import type { PlayerPatches } from '../player/patchRules.js';
|
|
5
|
+
import { type Game } from '../formats/game.js';
|
|
5
6
|
export interface RunOptions {
|
|
6
7
|
/** Called when the player picks Quit; return true to show the menu again. */
|
|
7
8
|
onQuit?: () => Promise<boolean> | boolean;
|
|
@@ -31,10 +32,14 @@ export interface RunOptions {
|
|
|
31
32
|
* positions, times and F-keys as its patched .EXE shows them. Left out, the CD's player.
|
|
32
33
|
*/
|
|
33
34
|
player?: PlayerPatches;
|
|
35
|
+
/** An imported game's own player About page and first build (F10 shows them, read-only). */
|
|
36
|
+
playerAbout?: PlayerAbout;
|
|
34
37
|
/** A touch changed a map cell while the player played (never during a demo's playback). */
|
|
35
38
|
onTouched?: Session['onTouched'];
|
|
36
39
|
/** Play ended with this score (never during a demo's playback; see Session.onPlayEnd). */
|
|
37
40
|
onPlayEnd?: Session['onPlayEnd'];
|
|
41
|
+
/** A player lost a life, including game over; never reported during playback. */
|
|
42
|
+
onLifeLost?: Session['onLifeLost'];
|
|
38
43
|
/**
|
|
39
44
|
* The editor's Play Level test only (never a saved, exported or DOS game): skip the title, its
|
|
40
45
|
* song and the menu, and start play at once by scene 0's first link (which the test copy points
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type Frame, type Sequence } from '../formats/character';
|
|
2
|
-
import { type World } from './world';
|
|
1
|
+
import { type Frame, type Sequence } from '../formats/character.js';
|
|
2
|
+
import { type World } from './world.js';
|
|
3
3
|
export declare function chrSeq(w: World): Sequence;
|
|
4
4
|
/** CHRFRAME. cframe can be -1 between sequences; the original then read stray memory. */
|
|
5
5
|
export declare function chrFrame(w: World): Frame;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Screen } from '../gfx/screen';
|
|
2
|
-
import { Keyboard, type TypedKey } from '../input/keyboard';
|
|
3
|
-
import { AudioEngine } from '../audio/engine';
|
|
4
|
-
import { GameFiles } from '../
|
|
1
|
+
import { Screen } from '../gfx/screen.js';
|
|
2
|
+
import { Keyboard, type TypedKey } from '../input/keyboard.js';
|
|
3
|
+
import { AudioEngine } from '../audio/engine.js';
|
|
4
|
+
import { GameFiles } from '../core/gameFiles.js';
|
|
5
5
|
/** Thrown to unwind the game when the player is closed. */
|
|
6
6
|
export declare class GameAborted extends Error {
|
|
7
7
|
constructor();
|
|
@@ -29,6 +29,8 @@ export declare class Machine {
|
|
|
29
29
|
files: GameFiles;
|
|
30
30
|
readonly pointer: PointerState;
|
|
31
31
|
aborted: boolean;
|
|
32
|
+
/** Observer only (the walkthrough player's item marks): a picture, cartoon, text page or slide list is about to be shown. Never changes timing. */
|
|
33
|
+
onItem?: (kind: 'picture' | 'cartoon' | 'text' | 'slides', name: string) => void;
|
|
32
34
|
private frameWaiters;
|
|
33
35
|
private raf;
|
|
34
36
|
/** The ball mouse on the desk moves the pointer and clicks too. */
|
|
@@ -85,6 +87,8 @@ export declare class Machine {
|
|
|
85
87
|
keyPoll?: (polls: number) => number[];
|
|
86
88
|
/** Demo playback: a key-reading screen ended; returns recorded keys it must have taken. */
|
|
87
89
|
keyPollEnd?: () => number[];
|
|
90
|
+
/** Demo playback: one GetKey poll by a screen (tick-counted recordings); returns the recorded keys now due. */
|
|
91
|
+
keyPollOnce?: () => number[];
|
|
88
92
|
/** Clears keyboard input (while (GetKey(1)) GetKey(0)). */
|
|
89
93
|
flushKeys(): void;
|
|
90
94
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { Machine } from './machine';
|
|
2
|
-
import type { Screen } from '../gfx/screen';
|
|
3
|
-
import { type PlayerLook } from './playerLook';
|
|
4
|
-
import { type HighScore } from '../formats/highscore';
|
|
1
|
+
import type { Machine } from './machine.js';
|
|
2
|
+
import type { Screen } from '../gfx/screen.js';
|
|
3
|
+
import { type PlayerLook } from './playerLook.js';
|
|
4
|
+
import { type HighScore } from '../formats/highscore.js';
|
|
5
5
|
/**
|
|
6
6
|
* GVertMenu: a marker box left of `count` rows (`dy` pixels apart starting at y).
|
|
7
7
|
* Returns 1..count, 0 for Esc, count+1 when idle for `idleSeconds`. The C timed the
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import { type PlayerPatches } from '../
|
|
1
|
+
import { type PlayerPatches } from '../player/patchRules.js';
|
|
2
|
+
import { type PlayTuning } from './world.js';
|
|
3
|
+
/**
|
|
4
|
+
* An imported game's own player About page (F10), as that player drew it: read-only, since it is
|
|
5
|
+
* RSD's or the original author's attribution (dosPlayer/playerOrigin.ts).
|
|
6
|
+
*/
|
|
7
|
+
export interface PlayerAbout {
|
|
8
|
+
/** Its pages, each a list of [y, text] lines drawn at x 40; left out, this edition's own words. */
|
|
9
|
+
pages?: [number, string][][];
|
|
10
|
+
/** The build it was first made with, as lines for the last page ("Originally made with" / "Game-Maker 2.02."). */
|
|
11
|
+
origin?: string[];
|
|
12
|
+
}
|
|
2
13
|
type Rgb = [number, number, number];
|
|
3
14
|
type Frame = [number, number, number, number];
|
|
4
15
|
export interface PlayerLook {
|
|
@@ -17,10 +28,17 @@ export interface PlayerLook {
|
|
|
17
28
|
seconds(id: string): number;
|
|
18
29
|
/** False when the player's patches turned this F-key off. */
|
|
19
30
|
fkeyOn(key: number): boolean;
|
|
31
|
+
/** An imported game's own About page and first build, when it has them. */
|
|
32
|
+
about?: PlayerAbout;
|
|
20
33
|
}
|
|
21
34
|
/** sprintf for the player's formats: %d, %ld, %s with a width and the 0 and - flags. */
|
|
22
35
|
export declare function sprintf(format: string, args: (number | string)[]): string;
|
|
23
|
-
export declare function playerLook(patches?: PlayerPatches): PlayerLook;
|
|
36
|
+
export declare function playerLook(patches?: PlayerPatches, about?: PlayerAbout): PlayerLook;
|
|
37
|
+
/**
|
|
38
|
+
* The gameplay constants `look`'s patches give, for World.tune: with none, PLAYGAME.C's own
|
|
39
|
+
* #defines (STOCK_TUNING), so play is exactly as before.
|
|
40
|
+
*/
|
|
41
|
+
export declare function playTuning(look: PlayerLook): PlayTuning;
|
|
24
42
|
/** The CD player's own look. */
|
|
25
43
|
export declare const STOCK_LOOK: PlayerLook;
|
|
26
44
|
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type RecordedKey } from '../formats/recording';
|
|
1
|
+
import { type RecordedKey } from '../formats/recording.js';
|
|
2
2
|
export declare const RECORD = 1;
|
|
3
3
|
export declare class Recorder {
|
|
4
4
|
mode: number;
|
|
@@ -33,6 +33,13 @@ export declare class Recorder {
|
|
|
33
33
|
* Returns the recorded keys that become pending (only for recordings made in DOS).
|
|
34
34
|
*/
|
|
35
35
|
advanceWait(polls: number): number[];
|
|
36
|
+
/**
|
|
37
|
+
* One GetKey poll (DoPlayBack) by a key screen in a tick-counted recording, where waits
|
|
38
|
+
* otherwise don't count (advanceWait): the keys a demo gave that screen one tick after
|
|
39
|
+
* its opening key (DropBloc's Enter) become pending. Recordings made in DOS count their waits.
|
|
40
|
+
*/
|
|
41
|
+
pollOnce(): number[];
|
|
42
|
+
private advanceByTick;
|
|
36
43
|
/**
|
|
37
44
|
* A screen that read keys has finished. In DOS the next key may have come during it
|
|
38
45
|
* (the recording shows a gap of many thousands of polls), however fast that PC polled;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Screen } from '../gfx/screen';
|
|
2
|
-
import { type World } from './world';
|
|
1
|
+
import type { Screen } from '../gfx/screen.js';
|
|
2
|
+
import { type World } from './world.js';
|
|
3
3
|
/** CropScreen: the CRTC was reprogrammed to show only 312x196 pixels while playing. */
|
|
4
4
|
export declare const VISIBLE_W = 312;
|
|
5
5
|
export declare const VISIBLE_H = 196;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { Machine } from './machine';
|
|
2
|
-
import type { Recorder } from './recorder';
|
|
3
|
-
import { type PlayerLook } from './playerLook';
|
|
4
|
-
import { type CharInfo, type Coord, type World } from './world';
|
|
5
|
-
import { type SoundSet } from '../formats/sound';
|
|
6
|
-
import { type Game } from '../formats/game';
|
|
7
|
-
import { type PcCosts } from './slowPc';
|
|
8
|
-
import { type PlayGain } from './playGain';
|
|
1
|
+
import type { Machine } from './machine.js';
|
|
2
|
+
import type { Recorder } from './recorder.js';
|
|
3
|
+
import { type PlayerLook } from './playerLook.js';
|
|
4
|
+
import { type CharInfo, type Coord, type World } from './world.js';
|
|
5
|
+
import { type SoundSet } from '../formats/sound.js';
|
|
6
|
+
import { type Game } from '../formats/game.js';
|
|
7
|
+
import { type PcCosts } from './slowPc.js';
|
|
8
|
+
import { type PlayGain } from './playGain.js';
|
|
9
9
|
/** How a play ended: the Game Won scene, no lives left, or Esc (or a scene that could not load). */
|
|
10
10
|
type PlayEnd = 'won' | 'lost' | 'quit';
|
|
11
11
|
/** Everything that lives across scenes (playgame's globals outside a level). */
|
|
@@ -31,6 +31,20 @@ export interface Session {
|
|
|
31
31
|
onFrame?: (w: World) => void;
|
|
32
32
|
/** Emulated PC speed; read every tick so it can change while playing. */
|
|
33
33
|
speed: SpeedSetting;
|
|
34
|
+
/** Milliseconds a voice read still owes the emulated PC (SpeedSetting.slowPc); playscene charges it. */
|
|
35
|
+
vocStall?: number;
|
|
36
|
+
/** The last scene load the emulated slow disk was charged for (sceneLoadMs): files opened, bytes read, milliseconds. Cleared by a load that costs nothing. */
|
|
37
|
+
sceneLoad?: {
|
|
38
|
+
files: number;
|
|
39
|
+
bytes: number;
|
|
40
|
+
ms: number;
|
|
41
|
+
};
|
|
42
|
+
/** A voice read from the emulated slow disk that has not started yet: the last one asked for, its total wait, the time it is due. */
|
|
43
|
+
vocLate?: {
|
|
44
|
+
bytes: Uint8Array;
|
|
45
|
+
wait: number;
|
|
46
|
+
due?: number;
|
|
47
|
+
};
|
|
34
48
|
/** RunOptions.gmHelp: Game-Maker's own GMHELP.TXT, which F1 shows first (PLAYGAME.EXE only). */
|
|
35
49
|
gmHelp?: Uint8Array;
|
|
36
50
|
/** RunOptions.player as the screens use it (engine/playerLook.ts); left out, the CD player's look. */
|
|
@@ -47,6 +61,8 @@ export interface Session {
|
|
|
47
61
|
* `gained` is the points the play scored itself, leaving out those F6 loaded (playGain.ts).
|
|
48
62
|
*/
|
|
49
63
|
onPlayEnd?: (score: number, how: PlayEnd, gained: number) => void;
|
|
64
|
+
/** A player death, including the final life. Never called during recorded playback. */
|
|
65
|
+
onLifeLost?: () => void;
|
|
50
66
|
/** Not PLAYGAME's: the stretches of this play between F6 loads (playGain.ts). */
|
|
51
67
|
gain?: PlayGain;
|
|
52
68
|
/**
|
|
@@ -77,13 +93,29 @@ interface LoadedScene {
|
|
|
77
93
|
* 6 character blocks, 7 sound set, 8 song. In the C a slot left blank keeps whatever
|
|
78
94
|
* the previous scene loaded into the globals; `prev` stands in for those globals.
|
|
79
95
|
*/
|
|
80
|
-
export declare function loadScene(s: Pick<Session, 'game' | 'rand' | 'onTouched'> & {
|
|
96
|
+
export declare function loadScene(s: Pick<Session, 'game' | 'rand' | 'onTouched' | 'look'> & {
|
|
81
97
|
m: {
|
|
82
98
|
files: {
|
|
83
99
|
get(name: string): Uint8Array | undefined;
|
|
84
100
|
};
|
|
85
101
|
};
|
|
86
102
|
}, scnum: number, prev?: World): LoadedScene | string[];
|
|
103
|
+
export declare const LOADGAME_SLOTS: readonly number[];
|
|
104
|
+
/**
|
|
105
|
+
* The byte count of each file a scene load reads from disk, in the order it reads them: every named slot of
|
|
106
|
+
* `slots`, then the song when there is a card to play it on (loadscene only calls InitMusic with CARDEXIST).
|
|
107
|
+
* A blank slot opens nothing. A named file the disk does not have costs its directory search (0 bytes).
|
|
108
|
+
* The .snd counts whole (loadspecsnd reads its 2,430 byte table, which is the whole file). Nothing is cached:
|
|
109
|
+
* PLAYGAME reads a scene afresh every time it starts one, after a lost life too.
|
|
110
|
+
*/
|
|
111
|
+
export declare function sceneReads(s: {
|
|
112
|
+
game: Game;
|
|
113
|
+
m: {
|
|
114
|
+
files: {
|
|
115
|
+
get(name: string): Uint8Array | undefined;
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
}, scnum: number, card: boolean, slots?: readonly number[]): number[];
|
|
87
119
|
/** StartGameInits */
|
|
88
120
|
export declare function startGameInits(s: Session): void;
|
|
89
121
|
/** StartSceneInits */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type World } from './world';
|
|
1
|
+
import { type World } from './world.js';
|
|
2
2
|
/**
|
|
3
3
|
* Scene setup: Scroll.up/down/left/right are stored mirrored in the .gam file (the
|
|
4
4
|
* flags here name the way the map moves, e.g. `left` lets the map slide left as the
|
|
@@ -11,6 +11,14 @@ export declare function initScroll(w: World, s: {
|
|
|
11
11
|
right: number;
|
|
12
12
|
}): void;
|
|
13
13
|
/** ChangeScroll: decide scrolling speed from how far the character is from center. */
|
|
14
|
+
/**
|
|
15
|
+
* PLAYGAME.C ChangeScroll's `((dist)*SPD)/span` (negated first for the right and down scrolls).
|
|
16
|
+
* Everything there is a Borland 16-bit `int`, so the product wraps once the character is more
|
|
17
|
+
* than 819 pixels past the stop line (SPD is 40), and the speed flips sign or shrinks. We wrap
|
|
18
|
+
* the same way, then divide as a signed 16-bit division truncating toward zero.
|
|
19
|
+
* Credit: chiguire (upstream gandrewstone/GameMaker PR #4) found this overflow in a DOS run.
|
|
20
|
+
*/
|
|
21
|
+
export declare function dosSpeed(dist: number, spd: number, span: number, negate?: boolean): number;
|
|
14
22
|
export declare function changeScroll(w: World): number;
|
|
15
23
|
/** DrawScroll: one 1/145.6 s scroll step. Returns how many new 4 pixel edge strips it took. */
|
|
16
24
|
export declare function drawScroll(w: World): number;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Machine } from './machine';
|
|
2
|
-
import { type PlayerLook } from './playerLook';
|
|
1
|
+
import type { Machine } from './machine.js';
|
|
2
|
+
import { type PlayerLook } from './playerLook.js';
|
|
3
3
|
export declare const RGB_RED: [number, number, number];
|
|
4
4
|
export declare const RGB_BLUE: [number, number, number];
|
|
5
5
|
export declare const RGB_YELLOW: [number, number, number];
|
|
@@ -36,6 +36,37 @@ export interface PcCosts {
|
|
|
36
36
|
scrollPassMs: number;
|
|
37
37
|
/** Each new 4 pixel edge strip DrawScroll draws and copies to the screen. */
|
|
38
38
|
stripMs: number;
|
|
39
|
+
/** Each animated map cell redrawn on screen that tick (up to 170 dirty rectangles, DIRTRECT.C MAXDIRTY). */
|
|
40
|
+
cellMs?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Disk read of a .voc (PlaySbVocFile reads the whole file on the game's own thread): fixed cost to open it
|
|
43
|
+
* (directory lookup, FAT, seek to the data). It is also what each scene file costs to open: loadscene
|
|
44
|
+
* (PLAYGAME.C 4239) reads a scene's files from this same disk while the screen is black, and F6's LoadGame
|
|
45
|
+
* reads most of them again (sceneLoadMs). One disk per machine, so a sound and a level never disagree about
|
|
46
|
+
* how fast it is. Left out (TURBO_OFF), sounds and scenes load in no time.
|
|
47
|
+
*/
|
|
48
|
+
vocOpenMs?: number;
|
|
49
|
+
/** ...and how many bytes a millisecond the disk then delivers. */
|
|
50
|
+
vocBytesPerMs?: number;
|
|
51
|
+
/**
|
|
52
|
+
* The largest .voc the far heap still has room for (PlaySbVocFile `new`s the whole file; when that
|
|
53
|
+
* fails it returns FALSE and the game goes on without the sound). An estimate: 640 KB less DOS,
|
|
54
|
+
* drivers and PLAYGAME's own data. Left out, any size fits.
|
|
55
|
+
*/
|
|
56
|
+
vocHeapBytes?: number;
|
|
57
|
+
/**
|
|
58
|
+
* Optional custom-PC assumption: a disk BIOS masks the timer for the whole read, so PlayIt cannot
|
|
59
|
+
* advance and the FM chip holds its notes. The built-in presets leave the timer running: Lewis's
|
|
60
|
+
* PS/1 Model 2011 observation in 86Box (November 6, 2026) found a gameplay pause without a music hold.
|
|
61
|
+
* That observation does not establish IRQ behavior for every historical BIOS.
|
|
62
|
+
*/
|
|
63
|
+
readHoldsMusic?: boolean;
|
|
64
|
+
/** The timer interrupt itself (entry, EOI, PlayIt's bookkeeping; the BIOS clock every 8th), per interrupt. */
|
|
65
|
+
isrMs?: number;
|
|
66
|
+
/** Each CMF event PlayIt hands the FM driver (the driver call, without its register writes). */
|
|
67
|
+
fmEventMs?: number;
|
|
68
|
+
/** Each OPL2 register write: about 3.3 µs after the address and 23 µs after the data, waited out on the ISA bus. */
|
|
69
|
+
fmWriteMs?: number;
|
|
39
70
|
}
|
|
40
71
|
/** A 486DX2-66 with turbo off: about a 286-16. */
|
|
41
72
|
export declare const TURBO_OFF: PcCosts;
|
|
@@ -56,7 +87,38 @@ export declare class SlowCpu {
|
|
|
56
87
|
spend(ms: number): void;
|
|
57
88
|
reset(): void;
|
|
58
89
|
}
|
|
59
|
-
/** What one game tick costs with `monsters` drawn on screen. */
|
|
60
|
-
export declare function tickCost(pc: PcCosts, monsters: number): number;
|
|
90
|
+
/** What one game tick costs with `monsters` drawn on screen and `cells` animated map cells redrawn. */
|
|
91
|
+
export declare function tickCost(pc: PcCosts, monsters: number, cells?: number): number;
|
|
61
92
|
/** What one DrawScroll call costs when it drew `strips` new 4 pixel strips. */
|
|
62
93
|
export declare function scrollCost(pc: PcCosts, strips: number): number;
|
|
94
|
+
/**
|
|
95
|
+
* "Run like a..." in the player: the same cost model as TURBO_OFF for three machines of the
|
|
96
|
+
* period. Every number is an ESTIMATE from period hardware figures, not a measurement
|
|
97
|
+
* (programmer's guide chapters 10b and 12). A quiet tick is the game's work plus the copy of
|
|
98
|
+
* the changed rectangles to the screen. The timer interrupt and the music (about 1% of a 286)
|
|
99
|
+
* are charged per interrupt (songIsrMs). Today (no costs) is a fast modern machine.
|
|
100
|
+
*/
|
|
101
|
+
export type SpeedChoice = 'today' | '486' | '386' | '286' | 'ps1';
|
|
102
|
+
export declare const SPEED_CHOICES: readonly SpeedChoice[];
|
|
103
|
+
export declare const SPEED_PRESETS: Record<Exclude<SpeedChoice, 'today'>, PcCosts>;
|
|
104
|
+
/** The costs for a choice; undefined for Today (nothing is charged). */
|
|
105
|
+
export declare function costsFor(choice: SpeedChoice): PcCosts | undefined;
|
|
106
|
+
/**
|
|
107
|
+
* How long reading a voice of `bytes` freezes the game: open plus transfer. PLAYGAME reads
|
|
108
|
+
* the whole file before the tick goes on, so game time stops, and the sound only starts once the
|
|
109
|
+
* read is over (the player waits that long). On a PC with readHoldsMusic the music stops for it too
|
|
110
|
+
* (musicHoldMs). 0 when the PC has no disk figures (TURBO_OFF) or on Today.
|
|
111
|
+
*/
|
|
112
|
+
export declare function vocStallMs(pc: PcCosts | undefined, bytes: number): number;
|
|
113
|
+
/** How long the music stands still for a disk read of `readMs` (0 on Today, the desk turbo, or a PC whose timer keeps going). */
|
|
114
|
+
export declare function musicHoldMs(pc: PcCosts | undefined, readMs: number): number;
|
|
115
|
+
/** True when the far heap cannot hold a .voc of `bytes`: PlaySbVocFile returns FALSE and nothing plays. */
|
|
116
|
+
export declare function vocWontFit(pc: PcCosts | undefined, bytes: number): boolean;
|
|
117
|
+
/**
|
|
118
|
+
* How long a scene load keeps the screen black on this PC: each file opened costs vocOpenMs, then its
|
|
119
|
+
* bytes at vocBytesPerMs (`reads` is the byte count of each file read, 0 for one that failed to open).
|
|
120
|
+
* Game time does not move: playscene sets TimerCounter back to its value from before the load (PLAYGAME.C
|
|
121
|
+
* 1404), and LoadGame puts back the saved clock. Every preset (486, 386, 286, PS/1) has disk figures; 0 on a
|
|
122
|
+
* PC without them (TURBO_OFF) or on Today.
|
|
123
|
+
*/
|
|
124
|
+
export declare function sceneLoadMs(pc: PcCosts | undefined, reads: readonly number[]): number;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { PcCosts } from './slowPc.js';
|
|
2
|
+
export interface SongLoad {
|
|
3
|
+
/** Interrupts in one pass of the song. */
|
|
4
|
+
interrupts: number;
|
|
5
|
+
events: number;
|
|
6
|
+
writes: number;
|
|
7
|
+
/** The most events and register writes due on one interrupt. */
|
|
8
|
+
peakEvents: number;
|
|
9
|
+
peakWrites: number;
|
|
10
|
+
}
|
|
11
|
+
/** One pass through a CMF's music, walked as PlayIt walks it; undefined for a song PLAYGAME refuses. */
|
|
12
|
+
export declare function songLoad(cmf: Uint8Array | undefined): SongLoad | undefined;
|
|
13
|
+
/** The song's average cost per timer interrupt on `pc`, in ms (0 without music figures or a song). */
|
|
14
|
+
export declare function songIsrMs(pc: PcCosts | undefined, load: SongLoad | undefined): number;
|
|
15
|
+
/** The busiest interrupt of the song on `pc`, in ms: what it would take to make the timer lose a beat. */
|
|
16
|
+
export declare function songPeakIsrMs(pc: PcCosts, load: SongLoad): number;
|
|
17
|
+
/** Milliseconds between two timer interrupts (6.87). */
|
|
18
|
+
export declare const INTERRUPT_MS: number;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Block } from '../formats/blocks';
|
|
2
|
-
import { type Coord, type TouchBlocks, type World } from './world';
|
|
1
|
+
import type { Block } from '../formats/blocks.js';
|
|
2
|
+
import { type Coord, type TouchBlocks, type World } from './world.js';
|
|
3
3
|
export declare const KILLSOUND = 1;
|
|
4
4
|
export declare function doSound(w: World, n: number): boolean;
|
|
5
5
|
/** ScrnLowBnd/ScrnUpBnd ({1,1} to {SIZEX-BLEN,SIZEY-BLEN}): where a 20x20 sprite is considered on screen. */
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { BACKBL, BLEN, CHARBL, MONBL, type Block } from '../formats/blocks';
|
|
2
|
-
import type { Character } from '../formats/character';
|
|
3
|
-
import { INF_REPEAT, MAXINV, MAXSEQ } from '../formats/character';
|
|
4
|
-
import { LASTMON, type Monster } from '../formats/monster';
|
|
5
|
-
import { LASTMM, MLEN, MWID, type GameMap } from '../formats/map';
|
|
6
|
-
import { LASTSND } from '../formats/sound';
|
|
1
|
+
import { BACKBL, BLEN, CHARBL, MONBL, type Block } from '../formats/blocks.js';
|
|
2
|
+
import type { Character } from '../formats/character.js';
|
|
3
|
+
import { INF_REPEAT, MAXINV, MAXSEQ } from '../formats/character.js';
|
|
4
|
+
import { LASTMON, type Monster } from '../formats/monster.js';
|
|
5
|
+
import { LASTMM, MLEN, MWID, type GameMap } from '../formats/map.js';
|
|
6
|
+
import { LASTSND } from '../formats/sound.js';
|
|
7
7
|
export { BLEN, BACKBL, MONBL, CHARBL, LASTMM, LASTMON, MLEN, MWID, MAXSEQ, MAXINV, INF_REPEAT, LASTSND };
|
|
8
8
|
export declare const SIZEX = 320;
|
|
9
9
|
export declare const SIZEY = 200;
|
|
@@ -23,6 +23,26 @@ export declare const SCROLLPERMOVE = 6;
|
|
|
23
23
|
export declare const MAXMONSSHOWN = 50;
|
|
24
24
|
export declare const CHARPOWER = 100;
|
|
25
25
|
export declare const NEVER = 4294967295;
|
|
26
|
+
/** The gameplay constants a DOS player patch can change (playerLook.ts playTuning); World.tune. */
|
|
27
|
+
export interface PlayTuning {
|
|
28
|
+
/** MAXGRAV: the character's most pixels a move, either way. */
|
|
29
|
+
maxSpeed: number;
|
|
30
|
+
/** MAXGRAV*4: the most momentum gravity blocks build. */
|
|
31
|
+
gravityCap: number;
|
|
32
|
+
/** GRAVSPEED: ticks between gravity pulls. */
|
|
33
|
+
gravityTicks: number;
|
|
34
|
+
/** ATTACKX, ATTACKY: blocks within which a monster starts its attack path. */
|
|
35
|
+
attackX: number;
|
|
36
|
+
attackY: number;
|
|
37
|
+
/** SCRSPDX, SCRSPDY: the scroll speed at the screen edge. */
|
|
38
|
+
scrollSpeedX: number;
|
|
39
|
+
scrollSpeedY: number;
|
|
40
|
+
/** SCROLLPERMOVE: timer ticks one scroll move is spread over. */
|
|
41
|
+
scrollSteps: number;
|
|
42
|
+
/** FadeTo's and FadeAllTo's step count. */
|
|
43
|
+
fadeSteps: number;
|
|
44
|
+
}
|
|
45
|
+
export declare const STOCK_TUNING: Readonly<PlayTuning>;
|
|
26
46
|
export interface Coord {
|
|
27
47
|
x: number;
|
|
28
48
|
y: number;
|
|
@@ -196,6 +216,8 @@ export interface World {
|
|
|
196
216
|
* tracking listens here; nothing in play reads it.
|
|
197
217
|
*/
|
|
198
218
|
onTouched?: (x: number, y: number, bnum: number) => void;
|
|
219
|
+
/** The gameplay constants, STOCK_TUNING unless the player's patches change them (playerLook.ts playTuning). */
|
|
220
|
+
tune: PlayTuning;
|
|
199
221
|
}
|
|
200
222
|
export declare function newCharInfo(): CharInfo;
|
|
201
223
|
/** Pads a block list with empty blocks so out-of-range numbers are harmless. */
|