Modding: Difference between revisions
Created page with "= Wall Street Raider - Modding Guide = This document describes '''everything you can mod today''' in Wall Street Raider, and exactly how to do it. It covers the practical, supported surfaces: the Electron frontend (code, styling, assets, help, translations, hints), the plain-text company-name database, the REST + WebSocket API, and the '''CustomData API''' - the one supported path for writing your own persistent data into a save file. It does '''not''' cover modifying..." |
No edit summary |
||
| Line 1: | Line 1: | ||
= Wall Street Raider - Modding Guide = | = Wall Street Raider - Modding Guide = | ||
A reference for everything you can mod in WSR with nothing but a text editor. The Electron frontend has '''no build step''' and the install ships with <code>asar: false</code>, so every file under <code>resources/app/</code> is a normal editable file. '''Edit, restart, see your change.''' | |||
What you '''can't''' mod without recompiling: the PowerBASIC engine (<code>wsr.exe</code>), the C++ bridge (<code>ui.dll</code>), and the binary <code>.DAT</code>/<code>.PRM</code> files. | |||
{{Note|Back up before you edit. Steam's "verify integrity of game files" reverts changes - that's also your safety net.}} | |||
== TL;DR - what's moddable == | |||
< | {| class="wikitable" | ||
! I want to… !! Do this | |||
|- | |||
</ | | Re-theme the game || Edit <code>css/variables.css</code>, then <code>css/theme.css</code> / <code>css/components/</code> | ||
|- | |||
| Change UI text / layout / behavior || Edit the relevant file in <code>js/components/</code> | |||
|- | |||
| Add a new panel or feature || New file in <code>js/components/</code>, import it from a parent or <code>app.js</code> | |||
|- | |||
| Replace a background video or image || Drop a same-named file into <code>assets/</code> | |||
|- | |||
| Rewrite the manual || Edit <code>assets/help/wsrbook.htm</code> | |||
|- | |||
| Add a language || New <code>js/locale/<code>.js</code> + register in <code>localeManager.js</code> | |||
|- | |||
| Change / add contextual hints || Edit files in <code>js/data/hints/</code> | |||
|- | |||
| Rename companies / tickers / nations || Edit <code>CORPNAME.DAT</code> (plain text). Effective on '''next New Game'''. | |||
|- | |||
| Drive the game from JS || Call an <code>api.js</code> function ([[#Calling the API from a frontend mod|§api.js]]) | |||
|- | |||
| Drive the game from a third-party app || Hit the REST API directly ([[#REST API reference|§REST API]]) | |||
|- | |||
| Read game state || <code>useGameStore(s => s.gameState.<field>)</code> or <code>GET /gamestate</code> ([[#gameState reference|§gameState]]) | |||
|- | |||
| Persist your mod's data in saves || <code>setCustomData({ myKey: … })</code> ([[#CustomData - your persistent storage|§CustomData]]) | |||
|- | |||
| Change a core simulation rule || '''Not moddable.''' Requires engine source + recompile. | |||
|} | |||
== | == Architecture in one breath == | ||
<pre> | <pre> | ||
PowerBASIC engine → C++ bridge DLL (REST + WebSocket) → Electron UI | |||
wsr.exe ui.dll everything you see | |||
</pre> | </pre> | ||
At launch, <code>ui.dll</code> picks ephemeral REST + WS ports and writes them to <code>%LOCALAPPDATA%\Wall Street Raider\runtime.json</code>: | |||
<pre> | <pre> | ||
{ "pid": 12345, "rest_port": 54321, "ws_port": 54322 } | |||
</pre> | </pre> | ||
<code> | Electron reads that file and hands the ports to the renderer; <code>js/api.js</code> builds <code>http://127.0.0.1:<rest></code> and <code>ws://127.0.0.1:<ws></code>. WS pushes a full snapshot on connect, then JSON-patch diffs; if WS drops, the UI polls <code>GET /gamestate</code>. | ||
'''Third-party apps:''' read <code>runtime.json</code> the same way. Ports change every launch. | |||
== | == File layout == | ||
In source (<code>electron/</code>) and in an installed copy (<code><install>/resources/app/</code>) the layout is identical - the install is a verbatim copy of the source tree (see <code>extraFiles</code> in <code>electron/package.json</code>): | |||
<pre> | |||
resources/app/ | |||
├── index.html # entry point | |||
├── main.js # Electron main process | |||
├── js/ # ← all frontend code (editable) | |||
│ ├── app.js # Preact root, WS wiring, hotkeys | |||
│ ├── api.js # REST/WS client, gameStore, ID constants, endpoint wrappers | |||
│ ├── components/ # 100+ Preact panels/modals/tables/tabs | |||
│ ├── hooks/ # useCookie, useInterval, usePanelSelection, … | |||
│ ├── services/ # hintMatcher.js, … | |||
│ ├── utils/ # misc | |||
│ ├── icons.js # inline SVG | |||
│ ├── debug-log.js # frontend logging | |||
│ ├── lib/ # vendored: preact, zustand, tailwind, fast-json-patch | |||
│ ├── locale/ # ← translations | |||
│ └── data/hints/ # ← contextual hints | |||
├── css/ # ← all styling (editable) | |||
├── assets/ # ← images / videos / icons / help (editable) | |||
├── CORPNAME.DAT # ← plain-text company database (editable) | |||
├── wsr.exe # engine (binary) | |||
├── ui.dll # bridge (binary) | |||
└── *.DAT / *.PRM # other game data (binary/encoded - leave alone) | |||
</pre> | |||
== Frontend code (<code>js/</code>) == | |||
Components use '''Preact + htm''' (JSX-like template literals, no compile step) and '''Zustand''' for state. | Components use '''Preact + htm''' (JSX-like template literals, no compile step) and '''Zustand''' for state. Minimal example: | ||
<pre> | <pre> | ||
| Line 87: | Line 93: | ||
export function MyPanel() { | export function MyPanel() { | ||
const cash = useGameStore(s = | const cash = useGameStore(s => s.gameState.cash); | ||
return html`<div class="p-4">Cash: ${cash}</div>`; | return html`<div class="p-4">Cash: ${cash}</div>`; | ||
} | } | ||
</pre> | </pre> | ||
You can | You can: | ||
* | * Edit any existing component (layout, text, colors, behavior). | ||
* Add | * Add new components and import them into <code>app.js</code> or any parent. | ||
* Add new hotkeys ( | * Add new hotkeys (grep <code>hotkey</code>; handling lives in <code>app.js</code> + <code>HotkeyButtonBar.js</code>). | ||
* Add new | * Add new buttons that call any API endpoint ([[#Calling the API from a frontend mod|§api.js]]). | ||
* Add new | * Add new derived displays from <code>gameState</code> ([[#gameState reference|§gameState]]). | ||
== Styling (<code>css/</code>) == | |||
<pre> | <pre> | ||
css/ | css/ | ||
├── style.css | ├── style.css # the file index.html links - imports the rest | ||
├── base.css | ├── base.css # resets / base element styles | ||
├── variables.css | ├── variables.css # ← CSS custom properties (theme palette lives here) | ||
├── theme.css | ├── theme.css # theme layer | ||
├── layout.css | ├── layout.css # app layout | ||
├── chart-styles.js | ├── chart-styles.js # chart styling injected as JS | ||
├── components/ | ├── components/ # buttons, dropdown, forms, modals, panels, tables, tabs | ||
├── features/ | ├── features/ # assets, calculator, help, market, menu, notes, ticker, tutorial | ||
└── pages/ | └── pages/ # game, main-menu, quotes | ||
</pre> | </pre> | ||
Tailwind | Tailwind utilities are also available via <code>js/lib/tailwind.module.js</code>, so most components use Tailwind inline + the modular CSS for anything custom. | ||
'''To re-theme | '''To re-theme:''' start with <code>variables.css</code> - override the custom properties and the whole app follows. | ||
== Assets (<code>assets/</code>) == | |||
{| class="wikitable" | {| class="wikitable" | ||
! | ! What !! Files | ||
|- | |- | ||
| Background videos || | | Background videos || the many <code>*.mp4</code> (Wall Street footage, industry b-roll, …) | ||
|- | |- | ||
| Event images || <code>blackswan.png</code>, <code>ponzi.jpg</code>, <code>soupline.jpg</code>, <code>zimbabwe.jpg</code>, <code>helicopter.jpg</code>, <code>yacht.mp4</code> | | Event images || <code>blackswan.png</code>, <code>ponzi.jpg</code>, <code>soupline.jpg</code>, <code>zimbabwe.jpg</code>, <code>helicopter.jpg</code>, <code>yacht.mp4</code> | ||
| Line 142: | Line 142: | ||
|} | |} | ||
Replace any of these with a same-named file of the same type | Replace any of these with a same-named file of the same type. '''Keep filenames identical''' - components reference them by name. | ||
<code> | == Help content (<code>assets/help/</code>) == | ||
<code>wsrbook.htm</code> is the in-game manual - one ~1 MB plain HTML file. Edit it directly. The <code>*.jpg</code> files in the same folder are diagrams it embeds. <code>electron/UI_HELP_CATALOG.txt</code> is a developer catalog of help sections. | |||
== Translations (<code>js/locale/</code>) == | |||
Pure data - '''no engine changes needed to add a language.''' | |||
* <code>LANGUAGE_OPTIONS</code> maps | How it works (<code>localeManager.js</code>): | ||
* Each <code>dictionary</code> is a flat | * <code>LANGUAGE_OPTIONS</code> maps locale code → <code>{ name, dictionary, warning? }</code>. | ||
* | * Each <code>dictionary</code> is a flat <code>{ "English source": "translation", … }</code>. | ||
* Existing | * The <code>translator</code> does runtime lookup/replacement on UI strings. | ||
* Existing locales: <code>zh-CN.js</code>, <code>es-419.js</code>, <code>ja-JP.js</code>, <code>pt-BR.js</code>, <code>ru-RU.js</code>. | |||
To add | To add a language: | ||
<pre> | <pre> | ||
// js/locale/fr-FR.js | |||
export const frFR = { | export const frFR = { | ||
"New Game": "Nouvelle partie", | "New Game": "Nouvelle partie", | ||
| Line 171: | Line 167: | ||
// … one entry per source string you want translated | // … one entry per source string you want translated | ||
}; | }; | ||
// js/locale/localeManager.js | |||
import { frFR } from './fr-FR.js'; | import { frFR } from './fr-FR.js'; | ||
// … | // … | ||
'fr-FR': { name: 'Français', dictionary: frFR }, | 'fr-FR': { name: 'Français', dictionary: frFR }, | ||
</pre> | </pre> | ||
The chosen locale persists in <code>CFIG.WSR</code>, surfaces as <code>gameState.locale</code>, and switches via <code>POST /set_locale</code>. | |||
The <code>*-backup-*</code> files | {{Note|Missing strings fall through to English, so a partial translation is fine. The <code>*-backup-*</code> files are timestamped editing backups (git-ignored) - ignore them.}} | ||
== | == Contextual hints (<code>js/data/hints/</code>) == | ||
Hint files are grouped by view and aggregated by <code>index.js</code>: | |||
<pre> | <pre> | ||
hints/ | |||
├── index.js | ├── index.js # aggregates all sets | ||
├── global.js | ├── global.js # fallback hints | ||
├── company.js | ├── company.js # while viewing a company | ||
├── industry.js | ├── industry.js | ||
├── market.js | ├── market.js | ||
| Line 203: | Line 195: | ||
</pre> | </pre> | ||
Each | Each entry: | ||
<pre> | <pre> | ||
| Line 214: | Line 206: | ||
</pre> | </pre> | ||
<code>js/services/hintMatcher.js</code> picks the most specific match for the current screen. Rewrite text, add new hints, retarget <code>match</code> conditions - no engine changes. | |||
== Company names (<code>CORPNAME.DAT</code>) == | |||
Plain pipe-delimited text: | |||
<pre> | <pre> | ||
| Line 226: | Line 218: | ||
</pre> | </pre> | ||
Columns: <code>company ID | nation code | ticker | Columns: <code>company ID | nation code | ticker | name</code>. Read by the engine's <code>GetCorpNames</code> at '''new-game''' time, so edits take effect on the '''next''' new game (not on already-saved games). Rename companies, change tickers, change a company's home nation. | ||
'''Keep the format exact:''' 4-digit zero-padded ID, 2-digit nation code, fixed-width symbol field, and the surrounding <code>|</code> + spacing. <code>CORPNAME.ORI</code> is the pristine original - keep as your backup. | |||
== Other game data (don't bother) == | |||
{| class="wikitable" | {| class="wikitable" | ||
! File(s) !! Content !! Why limited | ! File(s) !! Content !! Why limited | ||
|- | |- | ||
| <code>QUOTEWSR.DAT</code> || | | <code>QUOTEWSR.DAT</code> || quote of the day || encoded; <code>quotehs.prm</code> is a near-plaintext source the engine doesn't read | ||
|- | |- | ||
| <code>MSCNEWS.DAT</code> || | | <code>MSCNEWS.DAT</code> || news / scenario headlines || encoded; same story with <code>MSCNEWS.PRM</code> | ||
|- | |- | ||
| <code>scenwin1-4.prm</code> || | | <code>scenwin1-4.prm</code> || scenario window text || engine-internal format | ||
|- | |- | ||
| <code>GAME01 | | <code>GAME01-50.DAT</code> || save slots / scenario data || proprietary binary | ||
|- | |- | ||
| <code>WSR101.DAT</code>, <code>REGINFO.DAT</code>, | | <code>WSR101.DAT</code>, <code>REGINFO.DAT</code>, … || engine data || proprietary binary | ||
|} | |} | ||
Treat | Treat as read-only. <code>CORPNAME.DAT</code> is the one truly editable game-data file. | ||
== | == Calling the API from a frontend mod == | ||
<code>js/api.js</code> is the '''wrapper layer''' for the REST API: each endpoint has a named async function with semantic parameters, plus the live <code>gameStore</code>, plus exported '''ID constants''' (entity IDs, industry indices, special asset IDs, <code>UI_*</code> report IDs). '''Frontend mods should call <code>api.js</code> functions, not raw HTTP.''' | |||
<pre> | <pre> | ||
import * as api from '../api.js'; | |||
await api.buyStock(companyId); // /buy_stock | |||
await api.setDividend(actingAsId); // /set_dividend | |||
await api.merger(targetId, actingAsId); // /merger | |||
await api.cheatAddCash(); // /cheat_add_cash | |||
const state = await api.getGameState(); // GET /gamestate | |||
</pre> | </pre> | ||
For endpoints that don't have a named wrapper yet, four request shapes are also exported: | |||
{| class="wikitable" | |||
! Helper !! Body shape !! Use | |||
|- | |||
| <code>postNoArg(path)</code> || <code>{}</code> || actions with no parameter | |||
|- | |||
| <code>postIdArg(path, id)</code> || <code>{ id }</code> || actions on a specific entity / slot | |||
|- | |||
| <code>postIdArgWithActingAs(path, id, actingAsId)</code> || <code>{ id, intParam2 }</code> || most corporate / trade actions | |||
|- | |||
| <code>postOptionsTradeWithActingAs(path, id, actingAsId, underlyingId)</code> || <code>{ id, intParam2, underlyingId }</code> || options trades | |||
|- | |||
| <code>postStringArg(path, str)</code> || <code>{ str }</code> || actions taking a string (e.g. save-as filename) | |||
|- | |||
| <code>getJSON(path)</code> || - || GET endpoints | |||
|} | |||
For wire-level details (the underlying routes, the JSON body the bridge actually receives, what each generic field means per endpoint), see [[#REST API reference|§REST API reference]]. | |||
== REST API reference == | |||
For third-party apps and direct integrations. Frontend mods should prefer <code>api.js</code> ([[#Calling the API from a frontend mod|§api.js]]). | |||
=== Conventions === | |||
* '''Base URL:''' <code>http://127.0.0.1:<rest_port></code>. Read <code>rest_port</code> from <code>%LOCALAPPDATA%\Wall Street Raider\runtime.json</code>; ports change every launch. | |||
* '''Content-Type:''' <code>application/json</code> on all POSTs. | |||
* '''Generic body schema:''' the bridge accepts a single envelope <code>{ id?, intParam2?, underlyingId?, str?, answer?, value?, filename? }</code>. Each endpoint uses 0-3 of these fields with '''endpoint-specific meanings''' - the per-endpoint tables below spell out which fields are read and what they mean in context. | |||
* '''Omitted fields default to 0 / empty.''' Most action endpoints can be called as <code>{}</code> if they don't strictly need params (the engine often falls back to "use the currently active entity" or "show the picker modal"). | |||
* '''Most actions open a modal''' rather than executing immediately. After POSTing, the engine pushes a modal state via WS / <code>gameState.modalType</code>; respond with <code>POST /modal_result</code>. | |||
==== The <code>actingAsId</code> pattern ==== | |||
Most corporate / trade endpoints accept an '''<code>actingAsId</code>''' (sent as the JSON field <code>intParam2</code>): the entity that '''executes''' the action. | |||
* <code>0</code> (or omitted) = act as the player. | |||
* Any other ID = act as that controlled company. The engine momentarily switches context, runs the action ''as that company'', then restores. | |||
When the per-endpoint tables below show <code>intParam2</code>, it means actingAsId unless noted otherwise. | |||
==== Response shape ==== | |||
GET endpoints return endpoint-specific JSON (documented in the GET table). POST endpoints return <code>{ status: "ok" }</code> on success and a <code>{ error: "…" }</code> with HTTP 4xx/5xx on failure. Side effects show up via the next WS broadcast. | |||
=== GET endpoints === | |||
{| class="wikitable" | |||
! Method !! Endpoint !! Returns | |||
|- | |||
| GET || <code>/status</code> || health check | |||
|- | |||
| GET || <code>/gamestate</code> || full game-state JSON ([[#gameState reference|§gameState]]) | |||
|- | |||
| GET || <code>/quote</code> || <code>{ quote: string }</code> - quote of the day | |||
|- | |||
| POST || <code>/asset_chart</code> || body <code>{ id }</code>; returns 60 months of price history for an asset (<code>{ prices, highs, lows, xAxisTitle, yAxisTitle, baseMonth, baseYear }</code>) | |||
|- | |||
| GET || <code>/database_data</code> || searchable database of all companies (industry, financials, ratings, …) | |||
|- | |||
| GET || <code>/ownership_tree</code> || ownership hierarchy of the active entity (who owns it, at what %) | |||
|- | |||
| GET || <code>/subsidiaries_tree</code> || what the active entity owns (its subsidiaries, at what %) | |||
|} | |||
=== Session / clock === | |||
{| class="wikitable" | |||
! Endpoint !! Body !! Description | |||
|- | |||
| POST <code>/newgame</code> || <code>{}</code> || Start a new game. | |||
|- | |||
| POST <code>/loadgame</code> || <code>{}</code> || Open the load-game flow. | |||
|- | |||
| POST <code>/load_specific_save</code> || <code>{ filename }</code> || Load a specific save. <code>filename</code> = save basename; must not contain <code>..</code>, <code>/</code>, <code>\</code>, <code>:</code>. | |||
|- | |||
| POST <code>/savegame</code> || <code>{}</code> || Save to the default slot. | |||
|- | |||
| POST <code>/savegameas</code> || <code>{ filename }</code> || Save under a name. Empty <code>filename</code> shows the Save-As dialog. | |||
|- | |||
| POST <code>/exit_game</code> || <code>{}</code> || Exit to main menu. | |||
|- | |||
| POST <code>/check_scoreboard</code> || <code>{}</code> || Open the scoreboard view. | |||
|- | |||
| POST <code>/start_ticker</code> || <code>{}</code> || Start the market ticker animations. | |||
|- | |||
| POST <code>/run_ticker</code> || <code>{}</code> || Advance the ticker by one queue item. | |||
|- | |||
| POST <code>/stop_ticker</code> || <code>{}</code> || Stop the ticker. | |||
|- | |||
| POST <code>/set_ticker_speed</code> || <code>{ id }</code> || Set ticker speed. <code>id</code> = speed enum. | |||
|- | |||
| POST <code>/ticker_advance</code> || <code>{}</code> || Pop the front of the ticker queue, append a new item. | |||
|- | |||
| POST <code>/clear_event_string</code> || <code>{}</code> || Clear the event message log. | |||
|- | |||
| POST <code>/splash_screen_played</code> || <code>{}</code> || Mark splash as shown so it's skipped next time. | |||
|} | |||
=== Trading - stocks & bonds === | |||
== | {| class="wikitable" | ||
! Endpoint !! Body !! Description | |||
|- | |||
| POST <code>/buy_stock</code> || <code>{ id, intParam2? }</code> || Buy stock. <code>id</code> = company to buy. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/sell_stock</code> || <code>{ id, intParam2? }</code> || Sell stock. <code>id</code> = company to sell. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/short_stock</code> || <code>{ id, intParam2? }</code> || Open short. <code>id</code> = company to short. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/cover_short_stock</code> || <code>{ id, intParam2? }</code> || Cover an existing short. <code>id</code> = company. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/buy_corporate_bond</code> || <code>{ id, intParam2? }</code> || Buy a corporate bond. <code>id</code> = issuing company. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/sell_corporate_bond</code> || <code>{ id, intParam2? }</code> || Sell a corporate bond. <code>id</code> = issuing company. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/buy_long_govt_bonds</code> || <code>{ intParam2 }</code> || Buy long-term govt bonds. <code>intParam2</code> = actingAsId. <code>id</code> ignored. | |||
|- | |||
| POST <code>/sell_long_govt_bonds</code> || <code>{ intParam2 }</code> || Sell long-term govt bonds. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/buy_short_govt_bonds</code> || <code>{ intParam2 }</code> || Buy short-term govt bonds. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/sell_short_govt_bonds</code> || <code>{ intParam2 }</code> || Sell short-term govt bonds. <code>intParam2</code> = actingAsId. | |||
|} | |||
=== Trading - commodities & crypto === | |||
=== | {| class="wikitable" | ||
! Endpoint !! Body !! Description | |||
|- | |||
| POST <code>/buy_commodity_futures</code> || <code>{ id, intParam2? }</code> || Buy commodity futures. <code>id</code> = commodity ID (e.g. <code>OIL_ID</code>, <code>GOLD_ID</code>). <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/sell_commodity_futures</code> || <code>{ id, intParam2? }</code> || Sell commodity futures. <code>id</code> = commodity ID. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/close_long_commodity_futures_by_slot</code> || <code>{ id, intParam2? }</code> || Close a specific long position. <code>id</code> = '''portfolio slot index''' (not a commodity ID). <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/short_commodity_futures</code> || <code>{ id, intParam2? }</code> || Short commodity futures. <code>id</code> = commodity ID. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/cover_short_commodity_futures</code> || <code>{ id, intParam2? }</code> || Cover commodity short. <code>id</code> = commodity ID. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/cover_short_commodity_futures_by_slot</code> || <code>{ id, intParam2? }</code> || Cover a specific short position. <code>id</code> = '''portfolio slot index'''. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/buy_physical_commodity</code> || <code>{ id, intParam2? }</code> || Buy physical commodity. <code>id</code> = commodity ID. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/sell_physical_commodity</code> || <code>{ id, intParam2? }</code> || Sell physical commodity. <code>id</code> = commodity ID. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/buy_physical_crypto</code> || <code>{ id, intParam2? }</code> || Buy physical crypto. <code>id</code> = crypto ID (e.g. <code>BITCOIN_ID</code>). <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/sell_physical_crypto</code> || <code>{ id }</code> || Sell physical crypto. <code>id</code> = crypto ID. (No actingAs param on this one.) | |||
|- | |||
| POST <code>/buy_crypto_futures</code> || <code>{ id, intParam2? }</code> || Buy crypto futures. <code>id</code> = crypto ID. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/sell_crypto_futures</code> || <code>{ id, intParam2? }</code> || Sell crypto futures. <code>id</code> = crypto ID. <code>intParam2</code> = actingAsId. | |||
|} | |||
=== Trading - options === | |||
< | {| class="wikitable" | ||
! Endpoint !! Body !! Description | |||
|- | |||
| POST <code>/buy_calls</code> || <code>{ id, intParam2?, underlyingId? }</code> || Buy calls. <code>id</code> = strike-price ID (or 0 to show the strike picker). <code>intParam2</code> = actingAsId. <code>underlyingId</code> = company being optioned (when <code>id>0</code> and <code>underlyingId>0</code>, modal is skipped — used by CLI flows like "CALL ABC"). | |||
|- | |||
| POST <code>/sell_calls</code> || <code>{ id, intParam2?, underlyingId? }</code> || Sell calls. Same param semantics as <code>/buy_calls</code>. | |||
|- | |||
| POST <code>/buy_puts</code> || <code>{ id, intParam2?, underlyingId? }</code> || Buy puts. Same param semantics as <code>/buy_calls</code>. | |||
|- | |||
| POST <code>/sell_puts</code> || <code>{ id, intParam2?, underlyingId? }</code> || Sell puts. Same param semantics as <code>/buy_calls</code>. | |||
|- | |||
| POST <code>/advanced_options_trading</code> || <code>{ intParam2 }</code> || Open the advanced options panel (spreads, collars). <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/exercise_call_options_early</code> || <code>{ id, intParam2? }</code> || Exercise a call early. <code>id</code> = call contract ID. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/exercise_put_options_early</code> || <code>{ id, intParam2? }</code> || Exercise a put early. <code>id</code> = put contract ID. <code>intParam2</code> = actingAsId. | |||
|} | |||
=== Corporate management === | |||
All of these operate on the acting-as entity. <code>id</code> is unused unless noted; pass <code>0</code> or omit. | |||
{| class="wikitable" | {| class="wikitable" | ||
! | ! Endpoint !! Body !! Description | ||
|- | |- | ||
| <code> | | POST <code>/prepay_taxes</code> || <code>{ intParam2 }</code> || Prepay taxes. <code>intParam2</code> = actingAsId. | ||
|- | |||
| POST <code>/elect_ceo</code> || <code>{ intParam2 }</code> || Open CEO-election picker for the entity. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/resign_as_ceo</code> || <code>{ intParam2 }</code> || Acting-as entity resigns as CEO. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/change_managers</code> || <code>{ intParam2 }</code> || Hire / fire managers. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/set_dividend</code> || <code>{ intParam2 }</code> || Set the company's dividend. <code>intParam2</code> = actingAsId (dividend issuer). | |||
|- | |||
| POST <code>/set_productivity</code> || <code>{ intParam2 }</code> || Set productivity. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/set_growth_rate</code> || <code>{ intParam2 }</code> || Set growth rate. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/restructure</code> || <code>{ intParam2 }</code> || Open restructuring modal. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/buy_corporate_assets</code> || <code>{ intParam2 }</code> || Buy plant / equipment. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/sell_corporate_assets</code> || <code>{ intParam2 }</code> || Sell plant / equipment. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/offer_corporate_assets_for_sale</code> || <code>{ intParam2 }</code> || List assets for other entities to buy. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/view_for_sale_items</code> || <code>{ intParam2 }</code> || Browse assets currently for sale. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/sell_subsidiary_stock</code> || <code>{ id, intParam2? }</code> || Sell stock the entity holds in a subsidiary. <code>id</code> = subsidiary company ID. <code>intParam2</code> = actingAsId (seller). | |||
|- | |||
| POST <code>/rebrand</code> || <code>{ intParam2 }</code> || Rename the company. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/toggle_company_autopilot</code> || <code>{ id }</code> || Toggle one company's autopilot. <code>id</code> = company. | |||
|- | |- | ||
| <code> | | POST <code>/toggle_global_autopilot</code> || <code>{ intParam2 }</code> || Toggle autopilot across all of the entity's holdings. <code>intParam2</code> = actingAsId. | ||
|- | |- | ||
| <code> | | POST <code>/become_etf_advisor</code> || <code>{ intParam2 }</code> || Become ETF advisor. <code>intParam2</code> = actingAsId. | ||
|- | |- | ||
| <code> | | POST <code>/set_advisory_fee</code> || <code>{ intParam2 }</code> || Set the ETF advisory fee. <code>intParam2</code> = actingAsId. | ||
|- | |- | ||
| <code> | | POST <code>/decrease_earnings</code> || <code>{ intParam2 }</code> || Accounting adjustment to lower reported earnings. <code>intParam2</code> = actingAsId. | ||
|- | |- | ||
| <code> | | POST <code>/increase_earnings</code> || <code>{ intParam2 }</code> || Accounting adjustment to raise reported earnings. <code>intParam2</code> = actingAsId. | ||
|} | |} | ||
=== M&A / corporate finance === | |||
=== | {| class="wikitable" | ||
! Endpoint !! Body !! Description | |||
|- | |||
| POST <code>/merger</code> || <code>{ id, intParam2? }</code> || Start a merger. <code>id</code> = target company. <code>intParam2</code> = actingAsId (acquirer). | |||
|- | |||
| POST <code>/greenmail</code> || <code>{ id, intParam2? }</code> || Greenmail (coercive buyback). <code>id</code> = target. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/lbo</code> || <code>{ id, intParam2? }</code> || Leveraged buyout. <code>id</code> = target. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/startup</code> || <code>{ intParam2 }</code> || Found a new startup. <code>intParam2</code> = actingAsId (founder). | |||
|- | |||
| POST <code>/capital_contribution</code> || <code>{ intParam2 }</code> || Inject cash into a subsidiary. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/public_stock_offering</code> || <code>{ intParam2 }</code> || IPO. <code>intParam2</code> = actingAsId (company going public). | |||
|- | |||
| POST <code>/private_stock_offering</code> || <code>{ intParam2 }</code> || Private placement. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/issue_new_corp_bonds</code> || <code>{ intParam2 }</code> || Issue new corporate bonds. <code>intParam2</code> = actingAsId (issuer). | |||
|- | |||
| POST <code>/redeem_corp_bonds</code> || <code>{ intParam2 }</code> || Retire outstanding bonds. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/extraordinary_dividend</code> || <code>{ intParam2 }</code> || One-time special dividend. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/tax_free_liquidation</code> || <code>{ intParam2 }</code> || Liquidate with tax deferral. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/taxable_liquidation</code> || <code>{ intParam2 }</code> || Liquidate; pay tax on gains. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/spin_off</code> || <code>{ id, intParam2? }</code> || Spin off a subsidiary. <code>id</code> = subsidiary to spin off. <code>intParam2</code> = actingAsId (parent). | |||
|- | |||
| POST <code>/split_stock</code> || <code>{ intParam2 }</code> || Forward stock split. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/reverse_split_stock</code> || <code>{ intParam2 }</code> || Reverse stock split. <code>intParam2</code> = actingAsId. | |||
|} | |||
=== Banking / loans / swaps === | |||
{| class="wikitable" | {| class="wikitable" | ||
! Endpoint !! | ! Endpoint !! Body !! Description | ||
|- | |||
| POST <code>/borrow_money</code> || <code>{ intParam2 }</code> || Take out a loan. <code>intParam2</code> = actingAsId (borrower). | |||
|- | |||
| POST <code>/repay_loan</code> || <code>{ intParam2 }</code> || Pay down / repay. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/advance_funds</code> || <code>{ intParam2 }</code> || Lender advances cash against securities. <code>intParam2</code> = actingAsId (lender). | |||
|- | |||
| POST <code>/call_in_advance</code> || <code>{ id }</code> || Terminate an advance. <code>id</code> = advance ID. | |||
|- | |||
| POST <code>/interest_rate_swaps</code> || <code>{ id, intParam2? }</code> || Open the swaps modal. <code>id</code> = asset ID. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/view_swap_details</code> || <code>{ id, intParam2? }</code> || View one swap's details. <code>id</code> = swap ID. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/terminate_swap</code> || <code>{ id, intParam2? }</code> || Terminate a swap early. <code>id</code> = swap ID. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/set_bank_allocation</code> || <code>{ intParam2 }</code> || Distribute cash across banks. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/trade_tbills</code> || <code>{ intParam2 }</code> || Trade T-bills. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/list_bank_loans</code> || <code>{ intParam2 }</code> || List the entity's outstanding bank loans (when acting as a bank). <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/change_bank</code> || <code>{ intParam2 }</code> || Change primary bank. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/call_in_loan</code> || <code>{ id }</code> || Demand immediate repayment. <code>id</code> = loan ID. | |||
|- | |||
| POST <code>/buy_bank_loans</code> || <code>{}</code> || Open the bank-loans investment picker. | |||
|- | |||
| POST <code>/buy_business_loans</code> || <code>{ intParam2 }</code> || Buy business-loan portfolio. <code>intParam2</code> = actingAsId (investor). | |||
|- | |||
| POST <code>/sell_business_loan</code> || <code>{ id }</code> || Sell one business-loan holding. <code>id</code> = loan ID. | |||
|- | |||
| POST <code>/buy_consumer_loans</code> || <code>{ intParam2 }</code> || Buy consumer-loan portfolio. <code>intParam2</code> = actingAsId. | |||
|- | |- | ||
| <code>/ | | POST <code>/sell_consumer_loans</code> || <code>{ intParam2 }</code> || Sell consumer-loan holdings. <code>intParam2</code> = actingAsId. | ||
|- | |- | ||
| <code>/ | | POST <code>/buy_prime_mortgages</code> || <code>{ intParam2 }</code> || Buy prime mortgages. <code>intParam2</code> = actingAsId. | ||
|- | |- | ||
| <code>/ | | POST <code>/sell_prime_mortgages</code> || <code>{ intParam2 }</code> || Sell prime mortgages. <code>intParam2</code> = actingAsId. | ||
|- | |- | ||
| <code>/ | | POST <code>/buy_subprime_mortgages</code> || <code>{ intParam2 }</code> || Buy subprime mortgages. <code>intParam2</code> = actingAsId. | ||
|- | |- | ||
| <code>/ | | POST <code>/sell_subprime_mortgages</code> || <code>{ intParam2 }</code> || Sell subprime mortgages. <code>intParam2</code> = actingAsId. | ||
|- | |- | ||
| <code>/ | | POST <code>/list_etfs</code> || <code>{}</code> || Show available ETFs. | ||
|- | |- | ||
| <code>/ | | POST <code>/freeze_all_loans</code> || <code>{ intParam2 }</code> || Freeze all loan repayments across the lender's portfolio. <code>intParam2</code> = actingAsId. | ||
|- | |||
| POST <code>/freeze_loan</code> || <code>{ id }</code> || Freeze one specific loan. <code>id</code> = loan ID. | |||
|} | |} | ||
=== Legal / dirty tricks === | |||
{| class="wikitable" | |||
! Endpoint !! Body !! Description | |||
|- | |||
| POST <code>/change_law_firm</code> || <code>{ intParam2 }</code> || Hire a law firm. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/credit_info</code> || <code>{ intParam2 }</code> || Show credit info (rating, history). <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/antitrust_lawsuit</code> || <code>{ id, intParam2? }</code> || Sue for antitrust. <code>id</code> = target company. <code>intParam2</code> = actingAsId (plaintiff). | |||
|- | |||
| POST <code>/harrassing_lawsuit</code> || <code>{ id, intParam2? }</code> || File a frivolous lawsuit. <code>id</code> = target. <code>intParam2</code> = actingAsId. | |||
|- | |||
| POST <code>/spread_rumors</code> || <code>{ id, intParam2? }</code> || Spread rumors to damage stock / reputation. <code>id</code> = target company. <code>intParam2</code> = actingAsId. | |||
|} | |||
=== Reports / views / navigation === | |||
{| class="wikitable" | |||
! Endpoint !! Body !! Description | |||
|- | |||
| POST <code>/set_active_ui_report</code> || <code>{ id }</code> || Switch active report panel. <code>id</code> = a <code>UI_*</code> enum (see <code>api.js</code> constants). | |||
|- | |||
| POST <code>/set_view_asset</code> || <code>{ id }</code> || Navigate to a company / player. <code>id</code> = entity ID. Updates nav history. | |||
|- | |||
| POST <code>/set_view_industry</code> || <code>{ id }</code> || Navigate to an industry overview. <code>id</code> = industry index, or <code>-2</code> = trigger DB Search. | |||
|- | |||
| POST <code>/database_search</code> || <code>{}</code> || Open the database search interface. | |||
|- | |||
| POST <code>/clear_chart</code> || <code>{}</code> || Clear the active price chart. | |||
|- | |||
| POST <code>/growth_throttle</code> || <code>{}</code> || Open growth-throttle settings. | |||
|- | |||
| POST <code>/clear_stream_list</code> || <code>{}</code> || Clear the streaming-quotes watchlist. | |||
|- | |||
| POST <code>/fill_stream_list</code> || <code>{}</code> || Populate the streaming-quotes watchlist with defaults. | |||
|- | |||
| POST <code>/toggle_streaming_quote</code> || <code>{ id }</code> || Toggle live updates for one asset. <code>id</code> = company / asset. | |||
|- | |||
| POST <code>/nav_back</code> || <code>{}</code> || Nav history: back. | |||
|- | |||
| POST <code>/nav_forward</code> || <code>{}</code> || Nav history: forward. | |||
|- | |||
| POST <code>/nav_clear</code> || <code>{}</code> || Clear nav history. | |||
|- | |||
| POST <code>/nav_goto</code> || <code>{ id }</code> || Jump to a nav-history index. <code>id</code> = position in history stack. | |||
|- | |||
| POST <code>/nav_set_history</code> || JSON array || Restore nav history from saved state. Body = array of <code>{ id, type }</code> where <code>type</code> is <code>"asset"</code> or <code>"industry"</code>, most-recent-first. | |||
|- | |||
| POST <code>/set_who_owns_filter</code> || <code>{ value }</code> || Set the "who owns what" filter. <code>value</code> = filter enum. | |||
|- | |||
| POST <code>/view_current_interest_rates</code> || <code>{}</code> || Show current interest rates. | |||
|- | |||
| POST <code>/whos_ahead</code> || <code>{}</code> || Show the leaderboard. | |||
|- | |||
| POST <code>/db_research_tool</code> || <code>{}</code> || Open the research database tool. | |||
|- | |||
| POST <code>/economic_stats</code> || <code>{}</code> || Show macro stats. | |||
|- | |||
| POST <code>/most_cash_report</code> || <code>{}</code> || Most-cash leaderboard. | |||
|- | |||
| POST <code>/largest_market_cap</code> || <code>{}</code> || Market-cap leaderboard. | |||
|- | |||
| POST <code>/largest_tax_losses</code> || <code>{}</code> || Largest carried-forward tax losses. | |||
|- | |||
| POST <code>/industry_summary</code> || <code>{}</code> || Industry summary stats. | |||
|- | |||
| POST <code>/industry_projections</code> || <code>{}</code> || Industry growth / decline projections. | |||
|- | |||
| POST <code>/view_corp_assets_for_sale</code> || <code>{}</code> || All corporate assets currently for sale. | |||
|} | |||
=== Settings / toggles === | |||
These all open a settings menu / cycle the toggle. No params except where noted. | |||
{| class="wikitable" | |||
! Endpoint !! Body !! Description | |||
|- | |||
| POST <code>/supp_earn_select</code> || <code>{}</code> || Suppress earnings-warnings menu. | |||
|- | |||
| POST <code>/currency_select</code> || <code>{}</code> || Currency selection. | |||
|- | |||
| POST <code>/supp_warn_select</code> || <code>{}</code> || Suppress-warnings menu. | |||
|- | |||
| POST <code>/suppress_select</code> || <code>{}</code> || Suppress messages / alerts menu. | |||
|- | |||
| POST <code>/autosave_select</code> || <code>{}</code> || Autosave settings. | |||
|- | |||
| POST <code>/exercise_select</code> || <code>{}</code> || Auto-exercise options settings. | |||
|- | |||
| POST <code>/sweep_select</code> || <code>{}</code> || Cash-sweep settings. | |||
|- | |||
| POST <code>/makedelivery_select</code> || <code>{}</code> || Make-delivery settings (commodity/crypto). | |||
|- | |||
| POST <code>/takedelivery_select</code> || <code>{}</code> || Take-delivery settings. | |||
|- | |||
| POST <code>/tooltips_select</code> || <code>{}</code> || Tooltip enable / disable. | |||
|- | |||
| POST <code>/shareholdergraph_select</code> || <code>{}</code> || Shareholder-graph display settings. | |||
|- | |||
| POST <code>/disablehotkeys_select</code> || <code>{}</code> || Hotkey enable / disable. | |||
|- | |||
| POST <code>/autoadd_select</code> || <code>{}</code> || Auto-add-to-watchlist settings. | |||
|- | |||
| POST <code>/set_chart_type</code> || <code>{ id }</code> || Set chart style. <code>id</code> = chart type enum. | |||
|- | |||
| POST <code>/set_locale</code> || <code>{ str }</code> || Set UI locale. <code>str</code> = locale code (e.g. <code>"en-US"</code>, <code>"ja-JP"</code>). | |||
|} | |||
=== Cheat menu === | |||
{| class="wikitable" | |||
! Endpoint !! Body !! Description | |||
|- | |||
| POST <code>/cheat_disable</code> || <code>{}</code> || Lock cheats off. | |||
|- | |||
| POST <code>/cheat_disable_lawsuits</code> || <code>{}</code> || Disable lawsuits entirely. | |||
|- | |||
| POST <code>/cheat_merger_info</code> || <code>{}</code> || Reveal hidden merger info. | |||
|- | |||
| POST <code>/cheat_earnings_info</code> || <code>{}</code> || Reveal earnings forecasts and hidden financials. | |||
|- | |||
| POST <code>/cheat_add_cash</code> || <code>{}</code> || Add a fixed amount of cash. (No "set cash to N" - this is fixed.) | |||
|} | |||
=== Modals === | |||
{| class="wikitable" | |||
! Endpoint !! Body !! Description | |||
|- | |||
| POST <code>/close_modal</code> || <code>{}</code> || Close the current modal. | |||
|- | |||
| POST <code>/modal_result</code> || <code>{ answer }</code> or <code>{ str }</code> || Submit the player's modal response. Use <code>answer</code> for numeric / choice modals; <code>str</code> for text-entry modals. | |||
|} | |||
=== Tutorial === | |||
{| class="wikitable" | |||
! Endpoint !! Body !! Description | |||
|- | |||
| POST <code>/set_tutorial_step</code> || <code>{ id }</code> || Jump tutorial to a step. <code>id</code> = step number. | |||
|- | |||
| POST <code>/set_tutorial_enabled</code> || <code>{ id }</code> || Enable / disable tutorial. <code>id</code> = <code>1</code> on, <code>0</code> off. | |||
|} | |||
=== Price alerts === | |||
{| class="wikitable" | |||
! Endpoint !! Body !! Description | |||
|- | |||
| POST <code>/show_price_alerts</code> || <code>{}</code> || Open the alerts management dialog. | |||
|- | |||
| POST <code>/create_price_alert</code> || <code>{ str }</code> || Create an alert. <code>str</code> = pipe-delimited <code>"entityId|direction|targetPrice"</code>; direction is <code>"up"</code> or <code>"down"</code>. | |||
|- | |||
| POST <code>/delete_price_alert</code> || <code>{ id }</code> || Delete one alert. <code>id</code> = alert slot index. | |||
|} | |||
=== CustomData === | |||
= | {| class="wikitable" | ||
! Endpoint !! Body !! Description | |||
|- | |||
| POST <code>/set_custom_data</code> || JSON object || Shallow-merge mod data into <code>customData</code>. See [[#CustomData - your persistent storage|§CustomData]]. | |||
|} | |||
<code>GET /gamestate</code> | == gameState reference == | ||
<code>GET /gamestate</code> returns one JSON object - ~90 top-level fields - that is the complete read model. In a frontend mod, subscribe to only the fields you need so you only re-render on those: | |||
<pre> | <pre> | ||
const cash = useGameStore(s => s.gameState.cash); | |||
const cash = useGameStore(s = | const year = useGameStore(s => s.gameState.currentYear); | ||
const year = useGameStore(s = | |||
</pre> | </pre> | ||
{| class="wikitable" | |||
! Group !! Fields | |||
|- | |||
| '''Player''' || <code>cash</code>, <code>otherAssets</code>, <code>totalAssets</code>, <code>totalDebt</code>, <code>netWorth</code>, <code>playerId</code>, <code>playerName</code>, <code>chairedCompanyId</code> | |||
|- | |||
| '''Clock / session''' || <code>currentYear</code>, <code>currentQuarter</code>, <code>currentMonth</code>, <code>currentDay</code>, <code>currentTime</code>, <code>nextEarningsDate</code>, <code>gameLoaded</code>, <code>gameOver</code>, <code>readyToRestart</code>, <code>isTickerRunning</code>, <code>tickSpeed</code>, <code>splashScreenPlayed</code> | |||
|- | |||
| '''Active selection''' || <code>activeEntityNum</code>, <code>activeEntityName</code>, <code>activeEntitySymbol</code>, <code>activeIndustryNum</code>, <code>activeIndustryId</code>, <code>actingAsId</code>, <code>actingAsName</code>, <code>actingAsSymbol</code>, <code>actingAsIndustryId</code>, <code>actingAs</code> | |||
|- | |||
| '''Modal state''' || <code>modalType</code>, <code>modalText</code>, <code>modalTitle</code>, <code>modalDefault</code>, <code>modalFilter</code> | |||
|- | |||
| '''Collections''' || <code>allCompanies</code>, <code>allPlayers</code>, <code>allSecurities</code>, <code>allIndustries</code>, <code>controlledCompanies</code>, <code>tickerQueue</code>, <code>streamingQuotesList</code>, <code>newsHeadlines</code>, <code>trendingNews</code>, <code>navHistory</code>, <code>navPointerIndex</code> | |||
|- | |||
| '''Active entity detail''' || <code>activeEntityData</code>, <code>activeEntityFinancials</code> (deep: cash/assets/debt/equity/EPS/cash-flow/…), <code>activeEntityPlayerFinancials</code>, <code>financialProfile</code>, <code>cashflowProjection</code>, <code>portfolio</code>, <code>portHoldings</code>, <code>swapsPortfolio</code>, <code>optionsList</code>, <code>commodityList</code>, <code>shareholdersList</code>, <code>researchReport</code>, <code>advisorySummary</code>, <code>earningsReport</code>, <code>advances</code>, <code>loansReport</code>, <code>myCorporationsReport</code> | |||
|- | |||
| '''Market reports''' || <code>whoOwns*Report</code> (futures, physical commodities, options, swaps, stocks, investment contracts), <code>industrySummaryReport</code>, <code>industryProjectionReport</code>, <code>industryGrowthRatesReport</code>, <code>mostMarketShareReport</code>, <code>mostTaxLossReport</code>, <code>mostCashReport</code>, <code>mostMarketCapReport</code>, <code>economicDataReport</code>, <code>interestRatesReport</code>, <code>whosAheadReport</code> | |||
|- | |||
| '''Settings''' || <code>suppEarnSetting</code>, <code>suppWarnSetting</code>, <code>supPopupSetting</code>, <code>autosaveSetting</code>, <code>exerciseItSetting</code>, <code>sweepSetting</code>, <code>makeDeliverySetting</code>, <code>takeDeliverySetting</code>, <code>tooltipsSetting</code>, <code>shareholderGraphSetting</code>, <code>unethicalSetting</code>, <code>disableHotkeysSetting</code>, <code>chartType</code>, <code>locale</code> | |||
|- | |||
| '''Tutorial / misc''' || <code>tutorialEnabled</code>, <code>tutorialStep</code>, <code>alerts</code>, <code>frozenAllLoans</code>, <code>dlrSign</code>, <code>euro</code>, '''<code>customData</code>''' ([[#CustomData - your persistent storage|§CustomData]]) | |||
|} | |||
The exact shape can change between versions - the authoritative move is to <code>GET /gamestate</code> from a running game. <code>Ctrl+Shift+I</code> opens DevTools; <code>gameStore.getState().gameState</code> in the console dumps the live object. | |||
== CustomData - your persistent storage == | |||
<code>customData</code> is a free-form JSON object that '''rides along inside the save file'''. The engine never reads it for simulation - it stores it verbatim, writes it into the <code>.WSR</code>/<code>.DAT</code> save, restores it on load, and exposes it on every <code>gameState</code> as <code>gameState.customData</code>. | |||
<code>customData</code> is a free-form JSON object that '''rides along inside the save file'''. The engine never reads it for simulation - it | |||
This is the supported way for a mod to persist its own state per-save. | This is the supported way for a mod to persist its own state per-save. | ||
=== | === Read === | ||
<pre> | <pre> | ||
const customData = useGameStore(s => s.gameState?.customData ?? {}); | |||
const customData = useGameStore(s = | |||
const myStuff = customData.myModKey; | const myStuff = customData.myModKey; | ||
</pre> | </pre> | ||
=== | === Write === | ||
<pre> | <pre> | ||
| Line 404: | Line 786: | ||
</pre> | </pre> | ||
=== Semantics === | |||
* '''Shallow merge.''' <code>POST /set_custom_data</code> merges | * '''Shallow merge.''' <code>POST /set_custom_data</code> merges into the existing <code>customData</code> at the '''top level''' - incoming top-level keys overwrite, others are preserved. '''Namespace your data under one top-level key''' (e.g. <code>myModKey</code>) and write that whole sub-object each time. | ||
* ''' | * '''Round-trips.''' New value comes back on the next <code>gameState</code> broadcast. | ||
* ''' | * '''Inert.''' Cannot change cash, prices, or any engine value - only action endpoints can ([[#REST API reference|§REST API]]). | ||
* ''' | * '''Per-save.''' Saved/loaded with the game; a fresh New Game starts empty. | ||
=== | === Reserved top-level keys === | ||
The | The base UI already stores a few things in <code>customData</code>. '''Don't reuse these keys''': | ||
{| class="wikitable" | {| class="wikitable" | ||
! Key !! Used by !! Contents | ! Key !! Used by !! Contents | ||
|- | |- | ||
| <code>notes</code> || Notes modal || | | <code>notes</code> || Notes modal || free-text notes | ||
|- | |- | ||
| <code>navHistory</code> || Navigation system || saved | | <code>navHistory</code> || Navigation system || saved nav history | ||
|- | |- | ||
| (DB search criteria) || Database Search view || saved | | (DB search criteria) || Database Search view || saved filters / sort | ||
|- | |- | ||
| (holdings prefs) || Portfolio Holdings table || saved filters/sort/hide-subsidiaries | | (holdings prefs) || Portfolio Holdings table || saved filters / sort / hide-subsidiaries | ||
|} | |} | ||
Pick a unique | Pick a unique key for your mod (e.g. <code>customData["mymod:state"]</code>). | ||
== | == Limits - what you '''can't''' do == | ||
* '''No raw memory / pointer writes.''' Only curated endpoints. The only free-form write is <code>customData</code>, and it's inert. | |||
* '''No intercepting the update loop.''' WS delivers a snapshot. Read and react; you cannot mutate values mid-update or inject into serialization. | |||
* '''No new engine behavior without recompiling.''' New mechanic / event type / endpoint = edit PowerBASIC source (<code>src/main/wsr/*.inc</code>, ~178K lines), rebuild <code>wsr.exe</code> (PB 9.80) and usually <code>ui.dll</code> (VS C++). That's engine development, not modding. | |||
* '''No editing binary game data.''' Saves and most <code>.DAT</code>/<code>.PRM</code> are proprietary binary or encoded. <code>CORPNAME.DAT</code> is the one safe exception. | |||
== Workflow == | |||
== | === Edit / test === | ||
# Edit a file under <code>js/</code>, <code>css/</code>, <code>assets/</code>, or <code>CORPNAME.DAT</code>. | # Edit a file under <code>js/</code>, <code>css/</code>, <code>assets/</code>, or <code>CORPNAME.DAT</code>. | ||
# Restart the game ( | # Restart the game (no hot reload). | ||
# | # <code>Ctrl+Shift+I</code> for DevTools - console errors, the <code>gameStore</code>, network tab against the REST API. | ||
=== Packaging | === Packaging for others === | ||
A mod is '''a set of replacement files'''. Simplest distribution: a zip mirroring the <code>resources/app/</code> structure: | |||
<pre> | <pre> | ||
| Line 456: | Line 836: | ||
</pre> | </pre> | ||
Install instructions: "drop these into <code>resources/app/</code>, overwriting." | |||
Good practice: | Good practice: | ||
* Keep mods '''additive''' where possible - new component files wired in with one import line, | * Keep mods '''additive''' where possible - new component files wired in with one import line, not rewrites of core files. Two additive mods are less likely to collide. | ||
* Document which base files you replace. | * Document which base files you replace. | ||
* Tell users that Steam's "verify integrity of game files" reverts changes (their uninstall path) and | * Tell users that Steam's "verify integrity of game files" reverts changes (their uninstall path) and to back up originals. | ||
[[Category:Modding]] | [[Category:Modding]] | ||
[[Category:Wall Street Raider]] | [[Category:Wall Street Raider]] | ||
Revision as of 13:16, 15 May 2026
Wall Street Raider - Modding Guide
A reference for everything you can mod in WSR with nothing but a text editor. The Electron frontend has no build step and the install ships with asar: false, so every file under resources/app/ is a normal editable file. Edit, restart, see your change.
What you can't mod without recompiling: the PowerBASIC engine (wsr.exe), the C++ bridge (ui.dll), and the binary .DAT/.PRM files.
TL;DR - what's moddable
| I want to… | Do this |
|---|---|
| Re-theme the game | Edit css/variables.css, then css/theme.css / css/components/
|
| Change UI text / layout / behavior | Edit the relevant file in js/components/
|
| Add a new panel or feature | New file in js/components/, import it from a parent or app.js
|
| Replace a background video or image | Drop a same-named file into assets/
|
| Rewrite the manual | Edit assets/help/wsrbook.htm
|
| Add a language | New js/locale/<code>.js + register in localeManager.js
|
| Change / add contextual hints | Edit files in js/data/hints/
|
| Rename companies / tickers / nations | Edit CORPNAME.DAT (plain text). Effective on next New Game.
|
| Drive the game from JS | Call an api.js function (§api.js)
|
| Drive the game from a third-party app | Hit the REST API directly (§REST API) |
| Read game state | useGameStore(s => s.gameState.<field>) or GET /gamestate (§gameState)
|
| Persist your mod's data in saves | setCustomData({ myKey: … }) (§CustomData)
|
| Change a core simulation rule | Not moddable. Requires engine source + recompile. |
Architecture in one breath
PowerBASIC engine → C++ bridge DLL (REST + WebSocket) → Electron UI wsr.exe ui.dll everything you see
At launch, ui.dll picks ephemeral REST + WS ports and writes them to %LOCALAPPDATA%\Wall Street Raider\runtime.json:
{ "pid": 12345, "rest_port": 54321, "ws_port": 54322 }
Electron reads that file and hands the ports to the renderer; js/api.js builds http://127.0.0.1:<rest> and ws://127.0.0.1:<ws>. WS pushes a full snapshot on connect, then JSON-patch diffs; if WS drops, the UI polls GET /gamestate.
Third-party apps: read runtime.json the same way. Ports change every launch.
File layout
In source (electron/) and in an installed copy (<install>/resources/app/) the layout is identical - the install is a verbatim copy of the source tree (see extraFiles in electron/package.json):
resources/app/ ├── index.html # entry point ├── main.js # Electron main process ├── js/ # ← all frontend code (editable) │ ├── app.js # Preact root, WS wiring, hotkeys │ ├── api.js # REST/WS client, gameStore, ID constants, endpoint wrappers │ ├── components/ # 100+ Preact panels/modals/tables/tabs │ ├── hooks/ # useCookie, useInterval, usePanelSelection, … │ ├── services/ # hintMatcher.js, … │ ├── utils/ # misc │ ├── icons.js # inline SVG │ ├── debug-log.js # frontend logging │ ├── lib/ # vendored: preact, zustand, tailwind, fast-json-patch │ ├── locale/ # ← translations │ └── data/hints/ # ← contextual hints ├── css/ # ← all styling (editable) ├── assets/ # ← images / videos / icons / help (editable) ├── CORPNAME.DAT # ← plain-text company database (editable) ├── wsr.exe # engine (binary) ├── ui.dll # bridge (binary) └── *.DAT / *.PRM # other game data (binary/encoded - leave alone)
Frontend code (js/)
Components use Preact + htm (JSX-like template literals, no compile step) and Zustand for state. Minimal example:
import { html, Component } from '../lib/preact.standalone.module.js';
import { useGameStore } from '../api.js';
export function MyPanel() {
const cash = useGameStore(s => s.gameState.cash);
return html`<div class="p-4">Cash: ${cash}</div>`;
}
You can:
- Edit any existing component (layout, text, colors, behavior).
- Add new components and import them into
app.jsor any parent. - Add new hotkeys (grep
hotkey; handling lives inapp.js+HotkeyButtonBar.js). - Add new buttons that call any API endpoint (§api.js).
- Add new derived displays from
gameState(§gameState).
Styling (css/)
css/ ├── style.css # the file index.html links - imports the rest ├── base.css # resets / base element styles ├── variables.css # ← CSS custom properties (theme palette lives here) ├── theme.css # theme layer ├── layout.css # app layout ├── chart-styles.js # chart styling injected as JS ├── components/ # buttons, dropdown, forms, modals, panels, tables, tabs ├── features/ # assets, calculator, help, market, menu, notes, ticker, tutorial └── pages/ # game, main-menu, quotes
Tailwind utilities are also available via js/lib/tailwind.module.js, so most components use Tailwind inline + the modular CSS for anything custom.
To re-theme: start with variables.css - override the custom properties and the whole app follows.
Assets (assets/)
| What | Files |
|---|---|
| Background videos | the many *.mp4 (Wall Street footage, industry b-roll, …)
|
| Event images | blackswan.png, ponzi.jpg, soupline.jpg, zimbabwe.jpg, helicopter.jpg, yacht.mp4
|
| Branding / logos | wallstreetraider_logo.png, hackjackgames_logo.png, roninsoft_logo.png
|
| Social widgets | discord-widget.png, reddit-widget.png
|
| Loading spinner | loading.gif
|
| App icon | wsr.ico
|
Replace any of these with a same-named file of the same type. Keep filenames identical - components reference them by name.
Help content (assets/help/)
wsrbook.htm is the in-game manual - one ~1 MB plain HTML file. Edit it directly. The *.jpg files in the same folder are diagrams it embeds. electron/UI_HELP_CATALOG.txt is a developer catalog of help sections.
Translations (js/locale/)
Pure data - no engine changes needed to add a language.
How it works (localeManager.js):
LANGUAGE_OPTIONSmaps locale code →{ name, dictionary, warning? }.- Each
dictionaryis a flat{ "English source": "translation", … }. - The
translatordoes runtime lookup/replacement on UI strings. - Existing locales:
zh-CN.js,es-419.js,ja-JP.js,pt-BR.js,ru-RU.js.
To add a language:
// js/locale/fr-FR.js
export const frFR = {
"New Game": "Nouvelle partie",
"Buy Stock": "Acheter des actions",
// … one entry per source string you want translated
};
// js/locale/localeManager.js
import { frFR } from './fr-FR.js';
// …
'fr-FR': { name: 'Français', dictionary: frFR },
The chosen locale persists in CFIG.WSR, surfaces as gameState.locale, and switches via POST /set_locale.
Contextual hints (js/data/hints/)
Hint files are grouped by view and aggregated by index.js:
hints/ ├── index.js # aggregates all sets ├── global.js # fallback hints ├── company.js # while viewing a company ├── industry.js ├── market.js ├── modals.js ├── player.js ├── portfolio.js └── swap-info-text.js
Each entry:
{
id: 'company-overview-controlled-etf',
match: { view: 'company', tab: 'overview', controls: true, entityKind: 'mutual-fund' },
title: 'Advising an ETF',
content: `<p>HTML body shown to the player…</p>`,
}
js/services/hintMatcher.js picks the most specific match for the current screen. Rewrite text, add new hints, retarget match conditions - no engine changes.
Company names (CORPNAME.DAT)
Plain pipe-delimited text:
0011|00|JPB | J.P. MULLINS WALL ST. BANK | 0012|00|UBC | URBAN BANCORP | 0015|26|KYOB | KYOTO BANK |
Columns: company ID | nation code | ticker | name. Read by the engine's GetCorpNames at new-game time, so edits take effect on the next new game (not on already-saved games). Rename companies, change tickers, change a company's home nation.
Keep the format exact: 4-digit zero-padded ID, 2-digit nation code, fixed-width symbol field, and the surrounding | + spacing. CORPNAME.ORI is the pristine original - keep as your backup.
Other game data (don't bother)
| File(s) | Content | Why limited |
|---|---|---|
QUOTEWSR.DAT |
quote of the day | encoded; quotehs.prm is a near-plaintext source the engine doesn't read
|
MSCNEWS.DAT |
news / scenario headlines | encoded; same story with MSCNEWS.PRM
|
scenwin1-4.prm |
scenario window text | engine-internal format |
GAME01-50.DAT |
save slots / scenario data | proprietary binary |
WSR101.DAT, REGINFO.DAT, … |
engine data | proprietary binary |
Treat as read-only. CORPNAME.DAT is the one truly editable game-data file.
Calling the API from a frontend mod
js/api.js is the wrapper layer for the REST API: each endpoint has a named async function with semantic parameters, plus the live gameStore, plus exported ID constants (entity IDs, industry indices, special asset IDs, UI_* report IDs). Frontend mods should call api.js functions, not raw HTTP.
import * as api from '../api.js'; await api.buyStock(companyId); // /buy_stock await api.setDividend(actingAsId); // /set_dividend await api.merger(targetId, actingAsId); // /merger await api.cheatAddCash(); // /cheat_add_cash const state = await api.getGameState(); // GET /gamestate
For endpoints that don't have a named wrapper yet, four request shapes are also exported:
| Helper | Body shape | Use |
|---|---|---|
postNoArg(path) |
{} |
actions with no parameter |
postIdArg(path, id) |
{ id } |
actions on a specific entity / slot |
postIdArgWithActingAs(path, id, actingAsId) |
{ id, intParam2 } |
most corporate / trade actions |
postOptionsTradeWithActingAs(path, id, actingAsId, underlyingId) |
{ id, intParam2, underlyingId } |
options trades |
postStringArg(path, str) |
{ str } |
actions taking a string (e.g. save-as filename) |
getJSON(path) |
- | GET endpoints |
For wire-level details (the underlying routes, the JSON body the bridge actually receives, what each generic field means per endpoint), see §REST API reference.
REST API reference
For third-party apps and direct integrations. Frontend mods should prefer api.js (§api.js).
Conventions
- Base URL:
http://127.0.0.1:<rest_port>. Readrest_portfrom%LOCALAPPDATA%\Wall Street Raider\runtime.json; ports change every launch. - Content-Type:
application/jsonon all POSTs. - Generic body schema: the bridge accepts a single envelope
{ id?, intParam2?, underlyingId?, str?, answer?, value?, filename? }. Each endpoint uses 0-3 of these fields with endpoint-specific meanings - the per-endpoint tables below spell out which fields are read and what they mean in context. - Omitted fields default to 0 / empty. Most action endpoints can be called as
{}if they don't strictly need params (the engine often falls back to "use the currently active entity" or "show the picker modal"). - Most actions open a modal rather than executing immediately. After POSTing, the engine pushes a modal state via WS /
gameState.modalType; respond withPOST /modal_result.
The actingAsId pattern
Most corporate / trade endpoints accept an actingAsId (sent as the JSON field intParam2): the entity that executes the action.
0(or omitted) = act as the player.- Any other ID = act as that controlled company. The engine momentarily switches context, runs the action as that company, then restores.
When the per-endpoint tables below show intParam2, it means actingAsId unless noted otherwise.
Response shape
GET endpoints return endpoint-specific JSON (documented in the GET table). POST endpoints return { status: "ok" } on success and a { error: "…" } with HTTP 4xx/5xx on failure. Side effects show up via the next WS broadcast.
GET endpoints
| Method | Endpoint | Returns |
|---|---|---|
| GET | /status |
health check |
| GET | /gamestate |
full game-state JSON (§gameState) |
| GET | /quote |
{ quote: string } - quote of the day
|
| POST | /asset_chart |
body { id }; returns 60 months of price history for an asset ({ prices, highs, lows, xAxisTitle, yAxisTitle, baseMonth, baseYear })
|
| GET | /database_data |
searchable database of all companies (industry, financials, ratings, …) |
| GET | /ownership_tree |
ownership hierarchy of the active entity (who owns it, at what %) |
| GET | /subsidiaries_tree |
what the active entity owns (its subsidiaries, at what %) |
Session / clock
| Endpoint | Body | Description |
|---|---|---|
POST /newgame |
{} |
Start a new game. |
POST /loadgame |
{} |
Open the load-game flow. |
POST /load_specific_save |
{ filename } |
Load a specific save. filename = save basename; must not contain .., /, \, :.
|
POST /savegame |
{} |
Save to the default slot. |
POST /savegameas |
{ filename } |
Save under a name. Empty filename shows the Save-As dialog.
|
POST /exit_game |
{} |
Exit to main menu. |
POST /check_scoreboard |
{} |
Open the scoreboard view. |
POST /start_ticker |
{} |
Start the market ticker animations. |
POST /run_ticker |
{} |
Advance the ticker by one queue item. |
POST /stop_ticker |
{} |
Stop the ticker. |
POST /set_ticker_speed |
{ id } |
Set ticker speed. id = speed enum.
|
POST /ticker_advance |
{} |
Pop the front of the ticker queue, append a new item. |
POST /clear_event_string |
{} |
Clear the event message log. |
POST /splash_screen_played |
{} |
Mark splash as shown so it's skipped next time. |
Trading - stocks & bonds
| Endpoint | Body | Description |
|---|---|---|
POST /buy_stock |
{ id, intParam2? } |
Buy stock. id = company to buy. intParam2 = actingAsId.
|
POST /sell_stock |
{ id, intParam2? } |
Sell stock. id = company to sell. intParam2 = actingAsId.
|
POST /short_stock |
{ id, intParam2? } |
Open short. id = company to short. intParam2 = actingAsId.
|
POST /cover_short_stock |
{ id, intParam2? } |
Cover an existing short. id = company. intParam2 = actingAsId.
|
POST /buy_corporate_bond |
{ id, intParam2? } |
Buy a corporate bond. id = issuing company. intParam2 = actingAsId.
|
POST /sell_corporate_bond |
{ id, intParam2? } |
Sell a corporate bond. id = issuing company. intParam2 = actingAsId.
|
POST /buy_long_govt_bonds |
{ intParam2 } |
Buy long-term govt bonds. intParam2 = actingAsId. id ignored.
|
POST /sell_long_govt_bonds |
{ intParam2 } |
Sell long-term govt bonds. intParam2 = actingAsId.
|
POST /buy_short_govt_bonds |
{ intParam2 } |
Buy short-term govt bonds. intParam2 = actingAsId.
|
POST /sell_short_govt_bonds |
{ intParam2 } |
Sell short-term govt bonds. intParam2 = actingAsId.
|
Trading - commodities & crypto
| Endpoint | Body | Description |
|---|---|---|
POST /buy_commodity_futures |
{ id, intParam2? } |
Buy commodity futures. id = commodity ID (e.g. OIL_ID, GOLD_ID). intParam2 = actingAsId.
|
POST /sell_commodity_futures |
{ id, intParam2? } |
Sell commodity futures. id = commodity ID. intParam2 = actingAsId.
|
POST /close_long_commodity_futures_by_slot |
{ id, intParam2? } |
Close a specific long position. id = portfolio slot index (not a commodity ID). intParam2 = actingAsId.
|
POST /short_commodity_futures |
{ id, intParam2? } |
Short commodity futures. id = commodity ID. intParam2 = actingAsId.
|
POST /cover_short_commodity_futures |
{ id, intParam2? } |
Cover commodity short. id = commodity ID. intParam2 = actingAsId.
|
POST /cover_short_commodity_futures_by_slot |
{ id, intParam2? } |
Cover a specific short position. id = portfolio slot index. intParam2 = actingAsId.
|
POST /buy_physical_commodity |
{ id, intParam2? } |
Buy physical commodity. id = commodity ID. intParam2 = actingAsId.
|
POST /sell_physical_commodity |
{ id, intParam2? } |
Sell physical commodity. id = commodity ID. intParam2 = actingAsId.
|
POST /buy_physical_crypto |
{ id, intParam2? } |
Buy physical crypto. id = crypto ID (e.g. BITCOIN_ID). intParam2 = actingAsId.
|
POST /sell_physical_crypto |
{ id } |
Sell physical crypto. id = crypto ID. (No actingAs param on this one.)
|
POST /buy_crypto_futures |
{ id, intParam2? } |
Buy crypto futures. id = crypto ID. intParam2 = actingAsId.
|
POST /sell_crypto_futures |
{ id, intParam2? } |
Sell crypto futures. id = crypto ID. intParam2 = actingAsId.
|
Trading - options
| Endpoint | Body | Description |
|---|---|---|
POST /buy_calls |
{ id, intParam2?, underlyingId? } |
Buy calls. id = strike-price ID (or 0 to show the strike picker). intParam2 = actingAsId. underlyingId = company being optioned (when id>0 and underlyingId>0, modal is skipped — used by CLI flows like "CALL ABC").
|
POST /sell_calls |
{ id, intParam2?, underlyingId? } |
Sell calls. Same param semantics as /buy_calls.
|
POST /buy_puts |
{ id, intParam2?, underlyingId? } |
Buy puts. Same param semantics as /buy_calls.
|
POST /sell_puts |
{ id, intParam2?, underlyingId? } |
Sell puts. Same param semantics as /buy_calls.
|
POST /advanced_options_trading |
{ intParam2 } |
Open the advanced options panel (spreads, collars). intParam2 = actingAsId.
|
POST /exercise_call_options_early |
{ id, intParam2? } |
Exercise a call early. id = call contract ID. intParam2 = actingAsId.
|
POST /exercise_put_options_early |
{ id, intParam2? } |
Exercise a put early. id = put contract ID. intParam2 = actingAsId.
|
Corporate management
All of these operate on the acting-as entity. id is unused unless noted; pass 0 or omit.
| Endpoint | Body | Description |
|---|---|---|
POST /prepay_taxes |
{ intParam2 } |
Prepay taxes. intParam2 = actingAsId.
|
POST /elect_ceo |
{ intParam2 } |
Open CEO-election picker for the entity. intParam2 = actingAsId.
|
POST /resign_as_ceo |
{ intParam2 } |
Acting-as entity resigns as CEO. intParam2 = actingAsId.
|
POST /change_managers |
{ intParam2 } |
Hire / fire managers. intParam2 = actingAsId.
|
POST /set_dividend |
{ intParam2 } |
Set the company's dividend. intParam2 = actingAsId (dividend issuer).
|
POST /set_productivity |
{ intParam2 } |
Set productivity. intParam2 = actingAsId.
|
POST /set_growth_rate |
{ intParam2 } |
Set growth rate. intParam2 = actingAsId.
|
POST /restructure |
{ intParam2 } |
Open restructuring modal. intParam2 = actingAsId.
|
POST /buy_corporate_assets |
{ intParam2 } |
Buy plant / equipment. intParam2 = actingAsId.
|
POST /sell_corporate_assets |
{ intParam2 } |
Sell plant / equipment. intParam2 = actingAsId.
|
POST /offer_corporate_assets_for_sale |
{ intParam2 } |
List assets for other entities to buy. intParam2 = actingAsId.
|
POST /view_for_sale_items |
{ intParam2 } |
Browse assets currently for sale. intParam2 = actingAsId.
|
POST /sell_subsidiary_stock |
{ id, intParam2? } |
Sell stock the entity holds in a subsidiary. id = subsidiary company ID. intParam2 = actingAsId (seller).
|
POST /rebrand |
{ intParam2 } |
Rename the company. intParam2 = actingAsId.
|
POST /toggle_company_autopilot |
{ id } |
Toggle one company's autopilot. id = company.
|
POST /toggle_global_autopilot |
{ intParam2 } |
Toggle autopilot across all of the entity's holdings. intParam2 = actingAsId.
|
POST /become_etf_advisor |
{ intParam2 } |
Become ETF advisor. intParam2 = actingAsId.
|
POST /set_advisory_fee |
{ intParam2 } |
Set the ETF advisory fee. intParam2 = actingAsId.
|
POST /decrease_earnings |
{ intParam2 } |
Accounting adjustment to lower reported earnings. intParam2 = actingAsId.
|
POST /increase_earnings |
{ intParam2 } |
Accounting adjustment to raise reported earnings. intParam2 = actingAsId.
|
M&A / corporate finance
| Endpoint | Body | Description |
|---|---|---|
POST /merger |
{ id, intParam2? } |
Start a merger. id = target company. intParam2 = actingAsId (acquirer).
|
POST /greenmail |
{ id, intParam2? } |
Greenmail (coercive buyback). id = target. intParam2 = actingAsId.
|
POST /lbo |
{ id, intParam2? } |
Leveraged buyout. id = target. intParam2 = actingAsId.
|
POST /startup |
{ intParam2 } |
Found a new startup. intParam2 = actingAsId (founder).
|
POST /capital_contribution |
{ intParam2 } |
Inject cash into a subsidiary. intParam2 = actingAsId.
|
POST /public_stock_offering |
{ intParam2 } |
IPO. intParam2 = actingAsId (company going public).
|
POST /private_stock_offering |
{ intParam2 } |
Private placement. intParam2 = actingAsId.
|
POST /issue_new_corp_bonds |
{ intParam2 } |
Issue new corporate bonds. intParam2 = actingAsId (issuer).
|
POST /redeem_corp_bonds |
{ intParam2 } |
Retire outstanding bonds. intParam2 = actingAsId.
|
POST /extraordinary_dividend |
{ intParam2 } |
One-time special dividend. intParam2 = actingAsId.
|
POST /tax_free_liquidation |
{ intParam2 } |
Liquidate with tax deferral. intParam2 = actingAsId.
|
POST /taxable_liquidation |
{ intParam2 } |
Liquidate; pay tax on gains. intParam2 = actingAsId.
|
POST /spin_off |
{ id, intParam2? } |
Spin off a subsidiary. id = subsidiary to spin off. intParam2 = actingAsId (parent).
|
POST /split_stock |
{ intParam2 } |
Forward stock split. intParam2 = actingAsId.
|
POST /reverse_split_stock |
{ intParam2 } |
Reverse stock split. intParam2 = actingAsId.
|
Banking / loans / swaps
| Endpoint | Body | Description |
|---|---|---|
POST /borrow_money |
{ intParam2 } |
Take out a loan. intParam2 = actingAsId (borrower).
|
POST /repay_loan |
{ intParam2 } |
Pay down / repay. intParam2 = actingAsId.
|
POST /advance_funds |
{ intParam2 } |
Lender advances cash against securities. intParam2 = actingAsId (lender).
|
POST /call_in_advance |
{ id } |
Terminate an advance. id = advance ID.
|
POST /interest_rate_swaps |
{ id, intParam2? } |
Open the swaps modal. id = asset ID. intParam2 = actingAsId.
|
POST /view_swap_details |
{ id, intParam2? } |
View one swap's details. id = swap ID. intParam2 = actingAsId.
|
POST /terminate_swap |
{ id, intParam2? } |
Terminate a swap early. id = swap ID. intParam2 = actingAsId.
|
POST /set_bank_allocation |
{ intParam2 } |
Distribute cash across banks. intParam2 = actingAsId.
|
POST /trade_tbills |
{ intParam2 } |
Trade T-bills. intParam2 = actingAsId.
|
POST /list_bank_loans |
{ intParam2 } |
List the entity's outstanding bank loans (when acting as a bank). intParam2 = actingAsId.
|
POST /change_bank |
{ intParam2 } |
Change primary bank. intParam2 = actingAsId.
|
POST /call_in_loan |
{ id } |
Demand immediate repayment. id = loan ID.
|
POST /buy_bank_loans |
{} |
Open the bank-loans investment picker. |
POST /buy_business_loans |
{ intParam2 } |
Buy business-loan portfolio. intParam2 = actingAsId (investor).
|
POST /sell_business_loan |
{ id } |
Sell one business-loan holding. id = loan ID.
|
POST /buy_consumer_loans |
{ intParam2 } |
Buy consumer-loan portfolio. intParam2 = actingAsId.
|
POST /sell_consumer_loans |
{ intParam2 } |
Sell consumer-loan holdings. intParam2 = actingAsId.
|
POST /buy_prime_mortgages |
{ intParam2 } |
Buy prime mortgages. intParam2 = actingAsId.
|
POST /sell_prime_mortgages |
{ intParam2 } |
Sell prime mortgages. intParam2 = actingAsId.
|
POST /buy_subprime_mortgages |
{ intParam2 } |
Buy subprime mortgages. intParam2 = actingAsId.
|
POST /sell_subprime_mortgages |
{ intParam2 } |
Sell subprime mortgages. intParam2 = actingAsId.
|
POST /list_etfs |
{} |
Show available ETFs. |
POST /freeze_all_loans |
{ intParam2 } |
Freeze all loan repayments across the lender's portfolio. intParam2 = actingAsId.
|
POST /freeze_loan |
{ id } |
Freeze one specific loan. id = loan ID.
|
Legal / dirty tricks
| Endpoint | Body | Description |
|---|---|---|
POST /change_law_firm |
{ intParam2 } |
Hire a law firm. intParam2 = actingAsId.
|
POST /credit_info |
{ intParam2 } |
Show credit info (rating, history). intParam2 = actingAsId.
|
POST /antitrust_lawsuit |
{ id, intParam2? } |
Sue for antitrust. id = target company. intParam2 = actingAsId (plaintiff).
|
POST /harrassing_lawsuit |
{ id, intParam2? } |
File a frivolous lawsuit. id = target. intParam2 = actingAsId.
|
POST /spread_rumors |
{ id, intParam2? } |
Spread rumors to damage stock / reputation. id = target company. intParam2 = actingAsId.
|
Reports / views / navigation
| Endpoint | Body | Description |
|---|---|---|
POST /set_active_ui_report |
{ id } |
Switch active report panel. id = a UI_* enum (see api.js constants).
|
POST /set_view_asset |
{ id } |
Navigate to a company / player. id = entity ID. Updates nav history.
|
POST /set_view_industry |
{ id } |
Navigate to an industry overview. id = industry index, or -2 = trigger DB Search.
|
POST /database_search |
{} |
Open the database search interface. |
POST /clear_chart |
{} |
Clear the active price chart. |
POST /growth_throttle |
{} |
Open growth-throttle settings. |
POST /clear_stream_list |
{} |
Clear the streaming-quotes watchlist. |
POST /fill_stream_list |
{} |
Populate the streaming-quotes watchlist with defaults. |
POST /toggle_streaming_quote |
{ id } |
Toggle live updates for one asset. id = company / asset.
|
POST /nav_back |
{} |
Nav history: back. |
POST /nav_forward |
{} |
Nav history: forward. |
POST /nav_clear |
{} |
Clear nav history. |
POST /nav_goto |
{ id } |
Jump to a nav-history index. id = position in history stack.
|
POST /nav_set_history |
JSON array | Restore nav history from saved state. Body = array of { id, type } where type is "asset" or "industry", most-recent-first.
|
POST /set_who_owns_filter |
{ value } |
Set the "who owns what" filter. value = filter enum.
|
POST /view_current_interest_rates |
{} |
Show current interest rates. |
POST /whos_ahead |
{} |
Show the leaderboard. |
POST /db_research_tool |
{} |
Open the research database tool. |
POST /economic_stats |
{} |
Show macro stats. |
POST /most_cash_report |
{} |
Most-cash leaderboard. |
POST /largest_market_cap |
{} |
Market-cap leaderboard. |
POST /largest_tax_losses |
{} |
Largest carried-forward tax losses. |
POST /industry_summary |
{} |
Industry summary stats. |
POST /industry_projections |
{} |
Industry growth / decline projections. |
POST /view_corp_assets_for_sale |
{} |
All corporate assets currently for sale. |
Settings / toggles
These all open a settings menu / cycle the toggle. No params except where noted.
| Endpoint | Body | Description |
|---|---|---|
POST /supp_earn_select |
{} |
Suppress earnings-warnings menu. |
POST /currency_select |
{} |
Currency selection. |
POST /supp_warn_select |
{} |
Suppress-warnings menu. |
POST /suppress_select |
{} |
Suppress messages / alerts menu. |
POST /autosave_select |
{} |
Autosave settings. |
POST /exercise_select |
{} |
Auto-exercise options settings. |
POST /sweep_select |
{} |
Cash-sweep settings. |
POST /makedelivery_select |
{} |
Make-delivery settings (commodity/crypto). |
POST /takedelivery_select |
{} |
Take-delivery settings. |
POST /tooltips_select |
{} |
Tooltip enable / disable. |
POST /shareholdergraph_select |
{} |
Shareholder-graph display settings. |
POST /disablehotkeys_select |
{} |
Hotkey enable / disable. |
POST /autoadd_select |
{} |
Auto-add-to-watchlist settings. |
POST /set_chart_type |
{ id } |
Set chart style. id = chart type enum.
|
POST /set_locale |
{ str } |
Set UI locale. str = locale code (e.g. "en-US", "ja-JP").
|
Cheat menu
| Endpoint | Body | Description |
|---|---|---|
POST /cheat_disable |
{} |
Lock cheats off. |
POST /cheat_disable_lawsuits |
{} |
Disable lawsuits entirely. |
POST /cheat_merger_info |
{} |
Reveal hidden merger info. |
POST /cheat_earnings_info |
{} |
Reveal earnings forecasts and hidden financials. |
POST /cheat_add_cash |
{} |
Add a fixed amount of cash. (No "set cash to N" - this is fixed.) |
Modals
| Endpoint | Body | Description |
|---|---|---|
POST /close_modal |
{} |
Close the current modal. |
POST /modal_result |
{ answer } or { str } |
Submit the player's modal response. Use answer for numeric / choice modals; str for text-entry modals.
|
Tutorial
| Endpoint | Body | Description |
|---|---|---|
POST /set_tutorial_step |
{ id } |
Jump tutorial to a step. id = step number.
|
POST /set_tutorial_enabled |
{ id } |
Enable / disable tutorial. id = 1 on, 0 off.
|
Price alerts
| Endpoint | Body | Description |
|---|---|---|
POST /show_price_alerts |
{} |
Open the alerts management dialog. |
POST /create_price_alert |
{ str } |
direction|targetPrice"; direction is "up" or "down".
|
POST /delete_price_alert |
{ id } |
Delete one alert. id = alert slot index.
|
CustomData
| Endpoint | Body | Description |
|---|---|---|
POST /set_custom_data |
JSON object | Shallow-merge mod data into customData. See §CustomData.
|
gameState reference
GET /gamestate returns one JSON object - ~90 top-level fields - that is the complete read model. In a frontend mod, subscribe to only the fields you need so you only re-render on those:
const cash = useGameStore(s => s.gameState.cash); const year = useGameStore(s => s.gameState.currentYear);
| Group | Fields |
|---|---|
| Player | cash, otherAssets, totalAssets, totalDebt, netWorth, playerId, playerName, chairedCompanyId
|
| Clock / session | currentYear, currentQuarter, currentMonth, currentDay, currentTime, nextEarningsDate, gameLoaded, gameOver, readyToRestart, isTickerRunning, tickSpeed, splashScreenPlayed
|
| Active selection | activeEntityNum, activeEntityName, activeEntitySymbol, activeIndustryNum, activeIndustryId, actingAsId, actingAsName, actingAsSymbol, actingAsIndustryId, actingAs
|
| Modal state | modalType, modalText, modalTitle, modalDefault, modalFilter
|
| Collections | allCompanies, allPlayers, allSecurities, allIndustries, controlledCompanies, tickerQueue, streamingQuotesList, newsHeadlines, trendingNews, navHistory, navPointerIndex
|
| Active entity detail | activeEntityData, activeEntityFinancials (deep: cash/assets/debt/equity/EPS/cash-flow/…), activeEntityPlayerFinancials, financialProfile, cashflowProjection, portfolio, portHoldings, swapsPortfolio, optionsList, commodityList, shareholdersList, researchReport, advisorySummary, earningsReport, advances, loansReport, myCorporationsReport
|
| Market reports | whoOwns*Report (futures, physical commodities, options, swaps, stocks, investment contracts), industrySummaryReport, industryProjectionReport, industryGrowthRatesReport, mostMarketShareReport, mostTaxLossReport, mostCashReport, mostMarketCapReport, economicDataReport, interestRatesReport, whosAheadReport
|
| Settings | suppEarnSetting, suppWarnSetting, supPopupSetting, autosaveSetting, exerciseItSetting, sweepSetting, makeDeliverySetting, takeDeliverySetting, tooltipsSetting, shareholderGraphSetting, unethicalSetting, disableHotkeysSetting, chartType, locale
|
| Tutorial / misc | tutorialEnabled, tutorialStep, alerts, frozenAllLoans, dlrSign, euro, customData (§CustomData)
|
The exact shape can change between versions - the authoritative move is to GET /gamestate from a running game. Ctrl+Shift+I opens DevTools; gameStore.getState().gameState in the console dumps the live object.
CustomData - your persistent storage
customData is a free-form JSON object that rides along inside the save file. The engine never reads it for simulation - it stores it verbatim, writes it into the .WSR/.DAT save, restores it on load, and exposes it on every gameState as gameState.customData.
This is the supported way for a mod to persist its own state per-save.
Read
const customData = useGameStore(s => s.gameState?.customData ?? {});
const myStuff = customData.myModKey;
Write
import { setCustomData } from '../api.js';
await setCustomData({ myModKey: { highScore: 42, notes: 'hello' } });
Semantics
- Shallow merge.
POST /set_custom_datamerges into the existingcustomDataat the top level - incoming top-level keys overwrite, others are preserved. Namespace your data under one top-level key (e.g.myModKey) and write that whole sub-object each time. - Round-trips. New value comes back on the next
gameStatebroadcast. - Inert. Cannot change cash, prices, or any engine value - only action endpoints can (§REST API).
- Per-save. Saved/loaded with the game; a fresh New Game starts empty.
Reserved top-level keys
The base UI already stores a few things in customData. Don't reuse these keys:
| Key | Used by | Contents |
|---|---|---|
notes |
Notes modal | free-text notes |
navHistory |
Navigation system | saved nav history |
| (DB search criteria) | Database Search view | saved filters / sort |
| (holdings prefs) | Portfolio Holdings table | saved filters / sort / hide-subsidiaries |
Pick a unique key for your mod (e.g. customData["mymod:state"]).
Limits - what you can't do
- No raw memory / pointer writes. Only curated endpoints. The only free-form write is
customData, and it's inert. - No intercepting the update loop. WS delivers a snapshot. Read and react; you cannot mutate values mid-update or inject into serialization.
- No new engine behavior without recompiling. New mechanic / event type / endpoint = edit PowerBASIC source (
src/main/wsr/*.inc, ~178K lines), rebuildwsr.exe(PB 9.80) and usuallyui.dll(VS C++). That's engine development, not modding. - No editing binary game data. Saves and most
.DAT/.PRMare proprietary binary or encoded.CORPNAME.DATis the one safe exception.
Workflow
Edit / test
- Edit a file under
js/,css/,assets/, orCORPNAME.DAT. - Restart the game (no hot reload).
Ctrl+Shift+Ifor DevTools - console errors, thegameStore, network tab against the REST API.
Packaging for others
A mod is a set of replacement files. Simplest distribution: a zip mirroring the resources/app/ structure:
my-dark-theme/ ├── css/variables.css └── css/theme.css
Install instructions: "drop these into resources/app/, overwriting."
Good practice:
- Keep mods additive where possible - new component files wired in with one import line, not rewrites of core files. Two additive mods are less likely to collide.
- Document which base files you replace.
- Tell users that Steam's "verify integrity of game files" reverts changes (their uninstall path) and to back up originals.