@scm-js/plugin-api 1.67.0 → 1.68.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.
Files changed (2) hide show
  1. package/index.d.ts +233 -22
  2. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -905,8 +905,8 @@ export type TerrainPick = {
905
905
  id: number;
906
906
  };
907
907
  export interface UnitsDat {
908
- /** flingy.dat index. */
909
- flingy: Uint8Array;
908
+ /** flingy.dat index (a byte in the game's layout, a word in the extended ones). */
909
+ flingy: Uint16Array;
910
910
  /** First subunit (turret), or NO_UNIT. */
911
911
  subunit: Uint16Array;
912
912
  /** Starting facing 0–31 (0 = up, 8 = right, 16 = down), or RANDOM_DIRECTION. */
@@ -932,16 +932,16 @@ export interface UnitsDat {
932
932
  buildTime: Uint16Array;
933
933
  armor: Uint8Array;
934
934
  /** weapons.dat ids, or NO_WEAPON. */
935
- groundWeapon: Uint8Array;
936
- airWeapon: Uint8Array;
935
+ groundWeapon: Uint16Array;
936
+ airWeapon: Uint16Array;
937
937
  /** Bit 0 Zerg, 1 Terran, 2 Protoss, 3 men, 4 building, 5 factory, 6 independent, 7 neutral. */
938
938
  groupFlags: Uint8Array;
939
939
  /** StarEdit availability flags; all zero for the legacy layout. */
940
940
  availability: Uint16Array;
941
941
  /**
942
942
  * The unit's voice, as sfxdata.dat ids (0 for none): the one it says when trained, and
943
- * first–last ranges for a click, repeated clicks and an order. Only the first
944
- * `VOICED_UNITS` have ready / annoyed / yes sounds; every unit has a "what".
943
+ * first–last ranges for a click, repeated clicks and an order. In the game's layout only
944
+ * the first `VOICED_UNITS` have ready / annoyed / yes sounds (0 past them); every unit has a "what".
945
945
  */
946
946
  readySound: Uint16Array;
947
947
  whatSoundStart: Uint16Array;
@@ -1488,7 +1488,7 @@ export interface StackedLine {
1488
1488
  pieces: number;
1489
1489
  }
1490
1490
  /** Every built-in dialog, by the id `openDialogAtom` takes; `DialogHost` maps each to its component. */
1491
- export type DialogId = "newMap" | "openMap" | "saveAs" | "exportImage" | "mapProperties" | "resizeMap" | "mapRevision" | "playerSettings" | "forceSettings" | "playerColors" | "unitSettings" | "upgradeSettings" | "techSettings" | "stringEditor" | "soundEditor" | "switches" | "locationList" | "unitProperties" | "locationProperties" | "spriteProperties" | "triggerEditor" | "missionBriefing" | "cuwpEditor" | "replaceTerrain" | "autoStarts" | "testMap" | "symmetry" | "gridSettings" | "preferences" | "shortcuts" | "validateMap" | "statistics" | "importTriggers" | "exportTriggers" | "importStrings" | "exportStrings" | "find" | "about" | "confirmClose" | "recovery" | "previousVersions" | "plugins" | "confirmPlugin" | "pluginDialog" | "gameData" | "update" | "whatsNew";
1491
+ export type DialogId = "newMap" | "openMap" | "saveAs" | "exportImage" | "mapProperties" | "resizeMap" | "mapRevision" | "playerSettings" | "forceSettings" | "playerColors" | "unitSettings" | "upgradeSettings" | "techSettings" | "stringEditor" | "soundEditor" | "switches" | "locationList" | "unitProperties" | "locationProperties" | "spriteProperties" | "triggerEditor" | "missionBriefing" | "cuwpEditor" | "replaceTerrain" | "autoStarts" | "testMap" | "symmetry" | "gridSettings" | "preferences" | "shortcuts" | "validateMap" | "statistics" | "importTriggers" | "exportTriggers" | "importStrings" | "exportStrings" | "find" | "about" | "confirmClose" | "recovery" | "previousVersions" | "plugins" | "confirmPlugin" | "pluginDialog" | "gameData" | "update" | "whatsNew" | "mapDataSet";
1492
1492
  export interface MapImageOptions {
1493
1493
  /** Output pixels per map tile. 32 matches the game's art 1:1; 1 is a minimap. */
1494
1494
  pixelsPerTile: number;
@@ -4220,8 +4220,10 @@ export interface GraphicsApi {
4220
4220
  * The game's own button picture for a unit type, an upgrade or a technology — the icon
4221
4221
  * its command card shows — 36 × 34 at most and transparent round the edges. `look`
4222
4222
  * colours it as the game does: `"normal"` yellow (the default), `"disabled"` grey,
4223
- * `"selected"` white. It does not depend on the map or its tileset. Null for an id with
4224
- * no picture (a unit type past 227, a turret) and until `requestIcons` has the pictures;
4223
+ * `"selected"` white. It does not depend on the map or its tileset. A unit type past 227
4224
+ * has a picture only where the data set carries a sheet for every type
4225
+ * (`unit\cmdbtns\uniticons.grp`). Null for an id with no picture (a turret, a type past
4226
+ * 227 without that sheet) and until `requestIcons` has the pictures;
4225
4227
  * null for good from a game data copy made before the editor kept them (installing the
4226
4228
  * game data again brings them). The canvas is the editor's own cached one: draw from it.
4227
4229
  *
@@ -4248,6 +4250,39 @@ export interface GraphicsApi {
4248
4250
  requestIcons(): boolean;
4249
4251
  /** A GRP finished loading: anything drawn from one may look different now. */
4250
4252
  onImageLoaded(listener: () => void): Disposable;
4253
+ /**
4254
+ * Draw one frame of a GRP you hold — a mod's picture, one you made — the way the editor
4255
+ * draws a unit's: through a tileset's palette, with the team colour slots in a player's
4256
+ * colour, index 0 transparent. Either kind of GRP is read (the run-length one units use,
4257
+ * and the uncompressed one of the interface pictures). The canvas is the GRP's whole box,
4258
+ * as `unitImage`'s is. Resolves null when the bytes are no GRP, the frame is past its
4259
+ * last, or there is no game data to take the palette from. A GRP drawn through
4260
+ * `remapping` (fire, sparks) or Remastered's pictures are not drawn here.
4261
+ *
4262
+ * @example
4263
+ * const grp = await api.gameData.read("unit/terran/marine.grp");
4264
+ * const pic = grp && (await api.graphics.grpImage(grp, 0, { palette: "jungle", teamColor: 1 }));
4265
+ * if (pic) panelBody.append(pic.image);
4266
+ */
4267
+ grpImage(grp: Uint8Array, frame: number, options?: GrpImageOptions): Promise<PluginImage | null>;
4268
+ }
4269
+ /** How `graphics.grpImage` draws a GRP. */
4270
+ export interface GrpImageOptions {
4271
+ /**
4272
+ * The tileset whose palette the picture is drawn through, by its file name: `badlands`,
4273
+ * `platform`, `install`, `ashworld`, `jungle`, `desert`, `ice` or `twilight`. The open
4274
+ * map's by default.
4275
+ */
4276
+ palette?: string;
4277
+ /**
4278
+ * The player whose colour the team slots (indices 8–15) take, as the open map colours
4279
+ * its players; 0 by default. Null leaves them the palette's own.
4280
+ */
4281
+ teamColor?: number | null;
4282
+ /** Mirrored left to right, as a unit facing the other way is drawn. */
4283
+ flipped?: boolean;
4284
+ /** As a shadow: a half-transparent black silhouette. */
4285
+ shadow?: boolean;
4251
4286
  }
4252
4287
  /**
4253
4288
  * The game's own tables, as the editor decoded them: `units.dat` and its neighbours.
@@ -4283,25 +4318,46 @@ export interface DataApi {
4283
4318
  }
4284
4319
  /**
4285
4320
  * A data set: one set of game files the editor draws from. The default is the game's
4286
- * own; any other is a mod's, installed under an id of its own, the same formats and
4287
- * table sizes with some of the files replaced.
4321
+ * own; any other is installed under an id of its own, the same formats with some of the
4322
+ * files replaced — its tables at any width (the game's, DatExtend's expanded layout or
4323
+ * GPTP's `.datex`), its tilesets at any size.
4288
4324
  */
4289
4325
  export interface GameDataProfile {
4290
4326
  /** Lower-case letters, digits and hyphens, up to 40 characters. */
4291
4327
  id: string;
4292
4328
  /** What the dialog and the status line call it. */
4293
4329
  name: string;
4330
+ /**
4331
+ * Where the data set's makers publish it, an `http(s)` address. A map saved with the data
4332
+ * set records it, and someone who opens that map without the data set is told it. The
4333
+ * editor never fetches it.
4334
+ */
4335
+ url?: string;
4294
4336
  }
4295
4337
  /** Where the session's game data comes from. */
4296
4338
  export interface GameDataSource {
4297
- /** `bundled` (this build's own files, or the desktop app's extraction), `stored` (a copy kept in the browser), or `none`. */
4298
- kind: "bundled" | "stored" | "none";
4339
+ /**
4340
+ * `bundled` (this build's own files, or the desktop app's extraction), `stored` (a copy kept in the browser),
4341
+ * `reader` (a data set read through a function in memory, `gameData.attach`), or `none`.
4342
+ */
4343
+ kind: "bundled" | "stored" | "reader" | "none";
4299
4344
  /** One line, as Help ▸ Game Data… shows it. */
4300
4345
  label: string;
4301
4346
  /** The data set the files belong to. */
4302
4347
  profile: GameDataProfile;
4303
4348
  /** Set when the desktop app extracted the files from a StarCraft installation. */
4304
4349
  desktop: boolean;
4350
+ /**
4351
+ * The data set's hash, where one is known: what a map saved with it records. Sixteen hex
4352
+ * digits over the files of the set that differ from the game's, by the rule `scmjs mod
4353
+ * play` hands the play page (`docs/cli.md`, "Playing a mod on the play page").
4354
+ */
4355
+ hash?: string;
4356
+ /**
4357
+ * The data set this one was laid over, when it was: `GameDataFiles.base` for an installed
4358
+ * set, and for a set in memory (`kind: "reader"`) the set in use when it was attached.
4359
+ */
4360
+ base?: string;
4305
4361
  }
4306
4362
  /** Bytes for an install: a `File` / `Blob`, or already an array. */
4307
4363
  export type GameDataBytes = Blob | Uint8Array;
@@ -4316,11 +4372,58 @@ export interface GameDataFiles {
4316
4372
  name: string;
4317
4373
  data: GameDataBytes;
4318
4374
  }[];
4319
- /** Loose files by member path (`arr/units.dat`, `unit/terran/marine.grp`), read before any archive. */
4375
+ /** Loose files by member path (`arr/units.dat`, `arr/units.datex`, `unit/terran/marine.grp`), read before any archive. */
4320
4376
  files?: readonly {
4321
4377
  path: string;
4322
4378
  data: GameDataBytes;
4323
4379
  }[];
4380
+ /**
4381
+ * Lay the files over the copy of this data set (an id from `profiles()`; the game's own is
4382
+ * `"starcraft"`) instead of over the game's archives, which `archives` then need not hold.
4383
+ * A copy the play page has used (one made with the game's files for playing) has every
4384
+ * picture the tables name; an older one has only what its own units show.
4385
+ */
4386
+ base?: string;
4387
+ }
4388
+ /**
4389
+ * What a `GameDataReader` is told beside the path it is asked for.
4390
+ */
4391
+ export interface GameDataReadContext {
4392
+ /**
4393
+ * The file as the game's archives name it, in lower case with backslashes:
4394
+ * `arr\units.dat` for `arr/units.dat`, `rez\stat_txt.tbl` for `tileset/stat_txt.tbl`,
4395
+ * `tileset\jungle\ofire.pcx` for `tileset/jungle.ofire.pcx`. A mod keeps its files under
4396
+ * these names, so a reader can look the file up without knowing the copy's layout. Null
4397
+ * for `unit/manifest.json` and `tileset/manifest.json`, the extraction's own lists.
4398
+ */
4399
+ member: string | null;
4400
+ /**
4401
+ * A file of the data set the reader was attached over, by its path in the extracted copy:
4402
+ * the game's own file beneath a mod's, to compare with or to answer from. Null when that
4403
+ * set has no such file. The same as `gameData.read(path, { under: true })`.
4404
+ */
4405
+ under(path: string): Promise<Uint8Array | null>;
4406
+ }
4407
+ /**
4408
+ * A data set that is a function in memory (`gameData.attach`): asked for a file by its path
4409
+ * in the extracted copy (`arr/units.dat`, `unit/zerg/drone.grp`), it answers the bytes, or
4410
+ * null to leave the file to the data set underneath. It may answer at once or with a promise.
4411
+ * `file` names the same file as the game does and reads the set underneath.
4412
+ *
4413
+ * It need not answer `unit/manifest.json`: when it answers null the editor takes the list
4414
+ * of the set underneath and works out from the reader's own files which tables are in
4415
+ * GPTP's `.datex` layout and whether it brings unit names (`rez/unitnames.tbl`) and command
4416
+ * card pictures (`unit/cmdbtns/uniticons.grp`). A reader that answers one is taken at its word.
4417
+ */
4418
+ export type GameDataReader = (path: string, file: GameDataReadContext) => Uint8Array | null | undefined | Promise<Uint8Array | null | undefined>;
4419
+ /** How `gameData.read` reads. */
4420
+ export interface GameDataReadOptions {
4421
+ /**
4422
+ * Read the data set under the one in use: when a set in memory is attached
4423
+ * (`gameData.attach`), the set it was attached over — the game's own file beneath a mod's,
4424
+ * without the reader being asked. The set in use when none is attached.
4425
+ */
4426
+ under?: boolean;
4324
4427
  }
4325
4428
  /**
4326
4429
  * The game data as a whole: which set of files the editor draws from, and installing,
@@ -4329,9 +4432,11 @@ export interface GameDataFiles {
4329
4432
  * `names` (the labels, which follow the loaded set: a unit, weapon, upgrade or
4330
4433
  * technology a mod renamed shows its new name, and the rest keep StarEdit's).
4331
4434
  *
4332
- * A data set is a name over files in the game's own formats; a mod that extends the
4333
- * tables past the game's sizes is not covered. Installing needs the game's two archives
4334
- * every time, since a mod's files are laid over them the way its loader lays them.
4435
+ * A data set is a name over files in the game's own formats, at whatever width they
4436
+ * have: a units.dat of more than 228 types gives the palette more types (named by
4437
+ * `rez\unitnames.tbl` and pictured by `unit\cmdbtns\uniticons.grp` where the data set has
4438
+ * them), a tileset of more than 2,048 groups draws its extended tiles. Installing lays the
4439
+ * files over the game's two archives, as a loader does, or over a copy already here (`base`).
4335
4440
  *
4336
4441
  * @example
4337
4442
  * // A mod plugin: install its files once, then draw with them.
@@ -4368,8 +4473,59 @@ export interface GameDataApi {
4368
4473
  * lower case with forward slashes (`arr/units.dat`, `tileset/jungle.cv5`,
4369
4474
  * `unit/zerg/drone.grp`). `tileset/manifest.json` and `unit/manifest.json` list what was
4370
4475
  * extracted. Null when the copy has no such file, or there is no game data.
4476
+ *
4477
+ * @example
4478
+ * // A mod's units.dat against the game's beneath it, while the mod is attached.
4479
+ * const mine = await api.gameData.read("arr/units.dat");
4480
+ * const theirs = await api.gameData.read("arr/units.dat", { under: true });
4481
+ */
4482
+ read(path: string, options?: GameDataReadOptions): Promise<Uint8Array | null>;
4483
+ /**
4484
+ * Where a copy keeps a file the game's archives name `member` — the path `read` and a
4485
+ * `GameDataReader` take: `arr\units.dat` is `arr/units.dat`, `rez\stat_txt.tbl` is
4486
+ * `tileset/stat_txt.tbl`, `tileset\jungle\ofire.pcx` is `tileset/jungle.ofire.pcx`. Any case,
4487
+ * either slash.
4488
+ */
4489
+ storedPath(member: string): string;
4490
+ /**
4491
+ * Draw from a data set that is a function in memory: every file is asked of `read` first,
4492
+ * and what it leaves (null) comes from the data set in use now. Nothing is stored and the
4493
+ * choice is not remembered — `select`, `install` or a reload leave it. The table and
4494
+ * picture editor's loop: change a file, `reload()`, see the map drawn with it. Units'
4495
+ * Remastered pictures are not drawn from such a data set (what it changed is not known).
4496
+ * `options.hash` is what a map saved with the set records and Test Map hands on (sixteen
4497
+ * hex digits by the rule of `GameDataSource.hash`); it can follow later with `rehash`.
4498
+ * Attaching reads everything again, even with the same profile and function.
4499
+ * Resolves with the source in use afterwards.
4500
+ *
4501
+ * @example
4502
+ * // Try a units.dat edit on the open map without installing anything.
4503
+ * const edited = new Map<string, Uint8Array>([["arr/units.dat", myUnitsDat]]);
4504
+ * await api.gameData.attach({ id: "draft", name: "Draft" }, (path) => edited.get(path) ?? null);
4505
+ * // … later, after changing the bytes in `edited`:
4506
+ * api.gameData.reload();
4507
+ */
4508
+ attach(profile: GameDataProfile, read: GameDataReader, options?: {
4509
+ hash?: string;
4510
+ }): Promise<GameDataSource>;
4511
+ /**
4512
+ * Read the data set in use afresh: everything decoded from it (tables, tilesets, pictures,
4513
+ * names) is dropped and the map is drawn again — after the files behind `attach` changed.
4514
+ */
4515
+ reload(): void;
4516
+ /**
4517
+ * Give the set in memory in use (`attach`) a new hash, or none with null, without reading
4518
+ * anything again: for a hash worked out after the files were attached or redrawn. What a
4519
+ * map saved from then on records and what Test Map hands on change; nothing is decoded
4520
+ * afresh. The `"gameData"` event fires. Returns the source, or null (and nothing
4521
+ * changes) when no set in memory is in use.
4522
+ *
4523
+ * @example
4524
+ * // Draw from the edit at once, and name it when its digest is ready.
4525
+ * api.gameData.reload();
4526
+ * api.gameData.rehash(await digestOfChangedFiles());
4371
4527
  */
4372
- read(path: string): Promise<Uint8Array | null>;
4528
+ rehash(hash: string | null): GameDataSource | null;
4373
4529
  }
4374
4530
  /**
4375
4531
  * The constants a plugin needs to *write* a record rather than read one: the bit masks in
@@ -5154,9 +5310,34 @@ export interface PanelSpec {
5154
5310
  export interface FileTypeSpec {
5155
5311
  /** What the files are, for File ▸ Open's list of types ("StarCraft replay"). */
5156
5312
  name: string;
5157
- /** With the dot, any case: `[".rep"]`. */
5313
+ /** With the dot, any case: `[".rep"]`. May be empty for a type that only opens folders (`openFolder`). */
5158
5314
  extensions: string[];
5159
5315
  open(files: File[]): void | Promise<void>;
5316
+ /**
5317
+ * A folder dropped on the editor, as its files with their paths. Each folder of a drop is
5318
+ * offered on its own to the plugins that have `openFolder`, in the order they registered;
5319
+ * the first whose `acceptsFolder` passes (or that has none) takes it. A folder none
5320
+ * takes is left alone. Without `openFolder` a plugin is never offered a folder.
5321
+ *
5322
+ * @example
5323
+ * api.ui.fileType({
5324
+ * name: "StarCraft mod", extensions: [".mpq"], open: openArchives,
5325
+ * acceptsFolder: (paths) => paths.some((p) => /(^|\/)arr\/units\.dat(ex)?$/i.test(p)),
5326
+ * openFolder: (files) => openLooseFiles(files),
5327
+ * });
5328
+ */
5329
+ openFolder?(files: FolderFile[]): void | Promise<void>;
5330
+ /** Whether a dropped folder is one `openFolder` opens, by its files' paths (`FolderFile.path`). */
5331
+ acceptsFolder?(paths: readonly string[]): boolean;
5332
+ }
5333
+ /**
5334
+ * One file of a folder the user picked or dropped, with where it is in the folder:
5335
+ * `path` runs from the folder's parent with forward slashes, the folder's own name first
5336
+ * (`My Mod/arr/units.dat`), as the browser's `webkitRelativePath` has it.
5337
+ */
5338
+ export interface FolderFile {
5339
+ path: string;
5340
+ file: File;
5160
5341
  }
5161
5342
  /**
5162
5343
  * Another way to look at a map, shown where the map is drawn (`ui.mapView`): a replay's
@@ -5521,6 +5702,17 @@ export interface TestMapFile {
5521
5702
  bytes: Uint8Array;
5522
5703
  /** The file name the game would list, `.scx` or `.scm`. */
5523
5704
  name: string;
5705
+ /**
5706
+ * The data set the map is being made with, when it is not the game's own: its id and
5707
+ * name as `gameData.profile()` gives them, and the digest of its files (empty when none
5708
+ * is known). A target that plays the map should play it with those files — the site's
5709
+ * play page is handed it as `dataSet` in its `scmjs.play` message.
5710
+ */
5711
+ dataSet?: {
5712
+ id: string;
5713
+ name: string;
5714
+ hash: string;
5715
+ };
5524
5716
  }
