Author Topic: SEXP Collection Classes (Very Alpha!)  (Read 2908 times)

0 Members and 1 Guest are viewing this topic.

Offline karajorma

  • King Louie - Jungle VIP
  • Administrator
  • 214
    • Karajorma's Freespace FAQ
SEXP Collection Classes (Very Alpha!)
I've been going on for years about doing this but I've only recently had the time to give this a try. This is a very, very alpha build of FS2 with support for collections. What does that mean? Well basically this build allows the FREDder to define a list in much the same way they can currently define a variable. So for instance you can have this

Code: [Select]
$Name: ShipsList
$Data: ( "Beta 1" "Beta 2" "Beta 3")

You can then do this

Do something To Beta
when
-Whatever Trigger
-Do something to
--Whichever ship is the first is on the list
-Do something else to
--Whichever ship is the first is on the list
-Remove the first ship from the list
-Add Gamma 4 to the list

Yes you can do this with arguments, but unlike arguments you aren't limited to one list at a time, there is no reason you can't do this

when
-Whatever
-Repair-ship
--First ship on Ships's List
-Send Message
--First ship on Ships's List
--High
--First message on SayThankYouMessages (and remove it)
-Add Ship to Ships List
-Repair Ship
--Last Ship on Ships List
-Send Message
--Third ship on Ships's List
--High
--First Message on SayThankYouMessages (but don't remove it)

I'm including a test build and a mission to show how the feature works. There's currently no support for FRED (although I've included a build which can understand the syntax). You'll need to do everything in notepad for now.

Basically you define a list immediately following the SEXP Variables section like this

Code: [Select]

#Sexp_containers

$Lists

$Name: SomeShipsList
$Data Type: String
$Data: ( "Beta 1" "Beta 2" "Beta 3" )

Then in the SEXP section you call the list like this

Code: [Select]
( send-message
      &SomeShipsList& 16
      "High"
      &MessageList& 128
)

The numbers following the list name tell the SEXP how to use the list. The ones that work at the moment are

8 - Get the first entry from the list
16 - Get the last entry from the list
64 - Get the first entry from the list and then remove it from the list
128 - Get the last entry from the list and then remove it from the list

I've also added preliminary support for 512 in action operators. 512 will make the SEXP do this action repeatedly for every ship in the list. So self-destruct &ShipsList& 512 will make all the ships on the list self-destruct. don't try using 512 in anything other than an action operator, I suspect you'll crash the game!

You can find the build here if you want to play with it.
Here's the code.

Code: [Select]
Index: code/globalincs/vmallocator.h
===================================================================
--- code/globalincs/vmallocator.h (revision 11070)
+++ code/globalincs/vmallocator.h (working copy)
@@ -112,7 +112,12 @@
 template< typename T >
 class SCP_vector : public std::vector< T, SCP_vm_allocator< T > > { };
 
+/*
 template< typename T >
+class SCP_deque : public std::deque< T, SCP_vm_allocator< T > > { };
+*/
+
+template< typename T >
 class SCP_list : public std::list< T, SCP_vm_allocator< T > > { };
 
 typedef std::basic_string<char, std::char_traits<char>, SCP_vm_allocator<char> > SCP_string;
Index: code/mission/missionparse.cpp
===================================================================
--- code/mission/missionparse.cpp (revision 11070)
+++ code/mission/missionparse.cpp (working copy)
@@ -5396,6 +5396,24 @@
  }
 }
 
+void parse_list_collections()
+{
+ if (! optional_string("#Sexp_containers") ) {
+ return;
+ }
+ else {
+ if (optional_string("$Lists")) {
+ stuff_sexp_list_container();
+ required_string("$End Lists");
+ }
+
+ if (optional_string("$Maps")) {
+ stuff_sexp_map_container();
+ required_string("$End Maps");
+ }
+ }
+}
+
 void parse_variables()
 {
  int i, j, num_variables = 0;
@@ -5498,6 +5516,7 @@
 
  parse_plot_info(pm);
  parse_variables();
+ parse_list_collections();
  parse_briefing_info(pm); // TODO: obsolete code, keeping so we don't obsolete existing mission files
  parse_cutscenes(pm);
  parse_fiction(pm);
Index: code/parse/sexp.cpp
===================================================================
--- code/parse/sexp.cpp (revision 11070)
+++ code/parse/sexp.cpp (working copy)
@@ -677,6 +677,9 @@
  { "string-get-substring", OP_STRING_GET_SUBSTRING, 4, 4, SEXP_ACTION_OPERATOR, }, // Goober5000
  { "string-set-substring", OP_STRING_SET_SUBSTRING, 5, 5, SEXP_ACTION_OPERATOR, }, // Goober5000 
 
+ //Variable Category
+ { "add-to-list", OP_COLLECTION_ADD_TO_LIST, 2, 2, SEXP_ACTION_OPERATOR, }, // Karajorma
+
  //Other Sub-Category
  { "damaged-escort-priority", OP_DAMAGED_ESCORT_LIST, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, //phreak
  { "damaged-escort-priority-all", OP_DAMAGED_ESCORT_LIST_ALL, 1, MAX_COMPLETE_ESCORT_LIST, SEXP_ACTION_OPERATOR, }, // Goober5000
@@ -806,6 +809,7 @@
  "LAG GUAGE"
 };
 
+ SCP_vector<SCP_string> Temp_CTEXT_strings;
 
 void sexp_set_skybox_model_preload(char *name); // taylor
 int Num_skybox_flags = 6;
@@ -841,6 +845,9 @@
 sexp_variable Sexp_variables[MAX_SEXP_VARIABLES];
 sexp_variable Block_variables[MAX_SEXP_VARIABLES]; // used for compatibility with retail.
 
+SCP_vector<sexp_map_container> Sexp_map_containers;
+SCP_vector<sexp_list_container> Sexp_list_containers;
+
 int Num_special_expl_blocks;
 
 SCP_vector<int> Current_sexp_operator;
@@ -885,8 +892,8 @@
 void build_extended_sexp_string(SCP_string &accumulator, int cur_node, int level, int mode);
 void update_sexp_references(const char *old_name, const char *new_name, int format, int node);
 int sexp_determine_team(char *subj);
-int extract_sexp_variable_index(int node);
 void init_sexp_vars();
+void init_sexp_collections();
 int eval_num(int node);
 
 // for handling variables
@@ -897,6 +904,9 @@
 void sexp_copy_variable_from_index(int node);
 void sexp_copy_variable_between_indexes(int node);
 
+// for handling collections
+void sexp_add_to_list(int node);
+
 SCP_vector<char*> Sexp_replacement_arguments;
 int Sexp_current_argument_nesting_level;
 SCP_vector<char*> Applicable_arguments_temp;
@@ -1099,6 +1109,7 @@
 
  sexp_nodes_init();
  init_sexp_vars();
+ init_sexp_collections();
  Locked_sexp_false = Locked_sexp_true = -1;
 
  Locked_sexp_false = alloc_sexp("false", SEXP_LIST, SEXP_ATOM_OPERATOR, -1, -1);
@@ -1162,6 +1173,8 @@
  Sexp_nodes[node].value = SEXP_UNKNOWN;
  Sexp_nodes[node].flags = SNF_DEFAULT_VALUE; // Goober5000
  Sexp_nodes[node].op_index = NO_OPERATOR_INDEX_DEFINED;
+ Sexp_nodes[node].iteration = -1;
+ Sexp_nodes[node].safe_to_update_iterations = true;
 
  return node;
 }
@@ -1345,6 +1358,17 @@
  flush_sexp_tree(Sexp_nodes[node].rest);
 }
 
