@scm-js/plugin-api 1.39.0 → 1.41.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 +88 -43
  2. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -297,6 +297,14 @@ export interface TriggerRecord {
297
297
  /** The game's bookkeeping byte (offset 2399); StarEdit writes 0. */
298
298
  currentAction: number;
299
299
  }
300
+ /** A trigger that is only read: nothing on it, its conditions or its actions may be written. */
301
+ export interface ReadonlyTrigger {
302
+ readonly conditions: readonly Readonly<ConditionRecord>[];
303
+ readonly actions: readonly Readonly<ActionRecord>[];
304
+ readonly flags: number;
305
+ readonly players: readonly number[];
306
+ readonly currentAction: number;
307
+ }
300
308
  declare const ConditionType: {
301
309
  readonly None: 0;
302
310
  readonly CountdownTimer: 1;
@@ -2193,6 +2201,29 @@ export declare const PLUGIN_API_VERSION = 1;
2193
2201
  export interface Disposable {
2194
2202
  dispose(): void;
2195
2203
  }
2204
+ export type Primitive = string | number | boolean | bigint | symbol | null | undefined;
2205
+ /** A typed array without the calls that write to it, and with a read-only index. */
2206
+ export type ReadonlyTyped<T> = {
2207
+ readonly [K in keyof T as K extends "set" | "fill" | "copyWithin" | "reverse" | "sort" ? never : K]: T[K];
2208
+ };
2209
+ /**
2210
+ * `T` with every property, element and entry read-only, all the way down: arrays become
2211
+ * `readonly` arrays, a `Set` or `Map` its read-only form, a typed array `ReadonlyTyped`.
2212
+ */
2213
+ export type DeepReadonly<T> = T extends Primitive | ((...args: never[]) => unknown) ? T : T extends Uint8Array | Uint16Array | Uint32Array ? ReadonlyTyped<T> : T extends Set<infer V> ? ReadonlySet<DeepReadonly<V>> : T extends Map<infer K, infer V> ? ReadonlyMap<K, DeepReadonly<V>> : T extends readonly (infer V)[] ? readonly DeepReadonly<V>[] : {
2214
+ readonly [K in keyof T]: DeepReadonly<T[K]>;
2215
+ };
2216
+ /**
2217
+ * The open map as a plugin sees it: the editor's own live object, typed so that writing
2218
+ * to it does not compile. A direct write would skip undo, the file's dirty tracking, the
2219
+ * repaint and a shared map's other editors — every change goes through `document.edit`,
2220
+ * `document.update` or `document.sections`.
2221
+ *
2222
+ * The object is live, so a read after an edit sees the edit; copy what you need to keep
2223
+ * (`[...scn.units]`, `scn.tiles.slice()`). A copy of a record is an ordinary one:
2224
+ * `{ ...scn.units[0], x: 64 }` is a `UnitRecord` to hand to `tx.addUnits`.
2225
+ */
2226
+ export type ReadonlyScenario = DeepReadonly<Scenario>;
2196
2227
  /**
2197
2228
  * A transaction builder's return type: whatever it likes, so long as it is not a promise.
2198
2229
  *
@@ -2586,10 +2617,10 @@ export interface DocumentApi {
2586
2617
  isOpen(): boolean;
2587
2618
  info(): DocumentInfo | null;
2588
2619
  /**
2589
- * The live scenario, for reading. Writing to it directly bypasses undo, dirty
2590
- * tracking and repaints — use `edit`.
2620
+ * The live scenario, for reading — see `ReadonlyScenario`. To change the map, use
2621
+ * `edit`, `update` or `sections`.
2591
2622
  */
2592
- scenario(): Scenario | null;
2623
+ scenario(): ReadonlyScenario | null;
2593
2624
  /**
2594
2625
  * Run `build` against a transaction and record what it did as one undo entry.
2595
2626
  * Operations apply as they are called, so later ones see earlier ones' results.
@@ -2809,7 +2840,7 @@ export interface SectionsApi {
2809
2840
  * are left as they are and missing from `rebuilt`; omit `names` for every modelled
2810
2841
  * section the map has a model for.
2811
2842
  */
2812
- rebuild(names?: string[]): RebuildResult;
2843
+ rebuild(names?: readonly string[]): RebuildResult;
2813
2844
  }
2814
2845
  /** What `tx.rebuildIsom` did. */
