Changing a map
Each command here writes the map back over itself, or to another file with
--out other.scx, and keeps everything else in the file as it was: its sounds and other
files, and the way the scenario was compressed, as Save does in the editor.
Name, description and revision#
scmjs map set is Map Properties. It sets what you give and leaves the rest, and writes
nothing when nothing changed.
scmjs map set ridge.scx --name "Ridge <04>2.1" --description "Final for the tournament."
scmjs map set ridge.scx --revision remastered
scmjs map set ridge.scx --encoding euc-kr
✔ ridge.scx: Name ridge → Ridge · written to ridge.scx · 3.0 KB
In a name or description, <XX> is one of the game's colour codes, as in the string
files. --revision is one of original, hybrid, broodwar, remastered and remasteredHybrid,
and does what the editor's revision change does. --encoding is how the map's text is
stored for 1.16.1: utf-8, euc-kr, shift_jis, gbk, big5, windows-1251 or windows-1252.
Remastered reads UTF-8.
Resizing#
scmjs map resize ridge.scx --size 128x96 is Scenario ▸ Resize.
✔ ridge.scx: 96 × 64 → 128 × 96 · written to ridge.scx · 3.6 KB
--anchor says which part of the map stays where it is: center (the default), top-left,
top, top-right, left, right, bottom-left, bottom or bottom-right. Units and other things
that fall outside the new edge are dropped, and the command lists them. Locations that
hang over the edge are pulled inside unless you pass --no-clamp. New ground is the
tileset's base terrain.
Triggers and strings as text#
A map's triggers and strings can live beside it as text files. In git a change to them shows as a diff; in any text editor you can search and replace across a hundred triggers. Then they go back into the map.
scmjs map triggers export mymap.scx > triggers.txt
scmjs map triggers import mymap.scx triggers.txt
triggers export prints the triggers in the text form the editor's trigger window uses
(the trigger reference lists every condition and action in it), or writes
them to --out. A file name ending in .trg writes the raw trigger records instead: the
file SCMDraft's Export Triggers makes and reads. --briefing exports the mission
briefing's triggers.
triggers import replaces the map's triggers with the file's, or adds them after the
map's own with --append. Text the map cannot resolve, such as a condition it does not
know or a location it does not have, is refused with its line number, and nothing is
written.
scmjs map strings export mymap.scx > strings.txt
scmjs map strings import mymap.scx strings.txt
The strings file has one string a line: the string's number, a tab, and its text, with the
game's colour codes written as <XX>. A # starts a comment. strings import sets each
numbered string in place and adds any numbered past the end. Nothing is renumbered,
because the map's triggers and locations refer to strings by number; a line you take out
leaves that string as it is.
Editing triggers or strings in your editor#
edit exports, opens the text in your editor, and imports it when you close the editor:
scmjs map triggers edit mymap.scx
scmjs map strings edit mymap.scx --editor "code --wait"
The command waits until the editor is closed. If you saved a change, the text is imported
as import reads it and the map is written. If you saved nothing, or only a comment, the
map is left alone and the command says nothing changed.
Text that cannot be read, such as a condition the map does not know or a strings line that
is not a number and a tab, is not imported and the map is left alone. The error gives the
line. The file you edited is kept with your changes in it, and the command prints its path
and the import command that brings it in once you have fixed it.
The editor is --editor, or else the VISUAL or EDITOR setting as git uses them, or
else Notepad on Windows and vi elsewhere. An editor that opens a window has to be told to
wait for it to close, or the command sees the file unchanged straight away: code --wait,
subl -w, gedit -s. triggers edit --briefing edits the briefing's triggers.
Removing unused strings#
scmjs map strings prune ridge.scx is the string editor's Delete Unused. A string that no
trigger, location, name, force, sound or setting refers to is blanked, and the blank slots
at the end of the table are dropped. Nothing is renumbered, so the triggers still point at
the right strings. --dry-run lists what would go and writes nothing.
Making a map smaller#
scmjs map optimize takes out of a map what the game never uses and stores the rest as
small as the game still reads it. It writes over the map unless --out names another
file; --dry-run says what would go and writes nothing. A table says what each step
saved:
$ scmjs map optimize "(3)Defenders of the Galaxy.scm"
✔ (3)Defenders of the Galaxy.scm: 44 KB → 36 KB (−17.7%) · written to (3)Defenders of the Galaxy.scm
Step Saved
Unused sounds none -
Other files none -
Unused strings 23 strings 243 B
Editor sections skipped: with --strip-editor -
Compression PKWARE, rewritten 7.5 KB
Removed
string 21 - Defend your Nexus at all costs. nothing in the map refers to it
…
In order, the steps are:
- Sounds that no trigger or briefing plays and the sound list does not name, and the sound list's entries for files the map does not hold. A sound only the list names stays: it is in the Sound Editor, waiting for a trigger.
- Any other file in the archive that nothing in the map names, listed one by one.
- The strings nothing refers to, as
strings prunedrops them. - With
--strip-editor, the sections only editors read (below). - Every file compressed again. PKWARE is the default, since every version of the game
reads it. A map that was already in zlib, which only Remastered reads, gets the smaller
of the two.
--compression zlibor--compression pkwarechooses.
Some files stay whatever refers to them. The game itself opens a few files by name:
(attributes), (signature) and the marks Blizzard's own maps and the ladder maps carry,
such as blizzard_2.cnd. TrigScript's, Magenta's and the editor's build files stay
(trigscript\, magenta\, scmjs\), as do files the archive holds without a name. A
sound StarEdit imported is stored with the game's own sound compression, which loses a
little quality to be about four times smaller; the editor reads it but cannot write it,
and writing it any other way would make the map larger, so such a file is kept exactly as
it is stored. --keep names other files to keep: --keep "notes\" keeps a folder.
A map built from a TrigScript script keeps its scenario as it was built, so the editor still opens the map you were editing; only its stray files and its compression change. A map whose triggers write to the game's memory (an EUD map) keeps every string, since such a map can reach a string by its place in the table.
--strip-editor also drops ISOM, TILE, DD2, IVER, IVE2, IOWN, UPUS, SWNM
and WAV, the sections the CHK reference marks as read by editors only,
with the strings and sounds only they named. The map plays the same, but an editor that
opens it has no isometric terrain to paint, no doodads to select, no sound list and no
switch names. It is the Save dialog's Smallest that plays for these sections; keep the
original to go on editing.
The result is opened again as the editor opens a map before anything is written, and when
the editor cannot write the map any smaller, nothing is written. scmjs map diff between
the two shows only what went.
The archive as a folder#
A map file is an archive: the scenario, staredit\scenario.chk, with the map's sounds, a
TrigScript script's files and anything else put in it. scmjs map unpack writes those
files into a folder at their paths in the archive, and scmjs map pack puts the folder
back together:
scmjs map unpack ridge.scx # → ridge/staredit/scenario.chk, ridge/staredit/wav/…, ridge/archive.json
scmjs map unpack ridge.scx --list # the files and their sizes, nothing written
scmjs map pack ridge # → ridge.scx again, beside the folder
archive.json in the folder records what the files cannot: the map's name, how the
scenario was compressed and whether it was encrypted, whether the archive had a file list,
and the files a protected map keeps without a name, which go under stored/ byte for
byte. pack writes the archive as archive.json says, so an unpacked and repacked map
reads the same in the editor and the game.
A file you add to the folder goes into the map at its path: a sound dropped into
staredit/wav/, a script's file under trigscript/. --compression none|pkware|zlib
changes how every file is stored, and --out names the folder or the file to write.
Nothing is parsed or restored on the way: the folder is the file as it is. A map the
editor built from a TrigScript script unpacks into its built scenario and the scmjs/
files holding the source, and packs back into the same built map. The packed file is
opened as a map before it is written, so a folder whose scenario is not a scenario is
refused.
With --text, the scenario itself is written as text files under scenario/ instead of
the one binary staredit/scenario.chk, so the map can be kept in git and read and merged
as text. A map as a folder of text explains the folder.
Building a TrigScript map#
The editor builds a map with a TrigScript script every time it saves it:
the script's trigger() calls become triggers in the map, and its program()s are built
into the file. scmjs map build does the same without the editor, so a map kept in git
can be built where it is checked, and a script edited outside the editor can be applied
from the terminal.
$ scmjs map build maps/ridge.scx --test
✔ ridge.scx: 12 triggers → #4–#15 · 2 programs (Remastered) · 7 tests passed · written to maps/ridge.scx · 48 KB
The script is the one the map keeps, with the copies of libraries the map keeps. It is compiled against the map's names, settings and placed units, as the editor compiles it. The built file opens in the editor as the map you were editing, with the build kept beside it, as a saved one does.
| Option | What it does |
|---|---|
--out |
Writes the built map to another file |
--test |
Runs the script's tests first, in the simulator; a failing test stops the build. A map whose script settings make tests guard the build runs them without the flag |
--check |
Compiles (and tests, with --test) and writes nothing: the check for a pull request |
An error in the script is printed as main.ts:12:5 error …, a failing test with its name
and line, and the command exits with 1 with nothing written.
The compiler is TrigScript's own, the version this release of the editor ships, so the
command and the editor build the same map. It compiles with TypeScript, which is not part
of the command: install it in the map's project (npm install typescript) or globally
(npm install -g typescript). A sound of two seconds or more is encoded as Ogg Vorbis
with an encoder the command fetches once, unless the project has wasm-media-encoders
installed.
Two things differ from the editor. In the tests, a unit type's hit points come only from the map's own unit settings: the editor falls back on the game's unit data for a type the map leaves at its defaults, and the command does not have that data. A test that counts on a Marine's 40 hit points should set them in the map. And programs that other plugins add to a build are not built here: a map with Magenta's triggers is refused, and is built by saving it in the editor.
Running a plugin over a map#
scmjs map run gives a plugin the editor's plugin API with a map open and
no editor around it. The plugin can be a folder with a plugin.json, the manifest itself,
a single script file, github:owner/repo or a URL: whatever Plugins ▸ Add takes, and a
file on your disk too. A script is a file that exports activate(api) as a plugin's entry
does, so a few lines that read or change a map need no plugin around them:
// starts.mjs
export default (api) => {
const starts = api.document.scenario().units.filter((u) => u.unitId === 214);
api.ui.status(`${api.document.info().name}: ${starts.length} start locations`);
};
scmjs map run starts.mjs "(4)Crossing.scx"
scmjs map run ../plugin-repair maps/ridge.scx --command repair --dry-run
What the plugin would show is printed as it happens: its status line, its toasts, and the
warnings and errors in its log. Dialogs and panels register and show nothing, and a
request for a file gets none. --command <id> runs one of the commands the plugin
registered, with --arg values as its arguments; --json includes what the command
returned.
The map is written back only when the plugin changed it (--out writes it elsewhere, even
unchanged), and the last line says what changed, in the terms of
map diff. --dry-run prints that comparison in full and writes
nothing. Saving is the command's job: api.document.save() has no file picker to open
here. A plugin written in TypeScript needs TypeScript installed, as map build does; a
built plugin or a JavaScript script needs nothing more.
This is the editor's own plugin host, so a plugin's logic runs as it does in the editor. You can try a plugin from a terminal, or run it over many maps from a shell loop, before anyone installs it.