Modding, Mission Design, and Coding > FS2 Open Coding - The Source Code Project (SCP)

[Master Sticky] The "Read Me First" Posts

<< < (2/2)

The E:
Okay, so, CommanderDJ asked about how to write sexps. Since I was bored, I volunteered to write a short guide. This will be posted on the wiki eventually, but for the moment, I'd like more eyes to look over it. Please point out areas that need clearing up, or parts that need to be added.

==========================================================

Writing a new sexp, a guided tour.

Hello and welcome to FSO coding. This lesson will cover the creation of new sexps.

===FILES YOU NEED TO EDIT===

1. sexp.cpp
2. sexp.h
3. (occasionally) sexp_tree.cpp
4. (also occasionally) sexp_tree.h


Okay, so you have an idea for a new sexp. Here's what you need to do in order to make it work. Note that basic understanding of C++ Syntax is needed and assumed present.

Step 1: Find out if the functionality isn't already covered by an existing sexp. There are a lot of functions already present in the sexp system, if you can use them to do what you want without adding another one, or by modifying an existing one, that's almost certainly preferable to writing a new one.

Step 2: Okay, so you've done step one and have determined that you need to do a completely new sexp. So here's the steps you need to do.
   1. In sexp.h, locate a giant list of #defines starting with "#define OP_PLUS". These are the operator constants, which are used internally to route sexp code around. Each of these defines follows a common pattern, a hex number OR'ed together with a category and (in most cases) the OP_NONCAMPAIGN_FLAG flag. Look around until you find the category where your sexp will fit in (which, in most cases, will be the "change" category), and add your flag at the bottom of the list. The hex number must follow consecutively from the last one used in that category. Then add the Category flag, and the OP_NONCAMPAIGN_FLAG (This flag is used to determine if a given sexp can be used in a campaign file to determine mission progression). If your new sexp should be present only in the campaign room, you need to use OP_CAMPAIGN_ONLY_FLAG here.
   2. In sexp.cpp, locate the Operators[] array. Each sexp has a distinct entry here. The format is { "sexp-name", OPERATOR_CONSTANT, Min number of arguments, Max number of arguments }. The sexp name string is what the sexps will later be saved as in mission files, and the documentation.
   3. In sexp.cpp, locate the function eval_sexp(int cur_node, int referenced_node). This function is called to evaluate sexps at runtime, and basically calls the sexps' evaluation function and then returns a return value. Possible return values are:
      SEXP_TRUE: This means that the sexp has executed successfully. This is the default return for all sexps, even those that do not have usable return values.
      SEXP_FALSE: This is used in sexps that return boolean values.
      SEXP_KNOWN_TRUE and SEXP_KNOWN_FALSE: These should be treated with caution. They should only be used in cases where you are absolutely certain that a given result will stay true or false for the entire mission. Examples are found in the is-destroyed sexp for example, which returns SEXP_KNOWN_FALSE if the ship or wing passed as an argument has departed (warped out). The danger here is that once you return a KNOWN value, the sexp system will stop evaluating that specific expression.
      SEXP_UNKNOWN: This is the default value any given sexp tree node has before evaluation starts.
      SEXP_NAN and SEXP_NAN_FOREVER: Should be returned when an operation is supposed to return a number, but can't for whatever reason. SEXP_NAN_FOREVER acts much like the SEXP_KNOWN return values; using it means that a given operation will always return NAN. As an example, if you use get-object-speed on a departed or destroyed object, it will return SEXP_NAN_FOREVER.
      SEXP_CANT_EVAL: Used when the sexp cannot be evaluated yet for whatever reason. For example, this is returned when is-destroyed is used on a ship that hasn't arrived yet.
   These values can and should be returned from your evaluation function, if necessary. It is here that you should insert a call to your evaluation function, with the current node as an argument.
   4. In sexp.cpp, locate the query_operator_return_type(int op) function. In it, you will find a massive switch statement. This is used to determine what your sexp returns, which is in turn used by the sexp syntax checker to determine if your sexp is placed correctly, and by FRED to determine which sexps are available in a given situation. Possible values are:
      OPR_NULL: This sexp does not return anything
      OPR_BOOL: This sexp returns a boolean value
      OPR_NUMBER: This sexp returns a number
      OPR_POSITIVE: This sexp returns a positive number
   There are a few special return types as well. These are used for type matching (that is, to see if a given sexp is valid at a given point in the expression)
      OPR_AI_GOAL, OPR_STRING, OPR_AMBIGUOUS, OPR_FLEXIBLE_ARGUMENT
   5. In sexp.cpp, locate the query_operator_argument_type(int op, int argnum) function. This is similar to query_operator_return_type(), in that it is used to check sexp syntax. If your sexp has more than one argument, or more than one type of argument, you need to use the argnum variable to determine what you need to return. Possible return values are found in sexp.h, look for the block of OPF_* defines.
   6. In sexp.cpp, locate the get_subcategory(int sexp_id) function. Locate the subcategory where your sexp will fit in best, and add your operator constant to the switch statement there.
   7. In sexp.cpp, locate the Sexp_help[] array. This is where you need to add a short description of your sexp, which will appear in the event editor. Syntax is simply { OPERATOR_CONSTANT, "help text" }. Look at the other entries, and follow the formatting found there.
   
   Okay, so that's the whole setup out of the way. Now we'll have to write the evaluation function.
   Almost all evaluation functions have the current node in the sexp tree as an argument. This is used to retrieve arguments given to the sexp by the FREDer. To retrieve arguments from a node, the following helper functions and macros exist:
      CDR(n): Returns the next branch in the tree
      CAR(n): Returns the root node of the current element
      CTEXT(n): Returns a c-string representation of a text argument.
      eval_num(n): Returns a numeric argument
      is_sexp_true(n): Evaluates boolean arguments. Returns true if the expression is true or KNOWN_TRUE.
      sexp_get_ship_from_node(n): Returns a pointer to the Ship pointer, or NULL if the ship can'T be found in the mission.
      ship_name_lookup(CTEXT(n)): Returns the ships' position in the Ships[] array.
      ship_type_name_lookup(CTEXT(n)): Returns the ship class' position the Ship_info[] array.
      
   