+void sexp_tree_reset_iterations(int node)
+{
+ if (node < 0){
+ return;
+ }
+
+ Sexp_nodes[node].iteration = -1;
+ sexp_tree_reset_iterations(Sexp_nodes[node].first);
+ sexp_tree_reset_iterations(Sexp_nodes[node].rest);
+}
+
 int verify_sexp_tree(int node)
 {
  if (node == -1){
@@ -1709,6 +1733,13 @@
  if (bad_node)
  *bad_node = node;
 
+ if (Sexp_nodes[node].subtype & SEXP_ATOM_CONTAINER)  {
+ // Ignore containers for now.
+ node = Sexp_nodes[node].rest;
+ argnum++;
+ continue;
+ }
+
  if (Sexp_nodes[node].subtype == SEXP_ATOM_LIST) {
  i = Sexp_nodes[node].first;
  if (bad_node)
@@ -1720,49 +1751,51 @@
  if (Sexp_nodes[i].subtype == SEXP_ATOM_LIST)
  return 0;
 
- op2 = get_operator_index(CTEXT(i));
- if (op2 == -1)
- return SEXP_CHECK_UNKNOWN_OP;
+ else {
+ op2 = get_operator_index(CTEXT(i));
+ if (op2 == -1)
+ return SEXP_CHECK_UNKNOWN_OP;
 
- type2 = query_operator_return_type(op2);
- if (recursive) {
- switch (type) {
- case OPF_NUMBER:
- t = OPR_NUMBER;
- break;
+ type2 = query_operator_return_type(op2);
+ if (recursive) {
+ switch (type) {
+ case OPF_NUMBER:
+ t = OPR_NUMBER;
+ break;
 
- case OPF_POSITIVE:
- t = OPR_POSITIVE;
- break;
+ case OPF_POSITIVE:
+ t = OPR_POSITIVE;
+ break;
 
- case OPF_BOOL:
- t = OPR_BOOL;
- break;
+ case OPF_BOOL:
+ t = OPR_BOOL;
+ break;
 
- case OPF_NULL:
- t = OPR_NULL;
- break;
+ case OPF_NULL:
+ t = OPR_NULL;
+ break;
 
- // Goober5000
- case OPF_FLEXIBLE_ARGUMENT:
- t = OPR_FLEXIBLE_ARGUMENT;
- break;
+ // Goober5000
+ case OPF_FLEXIBLE_ARGUMENT:
+ t = OPR_FLEXIBLE_ARGUMENT;
+ break;
 
- case OPF_AI_GOAL:
- t = OPR_AI_GOAL;
- break;
+ case OPF_AI_GOAL:
+ t = OPR_AI_GOAL;
+ break;
 
- // special case for modify-variable
- case OPF_AMBIGUOUS:
- t = OPR_AMBIGUOUS;
- break;
+ // special case for modify-variable
+ case OPF_AMBIGUOUS:
+ t = OPR_AMBIGUOUS;
+ break;
 
- default:
- return SEXP_CHECK_UNKNOWN_TYPE;  // no other return types available
- }
+ default:
+ return SEXP_CHECK_UNKNOWN_TYPE;  // no other return types available
+ }
 
- if ((z = check_sexp_syntax(i, t, recursive, bad_node)) != 0) {
- return z;
+ if ((z = check_sexp_syntax(i, t, recursive, bad_node)) != 0) {
+ return z;
+ }
  }
  }
 
@@ -3115,6 +3148,10 @@
  return SEXP_CHECK_INVALID_SHIP_EFFECT;
  }
  break;
+
+ case OPF_LIST_CONTAINER_NAME:
+ // TO DO : Add error code here!
+ break;
 
 
  default:
@@ -3292,6 +3329,25 @@
 
  }
 
+ // container
+ else if (*Mp == SEXP_CONTAINER_CHAR) {
+ Mp++;
+
+ char container[TOKEN_LENGTH];
+ stuff_string(container, F_NAME, TOKEN_LENGTH, "&");
+
+ // bump past closing '&'
+ Mp += 2;
+
+ //read the container function flag
+ int flags;
+ stuff_int(&flags);
+
+ node = alloc_sexp(container, (SEXP_ATOM | SEXP_FLAG_CONTAINER), SEXP_ATOM_CONTAINER, -1, -1);
+ Sexp_nodes[node].flags |= flags;
+ ignore_white_space();
+ }
+
  // Sexp operator or number
  else {
  int len = 0;
@@ -3495,8 +3551,115 @@
  return start;
 }
 
+bool stuff_one_generic_sexp_container(SCP_string &name, int &type, SCP_vector<SCP_string> &data)
+{
+ SCP_string temp_type_string;
+ data.clear();
 
+ required_string("$Name:");
+ stuff_string(name, F_NAME);
+
+ required_string("$Data Type:");
+ stuff_string(temp_type_string, F_NAME);
+ if (!strcmp(temp_type_string.c_str(), "Number")) {
+ type |= SEXP_CONTAINER_NUMBER_DATA;
+ }
+ else if (!strcmp(temp_type_string.c_str(), "String")) {
+ type |= SEXP_CONTAINER_STRING_DATA;
+ }
+ else {
+ Warning(LOCATION, "Unknown SEXP Container type %s found", temp_type_string.c_str());
+ log_printf(LOGFILE_EVENT_LOG, "Unknown SEXP Container type %s found", temp_type_string.c_str());
+ return false;
+ }
+
+ if (optional_string("$Key Type:")) {
+ Assertion ((type & SEXP_CONTAINER_MAP), "$Key Type: found for container which doesn't use keys!"); 
+
+ stuff_string(temp_type_string, F_NAME);
+ if (!strcmp(temp_type_string.c_str(), "Number")) {
+ type |= SEXP_CONTAINER_NUMBER_KEYS;
+ }
+ else if (!strcmp(temp_type_string.c_str(), "String")) {
+ type |= SEXP_CONTAINER_STRING_KEYS;
+ }
+ else {
+ Warning(LOCATION, "Unknown SEXP Container type %s found", temp_type_string.c_str());
+ log_printf(LOGFILE_EVENT_LOG, "Unknown SEXP Container type %s found", temp_type_string.c_str());
+ return false;
+ }
+ }
+
+ if (optional_string("+Strongly Typed Keys")) {
+ Assertion ((type & SEXP_CONTAINER_MAP), "+Strongly Typed Keys found for container which doesn't use keys!"); 
+ type |= SEXP_CONTAINER_STRONGLY_TYPED_KEYS;
+ }
+
+ if (optional_string("+Strongly Typed Data")) {
+ type |= SEXP_CONTAINER_STRONGLY_TYPED_DATA;
+ }
+
+ required_string("$Data:");
+ stuff_string_list(data);
+
+ return true;
+}
+
 /**
+ * Stuffs a sexp list type container
+ */
+void stuff_sexp_list_container()
+{
+ sexp_list_container temp_list;
+ SCP_vector<SCP_string> parsed_data;
+
+
+ while (required_string_either("$End Lists", "$Name:")) {
+ temp_list.type = SEXP_CONTAINER_LIST;
+ if (stuff_one_generic_sexp_container(temp_list.list_name, temp_list.type, parsed_data)){
+ temp_list.data.clear();
+ for (int i = 0; i < (int)parsed_data.size(); i++) {
+ temp_list.data.push_back(parsed_data[i]);
+ }
+ }
+
+ Sexp_list_containers.push_back(temp_list);
+ }
+}
+
+/**
+ * Stuffs a sexp map type container
+ */
+void stuff_sexp_map_container()
+{
+ sexp_map_container temp_map;
+ sexp_list_container temp_list;
+ SCP_vector<SCP_string> parsed_data;
+
+
+ while (required_string_either("$End Maps", "$Name:")) {
+ temp_map.type = SEXP_CONTAINER_MAP;
+ if (stuff_one_generic_sexp_container(temp_map.list_name, temp_map.type, parsed_data)){
+
+ // if the data is corrupt, discard it
+ if (parsed_data.size() % 2 != 0) {
+ Warning(LOCATION, "Data in the SEXP Map container is corrupt. Must be an even number of entries. Instead have %d", parsed_data.size() );
+ log_printf(LOGFILE_EVENT_LOG, "Data in the SEXP Map container is corrupt. Must be an even number of entries. Instead have %d", parsed_data.size() );
+ continue;
+ }
+
+ // move the data into the new entry
+ temp_map.data.clear();
+ for (int i = 0; i < (int)parsed_data.size(); i = i+2) {
+ temp_map.data.insert(std::pair<SCP_string, SCP_string>(parsed_data[i], parsed_data[i+1]));
+ }
+ }
+
+ Sexp_map_containers.push_back(temp_map);
+ }
+}
+
+/**
  * Stuffs a list of sexp variables
  */
 int stuff_sexp_variable_list()
@@ -22314,7 +22477,7 @@
 
  } else {
  int op_num;
-
+
  node = CDR(cur_node); // makes reading the next bit of code a little easier.
 
  op_num = get_operator_const(cur_node);
@@ -22604,6 +22767,11 @@
  sexp_val = SEXP_TRUE;
  break;
 
+ case OP_COLLECTION_ADD_TO_LIST:
+ sexp_add_to_list(node);
+ sexp_val = SEXP_TRUE;
+ break;
+
  case OP_TIME_SHIP_DESTROYED:
  sexp_val = sexp_time_destroyed(node);
  break;
@@ -24468,7 +24636,23 @@
  if (Log_event) {
  add_to_event_log_buffer(get_operator_index(cur_node), sexp_val);
  }
+
+ // since we're leaving the SEXP, we can update the number of iterations
+ Sexp_nodes[cur_node].safe_to_update_iterations = true;
 
+ // if this sexp has any containers, we might need to recurse so that we can deal with iterations
+ if (Sexp_nodes[cur_node].iteration != -1) {
+ // we've just run the last iteration. No need to recurse any further
+ if (Sexp_nodes[cur_node].flags & SNF_CONTAINER_FINAL_ITERATION) {
+ Sexp_nodes[cur_node].flags &= ~SNF_CONTAINER_FINAL_ITERATION;
+ sexp_tree_reset_iterations(cur_node);
+ }
+ // recurse
+ else {
+ eval_sexp(cur_node, referenced_node);
+ }
+ }
+
  Assert(!Current_sexp_operator.empty());
  Current_sexp_operator.pop_back();
 
@@ -25455,6 +25639,7 @@
  case OP_NEBULA_CHANGE_PATTERN:
  case OP_COPY_VARIABLE_FROM_INDEX:
  case OP_COPY_VARIABLE_BETWEEN_INDEXES:
+ case OP_COLLECTION_ADD_TO_LIST:
  case OP_SET_ETS_VALUES:
  case OP_CALL_SSM_STRIKE:
  case OP_SET_MOTION_DEBRIS:
@@ -25971,6 +26156,14 @@
  case OP_GET_VARIABLE_BY_INDEX:
  case OP_COPY_VARIABLE_BETWEEN_INDEXES:
  return OPF_POSITIVE;
+
+ case OP_COLLECTION_ADD_TO_LIST:
+ if (argnum == 0) {
+ return OPF_LIST_CONTAINER_NAME;
+ }
+ else {
+ return OPF_AMBIGUOUS;
+ }
 
  case OP_COPY_VARIABLE_FROM_INDEX:
  if (argnum == 0) {
@@ -28048,36 +28241,116 @@
  return ai_query_goal_valid(ship_num, Sexp_ai_goal_links[i].ai_goal);
 }
 
-// Takes an Sexp_node.text pointing to a variable (of form "Sexp_variables[xx]=string" or "Sexp_variables[xx]=number")
-// and returns the index into the Sexp_variables array of the actual value
-int extract_sexp_variable_index(int node)
+char* deal_with_container(int n)
 {
- char *text = Sexp_nodes[node].text;
- char char_index[8];
- char *start_index;
- int variable_index;
+ if  (Sexp_nodes[n].type & SEXP_FLAG_LIST_CONTAINER) {
+ // the node flags will tell us what we're supposed to do with this container
+ int actual_flag = Sexp_nodes[n].flags & SNF_CONTAINER_MASK;
 
- // get past the '['
- start_index = text + 15;
- Assert(isdigit(*start_index));
+ int index = get_index_sexp_container_name(Sexp_nodes[n].text);
+ if (index < 0)  {
+ return "";
+ }
+
+ if (Sexp_list_containers[index].data.empty()) {
+ return "";
+ }
 
- int len = 0;
+ SCP_string temp_string;
+ int parent = -1;
+ int parent_node = -1;
+ int recurse_at_this_node = -1;
+ int op_type;
 
- while ( *start_index != ']' ) {
- char_index[len++] = *(start_index++);
- Assert(len < 3);
- }
+ switch (actual_flag) {
+ case SNF_CONTAINER_GET_FIRST:
+ temp_string.assign(Sexp_list_containers[index].data.front());
+ break;
 
- Assert(len > 0);
- char_index[len] = 0; // append null termination to string
+ case SNF_CONTAINER_GET_LAST:
+ temp_string.assign(Sexp_list_containers[index].data.back());
+ break;
 
- variable_index = atoi(char_index);
- Assert( (variable_index >= 0) && (variable_index < MAX_SEXP_VARIABLES) );
+ case SNF_CONTAINER_REMOVE_FIRST:
+ temp_string.assign(Sexp_list_containers[index].data.front());
+ Sexp_list_containers[index].data.pop_front();
+ break;
 
- return variable_index;
+ case SNF_CONTAINER_REMOVE_LAST:
+ temp_string.assign(Sexp_list_containers[index].data.back());
+ Sexp_list_containers[index].data.pop_back();
+ break;
+
+ case SNF_CONTAINER_ITERATE_FORWARDS:
+ // get the parent node
+ parent = find_parent_operator(n);
+ if (parent == -1) {
+ return "";
+ }
+ parent_node = parent;
+
+ // we need to find the point we're going to recurse from
+ while (true) {
+ op_type = Operators[get_operator_index(parent)].type;
+
+ // Action operators are suitable
+ if (op_type & SEXP_ACTION_OPERATOR) {
+ recurse_at_this_node = parent;
+ break;
+ }
+
+ // Conditional operators are also suitable
+ if (op_type & SEXP_CONDITIONAL_OPERATOR) {
+ recurse_at_this_node = parent;
+ break;
+ }
+
+ // anything else, back up further
+ parent = find_parent_operator(parent);
+ if (parent == -1) {
+ return "";
+ }
+ }
+
+ // does the parent node know we are iterating already?
+ if(Sexp_nodes[recurse_at_this_node].iteration == -1) {
+ Sexp_nodes[recurse_at_this_node].iteration = (int)Sexp_list_containers[index].data.size();
+ }
+
+ if (Sexp_nodes[n].iteration < (int)Sexp_list_containers[index].data.size()) {
+ if (Sexp_nodes[parent_node].safe_to_update_iterations == true) {
+ // since we're entering the eval code, it is no longer safe to update the number of iterations
+ Sexp_nodes[parent_node].safe_to_update_iterations = false;
+ Sexp_nodes[n].iteration++ ;  // we start at -1 so this needs to go up BEFORE we use it as an index!
+ }
+ temp_string.assign(Sexp_list_containers[index].data[Sexp_nodes[n].iteration]);
+ }
+ // Well, this was clumsy mission design! Someone has used two or more lists in the same SEXP and a later one is shorter than the other(s)!
+ else {
+ // just return the last entry and hope it's valid
+ temp_string.assign(Sexp_list_containers[index].data.back());
+ Warning(LOCATION, "Error - SEXP List %s does not contain %d members but has been asked to provide that number of entries", Sexp_list_containers[index].list_name.c_str(), Sexp_nodes[recurse_at_this_node].iteration);
+ log_printf(LOGFILE_EVENT_LOG, "Error - SEXP List %s does not contain %d members but has been asked to provide that number of entries", Sexp_list_containers[index].list_name.c_str(), Sexp_nodes[recurse_at_this_node].iteration);
+ }
+
+ // final iteration for this container
+ if (Sexp_nodes[n].iteration + 1 == (int)Sexp_list_containers[index].data.size()) {
+ Sexp_nodes[recurse_at_this_node].flags |= SNF_CONTAINER_FINAL_ITERATION;
+ }
+
+ break;
+
+ default:
+ return "";
+ }
+
+ Temp_CTEXT_strings.push_back(temp_string);
+ return const_cast<char*>(Temp_CTEXT_strings.back().c_str());
+ }
+
+ return "";
 }
 
-
 /**
  * Wrapper around Sexp_node[xx].text for normal and variable
  */
@@ -28152,8 +28425,18 @@
 
  return Sexp_variables[sexp_variable_index].text;
  }
- else
- {
+
+ // Karajorma - check we're not dealing with a list container
+ if (Sexp_nodes[n].type & SEXP_FLAG_LIST_CONTAINER) {
+ return deal_with_container(n);
+ }
+
+ // Karajorma - check we're not dealing with a map container
+ if (Sexp_nodes[n].type & SEXP_FLAG_MAP_CONTAINER) {
+ return "";
+ }
+
+ else {
  return Sexp_nodes[n].text;
  }
 }
@@ -28170,6 +28453,12 @@
  }
 }
 
