This is a conversion of the Crossfire GTK-v2 client to TypeScript and Svelte.
Vite is used for the building everything.
Almost everything was done by GitHub Copilot with Claude Opus or Sonnet, and me just testing the different versions.
There is a test server at crossfire.diegeekdie.com. It is just for testing and can go down at any time.
Append a ?server= query parameter to the page URL to pre-fill (and
automatically initiate) the connection to a particular server, e.g.:
https://example.com/?server=wss://crossfire.example.com/ws
When this parameter is present the server-address input field is hidden and the client connects automatically, bypassing the manual "Enter" step.
The client supports both the old login method of logging in to
the character and the latest where new characters are created in
the client.
Add a query parameter ?loginmethod=0 to use the old login method.
The new one is the default.
When recording is enabled (?record), downloaded logs are in the compact format:
<timestamp>\t<TX|RX>\t<byte_length>\t<base64_payload>
<timestamp>\t<MARK>\t<json_marker_text>
Use this converter to transform them into:
timestamp TX/RX/MARK text
where text is C-string escaped (\n for newline, \xXX for non-printables).
npm run recording:convert -- input.log output.txtIf output.txt is omitted, converted output is written to stdout.
There is also a separate replay page at:
/replay.html
It accepts both the compact downloaded ws-recording logs and the converted text
format from npm run recording:convert.
The replay page can export state snapshot files in the same format as used by the mapdata replay tests.
More marks can be added by editing the logfile.
Run an end-to-end replay with:
npm run replay:playwright -- --log tests/replay-mapdata/logs/scorn.converted.log --mark-regex ".*"This command starts:
- a local Vite dev server
- a replay WebSocket server that replays the logfile to the client
- a Playwright Chromium browser connected to that replay server
Behavior:
- For each
TXline in the logfile, replay waits for the client to send a matching command (or timeout). - Matching is command-only, except for
ncomwhere the embedded command text is also matched (ncom.north,ncom.apply, etc.). - The next replayed
RX comcpacket is sequence-patched to the sequence sent by the client for that matchedncom. - Every
MARKline whose label matches--mark-regexsaves a screenshot inscreenshots/replay-playwright/.
Use --instructions to apply Playwright actions synchronized to TX lines:
npm run replay:playwright -- \
--log tests/replay-mapdata/logs/scorn.converted.log \
--instructions scripts/replay-playwright.instructions.example.json \
--mark-regex ".*"Instruction format (JSON):
- top-level
txarray - each entry has:
match: regex string tested against replay command key- normal commands:
accountlogin,accountplay ncomcommands:ncom.<embedded command>, e.g.ncom.north
- normal commands:
actions: list of actions (clickRole,clickText,fillRole,clickSelector,fillSelector,press,keyDown,keyUp,waitForTimeout,screenshot)- Action string values support regex captures from
matchvia$1,$2, etc.
- Action string values support regex captures from
Built-in actions already exist for accountlogin (click Log In) and
accountplay (click matching character button), and can be extended with
instruction rules.
Run the mapdata replay tests with:
npm run test:mapdataTest cases are listed in:
tests/replay-mapdata/tests.json
Each case has:
name: test namelog: replay log file path (in compact or converted format)mark: MARK label to replay the log up tostate: path to expected mapdata state file
Only cells listed in the state file are compared.
Web pages can't use raw TCP sockets so WebSockets has to be used.
The public crossfire clients don't support WebSockets, so a WebSocket proxy has to be used when connecting to them. There is one included in the repo (under scripts/) that also handles crossfire's protocol's length header.
The crossfire-server fork has a branch with built in support for WebSockets. It is not thoroughly reviewed yet, but it works well.
The client doesn't contact a metaserver as the normal servers don't use support WebSockets anyway.
A web page can't override all the browser's built in hot keys, so ALT is used instead of CTRL for running. That way it hopefully leads to fewer conflicts with the browser.
There is a "Keyboard" menu for handling key bindings.
Key bindings are stored locally in the browser. If playing from different computers/browsers, the bindings will have to be redone.
Bindings can be stored for all characters or the current logged in one.
Press Tab to enter UI navigation mode. In that mode the visible UI can be
navigated with mode-specific keyboard or gamepad controls, Escape leaves the
mode. Return selects items/shows menus in the UI navigation mode.
The local commands ui_nav is used to enter the mode. ui_nav --stay
keeps the mode active after running a UI command; by default it exits
automatically after a UI command is sent.
Left clicking on items activates them.
Right clicking on items and skills brings up a menu.
Left clicking on a spell selects it.
Right clicking on skills brings up a menu for use/ready of it.
The client supports music and sfx, they are downloaded as needed from the server. Both can be muted.
It works somewhat like the DockWindow client, the map is integrated into the unseen parts of the main map. Walls are drawn as thin lines.
There is simple gamepad support built in. Currently it only has default bindings for my XBox One controller.
When UI navigation mode is active, the controller switches to UI navigation bindings instead of the normal gameplay bindings. By default the left stick or D-pad moves between UI elements, A activates the current element, X opens the current element's menu, and B exits UI navigation mode.
It is possible to configure the client for more controllers, but it takes some time. It is probably easier to change it in the code instead.
PRs for more controllers are welcome.