Note that, at this time, this guide does not explain how to set up sexps for multiplayer. Use Karajorma's guide for this

The E:
For convenience, here's the post about multiplayer sexps:


--- Quote from: karajorma on September 18, 2009, 02:38:34 am ---I got a request from CooperHawkes for an explanation of how you fix a SEXP so that it works in multiplayer so I figured I'd just post the explanation so that the other SCP coders would see how easy it is to fix them.

Firstly you must determine if the SEXP is suitable for fixing. Only the Change SEXPs work with this method. Not every change SEXP needs a fix, only those where the effect is only seen on the server but fails to make it to the client machines.

Fixing a SEXP involves using the Multi_SEXP packet system in order to send the information to the client machines. I coded this in such a way that the coder doesn't need to understand how this works though. All they have to do is call a set of send_x functions on the server and a set of get_x functions on the client. To show how this works I'll convert fade-in to a multi capable SEXP as an example.


--- Code: ---void sexp_fade_in(int n)
{
float delta_time = 0.0f;

if(n != -1)
delta_time = eval_num(n)/1000.0f;

if(delta_time > 0.0f)
{
Fade_delta_time = delta_time;
Fade_type = FI_FADEIN;
}
else
{
Fade_type = FI_NONE;
gr_create_shader(&Viewer_shader, 0, 0, 0, 0);
}
}
--- End code ---

Here's how the SEXP currently looks. From that you can see that the only variable the client machine needs to be aware of is delta_time.

So before the final brace we add the code for the multi call back.


--- Code: ---// multiplayer callback
multi_start_packet();
multi_send_float(delta_time);
multi_end_packet();
}
--- End code ---

