-
Notifications
You must be signed in to change notification settings - Fork 182
Add AI agent guidance, architecture docs, and project skills #7550
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
The-E
wants to merge
11
commits into
scp-fs2open:master
Choose a base branch
from
The-E:feature/agent-guidance
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
2950dc8
adds guidance documents for AI agents
The-E 3ab8a98
adds fso-specific skills
The-E c935466
add more docs
The-E 6c3c0da
add review skill
The-E 22f81a7
make guidance vendor agnostic
The-E 9391e06
add section about core philosophies of the scp
The-E 21d68fd
expand guidance on logging practices and hardware compatibility requi…
The-E 96ec752
fix typo in ci script and update clang-tidy documentation in AGENTS.md
The-E da0e567
Suppress SDL_Init one-time leak reported via os_init in valgrind
The-E efc0f69
address review feedback
The-E cdf145d
update to current master
The-E File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| # FSO Project Skills | ||
|
|
||
| Vendor-agnostic Agent Skills for the FreeSpace 2 Open engine. Each skill is | ||
| a directory containing a `SKILL.md` that encodes an FSO-specific workflow | ||
| (conventions, the exact files/functions to touch, and how to verify). Skills | ||
| auto-trigger from natural requests; you can also invoke one explicitly by name. | ||
|
|
||
| This directory (`.agents/skills/`) is the single source of truth. Vendor skill | ||
| directories contain thin pointer stubs back to it (see "Cross-agent compatibility" | ||
| below). | ||
|
|
||
| ## Available skills | ||
|
|
||
| | Skill | Use it when you want to… | Key code | Module guide | | ||
| | --- | --- | --- | --- | | ||
| | `fso-add-table-field` | Add/modify a parsed option in a `.tbl`/`.tbm` (ships, weapons, ai_profiles, game_settings, hud_gauges) and wire it into an `*_info` struct | `code/parse/parselo.*` + the owning module's parser | `documentation/modules/parse.md` | | ||
| | `fso-add-sexp` | Add a new SEXP operator (mission-scripting function): `OP_*`, `Operators` table, arg typing, `eval_sexp`, help text | `code/parse/sexp.{h,cpp}` | `documentation/modules/parse.md` | | ||
| | `fso-add-lua-api` | Expose engine data/functions to Lua via ADE bindings, or add a `scripting::Hook` mods can subscribe to | `code/scripting/` (`api/`, `global_hooks.*`) | `documentation/modules/scripting.md` | | ||
| | `fso-add-object-type` | **Last resort** — introduce a new in-world `OBJ_*` entity with create/move/delete + collision + render; only when no existing object type (ship/weapon/debris/asteroid/etc.) can represent it | `code/object/object.{h,cpp}` | `documentation/modules/object.md` | | ||
| | `fso-add-hud-gauge` | Add a new built-in HUD gauge (`HudGauge` subclass + `HUD_OBJECT_*` + `hud_gauges.tbl` parsing) | `code/hud/hud.h`, `code/hud/hudparse.*` | `documentation/modules/hud.md` | | ||
| | `fso-build-and-test` | Configure/build with CMake+Ninja, run `unittests`, and check clang-format/clang-tidy compliance, mirroring CI | `CMakeLists.txt`, `ci/linux/*` | root `AGENTS.md` | | ||
|
|
||
| ## Review skills | ||
|
|
||
| | Skill | Use it when you want to… | Invocation | | ||
| | --- | --- | --- | | ||
| | `thermo-nuclear-code-quality-review` | Run an extremely strict maintainability/abstraction audit of the current branch's changes (file-size growth, spaghetti conditionals, missed "code-judo" simplifications) | Explicit only (`disable-model-invocation`); ask for a "thermo-nuclear review" | | ||
|
|
||
| ## Conventions shared by all skills | ||
|
|
||
| - Builds use `FSO_FATAL_WARNINGS=ON` (CI default) — keep changes warning-clean. | ||
| - Code must compile on GCC, Clang, and MSVC; isolate platform code under | ||
| `code/osapi/`, `code/windows_stub/`, or platform guards. | ||
| - New parsed options use `optional_string` (never `required_string`) to preserve | ||
| backward compatibility with existing tables — unless nested inside a block | ||
| that's itself only entered after a new `optional_string` was matched (e.g. | ||
| sub-fields of a new optional block), where `required_string` is fine since | ||
| the whole block is already optional. | ||
| - Author-facing ship/weapon/SEXP changes may also need FRED updates (`fred2/`, `qtfred/`). | ||
| - After any change, validate with the `fso-build-and-test` skill. | ||
|
|
||
| ## How skills load | ||
|
|
||
| - **Automatic:** the agent matches a request to a skill's `description` and reads | ||
| its `SKILL.md` before acting. | ||
| - **Explicit:** reference the skill by name (e.g. "use `fso-add-sexp`"). | ||
|
|
||
| ## Cross-agent compatibility | ||
|
|
||
| The skills here live in a vendor-agnostic folder. Each vendor reads its own skills | ||
| directory, so the per-vendor directories carry thin pointer stubs (plain files, not | ||
| symlinks — symlinks are avoided for Windows checkout compatibility). | ||
|
|
||
| | Path | Agent | Kind | | ||
| | --- | --- | --- | | ||
| | `.agents/skills/` | opencode (native), shared canonical | full playbooks | | ||
| | `.cursor/skills/` | Cursor | pointer stubs → `.agents/skills` | | ||
| | `.claude/skills/` | Claude Code, opencode | pointer stubs → `.agents/skills` | | ||
|
|
||
| Project guidance (build/style/test conventions) is shared via the root `AGENTS.md`, | ||
| read natively by Cursor and opencode; Claude Code reads it through the root | ||
| `CLAUDE.md`, which imports `AGENTS.md`. | ||
|
|
||
| Each stub at `.cursor/skills/<name>/SKILL.md` and `.claude/skills/<name>/SKILL.md` | ||
| carries the discovery frontmatter (`name` + `description`) and a body that points | ||
| to the canonical playbook here. Edit the playbook here in `.agents/skills/`; if you | ||
| change a skill's `name`/`description`, mirror that line into the matching stubs so | ||
| triggering stays in sync. The Cursor stub for `thermo-nuclear-code-quality-review` | ||
| also keeps `disable-model-invocation: true` (a Cursor-only field) to preserve its | ||
| explicit-only behavior. | ||
|
|
||
| ## Authoring more skills | ||
|
|
||
| Use the built-in `create-skill` workflow. Place new project skills here | ||
| (`.agents/skills/<name>/SKILL.md`); keep descriptions third-person with clear | ||
| WHAT + WHEN trigger terms, and link one level deep to the relevant | ||
| `documentation/modules/*.md`. Then add a matching pointer stub under | ||
| `.cursor/skills/<name>/SKILL.md` and `.claude/skills/<name>/SKILL.md`. | ||
|
|
||
| ## Related documentation | ||
|
|
||
| - Engine architecture overview: `documentation/ARCHITECTURE.md` | ||
| - Per-module entry-point guides: `documentation/modules/` | ||
| - Build/style/test conventions: root `AGENTS.md` | ||
| - Table-option reference (wiki): https://wiki.hard-light.net/index.php/Tables | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| --- | ||
| name: fso-add-hud-gauge | ||
| description: >- | ||
| Add a new HUD gauge to FSO. Use when creating a HUD element/gauge, subclassing | ||
| HudGauge, adding a HUD_OBJECT_ type, or wiring a gauge into hud_gauges.tbl | ||
| parsing and the per-frame HUD render path. | ||
| --- | ||
|
|
||
| # FSO: Add a HUD Gauge | ||
|
|
||
| HUD gauges live in `code/hud/`. Each gauge is a subclass of `HudGauge` | ||
| (`code/hud/hud.h`, ~line 214) with a `render()` method; gauges are instantiated | ||
| from `hud_gauges.tbl` via `code/hud/hudparse.cpp`. Decide first which path you need. | ||
|
|
||
| ## Choose an approach | ||
|
|
||
| - **Mod-authored custom gauge** → usually no engine change: mission/mod authors use | ||
| the `+Scripted Gauge:` table entry (`HUD_OBJECT_SCRIPTING`, backed by | ||
| `HudGaugeScripting` in `code/hud/hudscripting.{h,cpp}`), whose `render()` calls | ||
| straight into a Lua-registered render function — the fully Lua-rendered path. | ||
| The older `HUD_OBJECT_CUSTOM` (`code/scripting/api/objs/hudgauge.cpp`) only | ||
| exposes Name/Text and general Lua HUD-draw hooks. Point the user to the | ||
| `fso-add-lua-api` skill + `hud_gauges.tbl` if either of these suffices. | ||
| - **New built-in gauge type** → follow the steps below. | ||
|
|
||
| ## Steps (new built-in gauge) | ||
|
|
||
| 1. **Subclass `HudGauge`.** Create `hudmygauge.h`/`.cpp` (or add to a related | ||
| existing gauge file). Implement the constructor (gauge type, default position, | ||
| colors) and override `render(float frametime)` using `gr_*` / `renderString` / | ||
| `renderBitmap` helpers from the base class. | ||
|
|
||
| 2. **Add a gauge type id.** Add `#define HUD_OBJECT_MY_GAUGE NN` in | ||
| `code/hud/hudparse.h` (the `HUD_OBJECT_*` list). If the gauge belongs in the | ||
| legacy default set, also add it to `Legacy_HUD_gauges[]` and bump | ||
| `NUM_HUD_GAUGES` (`code/hud/hudgauges.h`); newer gauges (e.g. the scripting | ||
| gauge) are explicitly excluded from that legacy list and don't need it. | ||
|
|
||
| 3. **Parse + load.** In `code/hud/hudparse.cpp`: | ||
| - Add a `load_gauge_my_gauge(...)` that reads gauge settings (coords, font, | ||
| color, frames) and constructs your gauge, registering it on the ship/HUD. | ||
| - Add a `case HUD_OBJECT_MY_GAUGE:` to the load dispatch (the switch around | ||
| line 1184) calling `load_gauge_my_gauge(settings)`. | ||
| - Map the gauge's table name string to the new id where gauge names are parsed. | ||
|
|
||
| 4. **Render path.** Gauges added to the HUD list are rendered automatically each | ||
| frame; confirm your gauge is added to the per-ship gauge list so it draws. | ||
|
|
||
| 5. **Defaults.** Provide retail-style default coordinates for 640 and 1024 layouts | ||
| like the existing gauges, so it works without an explicit `hud_gauges.tbl` entry. | ||
|
|
||
| ## Conventions | ||
|
|
||
| - Reuse base-class draw helpers; don't call backend `gr_*` for text/bitmaps if a | ||
| `HudGauge::render*` helper exists. | ||
| - Respect HUD config (gauge can be toggled); check the gauge's active/visible state. | ||
| - Keep warning-clean (`FSO_FATAL_WARNINGS=ON`). | ||
|
|
||
| ## Verify | ||
|
|
||
| - Build (see `fso-build-and-test`), start a mission, confirm the gauge renders at | ||
| the right place, scales across resolutions, and obeys HUD config show/hide. | ||
| - Test a `hud_gauges.tbl` entry that positions/recolors it. | ||
|
|
||
| ## Reference | ||
|
|
||
| - `code/hud/hud.h` (`HudGauge`), `code/hud/hudparse.{h,cpp}`, `documentation/modules/hud.md`. | ||
| - hud_gauges.tbl docs: https://wiki.hard-light.net/index.php/Hud_gauges.tbl |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| --- | ||
| name: fso-add-lua-api | ||
| description: >- | ||
| Expose engine functionality to Lua scripting in FSO via ADE bindings or add a | ||
| new scripting hook. Use when adding a Lua API object/library/function/property, | ||
| writing ADE_OBJ/ADE_FUNC/ADE_VIRTVAR/ADE_LIB bindings, or creating a | ||
| scripting::Hook/OverridableHook that mods can subscribe to. | ||
| --- | ||
|
|
||
| # FSO: Add a Lua/ADE Binding or Hook | ||
|
|
||
| FSO's Lua layer (ADE) lives in `code/scripting/`. There are two common tasks: | ||
| **A)** expose data/functions to Lua, and **B)** fire an engine event mods can hook. | ||
| (For mission-designer scripting use the `fso-add-sexp` skill instead.) | ||
|
|
||
| ## A) Add a Lua binding (ADE) | ||
|
|
||
| Bound **objects** live in `code/scripting/api/objs/`, **libraries** in | ||
| `code/scripting/api/libs/`. Copy an existing file (e.g. `objs/wing.cpp`) as a model. | ||
|
|
||
| - **Define an object type:** | ||
| ```cpp | ||
| ADE_OBJ(l_MyThing, my_thing_h, "mything", "My thing handle"); | ||
| ``` | ||
| - **Add a method:** | ||
| ```cpp | ||
| ADE_FUNC(doStuff, l_MyThing, "number amount", | ||
| "Does stuff to the thing.", "boolean", "true on success") | ||
| { /* parse args with ade_get_args, act, return with ade_set_args */ } | ||
| ``` | ||
| - **Add a gettable/settable property:** | ||
| ```cpp | ||
| ADE_VIRTVAR(Name, l_MyThing, "string", "The name.", "string", "name or empty") | ||
| { /* ... */ } | ||
| ``` | ||
| - **Add to a library:** use `ADE_LIB` / `ADE_LIB_DERIV` and attach functions. | ||
|
|
||
| Argument marshalling uses `ade_get_args(L, "...", ...)` and | ||
| `ade_set_args(L, "...", ...)`; format strings are documented in | ||
| `code/scripting/ade_args.h`. Match the documented type string in the macro to the | ||
| actual returned type, since the Lua API docs are generated from these. | ||
|
|
||
| ## B) Add a scripting hook | ||
|
|
||
| 1. Declare the hook in `code/scripting/global_hooks.h` and define it in | ||
| `global_hooks.cpp`, choosing `scripting::Hook<...>` (non-overridable) or | ||
| `scripting::OverridableHook<...>` (script can replace default behaviour). | ||
| 2. Define any condition/parameter types via the hook's template args (see | ||
| `hook_conditions.h` / `hook_api.h`). | ||
| 3. **Fire it** from the relevant subsystem at the right point: | ||
| ```cpp | ||
| if (scripting::hooks::OnMyEvent->isActive()) { | ||
| scripting::hooks::OnMyEvent->run(scripting::hook_param_list(/* params */)); | ||
| } | ||
| ``` | ||
| For overridable hooks, check `isOverride(...)` to let scripts suppress default logic. | ||
|
|
||
| ## Conventions | ||
|
|
||
| - Keep binding docs accurate — they are the generated Lua API reference (`doc_*`). | ||
| - Register script files/tables via `scripting.tbl` (`script_parse_table`). | ||
| - Keep warning-clean (`FSO_FATAL_WARNINGS=ON`). | ||
|
|
||
| ## Verify | ||
|
|
||
| - Build (see `fso-build-and-test`). | ||
| - Test from a Lua script (a `scripting.tbl` `$GameInit:` block — the "On Game | ||
| Init" hook's table tag — or another hook) that calls the new API / responds | ||
| to the new hook. | ||
|
|
||
| ## Reference | ||
|
|
||
| - `code/scripting/ade_api.h`, `code/scripting/ade_args.h`, `code/scripting/hook_api.h`. | ||
| - `documentation/modules/scripting.md`. | ||
| - Lua API wiki: https://wiki.hard-light.net/index.php/Scripting |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| --- | ||
| name: fso-add-object-type | ||
| description: >- | ||
| Add a new in-world object type to the FSO object system (alongside ships, | ||
| weapons, debris, asteroids, etc.). Use when introducing a new OBJ_ type, a new | ||
| kind of simulated entity, or wiring a new entity into obj_create/obj_move_all | ||
| and the collision system. | ||
| --- | ||
|
|
||
| # FSO: Add a New Object Type | ||
|
|
||
| Every world entity is an `object` (`code/object/object.h`) whose `type` is an | ||
| `OBJ_*` constant and whose `instance` indexes a type-specific array. Adding a new | ||
| type means defining that array + the create/move/delete trio and registering it | ||
| with the object system. | ||
|
|
||
| This is a last resort: adding a new `OBJ_*` type touches `MAX_OBJECT_TYPES`, | ||
| every switch over object type, and the collision matrix. Prefer extending an | ||
| existing type (ship subsystem, weapon subtype, etc.) if it can represent the | ||
| new entity at all. | ||
|
|
||
| ## Checklist | ||
|
|
||
| ``` | ||
| - [ ] 1. Define OBJ_MY_TYPE + bump MAX_OBJECT_TYPES (object.h) | ||
| - [ ] 2. Add the name to Object_type_names[] (object.cpp) | ||
| - [ ] 3. Create the instance storage + *_info if data-driven (info-vs-instance) | ||
| - [ ] 4. Implement my_create() (wraps obj_create), my_move(), my_delete() | ||
| - [ ] 5. Hook my_move into obj_move_all_pre/post dispatch | ||
| - [ ] 6. Hook deletion into obj_delete_all_that_should_be_dead path | ||
| - [ ] 7. Add collision handling if it collides (object/objcollide + collide*.cpp) | ||
| - [ ] 8. Add rendering in the object render dispatch | ||
| ``` | ||
|
|
||
| ## Steps | ||
|
|
||
| 1. **Type constant** — add `#define OBJ_MY_TYPE NN` in `object.h` and increase | ||
| `MAX_OBJECT_TYPES`. Add the matching string to `Object_type_names[]` in | ||
| `object.cpp` (order must match). | ||
|
|
||
| 2. **Storage** — follow the **info-vs-instance** pattern used by ships/weapons: | ||
| a `my_type_info` class table (parsed from a `.tbl`, see `fso-add-table-field`) | ||
| if the type is data-driven, plus a `my_type` instance array or `SCP_vector`. | ||
|
|
||
| 3. **Create** — write `int my_create(...)` that calls | ||
| `obj_create(OBJ_MY_TYPE, parent, instance, &orient, &pos, radius, flags)` and | ||
| initializes the instance. Set `Collides` flag only if it participates in collisions. | ||
|
|
||
| 4. **Move** — write `my_move(object *objp, float frametime)` and call it from the | ||
| per-type dispatch inside `obj_move_all_pre()` / `obj_move_all_post()` | ||
| (`object.cpp`). Physics runs via `obj_move_call_physics()` if `phys_info` is used. | ||
|
|
||
| 5. **Delete** — to destroy, set the `Should_be_dead` flag. Add a `case OBJ_MY_TYPE:` | ||
| in `obj_delete_all_that_should_be_dead()` (or the delete dispatch) to free | ||
| instance data, then `obj_delete()`. | ||
|
|
||
| 6. **Collisions** (optional) — register pairs in `code/object/objcollide.cpp` and | ||
| add a `collide_my_type_*` handler (model the existing `collide*.cpp` files). | ||
|
|
||
| 7. **Rendering** — add a `case OBJ_MY_TYPE:` to the object render dispatch | ||
| (`obj_render` / `obj_queue_render` in `object.cpp`) that queues your draw call. | ||
|
|
||
| ## Conventions | ||
|
|
||
| - Use `object_h`/signatures for cross-frame references, not raw objnums. | ||
| - Change flags via `obj_set_flags()` so collision-pair state stays consistent. | ||
| - Respect `MAX_OBJECTS` (must stay < 2^16-1 for collision-pair caching). | ||
| - Keep warning-clean (`FSO_FATAL_WARNINGS=ON`). | ||
|
|
||
| ## Verify | ||
|
|
||
| - Build (see `fso-build-and-test`), spawn the entity (via code, SEXP, or Lab), | ||
| confirm it moves, renders, collides, and cleans up without asserts. | ||
|
|
||
| ## Reference | ||
|
|
||
| - `code/object/object.h` / `object.cpp`, `documentation/modules/object.md`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| --- | ||
| name: fso-add-sexp | ||
| description: >- | ||
| Add a new SEXP operator (mission-scripting function) to FSO. Use when creating | ||
| a new SEXP, adding a mission event/goal operator, registering an OP_ identifier, | ||
| or wiring a sexp into eval_sexp. Covers sexp.h enum, the Operators table, | ||
| argument typing, evaluation, and help text in code/parse/sexp.cpp. | ||
| --- | ||
|
|
||
| # FSO: Add a SEXP Operator | ||
|
|
||
| SEXPs are the mission designer's scripting language. Adding one touches a fixed | ||
| set of locations in `code/parse/sexp.h` and `code/parse/sexp.cpp`. Search an | ||
| existing similar operator name across both files to find every spot to edit. | ||
|
|
||
| ## Checklist | ||
|
|
||
| ``` | ||
| - [ ] 1. Declare OP_* identifier (sexp.h) | ||
| - [ ] 2. Register in the Operators table (sexp.cpp ~line 146) | ||
| - [ ] 3. Add argument type-checking in query_operator_argument_type() (sexp.cpp ~line 32126) | ||
| - [ ] 4. Implement the handler + dispatch in eval_sexp() (sexp.cpp ~line 28169) | ||
| - [ ] 5. Add help text to Sexp_help (sexp.cpp ~line 38004) | ||
| - [ ] 6. Add the FRED submenu category in get_category()/get_subcategory() (sexp.cpp ~line 36656) | ||
| - [ ] 7. (If multiplayer-relevant) handle packing in network/multi_sexp.cpp | ||
| ``` | ||
|
|
||
| ## Steps | ||
|
|
||
| 1. **OP_ identifier** — add to the operator enum in `code/parse/sexp.h` | ||
| (the block starting `OP_PLUS = FIRST_OP, ...`). Do not reuse a value. | ||
|
|
||
| 2. **Register the operator** in the `Operators` vector (`sexp.cpp`, ~line 146). | ||
| Entry format is `{ text, OP_id, min_args, max_args, type }`: | ||
|
|
||
| ```cpp | ||
| { "my-operator", OP_MY_OPERATOR, 1, 2, SEXP_ACTION_OPERATOR, }, | ||
| ``` | ||
| The 5th field (`sexp_oper_type`, the `SEXP_*_OPERATOR` macros defined near | ||
| `sexp.cpp` ~line 126) classifies the operator's *kind* | ||
| (action/conditional/arithmetic/boolean/…) — it does **not** control the FRED | ||
| submenu; that's step 6. | ||
|
|
||
| 3. **Argument typing** — add a `case OP_MY_OPERATOR:` to the big switch in | ||
| `query_operator_argument_type()` (`sexp.cpp`, ~line 32126), declaring what | ||
| each argument slot expects using the `OPF_*` enums (`OPF_NUMBER`, `OPF_SHIP`, …). | ||
|
|
||
| 4. **Implement + dispatch** — write a handler function, then add a `case OP_MY_OPERATOR:` | ||
| in `eval_sexp()` (`sexp.cpp`, ~line 28169) that calls it and returns a SEXP | ||
| result (`SEXP_TRUE`/`SEXP_FALSE`/`SEXP_KNOWN_*`, or a number for arithmetic ops). | ||
| Read arguments via the `CDR`/`CADR` node walk like neighbouring cases. | ||
|
|
||
| 5. **Help text** — add an entry to the `Sexp_help` vector (`sexp.cpp`, ~line 38004): | ||
|
|
||
| ```cpp | ||
| { OP_MY_OPERATOR, "my-operator\r\n" | ||
| "\tWhat it does.\r\n\r\n" | ||
| "Takes 1 or 2 arguments...\r\n" | ||
| "\t1:\tFirst argument.\r\n" | ||
| "\t2:\t(optional) Second argument." }, | ||
| ``` | ||
|
|
||
| 6. **FRED submenu category** — add a `case OP_MY_OPERATOR:` to `get_category()` | ||
| (`sexp.cpp`, ~line 36656) so the operator appears under the right FRED | ||
| category; add it to `get_subcategory()` (~line 37380) too if the category | ||
| has subcategories. | ||
|
|
||
| 7. **Multiplayer** — if the operator changes game state on the server, ensure it | ||
| packs/sends correctly via `code/network/multi_sexp.*`. | ||
|
|
||
| ## Conventions | ||
|
|
||
| - Operator names are lowercase-hyphenated and must be ≤ `OPERATOR_LENGTH` (30). | ||
| - Prefer adding self-contained logic; large new systems may live under `code/parse/sexp/`. | ||
| - Keep warning-clean (`FSO_FATAL_WARNINGS=ON`). | ||
|
|
||
| ## Verify | ||
|
|
||
| - Build (see `fso-build-and-test`), open FRED/qtFRED, confirm the operator appears | ||
| in the right category with correct arg constraints, and test it in a mission. | ||
|
|
||
| ## Reference | ||
|
|
||
| - `code/parse/sexp.h` — `OP_*` enum, `OPF_*` arg types, `OP_CATEGORY_*`, return codes. | ||
| - `documentation/modules/parse.md`. | ||
| - SEXP list: https://wiki.hard-light.net/index.php/SEXP |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"unless gated as suboptions only passed if a new optional_string is hit"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added.