+void init_sexp_collections()
+{
+ Sexp_map_containers.clear();
+ Sexp_list_containers.clear();
+}
+
 /**
  * Add a variable to the block variable array rather than the Sexp_variables array
  */
@@ -28843,6 +29132,39 @@
 }
 
 /**
+ * Return index of sexp_container by its name, -1 if not found
+ */
+int get_index_sexp_container_name(const char *text)
+{
+ for (int i = 0; i < (int)Sexp_list_containers.size() ; i++) {
+ if ( !stricmp(Sexp_list_containers[i].list_name.c_str(), text) ) {
+ return i;
+ }
+ }
+
+ // not found
+ return -1;
+}
+
+/**
+ * Add an entry to a SEXP collection.
+ */
+void sexp_add_to_list (int node)
+{
+ int list_index = get_index_sexp_container_name(CTEXT(node));
+
+ if (list_index < 0) {
+ Warning(LOCATION, "Collection %s does not exist.", CTEXT(node));
+ }
+
+ node = CDR(node);
+
+ // TO DO - ADD TYPE CHECKING CODE HERE!
+
+ Sexp_list_containers[list_index].data.push_back(CTEXT(node));
+}
+
+/**
  * Evaluate number which may result from an operator or may be text
  */
 int eval_num(int n)
@@ -29235,6 +29557,9 @@
  case OP_STRING_SET_SUBSTRING:
  return CHANGE_SUBCATEGORY_VARIABLES;
 
+ case OP_COLLECTION_ADD_TO_LIST:
+ return CHANGE_SUBCATEGORY_COLLECTIONS;
+
  case OP_DAMAGED_ESCORT_LIST:
  case OP_DAMAGED_ESCORT_LIST_ALL:
  case OP_SET_SUPPORT_SHIP:
@@ -30325,6 +30650,12 @@
  "\t1:\tIndex of source variable, from 0 to MAX_SEXP_VARIABLES - 1.\r\n"
  "\t2:\tIndex of destination variable, from 0 to MAX_SEXP_VARIABLES - 1.  The types of both variables must match." },
 