The first and last functions say that this is the start and end of a packet of info you want sent to the client machines. Between these you want to include any variables that you need to send to the client machines (including any optional ones the SEXP may have).

And that's it as far as the server is concerned.

Client side you need to add a function that is called when this packet arrives.


--- Code: ---void multi_sexp_fade_in()
{
float delta_time = 0.0f;

multi_get_float(delta_time);

if(delta_time > 0.0f) {
Fade_delta_time = delta_time;
Fade_type = FI_FADEIN;
}
else {
Fade_type = FI_NONE;
gr_create_shader(&Viewer_shader, 0, 0, 0, 0);
}
}
--- End code ---

The majority of the function is a simple copy of the actions carried out in on the host side (for longer SEXPs you should spin that off into a function and call it from both the host and client functions but this one was simple enough to justify a cut and paste instead. :p)

The function starts with a list of calls to the multi_get_x functions. When dealing with optional arguments you can simply test for
--- Code: ---if (multi_get_x)
--- End code ---
as this will return false if the optional argument wasn't present.

The final step is to find void multi_sexp_eval() and add a case for the this particular SEXP pointing back to the function to call (in this case multi_sexp_fade_in() ).


--- Code: --- case OP_CUTSCENES_FADE_IN:
multi_sexp_fade_in();
break;
--- End code ---

And that's it. The code handles everything else internally. So now none of you have any excuse for making SEXPs that don't work in multiplayer. :p

--- End quote ---

niffiwan:
This is basically the same info as post #5, except for Linux with gdb instead of Windows.

gdb is pretty simple, in order to get a stack trace from a FSO crash, do the following in a terminal:

1) install Git and checkout a current copy of the code

2) install gdb

--- Code: (Debian/Ubuntu & derivatives) ---sudo apt-get install gdb

--- End code ---

--- Code: (Fedora & friends) ---sudo yum install gdb

--- End code ---

3) setup your mod & settings using wxLauncher - in addition to your normal settings you must have:

* a smaller-than-your-entire-monitor resolution (Basic Settings)
* "run in a window" (Advanced Settings -> Dev Tool)
* "don't grab mouse/keyboard in a window" (Advanced Settings -> Dev Tool)If you don't set these three options, then you'll find that you can't leave the FSO window in order to use the debugger.

4) Run the debug version of FSO (if you don't already have it, pass "--enable-debug" as a parameter to autogen.sh and then compile it)

--- Code: ---gdb fs2_open_3.6.15_DEBUG
--- End code ---


4a) You may need to tell gdb where the FSO source code is, specifically the fs2_open/code directory.

--- Code: ---gdb dir /path/to/fso/source/code
--- End code ---

5) gdb has it's own prompt.  Run FSO with this obvious command ;)

--- Code: ---run
--- End code ---

6) Switch to your FSO window and do whatever is necessary to reproduce the crash

7) When the crash has occurred, switch back to your terminal and enter this at the gdb prompt:

--- Code: ---backtrace
--- End code ---

8) copy this output and post it where an SCP member can see it

9) Exit FSO by running this at the gdb prompt:

--- Code: ---quit
--- End code ---


The procedure above is probably all you need. However, if you're interested, here's some more info:

Backtraces
A backtrace (MSVC calls it a stack trace) displays all the function calls that the program has passed through to get to its current position.  Here's an example:

--- Code: ---(gdb) backtrace
#0  wing_name_lookup (name=0x1993500 "Gamma", ignore_count=1) at ship/ship.cpp:11316
#1  0x00000000005d7dc4 in post_process_mission () at mission/missionparse.cpp:5317
#2  0x00000000005d7b37 in parse_mission (pm=0xfb8120, flags=0) at mission/missionparse.cpp:5270
#3  0x00000000005d8be9 in parse_main (mission_name=0x7fffffffded0 "respawn_crash.fs2", flags=0) at mission/missionparse.cpp:5642
#4  0x00000000005bfd9f in mission_load (filename_ext=0xbfb9c0 "respawn_crash") at mission/missionload.cpp:107
#5  0x000000000040c465 in game_start_mission () at freespace2/freespace.cpp:1450
#6  0x0000000000415e4f in game_enter_state (old_state=20, new_state=52) at freespace2/freespace.cpp:5974
#7  0x00000000004b90fc in gameseq_set_state (new_state=52, override=0) at gamesequence/gamesequence.cpp:282
#8  0x0000000000414ccd in game_process_event (current_state=20, event=1) at freespace2/freespace.cpp:5145
#9  0x00000000004b95f2 in gameseq_process_events () at gamesequence/gamesequence.cpp:397
#10 0x0000000000417605 in game_main (cmdline=0x227edd0 "") at freespace2/freespace.cpp:7092
#11 0x00000000004177da in main (argc=1, argv=0x7fffffffe308) at freespace2/freespace.cpp:7226
(gdb)

--- End code ---
The backtrace shows all the function names and the values of variables passed to those functions, e.g. on line 6 above (game_enter_state), you can see that variable old_state was 20, and variable new_state was 52.

Viewing variables
You can view the current value of variables using the gdb print command e.g.

--- Code: ---print Ship_info[0].name

--- End code ---
This can also be used to display nearly any C/C++ expression.

Breakpoints
Breakpoints are a way of stopping FSO from running at a certain point in the code. Set one by entering the following at the gdb prompt:

--- Code: ---break freespace2/freespace.cpp:1758

--- End code ---
The format is directory/filename:line-number (obviously the source code you're getting the file & line number from must match the executable your running, otherwise this information may not match)

Debugging with an FSO release executable
In the rare occasion when you cannot reproduce the crash in FSO debug you can also get a backtrace from the release version.  Use the same procedure as above, but keep in mind these caveats:
1) you must recompile the executable with the gcc -g option set (i.e. CXXFLAGS=-g make)
2) debugging may not be 100% reliable because the release version uses various compiler optimistaions (i.e. gcc -O2)

To workaround both these issues, compile like this and run gdb with the resulting executable:

--- Code: ---make distclean && CXXFLAGS='-g -O0' sh autogen.sh && V=1 make

--- End code ---

(The V=1 is not strictly neccessary, but it does show you if the CXXFLAGS were picked up correctly)

Debugging with a GUI
The command line is all well and good, but sometimes a GUI really helps and ddd is what I've used (the package name should be "ddd" to install with apt-get or yum).  It's very similar to gdb (and in fact, runs gdb underneath) but you get two obvious extras, a window to display variables/data at all times, and an interactive display of the source code (where, among other things, you can set break points via right-clicking on the appropriate line of code).

Other GUI's include:

* cgdb: TUI based
* nemiver: gnome oriented, vaguely in the style of visual studio
* qtCreator: a complete IDE, but can be used for standalone debugging, my current favourite

The E:
(The following was copied from this thread)


Yesterday, Karajorma had a bit of a problem. His work on sexp container classes was locked up in a patch file against the last revision of our SVN, and none of the solutions we found for converting an svn patch into a git patch actually worked (If you know how to do this, please mention it!).

I then volunteered to convert the stuff to git manually. Kara then asked me to document the process I used so that others may learn from it.

So, basically, here's the workflow I used.

* Create a new branch in git based off of the last revision of the svn. In git, this is represented by this commit.
* Apply the svn patch to a clean checkout of the svn repo.
* Copy all files changed by that patch into the git checkout.
* Commit the changes made through this.
* Merge the newly created branch with the current master in the git repo.
* Resolve any merge errors.
* Try to compile, fix any compilation errors. In this case, errors were caused by Kara's use of SCP_hash_map, a container that has since been deprecated and replaced with SCP_unordered_map.
* Commit any changes made.
And that's it.

Navigation

[0] Message Index

[*] Previous page

Go to full version