2815
2846
  export interface IsomRebuildResult {
@@ -2825,7 +2856,7 @@ export interface IsomRebuildResult {
2825
2856
  /** Cells for the bulk terrain operations: a tile rect, or cell indices (`y * width + x`). */
2826
2857
  export type Cells = Rect | Iterable<number>;
2827
2858
  export interface EditTransaction {
2828
- readonly scenario: Scenario;
2859
+ readonly scenario: ReadonlyScenario;
2829
2860
  readonly width: number;
2830
2861
  readonly height: number;
2831
2862
  /** MTXM (what the game draws) at a cell. */
@@ -2859,9 +2890,9 @@ export interface EditTransaction {
2859
2890
  rebuildIsom(): IsomRebuildResult | null;
2860
2891
  /** A StarEdit-style unit record (fresh serial, valid/used masks) centred on map pixels. */
2861
2892
  makeUnit(unitId: number, owner: number, x: number, y: number): UnitRecord;
2862
- addUnits(records: UnitRecord[]): number[];
2863
- removeUnits(indices: number[]): number;
2864
- updateUnits(indices: number[], patch: (u: UnitRecord) => Partial<UnitRecord>): number;
2893
+ addUnits(records: readonly UnitRecord[]): number[];
2894
+ removeUnits(indices: readonly number[]): number;
2895
+ updateUnits(indices: readonly number[], patch: (u: UnitRecord) => Partial<UnitRecord>): number;
2865
2896
  /**
2866
2897
  * A unit the way the Units palette places one: a building snaps its placement box to
2867
2898
  * the tile grid (when the palette's *Snap to grid* is on), anything else lands where
@@ -2875,8 +2906,8 @@ export interface EditTransaction {
2875
2906
  flipped?: boolean;
2876
2907
  disabled?: boolean;
2877
2908
  }): SpriteRecord;
2878
- addSprites(records: SpriteRecord[]): number[];
2879
- removeSprites(indices: number[]): number;
2909
+ addSprites(records: readonly SpriteRecord[]): number[];
2910
+ removeSprites(indices: readonly number[]): number;
2880
2911
  /** `makeSprite` + `addSprites` in one, kept on the map; returns the record's index. */
2881
2912
  placeSprite(kind: SpriteKind, id: number, owner: number, x: number, y: number, opts?: {
2882
2913
  flipped?: boolean;
@@ -2884,14 +2915,14 @@ export interface EditTransaction {
2884
2915
  }): number;
2885
2916
  /** Stamp a doodad (a `dddata.bin` id) at a tile; returns its record index, or -1 when unknown or off the map. */
2886
2917
  placeDoodad(doodadId: number, tx: number, ty: number, owner?: number): number;
2887
- removeDoodads(indices: number[]): number;
2918
+ removeDoodads(indices: readonly number[]): number;
2888
2919
  /**
2889
2920
  * Make doodads plain terrain: their records go, their tiles stay (in TILE as well as
2890
2921
  * MTXM, so the cells are ground to every tool from here on) and an overlay stays as an
2891
2922
  * ordinary sprite. The way to touch up a ramp or a cliff piece tile by tile.
2892
2923
  * Returns records converted.
2893
2924
  */
2894
- convertDoodads(indices: number[]): number;
2925
+ convertDoodads(indices: readonly number[]): number;
2895
2926
  /**
2896
2927
  * Stamp a `Clip` with its top-left tile at (tx, ty) inside this transaction — the
2897
2928
  * clipboard's paste without the clipboard: the clip can come from anywhere (`clipboard.copy`,
@@ -2905,7 +2936,7 @@ export interface EditTransaction {
2905
2936
  /** A location in the lowest free slot (pixel bounds); returns the slot, or -1 when the table is full. */
2906
2937
  addLocation(bounds: Bounds, name?: string, elevationFlags?: number): number;
2907
2938
  editLocation(index: number, patch: LocationPatch): boolean;
2908
- removeLocations(indices: number[]): number;
2939
+ removeLocations(indices: readonly number[]): number;
2909
2940
  /** Set (`"fog"`) or clear the `players` bits (bit n = player n + 1) over cells; creates MASK on first use. */
2910
2941
  setFog(cells: Cells, players: number, mode: FogMode): number;
2911
2942
  /**
@@ -2946,7 +2977,7 @@ export interface EditTransaction {
2946
2977
  y: number;
2947
2978
  }[];
2948
2979
  /** Shift units by a pixel delta; buildings re-snap to the grid when `snap` (the palette's option when omitted). Returns records changed. */
2949
- moveUnits(indices: number[], dx: number, dy: number, snap?: boolean): number;
2980
+ moveUnits(indices: readonly number[], dx: number, dy: number, snap?: boolean): number;
2950
2981
  /**
2951
2982
  * Tools ▸ Auto-place Start Locations: one per player on a ring or in the corners, each
2952
2983
  * moved to the nearest spot the placement checks accept; `replace` removes the existing
@@ -2958,11 +2989,11 @@ export interface EditTransaction {
2958
2989
  margin?: number;
2959
2990
  replace?: boolean;
2960
2991
  }): StartPlacementResult;
2961
- updateSprites(indices: number[], patch: (r: SpriteRecord) => Partial<SpriteRecord>): number;
2992
+ updateSprites(indices: readonly number[], patch: (r: SpriteRecord) => Partial<SpriteRecord>): number;
2962
2993
  /** Shift sprites by a pixel delta, clamped to the map. Returns records changed. */
2963
- moveSprites(indices: number[], dx: number, dy: number): number;
2994
+ moveSprites(indices: readonly number[], dx: number, dy: number): number;
2964
2995
  /** Change a doodad's owner or disabled flag (its tiles stay). Returns records changed. */
2965
- updateDoodads(indices: number[], patch: {
2996
+ updateDoodads(indices: readonly number[], patch: {
2966
2997
  owner?: number;
2967
2998
  disabled?: number;
2968
2999
  }): number;
@@ -2997,7 +3028,7 @@ export interface TriggerListUpdate {
2997
3028
  add(trigger: TriggerRecord, at?: number): number;
2998
3029
  /** Replace one record; false when there is none at `index`. */
2999
3030
  replace(index: number, trigger: TriggerRecord): boolean;
3000
- remove(indices: number[]): number;
3031
+ remove(indices: readonly number[]): number;
3001
3032
  move(from: number, to: number): boolean;
3002
3033
  /**
3003
3034
  * Parse the text format (`triggers.text.print`'s inverse) and append the result, or
@@ -3042,7 +3073,7 @@ export interface SwitchesUpdate {
3042
3073
  * the end marks the map modified and tells the chrome to re-read.
3043
3074
  */
3044
3075
  export interface UpdateTransaction {
3045
- readonly scenario: Scenario;
3076
+ readonly scenario: ReadonlyScenario;
3046
3077
  /** TRIG. */
3047
3078
  readonly triggers: TriggerListUpdate;
3048
3079
  /** MBRF, the mission briefing's own list of the same records. */
@@ -3180,7 +3211,7 @@ export interface TriggerDefsApi {
3180
3211
  }
3181
3212
  /** Printing and parsing the text trigger format (File ▸ Import / Export ▸ Triggers). */
3182
3213
  export interface TriggerTextApi {
3183
- print(triggers: TriggerRecord[], options?: {
3214
+ print(triggers: readonly ReadonlyTrigger[], options?: {
3184
3215
  briefing?: boolean;
3185
3216
  }): string;
3186
3217
  /**
@@ -3191,7 +3222,7 @@ export interface TriggerTextApi {
3191
3222
  briefing?: boolean;
3192
3223
  }): TextTrigger[];
3193
3224
  /** One trigger as its `Trigger(…)` block. */
3194
- one(trigger: TriggerRecord, options?: {
3225
+ one(trigger: ReadonlyTrigger, options?: {
3195
3226
  briefing?: boolean;
3196
3227
  }): string;
3197
3228
  }
@@ -3213,22 +3244,22 @@ export interface TriggersApi {
3213
3244
  readonly defs: TriggerDefsApi;
3214
3245
  readonly text: TriggerTextApi;
3215
3246
  /** An empty trigger owned by the given player groups (All Players by default). */
3216
- newTrigger(players?: number[]): TriggerRecord;
3247
+ newTrigger(players?: readonly number[]): TriggerRecord;
3217
3248
  /** A condition of a type, with StarEdit's defaults for its arguments. */
3218
3249
  newCondition(type: number): ConditionRecord;
3219
3250
  newAction(type: number, briefing?: boolean): ActionRecord;
3220
- isPreserved(trigger: TriggerRecord): boolean;
3221
- setPreserved(trigger: TriggerRecord, on: boolean): TriggerRecord;
3251
+ isPreserved(trigger: ReadonlyTrigger): boolean;
3252
+ setPreserved(trigger: ReadonlyTrigger, on: boolean): TriggerRecord;
3222
3253
  /** The indices of the triggers any of these player groups own. */
3223
- triggersFor(list: TriggerRecord[], groups: number[]): number[];
3254
+ triggersFor(list: readonly ReadonlyTrigger[], groups: readonly number[]): number[];
3224
3255
  /** The three lines the trigger list shows: players, conditions, actions. */
3225
- summarize(trigger: TriggerRecord, briefing?: boolean): {
3256
+ summarize(trigger: ReadonlyTrigger, briefing?: boolean): {
3226
3257
  players: string;
3227
3258
  conditions: string;
3228
3259
  actions: string;
3229
3260
  };
3230
3261
  /** A trigger's `Comment` action text, when it has one. */
3231
- comment(trigger: TriggerRecord): string | null;
3262
+ comment(trigger: ReadonlyTrigger): string | null;
3232
3263
  /**
3233
3264
  * EUD: the player value that reaches a memory address through the deaths table
3234
3265
  * (`(address − consts.triggers.deathsTable) / 4`, the address rounded down to a dword)
@@ -3244,7 +3275,7 @@ export interface TriggersApi {
3244
3275
  * finds its run by (`TriggerClaimSpec.locate`), and what an editor keeps a selection
3245
3276
  * by across a list that changed under it.
3246
3277
  */
3247
- fingerprint(trigger: TriggerRecord): string;
3278
+ fingerprint(trigger: ReadonlyTrigger): string;
3248
3279
  /**
3249
3280
  * Every death-counter cell (`[player, unit]`) and switch the triggers read or write —
3250
3281
  * TRIG when `list` is omitted. A player group counts as every slot it can resolve to
@@ -3252,7 +3283,7 @@ export interface TriggersApi {
3252
3283
  * counts for nothing, and an EUD player is an address, not a cell. What a plugin that
3253
3284
  * allocates counters or switches of its own must keep clear of.
3254
3285
  */
3255
- usage(list?: TriggerRecord[]): {
3286
+ usage(list?: readonly ReadonlyTrigger[]): {
3256
3287
  cells: [
3257
3288
  player: number,
3258
3289
  unit: number
@@ -3278,7 +3309,7 @@ export interface TriggersApi {
3278
3309
  * const set = new Set(refs.filter((r) => r.access === "write").map((r) => r.id));
3279
3310
  * const never = [...new Set(refs.filter((r) => r.access === "read" && !set.has(r.id)).map((r) => r.id))];
3280
3311
  */
3281
- references(list?: TriggerRecord[], options?: {
3312
+ references(list?: readonly ReadonlyTrigger[], options?: {
3282
3313
  briefing?: boolean;
3283
3314
  }): TriggerRefs[];
3284
3315
  /**
@@ -3288,8 +3319,8 @@ export interface TriggersApi {
3288
3319
  * settled while the game runs (*Foes*, *Allies*, *Neutral Players*, *Non Allied Victory
3289
3320
  * Players*); `players` is then every slot it could name.
3290
3321
  */
3291
- resolvePlayers(group: number, owners?: number[]): {
3292
- players: number[];
3322
+ resolvePlayers(group: number, owners?: readonly number[]): {
3323
+ players: readonly number[];
3293
3324
  approximate: boolean;
3294
3325
  };
3295
3326
  /**
@@ -3305,7 +3336,7 @@ export interface TriggersApi {
3305
3336
  * run first — what an editor of the trigger list needs to fence or lock the generated
3306
3337
  * runs. A claim whose `locate` finds nothing, or throws, is left out.
3307
3338
  */
3308
- claims(list?: TriggerRecord[]): TriggerClaimRange[];
3339
+ claims(list?: readonly ReadonlyTrigger[]): TriggerClaimRange[];
3309
3340
  }
3310
3341
  /** One claimed run as `triggers.claims` located it. */
3311
3342
  export interface TriggerClaimRange {
@@ -3324,19 +3355,24 @@ export interface TriggerClaimRange {
3324
3355
  * in it — by a hash of the records, say — or null when the records are not there.
3325
3356
  */
3326
3357
  export interface TriggerClaimSpec {
3327
- /** What generated the run, in words, as a sentence would use it: `"the trigger script"`. */
3358
+ /**
3359
+ * What generated the run, in words, as a sentence would use it: `"the trigger script"`.
3360
+ * `label`, `badge` and `openLabel` are given in English and shown through the plugin's
3361
+ * registered catalogues, so they follow a language change; `api.triggers.claims` hands
3362
+ * back the English.
3363
+ */
3328
3364
  label: string;
3329
3365
  /** The word on the badge the trigger list shows on each row; the plugin's id by default. */
3330
3366
  badge?: string;
3331
3367
  /** Where the run sits in `list`, or null when it is not there (edited by hand, or gone). */
3332
- locate(list: TriggerRecord[]): {
3368
+ locate(list: readonly ReadonlyTrigger[]): {
3333
3369
  start: number;
3334
3370
  count: number;
3335
3371
  } | null;
3336
3372
  /** A sentence about one trigger of the run (its index in `list`), shown in place of the editor's form. */
3337
- describe?(index: number, list: TriggerRecord[]): string;
3373
+ describe?(index: number, list: readonly ReadonlyTrigger[]): string;
3338
3374
  /** Open the plugin's own editor on that trigger — the button under the sentence. */
3339
- open?(index: number, list: TriggerRecord[]): void;
3375
+ open?(index: number, list: readonly ReadonlyTrigger[]): void;
3340
3376
  /** That button's label; `Open <plugin name>` by default. */
3341
3377
  openLabel?: string;
3342
3378
  }
@@ -3992,13 +4028,13 @@ export interface SelectionApi {
3992
4028
  markedArea(): Rect | null;
3993
4029
  markArea(rect: Rect | null): void;
3994
4030
  units(): number[];
3995
- setUnits(indices: number[]): void;
4031
+ setUnits(indices: readonly number[]): void;
3996
4032
  sprites(): number[];
3997
- setSprites(indices: number[]): void;
4033
+ setSprites(indices: readonly number[]): void;
3998
4034
  doodads(): number[];
3999
- setDoodads(indices: number[]): void;
4035
+ setDoodads(indices: readonly number[]): void;
4000
4036
  locations(): number[];
4001
- setLocations(indices: number[]): void;
4037
+ setLocations(indices: readonly number[]): void;
4002
4038
  layer(): EditorLayer;
4003
4039
  setLayer(layer: EditorLayer): void;
4004
4040
  /** Layers the Layers panel has locked: their tools refuse to change the map. */
@@ -4068,7 +4104,7 @@ export interface ClipboardApi {
4068
4104
  */
4069
4105
  export interface ExchangeApi {
4070
4106
  /** Raw 2400-byte TRIG records, SCMDraft's `.trg`; string indices are the map's own. */
4071
- encodeTrg(triggers: TriggerRecord[]): Uint8Array;
4107
+ encodeTrg(triggers: readonly ReadonlyTrigger[]): Uint8Array;
4072
4108
  decodeTrg(bytes: Uint8Array): TriggerRecord[];
4073
4109
  /** The whole string table as `index<TAB>text` lines, control bytes as `<XX>`. */
4074
4110
  formatStrings(): string;
@@ -4605,7 +4641,7 @@ export type OverlayAbove =
4605
4641
  * it goes away with the plugin.
4606
4642
  */
4607
4643
  export interface OverlaySpec {
4608
- /** Shown in View ▸ Overlays and the Layers panel. Unique per plugin. */
4644
+ /** Shown in View ▸ Overlays and the Layers panel. Unique per plugin. In English, like a menu label: shown through the plugin's registered catalogues. */
4609
4645
  name: string;
4610
4646
  /** Start visible; true by default. What the user last set for this name wins for the session. */
4611
4647
  visible?: boolean;
@@ -5105,6 +5141,11 @@ export type TopMenu = "File" | "Edit" | "View" | "Layer" | "Scenario" | "Trigger
5105
5141
  */
5106
5142
  export type MenuPath = TopMenu | `${TopMenu}/${string}` | (string & {});
5107
5143
  export interface MenuItemSpec {
5144
+ /**
5145
+ * In English, like the path's segments: it is the item's identity (another plugin's
5146
+ * `after` names it), and the menu shows it translated through the catalogues the plugin
5147
+ * registered with `i18n.register`, so it follows a language change.
5148
+ */
5108
5149
  label: string;
5109
5150
  /** Display only — bind the key with `hotkeys.add`. */
5110
5151
  shortcut?: string;
@@ -5166,6 +5207,10 @@ export interface ContextMenuContext {
5166
5207
  markedArea: Rect | null;
5167
5208
  }
5168
5209
  export interface ContextItemSpec {
5210
+ /**
5211
+ * A fixed label is given in English and shown through the plugin's registered catalogues;
5212
+ * a function is called when the menu opens, so it translates with `i18n.t` itself.
5213
+ */
5169
5214
  label: string | ((ctx: ContextMenuContext) => string);
5170
5215
  enabled?: (ctx: ContextMenuContext) => boolean;
5171
5216
  visible?: (ctx: ContextMenuContext) => boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scm-js/plugin-api",
3
- "version": "1.39.0",
3
+ "version": "1.41.0",
4
4
  "description": "Type declarations for the scmJS plugin API",
5
5
  "license": "MIT",
6
6
  "types": "index.d.ts",