+ { OP_COLLECTION_ADD_TO_LIST, "add-to-list\r\n"
+ "\tAdds a new entry to a SEXP collection.\r\n\r\n"
+ "Takes 2 arguments...\r\n"
+ "\t1:\tName of the list.\r\n"
+ "\t2:\tThe data to be added. In strongly typed collections, this type must match the type of data of the class" },
+
  { OP_PROTECT_SHIP, "Protect ship (Action operator)\r\n"
  "\tProtects a ship from being attacked by any enemy ship.  Any ship "
  "that is protected will not come under enemy fire.\r\n\r\n"
@@ -33036,6 +33367,7 @@
  { "Jump Nodes", CHANGE_SUBCATEGORY_JUMP_NODES },
  { "Special Effects", CHANGE_SUBCATEGORY_SPECIAL_EFFECTS },
  { "Variables", CHANGE_SUBCATEGORY_VARIABLES },
+ { "Collections", CHANGE_SUBCATEGORY_COLLECTIONS },
  { "Other", CHANGE_SUBCATEGORY_OTHER },
  { "Mission", STATUS_SUBCATEGORY_MISSION },
  { "Player", STATUS_SUBCATEGORY_PLAYER },
Index: code/parse/sexp.h
===================================================================
--- code/parse/sexp.h (revision 11070)
+++ code/parse/sexp.h (working copy)
@@ -112,6 +112,8 @@
 #define OPF_TEAM_COLOR 85 // The E - Color settings as defined in Colors.tbl
 #define OPF_NEBULA_PATTERN 86 // Axem - Full Nebula Background Patterns, as defined in nebula.tbl
 #define OPF_SKYBOX_FLAGS 87 // niffiwan - valid skybox flags
+#define OPF_LIST_CONTAINER_NAME 88 // Karajorma - The name of a list container class
+#define OPF_MAP_CONTAINER_NAME 89 // Karajorma - The name of a map container class
 
 // Operand return types
 #define OPR_NUMBER 1 // returns number
@@ -197,7 +199,8 @@
 #define CHANGE_SUBCATEGORY_JUMP_NODES (0x0010 | OP_CATEGORY_CHANGE)
 #define CHANGE_SUBCATEGORY_SPECIAL_EFFECTS (0x0011 | OP_CATEGORY_CHANGE)
 #define CHANGE_SUBCATEGORY_VARIABLES (0x0012 | OP_CATEGORY_CHANGE)
-#define CHANGE_SUBCATEGORY_OTHER (0x0013 | OP_CATEGORY_CHANGE)
+#define CHANGE_SUBCATEGORY_COLLECTIONS (0x0013 | OP_CATEGORY_CHANGE)
+#define CHANGE_SUBCATEGORY_OTHER (0x0014 | OP_CATEGORY_CHANGE)
 
 
 #define STATUS_SUBCATEGORY_MISSION (0x0000 | OP_CATEGORY_STATUS)
@@ -724,6 +727,7 @@
 #define OP_HUD_SET_CUSTOM_GAUGE_ACTIVE (0x0026 | OP_CATEGORY_CHANGE2 | OP_NONCAMPAIGN_FLAG) // The E, just revamped a bit by Axem
 #define OP_HUD_SET_RETAIL_GAUGE_ACTIVE (0x0027 | OP_CATEGORY_CHANGE2 | OP_NONCAMPAIGN_FLAG) // The E, just revamped a bit by Axem
 #define OP_SCRIPT_EVAL_MULTI (0x0028 | OP_CATEGORY_CHANGE2 | OP_NONCAMPAIGN_FLAG) // Karajorma
+#define OP_COLLECTION_ADD_TO_LIST (0x0029 | OP_CATEGORY_CHANGE2 | OP_NONCAMPAIGN_FLAG) // Karajorma
 
 // defined for AI goals
 #define OP_AI_CHASE (0x0000 | OP_CATEGORY_AI | OP_NONCAMPAIGN_FLAG)
@@ -858,7 +862,12 @@
 // flags for sexpressions -- masked onto the end of the type field
 #define SEXP_FLAG_PERSISTENT (1<<31) // should this sexp node be persistant across missions
 #define SEXP_FLAG_VARIABLE (1<<30)
+#define SEXP_FLAG_LIST_CONTAINER (1<<29)
+#define SEXP_FLAG_MAP_CONTAINER (1<<28)
 
+
+#define SEXP_FLAG_CONTAINER (SEXP_FLAG_LIST_CONTAINER | SEXP_FLAG_MAP_CONTAINER)
+
 // sexp variable definitions
 #define SEXP_VARIABLE_CHAR ('@')
 // defines for type field of sexp_variable.  Be sure not to conflict with type field of sexp_node
@@ -879,6 +888,16 @@
 //Karajorma
 #define SEXP_VARIABLE_NETWORK (1<<28)
 
+#define SEXP_CONTAINER_CHAR ('&')
+#define SEXP_CONTAINER_LIST (1<<0)
+#define SEXP_CONTAINER_MAP (1<<1)
+#define SEXP_CONTAINER_NUMBER_DATA (1<<2)
+#define SEXP_CONTAINER_STRING_DATA (1<<3)
+#define SEXP_CONTAINER_NUMBER_KEYS (1<<4)
+#define SEXP_CONTAINER_STRING_KEYS (1<<5)
+#define SEXP_CONTAINER_STRONGLY_TYPED_KEYS (1<<6)
+#define SEXP_CONTAINER_STRONGLY_TYPED_DATA (1<<7)
+
 #define BLOCK_EXP_SIZE 6
 #define INNER_RAD 0
 #define OUTER_RAD 1
@@ -903,6 +922,7 @@
 #define SEXP_ATOM_OPERATOR 1
 #define SEXP_ATOM_NUMBER 2
 #define SEXP_ATOM_STRING 3
+#define SEXP_ATOM_CONTAINER 4
 
 // defines to short circuit evaluation when possible. Also used when goals can't
 // be satisfied yet because ship (or wing) hasn't been created yet.
@@ -1022,12 +1042,28 @@
  int rest; // index into Sexp_nodes of rest of parameters
  int value; // known to be true, known to be false, or not known
  int flags; // Goober5000
+ int iteration;
+ bool safe_to_update_iterations;
 } sexp_node;
 
 // Goober5000
-#define SNF_ARGUMENT_VALID (1<<0)
-#define SNF_ARGUMENT_SELECT (1<<1)
+#define SNF_ARGUMENT_VALID (1<<0)
+#define SNF_ARGUMENT_SELECT (1<<1)
+// Karajorma - If a container flag is added, SNF_CONTAINER_MASK should be updated to include it.
+#define SNF_CONTAINER_GET_DATA (1<<2)
+#define SNF_CONTAINER_GET_FIRST (1<<3)
+#define SNF_CONTAINER_GET_LAST (1<<4)
+#define SNF_CONTAINER_ADD_BACK (1<<5)
+#define SNF_CONTAINER_REMOVE_FIRST (1<<6)
+#define SNF_CONTAINER_REMOVE_LAST (1<<7)
+#define SNF_CONTAINER_GET_SIZE (1<<8)
+#define SNF_CONTAINER_ITERATE_FORWARDS (1<<9)
+#define SNF_CONTAINER_ITERATE_BACKWARDS (1<<10)
+
+#define SNF_CONTAINER_FINAL_ITERATION (1<<31)
+
 #define SNF_DEFAULT_VALUE SNF_ARGUMENT_VALID
+#define SNF_CONTAINER_MASK ( SNF_CONTAINER_GET_DATA | SNF_CONTAINER_GET_FIRST | SNF_CONTAINER_GET_LAST | SNF_CONTAINER_ADD_BACK | SNF_CONTAINER_REMOVE_FIRST | SNF_CONTAINER_REMOVE_LAST | SNF_CONTAINER_GET_SIZE | SNF_CONTAINER_ITERATE_FORWARDS | SNF_CONTAINER_ITERATE_BACKWARDS)
 
 typedef struct sexp_variable {
  int type;
@@ -1035,7 +1071,19 @@
  char variable_name[TOKEN_LENGTH];
 } sexp_variable;
 
+typedef struct sexp_list_container {
+ SCP_deque<SCP_string> data;
+ SCP_string list_name;
+ int type;
+}sexp_list_container;
 
+typedef struct sexp_map_container {
+ SCP_hash_map<SCP_string, SCP_string> data;
+ SCP_string list_name;
+ int type;
+}sexp_map_container;
+
+
 #define ARG_ITEM_F_DUP (1<<0)
 
 // Goober5000 - adapted from sexp_list_item in Sexp_tree.h
@@ -1071,6 +1119,9 @@
 extern sexp_variable Sexp_variables[MAX_SEXP_VARIABLES];
 extern sexp_variable Block_variables[MAX_SEXP_VARIABLES];
 
+extern SCP_vector<sexp_map_container> Sexp_map_containers;
+extern SCP_vector<sexp_list_container> Sexp_list_containers;
+
 extern sexp_oper Operators[];
 extern int Num_operators;
 extern int Locked_sexp_true, Locked_sexp_false;
@@ -1120,6 +1171,8 @@
 extern int get_sexp_main(void); // Returns start node
 extern int run_sexp(const char* sexpression); // debug and lua sexps
 extern int stuff_sexp_variable_list();
+extern void stuff_sexp_list_container();
+extern void stuff_sexp_map_container();
 extern int eval_sexp(int cur_node, int referenced_node = -1);
 extern int is_sexp_true(int cur_node, int referenced_node = -1);
 extern int query_operator_return_type(int op);
@@ -1169,6 +1222,9 @@
 int num_block_variables();
 bool has_special_explosion_block_index(ship *shipp, int *index);
 
+// sexp_container
+int get_index_sexp_container_name(const char *text);
+
 // Karajorma
 void set_primary_ammo (int ship_index, int requested_bank, int requested_ammo, int rearm_limit=-1, bool update=true);
 void set_secondary_ammo (int ship_index, int requested_bank, int requested_ammo, int rearm_limit=-1, bool update=true);

