@lewismoten/gamemaker-sandbox-engine 0.0.0-stage → 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/LICENSE +24 -0
- package/NOTICE.md +58 -0
- package/README.md +149 -2
- package/dist/assets/music.worklet-aD2x7RRM.js +466 -0
- 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/cmfLoad.d.ts +7 -0
- package/dist/types/audio/cmfPlayer.d.ts +69 -0
- package/dist/types/audio/context.d.ts +12 -0
- package/dist/types/audio/engine.d.ts +60 -0
- package/dist/types/audio/mix.d.ts +14 -0
- package/dist/types/audio/music.worklet.d.ts +22 -0
- package/dist/types/audio/opl.d.ts +37 -0
- package/dist/types/audio/output.d.ts +13 -0
- package/dist/types/core/gameFiles.d.ts +49 -0
- package/dist/types/engine/character.d.ts +30 -0
- package/dist/types/engine/funcKeys.d.ts +35 -0
- package/dist/types/engine/game.d.ts +57 -0
- package/dist/types/engine/keys.d.ts +17 -0
- package/dist/types/engine/machine.d.ts +103 -0
- package/dist/types/engine/menus.d.ts +46 -0
- package/dist/types/engine/monsters.d.ts +19 -0
- package/dist/types/engine/playGain.d.ts +11 -0
- package/dist/types/engine/playerLook.d.ts +44 -0
- package/dist/types/engine/recorder.d.ts +67 -0
- package/dist/types/engine/render.d.ts +8 -0
- package/dist/types/engine/scene.d.ts +188 -0
- package/dist/types/engine/scroll.d.ts +24 -0
- package/dist/types/engine/showGraphic.d.ts +39 -0
- package/dist/types/engine/slowPc.d.ts +124 -0
- package/dist/types/engine/slowPcMusic.d.ts +18 -0
- package/dist/types/engine/touch.d.ts +33 -0
- package/dist/types/engine/world.d.ts +237 -0
- package/dist/types/formats/binary.d.ts +44 -0
- package/dist/types/formats/blocks.d.ts +52 -0
- package/dist/types/formats/character.d.ts +65 -0
- package/dist/types/formats/cmf.d.ts +33 -0
- 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 +56 -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 +79 -0
- package/dist/types/formats/gif.d.ts +15 -0
- package/dist/types/formats/gifProgressive.d.ts +46 -0
- package/dist/types/formats/graphicScripts.d.ts +18 -0
- package/dist/types/formats/highscore.d.ts +10 -0
- package/dist/types/formats/jpeg.d.ts +20 -0
- package/dist/types/formats/map.d.ts +24 -0
- package/dist/types/formats/monster.d.ts +48 -0
- package/dist/types/formats/palette.d.ts +15 -0
- package/dist/types/formats/recording.d.ts +7 -0
- package/dist/types/formats/sound.d.ts +13 -0
- package/dist/types/formats/voc.d.ts +13 -0
- package/dist/types/gfx/colorVision.d.ts +26 -0
- package/dist/types/gfx/cp437Glyphs.d.ts +2 -0
- package/dist/types/gfx/font8x8.d.ts +2 -0
- package/dist/types/gfx/screen.d.ts +50 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/input/chords.d.ts +22 -0
- package/dist/types/input/deskMouse.d.ts +14 -0
- package/dist/types/input/keyboard.d.ts +39 -0
- package/dist/types/input/scancodes.d.ts +3 -0
- package/dist/types/player/fileExtensions.d.ts +44 -0
- package/dist/types/player/patchRules.d.ts +24 -0
- package/dist/types/player/patchTable.d.ts +83 -0
- package/dist/types/text/cp437.d.ts +6 -0
- package/package.json +339 -4
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface PlayGain {
|
|
2
|
+
/** Points from the stretches F6 closed. */
|
|
3
|
+
banked: number;
|
|
4
|
+
/** The score the current stretch started from (0, or the score a load put in place). */
|
|
5
|
+
base: number;
|
|
6
|
+
}
|
|
7
|
+
export declare const newPlayGain: () => PlayGain;
|
|
8
|
+
/** F6 loaded a save: the score went from `before` to the save's `after`. */
|
|
9
|
+
export declare function gainAtLoad(g: PlayGain, before: number, after: number): void;
|
|
10
|
+
/** The points this play scored, ending on `score`. */
|
|
11
|
+
export declare const gainAtEnd: (g: PlayGain, score: number) => number;
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
}
|
|
13
|
+
type Rgb = [number, number, number];
|
|
14
|
+
type Frame = [number, number, number, number];
|
|
15
|
+
export interface PlayerLook {
|
|
16
|
+
/** A word or line (str.*). */
|
|
17
|
+
text(id: string): string;
|
|
18
|
+
/** A printf format (str.*) filled in as sprintf would. */
|
|
19
|
+
format(id: string, ...args: (number | string)[]): string;
|
|
20
|
+
/** The palette index a screen's color selector (color.select.*) matches in `pal`. */
|
|
21
|
+
color(pal: Uint8Array, select: string): number;
|
|
22
|
+
/** A color's levels (color.rgb.*, color.missing.*). */
|
|
23
|
+
rgb(id: string): Rgb;
|
|
24
|
+
frame(id: string): Frame;
|
|
25
|
+
/** A position or count (layout.*, border.marker.width). */
|
|
26
|
+
num(id: string): number;
|
|
27
|
+
/** A wait in seconds (timing.*); -1 is PauseTimeKey's unsigned 65535 (until a key). */
|
|
28
|
+
seconds(id: string): number;
|
|
29
|
+
/** False when the player's patches turned this F-key off. */
|
|
30
|
+
fkeyOn(key: number): boolean;
|
|
31
|
+
/** An imported game's own About page and first build, when it has them. */
|
|
32
|
+
about?: PlayerAbout;
|
|
33
|
+
}
|
|
34
|
+
/** sprintf for the player's formats: %d, %ld, %s with a width and the 0 and - flags. */
|
|
35
|
+
export declare function sprintf(format: string, args: (number | string)[]): string;
|
|
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;
|
|
42
|
+
/** The CD player's own look. */
|
|
43
|
+
export declare const STOCK_LOOK: PlayerLook;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type RecordedKey } from '../formats/recording.js';
|
|
2
|
+
export declare const RECORD = 1;
|
|
3
|
+
export declare class Recorder {
|
|
4
|
+
mode: number;
|
|
5
|
+
private keys;
|
|
6
|
+
private index;
|
|
7
|
+
private timer;
|
|
8
|
+
private lastKeyPress;
|
|
9
|
+
private paused;
|
|
10
|
+
initRecord(): void;
|
|
11
|
+
/**
|
|
12
|
+
* Whether waits for a key count toward playback time. PLAYGAME's GetKey called
|
|
13
|
+
* DoPlayBack on every poll, so while a "hit any key" screen waited the timer raced
|
|
14
|
+
* ahead (thousands of polls a second), and a demo recorded in DOS stores those polls
|
|
15
|
+
* as one huge DeltaTime before the key that dismissed the screen (Nebula's 26058).
|
|
16
|
+
* Demos recorded here count game ticks only, so for them waits must not count.
|
|
17
|
+
*/
|
|
18
|
+
countsWaits: boolean;
|
|
19
|
+
initPlayback(bytes: Uint8Array | undefined): boolean;
|
|
20
|
+
stop(): void;
|
|
21
|
+
restart(): void;
|
|
22
|
+
pause(): void;
|
|
23
|
+
unpause(): void;
|
|
24
|
+
/** Game ticks counted since the recording or playback (re)started; the playback position. */
|
|
25
|
+
get tick(): number;
|
|
26
|
+
get playing(): boolean;
|
|
27
|
+
/** Paused: play has left the levels (the won/lost pictures and score table follow). */
|
|
28
|
+
get halted(): boolean;
|
|
29
|
+
/** DoPlayBack: advance one tick; returns the keys that become pending now. */
|
|
30
|
+
doPlayback(): number[];
|
|
31
|
+
/**
|
|
32
|
+
* GetKey polls while a screen waits for a key: `polls` calls to DoPlayBack at once.
|
|
33
|
+
* Returns the recorded keys that become pending (only for recordings made in DOS).
|
|
34
|
+
*/
|
|
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;
|
|
43
|
+
/**
|
|
44
|
+
* A screen that read keys has finished. In DOS the next key may have come during it
|
|
45
|
+
* (the recording shows a gap of many thousands of polls), however fast that PC polled;
|
|
46
|
+
* catch up to it, and to keys a poll or two behind it (taken by the keyboard flush that
|
|
47
|
+
* follows every screen), so gameplay resumes in step. Only for DOS recordings.
|
|
48
|
+
*/
|
|
49
|
+
finishWait(): number[];
|
|
50
|
+
/**
|
|
51
|
+
* DoRecord: called when a key is actually applied to the character (keys held back
|
|
52
|
+
* while a sequence can't be interrupted are recorded on the tick they take effect).
|
|
53
|
+
* DeltaTime is a 16-bit uint in the C, and at most RECLEN keys are kept.
|
|
54
|
+
*/
|
|
55
|
+
doRecord(key: number): void;
|
|
56
|
+
/** The recording so far, in .rec format. */
|
|
57
|
+
save(): Uint8Array;
|
|
58
|
+
/** For save games (the C wrote the Recording object and its keys into the .sav). */
|
|
59
|
+
snapshot(): unknown;
|
|
60
|
+
restore(s: {
|
|
61
|
+
mode: number;
|
|
62
|
+
keys: RecordedKey[];
|
|
63
|
+
index: number;
|
|
64
|
+
timer: number;
|
|
65
|
+
lastKeyPress: number;
|
|
66
|
+
}): void;
|
|
67
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Screen } from '../gfx/screen.js';
|
|
2
|
+
import { type World } from './world.js';
|
|
3
|
+
/** CropScreen: the CRTC was reprogrammed to show only 312x196 pixels while playing. */
|
|
4
|
+
export declare const VISIBLE_W = 312;
|
|
5
|
+
export declare const VISIBLE_H = 196;
|
|
6
|
+
/** drawchars' bookkeeping without the drawing: whether top/bottom blocks are shown (used by GetTouchbl). */
|
|
7
|
+
export declare function updateCharDrawState(w: World): void;
|
|
8
|
+
export declare function renderWorld(screen: Screen, w: World): void;
|
|
@@ -0,0 +1,188 @@
|
|
|
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
|
+
/** How a play ended: the Game Won scene, no lives left, or Esc (or a scene that could not load). */
|
|
10
|
+
type PlayEnd = 'won' | 'lost' | 'quit';
|
|
11
|
+
/** Everything that lives across scenes (playgame's globals outside a level). */
|
|
12
|
+
export interface Session {
|
|
13
|
+
m: Machine;
|
|
14
|
+
game: Game;
|
|
15
|
+
gameName: string;
|
|
16
|
+
ci: CharInfo;
|
|
17
|
+
/** The `colors` global: the palette of the current scene or menu screen. */
|
|
18
|
+
colors: Uint8Array;
|
|
19
|
+
rec: Recorder;
|
|
20
|
+
sndClock: number;
|
|
21
|
+
timerCounter: number;
|
|
22
|
+
soundOn: boolean;
|
|
23
|
+
musicOn: boolean;
|
|
24
|
+
/** Use the PC speaker even when a .voc name is given (no Sound Blaster). */
|
|
25
|
+
speakerOnly: boolean;
|
|
26
|
+
joystick?: () => {
|
|
27
|
+
pos: number;
|
|
28
|
+
buttons: number;
|
|
29
|
+
} | null;
|
|
30
|
+
/** Called every rendered frame with the live world (for HUD overlays, touch controls…). */
|
|
31
|
+
onFrame?: (w: World) => void;
|
|
32
|
+
/** Emulated PC speed; read every tick so it can change while playing. */
|
|
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
|
+
};
|
|
48
|
+
/** RunOptions.gmHelp: Game-Maker's own GMHELP.TXT, which F1 shows first (PLAYGAME.EXE only). */
|
|
49
|
+
gmHelp?: Uint8Array;
|
|
50
|
+
/** RunOptions.player as the screens use it (engine/playerLook.ts); left out, the CD player's look. */
|
|
51
|
+
look?: PlayerLook;
|
|
52
|
+
/** The random numbers a level's monsters get as it loads (default Math.random; a walkthrough seeds them). */
|
|
53
|
+
rand?: () => () => number;
|
|
54
|
+
/** Told the scene number each time play() enters a scene (before its picture). */
|
|
55
|
+
onScene?: (scene: number) => void;
|
|
56
|
+
/** Not PLAYGAME's: a touch in `scene` changed map cell (x, y), shown as block `bnum` (World.onTouched). */
|
|
57
|
+
onTouched?: (scene: number, x: number, y: number, bnum: number) => void;
|
|
58
|
+
/**
|
|
59
|
+
* Not PLAYGAME's: play ended (won, lost, or left with Esc) with this score, just before the
|
|
60
|
+
* high score table. Never told during a demo's playback. It only listens: nothing changes.
|
|
61
|
+
* `gained` is the points the play scored itself, leaving out those F6 loaded (playGain.ts).
|
|
62
|
+
*/
|
|
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;
|
|
66
|
+
/** Not PLAYGAME's: the stretches of this play between F6 loads (playGain.ts). */
|
|
67
|
+
gain?: PlayGain;
|
|
68
|
+
/**
|
|
69
|
+
* The last level played. loadscene loads into globals, so a level that leaves its
|
|
70
|
+
* palette, block or character slot blank keeps the previous level's (PLAYGAME.C 4239).
|
|
71
|
+
*/
|
|
72
|
+
lastWorld?: World;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* How fast the emulated PC is. The game clock always runs at 18.2 Hz, but playgame
|
|
76
|
+
* skips (rather than catches up) ticks its main loop was too slow to handle, and loses
|
|
77
|
+
* the scroll steps of interrupts that went by while it was busy. `slowPc` (see slowPc.ts)
|
|
78
|
+
* charges each pass of the loop what it cost on a slower PC; left out, the PC is fast
|
|
79
|
+
* enough for every pass (a 486 in turbo).
|
|
80
|
+
*/
|
|
81
|
+
export interface SpeedSetting {
|
|
82
|
+
slowPc?: PcCosts;
|
|
83
|
+
}
|
|
84
|
+
export declare function sceneFile(game: Game, scene: number, slot: number): string | undefined;
|
|
85
|
+
interface LoadedScene {
|
|
86
|
+
world: World;
|
|
87
|
+
sounds?: SoundSet;
|
|
88
|
+
song?: Uint8Array;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* loadscene: returns the names of missing files on failure. Scene file slots are
|
|
92
|
+
* 0 palette, 1 map, 2 background blocks, 3 monsters, 4 monster blocks, 5 character,
|
|
93
|
+
* 6 character blocks, 7 sound set, 8 song. In the C a slot left blank keeps whatever
|
|
94
|
+
* the previous scene loaded into the globals; `prev` stands in for those globals.
|
|
95
|
+
*/
|
|
96
|
+
export declare function loadScene(s: Pick<Session, 'game' | 'rand' | 'onTouched' | 'look'> & {
|
|
97
|
+
m: {
|
|
98
|
+
files: {
|
|
99
|
+
get(name: string): Uint8Array | undefined;
|
|
100
|
+
};
|
|
101
|
+
};
|
|
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[];
|
|
119
|
+
/** StartGameInits */
|
|
120
|
+
export declare function startGameInits(s: Session): void;
|
|
121
|
+
/** StartSceneInits */
|
|
122
|
+
export declare function startSceneInits(w: World, start: Coord): void;
|
|
123
|
+
/**
|
|
124
|
+
* keydn[CHRSEQ.key] = 1 at the end of the C's scene inits (PLAYGAME.C). keydn is a 128 byte
|
|
125
|
+
* array indexed by scan code, but a sequence's key is the whole 16 bit word (scan code in the
|
|
126
|
+
* high byte, ASCII in the low byte, CHAR.H), so the C only set a keydn entry when the key had
|
|
127
|
+
* no scan code: key 0 (no key, which every idle sequence on the CD has) sets keydn[0], and
|
|
128
|
+
* the idle sequence's "quit if key lifted" test (keydn[key>>8]==0) then reads it as held.
|
|
129
|
+
* For a real key the write landed key bytes past keydn (DGROUP:4C4E in PLAYGAME.MAP), never
|
|
130
|
+
* on another keydn entry: scan 1..4 in dirtrect.c's DirtyBoxes, scan 5 in the next modules'
|
|
131
|
+
* buffers, scan 5..0x48 in gifc.c's decoder tables, 0x49..0x4A in the atexit table and higher
|
|
132
|
+
* in the far end of the 15000 byte stack. Those are all written before they are read, so the
|
|
133
|
+
* stray byte had no effect: the key does not read as held (keydn[scan] stays 0).
|
|
134
|
+
*/
|
|
135
|
+
export declare function setStartKey(keydn: Uint8Array, key: number): void;
|
|
136
|
+
/** changeblks: animate background blocks. */
|
|
137
|
+
export declare function changeBlocks(w: World): void;
|
|
138
|
+
interface SceneResult {
|
|
139
|
+
ok: boolean;
|
|
140
|
+
link: number;
|
|
141
|
+
/**
|
|
142
|
+
* The level played at the end and the one before it: another level's after F6 loaded a game
|
|
143
|
+
* saved there (LoadGame sets play's curscene and oldscene, globals in the C).
|
|
144
|
+
*/
|
|
145
|
+
doscene?: number;
|
|
146
|
+
prevscene?: number;
|
|
147
|
+
}
|
|
148
|
+
/** What F5 writes to game<n>.sav (this port's JSON in place of SaveGame's binary records). */
|
|
149
|
+
export interface Saved {
|
|
150
|
+
version: 1;
|
|
151
|
+
prevscene: number;
|
|
152
|
+
doscene: number;
|
|
153
|
+
linkin: number;
|
|
154
|
+
sndClock: number;
|
|
155
|
+
world: Pick<World, 'nxtchg' | 'ci' | 'mx' | 'my' | 'mi' | 'mm' | 'noMons' | 'blkMon'> & {
|
|
156
|
+
blkmap: number[];
|
|
157
|
+
map: number[];
|
|
158
|
+
mapMon: number[];
|
|
159
|
+
chr: {
|
|
160
|
+
x: number[];
|
|
161
|
+
y: number[];
|
|
162
|
+
cframe: number;
|
|
163
|
+
cseq: number;
|
|
164
|
+
};
|
|
165
|
+
};
|
|
166
|
+
rec: ReturnType<Recorder['snapshot']>;
|
|
167
|
+
}
|
|
168
|
+
/** playscene: plays one level. link in/out follows the original's conventions. */
|
|
169
|
+
export declare function playScene(s: Session, dosc: number, prev: number, linkIn: number): Promise<SceneResult>;
|
|
170
|
+
/** The save of a game at this moment (F5; project/saveData's newGameSave makes one at the start). */
|
|
171
|
+
export declare function savedState(w: World, where: {
|
|
172
|
+
prevscene: number;
|
|
173
|
+
doscene: number;
|
|
174
|
+
linkin: number;
|
|
175
|
+
}, sndClock: number, rec: Pick<Recorder, 'snapshot'>): Saved;
|
|
176
|
+
/**
|
|
177
|
+
* F6's LoadGame once the save is read: the saved level loaded fresh, then the saved state put
|
|
178
|
+
* over it (also how project/saveData's tests check a save it made). Undefined when the
|
|
179
|
+
* level's files are missing.
|
|
180
|
+
*/
|
|
181
|
+
export declare function restoreSaved(s: Pick<Session, 'game' | 'rand' | 'ci' | 'rec' | 'sndClock' | 'onTouched'> & {
|
|
182
|
+
m: {
|
|
183
|
+
files: {
|
|
184
|
+
get(name: string): Uint8Array | undefined;
|
|
185
|
+
};
|
|
186
|
+
};
|
|
187
|
+
}, w: World | undefined, data: Saved): LoadedScene | undefined;
|
|
188
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type World } from './world.js';
|
|
2
|
+
/**
|
|
3
|
+
* Scene setup: Scroll.up/down/left/right are stored mirrored in the .gam file (the
|
|
4
|
+
* flags here name the way the map moves, e.g. `left` lets the map slide left as the
|
|
5
|
+
* character walks right). 0xff means that direction is allowed.
|
|
6
|
+
*/
|
|
7
|
+
export declare function initScroll(w: World, s: {
|
|
8
|
+
up: number;
|
|
9
|
+
down: number;
|
|
10
|
+
left: number;
|
|
11
|
+
right: number;
|
|
12
|
+
}): void;
|
|
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;
|
|
22
|
+
export declare function changeScroll(w: World): number;
|
|
23
|
+
/** DrawScroll: one 1/145.6 s scroll step. Returns how many new 4 pixel edge strips it took. */
|
|
24
|
+
export declare function drawScroll(w: World): number;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Machine } from './machine.js';
|
|
2
|
+
import { type PlayerLook } from './playerLook.js';
|
|
3
|
+
export declare const RGB_RED: [number, number, number];
|
|
4
|
+
export declare const RGB_BLUE: [number, number, number];
|
|
5
|
+
export declare const RGB_YELLOW: [number, number, number];
|
|
6
|
+
export declare const RGB_PURPLE: [number, number, number];
|
|
7
|
+
export declare const match: (pal: Uint8Array, c: [number, number, number]) => number;
|
|
8
|
+
interface GraphicOptions {
|
|
9
|
+
/** Receives the palette of the shown picture (the NewCols argument). */
|
|
10
|
+
cols?: Uint8Array;
|
|
11
|
+
/** Seconds to wait for a key; -1 = the default for the file type. */
|
|
12
|
+
pause?: number;
|
|
13
|
+
/** The game's current palette, used to pick text colors (`colors` global). */
|
|
14
|
+
gamePalette: Uint8Array;
|
|
15
|
+
/** The player's look (RunOptions.player): text page colors, default times. */
|
|
16
|
+
look?: PlayerLook;
|
|
17
|
+
}
|
|
18
|
+
/** Returns false when the player pressed Esc (which aborts slide shows). */
|
|
19
|
+
export declare function showGraphic(m: Machine, file: string, opts: GraphicOptions): Promise<boolean>;
|
|
20
|
+
/**
|
|
21
|
+
* FLI::View: every frame once, `speed` vertical retraces (1/70 s) apart. The C drew
|
|
22
|
+
* straight onto the VGA screen, so the animation starts over whatever is shown.
|
|
23
|
+
*
|
|
24
|
+
* A speed of 0 waits no retrace at all: the C's delay loop is
|
|
25
|
+
* for (uint Delay=0;Delay<Head.FrameSpd;Delay++) { wait out retrace; wait for retrace; }
|
|
26
|
+
* so with FrameSpd 0 the next frame was decoded into video memory straight away, as fast
|
|
27
|
+
* as the PC could read and draw it, and the monitor showed whatever was in video memory at
|
|
28
|
+
* each refresh (frames in between were never seen). The port does the same: it draws the
|
|
29
|
+
* next frame at once, only yielding to the event loop (no frame wait) so the page stays
|
|
30
|
+
* responsive, and each browser repaint shows the frame that is current then. Speed 0 FLIs
|
|
31
|
+
* on the CD (black.fli, menubkg.fli, textbkg.fli, ...) are one or two frame backgrounds.
|
|
32
|
+
*/
|
|
33
|
+
export declare function playFli(m: Machine, file: string): Promise<boolean>;
|
|
34
|
+
/**
|
|
35
|
+
* Display_File: text pages over the current picture, 16 lines (rows 5..20 of 8 pixels)
|
|
36
|
+
* per page. fgets(…, 32) read at most 31 characters at a time, so long lines wrap.
|
|
37
|
+
*/
|
|
38
|
+
export declare function displayFile(m: Machine, file: string, col: number, pause: number, gamePalette: Uint8Array, bytes?: Uint8Array<ArrayBufferLike> | undefined, look?: PlayerLook): Promise<boolean>;
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The emulated slow PC behind the desk's TURBO button (turbo off).
|
|
3
|
+
*
|
|
4
|
+
* A 486DX2-66's turbo switch did not make it a 286. On most boards turbo off turned off
|
|
5
|
+
* the CPU cache (some also dropped the bus or core clock), which left the machine at
|
|
6
|
+
* roughly 286-16 to 386-25 speed, depending on the board. So turbo off here is "about a
|
|
7
|
+
* fast 286": the PC Lewis played these games on, where they ran fine.
|
|
8
|
+
*
|
|
9
|
+
* Why they ran fine: playgame's main loop (PLAYGAME.C playscene) does two kinds of work.
|
|
10
|
+
* - Once per NewTimer interrupt (8 per game tick, 145.6 Hz), DrawScroll while the view
|
|
11
|
+
* scrolls. On a VGA/SVGA card this is hardware scrolling: it moves the CRTC start
|
|
12
|
+
* address, draws only the new 4 pixel strips at the edge (drawhorizside/
|
|
13
|
+
* drawvertside: 320x4 or 4x200 pixels each) and erases/redraws the character. It never
|
|
14
|
+
* redraws the whole screen.
|
|
15
|
+
* - Once per 18.2 Hz game tick: move and redraw the character and the monsters on screen,
|
|
16
|
+
* animate blocks, copy the dirty rectangles to the screen, check links.
|
|
17
|
+
* On a 286-class PC a still tick costs about 15 ms plus a millisecond or so per monster on
|
|
18
|
+
* screen, well under the 55 ms a tick lasts, so the game keeps full pace. Scrolling only
|
|
19
|
+
* runs once per pass of the loop, and the pass that runs the game tick takes several
|
|
20
|
+
* interrupts' worth of time; DrawScroll is called with a step of 1 however many
|
|
21
|
+
* interrupts went by (its `speed` argument is commented out), so those scroll steps are
|
|
22
|
+
* simply lost and the view trails the character a little more. Only a crowded screen
|
|
23
|
+
* that is also scrolling can take longer than a tick, and then playgame skips the missed
|
|
24
|
+
* tick (OldClock jumps to TimerCounter) instead of catching up.
|
|
25
|
+
*
|
|
26
|
+
* The figures below are estimates for that machine, in milliseconds. They are kept
|
|
27
|
+
* deliberately simple and deterministic (no wall clock): the same keys give the same
|
|
28
|
+
* game, and recordings replay the same way.
|
|
29
|
+
*/
|
|
30
|
+
export interface PcCosts {
|
|
31
|
+
/** A game tick with nothing on screen but the character. */
|
|
32
|
+
tickMs: number;
|
|
33
|
+
/** Each monster drawn on screen that tick (move, erase, masked draw, dirty rect). */
|
|
34
|
+
monsterMs: number;
|
|
35
|
+
/** One DrawScroll call (Bresenham step, CRTC update, character erase/redraw). */
|
|
36
|
+
scrollPassMs: number;
|
|
37
|
+
/** Each new 4 pixel edge strip DrawScroll draws and copies to the screen. */
|
|
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;
|
|
70
|
+
}
|
|
71
|
+
/** A 486DX2-66 with turbo off: about a 286-16. */
|
|
72
|
+
export declare const TURBO_OFF: PcCosts;
|
|
73
|
+
/**
|
|
74
|
+
* Keeps track of the time the slow PC still owes on work it started, counted in NewTimer
|
|
75
|
+
* interrupts. While it owes a whole interrupt or more, that loop pass never happens.
|
|
76
|
+
*/
|
|
77
|
+
export declare class SlowCpu {
|
|
78
|
+
private readonly interruptMs;
|
|
79
|
+
/** Milliseconds of work still running. */
|
|
80
|
+
owed: number;
|
|
81
|
+
constructor(interruptMs: number);
|
|
82
|
+
/**
|
|
83
|
+
* Called once per NewTimer interrupt: true when the PC is still busy with earlier work
|
|
84
|
+
* and this interrupt's pass of the main loop (scroll step, game tick) is lost.
|
|
85
|
+
*/
|
|
86
|
+
busy(): boolean;
|
|
87
|
+
spend(ms: number): void;
|
|
88
|
+
reset(): void;
|
|
89
|
+
}
|
|
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;
|
|
92
|
+
/** What one DrawScroll call costs when it drew `strips` new 4 pixel strips. */
|
|
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;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Block } from '../formats/blocks.js';
|
|
2
|
+
import { type Coord, type TouchBlocks, type World } from './world.js';
|
|
3
|
+
export declare const KILLSOUND = 1;
|
|
4
|
+
export declare function doSound(w: World, n: number): boolean;
|
|
5
|
+
/** ScrnLowBnd/ScrnUpBnd ({1,1} to {SIZEX-BLEN,SIZEY-BLEN}): where a 20x20 sprite is considered on screen. */
|
|
6
|
+
export declare function inScreenBounds(p: Coord): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* onscrn: screen position of map block (x, y); true if inside the screen bounds.
|
|
9
|
+
* Cells left of / above the scroll origin (mx, my) are taken as wrapped around the map.
|
|
10
|
+
*/
|
|
11
|
+
export declare function onscrn(w: World, x: number, y: number, ret: Coord): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* GetTouchbl: the up to 6 map cells the (two block high) character overlaps:
|
|
14
|
+
* 0/1 the top row (only while the top half is drawn), 2/3 its own row, 4/5 the
|
|
15
|
+
* row below, left then right. The right/lower cells count only when the pixel
|
|
16
|
+
* offset is past 0. Unused cells keep x = MLEN and BACKBL; blknum is the
|
|
17
|
+
* displayed variant (blkmap) of the map block.
|
|
18
|
+
*/
|
|
19
|
+
export declare function getTouchBlocks(w: World): TouchBlocks;
|
|
20
|
+
/** checkseq_fortie: turns on/off every sequence tied to `block` (picked up or dropped). */
|
|
21
|
+
export declare function checkSeqForTie(w: World, block: number, setting: boolean): void;
|
|
22
|
+
/**
|
|
23
|
+
* DoBlockTouchChar: applies a block's (or monster block's) effects to the character.
|
|
24
|
+
* Returns 0 when blocked by an empty counter, otherwise (sound played ? 2 : 1).
|
|
25
|
+
* Effect 1 is lives and 3 is money; effects 3 and up are counters (ci.meter), and
|
|
26
|
+
* 3 or 20 and up refuse the touch when they would leave the counter at 0 or less.
|
|
27
|
+
* i16/u16 keep the C's 16-bit int wrap and its unsigned overflow checks.
|
|
28
|
+
*/
|
|
29
|
+
export declare function doBlockTouchChar(w: World, blk: Block): number;
|
|
30
|
+
/** really_checktouch: 2 = touch refused (counter empty), else true. x === MLEN is an unused touch cell. */
|
|
31
|
+
export declare function reallyCheckTouch(w: World, x: number, y: number, bnum: number): number;
|
|
32
|
+
/** CheckTouch: touches every overlapped cell except objects, which only PickupBloc takes. */
|
|
33
|
+
export declare function checkTouch(w: World): void;
|