/* * 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" #include "graphics/gropenglshader.h" #include "graphics/gropenglpostprocessing.h" #include "sound/ds.h" #include "globalincs/alphacolors.h" #include "localization/localize.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 #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(const 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 -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 { 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, ""); 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, const char *text) { Assert(type != SEXPT_UNUSED); Assert(tree_nodes[node].type != SEXPT_UNUSED); tree_nodes[node].type = type; size_t max_length; if (type & SEXPT_VARIABLE) { max_length = 2 * TOKEN_LENGTH + 2; } else { 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, const 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; iGetMenuItemCount(); 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; iAppendMenu(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) { int modify_type = get_modify_variable_type(parent); if (modify_type == OPF_NUMBER) { type = SEXPT_NUMBER; } else if (modify_type == OPF_AMBIGUOUS) { type = SEXPT_STRING; } else { Int3(); type = tree_nodes[first_arg].type; } } // Goober5000 - certain types accept both integers and a list of strings if (op_type == OPF_GAME_SND || op_type == OPF_WEAPON_BANK_NUMBER) { type = SEXPT_NUMBER | SEXPT_STRING; } 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; idxAppendMenu(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; iTrackPopupMenu(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= 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; iop >= 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; } else if (type == OPF_AI_GOAL) { replace_type = OPR_AI_GOAL; } // Goober5000 else if (type == OPF_FLEXIBLE_ARGUMENT) { replace_type = OPR_FLEXIBLE_ARGUMENT; } // Goober5000 else if (type == OPF_GAME_SND || type == OPF_WEAPON_BANK_NUMBER) { // enable number even though we are also going to default to string menu.EnableMenuItem(ID_REPLACE_NUMBER, MF_ENABLED); } // default to string if (replace_type == OPR_STRING) { menu.EnableMenuItem(ID_REPLACE_STRING, MF_ENABLED); } // modify string or number if (modify_variable) if ( Operators[op].value == OP_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 } else if ( Operators[op].value == OP_SET_VARIABLE_BY_INDEX ) { // it depends on which argument we are modifying // first argument is always a number if (Replace_count == 0) { menu.EnableMenuItem(ID_REPLACE_NUMBER, MF_ENABLED); menu.EnableMenuItem(ID_REPLACE_STRING, MF_GRAYED); } // second argument could be anything else { 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= -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 -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= 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; // let's make sure we aren't introducing any invalid characters, per Mantis #2893 lcl_fred_replace_stuff(str, TOKEN_LENGTH - 1); for (node=0; nodehandler(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) { const char *op = match_closest_operator(str, node); if (!op) return 0; // Goober5000 - avoids crashing SetItemText(h, op); item_index = node; int op_num = get_operator_index(op); 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' // const char *sexp_tree::match_closest_operator(const char *str, int node) { int z, i, op, arg_num, opf, opr; const 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 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= 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 -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= FIRST_OP) { // do we have an op value instead of an op number (index)? for (i=0; inext) ptr = ptr->next; ptr->next = item; item->set_op(op_num); } // add a node to end of list // Defaults: t = SEXPT_STRING // void sexp_list_item::add_data(const char *str, int t) { sexp_list_item *item, *ptr; item = new sexp_list_item; ptr = this; while (ptr->next) ptr = ptr->next; ptr->next = item; item->set_data(str, t); } // add a node to end of list, allocating memory for the text // Defaults: t = SEXPT_STRING // void sexp_list_item::add_data_dup(const char *str, int t) { sexp_list_item *item, *ptr; item = new sexp_list_item; ptr = this; while (ptr->next) ptr = ptr->next; ptr->next = item; item->set_data(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((void *) 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; if (get_default_value(&item, buf, 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 && Operators[op].value == OP_MODIFY_VARIABLE) || (argnum == 8 && Operators[op].value == OP_ADD_BACKGROUND_BITMAP) || (argnum == 5 && Operators[op].value == OP_ADD_SUN_BITMAP) || (argnum == 2 && Operators[op].value == OP_STRING_CONCATENATE) || (argnum == 1 && Operators[op].value == OP_INT_TO_STRING) || (argnum == 3 && Operators[op].value == OP_STRING_GET_SUBSTRING) || (argnum == 4 && Operators[op].value == OP_STRING_SET_SUBSTRING) || (argnum == 1 && Operators[op].value == OP_COPY_VARIABLE_FROM_INDEX) || (argnum == 1 && Operators[op].value == OP_SCRIPT_EVAL_STRING)) { 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; get_default_value(&temp_item, buf2, 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, char *text_buf, 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(""); 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) || (Operators[op].value == OP_TRIGGER_SUBMODEL_ANIMATION) && (i == 3) ) { item->set_data("1", (SEXPT_NUMBER | SEXPT_VALID)); } else if ( (Operators[op].value == OP_EXPLOSION_EFFECT) ) { int temp; char sexp_str_token[TOKEN_LENGTH]; switch (i) { case 3: temp = 10; break; case 4: temp = 10; break; case 5: temp = 100; break; case 6: temp = 10; break; case 7: temp = 100; break; case 11: temp = (int)EMP_DEFAULT_INTENSITY; break; case 12: temp = (int)EMP_DEFAULT_TIME; break; default: temp = 0; break; } // 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; 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("", (SEXPT_STRING | SEXPT_VALID)); } } else if ((Operators[op].value == OP_SET_VARIABLE_BY_INDEX)) { if (i == 0) { item->set_data("0", (SEXPT_NUMBER | SEXPT_VALID)); } else { item->set_data("", (SEXPT_STRING | SEXPT_VALID)); } } else { item->set_data("0", (SEXPT_NUMBER | SEXPT_VALID)); } return 0; // Goober5000 - special cases that used to be numbers but are now hybrids case OPF_GAME_SND: int sound_index = -1; if ( (Operators[op].value == OP_EXPLOSION_EFFECT) ) { sound_index = SND_SHIP_EXPLODE_1; } else if ( (Operators[op].value == OP_WARP_EFFECT) ) { sound_index = (i == 8) ? SND_CAPITAL_WARP_IN : SND_CAPITAL_WARP_OUT; } if (sound_index >= 0) { game_snd *snd = &Snds[sound_index]; if (can_construe_as_integer(snd->name.c_str())) item->set_data(snd->name.c_str(), (SEXPT_NUMBER | SEXPT_VALID)); else item->set_data(snd->name.c_str(), (SEXPT_STRING | SEXPT_VALID)); return 0; } // if no hardcoded default, just use the listing default break; } 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((void *) first_ptr->text); delete first_ptr; } if (list) { // copy the information from the list to the passed-in item *item = *list; // but use the provided text buffer strcpy(text_buf, list->text); item->text = text_buf; // get rid of the list, since we're done with it list->destroy(); item->next = NULL; return 0; } // catch anything that doesn't have a default value. Just describe what should be here instead switch (type) { case OPF_SHIP: case OPF_SHIP_NOT_PLAYER: case OPF_SHIP_POINT: case OPF_SHIP_WING: case OPF_SHIP_WING_WHOLETEAM: case OPF_SHIP_WING_SHIPONTEAM_POINT: case OPF_SHIP_WING_POINT: str = ""; break; case OPF_ORDER_RECIPIENT: str = ""; break; case OPF_SHIP_OR_NONE: case OPF_SUBSYSTEM_OR_NONE: case OPF_SHIP_WING_POINT_OR_NONE: str = SEXP_NONE_STRING; break; case OPF_WING: str = ""; break; case OPF_DOCKER_POINT: str = ""; break; case OPF_DOCKEE_POINT: str = ""; break; case OPF_SUBSYSTEM: case OPF_AWACS_SUBSYSTEM: case OPF_ROTATING_SUBSYSTEM: case OPF_SUBSYS_OR_GENERIC: str = ""; break; case OPF_SUBSYSTEM_TYPE: str = Subsystem_types[SUBSYSTEM_NONE]; break; case OPF_POINT: str = ""; break; case OPF_MESSAGE: str = ""; break; case OPF_WHO_FROM: //str = ""; str = ""; break; case OPF_WAYPOINT_PATH: str = ""; break; case OPF_MISSION_NAME: str = ""; break; case OPF_GOAL_NAME: str = ""; break; case OPF_SHIP_TYPE: str = ""; break; case OPF_EVENT_NAME: str = ""; break; case OPF_HUGE_WEAPON: str = ""; break; case OPF_JUMP_NODE_NAME: str = ""; break; case OPF_NAV_POINT: str = "