Apologies for the mess, I haven't tidied it up in the slightest!

I'd like some feedback on the idea before I go forwards, but so far I have planned to add the following soonish

- The ability to get the element at an index in the array, not just the start and end (including one at random).
- A SEXP for removing elements

- Support for maps as well as lists is on the drawing board (as you can see the build will load them, it just can't use them).


[attachment kidnapped by pirates]
« Last Edit: September 08, 2014, 10:55:21 am by karajorma »
Karajorma's Freespace FAQ. It's almost like asking me yourself.

[ Diaspora ] - [ Seeds Of Rebellion ] - [ Mind Games ]

 

Offline AdmiralRalwood

  • 211
  • The Cthulhu programmer himself!
    • Skype
    • Steam
    • Twitter
Re: SEXP Collection Classes (Very Alpha!)
Whoa. [/neo]
Ph'nglui mglw'nafh Codethulhu GitHub wgah'nagl fhtagn.

schrödinbug (noun) - a bug that manifests itself in running software after a programmer notices that the code should never have worked in the first place.

When you gaze long into BMPMAN, BMPMAN also gazes into you.

"I am one of the best FREDders on Earth" -General Battuta

<Aesaar> literary criticism is vladimir putin

<MageKing17> "There's probably a reason the code is the way it is" is a very dangerous line of thought. :P
<MageKing17> Because the "reason" often turns out to be "nobody noticed it was wrong".
(the very next day)
<MageKing17> this ****ing code did it to me again
<MageKing17> "That doesn't really make sense to me, but I'll assume it was being done for a reason."
<MageKing17> **** ME
<MageKing17> THE REASON IS PEOPLE ARE STUPID
<MageKing17> ESPECIALLY ME

<MageKing17> God damn, I do not understand how this is breaking.
<MageKing17> Everything points to "this should work fine", and yet it's clearly not working.
<MjnMixael> 2 hours later... "God damn, how did this ever work at all?!"
(...)
<MageKing17> so
<MageKing17> more than two hours
<MageKing17> but once again we have reached the inevitable conclusion
<MageKing17> How did this code ever work in the first place!?

<@The_E> Welcome to OpenGL, where standards compliance is optional, and error reporting inconsistent

<MageKing17> It was all working perfectly until I actually tried it on an actual mission.

<IronWorks> I am useful for FSO stuff again. This is a red-letter day!
* z64555 erases "Thursday" and rewrites it in red ink

<MageKing17> TIL the entire homing code is held up by shoestrings and duct tape, basically.

 

Offline Cyborg17

  • 29
  • Life? Don't talk to me about life....
Re: SEXP Collection Classes (Very Alpha!)
Totally wish I could fiddle with and test this but I'm horrible in notepad.

 

Offline karajorma

  • King Louie - Jungle VIP
  • Administrator
  • 214
    • Karajorma's Freespace FAQ
Re: SEXP Collection Classes (Very Alpha!)
I tend to make the mission in FRED first and then alter the things I need to in notepad.

There is a test mission included in the download. :)
Karajorma's Freespace FAQ. It's almost like asking me yourself.

[ Diaspora ] - [ Seeds Of Rebellion ] - [ Mind Games ]

 

Offline karajorma

  • King Louie - Jungle VIP
  • Administrator
  • 214
    • Karajorma's Freespace FAQ
Re: SEXP Collection Classes (Very Alpha!)
Not entirely sure if I'm just talking to myself here but I've updated things hugely. All that strange stuff with numbers is gone and replaced with nice English strings.

This build now supports lists, maps and multidimentional mixtures of the two. What does that mean? Well suppose you had a mission where one of your wingmen is revealed to be a traitor. In order to give the mission replay value, you decide to make who is the traitor completely random.

Usually when this happens in a mission people decide to have the interaction with the traitor fairly limited to keep the FREDing simple. But now you can do this. First define a list of messages for Alpha 2.

Code: [Select]
$Name: Alpha2Messages
$Data Type: String
$Data: ( "Message 1" "Message 2" "Message 3"  ...... "Message 20" )

Create 2 more for Alpha 3 and 4.

Code: [Select]
$Name: Alpha3Messages
$Data Type: String
$Data: ( "Message 21" "Message 22" "Message 23"  ...... "Message 40" )

etc.....

Now create a map with the name of the wingman mapped to the message list.

Code: [Select]
$Name: PossibleTraitors
$Data Type: String
$Data: ( "Alpha 2" "&Alpha2Messages&" "Alpha 3" "&Alpha3Messages&" "Alpha 4" "&Alpha4Messages&" )


Now the mission virtually writes itself. First you pick a traitor at random and assign their name to a variable

Code: [Select]
when-argument
-random-of
--Alpha 2
--Alpha 3
--Alpha 4
-Whatever trigger
-modify-variable
--Traitor[]
--Argument

and then you use it whenever you want to send messages

Code: [Select]
when
-<
--hits-left
---Traitor[]
-send-message
--Traitor[]
--High
--&PossibleTraitors&   <------------- Go to the PossibleTraitors map
--Traitor[]       <----------------------- Search for the entry called Alpha 2, 3 or 4. Whatever is in the Traitor variable. This will return the &Alpha"x"Messages& list
--Get_Last     <------------------------ this command tells the game to get the last entry on that list.



Yes you can do this with arguments. Good luck though, it would be incredibly tedious. Using this feature will give you a lot of options. I'm only scratching at the surface here.

Anyway, enough of the hard sell on what it's for. Here's how it works. Add this after the #Sexp_variables section to set up your containers / collections (yes I need to pick a word and stick with it!).

Code: [Select]
#Sexp_collections

$Lists

$Name: MessageList
$Data Type: String
$Data: ( "Message 1" "Message 2" "Message 3" )

$End Lists


$Maps

$Name: WingmanNamesMap
$Data Type: String
$Data: ( "Wingman 7" "#Porthos" "Wingman 5" "#Athos" "Wingman 6" "#Aramis"  )

$End Maps

Sexps are fairly simple now too. For maps you simply replace the string you want with this

Code: [Select]
&map_name&   <-----------------------map name with a '&" before and after it
( "key' )


When making the map the odd numbered entries are keys, even numbers are the value. If you want a multidimentional map (e.g a map containing several lists), the value should simply be the name of another container.

For lists you do this

Code: [Select]
&list_name&   <-----------------------map name with a '&" before and after it
( "What to do to the list' )


The options you can use are

Code: [Select]
"Get_First",  "Get_Last", "Remove_First", "Remove_Last", "Get_Random"

The remove options return the value and then remove it from the list.

Anyway, here's the ugly, ugly code.

Code: [Select]
Index: code/mission/missionparse.cpp
===================================================================
--- code/mission/missionparse.cpp (revision 11115)
+++ code/mission/missionparse.cpp (working copy)
@@ -5453,6 +5453,25 @@
  }
 }
 
+void parse_list_collections()
+{
+ if (! optional_string("#Sexp_collections") ) {
+ return;
+ }
+ else {
+ if (optional_string("$Lists")) {
+ stuff_sexp_list_collection();
+ required_string("$End Lists");
+ }
+
+ if (optional_string("$Maps")) {
+ stuff_sexp_map_collection();
+ required_string("$End Maps");
+ }
+ }
+}
+
+
 int parse_mission(mission *pm, int flags)
 {
  int saved_warning_count = Global_warning_count;
@@ -5498,6 +5517,7 @@
 
  parse_plot_info(pm);
  parse_variables();
+ parse_list_collections();
  parse_briefing_info(pm); // TODO: obsolete code, keeping so we don't obsolete existing mission files
  parse_cutscenes(pm);
  parse_fiction(pm);
Index: code/parse/sexp.cpp
===================================================================
--- code/parse/sexp.cpp (revision 11115)
+++ code/parse/sexp.cpp (working copy)
@@ -841,6 +841,10 @@
 sexp_variable Sexp_variables[MAX_SEXP_VARIABLES];
 sexp_variable Block_variables[MAX_SEXP_VARIABLES]; // used for compatibility with retail.
 
+SCP_vector<sexp_collection> Sexp_collections;
+// SCP_string Temp_collection_string;
+SCP_vector<SCP_string> Temp_collection_strings;
+
 int Num_special_expl_blocks;
 
 SCP_vector<int> Current_sexp_operator;
@@ -887,6 +891,7 @@
 int sexp_determine_team(char *subj);
 int extract_sexp_variable_index(int node);
 void init_sexp_vars();
+void init_sexp_collections();
 int eval_num(int node);
 
 // for handling variables
@@ -1099,6 +1104,7 @@
 
  sexp_nodes_init();
  init_sexp_vars();
+ init_sexp_collections();
  Locked_sexp_false = Locked_sexp_true = -1;
 
  Locked_sexp_false = alloc_sexp("false", SEXP_LIST, SEXP_ATOM_OPERATOR, -1, -1);
@@ -1709,6 +1715,13 @@
  if (bad_node)
  *bad_node = node;
 
+ // for now we'll completely ignore SEXP containters
+ if (Sexp_nodes[node].subtype & SEXP_ATOM_COLLECTION)  {
+ node = Sexp_nodes[node].rest;
+ argnum++;
+ continue;
+ }
+
  if (Sexp_nodes[node].subtype == SEXP_ATOM_LIST) {
  i = Sexp_nodes[node].first;
  if (bad_node)
@@ -3133,6 +3146,11 @@
  }
  break;
 
+/* case OPF_LIST_COLLECTION_NAME:
+ // TO DO : Add error code here!
+ break;
+ */
+
  default:
  Error(LOCATION, "Unhandled argument format");
  }
