mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-09 17:52:08 -10:00
5609 lines
136 KiB
C++
5609 lines
136 KiB
C++
/*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
|
|
|
|
#include "stdafx.h"
|
|
#include "Sexp_tree.h"
|
|
#include "FRED.h"
|
|
#include "FREDDoc.h"
|
|
#include "Management.h"
|
|
#include "parse/sexp.h"
|
|
#include "OperatorArgTypeSelect.h"
|
|
#include "globalincs/linklist.h"
|
|
#include "EventEditor.h"
|
|
#include "MissionGoalsDlg.h"
|
|
#include "ai/aigoals.h"
|
|
#include "mission/missionmessage.h"
|
|
#include "mission/missioncampaign.h"
|
|
#include "CampaignEditorDlg.h"
|
|
#include "hud/hudsquadmsg.h"
|
|
#include "IgnoreOrdersDlg.h"
|
|
#include "stats/medals.h"
|
|
#include "controlconfig/controlsconfig.h"
|
|
#include "hud/hudgauges.h"
|
|
#include "starfield/starfield.h"
|
|
#include "nebula/neb.h"
|
|
#include "nebula/neblightning.h"
|
|
#include "jumpnode/jumpnode.h"
|
|
#include "AddVariableDlg.h"
|
|
#include "ModifyVariableDlg.h"
|
|
#include "gamesnd/eventmusic.h" // for change-soundtrack
|
|
#include "menuui/techmenu.h" // for intel stuff
|
|
#include "weapon/emp.h"
|
|
#include "gamesnd/gamesnd.h"
|
|
#include "weapon/weapon.h"
|
|
#include "hud/hudartillery.h"
|
|
#include "iff_defs/iff_defs.h"
|
|
#include "mission/missionmessage.h"
|
|
|
|
#define TREE_NODE_INCREMENT 100
|
|
|
|
#define MAX_OP_MENUS 30
|
|
#define MAX_SUBMENUS (MAX_OP_MENUS * MAX_OP_MENUS)
|
|
|
|
#define ID_VARIABLE_MENU 0xda00
|
|
#define ID_ADD_MENU 0xdc00
|
|
#define ID_REPLACE_MENU 0xde00
|
|
// note: stay below 0xe000 so we don't collide with MFC defines..
|
|
|
|
#ifdef _DEBUG
|
|
#define new DEBUG_NEW
|
|
#undef THIS_FILE
|
|
static char THIS_FILE[] = __FILE__;
|
|
#endif
|
|
|
|
//********************sexp_tree********************
|
|
|
|
BEGIN_MESSAGE_MAP(sexp_tree, CTreeCtrl)
|
|
//{{AFX_MSG_MAP(sexp_tree)
|
|
ON_NOTIFY_REFLECT(TVN_BEGINDRAG, OnBegindrag)
|
|
ON_WM_MOUSEMOVE()
|
|
ON_WM_LBUTTONUP()
|
|
ON_WM_DESTROY()
|
|
ON_WM_LBUTTONDOWN()
|
|
ON_NOTIFY_REFLECT(TVN_KEYDOWN, OnKeydown)
|
|
//}}AFX_MSG_MAP
|
|
END_MESSAGE_MAP()
|
|
|
|
static int Add_count, Replace_count;
|
|
static int Modify_variable;
|
|
|
|
extern sexp_help_struct Sexp_help[];
|
|
extern op_menu_struct op_menu[];
|
|
extern op_menu_struct op_submenu[];
|
|
|
|
// constructor
|
|
sexp_tree::sexp_tree()
|
|
{
|
|
select_sexp_node = -1;
|
|
root_item = -1;
|
|
m_mode = 0;
|
|
m_dragging = FALSE;
|
|
m_p_image_list = NULL;
|
|
help_box = NULL;
|
|
clear_tree();
|
|
}
|
|
|
|
// clears out the tree, so all the nodes are unused.
|
|
void sexp_tree::clear_tree(char *op)
|
|
{
|
|
mprintf(("Resetting dynamic tree node limit from %d to %d...\n", tree_nodes.size(), 0));
|
|
|
|
total_nodes = flag = 0;
|
|
tree_nodes.clear();
|
|
|
|
if (op) {
|
|
DeleteAllItems();
|
|
if (strlen(op)) {
|
|
set_node(allocate_node(-1), (SEXPT_OPERATOR | SEXPT_VALID), op);
|
|
build_tree();
|
|
}
|
|
}
|
|
}
|
|
|
|
void sexp_tree::reset_handles()
|
|
{
|
|
uint i;
|
|
|
|
for (i=0; i<tree_nodes.size(); i++)
|
|
tree_nodes[i].handle = NULL;
|
|
}
|
|
|
|
// initializes and creates a tree from a given sexp startpoint.
|
|
void sexp_tree::load_tree(int index, char *deflt)
|
|
{
|
|
int cur;
|
|
|
|
clear_tree();
|
|
root_item = 0;
|
|
if (index < 0) {
|
|
cur = allocate_node(-1);
|
|
set_node(cur, (SEXPT_OPERATOR | SEXPT_VALID), deflt); // setup a default tree if none
|
|
build_tree();
|
|
return;
|
|
}
|
|
|
|
if (Sexp_nodes[index].subtype == SEXP_ATOM_NUMBER) { // handle numbers allender likes to use so much..
|
|
cur = allocate_node(-1);
|
|
if (atoi(Sexp_nodes[index].text))
|
|
set_node(cur, (SEXPT_OPERATOR | SEXPT_VALID), "true");
|
|
else
|
|
set_node(cur, (SEXPT_OPERATOR | SEXPT_VALID), "false");
|
|
|
|
build_tree();
|
|
return;
|
|
}
|
|
|
|
// assumption: first token is an operator. I require this because it would cause problems
|
|
// with child/parent relations otherwise, and it should be this way anyway, since the
|
|
// return type of the whole sexp is boolean, and only operators can satisfy this.
|
|
Assert(Sexp_nodes[index].subtype == SEXP_ATOM_OPERATOR);
|
|
load_branch(index, -1);
|
|
build_tree();
|
|
}
|
|
|
|
void get_combined_variable_name(char *combined_name, const char *sexp_var_name)
|
|
{
|
|
int sexp_var_index = get_index_sexp_variable_name(sexp_var_name);
|
|
Assert(sexp_var_index > -1);
|
|
|
|
sprintf(combined_name, "%s(%s)", Sexp_variables[sexp_var_index].variable_name, Sexp_variables[sexp_var_index].text);
|
|
}
|
|
|
|
// creates a tree from a given Sexp_nodes[] point under a given parent. Recursive.
|
|
// Returns the allocated current node.
|
|
int sexp_tree::load_branch(int index, int parent)
|
|
{
|
|
int cur = -1;
|
|
char combined_var_name[2*TOKEN_LENGTH + 2];
|
|
|
|
while (index != -1) {
|
|
Assert(Sexp_nodes[index].type != SEXP_NOT_USED);
|
|
if (Sexp_nodes[index].subtype == SEXP_ATOM_LIST) {
|
|
load_branch(Sexp_nodes[index].first, parent); // do the sublist and continue
|
|
|
|
} else if (Sexp_nodes[index].subtype == SEXP_ATOM_OPERATOR) {
|
|
cur = allocate_node(parent);
|
|
if ((index == select_sexp_node) && !flag) { // translate sexp node to our node
|
|
select_sexp_node = cur;
|
|
flag = 1;
|
|
}
|
|
|
|
set_node(cur, (SEXPT_OPERATOR | SEXPT_VALID), Sexp_nodes[index].text);
|
|
load_branch(Sexp_nodes[index].rest, cur); // operator is new parent now
|
|
return cur; // 'rest' was just used, so nothing left to use.
|
|
|
|
} else if (Sexp_nodes[index].subtype == SEXP_ATOM_NUMBER) {
|
|
cur = allocate_node(parent);
|
|
if (Sexp_nodes[index].type & SEXP_FLAG_VARIABLE) {
|
|
get_combined_variable_name(combined_var_name, Sexp_nodes[index].text);
|
|
set_node(cur, (SEXPT_VARIABLE | SEXPT_NUMBER | SEXPT_VALID), combined_var_name);
|
|
} else {
|
|
set_node(cur, (SEXPT_NUMBER | SEXPT_VALID), Sexp_nodes[index].text);
|
|
}
|
|
|
|
} else if (Sexp_nodes[index].subtype == SEXP_ATOM_STRING) {
|
|
cur = allocate_node(parent);
|
|
if (Sexp_nodes[index].type & SEXP_FLAG_VARIABLE) {
|
|
get_combined_variable_name(combined_var_name, Sexp_nodes[index].text);
|
|
set_node(cur, (SEXPT_VARIABLE | SEXPT_STRING | SEXPT_VALID), combined_var_name);
|
|
} else {
|
|
set_node(cur, (SEXPT_STRING | SEXPT_VALID), Sexp_nodes[index].text);
|
|
}
|
|
|
|
} else
|
|
Assert(0); // unknown and/or invalid sexp type
|
|
|
|
if ((index == select_sexp_node) && !flag) { // translate sexp node to our node
|
|
select_sexp_node = cur;
|
|
flag = 1;
|
|
}
|
|
|
|
index = Sexp_nodes[index].rest;
|
|
if (index == -1)
|
|
return cur;
|
|
}
|
|
|
|
return cur;
|
|
}
|
|
|
|
int sexp_tree::query_false(int node)
|
|
{
|
|
if (node < 0)
|
|
node = root_item;
|
|
|
|
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;
|
|
}
|
|
|
|
// builds an sexp of the tree and returns the index of it. This allocates sexp nodes.
|
|
int sexp_tree::save_tree(int node)
|
|
{
|
|
if (node < 0)
|
|
node = root_item;
|
|
|
|
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);
|
|
}
|
|
|
|
// get variable name from sexp_tree node .text
|
|
void var_name_from_sexp_tree_text(char *var_name, const char *text)
|
|
{
|
|
int 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';
|
|
}
|
|
|
|
#define NO_PREVIOUS_NODE -9
|
|
// called recursively to save a tree branch and everything under it
|
|
int sexp_tree::save_branch(int cur, int at_root)
|
|
{
|
|
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_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 if (tree_nodes[cur].type & SEXPT_STRING) {
|
|
Assert( !(tree_nodes[cur].type & SEXPT_VARIABLE) );
|
|
Int3();
|
|
} 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;
|
|
}
|
|
|
|
// find the next free tree node and return its index.
|
|
int sexp_tree::find_free_node()
|
|
{
|
|
int i;
|
|
|
|
for (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 sexp_tree::allocate_node()
|
|
{
|
|
int node = find_free_node();
|
|
|
|
// need more tree nodes?
|
|
if (node < 0)
|
|
{
|
|
int old_size = 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, 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, "<uninitialized tree node>");
|
|
tree_nodes[node].handle = NULL;
|
|
|
|
total_nodes++;
|
|
return node;
|
|
}
|
|
|
|
// allocate a child node under 'parent'. Appends to end of list.
|
|
int sexp_tree::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;
|
|
}
|
|
|
|
// 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 sexp_tree::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 sexp_tree::free_node2(int node)
|
|
{
|
|
Assert(node != -1);
|
|
Assert(tree_nodes[node].type != SEXPT_UNUSED);
|
|
Assert(total_nodes > 0);
|
|
*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);
|
|
}
|
|
|
|
// initialize the data for a node. Should be called right after a new node is allocated.
|
|
void sexp_tree::set_node(int node, int type, 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 {
|
|
max_length = TOKEN_LENGTH;
|
|
}
|
|
Assert(strlen(text) < max_length);
|
|
strcpy_s(tree_nodes[node].text, text);
|
|
}
|
|
|
|
void sexp_tree::post_load()
|
|
{
|
|
if (!flag)
|
|
select_sexp_node = -1;
|
|
}
|
|
|
|
// build or rebuild a CTreeCtrl object with the current tree data
|
|
void sexp_tree::build_tree()
|
|
{
|
|
if (!flag)
|
|
select_sexp_node = -1;
|
|
|
|
DeleteAllItems();
|
|
add_sub_tree(0, TVI_ROOT);
|
|
}
|
|
|
|
// Create the CTreeCtrl tree from the tree data. The tree data should already be setup by
|
|
// this point.
|
|
void sexp_tree::add_sub_tree(int node, HTREEITEM root)
|
|
{
|
|
// char str[80];
|
|
int node2;
|
|
|
|
Assert(node >= 0 && node < (int)tree_nodes.size());
|
|
node2 = tree_nodes[node].child;
|
|
|
|
// check for single argument operator case (prints as one line)
|
|
/* if (node2 != -1 && tree_nodes[node2].child == -1 && tree_nodes[node2].next == -1) {
|
|
sprintf(str, "%s %s", tree_nodes[node].text, tree_nodes[node2].text);
|
|
tree_nodes[node].handle = insert(str, root);
|
|
tree_nodes[node].flags = OPERAND | EDITABLE;
|
|
tree_nodes[node2].flags = COMBINED;
|
|
return;
|
|
}*/
|
|
|
|
// bitmap to draw in tree
|
|
int bitmap;
|
|
|
|
if (tree_nodes[node].type & SEXPT_OPERATOR) {
|
|
tree_nodes[node].flags = OPERAND;
|
|
bitmap = BITMAP_OPERATOR;
|
|
} else {
|
|
if (tree_nodes[node].type & SEXPT_VARIABLE) {
|
|
tree_nodes[node].flags = NOT_EDITABLE;
|
|
bitmap = BITMAP_VARIABLE;
|
|
} else {
|
|
tree_nodes[node].flags = EDITABLE;
|
|
bitmap = get_data_image(node);
|
|
}
|
|
}
|
|
|
|
root = tree_nodes[node].handle = insert(tree_nodes[node].text, bitmap, bitmap, root);
|
|
|
|
node = node2;
|
|
while (node != -1) {
|
|
Assert(node >= 0 && node < (int)tree_nodes.size());
|
|
Assert(tree_nodes[node].type & SEXPT_VALID);
|
|
if (tree_nodes[node].type & SEXPT_OPERATOR) {
|
|
add_sub_tree(node, root);
|
|
|
|
} else {
|
|
Assert(tree_nodes[node].child == -1);
|
|
if (tree_nodes[node].type & SEXPT_VARIABLE) {
|
|
tree_nodes[node].handle = insert(tree_nodes[node].text, BITMAP_VARIABLE, BITMAP_VARIABLE, root);
|
|
tree_nodes[node].flags = NOT_EDITABLE;
|
|
} else {
|
|
int bmap = get_data_image(node);
|
|
tree_nodes[node].handle = insert(tree_nodes[node].text, bmap, bmap, root);
|
|
tree_nodes[node].flags = EDITABLE;
|
|
}
|
|
}
|
|
|
|
node = tree_nodes[node].next;
|
|
}
|
|
}
|
|
|
|
// construct tree nodes for an sexp, adding them to the list and returning first node
|
|
int sexp_tree::load_sub_tree(int index, bool valid, char *text)
|
|
{
|
|
int cur;
|
|
|
|
if (index < 0) {
|
|
cur = allocate_node(-1);
|
|
set_node(cur, (SEXPT_OPERATOR | (valid ? SEXPT_VALID : 0)), text); // setup a default tree if none
|
|
return cur;
|
|
}
|
|
|
|
// assumption: first token is an operator. I require this because it would cause problems
|
|
// with child/parent relations otherwise, and it should be this way anyway, since the
|
|
// return type of the whole sexp is boolean, and only operators can satisfy this.
|
|
Assert(Sexp_nodes[index].subtype == SEXP_ATOM_OPERATOR);
|
|
cur = load_branch(index, -1);
|
|
return cur;
|
|
}
|
|
|
|
void sexp_tree::setup_selected(HTREEITEM h)
|
|
{
|
|
uint i;
|
|
|
|
item_index = -1;
|
|
item_handle = h;
|
|
if (!h)
|
|
item_handle = GetSelectedItem();
|
|
|
|
for (i=0; i<tree_nodes.size(); i++)
|
|
if (tree_nodes[i].handle == item_handle) {
|
|
item_index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Goober5000
|
|
int get_sexp_id(char *sexp_name)
|
|
{
|
|
for (int i = 0; i < Num_operators; i++)
|
|
{
|
|
if (!stricmp(sexp_name, Operators[i].text))
|
|
return Operators[i].value;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// Goober5000
|
|
int category_of_subcategory(int subcategory_id)
|
|
{
|
|
return (subcategory_id & OP_CATEGORY_MASK);
|
|
}
|
|
|
|
// Goober5000
|
|
int get_category_id(char *category_name)
|
|
{
|
|
for (int i = 0; i < Num_op_menus; i++)
|
|
{
|
|
if (!stricmp(category_name, op_menu[i].name))
|
|
{
|
|
return op_menu[i].id;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// Goober5000
|
|
int has_submenu(char *category_name)
|
|
{
|
|
int category_id = get_category_id(category_name);
|
|
if (category_id != -1)
|
|
{
|
|
for (int i = 0; i < Num_submenus; i++)
|
|
{
|
|
if (category_of_subcategory(op_submenu[i].id) == category_id)
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// handler for right mouse button clicks.
|
|
void sexp_tree::right_clicked(int mode)
|
|
{
|
|
char buf[256];
|
|
int i, j, z, count, op, add_type, replace_type, type, subcategory_id;
|
|
sexp_list_item *list;
|
|
UINT _flags;
|
|
HTREEITEM h;
|
|
POINT click_point, mouse;
|
|
CMenu menu, *mptr, *popup_menu, *add_data_menu = NULL, *replace_data_menu = NULL;
|
|
CMenu *add_op_menu, add_op_submenu[MAX_OP_MENUS];
|
|
CMenu *replace_op_menu, replace_op_submenu[MAX_OP_MENUS];
|
|
CMenu *insert_op_menu, insert_op_submenu[MAX_OP_MENUS];
|
|
CMenu *replace_variable_menu = NULL;
|
|
CMenu add_op_subcategory_menu[MAX_SUBMENUS];
|
|
CMenu replace_op_subcategory_menu[MAX_SUBMENUS];
|
|
CMenu insert_op_subcategory_menu[MAX_SUBMENUS];
|
|
|
|
m_mode = mode;
|
|
add_instance = replace_instance = -1;
|
|
Assert(Num_operators <= MAX_OPERATORS);
|
|
Assert(Num_op_menus < MAX_OP_MENUS);
|
|
Assert(Num_submenus < MAX_SUBMENUS);
|
|
|
|
GetCursorPos(&mouse);
|
|
click_point = mouse;
|
|
ScreenToClient(&click_point);
|
|
h = HitTest(CPoint(click_point), &_flags); // find out what they clicked on
|
|
|
|
if (h && menu.LoadMenu(IDR_MENU_EDIT_SEXP_TREE)) {
|
|
update_help(h);
|
|
popup_menu = menu.GetSubMenu(0);
|
|
ASSERT(popup_menu != NULL);
|
|
//SelectDropTarget(h); // WTF: Why was this here???
|
|
|
|
add_op_menu = replace_op_menu = insert_op_menu = NULL;
|
|
|
|
// get pointers to several key popup menus we'll need to modify
|
|
i = popup_menu->GetMenuItemCount();
|
|
while (i--) {
|
|
if ( (mptr = popup_menu->GetSubMenu(i)) > 0 ) {
|
|
popup_menu->GetMenuString(i, buf, sizeof(buf), MF_BYPOSITION);
|
|
|
|
if (!stricmp(buf, "add operator")) {
|
|
add_op_menu = mptr;
|
|
|
|
} else if (!stricmp(buf, "replace operator")) {
|
|
replace_op_menu = mptr;
|
|
|
|
} else if (!stricmp(buf, "add data")) {
|
|
add_data_menu = mptr;
|
|
|
|
} else if (!stricmp(buf, "replace data")) {
|
|
replace_data_menu = mptr;
|
|
|
|
} else if (!stricmp(buf, "insert operator")) {
|
|
insert_op_menu = mptr;
|
|
|
|
} else if (!stricmp(buf, "replace variable")) {
|
|
replace_variable_menu = mptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
// add popup menus for all the operator categories
|
|
for (i=0; i<Num_op_menus; i++)
|
|
{
|
|
add_op_submenu[i].CreatePopupMenu();
|
|
replace_op_submenu[i].CreatePopupMenu();
|
|
insert_op_submenu[i].CreatePopupMenu();
|
|
|
|
add_op_menu->AppendMenu(MF_POPUP, (UINT) add_op_submenu[i].m_hMenu, op_menu[i].name);
|
|
replace_op_menu->AppendMenu(MF_POPUP, (UINT) replace_op_submenu[i].m_hMenu, op_menu[i].name);
|
|
insert_op_menu->AppendMenu(MF_POPUP, (UINT) insert_op_submenu[i].m_hMenu, op_menu[i].name);
|
|
}
|
|
|
|
// get rid of the placeholders we needed to ensure popup menus stayed popup menus,
|
|
// i.e. MSDEV will convert empty popup menus into normal menu items.
|
|
add_op_menu->DeleteMenu(ID_PLACEHOLDER, MF_BYCOMMAND);
|
|
replace_op_menu->DeleteMenu(ID_PLACEHOLDER, MF_BYCOMMAND);
|
|
insert_op_menu->DeleteMenu(ID_PLACEHOLDER, MF_BYCOMMAND);
|
|
replace_variable_menu->DeleteMenu(ID_PLACEHOLDER, MF_BYCOMMAND);
|
|
|
|
// get item_index
|
|
item_index = -1;
|
|
for (i=0; i<(int)tree_nodes.size(); i++) {
|
|
if (tree_nodes[i].handle == h) {
|
|
item_index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
Goober5000 - allow variables in all modes;
|
|
the restriction seems unnecessary IMHO
|
|
|
|
// Do SEXP_VARIABLE stuff here.
|
|
if (m_mode != MODE_EVENTS)
|
|
{
|
|
// only allow variables in event mode
|
|
menu.EnableMenuItem(ID_SEXP_TREE_ADD_VARIABLE, MF_GRAYED);
|
|
menu.EnableMenuItem(ID_SEXP_TREE_MODIFY_VARIABLE, MF_GRAYED);
|
|
}
|
|
else
|
|
*/
|
|
{
|
|
menu.EnableMenuItem(ID_SEXP_TREE_ADD_VARIABLE, MF_ENABLED);
|
|
menu.EnableMenuItem(ID_SEXP_TREE_MODIFY_VARIABLE, MF_ENABLED);
|
|
|
|
// check not root (-1)
|
|
if (item_index >= 0) {
|
|
// get type of sexp_tree item clicked on
|
|
int type = get_type(h);
|
|
|
|
int parent = tree_nodes[item_index].parent;
|
|
if (parent >= 0) {
|
|
op = get_operator_index(tree_nodes[parent].text);
|
|
Assert(op >= 0);
|
|
int first_arg = tree_nodes[parent].child;
|
|
|
|
// get arg count of item to replace
|
|
Replace_count = 0;
|
|
int temp = first_arg;
|
|
while (temp != item_index) {
|
|
Replace_count++;
|
|
temp = tree_nodes[temp].next;
|
|
|
|
// DB - added 3/4/99
|
|
if(temp == -1){
|
|
break;
|
|
}
|
|
}
|
|
|
|
int op_type = query_operator_argument_type(op, Replace_count); // check argument type at this position
|
|
|
|
// special case don't allow replace data for variable names
|
|
// Goober5000 - why? the only place this happens is when replacing the ambiguous argument in
|
|
// modify-variable with a variable, which seems legal enough.
|
|
//if (op_type != OPF_AMBIGUOUS) {
|
|
|
|
// Goober5000 - given the above, we have to figure out what type this stands for
|
|
if (op_type == OPF_AMBIGUOUS)
|
|
{
|
|
type = tree_nodes[first_arg].type;
|
|
}
|
|
|
|
if ( (type & SEXPT_STRING) || (type & SEXPT_NUMBER) ) {
|
|
|
|
int max_sexp_vars = MAX_SEXP_VARIABLES;
|
|
// prevent collisions in id numbers: ID_VARIABLE_MENU + 512 = ID_ADD_MENU
|
|
Assert(max_sexp_vars < 512);
|
|
|
|
for (int idx=0; idx<max_sexp_vars; idx++) {
|
|
if (Sexp_variables[idx].type & SEXP_VARIABLE_SET) {
|
|
// skip block variables
|
|
if (Sexp_variables[idx].type & SEXP_VARIABLE_BLOCK) {
|
|
continue;
|
|
}
|
|
|
|
UINT flag = MF_STRING | MF_GRAYED;
|
|
// maybe gray flag MF_GRAYED
|
|
|
|
// get type -- gray "string" or number accordingly
|
|
if ( type & SEXPT_STRING ) {
|
|
if ( Sexp_variables[idx].type & SEXP_VARIABLE_STRING ) {
|
|
flag &= ~MF_GRAYED;
|
|
}
|
|
} else if ( type & SEXPT_NUMBER ) {
|
|
if ( Sexp_variables[idx].type & SEXP_VARIABLE_NUMBER ) {
|
|
flag &= ~MF_GRAYED;
|
|
}
|
|
}
|
|
|
|
// if modify-variable and changing variable, enable all variables
|
|
if (op_type == OPF_VARIABLE_NAME) {
|
|
Modify_variable = 1;
|
|
flag &= ~MF_GRAYED;
|
|
} else {
|
|
Modify_variable = 0;
|
|
}
|
|
|
|
// enable navsystem always
|
|
if (op_type == OPF_NAV_POINT)
|
|
flag &= ~MF_GRAYED;
|
|
|
|
char buf[128];
|
|
// append list of variable names and values
|
|
// set id as ID_VARIABLE_MENU + idx
|
|
sprintf(buf, "%s (%s)", Sexp_variables[idx].variable_name, Sexp_variables[idx].text);
|
|
|
|
replace_variable_menu->AppendMenu(flag, (ID_VARIABLE_MENU + idx), buf);
|
|
}
|
|
}
|
|
}
|
|
//}
|
|
}
|
|
}
|
|
|
|
// can't modify if no variables
|
|
if (sexp_variable_count() == 0) {
|
|
menu.EnableMenuItem(ID_SEXP_TREE_MODIFY_VARIABLE, MF_GRAYED);
|
|
}
|
|
}
|
|
|
|
// add all the submenu items first
|
|
for (i=0; i<Num_submenus; i++)
|
|
{
|
|
add_op_subcategory_menu[i].CreatePopupMenu();
|
|
replace_op_subcategory_menu[i].CreatePopupMenu();
|
|
insert_op_subcategory_menu[i].CreatePopupMenu();
|
|
|
|
for (j=0; j<Num_op_menus; j++)
|
|
{
|
|
if (op_menu[j].id == category_of_subcategory(op_submenu[i].id))
|
|
{
|
|
add_op_submenu[j].AppendMenu(MF_POPUP, (UINT) add_op_subcategory_menu[i].m_hMenu, op_submenu[i].name);
|
|
replace_op_submenu[j].AppendMenu(MF_POPUP, (UINT) replace_op_subcategory_menu[i].m_hMenu, op_submenu[i].name);
|
|
insert_op_submenu[j].AppendMenu(MF_POPUP, (UINT) insert_op_subcategory_menu[i].m_hMenu, op_submenu[i].name);
|
|
break; // only 1 category valid
|
|
}
|
|
}
|
|
}
|
|
|
|
// add operator menu items to the various CATEGORY submenus they belong in
|
|
for (i=0; i<Num_operators; i++)
|
|
{
|
|
// add only if it is not in a subcategory
|
|
subcategory_id = get_subcategory(Operators[i].value);
|
|
if (subcategory_id == -1)
|
|
{
|
|
// put it in the appropriate menu
|
|
for (j=0; j<Num_op_menus; j++)
|
|
{
|
|
if (op_menu[j].id == (Operators[i].value & OP_CATEGORY_MASK))
|
|
{
|
|
// Commented out by Goober5000 to allow these sexps to be selectable
|
|
/*#ifdef NDEBUG
|
|
switch (Operators[i].value) {
|
|
case OP_WAS_PROMOTION_GRANTED:
|
|
case OP_WAS_MEDAL_GRANTED:
|
|
case OP_GRANT_PROMOTION:
|
|
case OP_GRANT_MEDAL:
|
|
case OP_TECH_ADD_SHIP:
|
|
case OP_TECH_ADD_WEAPON:
|
|
case OP_TECH_ADD_INTEL:
|
|
case OP_TECH_RESET_TO_DEFAULT:
|
|
j = Num_op_menus; // don't allow these operators in final release
|
|
}
|
|
#endif*/
|
|
if (j < Num_op_menus) {
|
|
add_op_submenu[j].AppendMenu(MF_STRING | MF_GRAYED, Operators[i].value, Operators[i].text);
|
|
replace_op_submenu[j].AppendMenu(MF_STRING | MF_GRAYED, Operators[i].value | OP_REPLACE_FLAG, Operators[i].text);
|
|
insert_op_submenu[j].AppendMenu(MF_STRING, Operators[i].value | OP_INSERT_FLAG, Operators[i].text);
|
|
}
|
|
|
|
break; // only 1 category valid
|
|
}
|
|
}
|
|
}
|
|
// if it is in a subcategory, handle it
|
|
else
|
|
{
|
|
// put it in the appropriate submenu
|
|
for (j=0; j<Num_submenus; j++)
|
|
{
|
|
if (op_submenu[j].id == subcategory_id)
|
|
{
|
|
add_op_subcategory_menu[j].AppendMenu(MF_STRING | MF_GRAYED, Operators[i].value, Operators[i].text);
|
|
replace_op_subcategory_menu[j].AppendMenu(MF_STRING | MF_GRAYED, Operators[i].value | OP_REPLACE_FLAG, Operators[i].text);
|
|
insert_op_subcategory_menu[j].AppendMenu(MF_STRING, Operators[i].value | OP_INSERT_FLAG, Operators[i].text);
|
|
break; // only 1 subcategory valid
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// find local index (i) of current item (from its handle)
|
|
SelectItem(item_handle = h);
|
|
for (i=0; i<(int)tree_nodes.size(); i++) {
|
|
if (tree_nodes[i].handle == h) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// special case: item is a ROOT node, and a label that can be edited (not an item in the sexp tree)
|
|
if ((item_index == -1) && (m_mode & ST_LABELED_ROOT)) {
|
|
if (m_mode & ST_ROOT_EDITABLE) {
|
|
menu.EnableMenuItem(ID_EDIT_TEXT, MF_ENABLED);
|
|
} else {
|
|
menu.EnableMenuItem(ID_EDIT_TEXT, MF_GRAYED);
|
|
}
|
|
|
|
// disable copy, insert op
|
|
menu.EnableMenuItem(ID_EDIT_COPY, MF_GRAYED);
|
|
for (j=0; j<Num_operators; j++) {
|
|
menu.EnableMenuItem(Operators[j].value | OP_INSERT_FLAG, MF_GRAYED);
|
|
}
|
|
|
|
gray_menu_tree(popup_menu);
|
|
popup_menu->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, mouse.x, mouse.y, this);
|
|
return;
|
|
}
|
|
|
|
Assert(item_index != -1); // handle not found, which should be impossible.
|
|
if (!(tree_nodes[item_index].flags & EDITABLE)) {
|
|
menu.EnableMenuItem(ID_EDIT_TEXT, MF_GRAYED);
|
|
}
|
|
|
|
if (tree_nodes[item_index].parent == -1) { // root node
|
|
menu.EnableMenuItem(ID_DELETE, MF_GRAYED); // can't delete the root item.
|
|
}
|
|
|
|
/* if ((tree_nodes[item_index].flags & OPERAND) && (tree_nodes[item_index].flags & EDITABLE)) // expandable?
|
|
menu.EnableMenuItem(ID_SPLIT_LINE, MF_ENABLED);
|
|
|
|
z = tree_nodes[item_index].child;
|
|
if (z != -1 && tree_nodes[z].next == -1 && tree_nodes[z].child == -1)
|
|
menu.EnableMenuItem(ID_SPLIT_LINE, MF_ENABLED);
|
|
|
|
z = tree_nodes[tree_nodes[item_index].parent].child;
|
|
if (z != -1 && tree_nodes[z].next == -1 && tree_nodes[z].child == -1)
|
|
menu.EnableMenuItem(ID_SPLIT_LINE, MF_ENABLED);*/
|
|
|
|
// change enabled status of 'add' type menu options.
|
|
add_type = 0;
|
|
if (tree_nodes[item_index].flags & OPERAND) {
|
|
add_type = OPR_STRING;
|
|
int child = tree_nodes[item_index].child;
|
|
Add_count = count_args(child);
|
|
op = get_operator_index(tree_nodes[item_index].text);
|
|
Assert(op >= 0);
|
|
|
|
// get listing of valid argument values and add to menus
|
|
type = query_operator_argument_type(op, Add_count);
|
|
list = get_listing_opf(type, item_index, Add_count);
|
|
if (list) {
|
|
sexp_list_item *ptr;
|
|
|
|
int data_idx = 0;
|
|
ptr = list;
|
|
while (ptr) {
|
|
if (ptr->op >= 0) {
|
|
// enable operators with correct return type
|
|
menu.EnableMenuItem(Operators[ptr->op].value, MF_ENABLED);
|
|
|
|
} else {
|
|
// add data
|
|
if ( (data_idx + 3) % 30) {
|
|
add_data_menu->AppendMenu(MF_STRING | MF_ENABLED, ID_ADD_MENU + data_idx, ptr->text);
|
|
} else {
|
|
add_data_menu->AppendMenu(MF_MENUBARBREAK | MF_STRING | MF_ENABLED, ID_ADD_MENU + data_idx, ptr->text);
|
|
}
|
|
}
|
|
|
|
data_idx++;
|
|
ptr = ptr->next;
|
|
}
|
|
}
|
|
|
|
// special handling for the non-string formats
|
|
if (type == OPF_NONE) { // an argument can't be added
|
|
add_type = 0;
|
|
|
|
} else if (type == OPF_NULL) { // arguments with no return values
|
|
add_type = OPR_NULL;
|
|
|
|
// Goober5000
|
|
} else if (type == OPF_FLEXIBLE_ARGUMENT) {
|
|
add_type = OPR_FLEXIBLE_ARGUMENT;
|
|
|
|
} else if (type == OPF_NUMBER) { // takes numbers
|
|
add_type = OPR_NUMBER;
|
|
menu.EnableMenuItem(ID_ADD_NUMBER, MF_ENABLED);
|
|
|
|
} else if (type == OPF_POSITIVE) { // takes non-negative numbers
|
|
add_type = OPR_POSITIVE;
|
|
menu.EnableMenuItem(ID_ADD_NUMBER, MF_ENABLED);
|
|
|
|
} else if (type == OPF_BOOL) { // takes true/false bool values
|
|
add_type = OPR_BOOL;
|
|
|
|
} else if (type == OPF_AI_GOAL) {
|
|
add_type = OPR_AI_GOAL;
|
|
}
|
|
|
|
// add_type unchanged from above
|
|
if (add_type == OPR_STRING) {
|
|
menu.EnableMenuItem(ID_ADD_STRING, MF_ENABLED);
|
|
}
|
|
|
|
list->destroy();
|
|
}
|
|
|
|
// disable operators that do not have arguments available
|
|
for (j=0; j<Num_operators; j++) {
|
|
if (!query_default_argument_available(j)) {
|
|
menu.EnableMenuItem(Operators[j].value, MF_GRAYED);
|
|
}
|
|
}
|
|
|
|
|
|
// change enabled status of 'replace' type menu options.
|
|
replace_type = 0;
|
|
int parent = tree_nodes[item_index].parent;
|
|
if (parent >= 0) {
|
|
replace_type = OPR_STRING;
|
|
op = get_operator_index(tree_nodes[parent].text);
|
|
Assert(op >= 0);
|
|
int first_arg = tree_nodes[parent].child;
|
|
count = count_args(tree_nodes[parent].child);
|
|
|
|
// already at minimum number of arguments?
|
|
if (count <= Operators[op].min) {
|
|
menu.EnableMenuItem(ID_DELETE, MF_GRAYED);
|
|
}
|
|
|
|
// get arg count of item to replace
|
|
Replace_count = 0;
|
|
int temp = first_arg;
|
|
while (temp != item_index) {
|
|
Replace_count++;
|
|
temp = tree_nodes[temp].next;
|
|
|
|
// DB - added 3/4/99
|
|
if(temp == -1){
|
|
break;
|
|
}
|
|
}
|
|
|
|
// maybe gray delete
|
|
for (i=Replace_count+1; i<count; i++) {
|
|
if (query_operator_argument_type(op, i-1) != query_operator_argument_type(op, i)) {
|
|
menu.EnableMenuItem(ID_DELETE, MF_GRAYED);
|
|
break;
|
|
}
|
|
}
|
|
|
|
type = query_operator_argument_type(op, Replace_count); // check argument type at this position
|
|
|
|
// special case reset type for ambiguous
|
|
if (type == OPF_AMBIGUOUS) {
|
|
type = get_modify_variable_type(parent);
|
|
}
|
|
|
|
list = get_listing_opf(type, parent, Replace_count);
|
|
|
|
// special case don't allow replace data for variable names
|
|
if ( (type != OPF_VARIABLE_NAME) && list) {
|
|
sexp_list_item *ptr;
|
|
|
|
int data_idx = 0;
|
|
ptr = list;
|
|
while (ptr) {
|
|
if (ptr->op >= 0) {
|
|
menu.EnableMenuItem(Operators[ptr->op].value | OP_REPLACE_FLAG, MF_ENABLED);
|
|
|
|
} else {
|
|
if ( (data_idx + 3) % 30)
|
|
replace_data_menu->AppendMenu(MF_STRING | MF_ENABLED, ID_REPLACE_MENU + data_idx, ptr->text);
|
|
else
|
|
replace_data_menu->AppendMenu(MF_MENUBARBREAK | MF_STRING | MF_ENABLED, ID_REPLACE_MENU + data_idx, ptr->text);
|
|
}
|
|
|
|
data_idx++;
|
|
ptr = ptr->next;
|
|
}
|
|
}
|
|
|
|
if (type == OPF_NONE) { // takes no arguments
|
|
replace_type = 0;
|
|
|
|
} else if (type == OPF_NUMBER) { // takes numbers
|
|
replace_type = OPR_NUMBER;
|
|
menu.EnableMenuItem(ID_REPLACE_NUMBER, MF_ENABLED);
|
|
|
|
} else if (type == OPF_POSITIVE) { // takes non-negative numbers
|
|
replace_type = OPR_POSITIVE;
|
|
menu.EnableMenuItem(ID_REPLACE_NUMBER, MF_ENABLED);
|
|
|
|
} else if (type == OPF_BOOL) { // takes true/false bool values
|
|
replace_type = OPR_BOOL;
|
|
|
|
} else if (type == OPF_NULL) { // takes operator that doesn't return a value
|
|
replace_type = OPR_NULL;
|
|
|
|
// Goober5000
|
|
} else if (type == OPF_FLEXIBLE_ARGUMENT) {
|
|
replace_type = OPR_FLEXIBLE_ARGUMENT;
|
|
|
|
} else if (type == OPF_AI_GOAL) {
|
|
replace_type = OPR_AI_GOAL;
|
|
}
|
|
|
|
// default to string
|
|
if (replace_type == OPR_STRING) {
|
|
menu.EnableMenuItem(ID_REPLACE_STRING, MF_ENABLED);
|
|
}
|
|
|
|
// modify string or number if (modify_variable)
|
|
if ( !stricmp(Operators[op].text, "modify-variable") ) {
|
|
int modify_type = get_modify_variable_type(parent);
|
|
|
|
if (modify_type == OPF_NUMBER) {
|
|
menu.EnableMenuItem(ID_REPLACE_NUMBER, MF_ENABLED);
|
|
menu.EnableMenuItem(ID_REPLACE_STRING, MF_GRAYED);
|
|
}
|
|
// no change for string type
|
|
}
|
|
|
|
list->destroy();
|
|
|
|
} else { // top node, so should be a Boolean type.
|
|
if (m_mode == MODE_EVENTS) { // return type should be null
|
|
replace_type = OPR_NULL;
|
|
for (j=0; j<Num_operators; j++)
|
|
if (query_operator_return_type(j) == OPR_NULL)
|
|
menu.EnableMenuItem(Operators[j].value | OP_REPLACE_FLAG, MF_ENABLED);
|
|
|
|
} else {
|
|
replace_type = OPR_BOOL;
|
|
for (j=0; j<Num_operators; j++)
|
|
if (query_operator_return_type(j) == OPR_BOOL)
|
|
menu.EnableMenuItem(Operators[j].value | OP_REPLACE_FLAG, MF_ENABLED);
|
|
}
|
|
}
|
|
|
|
// disable operators that do not have arguments available
|
|
for (j=0; j<Num_operators; j++) {
|
|
if (!query_default_argument_available(j)) {
|
|
menu.EnableMenuItem(Operators[j].value | OP_REPLACE_FLAG, MF_GRAYED);
|
|
}
|
|
}
|
|
|
|
|
|
// change enabled status of 'insert' type menu options.
|
|
z = tree_nodes[item_index].parent;
|
|
Assert(z >= -1);
|
|
if (z != -1) {
|
|
op = get_operator_index(tree_nodes[z].text);
|
|
Assert(op != -1);
|
|
j = tree_nodes[z].child;
|
|
count = 0;
|
|
while (j != item_index) {
|
|
count++;
|
|
j = tree_nodes[j].next;
|
|
}
|
|
|
|
type = query_operator_argument_type(op, count); // check argument type at this position
|
|
|
|
} else {
|
|
if (m_mode == MODE_EVENTS)
|
|
type = OPF_NULL;
|
|
else
|
|
type = OPF_BOOL;
|
|
}
|
|
|
|
for (j=0; j<Num_operators; j++) {
|
|
z = query_operator_return_type(j);
|
|
if (!sexp_query_type_match(type, z) || (Operators[j].min < 1))
|
|
menu.EnableMenuItem(Operators[j].value | OP_INSERT_FLAG, MF_GRAYED);
|
|
|
|
z = query_operator_argument_type(j, 0);
|
|
if ((type == OPF_NUMBER) && (z == OPF_POSITIVE))
|
|
z = OPF_NUMBER;
|
|
|
|
// Goober5000's number hack
|
|
if ((type == OPF_POSITIVE) && (z == OPF_NUMBER))
|
|
z = OPF_POSITIVE;
|
|
|
|
if (z != type)
|
|
menu.EnableMenuItem(Operators[j].value | OP_INSERT_FLAG, MF_GRAYED);
|
|
}
|
|
|
|
// disable operators that do not have arguments available
|
|
for (j=0; j<Num_operators; j++) {
|
|
if (!query_default_argument_available(j)) {
|
|
menu.EnableMenuItem(Operators[j].value | OP_INSERT_FLAG, MF_GRAYED);
|
|
}
|
|
}
|
|
|
|
|
|
// disable non campaign operators if in campaign mode
|
|
for (j=0; j<Num_operators; j++) {
|
|
z = 0;
|
|
if (m_mode == MODE_CAMPAIGN) {
|
|
if (Operators[j].value & OP_NONCAMPAIGN_FLAG)
|
|
z = 1;
|
|
|
|
} else {
|
|
if (Operators[j].value & OP_CAMPAIGN_ONLY_FLAG)
|
|
z = 1;
|
|
}
|
|
|
|
if (z) {
|
|
menu.EnableMenuItem(Operators[j].value, MF_GRAYED);
|
|
menu.EnableMenuItem(Operators[j].value | OP_REPLACE_FLAG, MF_GRAYED);
|
|
menu.EnableMenuItem(Operators[j].value | OP_INSERT_FLAG, MF_GRAYED);
|
|
}
|
|
}
|
|
|
|
if ((Sexp_clipboard > -1) && (Sexp_nodes[Sexp_clipboard].type != SEXP_NOT_USED)) {
|
|
Assert(Sexp_nodes[Sexp_clipboard].subtype != SEXP_ATOM_LIST);
|
|
|
|
if (Sexp_nodes[Sexp_clipboard].subtype == SEXP_ATOM_OPERATOR) {
|
|
j = get_operator_const(CTEXT(Sexp_clipboard));
|
|
Assert(j);
|
|
z = query_operator_return_type(j);
|
|
|
|
if ((z == OPR_POSITIVE) && (replace_type == OPR_NUMBER))
|
|
z = OPR_NUMBER;
|
|
|
|
// Goober5000's number hack
|
|
if ((z == OPR_NUMBER) && (replace_type == OPR_POSITIVE))
|
|
z = OPR_POSITIVE;
|
|
|
|
if (replace_type == z)
|
|
menu.EnableMenuItem(ID_EDIT_PASTE, MF_ENABLED);
|
|
|
|
z = query_operator_return_type(j);
|
|
if ((z == OPR_POSITIVE) && (add_type == OPR_NUMBER))
|
|
z = OPR_NUMBER;
|
|
|
|
if (add_type == z)
|
|
menu.EnableMenuItem(ID_EDIT_PASTE_SPECIAL, MF_ENABLED);
|
|
|
|
} else if (Sexp_nodes[Sexp_clipboard].subtype == SEXP_ATOM_NUMBER) {
|
|
if ((replace_type == OPR_POSITIVE) && (atoi(CTEXT(Sexp_clipboard)) > -1))
|
|
menu.EnableMenuItem(ID_EDIT_PASTE, MF_ENABLED);
|
|
|
|
else if (replace_type == OPR_NUMBER)
|
|
menu.EnableMenuItem(ID_EDIT_PASTE, MF_ENABLED);
|
|
|
|
if ((add_type == OPR_POSITIVE) && (atoi(CTEXT(Sexp_clipboard)) > -1))
|
|
menu.EnableMenuItem(ID_EDIT_PASTE_SPECIAL, MF_ENABLED);
|
|
|
|
else if (add_type == OPR_NUMBER)
|
|
menu.EnableMenuItem(ID_EDIT_PASTE_SPECIAL, MF_ENABLED);
|
|
|
|
} else if (Sexp_nodes[Sexp_clipboard].subtype == SEXP_ATOM_STRING) {
|
|
if (replace_type == OPR_STRING)
|
|
menu.EnableMenuItem(ID_EDIT_PASTE, MF_ENABLED);
|
|
|
|
if (add_type == OPR_STRING)
|
|
menu.EnableMenuItem(ID_EDIT_PASTE_SPECIAL, MF_ENABLED);
|
|
|
|
} else
|
|
Int3(); // unknown and/or invalid sexp type
|
|
}
|
|
|
|
if (!(menu.GetMenuState(ID_DELETE, MF_BYCOMMAND) & MF_GRAYED))
|
|
menu.EnableMenuItem(ID_EDIT_CUT, MF_ENABLED);
|
|
|
|
gray_menu_tree(popup_menu);
|
|
popup_menu->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, mouse.x, mouse.y, this);
|
|
}
|
|
}
|
|
|
|
// counts the number of arguments an operator has. Call this with the node of the first
|
|
// argument of the operator
|
|
int sexp_tree::count_args(int node)
|
|
{
|
|
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 sexp_tree::identify_arg_type(int node)
|
|
{
|
|
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;
|
|
}
|
|
|
|
// determine if an item should be editable. This doesn't actually edit the label.
|
|
int sexp_tree::edit_label(HTREEITEM h)
|
|
{
|
|
uint i;
|
|
|
|
for (i=0; i<tree_nodes.size(); i++) {
|
|
if (tree_nodes[i].handle == h) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Check if tree root
|
|
if (i == tree_nodes.size()) {
|
|
if (m_mode & ST_ROOT_EDITABLE) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Operators are editable
|
|
if (tree_nodes[i].type & SEXPT_OPERATOR) {
|
|
return 1;
|
|
}
|
|
|
|
// Variables must be edited through dialog box
|
|
if (tree_nodes[i].type & SEXPT_VARIABLE) {
|
|
return 0;
|
|
}
|
|
|
|
// Don't edit if not flaged as editable
|
|
if (!(tree_nodes[i].flags & EDITABLE)) {
|
|
return 0;
|
|
}
|
|
|
|
// Otherwise, allow editing
|
|
return 1;
|
|
|
|
/*
|
|
if (tree_nodes[i].flags & OPERAND) {
|
|
data = tree_nodes[i].child;
|
|
|
|
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].flags = EDITABLE;
|
|
Expand(h, TVE_EXPAND);
|
|
SelectItem(item_handle);
|
|
return 2;
|
|
}
|
|
*/
|
|
}
|
|
|
|
// given a tree node, returns the argument type it should be.
|
|
int sexp_tree::query_node_argument_type(int node)
|
|
{
|
|
int argnum = 0;
|
|
int parent_node = tree_nodes[node].parent;
|
|
Assert(parent_node >= 0);
|
|
argnum = find_argument_number(parent_node, node);
|
|
int op_num = get_operator_index(tree_nodes[parent_node].text);
|
|
return query_operator_argument_type(op_num, argnum);
|
|
}
|
|
|
|
int sexp_tree::end_label_edit(TVITEMA &item)
|
|
{
|
|
HTREEITEM h = item.hItem;
|
|
char *str = item.pszText;
|
|
int len, r = 1;
|
|
bool update_node = true;
|
|
uint node;
|
|
|
|
*modified = 1;
|
|
if (!str)
|
|
return 0;
|
|
|
|
for (node=0; node<tree_nodes.size(); node++)
|
|
if (tree_nodes[node].handle == h)
|
|
break;
|
|
|
|
if (node == tree_nodes.size()) {
|
|
if (m_mode == MODE_EVENTS) {
|
|
item_index = GetItemData(h);
|
|
Assert(Event_editor_dlg);
|
|
node = Event_editor_dlg->handler(ROOT_RENAMED, item_index, str);
|
|
return 1;
|
|
|
|
} else
|
|
Int3(); // root labels shouldn't have been editable!
|
|
}
|
|
|
|
Assert(node < tree_nodes.size());
|
|
if (tree_nodes[node].type & SEXPT_OPERATOR) {
|
|
str = match_closest_operator(str, node);
|
|
if (!str) return 0; // Goober5000 - avoids crashing
|
|
|
|
SetItemText(h, str);
|
|
item_index = node;
|
|
int op_num = get_operator_index(str);
|
|
if (op_num >= 0 ) {
|
|
add_or_replace_operator(op_num, 1);
|
|
}
|
|
else {
|
|
update_node = false;
|
|
}
|
|
r = 0;
|
|
}
|
|
|
|
// gotta sidestep Goober5000's number hack and check entries are actually positive.
|
|
else if (tree_nodes[node].type & SEXPT_NUMBER) {
|
|
if (query_node_argument_type(node) == OPF_POSITIVE) {
|
|
int val = atoi(str);
|
|
if (val < 0) {
|
|
MessageBox("Can not enter a negative value", "Invalid Number", MB_ICONEXCLAMATION);
|
|
update_node = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Error checking would not hurt here
|
|
len = strlen(str);
|
|
if (len >= TOKEN_LENGTH)
|
|
len = TOKEN_LENGTH - 1;
|
|
|
|
if (update_node) {
|
|
strncpy(tree_nodes[node].text, str, len);
|
|
tree_nodes[node].text[len] = 0;
|
|
}
|
|
else {
|
|
strncpy(str, tree_nodes[node].text, len);
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* node = tree_nodes[node].parent;
|
|
if (node != -1) {
|
|
child = tree_nodes[node].child;
|
|
if (child != -1 && tree_nodes[child].next == -1 && tree_nodes[child].child == -1) {
|
|
merge_operator(child);
|
|
return 0;
|
|
}
|
|
}*/
|
|
|
|
return r;
|
|
}
|
|
|
|
// 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'
|
|
//
|
|
char *sexp_tree::match_closest_operator(char *str, int node)
|
|
{
|
|
int z, i, op, arg_num, opf, opr;
|
|
char *sub_best = NULL, *best = NULL;
|
|
|
|
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
|
|
opr = query_operator_return_type(op);
|
|
for (i=0; i<Num_operators; i++) {
|
|
if (sexp_query_type_match(opf, opr)) {
|
|
if ( (stricmp(str, Operators[i].text) <= 0) && (!best || (stricmp(str, best) < 0)) )
|
|
best = Operators[i].text;
|
|
|
|
if ( !sub_best || (stricmp(Operators[i].text, sub_best) > 0) )
|
|
sub_best = Operators[i].text;
|
|
}
|
|
}
|
|
|
|
if (!best)
|
|
best = sub_best; // no best found, use our plan #2 best found.
|
|
|
|
Assert(best); // we better have some valid operator at this point.
|
|
return best;
|
|
|
|
/* char buf[256];
|
|
int child;
|
|
|
|
if (tree_nodes[node].flags == EDITABLE) // data
|
|
node = tree_nodes[node].parent;
|
|
|
|
if (node != -1) {
|
|
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);
|
|
tree_nodes[node].flags = OPERAND | EDITABLE;
|
|
tree_nodes[child].flags = COMBINED;
|
|
DeleteItem(tree_nodes[child].handle);
|
|
tree_nodes[child].handle = NULL;
|
|
return;
|
|
}
|
|
}*/
|
|
}
|
|
|
|
// this really only handles messages generated by the right click popup menu
|
|
BOOL sexp_tree::OnCommand(WPARAM wParam, LPARAM lParam)
|
|
{
|
|
int i, z, id, node, op, type;
|
|
sexp_list_item *list, *ptr;
|
|
HTREEITEM h;
|
|
|
|
if ((item_index >= 0) && (item_index < total_nodes))
|
|
item_handle = tree_nodes[item_index].handle;
|
|
|
|
id = LOWORD(wParam);
|
|
|
|
|
|
// Add variable
|
|
if (id == ID_SEXP_TREE_ADD_VARIABLE) {
|
|
CAddVariableDlg dlg;
|
|
dlg.DoModal();
|
|
|
|
if ( dlg.m_create ) {
|
|
|
|
// set type
|
|
int type;
|
|
if ( dlg.m_type_number ) {
|
|
type = SEXP_VARIABLE_NUMBER;
|
|
} else {
|
|
type = SEXP_VARIABLE_STRING;
|
|
}
|
|
|
|
if ( dlg.m_type_network_variable ) {
|
|
type |= SEXP_VARIABLE_NETWORK;
|
|
}
|
|
|
|
if ( dlg.m_type_campaign_persistent ) {
|
|
type |= SEXP_VARIABLE_CAMPAIGN_PERSISTENT;
|
|
} else if ( dlg.m_type_player_persistent ) {
|
|
type |= SEXP_VARIABLE_PLAYER_PERSISTENT;
|
|
}
|
|
|
|
// add variable
|
|
sexp_add_variable(dlg.m_default_value, dlg.m_variable_name, type);
|
|
|
|
// sort variable
|
|
sexp_variable_sort();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
// Modify variable
|
|
if (id == ID_SEXP_TREE_MODIFY_VARIABLE) {
|
|
CModifyVariableDlg dlg;
|
|
|
|
// get sexp_variable index for item index
|
|
dlg.m_start_index = get_item_index_to_var_index();
|
|
|
|
// get pointer to tree
|
|
dlg.m_p_sexp_tree = this;
|
|
|
|
dlg.DoModal();
|
|
|
|
Assert( !(dlg.m_deleted && dlg.m_do_modify) );
|
|
|
|
if (dlg.m_deleted) {
|
|
// find index in sexp_variable list
|
|
int sexp_var_index = get_index_sexp_variable_name(dlg.m_cur_variable_name);
|
|
Assert(sexp_var_index != -1);
|
|
|
|
// delete from list
|
|
sexp_variable_delete(sexp_var_index);
|
|
|
|
// sort list
|
|
sexp_variable_sort();
|
|
|
|
// delete from sexp_tree, replacing with "number" or "string" as needed
|
|
// further error checking from add_data()
|
|
delete_sexp_tree_variable(dlg.m_cur_variable_name);
|
|
|
|
return 1;
|
|
}
|
|
|
|
if (dlg.m_do_modify) {
|
|
// check sexp_tree -- warn on type
|
|
// find index and change either (1) name, (2) type, (3) value
|
|
int sexp_var_index = get_index_sexp_variable_name(dlg.m_old_var_name);
|
|
Assert(sexp_var_index != -1);
|
|
|
|
// save old name, since name may be modified
|
|
char old_name[TOKEN_LENGTH];
|
|
strcpy_s(old_name, Sexp_variables[sexp_var_index].variable_name);
|
|
|
|
// set type
|
|
int type;
|
|
if (dlg.m_type_number) {
|
|
type = SEXP_VARIABLE_NUMBER;
|
|
} else {
|
|
type = SEXP_VARIABLE_STRING;
|
|
}
|
|
|
|
if ( dlg.m_type_network_variable ) {
|
|
type |= SEXP_VARIABLE_NETWORK;
|
|
}
|
|
|
|
if ( dlg.m_type_campaign_persistent ) {
|
|
type |= SEXP_VARIABLE_CAMPAIGN_PERSISTENT;
|
|
} else if ( dlg.m_type_player_persistent ) {
|
|
type |= SEXP_VARIABLE_PLAYER_PERSISTENT;
|
|
}
|
|
|
|
// update sexp_variable
|
|
sexp_fred_modify_variable(dlg.m_default_value, dlg.m_cur_variable_name, sexp_var_index, type);
|
|
|
|
// modify sexp_tree
|
|
modify_sexp_tree_variable(old_name, sexp_var_index);
|
|
|
|
// Don't sort until after modify, since modify uses index
|
|
if (dlg.m_modified_name) {
|
|
sexp_variable_sort();
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
// no change
|
|
return 1;
|
|
}
|
|
|
|
|
|
// check if REPLACE_VARIABLE_MENU
|
|
if ( (id >= ID_VARIABLE_MENU) && (id < ID_VARIABLE_MENU + 511)) {
|
|
|
|
Assert(item_index >= 0);
|
|
|
|
// get index into list of type valid variables
|
|
int var_idx = id - ID_VARIABLE_MENU;
|
|
Assert( (var_idx >= 0) && (var_idx < MAX_SEXP_VARIABLES) );
|
|
|
|
int type = get_type(item_handle);
|
|
Assert( (type & SEXPT_NUMBER) || (type & SEXPT_STRING) );
|
|
|
|
// don't do type check for modify-variable
|
|
if (Modify_variable) {
|
|
if (Sexp_variables[var_idx].type & SEXP_VARIABLE_NUMBER) {
|
|
type = SEXPT_NUMBER;
|
|
} else if (Sexp_variables[var_idx].type & SEXP_VARIABLE_STRING) {
|
|
type = SEXPT_STRING;
|
|
} else {
|
|
Int3(); // unknown type
|
|
}
|
|
|
|
} else {
|
|
// verify type in tree is same as type in Sexp_variables array
|
|
if (type & SEXPT_NUMBER) {
|
|
Assert(Sexp_variables[var_idx].type & SEXP_VARIABLE_NUMBER);
|
|
}
|
|
|
|
if (type & SEXPT_STRING) {
|
|
Assert( (Sexp_variables[var_idx].type & SEXP_VARIABLE_STRING) );
|
|
}
|
|
}
|
|
|
|
// Replace data
|
|
replace_variable_data(var_idx, (type | SEXPT_VARIABLE));
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
if ((id >= ID_ADD_MENU) && (id < ID_ADD_MENU + 511)) {
|
|
Assert(item_index >= 0);
|
|
op = get_operator_index(tree_nodes[item_index].text);
|
|
Assert(op >= 0);
|
|
|
|
type = query_operator_argument_type(op, Add_count);
|
|
list = get_listing_opf(type, item_index, Add_count);
|
|
Assert(list);
|
|
|
|
id -= ID_ADD_MENU;
|
|
ptr = list;
|
|
while (id) {
|
|
id--;
|
|
ptr = ptr->next;
|
|
Assert(ptr);
|
|
}
|
|
|
|
Assert((SEXPT_TYPE(ptr->type) != SEXPT_OPERATOR) && (ptr->op < 0));
|
|
expand_operator(item_index);
|
|
add_data(ptr->text, ptr->type);
|
|
list->destroy();
|
|
return 1;
|
|
}
|
|
|
|
if ((id >= ID_REPLACE_MENU) && (id < ID_REPLACE_MENU + 511)) {
|
|
Assert(item_index >= 0);
|
|
Assert(tree_nodes[item_index].parent >= 0);
|
|
op = get_operator_index(tree_nodes[tree_nodes[item_index].parent].text);
|
|
Assert(op >= 0);
|
|
|
|
type = query_operator_argument_type(op, Replace_count); // check argument type at this position
|
|
list = get_listing_opf(type, tree_nodes[item_index].parent, Replace_count);
|
|
Assert(list);
|
|
|
|
id -= ID_REPLACE_MENU;
|
|
ptr = list;
|
|
while (id) {
|
|
id--;
|
|
ptr = ptr->next;
|
|
Assert(ptr);
|
|
}
|
|
|
|
Assert((SEXPT_TYPE(ptr->type) != SEXPT_OPERATOR) && (ptr->op < 0));
|
|
expand_operator(item_index);
|
|
replace_data(ptr->text, ptr->type);
|
|
list->destroy();
|
|
return 1;
|
|
}
|
|
|
|
for (op=0; op<Num_operators; op++) {
|
|
if (id == Operators[op].value) {
|
|
add_or_replace_operator(op);
|
|
return 1;
|
|
}
|
|
|
|
if (id == (Operators[op].value | OP_REPLACE_FLAG)) {
|
|
add_or_replace_operator(op, 1);
|
|
expand_branch(item_handle);
|
|
return 1;
|
|
}
|
|
|
|
if (id == (Operators[op].value | OP_INSERT_FLAG)) {
|
|
int flags;
|
|
|
|
z = tree_nodes[item_index].parent;
|
|
flags = tree_nodes[item_index].flags;
|
|
node = allocate_node(z, item_index);
|
|
set_node(node, (SEXPT_OPERATOR | SEXPT_VALID), Operators[op].text);
|
|
tree_nodes[node].flags = flags;
|
|
if (z >= 0)
|
|
h = tree_nodes[z].handle;
|
|
|
|
else {
|
|
h = GetParentItem(tree_nodes[item_index].handle);
|
|
if (m_mode == MODE_GOALS) {
|
|
Assert(Goal_editor_dlg);
|
|
Goal_editor_dlg->insert_handler(item_index, node);
|
|
SetItemData(h, node);
|
|
|
|
} else if (m_mode == MODE_EVENTS) {
|
|
Assert(Event_editor_dlg);
|
|
Event_editor_dlg->insert_handler(item_index, node);
|
|
SetItemData(h, node);
|
|
|
|
} else if (m_mode == MODE_CAMPAIGN) {
|
|
Campaign_tree_formp->insert_handler(item_index, node);
|
|
SetItemData(h, node);
|
|
|
|
} else {
|
|
h = TVI_ROOT;
|
|
root_item = node;
|
|
}
|
|
}
|
|
|
|
item_handle = tree_nodes[node].handle = insert(Operators[op].text, BITMAP_OPERATOR, BITMAP_OPERATOR, h, tree_nodes[item_index].handle);
|
|
move_branch(item_index, node);
|
|
|
|
item_index = node;
|
|
for (i=1; i<Operators[op].min; i++)
|
|
add_default_operator(op, i);
|
|
|
|
Expand(item_handle, TVE_EXPAND);
|
|
*modified = 1;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
switch (id) {
|
|
case ID_EDIT_COPY:
|
|
// If a clipboard already exist, unmark it as persistent and free old clipboard
|
|
if (Sexp_clipboard != -1) {
|
|
sexp_unmark_persistent(Sexp_clipboard);
|
|
free_sexp2(Sexp_clipboard);
|
|
}
|
|
|
|
// Allocate new clipboard and mark persistent
|
|
Sexp_clipboard = save_branch(item_index, 1);
|
|
sexp_mark_persistent(Sexp_clipboard);
|
|
return 1;
|
|
|
|
case ID_EDIT_PASTE:
|
|
// the following assumptions are made..
|
|
Assert((Sexp_clipboard > -1) && (Sexp_nodes[Sexp_clipboard].type != SEXP_NOT_USED));
|
|
Assert(Sexp_nodes[Sexp_clipboard].subtype != SEXP_ATOM_LIST);
|
|
|
|
if (Sexp_nodes[Sexp_clipboard].subtype == SEXP_ATOM_OPERATOR) {
|
|
expand_operator(item_index);
|
|
replace_operator(CTEXT(Sexp_clipboard));
|
|
if (Sexp_nodes[Sexp_clipboard].rest != -1) {
|
|
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);
|
|
i = tree_nodes[i].next;
|
|
}
|
|
}
|
|
|
|
} else if (Sexp_nodes[Sexp_clipboard].subtype == SEXP_ATOM_NUMBER) {
|
|
Assert(Sexp_nodes[Sexp_clipboard].rest == -1);
|
|
if (Sexp_nodes[Sexp_clipboard].type & SEXP_FLAG_VARIABLE) {
|
|
int var_idx = get_index_sexp_variable_name(Sexp_nodes[Sexp_clipboard].text);
|
|
Assert(var_idx > -1);
|
|
replace_variable_data(var_idx, (SEXPT_VARIABLE | SEXPT_NUMBER | SEXPT_VALID));
|
|
}
|
|
else {
|
|
expand_operator(item_index);
|
|
replace_data(CTEXT(Sexp_clipboard), (SEXPT_NUMBER | SEXPT_VALID));
|
|
}
|
|
|
|
} else if (Sexp_nodes[Sexp_clipboard].subtype == SEXP_ATOM_STRING) {
|
|
Assert(Sexp_nodes[Sexp_clipboard].rest == -1);
|
|
if (Sexp_nodes[Sexp_clipboard].type & SEXP_FLAG_VARIABLE) {
|
|
int var_idx = get_index_sexp_variable_name(Sexp_nodes[Sexp_clipboard].text);
|
|
Assert(var_idx > -1);
|
|
replace_variable_data(var_idx, (SEXPT_VARIABLE | SEXPT_STRING | SEXPT_VALID));
|
|
}
|
|
else {
|
|
expand_operator(item_index);
|
|
replace_data(CTEXT(Sexp_clipboard), (SEXPT_STRING | SEXPT_VALID));
|
|
}
|
|
|
|
} else
|
|
Assert(0); // unknown and/or invalid sexp type
|
|
|
|
expand_branch(item_handle);
|
|
|
|
return 1;
|
|
|
|
case ID_EDIT_PASTE_SPECIAL: // add paste, instead of replace.
|
|
// the following assumptions are made..
|
|
Assert((Sexp_clipboard > -1) && (Sexp_nodes[Sexp_clipboard].type != SEXP_NOT_USED));
|
|
Assert(Sexp_nodes[Sexp_clipboard].subtype != SEXP_ATOM_LIST);
|
|
|
|
if (Sexp_nodes[Sexp_clipboard].subtype == SEXP_ATOM_OPERATOR) {
|
|
expand_operator(item_index);
|
|
add_operator(CTEXT(Sexp_clipboard));
|
|
if (Sexp_nodes[Sexp_clipboard].rest != -1) {
|
|
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);
|
|
i = tree_nodes[i].next;
|
|
}
|
|
}
|
|
|
|
} else if (Sexp_nodes[Sexp_clipboard].subtype == SEXP_ATOM_NUMBER) {
|
|
Assert(Sexp_nodes[Sexp_clipboard].rest == -1);
|
|
expand_operator(item_index);
|
|
add_data(CTEXT(Sexp_clipboard), (SEXPT_NUMBER | SEXPT_VALID));
|
|
|
|
} else if (Sexp_nodes[Sexp_clipboard].subtype == SEXP_ATOM_STRING) {
|
|
Assert(Sexp_nodes[Sexp_clipboard].rest == -1);
|
|
expand_operator(item_index);
|
|
add_data(CTEXT(Sexp_clipboard), (SEXPT_STRING | SEXPT_VALID));
|
|
|
|
} else
|
|
Assert(0); // unknown and/or invalid sexp type
|
|
|
|
expand_branch(item_handle);
|
|
|
|
return 1;
|
|
|
|
/* case ID_SPLIT_LINE:
|
|
if ((tree_nodes[item_index].flags & OPERAND) && (tree_nodes[item_index].flags & EDITABLE)) // expandable?
|
|
expand_operator(item_index);
|
|
else
|
|
merge_operator(item_index);
|
|
|
|
return 1;*/
|
|
|
|
case ID_EXPAND_ALL:
|
|
expand_branch(item_handle);
|
|
return 1;
|
|
|
|
case ID_EDIT_TEXT:
|
|
if (edit_label(item_handle)) {
|
|
*modified = 1;
|
|
EditLabel(item_handle);
|
|
}
|
|
|
|
return 1;
|
|
|
|
case ID_REPLACE_NUMBER:
|
|
expand_operator(item_index);
|
|
replace_data("number", (SEXPT_NUMBER | SEXPT_VALID));
|
|
EditLabel(tree_nodes[item_index].handle);
|
|
return 1;
|
|
|
|
case ID_REPLACE_STRING:
|
|
expand_operator(item_index);
|
|
replace_data("string", (SEXPT_STRING | SEXPT_VALID));
|
|
EditLabel(tree_nodes[item_index].handle);
|
|
return 1;
|
|
|
|
case ID_ADD_STRING: {
|
|
int theNode;
|
|
|
|
theNode = add_data("string", (SEXPT_STRING | SEXPT_VALID));
|
|
EditLabel(tree_nodes[theNode].handle);
|
|
return 1;
|
|
}
|
|
|
|
case ID_ADD_NUMBER: {
|
|
int theNode;
|
|
|
|
theNode = add_data("number", (SEXPT_NUMBER | SEXPT_VALID));
|
|
EditLabel(tree_nodes[theNode].handle);
|
|
return 1;
|
|
}
|
|
|
|
case ID_EDIT_CUT:
|
|
if (Sexp_clipboard != -1) {
|
|
sexp_unmark_persistent(Sexp_clipboard);
|
|
free_sexp2(Sexp_clipboard);
|
|
}
|
|
|
|
Sexp_clipboard = save_branch(item_index, 1);
|
|
sexp_mark_persistent(Sexp_clipboard);
|
|
// fall through to ID_DELETE case.
|
|
|
|
case ID_DELETE: {
|
|
int parent, theNode;
|
|
HTREEITEM h_parent;
|
|
|
|
if ((m_mode & ST_ROOT_DELETABLE) && (item_index == -1)) {
|
|
item_index = GetItemData(item_handle);
|
|
if (m_mode == MODE_GOALS) {
|
|
Assert(Goal_editor_dlg);
|
|
theNode = Goal_editor_dlg->handler(ROOT_DELETED, item_index);
|
|
|
|
} else if (m_mode == MODE_EVENTS) {
|
|
Assert(Event_editor_dlg);
|
|
theNode = Event_editor_dlg->handler(ROOT_DELETED, item_index);
|
|
|
|
} else {
|
|
Assert(m_mode == MODE_CAMPAIGN);
|
|
theNode = Campaign_tree_formp->handler(ROOT_DELETED, item_index);
|
|
}
|
|
|
|
Assert(theNode >= 0);
|
|
free_node2(theNode);
|
|
DeleteItem(item_handle);
|
|
*modified = 1;
|
|
return 1;
|
|
}
|
|
|
|
Assert(item_index >= 0);
|
|
h_parent = GetParentItem(item_handle);
|
|
parent = tree_nodes[item_index].parent;
|
|
if ((parent == -1) && (m_mode == MODE_EVENTS))
|
|
Int3(); // no longer used, temporary to check if called still.
|
|
|
|
Assert(parent != -1 && tree_nodes[parent].handle == h_parent);
|
|
free_node(item_index);
|
|
DeleteItem(item_handle);
|
|
|
|
theNode = tree_nodes[parent].child;
|
|
/* if (node != -1 && tree_nodes[node].next == -1 && tree_nodes[node].child == -1) {
|
|
sprintf(buf, "%s %s", tree_nodes[parent].text, tree_nodes[node].text);
|
|
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);
|
|
}*/
|
|
|
|
*modified = 1;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return CTreeCtrl::OnCommand(wParam, lParam);
|
|
}
|
|
|
|
// adds to or replaces (based on passed in flag) the current operator
|
|
void sexp_tree::add_or_replace_operator(int op, int replace_flag)
|
|
{
|
|
int i, op_index, op2;
|
|
|
|
op_index = item_index;
|
|
if (replace_flag) {
|
|
if (tree_nodes[item_index].flags & OPERAND) { // are both operators?
|
|
op2 = get_operator_index(tree_nodes[item_index].text);
|
|
Assert(op2 >= 0);
|
|
i = count_args(tree_nodes[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)) // does each arg match expected type?
|
|
break;
|
|
|
|
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);
|
|
SetItemText(tree_nodes[item_index].handle, Operators[op].text);
|
|
tree_nodes[item_index].flags = OPERAND;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
replace_operator(Operators[op].text);
|
|
|
|
} else
|
|
add_operator(Operators[op].text);
|
|
|
|
// fill in all the required (minimum) arguments with default values
|
|
for (i=0; i<Operators[op].min; i++)
|
|
add_default_operator(op, i);
|
|
|
|
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<Num_operators; 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(char *str, int t)
|
|
{
|
|
op = -1;
|
|
text = str;
|
|
type = t;
|
|
}
|
|
|
|
// initialize node, type data, allocating memory for the text
|
|
// Defaults: t = SEXPT_STRING
|
|
//
|
|
void sexp_list_item::set_data_dup(char *str, int t)
|
|
{
|
|
op = -1;
|
|
text = strdup(str);
|
|
flags |= SEXP_ITEM_F_DUP;
|
|
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(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 a node to end of list, allocating memory for the text
|
|
// Defaults: t = SEXPT_STRING
|
|
//
|
|
void sexp_list_item::add_data_dup(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(strdup(str), t);
|
|
item->flags |= SEXP_ITEM_F_DUP;
|
|
}
|
|
|
|
// 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;
|
|
if (ptr->flags & SEXP_ITEM_F_DUP)
|
|
free(ptr->text);
|
|
|
|
delete ptr;
|
|
ptr = ptr2;
|
|
}
|
|
}
|
|
|
|
int sexp_tree::add_default_operator(int op, int argnum)
|
|
{
|
|
char buf[256];
|
|
int index;
|
|
sexp_list_item item;
|
|
HTREEITEM h;
|
|
|
|
h = item_handle;
|
|
index = item_index;
|
|
item.text = buf;
|
|
if (get_default_value(&item, op, argnum))
|
|
return -1;
|
|
|
|
if (item.type & SEXPT_OPERATOR) {
|
|
Assert((item.op >= 0) && (item.op < Num_operators));
|
|
add_or_replace_operator(item.op);
|
|
item_index = index;
|
|
item_handle = h;
|
|
|
|
} else {
|
|
// special case for modify-variable (data added 1st arg is variable)
|
|
//if ( !stricmp(Operators[op].text, "modify-variable") ) {
|
|
if ( query_operator_argument_type(op, argnum) == OPF_VARIABLE_NAME)
|
|
{
|
|
if ((argnum == 0 && !stricmp(Operators[op].text, "modify-variable")) ||
|
|
(argnum == 8 && !stricmp(Operators[op].text, "add-background-bitmap")) ||
|
|
(argnum == 5 && !stricmp(Operators[op].text, "add-sun-bitmap")))
|
|
{
|
|
|
|
int sexp_var_index = get_index_sexp_variable_name(item.text);
|
|
Assert(sexp_var_index != -1);
|
|
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, Sexp_variables[sexp_var_index].text);
|
|
add_variable_data(node_text, type);
|
|
} else {
|
|
// the the variable name
|
|
char buf2[256];
|
|
Assert(argnum == 1);
|
|
sexp_list_item temp_item;
|
|
temp_item.text = buf2;
|
|
get_default_value(&temp_item, op, 0);
|
|
int sexp_var_index = get_index_sexp_variable_name(temp_item.text);
|
|
Assert(sexp_var_index != -1);
|
|
|
|
// from name get type
|
|
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, type);
|
|
}
|
|
} else {
|
|
add_data(item.text, item.type);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int sexp_tree::get_default_value(sexp_list_item *item, int op, int i)
|
|
{
|
|
char *str = NULL;
|
|
int type, index;
|
|
sexp_list_item *list;
|
|
HTREEITEM h;
|
|
|
|
h = item_handle;
|
|
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:
|
|
item->set_data("<any data>");
|
|
return 0;
|
|
|
|
case OPF_NUMBER:
|
|
case OPF_POSITIVE:
|
|
case OPF_AMBIGUOUS:
|
|
// if the top level operators 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)) && (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) )
|
|
{
|
|
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 10:
|
|
temp = SND_SHIP_EXPLODE_1;
|
|
break;
|
|
case 11:
|
|
temp = (int)EMP_DEFAULT_INTENSITY;
|
|
break;
|
|
case 12:
|
|
temp = (int)EMP_DEFAULT_TIME;
|
|
break;
|
|
default:
|
|
temp = 0;
|
|
break;
|
|
}
|
|
|
|
// Goober5000 - set_data_dup is required if we're passing a variable
|
|
sprintf(sexp_str_token, "%d", temp);
|
|
item->set_data_dup(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;
|
|
case 8:
|
|
temp = SND_CAPITAL_WARP_IN;
|
|
break;
|
|
case 9:
|
|
temp = SND_CAPITAL_WARP_OUT;
|
|
break;
|
|
default:
|
|
temp = 0;
|
|
break;
|
|
}
|
|
|
|
// Goober5000 - set_data_dup is required if we're passing a variable
|
|
sprintf(sexp_str_token, "%d", temp);
|
|
item->set_data_dup(sexp_str_token, (SEXPT_NUMBER | SEXPT_VALID));
|
|
}
|
|
else if ((Operators[op].value == OP_ADD_BACKGROUND_BITMAP))
|
|
{
|
|
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_dup(sexp_str_token, (SEXPT_NUMBER | SEXPT_VALID));
|
|
}
|
|
else if ((Operators[op].value == OP_ADD_SUN_BITMAP))
|
|
{
|
|
int temp = 0;
|
|
char sexp_str_token[TOKEN_LENGTH];
|
|
|
|
if (i==4) temp = 100;
|
|
|
|
sprintf(sexp_str_token, "%d", temp);
|
|
item->set_data_dup(sexp_str_token, (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("<any data>", (SEXPT_STRING | SEXPT_VALID));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
item->set_data("0", (SEXPT_NUMBER | 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 && !strcmp(list->text, SEXP_ARGUMENT_STRING))
|
|
{
|
|
sexp_list_item *first_ptr;
|
|
|
|
first_ptr = list;
|
|
list = list->next;
|
|
|
|
if (first_ptr->flags & SEXP_ITEM_F_DUP)
|
|
free(first_ptr->text);
|
|
|
|
delete first_ptr;
|
|
}
|
|
|
|
if (list) {
|
|
char *ptr;
|
|
|
|
ptr = item->text;
|
|
*item = *list;
|
|
item->text = ptr;
|
|
strcpy(item->text, list->text);
|
|
|
|
list->destroy();
|
|
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_WING:
|
|
case OPF_SHIP_POINT:
|
|
case OPF_SHIP_WING_POINT:
|
|
str = "<name of ship here>";
|
|
break;
|
|
|
|
case OPF_ORDER_RECIPIENT:
|
|
str = "<all fighters>";
|
|
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 = "<name of wing here>";
|
|
break;
|
|
|
|
case OPF_DOCKER_POINT:
|
|
str = "<docker point>";
|
|
break;
|
|
|
|
case OPF_DOCKEE_POINT:
|
|
str = "<dockee point>";
|
|
break;
|
|
|
|
case OPF_SUBSYSTEM:
|
|
case OPF_AWACS_SUBSYSTEM:
|
|
case OPF_ROTATING_SUBSYSTEM:
|
|
case OPF_SUBSYS_OR_GENERIC:
|
|
str = "<name of subsystem>";
|
|
break;
|
|
|
|
case OPF_POINT:
|
|
str = "<waypoint>";
|
|
break;
|
|
|
|
case OPF_MESSAGE:
|
|
str = "<Message>";
|
|
break;
|
|
|
|
case OPF_WHO_FROM:
|
|
//str = "<any allied>";
|
|
str = "<any wingman>";
|
|
break;
|
|
|
|
case OPF_WAYPOINT_PATH:
|
|
str = "<waypoint path>";
|
|
break;
|
|
|
|
case OPF_MISSION_NAME:
|
|
str = "<mission name>";
|
|
break;
|
|
|
|
case OPF_GOAL_NAME:
|
|
str = "<goal name>";
|
|
break;
|
|
|
|
case OPF_SHIP_TYPE:
|
|
str = "<ship type here>";
|
|
break;
|
|
|
|
case OPF_EVENT_NAME:
|
|
str = "<event name>";
|
|
break;
|
|
|
|
case OPF_HUGE_WEAPON:
|
|
str = "<huge weapon type>";
|
|
break;
|
|
|
|
case OPF_JUMP_NODE_NAME:
|
|
str = "<Jump node name>";
|
|
break;
|
|
|
|
case OPF_NAV_POINT:
|
|
str = "<Nav 1>";
|
|
break;
|
|
|
|
case OPF_ANYTHING:
|
|
str = "<any data>";
|
|
break;
|
|
|
|
case OPF_PERSONA:
|
|
str = "<persona name>";
|
|
break;
|
|
|
|
default:
|
|
str = "<new default required!>";
|
|
break;
|
|
}
|
|
|
|
item->set_data(str, (SEXPT_STRING | SEXPT_VALID));
|
|
return 0;
|
|
}
|
|
|
|
int sexp_tree::query_default_argument_available(int op)
|
|
{
|
|
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 sexp_tree::query_default_argument_available(int op, int i)
|
|
{
|
|
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_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_HUD_GAUGE_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_SKYBOX_MODEL_NAME:
|
|
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:
|
|
return 1;
|
|
|
|
case OPF_SHIP:
|
|
case OPF_SHIP_WING:
|
|
case OPF_SHIP_POINT:
|
|
case OPF_SHIP_WING_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_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:
|
|
if (Num_personas)
|
|
return 1;
|
|
return 0;
|
|
|
|
|
|
case OPF_POINT:
|
|
case OPF_WAYPOINT_PATH:
|
|
if (Num_waypoint_lists)
|
|
return 1;
|
|
return 0;
|
|
|
|
case OPF_MISSION_NAME:
|
|
if (m_mode != MODE_CAMPAIGN) {
|
|
if (!(*Mission_filename))
|
|
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;
|
|
|
|
// need to be sure that previous-goal functions are available. (i.e. we are providing a default argument for them)
|
|
else if ((value == OP_PREVIOUS_GOAL_TRUE) || (value == OP_PREVIOUS_GOAL_FALSE) || (value == OP_PREVIOUS_GOAL_INCOMPLETE) || Num_goals)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
case OPF_EVENT_NAME: {
|
|
int value;
|
|
|
|
value = Operators[op].value;
|
|
if (m_mode == MODE_CAMPAIGN)
|
|
return 1;
|
|
|
|
// need to be sure that previous-event functions are available. (i.e. we are providing a default argument for them)
|
|
else if ((value == OP_PREVIOUS_EVENT_TRUE) || (value == OP_PREVIOUS_EVENT_FALSE) || (value == OP_PREVIOUS_EVENT_INCOMPLETE) || Num_mission_events)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
case OPF_MESSAGE:
|
|
if (m_mode == MODE_EVENTS) {
|
|
Assert(Event_editor_dlg);
|
|
if (Event_editor_dlg->current_message_name(0))
|
|
return 1;
|
|
|
|
} else {
|
|
if (Num_messages > Num_builtin_messages)
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
|
|
case OPF_VARIABLE_NAME:
|
|
if (sexp_variable_count() > 0) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
case OPF_NAV_POINT:
|
|
return 1;
|
|
|
|
case OPF_SSM_CLASS:
|
|
if (Ssm_info_count > 0)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
|
|
default:
|
|
Int3();
|
|
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// expand a combined line (one with an operator and its one argument on the same line) into
|
|
// 2 lines.
|
|
void sexp_tree::expand_operator(int node)
|
|
{
|
|
int data;
|
|
HTREEITEM h;
|
|
|
|
if (tree_nodes[node].flags & COMBINED) {
|
|
node = tree_nodes[node].parent;
|
|
Assert((tree_nodes[node].flags & OPERAND) && (tree_nodes[node].flags & EDITABLE));
|
|
}
|
|
|
|
if ((tree_nodes[node].flags & OPERAND) && (tree_nodes[node].flags & EDITABLE)) { // expandable?
|
|
Assert(tree_nodes[node].type & SEXPT_OPERATOR);
|
|
h = tree_nodes[node].handle;
|
|
data = tree_nodes[node].child;
|
|
Assert(data != -1 && tree_nodes[data].next == -1 && tree_nodes[data].child == -1);
|
|
|
|
SetItem(h, TVIF_TEXT, tree_nodes[node].text, 0, 0, 0, 0, 0);
|
|
tree_nodes[node].flags = OPERAND;
|
|
int bmap = get_data_image(data);
|
|
tree_nodes[data].handle = insert(tree_nodes[data].text, bmap, bmap, h);
|
|
tree_nodes[data].flags = EDITABLE;
|
|
Expand(h, TVE_EXPAND);
|
|
}
|
|
}
|
|
|
|
// expand a CTreeCtrl branch and all of its children
|
|
void sexp_tree::expand_branch(HTREEITEM h)
|
|
{
|
|
Expand(h, TVE_EXPAND);
|
|
h = GetChildItem(h);
|
|
while (h) {
|
|
expand_branch(h);
|
|
h = GetNextSiblingItem(h);
|
|
}
|
|
}
|
|
|
|
void sexp_tree::merge_operator(int node)
|
|
{
|
|
/* char buf[256];
|
|
int child;
|
|
|
|
if (tree_nodes[node].flags == EDITABLE) // data
|
|
node = tree_nodes[node].parent;
|
|
|
|
if (node != -1) {
|
|
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);
|
|
tree_nodes[node].flags = OPERAND | EDITABLE;
|
|
tree_nodes[child].flags = COMBINED;
|
|
DeleteItem(tree_nodes[child].handle);
|
|
tree_nodes[child].handle = NULL;
|
|
return;
|
|
}
|
|
}*/
|
|
}
|
|
|
|
// add a data node under operator pointed to by item_index
|
|
int sexp_tree::add_data(char *data, int type)
|
|
{
|
|
int node;
|
|
|
|
expand_operator(item_index);
|
|
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].flags = EDITABLE;
|
|
*modified = 1;
|
|
return node;
|
|
}
|
|
|
|
// add a (variable) data node under operator pointed to by item_index
|
|
int sexp_tree::add_variable_data(char *data, int type)
|
|
{
|
|
int node;
|
|
|
|
Assert(type & SEXPT_VARIABLE);
|
|
|
|
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].flags = NOT_EDITABLE;
|
|
*modified = 1;
|
|
return node;
|
|
}
|
|
|
|
// add an operator under operator pointed to by item_index. Updates item_index to point
|
|
// to this new operator.
|
|
void sexp_tree::add_operator(char *op, HTREEITEM h)
|
|
{
|
|
int node;
|
|
|
|
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);
|
|
|
|
} 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].flags = OPERAND;
|
|
item_index = node;
|
|
*modified = 1;
|
|
}
|
|
|
|
// add an operator with one argument under operator pointed to by item_index. This function
|
|
// exists because the one arg case is a special case. The operator and argument is
|
|
// displayed on the same line.
|
|
/*void sexp_tree::add_one_arg_operator(char *op, char *data, int type)
|
|
{
|
|
char str[80];
|
|
int node1, node2;
|
|
|
|
expand_operator(item_index);
|
|
node1 = allocate_node(item_index);
|
|
node2 = allocate_node(node1);
|
|
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].flags = OPERAND | EDITABLE;
|
|
tree_nodes[node2].flags = COMBINED;
|
|
*modified = 1;
|
|
}*/
|
|
|
|
/*
|
|
int sexp_tree::verify_tree(int *bypass)
|
|
{
|
|
return verify_tree(0, bypass);
|
|
}
|
|
|
|
// check the sexp tree for errors. Return -1 if error, or 0 if no errors. If an error
|
|
// is found, item_index = node of error.
|
|
int sexp_tree::verify_tree(int node, int *bypass)
|
|
{
|
|
int i, type, count, op, type2, op2, argnum = 0;
|
|
|
|
if (!total_nodes)
|
|
return 0; // nothing to check
|
|
|
|
Assert(node >= 0 && node < tree_nodes.size());
|
|
Assert(tree_nodes[node].type == SEXPT_OPERATOR);
|
|
|
|
op = get_operator_index(tree_nodes[node].text);
|
|
if (op == -1)
|
|
return node_error(node, "Unknown operator", bypass);
|
|
|
|
count = count_args(tree_nodes[node].child);
|
|
if (count < Operators[op].min)
|
|
return node_error(node, "Too few arguments for operator", bypass);
|
|
if (count > Operators[op].max)
|
|
return node_error(node, "Too many arguments for operator", bypass);
|
|
|
|
node = tree_nodes[node].child; // get first argument
|
|
while (node != -1) {
|
|
type = query_operator_argument_type(op, argnum);
|
|
Assert(tree_nodes[node].type & SEXPT_VALID);
|
|
if (tree_nodes[node].type == SEXPT_OPERATOR) {
|
|
if (verify_tree(node) == -1)
|
|
return -1;
|
|
|
|
op2 = get_operator_index(tree_nodes[node].text); // no error checking, because it was done in the call above.
|
|
type2 = query_operator_return_type(op2);
|
|
|
|
} else if (tree_nodes[node].type == SEXPT_NUMBER) {
|
|
char *ptr;
|
|
|
|
type2 = OPR_NUMBER;
|
|
ptr = tree_nodes[node].text;
|
|
while (*ptr)
|
|
if (!isdigit(*ptr++))
|
|
return node_error(node, "Number is invalid", bypass);
|
|
|
|
} else if (tree_nodes[node].type == SEXPT_STRING) {
|
|
type2 = SEXP_ATOM_STRING;
|
|
|
|
} else
|
|
Assert(0); // unknown and invalid sexp node type.
|
|
|
|
switch (type) {
|
|
case OPF_NUMBER:
|
|
if (type2 != OPR_NUMBER)
|
|
return node_error(node, "Number or number return type expected here", bypass);
|
|
|
|
break;
|
|
|
|
case OPF_SHIP:
|
|
if (type2 == SEXP_ATOM_STRING)
|
|
if (ship_name_lookup(tree_nodes[node].text, 1) == -1)
|
|
type2 = 0;
|
|
|
|
if (type2 != SEXP_ATOM_STRING)
|
|
return node_error(node, "Ship name expected here", bypass);
|
|
|
|
break;
|
|
|
|
case OPF_WING:
|
|
if (type2 == SEXP_ATOM_STRING)
|
|
if (wing_name_lookup(tree_nodes[node].text) == -1)
|
|
type2 = 0;
|
|
|
|
if (type2 != SEXP_ATOM_STRING)
|
|
return node_error(node, "Wing name expected here", bypass);
|
|
|
|
break;
|
|
|
|
case OPF_SHIP_WING:
|
|
if (type2 == SEXP_ATOM_STRING)
|
|
if (ship_name_lookup(tree_nodes[node].text, 1) == -1)
|
|
if (wing_name_lookup(tree_nodes[node].text) == -1)
|
|
type2 = 0;
|
|
|
|
if (type2 != SEXP_ATOM_STRING)
|
|
return node_error(node, "Ship or wing name expected here", bypass);
|
|
|
|
break;
|
|
|
|
case OPF_BOOL:
|
|
if (type2 != OPR_BOOL)
|
|
return node_error(node, "Boolean return type expected here", bypass);
|
|
|
|
break;
|
|
|
|
case OPF_NULL:
|
|
if (type2 != OPR_NULL)
|
|
return node_error(node, "No return type operator expected here", bypass);
|
|
|
|
break;
|
|
|
|
case OPF_POINT:
|
|
if (type2 != SEXP_ATOM_STRING || verify_vector(tree_nodes[node].text))
|
|
return node_error(node, "3d coordinate expected here", bypass);
|
|
|
|
break;
|
|
|
|
case OPF_SUBSYSTEM:
|
|
case OPF_AWACS_SUBSYSTEM:
|
|
case OPF_ROTATING_SUBSYSTEM:
|
|
if (type2 == SEXP_ATOM_STRING)
|
|
if (ai_get_subsystem_type(tree_nodes[node].text) == SUBSYSTEM_UNKNOWN)
|
|
type2 = 0;
|
|
|
|
if (type2 != SEXP_ATOM_STRING)
|
|
return node_error(node, "Subsystem name expected here", bypass);
|
|
|
|
break;
|
|
|
|
case OPF_IFF:
|
|
if (type2 == SEXP_ATOM_STRING) {
|
|
for (i=0; i<Num_team_names; i++)
|
|
if (!stricmp(Team_names[i], tree_nodes[node].text))
|
|
break;
|
|
}
|
|
|
|
if (i == Num_team_names)
|
|
return node_error(node, "Iff team type expected here", bypass);
|
|
|
|
break;
|
|
|
|
case OPF_AI_GOAL:
|
|
if (type2 != OPR_AI_GOAL)
|
|
return node_error(node, "Ai goal return type expected here", bypass);
|
|
|
|
break;
|
|
|
|
case OPF_FLEXIBLE_ARGUMENT:
|
|
if (type2 != OPR_FLEXIBLE_ARGUMENT)
|
|
return node_error(node, "Flexible argument return type expected here", bypass);
|
|
|
|
break;
|
|
|
|
case OPF_ANYTHING:
|
|
break;
|
|
|
|
case OPF_DOCKER_POINT:
|
|
if (type2 != SEXP_ATOM_STRING)
|
|
return node_error(node, "Docker docking point name expected here", bypass);
|
|
|
|
break;
|
|
|
|
case OPF_DOCKEE_POINT:
|
|
if (type2 != SEXP_ATOM_STRING)
|
|
return node_error(node, "Dockee docking point name expected here", bypass);
|
|
|
|
break;
|
|
}
|
|
|
|
node = tree_nodes[node].next;
|
|
argnum++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
*/
|
|
|
|
// display an error message and position to point of error (a node)
|
|
int sexp_tree::node_error(int node, char *msg, int *bypass)
|
|
{
|
|
char text[512];
|
|
|
|
if (bypass)
|
|
*bypass = 1;
|
|
|
|
item_index = node;
|
|
item_handle = tree_nodes[node].handle;
|
|
if (tree_nodes[node].flags & COMBINED)
|
|
item_handle = tree_nodes[tree_nodes[node].parent].handle;
|
|
|
|
ensure_visible(node);
|
|
SelectItem(item_handle);
|
|
sprintf(text, "%s\n\nContinue checking for more errors?", msg);
|
|
if (MessageBox(text, "Sexp error", MB_YESNO | MB_ICONEXCLAMATION) == IDNO)
|
|
return -1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
void sexp_tree::hilite_item(int node)
|
|
{
|
|
ensure_visible(node);
|
|
SelectItem(tree_nodes[node].handle);
|
|
}
|
|
|
|
// because the MFC function EnsureVisible() doesn't do what it says it does, I wrote this.
|
|
void sexp_tree::ensure_visible(int node)
|
|
{
|
|
Assert(node != -1);
|
|
if (tree_nodes[node].parent != -1)
|
|
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
|
|
}
|
|
|
|
void sexp_tree::link_modified(int *ptr)
|
|
{
|
|
modified = ptr;
|
|
}
|
|
|
|
void get_variable_default_text_from_variable_text(char *text, char *default_text)
|
|
{
|
|
int len;
|
|
char *start;
|
|
|
|
// find '('
|
|
start = strstr(text, "(");
|
|
Assert(start);
|
|
start++;
|
|
|
|
// get length and copy all but last char ")"
|
|
len = strlen(start);
|
|
strncpy(default_text, start, len-1);
|
|
|
|
// add null termination
|
|
default_text[len-1] = '\0';
|
|
}
|
|
|
|
void get_variable_name_from_sexp_tree_node_text(const char *text, char *var_name)
|
|
{
|
|
int length;
|
|
length = strcspn(text, "(");
|
|
|
|
strncpy(var_name, text, length);
|
|
var_name[length] = '\0';
|
|
}
|
|
|
|
int sexp_tree::get_modify_variable_type(int parent)
|
|
{
|
|
Assert(parent != -1);
|
|
int sexp_var_index = -1;
|
|
|
|
if ( !stricmp(tree_nodes[parent].text, "modify-variable") ) {
|
|
Assert(tree_nodes[parent].child != -1);
|
|
sexp_var_index = get_tree_name_to_sexp_variable_index(tree_nodes[tree_nodes[parent].child].text);
|
|
Assert(sexp_var_index != -1);
|
|
} else {
|
|
Int3(); // should not be called otherwise
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
|
|
void sexp_tree::verify_and_fix_arguments(int node)
|
|
{
|
|
int op, arg_num, type, tmp;
|
|
static int flag = 0;
|
|
sexp_list_item *list, *ptr;
|
|
bool is_variable_arg = false;
|
|
|
|
if (flag)
|
|
return;
|
|
|
|
flag++;
|
|
op = get_operator_index(tree_nodes[node].text);
|
|
if (op < 0)
|
|
return;
|
|
|
|
tmp = item_index;
|
|
item_index = node;
|
|
|
|
arg_num = 0;
|
|
item_index = tree_nodes[node].child;
|
|
while (item_index >= 0) {
|
|
// get listing of valid argument values for node item_index
|
|
type = query_operator_argument_type(op, arg_num);
|
|
// special case for modify-variable
|
|
if (type == OPF_AMBIGUOUS) {
|
|
is_variable_arg = true;
|
|
type = get_modify_variable_type(node);
|
|
}
|
|
if (query_restricted_opf_range(type)) {
|
|
list = get_listing_opf(type, node, arg_num);
|
|
if (!list && (arg_num >= Operators[op].min)) {
|
|
free_node(item_index, 1);
|
|
item_index = tmp;
|
|
flag--;
|
|
return;
|
|
}
|
|
|
|
if (list) {
|
|
// get a pointer to tree_nodes[item_index].text for normal value
|
|
// or default variable value if variable
|
|
char *text_ptr;
|
|
char default_variable_text[TOKEN_LENGTH];
|
|
if (tree_nodes[item_index].type & SEXPT_VARIABLE) {
|
|
// special case for SEXPs which can modify a variable
|
|
if ( (!stricmp(Operators[op].text, "modify-variable")) ||
|
|
((!stricmp(Operators[op].text, "add-background-bitmap")) && (arg_num == 8)) ||
|
|
((!stricmp(Operators[op].text, "add-sun-bitmap")) && (arg_num == 5)) ){
|
|
// make text_ptr to start - before '('
|
|
get_variable_name_from_sexp_tree_node_text(tree_nodes[item_index].text, default_variable_text);
|
|
text_ptr = default_variable_text;
|
|
} else {
|
|
// only the type needs checking for variables. It's up the to the FREDder to ensure the value is valid
|
|
get_variable_name_from_sexp_tree_node_text(tree_nodes[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) {
|
|
// on to the next argument
|
|
item_index = tree_nodes[item_index].next;
|
|
arg_num++;
|
|
continue;
|
|
}
|
|
else {
|
|
// shouldn't really be getting here unless someone has been hacking the mission in a text editor
|
|
get_variable_default_text_from_variable_text(tree_nodes[item_index].text, default_variable_text);
|
|
text_ptr = default_variable_text;
|
|
}
|
|
}
|
|
} else {
|
|
text_ptr = tree_nodes[item_index].text;
|
|
}
|
|
|
|
ptr = list;
|
|
while (ptr) {
|
|
|
|
if (ptr->text != NULL) {
|
|
// make sure text is not NULL
|
|
// check that proposed text is valid for operator
|
|
if ( !stricmp(ptr->text, text_ptr) )
|
|
break;
|
|
|
|
ptr = ptr->next;
|
|
} else {
|
|
// text is NULL, so set ptr to NULL to end loop
|
|
ptr = NULL;
|
|
}
|
|
}
|
|
|
|
if (!ptr) { // argument isn't in list of valid choices,
|
|
if (list->op >= 0) {
|
|
replace_operator(list->text);
|
|
} else {
|
|
replace_data(list->text, list->type);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
bool invalid = false;
|
|
if (type == OPF_AMBIGUOUS) {
|
|
if (SEXPT_TYPE(tree_nodes[item_index].type) == SEXPT_OPERATOR) {
|
|
invalid = true;
|
|
}
|
|
} else {
|
|
if (SEXPT_TYPE(tree_nodes[item_index].type) != SEXPT_OPERATOR) {
|
|
invalid = true;
|
|
}
|
|
}
|
|
|
|
if (invalid) {
|
|
replace_data("<Invalid>", (SEXPT_STRING | SEXPT_VALID));
|
|
}
|
|
}
|
|
|
|
if (tree_nodes[item_index].type & SEXPT_OPERATOR)
|
|
verify_and_fix_arguments(item_index);
|
|
|
|
}
|
|
|
|
//fix the node if it is the argument for modify-variable
|
|
if (is_variable_arg //&&
|
|
// !(tree_nodes[item_index].type & SEXPT_OPERATOR || tree_nodes[item_index].type & SEXPT_VARIABLE )
|
|
) {
|
|
switch (type) {
|
|
case OPF_AMBIGUOUS:
|
|
tree_nodes[item_index].type |= SEXPT_STRING;
|
|
tree_nodes[item_index].type &= ~SEXPT_NUMBER;
|
|
break;
|
|
|
|
case OPF_NUMBER:
|
|
tree_nodes[item_index].type |= SEXPT_NUMBER;
|
|
tree_nodes[item_index].type &= ~SEXPT_STRING;
|
|
break;
|
|
|
|
default:
|
|
Int3();
|
|
}
|
|
}
|
|
|
|
item_index = tree_nodes[item_index].next;
|
|
arg_num++;
|
|
}
|
|
|
|
item_index = tmp;
|
|
flag--;
|
|
}
|
|
|
|
void sexp_tree::replace_data(char *data, int type)
|
|
{
|
|
int node;
|
|
HTREEITEM h;
|
|
|
|
node = tree_nodes[item_index].child;
|
|
if (node != -1)
|
|
free_node2(node);
|
|
|
|
tree_nodes[item_index].child = -1;
|
|
h = tree_nodes[item_index].handle;
|
|
while (ItemHasChildren(h))
|
|
DeleteItem(GetChildItem(h));
|
|
|
|
set_node(item_index, type, data);
|
|
SetItemText(h, data);
|
|
int bmap = get_data_image(item_index);
|
|
SetItemImage(h, bmap, bmap);
|
|
tree_nodes[item_index].flags = EDITABLE;
|
|
|
|
// check remaining data beyond replaced data for validity (in case any of it is dependent on data just replaced)
|
|
verify_and_fix_arguments(tree_nodes[item_index].parent);
|
|
|
|
*modified = 1;
|
|
update_help(GetSelectedItem());
|
|
}
|
|
|
|
|
|
// Replaces data with sexp_variable type data
|
|
void sexp_tree::replace_variable_data(int var_idx, int type)
|
|
{
|
|
int node;
|
|
HTREEITEM h;
|
|
char buf[128];
|
|
|
|
Assert(type & SEXPT_VARIABLE);
|
|
|
|
node = tree_nodes[item_index].child;
|
|
if (node != -1)
|
|
free_node2(node);
|
|
|
|
tree_nodes[item_index].child = -1;
|
|
h = tree_nodes[item_index].handle;
|
|
while (ItemHasChildren(h)) {
|
|
DeleteItem(GetChildItem(h));
|
|
}
|
|
|
|
// Assemble name
|
|
sprintf(buf, "%s(%s)", Sexp_variables[var_idx].variable_name, Sexp_variables[var_idx].text);
|
|
|
|
set_node(item_index, type, buf);
|
|
SetItemText(h, buf);
|
|
SetItemImage(h, BITMAP_VARIABLE, BITMAP_VARIABLE);
|
|
tree_nodes[item_index].flags = NOT_EDITABLE;
|
|
|
|
// check remaining data beyond replaced data for validity (in case any of it is dependent on data just replaced)
|
|
verify_and_fix_arguments(tree_nodes[item_index].parent);
|
|
|
|
*modified = 1;
|
|
update_help(GetSelectedItem());
|
|
}
|
|
|
|
|
|
|
|
void sexp_tree::replace_operator(char *op)
|
|
{
|
|
int node;
|
|
HTREEITEM h;
|
|
|
|
node = tree_nodes[item_index].child;
|
|
if (node != -1)
|
|
free_node2(node);
|
|
|
|
tree_nodes[item_index].child = -1;
|
|
h = tree_nodes[item_index].handle;
|
|
while (ItemHasChildren(h))
|
|
DeleteItem(GetChildItem(h));
|
|
|
|
set_node(item_index, (SEXPT_OPERATOR | SEXPT_VALID), op);
|
|
SetItemText(h, op);
|
|
tree_nodes[item_index].flags = OPERAND;
|
|
*modified = 1;
|
|
update_help(GetSelectedItem());
|
|
|
|
// hack added at Allender's request. If changing ship in an ai-dock operator, re-default
|
|
// docking point.
|
|
}
|
|
|
|
/*void sexp_tree::replace_one_arg_operator(char *op, char *data, int type)
|
|
{
|
|
char str[80];
|
|
int node;
|
|
HTREEITEM h;
|
|
|
|
node = tree_nodes[item_index].child;
|
|
if (node != -1)
|
|
free_node2(node);
|
|
|
|
tree_nodes[item_index].child = -1;
|
|
h = tree_nodes[item_index].handle;
|
|
while (ItemHasChildren(h))
|
|
DeleteItem(GetChildItem(h));
|
|
|
|
node = allocate_node(item_index);
|
|
set_node(item_index, SEXPT_OPERATOR, op);
|
|
set_node(node, type, data);
|
|
sprintf(str, "%s %s", op, data);
|
|
SetItemText(h, str);
|
|
tree_nodes[item_index].flags = OPERAND | EDITABLE;
|
|
tree_nodes[node].flags = COMBINED;
|
|
*modified = 1;
|
|
update_help(GetSelectedItem());
|
|
}*/
|
|
|
|
// moves a whole sexp tree branch to a new position under 'parent' and after 'after'.
|
|
// The expansion state is preserved, and node handles are updated.
|
|
void sexp_tree::move_branch(int source, int parent)
|
|
{
|
|
int node;
|
|
|
|
// if no source, skip everything
|
|
if (source != -1) {
|
|
node = tree_nodes[source].parent;
|
|
if (node != -1) {
|
|
if (tree_nodes[node].child == source)
|
|
tree_nodes[node].child = tree_nodes[source].next;
|
|
else {
|
|
node = tree_nodes[node].child;
|
|
while (tree_nodes[node].next != source) {
|
|
node = tree_nodes[node].next;
|
|
Assert(node != -1);
|
|
}
|
|
|
|
tree_nodes[node].next = tree_nodes[source].next;
|
|
}
|
|
}
|
|
|
|
tree_nodes[source].parent = parent;
|
|
tree_nodes[source].next = -1;
|
|
if (parent) {
|
|
if (tree_nodes[parent].child == -1)
|
|
tree_nodes[parent].child = source;
|
|
else {
|
|
node = tree_nodes[parent].child;
|
|
while (tree_nodes[node].next != -1)
|
|
node = tree_nodes[node].next;
|
|
|
|
tree_nodes[node].next = source;
|
|
}
|
|
|
|
move_branch(tree_nodes[source].handle, tree_nodes[parent].handle);
|
|
|
|
} else
|
|
move_branch(tree_nodes[source].handle);
|
|
}
|
|
}
|
|
|
|
HTREEITEM sexp_tree::move_branch(HTREEITEM source, HTREEITEM parent, HTREEITEM after)
|
|
{
|
|
uint i;
|
|
int image1, image2;
|
|
HTREEITEM h = 0, child, next;
|
|
|
|
if (source) {
|
|
for (i=0; i<tree_nodes.size(); i++)
|
|
if (tree_nodes[i].handle == source)
|
|
break;
|
|
|
|
if (i < tree_nodes.size()) {
|
|
GetItemImage(source, image1, image2);
|
|
h = insert(GetItemText(source), image1, image2, parent, after);
|
|
tree_nodes[i].handle = h;
|
|
|
|
} else {
|
|
GetItemImage(source, image1, image2);
|
|
h = insert(GetItemText(source), image1, image2, parent, after);
|
|
}
|
|
|
|
SetItemData(h, GetItemData(source));
|
|
child = GetChildItem(source);
|
|
while (child) {
|
|
next = GetNextSiblingItem(child);
|
|
move_branch(child, h);
|
|
child = next;
|
|
}
|
|
|
|
if (GetItemState(source, TVIS_EXPANDED) & TVIS_EXPANDED)
|
|
Expand(h, TVE_EXPAND);
|
|
|
|
DeleteItem(source);
|
|
}
|
|
|
|
return h;
|
|
}
|
|
|
|
void sexp_tree::copy_branch(HTREEITEM source, HTREEITEM parent, HTREEITEM after)
|
|
{
|
|
uint i;
|
|
int image1, image2;
|
|
HTREEITEM h, child;
|
|
|
|
if (source) {
|
|
for (i=0; i<tree_nodes.size(); i++)
|
|
if (tree_nodes[i].handle == source)
|
|
break;
|
|
|
|
if (i < tree_nodes.size()) {
|
|
GetItemImage(source, image1, image2);
|
|
h = insert(GetItemText(source), image1, image2, parent, after);
|
|
tree_nodes[i].handle = h;
|
|
|
|
} else {
|
|
GetItemImage(source, image1, image2);
|
|
h = insert(GetItemText(source), image1, image2, parent, after);
|
|
}
|
|
|
|
SetItemData(h, GetItemData(source));
|
|
child = GetChildItem(source);
|
|
while (child) {
|
|
copy_branch(child, h);
|
|
child = GetNextSiblingItem(child);
|
|
}
|
|
|
|
if (GetItemState(source, TVIS_EXPANDED) & TVIS_EXPANDED)
|
|
Expand(h, TVE_EXPAND);
|
|
}
|
|
}
|
|
|
|
void sexp_tree::swap_roots(HTREEITEM one, HTREEITEM two)
|
|
{
|
|
HTREEITEM h;
|
|
|
|
Assert(!GetParentItem(one));
|
|
Assert(!GetParentItem(two));
|
|
// copy_branch(one, TVI_ROOT, two);
|
|
// move_branch(two, TVI_ROOT, one);
|
|
// DeleteItem(one);
|
|
h = move_branch(one, TVI_ROOT, two);
|
|
SelectItem(h);
|
|
SelectItem(h);
|
|
*modified = 1;
|
|
}
|
|
|
|
void sexp_tree::OnBegindrag(NMHDR* pNMHDR, LRESULT* pResult)
|
|
{
|
|
UINT flags = 0;
|
|
|
|
// ScreenToClient(&m_pt);
|
|
ASSERT(!m_dragging);
|
|
m_h_drag = HitTest(m_pt, &flags);
|
|
m_h_drop = NULL;
|
|
|
|
if (!m_mode || GetParentItem(m_h_drag))
|
|
return;
|
|
|
|
ASSERT(m_p_image_list == NULL);
|
|
m_p_image_list = CreateDragImage(m_h_drag); // get the image list for dragging
|
|
if (!m_p_image_list)
|
|
return;
|
|
|
|
m_p_image_list->DragShowNolock(TRUE);
|
|
m_p_image_list->SetDragCursorImage(0, CPoint(0, 0));
|
|
m_p_image_list->BeginDrag(0, CPoint(0,0));
|
|
m_p_image_list->DragMove(m_pt);
|
|
m_p_image_list->DragEnter(this, m_pt);
|
|
SetCapture();
|
|
m_dragging = TRUE;
|
|
}
|
|
|
|
void sexp_tree::OnLButtonDown(UINT nFlags, CPoint point)
|
|
{
|
|
m_pt = point;
|
|
CTreeCtrl::OnLButtonDown(nFlags, point);
|
|
}
|
|
|
|
void sexp_tree::OnMouseMove(UINT nFlags, CPoint point)
|
|
{
|
|
HTREEITEM hitem = NULL;
|
|
UINT flags = 0;
|
|
|
|
if (m_dragging) {
|
|
ASSERT(m_p_image_list != NULL);
|
|
m_p_image_list->DragMove(point);
|
|
if ((hitem = HitTest(point, &flags)) != NULL)
|
|
if (!GetParentItem(hitem)) {
|
|
m_p_image_list->DragLeave(this);
|
|
SelectDropTarget(hitem);
|
|
m_h_drop = hitem;
|
|
m_p_image_list->DragEnter(this, point);
|
|
}
|
|
}
|
|
|
|
CTreeCtrl::OnMouseMove(nFlags, point);
|
|
}
|
|
|
|
void sexp_tree::OnLButtonUp(UINT nFlags, CPoint point)
|
|
{
|
|
int index1, index2;
|
|
|
|
if (m_dragging) {
|
|
ASSERT(m_p_image_list != NULL);
|
|
m_p_image_list->DragLeave(this);
|
|
m_p_image_list->EndDrag();
|
|
delete m_p_image_list;
|
|
m_p_image_list = NULL;
|
|
|
|
if (m_h_drop && m_h_drag != m_h_drop) {
|
|
Assert(m_h_drag);
|
|
index1 = GetItemData(m_h_drag);
|
|
index2 = GetItemData(m_h_drop);
|
|
swap_roots(m_h_drag, m_h_drop);
|
|
if (m_mode == MODE_GOALS) {
|
|
Assert(Goal_editor_dlg);
|
|
Goal_editor_dlg->swap_handler(index1, index2);
|
|
|
|
} else if (m_mode == MODE_EVENTS) {
|
|
Assert(Event_editor_dlg);
|
|
Event_editor_dlg->swap_handler(index1, index2);
|
|
|
|
} else if (m_mode == MODE_CAMPAIGN) {
|
|
Campaign_tree_formp->swap_handler(index1, index2);
|
|
|
|
} else
|
|
Assert(0);
|
|
|
|
} else
|
|
MessageBeep(0);
|
|
|
|
ReleaseCapture();
|
|
m_dragging = FALSE;
|
|
SelectDropTarget(NULL);
|
|
}
|
|
|
|
CTreeCtrl::OnLButtonUp(nFlags, point);
|
|
}
|
|
|
|
const static UINT Numbered_data_bitmaps[] = {
|
|
IDB_DATA_00,
|
|
IDB_DATA_05,
|
|
IDB_DATA_10,
|
|
IDB_DATA_15,
|
|
IDB_DATA_20,
|
|
IDB_DATA_25,
|
|
IDB_DATA_30,
|
|
IDB_DATA_35,
|
|
IDB_DATA_40,
|
|
IDB_DATA_45,
|
|
IDB_DATA_50,
|
|
IDB_DATA_55,
|
|
IDB_DATA_60,
|
|
IDB_DATA_65,
|
|
IDB_DATA_70,
|
|
IDB_DATA_75,
|
|
IDB_DATA_80,
|
|
IDB_DATA_85,
|
|
IDB_DATA_90,
|
|
IDB_DATA_95
|
|
};
|
|
|
|
void sexp_tree::setup(CEdit *ptr)
|
|
{
|
|
CImageList *pimagelist;
|
|
CBitmap bitmap;
|
|
|
|
help_box = ptr;
|
|
if (help_box) {
|
|
int stops[2] = { 10, 30 };
|
|
|
|
help_box -> SetTabStops(2, (LPINT) stops);
|
|
}
|
|
|
|
pimagelist = GetImageList(TVSIL_NORMAL);
|
|
if (!pimagelist) {
|
|
pimagelist = new CImageList();
|
|
pimagelist->Create(16, 16, TRUE/*bMask*/, 2, 22);
|
|
|
|
//*****Add generic images
|
|
bitmap.LoadBitmap(IDB_OPERATOR);
|
|
pimagelist->Add(&bitmap, (COLORREF) 0xFFFFFF);
|
|
bitmap.DeleteObject();
|
|
|
|
bitmap.LoadBitmap(IDB_DATA);
|
|
pimagelist->Add(&bitmap, (COLORREF) 0xFF00FF);
|
|
bitmap.DeleteObject();
|
|
|
|
bitmap.LoadBitmap(IDB_VARIABLE);
|
|
pimagelist->Add(&bitmap, (COLORREF) 0xFF00FF);
|
|
bitmap.DeleteObject();
|
|
|
|
bitmap.LoadBitmap(IDB_ROOT);
|
|
pimagelist->Add(&bitmap, (COLORREF) 0xFF00FF);
|
|
bitmap.DeleteObject();
|
|
|
|
bitmap.LoadBitmap(IDB_ROOT_DIRECTIVE);
|
|
pimagelist->Add(&bitmap, (COLORREF) 0xFFFFFF);
|
|
bitmap.DeleteObject();
|
|
|
|
bitmap.LoadBitmap(IDB_CHAINED);
|
|
pimagelist->Add(&bitmap, (COLORREF) 0xFF00FF);
|
|
bitmap.DeleteObject();
|
|
|
|
bitmap.LoadBitmap(IDB_CHAINED_DIRECTIVE);
|
|
pimagelist->Add(&bitmap, (COLORREF) 0xFFFFFF);
|
|
bitmap.DeleteObject();
|
|
|
|
bitmap.LoadBitmap(IDB_GREEN_DOT);
|
|
pimagelist->Add(&bitmap, (COLORREF) 0xFFFFFF);
|
|
bitmap.DeleteObject();
|
|
|
|
bitmap.LoadBitmap(IDB_BLACK_DOT);
|
|
pimagelist->Add(&bitmap, (COLORREF) 0xFFFFFF);
|
|
bitmap.DeleteObject();
|
|
|
|
//*****Add numbered data entries
|
|
int num = sizeof(Numbered_data_bitmaps)/sizeof(UINT);
|
|
int i = 0;
|
|
for(i = 0; i < num; i++)
|
|
{
|
|
bitmap.LoadBitmap(Numbered_data_bitmaps[i]);
|
|
pimagelist->Add(&bitmap, (COLORREF) 0xFF00FF);
|
|
bitmap.DeleteObject();
|
|
}
|
|
|
|
SetImageList(pimagelist, TVSIL_NORMAL);
|
|
}
|
|
}
|
|
//#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
|
|
|
|
|
|
HTREEITEM sexp_tree::insert(LPCTSTR lpszItem, int image, int sel_image, HTREEITEM hParent, HTREEITEM hInsertAfter)
|
|
{
|
|
return InsertItem(lpszItem, image, sel_image, hParent, hInsertAfter);
|
|
|
|
}
|
|
|
|
void sexp_tree::OnDestroy()
|
|
{
|
|
CImageList *pimagelist;
|
|
|
|
pimagelist = GetImageList(TVSIL_NORMAL);
|
|
if (pimagelist) {
|
|
pimagelist->DeleteImageList();
|
|
delete pimagelist;
|
|
}
|
|
|
|
CTreeCtrl::OnDestroy();
|
|
}
|
|
|
|
HTREEITEM sexp_tree::handle(int node)
|
|
{
|
|
return tree_nodes[node].handle;
|
|
}
|
|
|
|
char *sexp_tree::help(int code)
|
|
{
|
|
int i;
|
|
|
|
i = Num_sexp_help;
|
|
while (i--) {
|
|
if (Sexp_help[i].id == code)
|
|
break;
|
|
}
|
|
|
|
if (i >= 0)
|
|
return Sexp_help[i].help;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
// get type of item clicked on
|
|
int sexp_tree::get_type(HTREEITEM h)
|
|
{
|
|
uint i;
|
|
|
|
// get index into sexp_tree
|
|
for (i=0; i<tree_nodes.size(); i++)
|
|
if (tree_nodes[i].handle == h)
|
|
break;
|
|
|
|
if ( (i >= tree_nodes.size()) ) {
|
|
// Int3(); // This would be the root of the tree -- ie, event name
|
|
return -1;
|
|
}
|
|
|
|
return tree_nodes[i].type;
|
|
}
|
|
|
|
|
|
void sexp_tree::update_help(HTREEITEM h)
|
|
{
|
|
char *str;
|
|
int i, j, z, c, code, index, sibling_place;
|
|
CString text;
|
|
|
|
/* Goober5000 - this is just annoying
|
|
for (i=0; i<Num_operators; i++)
|
|
for (j=0; j<Num_op_menus; j++)
|
|
if ((Operators[i].value & OP_CATEGORY_MASK) == op_menu[j].id) {
|
|
if (!help(Operators[i].value))
|
|
Int3(); // Allender! If you add new sexp operators, add help for them too! :)
|
|
}
|
|
*/
|
|
help_box = (CEdit *) GetParent()->GetDlgItem(IDC_HELP_BOX);
|
|
if (!help_box || !::IsWindow(help_box->m_hWnd))
|
|
return;
|
|
|
|
mini_help_box = (CEdit *) GetParent()->GetDlgItem(IDC_MINI_HELP_BOX);
|
|
if (!mini_help_box || !::IsWindow(mini_help_box->m_hWnd))
|
|
return;
|
|
|
|
for (i=0; i<(int)tree_nodes.size(); i++)
|
|
if (tree_nodes[i].handle == h)
|
|
break;
|
|
|
|
if ((i >= (int)tree_nodes.size()) || !tree_nodes[i].type) {
|
|
help_box->SetWindowText("");
|
|
mini_help_box->SetWindowText("");
|
|
return;
|
|
}
|
|
|
|
if (SEXPT_TYPE(tree_nodes[i].type) != SEXPT_OPERATOR)
|
|
{
|
|
z = tree_nodes[i].parent;
|
|
if (z < 0) {
|
|
Warning(LOCATION, "Sexp data \"%s\" has no parent!", tree_nodes[i].text);
|
|
return;
|
|
}
|
|
|
|
code = get_operator_const(tree_nodes[z].text);
|
|
index = get_operator_index(tree_nodes[z].text);
|
|
sibling_place = get_sibling_place(i) + 1; //We want it to start at 1
|
|
|
|
//*****Minihelp box
|
|
if((SEXPT_TYPE(tree_nodes[i].type) == SEXPT_NUMBER) || (SEXPT_TYPE(tree_nodes[i].type) == SEXPT_STRING) && sibling_place > 0)
|
|
{
|
|
char buffer[10240] = {""};
|
|
|
|
//Get the help for the current operator
|
|
char *helpstr = help(code);
|
|
bool display_number = true;
|
|
|
|
//If a help string exists, try to display it
|
|
if(helpstr != NULL)
|
|
{
|
|
char searchstr[32];
|
|
char *loc=NULL, *loc2=NULL;
|
|
|
|
if(loc == NULL)
|
|
{
|
|
sprintf(searchstr, "\n%d:", sibling_place);
|
|
loc = strstr(helpstr, searchstr);
|
|
}
|
|
|
|
if(loc == NULL)
|
|
{
|
|
sprintf(searchstr, "\t%d:", sibling_place);
|
|
loc = strstr(helpstr, searchstr);
|
|
}
|
|
if(loc == NULL)
|
|
{
|
|
sprintf(searchstr, " %d:", sibling_place);
|
|
loc = strstr(helpstr, searchstr);
|
|
}
|
|
if(loc == NULL)
|
|
{
|
|
sprintf(searchstr, "%d:", sibling_place);
|
|
loc = strstr(helpstr, searchstr);
|
|
}
|
|
|
|
if(loc != NULL)
|
|
{
|
|
//Skip whitespace
|
|
while(*loc=='\r' || *loc == '\n' || *loc == ' ' || *loc == '\t') loc++;
|
|
|
|
//Find EOL
|
|
loc2 = strpbrk(loc, "\r\n");
|
|
if(loc2 != NULL)
|
|
{
|
|
size_t size = loc2-loc;
|
|
strncpy(buffer, loc, size);
|
|
if(size < sizeof(buffer))
|
|
{
|
|
buffer[size] = '\0';
|
|
}
|
|
display_number = false;
|
|
}
|
|
else
|
|
{
|
|
strcpy_s(buffer, loc);
|
|
display_number = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Display argument number
|
|
if(display_number)
|
|
{
|
|
sprintf(buffer, "%d:", sibling_place);
|
|
}
|
|
|
|
mini_help_box->SetWindowText(buffer);
|
|
}
|
|
|
|
if (index >= 0) {
|
|
c = 0;
|
|
j = tree_nodes[z].child;
|
|
while ((j >= 0) && (j != i)) {
|
|
j = tree_nodes[j].next;
|
|
c++;
|
|
}
|
|
|
|
Assert(j >= 0);
|
|
if (query_operator_argument_type(index, c) == OPF_MESSAGE) {
|
|
for (j=0; j<Num_messages; j++)
|
|
if (!stricmp(Messages[j].name, tree_nodes[i].text)) {
|
|
text.Format("Message Text:\r\n%s", Messages[j].message);
|
|
help_box->SetWindowText(text);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
i = z;
|
|
}
|
|
|
|
code = get_operator_const(tree_nodes[i].text);
|
|
str = help(code);
|
|
if (!str)
|
|
str = "No help available";
|
|
|
|
help_box->SetWindowText(str);
|
|
}
|
|
|
|
// find list of sexp_tree nodes with text
|
|
// stuff node indices into find[]
|
|
int sexp_tree::find_text(char *text, int *find)
|
|
{
|
|
uint i;
|
|
int find_count;
|
|
|
|
// initialize find
|
|
for (i=0; i<MAX_SEARCH_MESSAGE_DEPTH; i++) {
|
|
find[i] = -1;
|
|
}
|
|
|
|
find_count = 0;
|
|
|
|
for (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++] = i;
|
|
|
|
// don't exceed max count - array bounds
|
|
if (find_count == MAX_SEARCH_MESSAGE_DEPTH) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return find_count;
|
|
}
|
|
|
|
|
|
void sexp_tree::OnKeydown(NMHDR *pNMHDR, LRESULT *pResult)
|
|
{
|
|
int key;
|
|
TV_KEYDOWN *pTVKeyDown = (TV_KEYDOWN *) pNMHDR;
|
|
|
|
key = pTVKeyDown->wVKey;
|
|
if (key == VK_SPACE)
|
|
EditLabel(GetSelectedItem());
|
|
|
|
*pResult = 0;
|
|
}
|
|
|
|
// 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 sexp_tree::query_restricted_opf_range(int opf)
|
|
{
|
|
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:
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
// generate listing of valid argument values.
|
|
// opf = operator format to generate list for
|
|
// parent_node = the parent node we are generating list for
|
|
// arg_index = argument number of parent this argument will go at
|
|
//
|
|
// Goober5000 - add the listing from get_listing_opf_sub to the end of a new list containing
|
|
// the special argument item, but only if it's a child of a when-argument (or similar) sexp.
|
|
// Also only do this if the list has at least one item, because otherwise the argument code
|
|
// would have nothing to select from.
|
|
sexp_list_item *sexp_tree::get_listing_opf(int opf, int parent_node, int arg_index)
|
|
{
|
|
sexp_list_item head;
|
|
sexp_list_item *list = NULL;
|
|
int w_arg, e_arg;
|
|
|
|
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_WING:
|
|
list = get_listing_opf_wing();
|
|
break;
|
|
|
|
case OPF_AWACS_SUBSYSTEM:
|
|
case OPF_ROTATING_SUBSYSTEM:
|
|
case OPF_SUBSYSTEM:
|
|
list = get_listing_opf_subsystem(parent_node, arg_index);
|
|
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);
|
|
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_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_HUD_GAUGE_NAME:
|
|
list = get_listing_opf_hud_gauge_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_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_PERSONA:
|
|
list = get_listing_opf_persona();
|
|
break;
|
|
|
|
default:
|
|
Int3(); // unknown OPF code
|
|
list = NULL;
|
|
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... meaning if either w_arg or e_arg is >= 1, we can continue
|
|
w_arg = find_ancestral_argument_number(OP_WHEN_ARGUMENT, parent_node);
|
|
e_arg = find_ancestral_argument_number(OP_EVERY_TIME_ARGUMENT, parent_node);
|
|
if (w_arg < 1 && e_arg < 1 /* && the same for any future _ARGUMENT sexps */ ) {
|
|
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;
|
|
}
|
|
|
|
// Goober5000
|
|
int sexp_tree::find_argument_number(int parent_node, int child_node)
|
|
{
|
|
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 sexp_tree::find_ancestral_argument_number(int parent_op, int child_node)
|
|
{
|
|
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;
|
|
}
|
|
|
|
// not found
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Gets the proper data image for the tree item's place
|
|
* in its parent hierarchy.
|
|
*/
|
|
int sexp_tree::get_data_image(int node)
|
|
{
|
|
int count = get_sibling_place(node) + 1;
|
|
|
|
if (count <= 0) {
|
|
return BITMAP_DATA;
|
|
}
|
|
|
|
if (count % 5) {
|
|
return BITMAP_DATA;
|
|
}
|
|
|
|
int idx = (count % 100) / 5;
|
|
|
|
int num = sizeof(Numbered_data_bitmaps)/sizeof(UINT);
|
|
|
|
if (idx > num) {
|
|
return BITMAP_DATA;
|
|
}
|
|
|
|
return BITMAP_NUMBERED_DATA + idx;
|
|
}
|
|
|
|
int sexp_tree::get_sibling_place(int node)
|
|
{
|
|
if(tree_nodes[node].parent < 0 || tree_nodes[node].parent > (int)tree_nodes.size())
|
|
return -1;
|
|
|
|
sexp_tree_item *myparent = &tree_nodes[tree_nodes[node].parent];
|
|
|
|
if(myparent->child == -1)
|
|
return -1;
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_null()
|
|
{
|
|
int i;
|
|
sexp_list_item head;
|
|
|
|
for (i=0; i<Num_operators; i++)
|
|
if (query_operator_return_type(i) == OPR_NULL)
|
|
head.add_op(i);
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_flexible_argument()
|
|
{
|
|
int i;
|
|
sexp_list_item head;
|
|
|
|
for (i=0; i<Num_operators; i++)
|
|
if (query_operator_return_type(i) == OPR_FLEXIBLE_ARGUMENT)
|
|
head.add_op(i);
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::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<Num_operators; 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 *sexp_tree::get_listing_opf_positive()
|
|
{
|
|
int i, z;
|
|
sexp_list_item head;
|
|
|
|
for (i=0; i<Num_operators; 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 *sexp_tree::get_listing_opf_number()
|
|
{
|
|
int i, z;
|
|
sexp_list_item head;
|
|
|
|
for (i=0; i<Num_operators; i++) {
|
|
z = query_operator_return_type(i);
|
|
if ((z == OPR_NUMBER) || (z == OPR_POSITIVE))
|
|
head.add_op(i);
|
|
}
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::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].flags & SIF_HUGE_SHIP) && // big ship
|
|
!(Ships[ptr->instance].flags2 & SF2_TOGGLE_SUBSYSTEM_SCANNING) )|| // which is not flagged OR
|
|
((!(Ship_info[Ships[ptr->instance].ship_info_index].flags & SIF_HUGE_SHIP)) && // small ship
|
|
(Ships[ptr->instance].flags2 & SF2_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].flags & SIF_HUGE_SHIP) ) {
|
|
head.add_data(Ships[ptr->instance].ship_name);
|
|
}
|
|
}
|
|
}
|
|
|
|
ptr = GET_NEXT(ptr);
|
|
}
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_wing()
|
|
{
|
|
int i;
|
|
sexp_list_item head;
|
|
|
|
for (i=0; i<MAX_WINGS; i++){
|
|
if (Wings[i].wave_count){
|
|
head.add_data(Wings[i].name);
|
|
}
|
|
}
|
|
|
|
return head.next;
|
|
}
|
|
|
|
// 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
|
|
sexp_list_item *sexp_tree::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;
|
|
Assert(child >= 0);
|
|
|
|
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;
|
|
|
|
// 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;
|
|
|
|
// 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)
|
|
{
|
|
child = tree_nodes[child].next;
|
|
Assert(child >= 0);
|
|
child = tree_nodes[child].next;
|
|
}
|
|
else
|
|
{
|
|
Assert(arg_index == 1);
|
|
}
|
|
break;
|
|
|
|
// these sexps check the subsystem of the *second entry* on the list, not the first
|
|
case OP_DISTANCE_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:
|
|
// 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:
|
|
child = tree_nodes[child].next;
|
|
Assert(child >= 0);
|
|
child = tree_nodes[child].next;
|
|
Assert(child >= 0);
|
|
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;
|
|
Assert(child >= 0);
|
|
child = tree_nodes[child].next;
|
|
Assert(child >= 0);
|
|
child = tree_nodes[child].next;
|
|
Assert(child >= 0);
|
|
child = tree_nodes[child].next;
|
|
Assert(child >= 0);
|
|
child = tree_nodes[child].next;
|
|
Assert(child >= 0);
|
|
child = tree_nodes[child].next;
|
|
Assert(child >= 0);
|
|
child = tree_nodes[child].next;
|
|
Assert(child >= 0);
|
|
child = tree_nodes[child].next;
|
|
break;
|
|
}
|
|
|
|
// now find the ship and add all relevant subsystems
|
|
Assert(child >= 0);
|
|
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:
|
|
if (valid_cap_subsys_cargo_list(subsys->system_info->subobj_name) ) {
|
|
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 & MSS_FLAG_AWACS) {
|
|
head.add_data(subsys->system_info->subobj_name);
|
|
}
|
|
break;
|
|
|
|
// rotating
|
|
case OPS_ROTATE:
|
|
if (subsys->system_info->flags & MSS_FLAG_ROTATES) {
|
|
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);
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_point()
|
|
{
|
|
char buf[NAME_LENGTH+8];
|
|
int i, j;
|
|
sexp_list_item head;
|
|
|
|
for (i=0; i<Num_waypoint_lists; i++)
|
|
for (j=0; j<Waypoint_lists[i].count; j++) {
|
|
sprintf(buf, "%s:%d", Waypoint_lists[i].name, j + 1);
|
|
head.add_data_dup(buf);
|
|
}
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_iff()
|
|
{
|
|
int i;
|
|
sexp_list_item head;
|
|
|
|
for (i=0; i<Num_iffs; i++)
|
|
head.add_data(Iff_info[i].iff_name);
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_ai_class()
|
|
{
|
|
int i;
|
|
sexp_list_item head;
|
|
|
|
for (i=0; i<Num_ai_classes; i++)
|
|
head.add_data(Ai_class_names[i]);
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_support_ship_class()
|
|
{
|
|
int i;
|
|
sexp_list_item head;
|
|
|
|
head.add_data("<any support ship class>");
|
|
|
|
for (i=0; i<Num_ship_classes; i++)
|
|
{
|
|
if (Ship_info[i].flags & SIF_SUPPORT)
|
|
{
|
|
head.add_data(Ship_info[i].name);
|
|
}
|
|
}
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_ssm_class()
|
|
{
|
|
int i;
|
|
sexp_list_item head;
|
|
|
|
for (i=0; i<Ssm_info_count; i++)
|
|
{
|
|
head.add_data(Ssm_info[i].name);
|
|
}
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_ship_with_bay()
|
|
{
|
|
object *objp;
|
|
sexp_list_item head;
|
|
|
|
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 docking bay
|
|
if (ship_has_dock_bay(objp->instance))
|
|
{
|
|
head.add_data(Ships[objp->instance].ship_name);
|
|
}
|
|
}
|
|
}
|
|
|
|
head.add_data("<no anchor>");
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_soundtrack_name()
|
|
{
|
|
int i;
|
|
sexp_list_item head;
|
|
|
|
head.add_data("<No Music>");
|
|
|
|
for (i=0; i<Num_soundtracks; i++)
|
|
{
|
|
head.add_data(Soundtracks[i].name);
|
|
}
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_arrival_location()
|
|
{
|
|
int i;
|
|
sexp_list_item head;
|
|
|
|
for (i=0; i<MAX_ARRIVAL_NAMES; i++)
|
|
head.add_data(Arrival_location_names[i]);
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_departure_location()
|
|
{
|
|
int i;
|
|
sexp_list_item head;
|
|
|
|
for (i=0; i<MAX_DEPARTURE_NAMES; i++)
|
|
head.add_data(Departure_location_names[i]);
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_arrival_anchor_all()
|
|
{
|
|
int i, restrict_to_players;
|
|
object *objp;
|
|
sexp_list_item head;
|
|
|
|
for (restrict_to_players = 0; restrict_to_players < 2; restrict_to_players++)
|
|
{
|
|
for (i = 0; i < Num_iffs; i++)
|
|
{
|
|
char tmp[NAME_LENGTH + 15];
|
|
stuff_special_arrival_anchor_name(tmp, i, restrict_to_players, 0);
|
|
|
|
head.add_data(tmp);
|
|
}
|
|
}
|
|
|
|
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) )
|
|
{
|
|
head.add_data(Ships[objp->instance].ship_name);
|
|
}
|
|
}
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::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;
|
|
Assert(child >= 0);
|
|
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<Num_operators; 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; w<Wings[z].wave_count; w++) {
|
|
n = Wings[z].ship_index[w];
|
|
// add operators if it's an ai-goal and ai-goal is allowed for that ship
|
|
for (i=0; i<Num_operators; i++) {
|
|
if ( (query_operator_return_type(i) == OPR_AI_GOAL) && query_sexp_ai_goal_valid(Operators[i].value, n) )
|
|
head.add_op(i);
|
|
}
|
|
}
|
|
// when dealing with the special argument add them all. It's up to the FREDder to ensure invalid orders aren't given
|
|
} else if (!strcmp(tree_nodes[child].text, SEXP_ARGUMENT_STRING)) {
|
|
for (i=0; i<Num_operators; i++) {
|
|
if (query_operator_return_type(i) == OPR_AI_GOAL) {
|
|
head.add_op(i);
|
|
}
|
|
}
|
|
} else
|
|
return NULL; // no valid ship or wing to check against, make nothing available
|
|
}
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_docker_point(int parent_node)
|
|
{
|
|
int i, z, sh;
|
|
sexp_list_item head;
|
|
|
|
Assert(parent_node >= 0);
|
|
Assert(!stricmp(tree_nodes[parent_node].text, "ai-dock"));
|
|
|
|
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);
|
|
|
|
sh = ship_name_lookup(tree_nodes[z].text, 1);
|
|
if (sh >= 0) {
|
|
z = get_docking_list(Ship_info[Ships[sh].ship_info_index].model_num);
|
|
for (i=0; i<z; i++)
|
|
head.add_data(Docking_bay_list[i]);
|
|
}
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_dockee_point(int parent_node)
|
|
{
|
|
int i, z, sh;
|
|
sexp_list_item head;
|
|
|
|
Assert(parent_node >= 0);
|
|
Assert(!stricmp(tree_nodes[parent_node].text, "ai-dock"));
|
|
|
|
z = tree_nodes[parent_node].child;
|
|
Assert(z >= 0);
|
|
|
|
sh = ship_name_lookup(tree_nodes[z].text, 1);
|
|
if (sh >= 0) {
|
|
z = get_docking_list(Ship_info[Ships[sh].ship_info_index].model_num);
|
|
for (i=0; i<z; i++)
|
|
head.add_data(Docking_bay_list[i]);
|
|
}
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_message()
|
|
{
|
|
char *str;
|
|
int i;
|
|
sexp_list_item head;
|
|
|
|
if (m_mode == MODE_EVENTS) {
|
|
Assert(Event_editor_dlg);
|
|
// this for looks a litle strange, but had to do it get rid of a warning. Conditional
|
|
//uses last statement is sequence, i.e. same as for (i=0, str, i++)
|
|
for (i=0; str = Event_editor_dlg->current_message_name(i), str; i++)
|
|
head.add_data(str);
|
|
|
|
} else {
|
|
for (i=Num_builtin_messages; i<Num_messages; i++)
|
|
head.add_data(Messages[i].name);
|
|
}
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_persona()
|
|
{
|
|
int i;
|
|
sexp_list_item head;
|
|
|
|
for (i = 0; i < Num_personas; i++) {
|
|
if (Personas[i].flags & PERSONA_FLAG_WINGMAN) {
|
|
head.add_data (Personas[i].name);
|
|
}
|
|
}
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_who_from()
|
|
{
|
|
object *ptr;
|
|
sexp_list_item head;
|
|
|
|
//head.add_data("<any allied>");
|
|
head.add_data("#Command");
|
|
head.add_data("<any wingman>");
|
|
|
|
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[get_ship_from_obj(ptr)].ship_info_index].flags & SIF_NOT_FLYABLE))
|
|
head.add_data(Ships[ptr->instance].ship_name);
|
|
|
|
ptr = GET_NEXT(ptr);
|
|
}
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::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 *sexp_tree::get_listing_opf_waypoint_path()
|
|
{
|
|
int i;
|
|
sexp_list_item head;
|
|
|
|
for (i=0; i<Num_waypoint_lists; i++)
|
|
head.add_data(Waypoint_lists[i].name);
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::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 *sexp_tree::get_listing_opf_ship_wing_point()
|
|
{
|
|
int i;
|
|
sexp_list_item head;
|
|
|
|
for (i = 0; i < Num_iffs; i++)
|
|
{
|
|
char tmp[NAME_LENGTH + 7];
|
|
sprintf(tmp, "<any %s>", Iff_info[i].iff_name);
|
|
strlwr(tmp);
|
|
head.add_data_dup(tmp);
|
|
}
|
|
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 *sexp_tree::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;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_mission_name()
|
|
{
|
|
int i;
|
|
sexp_list_item head;
|
|
|
|
if ((m_mode == MODE_CAMPAIGN) && (Cur_campaign_mission >= 0)) {
|
|
for (i=0; i<Campaign.num_missions; i++)
|
|
if ( (i == Cur_campaign_mission) || (Campaign.missions[i].level < Campaign.missions[Cur_campaign_mission].level) )
|
|
head.add_data(Campaign.missions[i].name);
|
|
|
|
} else
|
|
head.add_data(Mission_filename);
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_goal_name(int parent_node)
|
|
{
|
|
int i, m;
|
|
sexp_list_item head;
|
|
|
|
if (m_mode == MODE_CAMPAIGN) {
|
|
int child;
|
|
|
|
Assert(parent_node >= 0);
|
|
child = tree_nodes[parent_node].child;
|
|
Assert(child >= 0);
|
|
|
|
for (m=0; m<Campaign.num_missions; m++)
|
|
if (!stricmp(Campaign.missions[m].name, tree_nodes[child].text))
|
|
break;
|
|
|
|
if (m < Campaign.num_missions) {
|
|
if (Campaign.missions[m].num_goals < 0) // haven't loaded goal names yet.
|
|
read_mission_goal_list(m);
|
|
|
|
for (i=0; i<Campaign.missions[m].num_goals; i++)
|
|
head.add_data(Campaign.missions[m].goals[i].name);
|
|
}
|
|
|
|
} else {
|
|
for (i=0; i<Num_goals; i++)
|
|
head.add_data(Mission_goals[i].name);
|
|
}
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::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 *sexp_tree::get_listing_opf_order_recipient()
|
|
{
|
|
sexp_list_item head;
|
|
|
|
head.add_data("<all fighters>");
|
|
|
|
head.add_list(get_listing_opf_ship());
|
|
head.add_list(get_listing_opf_wing());
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_ship_type()
|
|
{
|
|
unsigned int i;
|
|
sexp_list_item head;
|
|
|
|
for (i=0; i<Ship_types.size(); i++){
|
|
head.add_data(Ship_types[i].name);
|
|
}
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_keypress()
|
|
{
|
|
int i;
|
|
sexp_list_item head;
|
|
|
|
for (i=0; i<CCFG_MAX; i++) {
|
|
if (Control_config[i].key_default > 0) {
|
|
head.add_data_dup(textify_scancode(Control_config[i].key_default));
|
|
}
|
|
}
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_event_name(int parent_node)
|
|
{
|
|
int i, m;
|
|
sexp_list_item head;
|
|
|
|
|
|
if (m_mode == MODE_CAMPAIGN) {
|
|
int child;
|
|
|
|
Assert(parent_node >= 0);
|
|
child = tree_nodes[parent_node].child;
|
|
Assert(child >= 0);
|
|
|
|
for (m=0; m<Campaign.num_missions; m++)
|
|
if (!stricmp(Campaign.missions[m].name, tree_nodes[child].text))
|
|
break;
|
|
|
|
if (m < Campaign.num_missions) {
|
|
if (Campaign.missions[m].num_events < 0) // haven't loaded goal names yet.
|
|
read_mission_goal_list(m);
|
|
|
|
for (i=0; i<Campaign.missions[m].num_events; i++)
|
|
head.add_data(Campaign.missions[m].events[i].name);
|
|
}
|
|
|
|
} else {
|
|
for (i=0; i<Num_mission_events; i++)
|
|
head.add_data(Mission_events[i].name);
|
|
}
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_ai_order()
|
|
{
|
|
int i;
|
|
sexp_list_item head;
|
|
|
|
for (i=0; i<NUM_COMM_ORDER_ITEMS; i++)
|
|
head.add_data(Comm_orders[i].name);
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_skill_level()
|
|
{
|
|
int i;
|
|
sexp_list_item head;
|
|
|
|
for (i=0; i<NUM_SKILL_LEVELS; i++)
|
|
head.add_data(Skill_level_names(i, 0));
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_cargo()
|
|
{
|
|
sexp_list_item head;
|
|
|
|
head.add_data("Nothing");
|
|
for (int i=0; i<Num_cargo; i++)
|
|
{
|
|
if (stricmp(Cargo_names[i], "nothing"))
|
|
head.add_data(Cargo_names[i]);
|
|
}
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_string()
|
|
{
|
|
sexp_list_item head;
|
|
|
|
head.add_data("<any string>");
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_medal_name()
|
|
{
|
|
int i;
|
|
sexp_list_item head;
|
|
|
|
for (i=0; i<MAX_ASSIGNABLE_MEDALS; i++)
|
|
head.add_data(Medals[i].name);
|
|
|
|
// also add SOC crest (index 17) and Wings (index 13)
|
|
head.add_data(Medals[13].name);
|
|
head.add_data(Medals[17].name);
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_weapon_name()
|
|
{
|
|
int i;
|
|
sexp_list_item head;
|
|
|
|
for (i=0; i<Num_weapon_types; i++)
|
|
head.add_data(Weapon_info[i].name);
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_intel_name()
|
|
{
|
|
int i;
|
|
sexp_list_item head;
|
|
|
|
for (i=0; i<Intel_info_size; i++)
|
|
head.add_data(Intel_info[i].name);
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_ship_class_name()
|
|
{
|
|
int i;
|
|
sexp_list_item head;
|
|
|
|
for (i=0; i<Num_ship_classes; i++)
|
|
head.add_data(Ship_info[i].name);
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_hud_gauge_name()
|
|
{
|
|
int i;
|
|
sexp_list_item head;
|
|
|
|
for (i=0; i<NUM_HUD_GAUGES; i++)
|
|
head.add_data(HUD_gauge_text[i]);
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_huge_weapon()
|
|
{
|
|
int i;
|
|
sexp_list_item head;
|
|
|
|
for (i=0; i<Num_weapon_types; i++) {
|
|
if (Weapon_info[i].wi_flags & WIF_HUGE)
|
|
head.add_data(Weapon_info[i].name);
|
|
}
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::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 *sexp_tree::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;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::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 *sexp_tree::get_listing_opf_subsys_or_generic(int parent_node, int arg_index)
|
|
{
|
|
sexp_list_item head;
|
|
|
|
head.add_data(SEXP_ALL_ENGINES_STRING);
|
|
head.add_data(SEXP_ALL_TURRETS_STRING);
|
|
head.add_list(get_listing_opf_subsystem(parent_node, arg_index));
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_jump_nodes()
|
|
{
|
|
sexp_list_item head;
|
|
|
|
for ( jump_node *jnp = (jump_node *)Jump_nodes.get_first(); !Jump_nodes.is_end(jnp); jnp = (jump_node *)jnp->get_next() ) {
|
|
head.add_data( jnp->get_name_ptr());
|
|
}
|
|
|
|
return head.next;
|
|
}
|
|
|
|
// creates list of Sexp_variables
|
|
sexp_list_item *sexp_tree::get_listing_opf_variable_names()
|
|
{
|
|
int i;
|
|
sexp_list_item head;
|
|
|
|
for (i=0; i<MAX_SEXP_VARIABLES; i++) {
|
|
if (Sexp_variables[i].type & SEXP_VARIABLE_SET) {
|
|
head.add_data( Sexp_variables[i].variable_name );
|
|
}
|
|
}
|
|
|
|
return head.next;
|
|
}
|
|
|
|
// get default skybox model name
|
|
sexp_list_item *sexp_tree::get_listing_opf_skybox_model()
|
|
{
|
|
|
|
sexp_list_item head;
|
|
head.add_data("default");
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_background_bitmap()
|
|
{
|
|
sexp_list_item head;
|
|
int i;
|
|
|
|
for (i=0; i < stars_get_num_entries(false, true); i++)
|
|
{
|
|
head.add_data( (char*)stars_get_name_FRED(i, false) );
|
|
}
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_sun_bitmap()
|
|
{
|
|
sexp_list_item head;
|
|
int i;
|
|
|
|
for (i=0; i < stars_get_num_entries(true, true); i++)
|
|
{
|
|
head.add_data( (char*)stars_get_name_FRED(i, true) );
|
|
}
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_nebula_storm_type()
|
|
{
|
|
sexp_list_item head;
|
|
int i;
|
|
|
|
head.add_data("none");
|
|
|
|
for (i=0; i < Num_storm_types; i++)
|
|
{
|
|
head.add_data(Storm_types[i].name);
|
|
}
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_nebula_poof()
|
|
{
|
|
sexp_list_item head;
|
|
int i;
|
|
|
|
for (i=0; i < MAX_NEB2_POOFS; i++)
|
|
{
|
|
head.add_data(Neb2_poof_filenames[i]);
|
|
}
|
|
|
|
return head.next;
|
|
}
|
|
|
|
sexp_list_item *sexp_tree::get_listing_opf_turret_target_order()
|
|
{
|
|
int i;
|
|
sexp_list_item head;
|
|
|
|
for (i=0; i<NUM_TURRET_ORDER_TYPES; i++)
|
|
head.add_data(Turret_target_order_names[i]);
|
|
|
|
return head.next;
|
|
}
|
|
|
|
|
|
// Deletes sexp_variable from sexp_tree.
|
|
// resets tree to not include given variable, and resets text and type
|
|
void sexp_tree::delete_sexp_tree_variable(const char *var_name)
|
|
{
|
|
char search_str[64];
|
|
char replace_text[TOKEN_LENGTH];
|
|
|
|
sprintf(search_str, "%s(", var_name);
|
|
|
|
// store old item index
|
|
int old_item_index = item_index;
|
|
|
|
for (uint idx=0; idx<tree_nodes.size(); idx++) {
|
|
if (tree_nodes[idx].type & SEXPT_VARIABLE) {
|
|
if ( strstr(tree_nodes[idx].text, search_str) != NULL ) {
|
|
|
|
// check type is number or string
|
|
Assert( (tree_nodes[idx].type & SEXPT_NUMBER) || (tree_nodes[idx].type & SEXPT_STRING) );
|
|
|
|
// reset type as not variable
|
|
int type = tree_nodes[idx].type &= ~SEXPT_VARIABLE;
|
|
|
|
// reset text
|
|
if (tree_nodes[idx].type & SEXPT_NUMBER) {
|
|
strcpy_s(replace_text, "number");
|
|
} else {
|
|
strcpy_s(replace_text, "string");
|
|
}
|
|
|
|
// set item_index and replace data
|
|
item_index = idx;
|
|
replace_data(replace_text, type);
|
|
}
|
|
}
|
|
}
|
|
|
|
// restore item_index
|
|
item_index = old_item_index;
|
|
}
|
|
|
|
|
|
// Modify sexp_tree for a change in sexp_variable (name, type, or default value)
|
|
void sexp_tree::modify_sexp_tree_variable(const char *old_name, int sexp_var_index)
|
|
{
|
|
char search_str[64];
|
|
int type;
|
|
|
|
Assert(Sexp_variables[sexp_var_index].type & SEXP_VARIABLE_SET);
|
|
Assert( (Sexp_variables[sexp_var_index].type & SEXP_VARIABLE_NUMBER) || (Sexp_variables[sexp_var_index].type & SEXP_VARIABLE_STRING) );
|
|
|
|
// Get type for sexp_tree node
|
|
if (Sexp_variables[sexp_var_index].type & SEXP_VARIABLE_NUMBER) {
|
|
type = (SEXPT_NUMBER | SEXPT_VALID);
|
|
} else {
|
|
type = (SEXPT_STRING | SEXPT_VALID);
|
|
}
|
|
|
|
// store item index;
|
|
int old_item_index = item_index;
|
|
|
|
// Search string in sexp_tree nodes
|
|
sprintf(search_str, "%s(", old_name);
|
|
|
|
for (uint idx=0; idx<tree_nodes.size(); idx++) {
|
|
if (tree_nodes[idx].type & SEXPT_VARIABLE) {
|
|
if ( strstr(tree_nodes[idx].text, search_str) != NULL ) {
|
|
// temp set item_index
|
|
item_index = idx;
|
|
|
|
// replace variable data
|
|
replace_variable_data(sexp_var_index, (type | SEXPT_VARIABLE));
|
|
}
|
|
}
|
|
}
|
|
|
|
// restore item_index
|
|
item_index = old_item_index;
|
|
}
|
|
|
|
|
|
// convert from item_index to sexp_variable index, -1 if not
|
|
int sexp_tree::get_item_index_to_var_index()
|
|
{
|
|
// 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 sexp_tree::get_tree_name_to_sexp_variable_index(const char *tree_name)
|
|
{
|
|
char var_name[TOKEN_LENGTH];
|
|
|
|
int 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 sexp_tree::get_variable_count(const char *var_name)
|
|
{
|
|
uint idx;
|
|
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 (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 sexp_tree::get_loadout_variable_count(int var_index)
|
|
{
|
|
// 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 (var_index == Team_data[i].ship_list_variables[idx]) {
|
|
count++;
|
|
}
|
|
|
|
if (var_index == Team_data[i].ship_count_variables[idx]) {
|
|
count++;
|
|
}
|
|
}
|
|
|
|
for (idx=0; idx<Team_data[i].num_weapon_choices; idx++) {
|
|
if (var_index == Team_data[i].weaponry_pool_variable[idx]) {
|
|
count++;
|
|
}
|
|
if (var_index == Team_data[i].weaponry_amount_variable[idx]) {
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|