From 4e788b3c527b4b63064944c178af1bd2f341a8fa Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Mon, 22 Sep 2025 18:11:00 -0500 Subject: [PATCH 01/73] long project will be long --- SEXP_TREE_PLAN.md | 283 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100644 SEXP_TREE_PLAN.md diff --git a/SEXP_TREE_PLAN.md b/SEXP_TREE_PLAN.md new file mode 100644 index 00000000000..db05c746c11 --- /dev/null +++ b/SEXP_TREE_PLAN.md @@ -0,0 +1,283 @@ +# Sexp Tree Model/UI Separation Plan + +## Problem + +FRED2's `sexp_tree.cpp` (8,288 lines) and QtFRED's `sexp_tree.cpp` (8,027 lines) are near-identical implementations of the same logic, duplicated across two UI frameworks. Both mix pure model logic with UI-specific code. New features/fixes must be applied to both files. + +## Goal + +Extract all UI-independent model logic into shared files under `code/missioneditor/`, leaving FRED2 and QtFRED as thin UI wrappers that delegate to the shared model. + +## Architecture: Three Shared Files + +### 1. `code/missioneditor/sexp_tree_model.h/.cpp` — Core Tree Model + +The main model class `SexpTreeModel` owns the tree node data and all pure-logic operations. + +**Data structures (moved from both headers):** +- `sexp_tree_item` — node struct, but with `void* handle` instead of `HTREEITEM`/`QTreeWidgetItem*` (UI layers cast as needed) +- `sexp_list_item` — linked list for option listings (already UI-independent) +- Shared constants: `SEXPT_*` type/flag defines, `BITMAP_*`/`NodeImage` enum (unified into a single `NodeImage` enum) +- `TreeFlags` flagset (already exists in QtFRED, would be shared) + +**SexpTreeModel class — pure model operations (no UI calls):** + +*Tree node management:* +- `tree_nodes` vector, `total_nodes`, `root_item`, `item_index` +- `allocate_node()` (index-only overload), `find_free_node()`, `set_node()`, `free_node2()` +- `clear_nodes()` — resets node array only (UI layer calls this then clears its widget) + +*Tree serialization (works with global Sexp_nodes array):* +- `save_tree()`, `save_branch()` +- `load_sub_tree()` — builds node data without inserting UI items + +*Query/analysis (pure computation on tree_nodes):* +- `count_args()`, `identify_arg_type()` +- `find_argument_number()`, `find_ancestral_argument_number()` +- `query_node_argument_type()`, `query_restricted_opf_range()` +- `get_default_value()`, `query_default_argument_available()` +- `get_data_image()` — returns `NodeImage` enum +- `get_sibling_place()` +- `query_false()` +- `match_closest_operator()` +- `verify_and_fix_arguments()` +- `is_node_eligible_for_special_argument()` + +*Variable/container utilities:* +- `delete_sexp_tree_variable()`, `modify_sexp_tree_variable()` +- `get_item_index_to_var_index()`, `get_tree_name_to_sexp_variable_index()` +- `get_modify_variable_type()`, `get_variable_count()`, `get_loadout_variable_count()` +- `get_container_usage_count()`, `rename_container_nodes()` +- `is_matching_container_node()`, `is_container_name_argument()` +- `is_container_name_opf_type()` (static) + +*Help text:* +- `help()` (static) — returns help string for an operator code + +*Tree search (model portion):* +- `find_text_in_nodes()` — searches `tree_nodes[]` text, returns node index (UI layer handles highlighting/scrolling) + +**Context interface (`SexpTreeEditorInterface`):** +Moved here from QtFRED. Both FRED2 and QtFRED dialogs implement this interface. This provides the contextual data that OPF functions and menu logic need: +- `getMessages()`, `hasDefaultMessageParameter()` +- `getMissionGoals()`, `hasDefaultGoal()` +- `getMissionEvents()`, `hasDefaultEvent()` +- `getMissionNames()`, `hasDefaultMissionName()` +- `getRootReturnType()` +- `requireCampaignOperators()` +- `getFlags()` — returns `TreeFlags` flagset + +Note: The Qt-specific `getContextMenuExtras()` would NOT move here — that stays in QtFRED's UI layer. + +### 2. `code/missioneditor/sexp_tree_opf.h/.cpp` — OPF Listings + +**All ~70+ `get_listing_opf_*()` functions.** These are the functions that get added to most frequently and are 100% model logic — they query game data arrays (Ships[], Wings[], Ai_info[], weapon tables, etc.) and build `sexp_list_item` linked lists. + +**Functions:** +- `get_listing_opf()` — main dispatcher (giant switch on OPF type) +- `get_listing_opf_bool()`, `get_listing_opf_ship()`, `get_listing_opf_wing()`, ... (all 70+) +- `get_container_modifiers()`, `get_list_container_modifiers()`, `get_map_container_modifiers()` +- `check_for_dynamic_sexp_enum()` + +**Design consideration:** These functions currently access `tree_nodes[]` (e.g., `get_listing_opf_bool` checks parent node type, `get_listing_opf_subsystem` looks up parent ship). They also use `_interface` for context (messages, goals, events, mission names). So these are methods on `SexpTreeModel` (or could be free functions that take a `SexpTreeModel&` reference). Keeping them as methods is simpler. + +**Why separate file:** Contributors frequently add new OPF types. Having them isolated means: +- Easy to find where to add new types +- Merge conflicts are localized +- The main model file stays manageable + +### 3. `code/missioneditor/sexp_tree_actions.h/.cpp` — Action Logic + +A **separate class** `SexpTreeActions`, constructed with `SexpTreeModel&` + `ISexpTreeUI&`. This handles the "what to do" when a user triggers a menu action, separated from the "how to show the menu" (UI) and "how to render the result" (UI). + +```cpp +class SexpTreeActions { +public: + SexpTreeActions(SexpTreeModel& model, ISexpTreeUI& ui); + + // Menu structure computation + SexpMenuItems compute_menu_items(int selected_node); + + // Action execution + void add_or_replace_operator(int op, int replace_flag = 0); + void replace_operator(const char* op); + void replace_data(const char* data, int type); + void replace_variable_data(int var_idx, int type); + void replace_container_name(const sexp_container& container); + void replace_container_data(const sexp_container& container, int type, + bool test_child_nodes, bool delete_child_nodes, + bool set_default_modifier); + void add_default_modifier(const sexp_container& container); + void expand_operator(int node); + void merge_operator(int node); + void add_default_operator(int op, int argnum); + + // Clipboard operations + void node_cut(); + void node_copy(); + void node_paste(bool replace); + void node_delete(); + +private: + SexpTreeModel& _model; + ISexpTreeUI& _ui; +}; +``` + +**`SexpMenuItems` data structure** — returned by `compute_menu_items()`, describes what menu items should be available: +- Which operator categories to show for add/replace/insert +- Which variables are valid replacements +- Which container names/data are valid +- Whether cut/copy/paste/delete are enabled +- Whether edit-data, edit-comment, edit-color are enabled +- Replaces the bulk of `right_clicked()` (FRED2) / `buildContextMenu()` (QtFRED) logic + +**`ISexpTreeUI` callback interface** — defined in `sexp_tree_model.h`, implemented by both UI layers: +```cpp +class ISexpTreeUI { +public: + virtual ~ISexpTreeUI() = default; + + // Tree widget manipulation + virtual void* ui_insert_item(const char* text, NodeImage image, + void* parent, void* insert_after) = 0; + virtual void ui_delete_item(void* handle) = 0; + virtual void ui_set_item_text(void* handle, const char* text) = 0; + virtual void ui_set_item_image(void* handle, NodeImage image) = 0; + virtual void* ui_get_parent_item(void* handle) = 0; + virtual void* ui_get_child_item(void* handle) = 0; + virtual void* ui_get_next_item(void* handle) = 0; + virtual void ui_ensure_visible(void* handle) = 0; + virtual void ui_select_item(void* handle) = 0; + virtual void ui_expand_item(void* handle) = 0; + + // Notifications + virtual void ui_notify_modified() = 0; + virtual void ui_show_node_error(int node, const char* msg) = 0; +}; +``` + +FRED2 implements this with MFC calls (`InsertItem`, `DeleteItem`, etc.). +QtFRED implements this with Qt calls (`addTopLevelItem`, `removeChild`, etc.). + +UI layers own both a `SexpTreeModel` and a `SexpTreeActions`: +```cpp +// QtFRED example +class sexp_tree : public QTreeWidget, public ISexpTreeUI { + SexpTreeModel _model; + SexpTreeActions _actions; // constructed with _model + *this + // ... +}; +``` + +### What Stays in the UI Layers + +**FRED2 `fred2/sexp_tree.h/.cpp`:** +- `class sexp_tree : public CTreeCtrl, public ISexpTreeUI` +- Owns a `SexpTreeModel` (composition, not inheritance) +- MFC message map handlers: `OnBegindrag`, `OnMouseMove`, `OnLButtonUp`, `OnKeyDown`, etc. +- `right_clicked()` — calls model's `compute_menu_items()`, builds MFC `CMenu`, calls `TrackPopupMenu()` +- `OnCommand()` — dispatches MFC command IDs to model action functions +- `build_tree()` — iterates model nodes, inserts MFC tree items +- `load_tree()`/`load_branch()` — calls model load, then syncs UI +- `edit_label()`, `end_label_edit()` — MFC inline editing +- `start_operator_edit()`, `end_operator_edit()` — MFC combo box +- `OperatorComboBox` — MFC-specific widget +- Drag-and-drop event handling +- `ISexpTreeUI` implementation (MFC calls) + +**QtFRED `qtfred/src/ui/widgets/sexp_tree.h/.cpp`:** +- `class sexp_tree : public QTreeWidget, public ISexpTreeUI` +- Owns a `SexpTreeModel` (composition) +- Qt event overrides: `keyPressEvent`, `mousePressEvent`, `mouseMoveEvent`, etc. +- `buildContextMenu()` — calls model's `compute_menu_items()`, builds `QMenu` with `QAction`s +- `customMenuHandler()` — Qt context menu slot +- `handleNewItemSelected()` — Qt selection change +- Operator quick-search popup (`_opPopup`, `_opEdit`, `_opList`) +- Qt signals for `modified()`, `rootNodeDeleted()`, etc. +- `NoteBadgeDelegate` — Qt custom painting +- `ISexpTreeUI` implementation (Qt calls) + +## Node Handle Strategy + +`sexp_tree_item::handle` becomes `void*` in the shared model. UI layers cast: +- FRED2: `static_cast(node.handle)` / `reinterpret_cast(htreeitem)` +- QtFRED: `static_cast(node.handle)` / `static_cast(qtreewidgetitem)` + +This is pragmatic — these are opaque pointers the model never dereferences. The model stores them; only the UI layer reads/writes them through `ISexpTreeUI`. + +## Tree Coloring + +Node coloring determination is model logic (which color to assign based on node state/type) — goes in `sexp_tree_model`. The actual rendering (setting background brush, calling `setBackground()` or `SetItemColor()`) stays in the UI layer. + +The model provides: +```cpp +// Returns the appropriate color for a node based on its state +QColor get_node_color(int node) const; // or use a framework-agnostic color struct +``` + +Or simpler: define a `NodeColorState` enum in the model and let UI map it to actual colors. + +## Tree Searching + +`find_text()` splits: +- **Model** (`sexp_tree_model`): `find_text_in_nodes(const char* text, int* find)` — walks `tree_nodes[]`, returns the node index of the match +- **UI**: calls model search, then does `ensure_visible()` + `hilite_item()` on the returned node + +## Migration Strategy (Incremental) + +This is too large for a single PR. Suggested order: + +### Phase 1: Foundation +1. Create `sexp_tree_model.h/.cpp` with data structures (`sexp_tree_item` with `void*` handle, `sexp_list_item`, `NodeImage` enum, `TreeFlags`, `SexpTreeEditorInterface`) +2. Create empty `sexp_tree_opf.h/.cpp` and `sexp_tree_actions.h/.cpp` +3. Add all files to `code/source_groups.cmake` +4. Both FRED2 and QtFRED include the new headers but don't use them yet + +### Phase 2: OPF Functions (biggest bang for buck) +5. Move all `get_listing_opf_*()` implementations to `sexp_tree_opf.cpp` +6. Both UI sexp_tree classes call the shared implementations +7. Delete duplicate code from both `sexp_tree.cpp` files + - This alone removes ~4,000 lines of duplication + +### Phase 3: Pure Model Functions +8. Move pure query/analysis functions to `sexp_tree_model.cpp` +9. Move save_tree/save_branch to model +10. Move variable/container utilities to model +11. UI classes delegate to model + +### Phase 4: Actions + ISexpTreeUI +12. Define `ISexpTreeUI` interface +13. Implement in both FRED2 and QtFRED +14. Move action functions to `sexp_tree_actions.cpp` +15. Move mixed functions (load_tree, expand_operator, etc.) using the UI callback interface +16. Move menu computation logic + +### Phase 5: Cleanup +17. Remove remaining duplication +18. Both UI `sexp_tree.cpp` files should be ~500-1000 lines each (down from ~8,000) + +## File Size Estimates (Final State) + +| File | Lines | Content | +|------|-------|---------| +| `code/missioneditor/sexp_tree_model.h` | ~200 | Data structures, model class declaration, ISexpTreeUI, SexpTreeEditorInterface | +| `code/missioneditor/sexp_tree_model.cpp` | ~1,500 | Core model logic, tree management, queries, variables, help | +| `code/missioneditor/sexp_tree_opf.h` | ~10 | Just the header (declarations are in model class) | +| `code/missioneditor/sexp_tree_opf.cpp` | ~4,000 | All OPF listing functions | +| `code/missioneditor/sexp_tree_actions.h` | ~100 | SexpTreeActions class, SexpMenuItems data structure | +| `code/missioneditor/sexp_tree_actions.cpp` | ~1,500 | Menu computation, action execution | +| `fred2/sexp_tree.cpp` | ~800 | MFC UI wrapper | +| `qtfred/sexp_tree.cpp` | ~800 | Qt UI wrapper | + +## Resolved Design Decisions + +1. **Composition for model.** UI classes (`sexp_tree`) own a `SexpTreeModel` via composition, not inheritance. Cleaner separation even though it requires forwarding. + +2. **SCP types in the model.** `SexpTreeEditorInterface` uses `SCP_vector` instead of `QStringList`/`QString`. The Qt-specific `getContextMenuExtras(QObject*)` stays in a QtFRED-only subclass. UI layers translate between SCP and framework types at the boundary. + +3. **Color representation.** Model uses a `NodeColorState` enum or simple struct. UI layers map to `QColor` / MFC color as needed. + +4. **OPF: same class, split file.** `get_listing_opf_*()` remain methods on `SexpTreeModel`, declared in `sexp_tree_model.h`, implemented in `sexp_tree_opf.cpp`. No call-site changes needed. + +5. **Actions: separate class.** `SexpTreeActions` is its own class, constructed with `SexpTreeModel&` + `ISexpTreeUI&`. Provides menu computation and action execution. UI layers own an instance alongside their model. From a1afb0947c31e582fb47d9122b593a1faa3214c3 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Mon, 22 Sep 2025 18:54:00 -0500 Subject: [PATCH 02/73] add shared foundation files --- code/missioneditor/sexp_tree_actions.cpp | 15 ++ code/missioneditor/sexp_tree_actions.h | 16 ++ code/missioneditor/sexp_tree_model.cpp | 194 +++++++++++++++++++++++ code/missioneditor/sexp_tree_model.h | 189 ++++++++++++++++++++++ code/missioneditor/sexp_tree_opf.cpp | 16 ++ code/missioneditor/sexp_tree_opf.h | 18 +++ code/source_groups.cmake | 6 + 7 files changed, 454 insertions(+) create mode 100644 code/missioneditor/sexp_tree_actions.cpp create mode 100644 code/missioneditor/sexp_tree_actions.h create mode 100644 code/missioneditor/sexp_tree_model.cpp create mode 100644 code/missioneditor/sexp_tree_model.h create mode 100644 code/missioneditor/sexp_tree_opf.cpp create mode 100644 code/missioneditor/sexp_tree_opf.h diff --git a/code/missioneditor/sexp_tree_actions.cpp b/code/missioneditor/sexp_tree_actions.cpp new file mode 100644 index 00000000000..598183ec5f8 --- /dev/null +++ b/code/missioneditor/sexp_tree_actions.cpp @@ -0,0 +1,15 @@ +/* + * Copyright (C) Volition, Inc. 1999. All rights reserved. + * + * All source code herein is the property of Volition, Inc. You may not sell + * or otherwise commercially exploit the source or things you created based on the + * source. + * + */ + +// Sexp tree action logic — menu computation and action execution. +// +// This file is intentionally empty in Phase 1. +// Action logic will be moved here in Phase 4. + +#include "missioneditor/sexp_tree_actions.h" diff --git a/code/missioneditor/sexp_tree_actions.h b/code/missioneditor/sexp_tree_actions.h new file mode 100644 index 00000000000..987c00c1861 --- /dev/null +++ b/code/missioneditor/sexp_tree_actions.h @@ -0,0 +1,16 @@ +/* + * Copyright (C) Volition, Inc. 1999. All rights reserved. + * + * All source code herein is the property of Volition, Inc. You may not sell + * or otherwise commercially exploit the source or things you created based on the + * source. + * + */ + +#pragma once + +// Sexp tree action logic — menu computation and action execution. +// Separates "what to do" from "how to show" (UI). +// +// This file is intentionally empty in Phase 1. +// Action logic will be moved here in Phase 4. diff --git a/code/missioneditor/sexp_tree_model.cpp b/code/missioneditor/sexp_tree_model.cpp new file mode 100644 index 00000000000..d8440abb02f --- /dev/null +++ b/code/missioneditor/sexp_tree_model.cpp @@ -0,0 +1,194 @@ +/* + * Copyright (C) Volition, Inc. 1999. All rights reserved. + * + * All source code herein is the property of Volition, Inc. You may not sell + * or otherwise commercially exploit the source or things you created based on the + * source. + * + */ + +// Shared sexp tree model — UI-independent data structures and logic. +// Used by both FRED2 (MFC) and QtFRED (Qt) sexp tree implementations. + +#include "missioneditor/sexp_tree_model.h" + +#include "parse/sexp.h" +#include "mission/missiongoals.h" +#include "mission/missionmessage.h" +#include "mission/missionparse.h" + +// ----------------------------------------------------------------------- +// sexp_list_item implementation +// ----------------------------------------------------------------------- + +void sexp_list_item::set_op(int op_num) +{ + int i; + + if (op_num >= FIRST_OP) { // do we have an op value instead of an op number (index)? + for (i = 0; i < (int)Operators.size(); i++) + if (op_num == Operators[i].value) + op_num = i; // convert op value to op number + } + + op = op_num; + text = Operators[op].text; + type = (SEXPT_OPERATOR | SEXPT_VALID); +} + +void sexp_list_item::set_data(const char* str, int t) +{ + op = -1; + text = str; + type = t; +} + +void sexp_list_item::add_op(int op_num) +{ + sexp_list_item* item; + sexp_list_item* ptr; + + item = new sexp_list_item; + ptr = this; + while (ptr->next) + ptr = ptr->next; + + ptr->next = item; + item->set_op(op_num); +} + +void sexp_list_item::add_data(const char* str, int t) +{ + sexp_list_item* item; + sexp_list_item* ptr; + + item = new sexp_list_item; + ptr = this; + while (ptr->next) + ptr = ptr->next; + + ptr->next = item; + item->set_data(str, t); +} + +void sexp_list_item::add_list(sexp_list_item* list) +{ + sexp_list_item* ptr; + + ptr = this; + while (ptr->next) + ptr = ptr->next; + + ptr->next = list; +} + +void sexp_list_item::destroy() +{ + sexp_list_item* ptr; + sexp_list_item* ptr2; + + ptr = this; + while (ptr) { + ptr2 = ptr->next; + + delete ptr; + ptr = ptr2; + } +} + +// ----------------------------------------------------------------------- +// SexpTreeEditorInterface implementation +// ----------------------------------------------------------------------- + +SexpTreeEditorInterface::SexpTreeEditorInterface() + : SexpTreeEditorInterface(flagset{TreeFlags::LabeledRoot, TreeFlags::RootDeletable}) +{ +} + +SexpTreeEditorInterface::SexpTreeEditorInterface(const flagset& flags) + : _flags(flags) +{ +} + +SexpTreeEditorInterface::~SexpTreeEditorInterface() = default; + +bool SexpTreeEditorInterface::hasDefaultMessageParameter() +{ + return Num_messages > Num_builtin_messages; +} + +SCP_vector SexpTreeEditorInterface::getMessages() +{ + SCP_vector list; + + for (auto i = Num_builtin_messages; i < Num_messages; i++) { + list.emplace_back(Messages[i].name); + } + + return list; +} + +SCP_vector SexpTreeEditorInterface::getMissionGoals(const SCP_string& /*reference_name*/) +{ + SCP_vector list; + list.reserve(Mission_goals.size()); + + for (const auto& goal : Mission_goals) { + list.emplace_back(goal.name, 0, NAME_LENGTH - 1); + } + + return list; +} + +bool SexpTreeEditorInterface::hasDefaultGoal(int operator_value) +{ + return (operator_value == OP_PREVIOUS_GOAL_TRUE) || (operator_value == OP_PREVIOUS_GOAL_FALSE) + || (operator_value == OP_PREVIOUS_GOAL_INCOMPLETE) || !Mission_goals.empty(); +} + +SCP_vector SexpTreeEditorInterface::getMissionEvents(const SCP_string& /*reference_name*/) +{ + SCP_vector list; + list.reserve(Mission_events.size()); + + for (const auto& event : Mission_events) { + list.emplace_back(event.name, 0, NAME_LENGTH - 1); + } + + return list; +} + +bool SexpTreeEditorInterface::hasDefaultEvent(int operator_value) +{ + return (operator_value == OP_PREVIOUS_EVENT_TRUE) || (operator_value == OP_PREVIOUS_EVENT_FALSE) + || (operator_value == OP_PREVIOUS_EVENT_INCOMPLETE) || !Mission_events.empty(); +} + +SCP_vector SexpTreeEditorInterface::getMissionNames() +{ + SCP_vector list; + if (*Mission_filename != '\0') { + list.emplace_back(Mission_filename); + } + return list; +} + +bool SexpTreeEditorInterface::hasDefaultMissionName() +{ + return *Mission_filename != '\0'; +} + +int SexpTreeEditorInterface::getRootReturnType() const +{ + return OPR_BOOL; +} + +const flagset& SexpTreeEditorInterface::getFlags() const +{ + return _flags; +} + +bool SexpTreeEditorInterface::requireCampaignOperators() const +{ + return false; +} diff --git a/code/missioneditor/sexp_tree_model.h b/code/missioneditor/sexp_tree_model.h new file mode 100644 index 00000000000..fcf79565923 --- /dev/null +++ b/code/missioneditor/sexp_tree_model.h @@ -0,0 +1,189 @@ +/* + * Copyright (C) Volition, Inc. 1999. All rights reserved. + * + * All source code herein is the property of Volition, Inc. You may not sell + * or otherwise commercially exploit the source or things you created based on the + * source. + * + */ + +#pragma once + +// Shared sexp tree model — UI-independent data structures and logic. +// Used by both FRED2 (MFC) and QtFRED (Qt) sexp tree implementations. + +#include "globalincs/globals.h" +#include "globalincs/flagset.h" +#include "globalincs/vmallocator.h" // SCP_string, SCP_vector + +// ----------------------------------------------------------------------- +// SEXPT_* node type/status constants +// ----------------------------------------------------------------------- + +#define SEXPT_UNUSED 0x0000 +#define SEXPT_UNINIT 0x0001 +#define SEXPT_UNKNOWN 0x0002 + +#define SEXPT_VALID 0x1000 +#define SEXPT_TYPE_MASK 0x07ff +#define SEXPT_TYPE(X) (SEXPT_TYPE_MASK & (X)) + +#define SEXPT_OPERATOR 0x0010 +#define SEXPT_NUMBER 0x0020 +#define SEXPT_STRING 0x0040 +#define SEXPT_VARIABLE 0x0080 +#define SEXPT_CONTAINER_NAME 0x0100 +#define SEXPT_CONTAINER_DATA 0x0200 +#define SEXPT_MODIFIER 0x0400 + +// ----------------------------------------------------------------------- +// Node flags (editability) +// ----------------------------------------------------------------------- + +#define NOT_EDITABLE 0x00 +#define OPERAND 0x01 +#define EDITABLE 0x02 +#define COMBINED 0x04 + +// ----------------------------------------------------------------------- +// NodeImage — unified icon enum for tree nodes +// ----------------------------------------------------------------------- +// Replaces FRED2's BITMAP_* #defines and QtFRED's NodeImage enum class. +// Numeric values match the original BITMAP_* constants for compatibility. + +enum class NodeImage : int { + OPERATOR = 0, + DATA = 1, + VARIABLE = 2, + ROOT = 3, + ROOT_DIRECTIVE = 4, + CHAIN = 5, + CHAIN_DIRECTIVE = 6, + GREEN_DOT = 7, + BLACK_DOT = 8, + // Numbered data icons (DATA_00 through DATA_95, stepping by 5) + DATA_00 = 9, + DATA_05 = 10, + DATA_10 = 11, + DATA_15 = 12, + DATA_20 = 13, + DATA_25 = 14, + DATA_30 = 15, + DATA_35 = 16, + DATA_40 = 17, + DATA_45 = 18, + DATA_50 = 19, + DATA_55 = 20, + DATA_60 = 21, + DATA_65 = 22, + DATA_70 = 23, + DATA_75 = 24, + DATA_80 = 25, + DATA_85 = 26, + DATA_90 = 27, + DATA_95 = 28, + COMMENT = 29, + CONTAINER_NAME = 30, + CONTAINER_DATA = 31, +}; + +// ----------------------------------------------------------------------- +// Tree behavior mode flags and modes +// ----------------------------------------------------------------------- + +FLAG_LIST(TreeFlags) { + LabeledRoot = 0, + RootDeletable, + RootEditable, + AnnotationsAllowed, + + NUM_VALUES +}; + +// Legacy ST_* / MODE_* constants (FRED2 compatibility) +#define ST_LABELED_ROOT 0x10000 +#define ST_ROOT_DELETABLE 0x20000 +#define ST_ROOT_EDITABLE 0x40000 + +#define MODE_GOALS (1 | ST_LABELED_ROOT | ST_ROOT_DELETABLE) +#define MODE_EVENTS (2 | ST_LABELED_ROOT | ST_ROOT_DELETABLE | ST_ROOT_EDITABLE) +#define MODE_CAMPAIGN (3 | ST_LABELED_ROOT | ST_ROOT_DELETABLE) +#define MODE_CUTSCENES (4 | ST_LABELED_ROOT | ST_ROOT_DELETABLE) + +// ----------------------------------------------------------------------- +// sexp_tree_item — a single node in the sexp tree +// ----------------------------------------------------------------------- +// The handle member is a void* that each UI layer casts to its native type: +// FRED2: static_cast(handle) +// QtFRED: static_cast(handle) + +class sexp_tree_item { +public: + sexp_tree_item() : type(SEXPT_UNUSED), parent(-1), child(-1), next(-1), flags(0), handle(nullptr) { + text[0] = '\0'; + } + + int type; + int parent; // index of parent node (-1 if none) + int child; // index of first child node (-1 if none) + int next; // index of next sibling (-1 if none) + int flags; + char text[2 * TOKEN_LENGTH + 2]; + void* handle; // opaque UI handle — never dereferenced by model code +}; + +// ----------------------------------------------------------------------- +// sexp_list_item — linked list node for building option listings +// ----------------------------------------------------------------------- + +class sexp_list_item { +public: + int type; + int op; + SCP_string text; + sexp_list_item* next; + + sexp_list_item() : type(0), op(-1), next(nullptr) {} + + void set_op(int op_num); + void set_data(const char* str, int t = (SEXPT_STRING | SEXPT_VALID)); + void add_op(int op_num); + void add_data(const char* str, int t = (SEXPT_STRING | SEXPT_VALID)); + void add_list(sexp_list_item* list); + void shallow_copy(const sexp_list_item* src); + void destroy(); +}; + +// ----------------------------------------------------------------------- +// SexpTreeEditorInterface — context interface implemented by editor dialogs +// ----------------------------------------------------------------------- +// Both FRED2 and QtFRED dialogs can implement this to provide context-dependent +// data (messages, goals, events, mission names) to the shared tree model. +// Uses SCP types — UI layers translate at the boundary as needed. + +class SexpTreeEditorInterface { + flagset _flags; + +public: + SexpTreeEditorInterface(); + explicit SexpTreeEditorInterface(const flagset& flags); + virtual ~SexpTreeEditorInterface(); + + virtual bool hasDefaultMessageParameter(); + virtual SCP_vector getMessages(); + + virtual SCP_vector getMissionGoals(const SCP_string& reference_name); + virtual bool hasDefaultGoal(int operator_value); + + virtual SCP_vector getMissionEvents(const SCP_string& reference_name); + virtual bool hasDefaultEvent(int operator_value); + + virtual SCP_vector getMissionNames(); + virtual bool hasDefaultMissionName(); + + virtual int getRootReturnType() const; + + const flagset& getFlags() const; + + virtual bool requireCampaignOperators() const; +}; diff --git a/code/missioneditor/sexp_tree_opf.cpp b/code/missioneditor/sexp_tree_opf.cpp new file mode 100644 index 00000000000..c62d71b6f9f --- /dev/null +++ b/code/missioneditor/sexp_tree_opf.cpp @@ -0,0 +1,16 @@ +/* + * Copyright (C) Volition, Inc. 1999. All rights reserved. + * + * All source code herein is the property of Volition, Inc. You may not sell + * or otherwise commercially exploit the source or things you created based on the + * source. + * + */ + +// OPF listing functions for the sexp tree model. +// All get_listing_opf_*() functions will be implemented here. +// +// This file is intentionally empty in Phase 1. +// OPF functions will be moved here in Phase 2. + +#include "missioneditor/sexp_tree_opf.h" diff --git a/code/missioneditor/sexp_tree_opf.h b/code/missioneditor/sexp_tree_opf.h new file mode 100644 index 00000000000..3a5bbe8b60b --- /dev/null +++ b/code/missioneditor/sexp_tree_opf.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) Volition, Inc. 1999. All rights reserved. + * + * All source code herein is the property of Volition, Inc. You may not sell + * or otherwise commercially exploit the source or things you created based on the + * source. + * + */ + +#pragma once + +// OPF listing functions for the sexp tree model. +// All get_listing_opf_*() functions will be implemented here. +// These are methods on SexpTreeModel, declared in sexp_tree_model.h, +// but implemented in this separate file for maintainability. +// +// This file is intentionally empty in Phase 1. +// OPF functions will be moved here in Phase 2. diff --git a/code/source_groups.cmake b/code/source_groups.cmake index cd55353b901..96a60b8beb3 100644 --- a/code/source_groups.cmake +++ b/code/source_groups.cmake @@ -847,6 +847,12 @@ add_file_folder("MissionEditor" missioneditor/campaignsave.h missioneditor/missionsave.cpp missioneditor/missionsave.h + missioneditor/sexp_tree_model.cpp + missioneditor/sexp_tree_model.h + missioneditor/sexp_tree_opf.cpp + missioneditor/sexp_tree_opf.h + missioneditor/sexp_tree_actions.cpp + missioneditor/sexp_tree_actions.h ) # MissionUI files From 56a84d58a76a1eae740d8d9fad31d0d844eddb85 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Mon, 22 Sep 2025 19:36:00 -0500 Subject: [PATCH 03/73] unify some shared types across both editors --- code/missioneditor/sexp_tree_model.cpp | 2 +- code/missioneditor/sexp_tree_model.h | 2 +- fred2/sexp_tree.cpp | 177 +++++----------- fred2/sexp_tree.h | 110 ++-------- qtfred/src/ui/widgets/sexp_tree.cpp | 269 +++++-------------------- qtfred/src/ui/widgets/sexp_tree.h | 156 ++------------ 6 files changed, 141 insertions(+), 575 deletions(-) diff --git a/code/missioneditor/sexp_tree_model.cpp b/code/missioneditor/sexp_tree_model.cpp index d8440abb02f..54763f7d87d 100644 --- a/code/missioneditor/sexp_tree_model.cpp +++ b/code/missioneditor/sexp_tree_model.cpp @@ -112,7 +112,7 @@ SexpTreeEditorInterface::SexpTreeEditorInterface(const flagset& flags SexpTreeEditorInterface::~SexpTreeEditorInterface() = default; -bool SexpTreeEditorInterface::hasDefaultMessageParameter() +bool SexpTreeEditorInterface::hasDefaultMessageParamter() { return Num_messages > Num_builtin_messages; } diff --git a/code/missioneditor/sexp_tree_model.h b/code/missioneditor/sexp_tree_model.h index fcf79565923..cf358598df0 100644 --- a/code/missioneditor/sexp_tree_model.h +++ b/code/missioneditor/sexp_tree_model.h @@ -169,7 +169,7 @@ class SexpTreeEditorInterface { explicit SexpTreeEditorInterface(const flagset& flags); virtual ~SexpTreeEditorInterface(); - virtual bool hasDefaultMessageParameter(); + virtual bool hasDefaultMessageParamter(); virtual SCP_vector getMessages(); virtual SCP_vector getMissionGoals(const SCP_string& reference_name); diff --git a/fred2/sexp_tree.cpp b/fred2/sexp_tree.cpp index a37a1d03cdf..fff822258e3 100644 --- a/fred2/sexp_tree.cpp +++ b/fred2/sexp_tree.cpp @@ -563,7 +563,8 @@ void sexp_tree::add_sub_tree(int node, HTREEITEM root) } } - root = tree_nodes[node].handle = insert(tree_nodes[node].text, bitmap, bitmap, root); + tree_nodes[node].handle = insert(tree_nodes[node].text, bitmap, bitmap, root); + root = tree_item_handle(tree_nodes[node]); node = node2; while (node != -1) { @@ -1811,7 +1812,8 @@ int sexp_tree::edit_label(HTREEITEM h, bool *is_operator) SetItemText(h, tree_nodes[i].text); tree_nodes[i].flags = OPERAND; - item_handle = tree_nodes[data].handle = insert(tree_nodes[data].text, tree_nodes[data].type, tree_nodes[data].flags, h); + tree_nodes[data].handle = insert(tree_nodes[data].text, tree_nodes[data].type, tree_nodes[data].flags, h); + item_handle = tree_item_handle(tree_nodes[data]); tree_nodes[data].flags = EDITABLE; Expand(h, TVE_EXPAND); SelectItem(item_handle); @@ -2043,7 +2045,7 @@ BOOL sexp_tree::OnCommand(WPARAM wParam, LPARAM lParam) HTREEITEM h; if ((item_index >= 0) && (item_index < total_nodes) && !tree_nodes.empty()) - item_handle = tree_nodes[item_index].handle; + item_handle = tree_item_handle(tree_nodes[item_index]); id = LOWORD(wParam); data = HIWORD(wParam); @@ -2388,7 +2390,7 @@ BOOL sexp_tree::OnCommand(WPARAM wParam, LPARAM lParam) type &= ~(SEXPT_VARIABLE | SEXPT_CONTAINER_NAME); replace_container_data(containers[container_index], (type | SEXPT_CONTAINER_DATA), true, true, true); - HTREEITEM handle = tree_nodes[item_index].handle; + HTREEITEM handle = tree_item_handle(tree_nodes[item_index]); expand_branch(handle); } @@ -2413,10 +2415,10 @@ BOOL sexp_tree::OnCommand(WPARAM wParam, LPARAM lParam) set_node(node, (SEXPT_OPERATOR | SEXPT_VALID), Operators[op].text.c_str()); tree_nodes[node].flags = flags; if (z >= 0) - h = tree_nodes[z].handle; + h = tree_item_handle(tree_nodes[z]); else { - h = GetParentItem(tree_nodes[item_index].handle); + h = GetParentItem(tree_item_handle(tree_nodes[item_index])); if (m_mode == MODE_GOALS) { Assert(Goal_editor_dlg); Goal_editor_dlg->insert_handler(item_index, node); @@ -2437,7 +2439,8 @@ BOOL sexp_tree::OnCommand(WPARAM wParam, LPARAM lParam) } } - item_handle = tree_nodes[node].handle = insert(Operators[op].text.c_str(), BITMAP_OPERATOR, BITMAP_OPERATOR, h, tree_nodes[item_index].handle); + tree_nodes[node].handle = insert(Operators[op].text.c_str(), BITMAP_OPERATOR, BITMAP_OPERATOR, h, tree_item_handle(tree_nodes[item_index])); + item_handle = tree_item_handle(tree_nodes[node]); move_branch(item_index, node); item_index = node; @@ -2497,9 +2500,9 @@ BOOL sexp_tree::OnCommand(WPARAM wParam, LPARAM lParam) } else { replace_data("number", (SEXPT_NUMBER | SEXPT_VALID)); } - EditLabel(tree_nodes[item_index].handle); + EditLabel(tree_item_handle(tree_nodes[item_index])); return 1; - + case ID_REPLACE_STRING: expand_operator(item_index); if (tree_nodes[item_index].type & SEXPT_MODIFIER) { @@ -2507,7 +2510,7 @@ BOOL sexp_tree::OnCommand(WPARAM wParam, LPARAM lParam) } else { replace_data("string", (SEXPT_STRING | SEXPT_VALID)); } - EditLabel(tree_nodes[item_index].handle); + EditLabel(tree_item_handle(tree_nodes[item_index])); return 1; case ID_ADD_STRING: { @@ -2518,7 +2521,7 @@ BOOL sexp_tree::OnCommand(WPARAM wParam, LPARAM lParam) } else { theNode = add_data("string", (SEXPT_STRING | SEXPT_VALID)); } - EditLabel(tree_nodes[theNode].handle); + EditLabel(tree_item_handle(tree_nodes[theNode])); return 1; } @@ -2530,7 +2533,7 @@ BOOL sexp_tree::OnCommand(WPARAM wParam, LPARAM lParam) } else { theNode = add_data("number", (SEXPT_NUMBER | SEXPT_VALID)); } - EditLabel(tree_nodes[theNode].handle); + EditLabel(tree_item_handle(tree_nodes[theNode])); return 1; } @@ -2663,7 +2666,7 @@ void sexp_tree::NodeDelete() SetItem(h_parent, TVIF_TEXT, buf, 0, 0, 0, 0, 0); tree_nodes[parent].flags = OPERAND | EDITABLE; tree_nodes[node].flags = COMBINED; - DeleteItem(tree_nodes[node].handle); + DeleteItem(tree_item_handle(tree_nodes[node])); }*/ *modified = 1; @@ -2706,7 +2709,7 @@ void sexp_tree::NodeReplacePaste() load_branch(Sexp_nodes[Sexp_clipboard].rest, item_index); i = tree_nodes[item_index].child; while (i != -1) { - add_sub_tree(i, tree_nodes[item_index].handle); + add_sub_tree(i, tree_item_handle(tree_nodes[item_index])); i = tree_nodes[i].next; } } @@ -2726,7 +2729,7 @@ void sexp_tree::NodeReplacePaste() load_branch(Sexp_nodes[Sexp_clipboard].first, item_index); i = tree_nodes[item_index].child; while (i != -1) { - add_sub_tree(i, tree_nodes[item_index].handle); + add_sub_tree(i, tree_item_handle(tree_nodes[item_index])); i = tree_nodes[i].next; } } else { @@ -2784,7 +2787,7 @@ void sexp_tree::NodeAddPaste() load_branch(Sexp_nodes[Sexp_clipboard].rest, item_index); i = tree_nodes[item_index].child; while (i != -1) { - add_sub_tree(i, tree_nodes[item_index].handle); + add_sub_tree(i, tree_item_handle(tree_nodes[item_index])); i = tree_nodes[i].next; } } @@ -2797,7 +2800,7 @@ void sexp_tree::NodeAddPaste() load_branch(modifier_node, item_index); i = tree_nodes[item_index].child; while (i != -1) { - add_sub_tree(i, tree_nodes[item_index].handle); + add_sub_tree(i, tree_item_handle(tree_nodes[item_index])); i = tree_nodes[i].next; } } else { @@ -2843,7 +2846,7 @@ void sexp_tree::add_or_replace_operator(int op, int replace_flag) if (i < 0) { // everything is ok, so we can keep old arguments with new operator set_node(item_index, (SEXPT_OPERATOR | SEXPT_VALID), Operators[op].text.c_str()); - SetItemText(tree_nodes[item_index].handle, Operators[op].text.c_str()); + SetItemText(tree_item_handle(tree_nodes[item_index]), Operators[op].text.c_str()); tree_nodes[item_index].flags = OPERAND; return; } @@ -2862,91 +2865,7 @@ void sexp_tree::add_or_replace_operator(int op, int replace_flag) Expand(item_handle, TVE_EXPAND); } -// initialize node, type operator -// -void sexp_list_item::set_op(int op_num) -{ - int i; - - if (op_num >= FIRST_OP) { // do we have an op value instead of an op number (index)? - for (i=0; i<(int)Operators.size(); i++) - if (op_num == Operators[i].value) - op_num = i; // convert op value to op number - } - - op = op_num; - text = Operators[op].text; - type = (SEXPT_OPERATOR | SEXPT_VALID); -} - -// initialize node, type data -// Defaults: t = SEXPT_STRING -// -void sexp_list_item::set_data(const char *str, int t) -{ - op = -1; - text = str; - type = t; -} - -// add a node to end of list -// -void sexp_list_item::add_op(int op_num) -{ - sexp_list_item *item, *ptr; - - item = new sexp_list_item; - ptr = this; - while (ptr->next) - ptr = ptr->next; - - ptr->next = item; - item->set_op(op_num); -} - -// add a node to end of list -// Defaults: t = SEXPT_STRING -// -void sexp_list_item::add_data(const char *str, int t) -{ - sexp_list_item *item, *ptr; - - item = new sexp_list_item; - ptr = this; - while (ptr->next) - ptr = ptr->next; - - ptr->next = item; - item->set_data(str, t); -} - -// add an sexp list to end of another list (join lists) -// -void sexp_list_item::add_list(sexp_list_item *list) -{ - sexp_list_item *ptr; - - ptr = this; - while (ptr->next) - ptr = ptr->next; - - ptr->next = list; -} - -// free all nodes of list -// -void sexp_list_item::destroy() -{ - sexp_list_item *ptr, *ptr2; - - ptr = this; - while (ptr) { - ptr2 = ptr->next; - - delete ptr; - ptr = ptr2; - } -} +// sexp_list_item methods are now in the shared sexp_tree_model.cpp int sexp_tree::add_default_operator(int op_index, int argnum) { @@ -3793,7 +3712,7 @@ void sexp_tree::expand_operator(int node) if ((tree_nodes[node].flags & OPERAND) && (tree_nodes[node].flags & EDITABLE)) { // expandable? Assert(tree_nodes[node].type & SEXPT_OPERATOR); - h = tree_nodes[node].handle; + h = tree_item_handle(tree_nodes[node]); data = tree_nodes[node].child; Assert(data != -1 && tree_nodes[data].next == -1 && tree_nodes[data].child == -1); @@ -3829,10 +3748,10 @@ void sexp_tree::merge_operator(int node) child = tree_nodes[node].child; if (child != -1 && tree_nodes[child].next == -1 && tree_nodes[child].child == -1) { sprintf(buf, "%s %s", tree_nodes[node].text, tree_nodes[child].text); - SetItemText(tree_nodes[node].handle, buf); + SetItemText(tree_item_handle(tree_nodes[node]), buf); tree_nodes[node].flags = OPERAND | EDITABLE; tree_nodes[child].flags = COMBINED; - DeleteItem(tree_nodes[child].handle); + DeleteItem(tree_item_handle(tree_nodes[child])); tree_nodes[child].handle = NULL; return; } @@ -3848,7 +3767,7 @@ int sexp_tree::add_data(const char *data, int type) node = allocate_node(item_index); set_node(node, type, data); int bmap = get_data_image(node); - tree_nodes[node].handle = insert(data, bmap, bmap, tree_nodes[item_index].handle); + tree_nodes[node].handle = insert(data, bmap, bmap, tree_item_handle(tree_nodes[item_index])); tree_nodes[node].flags = EDITABLE; *modified = 1; return node; @@ -3864,7 +3783,7 @@ int sexp_tree::add_variable_data(const char *data, int type) expand_operator(item_index); node = allocate_node(item_index); set_node(node, type, data); - tree_nodes[node].handle = insert(data, BITMAP_VARIABLE, BITMAP_VARIABLE, tree_nodes[item_index].handle); + tree_nodes[node].handle = insert(data, BITMAP_VARIABLE, BITMAP_VARIABLE, tree_item_handle(tree_nodes[item_index])); tree_nodes[node].flags = NOT_EDITABLE; *modified = 1; return node; @@ -3882,7 +3801,7 @@ int sexp_tree::add_container_name(const char *container_name) int node = allocate_node(item_index); set_node(node, (SEXPT_VALID | SEXPT_CONTAINER_NAME | SEXPT_STRING), container_name); tree_nodes[node].handle = - insert(container_name, BITMAP_CONTAINER_NAME, BITMAP_CONTAINER_NAME, tree_nodes[item_index].handle); + insert(container_name, BITMAP_CONTAINER_NAME, BITMAP_CONTAINER_NAME, tree_item_handle(tree_nodes[item_index])); tree_nodes[node].flags = NOT_EDITABLE; *modified = 1; return node; @@ -3898,7 +3817,7 @@ void sexp_tree::add_container_data(const char *container_name) const int node = allocate_node(item_index); set_node(node, (SEXPT_VALID | SEXPT_CONTAINER_DATA | SEXPT_STRING), container_name); tree_nodes[node].handle = - insert(container_name, BITMAP_CONTAINER_DATA, BITMAP_CONTAINER_DATA, tree_nodes[item_index].handle); + insert(container_name, BITMAP_CONTAINER_DATA, BITMAP_CONTAINER_DATA, tree_item_handle(tree_nodes[item_index])); tree_nodes[node].flags = NOT_EDITABLE; item_index = node; *modified = 1; @@ -3913,13 +3832,15 @@ void sexp_tree::add_operator(const char *op, HTREEITEM h) if (item_index == -1) { node = allocate_node(-1); set_node(node, (SEXPT_OPERATOR | SEXPT_VALID), op); - item_handle = tree_nodes[node].handle = insert(op, BITMAP_OPERATOR, BITMAP_OPERATOR, h); + tree_nodes[node].handle = insert(op, BITMAP_OPERATOR, BITMAP_OPERATOR, h); + item_handle = tree_item_handle(tree_nodes[node]); } else { expand_operator(item_index); node = allocate_node(item_index); set_node(node, (SEXPT_OPERATOR | SEXPT_VALID), op); - item_handle = tree_nodes[node].handle = insert(op, BITMAP_OPERATOR, BITMAP_OPERATOR, tree_nodes[item_index].handle); + tree_nodes[node].handle = insert(op, BITMAP_OPERATOR, BITMAP_OPERATOR, tree_item_handle(tree_nodes[item_index])); + item_handle = tree_item_handle(tree_nodes[node]); } tree_nodes[node].flags = OPERAND; @@ -3941,7 +3862,7 @@ void sexp_tree::add_operator(const char *op, HTREEITEM h) set_node(node1, SEXPT_OPERATOR, op); set_node(node2, type, data); sprintf(str, "%s %s", op, data); - tree_nodes[node1].handle = insert(str, tree_nodes[item_index].handle); + tree_nodes[node1].handle = insert(str, tree_item_handle(tree_nodes[item_index])); tree_nodes[node1].flags = OPERAND | EDITABLE; tree_nodes[node2].flags = COMBINED; *modified = 1; @@ -4127,9 +4048,9 @@ int sexp_tree::node_error(int node, const char *msg, int *bypass) *bypass = 1; item_index = node; - item_handle = tree_nodes[node].handle; + item_handle = tree_item_handle(tree_nodes[node]); if (tree_nodes[node].flags & COMBINED) - item_handle = tree_nodes[tree_nodes[node].parent].handle; + item_handle = tree_item_handle(tree_nodes[tree_nodes[node].parent]); ensure_visible(node); SelectItem(item_handle); @@ -4143,7 +4064,7 @@ int sexp_tree::node_error(int node, const char *msg, int *bypass) void sexp_tree::hilite_item(int node) { ensure_visible(node); - SelectItem(tree_nodes[node].handle); + SelectItem(tree_item_handle(tree_nodes[node])); } // because the MFC function EnsureVisible() doesn't do what it says it does, I wrote this. @@ -4154,7 +4075,7 @@ void sexp_tree::ensure_visible(int node) ensure_visible(tree_nodes[node].parent); // expand all parents first if (tree_nodes[node].child != -1) // expandable? - Expand(tree_nodes[node].handle, TVE_EXPAND); // expand this item + Expand(tree_item_handle(tree_nodes[node]), TVE_EXPAND); // expand this item } void sexp_tree::link_modified(int *ptr) @@ -4399,7 +4320,7 @@ void sexp_tree::replace_data(const char *data, int type) free_node2(node); tree_nodes[item_index].child = -1; - h = tree_nodes[item_index].handle; + h = tree_item_handle(tree_nodes[item_index]); while (ItemHasChildren(h)) DeleteItem(GetChildItem(h)); @@ -4431,7 +4352,7 @@ void sexp_tree::replace_variable_data(int var_idx, int type) free_node2(node); tree_nodes[item_index].child = -1; - h = tree_nodes[item_index].handle; + h = tree_item_handle(tree_nodes[item_index]); while (ItemHasChildren(h)) { DeleteItem(GetChildItem(h)); } @@ -4453,14 +4374,14 @@ void sexp_tree::replace_variable_data(int var_idx, int type) void sexp_tree::replace_container_name(const sexp_container &container) { - HTREEITEM h = tree_nodes[item_index].handle; + HTREEITEM h = tree_item_handle(tree_nodes[item_index]); // clean up any child nodes int node = tree_nodes[item_index].child; if (node != -1) free_node2(node); tree_nodes[item_index].child = -1; - h = tree_nodes[item_index].handle; + h = tree_item_handle(tree_nodes[item_index]); while (ItemHasChildren(h)) { DeleteItem(GetChildItem(h)); } @@ -4480,7 +4401,7 @@ void sexp_tree::replace_container_data(const sexp_container &container, bool delete_child_nodes, bool set_default_modifier) { - HTREEITEM h = tree_nodes[item_index].handle; + HTREEITEM h = tree_item_handle(tree_nodes[item_index]); // if this is already a container of the right type, don't alter the child nodes if (test_child_nodes && (tree_nodes[item_index].type & SEXPT_CONTAINER_DATA)) { @@ -4564,7 +4485,7 @@ void sexp_tree::replace_operator(const char *op) free_node2(node); tree_nodes[item_index].child = -1; - h = tree_nodes[item_index].handle; + h = tree_item_handle(tree_nodes[item_index]); while (ItemHasChildren(h)) DeleteItem(GetChildItem(h)); @@ -4589,7 +4510,7 @@ void sexp_tree::replace_operator(const char *op) free_node2(node); tree_nodes[item_index].child = -1; - h = tree_nodes[item_index].handle; + h = tree_item_handle(tree_nodes[item_index]); while (ItemHasChildren(h)) DeleteItem(GetChildItem(h)); @@ -4640,10 +4561,10 @@ void sexp_tree::move_branch(int source, int parent) tree_nodes[node].next = source; } - move_branch(tree_nodes[source].handle, tree_nodes[parent].handle); + move_branch(tree_item_handle(tree_nodes[source]), tree_item_handle(tree_nodes[parent])); } else - move_branch(tree_nodes[source].handle); + move_branch(tree_item_handle(tree_nodes[source])); } } @@ -4981,7 +4902,7 @@ void sexp_tree::OnDestroy() HTREEITEM sexp_tree::handle(int node) { - return tree_nodes[node].handle; + return tree_item_handle(tree_nodes[node]); } const char *sexp_tree::help(int code) @@ -8251,7 +8172,7 @@ bool sexp_tree::rename_container_nodes(const SCP_string &old_name, const SCP_str for (int node_idx = 0; node_idx < (int)tree_nodes.size(); node_idx++) { if (is_matching_container_node(node_idx, old_name)) { strcpy_s(tree_nodes[node_idx].text, new_name.c_str()); - SetItemText(tree_nodes[node_idx].handle, new_name.c_str()); + SetItemText(tree_item_handle(tree_nodes[node_idx]), new_name.c_str()); renamed_anything = true; } } diff --git a/fred2/sexp_tree.h b/fred2/sexp_tree.h index fd712f4009d..b386c7dc8b8 100644 --- a/fred2/sexp_tree.h +++ b/fred2/sexp_tree.h @@ -14,6 +14,7 @@ #pragma warning(disable: 4786) #include "OperatorComboBox.h" +#include "missioneditor/sexp_tree_model.h" #include "parse/sexp.h" #include "parse/sexp_container.h" #include "parse/parselo.h" @@ -23,101 +24,32 @@ //#define MAX_SEXP_TREE_SIZE 1050 //#define MAX_SEXP_TREE_SIZE ((MAX_SEXP_NODES)*2/3) -// tree_node type -#define SEXPT_UNUSED 0x0000 -#define SEXPT_UNINIT 0x0001 -#define SEXPT_UNKNOWN 0x0002 - -#define SEXPT_VALID 0x1000 -#define SEXPT_TYPE_MASK 0x07ff -#define SEXPT_TYPE(X) (SEXPT_TYPE_MASK & X) - -#define SEXPT_OPERATOR 0x0010 -#define SEXPT_NUMBER 0x0020 -#define SEXPT_STRING 0x0040 -#define SEXPT_VARIABLE 0x0080 -#define SEXPT_CONTAINER_NAME 0x0100 -#define SEXPT_CONTAINER_DATA 0x0200 -#define SEXPT_MODIFIER 0x0400 - -// tree_node flag -#define NOT_EDITABLE 0x00 -#define OPERAND 0x01 -#define EDITABLE 0x02 -#define COMBINED 0x04 - -// Bitmaps -#define BITMAP_OPERATOR 0 -#define BITMAP_DATA 1 -#define BITMAP_VARIABLE 2 -#define BITMAP_ROOT 3 -#define BITMAP_ROOT_DIRECTIVE 4 -#define BITMAP_CHAIN 5 -#define BITMAP_CHAIN_DIRECTIVE 6 -#define BITMAP_GREEN_DOT 7 -#define BITMAP_BLACK_DOT 8 -#define BITMAP_BLUE_DOT BITMAP_ROOT -#define BITMAP_RED_DOT BITMAP_ROOT_DIRECTIVE -#define BITMAP_NUMBERED_DATA 9 +// FRED2 BITMAP_* compatibility aliases (map to shared NodeImage enum values) +#define BITMAP_OPERATOR static_cast(NodeImage::OPERATOR) +#define BITMAP_DATA static_cast(NodeImage::DATA) +#define BITMAP_VARIABLE static_cast(NodeImage::VARIABLE) +#define BITMAP_ROOT static_cast(NodeImage::ROOT) +#define BITMAP_ROOT_DIRECTIVE static_cast(NodeImage::ROOT_DIRECTIVE) +#define BITMAP_CHAIN static_cast(NodeImage::CHAIN) +#define BITMAP_CHAIN_DIRECTIVE static_cast(NodeImage::CHAIN_DIRECTIVE) +#define BITMAP_GREEN_DOT static_cast(NodeImage::GREEN_DOT) +#define BITMAP_BLACK_DOT static_cast(NodeImage::BLACK_DOT) +#define BITMAP_BLUE_DOT BITMAP_ROOT +#define BITMAP_RED_DOT BITMAP_ROOT_DIRECTIVE +#define BITMAP_NUMBERED_DATA static_cast(NodeImage::DATA_00) // There are 20 number bitmaps, 9 to 28, counting by 5s from 0 to 95 -#define BITMAP_COMMENT 29 -#define BITMAP_CONTAINER_NAME 30 -#define BITMAP_CONTAINER_DATA 31 - - -// tree behavior modes (or tree subtype) -#define ST_LABELED_ROOT 0x10000 -#define ST_ROOT_DELETABLE 0x20000 -#define ST_ROOT_EDITABLE 0x40000 - -#define MODE_GOALS (1 | ST_LABELED_ROOT | ST_ROOT_DELETABLE) -#define MODE_EVENTS (2 | ST_LABELED_ROOT | ST_ROOT_DELETABLE | ST_ROOT_EDITABLE) -#define MODE_CAMPAIGN (3 | ST_LABELED_ROOT | ST_ROOT_DELETABLE) -#define MODE_CUTSCENES (4 | ST_LABELED_ROOT | ST_ROOT_DELETABLE) +#define BITMAP_COMMENT static_cast(NodeImage::COMMENT) +#define BITMAP_CONTAINER_NAME static_cast(NodeImage::CONTAINER_NAME) +#define BITMAP_CONTAINER_DATA static_cast(NodeImage::CONTAINER_DATA) // various tree operations notification codes (to be handled by derived class) #define ROOT_DELETED 1 #define ROOT_RENAMED 2 -/* - * Notes: An sexp_tree_item is basically a node in a tree. The sexp_tree is an array of - * these node items. - */ - -class sexp_tree_item -{ -public: - sexp_tree_item() : type(SEXPT_UNUSED) {} - - int type; - int parent; // pointer to parent of this item - int child; // pointer to first child of this item - int next; // pointer to next sibling - int flags; - char text[2 * TOKEN_LENGTH + 2]; - HTREEITEM handle; -}; - -class sexp_list_item -{ -public: - int type; - int op; - SCP_string text; - sexp_list_item *next; - - sexp_list_item() : next(nullptr) {} - - void set_op(int op_num); - void set_data(const char *str, int t = (SEXPT_STRING | SEXPT_VALID)); - - void add_op(int op_num); - void add_data(const char *str, int t = (SEXPT_STRING | SEXPT_VALID)); - void add_list(sexp_list_item *list); - - void shallow_copy(const sexp_list_item *src); - void destroy(); -}; +// Typed handle accessors for FRED2 (MFC HTREEITEM) +inline HTREEITEM tree_item_handle(const sexp_tree_item& item) { + return static_cast(item.handle); +} class sexp_tree : public CTreeCtrl { diff --git a/qtfred/src/ui/widgets/sexp_tree.cpp b/qtfred/src/ui/widgets/sexp_tree.cpp index c4e0d23e7f8..50114c99b1f 100644 --- a/qtfred/src/ui/widgets/sexp_tree.cpp +++ b/qtfred/src/ui/widgets/sexp_tree.cpp @@ -158,74 +158,7 @@ bool isRoot(QTreeWidgetItem* it) } } -SexpTreeEditorInterface::SexpTreeEditorInterface() : - SexpTreeEditorInterface(flagset{ TreeFlags::LabeledRoot, TreeFlags::RootDeletable }) { -} -SexpTreeEditorInterface::SexpTreeEditorInterface(const flagset& flags) : _flags(flags) { - -} -bool SexpTreeEditorInterface::hasDefaultMessageParamter() { - return Num_messages > Num_builtin_messages; -} -SCP_vector SexpTreeEditorInterface::getMessages() { - SCP_vector list; - - for (auto i = Num_builtin_messages; i < Num_messages; i++) { - list.emplace_back(Messages[i].name); - } - - return list; -} -QStringList SexpTreeEditorInterface::getMissionGoals(const QString& /*reference_name*/) { - QStringList list; - list.reserve((int)Mission_goals.size()); - - for (const auto &goal: Mission_goals) { - auto temp_name = SCP_string(goal.name, 0, NAME_LENGTH - 1); - list << temp_name.c_str(); - } - - return list; -} -QStringList SexpTreeEditorInterface::getMissionEvents(const QString& /*reference_name*/) { - QStringList list; - list.reserve((int)Mission_events.size()); - - for (const auto &event: Mission_events) { - auto temp_name = SCP_string(event.name, 0, NAME_LENGTH - 1); - list << temp_name.c_str(); - } - - return list; -} -QStringList SexpTreeEditorInterface::getMissionNames() { - return { Mission_filename }; -} -bool SexpTreeEditorInterface::hasDefaultMissionName() { - return *Mission_filename != '\0'; -} -bool SexpTreeEditorInterface::hasDefaultGoal(int operator_value) { - return (operator_value == OP_PREVIOUS_GOAL_TRUE) || (operator_value == OP_PREVIOUS_GOAL_FALSE) - || (operator_value == OP_PREVIOUS_GOAL_INCOMPLETE) || !Mission_goals.empty(); -} -bool SexpTreeEditorInterface::hasDefaultEvent(int operator_value) { - return (operator_value == OP_PREVIOUS_EVENT_TRUE) || (operator_value == OP_PREVIOUS_EVENT_FALSE) - || (operator_value == OP_PREVIOUS_EVENT_INCOMPLETE) || !Mission_events.empty(); - -} -const flagset& SexpTreeEditorInterface::getFlags() const { - return _flags; -} -int SexpTreeEditorInterface::getRootReturnType() const { - return OPR_BOOL; -} -bool SexpTreeEditorInterface::requireCampaignOperators() const { - return false; -} -QList SexpTreeEditorInterface::getContextMenuExtras(QObject */*parent*/) { - return {}; -} -SexpTreeEditorInterface::~SexpTreeEditorInterface() = default; +// SexpTreeEditorInterface methods are now in the shared sexp_tree_model.cpp QIcon sexp_tree::convertNodeImageToIcon(NodeImage image) { return QIcon(node_image_to_resource_name(image)); @@ -245,7 +178,7 @@ class NoteBadgeDelegate final : public QStyledItemDelegate { const QStyle* s = w ? w->style() : QApplication::style(); s->drawControl(QStyle::CE_ItemViewItem, &opt, p, w); - // if there’s a note, paint the badge directly after the text + // if there�s a note, paint the badge directly after the text const QString note = index.data(sexp_tree::NoteRole).toString(); if (!note.isEmpty()) { // where Qt drew the text @@ -757,10 +690,11 @@ void sexp_tree::add_sub_tree(int node, QTreeWidgetItem* root) { } } - root = tree_nodes[node].handle = insert(tree_nodes[node].text, bitmap, root); + tree_nodes[node].handle = insert(tree_nodes[node].text, bitmap, root); + root = tree_item_handle(tree_nodes[node]); - tree_nodes[node].handle->setFlags( - tree_nodes[node].handle->flags().setFlag(Qt::ItemIsEditable, (tree_nodes[node].flags & EDITABLE))); + tree_item_handle(tree_nodes[node])->setFlags( + tree_item_handle(tree_nodes[node])->flags().setFlag(Qt::ItemIsEditable, (tree_nodes[node].flags & EDITABLE))); node = node2; while (node != -1) { @@ -784,8 +718,8 @@ void sexp_tree::add_sub_tree(int node, QTreeWidgetItem* root) { tree_nodes[node].flags = EDITABLE; } - tree_nodes[node].handle->setFlags( - tree_nodes[node].handle->flags().setFlag(Qt::ItemIsEditable, (tree_nodes[node].flags & EDITABLE))); + tree_item_handle(tree_nodes[node])->setFlags( + tree_item_handle(tree_nodes[node])->flags().setFlag(Qt::ItemIsEditable, (tree_nodes[node].flags & EDITABLE))); } node = tree_nodes[node].next; @@ -919,7 +853,7 @@ void sexp_tree::add_or_replace_operator(int op, int replace_flag) { if (i < 0) { // everything is ok, so we can keep old arguments with new operator set_node(item_index, (SEXPT_OPERATOR | SEXPT_VALID), Operators[op].text.c_str()); - tree_nodes[item_index].handle->setText(0, QString::fromStdString(Operators[op].text)); + tree_item_handle(tree_nodes[item_index])->setText(0, QString::fromStdString(Operators[op].text)); tree_nodes[item_index].flags = OPERAND; nodeChanged(item_index); return; @@ -939,90 +873,7 @@ void sexp_tree::add_or_replace_operator(int op, int replace_flag) { } } -// initialize node, type operator -// -void sexp_list_item::set_op(int op_num) { - int i; - - if (op_num >= FIRST_OP) { // do we have an op value instead of an op number (index)? - for (i = 0; i < (int) Operators.size(); i++) { - if (op_num == Operators[i].value) { - op_num = i; - } - } // convert op value to op number - } - - op = op_num; - text = Operators[op].text; - type = (SEXPT_OPERATOR | SEXPT_VALID); -} - -// initialize node, type data -// Defaults: t = SEXPT_STRING -// -void sexp_list_item::set_data(const char* str, int t) { - op = -1; - text = str; - type = t; -} - -// add a node to end of list -// -void sexp_list_item::add_op(int op_num) { - sexp_list_item* item, * ptr; - - item = new sexp_list_item; - ptr = this; - while (ptr->next) { - ptr = ptr->next; - } - - ptr->next = item; - item->set_op(op_num); -} - -// add a node to end of list -// Defaults: t = SEXPT_STRING -// -void sexp_list_item::add_data(const char* str, int t) { - sexp_list_item* item, * ptr; - - item = new sexp_list_item; - ptr = this; - while (ptr->next) { - ptr = ptr->next; - } - - ptr->next = item; - item->set_data(str, t); -} - -// add an sexp list to end of another list (join lists) -// -void sexp_list_item::add_list(sexp_list_item* list) { - sexp_list_item* ptr; - - ptr = this; - while (ptr->next) { - ptr = ptr->next; - } - - ptr->next = list; -} - -// free all nodes of list -// -void sexp_list_item::destroy() { - sexp_list_item* ptr, * ptr2; - - ptr = this; - while (ptr) { - ptr2 = ptr->next; - - delete ptr; - ptr = ptr2; - } -} +// sexp_list_item methods are now in the shared sexp_tree_model.cpp int sexp_tree::add_default_operator(int op_index, int argnum) { char buf[256]; @@ -1839,7 +1690,7 @@ void sexp_tree::expand_operator(int node) { if ((tree_nodes[node].flags & OPERAND) && (tree_nodes[node].flags & EDITABLE)) { // expandable? Assert(tree_nodes[node].type & SEXPT_OPERATOR); - auto h = tree_nodes[node].handle; + auto h = tree_item_handle(tree_nodes[node]); auto child_data = tree_nodes[node].child; Assert(child_data != -1 && tree_nodes[child_data].next == -1 && tree_nodes[child_data].child == -1); @@ -1897,10 +1748,10 @@ void sexp_tree::merge_operator(int /*node*/) { child = tree_nodes[node].child; if (child != -1 && tree_nodes[child].next == -1 && tree_nodes[child].child == -1) { sprintf(buf, "%s %s", tree_nodes[node].text, tree_nodes[child].text); - SetItemText(tree_nodes[node].handle, buf); + SetItemText(tree_item_handle(tree_nodes[node]), buf); tree_nodes[node].flags = OPERAND | EDITABLE; tree_nodes[child].flags = COMBINED; - DeleteItem(tree_nodes[child].handle); + DeleteItem(tree_item_handle(tree_nodes[child])); tree_nodes[child].handle = NULL; return; } @@ -1915,7 +1766,7 @@ int sexp_tree::add_data(const char* new_data, int type) { node = allocate_node(item_index); set_node(node, type, new_data); auto bmap = get_data_image(node); - tree_nodes[node].handle = insert(new_data, bmap, tree_nodes[item_index].handle); + tree_nodes[node].handle = insert(new_data, bmap, tree_item_handle(tree_nodes[item_index])); tree_nodes[node].flags = EDITABLE; modified(); return node; @@ -1930,8 +1781,8 @@ int sexp_tree::add_variable_data(const char* new_data, int type) { expand_operator(item_index); node = allocate_node(item_index); set_node(node, type, new_data); - tree_nodes[node].handle = insert(new_data, NodeImage::VARIABLE, tree_nodes[item_index].handle); - tree_nodes[node].handle->setFlags(tree_nodes[node].handle->flags().setFlag(Qt::ItemIsEditable, false)); + tree_nodes[node].handle = insert(new_data, NodeImage::VARIABLE, tree_item_handle(tree_nodes[item_index])); + tree_item_handle(tree_nodes[node])->setFlags(tree_item_handle(tree_nodes[node])->flags().setFlag(Qt::ItemIsEditable, false)); tree_nodes[node].flags = NOT_EDITABLE; modified(); return node; @@ -1948,8 +1799,8 @@ int sexp_tree::add_container_name(const char* container_name) expand_operator(item_index); int node = allocate_node(item_index); set_node(node, (SEXPT_VALID | SEXPT_CONTAINER_NAME | SEXPT_STRING), container_name); - tree_nodes[node].handle = insert(container_name, NodeImage::CONTAINER_NAME, tree_nodes[item_index].handle); - tree_nodes[node].handle->setFlags(tree_nodes[node].handle->flags().setFlag(Qt::ItemIsEditable, false)); + tree_nodes[node].handle = insert(container_name, NodeImage::CONTAINER_NAME, tree_item_handle(tree_nodes[item_index])); + tree_item_handle(tree_nodes[node])->setFlags(tree_item_handle(tree_nodes[node])->flags().setFlag(Qt::ItemIsEditable, false)); tree_nodes[node].flags = NOT_EDITABLE; modified(); return node; @@ -1964,8 +1815,8 @@ void sexp_tree::add_container_data(const char* container_name) container_name); const int node = allocate_node(item_index); set_node(node, (SEXPT_VALID | SEXPT_CONTAINER_DATA | SEXPT_STRING), container_name); - tree_nodes[node].handle = insert(container_name, NodeImage::CONTAINER_DATA, tree_nodes[item_index].handle); - tree_nodes[node].handle->setFlags(tree_nodes[node].handle->flags().setFlag(Qt::ItemIsEditable, false)); + tree_nodes[node].handle = insert(container_name, NodeImage::CONTAINER_DATA, tree_item_handle(tree_nodes[item_index])); + tree_item_handle(tree_nodes[node])->setFlags(tree_item_handle(tree_nodes[node])->flags().setFlag(Qt::ItemIsEditable, false)); tree_nodes[node].flags = NOT_EDITABLE; item_index = node; modified(); @@ -1984,7 +1835,7 @@ int sexp_tree::add_operator(const char* op, QTreeWidgetItem* h) { expand_operator(item_index); node = allocate_node(item_index); set_node(node, (SEXPT_OPERATOR | SEXPT_VALID), op); - tree_nodes[node].handle = insert(op, NodeImage::OPERATOR, tree_nodes[item_index].handle); + tree_nodes[node].handle = insert(op, NodeImage::OPERATOR, tree_item_handle(tree_nodes[item_index])); } tree_nodes[node].flags = OPERAND; @@ -2192,9 +2043,9 @@ int sexp_tree::node_error(int node, const char* msg, int* bypass) { } item_index = node; - auto item_handle = tree_nodes[node].handle; + auto item_handle = tree_item_handle(tree_nodes[node]); if (tree_nodes[node].flags & COMBINED) { - item_handle = tree_nodes[tree_nodes[node].parent].handle; + item_handle = tree_item_handle(tree_nodes[tree_nodes[node].parent]); } ensure_visible(node); @@ -2213,13 +2064,13 @@ void sexp_tree::hilite_item(int node) { ensure_visible(node); clearSelection(); - setCurrentItem(tree_nodes[node].handle); - scrollToItem(tree_nodes[node].handle); + setCurrentItem(tree_item_handle(tree_nodes[node])); + scrollToItem(tree_item_handle(tree_nodes[node])); } // because the MFC function EnsureVisible() doesn't do what it says it does, I wrote this. void sexp_tree::ensure_visible(int node) { - auto handle = tree_nodes[node].handle->parent(); + auto handle = tree_item_handle(tree_nodes[node])->parent(); while (handle != nullptr) { handle->setExpanded(true); @@ -2460,7 +2311,7 @@ void sexp_tree::replace_data(const char* new_data, int type) { } tree_nodes[item_index].child = -1; - auto h = tree_nodes[item_index].handle; + auto h = tree_item_handle(tree_nodes[item_index]); while (h->childCount() > 0) { h->removeChild(h->child(0)); } @@ -2494,7 +2345,7 @@ void sexp_tree::replace_variable_data(int var_idx, int type) { } tree_nodes[item_index].child = -1; - auto h = tree_nodes[item_index].handle; + auto h = tree_item_handle(tree_nodes[item_index]); while (h->childCount() > 0) { h->removeChild(h->child(0)); } @@ -2525,7 +2376,7 @@ void sexp_tree::replace_container_name(const sexp_container &container) free_node2(node); } tree_nodes[item_index].child = -1; - auto *h = tree_nodes[item_index].handle; + auto *h = tree_item_handle(tree_nodes[item_index]); while (h->childCount() > 0) { h->removeChild(h->child(0)); } @@ -2546,7 +2397,7 @@ void sexp_tree::replace_container_data(const sexp_container &container, bool delete_child_nodes, bool set_default_modifier) { - auto *h = tree_nodes[item_index].handle; + auto *h = tree_item_handle(tree_nodes[item_index]); // if this is already a container of the right type, don't alter the child nodes if (test_child_nodes && (tree_nodes[item_index].type & SEXPT_CONTAINER_DATA)) { @@ -2629,7 +2480,7 @@ void sexp_tree::replace_operator(const char* op) { } tree_nodes[item_index].child = -1; - auto h = tree_nodes[item_index].handle; + auto h = tree_item_handle(tree_nodes[item_index]); while (h->childCount() > 0) { h->removeChild(h->child(0)); } @@ -2656,7 +2507,7 @@ void sexp_tree::replace_operator(const char* op) { free_node2(node); tree_nodes[item_index].child = -1; - h = tree_nodes[item_index].handle; + h = tree_item_handle(tree_nodes[item_index]); while (ItemHasChildren(h)) DeleteItem(GetChildItem(h)); @@ -2707,10 +2558,10 @@ void sexp_tree::move_branch(int source, int parent) { tree_nodes[node].next = source; } - move_branch(tree_nodes[source].handle, tree_nodes[parent].handle); + move_branch(tree_item_handle(tree_nodes[source]), tree_item_handle(tree_nodes[parent])); } else { - move_branch(tree_nodes[source].handle); + move_branch(tree_item_handle(tree_nodes[source])); } } } @@ -2927,14 +2778,14 @@ void sexp_tree::mouseMoveEvent(QMouseEvent* e) return; } - // “Dragging” – we just highlight potential drop target (a root under the cursor) + // �Dragging� � we just highlight potential drop target (a root under the cursor) s_dragging = true; if (auto* over = itemAt(e->pos())) { if (isRoot(over)) - setCurrentItem(over); // simple visual cue like OG’s SelectDropTarget + setCurrentItem(over); // simple visual cue like OG�s SelectDropTarget } - // No QDrag payload; we’ll do the move on mouse release to keep logic simple. + // No QDrag payload; we�ll do the move on mouse release to keep logic simple. QTreeWidget::mouseMoveEvent(e); } @@ -2944,7 +2795,7 @@ void sexp_tree::mouseReleaseEvent(QMouseEvent* e) auto* dropTarget = itemAt(e->pos()); if (dropTarget && isRoot(dropTarget) && dropTarget != s_dragSourceRoot) { // OG rule: if moving up, insert_before=true; if moving down, insert_after - // (so we “end up where we dropped”). :contentReference[oaicite:1]{index=1} + // (so we �end up where we dropped�). :contentReference[oaicite:1]{index=1} const int srcIdx = indexOfTopLevelItem(s_dragSourceRoot); const int dstIdx = indexOfTopLevelItem(dropTarget); const bool insert_before = (srcIdx > dstIdx); @@ -2986,7 +2837,7 @@ QTreeWidgetItem* sexp_tree::insertWithIcon(const QString& lpszItem, } QTreeWidgetItem* sexp_tree::handle(int node) { - return tree_nodes[node].handle; + return tree_item_handle(tree_nodes[node]); } const char* sexp_tree::help(int code) { @@ -6204,16 +6055,8 @@ std::unique_ptr sexp_tree::buildContextMenu(QTreeWidgetItem* h) { auto replace_container_name_menu = popup_menu->addMenu(tr("Replace Container Name")); auto replace_container_data_menu = popup_menu->addMenu(tr("Replace Container Data")); - if (_interface) { - auto extra_acts = _interface->getContextMenuExtras(popup_menu.get()); - if (! extra_acts.isEmpty()) { - popup_menu->addSection("Special Actions"); - - for (QAction *act : extra_acts) { - popup_menu->addAction(act); - } - } - } + // TODO: Context menu extras will be handled through a Qt-specific interface extension + // when needed. Currently no dialog provides extras (all return empty list). update_help(h); //SelectDropTarget(h); // WTF: Why was this here??? @@ -7430,7 +7273,7 @@ void sexp_tree::startOperatorQuickSearch(QTreeWidgetItem* item, const QString& s return; // Only allow on editable positions (operator or data) that live beneath a parent - // (We’ll compute OPF from parent or root as necessary) + // (We�ll compute OPF from parent or root as necessary) _opAll = validOperatorsForNode(nodeIdx); if (_opAll.isEmpty()) return; @@ -7572,7 +7415,7 @@ void sexp_tree::endOperatorQuickSearch(bool confirm) if (op_num >= 0) { add_or_replace_operator(op_num, /*replace_flag*/ 1); if (tree_nodes[node].handle) - tree_nodes[node].handle->setExpanded(true); + tree_item_handle(tree_nodes[node])->setExpanded(true); } } @@ -7685,7 +7528,7 @@ void sexp_tree::pasteActionHandler() { load_branch(Sexp_nodes[Sexp_clipboard].rest, item_index); auto i = tree_nodes[item_index].child; while (i != -1) { - add_sub_tree(i, tree_nodes[item_index].handle); + add_sub_tree(i, tree_item_handle(tree_nodes[item_index])); i = tree_nodes[i].next; } } @@ -7705,7 +7548,7 @@ void sexp_tree::pasteActionHandler() { load_branch(Sexp_nodes[Sexp_clipboard].first, item_index); int i = tree_nodes[item_index].child; while (i != -1) { - add_sub_tree(i, tree_nodes[item_index].handle); + add_sub_tree(i, tree_item_handle(tree_nodes[item_index])); i = tree_nodes[i].next; } } else { @@ -7750,9 +7593,9 @@ void sexp_tree::insertOperatorAction(int op) { tree_nodes[node].flags = flags; QTreeWidgetItem* h; if (z >= 0) { - h = tree_nodes[z].handle; + h = tree_item_handle(tree_nodes[z]); } else { - h = tree_nodes[item_index].handle->parent(); + h = tree_item_handle(tree_nodes[item_index])->parent(); if (!_interface->getFlags()[TreeFlags::LabeledRoot]) { h = nullptr; root_item = node; @@ -7763,7 +7606,7 @@ void sexp_tree::insertOperatorAction(int op) { } auto item_handle = tree_nodes[node].handle = - insert(Operators[op].text.c_str(), NodeImage::OPERATOR, h, tree_nodes[item_index].handle); + insert(Operators[op].text.c_str(), NodeImage::OPERATOR, h, tree_item_handle(tree_nodes[item_index])); move_branch(item_index, node); setCurrentItemIndex(node); @@ -7781,7 +7624,7 @@ void sexp_tree::addNumberDataHandler() { } int theNode = add_data("number", theType); - beginItemEdit(tree_nodes[theNode].handle); + beginItemEdit(tree_item_handle(tree_nodes[theNode])); } void sexp_tree::addStringDataHandler() { int theType = SEXPT_STRING | SEXPT_VALID; @@ -7790,7 +7633,7 @@ void sexp_tree::addStringDataHandler() { } int theNode = add_data("string", theType); - beginItemEdit(tree_nodes[theNode].handle); + beginItemEdit(tree_item_handle(tree_nodes[theNode])); } void sexp_tree::replaceNumberDataHandler() { expand_operator(item_index); @@ -7800,7 +7643,7 @@ void sexp_tree::replaceNumberDataHandler() { } replace_data("number", type); - beginItemEdit(tree_nodes[item_index].handle); + beginItemEdit(tree_item_handle(tree_nodes[item_index])); } void sexp_tree::replaceStringDataHandler() { expand_operator(item_index); @@ -7810,7 +7653,7 @@ void sexp_tree::replaceStringDataHandler() { } replace_data("string", type); - beginItemEdit(tree_nodes[item_index].handle); + beginItemEdit(tree_item_handle(tree_nodes[item_index])); } void sexp_tree::beginItemEdit(QTreeWidgetItem* item) { _currently_editing = true; @@ -7869,7 +7712,7 @@ void sexp_tree::addPasteActionHandler() { load_branch(Sexp_nodes[Sexp_clipboard].rest, item_index); auto i = tree_nodes[item_index].child; while (i != -1) { - add_sub_tree(i, tree_nodes[item_index].handle); + add_sub_tree(i, tree_item_handle(tree_nodes[item_index])); i = tree_nodes[i].next; } } @@ -7882,7 +7725,7 @@ void sexp_tree::addPasteActionHandler() { load_branch(modifier_node, item_index); int i = tree_nodes[item_index].child; while (i != -1) { - add_sub_tree(i, tree_nodes[item_index].handle); + add_sub_tree(i, tree_item_handle(tree_nodes[item_index])); i = tree_nodes[i].next; } } else { @@ -7914,7 +7757,7 @@ void sexp_tree::setCurrentItemIndex(int node) { if (node < 0) { setCurrentItem(nullptr); } else { - setCurrentItem(tree_nodes[node].handle); + setCurrentItem(tree_item_handle(tree_nodes[node])); } } void sexp_tree::handleReplaceVariableAction(int id) { @@ -7981,7 +7824,7 @@ void sexp_tree::handleReplaceContainerDataAction(int idx) { type &= ~(SEXPT_VARIABLE | SEXPT_CONTAINER_NAME); replace_container_data(containers[idx], (type | SEXPT_CONTAINER_DATA), true, true, true); - auto *handle = tree_nodes[item_index].handle; + auto *handle = tree_item_handle(tree_nodes[item_index]); expand_branch(handle); } void sexp_tree::handleNewItemSelected() { diff --git a/qtfred/src/ui/widgets/sexp_tree.h b/qtfred/src/ui/widgets/sexp_tree.h index 6385b08aa16..08eec5b8fd1 100644 --- a/qtfred/src/ui/widgets/sexp_tree.h +++ b/qtfred/src/ui/widgets/sexp_tree.h @@ -9,6 +9,7 @@ #pragma once +#include "missioneditor/sexp_tree_model.h" #include "parse/sexp.h" #include "parse/sexp_container.h" #include "parse/parselo.h" @@ -19,155 +20,24 @@ #include #include -namespace fso { -namespace fred { - -// Goober5000 - it's dynamic now -//#define MAX_SEXP_TREE_SIZE 500 -//#define MAX_SEXP_TREE_SIZE 1050 -//#define MAX_SEXP_TREE_SIZE ((MAX_SEXP_NODES)*2/3) - -// tree_node type -#define SEXPT_UNUSED 0x0000 -#define SEXPT_UNINIT 0x0001 -#define SEXPT_UNKNOWN 0x0002 - -#define SEXPT_VALID 0x1000 -#define SEXPT_TYPE_MASK 0x07ff -#define SEXPT_TYPE(X) (SEXPT_TYPE_MASK & X) - -#define SEXPT_OPERATOR 0x0010 -#define SEXPT_NUMBER 0x0020 -#define SEXPT_STRING 0x0040 -#define SEXPT_VARIABLE 0x0080 -#define SEXPT_CONTAINER_NAME 0x0100 -#define SEXPT_CONTAINER_DATA 0x0200 -#define SEXPT_MODIFIER 0x0400 - -// tree_node flag -#define NOT_EDITABLE 0x00 -#define OPERAND 0x01 -#define EDITABLE 0x02 -#define COMBINED 0x04 - // various tree operations notification codes (to be handled by derived class) #define ROOT_DELETED 1 #define ROOT_RENAMED 2 -// tree behavior modes (or tree subtype) -FLAG_LIST(TreeFlags) { - LabeledRoot = 0, - RootDeletable, - RootEditable, - AnnotationsAllowed, - - NUM_VALUES -}; - -enum class NodeImage { - OPERATOR = 0, - DATA, - VARIABLE, - ROOT, - ROOT_DIRECTIVE, - CHAIN, - CHAIN_DIRECTIVE, - GREEN_DOT, - BLACK_DOT, - DATA_00, - DATA_05, - DATA_10, - DATA_15, - DATA_20, - DATA_25, - DATA_30, - DATA_35, - DATA_40, - DATA_45, - DATA_50, - DATA_55, - DATA_60, - DATA_65, - DATA_70, - DATA_75, - DATA_80, - DATA_85, - DATA_90, - DATA_95, - COMMENT, - CONTAINER_NAME, - CONTAINER_DATA -}; - -/** - * @brief Generic interface for operations that may depend on the context of the SEXP tree - */ -class SexpTreeEditorInterface { - flagset _flags; - public: - SexpTreeEditorInterface(); - explicit SexpTreeEditorInterface(const flagset& flags); - virtual ~SexpTreeEditorInterface(); - - virtual bool hasDefaultMessageParamter(); - virtual SCP_vector getMessages(); - - virtual QStringList getMissionGoals(const QString& reference_name); - virtual bool hasDefaultGoal(int operator_value); - - virtual QStringList getMissionEvents(const QString& reference_name); - virtual bool hasDefaultEvent(int operator_value); - - virtual QStringList getMissionNames(); - virtual bool hasDefaultMissionName(); - - virtual int getRootReturnType() const; - - const flagset& getFlags() const; - - virtual bool requireCampaignOperators() const; - - virtual QList getContextMenuExtras(QObject *parent = nullptr); -}; - -/* - * Notes: An sexp_tree_item is basically a node in a tree. The sexp_tree is an array of - * these node items. - */ - -class sexp_tree_item { - public: - sexp_tree_item() : type(SEXPT_UNUSED) { - } - - int type; - int parent; // pointer to parent of this item - int child; // pointer to first child of this item - int next; // pointer to next sibling - int flags; - char text[2 * TOKEN_LENGTH + 2]; - QTreeWidgetItem* handle; -}; - -class sexp_list_item { - public: - int type; - int op; - SCP_string text; - sexp_list_item* next; - - sexp_list_item() : next(nullptr) { - } - - void set_op(int op_num); - void set_data(const char* str, int t = (SEXPT_STRING | SEXPT_VALID)); +namespace fso { +namespace fred { - void add_op(int op_num); - void add_data(const char* str, int t = (SEXPT_STRING | SEXPT_VALID)); - void add_list(sexp_list_item* list); +// Bring shared types into the fso::fred namespace so existing code compiles unchanged +using ::sexp_tree_item; +using ::sexp_list_item; +using ::NodeImage; +using ::TreeFlags; +using ::SexpTreeEditorInterface; - void destroy(); -}; +// Typed handle accessor for QtFRED (Qt QTreeWidgetItem*) +inline QTreeWidgetItem* tree_item_handle(const sexp_tree_item& item) { + return static_cast(item.handle); +} class sexp_tree: public QTreeWidget { From 2c882f5c28eb223aa212ea9a837b9d3af4485f26 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Wed, 24 Sep 2025 18:42:00 -0500 Subject: [PATCH 04/73] sexptreemodel framework --- code/missioneditor/sexp_tree_model.cpp | 72 ++++++++++++ code/missioneditor/sexp_tree_model.h | 146 +++++++++++++++++++++++++ 2 files changed, 218 insertions(+) diff --git a/code/missioneditor/sexp_tree_model.cpp b/code/missioneditor/sexp_tree_model.cpp index 54763f7d87d..50aab64f2c1 100644 --- a/code/missioneditor/sexp_tree_model.cpp +++ b/code/missioneditor/sexp_tree_model.cpp @@ -192,3 +192,75 @@ bool SexpTreeEditorInterface::requireCampaignOperators() const { return false; } + +// ----------------------------------------------------------------------- +// SexpTreeModel implementation +// ----------------------------------------------------------------------- + +SexpTreeModel::SexpTreeModel() + : total_nodes(0), m_mode(0), item_index(-1), _interface(nullptr) +{ +} + +SexpTreeModel::~SexpTreeModel() = default; + +// Goober5000 +int SexpTreeModel::find_argument_number(int parent_node, int child_node) const +{ + int arg_num, current_node; + + // code moved/adapted from match_closest_operator + arg_num = 0; + current_node = tree_nodes[parent_node].child; + while (current_node >= 0) + { + // found? + if (current_node == child_node) + return arg_num; + + // continue iterating + arg_num++; + current_node = tree_nodes[current_node].next; + } + + // not found + return -1; +} + +// Goober5000 +// backtrack through parents until we find the operator matching +// parent_op, then find the argument we went through +int SexpTreeModel::find_ancestral_argument_number(int parent_op, int child_node) const +{ + if (child_node == -1) + return -1; + + int parent_node; + int current_node; + + current_node = child_node; + parent_node = tree_nodes[current_node].parent; + + while (parent_node >= 0) + { + // check if the parent operator is the one we're looking for + if (get_operator_const(tree_nodes[parent_node].text) == parent_op) + return find_argument_number(parent_node, current_node); + + // continue iterating up the tree + current_node = parent_node; + parent_node = tree_nodes[current_node].parent; + } + + return -1; +} + +bool SexpTreeModel::is_node_eligible_for_special_argument(int parent_node) const +{ + Assertion(parent_node != -1, + "Attempt to access invalid parent node for special arg eligibility check. Please report!"); + + const int w_arg = find_ancestral_argument_number(OP_WHEN_ARGUMENT, parent_node); + const int e_arg = find_ancestral_argument_number(OP_EVERY_TIME_ARGUMENT, parent_node); + return w_arg >= 1 || e_arg >= 1; +} diff --git a/code/missioneditor/sexp_tree_model.h b/code/missioneditor/sexp_tree_model.h index cf358598df0..5644820ed27 100644 --- a/code/missioneditor/sexp_tree_model.h +++ b/code/missioneditor/sexp_tree_model.h @@ -187,3 +187,149 @@ class SexpTreeEditorInterface { virtual bool requireCampaignOperators() const; }; + +// Forward declaration for OPF function parameter +enum class ContainerType; + +// ----------------------------------------------------------------------- +// SexpTreeModel — shared UI-independent sexp tree model +// ----------------------------------------------------------------------- +// Owns tree node data and provides all pure-logic operations. +// Both FRED2 and QtFRED sexp_tree classes delegate to this model. +// OPF listing functions are declared here and implemented in sexp_tree_opf.cpp. + +class SexpTreeModel { +public: + SexpTreeModel(); + ~SexpTreeModel(); + + // Tree node storage + SCP_vector tree_nodes; + int total_nodes; + int m_mode; + int item_index; + + // Editor context interface (set by UI layer) + SexpTreeEditorInterface* _interface; + + // --- Tree navigation helpers (used by OPF functions) --- + int find_argument_number(int parent_node, int child_node) const; + int find_ancestral_argument_number(int parent_op, int child_node) const; + bool is_node_eligible_for_special_argument(int parent_node) const; + + // --- OPF listing functions (implemented in sexp_tree_opf.cpp) --- + sexp_list_item* get_listing_opf(int opf, int parent_node, int arg_index); + sexp_list_item* get_listing_opf_null(); + sexp_list_item* get_listing_opf_flexible_argument(); + sexp_list_item* get_listing_opf_bool(int parent_node = -1); + sexp_list_item* get_listing_opf_positive(); + sexp_list_item* get_listing_opf_number(); + sexp_list_item* get_listing_opf_ship(int parent_node = -1); + sexp_list_item* get_listing_opf_prop(); + sexp_list_item* get_listing_opf_wing(); + sexp_list_item* get_listing_opf_subsystem(int parent_node, int arg_index); + sexp_list_item* get_listing_opf_subsystem_type(int parent_node); + sexp_list_item* get_listing_opf_point(); + sexp_list_item* get_listing_opf_iff(); + sexp_list_item* get_listing_opf_ai_class(); + sexp_list_item* get_listing_opf_support_ship_class(); + sexp_list_item* get_listing_opf_ssm_class(); + sexp_list_item* get_listing_opf_ship_with_bay(); + sexp_list_item* get_listing_opf_soundtrack_name(); + sexp_list_item* get_listing_opf_arrival_location(); + sexp_list_item* get_listing_opf_departure_location(); + sexp_list_item* get_listing_opf_arrival_anchor_all(); + sexp_list_item* get_listing_opf_ai_goal(int parent_node); + sexp_list_item* get_listing_opf_docker_point(int parent_node, int arg_index); + sexp_list_item* get_listing_opf_dockee_point(int parent_node); + sexp_list_item* get_listing_opf_message(); + sexp_list_item* get_listing_opf_persona(); + sexp_list_item* get_listing_opf_font(); + sexp_list_item* get_listing_opf_who_from(); + sexp_list_item* get_listing_opf_priority(); + sexp_list_item* get_listing_opf_sound_environment(); + sexp_list_item* get_listing_opf_sound_environment_option(); + sexp_list_item* get_listing_opf_adjust_audio_volume(); + sexp_list_item* get_listing_opf_builtin_hud_gauge(); + sexp_list_item* get_listing_opf_custom_hud_gauge(); + sexp_list_item* get_listing_opf_any_hud_gauge(); + sexp_list_item* get_listing_opf_ship_effect(); + sexp_list_item* get_listing_opf_explosion_option(); + sexp_list_item* get_listing_opf_waypoint_path(); + sexp_list_item* get_listing_opf_ship_point(); + sexp_list_item* get_listing_opf_ship_wing_wholeteam(); + sexp_list_item* get_listing_opf_ship_wing_shiponteam_point(); + sexp_list_item* get_listing_opf_ship_wing_point(); + sexp_list_item* get_listing_opf_ship_wing_point_or_none(); + sexp_list_item* get_listing_opf_mission_name(); + sexp_list_item* get_listing_opf_goal_name(int parent_node); + sexp_list_item* get_listing_opf_ship_wing(); + sexp_list_item* get_listing_opf_ship_prop(); + sexp_list_item* get_listing_opf_order_recipient(); + sexp_list_item* get_listing_opf_ship_type(); + sexp_list_item* get_listing_opf_keypress(); + sexp_list_item* get_listing_opf_event_name(int parent_node); + sexp_list_item* get_listing_opf_ai_order(); + sexp_list_item* get_listing_opf_skill_level(); + sexp_list_item* get_listing_opf_cargo(); + sexp_list_item* get_listing_opf_string(); + sexp_list_item* get_listing_opf_medal_name(); + sexp_list_item* get_listing_opf_weapon_name(); + sexp_list_item* get_listing_opf_intel_name(); + sexp_list_item* get_listing_opf_ship_class_name(); + sexp_list_item* get_listing_opf_prop_class_name(); + sexp_list_item* get_listing_opf_huge_weapon(); + sexp_list_item* get_listing_opf_ship_not_player(); + sexp_list_item* get_listing_opf_ship_or_none(); + sexp_list_item* get_listing_opf_subsystem_or_none(int parent_node, int arg_index); + sexp_list_item* get_listing_opf_subsys_or_generic(int parent_node, int arg_index); + sexp_list_item* get_listing_opf_jump_nodes(); + sexp_list_item* get_listing_opf_variable_names(); + sexp_list_item* get_listing_opf_skybox_model(); + sexp_list_item* get_listing_opf_skybox_flags(); + sexp_list_item* get_listing_opf_background_bitmap(); + sexp_list_item* get_listing_opf_sun_bitmap(); + sexp_list_item* get_listing_opf_nebula_storm_type(); + sexp_list_item* get_listing_opf_nebula_poof(); + sexp_list_item* get_listing_opf_turret_target_order(); + sexp_list_item* get_listing_opf_turret_types(); + sexp_list_item* get_listing_opf_post_effect(); + sexp_list_item* get_listing_opf_turret_target_priorities(); + sexp_list_item* get_listing_opf_armor_type(); + sexp_list_item* get_listing_opf_damage_type(); + sexp_list_item* get_listing_opf_animation_type(); + sexp_list_item* get_listing_opf_hud_elements(); + sexp_list_item* get_listing_opf_weapon_banks(); + sexp_list_item* get_listing_opf_mission_moods(); + sexp_list_item* get_listing_opf_ship_flags(); + sexp_list_item* get_listing_opf_wing_flags(); + sexp_list_item* get_listing_opf_team_colors(); + sexp_list_item* get_listing_opf_nebula_patterns(); + sexp_list_item* get_listing_opf_asteroid_types(); + sexp_list_item* get_listing_opf_debris_types(); + sexp_list_item* get_listing_opf_motion_debris(); + sexp_list_item* get_listing_opf_game_snds(); + sexp_list_item* get_listing_opf_fireball(); + sexp_list_item* get_listing_opf_species(); + sexp_list_item* get_listing_opf_language(); + sexp_list_item* get_listing_opf_functional_when_eval_type(); + sexp_list_item* get_listing_opf_animation_name(int parent_node); + sexp_list_item* get_listing_opf_sexp_containers(ContainerType con_type); + sexp_list_item* get_listing_opf_wing_formation(); + sexp_list_item* get_listing_opf_bolt_types(); + sexp_list_item* get_listing_opf_traitor_overrides(); + sexp_list_item* get_listing_opf_lua_general_orders(); + sexp_list_item* get_listing_opf_message_types(); + sexp_list_item* get_listing_opf_lua_enum(int parent_node, int arg_index); + sexp_list_item* get_listing_opf_mission_custom_strings(); + sexp_list_item* check_for_dynamic_sexp_enum(int opf); + + // Container modifier helpers + sexp_list_item* get_container_modifiers(int con_data_node) const; + sexp_list_item* get_list_container_modifiers() const; + sexp_list_item* get_map_container_modifiers(int con_data_node) const; + sexp_list_item* get_container_multidim_modifiers(int con_data_node) const; + + // Static utilities + static bool is_container_name_opf_type(int op_type); +}; From 40bcf16b8f224a6a09d95fec6af292b3d00fd9c2 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Wed, 24 Sep 2025 19:11:00 -0500 Subject: [PATCH 05/73] move opf functions to the model --- code/missioneditor/sexp_tree_opf.cpp | 2588 +++++++++++++++++++++++++- 1 file changed, 2584 insertions(+), 4 deletions(-) diff --git a/code/missioneditor/sexp_tree_opf.cpp b/code/missioneditor/sexp_tree_opf.cpp index c62d71b6f9f..d3a95cd8d2a 100644 --- a/code/missioneditor/sexp_tree_opf.cpp +++ b/code/missioneditor/sexp_tree_opf.cpp @@ -8,9 +8,2589 @@ */ // OPF listing functions for the sexp tree model. -// All get_listing_opf_*() functions will be implemented here. -// -// This file is intentionally empty in Phase 1. -// OPF functions will be moved here in Phase 2. +// All get_listing_opf_*() functions are implemented here as SexpTreeModel methods. +// Moved from fred2/sexp_tree.cpp and qtfred/src/ui/widgets/sexp_tree.cpp. #include "missioneditor/sexp_tree_opf.h" +#include "missioneditor/sexp_tree_model.h" + +#include "parse/sexp.h" +#include "globalincs/linklist.h" +#include "object/object.h" +#include "object/waypoint.h" +#include "ship/ship.h" +#include "prop/prop.h" +#include "iff_defs/iff_defs.h" +#include "ai/ai.h" +#include "ai/aigoals.h" +#include "hud/hudartillery.h" +#include "gamesnd/eventmusic.h" +#include "mission/missionparse.h" +#include "mission/missionmessage.h" +#include "missioneditor/common.h" +#include "model/model.h" +#include "sound/ds.h" +#include "hud/hud.h" +#include "graphics/software/FontManager.h" +#include "hud/hudsquadmsg.h" +#include "controlconfig/controlsconfig.h" +#include "mission/missiongoals.h" +#include "mission/missioncampaign.h" +#include "stats/medals.h" +#include "menuui/techmenu.h" +#include "weapon/weapon.h" +#include "jumpnode/jumpnode.h" +#include "starfield/starfield.h" +#include "nebula/neblightning.h" +#include "nebula/neb.h" +#include "graphics/2d.h" +#include "model/animation/modelanimation.h" +#include "globalincs/alphacolors.h" +#include "asteroid/asteroid.h" +#include "fireball/fireballs.h" +#include "species_defs/species_defs.h" +#include "localization/localize.h" +#include "gamesnd/gamesnd.h" +#include "parse/sexp/sexp_lookup.h" +#include "ai/ailua.h" +#include "stats/scoring.h" +#include "parse/sexp_container.h" + +// ----------------------------------------------------------------------- +// Batch 1: null, flexible_argument, bool, positive, number, +// ship, prop, wing, point, iff +// ----------------------------------------------------------------------- + +sexp_list_item *SexpTreeModel::get_listing_opf_null() +{ + int i; + sexp_list_item head; + + for (i=0; i<(int)Operators.size(); i++) + if (query_operator_return_type(i) == OPR_NULL) + head.add_op(i); + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_flexible_argument() +{ + int i; + sexp_list_item head; + + for (i=0; i<(int)Operators.size(); i++) + if (query_operator_return_type(i) == OPR_FLEXIBLE_ARGUMENT) + head.add_op(i); + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_bool(int parent_node) +{ + int i, only_basic; + sexp_list_item head; + + // search for the previous goal/event operators. If found, only add the true/false + // sexpressions to the list + only_basic = 0; + if ( parent_node != -1 ) { + int op; + + op = get_operator_const(tree_nodes[parent_node].text); + if ( (op == OP_PREVIOUS_GOAL_TRUE) || (op == OP_PREVIOUS_GOAL_FALSE) || (op == OP_PREVIOUS_EVENT_TRUE) || (op == OP_PREVIOUS_EVENT_FALSE) ) + only_basic = 1; + + } + + for (i=0; i<(int)Operators.size(); i++) { + if (query_operator_return_type(i) == OPR_BOOL) { + if ( !only_basic || (only_basic && ((Operators[i].value == OP_TRUE) || (Operators[i].value == OP_FALSE))) ) { + head.add_op(i); + } + } + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_positive() +{ + int i, z; + sexp_list_item head; + + for (i=0; i<(int)Operators.size(); i++) { + z = query_operator_return_type(i); + // Goober5000's number hack + if ((z == OPR_NUMBER) || (z == OPR_POSITIVE)) + head.add_op(i); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_number() +{ + int i, z; + sexp_list_item head; + + for (i=0; i<(int)Operators.size(); i++) { + z = query_operator_return_type(i); + if ((z == OPR_NUMBER) || (z == OPR_POSITIVE)) + head.add_op(i); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_ship(int parent_node) +{ + object *ptr; + sexp_list_item head; + int op = 0, dock_ship = -1, require_cap_ship = 0; + + // look at the parent node and get the operator. Some ship lists should be filtered based + // on what the parent operator is + if ( parent_node >= 0 ) { + op = get_operator_const(tree_nodes[parent_node].text); + + // get the dock_ship number of if this goal is an ai dock goal. used to prune out unwanted ships out + // of the generated ship list + dock_ship = -1; + if ( op == OP_AI_DOCK ) { + int z; + + z = tree_nodes[parent_node].parent; + Assert(z >= 0); + Assert(!stricmp(tree_nodes[z].text, "add-ship-goal") || !stricmp(tree_nodes[z].text, "add-wing-goal") || !stricmp(tree_nodes[z].text, "add-goal")); + + z = tree_nodes[z].child; + Assert(z >= 0); + + dock_ship = ship_name_lookup(tree_nodes[z].text, 1); + Assert( dock_ship != -1 ); + } + } + + ptr = GET_FIRST(&obj_used_list); + while (ptr != END_OF_LIST(&obj_used_list)) { + if ((ptr->type == OBJ_SHIP) || (ptr->type == OBJ_START)) { + if ( op == OP_AI_DOCK ) { + // only include those ships in the list which the given ship can dock with. + if ( (dock_ship != ptr->instance) && ship_docking_valid(dock_ship , ptr->instance) ) + head.add_data(Ships[ptr->instance].ship_name ); + + } + else if (op == OP_CAP_SUBSYS_CARGO_KNOWN_DELAY) { + if ( ((Ship_info[Ships[ptr->instance].ship_info_index].is_huge_ship()) && // big ship + !(Ships[ptr->instance].flags[Ship::Ship_Flags::Toggle_subsystem_scanning]) )|| // which is not flagged OR + ((!(Ship_info[Ships[ptr->instance].ship_info_index].is_huge_ship())) && // small ship + (Ships[ptr->instance].flags[Ship::Ship_Flags::Toggle_subsystem_scanning]) ) ) { // which is flagged + + head.add_data(Ships[ptr->instance].ship_name); + } + } + else { + if ( !require_cap_ship || Ship_info[Ships[ptr->instance].ship_info_index].is_huge_ship() ) { + head.add_data(Ships[ptr->instance].ship_name); + } + } + } + + ptr = GET_NEXT(ptr); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_prop() +{ + object *ptr; + sexp_list_item head; + + ptr = GET_FIRST(&obj_used_list); + while (ptr != END_OF_LIST(&obj_used_list)) { + if (ptr->type == OBJ_PROP) { + head.add_data(prop_id_lookup(ptr->instance)->prop_name); + } + + ptr = GET_NEXT(ptr); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_wing() +{ + int i; + sexp_list_item head; + + for (i=0; i"); + + for (auto it = Ship_info.cbegin(); it != Ship_info.cend(); ++it) + { + if (it->flags[Ship::Info_Flags::Support]) + { + head.add_data(it->name); + } + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_ssm_class() +{ + sexp_list_item head; + + for (auto it = Ssm_info.cbegin(); it != Ssm_info.cend(); ++it) + { + head.add_data(it->name); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_ship_with_bay() +{ + object *objp; + sexp_list_item head; + + head.add_data(""); + + for ( objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) + { + if ( (objp->type == OBJ_SHIP) || (objp->type == OBJ_START) ) + { + // determine if this ship has a hangar bay + if (ship_has_hangar_bay(objp->instance)) + { + head.add_data(Ships[objp->instance].ship_name); + } + } + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_soundtrack_name() +{ + sexp_list_item head; + + head.add_data(""); + + for (auto &st: Soundtracks) + head.add_data(st.name); + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_arrival_location() +{ + int i; + sexp_list_item head; + + for (i=0; itype == OBJ_SHIP) || (objp->type == OBJ_START) ) + { + head.add_data(Ships[objp->instance].ship_name); + } + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_ai_goal(int parent_node) +{ + int i, n, w, z, child; + sexp_list_item head; + + Assert(parent_node >= 0); + child = tree_nodes[parent_node].child; + if (child < 0) + return nullptr; + + n = ship_name_lookup(tree_nodes[child].text, 1); + if (n >= 0) { + // add operators if it's an ai-goal and ai-goal is allowed for that ship + for (i=0; i<(int)Operators.size(); i++) { + if ( (query_operator_return_type(i) == OPR_AI_GOAL) && query_sexp_ai_goal_valid(Operators[i].value, n) ) + head.add_op(i); + } + + } else { + z = wing_name_lookup(tree_nodes[child].text); + if (z >= 0) { + for (w=0; wgetMessages()) { + head.add_data(msg.c_str()); + } + + return head.next; +} + +// ----------------------------------------------------------------------- +// Batch 3: docker_point, dockee_point, persona, font, who_from, +// priority, sound_environment, sound_environment_option, +// adjust_audio_volume, builtin_hud_gauge +// ----------------------------------------------------------------------- + +sexp_list_item *SexpTreeModel::get_listing_opf_docker_point(int parent_node, int arg_num) +{ + int z; + sexp_list_item head; + int sh = -1; + + Assert(parent_node >= 0); + Assert(!stricmp(tree_nodes[parent_node].text, "ai-dock") || !stricmp(tree_nodes[parent_node].text, "set-docked") || + get_operator_const(tree_nodes[parent_node].text) >= (int)First_available_operator_id); + + if (!stricmp(tree_nodes[parent_node].text, "ai-dock")) + { + z = tree_nodes[parent_node].parent; + if (z < 0) + return nullptr; + Assert(!stricmp(tree_nodes[z].text, "add-ship-goal") || !stricmp(tree_nodes[z].text, "add-wing-goal") || !stricmp(tree_nodes[z].text, "add-goal")); + + z = tree_nodes[z].child; + if (z < 0) + return nullptr; + sh = ship_name_lookup(tree_nodes[z].text, 1); + } + else if (!stricmp(tree_nodes[parent_node].text, "set-docked")) + { + //Docker ship should be the first child node + z = tree_nodes[parent_node].child; + if (z < 0) + return nullptr; + sh = ship_name_lookup(tree_nodes[z].text, 1); + } + // for Lua sexps + else if (get_operator_const(tree_nodes[parent_node].text) >= (int)First_available_operator_id) + { + int this_index = get_dynamic_parameter_index(tree_nodes[parent_node].text, arg_num); + + if (this_index >= 0) { + z = tree_nodes[parent_node].child; + + for (int j = 0; j < this_index; j++) { + z = tree_nodes[z].next; + } + + sh = ship_name_lookup(tree_nodes[z].text, 1); + } else { + error_display(1, "Expected to find a dynamic lua parent parameter for node %i in operator %s but found nothing!", + arg_num, + tree_nodes[parent_node].text); + } + } + + if (sh >= 0) + { + polymodel *pm = model_get(Ship_info[Ships[sh].ship_info_index].model_num); + for (int i = 0; i < pm->n_docks; i++) + head.add_data(pm->docking_bays[i].name); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_dockee_point(int parent_node) +{ + int z; + sexp_list_item head; + int sh = -1; + + Assert(parent_node >= 0); + Assert(!stricmp(tree_nodes[parent_node].text, "ai-dock") || !stricmp(tree_nodes[parent_node].text, "set-docked")); + + if (!stricmp(tree_nodes[parent_node].text, "ai-dock")) + { + z = tree_nodes[parent_node].child; + if (z < 0) + return nullptr; + + sh = ship_name_lookup(tree_nodes[z].text, 1); + } + else if (!stricmp(tree_nodes[parent_node].text, "set-docked")) + { + //Dockee ship should be the third child node + z = tree_nodes[parent_node].child; // 1 + if (z < 0) return nullptr; + z = tree_nodes[z].next; // 2 + if (z < 0) return nullptr; + z = tree_nodes[z].next; // 3 + if (z < 0) return nullptr; + + sh = ship_name_lookup(tree_nodes[z].text, 1); + } + + if (sh >= 0) + { + polymodel *pm = model_get(Ship_info[Ships[sh].ship_info_index].model_num); + for (int i = 0; i < pm->n_docks; i++) + head.add_data(pm->docking_bays[i].name); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_persona() +{ + sexp_list_item head; + + for (const auto &persona: Personas) { + if (persona.flags & PERSONA_FLAG_WINGMAN) { + head.add_data(persona.name); + } + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_font() +{ + int i; + sexp_list_item head; + + for (i = 0; i < font::FontManager::numberOfFonts(); i++) { + head.add_data(font::FontManager::getFont(i)->getName().c_str()); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_who_from() +{ + object *ptr; + sexp_list_item head; + + //head.add_data(""); + head.add_data("#Command"); + head.add_data(""); + head.add_data(""); + + ptr = GET_FIRST(&obj_used_list); + while (ptr != END_OF_LIST(&obj_used_list)) { + if ((ptr->type == OBJ_SHIP) || (ptr->type == OBJ_START)) + if (Ship_info[Ships[ptr->instance].ship_info_index].is_flyable()) + head.add_data(Ships[ptr->instance].ship_name); + + ptr = GET_NEXT(ptr); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_priority() +{ + sexp_list_item head; + + head.add_data("High"); + head.add_data("Normal"); + head.add_data("Low"); + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_sound_environment() +{ + sexp_list_item head; + + head.add_data(SEXP_NONE_STRING); + for (int i = 0; i < (int)EFX_presets.size(); i++) { + head.add_data(EFX_presets[i].name.c_str()); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_sound_environment_option() +{ + sexp_list_item head; + + for (int i = 0; i < Num_sound_environment_options; i++) + head.add_data(Sound_environment_option[i]); + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_adjust_audio_volume() +{ + sexp_list_item head; + + for (int i = 0; i < Num_adjust_audio_options; i++) + head.add_data(Adjust_audio_options[i]); + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_builtin_hud_gauge() +{ + sexp_list_item head; + + for (int i = 0; i < Num_hud_gauge_types; i++) + head.add_data(Hud_gauge_types[i].name); + + return head.next; +} + +// ----------------------------------------------------------------------- +// Batch 4: custom_hud_gauge, any_hud_gauge, ship_effect, explosion_option, +// waypoint_path, ship_point, ship_wing_wholeteam, +// ship_wing_shiponteam_point, ship_wing_point, ship_wing_point_or_none +// ----------------------------------------------------------------------- + +sexp_list_item *SexpTreeModel::get_listing_opf_custom_hud_gauge() +{ + sexp_list_item head; + // prevent duplicate names, comparing case-insensitively + SCP_unordered_set all_gauges; + + for (auto &gauge : default_hud_gauges) + { + SCP_string name = gauge->getCustomGaugeName(); + if (!name.empty() && all_gauges.count(name) == 0) + { + head.add_data(name.c_str()); + all_gauges.insert(std::move(name)); + } + } + + for (auto &si : Ship_info) + { + for (auto &gauge : si.hud_gauges) + { + SCP_string name = gauge->getCustomGaugeName(); + if (!name.empty() && all_gauges.count(name) == 0) + { + head.add_data(name.c_str()); + all_gauges.insert(std::move(name)); + } + } + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_any_hud_gauge() +{ + sexp_list_item head; + + head.add_list(get_listing_opf_builtin_hud_gauge()); + head.add_list(get_listing_opf_custom_hud_gauge()); + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_ship_effect() +{ + sexp_list_item head; + + for (SCP_vector::iterator sei = Ship_effects.begin(); sei != Ship_effects.end(); ++sei) { + head.add_data(sei->name); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_explosion_option() +{ + sexp_list_item head; + + for (int i = 0; i < Num_explosion_options; i++) + head.add_data(Explosion_option[i]); + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_waypoint_path() +{ + sexp_list_item head; + + for (const auto &ii: Waypoint_lists) + head.add_data(ii.get_name()); + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_ship_point() +{ + sexp_list_item head; + + head.add_list(get_listing_opf_ship()); + head.add_list(get_listing_opf_point()); + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_ship_wing_wholeteam() +{ + int i; + sexp_list_item head; + + for (i = 0; i < (int)Iff_info.size(); i++) + head.add_data(Iff_info[i].iff_name); + + head.add_list(get_listing_opf_ship_wing()); + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_ship_wing_shiponteam_point() +{ + int i; + sexp_list_item head; + + for (i = 0; i < (int)Iff_info.size(); i++) + { + char tmp[NAME_LENGTH + 7]; + sprintf(tmp, "", Iff_info[i].iff_name); + strlwr(tmp); + head.add_data(tmp); + } + + head.add_list(get_listing_opf_ship_wing_point()); + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_ship_wing_point() +{ + sexp_list_item head; + + head.add_list(get_listing_opf_ship()); + head.add_list(get_listing_opf_wing()); + head.add_list(get_listing_opf_point()); + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_ship_wing_point_or_none() +{ + sexp_list_item head; + + head.add_data(SEXP_NONE_STRING); + head.add_list(get_listing_opf_ship_wing_point()); + + return head.next; +} + +// ----------------------------------------------------------------------- +// Batch 5: mission_name, goal_name, ship_wing, ship_prop, order_recipient, +// ship_type, keypress, event_name, ai_order, skill_level +// ----------------------------------------------------------------------- + +sexp_list_item *SexpTreeModel::get_listing_opf_mission_name() +{ + sexp_list_item head; + + for (auto& mission : _interface->getMissionNames()) { + head.add_data(mission.c_str()); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_goal_name(int parent_node) +{ + sexp_list_item head; + + Assert(parent_node >= 0); + int child = tree_nodes[parent_node].child; + + // reference_name is used by campaign editor to filter goals for a specific mission + SCP_string reference_name = (child >= 0) ? tree_nodes[child].text : ""; + + for (auto& entry : _interface->getMissionGoals(reference_name)) { + head.add_data(entry.c_str()); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_ship_wing() +{ + sexp_list_item head; + + head.add_list(get_listing_opf_ship()); + head.add_list(get_listing_opf_wing()); + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_ship_prop() +{ + sexp_list_item head; + + head.add_list(get_listing_opf_ship()); + head.add_list(get_listing_opf_prop()); + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_order_recipient() +{ + sexp_list_item head; + + head.add_data(""); + + head.add_list(get_listing_opf_ship()); + head.add_list(get_listing_opf_wing()); + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_ship_type() +{ + unsigned int i; + sexp_list_item head; + + for (i=0; i= 0) && !Control_config[i].disabled) { + head.add_data(textify_scancode_universal(btn)); + } + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_event_name(int parent_node) +{ + sexp_list_item head; + + Assert(parent_node >= 0); + int child = tree_nodes[parent_node].child; + + // reference_name is used by campaign editor to filter events for a specific mission + SCP_string reference_name = (child >= 0) ? tree_nodes[child].text : ""; + + for (auto& entry : _interface->getMissionEvents(reference_name)) { + head.add_data(entry.c_str()); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_ai_order() +{ + sexp_list_item head; + + for (const auto& order : Player_orders) + head.add_data(order.hud_name.c_str()); + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_skill_level() +{ + int i; + sexp_list_item head; + + for (i=0; i 0)) + continue; + head.add_data(Medals[i].name); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_weapon_name() +{ + sexp_list_item head; + + for (auto &wi : Weapon_info) + head.add_data(wi.name); + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_intel_name() +{ + sexp_list_item head; + + for (auto &ii : Intel_info) + head.add_data(ii.name); + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_ship_class_name() +{ + sexp_list_item head; + + for (auto &si : Ship_info) + head.add_data(si.name); + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_prop_class_name() +{ + sexp_list_item head; + + for (auto& pi : Prop_info) + head.add_data(pi.name.c_str()); + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_huge_weapon() +{ + sexp_list_item head; + + for (auto &wi : Weapon_info) { + if (wi.wi_flags[Weapon::Info_Flags::Huge]) + head.add_data(wi.name); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_ship_not_player() +{ + object *ptr; + sexp_list_item head; + + ptr = GET_FIRST(&obj_used_list); + while (ptr != END_OF_LIST(&obj_used_list)) { + if (ptr->type == OBJ_SHIP) + head.add_data(Ships[ptr->instance].ship_name); + + ptr = GET_NEXT(ptr); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_ship_or_none() +{ + sexp_list_item head; + + head.add_data(SEXP_NONE_STRING); + head.add_list(get_listing_opf_ship()); + + return head.next; +} + +// ----------------------------------------------------------------------- +// Batch 7: subsystem_or_none, subsys_or_generic, jump_nodes, +// variable_names, skybox_model, skybox_flags, background_bitmap, +// sun_bitmap, nebula_storm_type, nebula_poof +// ----------------------------------------------------------------------- + +sexp_list_item *SexpTreeModel::get_listing_opf_subsystem_or_none(int parent_node, int arg_index) +{ + sexp_list_item head; + + head.add_data(SEXP_NONE_STRING); + head.add_list(get_listing_opf_subsystem(parent_node, arg_index)); + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_subsys_or_generic(int parent_node, int arg_index) +{ + sexp_list_item head; + char buffer[NAME_LENGTH]; + + for (int i = 0; i < SUBSYSTEM_MAX; ++i) + { + // it's not clear what the "activator" subsystem was intended to do, so let's not display it by default + if (i != SUBSYSTEM_NONE && i != SUBSYSTEM_UNKNOWN && i != SUBSYSTEM_ACTIVATION) + { + sprintf(buffer, SEXP_ALL_GENERIC_SUBSYSTEM_STRING, Subsystem_types[i]); + SCP_tolower(buffer); + head.add_data(buffer); + } + } + head.add_data(SEXP_ALL_SUBSYSTEMS_STRING); + head.add_list(get_listing_opf_subsystem(parent_node, arg_index)); + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_jump_nodes() +{ + sexp_list_item head; + + SCP_list::iterator jnp; + for (jnp = Jump_nodes.begin(); jnp != Jump_nodes.end(); ++jnp) { + head.add_data( jnp->GetName()); + } + + return head.next; +} + +// creates list of Sexp_variables +sexp_list_item *SexpTreeModel::get_listing_opf_variable_names() +{ + int i; + sexp_list_item head; + + for (i=0; i ppe_names; + gr_get_post_process_effect_names(ppe_names); + for (i=0; i < ppe_names.size(); i++) { + head.add_data(ppe_names[i].c_str()); + } + head.add_data("lightshafts"); + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_turret_target_priorities() +{ + size_t t; + sexp_list_item head; + + for(t = 0; t < Ai_tp_list.size(); t++) { + head.add_data(Ai_tp_list[t].name); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_armor_type() +{ + size_t t; + sexp_list_item head; + head.add_data(SEXP_NONE_STRING); + for (t=0; t::iterator iter = Builtin_moods.begin(); iter != Builtin_moods.end(); ++iter) { + head.add_data(iter->c_str()); + } + + return head.next; +} + +// ----------------------------------------------------------------------- +// Batch 9: ship_flags, wing_flags, team_colors, nebula_patterns, +// asteroid_types, debris_types, motion_debris, game_snds, +// fireball, species +// ----------------------------------------------------------------------- + +// Helper template for flag name lists with deduplication +template +static void add_flag_name_helper(M& flag_name_map, sexp_list_item& head, T flag_name_array[], PTM T::* member, size_t flag_name_count) +{ + for (size_t i = 0; i < flag_name_count; i++) + { + auto name = flag_name_array[i].*member; + if (flag_name_map.count(name) == 0) + { + head.add_data(name); + flag_name_map.insert(name); + } + } +} + +sexp_list_item *SexpTreeModel::get_listing_opf_ship_flags() +{ + sexp_list_item head; + // prevent duplicate names, comparing case-insensitively + SCP_unordered_set all_flags; + + add_flag_name_helper(all_flags, head, Object_flag_names, &obj_flag_name::flag_name, (size_t)Num_object_flag_names); + add_flag_name_helper(all_flags, head, Ship_flag_names, &ship_flag_name::flag_name, Num_ship_flag_names); + add_flag_name_helper(all_flags, head, Parse_object_flags, &flag_def_list_new::name, Num_parse_object_flags); + add_flag_name_helper(all_flags, head, Ai_flag_names, &ai_flag_name::flag_name, (size_t)Num_ai_flag_names); + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_wing_flags() +{ + size_t i; + sexp_list_item head; + // wing flags + for ( i = 0; i < Num_wing_flag_names; i++) { + head.add_data(Wing_flag_names[i].flag_name); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_team_colors() +{ + sexp_list_item head; + head.add_data("None"); + for (SCP_map::iterator tcolor = Team_Colors.begin(); tcolor != Team_Colors.end(); ++tcolor) { + head.add_data(tcolor->first.c_str()); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_nebula_patterns() +{ + sexp_list_item head; + + head.add_data(SEXP_NONE_STRING); + + for (int i = 0; i < (int)Neb2_bitmap_filenames.size(); i++) { + head.add_data(Neb2_bitmap_filenames[i].c_str()); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_asteroid_types() +{ + sexp_list_item head; + + head.add_data(SEXP_NONE_STRING); + + auto list = get_list_valid_asteroid_subtypes(); + + for (const auto& this_asteroid : list) { + head.add_data(this_asteroid.c_str()); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_debris_types() +{ + sexp_list_item head; + + head.add_data(SEXP_NONE_STRING); + + for (const auto& this_asteroid : Asteroid_info) { + if (this_asteroid.type == ASTEROID_TYPE_DEBRIS) { + head.add_data(this_asteroid.name); + } + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_motion_debris() +{ + sexp_list_item head; + + head.add_data(SEXP_NONE_STRING); + + for (int i = 0; i < (int)Motion_debris_info.size(); i++) { + head.add_data(Motion_debris_info[i].name.c_str()); + } + + return head.next; +} + +extern SCP_vector Snds; + +sexp_list_item *SexpTreeModel::get_listing_opf_game_snds() +{ + sexp_list_item head; + + head.add_data(SEXP_NONE_STRING); + + for (SCP_vector::iterator iter = Snds.begin(); iter != Snds.end(); ++iter) { + if (!can_construe_as_integer(iter->name.c_str())) { + head.add_data(iter->name.c_str()); + } + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_fireball() +{ + sexp_list_item head; + + for (const auto &fi: Fireball_info) + { + auto unique_id = fi.unique_id; + + if (strlen(unique_id) > 0) + head.add_data(unique_id); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_species() // NOLINT +{ + sexp_list_item head; + + for (auto &species : Species_info) + head.add_data(species.species_name); + + return head.next; +} + +// ----------------------------------------------------------------------- +// Batch 10: language, functional_when_eval_type, animation_name, +// sexp_containers, wing_formation, bolt_types, +// traitor_overrides, lua_general_orders, message_types, +// mission_custom_strings, lua_enum +// ----------------------------------------------------------------------- + +sexp_list_item *SexpTreeModel::get_listing_opf_language() // NOLINT +{ + sexp_list_item head; + + for (auto &lang: Lcl_languages) + head.add_data(lang.lang_name); + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_functional_when_eval_type() // NOLINT +{ + sexp_list_item head; + + for (int i = 0; i < Num_functional_when_eval_types; i++) + head.add_data(Functional_when_eval_type[i]); + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_animation_name(int parent_node) +{ + int op, child, sh; + sexp_list_item head; + + Assert(parent_node >= 0); + + // get the operator type of the node + op = get_operator_const(tree_nodes[parent_node].text); + + // first child node + child = tree_nodes[parent_node].child; + if (child < 0) + return nullptr; + sh = ship_name_lookup(tree_nodes[child].text, 1); + + switch(op) { + case OP_TRIGGER_ANIMATION_NEW: + case OP_STOP_LOOPING_ANIMATION: { + child = tree_nodes[child].next; + auto triggerType = animation::anim_match_type(tree_nodes[child].text); + + for(const auto& anim : Ship_info[Ships[sh].ship_info_index].animations.getRegisteredTriggers()){ + if(anim.type != triggerType) + continue; + + if(anim.subtype != animation::ModelAnimationSet::SUBTYPE_DEFAULT) { + int animationSubtype = anim.subtype; + + if(anim.type == animation::ModelAnimationTriggerType::DockBayDoor){ + //Because of the old system, this is this weird exception. Don't explicitly suggest the NOT doors, as they cannot be explicitly targeted anyways + if(anim.subtype < 0) + continue; + + animationSubtype--; + } + + head.add_data(std::to_string(animationSubtype).c_str()); + } + else + head.add_data(anim.name.c_str()); + } + + break; + } + + case OP_UPDATE_MOVEABLE: + for(const auto& moveable : Ship_info[Ships[sh].ship_info_index].animations.getRegisteredMoveables()) + head.add_data(moveable.c_str()); + + break; + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_sexp_containers(ContainerType con_type) +{ + sexp_list_item head; + + for (const auto &container : get_all_sexp_containers()) { + if (any(container.type & con_type)) { + head.add_data(container.container_name.c_str(), (SEXPT_CONTAINER_NAME | SEXPT_STRING | SEXPT_VALID)); + } + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_wing_formation() // NOLINT +{ + sexp_list_item head; + + head.add_data("Default"); + for (const auto &formation : Wing_formations) + head.add_data(formation.name); + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_bolt_types() +{ + sexp_list_item head; + + head.add_data(SEXP_NONE_STRING); + + for (int i = 0; i < (int)Bolt_types.size(); i++) { + head.add_data(Bolt_types[i].name); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_traitor_overrides() +{ + sexp_list_item head; + + head.add_data(SEXP_NONE_STRING); + + for (int i = 0; i < (int)Traitor_overrides.size(); i++) { + head.add_data(Traitor_overrides[i].name.c_str()); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_lua_general_orders() +{ + sexp_list_item head; + + SCP_vector orders = ai_lua_get_general_orders(); + + for (const auto& val : orders) { + head.add_data(val.c_str()); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_message_types() +{ + sexp_list_item head; + + for (const auto& val : Builtin_messages) { + head.add_data(val.name); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_mission_custom_strings() +{ + sexp_list_item head; + + for (const auto& val : The_mission.custom_strings) { + head.add_data(val.name.c_str()); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_lua_enum(int parent_node, int arg_index) +{ + // first child node + int child = tree_nodes[parent_node].child; + if (child < 0) + return nullptr; + + int this_index = get_dynamic_parameter_index(tree_nodes[parent_node].text, arg_index); + + if (this_index >= 0) { + for (int count = 0; count < this_index; count++) { + child = tree_nodes[child].next; + } + } else { + error_display(1, + "Expected to find an enum parent parameter for node %i in operator %s but found nothing!", + arg_index, + tree_nodes[parent_node].text); + return nullptr; + } + + // Append the suffix if it exists + SCP_string enum_name = tree_nodes[child].text + get_child_enum_suffix(tree_nodes[parent_node].text, arg_index); + + sexp_list_item head; + + int item = get_dynamic_enum_position(enum_name); + + if (item >= 0 && item < static_cast(Dynamic_enums.size())) { + + for (const SCP_string& enum_item : Dynamic_enums[item].list) { + head.add_data(enum_item.c_str()); + } + } else { + // else if enum is invalid do this + mprintf(("Could not find Lua Enum %s! Using instead!", enum_name.c_str())); + head.add_data(""); + } + return head.next; +} + +// ----------------------------------------------------------------------- +// Batch 11: subsystem, subsystem_type, container modifiers, +// check_for_dynamic_sexp_enum, is_container_name_opf_type +// ----------------------------------------------------------------------- + +// specific types of subsystems we're looking for +#define OPS_CAP_CARGO 1 +#define OPS_STRENGTH 2 +#define OPS_BEAM_TURRET 3 +#define OPS_AWACS 4 +#define OPS_ROTATE 5 +#define OPS_TRANSLATE 6 +#define OPS_ARMOR 7 + +sexp_list_item *SexpTreeModel::get_listing_opf_subsystem(int parent_node, int arg_index) +{ + int op, child, sh; + int special_subsys = 0; + sexp_list_item head; + ship_subsys *subsys; + + // determine if the parent is one of the set subsystem strength items. If so, + // we want to append the "Hull" name onto the end of the menu + Assert(parent_node >= 0); + + // get the operator type of the node + op = get_operator_const(tree_nodes[parent_node].text); + + // first child node + child = tree_nodes[parent_node].child; + if (child < 0) + return nullptr; + + switch(op) + { + // where we care about hull strength + case OP_REPAIR_SUBSYSTEM: + case OP_SABOTAGE_SUBSYSTEM: + case OP_SET_SUBSYSTEM_STRNGTH: + special_subsys = OPS_STRENGTH; + break; + + // Armor types need Hull and Shields but not Simulated Hull + case OP_SET_ARMOR_TYPE: + case OP_HAS_ARMOR_TYPE: + special_subsys = OPS_ARMOR; + break; + + // awacs subsystems + case OP_AWACS_SET_RADIUS: + special_subsys = OPS_AWACS; + break; + + // rotating + case OP_LOCK_ROTATING_SUBSYSTEM: + case OP_FREE_ROTATING_SUBSYSTEM: + case OP_REVERSE_ROTATING_SUBSYSTEM: + case OP_ROTATING_SUBSYS_SET_TURN_TIME: + special_subsys = OPS_ROTATE; + break; + + // translating + case OP_LOCK_TRANSLATING_SUBSYSTEM: + case OP_FREE_TRANSLATING_SUBSYSTEM: + case OP_REVERSE_TRANSLATING_SUBSYSTEM: + case OP_TRANSLATING_SUBSYS_SET_SPEED: + special_subsys = OPS_TRANSLATE; + break; + + // where we care about capital ship subsystem cargo + case OP_CAP_SUBSYS_CARGO_KNOWN_DELAY: + special_subsys = OPS_CAP_CARGO; + + // get the next sibling + child = tree_nodes[child].next; + break; + + // where we care about turrets carrying beam weapons + case OP_BEAM_FIRE: + special_subsys = OPS_BEAM_TURRET; + + // if this is arg index 3 (targeted ship) + if(arg_index == 3) + { + special_subsys = OPS_STRENGTH; + + // iterate to the next field two times + child = tree_nodes[child].next; + if (child < 0) return nullptr; + child = tree_nodes[child].next; + } + else + { + Assert(arg_index == 1); + } + break; + + case OP_BEAM_FIRE_COORDS: + special_subsys = OPS_BEAM_TURRET; + break; + + // these sexps check the subsystem of the *second entry* on the list, not the first + case OP_DISTANCE_CENTER_SUBSYSTEM: + case OP_DISTANCE_BBOX_SUBSYSTEM: + case OP_SET_CARGO: + case OP_IS_CARGO: + case OP_CHANGE_AI_CLASS: + case OP_IS_AI_CLASS: + case OP_MISSILE_LOCKED: + case OP_SHIP_SUBSYS_GUARDIAN_THRESHOLD: + case OP_IS_IN_TURRET_FOV: + case OP_TURRET_SET_FORCED_TARGET: + // iterate to the next field + child = tree_nodes[child].next; + break; + + // this sexp checks the subsystem of the *fourth entry* on the list + case OP_QUERY_ORDERS: + // iterate to the next field three times + child = tree_nodes[child].next; + if (child < 0) return nullptr; + child = tree_nodes[child].next; + if (child < 0) return nullptr; + child = tree_nodes[child].next; + break; + + // this sexp checks the subsystem of the *seventh entry* on the list + case OP_BEAM_FLOATING_FIRE: + // iterate to the next field six times + child = tree_nodes[child].next; + if (child < 0) return nullptr; + child = tree_nodes[child].next; + if (child < 0) return nullptr; + child = tree_nodes[child].next; + if (child < 0) return nullptr; + child = tree_nodes[child].next; + if (child < 0) return nullptr; + child = tree_nodes[child].next; + if (child < 0) return nullptr; + child = tree_nodes[child].next; + break; + + // this sexp checks the subsystem of the *ninth entry* on the list + case OP_WEAPON_CREATE: + // iterate to the next field eight times + child = tree_nodes[child].next; + if (child < 0) return nullptr; + child = tree_nodes[child].next; + if (child < 0) return nullptr; + child = tree_nodes[child].next; + if (child < 0) return nullptr; + child = tree_nodes[child].next; + if (child < 0) return nullptr; + child = tree_nodes[child].next; + if (child < 0) return nullptr; + child = tree_nodes[child].next; + if (child < 0) return nullptr; + child = tree_nodes[child].next; + if (child < 0) return nullptr; + child = tree_nodes[child].next; + break; + + // this sexp checks the third entry, but only for the 4th argument + case OP_TURRET_SET_FORCED_SUBSYS_TARGET: + if (arg_index >= 3) { + child = tree_nodes[child].next; + if (child < 0) return nullptr; + child = tree_nodes[child].next; + } + break; + + default: + if (op < First_available_operator_id) { + break; + } else { + int this_index = get_dynamic_parameter_index(tree_nodes[parent_node].text, arg_index); + + if (this_index >= 0) { + for (int count = 0; count < this_index; count++) { + child = tree_nodes[child].next; + } + } else { + error_display(1, "Expected to find a dynamic lua parent parameter for node %i in operator %s but found nothing!", + arg_index, + tree_nodes[parent_node].text); + } + } + } + + if (child < 0) + return nullptr; + + + // if one of the subsystem strength operators, append the Hull string and the Simulated Hull string + if (special_subsys == OPS_STRENGTH) { + head.add_data(SEXP_HULL_STRING); + head.add_data(SEXP_SIM_HULL_STRING); + } + + // if setting armor type we only need Hull and Shields + if (special_subsys == OPS_ARMOR) { + head.add_data(SEXP_HULL_STRING); + head.add_data(SEXP_SHIELD_STRING); + } + + + // now find the ship and add all relevant subsystems + sh = ship_name_lookup(tree_nodes[child].text, 1); + if (sh >= 0) { + subsys = GET_FIRST(&Ships[sh].subsys_list); + while (subsys != END_OF_LIST(&Ships[sh].subsys_list)) { + // add stuff + switch(special_subsys){ + // subsystem cargo + case OPS_CAP_CARGO: + head.add_data(subsys->system_info->subobj_name); + break; + + // beam fire + case OPS_BEAM_TURRET: + head.add_data(subsys->system_info->subobj_name); + break; + + // awacs level + case OPS_AWACS: + if (subsys->system_info->flags[Model::Subsystem_Flags::Awacs]) { + head.add_data(subsys->system_info->subobj_name); + } + break; + + // rotating + case OPS_ROTATE: + if (subsys->system_info->flags[Model::Subsystem_Flags::Rotates]) { + head.add_data(subsys->system_info->subobj_name); + } + break; + + // translating + case OPS_TRANSLATE: + if (subsys->system_info->flags[Model::Subsystem_Flags::Translates]) { + head.add_data(subsys->system_info->subobj_name); + } + break; + + // everything else + default: + head.add_data(subsys->system_info->subobj_name); + break; + } + + // next subsystem + subsys = GET_NEXT(subsys); + } + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_listing_opf_subsystem_type(int parent_node) +{ + int i, child, shipnum, num_added = 0; + sexp_list_item head; + ship_subsys *subsys; + + // first child node + child = tree_nodes[parent_node].child; + if (child < 0) + return nullptr; + + // now find the ship + shipnum = ship_name_lookup(tree_nodes[child].text, 1); + if (shipnum < 0) { + return head.next; + } + + // add all relevant subsystem types + for (i = 0; i < SUBSYSTEM_MAX; i++) { + // don't allow these two + if (i == SUBSYSTEM_NONE || i == SUBSYSTEM_UNKNOWN) + continue; + + // loop through all ship subsystems + subsys = GET_FIRST(&Ships[shipnum].subsys_list); + while (subsys != END_OF_LIST(&Ships[shipnum].subsys_list)) { + // check if this subsystem is of this type + if (i == subsys->system_info->type) { + // subsystem type is applicable, so add it + head.add_data(Subsystem_types[i]); + num_added++; + break; + } + + // next subsystem + subsys = GET_NEXT(subsys); + } + } + + // if no subsystem types, go ahead and add NONE (even though it won't be checked) + if (num_added == 0) { + head.add_data(Subsystem_types[SUBSYSTEM_NONE]); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_container_modifiers(int con_data_node) const +{ + Assertion(con_data_node != -1, "Attempt to get modifiers for invalid container node. Please report!"); + Assertion(tree_nodes[con_data_node].type & SEXPT_CONTAINER_DATA, + "Attempt to get modifiers for non-container data node %s. Please report!", + tree_nodes[con_data_node].text); + + const auto *p_container = get_sexp_container(tree_nodes[con_data_node].text); + Assertion(p_container, + "Attempt to get modifiers for unknown container %s. Please report!", + tree_nodes[con_data_node].text); + const auto &container = *p_container; + + sexp_list_item head; + sexp_list_item *list = nullptr; + + if (container.is_list()) { + list = get_list_container_modifiers(); + } else if (container.is_map()) { + // start the list with "" if relevant + if (is_node_eligible_for_special_argument(con_data_node) && + any(container.type & ContainerType::STRING_KEYS)) { + head.add_data(SEXP_ARGUMENT_STRING, (SEXPT_VALID | SEXPT_STRING | SEXPT_MODIFIER)); + } + + list = get_map_container_modifiers(con_data_node); + } else { + UNREACHABLE("Unknown container type %d", (int)p_container->type); + } + + if (list) { + head.add_list(list); + } + + return head.next; +} + +sexp_list_item *SexpTreeModel::get_list_container_modifiers() const +{ + sexp_list_item head; + + for (const auto &modifier : get_all_list_modifiers()) { + head.add_data(modifier.name, SEXPT_VALID | SEXPT_MODIFIER | SEXPT_STRING); + } + + return head.next; +} + +// FIXME TODO: if you use this function with remove-from-map SEXP, don't use SEXPT_MODIFIER +sexp_list_item *SexpTreeModel::get_map_container_modifiers(int con_data_node) const +{ + sexp_list_item head; + + Assertion(tree_nodes[con_data_node].type & SEXPT_CONTAINER_DATA, + "Found map modifier for non-container data node %s. Please report!", + tree_nodes[con_data_node].text); + + const auto *p_container = get_sexp_container(tree_nodes[con_data_node].text); + Assertion(p_container != nullptr, + "Found map modifier for unknown container %s. Please report!", + tree_nodes[con_data_node].text); + + const auto &container = *p_container; + Assertion(container.is_map(), + "Found map modifier for non-map container %s with type %d. Please report!", + tree_nodes[con_data_node].text, + (int)container.type); + + int type = SEXPT_VALID | SEXPT_MODIFIER; + if (any(container.type & ContainerType::STRING_KEYS)) { + type |= SEXPT_STRING; + } else if (any(container.type & ContainerType::NUMBER_KEYS)) { + type |= SEXPT_NUMBER; + } else { + UNREACHABLE("Unknown map container key type %d", (int)container.type); + } + + for (const auto &kv_pair : container.map_data) { + head.add_data(kv_pair.first.c_str(), type); + } + + return head.next; +} + +// get potential options for container multidimensional modifiers +// the value could be either string or number, checked in-mission +sexp_list_item *SexpTreeModel::get_container_multidim_modifiers(int con_data_node) const +{ + Assertion(con_data_node != -1, + "Attempt to get multidimensional modifiers for invalid container node. Please report!"); + Assertion(tree_nodes[con_data_node].type & SEXPT_CONTAINER_DATA, + "Attempt to get multidimensional modifiers for non-container data node %s. Please report!", + tree_nodes[con_data_node].text); + + sexp_list_item head; + + if (is_node_eligible_for_special_argument(con_data_node)) { + head.add_data(SEXP_ARGUMENT_STRING, (SEXPT_VALID | SEXPT_STRING | SEXPT_MODIFIER)); + } + + // the FREDder might want to use a list modifier + sexp_list_item *list = get_list_container_modifiers(); + + head.add_list(list); + + return head.next; +} + +sexp_list_item *SexpTreeModel::check_for_dynamic_sexp_enum(int opf) +{ + sexp_list_item head; + + int item = opf - First_available_opf_id; + + if (item < (int)Dynamic_enums.size()) { + + for (const SCP_string& enum_item : Dynamic_enums[item].list) { + head.add_data(enum_item.c_str()); + } + return head.next; + } else { + // else if opf is invalid do this + UNREACHABLE("Unhandled SEXP argument type!"); // unknown OPF code + return nullptr; + } +} + +bool SexpTreeModel::is_container_name_opf_type(const int op_type) +{ + return (op_type == OPF_CONTAINER_NAME) || + (op_type == OPF_LIST_CONTAINER_NAME) || + (op_type == OPF_MAP_CONTAINER_NAME); +} + +// ----------------------------------------------------------------------- +// Main OPF dispatch function +// ----------------------------------------------------------------------- + +sexp_list_item *SexpTreeModel::get_listing_opf(int opf, int parent_node, int arg_index) +{ + sexp_list_item head; + sexp_list_item *list = NULL; + + switch (opf) { + case OPF_NONE: + list = NULL; + break; + + case OPF_NULL: + list = get_listing_opf_null(); + break; + + case OPF_BOOL: + list = get_listing_opf_bool(parent_node); + break; + + case OPF_NUMBER: + list = get_listing_opf_number(); + break; + + case OPF_SHIP: + list = get_listing_opf_ship(parent_node); + break; + + case OPF_PROP: + list = get_listing_opf_prop(); + break; + + case OPF_WING: + list = get_listing_opf_wing(); + break; + + case OPF_AWACS_SUBSYSTEM: + case OPF_ROTATING_SUBSYSTEM: + case OPF_TRANSLATING_SUBSYSTEM: + case OPF_SUBSYSTEM: + list = get_listing_opf_subsystem(parent_node, arg_index); + break; + + case OPF_SUBSYSTEM_TYPE: + list = get_listing_opf_subsystem_type(parent_node); + break; + + case OPF_POINT: + list = get_listing_opf_point(); + break; + + case OPF_IFF: + list = get_listing_opf_iff(); + break; + + case OPF_AI_CLASS: + list = get_listing_opf_ai_class(); + break; + + case OPF_SUPPORT_SHIP_CLASS: + list = get_listing_opf_support_ship_class(); + break; + + case OPF_SSM_CLASS: + list = get_listing_opf_ssm_class(); + break; + + case OPF_ARRIVAL_LOCATION: + list = get_listing_opf_arrival_location(); + break; + + case OPF_DEPARTURE_LOCATION: + list = get_listing_opf_departure_location(); + break; + + case OPF_ARRIVAL_ANCHOR_ALL: + list = get_listing_opf_arrival_anchor_all(); + break; + + case OPF_SHIP_WITH_BAY: + list = get_listing_opf_ship_with_bay(); + break; + + case OPF_SOUNDTRACK_NAME: + list = get_listing_opf_soundtrack_name(); + break; + + case OPF_AI_GOAL: + list = get_listing_opf_ai_goal(parent_node); + break; + + case OPF_FLEXIBLE_ARGUMENT: + list = get_listing_opf_flexible_argument(); + break; + + case OPF_DOCKER_POINT: + list = get_listing_opf_docker_point(parent_node, arg_index); + break; + + case OPF_DOCKEE_POINT: + list = get_listing_opf_dockee_point(parent_node); + break; + + case OPF_MESSAGE: + list = get_listing_opf_message(); + break; + + case OPF_WHO_FROM: + list = get_listing_opf_who_from(); + break; + + case OPF_PRIORITY: + list = get_listing_opf_priority(); + break; + + case OPF_WAYPOINT_PATH: + list = get_listing_opf_waypoint_path(); + break; + + case OPF_POSITIVE: + list = get_listing_opf_positive(); + break; + + case OPF_MISSION_NAME: + list = get_listing_opf_mission_name(); + break; + + case OPF_SHIP_POINT: + list = get_listing_opf_ship_point(); + break; + + case OPF_GOAL_NAME: + list = get_listing_opf_goal_name(parent_node); + break; + + case OPF_SHIP_WING: + list = get_listing_opf_ship_wing(); + break; + + case OPF_SHIP_PROP: + list = get_listing_opf_ship_prop(); + break; + + case OPF_SHIP_WING_WHOLETEAM: + list = get_listing_opf_ship_wing_wholeteam(); + break; + + case OPF_SHIP_WING_SHIPONTEAM_POINT: + list = get_listing_opf_ship_wing_shiponteam_point(); + break; + + case OPF_SHIP_WING_POINT: + list = get_listing_opf_ship_wing_point(); + break; + + case OPF_SHIP_WING_POINT_OR_NONE: + list = get_listing_opf_ship_wing_point_or_none(); + break; + + case OPF_ORDER_RECIPIENT: + list = get_listing_opf_order_recipient(); + break; + + case OPF_SHIP_TYPE: + list = get_listing_opf_ship_type(); + break; + + case OPF_KEYPRESS: + list = get_listing_opf_keypress(); + break; + + case OPF_EVENT_NAME: + list = get_listing_opf_event_name(parent_node); + break; + + case OPF_AI_ORDER: + list = get_listing_opf_ai_order(); + break; + + case OPF_SKILL_LEVEL: + list = get_listing_opf_skill_level(); + break; + + case OPF_CARGO: + list = get_listing_opf_cargo(); + break; + + case OPF_STRING: + list = get_listing_opf_string(); + break; + + case OPF_MEDAL_NAME: + list = get_listing_opf_medal_name(); + break; + + case OPF_WEAPON_NAME: + list = get_listing_opf_weapon_name(); + break; + + case OPF_INTEL_NAME: + list = get_listing_opf_intel_name(); + break; + + case OPF_SHIP_CLASS_NAME: + list = get_listing_opf_ship_class_name(); + break; + + case OPF_PROP_CLASS_NAME: + list = get_listing_opf_prop_class_name(); + break; + + case OPF_HUGE_WEAPON: + list = get_listing_opf_huge_weapon(); + break; + + case OPF_SHIP_NOT_PLAYER: + list = get_listing_opf_ship_not_player(); + break; + + case OPF_SHIP_OR_NONE: + list = get_listing_opf_ship_or_none(); + break; + + case OPF_SUBSYSTEM_OR_NONE: + list = get_listing_opf_subsystem_or_none(parent_node, arg_index); + break; + + case OPF_SUBSYS_OR_GENERIC: + list = get_listing_opf_subsys_or_generic(parent_node, arg_index); + break; + + case OPF_JUMP_NODE_NAME: + list = get_listing_opf_jump_nodes(); + break; + + case OPF_VARIABLE_NAME: + list = get_listing_opf_variable_names(); + break; + + case OPF_AMBIGUOUS: + list = NULL; + break; + + case OPF_ANYTHING: + list = NULL; + break; + + case OPF_SKYBOX_MODEL_NAME: + list = get_listing_opf_skybox_model(); + break; + + case OPF_SKYBOX_FLAGS: + list = get_listing_opf_skybox_flags(); + break; + + case OPF_BACKGROUND_BITMAP: + list = get_listing_opf_background_bitmap(); + break; + + case OPF_SUN_BITMAP: + list = get_listing_opf_sun_bitmap(); + break; + + case OPF_NEBULA_STORM_TYPE: + list = get_listing_opf_nebula_storm_type(); + break; + + case OPF_NEBULA_POOF: + list = get_listing_opf_nebula_poof(); + break; + + case OPF_TURRET_TARGET_ORDER: + list = get_listing_opf_turret_target_order(); + break; + + case OPF_TURRET_TYPE: + list = get_listing_opf_turret_types(); + break; + + case OPF_TARGET_PRIORITIES: + list = get_listing_opf_turret_target_priorities(); + break; + + case OPF_ARMOR_TYPE: + list = get_listing_opf_armor_type(); + break; + + case OPF_DAMAGE_TYPE: + list = get_listing_opf_damage_type(); + break; + + case OPF_ANIMATION_TYPE: + list = get_listing_opf_animation_type(); + break; + + case OPF_PERSONA: + list = get_listing_opf_persona(); + break; + + case OPF_POST_EFFECT: + list = get_listing_opf_post_effect(); + break; + + case OPF_FONT: + list = get_listing_opf_font(); + break; + + case OPF_HUD_ELEMENT: + list = get_listing_opf_hud_elements(); + break; + + case OPF_SOUND_ENVIRONMENT: + list = get_listing_opf_sound_environment(); + break; + + case OPF_SOUND_ENVIRONMENT_OPTION: + list = get_listing_opf_sound_environment_option(); + break; + + case OPF_AUDIO_VOLUME_OPTION: + list = get_listing_opf_adjust_audio_volume(); + break; + + case OPF_EXPLOSION_OPTION: + list = get_listing_opf_explosion_option(); + break; + + case OPF_WEAPON_BANK_NUMBER: + list = get_listing_opf_weapon_banks(); + break; + + case OPF_MESSAGE_OR_STRING: + list = get_listing_opf_message(); + break; + + case OPF_BUILTIN_HUD_GAUGE: + list = get_listing_opf_builtin_hud_gauge(); + break; + + case OPF_CUSTOM_HUD_GAUGE: + list = get_listing_opf_custom_hud_gauge(); + break; + + case OPF_ANY_HUD_GAUGE: + list = get_listing_opf_any_hud_gauge(); + break; + + case OPF_SHIP_EFFECT: + list = get_listing_opf_ship_effect(); + break; + + case OPF_MISSION_MOOD: + list = get_listing_opf_mission_moods(); + break; + + case OPF_SHIP_FLAG: + list = get_listing_opf_ship_flags(); + break; + + case OPF_WING_FLAG: + list = get_listing_opf_wing_flags(); + break; + + case OPF_TEAM_COLOR: + list = get_listing_opf_team_colors(); + break; + + case OPF_NEBULA_PATTERN: + list = get_listing_opf_nebula_patterns(); + break; + + case OPF_GAME_SND: + list = get_listing_opf_game_snds(); + break; + + case OPF_FIREBALL: + list = get_listing_opf_fireball(); + break; + + case OPF_SPECIES: + list = get_listing_opf_species(); + break; + + case OPF_LANGUAGE: + list = get_listing_opf_language(); + break; + + case OPF_FUNCTIONAL_WHEN_EVAL_TYPE: + list = get_listing_opf_functional_when_eval_type(); + break; + + case OPF_ANIMATION_NAME: + list = get_listing_opf_animation_name(parent_node); + break; + + case OPF_CONTAINER_NAME: + list = get_listing_opf_sexp_containers(ContainerType::LIST | ContainerType::MAP); + break; + + case OPF_LIST_CONTAINER_NAME: + list = get_listing_opf_sexp_containers(ContainerType::LIST); + break; + + case OPF_MAP_CONTAINER_NAME: + list = get_listing_opf_sexp_containers(ContainerType::MAP); + break; + + case OPF_CONTAINER_VALUE: + list = nullptr; + break; + + case OPF_DATA_OR_STR_CONTAINER: + list = nullptr; + break; + + case OPF_ASTEROID_TYPES: + list = get_listing_opf_asteroid_types(); + break; + + case OPF_DEBRIS_TYPES: + list = get_listing_opf_debris_types(); + break; + + case OPF_WING_FORMATION: + list = get_listing_opf_wing_formation(); + break; + + case OPF_MOTION_DEBRIS: + list = get_listing_opf_motion_debris(); + break; + + case OPF_BOLT_TYPE: + list = get_listing_opf_bolt_types(); + break; + + case OPF_TRAITOR_OVERRIDE: + list = get_listing_opf_traitor_overrides(); + break; + + case OPF_LUA_GENERAL_ORDER: + list = get_listing_opf_lua_general_orders(); + break; + + case OPF_MESSAGE_TYPE: + list = get_listing_opf_message_types(); + break; + + case OPF_CHILD_LUA_ENUM: + list = get_listing_opf_lua_enum(parent_node, arg_index); + break; + + case OPF_MISSION_CUSTOM_STRING: + list = get_listing_opf_mission_custom_strings(); + break; + + default: + //We're at the end of the list so check for any dynamic enums + list = check_for_dynamic_sexp_enum(opf); + break; + } + + + // skip OPF_NONE, also skip for OPF_NULL, because it takes no data (though it can take plenty of operators) + if (opf == OPF_NULL || opf == OPF_NONE) { + return list; + } + + // skip the special argument if we aren't at the right spot in when-argument or + // every-time-argument + if (!is_node_eligible_for_special_argument(parent_node)) { + return list; + } + + // the special item is a string and should not be added for numeric lists + if (opf != OPF_NUMBER && opf != OPF_POSITIVE) { + head.add_data(SEXP_ARGUMENT_STRING); + } + + if (list != NULL) { + // append other list + head.add_list(list); + } + + // return listing + return head.next; +} From dc21402c3ad57f92c4fe25216aff00c7d01a88d9 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Wed, 24 Sep 2025 20:03:00 -0500 Subject: [PATCH 06/73] move shared functions into the model --- code/missioneditor/sexp_tree_model.cpp | 773 ++++++++++++++++++++++++- code/missioneditor/sexp_tree_model.h | 34 +- 2 files changed, 805 insertions(+), 2 deletions(-) diff --git a/code/missioneditor/sexp_tree_model.cpp b/code/missioneditor/sexp_tree_model.cpp index 50aab64f2c1..3cff9fee1f7 100644 --- a/code/missioneditor/sexp_tree_model.cpp +++ b/code/missioneditor/sexp_tree_model.cpp @@ -13,9 +13,22 @@ #include "missioneditor/sexp_tree_model.h" #include "parse/sexp.h" +#include "parse/sexp_container.h" #include "mission/missiongoals.h" #include "mission/missionmessage.h" #include "mission/missionparse.h" +#include "mission/missioncampaign.h" +#include "object/object.h" +#include "object/waypoint.h" +#include "ship/ship.h" +#include "ai/ailua.h" +#include "asteroid/asteroid.h" +#include "hud/hudartillery.h" +#include "nebula/neblightning.h" +#include "starfield/starfield.h" +#include "stats/scoring.h" + +#define TREE_NODE_INCREMENT 100 // ----------------------------------------------------------------------- // sexp_list_item implementation @@ -198,12 +211,546 @@ bool SexpTreeEditorInterface::requireCampaignOperators() const // ----------------------------------------------------------------------- SexpTreeModel::SexpTreeModel() - : total_nodes(0), m_mode(0), item_index(-1), _interface(nullptr) + : total_nodes(0), m_mode(0), item_index(-1), _interface(nullptr), modified(nullptr) { } SexpTreeModel::~SexpTreeModel() = default; +// ----------------------------------------------------------------------- +// Tree node management +// ----------------------------------------------------------------------- + +int SexpTreeModel::find_free_node() const +{ + for (int i = 0; i < (int)tree_nodes.size(); i++) { + if (tree_nodes[i].type == SEXPT_UNUSED) + return i; + } + + return -1; +} + +// allocate a node. Remains used until freed. +int SexpTreeModel::allocate_node() +{ + int node = find_free_node(); + + // need more tree nodes? + if (node < 0) { + int old_size = (int)tree_nodes.size(); + + Assert(TREE_NODE_INCREMENT > 0); + + // allocate in blocks of TREE_NODE_INCREMENT + tree_nodes.resize(tree_nodes.size() + TREE_NODE_INCREMENT); + + mprintf(("Bumping dynamic tree node limit from %d to %d...\n", old_size, (int)tree_nodes.size())); + +#ifndef NDEBUG + for (int i = old_size; i < (int)tree_nodes.size(); i++) { + sexp_tree_item* item = &tree_nodes[i]; + Assert(item->type == SEXPT_UNUSED); + } +#endif + + // our new sexp is the first out of the ones we just created + node = old_size; + } + + // reset the new node + tree_nodes[node].type = SEXPT_UNINIT; + tree_nodes[node].parent = -1; + tree_nodes[node].child = -1; + tree_nodes[node].next = -1; + tree_nodes[node].flags = 0; + strcpy_s(tree_nodes[node].text, ""); + tree_nodes[node].handle = nullptr; + + total_nodes++; + return node; +} + +// allocate a child node under 'parent'. Appends to end of list. +int SexpTreeModel::allocate_node(int parent, int after) +{ + int i, index = allocate_node(); + + if (parent != -1) { + i = tree_nodes[parent].child; + if (i == -1) { + tree_nodes[parent].child = index; + + } else { + while ((i != after) && (tree_nodes[i].next != -1)) + i = tree_nodes[i].next; + + tree_nodes[index].next = tree_nodes[i].next; + tree_nodes[i].next = index; + } + } + + tree_nodes[index].parent = parent; + return index; +} + +// initialize the data for a node. Should be called right after a new node is allocated. +void SexpTreeModel::set_node(int node, int type, const char* text) +{ + Assert(type != SEXPT_UNUSED); + Assert(tree_nodes[node].type != SEXPT_UNUSED); + tree_nodes[node].type = type; + size_t max_length; + if (type & SEXPT_VARIABLE) { + max_length = 2 * TOKEN_LENGTH + 2; + } else if (type & (SEXPT_CONTAINER_NAME | SEXPT_CONTAINER_DATA)) { + max_length = sexp_container::NAME_MAX_LENGTH + 1; + } else { + max_length = TOKEN_LENGTH; + } + Assert(strlen(text) < max_length); + strcpy_s(tree_nodes[node].text, text); +} + +// free a node and all its children. Also clears pointers to it, if any. +// node = node chain to free +// cascade = 0: free just this node and children under it. (default) +// !0: free this node and all siblings after it. +void SexpTreeModel::free_node(int node, int cascade) +{ + int i; + + // clear the pointer to node + i = tree_nodes[node].parent; + Assert(i != -1); + if (tree_nodes[i].child == node) + tree_nodes[i].child = tree_nodes[node].next; + + else { + i = tree_nodes[i].child; + while (tree_nodes[i].next != -1) { + if (tree_nodes[i].next == node) { + tree_nodes[i].next = tree_nodes[node].next; + break; + } + + i = tree_nodes[i].next; + } + } + + if (!cascade) + tree_nodes[node].next = -1; + + // now free up the node and its children + free_node2(node); +} + +// more simple node freer, which works recursively. It frees the given node and all siblings +// that come after it, as well as all children of these. Doesn't clear any links to any of +// these freed nodes, so make sure all links are broken first. (i.e. use free_node() if you can) +void SexpTreeModel::free_node2(int node) +{ + Assert(node != -1); + Assert(tree_nodes[node].type != SEXPT_UNUSED); + Assert(total_nodes > 0); + if (modified) + *modified = 1; + tree_nodes[node].type = SEXPT_UNUSED; + total_nodes--; + if (tree_nodes[node].child != -1) + free_node2(tree_nodes[node].child); + + if (tree_nodes[node].next != -1) + free_node2(tree_nodes[node].next); +} + +// ----------------------------------------------------------------------- +// Tree serialization +// ----------------------------------------------------------------------- + +// get variable name from sexp_tree node .text +static void var_name_from_sexp_tree_text(char* var_name, const char* text) +{ + auto var_name_length = strcspn(text, "("); + Assert(var_name_length < TOKEN_LENGTH - 1); + + strncpy(var_name, text, var_name_length); + var_name[var_name_length] = '\0'; +} + +// builds an sexp of the tree and returns the index of it. This allocates sexp nodes. +int SexpTreeModel::save_tree(int node) const +{ + Assert(node >= 0); + Assert(tree_nodes[node].type == (SEXPT_OPERATOR | SEXPT_VALID)); + Assert(tree_nodes[node].next == -1); // must make this assumption or else it will confuse code! + return save_branch(node); +} + +#define NO_PREVIOUS_NODE -9 +// called recursively to save a tree branch and everything under it +// SEXPT_CONTAINER_NAME and SEXPT_MODIFIER require no special handling here +int SexpTreeModel::save_branch(int cur, int at_root) const +{ + int start, node = -1, last = NO_PREVIOUS_NODE; + char var_name_text[TOKEN_LENGTH]; + + start = -1; + while (cur != -1) { + if (tree_nodes[cur].type & SEXPT_OPERATOR) { + node = alloc_sexp(tree_nodes[cur].text, SEXP_ATOM, SEXP_ATOM_OPERATOR, -1, save_branch(tree_nodes[cur].child)); + + if ((tree_nodes[cur].parent >= 0) && !at_root) { + node = alloc_sexp("", SEXP_LIST, SEXP_ATOM_LIST, node, -1); + } + } else if (tree_nodes[cur].type & SEXPT_CONTAINER_NAME) { + Assertion(get_sexp_container(tree_nodes[cur].text) != nullptr, + "Attempt to save unknown container %s from SEXP tree. Please report!", + tree_nodes[cur].text); + node = alloc_sexp(tree_nodes[cur].text, SEXP_ATOM, SEXP_ATOM_CONTAINER_NAME, -1, -1); + } else if (tree_nodes[cur].type & SEXPT_CONTAINER_DATA) { + Assertion(get_sexp_container(tree_nodes[cur].text) != nullptr, + "Attempt to save unknown container %s from SEXP tree. Please report!", + tree_nodes[cur].text); + node = alloc_sexp(tree_nodes[cur].text, SEXP_ATOM, SEXP_ATOM_CONTAINER_DATA, save_branch(tree_nodes[cur].child), -1); + } else if (tree_nodes[cur].type & SEXPT_NUMBER) { + // allocate number, maybe variable + if (tree_nodes[cur].type & SEXPT_VARIABLE) { + var_name_from_sexp_tree_text(var_name_text, tree_nodes[cur].text); + node = alloc_sexp(var_name_text, (SEXP_ATOM | SEXP_FLAG_VARIABLE), SEXP_ATOM_NUMBER, -1, -1); + } else { + node = alloc_sexp(tree_nodes[cur].text, SEXP_ATOM, SEXP_ATOM_NUMBER, -1, -1); + } + } else if (tree_nodes[cur].type & SEXPT_STRING) { + // allocate string, maybe variable + if (tree_nodes[cur].type & SEXPT_VARIABLE) { + var_name_from_sexp_tree_text(var_name_text, tree_nodes[cur].text); + node = alloc_sexp(var_name_text, (SEXP_ATOM | SEXP_FLAG_VARIABLE), SEXP_ATOM_STRING, -1, -1); + } else { + node = alloc_sexp(tree_nodes[cur].text, SEXP_ATOM, SEXP_ATOM_STRING, -1, -1); + } + } else { + Assert(0); // unknown and/or invalid type + } + + if (last == NO_PREVIOUS_NODE) { + start = node; + } else if (last >= 0) { + Sexp_nodes[last].rest = node; + } + + last = node; + Assert(last != NO_PREVIOUS_NODE); // should be impossible + cur = tree_nodes[cur].next; + if (at_root) { + return start; + } + } + + return start; +} + +// ----------------------------------------------------------------------- +// Default argument availability +// ----------------------------------------------------------------------- + +int SexpTreeModel::query_default_argument_available(int op) const +{ + int i; + + Assert(op >= 0); + for (i = 0; i < Operators[op].min; i++) + if (!query_default_argument_available(op, i)) + return 0; + + return 1; +} + +int SexpTreeModel::query_default_argument_available(int op, int i) const +{ + int j, type; + object* ptr; + + type = query_operator_argument_type(op, i); + switch (type) { + case OPF_NONE: + case OPF_NULL: + case OPF_BOOL: + case OPF_NUMBER: + case OPF_POSITIVE: + case OPF_IFF: + case OPF_AI_CLASS: + case OPF_WHO_FROM: + case OPF_PRIORITY: + case OPF_SHIP_TYPE: + case OPF_SUBSYSTEM: + case OPF_AWACS_SUBSYSTEM: + case OPF_ROTATING_SUBSYSTEM: + case OPF_TRANSLATING_SUBSYSTEM: + case OPF_SUBSYSTEM_TYPE: + case OPF_DOCKER_POINT: + case OPF_DOCKEE_POINT: + case OPF_AI_GOAL: + case OPF_KEYPRESS: + case OPF_AI_ORDER: + case OPF_SKILL_LEVEL: + case OPF_MEDAL_NAME: + case OPF_WEAPON_NAME: + case OPF_INTEL_NAME: + case OPF_SHIP_CLASS_NAME: + case OPF_PROP_CLASS_NAME: + case OPF_HUGE_WEAPON: + case OPF_JUMP_NODE_NAME: + case OPF_AMBIGUOUS: + case OPF_CARGO: + case OPF_ARRIVAL_LOCATION: + case OPF_DEPARTURE_LOCATION: + case OPF_ARRIVAL_ANCHOR_ALL: + case OPF_SUPPORT_SHIP_CLASS: + case OPF_SHIP_WITH_BAY: + case OPF_SOUNDTRACK_NAME: + case OPF_STRING: + case OPF_FLEXIBLE_ARGUMENT: + case OPF_ANYTHING: + case OPF_DATA_OR_STR_CONTAINER: + case OPF_SKYBOX_MODEL_NAME: + case OPF_SKYBOX_FLAGS: + case OPF_SHIP_OR_NONE: + case OPF_SUBSYSTEM_OR_NONE: + case OPF_SHIP_WING_POINT_OR_NONE: + case OPF_SUBSYS_OR_GENERIC: + case OPF_BACKGROUND_BITMAP: + case OPF_SUN_BITMAP: + case OPF_NEBULA_STORM_TYPE: + case OPF_NEBULA_POOF: + case OPF_TURRET_TARGET_ORDER: + case OPF_TURRET_TYPE: + case OPF_POST_EFFECT: + case OPF_TARGET_PRIORITIES: + case OPF_ARMOR_TYPE: + case OPF_DAMAGE_TYPE: + case OPF_FONT: + case OPF_HUD_ELEMENT: + case OPF_SOUND_ENVIRONMENT: + case OPF_SOUND_ENVIRONMENT_OPTION: + case OPF_EXPLOSION_OPTION: + case OPF_AUDIO_VOLUME_OPTION: + case OPF_WEAPON_BANK_NUMBER: + case OPF_MESSAGE_OR_STRING: + case OPF_BUILTIN_HUD_GAUGE: + case OPF_CUSTOM_HUD_GAUGE: + case OPF_ANY_HUD_GAUGE: + case OPF_SHIP_EFFECT: + case OPF_ANIMATION_TYPE: + case OPF_SHIP_FLAG: + case OPF_WING_FLAG: + case OPF_NEBULA_PATTERN: + case OPF_NAV_POINT: + case OPF_TEAM_COLOR: + case OPF_GAME_SND: + case OPF_FIREBALL: + case OPF_SPECIES: + case OPF_LANGUAGE: + case OPF_FUNCTIONAL_WHEN_EVAL_TYPE: + case OPF_ANIMATION_NAME: + case OPF_CONTAINER_VALUE: + case OPF_WING_FORMATION: + case OPF_CHILD_LUA_ENUM: + case OPF_MESSAGE_TYPE: + return 1; + + case OPF_SHIP: + case OPF_SHIP_WING: + case OPF_SHIP_POINT: + case OPF_SHIP_WING_POINT: + case OPF_SHIP_WING_WHOLETEAM: + case OPF_SHIP_WING_SHIPONTEAM_POINT: + ptr = GET_FIRST(&obj_used_list); + while (ptr != END_OF_LIST(&obj_used_list)) { + if (ptr->type == OBJ_SHIP || ptr->type == OBJ_START) + return 1; + + ptr = GET_NEXT(ptr); + } + + return 0; + + case OPF_SHIP_PROP: + ptr = GET_FIRST(&obj_used_list); + while (ptr != END_OF_LIST(&obj_used_list)) { + if (ptr->type == OBJ_SHIP || ptr->type == OBJ_START || ptr->type == OBJ_PROP) + return 1; + + ptr = GET_NEXT(ptr); + } + + return 0; + + case OPF_PROP: + ptr = GET_FIRST(&obj_used_list); + while (ptr != END_OF_LIST(&obj_used_list)) { + if (ptr->type == OBJ_PROP) + return 1; + + ptr = GET_NEXT(ptr); + } + return 0; + + case OPF_SHIP_NOT_PLAYER: + case OPF_ORDER_RECIPIENT: + ptr = GET_FIRST(&obj_used_list); + while (ptr != END_OF_LIST(&obj_used_list)) { + if (ptr->type == OBJ_SHIP) + return 1; + + ptr = GET_NEXT(ptr); + } + + return 0; + + case OPF_WING: + for (j = 0; j < MAX_WINGS; j++) + if (Wings[j].wave_count) + return 1; + + return 0; + + case OPF_PERSONA: + return Personas.empty() ? 0 : 1; + + case OPF_POINT: + case OPF_WAYPOINT_PATH: + return Waypoint_lists.empty() ? 0 : 1; + + case OPF_MISSION_NAME: + if (m_mode != MODE_CAMPAIGN) { + if (_interface && !_interface->hasDefaultMissionName()) + return 0; + + return 1; + } + + if (Campaign.num_missions > 0) + return 1; + + return 0; + + case OPF_GOAL_NAME: { + int value; + + value = Operators[op].value; + + if (m_mode == MODE_CAMPAIGN) + return 1; + + else if (_interface && _interface->hasDefaultGoal(value)) + return 1; + + return 0; + } + + case OPF_EVENT_NAME: { + int value; + + value = Operators[op].value; + + if (m_mode == MODE_CAMPAIGN) + return 1; + + else if (_interface && _interface->hasDefaultEvent(value)) + return 1; + + return 0; + } + + case OPF_MESSAGE: + if (_interface && _interface->hasDefaultMessageParamter()) + return 1; + + return 0; + + case OPF_VARIABLE_NAME: + return (sexp_variable_count() > 0) ? 1 : 0; + + case OPF_SSM_CLASS: + return Ssm_info.empty() ? 0 : 1; + + case OPF_MISSION_MOOD: + return Builtin_moods.empty() ? 0 : 1; + + case OPF_CONTAINER_NAME: + return get_all_sexp_containers().empty() ? 0 : 1; + + case OPF_LIST_CONTAINER_NAME: + for (const auto& container : get_all_sexp_containers()) { + if (container.is_list()) { + return 1; + } + } + return 0; + + case OPF_MAP_CONTAINER_NAME: + for (const auto& container : get_all_sexp_containers()) { + if (container.is_map()) { + return 1; + } + } + return 0; + + case OPF_ASTEROID_TYPES: + if (!get_list_valid_asteroid_subtypes().empty()) { + return 1; + } + return 0; + + case OPF_DEBRIS_TYPES: + for (const auto& this_asteroid : Asteroid_info) { + if (this_asteroid.type == ASTEROID_TYPE_DEBRIS) { + return 1; + } + } + return 0; + + case OPF_MOTION_DEBRIS: + if (Motion_debris_info.size() > 0) { + return 1; + } + return 0; + + case OPF_BOLT_TYPE: + if (Bolt_types.size() > 0) { + return 1; + } + return 0; + + case OPF_TRAITOR_OVERRIDE: + return Traitor_overrides.empty() ? 0 : 1; + + case OPF_LUA_GENERAL_ORDER: + return (ai_lua_get_num_general_orders() > 0) ? 1 : 0; + + case OPF_MISSION_CUSTOM_STRING: + return The_mission.custom_strings.empty() ? 0 : 1; + + default: + if (!Dynamic_enums.empty()) { + if ((type - First_available_opf_id) < (int)Dynamic_enums.size()) { + return 1; + } else { + UNREACHABLE("Unhandled SEXP argument type!"); + } + } else { + UNREACHABLE("Unhandled SEXP argument type!"); + } + } + + return 0; +} + +// ----------------------------------------------------------------------- +// Tree navigation helpers +// ----------------------------------------------------------------------- + // Goober5000 int SexpTreeModel::find_argument_number(int parent_node, int child_node) const { @@ -264,3 +811,227 @@ bool SexpTreeModel::is_node_eligible_for_special_argument(int parent_node) const const int e_arg = find_ancestral_argument_number(OP_EVERY_TIME_ARGUMENT, parent_node); return w_arg >= 1 || e_arg >= 1; } + +// ----------------------------------------------------------------------- +// Query / analysis functions +// ----------------------------------------------------------------------- + +int SexpTreeModel::count_args(int node) const +{ + int count = 0; + + while (node != -1) { + count++; + node = tree_nodes[node].next; + } + + return count; +} + +// identify what type of argument this is. You call it with the node of the first argument +// of an operator. It will search through enough of the arguments to determine what type of +// data they are. +int SexpTreeModel::identify_arg_type(int node) const +{ + int type = -1; + + while (node != -1) { + Assert(tree_nodes[node].type & SEXPT_VALID); + switch (SEXPT_TYPE(tree_nodes[node].type)) { + case SEXPT_OPERATOR: + type = get_operator_const(tree_nodes[node].text); + Assert(type); + return query_operator_return_type(type); + + case SEXPT_NUMBER: + return OPR_NUMBER; + + case SEXPT_STRING: // either a ship or a wing + type = SEXP_ATOM_STRING; + break; // don't return, because maybe we can narrow selection down more. + } + + node = tree_nodes[node].next; + } + + return type; +} + +// given a tree node, returns the argument type it should be. +// OPF_NULL means no value (or a "void" value) is returned. OPF_NONE means there shouldn't +// be any argument at this position at all. +int SexpTreeModel::query_node_argument_type(int node) const +{ + int parent_node = tree_nodes[node].parent; + if (parent_node < 0) { // parent nodes are -1 for a top-level operator like 'when' + return OPF_NULL; + } + + int argnum = find_argument_number(parent_node, node); + if (argnum < 0) { + return OPF_NONE; + } + + int op_num = get_operator_index(tree_nodes[parent_node].text); + if (op_num < 0) { + return OPF_NONE; + } + + return query_operator_argument_type(op_num, argnum); +} + +// Determine if a given opf code has a restricted argument range (i.e. has a specific, limited +// set of argument values, or has virtually unlimited possibilities. For example, boolean values +// only have true or false, so it is restricted, but a number could be anything, so it's not. +int SexpTreeModel::query_restricted_opf_range(int opf) const +{ + switch (opf) { + case OPF_NUMBER: + case OPF_POSITIVE: + case OPF_WHO_FROM: + + // Goober5000 - these are needed too (otherwise the arguments revert to their defaults) + case OPF_STRING: + case OPF_ANYTHING: + case OPF_CONTAINER_VALUE: // jg18 + case OPF_DATA_OR_STR_CONTAINER: // jg18 + return 0; + } + + return 1; +} + +int SexpTreeModel::get_sibling_place(int node) const +{ + if (tree_nodes[node].parent < 0 || tree_nodes[node].parent > (int)tree_nodes.size()) + return -1; + + const sexp_tree_item* myparent = &tree_nodes[tree_nodes[node].parent]; + + if (myparent->child == -1) + return -1; + + const sexp_tree_item* mysibling = &tree_nodes[myparent->child]; + + int count = 0; + while (true) { + if (mysibling == &tree_nodes[node]) + break; + + if (mysibling->next == -1) + break; + + count++; + mysibling = &tree_nodes[mysibling->next]; + } + + return count; +} + +NodeImage SexpTreeModel::get_data_image(int node) const +{ + int count = get_sibling_place(node) + 1; + + if (count <= 0) { + return NodeImage::DATA; + } + + if (count % 5 != 0) { + return NodeImage::DATA; + } + + int idx = (count % 100) / 5; + + // There are 20 numbered data icons (DATA_00 through DATA_95) + if (idx > 20) { + return NodeImage::DATA; + } + + return static_cast(static_cast(NodeImage::DATA_00) + idx); +} + +int SexpTreeModel::query_false(int node) const +{ + Assert(node >= 0); + Assert(tree_nodes[node].type == (SEXPT_OPERATOR | SEXPT_VALID)); + Assert(tree_nodes[node].next == -1); // must make this assumption or else it will confuse code! + if (get_operator_const(tree_nodes[node].text) == OP_FALSE) { + return TRUE; + } + + return FALSE; +} + +// Look for the valid operator that is the closest match for 'str' and return the operator +// number of it. What operators are valid is determined by 'node', and an operator is valid +// if it is allowed to fit at position 'node' +const SCP_string& SexpTreeModel::match_closest_operator(const SCP_string& str, int node) const +{ + int z, op, arg_num, opf; + + z = tree_nodes[node].parent; + if (z < 0) { + return str; + } + + op = get_operator_index(tree_nodes[z].text); + if (op < 0) + return str; + + // determine which argument we are of the parent + arg_num = find_argument_number(z, node); + opf = query_operator_argument_type(op, arg_num); // check argument type at this position + + // find the best operator + int best = sexp_match_closest_operator(str, opf); + if (best < 0) { + Warning(LOCATION, "Unable to find an operator match for string '%s' and argument type %d", str.c_str(), opf); + return str; + } + return Operators[best].text; +} + +const char* SexpTreeModel::help(int code) +{ + int i; + + i = (int)Sexp_help.size(); + while (i--) { + if (Sexp_help[i].id == code) + break; + } + + if (i >= 0) + return Sexp_help[i].help.c_str(); + + return nullptr; +} + +int SexpTreeModel::find_text(const char* text, int* find, int max_depth) const +{ + int find_count; + + // initialize find + for (int i = 0; i < max_depth; i++) { + find[i] = -1; + } + + find_count = 0; + + for (size_t i = 0; i < tree_nodes.size(); i++) { + // only look at used and editable nodes + if ((tree_nodes[i].flags & EDITABLE) && (tree_nodes[i].type != SEXPT_UNUSED)) { + // find the text + if (!stricmp(tree_nodes[i].text, text)) { + find[find_count++] = static_cast(i); + + // don't exceed max count - array bounds + if (find_count == max_depth) { + break; + } + } + } + } + + return find_count; +} diff --git a/code/missioneditor/sexp_tree_model.h b/code/missioneditor/sexp_tree_model.h index 5644820ed27..79fbf471e20 100644 --- a/code/missioneditor/sexp_tree_model.h +++ b/code/missioneditor/sexp_tree_model.h @@ -212,11 +212,43 @@ class SexpTreeModel { // Editor context interface (set by UI layer) SexpTreeEditorInterface* _interface; - // --- Tree navigation helpers (used by OPF functions) --- + // Modification tracking — UI layer sets this to point at its dirty flag. + // Model code sets *modified = 1 when tree data changes. + int* modified; + + // --- Tree navigation helpers --- int find_argument_number(int parent_node, int child_node) const; int find_ancestral_argument_number(int parent_op, int child_node) const; bool is_node_eligible_for_special_argument(int parent_node) const; + // --- Tree node management --- + int find_free_node() const; + int allocate_node(); + int allocate_node(int parent, int after = -1); + void set_node(int node, int type, const char* text); + void free_node(int node, int cascade = 0); + void free_node2(int node); + + // --- Tree serialization --- + int save_tree(int node) const; + int save_branch(int cur, int at_root = 0) const; + + // --- Default argument availability --- + int query_default_argument_available(int op) const; + int query_default_argument_available(int op, int i) const; + + // --- Query / analysis functions --- + int count_args(int node) const; + int identify_arg_type(int node) const; + int query_node_argument_type(int node) const; + int query_restricted_opf_range(int opf) const; + int get_sibling_place(int node) const; + NodeImage get_data_image(int node) const; + int query_false(int node) const; + const SCP_string& match_closest_operator(const SCP_string& str, int node) const; + static const char* help(int code); + int find_text(const char* text, int* find, int max_depth) const; + // --- OPF listing functions (implemented in sexp_tree_opf.cpp) --- sexp_list_item* get_listing_opf(int opf, int parent_node, int arg_index); sexp_list_item* get_listing_opf_null(); From 9e6f1f9690e3662192bfa435753f8f0e01363ae2 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Fri, 3 Oct 2025 21:14:00 -0500 Subject: [PATCH 07/73] move variable/container stuff to the model --- code/missioneditor/sexp_tree_model.cpp | 157 +++++++++++++++++++++++++ code/missioneditor/sexp_tree_model.h | 10 ++ 2 files changed, 167 insertions(+) diff --git a/code/missioneditor/sexp_tree_model.cpp b/code/missioneditor/sexp_tree_model.cpp index 3cff9fee1f7..0a4bd4a2ada 100644 --- a/code/missioneditor/sexp_tree_model.cpp +++ b/code/missioneditor/sexp_tree_model.cpp @@ -1035,3 +1035,160 @@ int SexpTreeModel::find_text(const char* text, int* find, int max_depth) const return find_count; } + +// ----------------------------------------------------------------------- +// Variable / container utilities +// ----------------------------------------------------------------------- + +int SexpTreeModel::get_item_index_to_var_index() const +{ + // check valid item index and node is a variable + if ((item_index > 0) && (tree_nodes[item_index].type & SEXPT_VARIABLE)) { + return get_tree_name_to_sexp_variable_index(tree_nodes[item_index].text); + } else { + return -1; + } +} + +int SexpTreeModel::get_tree_name_to_sexp_variable_index(const char* tree_name) +{ + char var_name[TOKEN_LENGTH]; + + auto chars_to_copy = strcspn(tree_name, "("); + Assert(chars_to_copy < TOKEN_LENGTH - 1); + + // Copy up to '(' and add null termination + strncpy(var_name, tree_name, chars_to_copy); + var_name[chars_to_copy] = '\0'; + + // Look up index + return get_index_sexp_variable_name(var_name); +} + +int SexpTreeModel::get_modify_variable_type(int parent) const +{ + int sexp_var_index = -1; + + Assert(parent >= 0); + int op_const = get_operator_const(tree_nodes[parent].text); + + Assert(tree_nodes[parent].child >= 0); + char* node_text = tree_nodes[tree_nodes[parent].child].text; + + if (op_const == OP_MODIFY_VARIABLE) { + sexp_var_index = get_tree_name_to_sexp_variable_index(node_text); + } else if (op_const == OP_SET_VARIABLE_BY_INDEX) { + if (can_construe_as_integer(node_text)) { + sexp_var_index = atoi(node_text); + } else if (strchr(node_text, '(') && strchr(node_text, ')')) { + // the variable index is itself a variable! + return OPF_AMBIGUOUS; + } + } else { + Int3(); // should not be called otherwise + } + + // if we don't have a valid variable, allow replacement with anything + if (sexp_var_index < 0) + return OPF_AMBIGUOUS; + + if (Sexp_variables[sexp_var_index].type & SEXP_VARIABLE_BLOCK || Sexp_variables[sexp_var_index].type & SEXP_VARIABLE_NOT_USED) { + // assume number so that we can allow tree display of number operators + return OPF_NUMBER; + } else if (Sexp_variables[sexp_var_index].type & SEXP_VARIABLE_NUMBER) { + return OPF_NUMBER; + } else if (Sexp_variables[sexp_var_index].type & SEXP_VARIABLE_STRING) { + return OPF_AMBIGUOUS; + } else { + Int3(); + return 0; + } +} + +int SexpTreeModel::get_variable_count(const char* var_name) const +{ + int count = 0; + char compare_name[64]; + + // get name to compare + strcpy_s(compare_name, var_name); + strcat_s(compare_name, "("); + + // look for compare name + for (size_t idx = 0; idx < tree_nodes.size(); idx++) { + if (tree_nodes[idx].type & SEXPT_VARIABLE) { + if (strstr(tree_nodes[idx].text, compare_name)) { + count++; + } + } + } + + return count; +} + +// Returns the number of times a variable with this name has been used by player loadout +int SexpTreeModel::get_loadout_variable_count(int var_index) const +{ + // we shouldn't be being passed the index of variables that do not exist + Assert(var_index >= 0 && var_index < MAX_SEXP_VARIABLES); + + int idx; + int count = 0; + + for (int i = 0; i < MAX_TVT_TEAMS; i++) { + for (idx = 0; idx < Team_data[i].num_ship_choices; idx++) { + if (!strcmp(Team_data[i].ship_list_variables[idx], Sexp_variables[var_index].variable_name)) { + count++; + } + + if (!strcmp(Team_data[i].ship_count_variables[idx], Sexp_variables[var_index].variable_name)) { + count++; + } + } + + for (idx = 0; idx < Team_data[i].num_weapon_choices; idx++) { + if (!strcmp(Team_data[i].weaponry_pool_variable[idx], Sexp_variables[var_index].variable_name)) { + count++; + } + if (!strcmp(Team_data[i].weaponry_amount_variable[idx], Sexp_variables[var_index].variable_name)) { + count++; + } + } + } + + return count; +} + +int SexpTreeModel::get_container_usage_count(const SCP_string& container_name) const +{ + int count = 0; + + for (int node_idx = 0; node_idx < (int)tree_nodes.size(); node_idx++) { + if (is_matching_container_node(node_idx, container_name)) { + count++; + } + } + + return count; +} + +bool SexpTreeModel::is_matching_container_node(int node, const SCP_string& container_name) const +{ + return (tree_nodes[node].type & SEXPT_VALID) && + (tree_nodes[node].type & (SEXPT_CONTAINER_NAME | SEXPT_CONTAINER_DATA)) && + !stricmp(tree_nodes[node].text, container_name.c_str()); +} + +bool SexpTreeModel::is_container_name_argument(int node) const +{ + Assertion(node >= 0 && node < (int)tree_nodes.size(), + "Attempt to check if out-of-range node %d is a container name argument. Please report!", + node); + + if (tree_nodes[node].parent == -1) { + return false; + } + + const int arg_opf_type = query_node_argument_type(node); + return is_container_name_opf_type(arg_opf_type); +} diff --git a/code/missioneditor/sexp_tree_model.h b/code/missioneditor/sexp_tree_model.h index 79fbf471e20..c69ded4448a 100644 --- a/code/missioneditor/sexp_tree_model.h +++ b/code/missioneditor/sexp_tree_model.h @@ -249,6 +249,16 @@ class SexpTreeModel { static const char* help(int code); int find_text(const char* text, int* find, int max_depth) const; + // --- Variable / container utilities --- + int get_item_index_to_var_index() const; + static int get_tree_name_to_sexp_variable_index(const char* tree_name); + int get_modify_variable_type(int parent) const; + int get_variable_count(const char* var_name) const; + int get_loadout_variable_count(int var_index) const; + int get_container_usage_count(const SCP_string& container_name) const; + bool is_matching_container_node(int node, const SCP_string& container_name) const; + bool is_container_name_argument(int node) const; + // --- OPF listing functions (implemented in sexp_tree_opf.cpp) --- sexp_list_item* get_listing_opf(int opf, int parent_node, int arg_index); sexp_list_item* get_listing_opf_null(); From 03fec252d803835312dcccea678a33e3950e24ec Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Fri, 3 Oct 2025 22:06:00 -0500 Subject: [PATCH 08/73] sexptreeactions and ui interface --- code/missioneditor/sexp_tree_actions.cpp | 567 ++++++++++++++++++++++- code/missioneditor/sexp_tree_actions.h | 53 ++- code/missioneditor/sexp_tree_model.cpp | 487 +++++++++++++++++++ code/missioneditor/sexp_tree_model.h | 40 +- 4 files changed, 1137 insertions(+), 10 deletions(-) diff --git a/code/missioneditor/sexp_tree_actions.cpp b/code/missioneditor/sexp_tree_actions.cpp index 598183ec5f8..f28faa7b477 100644 --- a/code/missioneditor/sexp_tree_actions.cpp +++ b/code/missioneditor/sexp_tree_actions.cpp @@ -7,9 +7,568 @@ * */ -// Sexp tree action logic — menu computation and action execution. -// -// This file is intentionally empty in Phase 1. -// Action logic will be moved here in Phase 4. +// Sexp tree action logic — action execution that bridges model and UI. #include "missioneditor/sexp_tree_actions.h" + +#include "parse/sexp.h" +#include "parse/sexp_container.h" + +SexpTreeActions::SexpTreeActions(SexpTreeModel& model, ISexpTreeUI& ui) + : _model(model), _ui(ui) +{ +} + +// Helper: delete all UI children of a node and clear model child link +void SexpTreeActions::clear_node_children(int node_index) +{ + int child = _model.tree_nodes[node_index].child; + if (child != -1) + _model.free_node2(child); + + _model.tree_nodes[node_index].child = -1; + + void* h = _model.tree_nodes[node_index].handle; + while (_ui.ui_has_children(h)) + _ui.ui_delete_item(_ui.ui_get_child_item(h)); +} + +// ----------------------------------------------------------------------- +// Replace operations +// ----------------------------------------------------------------------- + +void SexpTreeActions::replace_data(const char* data, int type) +{ + int node_idx = _model.item_index; + + clear_node_children(node_idx); + + _model.set_node(node_idx, type, data); + void* h = _model.tree_nodes[node_idx].handle; + _ui.ui_set_item_text(h, data); + NodeImage bmap = _model.get_data_image(node_idx); + _ui.ui_set_item_image(h, bmap); + _model.tree_nodes[node_idx].flags = EDITABLE; + + // check remaining data beyond replaced data for validity + verify_and_fix_arguments(_model.tree_nodes[node_idx].parent); + + if (_model.modified) + *_model.modified = 1; + _ui.ui_update_help(h); +} + +void SexpTreeActions::replace_variable_data(int var_idx, int type) +{ + char buf[128]; + + Assert(type & SEXPT_VARIABLE); + + int node_idx = _model.item_index; + + clear_node_children(node_idx); + + // Assemble name + sprintf(buf, "%s(%s)", Sexp_variables[var_idx].variable_name, Sexp_variables[var_idx].text); + + _model.set_node(node_idx, type, buf); + void* h = _model.tree_nodes[node_idx].handle; + _ui.ui_set_item_text(h, buf); + _ui.ui_set_item_image(h, NodeImage::VARIABLE); + _model.tree_nodes[node_idx].flags = NOT_EDITABLE; + + // check remaining data beyond replaced data for validity + verify_and_fix_arguments(_model.tree_nodes[node_idx].parent); + + if (_model.modified) + *_model.modified = 1; + _ui.ui_update_help(h); +} + +void SexpTreeActions::replace_container_name(const sexp_container& container) +{ + int node_idx = _model.item_index; + + clear_node_children(node_idx); + + _model.set_node(node_idx, (SEXPT_VALID | SEXPT_STRING | SEXPT_CONTAINER_NAME), container.container_name.c_str()); + void* h = _model.tree_nodes[node_idx].handle; + _ui.ui_set_item_image(h, NodeImage::CONTAINER_NAME); + _ui.ui_set_item_text(h, container.container_name.c_str()); + _model.tree_nodes[node_idx].flags = NOT_EDITABLE; + + if (_model.modified) + *_model.modified = 1; + _ui.ui_update_help(h); +} + +void SexpTreeActions::replace_container_data(const sexp_container& container, + int type, + bool test_child_nodes, + bool delete_child_nodes, + bool set_default_modifier) +{ + int node_idx = _model.item_index; + + // if this is already a container of the right type, don't alter the child nodes + if (test_child_nodes && (_model.tree_nodes[node_idx].type & SEXPT_CONTAINER_DATA)) { + if (container.is_list()) { + const auto* p_old_container = get_sexp_container(_model.tree_nodes[node_idx].text); + + Assertion(p_old_container != nullptr, + "Attempt to Replace Container Data of unknown previous container %s. Please report!", + _model.tree_nodes[node_idx].text); + + if (p_old_container->is_list()) { + if (container.opf_type == p_old_container->opf_type) { + delete_child_nodes = false; + set_default_modifier = false; + } + } + } + } + + if (delete_child_nodes) { + clear_node_children(node_idx); + } + + _model.set_node(node_idx, type, container.container_name.c_str()); + void* h = _model.tree_nodes[node_idx].handle; + _ui.ui_set_item_image(h, NodeImage::CONTAINER_DATA); + _ui.ui_set_item_text(h, container.container_name.c_str()); + _model.tree_nodes[node_idx].flags = NOT_EDITABLE; + + if (set_default_modifier) { + add_default_modifier(container); + } + + if (_model.modified) + *_model.modified = 1; + _ui.ui_update_help(h); +} + +void SexpTreeActions::replace_operator(const char* op) +{ + int node_idx = _model.item_index; + + clear_node_children(node_idx); + + _model.set_node(node_idx, (SEXPT_OPERATOR | SEXPT_VALID), op); + void* h = _model.tree_nodes[node_idx].handle; + _ui.ui_set_item_text(h, op); + _model.tree_nodes[node_idx].flags = OPERAND; + + if (_model.modified) + *_model.modified = 1; + _ui.ui_update_help(h); +} + +// ----------------------------------------------------------------------- +// Expand/merge operations +// ----------------------------------------------------------------------- + +void SexpTreeActions::expand_operator(int node) +{ + int data; + + if (_model.tree_nodes[node].flags & COMBINED) { + node = _model.tree_nodes[node].parent; + Assert((_model.tree_nodes[node].flags & OPERAND) && (_model.tree_nodes[node].flags & EDITABLE)); + } + + if ((_model.tree_nodes[node].flags & OPERAND) && (_model.tree_nodes[node].flags & EDITABLE)) { + Assert(_model.tree_nodes[node].type & SEXPT_OPERATOR); + void* h = _model.tree_nodes[node].handle; + data = _model.tree_nodes[node].child; + Assert(data != -1 && _model.tree_nodes[data].next == -1 && _model.tree_nodes[data].child == -1); + + _ui.ui_set_item_text(h, _model.tree_nodes[node].text); + _model.tree_nodes[node].flags = OPERAND; + NodeImage bmap = _model.get_data_image(data); + _model.tree_nodes[data].handle = _ui.ui_insert_item(_model.tree_nodes[data].text, bmap, h, nullptr); + _model.tree_nodes[data].flags = EDITABLE; + _ui.ui_expand_item(h); + } +} + +// ----------------------------------------------------------------------- +// Add operations +// ----------------------------------------------------------------------- + +int SexpTreeActions::add_data(const char* data, int type) +{ + int node_idx = _model.item_index; + + expand_operator(node_idx); + int node = _model.allocate_node(node_idx); + _model.set_node(node, type, data); + NodeImage bmap = _model.get_data_image(node); + _model.tree_nodes[node].handle = _ui.ui_insert_item(data, bmap, _model.tree_nodes[node_idx].handle, nullptr); + _model.tree_nodes[node].flags = EDITABLE; + if (_model.modified) + *_model.modified = 1; + return node; +} + +int SexpTreeActions::add_variable_data(const char* data, int type) +{ + Assert(type & SEXPT_VARIABLE); + + int node_idx = _model.item_index; + + expand_operator(node_idx); + int node = _model.allocate_node(node_idx); + _model.set_node(node, type, data); + _model.tree_nodes[node].handle = _ui.ui_insert_item(data, NodeImage::VARIABLE, _model.tree_nodes[node_idx].handle, nullptr); + _model.tree_nodes[node].flags = NOT_EDITABLE; + if (_model.modified) + *_model.modified = 1; + return node; +} + +int SexpTreeActions::add_container_name(const char* container_name) +{ + Assertion(container_name != nullptr, "Attempt to add null container name. Please report!"); + Assertion(get_sexp_container(container_name) != nullptr, + "Attempt to add unknown container name %s. Please report!", + container_name); + + int node_idx = _model.item_index; + + expand_operator(node_idx); + int node = _model.allocate_node(node_idx); + _model.set_node(node, (SEXPT_VALID | SEXPT_CONTAINER_NAME | SEXPT_STRING), container_name); + _model.tree_nodes[node].handle = + _ui.ui_insert_item(container_name, NodeImage::CONTAINER_NAME, _model.tree_nodes[node_idx].handle, nullptr); + _model.tree_nodes[node].flags = NOT_EDITABLE; + if (_model.modified) + *_model.modified = 1; + return node; +} + +void SexpTreeActions::add_container_data(const char* container_name) +{ + Assertion(container_name != nullptr, "Attempt to add null container. Please report!"); + Assertion(get_sexp_container(container_name) != nullptr, + "Attempt to add unknown container %s. Please report!", + container_name); + int node_idx = _model.item_index; + int node = _model.allocate_node(node_idx); + _model.set_node(node, (SEXPT_VALID | SEXPT_CONTAINER_DATA | SEXPT_STRING), container_name); + _model.tree_nodes[node].handle = + _ui.ui_insert_item(container_name, NodeImage::CONTAINER_DATA, _model.tree_nodes[node_idx].handle, nullptr); + _model.tree_nodes[node].flags = NOT_EDITABLE; + _model.item_index = node; + if (_model.modified) + *_model.modified = 1; +} + +void SexpTreeActions::add_operator(const char* op, void* parent_handle) +{ + int node; + + if (_model.item_index == -1) { + node = _model.allocate_node(-1); + _model.set_node(node, (SEXPT_OPERATOR | SEXPT_VALID), op); + _model.tree_nodes[node].handle = _ui.ui_insert_item(op, NodeImage::OPERATOR, parent_handle, nullptr); + + } else { + expand_operator(_model.item_index); + node = _model.allocate_node(_model.item_index); + _model.set_node(node, (SEXPT_OPERATOR | SEXPT_VALID), op); + _model.tree_nodes[node].handle = _ui.ui_insert_item(op, NodeImage::OPERATOR, _model.tree_nodes[_model.item_index].handle, nullptr); + } + + _model.tree_nodes[node].flags = OPERAND; + _model.item_index = node; + if (_model.modified) + *_model.modified = 1; +} + +void SexpTreeActions::add_default_modifier(const sexp_container& container) +{ + sexp_list_item item; + + int type_to_use = (SEXPT_VALID | SEXPT_MODIFIER); + + if (container.is_map()) { + if (any(container.type & ContainerType::STRING_KEYS)) { + item.set_data(""); + type_to_use |= SEXPT_STRING; + } else if (any(container.type & ContainerType::NUMBER_KEYS)) { + item.set_data("0"); + type_to_use |= SEXPT_NUMBER; + } else { + UNREACHABLE("Unknown map container key type %d", (int)container.type); + } + } else if (container.is_list()) { + item.set_data(get_all_list_modifiers()[0].name); + type_to_use |= SEXPT_STRING; + } else { + UNREACHABLE("Unknown container type %d", (int)container.type); + } + + item.type = type_to_use; + add_data(item.text.c_str(), item.type); +} + +// ----------------------------------------------------------------------- +// Compound operations +// ----------------------------------------------------------------------- + +void SexpTreeActions::add_or_replace_operator(int op, int replace_flag) +{ + int i, op2; + + int saved_index = _model.item_index; + if (replace_flag) { + if (_model.tree_nodes[_model.item_index].type & SEXPT_OPERATOR) { // are both operators? + op2 = get_operator_index(_model.tree_nodes[_model.item_index].text); + Assert(op2 >= 0); + i = _model.count_args(_model.tree_nodes[_model.item_index].child); + if ((i >= Operators[op].min) && (i <= Operators[op].max)) { // are old num args valid? + while (i--) + if (query_operator_argument_type(op2, i) != query_operator_argument_type(op, i)) + break; + + if (i < 0) { // everything is ok, so we can keep old arguments with new operator + _model.set_node(_model.item_index, (SEXPT_OPERATOR | SEXPT_VALID), Operators[op].text.c_str()); + _ui.ui_set_item_text(_model.tree_nodes[_model.item_index].handle, Operators[op].text.c_str()); + _model.tree_nodes[_model.item_index].flags = OPERAND; + return; + } + } + } + + replace_operator(Operators[op].text.c_str()); + + } else { + add_operator(Operators[op].text.c_str()); + } + + // fill in all the required (minimum) arguments with default values + for (i = 0; i < Operators[op].min; i++) + add_default_operator(op, i); + + _ui.ui_expand_item(_model.tree_nodes[saved_index].handle); +} + +int SexpTreeActions::add_default_operator(int op_index, int argnum) +{ + char buf[256]; + sexp_list_item item; + + void* saved_handle = _model.tree_nodes[_model.item_index].handle; + int saved_index = _model.item_index; + if (_model.get_default_value(&item, buf, op_index, argnum)) + return -1; + + if (item.type & SEXPT_OPERATOR) { + Assert((item.op >= 0) && (item.op < (int)Operators.size())); + add_or_replace_operator(item.op); + _model.item_index = saved_index; + + } else { + int sexp_var_index; + // special case for sexps that take variables + const int op_type = query_operator_argument_type(op_index, argnum); + if ((op_type == OPF_VARIABLE_NAME) && ((sexp_var_index = get_index_sexp_variable_name(item.text)) >= 0)) { + int type = SEXPT_VALID | SEXPT_VARIABLE; + if (Sexp_variables[sexp_var_index].type & SEXP_VARIABLE_STRING) { + type |= SEXPT_STRING; + } else if (Sexp_variables[sexp_var_index].type & SEXP_VARIABLE_NUMBER) { + type |= SEXPT_NUMBER; + } else { + Int3(); + } + + char node_text[2 * TOKEN_LENGTH + 2]; + sprintf(node_text, "%s(%s)", item.text.c_str(), Sexp_variables[sexp_var_index].text); + add_variable_data(node_text, type); + } + // special case for sexps that take containers + else if (item.type & SEXPT_CONTAINER_NAME) { + Assertion(SexpTreeModel::is_container_name_opf_type(op_type) || op_type == OPF_DATA_OR_STR_CONTAINER, + "Attempt to add default container name for a node of non-container type (%d). Please report!", + op_type); + add_container_name(item.text.c_str()); + } + // modify-variable data type depends on type of variable being modified + else if (Operators[op_index].value == OP_MODIFY_VARIABLE) { + char buf2[256]; + Assert(argnum == 1); + sexp_list_item temp_item; + _model.get_default_value(&temp_item, buf2, op_index, 0); + sexp_var_index = get_index_sexp_variable_name(temp_item.text); + Assert(sexp_var_index != -1); + + int temp_type = Sexp_variables[sexp_var_index].type; + int type = 0; + if (temp_type & SEXP_VARIABLE_NUMBER) { + type = SEXPT_VALID | SEXPT_NUMBER; + } else if (temp_type & SEXP_VARIABLE_STRING) { + type = SEXPT_VALID | SEXPT_STRING; + } else { + Int3(); + } + add_data(item.text.c_str(), type); + } + // all other sexps and parameters + else { + add_data(item.text.c_str(), item.type); + } + } + + return 0; +} + +// ----------------------------------------------------------------------- +// Validation +// ----------------------------------------------------------------------- + +void SexpTreeActions::verify_and_fix_arguments(int node) +{ + int op_index, arg_num, type, tmp; + static int here_count = 0; + sexp_list_item* list; + sexp_list_item* ptr; + bool is_variable_arg = false; + + if (here_count) + return; + + here_count++; + op_index = get_operator_index(_model.tree_nodes[node].text); + if (op_index < 0) { + here_count--; + return; + } + + tmp = _model.item_index; + + arg_num = 0; + _model.item_index = _model.tree_nodes[node].child; + while (_model.item_index >= 0) { + // get listing of valid argument values for node item_index + type = query_operator_argument_type(op_index, arg_num); + // special case for modify-variable + if (type == OPF_AMBIGUOUS) { + is_variable_arg = true; + type = _model.get_modify_variable_type(node); + } + if (_model.tree_nodes[_model.item_index].type & SEXPT_CONTAINER_DATA) { + _model.item_index = _model.tree_nodes[_model.item_index].next; + arg_num++; + continue; + } + if (_model.query_restricted_opf_range(type)) { + list = _model.get_listing_opf(type, node, arg_num); + if (!list && (arg_num >= Operators[op_index].min)) { + _model.free_node(_model.item_index, 1); + _model.item_index = tmp; + here_count--; + return; + } + + if (list) { + char* text_ptr; + char default_variable_text[TOKEN_LENGTH]; + if (_model.tree_nodes[_model.item_index].type & SEXPT_VARIABLE) { + if (type == OPF_VARIABLE_NAME) { + get_variable_name_from_sexp_tree_node_text(_model.tree_nodes[_model.item_index].text, default_variable_text); + text_ptr = default_variable_text; + } else { + get_variable_name_from_sexp_tree_node_text(_model.tree_nodes[_model.item_index].text, default_variable_text); + int sexp_var_index = get_index_sexp_variable_name(default_variable_text); + bool types_match = false; + Assert(sexp_var_index != -1); + + switch (type) { + case OPF_NUMBER: + case OPF_POSITIVE: + if (Sexp_variables[sexp_var_index].type & SEXP_VARIABLE_NUMBER) { + types_match = true; + } + break; + + default: + if (Sexp_variables[sexp_var_index].type & SEXP_VARIABLE_STRING) { + types_match = true; + } + } + + if (types_match) { + _model.item_index = _model.tree_nodes[_model.item_index].next; + arg_num++; + continue; + } else { + get_variable_default_text_from_variable_text(_model.tree_nodes[_model.item_index].text, default_variable_text); + text_ptr = default_variable_text; + } + } + } else { + text_ptr = _model.tree_nodes[_model.item_index].text; + } + + ptr = list; + while (ptr) { + if (!stricmp(ptr->text.c_str(), text_ptr)) + break; + + ptr = ptr->next; + } + + if (!ptr) { // argument isn't in list of valid choices + if (list->op >= 0) { + replace_operator(list->text.c_str()); + } else { + replace_data(list->text.c_str(), list->type); + } + } + + } else { + bool invalid = false; + if (type == OPF_AMBIGUOUS) { + if (SEXPT_TYPE(_model.tree_nodes[_model.item_index].type) == SEXPT_OPERATOR) { + invalid = true; + } + } else { + if (SEXPT_TYPE(_model.tree_nodes[_model.item_index].type) != SEXPT_OPERATOR) { + invalid = true; + } + } + + if (invalid) { + replace_data("", (SEXPT_STRING | SEXPT_VALID)); + } + } + + if (_model.tree_nodes[_model.item_index].type & SEXPT_OPERATOR) + verify_and_fix_arguments(_model.item_index); + } + + // fix the node if it is the argument for modify-variable + if (is_variable_arg) { + switch (type) { + case OPF_AMBIGUOUS: + _model.tree_nodes[_model.item_index].type |= SEXPT_STRING; + _model.tree_nodes[_model.item_index].type &= ~SEXPT_NUMBER; + break; + + case OPF_NUMBER: + _model.tree_nodes[_model.item_index].type |= SEXPT_NUMBER; + _model.tree_nodes[_model.item_index].type &= ~SEXPT_STRING; + break; + + default: + Int3(); + } + } + + _model.item_index = _model.tree_nodes[_model.item_index].next; + arg_num++; + } + + _model.item_index = tmp; + here_count--; +} diff --git a/code/missioneditor/sexp_tree_actions.h b/code/missioneditor/sexp_tree_actions.h index 987c00c1861..b4ce9375124 100644 --- a/code/missioneditor/sexp_tree_actions.h +++ b/code/missioneditor/sexp_tree_actions.h @@ -9,8 +9,51 @@ #pragma once -// Sexp tree action logic — menu computation and action execution. -// Separates "what to do" from "how to show" (UI). -// -// This file is intentionally empty in Phase 1. -// Action logic will be moved here in Phase 4. +// Sexp tree action logic — action execution that bridges model and UI. +// These functions modify model data AND update the UI via ISexpTreeUI callbacks. +// Both FRED2 and QtFRED create an instance alongside their SexpTreeModel. + +#include "missioneditor/sexp_tree_model.h" + +class sexp_container; + +class SexpTreeActions { +public: + SexpTreeActions(SexpTreeModel& model, ISexpTreeUI& ui); + + // --- Replace operations (modify current item_index node) --- + void replace_data(const char* data, int type); + void replace_variable_data(int var_idx, int type); + void replace_container_name(const sexp_container& container); + void replace_container_data(const sexp_container& container, + int type, + bool test_child_nodes, + bool delete_child_nodes, + bool set_default_modifier); + void replace_operator(const char* op); + + // --- Expand/merge operations --- + void expand_operator(int node); + + // --- Add operations (add child under current item_index node) --- + int add_data(const char* data, int type); + int add_variable_data(const char* data, int type); + int add_container_name(const char* container_name); + void add_container_data(const char* container_name); + void add_operator(const char* op, void* parent_handle = nullptr); + void add_default_modifier(const sexp_container& container); + + // --- Compound operations --- + void add_or_replace_operator(int op, int replace_flag = 0); + int add_default_operator(int op_index, int argnum); + + // --- Validation --- + void verify_and_fix_arguments(int node); + +private: + // Helper: delete all UI children of a node and clear model child link + void clear_node_children(int node_index); + + SexpTreeModel& _model; + ISexpTreeUI& _ui; +}; diff --git a/code/missioneditor/sexp_tree_model.cpp b/code/missioneditor/sexp_tree_model.cpp index 0a4bd4a2ada..72e1c1ca72e 100644 --- a/code/missioneditor/sexp_tree_model.cpp +++ b/code/missioneditor/sexp_tree_model.cpp @@ -23,10 +23,15 @@ #include "ship/ship.h" #include "ai/ailua.h" #include "asteroid/asteroid.h" +#include "fireball/fireballs.h" +#include "gamesnd/gamesnd.h" +#include "graphics/software/FontManager.h" #include "hud/hudartillery.h" +#include "model/model.h" #include "nebula/neblightning.h" #include "starfield/starfield.h" #include "stats/scoring.h" +#include "weapon/emp.h" #define TREE_NODE_INCREMENT 100 @@ -747,6 +752,459 @@ int SexpTreeModel::query_default_argument_available(int op, int i) const return 0; } +// Determine and return the default value for operator argument position i. +// Returns 0 on success, -1 if no default available. +int SexpTreeModel::get_default_value(sexp_list_item* item, char* text_buf, int op, int i) +{ + const char* str = nullptr; + int type, index; + sexp_list_item* list; + + index = item_index; + type = query_operator_argument_type(op, i); + switch (type) + { + case OPF_NULL: + item->set_op(OP_NOP); + return 0; + + case OPF_BOOL: + item->set_op(OP_TRUE); + return 0; + + case OPF_ANYTHING: + if (Operators[op].value == OP_INVALIDATE_ARGUMENT || Operators[op].value == OP_VALIDATE_ARGUMENT) + item->set_data(SEXP_ARGUMENT_STRING); + else + item->set_data(""); + return 0; + + case OPF_DATA_OR_STR_CONTAINER: + item->set_data(""); + return 0; + + case OPF_NUMBER: + case OPF_POSITIVE: + case OPF_AMBIGUOUS: + // if the top level operator is an AI goal, and we are adding the last number required, + // assume that this number is a priority and make it 89 instead of 1. + if ((query_operator_return_type(op) == OPR_AI_GOAL) && (i == (Operators[op].min - 1))) + { + item->set_data("89", (SEXPT_NUMBER | SEXPT_VALID)); + } + else if (((Operators[op].value == OP_HAS_DOCKED_DELAY) || (Operators[op].value == OP_HAS_UNDOCKED_DELAY) || (Operators[op].value == OP_TIME_DOCKED) || (Operators[op].value == OP_TIME_UNDOCKED)) && (i == 2)) + { + item->set_data("1", (SEXPT_NUMBER | SEXPT_VALID)); + } + else if ((Operators[op].value == OP_SHIP_TYPE_DESTROYED) || (Operators[op].value == OP_GOOD_SECONDARY_TIME)) + { + item->set_data("100", (SEXPT_NUMBER | SEXPT_VALID)); + } + else if (Operators[op].value == OP_SET_SUPPORT_SHIP) + { + item->set_data("-1", (SEXPT_NUMBER | SEXPT_VALID)); + } + else if ((Operators[op].value == OP_SHIP_TAG) && (i == 1) || (Operators[op].value == OP_TRIGGER_SUBMODEL_ANIMATION) && (i == 3)) + { + item->set_data("1", (SEXPT_NUMBER | SEXPT_VALID)); + } + else if (Operators[op].value == OP_EXPLOSION_EFFECT) + { + int temp; + char sexp_str_token[TOKEN_LENGTH]; + + switch (i) + { + case 3: + temp = 10; + break; + case 4: + temp = 10; + break; + case 5: + temp = 100; + break; + case 6: + temp = 10; + break; + case 7: + temp = 100; + break; + case 11: + temp = (int)EMP_DEFAULT_INTENSITY; + break; + case 12: + temp = (int)EMP_DEFAULT_TIME; + break; + default: + temp = 0; + break; + } + + sprintf(sexp_str_token, "%d", temp); + item->set_data(sexp_str_token, (SEXPT_NUMBER | SEXPT_VALID)); + } + else if (Operators[op].value == OP_WARP_EFFECT) + { + int temp; + char sexp_str_token[TOKEN_LENGTH]; + + switch (i) + { + case 6: + temp = 100; + break; + case 7: + temp = 10; + break; + default: + temp = 0; + break; + } + + sprintf(sexp_str_token, "%d", temp); + item->set_data(sexp_str_token, (SEXPT_NUMBER | SEXPT_VALID)); + } + else if (Operators[op].value == OP_CHANGE_BACKGROUND) + { + item->set_data("1", (SEXPT_NUMBER | SEXPT_VALID)); + } + else if (Operators[op].value == OP_ADD_BACKGROUND_BITMAP || Operators[op].value == OP_ADD_BACKGROUND_BITMAP_NEW) + { + int temp = 0; + char sexp_str_token[TOKEN_LENGTH]; + + switch (i) + { + case 4: + case 5: + temp = 100; + break; + + case 6: + case 7: + temp = 1; + break; + } + + sprintf(sexp_str_token, "%d", temp); + item->set_data(sexp_str_token, (SEXPT_NUMBER | SEXPT_VALID)); + } + else if (Operators[op].value == OP_ADD_SUN_BITMAP || Operators[op].value == OP_ADD_SUN_BITMAP_NEW) + { + int temp = 0; + char sexp_str_token[TOKEN_LENGTH]; + + if (i == 4) + temp = 100; + + sprintf(sexp_str_token, "%d", temp); + item->set_data(sexp_str_token, (SEXPT_NUMBER | SEXPT_VALID)); + } + else if (Operators[op].value == OP_MISSION_SET_NEBULA) + { + if (i == 0) + item->set_data("1", (SEXPT_NUMBER | SEXPT_VALID)); + else + item->set_data("3000", (SEXPT_NUMBER | SEXPT_VALID)); + } + else if (Operators[op].value == OP_MODIFY_VARIABLE) + { + if (get_modify_variable_type(index) == OPF_NUMBER) + item->set_data("0", (SEXPT_NUMBER | SEXPT_VALID)); + else + item->set_data("", (SEXPT_STRING | SEXPT_VALID)); + } + else if (Operators[op].value == OP_MODIFY_VARIABLE_XSTR) + { + if (i == 1) + item->set_data("", (SEXPT_STRING | SEXPT_VALID)); + else + item->set_data("-1", (SEXPT_NUMBER | SEXPT_VALID)); + } + else if (Operators[op].value == OP_SET_VARIABLE_BY_INDEX) + { + if (i == 0) + item->set_data("0", (SEXPT_NUMBER | SEXPT_VALID)); + else + item->set_data("", (SEXPT_STRING | SEXPT_VALID)); + } + else if (Operators[op].value == OP_JETTISON_CARGO_NEW) + { + item->set_data("25", (SEXPT_NUMBER | SEXPT_VALID)); + } + else if (Operators[op].value == OP_TECH_ADD_INTEL_XSTR || Operators[op].value == OP_TECH_REMOVE_INTEL_XSTR) + { + item->set_data("-1", (SEXPT_NUMBER | SEXPT_VALID)); + } + else + { + item->set_data("0", (SEXPT_NUMBER | SEXPT_VALID)); + } + + return 0; + + // Goober5000 - special cases that used to be numbers but are now hybrids + case OPF_GAME_SND: + { + gamesnd_id sound_index; + + if ((Operators[op].value == OP_EXPLOSION_EFFECT)) + { + sound_index = GameSounds::SHIP_EXPLODE_1; + } + else if ((Operators[op].value == OP_WARP_EFFECT)) + { + sound_index = (i == 8) ? GameSounds::CAPITAL_WARP_IN : GameSounds::CAPITAL_WARP_OUT; + } + + if (sound_index.isValid()) + { + game_snd* snd = gamesnd_get_game_sound(sound_index); + if (can_construe_as_integer(snd->name.c_str())) + item->set_data(snd->name.c_str(), (SEXPT_NUMBER | SEXPT_VALID)); + else + item->set_data(snd->name.c_str(), (SEXPT_STRING | SEXPT_VALID)); + return 0; + } + + // if no hardcoded default, just use the listing default + break; + } + + // Goober5000 - ditto + case OPF_FIREBALL: + { + int fireball_index = -1; + + if (Operators[op].value == OP_EXPLOSION_EFFECT) + { + fireball_index = FIREBALL_MEDIUM_EXPLOSION; + } + else if (Operators[op].value == OP_WARP_EFFECT) + { + fireball_index = FIREBALL_WARP; + } + + if (fireball_index >= 0) + { + char* unique_id = Fireball_info[fireball_index].unique_id; + if (strlen(unique_id) > 0) + item->set_data(unique_id, (SEXPT_STRING | SEXPT_VALID)); + else + { + char num_str[NAME_LENGTH]; + sprintf(num_str, "%d", fireball_index); + item->set_data(num_str, (SEXPT_NUMBER | SEXPT_VALID)); + } + return 0; + } + + // if no hardcoded default, just use the listing default + break; + } + + // new default value + case OPF_PRIORITY: + item->set_data("Normal", (SEXPT_STRING | SEXPT_VALID)); + return 0; + } + + list = get_listing_opf(type, index, i); + + // Goober5000 - the way this is done is really stupid, so stupid hacks are needed to deal with it + // this particular hack is necessary because the argument string should never be a default + if (list && list->text == SEXP_ARGUMENT_STRING) + { + sexp_list_item* first_ptr; + + first_ptr = list; + list = list->next; + + delete first_ptr; + } + + if (list) + { + // copy the information from the list to the passed-in item + *item = *list; + + // but use the provided text buffer + strcpy(text_buf, list->text.c_str()); + item->text = text_buf; + + // get rid of the list, since we're done with it + list->destroy(); + item->next = nullptr; + + return 0; + } + + // catch anything that doesn't have a default value. Just describe what should be here instead + switch (type) + { + case OPF_SHIP: + case OPF_SHIP_NOT_PLAYER: + case OPF_SHIP_POINT: + case OPF_SHIP_WING: + case OPF_SHIP_PROP: + case OPF_SHIP_WING_WHOLETEAM: + case OPF_SHIP_WING_SHIPONTEAM_POINT: + case OPF_SHIP_WING_POINT: + str = ""; + break; + + case OPF_PROP: + str = ""; + break; + + case OPF_ORDER_RECIPIENT: + str = ""; + break; + + case OPF_SHIP_OR_NONE: + case OPF_SUBSYSTEM_OR_NONE: + case OPF_SHIP_WING_POINT_OR_NONE: + str = SEXP_NONE_STRING; + break; + + case OPF_WING: + str = ""; + break; + + case OPF_DOCKER_POINT: + str = ""; + break; + + case OPF_DOCKEE_POINT: + str = ""; + break; + + case OPF_SUBSYSTEM: + case OPF_AWACS_SUBSYSTEM: + case OPF_ROTATING_SUBSYSTEM: + case OPF_TRANSLATING_SUBSYSTEM: + case OPF_SUBSYS_OR_GENERIC: + str = ""; + break; + + case OPF_SUBSYSTEM_TYPE: + str = Subsystem_types[SUBSYSTEM_NONE]; + break; + + case OPF_POINT: + str = ""; + break; + + case OPF_MESSAGE: + str = ""; + break; + + case OPF_WHO_FROM: + str = ""; + break; + + case OPF_WAYPOINT_PATH: + str = ""; + break; + + case OPF_MISSION_NAME: + str = ""; + break; + + case OPF_GOAL_NAME: + str = ""; + break; + + case OPF_SHIP_TYPE: + str = ""; + break; + + case OPF_EVENT_NAME: + str = ""; + break; + + case OPF_HUGE_WEAPON: + str = ""; + break; + + case OPF_JUMP_NODE_NAME: + str = ""; + break; + + case OPF_NAV_POINT: + str = "