@@ -3320,6 +3338,40 @@
 
  }
 
+ // collection
+ else if (*Mp == SEXP_COLLECTION_CHAR) {
+ Mp++;
+
+ char collection[TOKEN_LENGTH];
+ stuff_string(collection, F_NAME, TOKEN_LENGTH, "&");
+
+ // bump past closing '&'
+ Mp += 2;
+
+ // what kind of collection?
+ int collection_index = get_index_sexp_collection_name(collection);
+
+ if (collection_index == -1) {
+ Error(LOCATION, "Unknown collection type detected in mission");
+ }
+
+ // advance to the control options since we'll read them in using get_sexp() below
+ while ( *Mp != '(' ) {
+ Mp++;
+ }
+ Mp++ ;
+
+ if (Sexp_collections[collection_index].type & SEXP_COLLECTION_MAP) {
+ node = alloc_sexp(collection, (SEXP_ATOM | SEXP_FLAG_MAP_COLLECTION), SEXP_ATOM_COLLECTION, get_sexp(), -1);
+ }
+ else if ( Sexp_collections[collection_index].type & SEXP_COLLECTION_LIST) {
+ node = alloc_sexp(collection, (SEXP_ATOM | SEXP_FLAG_LIST_COLLECTION), SEXP_ATOM_COLLECTION, get_sexp(), -1);
+ }
+
+ ignore_white_space();
+ }
+
+
  // Sexp operator or number
  else {
  int len = 0;
@@ -3538,7 +3590,119 @@
 }
 
 
+bool stuff_one_generic_sexp_collection(SCP_string &name, int &type, SCP_vector<SCP_string> &data)
+{
+ SCP_string temp_type_string;
+ data.clear();
+
+ required_string("$Name:");
+ stuff_string(name, F_NAME);
+
+ required_string("$Data Type:");
+ stuff_string(temp_type_string, F_NAME);
+ if (!strcmp(temp_type_string.c_str(), "Number")) {
+ type |= SEXP_COLLECTION_NUMBER_DATA;
+ }
+ else if (!strcmp(temp_type_string.c_str(), "String")) {
+ type |= SEXP_COLLECTION_STRING_DATA;
+ }
+ else {
+ Warning(LOCATION, "Unknown SEXP Collection type %s found", temp_type_string.c_str());
+ log_printf(LOGFILE_EVENT_LOG, "Unknown SEXP Collection type %s found", temp_type_string.c_str());
+ return false;
+ }
+
+ if (optional_string("$Key Type:")) {
+ Assertion ((type & SEXP_COLLECTION_MAP), "$Key Type: found for collection which doesn't use keys!"); 
+
+ stuff_string(temp_type_string, F_NAME);
+ if (!strcmp(temp_type_string.c_str(), "Number")) {
+ type |= SEXP_COLLECTION_NUMBER_KEYS;
+ }
+ else if (!strcmp(temp_type_string.c_str(), "String")) {
+ type |= SEXP_COLLECTION_STRING_KEYS;
+ }
+ else {
+ Warning(LOCATION, "Unknown SEXP Collection type %s found", temp_type_string.c_str());
+ log_printf(LOGFILE_EVENT_LOG, "Unknown SEXP Collection type %s found", temp_type_string.c_str());
+ return false;
+ }
+ }
+
+ if (optional_string("+Strongly Typed Keys")) {
+ Assertion ((type & SEXP_COLLECTION_MAP), "+Strongly Typed Keys found for collection which doesn't use keys!"); 
+ type |= SEXP_COLLECTION_STRONGLY_TYPED_KEYS;
+ }
+
+ if (optional_string("+Strongly Typed Data")) {
+ type |= SEXP_COLLECTION_STRONGLY_TYPED_DATA;
+ }
+
+ required_string("$Data:");
+ stuff_string_list(data);
+
+ return true;
+}
+
 /**
+ * Stuffs a sexp list type collection
+ */
+
+void stuff_sexp_list_collection()
+{
+ sexp_collection temp_list;
+ SCP_vector<SCP_string> parsed_data;
+
+
+ while (required_string_either("$End Lists", "$Name:")) {
+ temp_list.type = SEXP_COLLECTION_LIST;
+ if (stuff_one_generic_sexp_collection(temp_list.container_name, temp_list.type, parsed_data)){
+ temp_list.list_data.clear();
+ for (int i = 0; i < (int)parsed_data.size(); i++) {
+ temp_list.list_data.push_back(parsed_data[i]);
+ }
+ }
+
+ Sexp_collections.push_back(temp_list);
+ }
+}
+
+
+/**
+ * Stuffs a sexp map type collection
+ */
+void stuff_sexp_map_collection()
+{
+ sexp_collection temp_map;
+ SCP_vector<SCP_string> parsed_data;
+
+
+ while (required_string_either("$End Maps", "$Name:")) {
+ temp_map.type = SEXP_COLLECTION_MAP;
+ if (stuff_one_generic_sexp_collection(temp_map.container_name, temp_map.type, parsed_data)){
+
+ // if the data is corrupt, discard it
+ if (parsed_data.size() % 2 != 0) {
+ Warning(LOCATION, "Data in the SEXP Map collection is corrupt. Must be an even number of entries. Instead have %d", parsed_data.size() );
+ log_printf(LOGFILE_EVENT_LOG, "Data in the SEXP Map collection is corrupt. Must be an even number of entries. Instead have %d", parsed_data.size() );
+ continue;
+ }
+
+ // move the data into the new entry
+ temp_map.map_data.clear();
+ for (int i = 0; i < (int)parsed_data.size(); i = i+2) {
+ temp_map.map_data.insert(std::pair<SCP_string, SCP_string>(parsed_data[i], parsed_data[i+1]));
+ }
+ }
+
+
+ Sexp_collections.push_back(temp_map);
+ }
+}
+
+
+
+/**
  * Stuffs a list of sexp variables
  */
 int stuff_sexp_variable_list()
@@ -28188,7 +28352,348 @@
  return variable_index;
 }
 