5525
5717
  /** What a preferences page's `mount` is handed. */
5526
5718
  export interface PreferencesPageHost {
@@ -5717,9 +5909,15 @@ export interface PickedObject {
5717
5909
  index: number;
5718
5910
  }
5719
5911
  export interface PickFilesOptions {
5720
- /** `accept` for the file input, e.g. `"image/*"` or `".png,.jpg"`. */
5912
+ /** `accept` for the file input, e.g. `"image/*"` or `".png,.jpg"`. Not used for a folder. */
5721
5913
  accept?: string;
5722
5914
  multiple?: boolean;
5915
+ /**
5916
+ * Pick a folder rather than files: `pickFiles` then resolves with every file in it and
5917
+ * its subfolders as `FolderFile`s, with their paths. The browser's folder picker where it
5918
+ * has one, else the file input's folder mode; an empty list when the user cancels.
5919
+ */
5920
+ directory?: boolean;
5723
5921
  }
5724
5922
  export interface UiApi {
5725
5923
  /**
@@ -5834,8 +6032,21 @@ export interface UiApi {
5834
6032
  * hears the pointer, but never takes it. As many as you like; they go with the plugin.
5835
6033
  */
5836
6034
  overlay(spec: OverlaySpec): OverlayHandle;
5837
- /** The browser's file picker; resolves with an empty list on cancel. */
5838
- pickFiles(options?: PickFilesOptions): Promise<File[]>;
6035
+ /**
6036
+ * The browser's file picker; resolves with an empty list on cancel. With `directory: true`
6037
+ * the folder picker, resolving with the folder's files and their paths.
6038
+ *
6039
+ * @example
6040
+ * const files = await api.ui.pickFiles({ directory: true });
6041
+ * for (const { path, file } of files) api.log(`${path}: ${file.size} bytes`); // "My Mod/arr/units.dat: 19876 bytes"
6042
+ */
6043
+ pickFiles(options: PickFilesOptions & {
6044
+ directory: true;
6045
+ }): Promise<FolderFile[]>;
6046
+ pickFiles(options?: PickFilesOptions & {
6047
+ directory?: false;
6048
+ }): Promise<File[]>;
6049
+ pickFiles(options?: PickFilesOptions): Promise<File[] | FolderFile[]>;
5839
6050
  /**
5840
6051
  * Let the user drag a rectangle on the map. The viewport switches to a crosshair, draws
5841
6052
  * the marquee and takes the gesture ahead of the active layer's tools; resolves with
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scm-js/plugin-api",
3
- "version": "1.67.0",
3
+ "version": "1.68.0",
4
4
  "description": "Type declarations for the scmJS plugin API",
5
5
  "license": "MIT",
6
6
  "types": "index.d.ts",