+
+/*
+-// Takes an Sexp_node.text pointing to a variable (of form "Sexp_variables[xx]=string" or "Sexp_variables[xx]=number")
+-// and returns the index into the Sexp_variables array of the actual value
+-int extract_sexp_variable_index(int node)
++char* deal_with_map_collection(int n)
+ {
+- char *text = Sexp_nodes[node].text;
+- char char_index[8];
+- char *start_index;
+- int variable_index;
++ Assertion((Sexp_nodes[n].type & SEXP_FLAG_MAP_COLLECTION), "deal_with_map_collection() called for a collection which isn't a map.");
+
+- // get past the '['
+- start_index = text + 15;
+- Assert(isdigit(*start_index));
++ int index = get_index_sexp_map_collection_name(Sexp_nodes[n].text);
++ if (index < 0)  {
++ return "";
++ }
++
++ if (Sexp_map_collections[index].data.empty()) {
++ return "";
++ }
+
+- int len = 0;
++ SCP_string temp_string;
+
+- while ( *start_index != ']' ) {
+- char_index[len++] = *(start_index++);
+- Assert(len < 3);
++ int key_node = CAR(n);
++ SCP_hash_map<SCP_string, SCP_string>::iterator value = Sexp_map_collections[index].data.find(CTEXT(key_node));
++
++ // not found
++ if (value == Sexp_map_collections[index].data.end()) {
++ // probably should write to the event.log that the element wasn't found here.
++ return "";
+ }
+
+- Assert(len > 0);
+- char_index[len] = 0; // append null termination to string
++ Temp_CTEXT_strings.push_back(value->second);
++ return const_cast<char*>(Temp_CTEXT_strings.back().c_str());
++}
+
+- variable_index = atoi(char_index);
+- Assert( (variable_index >= 0) && (variable_index < MAX_SEXP_VARIABLES) );
++char* deal_with_list_collection(int n)
++{
++ Assertion((Sexp_nodes[n].type & SEXP_FLAG_LIST_COLLECTION), "deal_with_list_collection() called for a collection which isn't a list.");
+
+- return variable_index;
++ // the node flags will tell us what we're supposed to do with this collection
++ int actual_flag = Sexp_nodes[n].flags & SNF_COLLECTION_MASK;
++
++ int index = get_index_sexp_list_collection_name(Sexp_nodes[n].text);
++ if (index < 0)  {
++ return "";
++ }
++
++ if (Sexp_list_collections[index].data.empty()) {
++ return "";
++ }
++
++ SCP_string temp_string;
++ int parent = -1;
++ int parent_node = -1;
++ int recurse_at_this_node = -1;
++ int op_type;
++ int number_of_elements;
++ int data_index;
++
++ switch (actual_flag) {
++ case SNF_COLLECTION_GET_FIRST:
++ temp_string.assign(Sexp_list_collections[index].data.front());
++ break;
++
++ case SNF_COLLECTION_GET_LAST:
++ temp_string.assign(Sexp_list_collections[index].data.back());
++ break;
++
++ case SNF_COLLECTION_REMOVE_FIRST:
++ temp_string.assign(Sexp_list_collections[index].data.front());
++ Sexp_list_collections[index].data.pop_front();
++ break;
++
++ case SNF_COLLECTION_REMOVE_LAST:
++ temp_string.assign(Sexp_list_collections[index].data.back());
++ Sexp_list_collections[index].data.pop_back();
++ break;
++
++ case SNF_COLLECTION_GET_RANDOM:
++ number_of_elements = (int)Sexp_list_collections[index].data.size();
++ data_index = rand_internal(0, number_of_elements);
++ temp_string.assign(Sexp_list_collections[index].data.at(data_index));
++ break;
++
++ case SNF_COLLECTION_ITERATE_FORWARDS:
++ // get the parent node
++ parent = find_parent_operator(n);
++ if (parent == -1) {
++ return "";
++ }
++ parent_node = parent;
++
++ // we need to find the point we're going to recurse from
++ while (true) {
++ op_type = Operators[get_operator_index(parent)].type;
++
++ // Action operators are suitable
++ if (op_type & SEXP_ACTION_OPERATOR) {
++ recurse_at_this_node = parent;
++ break;
++ }
++
++ // Conditional operators are also suitable
++ if (op_type & SEXP_CONDITIONAL_OPERATOR) {
++ recurse_at_this_node = parent;
++ break;
++ }
++
++ // anything else, back up further
++ parent = find_parent_operator(parent);
++ if (parent == -1) {
++ return "";
++ }
++ }
++
++ // does the parent node know we are iterating already?
++ if(Sexp_nodes[recurse_at_this_node].iteration == -1) {
++ Sexp_nodes[recurse_at_this_node].iteration = (int)Sexp_list_collections[index].data.size();
++ }
++
++ if (Sexp_nodes[n].iteration < (int)Sexp_list_collections[index].data.size()) {
++ if (Sexp_nodes[parent_node].safe_to_update_iterations == true) {
++ // since we're entering the eval code, it is no longer safe to update the number of iterations
++ Sexp_nodes[parent_node].safe_to_update_iterations = false;
++ Sexp_nodes[n].iteration++ ;  // we start at -1 so this needs to go up BEFORE we use it as an index!
++ }
++ temp_string.assign(Sexp_list_collections[index].data[Sexp_nodes[n].iteration]);
++ }
++ // Well, this was clumsy mission design! Someone has used two or more lists in the same SEXP and a later one is shorter than the other(s)!
++ else {
++ // just return the last entry and hope it's valid
++ temp_string.assign(Sexp_list_collections[index].data.back());
++ Warning(LOCATION, "Error - SEXP List %s does not contain %d members but has been asked to provide that number of entries", Sexp_list_collections[index].list_name.c_str(), Sexp_nodes[recurse_at_this_node].iteration);
++ log_printf(LOGFILE_EVENT_LOG, "Error - SEXP List %s does not contain %d members but has been asked to provide that number of entries", Sexp_list_collections[index].list_name.c_str(), Sexp_nodes[recurse_at_this_node].iteration);
++ }
++
++ // final iteration for this collection
++ if (Sexp_nodes[n].iteration + 1 == (int)Sexp_list_collections[index].data.size()) {
++ Sexp_nodes[recurse_at_this_node].flags |= SNF_COLLECTION_FINAL_ITERATION;
++ }
++
++ break;
++
++ default:
++ return "";
++ }
++
++ Temp_CTEXT_strings.push_back(temp_string);
++ return const_cast<char*>(Temp_CTEXT_strings.back().c_str());
+ }
+
++bool deal_with_collection(int node, int collection_index, int collection_type)
++{
++ if (collection_type & SEXP_FLAG_MAP_COLLECTION) {
++
++ if (Sexp_map_collections[collection_index].data.empty()) {
++ return false;
++ }
+
+-/**
++ SCP_string temp_string;
++
++ SCP_hash_map<SCP_string, SCP_string>::iterator value = Sexp_map_collections[collection_index].data.find(CTEXT(node));
++
++ // not found
++ if (value == Sexp_map_collections[collection_index].data.end()) {
++ // probably should write to the event.log that the element wasn't found here.
++ return false;
++ }
++
++ Temp_CTEXT_strings.push_back(value->second);
++ return true;
++ }
++
++ else if (collection_type & SEXP_FLAG_LIST_COLLECTION) {
++ // TODO - Actual code goes here
++ return false;
++ }
++
++ return false;
++}
++/*
++char* deal_with_collection(int n)
++{
++ if  (Sexp_nodes[n].type & SEXP_FLAG_LIST_COLLECTION) {
++ return deal_with_list_collection(n);
++ }
++ else if (Sexp_nodes[n].type & SEXP_FLAG_MAP_COLLECTION) {
++ return deal_with_map_collection(n);
++ }
++
++ Warning(LOCATION, "deal_with_collection() called for an invalid containter type");
++ return "";
++}
++*/
 
+#define SNF_CONTAINER_GET_FIRST 0
+#define SNF_CONTAINER_GET_LAST 1
+#define SNF_CONTAINER_REMOVE_FIRST 2
+#define SNF_CONTAINER_REMOVE_LAST 3
+#define SNF_CONTAINER_GET_RANDOM 4
+#define SNF_CONTAINER_REMOVE_RANDOM 5
+
+typedef struct container_modifier {
+ char *name;
+ int def;
+} container_modifier;
+
+container_modifier Container_modifiers[] = {
+ { "Get_First", SNF_CONTAINER_GET_FIRST, },
+ { "Get_Last", SNF_CONTAINER_GET_LAST, },
+ { "Remove_First", SNF_CONTAINER_REMOVE_FIRST, },
+ { "Remove_Last", SNF_CONTAINER_REMOVE_LAST, },
+ { "Get_Random", SNF_CONTAINER_GET_RANDOM, },
+ { "Remove_Random", SNF_CONTAINER_REMOVE_RANDOM, },
+};
+
+#define NUM_CONTAINER_MODIFIERS 6
+
+bool deal_with_collection_sub(int node, int collection_index, SCP_string &result)
+{
+ if (Sexp_collections[collection_index].type & SEXP_COLLECTION_MAP) {
+ SCP_hash_map<SCP_string, SCP_string>::iterator value = Sexp_collections[collection_index].map_data.find(CTEXT(node));
+
+ // not found
+ if (value == Sexp_collections[collection_index].map_data.end()) {
+ // probably should write to the event.log that the element wasn't found here but this might not actually be an error.
+ return false;
+ }
+
+ result.assign(value->second);
+ return true;
+ }
+ else if (Sexp_collections[collection_index].type & SEXP_COLLECTION_LIST) {
+
+ // if the collection is empty, we might as well quit now
+ if (Sexp_collections[collection_index].list_data.empty()) {
+ return false;
+ }
+
+ char *modifier = CTEXT(node);
+
+ int modifier_index;
+ bool found = false;
+ for (modifier_index = 0; modifier_index < NUM_CONTAINER_MODIFIERS; modifier_index++) {
+ if (!stricmp(modifier, Container_modifiers[modifier_index].name)) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ Warning(LOCATION, "Illegal operation attempted on %s container. There is no modifier called %s.", Sexp_collections[collection_index].container_name, CTEXT(node) );
+ log_printf(LOGFILE_EVENT_LOG, "Illegal operation attempted on %s container. There is no modifier called %s.", Sexp_collections[collection_index].container_name, CTEXT(node) );
+ }
+
+ int number_of_elements;
+ int data_index;
+
+ switch (modifier_index) {
+ case SNF_CONTAINER_GET_FIRST:
+ result.assign(Sexp_collections[collection_index].list_data.front());
+ return true;
+
+ case SNF_CONTAINER_GET_LAST:
+ result.assign(Sexp_collections[collection_index].list_data.back());
+ return true;
+
+ case SNF_CONTAINER_REMOVE_FIRST:
+ result.assign(Sexp_collections[collection_index].list_data.front());
+ Sexp_collections[collection_index].list_data.pop_front();
+ return true;
+
+ case SNF_CONTAINER_REMOVE_LAST:
+ result.assign(Sexp_collections[collection_index].list_data.back());
+ Sexp_collections[collection_index].list_data.pop_back();
+ return true;
+
+ case SNF_CONTAINER_GET_RANDOM:
+ number_of_elements = (int)Sexp_collections[collection_index].list_data.size();
+ data_index = rand_internal(0, number_of_elements);
+ result.assign(Sexp_collections[collection_index].list_data.at(data_index));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+char* deal_with_collection(int node, int collection_index) 
+{
+ bool success = true;
+ int sanity = 0;
+ SCP_string result;
+
+ Assertion (((collection_index > -1) && (collection_index < (int)Sexp_collections.size())), "deal_with_collection() called for index %d when there are only %d collections", collection_index, Sexp_collections.size());
+
+ do {
+ sanity++;
+ success = deal_with_collection_sub(node, collection_index, result);
+
+ // if this isn't an array or is empty we exit
+ if (!success) {
+ return "";
+ }
+
+ if ( result.at(0) != SEXP_COLLECTION_CHAR ) {
+ Temp_collection_strings.push_back(result);
+ return const_cast<char*>(Temp_collection_strings.back().c_str());
+ //return const_cast<char*>(Temp_collection_string.c_str());
+ }
+
+ // we're dealing with a multidimentional array
+ node = CDR(node);
+
+ collection_index = get_index_sexp_collection_name(result.substr(1, (result.length() - 2)).c_str());
+
+ // if it's still nonsense, warn then bail
+ if ( collection_index == -1 ) {
+ Warning(LOCATION, "There is no collection called %s in this mission.", result.c_str());
+ return "";
+ }
+
+ } while (sanity++ < 20);
+
+ return "";
+}
+
 /**
  * Wrapper around Sexp_node[xx].text for normal and variable
  */
@@ -28263,6 +28768,22 @@
 
  return Sexp_variables[sexp_variable_index].text;
  }
+ // Karajorma - check we're not dealing with a collection
+ else if (Sexp_nodes[n].type & SEXP_FLAG_COLLECTION) {
+ Assertion((Sexp_nodes[n].type & SEXP_FLAG_COLLECTION), "deal_with_collection() called for an unknown type of collection");
+
+ int collection_index = get_index_sexp_collection_name(Sexp_nodes[n].text);
+
+ if (collection_index < 0)  {
+ Warning(LOCATION, "Deal_with_collection called for %s, a collection which does not exist!", Sexp_nodes[n].text);
+ log_printf(LOGFILE_EVENT_LOG, "Deal_with_collection called for %s, a collection which does not exist!", Sexp_nodes[n].text);
+ return "";
+ }
+
+ n = CAR(n);
+
+ return deal_with_collection(n, collection_index);
+ }
  else
  {
  return Sexp_nodes[n].text;
@@ -28282,6 +28803,16 @@
 }
 
 /**
+ * Clear the SEXP Collectors vector
+ */
+void init_sexp_collections()
+{
+ Sexp_collections.clear();
+}
+
+
+
+/**
  * Add a variable to the block variable array rather than the Sexp_variables array
  */
 void add_block_variable(const char *text, const char *var_name, int type, int index)
@@ -28954,6 +29485,21 @@
 }
 
 /**
+ * Return index of a map type sexp_collection by its name, -1 if not found
+ */
+int get_index_sexp_collection_name(const char *text)
+{
+ for (int i = 0; i < (int)Sexp_collections.size() ; i++) {
+ if ( !stricmp(Sexp_collections[i].container_name.c_str(), text) ) {
+ return i;
+ }
+ }
+
+ // not found
+ return -1;
+}
+
+/**
  * Evaluate number which may result from an operator or may be text
  */
 int eval_num(int n)
Index: code/parse/sexp.h
===================================================================
--- code/parse/sexp.h (revision 11115)
+++ code/parse/sexp.h (working copy)
@@ -198,7 +198,8 @@
 #define CHANGE_SUBCATEGORY_JUMP_NODES (0x0010 | OP_CATEGORY_CHANGE)
 #define CHANGE_SUBCATEGORY_SPECIAL_EFFECTS (0x0011 | OP_CATEGORY_CHANGE)
 #define CHANGE_SUBCATEGORY_VARIABLES (0x0012 | OP_CATEGORY_CHANGE)
-#define CHANGE_SUBCATEGORY_OTHER (0x0013 | OP_CATEGORY_CHANGE)
+#define CHANGE_SUBCATEGORY_COLLECTIONS (0x0013 | OP_CATEGORY_CHANGE)
+#define CHANGE_SUBCATEGORY_OTHER (0x0014 | OP_CATEGORY_CHANGE)
 
 
 #define STATUS_SUBCATEGORY_MISSION (0x0000 | OP_CATEGORY_STATUS)
@@ -859,7 +860,11 @@
 // flags for sexpressions -- masked onto the end of the type field
 #define SEXP_FLAG_PERSISTENT (1<<31) // should this sexp node be persistant across missions
 #define SEXP_FLAG_VARIABLE (1<<30)
+#define SEXP_FLAG_LIST_COLLECTION (1<<29)
+#define SEXP_FLAG_MAP_COLLECTION (1<<28)
 
+#define SEXP_FLAG_COLLECTION (SEXP_FLAG_LIST_COLLECTION | SEXP_FLAG_MAP_COLLECTION)
+
 // sexp variable definitions
 #define SEXP_VARIABLE_CHAR ('@')
 // defines for type field of sexp_variable.  Be sure not to conflict with type field of sexp_node
@@ -880,6 +885,18 @@
 //Karajorma
 #define SEXP_VARIABLE_NETWORK (1<<28)
 
+// sexp collection definitions
+#define SEXP_COLLECTION_CHAR ('&')
+#define SEXP_COLLECTION_LIST (1<<0)
+#define SEXP_COLLECTION_MAP (1<<1)
+#define SEXP_COLLECTION_STRONGLY_TYPED_KEYS (1<<2)
+#define SEXP_COLLECTION_STRONGLY_TYPED_DATA (1<<3)
+#define SEXP_COLLECTION_NUMBER_DATA (1<<4)
+#define SEXP_COLLECTION_STRING_DATA (1<<5)
+#define SEXP_COLLECTION_NUMBER_KEYS (1<<6)
+#define SEXP_COLLECTION_STRING_KEYS (1<<7)
+
+
 #define BLOCK_EXP_SIZE 6
 #define INNER_RAD 0
 #define OUTER_RAD 1
@@ -904,6 +921,7 @@
 #define SEXP_ATOM_OPERATOR 1
 #define SEXP_ATOM_NUMBER 2
 #define SEXP_ATOM_STRING 3
+#define SEXP_ATOM_COLLECTION 4
 
 // defines to short circuit evaluation when possible. Also used when goals can't
 // be satisfied yet because ship (or wing) hasn't been created yet.
@@ -1037,6 +1055,15 @@
  char variable_name[TOKEN_LENGTH];
 } sexp_variable;
 
+class sexp_collection
+{
+ public:
+ SCP_string container_name;
+ int type;
+ int opf_type;
+ SCP_deque<SCP_string> list_data;
+ SCP_hash_map<SCP_string, SCP_string> map_data;
+};
 
 #define ARG_ITEM_F_DUP (1<<0)
 
@@ -1073,6 +1100,8 @@
 extern sexp_variable Sexp_variables[MAX_SEXP_VARIABLES];
 extern sexp_variable Block_variables[MAX_SEXP_VARIABLES];
 
+extern SCP_vector<sexp_collection> Sexp_collections;
+
 extern sexp_oper Operators[];
 extern int Num_operators;
 extern int Locked_sexp_true, Locked_sexp_false;
@@ -1122,6 +1151,8 @@
 extern int get_sexp_main(void); // Returns start node
 extern int run_sexp(const char* sexpression); // debug and lua sexps
 extern int stuff_sexp_variable_list();
+extern void stuff_sexp_list_collection();
+extern void stuff_sexp_map_collection();
 extern int eval_sexp(int cur_node, int referenced_node = -1);
 extern int is_sexp_true(int cur_node, int referenced_node = -1);
 extern int query_operator_return_type(int op);
@@ -1171,6 +1202,9 @@
 int num_block_variables();
 bool has_special_explosion_block_index(ship *shipp, int *index);
 
+// sexp_collection
+int get_index_sexp_collection_name(const char *text);
+
 // Karajorma
 void set_primary_ammo (int ship_index, int requested_bank, int requested_ammo, int rearm_limit=-1, bool update=true);
 void set_secondary_ammo (int ship_index, int requested_bank, int requested_ammo, int rearm_limit=-1, bool update=true);

Builds are available from here and include a test mission.

[attachment kidnapped by pirates]
Karajorma's Freespace FAQ. It's almost like asking me yourself.

[ Diaspora ] - [ Seeds Of Rebellion ] - [ Mind Games ]

 

Offline Axem

  • 211
Re: SEXP Collection Classes (Very Alpha!)
This is hard-core awesomeness. I was going through this one fairly complicated mission recently with this intricate web of sexps and conditions nearly a year after writing it and I'm all but lost in trying to remember why I was doing certain things. This new feature looks like it could have made things so much simpler, but I lack the stamina and effort to redo that mission again. (It works now, so why fix it!)

But I will keep my brain open for new ways I could abuse this stuff...

 

Offline SypheDMar

  • 210
  • Student, Volunteer, Savior
    • Minecraft
Re: SEXP Collection Classes (Very Alpha!)
Holy crap! Why isn't this more publicized?

 
Re: SEXP Collection Classes (Very Alpha!)
Good question. I remember thinking it was all kinds of awesome when kara first posted this, but since neither me nor anyone else commented on it, I guess people just forgot about it up until now.

This thing has some serious potential, proper complex data structures is something FRED has been lacking for a while now. I just wish I had time to dedicate to testing this thoroughly.

 

Offline karajorma

  • King Louie - Jungle VIP
  • Administrator
  • 214
    • Karajorma's Freespace FAQ
Re: SEXP Collection Classes (Very Alpha!)
Well FRED still lacks it cause at the moment it only works in FS2. :)

Support for FRED is usually the last thing to be added cause I have to nail down the features first.
Karajorma's Freespace FAQ. It's almost like asking me yourself.

[ Diaspora ] - [ Seeds Of Rebellion ] - [ Mind Games ]