1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
4  * All source code herein is the property of Volition, Inc. You may not sell
5  * or otherwise commercially exploit the source or things you created based on the
6  * source.
7  *
8 */
9 
10 
11 
12 //	Parse a symbolic expression.
13 //	These are identical to Lisp functions.
14 //	It uses a very baggy format, allocating 16 characters per token, regardless
15 //	of how many are used.
16 
17 #include <cstdio>
18 #include <cstdlib>
19 #include <cstring>
20 #include <cctype>
21 #include <cassert>
22 #include <climits>
23 #include <cstdint>
24 
25 #include "ai/aigoals.h"
26 #include "asteroid/asteroid.h"
27 #include "autopilot/autopilot.h"
28 #include "camera/camera.h"
29 #include "cmdline/cmdline.h"
30 #include "debris/debris.h"
31 #include "debugconsole/console.h"
32 #include "fireball/fireballs.h"		// for explosion stuff
33 #include "freespace.h"
34 #include "gamesequence/gamesequence.h"
35 #include "gamesnd/eventmusic.h"		// for change-soundtrack
36 #include "gamesnd/gamesnd.h"
37 #include "globalincs/alphacolors.h"
38 #include "globalincs/linklist.h"
39 #include "globalincs/systemvars.h"
40 #include "globalincs/version.h"
41 #include "graphics/2d.h"
42 #include "graphics/font.h"
43 #include "graphics/light.h"
44 #include "hud/hud.h"
45 #include "hud/hudartillery.h"
46 #include "hud/hudescort.h"
47 #include "hud/hudets.h"
48 #include "hud/hudmessage.h"
49 #include "hud/hudparse.h"
50 #include "hud/hudshield.h"
51 #include "hud/hudsquadmsg.h"		// for the order sexp
52 #include "iff_defs/iff_defs.h"
53 #include "io/keycontrol.h"
54 #include "io/timer.h"
55 #include "jumpnode/jumpnode.h"
56 #include "localization/localize.h"
57 #include "math/fvi.h"
58 #include "menuui/techmenu.h"		// for intel stuff
59 #include "mission/missionbriefcommon.h"
60 #include "mission/missioncampaign.h"
61 #include "mission/missiongoals.h"
62 #include "mission/missionlog.h"
63 #include "mission/missionmessage.h"
64 #include "mission/missionparse.h"		// for p_object definition
65 #include "mission/missiontraining.h"
66 #include "missionui/redalert.h"
67 #include "mod_table/mod_table.h"
68 #include "nebula/neb.h"
69 #include "nebula/neblightning.h"
70 #include "network/multi.h"
71 #include "network/multi_obj.h"
72 #include "network/multi_sexp.h"
73 #include "network/multi_team.h"
74 #include "network/multimsgs.h"
75 #include "network/multiutil.h"
76 #include "object/objcollide.h"
77 #include "object/objectdock.h"
78 #include "object/objectshield.h"
79 #include "object/objectsnd.h"
80 #include "object/waypoint.h"
81 #include "parse/generic_log.h"
82 #include "parse/parselo.h"
83 #include "scripting/scripting.h"
84 #include "parse/sexp.h"
85 #include "parse/sexp_container.h"
86 #include "playerman/player.h"
87 #include "render/3d.h"
88 #include "ship/afterburner.h"
89 #include "ship/awacs.h"
90 #include "ship/ship.h"
91 #include "ship/shipfx.h"
92 #include "ship/shiphit.h"
93 #include "ship/ship_flags.h"
94 #include "sound/audiostr.h"
95 #include "sound/ds.h"
96 #include "sound/sound.h"
97 #include "utils/unicode.h"
98 #include "starfield/starfield.h"
99 #include "starfield/supernova.h"
100 #include "stats/medals.h"
101 #include "utils/Random.h"
102 #include "weapon/beam.h"
103 #include "weapon/emp.h"
104 #include "weapon/shockwave.h"
105 #include "weapon/weapon.h"
106 
107 #include "parse/sexp/sexp_lookup.h"
108 
109 #ifndef NDEBUG
110 #include "hud/hudmessage.h"
111 #endif
112 
113 // Stupid windows workaround...
114 #ifdef MessageBox
115 #undef MessageBox
116 #endif
117 
118 // these are useful for embedding numbers in SEXP help strings
119 // see https://stackoverflow.com/questions/5459868/concatenate-int-to-string-using-c-preprocessor
120 #define STR_HELPER(x) #x
121 #define STR(x) STR_HELPER(x)
122 
123 
124 SCP_vector<sexp_oper> Operators = {
125 //   Operator, Identity, Min / Max arguments
126 	//Arithmetic Category
127 	{ "+",								OP_PLUS,								2,	INT_MAX,	SEXP_ARITHMETIC_OPERATOR,	},
128 	{ "-",								OP_MINUS,								2,	INT_MAX,	SEXP_ARITHMETIC_OPERATOR,	},
129 	{ "*",								OP_MUL,									2,	INT_MAX,	SEXP_ARITHMETIC_OPERATOR,	},
130 	{ "/",								OP_DIV,									2,	INT_MAX,	SEXP_ARITHMETIC_OPERATOR,	},
131 	{ "mod",							OP_MOD,									2,	INT_MAX,	SEXP_ARITHMETIC_OPERATOR,	},
132 	{ "rand",							OP_RAND,								2,	3,			SEXP_ARITHMETIC_OPERATOR,	},
133 	{ "rand-multiple",					OP_RAND_MULTIPLE,						2,	3,			SEXP_ARITHMETIC_OPERATOR,	},	// Goober5000
134 	{ "abs",							OP_ABS,									1,	1,			SEXP_ARITHMETIC_OPERATOR,	},	// Goober5000
135 	{ "min",							OP_MIN,									1,	INT_MAX,	SEXP_ARITHMETIC_OPERATOR,	},	// Goober5000
136 	{ "max",							OP_MAX,									1,	INT_MAX,	SEXP_ARITHMETIC_OPERATOR,	},	// Goober5000
137 	{ "avg",							OP_AVG,									1,	INT_MAX,	SEXP_ARITHMETIC_OPERATOR,	},	// Goober5000
138 	{ "pow",							OP_POW,									2,	2,			SEXP_ARITHMETIC_OPERATOR,	},	// Goober5000
139 	{ "signum",							OP_SIGNUM,								1,	1,			SEXP_ARITHMETIC_OPERATOR,	},	// Goober5000
140 	{ "is-nan",							OP_IS_NAN,								1,	1,			SEXP_ARITHMETIC_OPERATOR,	},	// Goober5000
141 	{ "nan-to-number",					OP_NAN_TO_NUMBER,						1,	1,			SEXP_ARITHMETIC_OPERATOR,	},	// Goober5000
142 	{ "set-bit",						OP_SET_BIT,								2,	2,			SEXP_ARITHMETIC_OPERATOR,	},	// Goober5000
143 	{ "unset-bit",						OP_UNSET_BIT,							2,	2,			SEXP_ARITHMETIC_OPERATOR,	},	// Goober5000
144 	{ "is-bit-set",						OP_IS_BIT_SET,							2,	2,			SEXP_BOOLEAN_OPERATOR,		},	// Goober5000
145 	{ "bitwise-and",					OP_BITWISE_AND,							2,	INT_MAX,	SEXP_ARITHMETIC_OPERATOR,	},	// Goober5000
146 	{ "bitwise-or",						OP_BITWISE_OR,							2,	INT_MAX,	SEXP_ARITHMETIC_OPERATOR,	},	// Goober5000
147 	{ "bitwise-not",					OP_BITWISE_NOT,							1,	1,			SEXP_ARITHMETIC_OPERATOR,	},	// Goober5000
148 	{ "bitwise-xor",					OP_BITWISE_XOR,							2,	2,			SEXP_ARITHMETIC_OPERATOR,	},	// Goober5000
149 
150 	//Logical Category
151 	{ "true",							OP_TRUE,								0,	0,			SEXP_BOOLEAN_OPERATOR,	},
152 	{ "false",							OP_FALSE,								0,	0,			SEXP_BOOLEAN_OPERATOR,	},
153 	{ "and",							OP_AND,									2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
154 	{ "and-in-sequence",				OP_AND_IN_SEQUENCE,						2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
155 	{ "or",								OP_OR,									2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
156 	{ "not",							OP_NOT,									1,	1,			SEXP_BOOLEAN_OPERATOR,	},
157 	{ "xor",							OP_XOR,									2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},	// Goober5000
158 	{ "=",								OP_EQUALS,								2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
159 	{ "!=",								OP_NOT_EQUAL,							2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},	// Goober5000
160 	{ ">",								OP_GREATER_THAN,						2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
161 	{ ">=",								OP_GREATER_OR_EQUAL,					2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},	// Goober5000
162 	{ "<",								OP_LESS_THAN,							2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
163 	{ "<=",								OP_LESS_OR_EQUAL,						2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},	// Goober5000
164 	{ "string-equals",					OP_STRING_EQUALS,						2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
165 	{ "string-greater-than",			OP_STRING_GREATER_THAN,					2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
166 	{ "string-less-than",				OP_STRING_LESS_THAN,					2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
167 	{ "perform-actions",				OP_PERFORM_ACTIONS,						2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},	// Goober5000
168 	{ "has-time-elapsed",				OP_HAS_TIME_ELAPSED,					1,	1,			SEXP_BOOLEAN_OPERATOR,	},
169 
170 	//Event/Goals Category
171 	{ "is-goal-true-delay",				OP_GOAL_TRUE_DELAY,						2,	2,			SEXP_BOOLEAN_OPERATOR,	},
172 	{ "is-goal-false-delay",			OP_GOAL_FALSE_DELAY,					2,	2,			SEXP_BOOLEAN_OPERATOR,	},
173 	{ "is-goal-incomplete",				OP_GOAL_INCOMPLETE,						1,	1,			SEXP_BOOLEAN_OPERATOR,	},
174 	{ "is-event-true",					OP_EVENT_TRUE,							1,	1,			SEXP_BOOLEAN_OPERATOR,	},
175 	{ "is-event-true-delay",			OP_EVENT_TRUE_DELAY,					2,	3,			SEXP_BOOLEAN_OPERATOR,	},
176 	{ "is-event-true-msecs-delay",		OP_EVENT_TRUE_MSECS_DELAY,				2,	3,			SEXP_BOOLEAN_OPERATOR,	},
177 	{ "is-event-false",					OP_EVENT_FALSE,							1,	1,			SEXP_BOOLEAN_OPERATOR,	},
178 	{ "is-event-false-delay",			OP_EVENT_FALSE_DELAY,					2,	3,			SEXP_BOOLEAN_OPERATOR,	},
179 	{ "is-event-false-msecs-delay",		OP_EVENT_FALSE_MSECS_DELAY,				2,	3,			SEXP_BOOLEAN_OPERATOR,	},
180 	{ "is-event-incomplete",			OP_EVENT_INCOMPLETE,					1,	1,			SEXP_BOOLEAN_OPERATOR,	},
181 	{ "is-previous-goal-true",			OP_PREVIOUS_GOAL_TRUE,					2,	3,			SEXP_BOOLEAN_OPERATOR,	},
182 	{ "is-previous-goal-false",			OP_PREVIOUS_GOAL_FALSE,					2,	3,			SEXP_BOOLEAN_OPERATOR,	},
183 	{ "is-previous-goal-incomplete",	OP_PREVIOUS_GOAL_INCOMPLETE,			2,	3,			SEXP_BOOLEAN_OPERATOR,	},
184 	{ "is-previous-event-true",			OP_PREVIOUS_EVENT_TRUE,					2,	3,			SEXP_BOOLEAN_OPERATOR,	},
185 	{ "is-previous-event-false",		OP_PREVIOUS_EVENT_FALSE,				2,	3,			SEXP_BOOLEAN_OPERATOR,	},
186 	{ "is-previous-event-incomplete",	OP_PREVIOUS_EVENT_INCOMPLETE,			2,	3,			SEXP_BOOLEAN_OPERATOR,	},
187 
188 	//Objectives Category
189 	{ "is-destroyed",					OP_IS_DESTROYED,						1,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
190 	{ "is-destroyed-delay",				OP_IS_DESTROYED_DELAY,					2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
191 	{ "was-destroyed-by-delay",			OP_WAS_DESTROYED_BY_DELAY,				3,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,  },
192 	{ "is-subsystem-destroyed",			OP_IS_SUBSYSTEM_DESTROYED,				2,	2,			SEXP_BOOLEAN_OPERATOR,	},
193 	{ "is-subsystem-destroyed-delay",	OP_IS_SUBSYSTEM_DESTROYED_DELAY,		3,	3,			SEXP_BOOLEAN_OPERATOR,	},
194 	{ "is-disabled",					OP_IS_DISABLED,							1,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
195 	{ "is-disabled-delay",				OP_IS_DISABLED_DELAY,					2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
196 	{ "is-disarmed",					OP_IS_DISARMED,							1,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
197 	{ "is-disarmed-delay",				OP_IS_DISARMED_DELAY,					2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
198 	{ "has-docked",						OP_HAS_DOCKED,							3,	3,			SEXP_BOOLEAN_OPERATOR,	},
199 	{ "has-docked-delay",				OP_HAS_DOCKED_DELAY,					4,	4,			SEXP_BOOLEAN_OPERATOR,	},
200 	{ "has-undocked",					OP_HAS_UNDOCKED,						3,	3,			SEXP_BOOLEAN_OPERATOR,	},
201 	{ "has-undocked-delay",				OP_HAS_UNDOCKED_DELAY,					4,	4,			SEXP_BOOLEAN_OPERATOR,	},
202 	{ "has-arrived",					OP_HAS_ARRIVED,							1,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
203 	{ "has-arrived-delay",				OP_HAS_ARRIVED_DELAY,					2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
204 	{ "has-departed",					OP_HAS_DEPARTED,						1,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
205 	{ "has-departed-delay",				OP_HAS_DEPARTED_DELAY,					2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
206 	{ "are-waypoints-done",				OP_WAYPOINTS_DONE,						2,	2,			SEXP_BOOLEAN_OPERATOR,	},
207 	{ "are-waypoints-done-delay",		OP_WAYPOINTS_DONE_DELAY,				3,	4,			SEXP_BOOLEAN_OPERATOR,	},
208 	{ "is-nav-visited",					OP_NAV_IS_VISITED,						1,	1,			SEXP_BOOLEAN_OPERATOR,	}, // Kazan
209 	{ "ship-type-destroyed",			OP_SHIP_TYPE_DESTROYED,					2,	2,			SEXP_BOOLEAN_OPERATOR,	},
210 	{ "percent-ships-destroyed",		OP_PERCENT_SHIPS_DESTROYED,				2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
211 	{ "percent-ships-disabled",			OP_PERCENT_SHIPS_DISABLED,				2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
212 	{ "percent-ships-disarmed",			OP_PERCENT_SHIPS_DISARMED,				2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
213 	{ "percent-ships-departed",			OP_PERCENT_SHIPS_DEPARTED,				2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
214 	{ "percent-ships-arrived",			OP_PERCENT_SHIPS_ARRIVED,				2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
215 	{ "depart-node-delay",				OP_DEPART_NODE_DELAY,					3,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
216 	{ "destroyed-or-departed-delay",	OP_DESTROYED_DEPARTED_DELAY,			2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
217 
218 	//Status Category
219 	//Mission Sub-Category
220 	{ "num-ships-in-battle",			OP_NUM_SHIPS_IN_BATTLE,					0,	INT_MAX,	SEXP_INTEGER_OPERATOR,	},	//phreak modified by FUBAR
221 	{ "num-ships-in-wing",				OP_NUM_SHIPS_IN_WING,					1,	INT_MAX,	SEXP_INTEGER_OPERATOR,	},	// Karajorma
222 	{ "directive-value",				OP_DIRECTIVE_VALUE,						1,	2,			SEXP_INTEGER_OPERATOR,	},	// Karajorma
223 	{ "get-hotkey",						OP_GET_HOTKEY,							1,	1,			SEXP_INTEGER_OPERATOR,	},	// wookieejedi
224 
225 	//Player Sub-Category
226 	{ "was-promotion-granted",			OP_WAS_PROMOTION_GRANTED,				0,	1,			SEXP_BOOLEAN_OPERATOR,	},
227 	{ "was-medal-granted",				OP_WAS_MEDAL_GRANTED,					0,	1,			SEXP_BOOLEAN_OPERATOR,	},
228 	{ "skill-level-at-least",			OP_SKILL_LEVEL_AT_LEAST,				1,	1,			SEXP_BOOLEAN_OPERATOR,	},
229 	{ "num_kills",						OP_NUM_KILLS,							1,	1,			SEXP_INTEGER_OPERATOR,	},
230 	{ "num_assists",					OP_NUM_ASSISTS,							1,	1,			SEXP_INTEGER_OPERATOR,	},
231 	{ "num_type_kills",					OP_NUM_TYPE_KILLS,						2,	2,			SEXP_INTEGER_OPERATOR,	},
232 	{ "num_class_kills",				OP_NUM_CLASS_KILLS,						2,	2,			SEXP_INTEGER_OPERATOR,	},
233 	{ "ship_score",						OP_SHIP_SCORE,							1,	1,			SEXP_INTEGER_OPERATOR,	},
234 	{ "time-elapsed-last-order",		OP_LAST_ORDER_TIME,						2,	2,			SEXP_INTEGER_OPERATOR,	},
235 	{ "player-is-cheating",				OP_PLAYER_IS_CHEATING_BASTARD,			0,  0,			SEXP_BOOLEAN_OPERATOR,  },
236 	{ "is-language",					OP_IS_LANGUAGE,							1,	1,			SEXP_BOOLEAN_OPERATOR, }, // Goober5000
237 
238 	//Multiplayer Sub-Category
239 	{ "num-players",					OP_NUM_PLAYERS,							0,	0,			SEXP_INTEGER_OPERATOR,	},
240 	{ "team-score",						OP_TEAM_SCORE,							1,	1,			SEXP_INTEGER_OPERATOR,	},
241 	{ "ship-deaths",					OP_SHIP_DEATHS,							1,	1,			SEXP_INTEGER_OPERATOR,	},
242 	{ "respawns-left",					OP_RESPAWNS_LEFT,						1,	1,			SEXP_INTEGER_OPERATOR,	},
243 	{ "is-player",						OP_IS_PLAYER,							2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},	// Karajorma
244 
245 	//Ship Status Sub-Category
246 	{ "is-in-mission",					OP_IS_IN_MISSION,						1,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},	// Goober5000
247 	{ "is-docked",						OP_IS_DOCKED,							1,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},	// Goober5000
248 	{ "is-ship-visible",				OP_IS_SHIP_VISIBLE,						1,	2,			SEXP_BOOLEAN_OPERATOR,	},
249 	{ "is-ship-stealthy",				OP_IS_SHIP_STEALTHY,					1,	1,			SEXP_BOOLEAN_OPERATOR,	},
250 	{ "is-friendly-stealth-visible",	OP_IS_FRIENDLY_STEALTH_VISIBLE,			1,	1,			SEXP_BOOLEAN_OPERATOR,	},
251 	{ "is-iff",							OP_IS_IFF,								2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
252 	{ "is-ai-class",					OP_IS_AI_CLASS,							2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
253 	{ "is-ship-type",					OP_IS_SHIP_TYPE,						2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
254 	{ "is-ship-class",					OP_IS_SHIP_CLASS,						2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
255 	{ "is-facing",						OP_IS_FACING,							3,	4,			SEXP_BOOLEAN_OPERATOR,	},
256 	{ "is_tagged",						OP_IS_TAGGED,							1,	1,			SEXP_BOOLEAN_OPERATOR,	},
257 	{ "has-been-tagged-delay",			OP_HAS_BEEN_TAGGED_DELAY,				2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
258 	{ "are-ship-flags-set",				OP_ARE_SHIP_FLAGS_SET,					2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},	// Karajorma
259 
260 	//Shields, Engines and Weapons Sub-Category
261 	{ "has-primary-weapon",				OP_HAS_PRIMARY_WEAPON,					3,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},	// Karajorma
262 	{ "has-secondary-weapon",			OP_HAS_SECONDARY_WEAPON,				3,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},	// Karajorma
263 	{ "is-primary-selected",			OP_IS_PRIMARY_SELECTED,					2,	2,			SEXP_BOOLEAN_OPERATOR,	},
264 	{ "is-secondary-selected",			OP_IS_SECONDARY_SELECTED,				2,	2,			SEXP_BOOLEAN_OPERATOR,	},
265 	{ "primary-fired-since",			OP_PRIMARY_FIRED_SINCE,					3,	3,			SEXP_INTEGER_OPERATOR,	},	// Karajorma
266 	{ "secondary-fired-since",			OP_SECONDARY_FIRED_SINCE,				3,	3,			SEXP_INTEGER_OPERATOR,	},	// Karajorma
267 	{ "primary-ammo-pct",				OP_PRIMARY_AMMO_PCT,					2,	2,			SEXP_INTEGER_OPERATOR,	},
268 	{ "secondary-ammo-pct",				OP_SECONDARY_AMMO_PCT,					2,	2,			SEXP_INTEGER_OPERATOR,	},
269 	{ "get-primary-ammo",				OP_GET_PRIMARY_AMMO,					2,	2,			SEXP_INTEGER_OPERATOR,	}, // Karajorma
270 	{ "get-secondary-ammo",				OP_GET_SECONDARY_AMMO,					2,	2,			SEXP_INTEGER_OPERATOR,	}, // Karajorma
271 	{ "get-num-countermeasures",		OP_GET_NUM_COUNTERMEASURES,				1,	1,			SEXP_INTEGER_OPERATOR,	}, // Karajorma
272 	{ "weapon-energy-pct",				OP_WEAPON_ENERGY_LEFT,					1,	1,			SEXP_INTEGER_OPERATOR,	},
273 	{ "afterburner-energy-pct",			OP_AFTERBURNER_LEFT,					1,	1,			SEXP_INTEGER_OPERATOR,	},
274 	{ "shield-recharge-pct",			OP_SHIELD_RECHARGE_PCT,					1,	1,			SEXP_INTEGER_OPERATOR,	},
275 	{ "weapon-recharge-pct",			OP_WEAPON_RECHARGE_PCT,					1,	1,			SEXP_INTEGER_OPERATOR,	},
276 	{ "engine-recharge-pct",			OP_ENGINE_RECHARGE_PCT,					1,	1,			SEXP_INTEGER_OPERATOR,	},
277 	{ "shield-quad-low",				OP_SHIELD_QUAD_LOW,						2,	2,			SEXP_INTEGER_OPERATOR,	},
278 	{ "get-throttle-speed",				OP_GET_THROTTLE_SPEED,					1,	1,			SEXP_INTEGER_OPERATOR,	}, // Karajorma
279 	{ "current-speed",					OP_CURRENT_SPEED,						1,	1,			SEXP_INTEGER_OPERATOR,	},
280 
281 	//Cargo Sub-Category
282 	{ "is-cargo-known",					OP_IS_CARGO_KNOWN,						1,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
283 	{ "is-cargo-known-delay",			OP_CARGO_KNOWN_DELAY,					2,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
284 	{ "cap-subsys-cargo-known-delay",	OP_CAP_SUBSYS_CARGO_KNOWN_DELAY,		3,	INT_MAX,	SEXP_BOOLEAN_OPERATOR,	},
285 	{ "is-cargo",						OP_IS_CARGO,							2,	3,			SEXP_BOOLEAN_OPERATOR,	},
286 
287 	//Damage Sub-Category
288 	{ "shields-left",					OP_SHIELDS_LEFT,						1,	1,			SEXP_INTEGER_OPERATOR,	},
289 	{ "hits-left",						OP_HITS_LEFT,							1,	1,			SEXP_INTEGER_OPERATOR,	},
290 	{ "hits-left-subsystem",			OP_HITS_LEFT_SUBSYSTEM,					2,	3,			SEXP_INTEGER_OPERATOR,	},
291 	{ "hits-left-subsystem-generic",	OP_HITS_LEFT_SUBSYSTEM_GENERIC,			2,	2,			SEXP_INTEGER_OPERATOR,	},	// Goober5000
292 	{ "hits-left-subsystem-specific",	OP_HITS_LEFT_SUBSYSTEM_SPECIFIC,		2,	2,			SEXP_INTEGER_OPERATOR,	},	// Goober5000
293 	{ "sim-hits-left",					OP_SIM_HITS_LEFT,						1,	1,			SEXP_INTEGER_OPERATOR,	}, // Turey
294 	{ "get-damage-caused",				OP_GET_DAMAGE_CAUSED,					2,	INT_MAX,	SEXP_INTEGER_OPERATOR,	},
295 
296 	//Distance and Coordinates Sub-Category
297 	{ "distance",						OP_DISTANCE,							2,	2,			SEXP_INTEGER_OPERATOR,	},
298 	{ "distance-to-center",				OP_DISTANCE_CENTER,						2,	2,			SEXP_INTEGER_OPERATOR, },	// Goober5000
299 	{ "distance-to-bbox",				OP_DISTANCE_BBOX,						2,	2,			SEXP_INTEGER_OPERATOR, },	// Goober5000
300 	{ "distance-center-to-subsystem",	OP_DISTANCE_CENTER_SUBSYSTEM,			3,	3,			SEXP_INTEGER_OPERATOR,	},	// Goober5000
301 	{ "distance-bbox-to-subsystem",		OP_DISTANCE_BBOX_SUBSYSTEM,				3,	3,			SEXP_INTEGER_OPERATOR, },	// Goober5000
302 	{ "distance-to-nav",				OP_NAV_DISTANCE,						1,	1,			SEXP_INTEGER_OPERATOR,	},	// Kazan
303 	{ "num-within-box",					OP_NUM_WITHIN_BOX,						7,	INT_MAX,	SEXP_INTEGER_OPERATOR,	},	//WMC
304 	{ "is-in-box",						OP_IS_IN_BOX,							7,	8,			SEXP_INTEGER_OPERATOR,	},	//Sushi
305 	{ "special-warp-dist",				OP_SPECIAL_WARP_DISTANCE,				1,	1,			SEXP_INTEGER_OPERATOR,	},
306 	{ "get-object-x",					OP_GET_OBJECT_X,						1,	5,			SEXP_INTEGER_OPERATOR,	},	// Goober5000
307 	{ "get-object-y",					OP_GET_OBJECT_Y,						1,	5,			SEXP_INTEGER_OPERATOR,	},	// Goober5000
308 	{ "get-object-z",					OP_GET_OBJECT_Z,						1,	5,			SEXP_INTEGER_OPERATOR,	},	// Goober5000
309 	{ "get-object-pitch",				OP_GET_OBJECT_PITCH,					1,	1,			SEXP_INTEGER_OPERATOR,	},	// Goober5000
310 	{ "get-object-bank",				OP_GET_OBJECT_BANK,						1,	1,			SEXP_INTEGER_OPERATOR,	},	// Goober5000
311 	{ "get-object-heading",				OP_GET_OBJECT_HEADING,					1,	1,			SEXP_INTEGER_OPERATOR,	},	// Goober5000
312 	{ "get-object-speed-x",				OP_GET_OBJECT_SPEED_X,					1,	2,			SEXP_INTEGER_OPERATOR,	},
313 	{ "get-object-speed-y",				OP_GET_OBJECT_SPEED_Y,					1,	2,			SEXP_INTEGER_OPERATOR,	},
314 	{ "get-object-speed-z",				OP_GET_OBJECT_SPEED_Z,					1,	2,			SEXP_INTEGER_OPERATOR,	},
315 
316 	//Variables Sub-Category
317 	{ "string-to-int",					OP_STRING_TO_INT,						1,	1,			SEXP_INTEGER_OPERATOR,	}, // Karajorma
318 	{ "string-get-length",				OP_STRING_GET_LENGTH,					1,	1,			SEXP_INTEGER_OPERATOR,	}, // Goober5000
319 
320 	//Other Sub-Category
321 	{ "script-eval-num",				OP_SCRIPT_EVAL_NUM,						1,	1,			SEXP_INTEGER_OPERATOR,	},
322 	{ "script-eval-string",				OP_SCRIPT_EVAL_STRING,					2,	2,			SEXP_ACTION_OPERATOR,	},
323 
324 	//Time Category
325 	{ "time-ship-destroyed",			OP_TIME_SHIP_DESTROYED,					1,	1,			SEXP_INTEGER_OPERATOR,	},
326 	{ "time-ship-arrived",				OP_TIME_SHIP_ARRIVED,					1,	1,			SEXP_INTEGER_OPERATOR,	},
327 	{ "time-ship-departed",				OP_TIME_SHIP_DEPARTED,					1,	1,			SEXP_INTEGER_OPERATOR,	},
328 	{ "time-wing-destroyed",			OP_TIME_WING_DESTROYED,					1,	1,			SEXP_INTEGER_OPERATOR,	},
329 	{ "time-wing-arrived",				OP_TIME_WING_ARRIVED,					1,	1,			SEXP_INTEGER_OPERATOR,	},
330 	{ "time-wing-departed",				OP_TIME_WING_DEPARTED,					1,	1,			SEXP_INTEGER_OPERATOR,	},
331 	{ "mission-time",					OP_MISSION_TIME,						0,	0,			SEXP_INTEGER_OPERATOR,	},
332 	{ "mission-time-msecs",				OP_MISSION_TIME_MSECS,					0,	0,			SEXP_INTEGER_OPERATOR,	},	// Goober5000
333 	{ "time-docked",					OP_TIME_DOCKED,							3,	3,			SEXP_INTEGER_OPERATOR,	},
334 	{ "time-undocked",					OP_TIME_UNDOCKED,						3,	3,			SEXP_INTEGER_OPERATOR,	},
335 	{ "time-to-goal",					OP_TIME_TO_GOAL,						1,	1,			SEXP_INTEGER_OPERATOR,	},	// tcrayford
336 
337 	//Conditionals Category
338 	{ "cond",							OP_COND,								1,	INT_MAX,	SEXP_CONDITIONAL_OPERATOR,},
339 	{ "when",							OP_WHEN,								2,	INT_MAX,	SEXP_CONDITIONAL_OPERATOR,},
340 	{ "when-argument",					OP_WHEN_ARGUMENT,						3,	INT_MAX,	SEXP_CONDITIONAL_OPERATOR,},	// Goober5000
341 	{ "every-time",						OP_EVERY_TIME,							2,	INT_MAX,	SEXP_CONDITIONAL_OPERATOR,},	// Goober5000
342 	{ "every-time-argument",			OP_EVERY_TIME_ARGUMENT,					3,	INT_MAX,	SEXP_CONDITIONAL_OPERATOR,},	// Goober5000
343 	{ "if-then-else",					OP_IF_THEN_ELSE,						3,	INT_MAX,	SEXP_CONDITIONAL_OPERATOR,},	// Goober5000
344 	{ "functional-if-then-else",		OP_FUNCTIONAL_IF_THEN_ELSE,				3,	3,			SEXP_CONDITIONAL_OPERATOR, },	// Goober5000
345 	{ "switch",							OP_SWITCH,								2,	INT_MAX,	SEXP_CONDITIONAL_OPERATOR, },	// Goober5000
346 	{ "functional-switch",				OP_FUNCTIONAL_SWITCH,					2,	INT_MAX,	SEXP_CONDITIONAL_OPERATOR, },	// Goober5000
347 	{ "any-of",							OP_ANY_OF,								1,	INT_MAX,	SEXP_ARGUMENT_OPERATOR,	},	// Goober5000
348 	{ "every-of",						OP_EVERY_OF,							1,	INT_MAX,	SEXP_ARGUMENT_OPERATOR,	},	// Goober5000
349 	{ "random-of",						OP_RANDOM_OF,							1,	INT_MAX,	SEXP_ARGUMENT_OPERATOR,	},	// Goober5000
350 	{ "random-multiple-of",				OP_RANDOM_MULTIPLE_OF,					1,	INT_MAX,	SEXP_ARGUMENT_OPERATOR,	},	// Karajorma
351 	{ "number-of",						OP_NUMBER_OF,							2,	INT_MAX,	SEXP_ARGUMENT_OPERATOR,	},	// Goober5000
352 	{ "first-of",						OP_FIRST_OF,							2,	INT_MAX,	SEXP_ARGUMENT_OPERATOR, },	// MageKing17
353 	{ "in-sequence",					OP_IN_SEQUENCE,							1,	INT_MAX,	SEXP_ARGUMENT_OPERATOR,	},	// Karajorma
354 	{ "for-counter",					OP_FOR_COUNTER,							2,	3,			SEXP_ARGUMENT_OPERATOR,	},	// Goober5000
355 	{ "for-ship-class",					OP_FOR_SHIP_CLASS,						1,	INT_MAX,	SEXP_ARGUMENT_OPERATOR, },	// Goober5000
356 	{ "for-ship-type",					OP_FOR_SHIP_TYPE,						1,	INT_MAX,	SEXP_ARGUMENT_OPERATOR, },	// Goober5000
357 	{ "for-ship-team",					OP_FOR_SHIP_TEAM,						1,	INT_MAX,	SEXP_ARGUMENT_OPERATOR, },	// Goober5000
358 	{ "for-ship-species",				OP_FOR_SHIP_SPECIES,					1,	INT_MAX,	SEXP_ARGUMENT_OPERATOR, },	// Goober5000
359 	{ "for-players",					OP_FOR_PLAYERS,							0,	0,			SEXP_ARGUMENT_OPERATOR, },	// Goober5000
360 	{ "invalidate-argument",			OP_INVALIDATE_ARGUMENT,					1,	INT_MAX,	SEXP_ACTION_OPERATOR,		},	// Goober5000
361 	{ "invalidate-all-arguments",		OP_INVALIDATE_ALL_ARGUMENTS,			0,	0,			SEXP_ACTION_OPERATOR,	},	// Karajorma
362 	{ "validate-argument",				OP_VALIDATE_ARGUMENT,					1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Karajorma
363 	{ "validate-all-arguments",			OP_VALIDATE_ALL_ARGUMENTS,				0,	0,			SEXP_ACTION_OPERATOR,	},	// Karajorma
364 	{ "do-for-valid-arguments",			OP_DO_FOR_VALID_ARGUMENTS,				1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Karajorma
365 	{ "num-valid-arguments",			OP_NUM_VALID_ARGUMENTS,					0,	0,			SEXP_ACTION_OPERATOR,	},	// Karajorma
366 
367 	//Change Category
368 	//Messaging Sub-Category
369 	{ "send-message",					OP_SEND_MESSAGE,						3,	3,			SEXP_ACTION_OPERATOR,	},
370 	{ "send-message-list",				OP_SEND_MESSAGE_LIST,					4,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
371 	{ "send-message-chain",				OP_SEND_MESSAGE_CHAIN,					5,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Goober5000
372 	{ "send-random-message",			OP_SEND_RANDOM_MESSAGE,					3,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
373 	{ "scramble-messages",				OP_SCRAMBLE_MESSAGES,					0,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
374 	{ "unscramble-messages",			OP_UNSCRAMBLE_MESSAGES,					0,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
375 	{ "disable-builtin-messages",		OP_DISABLE_BUILTIN_MESSAGES,			0,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Karajorma
376 	{ "enable-builtin-messages",		OP_ENABLE_BUILTIN_MESSAGES,				0,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Karajorma
377 	{ "set-persona",					OP_SET_PERSONA,							2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Karajorma
378 	{ "set-death-message",				OP_SET_DEATH_MESSAGE,					1,	1,			SEXP_ACTION_OPERATOR,	},	// Goober5000
379 	{ "set-mission-mood",				OP_SET_MISSION_MOOD,					1,	1,			SEXP_ACTION_OPERATOR,	},	// Karajorma
380 
381 
382 	//AI Control Sub-Category
383 	{ "add-goal",						OP_ADD_GOAL,							2,	2,			SEXP_ACTION_OPERATOR,	},
384 	{ "remove-goal",					OP_REMOVE_GOAL,							2,	2,			SEXP_ACTION_OPERATOR,	},	// Goober5000
385 	{ "add-ship-goal",					OP_ADD_SHIP_GOAL,						2,	2,			SEXP_ACTION_OPERATOR,	},
386 	{ "add-wing-goal",					OP_ADD_WING_GOAL,						2,	2,			SEXP_ACTION_OPERATOR,	},
387 	{ "clear-goals",					OP_CLEAR_GOALS,							1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
388 	{ "clear-ship-goals",				OP_CLEAR_SHIP_GOALS,					1,	1,			SEXP_ACTION_OPERATOR,	},
389 	{ "clear-wing-goals",				OP_CLEAR_WING_GOALS,					1,	1,			SEXP_ACTION_OPERATOR,	},
390 	{ "good-rearm-time",				OP_GOOD_REARM_TIME,						2,	2,			SEXP_ACTION_OPERATOR,	},
391 	{ "good-secondary-time",			OP_GOOD_SECONDARY_TIME,					4,	4,			SEXP_ACTION_OPERATOR,	},
392 	{ "change-ai-class",				OP_CHANGE_AI_CLASS,						2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
393 	{ "player-use-ai",					OP_PLAYER_USE_AI,						0,	0,			SEXP_ACTION_OPERATOR,	},	// Goober5000
394 	{ "player-not-use-ai",				OP_PLAYER_NOT_USE_AI,					0,	0,			SEXP_ACTION_OPERATOR,	},	// Goober5000
395 	{ "set-player-orders",				OP_SET_PLAYER_ORDERS,					3,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Karajorma
396 	{ "cap-waypoint-speed",				OP_CAP_WAYPOINT_SPEED,					2,	2,			SEXP_ACTION_OPERATOR,	},
397 
398 	//Ship Status Sub-Category
399 	{ "protect-ship",					OP_PROTECT_SHIP,						1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
400 	{ "unprotect-ship",					OP_UNPROTECT_SHIP,						1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
401 	{ "beam-protect-ship",				OP_BEAM_PROTECT_SHIP,					1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
402 	{ "beam-unprotect-ship",			OP_BEAM_UNPROTECT_SHIP,					1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
403 	{ "turret-protect-ship",			OP_TURRET_PROTECT_SHIP,					2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Goober5000
404 	{ "turret-unprotect-ship",			OP_TURRET_UNPROTECT_SHIP,				2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Goober5000
405 	{ "ship-invisible",					OP_SHIP_INVISIBLE,						1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
406 	{ "ship-visible",					OP_SHIP_VISIBLE,						1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
407 	{ "ship-stealthy",					OP_SHIP_STEALTHY,						1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
408 	{ "ship-unstealthy",				OP_SHIP_UNSTEALTHY,						1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Goober5000
409 	{ "friendly-stealth-invisible",		OP_FRIENDLY_STEALTH_INVISIBLE,			1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Goober5000
410 	{ "friendly-stealth-visible",		OP_FRIENDLY_STEALTH_VISIBLE,			1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Goober5000
411 	{ "primitive-sensors-set-range",	OP_PRIMITIVE_SENSORS_SET_RANGE,			2,	2,			SEXP_ACTION_OPERATOR,	},	// Goober5000
412 	{ "ship-targetable-as-bomb",		OP_SHIP_BOMB_TARGETABLE,				1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
413 	{ "ship-untargetable-as-bomb",		OP_SHIP_BOMB_UNTARGETABLE,				1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
414 	{ "kamikaze",						OP_KAMIKAZE,							2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Sesquipedalian
415 	{ "change-iff",						OP_CHANGE_IFF,							2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
416 	{ "change-iff-color",				OP_CHANGE_IFF_COLOR,					6,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
417 	{ "add-remove-escort",				OP_ADD_REMOVE_ESCORT,					2,	2,			SEXP_ACTION_OPERATOR,	},
418 	{ "ship-change-alt-name",			OP_SHIP_CHANGE_ALT_NAME,				2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Goober5000
419 	{ "ship-change-callsign",			OP_SHIP_CHANGE_CALLSIGN,				2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// FUBAR
420 	{ "ship-tag",						OP_SHIP_TAG,							3,	8,			SEXP_ACTION_OPERATOR,	},	// Goober5000
421 	{ "ship-untag",						OP_SHIP_UNTAG,							1,	1,			SEXP_ACTION_OPERATOR,	},	// Goober5000
422 	{ "set-arrival-info",				OP_SET_ARRIVAL_INFO,					2,	7,			SEXP_ACTION_OPERATOR,	},	// Goober5000
423 	{ "set-departure-info",				OP_SET_DEPARTURE_INFO,					2,	6,			SEXP_ACTION_OPERATOR,	},	// Goober5000
424 
425 	//Shields, Engines and Weapons Sub-Category
426 	{ "set-weapon-energy",				OP_SET_WEAPON_ENERGY,					2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Karajorma
427 	{ "set-shield-energy",				OP_SET_SHIELD_ENERGY,					2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Karajorma
428 	{ "set-player-throttle-speed",		OP_SET_PLAYER_THROTTLE_SPEED,			2,	2,			SEXP_ACTION_OPERATOR,	},	// CommanderDJ
429 	{ "set-afterburner-energy",			OP_SET_AFTERBURNER_ENERGY,				2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Karajorma
430 	{ "set-subspace-drive",				OP_SET_SUBSPACE_DRIVE,					2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
431 	{ "set-primary-weapon",				OP_SET_PRIMARY_WEAPON,					3,	5,			SEXP_ACTION_OPERATOR,	},	// Karajorma
432 	{ "set-secondary-weapon",			OP_SET_SECONDARY_WEAPON,				3,	5,			SEXP_ACTION_OPERATOR,	},	// Karajorma
433 	{ "set-primary-ammo",				OP_SET_PRIMARY_AMMO,					3,	4,			SEXP_ACTION_OPERATOR,	},	// Karajorma
434 	{ "set-secondary-ammo",				OP_SET_SECONDARY_AMMO,					3,	4,			SEXP_ACTION_OPERATOR,	},	// Karajorma
435 	{ "set-num-countermeasures",		OP_SET_NUM_COUNTERMEASURES,				2,	2,			SEXP_ACTION_OPERATOR,	},	// Karajorma
436 	{ "lock-primary-weapon",			OP_LOCK_PRIMARY_WEAPON,					1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Karajorma
437 	{ "unlock-primary-weapon",			OP_UNLOCK_PRIMARY_WEAPON,				1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Karajorma
438 	{ "lock-secondary-weapon",			OP_LOCK_SECONDARY_WEAPON,				1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Karajorma
439 	{ "unlock-secondary-weapon",		OP_UNLOCK_SECONDARY_WEAPON,				1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Karajorma
440 	{ "lock-afterburner",				OP_LOCK_AFTERBURNER,					1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// KeldorKatarn
441 	{ "unlock-afterburner",				OP_UNLOCK_AFTERBURNER,					1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// KeldorKatarn
442 	{ "shields-on",						OP_SHIELDS_ON,							1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Sesquipedalian
443 	{ "shields-off",					OP_SHIELDS_OFF,							1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Sesquipedalian
444 	{ "force-glide",					OP_FORCE_GLIDE,							2,	2,			SEXP_ACTION_OPERATOR,	},	// The E
445 	{ "disable-ets",					OP_DISABLE_ETS,							1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// The E
446 	{ "enable-ets",						OP_ENABLE_ETS,							1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// The E
447 	{ "break-warp",						OP_WARP_BROKEN,							1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
448 	{ "fix-warp",						OP_WARP_NOT_BROKEN,						1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
449 	{ "never-warp",						OP_WARP_NEVER,							1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
450 	{ "allow-warp",						OP_WARP_ALLOWED,						1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
451 	{ "special-warpout-name",			OP_SET_SPECIAL_WARPOUT_NAME,			2,	2,			SEXP_ACTION_OPERATOR,	},
452 	{ "get-ets-value",					OP_GET_ETS_VALUE,						2,	2,			SEXP_ACTION_OPERATOR,	},	// niffiwan
453 	{ "set-ets-values",					OP_SET_ETS_VALUES,						4,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// niffiwan
454 	{ "get-power-output",				OP_GET_POWER_OUTPUT,					1,	1,			SEXP_ACTION_OPERATOR,	},	// The E
455 
456 	//Subsystems and Health Sub-Category
457 	{ "ship-invulnerable",				OP_SHIP_INVULNERABLE,					1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
458 	{ "ship-vulnerable",				OP_SHIP_VULNERABLE,						1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
459 	{ "ship-guardian",					OP_SHIP_GUARDIAN,						1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
460 	{ "ship-no-guardian",				OP_SHIP_NO_GUARDIAN,					1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
461 	{ "ship-guardian-threshold",		OP_SHIP_GUARDIAN_THRESHOLD,				2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
462 	{ "ship-subsys-guardian-threshold",	OP_SHIP_SUBSYS_GUARDIAN_THRESHOLD,		3,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
463 	{ "self-destruct",					OP_SELF_DESTRUCT,						1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
464 	{ "destroy-instantly",				OP_DESTROY_INSTANTLY,					1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Admiral MS
465 	{ "destroy-subsys-instantly",		OP_DESTROY_SUBSYS_INSTANTLY,			2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Admiral MS
466 	{ "sabotage-subsystem",				OP_SABOTAGE_SUBSYSTEM,					3,	3,			SEXP_ACTION_OPERATOR,	},
467 	{ "repair-subsystem",				OP_REPAIR_SUBSYSTEM,					3,	5,			SEXP_ACTION_OPERATOR,	},
468 	{ "ship-copy-damage",				OP_SHIP_COPY_DAMAGE,					2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Goober5000
469 	{ "set-subsystem-strength",			OP_SET_SUBSYSTEM_STRNGTH,				3,	5,			SEXP_ACTION_OPERATOR,	},
470 	{ "subsys-set-random",				OP_SUBSYS_SET_RANDOM,					3,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
471 	{ "lock-rotating-subsystem",		OP_LOCK_ROTATING_SUBSYSTEM,				2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Goober5000
472 	{ "free-rotating-subsystem",		OP_FREE_ROTATING_SUBSYSTEM,				2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Goober5000
473 	{ "reverse-rotating-subsystem",		OP_REVERSE_ROTATING_SUBSYSTEM,			2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Goober5000
474 	{ "rotating-subsys-set-turn-time",	OP_ROTATING_SUBSYS_SET_TURN_TIME,		3,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Goober5000
475 	{ "trigger-submodel-animation",		OP_TRIGGER_SUBMODEL_ANIMATION,			4,	6,			SEXP_ACTION_OPERATOR,	},	// Goober5000
476 	{ "change-subsystem-name",			OP_CHANGE_SUBSYSTEM_NAME,				3,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Karajorma
477 	{ "ship-subsys-targetable",			OP_SHIP_SUBSYS_TARGETABLE,				2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Goober5000
478 	{ "ship-subsys-untargetable",		OP_SHIP_SUBSYS_UNTARGETABLE,			2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Goober5000
479 	{ "ship-subsys-no-replace",			OP_SHIP_SUBSYS_NO_REPLACE,				3,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// FUBAR
480 	{ "ship-subsys-no-live-debris",		OP_SHIP_SUBSYS_NO_LIVE_DEBRIS,			3,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// FUBAR
481 	{ "ship-subsys-vanish",				OP_SHIP_SUBSYS_VANISHED,				3,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// FUBAR
482 	{ "ship-subsys-ignore_if_dead",		OP_SHIP_SUBSYS_IGNORE_IF_DEAD,			3,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// FUBAR
483 	{ "awacs-set-radius",				OP_AWACS_SET_RADIUS,					3,	3,			SEXP_ACTION_OPERATOR,	},
484 	{ "alter-ship-flag",				OP_ALTER_SHIP_FLAG,						3,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Karajorma
485 
486 	//Cargo Sub-Category
487 	{ "transfer-cargo",					OP_TRANSFER_CARGO,						2,	2,			SEXP_ACTION_OPERATOR,	},
488 	{ "exchange-cargo",					OP_EXCHANGE_CARGO,						2,	2,			SEXP_ACTION_OPERATOR,	},
489 	{ "set-cargo",						OP_SET_CARGO,							2,	3,			SEXP_ACTION_OPERATOR,	},
490 	{ "jettison-cargo-delay",			OP_JETTISON_CARGO_DELAY,				2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
491 	{ "jettison-cargo",					OP_JETTISON_CARGO_NEW,					1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Goober5000
492 	{ "set-docked",						OP_SET_DOCKED,							4,	4,			SEXP_ACTION_OPERATOR,	},	// Sushi
493 	{ "cargo-no-deplete",				OP_CARGO_NO_DEPLETE,					1,	2,			SEXP_ACTION_OPERATOR,	},
494 	{ "set-scanned",					OP_SET_SCANNED,							1,	2,			SEXP_ACTION_OPERATOR,	},
495 	{ "set-unscanned",					OP_SET_UNSCANNED,						1,	2,			SEXP_ACTION_OPERATOR,	},
496 
497 	//Armor and Damage Types Sub-Category
498 	{ "set-armor-type",					OP_SET_ARMOR_TYPE,						4,	INT_MAX,	SEXP_ACTION_OPERATOR,	},  // FUBAR
499 	{ "weapon-set-damage-type",			OP_WEAPON_SET_DAMAGE_TYPE,				4,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// FUBAR
500 	{ "ship-set-damage-type",			OP_SHIP_SET_DAMAGE_TYPE,				4,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// FUBAR
501 	{ "ship-set-shockwave-damage-type",	OP_SHIP_SHOCKWAVE_SET_DAMAGE_TYPE,		3,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// FUBAR
502 	{ "field-set-damage-type",			OP_FIELD_SET_DAMAGE_TYPE,				2,	2,			SEXP_ACTION_OPERATOR,	},	// FUBAR
503 
504 	//Beams and Turrets Sub-Category
505 	{ "fire-beam",						OP_BEAM_FIRE,							3,	5,			SEXP_ACTION_OPERATOR,	},
506 	{ "fire-beam-at-coordinates",		OP_BEAM_FIRE_COORDS,					5,	9,			SEXP_ACTION_OPERATOR,	},
507 	{ "beam-create",					OP_BEAM_FLOATING_FIRE,					7,	14,			SEXP_ACTION_OPERATOR,	},
508 	{ "beam-free",						OP_BEAM_FREE,							2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
509 	{ "beam-free-all",					OP_BEAM_FREE_ALL,						1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
510 	{ "beam-lock",						OP_BEAM_LOCK,							2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
511 	{ "beam-lock-all",					OP_BEAM_LOCK_ALL,						1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
512 	{ "turret-free",					OP_TURRET_FREE,							2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
513 	{ "turret-free-all",				OP_TURRET_FREE_ALL,						1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
514 	{ "turret-lock",					OP_TURRET_LOCK,							2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
515 	{ "turret-lock-all",				OP_TURRET_LOCK_ALL,						1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
516 	{ "turret-tagged-only",				OP_TURRET_TAGGED_ONLY_ALL,				1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
517 	{ "turret-tagged-clear",			OP_TURRET_TAGGED_CLEAR_ALL,				1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
518 	{ "turret-tagged-specific",			OP_TURRET_TAGGED_SPECIFIC,				2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	//phreak
519 	{ "turret-tagged-clear-specific",	OP_TURRET_TAGGED_CLEAR_SPECIFIC,		2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	//phreak
520 	{ "turret-change-weapon",			OP_TURRET_CHANGE_WEAPON,				5,	5,			SEXP_ACTION_OPERATOR,	},	//WMC
521 	{ "turret-set-direction-preference",OP_TURRET_SET_DIRECTION_PREFERENCE,		3,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	//FUBAR
522 	{ "turret-set-rate-of-fire",		OP_TURRET_SET_RATE_OF_FIRE,				3,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	//FUBAR
523 	{ "turret-set-optimum-range",		OP_TURRET_SET_OPTIMUM_RANGE,			3,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	//FUBAR
524 	{ "turret-set-target-priorities",	OP_TURRET_SET_TARGET_PRIORITIES,		3,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	//FUBAR
525 	{ "turret-set-inaccuracy",	        OP_TURRET_SET_INACCURACY,		        2,	INT_MAX,	SEXP_ACTION_OPERATOR,   },	// Asteroth
526 	{ "turret-set-target-order",		OP_TURRET_SET_TARGET_ORDER,				2,	2+ NUM_TURRET_ORDER_TYPES,	SEXP_ACTION_OPERATOR,	},	//WMC
527 	{ "ship-turret-target-order",		OP_SHIP_TURRET_TARGET_ORDER,			1,	1+ NUM_TURRET_ORDER_TYPES,	SEXP_ACTION_OPERATOR,	},	//WMC
528 	{ "turret-subsys-target-disable",	OP_TURRET_SUBSYS_TARGET_DISABLE,		2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
529 	{ "turret-subsys-target-enable",	OP_TURRET_SUBSYS_TARGET_ENABLE,			2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
530 	{ "turret-set-forced-target",	    OP_TURRET_SET_FORCED_TARGET,			3,	INT_MAX,	SEXP_ACTION_OPERATOR,   },  // Asteroth
531 	{ "turret-set-forced-subsys-target",OP_TURRET_SET_FORCED_SUBSYS_TARGET,		4,	INT_MAX,	SEXP_ACTION_OPERATOR, },  // Asteroth
532 	{ "turret-clear-forced-target",	    OP_TURRET_CLEAR_FORCED_TARGET,			2,	INT_MAX,	SEXP_ACTION_OPERATOR,   },  // Asteroth
533 	{ "turret-set-primary-ammo",		OP_TURRET_SET_PRIMARY_AMMO,				4,	4,			SEXP_ACTION_OPERATOR,	},	// DahBlount
534 	{ "turret-set-secondary-ammo",		OP_TURRET_SET_SECONDARY_AMMO,			4,	4,			SEXP_ACTION_OPERATOR,	},	// DahBlount
535 	{ "turret-get-primary-ammo",		OP_TURRET_GET_PRIMARY_AMMO,				3,	3,			SEXP_INTEGER_OPERATOR,	},	// DahBlount
536 	{ "turret-get-secondary-ammo",		OP_TURRET_GET_SECONDARY_AMMO,			3,	3,			SEXP_INTEGER_OPERATOR,	},	// DahBlount
537 	{ "is-in-turret-fov",				OP_IS_IN_TURRET_FOV,					3,	4,			SEXP_BOOLEAN_OPERATOR,	},	// Goober5000
538 
539 	//Models and Textures Sub-Category
540 	{ "change-ship-class",				OP_CHANGE_SHIP_CLASS,					2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Goober5000
541 	{ "deactivate-glow-maps",			OP_DEACTIVATE_GLOW_MAPS,				1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	//-Bobboau
542 	{ "activate-glow-maps",				OP_ACTIVATE_GLOW_MAPS,					1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	//-Bobboau
543 	{ "deactivate-glow-points",			OP_DEACTIVATE_GLOW_POINTS,				1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	//-Bobboau
544 	{ "activate-glow-points",			OP_ACTIVATE_GLOW_POINTS,				1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	//-Bobboau
545 	{ "deactivate-glow-point-bank",		OP_DEACTIVATE_GLOW_POINT_BANK,			2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	//-Bobboau
546 	{ "activate-glow-point-bank",		OP_ACTIVATE_GLOW_POINT_BANK,			2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	//-Bobboau
547 	{ "set-thrusters-status",			OP_SET_THRUSTERS,						2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// The E
548 	{ "don't-collide-invisible",		OP_DONT_COLLIDE_INVISIBLE,				1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Goober5000
549 	{ "collide-invisible",				OP_COLLIDE_INVISIBLE,					1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Goober5000
550 	{ "add-to-collision-group",			OP_ADD_TO_COLGROUP,						2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// The E
551 	{ "remove-from-collision-group",	OP_REMOVE_FROM_COLGROUP,				2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// The E
552 	{ "add-to-collision-group2",		OP_ADD_TO_COLGROUP2,					2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Goober5000
553 	{ "remove-from-collision-group2",	OP_REMOVE_FROM_COLGROUP2,				2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Goober5000
554 	{ "get-collision-group",			OP_GET_COLGROUP_ID,						1,	1,			SEXP_ACTION_OPERATOR,	},
555 	{ "change-team-color",				OP_CHANGE_TEAM_COLOR,					3,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// The E
556 	{ "replace-texture",				OP_REPLACE_TEXTURE,						3,  INT_MAX,	SEXP_ACTION_OPERATOR,   },  // Lafiel
557 
558 	//Coordinate Manipulation Sub-Category
559 	{ "set-object-position",			OP_SET_OBJECT_POSITION,					4,	4,			SEXP_ACTION_OPERATOR,	},	// WMC
560 	{ "set-object-orientation",			OP_SET_OBJECT_ORIENTATION,				4,	4,			SEXP_ACTION_OPERATOR,	},	// Goober5000
561 	{ "set-object-facing",				OP_SET_OBJECT_FACING,					4,	6,			SEXP_ACTION_OPERATOR,	},	// Goober5000
562 	{ "set-object-facing-object",		OP_SET_OBJECT_FACING_OBJECT,			2,	4,			SEXP_ACTION_OPERATOR,	},	// Goober5000
563 	{ "set-object-speed-x",				OP_SET_OBJECT_SPEED_X,					2,	3,			SEXP_ACTION_OPERATOR,	},	// WMC // Deprecated by wookieejedi
564 	{ "set-object-speed-y",				OP_SET_OBJECT_SPEED_Y,					2,	3,			SEXP_ACTION_OPERATOR,	},	// WMC // Deprecated by wookieejedi
565 	{ "set-object-speed-z",				OP_SET_OBJECT_SPEED_Z,					2,	3,			SEXP_ACTION_OPERATOR,	},	// WMC // Deprecated by wookieejedi
566 	{ "ship-maneuver",					OP_SHIP_MANEUVER,						10, 11,			SEXP_ACTION_OPERATOR,	},	// Wanderer
567 	{ "ship-rot-maneuver",				OP_SHIP_ROT_MANEUVER,					6,	7,			SEXP_ACTION_OPERATOR,	},	// Wanderer
568 	{ "ship-lat-maneuver",				OP_SHIP_LAT_MANEUVER,					6,	7,			SEXP_ACTION_OPERATOR,	},	// Wanderer
569 	{ "set-immobile",					OP_SET_IMMOBILE,						1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Goober5000
570 	{ "set-mobile",						OP_SET_MOBILE,							1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Goober5000
571 
572 	//Mission and Campaign Sub-Category
573 	{ "invalidate-goal",				OP_INVALIDATE_GOAL,						1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
574 	{ "validate-goal",					OP_VALIDATE_GOAL,						1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
575 	{ "red-alert",						OP_RED_ALERT,							0,	0,			SEXP_ACTION_OPERATOR,	},
576 	{ "end-mission",					OP_END_MISSION,							0,	3,			SEXP_ACTION_OPERATOR,	},	//-Sesquipedalian
577 	{ "force-jump",						OP_FORCE_JUMP,							0,	0,			SEXP_ACTION_OPERATOR,	},	// Goober5000
578 	{ "next-mission",					OP_NEXT_MISSION,						1,	1,			SEXP_ACTION_OPERATOR,	},
579 	{ "end-campaign",					OP_END_CAMPAIGN,						0,	1,			SEXP_ACTION_OPERATOR,	},
580 	{ "end-of-campaign",				OP_END_OF_CAMPAIGN,						0,	0,			SEXP_ACTION_OPERATOR,	},
581 	{ "set-debriefing-toggled",			OP_SET_DEBRIEFING_TOGGLED,				1,	1,			SEXP_ACTION_OPERATOR,	},	// Goober5000
582 	{ "set-debriefing-persona",			OP_SET_DEBRIEFING_PERSONA,				1,	1,			SEXP_ACTION_OPERATOR,	},	// Goober5000
583 	{ "allow-treason",					OP_ALLOW_TREASON,						1,	1,			SEXP_ACTION_OPERATOR,	},	// Karajorma
584 	{ "grant-promotion",				OP_GRANT_PROMOTION,						0,	0,			SEXP_ACTION_OPERATOR,	},
585 	{ "grant-medal",					OP_GRANT_MEDAL,							1,	1,			SEXP_ACTION_OPERATOR,	},
586 	{ "allow-ship",						OP_ALLOW_SHIP,							1,	1,			SEXP_ACTION_OPERATOR,	},
587 	{ "allow-weapon",					OP_ALLOW_WEAPON,						1,	1,			SEXP_ACTION_OPERATOR,	},
588 	{ "tech-add-ships",					OP_TECH_ADD_SHIP,						1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
589 	{ "tech-add-weapons",				OP_TECH_ADD_WEAPON,						1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
590 	{ "tech-add-intel",					OP_TECH_ADD_INTEL,						1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Goober5000
591 	{ "tech-remove-intel",				OP_TECH_REMOVE_INTEL,					1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// wookieejedi
592 	{ "tech-add-intel-xstr",			OP_TECH_ADD_INTEL_XSTR,					2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Goober5000
593 	{ "tech-remove-intel-xstr",			OP_TECH_REMOVE_INTEL_XSTR,				2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// wookieejedi
594 	{ "tech-reset-to-default",			OP_TECH_RESET_TO_DEFAULT,				0,	0,			SEXP_ACTION_OPERATOR,	},	// Goober5000
595 	{ "change-player-score",			OP_CHANGE_PLAYER_SCORE,					2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Karajorma
596 	{ "change-team-score",				OP_CHANGE_TEAM_SCORE,					2,	2,			SEXP_ACTION_OPERATOR,	},	// Karajorma
597 	{ "set-respawns",					OP_SET_RESPAWNS,						2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Karajorma
598 	{ "add-remove-hotkey",				OP_ADD_REMOVE_HOTKEY,					3,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// wookieejedi
599 
600 	//Music and Sound Sub-Category
601 	{ "change-soundtrack",				OP_CHANGE_SOUNDTRACK,					1,	1,			SEXP_ACTION_OPERATOR,	},	// Goober5000
602 	{ "play-sound-from-table",			OP_PLAY_SOUND_FROM_TABLE,				4,	4,			SEXP_ACTION_OPERATOR,	},	// Goober5000
603 	{ "play-sound-from-file",			OP_PLAY_SOUND_FROM_FILE,				1,	4,			SEXP_ACTION_OPERATOR,	},	// Goober5000
604 	{ "close-sound-from-file",			OP_CLOSE_SOUND_FROM_FILE,				0,	2,			SEXP_ACTION_OPERATOR,	},	// Goober5000
605 	{ "pause-sound-from-file",			OP_PAUSE_SOUND_FROM_FILE,				1,	2,			SEXP_ACTION_OPERATOR,	},	// Goober5000
606 	{ "set-sound-environment",			OP_SET_SOUND_ENVIRONMENT,				1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Taylor
607 	{ "update-sound-environment",		OP_UPDATE_SOUND_ENVIRONMENT,			2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Taylor
608 	{ "adjust-audio-volume",			OP_ADJUST_AUDIO_VOLUME,					1,	3,			SEXP_ACTION_OPERATOR,	},
609 
610 	//HUD Sub-Category
611 	{ "hud-disable",					OP_HUD_DISABLE,							1,	1,			SEXP_ACTION_OPERATOR,	},	// Goober5000
612 	{ "hud-disable-except-messages",	OP_HUD_DISABLE_EXCEPT_MESSAGES,			1,	1,			SEXP_ACTION_OPERATOR,	},	// Goober5000
613 	{ "hud-set-custom-gauge-active",	OP_HUD_SET_CUSTOM_GAUGE_ACTIVE,			2, 	INT_MAX, 	SEXP_ACTION_OPERATOR,	},
614 	{ "hud-set-builtin-gauge-active",	OP_HUD_SET_BUILTIN_GAUGE_ACTIVE,		2, 	INT_MAX,	SEXP_ACTION_OPERATOR,	},
615 	{ "hud-set-text",					OP_HUD_SET_TEXT,						2,	2,			SEXP_ACTION_OPERATOR,	},	//WMCoolmon
616 	{ "hud-set-text-num",				OP_HUD_SET_TEXT_NUM,					2,	2,			SEXP_ACTION_OPERATOR,	},	//WMCoolmon
617 	{ "hud-set-message",				OP_HUD_SET_MESSAGE,						2,	2,			SEXP_ACTION_OPERATOR,	},	//The E
618 	{ "hud-set-directive",				OP_HUD_SET_DIRECTIVE,					2,	2,			SEXP_ACTION_OPERATOR,	},	//The E
619 	{ "hud-set-frame",					OP_HUD_SET_FRAME,						2,	2,			SEXP_ACTION_OPERATOR,	},	//WMCoolmon
620 	{ "hud-set-coords",					OP_HUD_SET_COORDS,						3,	3,			SEXP_ACTION_OPERATOR,	},	//WMCoolmon
621 	{ "hud-set-color",					OP_HUD_SET_COLOR,						4,	4,			SEXP_ACTION_OPERATOR,	},	//WMCoolmon
622 	{ "hud-display-gauge",				OP_HUD_DISPLAY_GAUGE,					2,	2,			SEXP_ACTION_OPERATOR,	},
623 	{ "hud-gauge-set-active",			OP_HUD_GAUGE_SET_ACTIVE,				2,	2,			SEXP_ACTION_OPERATOR,	},	//Deprecated
624 	{ "hud-activate-gauge-type",		OP_HUD_ACTIVATE_GAUGE_TYPE,				2,	2,			SEXP_ACTION_OPERATOR,	},	//Deprecated
625 	{ "hud-clear-messages",				OP_HUD_CLEAR_MESSAGES,					0,	0,			SEXP_ACTION_OPERATOR,	},	// swifty
626 	{ "hud-set-max-targeting-range",	OP_HUD_SET_MAX_TARGETING_RANGE,			1,	1,			SEXP_ACTION_OPERATOR,	},	// Goober5000
627 
628 	//Nav Sub-Category
629 	{ "add-nav-waypoint",				OP_NAV_ADD_WAYPOINT,					3,	4,			SEXP_ACTION_OPERATOR,	},	//kazan
630 	{ "add-nav-ship",					OP_NAV_ADD_SHIP,						2,	2,			SEXP_ACTION_OPERATOR,	},	//kazan
631 	{ "del-nav",						OP_NAV_DEL,								1,	1,			SEXP_ACTION_OPERATOR,	},	//kazan
632 	{ "hide-nav",						OP_NAV_HIDE,							1,	1,			SEXP_ACTION_OPERATOR,	},	//kazan
633 	{ "restrict-nav",					OP_NAV_RESTRICT,						1,	1,			SEXP_ACTION_OPERATOR,	},	//kazan
634 	{ "unhide-nav",						OP_NAV_UNHIDE,							1,	1,			SEXP_ACTION_OPERATOR,	},	//kazan
635 	{ "unrestrict-nav",					OP_NAV_UNRESTRICT,						1,	1,			SEXP_ACTION_OPERATOR,	},	//kazan
636 	{ "set-nav-visited",				OP_NAV_SET_VISITED,						1,	1,			SEXP_ACTION_OPERATOR,	},	//kazan
637 	{ "unset-nav-visited",				OP_NAV_UNSET_VISITED,					1,	1,			SEXP_ACTION_OPERATOR,	},	//kazan
638 	{ "set-nav-carry",					OP_NAV_SET_CARRY,						1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	//kazan
639 	{ "unset-nav-carry",				OP_NAV_UNSET_CARRY,						1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	//kazan
640 	{ "set-nav-needslink",				OP_NAV_SET_NEEDSLINK,					1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	//kazan
641 	{ "unset-nav-needslink",			OP_NAV_UNSET_NEEDSLINK,					1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	//kazan
642 	{ "is-nav-linked",					OP_NAV_ISLINKED,						1,	1,			SEXP_BOOLEAN_OPERATOR,	},	//kazan
643 	{ "use-nav-cinematics",				OP_NAV_USECINEMATICS,					1,	1,			SEXP_ACTION_OPERATOR,	},	//kazan
644 	{ "use-autopilot",					OP_NAV_USEAP,							1,	1,			SEXP_ACTION_OPERATOR,	},	//kazan
645 	{ "select-nav",						OP_NAV_SELECT,							1,	1,			SEXP_ACTION_OPERATOR,	},	//Talon1024
646 	{ "unselect-nav",					OP_NAV_UNSELECT,						0,	0,			SEXP_ACTION_OPERATOR,	},	//Talon1024
647 
648 	//Cutscene Sub-Category
649 	{ "set-cutscene-bars",				OP_CUTSCENES_SET_CUTSCENE_BARS,			0,	1,			SEXP_ACTION_OPERATOR,	},
650 	{ "unset-cutscene-bars",			OP_CUTSCENES_UNSET_CUTSCENE_BARS,		0,	1,			SEXP_ACTION_OPERATOR,	},
651 	{ "fade-in",						OP_CUTSCENES_FADE_IN,					0,	4,			SEXP_ACTION_OPERATOR,	},
652 	{ "fade-out",						OP_CUTSCENES_FADE_OUT,					0,	4,			SEXP_ACTION_OPERATOR,	},
653 	{ "set-camera",						OP_CUTSCENES_SET_CAMERA,				0,	1,			SEXP_ACTION_OPERATOR,	},
654 	{ "set-camera-position",			OP_CUTSCENES_SET_CAMERA_POSITION,		3,	6,			SEXP_ACTION_OPERATOR,	},
655 	{ "set-camera-facing",				OP_CUTSCENES_SET_CAMERA_FACING,			3,	6,			SEXP_ACTION_OPERATOR,	},
656 	{ "set-camera-facing-object",		OP_CUTSCENES_SET_CAMERA_FACING_OBJECT,	1,	4,			SEXP_ACTION_OPERATOR,	},
657 	{ "set-camera-rotation",			OP_CUTSCENES_SET_CAMERA_ROTATION,		3,	6,			SEXP_ACTION_OPERATOR,	},
658 	{ "set-camera-host",				OP_CUTSCENES_SET_CAMERA_HOST,			1,	2,			SEXP_ACTION_OPERATOR,	},
659 	{ "set-camera-target",				OP_CUTSCENES_SET_CAMERA_TARGET,			1,	2,			SEXP_ACTION_OPERATOR,	},
660 	{ "set-camera-fov",					OP_CUTSCENES_SET_CAMERA_FOV,			1,	5,			SEXP_ACTION_OPERATOR,	},
661 	{ "set-fov",						OP_CUTSCENES_SET_FOV,					1,	1,			SEXP_ACTION_OPERATOR,	},
662 	{ "get-fov",						OP_CUTSCENES_GET_FOV,					0,	0,			SEXP_INTEGER_OPERATOR,	},
663 	{ "reset-fov",						OP_CUTSCENES_RESET_FOV,					0,	0,			SEXP_ACTION_OPERATOR,	},
664 	{ "reset-camera",					OP_CUTSCENES_RESET_CAMERA,				0,	1,			SEXP_ACTION_OPERATOR,	},
665 	{ "show-subtitle",					OP_CUTSCENES_SHOW_SUBTITLE,				4,	13,			SEXP_ACTION_OPERATOR,	},
666 	{ "show-subtitle-text",				OP_CUTSCENES_SHOW_SUBTITLE_TEXT,		6,	13,			SEXP_ACTION_OPERATOR,	},
667 	{ "show-subtitle-image",			OP_CUTSCENES_SHOW_SUBTITLE_IMAGE,		8,	10,			SEXP_ACTION_OPERATOR,	},
668 	{ "clear-subtitles",				OP_CLEAR_SUBTITLES,						0,	0,			SEXP_ACTION_OPERATOR,	},
669 	{ "lock-perspective",				OP_CUTSCENES_FORCE_PERSPECTIVE,			1,	2,			SEXP_ACTION_OPERATOR,	},
670 	{ "set-camera-shudder",				OP_SET_CAMERA_SHUDDER,					2,	2,			SEXP_ACTION_OPERATOR,	},
671 	{ "supernova-start",				OP_SUPERNOVA_START,						1,	1,			SEXP_ACTION_OPERATOR,	},
672 	{ "supernova-stop",					OP_SUPERNOVA_STOP,						0,	0,			SEXP_ACTION_OPERATOR,	},	//CommanderDJ
673 	{ "set-motion-debris-override",		OP_SET_MOTION_DEBRIS,					1,  1,			SEXP_ACTION_OPERATOR,	},	// The E
674 
675 	//Background and Nebula Sub-Category
676 	{ "mission-set-nebula",				OP_MISSION_SET_NEBULA,					1,	2,			SEXP_ACTION_OPERATOR,	},	// Sesquipedalian
677 	{ "mission-set-subspace",			OP_MISSION_SET_SUBSPACE,				1,	1,			SEXP_ACTION_OPERATOR,	},
678 	{ "change-background",				OP_CHANGE_BACKGROUND,					1,	1,			SEXP_ACTION_OPERATOR,	},	// Goober5000
679 	{ "add-background-bitmap",			OP_ADD_BACKGROUND_BITMAP,				8,	9,			SEXP_ACTION_OPERATOR,	},	// phreak
680 	{ "remove-background-bitmap",		OP_REMOVE_BACKGROUND_BITMAP,			1,	1,			SEXP_ACTION_OPERATOR,	},	// phreak
681 	{ "add-sun-bitmap",					OP_ADD_SUN_BITMAP,						5,	6,			SEXP_ACTION_OPERATOR,	},	// phreak
682 	{ "remove-sun-bitmap",				OP_REMOVE_SUN_BITMAP,					1,	1,			SEXP_ACTION_OPERATOR,	},	// phreak
683 	{ "nebula-change-storm",			OP_NEBULA_CHANGE_STORM,					1,	1,			SEXP_ACTION_OPERATOR,	},	// phreak
684 	{ "nebula-toggle-poof",				OP_NEBULA_TOGGLE_POOF,					2,	2,			SEXP_ACTION_OPERATOR,	},	// phreak
685 	{ "nebula-change-pattern",			OP_NEBULA_CHANGE_PATTERN,				1,	1,			SEXP_ACTION_OPERATOR,	},	// Axem
686 	{ "nebula-change-fog-color",		OP_NEBULA_CHANGE_FOG_COLOR,				3,	3,			SEXP_ACTION_OPERATOR,   },	// Asteroth
687 	{ "set-skybox-model",				OP_SET_SKYBOX_MODEL,					1,	8,			SEXP_ACTION_OPERATOR,	},	// taylor
688 	{ "set-skybox-orientation",			OP_SET_SKYBOX_ORIENT,					3,	3,			SEXP_ACTION_OPERATOR,	},	// Goober5000
689 	{ "set-ambient-light",				OP_SET_AMBIENT_LIGHT,					3,	3,			SEXP_ACTION_OPERATOR,	},	// Karajorma
690 
691 	//Jump Node Sub-Category
692 	{ "set-jumpnode-name",				OP_JUMP_NODE_SET_JUMPNODE_NAME,			2,	2,			SEXP_ACTION_OPERATOR,	},	//CommanderDJ
693 	{ "set-jumpnode-color",				OP_JUMP_NODE_SET_JUMPNODE_COLOR,		5,	5,			SEXP_ACTION_OPERATOR,	},
694 	{ "set-jumpnode-model",				OP_JUMP_NODE_SET_JUMPNODE_MODEL,		3,	3,			SEXP_ACTION_OPERATOR,	},
695 	{ "show-jumpnode",					OP_JUMP_NODE_SHOW_JUMPNODE,				1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
696 	{ "hide-jumpnode",					OP_JUMP_NODE_HIDE_JUMPNODE,				1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
697 
698 	//Special Effects Sub-Category
699 	{ "set-post-effect",				OP_SET_POST_EFFECT,						2,	5,			SEXP_ACTION_OPERATOR,	},	// Hery
700 	{ "reset-post-effects",				OP_RESET_POST_EFFECTS,					0,	0,			SEXP_ACTION_OPERATOR,	},	// Goober5000
701 	{ "ship-effect",					OP_SHIP_EFFECT,							3,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Valathil
702 	{ "ship-create",					OP_SHIP_CREATE,							5,	9,			SEXP_ACTION_OPERATOR,	},	// WMC
703 	{ "weapon-create",					OP_WEAPON_CREATE,						5,	10,			SEXP_ACTION_OPERATOR,	},	// Goober5000
704 	{ "ship-vanish",					OP_SHIP_VANISH,							1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
705 	{ "ship-vaporize",					OP_SHIP_VAPORIZE,						1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Goober5000
706 	{ "ship-no-vaporize",				OP_SHIP_NO_VAPORIZE,					1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Goober5000
707 	{ "set-explosion-option",			OP_SET_EXPLOSION_OPTION,				3,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Goober5000
708 	{ "explosion-effect",				OP_EXPLOSION_EFFECT,					11,	14,			SEXP_ACTION_OPERATOR,	},	// Goober5000
709 	{ "warp-effect",					OP_WARP_EFFECT,							12, 14,			SEXP_ACTION_OPERATOR,	},	// Goober5000
710 	{ "clear-weapons",					OP_CLEAR_WEAPONS,						0,	1,			SEXP_ACTION_OPERATOR,	},	// Karajorma
711 	{ "clear-debris",					OP_CLEAR_DEBRIS,						0,	1,			SEXP_ACTION_OPERATOR, },	// Goober5000
712 	{ "set-time-compression",			OP_CUTSCENES_SET_TIME_COMPRESSION,		1,	3,			SEXP_ACTION_OPERATOR,	},
713 	{ "reset-time-compression",			OP_CUTSCENES_RESET_TIME_COMPRESSION,	0,	0,			SEXP_ACTION_OPERATOR,	},
714 	{ "call-ssm-strike",				OP_CALL_SSM_STRIKE,						3,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// X3N0-Life-Form
715 
716 	//Variable Category
717 	{ "modify-variable",				OP_MODIFY_VARIABLE,						2,	2,			SEXP_ACTION_OPERATOR,	},
718 	{ "get-variable-by-index",			OP_GET_VARIABLE_BY_INDEX,				1,	1,			SEXP_INTEGER_OPERATOR,	},	// Goober5000
719 	{ "set-variable-by-index",			OP_SET_VARIABLE_BY_INDEX,				2,	2,			SEXP_ACTION_OPERATOR,	},	// Goober5000
720 	{ "copy-variable-from-index",		OP_COPY_VARIABLE_FROM_INDEX,			2,	2,			SEXP_ACTION_OPERATOR,	},	// Goober5000
721 	{ "copy-variable-between-indexes",	OP_COPY_VARIABLE_BETWEEN_INDEXES,		2,	2,			SEXP_ACTION_OPERATOR,	},	// Goober5000
722 	{ "int-to-string",					OP_INT_TO_STRING,						2,	2,			SEXP_ACTION_OPERATOR,	},	// Goober5000
723 	{ "string-concatenate",				OP_STRING_CONCATENATE,					3,	3,			SEXP_ACTION_OPERATOR,	},	// Goober5000
724 	{ "string-concatenate-block",		OP_STRING_CONCATENATE_BLOCK,			3,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Goober5000
725 	{ "string-get-substring",			OP_STRING_GET_SUBSTRING,				4,	4,			SEXP_ACTION_OPERATOR,	},	// Goober5000
726 	{ "string-set-substring",			OP_STRING_SET_SUBSTRING,				5,	5,			SEXP_ACTION_OPERATOR,	},	// Goober5000
727 	{ "modify-variable-xstr",			OP_MODIFY_VARIABLE_XSTR,				3,	3,			SEXP_ACTION_OPERATOR,	},	// m!m
728 
729 	//Other Sub-Category
730 	{ "damaged-escort-priority",		OP_DAMAGED_ESCORT_LIST,					3,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	//phreak
731 	{ "damaged-escort-priority-all",	OP_DAMAGED_ESCORT_LIST_ALL,				1,	MAX_COMPLETE_ESCORT_LIST,	SEXP_ACTION_OPERATOR,	},	// Goober5000
732 	{ "set-support-ship",				OP_SET_SUPPORT_SHIP,					6,	7,			SEXP_ACTION_OPERATOR,	},	// Goober5000
733 	{ "script-eval",					OP_SCRIPT_EVAL,							1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
734 	{ "script-eval-block",				OP_SCRIPT_EVAL_BLOCK,					1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
735 	{ "multi-eval",						OP_SCRIPT_EVAL_MULTI,					2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
736 	{ "debug",							OP_DEBUG,								2,	2,			SEXP_ACTION_OPERATOR,	},	// Karajorma
737 	{ "do-nothing",						OP_NOP,									0,	0,			SEXP_ACTION_OPERATOR,	},
738 
739 	//AI Goals Category
740 	{ "ai-chase",						OP_AI_CHASE,							2,	3,			SEXP_GOAL_OPERATOR,	},
741 	{ "ai-chase-wing",					OP_AI_CHASE_WING,						2,	3,			SEXP_GOAL_OPERATOR,	},
742 	{ "ai-chase-ship-class",			OP_AI_CHASE_SHIP_CLASS,					2,	3,			SEXP_GOAL_OPERATOR, },
743 	{ "ai-chase-any",					OP_AI_CHASE_ANY,						1,	1,			SEXP_GOAL_OPERATOR,	},
744 	{ "ai-guard",						OP_AI_GUARD,							2,	2,			SEXP_GOAL_OPERATOR,	},
745 	{ "ai-guard-wing",					OP_AI_GUARD_WING,						2,	2,			SEXP_GOAL_OPERATOR,	},
746 	{ "ai-destroy-subsystem",			OP_AI_DESTROY_SUBSYS,					3,	4,			SEXP_GOAL_OPERATOR,	},
747 	{ "ai-disable-ship",				OP_AI_DISABLE_SHIP,						2,	3,			SEXP_GOAL_OPERATOR,	},
748 	{ "ai-disarm-ship",					OP_AI_DISARM_SHIP,						2,	3,			SEXP_GOAL_OPERATOR,	},
749 	{ "ai-warp",						OP_AI_WARP,								2,	2,			SEXP_GOAL_OPERATOR,	},
750 	{ "ai-warp-out",					OP_AI_WARP_OUT,							1,	1,			SEXP_GOAL_OPERATOR,	},
751 	{ "ai-dock",						OP_AI_DOCK,								4,	4,			SEXP_GOAL_OPERATOR,	},
752 	{ "ai-undock",						OP_AI_UNDOCK,							1,	2,			SEXP_GOAL_OPERATOR,	},
753 	{ "ai-waypoints",					OP_AI_WAYPOINTS,						2,	2,			SEXP_GOAL_OPERATOR,	},
754 	{ "ai-waypoints-once",				OP_AI_WAYPOINTS_ONCE,					2,	2,			SEXP_GOAL_OPERATOR,	},
755 	{ "ai-ignore",						OP_AI_IGNORE,							2,	2,			SEXP_GOAL_OPERATOR,	},
756 	{ "ai-ignore-new",					OP_AI_IGNORE_NEW,						2,	2,			SEXP_GOAL_OPERATOR,	},
757 	{ "ai-stay-near-ship",				OP_AI_STAY_NEAR_SHIP,					2,	2,			SEXP_GOAL_OPERATOR,	},
758 	{ "ai-evade-ship",					OP_AI_EVADE_SHIP,						2,	2,			SEXP_GOAL_OPERATOR,	},
759 	{ "ai-keep-safe-distance",			OP_AI_KEEP_SAFE_DISTANCE,				1,	1,			SEXP_GOAL_OPERATOR,	},
760 	{ "ai-stay-still",					OP_AI_STAY_STILL,						2,	2,			SEXP_GOAL_OPERATOR,	},
761 	{ "ai-play-dead",					OP_AI_PLAY_DEAD,						1,	1,			SEXP_GOAL_OPERATOR,	},
762 	{ "ai-play-dead-persistent",		OP_AI_PLAY_DEAD_PERSISTENT,				1,	1,			SEXP_GOAL_OPERATOR, },
763 	{ "ai-form-on-wing",				OP_AI_FORM_ON_WING,						1,	1,			SEXP_GOAL_OPERATOR,	},
764 
765 	{ "goals",							OP_GOALS_ID,							1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
766 
767 	//Training Category
768 	{ "key-pressed",					OP_KEY_PRESSED,							1,	2,			SEXP_BOOLEAN_OPERATOR,	},
769 	{ "key-reset",						OP_KEY_RESET,							1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
770 	{ "key-reset-multiple",				OP_KEY_RESET_MULTIPLE,					1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
771 	{ "ignore-key",						OP_IGNORE_KEY,							2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// Karajorma
772 	{ "targeted",						OP_TARGETED,							1,	3,			SEXP_BOOLEAN_OPERATOR,	},
773 	{ "node-targeted",					OP_NODE_TARGETED,						1,	2,			SEXP_BOOLEAN_OPERATOR,	},	// FUBAR
774 	{ "missile-locked",					OP_MISSILE_LOCKED,						1,	3,			SEXP_BOOLEAN_OPERATOR,	},	// Sesquipedalian
775 	{ "speed",							OP_SPEED,								1,	1,			SEXP_BOOLEAN_OPERATOR,	},
776 	{ "facing",							OP_FACING,								2,	2,			SEXP_BOOLEAN_OPERATOR,	},
777 	{ "facing-waypoint",				OP_FACING2,								2,	2,			SEXP_BOOLEAN_OPERATOR,	},
778 	{ "order",							OP_ORDER,								2,	3,			SEXP_BOOLEAN_OPERATOR,	},
779 	{ "query-orders",					OP_QUERY_ORDERS,						3,	6,			SEXP_BOOLEAN_OPERATOR,	}, // Karajorma
780 	{ "reset-orders",					OP_RESET_ORDERS,						0,	0,			SEXP_ACTION_OPERATOR,	}, // Karajorma
781 	{ "waypoint-missed",				OP_WAYPOINT_MISSED,						0,	0,			SEXP_BOOLEAN_OPERATOR,	},
782 	{ "waypoint-twice",					OP_WAYPOINT_TWICE,						0,	0,			SEXP_BOOLEAN_OPERATOR,	},
783 	{ "path-flown",						OP_PATH_FLOWN,							0,	0,			SEXP_BOOLEAN_OPERATOR,	},
784 	{ "training-msg",					OP_TRAINING_MSG,						1,	4,			SEXP_ACTION_OPERATOR,	},
785 	{ "flash-hud-gauge",				OP_FLASH_HUD_GAUGE,						1,	1,			SEXP_ACTION_OPERATOR,	},
786 	{ "primaries-depleted",				OP_PRIMARIES_DEPLETED,					1,	1,			SEXP_BOOLEAN_OPERATOR,	},
787 	{ "secondaries-depleted",			OP_SECONDARIES_DEPLETED,				1,	1,			SEXP_BOOLEAN_OPERATOR,	},
788 	{ "special-check",					OP_SPECIAL_CHECK,						1,	1,			SEXP_ACTION_OPERATOR,	},
789 	{ "set-training-context-fly-path",	OP_SET_TRAINING_CONTEXT_FLY_PATH,		2,	2,			SEXP_ACTION_OPERATOR,	},
790 	{ "set-training-context-speed",		OP_SET_TRAINING_CONTEXT_SPEED,			2,	2,			SEXP_ACTION_OPERATOR,	},
791 };
792 
793 sexp_ai_goal_link Sexp_ai_goal_links[] = {
794 	{ AI_GOAL_CHASE, OP_AI_CHASE },
795 	{ AI_GOAL_CHASE_WING, OP_AI_CHASE_WING },
796 	{ AI_GOAL_CHASE_SHIP_CLASS, OP_AI_CHASE_SHIP_CLASS },
797 	{ AI_GOAL_CHASE_ANY, OP_AI_CHASE_ANY },
798 	{ AI_GOAL_DOCK, OP_AI_DOCK },
799 	{ AI_GOAL_UNDOCK, OP_AI_UNDOCK },
800 	{ AI_GOAL_WARP, OP_AI_WARP_OUT },
801 	{ AI_GOAL_WARP, OP_AI_WARP },
802 	{ AI_GOAL_WAYPOINTS, OP_AI_WAYPOINTS },
803 	{ AI_GOAL_WAYPOINTS_ONCE, OP_AI_WAYPOINTS_ONCE },
804 	{ AI_GOAL_DESTROY_SUBSYSTEM, OP_AI_DESTROY_SUBSYS },
805 	{ AI_GOAL_DISABLE_SHIP, OP_AI_DISABLE_SHIP },
806 	{ AI_GOAL_DISARM_SHIP, OP_AI_DISARM_SHIP },
807 	{ AI_GOAL_GUARD, OP_AI_GUARD },
808 	{ AI_GOAL_GUARD_WING, OP_AI_GUARD_WING },
809 	{ AI_GOAL_EVADE_SHIP, OP_AI_EVADE_SHIP },
810 	{ AI_GOAL_STAY_NEAR_SHIP, OP_AI_STAY_NEAR_SHIP },
811 	{ AI_GOAL_KEEP_SAFE_DISTANCE, OP_AI_KEEP_SAFE_DISTANCE },
812 	{ AI_GOAL_IGNORE, OP_AI_IGNORE },
813 	{ AI_GOAL_IGNORE_NEW, OP_AI_IGNORE_NEW },
814 	{ AI_GOAL_STAY_STILL, OP_AI_STAY_STILL },
815 	{ AI_GOAL_PLAY_DEAD, OP_AI_PLAY_DEAD },
816 	{ AI_GOAL_PLAY_DEAD_PERSISTENT, OP_AI_PLAY_DEAD_PERSISTENT },
817 	{ AI_GOAL_FORM_ON_WING, OP_AI_FORM_ON_WING }
818 };
819 
820 void sexp_set_skybox_model_preload(const char *name); // taylor
821 int Num_skybox_flags = 6;
822 const char *Skybox_flags[] = {
823 	"force-clamp",
824 	"add-lighting",
825 	"no-transparency",
826 	"add-zbuffer",
827 	"add-culling",
828 	"no-glowmaps",
829 };
830 
831 int	Directive_count;
832 int	Sexp_useful_number;  // a variable to pass useful info in from external modules
833 int	Locked_sexp_true, Locked_sexp_false;
834 int	Num_sexp_ai_goal_links = sizeof(Sexp_ai_goal_links) / sizeof(sexp_ai_goal_link);
835 int	Sexp_clipboard = -1;  // used by Fred
836 int	Training_context = 0;
837 int	Training_context_speed_set;
838 int	Training_context_speed_min;
839 int	Training_context_speed_max;
840 int	Training_context_speed_timestamp;
841 waypoint_list *Training_context_path;
842 int Training_context_goal_waypoint;
843 int Training_context_at_waypoint;
844 float	Training_context_distance;
845 
846 #define SEXP_NODE_INCREMENT	250
847 int Num_sexp_nodes = 0;
848 sexp_node *Sexp_nodes = nullptr;
849 
850 sexp_variable Sexp_variables[MAX_SEXP_VARIABLES];
851 sexp_variable Block_variables[MAX_SEXP_VARIABLES];			// used for compatibility with retail.
852 
853 int Num_special_expl_blocks;
854 
855 SCP_vector<int> Current_sexp_operator;
856 
857 int Players_target = UNINITIALIZED;
858 int Players_mlocked = UNINITIALIZED; // for is-missile-locked - Sesquipedalian
859 ship_subsys *Players_targeted_subsys;
860 int Players_target_timestamp;
861 int Players_mlocked_timestamp;
862 
863 // for sexp_fade
864 static int Fade_out_r = 0;
865 static int Fade_out_g = 0;
866 static int Fade_out_b = 0;
867 
868 // for play-music - Goober5000
869 SCP_vector<int>	Sexp_music_handles;		// All handles used by all invocations of play-sound-from-file.  The default handle is in index 0.
870 
871 // for sound environments - Goober5000/Taylor
872 #define SEO_VOLUME		0
873 #define SEO_DECAY_TIME	1
874 #define SEO_DAMPING		2
875 int sexp_sound_environment_option_lookup(const char *text);
876 const char *Sound_environment_option[] = { "volume", "decay time", "damping" };
877 int Num_sound_environment_options = 3;
878 
879 // for adjust-audio-volume - The E
880 const char *Adjust_audio_options[] = { "Music", "Voice", "Effects" };
881 int Num_adjust_audio_options = 3;
882 int audio_volume_option_lookup(const char *text);
883 
884 int hud_gauge_type_lookup(const char* name);
885 
886 // for explosions - Goober5000
887 #define EO_DAMAGE			0
888 #define EO_BLAST			1
889 #define EO_INNER_RADIUS		2
890 #define EO_OUTER_RADIUS		3
891 #define EO_SHOCKWAVE_SPEED	4
892 #define EO_DEATH_ROLL_TIME	5
893 int sexp_explosion_option_lookup(const char *text);
894 const char *Explosion_option[] = { "damage", "blast", "inner radius", "outer radius", "shockwave speed", "death roll time" };
895 int Num_explosion_options = 6;
896 
897 int get_sexp();
898 void build_extended_sexp_string(SCP_string &accumulator, int cur_node, int level, int mode);
899 void update_sexp_references(const char *old_name, const char *new_name, int format, int node);
900 int sexp_determine_team(const char *subj);
901 void init_sexp_vars();
902 
903 // for handling variables
904 void add_block_variable(const char *text, const char *var_name, int type, int index);
905 void sexp_modify_variable(int node);
906 int sexp_get_variable_by_index(int node);
907 void sexp_set_variable_by_index(int node);
908 void sexp_copy_variable_from_index(int node);
909 void sexp_copy_variable_between_indexes(int node);
910 
911 int verify_vector(const char *text);
912 
913 
914 #define ARG_ITEM_F_DUP	(1<<0)
915 
916 // Goober5000 - adapted from sexp_list_item in Sexp_tree.h
917 struct arg_item
918 {
919 	char *text = nullptr;
920 	int node = -1;
921 
922 	arg_item *next = nullptr;
923 	int flags = 0;
924 	int nesting_level = 0;
925 
926 	arg_item() = default;
927 
928 	void add_data(char *str, int n);
929 	void add_data(const std::pair<char*, int> &data);
930 	void add_data_dup(char *str, int n);
931 	void add_data_dup(const std::pair<char*, int> &data);
932 	void add_data_set_dup(char *str, int n);
933 	void add_data_set_dup(const std::pair<char*, int> &data);
934 	void expunge();
935 	int is_empty();
936 	arg_item *get_next();
937 	void clear_nesting_level();
938 };
939 
940 arg_item Sexp_applicable_argument_list;
941 SCP_vector<std::pair<char*, int>> Sexp_replacement_arguments;
942 int Sexp_current_argument_nesting_level;
943 
944 
945 // Goober5000
946 bool is_blank_argument_op(int op_const);
947 bool is_blank_of_op(int op_const);
948 int get_handler_for_x_of_operator(int node);
949 
950 //Karajorma
951 int get_generic_subsys(const char *subsy_name);
952 bool ship_class_unchanged(const ship_registry_entry *ship_entry);
953 void multi_sexp_modify_variable();
954 
955 #define NO_OPERATOR_INDEX_DEFINED		-2
956 #define NOT_A_SEXP_OPERATOR				-1
957 
958 // hud-display-gauge magic values
959 #define SEXP_HUD_GAUGE_WARPOUT "warpout"
960 
961 // event log stuff
962 SCP_vector<SCP_string> *Current_event_log_buffer;
963 SCP_vector<SCP_string> *Current_event_log_variable_buffer;
964 SCP_vector<SCP_string> *Current_event_log_argument_buffer;
965 
966 // Goober5000 - arg_item class stuff, borrowed from sexp_list_item class stuff -------------
add_data(char * str,int n)967 void arg_item::add_data(char *str, int n)
968 {
969 	arg_item *item, *ptr;
970 
971 	// create item
972 	item = new arg_item();
973 	item->text = str;
974 	item->node = n;
975 	item->nesting_level = Sexp_current_argument_nesting_level;
976 
977 	// prepend item to existing list
978 	ptr = this->next;
979 	this->next = item;
980 	item->next = ptr;
981 }
982 
add_data(const std::pair<char *,int> & data)983 void arg_item::add_data(const std::pair<char*, int> &data)
984 {
985 	add_data(data.first, data.second);
986 }
987 
add_data_dup(char * str,int n)988 void arg_item::add_data_dup(char *str, int n)
989 {
990 	arg_item *item, *ptr;
991 
992 	// create item
993 	item = new arg_item();
994 	item->text = vm_strdup(str);
995 	item->flags |= ARG_ITEM_F_DUP;
996 	item->node = n;
997 	item->nesting_level = Sexp_current_argument_nesting_level;
998 
999 	// prepend item to existing list
1000 	ptr = this->next;
1001 	this->next = item;
1002 	item->next = ptr;
1003 }
1004 
add_data_dup(const std::pair<char *,int> & data)1005 void arg_item::add_data_dup(const std::pair<char*, int> &data)
1006 {
1007 	add_data_dup(data.first, data.second);
1008 }
1009 
add_data_set_dup(char * str,int n)1010 void arg_item::add_data_set_dup(char *str, int n)
1011 {
1012 	arg_item *item, *ptr;
1013 
1014 	// create item
1015 	item = new arg_item();
1016 	item->text = str;
1017 	item->flags |= ARG_ITEM_F_DUP;
1018 	item->node = n;
1019 	item->nesting_level = Sexp_current_argument_nesting_level;
1020 
1021 	// prepend item to existing list
1022 	ptr = this->next;
1023 	this->next = item;
1024 	item->next = ptr;
1025 }
1026 
add_data_set_dup(const std::pair<char *,int> & data)1027 void arg_item::add_data_set_dup(const std::pair<char*, int> &data)
1028 {
1029 	add_data_set_dup(data.first, data.second);
1030 }
1031 
get_next()1032 arg_item* arg_item::get_next()
1033 {
1034 	if (this->next != nullptr) {
1035 		if (this->next->nesting_level >= Sexp_current_argument_nesting_level) {
1036 			return this->next;
1037 		}
1038 	}
1039 
1040 	return nullptr;
1041 }
1042 
expunge()1043 void arg_item::expunge()
1044 {
1045 	arg_item *ptr;
1046 
1047 	// contiually delete first item of list
1048 	while (this->next != nullptr)
1049 	{
1050 		ptr = this->next->next;
1051 
1052 		if (this->next->flags & ARG_ITEM_F_DUP)
1053 			vm_free(this->next->text);
1054 		delete this->next;
1055 
1056 		this->next = ptr;
1057 	}
1058 }
1059 
clear_nesting_level()1060 void arg_item::clear_nesting_level()
1061 {
1062 	arg_item *ptr;
1063 
1064 	// contiually delete first item of list
1065 	while (this->next != nullptr && this->next->nesting_level >= Sexp_current_argument_nesting_level )
1066 	{
1067 		ptr = this->next->next;
1068 
1069 		if (this->next->flags & ARG_ITEM_F_DUP)
1070 			vm_free(this->next->text);
1071 		delete this->next;
1072 
1073 		this->next = ptr;
1074 	}
1075 }
1076 
is_empty()1077 int arg_item::is_empty()
1078 {
1079 	return (this->next == nullptr);
1080 }
1081 //-------------------------------------------------------------------------------------------------
1082 
sexp_nodes_init()1083 void sexp_nodes_init()
1084 {
1085 	if (Num_sexp_nodes == 0 || Sexp_nodes == nullptr)
1086 		return;
1087 
1088 	nprintf(("SEXP", "Reinitializing sexp nodes...\n"));
1089 	nprintf(("SEXP", "Entered function with %d nodes.\n", Num_sexp_nodes));
1090 
1091 	// usually, the persistent nodes are grouped at the beginning of the array;
1092 	// so we ought to be able to free all the subsequent nodes
1093 	int i, last_persistent_node = -1;
1094 
1095 	for (i = 0; i < Num_sexp_nodes; i++)
1096 	{
1097 		if (Sexp_nodes[i].type & SEXP_FLAG_PERSISTENT)
1098 			last_persistent_node = i;					// keep track of it
1099 		else
1100 			Sexp_nodes[i].type = SEXP_NOT_USED;			// it's not needed
1101 
1102 		// free anything cached
1103 		if (Sexp_nodes[i].cache)
1104 		{
1105 			delete Sexp_nodes[i].cache;
1106 			Sexp_nodes[i].cache = nullptr;
1107 		}
1108 	}
1109 
1110 	nprintf(("SEXP", "Last persistent node index is %d.\n", last_persistent_node));
1111 
1112 	// if all the persistent nodes are gone, free all the nodes
1113 	if (last_persistent_node == -1)
1114 	{
1115 		vm_free(Sexp_nodes);
1116 		Sexp_nodes = nullptr;
1117 		Num_sexp_nodes = 0;
1118 	}
1119 	// if there's enough of a difference to make it worthwhile, free some nodes
1120 	else if (Num_sexp_nodes - (last_persistent_node + 1) > 2 * SEXP_NODE_INCREMENT)
1121 	{
1122 		// round it up to the next evenly divisible size
1123 		Num_sexp_nodes = (last_persistent_node + 1);
1124 		Num_sexp_nodes += SEXP_NODE_INCREMENT - (Num_sexp_nodes % SEXP_NODE_INCREMENT);
1125 
1126 		Sexp_nodes = (sexp_node *) vm_realloc(Sexp_nodes, sizeof(sexp_node) * Num_sexp_nodes);
1127 		Verify(Sexp_nodes != nullptr);
1128 	}
1129 
1130 	nprintf(("SEXP", "Exited function with %d nodes.\n", Num_sexp_nodes));
1131 }
1132 
sexp_nodes_close()1133 static void sexp_nodes_close()
1134 {
1135 	// free all sexp nodes... should only be done on game shutdown
1136 	if (Sexp_nodes != nullptr)
1137 	{
1138 		// free anything cached
1139 		for (int i = 0; i < Num_sexp_nodes; i++)
1140 		{
1141 			if (Sexp_nodes[i].cache)
1142 			{
1143 				delete Sexp_nodes[i].cache;
1144 				Sexp_nodes[i].cache = nullptr;
1145 			}
1146 		}
1147 
1148 		vm_free(Sexp_nodes);
1149 		Sexp_nodes = nullptr;
1150 		Num_sexp_nodes = 0;
1151 	}
1152 }
1153 
init_sexp()1154 void init_sexp()
1155 {
1156 	// Goober5000
1157 	Sexp_replacement_arguments.clear();
1158 	Sexp_applicable_argument_list.expunge();
1159 	Sexp_current_argument_nesting_level = 0;
1160 	Current_sexp_network_packet.initialize();
1161 
1162 	sexp_nodes_init();
1163 	init_sexp_vars();
1164 	init_sexp_containers();
1165 	Locked_sexp_false = Locked_sexp_true = -1;
1166 
1167 	Locked_sexp_false = alloc_sexp("false", SEXP_LIST, SEXP_ATOM_OPERATOR, -1, -1);
1168 	Assert(Locked_sexp_false != -1);
1169 	Sexp_nodes[Locked_sexp_false].type = SEXP_ATOM;  // fix bypassing value
1170 	Sexp_nodes[Locked_sexp_false].value = SEXP_KNOWN_FALSE;
1171 
1172 	Locked_sexp_true = alloc_sexp("true", SEXP_LIST, SEXP_ATOM_OPERATOR, -1, -1);
1173 	Assert(Locked_sexp_true != -1);
1174 	Sexp_nodes[Locked_sexp_true].type = SEXP_ATOM;  // fix bypassing value
1175 	Sexp_nodes[Locked_sexp_true].value = SEXP_KNOWN_TRUE;
1176 
1177 	// init fade_out colors
1178 	Fade_out_r = 0;
1179 	Fade_out_g = 0;
1180 	Fade_out_b = 0;
1181 }
1182 
sexp_shutdown()1183 void sexp_shutdown() {
1184 	sexp_nodes_close();
1185 
1186 	sexp::dynamic_sexp_shutdown();
1187 }
1188 
1189 /**
1190  * Allocate an sexp node.
1191  */
alloc_sexp(const char * text,int type,int subtype,int first,int rest)1192 int alloc_sexp(const char *text, int type, int subtype, int first, int rest)
1193 {
1194 	int node;
1195 	int sexp_const = get_operator_const(text);
1196 
1197 	if ((sexp_const == OP_TRUE) && (type == SEXP_ATOM) && (subtype == SEXP_ATOM_OPERATOR))
1198 		return Locked_sexp_true;
1199 
1200 	else if ((sexp_const == OP_FALSE) && (type == SEXP_ATOM) && (subtype == SEXP_ATOM_OPERATOR))
1201 		return Locked_sexp_false;
1202 
1203 	node = find_free_sexp();
1204 
1205 	// need more sexp nodes?
1206 	if (node == Num_sexp_nodes || node == -1)
1207 	{
1208 		int old_size = Num_sexp_nodes;
1209 
1210 		Assert(SEXP_NODE_INCREMENT > 0);
1211 
1212 		// allocate in blocks of SEXP_NODE_INCREMENT
1213 		Num_sexp_nodes += SEXP_NODE_INCREMENT;
1214 		Sexp_nodes = (sexp_node *) vm_realloc(Sexp_nodes, sizeof(sexp_node) * Num_sexp_nodes);
1215 
1216 		Verify(Sexp_nodes != nullptr);
1217 		nprintf(("SEXP", "Bumping dynamic sexp node limit from %d to %d...\n", old_size, Num_sexp_nodes));
1218 
1219 		// clear all the new sexp nodes we just allocated
1220 		memset(&Sexp_nodes[old_size], 0, sizeof(sexp_node) * SEXP_NODE_INCREMENT); //-V512
1221 
1222 		// our new sexp is the first out of the ones we just created
1223 		node = old_size;
1224 	}
1225 
1226 	Assert(node != Locked_sexp_true);
1227 	Assert(node != Locked_sexp_false);
1228 	Assert(strlen(text) < TOKEN_LENGTH);
1229 	Assert(type >= 0);
1230 
1231 	strcpy_s(Sexp_nodes[node].text, text);
1232 	Sexp_nodes[node].type = type;
1233 	Sexp_nodes[node].subtype = subtype;
1234 	Sexp_nodes[node].first = first;
1235 	Sexp_nodes[node].rest = rest;
1236 	Sexp_nodes[node].value = SEXP_UNKNOWN;
1237 	Sexp_nodes[node].flags = SNF_DEFAULT_VALUE;
1238 	Sexp_nodes[node].op_index = NO_OPERATOR_INDEX_DEFINED;
1239 	Sexp_nodes[node].cache = nullptr;
1240 	Sexp_nodes[node].cached_variable_index = -1;
1241 
1242 	// special-arg?
1243 	if (type == SEXP_ATOM && !strcmp(text, SEXP_ARGUMENT_STRING))
1244 		Sexp_nodes[node].flags |= SNF_SPECIAL_ARG_IN_NODE;
1245 
1246 	return node;
1247 }
1248 
1249 static int Sexp_hwm = 0;
1250 
count_free_sexp_nodes()1251 int count_free_sexp_nodes()
1252 {
1253 	int i, f = 0, p = 0;
1254 
1255 	for (i = 0; i < Num_sexp_nodes; i++)
1256 	{
1257 		if (Sexp_nodes[i].type == SEXP_NOT_USED)
1258 			f++;
1259 		else if (Sexp_nodes[i].type & SEXP_FLAG_PERSISTENT)
1260 			p++;
1261 	}
1262 
1263 	if (Num_sexp_nodes - f > Sexp_hwm)
1264 	{
1265 		nprintf(("Sexp", "Sexp nodes: Free=%d, Used=%d, Persistent=%d\n", f, Num_sexp_nodes - f, p));
1266 		Sexp_hwm = Num_sexp_nodes - f;
1267 	}
1268 
1269 	return f;
1270 }
1271 
1272 /**
1273  * Find the next free sexp and return its index.
1274  */
find_free_sexp()1275 int find_free_sexp()
1276 {
1277 	int i;
1278 
1279 	// sanity
1280 	if (Num_sexp_nodes == 0 || Sexp_nodes == nullptr)
1281 		return -1;
1282 
1283 	for (i = 0; i < Num_sexp_nodes; i++)
1284 	{
1285 		if (Sexp_nodes[i].type == SEXP_NOT_USED)
1286 			return i;
1287 	}
1288 
1289 	return -1;
1290 }
1291 
1292 /**
1293  * Mark a whole sexp tree with the persistent flag so that it won't get re-used between missions
1294  */
sexp_mark_persistent(int n)1295 void sexp_mark_persistent(int n)
1296 {
1297 	if (n == -1){
1298 		return;
1299 	}
1300 
1301 	// total hack because of the true/false locked sexps -- we should make those persistent as well
1302 	if ( (n == Locked_sexp_true) || (n == Locked_sexp_false) ){
1303 		return;
1304 	}
1305 
1306 	Assert( !(Sexp_nodes[n].type & SEXP_FLAG_PERSISTENT) );
1307 	Sexp_nodes[n].type |= SEXP_FLAG_PERSISTENT;
1308 
1309 	sexp_mark_persistent(Sexp_nodes[n].first);
1310 	sexp_mark_persistent(Sexp_nodes[n].rest);
1311 
1312 }
1313 
1314 /**
1315  * Remove the persistent flag from all nodes in the tree
1316  */
sexp_unmark_persistent(int n)1317 void sexp_unmark_persistent(int n)
1318 {
1319 	if (n == -1){
1320 		return;
1321 	}
1322 
1323 	if ( (n == Locked_sexp_true) || (n == Locked_sexp_false) ){
1324 		return;
1325 	}
1326 
1327 	Assert( Sexp_nodes[n].type & SEXP_FLAG_PERSISTENT );
1328 	Sexp_nodes[n].type &= ~SEXP_FLAG_PERSISTENT;
1329 
1330 	sexp_unmark_persistent(Sexp_nodes[n].first);
1331 	sexp_unmark_persistent(Sexp_nodes[n].rest);
1332 }
1333 
1334 /**
1335  * Free up the specified sexp node,  Leaves link chains untouched.
1336  */
free_one_sexp(int num)1337 int free_one_sexp(int num)
1338 {
1339 	Assert(Fred_running);
1340 	Assert((num >= 0) && (num < Num_sexp_nodes));
1341 	Assert(Sexp_nodes[num].type != SEXP_NOT_USED);  // make sure it is actually used
1342 	Assert(!(Sexp_nodes[num].type & SEXP_FLAG_PERSISTENT));
1343 
1344 	if ((num == Locked_sexp_true) || (num == Locked_sexp_false))
1345 		return 0;
1346 
1347 	Sexp_nodes[num].type = SEXP_NOT_USED;
1348 	if (Sexp_nodes[num].cache)
1349 	{
1350 		delete Sexp_nodes[num].cache;
1351 		Sexp_nodes[num].cache = nullptr;
1352 	}
1353 	return 1;
1354 }
1355 
1356 /**
1357  * Free a used sexp node, so it can be reused later.
1358  *
1359  * Should only be called on an atom or a list, and not an operator.  If on a list, the
1360  * list and everything in it will be freed (including the operator).
1361  */
free_sexp(int num)1362 int free_sexp(int num)
1363 {
1364 	int i, rest, count = 0;
1365 
1366 	Assert((num >= 0) && (num < Num_sexp_nodes));
1367 	Assert(Sexp_nodes[num].type != SEXP_NOT_USED);  // make sure it is actually used
1368 	Assert(!(Sexp_nodes[num].type & SEXP_FLAG_PERSISTENT));
1369 
1370 	if ((num == Locked_sexp_true) || (num == Locked_sexp_false))
1371 		return 0;
1372 
1373 	Sexp_nodes[num].type = SEXP_NOT_USED;
1374 	if (Sexp_nodes[num].cache)
1375 	{
1376 		delete Sexp_nodes[num].cache;
1377 		Sexp_nodes[num].cache = nullptr;
1378 	}
1379 	count++;
1380 
1381 	i = Sexp_nodes[num].first;
1382 	while (i != -1)
1383 	{
1384 		count += free_sexp(i);
1385 		i = Sexp_nodes[i].rest;
1386 	}
1387 
1388 	rest = Sexp_nodes[num].rest;
1389 	for (i = 0; i < Num_sexp_nodes; i++)
1390 	{
1391 		if (Sexp_nodes[i].first == num)
1392 			Sexp_nodes[i].first = rest;
1393 
1394 		if (Sexp_nodes[i].rest == num)
1395 			Sexp_nodes[i].rest = rest;
1396 	}
1397 
1398 	return count;  // total elements freed up.
1399 }
1400 
1401 /**
1402  * Free up an entire sexp tree.
1403  *
1404  * Because the root node is an operator, instead of a list, we can't simply call free_sexp().
1405  * This function should only be called on the root node of an sexp, otherwise the linking will get screwed up.
1406  */
free_sexp2(int num)1407 int free_sexp2(int num)
1408 {
1409 	int i, count = 0;
1410 
1411 	if ((num == -1) || (num == Locked_sexp_true) || (num == Locked_sexp_false)){
1412 		return 0;
1413 	}
1414 
1415 	i = Sexp_nodes[num].rest;
1416 	while (i != -1) {
1417 		count += free_sexp(i);
1418 		i = Sexp_nodes[i].rest;
1419 	}
1420 
1421 	count += free_sexp(num);
1422 	return count;
1423 }
1424 
1425 /**
1426  * Reset the status of all the nodes in a tree, forcing them to all be evaulated again.
1427  */
flush_sexp_tree(int node)1428 void flush_sexp_tree(int node)
1429 {
1430 	if (node < 0){
1431 		return;
1432 	}
1433 
1434 	Sexp_nodes[node].value = SEXP_UNKNOWN;
1435 	if (Sexp_nodes[node].cache)
1436 	{
1437 		delete Sexp_nodes[node].cache;
1438 		Sexp_nodes[node].cache = nullptr;
1439 	}
1440 	Sexp_nodes[node].cached_variable_index = -1;
1441 
1442 	flush_sexp_tree(Sexp_nodes[node].first);
1443 	flush_sexp_tree(Sexp_nodes[node].rest);
1444 }
1445 
verify_sexp_tree(int node)1446 int verify_sexp_tree(int node)
1447 {
1448 	if (node == -1){
1449 		return 0;
1450 	}
1451 
1452 	if ((Sexp_nodes[node].type == SEXP_NOT_USED) ||
1453 		(Sexp_nodes[node].first == node) ||
1454 		(Sexp_nodes[node].rest == node)) {
1455 		Error(LOCATION, "Sexp node is corrupt");
1456 		return -1;
1457 	}
1458 
1459 	if (Sexp_nodes[node].first != -1){
1460 		verify_sexp_tree(Sexp_nodes[node].first);
1461 	}
1462 	if (Sexp_nodes[node].rest != -1){
1463 		verify_sexp_tree(Sexp_nodes[node].rest);
1464 	}
1465 
1466 	return 0;
1467 }
1468 
1469 /**
1470  * @todo CASE OF SEXP VARIABLES - ONLY 1 COPY OF VARIABLE
1471  */
dup_sexp_chain(int node)1472 int dup_sexp_chain(int node)
1473 {
1474 	int cur, first, rest;
1475 
1476 	if (node == -1){
1477 		return -1;
1478 	}
1479 
1480 	// TODO - CASE OF SEXP VARIABLES - ONLY 1 COPY OF VARIABLE
1481 	first = dup_sexp_chain(Sexp_nodes[node].first);
1482 	rest = dup_sexp_chain(Sexp_nodes[node].rest);
1483 	cur = alloc_sexp(Sexp_nodes[node].text, Sexp_nodes[node].type, Sexp_nodes[node].subtype, first, rest);
1484 
1485 	if (cur == -1) {
1486 		if (first != -1){
1487 			free_sexp(first);
1488 		}
1489 		if (rest != -1){
1490 			free_sexp(rest);
1491 		}
1492 	}
1493 
1494 	return cur;
1495 }
1496 
1497 /**
1498  * Compare SEXP chains
1499  * @return 1 if they are the same, 0 if different
1500  */
cmp_sexp_chains(int node1,int node2)1501 int cmp_sexp_chains(int node1, int node2)
1502 {
1503 	if ((node1 == -1) && (node2 == -1)){
1504 		return 1;
1505 	}
1506 
1507 	if ((node1 == -1) || (node2 == -1)){
1508 		return 0;
1509 	}
1510 
1511 	// DA: 1/7/99 Need to check the actual Sexp_node.text, not possible variable, which can be equal
1512 	if (stricmp(Sexp_nodes[node1].text, Sexp_nodes[node2].text) != 0){
1513 		return 0;
1514 	}
1515 
1516 	if (!cmp_sexp_chains(Sexp_nodes[node1].first, Sexp_nodes[node2].first)){
1517 		return 0;
1518 	}
1519 
1520 	if (!cmp_sexp_chains(Sexp_nodes[node1].rest, Sexp_nodes[node2].rest)){
1521 		return 0;
1522 	}
1523 
1524 	return 1;
1525 }
1526 
1527 /**
1528  * Determine if an sexp node is within the given sexp chain.
1529  */
query_node_in_sexp(int node,int sexp)1530 int query_node_in_sexp(int node, int sexp)
1531 {
1532 	if (sexp == -1){
1533 		return 0;
1534 	}
1535 	if (node == sexp){
1536 		return 1;
1537 	}
1538 
1539 	if (query_node_in_sexp(node, Sexp_nodes[sexp].first)){
1540 		return 1;
1541 	}
1542 	if (query_node_in_sexp(node, Sexp_nodes[sexp].rest)){
1543 		return 1;
1544 	}
1545 
1546 	return 0;
1547 }
1548 
1549 /**
1550  * Find the index of the list associated with an operator
1551  */
find_sexp_list(int num)1552 int find_sexp_list(int num)
1553 {
1554 	int i;
1555 
1556 	for (i = 0; i < Num_sexp_nodes; i++)
1557 	{
1558 		if (Sexp_nodes[i].first == num)
1559 			return i;
1560 	}
1561 
1562 	// not found
1563 	return -1;
1564 }
1565 
1566 /**
1567  * Find node of operator that item is an argument of.
1568  */
find_parent_operator(int node)1569 int find_parent_operator(int node)
1570 {
1571 	int i;
1572 	Assert((node >= 0) && (node < Num_sexp_nodes));
1573 
1574 	if (Sexp_nodes[node].subtype == SEXP_ATOM_OPERATOR)
1575 	{
1576 		node = find_sexp_list(node);
1577 
1578 		// are we already at the top of the list?  this will happen for non-standard sexps
1579 		// (sexps that fire instantly instead of using a conditional) such as:
1580 		// $Formula: ( do-nothing )
1581 		if (node < 0)
1582 			return -1;
1583 	}
1584 
1585 	// iterate backwards through the sexps nodes (i.e. do the inverse of CDR)
1586 	while (Sexp_nodes[node].subtype != SEXP_ATOM_OPERATOR)
1587 	{
1588 		for (i = 0; i < Num_sexp_nodes; i++)
1589 		{
1590 			if (Sexp_nodes[i].rest == node)
1591 				break;
1592 		}
1593 
1594 		if (i == Num_sexp_nodes)
1595 			return -1;  // not found, probably at top node already.
1596 
1597 		node = i;
1598 	}
1599 
1600 	return node;
1601 }
1602 
1603 /**
1604  * Determine if an sexpression node is the top level node of an sexpression tree.
1605  *
1606  * Top level nodes do not have their node id in anyone elses first or rest index.
1607  */
is_sexp_top_level(int node)1608 int is_sexp_top_level( int node )
1609 {
1610 	int i;
1611 
1612 	Assert((node >= 0) && (node < Num_sexp_nodes));
1613 
1614 	if (Sexp_nodes[node].type == SEXP_NOT_USED)
1615 		return 0;
1616 
1617 	for (i = 0; i < Num_sexp_nodes; i++)
1618 	{
1619 		if ((Sexp_nodes[i].type == SEXP_NOT_USED) || (i == node ))				// don't check myself or unused nodes
1620 			continue;
1621 
1622 		if ((Sexp_nodes[i].first == node) || (Sexp_nodes[i].rest == node))
1623 			return 0;
1624 	}
1625 
1626 	return 1;
1627 }
1628 
1629 /**
1630  * Find argument number
1631  */
find_argnum(int parent_node,int arg_node)1632 int find_argnum(int parent_node, int arg_node)
1633 {
1634 	int n, tally;
1635 	Assertion((parent_node >= 0) && (parent_node < Num_sexp_nodes), "find_argnum was passed an invalid parent!");
1636 	Assertion((arg_node >= 0) && (arg_node < Num_sexp_nodes), "find_argnum was passed an invalid child!");
1637 
1638 	n = CDR(parent_node);
1639 	tally = 0;
1640 
1641 	while (n >= 0)
1642 	{
1643 		// check if there is an operator node at this position which matches our expected node
1644 		if (CAR(n) == arg_node)
1645 			return tally;
1646 
1647 		tally++;
1648 		n = CDR(n);
1649 	}
1650 
1651 	// argument node not found
1652 	return -1;
1653 }
1654 
1655 /**
1656  * From an operator name, return its index in the array Operators
1657  */
get_operator_index(const char * token)1658 int get_operator_index(const char *token)
1659 {
1660 	Assertion(token != nullptr, "get_operator_index(char*) called with a null token; get a coder!\n");
1661 
1662 	for (size_t i=0; i < Operators.size(); i++){
1663 		if (Operators[i].text == token){
1664 			return (int)i;
1665 		}
1666 	}
1667 
1668 	return NOT_A_SEXP_OPERATOR;
1669 }
1670 
1671 /**
1672  * From a sexp node, return the index in the array Operators or NOT_A_SEXP_OPERATOR if not an operator
1673  */
get_operator_index(int node)1674 int get_operator_index(int node)
1675 {
1676 	Assertion(node >= 0 && node < Num_sexp_nodes, "Passed an out-of-range node index (%d) to get_operator_index(int)!", node);
1677 
1678 	if (!Fred_running && (Sexp_nodes[node].op_index != NO_OPERATOR_INDEX_DEFINED) ) {
1679 		return Sexp_nodes[node].op_index;
1680 	}
1681 
1682 	int index = get_operator_index(Sexp_nodes[node].text);
1683 	Sexp_nodes[node].op_index = index;
1684 	return index;
1685 }
1686 
1687 
1688 /**
1689  * From an operator name, return its constant (the number it was define'd with)
1690  */
get_operator_const(const char * token)1691 int get_operator_const(const char *token)
1692 {
1693 	int	idx = get_operator_index(token);
1694 
1695 	if (idx == NOT_A_SEXP_OPERATOR)
1696 		return 0;
1697 
1698 	return Operators[idx].value;
1699 }
1700 
get_operator_const(int node)1701 int get_operator_const(int node)
1702 {
1703 	if (!Fred_running && Sexp_nodes[node].op_index >= 0) {
1704 		return Operators[Sexp_nodes[node].op_index].value;
1705 	}
1706 
1707 	int	idx = get_operator_index(node);
1708 
1709 	if (idx == NOT_A_SEXP_OPERATOR)
1710 		return 0;
1711 
1712 	return Operators[idx].value;
1713 }
1714 
query_sexp_args_count(int node,bool only_valid_args=false)1715 int query_sexp_args_count(int node, bool only_valid_args = false)
1716 {
1717 	int count = 0;
1718 	int n = CDR(node);
1719 
1720 	for ( ; n != -1; n = CDR(n))
1721 	{
1722 		if (only_valid_args && !(Sexp_nodes[n].flags & SNF_ARGUMENT_VALID))
1723 			continue;
1724 
1725 		count++;
1726 	}
1727 
1728 	return count;
1729 }
1730 
1731 /**
1732  * Needed to fix bug with sexps like send-message list which have arguments that need to be supplied as a block
1733  *
1734  * @return 0 if the number of arguments for the supplied operation is wrong, 1 otherwise.
1735  */
check_operator_argument_count(int count,int op)1736 int check_operator_argument_count(int count, int op)
1737 {
1738 	if (count < Operators[op].min || count > Operators[op].max)
1739 		return 0;
1740 
1741 	// send-message-list has arguments as blocks of 4
1742 	// same with send-message-chain, but there's an extra argument
1743 
1744 	if (op == OP_SEND_MESSAGE_CHAIN)
1745 		count--;
1746 
1747 	if (op == OP_SEND_MESSAGE_LIST || op == OP_SEND_MESSAGE_CHAIN)
1748 		if (count % 4 != 0)
1749 			return 0;
1750 
1751 	return 1;
1752 }
1753 
1754 /**
1755  * Check SEXP syntax
1756  * @return 0 if ok, negative if there's an error in expression..
1757  * See the returns types in sexp.h
1758  */
check_sexp_syntax(int node,int return_type,int recursive,int * bad_node,int mode)1759 int check_sexp_syntax(int node, int return_type, int recursive, int *bad_node, int mode)
1760 {
1761 	int i = 0, z, t, type, argnum = 0, count, op, type2 = 0, op2;
1762 	int op_node;
1763 	int var_index = -1;
1764 	size_t st;
1765 
1766 	Assert(node >= 0 && node < Num_sexp_nodes);
1767 	Assert(Sexp_nodes[node].type != SEXP_NOT_USED);
1768 	if (Sexp_nodes[node].subtype == SEXP_ATOM_NUMBER && return_type == OPR_BOOL) {
1769 		// special case Mark seems to want supported
1770 		Assert(Sexp_nodes[node].first == -1);  // only lists should have a first pointer
1771 		if (Sexp_nodes[node].rest != -1)  // anything after the number?
1772 			return SEXP_CHECK_NONOP_ARGS; // if so, it's a syntax error
1773 
1774 		return 0;
1775 	}
1776 
1777 	op_node = node;		// save the node of the operator since we need to get to other args.
1778 	if (bad_node)
1779 		*bad_node = op_node;
1780 
1781 	if (Sexp_nodes[op_node].subtype != SEXP_ATOM_OPERATOR)
1782 		return SEXP_CHECK_OP_EXPECTED;  // not an operator, which it should always be
1783 
1784 	op = get_operator_index(op_node);
1785 	if (op == -1)
1786 		return SEXP_CHECK_UNKNOWN_OP;  // unrecognized operator
1787 
1788 	// check that types match - except that OPR_AMBIGUOUS matches everything
1789 	if (return_type != OPR_AMBIGUOUS)
1790 	{
1791 		// get the return type of the next thing
1792 		z = query_operator_return_type(op);
1793 		if (z == OPR_POSITIVE && return_type == OPR_NUMBER)
1794 		{
1795 			// positive data type can map to number data type just fine
1796 		}
1797 		// Goober5000's number hack
1798 		else if (z == OPR_NUMBER && return_type == OPR_POSITIVE)
1799 		{
1800 			// this isn't kosher, but we hack it to make it work
1801 		}
1802 		else if (z != return_type)
1803 		{
1804 			// anything else is a mismatch
1805 			return SEXP_CHECK_TYPE_MISMATCH;
1806 		}
1807 	}
1808 
1809 	count = query_sexp_args_count(op_node);
1810 
1811 	if (!check_operator_argument_count(count, op))
1812 		return SEXP_CHECK_BAD_ARG_COUNT;  // incorrect number of arguments
1813 
1814 	// Goober5000 - if this is a list of stuff that has the special argument as
1815 	// an item in the list, assume it's valid
1816 	if (special_argument_appears_in_sexp_list(op_node))
1817 		return 0;
1818 
1819 	node = Sexp_nodes[op_node].rest;
1820 	while (node != -1) {
1821 		type = query_operator_argument_type(op, argnum);
1822 		Assert(Sexp_nodes[node].type != SEXP_NOT_USED);
1823 		if (bad_node)
1824 			*bad_node = node;
1825 
1826 		if (Sexp_nodes[node].subtype == SEXP_ATOM_LIST) {
1827 			i = Sexp_nodes[node].first;
1828 			if (bad_node)
1829 				*bad_node = i;
1830 
1831 			// be sure to check to see if this node is a list of stuff and not an actual operator type
1832 			// thing.  (i.e. in the case of a cond statement, the conditional will fall into this if
1833 			// statement.  MORE TO DO HERE!!!!
1834 			if (Sexp_nodes[i].subtype == SEXP_ATOM_LIST)
1835 				return 0;
1836 
1837 			op2 = get_operator_index(i);
1838 			if (op2 == -1)
1839 				return SEXP_CHECK_UNKNOWN_OP;
1840 
1841 			type2 = query_operator_return_type(op2);
1842 			if (recursive) {
1843 				switch (type) {
1844 					case OPF_NUMBER:
1845 						t = OPR_NUMBER;
1846 						break;
1847 
1848 					case OPF_POSITIVE:
1849 						t = OPR_POSITIVE;
1850 						break;
1851 
1852 					case OPF_BOOL:
1853 						t = OPR_BOOL;
1854 						break;
1855 
1856 					case OPF_NULL:
1857 						t = OPR_NULL;
1858 						break;
1859 
1860 					// Goober5000
1861 					case OPF_FLEXIBLE_ARGUMENT:
1862 						t = OPR_FLEXIBLE_ARGUMENT;
1863 						break;
1864 
1865 					case OPF_AI_GOAL:
1866 						t = OPR_AI_GOAL;
1867 						break;
1868 
1869 					// special case for modify-variable
1870 					case OPF_AMBIGUOUS:
1871 						t = OPR_AMBIGUOUS;
1872 						break;
1873 
1874 					// these types can accept either lists of strings or indexes
1875 					case OPF_GAME_SND:
1876 					case OPF_FIREBALL:
1877 					case OPF_WEAPON_BANK_NUMBER:
1878 						t = OPR_POSITIVE;
1879 						break;
1880 
1881 					default:
1882 						return SEXP_CHECK_UNKNOWN_TYPE;  // no other return types available
1883 				}
1884 
1885 				if ((z = check_sexp_syntax(i, t, recursive, bad_node)) != 0) {
1886 					return z;
1887 				}
1888 			}
1889 
1890 		} else if (Sexp_nodes[node].subtype == SEXP_ATOM_NUMBER) {
1891 			type2 = OPR_POSITIVE;
1892 			auto ptr = CTEXT(node);
1893 			if (*ptr == '-') {
1894 				type2 = OPR_NUMBER;
1895 				ptr++;
1896 			}
1897 
1898 			if (type == OPF_BOOL)  // allow numbers to be used where boolean is required.
1899 				type2 = OPR_BOOL;
1900 
1901 			while (*ptr) {
1902 				if (!isdigit(*ptr))
1903 					return SEXP_CHECK_INVALID_NUM;  // not a valid number
1904 
1905 				ptr++;
1906 			}
1907 
1908 			i = atoi(CTEXT(node));
1909 			z = get_operator_const(op_node);
1910 			if ( (z == OP_HAS_DOCKED_DELAY) || (z == OP_HAS_UNDOCKED_DELAY) )
1911 				if ( (argnum == 2) && (i < 1) )
1912 					return SEXP_CHECK_NUM_RANGE_INVALID;
1913 
1914 			// valid color range 0 to 255 - FUBAR
1915 			if ((z == OP_CHANGE_IFF_COLOR)  && ((argnum >= 2) && (argnum <= 4)))
1916 			{
1917 				if ( i < 0 || i > 255)
1918 				{
1919 					return SEXP_CHECK_NUM_RANGE_INVALID;
1920 				}
1921 			}
1922 
1923 			z = get_operator_index(op_node);
1924 			if ( (query_operator_return_type(z) == OPR_AI_GOAL) && (argnum == Operators[op].min - 1) )
1925 				if ( (i < 0) || (i > 200) )
1926 					return SEXP_CHECK_NUM_RANGE_INVALID;
1927 
1928 		} else if (Sexp_nodes[node].subtype == SEXP_ATOM_STRING) {
1929 			type2 = SEXP_ATOM_STRING;
1930 
1931 		} else {
1932 			Assert(0);
1933 		}
1934 
1935 		// variables should only be typechecked.
1936 		if ((Sexp_nodes[node].type & SEXP_FLAG_VARIABLE) && (type != OPF_VARIABLE_NAME)) {
1937 			var_index = sexp_get_variable_index(node);
1938 			Assert(var_index != -1);
1939 
1940 			switch (type) {
1941 				case OPF_NUMBER:
1942 				case OPF_POSITIVE:
1943 					if (!(Sexp_variables[var_index].type & SEXP_VARIABLE_NUMBER))
1944 						return SEXP_CHECK_INVALID_VARIABLE_TYPE;
1945 				break;
1946 
1947 				case OPF_AMBIGUOUS:
1948 				case OPF_GAME_SND:
1949 				case OPF_FIREBALL:
1950 				case OPF_WEAPON_BANK_NUMBER:
1951 					break;
1952 
1953 				default:
1954 					if (!(Sexp_variables[var_index].type & SEXP_VARIABLE_STRING))
1955 						return SEXP_CHECK_INVALID_VARIABLE_TYPE;
1956 			}
1957 			node = Sexp_nodes[node].rest;
1958 			argnum++;
1959 			continue;
1960 		}
1961 
1962 		switch (type) {
1963 			case OPF_NAV_POINT:
1964 				if (type2 != SEXP_ATOM_STRING){
1965 					return SEXP_CHECK_TYPE_MISMATCH;
1966 				}
1967 				break;
1968 
1969 			case OPF_NUMBER:
1970 				if ((type2 != OPR_NUMBER) && (type2 != OPR_POSITIVE)){
1971 					return SEXP_CHECK_TYPE_MISMATCH;
1972 				}
1973 
1974 				break;
1975 
1976 			case OPF_POSITIVE:
1977 				if (type2 == OPR_NUMBER){
1978 					// Goober5000's number hack
1979 					break;
1980 					// return SEXP_CHECK_NEGATIVE_NUM;
1981 				}
1982 
1983 				if (type2 != OPR_POSITIVE){
1984 					return SEXP_CHECK_TYPE_MISMATCH;
1985 				}
1986 
1987 				break;
1988 
1989 			case OPF_SHIP_NOT_PLAYER:
1990 				if (type2 != SEXP_ATOM_STRING){
1991 					return SEXP_CHECK_TYPE_MISMATCH;
1992 				}
1993 
1994 				if (ship_name_lookup(CTEXT(node), 0) < 0)
1995 				{
1996 					if (Fred_running || !mission_check_ship_yet_to_arrive(CTEXT(node)))
1997 					{
1998 						return SEXP_CHECK_INVALID_SHIP;
1999 					}
2000 				}
2001 
2002 				break;
2003 
2004 			case OPF_SHIP_OR_NONE:
2005 				if (type2 != SEXP_ATOM_STRING)
2006 				{
2007 					return SEXP_CHECK_TYPE_MISMATCH;
2008 				}
2009 
2010 				if (stricmp(CTEXT(node), SEXP_NONE_STRING) != 0)		// none is okay
2011 				{
2012 					if (ship_name_lookup(CTEXT(node), 1) < 0)
2013 					{
2014 						if (Fred_running || !mission_check_ship_yet_to_arrive(CTEXT(node)))
2015 						{
2016 							return SEXP_CHECK_INVALID_SHIP;
2017 						}
2018 					}
2019 				}
2020 
2021 				break;
2022 
2023 			case OPF_SHIP:
2024 			case OPF_SHIP_POINT:
2025 				if (type2 != SEXP_ATOM_STRING){
2026 					return SEXP_CHECK_TYPE_MISMATCH;
2027 				}
2028 
2029 				if (ship_name_lookup(CTEXT(node), 1) < 0) {
2030 					if (Fred_running || !mission_check_ship_yet_to_arrive(CTEXT(node)))
2031 					{
2032 						if (type == OPF_SHIP)
2033 						{													// return invalid ship if not also looking for point
2034 							return SEXP_CHECK_INVALID_SHIP;
2035 						}
2036 
2037 						if (find_matching_waypoint(CTEXT(node)) == nullptr)
2038 						{
2039 							if (verify_vector(CTEXT(node)))					// verify return non-zero on invalid point
2040 							{
2041 								return SEXP_CHECK_INVALID_POINT;
2042 							}
2043 						}
2044 					}
2045 				}
2046 
2047 				break;
2048 
2049 			case OPF_WING:
2050 				if (type2 != SEXP_ATOM_STRING){
2051 					return SEXP_CHECK_TYPE_MISMATCH;
2052 				}
2053 
2054 				if (wing_name_lookup(CTEXT(node), 1) < 0){
2055 					return SEXP_CHECK_INVALID_WING;
2056 				}
2057 
2058 				break;
2059 
2060 			case OPF_SHIP_WING:
2061 			case OPF_SHIP_WING_WHOLETEAM:
2062 			case OPF_SHIP_WING_SHIPONTEAM_POINT:
2063 			case OPF_SHIP_WING_POINT:
2064 			case OPF_SHIP_WING_POINT_OR_NONE:
2065 			case OPF_ORDER_RECIPIENT:
2066 				if ( type2 != SEXP_ATOM_STRING ){
2067 					return SEXP_CHECK_TYPE_MISMATCH;
2068 				}
2069 
2070 				if (type == OPF_ORDER_RECIPIENT) {
2071 					if (!strcmp ("<all fighters>", CTEXT(node))) {
2072 						break;
2073 					}
2074 				}
2075 
2076 				// all of these have ships and wings in common
2077 				if (ship_name_lookup(CTEXT(node), 1) >= 0 || wing_name_lookup(CTEXT(node), 1) >= 0) {
2078 					break;
2079 				}
2080 				// also check arrival list if we're running the game
2081 				if (!Fred_running && mission_check_ship_yet_to_arrive(CTEXT(node))) {
2082 					break;
2083 				}
2084 
2085 				// none is okay for _OR_NONE
2086 				if (type == OPF_SHIP_WING_POINT_OR_NONE && !stricmp(CTEXT(node), SEXP_NONE_STRING))	{
2087 					break;
2088 				}
2089 
2090 				// two different ways of checking teams
2091 				if ((type == OPF_SHIP_WING_WHOLETEAM) && iff_lookup(CTEXT(node)) >= 0) {
2092 					break;
2093 				}
2094 				if ((type == OPF_SHIP_WING_SHIPONTEAM_POINT) && sexp_determine_team(CTEXT(node)) >= 0)	{
2095 					break;
2096 				}
2097 
2098 				// only other possibility is waypoints
2099 				if (type == OPF_SHIP_WING_SHIPONTEAM_POINT || type == OPF_SHIP_WING_POINT || type == OPF_SHIP_WING_POINT_OR_NONE) {
2100 						if (find_matching_waypoint(CTEXT(node)) == nullptr){
2101 							if (verify_vector(CTEXT(node))){  // non-zero on verify vector mean invalid!
2102 									return SEXP_CHECK_INVALID_POINT;
2103 								}
2104 							}
2105 					break;
2106 				}
2107 
2108 				// nothing left
2109 				return SEXP_CHECK_INVALID_SHIP_WING;
2110 
2111 			case OPF_AWACS_SUBSYSTEM:
2112 			case OPF_ROTATING_SUBSYSTEM:
2113 			case OPF_SUBSYSTEM:
2114 			case OPF_SUBSYSTEM_OR_NONE:
2115 			case OPF_SUBSYS_OR_GENERIC:
2116 			{
2117 				int shipnum,ship_class;
2118 				int ship_index;
2119 
2120 				if (type2 != SEXP_ATOM_STRING){
2121 					return SEXP_CHECK_TYPE_MISMATCH;
2122 				}
2123 
2124 				// none is okay for subsys_or_none
2125 				if (type == OPF_SUBSYSTEM_OR_NONE && !stricmp(CTEXT(node), SEXP_NONE_STRING))
2126 				{
2127 					break;
2128 				}
2129 
2130 				//  subsys_or_generic has a few extra options it accepts
2131 				if (type == OPF_SUBSYS_OR_GENERIC && (!(stricmp(CTEXT(node), SEXP_ALL_ENGINES_STRING)) || !(stricmp(CTEXT(node), SEXP_ALL_TURRETS_STRING)) )) {
2132 					break;
2133 				}
2134 
2135 				// we must get the model of the ship that is part of this sexpression and find a subsystem
2136 				// with that name.  This code assumes by default that the ship is *always* the first name
2137 				// in the sexpression.  If this is ever not the case, the code here must be changed to
2138 				// get the correct ship name.
2139 				switch(get_operator_const(op_node))
2140 				{
2141 					case OP_CAP_SUBSYS_CARGO_KNOWN_DELAY:
2142 					case OP_DISTANCE_CENTER_SUBSYSTEM:
2143 					case OP_DISTANCE_BBOX_SUBSYSTEM:
2144 					case OP_SET_CARGO:
2145 					case OP_IS_CARGO:
2146 					case OP_CHANGE_AI_CLASS:
2147 					case OP_IS_AI_CLASS:
2148 					case OP_MISSILE_LOCKED:
2149 					case OP_SHIP_SUBSYS_GUARDIAN_THRESHOLD:
2150 					case OP_IS_IN_TURRET_FOV:
2151 					case OP_TURRET_SET_FORCED_TARGET:
2152 						ship_index = CDR(CDR(op_node));
2153 						break;
2154 
2155 					case OP_BEAM_FIRE:
2156 					case OP_TURRET_SET_FORCED_SUBSYS_TARGET:
2157 						if(argnum == 1){
2158 							ship_index = CDR(op_node);
2159 						} else {
2160 							ship_index = CDR(CDR(CDR(op_node)));
2161 						}
2162 						break;
2163 
2164 					case OP_BEAM_FLOATING_FIRE:
2165 						ship_index = CDDDDDR(CDDR(op_node));
2166 						break;
2167 
2168 					case OP_QUERY_ORDERS:
2169 						ship_index = CDR(CDR(CDR(CDR(op_node))));
2170 						break;
2171 
2172 					case OP_WEAPON_CREATE:
2173 						ship_index = CDDDDDR(CDDDDR(op_node));
2174 						break;
2175 
2176 					default:
2177 						ship_index = CDR(op_node);
2178 						break;
2179 				}
2180 
2181 				auto shipname = CTEXT(ship_index);
2182 				shipnum = ship_name_lookup(shipname, 1);
2183 				if (shipnum >= 0)
2184 				{
2185 					ship_class = Ships[shipnum].ship_info_index;
2186 				}
2187 				else
2188 				{
2189 					// must try to find the ship in the arrival list
2190 					p_object *p_objp = mission_parse_get_arrival_ship(shipname);
2191 
2192 					if (!p_objp)
2193 					{
2194 						if (type == OPF_SUBSYSTEM_OR_NONE)
2195 							break;
2196 						else
2197 							return SEXP_CHECK_INVALID_SHIP;
2198 					}
2199 
2200 					ship_class = p_objp->ship_class;
2201 				}
2202 
2203 				// check for the special "hull" value
2204 				if ( (Operators[op].value == OP_SABOTAGE_SUBSYSTEM) || (Operators[op].value == OP_REPAIR_SUBSYSTEM) || (Operators[op].value == OP_SET_SUBSYSTEM_STRNGTH) || (Operators[op].value == OP_SET_ARMOR_TYPE) || (Operators[op].value == OP_BEAM_FIRE)) {
2205 					if ( !stricmp( CTEXT(node), SEXP_HULL_STRING) || !stricmp( CTEXT(node), SEXP_SIM_HULL_STRING) ){
2206 						break;
2207 					}
2208 				}
2209 				// check for special "shields" value for armor types
2210 				if (Operators[op].value == OP_SET_ARMOR_TYPE) {
2211 					if ( !stricmp( CTEXT(node), SEXP_SHIELD_STRING) || !stricmp( CTEXT(node), SEXP_SIM_HULL_STRING) ){
2212 						break;
2213 					}
2214 				}
2215 
2216 				for (i=0; i<Ship_info[ship_class].n_subsystems; i++)
2217 				{
2218 					if (!subsystem_stricmp(Ship_info[ship_class].subsystems[i].subobj_name, CTEXT(node)))
2219 					{
2220 						break;
2221 					}
2222 				}
2223 
2224 				if (i == Ship_info[ship_class].n_subsystems)
2225 				{
2226 					return SEXP_CHECK_INVALID_SUBSYS;
2227 				}
2228 
2229 				if(Fred_running)
2230 				{
2231 					// if we're checking for an AWACS subsystem and this is not an awacs subsystem
2232 					if((type == OPF_AWACS_SUBSYSTEM) && !(Ship_info[ship_class].subsystems[i].flags[Model::Subsystem_Flags::Awacs]))
2233 					{
2234 						return SEXP_CHECK_INVALID_SUBSYS;
2235 					}
2236 
2237 					// rotating subsystem, like above - Goober5000
2238 					if ((type == OPF_ROTATING_SUBSYSTEM) && !(Ship_info[ship_class].subsystems[i].flags[Model::Subsystem_Flags::Rotates]))
2239 					{
2240 						return SEXP_CHECK_INVALID_SUBSYS;
2241 					}
2242 				}
2243 
2244 				break;
2245 			}
2246 
2247 			case OPF_SUBSYSTEM_TYPE:
2248 				for (i = 0; i < SUBSYSTEM_MAX; i++)
2249 				{
2250 					if (!stricmp(CTEXT(node), Subsystem_types[i]))
2251 						break;
2252 				}
2253 
2254 				if (i == SUBSYSTEM_MAX)
2255 					return SEXP_CHECK_INVALID_SUBSYS_TYPE;
2256 
2257 				break;
2258 
2259 			case OPF_POINT:
2260 				if (type2 != SEXP_ATOM_STRING)
2261 				{
2262 					return SEXP_CHECK_TYPE_MISMATCH;
2263 				}
2264 
2265 				if (find_matching_waypoint(CTEXT(node)) == nullptr)
2266 				{
2267 					if (verify_vector(CTEXT(node)))
2268 					{
2269 						return SEXP_CHECK_INVALID_POINT;
2270 					}
2271 				}
2272 
2273 				break;
2274 
2275 			case OPF_IFF:
2276 				if (type2 != SEXP_ATOM_STRING)
2277 				{
2278 					return SEXP_CHECK_TYPE_MISMATCH;
2279 				}
2280 
2281 				if (iff_lookup(CTEXT(node)) < 0)
2282 				{
2283 					return SEXP_CHECK_INVALID_IFF;
2284 				}
2285 
2286 				break;
2287 
2288 			case OPF_AI_CLASS:
2289 				if (type2 != SEXP_ATOM_STRING)
2290 				{
2291 					return SEXP_CHECK_TYPE_MISMATCH;
2292 				}
2293 
2294 				for (i=0; i<Num_ai_classes; i++)
2295 				{
2296 					if (!stricmp(Ai_class_names[i], CTEXT(node)))
2297 					{
2298 						break;
2299 					}
2300 				}
2301 
2302 				if (i == Num_ai_classes)
2303 				{
2304 					return SEXP_CHECK_INVALID_AI_CLASS;
2305 				}
2306 
2307 				break;
2308 
2309 			case OPF_ARRIVAL_LOCATION:
2310 				if (type2 != SEXP_ATOM_STRING)
2311 				{
2312 					return SEXP_CHECK_TYPE_MISMATCH;
2313 				}
2314 
2315 				for (i=0; i<MAX_ARRIVAL_NAMES; i++)
2316 				{
2317 					if (!stricmp(Arrival_location_names[i], CTEXT(node)))
2318 					{
2319 						break;
2320 					}
2321 				}
2322 
2323 				if (i == MAX_ARRIVAL_NAMES)
2324 				{
2325 					return SEXP_CHECK_INVALID_ARRIVAL_LOCATION;
2326 				}
2327 
2328 				break;
2329 
2330 			case OPF_DEPARTURE_LOCATION:
2331 				if (type2 != SEXP_ATOM_STRING)
2332 				{
2333 					return SEXP_CHECK_TYPE_MISMATCH;
2334 				}
2335 
2336 				for (i=0; i<MAX_DEPARTURE_NAMES; i++)
2337 				{
2338 					if (!stricmp(Departure_location_names[i], CTEXT(node)))
2339 					{
2340 						break;
2341 					}
2342 				}
2343 
2344 				if (i == MAX_DEPARTURE_NAMES)
2345 				{
2346 					return SEXP_CHECK_INVALID_DEPARTURE_LOCATION;
2347 				}
2348 
2349 				break;
2350 
2351 			case OPF_ARRIVAL_ANCHOR_ALL:
2352 				if (type2 != SEXP_ATOM_STRING)
2353 				{
2354 					return SEXP_CHECK_TYPE_MISMATCH;
2355 				}
2356 				else
2357 				{
2358 					int valid = 0;
2359 
2360 					// <any friendly>, etc.
2361 					if (get_special_anchor(CTEXT(node)) >= 0)
2362 					{
2363 						valid = 1;
2364 					}
2365 
2366 					if (ship_name_lookup(CTEXT(node), 1) >= 0)
2367 					{
2368 						valid = 1;
2369 					}
2370 
2371 					if (!Fred_running && mission_check_ship_yet_to_arrive(CTEXT(node)))
2372 					{
2373 						valid = 1;
2374 					}
2375 
2376 					if (!valid)
2377 					{
2378 						return SEXP_CHECK_INVALID_ARRIVAL_ANCHOR_ALL;
2379 					}
2380 				}
2381 
2382 				break;
2383 
2384 			case OPF_SOUNDTRACK_NAME:
2385 				if (type2 != SEXP_ATOM_STRING){
2386 					return SEXP_CHECK_TYPE_MISMATCH;
2387 				}
2388 
2389 				if (!stricmp(CTEXT(node), "<No Music>"))
2390 					break;
2391 
2392 				if (Cmdline_freespace_no_music)
2393 					break;
2394 
2395 				for (i=0; i<Num_soundtracks; i++)
2396 				{
2397 					if (!stricmp(CTEXT(node), Soundtracks[i].name))
2398 					{
2399 						break;
2400 					}
2401 				}
2402 
2403 				if (i == Num_soundtracks)
2404 					return SEXP_CHECK_INVALID_SOUNDTRACK_NAME;
2405 
2406 				break;
2407 
2408 			case OPF_SHIP_WITH_BAY:
2409 			{
2410 				auto name = CTEXT(node);
2411 				int shipnum = -1;
2412 
2413 				if (type2 != SEXP_ATOM_STRING)
2414 					return SEXP_CHECK_TYPE_MISMATCH;
2415 
2416 				if (!stricmp(name, "<no anchor>"))
2417 					break;
2418 
2419 				shipnum = ship_name_lookup(name, 1);
2420 				if (shipnum < 0)
2421 				{
2422 					if (Fred_running)
2423 						return SEXP_CHECK_INVALID_SHIP;
2424 
2425 					if (!mission_check_ship_yet_to_arrive(name))
2426 						return SEXP_CHECK_INVALID_SHIP;
2427 
2428 					// Goober5000 - since we can't check POFs for ships which have yet to arrive
2429 					// (not without a bit of work anyway), just assume they're okay
2430 					break;
2431 				}
2432 
2433 				// ship exists at this point
2434 
2435 				// now determine if this ship has a docking bay
2436 				if (!ship_has_dock_bay(shipnum))
2437 					return SEXP_CHECK_INVALID_SHIP_WITH_BAY;
2438 
2439 				break;
2440 			}
2441 
2442 			case OPF_SUPPORT_SHIP_CLASS:
2443 				if (type2 != SEXP_ATOM_STRING){
2444 					return SEXP_CHECK_TYPE_MISMATCH;
2445 				}
2446 
2447 				if (!stricmp(CTEXT(node), "<species support ship class>"))
2448 					break;
2449 
2450 				if (!stricmp(CTEXT(node), "<any support ship class>"))
2451 					break;
2452 
2453 				i = -1;
2454 				for (auto it = Ship_info.cbegin(); it != Ship_info.cend(); ++it) {
2455 					if (!stricmp(CTEXT(node), it->name))
2456 					{
2457 						if (it->flags[Ship::Info_Flags::Support])
2458 						{
2459 							i = (int)std::distance(Ship_info.cbegin(), it);
2460 							break;
2461 						}
2462 					}
2463 				}
2464 
2465 				if (i == -1)
2466 					return SEXP_CHECK_INVALID_SUPPORT_SHIP_CLASS;
2467 
2468 				break;
2469 
2470 			case OPF_BOOL:
2471 				if (type2 != OPR_BOOL){
2472 					return SEXP_CHECK_TYPE_MISMATCH;
2473 				}
2474 
2475 				break;
2476 
2477 			case OPF_AI_ORDER:
2478 				if ( type2 != SEXP_ATOM_STRING ){
2479 					return SEXP_CHECK_TYPE_MISMATCH;
2480 				}
2481 
2482 				break;
2483 
2484 			case OPF_NULL:
2485 				if (type2 != OPR_NULL){
2486 					return SEXP_CHECK_TYPE_MISMATCH;
2487 				}
2488 
2489 				break;
2490 
2491 			case OPF_SSM_CLASS:
2492 				if ( type2 != SEXP_ATOM_STRING ) {
2493 					return SEXP_CHECK_TYPE_MISMATCH;
2494 				}
2495 
2496 				if (ssm_info_lookup(CTEXT(node)) < 0) {
2497 					return SEXP_CHECK_INVALID_SSM_CLASS;
2498 				}
2499 
2500 				break;
2501 
2502 			// Goober5000
2503 			case OPF_FLEXIBLE_ARGUMENT:
2504 				if (type2 != OPR_FLEXIBLE_ARGUMENT) {
2505 					return SEXP_CHECK_TYPE_MISMATCH;
2506 				}
2507 				break;
2508 
2509 			// Goober5000
2510 			case OPF_ANYTHING:
2511 				break;
2512 
2513 			case OPF_AI_GOAL:
2514 				if (type2 != OPR_AI_GOAL){
2515 					return SEXP_CHECK_TYPE_MISMATCH;
2516 				}
2517 
2518 				if (Fred_running) {
2519 					int ship_num, ship2, w = 0;
2520 
2521 					// if it's the "goals" operator, this is part of initial orders, so just assume it's okay
2522 					if (get_operator_const(op_node) == OP_GOALS_ID) {
2523 						break;
2524 					}
2525 
2526 					ship_num = ship_name_lookup(CTEXT(Sexp_nodes[op_node].rest), 1);	// Goober5000 - include players
2527 					if (ship_num < 0) {
2528 						w = wing_name_lookup(CTEXT(Sexp_nodes[op_node].rest));
2529 						if (w < 0) {
2530 							if (bad_node){
2531 								*bad_node = Sexp_nodes[op_node].rest;
2532 							}
2533 
2534 							return SEXP_CHECK_INVALID_SHIP;  // should have already been caught earlier, but just in case..
2535 						}
2536 					}
2537 
2538 					Assert(Sexp_nodes[node].subtype == SEXP_ATOM_LIST);
2539 					z = Sexp_nodes[node].first;
2540 					Assert(Sexp_nodes[z].subtype != SEXP_ATOM_LIST);
2541 					z = get_operator_const(z);
2542 					if (ship_num >= 0) {
2543 						if (!query_sexp_ai_goal_valid(z, ship_num)){
2544 							return SEXP_CHECK_ORDER_NOT_ALLOWED;
2545 						}
2546 
2547 					} else {
2548 						for (i=0; i<Wings[w].wave_count; i++){
2549 							if (!query_sexp_ai_goal_valid(z, Wings[w].ship_index[i])){
2550 								return SEXP_CHECK_ORDER_NOT_ALLOWED;
2551 							}
2552 						}
2553 					}
2554 
2555 					if ((z == OP_AI_DOCK) && (Sexp_nodes[node].rest >= 0)) {
2556 						ship2 = ship_name_lookup(CTEXT(Sexp_nodes[node].rest), 1);	// Goober5000 - include players
2557 						if ((ship_num < 0) || !ship_docking_valid(ship_num, ship2)){
2558 							return SEXP_CHECK_DOCKING_NOT_ALLOWED;
2559 						}
2560 					}
2561 				}
2562 
2563 				// we should check the syntax of the actual goal!!!!
2564 				z = Sexp_nodes[node].first;
2565 				if ((z = check_sexp_syntax(z, OPR_AI_GOAL, recursive, bad_node)) != 0){
2566 					return z;
2567 				}
2568 
2569 				break;
2570 
2571 			case OPF_SHIP_TYPE:
2572 				if (type2 != SEXP_ATOM_STRING){
2573 					return SEXP_CHECK_TYPE_MISMATCH;
2574 				}
2575 
2576 				i = ship_type_name_lookup(CTEXT(node));
2577 
2578 				if (i < 0){
2579 					return SEXP_CHECK_INVALID_SHIP_TYPE;
2580 				}
2581 
2582 				break;
2583 
2584 			case OPF_WAYPOINT_PATH:
2585 				if (find_matching_waypoint_list(CTEXT(node)) == nullptr) {
2586 					return SEXP_CHECK_TYPE_MISMATCH;
2587 				}
2588 				break;
2589 
2590 			case OPF_MESSAGE:
2591 				if (type2 != SEXP_ATOM_STRING)
2592 					return SEXP_CHECK_TYPE_MISMATCH;
2593 
2594 				if (Fred_running) {
2595 					for (i=0; i<Num_messages; i++)
2596 						if (!stricmp(Messages[i].name, CTEXT(node)))
2597 							break;
2598 
2599 					if (i == Num_messages)
2600 						return SEXP_CHECK_UNKNOWN_MESSAGE;
2601 				}
2602 
2603 				break;
2604 
2605 			case OPF_PRIORITY: {
2606 				if (type2 != SEXP_ATOM_STRING)
2607 					return SEXP_CHECK_TYPE_MISMATCH;
2608 
2609 				if (Fred_running) {  // should still check in Fred though..
2610 					auto name = CTEXT(node);
2611 					if (!stricmp(name, "low") || !stricmp(name, "normal") || !stricmp(name, "high"))
2612 						break;
2613 
2614 					return SEXP_CHECK_INVALID_PRIORITY;
2615 				}
2616 
2617 				break;
2618 			}
2619 
2620 			case OPF_MISSION_NAME:
2621 				if (type2 != SEXP_ATOM_STRING)
2622 					return SEXP_CHECK_TYPE_MISMATCH;
2623 
2624 				if (Fred_running) {
2625 					if (mode == SEXP_MODE_CAMPAIGN) {
2626 						for (i=0; i<Campaign.num_missions; i++)
2627 							if (!stricmp(CTEXT(node), Campaign.missions[i].name)) {
2628 								if ((i != Sexp_useful_number) && (Campaign.missions[i].level >= Campaign.missions[Sexp_useful_number].level))
2629 									return SEXP_CHECK_INVALID_LEVEL;
2630 
2631 								break;
2632 							}
2633 
2634 						if (i == Campaign.num_missions)
2635 							return SEXP_CHECK_INVALID_MISSION_NAME;
2636 
2637 					} else {
2638 						// mwa -- put the following if statement to prevent Fred errors for possibly valid
2639 						// conditions.  We should do something else here!!!
2640 						if ( (Operators[op].value == OP_PREVIOUS_EVENT_TRUE) || (Operators[op].value == OP_PREVIOUS_EVENT_FALSE) || (Operators[op].value == OP_PREVIOUS_EVENT_INCOMPLETE)
2641 							|| (Operators[op].value == OP_PREVIOUS_GOAL_TRUE) || (Operators[op].value == OP_PREVIOUS_GOAL_FALSE) || (Operators[op].value == OP_PREVIOUS_GOAL_INCOMPLETE) )
2642 							break;
2643 
2644 						if (!(*Mission_filename) || stricmp(Mission_filename, CTEXT(node)) != 0)
2645 							return SEXP_CHECK_INVALID_MISSION_NAME;
2646 					}
2647 				}
2648 
2649 				break;
2650 
2651 			case OPF_GOAL_NAME:
2652 				if (type2 != SEXP_ATOM_STRING)
2653 					return SEXP_CHECK_TYPE_MISMATCH;
2654 
2655 				// we only need to check the campaign list if running in Fred and are in campaign mode.
2656 				// otherwise, check the set of current goals
2657 				if ( Fred_running && (mode == SEXP_MODE_CAMPAIGN) ) {
2658 					z = find_parent_operator(node);
2659 					Assert(z >= 0);
2660 					z = Sexp_nodes[z].rest;  // first argument of operator should be mission name
2661 					Assert(z >= 0);
2662 					for (i=0; i<Campaign.num_missions; i++)
2663 						if (!stricmp(CTEXT(z), Campaign.missions[i].name))
2664 							break;
2665 
2666 					// read the goal/event list from the mission file if both num_goals and num_events
2667 					// are < 0
2668 					if ((Campaign.missions[i].num_goals <= 0) && (Campaign.missions[i].num_events <= 0) )
2669 						read_mission_goal_list(i);
2670 
2671 					if (i < Campaign.num_missions) {
2672 						for (t=0; t<Campaign.missions[i].num_goals; t++)
2673 							if (!stricmp(CTEXT(node), Campaign.missions[i].goals[t].name))
2674 								break;
2675 
2676 						if (t == Campaign.missions[i].num_goals)
2677 							return SEXP_CHECK_INVALID_GOAL_NAME;
2678 					}
2679 				} else {
2680 					// MWA -- short circuit evaluation of these things for now.
2681 					if ( (Operators[op].value == OP_PREVIOUS_GOAL_TRUE) || (Operators[op].value == OP_PREVIOUS_GOAL_FALSE) || (Operators[op].value == OP_PREVIOUS_GOAL_INCOMPLETE) )
2682 						break;
2683 
2684 					for (i=0; i<Num_goals; i++)
2685 						if (!stricmp(CTEXT(node), Mission_goals[i].name))
2686 							break;
2687 
2688 					if (i == Num_goals)
2689 						return SEXP_CHECK_INVALID_GOAL_NAME;
2690 				}
2691 
2692 				break;
2693 
2694 			case OPF_EVENT_NAME:
2695 				if ( type2 != SEXP_ATOM_STRING )
2696 					return SEXP_CHECK_TYPE_MISMATCH;
2697 
2698 				// like above checking for goals, check events in the campaign only if in Fred
2699 				// and only if in campaign mode.  Otherwise, check the current set of events
2700 				if ( Fred_running && (mode == SEXP_MODE_CAMPAIGN) ) {
2701 					z = find_parent_operator(node);
2702 					Assert(z >= 0);
2703 					z = Sexp_nodes[z].rest;  // first argument of operator should be mission name
2704 					Assert(z >= 0);
2705 					for (i=0; i<Campaign.num_missions; i++)
2706 						if (!stricmp(CTEXT(z), Campaign.missions[i].name))
2707 							break;
2708 
2709 					// read the goal/event list from the mission file if both num_goals and num_events
2710 					// are < 0
2711 					if ((Campaign.missions[i].num_goals <= 0) && (Campaign.missions[i].num_events <= 0) )
2712 						read_mission_goal_list(i);
2713 
2714 					if (i < Campaign.num_missions) {
2715 						for (t=0; t<Campaign.missions[i].num_events; t++)
2716 							if (!stricmp(CTEXT(node), Campaign.missions[i].events[t].name))
2717 								break;
2718 
2719 						if (t == Campaign.missions[i].num_events)
2720 							return SEXP_CHECK_INVALID_EVENT_NAME;
2721 					}
2722 				} else {
2723 					// MWA -- short circuit evaluation of these things for now.
2724 					if ( (Operators[op].value == OP_PREVIOUS_EVENT_TRUE) || (Operators[op].value == OP_PREVIOUS_EVENT_FALSE) || (Operators[op].value == OP_PREVIOUS_EVENT_INCOMPLETE) )
2725 						break;
2726 
2727 					for ( i = 0; i < Num_mission_events; i++ ) {
2728 						if ( !stricmp(CTEXT(node), Mission_events[i].name) )
2729 							break;
2730 					}
2731 					if ( i == Num_mission_events )
2732 						return SEXP_CHECK_INVALID_EVENT_NAME;
2733 				}
2734 				break;
2735 
2736 			case OPF_DOCKER_POINT:
2737 				if (type2 != SEXP_ATOM_STRING)
2738 					return SEXP_CHECK_TYPE_MISMATCH;
2739 
2740 				// This makes massive assumptions about the structure of the SEXP using it. If you add any
2741 				// new SEXPs that use this OPF, you will probably need to edit this section to accommodate them.
2742 				if (Fred_running) {
2743 					int ship_num, model;
2744 
2745 					// Look for the node containing the docker ship as its first argument. For set-docked, we want
2746 					// the current SEXP. Otherwise (for ai-dock), we want its parent.
2747 					if (get_operator_const(op_node) == OP_SET_DOCKED) {
2748 						z = op_node;
2749 					}
2750 					else {
2751 						z = find_parent_operator(op_node);
2752 
2753 						// if it's the "goals" operator, this is part of initial orders, so just assume it's okay
2754 						if (get_operator_const(z) == OP_GOALS_ID) {
2755 							break;
2756 						}
2757 					}
2758 
2759 					// look for the ship this goal is being assigned to
2760 					ship_num = ship_name_lookup(CTEXT(Sexp_nodes[z].rest), 1);
2761 					if (ship_num < 0) {
2762 						if (bad_node)
2763 							*bad_node = Sexp_nodes[z].rest;
2764 
2765 						return SEXP_CHECK_INVALID_SHIP;  // should have already been caught earlier, but just in case..
2766 					}
2767 
2768 					model = Ship_info[Ships[ship_num].ship_info_index].model_num;
2769 					z = model_get_num_dock_points(model);
2770 					for (i=0; i<z; i++)
2771 						if (!stricmp(CTEXT(node), model_get_dock_name(model, i)))
2772 							break;
2773 
2774 					if (i == z)
2775 						return SEXP_CHECK_INVALID_DOCKER_POINT;
2776 				}
2777 
2778 				break;
2779 
2780 			case OPF_DOCKEE_POINT:
2781 				if (type2 != SEXP_ATOM_STRING)
2782 					return SEXP_CHECK_TYPE_MISMATCH;
2783 
2784 				// This makes massive assumptions about the structure of the SEXP using it. If you add any
2785 				// new SEXPs that use this OPF, you will probably need to edit this section to accommodate them.
2786 				if (Fred_running) {
2787 					int ship_num, model;
2788 
2789 					// If we're using set-docked, we want to look up the ship from the third SEXP argument.
2790 					if (get_operator_const(op_node) == OP_SET_DOCKED) {
2791 						//Navigate to the third argument
2792 						z = op_node;
2793 						for (i = 0; i < 3; i++)
2794 							z = Sexp_nodes[z].rest;
2795 
2796 						ship_num = ship_name_lookup(Sexp_nodes[z].text, 1);
2797 					}
2798 					else {
2799 						ship_num = ship_name_lookup(CTEXT(Sexp_nodes[op_node].rest), 1);
2800 					}
2801 
2802 					if (ship_num < 0) {
2803 						if (bad_node)
2804 							*bad_node = Sexp_nodes[op_node].rest;
2805 
2806 						return SEXP_CHECK_INVALID_SHIP;  // should have already been caught earlier, but just in case..
2807 					}
2808 
2809 					model = Ship_info[Ships[ship_num].ship_info_index].model_num;
2810 					z = model_get_num_dock_points(model);
2811 					for (i=0; i<z; i++)
2812 						if (!stricmp(CTEXT(node), model_get_dock_name(model, i)))
2813 							break;
2814 
2815 					if (i == z)
2816 						return SEXP_CHECK_INVALID_DOCKEE_POINT;
2817 				}
2818 
2819 				break;
2820 
2821 			case OPF_WHO_FROM:
2822 				if (type2 != SEXP_ATOM_STRING)
2823 					return SEXP_CHECK_TYPE_MISMATCH;
2824 
2825 				if (*CTEXT(node) != '#') {  // not a manual source?
2826 					if ( stricmp(CTEXT(node), "<any wingman>") != 0)
2827 						if ( stricmp(CTEXT(node), "<none>") != 0 ) // not a special token?
2828 							if ((ship_name_lookup(CTEXT(node), 1) < 0) && (wing_name_lookup(CTEXT(node), 1) < 0))  // is it in the mission?
2829 								if (Fred_running || !mission_check_ship_yet_to_arrive(CTEXT(node)))
2830 									return SEXP_CHECK_INVALID_MSG_SOURCE;
2831 				}
2832 
2833 				break;
2834 
2835 			//Karajorma
2836 			case OPF_PERSONA:
2837 				if (type2 != SEXP_ATOM_STRING) {
2838 					return SEXP_CHECK_TYPE_MISMATCH;
2839 				}
2840 
2841 				for (i=0; i < Num_personas ; i++) {
2842 					if (!strcmp(CTEXT(node), Personas[i].name)) {
2843 						break;
2844 					}
2845 				}
2846 
2847 				if (i == Num_personas) {
2848 					return SEXP_CHECK_INVALID_PERSONA_NAME;
2849 				}
2850 				break;
2851 
2852 			case OPF_MISSION_MOOD:
2853 				if (type2 != SEXP_ATOM_STRING) {
2854 					return SEXP_CHECK_TYPE_MISMATCH;
2855 				}
2856 
2857 				for (i = 0; i < (int)Builtin_moods.size(); i++) {
2858 					if (!strcmp(Builtin_moods[i].c_str(), CTEXT(node))) {
2859 						break;
2860 					}
2861 				}
2862 
2863 				if (i == (int)Builtin_moods.size()) {
2864 					return SEXP_CHECK_INVALID_MISSION_MOOD;
2865 				}
2866 
2867 				break;
2868 
2869 			case OPF_SHIP_FLAG:
2870 				{
2871 				bool found = false;
2872 				for ( i = 0; i < MAX_OBJECT_FLAG_NAMES; i++) {
2873 					if (!stricmp(Object_flag_names[i].flag_name, CTEXT(node))) {
2874 						found = true;
2875 						break;
2876 					}
2877 				}
2878 
2879 				if (!found) {
2880 					for ( i = 0; i < MAX_SHIP_FLAG_NAMES; i++) {
2881 						if (!stricmp(Ship_flag_names[i].flag_name, CTEXT(node))) {
2882 							found = true;
2883 							break;
2884 						}
2885 					}
2886 				}
2887 
2888 				if (!found) {
2889 					for ( i = 0; i < MAX_AI_FLAG_NAMES; i++) {
2890 						if (!stricmp(Ai_flag_names[i].flag_name, CTEXT(node))) {
2891 							found = true;
2892 							break;
2893 						}
2894 					}
2895 				}
2896 
2897 				if (!found) {
2898 					return SEXP_CHECK_INVALID_SHIP_FLAG;
2899 				}
2900 
2901 				break;
2902 				}
2903 
2904 			case OPF_TEAM_COLOR:
2905 				if (type2 != SEXP_ATOM_STRING) {
2906 					return SEXP_CHECK_TYPE_MISMATCH;
2907 				}
2908 
2909 				if (!stricmp(CTEXT(node), "none"))
2910 					break;
2911 
2912 				if (Team_Colors.find(CTEXT(node)) == Team_Colors.end())
2913 					return SEXP_CHECK_INVALID_TEAM_COLOR;
2914 
2915 				break;
2916 
2917 			case OPF_FONT:
2918 				if (type2 != SEXP_ATOM_STRING) {
2919 					return SEXP_CHECK_TYPE_MISMATCH;
2920 				}
2921 
2922 				if (font::FontManager::getFont(CTEXT(node)) == nullptr)
2923 					return SEXP_CHECK_INVALID_FONT;
2924 
2925 				break;
2926 
2927 			case OPF_SOUND_ENVIRONMENT:
2928 				if (type2 != SEXP_ATOM_STRING) {
2929 					return SEXP_CHECK_TYPE_MISMATCH;
2930 				}
2931 
2932 				if (stricmp(CTEXT(node), SEXP_NONE_STRING) != 0 && ds_eax_get_preset_id(CTEXT(node)) < 0) {
2933 					return SEXP_CHECK_INVALID_SOUND_ENVIRONMENT;
2934 				}
2935 				break;
2936 
2937 			case OPF_AUDIO_VOLUME_OPTION:
2938 				if (type2 != SEXP_ATOM_STRING) {
2939 					return SEXP_CHECK_TYPE_MISMATCH;
2940 				}
2941 
2942 				if (audio_volume_option_lookup(CTEXT(node)) == -1)
2943 					return SEXP_CHECK_INVALID_AUDIO_VOLUME_OPTION;
2944 				break;
2945 
2946 			case OPF_BUILTIN_HUD_GAUGE:
2947 			{
2948 				if (type2 != SEXP_ATOM_STRING) {
2949 					return SEXP_CHECK_TYPE_MISMATCH;
2950 				}
2951 				auto gauge_name = CTEXT(node);
2952 
2953 				// for compatibility, since this operator now uses a different set of parameters
2954 				if (get_operator_const(op_node) == OP_FLASH_HUD_GAUGE) {
2955 					bool found = false;
2956 					for (int legacy_idx = 0; legacy_idx < NUM_HUD_GAUGES; legacy_idx++) {
2957 						if (stricmp(gauge_name, Legacy_HUD_gauges[legacy_idx].hud_gauge_text) == 0) {
2958 							found = true;
2959 							break;
2960 						}
2961 					}
2962 					if (found) {
2963 						break;
2964 					}
2965 				}
2966 
2967 				if (hud_gauge_type_lookup(gauge_name) == -1)
2968 					return SEXP_CHECK_INVALID_BUILTIN_HUD_GAUGE;
2969 
2970 				break;
2971 			}
2972 
2973 			case OPF_CUSTOM_HUD_GAUGE:
2974 				if (type2 != SEXP_ATOM_STRING) {
2975 					return SEXP_CHECK_TYPE_MISMATCH;
2976 				}
2977 
2978 				if (hud_get_gauge(CTEXT(node)) == nullptr)
2979 					return SEXP_CHECK_INVALID_CUSTOM_HUD_GAUGE;
2980 
2981 				break;
2982 
2983 			case OPF_SOUND_ENVIRONMENT_OPTION:
2984 				if (type2 != SEXP_ATOM_STRING) {
2985 					return SEXP_CHECK_TYPE_MISMATCH;
2986 				}
2987 
2988 				if (sexp_sound_environment_option_lookup(CTEXT(node)) < 0) {
2989 					return SEXP_CHECK_INVALID_SOUND_ENVIRONMENT_OPTION;
2990 				}
2991 				break;
2992 
2993 			case OPF_EXPLOSION_OPTION:
2994 				if (type2 != SEXP_ATOM_STRING) {
2995 					return SEXP_CHECK_TYPE_MISMATCH;
2996 				}
2997 
2998 				if (sexp_explosion_option_lookup(CTEXT(node)) < 0) {
2999 					return SEXP_CHECK_INVALID_EXPLOSION_OPTION;
3000 				}
3001 				break;
3002 
3003 			case OPF_KEYPRESS:
3004 				if (type2 != SEXP_ATOM_STRING)
3005 					return SEXP_CHECK_TYPE_MISMATCH;
3006 
3007 				break;
3008 
3009 			case OPF_CARGO:
3010 			case OPF_STRING:
3011 			case OPF_MESSAGE_OR_STRING:
3012 				if (type2 != SEXP_ATOM_STRING)
3013 					return SEXP_CHECK_TYPE_MISMATCH;
3014 				break;
3015 
3016 			case OPF_SKILL_LEVEL:
3017 				if ( type2 != SEXP_ATOM_STRING )
3018 					return SEXP_CHECK_TYPE_MISMATCH;
3019 
3020 				for (i = 0; i < NUM_SKILL_LEVELS; i++) {
3021 					if ( !stricmp(CTEXT(node), Skill_level_names(i, 0)) )
3022 						break;
3023 				}
3024 				if ( i == NUM_SKILL_LEVELS )
3025 					return SEXP_CHECK_INVALID_SKILL_LEVEL;
3026 				break;
3027 
3028 			case OPF_MEDAL_NAME:
3029 				if ( type2 != SEXP_ATOM_STRING)
3030 					return SEXP_CHECK_TYPE_MISMATCH;
3031 
3032 				for (i = 0; i < Num_medals; i++) {
3033 					if ( !stricmp(CTEXT(node), Medals[i].name) )
3034 						break;
3035 				}
3036 
3037 				if ( i == Num_medals )
3038 					return SEXP_CHECK_INVALID_MEDAL_NAME;
3039 				break;
3040 
3041 			case OPF_HUGE_WEAPON:
3042 			case OPF_WEAPON_NAME:
3043 				if ( type2 != SEXP_ATOM_STRING )
3044 					return SEXP_CHECK_TYPE_MISMATCH;
3045 
3046 				i = weapon_info_lookup(CTEXT(node));
3047 
3048 				if ( i < 0 )
3049 					return SEXP_CHECK_INVALID_WEAPON_NAME;
3050 
3051 				// we need to be sure that for huge weapons, the WIF_HUGE flag is set
3052 				if ( type == OPF_HUGE_WEAPON ) {
3053 					if ( !(Weapon_info[i].wi_flags[Weapon::Info_Flags::Huge]) )
3054 						return SEXP_CHECK_INVALID_WEAPON_NAME;
3055 				}
3056 
3057 				break;
3058 
3059 			// Goober5000
3060 			case OPF_INTEL_NAME:
3061 				if ( type2 != SEXP_ATOM_STRING )
3062 					return SEXP_CHECK_TYPE_MISMATCH;
3063 
3064 				for ( i = 0; i < intel_info_size(); i++ ) {
3065 					if ( !stricmp(CTEXT(node), Intel_info[i].name) )
3066 						break;
3067 				}
3068 
3069 				if ( i == intel_info_size() )
3070 					return SEXP_CHECK_INVALID_INTEL_NAME;
3071 
3072 				break;
3073 
3074 			case OPF_TURRET_TARGET_ORDER:
3075 				if ( type2 != SEXP_ATOM_STRING )
3076 					return SEXP_CHECK_TYPE_MISMATCH;
3077 
3078 				for (i = 0; i < NUM_TURRET_ORDER_TYPES; i++ ) {
3079 					if ( !stricmp(CTEXT(node), Turret_target_order_names[i]) )
3080 						break;
3081 				}
3082 
3083 				if ( i == NUM_TURRET_ORDER_TYPES )
3084 					return SEXP_CHECK_INVALID_TURRET_TARGET_ORDER;
3085 
3086 				break;
3087 
3088 			case OPF_ARMOR_TYPE:
3089 				if ( type2 != SEXP_ATOM_STRING )
3090 					return SEXP_CHECK_TYPE_MISMATCH;
3091 
3092 				if (!stricmp(CTEXT(node), SEXP_NONE_STRING))
3093 					break;
3094 
3095 				for (st = 0; st < Armor_types.size(); st++ ) {
3096 					if ( !stricmp(CTEXT(node), Armor_types[st].GetNamePtr()) )
3097 						break;
3098 				}
3099 
3100 				if ( st == Armor_types.size() )
3101 					return SEXP_CHECK_INVALID_ARMOR_TYPE;
3102 
3103 				break;
3104 
3105 			case OPF_DAMAGE_TYPE:
3106 				if ( type2 != SEXP_ATOM_STRING )
3107 					return SEXP_CHECK_TYPE_MISMATCH;
3108 
3109 				if (!stricmp(CTEXT(node), SEXP_NONE_STRING))
3110 					break;
3111 
3112 				for (st = 0; st < Damage_types.size(); st++ ) {
3113 					if ( !stricmp(CTEXT(node), Damage_types[st].name) )
3114 						break;
3115 				}
3116 
3117 				if ( st == Damage_types.size() )
3118 					return SEXP_CHECK_INVALID_DAMAGE_TYPE;
3119 
3120 				break;
3121 
3122 			case OPF_ANIMATION_TYPE:
3123 				if ( type2 != SEXP_ATOM_STRING )
3124 					return SEXP_CHECK_TYPE_MISMATCH;
3125 
3126 				if (model_anim_match_type(CTEXT(node)) == AnimationTriggerType::None )
3127 					return SEXP_CHECK_INVALID_ANIMATION_TYPE;
3128 
3129 				break;
3130 
3131 			case OPF_TARGET_PRIORITIES:
3132 				if ( type2 != SEXP_ATOM_STRING )
3133 					return SEXP_CHECK_TYPE_MISMATCH;
3134 
3135 				for(st = 0; st < Ai_tp_list.size(); st++) {
3136 					if ( !stricmp(CTEXT(node), Ai_tp_list[st].name) )
3137 						break;
3138 				}
3139 
3140 				if ( st == Ai_tp_list.size() )
3141 					return SEXP_CHECK_INVALID_TARGET_PRIORITIES;
3142 
3143 				break;
3144 
3145 			case OPF_SHIP_CLASS_NAME:
3146 				if ( type2 != SEXP_ATOM_STRING )
3147 					return SEXP_CHECK_TYPE_MISMATCH;
3148 
3149 				if (ship_info_lookup(CTEXT(node)) < 0)
3150 					return SEXP_CHECK_INVALID_SHIP_CLASS_NAME;
3151 
3152 				break;
3153 
3154 			case OPF_SKYBOX_MODEL_NAME:
3155 				if ( type2 != SEXP_ATOM_STRING )
3156 					return SEXP_CHECK_TYPE_MISMATCH;
3157 
3158 				if ( stricmp(CTEXT(node), NOX("default")) != 0 && !strstr(CTEXT(node), NOX(".pof")) )
3159 					return SEXP_CHECK_INVALID_SKYBOX_NAME;
3160 
3161 				break;
3162 
3163 			case OPF_SKYBOX_FLAGS:
3164 				if ( type2 != SEXP_ATOM_STRING )
3165 					return SEXP_CHECK_TYPE_MISMATCH;
3166 
3167 				for ( i = 0; i < Num_skybox_flags; ++i ) {
3168 					if ( !stricmp( CTEXT(node), Skybox_flags[i]) )
3169 						break;
3170 				}
3171 
3172 				// if we reached the end of the list, then the flag is invalid
3173 				if ( i == Num_skybox_flags )
3174 					return SEXP_CHECK_INVALID_SKYBOX_FLAG;
3175 
3176 				break;
3177 
3178 			case OPF_JUMP_NODE_NAME:
3179 				if ( type2 != SEXP_ATOM_STRING )
3180 					return SEXP_CHECK_TYPE_MISMATCH;
3181 
3182 				if (jumpnode_get_by_name(CTEXT(node)) == nullptr)
3183 					return SEXP_CHECK_INVALID_JUMP_NODE;
3184 
3185 				break;
3186 
3187 
3188 			case OPF_VARIABLE_NAME:
3189 				var_index = sexp_get_variable_index(node);
3190 				if ( var_index  == -1) {
3191 					return SEXP_CHECK_INVALID_VARIABLE;
3192 				}
3193 
3194 				switch (Operators[op].value)
3195 				{
3196 					// some SEXPs demand a number variable
3197 					case OP_ADD_BACKGROUND_BITMAP:
3198 					case OP_ADD_SUN_BITMAP:
3199 					case OP_PLAY_SOUND_FROM_FILE:
3200 					case OP_PAUSE_SOUND_FROM_FILE:
3201 					case OP_CLOSE_SOUND_FROM_FILE:
3202 						if (!(Sexp_variables[var_index].type & SEXP_VARIABLE_NUMBER))
3203 							return SEXP_CHECK_INVALID_VARIABLE_TYPE;
3204 						break;
3205 
3206 					// some demand a string variable
3207 					case OP_STRING_CONCATENATE:
3208 					case OP_STRING_CONCATENATE_BLOCK:
3209 					case OP_INT_TO_STRING:
3210 					case OP_STRING_GET_SUBSTRING:
3211 					case OP_STRING_SET_SUBSTRING:
3212 					case OP_SCRIPT_EVAL_STRING:
3213 						if (!(Sexp_variables[var_index].type & SEXP_VARIABLE_STRING))
3214 							return SEXP_CHECK_INVALID_VARIABLE_TYPE;
3215 						break;
3216 
3217 					default:
3218 						break;
3219 				}
3220 
3221 				// otherwise anything goes
3222 				break;
3223 
3224 			case OPF_AMBIGUOUS:
3225 				// type checking for modify-variable
3226 				// string or number -- anything goes
3227 				break;
3228 
3229 			case OPF_BACKGROUND_BITMAP:
3230 			case OPF_SUN_BITMAP:
3231 			case OPF_NEBULA_STORM_TYPE:
3232 			case OPF_NEBULA_POOF:
3233 			case OPF_NEBULA_PATTERN:
3234 			case OPF_POST_EFFECT:
3235 				if (type2 != SEXP_ATOM_STRING) {
3236 					return SEXP_CHECK_TYPE_MISMATCH;
3237 				}
3238 				break;
3239 
3240 			case OPF_HUD_ELEMENT:
3241 				if (type2 != SEXP_ATOM_STRING) {
3242 					return SEXP_CHECK_TYPE_MISMATCH;
3243 				} else {
3244 					auto gauge = CTEXT(node);
3245 					if ( strcmp(SEXP_HUD_GAUGE_WARPOUT, gauge) == 0 ) {
3246 						break;
3247 					}
3248 				}
3249 				return SEXP_CHECK_INVALID_HUD_ELEMENT;
3250 
3251 			case OPF_WEAPON_BANK_NUMBER:
3252 				if (type2 != SEXP_ATOM_STRING) {
3253 					return SEXP_CHECK_TYPE_MISMATCH;
3254 				}
3255 
3256 				if (!stricmp(CTEXT(node), SEXP_ALL_BANKS_STRING)) {
3257 					break;
3258 				}
3259 
3260 				// if we haven't specified all banks we need to check the number of the bank is legal
3261 				else {
3262 					int num_banks = atoi(CTEXT(node));
3263 					if ((num_banks >= MAX_SHIP_PRIMARY_BANKS) && (num_banks >= MAX_SHIP_SECONDARY_BANKS)) { // NOLINT
3264 						return SEXP_CHECK_NUM_RANGE_INVALID;
3265 					}
3266 				}
3267 				break;
3268 
3269 			case OPF_SHIP_EFFECT:
3270 				if (type2 != SEXP_ATOM_STRING) {
3271 					return SEXP_CHECK_TYPE_MISMATCH;
3272 				}
3273 
3274 				if (get_effect_from_name(CTEXT(node)) == -1 ) {
3275 					return SEXP_CHECK_INVALID_SHIP_EFFECT;
3276 				}
3277 				break;
3278 
3279 			case OPF_GAME_SND:
3280 				if (type2 == SEXP_ATOM_NUMBER)
3281 				{
3282 					if (!gamesnd_get_by_tbl_index(atoi(CTEXT(node))).isValid())
3283 					{
3284 						return SEXP_CHECK_NUM_RANGE_INVALID;
3285 					}
3286 				}
3287 				else if (type2 == SEXP_ATOM_STRING)
3288 				{
3289 					if (stricmp(CTEXT(node), SEXP_NONE_STRING) != 0 && !gamesnd_get_by_name(CTEXT(node)).isValid())
3290 					{
3291 						return SEXP_CHECK_INVALID_GAME_SND;
3292 					}
3293 				}
3294 				break;
3295 
3296 			case OPF_FIREBALL:
3297 				if (type2 == SEXP_ATOM_NUMBER || can_construe_as_integer(CTEXT(node)))
3298 				{
3299 					int num = atoi(CTEXT(node));
3300 					if (num < 0 || num >= Num_fireball_types)
3301 					{
3302 						return SEXP_CHECK_NUM_RANGE_INVALID;
3303 					}
3304 				}
3305 				else if (type2 == SEXP_ATOM_STRING)
3306 				{
3307 					if (fireball_info_lookup(CTEXT(node)) < 0)
3308 					{
3309 						return SEXP_CHECK_INVALID_FIREBALL;
3310 					}
3311 				}
3312 				break;
3313 
3314 			case OPF_SPECIES:
3315 				if (type2 != SEXP_ATOM_STRING) {
3316 					return SEXP_CHECK_TYPE_MISMATCH;
3317 				}
3318 
3319 				if (species_info_lookup(CTEXT(node)) < 0 ) {
3320 					return SEXP_CHECK_INVALID_SPECIES;
3321 				}
3322 				break;
3323 
3324 			case OPF_LANGUAGE:
3325 				if (type2 != SEXP_ATOM_STRING) {
3326 					return SEXP_CHECK_TYPE_MISMATCH;
3327 				}
3328 
3329 				// that's all we do, since there may be a language the game doesn't know about
3330 				break;
3331 
3332 			default:
3333 				Error(LOCATION, "Unhandled argument format");
3334 		}
3335 
3336 		node = Sexp_nodes[node].rest;
3337 		argnum++;
3338 	}
3339 
3340 	return 0;
3341 }
3342 
3343 // Goober5000
get_unformatted_sexp_variable_name(char * unformatted,const char * formatted_pre)3344 void get_unformatted_sexp_variable_name(char *unformatted, const char *formatted_pre)
3345 {
3346 	const char *formatted;
3347 
3348 	// Goober5000 - trim @ if needed
3349 	if (formatted_pre[0] == SEXP_VARIABLE_CHAR)
3350 		formatted = formatted_pre+1;
3351 	else
3352 		formatted = formatted_pre;
3353 
3354 	// get variable name (up to '['
3355 	auto end_index = strcspn(formatted, "[");
3356 	Assert( (end_index != 0) && (end_index < TOKEN_LENGTH-1) );
3357 	strncpy(unformatted, formatted, end_index);
3358 	unformatted[end_index] = '\0';
3359 }
3360 
3361 /**
3362  * Get text to stuff into Sexp_node in case of variable
3363  *
3364  * If Fred_running - stuff Sexp_variables[].variable_name
3365  * otherwise - stuff index into Sexp_variables array.
3366  */
get_sexp_text_for_variable(char * text,const char * token)3367 void get_sexp_text_for_variable(char *text, const char *token)
3368 {
3369 	int sexp_var_index;
3370 
3371 	get_unformatted_sexp_variable_name(text, token);
3372 
3373 	if ( !Fred_running ) {
3374 		// freespace - get index into Sexp_variables array
3375 		sexp_var_index = get_index_sexp_variable_name(text);
3376 		if (sexp_var_index == -1) {
3377 			Error(LOCATION, "Invalid variable name [%s]!", text);
3378 		}
3379 		sprintf(text, "%d", sexp_var_index);
3380 	}
3381 }
3382 
3383 // Goober5000
do_preload_for_arguments(void (* preloader)(const char *),int arg_node,int arg_handler_node)3384 void do_preload_for_arguments(void (*preloader)(const char *), int arg_node, int arg_handler_node)
3385 {
3386 	// we have a special argument
3387 	if (Sexp_nodes[arg_node].flags & SNF_SPECIAL_ARG_IN_NODE)
3388 	{
3389 		int n;
3390 
3391 		// we might not be handling it, either because this is not a *_of operator
3392 		// or because we've disabled preloading for special arguments
3393 		if (arg_handler_node < 0)
3394 			return;
3395 
3396 		// preload for each argument
3397 		for (n = CDR(arg_handler_node); n != -1; n = CDR(n))
3398 		{
3399 			preloader(CTEXT(n));
3400 		}
3401 	}
3402 	// we don't
3403 	else
3404 	{
3405 		// preload for just the one argument
3406 		preloader(CTEXT(arg_node));
3407 	}
3408 }
3409 
3410 // Goober5000
preload_change_ship_class(const char * text)3411 void preload_change_ship_class(const char *text)
3412 {
3413 	int idx;
3414 	ship_info *sip;
3415 
3416 	idx = ship_info_lookup(text);
3417 	if (idx < 0)
3418 		return;
3419 
3420 	// preload the model, just in case there is no other ship of this class in the mission
3421 	// (this eliminates the slight pause during a mission when changing to a previously unloaded model)
3422 	sip = &Ship_info[idx];
3423 	sip->model_num = model_load(sip->pof_file, sip->n_subsystems, &sip->subsystems[0]);
3424 
3425 	if (sip->model_num >= 0)
3426 		model_page_in_textures(sip->model_num, idx);
3427 }
3428 
3429 // Goober5000
preload_turret_change_weapon(const char * text)3430 void preload_turret_change_weapon(const char *text)
3431 {
3432 	int idx;
3433 
3434 	idx = weapon_info_lookup(text);
3435 	if (idx < 0)
3436 		return;
3437 
3438 	weapon_mark_as_used(idx);
3439 }
3440 
preload_texture(const char * text)3441 void preload_texture(const char* text)
3442 {
3443 	int texture = bm_load(text);
3444 
3445 	if (texture < 0) {
3446 		Warning(LOCATION, "Could not preload texture %s", text);
3447 		return;
3448 	}
3449 
3450 	bm_page_in_texture(texture);
3451 }
3452 
3453 // Goober5000
3454 // we don't use the do_preload function because the preloader needs to access two nodes at a time
3455 // also we're not using CTEXT or eval_num here because XSTR should really be constant, and
3456 // also because we can't really run sexp stuff in a preloader
localize_sexp(int text_node,int id_node)3457 void localize_sexp(int text_node, int id_node)
3458 {
3459 	// we need both a name and an id
3460 	if (text_node < 0 || id_node < 0)
3461 		return;
3462 
3463 	int id = atoi(Sexp_nodes[id_node].text);
3464 	Assert(id < 10000000);
3465 	SCP_string xstr;
3466 	sprintf(xstr, "XSTR(\"%s\", %d)", Sexp_nodes[text_node].text, id);
3467 
3468 	memset(Sexp_nodes[text_node].text, 0, TOKEN_LENGTH * sizeof(char));
3469 	lcl_ext_localize(xstr.c_str(), Sexp_nodes[text_node].text, TOKEN_LENGTH - 1);
3470 }
3471 
3472 /**
3473  * Returns the first sexp index of data this function allocates. (start of this sexp)
3474  *
3475  * NOTE: On entry into this function, Mp points to the first character past the opening parenthesis.
3476  */
get_sexp()3477 int get_sexp()
3478 {
3479 	int start, node, last, op, count;
3480 	char token[TOKEN_LENGTH];
3481 	char variable_text[TOKEN_LENGTH];
3482 
3483 	Assert(*(Mp-1) == '(');
3484 
3485 	// start - the node allocated in first instance of function
3486 	// node - the node allocated in current instance of function
3487 	// count - number of nodes allocated this instance of function [do we set last.rest or .first]
3488 	// variable - whether string or number is a variable referencing Sexp_variables
3489 
3490 	// initialization
3491 	start = last = -1;
3492 	count = 0;
3493 
3494 	ignore_white_space();
3495 	while (*Mp != ')') {
3496 		// end of string or end of file
3497 		if (*Mp == '\0') {
3498 			Error(LOCATION, "Unexpected end of sexp!");
3499 			return -1;
3500 		}
3501 
3502 		// Sexp list
3503 		if (*Mp == '(') {
3504 			Mp++;
3505 			node = alloc_sexp("", SEXP_LIST, SEXP_ATOM_LIST, get_sexp(), -1);
3506 		}
3507 
3508 		// Sexp string
3509 		else if (*Mp == '\"') {
3510 			auto len = strcspn(Mp + 1, "\"");
3511 			// was closing quote not found?
3512 			if (*(Mp + 1 + len) != '\"') {
3513 				Error(LOCATION, "Unexpected end of quoted string embedded in sexp!");
3514 				return -1;
3515 			}
3516 
3517 			// check if string variable
3518 			if ( *(Mp + 1) == SEXP_VARIABLE_CHAR ) {
3519 				char variable_token[2*TOKEN_LENGTH+2];	// variable_token[contents_token]
3520 
3521 				// reduce length by 1 for end \"
3522 				auto length = len - 1;
3523 				if (length >= 2*TOKEN_LENGTH+2) {
3524 					Error(LOCATION, "Variable token %s is too long. Needs to be %d characters or shorter.", Mp, 2*TOKEN_LENGTH+2 - 1);
3525 					return -1;
3526 				}
3527 
3528 				// start copying after skipping 1st char (i.e. variable char)
3529 				strncpy(variable_token, Mp + 2, length);
3530 				variable_token[length] = 0;
3531 
3532 				get_sexp_text_for_variable(variable_text, variable_token);
3533 				node = alloc_sexp(variable_text, (SEXP_ATOM | SEXP_FLAG_VARIABLE), SEXP_ATOM_STRING, -1, -1);
3534 			} else {
3535 				// token is too long?
3536 				if (len >= TOKEN_LENGTH) {
3537 					Error(LOCATION, "Token %s is too long. Needs to be %d characters or shorter.", Mp, TOKEN_LENGTH - 1);
3538 					return -1;
3539 				}
3540 
3541 				strncpy(token, Mp + 1, len);
3542 				token[len] = 0;
3543 				node = alloc_sexp(token, SEXP_ATOM, SEXP_ATOM_STRING, -1, -1);
3544 			}
3545 
3546 			// bump past closing \" by 1 char
3547 			Mp += (len + 2);
3548 
3549 		}
3550 
3551 		// Sexp operator or number
3552 		else {
3553 			int len = 0;
3554 			bool variable = false;
3555 			while (*Mp != ')' && !is_white_space(*Mp)) {
3556 				// numeric variable?
3557 				if ( (len == 0) && (*Mp == SEXP_VARIABLE_CHAR) ) {
3558 					variable = true;
3559 					Mp++;
3560 					continue;
3561 				}
3562 
3563 				// end of string or end of file?
3564 				if (*Mp == '\0') {
3565 					Error(LOCATION, "Unexpected end of sexp!");
3566 					return -1;
3567 				}
3568 
3569 				// token is too long?
3570 				if (len >= TOKEN_LENGTH - 1) {
3571 					token[TOKEN_LENGTH - 1] = '\0';
3572 					Error(LOCATION, "Token %s is too long. Needs to be %d characters or shorter.", token, TOKEN_LENGTH - 1);
3573 					return -1;
3574 				}
3575 
3576 				// build the token
3577 				token[len++] = *Mp++;
3578 			}
3579 			token[len] = 0;
3580 
3581 			// maybe replace deprecated names
3582 			if (!stricmp(token, "set-ship-position"))
3583 				strcpy_s(token, "set-object-position");
3584 			else if (!stricmp(token, "set-ship-facing"))
3585 				strcpy_s(token, "set-object-facing");
3586 			else if (!stricmp(token, "set-ship-facing-object"))
3587 				strcpy_s(token, "set-object-facing-object");
3588 			else if (!stricmp(token, "ai-chase-any-except"))
3589 				strcpy_s(token, "ai-chase-any");
3590 			else if (!stricmp(token, "change-ship-model"))
3591 				strcpy_s(token, "change-ship-class");
3592 			else if (!stricmp(token, "radar-set-max-range"))
3593 				strcpy_s(token, "hud-set-max-targeting-range");
3594 			else if (!stricmp(token, "ship-subsys-vanished"))
3595 				strcpy_s(token, "ship-subsys-vanish");
3596 			else if (!stricmp(token, "directive-is-variable"))
3597 				strcpy_s(token, "directive-value");
3598 			else if (!stricmp(token, "variable-array-get"))
3599 				strcpy_s(token, "get-variable-by-index");
3600 			else if (!stricmp(token, "variable-array-set"))
3601 				strcpy_s(token, "set-variable-by-index");
3602 			else if (!stricmp(token, "distance-ship-subsystem"))
3603 				strcpy_s(token, "distance-center-to-subsystem");
3604 			else if (!stricmp(token, "remove-weapons"))
3605 				strcpy_s(token, "clear-weapons");
3606 			else if (!stricmp(token, "hud-set-retail-gauge-active"))
3607 				strcpy_s(token, "hud-set-builtin-gauge-active");
3608 
3609 			op = get_operator_index(token);
3610 			if (op >= 0) {
3611 				node = alloc_sexp(token, SEXP_ATOM, SEXP_ATOM_OPERATOR, -1, -1);
3612 			} else {
3613 				if ( variable ) {
3614 					// convert token text for variable
3615 					get_sexp_text_for_variable(variable_text, token);
3616 
3617 					node = alloc_sexp(variable_text, (SEXP_ATOM | SEXP_FLAG_VARIABLE), SEXP_ATOM_NUMBER, -1, -1);
3618 				} else {
3619 					node = alloc_sexp(token, SEXP_ATOM, SEXP_ATOM_NUMBER, -1, -1);
3620 				}
3621 			}
3622 		}
3623 
3624 		// update links
3625 		if (count++) {
3626 			Assert(last != -1);
3627 			Sexp_nodes[last].rest = node;
3628 		} else {
3629 			start = node;
3630 		}
3631 
3632 		Assert(node != -1);  // ran out of nodes.  Time to raise the MAX!
3633 		last = node;
3634 		ignore_white_space();
3635 	}
3636 
3637 	Mp++;  // skip past the ')'
3638 
3639 
3640 	// Goober5000 - backwards compatibility for removed ai-chase-any-except
3641 	if (get_operator_const(start) == OP_AI_CHASE_ANY)
3642 	{
3643 		// if there is more than one argument, free the extras
3644 		int n = CDR(CDR(start));
3645 		if (n >= 0)
3646 		{
3647 			// free the entire rest of the argument list
3648 			free_sexp2(n);
3649 		}
3650 	}
3651 
3652 	// Goober5000 - preload stuff for certain sexps
3653 	if (!Fred_running)
3654 	{
3655 		int n, parent, arg_handler = -1;
3656 
3657 		// see if we're using special arguments
3658 		parent = find_parent_operator(start);
3659 		if (parent >= 0 && is_blank_argument_op(get_operator_const(parent)))
3660 		{
3661 			// get the first op of the parent, which should be a *_of operator
3662 			arg_handler = CADR(parent);
3663 			if (arg_handler >= 0 && !is_blank_of_op(get_operator_const(arg_handler)))
3664 				arg_handler = -1;
3665 		}
3666 
3667 		// DISABLE ARGUMENT PRELOADING FOR NOW
3668 		// see Mantis #925 for discussion
3669 		// Also, the preloader will have to be moved to a different function (after the parsing is finished)
3670 		// in order to work properly with special arguments.  The current node is an orphan until get_sexp
3671 		// completes, at which time it will be linked into the sexp node list; this means that it is
3672 		// impossible to get the parent node.
3673 		arg_handler = -1;
3674 
3675 		// preload according to sexp
3676 		op = get_operator_const(start);
3677 		switch (op)
3678 		{
3679 			case OP_CHANGE_SHIP_CLASS:
3680 				// ship class is argument #1
3681 				n = CDR(start);
3682 				do_preload_for_arguments(preload_change_ship_class, n, arg_handler);
3683 				break;
3684 
3685 			case OP_SHIP_CREATE:
3686 				// ship class is argument #2
3687 				n = CDDR(start);
3688 				// page in ship classes of dynamically created ships
3689 				// preload_change_ship_class doesn't require a class change, so we can use that here -zookeeper
3690 				do_preload_for_arguments(preload_change_ship_class, n, arg_handler);
3691 				break;
3692 
3693 			case OP_SET_SPECIAL_WARPOUT_NAME:
3694 				// set flag for taylor
3695 				Knossos_warp_ani_used = 1;
3696 				break;
3697 
3698 			case OP_MISSION_SET_NEBULA:
3699 				// set flag for WMC
3700 				Nebula_sexp_used = true;
3701 				break;
3702 
3703 			case OP_WARP_EFFECT:
3704 				// type of warp is argument #11
3705 				n = CDDDDDR(start);
3706 				n = CDDDDDR(n);
3707 				n = CDR(n);
3708 
3709 				// set flag for taylor
3710 				if (CAR(n) != -1 || Sexp_nodes[n].flags & SNF_SPECIAL_ARG_IN_NODE)		// if it's evaluating a sexp or a special argument
3711 					Knossos_warp_ani_used = 1;												// set flag just in case
3712 				else if (atoi(CTEXT(n)) != 0)											// if it's not the default 0
3713 					Knossos_warp_ani_used = 1;												// set flag just in case
3714 				break;
3715 
3716 			case OP_SET_SKYBOX_MODEL:
3717 				// model is argument #1
3718 				n = CDR(start);
3719 				do_preload_for_arguments(sexp_set_skybox_model_preload, n, arg_handler);
3720 				break;
3721 
3722 			case OP_TURRET_CHANGE_WEAPON:
3723 				// weapon to change to is arg #3
3724 				n = CDDDR(start);
3725 				do_preload_for_arguments(preload_turret_change_weapon, n, arg_handler);
3726 				break;
3727 
3728 			case OP_CHANGE_BACKGROUND:
3729 				n = CDR(start);
3730 				do_preload_for_arguments(stars_preload_background, n, arg_handler);
3731 				break;
3732 
3733 			case OP_ADD_SUN_BITMAP:
3734 				n = CDR(start);
3735 				do_preload_for_arguments(stars_preload_sun_bitmap, n, arg_handler);
3736 				break;
3737 
3738 			case OP_ADD_BACKGROUND_BITMAP:
3739 				n = CDR(start);
3740 				do_preload_for_arguments(stars_preload_background_bitmap, n, arg_handler);
3741 				break;
3742 
3743 			case OP_TECH_ADD_INTEL_XSTR:
3744 			case OP_TECH_REMOVE_INTEL_XSTR:
3745 				// do XSTR translation for each entry in the list
3746 				// we don't use the do_preload function because the preloader needs to access two nodes at a time
3747 				// also we're not using CTEXT or eval_num here because XSTR should really be constant, and
3748 				// also because we can't really run sexp stuff in a preloader
3749 				n = CDR(start);
3750 				while (n >= 0 && CDR(n) >= 0)
3751 				{
3752 					localize_sexp(n, CDR(n));
3753 					n = CDDR(n);
3754 				}
3755 				break;
3756 
3757 			case OP_MODIFY_VARIABLE_XSTR:
3758 				n = CDDR(start); // First parameter is the variable name so we need to get the second parameter
3759 				localize_sexp(n, CDR(n));
3760 				break;
3761 
3762 			case OP_REPLACE_TEXTURE:
3763 				//Texture name is argument 2
3764 				n = CDDR(start);
3765 				do_preload_for_arguments(preload_texture, n, arg_handler);
3766 				break;
3767 		}
3768 	}
3769 
3770 	return start;
3771 }
3772 
3773 
3774 /**
3775  * Stuffs a list of sexp variables
3776  */
stuff_sexp_variable_list()3777 int stuff_sexp_variable_list()
3778 {
3779 	int count;
3780 	char var_name[TOKEN_LENGTH];
3781 	char default_value[TOKEN_LENGTH];
3782 	char str_type[TOKEN_LENGTH];
3783 	char persistent[TOKEN_LENGTH];
3784 	char network[TOKEN_LENGTH];
3785 	int index;
3786 	int type;
3787 
3788 	count = 0;
3789 	required_string("$Variables:");
3790 	ignore_white_space();
3791 
3792 	// check for start of list
3793 	if (*Mp != '(') {
3794 		error_display(1, "Reading sexp variable list.  Found [%c].  Expecting '('.\n", *Mp);
3795 		throw parse::ParseException("Syntax error");
3796 	}
3797 
3798 	Mp++;
3799 	ignore_white_space();
3800 
3801 	while (*Mp != ')') {
3802 		Assert(count < MAX_SEXP_VARIABLES);
3803 
3804 		// get index - for debug
3805 		stuff_int(&index);
3806 		ignore_gray_space();
3807 
3808 		// get var_name
3809 		get_string(var_name);
3810 		ignore_gray_space();
3811 
3812 		// get default_value;
3813 		get_string(default_value);
3814 		ignore_gray_space();
3815 
3816 		// get type
3817 		get_string(str_type);
3818 		ignore_white_space();
3819 
3820 		// determine type
3821 		if (!stricmp(str_type, "number")) {
3822 			type = SEXP_VARIABLE_NUMBER;
3823 		} else if (!stricmp(str_type, "string")) {
3824 			type = SEXP_VARIABLE_STRING;
3825 		} else if (!stricmp(str_type, "block")) {
3826 			// Goober5000 - This looks dangerous... these flags are needed for certain things, but it
3827 			// looks like BLOCK_*_SIZE is the only thing that keeps a block from running off the end
3828 			// of its boundary.
3829 			type = SEXP_VARIABLE_BLOCK;
3830 		} else {
3831 			type = SEXP_VARIABLE_UNKNOWN;
3832 			Error(LOCATION, "SEXP variable '%s' is an unknown type!", var_name);
3833 		}
3834 
3835 		// possibly get network-variable
3836 		if (check_for_string("\"network-variable\"")) {
3837 			// eat it
3838 			get_string(network);
3839 			ignore_white_space();
3840 
3841 			// set type
3842 			type |= SEXP_VARIABLE_NETWORK;
3843 		}
3844 
3845 		// maybe this is an eternal persistent variable of some type
3846 		if (check_for_string("\"eternal\"")) {
3847 			// eat it
3848 			get_string(persistent);
3849 			ignore_white_space();
3850 
3851 			// set type
3852 			type |= SEXP_VARIABLE_SAVE_TO_PLAYER_FILE;
3853 		}
3854 
3855 		// maybe this is a persistent variable of some type
3856 		if (check_for_string("\"player-persistent\"") || check_for_string("\"save-on-mission-close\"")) {
3857 			// eat it
3858 			get_string(persistent);
3859 			ignore_white_space();
3860 
3861 			// set type
3862 			type |= SEXP_VARIABLE_SAVE_ON_MISSION_CLOSE;
3863 		} else if (check_for_string("\"campaign-persistent\"") || check_for_string("\"save-on-mission-progress\"")) {
3864 			// eat it
3865 			get_string(persistent);
3866 			ignore_white_space();
3867 
3868 			// set type
3869 			type |= SEXP_VARIABLE_SAVE_ON_MISSION_PROGRESS;
3870 		// trap error
3871 		} else if (check_for_string("\"")) {
3872 			// eat garbage
3873 			get_string(persistent);
3874 			ignore_white_space();
3875 
3876 			// notify of error
3877 			Error(LOCATION, "Error parsing sexp variables - unknown persistence type encountered.  You can continue from here without trouble.");
3878 		}
3879 
3880 		// check if variable name already exists
3881 		if ( (type & SEXP_VARIABLE_NUMBER) || (type & SEXP_VARIABLE_STRING) ) {
3882 			Assert(get_index_sexp_variable_name(var_name) == -1);
3883 		}
3884 
3885 		if ( type & SEXP_VARIABLE_BLOCK ) {
3886 			add_block_variable(default_value, var_name, type, index);
3887 		}
3888 		else {
3889 			count++;
3890 			sexp_add_variable(default_value, var_name, type, index);
3891 		}
3892 	}
3893 
3894 	Mp++;
3895 
3896 	return count;
3897 }
3898 
has_special_explosion_block_index(ship * shipp,int * index)3899 bool has_special_explosion_block_index(ship *shipp, int *index)
3900 {
3901 	int current_index;
3902 
3903 	for ( current_index = (MAX_SEXP_VARIABLES - BLOCK_EXP_SIZE) ; current_index >= (MAX_SEXP_VARIABLES - (BLOCK_EXP_SIZE * Num_special_expl_blocks)) ; current_index = (current_index - BLOCK_EXP_SIZE) ) {
3904 		if (
3905 			(atoi(Block_variables[current_index+INNER_RAD].text) == shipp->special_exp_inner) &&
3906 			(atoi(Block_variables[current_index+OUTER_RAD].text) == shipp->special_exp_outer) &&
3907 			(atoi(Block_variables[current_index+DAMAGE].text) == shipp->special_exp_damage) &&
3908 			(atoi(Block_variables[current_index+BLAST].text) == shipp->special_exp_blast) &&
3909 			(atoi(Block_variables[current_index+PROPAGATE].text) == (shipp->use_shockwave ? 1:0) ) &&
3910 			(atoi(Block_variables[current_index+SHOCK_SPEED].text) == shipp->special_exp_shockwave_speed)
3911 			) {
3912 				*index = current_index;
3913 				return true;
3914 		}
3915 	}
3916 
3917 	// if we got here, this ship's special explosions aren't represented in the Block_variables array
3918 	*index = current_index;
3919 	return false;
3920 }
3921 
generate_special_explosion_block_variables()3922 bool generate_special_explosion_block_variables()
3923 {
3924 	ship *shipp;
3925 	int current_index;
3926 	bool already_added = false;
3927 	int num_sexp_variables;
3928 	int i;
3929 
3930 	// since we're (re)generating the block variable index, we don't start off with any block variables
3931 	Num_special_expl_blocks = 0;
3932 
3933 	// get the number of sexp_variables we currently have. We must not try to add a block variable with an index below this.
3934 	num_sexp_variables = sexp_variable_count();
3935 
3936 	for ( auto sop = GET_FIRST(&Ship_obj_list); sop != END_OF_LIST(&Ship_obj_list); sop = GET_NEXT(sop) ) {
3937 		shipp=&Ships[Objects[sop->objnum].instance];
3938 
3939 		if (!(shipp->use_special_explosion)) {
3940 			continue;
3941 		}
3942 
3943 		already_added = has_special_explosion_block_index(shipp, &current_index);
3944 
3945 		// if we can't add an index for this add this ship to the list of those which failed
3946 		if (current_index < num_sexp_variables) {
3947 			// fail list code goes here
3948 			continue;
3949 		}
3950 
3951 		//if we haven't added this entry already, do so
3952 		if (!already_added) {
3953 			sprintf(Block_variables[current_index+INNER_RAD].text, "%d", shipp->special_exp_inner);
3954 			sprintf(Block_variables[current_index+OUTER_RAD].text, "%d", shipp->special_exp_outer);
3955 			sprintf(Block_variables[current_index+DAMAGE].text, "%d", shipp->special_exp_damage);
3956 			sprintf(Block_variables[current_index+BLAST].text, "%d", shipp->special_exp_blast);
3957 			sprintf(Block_variables[current_index+PROPAGATE].text, "%d", (shipp->use_shockwave ? 1:0) );
3958 			sprintf(Block_variables[current_index+SHOCK_SPEED].text, "%d", shipp->special_exp_shockwave_speed);
3959 
3960 			// add the names
3961 			for (i = current_index; i < (current_index + BLOCK_EXP_SIZE); i++ ) {
3962 				sprintf(Block_variables[i].variable_name, "%s", shipp->ship_name);
3963 			}
3964 
3965 			Num_special_expl_blocks++;
3966 		}
3967 	}
3968 
3969 	return true;
3970 }
3971 
num_block_variables()3972 int num_block_variables()
3973 {
3974 	return Num_special_expl_blocks * BLOCK_EXP_SIZE;
3975 }
3976 
3977 /**
3978  * Stuff this particular SEXP node (just the node, not the tree) into a string representation
3979  */
stuff_sexp_text_string(SCP_string & dest,int node,int mode)3980 void stuff_sexp_text_string(SCP_string &dest, int node, int mode)
3981 {
3982 	Assert((node >= 0) && (node < Num_sexp_nodes));
3983 
3984 	if (Sexp_nodes[node].type & SEXP_FLAG_VARIABLE)
3985 	{
3986 		int sexp_variables_index = get_index_sexp_variable_name(Sexp_nodes[node].text);
3987 		// during the last pass through error-reporting mode, sexp variables have already been transcoded to their indexes
3988 		if (mode == SEXP_ERROR_CHECK_MODE && sexp_variables_index < 0)
3989 		{
3990 			if (can_construe_as_integer(Sexp_nodes[node].text))
3991 				sexp_variables_index = atoi(Sexp_nodes[node].text);
3992 		}
3993 		Assertion(sexp_variables_index != -1, "Couldn't find variable: %s\n", Sexp_nodes[node].text);
3994 		Assert((Sexp_variables[sexp_variables_index].type & SEXP_VARIABLE_NUMBER) || (Sexp_variables[sexp_variables_index].type & SEXP_VARIABLE_STRING));
3995 
3996 		// Error check - can be Fred or FreeSpace
3997 		if (mode == SEXP_ERROR_CHECK_MODE)
3998 		{
3999 			if (Fred_running)
4000 				sprintf(dest, "@%s[%s] ", Sexp_nodes[node].text, Sexp_variables[sexp_variables_index].text);
4001 			else
4002 				sprintf(dest, "@%s[%s] ", Sexp_variables[sexp_variables_index].variable_name, Sexp_variables[sexp_variables_index].text);
4003 		}
4004 		else
4005 		{
4006 			// number
4007 			if (Sexp_nodes[node].subtype == SEXP_ATOM_NUMBER)
4008 			{
4009 				Assert(Sexp_variables[sexp_variables_index].type & SEXP_VARIABLE_NUMBER);
4010 
4011 				// Save as string - only Fred
4012 				Assert(mode == SEXP_SAVE_MODE);
4013 				sprintf(dest, "@%s[%s] ", Sexp_nodes[node].text, Sexp_variables[sexp_variables_index].text);
4014 			}
4015 			// string
4016 			else
4017 			{
4018 				Assert(Sexp_nodes[node].subtype == SEXP_ATOM_STRING);
4019 				Assert(Sexp_variables[sexp_variables_index].type & SEXP_VARIABLE_STRING);
4020 
4021 				// Save as string - only Fred
4022 				Assert(mode == SEXP_SAVE_MODE);
4023 				sprintf(dest, "\"@%s[%s]\" ", Sexp_nodes[node].text, Sexp_variables[sexp_variables_index].text);
4024 			}
4025 		}
4026 	}
4027 	// not a variable
4028 	else
4029 	{
4030 		const char *ctext_string = CTEXT(node);
4031 
4032 		// strings are enclosed in quotes
4033 		if (Sexp_nodes[node].subtype == SEXP_ATOM_STRING)
4034 		{
4035 			sprintf(dest, "\"%s\" ", ctext_string);
4036 		}
4037 		// numbers and operators are printed as-is
4038 		else
4039 		{
4040 			// do some sanity checking based on Github issue #2314
4041 			if (Sexp_nodes[node].subtype == SEXP_ATOM_NUMBER)
4042 			{
4043 				// if (for whatever reason) we have an empty string or an invalid number, print 0
4044 				// do not do this trick in error mode, because we want to know what the text actually is
4045 				if ((mode != SEXP_ERROR_CHECK_MODE) &&
4046 					(*ctext_string == '\0' || !can_construe_as_integer(ctext_string)))
4047 				{
4048 					mprintf(("SEXP: '%s' is not a number; using '0' instead\n", ctext_string));
4049 					ctext_string = "0";
4050 				}
4051 			}
4052 			sprintf(dest, "%s ", ctext_string);
4053 		}
4054 	}
4055 }
4056 
build_sexp_string(SCP_string & accumulator,int cur_node,int level,int mode)4057 int build_sexp_string(SCP_string &accumulator, int cur_node, int level, int mode)
4058 {
4059 	SCP_string buf;
4060 	int node;
4061 	auto old_length = accumulator.length();
4062 
4063 	accumulator += "( ";
4064 	node = cur_node;
4065 	while (node != -1) {
4066 		Assert(node >= 0 && node < Num_sexp_nodes);
4067 		if (Sexp_nodes[node].first == -1) {
4068 			// build text to string
4069 			stuff_sexp_text_string(buf, node, mode);
4070 			accumulator += buf;
4071 
4072 		} else {
4073 			build_sexp_string(accumulator, Sexp_nodes[node].first, level + 1, mode);
4074 		}
4075 
4076 		node = Sexp_nodes[node].rest;
4077 	}
4078 
4079 	accumulator += ") ";
4080 	if ((accumulator.length() - old_length) > 40) {
4081 		accumulator.resize(old_length);
4082 		build_extended_sexp_string(accumulator, cur_node, level, mode);
4083 		return 1;
4084 	}
4085 
4086 	return 0;
4087 }
4088 
build_extended_sexp_string(SCP_string & accumulator,int cur_node,int level,int mode)4089 void build_extended_sexp_string(SCP_string &accumulator, int cur_node, int level, int mode)
4090 {
4091 	SCP_string buf;
4092 	int i, flag = 0, node;
4093 
4094 	accumulator += "( ";
4095 	node = cur_node;
4096 	while (node != -1) {
4097 		// not the first line?
4098 		if (flag) {
4099 			for (i=0; i<level + 1; i++)
4100 				accumulator += "   ";
4101 		}
4102 
4103 		flag = 1;
4104 		Assert(node >= 0 && node < Num_sexp_nodes);
4105 		if (Sexp_nodes[node].first == -1) {
4106 			stuff_sexp_text_string(buf, node, mode);
4107 			accumulator += buf;
4108 
4109 		} else {
4110 			build_sexp_string(accumulator, Sexp_nodes[node].first, level + 1, mode);
4111 		}
4112 
4113 		accumulator += "\n";
4114 		node = Sexp_nodes[node].rest;
4115 	}
4116 
4117 	for (i=0; i<level; i++)
4118 		accumulator += "   ";
4119 
4120 	accumulator += ")";
4121 }
4122 
convert_sexp_to_string(SCP_string & dest,int cur_node,int mode)4123 void convert_sexp_to_string(SCP_string &dest, int cur_node, int mode)
4124 {
4125 	if (cur_node >= 0) {
4126 		dest = "";
4127 		build_sexp_string(dest, cur_node, 0, mode);
4128 	} else {
4129 		dest = "( )";
4130 	}
4131 }
4132 
4133 
4134 // -----------------------------------------------------------------------------------
4135 // Helper methods for getting data from nodes. Cause it's stupid to keep re-rolling this stuff for every single SEXP
4136 // -----------------------------------------------------------------------------------
4137 
4138 /**
4139  * Evaluate number which may result from an operator or may be text
4140  */
eval_num(int n,bool & is_nan,bool & is_nan_forever)4141 int eval_num(int n, bool &is_nan, bool &is_nan_forever)
4142 {
4143 	is_nan = false;
4144 	is_nan_forever = false;
4145 
4146 	Assert(n >= 0);
4147 	if (n < 0)
4148 		return 0;
4149 
4150 	if (CAR(n) != -1)				// if argument is a sexp
4151 	{
4152 		int val = eval_sexp(CAR(n));
4153 
4154 		// NaNs will propagate through operations, so let the calling function know
4155 		if (Sexp_nodes[CAR(n)].value == SEXP_NAN)
4156 		{
4157 			val = 0;
4158 			is_nan = true;
4159 		}
4160 		else if (Sexp_nodes[CAR(n)].value == SEXP_NAN_FOREVER)
4161 		{
4162 			val = 0;
4163 			is_nan_forever = true;
4164 		}
4165 
4166 		return val;
4167 	}
4168 	else
4169 		return sexp_atoi(n);		// otherwise, just get the number
4170 }
4171 
4172 template <typename T>
eval_nums(int & n,bool & is_nan,bool & is_nan_forever,T & arg)4173 int eval_nums(int &n, bool &is_nan, bool &is_nan_forever, T &arg)
4174 {
4175 	if (n >= 0)
4176 	{
4177 		int val = eval_num(n, is_nan, is_nan_forever);
4178 		n = CDR(n);
4179 
4180 		arg = (T)val;
4181 		return 1;
4182 	}
4183 	else
4184 	{
4185 		is_nan = false;
4186 		is_nan_forever = false;
4187 
4188 		arg = (T)0;
4189 		return 0;
4190 	}
4191 }
4192 
4193 /**
4194  * Populate variadic arguments by running eval_num repeatedly.  No custom converter function is used; all numbers are cast from int to the desired type.
4195  * The count of numbers actually found (which depending on the sexp may not be the count of parameters) is returned.
4196  * NOTE: in contrast to eval_num, the *n* parameter will be advanced along the CDR path
4197  */
4198 template <typename T, typename... Args>
eval_nums(int & n,bool & is_nan,bool & is_nan_forever,T & first,Args &...rest)4199 int eval_nums(int &n, bool &is_nan, bool &is_nan_forever, T& first, Args&... rest)
4200 {
4201 	bool temp_nan, temp_nan_forever;
4202 	int count = 0;
4203 
4204 	is_nan = false;
4205 	is_nan_forever = false;
4206 
4207 	count += eval_nums(n, temp_nan, temp_nan_forever, first);
4208 	if (temp_nan)
4209 		is_nan = true;
4210 	if (temp_nan_forever)
4211 		is_nan_forever = true;
4212 
4213 	count += eval_nums(n, temp_nan, temp_nan_forever, rest...);
4214 	if (temp_nan)
4215 		is_nan = true;
4216 	if (temp_nan_forever)
4217 		is_nan_forever = true;
4218 
4219 	return count;
4220 }
4221 
4222 /**
4223  * Populate a numeric array by running eval_num repeatedly.  The converter function/lambda can be used to adapt the numbers returned from eval_num, such as casting (the default)
4224  * or restricting to a range.  The count of numbers actually found (which depending on the sexp may not be the size of the array) is returned.
4225  * NOTE: in contrast to eval_num, the *n* parameter will be advanced along the CDR path
4226  */
4227 template <typename T, std::size_t SIZE>
eval_array(std::array<T,SIZE> & numbers,int & n,bool & is_nan,bool & is_nan_forever,T (* converter)(int),const T & value_if_missing=(T)0)4228 int eval_array(std::array<T, SIZE> &numbers, int &n, bool &is_nan, bool &is_nan_forever, T(*converter)(int), const T &value_if_missing = (T)0)
4229 {
4230 	bool temp_nan, temp_nan_forever;
4231 	int count = 0;
4232 
4233 	is_nan = false;
4234 	is_nan_forever = false;
4235 
4236 	// fill up the array
4237 	for (std::size_t i = 0; i < SIZE; ++i)
4238 	{
4239 		// see if we have a number
4240 		if (n >= 0)
4241 		{
4242 			int num = eval_num(n, temp_nan, temp_nan_forever);
4243 			n = CDR(n);
4244 			++count;
4245 
4246 			if (temp_nan)
4247 				is_nan = true;
4248 			if (temp_nan_forever)
4249 				is_nan_forever = true;
4250 
4251 			// populate the array with that number
4252 			numbers[i] = converter(num);
4253 		}
4254 		// use the default
4255 		else
4256 			numbers[i] = value_if_missing;
4257 	}
4258 
4259 	return count;
4260 }
4261 
4262 /**
4263  * Certain compilers don't like a lambda as a default argument, so here's an extra function definition that supplies a standard converter.  Thanks to Gasbow for the insight.
4264  */
4265 template <typename T, std::size_t SIZE>
eval_array(std::array<T,SIZE> & numbers,int & n,bool & is_nan,bool & is_nan_forever)4266 int eval_array(std::array<T, SIZE> &numbers, int &n, bool &is_nan, bool &is_nan_forever)
4267 {
4268 	return eval_array<T>(numbers, n, is_nan, is_nan_forever, [](int num) -> T { return (T)num; });
4269 }
4270 
4271 /**
4272  * Populate a vector by running eval_num on up to three consecutive nodes.  Returns the number of nodes processed.
4273  * NOTE: in contrast to eval_num, the *n* parameter will be advanced along the CDR path
4274  */
eval_vec3d(vec3d * vec,int & n,bool & is_nan,bool & is_nan_forever)4275 int eval_vec3d(vec3d *vec, int &n, bool &is_nan, bool &is_nan_forever)
4276 {
4277 	Assertion(vec != nullptr, "Vec must not be null!");
4278 
4279 	// this is slightly more verbose than ideal, because one can't cast between C and C++ arrays
4280 	std::array<float, 3> a1d;
4281 	int count = eval_array(a1d, n, is_nan, is_nan_forever);
4282 	std::copy(a1d.begin(), a1d.end(), std::begin(vec->a1d));
4283 
4284 	return count;
4285 }
4286 
4287 /**
4288  * Populate an angles struct by running eval_num on up to three consecutive nodes.  Returns the number of nodes processed.
4289  * NOTE: in contrast to eval_num, the *n* parameter will be advanced along the CDR path
4290  */
eval_angles(angles * a,int & n,bool & is_nan,bool & is_nan_forever)4291 int eval_angles(angles *a, int &n, bool &is_nan, bool &is_nan_forever)
4292 {
4293 	Assertion(a != nullptr, "a must not be null!");
4294 
4295 	std::array<int, 3> a1d;
4296 	int count = eval_array(a1d, n, is_nan, is_nan_forever);
4297 
4298 	a->p = fl_radians(a1d[0] % 360);
4299 	a->b = fl_radians(a1d[1] % 360);
4300 	a->h = fl_radians(a1d[2] % 360);
4301 
4302 	return count;
4303 }
4304 
4305 /**
4306  * Takes a ship entry and returns the player for that ship or NULL if it is an AI ship.
4307  * In singleplayer mode, this is *the* player, but in multiplayer mode it could be any player.
4308  */
get_player_from_ship_entry(const ship_registry_entry * ship_entry,bool test_respawns=false,int * netplayer_index=nullptr)4309 player *get_player_from_ship_entry(const ship_registry_entry *ship_entry, bool test_respawns = false, int *netplayer_index = nullptr)
4310 {
4311 	Assertion(ship_entry, "ship_entry cannot be null!");
4312 
4313 	if (netplayer_index)
4314 		*netplayer_index = -1;
4315 
4316 	// singleplayer
4317 	if (!(Game_mode & GM_MULTIPLAYER)) {
4318 		if (Player_obj == ship_entry->objp) {
4319 			return Player;
4320 		}
4321 		return nullptr;
4322 	}
4323 	// multiplayer
4324 	else {
4325 		int np_index = -1;
4326 
4327 		if (ship_entry->objp) {
4328 			// try and find the player
4329 			np_index = multi_find_player_by_object(ship_entry->objp);
4330 		}
4331 		if (test_respawns && np_index < 0) {
4332 			// Respawning ships don't have an objnum so we need to take a different approach
4333 			if (ship_entry->p_objp) {
4334 				np_index = multi_find_player_by_parse_object(ship_entry->p_objp);
4335 			}
4336 		}
4337 
4338 		if ((np_index >= 0) && (np_index < MAX_PLAYERS)) {
4339 			if (netplayer_index)
4340 				*netplayer_index = np_index;
4341 
4342 			return Net_players[np_index].m_player;
4343 		}
4344 
4345 		return nullptr;
4346 	}
4347 }
4348 
4349 /**
4350  * Forwards to get_player_from_ship_entry using a ship_entry generated from this SEXP node.
4351  */
get_player_from_ship_node(int node,bool test_respawns=false,int * netplayer_index=nullptr)4352 player *get_player_from_ship_node(int node, bool test_respawns = false, int *netplayer_index = nullptr)
4353 {
4354 	auto ship_entry = eval_ship(node);
4355 	if (!ship_entry)
4356 		return nullptr;
4357 
4358 	return get_player_from_ship_entry(ship_entry, test_respawns, netplayer_index);
4359 }
4360 
4361 // -----------------------------------------------------------------------------------
4362 // SEXP caching
4363 // -----------------------------------------------------------------------------------
4364 
4365 /**
4366  * Gets a ship from a sexp node.  Returns the ship registry entry, or NULL if the ship is unknown.
4367  */
eval_ship(int node)4368 const ship_registry_entry *eval_ship(int node)
4369 {
4370 	Assert(node >= 0);
4371 
4372 	// check cache
4373 	if (Sexp_nodes[node].cache)
4374 	{
4375 		// have we cached something else?
4376 		if (Sexp_nodes[node].cache->sexp_node_data_type != OPF_SHIP)
4377 			return nullptr;
4378 
4379 		return &Ship_registry[Sexp_nodes[node].cache->ship_registry_index];
4380 	}
4381 
4382 	// maybe forward to a special-arg node
4383 	if (Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_NODE)
4384 	{
4385 		auto current_argument = Sexp_replacement_arguments.back();
4386 		int arg_node = current_argument.second;
4387 
4388 		if (arg_node >= 0)
4389 			return eval_ship(arg_node);
4390 	}
4391 
4392 	auto ship_it = Ship_registry_map.find(CTEXT(node));
4393 	if (ship_it != Ship_registry_map.end())
4394 	{
4395 		// cache the value, unless this node is a variable or argument because the value may change
4396 		if (!(Sexp_nodes[node].type & SEXP_FLAG_VARIABLE) && !(Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_NODE))
4397 			Sexp_nodes[node].cache = new sexp_cached_data(OPF_SHIP, -1, ship_it->second);
4398 
4399 		return &Ship_registry[ship_it->second];
4400 	}
4401 
4402 	// we know nothing about this ship, apparently
4403 	return nullptr;
4404 }
4405 
4406 /**
4407  * Gets a wing from a sexp node.  Returns the wing pointer, or NULL if the wing is unknown.
4408  */
eval_wing(int node)4409 wing *eval_wing(int node)
4410 {
4411 	Assert(node >= 0);
4412 
4413 	// check cache
4414 	if (Sexp_nodes[node].cache)
4415 	{
4416 		// have we cached something else?
4417 		if (Sexp_nodes[node].cache->sexp_node_data_type != OPF_WING)
4418 			return nullptr;
4419 
4420 		return static_cast<wing*>(Sexp_nodes[node].cache->pointer);
4421 	}
4422 
4423 	// maybe forward to a special-arg node
4424 	if (Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_NODE)
4425 	{
4426 		auto current_argument = Sexp_replacement_arguments.back();
4427 		int arg_node = current_argument.second;
4428 
4429 		if (arg_node >= 0)
4430 			return eval_wing(arg_node);
4431 	}
4432 
4433 	int wing_num = wing_lookup(CTEXT(node));
4434 	if (wing_num >= 0)
4435 	{
4436 		auto wingp = &Wings[wing_num];
4437 
4438 		// cache the value, unless this node is a variable or argument because the value may change
4439 		if (!(Sexp_nodes[node].type & SEXP_FLAG_VARIABLE) && !(Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_NODE))
4440 			Sexp_nodes[node].cache = new sexp_cached_data(OPF_WING, wingp);
4441 
4442 		return wingp;
4443 	}
4444 
4445 	// it must not be a wing
4446 	return nullptr;
4447 }
4448 
4449 /**
4450  * Returns a number parsed from the sexp node text.
4451  * NOTE: sexp_atoi can only be used if CTEXT was used; i.e. atoi(CTEXT(n))
4452  */
sexp_atoi(int node)4453 int sexp_atoi(int node)
4454 {
4455 	Assertion(!Fred_running, "This function relies on SEXP caching which is not set up to work in FRED!");
4456 	Assert(node >= 0);
4457 
4458 	// check cache
4459 	if (Sexp_nodes[node].cache)
4460 	{
4461 		// have we cached something else?
4462 		if (Sexp_nodes[node].cache->sexp_node_data_type != OPF_NUMBER)
4463 			return 0;
4464 
4465 		return Sexp_nodes[node].cache->numeric_literal;
4466 	}
4467 
4468 	// maybe forward to a special-arg node
4469 	if (Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_NODE)
4470 	{
4471 		auto current_argument = Sexp_replacement_arguments.back();
4472 		int arg_node = current_argument.second;
4473 
4474 		if (arg_node >= 0)
4475 			return sexp_atoi(arg_node);
4476 	}
4477 
4478 	int num = atoi(CTEXT(node));
4479 
4480 	// cache the value, unless this node is a variable or argument because the value may change
4481 	if (!(Sexp_nodes[node].type & SEXP_FLAG_VARIABLE) && !(Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_NODE))
4482 		Sexp_nodes[node].cache = new sexp_cached_data(OPF_NUMBER, num, -1);
4483 
4484 	return num;
4485 }
4486 
4487 /**
4488  * A version of can_construe_as_integer that knows about number caching.
4489  */
sexp_can_construe_as_integer(int node)4490 bool sexp_can_construe_as_integer(int node)
4491 {
4492 	Assertion(!Fred_running, "This function relies on SEXP caching which is not set up to work in FRED!");
4493 	Assert(node >= 0);
4494 
4495 	if (Sexp_nodes[node].cache && Sexp_nodes[node].cache->sexp_node_data_type == OPF_NUMBER)
4496 		return true;
4497 
4498 	// maybe forward to a special-arg node
4499 	if (Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_NODE)
4500 	{
4501 		auto current_argument = Sexp_replacement_arguments.back();
4502 		int arg_node = current_argument.second;
4503 
4504 		if (arg_node >= 0)
4505 			return sexp_can_construe_as_integer(arg_node);
4506 	}
4507 
4508 	return can_construe_as_integer(CTEXT(node));
4509 }
4510 
4511 /*
4512  * This can be used by both FRED and FSO.  When in FSO, it incorporates caching that runs parallel to the main data caching.
4513  * Note that this function does not do special-arg forwarding because it operates on the node text, not the CTEXT, and thus
4514  * there is no possibility of processing a special argument.
4515  */
sexp_get_variable_index(int node)4516 int sexp_get_variable_index(int node)
4517 {
4518 	Assert(node >= 0);
4519 
4520 	if (Sexp_nodes[node].cached_variable_index >= 0)
4521 		return Sexp_nodes[node].cached_variable_index;
4522 
4523 	if (!(Sexp_nodes[node].type & SEXP_FLAG_VARIABLE))
4524 		return -1;
4525 
4526 	Assert(Sexp_nodes[node].first == -1);
4527 
4528 	if (Fred_running)
4529 		return get_index_sexp_variable_name(Sexp_nodes[node].text);
4530 
4531 	// parse it
4532 	int index = atoi(Sexp_nodes[node].text);
4533 
4534 	// verify variable set
4535 	Assert(Sexp_variables[index].type & SEXP_VARIABLE_SET);
4536 
4537 	// cache and return
4538 	Sexp_nodes[node].cached_variable_index = index;
4539 	return index;
4540 }
4541 
4542 // -----------------------------------------------------------------------------------
4543 // SEXP implementations
4544 // -----------------------------------------------------------------------------------
4545 
4546 // arithmetic functions
add_sexps(int n)4547 int add_sexps(int n)
4548 {
4549 	int	val, sum = 0;
4550 
4551 	if (n != -1) {
4552 		if (CAR(n) != -1) {
4553 			val = eval_sexp(CAR(n));
4554 
4555 			// be sure to check for the NAN value when doing arithmetic -- this value should
4556 			// get propagated to the next highest function.
4557 			if (Sexp_nodes[CAR(n)].value == SEXP_NAN)
4558 				return SEXP_NAN;
4559 			else if (Sexp_nodes[CAR(n)].value == SEXP_NAN_FOREVER)
4560 				return SEXP_NAN_FOREVER;
4561 
4562 			sum = val;
4563 		}
4564 		else
4565 			sum = sexp_atoi(n);
4566 
4567 		while (CDR(n) != -1) {
4568 			val = eval_sexp(CDR(n));
4569 
4570 			// be sure to check for the NAN value when doing arithmetic -- this value should
4571 			// get propagated to the next highest function.
4572 			if (Sexp_nodes[CDR(n)].value == SEXP_NAN)
4573 				return SEXP_NAN;
4574 			else if (Sexp_nodes[CDR(n)].value == SEXP_NAN_FOREVER)
4575 				return SEXP_NAN_FOREVER;
4576 
4577 			sum += val;
4578 			n = CDR(n);
4579 		}
4580 	}
4581 
4582 	return sum;
4583 }
4584 
sub_sexps(int n)4585 int sub_sexps(int n)
4586 {
4587 	int	val, sum = 0;
4588 
4589 	if (n != -1) {
4590 		if (CAR(n) != -1) {
4591 			val = eval_sexp(CAR(n));
4592 
4593 			// be sure to check for the NAN value when doing arithmetic -- this value should
4594 			// get propagated to the next highest function.
4595 			if (Sexp_nodes[CAR(n)].value == SEXP_NAN)
4596 				return SEXP_NAN;
4597 			else if (Sexp_nodes[CAR(n)].value == SEXP_NAN_FOREVER)
4598 				return SEXP_NAN_FOREVER;
4599 
4600 			sum = val;
4601 		}
4602 		else
4603 			sum = sexp_atoi(n);
4604 
4605 		while (CDR(n) != -1) {
4606 			val = eval_sexp(CDR(n));
4607 
4608 			// be sure to check for the NAN value when doing arithmetic -- this value should
4609 			// get propagated to the next highest function.
4610 			if (Sexp_nodes[CDR(n)].value == SEXP_NAN)
4611 				return SEXP_NAN;
4612 			else if (Sexp_nodes[CDR(n)].value == SEXP_NAN_FOREVER)
4613 				return SEXP_NAN_FOREVER;
4614 
4615 			sum -= val;
4616 			n = CDR(n);
4617 		}
4618 	}
4619 
4620 	return sum;
4621 }
4622 
mul_sexps(int n)4623 int mul_sexps(int n)
4624 {
4625 	int	val, sum = 0;
4626 
4627 	if (n != -1) {
4628 		if (CAR(n) != -1) {
4629 			val = eval_sexp(CAR(n));
4630 
4631 			// be sure to check for the NAN value when doing arithmetic -- this value should
4632 			// get propagated to the next highest function.
4633 			if (Sexp_nodes[CAR(n)].value == SEXP_NAN)
4634 				return SEXP_NAN;
4635 			else if (Sexp_nodes[CAR(n)].value == SEXP_NAN_FOREVER)
4636 				return SEXP_NAN_FOREVER;
4637 
4638 			sum = val;
4639 		}
4640 		else
4641 			sum = sexp_atoi(n);
4642 
4643 		while (CDR(n) != -1) {
4644 			val = eval_sexp(CDR(n));
4645 
4646 			// be sure to check for the NAN value when doing arithmetic -- this value should
4647 			// get propagated to the next highest function.
4648 			if (Sexp_nodes[CDR(n)].value == SEXP_NAN)
4649 				return SEXP_NAN;
4650 			else if (Sexp_nodes[CDR(n)].value == SEXP_NAN_FOREVER)
4651 				return SEXP_NAN_FOREVER;
4652 
4653 			sum *= val;
4654 			n = CDR(n);
4655 		}
4656 	}
4657 
4658 	return sum;
4659 }
4660 
div_sexps(int n)4661 int div_sexps(int n)
4662 {
4663 	int	val, sum = 0;
4664 
4665 	if (n != -1) {
4666 		if (CAR(n) != -1) {
4667 			val = eval_sexp(CAR(n));
4668 
4669 			// be sure to check for the NAN value when doing arithmetic -- this value should
4670 			// get propagated to the next highest function.
4671 			if (Sexp_nodes[CAR(n)].value == SEXP_NAN)
4672 				return SEXP_NAN;
4673 			else if (Sexp_nodes[CAR(n)].value == SEXP_NAN_FOREVER)
4674 				return SEXP_NAN_FOREVER;
4675 
4676 			sum = val;
4677 		}
4678 		else
4679 			sum = sexp_atoi(n);
4680 
4681 		while (CDR(n) != -1) {
4682 			val = eval_sexp(CDR(n));
4683 
4684 			// be sure to check for the NAN value when doing arithmetic -- this value should
4685 			// get propagated to the next highest function.
4686 			if (Sexp_nodes[CDR(n)].value == SEXP_NAN)
4687 				return SEXP_NAN;
4688 			else if (Sexp_nodes[CDR(n)].value == SEXP_NAN_FOREVER)
4689 				return SEXP_NAN_FOREVER;
4690 
4691 			if (val == 0) {
4692 				Warning(LOCATION, "Division by zero in sexp. Please check all uses of the / operator for possible causes.\n");
4693 				return SEXP_NAN;
4694 			}
4695 
4696 			sum /= val;
4697 			n = CDR(n);
4698 		}
4699 	}
4700 
4701 	return sum;
4702 }
4703 
mod_sexps(int n)4704 int mod_sexps(int n)
4705 {
4706 	int	val, sum = 0;
4707 
4708 	if (n != -1) {
4709 		if (CAR(n) != -1) {
4710 			val = eval_sexp(CAR(n));
4711 
4712 			// be sure to check for the NAN value when doing arithmetic -- this value should
4713 			// get propagated to the next highest function.
4714 			if (Sexp_nodes[CAR(n)].value == SEXP_NAN)
4715 				return SEXP_NAN;
4716 			else if (Sexp_nodes[CAR(n)].value == SEXP_NAN_FOREVER)
4717 				return SEXP_NAN_FOREVER;
4718 
4719 			sum = val;
4720 		}
4721 		else
4722 			sum = sexp_atoi(n);
4723 
4724 		while (CDR(n) != -1) {
4725 			val = eval_sexp(CDR(n));
4726 
4727 			// be sure to check for the NAN value when doing arithmetic -- this value should
4728 			// get propagated to the next highest function.
4729 			if (Sexp_nodes[CDR(n)].value == SEXP_NAN)
4730 				return SEXP_NAN;
4731 			else if (Sexp_nodes[CDR(n)].value == SEXP_NAN_FOREVER)
4732 				return SEXP_NAN_FOREVER;
4733 
4734 			if (val == 0) {
4735 				Warning(LOCATION, "Modulo by zero in sexp. Please check all uses of the %% operator for possible causes.\n");
4736 				return SEXP_NAN;
4737 			}
4738 
4739 			sum = sum % val;
4740 			n = CDR(n);
4741 		}
4742 	}
4743 
4744 	return sum;
4745 }
4746 
rand_internal(int low,int high,int seed=0)4747 int rand_internal(int low, int high, int seed = 0)
4748 {
4749 	// maybe seed it
4750 	if (seed > 0)
4751 		Random::seed(seed);
4752 
4753 	return Random::next(low, high);
4754 }
4755 
4756 // Goober5000
abs_sexp(int n)4757 int abs_sexp(int n)
4758 {
4759 	bool is_nan, is_nan_forever;
4760 	int val = eval_num(n, is_nan, is_nan_forever);
4761 
4762 	if (is_nan)
4763 		return SEXP_NAN;
4764 	if (is_nan_forever)
4765 		return SEXP_NAN_FOREVER;
4766 
4767 	return abs(val);
4768 }
4769 
4770 // Goober5000
min_sexp(int n)4771 int min_sexp(int n)
4772 {
4773 	bool is_nan, is_nan_forever;
4774 	int temp, min_val = INT_MAX;
4775 
4776 	while (n != -1)
4777 	{
4778 		temp = eval_num(n, is_nan, is_nan_forever);
4779 
4780 		if (is_nan)
4781 			return SEXP_NAN;
4782 		if (is_nan_forever)
4783 			return SEXP_NAN_FOREVER;
4784 
4785 		if (temp < min_val)
4786 			min_val = temp;
4787 
4788 		n = CDR(n);
4789 	}
4790 
4791 	return min_val;
4792 }
4793 
4794 // Goober5000
max_sexp(int n)4795 int max_sexp(int n)
4796 {
4797 	bool is_nan, is_nan_forever;
4798 	int temp, max_val = INT_MIN;
4799 
4800 	while (n != -1)
4801 	{
4802 		temp = eval_num(n, is_nan, is_nan_forever);
4803 
4804 		if (is_nan)
4805 			return SEXP_NAN;
4806 		if (is_nan_forever)
4807 			return SEXP_NAN_FOREVER;
4808 
4809 		if (temp > max_val)
4810 			max_val = temp;
4811 
4812 		n = CDR(n);
4813 	}
4814 
4815 	return max_val;
4816 }
4817 
4818 // Goober5000
avg_sexp(int n)4819 int avg_sexp(int n)
4820 {
4821 	bool is_nan, is_nan_forever;
4822 	int num = 0, avg_val = 0;
4823 
4824 	while (n != -1)
4825 	{
4826 		int val = eval_num(n, is_nan, is_nan_forever);
4827 
4828 		if (is_nan)
4829 			return SEXP_NAN;
4830 		if (is_nan_forever)
4831 			return SEXP_NAN_FOREVER;
4832 
4833 		avg_val += val;
4834 		num++;
4835 
4836 		n = CDR(n);
4837 	}
4838 
4839 	return (int) floor(((double) avg_val / num) + 0.5);
4840 }
4841 
4842 // Goober5000
pow_sexp(int node)4843 int pow_sexp(int node)
4844 {
4845 	int num_1, num_2;
4846 	bool is_nan, is_nan_forever;
4847 
4848 	eval_nums(node, is_nan, is_nan_forever, num_1, num_2);
4849 	if (is_nan)
4850 		return SEXP_NAN;
4851 	if (is_nan_forever)
4852 		return SEXP_NAN_FOREVER;
4853 
4854 	// this is disallowed in FRED, but can still happen through careless arithmetic
4855 	if (num_2 < 0)
4856 	{
4857 		Warning(LOCATION, "Power function pow(%d, %d) attempted to raise to a negative power!", num_1, num_2);
4858 		return 0;
4859 	}
4860 
4861 	// shortcuts
4862 	if (num_1 == 0 && num_2 == 0)
4863 	{
4864 		Warning(LOCATION, "Zero raised to the power of zero in sexp. Please check all uses of the power operator for possible causes.\n");
4865 		return SEXP_NAN;
4866 	}
4867 	else if (num_1 == 0 || num_1 == 1 || num_2 == 1)
4868 		return num_1;
4869 	else if (num_2 == 0)
4870 		return 1;
4871 
4872 	double pow_result = pow(static_cast<double>(num_1), num_2);
4873 
4874 	if (pow_result > static_cast<double>(INT_MAX))
4875 	{
4876 		nprintf(("SEXP", "Power function pow(%d, %d) is greater than INT_MAX!  Returning INT_MAX.", num_1, num_2));
4877 		return INT_MAX;
4878 	}
4879 	else if (pow_result < static_cast<double>(INT_MIN))
4880 	{
4881 		nprintf(("SEXP", "Power function pow(%d, %d) is less than INT_MIN!  Returning INT_MIN.", num_1, num_2));
4882 		return INT_MIN;
4883 	}
4884 
4885 	return static_cast<int>(pow_result);
4886 }
4887 
4888 // is there a better place to put this?  seems useful...
4889 // credit to http://stackoverflow.com/questions/1903954/is-there-a-standard-sign-function-signum-sgn-in-c-c
4890 template <typename T>
sign(T t)4891 T sign(T t)
4892 {
4893 	if (t == 0)
4894 		return T(0);
4895 	else
4896 		return (t < 0) ? T(-1) : T(1);
4897 }
4898 
4899 // Goober5000
signum_sexp(int node)4900 int signum_sexp(int node)
4901 {
4902 	bool is_nan, is_nan_forever;
4903 	int num = eval_num(node, is_nan, is_nan_forever);
4904 
4905 	if (is_nan)
4906 		return SEXP_NAN;
4907 	if (is_nan_forever)
4908 		return SEXP_NAN_FOREVER;
4909 
4910 	return sign(num);
4911 }
4912 
4913 // Goober5000
sexp_is_nan(int n)4914 int sexp_is_nan(int n)
4915 {
4916 	// if this sexp has an operator, evaluate it
4917 	if (CAR(n) != -1) {
4918 		eval_sexp(CAR(n));
4919 
4920 		// see whether the result was NaN
4921 		if (Sexp_nodes[CAR(n)].value == SEXP_NAN)
4922 			return SEXP_TRUE;
4923 		if (Sexp_nodes[CAR(n)].value == SEXP_NAN_FOREVER)
4924 			return SEXP_KNOWN_TRUE;
4925 
4926 		// see whether the result won't change
4927 		if (Sexp_nodes[CAR(n)].value == SEXP_KNOWN_TRUE || Sexp_nodes[CAR(n)].value == SEXP_KNOWN_FALSE)
4928 			return SEXP_KNOWN_FALSE;
4929 
4930 		// it's not NaN, for now
4931 		return SEXP_FALSE;
4932 	}
4933 	// straight-up numeric or string arguments cannot be NaN
4934 	else
4935 		return SEXP_KNOWN_FALSE;
4936 }
4937 
4938 // Goober5000
sexp_nan_to_number(int n)4939 int sexp_nan_to_number(int n)
4940 {
4941 	// if this sexp has an operator, evaluate it
4942 	if (CAR(n) != -1) {
4943 		int val = eval_sexp(CAR(n));
4944 
4945 		// see whether the result was NaN
4946 		if (Sexp_nodes[CAR(n)].value == SEXP_NAN || Sexp_nodes[CAR(n)].value == SEXP_NAN_FOREVER)
4947 			return 0;
4948 
4949 		// if not NaN, result is acceptable
4950 		return val;
4951 	}
4952 	// straight-up numeric or string arguments cannot be NaN
4953 	else
4954 		return sexp_atoi(n);
4955 }
4956 
4957 // Goober5000
sexp_set_bit(int node,bool set_it)4958 int sexp_set_bit(int node, bool set_it)
4959 {
4960 	int val, bit_index;
4961 	bool is_nan, is_nan_forever;
4962 
4963 	eval_nums(node, is_nan, is_nan_forever, val, bit_index);
4964 	if (is_nan)
4965 		return SEXP_NAN;
4966 	if (is_nan_forever)
4967 		return SEXP_NAN_FOREVER;
4968 
4969 	if (bit_index < 0 || bit_index > 31)
4970 	{
4971 		// out of range, so just return the original value
4972 		// (we used to warn, but it's more convenient to FREDders to fail gracefully)
4973 		return val;
4974 	}
4975 
4976 	if (set_it)
4977 		return val | (1<<bit_index);
4978 	else
4979 		return val & ~(1<<bit_index);
4980 }
4981 
4982 // Goober5000
sexp_is_bit_set(int node)4983 int sexp_is_bit_set(int node)
4984 {
4985 	int val, bit_index;
4986 	bool is_nan, is_nan_forever;
4987 
4988 	eval_nums(node, is_nan, is_nan_forever, val, bit_index);
4989 	if (is_nan)
4990 		return SEXP_FALSE;
4991 	if (is_nan_forever)
4992 		return SEXP_KNOWN_FALSE;
4993 
4994 	if (bit_index < 0 || bit_index > 31)
4995 	{
4996 		// out of range, so just return false
4997 		// (we used to warn, but it's more convenient to FREDders to fail gracefully)
4998 		return SEXP_FALSE;
4999 	}
5000 
5001 	if (val & (1<<bit_index))
5002 		return SEXP_TRUE;
5003 	else
5004 		return SEXP_FALSE;
5005 }
5006 
5007 // Goober5000
sexp_bitwise_and(int node)5008 int sexp_bitwise_and(int node)
5009 {
5010 	bool is_nan, is_nan_forever;
5011 
5012 	int result = eval_num(node, is_nan, is_nan_forever);
5013 	if (is_nan)
5014 		return SEXP_NAN;
5015 	if (is_nan_forever)
5016 		return SEXP_NAN_FOREVER;
5017 
5018 	for (int n = CDR(node); n != -1; n = CDR(n))
5019 	{
5020 		int val = eval_num(n, is_nan, is_nan_forever);
5021 		if (is_nan)
5022 			return SEXP_NAN;
5023 		if (is_nan_forever)
5024 			return SEXP_NAN_FOREVER;
5025 
5026 		result &= val;
5027 	}
5028 
5029 	return result;
5030 }
5031 
5032 // Goober5000
sexp_bitwise_or(int node)5033 int sexp_bitwise_or(int node)
5034 {
5035 	bool is_nan, is_nan_forever;
5036 
5037 	int result = eval_num(node, is_nan, is_nan_forever);
5038 	if (is_nan)
5039 		return SEXP_NAN;
5040 	if (is_nan_forever)
5041 		return SEXP_NAN_FOREVER;
5042 
5043 	for (int n = CDR(node); n != -1; n = CDR(n))
5044 	{
5045 		int val = eval_num(n, is_nan, is_nan_forever);
5046 		if (is_nan)
5047 			return SEXP_NAN;
5048 		if (is_nan_forever)
5049 			return SEXP_NAN_FOREVER;
5050 
5051 		result |= val;
5052 	}
5053 
5054 	return result;
5055 }
5056 
5057 // Goober5000
sexp_bitwise_not(int node)5058 int sexp_bitwise_not(int node)
5059 {
5060 	bool is_nan, is_nan_forever;
5061 
5062 	int val = eval_num(node, is_nan, is_nan_forever);
5063 	if (is_nan)
5064 		return SEXP_NAN;
5065 	if (is_nan_forever)
5066 		return SEXP_NAN_FOREVER;
5067 
5068 	// flip the bits, then clear the sign bit
5069 	return (~val) & INT_MAX;
5070 }
5071 
5072 // Goober5000
sexp_bitwise_xor(int node)5073 int sexp_bitwise_xor(int node)
5074 {
5075 	int val1, val2;
5076 	bool is_nan, is_nan_forever;
5077 
5078 	eval_nums(node, is_nan, is_nan_forever, val1, val2);
5079 	if (is_nan)
5080 		return SEXP_NAN;
5081 	if (is_nan_forever)
5082 		return SEXP_NAN_FOREVER;
5083 
5084 	return val1 ^ val2;
5085 }
5086 
5087 // seeding added by Karajorma and Goober5000
rand_sexp(int node,bool multiple)5088 int rand_sexp(int node, bool multiple)
5089 {
5090 	bool is_nan, is_nan_forever;
5091 	int n = node, low, high, rand_num, seed;
5092 
5093 	// when getting a saved value
5094 	if (Sexp_nodes[node].value == SEXP_NUM_EVAL)
5095 	{
5096 		// don't regenerate new random number
5097 		return sexp_atoi(node);
5098 	}
5099 
5100 	// get low, high, and (optional) seed - seed will be 0, per eval_nums, if not specified
5101 	eval_nums(n, is_nan, is_nan_forever, low, high, seed);
5102 	if (is_nan)
5103 		return SEXP_NAN;
5104 	if (is_nan_forever)
5105 		return SEXP_NAN_FOREVER;
5106 
5107 	if (low > high) {
5108 		Warning(LOCATION, "rand%s was passed an invalid range (%d ... %d)!", multiple ? "-multiple" : "", low, high);
5109 		// preserve old behavior from before Random class was introduced
5110 		return low;
5111 	}
5112 
5113 	// get the random number
5114 	rand_num = rand_internal(low, high, seed);
5115 
5116 	// when saving the value
5117 	if (!multiple)
5118 	{
5119 		// set .value and .text so random number is generated only once.
5120 		Sexp_nodes[node].value = SEXP_NUM_EVAL;
5121 		sprintf(Sexp_nodes[node].text, "%d", rand_num);
5122 	}
5123 	// if this is multiple with a nonzero seed provided
5124 	else if (seed > 0)
5125 	{
5126 		// Set the seed to a new seeded random value. This will ensure that the next time the method
5127 		// is called it will return a predictable but different number from the previous time.
5128 		sprintf(Sexp_nodes[CDDR(node)].text, "%d", rand_internal(1, INT_MAX, seed));
5129 	}
5130 
5131 	return rand_num;
5132 }
5133 
5134 // boolean evaluation functions.  Evaluate all sexpressions in the 'or' operator.  Needed to mark
5135 // entries in the mission log as essential so when pruning the log, we know which entries we might
5136 // need to keep.
sexp_or(int n)5137 int sexp_or(int n)
5138 {
5139 	bool all_false = true;
5140 	bool result = false;
5141 
5142 	if (n != -1)
5143 	{
5144 		if (CAR(n) != -1)
5145 		{
5146 			result = is_sexp_true(CAR(n)) || result;
5147 			if (Sexp_nodes[CAR(n)].value == SEXP_KNOWN_TRUE)
5148 				return SEXP_KNOWN_TRUE;								// if one of the OR clauses is TRUE, whole clause is true
5149 			if (Sexp_nodes[CAR(n)].value != SEXP_KNOWN_FALSE)		// if the value is still unknown, they all can't be false
5150 				all_false = false;
5151 		}
5152 		// this should never happen, because all arguments which return logical values are operators
5153 		else
5154 			result = (sexp_atoi(n) != 0) || result;
5155 
5156 		while (CDR(n) != -1)
5157 		{
5158 			result = is_sexp_true(CDR(n)) || result;
5159 			if ( Sexp_nodes[CDR(n)].value == SEXP_KNOWN_TRUE )
5160 				return SEXP_KNOWN_TRUE;								// if one of the OR clauses is TRUE, whole clause is true
5161 			if ( Sexp_nodes[CDR(n)].value != SEXP_KNOWN_FALSE )		// if the value is still unknown, they all can't be false
5162 				all_false = false;
5163 
5164 			n = CDR(n);
5165 		}
5166 	}
5167 
5168 	if (all_false)
5169 		return SEXP_KNOWN_FALSE;
5170 
5171 	return result ? SEXP_TRUE : SEXP_FALSE;
5172 }
5173 
5174 // this function does the 'and' operator.  It will short circuit evaluation  *but* it will still
5175 // evaluate other members of the and construct.  I do this because I need events in the mission log
5176 // to get marked as essential for goal purposes, and evaluation is pretty much the only way
sexp_and(int n)5177 int sexp_and(int n)
5178 {
5179 	bool all_true = true;
5180 	bool result = true;
5181 
5182 	if (n != -1)
5183 	{
5184 		if (CAR(n) != -1)
5185 		{
5186 			result = is_sexp_true(CAR(n)) && result;
5187 			if ( Sexp_nodes[CAR(n)].value == SEXP_KNOWN_FALSE || Sexp_nodes[CAR(n)].value == SEXP_NAN_FOREVER )
5188 				return SEXP_KNOWN_FALSE;							// if one of the AND clauses is FALSE, whole clause is false
5189 			if ( Sexp_nodes[CAR(n)].value != SEXP_KNOWN_TRUE )		// if the value is still unknown, they all can't be true
5190 				all_true = false;
5191 		}
5192 		// this should never happen, because all arguments which return logical values are operators
5193 		else
5194 			result = (sexp_atoi(n) != 0) && result;
5195 
5196 		while (CDR(n) != -1)
5197 		{
5198 			result = is_sexp_true(CDR(n)) && result;
5199 			if ( Sexp_nodes[CDR(n)].value == SEXP_KNOWN_FALSE || Sexp_nodes[CDR(n)].value == SEXP_NAN_FOREVER )
5200 				return SEXP_KNOWN_FALSE;							// if one of the AND clauses is FALSE, whole clause is false
5201 			if ( Sexp_nodes[CDR(n)].value != SEXP_KNOWN_TRUE )		// if the value is still unknown, they all can't be true
5202 				all_true = false;
5203 
5204 			n = CDR(n);
5205 		}
5206 	}
5207 
5208 	if (all_true)
5209 		return SEXP_KNOWN_TRUE;
5210 
5211 	return result ? SEXP_TRUE : SEXP_FALSE;
5212 }
5213 
5214 // this version of the 'and' operator determines whether or not its arguments become true
5215 // in the order in which they are specified in the when statement.  Should be a simple matter of
5216 // seeing if anything evaluates to true later than something that evaluated to false
sexp_and_in_sequence(int n)5217 int sexp_and_in_sequence(int n)
5218 {
5219 	bool all_true = true;									// represents whether or not all nodes we have seen so far are true
5220 	bool result = true;
5221 
5222 	if (n != -1)
5223 	{
5224 		if (CAR(n) != -1)
5225 		{
5226 			result = is_sexp_true(CAR(n)) && result;
5227 			if ( Sexp_nodes[CAR(n)].value == SEXP_KNOWN_FALSE || Sexp_nodes[CAR(n)].value == SEXP_NAN_FOREVER )
5228 				return SEXP_KNOWN_FALSE;							// if one of the AND clauses is FALSE, whole clause is false
5229 			if ( Sexp_nodes[CAR(n)].value != SEXP_KNOWN_TRUE )		// if value is true, mark our all_true variable for later checking
5230 				all_true = false;
5231 		}
5232 		// this should never happen, because all arguments which return logical values are operators
5233 		else
5234 			result = (sexp_atoi(n) != 0) && result;
5235 
5236 		// a little test -- if the previous sexpressions was true, then mark the node itself as always
5237 		// true.  I did this because of the distance function.  It might become true, then when waiting for
5238 		// the second evalation, it might become false, rendering this function false.  So, when one becomes
5239 		// true -- mark it true forever.
5240 		if ( result )
5241 			Sexp_nodes[CAR(n)].value = SEXP_KNOWN_TRUE;
5242 
5243 		while (CDR(n) != -1)
5244 		{
5245 			bool next_result = is_sexp_true(CDR(n));
5246 			if ( next_result && !result )				// if current result is true, and our running result is false, things didn't become true in order
5247 				return SEXP_KNOWN_FALSE;
5248 
5249 			result = next_result && result;
5250 			if ( Sexp_nodes[CDR(n)].value == SEXP_KNOWN_FALSE || Sexp_nodes[CDR(n)].value == SEXP_NAN_FOREVER )
5251 				return SEXP_KNOWN_FALSE;							// if one of the AND clauses is FALSE, whole clause is false
5252 			if ( Sexp_nodes[CDR(n)].value != SEXP_KNOWN_TRUE )		// if the value is still unknown, they all can't be true
5253 				all_true = false;
5254 
5255 			// see comment above for explanation of next lines
5256 			if ( result )
5257 				Sexp_nodes[CDR(n)].value = SEXP_KNOWN_TRUE;
5258 
5259 			n = CDR(n);
5260 		}
5261 	}
5262 
5263 	if ( all_true )
5264 		return SEXP_KNOWN_TRUE;
5265 
5266 	return result ? SEXP_TRUE : SEXP_FALSE;
5267 }
5268 
5269 // for these four basic boolean operations (not, <, >, and =), we have special cases that we must deal
5270 // with.  We have sexpressions operators that might return a NAN type return value (such as the distance
5271 // between two ships when one of the ships is destroyed or departed).  These operations need to check for
5272 // this special NAN value and adjust their return types accordingly.  NAN values represent false return values
sexp_not(int n)5273 int sexp_not(int n)
5274 {
5275 	bool result = false;
5276 
5277 	if (n != -1)
5278 	{
5279 		if (CAR(n) != -1)
5280 		{
5281 			result = is_sexp_true(CAR(n));
5282 			if ( Sexp_nodes[CAR(n)].value == SEXP_KNOWN_FALSE || Sexp_nodes[CAR(n)].value == SEXP_NAN_FOREVER )
5283 				return SEXP_KNOWN_TRUE;												// not KNOWN_FALSE == KNOWN_TRUE;
5284 			else if ( Sexp_nodes[CAR(n)].value == SEXP_KNOWN_TRUE )		// not KNOWN_TRUE == KNOWN_FALSE
5285 				return SEXP_KNOWN_FALSE;
5286 			else if ( Sexp_nodes[CAR(n)].value == SEXP_NAN )				// not NAN == TRUE (I think)
5287 				return SEXP_TRUE;
5288 		}
5289 		// this should never happen, because all arguments which return logical values are operators
5290 		else
5291 			result = (sexp_atoi(n) != 0);
5292 	}
5293 
5294 	return result ? SEXP_FALSE : SEXP_TRUE;
5295 }
5296 
sexp_xor(int n)5297 int sexp_xor(int n)
5298 {
5299 	bool result;
5300 	int num_true = 0, num_known_true = 0, num_known_false = 0, num_args = 0;
5301 
5302 	if (n != -1)
5303 	{
5304 		if (CAR(n) != -1)
5305 		{
5306 			num_args++;
5307 
5308 			result = is_sexp_true(CAR(n));
5309 			if (Sexp_nodes[CAR(n)].value == SEXP_KNOWN_TRUE)
5310 				num_known_true++;
5311 			else if (Sexp_nodes[CAR(n)].value == SEXP_KNOWN_FALSE || Sexp_nodes[CAR(n)].value == SEXP_NAN_FOREVER)
5312 				num_known_false++;
5313 			else if (result)
5314 				num_true++;
5315 		}
5316 		// this should never happen, because all arguments which return logical values are operators
5317 		else
5318 			result = (sexp_atoi(n) != 0);
5319 
5320 		while (CDR(n) != -1)
5321 		{
5322 			num_args++;
5323 
5324 			result = is_sexp_true(CDR(n));
5325 			if (Sexp_nodes[CDR(n)].value == SEXP_KNOWN_TRUE)
5326 				num_known_true++;
5327 			else if (Sexp_nodes[CDR(n)].value == SEXP_KNOWN_FALSE || Sexp_nodes[CDR(n)].value == SEXP_NAN_FOREVER)
5328 				num_known_false++;
5329 			else if (result)
5330 				num_true++;
5331 
5332 			n = CDR(n);
5333 		}
5334 	}
5335 
5336 	if (num_known_true + num_known_false == num_args)
5337 	{
5338 		if (num_known_true == 1)
5339 			return SEXP_KNOWN_TRUE;
5340 		else
5341 			return SEXP_KNOWN_FALSE;
5342 	}
5343 
5344 	if (num_true + num_known_true == 1)
5345 		return SEXP_TRUE;
5346 	else
5347 		return SEXP_FALSE;
5348 }
5349 
5350 // Goober5000
sexp_number_compare(int n,int op)5351 int sexp_number_compare(int n, int op)
5352 {
5353 	int first_node = n;
5354 	int current_node;
5355 	int first_number = eval_sexp(first_node);
5356 	int current_number;
5357 
5358 	// bail on NANs
5359 	if (CAR(first_node) != -1)
5360 	{
5361 		if (Sexp_nodes[CAR(first_node)].value == SEXP_NAN) return SEXP_FALSE;
5362 		if (Sexp_nodes[CAR(first_node)].value == SEXP_NAN_FOREVER) return SEXP_KNOWN_FALSE;
5363 	}
5364 	if (CDR(first_node) != -1)
5365 	{
5366 		if (Sexp_nodes[CDR(first_node)].value == SEXP_NAN) return SEXP_FALSE;
5367 		if (Sexp_nodes[CDR(first_node)].value == SEXP_NAN_FOREVER) return SEXP_KNOWN_FALSE;
5368 	}
5369 
5370 	// compare first node with each of the others
5371 	for (current_node = CDR(first_node); current_node != -1; current_node = CDR(current_node))
5372 	{
5373 		// bail on NANs
5374 		if (CAR(current_node) != -1)
5375 		{
5376 			if (Sexp_nodes[CAR(current_node)].value == SEXP_NAN) return SEXP_FALSE;
5377 			if (Sexp_nodes[CAR(current_node)].value == SEXP_NAN_FOREVER) return SEXP_KNOWN_FALSE;
5378 		}
5379 		if (CDR(current_node) != -1)
5380 		{
5381 			if (Sexp_nodes[CDR(current_node)].value == SEXP_NAN) return SEXP_FALSE;
5382 			if (Sexp_nodes[CDR(current_node)].value == SEXP_NAN_FOREVER) return SEXP_KNOWN_FALSE;
5383 		}
5384 
5385 		current_number = eval_sexp(current_node);
5386 
5387 		// must satisfy our particular operator
5388 		switch(op)
5389 		{
5390 			case OP_EQUALS:
5391 				if (first_number != current_number) return SEXP_FALSE;
5392 				break;
5393 
5394 			case OP_NOT_EQUAL:
5395 				if (first_number == current_number) return SEXP_FALSE;
5396 				break;
5397 
5398 			case OP_GREATER_THAN:
5399 				if (first_number <= current_number) return SEXP_FALSE;
5400 				break;
5401 
5402 			case OP_GREATER_OR_EQUAL:
5403 				if (first_number < current_number) return SEXP_FALSE;
5404 				break;
5405 
5406 			case OP_LESS_THAN:
5407 				if (first_number >= current_number) return SEXP_FALSE;
5408 				break;
5409 
5410 			case OP_LESS_OR_EQUAL:
5411 				if (first_number > current_number) return SEXP_FALSE;
5412 				break;
5413 
5414 			default:
5415 				Warning(LOCATION, "Unhandled comparison case!  Operator = %d", op);
5416 				break;
5417 		}
5418 	}
5419 
5420 	// it satisfies the operator for all the arguments
5421 	return SEXP_TRUE;
5422 }
5423 
5424 // Goober5000
sexp_string_compare(int n,int op)5425 int sexp_string_compare(int n, int op)
5426 {
5427 	int first_node = n;
5428 	int current_node;
5429 	int val;
5430 	auto first_string = CTEXT(first_node);
5431 
5432 	// compare first node with each of the others
5433 	for (current_node = CDR(first_node); current_node != -1; current_node = CDR(current_node))
5434 	{
5435 		val = strcmp(first_string, CTEXT(current_node));
5436 
5437 		// must satisfy our particular operator
5438 		switch(op)
5439 		{
5440 			case OP_STRING_EQUALS:
5441 				if (val != 0) return SEXP_FALSE;
5442 				break;
5443 
5444 			case OP_STRING_GREATER_THAN:
5445 				if (val <= 0) return SEXP_FALSE;
5446 				break;
5447 
5448 			case OP_STRING_LESS_THAN:
5449 				if (val >= 0) return SEXP_FALSE;
5450 				break;
5451 		}
5452 	}
5453 
5454 	// it satisfies the operator for all the arguments
5455 	return SEXP_TRUE;
5456 }
5457 
5458 #define OSWPT_TYPE_NONE				0
5459 #define OSWPT_TYPE_SHIP				1
5460 #define OSWPT_TYPE_WING				2
5461 #define OSWPT_TYPE_WAYPOINT			3
5462 #define OSWPT_TYPE_SHIP_ON_TEAM		4	// e.g. <any friendly>
5463 #define OSWPT_TYPE_WHOLE_TEAM		5	// e.g. Friendly
5464 #define OSWPT_TYPE_PARSE_OBJECT		6	// a "ship" that hasn't arrived yet
5465 #define OSWPT_TYPE_EXITED			7
5466 #define OSWPT_TYPE_WING_NOT_PRESENT	8	// a wing that hasn't arrived yet or is between waves
5467 
5468 // Goober5000
5469 struct object_ship_wing_point_team
5470 {
5471 	const char *object_name = nullptr;
5472 	int type = OSWPT_TYPE_NONE;
5473 
5474 	const ship_registry_entry *ship_entry = nullptr;
5475 	object *objp = nullptr;
5476 	wing *wingp = nullptr;
5477 	waypoint *waypointp = nullptr;
5478 	int team = -1;
5479 
5480 	object_ship_wing_point_team() = default;
5481 
object_ship_wing_point_teamobject_ship_wing_point_team5482 	object_ship_wing_point_team(ship *sp)
5483 		: object_name(sp->ship_name), type(OSWPT_TYPE_SHIP), objp(&Objects[sp->objnum])
5484 	{
5485 		ship_entry = ship_registry_get(sp->ship_name);
5486 		if (ship_entry->status == EXITED)
5487 		{
5488 			type = OSWPT_TYPE_EXITED;
5489 		}
5490 	}
5491 
object_ship_wing_point_teamobject_ship_wing_point_team5492 	object_ship_wing_point_team(p_object *pop)
5493 		: object_name(pop->name), type(OSWPT_TYPE_PARSE_OBJECT)
5494 	{
5495 		ship_entry = ship_registry_get(pop->name);
5496 	}
5497 
object_ship_wing_point_teamobject_ship_wing_point_team5498 	object_ship_wing_point_team(ship_obj *sop)
5499 		: object_ship_wing_point_team(&Ships[Objects[sop->objnum].instance])
5500 	{}
5501 
object_ship_wing_point_teamobject_ship_wing_point_team5502 	object_ship_wing_point_team(wing *wp)
5503 		: object_name(wp->name), wingp(wp)
5504 	{
5505 		if (wingp->current_count > 0)
5506 			type = OSWPT_TYPE_WING;
5507 		else
5508 			type = OSWPT_TYPE_WING_NOT_PRESENT;
5509 
5510 		// point to wing leader if he is valid
5511 		if ((wingp->special_ship >= 0) && (wingp->ship_index[wingp->special_ship] >= 0))
5512 		{
5513 			objp = &Objects[Ships[wingp->ship_index[wingp->special_ship]].objnum];
5514 		}
5515 		// boo... well, just point to ship at index 0
5516 		else
5517 		{
5518 			objp = &Objects[Ships[wingp->ship_index[0]].objnum];
5519 			Warning(LOCATION, "Substituting ship '%s' at index 0 for nonexistent wing leader at index %d!", Ships[objp->instance].ship_name, wingp->special_ship);
5520 		}
5521 	}
5522 
clearobject_ship_wing_point_team5523 	void clear()
5524 	{
5525 		object_name = nullptr;
5526 		type = OSWPT_TYPE_NONE;
5527 
5528 		ship_entry = nullptr;
5529 		objp = nullptr;
5530 		wingp = nullptr;
5531 		waypointp = nullptr;
5532 		team = -1;
5533 	}
5534 };
5535 
5536 // Goober5000
eval_object_ship_wing_point_team(object_ship_wing_point_team * oswpt,int node,const char * ctext_override=nullptr)5537 void eval_object_ship_wing_point_team(object_ship_wing_point_team *oswpt, int node, const char *ctext_override = nullptr)
5538 {
5539 	const ship_registry_entry *ship_entry = nullptr;
5540 	wing *wingp = nullptr;
5541 	const char *object_name = nullptr;
5542 
5543 	Assert(oswpt != nullptr);
5544 	Assert(node >= 0 || ctext_override);
5545 
5546 	oswpt->clear();
5547 
5548 	// this is mainly for multiplayer, where you can't cache something over the network
5549 	if (ctext_override)
5550 	{
5551 		ship_entry = ship_registry_get(ctext_override);
5552 		if (!ship_entry)
5553 		{
5554 			int wingnum = wing_lookup(ctext_override);
5555 			if (wingnum >= 0)
5556 				wingp = &Wings[wingnum];
5557 			else
5558 			{
5559 				object_name = ctext_override;
5560 			}
5561 		}
5562 	}
5563 	// check caching
5564 	else if (Sexp_nodes[node].cache)
5565 	{
5566 		if (Sexp_nodes[node].cache->sexp_node_data_type == OPF_SHIP)
5567 		{
5568 			ship_entry = &Ship_registry[Sexp_nodes[node].cache->ship_registry_index];
5569 		}
5570 		else if (Sexp_nodes[node].cache->sexp_node_data_type == OPF_WING)
5571 		{
5572 			wingp = static_cast<wing*>(Sexp_nodes[node].cache->pointer);
5573 		}
5574 		// TODO: other caching
5575 		else
5576 		{
5577 			Assertion(false, "Only ship and wing caching are currently handled!");
5578 		}
5579 	}
5580 	// evaluate from the node itself
5581 	else
5582 	{
5583 		// check each possibility
5584 		ship_entry = eval_ship(node);
5585 		if (!ship_entry)
5586 		{
5587 			wingp = eval_wing(node);
5588 			if (!wingp)
5589 			{
5590 				object_name = CTEXT(node);
5591 			}
5592 		}
5593 	}
5594 
5595 	// see if this is a ship
5596 	if (ship_entry)
5597 	{
5598 		oswpt->ship_entry = ship_entry;
5599 		oswpt->object_name = ship_entry->name;
5600 		oswpt->objp = ship_entry->objp;
5601 
5602 		switch (ship_entry->status)
5603 		{
5604 			case NOT_YET_PRESENT:
5605 				oswpt->type = OSWPT_TYPE_PARSE_OBJECT;
5606 				break;
5607 
5608 			case PRESENT:
5609 				oswpt->type = OSWPT_TYPE_SHIP;
5610 				break;
5611 
5612 			case EXITED:
5613 				oswpt->type = OSWPT_TYPE_EXITED;
5614 				break;
5615 
5616 			default:
5617 				Assertion(false, "Unhandled ship status!");
5618 		}
5619 
5620 		return;
5621 	}
5622 
5623 	// see if this is a wing
5624 	if (wingp)
5625 	{
5626 		oswpt->wingp = wingp;
5627 		oswpt->object_name = wingp->name;
5628 
5629 		if (wingp->flags[Ship::Wing_Flags::Gone])
5630 		{
5631 			oswpt->type = OSWPT_TYPE_EXITED;
5632 		}
5633 		// make sure that at least one ship exists
5634 		else if (wingp->current_count > 0)
5635 		{
5636 			oswpt->type = OSWPT_TYPE_WING;
5637 
5638 			// point to wing leader if he is valid
5639 			if ((wingp->special_ship >= 0) && (wingp->ship_index[wingp->special_ship] >= 0))
5640 			{
5641 				oswpt->objp = &Objects[Ships[wingp->ship_index[wingp->special_ship]].objnum];
5642 			}
5643 			// boo... well, just point to ship at index 0
5644 			else
5645 			{
5646 				oswpt->objp = &Objects[Ships[wingp->ship_index[0]].objnum];
5647 				Warning(LOCATION, "Substituting ship '%s' at index 0 for nonexistent wing leader at index %d!", Ships[oswpt->objp->instance].ship_name, wingp->special_ship);
5648 			}
5649 		}
5650 		// it's still a valid wing even if nobody is here
5651 		else
5652 		{
5653 			oswpt->type = OSWPT_TYPE_WING_NOT_PRESENT;
5654 		}
5655 
5656 		return;
5657 	}
5658 
5659 
5660 	// check if we have nothing
5661 	if(!stricmp(object_name, SEXP_NONE_STRING))
5662 	{
5663 		oswpt->type = OSWPT_TYPE_NONE;
5664 		return;
5665 	}
5666 
5667 
5668 	// check if we have a point for a target
5669 	auto wpt = find_matching_waypoint(object_name);
5670 	if ((wpt != nullptr) && (wpt->get_objnum() >= 0))
5671 	{
5672 		oswpt->type = OSWPT_TYPE_WAYPOINT;
5673 
5674 		oswpt->waypointp = wpt;
5675 		oswpt->objp = &Objects[wpt->get_objnum()];
5676 
5677 		return;
5678 	}
5679 
5680 
5681 	// check if we have an "<any team>" type
5682 	int team = sexp_determine_team(object_name);
5683 	if (team >= 0)
5684 	{
5685 		oswpt->type = OSWPT_TYPE_SHIP_ON_TEAM;
5686 		oswpt->team = team;
5687 		return;
5688 	}
5689 
5690 
5691 	// check if we have a whole-team type
5692 	team = iff_lookup(object_name);
5693 	if (team >= 0)
5694 	{
5695 		oswpt->type = OSWPT_TYPE_WHOLE_TEAM;
5696 		oswpt->team = team;
5697 		return;
5698 	}
5699 
5700 
5701 	// we apparently don't have anything legal
5702 	return;
5703 }
5704 
5705 /**
5706  * Return the number of ships of a given team in the area battle
5707  */
sexp_num_ships_in_battle(int n)5708 int sexp_num_ships_in_battle(int n)
5709 {
5710 	int count=0;
5711 	ship_obj	*so;
5712 
5713 	if ( n == -1) {
5714 		for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
5715 			count++;
5716 		}
5717 
5718 		return count;
5719 	}
5720 
5721 	while (n != -1) {
5722 		object_ship_wing_point_team oswpt1;
5723 		eval_object_ship_wing_point_team(&oswpt1, n);
5724 
5725 		switch (oswpt1.type) {
5726  			case OSWPT_TYPE_WHOLE_TEAM:
5727 			  for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
5728 				  auto shipp=&Ships[Objects[so->objnum].instance];
5729 				  if (shipp->team == oswpt1.team) {
5730 						 count++;
5731 					  }
5732 			  }
5733 			  break;
5734 
5735   			case OSWPT_TYPE_SHIP:
5736 			  count++;
5737 			  break;
5738 
5739 			case OSWPT_TYPE_WING:
5740 			  count += oswpt1.wingp->current_count;
5741 			  break;
5742 		}
5743 
5744 		n = CDR(n);
5745 	}
5746 
5747 	return count;
5748 }
5749 
5750 /**
5751  * Return the number of ships of a given wing or wings in the battle area
5752  */
sexp_num_ships_in_wing(int n)5753 int sexp_num_ships_in_wing(int n)
5754 {
5755 	int num_ships = 0;
5756 
5757 	// A wing name must be provided, Assert that there is one.
5758 	Assert ( n != -1 );
5759 
5760 	//Cycle through the list of ships given
5761 	while (n != -1)
5762 	{
5763 		// Get the name of the wing
5764 		auto wingp = eval_wing(n);
5765 
5766 		//If the wing exists add the number of ships in it to the total
5767 		if (wingp)
5768 		{
5769 			num_ships += wingp->current_count;
5770 		}
5771 
5772 		//get the next node
5773 		n = CDR (n) ;
5774 	}
5775 
5776 	return num_ships ;
5777 }
5778 
5779 /**
5780  * Gets the 'real' speed of an object, taking into account docking
5781  */
sexp_get_real_speed(object * obj)5782 int sexp_get_real_speed(object *obj)
5783 {
5784 	return fl2i(dock_calc_docked_speed(obj));
5785 }
5786 
5787 /**
5788  * Gets the current speed of the specified object
5789  *
5790  * Uses a lot of code shamelessly ripped from get_object_coordinates
5791  */
sexp_current_speed(int n)5792 int sexp_current_speed(int n)
5793 {
5794 	object_ship_wing_point_team oswpt;
5795 	eval_object_ship_wing_point_team(&oswpt, n);
5796 
5797 	switch (oswpt.type)
5798 	{
5799 		case OSWPT_TYPE_EXITED:
5800 			return SEXP_NAN;
5801 
5802 		case OSWPT_TYPE_SHIP:
5803 		case OSWPT_TYPE_WING:
5804 			return sexp_get_real_speed(oswpt.objp);
5805 	}
5806 
5807 	return 0;
5808 }
5809 
sexp_check_objective_delay(int delay_node,int objective_node,int (* objective_function)(int,fix *))5810 int sexp_check_objective_delay(int delay_node, int objective_node, int(*objective_function)(int, fix*))
5811 {
5812 	fix delay, time;
5813 	int val;
5814 	bool is_nan, is_nan_forever;
5815 
5816 	time = 0;
5817 
5818 	delay = i2f(eval_num(delay_node, is_nan, is_nan_forever));
5819 	if (is_nan)
5820 		return SEXP_FALSE;
5821 	if (is_nan_forever)
5822 		return SEXP_KNOWN_FALSE;
5823 
5824 	// check value of function
5825 	val = objective_function(objective_node, &time);
5826 
5827 	if ((val == SEXP_TRUE) || (val == SEXP_KNOWN_TRUE))
5828 	{
5829 		if ((Missiontime - time) >= delay)
5830 			return val;
5831 		else
5832 			return SEXP_FALSE;
5833 	}
5834 
5835 	return val;
5836 }
5837 
5838 /**
5839  * Evaluate if given ship is destroyed.
5840  * @return true if the ship in the expression has been destroyed.
5841  */
sexp_is_destroyed(int n,fix * latest_time)5842 int sexp_is_destroyed(int n, fix *latest_time)
5843 {
5844 	int	count, num_destroyed, wing_index;
5845 	bool has_been_destroyed;
5846 	fix	time = 0;
5847 
5848 	count = 0;
5849 	num_destroyed = 0;
5850 	wing_index = -1;
5851 	while (n != -1)
5852 	{
5853 		++count;
5854 
5855 		auto ship_entry = eval_ship(n);
5856 		wing *wingp = nullptr;
5857 		has_been_destroyed = false;
5858 
5859 		// this might be a ship
5860 		if (ship_entry)
5861 		{
5862 			if (ship_entry->status == ShipStatus::NOT_YET_PRESENT)
5863 				return SEXP_CANT_EVAL;
5864 
5865 			if (ship_entry->status == ShipStatus::EXITED)
5866 			{
5867 				// check the mission log
5868 				if (mission_log_get_time(LOG_SHIP_DESTROYED, ship_entry->name, nullptr, &time) || mission_log_get_time(LOG_SELF_DESTRUCTED, ship_entry->name, nullptr, &time))
5869 				{
5870 					has_been_destroyed = true;
5871 				}
5872 				// exited without being destroyed
5873 				else
5874 				{
5875 					return SEXP_KNOWN_FALSE;
5876 				}
5877 			}
5878 		}
5879 		else
5880 			wingp = eval_wing(n);
5881 
5882 		// this might be a wing
5883 		if (wingp)
5884 		{
5885 			if (wing_has_yet_to_arrive(wingp))
5886 				return SEXP_CANT_EVAL;
5887 
5888 			if (wingp->flags[Ship::Wing_Flags::Gone])
5889 			{
5890 				// check the mission log
5891 				if (mission_log_get_time(LOG_WING_DESTROYED, wingp->name, nullptr, &time))
5892 				{
5893 					has_been_destroyed = true;
5894 				}
5895 				// exited without being destroyed
5896 				else
5897 				{
5898 					return SEXP_KNOWN_FALSE;
5899 				}
5900 			}
5901 		}
5902 
5903 		if (has_been_destroyed)
5904 		{
5905 			++num_destroyed;
5906 			if (latest_time && (time > *latest_time))
5907 				*latest_time = time;
5908 		}
5909 		// at this point we assume the ship or wing is in-mission, even if no ship or wing actually exists
5910 		else
5911 		{
5912 			// If a previous SEXP already had an empty wing then this code would expose the internal value as the
5913 			// directive count. Instead, we reset the count to zero here to make sure that the wing or ship count is
5914 			// correct.
5915 			if (Directive_count == DIRECTIVE_WING_ZERO)
5916 			{
5917 #ifndef NDEBUG
5918 				static bool wing_zero_warning_shown = false;
5919 				if (!wing_zero_warning_shown)
5920 				{
5921 					mprintf(("SEXP: is-destroyed-delay was used multiple times in a directive event! This might have "
5922 						"unintended effects and should be replaced by a single use of is-destroyed-delay.\n"));
5923 					wing_zero_warning_shown = true;
5924 				}
5925 #endif
5926 				Directive_count = 0;
5927 			}
5928 
5929 			// ship or wing isn't destroyed -- add to directive count
5930 			if (wingp)
5931 			{
5932 				wing_index = static_cast<int>(wingp - Wings);
5933 				Directive_count += Wings[wing_index].current_count;
5934 			}
5935 			else
5936 				++Directive_count;
5937 		}
5938 
5939 		// move to next ship/wing in list
5940 		n = CDR(n);
5941 	}
5942 
5943 	// special case to mark a directive for destroy wing objectives true after a short amount
5944 	// of time when there are more waves for this wing.
5945 	if ( (count == 1) && (wing_index >= 0) && (Directive_count == 0) ) {
5946 		if ( Wings[wing_index].current_wave < Wings[wing_index].num_waves )
5947 			Directive_count =	DIRECTIVE_WING_ZERO;
5948 	}
5949 
5950 	if ( count == num_destroyed )
5951 		return SEXP_KNOWN_TRUE;
5952 	else
5953 		return SEXP_FALSE;
5954 }
5955 
sexp_is_destroyed_delay(int n)5956 int sexp_is_destroyed_delay(int n)
5957 {
5958 	return sexp_check_objective_delay(n, CDR(n), sexp_is_destroyed);
5959 }
5960 
5961 /**
5962  * Return true if the subsystem of the given ship has been destroyed
5963  */
sexp_is_subsystem_destroyed(int n,fix * time)5964 int sexp_is_subsystem_destroyed(int n, fix *time)
5965 {
5966 	auto ship_entry = eval_ship(n);
5967 	if (!ship_entry)
5968 		return SEXP_FALSE;
5969 	if (ship_entry->status == ShipStatus::NOT_YET_PRESENT)
5970 		return SEXP_CANT_EVAL;
5971 
5972 	auto subsys_name = CTEXT(CDR(n));
5973 
5974 	if ( mission_log_get_time(LOG_SHIP_SUBSYS_DESTROYED, ship_entry->name, subsys_name, time) )
5975 		return SEXP_KNOWN_TRUE;
5976 
5977 	// if the ship has exited, no way to destroy its subsystem.
5978 	if (ship_entry->status == ShipStatus::EXITED)
5979 		return SEXP_KNOWN_FALSE;
5980 
5981 	return SEXP_FALSE;
5982 }
5983 
sexp_is_subsystem_destroyed_delay(int n)5984 int sexp_is_subsystem_destroyed_delay(int n)
5985 {
5986 	return sexp_check_objective_delay(CDDR(n), n, sexp_is_subsystem_destroyed);
5987 }
5988 
5989 /**
5990  * Determine if a ship has arrived onto the scene
5991  */
sexp_has_arrived(int n,fix * latest_time)5992 int sexp_has_arrived(int n, fix *latest_time)
5993 {
5994 	int	count, num_arrived;
5995 	bool has_arrived;
5996 	fix	time = 0;
5997 
5998 	count = 0;
5999 	num_arrived = 0;
6000 	while ( n != -1 )
6001 	{
6002 		++count;
6003 
6004 		auto ship_entry = eval_ship(n);
6005 		wing *wingp = nullptr;
6006 		has_arrived = false;
6007 
6008 		// this might be a ship
6009 		if (ship_entry)
6010 		{
6011 			if (ship_entry->status != ShipStatus::NOT_YET_PRESENT)
6012 			{
6013 				// check the mission log
6014 				if (mission_log_get_time(LOG_SHIP_ARRIVED, ship_entry->name, nullptr, &time))
6015 					has_arrived = true;
6016 			}
6017 		}
6018 		else
6019 			wingp = eval_wing(n);
6020 
6021 		// this might be a wing
6022 		if (wingp)
6023 		{
6024 			if (!wing_has_yet_to_arrive(wingp))
6025 			{
6026 				// check the mission log
6027 				if (mission_log_get_time(LOG_WING_ARRIVED, wingp->name, nullptr, &time))
6028 					has_arrived = true;
6029 			}
6030 		}
6031 
6032 		if (has_arrived)
6033 		{
6034 			++num_arrived;
6035 			if (latest_time && (time > *latest_time))
6036 				*latest_time = time;
6037 		}
6038 
6039 		// move to next ship/wing in list
6040 		n = CDR(n);
6041 	}
6042 
6043 	if ( count == num_arrived )
6044 		return SEXP_KNOWN_TRUE;
6045 	else
6046 		return SEXP_FALSE;
6047 }
6048 
sexp_has_arrived_delay(int n)6049 int sexp_has_arrived_delay(int n)
6050 {
6051 	return sexp_check_objective_delay(n, CDR(n), sexp_has_arrived);
6052 }
6053 
6054 /**
6055  * Determine if a ship/wing has departed
6056  */
sexp_has_departed(int n,fix * latest_time)6057 int sexp_has_departed(int n, fix *latest_time)
6058 {
6059 	int count, num_departed;
6060 	bool has_departed;
6061 	fix time = 0;
6062 
6063 	count = 0;
6064 	num_departed = 0;
6065 	while ( n != -1 )
6066 	{
6067 		++count;
6068 
6069 		auto ship_entry = eval_ship(n);
6070 		wing *wingp = nullptr;
6071 		has_departed = false;
6072 
6073 		// this might be a ship
6074 		if (ship_entry)
6075 		{
6076 			if (ship_entry->status == ShipStatus::NOT_YET_PRESENT)
6077 				return SEXP_CANT_EVAL;
6078 
6079 			if (ship_entry->status == ShipStatus::EXITED)
6080 			{
6081 				// check the mission log
6082 				if (mission_log_get_time(LOG_SHIP_DEPARTED, ship_entry->name, nullptr, &time))
6083 				{
6084 					has_departed = true;
6085 				}
6086 				// exited without departing
6087 				else
6088 				{
6089 					return SEXP_KNOWN_FALSE;
6090 				}
6091 			}
6092 		}
6093 		else
6094 			wingp = eval_wing(n);
6095 
6096 		// this might be a wing
6097 		if (wingp)
6098 		{
6099 			if (wing_has_yet_to_arrive(wingp))
6100 				return SEXP_CANT_EVAL;
6101 
6102 			if (wingp->flags[Ship::Wing_Flags::Gone])
6103 			{
6104 				// check the mission log
6105 				if (mission_log_get_time(LOG_WING_DEPARTED, wingp->name, nullptr, &time))
6106 				{
6107 					has_departed = true;
6108 				}
6109 				// exited without departing
6110 				else
6111 				{
6112 					return SEXP_KNOWN_FALSE;
6113 				}
6114 			}
6115 		}
6116 
6117 		if (has_departed)
6118 		{
6119 			++num_departed;
6120 			if (latest_time && (time > *latest_time))
6121 				*latest_time = time;
6122 		}
6123 
6124 		// move to next ship/wing in list
6125 		n = CDR(n);
6126 	}
6127 
6128 	if ( count == num_departed )
6129 		return SEXP_KNOWN_TRUE;
6130 	else
6131 		return SEXP_FALSE;
6132 }
6133 
sexp_has_departed_delay(int n)6134 int sexp_has_departed_delay(int n)
6135 {
6136 	return sexp_check_objective_delay(n, CDR(n), sexp_has_departed);
6137 }
6138 
6139 /**
6140  * Determine if a ship is disabled or disarmed, but not both
6141  */
sexp_is_disabled_xor_disarmed(int n,bool check_disabled,fix * latest_time)6142 int sexp_is_disabled_xor_disarmed(int n, bool check_disabled, fix *latest_time)
6143 {
6144 	int count, num_satisfied;
6145 	fix time;
6146 
6147 	count = 0;
6148 	num_satisfied = 0;
6149 	while ( n != -1 )
6150 	{
6151 		++count;
6152 
6153 		auto ship_entry = eval_ship(n);
6154 		if (!ship_entry)
6155 			return SEXP_FALSE;
6156 		if (ship_entry->status == ShipStatus::NOT_YET_PRESENT)
6157 			return SEXP_CANT_EVAL;
6158 
6159 		// check mission log
6160 		if (mission_log_get_time(check_disabled ? LOG_SHIP_DISABLED : LOG_SHIP_DISARMED, ship_entry->name, nullptr, &time))
6161 		{
6162 			++num_satisfied;
6163 			if (latest_time && (time > *latest_time))
6164 				*latest_time = time;
6165 		}
6166 		// if the ship has exited, no way to destroy its subsystem.
6167 		else if (ship_entry->status == ShipStatus::EXITED)
6168 			return SEXP_KNOWN_FALSE;
6169 
6170 		// move to next ship in list
6171 		n = CDR(n);
6172 	}
6173 
6174 	if ( count == num_satisfied )
6175 		return SEXP_KNOWN_TRUE;
6176 	else
6177 		return SEXP_FALSE;
6178 }
6179 
sexp_is_disabled_xor_disarmed_delay(int n,bool check_disabled)6180 int sexp_is_disabled_xor_disarmed_delay(int n, bool check_disabled)
6181 {
6182 	// since we can't use captured-value lambdas as function pointers, we need a stateless lambda for each value of the variable
6183 	if (check_disabled)
6184 		return sexp_check_objective_delay(n, CDR(n), [](int _n, fix *_time)->int { return sexp_is_disabled_xor_disarmed(_n, true, _time); });
6185 	else
6186 		return sexp_check_objective_delay(n, CDR(n), [](int _n, fix *_time)->int { return sexp_is_disabled_xor_disarmed(_n, false, _time); });
6187 }
6188 
6189 /**
6190  * Determine if a ship or wing is done flying waypoints
6191  */
sexp_are_waypoints_done(int n,fix * time,int count=1)6192 int sexp_are_waypoints_done(int n, fix *time, int count = 1)
6193 {
6194 	auto ship_entry = eval_ship(n);
6195 	if (ship_entry)
6196 	{
6197 		if (ship_entry->status == ShipStatus::NOT_YET_PRESENT)
6198 			return SEXP_CANT_EVAL;
6199 
6200 		auto waypoint_name = CTEXT(CDR(n));
6201 
6202 		if (mission_log_get_time_indexed(LOG_WAYPOINTS_DONE, ship_entry->name, waypoint_name, count, time))
6203 			return SEXP_KNOWN_TRUE;
6204 
6205 		// if the ship has exited, no way to complete waypoints
6206 		if (ship_entry->status == ShipStatus::EXITED)
6207 			return SEXP_KNOWN_FALSE;
6208 
6209 		return SEXP_FALSE;
6210 	}
6211 
6212 	auto wingp = eval_wing(n);
6213 	if (wingp)
6214 	{
6215 		if (wing_has_yet_to_arrive(wingp))
6216 			return SEXP_CANT_EVAL;
6217 
6218 		auto waypoint_name = CTEXT(CDR(n));
6219 
6220 		if (mission_log_get_time_indexed(LOG_WAYPOINTS_DONE, wingp->name, waypoint_name, count, time))
6221 			return SEXP_KNOWN_TRUE;
6222 
6223 		// if the wing is gone, no way to complete waypoints
6224 		if (wingp->flags[Ship::Wing_Flags::Gone])
6225 			return SEXP_KNOWN_FALSE;
6226 
6227 		return SEXP_FALSE;
6228 	}
6229 
6230 	// neither a ship nor a wing
6231 	return SEXP_FALSE;
6232 }
6233 
6234 // since we can't use lambda-captures as function pointers (see also disabled_xor_disarmed), we need a lot of code duplication from sexp_check_objective_delay
sexp_are_waypoints_done_delay(int n)6235 int sexp_are_waypoints_done_delay(int n)
6236 {
6237 	int delay_node = CDDR(n), objective_node = n, count_node = CDDDR(n);
6238 	fix delay, time;
6239 	int count, val;
6240 	bool is_nan, is_nan_forever;
6241 
6242 	time = 0;
6243 
6244 	delay = i2f(eval_num(delay_node, is_nan, is_nan_forever));
6245 	if (is_nan)
6246 		return SEXP_FALSE;
6247 	if (is_nan_forever)
6248 		return SEXP_KNOWN_FALSE;
6249 
6250 	if (count_node >= 0)
6251 	{
6252 		count = i2f(eval_num(count_node, is_nan, is_nan_forever));
6253 		if (is_nan)
6254 			return SEXP_FALSE;
6255 		if (is_nan_forever)
6256 			return SEXP_KNOWN_FALSE;
6257 
6258 		if (count <= 0)
6259 		{
6260 			Warning(LOCATION, "Are-waypoints-done-delay count should be at least 1!  This has been automatically adjusted.");
6261 			count = 1;
6262 		}
6263 	}
6264 	else
6265 		count = 1;
6266 
6267 	// check value of function
6268 	val = sexp_are_waypoints_done(objective_node, &time, count);
6269 
6270 	if ((val == SEXP_TRUE) || (val == SEXP_KNOWN_TRUE))
6271 	{
6272 		if ((Missiontime - time) >= delay)
6273 			return val;
6274 		else
6275 			return SEXP_FALSE;
6276 	}
6277 
6278 	return val;
6279 }
6280 
6281 // First ship is the destroyer, rest of the arguments are the destroyed ships.
sexp_was_destroyed_by(int n,fix * latest_time)6282 int sexp_was_destroyed_by(int n, fix *latest_time)
6283 {
6284 	int count, num_destroyed;
6285 	fix time;
6286 
6287 	auto ship_entry = eval_ship(n);
6288 	if (!ship_entry)
6289 		return SEXP_FALSE;
6290 	if (ship_entry->status == ShipStatus::NOT_YET_PRESENT)
6291 		return SEXP_CANT_EVAL;
6292 
6293 	count = 0;
6294 	num_destroyed = 0;
6295 	for (n = CDR(n); n != -1; n = CDR(n))
6296 	{
6297 		++count;
6298 
6299 		auto destroyed_ship_entry = eval_ship(n);
6300 		if (!destroyed_ship_entry)
6301 			return SEXP_FALSE;
6302 		if (destroyed_ship_entry->status == ShipStatus::NOT_YET_PRESENT)
6303 			return SEXP_CANT_EVAL;
6304 
6305 		// check the mission log
6306 		if (mission_log_get_time(LOG_SHIP_DESTROYED, destroyed_ship_entry->name, ship_entry->name, &time))
6307 		{
6308 			++num_destroyed;
6309 			if (latest_time && (time > *latest_time))
6310 				*latest_time = time;
6311 		}
6312 		// if the ship has exited, no way to destroy it
6313 		else if (ship_entry->status == ShipStatus::EXITED)
6314 			return SEXP_KNOWN_FALSE;
6315 	}
6316 
6317 	if (count == num_destroyed)
6318 		return SEXP_KNOWN_TRUE;
6319 	else
6320 		return SEXP_FALSE;
6321 }
6322 
sexp_was_destroyed_by_delay(int n)6323 int sexp_was_destroyed_by_delay(int n)
6324 {
6325 	return sexp_check_objective_delay(n, CDR(n), sexp_was_destroyed_by);
6326 }
6327 
sexp_has_docked_or_undocked(int n,int op_num)6328 int sexp_has_docked_or_undocked(int n, int op_num)
6329 {
6330 	bool is_nan, is_nan_forever;
6331 	Assert(op_num == OP_HAS_DOCKED || op_num == OP_HAS_UNDOCKED || op_num == OP_HAS_DOCKED_DELAY || op_num == OP_HAS_UNDOCKED_DELAY);
6332 
6333 	auto docker = eval_ship(n);
6334 	if (!docker)
6335 		return SEXP_FALSE;
6336 	if (docker->status == ShipStatus::NOT_YET_PRESENT)
6337 		return SEXP_CANT_EVAL;
6338 	n = CDR(n);
6339 
6340 	auto dockee = eval_ship(n);
6341 	if (!dockee)
6342 		return SEXP_FALSE;
6343 	if (dockee->status == ShipStatus::NOT_YET_PRESENT)
6344 		return SEXP_CANT_EVAL;
6345 	n = CDR(n);
6346 
6347 	// count of times that we should look for
6348 	int count = eval_num(n, is_nan, is_nan_forever);
6349 	if (is_nan)
6350 		return SEXP_FALSE;
6351 	if (is_nan_forever)
6352 		return SEXP_KNOWN_FALSE;
6353 	n = CDR(n);
6354 
6355 	if (count <= 0)
6356 	{
6357 		Warning(LOCATION, "Has-%sdocked%s count should be at least 1!  This has been automatically adjusted.", (op_num == OP_HAS_UNDOCKED || op_num == OP_HAS_UNDOCKED_DELAY ? "un" : ""), (op_num == OP_HAS_DOCKED_DELAY || op_num == OP_HAS_UNDOCKED_DELAY ? "-delay" : ""));
6358 		count = 1;
6359 	}
6360 
6361 	if (op_num == OP_HAS_DOCKED_DELAY || op_num == OP_HAS_UNDOCKED_DELAY)
6362 	{
6363 		fix delay = i2f(eval_num(n, is_nan, is_nan_forever));
6364 		fix time;
6365 
6366 		if (is_nan)
6367 			return SEXP_FALSE;
6368 		if (is_nan_forever)
6369 			return SEXP_KNOWN_FALSE;
6370 
6371 		if (mission_log_get_time_indexed(op_num == OP_HAS_DOCKED_DELAY ? LOG_SHIP_DOCKED : LOG_SHIP_UNDOCKED, docker->name, dockee->name, count, &time))
6372 		{
6373 			if ((Missiontime - time) >= delay)
6374 				return SEXP_KNOWN_TRUE;
6375 		}
6376 		// if either ship has exited, no way to dock
6377 		else if (docker->status == ShipStatus::EXITED || dockee->status == ShipStatus::EXITED)
6378 			return SEXP_KNOWN_FALSE;
6379 	}
6380 	else
6381 	{
6382 		if (mission_log_get_time_indexed(op_num == OP_HAS_DOCKED ? LOG_SHIP_DOCKED : LOG_SHIP_UNDOCKED, docker->name, dockee->name, count, nullptr))
6383 		{
6384 			return SEXP_KNOWN_TRUE;
6385 		}
6386 		// if either ship has exited, no way to dock
6387 		else if (docker->status == ShipStatus::EXITED || dockee->status == ShipStatus::EXITED)
6388 			return SEXP_KNOWN_FALSE;
6389 	}
6390 
6391 	return SEXP_FALSE;
6392 }
6393 
6394 /**
6395  * Determine is all of a given ship type are destroyed
6396  */
sexp_ship_type_destroyed(int n)6397 int sexp_ship_type_destroyed(int n)
6398 {
6399 	bool is_nan, is_nan_forever;
6400 
6401 	int percent = eval_num(n, is_nan, is_nan_forever);
6402 	if (is_nan)
6403 		return SEXP_FALSE;
6404 	if (is_nan_forever)
6405 		return SEXP_KNOWN_FALSE;
6406 
6407 	auto shiptype = CTEXT(CDR(n));
6408 
6409 	int type = ship_type_name_lookup(shiptype);
6410 
6411 	// bogus if we reach the end of this array!!!!
6412 	if ( type < 0 ) {
6413 		Warning(LOCATION, "Invalid shiptype passed to ship-type-destroyed");
6414 		return SEXP_FALSE;
6415 	}
6416 
6417 	if ( type >= (int)Ship_type_counts.size() || Ship_type_counts[type].total == 0 )
6418 		return SEXP_FALSE;
6419 
6420 	//We are safe from array indexing probs b/c of previous if.
6421 	// determine if the percentage of killed/total is >= percentage given in the expression
6422 	if ( (Ship_type_counts[type].killed * 100 / Ship_type_counts[type].total) >= percent)
6423 		return SEXP_KNOWN_TRUE;
6424 
6425 	return SEXP_FALSE;
6426 }
6427 
6428 // following are time based functions
sexp_has_time_elapsed(int n)6429 int sexp_has_time_elapsed(int n)
6430 {
6431 	bool is_nan, is_nan_forever;
6432 	int time = eval_num(n, is_nan, is_nan_forever);
6433 	if (is_nan)
6434 		return SEXP_FALSE;
6435 	if (is_nan_forever)
6436 		return SEXP_KNOWN_FALSE;
6437 
6438 	if ( f2i(Missiontime) >= time )
6439 		return SEXP_KNOWN_TRUE;
6440 
6441 	return SEXP_FALSE;
6442 }
6443 
6444 /**
6445  * Returns the time into the mission
6446  */
sexp_mission_time()6447 int sexp_mission_time()
6448 {
6449 	return f2i(Missiontime);
6450 }
6451 
6452 /**
6453  * Returns the time into the mission, in milliseconds
6454  */
sexp_mission_time_msecs()6455 int sexp_mission_time_msecs()
6456 {
6457 	// multiplying by 1000 can go over the limit for LONG_MAX so cast to long long int first
6458 	auto mission_time = (std::int64_t) Missiontime;
6459 	// This hack is necessary since fix is a 32-bit integer which would overflow if f2i would be used
6460 	return (int)((mission_time * 1000) / 65536);
6461 }
6462 
6463 /**
6464  * Returns percent of length of distance to special warpout plane
6465  */
sexp_special_warp_dist(int n)6466 int sexp_special_warp_dist( int n)
6467 {
6468 	vec3d center_pos, actual_local_center;
6469 	float half_length, dist_to_plane;
6470 
6471 	// get ship
6472 	auto ship_entry = eval_ship(n);
6473 
6474 	// ships which aren't present get NAN
6475 	if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT)
6476 		return SEXP_NAN;
6477 	if (ship_entry->status == ShipStatus::EXITED)
6478 		return SEXP_NAN_FOREVER;
6479 
6480 	auto shipp = ship_entry->shipp;
6481 
6482 	// check that ship has warpout_objnum
6483 	if (shipp->special_warpout_objnum < 0 || shipp->special_warpout_objnum >= MAX_OBJECTS) {
6484 		return SEXP_NAN;
6485 	}
6486 
6487 	auto sip = &Ship_info[shipp->ship_info_index];
6488 	auto objp = ship_entry->objp;
6489 	object *knossos_objp = &Objects[shipp->special_warpout_objnum];
6490 
6491 	// check the special warpout device is valid
6492 	if (knossos_objp->type != OBJ_SHIP || !Ship_info[Ships[knossos_objp->instance].ship_info_index].flags[Ship::Info_Flags::Knossos_device]) {
6493 		return SEXP_NAN;
6494 	}
6495 
6496 	// check if within 45 degree half-angle cone of facing
6497 	float dot = fl_abs(vm_vec_dot(&knossos_objp->orient.vec.fvec, &objp->orient.vec.fvec));
6498 	if (dot < 0.707f) {
6499 		return SEXP_NAN;
6500 	}
6501 
6502 	// determine the correct center of the model (which may not be the model's origin)
6503 	if (object_is_docked(objp))
6504 		dock_calc_docked_actual_center(&actual_local_center, objp);
6505 	else
6506 		ship_class_get_actual_center(sip, &actual_local_center);
6507 
6508 	// find world position of the center of the ship assembly
6509 	vm_vec_unrotate(&center_pos, &actual_local_center, &objp->orient);
6510 	vm_vec_add2(&center_pos, &objp->pos);
6511 
6512 	// determine the half-length
6513 	if (object_is_docked(objp))
6514 	{
6515 		// we need to get the longitudinal radius of our ship, so find the semilatus rectum along the Z-axis
6516 		half_length = dock_calc_max_semilatus_rectum_parallel_to_axis(objp, Z_AXIS);
6517 	}
6518 	else
6519 		half_length = 0.5f * ship_class_get_length(sip);
6520 
6521 	// get distance
6522 	dist_to_plane = fvi_ray_plane(nullptr, &knossos_objp->pos, &knossos_objp->orient.vec.fvec, &center_pos, &objp->orient.vec.fvec, 0.0f);
6523 	dist_to_plane -= half_length;
6524 
6525 	// return as a percent of length -- simplified from 100*dist/(2*half_length)
6526 	return (int) (50.0f * dist_to_plane / half_length);
6527 }
6528 
sexp_time_exited(int n,int op_num)6529 int sexp_time_exited(int n, int op_num)
6530 {
6531 	bool ship = (op_num == OP_TIME_SHIP_DESTROYED || op_num == OP_TIME_SHIP_DEPARTED);
6532 	bool destroyed = (op_num == OP_TIME_SHIP_DESTROYED || op_num == OP_TIME_WING_DESTROYED);
6533 	fix time;
6534 
6535 	if (ship)
6536 	{
6537 		auto ship_entry = eval_ship(n);
6538 		if (!ship_entry || ship_entry->status != ShipStatus::EXITED)
6539 			return SEXP_NAN;
6540 
6541 		if (destroyed)
6542 		{
6543 			if (!mission_log_get_time(LOG_SHIP_DESTROYED, ship_entry->name, nullptr, &time)
6544 				&& !mission_log_get_time(LOG_SELF_DESTRUCTED, ship_entry->name, nullptr, &time)) {		// returns 0 when not found
6545 				return SEXP_NAN_FOREVER;	// exited but not destroyed
6546 			}
6547 		}
6548 		else
6549 		{
6550 			if (!mission_log_get_time(LOG_SHIP_DEPARTED, ship_entry->name, nullptr, &time)) {			// returns 0 when not found
6551 				return SEXP_NAN_FOREVER;	// exited but not departed
6552 			}
6553 		}
6554 	}
6555 	else
6556 	{
6557 		auto wingp = eval_wing(n);
6558 		if (!wingp || !wingp->flags[Ship::Wing_Flags::Gone])
6559 			return SEXP_NAN;
6560 
6561 		if (!mission_log_get_time(destroyed ? LOG_WING_DESTROYED : LOG_WING_DEPARTED, wingp->name, nullptr, &time)) {
6562 			return SEXP_NAN_FOREVER;		// exited but not de[stroy|part]ed
6563 		}
6564 	}
6565 
6566 	// if we're here we know that the ship/wing did its thing
6567 	return f2i(time);
6568 }
6569 
sexp_time_arrived(int n,bool ship)6570 int sexp_time_arrived(int n, bool ship)
6571 {
6572 	fix time;
6573 
6574 	if (ship)
6575 	{
6576 		auto ship_entry = eval_ship(n);
6577 		if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT)
6578 			return SEXP_NAN;
6579 
6580 		if (!mission_log_get_time(LOG_SHIP_ARRIVED, CTEXT(n), nullptr, &time)) {
6581 			return SEXP_NAN;
6582 		}
6583 	}
6584 	else
6585 	{
6586 		auto wingp = eval_wing(n);
6587 		if (!wingp || wing_has_yet_to_arrive(wingp))
6588 			return SEXP_NAN;
6589 
6590 		if (!mission_log_get_time(LOG_WING_ARRIVED, CTEXT(n), nullptr, &time)) {
6591 			return SEXP_NAN;
6592 		}
6593 	}
6594 
6595 	// if we're here we know that the ship/wing has arrived
6596 	return f2i(time);
6597 }
6598 
sexp_time_docked_or_undocked(int n,bool docked)6599 int sexp_time_docked_or_undocked(int n, bool docked)
6600 {
6601 	fix time;
6602 	bool is_nan, is_nan_forever;
6603 
6604 	auto docker = eval_ship(n);
6605 	if (!docker || docker->status == ShipStatus::NOT_YET_PRESENT)
6606 		return SEXP_NAN;
6607 	n = CDR(n);
6608 
6609 	auto dockee = eval_ship(n);
6610 	if (!dockee || dockee->status == ShipStatus::NOT_YET_PRESENT)
6611 		return SEXP_NAN;
6612 	n = CDR(n);
6613 
6614 	int count = eval_num(n, is_nan, is_nan_forever);
6615 	if (is_nan)
6616 		return SEXP_NAN;
6617 	if (is_nan_forever)
6618 		return SEXP_NAN_FOREVER;
6619 
6620 	if (count <= 0) {
6621 		Warning(LOCATION, "Time-%sdocked count should be at least 1!  This has been automatically adjusted.", docked ? "" : "un");
6622 		count = 1;
6623 	}
6624 
6625 	if (mission_log_get_time_indexed(docked ? LOG_SHIP_DOCKED : LOG_SHIP_UNDOCKED, docker->name, dockee->name, count, &time))
6626 	{
6627 		return f2i(time);
6628 	}
6629 	// if either ship has exited, no way to dock
6630 	else if (docker->status == ShipStatus::EXITED || dockee->status == ShipStatus::EXITED)
6631 		return SEXP_NAN_FOREVER;
6632 
6633 	return SEXP_NAN;
6634 }
6635 
sexp_set_energy_pct(int node,int op_num)6636 void sexp_set_energy_pct (int node, int op_num)
6637 {
6638 	float new_pct;
6639 	bool is_nan, is_nan_forever;
6640 
6641 	Assert (node >= 0);
6642 
6643 	new_pct = eval_num(node, is_nan, is_nan_forever) / 100.0f;
6644 	if (is_nan || is_nan_forever)
6645 		return;
6646 
6647 	// deal with ridiculous percentages
6648 	CLAMP(new_pct, 0.0f, 1.0f);
6649 
6650 	// only need to send a packet for afterburners because shields and weapon energy are sent from server to clients
6651 	if (MULTIPLAYER_MASTER && (op_num == OP_SET_AFTERBURNER_ENERGY)) {
6652 		Current_sexp_network_packet.start_callback();
6653 		Current_sexp_network_packet.send_float(new_pct);
6654 	}
6655 
6656 	node = CDR(node);
6657 
6658 	for (; node >= 0; node = CDR(node)) {
6659 		// get the ship
6660 		auto ship_entry = eval_ship(node);
6661 		if (!ship_entry || !ship_entry->shipp) {
6662 			continue;
6663 		}
6664 		auto shipp = ship_entry->shipp;
6665 		auto sip = &Ship_info[shipp->ship_info_index];
6666 
6667 		switch (op_num) {
6668 			case OP_SET_AFTERBURNER_ENERGY:
6669 				shipp->afterburner_fuel = sip->afterburner_fuel_capacity * new_pct;
6670 				break;
6671 
6672 			case OP_SET_WEAPON_ENERGY:
6673 				if (!(ship_has_energy_weapons(shipp)) ) {
6674 					continue;
6675 				}
6676 
6677 				shipp->weapon_energy = sip->max_weapon_reserve * new_pct;
6678 				break;
6679 
6680 			case OP_SET_SHIELD_ENERGY:
6681 				if (shipp->ship_max_shield_strength == 0.0f) {
6682 					continue;
6683 				}
6684 
6685 				shield_set_strength(&Objects[shipp->objnum], (shield_get_max_strength(&Objects[shipp->objnum]) * new_pct));
6686 				break;
6687 		}
6688 
6689 		if (MULTIPLAYER_MASTER && (op_num == OP_SET_AFTERBURNER_ENERGY)) {
6690 			Current_sexp_network_packet.send_ship(shipp);
6691 		}
6692 	}
6693 
6694 	if (MULTIPLAYER_MASTER && (op_num == OP_SET_AFTERBURNER_ENERGY)) {
6695 		Current_sexp_network_packet.end_callback();
6696 	}
6697 }
6698 
multi_sexp_set_energy_pct()6699 void multi_sexp_set_energy_pct()
6700 {
6701 	ship *shipp;
6702 	float new_pct;
6703 	ship_info * sip;
6704 
6705 	int op_num = Current_sexp_network_packet.get_operator();
6706 
6707 	Current_sexp_network_packet.get_float(new_pct);
6708 	while (Current_sexp_network_packet.get_ship(shipp)) {
6709 		sip = &Ship_info[shipp->ship_info_index];
6710 
6711 		switch (op_num) {
6712 			case OP_SET_AFTERBURNER_ENERGY:
6713 				shipp->afterburner_fuel = sip->afterburner_fuel_capacity * new_pct;
6714 				break;
6715 
6716 			case OP_SET_WEAPON_ENERGY:
6717 				shipp->weapon_energy = sip->max_weapon_reserve * new_pct;
6718 				break;
6719 
6720 			case OP_SET_SHIELD_ENERGY:
6721 				shield_set_strength(&Objects[shipp->objnum], (shield_get_max_strength(&Objects[shipp->objnum]) * new_pct));
6722 				break;
6723 		}
6724 	}
6725 }
6726 
sexp_get_energy_pct(int node,int op_num)6727 int sexp_get_energy_pct (int node, int op_num)
6728 {
6729 	float maximum = 0.0f, current = 0.0f;
6730 
6731 	// get the ship
6732 	auto ship_entry = eval_ship(node);
6733 	if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT)
6734 		return SEXP_NAN;
6735 	if (ship_entry->status == ShipStatus::EXITED)
6736 		return SEXP_NAN_FOREVER;
6737 	auto shipp = ship_entry->shipp;
6738 	auto sip = &Ship_info[shipp->ship_info_index];
6739 
6740 	switch (op_num) {
6741 		case OP_AFTERBURNER_LEFT:
6742 			maximum = sip->afterburner_fuel_capacity;
6743 			current = shipp->afterburner_fuel;
6744 			break;
6745 		case OP_WEAPON_ENERGY_LEFT:
6746 			if (ship_has_energy_weapons(shipp)) {
6747 				maximum = sip->max_weapon_reserve;
6748 				current = shipp->weapon_energy;
6749 			}
6750 			break;
6751 	}
6752 	if (maximum < WEAPON_RESERVE_THRESHOLD || current < WEAPON_RESERVE_THRESHOLD) {
6753 		return 0;
6754 	}
6755 
6756 	return (int)(100 * (current/maximum));
6757 }
6758 
6759 /**
6760  * Return the remaining shields as a percentage of the given ship.
6761  */
sexp_shields_left(int node)6762 int sexp_shields_left(int node)
6763 {
6764 	// get the ship
6765 	auto ship_entry = eval_ship(node);
6766 	if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT)
6767 		return SEXP_NAN;
6768 	if (ship_entry->status == ShipStatus::EXITED)
6769 		return SEXP_NAN_FOREVER;
6770 
6771 	// Goober5000: in case ship has no shields
6772 	if (ship_entry->shipp->ship_max_shield_strength == 0.0f)
6773 	{
6774 		return 0;
6775 	}
6776 
6777 	// now return the amount of shields left as a percentage of the whole.
6778 	int percent = (int)std::lround(get_shield_pct(ship_entry->objp) * 100.0f);
6779 	return percent;
6780 }
6781 
6782 /**
6783  * Return the remaining hits left as a percentage of the whole.
6784  *
6785  * This hit amount counts for all hits on the ship (hull + subsystems).  Use hits_left_hull to find hull hits remaining.
6786  */
sexp_hits_left(int node,bool sim_hull)6787 int sexp_hits_left(int node, bool sim_hull)
6788 {
6789 	// get the ship
6790 	auto ship_entry = eval_ship(node);
6791 	if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT)
6792 		return SEXP_NAN;
6793 	if (ship_entry->status == ShipStatus::EXITED)
6794 		return SEXP_NAN_FOREVER;
6795 
6796 	float hull_pct = sim_hull ? get_sim_hull_pct(ship_entry->objp) : get_hull_pct(ship_entry->objp);
6797 
6798 	// now return the amount of hits left as a percentage of the whole.
6799 	int percent = (int)std::lround(hull_pct * 100.0f);
6800 	return percent;
6801 }
6802 
6803 /**
6804  * Determine if ship visible on radar
6805  *
6806  * @return 0 - not visible
6807  * @return 1 - marginally targetable (jiggly on radar)
6808  * @return 2 - fully targetable
6809  */
sexp_is_ship_visible(int node)6810 int sexp_is_ship_visible(int node)
6811 {
6812 	ship *viewer_shipp = nullptr;
6813 	int n = node, ship_is_visible = 0;
6814 
6815 	// get the ship
6816 	auto ship_entry = eval_ship(n);
6817 	if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT)
6818 		return SEXP_NAN;
6819 	if (ship_entry->status == ShipStatus::EXITED)
6820 		return SEXP_NAN_FOREVER;
6821 	n = CDR(n);
6822 
6823 	// the second argument is the viewer
6824 	if (n >= 0)
6825 	{
6826 		auto viewer_entry = eval_ship(n);
6827 		if (viewer_entry)
6828 			viewer_shipp = viewer_entry->shipp;
6829 	}
6830 	else
6831 	{
6832 		// if the second argument is not supplied, default to the player, per retail
6833 		if (Game_mode & GM_MULTIPLAYER)
6834 		{
6835 			mprintf(("In multiplayer, is-ship-visible must have two arguments!  Defaulting to the first player.\n"));
6836 
6837 			// to make allowances for buggy missions (such as retail), just pick the first player
6838 			// if we actually have no valid players, viewer_shipp will be NULL, but that's ok
6839 			for (int i = 0; i < MAX_PLAYERS; ++i)
6840 			{
6841 				int shipnum = multi_get_player_ship(i);
6842 				if (shipnum >= 0)
6843 				{
6844 					viewer_shipp = &Ships[shipnum];
6845 					break;
6846 				}
6847 			}
6848 		}
6849 		else
6850 			viewer_shipp = Player_ship;
6851 	}
6852 
6853 	// get ship's *radar* visiblity
6854 	if (viewer_shipp)
6855 	{
6856 		if (ship_is_visible_by_team(ship_entry->objp, viewer_shipp))
6857 		{
6858 			ship_is_visible = 2;
6859 		}
6860 	}
6861 
6862 	// only check awacs level if ship is not visible by team
6863 	if (viewer_shipp && !ship_is_visible) {
6864 		float awacs_level = awacs_get_level(ship_entry->objp, viewer_shipp);
6865 		if (awacs_level >= 1.0f) {
6866 			ship_is_visible = 2;
6867 		} else if (awacs_level > 0) {
6868 			ship_is_visible = 1;
6869 		}
6870 	}
6871 
6872 	return ship_is_visible;
6873 }
6874 
6875 /**
6876  * Determine if a flag is set on this ship
6877  */
sexp_check_ship_flag(int node,Ship::Ship_Flags flag)6878 int sexp_check_ship_flag(int node, Ship::Ship_Flags flag)
6879 {
6880 	// get the ship
6881 	auto ship_entry = eval_ship(node);
6882 	if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT)
6883 		return SEXP_NAN;
6884 	if (ship_entry->status == ShipStatus::EXITED)
6885 		return SEXP_NAN_FOREVER;
6886 
6887 	if (ship_entry->shipp->flags[flag])
6888 		return SEXP_TRUE;
6889 	else
6890 		return SEXP_FALSE;
6891 }
6892 
6893 // get multi team v team score
6894 // if not multi team v team return 0
6895 // if invalid team return 0
sexp_team_score(int node)6896 int sexp_team_score(int node)
6897 {
6898 	// if multi t vs t
6899 	if (Game_mode & GM_MULTIPLAYER) {
6900 		if (Netgame.type_flags & NG_TYPE_TEAM) {
6901 
6902 			bool is_nan, is_nan_forever;
6903 			int team = eval_num(node, is_nan, is_nan_forever);
6904 			if (is_nan)
6905 				return SEXP_NAN;
6906 			if (is_nan_forever)
6907 				return SEXP_NAN_FOREVER;
6908 
6909 			// Teams can only be 1 or 2 at the moment but we should use Num_teams in case more become possible in the future
6910 			if (team <= 0 || team > Num_teams)
6911 			{
6912 				// invalid team index
6913 				Warning(LOCATION, "sexp-team-score: team %d is not a valid team #", team);
6914 				return 0;
6915 			}
6916 
6917 			return Multi_team_score[team - 1];
6918 		}
6919 	}
6920 
6921 	return 0;
6922 }
6923 
6924 /**
6925  * Return the remaining hits left on a subsystem as a percentage of the whole.
6926  *
6927  * Goober5000 - this sexp is DEPRECATED because it works just like the new hits-left-substem-generic
6928  */
sexp_hits_left_subsystem(int n)6929 int sexp_hits_left_subsystem(int n)
6930 {
6931 	bool single_subsystem = false;
6932 	int percent, type;
6933 
6934 	// get the ship
6935 	auto ship_entry = eval_ship(n);
6936 	if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT)
6937 		return SEXP_NAN;
6938 	if (ship_entry->status == ShipStatus::EXITED)
6939 		return SEXP_NAN_FOREVER;
6940 
6941 	auto subsys_name = CTEXT(CDR(n));
6942 	auto ss = ship_get_subsys(ship_entry->shipp, subsys_name);
6943 	if (ss)
6944 		type = ss->system_info->type;
6945 	else
6946 		type = SUBSYSTEM_NONE;
6947 
6948 	if ( (type >= 0) && (type < SUBSYSTEM_MAX) ) {
6949 		// check for the optional argument
6950 		n = CDDR(n);
6951 		if (n >= 0) {
6952 			single_subsystem = is_sexp_true(n);
6953 		}
6954 
6955 		// if the third option is present or if this is an unknown subsystem type we only want to find the percentage of the
6956 		// named subsystem
6957 		if (single_subsystem || (type == SUBSYSTEM_UNKNOWN)) {
6958 			if (ss) {
6959 				percent = (int)std::lround(ss->current_hits / ss->max_hits * 100.0f);
6960 				return percent;
6961 			}
6962 
6963 			// we reached end of ship subsys list without finding subsys_name
6964 			if (ship_class_unchanged(ship_entry)) {
6965 				Warning(LOCATION, "Invalid subsystem '%s' passed to hits-left-subsystem", subsys_name);
6966 			}
6967 			return SEXP_NAN;
6968 
6969 		// by default we return as a percentage the hits remaining on the subsystem as a whole (i.e. for 3 engines,
6970 		// we are returning the sum of the hits on the 3 engines)
6971 		} else {
6972 			percent = (int)std::lround(ship_get_subsystem_strength(ship_entry->shipp, type) * 100.0f);
6973 			return percent;
6974 		}
6975 	}
6976 	return SEXP_NAN;			// if for some strange reason, the type field of the subsystem is bogus
6977 }
6978 
6979 // Goober5000
sexp_hits_left_subsystem_generic(int node)6980 int sexp_hits_left_subsystem_generic(int node)
6981 {
6982 	// get the ship
6983 	auto ship_entry = eval_ship(node);
6984 	if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT)
6985 		return SEXP_NAN;
6986 	if (ship_entry->status == ShipStatus::EXITED)
6987 		return SEXP_NAN_FOREVER;
6988 
6989 	auto subsys_type_name = CTEXT(CDR(node));
6990 
6991 	// find subsystem type
6992 	int subsys_type = -1;
6993 	for (int i = 0; i < SUBSYSTEM_MAX; i++)
6994 	{
6995 		if (!stricmp(subsys_type_name, Subsystem_types[i]))
6996 			subsys_type = i;
6997 	}
6998 
6999 	// error checking
7000 	if (subsys_type < 0) {
7001 		Warning(LOCATION, "Subsystem type '%s' not recognized in hits-left-subsystem-generic!", subsys_type_name);
7002 		return SEXP_NAN_FOREVER;
7003 	} else if (subsys_type == SUBSYSTEM_NONE) {
7004 		// as you wish...?
7005 		return 0;
7006 	} else if (subsys_type == SUBSYSTEM_UNKNOWN) {
7007 		Warning(LOCATION, "Cannot use SUBSYSTEM_UNKNOWN in hits-left-subsystem-generic!");
7008 		return SEXP_NAN_FOREVER;
7009 	}
7010 
7011 	// return as a percentage the hits remaining on the subsystem as a whole (i.e. for 3 engines,
7012 	// we are returning the sum of the hits on the 3 engines)
7013 	return (int)std::lround(ship_get_subsystem_strength(ship_entry->shipp, subsys_type) * 100.0f);
7014 }
7015 
7016 // Goober5000
sexp_hits_left_subsystem_specific(int node)7017 int sexp_hits_left_subsystem_specific(int node)
7018 {
7019 	// get the ship
7020 	auto ship_entry = eval_ship(node);
7021 	if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT)
7022 		return SEXP_NAN;
7023 	if (ship_entry->status == ShipStatus::EXITED)
7024 		return SEXP_NAN_FOREVER;
7025 
7026 	auto subsys_name = CTEXT(CDR(node));
7027 
7028 	// find subsystem
7029 	auto ss = ship_get_subsys(ship_entry->shipp, subsys_name);
7030 	if (ss) {
7031 		// return as a percentage the hits remaining on this subsystem only
7032 		return (int)std::lround(ss->current_hits / ss->max_hits * 100.0f);
7033 	}
7034 
7035 	// we reached end of ship subsys list without finding subsys_name
7036 	if (ship_class_unchanged(ship_entry)) {
7037 		Warning(LOCATION, "Invalid subsystem '%s' passed to hits-left-subsystem", subsys_name);
7038 	}
7039 	return SEXP_NAN;
7040 }
7041 
sexp_directive_value(int n)7042 int sexp_directive_value(int n)
7043 {
7044 	int replace_current_value = SEXP_TRUE;
7045 	int directive_value;
7046 	bool is_nan, is_nan_forever;
7047 
7048 	Assert(n >= 0);
7049 
7050 	directive_value = eval_num(n, is_nan, is_nan_forever);
7051 
7052 	if (is_nan || is_nan_forever) {
7053 		directive_value = 0;
7054 	}
7055 
7056 	n = CDR(n);
7057 	if (n > -1) {
7058 		replace_current_value = eval_sexp(n);
7059 	}
7060 
7061 	if (replace_current_value == SEXP_FALSE) { // note: any SEXP_KNOWN_FALSE result will return SEXP_FALSE
7062 		Directive_count += directive_value;
7063 	}
7064 	else {
7065 		Directive_count = directive_value;
7066 	}
7067 
7068 	return SEXP_TRUE;
7069 }
7070 
sexp_determine_team(const char * subj)7071 int sexp_determine_team(const char *subj)
7072 {
7073 	char team_name[NAME_LENGTH];
7074 
7075 	// quick check
7076 	if (strnicmp(subj, "<any ", 5) != 0)
7077 		return -1;
7078 
7079 	// grab IFF (rest of string except for closing angle bracket)
7080 	auto len = strlen(subj + 5) - 1;
7081 	strncpy(team_name, subj + 5, len);
7082 	team_name[len] = '\0';
7083 
7084 	// find it
7085 	return iff_lookup(team_name);
7086 }
7087 
7088 /**
7089  * Check distance between the center of two given objects
7090  */
sexp_center_distance3(object * objp1,object * objp2)7091 int sexp_center_distance3(object *objp1, object *objp2)
7092 {
7093 	Assertion(objp1, "First object should be non-NULL based on check in sexp_distance2");
7094 
7095 	// if the object isn't present in the mission now
7096 	if (!objp2)
7097 		return SEXP_NAN;
7098 
7099 	return (int)vm_vec_dist(&objp1->pos, &objp2->pos);
7100 }
7101 
7102 /**
7103  * Check distance between the bounding boxes of two given objects
7104  */
sexp_bbox_distance3(object * objp1,object * objp2)7105 int sexp_bbox_distance3(object *objp1, object *objp2)
7106 {
7107 	Assertion(objp1, "First object should be non-NULL based on check in sexp_distance2");
7108 
7109 	// if the object isn't present in the mission now
7110 	if (!objp2)
7111 		return SEXP_NAN;
7112 
7113 	if (objp1->type == OBJ_SHIP && objp2->type == OBJ_SHIP)
7114 	{
7115 		vec3d temp1, temp2;
7116 		int model_num1 = Ship_info[Ships[objp1->instance].ship_info_index].model_num;
7117 		int model_num2 = Ship_info[Ships[objp2->instance].ship_info_index].model_num;
7118 		return (int)model_find_closest_points(&temp1, model_num1, -1, &objp1->orient, &objp1->pos, &temp2, model_num2, -1, &objp2->orient, &objp2->pos);
7119 	}
7120 	else if (objp1->type == OBJ_SHIP)
7121 	{
7122 		return (int)hud_find_target_distance(objp1, &objp2->pos);
7123 	}
7124 	else if (objp2->type == OBJ_SHIP)
7125 	{
7126 		return (int)hud_find_target_distance(objp2, &objp1->pos);
7127 	}
7128 	else
7129 	{
7130 		return (int)vm_vec_dist(&objp1->pos, &objp2->pos);
7131 	}
7132 }
7133 
7134 /**
7135  * Check distance between two given objects using the wonky retail method
7136  */
sexp_retail_distance3(object * objp1,object * objp2)7137 int sexp_retail_distance3(object *objp1, object *objp2)
7138 {
7139 	Assertion(objp1, "First object should be non-NULL based on check in sexp_distance2");
7140 
7141 	// if the object isn't present in the mission now
7142 	if (!objp2)
7143 		return SEXP_NAN;
7144 
7145 	if ((objp1->type == OBJ_SHIP) && (objp2->type == OBJ_SHIP))
7146 	{
7147 		if (Player_obj == objp1)
7148 			return (int) hud_find_target_distance(objp2, objp1);
7149 		else
7150 			return (int) hud_find_target_distance(objp1, objp2);
7151 	}
7152 	else
7153 	{
7154 		return (int) vm_vec_dist_quick(&objp1->pos, &objp2->pos);
7155 	}
7156 }
7157 
7158 /**
7159  * Check distance between a given ship and a given subject (ship, wing, any \<team\>).
7160  */
sexp_distance2(object * objp1,object_ship_wing_point_team * oswpt2,int (* distance_method)(object *,object *))7161 int sexp_distance2(object *objp1, object_ship_wing_point_team *oswpt2, int(*distance_method)(object*, object*))
7162 {
7163 	int dist, dist_min = 0;
7164 	bool inited = false;
7165 
7166 	// if the object isn't present in the mission now
7167 	if (!objp1)
7168 		return SEXP_NAN;
7169 
7170 	switch (oswpt2->type)
7171 	{
7172 		// we have a ship-on-team type, so check all ships of that type
7173 		case OSWPT_TYPE_SHIP_ON_TEAM:
7174 		{
7175 			for (auto so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so))
7176 			{
7177 				if (Ships[Objects[so->objnum].instance].team == oswpt2->team)
7178 				{
7179 					dist = distance_method(objp1, &Objects[so->objnum]);
7180 					if (dist != SEXP_NAN)
7181 					{
7182 						if (!inited || (dist < dist_min))
7183 						{
7184 							dist_min = dist;
7185 							inited = true;
7186 						}
7187 					}
7188 				}
7189 			}
7190 
7191 			// no objects were checked
7192 			if (!inited)
7193 				return SEXP_NAN;
7194 
7195 			return dist_min;
7196 		}
7197 
7198 		// check ships and points
7199 		case OSWPT_TYPE_SHIP:
7200 		case OSWPT_TYPE_WAYPOINT:
7201 		{
7202 			return distance_method(objp1, oswpt2->objp);
7203 		}
7204 
7205 		// check wings
7206 		case OSWPT_TYPE_WING:
7207 		{
7208 			for (int i = 0; i < oswpt2->wingp->current_count; i++)
7209 			{
7210 				dist = distance_method(objp1, &Objects[Ships[oswpt2->wingp->ship_index[i]].objnum]);
7211 				if (dist != SEXP_NAN)
7212 				{
7213 					if (!inited || (dist < dist_min))
7214 					{
7215 						dist_min = dist;
7216 						inited = true;
7217 					}
7218 				}
7219 			}
7220 
7221 			// no objects were checked
7222 			if (!inited)
7223 				return SEXP_NAN;
7224 
7225 			return dist_min;
7226 		}
7227 	}
7228 
7229 	return SEXP_NAN;
7230 }
7231 
7232 /**
7233  * Returns the distance between two objects.
7234  *
7235  * If a wing is specified as one (or both) of the arguments to this function, we are looking for the closest distance
7236  */
sexp_distance(int n,int (* distance_method)(object *,object *))7237 int sexp_distance(int n, int(*distance_method)(object*, object*))
7238 {
7239 	int dist, dist_min = 0;
7240 	bool inited = false;
7241 	object_ship_wing_point_team oswpt1, oswpt2;
7242 
7243 	eval_object_ship_wing_point_team(&oswpt1, n);
7244 	eval_object_ship_wing_point_team(&oswpt2, CDR(n));
7245 
7246 	// check to see if either object was destroyed or departed
7247 	if (oswpt1.type == OSWPT_TYPE_EXITED || oswpt2.type == OSWPT_TYPE_EXITED)
7248 		return SEXP_NAN_FOREVER;
7249 
7250 	switch (oswpt1.type)
7251 	{
7252 		// we have a ship-on-team type, so check all ships of that type
7253 		case OSWPT_TYPE_SHIP_ON_TEAM:
7254 		{
7255 			for (auto so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so))
7256 			{
7257 				if (Ships[Objects[so->objnum].instance].team == oswpt1.team)
7258 				{
7259 					dist = sexp_distance2(&Objects[so->objnum], &oswpt2, distance_method);
7260 					if (dist != SEXP_NAN)
7261 					{
7262 						if (!inited || (dist < dist_min))
7263 						{
7264 							dist_min = dist;
7265 							inited = true;
7266 						}
7267 					}
7268 				}
7269 			}
7270 
7271 			// no objects were checked
7272 			if (!inited)
7273 				return SEXP_NAN;
7274 
7275 			return dist_min;
7276 		}
7277 
7278 		// check ships and points
7279 		case OSWPT_TYPE_SHIP:
7280 		case OSWPT_TYPE_WAYPOINT:
7281 		{
7282 			return sexp_distance2(oswpt1.objp, &oswpt2, distance_method);
7283 		}
7284 
7285 		// check wings
7286 		case OSWPT_TYPE_WING:
7287 		{
7288 			for (int i = 0; i < oswpt1.wingp->current_count; i++)
7289 			{
7290 				dist = sexp_distance2(&Objects[Ships[oswpt1.wingp->ship_index[i]].objnum], &oswpt2, distance_method);
7291 				if (dist != SEXP_NAN)
7292 				{
7293 					if (!inited || (dist < dist_min))
7294 					{
7295 						dist_min = dist;
7296 						inited = true;
7297 					}
7298 				}
7299 			}
7300 
7301 			// no objects were checked
7302 			if (!inited)
7303 				return SEXP_NAN;
7304 
7305 			return dist_min;
7306 		}
7307 	}
7308 
7309 	return SEXP_NAN;
7310 }
7311 
7312 /**
7313  * Locate the subsystem on a ship - Goober5000
7314  *
7315  * Switched to a boolean so that it can report failure to do so
7316  */
sexp_get_subsystem_world_pos(vec3d * subsys_world_pos,const ship_registry_entry * ship_entry,const char * subsys_name)7317 bool sexp_get_subsystem_world_pos(vec3d *subsys_world_pos, const ship_registry_entry *ship_entry, const char *subsys_name)
7318 {
7319 	Assert(subsys_world_pos && ship_entry && subsys_name);
7320 
7321 	// find the ship subsystem
7322 	ship_subsys *ss = ship_get_subsys(ship_entry->shipp, subsys_name);
7323 	if (ss)
7324 	{
7325 		// find world position of subsystem on this object (the ship)
7326 		get_subsystem_world_pos(ship_entry->objp, ss, subsys_world_pos);
7327 		return true;
7328 	}
7329 
7330 	// we reached end of ship subsys list without finding subsys_name
7331 	if (ship_class_unchanged(ship_entry)) {
7332 		// this ship should have had the subsystem named as it shouldn't have changed class
7333 		Warning(LOCATION, "sexp_get_subsystem_world_pos could not find subsystem '%s'", subsys_name);
7334 	}
7335 	return false;
7336 }
7337 
7338 /**
7339  * Check distance between the center of an object and a position
7340  */
sexp_center_distance_point(object * objp1,vec3d * pos)7341 int sexp_center_distance_point(object *objp1, vec3d *pos)
7342 {
7343 	Assertion(objp1 && pos, "Parameters should be non-NULL!");
7344 
7345 	return (int)vm_vec_dist(&objp1->pos, pos);
7346 }
7347 
7348 /**
7349  * Check distance between the bounding box of an object and a position
7350  */
sexp_bbox_distance_point(object * objp1,vec3d * pos)7351 int sexp_bbox_distance_point(object *objp1, vec3d *pos)
7352 {
7353 	Assertion(objp1 && pos, "Parameters should be non-NULL!");
7354 
7355 	if (objp1->type == OBJ_SHIP)
7356 	{
7357 		return (int)hud_find_target_distance(objp1, pos);
7358 	}
7359 	else
7360 	{
7361 		return (int)vm_vec_dist(&objp1->pos, pos);
7362 	}
7363 }
7364 
7365 /**
7366  * Returns the distance between an object and a ship subsystem.
7367  *
7368  * If a wing is specified as the object argument to this function, we are looking for the closest distance
7369  */
sexp_distance_subsystem(int n,int (* distance_method)(object *,vec3d *))7370 int sexp_distance_subsystem(int n, int(*distance_method)(object*, vec3d*))
7371 {
7372 	int dist, dist_min = 0;
7373 	bool inited = false;
7374 	vec3d subsys_pos;
7375 	object_ship_wing_point_team oswpt;
7376 
7377 	eval_object_ship_wing_point_team(&oswpt, n);
7378 
7379 	// quick out
7380 	if (oswpt.type == OSWPT_TYPE_EXITED)
7381 		return SEXP_NAN_FOREVER;
7382 
7383 	// get the ship
7384 	auto ship_with_subsys = eval_ship(CDR(n));
7385 	if (!ship_with_subsys || ship_with_subsys->status == ShipStatus::NOT_YET_PRESENT)
7386 		return SEXP_NAN;
7387 	if (ship_with_subsys->status == ShipStatus::EXITED)
7388 		return SEXP_NAN_FOREVER;
7389 
7390 	auto subsys_name = CTEXT(CDR(CDR(n)));
7391 
7392 	// get the subsystem's coordinates or bail if we can't
7393 	if (!sexp_get_subsystem_world_pos(&subsys_pos, ship_with_subsys, subsys_name))
7394 		return SEXP_NAN;
7395 
7396 	switch (oswpt.type)
7397 	{
7398 		// we have a ship-on-team type, so check all ships of that type
7399 		case OSWPT_TYPE_SHIP_ON_TEAM:
7400 		{
7401 			for (auto so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so))
7402 			{
7403 				if (Ships[Objects[so->objnum].instance].team == oswpt.team)
7404 				{
7405 					dist = distance_method(&Objects[so->objnum], &subsys_pos);
7406 
7407 					if (!inited || (dist < dist_min))
7408 					{
7409 						dist_min = dist;
7410 						inited = true;
7411 					}
7412 				}
7413 			}
7414 
7415 			// no objects were checked
7416 			if (!inited)
7417 				return SEXP_NAN;
7418 
7419 			return dist_min;
7420 		}
7421 
7422 		// check ships and points
7423 		case OSWPT_TYPE_SHIP:
7424 		case OSWPT_TYPE_WAYPOINT:
7425 		{
7426 			return distance_method(oswpt.objp, &subsys_pos);
7427 		}
7428 
7429 		// check wings
7430 		case OSWPT_TYPE_WING:
7431 		{
7432 			for (int i = 0; i < oswpt.wingp->current_count; i++)
7433 			{
7434 				dist = distance_method(&Objects[Ships[oswpt.wingp->ship_index[i]].objnum], &subsys_pos);
7435 
7436 				if (!inited || (dist < dist_min))
7437 				{
7438 					dist_min = dist;
7439 					inited = true;
7440 				}
7441 			}
7442 
7443 			// no objects were checked
7444 			if (!inited)
7445 				return SEXP_NAN;
7446 
7447 			return dist_min;
7448 		}
7449 	}
7450 
7451 	return SEXP_NAN;
7452 }
7453 
sexp_helper_is_within_box(const std::array<float,6> & box_vals,const vec3d * pos)7454 bool sexp_helper_is_within_box(const std::array<float, 6> &box_vals, const vec3d *pos)
7455 {
7456 	int i;
7457 	for(i = 0; i < 3; i++)
7458 	{
7459 		if(pos->a1d[i] < (box_vals[i] - box_vals[i+3])
7460 			|| pos->a1d[i] > (box_vals[i] + box_vals[i+3]))
7461 		{
7462 			return false;
7463 		}
7464 	}
7465 
7466 	return true;
7467 }
7468 
sexp_num_within_box(int n)7469 int sexp_num_within_box(int n)
7470 {
7471 	std::array<float, 6> box_vals;//x,y,z,width,height,depth
7472 	int retval = 0;
7473 	bool is_nan, is_nan_forever;
7474 
7475 	eval_array(box_vals, n, is_nan, is_nan_forever);
7476 
7477 	// nobody can be within a NaN box
7478 	if (is_nan || is_nan_forever)
7479 		return 0;
7480 
7481 	for(; n >= 0; n = CDR(n))
7482 	{
7483 		// this might be a ship
7484 		auto ship_entry = eval_ship(n);
7485 		if (ship_entry)
7486 		{
7487 			if (ship_entry->objp && sexp_helper_is_within_box(box_vals, &ship_entry->objp->pos))
7488 				retval++;
7489 		}
7490 		else
7491 		{
7492 			// this might be a wing.  if it is, someone in the wing must be present
7493 			auto wingp = eval_wing(n);
7494 			if (wingp && wingp->current_count > 0)
7495 			{
7496 				// every present member in the wing must be in the box
7497 				bool wing_check = true;
7498 				for (int i = 0; i < wingp->current_count; i++)
7499 				{
7500 					if (!sexp_helper_is_within_box(box_vals, &Objects[Ships[wingp->ship_index[i]].objnum].pos))
7501 					{
7502 						wing_check = false;
7503 						break;
7504 					}
7505 				}
7506 
7507 				if (wing_check)
7508 					retval++;
7509 			}
7510 		}
7511 	}
7512 
7513 	return retval;
7514 }
7515 
7516 // Goober5000
7517 // wookieejedi - this sexp is deprecated in favor of sexp_set_ship_man
sexp_set_object_speed(object * objp,int speed,int axis,bool subjective)7518 void sexp_set_object_speed(object *objp, int speed, int axis, bool subjective)
7519 {
7520 	Assert(axis >= 0 && axis <= 2);
7521 
7522 	if (subjective)
7523 	{
7524 		vec3d subjective_vel;
7525 
7526 		// translate objective into subjective velocity
7527 		vm_vec_rotate(&subjective_vel, &objp->phys_info.vel, &objp->orient);
7528 
7529 		// set it
7530 		subjective_vel.a1d[axis] = i2fl(speed);
7531 
7532 		// translate it back to objective
7533 		vm_vec_unrotate(&objp->phys_info.vel, &subjective_vel, &objp->orient);
7534 	}
7535 	else
7536 	{
7537 		objp->phys_info.vel.a1d[axis] = i2fl(speed);
7538 	}
7539 }
7540 
7541 // Goober5000
7542 // wookieejedi - this sexp is deprecated in favor of sexp_set_ship_man
sexp_set_object_speed(int n,int axis)7543 void sexp_set_object_speed(int n, int axis)
7544 {
7545 	Assert(n >= 0);
7546 
7547 	bool is_nan, is_nan_forever;
7548 	int speed;
7549 	bool subjective = false;
7550 	object_ship_wing_point_team oswpt;
7551 
7552 	eval_object_ship_wing_point_team(&oswpt, n);
7553 	n = CDR(n);
7554 
7555 	speed = eval_num(n, is_nan, is_nan_forever);
7556 	n = CDR(n);
7557 	if (is_nan || is_nan_forever)
7558 		return;
7559 
7560 	if (n >= 0)
7561 	{
7562 		subjective = is_sexp_true(n);
7563 		n = CDR(n);
7564 	}
7565 
7566 	Current_sexp_network_packet.start_callback();
7567 	Current_sexp_network_packet.send_int(speed);
7568 	Current_sexp_network_packet.send_int(axis);
7569 	Current_sexp_network_packet.send_bool(subjective);
7570 
7571 	switch (oswpt.type)
7572 	{
7573 		case OSWPT_TYPE_SHIP:
7574 			sexp_set_object_speed(oswpt.objp, speed, axis, subjective);
7575 			Current_sexp_network_packet.send_object(oswpt.objp);
7576 			break;
7577 
7578 		case OSWPT_TYPE_WING:
7579 		{
7580 			for (int i = 0; i < oswpt.wingp->current_count; ++i)
7581 			{
7582 				auto shipp = &Ships[oswpt.wingp->ship_index[i]];
7583 
7584 				sexp_set_object_speed(&Objects[shipp->objnum], speed, axis, subjective);
7585 				Current_sexp_network_packet.send_object(&Objects[shipp->objnum]);
7586 			}
7587 			break;
7588 		}
7589 	}
7590 
7591 	Current_sexp_network_packet.end_callback();
7592 }
7593 
7594 //CommanderDJ
multi_sexp_set_object_speed()7595 void multi_sexp_set_object_speed()
7596 {
7597 	object *objp;
7598 	int speed = 0, axis = 0;
7599 	bool subjective = false;
7600 
7601 	Current_sexp_network_packet.get_int(speed);
7602 	Current_sexp_network_packet.get_int(axis);
7603 	Current_sexp_network_packet.get_bool(subjective);
7604 
7605 	while (Current_sexp_network_packet.get_object(objp))
7606 		sexp_set_object_speed(objp, speed, axis, subjective);
7607 }
7608 
sexp_get_object_speed(object * objp,int axis,bool subjective)7609 int sexp_get_object_speed(object *objp, int axis, bool subjective)
7610 {
7611 	Assertion(((axis >= 0) && (axis <= 2)), "Axis is out of range (%d)", axis);
7612 	int speed;
7613 
7614 	if (subjective)
7615 	{
7616 		// return the speed based on the orentation of the object
7617 		vec3d subjective_vel;
7618 		vm_vec_rotate(&subjective_vel, &objp->phys_info.vel, &objp->orient);
7619 		speed = fl2i(subjective_vel.a1d[axis]);
7620 		vm_vec_unrotate(&objp->phys_info.vel, &subjective_vel, &objp->orient);
7621 	}
7622 	else
7623 	{
7624 		// return the speed according to the grid
7625 		speed = fl2i(objp->phys_info.vel.a1d[axis]);
7626 	}
7627 	return speed;
7628 }
7629 
sexp_get_object_speed(int n,int axis)7630 int sexp_get_object_speed(int n, int axis)
7631 {
7632 	Assert(n >= 0);
7633 
7634 	int speed;
7635 	bool subjective = false;
7636 	object_ship_wing_point_team oswpt;
7637 
7638 	eval_object_ship_wing_point_team(&oswpt, n);
7639 	n = CDR(n);
7640 
7641 	if (n >= 0)
7642 	{
7643 		subjective = is_sexp_true(n);
7644 		n = CDR(n);
7645 	}
7646 
7647 	switch (oswpt.type)
7648 	{
7649 		case OSWPT_TYPE_EXITED:
7650 			return SEXP_NAN_FOREVER;
7651 
7652 		case OSWPT_TYPE_SHIP:
7653 		case OSWPT_TYPE_WING:
7654 			speed = sexp_get_object_speed(oswpt.objp, axis, subjective);
7655 			break;
7656 
7657 		default:
7658 			return SEXP_NAN;
7659 	}
7660 	return speed;
7661 }
7662 
7663 // Goober5000
sexp_calculate_coordinate(vec3d * origin,matrix * orient,vec3d * relative_location,int axis)7664 int sexp_calculate_coordinate(vec3d *origin, matrix *orient, vec3d *relative_location, int axis)
7665 {
7666 	Assert(origin != nullptr);
7667 	Assert(orient != nullptr);
7668 	Assert(axis >= 0 && axis <= 2);
7669 
7670 	if (relative_location == nullptr)
7671 	{
7672 		return fl2i(origin->a1d[axis]);
7673 	}
7674 	else
7675 	{
7676 		vec3d new_world_pos;
7677 
7678 		vm_vec_unrotate(&new_world_pos, relative_location, orient);
7679 		vm_vec_add2(&new_world_pos, origin);
7680 
7681 		return fl2i(new_world_pos.a1d[axis]);
7682 	}
7683 }
7684 
7685 // Goober5000
sexp_calculate_angle(matrix * orient,int axis)7686 int sexp_calculate_angle(matrix *orient, int axis)
7687 {
7688 	Assert(orient != nullptr);
7689 	Assert(axis >= 0 && axis <= 2);
7690 
7691 	angles a;
7692 	vm_extract_angles_matrix_alternate(&a, orient);
7693 
7694 	// blugh
7695 	float rad;
7696 	switch (axis)
7697 	{
7698 		case 0:	rad = a.p; break;
7699 		case 1:	rad = a.b; break;
7700 		case 2:	rad = a.h; break;
7701 		default: rad = 0.0f; break;
7702 	}
7703 
7704 	float deg = fl_degrees(rad);
7705 
7706 	int deg2 = static_cast<int>(deg < 0.0f ? deg - 0.5f : deg + 0.5f);
7707 	if (deg2 < 0)
7708 		deg2 += 360;
7709 
7710 	return deg2;
7711 }
7712 
7713 // Goober5000
sexp_get_object_coordinate(int n,int axis)7714 int sexp_get_object_coordinate(int n, int axis)
7715 {
7716 	Assert(n >= 0);
7717 
7718 	const char *subsystem_name = nullptr;
7719 	vec3d *pos = nullptr, *relative_location = nullptr, relative_location_buf, subsys_pos_buf;
7720 	object_ship_wing_point_team oswpt;
7721 
7722 	eval_object_ship_wing_point_team(&oswpt, n);
7723 	n = CDR(n);
7724 
7725 	if (n >= 0)
7726 	{
7727 		subsystem_name = CTEXT(n);
7728 		n = CDR(n);
7729 
7730 		if (n >= 0)
7731 		{
7732 			relative_location = &relative_location_buf;
7733 
7734 			bool is_nan, is_nan_forever;
7735 			eval_vec3d(relative_location, n, is_nan, is_nan_forever);
7736 			if (is_nan)
7737 				return SEXP_NAN;
7738 			if (is_nan_forever)
7739 				return SEXP_NAN_FOREVER;
7740 		}
7741 	}
7742 
7743 	switch (oswpt.type)
7744 	{
7745 		case OSWPT_TYPE_EXITED:
7746 			return SEXP_NAN_FOREVER;
7747 
7748 		case OSWPT_TYPE_SHIP:
7749 		case OSWPT_TYPE_WING:
7750 		case OSWPT_TYPE_WAYPOINT:
7751 			pos = &oswpt.objp->pos;
7752 			break;
7753 
7754 		default:
7755 			return SEXP_NAN;
7756 	}
7757 
7758 	// see if we have a subsys
7759 	if (oswpt.objp->type == OBJ_SHIP)
7760 	{
7761 		if ((subsystem_name != nullptr) && stricmp(subsystem_name, SEXP_NONE_STRING) != 0 && stricmp(subsystem_name, SEXP_HULL_STRING) != 0)
7762 		{
7763 			pos = &subsys_pos_buf;
7764 			// get the world pos but bail if we can't get one
7765 			if (!sexp_get_subsystem_world_pos(pos, oswpt.ship_entry, subsystem_name))
7766 				return SEXP_NAN;
7767 		}
7768 	}
7769 
7770 	return sexp_calculate_coordinate(pos, &oswpt.objp->orient, relative_location, axis);
7771 }
7772 
7773 // Goober5000
sexp_get_object_angle(int n,int axis)7774 int sexp_get_object_angle(int n, int axis)
7775 {
7776 	Assert(n >= 0);
7777 
7778 	object_ship_wing_point_team oswpt;
7779 	eval_object_ship_wing_point_team(&oswpt, n);
7780 
7781 	switch (oswpt.type)
7782 	{
7783 		case OSWPT_TYPE_EXITED:
7784 			return SEXP_NAN_FOREVER;
7785 
7786 		case OSWPT_TYPE_SHIP:
7787 		case OSWPT_TYPE_WING:
7788 			return sexp_calculate_angle(&oswpt.objp->orient, axis);
7789 
7790 		default:
7791 			return SEXP_NAN;
7792 	}
7793 }
7794 
set_object_for_clients(object * objp)7795 void set_object_for_clients(object *objp)
7796 {
7797 	if (!(Game_mode & GM_MULTIPLAYER)) {
7798 		return;
7799 	}
7800 
7801 	// Tell the player (if this is a client) that they've moved.
7802 	if ((objp->flags[Object::Object_Flags::Player_ship]) && (objp != Player_obj) ){
7803 		multi_oo_send_changed_object(objp);
7804 	}
7805 }
7806 
sexp_set_object_position(int n)7807 void sexp_set_object_position(int n)
7808 {
7809 	vec3d target_vec, orig_leader_vec;
7810 	object_ship_wing_point_team oswpt;
7811 	bool something_collides = false;
7812 
7813 	eval_object_ship_wing_point_team(&oswpt, n);
7814 	n = CDR(n);
7815 
7816 	bool is_nan, is_nan_forever;
7817 	eval_vec3d(&target_vec, n, is_nan, is_nan_forever);
7818 	if (is_nan || is_nan_forever)
7819 		return;
7820 
7821 	switch (oswpt.type)
7822 	{
7823 		case OSWPT_TYPE_SHIP:
7824 		{
7825 			oswpt.objp->pos = target_vec;
7826 			set_object_for_clients(oswpt.objp);
7827 
7828 			if (oswpt.objp->flags[Object::Object_Flags::Collides])
7829 				something_collides = true;
7830 
7831 			break;
7832 		}
7833 
7834 		case OSWPT_TYPE_PARSE_OBJECT:
7835 		{
7836 			oswpt.ship_entry->p_objp->pos = target_vec;
7837 			break;
7838 		}
7839 
7840 		case OSWPT_TYPE_WAYPOINT:
7841 		{
7842 			oswpt.objp->pos = target_vec;
7843 			oswpt.waypointp->set_pos(&target_vec);
7844 			Current_sexp_network_packet.start_callback();
7845 			Current_sexp_network_packet.send_ushort(oswpt.objp->net_signature);
7846 			Current_sexp_network_packet.send_float(target_vec.xyz.x);
7847 			Current_sexp_network_packet.send_float(target_vec.xyz.y);
7848 			Current_sexp_network_packet.send_float(target_vec.xyz.z);
7849 			Current_sexp_network_packet.end_callback();
7850 			break;
7851 		}
7852 
7853 		case OSWPT_TYPE_WING:
7854 		{
7855 			// move the wing leader first
7856 			orig_leader_vec = oswpt.objp->pos;
7857 			oswpt.objp->pos = target_vec;
7858 			set_object_for_clients(oswpt.objp);
7859 
7860 			// move everything in the wing
7861 			for (int i = 0; i < oswpt.wingp->current_count; i++)
7862 			{
7863 				object *objp = &Objects[Ships[oswpt.wingp->ship_index[i]].objnum];
7864 
7865 				if (objp != oswpt.objp)
7866 				{
7867 					vm_vec_sub2(&objp->pos, &orig_leader_vec);
7868 					vm_vec_add2(&objp->pos, &target_vec);
7869 					set_object_for_clients(objp);
7870 				}
7871 
7872 				if (objp->flags[Object::Object_Flags::Collides])
7873 					something_collides = true;
7874 			}
7875 
7876 			break;
7877 		}
7878 
7879 		case OSWPT_TYPE_WING_NOT_PRESENT:
7880 		{
7881 			// search the arrival list for the wing leader and move him first
7882 			bool found = false;
7883 			for (p_object *p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp))
7884 			{
7885 				if (p_objp->wingnum == WING_INDEX(oswpt.wingp) && p_objp->pos_in_wing == 0)
7886 				{
7887 					orig_leader_vec = p_objp->pos;
7888 					p_objp->pos = target_vec;
7889 
7890 					found = true;
7891 					break;
7892 				}
7893 			}
7894 
7895 			// if we didn't find him, the wing will never arrive, so bail
7896 			if (!found)
7897 				break;
7898 
7899 			// move everything in the wing
7900 			for (p_object *p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp))
7901 			{
7902 				if (p_objp->wingnum == WING_INDEX(oswpt.wingp) && p_objp->pos_in_wing != 0)
7903 				{
7904 					vm_vec_sub2(&p_objp->pos, &orig_leader_vec);
7905 					vm_vec_add2(&p_objp->pos, &target_vec);
7906 				}
7907 			}
7908 
7909 			break;
7910 		}
7911 	}
7912 
7913 	// retime all collision pairs (so they're checked again) if we moved something that collides
7914 	if (something_collides)
7915 	{
7916 		obj_collide_retime_cached_pairs();
7917 	}
7918 }
7919 
7920 // only for waypoints cause they don't get transferred the normal way
multi_sexp_set_object_position()7921 void multi_sexp_set_object_position()
7922 {
7923 	object *objp;
7924 	vec3d wp_vec;
7925 	ushort obj_sig;
7926 	Current_sexp_network_packet.get_ushort(obj_sig);
7927 	Current_sexp_network_packet.get_float(wp_vec.xyz.x);
7928 	Current_sexp_network_packet.get_float(wp_vec.xyz.y);
7929 	Current_sexp_network_packet.get_float(wp_vec.xyz.z);
7930 	objp = multi_get_network_object(obj_sig);
7931 	if (objp->type == OBJ_WAYPOINT) {
7932 		objp->pos = wp_vec;
7933 		waypoint *wpt = find_waypoint_with_objnum(OBJ_INDEX(objp));
7934 		wpt->set_pos(&wp_vec);
7935 	}
7936 }
7937 
7938 // Goober5000
sexp_set_object_orientation(int n)7939 void sexp_set_object_orientation(int n)
7940 {
7941 	angles a;
7942 	matrix target_orient;
7943 	object_ship_wing_point_team oswpt;
7944 	bool something_collides = false;
7945 
7946 	eval_object_ship_wing_point_team(&oswpt, n);
7947 	n = CDR(n);
7948 
7949 	bool is_nan, is_nan_forever;
7950 	eval_angles(&a, n, is_nan, is_nan_forever);
7951 	if (is_nan || is_nan_forever)
7952 		return;
7953 
7954 	vm_angles_2_matrix(&target_orient, &a);
7955 
7956 	switch (oswpt.type)
7957 	{
7958 		case OSWPT_TYPE_SHIP:
7959 		{
7960 			oswpt.objp->orient = target_orient;
7961 			set_object_for_clients(oswpt.objp);
7962 
7963 			if (oswpt.objp->flags[Object::Object_Flags::Collides])
7964 				something_collides = true;
7965 
7966 			break;
7967 		}
7968 
7969 		case OSWPT_TYPE_PARSE_OBJECT:
7970 		{
7971 			oswpt.ship_entry->p_objp->orient = target_orient;
7972 			break;
7973 		}
7974 
7975 		case OSWPT_TYPE_WING:
7976 		{
7977 			// move everything in the wing
7978 			for (int i = 0; i < oswpt.wingp->current_count; i++)
7979 			{
7980 				object *objp = &Objects[Ships[oswpt.wingp->ship_index[i]].objnum];
7981 				objp->orient = target_orient;
7982 				set_object_for_clients(objp);
7983 
7984 				if (objp->flags[Object::Object_Flags::Collides])
7985 					something_collides = true;
7986 			}
7987 
7988 			break;
7989 		}
7990 
7991 		case OSWPT_TYPE_WING_NOT_PRESENT:
7992 		{
7993 			// move everything in the wing
7994 			for (p_object *p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp))
7995 			{
7996 				if (p_objp->wingnum == WING_INDEX(oswpt.wingp))
7997 					p_objp->orient = target_orient;
7998 			}
7999 
8000 			break;
8001 		}
8002 	}
8003 
8004 	// retime all collision pairs (so they're checked again) if we rotated something that collides
8005 	if (something_collides)
8006 	{
8007 		obj_collide_retime_cached_pairs();
8008 	}
8009 }
8010 
8011 // Goober5000
8012 // this is different from sexp_set_object_orientation
8013 // and now can be used for arbitrary orients (i.e. for parse objects), not just objects
sexp_set_orient_sub(matrix * orient_to_set,vec3d * pos,vec3d * location,int turn_time=0,int bank=0,object * objp=nullptr)8014 void sexp_set_orient_sub(matrix *orient_to_set, vec3d *pos, vec3d *location, int turn_time = 0, int bank = 0, object *objp = nullptr)
8015 {
8016 	Assert(orient_to_set && pos && location);
8017 
8018 	vec3d v_orient;
8019 	matrix m_orient;
8020 
8021 
8022 	// are we doing this via ai? -------------------
8023 	if (objp && turn_time)
8024 	{
8025 		// set flag
8026 		int bankflag = 0;
8027 		if (!bank)
8028 		{
8029 			bankflag = AITTV_IGNORE_BANK;
8030 		}
8031 
8032 		// slow down or speed up the ship's turnrate to match the given turn time
8033 		vec3d turnrate_mod;
8034 		float turnrate_adjust = Ship_info[Ships[objp->instance].ship_info_index].srotation_time / (turn_time / 1000.f);
8035 		vm_vec_make(&turnrate_mod, turnrate_adjust, turnrate_adjust, turnrate_adjust);
8036 		// turn
8037 		ai_turn_towards_vector(location, objp, nullptr, nullptr, 0.0f, (AITTV_VIA_SEXP | bankflag), nullptr, &turnrate_mod);
8038 
8039 		// return
8040 		return;
8041 	}
8042 
8043 
8044 	// calculate orientation matrix ----------------
8045 
8046 	vm_vec_sub(&v_orient, location, pos);
8047 
8048 	if (IS_VEC_NULL_SQ_SAFE(&v_orient))
8049 	{
8050 		Warning(LOCATION, "error in sexp setting ship orientation: can't point to self; quitting...\n");
8051 		return;
8052 	}
8053 
8054 	vm_vector_2_matrix(&m_orient, &v_orient, nullptr, nullptr);
8055 
8056 
8057 	// set orientation -----------------------------
8058 	*orient_to_set = m_orient;
8059 
8060 	// Tell the player (assuming it's a client) that they've moved.
8061 	if (objp)
8062 		set_object_for_clients(objp);
8063 }
8064 
8065 // Goober5000
sexp_stuff_oswpt_location(vec3d ** location,object_ship_wing_point_team * oswpt)8066 void sexp_stuff_oswpt_location(vec3d **location, object_ship_wing_point_team *oswpt)
8067 {
8068 	switch (oswpt->type)
8069 	{
8070 		case OSWPT_TYPE_SHIP:
8071 		case OSWPT_TYPE_WING:
8072 		case OSWPT_TYPE_WAYPOINT:
8073 			*location = &oswpt->objp->pos;
8074 			break;
8075 
8076 		case OSWPT_TYPE_PARSE_OBJECT:
8077 			*location = &oswpt->ship_entry->p_objp->pos;
8078 			break;
8079 
8080 		case OSWPT_TYPE_WING_NOT_PRESENT:
8081 		{
8082 			for (p_object *p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp))
8083 			{
8084 				// use the wing leader's position, same as if the wing were present
8085 				if (p_objp->wingnum == WING_INDEX(oswpt->wingp) && p_objp->pos_in_wing == 0)
8086 				{
8087 					*location = &p_objp->pos;
8088 					break;
8089 				}
8090 			}
8091 			break;
8092 		}
8093 	}
8094 }
8095 
8096 // Goober5000
sexp_set_oswpt_facing(object_ship_wing_point_team * oswpt,vec3d * location,int turn_time=0,int bank=0)8097 void sexp_set_oswpt_facing(object_ship_wing_point_team *oswpt, vec3d *location, int turn_time = 0, int bank = 0)
8098 {
8099 	Assert(oswpt && location);
8100 
8101 	switch (oswpt->type)
8102 	{
8103 		case OSWPT_TYPE_SHIP:
8104 			sexp_set_orient_sub(&oswpt->objp->orient, &oswpt->objp->pos, location, turn_time, bank, oswpt->objp);
8105 			break;
8106 
8107 		case OSWPT_TYPE_PARSE_OBJECT:
8108 			sexp_set_orient_sub(&oswpt->ship_entry->p_objp->orient, &oswpt->ship_entry->p_objp->pos, location);
8109 			break;
8110 
8111 		case OSWPT_TYPE_WING:
8112 		{
8113 			for (int i = 0; i < oswpt->wingp->current_count; i++)
8114 			{
8115 				object *objp = &Objects[Ships[oswpt->wingp->ship_index[i]].objnum];
8116 
8117 				sexp_set_orient_sub(&objp->orient, &objp->pos, location, turn_time, bank, objp);
8118 			}
8119 			break;
8120 		}
8121 
8122 		case OSWPT_TYPE_WING_NOT_PRESENT:
8123 		{
8124 			for (p_object *p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp))
8125 			{
8126 				if (p_objp->wingnum == WING_INDEX(oswpt->wingp))
8127 					sexp_set_orient_sub(&p_objp->orient, &p_objp->pos, location);
8128 			}
8129 			break;
8130 		}
8131 	}
8132 }
8133 
8134 // Goober5000
sexp_set_object_facing(int n,bool facing_object)8135 void sexp_set_object_facing(int n, bool facing_object)
8136 {
8137 	bool is_nan, is_nan_forever;
8138 	vec3d *location = nullptr;
8139 	vec3d location_buf;
8140 	int turn_time, bank;
8141 	object_ship_wing_point_team oswpt1, oswpt2;
8142 
8143 	// get ship or wing
8144 	eval_object_ship_wing_point_team(&oswpt1, n);
8145 	n = CDR(n);
8146 
8147 	// get location
8148 	if (facing_object)
8149 	{
8150 		eval_object_ship_wing_point_team(&oswpt2, n);
8151 		n = CDR(n);
8152 
8153 		sexp_stuff_oswpt_location(&location, &oswpt2);
8154 
8155 		// ensure it's valid
8156 		if (location == nullptr)
8157 			return;
8158 	}
8159 	else
8160 	{
8161 		location = &location_buf;
8162 
8163 		eval_vec3d(location, n, is_nan, is_nan_forever);
8164 		if (is_nan || is_nan_forever)
8165 			return;
8166 	}
8167 
8168 	// get optional turn time and bank
8169 	eval_nums(n, is_nan, is_nan_forever, turn_time, bank);
8170 	if (is_nan || is_nan_forever)
8171 		return;
8172 
8173 	sexp_set_oswpt_facing(&oswpt1, location, turn_time, bank);
8174 }
8175 
sexp_set_ship_man(ship * shipp,int duration,int heading,int pitch,int bank,bool apply_all_rotate,int up,int sideways,int forward,bool apply_all_lat,int maneuver_flags)8176 void sexp_set_ship_man(ship *shipp, int duration, int heading, int pitch, int bank, bool apply_all_rotate, int up, int sideways, int forward, bool apply_all_lat, int maneuver_flags)
8177 {
8178 	ai_info	*aip = &Ai_info[shipp->ai_index];
8179 
8180 	if (!(maneuver_flags & CIF_DONT_OVERRIDE_OLD_MANEUVERS)) {
8181 		aip->ai_override_flags.reset();
8182 	}
8183 	bool applied_rot, applied_lat;
8184 	applied_rot = applied_lat = false;
8185 
8186 	if (apply_all_rotate) {
8187 		aip->ai_override_flags.set(AI::Maneuver_Override_Flags::Full_rot);
8188 		aip->ai_override_ci.bank = bank / 100.0f;
8189 		aip->ai_override_ci.pitch = pitch / 100.0f;
8190 		aip->ai_override_ci.heading = heading / 100.0f;
8191 		applied_rot = true;
8192 	} else {
8193 		if (bank != 0) {
8194 			aip->ai_override_flags.set(AI::Maneuver_Override_Flags::Roll);
8195 			aip->ai_override_ci.bank = bank / 100.0f;
8196 			applied_rot = true;
8197 		}
8198 		if (pitch != 0) {
8199 			aip->ai_override_flags.set(AI::Maneuver_Override_Flags::Pitch);
8200 			aip->ai_override_ci.pitch = pitch / 100.0f;
8201 			applied_rot = true;
8202 		}
8203 		if (heading != 0) {
8204 			aip->ai_override_flags.set(AI::Maneuver_Override_Flags::Heading);
8205 			aip->ai_override_ci.heading = heading / 100.0f;
8206 			applied_rot = true;
8207 		}
8208 	}
8209 
8210 	if (apply_all_lat) {
8211 		aip->ai_override_flags.set(AI::Maneuver_Override_Flags::Full_lat);
8212 		aip->ai_override_ci.vertical = up / 100.0f;
8213 		aip->ai_override_ci.sideways = sideways / 100.0f;
8214 		aip->ai_override_ci.forward = forward / 100.0f;
8215 		applied_lat = true;
8216 	} else {
8217 		if (up != 0) {
8218 			aip->ai_override_flags.set(AI::Maneuver_Override_Flags::Up);
8219 			aip->ai_override_ci.vertical = up / 100.0f;
8220 			applied_lat = true;
8221 		}
8222 		if (sideways != 0) {
8223 			aip->ai_override_flags.set(AI::Maneuver_Override_Flags::Sideways);
8224 			aip->ai_override_ci.sideways = sideways / 100.0f;
8225 			applied_lat = true;
8226 		}
8227 		if (forward != 0) {
8228 			aip->ai_override_flags.set(AI::Maneuver_Override_Flags::Forward);
8229 			aip->ai_override_ci.forward = forward / 100.0f;
8230 			applied_lat = true;
8231 		}
8232 	}
8233 
8234 	// handle infinite timestamps
8235 	if (duration >= 2) {
8236 		if (applied_rot)
8237 			aip->ai_override_rot_timestamp = timestamp(duration);
8238 		if (applied_lat)
8239 			aip->ai_override_lat_timestamp = timestamp(duration);
8240 	}
8241 	else {
8242 		if (applied_rot) {
8243 			aip->ai_override_rot_timestamp = timestamp(10);
8244 			aip->ai_override_flags.set(AI::Maneuver_Override_Flags::Rotational_never_expire);
8245 		}
8246 		if (applied_lat) {
8247 			aip->ai_override_lat_timestamp = timestamp(10);
8248 			aip->ai_override_flags.set(AI::Maneuver_Override_Flags::Lateral_never_expire);
8249 		}
8250 	}
8251 
8252 	if (maneuver_flags & CIF_DONT_BANK_WHEN_TURNING) {
8253 		aip->ai_override_flags.set(AI::Maneuver_Override_Flags::Dont_bank_when_turning);
8254 	}
8255 	if (maneuver_flags & CIF_DONT_CLAMP_MAX_VELOCITY) {
8256 		aip->ai_override_flags.set(AI::Maneuver_Override_Flags::Dont_clamp_max_velocity);
8257 	}
8258 	if (maneuver_flags & CIF_INSTANTANEOUS_ACCELERATION) {
8259 		aip->ai_override_flags.set(AI::Maneuver_Override_Flags::Instantaneous_acceleration);
8260 	}
8261 }
8262 
sexp_set_oswpt_maneuver(object_ship_wing_point_team * oswpt,int duration,int heading,int pitch,int bank,bool apply_all_rotate,int up,int sideways,int forward,bool apply_all_lat,int maneuver_flags)8263 void sexp_set_oswpt_maneuver(object_ship_wing_point_team *oswpt, int duration, int heading, int pitch, int bank, bool apply_all_rotate, int up, int sideways, int forward, bool apply_all_lat, int maneuver_flags)
8264 {
8265 	Assert(oswpt);
8266 
8267 	switch (oswpt->type)
8268 	{
8269 		case OSWPT_TYPE_SHIP:
8270 			sexp_set_ship_man(oswpt->ship_entry->shipp, duration, heading, pitch, bank, apply_all_rotate, up, sideways, forward, apply_all_lat, maneuver_flags);
8271 			break;
8272 
8273 		case OSWPT_TYPE_WING:
8274 		{
8275 			for (int i = 0; i < oswpt->wingp->current_count; i++)
8276 			{
8277 				auto shipp = &Ships[oswpt->wingp->ship_index[i]];
8278 
8279 				sexp_set_ship_man(shipp, duration, heading, pitch, bank, apply_all_rotate, up, sideways, forward, apply_all_lat, maneuver_flags);
8280 			}
8281 
8282 			break;
8283 		}
8284 	}
8285 }
8286 
sexp_set_ship_maneuver(int n,int op_num)8287 void sexp_set_ship_maneuver(int n, int op_num)
8288 {
8289 	int bank = 0, heading = 0, pitch = 0;
8290 	int up = 0, sideways = 0, forward = 0;
8291 	int duration, i, temp, maneuver_flags = 0;
8292 	bool apply_all_rotate = false, apply_all_lat = false, is_nan, is_nan_forever;
8293 	object_ship_wing_point_team oswpt;
8294 
8295 	eval_object_ship_wing_point_team(&oswpt, n);
8296 
8297 	n = CDR(n);
8298 	duration = eval_num(n, is_nan, is_nan_forever);
8299 	if (is_nan || is_nan_forever)
8300 		return;
8301 
8302 	if (op_num == OP_SHIP_ROT_MANEUVER || op_num == OP_SHIP_MANEUVER) {
8303 		for(i=0;i<3;i++) {
8304 			n = CDR(n);
8305 
8306 			temp = eval_num(n, is_nan, is_nan_forever);
8307 			if (is_nan || is_nan_forever)
8308 				return;
8309 
8310 			if (i == 0)
8311 				heading = temp;
8312 			else if (i == 1)
8313 				pitch = temp;
8314 			else
8315 				bank = temp;
8316 		}
8317 
8318 		n = CDR(n);
8319 		apply_all_rotate = is_sexp_true(n);
8320 	}
8321 
8322 	if (op_num == OP_SHIP_LAT_MANEUVER || op_num == OP_SHIP_MANEUVER) {
8323 		for(i=0;i<3;i++) {
8324 			n = CDR(n);
8325 
8326 			temp = eval_num(n, is_nan, is_nan_forever);
8327 			if (is_nan || is_nan_forever)
8328 				return;
8329 
8330 			if (i == 0)
8331 				up = temp;
8332 			else if (i == 1)
8333 				sideways = temp;
8334 			else
8335 				forward = temp;
8336 		}
8337 
8338 		n = CDR(n);
8339 		apply_all_lat = is_sexp_true(n);
8340 	}
8341 
8342 	if ((bank == 0) && (pitch == 0) && (heading == 0) && !apply_all_rotate && (up == 0) && (sideways == 0) && (forward == 0) && !apply_all_lat)
8343 		return;
8344 
8345 	n = CDR(n);
8346 	if (n >= 0) {
8347 		maneuver_flags = eval_num(n, is_nan, is_nan_forever);
8348 		if (is_nan || is_nan_forever)
8349 			return;
8350 	}
8351 
8352 	sexp_set_oswpt_maneuver(&oswpt, duration, heading, pitch, bank, apply_all_rotate, up, sideways, forward, apply_all_lat, maneuver_flags);
8353 }
8354 
8355 /**
8356  * Determine when the last meaningful order was given to one or more ships.
8357  *
8358  * @return true or false depending on whether or not a meaningful order was received
8359  */
sexp_last_order_time(int n)8360 int sexp_last_order_time(int n)
8361 {
8362 	int i;
8363 	fix time;
8364 	ai_goal *aigp;
8365 	bool is_nan, is_nan_forever;
8366 
8367 	time = i2f(eval_num(n, is_nan, is_nan_forever));
8368 	if (is_nan)
8369 		return SEXP_FALSE;
8370 	if (is_nan_forever)
8371 		return SEXP_KNOWN_FALSE;
8372 	Assert ( time >= 0 );
8373 
8374 	n = CDR(n);
8375 	while ( n != -1 ) {
8376 		aigp = nullptr;
8377 
8378 		auto ship_entry = eval_ship(n);
8379 		if (ship_entry) {
8380 			if (ship_entry->status == ShipStatus::PRESENT) {
8381 				aigp = Ai_info[ship_entry->shipp->ai_index].goals;
8382 			}
8383 		} else {
8384 			auto wingp = eval_wing(n);
8385 			if (wingp && !wing_has_yet_to_arrive(wingp) && !wingp->flags[Ship::Wing_Flags::Gone]) {
8386 				aigp = wingp->ai_goals;
8387 			}
8388 		}
8389 
8390 		// if we cannot find ship or wing, return SEXP_FALSE
8391 		if (!aigp) {
8392 			return SEXP_FALSE;
8393 		}
8394 
8395 		// check the ai_goals structure and determine if there are any
8396 		// orders which are < time seconds since current mission time
8397 		for ( i = 0; i < MAX_AI_GOALS; i++ ) {
8398 			int mode;
8399 
8400 			mode = aigp->ai_mode;
8401 			if ( (mode  != AI_GOAL_NONE) && (mode != AI_GOAL_WARP) )
8402 				if ( (aigp->time + time) > Missiontime )
8403 					break;
8404 			aigp++;
8405 		}
8406 		if ( i == MAX_AI_GOALS )
8407 			return SEXP_TRUE;
8408 
8409 		n = CDR(n);
8410 	}
8411 
8412 	return SEXP_FALSE;
8413 }
8414 
8415 /**
8416  * Return the number of players in the mission
8417  */
sexp_num_players()8418 int sexp_num_players()
8419 {
8420 	int count;
8421 	object *objp;
8422 
8423 	count = 0;
8424 	for ( objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
8425 		if ( (objp->type == OBJ_SHIP) && (objp->flags[Object::Object_Flags::Player_ship]) )
8426 			count++;
8427 	}
8428 
8429 	return count;
8430 }
8431 
8432 /**
8433  * Determine if the current skill level of the game is at least the skill level given in the SEXP
8434  */
sexp_skill_level_at_least(int n)8435 int sexp_skill_level_at_least(int n)
8436 {
8437 	auto level_name = CTEXT(n);
8438 	if (!level_name)
8439 		return SEXP_KNOWN_FALSE;
8440 
8441 	for (int i = 0; i < NUM_SKILL_LEVELS; ++i) {
8442 		if ( !stricmp(level_name, Skill_level_names(i, 0)) ) {
8443 			if ( Game_skill_level >= i ){
8444 				return SEXP_TRUE;
8445 			} else {
8446 				return SEXP_FALSE;
8447 			}
8448 		}
8449 	}
8450 
8451 	// return SEXP_FALSE if not found!!!
8452 	return SEXP_FALSE;
8453 }
8454 
sexp_was_promotion_granted(int)8455 int sexp_was_promotion_granted(int  /*n*/)
8456 {
8457 	if (Player->flags & PLAYER_FLAGS_PROMOTED)
8458 		return SEXP_TRUE;
8459 
8460 	return SEXP_FALSE;
8461 }
8462 
sexp_was_medal_granted(int n)8463 int sexp_was_medal_granted(int n)
8464 {
8465 	if (n < 0) {
8466 		if (Player->stats.m_medal_earned >= 0)
8467 			return SEXP_TRUE;
8468 
8469 		return SEXP_FALSE;
8470 	}
8471 
8472 	auto medal_name = CTEXT(n);
8473 
8474 	for (int i=0; i<Num_medals; ++i) {
8475 		if (!stricmp(medal_name, Medals[i].name)) {
8476 			if (Player->stats.m_medal_earned == i) {
8477 				return SEXP_TRUE;
8478 			} else {
8479 				break;
8480 			}
8481 		}
8482 	}
8483 
8484 	return SEXP_FALSE;
8485 }
8486 
get_damage_caused(const ship_registry_entry * ship_entry,int attacker_sig)8487 float get_damage_caused(const ship_registry_entry *ship_entry, int attacker_sig)
8488 {
8489 	int idx;
8490 	float damage_total = 0.0f;
8491 
8492 	// is the ship that took damage on the exit list?
8493 	if (ship_entry->exited_index >= 0) {
8494 		//TO DO - Add code to check the damage ships which have exited have taken
8495 
8496 		for (idx = 0; idx < MAX_DAMAGE_SLOTS; idx++) {
8497 			if (Ships_exited[ship_entry->exited_index].damage_ship_id[idx] == attacker_sig) {
8498 				damage_total += Ships_exited[ship_entry->exited_index].damage_ship[idx];
8499 				break;
8500 			}
8501 		}
8502 	}
8503 	// is it referenceable?
8504 	else if (ship_entry->shipp) {
8505 		for (idx = 0; idx < MAX_DAMAGE_SLOTS; idx++) {
8506 			if (ship_entry->shipp->damage_ship_id[idx] == attacker_sig) {
8507 				damage_total += ship_entry->shipp->damage_ship[idx];
8508 				break;
8509 			}
8510 		}
8511 	}
8512 
8513 	return damage_total;
8514 }
8515 
8516 // Karajorma
sexp_get_damage_caused(int node)8517 int sexp_get_damage_caused(int node)
8518 {
8519 	int ship_class, attacker_sig;
8520 	float damage_caused = 0.0f;
8521 
8522 	auto ship_entry = eval_ship(node);
8523 	if (!ship_entry)
8524 		return SEXP_NAN;
8525 
8526 	// a ship which hasn't arrived can't have taken any damage yet
8527 	if (ship_entry->status == ShipStatus::NOT_YET_PRESENT) {
8528 		return 0;
8529 	}
8530 
8531 	// this ship may have exited already.
8532 	if (ship_entry->exited_index >= 0) {
8533 		ship_class = Ships_exited[ship_entry->exited_index].ship_class;
8534 	} else if (ship_entry->shipp) {
8535 		ship_class = ship_entry->shipp->ship_info_index;
8536 	} else {
8537 		// it probably vanished
8538 		return SEXP_NAN_FOREVER;
8539 	}
8540 
8541 	node = CDR(node);
8542 	Assert (node != -1);
8543 
8544 	// go through the list of ships who we think may have attacked the ship
8545 	for ( ; node != -1; node = CDR(node) ) {
8546 		auto attacker = eval_ship(node);
8547 
8548 		if (!attacker || attacker->status == ShipStatus::NOT_YET_PRESENT) {
8549 			continue;
8550 		}
8551 
8552 		// this ship may have exited already.
8553 		if (attacker->exited_index >= 0) {
8554 			attacker_sig = Ships_exited[attacker->exited_index].obj_signature;
8555 		} else if (attacker->shipp) {
8556 			attacker_sig = attacker->objp->signature;
8557 		} else {
8558 			// it probably vanished
8559 			continue;
8560 		}
8561 
8562 		damage_caused += get_damage_caused (ship_entry, attacker_sig);
8563 	}
8564 
8565 	Assertion((ship_class > -1) && (ship_class < ship_info_size()), "Invalid ship class '%d' passed to sexp_get_damage_caused() (should be >= 0 and < %d); get a coder!\n", ship_class, ship_info_size());
8566 	return (int) ((damage_caused/Ship_info[ship_class].max_hull_strength) * 100.0f);
8567 }
8568 
8569 /**
8570  * Returns true if the percentage of ships (and ships in wings) departed is at least the percentage given.
8571  *
8572  * what determine if we should check destroyed or departed status
8573  * Goober5000 - added disarm and disable
8574  */
sexp_percent_ships_arrive_depart_destroy_disarm_disable(int n,int what)8575 int sexp_percent_ships_arrive_depart_destroy_disarm_disable(int n, int what)
8576 {
8577 	int percent;
8578 	int total, count;
8579 	bool is_nan, is_nan_forever;
8580 
8581 	percent = eval_num(n, is_nan, is_nan_forever);
8582 	if (is_nan)
8583 		return SEXP_FALSE;
8584 	if (is_nan_forever)
8585 		return SEXP_KNOWN_FALSE;
8586 
8587 	total = 0;
8588 	count = 0;
8589 	// iterate through the rest of the ships/wings in the list and tally the departures and the
8590 	// total
8591 	for ( n = CDR(n); n != -1; n = CDR(n) ) {
8592 		// this might be a wing
8593 		auto wingp = eval_wing(n);
8594 		if (wingp) {
8595 			// for wings, we can increment the total by the total number of ships that we expect for
8596 			// this wing, and the departures by the number of departures stored for this wing
8597 			total += (wingp->wave_count * wingp->num_waves);
8598 
8599 			if ( what == OP_PERCENT_SHIPS_DEPARTED )
8600 				count += wingp->total_departed;
8601 			else if ( what == OP_PERCENT_SHIPS_DESTROYED )
8602 				count += wingp->total_destroyed;
8603 			else if ( what == OP_PERCENT_SHIPS_ARRIVED )
8604 				count += wingp->total_arrived_count;
8605 			else
8606 				Error(LOCATION, "Invalid status check '%d' for wing '%s' in sexp_percent_ships_arrive_depart_destroy_disarm_disable", what, wingp->name);
8607 		} else {
8608 			auto ship_entry = eval_ship(n);
8609 
8610 			// must be a ship, so increment the total by 1, then determine if this ship has departed
8611 			total++;
8612 
8613 			// no need to check invalid ships, or ones that haven't arrived
8614 			if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) {
8615 				continue;
8616 			}
8617 			auto name = ship_entry->name;
8618 
8619 			if ( what == OP_PERCENT_SHIPS_DEPARTED ) {
8620 				if ( mission_log_get_time(LOG_SHIP_DEPARTED, name, nullptr, nullptr) )
8621 					count++;
8622 			} else if ( what == OP_PERCENT_SHIPS_DESTROYED ) {
8623 				if ( mission_log_get_time(LOG_SHIP_DESTROYED, name, nullptr, nullptr) || mission_log_get_time(LOG_SELF_DESTRUCTED, name, nullptr, nullptr) )
8624 					count++;
8625 			} else if ( what == OP_PERCENT_SHIPS_DISABLED ) {
8626 				if ( mission_log_get_time(LOG_SHIP_DISABLED, name, nullptr, nullptr) )
8627 					count++;
8628 			} else if ( what == OP_PERCENT_SHIPS_DISARMED ) {
8629 				if ( mission_log_get_time(LOG_SHIP_DISARMED, name, nullptr, nullptr) )
8630 					count++;
8631 			} else if ( what == OP_PERCENT_SHIPS_ARRIVED ) {
8632 				if ( mission_log_get_time(LOG_SHIP_ARRIVED, name, nullptr, nullptr) )
8633 					count++;
8634 			} else
8635 				Error(LOCATION, "Invalid status check '%d' for ship '%s' in sexp_percent_ships_depart_destroy_disarm_disable", what, name);
8636 
8637 		}
8638 	}
8639 
8640 	// now, look at the percentage
8641 	if ( ((count * 100) / total) >= percent )
8642 		return SEXP_KNOWN_TRUE;
8643 	else
8644 		return SEXP_FALSE;
8645 }
8646 
8647 /**
8648  * Determine if a list of ships has departed from within a radius of a given jump node.
8649  * @return true N seconds after the list of ships have departed
8650  */
sexp_depart_node_delay(int n)8651 int sexp_depart_node_delay(int n)
8652 {
8653 	int count, num_departed;
8654 	fix delay, latest_time, this_time;
8655 	bool is_nan, is_nan_forever;
8656 
8657 	// get the delay
8658 	delay = i2f(eval_num(n, is_nan, is_nan_forever));
8659 	n = CDR(n);
8660 
8661 	if (is_nan)
8662 		return SEXP_FALSE;
8663 	if (is_nan_forever)
8664 		return SEXP_KNOWN_FALSE;
8665 
8666 	auto jump_node_name = CTEXT(n);
8667 	n = CDR(n);
8668 
8669 	// iterate through the list of ships
8670 	latest_time = 0;
8671 	count = 0;
8672 	num_departed = 0;
8673 	while ( n != -1 ) {
8674 		count++;
8675 
8676 		auto ship_entry = eval_ship(n);
8677 		if (!ship_entry)
8678 			return SEXP_FALSE;
8679 		if (ship_entry->status == ShipStatus::NOT_YET_PRESENT)
8680 			return SEXP_CANT_EVAL;
8681 
8682 		if (ship_entry->status == ShipStatus::EXITED)
8683 		{
8684 			if (mission_log_get_time(LOG_SHIP_DEPARTED, ship_entry->name, jump_node_name, &this_time)) {
8685 				num_departed++;
8686 				if (this_time > latest_time)
8687 					latest_time = this_time;
8688 			}
8689 			else
8690 				return SEXP_KNOWN_FALSE;	// exited but not departed (or not through the node)
8691 		}
8692 
8693 		n = CDR(n);
8694 	}
8695 
8696 	if ( (count == num_departed) && ((Missiontime - latest_time) >= delay) )
8697 		return SEXP_KNOWN_TRUE;
8698 	else
8699 		return SEXP_FALSE;
8700 }
8701 
8702 /**
8703  * Returns true when the listed ships/wings have all been destroyed or have departed.
8704  */
sexp_destroyed_departed_delay(int n)8705 int sexp_destroyed_departed_delay(int n)
8706 {
8707 	int count, total;
8708 	fix delay, latest_time;
8709 	bool is_nan, is_nan_forever;
8710 
8711 	Assert( n >= 0 );
8712 
8713 	// get the delay
8714 	delay = i2f(eval_num(n, is_nan, is_nan_forever));
8715 	n = CDR(n);
8716 
8717 	if (is_nan)
8718 		return SEXP_FALSE;
8719 	if (is_nan_forever)
8720 		return SEXP_KNOWN_FALSE;
8721 
8722 	count = 0;					// number destroyed or departed
8723 	total = 0;					// total number of ships/wings to check
8724 	latest_time = 0;
8725 	while ( n != -1 ) {
8726 		fix time_gone = 0;
8727 		total++;
8728 
8729 		auto wingp = eval_wing(n);
8730 		if (wingp) {
8731 			if (wing_has_yet_to_arrive(wingp)) {
8732 				return SEXP_CANT_EVAL;
8733 			}
8734 
8735 			// for wings, check the WF_GONE flag to see if there are no more ships in this wing to arrive.
8736 			if (wingp->flags[Ship::Wing_Flags::Gone]) {
8737 				// be sure to get the latest time of one of these
8738 				if (wingp->time_gone > latest_time) {
8739 					time_gone = wingp->time_gone;
8740 				}
8741 				count++;
8742 			}
8743 		} else {
8744 			auto ship_entry = eval_ship(n);
8745 			if (ship_entry) {
8746 				if (ship_entry->status == ShipStatus::NOT_YET_PRESENT) {
8747 					return SEXP_CANT_EVAL;
8748 				}
8749 
8750 				if (ship_entry->status == ShipStatus::EXITED) {
8751 					if (mission_log_get_time(LOG_SHIP_DEPARTED, ship_entry->name, nullptr, &time_gone)) {
8752 						count++;
8753 					} else if (mission_log_get_time(LOG_SHIP_DESTROYED, ship_entry->name, nullptr, &time_gone)) {
8754 						count++;
8755 					} else if (mission_log_get_time(LOG_SELF_DESTRUCTED, ship_entry->name, nullptr, &time_gone)) {
8756 						count++;
8757 					}
8758 					// apparently we don't count vanished ships
8759 					// (and we can't, because the mission log didn't record the time they left)
8760 					else {
8761 						return SEXP_KNOWN_FALSE;
8762 					}
8763 				}
8764 			}
8765 		}
8766 
8767 		// check our latest time
8768 		if ( time_gone > latest_time ){
8769 			latest_time = time_gone;
8770 		}
8771 
8772 		n = CDR(n);
8773 	}
8774 
8775 	if ( (count == total) && (Missiontime > (latest_time + delay)) )
8776 		return SEXP_KNOWN_TRUE;
8777 	else
8778 		return SEXP_FALSE;
8779 }
8780 
sexp_special_warpout_name(int node)8781 void sexp_special_warpout_name( int node )
8782 {
8783 	auto ship_entry = eval_ship(node);
8784 	if (!ship_entry || !ship_entry->shipp)
8785 		return;
8786 
8787 	auto knossos_entry = eval_ship(CDR(node));
8788 	if (!knossos_entry || !knossos_entry->shipp)
8789 		return;
8790 
8791 	// set special warpout objnum
8792 	ship_entry->shipp->special_warpout_objnum = knossos_entry->shipp->objnum;
8793 }
8794 
8795 /**
8796  * Determines if N seconds have elapsed since all discovery of all cargo of given ships
8797  *
8798  * Goober5000 - I reworked this function to allow for the set-scanned and set-unscanned sexps
8799  * to work multiple times in a row and also to fix the potential bug where exited ships are
8800  * checked against their departure time, not against their cargo known time
8801  */
sexp_is_cargo_known(int n,bool check_delay)8802 int sexp_is_cargo_known( int n, bool check_delay )
8803 {
8804 	bool is_nan, is_nan_forever;
8805 	int count, num_known, delay;
8806 
8807 	Assert ( n >= 0 );
8808 
8809 	count = 0;
8810 	num_known = 0;
8811 
8812 	// get the delay value (if there is one)
8813 	delay = 0;
8814 	if ( check_delay )
8815 	{
8816 		delay = eval_num(n, is_nan, is_nan_forever);
8817 		n = CDR(n);
8818 
8819 		if (is_nan)
8820 			return SEXP_FALSE;
8821 		if (is_nan_forever)
8822 			return SEXP_KNOWN_FALSE;
8823 	}
8824 
8825 	while ( n != -1 )
8826 	{
8827 		fix time_known;
8828 		bool is_known = false;
8829 		count++;
8830 
8831 		// see if we have already checked this entry
8832 		if ( Sexp_nodes[n].value == SEXP_KNOWN_TRUE )
8833 		{
8834 			is_known = true;
8835 		}
8836 		else
8837 		{
8838 			auto ship_entry = eval_ship(n);
8839 			if (!ship_entry)
8840 				return SEXP_FALSE;
8841 			if (ship_entry->status == ShipStatus::NOT_YET_PRESENT)
8842 				return SEXP_CANT_EVAL;
8843 
8844 			// see if the ship has already exited the mission (either through departure or destruction)
8845 			if (ship_entry->exited_index >= 0)
8846 			{
8847 				// if not known, the whole thing is known false
8848 				if ( !(Ships_exited[ship_entry->exited_index].flags[Ship::Exit_Flags::Cargo_known]) )
8849 					return SEXP_KNOWN_FALSE;
8850 
8851 				// check the delay of when we found out
8852 				time_known = Missiontime - Ships_exited[ship_entry->exited_index].time_cargo_revealed;
8853 				if ( f2i(time_known) >= delay )
8854 				{
8855 					is_known = true;
8856 
8857 					// here is the only place in the new sexp that this can be known true
8858 					Sexp_nodes[n].value = SEXP_KNOWN_TRUE;
8859 				}
8860 			}
8861 			// ship is in mission
8862 			else if (ship_entry->shipp)
8863 			{
8864 				if ( ship_entry->shipp->flags[Ship::Ship_Flags::Cargo_revealed] )
8865 				{
8866 					time_known = Missiontime - ship_entry->shipp->time_cargo_revealed;
8867 					if ( f2i(time_known) >= delay )
8868 						is_known = true;
8869 				}
8870 			}
8871 			// ship probably vanished
8872 			else
8873 				return SEXP_NAN_FOREVER;
8874 		}
8875 
8876 		// if cargo is known, mark our variable, but not the sexp, because it may change later
8877 		if ( is_known )
8878 		{
8879 			num_known++;
8880 		}
8881 
8882 		n = CDR(n);
8883 	}
8884 
8885 	Directive_count += count - num_known;
8886 	if ( count == num_known )
8887 		return SEXP_TRUE;
8888 	else
8889 		return SEXP_FALSE;
8890 }
8891 
get_cap_subsys_cargo_flags(const ship * shipp,const char * subsys_name,int * known,fix * time_revealed)8892 void get_cap_subsys_cargo_flags(const ship *shipp, const char *subsys_name, int *known, fix *time_revealed)
8893 {
8894 	// find the ship subsystem
8895 	ship_subsys *ss = ship_get_subsys(shipp, subsys_name);
8896 	if (ss)
8897 	{
8898 		// set the flags
8899 		*known = (ss->flags[Ship::Subsystem_Flags::Cargo_revealed]);
8900 		*time_revealed = ss->time_subsys_cargo_revealed;
8901 	}
8902 	// if we didn't find the subsystem, the ship hasn't arrived yet
8903 	else
8904 	{
8905 		*known = -1;
8906 		*time_revealed = 0;
8907 	}
8908 }
8909 
8910 // reworked by Goober5000 to allow for set-scanned and set-unscanned to be used more than once
sexp_cap_subsys_cargo_known_delay(int n)8911 int sexp_cap_subsys_cargo_known_delay(int n)
8912 {
8913 	bool is_nan, is_nan_forever;
8914 	int delay, count, num_known;
8915 
8916 	num_known = 0;
8917 	count = 0;
8918 
8919 	Assert( n >= 0 );
8920 
8921 	// get delay
8922 	delay = eval_num(n, is_nan, is_nan_forever);
8923 	n = CDR(n);
8924 
8925 	if (is_nan)
8926 		return SEXP_FALSE;
8927 	if (is_nan_forever)
8928 		return SEXP_KNOWN_FALSE;
8929 
8930 	// get ship
8931 	auto ship_entry = eval_ship(n);
8932 	if (!ship_entry)
8933 		return SEXP_FALSE;
8934 	if (ship_entry->status == ShipStatus::NOT_YET_PRESENT)
8935 		return SEXP_CANT_EVAL;
8936 	n = CDR(n);
8937 
8938 	while ( n != -1 )
8939 	{
8940 		fix time_known;
8941 		bool is_known = false;
8942 		count++;
8943 
8944 		// see if we have already checked this entry
8945 		if ( Sexp_nodes[n].value == SEXP_KNOWN_TRUE )
8946 		{
8947 			is_known = true;
8948 		}
8949 		else
8950 		{
8951 			// get subsys name
8952 			auto subsys_name = CTEXT(n);
8953 
8954 			// see if the ship has already exited the mission (either through departure or destruction)
8955 			if (ship_entry->status == ShipStatus::EXITED)
8956 			{
8957 				// check the delay of when we found out...
8958 				// Since there is no way to keep track of subsystem status once a ship has departed
8959 				// or has been destroyed, check the mission log.  This will work in 99.9999999% of
8960 				// all cases; however, if the mission designer repeatedly sets and resets the scanned
8961 				// status of the subsystem, the mission log will only return the first occurrence of the
8962 				// subsystem cargo being revealed (regardless of whether it was first hidden using
8963 				// set-unscanned).  Normally, ships keep track of cargo data in the subsystem struct,
8964 				// but once/ the ship has left the mission, the subsystem linked list is purged,
8965 				// causing the loss of this information.  I judged the significant rework of the
8966 				// subsystem code not worth the rare instance that this sexp may be required to work
8967 				// in this way, especially since this problem only occurs after the ship departs.  If
8968 				// the mission designer really needs this functionality, he or she can achieve the
8969 				// same result with creative combinations of event chaining and is-event-true.
8970 				if (!mission_log_get_time(LOG_CAP_SUBSYS_CARGO_REVEALED, ship_entry->name, subsys_name, &time_known))
8971 				{
8972 					// if not known, the whole thing is known false
8973 					return SEXP_KNOWN_FALSE;
8974 				}
8975 
8976 				if (f2i(Missiontime - time_known) >= delay)
8977 				{
8978 					is_known = true;
8979 
8980 					// here is the only place in the new sexp that this can be known true
8981 					Sexp_nodes[n].value = SEXP_KNOWN_TRUE;
8982 				}
8983 			}
8984 			// ship is in mission
8985 			else
8986 			{
8987 				int cargo_revealed(0);
8988 				fix time_revealed(0);
8989 
8990 				// get flags
8991 				get_cap_subsys_cargo_flags(ship_entry->shipp, subsys_name, &cargo_revealed, &time_revealed);
8992 
8993 				if (cargo_revealed)
8994 				{
8995 					time_known = Missiontime - time_revealed;
8996 					if ( f2i(time_known) >= delay )
8997 					{
8998 						is_known = true;
8999 					}
9000 				}
9001 			}
9002 		}
9003 
9004 		// if cargo is known, mark our variable, but not the sexp, because it may change later
9005 		if (is_known)
9006 		{
9007 			num_known++;
9008 		}
9009 
9010 		n = CDR(n);
9011 	}
9012 
9013 	Directive_count += count - num_known;
9014 	if ( count == num_known )
9015 		return SEXP_TRUE;
9016 	else
9017 		return SEXP_FALSE;
9018 }
9019 
9020 // Goober5000
sexp_set_scanned_unscanned(int n,int flag)9021 void sexp_set_scanned_unscanned(int n, int flag)
9022 {
9023 	// get ship
9024 	auto ship_entry = eval_ship(n);
9025 	if (!ship_entry || !ship_entry->shipp)
9026 		return;
9027 
9028 	// check for possible next optional argument: subsystem
9029 	n = CDR(n);
9030 
9031 	// if no subsystem specified, just do it for the ship and exit
9032 	if (n == -1)
9033 	{
9034 		if (flag)
9035 			ship_do_cargo_revealed(ship_entry->shipp);
9036 		else
9037 			ship_do_cargo_hidden(ship_entry->shipp);
9038 
9039 		return;
9040 	}
9041 
9042 	// iterate through all subsystems
9043 	while (n != -1)
9044 	{
9045 		auto subsys_name = CTEXT(n);
9046 
9047 		// find the ship subsystem
9048 		auto ss = ship_get_subsys(ship_entry->shipp, subsys_name);
9049 		if (ss)
9050 		{
9051 			// do it for the subsystem
9052 			if (flag)
9053 				ship_do_cap_subsys_cargo_revealed(ship_entry->shipp, ss);
9054 			else
9055 				ship_do_cap_subsys_cargo_hidden(ship_entry->shipp, ss);
9056 		}
9057 
9058 		// if we didn't find the subsystem -- bad
9059 		if (!ss && ship_class_unchanged(ship_entry)) {
9060 			Warning(LOCATION, "Couldn't find subsystem '%s' on ship '%s' in sexp_set_scanned_unscanned", subsys_name, ship_entry->name);
9061 		}
9062 
9063 		// but if it did, loop again
9064 		n = CDR(n);
9065 	}
9066 }
9067 
sexp_has_been_tagged_delay(int n)9068 int sexp_has_been_tagged_delay(int n)
9069 {
9070 	bool is_nan, is_nan_forever;
9071 	int count, num_known, delay;
9072 
9073 	Assert ( n >= 0 );
9074 
9075 	count = 0;
9076 	num_known = 0;
9077 
9078 	// get the delay value
9079 	delay = eval_num(n, is_nan, is_nan_forever);
9080 	n = CDR(n);
9081 
9082 	if (is_nan)
9083 		return SEXP_FALSE;
9084 	if (is_nan_forever)
9085 		return SEXP_KNOWN_FALSE;
9086 
9087 	while ( n != -1 )
9088 	{
9089 		fix time_known;
9090 		bool is_known = false;
9091 		count++;
9092 
9093 		// see if we have already checked this entry
9094 		if ( Sexp_nodes[n].value == SEXP_KNOWN_TRUE )
9095 		{
9096 			is_known = true;
9097 		}
9098 		else
9099 		{
9100 			auto ship_entry = eval_ship(n);
9101 			if (!ship_entry)
9102 				return SEXP_FALSE;
9103 			if (ship_entry->status == ShipStatus::NOT_YET_PRESENT)
9104 				return SEXP_CANT_EVAL;
9105 
9106 			// see if the ship has already exited the mission (either through departure or destruction).  If so,
9107 			// grab the status of the tag from this list
9108 			if (ship_entry->exited_index >= 0)
9109 			{
9110 				if ( !(Ships_exited[ship_entry->exited_index].flags[Ship::Exit_Flags::Been_tagged]) )
9111 					return SEXP_KNOWN_FALSE;
9112 
9113 				// check the delay of when we found out.  We use the ship died time which isn't entirely accurate
9114 				// but won't cause huge delays.
9115 				time_known = Missiontime - Ships_exited[ship_entry->exited_index].time;
9116 				if ( f2i(time_known) >= delay )
9117 					is_known = true;
9118 			}
9119 			// ship is in mission
9120 			else if (ship_entry->shipp)
9121 			{
9122 				if ( ship_entry->shipp->time_first_tagged != 0 )
9123 				{
9124 					time_known = Missiontime - ship_entry->shipp->time_first_tagged;
9125 					if ( f2i(time_known) >= delay )
9126 						is_known = true;
9127 				}
9128 			}
9129 			// ship probably vanished
9130 			else
9131 				return SEXP_NAN_FOREVER;
9132 		}
9133 
9134 		// if ship is tagged, mark our variable and this sexpression.
9135 		if ( is_known )
9136 		{
9137 			num_known++;
9138 			Sexp_nodes[n].value = SEXP_KNOWN_TRUE;
9139 		}
9140 
9141 		n = CDR(n);
9142 	}
9143 
9144 	Directive_count += count - num_known;
9145 	if ( count == num_known )
9146 		return SEXP_KNOWN_TRUE;
9147 	else
9148 		return SEXP_FALSE;
9149 }
9150 
9151 // Karajorma
eval_when_for_each_special_argument(int cur_node)9152 void eval_when_for_each_special_argument( int cur_node )
9153 {
9154 	arg_item *ptr;
9155 
9156 	// loop through all the supplied arguments
9157 	ptr = Sexp_applicable_argument_list.get_next();
9158 	while (ptr != nullptr)
9159 	{
9160 		// acquire argument to be used
9161 		Sexp_replacement_arguments.emplace_back(ptr->text, ptr->node);
9162 
9163 		Sexp_current_argument_nesting_level++;
9164 		Sexp_applicable_argument_list.add_data(ptr->text, ptr->node);
9165 
9166 		// execute sexp... CTEXT will insert the argument as necessary
9167 		eval_sexp(cur_node);
9168 
9169 		// clean up any special sexp stuff
9170 		Sexp_applicable_argument_list.clear_nesting_level();
9171 		Sexp_current_argument_nesting_level--;
9172 
9173 		// remove the argument
9174 		Sexp_replacement_arguments.pop_back();
9175 
9176 		// continue along argument list
9177 		ptr = ptr->get_next();
9178 	}
9179 }
9180 
9181 
9182 // Goober5000
do_action_for_each_special_argument(int cur_node)9183 void do_action_for_each_special_argument( int cur_node )
9184 {
9185 	arg_item *ptr;
9186 
9187 	// loop through all the supplied arguments
9188 	ptr = Sexp_applicable_argument_list.get_next();
9189 	while (ptr != nullptr)
9190 	{
9191 		// acquire argument to be used
9192 		Sexp_replacement_arguments.emplace_back(ptr->text, ptr->node);
9193 
9194 		// execute sexp... CTEXT will insert the argument as necessary
9195 		// (since these are all actions, they don't return any meaningful values)
9196 		eval_sexp(cur_node);
9197 
9198 		// remove the argument
9199 		Sexp_replacement_arguments.pop_back();
9200 		// continue along argument list
9201 		ptr = ptr->get_next();
9202 	}
9203 }
9204 
9205 // Goober5000
special_argument_appears_in_sexp_tree(int node)9206 bool special_argument_appears_in_sexp_tree(int node)
9207 {
9208 	// empty tree
9209 	if (node < 0)
9210 		return false;
9211 
9212 	// cached?
9213 	if (Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_TREE)
9214 		return true;
9215 	if (Sexp_nodes[node].flags & SNF_SPECIAL_ARG_NOT_IN_TREE)
9216 		return false;
9217 
9218 	// special argument?
9219 	if (Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_NODE)
9220 		return true;
9221 
9222 	// we don't want to include special arguments if they are nested in a new argument SEXP
9223 	if (Sexp_nodes[node].type == SEXP_ATOM && Sexp_nodes[node].subtype == SEXP_ATOM_OPERATOR) {
9224 		if (is_blank_argument_op(get_operator_const(node))) {
9225 			return false;
9226 		}
9227 	}
9228 
9229 	bool result = special_argument_appears_in_sexp_tree(CAR(node))
9230 				|| special_argument_appears_in_sexp_tree(CDR(node));
9231 
9232 	// cache this result
9233 	if (result)
9234 		Sexp_nodes[node].flags |= SNF_SPECIAL_ARG_IN_TREE;
9235 	else
9236 		Sexp_nodes[node].flags |= SNF_SPECIAL_ARG_NOT_IN_TREE;
9237 
9238 	return result;
9239 }
9240 
9241 // Goober5000
special_argument_appears_in_sexp_list(int node)9242 bool special_argument_appears_in_sexp_list(int node)
9243 {
9244 	// look through list
9245 	while (node != -1)
9246 	{
9247 		// special argument?
9248 		if (Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_NODE)
9249 			return true;
9250 
9251 		node = CDR(node);
9252 	}
9253 
9254 	return false;
9255 }
9256 
9257 // conditional sexpressions follow
9258 
9259 // Goober5000
eval_when_do_one_exp(int exp)9260 void eval_when_do_one_exp(int exp)
9261 {
9262 	arg_item *ptr;
9263 	int do_node;
9264 
9265 	switch (get_operator_const(exp))
9266 	{
9267 		// if the op is a conditional then we just evaluate it
9268 		case OP_WHEN:
9269 		case OP_EVERY_TIME:
9270 		case OP_IF_THEN_ELSE:
9271 			// need to account for the possibility this call uses <arguments>
9272 			if (special_argument_appears_in_sexp_tree(exp)) {
9273 				ptr = Sexp_applicable_argument_list.get_next();
9274 				if (ptr != nullptr) {
9275 					eval_when_for_each_special_argument(exp);
9276 				}
9277 				else {
9278 					eval_sexp(exp);
9279 				}
9280 			}
9281 			else {
9282 				eval_sexp(exp);
9283 			}
9284 			break;
9285 
9286 		case OP_DO_FOR_VALID_ARGUMENTS:
9287 			if (special_argument_appears_in_sexp_tree(exp)) {
9288 				Warning(LOCATION, "<Argument> used within do-for-valid-arguments SEXP. Skipping entire SEXP");
9289 				break;
9290 			}
9291 
9292 			do_node = CDR(exp);
9293 			while (do_node != -1) {
9294 				do_action_for_each_special_argument(do_node);
9295 				do_node = CDR(do_node);
9296 			}
9297 			break;
9298 
9299 		case OP_WHEN_ARGUMENT:
9300 		case OP_EVERY_TIME_ARGUMENT:
9301 			eval_sexp(exp);
9302 			break;
9303 
9304 		// otherwise we need to check if arguments are used
9305 		default:
9306 			// if we're using the special argument in this action
9307 			if (special_argument_appears_in_sexp_tree(exp))
9308 			{
9309 				do_action_for_each_special_argument(exp);			// these sexps eval'd only for side effects
9310 			}
9311 			// if not, just evaluate it once as-is
9312 			else
9313 			{
9314 				// Goober5000 - possible bug? (see when val is used below)
9315 				/*val = */eval_sexp(exp);							// these sexps eval'd only for side effects
9316 			}
9317 	}
9318 }
9319 
9320 
9321 // Karajorma
eval_when_do_all_exp(int all_actions,int when_op_num)9322 void eval_when_do_all_exp(int all_actions, int when_op_num)
9323 {
9324 	arg_item *ptr;
9325 	int exp;
9326 	int actions;
9327 	int op_num;
9328 
9329 	bool first_loop = true;
9330 
9331 	// loop through all the supplied arguments
9332 	ptr = Sexp_applicable_argument_list.get_next();
9333 
9334 	while (ptr != nullptr)
9335 	{
9336 		// acquire argument to be used
9337 		Sexp_replacement_arguments.emplace_back(ptr->text, ptr->node);
9338 		actions = all_actions;
9339 
9340 		while (actions != -1)
9341 		{
9342 			exp = CAR(actions);
9343 
9344 			op_num = get_operator_const(exp);
9345 
9346 			if (op_num == OP_DO_FOR_VALID_ARGUMENTS) {
9347 				int do_node = CDR(exp);
9348 				while (do_node != -1) {
9349 					eval_sexp(do_node);
9350 					do_node = CDR(do_node);
9351 				}
9352 			}
9353 			else if ( first_loop || special_argument_appears_in_sexp_tree(exp) ) {
9354 				switch (op_num)
9355 				{
9356 					// if the op is a conditional we have to make sure that it can access arguments
9357 					case OP_WHEN:
9358 					case OP_EVERY_TIME:
9359 					case OP_IF_THEN_ELSE:
9360 						Sexp_current_argument_nesting_level++;
9361 						Sexp_applicable_argument_list.add_data(ptr->text, ptr->node);
9362 						eval_sexp(exp);
9363 						Sexp_applicable_argument_list.clear_nesting_level();
9364 						Sexp_current_argument_nesting_level--;
9365 						break;
9366 
9367 					default:
9368 						eval_sexp(exp);
9369 				}
9370 			}
9371 
9372 			// iterate
9373 			actions = CDR(actions);
9374 
9375 			// if-then-else only has one "if" action
9376 			if (when_op_num == OP_IF_THEN_ELSE)
9377 				break;
9378 		}
9379 
9380 		first_loop = false;
9381 
9382 		// remove the argument
9383 		Sexp_replacement_arguments.pop_back();
9384 		// continue along argument list
9385 		ptr = ptr->get_next();
9386 	}
9387 }
9388 
9389 /**
9390  * This is like using when, but it takes a lot of shortcuts.  It's clearer just to separate it out into its own function, especially since it's not supposed to start
9391  * a new level of special argument handling, like eval_when would do.  It's a lot like the original retail version of eval_when!
9392  */
eval_perform_actions(int n)9393 int eval_perform_actions(int n)
9394 {
9395 	int cond, val, actions;
9396 	Assert( n >= 0 );
9397 
9398 	cond = CAR(n);
9399 	actions = CDR(n);
9400 
9401 	// evaluate the conditional to see what value we eventually return
9402 	val = eval_sexp(cond);
9403 
9404 	// perform all the actions in the rest of the sexp
9405 	// (Since we are technically inside a condition already, no special argument handling is needed.  The special argument, if any,
9406 	// will have been provided by a higher level of nesting.)
9407 	while (actions != -1)
9408 	{
9409 		// get the operator
9410 		int exp = CAR(actions);
9411 		if (exp != -1)
9412 			eval_sexp(exp);
9413 
9414 		// iterate
9415 		actions = CDR(actions);
9416 	}
9417 
9418 	// return whatever val was, but don't return known-*
9419 	// note: SEXP_KNOWN_TRUE/SEXP_KNOWN_FALSE are never returned from eval_sexp
9420 	return val;
9421 }
9422 
eval_switch(int n)9423 void eval_switch(int n)
9424 {
9425 	bool is_nan, is_nan_forever;
9426 
9427 	int choice = eval_num(n, is_nan, is_nan_forever);
9428 	if (is_nan || is_nan_forever || choice < 0)
9429 		return;
9430 	n = CDR(n);
9431 
9432 	// iterate until we land on the action we want to perform
9433 	while (choice > 0 && n >= 0)
9434 	{
9435 		--choice;
9436 		n = CDR(n);
9437 	}
9438 
9439 	// out of range?
9440 	if (n < 0)
9441 		return;
9442 
9443 	// do it
9444 	int exp = CAR(n);
9445 	if (exp >= 0)
9446 		eval_sexp(exp);
9447 }
9448 
9449 /**
9450  * Evaluates the when conditional
9451  *
9452  * @note Goober5000 - added capability for arguments
9453  * @note Goober5000 - and also if-then-else and perform-actions
9454  */
eval_when(int n,int when_op_num)9455 int eval_when(int n, int when_op_num)
9456 {
9457 	int arg_handler = -1, cond, val, actions;
9458 	Assert( n >= 0 );
9459 	arg_item *ptr;
9460 
9461 	// get the parts of the sexp and evaluate the conditional
9462 	if (is_blank_argument_op(when_op_num))
9463 	{
9464 		arg_handler = CAR(n);
9465 		cond = CADR(n);
9466 		actions = CDDR(n);
9467 
9468 		Sexp_current_argument_nesting_level++;
9469 		// evaluate for custom arguments
9470 		val = eval_sexp(arg_handler, cond);
9471 	}
9472 	// normal evaluation
9473 	else
9474 	{
9475 		cond = CAR(n);
9476 		actions = CDR(n);
9477 
9478 		// evaluate just as-is
9479 		val = eval_sexp(cond);
9480 	}
9481 
9482 
9483 	// if value is true, perform the actions in the 'then' part
9484 	if (val == SEXP_TRUE) // note: SEXP_KNOWN_TRUE is never returned from eval_sexp
9485 	{
9486 		// get the operator
9487 		int exp = CAR(actions);
9488 
9489 		// if the mod.tbl setting is in effect we want to each evaluate all the SEXPs for
9490 		// each argument
9491 		if (True_loop_argument_sexps && special_argument_appears_in_sexp_tree(actions)) {
9492 			if (exp != -1) {
9493 				eval_when_do_all_exp(actions, when_op_num);
9494 			}
9495 		}
9496 		// without the mod.tbl setting (or if there are no arguments in this SEXP) we loop
9497 		// through every action performing them for all arguments
9498 		else {
9499 			while (actions != -1)
9500 			{
9501 				// get the operator
9502 				exp = CAR(actions);
9503 				if (exp != -1)
9504 					eval_when_do_one_exp(exp);
9505 
9506 				// iterate
9507 				actions = CDR(actions);
9508 
9509 				// if-then-else only has one "if" action
9510 				if (when_op_num == OP_IF_THEN_ELSE)
9511 					break;
9512 			}
9513 		}
9514 	}
9515 	// if-then-else has actions to perform under "else"
9516 	else if (val == SEXP_FALSE && when_op_num == OP_IF_THEN_ELSE) // note: SEXP_KNOWN_FALSE is never returned from eval_sexp
9517 	{
9518 		// skip past the "if" action
9519 		actions = CDR(actions);
9520 
9521 		// loop through every action
9522 		while (actions != -1)
9523 		{
9524 			// get the operator
9525 			int exp = CAR(actions);
9526 			if (exp != -1)
9527 				eval_when_do_one_exp(exp);
9528 
9529 			// iterate
9530 			actions = CDR(actions);
9531 		}
9532 
9533 		// invert val so that we behave like a when with opposite results
9534 		// note: SEXP_KNOWN_FALSE is never returned from eval_sexp
9535 		val = SEXP_TRUE;
9536 	}
9537 
9538 	if (is_blank_argument_op(when_op_num))
9539 	{
9540 		if (Log_event) {
9541 			ptr = Sexp_applicable_argument_list.get_next();
9542 			while(ptr != nullptr) {
9543 				// See if we have an argument.
9544 				Current_event_log_argument_buffer->push_back(ptr->text);
9545 				ptr = ptr->get_next();
9546 			}
9547 		}
9548 
9549 		// clean up any special sexp stuff
9550 		Sexp_applicable_argument_list.clear_nesting_level();
9551 		Sexp_current_argument_nesting_level--;
9552 	}
9553 
9554 	// thanks to MageKing17 for noticing that we need to short-circuit on the correct node!
9555 	int short_circuit_node = (arg_handler >= 0) ? arg_handler : cond;
9556 
9557 	if (Sexp_nodes[short_circuit_node].value == SEXP_KNOWN_FALSE || Sexp_nodes[short_circuit_node].value == SEXP_NAN_FOREVER)
9558 		return SEXP_KNOWN_FALSE;  // no need to waste time on this anymore
9559 
9560 	// note: val can't be SEXP_KNOWN_FALSE at this point
9561 
9562 	return val;
9563 }
9564 
9565 /**
9566  * Evaluate the conditional
9567  */
eval_cond(int n)9568 int eval_cond(int n)
9569 {
9570 	int cond = 0, node, val = SEXP_FALSE;
9571 
9572 	Assert (n >= 0);
9573 	while (n >= 0)
9574 	{
9575 		node = CAR(n);
9576 		cond = CAR(node);
9577 		val = eval_sexp(cond);
9578 
9579 		// if the conditional evaluated to true, then we must evaluate the rest of the expression returning
9580 		// the value of this evaluation
9581 		if (val == SEXP_TRUE) // note: any SEXP_KNOWN_TRUE result is returned as SEXP_TRUE
9582 		{
9583 			int actions, exp;
9584 
9585 			val = SEXP_FALSE;
9586 			actions = CDR(node);
9587 			while (actions >= 0)
9588 			{
9589 				exp = CAR(actions);
9590 				if (exp >= -1)
9591 					val = eval_sexp(exp);								// these sexp evaled only for side effects
9592 
9593 				actions = CDR(actions);
9594 			}
9595 
9596 			break;
9597 		}
9598 
9599 		// move onto the next cond clause
9600 		n = CDR(n);
9601 	}
9602 
9603 	return val;
9604 }
9605 
9606 // Goober5000
9607 // NOTE: if you change this function, check to see if the following function should also be changed!
test_argument_nodes_for_condition(int n,int condition_node,int * num_true,int * num_false,int * num_known_true,int * num_known_false,int threshold=-1)9608 int test_argument_nodes_for_condition(int n, int condition_node, int *num_true, int *num_false, int *num_known_true, int *num_known_false, int threshold = -1)
9609 {
9610 	int val, num_valid_arguments;
9611 	SCP_vector<std::pair<char*, int>> Applicable_arguments_temp;
9612 	Assert(n != -1 && condition_node != -1);
9613 	Assert((num_true != nullptr) && (num_false != nullptr) && (num_known_true != nullptr) && (num_known_false != nullptr));
9614 
9615 	// ensure special argument list is empty
9616 	Sexp_applicable_argument_list.clear_nesting_level();
9617 	Applicable_arguments_temp.clear();
9618 
9619 	// ditto for counters
9620 	num_valid_arguments = 0;
9621 	*num_true = 0;
9622 	*num_false = 0;
9623 	*num_known_true = 0;
9624 	*num_known_false = 0;
9625 
9626 	// loop through all arguments
9627 	while (n != -1)
9628 	{
9629 		// only eval this argument if it's valid
9630 		if (Sexp_nodes[n].flags & SNF_ARGUMENT_VALID)
9631 		{
9632 			// evaluate conditional for current argument
9633 			Sexp_replacement_arguments.emplace_back(Sexp_nodes[n].text, n);
9634 			val = eval_sexp(condition_node);
9635 			if ( Sexp_nodes[condition_node].value == SEXP_KNOWN_TRUE ||
9636 					Sexp_nodes[condition_node].value == SEXP_KNOWN_FALSE) {
9637 				val = Sexp_nodes[condition_node].value;
9638 			} else if ( Sexp_nodes[condition_node].value == SEXP_NAN_FOREVER ) {
9639 				// In accordance with SEXP_NAN/SEXP_NAN_FOREVER becoming SEXP_FALSE in eval_sexp()
9640 				val = SEXP_KNOWN_FALSE;
9641 			}
9642 
9643 			switch (val)
9644 			{
9645 				case SEXP_TRUE:
9646 					(*num_true)++;
9647 					Applicable_arguments_temp.emplace_back(Sexp_nodes[n].text, n);
9648 					break;
9649 
9650 				case SEXP_FALSE:
9651 					(*num_false)++;
9652 					break;
9653 
9654 				case SEXP_KNOWN_TRUE:
9655 					(*num_known_true)++;
9656 					Applicable_arguments_temp.emplace_back(Sexp_nodes[n].text, n);
9657 					break;
9658 
9659 				case SEXP_KNOWN_FALSE:
9660 					(*num_known_false)++;
9661 					break;
9662 			}
9663 
9664 			// clear argument, but not list, as we'll need it later
9665 			Sexp_replacement_arguments.pop_back();
9666 
9667 			// increment
9668 			num_valid_arguments++;
9669 		}
9670 
9671 		// continue along argument list
9672 		n = CDR(n);
9673 	}
9674 
9675 	int count = 0;
9676 	// now we write from the temporary store into the real one, reversing the order. We do this because
9677 	// Sexp_applicable_argument_list is a stack and we want the first argument in the list to be the first one out
9678 	while (!Applicable_arguments_temp.empty() && (threshold == -1 || count < threshold))
9679 	{
9680 		Sexp_applicable_argument_list.add_data(Applicable_arguments_temp.back());
9681 		Applicable_arguments_temp.pop_back();
9682 		++count;
9683 	}
9684 
9685 	return num_valid_arguments;
9686 }
9687 
9688 // Goober5000
9689 // NOTE: if you change this function, check to see if the previous function should also be changed!
test_argument_vector_for_condition(const SCP_vector<std::pair<char *,int>> & argument_vector,bool already_dupped,int condition_node,int * num_true,int * num_false,int * num_known_true,int * num_known_false)9690 int test_argument_vector_for_condition(const SCP_vector<std::pair<char*, int>> &argument_vector, bool already_dupped, int condition_node, int *num_true, int *num_false, int *num_known_true, int *num_known_false)
9691 {
9692 	int val, num_valid_arguments;
9693 	SCP_vector<std::pair<char*, int>> Applicable_arguments_temp;
9694 	Assert(condition_node != -1);
9695 	Assert((num_true != nullptr) && (num_false != nullptr) && (num_known_true != nullptr) && (num_known_false != nullptr));
9696 
9697 	// ensure special argument list is empty
9698 	Sexp_applicable_argument_list.clear_nesting_level();
9699 	Applicable_arguments_temp.clear();
9700 
9701 	// ditto for counters
9702 	num_valid_arguments = 0;
9703 	*num_true = 0;
9704 	*num_false = 0;
9705 	*num_known_true = 0;
9706 	*num_known_false = 0;
9707 
9708 	// loop through all arguments
9709 	for (const auto &argument : argument_vector)
9710 	{
9711 		// since we can't see or modify the validity, assume all are valid
9712 		{
9713 			// evaluate conditional for current argument
9714 			Sexp_replacement_arguments.push_back(argument);
9715 			val = eval_sexp(condition_node);
9716 			if ( Sexp_nodes[condition_node].value == SEXP_KNOWN_TRUE ||
9717 					Sexp_nodes[condition_node].value == SEXP_KNOWN_FALSE) {
9718 				val = Sexp_nodes[condition_node].value;
9719 			} else if ( Sexp_nodes[condition_node].value == SEXP_NAN_FOREVER ) {
9720 				// In accordance with SEXP_NAN/SEXP_NAN_FOREVER becoming SEXP_FALSE in eval_sexp()
9721 				val = SEXP_KNOWN_FALSE;
9722 			}
9723 
9724 			switch (val)
9725 			{
9726 				case SEXP_TRUE:
9727 					(*num_true)++;
9728 					Applicable_arguments_temp.push_back(argument);
9729 					break;
9730 
9731 				case SEXP_FALSE:
9732 					(*num_false)++;
9733 					break;
9734 
9735 				case SEXP_KNOWN_TRUE:
9736 					(*num_known_true)++;
9737 					Applicable_arguments_temp.push_back(argument);
9738 					break;
9739 
9740 				case SEXP_KNOWN_FALSE:
9741 					(*num_known_false)++;
9742 					break;
9743 			}
9744 
9745 			// if the argument was already dup'd, but not added as an applicable argument,
9746 			// we need to free it here before we cause a memory leak
9747 			if ((val == SEXP_FALSE || val == SEXP_KNOWN_FALSE) && already_dupped)
9748 				vm_free(argument.first);
9749 
9750 			// clear argument, but not list, as we'll need it later
9751 			Sexp_replacement_arguments.pop_back();
9752 
9753 			// increment
9754 			num_valid_arguments++;
9755 		}
9756 	}
9757 
9758 	// now we write from the temporary store into the real one, reversing the order. We do this because
9759 	// Sexp_applicable_argument_list is a stack and we want the first argument in the list to be the first one out
9760 	while (!Applicable_arguments_temp.empty())
9761 	{
9762 		// we need to dup the strings regardless, since we're not using Sexp_nodes[n].text as a string buffer,
9763 		// but we need to know whether the calling function dup'd them, or whether we should dup them here
9764 		if (already_dupped)
9765 			Sexp_applicable_argument_list.add_data_set_dup(Applicable_arguments_temp.back());
9766 		else
9767 			Sexp_applicable_argument_list.add_data_dup(Applicable_arguments_temp.back());
9768 
9769 		Applicable_arguments_temp.pop_back();
9770 	}
9771 
9772 	return num_valid_arguments;
9773 }
9774 
9775 // Goober5000
eval_any_of(int arg_handler_node,int condition_node)9776 int eval_any_of(int arg_handler_node, int condition_node)
9777 {
9778 	int n, num_valid_arguments, num_true, num_false, num_known_true, num_known_false;
9779 	Assert(arg_handler_node != -1 && condition_node != -1);
9780 
9781 	// the arguments should just be data, not operators, so we can skip the CAR
9782 	n = CDR(arg_handler_node);
9783 
9784 	// test the whole argument list
9785 	num_valid_arguments = test_argument_nodes_for_condition(n, condition_node, &num_true, &num_false, &num_known_true, &num_known_false);
9786 
9787 	// use the sexp_or algorithm
9788 	if (num_known_true || num_true)
9789 		return SEXP_TRUE;
9790 	else if (num_known_false == num_valid_arguments)
9791 		return SEXP_KNOWN_FALSE;
9792 	else
9793 		return SEXP_FALSE;
9794 }
9795 
9796 // Goober5000
eval_every_of(int arg_handler_node,int condition_node)9797 int eval_every_of(int arg_handler_node, int condition_node)
9798 {
9799 	int n, num_valid_arguments, num_true, num_false, num_known_true, num_known_false;
9800 	Assert(arg_handler_node != -1 && condition_node != -1);
9801 
9802 	// the arguments should just be data, not operators, so we can skip the CAR
9803 	n = CDR(arg_handler_node);
9804 
9805 	// test the whole argument list
9806 	num_valid_arguments = test_argument_nodes_for_condition(n, condition_node, &num_true, &num_false, &num_known_true, &num_known_false);
9807 
9808 	// use the sexp_and algorithm
9809 	if ((!num_valid_arguments) || num_known_false)
9810 		return SEXP_KNOWN_FALSE;
9811 	else if (num_false)
9812 		return SEXP_FALSE;
9813 	else
9814 		return SEXP_TRUE;
9815 }
9816 
9817 // Goober5000
eval_number_of(int arg_handler_node,int condition_node)9818 int eval_number_of(int arg_handler_node, int condition_node)
9819 {
9820 	bool is_nan, is_nan_forever;
9821 	int n, num_valid_arguments, num_true, num_false, num_known_true, num_known_false, threshold;
9822 	Assert(arg_handler_node != -1 && condition_node != -1);
9823 
9824 	// the arguments should just be data, not operators, so we can skip the CAR
9825 	n = CDR(arg_handler_node);
9826 
9827 	// the first argument is the number threshold
9828 	threshold = eval_num(n, is_nan, is_nan_forever);
9829 	n = CDR(n);
9830 
9831 	// test the whole argument list
9832 	num_valid_arguments = test_argument_nodes_for_condition(n, condition_node, &num_true, &num_false, &num_known_true, &num_known_false);
9833 
9834 	// we check for NaN after the conditions are evaluated, just as the logical operators evaluate all their conditions
9835 	if (is_nan)
9836 		return SEXP_FALSE;
9837 	if (is_nan_forever)
9838 		return SEXP_KNOWN_FALSE;
9839 
9840 	// use the sexp_or algorithm, modified
9841 	// (true if at least threshold arguments are true)
9842 	if (num_true + num_known_true >= threshold)
9843 		return SEXP_TRUE;
9844 	else if (num_valid_arguments - num_known_false < threshold)
9845 		return SEXP_KNOWN_FALSE;
9846 	else
9847 		return SEXP_FALSE;
9848 }
9849 
9850 // Goober5000
9851 // this works a little differently... we randomly pick one argument to use
9852 // for our condition, but this argument must be saved among sexp calls...
9853 // so we select an argument and set its flag
9854 // addendum: hook karajorma's random-multiple-of into the same sexp
eval_random_of(int arg_handler_node,int condition_node,bool multiple)9855 int eval_random_of(int arg_handler_node, int condition_node, bool multiple)
9856 {
9857 	int n = -1, i, val, num_valid_args, random_argument, num_known_false = 0;
9858 	Assert(arg_handler_node != -1 && condition_node != -1);
9859 
9860 	// get the number of valid arguments
9861 	num_valid_args = query_sexp_args_count(arg_handler_node, true);
9862 	Assert(num_valid_args >= 0);
9863 
9864 	if (num_valid_args == 0)
9865 	{
9866 		return SEXP_KNOWN_FALSE;	// Not much point in trying to evaluate it again.
9867 	}
9868 
9869 	// find which argument we picked, if we picked one
9870 	if (!multiple)
9871 	{
9872 		n = CDR(arg_handler_node);
9873 
9874 		// iterate to the argument we previously selected
9875 		for ( ; n != -1; n = CDR(n))
9876 		{
9877 			if (Sexp_nodes[n].flags & SNF_ARGUMENT_SELECT)
9878 				break;
9879 		}
9880 	}
9881 
9882 	// if argument not found (or never specified in the first place), we have to pick one
9883 	if (n == -1)
9884 	{
9885 		n = CDR(arg_handler_node);
9886 		int temp_node = n;
9887 
9888 		// pick an argument and iterate to it
9889 		random_argument = rand_internal(1, num_valid_args);
9890 		i = 0;
9891 		for (int j = 0; j < num_valid_args; temp_node = CDR(temp_node))
9892 		{
9893 			Assert(n >= 0);
9894 
9895 			// count only valid arguments
9896 			if (Sexp_nodes[temp_node].flags & SNF_ARGUMENT_VALID) {
9897 				j++;
9898 				if (i < random_argument && (++i == random_argument)) {
9899 					// Found the node we want, store it for use
9900 					n = temp_node;
9901 				}
9902 
9903 				if ((Sexp_nodes[temp_node].value == SEXP_KNOWN_FALSE) || (Sexp_nodes[temp_node].value == SEXP_NAN_FOREVER))
9904 					num_known_false++;
9905 			}
9906 		}
9907 
9908 		if (num_known_false == num_valid_args) {
9909 			return SEXP_KNOWN_FALSE;	// We're going nowhere fast.
9910 		}
9911 
9912 		// save it, if we're saving
9913 		if (!multiple)
9914 		{
9915 			Sexp_nodes[n].flags |= SNF_ARGUMENT_SELECT;
9916 		}
9917 	}
9918 
9919 	// only eval this argument if it's valid
9920 	val = SEXP_FALSE;
9921 	if (Sexp_nodes[n].flags & SNF_ARGUMENT_VALID)
9922 	{
9923 		// ensure special argument list is empty
9924 		Sexp_applicable_argument_list.clear_nesting_level();
9925 
9926 		// evaluate conditional for current argument
9927 		Sexp_replacement_arguments.emplace_back(Sexp_nodes[n].text, n);
9928 		val = eval_sexp(condition_node);
9929 
9930 		// true?
9931 		if (val == SEXP_TRUE)
9932 		{
9933 			Sexp_applicable_argument_list.add_data(Sexp_nodes[n].text, n);
9934 		}
9935 		else if ((!multiple || num_valid_args == 1) && (Sexp_nodes[condition_node].value == SEXP_KNOWN_FALSE || Sexp_nodes[condition_node].value == SEXP_NAN_FOREVER))
9936 		{
9937 			val = SEXP_KNOWN_FALSE;	// If we can't randomly pick another one and this one is guaranteed never to be true, then give up now.
9938 		}
9939 
9940 		// clear argument, but not list, as we'll need it later
9941 		Sexp_replacement_arguments.pop_back();
9942 	}
9943 
9944 	// true if our selected argument is true
9945 	return val;
9946 }
9947 
9948 // Karajorma - this conditional returns the first valid option on its list.
eval_in_sequence(int arg_handler_node,int condition_node)9949 int eval_in_sequence(int arg_handler_node, int condition_node)
9950 {
9951 	int val = SEXP_FALSE;
9952 	int n = -1 ;
9953 
9954 	Assert(arg_handler_node != -1 && condition_node != -1);
9955 
9956 	// get the first argument
9957 	n = CDR(arg_handler_node);
9958 	Assert (n != -1);
9959 
9960 	// loop through the nodes until we find one that is holds a valid argument or run out of nodes
9961 	for (int i=1 ; i<query_sexp_args_count(arg_handler_node) ; i++)
9962 	{
9963 		if (!(Sexp_nodes[n].flags & SNF_ARGUMENT_VALID)) {
9964 			n = CDR(n) ;
9965 		}
9966 		// if we've found a valid node there is no need to continue
9967 		else {
9968 			break;
9969 		}
9970 	}
9971 
9972 	// Only execute if the argument is valid (if all nodes were invalid we would still reach this point)
9973 	if (Sexp_nodes[n].flags & SNF_ARGUMENT_VALID)
9974 	{
9975 		// ensure special argument list is empty
9976 		Sexp_applicable_argument_list.clear_nesting_level();
9977 
9978 		// evaluate conditional for current argument
9979 		Sexp_replacement_arguments.emplace_back(Sexp_nodes[n].text, n);
9980 		val = eval_sexp(condition_node);
9981 
9982 		// true?
9983 		if (val == SEXP_TRUE)
9984 		{
9985 			Sexp_applicable_argument_list.add_data(Sexp_nodes[n].text, n);
9986 		}
9987 		else if ((Sexp_nodes[condition_node].value == SEXP_KNOWN_FALSE) || (Sexp_nodes[condition_node].value == SEXP_NAN_FOREVER))
9988 		{
9989 			val = SEXP_KNOWN_FALSE;	// If we're wasting our time evaluating this ever again, just go ahead and short-circuit.
9990 		}
9991 
9992 		// clear argument, but not list, as we'll need it later
9993 		Sexp_replacement_arguments.pop_back();
9994 	}
9995 
9996 	// return the value of the conditional
9997 	return val;
9998 }
9999 
10000 // Goober5000
eval_for_counter(int arg_handler_node,int condition_node,bool just_count=false)10001 int eval_for_counter(int arg_handler_node, int condition_node, bool just_count = false)
10002 {
10003 	bool is_nan, is_nan_forever;
10004 	int n, num_valid_arguments, num_true, num_false, num_known_true, num_known_false;
10005 	int i, count, counter_start, counter_stop, counter_step;
10006 	SCP_vector<std::pair<char*, int>> argument_vector;
10007 	char buf[NAME_LENGTH];
10008 	Assert(arg_handler_node != -1 && condition_node != -1);
10009 
10010 	n = CDR(arg_handler_node);
10011 
10012 	// determine the counter parameters
10013 	count = eval_nums(n, is_nan, is_nan_forever, counter_start, counter_stop, counter_step);
10014 	if (is_nan)
10015 		return SEXP_FALSE;
10016 	if (is_nan_forever)
10017 		return SEXP_KNOWN_FALSE;
10018 	if (count < 3)
10019 		counter_step = 1;
10020 
10021 	// a bunch of error checking
10022 	if (counter_step == 0)
10023 	{
10024 		Warning(LOCATION, "A counter increment of 0 is illegal!  (start=%d, stop=%d, increment=%d)", counter_start, counter_stop, counter_step);
10025 		return SEXP_KNOWN_FALSE;
10026 	}
10027 	else if (counter_start == counter_stop)
10028 	{
10029 		Warning(LOCATION, "The counter start and stop values are identical!  (start=%d, stop=%d, increment=%d)", counter_start, counter_stop, counter_step);
10030 		return SEXP_KNOWN_FALSE;
10031 	}
10032 	else if (sign(counter_stop - counter_start) != sign(counter_step))
10033 	{
10034 		Warning(LOCATION, "The counter cannot complete with the given values!  (start=%d, stop=%d, increment=%d)", counter_start, counter_stop, counter_step);
10035 		return SEXP_KNOWN_FALSE;
10036 	}
10037 
10038 	// build a vector of counter values
10039 	for (i = counter_start; ((counter_step > 0) ? i <= counter_stop : i >= counter_stop); i += counter_step)
10040 	{
10041 		sprintf(buf, "%d", i);
10042 		argument_vector.emplace_back(vm_strdup(buf), -1);
10043 		// Note: we do not call vm_free() on the contents of argument_vector, and we don't for the very good
10044 		// reason that those pointers are then passed along by test_argument_vector_for_condition(), below.
10045 		// The strings will then be freed by arg_item::expunge() or arg_item::clear_nesting_level(), or even
10046 		// inside test_argument_vector_for_condition() (if the argument doesn't satisfy the condition). So
10047 		// under no circumstances try to free these strings inside this function! It will cause a double-free
10048 		// situation, resulting in a crash at best. -MageKing17
10049 	}
10050 
10051 	if (just_count)
10052 		return static_cast<int>(argument_vector.size());
10053 
10054 	// test the whole argument vector
10055 	num_valid_arguments = test_argument_vector_for_condition(argument_vector, true, condition_node, &num_true, &num_false, &num_known_true, &num_known_false);
10056 
10057 	// use the sexp_or algorithm
10058 	if (num_known_true || num_true)
10059 		return SEXP_TRUE;
10060 	else if (num_known_false == num_valid_arguments)
10061 		return SEXP_KNOWN_FALSE;
10062 	else
10063 		return SEXP_FALSE;
10064 }
10065 
10066 // Goober5000
eval_for_ship_collection(int arg_handler_node,int condition_node,int op_const,bool just_count=false)10067 int eval_for_ship_collection(int arg_handler_node, int condition_node, int op_const, bool just_count = false)
10068 {
10069 	int n, num_valid_arguments, num_true, num_false, num_known_true, num_known_false;
10070 	SCP_vector<std::pair<char*, int>> argument_vector;
10071 	Assert(arg_handler_node != -1 && condition_node != -1);
10072 
10073 	n = CDR(arg_handler_node);
10074 
10075 	// handle each parameter
10076 	for (; n >= 0; n = CDR(n))
10077 	{
10078 		auto constraint = CTEXT(n);
10079 		int constraint_index = -1;
10080 
10081 		switch (op_const)
10082 		{
10083 			case OP_FOR_SHIP_CLASS:
10084 				constraint_index = ship_info_lookup(constraint);
10085 				break;
10086 			case OP_FOR_SHIP_TYPE:
10087 				constraint_index = ship_type_name_lookup(constraint);
10088 				break;
10089 			case OP_FOR_SHIP_TEAM:
10090 				constraint_index = iff_lookup(constraint);
10091 				break;
10092 			case OP_FOR_SHIP_SPECIES:
10093 				constraint_index = species_info_lookup(constraint);
10094 				break;
10095 		}
10096 
10097 		if (constraint_index < 0)
10098 			continue;
10099 
10100 		// add all ships of this constraint which are present in the mission
10101 		for (auto objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp))
10102 		{
10103 			if (objp->type == OBJ_SHIP)
10104 			{
10105 				auto shipp = &Ships[objp->instance];
10106 				int ship_index = -1;
10107 
10108 				switch (op_const)
10109 				{
10110 					case OP_FOR_SHIP_CLASS:
10111 						ship_index = shipp->ship_info_index;
10112 						break;
10113 					case OP_FOR_SHIP_TYPE:
10114 						ship_index = ship_class_query_general_type(shipp->ship_info_index);
10115 						break;
10116 					case OP_FOR_SHIP_TEAM:
10117 						ship_index = shipp->team;
10118 						break;
10119 					case OP_FOR_SHIP_SPECIES:
10120 						ship_index = Ship_info[shipp->ship_info_index].species;
10121 						break;
10122 				}
10123 
10124 				if (constraint_index == ship_index)
10125 					argument_vector.emplace_back(vm_strdup(shipp->ship_name), -1);
10126 			}
10127 		}
10128 	}
10129 
10130 	if (just_count)
10131 		return static_cast<int>(argument_vector.size());
10132 
10133 	// test the whole argument vector
10134 	num_valid_arguments = test_argument_vector_for_condition(argument_vector, true, condition_node, &num_true, &num_false, &num_known_true, &num_known_false);
10135 	SCP_UNUSED(num_valid_arguments);
10136 
10137 	// use the sexp_or algorithm
10138 	if (num_known_true || num_true)
10139 		return SEXP_TRUE;
10140 // Don't short-circuit because ships can arrive later on, and this sexp's ship collection is intrinsically unknowable.
10141 //	else if (num_known_false == num_valid_arguments)
10142 //		return SEXP_KNOWN_FALSE;
10143 	else
10144 		return SEXP_FALSE;
10145 }
10146 
10147 // Goober5000
eval_for_players(int arg_handler_node,int condition_node,bool just_count=false)10148 int eval_for_players(int arg_handler_node, int condition_node, bool just_count = false)
10149 {
10150 	int num_valid_arguments, num_true, num_false, num_known_true, num_known_false;
10151 	SCP_vector<std::pair<char*, int>> argument_vector;
10152 	Assert(arg_handler_node != -1 && condition_node != -1);
10153 
10154 	if (Game_mode & GM_MULTIPLAYER)
10155 	{
10156 		// this operator has no parameters, so just generate a vector of players
10157 		for (int i = 0; i < MAX_PLAYERS; ++i)
10158 		{
10159 			int shipnum = multi_get_player_ship(i);
10160 			if (shipnum >= 0)
10161 				argument_vector.emplace_back(vm_strdup(Ships[shipnum].ship_name), -1);
10162 		}
10163 	}
10164 	else
10165 	{
10166 		// in single-player it's just one ship
10167 		if (Player_ship)
10168 			argument_vector.emplace_back(vm_strdup(Player_ship->ship_name), -1);
10169 	}
10170 
10171 	if (just_count)
10172 		return static_cast<int>(argument_vector.size());
10173 
10174 	// test the whole argument vector
10175 	num_valid_arguments = test_argument_vector_for_condition(argument_vector, true, condition_node, &num_true, &num_false, &num_known_true, &num_known_false);
10176 
10177 	// use the sexp_or algorithm
10178 	if (num_known_true || num_true)
10179 		return SEXP_TRUE;
10180 	else if (num_valid_arguments > 0 && num_known_false == num_valid_arguments)
10181 		return SEXP_KNOWN_FALSE;
10182 	else
10183 		return SEXP_FALSE;
10184 }
10185 
10186 // MageKing17 - this is sort of a cross between any-of, number-of, and in-sequence
eval_first_of(int arg_handler_node,int condition_node)10187 int eval_first_of(int arg_handler_node, int condition_node)
10188 {
10189 	bool is_nan, is_nan_forever;
10190 	int n, num_valid_arguments, num_true, num_false, num_known_true, num_known_false, threshold;
10191 	Assert(arg_handler_node != -1 && condition_node != -1);
10192 
10193 	// the arguments should just be data, not operators, so we can skip the CAR
10194 	n = CDR(arg_handler_node);
10195 
10196 	// the first argument is the number threshold
10197 	threshold = eval_num(n, is_nan, is_nan_forever);
10198 	n = CDR(n);
10199 
10200 	// test the whole argument list
10201 	num_valid_arguments = test_argument_nodes_for_condition(n, condition_node, &num_true, &num_false, &num_known_true, &num_known_false, threshold);
10202 
10203 	// we check for NaN after the conditions are evaluated, just as the logical operators evaluate all their conditions
10204 	if (is_nan)
10205 		return SEXP_FALSE;
10206 	if (is_nan_forever)
10207 		return SEXP_KNOWN_FALSE;
10208 
10209 	// use the sexp_or algorithm
10210 	if (num_known_true || num_true)
10211 		return SEXP_TRUE;
10212 	else if (num_known_false == num_valid_arguments)
10213 		return SEXP_KNOWN_FALSE;
10214 	else
10215 		return SEXP_FALSE;
10216 }
10217 
sexp_change_all_argument_validity(int n,bool invalidate)10218 void sexp_change_all_argument_validity(int n, bool invalidate)
10219 {
10220 	int arg_handler, arg_n;
10221 
10222 	arg_handler = get_handler_for_x_of_operator(n);
10223 
10224 	// prevent a crash if the SEXP is used somewhere it's not supposed to be
10225 	if (arg_handler < 0)
10226 		return;
10227 
10228 	// can't change validity of for-* sexps
10229 	auto op_const = get_operator_const(arg_handler);
10230 	if (op_const == OP_FOR_COUNTER || op_const == OP_FOR_PLAYERS
10231 		|| op_const == OP_FOR_SHIP_CLASS || op_const == OP_FOR_SHIP_TYPE || op_const == OP_FOR_SHIP_TEAM || op_const == OP_FOR_SHIP_SPECIES)
10232 		return;
10233 
10234 	while (n != -1)
10235 	{
10236 		arg_n = CDR(arg_handler);
10237 		while (arg_n != -1) {
10238 			if (invalidate) {
10239 				Sexp_nodes[arg_n].flags &= ~SNF_ARGUMENT_VALID;
10240 			}
10241 			else {
10242 				Sexp_nodes[arg_n].flags |= SNF_ARGUMENT_VALID;
10243 			}
10244 			// iterate
10245 			arg_n = CDR(arg_n);
10246 		}
10247 
10248 		// iterate
10249 		n = CDR(n);
10250 	}
10251 }
10252 
sexp_num_valid_arguments(int n)10253 int sexp_num_valid_arguments( int n )
10254 {
10255 	int arg_handler, arg_n;
10256 	int matches = 0;
10257 
10258 	arg_handler = get_handler_for_x_of_operator(n);
10259 
10260 	// prevent a crash if the SEXP is used somewhere it's not supposed to be
10261 	if (arg_handler < 0)
10262 		return 0;
10263 
10264 	// the for-* sexps require special handling: they don't list their arguments explicitly but rather generate them on-the-fly
10265 	auto op_const = get_operator_const(arg_handler);
10266 	switch (op_const)
10267 	{
10268 		case OP_FOR_COUNTER:
10269 			return eval_for_counter(arg_handler, Locked_sexp_true, true);
10270 
10271 		case OP_FOR_PLAYERS:
10272 			return eval_for_players(arg_handler, Locked_sexp_true, true);
10273 
10274 		case OP_FOR_SHIP_CLASS:
10275 		case OP_FOR_SHIP_TYPE:
10276 		case OP_FOR_SHIP_TEAM:
10277 		case OP_FOR_SHIP_SPECIES:
10278 			return eval_for_ship_collection(arg_handler, Locked_sexp_true, op_const, true);
10279 	}
10280 
10281 	// loop through arguments
10282 	arg_n = CDR(arg_handler);
10283 	while (arg_n != -1) {
10284 		if (Sexp_nodes[arg_n].flags & SNF_ARGUMENT_VALID) {
10285 			matches++;
10286 		}
10287 
10288 		// iterate
10289 		arg_n = CDR(arg_n);
10290 	}
10291 
10292 	return matches;
10293 }
10294 
10295 // Goober5000
sexp_change_argument_validity(int n,bool invalidate)10296 void sexp_change_argument_validity(int n, bool invalidate)
10297 {
10298 	int arg_handler, arg_n;
10299 	bool toggled;
10300 
10301 	arg_handler = get_handler_for_x_of_operator(n);
10302 
10303 	// prevent a crash if the SEXP is used somewhere it's not supposed to be
10304 	// (thanks to woutersmits for finding this bug)
10305 	if (arg_handler < 0)
10306 		return;
10307 
10308 	// can't change validity of for-* sexps
10309 	auto op_const = get_operator_const(arg_handler);
10310 	if (op_const == OP_FOR_COUNTER || op_const == OP_FOR_PLAYERS
10311 		|| op_const == OP_FOR_SHIP_CLASS || op_const == OP_FOR_SHIP_TYPE || op_const == OP_FOR_SHIP_TEAM || op_const == OP_FOR_SHIP_SPECIES)
10312 		return;
10313 
10314 	// loop through arguments
10315 	while (n != -1)
10316 	{
10317 		toggled = false;
10318 
10319 		// first we must check if the arg_handler marks a selection. At the moment random-of is the only one that does this
10320 		arg_n = CDR(arg_handler);
10321 		while (invalidate && (arg_n != -1)) {
10322 			if (Sexp_nodes[arg_n].flags & SNF_ARGUMENT_SELECT) {
10323 				// now check if the selected argument matches the one we want to invalidate
10324 				if (!strcmp(CTEXT(n), CTEXT(arg_n))) {
10325 					// set it as invalid
10326 					Sexp_nodes[arg_n].flags &= ~SNF_ARGUMENT_VALID;
10327 					toggled = true;
10328 				}
10329 			}
10330 
10331 			// iterate
10332 			arg_n = CDR(arg_n);
10333 		}
10334 
10335 		if (!toggled) {
10336 			// search for argument in arg_handler list
10337 			arg_n = CDR(arg_handler);
10338 			while (arg_n != -1)
10339 			{
10340 				// match?
10341 				if (!strcmp(CTEXT(n), CTEXT(arg_n)))
10342 				{
10343 					if (invalidate) {
10344 						// we need to check if the argument is already invalid as some argument lists may contain duplicates
10345 						if (Sexp_nodes[arg_n].flags & SNF_ARGUMENT_VALID) {
10346 							// set it as invalid
10347 							Sexp_nodes[arg_n].flags &= ~SNF_ARGUMENT_VALID;
10348 
10349 							// exit inner loop
10350 							break;
10351 						}
10352 					}
10353 					else {
10354 						if (!(Sexp_nodes[arg_n].flags & SNF_ARGUMENT_VALID)) {
10355 							// set it as valid
10356 							Sexp_nodes[arg_n].flags |= SNF_ARGUMENT_VALID;
10357 
10358 							// exit inner loop
10359 							break;
10360 						}
10361 					}
10362 				}
10363 
10364 				// iterate
10365 				arg_n = CDR(arg_n);
10366 			}
10367 		}
10368 
10369 		// iterate
10370 		n = CDR(n);
10371 	}
10372 }
10373 
get_handler_for_x_of_operator(int n)10374 int get_handler_for_x_of_operator(int n)
10375 {
10376 	int conditional, arg_handler;
10377 
10378 	if (n < 0) {
10379 		return -1;
10380 	}
10381 
10382 	conditional = n;
10383 	do {
10384 		// find the conditional sexp
10385 		conditional = find_parent_operator(conditional);
10386 		if (conditional == -1) {
10387 			return -1;
10388 		}
10389 	}
10390 	while (!is_blank_argument_op(get_operator_const(conditional)));
10391 
10392 	// get the first op of the parent, which should be a *_of operator
10393 	arg_handler = CADR(conditional);
10394 	if (arg_handler < 0 || !is_blank_of_op(get_operator_const(arg_handler))) {
10395 		return -1;
10396 	}
10397 
10398 	return arg_handler;
10399 }
10400 
10401 // Goober5000
is_blank_argument_op(int op_const)10402 bool is_blank_argument_op(int op_const)
10403 {
10404 	switch (op_const)
10405 	{
10406 		case OP_WHEN_ARGUMENT:
10407 		case OP_EVERY_TIME_ARGUMENT:
10408 			return true;
10409 
10410 		default:
10411 			return false;
10412 	}
10413 }
10414 
10415 // Goober5000
is_blank_of_op(int op_const)10416 bool is_blank_of_op(int op_const)
10417 {
10418 	switch (op_const)
10419 	{
10420 		case OP_ANY_OF:
10421 		case OP_EVERY_OF:
10422 		case OP_NUMBER_OF:
10423 		case OP_RANDOM_OF:
10424 		case OP_RANDOM_MULTIPLE_OF:
10425 		case OP_IN_SEQUENCE:
10426 		case OP_FOR_COUNTER:
10427 		case OP_FOR_SHIP_CLASS:
10428 		case OP_FOR_SHIP_TYPE:
10429 		case OP_FOR_SHIP_TEAM:
10430 		case OP_FOR_SHIP_SPECIES:
10431 		case OP_FOR_PLAYERS:
10432 		case OP_FIRST_OF:
10433 			return true;
10434 
10435 		default:
10436 			return false;
10437 	}
10438 }
10439 
10440 // Goober5000
sexp_functional_if_then_else(int node)10441 int sexp_functional_if_then_else(int node)
10442 {
10443 	int num1, num2, n;
10444 	bool is_nan, is_nan_forever;
10445 
10446 	Assertion(CAR(node) >= 0, "The condition in functional-if-then-else must be an operator!");
10447 
10448 	// decision time
10449 	int condition = eval_sexp(CAR(node));
10450 
10451 	// we need to evaluate both numbers regardless of which one we pick
10452 	n = CDR(node);
10453 	eval_nums(n, is_nan, is_nan_forever, num1, num2);
10454 	if (is_nan)
10455 		return SEXP_NAN;
10456 	if (is_nan_forever)
10457 		return SEXP_NAN_FOREVER;
10458 
10459 	// pick one
10460 	if (condition == SEXP_TRUE || condition == SEXP_KNOWN_TRUE)
10461 		return num1;
10462 	else
10463 		return num2;
10464 }
10465 
10466 // Goober5000
sexp_functional_switch(int node)10467 int sexp_functional_switch(int node)
10468 {
10469 	bool is_nan, is_nan_forever;
10470 	int n = node, result = 0;
10471 
10472 	int choice = eval_num(n, is_nan, is_nan_forever);
10473 	if (is_nan || is_nan_forever || choice < 0)
10474 		return 0;
10475 	n = CDR(n);
10476 
10477 	// evaluate all the numbers, but only keep the one we want
10478 	while (n >= 0)
10479 	{
10480 		int temp = eval_num(n, is_nan, is_nan_forever);
10481 
10482 		if (choice == 0)
10483 		{
10484 			if (is_nan)
10485 				result = SEXP_NAN;
10486 			else if (is_nan_forever)
10487 				result = SEXP_NAN_FOREVER;
10488 			else
10489 				result = temp;
10490 		}
10491 
10492 		--choice;
10493 		n = CDR(n);
10494 	}
10495 
10496 	return result;
10497 }
10498 
10499 // Goober5000 - added wing capability
sexp_is_iff(int n)10500 int sexp_is_iff(int n)
10501 {
10502 	int i, team;
10503 
10504 	// iff value is the first parameter, second is a list of one or more ships/wings to check to see if the
10505 	// iff value matches
10506 	team = iff_lookup(CTEXT(n));
10507 	n = CDR(n);
10508 
10509 	for ( ; n != -1; n = CDR(n) )
10510 	{
10511 		object_ship_wing_point_team oswpt;
10512 		eval_object_ship_wing_point_team(&oswpt, n);
10513 
10514 		switch (oswpt.type)
10515 		{
10516 			case OSWPT_TYPE_SHIP:
10517 			{
10518 				// if the team doesn't match the team specified, return false immediately
10519 				if (oswpt.ship_entry->shipp->team != team)
10520 					return SEXP_FALSE;
10521 
10522 				break;
10523 			}
10524 
10525 			case OSWPT_TYPE_PARSE_OBJECT:
10526 			{
10527 				// if the team doesn't match the team specified, return false immediately
10528 				if (oswpt.ship_entry->p_objp->team != team)
10529 					return SEXP_FALSE;
10530 
10531 				break;
10532 			}
10533 
10534 			case OSWPT_TYPE_WING:
10535 			case OSWPT_TYPE_WING_NOT_PRESENT:
10536 			{
10537 				for (i = 0; i < oswpt.wingp->current_count; i++)
10538 				{
10539 					// if the team doesn't match the team specified, return false immediately
10540 					if (Ships[oswpt.wingp->ship_index[i]].team != team)
10541 						return SEXP_FALSE;
10542 				}
10543 
10544 				break;
10545 			}
10546 
10547 			case OSWPT_TYPE_EXITED:
10548 			{
10549 				// see if we can find information about the exited ship (if it is a ship)
10550 				if (oswpt.ship_entry)
10551 				{
10552 					// ship is properly exited
10553 					if (oswpt.ship_entry->exited_index >= 0)
10554 					{
10555 						// if the team doesn't match the team specified, return false immediately
10556 						if (Ships_exited[oswpt.ship_entry->exited_index].team != team)
10557 							return SEXP_KNOWN_FALSE;
10558 					}
10559 					// ship is in the EXITED state but probably in the process of exploding
10560 					else if (oswpt.ship_entry->shipp)
10561 					{
10562 						// if the team doesn't match the team specified, return false immediately
10563 						if (oswpt.ship_entry->shipp->team != team)
10564 							return SEXP_KNOWN_FALSE;
10565 					}
10566 					// ship has vanished
10567 					else
10568 						return SEXP_NAN_FOREVER;
10569 				}
10570 				// it's probably an exited wing, which we don't store information about
10571 				else
10572 				{
10573 					return SEXP_NAN_FOREVER;
10574 				}
10575 
10576 				break;
10577 			}
10578 
10579 			// we don't handle the other cases
10580 			default:
10581 				return SEXP_NAN;
10582 		}
10583 	}
10584 
10585 	// got this far: we must be okay for all ships/wings
10586 	return SEXP_TRUE;
10587 }
10588 
10589 // Goober5000
sexp_ingame_ship_change_iff(ship * shipp,int new_team)10590 void sexp_ingame_ship_change_iff(ship *shipp, int new_team)
10591 {
10592 	Assert(shipp != nullptr);
10593 
10594 	shipp->team = new_team;
10595 }
10596 
10597 // Goober5000
sexp_parse_ship_change_iff(p_object * parse_obj,int new_team)10598 void sexp_parse_ship_change_iff(p_object *parse_obj, int new_team)
10599 {
10600 	Assert(parse_obj);
10601 
10602 	parse_obj->team = new_team;
10603 }
10604 
sexp_change_iff_helper(object_ship_wing_point_team oswpt,int new_team)10605 void sexp_change_iff_helper(object_ship_wing_point_team oswpt, int new_team)
10606 {
10607 	switch (oswpt.type)
10608 	{
10609 		// change ingame ship
10610 		case OSWPT_TYPE_SHIP:
10611 		{
10612 			sexp_ingame_ship_change_iff(oswpt.ship_entry->shipp, new_team);
10613 
10614 			break;
10615 		}
10616 
10617 		// change ship yet to arrive
10618 		case OSWPT_TYPE_PARSE_OBJECT:
10619 		{
10620 			sexp_parse_ship_change_iff(oswpt.ship_entry->p_objp, new_team);
10621 
10622 			break;
10623 		}
10624 
10625 		// change wing (we must set the flags for all ships present as well as all ships yet to arrive)
10626 		case OSWPT_TYPE_WING:
10627 		case OSWPT_TYPE_WING_NOT_PRESENT:
10628 		{
10629 			// current ships
10630 			for (int i = 0; i < oswpt.wingp->current_count; i++)
10631 				sexp_ingame_ship_change_iff(&Ships[oswpt.wingp->ship_index[i]], new_team);
10632 
10633 			// ships yet to arrive
10634 			for (p_object *p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp))
10635 			{
10636 				if (p_objp->wingnum == WING_INDEX(oswpt.wingp))
10637 					sexp_parse_ship_change_iff(p_objp, new_team);
10638 			}
10639 
10640 			break;
10641 		}
10642 	}
10643 }
10644 
10645 // Goober5000 - added wing capability
sexp_change_iff(int n)10646 void sexp_change_iff(int n)
10647 {
10648 	int new_team;
10649 
10650 	new_team = iff_lookup(CTEXT(n));
10651 	n = CDR(n);
10652 
10653 	Current_sexp_network_packet.start_callback();
10654 	Current_sexp_network_packet.send_int(new_team);
10655 
10656 	for ( ; n != -1; n = CDR(n) )
10657 	{
10658 		object_ship_wing_point_team oswpt;
10659 		eval_object_ship_wing_point_team(&oswpt, n);
10660 
10661 		Current_sexp_network_packet.send_string(CTEXT(n));
10662 
10663 		sexp_change_iff_helper(oswpt, new_team);
10664 	}
10665 
10666 	Current_sexp_network_packet.end_callback();
10667 }
10668 
multi_sexp_change_iff()10669 void multi_sexp_change_iff()
10670 {
10671 	int new_team;
10672 	char name[TOKEN_LENGTH];
10673 
10674 	Current_sexp_network_packet.get_int(new_team);
10675 	while (Current_sexp_network_packet.get_string(name)) {
10676 
10677 		object_ship_wing_point_team oswpt;
10678 		eval_object_ship_wing_point_team(&oswpt, -1, name);
10679 
10680 		sexp_change_iff_helper(oswpt, new_team);
10681 	}
10682 }
10683 
sexp_ingame_ship_change_iff_color(ship * shipp,int observer_team,int observed_team,int alternate_iff_color)10684 void sexp_ingame_ship_change_iff_color(ship *shipp, int observer_team, int observed_team, int alternate_iff_color)
10685 {
10686 	Assert(shipp != nullptr);
10687 
10688 	shipp->ship_iff_color[observer_team][observed_team] = alternate_iff_color;
10689 }
10690 
sexp_parse_ship_change_iff_color(p_object * parse_obj,int observer_team,int observed_team,int alternate_iff_color)10691 void sexp_parse_ship_change_iff_color(p_object *parse_obj, int observer_team, int observed_team, int alternate_iff_color)
10692 {
10693 	Assert(parse_obj);
10694 
10695 	parse_obj->alt_iff_color[observer_team][observed_team] = alternate_iff_color;
10696 }
10697 
sexp_change_iff_color_helper(object_ship_wing_point_team oswpt,int observer_team,int observed_team,int alternate_iff_color)10698 void sexp_change_iff_color_helper(object_ship_wing_point_team oswpt, int observer_team, int observed_team, int alternate_iff_color)
10699 {
10700 	int i;
10701 
10702 	switch (oswpt.type)
10703 	{
10704 		// change ingame ship
10705 		case OSWPT_TYPE_SHIP:
10706 		{
10707 			sexp_ingame_ship_change_iff_color(oswpt.ship_entry->shipp, observer_team, observed_team, alternate_iff_color);
10708 
10709 			break;
10710 		}
10711 
10712 		// change ship yet to arrive
10713 		case OSWPT_TYPE_PARSE_OBJECT:
10714 		{
10715 			sexp_parse_ship_change_iff_color(oswpt.ship_entry->p_objp, observer_team, observed_team, alternate_iff_color);
10716 
10717 			break;
10718 		}
10719 
10720 		// change wing (we must set the flags for all ships present as well as all ships yet to arrive)
10721 		case OSWPT_TYPE_WING:
10722 		case OSWPT_TYPE_WING_NOT_PRESENT:
10723 		{
10724 			// current ships
10725 			for (i = 0; i < oswpt.wingp->current_count; i++)
10726 				sexp_ingame_ship_change_iff_color(&Ships[oswpt.wingp->ship_index[i]], observer_team, observed_team, alternate_iff_color);
10727 
10728 			// ships yet to arrive
10729 			for (p_object *p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp))
10730 			{
10731 				if (p_objp->wingnum == WING_INDEX(oswpt.wingp))
10732 					sexp_parse_ship_change_iff_color(p_objp, observer_team, observed_team, alternate_iff_color);
10733 			}
10734 
10735 			break;
10736 		}
10737 	}
10738 }
10739 
10740  // Wanderer
sexp_change_iff_color(int n)10741 void sexp_change_iff_color(int n)
10742 {
10743 	int observer_team, observed_team, alternate_iff_color;
10744 	std::array<int, 3> rgb;
10745 	bool is_nan, is_nan_forever;
10746 
10747 	// First node
10748 	if (n == -1) {
10749 		Warning(LOCATION, "Detected missing observer team parameter in sexp-change_iff_color");
10750 		return;
10751 	}
10752 	observer_team = iff_lookup(CTEXT(n));
10753 	n = CDR(n);
10754 
10755 	// Second node
10756 	if (n == -1) {
10757 		Warning(LOCATION, "Detected missing observed team parameter in sexp-change_iff_color");
10758 		return;
10759 	}
10760 	observed_team = iff_lookup(CTEXT(n));
10761 	n = CDR(n);
10762 
10763 	// Three following nodes
10764 	int count = eval_array<int>(rgb, n, is_nan, is_nan_forever, [](int num)->int {
10765 		if (num < 0 || num > 255) {
10766 			Warning(LOCATION, "Invalid argument for iff color in sexp-change-iff-color. Valid range is 0 to 255.\n");
10767 			num = 255;
10768 		}
10769 		return num;
10770 	});
10771 	if (count < 3) {
10772 		Warning(LOCATION, "Detected incomplete color parameter list in sexp-change_iff_color\n");
10773 		return;
10774 	}
10775 	if (is_nan || is_nan_forever) {
10776 		return;
10777 	}
10778 	alternate_iff_color = iff_init_color(rgb[0], rgb[1], rgb[2]);
10779 
10780 	Current_sexp_network_packet.start_callback();
10781 	Current_sexp_network_packet.send_int(observer_team);
10782 	Current_sexp_network_packet.send_int(observed_team);
10783 	Current_sexp_network_packet.send_int(alternate_iff_color);
10784 
10785 	// Rest of the nodes
10786 	for (; n >= 0; n = CDR(n))
10787 	{
10788 		object_ship_wing_point_team oswpt;
10789 		eval_object_ship_wing_point_team(&oswpt, n);
10790 
10791 		Current_sexp_network_packet.send_string(CTEXT(n));
10792 
10793 		sexp_change_iff_color_helper(oswpt, observer_team, observed_team, alternate_iff_color);
10794 	}
10795 
10796 	Current_sexp_network_packet.end_callback();
10797 }
10798 
multi_sexp_change_iff_color()10799 void multi_sexp_change_iff_color()
10800 {
10801 	int observer_team, observed_team, alternate_iff_color;
10802 	char name[TOKEN_LENGTH];
10803 
10804 	Current_sexp_network_packet.get_int(observer_team);
10805 	Current_sexp_network_packet.get_int(observed_team);
10806 	Current_sexp_network_packet.get_int(alternate_iff_color);
10807 
10808 	while (Current_sexp_network_packet.get_string(name))
10809 	{
10810 		object_ship_wing_point_team oswpt;
10811 		eval_object_ship_wing_point_team(&oswpt, -1, name);
10812 
10813 		sexp_change_iff_color_helper(oswpt, observer_team, observed_team, alternate_iff_color);
10814 	}
10815 }
10816 
10817 // Goober5000
sexp_is_ship_class_or_type(int n,bool ship_class)10818 int sexp_is_ship_class_or_type(int n, bool ship_class)
10819 {
10820 	Assert( n >= 0 );
10821 
10822 	// get class or type
10823 	int index;
10824 	if (ship_class)
10825 		index = ship_info_lookup(CTEXT(n));
10826 	else
10827 		index = ship_type_name_lookup(CTEXT(n));
10828 	n = CDR(n);
10829 
10830 	// eval ships
10831 	while (n != -1)
10832 	{
10833 		// get ship
10834 		auto ship_entry = eval_ship(n);
10835 		if (!ship_entry)
10836 			return SEXP_NAN;
10837 
10838 		int other_index;
10839 		if (ship_entry->status == ShipStatus::NOT_YET_PRESENT)
10840 		{
10841 			other_index = ship_entry->p_objp->ship_class;
10842 		}
10843 		else if (ship_entry->exited_index >= 0)
10844 		{
10845 			other_index = Ships_exited[ship_entry->exited_index].ship_class;
10846 		}
10847 		else if (ship_entry->shipp)
10848 		{
10849 			other_index = ship_entry->shipp->ship_info_index;
10850 		}
10851 		else
10852 			return SEXP_NAN_FOREVER;
10853 
10854 		// maybe convert from class to type
10855 		if (!ship_class)
10856 			other_index = ship_class_query_general_type(other_index);
10857 
10858 		// if it doesn't match, return false
10859 		if (index != other_index)
10860 			return SEXP_FALSE;
10861 
10862 		// increment
10863 		n = CDR(n);
10864 	}
10865 
10866 	// we're this far; it must be true
10867 	return SEXP_TRUE;
10868 }
10869 
10870 // Goober5000
10871 // ai class value is the first parameter, second is a ship, rest are subsystems to check
sexp_is_ai_class(int n)10872 int sexp_is_ai_class(int n)
10873 {
10874 	Assert ( n >= 0 );
10875 
10876 	auto ai_class_name = CTEXT(n);
10877 	n = CDR(n);
10878 
10879 	// find ai class
10880 	int ai_class_to_check = -1;
10881 	for (int i=0; i<Num_ai_classes; ++i)
10882 	{
10883 		if (!stricmp(Ai_class_names[i], ai_class_name))
10884 			ai_class_to_check = i;
10885 	}
10886 
10887 	if (ai_class_to_check < 0) {
10888 		Warning(LOCATION, "is-ai-class called with nonexistent AI class [%s].", ai_class_name);
10889 		return SEXP_FALSE;
10890 	}
10891 
10892 	// find ship
10893 	auto ship_entry = eval_ship(n);
10894 	if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT)
10895 		return SEXP_NAN;
10896 	if (ship_entry->status == ShipStatus::EXITED)
10897 		return SEXP_NAN_FOREVER;
10898 	n = CDR(n);
10899 
10900 	// subsys?
10901 	if (n >= 0)
10902 	{
10903 		// loopity-loop
10904 		for ( ; n != -1; n = CDR(n) )
10905 		{
10906 			auto subsystem = CTEXT(n);
10907 
10908 			// find the ship subsystem
10909 			// if it doesn't match, or subsys isn't found, return false immediately
10910 			auto ss = ship_get_subsys(ship_entry->shipp, subsystem);
10911 			if (ss)
10912 			{
10913 				if (ss->weapons.ai_class != ai_class_to_check)
10914 					return SEXP_FALSE;
10915 			}
10916 			else
10917 				return SEXP_FALSE;
10918 		}
10919 
10920 		// we've come this far; it must all be true
10921 		return SEXP_TRUE;
10922 	}
10923 	// just the ship
10924 	else
10925 	{
10926 		if (Ai_info[ship_entry->shipp->ai_index].ai_class == ai_class_to_check)
10927 			return SEXP_TRUE;
10928 		else
10929 			return SEXP_FALSE;
10930 	}
10931 }
10932 
10933 // Goober5000
sexp_change_ai_class(int n)10934 void sexp_change_ai_class(int n)
10935 {
10936 	Assert ( n >= 0 );
10937 
10938 	auto ai_class_name = CTEXT(n);
10939 	n = CDR(n);
10940 
10941 	// find ai class
10942 	int new_ai_class = -1;
10943 	for (int i=0; i<Num_ai_classes; ++i)
10944 	{
10945 		if (!stricmp(Ai_class_names[i], ai_class_name))
10946 			new_ai_class = i;
10947 	}
10948 
10949 	if (new_ai_class < 0) {
10950 		Warning(LOCATION, "change-ai-class called with nonexistent AI class [%s].", ai_class_name);
10951 		return;
10952 	}
10953 
10954 	// find ship
10955 	auto ship_entry = eval_ship(n);
10956 	if (!ship_entry || !ship_entry->shipp)
10957 		return;
10958 	n = CDR(n);
10959 
10960 	Current_sexp_network_packet.start_callback();
10961 	Current_sexp_network_packet.send_ship(ship_entry->shipp);
10962 	Current_sexp_network_packet.send_int(new_ai_class);
10963 
10964 	// subsys?
10965 	if (n >= 0)
10966 	{
10967 		// loopity-loop
10968 		for ( ; n != -1; n = CDR(n) )
10969 		{
10970 			auto subsystem = CTEXT(n);
10971 			ship_subsystem_set_new_ai_class(ship_entry->shipp, subsystem, new_ai_class);
10972 
10973 			Current_sexp_network_packet.send_string(subsystem);
10974 		}
10975 	}
10976 	// just the one ship
10977 	else
10978 	{
10979 		ship_set_new_ai_class(ship_entry->shipp, new_ai_class);
10980 	}
10981 
10982 	Current_sexp_network_packet.end_callback();
10983 }
10984 
multi_sexp_change_ai_class()10985 void multi_sexp_change_ai_class()
10986 {
10987 	int new_ai_class;
10988 	ship *shipp;
10989 	char subsystem[TOKEN_LENGTH];
10990 
10991 	Current_sexp_network_packet.get_ship(shipp);
10992 	Current_sexp_network_packet.get_int(new_ai_class);
10993 
10994 	// subsystem?
10995 	if (Current_sexp_network_packet.get_string(subsystem)) {
10996 		ship_subsystem_set_new_ai_class(shipp, subsystem, new_ai_class);
10997 
10998 		// deal with any other subsystems
10999 		while (Current_sexp_network_packet.get_string(subsystem)) {
11000 			ship_subsystem_set_new_ai_class(shipp, subsystem, new_ai_class);
11001 		}
11002 	}
11003 	else {
11004 		ship_set_new_ai_class(shipp, new_ai_class);
11005 	}
11006 }
11007 
11008 // following routine adds an ai goal to a ship structure.  The sexpression index
11009 // passed in should be an ai-goal of the proper form.  The code in MissionGoal should
11010 // check the syntax.
11011 
11012 /**
11013  * Adds a goal to the specified entry (ships and wings have unique names between the two sets).
11014  */
sexp_add_goal(int n)11015 void sexp_add_goal(int n)
11016 {
11017 	Assert ( n >= 0 );
11018 
11019 	int goal_node = CDR(n);
11020 
11021 	auto ship_entry = eval_ship(n);
11022 	if (ship_entry)
11023 	{
11024 		if (ship_entry->status != ShipStatus::PRESENT)
11025 			return;										// ship not around anymore???? then forget it!
11026 
11027 		ai_add_ship_goal_sexp(goal_node, AIG_TYPE_EVENT_SHIP, &(Ai_info[ship_entry->shipp->ai_index]));
11028 		return;
11029 	}
11030 
11031 	auto wingp = eval_wing(n);
11032 	if (wingp)
11033 	{
11034 		if (wingp->flags[Ship::Wing_Flags::Gone])
11035 			return;										// wing not around anymore???? then forget it!
11036 
11037 		ai_add_wing_goal_sexp(goal_node, AIG_TYPE_EVENT_WING, wingp);
11038 	}
11039 }
11040 
11041 // Goober5000
sexp_remove_goal(int n)11042 void sexp_remove_goal(int n)
11043 {
11044 	Assert( n >= 0 );
11045 
11046 	int goal_node = CDR(n);
11047 
11048 	auto ship_entry = eval_ship(n);
11049 	if (ship_entry)
11050 	{
11051 		if (ship_entry->status != ShipStatus::PRESENT)
11052 			return;										// ship not around anymore???? then forget it!
11053 
11054 		int goalindex = ai_remove_goal_sexp_sub(goal_node, Ai_info[ship_entry->shipp->ai_index].goals);
11055 		if (goalindex >= 0)
11056 		{
11057 			if (Ai_info[ship_entry->shipp->ai_index].active_goal == goalindex)
11058 				Ai_info[ship_entry->shipp->ai_index].active_goal = AI_GOAL_NONE;
11059 		}
11060 		return;
11061 	}
11062 
11063 	auto wingp = eval_wing(n);
11064 	if (wingp)
11065 	{
11066 		if (wingp->flags[Ship::Wing_Flags::Gone])
11067 			return;										// wing not around anymore???? then forget it!
11068 
11069 		ai_remove_wing_goal_sexp(goal_node, wingp);
11070 	}
11071 }
11072 
11073 /**
11074  * Clear all AI goals for the given ship or wing
11075  */
sexp_clear_goals(int n)11076 void sexp_clear_goals(int n)
11077 {
11078 	for (; n >= 0; n = CDR(n))
11079 	{
11080 		auto ship_entry = eval_ship(n);
11081 		if (ship_entry)
11082 		{
11083 			if (ship_entry->status != ShipStatus::PRESENT)
11084 				return;										// ship not around anymore???? then forget it!
11085 
11086 			ai_clear_ship_goals(&(Ai_info[ship_entry->shipp->ai_index]));
11087 			continue;
11088 		}
11089 
11090 		auto wingp = eval_wing(n);
11091 		if (wingp)
11092 		{
11093 			if (wingp->flags[Ship::Wing_Flags::Gone])
11094 				return;										// wing not around anymore???? then forget it!
11095 
11096 			ai_clear_wing_goals(wingp);
11097 		}
11098 	}
11099 }
11100 
11101 // Goober5000
sexp_hud_disable(int n)11102 void sexp_hud_disable(int n)
11103 {
11104 	bool is_nan, is_nan_forever;
11105 	int disable_hud = eval_num(n, is_nan, is_nan_forever);
11106 	if (is_nan || is_nan_forever)
11107 		return;
11108 
11109 	hud_set_draw(!disable_hud);
11110 
11111 	Current_sexp_network_packet.start_callback();
11112 	Current_sexp_network_packet.send_int(disable_hud);
11113 	Current_sexp_network_packet.end_callback();
11114 }
11115 
multi_sexp_hud_disable()11116 void multi_sexp_hud_disable()
11117 {
11118 	int disable_hud;
11119 
11120 	if (Current_sexp_network_packet.get_int(disable_hud)) {
11121 		hud_set_draw(!disable_hud);
11122 	}
11123 }
11124 
11125 // Goober5000
sexp_hud_disable_except_messages(int n)11126 void sexp_hud_disable_except_messages(int n)
11127 {
11128 	bool is_nan, is_nan_forever;
11129 	int disable_hud = eval_num(n, is_nan, is_nan_forever);
11130 	if (is_nan || is_nan_forever)
11131 		return;
11132 
11133 	hud_disable_except_messages(disable_hud);
11134 
11135 	Current_sexp_network_packet.start_callback();
11136 	Current_sexp_network_packet.send_int(disable_hud);
11137 	Current_sexp_network_packet.end_callback();
11138 }
11139 
multi_sexp_hud_disable_except_messages()11140 void multi_sexp_hud_disable_except_messages()
11141 {
11142 	int disable_hud;
11143 
11144 	if (Current_sexp_network_packet.get_int(disable_hud)) {
11145 		hud_disable_except_messages(disable_hud);
11146 	}
11147 }
11148 
sexp_hud_set_text_num(int n)11149 void sexp_hud_set_text_num(int n)
11150 {
11151 	bool is_nan, is_nan_forever;
11152 	auto gaugename = CTEXT(n);
11153 	char tmp[16] = "";
11154 
11155 	HudGauge* cg = hud_get_gauge(gaugename);
11156 	if(cg) {
11157 		int num = eval_num(CDR(n), is_nan, is_nan_forever);
11158 		if (is_nan || is_nan_forever) {
11159 			strcpy_s(tmp, "NaN");
11160 		}
11161 		else {
11162 			sprintf(tmp, "%d", num);
11163 		}
11164 		cg->updateCustomGaugeText(tmp);
11165 	}
11166 }
11167 
sexp_hud_set_text(int n)11168 void sexp_hud_set_text(int n)
11169 {
11170 	auto gaugename = CTEXT(n);
11171 	auto text = CTEXT(CDR(n));
11172 
11173 	HudGauge* cg = hud_get_gauge(gaugename);
11174 	if(cg) {
11175 		cg->updateCustomGaugeText(text);
11176 	}
11177 }
11178 
sexp_hud_set_message(int n)11179 void sexp_hud_set_message(int n)
11180 {
11181 	auto gaugename = CTEXT(n);
11182 	auto text = CTEXT(CDR(n));
11183 	SCP_string message;
11184 
11185 	for (int i = 0; i < Num_messages; i++) {
11186 		if ( !stricmp(text, Messages[i].name) ) {
11187 			message = Messages[i].message;
11188 
11189 			sexp_replace_variable_names_with_values(message);
11190 
11191 			HudGauge* cg = hud_get_gauge(gaugename);
11192 			if(cg) {
11193 				cg->updateCustomGaugeText(message);
11194 			} else {
11195 				WarningEx(LOCATION, "Could not find a hud gauge named %s\n", gaugename);
11196 			}
11197 			return;
11198 		}
11199 	}
11200 
11201 	WarningEx(LOCATION, "sexp_hud_set_message couldn't find a message by the name of %s in the mission\n", text);
11202 }
11203 
sexp_hud_set_directive(int n)11204 void sexp_hud_set_directive(int n)
11205 {
11206 	auto gaugename = CTEXT(n);
11207 	auto text = CTEXT(CDR(n));
11208 	SCP_string message = message_translate_tokens(text);
11209 
11210 	if (message.size() > MESSAGE_LENGTH) {
11211 		WarningEx(LOCATION, "Message %s is too long for use in a HUD gauge. Please shorten it to %d characters or less.", message.c_str(), MESSAGE_LENGTH);
11212 		return;
11213 	}
11214 
11215 	HudGauge* cg = hud_get_gauge(gaugename);
11216 	if(cg) {
11217 		cg->updateCustomGaugeText(message);
11218 	} else {
11219 		WarningEx(LOCATION, "Could not find a hud gauge named %s\n", gaugename);
11220 	}
11221 }
11222 
sexp_hud_clear_messages()11223 void sexp_hud_clear_messages()
11224 {
11225 	if(!Ship_info[Player_ship->ship_info_index].hud_gauges.empty()) {
11226 		size_t num_gauges = Ship_info[Player_ship->ship_info_index].hud_gauges.size();
11227 
11228 		for(size_t i = 0; i < num_gauges; i++) {
11229 			if (Ship_info[Player_ship->ship_info_index].hud_gauges[i]->getObjectType() == HUD_OBJECT_MESSAGES) {
11230 				auto* gauge = static_cast<HudGaugeMessages*>(Ship_info[Player_ship->ship_info_index].hud_gauges[i].get());
11231 				gauge->clearMessages();
11232 			}
11233 		}
11234 	} else {
11235 		size_t num_gauges = default_hud_gauges.size();
11236 
11237 		for(size_t i = 0; i < num_gauges; i++) {
11238 			if (default_hud_gauges[i]->getObjectType() == HUD_OBJECT_MESSAGES) {
11239 				auto* gauge = static_cast<HudGaugeMessages*>(default_hud_gauges[i].get());
11240 				gauge->clearMessages();
11241 			}
11242 		}
11243 	}
11244 }
11245 
sexp_hud_set_coords(int n)11246 void sexp_hud_set_coords(int n)
11247 {
11248 	int coord_x, coord_y;
11249 	bool is_nan, is_nan_forever;
11250 
11251 	auto gaugename = CTEXT(n);
11252 	n = CDR(n);
11253 
11254 	eval_nums(n, is_nan, is_nan_forever, coord_x, coord_y);
11255 	if (is_nan || is_nan_forever)
11256 		return;
11257 
11258 	HudGauge* cg = hud_get_gauge(gaugename);
11259 	if(cg) {
11260 		cg->updateCustomGaugeCoords(coord_x, coord_y);
11261 	}
11262 }
11263 
sexp_hud_set_frame(int n)11264 void sexp_hud_set_frame(int n)
11265 {
11266 	bool is_nan, is_nan_forever;
11267 	auto gaugename = CTEXT(n);
11268 
11269 	int frame_num = eval_num(CDR(n), is_nan, is_nan_forever);
11270 	if (is_nan || is_nan_forever)
11271 		return;
11272 
11273 	HudGauge* cg = hud_get_gauge(gaugename);
11274 	if(cg) {
11275 		cg->updateCustomGaugeFrame(frame_num);
11276 	}
11277 }
11278 
sexp_hud_set_color(int n)11279 void sexp_hud_set_color(int n)
11280 {
11281 	auto gaugename = CTEXT(n);
11282 	n = CDR(n);
11283 
11284 	std::array<int, 3> rgb;
11285 	bool is_nan, is_nan_forever;
11286 	eval_array<int>(rgb, n, is_nan, is_nan_forever, [](int num)->int {
11287 		CLAMP(num, 0, 255);
11288 		return num;
11289 	});
11290 	if (is_nan || is_nan_forever)
11291 		return;
11292 
11293 	HudGauge* cg = hud_get_gauge(gaugename);
11294 	if (cg) {
11295 		cg->sexpLockConfigColor(false);
11296 		cg->updateColor((ubyte)rgb[0], (ubyte)rgb[1], (ubyte)rgb[2], (HUD_color_alpha + 1) * 16);
11297 		cg->sexpLockConfigColor(true);
11298 	}
11299 }
11300 
11301 // Goober5000
sexp_hud_set_max_targeting_range(int n)11302 void sexp_hud_set_max_targeting_range(int n)
11303 {
11304 	int val;
11305 	bool is_nan, is_nan_forever;
11306 
11307 	val = eval_num(n, is_nan, is_nan_forever);
11308 	if (is_nan || is_nan_forever)
11309 		return;
11310 	if (val < 0) {
11311 		val = 0;
11312 	}
11313 
11314 	Hud_max_targeting_range = val;
11315 
11316 	Current_sexp_network_packet.start_callback();
11317 	Current_sexp_network_packet.send_int(Hud_max_targeting_range);
11318 	Current_sexp_network_packet.end_callback();
11319 }
11320 
multi_sexp_hud_set_max_targeting_range()11321 void multi_sexp_hud_set_max_targeting_range()
11322 {
11323 	Current_sexp_network_packet.get_int(Hud_max_targeting_range);
11324 }
11325 
11326 /* Make sure that the Sexp_hud_display_* get added to the game_state
11327 transitions in freespace.cpp (game_enter_state()). */
11328 int Sexp_hud_display_warpout = 0;
11329 
sexp_hud_display_gauge(int n)11330 void sexp_hud_display_gauge(int n)
11331 {
11332 	bool is_nan, is_nan_forever;
11333 
11334 	int show_for = eval_num(n, is_nan, is_nan_forever);
11335 	if (is_nan || is_nan_forever)
11336 		return;
11337 
11338 	auto gauge = CTEXT(CDR(n));
11339 
11340 	if ( stricmp(SEXP_HUD_GAUGE_WARPOUT, gauge) == 0 ) {
11341 		Sexp_hud_display_warpout = (show_for > 1)? timestamp(show_for) : (show_for);
11342 	}
11343 }
11344 
sexp_hud_gauge_set_active(int n)11345 void sexp_hud_gauge_set_active(int n)
11346 {
11347 	HudGauge* hg;
11348 	auto name = CTEXT(n);
11349 	bool active = is_sexp_true(CDR(n));
11350 
11351 	hg = hud_get_gauge(name);
11352 
11353 	if (hg != nullptr) {
11354 		hg->updateActive(active);
11355 	}
11356 }
11357 
sexp_hud_set_custom_gauge_active(int node)11358 void sexp_hud_set_custom_gauge_active(int node)
11359 {
11360 	HudGauge* hg;
11361 	bool activate = is_sexp_true(node);
11362 	node = CDR(node);
11363 	for(; node >= 0; node = CDR(node)) {
11364 
11365 		auto name = CTEXT(node);
11366 		hg = hud_get_gauge(name);
11367 
11368 		if (hg != nullptr) {
11369 			hg->updateActive(activate);
11370 		}
11371 	}
11372 }
11373 
hud_gauge_type_lookup(const char * name)11374 int hud_gauge_type_lookup(const char* name)
11375 {
11376 	for(int i = 0; i < Num_hud_gauge_types; i++) {
11377 		if(!stricmp(name, Hud_gauge_types[i].name))
11378 			return Hud_gauge_types[i].def;
11379 	}
11380 	return -1;
11381 }
11382 
sexp_hud_activate_gauge_type(int n)11383 void sexp_hud_activate_gauge_type(int n)
11384 {
11385 	int config_type = hud_gauge_type_lookup(CTEXT(n));
11386 	bool active = is_sexp_true(CDR(n));
11387 
11388 	if (config_type != -1) {
11389 		if(!Ship_info[Player_ship->ship_info_index].hud_gauges.empty()) {
11390 			size_t num_gauges = Ship_info[Player_ship->ship_info_index].hud_gauges.size();
11391 
11392 			for(size_t i = 0; i < num_gauges; i++) {
11393 				if (Ship_info[Player_ship->ship_info_index].hud_gauges[i]->getObjectType() == config_type)
11394 					Ship_info[Player_ship->ship_info_index].hud_gauges[i]->updateSexpOverride(!active);
11395 			}
11396 		} else {
11397 			size_t num_gauges = default_hud_gauges.size();
11398 
11399 			for(size_t i = 0; i < num_gauges; i++) {
11400 				if (default_hud_gauges[i]->getObjectType() == config_type)
11401 					default_hud_gauges[i]->updateSexpOverride(!active);
11402 			}
11403 		}
11404 	}
11405 }
11406 
sexp_hud_set_builtin_gauge_active(int node)11407 void sexp_hud_set_builtin_gauge_active(int node)
11408 {
11409 	bool activate = is_sexp_true(node);
11410 	node = CDR(node);
11411 
11412 	for(; node >= 0; node = CDR(node)) {
11413 
11414 		int config_type = hud_gauge_type_lookup(CTEXT(node));
11415 
11416 		if (config_type != -1) {
11417 			if(!Ship_info[Player_ship->ship_info_index].hud_gauges.empty()) {
11418 			size_t num_gauges = Ship_info[Player_ship->ship_info_index].hud_gauges.size();
11419 
11420 			for(size_t i = 0; i < num_gauges; i++) {
11421 				if (Ship_info[Player_ship->ship_info_index].hud_gauges[i]->getObjectType() == config_type)
11422 					Ship_info[Player_ship->ship_info_index].hud_gauges[i]->updateSexpOverride(!activate);
11423 				}
11424 			} else {
11425 			size_t num_gauges = default_hud_gauges.size();
11426 
11427 			for(size_t i = 0; i < num_gauges; i++) {
11428 				if (default_hud_gauges[i]->getObjectType() == config_type)
11429 					default_hud_gauges[i]->updateSexpOverride(!activate);
11430 				}
11431 			}
11432 		}
11433 	}
11434 }
11435 
multi_sexp_hud_display_gauge()11436 void multi_sexp_hud_display_gauge()
11437 {
11438 	int show_for;
11439 
11440 	if (Current_sexp_network_packet.get_int(show_for)) {
11441 		Sexp_hud_display_warpout = (show_for > 1)? timestamp(show_for) : (show_for);
11442 	}
11443 }
11444 
11445 // Goober5000
11446 /**
11447  * Trigger whether player uses the game AI for stuff
11448  */
sexp_player_use_ai(int flag)11449 void sexp_player_use_ai(int flag)
11450 {
11451 	Player_use_ai = flag ? 1 : 0;
11452 }
11453 
11454 // Karajorma
sexp_allow_treason(int n)11455 void sexp_allow_treason (int n)
11456 {
11457 	if (n != -1) {
11458 		The_mission.flags.set(Mission::Mission_Flags::No_traitor, is_sexp_true(n));
11459 	}
11460 }
11461 
sexp_set_player_orders(int n)11462 void sexp_set_player_orders(int n)
11463 {
11464 	bool allow_order;
11465 	int orders = 0;
11466 	int i, default_orders;
11467 
11468 	auto ship_entry = eval_ship(n);
11469 	if (!ship_entry || !ship_entry->shipp) {
11470 		return;
11471 	}
11472 	auto shipp = ship_entry->shipp;
11473 
11474 	// we need to know which orders this ship class can accept.
11475 	default_orders = ship_get_default_orders_accepted(&Ship_info[shipp->ship_info_index]);
11476 	n = CDR(n);
11477 	allow_order = is_sexp_true(n);
11478 	n = CDR(n);
11479 	do {
11480 		for (i = 0 ; i < NUM_COMM_ORDER_ITEMS ; i++) {
11481 			// since it's the cheaper test, test first if the ship will even accept this order first
11482 			if (default_orders & Sexp_comm_orders[i].item) {
11483 				if (!stricmp(CTEXT(n), Sexp_comm_orders[i].name)) {
11484 					orders |= Sexp_comm_orders[i].item;
11485 					break;
11486 				}
11487 			}
11488 		}
11489 
11490 		n = CDR(n);
11491 	}while (n >= 0);
11492 
11493 	// set or unset the orders
11494 	if (allow_order) {
11495 		shipp->orders_accepted |= orders;
11496 	}
11497 	else {
11498 		shipp->orders_accepted &= ~orders;
11499 	}
11500 }
11501 
11502 // Goober5000
sexp_change_soundtrack(int n)11503 void sexp_change_soundtrack(int n)
11504 {
11505 	event_sexp_change_soundtrack(CTEXT(n));
11506 
11507 	if (MULTIPLAYER_MASTER) {
11508 		Current_sexp_network_packet.start_callback();
11509 		Current_sexp_network_packet.send_string(CTEXT(n));
11510 		Current_sexp_network_packet.end_callback();
11511 	}
11512 }
11513 
multi_sexp_change_soundtrack()11514 void multi_sexp_change_soundtrack()
11515 {
11516 	char new_track[NAME_LENGTH];
11517 
11518 	if (Current_sexp_network_packet.get_string(new_track)) {
11519 		event_sexp_change_soundtrack(new_track);
11520 	}
11521 }
11522 
11523 // Goober5000
sexp_find_music_handle_index(int sexp_var)11524 int sexp_find_music_handle_index(int sexp_var)
11525 {
11526 	// check the default case
11527 	if (sexp_var < 0)
11528 	{
11529 		// we might not have any music at this point
11530 		if (Sexp_music_handles.empty())
11531 			return -1;
11532 
11533 		return 0;
11534 	}
11535 
11536 	// get the handle from the variable
11537 	int music_handle = -1;
11538 	try
11539 	{
11540 		music_handle = std::stoi(Sexp_variables[sexp_var].text);
11541 	}
11542 	catch (const std::exception&)
11543 	{
11544 		// not a number
11545 		return -1;
11546 	}
11547 
11548 	// find it in our collection of handles (skip index 0)
11549 	for (size_t i = 1; i < Sexp_music_handles.size(); ++i)
11550 	{
11551 		if (Sexp_music_handles[i] == music_handle)
11552 			return (int)i;
11553 	}
11554 
11555 	return -1;
11556 }
11557 
11558 // Goober5000
sexp_pause_unpause_music(bool pause,int sexp_var)11559 void sexp_pause_unpause_music(bool pause, int sexp_var)
11560 {
11561 	if (Cmdline_freespace_no_music)
11562 		return;
11563 
11564 	int index = sexp_find_music_handle_index(sexp_var);
11565 	if (index < 0)
11566 	{
11567 		// bad variable, maybe
11568 		if (sexp_var >= 0)
11569 			Warning(LOCATION, "In sexp_pause_unpause_music, variable %s did not store a valid music handle!", Sexp_variables[sexp_var].variable_name);
11570 
11571 		return;
11572 	}
11573 
11574 	int music_handle = Sexp_music_handles[index];
11575 
11576 	// actually pause the music
11577 	if (music_handle >= 0)
11578 	{
11579 		if (pause && !audiostream_is_paused(music_handle))
11580 			audiostream_pause(music_handle, true);
11581 		else if (!pause && audiostream_is_paused(music_handle))
11582 			audiostream_unpause(music_handle, true);
11583 	}
11584 }
11585 
11586 // Goober5000
sexp_stop_music(bool fade,int sexp_var)11587 void sexp_stop_music(bool fade, int sexp_var)
11588 {
11589 	if (Cmdline_freespace_no_music)
11590 		return;
11591 
11592 	int index = sexp_find_music_handle_index(sexp_var);
11593 	if (index < 0)
11594 	{
11595 		// bad variable, maybe
11596 		if (sexp_var >= 0)
11597 			Warning(LOCATION, "In sexp_stop_music, variable %s did not store a valid music handle!", Sexp_variables[sexp_var].variable_name);
11598 
11599 		return;
11600 	}
11601 
11602 	int music_handle = Sexp_music_handles[index];
11603 
11604 	// actually stop the music and free the handle
11605 	if (music_handle >= 0)
11606 	{
11607 		audiostream_close_file(music_handle, fade);
11608 		Sexp_music_handles[index] = -1;
11609 	}
11610 
11611 	// also clear the variable
11612 	if (sexp_var >= 0)
11613 		sexp_modify_variable("-1", sexp_var);
11614 }
11615 
11616 // Goober5000
sexp_music_close()11617 void sexp_music_close()
11618 {
11619 	if (Cmdline_freespace_no_music)
11620 		return;
11621 
11622 	// close all music used by this mission
11623 	for (auto music_handle : Sexp_music_handles)
11624 		audiostream_close_file(music_handle);
11625 	Sexp_music_handles.clear();
11626 }
11627 
11628 // Goober5000
sexp_load_music(const char * filename,int type=-1,int sexp_var=-1)11629 void sexp_load_music(const char *filename, int type = -1, int sexp_var = -1)
11630 {
11631 	if (Cmdline_freespace_no_music || filename == nullptr)
11632 		return;
11633 
11634 	if (type < 0)
11635 		type = ASF_MENUMUSIC;
11636 
11637 	// the default music handle is always index 0, which at this point may or may not exist
11638 	if (Sexp_music_handles.empty())
11639 		Sexp_music_handles.push_back(-1);
11640 
11641 	int index = sexp_find_music_handle_index(sexp_var);
11642 
11643 	// since we know the default index 0 exists, this means we have a variable without an index
11644 	if (index < 0)
11645 	{
11646 		index = (int)Sexp_music_handles.size();
11647 		Sexp_music_handles.push_back(-1);
11648 	}
11649 
11650 	// if we were previously playing music on this handle, stop it
11651 	audiostream_close_file(Sexp_music_handles[index]);
11652 
11653 	// open the stream and save the handle in our list
11654 	Sexp_music_handles[index] = audiostream_open(filename, type);
11655 
11656 	// if we have a variable, save it there too
11657 	if (sexp_var >= 0)
11658 	{
11659 		char number_as_str[TOKEN_LENGTH];
11660 		sprintf(number_as_str, "%d", Sexp_music_handles[index]);
11661 		sexp_modify_variable(number_as_str, sexp_var);
11662 	}
11663 }
11664 
11665 // Goober5000
sexp_start_music(int loop,int sexp_var)11666 void sexp_start_music(int loop, int sexp_var)
11667 {
11668 	int index = sexp_find_music_handle_index(sexp_var);
11669 
11670 	// shouldn't happen
11671 	if (index < 0)
11672 	{
11673 		// really shouldn't happen
11674 		if (sexp_var < 0)
11675 			Warning(LOCATION, "In sexp_start_music, unable to find a default music index?!?!");
11676 		else
11677 			Warning(LOCATION, "In sexp_start_music, no music handle index available for variable %s!", Sexp_variables[sexp_var].variable_name);
11678 
11679 		return;
11680 	}
11681 
11682 	int music_handle = Sexp_music_handles[index];
11683 
11684 	// also shouldn't happen
11685 	if (music_handle < 0)
11686 	{
11687 		Warning(LOCATION, "In sexp_start_music, music handle was unavailable!");
11688 		return;
11689 	}
11690 
11691 	// start playing
11692 	if (!audiostream_is_playing(music_handle))
11693 		audiostream_play(music_handle, (Master_event_music_volume * aav_music_volume), loop);
11694 }
11695 
sexp_get_sound_index(int node)11696 gamesnd_id sexp_get_sound_index(int node)
11697 {
11698 	Assert(node >= 0);
11699 	bool is_nan, is_nan_forever;
11700 	gamesnd_id sound_index;
11701 
11702 	// this node is another SEXP operator or a plain number
11703 	if (CAR(node) != -1 || Sexp_nodes[node].subtype == SEXP_ATOM_NUMBER)
11704 	{
11705 		int index = eval_num(node, is_nan, is_nan_forever);
11706 		if (is_nan || is_nan_forever)
11707 			index = -1;
11708 
11709 		sound_index = gamesnd_get_by_tbl_index(index);
11710 	}
11711 	// it's gotta be a name
11712 	else
11713 	{
11714 		const char *sound_name = CTEXT(node);
11715 
11716 		// if it's not <none>, try looking it up
11717 		if (stricmp(sound_name, SEXP_NONE_STRING) != 0)
11718 		{
11719 			sound_index = gamesnd_get_by_name(sound_name);
11720 
11721 			if (!sound_index.isValid())
11722 				Warning(LOCATION, "unrecognized sound name \"%s\"!", sound_name);
11723 		}
11724 	}
11725 
11726 	return sound_index;
11727 }
11728 
11729 // Goober5000
sexp_play_sound_from_table(int n)11730 void sexp_play_sound_from_table(int n)
11731 {
11732 	bool is_nan, is_nan_forever;
11733 	vec3d origin;
11734 
11735 	Assert( n >= 0 );
11736 
11737 	// read in data --------------------------------
11738 	eval_vec3d(&origin, n, is_nan, is_nan_forever);
11739 	if (is_nan || is_nan_forever)
11740 		return;
11741 	auto sound_index = sexp_get_sound_index(n);
11742 
11743 
11744 	// play sound effect ---------------------------
11745 	if (sound_index.isValid()) {
11746 		game_snd *snd = gamesnd_get_game_sound(sound_index);
11747 		if (snd->min == 0 && snd->max == 0) {
11748 			// if sound doesn't specify 3d range, don't play in 3d
11749 			snd_play( snd, 0.0f, 1.0f, SND_PRIORITY_MUST_PLAY );
11750 		} else {
11751 			snd_play_3d( snd, &origin, &View_position, 0.0f, nullptr, 0, 1.0f, SND_PRIORITY_MUST_PLAY );
11752 		}
11753 	}
11754 
11755 	if (MULTIPLAYER_MASTER) {
11756 		Current_sexp_network_packet.start_callback();
11757 		Current_sexp_network_packet.send_float(origin.xyz.x);
11758 		Current_sexp_network_packet.send_float(origin.xyz.y);
11759 		Current_sexp_network_packet.send_float(origin.xyz.z);
11760 		Current_sexp_network_packet.send_int(sound_index.value());
11761 		Current_sexp_network_packet.end_callback();
11762 	}
11763 }
11764 
multi_sexp_play_sound_from_table()11765 void multi_sexp_play_sound_from_table()
11766 {
11767 	vec3d origin;
11768 	int sound_index = -1;
11769 
11770 	Current_sexp_network_packet.get_float(origin.xyz.x);
11771 	Current_sexp_network_packet.get_float(origin.xyz.y);
11772 	Current_sexp_network_packet.get_float(origin.xyz.z);
11773 	Current_sexp_network_packet.get_int(sound_index);
11774 
11775 	auto sound = gamesnd_id(sound_index);
11776 
11777 	// play sound effect ---------------------------
11778 	if (sound.isValid()) {
11779 		game_snd *snd = gamesnd_get_game_sound(sound);
11780 		if (snd->min == 0 && snd->max == 0) {
11781 			// if sound doesn't specify 3d range, don't play in 3d
11782 			snd_play( snd, 0.0f, 1.0f, SND_PRIORITY_MUST_PLAY );
11783 		} else {
11784 			snd_play_3d( snd, &origin, &View_position, 0.0f, nullptr, 0, 1.0f, SND_PRIORITY_MUST_PLAY );
11785 		}
11786 	}
11787 }
11788 
11789 // Goober5000
sexp_close_sound_from_file(int n)11790 void sexp_close_sound_from_file(int n)
11791 {
11792 	// fade out?
11793 	bool fade;
11794 	if (n < 0)
11795 		fade = true;
11796 	else
11797 	{
11798 		fade = is_sexp_true(n);
11799 		n = CDR(n);
11800 	}
11801 
11802 	// we might have a variable
11803 	int sexp_var = -1;
11804 	if (n >= 0)
11805 	{
11806 		sexp_var = sexp_get_variable_index(n);
11807 
11808 		if (!(Sexp_variables[sexp_var].type & SEXP_VARIABLE_NUMBER))
11809 		{
11810 			Warning(LOCATION, "close-sound-from-file: Variable %s must be a number variable!", Sexp_variables[sexp_var].variable_name);
11811 			return;
11812 		}
11813 	}
11814 
11815 	// stop sound file
11816 	sexp_stop_music(fade, sexp_var);
11817 
11818 	if (MULTIPLAYER_MASTER)
11819 	{
11820 		Current_sexp_network_packet.start_callback();
11821 		Current_sexp_network_packet.send_bool(fade);
11822 		Current_sexp_network_packet.send_int(sexp_var);
11823 		Current_sexp_network_packet.end_callback();
11824 	}
11825 }
11826 
multi_sexp_close_sound_from_file()11827 void multi_sexp_close_sound_from_file()
11828 {
11829 	bool fade = true;
11830 	int sexp_var = -1;
11831 
11832 	Current_sexp_network_packet.get_bool(fade);
11833 	Current_sexp_network_packet.get_int(sexp_var);
11834 
11835 	sexp_stop_music(fade, sexp_var);
11836 }
11837 
11838 // Goober5000
sexp_play_sound_from_file(int n)11839 void sexp_play_sound_from_file(int n)
11840 {
11841 	// get the music track
11842 	const char *filename = CTEXT(n);
11843 	n = CDR(n);
11844 
11845 	// we might loop it
11846 	bool loop = false;
11847 	if (n >= 0)
11848 	{
11849 		loop = (eval_sexp(n) != 0);
11850 		n = CDR(n);
11851 	}
11852 
11853 	// we might use environmental effects
11854 	int type = ASF_MENUMUSIC;
11855 	if (n >= 0)
11856 	{
11857 		if (eval_sexp(n) != 0)
11858 			type = ASF_SOUNDFX;
11859 		n = CDR(n);
11860 	}
11861 
11862 	// we might have a variable
11863 	int sexp_var = -1;
11864 	if (n >= 0)
11865 	{
11866 		sexp_var = sexp_get_variable_index(n);
11867 
11868 		if (!(Sexp_variables[sexp_var].type & SEXP_VARIABLE_NUMBER))
11869 		{
11870 			Warning(LOCATION, "play-sound-from-file: Variable %s must be a number variable!", Sexp_variables[sexp_var].variable_name);
11871 			return;
11872 		}
11873 	}
11874 
11875 	// load sound file
11876 	sexp_load_music(filename, type, sexp_var);
11877 
11878 	// play sound file
11879 	sexp_start_music(loop, sexp_var);
11880 
11881 	if (MULTIPLAYER_MASTER)
11882 	{
11883 		Current_sexp_network_packet.start_callback();
11884 		Current_sexp_network_packet.send_string(filename);
11885 		Current_sexp_network_packet.send_bool(loop);
11886 		Current_sexp_network_packet.send_int(type);
11887 		Current_sexp_network_packet.send_int(sexp_var);
11888 		Current_sexp_network_packet.end_callback();
11889 	}
11890 }
11891 
multi_sexp_play_sound_from_file()11892 void multi_sexp_play_sound_from_file()
11893 {
11894 	char filename[NAME_LENGTH];
11895 	bool loop = false;
11896 	int type = -1;
11897 	int sexp_var = -1;
11898 
11899 	if (!Current_sexp_network_packet.get_string(filename))
11900 		return;
11901 	Current_sexp_network_packet.get_bool(loop);
11902 	Current_sexp_network_packet.get_int(type);
11903 	Current_sexp_network_packet.get_int(sexp_var);
11904 
11905 	sexp_load_music(filename, type, sexp_var);
11906 	sexp_start_music(loop, sexp_var);
11907 }
11908 
11909 // Goober5000
sexp_pause_sound_from_file(int n)11910 void sexp_pause_sound_from_file(int n)
11911 {
11912 	bool pause = is_sexp_true(n);
11913 	n = CDR(n);
11914 
11915 	// we might have a variable
11916 	int sexp_var = -1;
11917 	if (n >= 0)
11918 	{
11919 		sexp_var = sexp_get_variable_index(n);
11920 
11921 		if (!(Sexp_variables[sexp_var].type & SEXP_VARIABLE_NUMBER))
11922 		{
11923 			Warning(LOCATION, "pause-sound-from-file: Variable %s must be a number variable!", Sexp_variables[sexp_var].variable_name);
11924 			return;
11925 		}
11926 	}
11927 
11928 	if (MULTIPLAYER_MASTER)
11929 	{
11930 		Current_sexp_network_packet.start_callback();
11931 		Current_sexp_network_packet.send_bool(pause);
11932 		Current_sexp_network_packet.send_int(sexp_var);
11933 		Current_sexp_network_packet.end_callback();
11934 	}
11935 
11936 	sexp_pause_unpause_music(pause, sexp_var);
11937 }
11938 
multi_sexp_pause_sound_from_file()11939 void multi_sexp_pause_sound_from_file()
11940 {
11941 	bool pause;
11942 	int sexp_var = -1;
11943 
11944 	if (!Current_sexp_network_packet.get_bool(pause))
11945 		return;
11946 	Current_sexp_network_packet.get_int(sexp_var);
11947 
11948 	sexp_pause_unpause_music(pause, sexp_var);
11949 }
11950 
sexp_sound_environment_option_lookup(const char * text)11951 int sexp_sound_environment_option_lookup(const char *text)
11952 {
11953 	int i;
11954 
11955 	Assert(text != nullptr);
11956 	if (text == nullptr) {
11957 		return -1;
11958 	}
11959 
11960 	for (i = 0; i < Num_sound_environment_options; i++) {
11961 		if (!strcmp(text, Sound_environment_option[i])) {
11962 			return i;
11963 		}
11964 	}
11965 
11966 	return -1;
11967 }
11968 
11969 // Taylor
sexp_set_sound_environment(int node)11970 void sexp_set_sound_environment(int node)
11971 {
11972 	int n = node;
11973 	sound_env env;
11974 	int preset_id = -1;
11975 	bool is_nan, is_nan_forever;
11976 
11977 	auto preset = CTEXT(n);
11978 	n = CDR(n);
11979 
11980 	if ( preset && !stricmp(preset, SEXP_NONE_STRING) ) {
11981 		sound_env_disable();
11982 		return;
11983 	}
11984 
11985 	preset_id = ds_eax_get_preset_id( preset );
11986 	if (preset_id < 0) {
11987 		return;
11988 	}
11989 
11990 	// fill in defaults for this preset, in case we don't set everything
11991 	if ( sound_env_get(&env, preset_id) ) {
11992 		return;
11993 	}
11994 
11995 	while (n >= 0) {
11996 		int option = sexp_sound_environment_option_lookup(CTEXT(n));
11997 		n = CDR(n);
11998 
11999 		// watch out for bogus options
12000 		if (n < 0) {
12001 			break;
12002 		}
12003 
12004 		float val = (float)eval_num(n, is_nan, is_nan_forever) / 1000.0f;
12005 		n = CDR(n);
12006 		if (is_nan || is_nan_forever)
12007 			continue;
12008 
12009 		if ( option == SEO_VOLUME ) {
12010 			env.volume = val;
12011 		} else if ( option == SEO_DECAY_TIME ) {
12012 			env.decay = val;
12013 		} else if ( option == SEO_DAMPING ) {
12014 			env.damping = val;
12015 		}
12016 	}
12017 
12018 	sound_env_set(&env);
12019 }
12020 
12021 // Taylor
sexp_update_sound_environment(int node)12022 void sexp_update_sound_environment(int node)
12023 {
12024 	int n = node;
12025 	bool is_nan, is_nan_forever;
12026 
12027 	while (n >= 0) {
12028 		int option = sexp_sound_environment_option_lookup(CTEXT(n));
12029 		n = CDR(n);
12030 
12031 		// watch out for bogus options
12032 		if (n < 0) {
12033 			break;
12034 		}
12035 
12036 		float val = (float)eval_num(n, is_nan, is_nan_forever) / 1000.0f;
12037 		n = CDR(n);
12038 		if (is_nan || is_nan_forever)
12039 			continue;
12040 
12041 		if ( option == SEO_VOLUME ) {
12042 			ds_eax_set_volume(val);
12043 		} else if ( option == SEO_DECAY_TIME ) {
12044 			ds_eax_set_decay_time(val);
12045 		} else if ( option == SEO_DAMPING ) {
12046 			ds_eax_set_damping(val);
12047 		}
12048 	}
12049 }
12050 
12051 //The E
12052 	//From sexp help:
12053 	//{ OP_ADJUST_AUDIO_VOLUME, "adjust-audio-volume\r\n"
12054 	//	"Adjusts the relative volume of one sound type. Takes 2 or 3 arguments....\r\n"
12055 	//	"\t1:\tSound Type to adjust, either Music, Voice or Effects\r\n"
12056 	//	"\t2:\tPercentage of the users' settings to adjust to, 0 will be silence, 100 means the maximum volume as set by the user\r\n"
12057 	//	"\t3:\tFade time (optional), time in milliseconds to adjust the volume"},
12058 
audio_volume_option_lookup(const char * text)12059 int audio_volume_option_lookup(const char *text)
12060 {
12061 	int i;
12062 
12063 	Assert(text != nullptr);
12064 	if (text == nullptr) {
12065 		return -1;
12066 	}
12067 
12068 	for (i = 0; i < Num_adjust_audio_options; i++) {
12069 		if (!strcmp(text, Adjust_audio_options[i])) {
12070 			return i;
12071 		}
12072 	}
12073 
12074 	return -1;
12075 }
12076 
sexp_adjust_audio_volume(int node)12077 void sexp_adjust_audio_volume(int node)
12078 {
12079 	int n = node;
12080 	bool is_nan, is_nan_forever;
12081 
12082 	if (n > 0) {
12083 		int option = audio_volume_option_lookup(CTEXT(n));
12084 		if (option >= 0) {
12085 			n = CDR(n);
12086 
12087 			float target_volume = 1.0f;
12088 			if (n >= 0) {
12089 				target_volume = (float)eval_num(n, is_nan, is_nan_forever) / 100.0f;
12090 				CLAMP(target_volume, 0.0f, 1.0f);
12091 				n = CDR(n);
12092 
12093 				if (is_nan || is_nan_forever)
12094 					return;
12095 			}
12096 
12097 			int time = 0;
12098 			if (n >= 0) {
12099 				time = eval_num(n, is_nan, is_nan_forever);
12100 				n = CDR(n);
12101 
12102 				if (is_nan || is_nan_forever)
12103 					return;
12104 			}
12105 
12106 			snd_adjust_audio_volume(option, target_volume, time);
12107 		}
12108 	}
12109 }
12110 
sexp_explosion_option_lookup(const char * text)12111 int sexp_explosion_option_lookup(const char *text)
12112 {
12113 	int i;
12114 
12115 	Assert(text != nullptr);
12116 	if (text == nullptr) {
12117 		return -1;
12118 	}
12119 
12120 	for (i = 0; i < Num_explosion_options; i++) {
12121 		if (!strcmp(text, Explosion_option[i])) {
12122 			return i;
12123 		}
12124 	}
12125 
12126 	return -1;
12127 }
12128 
12129 // Goober5000
sexp_set_explosion_option(int node)12130 void sexp_set_explosion_option(int node)
12131 {
12132 	int n = node;
12133 	ship_info *sip;
12134 	shockwave_create_info *sci;
12135 	bool is_nan, is_nan_forever;
12136 
12137 	// get ship
12138 	auto ship_entry = eval_ship(n);
12139 	if (!ship_entry || !ship_entry->shipp)
12140 		return;
12141 	n = CDR(n);
12142 
12143 	auto shipp = ship_entry->shipp;
12144 	sip = &Ship_info[shipp->ship_info_index];
12145 	sci = &sip->shockwave;
12146 
12147 
12148 	// if we haven't changed anything yet, create a new special-exp with the same values as a standard exp
12149 	if (!shipp->use_special_explosion)
12150 	{
12151 		shipp->special_exp_damage = fl2i(sci->damage);
12152 		shipp->special_exp_blast = fl2i(sci->blast);
12153 		shipp->special_exp_inner = fl2i(sci->inner_rad);
12154 		shipp->special_exp_outer = fl2i(sci->outer_rad);
12155 		shipp->special_exp_shockwave_speed = fl2i(sci->speed);
12156 		shipp->special_exp_deathroll_time = 0;
12157 
12158 		shipp->use_special_explosion = true;
12159 		shipp->use_shockwave = (sci->speed > 0);
12160 	}
12161 
12162 	// process all options
12163 	while (n >= 0)
12164 	{
12165 		int option = sexp_explosion_option_lookup(CTEXT(n));
12166 		n = CDR(n);
12167 
12168 		// watch out for bogus options
12169 		if (n < 0)
12170 			break;
12171 
12172 		int val = eval_num(n, is_nan, is_nan_forever);
12173 		Assert(val >= 0);	// should be true due to OPF_POSITIVE
12174 		n = CDR(n);
12175 
12176 		if (is_nan || is_nan_forever) {
12177 			continue;
12178 		} else if (option == EO_DAMAGE) {
12179 			shipp->special_exp_damage = val;
12180 		} else if (option == EO_BLAST) {
12181 			shipp->special_exp_blast = val;
12182 		} else if (option == EO_INNER_RADIUS) {
12183 			shipp->special_exp_inner = val;
12184 		} else if (option == EO_OUTER_RADIUS) {
12185 			shipp->special_exp_outer = val;
12186 		} else if (option == EO_SHOCKWAVE_SPEED) {
12187 			shipp->special_exp_shockwave_speed = val;
12188 			shipp->use_shockwave = (val > 0);
12189 		} else if (option == EO_DEATH_ROLL_TIME) {
12190 			shipp->special_exp_deathroll_time = val;
12191 
12192 			// hmm, it would be cool to modify the explosion in progress
12193 			if (shipp->flags[Ship::Ship_Flags::Dying] && val >= 2) {
12194 				shipp->final_death_time = timestamp(val);
12195 			}
12196 		}
12197 	}
12198 
12199 	// if all our values are the same as a standard exp, turn off the special exp
12200 	if ((shipp->special_exp_damage == sci->damage) && (shipp->special_exp_blast == sci->blast) && (shipp->special_exp_inner == sci->inner_rad)
12201 		&& (shipp->special_exp_outer == sci->outer_rad) && (shipp->special_exp_shockwave_speed == sci->speed) && (shipp->special_exp_deathroll_time == 0))
12202 	{
12203 		shipp->use_special_explosion = false;
12204 		shipp->use_shockwave = false;
12205 
12206 		shipp->special_exp_damage = -1;
12207 		shipp->special_exp_blast = -1;
12208 		shipp->special_exp_inner = -1;
12209 		shipp->special_exp_outer = -1;
12210 		shipp->special_exp_shockwave_speed = -1;
12211 		shipp->special_exp_deathroll_time = 0;
12212 	}
12213 }
12214 
12215 // Goober5000
12216 // Basically, this function pretends that there's a ship at the origin that's blowing up, and
12217 // it does stuff accordingly.  In some places, it has to tiptoe around a little because the
12218 // code often expects a parent object when in fact there is none. <.<  >.>
sexp_explosion_effect(int n)12219 void sexp_explosion_effect(int n)
12220 {
12221 	vec3d origin;
12222 	int max_damage, max_blast, explosion_size, inner_radius, outer_radius, shockwave_speed, num, fireball_type;
12223 	int emp_intensity, emp_duration;
12224 	bool use_emp_time_for_capship_turrets, is_nan, is_nan_forever;
12225 
12226 	Assert( n >= 0 );
12227 
12228 	// read in data --------------------------------
12229 	std::array<int, 9> numbers;
12230 	eval_array(numbers, n, is_nan, is_nan_forever);
12231 	if (is_nan || is_nan_forever)
12232 		return;
12233 
12234 	origin.xyz.x = (float)numbers[0];
12235 	origin.xyz.y = (float)numbers[1];
12236 	origin.xyz.z = (float)numbers[2];
12237 	max_damage = numbers[3];
12238 	max_blast = numbers[4];
12239 	explosion_size = numbers[5];
12240 	inner_radius = numbers[6];
12241 	outer_radius = numbers[7];
12242 	shockwave_speed = numbers[8];
12243 
12244 	// fireball type
12245 	// -------------
12246 
12247 	// this node is another SEXP operator or a plain number
12248 	num = -1;
12249 	if (CAR(n) != -1 || Sexp_nodes[n].subtype == SEXP_ATOM_NUMBER)
12250 	{
12251 		num = eval_num(n, is_nan, is_nan_forever);
12252 		if (is_nan || is_nan_forever)
12253 			return;
12254 	}
12255 	else if (sexp_can_construe_as_integer(n))
12256 		num = sexp_atoi(n);
12257 
12258 	// is it a number?
12259 	if (num >= 0)
12260 	{
12261 		if (num == 0)
12262 		{
12263 			fireball_type = FIREBALL_EXPLOSION_MEDIUM;
12264 		}
12265 		else if (num == 1)
12266 		{
12267 			fireball_type = FIREBALL_EXPLOSION_LARGE1;
12268 		}
12269 		else if (num == 2)
12270 		{
12271 			fireball_type = FIREBALL_EXPLOSION_LARGE2;
12272 		}
12273 		else if (num >= Num_fireball_types)
12274 		{
12275 			Warning(LOCATION, "explosion-effect fireball type is out of range; quitting the explosion...\n");
12276 			return;
12277 		}
12278 		else
12279 		{
12280 			fireball_type = num;
12281 		}
12282 	}
12283 	// it's gotta be a name
12284 	else
12285 	{
12286 		const char *unique_id = CTEXT(n);
12287 		fireball_type = fireball_info_lookup(unique_id);
12288 
12289 		if (fireball_type < 0)
12290 		{
12291 			Warning(LOCATION, "unrecognized fireball entry \'%s\'; quitting the explosion...\n", unique_id);
12292 			return;
12293 		}
12294 	}
12295 	n = CDR(n);
12296 
12297 	// -------------
12298 
12299 	auto sound_index = sexp_get_sound_index(n);
12300 	n = CDR(n);
12301 
12302 	// optional EMP
12303 	eval_nums(n, is_nan, is_nan_forever, emp_intensity, emp_duration);
12304 	if (is_nan || is_nan_forever)
12305 		return;
12306 	use_emp_time_for_capship_turrets = false;
12307 	if (n != -1)
12308 	{
12309 		use_emp_time_for_capship_turrets = is_sexp_true(n);
12310 		n = CDR(n);
12311 	}
12312 
12313 
12314 	// play sound effect ---------------------------
12315 	if (sound_index.isValid())
12316 	{
12317 		snd_play_3d( gamesnd_get_game_sound(sound_index), &origin, &View_position, 0.0f, nullptr, 0, 1.0f, SND_PRIORITY_MUST_PLAY  );
12318 	}
12319 
12320 
12321 	// create the fireball -------------------------
12322 	if (explosion_size && inner_radius && outer_radius)
12323 	{
12324 		if(fireball_type == FIREBALL_EXPLOSION_MEDIUM)
12325 			fireball_create( &origin, fireball_type, FIREBALL_MEDIUM_EXPLOSION, -1, (float)explosion_size );
12326 		else
12327 			fireball_create( &origin, fireball_type, FIREBALL_LARGE_EXPLOSION, -1, (float)explosion_size );
12328 	}
12329 
12330 	// apply area affect damage --------------------
12331 	if (max_damage || max_blast)
12332 	{
12333 		if ( shockwave_speed > 0 )
12334 		{
12335 			shockwave_create_info sci;
12336 			shockwave_create_info_init(&sci);
12337 
12338 			sci.inner_rad = (float)inner_radius;
12339 			sci.outer_rad = (float)outer_radius;
12340 			sci.blast = (float)max_blast;
12341 			sci.damage = (float)max_damage;
12342 			sci.speed = (float)shockwave_speed;
12343 			sci.rot_angles.p = frand_range(0.0f, 1.99f*PI);
12344 			sci.rot_angles.b = frand_range(0.0f, 1.99f*PI);
12345 			sci.rot_angles.h = frand_range(0.0f, 1.99f*PI);
12346 			shockwave_create(-1, &origin, &sci, SW_SHIP_DEATH);
12347 		}
12348 		else
12349 		{
12350 			object *objp;
12351 			float t_blast = 0.0f;
12352 			float t_damage = 0.0f;
12353 			for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) )
12354 			{
12355 				if ( (objp->type != OBJ_SHIP) && (objp->type != OBJ_ASTEROID) )
12356 				{
12357 					continue;
12358 				}
12359 
12360 				// don't blast navbuoys
12361 				if ( objp->type == OBJ_SHIP )
12362 				{
12363 					if ( ship_get_SIF(objp->instance)[Ship::Info_Flags::Navbuoy] )
12364 					{
12365 						continue;
12366 					}
12367 				}
12368 
12369 				if ( ship_explode_area_calc_damage( &origin, &objp->pos, (float)inner_radius, (float)outer_radius, (float)max_damage, (float)max_blast, &t_damage, &t_blast ) == -1 )
12370 				{
12371 					continue;
12372 				}
12373 
12374 				switch ( objp->type )
12375 				{
12376 					case OBJ_SHIP:
12377 						ship_apply_global_damage( objp, nullptr, &origin, t_damage, -1 );
12378 						vec3d force, vec_ship_to_impact;
12379 						vm_vec_sub( &vec_ship_to_impact, &objp->pos, &origin );
12380 						if (!IS_VEC_NULL_SQ_SAFE( &vec_ship_to_impact )) {
12381 							vm_vec_copy_normalize( &force, &vec_ship_to_impact );
12382 							vm_vec_scale( &force, (float)max_blast );
12383 							ship_apply_whack( &force, &origin, objp );
12384 						}
12385 						break;
12386 
12387 					case OBJ_ASTEROID:
12388 						asteroid_hit(objp, nullptr, nullptr, t_damage);
12389 						break;
12390 
12391 					default:
12392 						Assertion(false, "Object magically changed type after exploding!");
12393 						break;
12394 				}
12395 			}	// end for
12396 		}
12397 	}
12398 
12399 
12400 	// apply emp damage if applicable --------------
12401 	if (emp_intensity && emp_duration)
12402 	{
12403 		emp_apply(&origin, (float)inner_radius, (float)outer_radius, (float)emp_intensity, (float)emp_duration, use_emp_time_for_capship_turrets);
12404 	}
12405 }
12406 
12407 // Goober5000
sexp_warp_effect(int n)12408 void sexp_warp_effect(int n)
12409 {
12410 	vec3d origin, location, v_orient;
12411 	matrix m_orient;
12412 	int num, shape, fireball_type, extra_flags = FBF_WARP_VIA_SEXP;
12413 	float radius, duration, warp_open_duration, warp_close_duration;
12414 	bool is_nan, is_nan_forever;
12415 
12416 	// read in data --------------------------------
12417 	std::array<float, 8> numbers;
12418 	eval_array(numbers, n, is_nan, is_nan_forever);
12419 	if (is_nan || is_nan_forever)
12420 		return;
12421 
12422 	origin.xyz.x = numbers[0];
12423 	origin.xyz.y = numbers[1];
12424 	origin.xyz.z = numbers[2];
12425 
12426 	location.xyz.x = numbers[3];
12427 	location.xyz.y = numbers[4];
12428 	location.xyz.z = numbers[5];
12429 
12430 	radius = numbers[6];
12431 	duration = numbers[7];
12432 
12433 	if (duration < 4.0f)
12434 		duration = 4.0f;
12435 
12436 	auto warp_open_sound_index = sexp_get_sound_index(n);
12437 	n = CDR(n);
12438 	auto warp_close_sound_index = sexp_get_sound_index(n);
12439 	n = CDR(n);
12440 
12441 	// fireball type
12442 	// -------------
12443 
12444 	// this node is another SEXP operator or a plain number
12445 	num = -1;
12446 	if (CAR(n) != -1 || Sexp_nodes[n].subtype == SEXP_ATOM_NUMBER)
12447 	{
12448 		num = eval_num(n, is_nan, is_nan_forever);
12449 		if (is_nan || is_nan_forever)
12450 			return;
12451 	}
12452 	else if (sexp_can_construe_as_integer(n))
12453 		num = sexp_atoi(n);
12454 
12455 	// is it a number?
12456 	if (num >= 0)
12457 	{
12458 		if (num == 0)
12459 		{
12460 			fireball_type = FIREBALL_WARP;
12461 		}
12462 		else if (num == 1)
12463 		{
12464 			fireball_type = FIREBALL_KNOSSOS;
12465 		}
12466 		else if (num >= Num_fireball_types)
12467 		{
12468 			Warning(LOCATION, "warp-effect fireball type is out of range; quitting the warp...\n");
12469 			return;
12470 		}
12471 		else
12472 		{
12473 			fireball_type = num;
12474 		}
12475 	}
12476 	// it's gotta be a name
12477 	else
12478 	{
12479 		const char *unique_id = CTEXT(n);
12480 		fireball_type = fireball_info_lookup(unique_id);
12481 
12482 		if (fireball_type < 0)
12483 		{
12484 			Warning(LOCATION, "unrecognized fireball entry \'%s\'; quitting the warp...\n", unique_id);
12485 			return;
12486 		}
12487 	}
12488 	n = CDR(n);
12489 
12490 	// -------------
12491 
12492 	// shape
12493 	shape = eval_num(n, is_nan, is_nan_forever);
12494 	if (is_nan || is_nan_forever)
12495 		return;
12496 	if (shape == 0)
12497 	{
12498 		// do nothing; this is standard
12499 	}
12500 	else if (shape == 1)
12501 	{
12502 		extra_flags |= FBF_WARP_3D;
12503 	}
12504 	else
12505 	{
12506 		Warning(LOCATION, "warp-effect shape is out of range; quitting the warp...\n");
12507 		return;
12508 	}
12509 	n = CDR(n);
12510 
12511 	// opening and closing durations
12512 	warp_open_duration = warp_close_duration = -1.0f;
12513 	if (n >= 0)
12514 	{
12515 		warp_open_duration = warp_close_duration = ((float)eval_num(n, is_nan, is_nan_forever)) / 1000.0f;
12516 		n = CDR(n);
12517 
12518 		if (is_nan || is_nan_forever)
12519 			return;
12520 	}
12521 	if (n >= 0)
12522 	{
12523 		warp_close_duration = ((float)eval_num(n, is_nan, is_nan_forever)) / 1000.0f;
12524 		n = CDR(n);
12525 
12526 		if (is_nan || is_nan_forever)
12527 			return;
12528 	}
12529 
12530 	// sanity check, if these were specified
12531 	if (duration < warp_open_duration + warp_close_duration)
12532 	{
12533 		Warning(LOCATION, "Both warp opening and warp closing must occur within the duration of the warp effect.  Adjusting opening and closing durations to fit.");
12534 		warp_open_duration = warp_close_duration = duration / 2.0f;
12535 	}
12536 
12537 
12538 	// calculate orientation matrix ----------------
12539 
12540 	vm_vec_sub(&v_orient, &location, &origin);
12541 
12542 	if (IS_VEC_NULL_SQ_SAFE(&v_orient))
12543 	{
12544 		Warning(LOCATION, "error in warp-effect: warp can't point to itself; quitting the warp...\n");
12545 		return;
12546 	}
12547 
12548 	vm_vector_2_matrix(&m_orient, &v_orient, nullptr, nullptr);
12549 
12550 	// create fireball -----------------------------
12551 
12552 	fireball_create(&origin, fireball_type, FIREBALL_WARP_EFFECT, -1, radius, false, nullptr, duration, -1, &m_orient, 0, extra_flags, warp_open_sound_index, warp_close_sound_index, warp_open_duration, warp_close_duration);
12553 }
12554 
12555 // this function get called by send-message or send-message random with the name of the message, sender,
12556 // and priority.
sexp_send_one_message(const char * name,const char * who_from,const char * priority,int group,int delay,int event_num=-1)12557 void sexp_send_one_message( const char *name, const char *who_from, const char *priority, int group, int delay, int event_num = -1 )
12558 {
12559 	int ipriority, source;
12560 	int ship_index = -1;
12561 	int wingnum = -1;
12562 	ship *shipp;
12563 
12564 	if(physics_paused){
12565 		return;
12566 	}
12567 
12568 	Assert( (name != nullptr) && (who_from != nullptr) && (priority != nullptr) );
12569 
12570 	// determine the priority of the message
12571 	if ( !stricmp(priority, "low") )
12572 		ipriority = MESSAGE_PRIORITY_LOW;
12573 	else if ( !stricmp(priority, "normal") )
12574 		ipriority = MESSAGE_PRIORITY_NORMAL;
12575 	else if ( !stricmp(priority, "high") )
12576 		ipriority = MESSAGE_PRIORITY_HIGH;
12577 	else {
12578 		Warning(LOCATION, "Encountered invalid priority \"%s\" in send-message", priority);
12579 		ipriority = MESSAGE_PRIORITY_NORMAL;
12580 	}
12581 
12582 	// do some pre-checks
12583 	auto ship_entry = ship_registry_get(who_from);
12584 	if (!ship_entry)
12585 		wingnum = wing_lookup(who_from);
12586 
12587 	// check to see if the 'who_from' string is a ship that had been destroyed or departed.  If so,
12588 	// then don't send the message.  We must look at 'who_from' to determine what to look for.  who_from
12589 	// may be any allied person, any wingman, a wingman from a specific wing, or a specific ship
12590 	shipp = nullptr;
12591 	source = MESSAGE_SOURCE_COMMAND;
12592 	if ( who_from[0] == '#' ) {
12593 		message_send_unique_to_player( name, &(who_from[1]), MESSAGE_SOURCE_SPECIAL, ipriority, group, delay, event_num );
12594 		return;
12595 	} else if (!stricmp(who_from, "<any allied>")) {
12596 		return;
12597 	} else if ( wingnum >= 0 && Wings[wingnum].current_count > 0 ) {
12598 		// message from a wing
12599 		// choose wing leader to speak for wing (hence "1" at end of ship_get_random_ship_in_wing)
12600 		ship_index = ship_get_random_ship_in_wing( wingnum, SHIP_GET_UNSILENCED, 1 );
12601 		if ( ship_index == -1 ) {
12602 			if ( ipriority != MESSAGE_PRIORITY_HIGH )
12603 				return;
12604 			source = MESSAGE_SOURCE_COMMAND;
12605 		}
12606 	} else if ( ship_entry && ship_entry->shipp ) {
12607 		// Message from a specific ship
12608 		ship_index = ship_entry->objp->instance;
12609 
12610 	} else if ( ship_entry || wingnum >= 0  ) {
12611 		// getting into this if statement means that the ship or wing (sender) is no longer (or not yet) in the mission
12612 		// if message is high priority, make it come from Terran Command
12613 		if ( ipriority != MESSAGE_PRIORITY_HIGH )
12614 			return;
12615 		source = MESSAGE_SOURCE_COMMAND;
12616 
12617 	} else if ( !stricmp(who_from, "<any wingman>") || (wing_name_lookup(who_from) != -1) ) {
12618 		source = MESSAGE_SOURCE_WINGMAN;
12619 	} else if ( !stricmp(who_from, "<none>") ) {
12620 		source = MESSAGE_SOURCE_NONE;
12621 	} else {
12622 		Assertion(false, "Curious... all message sources should have been covered");
12623 	}
12624 
12625 	if ( ship_index == -1 ){
12626 		shipp = nullptr;
12627 	} else {
12628 		shipp = &Ships[ship_index];
12629 		source = MESSAGE_SOURCE_SHIP;
12630 	}
12631 
12632 	message_send_unique_to_player( name, shipp, source, ipriority, group, delay, event_num );
12633 }
12634 
sexp_send_message(int n)12635 void sexp_send_message(int n)
12636 {
12637 	if(physics_paused){
12638 		return;
12639 	}
12640 
12641 	Assert ( n != -1 );
12642 	auto who_from = CTEXT(n);
12643 	auto priority = CTEXT(CDR(n));
12644 	auto name = CTEXT(CDR(CDR(n)));
12645 
12646 	// a temporary check to see if the name field matched a priority since I am in the process
12647 	// of reordering the arguments
12648 	if ( !stricmp(name, "low") || !stricmp(name, "normal") || !stricmp(name, "high") ) {
12649 		auto tmp = name;
12650 		name = priority;
12651 		priority = tmp;
12652 	}
12653 
12654 	// we might override the sender
12655 	if (The_mission.flags[Mission::Mission_Flags::Override_hashcommand] && !strcmp(who_from, "#Command"))
12656 		who_from = The_mission.command_sender;
12657 
12658 	sexp_send_one_message( name, who_from, priority, 0, 0 );
12659 }
12660 
sexp_send_message_list(int n,bool send_message_chain)12661 void sexp_send_message_list(int n, bool send_message_chain)
12662 {
12663 	int delay = 0, event_num = -1;
12664 	bool is_nan, is_nan_forever;
12665 
12666 	if(physics_paused){
12667 		return;
12668 	}
12669 
12670 	// find the event that will cancel the message chain
12671 	if (send_message_chain) {
12672 		auto name = CTEXT(n);
12673 		n = CDR(n);
12674 
12675 		for (int i = 0; i < Num_mission_events; ++i) {
12676 			if (!stricmp(Mission_events[i].name, name)) {
12677 				event_num = i;
12678 				break;
12679 			}
12680 		}
12681 		if (event_num < 0) {
12682 			return;
12683 		}
12684 	}
12685 
12686 	// send a bunch of messages
12687 	while(n != -1){
12688 		auto who_from = CTEXT(n);
12689 		n = CDR(n);
12690 
12691 		// next node
12692 		if(n == -1){
12693 			Warning(LOCATION, "Detected incomplete parameter list in sexp-send-message-list");
12694 			return;
12695 		}
12696 		auto priority = CTEXT(n);
12697 		n = CDR(n);
12698 
12699 		// next node
12700 		if(n == -1){
12701 			Warning(LOCATION, "Detected incomplete parameter list in sexp-send-message-list");
12702 			return;
12703 		}
12704 		auto name = CTEXT(n);
12705 		n = CDR(n);
12706 
12707 		// next node
12708 		if(n == -1){
12709 			Warning(LOCATION, "Detected incomplete parameter list in sexp-send-message-list");
12710 			return;
12711 		}
12712 		delay += eval_num(n, is_nan, is_nan_forever);
12713 		n = CDR(n);
12714 
12715 		if (is_nan || is_nan_forever) {
12716 			Warning(LOCATION, "Encountered a NaN in sexp-send-message-list");
12717 			return;
12718 		}
12719 
12720 		// we might override the sender
12721 		if (The_mission.flags[Mission::Mission_Flags::Override_hashcommand] && !strcmp(who_from, "#Command"))
12722 			who_from = The_mission.command_sender;
12723 
12724 		// send the message
12725 		sexp_send_one_message(name, who_from, priority, 1, delay, event_num);
12726 	}
12727 }
12728 
sexp_send_random_message(int n)12729 void sexp_send_random_message(int n)
12730 {
12731 	int temp, num_messages, message_num;
12732 
12733 	Assert ( n != -1 );
12734 	auto who_from = CTEXT(n);
12735 	auto priority = CTEXT(CDR(n));
12736 
12737 	if(physics_paused){
12738 		return;
12739 	}
12740 
12741 	// count the number of messages that we have
12742 	n = CDR(CDR(n));
12743 	temp = n;
12744 	num_messages = 0;
12745 	while ( n != -1 ) {
12746 		n = CDR(n);
12747 		num_messages++;
12748 	}
12749 	Assert ( num_messages >= 1 );
12750 
12751 	// get a random message, and pass the parameters to send_one_message
12752 	message_num = Random::next(num_messages);
12753 	n = temp;
12754 	while ( n != -1 ) {
12755 		if ( message_num == 0 )
12756 			break;
12757 		message_num--;
12758 		n = CDR(n);
12759 	}
12760 	Assert (n != -1);		// should have found the message!!!
12761 	auto name = CTEXT(n);
12762 
12763 	sexp_send_one_message( name, who_from, priority, 0, 0 );
12764 }
12765 
sexp_self_destruct(int node)12766 void sexp_self_destruct(int node)
12767 {
12768 	for (int n = node; n != -1; n = CDR(n))	{
12769 		// get the ship
12770 		auto ship_entry = eval_ship(n);
12771 
12772 		// if it still exists, destroy it
12773 		if (ship_entry && ship_entry->status == ShipStatus::PRESENT) {
12774 			ship_self_destruct(ship_entry->objp);
12775 		}
12776 	}
12777 }
12778 
sexp_next_mission(int n)12779 void sexp_next_mission(int n)
12780 {
12781 	auto mission_name = CTEXT(n);
12782 
12783 	if (mission_name == nullptr) {
12784 		Error( LOCATION, "Mission name is NULL in campaign file for next-mission command!");
12785 	}
12786 
12787 	for (int i = 0; i < Campaign.num_missions; ++i) {
12788 		if ( !stricmp(Campaign.missions[i].name, mission_name) ) {
12789 			Campaign.next_mission = i;
12790 			return;
12791 		}
12792 	}
12793 	Error(LOCATION, "Mission name %s not found in campaign file for next-mission command", mission_name);
12794 }
12795 
12796 /**
12797  * Deal with the end-of-campaign sexpression.
12798  */
sexp_end_of_campaign(int)12799 void sexp_end_of_campaign(int  /*n*/)
12800 {
12801 	// this is really a do-nothing sexpression.  It is pretty much a placeholder to allow
12802 	// campaigns to have repeat-mission branches at the end of the campaign.  By not setting
12803 	// anything in this function, the higher level campaign code will see this as end-of-campaign
12804 	// since next_mission isn't set to anything.  (To be safe, we'll set to -1).
12805 	Campaign.next_mission = -1;
12806 }
12807 
12808 // sexpression to end everything.  One parameter is the movie to play when this is over.
12809 // Goober5000 - edited to only to the FS2-specific code when actually ending the FS2 main
12810 // campaign, and otherwise to do the conventional code
sexp_end_campaign(int n)12811 void sexp_end_campaign(int n)
12812 {
12813 	bool ignore_player_mortality = true;
12814 
12815 	if (!(Game_mode & GM_CAMPAIGN_MODE)) {
12816 		return;
12817 	}
12818 
12819 	if (n != -1) {
12820 		ignore_player_mortality = is_sexp_true(n);
12821 	}
12822 
12823 	// if the player is dead we may want to let the death screen handle things
12824 	if (!ignore_player_mortality && (Player_ship->flags[Ship::Ship_Flags::Dying])) {
12825 		return;
12826 	}
12827 
12828 	// in FS2 our ending is a bit wacky. we'll just flag the mission as having ended the campaign
12829 	//
12830 	// changed this to check for an active supernova rather than a special campaign since the supernova
12831 	// code needs special time to execute and will post GS_EVENT_END_CAMPAIGN with Game_mode check
12832 	// or show death-popup when it's done - taylor
12833 	if (supernova_active() /*&& !stricmp(Campaign.filename, "freespace2")*/) {
12834 		Campaign_ending_via_supernova = 1;
12835 	} else {
12836 		// post and event to move us to the end-of-campaign state
12837 		gameseq_post_event(GS_EVENT_END_CAMPAIGN);
12838 	}
12839 }
12840 
set_subsys_strength_and_maybe_ancestors(ship * shipp,ship_subsys * ss,polymodel * pm,const int * assign_percent,const int * repair_percent,const int * sabotage_percent,bool do_submodel_repair,bool repair_ancestors)12841 void set_subsys_strength_and_maybe_ancestors(ship *shipp, ship_subsys *ss, polymodel *pm, const int *assign_percent, const int *repair_percent, const int *sabotage_percent, bool do_submodel_repair, bool repair_ancestors)
12842 {
12843 	Assertion(shipp != nullptr && ss != nullptr, "Ship and subsys must not be null!");
12844 	Assertion(assign_percent != nullptr || repair_percent != nullptr || sabotage_percent != nullptr, "Either assign_percent or repair_percent or sabotage_percent must not be null!");
12845 
12846 	bool originally_zero = (ss->current_hits <= 0);
12847 	if (ss->submodel_instance_1 && ss->submodel_instance_1->blown_off)
12848 		originally_zero = true;
12849 	if (ss->submodel_instance_2 && ss->submodel_instance_2->blown_off)
12850 		originally_zero = true;
12851 
12852 	if (assign_percent != nullptr)
12853 	{
12854 		int percentage = *assign_percent;
12855 		Assertion(percentage >= 0 && percentage <= 100, "Percentage must be in range [0, 100]");
12856 
12857 		// assign the hitpoints
12858 		ss->current_hits = ss->max_hits * ((float)percentage / 100.0f);
12859 
12860 		// maybe blow up subsys
12861 		if (ss->current_hits <= 0 && !originally_zero)
12862 			do_subobj_destroyed_stuff(shipp, ss, nullptr);
12863 	}
12864 	else if (repair_percent != nullptr)
12865 	{
12866 		int percentage = *repair_percent;
12867 		Assertion(percentage > 0, "Repair percentage must be more than zero!");
12868 
12869 		// repair the hitpoints
12870 		float repair_hits = ss->max_hits * ((float)percentage / 100.0f);
12871 		ss->current_hits += repair_hits;
12872 		if (ss->current_hits > ss->max_hits)
12873 			ss->current_hits = ss->max_hits;
12874 	}
12875 	else if (sabotage_percent != nullptr)
12876 	{
12877 		int percentage = *sabotage_percent;
12878 		Assertion(percentage > 0, "Sabotage percentage must be more than zero!");
12879 
12880 		// sabotage the hitpoints
12881 		float sabotage_hits = ss->max_hits * ((float)percentage / 100.0f);
12882 		ss->current_hits -= sabotage_hits;
12883 		if (ss->current_hits < 0.0f)
12884 			ss->current_hits = 0.0f;
12885 
12886 		// maybe blow up subsys
12887 		if (ss->current_hits <= 0 && !originally_zero)
12888 			do_subobj_destroyed_stuff(shipp, ss, nullptr);
12889 	}
12890 	else
12891 		return;
12892 
12893 	// and now see if we are repairing from zero
12894 	if (originally_zero && ss->current_hits > 0 && do_submodel_repair)
12895 	{
12896 		if (ss->submodel_instance_1)
12897 			ss->submodel_instance_1->blown_off = false;
12898 		if (ss->submodel_instance_2)
12899 			ss->submodel_instance_2->blown_off = false;
12900 
12901 		// see if we are handling ancestors and if this subsystem has a submodel
12902 		int subobj = ss->system_info->subobj_num;
12903 		if (repair_ancestors && subobj >= 0)
12904 		{
12905 			if (pm == nullptr)
12906 				pm = model_get(Ship_info[shipp->ship_info_index].model_num);
12907 
12908 			// do we have a parent?
12909 			int parent_subobj = pm->submodel[subobj].parent;
12910 			if (parent_subobj >= 0)
12911 			{
12912 				// search for the subsystem
12913 				for (ship_subsys *parent_ss = GET_FIRST(&shipp->subsys_list); parent_ss != END_OF_LIST(&shipp->subsys_list); parent_ss = GET_NEXT(parent_ss))
12914 				{
12915 					// found parent?
12916 					if (parent_ss->system_info->subobj_num == parent_subobj)
12917 					{
12918 						bool parent_destroyed = (parent_ss->current_hits <= 0);
12919 						if (parent_ss->submodel_instance_1 && parent_ss->submodel_instance_1->blown_off)
12920 							parent_destroyed = true;
12921 						if (parent_ss->submodel_instance_2 && parent_ss->submodel_instance_2->blown_off)
12922 							parent_destroyed = true;
12923 
12924 						// if the parent subobject was destroyed...
12925 						if (parent_destroyed)
12926 						{
12927 							// ...repair the parent in the same way
12928 							set_subsys_strength_and_maybe_ancestors(shipp, parent_ss, pm, assign_percent, repair_percent, sabotage_percent, do_submodel_repair, repair_ancestors);
12929 						}
12930 
12931 						// only one parent
12932 						break;
12933 					}
12934 				}
12935 			}
12936 		}
12937 	}
12938 }
12939 
12940 /**
12941  * Reduces the strength of a subsystem by the given percentage.
12942  *
12943  * If it is reduced to below 0%, then the hits of the subsystem are set to 0
12944  */
sexp_sabotage_subsystem(int n)12945 void sexp_sabotage_subsystem(int n)
12946 {
12947 	const char *subsystem;
12948 	int	percentage, index, generic_type;
12949 	float sabotage_hits;
12950 	ship_subsys *ss = nullptr, *ss_start;
12951 	bool do_loop = true, is_nan, is_nan_forever;
12952 
12953 	auto ship_entry = eval_ship(n);
12954 	if (!ship_entry || !ship_entry->shipp)
12955 		return;
12956 	auto shipp = ship_entry->shipp;
12957 	n = CDR(n);
12958 
12959 	subsystem = CTEXT(n);
12960 	n = CDR(n);
12961 
12962 	percentage = eval_num(n, is_nan, is_nan_forever);
12963 	n = CDR(n);
12964 
12965 	if (is_nan || is_nan_forever)
12966 		return;
12967 
12968 	// abort if we're not even sabotaging anything
12969 	if (percentage <= 0) {
12970 		return;
12971 	}
12972 
12973 	// see if we are dealing with the HULL
12974 	if ( !stricmp( subsystem, SEXP_HULL_STRING) ) {
12975 		float ihs;
12976 		auto objp = ship_entry->objp;
12977 
12978 		ihs = shipp->ship_max_hull_strength;
12979 		sabotage_hits = ihs * ((float)percentage / 100.0f);
12980 		objp->hull_strength -= sabotage_hits;
12981 
12982 		// self destruct the ship if <= 0.
12983 		if ( objp->hull_strength <= 0.0f )
12984 			ship_self_destruct( objp );
12985 		return;
12986 	}
12987 
12988 	// see if we are dealing with the Simulated HULL
12989 	if ( !stricmp( subsystem, SEXP_SIM_HULL_STRING) ) {
12990 		float ihs;
12991 		auto objp = ship_entry->objp;
12992 
12993 		ihs = shipp->ship_max_hull_strength;
12994 		sabotage_hits = ihs * ((float)percentage / 100.0f);
12995 		objp->sim_hull_strength -= sabotage_hits;
12996 
12997 		return;
12998 	}
12999 
13000 	// now find the given subsystem on the ship.  This could be a generic type like <All Engines>
13001 	generic_type = get_generic_subsys(subsystem);
13002 	ss_start = GET_FIRST(&shipp->subsys_list);
13003 
13004 	while (do_loop) {
13005 		if (generic_type) {
13006 			// loop until we find a subsystem of that type
13007 			for ( ; ss_start != END_OF_LIST(&shipp->subsys_list); ss_start = GET_NEXT(ss_start)) {
13008 				ss = nullptr;
13009 				if (generic_type == ss_start->system_info->type) {
13010 					ss = ss_start;
13011 					ss_start = GET_NEXT(ss_start);
13012 					break;
13013 				}
13014 			}
13015 
13016 			// reached the end of the subsystem list
13017 			if (ss_start == END_OF_LIST(&shipp->subsys_list)) {
13018 				do_loop = false;
13019 				// If the last subsystem wasn't of interest we don't need to go any further
13020 				if (ss == nullptr) {
13021 					continue;
13022 				}
13023 			}
13024 		}
13025 		else {
13026 			do_loop = false;
13027 			index = ship_get_subsys_index(shipp, subsystem);
13028 			if ( index == -1 ) {
13029 				nprintf(("Warning", "Couldn't find subsystem %s on ship %s for sabotage subsystem\n", subsystem, shipp->ship_name));
13030 				continue;
13031 			}
13032 			// get the pointer to the subsystem.  Check it's current hits against it's max hits, and
13033 			// set the strength to the given percentage if current strength is > given percentage
13034 			ss = ship_get_indexed_subsys( shipp, index );
13035 			if (ss == nullptr) {
13036 				nprintf(("Warning", "Nonexistent subsystem for index %d on ship %s for sabotage subsystem\n", index, shipp->ship_name));
13037 				continue;
13038 			}
13039 		}
13040 
13041 		set_subsys_strength_and_maybe_ancestors(shipp, ss, nullptr, nullptr, nullptr, &percentage, false, false);
13042 	}
13043 
13044 	// recalculate when done
13045 	ship_recalc_subsys_strength(shipp);
13046 }
13047 
13048 /**
13049  * Adds some percentage of hits to a subsystem.
13050  *
13051  * Anything repaired about 100% is set to max hits
13052  */
sexp_repair_subsystem(int n)13053 void sexp_repair_subsystem(int n)
13054 {
13055 	const char *subsystem;
13056 	int	percentage, index, generic_type;
13057 	bool do_submodel_repair = true, do_ancestor_repair = true;
13058 	float repair_hits;
13059 	ship_subsys *ss = nullptr, *ss_start;
13060 	bool do_loop = true, is_nan, is_nan_forever;
13061 
13062 	auto ship_entry = eval_ship(n);
13063 	if (!ship_entry || !ship_entry->shipp)
13064 		return;
13065 	auto shipp = ship_entry->shipp;
13066 	n = CDR(n);
13067 
13068 	subsystem = CTEXT(n);
13069 	n = CDR(n);
13070 
13071 	percentage = eval_num(n, is_nan, is_nan_forever);
13072 	if (is_nan || is_nan_forever)
13073 		return;
13074 	n = CDR(n);
13075 
13076 	// abort if we're not even repairing anything
13077 	if (percentage <= 0) {
13078 		return;
13079 	}
13080 
13081 	if (n >= 0)
13082 	{
13083 		do_submodel_repair = is_sexp_true(n);
13084 		n = CDR(n);
13085 
13086 		if (n >= 0)
13087 			do_ancestor_repair = is_sexp_true(n);
13088 	}
13089 
13090 	// see if we are dealing with the HULL
13091 	if ( !stricmp( subsystem, SEXP_HULL_STRING) ) {
13092 		float ihs;
13093 		auto objp = ship_entry->objp;
13094 
13095 		ihs = shipp->ship_max_hull_strength;
13096 		repair_hits = ihs * ((float)percentage / 100.0f);
13097 		objp->hull_strength += repair_hits;
13098 
13099 		if ( objp->hull_strength > ihs )
13100 			objp->hull_strength = ihs;
13101 		return;
13102 	}
13103 
13104 	// see if we are dealing with the Simulated HULL
13105 	if ( !stricmp( subsystem, SEXP_SIM_HULL_STRING) ) {
13106 		float ihs;
13107 		auto objp = ship_entry->objp;
13108 
13109 		ihs = shipp->ship_max_hull_strength;
13110 		repair_hits = ihs * ((float)percentage / 100.0f);
13111 		objp->sim_hull_strength += repair_hits;
13112 
13113 		if ( objp->sim_hull_strength > ihs )
13114 			objp->sim_hull_strength = ihs;
13115 		return;
13116 	}
13117 
13118 	// now find the given subsystem on the ship.This could be a generic type like <All Engines>
13119 	generic_type = get_generic_subsys(subsystem);
13120 	ss_start = GET_FIRST(&shipp->subsys_list);
13121 
13122 	while (do_loop) {
13123 		if (generic_type) {
13124 			// loop until we find a subsystem of that type
13125 			for ( ; ss_start != END_OF_LIST(&shipp->subsys_list); ss_start = GET_NEXT(ss_start)) {
13126 				ss = nullptr;
13127 				if (generic_type == ss_start->system_info->type) {
13128 					ss = ss_start;
13129 					ss_start = GET_NEXT(ss_start);
13130 					break;
13131 				}
13132 			}
13133 
13134 			// reached the end of the subsystem list
13135 			if (ss_start == END_OF_LIST(&shipp->subsys_list)) {
13136 				do_loop = false;
13137 				// If the last subsystem wasn't of interest we don't need to go any further
13138 				if (ss == nullptr) {
13139 					continue;
13140 				}
13141 			}
13142 		}
13143 		else {
13144 			do_loop = false;
13145 			index = ship_get_subsys_index(shipp, subsystem);
13146 			if ( index == -1 ) {
13147 				nprintf(("Warning", "Couldn't find subsystem %s on ship %s for repair subsystem\n", subsystem, shipp->ship_name));
13148 				continue;
13149 			}
13150 			// get the pointer to the subsystem.  Check it's current hits against it's max hits, and
13151 			// set the strength to the given percentage if current strength is < given percentage
13152 			ss = ship_get_indexed_subsys( shipp, index );
13153 			if (ss == nullptr) {
13154 				nprintf(("Warning", "Nonexistent subsystem for index %d on ship %s for repair subsystem\n", index, shipp->ship_name));
13155 				continue;
13156 			}
13157 		}
13158 
13159 		set_subsys_strength_and_maybe_ancestors(shipp, ss, nullptr, nullptr, &percentage, nullptr, do_submodel_repair, do_ancestor_repair);
13160 	}
13161 
13162 	// recalculate when done
13163 	ship_recalc_subsys_strength(shipp);
13164 }
13165 
13166 /**
13167  * Set a subsystem of a ship at a specific percentage
13168  */
sexp_set_subsystem_strength(int n)13169 void sexp_set_subsystem_strength(int n)
13170 {
13171 	const char *subsystem;
13172 	int	percentage, index, generic_type;
13173 	bool do_submodel_repair = true, do_ancestor_repair = true;
13174 	ship_subsys *ss = nullptr, *ss_start;
13175 	bool do_loop = true, is_nan, is_nan_forever;
13176 
13177 	auto ship_entry = eval_ship(n);
13178 	if (!ship_entry || !ship_entry->shipp)
13179 		return;
13180 	auto shipp = ship_entry->shipp;
13181 	n = CDR(n);
13182 
13183 	subsystem = CTEXT(n);
13184 	n = CDR(n);
13185 
13186 	percentage = eval_num(n, is_nan, is_nan_forever);
13187 	if (is_nan || is_nan_forever)
13188 		return;
13189 	n = CDR(n);
13190 
13191 	if (n >= 0)
13192 	{
13193 		do_submodel_repair = is_sexp_true(n);
13194 		n = CDR(n);
13195 
13196 		if (n >= 0)
13197 			do_ancestor_repair = is_sexp_true(n);
13198 	}
13199 
13200 	if ( percentage > 100 ) {
13201 		mprintf(("Percentage for set_subsystem_strength > 100 on ship %s for subsystem '%s'-- setting to 100\n", ship_entry->name, subsystem));
13202 		percentage = 100;
13203 	} else if ( percentage < 0 ) {
13204 		mprintf(("Percantage for set_subsystem_strength < 0 on ship %s for subsystem '%s' -- setting to 0\n", ship_entry->name, subsystem));
13205 		percentage = 0;
13206 	}
13207 
13208 	// see if we are dealing with the HULL
13209 	if ( !stricmp( subsystem, SEXP_HULL_STRING) ) {
13210 		float ihs;
13211 		auto objp = ship_entry->objp;
13212 
13213 		// destroy the ship if percentage is 0
13214 		if ( percentage == 0 ) {
13215 			ship_self_destruct( objp );
13216 		} else {
13217 			ihs = shipp->ship_max_hull_strength;
13218 			objp->hull_strength = ihs * ((float)percentage / 100.0f);
13219 		}
13220 
13221 		return;
13222 	}
13223 
13224 	// see if we are dealing with the Simulated HULL
13225 	if ( !stricmp( subsystem, SEXP_SIM_HULL_STRING) ) {
13226 		float ihs;
13227 		auto objp = ship_entry->objp;
13228 
13229 		ihs = shipp->ship_max_hull_strength;
13230 		objp->sim_hull_strength = ihs * ((float)percentage / 100.0f);
13231 
13232 		return;
13233 	}
13234 
13235 	// now find the given subsystem on the ship.This could be a generic type like <All Engines>
13236 	generic_type = get_generic_subsys(subsystem);
13237 	ss_start = GET_FIRST(&shipp->subsys_list);
13238 
13239 	while (do_loop) {
13240 		if (generic_type) {
13241 			// loop until we find a subsystem of that type
13242 			for ( ; ss_start != END_OF_LIST(&shipp->subsys_list); ss_start = GET_NEXT(ss_start)) {
13243 				ss = nullptr;
13244 				if (generic_type == ss_start->system_info->type) {
13245 					ss = ss_start;
13246 					ss_start = GET_NEXT(ss_start);
13247 					break;
13248 				}
13249 			}
13250 
13251 			// reached the end of the subsystem list
13252 			if (ss_start == END_OF_LIST(&shipp->subsys_list)) {
13253 				do_loop = false;
13254 				// If the last subsystem wasn't of interest we don't need to go any further
13255 				if (ss == nullptr) {
13256 					continue;
13257 				}
13258 			}
13259 		}
13260 		else {
13261 			do_loop = false;
13262 			index = ship_get_subsys_index(shipp, subsystem);
13263 			if ( index == -1 ) {
13264 				nprintf(("Warning", "Couldn't find subsystem %s on ship %s for set subsystem strength\n", subsystem, shipp->ship_name));
13265 				continue;
13266 			}
13267 
13268 			// get the pointer to the subsystem.  Check it's current hits against it's max hits, and
13269 			// set the strength to the given percentage
13270 			ss = ship_get_indexed_subsys( shipp, index );
13271 			if (ss == nullptr) {
13272 				nprintf(("Warning", "Nonexistent subsystem for index %d on ship %s for set subsystem strength\n", index, shipp->ship_name));
13273 				continue;
13274 			}
13275 		}
13276 
13277 		set_subsys_strength_and_maybe_ancestors(shipp, ss, nullptr, &percentage, nullptr, nullptr, do_submodel_repair, do_ancestor_repair);
13278 	}
13279 
13280 	// recalculate when done
13281 	ship_recalc_subsys_strength(shipp);
13282 }
13283 
13284 // destroys a subsystem without explosions
sexp_destroy_subsys_instantly(int n)13285 void sexp_destroy_subsys_instantly(int n)
13286 {
13287 	const char *subsystem;
13288 	int	subsys_index, generic_type;
13289 	ship_subsys *ss;
13290 
13291 	auto ship_entry = eval_ship(n);
13292 	if (!ship_entry || !ship_entry->shipp)
13293 		return;
13294 	auto shipp = ship_entry->shipp;
13295 	n = CDR(n);
13296 
13297 	if (MULTIPLAYER_MASTER)
13298 	{
13299 		Current_sexp_network_packet.start_callback();
13300 		Current_sexp_network_packet.send_ship(shipp);
13301 	}
13302 
13303 	for ( ; n >= 0; n = CDR(n))
13304 	{
13305 		subsystem = CTEXT(n);
13306 
13307 		// use destroy-instantly if we want to do this
13308 		if (!stricmp(subsystem, SEXP_HULL_STRING) || !stricmp(subsystem, SEXP_SIM_HULL_STRING))
13309 			continue;
13310 
13311 		// deal with generic subsystems
13312 		generic_type = get_generic_subsys(subsystem);
13313 		if (generic_type != SUBSYSTEM_NONE)
13314 		{
13315 			for (ss = GET_FIRST(&shipp->subsys_list); ss != END_OF_LIST(&shipp->subsys_list); ss = GET_NEXT(ss))
13316 			{
13317 				if (generic_type == ss->system_info->type)
13318 				{
13319 					// do destruction stuff
13320 					ss->current_hits = 0;
13321 					do_subobj_destroyed_stuff(shipp, ss, nullptr, true);
13322 
13323 					if (MULTIPLAYER_MASTER)
13324 					{
13325 						subsys_index = ship_get_subsys_index(shipp, ss);
13326 						Assert(subsys_index >= 0);
13327 						Current_sexp_network_packet.send_int(subsys_index);
13328 					}
13329 				}
13330 			}
13331 		}
13332 		// normal subsystems
13333 		else
13334 		{
13335 			ss = ship_get_subsys(shipp, subsystem);
13336 			if (ss == nullptr)
13337 			{
13338 				nprintf(("Warning", "Nonexistent subsystem '%s' on ship %s for destroy-subsys-instantly\n", subsystem, shipp->ship_name));
13339 				continue;
13340 			}
13341 
13342 			// do destruction stuff
13343 			ss->current_hits = 0;
13344 			do_subobj_destroyed_stuff(shipp, ss, nullptr, true);
13345 
13346 			if (MULTIPLAYER_MASTER)
13347 			{
13348 				subsys_index = ship_get_subsys_index(shipp, ss);
13349 				Assert(subsys_index >= 0);
13350 				Current_sexp_network_packet.send_int(subsys_index);
13351 			}
13352 		}
13353 	}
13354 
13355 	// recalculate when done
13356 	ship_recalc_subsys_strength(shipp);
13357 
13358 	if (MULTIPLAYER_MASTER)
13359 		Current_sexp_network_packet.end_callback();
13360 }
13361 
multi_sexp_destroy_subsys_instantly()13362 void multi_sexp_destroy_subsys_instantly()
13363 {
13364 	ship *shipp;
13365 	int subsys_index;
13366 	ship_subsys *ss;
13367 
13368 	Current_sexp_network_packet.get_ship(shipp);
13369 
13370 	// destroy subsystems
13371 	while (Current_sexp_network_packet.get_int(subsys_index))
13372 	{
13373 		// find subsystem
13374 		Assert(subsys_index >= 0);
13375 		ss = ship_get_indexed_subsys(shipp, subsys_index);
13376 
13377 		// do destruction stuff
13378 		ss->current_hits = 0;
13379 		do_subobj_destroyed_stuff(shipp, ss, nullptr, true);
13380 	}
13381 
13382 	// recalculate when done
13383 	ship_recalc_subsys_strength(shipp);
13384 }
13385 
13386 /**
13387  * Changes the validity of a goal.
13388  *
13389  * The flag paramater tells us whether to mark the goals as valid or invalid
13390  */
sexp_change_goal_validity(int n,bool flag)13391 void sexp_change_goal_validity( int n, bool flag )
13392 {
13393 	while ( n != -1 ) {
13394 		auto name = CTEXT(n);
13395 		mission_goal_mark_valid( name, flag );
13396 
13397 		n = CDR(n);
13398 	}
13399 }
13400 
13401 // Goober5000
13402 // yeesh - be careful of the cargo-no-deplete flag :p
sexp_is_cargo(int n)13403 int sexp_is_cargo(int n)
13404 {
13405 	auto cargo = CTEXT(n);
13406 	n = CDR(n);
13407 
13408 	auto ship_entry = eval_ship(n);
13409 	if (!ship_entry)
13410 		return SEXP_NAN;
13411 	n = CDR(n);
13412 
13413 	auto subsystem = (n >= 0) ? CTEXT(n) : nullptr;
13414 
13415 	int cargo_index = -1;
13416 
13417 	// exited (on the exited_ships list)?
13418 	if (ship_entry->exited_index >= 0)
13419 	{
13420 		// can't check subsys of ships not in mission
13421 		if (subsystem)
13422 			return SEXP_NAN_FOREVER;
13423 
13424 		cargo_index = Ships_exited[ship_entry->exited_index].cargo1;
13425 	}
13426 	// not arrived yet?
13427 	else if (ship_entry->status == ShipStatus::NOT_YET_PRESENT)
13428 	{
13429 		// can't check subsys of ships not in mission
13430 		if (subsystem)
13431 			return SEXP_NAN;
13432 
13433 		cargo_index = ship_entry->p_objp->cargo1;
13434 	}
13435 	// in-mission?
13436 	else if (ship_entry->shipp)
13437 	{
13438 		if (subsystem)
13439 		{
13440 			// find the ship subsystem
13441 			ship_subsys *ss = ship_get_subsys(ship_entry->shipp, subsystem);
13442 			if (ss)
13443 			{
13444 				// set cargo
13445 				cargo_index = ss->subsys_cargo_name;
13446 			}
13447 		}
13448 		else
13449 		{
13450 			cargo_index = ship_entry->shipp->cargo1;
13451 		}
13452 	}
13453 	// probably vanished
13454 	else
13455 		return SEXP_NAN_FOREVER;
13456 
13457 	// did we get any cargo
13458 	if (cargo_index < 0)
13459 		return SEXP_FALSE;
13460 
13461 	// check cargo
13462 	if (!stricmp(Cargo_names[cargo_index & CARGO_INDEX_MASK], cargo))
13463 		return SEXP_TRUE;
13464 	else
13465 		return SEXP_FALSE;
13466 }
13467 
13468 // Goober5000
13469 // yeesh - be careful of the cargo-no-deplete flag :p
sexp_set_cargo(int n)13470 void sexp_set_cargo(int n)
13471 {
13472 	auto cargo = CTEXT(n);
13473 	n = CDR(n);
13474 
13475 	auto ship_entry = eval_ship(n);
13476 	if (!ship_entry)
13477 		return;
13478 	n = CDR(n);
13479 
13480 	auto subsystem = (n >= 0) ? CTEXT(n) : nullptr;
13481 
13482 	int cargo_index = -1;
13483 
13484 	// find this cargo in the cargo list
13485 	for (int i = 0; i < Num_cargo; ++i)
13486 	{
13487 		// found it?
13488 		if (!stricmp(cargo, Cargo_names[i]))
13489 		{
13490 			cargo_index = i;
13491 			break;
13492 		}
13493 	}
13494 
13495 	// not found
13496 	if (cargo_index == -1)
13497 	{
13498 		// make new entry if possible
13499 		if (Num_cargo + 1 >= MAX_CARGO)
13500 		{
13501 			Warning(LOCATION, "set-cargo: Maximum number of cargo names (%d) reached.  Ignoring new name.\n", MAX_CARGO);
13502 			return;
13503 		}
13504 
13505 		Assert(strlen(cargo) <= NAME_LENGTH - 1);
13506 
13507 		cargo_index = Num_cargo;
13508 		Num_cargo++;
13509 
13510 		strcpy(Cargo_names[cargo_index], cargo);
13511 	}
13512 
13513 	// exited (on the exited_ships list)?
13514 	if (ship_entry->exited_index >= 0)
13515 	{
13516 		return;
13517 	}
13518 	// not arrived yet?
13519 	else if (ship_entry->status == ShipStatus::NOT_YET_PRESENT)
13520 	{
13521 		// can't check subsys of ships not in mission
13522 		if (subsystem)
13523 			return;
13524 
13525 		ship_entry->p_objp->cargo1 = char(cargo_index | (ship_entry->p_objp->cargo1 & CARGO_NO_DEPLETE));
13526 	}
13527 	// in-mission?
13528 	else if (ship_entry->shipp)
13529 	{
13530 		if (subsystem)
13531 		{
13532 			ship_subsys *ss = ship_get_subsys(ship_entry->shipp, subsystem);
13533 			if (ss)
13534 			{
13535 				// set cargo
13536 				ss->subsys_cargo_name = cargo_index | (ss->subsys_cargo_name & CARGO_NO_DEPLETE);
13537 			}
13538 		}
13539 		else
13540 		{
13541 			// simply set the ship cargo
13542 			ship_entry->shipp->cargo1 = char(cargo_index | (ship_entry->shipp->cargo1 & CARGO_NO_DEPLETE));
13543 		}
13544 	}
13545 }
13546 
13547 /**
13548  * Transfer cargo from one ship to another
13549  */
sexp_transfer_cargo(int n)13550 void sexp_transfer_cargo(int n)
13551 {
13552 	// find the ships -- if neither in the mission, abort
13553 	auto ship1 = eval_ship(n);
13554 	if (!ship1 || !ship1->shipp)
13555 		return;
13556 	auto ship2 = eval_ship(CDR(n));
13557 	if (!ship2 || !ship2->shipp)
13558 		return;
13559 
13560 	// we must be sure that these two objects are indeed docked
13561 	if (!dock_check_find_direct_docked_object(ship1->objp, ship2->objp)) {
13562 		Warning(LOCATION, "Tried to transfer cargo between %s and %s although they aren't docked!", ship1->name, ship2->name);
13563 		return;
13564 	}
13565 
13566 	if ( !stricmp(Cargo_names[ship1->shipp->cargo1 & CARGO_INDEX_MASK], "nothing") ) {
13567 		return;
13568 	}
13569 
13570 	// transfer cargo from ship1 to ship2
13571 #ifndef NDEBUG
13572 	// Don't give warning for large ships (cruiser on up)
13573 	if (! (Ship_info[ship2->shipp->ship_info_index].is_big_or_huge()) ) {
13574 		if ( stricmp(Cargo_names[ship2->shipp->cargo1 & CARGO_INDEX_MASK], "nothing") != 0 ) {
13575 			Warning(LOCATION, "Transferring cargo to %s which already\nhas cargo %s.\nCargo will be replaced", ship2->name, Cargo_names[ship2->shipp->cargo1 & CARGO_INDEX_MASK] );
13576 		}
13577 	}
13578 #endif
13579 	ship2->shipp->cargo1 = char((ship1->shipp->cargo1 & CARGO_INDEX_MASK) | (ship2->shipp->cargo1 & CARGO_NO_DEPLETE));
13580 
13581 	if ( !(ship1->shipp->cargo1 & CARGO_NO_DEPLETE) ) {
13582 		int i = 0;
13583 
13584 		// need to set ship1's cargo to nothing.  scan the cargo_names array looking for the string nothing.
13585 		for (; i < Num_cargo; ++i) {
13586 			if ( !stricmp(Cargo_names[i], NOX("nothing")) ) {
13587 				ship1->shipp->cargo1 = char(i);
13588 				return;
13589 			}
13590 		}
13591 
13592 		// add it if not found
13593 		strcpy(Cargo_names[i], NOX("Nothing"));
13594 		Num_cargo++;
13595 	}
13596 }
13597 
13598 /**
13599  * Exchanges cargo between two ships
13600  */
sexp_exchange_cargo(int n)13601 void sexp_exchange_cargo(int n)
13602 {
13603 	// find the ships -- if neither in the mission, abort
13604 	auto ship1 = eval_ship(n);
13605 	if (!ship1 || !ship1->shipp)
13606 		return;
13607 	auto ship2 = eval_ship(CDR(n));
13608 	if (!ship2 || !ship2->shipp)
13609 		return;
13610 
13611 	// we must be sure that these two objects are indeed docked
13612 	if (!dock_check_find_direct_docked_object(ship1->objp, ship2->objp)) {
13613 		Warning(LOCATION, "Tried to exchange cargo between %s and %s although they aren't docked!", ship1->name, ship2->name);
13614 		return;
13615 	}
13616 
13617 	int temp = (ship1->shipp->cargo1 & CARGO_INDEX_MASK);
13618 	ship1->shipp->cargo1 = char(ship2->shipp->cargo1 & CARGO_INDEX_MASK);
13619 	ship2->shipp->cargo1 = char(temp);
13620 }
13621 
sexp_cap_waypoint_speed(int n)13622 void sexp_cap_waypoint_speed(int n)
13623 {
13624 	bool is_nan, is_nan_forever;
13625 
13626 	auto ship_entry = eval_ship(n);
13627 	if (!ship_entry || !ship_entry->shipp)
13628 		return;
13629 
13630 	int speed = eval_num(CDR(n), is_nan, is_nan_forever);
13631 	if (is_nan || is_nan_forever)
13632 		return;
13633 
13634 	// cap speed to range (-1, 32767) to store within int
13635 	if (speed < 0) {
13636 		speed = -1;
13637 	}
13638 
13639 	if (speed > 32767) {
13640 		speed = 32767;
13641 	}
13642 
13643 	Ai_info[ship_entry->shipp->ai_index].waypoint_speed_cap = speed;
13644 }
13645 
13646 /**
13647  * Causes a ship to jettison its cargo
13648  */
sexp_jettison_cargo(int n,bool jettison_new)13649 void sexp_jettison_cargo(int n, bool jettison_new)
13650 {
13651 	float jettison_speed;
13652 	bool is_nan, is_nan_forever;
13653 
13654 	auto parent = eval_ship(n);
13655 	if (!parent || !parent->shipp)
13656 		return;
13657 	n = CDR(n);
13658 
13659 	// in jettison-cargo-delay, this is the delay (which is unimplemented)
13660 	// in jettison-cargo, this is the jettison speed, which is optional
13661 	if (n >= 0)
13662 	{
13663 		jettison_speed = static_cast<float>(eval_num(n, is_nan, is_nan_forever));
13664 		n = CDR(n);
13665 
13666 		// it would be fun to break the physics engine, but let's not
13667 		if (is_nan || is_nan_forever)
13668 			return;
13669 	}
13670 	// per sexp help, if unspecified, default to 25
13671 	// (see also OP_JETTISON_CARGO_NEW in sexp_tree.cpp)
13672 	else
13673 		jettison_speed = 25.0f;
13674 
13675 	// no arguments - jettison all docked objects
13676 	if (n < 0)
13677 	{
13678 		// Goober5000 - as with ai_deathroll_start, we can't simply iterate through the dock list while we're
13679 		// undocking things.  So just repeatedly jettison the first object.
13680 		while (object_is_docked(parent->objp))
13681 		{
13682 			object_jettison_cargo(parent->objp, dock_get_first_docked_object(parent->objp), jettison_speed, jettison_new);
13683 		}
13684 	}
13685 	// arguments - jettison only those objects
13686 	else
13687 	{
13688 		for (; n != -1; n = CDR(n))
13689 		{
13690 			// make sure ship exists
13691 			auto child = eval_ship(n);
13692 			if (!child || !child->shipp)
13693 				continue;
13694 
13695 			// make sure we are docked to it
13696 			if (!dock_check_find_direct_docked_object(parent->objp, child->objp))
13697 				continue;
13698 
13699 			object_jettison_cargo(parent->objp, child->objp, jettison_speed, jettison_new);
13700 		}
13701 	}
13702 }
13703 
sexp_set_docked(int n)13704 void sexp_set_docked(int n)
13705 {
13706 	// get some data
13707 	auto docker = eval_ship(n);
13708 	if (!docker || !docker->shipp)
13709 		return;
13710 	n = CDR(n);
13711 
13712 	auto docker_point_name = CTEXT(n);
13713 	n = CDR(n);
13714 
13715 	auto dockee = eval_ship(n);
13716 	if (!dockee || !dockee->shipp)
13717 		return;
13718 	n = CDR(n);
13719 
13720 	auto dockee_point_name = CTEXT(n);
13721 
13722 	//Get dockpoints by name
13723 	int docker_point_index = model_find_dock_name_index(Ship_info[docker->shipp->ship_info_index].model_num, docker_point_name);
13724 	int dockee_point_index = model_find_dock_name_index(Ship_info[dockee->shipp->ship_info_index].model_num, dockee_point_name);
13725 
13726 	Assertion(docker_point_index >= 0, "Docker point '%s' not found on docker ship '%s'", docker_point_name, docker->name);
13727 	Assertion(dockee_point_index >= 0, "Dockee point '%s' not found on dockee ship '%s'", dockee_point_name, dockee->name);
13728 
13729 	//Make sure that the specified dockpoints are all free (if not, do nothing)
13730 	if (dock_find_object_at_dockpoint(docker->objp, docker_point_index) != nullptr ||
13731 		dock_find_object_at_dockpoint(dockee->objp, dockee_point_index) != nullptr)
13732 	{
13733 		return;
13734 	}
13735 
13736 	//Set docked
13737 	dock_orient_and_approach(docker->objp, docker_point_index, dockee->objp, dockee_point_index, DOA_DOCK_STAY);
13738 	ai_do_objects_docked_stuff(docker->objp, docker_point_index, dockee->objp, dockee_point_index, true);
13739 }
13740 
sexp_cargo_no_deplete(int n)13741 void sexp_cargo_no_deplete(int n)
13742 {
13743 	int no_deplete = 1;
13744 	bool is_nan, is_nan_forever;
13745 
13746 	// get some data
13747 	auto ship_entry = eval_ship(n);
13748 	if (!ship_entry || !ship_entry->shipp)
13749 		return;
13750 	n = CDR(n);
13751 
13752 	if ( !(Ship_info[ship_entry->shipp->ship_info_index].is_big_or_huge()) ) {
13753 		Warning(LOCATION, "Trying to make non BIG or HUGE ship %s with non-depletable cargo.\n", ship_entry->name);
13754 		return;
13755 	}
13756 
13757 	if (n != -1) {
13758 		no_deplete = eval_num(n, is_nan, is_nan_forever);
13759 		Assert((no_deplete == 0) || (no_deplete == 1));
13760 		if (is_nan || is_nan_forever) {
13761 			no_deplete = 0;
13762 		}
13763 		else if ( (no_deplete != 0) && (no_deplete != 1) ) {
13764 			no_deplete = 1;
13765 		}
13766 	}
13767 
13768 	if (no_deplete) {
13769 		ship_entry->shipp->cargo1 |= CARGO_NO_DEPLETE;
13770 	} else {
13771 		ship_entry->shipp->cargo1 &= (~CARGO_NO_DEPLETE);
13772 	}
13773 }
13774 
13775 // Goober5000
sexp_force_jump()13776 void sexp_force_jump()
13777 {
13778 	// Shouldn't be gliding now....
13779 	Player_obj->phys_info.flags &= ~PF_GLIDING;
13780 	Player_obj->phys_info.flags &= ~PF_FORCE_GLIDE;
13781 
13782 	if (Game_mode & GM_MULTIPLAYER) {
13783 		multi_handle_end_mission_request();
13784 	}
13785 	else {
13786 		// forced warp, taken from training failure code
13787 		gameseq_post_event( GS_EVENT_PLAYER_WARPOUT_START_FORCED );	//	Force player to warp out.
13788 	}
13789 
13790 }
13791 
sexp_mission_set_nebula(int n)13792 void sexp_mission_set_nebula(int n)
13793 {
13794 	bool is_nan, is_nan_forever;
13795 	int set_it, range;
13796 
13797 	// range is optional, so if it isn't found, it will be set to 0,
13798 	// which will in turn be set to a default in the next function
13799 	// (this means the sexp cannot set the nebula range to 0,
13800 	// but the same is true in the background editor)
13801 	eval_nums(n, is_nan, is_nan_forever, set_it, range);
13802 	if (is_nan || is_nan_forever)
13803 		return;
13804 
13805 	stars_set_nebula(set_it > 0, static_cast<float>(range));
13806 }
13807 
13808 /* freespace.cpp does not have these availiable externally, and we must call
13809 them so that the main simulation loop does not have to constantly check for
13810 Game_subspace_effect so that it could turn on the subspace sounds.
13811 
13812 Because these are in freespace.cpp there are also stubs of these functions
13813 in fred.cpp as it does not deal with the game loop (obviously) but still
13814 links against code.lib. */
13815 extern void game_start_subspace_ambient_sound();
13816 extern void game_stop_subspace_ambient_sound();
13817 
sexp_mission_set_subspace(int n)13818 void sexp_mission_set_subspace(int n)
13819 {
13820 	bool is_nan, is_nan_forever;
13821 	int set_it = eval_num(n, is_nan, is_nan_forever);
13822 	if (is_nan || is_nan_forever)
13823 		return;
13824 
13825 	if (set_it > 0) {
13826 		Game_subspace_effect = 1;
13827 		game_start_subspace_ambient_sound();
13828 	} else {
13829 		Game_subspace_effect = 0;
13830 		game_stop_subspace_ambient_sound();
13831 	}
13832 
13833 	stars_set_dynamic_environment(Game_subspace_effect != 0);
13834 	The_mission.flags.set(Mission::Mission_Flags::Subspace, Game_subspace_effect != 0);
13835 }
13836 
sexp_change_background(int node)13837 void sexp_change_background(int node)
13838 {
13839 	bool is_nan, is_nan_forever;
13840 	int background_idx = eval_num(node, is_nan, is_nan_forever);
13841 	if (is_nan || is_nan_forever)
13842 		return;
13843 
13844 	// human/computer offset
13845 	background_idx--;
13846 
13847 	// range check
13848 	if (background_idx < 0 || background_idx >= (int)Backgrounds.size())
13849 		return;
13850 
13851 	stars_load_background(background_idx);
13852 }
13853 
sexp_add_background_bitmap(int n,bool is_sun)13854 void sexp_add_background_bitmap(int n, bool is_sun)
13855 {
13856 	int sexp_var, new_number, sanity;
13857 	bool is_nan, is_nan_forever;
13858 	char number_as_str[TOKEN_LENGTH];
13859 	starfield_list_entry sle;
13860 
13861 	// filename
13862 	strcpy_s(sle.filename, CTEXT(n));
13863 	n = CDR(n);
13864 
13865 	// sanity checking
13866 	sanity = is_sun ? stars_find_sun(sle.filename) : stars_find_bitmap(sle.filename);
13867 	if (sanity < 0)
13868 	{
13869 		Warning(LOCATION, "sexp-add-%s-bitmap: '%s' not found!", is_sun ? "sun" : "background", sle.filename);
13870 		return;
13871 	}
13872 
13873 	// angles
13874 	eval_angles(&sle.ang, n, is_nan, is_nan_forever);
13875 	if (is_nan || is_nan_forever)
13876 		return;
13877 
13878 	if (is_sun)
13879 	{
13880 		int num = eval_num(n, is_nan, is_nan_forever);
13881 		n = CDR(n);
13882 		if (is_nan || is_nan_forever)
13883 			return;
13884 
13885 		// scale
13886 		sle.scale_x = num / 100.0f;
13887 		sle.scale_y = sle.scale_x;
13888 
13889 		// div
13890 		sle.div_x = 1;
13891 		sle.div_y = 1;
13892 
13893 		// restrict parameters
13894 		if (sle.scale_x > 50) sle.scale_x = 50;
13895 		if (sle.scale_x < 0.1f) sle.scale_x = 0.1f;
13896 		if (sle.scale_y > 50) sle.scale_y = 50;
13897 		if (sle.scale_y < 0.1f) sle.scale_y = 0.1f;
13898 	}
13899 	else
13900 	{
13901 		// next 4
13902 		std::array<int, 4> numbers;
13903 		eval_array(numbers, n, is_nan, is_nan_forever);
13904 		if (is_nan || is_nan_forever)
13905 			return;
13906 
13907 		// scale
13908 		sle.scale_x = numbers[0] / 100.0f;
13909 		sle.scale_y = numbers[1] / 100.0f;
13910 
13911 		// div
13912 		sle.div_x = numbers[2];
13913 		sle.div_y = numbers[3];
13914 
13915 		// restrict parameters
13916 		if (sle.scale_x > 18) sle.scale_x = 18;
13917 		if (sle.scale_x < 0.1f) sle.scale_x = 0.1f;
13918 		if (sle.scale_y > 18) sle.scale_y = 18;
13919 		if (sle.scale_y < 0.1f) sle.scale_y = 0.1f;
13920 		if (sle.div_x > 5) sle.div_x = 5;
13921 		if (sle.div_x < 1) sle.div_x = 1;
13922 		if (sle.div_y > 5) sle.div_y = 5;
13923 		if (sle.div_y < 1) sle.div_y = 1;
13924 	}
13925 
13926 	if (n == -1) {
13927 		if (is_sun) {
13928 			stars_add_sun_entry(&sle);
13929 		} else {
13930 			stars_add_bitmap_entry(&sle);
13931 		}
13932 	} else {
13933 		Assert((n >= 0) && (n < Num_sexp_nodes));
13934 
13935 		// ripped from sexp_modify_variable()
13936 		sexp_var = sexp_get_variable_index(n);
13937 
13938 		if (Sexp_variables[sexp_var].type & SEXP_VARIABLE_NUMBER)
13939 		{
13940 			// get new numerical value
13941 			new_number = is_sun ? stars_add_sun_entry(&sle) : stars_add_bitmap_entry(&sle);
13942 			if (new_number < 0)
13943 			{
13944 				Warning(LOCATION, "Unable to add %s: '%s'!", is_sun ? "sun" : "starfield bitmap", sle.filename);
13945 				new_number = 0;
13946 			}
13947 
13948 			sprintf(number_as_str, "%d", new_number);
13949 
13950 			// assign to variable
13951 			sexp_modify_variable(number_as_str, sexp_var);
13952 		}
13953 		else
13954 		{
13955 			Warning(LOCATION, "sexp-add-%s-bitmap: Variable %s must be a number variable!", is_sun ? "sun" : "background", Sexp_variables[sexp_var].variable_name);
13956 			return;
13957 		}
13958 	}
13959 }
13960 
sexp_remove_background_bitmap(int n,bool is_sun)13961 void sexp_remove_background_bitmap(int n, bool is_sun)
13962 {
13963 	bool is_nan, is_nan_forever;
13964 	int slot = eval_num(n, is_nan, is_nan_forever);
13965 
13966 	if (slot >= 0 && !is_nan && !is_nan_forever) {
13967 		int instances = is_sun ? stars_get_num_suns() : stars_get_num_bitmaps();
13968 		if (instances > slot) {
13969 			if (is_sun) {
13970 				stars_mark_sun_unused(slot);
13971 			} else {
13972 				stars_mark_bitmap_unused(slot);
13973 			}
13974 		} else {
13975 			Warning(LOCATION, "remove-%s-bitmap: slot %d does not exist. Slot must be less than %d.", is_sun ? "sun" : "background", slot, instances);
13976 		}
13977 	}
13978 }
13979 
sexp_nebula_change_storm(int n)13980 void sexp_nebula_change_storm(int n)
13981 {
13982 	if (!(The_mission.flags[Mission::Mission_Flags::Fullneb])) return;
13983 
13984 	nebl_set_storm(CTEXT(n));
13985 }
13986 
sexp_nebula_toggle_poof(int n)13987 void sexp_nebula_toggle_poof(int n)
13988 {
13989 	auto name = CTEXT(n);
13990 	bool result = is_sexp_true(CDR(n));
13991 	int i;
13992 
13993 	for (i = 0; i < (int)Poof_info.size(); i++)
13994 	{
13995 		if (!stricmp(name, Poof_info[i].name))
13996 			break;
13997 	}
13998 
13999 	//coulnd't find the poof
14000 	if (i == (int)Poof_info.size()) return;
14001 
14002 	neb2_toggle_poof(i, result);
14003 }
14004 
sexp_nebula_change_pattern(int n)14005 void sexp_nebula_change_pattern(int n)
14006 {
14007 	if (!(The_mission.flags[Mission::Mission_Flags::Fullneb])) return;
14008 
14009 	strcpy_s(Neb2_texture_name,(CTEXT(n)));
14010 
14011 	neb2_post_level_init();
14012 }
14013 
sexp_nebula_change_fog_color(int node)14014 void sexp_nebula_change_fog_color(int node)
14015 {
14016 	if (!(The_mission.flags[Mission::Mission_Flags::Fullneb])) return;
14017 
14018 	int red, green, blue = 0;
14019 	bool is_nan, is_nan_forever;
14020 
14021 	Assert(node >= 0);
14022 
14023 	eval_nums(node, is_nan, is_nan_forever, red, green, blue);
14024 	if (is_nan || is_nan_forever)
14025 		return;
14026 
14027 	CLAMP(red, 0, 255);
14028 	CLAMP(green, 0, 255);
14029 	CLAMP(blue, 0, 255);
14030 
14031 	Neb2_fog_color[0] = (ubyte)red;
14032 	Neb2_fog_color[1] = (ubyte)green;
14033 	Neb2_fog_color[2] = (ubyte)blue;
14034 }
14035 
14036 /**
14037  * End the mission.
14038  *
14039  * Implemented by Sesquipedalian; fixed by EdrickV; enhanced by others
14040  */
sexp_end_mission(int n)14041 void sexp_end_mission(int n)
14042 {
14043 	bool ignore_player_mortality = true;
14044 	bool boot_to_main_hall = false;
14045 	bool from_debrief_to_main_hall = false;
14046 
14047 	if (n != -1) {
14048 		ignore_player_mortality = is_sexp_true(n);
14049 		n = CDR(n);
14050 	}
14051 	if (n != -1) {
14052 		boot_to_main_hall = is_sexp_true(n);
14053 		n = CDR(n);
14054 	}
14055 	if (n != -1) {
14056 		from_debrief_to_main_hall = is_sexp_true(n);
14057 		n = CDR(n);
14058 	}
14059 
14060 	// if the player is dead we may want to let the death screen handle things
14061 	if (!ignore_player_mortality && (Player_ship->flags[Ship::Ship_Flags::Dying])) {
14062 		return;
14063 	}
14064 
14065 	// ending via debrief and then going to mainhall could maybe work with multiplayer?
14066 	The_mission.flags.set(Mission::Mission_Flags::End_to_mainhall, from_debrief_to_main_hall != 0);
14067 
14068 	// if we go straight to the main hall we have to clean up the mission without entering the debriefing
14069 	if (boot_to_main_hall && !(Game_mode & GM_MULTIPLAYER)) {
14070 		gameseq_post_event(GS_EVENT_END_GAME);
14071 	} else {
14072 		send_debrief_event();
14073 	}
14074 
14075 	// Karajorma - callback all the clients here.
14076 	if (MULTIPLAYER_MASTER)
14077 	{
14078 		multi_handle_sudden_mission_end();
14079 		send_force_end_mission_packet();
14080 	}
14081 }
14082 
14083 // Goober5000
sexp_set_debriefing_toggled(int node)14084 void sexp_set_debriefing_toggled(int node)
14085 {
14086 	The_mission.flags.set(Mission::Mission_Flags::Toggle_debriefing, is_sexp_true(node));
14087 }
14088 
14089 // Goober5000
sexp_set_debriefing_persona(int node)14090 void sexp_set_debriefing_persona(int node)
14091 {
14092 	bool is_nan, is_nan_forever;
14093 	int persona = eval_num(node, is_nan, is_nan_forever);
14094 	if (is_nan || is_nan_forever || persona < 0)
14095 		return;
14096 	The_mission.debriefing_persona = persona;
14097 }
14098 
14099 /**
14100  * Toggle the status bit for the AI code which tells the AI if it is a good time to rearm.
14101  *
14102  * The status being set means good time.  Status not being set (unset), means bad time. Designers must implement this.
14103  */
sexp_good_time_to_rearm(int n)14104 void sexp_good_time_to_rearm(int n)
14105 {
14106 	int team, time;
14107 	bool is_nan, is_nan_forever;
14108 
14109 	team = iff_lookup(CTEXT(n));
14110 	time = eval_num(CDR(n), is_nan, is_nan_forever);			// this is the time for how long a good rearm is active -- in seconds
14111 
14112 	if (is_nan || is_nan_forever)
14113 		return;
14114 
14115 	ai_set_rearm_status(team, time);
14116 }
14117 
14118 /**
14119  * Grants promotion to the player
14120  */
sexp_grant_promotion()14121 void sexp_grant_promotion()
14122 {
14123 	// short circuit multiplayer for now until we figure out what to do.
14124 	if ( Game_mode & GM_MULTIPLAYER )
14125 		return;
14126 
14127 	// set a bit to tell player should get promoted at the end of the mission.  I suppose the other
14128 	// thing that we could do would be to set the players score to at least the amount of
14129 	// points for the next level, but this way is better I think.
14130 	if ( Game_mode & GM_CAMPAIGN_MODE ) {
14131 		Player->flags |= PLAYER_FLAGS_PROMOTED;
14132 	}
14133 }
14134 
14135 /**
14136  * Gives the named medal to the players in the mission
14137  */
sexp_grant_medal(int n)14138 void sexp_grant_medal(int n)
14139 {
14140 	int i;
14141 
14142 	// don't give medals in normal gameplay when not in campaign mode
14143 	if ( (Game_mode & GM_NORMAL) && !(Game_mode & GM_CAMPAIGN_MODE) )
14144 		return;
14145 
14146 	auto medal_name = CTEXT(n);
14147 
14148 	if (Player->stats.m_medal_earned >= 0) {
14149 		Warning(LOCATION, "Cannot grant more than one medal per mission!  New medal '%s' will replace old medal '%s'!", medal_name, Medals[Player->stats.m_medal_earned].name);
14150 	}
14151 
14152 	for (i = 0; i < Num_medals; i++ ) {
14153 		if ( !stricmp(medal_name, Medals[i].name) )
14154 			break;
14155 	}
14156 
14157 	if ( i < Num_medals ) {
14158 		Player->stats.m_medal_earned = i;
14159 
14160 		if ( Game_mode & GM_MULTIPLAYER ) {
14161 			for ( int j = 0; j < MAX_PLAYERS; j++ ) {
14162 				if ( MULTI_CONNECTED(Net_players[j]) ) {
14163 					Net_players[j].m_player->stats.m_medal_earned = i;
14164 				}
14165 			}
14166 		}
14167 	}
14168 }
14169 
sexp_change_player_score(int node)14170 void sexp_change_player_score(int node)
14171 {
14172 	int score;
14173 	bool is_nan, is_nan_forever;
14174 
14175 	score = eval_num(node, is_nan, is_nan_forever);
14176 	node = CDR(node);
14177 
14178 	if (is_nan || is_nan_forever)
14179 		return;
14180 
14181 	while (node >= 0) {
14182 		auto player = get_player_from_ship_node(node, true);
14183 		if (player) {
14184 			player->stats.m_score += score;
14185 
14186 			if (player->stats.m_score < 0) {
14187 				player->stats.m_score = 0;
14188 			}
14189 		} else {
14190 			if (!(Game_mode & GM_MULTIPLAYER)) {
14191 				Warning(LOCATION, "Can not award points to '%s'. Ship is not a player!", CTEXT(node));
14192 			}
14193 		}
14194 
14195 		node = CDR(node);
14196 	}
14197 }
14198 
sexp_change_team_score(int node)14199 void sexp_change_team_score(int node)
14200 {
14201 	int i, score, team;
14202 	bool is_nan, is_nan_forever;
14203 
14204 	// since we only have a team score in TvT
14205 	if ( !(MULTI_TEAM) ) {
14206 		return;
14207 	}
14208 
14209 	eval_nums(node, is_nan, is_nan_forever, score, team);
14210 	if (is_nan || is_nan_forever)
14211 		return;
14212 
14213 	if (team == 0) {
14214 		for (i = 0; i < MAX_TVT_TEAMS; i++) {
14215 			Multi_team_score[i] += score;
14216 		}
14217 	}
14218 	else if (team > 0 && team <= MAX_TVT_TEAMS) {
14219 		Multi_team_score[team - 1] += score;
14220 	}
14221 	else {
14222 		Warning(LOCATION, "Invalid team number. Team %d does not exist", team);
14223 	}
14224 }
14225 
sexp_red_alert()14226 void sexp_red_alert()
14227 {
14228 	// in the case of a red_alert mission, simply call the red alert function to close
14229 	// the current campaign's mission and move forward to the next mission
14230 	red_alert_start_mission();
14231 
14232 	Current_sexp_network_packet.do_callback();
14233 }
14234 
multi_sexp_red_alert()14235 void multi_sexp_red_alert()
14236 {
14237 	red_alert_start_mission();
14238 }
14239 
sexp_tech_add_ship(int node)14240 void sexp_tech_add_ship(int node)
14241 {
14242 	// this function doesn't mean anything when not in campaign mode
14243 	if ( !(Game_mode & GM_CAMPAIGN_MODE) )
14244 		return;
14245 
14246 	while (node >= 0) {
14247 		auto name = CTEXT(node);
14248 		int i = ship_info_lookup(name);
14249 		if (i >= 0)
14250 		{
14251 			if (Player && (Player->flags & PLAYER_FLAGS_IS_MULTI))
14252 				Ship_info[i].flags.set(Ship::Info_Flags::In_tech_database_m);
14253 			else
14254 				Ship_info[i].flags.set(Ship::Info_Flags::In_tech_database);
14255 		}
14256 		else
14257 			Warning(LOCATION, "In tech-add-ship, ship class \"%s\" invalid", name);
14258 
14259 		node = CDR(node);
14260 	}
14261 }
14262 
sexp_tech_add_weapon(int node)14263 void sexp_tech_add_weapon(int node)
14264 {
14265 	// this function doesn't mean anything when not in campaign mode
14266 	if ( !(Game_mode & GM_CAMPAIGN_MODE) )
14267 		return;
14268 
14269 	while (node >= 0) {
14270 		auto name = CTEXT(node);
14271 		int i = weapon_info_lookup(name);
14272 		if (i >= 0)
14273 			Weapon_info[i].wi_flags.set(Weapon::Info_Flags::In_tech_database);
14274 		else
14275 			Warning(LOCATION, "In tech-add-weapon, weapon class \"%s\" invalid", name);
14276 
14277 		node = CDR(node);
14278 	}
14279 }
14280 
14281 // Goober5000
14282 // expanded with remove parameter by wookieejedi
sexp_tech_toggle_intel(int node,bool add,bool xstr)14283 void sexp_tech_toggle_intel(int node, bool add, bool xstr)
14284 {
14285 	int id, n = node;
14286 
14287 	// this function doesn't mean anything when not in campaign mode
14288 	if ( !(Game_mode & GM_CAMPAIGN_MODE) )
14289 		return;
14290 
14291 	while (n >= 0)
14292 	{
14293 		// don't use things like CTEXT or eval_num, since we didn't in the preloader
14294 		auto name = Sexp_nodes[n].text;
14295 		n = CDR(n);
14296 
14297 		// the xstr variant must have an id
14298 		if (xstr)
14299 		{
14300 			if (n < 0)
14301 				break;
14302 			id = atoi(Sexp_nodes[n].text);
14303 			n = CDR(n);
14304 		}
14305 		else
14306 			id = -1;
14307 
14308 		// if we have an xstr, we already translated this node in the preloader, so just look it up
14309 		int i = intel_info_lookup(name);
14310 		if (i >= 0) {
14311 			if (add) {
14312 				Intel_info[i].flags |= IIF_IN_TECH_DATABASE;
14313 			} else {
14314 				Intel_info[i].flags &= ~ IIF_IN_TECH_DATABASE;
14315 			}
14316 		}
14317 		else if (xstr)
14318 			Warning(LOCATION, "In tech-%s-intel-xstr, entry XSTR(\"%s\", %d) invalid", (add ? "add" : "remove"), name, id);
14319 		else
14320 			Warning(LOCATION, "In tech-%s-intel, entry \"%s\" invalid", (add ? "add" : "remove"), name);
14321 	}
14322 }
14323 
14324 // Goober5000 - reset all the tech entries to their default states
sexp_tech_reset_to_default()14325 void sexp_tech_reset_to_default()
14326 {
14327 	// this function doesn't mean anything when not in campaign mode
14328 	if ( !(Game_mode & GM_CAMPAIGN_MODE) )
14329 		return;
14330 
14331 	tech_reset_to_default();
14332 }
14333 
14334 /**
14335  * Set variables needed to grant a new ship/weapon to the player during the course
14336  * of a mission
14337  */
sexp_allow_ship(int n)14338 void sexp_allow_ship(int n)
14339 {
14340 	// this function doesn't mean anything when not in campaign mode
14341 	if ( !(Game_mode & GM_CAMPAIGN_MODE) )
14342 		return;
14343 
14344 	// get the name of the ship and lookup up the ship_info index for it
14345 	auto name = CTEXT(n);
14346 	int sindex = ship_info_lookup( name );
14347 	if ( sindex == -1 )
14348 		return;
14349 
14350 	// now we have a valid index --
14351 	mission_campaign_save_persistent( CAMPAIGN_PERSISTENT_SHIP, sindex );
14352 }
14353 
sexp_allow_weapon(int n)14354 void sexp_allow_weapon(int n)
14355 {
14356 	// this function doesn't mean anything when not in campaign mode
14357 	if ( !(Game_mode & GM_CAMPAIGN_MODE) )
14358 		return;
14359 
14360 	// get the name of the weapon and lookup up the weapon_info index for it
14361 	auto name = CTEXT(n);
14362 	int sindex = weapon_info_lookup( name );
14363 	if ( sindex == -1 )
14364 		return;
14365 
14366 	// now we have a valid index --
14367 	mission_campaign_save_persistent( CAMPAIGN_PERSISTENT_WEAPON, sindex );
14368 }
14369 
14370 /**
14371  * generic function for all those sexps that set flags
14372  * For all flag type parameters: If a particular flag should not be set, use the ::NUM_VALUES member of that enum
14373  *
14374  * @note this function has a similar purpose to sexp_alter_ship_flag_helper; make sure you check/update both
14375  */
sexp_deal_with_ship_flag(int node,bool process_subsequent_nodes,Object::Object_Flags object_flag,Ship::Ship_Flags ship_flag,Mission::Parse_Object_Flags p_object_flag,bool set_it,bool send_multiplayer=false)14376 void sexp_deal_with_ship_flag(int node, bool process_subsequent_nodes, Object::Object_Flags object_flag, Ship::Ship_Flags ship_flag, Mission::Parse_Object_Flags p_object_flag, bool set_it, bool send_multiplayer = false)
14377 {
14378 	int n = node;
14379 
14380 	if (send_multiplayer && MULTIPLAYER_MASTER) {
14381 		Current_sexp_network_packet.start_callback();
14382 		Current_sexp_network_packet.send_int((int)object_flag);
14383 		Current_sexp_network_packet.send_int((int)ship_flag);
14384 		Current_sexp_network_packet.send_int((int)p_object_flag);
14385 		Current_sexp_network_packet.send_bool(set_it);
14386 	}
14387 
14388 	// loop for all ships in the sexp
14389 	// NB: if the flag is set, we will continue acting on nodes until we run out of them;
14390 	//     if not, we will only act on the first one
14391 	for (; n >= 0; process_subsequent_nodes ? n = CDR(n) : n = -1)
14392 	{
14393 		// get ship
14394 		auto ship_entry = eval_ship(n);
14395 		if (!ship_entry)
14396 			continue;
14397 
14398 		// check to see if ship destroyed or departed.  In either case, do nothing.
14399 		if (ship_entry->status == ShipStatus::EXITED)
14400 			continue;
14401 
14402 		// if ship is in-mission
14403 		if (ship_entry->status == ShipStatus::PRESENT)
14404 		{
14405 			// save flags for state change comparisons
14406 			auto object_flag_orig = ship_entry->objp->flags;
14407 
14408 			// see if we have an object flag to set
14409 			if (object_flag != Object::Object_Flags::NUM_VALUES)
14410 			{
14411 				// set or clear?
14412 				ship_entry->objp->flags.set((Object::Object_Flags)object_flag, set_it);
14413 			}
14414 
14415 			// handle ETS when modifying shields
14416 			if (object_flag == Object::Object_Flags::No_shields) {
14417 				if (set_it) {
14418 					zero_one_ets(&ship_entry->shipp->shield_recharge_index, &ship_entry->shipp->weapon_recharge_index, &ship_entry->shipp->engine_recharge_index);
14419 				} else if (object_flag_orig[Object::Object_Flags::No_shields]) {
14420 					set_default_recharge_rates(ship_entry->objp);
14421 				}
14422 			}
14423 
14424 			// see if we have a ship flag to set
14425 			if (ship_flag != Ship::Ship_Flags::NUM_VALUES)
14426 			{
14427 				// set or clear?
14428 				ship_entry->shipp->flags.set((Ship::Ship_Flags)ship_flag, set_it);
14429 			}
14430 
14431 			// the lock afterburner SEXP also needs to set a physics flag
14432 			if (ship_flag == Ship::Ship_Flags::Afterburner_locked) {
14433 				if (set_it) {
14434 					afterburners_stop(ship_entry->objp, 1);
14435 				}
14436 			}
14437 
14438 			if (send_multiplayer && MULTIPLAYER_MASTER) {
14439 				Current_sexp_network_packet.send_bool(true);
14440 				Current_sexp_network_packet.send_ship(ship_entry->shipp);
14441 			}
14442 		}
14443 		// if it's not in-mission
14444 		else
14445 		{
14446 			// see if we have a p_object flag to set
14447 			if (p_object_flag != Mission::Parse_Object_Flags::NUM_VALUES)
14448 			{
14449 				// set or clear?
14450 				ship_entry->p_objp->flags.set((Mission::Parse_Object_Flags)p_object_flag, set_it);
14451 			}
14452 
14453 			if (send_multiplayer && MULTIPLAYER_MASTER) {
14454 				Current_sexp_network_packet.send_bool(false);
14455 				Current_sexp_network_packet.send_parse_object(ship_entry->p_objp);
14456 			}
14457 		}
14458 	}
14459 
14460 	if (send_multiplayer && MULTIPLAYER_MASTER) {
14461 		 Current_sexp_network_packet.end_callback();
14462 	}
14463 }
14464 
multi_sexp_deal_with_ship_flag()14465 void multi_sexp_deal_with_ship_flag()
14466 {
14467 	int object_flag = (int)Object::Object_Flags::NUM_VALUES;
14468 	int ship_flag = (int)Ship::Ship_Flags::NUM_VALUES;
14469 	int p_object_flag = (int)Mission::Parse_Object_Flags::NUM_VALUES;
14470 	bool set_it = false;
14471 	bool ship_arrived = true;
14472 	ship *shipp = nullptr;
14473 	p_object *pobjp = nullptr;
14474 
14475 	Current_sexp_network_packet.get_int(object_flag);
14476 	Current_sexp_network_packet.get_int(ship_flag);
14477 	Current_sexp_network_packet.get_int(p_object_flag);
14478 	Current_sexp_network_packet.get_bool(set_it);
14479 
14480 	// if any of the above failed so will this loop
14481 	while (Current_sexp_network_packet.get_bool(ship_arrived))
14482 	{
14483 		if (ship_arrived) {
14484 			Current_sexp_network_packet.get_ship(shipp);
14485 			if (shipp == nullptr) {
14486 				WarningEx(LOCATION, "Null ship pointer in multi_sexp_deal_with_ship_flag(), tell a coder.\n");
14487 				return;
14488 			}
14489 
14490 			// save flags for state change comparisons
14491 			auto object_flag_orig = Objects[shipp->objnum].flags;
14492 
14493 			if (object_flag != (int)Object::Object_Flags::NUM_VALUES) {
14494 				Objects[shipp->objnum].flags.set((Object::Object_Flags)object_flag, set_it);
14495 			}
14496 			if (ship_flag != (int)Ship::Ship_Flags::NUM_VALUES) {
14497 				shipp->flags.set((Ship::Ship_Flags)ship_flag, set_it);
14498 			}
14499 
14500 			// deal with side effects of these flags
14501 			if (object_flag == (int)Object::Object_Flags::No_shields) {
14502 				if (set_it) {
14503 					zero_one_ets(&shipp->shield_recharge_index, &shipp->weapon_recharge_index, &shipp->engine_recharge_index);
14504 				} else if (object_flag_orig[Object::Object_Flags::No_shields]) {
14505 					set_default_recharge_rates(&Objects[shipp->objnum]);
14506 				}
14507 			}
14508 
14509 			if (ship_flag == (int)Ship::Ship_Flags::Afterburner_locked) {
14510 				if (set_it) {
14511 					afterburners_stop(&Objects[shipp->objnum], 1);
14512 				}
14513 			}
14514 
14515 			if ((ship_flag == (int)Ship::Ship_Flags::Stealth) && !set_it) {
14516 				if (shipp->flags[Ship::Ship_Flags::Escort]) {
14517 					hud_add_ship_to_escort(shipp->objnum, 1);
14518 				}
14519 			}
14520 			if ((ship_flag == (int)Ship::Ship_Flags::Friendly_stealth_invis) && !set_it && (shipp->flags[Ship::Ship_Flags::Stealth]) && (shipp->team == Player_ship->team)) {
14521 				if (shipp->flags[Ship::Ship_Flags::Escort]) {
14522 					hud_add_ship_to_escort(shipp->objnum, 1);
14523 				}
14524 			}
14525 			if (ship_flag == (int)Ship::Ship_Flags::Hidden_from_sensors) {
14526 				if (set_it) {
14527 					if (Player_ai->target_objnum == shipp->objnum) {
14528 						hud_cease_targeting();
14529 					}
14530 				}
14531 				else {
14532 					if (shipp->flags[Ship::Ship_Flags::Escort]) {
14533 						hud_add_ship_to_escort(shipp->objnum, 1);
14534 					}
14535 				}
14536 			}
14537 		}
14538 		else {
14539 			Current_sexp_network_packet.get_parse_object(pobjp);
14540 			if ((pobjp != nullptr) && (p_object_flag != (int)Mission::Parse_Object_Flags::NUM_VALUES)) {
14541 				pobjp->flags.set((Mission::Parse_Object_Flags)p_object_flag, set_it);
14542 			}
14543 		}
14544 	}
14545 }
14546 
14547 /**
14548  * sets flags on objects from alter-ship-flag
14549  *
14550  * @note this function has a similar purpose to sexp_deal_with_ship_flag; make sure you check/update both
14551  */
sexp_alter_ship_flag_helper(object_ship_wing_point_team & oswpt,bool future_ships,Object::Object_Flags object_flag,Ship::Ship_Flags ship_flag,Mission::Parse_Object_Flags parse_obj_flag,AI::AI_Flags ai_flag,bool set_flag)14552 void sexp_alter_ship_flag_helper(object_ship_wing_point_team &oswpt, bool future_ships, Object::Object_Flags object_flag, Ship::Ship_Flags ship_flag, Mission::Parse_Object_Flags parse_obj_flag, AI::AI_Flags ai_flag, bool set_flag)
14553 {
14554 	int i;
14555 	flagset<Object::Object_Flags> object_flag_orig;
14556 	ship_obj	*so;
14557 	p_object *p_objp;
14558 
14559 	switch (oswpt.type)
14560 	{
14561 		case OSWPT_TYPE_NONE:
14562 		case OSWPT_TYPE_EXITED:
14563 			return;
14564 
14565 		case OSWPT_TYPE_WHOLE_TEAM:
14566 			Assert (oswpt.team >= 0);
14567 			for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ){
14568 				if (Ships[Objects[so->objnum].instance].team == oswpt.team) {
14569 					// recurse
14570 					object_ship_wing_point_team oswpt2(so);
14571 					sexp_alter_ship_flag_helper(oswpt2, future_ships, object_flag, ship_flag, parse_obj_flag, ai_flag, set_flag);
14572 				}
14573 			}
14574 
14575 			if (future_ships) {
14576 				for (p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp)) {
14577 					if (p_objp->team == oswpt.team) {
14578 						// recurse
14579 						object_ship_wing_point_team oswpt2(p_objp);
14580 						sexp_alter_ship_flag_helper(oswpt2, future_ships, object_flag, ship_flag, parse_obj_flag, ai_flag, set_flag);
14581 					}
14582 				}
14583 			}
14584 			break;
14585 
14586 		case OSWPT_TYPE_WING:
14587 		case OSWPT_TYPE_WING_NOT_PRESENT:
14588 			// if the wing isn't here, and we're only dealing with ships which are, we're done.
14589 			if  (!future_ships){
14590 				if (oswpt.type == OSWPT_TYPE_WING_NOT_PRESENT) {
14591 					return;
14592 				}
14593 			}
14594 			else {
14595 				for (p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp)) {
14596 					if (p_objp->wingnum == WING_INDEX(oswpt.wingp)) {
14597 						// recurse
14598 						object_ship_wing_point_team oswpt2(p_objp);
14599 						sexp_alter_ship_flag_helper(oswpt2, future_ships, object_flag, ship_flag, parse_obj_flag, ai_flag, set_flag);
14600 					}
14601 				}
14602 			}
14603 
14604 			for (i = 0; i < oswpt.wingp->current_count; i++) {
14605 				// recurse
14606 				object_ship_wing_point_team oswpt2(&Ships[oswpt.wingp->ship_index[i]]);
14607 				sexp_alter_ship_flag_helper(oswpt2, future_ships, object_flag, ship_flag, parse_obj_flag, ai_flag, set_flag);
14608 			}
14609 
14610 			break;
14611 
14612 		// finally! If we actually have a ship, we can set its flags!
14613 		case OSWPT_TYPE_SHIP:
14614 			// save flags for state change comparisons
14615 			object_flag_orig = oswpt.objp->flags;
14616 
14617 			// see if we have an object flag to set
14618 			if (object_flag != Object::Object_Flags::NUM_VALUES)
14619 			{
14620 				auto tmp_flagset = oswpt.objp->flags;
14621 				// set or clear?
14622 				tmp_flagset.set(object_flag, set_flag);
14623 
14624 				obj_set_flags(oswpt.objp, tmp_flagset);
14625 			}
14626 
14627 			// handle ETS when modifying shields
14628 			if (object_flag == Object::Object_Flags::No_shields) {
14629 				if (set_flag) {
14630 					zero_one_ets(&oswpt.ship_entry->shipp->shield_recharge_index, &oswpt.ship_entry->shipp->weapon_recharge_index, &oswpt.ship_entry->shipp->engine_recharge_index);
14631 				} else if (object_flag_orig[Object::Object_Flags::No_shields]) {
14632 					set_default_recharge_rates(oswpt.objp);
14633 				}
14634 			}
14635 
14636 			// see if we have a ship flag to set
14637 			if (ship_flag != Ship::Ship_Flags::NUM_VALUES)
14638 			{
14639 				// set or clear?
14640 					oswpt.ship_entry->shipp->flags.set((Ship::Ship_Flags)ship_flag, set_flag);
14641 			}
14642 
14643 			// the lock afterburner SEXP also needs to set a physics flag
14644 			if (ship_flag == Ship::Ship_Flags::Afterburner_locked) {
14645 				if (set_flag) {
14646 					afterburners_stop(oswpt.objp, 1);
14647 				}
14648 			}
14649 
14650 			// see if we have an ai flag to set
14651 			if (ai_flag != AI::AI_Flags::NUM_VALUES)
14652 			{
14653 				// set or clear?
14654 				Ai_info[oswpt.ship_entry->shipp->ai_index].ai_flags.set(ai_flag, set_flag);
14655 			}
14656 
14657 			// no break statement. We want to fall through.
14658 			FALLTHROUGH;
14659 
14660 		case OSWPT_TYPE_PARSE_OBJECT:
14661 			if (!future_ships) {
14662 				return;
14663 			}
14664 
14665 			// see if we have a p_object flag to set
14666 			if (parse_obj_flag != Mission::Parse_Object_Flags::NUM_VALUES && oswpt.ship_entry->p_objp != nullptr)
14667 			{
14668 				oswpt.ship_entry->p_objp->flags.set(parse_obj_flag, set_flag);
14669 			}
14670 			break;
14671 	}
14672 
14673 }
14674 
alter_flag_for_all_ships(bool future_ships,Object::Object_Flags object_flag,Ship::Ship_Flags ship_flag,Mission::Parse_Object_Flags parse_obj_flag,AI::AI_Flags ai_flag,bool set_flag)14675 void alter_flag_for_all_ships(bool future_ships, Object::Object_Flags object_flag, Ship::Ship_Flags ship_flag, Mission::Parse_Object_Flags parse_obj_flag, AI::AI_Flags ai_flag, bool set_flag)
14676 {
14677 	ship_obj	*so;
14678 	p_object	*p_objp;
14679 
14680 	// set all the ships present in the mission
14681 	for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
14682 		object_ship_wing_point_team oswpt(so);
14683 		sexp_alter_ship_flag_helper(oswpt, future_ships, object_flag, ship_flag, parse_obj_flag, ai_flag, set_flag);
14684 	}
14685 
14686 	// set up all the ships which have yet to arrive
14687 	if (future_ships) {
14688 		for (p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp)) {
14689 			object_ship_wing_point_team oswpt(p_objp);
14690 			sexp_alter_ship_flag_helper(oswpt, future_ships, object_flag, ship_flag, parse_obj_flag, ai_flag, set_flag);
14691 		}
14692 	}
14693 }
14694 
sexp_check_flag_arrays(const char * flag_name,Object::Object_Flags & object_flag,Ship::Ship_Flags & ship_flags,Mission::Parse_Object_Flags & parse_obj_flag,AI::AI_Flags & ai_flag)14695 bool sexp_check_flag_arrays(const char *flag_name, Object::Object_Flags &object_flag, Ship::Ship_Flags &ship_flags, Mission::Parse_Object_Flags &parse_obj_flag, AI::AI_Flags &ai_flag)
14696 {
14697 	int i;
14698 	bool send_multi = false;
14699 
14700 	for ( i = 0; i < MAX_OBJECT_FLAG_NAMES; i++) {
14701 		if (!stricmp(Object_flag_names[i].flag_name, flag_name)) {
14702 			// make sure the list writes to the correct list of flags!
14703 			if (Object_flag_names[i].flag_list == 1) {
14704 				object_flag = Object_flag_names[i].flag;
14705 			}
14706 			break;
14707 		}
14708 	}
14709 
14710 	for ( i = 0; i < MAX_SHIP_FLAG_NAMES; i++) {
14711 		if (!stricmp(Ship_flag_names[i].flag_name, flag_name)) {
14712 			// make sure the list writes to the correct list of flags!
14713 			ship_flags = Ship_flag_names[i].flag;
14714 			send_multi = true;
14715 			break;
14716 		}
14717 	}
14718 
14719 	// parse files already have a list of names in the same order as the flags, so we can do something slightly different here.
14720 	for (i = 0; i < (int)num_parse_object_flags; i++) {
14721 		if (!stricmp(Parse_object_flags[i].name, flag_name)) {
14722 			parse_obj_flag = Parse_object_flags[i].def;
14723 			break;
14724 		}
14725 	}
14726 
14727 	for ( i = 0; i < MAX_AI_FLAG_NAMES; i++) {
14728 		if (!stricmp(Ai_flag_names[i].flag_name, flag_name)) {
14729 			ai_flag = Ai_flag_names[i].flag;
14730 			break;
14731 		}
14732 	}
14733 
14734 	return send_multi;
14735 }
14736 
sexp_are_ship_flags_set(int node)14737 int sexp_are_ship_flags_set(int node)
14738 {
14739 	Object::Object_Flags object_flag = Object::Object_Flags::NUM_VALUES;
14740 	Ship::Ship_Flags ship_flags = Ship::Ship_Flags::NUM_VALUES;
14741 	Mission::Parse_Object_Flags parse_obj_flag = Mission::Parse_Object_Flags::NUM_VALUES;
14742 	AI::AI_Flags ai_flag = AI::AI_Flags::NUM_VALUES;
14743 
14744 	auto ship_entry = eval_ship(node);
14745 	if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT)
14746 		return SEXP_NAN;
14747 	if (ship_entry->status == ShipStatus::EXITED)
14748 		return SEXP_NAN_FOREVER;
14749 	node = CDR(node);
14750 
14751 	auto shipp = ship_entry->shipp;
14752 	auto objp = ship_entry->objp;
14753 	auto aip = &Ai_info[shipp->ai_index];
14754 
14755 	while (node != -1) {
14756 		auto flag_name = CTEXT(node);
14757 		sexp_check_flag_arrays(flag_name, object_flag, ship_flags, parse_obj_flag, ai_flag);
14758 
14759 		// now check the flags
14760 		if (object_flag != Object::Object_Flags::NUM_VALUES) {
14761 			if (!(objp->flags[object_flag]))
14762 				return SEXP_FALSE;
14763 		}
14764 
14765 		if (ship_flags != Ship::Ship_Flags::NUM_VALUES) {
14766 			if (!(shipp->flags[ship_flags]))
14767 				return SEXP_FALSE;
14768 		}
14769 
14770 		// we don't check parse flags
14771 
14772 		if (ai_flag != AI::AI_Flags::NUM_VALUES) {
14773 			if (!(aip->ai_flags[ai_flag]))
14774 				return SEXP_FALSE;
14775 		}
14776 
14777 		node = CDR(node);
14778 	}
14779 
14780 	// if we're still here, all the flags we were looking for were present
14781 	return SEXP_TRUE;
14782 }
14783 
sexp_alter_ship_flag(int node)14784 void sexp_alter_ship_flag(int node)
14785 {
14786 	Object::Object_Flags object_flag = Object::Object_Flags::NUM_VALUES;
14787 	Ship::Ship_Flags ship_flags = Ship::Ship_Flags::NUM_VALUES;
14788 	Mission::Parse_Object_Flags parse_obj_flag = Mission::Parse_Object_Flags::NUM_VALUES;
14789 	AI::AI_Flags ai_flag = AI::AI_Flags::NUM_VALUES;
14790 	bool set_flag = false;
14791 	bool future_ships = false;
14792 	object_ship_wing_point_team oswpt;
14793 
14794 	auto flag_name = CTEXT(node);
14795 
14796 	sexp_check_flag_arrays(flag_name, object_flag, ship_flags, parse_obj_flag, ai_flag);
14797 
14798 	node = CDR(node);
14799 	if (is_sexp_true(node)) {
14800 		set_flag = true;
14801 	}
14802 
14803 	node = CDR(node);
14804 	if (is_sexp_true(node)) {
14805 		future_ships = true;
14806 	}
14807 
14808 	// start the multiplayer packet
14809 	Current_sexp_network_packet.start_callback();
14810 	Current_sexp_network_packet.send_flag(object_flag);
14811 	Current_sexp_network_packet.send_flag(ship_flags);
14812 	Current_sexp_network_packet.send_flag(parse_obj_flag);
14813 	Current_sexp_network_packet.send_flag(ai_flag);
14814 	Current_sexp_network_packet.send_bool(set_flag);
14815 	Current_sexp_network_packet.send_bool(future_ships);
14816 
14817 	node = CDR(node);
14818 
14819 	// no 4th argument means do this to every ship in the mission (and if the flag is set, every ship that will be too).
14820 	if (node == -1) {
14821 		// send a message to the clients saying there were no more arguments
14822 		Current_sexp_network_packet.send_bool(false);
14823 
14824 		alter_flag_for_all_ships(future_ships, object_flag, ship_flags, parse_obj_flag, ai_flag, set_flag);
14825 	}
14826 	else {
14827 		// send a message to the clients saying there are more arguments
14828 		Current_sexp_network_packet.send_bool(true);
14829 
14830 		for (; node != -1; node = CDR(node)) {
14831 			eval_object_ship_wing_point_team(&oswpt, node);
14832 
14833 			// no point in setting these flags at all
14834 			if (oswpt.type == OSWPT_TYPE_NONE || oswpt.type == OSWPT_TYPE_EXITED ) {
14835 				continue;
14836 			}
14837 
14838 			sexp_alter_ship_flag_helper(oswpt, future_ships, object_flag, ship_flags, parse_obj_flag, ai_flag, set_flag);
14839 
14840 			Current_sexp_network_packet.send_int(oswpt.type);
14841 
14842 			switch (oswpt.type) {
14843 				case OSWPT_TYPE_SHIP:
14844 					Current_sexp_network_packet.send_ship(oswpt.ship_entry->shipp);
14845 					break;
14846 
14847 				case OSWPT_TYPE_PARSE_OBJECT:
14848 					Current_sexp_network_packet.send_parse_object(oswpt.ship_entry->p_objp);
14849 					break;
14850 
14851 				case OSWPT_TYPE_WING_NOT_PRESENT:
14852 				case OSWPT_TYPE_WING:
14853 					Current_sexp_network_packet.send_ushort(oswpt.wingp->net_signature);
14854 					break;
14855 
14856 				case OSWPT_TYPE_WHOLE_TEAM:
14857 					Current_sexp_network_packet.send_int(oswpt.team);
14858 					break;
14859 			}
14860 		}
14861 	}
14862 	Current_sexp_network_packet.end_callback();
14863 }
14864 
multi_sexp_alter_ship_flag()14865 void multi_sexp_alter_ship_flag()
14866 {
14867 	Object::Object_Flags object_flag = Object::Object_Flags::NUM_VALUES;
14868 	Ship::Ship_Flags ship_flag = Ship::Ship_Flags::NUM_VALUES;
14869 	Mission::Parse_Object_Flags parse_obj_flag = Mission::Parse_Object_Flags::NUM_VALUES;
14870 	AI::AI_Flags ai_flag = AI::AI_Flags::NUM_VALUES;
14871 	bool set_flag = false;
14872 	bool future_ships = true;
14873 	bool process_data = false;
14874 
14875 	Current_sexp_network_packet.get_flag(object_flag);
14876 	Current_sexp_network_packet.get_flag(ship_flag);
14877 	Current_sexp_network_packet.get_flag(parse_obj_flag);
14878 	Current_sexp_network_packet.get_flag(ai_flag);
14879 	Current_sexp_network_packet.get_bool(set_flag);
14880 	Current_sexp_network_packet.get_bool(future_ships);
14881 
14882 	// if any of the above failed so will this loop
14883 	if (!Current_sexp_network_packet.get_bool(process_data))
14884 	{
14885 		return;
14886 	}
14887 
14888 	// no more data means do this to every ship in the mission
14889 	if (!process_data)
14890 	{
14891 		alter_flag_for_all_ships(future_ships, object_flag, ship_flag, parse_obj_flag, ai_flag, set_flag);
14892 	}
14893 	else
14894 	{
14895 		int type;
14896 		while (Current_sexp_network_packet.get_int(type))
14897 		{
14898 			std::unique_ptr<object_ship_wing_point_team> oswptp;
14899 
14900 			switch (type)
14901 			{
14902 				case OSWPT_TYPE_SHIP:
14903 				{
14904 					ship *shipp;
14905 					if (Current_sexp_network_packet.get_ship(shipp))
14906 						oswptp.reset(new object_ship_wing_point_team(shipp));
14907 					else
14908 						Warning(LOCATION, "OSWPT had an invalid ship in multi_sexp_alter_ship_flag(), skipping");
14909 					break;
14910 				}
14911 
14912 				case OSWPT_TYPE_PARSE_OBJECT:
14913 				{
14914 					p_object *p_objp;
14915 					if (Current_sexp_network_packet.get_parse_object(p_objp))
14916 						oswptp.reset(new object_ship_wing_point_team(p_objp));
14917 					else
14918 						Warning(LOCATION, "OSWPT had an invalid parse object in multi_sexp_alter_ship_flag(), skipping");
14919 					break;
14920 				}
14921 
14922 				case OSWPT_TYPE_WING_NOT_PRESENT:
14923 				case OSWPT_TYPE_WING:
14924 				{
14925 					wing *wingp;
14926 					if (Current_sexp_network_packet.get_wing(wingp))
14927 						oswptp.reset(new object_ship_wing_point_team(wingp));
14928 					else
14929 						Warning(LOCATION, "OSWPT had an invalid wing in multi_sexp_alter_ship_flag(), skipping");
14930 					break;
14931 				}
14932 
14933 				case OSWPT_TYPE_WHOLE_TEAM:
14934 				{
14935 					int team;
14936 					if (Current_sexp_network_packet.get_int(team))
14937 					{
14938 						oswptp.reset(new object_ship_wing_point_team());
14939 						oswptp->type = OSWPT_TYPE_WHOLE_TEAM;
14940 						oswptp->team = team;
14941 					}
14942 					else
14943 						Warning(LOCATION, "OSWPT had an invalid team in multi_sexp_alter_ship_flag(), skipping");
14944 					break;
14945 				}
14946 			}
14947 
14948 			if (oswptp)
14949 				sexp_alter_ship_flag_helper(*oswptp, future_ships, object_flag, ship_flag, parse_obj_flag, ai_flag, set_flag);
14950 		}
14951 	}
14952 }
14953 
14954 // modified by Goober5000; now it should work properly
14955 // function to deal with breaking/fixing the warp engines on ships/wings.
14956 // --repairable is true when we are breaking the warp drive (can be repaired)
14957 // --damage_it is true when we are sabotaging it, one way or the other; false when fixing it
sexp_deal_with_warp(int n,bool repairable,bool damage_it)14958 void sexp_deal_with_warp(int n, bool repairable, bool damage_it)
14959 {
14960 	Ship::Ship_Flags ship_flag;
14961 	Mission::Parse_Object_Flags p_object_flag;
14962 
14963 	if (repairable)
14964 	{
14965 		ship_flag = Ship::Ship_Flags::Warp_broken;
14966 		p_object_flag = Mission::Parse_Object_Flags::SF_Warp_broken;
14967 	}
14968 	else
14969 	{
14970 		ship_flag = Ship::Ship_Flags::Warp_never;
14971 		p_object_flag = Mission::Parse_Object_Flags::SF_Warp_never;
14972 	}
14973 
14974 	sexp_deal_with_ship_flag(n, true, Object::Object_Flags::NUM_VALUES, ship_flag, p_object_flag, damage_it);
14975 }
14976 
14977 // Goober5000
sexp_set_subspace_drive(int node)14978 void sexp_set_subspace_drive(int node)
14979 {
14980 	bool set_flag = !is_sexp_true(node);
14981 
14982 	sexp_deal_with_ship_flag(CDR(node), true, Object::Object_Flags::NUM_VALUES, Ship::Ship_Flags::No_subspace_drive, Mission::Parse_Object_Flags::NUM_VALUES, set_flag);
14983 }
14984 
14985 /**
14986  * Tell the AI when it is okay to fire certain secondary weapons at other ships.
14987  */
sexp_good_secondary_time(int n)14988 void sexp_good_secondary_time(int n)
14989 {
14990 	bool is_nan, is_nan_forever;
14991 
14992 	auto team_name = CTEXT(n);
14993 	n = CDR(n);
14994 
14995 	int num_weapons = eval_num(n, is_nan, is_nan_forever);
14996 	if (is_nan || is_nan_forever)
14997 		return;
14998 	n = CDR(n);
14999 
15000 	auto weapon_name = CTEXT(n);
15001 	n = CDR(n);
15002 
15003 	auto ship_entry = eval_ship(n);
15004 	if (!ship_entry)
15005 		return;
15006 
15007 	int weapon_index = weapon_info_lookup(weapon_name);
15008 	if ( weapon_index == -1 ) {
15009 		nprintf(("Warning", "couldn't find weapon %s for good-secondary-time\n", weapon_name));
15010 		return;
15011 	}
15012 
15013 	// get the team type from the team_name
15014 	int team = iff_lookup(team_name);
15015 
15016 	// see if the ship has exited.  If so, then we don't need to set up the AI stuff
15017 	if (ship_entry->status == ShipStatus::EXITED)
15018 		return;
15019 
15020 	ai_good_secondary_time( team, weapon_index, num_weapons, ship_entry->name );
15021 }
15022 
15023 // Karajorma - Turns the built in messages for pilots and command on or off
sexp_toggle_builtin_messages(int node,bool enable_messages)15024 void sexp_toggle_builtin_messages (int node, bool enable_messages)
15025 {
15026 	// If no arguments were supplied then turn off all messages then bail
15027 	if (node < 0)
15028 	{
15029 		The_mission.flags.set(Mission::Mission_Flags::No_builtin_msgs, !enable_messages);
15030 		return;
15031 	}
15032 
15033 	// iterate through all the nodes supplied
15034 	while (node >= 0)
15035 	{
15036 		auto ship_name = CTEXT(node);
15037 
15038 		// check that this isn't a request to silence command.
15039 		if ((*ship_name == '#') && !stricmp(&ship_name[1], The_mission.command_sender))
15040 		{
15041 			// Either disable or enable messages from command
15042 			The_mission.flags.set(Mission::Mission_Flags::No_builtin_command, !enable_messages);
15043 		}
15044 		else if (!stricmp(ship_name, "<Any Wingman>"))
15045 		{
15046 			// Since trying to determine whose wingman in a stand alone multiplayer game opens a can of worms
15047 			// Any Wingman silences all ships in wings regardless of whose side they're on.
15048 			for (int wingnum = 0; wingnum < Num_wings; ++wingnum) {
15049 				for (int shipnum = 0; shipnum < Wings[wingnum].current_count; ++shipnum) {
15050 					int ship_index = Wings[wingnum].ship_index[shipnum];
15051 					Assert( ship_index != -1 );
15052 					Ships[ship_index].flags.set(Ship::Ship_Flags::No_builtin_messages, !enable_messages);
15053 				}
15054 			}
15055 		}
15056 		// If it isn't command then assume that we're dealing with a ship
15057 		else
15058 		{
15059 			sexp_deal_with_ship_flag(node, false, Object::Object_Flags::NUM_VALUES, Ship::Ship_Flags::No_builtin_messages, Mission::Parse_Object_Flags::SF_No_builtin_messages, !enable_messages);
15060 		}
15061 
15062 		node = CDR(node);
15063 	}
15064 }
15065 
sexp_set_persona(int node)15066 void sexp_set_persona (int node)
15067 {
15068 	int persona_index = -1;
15069 	auto persona_name = CTEXT(node);
15070 
15071 	for (int i = 0 ; i < Num_personas; ++i) {
15072 		if (!strcmp(persona_name, Personas[i].name) && (Personas[i].flags & PERSONA_FLAG_WINGMAN)) {
15073 			persona_index = i;
15074 			break;
15075 		}
15076 	}
15077 
15078 	if (persona_index == -1) {
15079 		Warning(LOCATION, "Unable to change to persona type: '%s'. Persona is not a wingman!", persona_name);
15080 		return;
15081 	}
15082 
15083 	node = CDR(node);
15084 	Assert (node >=0);
15085 
15086 	if (MULTIPLAYER_MASTER) {
15087 		Current_sexp_network_packet.start_callback();
15088 		Current_sexp_network_packet.send_int(persona_index);
15089 	}
15090 
15091 	// now loop through the list of ships
15092 	for ( ; node >= 0; node = CDR(node) ) {
15093 		auto ship_entry = eval_ship(node);
15094 		if (!ship_entry || !ship_entry->shipp) {
15095 			continue;
15096 		}
15097 
15098 		ship_entry->shipp->persona_index = persona_index;
15099 
15100 		if (MULTIPLAYER_MASTER) {
15101 			Current_sexp_network_packet.send_ship(ship_entry->shipp);
15102 		}
15103 	}
15104 
15105 	if (MULTIPLAYER_MASTER) {
15106 		Current_sexp_network_packet.end_callback();
15107 	}
15108 }
15109 
multi_sexp_set_persona()15110 void multi_sexp_set_persona()
15111 {
15112 	ship *shipp = nullptr;
15113 	int persona_index = -1;
15114 
15115 	if (! Current_sexp_network_packet.get_int(persona_index) ) {
15116 		return;
15117 	}
15118 
15119 	while (Current_sexp_network_packet.get_ship(shipp)) {
15120 		Assert(persona_index != -1);
15121 		if (shipp != nullptr) {
15122 			shipp->persona_index = persona_index;
15123 		}
15124 	}
15125 }
15126 
sexp_set_mission_mood(int node)15127 void sexp_set_mission_mood (int node)
15128 {
15129 	auto mood = CTEXT(node);
15130 	for (SCP_vector<SCP_string>::iterator iter = Builtin_moods.begin(); iter != Builtin_moods.end(); ++iter) {
15131 		if (!strcmp(iter->c_str(), mood)) {
15132 			Current_mission_mood = (int)std::distance(Builtin_moods.begin(), iter);
15133 			return;
15134 		}
15135 	}
15136 
15137 	Warning(LOCATION, "Sexp-mission-mood attempted to set mood %s which does not exist in messages.tbl", mood);
15138 }
15139 
sexp_weapon_fired_delay(int node,int op_num)15140 int sexp_weapon_fired_delay(int node, int op_num)
15141 {
15142 	ship *shipp;
15143 	int requested_bank, delay, last_fired = -1;
15144 	bool is_nan, is_nan_forever;
15145 
15146 	auto ship_entry = eval_ship(node);
15147 	if (!ship_entry) {
15148 		return SEXP_FALSE;
15149 	}
15150 	if (ship_entry->status == ShipStatus::EXITED) {
15151 		return SEXP_KNOWN_FALSE;
15152 	}
15153 	if (ship_entry->status == ShipStatus::NOT_YET_PRESENT) {
15154 		return SEXP_CANT_EVAL;
15155 	}
15156 	shipp = ship_entry->shipp;
15157 
15158 	// Get the bank to check
15159 	node = CDR(node);
15160 	requested_bank = eval_num(node, is_nan, is_nan_forever);
15161 	if (is_nan_forever) {
15162 		return SEXP_KNOWN_FALSE;
15163 	} else if (is_nan || requested_bank < 0) {
15164 		return SEXP_FALSE;
15165 	}
15166 
15167 	// get the delay
15168 	node = CDR(node);
15169 	delay = eval_num(node, is_nan, is_nan_forever);
15170 	if (is_nan_forever) {
15171 		return SEXP_KNOWN_FALSE;
15172 	} else if (is_nan || delay <= 0) {
15173 		return SEXP_FALSE;
15174 	}
15175 
15176 	switch (op_num) {
15177 		case OP_PRIMARY_FIRED_SINCE:
15178 			if (requested_bank >= shipp->weapons.num_primary_banks) {
15179 				return SEXP_FALSE;
15180 			}
15181 			last_fired = shipp->weapons.last_primary_fire_stamp[requested_bank];
15182 			break;
15183 
15184 		case OP_SECONDARY_FIRED_SINCE:
15185 			if (requested_bank >= shipp->weapons.num_secondary_banks) {
15186 				return SEXP_FALSE;
15187 			}
15188 			last_fired = shipp->weapons.last_secondary_fire_stamp[requested_bank];
15189 			break;
15190 	}
15191 
15192 	if (last_fired < 0) {
15193 		// weapon was never fired
15194 		return SEXP_FALSE;
15195 	}
15196 
15197 	if (timestamp() - delay < last_fired) {
15198 		return SEXP_TRUE;
15199 	}
15200 
15201 	return SEXP_FALSE;
15202 }
15203 
sexp_has_weapon(int node,int op_num)15204 int sexp_has_weapon(int node, int op_num)
15205 {
15206 	ship *shipp;
15207 	bool is_nan, is_nan_forever;
15208 	int i;
15209 	int requested_bank;
15210 	int weapon_index;
15211 	int num_weapon_banks = 0;
15212 	int *weapon_banks = nullptr;
15213 
15214 	auto ship_entry = eval_ship(node);
15215 	if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT)
15216 		return SEXP_NAN;
15217 	if (ship_entry->status == ShipStatus::EXITED)
15218 		return SEXP_NAN_FOREVER;
15219 	node = CDR(node);
15220 
15221 	shipp = ship_entry->shipp;
15222 
15223 	// Get the bank to check
15224 	if (!strcmp(CTEXT(node), SEXP_ALL_BANKS_STRING)) {
15225 		requested_bank = -1;
15226 	}
15227 	else {
15228 		requested_bank = eval_num(node, is_nan, is_nan_forever);
15229 		if (is_nan) {
15230 			return SEXP_FALSE;
15231 		}
15232 		if (is_nan_forever) {
15233 			return SEXP_KNOWN_FALSE;
15234 		}
15235 	}
15236 	node = CDR(node);
15237 
15238 	switch (op_num) {
15239 		case OP_HAS_PRIMARY_WEAPON:
15240 			weapon_banks = shipp->weapons.primary_bank_weapons;
15241 			num_weapon_banks = shipp->weapons.num_primary_banks;
15242 			break;
15243 
15244 		case OP_HAS_SECONDARY_WEAPON:
15245 			weapon_banks = shipp->weapons.secondary_bank_weapons;
15246 			num_weapon_banks = shipp->weapons.num_secondary_banks;
15247 			break;
15248 
15249 		default:
15250 			Warning(LOCATION, "Unrecognised bank type used in has-x-weapon. Returning false");
15251 			return SEXP_FALSE;
15252 	}
15253 
15254 
15255 	//loop through the weapons and test them
15256 	while (node > -1) {
15257 		weapon_index = weapon_info_lookup(CTEXT(node));
15258 		Assertion (weapon_index >= 0, "Weapon name %s is unknown.", CTEXT(node));
15259 
15260 		// if we're checking every bank
15261 		if (requested_bank == -1) {
15262 			for (i = 0; i < num_weapon_banks; i++) {
15263 				if (weapon_index == weapon_banks[i]) {
15264 					return SEXP_TRUE;
15265 				}
15266 			}
15267 		}
15268 
15269 		// if we're only checking one bank
15270 		else {
15271 			if (weapon_index == weapon_banks[requested_bank]) {
15272 				return SEXP_TRUE;
15273 			}
15274 		}
15275 
15276 		node = CDR(node);
15277 	}
15278 
15279 	return SEXP_FALSE;
15280 }
15281 
15282 /**
15283  * Gets status of goals for previous missions (in the current campaign).
15284  *
15285  * @param n Sexp node number
15286  * @param status tell this function if we are looking for a goal_satisfied, goal_failed, or goal incomplete event
15287  */
sexp_previous_goal_status(int n,int status)15288 int sexp_previous_goal_status( int n, int status )
15289 {
15290 	int rval = 0;
15291 	int i, mission_num;
15292 	bool default_value = false, use_defaults = true;
15293 
15294 	auto mission_name = CTEXT(n);
15295 	auto goal_name = CTEXT(CDR(n));
15296 
15297 	// check for possible next optional argument
15298 	n = CDR(CDR(n));
15299 	if ( n != -1 ) {
15300 		default_value = is_sexp_true(n);
15301 	}
15302 
15303 	// try to find the given mission name in the current list of missions in the campaign.
15304 	if ( Game_mode & GM_CAMPAIGN_MODE ) {
15305 		i = mission_campaign_find_mission( mission_name );
15306 
15307 		if ( i == -1 ) {
15308 			// if mission not found, assume that goal was false (so previous-goal-false returns true)
15309 			nprintf(("General", "Couldn't find mission name \"%s\" in current campaign's list of missions.\nReturning %s for goal-status function.\n", mission_name, (status==GOAL_COMPLETE)?"false":"true"));
15310 			if ( status == GOAL_COMPLETE )
15311 				rval = SEXP_KNOWN_FALSE;
15312 			else
15313 				rval = SEXP_KNOWN_TRUE;
15314 
15315 			use_defaults = false;
15316 		} else if (Campaign.missions[i].flags & CMISSION_FLAG_SKIPPED) {
15317 			use_defaults = true;
15318 		} else {
15319 			// now try and find the goal this mission
15320 			mission_num = i;
15321 			for (i = 0; i < Campaign.missions[mission_num].num_goals; i++) {
15322 				if ( !stricmp(Campaign.missions[mission_num].goals[i].name, goal_name) )
15323 					break;
15324 			}
15325 
15326 			if ( i == Campaign.missions[mission_num].num_goals ) {
15327 				Warning(LOCATION, "Couldn't find goal name \"%s\" in mission %s.\nReturning %s for goal-true function.", goal_name, mission_name, (status==GOAL_COMPLETE)?"false":"true");
15328 				if ( status == GOAL_COMPLETE )
15329 					rval = SEXP_KNOWN_FALSE;
15330 				else
15331 					rval = SEXP_KNOWN_TRUE;
15332 
15333 			} else {
15334 				// now return KNOWN_TRUE or KNOWN_FALSE based on the status field in the goal structure
15335 				if ( Campaign.missions[mission_num].goals[i].status == status )
15336 					rval = SEXP_KNOWN_TRUE;
15337 				else
15338 					rval = SEXP_KNOWN_FALSE;
15339 			}
15340 
15341 			use_defaults = false;
15342 		}
15343 	}
15344 
15345 	if (use_defaults) {
15346 		// when not in campaign mode, always return KNOWN_TRUE when looking for goal complete, and KNOWN_FALSE
15347 		// otherwise
15348 		if ( n != -1 ) {
15349 			if ( default_value )
15350 				rval = SEXP_KNOWN_TRUE;
15351 			else
15352 				rval = SEXP_KNOWN_FALSE;
15353 		} else {
15354 			if ( status == GOAL_COMPLETE )
15355 				rval = SEXP_KNOWN_TRUE;
15356 			else
15357 				rval = SEXP_KNOWN_FALSE;
15358 		}
15359 	}
15360 
15361 	return rval;
15362 }
15363 
15364 // sexpression which gets the status of an event from a previous mission.  Like the above function but
15365 // dealing with events instead of goals.  Again, the status parameter tells the code if we are looking
15366 // for an event_true, event_false, or event_incomplete status
sexp_previous_event_status(int n,int status)15367 int sexp_previous_event_status( int n, int status )
15368 {
15369 	int rval = 0;
15370 	int i, mission_num;
15371 	bool default_value = false, use_defaults = true;
15372 
15373 	auto mission_name = CTEXT(n);
15374 	auto name = CTEXT(CDR(n));
15375 
15376 	// check for possible optional parameter
15377 	n = CDR(CDR(n));
15378 	if ( n != -1 ){
15379 		default_value = is_sexp_true(n);
15380 	}
15381 
15382 	if ( Game_mode & GM_CAMPAIGN_MODE ) {
15383 		// following function returns -1 when mission isn't found.
15384 		i = mission_campaign_find_mission( mission_name );
15385 
15386 		// if the mission name wasn't found -- make this return FALSE for the event status.
15387 		if ( i == -1 ) {
15388 			nprintf(("General", "Couldn't find mission name \"%s\" in current campaign's list of missions.\nReturning %s for event-status function.\n", mission_name, (status==EVENT_SATISFIED)?"false":"true"));
15389 			if ( status == EVENT_SATISFIED ) {
15390 				rval = SEXP_KNOWN_FALSE;
15391 			} else {
15392 				rval = SEXP_KNOWN_TRUE;
15393 			}
15394 
15395 			use_defaults = false;
15396 		} else if (Campaign.missions[i].flags & CMISSION_FLAG_SKIPPED) {
15397 			use_defaults = true;
15398 		} else {
15399 			// now try and find the goal this mission
15400 			mission_num = i;
15401 			for (i = 0; i < Campaign.missions[mission_num].num_events; i++) {
15402 				if ( !stricmp(Campaign.missions[mission_num].events[i].name, name) )
15403 					break;
15404 			}
15405 
15406 			if ( i == Campaign.missions[mission_num].num_events ) {
15407 				Warning(LOCATION, "Couldn't find event name \"%s\" in mission %s.\nReturning %s for event_status function.", name, mission_name, (status==EVENT_SATISFIED)?"false":"true");
15408 				if ( status == EVENT_SATISFIED )
15409 					rval = SEXP_KNOWN_FALSE;
15410 				else
15411 					rval = SEXP_KNOWN_TRUE;
15412 
15413 			} else {
15414 				// now return KNOWN_TRUE or KNOWN_FALSE based on the status field in the goal structure
15415 				if ( Campaign.missions[mission_num].events[i].status == status )
15416 					rval = SEXP_KNOWN_TRUE;
15417 				else
15418 					rval = SEXP_KNOWN_FALSE;
15419 			}
15420 
15421 			use_defaults = false;
15422 		}
15423 	}
15424 
15425 	if (use_defaults) {
15426 		if ( n != -1 ) {
15427 			if ( default_value )
15428 				rval = SEXP_KNOWN_TRUE;
15429 			else
15430 				rval = SEXP_KNOWN_FALSE;
15431 		} else {
15432 			if ( status == EVENT_SATISFIED )
15433 				rval = SEXP_KNOWN_TRUE;
15434 			else
15435 				rval = SEXP_KNOWN_FALSE;
15436 		}
15437 	}
15438 
15439 	return rval;
15440 }
15441 
15442 /**
15443  * Return the status of an event in the current mission.
15444  *
15445  * @param n Sexp node number
15446  * @param want_true indicates if we are checking whether the event is true or the event is false.
15447  */
sexp_event_status(int n,int want_true)15448 int sexp_event_status( int n, int want_true )
15449 {
15450 	auto name = CTEXT(n);
15451 
15452 	for (int i = 0; i < Num_mission_events; ++i) {
15453 		// look for the event name, check it's status.  If formula is gone, we know the state won't ever change.
15454 		if ( !stricmp(Mission_events[i].name, name) ) {
15455 			int result = Mission_events[i].result;
15456 			if (Mission_events[i].formula < 0) {
15457 				if ( (want_true && result) || (!want_true && !result) )
15458 					return SEXP_KNOWN_TRUE;
15459 				else
15460 					return SEXP_KNOWN_FALSE;
15461 
15462 			} else {
15463 				if ( (want_true && result) || (!want_true && !result) )
15464 					return SEXP_TRUE;
15465 				else
15466 					return SEXP_FALSE;
15467 			}
15468 		}
15469 	}
15470 
15471 	return SEXP_FALSE;
15472 }
15473 
15474 /**
15475  * Return the status of an event N seconds after the event is true or false.
15476  *
15477  * Similar to above function but waits N seconds before returning true
15478  */
sexp_event_delay_status(int n,int want_true,bool use_msecs=false)15479 int sexp_event_delay_status( int n, int want_true, bool use_msecs = false)
15480 {
15481 	bool is_nan, is_nan_forever;
15482 	fix delay;
15483 	int rval = SEXP_FALSE;
15484 	bool use_as_directive = false;
15485 
15486 	auto name = CTEXT(n);
15487 
15488 	if (use_msecs) {
15489 		uint64_t tempDelay = eval_num(CDR(n), is_nan, is_nan_forever);
15490 		if (is_nan) {
15491 			return SEXP_FALSE;
15492 		}
15493 		else if (is_nan_forever) {
15494 			return SEXP_KNOWN_FALSE;
15495 		}
15496 
15497 		tempDelay = tempDelay << 16;
15498 		tempDelay = tempDelay / 1000;
15499 
15500 		delay = (fix) tempDelay;
15501 	} else {
15502 		delay = i2f(eval_num(CDR(n), is_nan, is_nan_forever));
15503 		if (is_nan) {
15504 			return SEXP_FALSE;
15505 		}
15506 		else if (is_nan_forever) {
15507 			return SEXP_KNOWN_FALSE;
15508 		}
15509 	}
15510 
15511 	for (int i = 0; i < Num_mission_events; ++i) {
15512 		// look for the event name, check it's status.  If formula is gone, we know the state won't ever change.
15513 		if ( !stricmp(Mission_events[i].name, name) ) {
15514 			if ( (fix) Mission_events[i].timestamp + delay >= Missiontime ) {
15515 				rval = SEXP_FALSE;
15516 				break;
15517 			}
15518 
15519 			int result = Mission_events[i].result;
15520 			if (Mission_events[i].formula < 0) {
15521 				if ( (want_true && result) || (!want_true && !result) ) {
15522 					rval = SEXP_KNOWN_TRUE;
15523 					break;
15524 				} else {
15525 					rval = SEXP_KNOWN_FALSE;
15526 					break;
15527 				}
15528 			} else {
15529 				if ( want_true && result ) {  //) || (!want_true && !result) )
15530 					rval = SEXP_TRUE;
15531 					break;
15532 				} else {
15533 					rval = SEXP_FALSE;
15534 					break;
15535 				}
15536 			}
15537 		}
15538 	}
15539 
15540 	// check for possible optional parameter
15541 	n = CDDR(n);
15542 	if (n != -1)
15543 		use_as_directive = is_sexp_true(n);
15544 
15545 	// zero out Sexp_useful_number if it's not true and we don't want this for specific directive use
15546 	if ( !use_as_directive && (rval != SEXP_TRUE) && (rval != SEXP_KNOWN_TRUE) )
15547 		Sexp_useful_number = 0;  // indicate sexp isn't current yet
15548 
15549 	return rval;
15550 }
15551 
15552 /**
15553  * Returns true if the given event is still incomplete
15554  */
sexp_event_incomplete(int n)15555 int sexp_event_incomplete(int n)
15556 {
15557 	auto name = CTEXT(n);
15558 
15559 	for (int i = 0; i < Num_mission_events; ++i) {
15560 		if ( !stricmp(Mission_events[i].name, name ) ) {
15561 			// if the formula is still >= 0 (meaning it is still getting eval'ed), then
15562 			// the event is incomplete
15563 			if ( Mission_events[i].formula != -1 )
15564 				return SEXP_TRUE;
15565 			else
15566 				return SEXP_KNOWN_FALSE;
15567 		}
15568 	}
15569 
15570 	return SEXP_FALSE;
15571 }
15572 
15573 /**
15574  * Return the status of an goal N seconds after the goal is true or false.
15575  *
15576  * Similar to above function but operates on goals instead of events
15577  */
sexp_goal_delay_status(int n,int want_true)15578 int sexp_goal_delay_status( int n, int want_true )
15579 {
15580 	fix delay, time;
15581 	bool is_nan, is_nan_forever;
15582 
15583 	auto name = CTEXT(n);
15584 	delay = i2f(eval_num(CDR(n), is_nan, is_nan_forever));
15585 	if (is_nan) {
15586 		return SEXP_FALSE;
15587 	}
15588 	else if (is_nan_forever) {
15589 		return SEXP_KNOWN_FALSE;
15590 	}
15591 
15592 	if ( want_true ) {
15593 		// if we are looking for a goal true entry and we find a false, then return known false here
15594 		if ( mission_log_get_time(LOG_GOAL_FAILED, name, nullptr, nullptr) )
15595 			return SEXP_KNOWN_FALSE;
15596 		else if ( mission_log_get_time(LOG_GOAL_SATISFIED, name, nullptr, &time) ) {
15597 			if ( (Missiontime - time) >= delay )
15598 				return SEXP_KNOWN_TRUE;
15599 		}
15600 	} else {
15601 		// if we are looking for a goal false entry and we find a true, then return known false here
15602 		if ( mission_log_get_time(LOG_GOAL_SATISFIED, name, nullptr, nullptr) )
15603 			return SEXP_KNOWN_FALSE;
15604 		else if ( mission_log_get_time(LOG_GOAL_FAILED, name, nullptr, &time) ) {
15605 			if ( (Missiontime - time) >= delay )
15606 				return SEXP_KNOWN_TRUE;
15607 		}
15608 	}
15609 
15610 	return SEXP_FALSE;
15611 }
15612 
15613 /**
15614  * Returns true if the given goal is still incomplete
15615  */
sexp_goal_incomplete(int n)15616 int sexp_goal_incomplete(int n)
15617 {
15618 	auto name = CTEXT(n);
15619 
15620 	if ( mission_log_get_time( LOG_GOAL_SATISFIED, name, nullptr, nullptr) || mission_log_get_time( LOG_GOAL_FAILED, name, nullptr, nullptr) )
15621 		return SEXP_KNOWN_FALSE;
15622 	else
15623 		return SEXP_TRUE;
15624 }
15625 
15626 /**
15627  * Protects/unprotects a ship.
15628  *
15629  * @param n Sexp node number
15630  * @param flag Whether or not the protect bit should be set (flag==true) or cleared (flag==false)
15631  */
sexp_protect_ships(int n,bool flag)15632 void sexp_protect_ships(int n, bool flag)
15633 {
15634 	sexp_deal_with_ship_flag(n, true, Object::Object_Flags::Protected, Ship::Ship_Flags::NUM_VALUES, Mission::Parse_Object_Flags::OF_Protected, flag);
15635 }
15636 
15637 /**
15638  * Protects/unprotects a ship from beams.
15639  *
15640  * @param n Sexp node number
15641  * @param flag Whether or not the protect bit should be set (flag==true) or cleared (flag==false)
15642  */
sexp_beam_protect_ships(int n,bool flag)15643 void sexp_beam_protect_ships(int n, bool flag)
15644 {
15645 	sexp_deal_with_ship_flag(n, true, Object::Object_Flags::Beam_protected, Ship::Ship_Flags::NUM_VALUES, Mission::Parse_Object_Flags::OF_Beam_protected, flag);
15646 }
15647 
15648 /**
15649  * Protects/unprotects a ship from various turrets.
15650  *
15651  * @param n Sexp node number
15652  * @param flag Whether or not the protect bit should be set (flag==true) or cleared (flag==false)
15653  */
sexp_turret_protect_ships(int n,bool flag)15654 void sexp_turret_protect_ships(int n, bool flag)
15655 {
15656 	auto turret_type = CTEXT(n);
15657 	n = CDR(n);
15658 
15659 	if (!stricmp(turret_type, "beam"))
15660 		sexp_deal_with_ship_flag(n, true, Object::Object_Flags::Beam_protected, Ship::Ship_Flags::NUM_VALUES, Mission::Parse_Object_Flags::OF_Beam_protected, flag);
15661 	else if (!stricmp(turret_type, "flak"))
15662 		sexp_deal_with_ship_flag(n, true, Object::Object_Flags::Flak_protected, Ship::Ship_Flags::NUM_VALUES, Mission::Parse_Object_Flags::OF_Flak_protected, flag);
15663 	else if (!stricmp(turret_type, "laser"))
15664 		sexp_deal_with_ship_flag(n, true, Object::Object_Flags::Laser_protected, Ship::Ship_Flags::NUM_VALUES, Mission::Parse_Object_Flags::OF_Laser_protected, flag);
15665 	else if (!stricmp(turret_type, "missile"))
15666 		sexp_deal_with_ship_flag(n, true, Object::Object_Flags::Missile_protected, Ship::Ship_Flags::NUM_VALUES, Mission::Parse_Object_Flags::OF_Missile_protected, flag);
15667 	else
15668 		Warning(LOCATION, "Invalid turret type '%s'!", turret_type);
15669 }
15670 
15671 // Goober5000 - sets the "don't collide invisible" flag on a list of ships
sexp_dont_collide_invisible(int n,bool dont_collide)15672 void sexp_dont_collide_invisible(int n, bool dont_collide)
15673 {
15674 	sexp_deal_with_ship_flag(n, true, Object::Object_Flags::NUM_VALUES, Ship::Ship_Flags::Dont_collide_invis, Mission::Parse_Object_Flags::SF_Dont_collide_invis, dont_collide);
15675 }
15676 
15677 // Goober5000 - sets the "immobile" flag on a list of ships
sexp_set_immobile(int n,bool immobile)15678 void sexp_set_immobile(int n, bool immobile)
15679 {
15680 	sexp_deal_with_ship_flag(n, true, Object::Object_Flags::Immobile, Ship::Ship_Flags::NUM_VALUES, Mission::Parse_Object_Flags::OF_Immobile, immobile);
15681 }
15682 
15683 // Goober5000 - sets the "no-ets" flag on a list of ships
sexp_disable_ets(int n,bool disable)15684 void sexp_disable_ets(int n, bool disable)
15685 {
15686 	sexp_deal_with_ship_flag(n, true, Object::Object_Flags::NUM_VALUES, Ship::Ship_Flags::No_ets, Mission::Parse_Object_Flags::SF_No_ets, disable);
15687 }
15688 
15689 // Goober5000 - sets the vaporize flag on a list of ships
sexp_ships_vaporize(int n,bool vaporize)15690 void sexp_ships_vaporize(int n, bool vaporize)
15691 {
15692 	sexp_deal_with_ship_flag(n, true, Object::Object_Flags::NUM_VALUES, Ship::Ship_Flags::Vaporize, Mission::Parse_Object_Flags::SF_Vaporize, vaporize);
15693 }
15694 
15695 /**
15696  * Make ships "visible" and "invisible" to sensors.
15697  *
15698  * @param n Sexp node number
15699  * @param visible Is true when making ships visible, false otherwise
15700  */
sexp_ships_visible(int n,bool visible)15701 void sexp_ships_visible(int n, bool visible)
15702 {
15703 	sexp_deal_with_ship_flag(n, true, Object::Object_Flags::NUM_VALUES, Ship::Ship_Flags::Hidden_from_sensors, Mission::Parse_Object_Flags::SF_Hidden_from_sensors, !visible, true);
15704 
15705 	// we also have to add any escort ships that were made visible
15706 	for (; n >= 0; n = CDR(n))
15707 	{
15708 		auto ship_entry = eval_ship(n);
15709 		if (!ship_entry || !ship_entry->shipp)
15710 			continue;
15711 
15712 		if (!visible && Player_ai->target_objnum == ship_entry->shipp->objnum) {
15713 			hud_cease_targeting();
15714 		}
15715 		else if (visible && (ship_entry->shipp->flags[Ship::Ship_Flags::Escort])) {
15716 			hud_add_ship_to_escort(ship_entry->shipp->objnum, 1);
15717 		}
15718 	}
15719 }
15720 
15721 // Goober5000
sexp_ships_stealthy(int n,bool stealthy)15722 void sexp_ships_stealthy(int n, bool stealthy)
15723 {
15724 	sexp_deal_with_ship_flag(n, true, Object::Object_Flags::NUM_VALUES, Ship::Ship_Flags::Stealth, Mission::Parse_Object_Flags::SF_Stealth, stealthy, true);
15725 
15726 	// we also have to add any escort ships that were made visible
15727 	if (!stealthy)
15728 	{
15729 		for (; n >= 0; n = CDR(n))
15730 		{
15731 			auto ship_entry = eval_ship(n);
15732 			if (!ship_entry || !ship_entry->shipp)
15733 				continue;
15734 
15735 			if (ship_entry->shipp->flags[Ship::Ship_Flags::Escort])
15736 				hud_add_ship_to_escort(ship_entry->shipp->objnum, 1);
15737 		}
15738 	}
15739 }
15740 
15741 // Goober5000
sexp_friendly_stealth_invisible(int n,bool invisible)15742 void sexp_friendly_stealth_invisible(int n, bool invisible)
15743 {
15744 	sexp_deal_with_ship_flag(n, true, Object::Object_Flags::NUM_VALUES, Ship::Ship_Flags::Friendly_stealth_invis, Mission::Parse_Object_Flags::SF_Friendly_stealth_invis, invisible, true);
15745 
15746 	// we also have to add any escort ships that were made visible
15747 	if (!invisible)
15748 	{
15749 		for (; n >= 0; n = CDR(n))
15750 		{
15751 			auto ship_entry = eval_ship(n);
15752 			if (!ship_entry || !ship_entry->shipp)
15753 				continue;
15754 
15755 			if (ship_entry->shipp->flags[Ship::Ship_Flags::Stealth] && Player_ship->team == ship_entry->shipp->team)
15756 			{
15757 				if (ship_entry->shipp->flags[Ship::Ship_Flags::Escort])
15758 					hud_add_ship_to_escort(ship_entry->shipp->objnum, 1);
15759 			}
15760 		}
15761 	}
15762 }
15763 
15764 //FUBAR
15765 //generic function to deal with subsystem flag sexps.
15766 //setit only passed for backward compatibility with older sexps.
sexp_ship_deal_with_subsystem_flag(int node,Ship::Subsystem_Flags ss_flag,bool sendit=false,bool setit=false)15767 void sexp_ship_deal_with_subsystem_flag(int node, Ship::Subsystem_Flags ss_flag, bool sendit = false, bool setit = false)
15768 {
15769 	ship *shipp;
15770 	ship_subsys *ss = nullptr;
15771 
15772 	// get ship
15773 	auto ship_entry = eval_ship(node);
15774 	if (!ship_entry || !ship_entry->shipp) {
15775 		return;
15776 	}
15777 	shipp = ship_entry->shipp;
15778 
15779 	//replace or not
15780 	// OP_SHIP_SUBSYS_TARGETABLE/UNTARGETABLE, OP_SHIP_SUBSYS_TARGETABLE and OP_TURRET_SUBSYS_TARGET_ENABLE/DISABLE
15781 	// will have already passed us this data we don't need to set it for them.
15782 	// backward compatibility hack for older sexps
15783 	if (!((ss_flag == Ship::Subsystem_Flags::Untargetable) || (ss_flag == Ship::Subsystem_Flags::No_SS_targeting)))
15784 	{
15785 		node = CDR(node);
15786 		setit = is_sexp_true(node);
15787 	}
15788 
15789 	//multiplayer packet start
15790 	if (sendit)
15791 	{
15792 		Current_sexp_network_packet.start_callback();
15793 		Current_sexp_network_packet.send_ship(shipp);
15794 		Current_sexp_network_packet.send_bool(setit);
15795 	}
15796 
15797 	//Process subsystems
15798 	while(node != -1)
15799 	{
15800 		// deal with generic subsystem names
15801 		int generic_type = get_generic_subsys(CTEXT(node));
15802 		if (generic_type) {
15803 			for (ss = GET_FIRST(&shipp->subsys_list); ss != END_OF_LIST(&shipp->subsys_list); ss = GET_NEXT(ss)) {
15804 				if (generic_type == ss->system_info->type) {
15805 					ss->flags.set(ss_flag, setit);
15806 				}
15807 			}
15808 		}
15809 		else
15810 		{
15811 			// get the subsystem
15812 			ss = ship_get_subsys(shipp, CTEXT(node));
15813 			if (ss)
15814 			{
15815 				// set the flag
15816 				ss->flags.set(ss_flag, setit);
15817 			}
15818 		}
15819 
15820 		// multiplayer send subsystem name
15821 		if (sendit)
15822 			Current_sexp_network_packet.send_string(CTEXT(node));
15823 
15824 		// next
15825 		node = CDR(node);
15826 	}
15827 
15828 	// mulitplayer end of packet
15829 	if (sendit)
15830 		Current_sexp_network_packet.end_callback();
15831 }
15832 
multi_sexp_deal_with_subsys_flag(Ship::Subsystem_Flags ss_flag)15833 void multi_sexp_deal_with_subsys_flag(Ship::Subsystem_Flags ss_flag)
15834 {
15835 	bool setit = false;
15836 	ship_subsys *ss = nullptr;
15837 	ship *shipp = nullptr;
15838 	char ss_name[MAX_NAME_LEN];
15839 
15840 	Current_sexp_network_packet.get_ship(shipp);
15841 	Current_sexp_network_packet.get_bool(setit);
15842 
15843 	while (Current_sexp_network_packet.get_string(ss_name))
15844 	{
15845 		// deal with generic subsystem names
15846 		int generic_type = get_generic_subsys(ss_name);
15847 		if (generic_type) {
15848 			for (ss = GET_FIRST(&shipp->subsys_list); ss != END_OF_LIST(&shipp->subsys_list); ss = GET_NEXT(ss)) {
15849 				if (generic_type == ss->system_info->type) {
15850 					ss->flags.set(ss_flag, setit);
15851 				}
15852 			}
15853 		}
15854 		else
15855 		{
15856 			ss = ship_get_subsys(shipp, ss_name);
15857 			if (ss)
15858 			{
15859 				// set the flag
15860 				ss->flags.set(ss_flag, setit);
15861 			}
15862 		}
15863 	}
15864 }
15865 
15866 // Goober5000
sexp_ship_tag(int n,int tag)15867 void sexp_ship_tag( int n, int tag )
15868 {
15869 	int tag_level, tag_time, ssm_index(0), ssm_team(0);
15870 	bool is_nan, is_nan_forever;
15871 
15872 	auto ship_entry = eval_ship(n);
15873 	if (!ship_entry || !ship_entry->shipp)
15874 		return;
15875 	n = CDR(n);
15876 
15877 	// if untag, then unset everything and leave
15878 	if (!tag)
15879 	{
15880 		ship_entry->shipp->tag_left = -1.0f;
15881 		ship_entry->shipp->level2_tag_left = -1.0f;
15882 		return;
15883 	}
15884 
15885 	// get the tag level and time
15886 	eval_nums(n, is_nan, is_nan_forever, tag_level, tag_time);
15887 	if (is_nan || is_nan_forever)
15888 		return;
15889 
15890 	// get SSM info if needed
15891 	vec3d start;
15892 	if (tag_level == 3)
15893 	{
15894 		if (n < 0)
15895 			return;
15896 		ssm_index = ssm_info_lookup(CTEXT(n));
15897 		if (ssm_index < 0)
15898 			return;
15899 		n = CDR(n);
15900 
15901 		eval_vec3d(&start, n, is_nan, is_nan_forever);
15902 		if (is_nan || is_nan_forever)
15903 			return;
15904 
15905 		if (n >= 0)
15906 			ssm_team = iff_lookup(CTEXT(n));
15907 	}
15908 
15909 	ship_apply_tag(ship_entry->shipp, tag_level, (float)tag_time, ship_entry->objp, &start, ssm_index, ssm_team);
15910 }
15911 
15912 // sexpression to toggle invulnerability flag of ships.
sexp_ships_invulnerable(int n,bool invulnerable)15913 void sexp_ships_invulnerable( int n, bool invulnerable )
15914 {
15915 	sexp_deal_with_ship_flag(n, true, Object::Object_Flags::Invulnerable, Ship::Ship_Flags::NUM_VALUES, Mission::Parse_Object_Flags::OF_Invulnerable, invulnerable);
15916 }
15917 
sexp_ships_bomb_targetable(int n,bool targetable)15918 void sexp_ships_bomb_targetable(int n, bool targetable)
15919 {
15920 	sexp_deal_with_ship_flag(n, true, Object::Object_Flags::Targetable_as_bomb, Ship::Ship_Flags::NUM_VALUES, Mission::Parse_Object_Flags::OF_Targetable_as_bomb, targetable, true);
15921 }
15922 
15923 // Goober5000
sexp_ship_guardian_threshold(int node)15924 void sexp_ship_guardian_threshold(int node)
15925 {
15926 	int threshold, n = node;
15927 	bool is_nan, is_nan_forever;
15928 
15929 	threshold = eval_num(n, is_nan, is_nan_forever);
15930 	if (is_nan || is_nan_forever)
15931 		return;
15932 	n = CDR(n);
15933 
15934 	// for all ships
15935 	for ( ; n != -1; n = CDR(n) ) {
15936 		auto ship_entry = eval_ship(n);
15937 		if (!ship_entry || !ship_entry->shipp) {
15938 			continue;
15939 		}
15940 
15941 		ship_entry->shipp->ship_guardian_threshold = threshold;
15942 	}
15943 }
15944 
15945 // Goober5000
sexp_ship_subsys_guardian_threshold(int node)15946 void sexp_ship_subsys_guardian_threshold(int node)
15947 {
15948 	int threshold, n = node;
15949 	bool is_nan, is_nan_forever;
15950 	ship_subsys *ss;
15951 
15952 	threshold = eval_num(n, is_nan, is_nan_forever);
15953 	if (is_nan || is_nan_forever)
15954 		return;
15955 	n = CDR(n);
15956 
15957 	auto ship_entry = eval_ship(n);
15958 	if (!ship_entry || !ship_entry->shipp)
15959 		return;
15960 	n = CDR(n);
15961 
15962 	// for all subsystems
15963 	for ( ; n != -1; n = CDR(n) ) {
15964 		// check for HULL
15965 		auto subsys_name = CTEXT(n);
15966 		if (!strcmp(subsys_name, SEXP_HULL_STRING)) {
15967 			ship_entry->shipp->ship_guardian_threshold = threshold;
15968 			continue;
15969 		}
15970 
15971 		int generic_type = get_generic_subsys(subsys_name);
15972 		if (generic_type) {
15973 			// search through all subsystems
15974 			for (ss = GET_FIRST(&ship_entry->shipp->subsys_list); ss != END_OF_LIST(&ship_entry->shipp->subsys_list); ss = GET_NEXT(ss)) {
15975 				if (generic_type == ss->system_info->type) {
15976 					ss->subsys_guardian_threshold = threshold;
15977 				}
15978 			}
15979 		}
15980 		else {
15981 			ss = ship_get_subsys(ship_entry->shipp, subsys_name);
15982 			if (ss) {
15983 				ss->subsys_guardian_threshold = threshold;
15984 			} else if (ship_class_unchanged(ship_entry)) {
15985 				Warning(LOCATION, "Invalid subsystem passed to ship-subsys-guardian-threshold: %s does not have a %s subsystem", ship_entry->name, subsys_name);
15986 			}
15987 		}
15988 	}
15989 }
15990 
15991 // sexpression to toggle KEEP ALIVE flag of ship object
sexp_ships_guardian(int n,bool guardian)15992 void sexp_ships_guardian( int n, bool guardian )
15993 {
15994 	for ( ; n != -1; n = CDR(n) )
15995 	{
15996 		auto ship_entry = eval_ship(n);
15997 		if (!ship_entry || ship_entry->status == ShipStatus::EXITED)
15998 			continue;
15999 
16000 		if (ship_entry->shipp) {
16001 			ship_entry->shipp->ship_guardian_threshold = guardian ? SHIP_GUARDIAN_THRESHOLD_DEFAULT : 0;
16002 		} else {
16003 			ship_entry->p_objp->flags.set(Mission::Parse_Object_Flags::SF_Guardian, guardian);
16004 		}
16005 	}
16006 }
16007 
sexp_ship_create(int n)16008 void sexp_ship_create(int n)
16009 {
16010 	int new_ship_class, angle_count, team = -1;
16011 	vec3d new_ship_pos;
16012 	angles new_ship_ang;
16013 	matrix new_ship_ori;
16014 	bool is_nan, is_nan_forever;
16015 
16016 	Assert( n >= 0 );
16017 
16018 	// get ship name
16019 	auto new_ship_name = CTEXT(n);
16020 	n = CDR(n);
16021 
16022 	// none means don't specify it
16023 	// if ship with this name already exists, ship_create will respond appropriately
16024 	if (!stricmp(new_ship_name, SEXP_NONE_STRING))
16025 		new_ship_name = nullptr;
16026 
16027 	//Get ship class
16028 	new_ship_class = ship_info_lookup(CTEXT(n));
16029 	if (new_ship_class < 0)
16030 	{
16031 		Warning(LOCATION, "Invalid ship class passed to ship-create; ship type '%s' does not exist", CTEXT(n));
16032 		return;
16033 	}
16034 	n = CDR(n);
16035 
16036 	eval_vec3d(&new_ship_pos, n, is_nan, is_nan_forever);
16037 	if (is_nan || is_nan_forever)
16038 		return;
16039 
16040 	angle_count = eval_angles(&new_ship_ang, n, is_nan, is_nan_forever);
16041 	if (is_nan || is_nan_forever)
16042 		return;
16043 
16044 	//This is a costly function, so only do it if needed
16045 	if (angle_count > 0)
16046 		vm_angles_2_matrix(&new_ship_ori, &new_ship_ang);
16047 	else
16048 		new_ship_ori = vmd_identity_matrix;
16049 
16050 	if (n >= 0)
16051 		team = iff_lookup(CTEXT(n));
16052 
16053 	int objnum = ship_create(&new_ship_ori, &new_ship_pos, new_ship_class, new_ship_name);
16054 	Assert(objnum != -1);
16055 
16056 	// do some initialization that is usually handled by parse_create_object_sub
16057 	int shipnum = Objects[objnum].instance;
16058 	ship *shipp = &Ships[shipnum];
16059 	ship_info *sip = &Ship_info[shipp->ship_info_index];
16060 
16061 	if (team >= 0)
16062 		shipp->team = team;
16063 
16064 	// note: model_page_in_textures was called via the sexp preloader
16065 
16066 	ship_set_warp_effects(&Objects[objnum]);
16067 
16068 	// if this name has a hash, create a default display name
16069 	if (get_pointer_to_first_hash_symbol(shipp->ship_name))
16070 	{
16071 		shipp->display_name = shipp->ship_name;
16072 		end_string_at_first_hash_symbol(shipp->display_name);
16073 		shipp->flags.set(Ship::Ship_Flags::Has_display_name);
16074 	}
16075 
16076 	if (sip->flags[Ship::Info_Flags::Intrinsic_no_shields])
16077 		Objects[objnum].flags.set(Object::Object_Flags::No_shields);
16078 
16079 	mission_log_add_entry(LOG_SHIP_ARRIVED, shipp->ship_name, nullptr);
16080 
16081 	if (Script_system.IsActiveAction(CHA_ONSHIPARRIVE)) {
16082 		Script_system.SetHookObjects(2, "Ship", &Objects[objnum], "Parent", nullptr);
16083 		Script_system.RunCondition(CHA_ONSHIPARRIVE, &Objects[objnum]);
16084 		Script_system.RemHookVars({"Ship", "Parent"});
16085 	}
16086 }
16087 
16088 // Goober5000
sexp_weapon_create(int n)16089 void sexp_weapon_create(int n)
16090 {
16091 	int weapon_class, parent_objnum, target_objnum, weapon_objnum, angle_count, is_locked;
16092 	ship_subsys *targeted_ss;
16093 	vec3d weapon_pos;
16094 	angles weapon_angles;
16095 	matrix weapon_orient;
16096 	bool is_nan, is_nan_forever;
16097 
16098 	parent_objnum = -1;
16099 	auto parent = eval_ship(n);
16100 	if (parent)
16101 	{
16102 		if (parent->shipp)
16103 			parent_objnum = parent->shipp->objnum;
16104 		else
16105 			return;		// parent ship isn't present
16106 	}
16107 	else if (stricmp(CTEXT(n), SEXP_NONE_STRING) != 0)
16108 	{
16109 		Warning(LOCATION, "A nonexistent ship %s was specified as a parent for weapon-create!", CTEXT(n));
16110 		return;
16111 	}
16112 	n = CDR(n);
16113 
16114 	weapon_class = weapon_info_lookup(CTEXT(n));
16115 	if (weapon_class < 0)
16116 	{
16117 		Warning(LOCATION, "Invalid weapon class passed to weapon-create; weapon type '%s' does not exist", CTEXT(n));
16118 		return;
16119 	}
16120 	n = CDR(n);
16121 
16122 	eval_vec3d(&weapon_pos, n, is_nan, is_nan_forever);
16123 	if (is_nan || is_nan_forever)
16124 		return;
16125 
16126 	angle_count = eval_angles(&weapon_angles, n, is_nan, is_nan_forever);
16127 	if (is_nan || is_nan_forever)
16128 		return;
16129 
16130 	// This is a costly function, so only do it if needed
16131 	if (angle_count > 0)
16132 		vm_angles_2_matrix(&weapon_orient, &weapon_angles);
16133 	else
16134 		weapon_orient = vmd_identity_matrix;
16135 
16136 	target_objnum = -1;
16137 	if (n >= 0)
16138 	{
16139 		auto ship_entry = eval_ship(n);
16140 		if (ship_entry && ship_entry->shipp)
16141 			target_objnum = ship_entry->shipp->objnum;
16142 
16143 		n = CDR(n);
16144 	}
16145 
16146 	targeted_ss = nullptr;
16147 	if (n >= 0)
16148 	{
16149 		if (target_objnum >= 0)
16150 			targeted_ss = ship_get_subsys(&Ships[Objects[target_objnum].instance], CTEXT(n));
16151 
16152 		n = CDR(n);
16153 	}
16154 
16155 	is_locked = (target_objnum >= 0) ? 1 : 0;	// assume full lock; this lets lasers track if people want them to
16156 
16157 	// create the weapon
16158 	weapon_objnum = weapon_create(&weapon_pos, &weapon_orient, weapon_class, parent_objnum, -1, is_locked);
16159 
16160 	// maybe make the weapon track its target
16161 	if (is_locked)
16162 		weapon_set_tracking_info(weapon_objnum, parent_objnum, target_objnum, is_locked, targeted_ss);
16163 }
16164 
16165 // make ship vanish without a trace (and what it's docked to)
sexp_ship_vanish(int n)16166 void sexp_ship_vanish(int n)
16167 {
16168 	for ( ; n != -1; n = CDR(n) )
16169 	{
16170 		auto ship_entry = eval_ship(n);
16171 		if (ship_entry && ship_entry->status == ShipStatus::PRESENT)
16172 			ship_actually_depart(ship_entry->objp->instance, SHIP_VANISHED);
16173 	}
16174 }
16175 
sexp_destroy_instantly(int n)16176 void sexp_destroy_instantly(int n)
16177 {
16178 	if (MULTIPLAYER_MASTER)
16179 		Current_sexp_network_packet.start_callback();
16180 
16181 	for ( ; n >= 0; n = CDR(n) )
16182 	{
16183 		auto ship_entry = eval_ship(n);
16184 		if (!ship_entry || ship_entry->status != ShipStatus::PRESENT)
16185 			continue;
16186 
16187 		// if it's the player don't destroy
16188 		if (!get_player_from_ship_node(n))
16189 		{
16190 			// multiplayer callback
16191 			if (MULTIPLAYER_MASTER)
16192 				Current_sexp_network_packet.send_ship(ship_entry->shipp);
16193 
16194 			ship_destroy_instantly(ship_entry->objp);
16195 		}
16196 	}
16197 
16198 	if (MULTIPLAYER_MASTER)
16199 		Current_sexp_network_packet.end_callback();
16200 }
16201 
multi_sexp_destroy_instantly()16202 void multi_sexp_destroy_instantly()
16203 {
16204 	ship *shipp;
16205 
16206 	// destroy ships
16207 	while (Current_sexp_network_packet.get_ship(shipp))
16208 		ship_destroy_instantly(&Objects[shipp->objnum]);
16209 }
16210 
sexp_shields_off(int n,bool shields_off)16211 void sexp_shields_off(int n, bool shields_off ) //-Sesquipedalian
16212 {
16213 	sexp_deal_with_ship_flag(n, true, Object::Object_Flags::No_shields, Ship::Ship_Flags::NUM_VALUES, Mission::Parse_Object_Flags::OF_No_shields, shields_off, true);
16214 }
16215 
16216 // Goober5000
sexp_ingame_ship_kamikaze(ship * shipp,int kdamage)16217 void sexp_ingame_ship_kamikaze(ship *shipp, int kdamage)
16218 {
16219 	Assertion(shipp, "Invalid ship pointer passed to sexp_ingame_ship_kamikaze.\n");
16220 	Assertion(kdamage >= 0, "Invalid value passed to sexp_ingame_ship_kamikaze. Kamikaze damage must be >= 0, is %i.\n", kdamage);
16221 
16222 	ai_info *aip = &Ai_info[shipp->ai_index];
16223 
16224 	aip->ai_flags.set(AI::AI_Flags::Kamikaze, kdamage > 0);
16225 	aip->kamikaze_damage = kdamage;
16226 
16227 }
16228 
16229 // Goober5000
sexp_parse_ship_kamikaze(p_object * parse_obj,int kdamage)16230 void sexp_parse_ship_kamikaze(p_object *parse_obj, int kdamage)
16231 {
16232 	Assert(parse_obj);
16233 
16234 	parse_obj->flags.set(Mission::Parse_Object_Flags::AIF_Kamikaze, kdamage > 0);
16235 	if (kdamage > 0)
16236 	{
16237 		parse_obj->kamikaze_damage = kdamage;
16238 	}
16239 	else
16240 	{
16241 		parse_obj->kamikaze_damage = 0;
16242 	}
16243 }
16244 
16245 // Goober5000 - redone, added wing stuff
sexp_kamikaze(int n,int kamikaze)16246 void sexp_kamikaze(int n, int kamikaze)
16247 {
16248 	int kdamage;
16249 	bool is_nan, is_nan_forever;
16250 
16251 	kdamage = 0;
16252 	if (kamikaze)
16253 	{
16254 		kdamage = eval_num(n, is_nan, is_nan_forever);
16255 		n = CDR(n);
16256 
16257 		if (is_nan || is_nan_forever)
16258 			return;
16259 	}
16260 
16261 	for ( ; n != -1; n = CDR(n) )
16262 	{
16263 		object_ship_wing_point_team oswpt;
16264 		eval_object_ship_wing_point_team(&oswpt, n);
16265 
16266 		switch (oswpt.type)
16267 		{
16268 			// change ingame ship
16269 			case OSWPT_TYPE_SHIP:
16270 			{
16271 				sexp_ingame_ship_kamikaze(oswpt.ship_entry->shipp, kdamage);
16272 				break;
16273 			}
16274 
16275 			// change ship yet to arrive
16276 			case OSWPT_TYPE_PARSE_OBJECT:
16277 			{
16278 				sexp_parse_ship_kamikaze(oswpt.ship_entry->p_objp, kdamage);
16279 				break;
16280 			}
16281 
16282 			// change wing (we must set the flags for all ships present as well as all ships yet to arrive)
16283 			case OSWPT_TYPE_WING:
16284 			case OSWPT_TYPE_WING_NOT_PRESENT:
16285 			{
16286 				// current ships
16287 				for (int i = 0; i < oswpt.wingp->current_count; i++)
16288 					sexp_ingame_ship_kamikaze(&Ships[oswpt.wingp->ship_index[i]], kdamage);
16289 
16290 				// ships yet to arrive
16291 				for (p_object *p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp))
16292 				{
16293 					if (p_objp->wingnum == WING_INDEX(oswpt.wingp))
16294 						sexp_parse_ship_kamikaze(p_objp, kdamage);
16295 				}
16296 				break;
16297 			}
16298 		}
16299 	}
16300 }
16301 
16302 // Goober5000
sexp_ingame_ship_alt_name_or_callsign(ship * shipp,int alt_index,bool alt_name)16303 void sexp_ingame_ship_alt_name_or_callsign(ship *shipp, int alt_index, bool alt_name)
16304 {
16305 	// we might be clearing it
16306 	if (alt_index < 0)
16307 	{
16308 		if (alt_name)
16309 			shipp->alt_type_index = -1;
16310 		else
16311 			shipp->callsign_index = -1;
16312 		return;
16313 	}
16314 
16315 	if (alt_name)
16316 	{
16317 		// see if this is actually the ship class
16318 		if (!stricmp(Ship_info[shipp->ship_info_index].name, Mission_alt_types[alt_index]))
16319 		{
16320 			shipp->alt_type_index = -1;
16321 			return;
16322 		}
16323 	}
16324 
16325 	if (alt_name)
16326 		shipp->alt_type_index = alt_index;
16327 	else
16328 		shipp->callsign_index = alt_index;
16329 }
16330 
16331 // Goober5000
sexp_parse_ship_alt_name_or_callsign(p_object * parse_obj,int alt_index,bool alt_name)16332 void sexp_parse_ship_alt_name_or_callsign(p_object *parse_obj, int alt_index, bool alt_name)
16333 {
16334 	// we might be clearing it
16335 	if (alt_index < 0)
16336 	{
16337 		if (alt_name)
16338 			parse_obj->alt_type_index = -1;
16339 		else
16340 			parse_obj->callsign_index = -1;
16341 		return;
16342 	}
16343 
16344 	if (alt_name)
16345 	{
16346 		// see if this is actually the ship class
16347 		if (!stricmp(Ship_class_names[parse_obj->ship_class], Mission_alt_types[alt_index]))
16348 		{
16349 			parse_obj->alt_type_index = -1;
16350 			return;
16351 		}
16352 	}
16353 
16354 	if (alt_name)
16355 		parse_obj->alt_type_index = alt_index;
16356 	else
16357 		parse_obj->callsign_index = alt_index;
16358 }
16359 
16360 // Goober5000
sexp_ship_change_alt_name_or_callsign(int node,bool alt_name)16361 void sexp_ship_change_alt_name_or_callsign(int node, bool alt_name)
16362 {
16363 	int n = node, new_index;
16364 
16365 	// get the string
16366 	auto new_string = CTEXT(n);
16367 	n = CDR(n);
16368 
16369 	// and its index
16370 	if (!*new_string || !stricmp(new_string, SEXP_ANY_STRING))
16371 	{
16372 		new_index = -1;
16373 	}
16374 	else
16375 	{
16376 		if (alt_name)
16377 		{
16378 			new_index = mission_parse_lookup_alt(new_string);
16379 			if (new_index < 0)
16380 				new_index = mission_parse_add_alt(new_string);
16381 		}
16382 		else
16383 		{
16384 			new_index = mission_parse_lookup_callsign(new_string);
16385 			if (new_index < 0)
16386 				new_index = mission_parse_add_callsign(new_string);
16387 		}
16388 	}
16389 
16390 	// packets for multi
16391 	if (MULTIPLAYER_MASTER)
16392 	{
16393 		Current_sexp_network_packet.start_callback();
16394 		Current_sexp_network_packet.send_string(new_string);
16395 	}
16396 
16397 	for ( ; n != -1; n = CDR(n) )
16398 	{
16399 		object_ship_wing_point_team oswpt;
16400 		eval_object_ship_wing_point_team(&oswpt, n);
16401 
16402 		if (MULTIPLAYER_MASTER)
16403 			Current_sexp_network_packet.send_int(oswpt.type);
16404 
16405 		switch (oswpt.type)
16406 		{
16407 			// change ingame ship
16408 			case OSWPT_TYPE_SHIP:
16409 			{
16410 				sexp_ingame_ship_alt_name_or_callsign(oswpt.ship_entry->shipp, new_index, alt_name);
16411 				if (MULTIPLAYER_MASTER)
16412 					Current_sexp_network_packet.send_ship(oswpt.ship_entry->shipp);
16413 				break;
16414 			}
16415 
16416 			// change ship yet to arrive
16417 			case OSWPT_TYPE_PARSE_OBJECT:
16418 			{
16419 				sexp_parse_ship_alt_name_or_callsign(oswpt.ship_entry->p_objp, new_index, alt_name);
16420 				if (MULTIPLAYER_MASTER)
16421 					Current_sexp_network_packet.send_parse_object(oswpt.ship_entry->p_objp);
16422 				break;
16423 			}
16424 
16425 			// change wing (we must set the flags for all ships present as well as all ships yet to arrive)
16426 			case OSWPT_TYPE_WING:
16427 			case OSWPT_TYPE_WING_NOT_PRESENT:
16428 			{
16429 				// current ships
16430 				for (int i = 0; i < oswpt.wingp->current_count; i++)
16431 					sexp_ingame_ship_alt_name_or_callsign(&Ships[oswpt.wingp->ship_index[i]], new_index, alt_name);
16432 
16433 				// ships yet to arrive
16434 				for (p_object *p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp))
16435 				{
16436 					if (p_objp->wingnum == WING_INDEX(oswpt.wingp))
16437 						sexp_parse_ship_alt_name_or_callsign(p_objp, new_index, alt_name);
16438 				}
16439 
16440 				if (MULTIPLAYER_MASTER)
16441 					Current_sexp_network_packet.send_wing(oswpt.wingp);
16442 				break;
16443 			}
16444 		}
16445 	}
16446 
16447 	if (MULTIPLAYER_MASTER)
16448 		Current_sexp_network_packet.end_callback();
16449 }
16450 
multi_sexp_ship_change_alt_name_or_callsign(bool alt_name)16451 void multi_sexp_ship_change_alt_name_or_callsign(bool alt_name)
16452 {
16453 	char new_string[TOKEN_LENGTH];
16454 	int type, new_index;
16455 
16456 	Current_sexp_network_packet.get_string(new_string);
16457 
16458 	if (!*new_string || !stricmp(new_string, SEXP_ANY_STRING))
16459 	{
16460 		new_index = -1;
16461 	}
16462 	else
16463 	{
16464 		if (alt_name)
16465 		{
16466 			new_index = mission_parse_lookup_alt(new_string);
16467 			if (new_index < 0)
16468 				new_index = mission_parse_add_alt(new_string);
16469 		}
16470 		else
16471 		{
16472 			new_index = mission_parse_lookup_callsign(new_string);
16473 			if (new_index < 0)
16474 				new_index = mission_parse_add_callsign(new_string);
16475 		}
16476 	}
16477 
16478 	while (Current_sexp_network_packet.get_int(type))
16479 	{
16480 		switch (type)
16481 		{
16482 			case OSWPT_TYPE_SHIP:
16483 			{
16484 				ship *shipp;
16485 				if (Current_sexp_network_packet.get_ship(shipp))
16486 					sexp_ingame_ship_alt_name_or_callsign(shipp, new_index, alt_name);
16487 				break;
16488 			}
16489 
16490 			case OSWPT_TYPE_PARSE_OBJECT:
16491 			{
16492 				p_object *pobjp;
16493 				if (Current_sexp_network_packet.get_parse_object(pobjp))
16494 					sexp_parse_ship_alt_name_or_callsign(pobjp, new_index, alt_name);
16495 				break;
16496 			}
16497 
16498 			case OSWPT_TYPE_WING:
16499 			case OSWPT_TYPE_WING_NOT_PRESENT:
16500 			{
16501 				wing *wingp;
16502 				if (Current_sexp_network_packet.get_wing(wingp))
16503 				{
16504 					// current ships
16505 					for (int i = 0; i < wingp->current_count; i++)
16506 						sexp_ingame_ship_alt_name_or_callsign(&Ships[wingp->ship_index[i]], new_index, alt_name);
16507 
16508 					// ships yet to arrive
16509 					for (p_object *p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp))
16510 					{
16511 						if (p_objp->wingnum == WING_INDEX(wingp))
16512 							sexp_parse_ship_alt_name_or_callsign(p_objp, new_index, alt_name);
16513 					}
16514 				}
16515 				break;
16516 			}
16517 		}
16518 	}
16519 }
16520 
16521 // Goober5000
sexp_set_death_message(int n)16522 void sexp_set_death_message(int n)
16523 {
16524 	int i;
16525 
16526 	// we'll suppose it's the string for now
16527 	Player->death_message = CTEXT(n);
16528 
16529 	// but use an actual message if one exists
16530 	for (i=0; i<Num_messages; i++)
16531 	{
16532 		if (!stricmp(Messages[i].name, Player->death_message.c_str()))
16533 		{
16534 			Player->death_message = Messages[i].message;
16535 			break;
16536 		}
16537 	}
16538 
16539 	// apply localization
16540 	lcl_replace_stuff(Player->death_message);
16541 
16542 	sexp_replace_variable_names_with_values(Player->death_message);
16543 }
16544 
sexp_key_pressed(int node)16545 int sexp_key_pressed(int node)
16546 {
16547 	int z, t;
16548 	bool is_nan, is_nan_forever;
16549 
16550 	Assert(node != -1);
16551 	z = translate_key_to_index(CTEXT(node), false);
16552 	if (z < 0){
16553 		return SEXP_FALSE;
16554 	}
16555 
16556 	if (!Control_config[z].used){
16557 		return SEXP_FALSE;
16558 	}
16559 
16560 	if (CDR(node) < 0){
16561 		return SEXP_TRUE;
16562 	}
16563 
16564 	t = eval_num(CDR(node), is_nan, is_nan_forever);
16565 	if (is_nan)
16566 		return SEXP_FALSE;
16567 	if (is_nan_forever)
16568 		return SEXP_KNOWN_FALSE;
16569 
16570 	return timestamp_has_time_elapsed(Control_config[z].used, t * 1000);
16571 }
16572 
sexp_key_reset(int node)16573 void sexp_key_reset(int node)
16574 {
16575 	int n, z;
16576 
16577 	for (n = node; n != -1; n = CDR(n))
16578 	{
16579 		z = translate_key_to_index(CTEXT(n), false);
16580 		if (z >= 0)
16581 			Control_config[z].used = 0;
16582 	}
16583 }
16584 
sexp_ignore_key(int node)16585 void sexp_ignore_key(int node)
16586 {
16587 	int ignore_count, ignored_key;
16588 	bool is_nan, is_nan_forever;
16589 
16590 	ignore_count = eval_num(node, is_nan, is_nan_forever);
16591 	if (is_nan || is_nan_forever)
16592 		return;
16593 
16594 	Current_sexp_network_packet.start_callback();
16595 	Current_sexp_network_packet.send_int(ignore_count);
16596 
16597 	node = CDR(node);
16598 	while (node >= 0) {
16599 		// get the key
16600 		ignored_key = translate_key_to_index(CTEXT(node), false);
16601 
16602 		if (ignored_key >= 0) {
16603 			Ignored_keys[ignored_key] = ignore_count;
16604 		}
16605 
16606 		Current_sexp_network_packet.send_int(ignored_key);
16607 
16608 		node = CDR(node);
16609 	}
16610 
16611 	Current_sexp_network_packet.end_callback();
16612 }
16613 
multi_sexp_ignore_key()16614 void multi_sexp_ignore_key()
16615 {
16616 	int ignored_key, ignore_count;
16617 
16618 	Current_sexp_network_packet.get_int(ignore_count);
16619 
16620 	while (Current_sexp_network_packet.get_int(ignored_key)) {
16621 		Ignored_keys[ignored_key] = ignore_count;
16622 	}
16623 }
16624 
sexp_targeted(int node)16625 int sexp_targeted(int node)
16626 {
16627 	int z;
16628 	bool is_nan, is_nan_forever;
16629 	ship_subsys *ptr;
16630 
16631 	auto ship_entry = eval_ship(node);
16632 	if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT)
16633 		return SEXP_FALSE;
16634 	if (ship_entry->status == ShipStatus::EXITED)
16635 		return SEXP_KNOWN_FALSE;
16636 
16637 	if (!Player_ai || (ship_entry->shipp->objnum != Players_target)){
16638 		return SEXP_FALSE;
16639 	}
16640 
16641 	if (CDR(node) >= 0) {
16642 		z = eval_num(CDR(node), is_nan, is_nan_forever) * 1000;
16643 		if (is_nan)
16644 			return SEXP_FALSE;
16645 		if (is_nan_forever)
16646 			return SEXP_KNOWN_FALSE;
16647 
16648 		if (!timestamp_has_time_elapsed(Players_target_timestamp, z)){
16649 			return SEXP_FALSE;
16650 		}
16651 
16652 		if (CDR(CDR(node)) >= 0) {
16653 			ptr = Players_targeted_subsys;
16654 			if (!ptr || subsystem_stricmp(ptr->system_info->subobj_name, CTEXT(CDR(CDR(node))))){
16655 				return SEXP_FALSE;
16656 			}
16657 		}
16658 	}
16659 
16660 	return SEXP_TRUE;
16661 }
16662 
sexp_node_targeted(int node)16663 int sexp_node_targeted(int node)
16664 {
16665 	int z;
16666 	bool is_nan, is_nan_forever;
16667 
16668 	CJumpNode *jnp = jumpnode_get_by_name(CTEXT(node));
16669 
16670 	if (jnp==nullptr || !Player_ai || (jnp->GetSCPObjectNumber() != Players_target)){
16671 		return SEXP_FALSE;
16672 	}
16673 
16674 	if (CDR(node) >= 0) {
16675 		z = eval_num(CDR(node), is_nan, is_nan_forever) * 1000;
16676 		if (is_nan)
16677 			return SEXP_FALSE;
16678 		if (is_nan_forever)
16679 			return SEXP_KNOWN_FALSE;
16680 
16681 		if (!timestamp_has_time_elapsed(Players_target_timestamp, z)){
16682 			return SEXP_FALSE;
16683 		}
16684 	}
16685 
16686 	return SEXP_TRUE;
16687 }
16688 
sexp_speed(int node)16689 int sexp_speed(int node)
16690 {
16691 	int z;
16692 	bool is_nan, is_nan_forever;
16693 
16694 	if (Training_context & TRAINING_CONTEXT_SPEED) {
16695 		if (Training_context_speed_set) {
16696 			z = eval_num(node, is_nan, is_nan_forever) * 1000;
16697 			if (is_nan)
16698 				return SEXP_FALSE;
16699 			if (is_nan_forever)
16700 				return SEXP_KNOWN_FALSE;
16701 
16702 			if (timestamp_has_time_elapsed(Training_context_speed_timestamp, z)){
16703 				return SEXP_KNOWN_TRUE;
16704 			}
16705 		}
16706 	}
16707 
16708 	return SEXP_FALSE;
16709 }
16710 
sexp_get_throttle_speed(int node)16711 int sexp_get_throttle_speed(int node)
16712 {
16713 	auto the_player = get_player_from_ship_node(node);
16714 
16715 	if (the_player) {
16716 		float max_speed = Ship_info[Ships[Objects[the_player->objnum].instance].ship_info_index].max_speed;
16717 		return (int)(the_player->ci.forward_cruise_percent / 100.0f * max_speed);
16718 	}
16719 
16720 	return 0;
16721 }
16722 
16723 // CommanderDJ
sexp_set_player_throttle_speed(int node)16724 void sexp_set_player_throttle_speed(int node)
16725 {
16726 	bool is_nan, is_nan_forever;
16727 
16728 	//get and sanity check the player first
16729 	auto the_player = get_player_from_ship_node(node);
16730 
16731 	if (the_player)
16732 	{
16733 		//now the throttle percentage
16734 		node = CDR(node);
16735 		int throttle_percent = eval_num(node, is_nan, is_nan_forever);
16736 		if (is_nan || is_nan_forever)
16737 			return;
16738 		CLAMP(throttle_percent, 0, 100);
16739 
16740 		//now actually set the throttle
16741 		the_player->ci.forward_cruise_percent = (float) throttle_percent;
16742 	}
16743 }
16744 
16745 // Goober5000
sexp_weapons_depleted(int node,bool primary)16746 int sexp_weapons_depleted(int node, bool primary)
16747 {
16748 	int num_banks, num_tested_banks, num_depleted_banks;
16749 	int *ammo;
16750 
16751 	// get ship
16752 	auto ship_entry = eval_ship(node);
16753 	if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT)
16754 		return SEXP_NAN;
16755 	if (ship_entry->status == ShipStatus::EXITED)
16756 		return SEXP_NAN_FOREVER;
16757 
16758 	// get bank stuff
16759 	if (primary) {
16760 		num_banks = ship_entry->shipp->weapons.num_primary_banks;
16761 		ammo = ship_entry->shipp->weapons.primary_bank_ammo;
16762 	} else {
16763 		num_banks = ship_entry->shipp->weapons.num_secondary_banks;
16764 		ammo = ship_entry->shipp->weapons.secondary_bank_ammo;
16765 	}
16766 	num_tested_banks = 0;
16767 	num_depleted_banks = 0;
16768 
16769 	// get number of depleted banks
16770 	for (int idx=0; idx<num_banks; idx++) {
16771 		if (primary) {
16772 			// is this a ballistic bank?
16773 			if (!(Weapon_info[ship_entry->shipp->weapons.primary_bank_weapons[idx]].wi_flags[Weapon::Info_Flags::Ballistic])) {
16774 				continue;
16775 			}
16776 		}
16777 		num_tested_banks++;
16778 
16779 		// is this bank out of ammo?
16780 		if (ammo[idx] == 0)	{
16781 			num_depleted_banks++;
16782 		}
16783 	}
16784 
16785 	// are they all depleted?
16786 	return (num_depleted_banks == num_tested_banks) ? SEXP_TRUE : SEXP_FALSE;
16787 }
16788 
sexp_facing(int node)16789 int sexp_facing(int node)
16790 {
16791 	int angle;
16792 	bool is_nan, is_nan_forever;
16793 	float a1, a2;
16794 	vec3d v1, v2;
16795 
16796 	if (!Player_obj) {
16797 		return SEXP_CANT_EVAL;
16798 	}
16799 
16800 	auto ship_entry = eval_ship(node);
16801 	if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT)
16802 		return SEXP_FALSE;
16803 	if (ship_entry->status == ShipStatus::EXITED)
16804 		return SEXP_KNOWN_FALSE;
16805 	auto target_objp = ship_entry->objp;
16806 	node = CDR(node);
16807 
16808 	angle = eval_num(node, is_nan, is_nan_forever);
16809 	if (is_nan)
16810 		return SEXP_FALSE;
16811 	if (is_nan_forever)
16812 		return SEXP_KNOWN_FALSE;
16813 
16814 	v1 = Player_obj->orient.vec.fvec;
16815 	vm_vec_normalize(&v1);
16816 
16817 	vm_vec_sub(&v2, &target_objp->pos, &Player_obj->pos);
16818 	vm_vec_normalize(&v2);
16819 
16820 	a1 = vm_vec_dot(&v1, &v2);
16821 	a2 = cosf(fl_radians(angle % 360));
16822 	if (a1 >= a2){
16823 		return SEXP_TRUE;
16824 	}
16825 
16826 	return SEXP_FALSE;
16827 }
16828 
sexp_is_facing(int node)16829 int sexp_is_facing(int node)
16830 {
16831 	int angle;
16832 	bool is_nan, is_nan_forever;
16833 	float a1, a2;
16834 	vec3d v1, v2;
16835 
16836 	auto ship_entry = eval_ship(node);
16837 	if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT)
16838 		return SEXP_FALSE;
16839 	if (ship_entry->status == ShipStatus::EXITED)
16840 		return SEXP_KNOWN_FALSE;
16841 	auto origin_objp = ship_entry->objp;
16842 	node = CDR(node);
16843 
16844 	object_ship_wing_point_team oswpt;
16845 	eval_object_ship_wing_point_team(&oswpt, node);
16846 
16847 	// check if target has departed or not yet arrived
16848 	if (oswpt.type == OSWPT_TYPE_EXITED) {
16849 		return SEXP_KNOWN_FALSE;
16850 	}
16851 	if (oswpt.type == OSWPT_TYPE_PARSE_OBJECT) {
16852 		return SEXP_FALSE;
16853 	}
16854 
16855 	if (!oswpt.objp)
16856 		return SEXP_FALSE;
16857 
16858 	auto target_objp = oswpt.objp;
16859 	node = CDR(node);
16860 
16861 	angle = eval_num(node, is_nan, is_nan_forever);
16862 	if (is_nan)
16863 		return SEXP_FALSE;
16864 	if (is_nan_forever)
16865 		return SEXP_KNOWN_FALSE;
16866 	node = CDR(node);
16867 
16868 	// check optional distance argument
16869 	if (node >= 0) {
16870 		int threshold = eval_num(node, is_nan, is_nan_forever);
16871 		if (is_nan)
16872 			return SEXP_FALSE;
16873 		if (is_nan_forever)
16874 			return SEXP_KNOWN_FALSE;
16875 
16876 		if (sexp_retail_distance3(origin_objp, target_objp) > threshold) {
16877 			return SEXP_FALSE;
16878 		}
16879 	}
16880 
16881 	v1 = origin_objp->orient.vec.fvec;
16882 	vm_vec_normalize(&v1);
16883 
16884 	vm_vec_sub(&v2, &target_objp->pos, &origin_objp->pos);
16885 	vm_vec_normalize(&v2);
16886 
16887 	a1 = vm_vec_dot(&v1, &v2);
16888 	a2 = cosf(fl_radians(angle % 360));
16889 	if (a1 >= a2){
16890 		return SEXP_TRUE;
16891 	}
16892 
16893 	return SEXP_FALSE;
16894 }
16895 
16896 // is ship facing first waypoint in waypoint path
sexp_facing2(int node)16897 int sexp_facing2(int node)
16898 {
16899 	int angle;
16900 	bool is_nan, is_nan_forever;
16901 	float a1, a2;
16902 	vec3d v1, v2;
16903 
16904 	// bail if Player_obj is not good
16905 	if (!Player_obj) {
16906 		return SEXP_CANT_EVAL;
16907 	}
16908 
16909 	// get waypoint name
16910 	auto waypoint_name = CTEXT(node);
16911 
16912 	// get position of first waypoint
16913 	waypoint_list *wp_list = find_matching_waypoint_list(waypoint_name);
16914 
16915 	if (wp_list == nullptr) {
16916 		return SEXP_KNOWN_FALSE;
16917 	}
16918 
16919 	// get angle
16920 	angle = eval_num(CDR(node), is_nan, is_nan_forever);
16921 	if (is_nan)
16922 		return SEXP_FALSE;
16923 	if (is_nan_forever)
16924 		return SEXP_KNOWN_FALSE;
16925 
16926 	v1 = Player_obj->orient.vec.fvec;
16927 	vm_vec_normalize(&v1);
16928 
16929 	vm_vec_sub(&v2, wp_list->get_waypoints().front().get_pos(), &Player_obj->pos);
16930 	vm_vec_normalize(&v2);
16931 
16932 	a1 = vm_vec_dot(&v1, &v2);
16933 	a2 = cosf(fl_radians(angle % 360));
16934 	if (a1 >= a2){
16935 		return SEXP_TRUE;
16936 	}
16937 
16938 	return SEXP_FALSE;
16939 }
16940 
sexp_order(int n)16941 int sexp_order(int n)
16942 {
16943 	auto order_to = CTEXT(n);
16944 	auto order = CTEXT(CDR(n));
16945 	const char *target = nullptr;
16946 
16947 	//target
16948 	n = CDDR(n);
16949 	if (n != -1)
16950 		target = CTEXT(n);
16951 
16952 	return hud_query_order_issued(order_to, order, target);
16953 }
16954 
sexp_query_orders(int n)16955 int sexp_query_orders (int n)
16956 {
16957 	auto order_to = CTEXT(n);
16958 	auto order = CTEXT(CDR(n));
16959 	const char *target = nullptr;
16960 	const char *order_from = nullptr;
16961 	const char *special = nullptr;
16962 	int timestamp = 0;
16963 	bool is_nan, is_nan_forever;
16964 
16965 	// delay
16966 	n = CDDR(n);
16967 	if (n != -1) {
16968 		timestamp = eval_num(n, is_nan, is_nan_forever);
16969 		n = CDR(n);
16970 
16971 		if (is_nan)
16972 			return SEXP_FALSE;
16973 		if (is_nan_forever)
16974 			return SEXP_KNOWN_FALSE;
16975 	}
16976 
16977 	//target
16978 	if (n != -1) {
16979 		target = CTEXT(n);
16980 		n = CDR(n);
16981 	}
16982 
16983 	// player order comes from
16984 	if (n != -1) {
16985 		order_from = CTEXT(n);
16986 		n = CDR(n);
16987 	}
16988 
16989 	// optional special argument
16990 	if (n != -1)
16991 		special = CTEXT(n);
16992 
16993 	return hud_query_order_issued(order_to, order, target, timestamp, order_from, special);
16994 }
16995 
sexp_time_to_goal(int n)16996 int sexp_time_to_goal(int n)
16997 {
16998 	auto ship_entry = eval_ship(n);
16999 
17000 	if (!ship_entry || !ship_entry->shipp)
17001 		return SEXP_NAN;
17002 
17003 	if (ship_entry->status == NOT_YET_PRESENT)
17004 		return SEXP_CANT_EVAL;
17005 
17006 	if (ship_entry->status == EXITED)
17007 		return SEXP_NAN_FOREVER;
17008 
17009 	auto shipp = ship_entry->shipp;
17010 	int time = ship_return_seconds_to_goal(shipp);
17011 
17012 	if (time < 0) {
17013 		return SEXP_CANT_EVAL;
17014 	}
17015 
17016 	return time;
17017 }
17018 
17019 // Karajorma
sexp_reset_orders(int)17020 void sexp_reset_orders (int  /*n*/)
17021 {
17022 	Squadmsg_history.clear();
17023 }
17024 
sexp_waypoint_missed()17025 int sexp_waypoint_missed()
17026 {
17027 	if (Training_context & TRAINING_CONTEXT_FLY_PATH) {
17028 		if (Training_context_at_waypoint > Training_context_goal_waypoint){
17029 			return SEXP_TRUE;
17030 		}
17031 	}
17032 
17033 	return SEXP_FALSE;
17034 }
17035 
sexp_waypoint_twice()17036 int sexp_waypoint_twice()
17037 {
17038 	if (Training_context & TRAINING_CONTEXT_FLY_PATH) {
17039 		if (Training_context_at_waypoint < Training_context_goal_waypoint - 1){
17040 			return SEXP_TRUE;
17041 		}
17042 	}
17043 
17044 	return SEXP_FALSE;
17045 }
17046 
sexp_path_flown()17047 int sexp_path_flown()
17048 {
17049 	if (Training_context & TRAINING_CONTEXT_FLY_PATH) {
17050 		if ((uint) Training_context_goal_waypoint == Training_context_path->get_waypoints().size()){
17051 			return SEXP_TRUE;
17052 		}
17053 	}
17054 
17055 	return SEXP_FALSE;
17056 }
17057 
sexp_send_training_message(int node)17058 void sexp_send_training_message(int node)
17059 {
17060 	bool is_nan, is_nan_forever;
17061 	int n = node, count, delay, duration;
17062 
17063 	if(physics_paused){
17064 		return;
17065 	}
17066 
17067 	Assert(node >= 0);
17068 	Assert(Event_index >= 0);
17069 
17070 	auto primary_message = CTEXT(n);
17071 	n = CDR(n);
17072 
17073 	auto secondary_message = (n >= 0) ? CTEXT(n) : nullptr;
17074 	n = CDR(n);
17075 
17076 	count = eval_nums(n, is_nan, is_nan_forever, delay, duration);
17077 	if (is_nan || is_nan_forever)
17078 		return;
17079 	if (count > 0) {
17080 		delay *= 1000;
17081 	}
17082 	if (count < 2) {
17083 		duration = -1;
17084 	}
17085 
17086 	Current_sexp_network_packet.start_callback();
17087 	Current_sexp_network_packet.send_int(duration);
17088 	Current_sexp_network_packet.send_int(delay);
17089 
17090 	if ((Mission_events[Event_index].repeat_count > 1) || (secondary_message == nullptr)) {
17091 		message_training_queue(primary_message, timestamp(delay), duration);
17092 		Current_sexp_network_packet.send_string(primary_message);
17093 	} else {
17094 		message_training_queue(secondary_message, timestamp(delay), duration);
17095 		Current_sexp_network_packet.send_string(secondary_message);
17096 	}
17097 	Current_sexp_network_packet.end_callback();
17098 }
17099 
multi_sexp_send_training_message()17100 void multi_sexp_send_training_message()
17101 {
17102 	int duration, delay;
17103 	char message[TOKEN_LENGTH];
17104 
17105 	Current_sexp_network_packet.get_int(duration);
17106 	Current_sexp_network_packet.get_int(delay);
17107 	if (Current_sexp_network_packet.get_string(message)) {
17108 		message_training_queue(message, timestamp(delay), duration);
17109 	}
17110 }
17111 
sexp_gse_recharge_pct(int node,int op_num)17112 int sexp_gse_recharge_pct(int node, int op_num)
17113 {
17114 	// get the ship
17115 	auto ship_entry = eval_ship(node);
17116 	if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT)
17117 		return SEXP_NAN;
17118 	else if (ship_entry->status == ShipStatus::EXITED)
17119 		return SEXP_NAN_FOREVER;
17120 
17121 	int index;
17122 	if (op_num == OP_WEAPON_RECHARGE_PCT)
17123 		index = ship_entry->shipp->weapon_recharge_index;
17124 	else if (op_num == OP_SHIELD_RECHARGE_PCT)
17125 		index = ship_entry->shipp->shield_recharge_index;
17126 	else if (op_num == OP_ENGINE_RECHARGE_PCT)
17127 		index = ship_entry->shipp->engine_recharge_index;
17128 	else
17129 		return SEXP_NAN_FOREVER;
17130 
17131 	// recharge pct
17132 	return (int)(100.0f * Energy_levels[index]);
17133 }
17134 
17135 /*
17136 * Get a ship's power output
17137 */
sexp_get_power_output(int node)17138 int sexp_get_power_output(int node)
17139 {
17140 	auto ship_entry = eval_ship(node);
17141 
17142 	if (ship_entry == nullptr || ship_entry->status == ShipStatus::EXITED)
17143 		return SEXP_NAN_FOREVER;
17144 
17145 	if (ship_entry->status == ShipStatus::NOT_YET_PRESENT)
17146 		return SEXP_NAN;
17147 
17148 	return (int)(std::lround(Ship_info[ship_entry->shipp->ship_info_index].power_output));
17149 }
17150 
17151 
17152 /**
17153  * retrieve one ETS index from a ship
17154  */
sexp_get_ets_value(int node)17155 int sexp_get_ets_value(int node)
17156 {
17157 	auto ets_type = CTEXT(node);
17158 	node = CDR(node);
17159 
17160 	auto ship_entry = eval_ship(node);
17161 	if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT)
17162 		return SEXP_NAN;
17163 	else if (ship_entry->status == ShipStatus::EXITED)
17164 		return SEXP_NAN_FOREVER;
17165 
17166 	if (!stricmp(ets_type, "engine")) {
17167 		return ship_entry->shipp->engine_recharge_index;
17168 	} else if (!stricmp(ets_type, "shield")) {
17169 		return ship_entry->shipp->shield_recharge_index;
17170 	} else if (!stricmp(ets_type, "weapon")) {
17171 		return ship_entry->shipp->weapon_recharge_index;
17172 	} else {
17173 		return SEXP_NAN_FOREVER;
17174 	}
17175 }
17176 
17177 /**
17178  * set all ETS indexes for one or more ships
17179  */
sexp_set_ets_values(int node)17180 void sexp_set_ets_values(int node)
17181 {
17182 	bool is_nan, is_nan_forever;
17183 	int ets_idx[num_retail_ets_gauges];
17184 
17185 	//get inputs
17186 	eval_nums(node, is_nan, is_nan_forever, ets_idx[ENGINES], ets_idx[SHIELDS], ets_idx[WEAPONS]);
17187 	if (is_nan || is_nan_forever)
17188 		return;
17189 
17190 	// sanity check inputs
17191 	sanity_check_ets_inputs(ets_idx);
17192 
17193 	Current_sexp_network_packet.start_callback();
17194 
17195 	// apply ETS settings to specified ships
17196 	for ( ; node != -1; node = CDR(node)) {
17197 		auto ship_entry = eval_ship(node);
17198 		if (!ship_entry || !ship_entry->shipp)
17199 			return;
17200 
17201 		if (validate_ship_ets_indxes(ship_entry->objp->instance, ets_idx)) {
17202 			ship_entry->shipp->engine_recharge_index = ets_idx[ENGINES];
17203 			ship_entry->shipp->shield_recharge_index = ets_idx[SHIELDS];
17204 			ship_entry->shipp->weapon_recharge_index = ets_idx[WEAPONS];
17205 
17206 			Current_sexp_network_packet.send_ship(ship_entry->shipp);
17207 			Current_sexp_network_packet.send_int(ets_idx[ENGINES]);
17208 			Current_sexp_network_packet.send_int(ets_idx[SHIELDS]);
17209 			Current_sexp_network_packet.send_int(ets_idx[WEAPONS]);
17210 		}
17211 	}
17212 	Current_sexp_network_packet.end_callback();
17213 }
17214 
multi_sexp_set_ets_values()17215 void multi_sexp_set_ets_values()
17216 {
17217 	int sindex;
17218 	int ets_idx[num_retail_ets_gauges];
17219 
17220 	while (Current_sexp_network_packet.get_ship(sindex)) {
17221 		Current_sexp_network_packet.get_int(ets_idx[ENGINES]);
17222 		Current_sexp_network_packet.get_int(ets_idx[SHIELDS]);
17223 		Current_sexp_network_packet.get_int(ets_idx[WEAPONS]);
17224 
17225 		Ships[sindex].engine_recharge_index = ets_idx[ENGINES];
17226 		Ships[sindex].shield_recharge_index = ets_idx[SHIELDS];
17227 		Ships[sindex].weapon_recharge_index = ets_idx[WEAPONS];
17228 	}
17229 }
17230 
sexp_shield_quad_low(int node)17231 int sexp_shield_quad_low(int node)
17232 {
17233 	float max_quad, check;
17234 	bool is_nan, is_nan_forever;
17235 
17236 	// get the ship
17237 	auto ship_entry = eval_ship(node);
17238 	if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT)
17239 		return SEXP_NAN;
17240 	if (ship_entry->status == ShipStatus::EXITED)
17241 		return SEXP_NAN_FOREVER;
17242 
17243 	auto sip = &Ship_info[ship_entry->shipp->ship_info_index];
17244 	if(!(sip->is_small_ship())){
17245 		return SEXP_KNOWN_FALSE;
17246 	}
17247 	max_quad = shield_get_max_quad(ship_entry->objp);
17248 
17249 	// shield pct
17250 	check = (float)eval_num(CDR(node), is_nan, is_nan_forever);
17251 	if (is_nan)
17252 		return SEXP_FALSE;
17253 	if (is_nan_forever)
17254 		return SEXP_KNOWN_FALSE;
17255 
17256 	// check his quadrants
17257 	for (int idx = 0; idx < ship_entry->objp->n_quadrants; ++idx) {
17258 		if (((ship_entry->objp->shield_quadrant[idx] / max_quad) * 100.0f) <= check) {
17259 			return SEXP_TRUE;
17260 		}
17261 	}
17262 
17263 	// all good
17264 	return SEXP_FALSE;
17265 }
17266 
sexp_get_ammo_sub(ship_weapon * swp,int bank_to_check,bool primary,bool do_percent)17267 int sexp_get_ammo_sub(ship_weapon *swp, int bank_to_check, bool primary, bool do_percent)
17268 {
17269 	int sum = 0;
17270 	int total = 0;
17271 
17272 	int num_banks, *ammo, *start_ammo, *weapons;
17273 	if (primary)
17274 	{
17275 		num_banks = swp->num_primary_banks;
17276 		ammo = swp->primary_bank_ammo;
17277 		start_ammo = swp->primary_bank_start_ammo;
17278 		weapons = swp->primary_bank_weapons;
17279 	}
17280 	else
17281 	{
17282 		num_banks = swp->num_secondary_banks;
17283 		ammo = swp->secondary_bank_ammo;
17284 		start_ammo = swp->secondary_bank_start_ammo;
17285 		weapons = swp->secondary_bank_weapons;
17286 	}
17287 
17288 	for (int i = 0; i < num_banks; ++i)
17289 	{
17290 		// if this is primary, skip non-ballistic weapons
17291 		if (primary && !Weapon_info[weapons[i]].wi_flags[Weapon::Info_Flags::Ballistic])
17292 			continue;
17293 
17294 		// if we are checking a specific bank, and this isn't it, skip it
17295 		if (bank_to_check >= 0 && bank_to_check < num_banks && i != bank_to_check)
17296 			continue;
17297 
17298 		sum += ammo[i];
17299 		total += start_ammo[i];
17300 	}
17301 
17302 	if (do_percent)
17303 	{
17304 		if (total == 0)
17305 			return 100;
17306 
17307 		return (int)(((float)sum / (float)total) * 100.0f);
17308 	}
17309 
17310 	return sum;
17311 }
17312 
17313 // Goober5000 & Karajorma
sexp_get_ammo(int node,bool for_turret,bool primary,bool do_percent)17314 int sexp_get_ammo(int node, bool for_turret, bool primary, bool do_percent)
17315 {
17316 	ship_weapon *swp;
17317 	bool is_nan, is_nan_forever;
17318 
17319 	// get the ship
17320 	auto ship_entry = eval_ship(node);
17321 	if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT)
17322 		return SEXP_NAN;
17323 	if (ship_entry->status == ShipStatus::EXITED)
17324 		return SEXP_NAN_FOREVER;
17325 	node = CDR(node);
17326 
17327 	// Get the turret
17328 	if (for_turret)
17329 	{
17330 		auto turret = ship_get_subsys(ship_entry->shipp, CTEXT(node));
17331 		if (!turret || !(turret->system_info->flags[Model::Subsystem_Flags::Turret_use_ammo]))
17332 			return 0;
17333 		node = CDR(node);
17334 
17335 		swp = &turret->weapons;
17336 	}
17337 	else
17338 		swp = &ship_entry->shipp->weapons;
17339 
17340 	// bank to check
17341 	int check = eval_num(node, is_nan, is_nan_forever);
17342 
17343 	// bogus check?
17344 	if (is_nan || is_nan_forever || check < 0)
17345 		return 0;
17346 
17347 	return sexp_get_ammo_sub(swp, check, primary, do_percent);
17348 }
17349 
17350 //Karajorma - Helper function for the set-*-ammo and weapon functions
sexp_set_ammo_sub(ship_weapon * swp,int requested_bank,int requested_ammo,int rearm_limit,bool primary)17351 void sexp_set_ammo_sub(ship_weapon *swp, int requested_bank, int requested_ammo, int rearm_limit, bool primary)
17352 {
17353 	int num_banks, *ammo_capacity, *ammo, *start_ammo, *weapons;
17354 	if (primary)
17355 	{
17356 		num_banks = swp->num_primary_banks;
17357 		ammo_capacity = swp->primary_bank_capacity;
17358 		ammo = swp->primary_bank_ammo;
17359 		start_ammo = swp->primary_bank_start_ammo;
17360 		weapons = swp->primary_bank_weapons;
17361 	}
17362 	else
17363 	{
17364 		num_banks = swp->num_secondary_banks;
17365 		ammo_capacity = swp->secondary_bank_capacity;
17366 		ammo = swp->secondary_bank_ammo;
17367 		start_ammo = swp->secondary_bank_start_ammo;
17368 		weapons = swp->secondary_bank_weapons;
17369 	}
17370 
17371 	// Can only set one bank at a time. Check that someone hasn't asked for the contents of all
17372 	// the banks or a non-existant bank
17373 	if ((requested_bank >= num_banks) || requested_bank < 0)
17374 		return;
17375 
17376 	if (primary)
17377 	{
17378 		// Check that this isn't a non-ballistic bank as it's pointless to set the amount of ammo for those
17379 		if (!(Weapon_info[weapons[requested_bank]].wi_flags[Weapon::Info_Flags::Ballistic]))
17380 			return;
17381 	}
17382 
17383 	int maximum_allowed = (int)std::lround(ammo_capacity[requested_bank] / Weapon_info[weapons[requested_bank]].cargo_size);
17384 
17385 	//Check that a valid number of weapons have been specified.
17386 	if (requested_ammo >= 0)
17387 	{
17388 		// Is the number requested larger than the maximum allowed for that particular bank?
17389 		if (requested_ammo > maximum_allowed)
17390 			requested_ammo = maximum_allowed;
17391 
17392 		// Set the number of weapons
17393 		ammo[requested_bank] = requested_ammo;
17394 	}
17395 	else if (ammo[requested_bank] > maximum_allowed)
17396 	{
17397 		// Make sure the current ammo doesn't exceed the maximum (this can otherwise happen with the set-*-weapon SEXPs)
17398 		ammo[requested_bank] = maximum_allowed;
17399 	}
17400 
17401 	// Check rearm validity
17402 	if (rearm_limit >= 0)
17403 	{
17404 		// Don't allow more weapons than the bank can actually hold.
17405 		if (rearm_limit > maximum_allowed)
17406 			rearm_limit = maximum_allowed;
17407 
17408 		start_ammo[requested_bank] = rearm_limit;
17409 	}
17410 	else	// Even if no rearm limit is explicitly set, don't allow more weapons than the bank can actually hold (this can otherwise happen with the set-*-weapon SEXPs).
17411 	{
17412 		start_ammo[requested_bank] = maximum_allowed;
17413 	}
17414 }
17415 
17416 // Karajorma - sets the amount of ammo in a certain ballistic or secondary weapon bank to the specified value
sexp_set_ammo(int node,bool for_turret,bool primary)17417 void sexp_set_ammo(int node, bool for_turret, bool primary)
17418 {
17419 	const char *turret_name = nullptr;
17420 	ship_weapon *swp;
17421 	bool is_nan, is_nan_forever;
17422 
17423 	// get the ship
17424 	auto ship_entry = eval_ship(node);
17425 	if (!ship_entry || !ship_entry->shipp)
17426 		return;
17427 	node = CDR(node);
17428 
17429 	// Get the turret
17430 	if (for_turret)
17431 	{
17432 		turret_name = CTEXT(node);
17433 		auto turret = ship_get_subsys(ship_entry->shipp, turret_name);
17434 		if (!turret || !(turret->system_info->flags[Model::Subsystem_Flags::Turret_use_ammo]))
17435 			return;
17436 		node = CDR(node);
17437 
17438 		swp = &turret->weapons;
17439 	}
17440 	else
17441 		swp = &ship_entry->shipp->weapons;
17442 
17443 	// Get the bank to set the number on
17444 	int requested_bank = eval_num(node, is_nan, is_nan_forever);
17445 	if (is_nan || is_nan_forever || requested_bank < 0)
17446 		return;
17447 	node = CDR(node);
17448 
17449 	//  Get the number of weapons requested
17450 	int requested_weapons = eval_num(node, is_nan, is_nan_forever);
17451 	if (is_nan || is_nan_forever || requested_weapons < 0)
17452 		return;
17453 	node = CDR(node);
17454 
17455 	// If a rearm limit hasn't been specified simply change the ammo. Otherwise read in the rearm limit
17456 	int rearm_limit = -1;
17457 	if (node >= 0) {
17458 		rearm_limit = eval_num(node, is_nan, is_nan_forever);
17459 		if (is_nan || is_nan_forever)
17460 			return;
17461 	}
17462 
17463 	sexp_set_ammo_sub(swp, requested_bank, requested_weapons, rearm_limit, primary);
17464 
17465 	// do the multiplayer callback here
17466 	if (MULTIPLAYER_MASTER)
17467 	{
17468 		Current_sexp_network_packet.start_callback();
17469 		Current_sexp_network_packet.send_ship(ship_entry->shipp);
17470 		if (for_turret)
17471 			Current_sexp_network_packet.send_string(turret_name);
17472 		Current_sexp_network_packet.send_int(requested_bank);
17473 		Current_sexp_network_packet.send_int(requested_weapons);
17474 		Current_sexp_network_packet.send_int(rearm_limit);
17475 		Current_sexp_network_packet.end_callback();
17476 	}
17477 }
17478 
multi_sexp_set_ammo(bool for_turret,bool primary)17479 void multi_sexp_set_ammo(bool for_turret, bool primary)
17480 {
17481 	ship *shipp;
17482 	char turret_name[TOKEN_LENGTH];
17483 	ship_weapon *swp;
17484 	int requested_bank = -1;
17485 	int requested_weapons = -1;
17486 	int rearm_limit = -1;
17487 
17488 	Current_sexp_network_packet.get_ship(shipp);
17489 	if (for_turret)
17490 		Current_sexp_network_packet.get_string(turret_name);
17491 	Current_sexp_network_packet.get_int(requested_bank);
17492 	Current_sexp_network_packet.get_int(requested_weapons);
17493 	Current_sexp_network_packet.get_int(rearm_limit);
17494 
17495 	if (for_turret)
17496 	{
17497 		auto turret = ship_get_subsys(shipp, turret_name);
17498 		if (!turret)
17499 			return;
17500 		swp = &turret->weapons;
17501 	}
17502 	else
17503 		swp = &shipp->weapons;
17504 
17505 	sexp_set_ammo_sub(swp, requested_bank, requested_weapons, rearm_limit, primary);
17506 }
17507 
17508 // Karajorma - Changes the weapon in the requested bank to the one supplied. Optionally sets the ammo and
17509 // rearm limit too.
sexp_set_weapon(int node,bool primary)17510 void sexp_set_weapon(int node, bool primary)
17511 {
17512 	int requested_bank, windex, requested_ammo = -1, rearm_limit = -1;
17513 	bool is_nan, is_nan_forever;
17514 
17515 	Assert(node != -1);
17516 
17517 	// Check that a ship has been supplied
17518 	auto ship_entry = eval_ship(node);
17519 	if (!ship_entry || !ship_entry->shipp)
17520 		return;
17521 	node = CDR(node);
17522 
17523 	// Get the bank to change the weapon of
17524 	requested_bank = eval_num(node, is_nan, is_nan_forever);
17525 	if (is_nan || is_nan_forever)
17526 		return;
17527 	node = CDR(node);
17528 
17529 	windex = weapon_info_lookup(CTEXT(node));
17530 	if (windex < 0)
17531 		return;
17532 	node = CDR(node);
17533 
17534 	// Change the weapon
17535 	if (primary)
17536 		ship_entry->shipp->weapons.primary_bank_weapons[requested_bank] = windex;
17537 	else
17538 		ship_entry->shipp->weapons.secondary_bank_weapons[requested_bank] = windex;
17539 
17540 	if (MULTIPLAYER_MASTER)
17541 	{
17542 		Current_sexp_network_packet.start_callback();
17543 		Current_sexp_network_packet.send_ship(ship_entry->shipp);
17544 		Current_sexp_network_packet.send_bool(primary);
17545 		Current_sexp_network_packet.send_int(requested_bank);
17546 		Current_sexp_network_packet.send_int(windex);
17547 	}
17548 
17549 	// Check to see if the optional ammo and rearm_limit settings were supplied
17550 	if (node >= 0) {
17551 		requested_ammo = eval_num(node, is_nan, is_nan_forever);
17552 		if (is_nan || is_nan_forever)
17553 			return;
17554 
17555 		if (requested_ammo < 0) {
17556 			requested_ammo = 0;
17557 		}
17558 		node = CDR(node);
17559 
17560 		// If nothing was supplied then set the rearm limit to a negative value so that it is ignored
17561 		if (node >= 0) {
17562 			rearm_limit = eval_num(node, is_nan, is_nan_forever);
17563 			if (is_nan || is_nan_forever)
17564 				return;
17565 		}
17566 	}
17567 
17568 	// Set the ammo -- No matter what - Cyborg17
17569 	sexp_set_ammo_sub(&ship_entry->shipp->weapons, requested_bank, requested_ammo, rearm_limit, primary);
17570 
17571 	// Now pass this info on to clients.
17572 	if (MULTIPLAYER_MASTER)
17573 	{
17574 		Current_sexp_network_packet.send_int(requested_ammo);
17575 		Current_sexp_network_packet.send_int(rearm_limit);
17576 		Current_sexp_network_packet.end_callback();
17577 	}
17578 }
17579 
multi_sexp_set_weapon()17580 void multi_sexp_set_weapon()
17581 {
17582 	ship *shipp;
17583 	int requested_bank;
17584 	int windex;
17585 	int requested_ammo;
17586 	int rearm_limit;
17587 	bool primary;
17588 
17589 	Current_sexp_network_packet.get_ship(shipp);
17590 	Current_sexp_network_packet.get_bool(primary);
17591 	Current_sexp_network_packet.get_int(requested_bank);
17592 	Current_sexp_network_packet.get_int(windex);
17593 	Current_sexp_network_packet.get_int(requested_ammo);
17594 	Current_sexp_network_packet.get_int(rearm_limit);
17595 
17596 	// Change the weapon
17597 	if (primary)
17598 		shipp->weapons.primary_bank_weapons[requested_bank] = windex;
17599 	else
17600 		shipp->weapons.secondary_bank_weapons[requested_bank] = windex;
17601 
17602 	// Set the ammo
17603 	sexp_set_ammo_sub(&shipp->weapons, requested_bank, requested_ammo, rearm_limit, primary);
17604 }
17605 
sexp_get_countermeasures(int node)17606 int sexp_get_countermeasures(int node)
17607 {
17608 	auto ship_entry = eval_ship(node);
17609 	if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT)
17610 		return SEXP_NAN;
17611 	if (ship_entry->status == ShipStatus::EXITED)
17612 		return SEXP_NAN_FOREVER;
17613 
17614 	return ship_entry->shipp->cmeasure_count;
17615 }
17616 
sexp_set_countermeasures(int node)17617 void sexp_set_countermeasures(int node)
17618 {
17619 	ship *shipp;
17620 	int num_cmeasures;
17621 	bool is_nan, is_nan_forever;
17622 
17623 	auto ship_entry = eval_ship(node);
17624 	if (!ship_entry || !ship_entry->shipp) {
17625 		return;
17626 	}
17627 	shipp = ship_entry->shipp;
17628 	node = CDR(node);
17629 
17630 	num_cmeasures = eval_num(node, is_nan, is_nan_forever);
17631 	if (is_nan || is_nan_forever) {
17632 		return;
17633 	}
17634 	if (num_cmeasures < 0) {
17635 		num_cmeasures = 0;
17636 	}
17637 	else if (num_cmeasures > Ship_info[shipp->ship_info_index].cmeasure_max) {
17638 		num_cmeasures = Ship_info[shipp->ship_info_index].cmeasure_max;
17639 	}
17640 
17641 	shipp->cmeasure_count = num_cmeasures;
17642 
17643 	Current_sexp_network_packet.start_callback();
17644 	Current_sexp_network_packet.send_ship(shipp);
17645 	Current_sexp_network_packet.send_int(num_cmeasures);
17646 	Current_sexp_network_packet.end_callback();
17647 }
17648 
multi_sexp_set_countermeasures()17649 void multi_sexp_set_countermeasures()
17650 {
17651 	int num_cmeasures = 0;
17652 	ship *shipp;
17653 
17654 	Current_sexp_network_packet.get_ship(shipp);
17655 	if (shipp == nullptr) {
17656 		return;
17657 	}
17658 	if (Current_sexp_network_packet.get_int(num_cmeasures)) {
17659 		shipp->cmeasure_count = num_cmeasures;
17660 	}
17661 }
17662 
17663 // KeldorKatarn - Locks or unlocks the afterburner on the requested ship
sexp_deal_with_afterburner_lock(int node,bool lock)17664 void sexp_deal_with_afterburner_lock (int node, bool lock)
17665 {
17666 	Assert (node != -1);
17667 	sexp_deal_with_ship_flag(node, true, Object::Object_Flags::NUM_VALUES, Ship::Ship_Flags::Afterburner_locked, Mission::Parse_Object_Flags::NUM_VALUES, lock, true);
17668 }
17669 
17670 // Karajorma - locks or unlocks primary weapons on the requested ship
sexp_deal_with_primary_lock(int node,bool lock)17671 void sexp_deal_with_primary_lock (int node, bool lock)
17672 {
17673 	Assert (node != -1);
17674 	sexp_deal_with_ship_flag(node, true, Object::Object_Flags::NUM_VALUES, Ship::Ship_Flags::Primaries_locked, Mission::Parse_Object_Flags::SF_Primaries_locked, lock, true);
17675 
17676 }
17677 
sexp_deal_with_secondary_lock(int node,bool lock)17678 void sexp_deal_with_secondary_lock (int node, bool lock)
17679 {
17680 	Assert (node != -1);
17681 	sexp_deal_with_ship_flag(node, true, Object::Object_Flags::NUM_VALUES, Ship::Ship_Flags::Secondaries_locked, Mission::Parse_Object_Flags::SF_Secondaries_locked, lock, true);
17682 
17683 }
17684 
17685 //Karajorma - Changes the subsystem name displayed on the HUD.
sexp_change_subsystem_name(int node)17686 void sexp_change_subsystem_name(int node)
17687 {
17688 	Assert (node != -1);
17689 
17690 	// Check that a ship has been supplied
17691 	auto ship_entry = eval_ship(node);
17692 	if (!ship_entry || !ship_entry->shipp) {
17693 		return;
17694 	}
17695 	node = CDR(node);
17696 
17697 	auto new_name = CTEXT(node);
17698 	node = CDR(node);
17699 
17700 	if (MULTIPLAYER_MASTER) {
17701 		Current_sexp_network_packet.start_callback();
17702 		Current_sexp_network_packet.send_ship(ship_entry->shipp);
17703 		Current_sexp_network_packet.send_string(new_name);
17704 	}
17705 
17706 	// loop through all the subsystems the SEXP has provided
17707 	while (node >= 0) {
17708 
17709 		//Get the new subsystem name
17710 		auto subsystem_to_rename = ship_get_subsys(ship_entry->shipp, CTEXT(node));
17711 		if (subsystem_to_rename != nullptr) {
17712 			ship_subsys_set_name(subsystem_to_rename, new_name);
17713 
17714 			if (MULTIPLAYER_MASTER) {
17715 				Current_sexp_network_packet.send_string(CTEXT(node));
17716 			}
17717 		}
17718 
17719 		node = CDR(node);
17720 	}
17721 
17722 	if (MULTIPLAYER_MASTER) {
17723 		Current_sexp_network_packet.end_callback();
17724 	}
17725 }
17726 
multi_sexp_change_subsystem_name()17727 void multi_sexp_change_subsystem_name()
17728 {
17729 	ship *shipp = nullptr;
17730 	char new_name[TOKEN_LENGTH];
17731 	char subsys_name[MAX_NAME_LEN];
17732 	ship_subsys *subsystem_to_rename;
17733 
17734 	Current_sexp_network_packet.get_ship(shipp);
17735 	Current_sexp_network_packet.get_string(new_name);
17736 	while (Current_sexp_network_packet.get_string(subsys_name)) {
17737 		subsystem_to_rename = ship_get_subsys(shipp, subsys_name);
17738 		if (subsystem_to_rename != nullptr) {
17739 			ship_subsys_set_name(subsystem_to_rename, new_name);
17740 		}
17741 	}
17742 }
17743 
17744 // Goober5000
sexp_change_ship_class(int n)17745 void sexp_change_ship_class(int n)
17746 {
17747 	int class_num = ship_info_lookup(CTEXT(n));
17748 	if (class_num < 0)
17749 		return;
17750 	n = CDR(n);
17751 
17752 	if (MULTIPLAYER_MASTER) {
17753 		Current_sexp_network_packet.start_callback();
17754 		Current_sexp_network_packet.send_int(class_num);
17755 	}
17756 
17757 	// all ships in the sexp
17758 	for ( ; n != -1; n = CDR(n))
17759 	{
17760 		auto ship_entry = eval_ship(n);
17761 		if (!ship_entry || ship_entry->status == ShipStatus::EXITED)
17762 			continue;
17763 
17764 		// If the ship hasn't arrived we still want the ability to change its class.
17765 		if (ship_entry->status == ShipStatus::NOT_YET_PRESENT)
17766 		{
17767 			swap_parse_object(ship_entry->p_objp, class_num);
17768 
17769 			if (MULTIPLAYER_MASTER) {
17770 				Current_sexp_network_packet.send_bool(false);
17771 				Current_sexp_network_packet.send_parse_object(ship_entry->p_objp);
17772 			}
17773 		}
17774 		// If the ship is already in the mission
17775 		else
17776 		{
17777 			// don't mess with a ship that's occupied
17778 			if (!ship_entry->shipp->is_arriving() && !ship_entry->shipp->is_dying_or_departing())
17779 			{
17780 				change_ship_type(ship_entry->objp->instance, class_num, 1);
17781 				if (ship_entry->shipp == Player_ship) {
17782 					set_current_hud();
17783 				}
17784 
17785 				if (MULTIPLAYER_MASTER) {
17786 					Current_sexp_network_packet.send_bool(true);
17787 					Current_sexp_network_packet.send_ship(ship_entry->shipp);
17788 				}
17789 			}
17790 		}
17791 	}
17792 
17793 	if (MULTIPLAYER_MASTER) {
17794 		Current_sexp_network_packet.end_callback();
17795 	}
17796 }
17797 
multi_sexp_change_ship_class()17798 void multi_sexp_change_ship_class()
17799 {
17800 	int class_num = -1;
17801 	int ship_num = -1;
17802 	bool ship_arrived;
17803 	p_object *pobjp = nullptr;
17804 
17805 	Current_sexp_network_packet.get_int(class_num);
17806 	while (Current_sexp_network_packet.get_bool(ship_arrived)) {
17807 		if (ship_arrived) {
17808 			Current_sexp_network_packet.get_ship(ship_num);
17809 			if ((class_num >= 0) && (ship_num >= 0)) {
17810 				change_ship_type(ship_num, class_num, 1);
17811 				if (&Ships[ship_num] == Player_ship) {
17812 					set_current_hud();
17813 				}
17814 			}
17815 		}
17816 		else {
17817 			Current_sexp_network_packet.get_parse_object(pobjp);
17818 			if ((class_num >= 0) && (pobjp != nullptr)) {
17819 				swap_parse_object(pobjp, class_num);
17820 			}
17821 		}
17822 	}
17823 }
17824 
17825 // Goober5000
ship_copy_damage(ship * target_shipp,ship * source_shipp)17826 void ship_copy_damage(ship *target_shipp, ship *source_shipp)
17827 {
17828 	int i;
17829 	object *target_objp = &Objects[target_shipp->objnum];
17830 	object *source_objp = &Objects[source_shipp->objnum];
17831 	ship_subsys *source_ss;
17832 	ship_subsys *target_ss;
17833 
17834 	if (target_shipp->ship_info_index != source_shipp->ship_info_index)
17835 	{
17836 		nprintf(("SEXP", "Copying damage of ship %s to ship %s which has a different ship class.  Strange results might occur.\n", source_shipp->ship_name, target_shipp->ship_name));
17837 	}
17838 
17839 
17840 	// copy hull...
17841 	target_shipp->special_hitpoints = source_shipp->special_hitpoints;
17842 	target_shipp->ship_max_hull_strength = source_shipp->ship_max_hull_strength;
17843 	target_objp->hull_strength = source_objp->hull_strength;
17844 
17845 	// ...and shields
17846 	target_shipp->special_shield = source_shipp->special_shield;
17847 	target_shipp->ship_max_shield_strength = source_shipp->ship_max_shield_strength;
17848 	for (i = 0; i < MIN(target_objp->n_quadrants, source_objp->n_quadrants); i++)
17849 		target_objp->shield_quadrant[i] = source_objp->shield_quadrant[i];
17850 
17851 
17852 	// search through all subsystems on source ship and map them onto target ship
17853 	for (source_ss = GET_FIRST(&source_shipp->subsys_list); source_ss != GET_LAST(&source_shipp->subsys_list); source_ss = GET_NEXT(source_ss))
17854 	{
17855 		// find subsystem to configure
17856 		target_ss = ship_get_subsys(target_shipp, source_ss->system_info->subobj_name);
17857 		if (target_ss == nullptr)
17858 			continue;
17859 
17860 		// copy
17861 		target_ss->max_hits = source_ss->max_hits;
17862 		target_ss->current_hits = source_ss->current_hits;
17863 		if (target_ss->submodel_instance_1 && source_ss->submodel_instance_1)
17864 			target_ss->submodel_instance_1->blown_off = source_ss->submodel_instance_1->blown_off;
17865 		if (target_ss->submodel_instance_2 && source_ss->submodel_instance_2)
17866 			target_ss->submodel_instance_2->blown_off = source_ss->submodel_instance_2->blown_off;
17867 	}
17868 }
17869 
sexp_replace_texture(int n)17870 void sexp_replace_texture(int n)
17871 {
17872 	auto old_name = CTEXT(n);
17873 	n = CDR(n);
17874 
17875 	auto new_name = CTEXT(n);
17876 	n = CDR(n);
17877 
17878 	for (; n != -1; n = CDR(n))
17879 	{
17880 
17881 		object_ship_wing_point_team oswpt;
17882 		eval_object_ship_wing_point_team(&oswpt, n);
17883 
17884 		// we only handle ships and wings that are present
17885 		switch (oswpt.type)
17886 		{
17887 		case OSWPT_TYPE_PARSE_OBJECT:
17888 		case OSWPT_TYPE_SHIP:
17889 		{
17890 			auto ship_entry = oswpt.ship_entry;
17891 
17892 			if (ship_entry->status == ShipStatus::EXITED)
17893 				continue;
17894 
17895 			if (ship_entry->status == ShipStatus::NOT_YET_PRESENT)
17896 			{
17897 				p_object* pobjp = ship_entry->p_objp;
17898 
17899 				texture_replace replace;
17900 
17901 				strcpy(replace.ship_name, ship_entry->name);
17902 				strcpy(replace.old_texture, old_name);
17903 				strcpy(replace.new_texture, new_name);
17904 				replace.new_texture_id = bm_load(new_name);
17905 
17906 				pobjp->replacement_textures.push_back(replace);
17907 			}
17908 			// If the ship is already in the mission
17909 			else
17910 			{
17911 				ship_replace_active_texture(ship_entry->objp->instance, old_name, new_name);
17912 			}
17913 			break;
17914 		}
17915 
17916 		case OSWPT_TYPE_WING:
17917 		{
17918 			auto wp = oswpt.wingp;
17919 			for (int i = 0; i < wp->current_count; ++i)
17920 			{
17921 				if (wp->ship_index[i] >= 0)
17922 				{
17923 					ship_replace_active_texture(wp->ship_index[i], old_name, new_name);
17924 				}
17925 			}
17926 			break;
17927 		}
17928 
17929 		case OSWPT_TYPE_WING_NOT_PRESENT:
17930 		{
17931 			for (p_object* p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp))
17932 			{
17933 				if (p_objp->wingnum == WING_INDEX(oswpt.wingp))
17934 				{
17935 					texture_replace replace;
17936 
17937 					strcpy(replace.ship_name, p_objp->name);
17938 					strcpy(replace.old_texture, old_name);
17939 					strcpy(replace.new_texture, new_name);
17940 					replace.new_texture_id = bm_load(new_name);
17941 
17942 					p_objp->replacement_textures.push_back(replace);
17943 				}
17944 			}
17945 			break;
17946 		}
17947 
17948 		default:
17949 			mprintf(("Invalid Shipname in SEXP ship-effect\n"));
17950 		}
17951 	}
17952 }
17953 
17954 extern int insert_subsys_status(p_object *pobjp);
17955 
17956 // Goober5000
parse_copy_damage(p_object * target_pobjp,ship * source_shipp)17957 void parse_copy_damage(p_object *target_pobjp, ship *source_shipp)
17958 {
17959 	object *source_objp = &Objects[source_shipp->objnum];
17960 	ship_subsys *source_ss;
17961 	subsys_status *target_sssp;
17962 
17963 	if (target_pobjp->ship_class != source_shipp->ship_info_index)
17964 	{
17965 		nprintf(("SEXP", "Copying damage of ship %s to ship %s which has a different ship class.  Strange results might occur.\n", source_shipp->ship_name, target_pobjp->name));
17966 	}
17967 
17968 	// copy hull...
17969 	target_pobjp->special_hitpoints = source_shipp->special_hitpoints;
17970 	target_pobjp->ship_max_hull_strength = source_shipp->ship_max_hull_strength;
17971 	target_pobjp->initial_hull = fl2i(get_hull_pct(source_objp) * 100.0f);
17972 
17973 	// ...and shields
17974 	target_pobjp->ship_max_shield_strength = source_shipp->ship_max_shield_strength;
17975 	target_pobjp->initial_shields = fl2i(get_shield_pct(source_objp) * 100.0f);
17976 	target_pobjp->max_shield_recharge = source_shipp->max_shield_recharge;
17977 
17978 
17979 	// search through all subsystems on source ship and map them onto target ship
17980 	for (source_ss = GET_FIRST(&source_shipp->subsys_list); source_ss != GET_LAST(&source_shipp->subsys_list); source_ss = GET_NEXT(source_ss))
17981 	{
17982 		// find subsystem to configure
17983 		target_sssp = parse_get_subsys_status(target_pobjp, source_ss->system_info->subobj_name);
17984 
17985 		// gak... none allocated; we need to allocate one!
17986 		if (target_sssp == nullptr)
17987 		{
17988 			// jam in the new subsystem at the end of the existing list for this parse object
17989 			int new_idx = insert_subsys_status(target_pobjp);
17990 			target_sssp = &Subsys_status[new_idx];
17991 
17992 			strcpy_s(target_sssp->name, source_ss->system_info->subobj_name);
17993 		}
17994 
17995 		// copy
17996 		if (source_ss->max_hits == 0.0f)
17997 		{
17998 			target_sssp->percent = 100.0f;
17999 		}
18000 		else
18001 		{
18002 			target_sssp->percent = 100.0f - (source_ss->current_hits / source_ss->max_hits) * 100.0f;
18003 		}
18004 	}
18005 }
18006 
18007 // Goober5000
sexp_ship_copy_damage(int node)18008 void sexp_ship_copy_damage(int node)
18009 {
18010 	int n;
18011 
18012 	// source ship must be present
18013 	auto source = eval_ship(node);
18014 	if (!source || !source->shipp)
18015 		return;
18016 
18017 	// loop through all subsequent arguments
18018 	for (n = CDR(node); n != -1; n = CDR(n))
18019 	{
18020 		auto target = eval_ship(n);
18021 		if (!target)
18022 			continue;
18023 
18024 		// maybe it's present in-mission
18025 		if (target->status == ShipStatus::PRESENT)
18026 		{
18027 			ship_copy_damage(target->shipp, source->shipp);
18028 			continue;
18029 		}
18030 
18031 		// maybe it's on the arrival list
18032 		if (target->status == ShipStatus::NOT_YET_PRESENT)
18033 		{
18034 			parse_copy_damage(target->p_objp, source->shipp);
18035 			continue;
18036 		}
18037 
18038 		// must have departed or not even existed... do nothing
18039 	}
18040 }
18041 
18042 //-Bobboau
sexp_activate_deactivate_glow_points(int n,bool activate)18043 void sexp_activate_deactivate_glow_points(int n, bool activate)
18044 {
18045 	for ( ; n != -1; n = CDR(n))
18046 	{
18047 		auto ship_entry = eval_ship(n);
18048 		if (!ship_entry || !ship_entry->shipp)
18049 			continue;
18050 
18051 		for (auto &bank_active : ship_entry->shipp->glow_point_bank_active)
18052 			bank_active = activate;
18053 	}
18054 }
18055 
18056 //-Bobboau
sexp_activate_deactivate_glow_point_bank(int n,bool activate)18057 void sexp_activate_deactivate_glow_point_bank(int n, bool activate)
18058 {
18059 	auto ship_entry = eval_ship(n);
18060 	if (!ship_entry || !ship_entry->shipp)
18061 		return;
18062 
18063 	for ( n = CDR(n); n != -1; n = CDR(n))
18064 	{
18065 		bool is_nan, is_nan_forever;
18066 		int num = eval_num(n, is_nan, is_nan_forever);
18067 		if (is_nan || is_nan_forever)
18068 			continue;
18069 
18070 		if (num >= 0 && num < (int)ship_entry->shipp->glow_point_bank_active.size())
18071 			ship_entry->shipp->glow_point_bank_active[num] = activate;
18072 	}
18073 }
18074 
18075 //-Bobboau
sexp_activate_deactivate_glow_maps(int n,bool activate)18076 void sexp_activate_deactivate_glow_maps(int n, bool activate)
18077 {
18078 	for ( ; n != -1; n = CDR(n))
18079 	{
18080 		auto ship_entry = eval_ship(n);
18081 		if (!ship_entry || !ship_entry->shipp)
18082 			continue;
18083 
18084 		ship_entry->shipp->flags.set(Ship::Ship_Flags::Glowmaps_disabled, !activate);
18085 	}
18086 }
18087 
sexp_set_ambient_light(int node)18088 void sexp_set_ambient_light(int node)
18089 {
18090 	int red, green, blue, level = 0;
18091 	bool is_nan, is_nan_forever;
18092 
18093 	Assert(node >= 0);
18094 
18095 	eval_nums(node, is_nan, is_nan_forever, red, green, blue);
18096 	if (is_nan || is_nan_forever)
18097 		return;
18098 
18099 	if (red < 0 || red > 255)
18100 		red = 0;
18101 	if (green < 0 || green > 255)
18102 		green = 0;
18103 	if (blue < 0 || blue > 255)
18104 		blue = 0;
18105 
18106 	level |= red;
18107 	level |= green << 8;
18108 	level |= blue << 16;
18109 
18110 	// setting the ambient light level for the mission won't actually do anything as it is parsed at mission
18111 	// start but it should be updated in case someone changes that later
18112 	The_mission.ambient_light_level = level;
18113 
18114 	// call the graphics function to actually set the level
18115 	gr_set_ambient_light(red, green, blue);
18116 
18117 	// do the multiplayer callback
18118 	if (MULTIPLAYER_MASTER) {
18119 		Current_sexp_network_packet.start_callback();
18120 		Current_sexp_network_packet.send_int(level);
18121 		Current_sexp_network_packet.end_callback();
18122 	}
18123 }
18124 
multi_sexp_set_ambient_light()18125 void multi_sexp_set_ambient_light()
18126 {
18127 	int level = 0;
18128 	if (Current_sexp_network_packet.get_int(level)) {
18129 		The_mission.ambient_light_level = level;
18130 		gr_set_ambient_light((level & 0xff),((level >> 8) & 0xff), ((level >> 16) & 0xff));
18131 	}
18132 }
18133 
sexp_set_post_effect(int node)18134 void sexp_set_post_effect(int node)
18135 {
18136 	std::array<float, 3> a1d;
18137 	vec3d rgb;
18138 	bool is_nan, is_nan_forever;
18139 
18140 	auto name = CTEXT(node);
18141 	if (name == nullptr || *name == '\0')
18142 		return;
18143 	node = CDR(node);
18144 
18145 	int amount = eval_num(node, is_nan, is_nan_forever);
18146 	if (is_nan || is_nan_forever)
18147 		return;
18148 	if (amount < 0 || amount > 100)
18149 		amount = 0;
18150 	node = CDR(node);
18151 
18152 	eval_array<float>(a1d, node, is_nan, is_nan_forever, [](int num)->float
18153 	{
18154 		float f = static_cast<float>(num) / 255.0f;
18155 		CAP(f, 0.0f, 1.0f);
18156 		return f;
18157 	});
18158 	if (is_nan || is_nan_forever)
18159 		return;
18160 	std::copy(a1d.begin(), a1d.end(), std::begin(rgb.a1d));
18161 
18162 	gr_post_process_set_effect(name, amount, &rgb);
18163 }
18164 
sexp_reset_post_effects()18165 void sexp_reset_post_effects()
18166 {
18167 	gr_post_process_set_defaults();
18168 }
18169 
18170 // Goober5000
sexp_set_skybox_orientation(int n)18171 void sexp_set_skybox_orientation(int n)
18172 {
18173 	matrix m;
18174 	angles a;
18175 	bool is_nan, is_nan_forever;
18176 
18177 	eval_angles(&a, n, is_nan, is_nan_forever);
18178 	if (is_nan || is_nan_forever)
18179 		return;
18180 
18181 	vm_angles_2_matrix(&m, &a);
18182 	stars_set_background_orientation(&m);
18183 }
18184 
18185 // taylor - load and set a skybox model
sexp_set_skybox_model(int n)18186 void sexp_set_skybox_model(int n)
18187 {
18188 	char new_skybox_model[TOKEN_LENGTH+1]; //max input is TOKEN_LENGTH, +1 for NUL
18189 	strcpy_s(new_skybox_model, CTEXT(n));
18190 	int new_skybox_model_flags = DEFAULT_NMODEL_FLAGS;
18191 
18192 	// check if we need to reset the animated texture timestamp
18193 	n = CDR(n);
18194 	if (n == -1 || is_sexp_true(n)) {
18195 		Skybox_timestamp = game_get_overall_frametime();
18196 	}
18197 
18198 	if (n != -1) n = CDR(n);
18199 
18200 	// gather any flags
18201 	while (n != -1) {
18202 		// this should check all entries in Skybox_flags
18203 		if ( !stricmp("add-lighting", CTEXT(n) )) {
18204 			new_skybox_model_flags &= ~MR_NO_LIGHTING;
18205 		}
18206 		else if ( !stricmp("no-transparency", CTEXT(n) )) {
18207 			new_skybox_model_flags &= ~MR_ALL_XPARENT;
18208 		}
18209 		else if ( !stricmp("add-zbuffer", CTEXT(n) )) {
18210 			new_skybox_model_flags &= ~MR_NO_ZBUFFER;
18211 		}
18212 		else if ( !stricmp("add-culling", CTEXT(n) )) {
18213 			new_skybox_model_flags &= ~MR_NO_CULL;
18214 		}
18215 		else if ( !stricmp("no-glowmaps", CTEXT(n) )) {
18216 			new_skybox_model_flags |= MR_NO_GLOWMAPS;
18217 		}
18218 		else if ( !stricmp("force-clamp", CTEXT(n) )) {
18219 			new_skybox_model_flags |= MR_FORCE_CLAMP;
18220 		}
18221 		else {
18222 			Warning(LOCATION, "Invalid flag passed to set-skybox-model: %s\n", CTEXT(n));
18223 		}
18224 		n = CDR(n);
18225 	}
18226 	if ( !stricmp("default", new_skybox_model )) {
18227 		stars_set_background_model( The_mission.skybox_model, nullptr, new_skybox_model_flags );
18228 	} else {
18229 		// stars_level_init() will set the actual mission skybox after this gets
18230 		// evaluated during parse. by setting it now we get everything loaded so
18231 		// there is less slowdown when it actually swaps out - taylor
18232 		stars_set_background_model( new_skybox_model, nullptr, new_skybox_model_flags );
18233 	}
18234 }
18235 
18236 // taylor - preload a skybox model.  this doesn't set anything as viewable, just loads it into memory
sexp_set_skybox_model_preload(const char * name)18237 void sexp_set_skybox_model_preload(const char *name)
18238 {
18239 	int i;
18240 
18241 	if ( !stricmp("default", name) ) {
18242 		// if there isn't a mission skybox model then don't load one
18243 		if ( strlen(The_mission.skybox_model) ) {
18244 			i = model_load( The_mission.skybox_model, 0, nullptr );
18245 			model_page_in_textures( i );
18246 		}
18247 	} else {
18248 		i = model_load( name, 0, nullptr );
18249 		model_page_in_textures( i );
18250 	}
18251 }
18252 
sexp_set_thrusters(int node)18253 void sexp_set_thrusters(int node)
18254 {
18255 	bool activate = is_sexp_true(node);
18256 	node = CDR(node);
18257 
18258 	for (; node >= 0; node = CDR(node))
18259 	{
18260 		auto ship_entry = eval_ship(node);
18261 		if (!ship_entry || !ship_entry->shipp)
18262 			continue;
18263 
18264 		ship_entry->shipp->flags.set(Ship::Ship_Flags::No_thrusters, !activate);
18265 	}
18266 }
18267 
sexp_beam_fire(int node,bool at_coords)18268 void sexp_beam_fire(int node, bool at_coords)
18269 {
18270 	int idx, n = node;
18271 	beam_fire_info fire_info;
18272 	bool is_nan, is_nan_forever;
18273 
18274 	// zero stuff out
18275 	memset(&fire_info, 0, sizeof(beam_fire_info));
18276 	fire_info.accuracy = 0.000001f;							// this will guarantee a hit
18277 	fire_info.burst_index = 0;
18278 
18279 	// get the firing ship
18280 	auto shooter = eval_ship(n);
18281 	if (!shooter || !shooter->shipp) {
18282 		return;
18283 	}
18284 	n = CDR(n);
18285 	fire_info.shooter = shooter->objp;
18286 
18287 	// get the subsystem
18288 	fire_info.turret = ship_get_subsys(shooter->shipp, CTEXT(n));
18289 	if (fire_info.turret == nullptr) {
18290 		return;
18291 	}
18292 	n = CDR(n);
18293 
18294 	if (at_coords) {
18295 		// get the target coordinates
18296 		eval_vec3d(&fire_info.target_pos1, n, is_nan, is_nan_forever);
18297 		if (is_nan || is_nan_forever) {
18298 			return;
18299 		}
18300 
18301 		fire_info.bfi_flags |= BFIF_TARGETING_COORDS;
18302 		fire_info.target = nullptr;
18303 		fire_info.target_subsys = nullptr;
18304 	} else {
18305 		// get the target
18306 		auto target = eval_ship(n);
18307 		if (!target || !target->shipp) {
18308 			return;
18309 		}
18310 		n = CDR(n);
18311 		fire_info.target = target->objp;
18312 
18313 		// see if the optional subsystem can be found
18314 		fire_info.target_subsys = nullptr;
18315 		if (n >= 0) {
18316 			fire_info.target_subsys = ship_get_subsys(target->shipp, CTEXT(n));
18317 			n = CDR(n);
18318 		}
18319 	}
18320 
18321 	// optionally force firing
18322 	if (n >= 0 && is_sexp_true(n)) {
18323 		fire_info.bfi_flags |= BFIF_FORCE_FIRING;
18324 		n = CDR(n);
18325 	}
18326 
18327 	// get the second set of coordinates
18328 	if (at_coords) {
18329 		int count = eval_vec3d(&fire_info.target_pos2, n, is_nan, is_nan_forever);
18330 		if (is_nan || is_nan_forever) {
18331 			return;
18332 		}
18333 
18334 		if (count == 0) {
18335 			fire_info.target_pos2 = fire_info.target_pos1;
18336 		}
18337 	}
18338 
18339 	// --- done getting arguments ---
18340 
18341 	// if it has no primary weapons
18342 	if (fire_info.turret->weapons.num_primary_banks <= 0) {
18343 		Warning(LOCATION, "Couldn't fire turret on ship %s; subsystem %s has no primary weapons", CTEXT(node), CTEXT(CDR(node)));
18344 		return;
18345 	}
18346 
18347 	// if the turret is destroyed
18348 	if (!(fire_info.bfi_flags & BFIF_FORCE_FIRING) && fire_info.turret->current_hits <= 0.0f) {
18349 		return;
18350 	}
18351 
18352 	// hmm, this could be wacky. Let's just simply select the first beam weapon in the turret
18353 	fire_info.beam_info_index = -1;
18354 	for (idx=0; idx<fire_info.turret->weapons.num_primary_banks; idx++) {
18355 		Assertion(fire_info.turret->weapons.primary_bank_weapons[idx] >= 0 && fire_info.turret->weapons.primary_bank_weapons[idx] < weapon_info_size(),
18356 				"sexp_beam_fire: found invalid weapon index (%i), get a coder\n!", fire_info.turret->weapons.primary_bank_weapons[idx]);
18357 		// store the weapon info index
18358 		if (Weapon_info[fire_info.turret->weapons.primary_bank_weapons[idx]].wi_flags[Weapon::Info_Flags::Beam]) {
18359 			fire_info.beam_info_index = fire_info.turret->weapons.primary_bank_weapons[idx];
18360 		}
18361 	}
18362 
18363 	// fire the beam
18364 	if (fire_info.beam_info_index != -1) {
18365 		fire_info.fire_method = BFM_TURRET_FORCE_FIRED;
18366 
18367 		beam_fire(&fire_info);
18368 	} else {
18369 		// it would appear the turret doesn't have any beam weapons
18370 		Warning(LOCATION, "Couldn't fire turret on ship %s; subsystem %s has no beam weapons", CTEXT(node), CTEXT(CDR(node)));
18371 	}
18372 }
18373 
sexp_beam_floating_fire(int n)18374 void sexp_beam_floating_fire(int n)
18375 {
18376 	bool is_nan, is_nan_forever;
18377 	beam_fire_info fire_info;
18378 	memset(&fire_info, 0, sizeof(beam_fire_info));
18379 	fire_info.accuracy = 0.000001f;							// this will guarantee a hit
18380 	fire_info.bfi_flags |= BFIF_FLOATING_BEAM;
18381 	fire_info.turret = nullptr;		// A free-floating beam isn't fired from a subsystem.
18382 	fire_info.burst_index = 0;
18383 
18384 	fire_info.beam_info_index = weapon_info_lookup(CTEXT(n));
18385 	n = CDR(n);
18386 	if (fire_info.beam_info_index < 0)
18387 	{
18388 		Warning(LOCATION, "Invalid weapon class passed to beam-create; weapon type '%s' does not exist!\n", CTEXT(n));
18389 		return;
18390 	}
18391 	if (!(Weapon_info[fire_info.beam_info_index].wi_flags[Weapon::Info_Flags::Beam]))
18392 	{
18393 		Warning(LOCATION, "Invalid weapon class passed to beam-create; weapon type '%s' is not a beam!\n", CTEXT(n));
18394 		return;
18395 	}
18396 
18397 	fire_info.shooter = nullptr;
18398 	if (stricmp(CTEXT(n), SEXP_NONE_STRING) != 0)
18399 	{
18400 		auto shooter = eval_ship(n);
18401 		if (!shooter || !shooter->shipp)
18402 			return;
18403 
18404 		fire_info.shooter = shooter->objp;
18405 	}
18406 	n = CDR(n);
18407 
18408 	fire_info.team = static_cast<char>(iff_lookup(CTEXT(n)));
18409 	n = CDR(n);
18410 
18411 	eval_vec3d(&fire_info.starting_pos, n, is_nan, is_nan_forever);
18412 	if (is_nan || is_nan_forever)
18413 		return;
18414 
18415 	fire_info.target = nullptr;
18416 	fire_info.target_subsys = nullptr;
18417 
18418 	const ship_registry_entry *target = nullptr;
18419 	if (stricmp(CTEXT(n), SEXP_NONE_STRING) != 0)
18420 	{
18421 		target = eval_ship(n);
18422 		if (!target || !target->shipp)
18423 			return;
18424 
18425 		fire_info.target = target->objp;
18426 	}
18427 	else
18428 		fire_info.bfi_flags |= BFIF_TARGETING_COORDS;
18429 	n = CDR(n);
18430 
18431 	if (n >= 0)
18432 	{
18433 		if (stricmp(CTEXT(n), SEXP_NONE_STRING) != 0)
18434 		{
18435 			if (target && target->shipp)
18436 				fire_info.target_subsys = ship_get_subsys(target->shipp, CTEXT(n));
18437 		}
18438 
18439 		n = CDR(n);
18440 	}
18441 
18442 	// if the nodes do not exist, the vector will be set to 0
18443 	eval_vec3d(&fire_info.target_pos1, n, is_nan, is_nan_forever);
18444 	if (is_nan || is_nan_forever)
18445 		return;
18446 
18447 	// if these nodes do not exist, the vector should be the same as the first
18448 	int count = eval_vec3d(&fire_info.target_pos2, n, is_nan, is_nan_forever);
18449 	if (is_nan || is_nan_forever)
18450 		return;
18451 	if (count == 0)
18452 		fire_info.target_pos2 = fire_info.target_pos1;
18453 
18454 	fire_info.fire_method = BFM_SEXP_FLOATING_FIRED;
18455 
18456 	beam_fire(&fire_info);
18457 }
18458 
sexp_beam_free_one_turret(ship_subsys * turret,bool is_beam,bool free)18459 void sexp_beam_free_one_turret(ship_subsys *turret, bool is_beam, bool free)
18460 {
18461 	if (is_beam)
18462 	{
18463 		if (free)
18464 		{
18465 			// flag it as beam free :)
18466 			if (!(turret->weapons.flags[Ship::Weapon_Flags::Beam_Free]))
18467 			{
18468 				turret->weapons.flags.set(Ship::Weapon_Flags::Beam_Free);
18469 				turret->turret_next_fire_stamp = timestamp((int)frand_range(50.0f, 4000.0f));
18470 			}
18471 		}
18472 		else
18473 		{
18474 			// flag it as not beam free
18475 			turret->weapons.flags.remove(Ship::Weapon_Flags::Beam_Free);
18476 		}
18477 	}
18478 	else
18479 	{
18480 		if (free)
18481 		{
18482 			// flag turret as no longer locked :)
18483 			if (turret->weapons.flags[Ship::Weapon_Flags::Turret_Lock])
18484 			{
18485 				turret->weapons.flags.remove(Ship::Weapon_Flags::Turret_Lock);
18486 				turret->turret_next_fire_stamp = timestamp((int)frand_range(50.0f, 4000.0f));
18487 			}
18488 		}
18489 		else
18490 		{
18491 			// flag turret as locked
18492 			turret->weapons.flags.set(Ship::Weapon_Flags::Turret_Lock);
18493 		}
18494 	}
18495 }
18496 
sexp_beam_or_turret_free_or_lock(int node,bool is_beam,bool free)18497 void sexp_beam_or_turret_free_or_lock(int node, bool is_beam, bool free)
18498 {
18499 	// get the firing ship
18500 	auto shooter = eval_ship(node);
18501 	if (!shooter || !shooter->shipp)
18502 		return;
18503 	node = CDR(node);
18504 
18505 	for ( ; node >= 0; node = CDR(node) )
18506 	{
18507 		// get the subsystem
18508 		auto turret = ship_get_subsys(shooter->shipp, CTEXT(node));
18509 		if (!turret || turret->system_info->type != SUBSYSTEM_TURRET)
18510 			continue;
18511 
18512 		sexp_beam_free_one_turret(turret, is_beam, free);
18513 	}
18514 }
18515 
sexp_beam_or_turret_free_or_lock_all(int node,bool is_beam,bool free)18516 void sexp_beam_or_turret_free_or_lock_all(int node, bool is_beam, bool free)
18517 {
18518 	for (int n = node; n >= 0; n = CDR(n))
18519 	{
18520 		// get the firing ship
18521 		auto shooter = eval_ship(n);
18522 		if (!shooter || !shooter->shipp)
18523 			continue;
18524 
18525 		// visit all beam weapons
18526 		for (auto turret = GET_FIRST(&shooter->shipp->subsys_list); turret != END_OF_LIST(&shooter->shipp->subsys_list); turret = GET_NEXT(turret))
18527 		{
18528 			if (turret->system_info->type != SUBSYSTEM_TURRET)
18529 				continue;
18530 
18531 			sexp_beam_free_one_turret(turret, is_beam, free);
18532 		}
18533 	}
18534 }
18535 
sexp_turret_tagged_or_clear_specific(int node,bool set_it)18536 void sexp_turret_tagged_or_clear_specific(int node, bool set_it)
18537 {
18538 	// get the firing ship
18539 	auto shooter = eval_ship(node);
18540 	if (!shooter || !shooter->shipp)
18541 		return;
18542 	node = CDR(node);
18543 
18544 	for ( ; node >= 0; node = CDR(node))
18545 	{
18546 		// get the subsystem
18547 		auto turret = ship_get_subsys(shooter->shipp, CTEXT(node));
18548 		if (!turret || turret->system_info->type != SUBSYSTEM_TURRET)
18549 			continue;
18550 
18551 		// flag turret as slaved to tag
18552 		turret->weapons.flags.set(Ship::Weapon_Flags::Tagged_Only, set_it);
18553 	}
18554 }
18555 
sexp_turret_tagged_only_or_clear_all(int node,bool set_it)18556 void sexp_turret_tagged_only_or_clear_all(int node, bool set_it)
18557 {
18558 	for (int n = node; n >= 0; n = CDR(n))
18559 	{
18560 		// get the firing ship
18561 		auto shooter = eval_ship(n);
18562 		if (!shooter || !shooter->shipp)
18563 			continue;
18564 
18565 		// visit all turrets
18566 		for (auto turret = GET_FIRST(&shooter->shipp->subsys_list); turret != END_OF_LIST(&shooter->shipp->subsys_list); turret = GET_NEXT(turret))
18567 		{
18568 			if (turret->system_info->type != SUBSYSTEM_TURRET)
18569 				continue;
18570 
18571 			turret->weapons.flags.set(Ship::Weapon_Flags::Tagged_Only, set_it);
18572 		}
18573 	}
18574 }
18575 
sexp_turret_change_weapon(int node)18576 void sexp_turret_change_weapon(int node)
18577 {
18578 	int windex;	//hehe
18579 	ship_subsys *turret;
18580 	ship_weapon *swp;
18581 
18582 	// get the firing ship
18583 	auto ship_entry = eval_ship(node);
18584 	if (!ship_entry || !ship_entry->shipp) {
18585 		return;
18586 	}
18587 	node = CDR(node);
18588 
18589 	//Get subsystem
18590 	turret = ship_get_subsys(ship_entry->shipp, CTEXT(node));
18591 	if (!turret) {
18592 		return;
18593 	}
18594 	swp = &turret->weapons;
18595 	node = CDR(node);
18596 
18597 	windex = weapon_info_lookup(CTEXT(node));
18598 	if (windex < 0) {
18599 		return;
18600 	}
18601 	node = CDR(node);
18602 
18603 	//Get the slot
18604 	float capacity, size;
18605 	int prim_slot, sec_slot;
18606 	bool is_nan, is_nan_forever;
18607 
18608 	eval_nums(node, is_nan, is_nan_forever, prim_slot, sec_slot);
18609 	if (is_nan || is_nan_forever)
18610 		return;
18611 
18612 	if (prim_slot)
18613 	{
18614 		if (prim_slot > MAX_SHIP_PRIMARY_BANKS) {
18615 			return;
18616 		}
18617 
18618 		if (prim_slot > swp->num_primary_banks) {
18619 			swp->num_primary_banks++;
18620 			prim_slot = swp->num_primary_banks;
18621 		}
18622 
18623 		//Get an index
18624 		prim_slot--;
18625 
18626 		//Get the max capacity
18627 		capacity = (float)swp->primary_bank_capacity[prim_slot];
18628 		size = (float)Weapon_info[windex].cargo_size;
18629 
18630 		//Set various vars
18631 		swp->primary_bank_start_ammo[prim_slot] = (int)(capacity / size);
18632 		swp->primary_bank_ammo[prim_slot] = swp->primary_bank_start_ammo[prim_slot];
18633 		swp->primary_bank_weapons[prim_slot] = windex;
18634 		swp->primary_bank_rearm_time[prim_slot] = timestamp(0);
18635 		swp->primary_bank_fof_cooldown[prim_slot] = 0.0f;
18636 	}
18637 	else if (sec_slot)
18638 	{
18639 		if (sec_slot > MAX_SHIP_SECONDARY_BANKS) {
18640 			return;
18641 		}
18642 
18643 		if (sec_slot > swp->num_secondary_banks) {
18644 			swp->num_secondary_banks++;
18645 			sec_slot = swp->num_secondary_banks;
18646 		}
18647 
18648 		//Get an index
18649 		sec_slot--;
18650 
18651 		//Get the max capacity
18652 		capacity = (float)swp->secondary_bank_capacity[sec_slot];
18653 		size = (float)Weapon_info[windex].cargo_size;
18654 
18655 		//Set various vars
18656 		swp->secondary_bank_start_ammo[sec_slot] = (int)(capacity / size);
18657 		swp->secondary_bank_ammo[sec_slot] = swp->secondary_bank_start_ammo[sec_slot];
18658 		swp->secondary_bank_weapons[sec_slot] = windex;
18659 		swp->secondary_bank_rearm_time[sec_slot] = timestamp(0);
18660 	}
18661 }
18662 
sexp_set_armor_type(int node)18663 void sexp_set_armor_type(int node)
18664 {
18665 	int armor;
18666 	bool rset;
18667 	ship_subsys *ss = nullptr;
18668 	ship *shipp = nullptr;
18669 	ship_info *sip = nullptr;
18670 
18671 	// get ship
18672 	auto ship_entry = eval_ship(node);
18673 	if (!ship_entry || !ship_entry->shipp) {
18674 		return;
18675 	}
18676 	shipp = ship_entry->shipp;
18677 	sip = &Ship_info[shipp->ship_info_index];
18678 	node = CDR(node);
18679 
18680 	// set or reset
18681 	rset = is_sexp_true(node);
18682 	node = CDR(node);
18683 
18684 	// get armor
18685 	if (!stricmp(SEXP_NONE_STRING, CTEXT(node))) {
18686 		armor = -1;
18687 	} else {
18688 		armor = armor_type_get_idx(CTEXT(node));
18689 	}
18690 	node = CDR(node);
18691 
18692 	//Set armor
18693 	for (; node != -1; node = CDR(node))
18694 	{
18695 		if (!stricmp(SEXP_HULL_STRING, CTEXT(node)))
18696 		{
18697 			// we are setting the ship itself
18698 			if (!rset)
18699 				shipp->armor_type_idx = sip->armor_type_idx;
18700 			else
18701 				shipp->armor_type_idx = armor;
18702 		}
18703 		else if (!stricmp(SEXP_SHIELD_STRING, CTEXT(node)))
18704 		{
18705 			// we are setting the ships shields
18706 			if (!rset)
18707 				shipp->shield_armor_type_idx = sip->shield_armor_type_idx;
18708 			else
18709 				shipp->shield_armor_type_idx = armor;
18710 		}
18711 		else
18712 		{
18713 			// get the subsystem
18714 			ss = ship_get_subsys(shipp, CTEXT(node));
18715 			if(ss == nullptr){
18716 				continue;
18717 			}
18718 
18719 			// set the range
18720 			if (!rset)
18721 				ss->armor_type_idx = ss->system_info->armor_type_idx;
18722 			else
18723 				ss->armor_type_idx = armor;
18724 		}
18725 	}
18726 }
18727 
sexp_weapon_set_damage_type(int node)18728 void sexp_weapon_set_damage_type(int node)
18729 {
18730 	int windex, damage;
18731 	bool swave, rset;
18732 	size_t t;
18733 
18734 	// weapon or shockwave
18735 	swave = is_sexp_true(node);
18736 
18737 	// get damage type
18738 	node = CDR(node);
18739 	if (!stricmp(SEXP_NONE_STRING, CTEXT(node)))
18740 		damage = -1;
18741 	else
18742 	{
18743 		for(t = 0; t < Damage_types.size(); t++)
18744 		{
18745 			if ( !stricmp(Damage_types[t].name, CTEXT(node)))
18746 				break;
18747 		}
18748 		if (t == Damage_types.size())
18749 			return;
18750 		damage = (int)t;
18751 	}
18752 
18753 	//Set or reset to default
18754 	node = CDR(node);
18755 	rset = is_sexp_true(node);
18756 
18757 	//Set Damage
18758 	node = CDR(node);
18759 	while(node != -1)
18760 	{
18761 		// get the weapon
18762 		windex = weapon_info_lookup(CTEXT(node));
18763 		if(windex >= 0)
18764 		{
18765 			// set the damage type
18766 			if (swave)
18767 				if (!rset)
18768 					Weapon_info[windex].damage_type_idx = Weapon_info[windex].damage_type_idx_sav;
18769 				else
18770 					Weapon_info[windex].damage_type_idx = damage;
18771 			else
18772 				if (!rset)
18773 					Weapon_info[windex].shockwave.damage_type_idx = Weapon_info[windex].shockwave.damage_type_idx_sav;
18774 				else
18775 					Weapon_info[windex].shockwave.damage_type_idx = damage;
18776 		// next
18777 		}
18778 		node = CDR(node);
18779 	}
18780 }
18781 
sexp_ship_set_damage_type(int node)18782 void sexp_ship_set_damage_type(int node)
18783 {
18784 	int damage;
18785 	bool set_collision, rset;
18786 	size_t t;
18787 
18788 	// collision or debris
18789 	set_collision = is_sexp_true(node);
18790 
18791 	// get damage type
18792 	node = CDR(node);
18793 	if (!stricmp(SEXP_NONE_STRING, CTEXT(node)))
18794 		damage = -1;
18795 	else
18796 	{
18797 		for(t = 0; t < Damage_types.size(); t++)
18798 		{
18799 			if ( !stricmp(Damage_types[t].name, CTEXT(node)))
18800 				break;
18801 		}
18802 		if (t == Damage_types.size())
18803 			return;
18804 		damage = (int)t;
18805 	}
18806 
18807 	//Set or reset to default
18808 	node = CDR(node);
18809 	rset = is_sexp_true(node);
18810 
18811 	//Set Damage
18812 	node = CDR(node);
18813 	while(node != -1)
18814 	{
18815 		// get the ship
18816 		auto ship_entry = eval_ship(node);
18817 		if (ship_entry && ship_entry->shipp)
18818 		{
18819 			auto shipp = ship_entry->shipp;
18820 
18821 			// set the damage type
18822 			if (set_collision)
18823 			{
18824 				if (!rset)
18825 					shipp->collision_damage_type_idx = Ship_info[shipp->ship_info_index].collision_damage_type_idx;
18826 				else
18827 					shipp->collision_damage_type_idx = damage;
18828 			}
18829 			else
18830 			{
18831 				if (!rset)
18832 					shipp->debris_damage_type_idx = Ship_info[shipp->ship_info_index].debris_damage_type_idx;
18833 				else
18834 					shipp->debris_damage_type_idx = damage;
18835 			}
18836 		}
18837 
18838 		// next
18839 		node = CDR(node);
18840 	}
18841 }
18842 
sexp_ship_shockwave_set_damage_type(int node)18843 void sexp_ship_shockwave_set_damage_type(int node)
18844 {
18845 	int sindex, damage;
18846 	bool rset;
18847 	size_t t;
18848 
18849 	// get damage type
18850 	if (!stricmp(SEXP_NONE_STRING, CTEXT(node)))
18851 		damage = -1;
18852 	else
18853 	{
18854 		for(t = 0; t < Damage_types.size(); t++)
18855 		{
18856 			if ( !stricmp(Damage_types[t].name, CTEXT(node)))
18857 				break;
18858 		}
18859 		if (t == Damage_types.size())
18860 			return;
18861 		damage = (int)t;
18862 	}
18863 
18864 	//Set or reset to default
18865 	node = CDR(node);
18866 	rset = is_sexp_true(node);
18867 
18868 	//Set Damage
18869 	node = CDR(node);
18870 	while(node != -1)
18871 	{
18872 		// get the ship
18873 		sindex = ship_info_lookup(CTEXT(node));
18874 		if(sindex >= 0)
18875 		{
18876 			// set the damage type
18877 			if (!rset)
18878 				Ship_info[sindex].shockwave.damage_type_idx = Ship_info[sindex].shockwave.damage_type_idx_sav;
18879 			else
18880 				Ship_info[sindex].shockwave.damage_type_idx = damage;
18881 		}
18882 
18883 		// next
18884 		node = CDR(node);
18885 	}
18886 }
18887 
sexp_field_set_damage_type(int node)18888 void sexp_field_set_damage_type(int node)
18889 {
18890 	int damage, rset;
18891 	size_t t;
18892 
18893 	// get damage type
18894 	if (!stricmp(SEXP_NONE_STRING, CTEXT(node)))
18895 		damage = -1;
18896 	else
18897 	{
18898 		for(t = 0; t < Damage_types.size(); t++)
18899 		{
18900 			if ( !stricmp(Damage_types[t].name, CTEXT(node)))
18901 				break;
18902 		}
18903 		if (t == Damage_types.size())
18904 			return;
18905 		damage = (int)t;
18906 	}
18907 
18908 	//Set or reset to default
18909 	node = CDR(node);
18910 	rset = is_sexp_true(node);
18911 
18912 	//Set Damage
18913 	node = CDR(node);
18914 	for(t = 0; t < Asteroid_info.size(); t++)
18915 	if (!rset)
18916 		Asteroid_info[t].damage_type_idx = Asteroid_info[t].damage_type_idx_sav;
18917 	else
18918 		Asteroid_info[t].damage_type_idx = damage;
18919 }
18920 
sexp_turret_set_target_order(int node)18921 void sexp_turret_set_target_order(int node)
18922 {
18923 	int i, oindex;
18924 
18925 	// get ship
18926 	auto ship_entry = eval_ship(node);
18927 	if (!ship_entry || !ship_entry->shipp) {
18928 		return;
18929 	}
18930 
18931 	//Get turret subsys
18932 	node = CDR(node);
18933 	auto turret = ship_get_subsys(ship_entry->shipp, CTEXT(node));
18934 	if(turret == nullptr){
18935 		return;
18936 	}
18937 
18938 	//Reset order
18939 	for(i = 0; i < NUM_TURRET_ORDER_TYPES; i++) {
18940 		turret->turret_targeting_order[i] = -1;
18941 	}
18942 
18943 	oindex = 0;
18944 	node = CDR(node);
18945 	while(node != -1)
18946 	{
18947 		if(oindex >= NUM_TURRET_ORDER_TYPES) {
18948 			break;
18949 		}
18950 
18951 		for(i = 0; i < NUM_TURRET_ORDER_TYPES; i++) {
18952 			if(!stricmp(Turret_target_order_names[i], CTEXT(node))) {
18953 				turret->turret_targeting_order[oindex] = i;
18954 			}
18955 		}
18956 
18957 		oindex++;
18958 		node = CDR(node);
18959 	}
18960 }
18961 
sexp_turret_set_direction_preference(int node)18962 void sexp_turret_set_direction_preference(int node)
18963 {
18964 	bool is_nan, is_nan_forever;
18965 
18966 	// get ship
18967 	auto ship_entry = eval_ship(node);
18968 	if (!ship_entry || !ship_entry->shipp) {
18969 		return;
18970 	}
18971 
18972 	//store direction preference
18973 	node = CDR(node);
18974 	int dirpref = eval_num(node, is_nan, is_nan_forever);
18975 	if (is_nan || is_nan_forever)
18976 		return;
18977 	node = CDR(node);
18978 
18979 	//Set range
18980 	for (; node != -1; node = CDR(node)) {
18981 		// get the subsystem
18982 		auto turret = ship_get_subsys(ship_entry->shipp, CTEXT(node));
18983 		if(turret == nullptr){
18984 			continue;
18985 		}
18986 
18987 		// set the range
18988 		if(dirpref < 0)
18989 			turret->optimum_range = turret->system_info->optimum_range;
18990 		else
18991 			if (dirpref == 0) {
18992 				turret->favor_current_facing = 0.0f;
18993 			} else {
18994 				CAP(dirpref, 1, 100);
18995 				turret->favor_current_facing = 1.0f + (((float) (100 - dirpref)) / 10.0f);
18996 			}
18997 	}
18998 }
18999 
sexp_turret_set_rate_of_fire(int node)19000 void sexp_turret_set_rate_of_fire(int node)
19001 {
19002 	float rof;
19003 	bool is_nan, is_nan_forever;
19004 
19005 	// get ship
19006 	auto ship_entry = eval_ship(node);
19007 	if (!ship_entry || !ship_entry->shipp) {
19008 		return;
19009 	}
19010 
19011 	//store rof
19012 	node = CDR(node);
19013 	rof = (float)eval_num(node, is_nan, is_nan_forever);
19014 	if (is_nan || is_nan_forever)
19015 		return;
19016 	node = CDR(node);
19017 
19018 	//Set rof
19019 	while (node >= 0)
19020 	{
19021 		// get the subsystem
19022 		auto turret = ship_get_subsys(ship_entry->shipp, CTEXT(node));
19023 		if (turret != nullptr)
19024 		{
19025 			// set the range
19026 			if (rof < 0)
19027 				turret->rof_scaler = turret->system_info->turret_rof_scaler;
19028 			else
19029 				turret->rof_scaler = rof / 100;
19030 		}
19031 
19032 		// next
19033 		node = CDR(node);
19034 	}
19035 }
19036 
sexp_turret_set_optimum_range(int node)19037 void sexp_turret_set_optimum_range(int node)
19038 {
19039 	float range;
19040 	bool is_nan, is_nan_forever;
19041 
19042 	// get ship
19043 	auto ship_entry = eval_ship(node);
19044 	if (!ship_entry || !ship_entry->shipp) {
19045 		return;
19046 	}
19047 
19048 	// store range
19049 	node = CDR(node);
19050 	range = (float)eval_num(node, is_nan, is_nan_forever);
19051 	if (is_nan || is_nan_forever)
19052 		return;
19053 	node = CDR(node);
19054 
19055 	//Set range
19056 	while (node >= 0)
19057 	{
19058 		// get the subsystem
19059 		auto turret = ship_get_subsys(ship_entry->shipp, CTEXT(node));
19060 		if (turret != nullptr)
19061 		{
19062 			// set the range
19063 			if (range < 0)
19064 				turret->optimum_range = turret->system_info->optimum_range;
19065 			else
19066 				turret->optimum_range = range;
19067 		}
19068 
19069 		// next
19070 		node = CDR(node);
19071 	}
19072 }
19073 
sexp_turret_set_target_priorities(int node)19074 void sexp_turret_set_target_priorities(int node)
19075 {
19076 	int i, j;
19077 
19078 	// get ship
19079 	auto ship_entry = eval_ship(node);
19080 	if (!ship_entry || !ship_entry->shipp) {
19081 		return;
19082 	}
19083 
19084 	//Get turret subsys
19085 	node = CDR(node);
19086 	auto turret = ship_get_subsys(ship_entry->shipp, CTEXT(node));
19087 	if(turret == nullptr){
19088 		return;
19089 	}
19090 
19091 	//Reset or new list
19092 	node = CDR(node);
19093 	if(!(is_sexp_true(node))) {	//Reset
19094 		turret->num_target_priorities = turret->system_info->num_target_priorities;
19095 		for (j = 0; j < 32; j++) {
19096 			turret->target_priority[j] = turret->system_info->target_priority[j];
19097 		}
19098 	}
19099 	else {					//New List
19100 		node = CDR(node);
19101 		//clear the list
19102 		turret->num_target_priorities = 0;
19103 		for (i = 0; i < 32; i++) {
19104 			turret->target_priority[i] = -1;
19105 		}
19106 		int num_groups = (int)Ai_tp_list.size();
19107 		// set the target priorities
19108 		while(node != -1){
19109 			if(turret->num_target_priorities < 32){
19110 				for(j = 0; j < num_groups; j++) {
19111 					if ( !stricmp(Ai_tp_list[j].name, CTEXT(node)))  {
19112 							turret->target_priority[turret->num_target_priorities] = j;
19113 							turret->num_target_priorities++;
19114 					}
19115 				}
19116 			}
19117 		// next
19118 		node = CDR(node);
19119 		}
19120 	}
19121 }
19122 
sexp_turret_set_forced_target(int node,bool targeting_subsys)19123 void sexp_turret_set_forced_target(int node, bool targeting_subsys)
19124 {
19125 	// get target
19126 	auto target = eval_ship(node);
19127 	if (!target || !target->shipp)
19128 		return;
19129 	node = CDR(node);
19130 
19131 	// maybe get target subsys
19132 	ship_subsys* target_subsys = nullptr;
19133 	if (targeting_subsys) {
19134 		target_subsys = ship_get_subsys(target->shipp, CTEXT(node));
19135 		if (target_subsys == nullptr)
19136 			return;
19137 		node = CDR(node);
19138 	}
19139 
19140 	// get ship
19141 	auto shooter = eval_ship(node);
19142 	if (!shooter || !shooter->shipp) {
19143 		return;
19144 	}
19145 	node = CDR(node);
19146 
19147 	while (node >= 0)
19148 	{
19149 		// get the turret
19150 		auto turret = ship_get_subsys(shooter->shipp, CTEXT(node));
19151 		if (turret != nullptr)
19152 		{
19153 			turret->turret_enemy_objnum = OBJ_INDEX(target->objp);
19154 			turret->turret_enemy_sig = target->objp->signature;
19155 			turret->flags.set(Ship::Subsystem_Flags::Forced_target);
19156 			turret->targeted_subsys = nullptr;
19157 
19158 			if (targeting_subsys) {
19159 				turret->targeted_subsys = target_subsys;
19160 				turret->flags.set(Ship::Subsystem_Flags::Forced_subsys_target);
19161 			}
19162 		}
19163 
19164 		// next
19165 		node = CDR(node);
19166 	}
19167 }
19168 
sexp_turret_clear_forced_target(int node)19169 void sexp_turret_clear_forced_target(int node)
19170 {
19171 	// get ship
19172 	auto ship_entry = eval_ship(node);
19173 	if (!ship_entry || !ship_entry->shipp) {
19174 		return;
19175 	}
19176 
19177 	//Set range
19178 	while (node >= 0)
19179 	{
19180 		// get the subsystem
19181 		auto turret = ship_get_subsys(ship_entry->shipp, CTEXT(node));
19182 		if (turret != nullptr)
19183 		{
19184 			turret->turret_enemy_objnum = -1;
19185 			turret->targeted_subsys = nullptr;
19186 			turret->flags.remove(Ship::Subsystem_Flags::Forced_target);
19187 			turret->flags.remove(Ship::Subsystem_Flags::Forced_subsys_target);
19188 		}
19189 
19190 		// next
19191 		node = CDR(node);
19192 	}
19193 }
19194 
sexp_turret_set_inaccuracy(int node)19195 void sexp_turret_set_inaccuracy(int node)
19196 {
19197 	bool is_nan, is_nan_forever;
19198 	// get ship
19199 	auto shooter = eval_ship(node);
19200 	if (!shooter || !shooter->shipp) {
19201 		return;
19202 	}
19203 
19204 	// get inaccuracy
19205 	node = CDR(node);
19206 	auto inaccuracy = (float)eval_num(node, is_nan, is_nan_forever);
19207 	if (is_nan || is_nan_forever)
19208 		return;
19209 
19210 	if (inaccuracy < 0.0f)
19211 		inaccuracy = 0.0f;
19212 
19213 	// input is in tenths so divide by 10
19214 	inaccuracy /= 10.0f;
19215 
19216 	node = CDR(node);
19217 	if (node < 0) { // affect all turrets
19218 		for (auto turret = GET_FIRST(&shooter->shipp->subsys_list); turret != END_OF_LIST(&shooter->shipp->subsys_list); turret = GET_NEXT(turret))
19219 		{
19220 			if (turret->system_info->type != SUBSYSTEM_TURRET)
19221 				continue;
19222 
19223 			turret->turret_inaccuracy = inaccuracy;
19224 		}
19225 	} else { // affect just some particular turret(s)
19226 		while (node >= 0)
19227 		{
19228 			// get the subsystem
19229 			ship_subsys* turret = ship_get_subsys(shooter->shipp, CTEXT(node));
19230 			if (turret != nullptr)
19231 				turret->turret_inaccuracy = inaccuracy;
19232 
19233 			// next
19234 			node = CDR(node);
19235 		}
19236 	}
19237 }
19238 
sexp_ship_turret_target_order(int node)19239 void sexp_ship_turret_target_order(int node)
19240 {
19241 	int oindex;
19242 	int i;
19243 	int new_target_order[NUM_TURRET_ORDER_TYPES];
19244 
19245 	// get ship
19246 	auto ship_entry = eval_ship(node);
19247 	if (!ship_entry || !ship_entry->shipp) {
19248 		return;
19249 	}
19250 
19251 	//Reset order
19252 	for(i = 0; i < NUM_TURRET_ORDER_TYPES; i++) {
19253 		new_target_order[i] = -1;
19254 	}
19255 
19256 	oindex = 0;
19257 	node = CDR(node);
19258 	while(node != -1)
19259 	{
19260 		if(oindex >= NUM_TURRET_ORDER_TYPES) {
19261 			break;
19262 		}
19263 
19264 		for(i = 0; i < NUM_TURRET_ORDER_TYPES; i++) {
19265 			if(!stricmp(Turret_target_order_names[i], CTEXT(node))) {
19266 				new_target_order[oindex] = i;
19267 			}
19268 		}
19269 
19270 		oindex++;
19271 		node = CDR(node);
19272 	}
19273 
19274 	for (auto turret = GET_FIRST(&ship_entry->shipp->subsys_list); turret != END_OF_LIST(&ship_entry->shipp->subsys_list); turret = GET_NEXT(turret))
19275 	{
19276 		memcpy(turret->turret_targeting_order, new_target_order, NUM_TURRET_ORDER_TYPES*sizeof(int));
19277 	}
19278 }
19279 
19280 // Goober5000
sexp_is_in_turret_fov(int node)19281 int sexp_is_in_turret_fov(int node)
19282 {
19283 	int n = node, range;
19284 	bool is_nan, is_nan_forever;
19285 	vec3d tpos, tvec;
19286 
19287 	auto target_ship = eval_ship(n);
19288 	if (!target_ship || target_ship->status == ShipStatus::NOT_YET_PRESENT)
19289 		return SEXP_FALSE;
19290 	if (target_ship->status == ShipStatus::EXITED)
19291 		return SEXP_KNOWN_FALSE;
19292 	n = CDR(n);
19293 
19294 	auto turret_ship = eval_ship(n);
19295 	if (!turret_ship || turret_ship->status == ShipStatus::NOT_YET_PRESENT)
19296 		return SEXP_FALSE;
19297 	if (turret_ship->status == ShipStatus::EXITED)
19298 		return SEXP_KNOWN_FALSE;
19299 	n = CDR(n);
19300 
19301 	auto turret_subsys_name = CTEXT(n);
19302 	n = CDR(n);
19303 
19304 	if (n >= 0) {
19305 		range = eval_num(n, is_nan, is_nan_forever);
19306 		if (is_nan)
19307 			return SEXP_FALSE;
19308 		if (is_nan_forever)
19309 			return SEXP_KNOWN_FALSE;
19310 	}
19311 	else
19312 		range = -1;
19313 
19314 	// find the turret
19315 	auto turret_subsys = ship_get_subsys(turret_ship->shipp, turret_subsys_name);
19316 	if (turret_subsys == nullptr) {
19317 		Warning(LOCATION, "Couldn't find turret subsystem '%s' on ship '%s' in sexp_is_in_turret_fov!", turret_subsys_name, turret_ship->name);
19318 		return SEXP_KNOWN_FALSE;
19319 	}
19320 
19321 	// find out where the turret is
19322 	ship_get_global_turret_info(turret_ship->objp, turret_subsys->system_info, &tpos, &tvec);
19323 
19324 	// see how far away is the target (this isn't used for a range check, only for vector math)
19325 	float dist = vm_vec_dist(&target_ship->objp->pos, &tpos);
19326 
19327 	// but we can still use it for the range check if we are optionally checking that
19328 	if (range >= 0 && dist > range)
19329 		return SEXP_FALSE;
19330 
19331 	// perform the check
19332 	return object_in_turret_fov(target_ship->objp, turret_subsys, &tvec, &tpos, dist) != 0 ? SEXP_TRUE : SEXP_FALSE;
19333 }
19334 
19335 // Goober5000
sexp_set_subsys_rotation_lock_free(int node,bool locked)19336 void sexp_set_subsys_rotation_lock_free(int node, bool locked)
19337 {
19338 	// get the ship
19339 	auto ship_entry = eval_ship(node);
19340 	if (!ship_entry || !ship_entry->shipp)
19341 		return;
19342 	node = CDR(node);
19343 
19344 	// loop for all specified subsystems
19345 	for ( ; node >= 0; node = CDR(node) )
19346 	{
19347 		// get the rotating subsystem
19348 		auto rotate = ship_get_subsys(ship_entry->shipp, CTEXT(node));
19349 		if (rotate == nullptr)
19350 			continue;
19351 
19352 		// set rotate or not, depending on flag
19353 		rotate->flags.set(Ship::Subsystem_Flags::Rotates, !locked);
19354 		if (locked)
19355 		{
19356 			if (rotate->subsys_snd_flags[Ship::Subsys_Sound_Flags::Rotate])
19357 			{
19358 				obj_snd_delete_type(ship_entry->shipp->objnum, rotate->system_info->rotation_snd, rotate);
19359 				rotate->subsys_snd_flags.remove(Ship::Subsys_Sound_Flags::Rotate);
19360 			}
19361 		}
19362 		else
19363 		{
19364 			if (rotate->system_info->rotation_snd.isValid())
19365 			{
19366 				obj_snd_assign(ship_entry->shipp->objnum, rotate->system_info->rotation_snd, &rotate->system_info->pnt, OS_SUBSYS_ROTATION, rotate);
19367 				rotate->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Rotate);
19368 			}
19369 		}
19370 	}
19371 }
19372 
19373 // Goober5000
sexp_reverse_rotating_subsystem(int node)19374 void sexp_reverse_rotating_subsystem(int node)
19375 {
19376 	// get the ship
19377 	auto ship_entry = eval_ship(node);
19378 	if (!ship_entry || !ship_entry->shipp)
19379 		return;
19380 	node = CDR(node);
19381 
19382 	// loop for all specified subsystems
19383 	for ( ; node >= 0; node = CDR(node) )
19384 	{
19385 		// get the rotating subsystem
19386 		auto rotate = ship_get_subsys(ship_entry->shipp, CTEXT(node));
19387 		if (rotate == nullptr || rotate->submodel_instance_1 == nullptr)
19388 			continue;
19389 
19390 		// switch direction of rotation
19391 		rotate->turn_rate *= -1.0f;
19392 		rotate->submodel_instance_1->current_turn_rate *= -1.0f;
19393 		rotate->submodel_instance_1->desired_turn_rate *= -1.0f;
19394 	}
19395 }
19396 
19397 // Goober5000
sexp_rotating_subsys_set_turn_time(int node)19398 void sexp_rotating_subsys_set_turn_time(int node)
19399 {
19400 	int n = node;
19401 	bool is_nan, is_nan_forever;
19402 	float turn_time, turn_accel;
19403 
19404 	// get the ship
19405 	auto ship_entry = eval_ship(n);
19406 	if (!ship_entry || !ship_entry->shipp)
19407 		return;
19408 	n = CDR(n);
19409 
19410 	// get the rotating subsystem
19411 	auto rotate = ship_get_subsys(ship_entry->shipp, CTEXT(n));
19412 	if (rotate == nullptr || rotate->submodel_instance_1 == nullptr)
19413 		return;
19414 	n = CDR(n);
19415 
19416 	// get and set the turn time
19417 	turn_time = eval_num(n, is_nan, is_nan_forever) / 1000.0f;
19418 	if (is_nan || is_nan_forever)
19419 		return;
19420 	rotate->submodel_instance_1->desired_turn_rate = PI2 / turn_time;
19421 	n = CDR(n);
19422 
19423 	// maybe get and set the turn accel
19424 	if (n != -1)
19425 	{
19426 		turn_accel = eval_num(n, is_nan, is_nan_forever) / 1000.0f;
19427 		if (is_nan || is_nan_forever)
19428 			return;
19429 		rotate->submodel_instance_1->turn_accel = PI2 / turn_accel;
19430 	}
19431 	else
19432 		rotate->submodel_instance_1->current_turn_rate = PI2 / turn_time;
19433 }
19434 
sexp_trigger_submodel_animation(int node)19435 void sexp_trigger_submodel_animation(int node)
19436 {
19437 	int animation_subtype, direction, n = node;
19438 	bool instant, is_nan, is_nan_forever;
19439 
19440 	// get the ship
19441 	auto ship_entry = eval_ship(n);
19442 	if (!ship_entry || !ship_entry->shipp)
19443 		return;
19444 	n = CDR(n);
19445 
19446 	// get the type
19447 	auto animation_type = model_anim_match_type(CTEXT(n));
19448 	if (animation_type == AnimationTriggerType::None)
19449 	{
19450 		Warning(LOCATION, "Unable to match animation type \"%s\"!", CTEXT(n));
19451 		return;
19452 	}
19453 	n = CDR(n);
19454 
19455 	// get the subtype and direction
19456 	eval_nums(n, is_nan, is_nan_forever, animation_subtype, direction);
19457 	if (is_nan || is_nan_forever)
19458 		return;
19459 
19460 	if (direction != 1 && direction != -1)
19461 	{
19462 		Warning(LOCATION, "Direction is %d; it must be 1 or -1!", direction);
19463 		return;
19464 	}
19465 
19466 	// instant or not
19467 	if (n >= 0)
19468 	{
19469 		instant = is_sexp_true(n);
19470 		n = CDR(n);
19471 	}
19472 	else
19473 		instant = false;
19474 
19475 	// do we narrow it to a specific subsystem?
19476 	if (n >= 0)
19477 	{
19478 		ship_subsys *ss = ship_get_subsys(ship_entry->shipp, CTEXT(n));
19479 		if (!ss)
19480 		{
19481 			Warning(LOCATION, "Subsystem \"%s\" not found on ship \"%s\"!", CTEXT(n), CTEXT(node));
19482 			return;
19483 		}
19484 		model_anim_start_type(ss, animation_type, animation_subtype, direction, instant);
19485 	}
19486 	else
19487 	{
19488 		model_anim_start_type(ship_entry->shipp, animation_type, animation_subtype, direction, instant);
19489 	}
19490 }
19491 
sexp_add_remove_escort(int node)19492 void sexp_add_remove_escort(int node)
19493 {
19494 	int flag;
19495 	bool is_nan, is_nan_forever;
19496 
19497 	// get the firing ship
19498 	auto ship_entry = eval_ship(node);
19499 	if (!ship_entry || !ship_entry->shipp) {
19500 		return;
19501 	}
19502 
19503 	// determine whether to add or remove it
19504 	flag = eval_num(CDR(node), is_nan, is_nan_forever);
19505 	if (is_nan || is_nan_forever) {
19506 		return;
19507 	}
19508 
19509 	// add/remove
19510 	if (flag) {
19511 		ship_entry->shipp->escort_priority = flag;
19512 		hud_add_ship_to_escort(ship_entry->shipp->objnum, 1);
19513 	} else {
19514 		hud_remove_ship_from_escort(ship_entry->shipp->objnum);
19515 	}
19516 
19517 	Current_sexp_network_packet.start_callback();
19518 	Current_sexp_network_packet.send_ship(ship_entry->shipp);
19519 	Current_sexp_network_packet.send_int(flag);
19520 	Current_sexp_network_packet.end_callback();
19521 }
19522 
multi_sexp_add_remove_escort()19523 void multi_sexp_add_remove_escort()
19524 {
19525 	ship *shipp;
19526 	int flag;
19527 
19528 	Current_sexp_network_packet.get_ship(shipp);
19529 	if (!(Current_sexp_network_packet.get_int(flag))) {
19530 		return;
19531 	}
19532 
19533 	// add/remove
19534 	if (flag) {
19535 		shipp->escort_priority = flag;
19536 		hud_add_ship_to_escort(shipp->objnum, 1);
19537 	} else {
19538 		hud_remove_ship_from_escort(shipp->objnum);
19539 	}
19540 }
19541 
19542 //given: two escort priorities and a list of ships
19543 //do:    sets the most damaged one to the first priority and the rest to the second.
sexp_damage_escort_list(int node)19544 void sexp_damage_escort_list(int node)
19545 {
19546 	int n = node;
19547 	int priority1;		//escort priority to set the most damaged ship
19548 	int priority2;		//""         ""   to set the other ships
19549 	bool is_nan, is_nan_forever;
19550 
19551 	float smallest_hull_pct=1;		//smallest hull pct found
19552 	ship *small_shipp=nullptr;		//entry in Ships[] of the above
19553 	float current_hull_pct;			//hull pct of current ship we are evaluating
19554 
19555 	eval_nums(n, is_nan, is_nan_forever, priority1, priority2);
19556 	if (is_nan || is_nan_forever)
19557 		return;
19558 
19559 	//loop through the ships
19560 	for ( ; n != -1; n = CDR(n) )
19561 	{
19562 		auto ship_entry = eval_ship(n);
19563 		if (!ship_entry || !ship_entry->shipp)
19564 			continue;
19565 
19566 		//calc hull integrity and compare
19567 		current_hull_pct = get_hull_pct(ship_entry->objp);
19568 
19569 		if (current_hull_pct < smallest_hull_pct)
19570 		{
19571 			if (small_shipp)	// avoid null during 1st loop iteration
19572 			{
19573 				small_shipp->escort_priority=priority2;				//give the previous smallest the lower priority
19574 			}
19575 
19576 			smallest_hull_pct = current_hull_pct;
19577 			small_shipp = ship_entry->shipp;
19578 
19579 			ship_entry->shipp->escort_priority = priority1;			//give the new smallest the higher priority
19580 			hud_add_ship_to_escort(ship_entry->shipp->objnum, 1);
19581 		}
19582 		else														//if its bigger to begin with give it lower priority
19583 		{
19584 			ship_entry->shipp->escort_priority = priority2;
19585 			hud_add_ship_to_escort(ship_entry->shipp->objnum, 1);
19586 		}
19587 	}
19588 }
19589 
19590 // Goober5000 - set stuff for mission support ship
sexp_set_support_ship(int n)19591 void sexp_set_support_ship(int n)
19592 {
19593 	int i, temp_val;
19594 	bool is_nan, is_nan_forever;
19595 
19596 	// get arrival location
19597 	temp_val = -1;
19598 	for (i = 0; i < MAX_ARRIVAL_NAMES; i++)
19599 	{
19600 		if (!stricmp(CTEXT(n), Arrival_location_names[i]))
19601 			temp_val = i;
19602 	}
19603 	if (temp_val < 0)
19604 	{
19605 		Warning(LOCATION, "Support ship arrival location '%s' not found.\n", CTEXT(n));
19606 		return;
19607 	}
19608 	The_mission.support_ships.arrival_location = temp_val;
19609 
19610 	// get arrival anchor
19611 	n = CDR(n);
19612 	if (!stricmp(CTEXT(n), "<no anchor>"))
19613 	{
19614 		// if no anchor, set arrival location to hyperspace
19615 		The_mission.support_ships.arrival_location = 0;
19616 	}
19617 	else
19618 	{
19619 		// anchor must exist - look for it
19620 		temp_val = -1;
19621 		for (i = 0; i < Num_parse_names; i++)
19622 		{
19623 			if (!stricmp(CTEXT(n), Parse_names[i]))
19624 				temp_val = i;
19625 		}
19626 		// if not found, make a new entry
19627 		if (temp_val < 0)
19628 		{
19629 			strcpy_s(Parse_names[Num_parse_names], CTEXT(n));
19630 			temp_val = Num_parse_names;
19631 			Num_parse_names++;
19632 		}
19633 		The_mission.support_ships.arrival_anchor = temp_val;
19634 	}
19635 
19636 	// get departure location
19637 	n = CDR(n);
19638 	temp_val = -1;
19639 	for (i = 0; i < MAX_DEPARTURE_NAMES; i++)
19640 	{
19641 		if (!stricmp(CTEXT(n), Departure_location_names[i]))
19642 			temp_val = i;
19643 	}
19644 	if (temp_val < 0)
19645 	{
19646 		Warning(LOCATION, "Support ship departure location '%s' not found.\n", CTEXT(n));
19647 		return;
19648 	}
19649 	The_mission.support_ships.departure_location = temp_val;
19650 
19651 	// get departure anchor
19652 	n = CDR(n);
19653 	if (!stricmp(CTEXT(n), "<no anchor>"))
19654 	{
19655 		// if no anchor, set departure location to hyperspace
19656 		The_mission.support_ships.departure_location = 0;
19657 	}
19658 	else
19659 	{
19660 		// anchor must exist - look for it
19661 		temp_val = -1;
19662 		for (i = 0; i < Num_parse_names; i++)
19663 		{
19664 			if (!stricmp(CTEXT(n), Parse_names[i]))
19665 				temp_val = i;
19666 		}
19667 		// if not found, make a new entry
19668 		if (temp_val < 0)
19669 		{
19670 			strcpy_s(Parse_names[Num_parse_names], CTEXT(n));
19671 			temp_val = Num_parse_names;
19672 			Num_parse_names++;
19673 		}
19674 		The_mission.support_ships.departure_anchor = temp_val;
19675 	}
19676 
19677 	// get ship class
19678 	n = CDR(n);
19679 	temp_val = ship_info_lookup(CTEXT(n));
19680 	if ((temp_val < 0) && ((stricmp(CTEXT(n), "<species support ship class>") != 0) && (stricmp(CTEXT(n), "<any support ship class>") != 0)))
19681 	{
19682 		Warning(LOCATION, "Support ship class '%s' not found.\n", CTEXT(n));
19683 		return;
19684 	}
19685 	if ((temp_val >= 0) && !(Ship_info[temp_val].flags[Ship::Info_Flags::Support]))
19686 	{
19687 		Warning(LOCATION, "Ship %s is not a support ship!", Ship_info[temp_val].name);
19688 		return;
19689 	}
19690 	The_mission.support_ships.ship_class = temp_val;
19691 
19692 	// get max number of ships allowed
19693 	n = CDR(n);
19694 	temp_val = eval_num(n, is_nan, is_nan_forever);
19695 	if (!is_nan && !is_nan_forever) {
19696 		The_mission.support_ships.max_support_ships = temp_val;
19697 	}
19698 
19699 	// get the number of concurrent ships allowed
19700 	n = CDR(n);
19701 	if ( n == -1 ) {
19702 		// 7th arg not specified, set default
19703 		The_mission.support_ships.max_concurrent_ships = 1;
19704 	} else {
19705 		temp_val = eval_num(n, is_nan, is_nan_forever);
19706 		if (!is_nan && !is_nan_forever) {
19707 			The_mission.support_ships.max_concurrent_ships = temp_val;
19708 		}
19709 	}
19710 }
19711 
19712 // Goober5000 - set stuff for arriving ships or wings
sexp_set_arrival_info(int node)19713 void sexp_set_arrival_info(int node)
19714 {
19715 	int i, arrival_location, arrival_anchor, arrival_mask, arrival_distance, arrival_delay, n = node;
19716 	bool show_warp, is_nan, is_nan_forever;
19717 	object_ship_wing_point_team oswpt;
19718 
19719 	// get ship or wing
19720 	eval_object_ship_wing_point_team(&oswpt, n);
19721 	n = CDR(n);
19722 
19723 	// get arrival location
19724 	arrival_location = -1;
19725 	for (i=0; i<MAX_ARRIVAL_NAMES; i++)
19726 	{
19727 		if (!stricmp(CTEXT(n), Arrival_location_names[i]))
19728 			arrival_location = i;
19729 	}
19730 	if (arrival_location < 0)
19731 	{
19732 		Warning(LOCATION, "Arrival location '%s' not found.\n", CTEXT(n));
19733 		return;
19734 	}
19735 	n = CDR(n);
19736 
19737 	// get arrival anchor
19738 	arrival_anchor = -1;
19739 	if ((n < 0) || !stricmp(CTEXT(n), "<no anchor>"))
19740 	{
19741 		// if no anchor, set arrival location to hyperspace
19742 		arrival_location = 0;
19743 	}
19744 	else
19745 	{
19746 		// anchor must exist - look for it
19747 		for (i=0; i<Num_parse_names; i++)
19748 		{
19749 			if (!stricmp(CTEXT(n), Parse_names[i]))
19750 				arrival_anchor = i;
19751 		}
19752 		// if not found, make a new entry
19753 		if (arrival_anchor < 0)
19754 		{
19755 			strcpy_s(Parse_names[Num_parse_names], CTEXT(n));
19756 			arrival_anchor = Num_parse_names;
19757 			Num_parse_names++;
19758 		}
19759 	}
19760 	n = CDR(n);
19761 
19762 	// get arrival path mask, distance, and delay
19763 	eval_nums(n, is_nan, is_nan_forever, arrival_mask, arrival_distance, arrival_delay);
19764 	if (is_nan || is_nan_forever)
19765 		return;
19766 
19767 	// get warp effect
19768 	show_warp = true;
19769 	if (n >= 0)
19770 		show_warp = is_sexp_true(n);
19771 
19772 	// now set all that information depending on the first argument
19773 	if (oswpt.type == OSWPT_TYPE_SHIP)
19774 	{
19775 		oswpt.ship_entry->shipp->arrival_location = arrival_location;
19776 		oswpt.ship_entry->shipp->arrival_anchor = arrival_anchor;
19777 		oswpt.ship_entry->shipp->arrival_path_mask = arrival_mask;
19778 		oswpt.ship_entry->shipp->arrival_distance = arrival_distance;
19779 		oswpt.ship_entry->shipp->arrival_delay = arrival_delay;
19780 
19781 		oswpt.ship_entry->shipp->flags.set(Ship::Ship_Flags::No_arrival_warp, !show_warp);
19782 	}
19783 	else if (oswpt.type == OSWPT_TYPE_WING || oswpt.type == OSWPT_TYPE_WING_NOT_PRESENT)
19784 	{
19785 		oswpt.wingp->arrival_location = arrival_location;
19786 		oswpt.wingp->arrival_anchor = arrival_anchor;
19787 		oswpt.wingp->arrival_path_mask = arrival_mask;
19788 		oswpt.wingp->arrival_distance = arrival_distance;
19789 		oswpt.wingp->arrival_delay = arrival_delay;
19790 
19791 		oswpt.wingp->flags.set(Ship::Wing_Flags::No_arrival_warp, !show_warp);
19792 	}
19793 	else if (oswpt.type == OSWPT_TYPE_PARSE_OBJECT)
19794 	{
19795 		oswpt.ship_entry->p_objp->arrival_location = arrival_location;
19796 		oswpt.ship_entry->p_objp->arrival_anchor = arrival_anchor;
19797 		oswpt.ship_entry->p_objp->arrival_path_mask = arrival_mask;
19798 		oswpt.ship_entry->p_objp->arrival_distance = arrival_distance;
19799 		oswpt.ship_entry->p_objp->arrival_delay = arrival_delay;
19800 
19801 		oswpt.ship_entry->p_objp->flags.set(Mission::Parse_Object_Flags::SF_No_arrival_warp, !show_warp);
19802 	}
19803 }
19804 
19805 // Goober5000 - set stuff for departing ships or wings
sexp_set_departure_info(int node)19806 void sexp_set_departure_info(int node)
19807 {
19808 	int i, departure_location, departure_anchor, departure_mask, departure_delay, n = node;
19809 	bool show_warp, is_nan, is_nan_forever;
19810 	object_ship_wing_point_team oswpt;
19811 
19812 	// get ship or wing
19813 	eval_object_ship_wing_point_team(&oswpt, n);
19814 	n = CDR(n);
19815 
19816 	// get departure location
19817 	departure_location = -1;
19818 	for (i=0; i<MAX_DEPARTURE_NAMES; i++)
19819 	{
19820 		if (!stricmp(CTEXT(n), Departure_location_names[i]))
19821 			departure_location = i;
19822 	}
19823 	if (departure_location < 0)
19824 	{
19825 		Warning(LOCATION, "Departure location '%s' not found.\n", CTEXT(n));
19826 		return;
19827 	}
19828 	n = CDR(n);
19829 
19830 	// get departure anchor
19831 	departure_anchor = -1;
19832 	if ((n < 0) || !stricmp(CTEXT(n), "<no anchor>"))
19833 	{
19834 		// if no anchor, set departure location to hyperspace
19835 		departure_location = 0;
19836 	}
19837 	else
19838 	{
19839 		// anchor must exist - look for it
19840 		for (i=0; i<Num_parse_names; i++)
19841 		{
19842 			if (!stricmp(CTEXT(n), Parse_names[i]))
19843 				departure_anchor = i;
19844 		}
19845 		// if not found, make a new entry
19846 		if (departure_anchor < 0)
19847 		{
19848 			strcpy_s(Parse_names[Num_parse_names], CTEXT(n));
19849 			departure_anchor = Num_parse_names;
19850 			Num_parse_names++;
19851 		}
19852 	}
19853 	n = CDR(n);
19854 
19855 	// get departure path mask and delay
19856 	eval_nums(n, is_nan, is_nan_forever, departure_mask, departure_delay);
19857 	if (is_nan || is_nan_forever)
19858 		return;
19859 
19860 	// get warp effect
19861 	show_warp = true;
19862 	if (n >= 0)
19863 		show_warp = is_sexp_true(n);
19864 
19865 	// now set all that information depending on the first argument
19866 	if (oswpt.type == OSWPT_TYPE_SHIP)
19867 	{
19868 		oswpt.ship_entry->shipp->departure_location = departure_location;
19869 		oswpt.ship_entry->shipp->departure_anchor = departure_anchor;
19870 		oswpt.ship_entry->shipp->departure_path_mask = departure_mask;
19871 		oswpt.ship_entry->shipp->departure_delay = departure_delay;
19872 
19873 		oswpt.ship_entry->shipp->flags.set(Ship::Ship_Flags::No_departure_warp, !show_warp);
19874 	}
19875 	else if (oswpt.type == OSWPT_TYPE_WING || oswpt.type == OSWPT_TYPE_WING_NOT_PRESENT)
19876 	{
19877 		oswpt.wingp->departure_location = departure_location;
19878 		oswpt.wingp->departure_anchor = departure_anchor;
19879 		oswpt.wingp->departure_path_mask = departure_mask;
19880 		oswpt.wingp->departure_delay = departure_delay;
19881 
19882 		oswpt.wingp->flags.set(Ship::Wing_Flags::No_departure_warp, !show_warp);
19883 	}
19884 	else if (oswpt.type == OSWPT_TYPE_PARSE_OBJECT)
19885 	{
19886 		oswpt.ship_entry->p_objp->departure_location = departure_location;
19887 		oswpt.ship_entry->p_objp->departure_anchor = departure_anchor;
19888 		oswpt.ship_entry->p_objp->departure_path_mask = departure_mask;
19889 		oswpt.ship_entry->p_objp->departure_delay = departure_delay;
19890 
19891 		oswpt.ship_entry->p_objp->flags.set(Mission::Parse_Object_Flags::SF_No_departure_warp, !show_warp);
19892 	}
19893 }
19894 
19895 // Goober5000
19896 // set *all* the escort priorities of ships in escort list as follows: most damaged ship gets
19897 // first priority in the argument list, next damaged gets next priority, etc.; if there are more
19898 // ships than priorities, all remaining ships get the final priority on the list
19899 // -- As indicated in the argument specification, there must be at least one argument but no more
19900 // than MAX_COMPLETE_ESCORT_LIST arguments
sexp_damage_escort_list_all(int n)19901 void sexp_damage_escort_list_all(int n)
19902 {
19903 	typedef struct
19904 	{
19905 		int index;
19906 		float hull;
19907 	} my_escort_ship;
19908 
19909 	std::array<int, MAX_COMPLETE_ESCORT_LIST> priority;
19910 	my_escort_ship escort_ship[MAX_COMPLETE_ESCORT_LIST];
19911 	int i, j, num_escort_ships, num_priorities, temp_i;
19912 	bool is_nan, is_nan_forever;
19913 	float temp_f;
19914 
19915 	// build list of priorities
19916 	num_priorities = eval_array(priority, n, is_nan, is_nan_forever);
19917 	if (is_nan || is_nan_forever)
19918 		return;
19919 
19920 	// build custom list of escort ships
19921 	num_escort_ships = 0;
19922 	for (auto so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so))
19923 	{
19924 		auto objp = &Objects[so->objnum];
19925 		Assertion(objp->type == OBJ_SHIP, "All objects on Ship_obj_list must be ships!");
19926 
19927 		// make sure it's on the escort list
19928 		if ( !(Ships[objp->instance].flags[Ship::Ship_Flags::Escort]) )
19929 			continue;
19930 
19931 		// set index
19932 		escort_ship[num_escort_ships].index = objp->instance;
19933 
19934 		// calc and set hull integrity
19935 		escort_ship[num_escort_ships].hull = get_hull_pct(objp);
19936 
19937 		num_escort_ships++;
19938 	}
19939 
19940 	// sort it bubbly, lowest hull to highest hull
19941 	for (i = 0; i < num_escort_ships; i++)
19942 	{
19943 		for (j = 0; j < i; j++)
19944 		{
19945 			if (escort_ship[i].hull < escort_ship[j].hull)
19946 			{
19947 				// swap
19948 				temp_i = escort_ship[i].index;
19949 				temp_f = escort_ship[i].hull;
19950 				escort_ship[i].index = escort_ship[j].index;
19951 				escort_ship[i].hull = escort_ship[j].hull;
19952 				escort_ship[j].index = temp_i;
19953 				escort_ship[j].hull = temp_f;
19954 			}
19955 		}
19956 	}
19957 
19958 	// loop through and assign priorities
19959 	for (i = 0; i < num_escort_ships; i++)
19960 	{
19961 		if (i >= num_priorities)
19962 			Ships[escort_ship[i].index].escort_priority = priority[num_priorities - 1];
19963 		else
19964 			Ships[escort_ship[i].index].escort_priority = priority[i];
19965 	}
19966 
19967 	// reorder the escort list
19968 	hud_resort_escort_list();
19969 }
19970 
sexp_awacs_set_radius(int node)19971 void sexp_awacs_set_radius(int node)
19972 {
19973 	int radius;
19974 	bool is_nan, is_nan_forever;
19975 
19976 	// get the firing ship
19977 	auto ship_entry = eval_ship(node);
19978 	if (!ship_entry || !ship_entry->shipp)
19979 		return;
19980 
19981 	// get the awacs subsystem
19982 	auto awacs = ship_get_subsys(ship_entry->shipp, CTEXT(CDR(node)));
19983 	if (!awacs || !(awacs->system_info->flags[Model::Subsystem_Flags::Awacs]))
19984 		return;
19985 
19986 	// set the new awacs radius
19987 	radius = eval_num(CDDR(node), is_nan, is_nan_forever);
19988 	if (is_nan || is_nan_forever)
19989 		return;
19990 	awacs->awacs_radius = (float)radius;
19991 }
19992 
19993 // Goober5000
sexp_primitive_sensors_set_range(int n)19994 void sexp_primitive_sensors_set_range(int n)
19995 {
19996 	int range;
19997 	bool is_nan, is_nan_forever;
19998 
19999 	// get the ship
20000 	auto ship_entry = eval_ship(n);
20001 	if (!ship_entry || !ship_entry->shipp)
20002 		return;
20003 
20004 	// set the new range
20005 	range = eval_num(CDR(n), is_nan, is_nan_forever);
20006 	if (is_nan || is_nan_forever)
20007 		return;
20008 	ship_entry->shipp->primitive_sensor_range = range;
20009 }
20010 
20011 //*************************************************************************************************
20012 // Kazan
20013 // AutoNav/AutoPilot system SEXPS
20014 
20015 //text: set-nav-carry
20016 //text: unset-nav-carry
20017 //args: 1+, Ship/Wing name
set_unset_nav_carry_status(int node,bool set_it)20018 void set_unset_nav_carry_status(int node, bool set_it)
20019 {
20020 	for (int n = node; n >= 0; n = CDR(n))
20021 	{
20022 		object_ship_wing_point_team oswpt;
20023 		eval_object_ship_wing_point_team(&oswpt, n);
20024 
20025 		switch (oswpt.type)
20026 		{
20027 			case OSWPT_TYPE_SHIP:
20028 				oswpt.ship_entry->shipp->flags.set(Ship::Ship_Flags::Navpoint_carry, set_it);
20029 				break;
20030 
20031 			case OSWPT_TYPE_WING:
20032 			case OSWPT_TYPE_WING_NOT_PRESENT:
20033 				oswpt.wingp->flags.set(Ship::Wing_Flags::Nav_carry, set_it);
20034 				break;
20035 		}
20036 	}
20037 }
20038 
20039 //text: set-nav-needslink
20040 //text: unset-nav-needslink
20041 //args: 1+, Ship name
set_unset_nav_needslink(int node,bool set_it)20042 void set_unset_nav_needslink(int node, bool set_it)
20043 {
20044 	for (int n = node; n >= 0; n = CDR(n))
20045 	{
20046 		auto ship_entry = eval_ship(n);
20047 		if (!ship_entry || !ship_entry->shipp)
20048 			continue;
20049 
20050 		if (set_it)
20051 			ship_entry->shipp->flags.remove(Ship::Ship_Flags::Navpoint_carry);
20052 
20053 		ship_entry->shipp->flags.set(Ship::Ship_Flags::Navpoint_needslink, set_it);
20054 	}
20055 }
20056 
add_nav_waypoint(object_ship_wing_point_team * oswpt,const char * nav,const char * WP_path,int vert)20057 void add_nav_waypoint(object_ship_wing_point_team *oswpt, const char *nav, const char *WP_path, int vert)
20058 {
20059 	bool add_for_this_player = true;
20060 
20061 	if (oswpt)
20062 	{
20063 		// we can't assume this nav should be visible to the player any more
20064 		add_for_this_player = false;
20065 
20066 		switch (oswpt->type)
20067 		{
20068 			case OSWPT_TYPE_WHOLE_TEAM:
20069 				if (oswpt->team == Player_ship->team)
20070 					add_for_this_player = true;
20071 				break;
20072 
20073 			case OSWPT_TYPE_SHIP:
20074 				if (oswpt->ship_entry->shipp == Player_ship)
20075 					add_for_this_player = true;
20076 				break;
20077 
20078 			case OSWPT_TYPE_WING:
20079 				for (int i = 0; i < oswpt->wingp->current_count; ++i)
20080 					if (Ships[oswpt->wingp->ship_index[i]].objnum == Player_ship->objnum)
20081 						add_for_this_player = true;
20082 				break;
20083 
20084 			// for all other oswpt types we simply ignore this
20085 			default:
20086 				break;
20087 		}
20088 	}
20089 
20090 	AddNav_Waypoint(nav, WP_path, vert, add_for_this_player ? 0 : NP_HIDDEN);
20091 }
20092 
20093 //text: add-nav-waypoint
20094 //args: 4, Nav Name, Waypoint Path Name, Waypoint Path point, ShipWingTeam
add_nav_waypoint(int node)20095 void add_nav_waypoint(int node)
20096 {
20097 	object_ship_wing_point_team oswpt, *oswptp = nullptr;
20098 	bool is_nan, is_nan_forever;
20099 
20100 	auto nav_name = CTEXT(node);
20101 	node = CDR(node);
20102 	auto way_name = CTEXT(node);
20103 	node = CDR(node);
20104 
20105 	int vert = eval_num(node, is_nan, is_nan_forever);
20106 	if (is_nan || is_nan_forever) {
20107 		return;
20108 	}
20109 	node = CDR(node);
20110 
20111 	if (node >= 0) {
20112 		eval_object_ship_wing_point_team(&oswpt, node);
20113 		oswptp = &oswpt;
20114 	}
20115 
20116 	add_nav_waypoint(oswptp, nav_name, way_name, vert);
20117 
20118 	Current_sexp_network_packet.start_callback();
20119 	Current_sexp_network_packet.send_string(nav_name);
20120 	Current_sexp_network_packet.send_string(way_name);
20121 	Current_sexp_network_packet.send_int(vert);
20122 	if (oswptp) {
20123 		Current_sexp_network_packet.send_string(oswptp->object_name);
20124 	}
20125 	Current_sexp_network_packet.end_callback();
20126 }
20127 
multi_sexp_add_nav_waypoint()20128 void multi_sexp_add_nav_waypoint()
20129 {
20130 	char nav_name[TOKEN_LENGTH];
20131 	char way_name[TOKEN_LENGTH];
20132 	char oswpt_name[TOKEN_LENGTH];
20133 	int vert;
20134 	object_ship_wing_point_team *oswptp = nullptr;
20135 
20136 	if (!Current_sexp_network_packet.get_string(nav_name))
20137 		return;
20138 	if (!Current_sexp_network_packet.get_string(way_name))
20139 		return;
20140 	if (!Current_sexp_network_packet.get_int(vert))
20141 		return;
20142 
20143 	if (Current_sexp_network_packet.get_string(oswpt_name)) {
20144 		object_ship_wing_point_team oswpt;
20145 		eval_object_ship_wing_point_team(&oswpt, -1, oswpt_name);
20146 		oswptp = &oswpt;
20147 	}
20148 
20149 	add_nav_waypoint(oswptp, nav_name, way_name, vert);
20150 }
20151 
20152 //text: add-nav-ship
20153 //args: 2, Nav Name, Ship Name
add_nav_ship(int node)20154 void add_nav_ship(int node)
20155 {
20156 	auto nav_name = CTEXT(node);
20157 	auto ship_name = CTEXT(CDR(node));
20158 	AddNav_Ship(nav_name, ship_name, 0);
20159 
20160 	Current_sexp_network_packet.start_callback();
20161 	Current_sexp_network_packet.send_string(nav_name);
20162 	Current_sexp_network_packet.send_string(ship_name);
20163 	Current_sexp_network_packet.end_callback();
20164 }
20165 
multi_add_nav_ship()20166 void multi_add_nav_ship()
20167 {
20168 	char nav_name[TOKEN_LENGTH];
20169 	char ship_name[TOKEN_LENGTH];
20170 
20171 	if (!Current_sexp_network_packet.get_string(nav_name)) {
20172 		return;
20173 	}
20174 
20175 	if (!Current_sexp_network_packet.get_string(ship_name)) {
20176 		return;
20177 	}
20178 
20179 	AddNav_Ship(nav_name, ship_name, 0);
20180 }
20181 
20182 //text: del-nav
20183 //args: 1, Nav Name
del_nav(int node)20184 void del_nav(int node)
20185 {
20186 	auto nav_name = CTEXT(node);
20187 	DelNavPoint(nav_name);
20188 
20189 	Current_sexp_network_packet.start_callback();
20190 	Current_sexp_network_packet.send_string(nav_name);
20191 	Current_sexp_network_packet.end_callback();
20192 }
20193 
multi_del_nav()20194 void multi_del_nav()
20195 {
20196 	char nav_name[TOKEN_LENGTH];
20197 
20198 	if (!Current_sexp_network_packet.get_string(nav_name)) {
20199 		return;
20200 	}
20201 
20202 	DelNavPoint(nav_name);
20203 }
20204 
20205 //text: use-nav-cinematics
20206 //args: 1, boolean enable/disable
set_use_ap_cinematics(int node)20207 void set_use_ap_cinematics(int node)
20208 {
20209 	The_mission.flags.set(Mission::Mission_Flags::Use_ap_cinematics, is_sexp_true(node));
20210 }
20211 
20212 //text: use-autopilot
20213 //args: 1, boolean enable/disable
set_use_ap(int node)20214 void set_use_ap(int node)
20215 {
20216 	The_mission.flags.set(Mission::Mission_Flags::Deactivate_ap, !is_sexp_true(node));
20217 }
20218 
20219 //text: hide-nav
20220 //text: unhide-nav
20221 //args: 1, Nav Name
hide_unhide_nav(int node,bool hide_it)20222 void hide_unhide_nav(int node, bool hide_it)
20223 {
20224 	auto nav_name = CTEXT(node);
20225 	if (hide_it)
20226 		Nav_Set_Hidden(nav_name);
20227 	else
20228 		Nav_UnSet_Hidden(nav_name);
20229 }
20230 
20231 //text: restrict-nav
20232 //text: unrestrict-nav
20233 //args: 1, nav name
restrict_unrestrict_nav(int node,bool restrict_it)20234 void restrict_unrestrict_nav(int node, bool restrict_it)
20235 {
20236 	auto nav_name = CTEXT(node);
20237 	if (restrict_it)
20238 		Nav_Set_NoAccess(nav_name);
20239 	else
20240 		Nav_UnSet_NoAccess(nav_name);
20241 }
20242 
20243 //text: set-nav-visited
20244 //text: unset-nav-visited
20245 //args: 1, Nav Name
set_unset_nav_visited(int node,bool set_it)20246 void set_unset_nav_visited(int node, bool set_it)
20247 {
20248 	auto nav_name = CTEXT(node);
20249 	if (set_it)
20250 		Nav_Set_Visited(nav_name);
20251 	else
20252 		Nav_UnSet_Visited(nav_name);
20253 }
20254 
20255 //text: is-nav-visited
20256 //args: 1, Nav Name
20257 //rets: true/false
is_nav_visited(int node)20258 int is_nav_visited(int node)
20259 {
20260 	auto nav_name = CTEXT(node);
20261 	return IsVisited(nav_name);
20262 }
20263 
20264 //text: is-nav_linked
20265 //args: 1, Ship name
20266 //rets: true/false
is_nav_linked(int node)20267 int is_nav_linked(int node)
20268 {
20269 	auto ship_entry = eval_ship(node);
20270 	if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT)
20271 		return SEXP_NAN;
20272 	if (ship_entry->status == ShipStatus::EXITED)
20273 		return SEXP_NAN_FOREVER;
20274 
20275 	return ship_entry->shipp->flags[Ship::Ship_Flags::Navpoint_carry] ? SEXP_TRUE : SEXP_FALSE;
20276 }
20277 
20278 //text: distance-to-nav
20279 //args: 1, Nav Name
20280 //rets: distance to nav
distance_to_nav(int node)20281 int distance_to_nav(int node)
20282 {
20283 	auto nav_name = CTEXT(node);
20284 	return DistanceTo(nav_name);
20285 }
20286 
select_unselect_nav(int node,bool select_it)20287 void select_unselect_nav(int node, bool select_it)
20288 {
20289 	if (select_it)
20290 	{
20291 		auto nav_name = CTEXT(node);
20292 		SelectNav(nav_name);
20293 	}
20294 	else
20295 		DeselectNav();
20296 }
20297 
20298 //*************************************************************************************************
20299 
sexp_is_tagged(int node)20300 int sexp_is_tagged(int node)
20301 {
20302 	auto ship_entry = eval_ship(node);
20303 	if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT)
20304 		return SEXP_NAN;
20305 	if (ship_entry->status == ShipStatus::EXITED)
20306 		return SEXP_NAN_FOREVER;
20307 
20308 	return ship_is_tagged(ship_entry->objp) ? SEXP_TRUE : SEXP_FALSE;
20309 }
20310 
20311 // Joint effort of Sesquipedalian and Goober5000.  Sesq found the code, mucked around making
20312 // sexps with it and learned things, Goober taught Sesq and made the sexp work properly. =D
20313 // Returns true so long as the player has held a missile lock for the specified time.
20314 // If the optional ship and/or ship's subsystem are specified, returns true when that
20315 // has been locked onto, but otherwise returns as long as anything has been locked onto.
sexp_missile_locked(int node)20316 int sexp_missile_locked(int node)
20317 {
20318 	int z;
20319 	bool is_nan, is_nan_forever;
20320 
20321 	// if we aren't targeting anything, it's false
20322 	if ((Players_target == -1) || (Players_target == UNINITIALIZED))
20323 		return SEXP_FALSE;
20324 
20325 	// if we aren't locked on to anything, it's false
20326 	if (!Players_mlocked)
20327 		return SEXP_FALSE;
20328 
20329 	// do we have a specific ship?
20330 	if (CDR(node) != -1)
20331 	{
20332 		// if we're not targeting the specific ship, it's false
20333 		if (stricmp(Ships[Objects[Players_target].instance].ship_name, CTEXT(CDR(node))) != 0)
20334 			return SEXP_FALSE;
20335 
20336 		// do we have a specific subsystem?
20337 		if (CDR(CDR(node)) != -1)
20338 		{
20339 			// if we aren't targeting a subsystem at all, it's false
20340 			if (!Player_ai->targeted_subsys)
20341 				return SEXP_FALSE;
20342 
20343 			// if we're not targeting the specific subsystem, it's false
20344 			if (subsystem_stricmp(Player_ai->targeted_subsys->system_info->subobj_name, CTEXT(CDR(CDR(node)))))
20345 				return SEXP_FALSE;
20346 		}
20347 	}
20348 
20349 	// if we've gotten this far, we must have satisfied whatever conditions the sexp imposed
20350 	// finally, test if we've locked for a certain period of time
20351 	z = eval_num(node, is_nan, is_nan_forever) * 1000;
20352 	if (is_nan)
20353 		return SEXP_FALSE;
20354 	if (is_nan_forever)
20355 		return SEXP_KNOWN_FALSE;
20356 	if (timestamp_has_time_elapsed(Players_mlocked_timestamp, z))
20357 	{
20358 		return SEXP_TRUE;
20359 	}
20360 
20361 	return SEXP_FALSE;
20362 }
20363 
sexp_is_player(int node)20364 int sexp_is_player(int node)
20365 {
20366 	bool standard_check = is_sexp_true(node);
20367 	node = CDR(node);
20368 
20369 	while (node >= 0) {
20370 		auto player = get_player_from_ship_node(node, true);
20371 		if (player) {
20372 			// in single-player mode, maybe check for AI-controlled player ships
20373 			if (!(Game_mode & GM_MULTIPLAYER) && standard_check && Player_use_ai) {
20374 				return SEXP_FALSE;
20375 			}
20376 		} else {
20377 			// ship is not a player
20378 			return SEXP_FALSE;
20379 		}
20380 
20381 		node = CDR(node);
20382 	}
20383 
20384 	// if we reached this far they all checked out
20385 	return SEXP_TRUE;
20386 }
20387 
sexp_set_respawns(int node)20388 void sexp_set_respawns(int node)
20389 {
20390 	int num_respawns;
20391 	bool is_nan, is_nan_forever;
20392 
20393 	// we're wasting our time if you can't respawn
20394 	if (!(Game_mode & GM_MULTIPLAYER)) {
20395 		return;
20396 	}
20397 
20398 	num_respawns = eval_num(node, is_nan, is_nan_forever);
20399 	if (is_nan || is_nan_forever) {
20400 		return;
20401 	}
20402 	node = CDR(node);
20403 
20404 	// send the information to clients
20405 	Current_sexp_network_packet.start_callback();
20406 	Current_sexp_network_packet.send_int(num_respawns);
20407 
20408 	for ( ; node >= 0; node = CDR(node)) {
20409 		// get the parse object for the ship
20410 		auto ship_entry = eval_ship(node);
20411 		if (!ship_entry || !ship_entry->p_objp) {
20412 			continue;
20413 		}
20414 
20415 		ship_entry->p_objp->respawn_count = num_respawns;
20416 		Current_sexp_network_packet.send_parse_object(ship_entry->p_objp);
20417 	}
20418 
20419 	Current_sexp_network_packet.end_callback();
20420 }
20421 
multi_sexp_set_respawns()20422 void multi_sexp_set_respawns()
20423 {
20424 	int num_respawns;
20425 	p_object *p_objp;
20426 
20427 	Current_sexp_network_packet.get_int(num_respawns);
20428 
20429 	while (Current_sexp_network_packet.get_parse_object(p_objp)) {
20430 		p_objp->respawn_count = num_respawns;
20431 	}
20432 }
20433 
20434 /**
20435  * get a hotkey for a one ship or wing
20436  */
sexp_get_hotkey(int node)20437 int sexp_get_hotkey(int node)
20438 {
20439 	object_ship_wing_point_team oswpt;
20440 	eval_object_ship_wing_point_team(&oswpt, node);
20441 	int hotkey;
20442 
20443 	// returns the hotkey of the ship or wing
20444 	// if argument is not ship or wing returns -1
20445 	if (oswpt.type == OSWPT_TYPE_SHIP) {
20446 		hotkey = oswpt.ship_entry->shipp->hotkey;
20447 	} else if (oswpt.type == OSWPT_TYPE_WING) {
20448 		hotkey = oswpt.wingp->hotkey;
20449 	} else {
20450 		hotkey = -1;
20451 	}
20452 
20453 	return hotkey;
20454 }
20455 
20456 /**
20457  * set a hotkey for one or more ships/wings
20458  */
sexp_add_remove_hotkey(int node)20459 void sexp_add_remove_hotkey(int node)
20460 {
20461 	int objnum, setnum, n = node;
20462 	bool is_adding, is_nan, is_nan_forever;
20463 
20464 	// True for add, False for remove
20465 	is_adding = is_sexp_true(n);
20466 	n = CDR(n);
20467 
20468 	setnum = eval_num(n, is_nan, is_nan_forever);
20469 	if (is_nan || is_nan_forever) {
20470 		return;
20471 	}
20472 	n = CDR(n);
20473 
20474 	// first, only proceed if setnum number is a valid hotkey
20475 	// look for ship name -- if found, then add or remove hotkey to ship
20476 	// if not a ship, look for wing name, then add or remove hotkey to each ship in wing
20477 	// note, hud_target_hotkey_add_remove() checks if the object has arrived or is destroyed or departed
20478 	if (setnum >= 0 && setnum < MAX_KEYED_TARGETS) {
20479 		for (; n != -1; n = CDR(n)) {
20480 			object_ship_wing_point_team oswpt;
20481 			eval_object_ship_wing_point_team(&oswpt, n);
20482 			if (oswpt.type == OSWPT_TYPE_SHIP) {
20483 				objnum = oswpt.ship_entry->shipp->objnum;
20484 				// check if the ship already has this hot-key and if sexp is adding or removing it
20485 				if (((oswpt.ship_entry->shipp->hotkey == setnum) && !is_adding) ||
20486 					((oswpt.ship_entry->shipp->hotkey != setnum) && is_adding) ) {
20487 					hud_target_hotkey_add_remove(setnum, &Objects[objnum], HOTKEY_USER_ADDED);
20488 				}
20489 			}
20490 			else if (oswpt.type == OSWPT_TYPE_WING) {
20491 				for (int i = 0; i < oswpt.wingp->current_count; i++) {
20492 					auto shipp = &Ships[oswpt.wingp->ship_index[i]];
20493 					objnum = shipp->objnum;
20494 					// check if the ship already has this hot-key and if sexp is adding or removing it
20495 					if (((shipp->hotkey == setnum) && !is_adding) ||
20496 						((shipp->hotkey != setnum) && is_adding)) {
20497 						hud_target_hotkey_add_remove(setnum, &Objects[objnum], HOTKEY_USER_ADDED);
20498 					}
20499 				}
20500 			}
20501 		}
20502 	}
20503 }
20504 
20505 // helper function for the clear-weapons and clear-debris SEXPs
actually_clear_weapons_or_debris(int op_num,int class_index)20506 void actually_clear_weapons_or_debris(int op_num, int class_index)
20507 {
20508 	for (auto objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp))
20509 	{
20510 		if (op_num == OP_CLEAR_WEAPONS && objp->type != OBJ_WEAPON)
20511 			continue;
20512 		if (op_num == OP_CLEAR_DEBRIS && objp->type != OBJ_DEBRIS)
20513 			continue;
20514 
20515 		if (class_index >= 0)
20516 		{
20517 			// weapon doesn't match the class
20518 			if (op_num == OP_CLEAR_WEAPONS && (Weapons[objp->instance].weapon_info_index != class_index))
20519 				continue;
20520 
20521 			// debris doesn't match the class
20522 			if (op_num == OP_CLEAR_DEBRIS && (Debris[objp->instance].ship_info_index != class_index))
20523 				continue;
20524 		}
20525 
20526 		objp->flags.set(Object::Object_Flags::Should_be_dead);
20527 	}
20528 }
20529 
sexp_clear_weapons_or_debris(int node,int op_num)20530 void sexp_clear_weapons_or_debris(int node, int op_num)
20531 {
20532 	int class_index = -1;
20533 
20534 	// if we have the optional argument, read it in
20535 	if (node >= 0) {
20536 		if (op_num == OP_CLEAR_WEAPONS) {
20537 			class_index = weapon_info_lookup(CTEXT(node));
20538 			if (class_index == -1) {
20539 				Warning(LOCATION, "Clear-weapons attempted to remove %s. Weapon class not found. Clear-weapons will remove all weapons currently in the mission\n", CTEXT(node));
20540 			}
20541 		} else if (op_num == OP_CLEAR_DEBRIS) {
20542 			class_index = ship_info_lookup(CTEXT(node));
20543 			if (class_index == -1) {
20544 				Warning(LOCATION, "Clear-debris attempted to remove %s. Ship class not found. Clear-debris will remove all debris currently in the mission\n", CTEXT(node));
20545 			}
20546 		}
20547 	}
20548 
20549 	actually_clear_weapons_or_debris(op_num, class_index);
20550 
20551 	// send the information to clients
20552 	Current_sexp_network_packet.start_callback();
20553 	Current_sexp_network_packet.send_int(class_index);
20554 	Current_sexp_network_packet.end_callback();
20555 }
20556 
multi_sexp_clear_weapons_or_debris(int op_num)20557 void multi_sexp_clear_weapons_or_debris(int op_num)
20558 {
20559 	int class_index = -1;
20560 
20561 	Current_sexp_network_packet.get_int(class_index);
20562 
20563 	actually_clear_weapons_or_debris(op_num, class_index);
20564 }
20565 
sexp_return_player_data(int node,int type)20566 int sexp_return_player_data(int node, int type)
20567 {
20568 	int np_index;
20569 	auto p = get_player_from_ship_node(node, true, &np_index);
20570 
20571 	// now, if we have a valid player, return his kills
20572 	if (p) {
20573 		switch (type) {
20574 			case OP_NUM_KILLS:
20575 				return p->stats.m_kill_count_ok;
20576 
20577 			case OP_NUM_ASSISTS:
20578 				return p->stats.m_assists;
20579 
20580 			case OP_SHIP_SCORE:
20581 				return p->stats.m_score;
20582 
20583 			case OP_SHIP_DEATHS:
20584 				return p->stats.m_player_deaths;
20585 
20586 			case OP_RESPAWNS_LEFT:
20587 				// Dogfight missions have no respawn limit
20588 				if (MULTI_NOT_DOGFIGHT) {
20589 					if ( Net_players[np_index].flags & NETINFO_FLAG_RESPAWNING ) {
20590 						// since the player hasn't actually respawned yet he hasn't used up a number or spawns equal to his deaths
20591 						// so add an extra life back.
20592 						return Netgame.respawn - p->stats.m_player_deaths + 1;
20593 					}
20594 					else {
20595 						return Netgame.respawn - p->stats.m_player_deaths;
20596 					}
20597 				}
20598 				break;
20599 
20600 			default:
20601 				Error(LOCATION, "return-player-data was called with invalid type %d on node %d!", type, node);
20602 		}
20603 	}
20604 	// AI ships also have a respawn count so we can return valid data for that at least
20605 	else if ( (Game_mode & GM_MULTIPLAYER) && (type == OP_SHIP_DEATHS || type == OP_RESPAWNS_LEFT ) ) {
20606 		auto ship_entry = eval_ship(node);
20607 		if (!ship_entry || !ship_entry->p_objp) {
20608 			return 0;
20609 		}
20610 
20611 		if (ship_entry->p_objp->flags[Mission::Parse_Object_Flags::OF_Player_start]) {
20612 			switch (type) {
20613 				case OP_SHIP_DEATHS:
20614 					// when an AI ship is finally killed its respawn count won't be updated so get the number of deaths
20615 					// from the log instead
20616 					return mission_log_get_count(LOG_SHIP_DESTROYED, ship_entry->p_objp->name, nullptr) + mission_log_get_count(LOG_SELF_DESTRUCTED, ship_entry->p_objp->name, nullptr);
20617 
20618 				case OP_RESPAWNS_LEFT:
20619 					return Netgame.respawn - ship_entry->p_objp->respawn_count;
20620 
20621 				default:
20622 					// We should never reach this.
20623 					Assert(false);
20624 			}
20625 		}
20626 	}
20627 
20628 	// AI ships
20629 	return 0;
20630 }
20631 
sexp_num_type_kills(int node)20632 int sexp_num_type_kills(int node)
20633 {
20634 	auto p = get_player_from_ship_node(node, true);
20635 	if (!p) {
20636 		return 0;
20637 	}
20638 
20639 	// lookup ship type name
20640 	int st_index = ship_type_name_lookup(CTEXT(CDR(node)));
20641 	if (st_index < 0) {
20642 		return 0;
20643 	}
20644 
20645 	// look stuff up
20646 	int total = 0;
20647 	for (int idx = 0; idx < ship_info_size(); idx++) {
20648 		if ((p->stats.m_okKills[idx] > 0) && ship_class_query_general_type(idx) == st_index) {
20649 			total += p->stats.m_okKills[idx];
20650 		}
20651 	}
20652 
20653 	// total
20654 	return total;
20655 }
20656 
sexp_num_class_kills(int node)20657 int sexp_num_class_kills(int node)
20658 {
20659 	auto p = get_player_from_ship_node(node, true);
20660 	if (!p) {
20661 		return 0;
20662 	}
20663 
20664 	// get the ship type we're looking for
20665 	int si_index = ship_info_lookup(CTEXT(CDR(node)));
20666 	if ((si_index < 0) || (si_index >= ship_info_size())) {
20667 		return 0;
20668 	}
20669 
20670 	// return the count
20671 	return p->stats.m_okKills[si_index];
20672 }
20673 
sexp_subsys_set_random(int node)20674 void sexp_subsys_set_random(int node)
20675 {
20676 	int low, high, n = node, idx, rand, exclusion_list[MAX_MODEL_SUBSYSTEMS];
20677 	bool is_nan, is_nan_forever;
20678 	ship_subsys *subsys;
20679 	ship *shipp;
20680 
20681 	// get ship
20682 	auto ship_entry = eval_ship(n);
20683 	if (!ship_entry || !ship_entry->shipp) {
20684 		return;
20685 	}
20686 	shipp = ship_entry->shipp;
20687 	n = CDR(n);
20688 
20689 	// get low and high
20690 	eval_nums(n, is_nan, is_nan_forever, low, high);
20691 	if (is_nan || is_nan_forever) {
20692 		return;
20693 	}
20694 	if (low < 0) {
20695 		low = 0;
20696 	}
20697 	if (high > 100) {
20698 		high = 100;
20699 	}
20700 	if (low > high) {
20701 		Warning(LOCATION, "subsys-set-random was passed an invalid range (%d ... %d)!", low, high);
20702 		return;
20703 	}
20704 
20705 	// init exclusion list
20706 	memset(exclusion_list, 0, sizeof(int) * Ship_info[shipp->ship_info_index].n_subsystems);
20707 
20708 	// get exclusion list
20709 	while( n != -1) {
20710 		int exclude_index = ship_get_subsys_index(shipp, CTEXT(n));
20711 		if (exclude_index >= 0) {
20712 			exclusion_list[exclude_index] = 1;
20713 		}
20714 
20715 		n = CDR(n);
20716 	}
20717 
20718 	// apply to all others
20719 	for (idx=0; idx<Ship_info[shipp->ship_info_index].n_subsystems; idx++) {
20720 		if ( exclusion_list[idx] == 0 ) {
20721 			// get non excluded subsystem
20722 			subsys = ship_get_indexed_subsys(shipp, idx, nullptr);
20723 			if (subsys == nullptr) {
20724 				nprintf(("Warning", "Nonexistent subsystem for index %d on ship %s for subsys set random\n", idx, shipp->ship_name));
20725 				continue;
20726 			}
20727 
20728 			// randomize its hit points
20729 			rand = rand_internal(low, high);
20730 			set_subsys_strength_and_maybe_ancestors(shipp, subsys, nullptr, &rand, nullptr, nullptr, true, true);
20731 		}
20732 	}
20733 
20734 	// needed to keep aggregate info correct
20735 	ship_recalc_subsys_strength(shipp);
20736 }
20737 
sexp_supernova_start(int node)20738 void sexp_supernova_start(int node)
20739 {
20740 	bool is_nan, is_nan_forever;
20741 	int countdown = eval_num(node, is_nan, is_nan_forever);
20742 	if (is_nan || is_nan_forever)
20743 		return;
20744 
20745 	supernova_start(countdown);
20746 }
20747 
sexp_supernova_stop(int)20748 void sexp_supernova_stop(int  /*node*/)
20749 {
20750 	supernova_stop();
20751 }
20752 
sexp_is_weapon_selected(int node,bool primary)20753 int sexp_is_weapon_selected(int node, bool primary)
20754 {
20755 	int bank, num_banks;
20756 	bool is_nan, is_nan_forever;
20757 
20758 	// lookup ship
20759 	auto ship_entry = eval_ship(node);
20760 	if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT)
20761 		return SEXP_NAN;
20762 	if (ship_entry->status == ShipStatus::EXITED)
20763 		return SEXP_NAN_FOREVER;
20764 
20765 	num_banks = primary ? ship_entry->shipp->weapons.num_primary_banks : ship_entry->shipp->weapons.num_secondary_banks;
20766 
20767 	// bogus value?
20768 	bank = eval_num(CDR(node), is_nan, is_nan_forever);
20769 	if (is_nan_forever)
20770 		return SEXP_KNOWN_FALSE;
20771 	if (is_nan || bank >= num_banks)
20772 		return SEXP_FALSE;
20773 
20774 	// is this the bank currently selected
20775 	if (primary)
20776 	{
20777 		if (ship_entry->shipp->flags[Ship::Ship_Flags::Primary_linked])
20778 			return SEXP_TRUE;
20779 		if (bank == ship_entry->shipp->weapons.current_primary_bank)
20780 			return SEXP_TRUE;
20781 	}
20782 	else
20783 	{
20784 		if (bank == ship_entry->shipp->weapons.current_secondary_bank)
20785 			return SEXP_TRUE;
20786 	}
20787 
20788 	// nope
20789 	return SEXP_FALSE;
20790 }
20791 
20792 //	Return SEXP_TRUE if quadrant quadnum is near max.
shield_quad_near_max(int quadnum)20793 int shield_quad_near_max(int quadnum)
20794 {
20795 	if (quadnum >= Player_obj->n_quadrants)
20796 		return SEXP_FALSE;
20797 
20798 	float	remaining = 0.0f;
20799 	for (int i=0; i<Player_obj->n_quadrants; i++) {
20800 		if (i == quadnum){
20801 			continue;
20802 		}
20803 		remaining += Player_obj->shield_quadrant[i];
20804 	}
20805 
20806 	if ((remaining < 2.0f) || (Player_obj->shield_quadrant[quadnum] > shield_get_max_quad(Player_obj) - 5.0f)) {
20807 		return SEXP_TRUE;
20808 	} else {
20809 		return SEXP_FALSE;
20810 	}
20811 }
20812 
20813 //	Return truth value for special SEXP.
20814 //	Used in training#5, perhaps in other missions.
process_special_sexps(int index)20815 int process_special_sexps(int index)
20816 {
20817 	switch (index) {
20818 	case 0:	//	Ship "Freighter 1" is aspect locked by player.
20819 		if (Player_ai->target_objnum != -1) {
20820 			if (!(stricmp(Ships[Objects[Player_ai->target_objnum].instance].ship_name, "Freighter 1"))) {
20821 				if (Player_ai->current_target_is_locked)
20822 					return SEXP_TRUE;
20823 			}
20824 		}
20825 		return SEXP_FALSE;
20826 
20827 	case 1:	//	Fired Interceptors
20828 		object	*objp;
20829 		for (objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) {
20830 			if (objp->type == OBJ_WEAPON) {
20831 				if (!stricmp(Weapon_info[Weapons[objp->instance].weapon_info_index].name, "Interceptor#weak")) {
20832 					int target = Weapons[objp->instance].target_num;
20833 					if (target != -1) {
20834 						if (Objects[target].type == OBJ_SHIP) {
20835 							if (!(stricmp(Ships[Objects[target].instance].ship_name, "Freighter 1")))
20836 								return SEXP_TRUE;
20837 						}
20838 					}
20839 				}
20840 			}
20841 		}
20842 		return SEXP_FALSE;
20843 
20844 	case 2:	//	Ship "Freighter 1", subsystem "Weapons" is aspect locked by player.
20845 		if (Player_ai->target_objnum != -1) {
20846 			if (!(stricmp(Ships[Objects[Player_ai->target_objnum].instance].ship_name, "Freighter 1"))) {
20847 				if (!(subsystem_stricmp(Player_ai->targeted_subsys->system_info->name, "Weapons"))) {
20848 					if (Player_ai->current_target_is_locked){
20849 						return SEXP_TRUE;
20850 					}
20851 				}
20852 			}
20853 		}
20854 		return SEXP_FALSE;
20855 
20856 	case 3:	//	Player ship suffering shield damage on front.
20857 		if (!(Ship_info[Player_ship->ship_info_index].flags[Ship::Info_Flags::Model_point_shields])) {
20858 			shield_apply_damage(Player_obj, FRONT_QUAD, 10.0f);
20859 			hud_shield_quadrant_hit(Player_obj, FRONT_QUAD);
20860 			return SEXP_TRUE;
20861 		} else {
20862 			nprintf(("Warning", "Shield-related Special-check SEXPs do not work on ship %s because it uses model point shields.\n", Player_ship->ship_name));
20863 			return SEXP_FALSE;
20864 		}
20865 		break;
20866 
20867 	case 4:	//	Player ship suffering much damage.
20868 		if (!(Ship_info[Player_ship->ship_info_index].flags[Ship::Info_Flags::Model_point_shields])) {
20869 			nprintf(("AI", "Frame %i\n", Framecount));
20870 			shield_apply_damage(Player_obj, FRONT_QUAD, 10.0f);
20871 			hud_shield_quadrant_hit(Player_obj, FRONT_QUAD);
20872 			if (Player_obj->shield_quadrant[FRONT_QUAD] < 2.0f)
20873 				return SEXP_TRUE;
20874 			else
20875 				return SEXP_FALSE;
20876 		} else {
20877 			nprintf(("Warning", "Shield-related Special-check SEXPs do not work on ship %s because it uses model point shields.\n", Player_ship->ship_name));
20878 			return SEXP_FALSE;
20879 		}
20880 		break;
20881 
20882 	case 5:	//	Player's shield is quick repaired
20883 		if (!(Ship_info[Player_ship->ship_info_index].flags[Ship::Info_Flags::Model_point_shields])) {
20884 			nprintf(("AI", "Frame %i, recharged to %7.3f\n", Framecount, Player_obj->shield_quadrant[FRONT_QUAD]));
20885 
20886 			shield_apply_damage(Player_obj, FRONT_QUAD, -flFrametime*200.0f);
20887 
20888 			if (Player_obj->shield_quadrant[FRONT_QUAD] > shield_get_max_quad(Player_obj))
20889 			Player_obj->shield_quadrant[FRONT_QUAD] = shield_get_max_quad(Player_obj);
20890 
20891 			if (Player_obj->shield_quadrant[FRONT_QUAD] > Player_obj->shield_quadrant[(FRONT_QUAD+1)%DEFAULT_SHIELD_SECTIONS] - 2.0f)
20892 				return SEXP_TRUE;
20893 			else
20894 				return SEXP_FALSE;
20895 		} else {
20896 			nprintf(("Warning", "Shield-related Special-check SEXPs do not work on ship %s because it uses model point shields.\n", Player_ship->ship_name));
20897 			return SEXP_FALSE;
20898 		}
20899 		break;
20900 
20901 	case 6:	//	3 of player's shield quadrants are reduced to 0.
20902 		if (!(Ship_info[Player_ship->ship_info_index].flags[Ship::Info_Flags::Model_point_shields])) {
20903 			Player_obj->shield_quadrant[1] = 1.0f;
20904 			Player_obj->shield_quadrant[2] = 1.0f;
20905 			Player_obj->shield_quadrant[3] = 1.0f;
20906 			hud_shield_quadrant_hit(Player_obj, FRONT_QUAD);
20907 		} else {
20908 			nprintf(("Warning", "Shield-related Special-check SEXPs do not work on ship %s because it uses model point shields.\n", Player_ship->ship_name));
20909 			return SEXP_FALSE;
20910 		}
20911 		return SEXP_TRUE;
20912 
20913 	case 7:	//	Make sure front quadrant has been maximized, or close to it.
20914 		if (!(Ship_info[Player_ship->ship_info_index].flags[Ship::Info_Flags::Model_point_shields])) {
20915 			if (shield_quad_near_max(FRONT_QUAD)) return SEXP_TRUE; else return SEXP_FALSE;
20916 		} else {
20917 			nprintf(("Warning", "Shield-related Special-check SEXPs do not work on ship %s because it uses model point shields.\n", Player_ship->ship_name));
20918 			return SEXP_FALSE;
20919 		}
20920 		break;
20921 
20922 	case 8:	//	Make sure rear quadrant has been maximized, or close to it.
20923 		if (!(Ship_info[Player_ship->ship_info_index].flags[Ship::Info_Flags::Model_point_shields])) {
20924 			if (shield_quad_near_max(REAR_QUAD)) return SEXP_TRUE; else return SEXP_FALSE;
20925 		} else {
20926 			nprintf(("Warning", "Shield-related Special-check SEXPs do not work on ship %s because it uses model point shields.\n", Player_ship->ship_name));
20927 			return SEXP_FALSE;
20928 		}
20929 		break;
20930 
20931 	case 9:	//	Zero left and right quadrants in preparation for maximizing rear quadrant.
20932 		if (!(Ship_info[Player_ship->ship_info_index].flags[Ship::Info_Flags::Model_point_shields])) {
20933 			Player_obj->shield_quadrant[LEFT_QUAD] = 0.0f;
20934 			Player_obj->shield_quadrant[RIGHT_QUAD] = 0.0f;
20935 			hud_shield_quadrant_hit(Player_obj, LEFT_QUAD);
20936 			return SEXP_TRUE;
20937 		} else {
20938 			nprintf(("Warning", "Shield-related Special-check SEXPs do not work on ship %s because it uses model point shields.\n", Player_ship->ship_name));
20939 			return SEXP_FALSE;
20940 		}
20941 		break;
20942 
20943 	case 10:	//	Return true if player is low on Interceptors.
20944 		if (Player_ship->weapons.secondary_bank_ammo[0] + Player_ship->weapons.secondary_bank_ammo[1] < 8)
20945 			return SEXP_TRUE;
20946 		else
20947 			return SEXP_FALSE;
20948 		break;
20949 
20950 	case 11:	//	Return true if player has plenty of Interceptors.
20951 		if (Player_ship->weapons.secondary_bank_ammo[0] + Player_ship->weapons.secondary_bank_ammo[1] >= 8)
20952 			return SEXP_TRUE;
20953 		else
20954 			return SEXP_FALSE;
20955 		break;
20956 
20957 	case 12:	//	Return true if player is low on Interceptors.
20958 		if (Player_ship->weapons.secondary_bank_ammo[0] + Player_ship->weapons.secondary_bank_ammo[1] < 4)
20959 			return SEXP_TRUE;
20960 		else
20961 			return SEXP_FALSE;
20962 		break;
20963 
20964 	case 13:	// Zero front shield quadrant.  Added for Jim Boone on August 26, 1999 by MK.
20965 		if (!(Ship_info[Player_ship->ship_info_index].flags[Ship::Info_Flags::Model_point_shields])) {
20966 			Player_obj->shield_quadrant[FRONT_QUAD] = 0.0f;
20967 			hud_shield_quadrant_hit(Player_obj, FRONT_QUAD);
20968 			return SEXP_TRUE;
20969 		} else {
20970 			nprintf(("Warning", "Shield-related Special-check SEXPs do not work on ship %s because it uses model point shields.\n", Player_ship->ship_name));
20971 			return SEXP_FALSE;
20972 		}
20973 		break;
20974 
20975 	case 100:	//	Return true if player is out of countermeasures.
20976 		if (Player_ship->cmeasure_count <= 0)
20977 			return SEXP_TRUE;
20978 		else
20979 			return SEXP_FALSE;
20980 
20981 	default:
20982 		Assertion(false, "Special sexp processing code was called for an unsupported node type!");
20983 	}
20984 
20985 	return SEXP_FALSE;
20986 }
20987 
20988 // Karajorma / Goober5000
sexp_string_to_int(int n)20989 int sexp_string_to_int(int n)
20990 {
20991 	bool first_ch = true;
20992 	char *buf_ch, buf[TOKEN_LENGTH];
20993 	Assert (n != -1);
20994 
20995 	if (Sexp_nodes[n].cache)
20996 		return Sexp_nodes[n].cache->numeric_literal;
20997 
20998 	// maybe forward to a special-arg node
20999 	if (Sexp_nodes[n].flags & SNF_SPECIAL_ARG_IN_NODE)
21000 	{
21001 		auto current_argument = Sexp_replacement_arguments.back();
21002 		int arg_node = current_argument.second;
21003 
21004 		if (arg_node >= 0)
21005 			return sexp_string_to_int(arg_node);
21006 	}
21007 
21008 	// copy all numeric characters to buf
21009 	// also, copy a sign symbol if we haven't copied numbers yet
21010 	buf_ch = buf;
21011 	for (auto ch = CTEXT(n); *ch != 0; ch++)
21012 	{
21013 		if ((first_ch && (*ch == '-' || *ch == '+')) || strchr("0123456789", *ch))
21014 		{
21015 			*buf_ch = *ch;
21016 			buf_ch++;
21017 
21018 			first_ch = false;
21019 		}
21020 
21021 		// don't save the fractional parts of decimal numbers
21022 		if (*ch == '.')
21023 			break;
21024 	}
21025 
21026 	// terminate string
21027 	*buf_ch = '\0';
21028 
21029 	int num = atoi(buf);
21030 
21031 	// cache the value, unless this node is a variable or argument because the value may change
21032 	if (!(Sexp_nodes[n].type & SEXP_FLAG_VARIABLE) && !(Sexp_nodes[n].flags & SNF_SPECIAL_ARG_IN_NODE))
21033 		Sexp_nodes[n].cache = new sexp_cached_data(OPF_NUMBER, num, -1);
21034 
21035 	return num;
21036 }
21037 
21038 // Goober5000
sexp_int_to_string(int n)21039 void sexp_int_to_string(int n)
21040 {
21041 	int i, sexp_variable_index;
21042 	bool is_nan, is_nan_forever;
21043 	char new_text[TOKEN_LENGTH];
21044 
21045 	// Only do single player or multi host
21046 	if (MULTIPLAYER_CLIENT)
21047 		return;
21048 
21049 	i = eval_num(n, is_nan, is_nan_forever);
21050 	n = CDR(n);
21051 
21052 	// get sexp_variable index
21053 	sexp_variable_index = sexp_get_variable_index(n);
21054 
21055 	// check variable type
21056 	if (!(Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_STRING))
21057 	{
21058 		Warning(LOCATION, "Cannot assign a string to a non-string variable %s!", Sexp_variables[sexp_variable_index].variable_name);
21059 		return;
21060 	}
21061 
21062 	// write string
21063 	if (is_nan || is_nan_forever)
21064 		sprintf(new_text, "NaN");
21065 	else
21066 		sprintf(new_text, "%d", i);
21067 
21068 	// assign to variable
21069 	sexp_modify_variable(new_text, sexp_variable_index);
21070 }
21071 
21072 // Goober5000
sexp_string_concatenate(int n)21073 void sexp_string_concatenate(int n)
21074 {
21075 	int sexp_variable_index;
21076 	char new_text[TOKEN_LENGTH * 2];
21077 
21078 	// Only do single player or multi host
21079 	if ( MULTIPLAYER_CLIENT )
21080 		return;
21081 
21082 	auto str1 = CTEXT(n);
21083 	n = CDR(n);
21084 	auto str2 = CTEXT(n);
21085 	n = CDR(n);
21086 
21087 	// get sexp_variable index
21088 	sexp_variable_index = sexp_get_variable_index(n);
21089 
21090 	// check variable type
21091 	if (!(Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_STRING))
21092 	{
21093 		Warning(LOCATION, "Cannot assign a string to a non-string variable %s!", Sexp_variables[sexp_variable_index].variable_name);
21094 		return;
21095 	}
21096 
21097 	// concatenate strings
21098 	strcpy_s(new_text, str1);
21099 	strcat_s(new_text, str2);
21100 
21101 	// check length
21102 	if (strlen(new_text) >= TOKEN_LENGTH)
21103 	{
21104 		Warning(LOCATION, "Concatenated string '%s' has " SIZE_T_ARG " characters, but the maximum is %d.  The string will be truncated.", new_text, strlen(new_text), TOKEN_LENGTH - 1);
21105 		new_text[TOKEN_LENGTH] = 0;
21106 	}
21107 
21108 	// assign to variable
21109 	sexp_modify_variable(new_text, sexp_variable_index);
21110 }
21111 
21112 // Goober5000
sexp_string_concatenate_block(int n)21113 void sexp_string_concatenate_block(int n)
21114 {
21115 	int sexp_variable_index;
21116 	SCP_string new_text;
21117 
21118 	// Only do single player or multi host
21119 	if (MULTIPLAYER_CLIENT)
21120 		return;
21121 
21122 	// get sexp_variable index
21123 	sexp_variable_index = sexp_get_variable_index(n);
21124 	n = CDR(n);
21125 
21126 	// check variable type
21127 	if (!(Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_STRING))
21128 	{
21129 		Warning(LOCATION, "Cannot assign a string to a non-string variable %s!", Sexp_variables[sexp_variable_index].variable_name);
21130 		return;
21131 	}
21132 
21133 	// concatenate strings
21134 	while (n >= 0)
21135 	{
21136 		new_text.append(CTEXT(n));
21137 		n = CDR(n);
21138 	}
21139 
21140 	// check length
21141 	if (new_text.length() >= TOKEN_LENGTH)
21142 	{
21143 		Warning(LOCATION, "Concatenated string '%s' has " SIZE_T_ARG " characters, but the maximum is %d.  The string will be truncated.", new_text.c_str(), new_text.length(), TOKEN_LENGTH - 1);
21144 		new_text.resize(TOKEN_LENGTH - 1);
21145 	}
21146 
21147 	// assign to variable
21148 	sexp_modify_variable(new_text.c_str(), sexp_variable_index);
21149 }
21150 
21151 // Goober5000
sexp_string_get_length(int node)21152 int sexp_string_get_length(int node)
21153 {
21154 	auto text = CTEXT(node);
21155 	return (int)unicode::num_codepoints(text, text + strlen(text));
21156 }
21157 
21158 // Goober5000
sexp_string_get_substring(int node)21159 void sexp_string_get_substring(int node)
21160 {
21161 	int n = node, pos, len, sexp_variable_index;
21162 	bool is_nan, is_nan_forever;
21163 	char new_text[TOKEN_LENGTH];
21164 	memset(new_text, 0, TOKEN_LENGTH);
21165 
21166 	// Only do single player or multi host
21167 	if ( MULTIPLAYER_CLIENT )
21168 		return;
21169 
21170 	auto parent = CTEXT(n);
21171 	n = CDR(n);
21172 
21173 	eval_nums(n, is_nan, is_nan_forever, pos, len);
21174 
21175 	// get sexp_variable index
21176 	sexp_variable_index = sexp_get_variable_index(n);
21177 
21178 	// check variable type
21179 	if (!(Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_STRING))
21180 	{
21181 		Warning(LOCATION, "Cannot assign a string to a non-string variable %s!", Sexp_variables[sexp_variable_index].variable_name);
21182 		return;
21183 	}
21184 
21185 	// now actually get the substring
21186 	if (!is_nan && !is_nan_forever)
21187 	{
21188 		auto parent_byte_len = strlen(parent);
21189 		auto parent_end = parent + parent_byte_len;
21190 		auto parent_len = (int)unicode::num_codepoints(parent, parent_end);
21191 
21192 		// sanity
21193 		if (pos >= parent_len)
21194 		{
21195 			Warning(LOCATION, "( string-get-substring %s %d %d ) failed: starting position is larger than the string length!", parent, pos, len);
21196 			return;
21197 		}
21198 
21199 		// sanity
21200 		if (pos + len > parent_len)
21201 			len = parent_len - pos;
21202 
21203 		// copy substring
21204 		auto start_ptr = parent;
21205 		// Advance the pointer by n codepoints to the start of our substring
21206 		unicode::advance(start_ptr, static_cast<size_t>(pos), parent_end);
21207 
21208 		auto end_ptr = start_ptr;
21209 		unicode::advance(end_ptr, static_cast<size_t>(len), parent_end);
21210 
21211 		auto byte_diff = end_ptr - start_ptr;
21212 
21213 		strncpy(new_text, start_ptr, byte_diff);
21214 	}
21215 
21216 	// assign to variable
21217 	sexp_modify_variable(new_text, sexp_variable_index);
21218 }
21219 
21220 // Goober5000
sexp_string_set_substring(int node)21221 void sexp_string_set_substring(int node)
21222 {
21223 	int n = node, pos, len, sexp_variable_index;
21224 	bool is_nan, is_nan_forever;
21225 
21226 	// Only do single player or multi host
21227 	if ( MULTIPLAYER_CLIENT )
21228 		return;
21229 
21230 	auto parent = CTEXT(n);
21231 	n = CDR(n);
21232 
21233 	eval_nums(n, is_nan, is_nan_forever, pos, len);
21234 	if (is_nan || is_nan_forever)
21235 		return;
21236 
21237 	auto new_substring = CTEXT(n);
21238 	n = CDR(n);
21239 
21240 	// get sexp_variable index
21241 	sexp_variable_index = sexp_get_variable_index(n);
21242 
21243 	// check variable type
21244 	if (!(Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_STRING))
21245 	{
21246 		Warning(LOCATION, "Cannot assign a string to a non-string variable %s!", Sexp_variables[sexp_variable_index].variable_name);
21247 		return;
21248 	}
21249 
21250 	int parent_byte_len = (int)strlen(parent);
21251 
21252 	auto parent_len = (int)unicode::num_codepoints(parent, parent + parent_byte_len);
21253 
21254 	// sanity
21255 	if (pos >= parent_len)
21256 	{
21257 		Warning(LOCATION, "( string-set-substring %s %d %d %s ) failed: starting position is larger than the string length!", parent, pos, len, new_substring);
21258 		return;
21259 	}
21260 
21261 	SCP_string new_text = parent;
21262 
21263 	auto range = unicode::codepoint_range(parent);
21264 	auto end_iter = range.end();
21265 
21266 	size_t substring_begin_byte = 0;
21267 	size_t substring_end_byte = 0;
21268 	auto i = 0;
21269 	for (auto iter = range.begin(); iter != end_iter; ++iter, ++i) {
21270 		if (i == pos) {
21271 			substring_begin_byte = iter.pos() - parent;
21272 		} else if (i == pos + len) {
21273 			substring_end_byte = iter.pos() - parent;
21274 			// We have reached the end byte so we have done everything we need to
21275 			break;
21276 		}
21277 	}
21278 
21279 	// This shouldn't happen
21280 	Assertion(substring_begin_byte < substring_end_byte,
21281 			  "The begin position of the substring must be less than the end position!");
21282 
21283 	new_text.replace(substring_begin_byte, substring_end_byte - substring_begin_byte, new_substring);
21284 
21285 	if (new_text.size() >= TOKEN_LENGTH) {
21286 		Warning(LOCATION, "Concatenated string is too long and will be truncated.");
21287 
21288 		new_text.resize(TOKEN_LENGTH - 1);
21289 
21290 		// This might have broken the UTF-8 sequence so that needs to be fixed in Unicode mode
21291 		if (Unicode_text_mode) {
21292 			auto invalid = utf8::find_invalid(new_text.begin(), new_text.end());
21293 
21294 			if (invalid != new_text.end()) {
21295 				// Found an invalid sequence. End the string right before that to make sure the string is still valid
21296 				new_text.erase(invalid, new_text.end());
21297 			}
21298 		}
21299 	}
21300 
21301 	// assign to variable
21302 	sexp_modify_variable(new_text.c_str(), sexp_variable_index);
21303 }
21304 
sexp_modify_variable_xstr(int n)21305 void sexp_modify_variable_xstr(int n)
21306 {
21307 	Assert(n >= 0);
21308 
21309 	// Only do single player or multi host
21310 	if ( MULTIPLAYER_CLIENT )
21311 		return;
21312 
21313 	// get sexp_variable index
21314 	auto sexp_variable_index = sexp_get_variable_index(n);
21315 	n = CDR(n);
21316 
21317 	if (!(Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_STRING)) {
21318 		Warning(LOCATION, "Variable for modify-variable-xstr has to be a string variable!");
21319 		return;
21320 	}
21321 
21322 	// get new string
21323 	const char* new_text = Sexp_nodes[n].text;
21324 
21325 	// assign to variable
21326 	sexp_modify_variable(new_text, sexp_variable_index);
21327 }
21328 
21329 // breaks into the code or sends a warning
sexp_debug(int node)21330 void sexp_debug(int node)
21331 {
21332 	int i;
21333 	SCP_string warning_message;
21334 
21335 	#ifdef NDEBUG
21336 	bool no_release_message = is_sexp_true(node);
21337 	#endif
21338 
21339 	node = CDR(node);
21340 	Assertion (node >= 0, "No message defined in debug SEXP");
21341 
21342 	// we'll suppose it's the string for now
21343 	warning_message = CTEXT(node);
21344 
21345 	// but use an actual message if one exists
21346 	for (i=0; i<Num_messages; i++) {
21347 		// find the message
21348 		if ( !stricmp(Messages[i].name, warning_message.c_str()) ) {
21349 			warning_message = Messages[i].message;
21350 			break;
21351 		}
21352 	}
21353 
21354 	// replace variables if necessary
21355 	sexp_replace_variable_names_with_values(warning_message);
21356 
21357 	//send the message
21358 	#ifndef NDEBUG
21359 		Warning(LOCATION, "%s", warning_message.c_str());
21360 	#else
21361 	if (!no_release_message) {
21362 		ReleaseWarning(LOCATION, "%s", warning_message.c_str());
21363 	}
21364 	#endif
21365 }
21366 
21367 // custom sexp operator for handling misc training stuff
sexp_special_training_check(int node)21368 int sexp_special_training_check(int node)
21369 {
21370 	int num, rtn;
21371 	bool is_nan, is_nan_forever;
21372 
21373 	num = eval_num(node, is_nan, is_nan_forever);
21374 	if (is_nan)
21375 		return SEXP_FALSE;
21376 	if (is_nan_forever)
21377 		return SEXP_KNOWN_FALSE;
21378 	if (num == SPECIAL_CHECK_TRAINING_FAILURE)
21379 		return Training_failure ? SEXP_TRUE : SEXP_FALSE;
21380 
21381 	// To MK: do whatever you want with this number here.
21382 	rtn = process_special_sexps(num);
21383 
21384 	return rtn;
21385 }
21386 
21387 // sexpression to flash a hud gauge.  gauge name is text value of node
sexp_flash_hud_gauge(int node)21388 void sexp_flash_hud_gauge( int node )
21389 {
21390 	auto name = CTEXT(node);
21391 	bool match = false;
21392 
21393 	// see if this is specified the new way, according to the HUD type #define
21394 	int type = hud_gauge_type_lookup(name);
21395 
21396 	// now go through and find out which gauge (index i in the legacy list) to flash
21397 	for (int i = 0; i < NUM_HUD_GAUGES; ++i) {
21398 		if (type < 0) {
21399 			if (stricmp(name, Legacy_HUD_gauges[i].hud_gauge_text) == 0) {
21400 				match = true;
21401 			}
21402 		} else if (type == Legacy_HUD_gauges[i].hud_gauge_type) {
21403 			match = true;
21404 		}
21405 
21406 		if (match) {
21407 			hud_gauge_start_flash(i);	// call HUD function to flash gauge
21408 
21409 			Current_sexp_network_packet.start_callback();
21410 			Current_sexp_network_packet.send_int(i);
21411 			Current_sexp_network_packet.end_callback();
21412 
21413 			break;
21414 		}
21415 	}
21416 
21417 	if (!match && type >= 0) {
21418 		Warning(LOCATION, "HUD gauge '%s' is not a legacy gauge; flashing is not supported", name);
21419 	}
21420 }
21421 
multi_sexp_flash_hud_gauge()21422 void multi_sexp_flash_hud_gauge()
21423 {
21424 	int i;
21425 
21426 	if (Current_sexp_network_packet.get_int(i)) {
21427 		hud_gauge_start_flash(i);
21428 	}
21429 }
21430 
sexp_set_training_context_fly_path(int node)21431 void sexp_set_training_context_fly_path(int node)
21432 {
21433 	bool is_nan, is_nan_forever;
21434 
21435 	waypoint_list *wp_list = find_matching_waypoint_list(CTEXT(node));
21436 	if (wp_list == nullptr)
21437 		return;
21438 
21439 	int distance = eval_num(CDR(node), is_nan, is_nan_forever);
21440 	if (is_nan || is_nan_forever)
21441 		return;
21442 
21443 	Training_context |= TRAINING_CONTEXT_FLY_PATH;
21444 	Training_context_path = wp_list;
21445 	Training_context_distance = (float)distance;
21446 	Training_context_goal_waypoint = 0;
21447 	Training_context_at_waypoint = -1;
21448 }
21449 
sexp_set_training_context_speed(int node)21450 void sexp_set_training_context_speed(int node)
21451 {
21452 	int min, max;
21453 	bool is_nan, is_nan_forever;
21454 
21455 	eval_nums(node, is_nan, is_nan_forever, min, max);
21456 	if (is_nan || is_nan_forever)
21457 		return;
21458 
21459 	Training_context |= TRAINING_CONTEXT_SPEED;
21460 	Training_context_speed_min = min;
21461 	Training_context_speed_max = max;
21462 	Training_context_speed_set = 0;
21463 }
21464 
sexp_scramble_messages(int node,bool scramble)21465 void sexp_scramble_messages(int node, bool scramble)
21466 {
21467 	if (node < 0)
21468 	{
21469 		// scramble messages on player ship... this isn't multi compatible, but neither was the old version of the sexp
21470 		Player_ship->flags.set(Ship::Ship_Flags::Scramble_messages, scramble);
21471 		return;
21472 	}
21473 
21474 	sexp_deal_with_ship_flag(node, true, Object::Object_Flags::NUM_VALUES, Ship::Ship_Flags::Scramble_messages, Mission::Parse_Object_Flags::SF_Scramble_messages, scramble, false);
21475 }
21476 
toggle_cutscene_bars(float delta_speed,int set)21477 void toggle_cutscene_bars(float delta_speed, int set)
21478 {
21479 	//Do we want the bars?
21480 	if (set) {
21481 		Cutscene_bar_flags |= CUB_CUTSCENE;
21482 	}
21483 	else {
21484 		Cutscene_bar_flags &= ~CUB_CUTSCENE;
21485 	}
21486 
21487 	if(delta_speed > 0.0f) {
21488 		Cutscene_bars_progress = 0.0f;
21489 		Cutscene_bar_flags |= CUB_GRADUAL;
21490 		Cutscene_delta_time = delta_speed;
21491 	}
21492 	else {
21493 		Cutscene_bar_flags &= ~CUB_GRADUAL;
21494 	}
21495 }
21496 
sexp_toggle_cutscene_bars(int node,int set)21497 void sexp_toggle_cutscene_bars(int node, int set)
21498 {
21499 	bool is_nan, is_nan_forever;
21500 	float delta_speed = 0.0f;
21501 
21502 	if (node != -1) {
21503 		delta_speed = eval_num(node, is_nan, is_nan_forever) / 1000.0f;
21504 		if (is_nan || is_nan_forever) {
21505 			return;
21506 		}
21507 	}
21508 
21509 	toggle_cutscene_bars(delta_speed, set);
21510 
21511 	Current_sexp_network_packet.start_callback();
21512 	Current_sexp_network_packet.send_float(delta_speed);
21513 	Current_sexp_network_packet.end_callback();
21514 }
21515 
multi_sexp_toggle_cutscene_bars(int set)21516 void multi_sexp_toggle_cutscene_bars(int set)
21517 {
21518 	float delta_speed;
21519 
21520 	if(Current_sexp_network_packet.get_float(delta_speed) ) {
21521 		toggle_cutscene_bars(delta_speed, set);
21522 	}
21523 }
21524 
sexp_fade(bool fade_in,int duration,ubyte R,ubyte G,ubyte B)21525 void sexp_fade(bool fade_in, int duration, ubyte R, ubyte G, ubyte B)
21526 {
21527 	if (duration > 0)
21528 	{
21529 		Fade_start_timestamp = timestamp();
21530 		Fade_end_timestamp = timestamp(duration);
21531 		Fade_type = fade_in ? FI_FADEIN : FI_FADEOUT;
21532 		gr_create_shader(&Viewer_shader, R, G, B, Viewer_shader.c);
21533 	}
21534 	else
21535 	{
21536 		Fade_type = FI_NONE;
21537 		gr_create_shader(&Viewer_shader, R, G, B, fade_in ? 0 : 255);
21538 	}
21539 }
21540 
sexp_fade(int n,bool fade_in)21541 void sexp_fade(int n, bool fade_in)
21542 {
21543 	int duration = 0, R, G, B;
21544 	bool is_nan, is_nan_forever;
21545 	std::array<int, 3> temp_rgb;
21546 
21547 	// get duration if present
21548 	if (n >= 0)
21549 	{
21550 		duration = eval_num(n, is_nan, is_nan_forever);
21551 		if (is_nan || is_nan_forever)
21552 			return;
21553 		n = CDR(n);
21554 	}
21555 
21556 	// get color if present, with range checks, defaulting to -1
21557 	eval_array<int>(temp_rgb, n, is_nan, is_nan_forever, [](int num) {
21558 		return (num < 0 || num > 255) ? -1 : num;
21559 	}, -1);
21560 	if (is_nan || is_nan_forever)
21561 		return;
21562 	R = temp_rgb[0];
21563 	G = temp_rgb[1];
21564 	B = temp_rgb[2];
21565 
21566 	// select legacy (or default) fade color
21567 	if (R < 0 || G < 0 || B < 0)
21568 	{
21569 		// fade white
21570 		if (R == 1)
21571 		{
21572 			R = G = B = 255;
21573 		}
21574 		// fade red
21575 		else if (R == 2)
21576 		{
21577 			R = 255;
21578 			G = B = 0;
21579 		}
21580 		// default: fade black or previous fadeout color
21581 		else
21582 		{
21583 			// Mantis #2944: if we're fading in, use the same color we used to fade out with
21584 			if (fade_in)
21585 			{
21586 				R = Fade_out_r;
21587 				G = Fade_out_g;
21588 				B = Fade_out_b;
21589 			}
21590 			else
21591 			{
21592 				R = G = B = 0;
21593 			}
21594 		}
21595 	}
21596 
21597 	// Mantis #2944
21598 	// If we're fading out, save its color so we can fade-in with the same color
21599 	if (!fade_in)
21600 	{
21601 		Fade_out_r = R;
21602 		Fade_out_g = G;
21603 		Fade_out_b = B;
21604 	}
21605 
21606 	sexp_fade(fade_in, duration, (ubyte) R, (ubyte) G, (ubyte) B);
21607 
21608 	Current_sexp_network_packet.start_callback();
21609 	Current_sexp_network_packet.send_int(duration);
21610 	Current_sexp_network_packet.send_int(R);
21611 	Current_sexp_network_packet.send_int(G);
21612 	Current_sexp_network_packet.send_int(B);
21613 	Current_sexp_network_packet.end_callback();
21614 }
21615 
multi_sexp_fade(bool fade_in)21616 void multi_sexp_fade(bool fade_in)
21617 {
21618 	int duration = 0;
21619 	int R = 0;
21620 	int G = 0;
21621 	int B = 0;
21622 
21623 	if (Current_sexp_network_packet.get_int(duration))
21624 		if (Current_sexp_network_packet.get_int(R))
21625 			if (Current_sexp_network_packet.get_int(G))
21626 				Current_sexp_network_packet.get_int(B);
21627 
21628 	sexp_fade(fade_in, duration, (ubyte) R, (ubyte) G, (ubyte) B);
21629 }
21630 
sexp_get_set_camera(bool reset=false)21631 camera* sexp_get_set_camera(bool reset = false)
21632 {
21633 	static camid sexp_camera;
21634 	if (!reset)
21635 	{
21636 		if (Viewer_mode & VM_FREECAMERA)
21637 		{
21638 			camera *cam = cam_get_current().getCamera();
21639 			if (cam != nullptr)
21640 				return cam;
21641 		}
21642 	}
21643 	if (!sexp_camera.isValid())
21644 	{
21645 		sexp_camera = cam_create("SEXP camera");
21646 	}
21647 
21648 	cam_set_camera(sexp_camera);
21649 
21650 	return sexp_camera.getCamera();
21651 }
21652 
sexp_set_camera(int node)21653 void sexp_set_camera(int node)
21654 {
21655 	if (node < 0)
21656 	{
21657 		sexp_get_set_camera(true);
21658 		return;
21659 	}
21660 
21661 	auto cam_name = CTEXT(node);
21662 	camid cid = cam_lookup(cam_name);
21663 	if (!cid.isValid())
21664 	{
21665 		cid = cam_create(cam_name);
21666 	}
21667 	cam_set_camera(cid);
21668 }
21669 
sexp_set_camera_position(int n)21670 void sexp_set_camera_position(int n)
21671 {
21672 	camera *cam = sexp_get_set_camera();
21673 	if (cam == nullptr)
21674 		return;
21675 
21676 	bool is_nan, is_nan_forever;
21677 	vec3d camera_vec;
21678 	float camera_time, camera_acc_time, camera_dec_time;
21679 
21680 	eval_vec3d(&camera_vec, n, is_nan, is_nan_forever);
21681 	if (is_nan || is_nan_forever)
21682 		return;
21683 
21684 	int count = eval_nums(n, is_nan, is_nan_forever, camera_time, camera_acc_time, camera_dec_time);
21685 	if (is_nan || is_nan_forever)
21686 		return;
21687 	if (count == 2)
21688 		camera_dec_time = camera_acc_time;
21689 	camera_time /= 1000.0f;
21690 	camera_acc_time /= 1000.0f;
21691 	camera_dec_time /= 1000.0f;
21692 
21693 	cam->set_position(&camera_vec, camera_time, camera_acc_time, camera_dec_time);
21694 
21695 	//multiplayer callback
21696 	Current_sexp_network_packet.start_callback();
21697 	Current_sexp_network_packet.send_float(camera_vec.xyz.x);
21698 	Current_sexp_network_packet.send_float(camera_vec.xyz.y);
21699 	Current_sexp_network_packet.send_float(camera_vec.xyz.z);
21700 	Current_sexp_network_packet.send_float(camera_time);
21701 	Current_sexp_network_packet.send_float(camera_acc_time);
21702 	Current_sexp_network_packet.send_float(camera_dec_time);
21703 	Current_sexp_network_packet.end_callback();
21704 }
21705 
21706 //CommanderDJ
multi_sexp_set_camera_position()21707 void multi_sexp_set_camera_position()
21708 {
21709 	camera *cam = sexp_get_set_camera();
21710 	Assert(cam != nullptr);
21711 
21712 	vec3d camera_vec;
21713 	float camera_time = 0.0f;
21714 	float camera_acc_time = 0.0f;
21715 	float camera_dec_time = 0.0f;
21716 
21717 	Current_sexp_network_packet.get_float(camera_vec.xyz.x);
21718 	Current_sexp_network_packet.get_float(camera_vec.xyz.y);
21719 	Current_sexp_network_packet.get_float(camera_vec.xyz.z);
21720 	Current_sexp_network_packet.get_float(camera_time);
21721 	Current_sexp_network_packet.get_float(camera_acc_time);
21722 	Current_sexp_network_packet.get_float(camera_dec_time);
21723 
21724 	cam->set_position(&camera_vec, camera_time, camera_acc_time, camera_dec_time);
21725 }
21726 
sexp_set_camera_rotation(int n)21727 void sexp_set_camera_rotation(int n)
21728 {
21729 	camera *cam = sexp_get_set_camera();
21730 	if (cam == nullptr)
21731 		return;
21732 
21733 	bool is_nan, is_nan_forever;
21734 	angles rot_angles;
21735 	float rot_time, rot_acc_time, rot_dec_time;
21736 
21737 	// Angles are in degrees
21738 	eval_angles(&rot_angles, n, is_nan, is_nan_forever);
21739 	if (is_nan || is_nan_forever)
21740 		return;
21741 
21742 	int count = eval_nums(n, is_nan, is_nan_forever, rot_time, rot_acc_time, rot_dec_time);
21743 	if (is_nan || is_nan_forever)
21744 		return;
21745 	if (count == 2)
21746 		rot_dec_time = rot_acc_time;
21747 	rot_time /= 1000.0f;
21748 	rot_acc_time /= 1000.0f;
21749 	rot_dec_time /= 1000.0f;
21750 
21751 	cam->set_rotation(&rot_angles, rot_time, rot_acc_time, rot_dec_time);
21752 
21753 	Current_sexp_network_packet.start_callback();
21754 	Current_sexp_network_packet.send_float(rot_angles.b);
21755 	Current_sexp_network_packet.send_float(rot_angles.h);
21756 	Current_sexp_network_packet.send_float(rot_angles.p);
21757 	Current_sexp_network_packet.send_float(rot_time);
21758 	Current_sexp_network_packet.send_float(rot_acc_time);
21759 	Current_sexp_network_packet.send_float(rot_dec_time);
21760 	Current_sexp_network_packet.end_callback();
21761 }
21762 
multi_sexp_set_camera_rotation()21763 void multi_sexp_set_camera_rotation()
21764 {
21765 	camera *cam = sexp_get_set_camera();
21766 	if (cam == nullptr)
21767 		return;
21768 
21769 	angles rot_angles;
21770 	float rot_time = 0.0f;
21771 	float rot_acc_time = 0.0f;
21772 	float rot_dec_time = 0.0f;
21773 
21774 	Current_sexp_network_packet.get_float(rot_angles.b);
21775 	Current_sexp_network_packet.get_float(rot_angles.h);
21776 	Current_sexp_network_packet.get_float(rot_angles.p);
21777 	Current_sexp_network_packet.get_float(rot_time);
21778 	Current_sexp_network_packet.get_float(rot_acc_time);
21779 	Current_sexp_network_packet.get_float(rot_dec_time);
21780 
21781 	cam->set_rotation(&rot_angles, rot_time, rot_acc_time, rot_dec_time);
21782 }
21783 
sexp_set_camera_facing(int n)21784 void sexp_set_camera_facing(int n)
21785 {
21786 	camera *cam = sexp_get_set_camera();
21787 	if (cam == nullptr)
21788 		return;
21789 
21790 	bool is_nan, is_nan_forever;
21791 	vec3d location;
21792 	float rot_time, rot_acc_time, rot_dec_time;
21793 
21794 	eval_vec3d(&location, n, is_nan, is_nan_forever);
21795 	if (is_nan || is_nan_forever)
21796 		return;
21797 
21798 	int count = eval_nums(n, is_nan, is_nan_forever, rot_time, rot_acc_time, rot_dec_time);
21799 	if (is_nan || is_nan_forever)
21800 		return;
21801 	if (count == 2)
21802 		rot_dec_time = rot_acc_time;
21803 	rot_time /= 1000.0f;
21804 	rot_acc_time /= 1000.0f;
21805 	rot_dec_time /= 1000.0f;
21806 
21807 	cam->set_rotation_facing(&location, rot_time, rot_acc_time, rot_dec_time);
21808 
21809 	//multiplayer callback
21810 	Current_sexp_network_packet.start_callback();
21811 	Current_sexp_network_packet.send_float(location.xyz.x);
21812 	Current_sexp_network_packet.send_float(location.xyz.y);
21813 	Current_sexp_network_packet.send_float(location.xyz.z);
21814 	Current_sexp_network_packet.send_float(rot_time);
21815 	Current_sexp_network_packet.send_float(rot_acc_time);
21816 	Current_sexp_network_packet.send_float(rot_dec_time);
21817 	Current_sexp_network_packet.end_callback();
21818 }
21819 
multi_sexp_set_camera_facing()21820 void multi_sexp_set_camera_facing()
21821 {
21822 	camera *cam = sexp_get_set_camera();
21823 	if (cam == nullptr)
21824 		return;
21825 
21826 	vec3d location;
21827 	float rot_time = 0.0f;
21828 	float rot_acc_time = 0.0f;
21829 	float rot_dec_time = 0.0f;
21830 
21831 	Current_sexp_network_packet.get_float(location.xyz.x);
21832 	Current_sexp_network_packet.get_float(location.xyz.y);
21833 	Current_sexp_network_packet.get_float(location.xyz.z);
21834 	Current_sexp_network_packet.get_float(rot_time);
21835 	Current_sexp_network_packet.get_float(rot_acc_time);
21836 	Current_sexp_network_packet.get_float(rot_dec_time);
21837 
21838 	cam->set_rotation_facing(&location, rot_time, rot_acc_time, rot_dec_time);
21839 }
21840 
sexp_set_camera_facing_object(int n)21841 void sexp_set_camera_facing_object(int n)
21842 {
21843 	camera *cam = sexp_get_set_camera();
21844 	if (cam == nullptr)
21845 		return;
21846 
21847 	object_ship_wing_point_team oswpt;
21848 	vec3d *pos;
21849 
21850 	eval_object_ship_wing_point_team(&oswpt, n);
21851 	switch (oswpt.type)
21852 	{
21853 		case OSWPT_TYPE_EXITED:
21854 		{
21855 			Warning(LOCATION, "Camera tried to face destroyed/departed object %s", oswpt.object_name);
21856 			return;
21857 		}
21858 
21859 		case OSWPT_TYPE_SHIP:
21860 		case OSWPT_TYPE_WING:
21861 		case OSWPT_TYPE_WAYPOINT:
21862 		{
21863 			pos = &oswpt.objp->pos;
21864 			break;
21865 		}
21866 
21867 		default:
21868 			return;
21869 	}
21870 	n = CDR(n);
21871 
21872 	bool is_nan, is_nan_forever;
21873 	float rot_time, rot_acc_time, rot_dec_time;
21874 
21875 	// Now get the rotation time values
21876 	int count = eval_nums(n, is_nan, is_nan_forever, rot_time, rot_acc_time, rot_dec_time);
21877 	if (is_nan || is_nan_forever)
21878 		return;
21879 	if (count == 2)
21880 		rot_dec_time = rot_acc_time;
21881 	rot_time /= 1000.0f;
21882 	rot_acc_time /= 1000.0f;
21883 	rot_dec_time /= 1000.0f;
21884 
21885 	cam->set_rotation_facing(pos, rot_time, rot_acc_time, rot_dec_time);
21886 
21887 	//multiplayer callback
21888 	Current_sexp_network_packet.start_callback();
21889 	Current_sexp_network_packet.send_vec3d(pos);
21890 	Current_sexp_network_packet.send_float(rot_time);
21891 	Current_sexp_network_packet.send_float(rot_acc_time);
21892 	Current_sexp_network_packet.send_float(rot_dec_time);
21893 	Current_sexp_network_packet.end_callback();
21894 }
21895 
21896 //CommanderDJ
multi_sexp_set_camera_facing_object()21897 void multi_sexp_set_camera_facing_object()
21898 {
21899 	camera *cam = sexp_get_set_camera();
21900 	if (cam == nullptr)
21901 		return;
21902 
21903 	vec3d pos;
21904 	float rot_time = 0.0f;
21905 	float rot_acc_time = 0.0f;
21906 	float rot_dec_time = 0.0f;
21907 
21908 	Current_sexp_network_packet.get_vec3d(&pos);
21909 	Current_sexp_network_packet.get_float(rot_time);
21910 	Current_sexp_network_packet.get_float(rot_acc_time);
21911 	Current_sexp_network_packet.get_float(rot_dec_time);
21912 
21913 	cam->set_rotation_facing(&pos, rot_time, rot_acc_time, rot_dec_time);
21914 }
21915 
sexp_set_camera_fov(int n)21916 void sexp_set_camera_fov(int n)
21917 {
21918 	camera *cam = sexp_get_set_camera();
21919 	if (cam == nullptr)
21920 		return;
21921 
21922 	bool is_nan, is_nan_forever;
21923 	int int_fov;
21924 	float camera_fov, camera_time, camera_acc_time, camera_dec_time = 0.0f;
21925 
21926 	int_fov = eval_num(n, is_nan, is_nan_forever);
21927 	if (is_nan || is_nan_forever)
21928 		return;
21929 	n = CDR(n);
21930 	camera_fov = fl_radians(int_fov % 360);
21931 
21932 	int count = eval_nums(n, is_nan, is_nan_forever, camera_time, camera_acc_time, camera_dec_time);
21933 	if (is_nan || is_nan_forever)
21934 		return;
21935 	if (count == 2)
21936 		camera_dec_time = camera_acc_time;
21937 	camera_time /= 1000.0f;
21938 	camera_acc_time /= 1000.0f;
21939 	camera_dec_time /= 1000.0f;
21940 
21941 	cam->set_fov(camera_fov, camera_time, camera_acc_time, camera_dec_time);
21942 
21943 	Current_sexp_network_packet.start_callback();
21944 	Current_sexp_network_packet.send_float(camera_fov);
21945 	Current_sexp_network_packet.send_float(camera_time);
21946 	Current_sexp_network_packet.send_float(camera_acc_time);
21947 	Current_sexp_network_packet.send_float(camera_dec_time);
21948 	Current_sexp_network_packet.end_callback();
21949 }
21950 
21951 //CommanderDJ
multi_sexp_set_camera_fov()21952 void multi_sexp_set_camera_fov()
21953 {
21954 	camera *cam = sexp_get_set_camera();
21955 	if (cam == nullptr)
21956 		return;
21957 
21958 	float camera_fov = VIEWER_ZOOM_DEFAULT;
21959 	float camera_time = 0.0f;
21960 	float camera_acc_time = 0.0f;
21961 	float camera_dec_time = 0.0f;
21962 
21963 	Current_sexp_network_packet.get_float(camera_fov);
21964 	Current_sexp_network_packet.get_float(camera_time);
21965 	Current_sexp_network_packet.get_float(camera_acc_time);
21966 	Current_sexp_network_packet.get_float(camera_dec_time);
21967 
21968 	cam->set_fov(camera_fov, camera_time, camera_acc_time, camera_dec_time);
21969 }
21970 
21971 //Internal helper function for set-target and set-host
sexp_camera_get_objsub(int node,int * o_submodel)21972 object *sexp_camera_get_objsub(int node, int *o_submodel)
21973 {
21974 	//Important variables
21975 	object *objp = nullptr;
21976 	int submodel = -1;
21977 
21978 	//Get arguments
21979 	int n = node;
21980 
21981 	object_ship_wing_point_team oswpt;
21982 	eval_object_ship_wing_point_team(&oswpt, n);
21983 	n = CDR(n);
21984 
21985 	const char *sub_name = nullptr;
21986 	if (n >= 0)
21987 		sub_name = CTEXT(n);
21988 
21989 	//*****Process object
21990 	switch (oswpt.type)
21991 	{
21992 		case OSWPT_TYPE_SHIP:
21993 		case OSWPT_TYPE_WING:
21994 		case OSWPT_TYPE_WAYPOINT:
21995 			objp = oswpt.objp;
21996 			break;
21997 
21998 		default:
21999 			objp = nullptr;
22000 	}
22001 
22002 	//*****Process submodel
22003 	if(objp != nullptr && sub_name != nullptr && oswpt.type == OSWPT_TYPE_SHIP)
22004 	{
22005 		if(stricmp(sub_name, SEXP_NONE_STRING) != 0)
22006 		{
22007 			ship_subsys *ss = ship_get_subsys(&Ships[objp->instance], sub_name);
22008 			if (ss != nullptr)
22009 			{
22010 				submodel = ss->system_info->subobj_num;
22011 			}
22012 		}
22013 	}
22014 
22015 	if(o_submodel != nullptr)
22016 		*o_submodel = submodel;
22017 
22018 	return objp;
22019 }
22020 
sexp_set_camera_host(int node)22021 void sexp_set_camera_host(int node)
22022 {
22023 	//Try to get current camera
22024 	camera *cam = sexp_get_set_camera();
22025 	if (cam == nullptr)
22026 		return;
22027 
22028 	//*****Get variables
22029 	int submodel = -1;
22030 	object *objp = sexp_camera_get_objsub(node, &submodel);
22031 
22032 	//*****Set
22033 	cam->set_object_host(objp, submodel);
22034 }
22035 
sexp_set_camera_target(int node)22036 void sexp_set_camera_target(int node)
22037 {
22038 	//Try to get current camera
22039 	camera *cam = sexp_get_set_camera();
22040 	if (cam == nullptr)
22041 		return;
22042 
22043 	//*****Get variables
22044 	int submodel = -1;
22045 	object *objp = sexp_camera_get_objsub(node, &submodel);
22046 
22047 	//*****Set
22048 	cam->set_object_target(objp, submodel);
22049 
22050 	Current_sexp_network_packet.start_callback();
22051 	Current_sexp_network_packet.send_object(objp);
22052 	Current_sexp_network_packet.send_int(submodel);
22053 	Current_sexp_network_packet.end_callback();
22054 }
22055 
multi_sexp_set_camera_target()22056 void multi_sexp_set_camera_target()
22057 {
22058 	int submodel;
22059 	object *objp;
22060 
22061 	//Try to get current camera
22062 	camera *cam = sexp_get_set_camera();
22063 	if (cam == nullptr)
22064 		return;
22065 
22066 	Current_sexp_network_packet.get_object(objp);
22067 	Current_sexp_network_packet.get_int(submodel);
22068 
22069 	cam->set_object_target(objp, submodel);
22070 }
22071 
sexp_set_fov(int n)22072 void sexp_set_fov(int n)
22073 {
22074 	camera *cam = Main_camera.getCamera();
22075 	if (cam == nullptr) {
22076 		game_render_frame_setup();
22077 		cam = Main_camera.getCamera();
22078 	}
22079 
22080 	bool is_nan, is_nan_forever;
22081 	int int_fov = eval_num(n, is_nan, is_nan_forever);
22082 	if (is_nan || is_nan_forever)
22083 		return;
22084 
22085 	//Cap FOV to something reasonable.
22086 	float new_fov = i2fl(int_fov % 360);
22087 	Sexp_fov = fl_radians(new_fov);
22088 
22089 	Current_sexp_network_packet.start_callback();
22090 	Current_sexp_network_packet.send_float(new_fov);
22091 	Current_sexp_network_packet.end_callback();
22092 }
22093 
multi_sexp_set_fov()22094 void multi_sexp_set_fov()
22095 {
22096 	float new_fov;
22097 
22098 	camera *cam = Main_camera.getCamera();
22099 	if (cam == nullptr) {
22100 		game_render_frame_setup();
22101 		cam = Main_camera.getCamera();
22102 	}
22103 
22104 	Current_sexp_network_packet.get_float(new_fov);
22105 	Sexp_fov = fl_radians(new_fov);
22106 }
22107 
sexp_get_fov()22108 int sexp_get_fov()
22109 {
22110 	camera *cam = Main_camera.getCamera();
22111 	if (cam == nullptr)
22112 		return -1;
22113 	else if (Sexp_fov > 0.0f)
22114 		// SEXP override has been set
22115 		return (int)fl_degrees(Sexp_fov);
22116 	else
22117 		return (int)fl_degrees(cam->get_fov());
22118 }
22119 
22120 /**
22121  * @todo Check VIEWER_ZOOM_DEFAULT
22122  */
sexp_reset_fov()22123 void sexp_reset_fov()
22124 {
22125 	camera *cam = Main_camera.getCamera();
22126 	if (cam == nullptr)
22127 		return;
22128 
22129 	Sexp_fov = 0.0;
22130 	//cam->set_fov(VIEWER_ZOOM_DEFAULT);
22131 
22132 	Current_sexp_network_packet.do_callback();
22133 }
22134 
multi_sexp_reset_fov()22135 void multi_sexp_reset_fov()
22136 {
22137 	camera *cam = Main_camera.getCamera();
22138 	if (cam == nullptr)
22139 		return;
22140 
22141 	Sexp_fov = 0.0;
22142 }
22143 
sexp_reset_camera(int node)22144 void sexp_reset_camera(int node)
22145 {
22146 	bool cam_reset = false;
22147 	camera *cam = cam_get_current().getCamera();
22148 	if (cam != nullptr)
22149 	{
22150 		if (is_sexp_true(node))
22151 		{
22152 			cam->reset();
22153 			cam_reset = true;
22154 		}
22155 	}
22156 	cam_reset_camera();
22157 	Current_sexp_network_packet.start_callback();
22158 	Current_sexp_network_packet.send_bool(cam_reset);
22159 	Current_sexp_network_packet.end_callback();
22160 }
22161 
multi_sexp_reset_camera()22162 void multi_sexp_reset_camera()
22163 {
22164 	camera *cam = cam_get_current().getCamera();
22165 	bool cam_reset = false;
22166 
22167 	Current_sexp_network_packet.get_bool(cam_reset);
22168 	if ((cam != nullptr) && cam_reset) {
22169 		cam->reset();
22170 	}
22171 	cam_reset_camera();
22172 }
22173 
sexp_show_subtitle(int node)22174 void sexp_show_subtitle(int node)
22175 {
22176 	//These should be set to the default if not required to be explicitly defined
22177 	int x_pos, y_pos, width = 0;
22178 	const char *text, *imageanim = nullptr;
22179 	float display_time, fade_time = 0.0f;
22180 	int n = node;
22181 	std::array<int, 3> rgb = { 255, 255, 255 };
22182 	bool is_nan, is_nan_forever, center_x = false, center_y = false, post_shaded = false;
22183 
22184 	eval_nums(n, is_nan, is_nan_forever, x_pos, y_pos);
22185 	if (is_nan || is_nan_forever)
22186 		return;
22187 
22188 	if (gr_screen.center_w != 1024)
22189 		x_pos = (int)((x_pos / 1024.0f) * gr_screen.center_w);
22190 	if (gr_screen.center_h != 768)
22191 		y_pos = (int)((y_pos / 768.0f) * gr_screen.center_h);
22192 
22193 	text = CTEXT(n);
22194 	n = CDR(n);
22195 
22196 	display_time = eval_num(n, is_nan, is_nan_forever) / 1000.0f;	//is in ms
22197 	if (is_nan || is_nan_forever)
22198 		return;
22199 	n = CDR(n);
22200 
22201 	if (n != -1)
22202 	{
22203 		imageanim = CTEXT(n);
22204 		n = CDR(n);
22205 
22206 		if (n != -1)
22207 		{
22208 			fade_time = eval_num(n, is_nan, is_nan_forever) / 1000.0f; //also in ms
22209 			if (is_nan || is_nan_forever)
22210 				return;
22211 			n = CDR(n);
22212 
22213 			if (n != -1)
22214 			{
22215 				center_x = is_sexp_true(n);
22216 				n = CDR(n);
22217 
22218 				if (n != -1)
22219 				{
22220 					center_y = is_sexp_true(n);
22221 					n = CDR(n);
22222 
22223 					eval_array<int>(rgb, n, is_nan, is_nan_forever, [](int num)->int {
22224 						CLAMP(num, 0, 255);
22225 						return num;
22226 					}, 255);
22227 					if (is_nan || is_nan_forever)
22228 						return;
22229 
22230 					if (n != -1)
22231 					{
22232 						post_shaded = is_sexp_true(n);
22233 					}
22234 				}
22235 			}
22236 		}
22237 	}
22238 
22239 	//FINALLY !!
22240 	color new_color;
22241 	gr_init_alphacolor(&new_color, rgb[0], rgb[1], rgb[2], 255);
22242 
22243 	subtitle new_subtitle(x_pos, y_pos, text, imageanim, display_time, fade_time, &new_color, -1, center_x, center_y, width, 0, post_shaded);
22244 	Subtitles.push_back(new_subtitle);
22245 }
22246 
sexp_clear_subtitles()22247 void sexp_clear_subtitles()
22248 {
22249 	Subtitles.clear();
22250 
22251 	Current_sexp_network_packet.do_callback();
22252 }
22253 
multi_sexp_clear_subtitles()22254 void multi_sexp_clear_subtitles()
22255 {
22256 	Subtitles.clear();
22257 }
22258 
sexp_show_subtitle_text(int node)22259 void sexp_show_subtitle_text(int node)
22260 {
22261 	bool is_nan, is_nan_forever;
22262 	int i, n = node, message_index = -1;
22263 	SCP_string text;
22264 
22265 	// we'll suppose it's the string for now
22266 	auto ctext = CTEXT(n);
22267 	n = CDR(n);
22268 
22269 	// but use an actual message if one exists
22270 	for (i=0; i<Num_messages; i++)
22271 	{
22272 		if (!stricmp(Messages[i].name, ctext))
22273 		{
22274 			ctext = Messages[i].message;
22275 			message_index = i;
22276 			break;
22277 		}
22278 	}
22279 
22280 	// translate things like keypresses, e.g. $T$ for targeting key
22281 	// (we don't need to do variable replacements because the subtitle code already does that)
22282 	text = message_translate_tokens(ctext);
22283 
22284 	std::array<int, 2> xy_pct;
22285 	eval_array<int>(xy_pct, n, is_nan, is_nan_forever, [](int num)->int {
22286 		CLAMP(num, -100, 100);
22287 		return num;
22288 	});
22289 	if (is_nan || is_nan_forever)
22290 		return;
22291 
22292 	bool center_x = is_sexp_true(n);
22293 	n = CDR(n);
22294 
22295 	bool center_y = is_sexp_true(n);
22296 	n = CDR(n);
22297 
22298 	float display_time, fade_time;
22299 	int width_pct;
22300 	eval_nums(n, is_nan, is_nan_forever, display_time, fade_time, width_pct);
22301 	if (is_nan || is_nan_forever)
22302 		return;
22303 	display_time /= 1000.0f;
22304 	fade_time /= 1000.0f;
22305 	// note: width_pct is OPF_POSITIVE
22306 	if (width_pct > 100)
22307 		width_pct = 100;
22308 
22309 	std::array<int, 3> rgb;
22310 	eval_array<int>(rgb, n, is_nan, is_nan_forever, [](int num)->int {
22311 		CLAMP(num, 0, 255);
22312 		return num;
22313 	}, 255);
22314 	if (is_nan || is_nan_forever)
22315 		return;
22316 
22317 	int fontnum = -1;
22318 	if (n >= 0)
22319 	{
22320 		auto font_name = CTEXT(n);
22321 		n = CDR(n);
22322 
22323 		fontnum = font::FontManager::getFontIndex(font_name);
22324 	}
22325 
22326 	bool post_shaded = false;
22327 	if (n >= 0)
22328 	{
22329 		post_shaded = is_sexp_true(n);
22330 		n = CDR(n);
22331 	}
22332 
22333 	color new_color;
22334 	gr_init_alphacolor(&new_color, rgb[0], rgb[1], rgb[2], 255);
22335 
22336 	// calculate pixel positions
22337 	int x_pos = fl2i(gr_screen.center_w * (xy_pct[0] / 100.0f));
22338 	int y_pos = fl2i(gr_screen.center_h * (xy_pct[1] / 100.0f));
22339 	int width = fl2i(gr_screen.center_w * (width_pct / 100.0f));
22340 
22341 	// add the subtitle
22342 	subtitle new_subtitle(x_pos, y_pos, text.c_str(), nullptr, display_time, fade_time, &new_color, fontnum, center_x, center_y, width, 0, post_shaded);
22343 	Subtitles.push_back(new_subtitle);
22344 
22345 	Current_sexp_network_packet.start_callback();
22346 	Current_sexp_network_packet.send_int(xy_pct[0]);
22347 	Current_sexp_network_packet.send_int(xy_pct[1]);
22348 	Current_sexp_network_packet.send_int (message_index);
22349 	// only send the text if it is not a message. If it is a message, we've already sent the index anyway.
22350 	if (message_index == -1) {
22351 		Current_sexp_network_packet.send_string(text);
22352 	}
22353 	Current_sexp_network_packet.send_float(display_time);
22354 	Current_sexp_network_packet.send_float(fade_time);
22355 	Current_sexp_network_packet.send_int(rgb[0]);
22356 	Current_sexp_network_packet.send_int(rgb[1]);
22357 	Current_sexp_network_packet.send_int(rgb[2]);
22358 	Current_sexp_network_packet.send_int(fontnum);
22359 	Current_sexp_network_packet.send_bool(center_x);
22360 	Current_sexp_network_packet.send_bool(center_y);
22361 	Current_sexp_network_packet.send_int(width_pct);
22362 	Current_sexp_network_packet.send_bool(post_shaded);
22363 	Current_sexp_network_packet.end_callback();
22364 }
22365 
multi_sexp_show_subtitle_text()22366 void multi_sexp_show_subtitle_text()
22367 {
22368 	int x_pct, y_pct, width_pct, fontnum, message_index = -1;
22369 	SCP_string text;
22370 	float display_time, fade_time=0.0f;
22371 	int red=255, green=255, blue=255;
22372 	bool center_x=false, center_y=false;
22373 	bool post_shaded = false;
22374 	color new_color;
22375 
22376 	 Current_sexp_network_packet.get_int(x_pct);
22377 	 Current_sexp_network_packet.get_int(y_pct);
22378 	 Current_sexp_network_packet.get_int(message_index);
22379 	if (message_index == -1) {
22380 		Current_sexp_network_packet.get_string(text);
22381 	}
22382 	else {
22383 		auto ctext = Messages[message_index].message;
22384 		text = message_translate_tokens(ctext);
22385 	}
22386 	Current_sexp_network_packet.get_float(display_time);
22387 	Current_sexp_network_packet.get_float(fade_time);
22388 	Current_sexp_network_packet.get_int(red);
22389 	Current_sexp_network_packet.get_int(green);
22390 	Current_sexp_network_packet.get_int(blue);
22391 	Current_sexp_network_packet.get_int(fontnum);
22392 	Current_sexp_network_packet.get_bool(center_x);
22393 	Current_sexp_network_packet.get_bool(center_y);
22394 	Current_sexp_network_packet.get_int(width_pct);
22395 	Current_sexp_network_packet.get_bool(post_shaded);
22396 
22397 	gr_init_alphacolor(&new_color, red, green, blue, 255);
22398 
22399 	// calculate pixel positions
22400 	int x_pos = (int)(gr_screen.center_w * (x_pct / 100.0f));
22401 	int y_pos = (int)(gr_screen.center_h * (y_pct / 100.0f));
22402 	int width = (int)(gr_screen.center_w * (width_pct / 100.0f));
22403 
22404 	// add the subtitle
22405 	subtitle new_subtitle(x_pos, y_pos, text.c_str(), nullptr, display_time, fade_time, &new_color, fontnum, center_x, center_y, width, 0, post_shaded);
22406 	Subtitles.push_back(new_subtitle);
22407 
22408 }
22409 
sexp_show_subtitle_image(int node)22410 void sexp_show_subtitle_image(int node)
22411 {
22412 	bool is_nan, is_nan_forever;
22413 	int n = node;
22414 
22415 	auto image = CTEXT(n);
22416 	n = CDR(n);
22417 
22418 	std::array<int, 2> xy_pct;
22419 	eval_array<int>(xy_pct, n, is_nan, is_nan_forever, [](int num)->int {
22420 		CLAMP(num, -100, 100);
22421 		return num;
22422 	});
22423 	if (is_nan || is_nan_forever)
22424 		return;
22425 
22426 	bool center_x = is_sexp_true(n);
22427 	n = CDR(n);
22428 
22429 	bool center_y = is_sexp_true(n);
22430 	n = CDR(n);
22431 
22432 	int width_pct, height_pct;
22433 	float display_time, fade_time;
22434 	eval_nums(n, is_nan, is_nan_forever, width_pct, height_pct, display_time, fade_time);
22435 	if (is_nan || is_nan_forever)
22436 		return;
22437 	// note: width_pct and height_pct are OPF_POSITIVE
22438 	if (width_pct > 100)
22439 		width_pct = 100;
22440 	if (height_pct > 100)
22441 		height_pct = 100;
22442 	display_time /= 1000.0f;
22443 	fade_time /= 1000.0f;
22444 
22445 	bool post_shaded = false;
22446 	if (n >= 0)
22447 	{
22448 		post_shaded = is_sexp_true(n);
22449 		n = CDR(n);
22450 	}
22451 
22452 	// calculate pixel positions
22453 	int x_pos = fl2i(gr_screen.center_w * (xy_pct[0] / 100.0f));
22454 	int y_pos = fl2i(gr_screen.center_h * (xy_pct[1] / 100.0f));
22455 	int width = fl2i(gr_screen.center_w * (width_pct / 100.0f));
22456 	int height = fl2i(gr_screen.center_h * (height_pct / 100.0f));
22457 
22458 	// add the subtitle
22459 	subtitle new_subtitle(x_pos, y_pos, nullptr, image, display_time, fade_time, nullptr, -1, center_x, center_y, width, height, post_shaded);
22460 	Subtitles.push_back(new_subtitle);
22461 
22462 	Current_sexp_network_packet.start_callback();
22463 	Current_sexp_network_packet.send_int(x_pos);
22464 	Current_sexp_network_packet.send_int(y_pos);
22465 	Current_sexp_network_packet.send_string(image);
22466 	Current_sexp_network_packet.send_float(display_time);
22467 	Current_sexp_network_packet.send_float(fade_time);
22468 	Current_sexp_network_packet.send_bool(center_x);
22469 	Current_sexp_network_packet.send_bool(center_y);
22470 	Current_sexp_network_packet.send_int(width);
22471 	Current_sexp_network_packet.send_int(height);
22472 	Current_sexp_network_packet.send_bool(post_shaded);
22473 	Current_sexp_network_packet.end_callback();
22474 }
22475 
multi_sexp_show_subtitle_image()22476 void multi_sexp_show_subtitle_image()
22477 {
22478 	int x_pos, y_pos, width=0, height=0;
22479 	char image[TOKEN_LENGTH];
22480 	float display_time, fade_time=0.0f;
22481 	bool center_x=false, center_y=false;
22482 	bool post_shaded = false;
22483 
22484 	Current_sexp_network_packet.get_int(x_pos);
22485 	Current_sexp_network_packet.get_int(y_pos);
22486 	Current_sexp_network_packet.get_string(image);
22487 	Current_sexp_network_packet.get_float(display_time);
22488 	Current_sexp_network_packet.get_float(fade_time);
22489 	Current_sexp_network_packet.get_bool(center_x);
22490 	Current_sexp_network_packet.get_bool(center_y);
22491 	Current_sexp_network_packet.get_int(width);
22492 	Current_sexp_network_packet.get_int(height);
22493 	Current_sexp_network_packet.get_bool(post_shaded);
22494 
22495 	// add the subtitle
22496 	subtitle new_subtitle(x_pos, y_pos, nullptr, image, display_time, fade_time, nullptr, -1, center_x, center_y, width, height, post_shaded);
22497 	Subtitles.push_back(new_subtitle);
22498 
22499 }
22500 
sexp_set_time_compression(int n)22501 void sexp_set_time_compression(int n)
22502 {
22503 	bool is_nan, is_nan_forever;
22504 	float new_multiplier, new_change_time, starting_multiplier;
22505 
22506 	int count = eval_nums(n, is_nan, is_nan_forever, new_multiplier, new_change_time, starting_multiplier);
22507 	if (is_nan || is_nan_forever)
22508 		return;
22509 
22510 	Current_sexp_network_packet.start_callback();
22511 
22512 	new_multiplier /= 100.0f;			//percent->decimal
22513 	Current_sexp_network_packet.send_float(new_multiplier);
22514 
22515 	//Time to change
22516 	if (count > 1) {
22517 		new_change_time /= 1000.0f;		//ms->seconds
22518 		Current_sexp_network_packet.send_float(new_change_time);
22519 	}
22520 
22521 	//Override current time compression with this value
22522 	if (count > 2) {
22523 		starting_multiplier /= 100.0f;	//percent->decimal
22524 		set_time_compression(starting_multiplier);
22525 		Current_sexp_network_packet.send_float(starting_multiplier);
22526 	}
22527 
22528 	Current_sexp_network_packet.end_callback();
22529 
22530 	set_time_compression(new_multiplier, new_change_time);
22531 	lock_time_compression(true);
22532 }
22533 
multi_sexp_set_time_compression()22534 void multi_sexp_set_time_compression()
22535 {
22536 	float new_change_time = 0.0f;
22537 	float new_multiplier = 0.0f;
22538 	float current_multiplier = 0.0f;
22539 
22540 	Current_sexp_network_packet.get_float(new_multiplier);
22541 	Current_sexp_network_packet.get_float(new_change_time);
22542 	if (Current_sexp_network_packet.get_float(current_multiplier)) {
22543 		set_time_compression(current_multiplier);
22544 	}
22545 
22546 	set_time_compression(new_multiplier, new_change_time);
22547 	lock_time_compression(true);
22548 }
22549 
sexp_reset_time_compression()22550 void sexp_reset_time_compression()
22551 {
22552 	set_time_compression(1);
22553 	lock_time_compression(false);
22554 
22555 	 Current_sexp_network_packet.start_callback();
22556 	 Current_sexp_network_packet.end_callback();
22557 }
22558 
multi_sexp_reset_time_compression()22559 void multi_sexp_reset_time_compression()
22560 {
22561 	set_time_compression(1);
22562 	lock_time_compression(false);
22563 }
22564 
sexp_force_perspective(int n)22565 void sexp_force_perspective(int n)
22566 {
22567 	Perspective_locked = is_sexp_true(n);
22568 	n=CDR(n);
22569 
22570 	if(n != -1)
22571 	{
22572 		bool is_nan, is_nan_forever;
22573 		n = eval_num(n, is_nan, is_nan_forever);
22574 		if (is_nan || is_nan_forever)
22575 			return;
22576 		switch(n)
22577 		{
22578 			case 0:
22579 				Viewer_mode = 0;
22580 				break;
22581 			case 1:
22582 				Viewer_mode = VM_CHASE;
22583 				break;
22584 			case 2:
22585 				Viewer_mode = VM_EXTERNAL;
22586 				break;
22587 			case 3:
22588 				Viewer_mode = VM_TOPDOWN;
22589 				break;
22590 		}
22591 	}
22592 }
22593 
sexp_set_camera_shudder(int n)22594 void sexp_set_camera_shudder(int n)
22595 {
22596 	int time;
22597 	float intensity;
22598 	bool is_nan, is_nan_forever;
22599 
22600 	eval_nums(n, is_nan, is_nan_forever, time, intensity);
22601 	if (is_nan || is_nan_forever)
22602 		return;
22603 	intensity *= 0.01f;
22604 
22605 	game_shudder_apply(time, intensity);
22606 
22607 	Current_sexp_network_packet.start_callback();
22608 	Current_sexp_network_packet.send_int(time);
22609 	Current_sexp_network_packet.send_float(intensity);
22610 	Current_sexp_network_packet.end_callback();
22611 }
22612 
multi_sexp_set_camera_shudder()22613 void multi_sexp_set_camera_shudder()
22614 {
22615 	int time;
22616 	float intensity;
22617 
22618 	Current_sexp_network_packet.get_int(time);
22619 	if (Current_sexp_network_packet.get_float(intensity)) {
22620 		game_shudder_apply(time, intensity);
22621 	}
22622 }
22623 
sexp_set_jumpnode_name(int n)22624 void sexp_set_jumpnode_name(int n) //CommanderDJ
22625 {
22626 	auto old_name = CTEXT(n);
22627 	n = CDR(n);
22628 
22629 	auto jnp = jumpnode_get_by_name(old_name);
22630 	if (!jnp)
22631 		return;
22632 
22633 	auto new_name = CTEXT(n);
22634 	jnp->SetName(new_name);
22635 
22636 	//multiplayer callback
22637 	Current_sexp_network_packet.start_callback();
22638 	Current_sexp_network_packet.send_string(old_name);
22639 	Current_sexp_network_packet.send_string(new_name);
22640 	Current_sexp_network_packet.end_callback();
22641 }
22642 
multi_sexp_set_jumpnode_name()22643 void multi_sexp_set_jumpnode_name() //CommanderDJ
22644 {
22645 	char old_name[TOKEN_LENGTH];
22646 	char new_name[TOKEN_LENGTH];
22647 
22648 	Current_sexp_network_packet.get_string(old_name);
22649 	Current_sexp_network_packet.get_string(new_name);
22650 
22651 	CJumpNode *jnp = jumpnode_get_by_name(old_name);
22652 	if (jnp == nullptr)
22653 		return;
22654 
22655 	jnp->SetName(new_name);
22656 }
22657 
sexp_set_jumpnode_color(int n)22658 void sexp_set_jumpnode_color(int n)
22659 {
22660 	auto jumpnode_name = CTEXT(n);
22661 	n = CDR(n);
22662 
22663 	auto jnp = jumpnode_get_by_name(jumpnode_name);
22664 	if (!jnp)
22665 		return;
22666 
22667 	bool is_nan, is_nan_forever;
22668 	std::array<int, 4> rgba;
22669 	eval_array<int>(rgba, n, is_nan, is_nan_forever, [](int num)->int {
22670 		CLAMP(num, 0, 255);
22671 		return num;
22672 	});
22673 	if (is_nan || is_nan_forever)
22674 		return;
22675 
22676 	jnp->SetAlphaColor(rgba[0], rgba[1], rgba[2], rgba[3]);
22677 
22678 	Current_sexp_network_packet.start_callback();
22679 	Current_sexp_network_packet.send_string(jumpnode_name);
22680 	Current_sexp_network_packet.send_int(rgba[0]);
22681 	Current_sexp_network_packet.send_int(rgba[1]);
22682 	Current_sexp_network_packet.send_int(rgba[2]);
22683 	Current_sexp_network_packet.send_int(rgba[3]);
22684 	Current_sexp_network_packet.end_callback();
22685 }
22686 
22687 //CommanderDJ
multi_sexp_set_jumpnode_color()22688 void multi_sexp_set_jumpnode_color()
22689 {
22690 	char jumpnode_name[TOKEN_LENGTH];
22691 	int red, blue, green, alpha;
22692 
22693 	Current_sexp_network_packet.get_string(jumpnode_name);
22694 
22695 	CJumpNode *jnp = jumpnode_get_by_name(jumpnode_name);
22696 	if (jnp == nullptr) {
22697 		Current_sexp_network_packet.discard_remaining_callback_data();
22698 		return;
22699 	}
22700 
22701 	Current_sexp_network_packet.get_int(red);
22702 	Current_sexp_network_packet.get_int(green);
22703 	Current_sexp_network_packet.get_int(blue);
22704 	Current_sexp_network_packet.get_int(alpha);
22705 
22706 	jnp->SetAlphaColor(red, green, blue, alpha);
22707 }
22708 
sexp_set_jumpnode_model(int n)22709 void sexp_set_jumpnode_model(int n)
22710 {
22711 	auto jumpnode_name = CTEXT(n);
22712 	n = CDR(n);
22713 
22714 	auto jnp = jumpnode_get_by_name(jumpnode_name);
22715 	if (!jnp)
22716 		return;
22717 
22718 	auto model_name = CTEXT(n);
22719 	n = CDR(n);
22720 
22721 	bool show_polys = is_sexp_true(n);
22722 
22723 	jnp->SetModel(model_name, show_polys);
22724 
22725 	Current_sexp_network_packet.start_callback();
22726 	Current_sexp_network_packet.send_string(jumpnode_name);
22727 	Current_sexp_network_packet.send_string(model_name);
22728 	Current_sexp_network_packet.send_bool(show_polys);
22729 	Current_sexp_network_packet.end_callback();
22730 }
22731 
multi_sexp_set_jumpnode_model()22732 void multi_sexp_set_jumpnode_model()
22733 {
22734 	char jumpnode_name[TOKEN_LENGTH];
22735 	char model_name[TOKEN_LENGTH];
22736 	bool show_polys;
22737 
22738 	Current_sexp_network_packet.get_string(jumpnode_name);
22739 	Current_sexp_network_packet.get_string(model_name);
22740 
22741 	CJumpNode *jnp = jumpnode_get_by_name(jumpnode_name);
22742 	if (jnp == nullptr) {
22743 		Current_sexp_network_packet.discard_remaining_callback_data();
22744 		return;
22745 	}
22746 
22747 	show_polys = Current_sexp_network_packet.get_bool(show_polys);
22748 
22749 	jnp->SetModel(model_name, show_polys);
22750 }
22751 
sexp_show_hide_jumpnode(int node,bool show)22752 void sexp_show_hide_jumpnode(int node, bool show)
22753 {
22754 	Current_sexp_network_packet.start_callback();
22755 
22756 	for (int n = node; n >= 0; n = CDR(n))
22757 	{
22758 		auto jnp = jumpnode_get_by_name(CTEXT(n));
22759 		if (jnp)
22760 		{
22761 			jnp->SetVisibility(show);
22762 			Current_sexp_network_packet.send_string(CTEXT(n));
22763 		}
22764 	}
22765 
22766 	Current_sexp_network_packet.end_callback();
22767 }
22768 
multi_sexp_show_hide_jumpnode(bool show)22769 void multi_sexp_show_hide_jumpnode(bool show)
22770 {
22771 	char jumpnode_name[TOKEN_LENGTH];
22772 
22773 	while (Current_sexp_network_packet.get_string(jumpnode_name))
22774 	{
22775 		CJumpNode *jnp = jumpnode_get_by_name(jumpnode_name);
22776 		if (jnp != nullptr)
22777 			jnp->SetVisibility(show);
22778 	}
22779 }
22780 
22781 //WMC - This is a bit of a hack, however, it's easier than
22782 //coding in a whole new Script_system function.
sexp_script_eval(int node,int return_type,bool concat_args=false)22783 int sexp_script_eval(int node, int return_type, bool concat_args = false)
22784 {
22785 	int n = node;
22786 
22787 	switch(return_type)
22788 	{
22789 		case OPR_NUMBER:
22790 			{
22791 				auto s = CTEXT(n);
22792 				int r = -1;
22793 				bool success = Script_system.EvalStringWithReturn(s, "|i", &r);
22794 
22795 				if(!success)
22796 					Warning(LOCATION, "sexp-script-eval failed to evaluate string \"%s\"; check your syntax", s);
22797 
22798 				return r;
22799 			}
22800 		case OPR_STRING:
22801 			{
22802 				const char* ret = nullptr;
22803 				auto s = CTEXT(n);
22804 
22805 				bool success = Script_system.EvalStringWithReturn(s, "|s", &ret);
22806 				n = CDR(n);
22807 
22808 				if(!success)
22809 					Warning(LOCATION, "sexp-script-eval failed to evaluate string \"%s\"; check your syntax", s);
22810 
22811 				if (n != -1 && success)
22812 				{
22813 					int variable_index = sexp_get_variable_index(n);
22814 
22815 					if (!(Sexp_variables[variable_index].type & SEXP_VARIABLE_STRING))
22816 					{
22817 						Warning(LOCATION, "Variable for script-eval has to be a string variable!");
22818 					}
22819 					else if (ret != nullptr)
22820 					{
22821 						// assign to variable
22822 						sexp_modify_variable(ret, variable_index);
22823 					}
22824 
22825 					n = CDR(n);
22826 				}
22827 				break;
22828 			}
22829 		case OPR_NULL:
22830 			{
22831 				SCP_string script_cmd;
22832 				while (n != -1)
22833 				{
22834 					auto s = CTEXT(n);
22835 
22836 					if (concat_args)
22837 					{
22838 						script_cmd.append(CTEXT(n));
22839 					}
22840 					else
22841 					{
22842 						bool success = Script_system.EvalString(s);
22843 
22844 						if (!success)
22845 							Warning(LOCATION, "sexp-script-eval failed to evaluate string \"%s\"; check your syntax", s);
22846 					}
22847 
22848 					n = CDR(n);
22849 				}
22850 
22851 				if (concat_args)
22852 				{
22853 					bool success = Script_system.EvalString(script_cmd.c_str());
22854 
22855 					if (!success)
22856 						Warning(LOCATION, "sexp-script-eval failed to evaluate string \"%s\"; check your syntax", script_cmd.c_str());
22857 				}
22858 				break;
22859 			}
22860 		default:
22861 			Error(LOCATION, "Bad type passed to sexp_script_eval - get a coder");
22862 			break;
22863 	}
22864 
22865 	return SEXP_TRUE;
22866 }
22867 
sexp_script_eval_multi(int node)22868 void sexp_script_eval_multi(int node)
22869 {
22870 	char script[TOKEN_LENGTH];
22871 	bool success = true;
22872 	bool execute_on_server;
22873 	player *p;
22874 
22875 	strcpy_s(script, CTEXT(node));
22876 	node = CDR(node);
22877 
22878 	execute_on_server = is_sexp_true(node);
22879 	node = CDR(node);
22880 
22881 	Current_sexp_network_packet.start_callback();
22882 	Current_sexp_network_packet.send_string(script);
22883 	// evalutate on all clients
22884 	if (node == -1) {
22885 		Current_sexp_network_packet.send_bool(true);
22886 		execute_on_server = true;
22887 	}
22888 	// we have to send to all clients but we need to send a list of ships so that they know if they evaluate or not
22889 	else {
22890 		Current_sexp_network_packet.send_bool(false);
22891 
22892 		for (; node != -1; node = CDR(node)) {
22893 			p = get_player_from_ship_node(node, true);
22894 
22895 			// not a player ship so skip it
22896 			if (p == nullptr ){
22897 				continue;
22898 			}
22899 			else {
22900 				// if this is me, flag that we should execute the script
22901 				if (p == Player) {
22902 					execute_on_server = true;
22903 				}
22904 				// otherwise notify the clients
22905 				else {
22906 					Current_sexp_network_packet.send_string(CTEXT(node));
22907 				}
22908 			}
22909 		}
22910 	}
22911 
22912 	Current_sexp_network_packet.end_callback();
22913 
22914 	if (execute_on_server) {
22915 		success = Script_system.EvalString(script, script);
22916 	}
22917 
22918 	if(!success) {
22919 		Warning(LOCATION, "sexp-script-eval failed to evaluate string \"%s\"; check your syntax", script);
22920 	}
22921 }
22922 
multi_sexp_script_eval_multi()22923 void multi_sexp_script_eval_multi()
22924 {
22925 	char script[TOKEN_LENGTH];
22926 	char ship_name[TOKEN_LENGTH];
22927 	bool sent_to_all = false;
22928 	bool success = true;
22929 
22930 	Current_sexp_network_packet.get_string(script);
22931 	Current_sexp_network_packet.get_bool(sent_to_all);
22932 
22933 	if (sent_to_all) {
22934 		success = Script_system.EvalString(script, script);
22935 	}
22936 	// go through all the ships that were sent and see if any of them match this client.
22937 	else {
22938 		while (Current_sexp_network_packet.get_string(ship_name)) {
22939 			auto ship_entry = ship_registry_get(ship_name);
22940 			Assertion(ship_entry, "Illegal value for the ship name sent in multi_sexp_script_eval_multi()! Ship %s does not exist!", ship_name);
22941 			if (Player == get_player_from_ship_entry(ship_entry)) {
22942 				success = Script_system.EvalString(script, script);
22943 			}
22944 		}
22945 	}
22946 
22947 	if(!success) {
22948 		Warning(LOCATION, "sexp-script-eval failed to evaluate string \"%s\"; check your syntax", script);
22949 	}
22950 }
22951 
sexp_force_glide(int node)22952 void sexp_force_glide(int node)
22953 {
22954 	// get ship
22955 	auto ship_entry = eval_ship(node);
22956 	if (!ship_entry || !ship_entry->shipp)
22957 		return;
22958 
22959 	//Can this ship glide?
22960 	if (!Ship_info[ship_entry->shipp->ship_info_index].can_glide)
22961 		return;
22962 
22963 	bool glide = is_sexp_true(CDR(node));
22964 
22965 	object_set_gliding(ship_entry->objp, glide, true);
22966 }
22967 
test_point_within_box(vec3d * test_point,vec3d * box_corner_1,vec3d * box_corner_2,object * reference_ship_obj)22968 bool test_point_within_box(vec3d *test_point, vec3d *box_corner_1, vec3d *box_corner_2, object *reference_ship_obj)
22969 {
22970 	vec3d tempv, test_point_buf;
22971 
22972 	// If reference_ship is specified, rotate test_point into its reference frame
22973 	if (reference_ship_obj != nullptr)
22974 	{
22975 		vm_vec_sub(&tempv, test_point, &reference_ship_obj->pos);
22976 		vm_vec_rotate(&test_point_buf, &tempv, &reference_ship_obj->orient);
22977 
22978 		test_point = &test_point_buf;
22979 	}
22980 
22981 	// Check to see if the test point is within the specified box as defined by two extreme corners
22982 	return ((test_point->xyz.x >= box_corner_1->xyz.x && test_point->xyz.x <= box_corner_2->xyz.x) &&
22983 			(test_point->xyz.y >= box_corner_1->xyz.y && test_point->xyz.y <= box_corner_2->xyz.y) &&
22984 			(test_point->xyz.z >= box_corner_1->xyz.z && test_point->xyz.z <= box_corner_2->xyz.z));
22985 }
22986 
sexp_is_in_box(int n)22987 int sexp_is_in_box(int n)
22988 {
22989 	int i;
22990 	float x1, x2, y1, y2, z1, z2;
22991 	bool is_nan, is_nan_forever;
22992 	Assert(n >= 0);
22993 
22994 	object_ship_wing_point_team oswpt;
22995 	eval_object_ship_wing_point_team(&oswpt, n);
22996 	n = CDR(n);
22997 
22998 	// Get box corners
22999 	eval_nums(n, is_nan, is_nan_forever, x1, x2, y1, y2, z1, z2);
23000 	if (is_nan)
23001 		return SEXP_FALSE;
23002 	if (is_nan_forever)
23003 		return SEXP_KNOWN_FALSE;
23004 	vec3d box_corner_1;
23005 	box_corner_1.xyz.x = MIN(x1, x2);
23006 	box_corner_1.xyz.y = MIN(y1, y2);
23007 	box_corner_1.xyz.z = MIN(z1, z2);
23008 	vec3d box_corner_2;
23009 	box_corner_2.xyz.x = MAX(x1, x2);
23010 	box_corner_2.xyz.y = MAX(y1, y2);
23011 	box_corner_2.xyz.z = MAX(z1, z2);
23012 
23013 	// Ship to define reference frame is optional
23014 	object* reference_ship_obj = nullptr;
23015 	if (n != -1)
23016 	{
23017 		auto reference = eval_ship(n);
23018 		if (!reference || reference->status == ShipStatus::NOT_YET_PRESENT)
23019 			return SEXP_FALSE;
23020 		if (reference->status == ShipStatus::EXITED)
23021 			return SEXP_KNOWN_FALSE;
23022 
23023 		reference_ship_obj = reference->objp;
23024 	}
23025 
23026 	// Check position of object
23027 	switch (oswpt.type)
23028 	{
23029 		case OSWPT_TYPE_EXITED:
23030 			return SEXP_KNOWN_FALSE;
23031 
23032 		case OSWPT_TYPE_WING:
23033 			for (i = 0; i < oswpt.wingp->current_count; i++)
23034 				if (!test_point_within_box(&Objects[Ships[oswpt.wingp->ship_index[i]].objnum].pos, &box_corner_1, &box_corner_2, reference_ship_obj))
23035 					return SEXP_FALSE;
23036 			return SEXP_TRUE;
23037 
23038 		case OSWPT_TYPE_SHIP:
23039 		case OSWPT_TYPE_WAYPOINT:
23040 			return test_point_within_box(&oswpt.objp->pos, &box_corner_1, &box_corner_2, reference_ship_obj);
23041 
23042 		default:
23043 			return SEXP_FALSE;
23044 	}
23045 }
23046 
sexp_is_in_mission(int node)23047 int sexp_is_in_mission(int node)
23048 {
23049 	for (int n = node; n != -1; n = CDR(n))
23050 	{
23051 		// For this sexp, we do not short-circuit known-true or known-false.
23052 		auto ship_entry = eval_ship(n);
23053 		if (!ship_entry || ship_entry->status != ShipStatus::PRESENT)
23054 			return SEXP_FALSE;
23055 	}
23056 
23057 	return SEXP_TRUE;
23058 }
23059 
sexp_is_docked(int node)23060 int sexp_is_docked(int node)
23061 {
23062 	object *host_objp = nullptr;
23063 
23064 	for (int n = node; n != -1; n = CDR(n))
23065 	{
23066 		auto current_entry = eval_ship(n);
23067 		if (!current_entry || current_entry->status == ShipStatus::NOT_YET_PRESENT)
23068 			return SEXP_NAN;
23069 		if (current_entry->status == ShipStatus::EXITED)
23070 			return SEXP_NAN_FOREVER;
23071 
23072 		// if host_objp is a nullptr then we're on the 1st loop iteration
23073 		if (host_objp == nullptr)
23074 		{
23075 			// if the host isn't docked to anything, no need to check each ship individually
23076 			if (!object_is_docked(current_entry->objp))
23077 				return SEXP_FALSE;
23078 
23079 			host_objp = current_entry->objp;
23080 			continue;
23081 		}
23082 
23083 		// if we are not docked, do a quick out
23084 		if (!dock_check_find_direct_docked_object(host_objp, current_entry->objp))
23085 			return SEXP_FALSE;
23086 	}
23087 
23088 	// all ships are docked
23089 	return SEXP_TRUE;
23090 }
23091 
sexp_manipulate_colgroup(int node,bool add_to_group)23092 void sexp_manipulate_colgroup(int node, bool add_to_group)
23093 {
23094 	auto ship_entry = eval_ship(node);
23095 	if (!ship_entry || !ship_entry->shipp)
23096 		return;
23097 	node = CDR(node);
23098 
23099 	int colgroup_id = ship_entry->objp->collision_group_id;
23100 
23101 	for (; node != -1; node = CDR(node)) {
23102 		bool is_nan, is_nan_forever;
23103 		int group = eval_num(node, is_nan, is_nan_forever);
23104 		if (is_nan || is_nan_forever) {
23105 			continue;
23106 		}
23107 
23108 		if (group < 0 || group > 31) {
23109 			WarningEx(LOCATION, "Invalid collision group id %d specified for object %s. Valid IDs range from 0 to 31.\n", group, ship_entry->name);
23110 		} else {
23111 			if (add_to_group) {
23112 				colgroup_id |= (1<<group);
23113 			} else {
23114 				colgroup_id &= ~(1<<group);
23115 			}
23116 		}
23117 	}
23118 
23119 	ship_entry->objp->collision_group_id = colgroup_id;
23120 }
23121 
sexp_manipulate_colgroup2(int node,bool add_to_group)23122 void sexp_manipulate_colgroup2(int node, bool add_to_group)
23123 {
23124 	bool is_nan, is_nan_forever;
23125 	int group = eval_num(node, is_nan, is_nan_forever);
23126 	if (is_nan || is_nan_forever)
23127 		return;
23128 	node = CDR(node);
23129 
23130 	if (group < 0 || group > 31)
23131 	{
23132 		WarningEx(LOCATION, "Invalid collision group id %d specified. Valid IDs range from 0 to 31.\n", group);
23133 		return;
23134 	}
23135 
23136 	for (; node != -1; node = CDR(node))
23137 	{
23138 		auto ship_entry = eval_ship(node);
23139 		if (!ship_entry || !ship_entry->shipp)
23140 			continue;
23141 
23142 		if (add_to_group)
23143 			ship_entry->objp->collision_group_id |= (1 << group);
23144 		else
23145 			ship_entry->objp->collision_group_id &= ~(1 << group);
23146 	}
23147 }
23148 
sexp_get_colgroup(int node)23149 int sexp_get_colgroup(int node)
23150 {
23151 	auto ship_entry = eval_ship(node);
23152 	if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT)
23153 		return SEXP_NAN;
23154 	if (ship_entry->status == ShipStatus::EXITED)
23155 		return SEXP_NAN_FOREVER;
23156 	return ship_entry->objp->collision_group_id;
23157 }
23158 
get_effect_from_name(const char * name)23159 int get_effect_from_name(const char* name)
23160 {
23161 	int i = 0;
23162 	for (SCP_vector<ship_effect>::iterator sei = Ship_effects.begin(); sei != Ship_effects.end(); ++sei) {
23163 		if (!stricmp(name, sei->name))
23164 			return i;
23165 		i++;
23166 	}
23167 	return -1;
23168 }
23169 
sexp_ship_effect(int n)23170 void sexp_ship_effect(int n)
23171 {
23172 	bool is_nan, is_nan_forever;
23173 
23174 	int effect_num = get_effect_from_name(CTEXT(n));
23175 	if (effect_num == -1) {
23176 		WarningEx(LOCATION, "Invalid effect name passed to ship-effect\n");
23177 		return;
23178 	}
23179 	n = CDR(n);
23180 
23181 	int effect_duration = eval_num(n, is_nan, is_nan_forever);
23182 	if (is_nan || is_nan_forever)
23183 		return;
23184 	n = CDR(n);
23185 
23186 	for (; n != -1; n = CDR(n))
23187 	{
23188 		object_ship_wing_point_team oswpt;
23189 		eval_object_ship_wing_point_team(&oswpt, n);
23190 
23191 		// we only handle ships and wings that are present
23192 		switch (oswpt.type)
23193 		{
23194 			case OSWPT_TYPE_SHIP:
23195 			{
23196 				auto sp = oswpt.ship_entry->shipp;
23197 				sp->shader_effect_active = true;
23198 				sp->shader_effect_num = effect_num;
23199 				sp->shader_effect_duration = effect_duration;
23200 				sp->shader_effect_start_time = timer_get_milliseconds();
23201 				break;
23202 			}
23203 
23204 			case OSWPT_TYPE_WING:
23205 			{
23206 				auto wp = oswpt.wingp;
23207 				for (int i = 0; i < wp->current_count; ++i)
23208 				{
23209 					if (wp->ship_index[i] >= 0)
23210 					{
23211 						auto sp = &Ships[wp->ship_index[i]];
23212 						sp->shader_effect_active = true;
23213 						sp->shader_effect_num = effect_num;
23214 						sp->shader_effect_duration = effect_duration;
23215 						sp->shader_effect_start_time = timer_get_milliseconds();
23216 					}
23217 				}
23218 				break;
23219 			}
23220 
23221 			default:
23222 				mprintf(("Invalid Shipname in SEXP ship-effect\n"));
23223 		}
23224 	}
23225 }
23226 
sexp_change_team_color(int n)23227 void sexp_change_team_color(int n)
23228 {
23229 	auto new_color = CTEXT(n);
23230 	n = CDR(n);
23231 
23232 	bool is_nan, is_nan_forever;
23233 	int fade_time = eval_num(n, is_nan, is_nan_forever);
23234 	if (is_nan || is_nan_forever)
23235 		return;
23236 	n = CDR(n);
23237 
23238 	Current_sexp_network_packet.start_callback();
23239 	Current_sexp_network_packet.send_string(new_color);
23240 	Current_sexp_network_packet.send_int(fade_time);
23241 
23242 	for ( ; n >= 0; n = CDR(n))
23243 	{
23244 		auto ship_entry = eval_ship(n);
23245 		if (!ship_entry || !ship_entry->shipp)
23246 			continue;
23247 		auto shp = ship_entry->shipp;
23248 
23249 		Current_sexp_network_packet.send_ship(shp);
23250 
23251 		if (fade_time == 0) {
23252 			shp->team_name = new_color;
23253 		} else {
23254 			shp->secondary_team_name = new_color;
23255 			shp->team_change_time = fade_time;
23256 			shp->team_change_timestamp = Missiontime;
23257 		}
23258 	}
23259 
23260 	Current_sexp_network_packet.end_callback();
23261 }
23262 
multi_sexp_change_team_color()23263 void multi_sexp_change_team_color()
23264 {
23265 	SCP_string new_color = "<none>";
23266 	int fade_time = 0;
23267 	ship* shipp;
23268 
23269 	Current_sexp_network_packet.get_string(new_color);
23270 	Current_sexp_network_packet.get_int(fade_time);
23271 
23272 	while (Current_sexp_network_packet.get_ship(shipp))
23273 	{
23274 		if (fade_time == 0) {
23275 			shipp->team_name = new_color;
23276 		} else {
23277 			shipp->secondary_team_name = new_color;
23278 			shipp->team_change_time = fade_time;
23279 			shipp->team_change_timestamp = Missiontime;
23280 		}
23281 	}
23282 }
23283 
sexp_call_ssm_strike(int node)23284 void sexp_call_ssm_strike(int node)
23285 {
23286 	int ssm_index = ssm_info_lookup(CTEXT(node));
23287 	node = CDR(node);
23288 	int calling_team = iff_lookup(CTEXT(node));
23289 	if (ssm_index < 0 || calling_team < 0)
23290 		return;
23291 
23292 	for (int n = node; n != -1; n = CDR(n))
23293 	{
23294 		// don't do anything if the ship isn't there
23295 		auto ship_entry = eval_ship(n);
23296 		if (!ship_entry || !ship_entry->shipp)
23297 			continue;
23298 
23299 		auto target_objp = ship_entry->objp;
23300 		auto start = &target_objp->pos;
23301 
23302 		vm_vec_scale_add(start, start, &target_objp->orient.vec.fvec, -1);
23303 
23304 		ssm_create(target_objp, start, ssm_index, nullptr, calling_team);
23305 	}
23306 }
23307 
23308 extern int Cheats_enabled;
sexp_player_is_cheating_bastard()23309 int sexp_player_is_cheating_bastard()
23310 {
23311 	if (Cheats_enabled) {
23312 		return SEXP_KNOWN_TRUE;
23313 	}
23314 
23315 	return SEXP_FALSE;
23316 }
23317 
sexp_is_language(int node)23318 int sexp_is_language(int node)
23319 {
23320 	// we don't check the language for validity because it could be a language the engine doesn't know about
23321 	auto lang = CTEXT(node);
23322 
23323 	// easy lookup
23324 	auto li = &Lcl_languages[lcl_get_current_lang_index()];
23325 
23326 	// easy comparison
23327 	if (stricmp(li->lang_name, lang) == 0)
23328 		return SEXP_KNOWN_TRUE;
23329 	else
23330 		return SEXP_KNOWN_FALSE;
23331 }
23332 
sexp_set_motion_debris(int node)23333 void sexp_set_motion_debris(int node)
23334 {
23335 	Motion_debris_override = is_sexp_true(node);
23336 }
23337 
23338 /**
23339  * Returns the subsystem type if the name of a subsystem is actually a generic type (e.g \<all engines\> or \<all turrets\>
23340  */
get_generic_subsys(const char * subsys_name)23341 int get_generic_subsys(const char *subsys_name)
23342 {
23343 	if (!strcmp(subsys_name, SEXP_ALL_ENGINES_STRING)) {
23344 		return SUBSYSTEM_ENGINE;
23345 	} else if (!strcmp(subsys_name, SEXP_ALL_TURRETS_STRING)) {
23346 		return SUBSYSTEM_TURRET;
23347 	}
23348 
23349 	Assert(SUBSYSTEM_NONE == 0);
23350 	return SUBSYSTEM_NONE;
23351 }
23352 
23353 // Karajorma - returns false if the ship class has changed since the mission was parsed in
23354 // Changes can be from use of the change-ship-class SEXP, loadout or any future method
ship_class_unchanged(const ship_registry_entry * ship_entry)23355 bool ship_class_unchanged(const ship_registry_entry *ship_entry)
23356 {
23357 	return ship_entry->p_objp && ship_entry->p_objp->ship_class == ship_entry->shipp->ship_info_index;
23358 }
23359 
23360 // Goober5000 - needed because any nonzero integer value is "true"
is_sexp_true(int cur_node,int referenced_node)23361 bool is_sexp_true(int cur_node, int referenced_node)
23362 {
23363 	int result = eval_sexp(cur_node, referenced_node);
23364 
23365 	// any SEXP_KNOWN_TRUE result will return SEXP_TRUE from eval_sexp, but let's be defensive
23366 	return (result == SEXP_TRUE) || (result == SEXP_KNOWN_TRUE);
23367 }
23368 
23369 /**
23370 *
23371 */
generate_event_log_flags_mask(int result)23372 int generate_event_log_flags_mask(int result)
23373 {
23374 	int matches = 0;
23375 	mission_event *current_event = &Mission_events[Event_index];
23376 
23377 	switch (result) {
23378 		case SEXP_TRUE:
23379 			matches |= MLF_SEXP_TRUE;
23380 			break;
23381 
23382 		case SEXP_FALSE:
23383 			matches |= MLF_SEXP_FALSE;
23384 			break;
23385 
23386 		default:
23387 			Error(LOCATION, "SEXP has a value which isn't true or false.");
23388 	}
23389 
23390 	if (( result == SEXP_TRUE ) || (result == SEXP_KNOWN_TRUE)) {
23391 		// now deal with the flags depending on repeat and trigger counts
23392 		switch (current_event->mission_log_flags) {
23393 			case MLF_FIRST_REPEAT_ONLY:
23394 				if (current_event->repeat_count > 1) {
23395 					matches |= MLF_FIRST_REPEAT_ONLY;
23396 				}
23397 				break;
23398 
23399 			case MLF_LAST_REPEAT_ONLY:
23400 				if (current_event->repeat_count == 1) {
23401 					matches |= MLF_LAST_REPEAT_ONLY;
23402 				}
23403 				break;
23404 
23405 			case MLF_FIRST_TRIGGER_ONLY:
23406 				if (current_event->trigger_count > 1) {
23407 					matches |= MLF_FIRST_TRIGGER_ONLY;
23408 				}
23409 				break;
23410 
23411 			case MLF_LAST_TRIGGER_ONLY:
23412 				if ((current_event->trigger_count == 1) && (current_event->flags & MEF_USING_TRIGGER_COUNT)) {
23413 					matches |= MLF_LAST_TRIGGER_ONLY;
23414 				}
23415 				break;
23416 		}
23417 	}
23418 
23419 	return matches;
23420 }
23421 
current_log_to_backup_log_buffer()23422 void current_log_to_backup_log_buffer()
23423 {
23424 	Mission_events[Event_index].backup_log_buffer.clear();
23425 	if (!(Mission_events[Event_index].mission_log_flags & MLF_STATE_CHANGE)){
23426 		return;
23427 	}
23428 
23429 	for (int i = 0; i < (int)Current_event_log_buffer->size(); i++) {
23430 		Mission_events[Event_index].backup_log_buffer.push_back(Current_event_log_buffer->at(i));
23431 	}
23432 }
23433 
maybe_write_previous_event_to_log(int result)23434 void maybe_write_previous_event_to_log(int result)
23435 {
23436 	mission_event *this_event = &Mission_events[Event_index];
23437 
23438 	// if the old log is empty, all we do is record the result for the next evaluation
23439 	// the old log should only be empty at mission start
23440 	if (this_event->backup_log_buffer.empty()) {
23441 		this_event->previous_result = result;
23442 		return;
23443 	}
23444 
23445 	// if there's no change in state, we don't write the previous state to the log
23446 	if ((this_event->mission_log_flags & MLF_STATE_CHANGE) && (result == this_event->previous_result)) {
23447 		current_log_to_backup_log_buffer();
23448 		return;
23449 	}
23450 
23451 	log_string(LOGFILE_EVENT_LOG, "Event has changed state. Old state");
23452 	while (!this_event->backup_log_buffer.empty()) {
23453 		log_string(LOGFILE_EVENT_LOG, this_event->backup_log_buffer.back().c_str());
23454 		this_event->backup_log_buffer.pop_back();
23455 	}
23456 	log_string(LOGFILE_EVENT_LOG, "New state");
23457 
23458 	// backup the current buffer as this may be a repeating event
23459 	current_log_to_backup_log_buffer();
23460 }
23461 
23462 /**
23463 * Checks the mission logs flags for this event and writes to the log if this has been asked for
23464 */
maybe_write_to_event_log(int result)23465 void maybe_write_to_event_log(int result)
23466 {
23467 	char buffer [256];
23468 
23469 	int mask = generate_event_log_flags_mask(result);
23470 	sprintf(buffer, "Event: %s at mission time %d seconds (%d milliseconds)", Mission_events[Event_index].name, f2i(Missiontime), f2i((longlong)Missiontime * 1000));
23471 	Current_event_log_buffer->push_back(buffer);
23472 
23473 	if (!Snapshot_all_events && (!(mask &=  Mission_events[Event_index].mission_log_flags))) {
23474 		current_log_to_backup_log_buffer();
23475 		Current_event_log_buffer->clear();
23476 		return;
23477 	}
23478 
23479 	// remove some of the flags
23480 	if (mask & (MLF_FIRST_REPEAT_ONLY | MLF_FIRST_TRIGGER_ONLY)) {
23481 		Mission_events[Event_index].mission_log_flags &= ~(MLF_FIRST_REPEAT_ONLY | MLF_FIRST_TRIGGER_ONLY) ;
23482 	}
23483 
23484 	if (Mission_events[Event_index].mission_log_flags & MLF_STATE_CHANGE) {
23485 		maybe_write_previous_event_to_log(result);
23486 	}
23487 
23488 	while (!Current_event_log_buffer->empty()) {
23489 		log_string(LOGFILE_EVENT_LOG, Current_event_log_buffer->back().c_str());
23490 		Current_event_log_buffer->pop_back();
23491 	}
23492 	log_string(LOGFILE_EVENT_LOG, "");
23493 
23494 }
23495 
23496 /**
23497 * Returns the constant used as a SEXP's result as text for printing to the event log
23498 */
sexp_get_result_as_text(int result)23499 const char *sexp_get_result_as_text(int result)
23500 {
23501 	switch (result) {
23502 		case SEXP_TRUE:
23503 			return "TRUE";
23504 
23505 		case SEXP_FALSE:
23506 			return "FALSE";
23507 
23508 		case SEXP_KNOWN_FALSE:
23509 			return "ALWAYS FALSE";
23510 
23511 		case SEXP_KNOWN_TRUE:
23512 			return "ALWAYS TRUE";
23513 
23514 		case SEXP_UNKNOWN:
23515 			return "UNKNOWN";
23516 
23517 		case SEXP_NAN:
23518 			return "NOT A NUMBER";
23519 
23520 		case SEXP_NAN_FOREVER:
23521 			return "CAN NEVER BE A NUMBER";
23522 
23523 		case SEXP_CANT_EVAL:
23524 			return "CAN'T EVALUATE";
23525 
23526 		default:
23527 			return nullptr;
23528 	}
23529 }
23530 
23531 /**
23532 * Checks the mission logs flags for this event and writes to the log if this has been asked for
23533 */
add_to_event_log_buffer(int op_num,int result)23534 void add_to_event_log_buffer(int op_num, int result)
23535 {
23536 	Assertion ((Current_event_log_buffer != nullptr) &&
23537 				(Current_event_log_variable_buffer != nullptr)&&
23538 				(Current_event_log_argument_buffer != nullptr), "Attempting to write to a non-existent log buffer");
23539 
23540 	if (op_num == -1) {
23541 		nprintf(("SEXP", "ERROR: add_to_event_log_buffer() function called with op_num of %i; this should not happen. Contact a coder.\n", op_num));
23542 		return; //How does this happen?
23543 	}
23544 
23545 	char buffer[TOKEN_LENGTH];
23546 	SCP_string tmp;
23547 	tmp.append(Operators[op_num].text);
23548 	tmp.append(" returned ");
23549 
23550 	if ((Operators[op_num].type & (SEXP_INTEGER_OPERATOR|SEXP_ARITHMETIC_OPERATOR)) || (sexp_get_result_as_text(result) == nullptr)) {
23551 		sprintf(buffer, "%d", result);
23552 		tmp.append(buffer);
23553 	}
23554 	else {
23555 		tmp.append(sexp_get_result_as_text(result));
23556 	}
23557 
23558 	if (True_loop_argument_sexps && !Sexp_replacement_arguments.empty()) {
23559 		tmp.append(" for argument ");
23560 		tmp.append(Sexp_replacement_arguments.back().first);
23561 	}
23562 
23563 	if (!Current_event_log_argument_buffer->empty()) {
23564 		tmp.append(" for the following arguments");
23565 		while (!Current_event_log_argument_buffer->empty()) {
23566 			tmp.append("\n");
23567 			tmp.append(Current_event_log_argument_buffer->back());
23568 			Current_event_log_argument_buffer->pop_back();
23569 		}
23570 	}
23571 
23572 	if (!Current_event_log_variable_buffer->empty()) {
23573 		tmp.append("\nVariables:\n");
23574 		while (!Current_event_log_variable_buffer->empty()) {
23575 			tmp.append(Current_event_log_variable_buffer->back());
23576 			Current_event_log_variable_buffer->pop_back();
23577 			tmp.append("[");
23578 			tmp.append(Current_event_log_variable_buffer->back());
23579 			Current_event_log_variable_buffer->pop_back();
23580 			tmp.append("]");
23581 		}
23582 	}
23583 
23584 	Current_event_log_buffer->push_back(tmp);
23585 }
23586 
23587 /**
23588  * High-level sexpression evaluator
23589  */
eval_sexp(int cur_node,int referenced_node)23590 int eval_sexp(int cur_node, int referenced_node)
23591 {
23592 	int node, type, sexp_val = UNINITIALIZED;
23593 	if (cur_node == -1)  // empty list, i.e. sexp: ( )
23594 		return SEXP_FALSE;
23595 
23596 	Assert(cur_node >= 0);			// we have special sexp nodes <= -1!!!  MWA
23597 									// which should be intercepted before we get here.  HOFFOSS
23598 	type = SEXP_NODE_TYPE(cur_node);
23599 	Assert( (type == SEXP_LIST) || (type == SEXP_ATOM) );
23600 
23601 	// trap known true and known false sexpressions.  We don't trap on SEXP_NAN sexpressions since
23602 	// they may yet evaluate to true or false. If the sexp depends on the special argument,
23603 	// we can't 'know' its value so we skip this behaviour.
23604 
23605 	// we want to log event values for KNOWN_X or FOREVER_X before returning
23606 	if (!special_argument_appears_in_sexp_tree(cur_node)) {
23607 		if (Log_event && ((Sexp_nodes[cur_node].value == SEXP_KNOWN_TRUE) || (Sexp_nodes[cur_node].value == SEXP_KNOWN_FALSE) || (Sexp_nodes[cur_node].value == SEXP_NAN_FOREVER))) {
23608 			// if this is a node that has been assigned the value by short-circuiting,
23609 			// it might not be the operator that returned the value
23610 			int op_index = get_operator_index(cur_node);
23611 			if (op_index < 0)
23612 				op_index = get_operator_index(CAR(cur_node));
23613 
23614 			// log the known value
23615 			add_to_event_log_buffer(op_index, Sexp_nodes[cur_node].value);
23616 		}
23617 
23618 		// now do a quick return whether or not we log, per the comment above about trapping known sexpressions
23619 		if (Sexp_nodes[cur_node].value == SEXP_KNOWN_TRUE) {
23620 			return SEXP_TRUE;
23621 		}
23622 		else if (Sexp_nodes[cur_node].value == SEXP_KNOWN_FALSE) {
23623 			return SEXP_FALSE;
23624 		}
23625 		else if (Sexp_nodes[cur_node].value == SEXP_NAN_FOREVER) {
23626 			return SEXP_FALSE;
23627 		}
23628 	}
23629 
23630 	if (Sexp_nodes[cur_node].first != -1) {
23631 		node = CAR(cur_node);
23632 		sexp_val = eval_sexp(node);
23633 		Sexp_nodes[cur_node].value = Sexp_nodes[node].value;	// higher level node gets node value
23634 		return sexp_val;
23635 
23636 	} else {
23637 		int op_num;
23638 
23639 		node = CDR(cur_node);		// makes reading the next bit of code a little easier.
23640 
23641 		op_num = get_operator_const(cur_node);
23642 		// add the op_num to the stack if it is an actual operator rather than a number
23643 		if (op_num) {
23644 			Current_sexp_operator.push_back(op_num);
23645 		}
23646 		switch ( op_num ) {
23647 		// arithmetic operators will always return just their value
23648 			case OP_PLUS:
23649 				sexp_val = add_sexps(node);
23650 				break;
23651 
23652 			case OP_MINUS:
23653 				sexp_val = sub_sexps(node);
23654 				break;
23655 
23656 			case OP_MUL:
23657 				sexp_val = mul_sexps(node);
23658 				break;
23659 
23660 			case OP_MOD:
23661 				sexp_val = mod_sexps(node);
23662 				break;
23663 
23664 			case OP_DIV:
23665 				sexp_val = div_sexps(node);
23666 				break;
23667 
23668 			case OP_RAND:
23669 			case OP_RAND_MULTIPLE:
23670 				sexp_val = rand_sexp( node, (op_num == OP_RAND_MULTIPLE) );
23671 				break;
23672 
23673 			case OP_ABS:
23674 				sexp_val = abs_sexp(node);
23675 				break;
23676 
23677 			case OP_MIN:
23678 				sexp_val = min_sexp(node);
23679 				break;
23680 
23681 			case OP_MAX:
23682 				sexp_val = max_sexp(node);
23683 				break;
23684 
23685 			case OP_AVG:
23686 				sexp_val = avg_sexp(node);
23687 				break;
23688 
23689 			case OP_POW:
23690 				sexp_val = pow_sexp(node);
23691 				break;
23692 
23693 			case OP_SIGNUM:
23694 				sexp_val = signum_sexp(node);
23695 				break;
23696 
23697 			case OP_IS_NAN:
23698 				sexp_val = sexp_is_nan(node);
23699 				break;
23700 
23701 			case OP_NAN_TO_NUMBER:
23702 				sexp_val = sexp_nan_to_number(node);
23703 				break;
23704 
23705 			case OP_SET_BIT:
23706 			case OP_UNSET_BIT:
23707 				sexp_val = sexp_set_bit(node, op_num == OP_SET_BIT);
23708 				break;
23709 
23710 			case OP_IS_BIT_SET:
23711 				sexp_val = sexp_is_bit_set(node);
23712 				break;
23713 
23714 			case OP_BITWISE_AND:
23715 				sexp_val = sexp_bitwise_and(node);
23716 				break;
23717 
23718 			case OP_BITWISE_OR:
23719 				sexp_val = sexp_bitwise_or(node);
23720 				break;
23721 
23722 			case OP_BITWISE_NOT:
23723 				sexp_val = sexp_bitwise_not(node);
23724 				break;
23725 
23726 			case OP_BITWISE_XOR:
23727 				sexp_val = sexp_bitwise_xor(node);
23728 				break;
23729 
23730 			// boolean operators can have one of the special sexp values (known true, known false, unknown)
23731 			case OP_TRUE:
23732 				sexp_val = SEXP_KNOWN_TRUE;
23733 				break;
23734 
23735 			case OP_FALSE:
23736 				sexp_val = SEXP_KNOWN_FALSE;
23737 				break;
23738 
23739 			case OP_OR:
23740 				sexp_val = sexp_or(node);
23741 				break;
23742 
23743 			case OP_AND:
23744 				sexp_val = sexp_and(node);
23745 				break;
23746 
23747 			case OP_AND_IN_SEQUENCE:
23748 				sexp_val = sexp_and_in_sequence(node);
23749 				break;
23750 
23751 			case OP_XOR:
23752 				sexp_val = sexp_xor(node);
23753 				break;
23754 
23755 			case OP_EQUALS:
23756 			case OP_GREATER_THAN:
23757 			case OP_LESS_THAN:
23758 			case OP_NOT_EQUAL:
23759 			case OP_GREATER_OR_EQUAL:
23760 			case OP_LESS_OR_EQUAL:
23761 				sexp_val = sexp_number_compare( node, op_num );
23762 				break;
23763 
23764 			case OP_STRING_GREATER_THAN:
23765 			case OP_STRING_LESS_THAN:
23766 			case OP_STRING_EQUALS:
23767 				sexp_val = sexp_string_compare( node, op_num );
23768 				break;
23769 
23770 			case OP_IS_IFF:
23771 				sexp_val = sexp_is_iff(node);
23772 				break;
23773 
23774 			case OP_NOT:
23775 				sexp_val = sexp_not(node);
23776 				break;
23777 
23778 			case OP_PREVIOUS_GOAL_TRUE:
23779 				sexp_val = sexp_previous_goal_status( node, GOAL_COMPLETE );
23780 				break;
23781 
23782 			case OP_PREVIOUS_GOAL_FALSE:
23783 				sexp_val = sexp_previous_goal_status( node, GOAL_FAILED );
23784 				break;
23785 
23786 			case OP_PREVIOUS_GOAL_INCOMPLETE:
23787 				sexp_val = sexp_previous_goal_status( node, GOAL_INCOMPLETE );
23788 				break;
23789 
23790 			case OP_PREVIOUS_EVENT_TRUE:
23791 				sexp_val = sexp_previous_event_status( node, EVENT_SATISFIED );
23792 				break;
23793 
23794 			case OP_PREVIOUS_EVENT_FALSE:
23795 				sexp_val = sexp_previous_event_status( node, EVENT_FAILED );
23796 				break;
23797 
23798 			case OP_PREVIOUS_EVENT_INCOMPLETE:
23799 				sexp_val = sexp_previous_event_status( node, EVENT_INCOMPLETE );
23800 				break;
23801 
23802 			case OP_EVENT_TRUE:
23803 			case OP_EVENT_FALSE:
23804 				sexp_val = sexp_event_status( node, (op_num == OP_EVENT_TRUE?1:0) );
23805 				if ((sexp_val != SEXP_TRUE) && (sexp_val != SEXP_KNOWN_TRUE))
23806 					Sexp_useful_number = 0;  // indicate sexp isn't current yet
23807 				break;
23808 
23809 			case OP_EVENT_TRUE_DELAY:
23810 			case OP_EVENT_FALSE_DELAY:
23811 				sexp_val = sexp_event_delay_status( node, (op_num == OP_EVENT_TRUE_DELAY?1:0) );
23812 				break;
23813 
23814 			case OP_EVENT_TRUE_MSECS_DELAY:
23815 			case OP_EVENT_FALSE_MSECS_DELAY:
23816 				sexp_val = sexp_event_delay_status( node, (op_num == OP_EVENT_TRUE_MSECS_DELAY?1:0), true );
23817 				break;
23818 
23819 			case OP_GOAL_TRUE_DELAY:
23820 			case OP_GOAL_FALSE_DELAY:
23821 				sexp_val = sexp_goal_delay_status( node, (op_num == OP_GOAL_TRUE_DELAY?1:0) );
23822 				break;
23823 
23824 			case OP_EVENT_INCOMPLETE:
23825 				sexp_val = sexp_event_incomplete(node);
23826 				if ((sexp_val != SEXP_TRUE) && (sexp_val != SEXP_KNOWN_TRUE))
23827 					Sexp_useful_number = 0;  // indicate sexp isn't current yet
23828 				break;
23829 
23830 			case OP_GOAL_INCOMPLETE:
23831 				sexp_val = sexp_goal_incomplete(node);
23832 				break;
23833 
23834 			// destroy type sexpressions
23835 			case OP_IS_DESTROYED:
23836 				sexp_val = sexp_is_destroyed(node, nullptr);
23837 				break;
23838 
23839 			case OP_WAS_DESTROYED_BY_DELAY:
23840 				sexp_val = sexp_was_destroyed_by_delay(node);
23841 				break;
23842 
23843 			case OP_IS_SUBSYSTEM_DESTROYED:
23844 				sexp_val = sexp_is_subsystem_destroyed(node, nullptr);
23845 				break;
23846 
23847 			case OP_HAS_ARRIVED:
23848 				sexp_val = sexp_has_arrived( node, nullptr );
23849 				break;
23850 
23851 			case OP_HAS_DEPARTED:
23852 				sexp_val = sexp_has_departed( node, nullptr );
23853 				break;
23854 
23855 			case OP_IS_DISABLED:
23856 				sexp_val = sexp_is_disabled_xor_disarmed( node, true, nullptr );
23857 				break;
23858 
23859 			case OP_IS_DISARMED:
23860 				sexp_val = sexp_is_disabled_xor_disarmed( node, false, nullptr );
23861 				break;
23862 
23863 			case OP_WAYPOINTS_DONE:
23864 				sexp_val = sexp_are_waypoints_done( node, nullptr );
23865 				break;
23866 
23867 			// objective operators that use a delay
23868 			case OP_IS_DESTROYED_DELAY:
23869 				sexp_val = sexp_is_destroyed_delay(node);
23870 				break;
23871 
23872 			case OP_IS_SUBSYSTEM_DESTROYED_DELAY:
23873 				sexp_val = sexp_is_subsystem_destroyed_delay(node);
23874 				break;
23875 
23876 			case OP_HAS_DOCKED:
23877 			case OP_HAS_UNDOCKED:
23878 			case OP_HAS_DOCKED_DELAY:
23879 			case OP_HAS_UNDOCKED_DELAY:
23880 				sexp_val = sexp_has_docked_or_undocked(node, op_num);
23881 				break;
23882 
23883 			case OP_HAS_ARRIVED_DELAY:
23884 				sexp_val = sexp_has_arrived_delay(node);
23885 				break;
23886 
23887 			case OP_HAS_DEPARTED_DELAY:
23888 				sexp_val = sexp_has_departed_delay(node);
23889 				break;
23890 
23891 			case OP_IS_DISABLED_DELAY:
23892 				sexp_val = sexp_is_disabled_xor_disarmed_delay(node, true);
23893 				break;
23894 
23895 			case OP_IS_DISARMED_DELAY:
23896 				sexp_val = sexp_is_disabled_xor_disarmed_delay(node, false);
23897 				break;
23898 
23899 			case OP_WAYPOINTS_DONE_DELAY:
23900 				sexp_val = sexp_are_waypoints_done_delay(node);
23901 				break;
23902 
23903 			case OP_SHIP_TYPE_DESTROYED:
23904 				sexp_val = sexp_ship_type_destroyed(node);
23905 				break;
23906 
23907 			// time based sexpressions
23908 			case OP_HAS_TIME_ELAPSED:
23909 				sexp_val = sexp_has_time_elapsed(node);
23910 				break;
23911 
23912 			case OP_MODIFY_VARIABLE:
23913 				sexp_modify_variable(node);
23914 				sexp_val = SEXP_TRUE;	// SEXP_TRUE means only do once.
23915 				break;
23916 
23917 			case OP_MODIFY_VARIABLE_XSTR:
23918 				sexp_modify_variable_xstr(node);
23919 				sexp_val = SEXP_TRUE;	// SEXP_TRUE means only do once.
23920 				break;
23921 
23922 			case OP_GET_VARIABLE_BY_INDEX:
23923 				sexp_val = sexp_get_variable_by_index(node);
23924 				break;
23925 
23926 			case OP_SET_VARIABLE_BY_INDEX:
23927 				sexp_set_variable_by_index(node);
23928 				sexp_val = SEXP_TRUE;
23929 				break;
23930 
23931 			case OP_COPY_VARIABLE_FROM_INDEX:
23932 				sexp_copy_variable_from_index(node);
23933 				sexp_val = SEXP_TRUE;
23934 				break;
23935 
23936 			case OP_COPY_VARIABLE_BETWEEN_INDEXES:
23937 				sexp_copy_variable_between_indexes(node);
23938 				sexp_val = SEXP_TRUE;
23939 				break;
23940 
23941 			case OP_TIME_SHIP_DESTROYED:
23942 			case OP_TIME_WING_DESTROYED:
23943 			case OP_TIME_SHIP_DEPARTED:
23944 			case OP_TIME_WING_DEPARTED:
23945 				sexp_val = sexp_time_exited(node, op_num);
23946 				break;
23947 
23948 			case OP_TIME_SHIP_ARRIVED:
23949 			case OP_TIME_WING_ARRIVED:
23950 				sexp_val = sexp_time_arrived(node, op_num == OP_TIME_SHIP_ARRIVED);
23951 				break;
23952 
23953 			case OP_MISSION_TIME:
23954 				sexp_val = sexp_mission_time();
23955 				break;
23956 
23957 			case OP_MISSION_TIME_MSECS:
23958 				sexp_val = sexp_mission_time_msecs();
23959 				break;
23960 
23961 			case OP_TIME_DOCKED:
23962 			case OP_TIME_UNDOCKED:
23963 				sexp_val = sexp_time_docked_or_undocked(node, op_num == OP_TIME_DOCKED);
23964 				break;
23965 
23966 			case OP_AFTERBURNER_LEFT:
23967 			case OP_WEAPON_ENERGY_LEFT:
23968 				sexp_val = sexp_get_energy_pct(node, op_num);
23969 				break;
23970 
23971 			case OP_SHIELDS_LEFT:
23972 				sexp_val = sexp_shields_left(node);
23973 				break;
23974 
23975 			case OP_HITS_LEFT:
23976 			case OP_SIM_HITS_LEFT:
23977 				sexp_val = sexp_hits_left(node, op_num == OP_SIM_HITS_LEFT);
23978 				break;
23979 
23980 			case OP_HITS_LEFT_SUBSYSTEM:
23981 				sexp_val = sexp_hits_left_subsystem(node);
23982 				break;
23983 
23984 			case OP_HITS_LEFT_SUBSYSTEM_GENERIC:
23985 				sexp_val = sexp_hits_left_subsystem_generic(node);
23986 				break;
23987 
23988 			case OP_HITS_LEFT_SUBSYSTEM_SPECIFIC:
23989 				sexp_val = sexp_hits_left_subsystem_specific(node);
23990 				break;
23991 
23992 			case OP_SPECIAL_WARP_DISTANCE:
23993 				sexp_val = sexp_special_warp_dist(node);
23994 				break;
23995 
23996 			case OP_DISTANCE:
23997 				sexp_val = sexp_distance(node, sexp_retail_distance3);
23998 				break;
23999 			case OP_DISTANCE_CENTER:
24000 				sexp_val = sexp_distance(node, sexp_center_distance3);
24001 				break;
24002 			case OP_DISTANCE_BBOX:
24003 				sexp_val = sexp_distance(node, sexp_bbox_distance3);
24004 				break;
24005 
24006 			case OP_DISTANCE_CENTER_SUBSYSTEM:
24007 				sexp_val = sexp_distance_subsystem(node, sexp_center_distance_point);
24008 				break;
24009 			case OP_DISTANCE_BBOX_SUBSYSTEM:
24010 				sexp_val = sexp_distance_subsystem(node, sexp_bbox_distance_point);
24011 				break;
24012 
24013 			case OP_NUM_WITHIN_BOX:
24014 				sexp_val = sexp_num_within_box(node);
24015 				break;
24016 
24017 			case OP_IS_IN_BOX:
24018 				sexp_val = sexp_is_in_box(node);
24019 				break;
24020 
24021 			case OP_IS_IN_MISSION:
24022 				sexp_val = sexp_is_in_mission(node);
24023 				break;
24024 
24025 			case OP_IS_DOCKED:
24026 				sexp_val = sexp_is_docked(node);
24027 				break;
24028 
24029 			case OP_IS_SHIP_VISIBLE:
24030 				sexp_val = sexp_is_ship_visible(node);
24031 				break;
24032 
24033 			case OP_IS_SHIP_STEALTHY:
24034 				sexp_val = sexp_check_ship_flag(node, Ship::Ship_Flags::Stealth);
24035 				break;
24036 
24037 			case OP_IS_FRIENDLY_STEALTH_VISIBLE:
24038 				sexp_val = sexp_check_ship_flag(node, Ship::Ship_Flags::Friendly_stealth_invis);
24039 				break;
24040 
24041 			case OP_TEAM_SCORE:
24042 				sexp_val = sexp_team_score(node);
24043 				break;
24044 
24045 			case OP_LAST_ORDER_TIME:
24046 				sexp_val = sexp_last_order_time(node);
24047 				break;
24048 
24049 			case OP_NUM_PLAYERS:
24050 				sexp_val = sexp_num_players();
24051 				break;
24052 
24053 			case OP_SKILL_LEVEL_AT_LEAST:
24054 				sexp_val = sexp_skill_level_at_least(node);
24055 				break;
24056 
24057 			case OP_IS_CARGO_KNOWN:
24058 			case OP_CARGO_KNOWN_DELAY:
24059 				sexp_val = sexp_is_cargo_known( node, op_num == OP_CARGO_KNOWN_DELAY );
24060 				break;
24061 
24062 			case OP_HAS_BEEN_TAGGED_DELAY:
24063 				sexp_val = sexp_has_been_tagged_delay(node);
24064 				break;
24065 
24066 			case OP_ARE_SHIP_FLAGS_SET:
24067 				sexp_val = sexp_are_ship_flags_set(node);
24068 				break;
24069 
24070 			case OP_CAP_SUBSYS_CARGO_KNOWN_DELAY:
24071 				sexp_val = sexp_cap_subsys_cargo_known_delay(node);
24072 				break;
24073 
24074 			case OP_WAS_PROMOTION_GRANTED:
24075 				sexp_val = sexp_was_promotion_granted(node);
24076 				break;
24077 
24078 			case OP_WAS_MEDAL_GRANTED:
24079 				sexp_val = sexp_was_medal_granted(node);
24080 				break;
24081 
24082 			case OP_GET_DAMAGE_CAUSED:
24083 				sexp_val = sexp_get_damage_caused(node);
24084 				break;
24085 
24086 			case OP_PERCENT_SHIPS_ARRIVED:
24087 			case OP_PERCENT_SHIPS_DEPARTED:
24088 			case OP_PERCENT_SHIPS_DESTROYED:
24089 			case OP_PERCENT_SHIPS_DISARMED:
24090 			case OP_PERCENT_SHIPS_DISABLED:
24091 				sexp_val = sexp_percent_ships_arrive_depart_destroy_disarm_disable(node, op_num);
24092 				break;
24093 
24094 			case OP_DEPART_NODE_DELAY:
24095 				sexp_val = sexp_depart_node_delay(node);
24096 				break;
24097 
24098 			case OP_DESTROYED_DEPARTED_DELAY:
24099 				sexp_val = sexp_destroyed_departed_delay(node);
24100 				break;
24101 
24102 			// conditional sexpressions
24103 			case OP_WHEN:
24104 			case OP_WHEN_ARGUMENT:
24105 			case OP_IF_THEN_ELSE:
24106 				sexp_val = eval_when( node, op_num );
24107 				break;
24108 
24109 			case OP_PERFORM_ACTIONS:
24110 				sexp_val = eval_perform_actions( node );
24111 				break;
24112 
24113 			case OP_SWITCH:
24114 				eval_switch(node);
24115 				sexp_val = SEXP_TRUE;
24116 				break;
24117 
24118 			case OP_COND:
24119 				sexp_val = eval_cond(node);
24120 				break;
24121 
24122 			// Goober5000: special case; evaluate like when, but flush the sexp tree
24123 			// and return SEXP_NAN so this will always be re-evaluated
24124 			case OP_EVERY_TIME:
24125 			case OP_EVERY_TIME_ARGUMENT:
24126 				eval_when( node, op_num );
24127 				flush_sexp_tree(node);
24128 				sexp_val = SEXP_NAN;
24129 				break;
24130 
24131 			// Goober5000
24132 			case OP_ANY_OF:
24133 				sexp_val = eval_any_of( cur_node, referenced_node );
24134 				break;
24135 
24136 			// Goober5000
24137 			case OP_EVERY_OF:
24138 				sexp_val = eval_every_of( cur_node, referenced_node );
24139 				break;
24140 
24141 			// Goober5000 and Karajorma
24142 			case OP_RANDOM_OF:
24143 			case OP_RANDOM_MULTIPLE_OF:
24144 				sexp_val = eval_random_of( cur_node, referenced_node, (op_num == OP_RANDOM_MULTIPLE_OF) );
24145 				break;
24146 
24147 			// Goober5000
24148 			case OP_NUMBER_OF:
24149 				sexp_val = eval_number_of( cur_node, referenced_node );
24150 				break;
24151 
24152 			// Karajorma
24153 			case OP_IN_SEQUENCE:
24154 				sexp_val = eval_in_sequence( cur_node, referenced_node );
24155 				break;
24156 
24157 			// Goober5000
24158 			case OP_FOR_COUNTER:
24159 				sexp_val = eval_for_counter( cur_node, referenced_node );
24160 				break;
24161 
24162 			// Goober5000
24163 			case OP_FOR_SHIP_CLASS:
24164 			case OP_FOR_SHIP_TYPE:
24165 			case OP_FOR_SHIP_TEAM:
24166 			case OP_FOR_SHIP_SPECIES:
24167 				sexp_val = eval_for_ship_collection( cur_node, referenced_node, op_num );
24168 				break;
24169 
24170 			// Goober5000
24171 			case OP_FOR_PLAYERS:
24172 				sexp_val = eval_for_players( cur_node, referenced_node );
24173 				break;
24174 
24175 			// MageKing17
24176 			case OP_FIRST_OF:
24177 				sexp_val = eval_first_of( cur_node, referenced_node );
24178 				break;
24179 
24180 			// Karajorma
24181 			case OP_INVALIDATE_ALL_ARGUMENTS:
24182 			case OP_VALIDATE_ALL_ARGUMENTS:
24183 				sexp_change_all_argument_validity(cur_node, (op_num == OP_INVALIDATE_ALL_ARGUMENTS));
24184 				sexp_val = SEXP_TRUE;
24185 			break;
24186 
24187 			// Goober5000
24188 			case OP_INVALIDATE_ARGUMENT:
24189 			case OP_VALIDATE_ARGUMENT:
24190 				sexp_change_argument_validity(node, (op_num == OP_INVALIDATE_ARGUMENT));
24191 				sexp_val = SEXP_TRUE;
24192 				break;
24193 
24194 			case OP_DO_FOR_VALID_ARGUMENTS:
24195 				// do-for-valid-arguments should only ever be called within eval_when()
24196 				Warning(LOCATION, "do-for-valid-arguments was encountered in eval_sexp()!");
24197 				break;
24198 
24199 			case OP_NUM_VALID_ARGUMENTS:
24200 				sexp_val = sexp_num_valid_arguments( cur_node );
24201 				break;
24202 
24203 			// this is a "conditional", but it's not really treated the same way as when, etc.
24204 			case OP_FUNCTIONAL_IF_THEN_ELSE:
24205 				sexp_val = sexp_functional_if_then_else(node);
24206 				break;
24207 
24208 			// this too
24209 			case OP_FUNCTIONAL_SWITCH:
24210 				sexp_val = sexp_functional_switch(node);
24211 				break;
24212 
24213 			// sexpressions with side effects
24214 			case OP_CHANGE_IFF:
24215 				sexp_change_iff(node);
24216 				sexp_val = SEXP_TRUE;
24217 				break;
24218 
24219 			case OP_ADD_SHIP_GOAL:
24220 			case OP_ADD_WING_GOAL:
24221 			case OP_ADD_GOAL:
24222 				sexp_add_goal(node);
24223 				sexp_val = SEXP_TRUE;
24224 				break;
24225 
24226 			case OP_REMOVE_GOAL:
24227 				sexp_remove_goal(node);
24228 				sexp_val = SEXP_TRUE;
24229 				break;
24230 
24231 			case OP_CLEAR_SHIP_GOALS:
24232 			case OP_CLEAR_WING_GOALS:
24233 			case OP_CLEAR_GOALS:
24234 				sexp_clear_goals(node);
24235 				sexp_val = SEXP_TRUE;
24236 				break;
24237 
24238 			case OP_PROTECT_SHIP:
24239 			case OP_UNPROTECT_SHIP:
24240 				sexp_protect_ships(node, (op_num == OP_PROTECT_SHIP));
24241 				sexp_val = SEXP_TRUE;
24242 				break;
24243 
24244 			case OP_BEAM_PROTECT_SHIP:
24245 			case OP_BEAM_UNPROTECT_SHIP:
24246 				sexp_beam_protect_ships(node, (op_num == OP_BEAM_PROTECT_SHIP));
24247 				sexp_val = SEXP_TRUE;
24248 				break;
24249 
24250 			case OP_TURRET_PROTECT_SHIP:
24251 			case OP_TURRET_UNPROTECT_SHIP:
24252 				sexp_turret_protect_ships(node, (op_num == OP_TURRET_PROTECT_SHIP));
24253 				sexp_val = SEXP_TRUE;
24254 				break;
24255 
24256 			case OP_SHIP_STEALTHY:
24257 			case OP_SHIP_UNSTEALTHY:
24258 				sexp_ships_stealthy(node, (op_num == OP_SHIP_STEALTHY));
24259 				sexp_val = SEXP_TRUE;
24260 				break;
24261 
24262 			case OP_FRIENDLY_STEALTH_INVISIBLE:
24263 			case OP_FRIENDLY_STEALTH_VISIBLE:
24264 				sexp_friendly_stealth_invisible(node, (op_num == OP_FRIENDLY_STEALTH_INVISIBLE));
24265 				sexp_val = SEXP_TRUE;
24266 				break;
24267 
24268 			case OP_SHIP_INVISIBLE:
24269 			case OP_SHIP_VISIBLE:
24270 				sexp_ships_visible(node, (op_num == OP_SHIP_VISIBLE));
24271 				sexp_val = SEXP_TRUE;
24272 				break;
24273 
24274 			// Goober5000
24275 			case OP_SHIP_TAG:
24276 			case OP_SHIP_UNTAG:
24277 				sexp_ship_tag(node, (op_num == OP_SHIP_TAG));
24278 				sexp_val = SEXP_TRUE;
24279 				break;
24280 
24281 			case OP_SHIP_CHANGE_ALT_NAME:
24282 			case OP_SHIP_CHANGE_CALLSIGN:
24283 				sexp_ship_change_alt_name_or_callsign(node, op_num == OP_SHIP_CHANGE_ALT_NAME);
24284 				sexp_val = SEXP_TRUE;
24285 				break;
24286 
24287 			case OP_SET_DEATH_MESSAGE:
24288 				sexp_set_death_message(node);
24289 				sexp_val = SEXP_TRUE;
24290 				break;
24291 
24292 			case OP_ALTER_SHIP_FLAG:
24293 				sexp_alter_ship_flag(node);
24294 				sexp_val = SEXP_TRUE;
24295 				break;
24296 
24297 			case OP_SHIP_VULNERABLE:
24298 			case OP_SHIP_INVULNERABLE:
24299 				sexp_ships_invulnerable(node, (op_num == OP_SHIP_INVULNERABLE));
24300 				sexp_val = SEXP_TRUE;
24301 				break;
24302 
24303 			case OP_SHIP_BOMB_TARGETABLE:
24304 			case OP_SHIP_BOMB_UNTARGETABLE:
24305 				sexp_ships_bomb_targetable(node, (op_num == OP_SHIP_BOMB_TARGETABLE));
24306 				sexp_val = SEXP_TRUE;
24307 				break;
24308 
24309 			case OP_SHIP_GUARDIAN:
24310 			case OP_SHIP_NO_GUARDIAN:
24311 				sexp_ships_guardian(node, (op_num == OP_SHIP_GUARDIAN));
24312 				sexp_val = SEXP_TRUE;
24313 				break;
24314 
24315 			case OP_SHIP_GUARDIAN_THRESHOLD:
24316 				sexp_ship_guardian_threshold(node);
24317 				sexp_val = SEXP_TRUE;
24318 				break;
24319 
24320 			case OP_SHIP_SUBSYS_GUARDIAN_THRESHOLD:
24321 				sexp_ship_subsys_guardian_threshold(node);
24322 				sexp_val = SEXP_TRUE;
24323 				break;
24324 
24325 			case OP_SHIP_SUBSYS_TARGETABLE:
24326 				sexp_ship_deal_with_subsystem_flag(node, Ship::Subsystem_Flags::Untargetable, true, false);
24327 				sexp_val = SEXP_TRUE;
24328 				break;
24329 
24330 			case OP_SHIP_SUBSYS_UNTARGETABLE:
24331 				sexp_ship_deal_with_subsystem_flag(node, Ship::Subsystem_Flags::Untargetable, true, true);
24332 				sexp_val = SEXP_TRUE;
24333 				break;
24334 
24335 			case OP_TURRET_SUBSYS_TARGET_DISABLE:
24336 				sexp_val = SEXP_TRUE;
24337 				sexp_ship_deal_with_subsystem_flag(node, Ship::Subsystem_Flags::No_SS_targeting, false, true);
24338 				break;
24339 
24340 			case OP_TURRET_SUBSYS_TARGET_ENABLE:
24341 				sexp_val = SEXP_TRUE;
24342 				sexp_ship_deal_with_subsystem_flag(node, Ship::Subsystem_Flags::No_SS_targeting, false, false);
24343 				break;
24344 
24345 			case OP_SHIP_SUBSYS_NO_REPLACE:
24346 				sexp_ship_deal_with_subsystem_flag(node, Ship::Subsystem_Flags::No_replace, true);
24347 				sexp_val = SEXP_TRUE;
24348 				break;
24349 
24350 			case OP_SHIP_SUBSYS_NO_LIVE_DEBRIS:
24351 				sexp_ship_deal_with_subsystem_flag(node, Ship::Subsystem_Flags::No_live_debris, true);
24352 				sexp_val = SEXP_TRUE;
24353 				break;
24354 
24355 			case OP_SHIP_SUBSYS_VANISHED:
24356 				sexp_ship_deal_with_subsystem_flag(node, Ship::Subsystem_Flags::Vanished, true);
24357 				sexp_val = SEXP_TRUE;
24358 				break;
24359 
24360 			case OP_SHIP_SUBSYS_IGNORE_IF_DEAD:
24361 				sexp_ship_deal_with_subsystem_flag(node, Ship::Subsystem_Flags::Missiles_ignore_if_dead, false);
24362 				sexp_val = SEXP_TRUE;
24363 				break;
24364 
24365 			case OP_SHIP_CREATE:
24366 				sexp_ship_create(node);
24367 				sexp_val = SEXP_TRUE;
24368 				break;
24369 
24370 			case OP_WEAPON_CREATE:
24371 				sexp_weapon_create(node);
24372 				sexp_val = SEXP_TRUE;
24373 				break;
24374 
24375 			case OP_SHIP_VANISH:
24376 				sexp_ship_vanish(node);
24377 				sexp_val = SEXP_TRUE;
24378 				break;
24379 
24380 			case OP_DESTROY_INSTANTLY:
24381 				sexp_destroy_instantly(node);
24382 				sexp_val = SEXP_TRUE;
24383 				break;
24384 
24385 			//-Sesquipedalian
24386 			case OP_SHIELDS_ON:
24387 			case OP_SHIELDS_OFF:
24388 				sexp_shields_off(node, (op_num == OP_SHIELDS_OFF));
24389 				sexp_val = SEXP_TRUE;
24390 				break;
24391 
24392 			//-Sesquipedalian
24393 			case OP_KAMIKAZE:
24394 				sexp_kamikaze(node, (op_num == OP_KAMIKAZE));
24395 				sexp_val = SEXP_TRUE;
24396 				break;
24397 
24398 			case OP_SET_CARGO:
24399 				sexp_set_cargo(node);
24400 				sexp_val = SEXP_TRUE;
24401 				break;
24402 
24403 			case OP_IS_CARGO:
24404 				sexp_val = sexp_is_cargo(node);
24405 				break;
24406 
24407 			case OP_CHANGE_AI_CLASS:
24408 				sexp_change_ai_class(node);
24409 				sexp_val = SEXP_TRUE;
24410 				break;
24411 
24412 			case OP_IS_AI_CLASS:
24413 				sexp_val = sexp_is_ai_class(node);
24414 				break;
24415 
24416 			case OP_IS_SHIP_TYPE:
24417 			case OP_IS_SHIP_CLASS:
24418 				sexp_val = sexp_is_ship_class_or_type(node, op_num == OP_IS_SHIP_CLASS);
24419 				break;
24420 
24421 			case OP_CHANGE_SOUNDTRACK:
24422 				sexp_change_soundtrack(node);
24423 				sexp_val = SEXP_TRUE;
24424 				break;
24425 
24426 			case OP_PLAY_SOUND_FROM_TABLE:
24427 				sexp_play_sound_from_table(node);
24428 				sexp_val = SEXP_TRUE;
24429 				break;
24430 
24431 			case OP_PLAY_SOUND_FROM_FILE:
24432 				sexp_play_sound_from_file(node);
24433 				sexp_val = SEXP_TRUE;
24434 				break;
24435 
24436 			case OP_CLOSE_SOUND_FROM_FILE:
24437 				sexp_close_sound_from_file(node);
24438 				sexp_val = SEXP_TRUE;
24439 				break;
24440 
24441 			case OP_PAUSE_SOUND_FROM_FILE:
24442 				sexp_pause_sound_from_file(node);
24443 				sexp_val = SEXP_TRUE;
24444 				break;
24445 
24446 			case OP_SET_SOUND_ENVIRONMENT:
24447 				sexp_set_sound_environment(node);
24448 				sexp_val = SEXP_TRUE;
24449 				break;
24450 
24451 			case OP_UPDATE_SOUND_ENVIRONMENT:
24452 				sexp_update_sound_environment(node);
24453 				sexp_val = SEXP_TRUE;
24454 				break;
24455 
24456 			case OP_ADJUST_AUDIO_VOLUME:
24457 				sexp_adjust_audio_volume(node);
24458 				sexp_val = SEXP_TRUE;
24459 				break;
24460 
24461 			case OP_HUD_DISABLE:
24462 				sexp_hud_disable(node);
24463 				sexp_val = SEXP_TRUE;
24464 				break;
24465 
24466 			case OP_HUD_DISABLE_EXCEPT_MESSAGES:
24467 				sexp_hud_disable_except_messages(node);
24468 				sexp_val = SEXP_TRUE;
24469 				break;
24470 
24471 			case OP_HUD_SET_TEXT:
24472 				sexp_hud_set_text(node);
24473 				sexp_val = SEXP_TRUE;
24474 				break;
24475 
24476 			case OP_HUD_SET_TEXT_NUM:
24477 				sexp_hud_set_text_num(node);
24478 				sexp_val = SEXP_TRUE;
24479 				break;
24480 
24481 			case OP_HUD_SET_COORDS:
24482 				sexp_hud_set_coords(node);
24483 				sexp_val = SEXP_TRUE;
24484 				break;
24485 
24486 			case OP_HUD_SET_FRAME:
24487 				sexp_hud_set_frame(node);
24488 				sexp_val = SEXP_TRUE;
24489 				break;
24490 
24491 			case OP_HUD_SET_COLOR:
24492 				sexp_hud_set_color(node);
24493 				sexp_val = SEXP_TRUE;
24494 				break;
24495 
24496 			case OP_HUD_SET_MAX_TARGETING_RANGE:
24497 				sexp_hud_set_max_targeting_range(node);
24498 				sexp_val = SEXP_TRUE;
24499 				break;
24500 
24501 			case OP_HUD_DISPLAY_GAUGE:
24502 				sexp_hud_display_gauge(node);
24503 				sexp_val = SEXP_TRUE;
24504 				break;
24505 
24506 			case OP_HUD_SET_MESSAGE:
24507 				sexp_hud_set_message(node);
24508 				sexp_val = SEXP_TRUE;
24509 				break;
24510 
24511 			// Goober5000
24512 			case OP_PLAYER_USE_AI:
24513 			case OP_PLAYER_NOT_USE_AI:
24514 				sexp_player_use_ai(op_num == OP_PLAYER_USE_AI);
24515 				sexp_val = SEXP_TRUE;
24516 				break;
24517 
24518 			//Karajorma
24519 			case OP_ALLOW_TREASON:
24520 				sexp_allow_treason(node);
24521 				sexp_val = SEXP_TRUE;
24522 				break;
24523 
24524 			//Karajorma
24525 			case OP_SET_PLAYER_ORDERS:
24526 				sexp_set_player_orders(node);
24527 				sexp_val = SEXP_TRUE;
24528 				break;
24529 
24530 			// Goober5000
24531 			case OP_EXPLOSION_EFFECT:
24532 				sexp_explosion_effect(node);
24533 				sexp_val = SEXP_TRUE;
24534 				break;
24535 
24536 			// Goober5000
24537 			case OP_WARP_EFFECT:
24538 				sexp_warp_effect(node);
24539 				sexp_val = SEXP_TRUE;
24540 				break;
24541 
24542 			case OP_SEND_MESSAGE:
24543 				sexp_send_message(node);
24544 				sexp_val = SEXP_TRUE;
24545 				break;
24546 
24547 			// Karajorma
24548 			case OP_ENABLE_BUILTIN_MESSAGES:
24549 			case OP_DISABLE_BUILTIN_MESSAGES:
24550 				sexp_toggle_builtin_messages (node, op_num == OP_ENABLE_BUILTIN_MESSAGES);
24551 				sexp_val = SEXP_TRUE;
24552 				break;
24553 
24554 			case OP_SET_PERSONA:
24555 				sexp_set_persona (node);
24556 				sexp_val = SEXP_TRUE;
24557 				break;
24558 
24559 			case OP_SET_MISSION_MOOD:
24560 				sexp_set_mission_mood (node);
24561 				sexp_val = SEXP_TRUE;
24562 				break;
24563 
24564 			case OP_SEND_MESSAGE_LIST:
24565 			case OP_SEND_MESSAGE_CHAIN:
24566 				sexp_send_message_list(node, op_num == OP_SEND_MESSAGE_CHAIN);
24567 				sexp_val = SEXP_TRUE;
24568 				break;
24569 
24570 			case OP_SEND_RANDOM_MESSAGE:
24571 				sexp_send_random_message(node);
24572 				sexp_val = SEXP_TRUE;
24573 				break;
24574 
24575 			case OP_SELF_DESTRUCT:
24576 				sexp_self_destruct(node);
24577 				sexp_val = SEXP_TRUE;
24578 				break;
24579 
24580 			case OP_NEXT_MISSION:
24581 				sexp_next_mission(node);
24582 				sexp_val = SEXP_TRUE;
24583 				break;
24584 
24585 			case OP_END_OF_CAMPAIGN:
24586 				sexp_end_of_campaign(node);
24587 				sexp_val = SEXP_TRUE;
24588 				break;
24589 
24590 			case OP_END_CAMPAIGN:
24591 				sexp_end_campaign(node);
24592 				sexp_val = SEXP_TRUE;
24593 				break;
24594 
24595 			case OP_SABOTAGE_SUBSYSTEM:
24596 				sexp_sabotage_subsystem(node);
24597 				sexp_val = SEXP_TRUE;
24598 				break;
24599 
24600 			case OP_REPAIR_SUBSYSTEM:
24601 				sexp_repair_subsystem(node);
24602 				sexp_val = SEXP_TRUE;
24603 				break;
24604 
24605 			case OP_SET_SUBSYSTEM_STRNGTH:
24606 				sexp_set_subsystem_strength(node);
24607 				sexp_val = SEXP_TRUE;
24608 				break;
24609 
24610 			case OP_DESTROY_SUBSYS_INSTANTLY:
24611 				sexp_destroy_subsys_instantly(node);
24612 				sexp_val = SEXP_TRUE;
24613 				break;
24614 
24615 			case OP_INVALIDATE_GOAL:
24616 			case OP_VALIDATE_GOAL:
24617 				sexp_change_goal_validity( node, (op_num==OP_INVALIDATE_GOAL?0:1) );
24618 				sexp_val = SEXP_TRUE;
24619 				break;
24620 
24621 			case OP_TRANSFER_CARGO:
24622 				sexp_transfer_cargo(node);
24623 				sexp_val = SEXP_TRUE;
24624 				break;
24625 
24626 			case OP_EXCHANGE_CARGO:
24627 				sexp_exchange_cargo(node);
24628 				sexp_val = SEXP_TRUE;
24629 				break;
24630 
24631 
24632 			case OP_JETTISON_CARGO_DELAY:
24633 			case OP_JETTISON_CARGO_NEW:
24634 				sexp_jettison_cargo(node, (op_num==OP_JETTISON_CARGO_NEW));
24635 				sexp_val = SEXP_TRUE;
24636 				break;
24637 
24638 			case OP_SET_DOCKED:
24639 				sexp_set_docked(node);
24640 				sexp_val = SEXP_TRUE;
24641 				break;
24642 
24643 			case OP_CARGO_NO_DEPLETE:
24644 				sexp_cargo_no_deplete(node);
24645 				sexp_val = SEXP_TRUE;
24646 				break;
24647 
24648 			case OP_SET_SCANNED:	// Goober5000
24649 			case OP_SET_UNSCANNED:
24650 				sexp_set_scanned_unscanned(node, op_num == OP_SET_SCANNED);
24651 				sexp_val = SEXP_TRUE;
24652 				break;
24653 
24654 			case OP_SET_SPECIAL_WARPOUT_NAME:
24655 				sexp_special_warpout_name(node);
24656 				sexp_val = SEXP_TRUE;
24657 				break;
24658 
24659 			//-WMC
24660 			case OP_MISSION_SET_NEBULA:
24661 				sexp_mission_set_nebula(node);
24662 				sexp_val = SEXP_TRUE;
24663 				break;
24664 
24665 			case OP_MISSION_SET_SUBSPACE:
24666 				sexp_mission_set_subspace(node);
24667 				sexp_val = SEXP_TRUE;
24668 				break;
24669 
24670 			case OP_CHANGE_BACKGROUND:
24671 				sexp_change_background(node);
24672 				sexp_val = SEXP_TRUE;
24673 				break;
24674 
24675 			case OP_ADD_SUN_BITMAP:
24676 			case OP_ADD_BACKGROUND_BITMAP:
24677 				sexp_add_background_bitmap(node, op_num == OP_ADD_SUN_BITMAP);
24678 				sexp_val = SEXP_TRUE;
24679 				break;
24680 
24681 			case OP_REMOVE_SUN_BITMAP:
24682 			case OP_REMOVE_BACKGROUND_BITMAP:
24683 				sexp_remove_background_bitmap(node, op_num == OP_REMOVE_SUN_BITMAP);
24684 				sexp_val = SEXP_TRUE;
24685 				break;
24686 
24687 			case OP_NEBULA_CHANGE_STORM:
24688 				sexp_nebula_change_storm(node);
24689 				sexp_val = SEXP_TRUE;
24690 				break;
24691 
24692 			case OP_NEBULA_TOGGLE_POOF:
24693 				sexp_nebula_toggle_poof(node);
24694 				sexp_val = SEXP_TRUE;
24695 				break;
24696 
24697 			case OP_NEBULA_CHANGE_PATTERN:
24698 				sexp_nebula_change_pattern(node);
24699 				sexp_val = SEXP_TRUE;
24700 				break;
24701 
24702 			case OP_NEBULA_CHANGE_FOG_COLOR:
24703 				sexp_nebula_change_fog_color(node);
24704 				sexp_val = SEXP_TRUE;
24705 				break;
24706 
24707 			case OP_END_MISSION:
24708 				sexp_end_mission(node);
24709 				sexp_val = SEXP_TRUE;
24710 				break;
24711 
24712 			// Goober5000
24713 			case OP_SET_DEBRIEFING_TOGGLED:
24714 				sexp_set_debriefing_toggled(node);
24715 				sexp_val = SEXP_TRUE;
24716 				break;
24717 
24718 			// Goober5000
24719 			case OP_SET_DEBRIEFING_PERSONA:
24720 				sexp_set_debriefing_persona(node);
24721 				sexp_val = SEXP_TRUE;
24722 				break;
24723 
24724 			// Goober5000
24725 			case OP_FORCE_JUMP:
24726 				sexp_force_jump();
24727 				sexp_val = SEXP_TRUE;
24728 				break;
24729 
24730 				// sexpressions for setting flag for good/bad time for someone to reasm
24731 			case OP_GOOD_REARM_TIME:
24732 				sexp_good_time_to_rearm(node);
24733 				sexp_val = SEXP_TRUE;
24734 				break;
24735 
24736 			case OP_GRANT_PROMOTION:
24737 				sexp_grant_promotion();
24738 				sexp_val = SEXP_TRUE;
24739 				break;
24740 
24741 			case OP_GRANT_MEDAL:
24742 				sexp_grant_medal(node);
24743 				sexp_val = SEXP_TRUE;
24744 				break;
24745 
24746 			case OP_SHIP_VAPORIZE:
24747 			case OP_SHIP_NO_VAPORIZE:
24748 				sexp_ships_vaporize( node, (op_num == OP_SHIP_VAPORIZE) );
24749 				sexp_val = SEXP_TRUE;
24750 				break;
24751 
24752 			case OP_SET_EXPLOSION_OPTION:
24753 				sexp_set_explosion_option(node);
24754 				sexp_val = SEXP_TRUE;
24755 				break;
24756 
24757 			case OP_DONT_COLLIDE_INVISIBLE:
24758 			case OP_COLLIDE_INVISIBLE:
24759 				sexp_dont_collide_invisible( node, (op_num == OP_DONT_COLLIDE_INVISIBLE) );
24760 				sexp_val = SEXP_TRUE;
24761 				break;
24762 
24763 			case OP_SET_MOBILE:
24764 			case OP_SET_IMMOBILE:
24765 				sexp_set_immobile(node, (op_num == OP_SET_IMMOBILE));
24766 				sexp_val = SEXP_TRUE;
24767 				break;
24768 
24769 			case OP_IGNORE_KEY:
24770 				sexp_ignore_key(node);
24771 				sexp_val = SEXP_TRUE;
24772 				break;
24773 
24774 			// Goober5000 - sigh, was this messed up all along?
24775 			case OP_WARP_BROKEN:
24776 			case OP_WARP_NOT_BROKEN:
24777 			case OP_WARP_NEVER:
24778 			case OP_WARP_ALLOWED:
24779 				sexp_deal_with_warp( node, (op_num==OP_WARP_BROKEN) || (op_num==OP_WARP_NOT_BROKEN),
24780 					(op_num==OP_WARP_BROKEN) || (op_num==OP_WARP_NEVER) );
24781 				sexp_val = SEXP_TRUE;
24782 				break;
24783 
24784 			// Goober5000
24785 			case OP_SET_SUBSPACE_DRIVE:
24786 				sexp_set_subspace_drive(node);
24787 				sexp_val = SEXP_TRUE;
24788 				break;
24789 
24790 			case OP_GOOD_SECONDARY_TIME:
24791 				sexp_good_secondary_time(node);
24792 				sexp_val = SEXP_TRUE;
24793 				break;
24794 
24795 			// sexpressions to allow shpis/weapons during the course of a mission
24796 			case OP_ALLOW_SHIP:
24797 				sexp_allow_ship(node);
24798 				sexp_val = SEXP_TRUE;
24799 				break;
24800 
24801 			case OP_ALLOW_WEAPON:
24802 				sexp_allow_weapon(node);
24803 				sexp_val = SEXP_TRUE;
24804 				break;
24805 
24806 			case OP_TECH_ADD_SHIP:
24807 				sexp_tech_add_ship(node);
24808 				sexp_val = SEXP_TRUE;
24809 				break;
24810 
24811 			case OP_TECH_ADD_WEAPON:
24812 				sexp_tech_add_weapon(node);
24813 				sexp_val = SEXP_TRUE;
24814 				break;
24815 
24816 			case OP_TECH_ADD_INTEL:
24817 			case OP_TECH_ADD_INTEL_XSTR:
24818 				sexp_tech_toggle_intel(node, true, op_num == OP_TECH_ADD_INTEL_XSTR);
24819 				sexp_val = SEXP_TRUE;
24820 				break;
24821 
24822 			case OP_TECH_REMOVE_INTEL:
24823 			case OP_TECH_REMOVE_INTEL_XSTR:
24824 				sexp_tech_toggle_intel(node, false, op_num == OP_TECH_REMOVE_INTEL_XSTR);
24825 				sexp_val = SEXP_TRUE;
24826 				break;
24827 
24828 			case OP_TECH_RESET_TO_DEFAULT:
24829 				sexp_tech_reset_to_default();
24830 				sexp_val = SEXP_TRUE;
24831 				break;
24832 
24833 			case OP_CHANGE_PLAYER_SCORE:
24834 				sexp_change_player_score(node);
24835 				sexp_val = SEXP_TRUE;
24836 				break;
24837 
24838 			case OP_CHANGE_TEAM_SCORE:
24839 				sexp_change_team_score(node);
24840 				sexp_val = SEXP_TRUE;
24841 				break;
24842 
24843 			case OP_RED_ALERT:
24844 				sexp_red_alert();
24845 				sexp_val = SEXP_TRUE;
24846 				break;
24847 
24848 			case OP_SET_OBJECT_SPEED_X:
24849 			case OP_SET_OBJECT_SPEED_Y:
24850 			case OP_SET_OBJECT_SPEED_Z:
24851 				sexp_set_object_speed(node, op_num - OP_SET_OBJECT_SPEED_X);
24852 				sexp_val = SEXP_TRUE;
24853 				break;
24854 
24855 			case OP_GET_OBJECT_SPEED_X:
24856 			case OP_GET_OBJECT_SPEED_Y:
24857 			case OP_GET_OBJECT_SPEED_Z:
24858 				sexp_val = sexp_get_object_speed(node, op_num - OP_GET_OBJECT_SPEED_X);
24859 				break;
24860 
24861 			case OP_GET_OBJECT_X:
24862 			case OP_GET_OBJECT_Y:
24863 			case OP_GET_OBJECT_Z:
24864 				sexp_val = sexp_get_object_coordinate(node, op_num - OP_GET_OBJECT_X);
24865 				break;
24866 
24867 			case OP_GET_OBJECT_PITCH:
24868 			case OP_GET_OBJECT_BANK:
24869 			case OP_GET_OBJECT_HEADING:
24870 				sexp_val = sexp_get_object_angle(node, op_num - OP_GET_OBJECT_PITCH);
24871 				break;
24872 
24873 			case OP_SET_OBJECT_POSITION:
24874 				sexp_set_object_position(node);
24875 				sexp_val = SEXP_TRUE;
24876 				break;
24877 
24878 			case OP_SET_OBJECT_ORIENTATION:
24879 				sexp_set_object_orientation(node);
24880 				sexp_val = SEXP_TRUE;
24881 				break;
24882 
24883 			case OP_SET_OBJECT_FACING:
24884 			case OP_SET_OBJECT_FACING_OBJECT:
24885 				sexp_set_object_facing(node, op_num == OP_SET_OBJECT_FACING_OBJECT);
24886 				sexp_val = SEXP_TRUE;
24887 				break;
24888 
24889 			case OP_SHIP_MANEUVER:
24890 			case OP_SHIP_ROT_MANEUVER:
24891 			case OP_SHIP_LAT_MANEUVER:
24892 				sexp_set_ship_maneuver(node, op_num);
24893 				sexp_val = SEXP_TRUE;
24894 				break;
24895 
24896 			// training operators
24897 			case OP_KEY_PRESSED:
24898 				sexp_val = sexp_key_pressed(node);
24899 				break;
24900 
24901 			case OP_SPECIAL_CHECK:
24902 				sexp_val = sexp_special_training_check(node);
24903 				break;
24904 
24905 			case OP_KEY_RESET:
24906 				sexp_key_reset(node);
24907 				sexp_val = SEXP_KNOWN_TRUE;  // only do it first time in repeating events.
24908 				break;
24909 
24910 			case OP_KEY_RESET_MULTIPLE:
24911 				sexp_key_reset(node);
24912 				sexp_val = SEXP_TRUE;
24913 				break;
24914 
24915 			case OP_MISSILE_LOCKED:
24916 				sexp_val = sexp_missile_locked(node);
24917 				break;
24918 
24919 			case OP_TARGETED:
24920 				sexp_val = sexp_targeted(node);
24921 				break;
24922 
24923 			case OP_NODE_TARGETED:
24924 				sexp_val = sexp_node_targeted(node);
24925 				break;
24926 
24927 			case OP_SPEED:
24928 				sexp_val = sexp_speed(node);
24929 				break;
24930 
24931 			case OP_GET_THROTTLE_SPEED:
24932 				sexp_val = sexp_get_throttle_speed(node);
24933 				break;
24934 
24935 			case OP_SET_PLAYER_THROTTLE_SPEED:
24936 				sexp_set_player_throttle_speed(node);
24937 				sexp_val = SEXP_TRUE;
24938 				break;
24939 
24940 			case OP_PRIMARIES_DEPLETED:
24941 			case OP_SECONDARIES_DEPLETED:
24942 				sexp_val = sexp_weapons_depleted(node, op_num == OP_PRIMARIES_DEPLETED);
24943 				break;
24944 
24945 			case OP_FACING:
24946 				sexp_val = sexp_facing(node);
24947 				break;
24948 
24949 			case OP_IS_FACING:
24950 				sexp_val = sexp_is_facing(node);
24951 				break;
24952 
24953 			case OP_FACING2:
24954 				sexp_val = sexp_facing2(node);
24955 				break;
24956 
24957 			case OP_ORDER:
24958 				sexp_val = sexp_order(node);
24959 				break;
24960 
24961 			case OP_QUERY_ORDERS:
24962 				sexp_val = sexp_query_orders(node);
24963 				break;
24964 
24965 			case OP_TIME_TO_GOAL:
24966 				sexp_val = sexp_time_to_goal(node);
24967 				break;
24968 
24969 
24970 			// Karajorma
24971 			case OP_RESET_ORDERS:
24972 				sexp_reset_orders(node);
24973 				sexp_val = SEXP_TRUE;
24974 				break;
24975 
24976 			case OP_WAYPOINT_MISSED:
24977 				sexp_val = sexp_waypoint_missed();
24978 				break;
24979 
24980 			case OP_WAYPOINT_TWICE:
24981 				sexp_val = sexp_waypoint_twice();
24982 				break;
24983 
24984 			case OP_PATH_FLOWN:
24985 				sexp_val = sexp_path_flown();
24986 				break;
24987 
24988 			case OP_TRAINING_MSG:
24989 				sexp_send_training_message(node);
24990 				sexp_val = SEXP_TRUE;
24991 				break;
24992 
24993 			case OP_FLASH_HUD_GAUGE:
24994 				sexp_flash_hud_gauge(node);
24995 				sexp_val = SEXP_TRUE;
24996 				break;
24997 
24998 			case OP_SET_TRAINING_CONTEXT_FLY_PATH:
24999 				sexp_set_training_context_fly_path(node);
25000 				sexp_val = SEXP_TRUE;
25001 				break;
25002 
25003 			case OP_SET_TRAINING_CONTEXT_SPEED:
25004 				sexp_set_training_context_speed(node);
25005 				sexp_val = SEXP_TRUE;
25006 				break;
25007 
25008 			// Karajorma
25009 			case OP_STRING_TO_INT:
25010 				sexp_val = sexp_string_to_int(node);
25011 				break;
25012 
25013 			// Goober5000
25014 			case OP_INT_TO_STRING:
25015 				sexp_int_to_string(node);
25016 				sexp_val = SEXP_TRUE;
25017 				break;
25018 
25019 			// Goober5000
25020 			case OP_STRING_CONCATENATE:
25021 				sexp_string_concatenate(node);
25022 				sexp_val = SEXP_TRUE;
25023 				break;
25024 
25025 			// Goober5000
25026 			case OP_STRING_CONCATENATE_BLOCK:
25027 				sexp_string_concatenate_block(node);
25028 				sexp_val = SEXP_TRUE;
25029 				break;
25030 
25031 				// Goober5000
25032 			case OP_STRING_GET_SUBSTRING:
25033 				sexp_string_get_substring(node);
25034 				sexp_val = SEXP_TRUE;
25035 				break;
25036 
25037 			// Goober5000
25038 			case OP_STRING_SET_SUBSTRING:
25039 				sexp_string_set_substring(node);
25040 				sexp_val = SEXP_TRUE;
25041 				break;
25042 
25043 			// Goober5000
25044 			case OP_STRING_GET_LENGTH:
25045 				sexp_val = sexp_string_get_length(node);
25046 				break;
25047 
25048 			// Karajorma
25049 			case OP_DEBUG:
25050 				sexp_debug(node);
25051 				sexp_val = SEXP_TRUE;
25052 				break;
25053 
25054 			case 0: // zero represents a non-operator
25055 				return sexp_atoi(cur_node);
25056 
25057 			case OP_NOP:
25058 				sexp_val = SEXP_TRUE;
25059 				break;
25060 
25061 			case OP_BEAM_FIRE:
25062 			case OP_BEAM_FIRE_COORDS:
25063 				sexp_beam_fire(node, op_num == OP_BEAM_FIRE_COORDS);
25064 				sexp_val = SEXP_TRUE;
25065 				break;
25066 
25067 			case OP_BEAM_FLOATING_FIRE:
25068 				sexp_beam_floating_fire(node);
25069 				sexp_val = SEXP_TRUE;
25070 				break;
25071 
25072 			case OP_IS_TAGGED:
25073 				sexp_val = sexp_is_tagged(node);
25074 				break;
25075 
25076 			case OP_IS_PLAYER:
25077 				sexp_val = sexp_is_player(node);
25078 				break;
25079 
25080 			case OP_NUM_KILLS:
25081 			case OP_NUM_ASSISTS:
25082 			case OP_SHIP_SCORE:
25083 			case OP_SHIP_DEATHS:
25084 			case OP_RESPAWNS_LEFT:
25085 				sexp_val = sexp_return_player_data(node, op_num);
25086 				break;
25087 
25088 			case OP_SET_RESPAWNS:
25089 				sexp_set_respawns(node);
25090 				sexp_val = SEXP_TRUE;
25091 				break;
25092 
25093 			case OP_ADD_REMOVE_HOTKEY:
25094 				sexp_add_remove_hotkey(node);
25095 				sexp_val = SEXP_TRUE;
25096 				break;
25097 
25098 			case OP_CLEAR_WEAPONS:
25099 			case OP_CLEAR_DEBRIS:
25100 				sexp_clear_weapons_or_debris(node, op_num);
25101 				sexp_val = SEXP_TRUE;
25102 				break;
25103 
25104 			case OP_NUM_TYPE_KILLS:
25105 				sexp_val = sexp_num_type_kills(node);
25106 				break;
25107 
25108 			case OP_NUM_CLASS_KILLS:
25109 				sexp_val = sexp_num_class_kills(node);
25110 				break;
25111 
25112 			case OP_BEAM_FREE:
25113 			case OP_BEAM_LOCK:
25114 			case OP_TURRET_FREE:
25115 			case OP_TURRET_LOCK:
25116 				sexp_beam_or_turret_free_or_lock(node, op_num == OP_BEAM_FREE || op_num == OP_BEAM_LOCK, op_num == OP_BEAM_FREE || op_num == OP_TURRET_FREE);
25117 				sexp_val = SEXP_TRUE;
25118 				break;
25119 
25120 			case OP_BEAM_FREE_ALL:
25121 			case OP_BEAM_LOCK_ALL:
25122 			case OP_TURRET_FREE_ALL:
25123 			case OP_TURRET_LOCK_ALL:
25124 				sexp_beam_or_turret_free_or_lock_all(node, op_num == OP_BEAM_FREE_ALL || op_num == OP_BEAM_LOCK_ALL, op_num == OP_BEAM_FREE_ALL || op_num == OP_TURRET_FREE_ALL);
25125 				sexp_val = SEXP_TRUE;
25126 				break;
25127 
25128 			case OP_TURRET_CHANGE_WEAPON:
25129 				sexp_val = SEXP_TRUE;
25130 				sexp_turret_change_weapon(node);
25131 				break;
25132 
25133 			case OP_TURRET_SET_DIRECTION_PREFERENCE:
25134 				sexp_val = SEXP_TRUE;
25135 				sexp_turret_set_direction_preference(node);
25136 				break;
25137 
25138 			case OP_TURRET_SET_RATE_OF_FIRE:
25139 				sexp_val = SEXP_TRUE;
25140 				sexp_turret_set_rate_of_fire(node);
25141 				break;
25142 
25143 			case OP_TURRET_SET_OPTIMUM_RANGE:
25144 				sexp_val = SEXP_TRUE;
25145 				sexp_turret_set_optimum_range(node);
25146 				break;
25147 
25148 			case OP_TURRET_SET_TARGET_PRIORITIES:
25149 				sexp_val = SEXP_TRUE;
25150 				sexp_turret_set_target_priorities(node);
25151 				break;
25152 
25153 			case OP_TURRET_SET_TARGET_ORDER:
25154 				sexp_val = SEXP_TRUE;
25155 				sexp_turret_set_target_order(node);
25156 				break;
25157 
25158 			case OP_TURRET_SET_FORCED_SUBSYS_TARGET:
25159 			case OP_TURRET_SET_FORCED_TARGET:
25160 				sexp_val = SEXP_TRUE;
25161 				sexp_turret_set_forced_target(node, op_num == OP_TURRET_SET_FORCED_SUBSYS_TARGET);
25162 				break;
25163 
25164 			case OP_TURRET_CLEAR_FORCED_TARGET:
25165 				sexp_val = SEXP_TRUE;
25166 				sexp_turret_clear_forced_target(node);
25167 				break;
25168 
25169 			case OP_TURRET_SET_INACCURACY:
25170 				sexp_val = SEXP_TRUE;
25171 				sexp_turret_set_inaccuracy(node);
25172 				break;
25173 
25174 			case OP_SET_ARMOR_TYPE:
25175 				sexp_val = SEXP_TRUE;
25176 				sexp_set_armor_type(node);
25177 				break;
25178 
25179 			case OP_WEAPON_SET_DAMAGE_TYPE:
25180 				sexp_val = SEXP_TRUE;
25181 				sexp_weapon_set_damage_type(node);
25182 				break;
25183 
25184 			case OP_SHIP_SET_DAMAGE_TYPE:
25185 				sexp_val = SEXP_TRUE;
25186 				sexp_ship_set_damage_type(node);
25187 				break;
25188 
25189 			case OP_SHIP_SHOCKWAVE_SET_DAMAGE_TYPE:
25190 				sexp_val = SEXP_TRUE;
25191 				sexp_ship_shockwave_set_damage_type(node);
25192 				break;
25193 
25194 			case OP_FIELD_SET_DAMAGE_TYPE:
25195 				sexp_val = SEXP_TRUE;
25196 				sexp_field_set_damage_type(node);
25197 				break;
25198 
25199 			case OP_SHIP_TURRET_TARGET_ORDER:
25200 				sexp_val = SEXP_TRUE;
25201 				sexp_ship_turret_target_order(node);
25202 				break;
25203 
25204 			case OP_ADD_REMOVE_ESCORT:
25205 				sexp_val = SEXP_TRUE;
25206 				sexp_add_remove_escort(node);
25207 				break;
25208 
25209 			case OP_DAMAGED_ESCORT_LIST:
25210 				sexp_val = SEXP_TRUE;
25211 				sexp_damage_escort_list(node);
25212 				break;
25213 
25214 			case OP_DAMAGED_ESCORT_LIST_ALL:
25215 				sexp_val = SEXP_TRUE;
25216 				sexp_damage_escort_list_all(node);
25217 				break;
25218 
25219 			case OP_AWACS_SET_RADIUS:
25220 				sexp_val = SEXP_TRUE;
25221 				sexp_awacs_set_radius(node);
25222 				break;
25223 
25224 			case OP_PRIMITIVE_SENSORS_SET_RANGE:
25225 				sexp_primitive_sensors_set_range(node);
25226 				sexp_val = SEXP_TRUE;
25227 				break;
25228 
25229 			case OP_CAP_WAYPOINT_SPEED:
25230 				sexp_val = SEXP_TRUE;
25231 				sexp_cap_waypoint_speed(node);
25232 				break;
25233 
25234 			case OP_TURRET_TAGGED_ONLY_ALL:
25235 			case OP_TURRET_TAGGED_CLEAR_ALL:
25236 				sexp_turret_tagged_only_or_clear_all(node, op_num == OP_TURRET_TAGGED_ONLY_ALL);
25237 				sexp_val = SEXP_TRUE;
25238 				break;
25239 
25240 			case OP_SUBSYS_SET_RANDOM:
25241 				sexp_val = SEXP_TRUE;
25242 				sexp_subsys_set_random(node);
25243 				break;
25244 
25245 			case OP_SUPERNOVA_START:
25246 				sexp_val = SEXP_TRUE;
25247 				sexp_supernova_start(node);
25248 				break;
25249 
25250 			case OP_SUPERNOVA_STOP:
25251 				sexp_val = SEXP_TRUE;
25252 				sexp_supernova_stop(node);
25253 				break;
25254 
25255 			case OP_SET_MOTION_DEBRIS:
25256 				sexp_val = SEXP_TRUE;
25257 				sexp_set_motion_debris(node);
25258 				break;
25259 
25260 			case OP_WEAPON_RECHARGE_PCT:
25261 			case OP_SHIELD_RECHARGE_PCT:
25262 			case OP_ENGINE_RECHARGE_PCT:
25263 				sexp_val = sexp_gse_recharge_pct(node, op_num);
25264 				break;
25265 
25266 			case OP_GET_ETS_VALUE:
25267 				sexp_val = sexp_get_ets_value(node);
25268 				break;
25269 
25270 			case OP_SET_ETS_VALUES:
25271 				sexp_val = SEXP_TRUE;
25272 				sexp_set_ets_values(node);
25273 				break;
25274 
25275 			case OP_GET_POWER_OUTPUT:
25276 				sexp_val = sexp_get_power_output(node);
25277 				break;
25278 
25279 			case OP_SHIELD_QUAD_LOW:
25280 				sexp_val = sexp_shield_quad_low(node);
25281 				break;
25282 
25283 			case OP_PRIMARY_AMMO_PCT:
25284 			case OP_SECONDARY_AMMO_PCT:
25285 			case OP_GET_PRIMARY_AMMO:
25286 			case OP_GET_SECONDARY_AMMO:
25287 				sexp_val = sexp_get_ammo(node, false, op_num == OP_PRIMARY_AMMO_PCT || op_num == OP_GET_PRIMARY_AMMO, op_num == OP_PRIMARY_AMMO_PCT || op_num == OP_SECONDARY_AMMO_PCT);
25288 				break;
25289 
25290 			case OP_TURRET_GET_PRIMARY_AMMO:
25291 			case OP_TURRET_GET_SECONDARY_AMMO:
25292 				sexp_val = sexp_get_ammo(node, true, op_num == OP_TURRET_GET_PRIMARY_AMMO, false);
25293 				break;
25294 
25295 			// Karajorma
25296 			case OP_SET_PRIMARY_AMMO:
25297 			case OP_SET_SECONDARY_AMMO:
25298 				sexp_set_ammo(node, false, op_num == OP_SET_PRIMARY_AMMO);
25299 				sexp_val = SEXP_TRUE;
25300 				break;
25301 
25302 			case OP_TURRET_SET_PRIMARY_AMMO:
25303 			case OP_TURRET_SET_SECONDARY_AMMO:
25304 				sexp_set_ammo(node, true, op_num == OP_TURRET_SET_PRIMARY_AMMO);
25305 				sexp_val = SEXP_TRUE;
25306 				break;
25307 
25308 			// Karajorma
25309 			case OP_GET_NUM_COUNTERMEASURES:
25310 				sexp_val = sexp_get_countermeasures(node);
25311 				break;
25312 
25313 			case OP_IS_PRIMARY_SELECTED:
25314 			case OP_IS_SECONDARY_SELECTED:
25315 				sexp_val = sexp_is_weapon_selected(node, op_num == OP_IS_PRIMARY_SELECTED);
25316 				break;
25317 
25318 			// Goober5000
25319 			case OP_SET_SUPPORT_SHIP:
25320 				sexp_set_support_ship(node);
25321 				sexp_val = SEXP_TRUE;
25322 				break;
25323 
25324 			// Goober5000
25325 			case OP_SET_ARRIVAL_INFO:
25326 				sexp_set_arrival_info(node);
25327 				sexp_val = SEXP_TRUE;
25328 				break;
25329 
25330 			// Goober5000
25331 			case OP_SET_DEPARTURE_INFO:
25332 				sexp_set_departure_info(node);
25333 				sexp_val = SEXP_TRUE;
25334 				break;
25335 
25336 			// Goober5000
25337 			case OP_CHANGE_SHIP_CLASS:
25338 				sexp_change_ship_class(node);
25339 				sexp_val = SEXP_TRUE;
25340 				break;
25341 
25342 			// Goober5000
25343 			case OP_SHIP_COPY_DAMAGE:
25344 				sexp_ship_copy_damage(node);
25345 				sexp_val = SEXP_TRUE;
25346 				break;
25347 
25348 			//-Bobboau
25349 			case OP_ACTIVATE_GLOW_POINTS:
25350 			case OP_DEACTIVATE_GLOW_POINTS:
25351 				sexp_activate_deactivate_glow_points(node, op_num == OP_ACTIVATE_GLOW_POINTS);
25352 				sexp_val = SEXP_TRUE;
25353 				break;
25354 
25355 			//-Bobboau
25356 			case OP_ACTIVATE_GLOW_MAPS:
25357 			case OP_DEACTIVATE_GLOW_MAPS:
25358 				sexp_activate_deactivate_glow_maps(node, op_num == OP_ACTIVATE_GLOW_MAPS);
25359 				sexp_val = SEXP_TRUE;
25360 				break;
25361 
25362 			//-Bobboau
25363 			case OP_ACTIVATE_GLOW_POINT_BANK:
25364 			case OP_DEACTIVATE_GLOW_POINT_BANK:
25365 				sexp_activate_deactivate_glow_point_bank(node, op_num == OP_ACTIVATE_GLOW_POINT_BANK);
25366 				sexp_val = SEXP_TRUE;
25367 				break;
25368 
25369 			// taylor
25370 			case OP_SET_SKYBOX_MODEL:
25371 				sexp_set_skybox_model(node);
25372 				sexp_val = SEXP_TRUE;
25373 				break;
25374 
25375 			case OP_SET_SKYBOX_ORIENT:
25376 				sexp_set_skybox_orientation(node);
25377 				sexp_val = SEXP_TRUE;
25378 				break;
25379 
25380 			case OP_TURRET_TAGGED_SPECIFIC:
25381 			case OP_TURRET_TAGGED_CLEAR_SPECIFIC:
25382 				sexp_turret_tagged_or_clear_specific(node, op_num == OP_TURRET_TAGGED_SPECIFIC);
25383 				sexp_val = SEXP_TRUE;
25384 				break;
25385 
25386 			case OP_LOCK_ROTATING_SUBSYSTEM:
25387 			case OP_FREE_ROTATING_SUBSYSTEM:
25388 				sexp_set_subsys_rotation_lock_free(node, op_num == OP_LOCK_ROTATING_SUBSYSTEM);
25389 				sexp_val = SEXP_TRUE;
25390 				break;
25391 
25392 			case OP_REVERSE_ROTATING_SUBSYSTEM:
25393 				sexp_reverse_rotating_subsystem(node);
25394 				sexp_val = SEXP_TRUE;
25395 				break;
25396 
25397 			case OP_ROTATING_SUBSYS_SET_TURN_TIME:
25398 				sexp_rotating_subsys_set_turn_time(node);
25399 				sexp_val = SEXP_TRUE;
25400 				break;
25401 
25402 			case OP_TRIGGER_SUBMODEL_ANIMATION:
25403 				sexp_trigger_submodel_animation(node);
25404 				sexp_val = SEXP_TRUE;
25405 				break;
25406 
25407 			// Karajorma
25408 			case OP_SET_PRIMARY_WEAPON:
25409 			case OP_SET_SECONDARY_WEAPON:
25410 				sexp_set_weapon(node, op_num == OP_SET_PRIMARY_WEAPON);
25411 				sexp_val = SEXP_TRUE;
25412 				break;
25413 
25414 			// Karajorma
25415 			case OP_SET_NUM_COUNTERMEASURES:
25416 				sexp_set_countermeasures(node);
25417 				sexp_val = SEXP_TRUE;
25418 				break;
25419 
25420 			// Karajorma
25421 			case OP_LOCK_PRIMARY_WEAPON:
25422 			case OP_UNLOCK_PRIMARY_WEAPON:
25423 				sexp_deal_with_primary_lock(node, op_num == OP_LOCK_PRIMARY_WEAPON);
25424 				sexp_val = SEXP_TRUE;
25425 				break;
25426 
25427 			case OP_LOCK_SECONDARY_WEAPON:
25428 			case OP_UNLOCK_SECONDARY_WEAPON:
25429 				sexp_deal_with_secondary_lock(node, op_num == OP_LOCK_SECONDARY_WEAPON);
25430 				sexp_val = SEXP_TRUE;
25431 				break;
25432 
25433 			// KeldorKatarn
25434 			case OP_LOCK_AFTERBURNER:
25435 			case OP_UNLOCK_AFTERBURNER:
25436 				sexp_deal_with_afterburner_lock(node, op_num == OP_LOCK_AFTERBURNER);
25437 				sexp_val = SEXP_TRUE;
25438 				break;
25439 
25440 			case OP_SET_AFTERBURNER_ENERGY:
25441 			case OP_SET_WEAPON_ENERGY:
25442 			case OP_SET_SHIELD_ENERGY:
25443 				sexp_set_energy_pct(node, op_num);
25444 				sexp_val = SEXP_TRUE;
25445 				break;
25446 
25447 			case OP_SET_AMBIENT_LIGHT:
25448 				sexp_set_ambient_light(node);
25449 				sexp_val = SEXP_TRUE;
25450 				break;
25451 
25452 			case OP_SET_POST_EFFECT:
25453 				sexp_set_post_effect(node);
25454 				sexp_val = SEXP_TRUE;
25455 				break;
25456 
25457 			case OP_RESET_POST_EFFECTS:
25458 				sexp_reset_post_effects();
25459 				sexp_val = SEXP_TRUE;
25460 				break;
25461 
25462 			case OP_PRIMARY_FIRED_SINCE:
25463 			case OP_SECONDARY_FIRED_SINCE:
25464 				sexp_val = sexp_weapon_fired_delay(node, op_num);
25465 				break;
25466 
25467 			case OP_HAS_PRIMARY_WEAPON:
25468 			case OP_HAS_SECONDARY_WEAPON:
25469 				sexp_val = sexp_has_weapon(node, op_num);
25470 				break;
25471 
25472 			case OP_DIRECTIVE_VALUE:
25473 				sexp_val = sexp_directive_value(node);
25474 				break;
25475 
25476 			case OP_GET_HOTKEY:
25477 				sexp_val = sexp_get_hotkey(node);
25478 				break;
25479 
25480 			case OP_CHANGE_SUBSYSTEM_NAME:
25481 				sexp_change_subsystem_name(node);
25482 				sexp_val = SEXP_TRUE;
25483 				break;
25484 
25485 			case OP_NUM_SHIPS_IN_BATTLE:	// phreak
25486 				sexp_val = sexp_num_ships_in_battle(node);
25487 				break;
25488 
25489 			// Karajorma
25490 			case OP_NUM_SHIPS_IN_WING:
25491 				sexp_val=sexp_num_ships_in_wing(node);
25492 				break;
25493 
25494 			case OP_CURRENT_SPEED:
25495 				sexp_val = sexp_current_speed(node);
25496 				break;
25497 
25498 			case OP_NAV_IS_VISITED: //kazan
25499 				sexp_val = is_nav_visited(node);
25500 				break;
25501 
25502 			case OP_NAV_DISTANCE: //kazan
25503 				sexp_val = distance_to_nav(node);
25504 				break;
25505 
25506 			case OP_NAV_ADD_WAYPOINT: //kazan
25507 				sexp_val = SEXP_TRUE;
25508 				add_nav_waypoint(node);
25509 				break;
25510 
25511 			case OP_NAV_ADD_SHIP: //kazan
25512 				sexp_val = SEXP_TRUE;
25513 				add_nav_ship(node);
25514 				break;
25515 
25516 			case OP_NAV_DEL: //kazan
25517 				sexp_val = SEXP_TRUE;
25518 				del_nav(node);
25519 				break;
25520 
25521 			case OP_NAV_HIDE: //kazan
25522 			case OP_NAV_UNHIDE: //kazan
25523 				sexp_val = SEXP_TRUE;
25524 				hide_unhide_nav(node, op_num == OP_NAV_HIDE);
25525 				break;
25526 
25527 			case OP_NAV_RESTRICT: //kazan
25528 			case OP_NAV_UNRESTRICT: //kazan
25529 				sexp_val = SEXP_TRUE;
25530 				restrict_unrestrict_nav(node, op_num == OP_NAV_RESTRICT);
25531 				break;
25532 
25533 			case OP_NAV_SET_VISITED: //kazan
25534 			case OP_NAV_UNSET_VISITED: //kazan
25535 				sexp_val = SEXP_TRUE;
25536 				set_unset_nav_visited(node, op_num == OP_NAV_SET_VISITED);
25537 				break;
25538 
25539 			case OP_NAV_SET_CARRY: //kazan
25540 			case OP_NAV_UNSET_CARRY: //kazan
25541 				sexp_val = SEXP_TRUE;
25542 				set_unset_nav_carry_status(node, op_num == OP_NAV_SET_CARRY);
25543 				break;
25544 
25545 			case OP_NAV_SET_NEEDSLINK:
25546 			case OP_NAV_UNSET_NEEDSLINK:
25547 				sexp_val = SEXP_TRUE;
25548 				set_unset_nav_needslink(node, op_num == OP_NAV_SET_NEEDSLINK);
25549 				break;
25550 
25551 			case OP_NAV_ISLINKED:
25552 				sexp_val = is_nav_linked(node);
25553 				break;
25554 
25555 			case OP_NAV_USEAP:
25556 				sexp_val = SEXP_TRUE;
25557 				set_use_ap(node);
25558 				break;
25559 
25560 			case OP_NAV_USECINEMATICS:
25561 				sexp_val = SEXP_TRUE;
25562 				set_use_ap_cinematics(node);
25563 				break;
25564 
25565 			//Talon1024
25566 			case OP_NAV_SELECT:
25567 			case OP_NAV_UNSELECT:
25568 				sexp_val = SEXP_TRUE;
25569 				select_unselect_nav(node, op_num == OP_NAV_SELECT);
25570 				break;
25571 
25572 			case OP_SCRAMBLE_MESSAGES:
25573 			case OP_UNSCRAMBLE_MESSAGES:
25574 				sexp_scramble_messages(node, op_num == OP_SCRAMBLE_MESSAGES );
25575 				sexp_val = SEXP_TRUE;
25576 				break;
25577 
25578 			case OP_CUTSCENES_SET_CUTSCENE_BARS:
25579 			case OP_CUTSCENES_UNSET_CUTSCENE_BARS:
25580 				sexp_val = SEXP_TRUE;
25581 				sexp_toggle_cutscene_bars(node, op_num == OP_CUTSCENES_SET_CUTSCENE_BARS);
25582 				break;
25583 
25584 			case OP_CUTSCENES_FADE_IN:
25585 			case OP_CUTSCENES_FADE_OUT:
25586 				sexp_val = SEXP_TRUE;
25587 				sexp_fade(node, op_num == OP_CUTSCENES_FADE_IN);
25588 				break;
25589 
25590 			case OP_CUTSCENES_SET_CAMERA:
25591 				sexp_val = SEXP_TRUE;
25592 				sexp_set_camera(node);
25593 				break;
25594 			case OP_CUTSCENES_SET_CAMERA_FACING:
25595 				sexp_val = SEXP_TRUE;
25596 				sexp_set_camera_facing(node);
25597 				break;
25598 			case OP_CUTSCENES_SET_CAMERA_FACING_OBJECT:
25599 				sexp_val = SEXP_TRUE;
25600 				sexp_set_camera_facing_object(node);
25601 				break;
25602 			case OP_CUTSCENES_SET_CAMERA_FOV:
25603 				sexp_val = SEXP_TRUE;
25604 				sexp_set_camera_fov(node);
25605 				break;
25606 			case OP_CUTSCENES_SET_CAMERA_HOST:
25607 				sexp_val = SEXP_TRUE;
25608 				sexp_set_camera_host(node);
25609 				break;
25610 			case OP_CUTSCENES_SET_CAMERA_POSITION:
25611 				sexp_val = SEXP_TRUE;
25612 				sexp_set_camera_position(node);
25613 				break;
25614 			case OP_CUTSCENES_SET_CAMERA_ROTATION:
25615 				sexp_val = SEXP_TRUE;
25616 				sexp_set_camera_rotation(node);
25617 				break;
25618 			case OP_CUTSCENES_SET_CAMERA_TARGET:
25619 				sexp_val = SEXP_TRUE;
25620 				sexp_set_camera_target(node);
25621 				break;
25622 			case OP_CUTSCENES_SET_FOV:
25623 				sexp_val = SEXP_TRUE;
25624 				sexp_set_fov(node);
25625 				break;
25626 			case OP_CUTSCENES_GET_FOV:
25627 				sexp_val = sexp_get_fov();
25628 				break;
25629 			case OP_CUTSCENES_RESET_FOV:
25630 				sexp_val = SEXP_TRUE;
25631 				sexp_reset_fov();
25632 				break;
25633 			case OP_CUTSCENES_RESET_CAMERA:
25634 				sexp_val = SEXP_TRUE;
25635 				sexp_reset_camera(node);
25636 				break;
25637 			case OP_CUTSCENES_SHOW_SUBTITLE:
25638 				sexp_val = SEXP_TRUE;
25639 				sexp_show_subtitle(node);
25640 				break;
25641 			case OP_CUTSCENES_SHOW_SUBTITLE_TEXT:
25642 				sexp_val = SEXP_TRUE;
25643 				sexp_show_subtitle_text(node);
25644 				break;
25645 			case OP_CUTSCENES_SHOW_SUBTITLE_IMAGE:
25646 				sexp_val = SEXP_TRUE;
25647 				sexp_show_subtitle_image(node);
25648 				break;
25649 			case OP_CUTSCENES_SET_TIME_COMPRESSION:
25650 				sexp_val = SEXP_TRUE;
25651 				sexp_set_time_compression(node);
25652 				break;
25653 			case OP_CUTSCENES_RESET_TIME_COMPRESSION:
25654 				sexp_val = SEXP_TRUE;
25655 				sexp_reset_time_compression();
25656 				break;
25657 			case OP_CUTSCENES_FORCE_PERSPECTIVE:
25658 				sexp_val = SEXP_TRUE;
25659 				sexp_force_perspective(node);
25660 				break;
25661 
25662 			case OP_SET_CAMERA_SHUDDER:
25663 				sexp_val = SEXP_TRUE;
25664 				sexp_set_camera_shudder(node);
25665 				break;
25666 
25667 			case OP_JUMP_NODE_SET_JUMPNODE_NAME: //CommanderDJ
25668 				sexp_val = SEXP_TRUE;
25669 				sexp_set_jumpnode_name(node);
25670 				break;
25671 
25672 			case OP_JUMP_NODE_SET_JUMPNODE_COLOR:
25673 				sexp_val = SEXP_TRUE;
25674 				sexp_set_jumpnode_color(node);
25675 				break;
25676 			case OP_JUMP_NODE_SET_JUMPNODE_MODEL:
25677 				sexp_val = SEXP_TRUE;
25678 				sexp_set_jumpnode_model(node);
25679 				break;
25680 			case OP_JUMP_NODE_SHOW_JUMPNODE:
25681 			case OP_JUMP_NODE_HIDE_JUMPNODE:
25682 				sexp_show_hide_jumpnode(node, op_num == OP_JUMP_NODE_SHOW_JUMPNODE);
25683 				sexp_val = SEXP_TRUE;
25684 				break;
25685 
25686 			case OP_SCRIPT_EVAL_NUM:
25687 				sexp_val = sexp_script_eval(node, OPR_NUMBER);
25688 				break;
25689 
25690 			case OP_SCRIPT_EVAL_STRING:
25691 				sexp_val = sexp_script_eval(node, OPR_STRING);
25692 				break;
25693 
25694 			case OP_SCRIPT_EVAL:
25695 				sexp_val = sexp_script_eval(node, OPR_NULL);
25696 				break;
25697 
25698 			case OP_SCRIPT_EVAL_BLOCK:
25699 				sexp_val = sexp_script_eval(node, OPR_NULL, true);
25700 				break;
25701 
25702 			case OP_SCRIPT_EVAL_MULTI:
25703 				sexp_script_eval_multi(node);
25704 				sexp_val = SEXP_TRUE;
25705 				break;
25706 
25707 			case OP_CHANGE_IFF_COLOR:
25708 				sexp_change_iff_color(node);
25709 				sexp_val = SEXP_TRUE;
25710 				break;
25711 
25712 			case OP_DISABLE_ETS:
25713 			case OP_ENABLE_ETS:
25714 				sexp_disable_ets(node, (op_num == OP_DISABLE_ETS));
25715 				sexp_val = SEXP_TRUE;
25716 				break;
25717 
25718 			case OP_FORCE_GLIDE:
25719 				sexp_val = SEXP_TRUE;
25720 				sexp_force_glide(node);
25721 				break;
25722 
25723 			case OP_HUD_SET_DIRECTIVE:
25724 				sexp_val = SEXP_TRUE;
25725 				sexp_hud_set_directive(node);
25726 				break;
25727 
25728 			case OP_HUD_GAUGE_SET_ACTIVE:
25729 				sexp_val = SEXP_TRUE;
25730 				sexp_hud_gauge_set_active(node);
25731 				break;
25732 
25733 			case OP_HUD_SET_CUSTOM_GAUGE_ACTIVE:
25734 				sexp_val = SEXP_TRUE;
25735 				sexp_hud_set_custom_gauge_active(node);
25736 				break;
25737 
25738 			case OP_HUD_CLEAR_MESSAGES:
25739 				sexp_val = SEXP_TRUE;
25740 				sexp_hud_clear_messages();
25741 				break;
25742 
25743 			case OP_HUD_ACTIVATE_GAUGE_TYPE:
25744 				sexp_val = SEXP_TRUE;
25745 				sexp_hud_activate_gauge_type(node);
25746 				break;
25747 
25748 			case OP_HUD_SET_BUILTIN_GAUGE_ACTIVE:
25749 				sexp_val = SEXP_TRUE;
25750 				sexp_hud_set_builtin_gauge_active(node);
25751 				break;
25752 
25753 			case OP_ADD_TO_COLGROUP:
25754 			case OP_REMOVE_FROM_COLGROUP:
25755 				sexp_manipulate_colgroup(node, op_num == OP_ADD_TO_COLGROUP);
25756 				sexp_val = SEXP_TRUE;
25757 				break;
25758 
25759 			case OP_ADD_TO_COLGROUP2:
25760 			case OP_REMOVE_FROM_COLGROUP2:
25761 				sexp_manipulate_colgroup2(node, op_num == OP_ADD_TO_COLGROUP2);
25762 				sexp_val = SEXP_TRUE;
25763 				break;
25764 
25765 			case OP_GET_COLGROUP_ID:
25766 				sexp_val = sexp_get_colgroup(node);
25767 				break;
25768 
25769 			case OP_SHIP_EFFECT:
25770 				sexp_val = SEXP_TRUE;
25771 				sexp_ship_effect(node);
25772 				break;
25773 
25774 			case OP_CLEAR_SUBTITLES:
25775 				sexp_val = SEXP_TRUE;
25776 				sexp_clear_subtitles();
25777 				break;
25778 
25779 			case OP_SET_THRUSTERS:
25780 				sexp_val = SEXP_TRUE;
25781 				sexp_set_thrusters(node);
25782 				break;
25783 
25784 			case OP_CHANGE_TEAM_COLOR:
25785 				sexp_val = SEXP_TRUE;
25786 				sexp_change_team_color(node);
25787 				break;
25788 
25789 			case OP_CALL_SSM_STRIKE:
25790 				sexp_val = SEXP_TRUE;
25791 				sexp_call_ssm_strike(node);
25792 				break;
25793 
25794 			case OP_PLAYER_IS_CHEATING_BASTARD:
25795 				sexp_val = sexp_player_is_cheating_bastard();
25796 				break;
25797 
25798 			case OP_IS_LANGUAGE:
25799 				sexp_val = sexp_is_language(node);
25800 				break;
25801 
25802 			case OP_IS_IN_TURRET_FOV:
25803 				sexp_val = sexp_is_in_turret_fov(node);
25804 				break;
25805 
25806 			case OP_REPLACE_TEXTURE:
25807 				sexp_val = SEXP_TRUE;
25808 				sexp_replace_texture(node);
25809 				break;
25810 
25811 			default:{
25812 				// Check if we have a dynamic SEXP with this operator and if there is, execute that
25813 				auto dynamicSEXP = sexp::get_dynamic_sexp(op_num);
25814 				if (dynamicSEXP != nullptr) {
25815 					sexp_val = dynamicSEXP->execute(node);
25816 				} else {
25817 					Error(LOCATION, "Looking for SEXP operator, found '%s'.\n", CTEXT(cur_node));
25818 				}
25819 			}
25820 		}
25821 
25822 		if (Log_event) {
25823 			add_to_event_log_buffer(get_operator_index(cur_node), sexp_val);
25824 		}
25825 
25826 		Assert(!Current_sexp_operator.empty());
25827 		Current_sexp_operator.pop_back();
25828 
25829 		Assertion(sexp_val != UNINITIALIZED, "SEXP %s didn't return a value!", CTEXT(cur_node));
25830 
25831 		// if we haven't returned, check the sexp value of the sexpression evaluation.  A special
25832 		// value of known true or known false means that we should set the sexp.value field for
25833 		// short circuit eval.
25834 		if (sexp_val == SEXP_KNOWN_TRUE) {
25835 			Sexp_nodes[cur_node].value = SEXP_KNOWN_TRUE;
25836 			return SEXP_TRUE;
25837 		}
25838 
25839 		if (sexp_val == SEXP_KNOWN_FALSE) {
25840 			Sexp_nodes[cur_node].value = SEXP_KNOWN_FALSE;
25841 			return SEXP_FALSE;
25842 		}
25843 
25844 		if ( sexp_val == SEXP_NAN ) {
25845 			Sexp_nodes[cur_node].value = SEXP_NAN;			// not a number values are false I would suspect
25846 			return SEXP_FALSE;
25847 		}
25848 
25849 		if ( sexp_val == SEXP_NAN_FOREVER ) {
25850 			Sexp_nodes[cur_node].value = SEXP_NAN_FOREVER;
25851 			// Goober5000 changed from sexp_val to SEXP_FALSE on 2/21/2006 in accordance with above comment
25852 			// NOTE: we return false rather than known-false to match the SEXP_KNOWN_FALSE case above
25853 			return SEXP_FALSE;
25854 		}
25855 
25856 		if ( sexp_val == SEXP_CANT_EVAL ) {
25857 			Sexp_nodes[cur_node].value = SEXP_CANT_EVAL;
25858 			Sexp_useful_number = 0;  // indicate sexp isn't current yet
25859 			return SEXP_FALSE;
25860 		}
25861 
25862 		if ( Sexp_nodes[cur_node].value == SEXP_NAN ) {	// if we had a nan, but now don't, reset the value
25863 			Sexp_nodes[cur_node].value = SEXP_UNKNOWN;
25864 			return sexp_val;
25865 		}
25866 
25867 #ifndef NDEBUG
25868 		// now, reconcile positive and negative - Goober5000
25869 		if (sexp_val < 0 && sexp_val > SEXP_UNLIKELY_RETURN_VALUE_BOUND)
25870 		{
25871 			int parent_node = find_parent_operator(cur_node);
25872 
25873 			// if the SEXP has no parent, the point is moot
25874 			if (parent_node >= 0)
25875 			{
25876 				int arg_num = find_argnum(parent_node, cur_node);
25877 				Assertion(arg_num >= 0, "Error finding sexp argument.  The SEXP is not listed among its parent's children.");
25878 
25879 				// if we need a positive value, make it positive
25880 				if (query_operator_argument_type(get_operator_index(parent_node), arg_num) == OPF_POSITIVE)
25881 				{
25882 					Warning(LOCATION, "Parent node %s, argument %d (value %d) is negative, but is required to be positive!", Sexp_nodes[parent_node].text, arg_num + 1, sexp_val);
25883 					sexp_val *= -1;
25884 				}
25885 			}
25886 		}
25887 #endif
25888 
25889 		if ( sexp_val ){
25890 			Sexp_nodes[cur_node].value = SEXP_TRUE;
25891 		} else {
25892 			Sexp_nodes[cur_node].value = SEXP_FALSE;
25893 		}
25894 
25895 		return sexp_val;
25896 	}
25897 }
25898 
25899 /**
25900  * Only runs on the client machines not the server. Evaluates the contents of a SEXP packet and calls the relevent multi_sexp_x
25901  * function(s).
25902  */
multi_sexp_eval()25903 void multi_sexp_eval()
25904 {
25905 	int op_num;
25906 
25907 	Assert (MULTIPLAYER_CLIENT);
25908 
25909 	while (Current_sexp_network_packet.sexp_bytes_left > 0) {
25910 		op_num = Current_sexp_network_packet.get_next_operator();
25911 
25912 		Assert (Current_sexp_network_packet.sexp_bytes_left);
25913 
25914 		if (op_num < 0) {
25915 			Warning(LOCATION, "Received invalid operator number from host in multi_sexp_eval(). Entire packet may be corrupt. Discarding packet");
25916 			return;
25917 		}
25918 
25919 		switch(op_num) {
25920 
25921 			case OP_CHANGE_SOUNDTRACK:
25922 				multi_sexp_change_soundtrack();
25923 				break;
25924 
25925 			case OP_SET_PERSONA:
25926 				multi_sexp_set_persona();
25927 				break;
25928 
25929 			case OP_CHANGE_SUBSYSTEM_NAME:
25930 				multi_sexp_change_subsystem_name();
25931 				break;
25932 
25933 			case OP_SHIP_SUBSYS_NO_REPLACE:
25934 				multi_sexp_deal_with_subsys_flag(Ship::Subsystem_Flags::No_replace);
25935 				break;
25936 			case OP_SHIP_SUBSYS_NO_LIVE_DEBRIS:
25937 				multi_sexp_deal_with_subsys_flag(Ship::Subsystem_Flags::No_live_debris);
25938 				break;
25939 			case OP_SHIP_SUBSYS_VANISHED:
25940 				multi_sexp_deal_with_subsys_flag(Ship::Subsystem_Flags::Vanished);
25941 				break;
25942 			case OP_SHIP_SUBSYS_IGNORE_IF_DEAD:
25943 				multi_sexp_deal_with_subsys_flag(Ship::Subsystem_Flags::Missiles_ignore_if_dead);
25944 				break;
25945 			case OP_SHIP_SUBSYS_TARGETABLE:
25946 			case OP_SHIP_SUBSYS_UNTARGETABLE:
25947 				multi_sexp_deal_with_subsys_flag(Ship::Subsystem_Flags::Untargetable);
25948 				break;
25949 
25950 			case OP_SHIP_CHANGE_ALT_NAME:
25951 			case OP_SHIP_CHANGE_CALLSIGN:
25952 				multi_sexp_ship_change_alt_name_or_callsign(op_num == OP_SHIP_CHANGE_ALT_NAME);
25953 				break;
25954 
25955 			case OP_SET_RESPAWNS:
25956 				multi_sexp_set_respawns();
25957 				break;
25958 
25959 			case OP_CLEAR_WEAPONS:
25960 			case OP_CLEAR_DEBRIS:
25961 				multi_sexp_clear_weapons_or_debris(op_num);
25962 				break;
25963 
25964 			case OP_CHANGE_SHIP_CLASS:
25965 				multi_sexp_change_ship_class();
25966 				break;
25967 
25968 			case OP_PLAY_SOUND_FROM_TABLE:
25969 				multi_sexp_play_sound_from_table();
25970 				break;
25971 
25972 			case OP_PLAY_SOUND_FROM_FILE:
25973 				multi_sexp_play_sound_from_file();
25974 				break;
25975 
25976 			case OP_CLOSE_SOUND_FROM_FILE:
25977 				multi_sexp_close_sound_from_file();
25978 				break;
25979 
25980 			case OP_PAUSE_SOUND_FROM_FILE:
25981 				multi_sexp_pause_sound_from_file();
25982 				break;
25983 
25984 			case OP_SHIP_BOMB_TARGETABLE:
25985 			case OP_SHIP_BOMB_UNTARGETABLE:
25986 			case OP_SHIP_INVISIBLE:
25987 			case OP_SHIP_VISIBLE:
25988 			case OP_SHIP_STEALTHY:
25989 			case OP_SHIP_UNSTEALTHY:
25990 			case OP_FRIENDLY_STEALTH_INVISIBLE:
25991 			case OP_FRIENDLY_STEALTH_VISIBLE:
25992 			case OP_LOCK_AFTERBURNER:
25993 			case OP_UNLOCK_AFTERBURNER:
25994 			case OP_LOCK_PRIMARY_WEAPON:
25995 			case OP_UNLOCK_PRIMARY_WEAPON:
25996 			case OP_LOCK_SECONDARY_WEAPON:
25997 			case OP_UNLOCK_SECONDARY_WEAPON:
25998 			case OP_SHIELDS_ON:
25999 			case OP_SHIELDS_OFF:
26000 				multi_sexp_deal_with_ship_flag();
26001 				break;
26002 
26003 			case OP_ALTER_SHIP_FLAG:
26004 				multi_sexp_alter_ship_flag();
26005 				break;
26006 
26007 			case OP_SET_AFTERBURNER_ENERGY:
26008 				multi_sexp_set_energy_pct();
26009 				break;
26010 
26011 			case OP_SET_AMBIENT_LIGHT:
26012 				multi_sexp_set_ambient_light();
26013 				break;
26014 
26015 			case OP_MODIFY_VARIABLE:
26016 				multi_sexp_modify_variable();
26017 				break;
26018 
26019 			case OP_NAV_ADD_WAYPOINT:
26020 				multi_sexp_add_nav_waypoint();
26021 				break;
26022 
26023 			case OP_CUTSCENES_FADE_IN:
26024 			case OP_CUTSCENES_FADE_OUT:
26025 				multi_sexp_fade(op_num == OP_CUTSCENES_FADE_IN);
26026 				break;
26027 
26028 			case OP_NAV_ADD_SHIP:
26029 				multi_add_nav_ship();
26030 				break;
26031 
26032 			case OP_NAV_DEL:
26033 				multi_del_nav();
26034 				break;
26035 
26036 			case OP_ADD_REMOVE_ESCORT:
26037 				multi_sexp_add_remove_escort();
26038 				break;
26039 
26040 			case OP_CUTSCENES_SHOW_SUBTITLE_TEXT:
26041 				 multi_sexp_show_subtitle_text();
26042 				 break;
26043 
26044 			case OP_CUTSCENES_SHOW_SUBTITLE_IMAGE:
26045 				 multi_sexp_show_subtitle_image();
26046 				 break;
26047 
26048 			case OP_TRAINING_MSG:
26049 				multi_sexp_send_training_message();
26050 				break;
26051 
26052 			case OP_HUD_DISABLE:
26053 				multi_sexp_hud_disable();
26054 				break;
26055 
26056 			case OP_HUD_DISABLE_EXCEPT_MESSAGES:
26057 				multi_sexp_hud_disable_except_messages();
26058 				break;
26059 
26060 			case OP_FLASH_HUD_GAUGE:
26061 				multi_sexp_flash_hud_gauge();
26062 				break;
26063 
26064 			case OP_HUD_DISPLAY_GAUGE:
26065 				multi_sexp_hud_display_gauge();
26066 				break;
26067 
26068 			case OP_CUTSCENES_SET_CUTSCENE_BARS:
26069 			case OP_CUTSCENES_UNSET_CUTSCENE_BARS:
26070 				multi_sexp_toggle_cutscene_bars(op_num == OP_CUTSCENES_SET_CUTSCENE_BARS );
26071 				break;
26072 
26073 			case OP_CUTSCENES_SET_CAMERA_FACING:
26074 				multi_sexp_set_camera_facing();
26075 				break;
26076 
26077 			case OP_CUTSCENES_SET_CAMERA_FACING_OBJECT:
26078 				multi_sexp_set_camera_facing_object();
26079 				break;
26080 
26081 			case OP_CUTSCENES_SET_CAMERA_TARGET :
26082 				multi_sexp_set_camera_target();
26083 				break;
26084 
26085 			case OP_CUTSCENES_SET_CAMERA_ROTATION:
26086 				multi_sexp_set_camera_rotation();
26087 				break;
26088 
26089 			case OP_CUTSCENES_SET_CAMERA_FOV:
26090 				multi_sexp_set_camera_fov();
26091 				break;
26092 
26093 			case OP_CUTSCENES_SET_CAMERA_POSITION:
26094 				multi_sexp_set_camera_position();
26095 				break;
26096 
26097 			case OP_SET_CAMERA_SHUDDER:
26098 				multi_sexp_set_camera_shudder();
26099 				break;
26100 
26101 			case OP_CUTSCENES_RESET_CAMERA:
26102 				multi_sexp_reset_camera();
26103 				break;
26104 
26105 			case OP_CUTSCENES_SET_FOV:
26106 				multi_sexp_set_fov();
26107 				break;
26108 
26109 			case OP_CUTSCENES_RESET_FOV:
26110 				multi_sexp_reset_fov();
26111 				break;
26112 
26113 			case OP_JUMP_NODE_SET_JUMPNODE_NAME:
26114 				multi_sexp_set_jumpnode_name();
26115 				break;
26116 
26117 			case OP_IGNORE_KEY:
26118 				multi_sexp_ignore_key();
26119 				break;
26120 
26121 			case OP_JUMP_NODE_SET_JUMPNODE_COLOR:
26122 				multi_sexp_set_jumpnode_color();
26123 				break;
26124 
26125 			case OP_JUMP_NODE_SET_JUMPNODE_MODEL:
26126 				multi_sexp_set_jumpnode_model();
26127 				break;
26128 
26129 			case OP_JUMP_NODE_SHOW_JUMPNODE:
26130 			case OP_JUMP_NODE_HIDE_JUMPNODE:
26131 				multi_sexp_show_hide_jumpnode(op_num == OP_JUMP_NODE_SHOW_JUMPNODE);
26132 				break;
26133 
26134 			case OP_CLEAR_SUBTITLES:
26135 				multi_sexp_clear_subtitles();
26136 				break;
26137 
26138 			case OP_SET_OBJECT_SPEED_X:
26139 			case OP_SET_OBJECT_SPEED_Y:
26140 			case OP_SET_OBJECT_SPEED_Z:
26141 				multi_sexp_set_object_speed();
26142 				break;
26143 
26144 			case OP_SET_OBJECT_POSITION:
26145 				multi_sexp_set_object_position();
26146 				break;
26147 
26148 			case OP_CHANGE_TEAM_COLOR:
26149 				multi_sexp_change_team_color();
26150 				break;
26151 
26152 			case OP_HUD_SET_MAX_TARGETING_RANGE:
26153 				multi_sexp_hud_set_max_targeting_range();
26154 				break;
26155 
26156 			case OP_CUTSCENES_SET_TIME_COMPRESSION:
26157 				multi_sexp_set_time_compression();
26158 				break;
26159 
26160 			case OP_CUTSCENES_RESET_TIME_COMPRESSION:
26161 				multi_sexp_reset_time_compression();
26162 				break;
26163 
26164 			case OP_SET_ETS_VALUES:
26165 				multi_sexp_set_ets_values();
26166 				break;
26167 
26168 			case OP_SCRIPT_EVAL_MULTI:
26169 				multi_sexp_script_eval_multi();
26170 				break;
26171 
26172 			case OP_SET_PRIMARY_AMMO:
26173 			case OP_SET_SECONDARY_AMMO:
26174 				multi_sexp_set_ammo(false, op_num == OP_SET_PRIMARY_AMMO);
26175 				break;
26176 
26177 			case OP_TURRET_SET_PRIMARY_AMMO:
26178 			case OP_TURRET_SET_SECONDARY_AMMO:
26179 				multi_sexp_set_ammo(true, op_num == OP_TURRET_SET_PRIMARY_AMMO);
26180 				break;
26181 
26182 			case OP_SET_PRIMARY_WEAPON:
26183 			case OP_SET_SECONDARY_WEAPON:
26184 				multi_sexp_set_weapon();
26185 				break;
26186 
26187 			case OP_SET_NUM_COUNTERMEASURES:
26188 				multi_sexp_set_countermeasures();
26189 				break;
26190 
26191 			case OP_CHANGE_AI_CLASS:
26192 				multi_sexp_change_ai_class();
26193 				break;
26194 
26195 			case OP_CHANGE_IFF:
26196 				multi_sexp_change_iff();
26197 				break;
26198 
26199 			case OP_CHANGE_IFF_COLOR:
26200 				multi_sexp_change_iff_color();
26201 				break;
26202 
26203 			case OP_DESTROY_INSTANTLY:
26204 				multi_sexp_destroy_instantly();
26205 				break;
26206 
26207 			case OP_DESTROY_SUBSYS_INSTANTLY:
26208 				multi_sexp_destroy_subsys_instantly();
26209 				break;
26210 
26211 			case OP_RED_ALERT:
26212 				multi_sexp_red_alert();
26213 				break;
26214 
26215 			// bad sexp in the packet
26216 			default:
26217 				// probably just a version error where the host supports a SEXP but a client does not
26218 				if (Current_sexp_network_packet.sexp_discard_operator()) {
26219 					Warning(LOCATION, "Received invalid SEXP operator number from host. Operator number %d is not supported by this version.", op_num);
26220 				}
26221 				// a more major problem
26222 				else {
26223 					Warning(LOCATION, "Received invalid SEXP packet from host. Function involving operator %d lacks termination. Entire packet may be corrupt. Discarding remaining packet", op_num);
26224 					return;
26225 				}
26226 		}
26227 
26228 		Current_sexp_network_packet.finished_callback();
26229 	}
26230 }
26231 
26232 //	get_sexp_main reads and builds the internal representation for a
26233 //	symbolic expression.
26234 //	On entry:
26235 //		Mp points at first character in expression.
26236 //	The symbolic expression is built in Sexp_nodes beginning at node 0.
get_sexp_main()26237 int get_sexp_main()
26238 {
26239 	int	start_node, op;
26240 
26241 	ignore_white_space();
26242 
26243 	if (*Mp != '(')
26244 	{
26245 		char buf[512];
26246 		strncpy(buf, Mp, 512);
26247 		if (buf[511] != '\0')
26248 			strcpy(&buf[506], "[...]");
26249 
26250 		Error(LOCATION, "Expected to find an open parenthesis in the following sexp:\n%s", buf);
26251 		return -1;
26252 	}
26253 
26254 	Mp++;
26255 	start_node = get_sexp();
26256 
26257 	// only need to check syntax if we have a operator
26258 	if (!Fred_running && (start_node >= 0))
26259 	{
26260 		op = get_operator_index(start_node);
26261 		if (op < 0)
26262 		{
26263 			Error(LOCATION, "Can't find operator %s in operator list!\n", CTEXT(start_node));
26264 			return -1;
26265 		}
26266 	}
26267 
26268 	return start_node;
26269 }
26270 
run_sexp(const char * sexpression,bool run_eval_num,bool * is_nan_or_nan_forever)26271 int run_sexp(const char* sexpression, bool run_eval_num, bool *is_nan_or_nan_forever)
26272 {
26273 	char* oldMp = Mp;
26274 	int n, sexp_val = UNINITIALIZED;
26275 	char buf[8192];
26276 
26277 	strcpy_s(buf, sexpression);
26278 
26279 	// HACK: ! -> "
26280 	for (auto ch = buf; *ch; ++ch)
26281 	{
26282 		// convert single ! to ", but don't convert !!
26283 		if (*ch == '!')
26284 		{
26285 			if (*(ch + 1) == '!')
26286 				++ch;
26287 			else
26288 				*ch = '\"';
26289 		}
26290 	}
26291 
26292 	// !! -> !
26293 	consolidate_double_characters(buf, '!');
26294 
26295 	Mp = buf;
26296 
26297 	n = get_sexp_main();
26298 	if (n != -1)
26299 	{
26300 		if (run_eval_num)
26301 		{
26302 			// if this sexp node is an operator, put it in another node so that CAR(node) will find it
26303 			if (get_operator_index(n) >= 0)
26304 				n = alloc_sexp("", 1, -1, n, -1);
26305 
26306 			bool is_nan, is_nan_forever;
26307 			sexp_val = eval_num(n, is_nan, is_nan_forever);
26308 			if (is_nan_or_nan_forever != nullptr)
26309 				*is_nan_or_nan_forever = (is_nan || is_nan_forever);
26310 		}
26311 		else
26312 			sexp_val = eval_sexp(n);
26313 
26314 		free_sexp2(n);
26315 	}
26316 
26317 	Mp = oldMp;
26318 
26319 	return sexp_val;
26320 }
26321 
26322 DCF(sexpc, "Always runs the given sexp command (Warning! There is no undo for this!)")
26323 {
26324 	SCP_string sexp;
26325 	SCP_string sexp_always;
26326 
26327 	if (dc_optional_string_either("help", "--help")) {
26328 		dc_printf( "Usage: sexpc sexpression\n. Always runs the given sexp as '( when ( true ) ( sexp ) )' .\n" );
26329 		return;
26330 	}
26331 
26332 	dc_stuff_string(sexp);
26333 
26334 	sexp_always = "( when ( true ) ( " + sexp + " ) )";
26335 
26336 	int sexp_val = run_sexp(sexp_always.c_str());
26337 	dc_printf("SEXP '%s' run, sexp_val = %d\n", sexp_always.c_str(), sexp_val);
26338 }
26339 
26340 
26341 DCF(sexp,"Runs the given sexp")
26342 {
26343 	SCP_string sexp;
26344 
26345 	if (dc_optional_string_either("help", "--help")) {
26346 		dc_printf( "Usage: sexp 'sexpression'\n. Runs the given sexp.\n");
26347 		return;
26348 	}
26349 
26350 	dc_stuff_string(sexp);
26351 
26352 	int sexp_val = run_sexp(sexp.c_str());
26353 	dc_printf("SEXP '%s' run, sexp_val = %d\n", sexp.c_str(), sexp_val);
26354 }
26355 
26356 // returns the data type returned by an operator
query_operator_return_type(int op)26357 int query_operator_return_type(int op)
26358 {
26359 	if (op < FIRST_OP)
26360 	{
26361 		Assert(op >= 0 && op < (int)Operators.size());
26362 		op = Operators[op].value;
26363 	}
26364 
26365 	switch (op)
26366 	{
26367 		case OP_TRUE:
26368 		case OP_FALSE:
26369 		case OP_AND:
26370 		case OP_AND_IN_SEQUENCE:
26371 		case OP_OR:
26372 		case OP_NOT:
26373 		case OP_XOR:
26374 		case OP_EQUALS:
26375 		case OP_GREATER_THAN:
26376 		case OP_LESS_THAN:
26377 		case OP_NOT_EQUAL:
26378 		case OP_GREATER_OR_EQUAL:
26379 		case OP_LESS_OR_EQUAL:
26380 		case OP_STRING_EQUALS:
26381 		case OP_STRING_GREATER_THAN:
26382 		case OP_STRING_LESS_THAN:
26383 		case OP_PERFORM_ACTIONS:
26384 		case OP_IS_DESTROYED:
26385 		case OP_IS_SUBSYSTEM_DESTROYED:
26386 		case OP_IS_DISABLED:
26387 		case OP_IS_DISARMED:
26388 		case OP_HAS_DOCKED:
26389 		case OP_HAS_UNDOCKED:
26390 		case OP_HAS_ARRIVED:
26391 		case OP_HAS_DEPARTED:
26392 		case OP_IS_DESTROYED_DELAY:
26393 		case OP_WAS_DESTROYED_BY_DELAY:
26394 		case OP_IS_SUBSYSTEM_DESTROYED_DELAY:
26395 		case OP_IS_DISABLED_DELAY:
26396 		case OP_IS_DISARMED_DELAY:
26397 		case OP_HAS_DOCKED_DELAY:
26398 		case OP_HAS_UNDOCKED_DELAY:
26399 		case OP_HAS_ARRIVED_DELAY:
26400 		case OP_HAS_DEPARTED_DELAY:
26401 		case OP_IS_IFF:
26402 		case OP_IS_AI_CLASS:
26403 		case OP_IS_SHIP_TYPE:
26404 		case OP_IS_SHIP_CLASS:
26405 		case OP_HAS_TIME_ELAPSED:
26406 		case OP_GOAL_INCOMPLETE:
26407 		case OP_GOAL_TRUE_DELAY:
26408 		case OP_GOAL_FALSE_DELAY:
26409 		case OP_EVENT_INCOMPLETE:
26410 		case OP_EVENT_TRUE_DELAY:
26411 		case OP_EVENT_FALSE_MSECS_DELAY:
26412 		case OP_EVENT_TRUE_MSECS_DELAY:
26413 		case OP_EVENT_FALSE_DELAY:
26414 		case OP_PREVIOUS_EVENT_TRUE:
26415 		case OP_PREVIOUS_EVENT_FALSE:
26416 		case OP_PREVIOUS_EVENT_INCOMPLETE:
26417 		case OP_PREVIOUS_GOAL_TRUE:
26418 		case OP_PREVIOUS_GOAL_FALSE:
26419 		case OP_PREVIOUS_GOAL_INCOMPLETE:
26420 		case OP_WAYPOINTS_DONE:
26421 		case OP_WAYPOINTS_DONE_DELAY:
26422 		case OP_SHIP_TYPE_DESTROYED:
26423 		case OP_LAST_ORDER_TIME:
26424 		case OP_KEY_PRESSED:
26425 		case OP_TARGETED:
26426 		case OP_NODE_TARGETED:
26427 		case OP_SPEED:
26428 		case OP_FACING:
26429 		case OP_FACING2:
26430 		case OP_ORDER:
26431 		case OP_QUERY_ORDERS:
26432 		case OP_WAYPOINT_MISSED:
26433 		case OP_WAYPOINT_TWICE:
26434 		case OP_PATH_FLOWN:
26435 		case OP_EVENT_TRUE:
26436 		case OP_EVENT_FALSE:
26437 		case OP_SKILL_LEVEL_AT_LEAST:
26438 		case OP_IS_CARGO_KNOWN:
26439 		case OP_HAS_BEEN_TAGGED_DELAY:
26440 		case OP_CAP_SUBSYS_CARGO_KNOWN_DELAY:
26441 		case OP_CARGO_KNOWN_DELAY:
26442 		case OP_WAS_PROMOTION_GRANTED:
26443 		case OP_WAS_MEDAL_GRANTED:
26444 		case OP_PERCENT_SHIPS_DEPARTED:
26445 		case OP_PERCENT_SHIPS_DESTROYED:
26446 		case OP_PERCENT_SHIPS_DISARMED:
26447 		case OP_PERCENT_SHIPS_DISABLED:
26448 		case OP_PERCENT_SHIPS_ARRIVED:
26449 		case OP_DEPART_NODE_DELAY:
26450 		case OP_DESTROYED_DEPARTED_DELAY:
26451 		case OP_SPECIAL_CHECK:
26452 		case OP_IS_TAGGED:
26453 		case OP_PRIMARIES_DEPLETED:
26454 		case OP_SECONDARIES_DEPLETED:
26455 		case OP_SHIELD_QUAD_LOW:
26456 		case OP_IS_SECONDARY_SELECTED:
26457 		case OP_IS_PRIMARY_SELECTED:
26458 		case OP_IS_SHIP_STEALTHY:
26459 		case OP_IS_FRIENDLY_STEALTH_VISIBLE:
26460 		case OP_IS_CARGO:
26461 		case OP_MISSILE_LOCKED:
26462 		case OP_NAV_IS_VISITED:
26463 		case OP_NAV_ISLINKED:
26464 		case OP_IS_PLAYER:
26465 		case OP_PRIMARY_FIRED_SINCE:
26466 		case OP_SECONDARY_FIRED_SINCE:
26467 		case OP_IS_FACING:
26468 		case OP_HAS_PRIMARY_WEAPON:
26469 		case OP_HAS_SECONDARY_WEAPON:
26470 		case OP_IS_BIT_SET:
26471 		case OP_IS_NAN:
26472 		case OP_DIRECTIVE_VALUE:
26473 		case OP_IS_IN_BOX:
26474 		case OP_IS_IN_MISSION:
26475 		case OP_IS_DOCKED:
26476 		case OP_PLAYER_IS_CHEATING_BASTARD:
26477 		case OP_ARE_SHIP_FLAGS_SET:
26478 		case OP_IS_IN_TURRET_FOV:
26479 		case OP_IS_LANGUAGE:
26480 			return OPR_BOOL;
26481 
26482 		case OP_PLUS:
26483 		case OP_MINUS:
26484 		case OP_MOD:
26485 		case OP_MUL:
26486 		case OP_DIV:
26487 		case OP_RAND:
26488 		case OP_RAND_MULTIPLE:
26489 		case OP_MIN:
26490 		case OP_MAX:
26491 		case OP_AVG:
26492 		case OP_POW:
26493 		case OP_SIGNUM:
26494 		case OP_NAN_TO_NUMBER:
26495 		case OP_GET_OBJECT_X:
26496 		case OP_GET_OBJECT_Y:
26497 		case OP_GET_OBJECT_Z:
26498 		case OP_GET_OBJECT_PITCH:
26499 		case OP_GET_OBJECT_BANK:
26500 		case OP_GET_OBJECT_HEADING:
26501 		case OP_GET_OBJECT_SPEED_X:
26502 		case OP_GET_OBJECT_SPEED_Y:
26503 		case OP_GET_OBJECT_SPEED_Z:
26504 		case OP_SCRIPT_EVAL_NUM:
26505 		case OP_STRING_TO_INT:
26506 		case OP_GET_THROTTLE_SPEED:
26507 		case OP_GET_VARIABLE_BY_INDEX:
26508 		case OP_GET_COLGROUP_ID:
26509 		case OP_FUNCTIONAL_IF_THEN_ELSE:
26510 		case OP_FUNCTIONAL_SWITCH:
26511 		case OP_GET_HOTKEY:
26512 			return OPR_NUMBER;
26513 
26514 		case OP_ABS:
26515 		case OP_SET_BIT:
26516 		case OP_UNSET_BIT:
26517 		case OP_BITWISE_AND:
26518 		case OP_BITWISE_OR:
26519 		case OP_BITWISE_NOT:
26520 		case OP_BITWISE_XOR:
26521 		case OP_TIME_SHIP_DESTROYED:
26522 		case OP_TIME_SHIP_ARRIVED:
26523 		case OP_TIME_SHIP_DEPARTED:
26524 		case OP_TIME_WING_DESTROYED:
26525 		case OP_TIME_WING_ARRIVED:
26526 		case OP_TIME_WING_DEPARTED:
26527 		case OP_MISSION_TIME:
26528 		case OP_MISSION_TIME_MSECS:
26529 		case OP_TIME_DOCKED:
26530 		case OP_TIME_UNDOCKED:
26531 		case OP_TIME_TO_GOAL:
26532 		case OP_AFTERBURNER_LEFT:
26533 		case OP_WEAPON_ENERGY_LEFT:
26534 		case OP_SHIELDS_LEFT:
26535 		case OP_HITS_LEFT:
26536 		case OP_HITS_LEFT_SUBSYSTEM:
26537 		case OP_HITS_LEFT_SUBSYSTEM_GENERIC:
26538 		case OP_HITS_LEFT_SUBSYSTEM_SPECIFIC:
26539 		case OP_SIM_HITS_LEFT:
26540 		case OP_DISTANCE:
26541 		case OP_DISTANCE_CENTER:
26542 		case OP_DISTANCE_BBOX:
26543 		case OP_DISTANCE_CENTER_SUBSYSTEM:
26544 		case OP_DISTANCE_BBOX_SUBSYSTEM:
26545 		case OP_NUM_WITHIN_BOX:
26546 		case OP_NUM_PLAYERS:
26547 		case OP_NUM_KILLS:
26548 		case OP_NUM_ASSISTS:
26549 		case OP_SHIP_DEATHS:
26550 		case OP_RESPAWNS_LEFT:
26551 		case OP_SHIP_SCORE:
26552 		case OP_NUM_TYPE_KILLS:
26553 		case OP_NUM_CLASS_KILLS:
26554 		case OP_SHIELD_RECHARGE_PCT:
26555 		case OP_ENGINE_RECHARGE_PCT:
26556 		case OP_WEAPON_RECHARGE_PCT:
26557 		case OP_PRIMARY_AMMO_PCT:
26558 		case OP_SECONDARY_AMMO_PCT:
26559 		case OP_GET_PRIMARY_AMMO:
26560 		case OP_GET_SECONDARY_AMMO:
26561 		case OP_GET_NUM_COUNTERMEASURES:
26562 		case OP_SPECIAL_WARP_DISTANCE:
26563 		case OP_IS_SHIP_VISIBLE:
26564 		case OP_TEAM_SCORE:
26565 		case OP_NUM_SHIPS_IN_BATTLE:
26566 		case OP_NUM_SHIPS_IN_WING:
26567 		case OP_CURRENT_SPEED:
26568 		case OP_NAV_DISTANCE:
26569 		case OP_GET_DAMAGE_CAUSED:
26570 		case OP_CUTSCENES_GET_FOV:
26571 		case OP_NUM_VALID_ARGUMENTS:
26572 		case OP_STRING_GET_LENGTH:
26573 		case OP_GET_ETS_VALUE:
26574 		case OP_TURRET_GET_PRIMARY_AMMO:
26575 		case OP_TURRET_GET_SECONDARY_AMMO:
26576 		case OP_GET_POWER_OUTPUT:
26577 			return OPR_POSITIVE;
26578 
26579 		case OP_COND:
26580 		case OP_WHEN:
26581 		case OP_WHEN_ARGUMENT:
26582 		case OP_EVERY_TIME:
26583 		case OP_EVERY_TIME_ARGUMENT:
26584 		case OP_IF_THEN_ELSE:
26585 		case OP_SWITCH:
26586 		case OP_INVALIDATE_ARGUMENT:
26587 		case OP_VALIDATE_ARGUMENT:
26588 		case OP_INVALIDATE_ALL_ARGUMENTS:
26589 		case OP_VALIDATE_ALL_ARGUMENTS:
26590 		case OP_DO_FOR_VALID_ARGUMENTS:
26591 		case OP_CHANGE_IFF:
26592 		case OP_CHANGE_AI_CLASS:
26593 		case OP_CLEAR_SHIP_GOALS:
26594 		case OP_CLEAR_WING_GOALS:
26595 		case OP_CLEAR_GOALS:
26596 		case OP_ADD_SHIP_GOAL:
26597 		case OP_ADD_WING_GOAL:
26598 		case OP_ADD_GOAL:
26599 		case OP_REMOVE_GOAL:
26600 		case OP_PROTECT_SHIP:
26601 		case OP_UNPROTECT_SHIP:
26602 		case OP_BEAM_PROTECT_SHIP:
26603 		case OP_BEAM_UNPROTECT_SHIP:
26604 		case OP_TURRET_PROTECT_SHIP:
26605 		case OP_TURRET_UNPROTECT_SHIP:
26606 		case OP_NOP:
26607 		case OP_GOALS_ID:
26608 		case OP_SEND_MESSAGE:
26609 		case OP_SELF_DESTRUCT:
26610 		case OP_NEXT_MISSION:
26611 		case OP_END_CAMPAIGN:
26612 		case OP_END_OF_CAMPAIGN:
26613 		case OP_SABOTAGE_SUBSYSTEM:
26614 		case OP_REPAIR_SUBSYSTEM:
26615 		case OP_INVALIDATE_GOAL:
26616 		case OP_VALIDATE_GOAL:
26617 		case OP_SEND_RANDOM_MESSAGE:
26618 		case OP_TRANSFER_CARGO:
26619 		case OP_EXCHANGE_CARGO:
26620 		case OP_SET_CARGO:
26621 		case OP_JETTISON_CARGO_DELAY:
26622 		case OP_JETTISON_CARGO_NEW:
26623 		case OP_SET_DOCKED:
26624 		case OP_CARGO_NO_DEPLETE:
26625 		case OP_SET_SCANNED:
26626 		case OP_SET_UNSCANNED:
26627 		case OP_KEY_RESET:
26628 		case OP_KEY_RESET_MULTIPLE:
26629 		case OP_TRAINING_MSG:
26630 		case OP_SET_TRAINING_CONTEXT_FLY_PATH:
26631 		case OP_SET_TRAINING_CONTEXT_SPEED:
26632 		case OP_END_MISSION:
26633 		case OP_SET_DEBRIEFING_TOGGLED:
26634 		case OP_SET_DEBRIEFING_PERSONA:
26635 		case OP_FORCE_JUMP:
26636 		case OP_SET_SUBSYSTEM_STRNGTH:
26637 		case OP_DESTROY_SUBSYS_INSTANTLY:
26638 		case OP_GOOD_REARM_TIME:
26639 		case OP_GRANT_PROMOTION:
26640 		case OP_GRANT_MEDAL:
26641 		case OP_ALLOW_SHIP:
26642 		case OP_ALLOW_WEAPON:
26643 		case OP_TECH_ADD_SHIP:
26644 		case OP_TECH_ADD_WEAPON:
26645 		case OP_TECH_ADD_INTEL:
26646 		case OP_TECH_REMOVE_INTEL:
26647 		case OP_TECH_ADD_INTEL_XSTR:
26648 		case OP_TECH_REMOVE_INTEL_XSTR:
26649 		case OP_TECH_RESET_TO_DEFAULT:
26650 		case OP_CHANGE_PLAYER_SCORE:
26651 		case OP_CHANGE_TEAM_SCORE:
26652 		case OP_WARP_BROKEN:
26653 		case OP_WARP_NOT_BROKEN:
26654 		case OP_WARP_NEVER:
26655 		case OP_WARP_ALLOWED:
26656 		case OP_SET_SUBSPACE_DRIVE:
26657 		case OP_FLASH_HUD_GAUGE:
26658 		case OP_GOOD_SECONDARY_TIME:
26659 		case OP_SHIP_VISIBLE:
26660 		case OP_SHIP_INVISIBLE:
26661 		case OP_SHIP_TAG:
26662 		case OP_SHIP_UNTAG:
26663 		case OP_SHIP_VULNERABLE:
26664 		case OP_SHIP_INVULNERABLE:
26665 		case OP_SHIP_BOMB_TARGETABLE:
26666 		case OP_SHIP_BOMB_UNTARGETABLE:
26667 		case OP_SHIP_GUARDIAN:
26668 		case OP_SHIP_NO_GUARDIAN:
26669 		case OP_SHIP_GUARDIAN_THRESHOLD:
26670 		case OP_SHIP_SUBSYS_GUARDIAN_THRESHOLD:
26671 		case OP_SHIP_VANISH:
26672 		case OP_DESTROY_INSTANTLY:
26673 		case OP_SHIELDS_ON:
26674 		case OP_SHIELDS_OFF:
26675 		case OP_SHIP_STEALTHY:
26676 		case OP_SHIP_UNSTEALTHY:
26677 		case OP_FRIENDLY_STEALTH_INVISIBLE:
26678 		case OP_FRIENDLY_STEALTH_VISIBLE:
26679 		case OP_SHIP_SUBSYS_NO_REPLACE:
26680 		case OP_SHIP_SUBSYS_NO_LIVE_DEBRIS:
26681 		case OP_SHIP_SUBSYS_VANISHED:
26682 		case OP_SHIP_SUBSYS_IGNORE_IF_DEAD:
26683 		case OP_SHIP_SUBSYS_TARGETABLE:
26684 		case OP_SHIP_SUBSYS_UNTARGETABLE:
26685 		case OP_RED_ALERT:
26686 		case OP_MODIFY_VARIABLE:
26687 		case OP_MODIFY_VARIABLE_XSTR:
26688 		case OP_SET_VARIABLE_BY_INDEX:
26689 		case OP_BEAM_FIRE:
26690 		case OP_BEAM_FIRE_COORDS:
26691 		case OP_BEAM_FLOATING_FIRE:
26692 		case OP_BEAM_FREE:
26693 		case OP_BEAM_FREE_ALL:
26694 		case OP_BEAM_LOCK:
26695 		case OP_BEAM_LOCK_ALL:
26696 		case OP_TURRET_FREE:
26697 		case OP_TURRET_FREE_ALL:
26698 		case OP_TURRET_LOCK:
26699 		case OP_TURRET_LOCK_ALL:
26700 		case OP_TURRET_CHANGE_WEAPON:
26701 		case OP_TURRET_SET_DIRECTION_PREFERENCE:
26702 		case OP_TURRET_SET_RATE_OF_FIRE:
26703 		case OP_TURRET_SET_OPTIMUM_RANGE:
26704 		case OP_TURRET_SET_TARGET_PRIORITIES:
26705 		case OP_TURRET_SET_TARGET_ORDER:
26706 		case OP_SET_ARMOR_TYPE:
26707 		case OP_WEAPON_SET_DAMAGE_TYPE:
26708 		case OP_SHIP_SET_DAMAGE_TYPE:
26709 		case OP_SHIP_SHOCKWAVE_SET_DAMAGE_TYPE:
26710 		case OP_FIELD_SET_DAMAGE_TYPE:
26711 		case OP_SHIP_TURRET_TARGET_ORDER:
26712 		case OP_TURRET_SUBSYS_TARGET_DISABLE:
26713 		case OP_TURRET_SUBSYS_TARGET_ENABLE:
26714 		case OP_ADD_REMOVE_ESCORT:
26715 		case OP_DAMAGED_ESCORT_LIST:
26716 		case OP_DAMAGED_ESCORT_LIST_ALL:
26717 		case OP_AWACS_SET_RADIUS:
26718 		case OP_PRIMITIVE_SENSORS_SET_RANGE:
26719 		case OP_SEND_MESSAGE_LIST:
26720 		case OP_SEND_MESSAGE_CHAIN:
26721 		case OP_CAP_WAYPOINT_SPEED:
26722 		case OP_TURRET_TAGGED_ONLY_ALL:
26723 		case OP_TURRET_TAGGED_CLEAR_ALL:
26724 		case OP_SUBSYS_SET_RANDOM:
26725 		case OP_SUPERNOVA_START:
26726 		case OP_SUPERNOVA_STOP:
26727 		case OP_SET_SPECIAL_WARPOUT_NAME:
26728 		case OP_SHIP_VAPORIZE:
26729 		case OP_SHIP_NO_VAPORIZE:
26730 		case OP_SET_EXPLOSION_OPTION:
26731 		case OP_DONT_COLLIDE_INVISIBLE:
26732 		case OP_COLLIDE_INVISIBLE:
26733 		case OP_SET_MOBILE:
26734 		case OP_SET_IMMOBILE:
26735 		case OP_IGNORE_KEY:
26736 		case OP_CHANGE_SHIP_CLASS:
26737 		case OP_SHIP_COPY_DAMAGE:
26738 		case OP_DEACTIVATE_GLOW_POINTS:
26739 		case OP_ACTIVATE_GLOW_POINTS:
26740 		case OP_DEACTIVATE_GLOW_MAPS:
26741 		case OP_ACTIVATE_GLOW_MAPS:
26742 		case OP_DEACTIVATE_GLOW_POINT_BANK:
26743 		case OP_ACTIVATE_GLOW_POINT_BANK:
26744 		case OP_SET_SKYBOX_MODEL:
26745 		case OP_SET_SKYBOX_ORIENT:
26746 		case OP_SET_SUPPORT_SHIP:
26747 		case OP_SET_ARRIVAL_INFO:
26748 		case OP_SET_DEPARTURE_INFO:
26749 		case OP_CHANGE_SOUNDTRACK:
26750 		case OP_PLAY_SOUND_FROM_FILE:
26751 		case OP_CLOSE_SOUND_FROM_FILE:
26752 		case OP_PAUSE_SOUND_FROM_FILE:
26753 		case OP_PLAY_SOUND_FROM_TABLE:
26754 		case OP_SET_SOUND_ENVIRONMENT:
26755 		case OP_UPDATE_SOUND_ENVIRONMENT:
26756 		case OP_ADJUST_AUDIO_VOLUME:
26757 		case OP_EXPLOSION_EFFECT:
26758 		case OP_WARP_EFFECT:
26759 		case OP_SET_OBJECT_POSITION:
26760 		case OP_SET_OBJECT_ORIENTATION:
26761 		case OP_SET_OBJECT_FACING:
26762 		case OP_SET_OBJECT_FACING_OBJECT:
26763 		case OP_SHIP_MANEUVER:
26764 		case OP_SHIP_ROT_MANEUVER:
26765 		case OP_SHIP_LAT_MANEUVER:
26766 		case OP_HUD_DISABLE:
26767 		case OP_HUD_DISABLE_EXCEPT_MESSAGES:
26768 		case OP_KAMIKAZE:
26769 		case OP_TURRET_TAGGED_SPECIFIC:
26770 		case OP_TURRET_TAGGED_CLEAR_SPECIFIC:
26771 		case OP_LOCK_ROTATING_SUBSYSTEM:
26772 		case OP_FREE_ROTATING_SUBSYSTEM:
26773 		case OP_REVERSE_ROTATING_SUBSYSTEM:
26774 		case OP_ROTATING_SUBSYS_SET_TURN_TIME:
26775 		case OP_TRIGGER_SUBMODEL_ANIMATION:
26776 		case OP_PLAYER_USE_AI:
26777 		case OP_PLAYER_NOT_USE_AI:
26778 		case OP_ALLOW_TREASON:
26779 		case OP_SET_PLAYER_ORDERS:
26780 		case OP_NAV_ADD_WAYPOINT:
26781 		case OP_NAV_ADD_SHIP:
26782 		case OP_NAV_DEL:
26783 		case OP_NAV_HIDE:
26784 		case OP_NAV_RESTRICT:
26785 		case OP_NAV_UNHIDE:
26786 		case OP_NAV_UNRESTRICT:
26787 		case OP_NAV_SET_VISITED:
26788 		case OP_NAV_UNSET_VISITED:
26789 		case OP_NAV_SET_CARRY:
26790 		case OP_NAV_UNSET_CARRY:
26791 		case OP_NAV_SET_NEEDSLINK:
26792 		case OP_NAV_UNSET_NEEDSLINK:
26793 		case OP_NAV_USECINEMATICS:
26794 		case OP_NAV_USEAP:
26795 		case OP_NAV_SELECT:
26796 		case OP_NAV_UNSELECT:
26797 		case OP_HUD_SET_TEXT:
26798 		case OP_HUD_SET_TEXT_NUM:
26799 		case OP_HUD_SET_MESSAGE:
26800 		case OP_HUD_SET_COORDS:
26801 		case OP_HUD_SET_FRAME:
26802 		case OP_HUD_SET_COLOR:
26803 		case OP_HUD_SET_MAX_TARGETING_RANGE:
26804 		case OP_HUD_CLEAR_MESSAGES:
26805 		case OP_SHIP_CHANGE_ALT_NAME:
26806 		case OP_SHIP_CHANGE_CALLSIGN:
26807 		case OP_SET_DEATH_MESSAGE:
26808 		case OP_SCRAMBLE_MESSAGES:
26809 		case OP_UNSCRAMBLE_MESSAGES:
26810 		case OP_CUTSCENES_SET_CUTSCENE_BARS:
26811 		case OP_CUTSCENES_UNSET_CUTSCENE_BARS:
26812 		case OP_CUTSCENES_FADE_IN:
26813 		case OP_CUTSCENES_FADE_OUT:
26814 		case OP_CUTSCENES_SET_CAMERA:
26815 		case OP_CUTSCENES_SET_CAMERA_FACING:
26816 		case OP_CUTSCENES_SET_CAMERA_FACING_OBJECT:
26817 		case OP_CUTSCENES_SET_CAMERA_FOV:
26818 		case OP_CUTSCENES_SET_CAMERA_HOST:
26819 		case OP_CUTSCENES_SET_CAMERA_POSITION:
26820 		case OP_CUTSCENES_SET_CAMERA_ROTATION:
26821 		case OP_CUTSCENES_SET_CAMERA_TARGET:
26822 		case OP_CUTSCENES_SET_FOV:
26823 		case OP_CUTSCENES_RESET_FOV:
26824 		case OP_CUTSCENES_RESET_CAMERA:
26825 		case OP_CUTSCENES_SHOW_SUBTITLE:
26826 		case OP_CUTSCENES_SHOW_SUBTITLE_TEXT:
26827 		case OP_CUTSCENES_SHOW_SUBTITLE_IMAGE:
26828 		case OP_CUTSCENES_SET_TIME_COMPRESSION:
26829 		case OP_CUTSCENES_RESET_TIME_COMPRESSION:
26830 		case OP_CUTSCENES_FORCE_PERSPECTIVE:
26831 		case OP_SET_CAMERA_SHUDDER:
26832 		case OP_JUMP_NODE_SET_JUMPNODE_NAME:
26833 		case OP_JUMP_NODE_SET_JUMPNODE_COLOR:
26834 		case OP_JUMP_NODE_SET_JUMPNODE_MODEL:
26835 		case OP_JUMP_NODE_SHOW_JUMPNODE:
26836 		case OP_JUMP_NODE_HIDE_JUMPNODE:
26837 		case OP_SET_OBJECT_SPEED_X:
26838 		case OP_SET_OBJECT_SPEED_Y:
26839 		case OP_SET_OBJECT_SPEED_Z:
26840 		case OP_SHIP_CREATE:
26841 		case OP_WEAPON_CREATE:
26842 		case OP_MISSION_SET_NEBULA:
26843 		case OP_CHANGE_BACKGROUND:
26844 		case OP_ADD_BACKGROUND_BITMAP:
26845 		case OP_REMOVE_BACKGROUND_BITMAP:
26846 		case OP_ADD_SUN_BITMAP:
26847 		case OP_REMOVE_SUN_BITMAP:
26848 		case OP_NEBULA_CHANGE_STORM:
26849 		case OP_NEBULA_TOGGLE_POOF:
26850 		case OP_SET_PRIMARY_AMMO:
26851 		case OP_SET_SECONDARY_AMMO:
26852 		case OP_SET_PRIMARY_WEAPON:
26853 		case OP_SET_SECONDARY_WEAPON:
26854 		case OP_SET_NUM_COUNTERMEASURES:
26855 		case OP_SCRIPT_EVAL:
26856 		case OP_SCRIPT_EVAL_BLOCK:
26857 		case OP_SCRIPT_EVAL_STRING:
26858 		case OP_SCRIPT_EVAL_MULTI:
26859 		case OP_ENABLE_BUILTIN_MESSAGES:
26860 		case OP_DISABLE_BUILTIN_MESSAGES:
26861 		case OP_LOCK_PRIMARY_WEAPON:
26862 		case OP_UNLOCK_PRIMARY_WEAPON:
26863 		case OP_LOCK_SECONDARY_WEAPON:
26864 		case OP_UNLOCK_SECONDARY_WEAPON:
26865 		case OP_LOCK_AFTERBURNER:
26866 		case OP_UNLOCK_AFTERBURNER:
26867 		case OP_RESET_ORDERS:
26868 		case OP_SET_PERSONA:
26869 		case OP_SET_MISSION_MOOD:
26870 		case OP_CHANGE_SUBSYSTEM_NAME:
26871 		case OP_SET_RESPAWNS:
26872 		case OP_ADD_REMOVE_HOTKEY:
26873 		case OP_SET_AFTERBURNER_ENERGY:
26874 		case OP_SET_WEAPON_ENERGY:
26875 		case OP_SET_SHIELD_ENERGY:
26876 		case OP_SET_AMBIENT_LIGHT:
26877 		case OP_SET_POST_EFFECT:
26878 		case OP_RESET_POST_EFFECTS:
26879 		case OP_CHANGE_IFF_COLOR:
26880 		case OP_CLEAR_WEAPONS:
26881 		case OP_CLEAR_DEBRIS:
26882 		case OP_MISSION_SET_SUBSPACE:
26883 		case OP_HUD_DISPLAY_GAUGE:
26884 		case OP_FORCE_GLIDE:
26885 		case OP_HUD_SET_DIRECTIVE:
26886 		case OP_HUD_GAUGE_SET_ACTIVE:
26887 		case OP_HUD_ACTIVATE_GAUGE_TYPE:
26888 		case OP_STRING_CONCATENATE:
26889 		case OP_STRING_CONCATENATE_BLOCK:
26890 		case OP_INT_TO_STRING:
26891 		case OP_DISABLE_ETS:
26892 		case OP_ENABLE_ETS:
26893 		case OP_STRING_GET_SUBSTRING:
26894 		case OP_STRING_SET_SUBSTRING:
26895 		case OP_ADD_TO_COLGROUP:
26896 		case OP_REMOVE_FROM_COLGROUP:
26897 		case OP_ADD_TO_COLGROUP2:
26898 		case OP_REMOVE_FROM_COLGROUP2:
26899 		case OP_SHIP_EFFECT:
26900 		case OP_CLEAR_SUBTITLES:
26901 		case OP_SET_THRUSTERS:
26902 		case OP_SET_PLAYER_THROTTLE_SPEED:
26903 		case OP_DEBUG:
26904 		case OP_HUD_SET_CUSTOM_GAUGE_ACTIVE:
26905 		case OP_HUD_SET_BUILTIN_GAUGE_ACTIVE:
26906 		case OP_ALTER_SHIP_FLAG:
26907 		case OP_CHANGE_TEAM_COLOR:
26908 		case OP_NEBULA_CHANGE_PATTERN:
26909 		case OP_COPY_VARIABLE_FROM_INDEX:
26910 		case OP_COPY_VARIABLE_BETWEEN_INDEXES:
26911 		case OP_SET_ETS_VALUES:
26912 		case OP_CALL_SSM_STRIKE:
26913 		case OP_SET_MOTION_DEBRIS:
26914 		case OP_TURRET_SET_PRIMARY_AMMO:
26915 		case OP_TURRET_SET_SECONDARY_AMMO:
26916 		case OP_TURRET_SET_FORCED_TARGET:
26917 		case OP_TURRET_SET_FORCED_SUBSYS_TARGET:
26918 		case OP_TURRET_CLEAR_FORCED_TARGET:
26919 		case OP_TURRET_SET_INACCURACY:
26920 		case OP_REPLACE_TEXTURE:
26921 		case OP_NEBULA_CHANGE_FOG_COLOR:
26922 			return OPR_NULL;
26923 
26924 		case OP_AI_CHASE:
26925 		case OP_AI_CHASE_WING:
26926 		case OP_AI_CHASE_SHIP_CLASS:
26927 		case OP_AI_CHASE_ANY:
26928 		case OP_AI_DOCK:
26929 		case OP_AI_UNDOCK:
26930 		case OP_AI_WARP:						// this particular operator is obsolete
26931 		case OP_AI_WARP_OUT:
26932 		case OP_AI_WAYPOINTS:
26933 		case OP_AI_WAYPOINTS_ONCE:
26934 		case OP_AI_DESTROY_SUBSYS:
26935 		case OP_AI_DISABLE_SHIP:
26936 		case OP_AI_DISARM_SHIP:
26937 		case OP_AI_GUARD:
26938 		case OP_AI_GUARD_WING:
26939 		case OP_AI_EVADE_SHIP:
26940 		case OP_AI_STAY_NEAR_SHIP:
26941 		case OP_AI_KEEP_SAFE_DISTANCE:
26942 		case OP_AI_IGNORE:
26943 		case OP_AI_IGNORE_NEW:
26944 		case OP_AI_STAY_STILL:
26945 		case OP_AI_PLAY_DEAD:
26946 		case OP_AI_PLAY_DEAD_PERSISTENT:
26947 		case OP_AI_FORM_ON_WING:
26948 			return OPR_AI_GOAL;
26949 
26950 		case OP_ANY_OF:
26951 		case OP_EVERY_OF:
26952 		case OP_RANDOM_OF:
26953 		case OP_RANDOM_MULTIPLE_OF:
26954 		case OP_NUMBER_OF:
26955 		case OP_IN_SEQUENCE:
26956 		case OP_FOR_COUNTER:
26957 		case OP_FOR_SHIP_CLASS:
26958 		case OP_FOR_SHIP_TYPE:
26959 		case OP_FOR_SHIP_TEAM:
26960 		case OP_FOR_SHIP_SPECIES:
26961 		case OP_FOR_PLAYERS:
26962 		case OP_FIRST_OF:
26963 			return OPR_FLEXIBLE_ARGUMENT;
26964 
26965 		default: {
26966 			auto dynamicSEXP = sexp::get_dynamic_sexp(op);
26967 			if (dynamicSEXP != nullptr) {
26968 				return dynamicSEXP->getReturnType();
26969 			}
26970 
26971 			Assertion(false, "query_operator_return_type() called for unsupported operator type %d!", op);
26972 		}
26973 	}
26974 
26975 	return 0;
26976 }
26977 
26978 /**
26979  * Return the data type of a specified argument to an operator.
26980  *
26981  * @param op operator index
26982  * @param argnum is 0 indexed.
26983  */
query_operator_argument_type(int op,int argnum)26984 int query_operator_argument_type(int op, int argnum)
26985 {
26986 	int index = op;
26987 
26988 	if (op < FIRST_OP)
26989 	{
26990 		Assert(index >= 0 && index < (int)Operators.size());
26991 		op = Operators[index].value;
26992 
26993 	} else {
26994 		Warning(LOCATION, "Possible unnecessary search for operator index.  Trace out and see if this is necessary.\n");
26995 
26996 		for (index=0; index<(int)Operators.size(); index++)
26997 			if (Operators[index].value == op)
26998 				break;
26999 
27000 		Assert(index < (int)Operators.size());
27001 	}
27002 
27003 	if (argnum >= Operators[index].max)
27004 		return OPF_NONE;
27005 
27006 	switch (op) {
27007 		case OP_TRUE:
27008 		case OP_FALSE:
27009 		case OP_MISSION_TIME:
27010 		case OP_MISSION_TIME_MSECS:
27011 		case OP_NOP:
27012 		case OP_WAYPOINT_MISSED:
27013 		case OP_WAYPOINT_TWICE:
27014 		case OP_PATH_FLOWN:
27015 		case OP_GRANT_PROMOTION:
27016 		case OP_WAS_PROMOTION_GRANTED:
27017 		case OP_RED_ALERT:
27018 		case OP_FORCE_JUMP:
27019 		case OP_RESET_ORDERS:
27020 		case OP_INVALIDATE_ALL_ARGUMENTS:
27021 		case OP_VALIDATE_ALL_ARGUMENTS:
27022 		case OP_NUM_VALID_ARGUMENTS:
27023 		case OP_SUPERNOVA_STOP:
27024 		case OP_NAV_UNSELECT:
27025 		case OP_PLAYER_IS_CHEATING_BASTARD:
27026 		case OP_RESET_POST_EFFECTS:
27027 		case OP_FOR_PLAYERS:
27028 			return OPF_NONE;
27029 
27030 		case OP_AND:
27031 		case OP_AND_IN_SEQUENCE:
27032 		case OP_OR:
27033 		case OP_NOT:
27034 		case OP_XOR:
27035 			return OPF_BOOL;
27036 
27037 		case OP_PLUS:
27038 		case OP_MINUS:
27039 		case OP_MOD:
27040 		case OP_MUL:
27041 		case OP_DIV:
27042 		case OP_EQUALS:
27043 		case OP_GREATER_THAN:
27044 		case OP_LESS_THAN:
27045 		case OP_NOT_EQUAL:
27046 		case OP_GREATER_OR_EQUAL:
27047 		case OP_LESS_OR_EQUAL:
27048 		case OP_RAND:
27049 		case OP_RAND_MULTIPLE:
27050 		case OP_ABS:
27051 		case OP_MIN:
27052 		case OP_MAX:
27053 		case OP_AVG:
27054 		case OP_SIGNUM:
27055 		case OP_IS_NAN:
27056 		case OP_NAN_TO_NUMBER:
27057 			return OPF_NUMBER;
27058 
27059 		case OP_POW:
27060 			if (argnum == 0)
27061 				return OPF_NUMBER;
27062 			else
27063 				return OPF_POSITIVE;
27064 
27065 		case OP_STRING_EQUALS:
27066 		case OP_STRING_GREATER_THAN:
27067 		case OP_STRING_LESS_THAN:
27068 		case OP_STRING_TO_INT:		// Karajorma
27069 		case OP_STRING_GET_LENGTH:	// Goober5000
27070 			return OPF_STRING;
27071 
27072 		case OP_STRING_CONCATENATE:
27073 			if (argnum == 0 || argnum == 1) {
27074 				return OPF_STRING;
27075 			} else if (argnum == 2) {
27076 				return OPF_VARIABLE_NAME;
27077 			} else {
27078 				// This shouldn't happen
27079 				return OPF_NONE;
27080 			}
27081 
27082 		case OP_STRING_CONCATENATE_BLOCK:
27083 			if (argnum == 0) {
27084 				return OPF_VARIABLE_NAME;
27085 			} else {
27086 				return OPF_STRING;
27087 			}
27088 
27089 		case OP_INT_TO_STRING:
27090 			if (argnum == 0) {
27091 				return OPF_NUMBER;
27092 			} else if (argnum == 1) {
27093 				return OPF_VARIABLE_NAME;
27094 			} else {
27095 				// This shouldn't happen
27096 				return OPF_NONE;
27097 			}
27098 
27099 		case OP_STRING_GET_SUBSTRING:
27100 			if (argnum == 0) {
27101 				return OPF_STRING;
27102 			} else if (argnum == 1 || argnum == 2) {
27103 				return OPF_POSITIVE;
27104 			} else if (argnum == 3) {
27105 				return OPF_VARIABLE_NAME;
27106 			} else {
27107 				// This shouldn't happen
27108 				return OPF_NONE;
27109 			}
27110 
27111 		case OP_STRING_SET_SUBSTRING:
27112 			if (argnum == 0) {
27113 				return OPF_STRING;
27114 			} else if (argnum == 1 || argnum == 2) {
27115 				return OPF_POSITIVE;
27116 			} else if (argnum == 3) {
27117 				return OPF_STRING;
27118 			} else if (argnum == 4) {
27119 				return OPF_VARIABLE_NAME;
27120 			} else {
27121 				// This shouldn't happen
27122 				return OPF_NONE;
27123 			}
27124 
27125 		case OP_DEBUG:
27126 			if (argnum == 0) {
27127 				return OPF_BOOL;
27128 			} else {
27129 				return OPF_MESSAGE_OR_STRING;
27130 			}
27131 
27132 		case OP_HAS_TIME_ELAPSED:
27133 		case OP_SPEED:
27134 		case OP_SET_TRAINING_CONTEXT_SPEED:
27135 		case OP_SPECIAL_CHECK:
27136 		case OP_AI_WARP_OUT:
27137 		case OP_TEAM_SCORE:
27138 		case OP_HUD_SET_MAX_TARGETING_RANGE:
27139 		case OP_MISSION_SET_NEBULA:	//WMC
27140 		case OP_MISSION_SET_SUBSPACE:
27141 		case OP_SET_BIT:
27142 		case OP_UNSET_BIT:
27143 		case OP_IS_BIT_SET:
27144 		case OP_BITWISE_AND:
27145 		case OP_BITWISE_OR:
27146 		case OP_BITWISE_NOT:
27147 		case OP_BITWISE_XOR:
27148 			return OPF_POSITIVE;
27149 
27150 		case OP_AI_WARP:								// this operator is obsolete
27151 		case OP_SET_TRAINING_CONTEXT_FLY_PATH:
27152 			if ( !argnum )
27153 				return OPF_WAYPOINT_PATH;
27154 			else
27155 				return OPF_NUMBER;
27156 
27157 		case OP_AI_WAYPOINTS:
27158 		case OP_AI_WAYPOINTS_ONCE:
27159 			if ( argnum == 0 )
27160 				return OPF_WAYPOINT_PATH;
27161 			else
27162 				return OPF_POSITIVE;
27163 
27164 		case OP_TURRET_PROTECT_SHIP:
27165 		case OP_TURRET_UNPROTECT_SHIP:
27166 			if (argnum == 0)
27167 				return OPF_STRING;
27168 			else
27169 				return OPF_SHIP;
27170 
27171 		case OP_IS_DISABLED:
27172 		case OP_IS_DISARMED:
27173 		case OP_TIME_SHIP_DESTROYED:
27174 		case OP_TIME_SHIP_ARRIVED:
27175 		case OP_TIME_SHIP_DEPARTED:
27176 		case OP_AFTERBURNER_LEFT:
27177 		case OP_WEAPON_ENERGY_LEFT:
27178 		case OP_SHIELDS_LEFT:
27179 		case OP_HITS_LEFT:
27180 		case OP_SIM_HITS_LEFT:
27181 		case OP_CLEAR_SHIP_GOALS:
27182 		case OP_PROTECT_SHIP:
27183 		case OP_UNPROTECT_SHIP:
27184 		case OP_BEAM_PROTECT_SHIP:
27185 		case OP_BEAM_UNPROTECT_SHIP:
27186 		case OP_TRANSFER_CARGO:
27187 		case OP_EXCHANGE_CARGO:
27188 		case OP_SHIP_INVISIBLE:
27189 		case OP_SHIP_VISIBLE:
27190 		case OP_SHIP_INVULNERABLE:
27191 		case OP_SHIP_VULNERABLE:
27192 		case OP_SHIP_BOMB_TARGETABLE:
27193 		case OP_SHIP_BOMB_UNTARGETABLE:
27194 		case OP_SHIP_GUARDIAN:
27195 		case OP_SHIP_NO_GUARDIAN:
27196 		case OP_SHIP_VANISH:
27197 		case OP_DESTROY_INSTANTLY:
27198 		case OP_SHIELDS_ON:
27199 		case OP_SHIELDS_OFF:
27200 		case OP_SHIP_STEALTHY:
27201 		case OP_SHIP_UNSTEALTHY:
27202 		case OP_FRIENDLY_STEALTH_INVISIBLE:
27203 		case OP_FRIENDLY_STEALTH_VISIBLE:
27204 		case OP_PRIMARIES_DEPLETED:
27205 		case OP_SECONDARIES_DEPLETED:
27206 		case OP_SPECIAL_WARP_DISTANCE:
27207 		case OP_SET_SPECIAL_WARPOUT_NAME:
27208 		case OP_IS_SHIP_VISIBLE:
27209 		case OP_IS_SHIP_STEALTHY:
27210 		case OP_IS_FRIENDLY_STEALTH_VISIBLE:
27211 		case OP_GET_DAMAGE_CAUSED:
27212 		case OP_GET_THROTTLE_SPEED:
27213 			return OPF_SHIP;
27214 
27215 		case OP_ALTER_SHIP_FLAG:
27216 			if(argnum == 0)
27217 				return OPF_SHIP_FLAG;
27218 			if(argnum == 1 || argnum == 2)
27219 				return OPF_BOOL;
27220 			else
27221 				return OPF_SHIP_WING_WHOLETEAM;
27222 
27223 		case OP_SET_PLAYER_THROTTLE_SPEED:
27224 			if(argnum == 0)
27225 				return OPF_SHIP;
27226 			else
27227 				return OPF_POSITIVE;
27228 
27229 		case OP_SHIP_CREATE:
27230 			if (argnum == 0)
27231 				return OPF_STRING;
27232 			else if (argnum == 1)
27233 				return OPF_SHIP_CLASS_NAME;
27234 			else if (argnum == 8)
27235 				return OPF_IFF;
27236 			else
27237 				return OPF_NUMBER;
27238 
27239 		case OP_WEAPON_CREATE:
27240 			if (argnum == 0)
27241 				return OPF_SHIP_OR_NONE;
27242 			else if (argnum == 1)
27243 				return OPF_WEAPON_NAME;
27244 			else if (argnum == 8)
27245 				return OPF_SHIP;
27246 			else if (argnum == 9)
27247 				return OPF_SUBSYSTEM;
27248 			else
27249 				return OPF_NUMBER;
27250 
27251 		case OP_CLEAR_WEAPONS:
27252 			return OPF_WEAPON_NAME;
27253 
27254 		case OP_CLEAR_DEBRIS:
27255 			return OPF_SHIP_CLASS_NAME;
27256 
27257 		case OP_SHIP_GUARDIAN_THRESHOLD:
27258 			if (argnum == 0)
27259 				return OPF_POSITIVE;
27260 			else
27261 				return OPF_SHIP;
27262 
27263 		case OP_SHIP_SUBSYS_GUARDIAN_THRESHOLD:
27264 			if (argnum == 0)
27265 				return OPF_POSITIVE;
27266 			else if (argnum == 1)
27267 				return OPF_SHIP;
27268 			else
27269 				return OPF_SUBSYS_OR_GENERIC;
27270 
27271 		case OP_SHIP_SUBSYS_TARGETABLE:
27272 		case OP_SHIP_SUBSYS_UNTARGETABLE:
27273 			if (argnum == 0)
27274 				return OPF_SHIP;
27275 			else
27276 				return OPF_SUBSYS_OR_GENERIC;
27277 
27278 		case OP_SHIP_SUBSYS_NO_REPLACE:
27279 		case OP_SHIP_SUBSYS_NO_LIVE_DEBRIS:
27280 		case OP_SHIP_SUBSYS_VANISHED:
27281 		case OP_SHIP_SUBSYS_IGNORE_IF_DEAD:
27282 			if (argnum == 0)
27283 				return OPF_SHIP;
27284 			else if (argnum == 1)
27285 				return OPF_BOOL;
27286 			else
27287 				return OPF_SUBSYS_OR_GENERIC;
27288 
27289 		case OP_IS_DESTROYED:
27290 		case OP_HAS_ARRIVED:
27291 		case OP_HAS_DEPARTED:
27292 		case OP_CLEAR_GOALS:
27293 			return OPF_SHIP_WING;
27294 
27295 		case OP_IS_DISABLED_DELAY:
27296 		case OP_IS_DISARMED_DELAY:
27297 			if ( argnum == 0 )
27298 				return OPF_POSITIVE;
27299 			else
27300 				return OPF_SHIP;
27301 
27302 		case OP_SHIP_TAG:
27303 			if (argnum == 0)
27304 				return OPF_SHIP;
27305 			else if (argnum == 3)
27306 				return OPF_SSM_CLASS;
27307 			else if (argnum == 7)
27308 				return OPF_IFF;
27309 			else if (argnum > 3)	// SSM origin coordinates shouldn't be limited to positive numbers
27310 				return OPF_NUMBER;
27311 			else
27312 				return OPF_POSITIVE;
27313 
27314 		case OP_SHIP_UNTAG:
27315 			return OPF_SHIP;
27316 
27317 		case OP_FACING:
27318 			if (argnum == 0)
27319 				return OPF_SHIP;
27320 			else
27321 				return OPF_POSITIVE;
27322 
27323 		case OP_FACING2:
27324 			if (argnum == 0) {
27325 				return OPF_WAYPOINT_PATH;
27326 			} else {
27327 				return OPF_POSITIVE;
27328 			}
27329 
27330 		case OP_ORDER:
27331 			if (argnum == 1)
27332 				return OPF_AI_ORDER;
27333 			else
27334 				return OPF_SHIP_WING;	// arg 0 or 2
27335 
27336 		case OP_QUERY_ORDERS:
27337 			if (argnum == 0)
27338 				return OPF_ORDER_RECIPIENT;
27339 			if (argnum == 1)
27340 				return OPF_AI_ORDER;
27341 			if (argnum == 2)
27342 				return OPF_POSITIVE;
27343 			if (argnum == 5)
27344 				return OPF_SUBSYSTEM;
27345 			else
27346 				return OPF_SHIP_WING;
27347 
27348 		case OP_TIME_TO_GOAL:
27349 				return OPF_SHIP;
27350 
27351 		case OP_WAS_DESTROYED_BY_DELAY:
27352 			if (argnum == 0)
27353 				return OPF_POSITIVE;
27354 			else
27355 				return OPF_SHIP;
27356 
27357 		case OP_IS_DESTROYED_DELAY:
27358 		case OP_HAS_ARRIVED_DELAY:
27359 		case OP_HAS_DEPARTED_DELAY:
27360 		case OP_LAST_ORDER_TIME:
27361 			if ( argnum == 0 )
27362 				return OPF_POSITIVE;
27363 			else
27364 				return OPF_SHIP_WING;
27365 
27366 		case OP_SHIP_CHANGE_ALT_NAME:
27367 			if (argnum == 0)
27368 				return OPF_STRING;
27369 			else
27370 				return OPF_SHIP_WING;
27371 
27372 		case OP_SHIP_CHANGE_CALLSIGN:
27373 			if (argnum == 0)
27374 				return OPF_STRING;
27375 			else
27376 				return OPF_SHIP;
27377 
27378 		case OP_SET_DEATH_MESSAGE:
27379 			return OPF_MESSAGE_OR_STRING;
27380 
27381 		case OP_DISTANCE:
27382 		case OP_DISTANCE_CENTER:
27383 		case OP_DISTANCE_BBOX:
27384 			return OPF_SHIP_WING_SHIPONTEAM_POINT;
27385 
27386 		case OP_SET_OBJECT_SPEED_X:
27387 		case OP_SET_OBJECT_SPEED_Y:
27388 		case OP_SET_OBJECT_SPEED_Z:
27389 			if (argnum == 0)
27390 				return OPF_SHIP_WING;
27391 			else if (argnum == 1)
27392 				return OPF_NUMBER;
27393 			else
27394 				return OPF_BOOL;
27395 
27396 		case OP_GET_OBJECT_SPEED_X:
27397 		case OP_GET_OBJECT_SPEED_Y:
27398 		case OP_GET_OBJECT_SPEED_Z:
27399 			if (argnum == 0)
27400 				return OPF_SHIP_WING;
27401 			else
27402 				return OPF_BOOL;
27403 
27404 		case OP_GET_OBJECT_X:
27405 		case OP_GET_OBJECT_Y:
27406 		case OP_GET_OBJECT_Z:
27407 			if (argnum == 0)
27408 				return OPF_SHIP_WING_POINT;
27409 			else if (argnum == 1)
27410 				return OPF_SUBSYSTEM_OR_NONE;
27411 			else
27412 				return OPF_NUMBER;
27413 
27414 		case OP_GET_OBJECT_PITCH:
27415 		case OP_GET_OBJECT_BANK:
27416 		case OP_GET_OBJECT_HEADING:
27417 			return OPF_SHIP_WING;
27418 
27419 		case OP_SET_OBJECT_POSITION:
27420 			if(argnum == 0)
27421 				return OPF_SHIP_WING_POINT;
27422 			else
27423 				return OPF_NUMBER;
27424 
27425 		case OP_SET_OBJECT_ORIENTATION:
27426 			if (argnum == 0)
27427 				return OPF_SHIP_WING;
27428 			else
27429 				return OPF_NUMBER;
27430 
27431 		case OP_SET_OBJECT_FACING:
27432 			if (argnum == 0)
27433 				return OPF_SHIP_WING;
27434 			else if (argnum < 4)
27435 				return OPF_NUMBER;
27436 			else
27437 				return OPF_POSITIVE;
27438 
27439 		case OP_SET_OBJECT_FACING_OBJECT:
27440 			if (argnum == 0)
27441 				return OPF_SHIP_WING;
27442 			else if (argnum == 1)
27443 				return OPF_SHIP_WING_POINT;
27444 			else
27445 				return OPF_POSITIVE;
27446 
27447 		case OP_SHIP_MANEUVER:
27448 			if (argnum == 0)
27449 				return OPF_SHIP_WING;
27450 			else if (argnum == 1)
27451 				return OPF_POSITIVE;
27452 			else if (argnum < 5)
27453 				return OPF_NUMBER;
27454 			else if (argnum == 5)
27455 				return OPF_BOOL;
27456 			else if (argnum < 9)
27457 				return OPF_NUMBER;
27458 			else if (argnum == 9)
27459 				return OPF_BOOL;
27460 			else
27461 				return OPF_POSITIVE;
27462 
27463 		case OP_SHIP_ROT_MANEUVER:
27464 		case OP_SHIP_LAT_MANEUVER:
27465 			if (argnum == 0)
27466 				return OPF_SHIP_WING;
27467 			else if (argnum == 1)
27468 				return OPF_POSITIVE;
27469 			else if (argnum < 5)
27470 				return OPF_NUMBER;
27471 			else if (argnum == 5)
27472 				return OPF_BOOL;
27473 			else
27474 				return OPF_POSITIVE;
27475 
27476 		case OP_MODIFY_VARIABLE:
27477 			if (argnum == 0) {
27478 				return OPF_VARIABLE_NAME;
27479 			} else {
27480 				return OPF_AMBIGUOUS;
27481 			}
27482 
27483 		case OP_MODIFY_VARIABLE_XSTR:
27484 			if (argnum == 0) {
27485 				return OPF_VARIABLE_NAME;
27486 			} else if (argnum == 1) {
27487 				return OPF_STRING;
27488 			} else if (argnum == 2) {
27489 				return OPF_NUMBER;
27490 			} else {
27491 				return OPF_NONE;
27492 			}
27493 
27494 		case OP_GET_VARIABLE_BY_INDEX:
27495 		case OP_COPY_VARIABLE_BETWEEN_INDEXES:
27496 			return OPF_POSITIVE;
27497 
27498 		case OP_COPY_VARIABLE_FROM_INDEX:
27499 			if (argnum == 0) {
27500 				return OPF_POSITIVE;
27501 			} else {
27502 				return OPF_VARIABLE_NAME;
27503 			}
27504 
27505 		case OP_SET_VARIABLE_BY_INDEX:
27506 			if (argnum == 0) {
27507 				return OPF_POSITIVE;
27508 			} else {
27509 				return OPF_AMBIGUOUS;
27510 			}
27511 
27512 		case OP_HAS_DOCKED:
27513 		case OP_HAS_UNDOCKED:
27514 		case OP_HAS_DOCKED_DELAY:
27515 		case OP_HAS_UNDOCKED_DELAY:
27516 		case OP_TIME_DOCKED:
27517 		case OP_TIME_UNDOCKED:
27518 			if ( argnum < 2 )
27519 				return OPF_SHIP;
27520 			else
27521 				return OPF_POSITIVE;
27522 
27523 		case OP_TIME_WING_DESTROYED:
27524 		case OP_TIME_WING_ARRIVED:
27525 		case OP_TIME_WING_DEPARTED:
27526 		case OP_CLEAR_WING_GOALS:
27527 			return OPF_WING;
27528 
27529 		case OP_SET_SCANNED:
27530 		case OP_SET_UNSCANNED:
27531 		case OP_IS_SUBSYSTEM_DESTROYED:
27532 			if (!argnum)
27533 				return OPF_SHIP;
27534 			else
27535 				return OPF_SUBSYSTEM;
27536 
27537 		case OP_HITS_LEFT_SUBSYSTEM:
27538 			if (argnum == 0)
27539 				return OPF_SHIP;
27540 			else if (argnum == 1)
27541 				return OPF_SUBSYSTEM;
27542 			else
27543 				return OPF_BOOL;
27544 
27545 		case OP_HITS_LEFT_SUBSYSTEM_GENERIC:
27546 			if (argnum == 0)
27547 				return OPF_SHIP;
27548 			else
27549 				return OPF_SUBSYSTEM_TYPE;
27550 
27551 		case OP_HITS_LEFT_SUBSYSTEM_SPECIFIC:
27552 			if (argnum == 0)
27553 				return OPF_SHIP;
27554 			else if (argnum == 1)
27555 				return OPF_SUBSYSTEM;
27556 			else
27557 				// This shouldn't happen
27558 				return OPF_NONE;
27559 
27560 		case OP_DISTANCE_CENTER_SUBSYSTEM:
27561 		case OP_DISTANCE_BBOX_SUBSYSTEM:
27562 			if (argnum == 0)
27563 				return OPF_SHIP_WING_SHIPONTEAM_POINT;
27564 			else if (argnum == 1)
27565 				return OPF_SHIP;
27566 			else if (argnum == 2)
27567 				return OPF_SUBSYSTEM;
27568 			else
27569 				// This shouldn't happen
27570 				return OPF_NONE;
27571 
27572 		case OP_NUM_WITHIN_BOX:
27573 			if(argnum < 3)
27574 				return OPF_NUMBER;
27575 			else if(argnum < 6)
27576 				return OPF_POSITIVE;
27577 			else
27578 				return OPF_SHIP_WING;
27579 
27580 		case OP_IS_IN_BOX:
27581 			if (argnum == 0) // First arg is a ship/wing/point
27582 				return OPF_SHIP_WING_POINT;
27583 			else if (argnum <= 6) // Next 6 args are coordinates
27584 				return OPF_NUMBER;
27585 			else // Next arg is a ship
27586 				return OPF_SHIP;
27587 
27588 		case OP_IS_IN_MISSION:
27589 			return OPF_STRING;
27590 
27591 		case OP_IS_DOCKED:
27592 			return OPF_SHIP;
27593 
27594 		// Sesquipedalian
27595 		case OP_MISSILE_LOCKED:
27596 			if (argnum == 0)
27597 				return OPF_POSITIVE;
27598 			else if (argnum == 1)
27599 				return OPF_SHIP;
27600 			else
27601 				return OPF_SUBSYSTEM;
27602 
27603 		case OP_TARGETED:
27604 			if (!argnum)
27605 				return OPF_SHIP;
27606 			else if (argnum == 1)
27607 				return OPF_POSITIVE;
27608 			else
27609 				return OPF_SUBSYSTEM;
27610 
27611 		case OP_NODE_TARGETED:
27612 			if (!argnum)
27613 				return OPF_JUMP_NODE_NAME;
27614 			else if (argnum == 1)
27615 				return OPF_POSITIVE;
27616 			else
27617 				return OPF_NONE;
27618 
27619 		case OP_IS_SUBSYSTEM_DESTROYED_DELAY:
27620 			if ( argnum == 0 )
27621 				return OPF_SHIP;
27622 			else if ( argnum == 1 )
27623 				return OPF_SUBSYSTEM;
27624 			else
27625 				return OPF_POSITIVE;
27626 
27627 		case OP_IS_IFF:
27628 		case OP_CHANGE_IFF:
27629 			if (!argnum)
27630 				return OPF_IFF;
27631 			else
27632 				return OPF_SHIP_WING;
27633 
27634 		case OP_ADD_SHIP_GOAL:
27635 			if (!argnum)
27636 				return OPF_SHIP;
27637 			else
27638 				return OPF_AI_GOAL;
27639 
27640 		case OP_ADD_WING_GOAL:
27641 			if (!argnum)
27642 				return OPF_WING;
27643 			else
27644 				return OPF_AI_GOAL;
27645 
27646 		case OP_ADD_GOAL:
27647 		case OP_REMOVE_GOAL:
27648 			if ( argnum == 0 )
27649 				return OPF_SHIP_WING;
27650 			else
27651 				return OPF_AI_GOAL;
27652 
27653 		case OP_COND:
27654 		case OP_WHEN:
27655 		case OP_EVERY_TIME:
27656 		case OP_IF_THEN_ELSE:
27657 		case OP_PERFORM_ACTIONS:
27658 			if (!argnum)
27659 				return OPF_BOOL;
27660 			else
27661 				return OPF_NULL;
27662 
27663 		case OP_SWITCH:
27664 			if (!argnum)
27665 				return OPF_NUMBER;
27666 			else
27667 				return OPF_NULL;
27668 
27669 		case OP_WHEN_ARGUMENT:
27670 		case OP_EVERY_TIME_ARGUMENT:
27671 			if (argnum == 0)
27672 				return OPF_FLEXIBLE_ARGUMENT;
27673 			else if (argnum == 1)
27674 				return OPF_BOOL;
27675 			else
27676 				return OPF_NULL;
27677 
27678 		case OP_DO_FOR_VALID_ARGUMENTS:
27679 			return OPF_NULL;
27680 
27681 		case OP_ANY_OF:
27682 		case OP_EVERY_OF:
27683 		case OP_RANDOM_OF:
27684 		case OP_RANDOM_MULTIPLE_OF:
27685 		case OP_IN_SEQUENCE:
27686 			return OPF_ANYTHING;
27687 
27688 		case OP_NUMBER_OF:
27689 		case OP_FIRST_OF:
27690 			if (argnum == 0)
27691 				return OPF_POSITIVE;
27692 			else
27693 				return OPF_ANYTHING;
27694 
27695 		case OP_FOR_COUNTER:
27696 			return OPF_NUMBER;
27697 
27698 		case OP_FOR_SHIP_CLASS:
27699 			return OPF_SHIP_CLASS_NAME;
27700 
27701 		case OP_FOR_SHIP_TYPE:
27702 			return OPF_SHIP_TYPE;
27703 
27704 		case OP_FOR_SHIP_TEAM:
27705 			return OPF_IFF;
27706 
27707 		case OP_FOR_SHIP_SPECIES:
27708 			return OPF_SPECIES;
27709 
27710 		case OP_INVALIDATE_ARGUMENT:
27711 		case OP_VALIDATE_ARGUMENT:
27712 			return OPF_ANYTHING;
27713 
27714 		case OP_FUNCTIONAL_IF_THEN_ELSE:
27715 			if (argnum == 0)
27716 				return OPF_BOOL;
27717 			else
27718 				return OPF_NUMBER;
27719 
27720 		case OP_FUNCTIONAL_SWITCH:
27721 			return OPF_NUMBER;
27722 
27723 		case OP_AI_DISABLE_SHIP:
27724 		case OP_AI_DISARM_SHIP:
27725 			if (argnum == 0)
27726 				return OPF_SHIP;
27727 			else if (argnum == 1)
27728 				return OPF_POSITIVE;
27729 			else
27730 				return OPF_BOOL;
27731 
27732 		case OP_AI_EVADE_SHIP:
27733 		case OP_AI_STAY_NEAR_SHIP:
27734 		case OP_AI_IGNORE:
27735 		case OP_AI_IGNORE_NEW:
27736 			if (!argnum)
27737 				return OPF_SHIP;
27738 			else
27739 				return OPF_POSITIVE;
27740 
27741 		case OP_AI_CHASE:
27742 			if (argnum == 0)
27743 				return OPF_SHIP_WING;
27744 			else if (argnum == 1)
27745 				return OPF_POSITIVE;
27746 			else
27747 				return OPF_BOOL;
27748 
27749 		case OP_AI_CHASE_WING:
27750 			if (argnum == 0)
27751 				return OPF_WING;
27752 			else if (argnum == 1)
27753 				return OPF_POSITIVE;
27754 			else
27755 				return OPF_BOOL;
27756 
27757 		case OP_AI_CHASE_SHIP_CLASS:
27758 			if (argnum == 0)
27759 				return OPF_SHIP_CLASS_NAME;
27760 			else if (argnum == 1)
27761 				return OPF_POSITIVE;
27762 			else
27763 				return OPF_BOOL;
27764 
27765 		case OP_AI_GUARD:
27766 			if (!argnum)
27767 				return OPF_SHIP_WING;
27768 			else
27769 				return OPF_POSITIVE;
27770 
27771 		case OP_AI_GUARD_WING:
27772 			if (!argnum)
27773 				return OPF_WING;
27774 			else
27775 				return OPF_POSITIVE;
27776 
27777 		case OP_AI_KEEP_SAFE_DISTANCE:
27778 			return OPF_POSITIVE;
27779 
27780 		case OP_AI_DOCK:
27781 			if (!argnum)
27782 				return OPF_SHIP;
27783 			else if (argnum == 1)
27784 				return OPF_DOCKER_POINT;
27785 			else if (argnum == 2)
27786 				return OPF_DOCKEE_POINT;
27787 			else
27788 				return OPF_POSITIVE;
27789 
27790 		case OP_AI_UNDOCK:
27791 			if (argnum == 0)
27792 				return OPF_POSITIVE;
27793 			else
27794 				return OPF_SHIP;
27795 
27796 		case OP_AI_DESTROY_SUBSYS:
27797 			if (!argnum)
27798 				return OPF_SHIP;
27799 			else if (argnum == 1)
27800 				return OPF_SUBSYSTEM;
27801 			else if (argnum == 2)
27802 				return OPF_POSITIVE;
27803 			else
27804 				return OPF_BOOL;
27805 
27806 		case OP_GOALS_ID:
27807 			return OPF_AI_GOAL;
27808 
27809 		case OP_SET_CARGO:
27810 		case OP_IS_CARGO:
27811 			if (argnum == 0)
27812 				return OPF_CARGO;
27813 			else if (argnum == 1)
27814 				return OPF_SHIP;
27815 			else
27816 				return OPF_SUBSYSTEM;
27817 
27818 		case OP_CHANGE_AI_CLASS:
27819 		case OP_IS_AI_CLASS:
27820 			if (argnum == 0)
27821 				return OPF_AI_CLASS;
27822 			else if (argnum == 1)
27823 				return OPF_SHIP;
27824 			else
27825 				return OPF_SUBSYSTEM;
27826 
27827 		case OP_IS_SHIP_TYPE:
27828 			if (argnum == 0)
27829 				return OPF_SHIP_TYPE;
27830 			else
27831 				return OPF_SHIP;
27832 
27833 		case OP_IS_SHIP_CLASS:
27834 			if (argnum == 0)
27835 				return OPF_SHIP_CLASS_NAME;
27836 			else
27837 				return OPF_SHIP;
27838 
27839 		case OP_CHANGE_SOUNDTRACK:
27840 			return OPF_SOUNDTRACK_NAME;
27841 
27842 		case OP_PLAY_SOUND_FROM_TABLE:
27843 			if (argnum == 3)
27844 				return OPF_GAME_SND;
27845 			else
27846 				return OPF_NUMBER;
27847 
27848 		case OP_PLAY_SOUND_FROM_FILE:
27849 			if (argnum == 0)
27850 				return OPF_STRING;
27851 			else if (argnum == 3)
27852 				return OPF_VARIABLE_NAME;
27853 			else
27854 				return OPF_NUMBER;
27855 
27856 		case OP_CLOSE_SOUND_FROM_FILE:
27857 		case OP_PAUSE_SOUND_FROM_FILE:
27858 			if (argnum == 0)
27859 				return OPF_BOOL;
27860 			else
27861 				return OPF_VARIABLE_NAME;
27862 
27863 		case OP_ALLOW_TREASON:
27864 		case OP_END_MISSION:
27865 		case OP_SET_DEBRIEFING_TOGGLED:
27866 			return OPF_BOOL;
27867 
27868 		case OP_SET_DEBRIEFING_PERSONA:
27869 			return OPF_POSITIVE;
27870 
27871 		case OP_SET_PLAYER_ORDERS:
27872 			if (argnum==0)
27873 				return OPF_SHIP;
27874 			if (argnum==1)
27875 				return OPF_BOOL;
27876 			else
27877 				return OPF_AI_ORDER;
27878 
27879 		case OP_SET_SOUND_ENVIRONMENT:
27880 			if (argnum == 0)
27881 				return OPF_SOUND_ENVIRONMENT;
27882 
27883 			// fall through
27884 			argnum--;
27885 			FALLTHROUGH;
27886 
27887 		case OP_UPDATE_SOUND_ENVIRONMENT:
27888 		{
27889 			// every two, the value repeats
27890 			int a_mod = argnum % 2;
27891 
27892 			if (a_mod == 0)
27893 				return OPF_SOUND_ENVIRONMENT_OPTION;
27894 			else
27895 				return OPF_POSITIVE;
27896 		}
27897 
27898 		case OP_ADJUST_AUDIO_VOLUME:
27899 		{
27900 			if (argnum == 0)
27901 				return OPF_AUDIO_VOLUME_OPTION;
27902 			else
27903 				return OPF_POSITIVE;
27904 		}
27905 
27906 		case OP_SET_EXPLOSION_OPTION:
27907 		{
27908 			// editing a ship
27909 			if (argnum == 0)
27910 				return OPF_SHIP;
27911 
27912 			// every two, the value repeats
27913 			int a_mod = (argnum - 1) % 2;
27914 
27915 			if (a_mod == 0)
27916 				return OPF_EXPLOSION_OPTION;
27917 			else
27918 				return OPF_POSITIVE;
27919 		}
27920 
27921 		case OP_HUD_DISABLE:
27922 		case OP_HUD_DISABLE_EXCEPT_MESSAGES:
27923 			return OPF_POSITIVE;
27924 
27925 		case OP_HUD_SET_TEXT:
27926 			if (argnum == 0)
27927 				return OPF_CUSTOM_HUD_GAUGE;
27928 			else
27929 				return OPF_STRING;
27930 
27931 		case OP_HUD_SET_MESSAGE:
27932 			if(argnum == 0)
27933 				return OPF_CUSTOM_HUD_GAUGE;
27934 			else
27935 				return OPF_MESSAGE;
27936 
27937 		case OP_HUD_SET_TEXT_NUM:
27938 		case OP_HUD_SET_COORDS:
27939 		case OP_HUD_SET_FRAME:
27940 		case OP_HUD_SET_COLOR:
27941 			if(argnum == 0)
27942 				return OPF_CUSTOM_HUD_GAUGE;
27943 			else
27944 				return OPF_POSITIVE;
27945 
27946 		case OP_HUD_CLEAR_MESSAGES:
27947 			return OPF_NONE;
27948 
27949 		case OP_PLAYER_USE_AI:
27950 		case OP_PLAYER_NOT_USE_AI:
27951 			return OPF_NONE;
27952 
27953 		case OP_EXPLOSION_EFFECT:
27954 			if (argnum <= 2)
27955 				return OPF_NUMBER;
27956 			else if (argnum == 9)
27957 				return OPF_FIREBALL;
27958 			else if (argnum == 10)
27959 				return OPF_GAME_SND;
27960 			else
27961 				return OPF_POSITIVE;
27962 
27963 		case OP_WARP_EFFECT:
27964 			if (argnum <= 5)
27965 				return OPF_NUMBER;
27966 			else if (argnum == 8 || argnum == 9)
27967 				return OPF_GAME_SND;
27968 			else if (argnum == 10)
27969 				return OPF_FIREBALL;
27970 			else
27971 				return OPF_POSITIVE;
27972 
27973 		case OP_SEND_MESSAGE:
27974 		case OP_SEND_RANDOM_MESSAGE:
27975 			if ( argnum == 0 )
27976 				return OPF_WHO_FROM;
27977 			else if ( argnum == 1 )
27978 				return OPF_PRIORITY;
27979 			else
27980 				return OPF_MESSAGE;
27981 
27982 		case OP_SEND_MESSAGE_LIST:
27983 		case OP_SEND_MESSAGE_CHAIN:
27984 		{
27985 			// chain has one extra argument but is otherwise the same
27986 			if (op == OP_SEND_MESSAGE_CHAIN)
27987 			{
27988 				if (argnum == 0)
27989 					return OPF_EVENT_NAME;
27990 				argnum--;
27991 			}
27992 
27993 			// every four, the value repeats
27994 			int a_mod = argnum % 4;
27995 
27996 			// who from
27997 			if(a_mod == 0)
27998 				return OPF_WHO_FROM;
27999 			else if(a_mod == 1)
28000 				return OPF_PRIORITY;
28001 			else if(a_mod == 2)
28002 				return OPF_MESSAGE;
28003 			else if(a_mod == 3)
28004 				return OPF_POSITIVE;
28005 			else
28006 				// This can't happen
28007 				return OPF_NONE;
28008 		}
28009 
28010 		case OP_TRAINING_MSG:
28011 			if (argnum < 2)
28012 				return OPF_MESSAGE;
28013 			else
28014 				return OPF_POSITIVE;
28015 
28016 		// Karajorma
28017 		case OP_ENABLE_BUILTIN_MESSAGES:
28018 		case OP_DISABLE_BUILTIN_MESSAGES:
28019 				return OPF_WHO_FROM;
28020 
28021 		case OP_SET_PERSONA:
28022 			if (argnum == 0)
28023 				return OPF_PERSONA;
28024 			else
28025 				return OPF_SHIP;
28026 
28027 		case OP_SET_MISSION_MOOD:
28028 			return OPF_MISSION_MOOD;
28029 
28030 		case OP_CHANGE_TEAM_COLOR:
28031 			if (argnum == 0)
28032 				return OPF_TEAM_COLOR;
28033 			else if (argnum == 1)
28034 				return OPF_NUMBER;
28035 			else
28036 				return OPF_SHIP;
28037 
28038 		case OP_CALL_SSM_STRIKE:
28039 			if (argnum == 0)
28040 				return OPF_SSM_CLASS;
28041 			else if (argnum == 1)
28042 				return OPF_IFF;
28043 			else
28044 				return OPF_SHIP;
28045 
28046 		case OP_SELF_DESTRUCT:
28047 			return OPF_SHIP;
28048 
28049 		case OP_NEXT_MISSION:
28050 			return OPF_MISSION_NAME;
28051 
28052 		case OP_END_CAMPAIGN:
28053 			return OPF_BOOL;
28054 
28055 		case OP_END_OF_CAMPAIGN:
28056 			return OPF_NONE;
28057 
28058 		case OP_PREVIOUS_GOAL_TRUE:
28059 		case OP_PREVIOUS_GOAL_FALSE:
28060 			if ( argnum == 0 )
28061 				return OPF_MISSION_NAME;
28062 			else if (argnum == 1 )
28063 				return OPF_GOAL_NAME;
28064 			else
28065 				return OPF_BOOL;
28066 
28067 		case OP_PREVIOUS_GOAL_INCOMPLETE:
28068 			return OPF_GOAL_NAME;
28069 
28070 		case OP_PREVIOUS_EVENT_TRUE:
28071 		case OP_PREVIOUS_EVENT_FALSE:
28072 		case OP_PREVIOUS_EVENT_INCOMPLETE:
28073 			if (!argnum)
28074 				return OPF_MISSION_NAME;
28075 			else if ( argnum == 1 )
28076 				return OPF_EVENT_NAME;
28077 			else
28078 				return OPF_BOOL;
28079 
28080 		case OP_SABOTAGE_SUBSYSTEM:
28081 			if (!argnum)
28082 				return OPF_SHIP;		// changed from OPF_SHIP_NOT_PLAYER by Goober5000: now it can be the player ship also
28083 			else if (argnum == 1 )
28084 				return OPF_SUBSYS_OR_GENERIC;
28085 			else
28086 				return OPF_POSITIVE;
28087 
28088 		case OP_REPAIR_SUBSYSTEM:
28089 		case OP_SET_SUBSYSTEM_STRNGTH:
28090 			if (!argnum)
28091 				return OPF_SHIP;		// changed from OPF_SHIP_NOT_PLAYER by Goober5000: now it can be the player ship also
28092 			else if (argnum == 1 )
28093 				return OPF_SUBSYS_OR_GENERIC;
28094 			else if (argnum == 2)
28095 				return OPF_POSITIVE;
28096 			else
28097 				return OPF_BOOL;
28098 
28099 		case OP_DESTROY_SUBSYS_INSTANTLY:
28100 			if (argnum == 0)
28101 				return OPF_SHIP;
28102 			else
28103 				return OPF_SUBSYS_OR_GENERIC;
28104 
28105 		case OP_WAYPOINTS_DONE:
28106 			if ( argnum == 0 )
28107 				return OPF_SHIP_WING;
28108 			else
28109 				return OPF_WAYPOINT_PATH;
28110 
28111 		case OP_WAYPOINTS_DONE_DELAY:
28112 			if ( argnum == 0 )
28113 				return OPF_SHIP_WING;
28114 			else if ( argnum == 1 )
28115 				return OPF_WAYPOINT_PATH;
28116 			else
28117 				return OPF_POSITIVE;
28118 
28119 		case OP_INVALIDATE_GOAL:
28120 		case OP_VALIDATE_GOAL:
28121 			return OPF_GOAL_NAME;
28122 
28123 		case OP_SHIP_TYPE_DESTROYED:
28124 			if ( argnum == 0 )
28125 				return OPF_POSITIVE;
28126 			else
28127 				return OPF_SHIP_TYPE;
28128 
28129 		case OP_KEY_PRESSED:
28130 			if (!argnum)
28131 				return OPF_KEYPRESS;
28132 			else
28133 				return OPF_POSITIVE;
28134 
28135 		case OP_KEY_RESET:
28136 		case OP_KEY_RESET_MULTIPLE:
28137 			return OPF_KEYPRESS;
28138 
28139 		case OP_EVENT_TRUE:
28140 		case OP_EVENT_FALSE:
28141 			return OPF_EVENT_NAME;
28142 
28143 		case OP_EVENT_INCOMPLETE:
28144 		case OP_EVENT_TRUE_DELAY:
28145 		case OP_EVENT_FALSE_DELAY:
28146 		case OP_EVENT_TRUE_MSECS_DELAY:
28147 		case OP_EVENT_FALSE_MSECS_DELAY:
28148 			if (argnum == 0)
28149 				return OPF_EVENT_NAME;
28150 			else if (argnum == 1)
28151 				return OPF_POSITIVE;
28152 			else if (argnum == 2)
28153 				return OPF_BOOL;
28154 			else
28155 				return OPF_NONE;
28156 
28157 		case OP_GOAL_INCOMPLETE:
28158 		case OP_GOAL_TRUE_DELAY:
28159 		case OP_GOAL_FALSE_DELAY:
28160 			if (!argnum)
28161 				return OPF_GOAL_NAME;
28162 			else
28163 				return OPF_POSITIVE;
28164 
28165 		case OP_AI_PLAY_DEAD:
28166 		case OP_AI_PLAY_DEAD_PERSISTENT:
28167 		case OP_AI_CHASE_ANY:
28168 			return OPF_POSITIVE;
28169 
28170 		case OP_AI_STAY_STILL:
28171 			if (!argnum)
28172 				return OPF_SHIP_POINT;
28173 			else
28174 				return OPF_POSITIVE;
28175 
28176 		case OP_AI_FORM_ON_WING:
28177 			return OPF_SHIP;
28178 
28179 		case OP_GOOD_REARM_TIME:
28180 			if ( argnum == 0 )
28181 				return OPF_IFF;
28182 			else
28183 				return OPF_POSITIVE;
28184 
28185 		case OP_NUM_PLAYERS:
28186 			return OPF_POSITIVE;
28187 
28188 		case OP_SKILL_LEVEL_AT_LEAST:
28189 			return OPF_SKILL_LEVEL;
28190 
28191 		case OP_GRANT_MEDAL:
28192 		case OP_WAS_MEDAL_GRANTED:
28193 			return OPF_MEDAL_NAME;
28194 
28195 		case OP_IS_CARGO_KNOWN:
28196 			return OPF_SHIP;
28197 
28198 		case OP_CARGO_KNOWN_DELAY:
28199 			if ( argnum == 0 )
28200 				return OPF_POSITIVE;
28201 			else
28202 				return OPF_SHIP;
28203 
28204 		case OP_HAS_BEEN_TAGGED_DELAY:
28205 			if ( argnum == 0 ) {
28206 				return OPF_POSITIVE;
28207 			} else {
28208 				return OPF_SHIP;
28209 			}
28210 
28211 		case OP_ARE_SHIP_FLAGS_SET:
28212 			if (argnum == 0) {
28213 				return OPF_SHIP;
28214 			} else {
28215 				return OPF_SHIP_FLAG;
28216 			}
28217 
28218 		case OP_CAP_SUBSYS_CARGO_KNOWN_DELAY:
28219 			if ( argnum == 0 ) {
28220 				return OPF_POSITIVE;
28221 			} else if ( argnum == 1 ) {
28222 				return OPF_SHIP;
28223 			} else {
28224 				return OPF_SUBSYSTEM;
28225 			}
28226 
28227 		case OP_ALLOW_SHIP:
28228 		case OP_TECH_ADD_SHIP:
28229 			return OPF_SHIP_CLASS_NAME;
28230 
28231 		case OP_ALLOW_WEAPON:
28232 		case OP_TECH_ADD_WEAPON:
28233 			return OPF_WEAPON_NAME;
28234 
28235 		case OP_TECH_ADD_INTEL:
28236 		case OP_TECH_REMOVE_INTEL:
28237 			return OPF_INTEL_NAME;
28238 
28239 		case OP_TECH_ADD_INTEL_XSTR:
28240 		case OP_TECH_REMOVE_INTEL_XSTR:
28241 			return !(argnum % 2) ? OPF_INTEL_NAME : OPF_NUMBER;
28242 
28243 		case OP_TECH_RESET_TO_DEFAULT:
28244 			return OPF_NONE;
28245 
28246 		case OP_CHANGE_PLAYER_SCORE:
28247 			if (argnum == 0)
28248 				return OPF_NUMBER;
28249 			else
28250 				return OPF_SHIP;
28251 
28252 		case OP_CHANGE_TEAM_SCORE:
28253 			return OPF_NUMBER;
28254 
28255 		case OP_SHIP_VAPORIZE:
28256 		case OP_SHIP_NO_VAPORIZE:
28257 			return OPF_SHIP;
28258 
28259 		case OP_DONT_COLLIDE_INVISIBLE:
28260 		case OP_COLLIDE_INVISIBLE:
28261 			return OPF_SHIP;
28262 
28263 		case OP_SET_MOBILE:
28264 		case OP_SET_IMMOBILE:
28265 			return OPF_SHIP;
28266 
28267 		case OP_IGNORE_KEY:
28268 			if (argnum == 0)
28269 				return OPF_NUMBER;
28270 			else
28271 				return OPF_KEYPRESS;
28272 
28273 
28274 		case OP_WARP_BROKEN:
28275 		case OP_WARP_NOT_BROKEN:
28276 		case OP_WARP_NEVER:
28277 		case OP_WARP_ALLOWED:
28278 			return OPF_SHIP;
28279 
28280 		case OP_SET_SUBSPACE_DRIVE:
28281 			if (argnum == 0)
28282 				return OPF_BOOL;
28283 			else
28284 				return OPF_SHIP;
28285 
28286 		case OP_FLASH_HUD_GAUGE:
28287 			return OPF_BUILTIN_HUD_GAUGE;
28288 
28289 		case OP_GOOD_SECONDARY_TIME:
28290 			if ( argnum == 0 )
28291 				return OPF_IFF;
28292 			else if ( argnum == 1 )
28293 				return OPF_POSITIVE;
28294 			else if ( argnum == 2 )
28295 				return OPF_HUGE_WEAPON;
28296 			else
28297 				return OPF_SHIP;
28298 
28299 		case OP_PERCENT_SHIPS_ARRIVED:
28300 		case OP_PERCENT_SHIPS_DEPARTED:
28301 		case OP_PERCENT_SHIPS_DESTROYED:
28302 			if ( argnum == 0 ){
28303 				return OPF_POSITIVE;
28304 			} else {
28305 				return OPF_SHIP_WING;
28306 			}
28307 			break;
28308 
28309 		case OP_PERCENT_SHIPS_DISARMED:
28310 		case OP_PERCENT_SHIPS_DISABLED:
28311 			if ( argnum == 0 ){
28312 				return OPF_POSITIVE;
28313 			} else {
28314 				return OPF_SHIP;
28315 			}
28316 			break;
28317 
28318 		case OP_DEPART_NODE_DELAY:
28319 			if ( argnum == 0 ){
28320 				return OPF_POSITIVE;
28321 			} else if ( argnum == 1 ){
28322 				return OPF_JUMP_NODE_NAME;
28323 			} else {
28324 				return OPF_SHIP;
28325 			}
28326 
28327 		case OP_DESTROYED_DEPARTED_DELAY:
28328 			if ( argnum == 0 ){
28329 				return OPF_POSITIVE;
28330 			} else {
28331 				return OPF_SHIP_WING;
28332 			}
28333 
28334 		case OP_JETTISON_CARGO_DELAY:
28335 		case OP_JETTISON_CARGO_NEW:
28336 			if(argnum == 1){
28337 				return OPF_POSITIVE;
28338 			} else {
28339 				return OPF_SHIP;
28340 			}
28341 
28342 		case OP_SET_DOCKED:
28343 			if (argnum == 0) {
28344 				return OPF_SHIP;
28345 			} else if (argnum == 1) {
28346 				return OPF_DOCKER_POINT;
28347 			} else if (argnum == 2) {
28348 				return OPF_SHIP;
28349 			} else {
28350 				return OPF_DOCKEE_POINT;
28351 			}
28352 
28353 		case OP_CARGO_NO_DEPLETE:
28354 			if (argnum == 0) {
28355 				return OPF_SHIP;
28356 			} else {
28357 				return OPF_NUMBER;
28358 			}
28359 
28360 		case OP_BEAM_FIRE:
28361 			switch(argnum) {
28362 				case 0:
28363 					return OPF_SHIP;
28364 				case 1:
28365 					return OPF_SUBSYSTEM;
28366 				case 2:
28367 					return OPF_SHIP;
28368 				case 3:
28369 					return OPF_SUBSYSTEM;
28370 				case 4:
28371 					return OPF_BOOL;
28372 				default:
28373 					UNREACHABLE("Invalid argnum %d detected!", argnum);
28374 					return OPF_NULL;
28375 			}
28376 
28377 		case OP_BEAM_FIRE_COORDS:
28378 			switch(argnum) {
28379 				case 0:
28380 					return OPF_SHIP;
28381 				case 1:
28382 					return OPF_SUBSYSTEM;
28383 				case 5:
28384 					return OPF_BOOL;
28385 				default:
28386 					return OPF_NUMBER;
28387 			}
28388 
28389 		case OP_BEAM_FLOATING_FIRE:
28390 			switch(argnum) {
28391 				case 0:
28392 					return OPF_WEAPON_NAME;
28393 				case 1:
28394 				case 6:
28395 					return OPF_SHIP_OR_NONE;
28396 				case 2:
28397 					return OPF_IFF;
28398 				case 7:
28399 					return OPF_SUBSYSTEM_OR_NONE;
28400 				default:
28401 					return OPF_NUMBER;
28402 			}
28403 
28404 		case OP_IS_TAGGED:
28405 			return OPF_SHIP;
28406 
28407 		case OP_IS_PLAYER:
28408 			if (argnum == 0) {
28409 				return OPF_BOOL;
28410 			} else {
28411 				return OPF_SHIP;
28412 			}
28413 
28414 		case OP_NUM_KILLS:
28415 		case OP_NUM_ASSISTS:
28416 		case OP_SHIP_SCORE:
28417 		case OP_SHIP_DEATHS:
28418 		case OP_RESPAWNS_LEFT:
28419 			return OPF_SHIP;
28420 
28421 		case OP_SET_RESPAWNS:
28422 			if (argnum == 0 ) {
28423 				return OPF_POSITIVE;
28424 			}
28425 			else {
28426 				return OPF_SHIP;
28427 			}
28428 
28429 		case OP_ADD_REMOVE_HOTKEY:
28430 			if (argnum == 0)
28431 				return OPF_BOOL;
28432 			if (argnum == 1)
28433 				return OPF_POSITIVE;
28434 			else
28435 				return OPF_SHIP_WING;
28436 
28437 		case OP_NUM_TYPE_KILLS:
28438 			if(argnum == 0){
28439 				return OPF_SHIP;
28440 			} else {
28441 				return OPF_SHIP_TYPE;
28442 			}
28443 
28444 		case OP_NUM_CLASS_KILLS:
28445 			if(argnum == 0){
28446 				return OPF_SHIP;
28447 			} else {
28448 				return OPF_SHIP_CLASS_NAME;
28449 			}
28450 
28451 		case OP_BEAM_FREE:
28452 		case OP_BEAM_LOCK:
28453 		case OP_TURRET_FREE:
28454 		case OP_TURRET_LOCK:
28455 		case OP_TURRET_TAGGED_SPECIFIC:
28456 		case OP_TURRET_TAGGED_CLEAR_SPECIFIC:
28457 			if(argnum == 0){
28458 				return OPF_SHIP;
28459 			} else {
28460 				return OPF_SUBSYSTEM;
28461 			}
28462 
28463 		case OP_TURRET_SUBSYS_TARGET_DISABLE:
28464 		case OP_TURRET_SUBSYS_TARGET_ENABLE:
28465 			if(argnum == 0){
28466 				return OPF_SHIP;
28467 			} else {
28468 				return OPF_SUBSYS_OR_GENERIC;
28469 			}
28470 
28471 		case OP_TURRET_CHANGE_WEAPON:
28472 			if(argnum == 0) {
28473 				return OPF_SHIP;
28474 			} else if(argnum == 1) {
28475 				return OPF_SUBSYSTEM;
28476 			} else if(argnum == 2) {
28477 				return OPF_WEAPON_NAME;
28478 			} else if(argnum > 2) {
28479 				return OPF_POSITIVE;
28480 			} else {
28481 				return OPF_NONE;
28482 			}
28483 
28484 		case OP_TURRET_SET_DIRECTION_PREFERENCE:
28485 			if(argnum == 0) {
28486 				return OPF_SHIP;
28487 			} else if(argnum == 1) {
28488 				return OPF_NUMBER;
28489 			} else {
28490 				return OPF_SUBSYSTEM;
28491 			}
28492 
28493 		case OP_TURRET_SET_RATE_OF_FIRE:
28494 			if(argnum == 0) {
28495 				return OPF_SHIP;
28496 			} else if(argnum == 1) {
28497 				return OPF_NUMBER;
28498 			} else {
28499 				return OPF_SUBSYSTEM;
28500 			}
28501 
28502 		case OP_TURRET_SET_OPTIMUM_RANGE:
28503 			if(argnum == 0) {
28504 				return OPF_SHIP;
28505 			} else if(argnum == 1) {
28506 				return OPF_NUMBER;
28507 			} else {
28508 				return OPF_SUBSYSTEM;
28509 			}
28510 
28511 		case OP_TURRET_SET_FORCED_TARGET:
28512 			if (argnum == 0) {
28513 				return OPF_SHIP;
28514 			}
28515 			else if (argnum == 1) {
28516 				return OPF_SHIP;
28517 			}
28518 			else {
28519 				return OPF_SUBSYSTEM;
28520 			}
28521 
28522 		case OP_TURRET_SET_FORCED_SUBSYS_TARGET:
28523 			if (argnum == 0) {
28524 				return OPF_SHIP;
28525 			}
28526 			else if (argnum == 1) {
28527 				return OPF_SUBSYSTEM;
28528 			}
28529 			else if (argnum == 2) {
28530 				return OPF_SHIP;
28531 			}
28532 			else {
28533 				return OPF_SUBSYSTEM;
28534 			}
28535 
28536 		case OP_TURRET_CLEAR_FORCED_TARGET:
28537 			if (argnum == 0) {
28538 				return OPF_SHIP;
28539 			}
28540 			else {
28541 				return OPF_SUBSYSTEM;
28542 			}
28543 
28544 		case OP_TURRET_SET_INACCURACY:
28545 			if (argnum == 0) {
28546 				return OPF_SHIP;
28547 			}
28548 			else if (argnum == 1) {
28549 				return OPF_NUMBER;
28550 			}
28551 			else {
28552 				return OPF_SUBSYSTEM;
28553 			}
28554 
28555 		case OP_TURRET_SET_TARGET_PRIORITIES:
28556 			if(argnum == 0) {
28557 				return OPF_SHIP;
28558 			} else if(argnum == 1) {
28559 				return OPF_SUBSYSTEM;
28560 			} else if(argnum == 2) {
28561 				return OPF_BOOL;
28562 			} else {
28563 				return OPF_TARGET_PRIORITIES;
28564 			}
28565 
28566 		case OP_SET_ARMOR_TYPE:
28567 			if(argnum == 0) {
28568 				return OPF_SHIP;
28569 			} else if(argnum == 1) {
28570 				return OPF_BOOL;
28571 			} else if(argnum == 2) {
28572 				return OPF_ARMOR_TYPE;
28573 			} else {
28574 				return OPF_SUBSYSTEM;
28575 			}
28576 
28577 		case OP_WEAPON_SET_DAMAGE_TYPE:
28578 			if(argnum == 0) {
28579 				return OPF_BOOL;
28580 			} else if(argnum == 1) {
28581 				return OPF_DAMAGE_TYPE;
28582 			} else if(argnum == 2) {
28583 				return OPF_BOOL;
28584 			} else {
28585 				return OPF_WEAPON_NAME;
28586 			}
28587 
28588 		case OP_SHIP_SET_DAMAGE_TYPE:
28589 			if(argnum == 0) {
28590 				return OPF_BOOL;
28591 			} else if(argnum == 1) {
28592 				return OPF_DAMAGE_TYPE;
28593 			} else if(argnum == 2) {
28594 				return OPF_BOOL;
28595 			} else {
28596 				return OPF_SHIP;
28597 			}
28598 
28599 		case OP_SHIP_SHOCKWAVE_SET_DAMAGE_TYPE:
28600 			if(argnum == 0) {
28601 				return OPF_DAMAGE_TYPE;
28602 			} else if(argnum == 1) {
28603 				return OPF_BOOL;
28604 			} else {
28605 				return OPF_SHIP_CLASS_NAME;
28606 			}
28607 
28608 		case OP_FIELD_SET_DAMAGE_TYPE:
28609 			if(argnum == 0) {
28610 				return OPF_DAMAGE_TYPE;
28611 			} else {
28612 				return OPF_BOOL;
28613 			}
28614 
28615 		case OP_TURRET_SET_TARGET_ORDER:
28616 			if(argnum == 0) {
28617 				return OPF_SHIP;
28618 			} else if(argnum == 1) {
28619 				return OPF_SUBSYSTEM;
28620 			} else {
28621 				return OPF_TURRET_TARGET_ORDER;
28622 			}
28623 
28624 		case OP_SHIP_TURRET_TARGET_ORDER:
28625 			if(argnum == 0) {
28626 				return OPF_SHIP;
28627 			} else {
28628 				return OPF_TURRET_TARGET_ORDER;
28629 			}
28630 
28631 		case OP_LOCK_ROTATING_SUBSYSTEM:
28632 		case OP_FREE_ROTATING_SUBSYSTEM:
28633 		case OP_REVERSE_ROTATING_SUBSYSTEM:
28634 			if (argnum == 0)
28635 				return OPF_SHIP;
28636 			else
28637 				return OPF_ROTATING_SUBSYSTEM;
28638 
28639 		case OP_ROTATING_SUBSYS_SET_TURN_TIME:
28640 			if (argnum == 0)
28641 				return OPF_SHIP;
28642 			else if (argnum == 1)
28643 				return OPF_ROTATING_SUBSYSTEM;
28644 			else if (argnum == 2)
28645 				return OPF_NUMBER;
28646 			else
28647 				return OPF_POSITIVE;
28648 
28649 		case OP_TRIGGER_SUBMODEL_ANIMATION:
28650 			if (argnum == 0)
28651 				return OPF_SHIP;
28652 			else if (argnum == 1)
28653 				return OPF_ANIMATION_TYPE;
28654 			else if (argnum == 2 || argnum == 3)
28655 				return OPF_NUMBER;
28656 			else if (argnum == 4)
28657 				return OPF_BOOL;
28658 			else if (argnum == 5)
28659 				return OPF_SUBSYSTEM;
28660 			else
28661 				return OPF_NONE;
28662 
28663 		case OP_BEAM_FREE_ALL:
28664 		case OP_BEAM_LOCK_ALL:
28665 		case OP_TURRET_FREE_ALL:
28666 		case OP_TURRET_LOCK_ALL:
28667 		case OP_TURRET_TAGGED_ONLY_ALL:
28668 		case OP_TURRET_TAGGED_CLEAR_ALL:
28669 			return OPF_SHIP;
28670 
28671 		case OP_ADD_REMOVE_ESCORT:
28672 			if(argnum == 0){
28673 				return OPF_SHIP;
28674 			} else {
28675 				return OPF_NUMBER;
28676 			}
28677 
28678 		case OP_AWACS_SET_RADIUS:
28679 			if(argnum == 0){
28680 				return OPF_SHIP;
28681 			} else if(argnum == 1){
28682 				return OPF_AWACS_SUBSYSTEM;
28683 			} else {
28684 				return OPF_NUMBER;
28685 			}
28686 
28687 		case OP_PRIMITIVE_SENSORS_SET_RANGE:
28688 			if (!argnum)
28689 				return OPF_SHIP;
28690 			else
28691 				return OPF_NUMBER;
28692 
28693 		case OP_CAP_WAYPOINT_SPEED:
28694 			if (argnum == 0) {
28695 				return OPF_SHIP;
28696 			} else {
28697 				return OPF_NUMBER;
28698 			}
28699 
28700 		case OP_SUBSYS_SET_RANDOM:
28701 			if (argnum == 0) {
28702 				return OPF_SHIP;
28703 			} else if (argnum == 1 || argnum == 2) {
28704 				return OPF_NUMBER;
28705 			} else {
28706 				return OPF_SUBSYSTEM;
28707 			}
28708 
28709 		case OP_SUPERNOVA_START:
28710 			return OPF_POSITIVE;
28711 
28712 		case OP_SHIELD_RECHARGE_PCT:
28713 		case OP_WEAPON_RECHARGE_PCT:
28714 		case OP_ENGINE_RECHARGE_PCT:
28715 			return OPF_SHIP;
28716 
28717 		case OP_GET_ETS_VALUE:
28718 			if (argnum == 0) {
28719 				return OPF_STRING;
28720 			} else {
28721 				return OPF_SHIP;
28722 			}
28723 
28724 		case OP_GET_POWER_OUTPUT:
28725 			return OPF_SHIP;
28726 
28727 		case OP_SET_ETS_VALUES:
28728 			if (argnum < 3) {
28729 				return OPF_POSITIVE;
28730 			} else {
28731 				return OPF_SHIP;
28732 			}
28733 
28734 		case OP_SHIELD_QUAD_LOW:
28735 			if(argnum == 0){
28736 				return OPF_SHIP;
28737 			} else {
28738 				return OPF_NUMBER;
28739 			}
28740 
28741 		case OP_PRIMARY_AMMO_PCT:
28742 		case OP_SECONDARY_AMMO_PCT:
28743 			if(argnum == 0){
28744 				return OPF_SHIP;
28745 			} else {
28746 				return OPF_NUMBER;
28747 			}
28748 
28749 		// Karajorma
28750 		case OP_GET_PRIMARY_AMMO:
28751 		case OP_SET_PRIMARY_AMMO:
28752 		case OP_GET_SECONDARY_AMMO:
28753 		case OP_SET_SECONDARY_AMMO:
28754 			if(argnum == 0){
28755 				return OPF_SHIP;
28756 			} else {
28757 				return OPF_NUMBER;
28758 			}
28759 
28760 		// Karajorma
28761 		case OP_SET_PRIMARY_WEAPON:
28762 		case OP_SET_SECONDARY_WEAPON:
28763 			if(argnum == 0)
28764 			{
28765 				return OPF_SHIP;
28766 			}
28767 
28768 			else if (argnum == 2)
28769 			{
28770 				return OPF_WEAPON_NAME;
28771 			}
28772 			else
28773 			{
28774 				return OPF_NUMBER;
28775 			}
28776 
28777 		// DahBlount
28778 		case OP_TURRET_GET_PRIMARY_AMMO:
28779 		case OP_TURRET_GET_SECONDARY_AMMO:
28780 		case OP_TURRET_SET_PRIMARY_AMMO:
28781 		case OP_TURRET_SET_SECONDARY_AMMO:
28782 			if (argnum == 0){
28783 				return OPF_SHIP;
28784 			} else if (argnum == 1){
28785 				return OPF_SUBSYSTEM;
28786 			} else {
28787 				return OPF_NUMBER;
28788 			}
28789 
28790 		// Goober5000
28791 		case OP_IS_IN_TURRET_FOV:
28792 			if (argnum == 0 || argnum == 1) {
28793 				return OPF_SHIP;
28794 			} else if (argnum == 2) {
28795 				return OPF_SUBSYSTEM;
28796 			} else {
28797 				return OPF_POSITIVE;
28798 			}
28799 
28800 		case OP_GET_NUM_COUNTERMEASURES:
28801 			return OPF_SHIP;
28802 
28803 		case OP_SET_NUM_COUNTERMEASURES:
28804 			if (argnum == 0)
28805 				return OPF_SHIP;
28806 			else
28807 				return OPF_POSITIVE;
28808 
28809 		// Karajorma
28810 		case OP_LOCK_PRIMARY_WEAPON:
28811 		case OP_UNLOCK_PRIMARY_WEAPON:
28812 		case OP_LOCK_SECONDARY_WEAPON:
28813 		case OP_UNLOCK_SECONDARY_WEAPON:
28814 		// KeldorKatarn
28815 		case OP_LOCK_AFTERBURNER:
28816 		case OP_UNLOCK_AFTERBURNER:
28817 			return OPF_SHIP;
28818 
28819 
28820 		case OP_SET_AFTERBURNER_ENERGY:
28821 		case OP_SET_WEAPON_ENERGY:
28822 		case OP_SET_SHIELD_ENERGY:
28823 			if (argnum == 0) {
28824 				return OPF_POSITIVE;
28825 			}
28826 			else {
28827 				return OPF_SHIP;
28828 			}
28829 
28830 		case OP_SET_AMBIENT_LIGHT:
28831 			return OPF_POSITIVE;
28832 
28833 		case OP_SET_POST_EFFECT:
28834 			if (argnum == 0)
28835 				return OPF_POST_EFFECT;
28836 			else
28837 				return OPF_POSITIVE;
28838 
28839 		case OP_CHANGE_SUBSYSTEM_NAME:
28840 			if (argnum == 0) {
28841 				return OPF_SHIP;
28842 			}
28843 			else if (argnum == 1) {
28844 				return OPF_STRING;
28845 			}
28846 			else {
28847 				return OPF_SUBSYSTEM;
28848 			}
28849 
28850 		case OP_IS_SECONDARY_SELECTED:
28851 		case OP_IS_PRIMARY_SELECTED:
28852 			if(argnum == 0){
28853 				return OPF_SHIP;
28854 			} else {
28855 				return OPF_NUMBER;
28856 			}
28857 
28858 		case OP_DAMAGED_ESCORT_LIST:
28859 			if (argnum < 2)
28860 			{
28861 				return OPF_NUMBER;
28862 			}
28863 			else
28864 			{
28865 				return OPF_SHIP;
28866 			}
28867 
28868 		case OP_DAMAGED_ESCORT_LIST_ALL:
28869 			return OPF_POSITIVE;
28870 
28871 		case OP_CHANGE_SHIP_CLASS:
28872 			if (!argnum)
28873 				return OPF_SHIP_CLASS_NAME;
28874 			else
28875 				return OPF_SHIP;
28876 
28877 		case OP_SHIP_COPY_DAMAGE:
28878 			return OPF_SHIP;
28879 
28880 		case OP_DEACTIVATE_GLOW_POINTS:	//-Bobboau
28881 		case OP_ACTIVATE_GLOW_POINTS:	//-Bobboau
28882 		case OP_DEACTIVATE_GLOW_MAPS:	//-Bobboau
28883 		case OP_ACTIVATE_GLOW_MAPS:		//-Bobboau
28884 			return OPF_SHIP;	//a list of ships that are to be activated/deactivated
28885 		case OP_DEACTIVATE_GLOW_POINT_BANK:
28886 		case OP_ACTIVATE_GLOW_POINT_BANK:
28887 			if (!argnum)
28888 				return OPF_SHIP;		//the ship
28889 			else
28890 				return OPF_POSITIVE;		//the glow bank to disable
28891 
28892 		// taylor
28893 		case OP_SET_SKYBOX_MODEL:
28894 			if (argnum == 0)
28895 				return OPF_SKYBOX_MODEL_NAME;
28896 			else if (argnum == 1)
28897 				return OPF_BOOL;
28898 			else
28899 				return OPF_SKYBOX_FLAGS;
28900 
28901 		case OP_SET_SKYBOX_ORIENT:
28902 			return OPF_NUMBER;
28903 
28904 		// Goober5000 - this is complicated :)
28905 		case OP_SET_SUPPORT_SHIP:
28906 			if ((argnum == 0) || (argnum == 2))
28907 				return OPF_DEPARTURE_LOCATION;	// use this for both because we don't want Near Ship or In Front of Ship
28908 			if ((argnum == 1) || (argnum == 3))
28909 				return OPF_SHIP_WITH_BAY;		// same - we don't want to anchor around anything without a docking bay
28910 			if (argnum == 4)
28911 				return OPF_SUPPORT_SHIP_CLASS;
28912 			if (argnum == 5)
28913 				return OPF_NUMBER;
28914 			if (argnum == 6)
28915 				return OPF_NUMBER;
28916 			break;
28917 
28918 		// Goober5000
28919 		case OP_SET_ARRIVAL_INFO:
28920 			if (argnum == 0)
28921 				return OPF_SHIP_WING;
28922 			else if (argnum == 1)
28923 				return OPF_ARRIVAL_LOCATION;
28924 			else if (argnum == 2)
28925 				return OPF_ARRIVAL_ANCHOR_ALL;
28926 			else if (argnum == 3)
28927 				return OPF_NUMBER;
28928 			else if (argnum == 4 || argnum == 5)
28929 				return OPF_POSITIVE;
28930 			else if (argnum == 6)
28931 				return OPF_BOOL;
28932 			break;
28933 
28934 		// Goober5000
28935 		case OP_SET_DEPARTURE_INFO:
28936 			if (argnum == 0)
28937 				return OPF_SHIP_WING;
28938 			else if (argnum == 1)
28939 				return OPF_DEPARTURE_LOCATION;
28940 			else if (argnum == 2)
28941 				return OPF_SHIP_WITH_BAY;
28942 			else if (argnum == 3 || argnum == 4)
28943 				return OPF_NUMBER;
28944 			else if (argnum == 5)
28945 				return OPF_BOOL;
28946 			break;
28947 
28948 		case OP_KAMIKAZE:
28949 			if (argnum==0)
28950 				return OPF_POSITIVE;
28951 			else
28952 				return OPF_SHIP_WING;
28953 
28954 		case OP_NUM_SHIPS_IN_BATTLE:	//phreak modified by FUBAR
28955 			return OPF_SHIP_WING_WHOLETEAM;
28956 
28957 		case OP_NUM_SHIPS_IN_WING:	// Karajorma
28958 			return OPF_WING;
28959 
28960 		case OP_CURRENT_SPEED:
28961 			return OPF_SHIP_WING;
28962 
28963 		case OP_PRIMARY_FIRED_SINCE:
28964 		case OP_SECONDARY_FIRED_SINCE:
28965 			if (argnum == 0)
28966 				return OPF_SHIP;
28967 			else
28968 				return OPF_POSITIVE;
28969 
28970 		case OP_HAS_PRIMARY_WEAPON:
28971 		case OP_HAS_SECONDARY_WEAPON:
28972 			if (argnum == 0)
28973 				return OPF_SHIP;
28974 			else if (argnum == 1)
28975 				return OPF_WEAPON_BANK_NUMBER;
28976 			else
28977 				return OPF_WEAPON_NAME;
28978 
28979 		case OP_DIRECTIVE_VALUE:
28980 			if (argnum == 0)
28981 				return OPF_NUMBER;
28982 			else
28983 				return OPF_BOOL;
28984 
28985 		case OP_GET_HOTKEY:
28986 			return OPF_SHIP_WING;
28987 
28988 		case OP_NAV_IS_VISITED:		//Kazan
28989 		case OP_NAV_DISTANCE:		//kazan
28990 		case OP_NAV_DEL:			//kazan
28991 		case OP_NAV_HIDE:			//kazan
28992 		case OP_NAV_RESTRICT:		//kazan
28993 		case OP_NAV_UNHIDE:			//kazan
28994 		case OP_NAV_UNRESTRICT:		//kazan
28995 		case OP_NAV_SET_VISITED:	//kazan
28996 		case OP_NAV_UNSET_VISITED:	//kazan
28997 		case OP_NAV_SELECT:			//Talon1024
28998 			return OPF_STRING;
28999 
29000 		case OP_NAV_SET_CARRY:		//kazan
29001 		case OP_NAV_UNSET_CARRY:	//kazan
29002 			return OPF_SHIP_WING;
29003 
29004 		case OP_NAV_SET_NEEDSLINK: // kazan
29005 		case OP_NAV_UNSET_NEEDSLINK:
29006 		case OP_NAV_ISLINKED:
29007 				return OPF_SHIP;
29008 
29009 		case OP_NAV_USECINEMATICS:
29010 		case OP_NAV_USEAP:
29011 			return OPF_BOOL;
29012 
29013 		case OP_NAV_ADD_WAYPOINT:	//kazan
29014 			if (argnum==0)
29015 				return OPF_STRING;
29016 			else if (argnum==1)
29017 				return OPF_WAYPOINT_PATH;
29018 			else if (argnum==2)
29019 				return OPF_POSITIVE;
29020 			else
29021 				return OPF_SHIP_WING_WHOLETEAM;
29022 
29023 		case OP_NAV_ADD_SHIP:		//kazan
29024 			if (argnum==0)
29025 				return OPF_STRING;
29026 			else
29027 				return OPF_SHIP;
29028 
29029 		//<Cutscenes>
29030 		case OP_SCRAMBLE_MESSAGES:
29031 		case OP_UNSCRAMBLE_MESSAGES:
29032 			return OPF_SHIP;
29033 
29034 		case OP_CUTSCENES_GET_FOV:
29035 			return OPF_NONE;
29036 
29037 		case OP_CUTSCENES_SET_CUTSCENE_BARS:
29038 		case OP_CUTSCENES_UNSET_CUTSCENE_BARS:
29039 		case OP_CUTSCENES_FADE_IN:
29040 		case OP_CUTSCENES_FADE_OUT:
29041 		case OP_CUTSCENES_SET_TIME_COMPRESSION:
29042 			return OPF_POSITIVE;
29043 
29044 		case OP_CUTSCENES_SET_FOV:
29045 			return OPF_NUMBER;
29046 
29047 		case OP_CUTSCENES_SET_CAMERA:
29048 			return OPF_STRING;
29049 
29050 		case OP_CUTSCENES_SET_CAMERA_POSITION:
29051 		case OP_CUTSCENES_SET_CAMERA_FACING:
29052 		case OP_CUTSCENES_SET_CAMERA_ROTATION:
29053 			if(argnum < 3)
29054 				return OPF_NUMBER;
29055 			else
29056 				return OPF_POSITIVE;
29057 
29058 		case OP_CUTSCENES_SET_CAMERA_FACING_OBJECT:
29059 			if(argnum < 1)
29060 				return OPF_SHIP_WING_POINT;
29061 			else
29062 				return OPF_POSITIVE;
29063 
29064 		case OP_CUTSCENES_SET_CAMERA_FOV:
29065 			return OPF_POSITIVE;
29066 
29067 		case OP_CUTSCENES_SET_CAMERA_HOST:
29068 		case OP_CUTSCENES_SET_CAMERA_TARGET:
29069 			if(argnum < 1)
29070 				return OPF_SHIP_WING_POINT_OR_NONE;
29071 			else
29072 				return OPF_SUBSYSTEM_OR_NONE;
29073 
29074 		case OP_CUTSCENES_RESET_CAMERA:
29075 			return OPF_BOOL;
29076 
29077 		case OP_CUTSCENES_RESET_FOV:
29078 		case OP_CUTSCENES_RESET_TIME_COMPRESSION:
29079 			return OPF_NONE;
29080 
29081 		case OP_CUTSCENES_FORCE_PERSPECTIVE:
29082 			if(argnum==0)
29083 				return OPF_BOOL;
29084 			else
29085 				return OPF_POSITIVE;
29086 
29087 		case OP_SET_CAMERA_SHUDDER:
29088 			return OPF_POSITIVE;
29089 
29090 		case OP_CUTSCENES_SHOW_SUBTITLE:
29091 			if(argnum < 2)
29092 				return OPF_NUMBER;
29093 			else if (argnum == 2)
29094 				return OPF_STRING;
29095 			else if (argnum == 3)
29096 				return OPF_POSITIVE;
29097 			else if (argnum == 4)
29098 				return OPF_STRING;
29099 			else if (argnum == 5)
29100 				return OPF_POSITIVE;
29101 			else if (argnum < 8)
29102 				return OPF_BOOL;
29103 			else if (argnum < 12)
29104 				return OPF_POSITIVE;
29105 			else if (argnum == 12 )
29106 				return OPF_BOOL;
29107 			else
29108 				return OPF_NONE;
29109 
29110 		case OP_CUTSCENES_SHOW_SUBTITLE_TEXT:
29111 			if (argnum == 0)
29112 				return OPF_MESSAGE_OR_STRING;
29113 			else if (argnum == 1 || argnum == 2)
29114 				return OPF_NUMBER;
29115 			else if (argnum == 3 || argnum == 4)
29116 				return OPF_BOOL;
29117 			else if (argnum >= 5 && argnum <= 10)
29118 				return OPF_POSITIVE;
29119 			else if (argnum == 11)
29120 				return OPF_FONT;
29121 			else if (argnum == 12)
29122 				return OPF_BOOL;
29123 			else
29124 				return OPF_NONE;
29125 
29126 		case OP_CUTSCENES_SHOW_SUBTITLE_IMAGE:
29127 			if (argnum == 0)
29128 				return OPF_STRING;
29129 			else if (argnum == 1 || argnum == 2)
29130 				return OPF_NUMBER;
29131 			else if (argnum == 3 || argnum == 4)
29132 				return OPF_BOOL;
29133 			else if (argnum >= 5 && argnum <= 8)
29134 				return OPF_POSITIVE;
29135 			else if (argnum == 9)
29136 				return OPF_BOOL;
29137 			else
29138 				return OPF_NONE;
29139 
29140 		//</Cutscenes>
29141 
29142 		case OP_JUMP_NODE_SET_JUMPNODE_NAME: //CommanderDJ
29143 			if(argnum==0)
29144 				return OPF_JUMP_NODE_NAME;
29145 			else if (argnum==1)
29146 				return OPF_STRING;
29147 			else
29148 				return OPF_NONE;
29149 
29150 		case OP_JUMP_NODE_SET_JUMPNODE_COLOR:
29151 			if(argnum==0)
29152 				return OPF_JUMP_NODE_NAME;
29153 			else
29154 				return OPF_POSITIVE;
29155 
29156 		case OP_JUMP_NODE_SET_JUMPNODE_MODEL:
29157 			if(argnum==0)
29158 				return OPF_JUMP_NODE_NAME;
29159 			else if (argnum == 1)
29160 				return OPF_STRING;
29161 			else
29162 				return OPF_BOOL;
29163 
29164 		case OP_JUMP_NODE_SHOW_JUMPNODE:
29165 		case OP_JUMP_NODE_HIDE_JUMPNODE:
29166 				return OPF_JUMP_NODE_NAME;
29167 
29168 		case OP_CHANGE_BACKGROUND:
29169 			return OPF_POSITIVE;
29170 
29171 		case OP_ADD_BACKGROUND_BITMAP:
29172 			if (argnum == 0)
29173 				return OPF_BACKGROUND_BITMAP;
29174 			else if (argnum == 8)
29175 				return OPF_VARIABLE_NAME;
29176 			else
29177 				return OPF_POSITIVE;
29178 
29179 		case OP_REMOVE_BACKGROUND_BITMAP:
29180 			return OPF_POSITIVE;
29181 
29182 		case OP_ADD_SUN_BITMAP:
29183 			if (argnum == 0)
29184 				return OPF_SUN_BITMAP;
29185 			else if (argnum == 5)
29186 				return OPF_VARIABLE_NAME;
29187 			else
29188 				return OPF_POSITIVE;
29189 
29190 		case OP_REMOVE_SUN_BITMAP:
29191 			return OPF_POSITIVE;
29192 
29193 		case OP_NEBULA_CHANGE_STORM:
29194 			return OPF_NEBULA_STORM_TYPE;
29195 
29196 		case OP_NEBULA_CHANGE_PATTERN:
29197 			return OPF_NEBULA_PATTERN;
29198 
29199 		case OP_NEBULA_TOGGLE_POOF:
29200 			if (!argnum)
29201 				return OPF_NEBULA_POOF;
29202 			else
29203 				return OPF_BOOL;
29204 
29205 		case OP_NEBULA_CHANGE_FOG_COLOR:
29206 			return OPF_POSITIVE;
29207 
29208 		case OP_SCRIPT_EVAL_NUM:
29209 		case OP_SCRIPT_EVAL_BLOCK:
29210 		case OP_SCRIPT_EVAL:
29211 			return OPF_STRING;
29212 
29213 		case OP_SCRIPT_EVAL_STRING:
29214 			if (!argnum)
29215 				return OPF_STRING;
29216 			else
29217 				return OPF_VARIABLE_NAME;
29218 
29219 		case OP_SCRIPT_EVAL_MULTI:
29220 			if (argnum == 0)
29221 				return OPF_STRING;
29222 			else if (argnum == 1)
29223 				return OPF_BOOL;
29224 			else
29225 				return OPF_SHIP;
29226 
29227 		case OP_CHANGE_IFF_COLOR:
29228 			if ((argnum == 0) || (argnum == 1))
29229 				return OPF_IFF;
29230 			else if ((argnum >= 2) && (argnum <=4))
29231 				return OPF_POSITIVE;
29232 			else return OPF_SHIP_WING;
29233 
29234 		case OP_HUD_DISPLAY_GAUGE:
29235 			if ( argnum == 0 ) {
29236 				return OPF_POSITIVE;
29237 			} else {
29238 				return OPF_HUD_ELEMENT;
29239 			}
29240 
29241 		case OP_DISABLE_ETS:
29242 		case OP_ENABLE_ETS:
29243 				return OPF_SHIP;
29244 
29245 		case OP_IS_FACING:
29246 			if (argnum == 0)
29247 				return OPF_SHIP;
29248 			else if (argnum == 1)
29249 				return OPF_SHIP_POINT;
29250 			else
29251 				return OPF_POSITIVE;
29252 
29253 		case OP_FORCE_GLIDE:
29254 			if (argnum == 0)
29255 				return OPF_SHIP;
29256 			else
29257 				return OPF_BOOL;
29258 
29259 		case OP_HUD_SET_DIRECTIVE:
29260 			if (argnum == 0)
29261 				return OPF_CUSTOM_HUD_GAUGE;
29262 			else
29263 				return OPF_STRING;
29264 
29265 		case OP_HUD_GAUGE_SET_ACTIVE:
29266 			if (argnum == 0)
29267 				return OPF_CUSTOM_HUD_GAUGE;
29268 			else
29269 				return OPF_BOOL;
29270 
29271 		case OP_HUD_ACTIVATE_GAUGE_TYPE:
29272 			if (argnum == 0)
29273 				return OPF_BUILTIN_HUD_GAUGE;
29274 			else
29275 				return OPF_BOOL;
29276 
29277 		case OP_HUD_SET_CUSTOM_GAUGE_ACTIVE:
29278 			if (argnum == 0)
29279 				return OPF_BOOL;
29280 			else
29281 				return OPF_CUSTOM_HUD_GAUGE;
29282 
29283 		case OP_HUD_SET_BUILTIN_GAUGE_ACTIVE:
29284 			if (argnum == 0)
29285 				return OPF_BOOL;
29286 			else
29287 				return OPF_BUILTIN_HUD_GAUGE;
29288 
29289 		case OP_GET_COLGROUP_ID:
29290 			return OPF_SHIP;
29291 
29292 		case OP_ADD_TO_COLGROUP:
29293 		case OP_REMOVE_FROM_COLGROUP:
29294 			if (argnum == 0)
29295 				return OPF_SHIP;
29296 			else
29297 				return OPF_POSITIVE;
29298 
29299 		case OP_ADD_TO_COLGROUP2:
29300 		case OP_REMOVE_FROM_COLGROUP2:
29301 			if (argnum == 0)
29302 				return OPF_POSITIVE;
29303 			else
29304 				return OPF_SHIP;
29305 
29306 		case OP_SHIP_EFFECT:
29307 			if (argnum == 0)
29308 				return OPF_SHIP_EFFECT;
29309 			else if (argnum == 1)
29310 				return OPF_NUMBER;
29311 			else
29312 				return OPF_SHIP_WING;
29313 
29314 		case OP_CLEAR_SUBTITLES:
29315 			return OPF_NONE;
29316 
29317 		case OP_SET_THRUSTERS:
29318 			if (argnum == 0)
29319 				return OPF_BOOL;
29320 			else
29321 				return OPF_SHIP;
29322 
29323 		case OP_SET_MOTION_DEBRIS:
29324 			return OPF_BOOL;
29325 
29326 		case OP_REPLACE_TEXTURE:
29327 			if (argnum == 0 || argnum == 1)
29328 				return OPF_STRING;
29329 			else
29330 				return OPF_SHIP_WING;
29331 
29332 		case OP_IS_LANGUAGE:
29333 			return OPF_LANGUAGE;
29334 
29335 		default: {
29336 			auto dynamicSEXP = sexp::get_dynamic_sexp(op);
29337 			if (dynamicSEXP != nullptr) {
29338 				return dynamicSEXP->getArgumentType(argnum);
29339 			}
29340 
29341 			Assertion(false, "query_operator_argument_type(%d, %d) called for unsupported operator type!", op, argnum);
29342 		}
29343 	}
29344 
29345 	return 0;
29346 }
29347 
29348 // DA: 1/7/99  Used to rename ships and waypoints, not variables
29349 // Strictly used in FRED
update_sexp_references(const char * old_name,const char * new_name)29350 void update_sexp_references(const char *old_name, const char *new_name)
29351 {
29352 	int i;
29353 
29354 	Assert(strlen(new_name) < TOKEN_LENGTH);
29355 	for (i = 0; i < Num_sexp_nodes; i++)
29356 	{
29357 		if ((SEXP_NODE_TYPE(i) == SEXP_ATOM) && (Sexp_nodes[i].subtype == SEXP_ATOM_STRING))
29358 			if (!stricmp(CTEXT(i), old_name))
29359 				strcpy(Sexp_nodes[i].text, new_name);
29360 	}
29361 }
29362 
29363 // DA: 1/7/99  Used to rename event names, goal names, not variables
29364 // Strictly used in FRED
update_sexp_references(const char * old_name,const char * new_name,int format)29365 void update_sexp_references(const char *old_name, const char *new_name, int format)
29366 {
29367 	int i;
29368 	if (!strcmp(old_name, new_name)) {
29369 		return;
29370 	}
29371 
29372 	Assert(strlen(new_name) < TOKEN_LENGTH);
29373 	for (i = 0; i < Num_sexp_nodes; i++)
29374 	{
29375 		if (is_sexp_top_level(i))
29376 			update_sexp_references(old_name, new_name, format, i);
29377 	}
29378 }
29379 
29380 // DA: 1/7/99  Used to rename event names, goal names, not variables
29381 // recursive function to update references to a certain type of data
update_sexp_references(const char * old_name,const char * new_name,int format,int node)29382 void update_sexp_references(const char *old_name, const char *new_name, int format, int node)
29383 {
29384 	int i, n, op;
29385 
29386 	if (node < 0)
29387 		return;
29388 
29389 	if ((SEXP_NODE_TYPE(node) == SEXP_LIST) && (Sexp_nodes[node].subtype == SEXP_ATOM_LIST))
29390 	{
29391 		if (Sexp_nodes[node].first)
29392 			update_sexp_references(old_name, new_name, format, Sexp_nodes[node].first);
29393 
29394 		if (Sexp_nodes[node].rest)
29395 			update_sexp_references(old_name, new_name, format, Sexp_nodes[node].rest);
29396 
29397 		return;
29398 	}
29399 
29400 	if (SEXP_NODE_TYPE(node) != SEXP_ATOM)
29401 		return;
29402 
29403 	if (Sexp_nodes[node].subtype != SEXP_ATOM_OPERATOR)
29404 		return;
29405 
29406 	op = get_operator_index(node);
29407 	Assert(Sexp_nodes[node].first < 0);
29408 	n = Sexp_nodes[node].rest;
29409 	i = 0;
29410 	while (n >= 0)
29411 	{
29412 		if (SEXP_NODE_TYPE(n) == SEXP_LIST)
29413 		{
29414 			update_sexp_references(old_name, new_name, format, Sexp_nodes[n].first);
29415 		}
29416 		else
29417 		{
29418 			Assert((SEXP_NODE_TYPE(n) == SEXP_ATOM) && ((Sexp_nodes[n].subtype == SEXP_ATOM_NUMBER) || (Sexp_nodes[n].subtype == SEXP_ATOM_STRING)));
29419 
29420 			if (query_operator_argument_type(op, i) == format)
29421 			{
29422 				if (!stricmp(CTEXT(n), old_name))
29423 					strcpy(Sexp_nodes[n].text, new_name);
29424 			}
29425 		}
29426 
29427 		n = Sexp_nodes[n].rest;
29428 		i++;
29429 	}
29430 }
29431 
query_referenced_in_sexp(int,const char * name,int * node)29432 int query_referenced_in_sexp(int  /*mode*/, const char *name, int *node)
29433 {
29434 	int i, n, j;
29435 
29436 	for (n=0; n<Num_sexp_nodes; n++){
29437 		if ((SEXP_NODE_TYPE(n) == SEXP_ATOM) && (Sexp_nodes[n].subtype == SEXP_ATOM_STRING)){
29438 			if (!stricmp(CTEXT(n), name)){
29439 				break;
29440 			}
29441 		}
29442 	}
29443 
29444 	if (n == Num_sexp_nodes){
29445 		return 0;
29446 	}
29447 
29448 	if (node){
29449 		*node = n;
29450 	}
29451 
29452 	// so we know it's being used somewhere..  Time to find out where..
29453 	for (i=0; i<MAX_SHIPS; i++)
29454 		if (Ships[i].objnum >= 0) {
29455 			if (query_node_in_sexp(n, Ships[i].arrival_cue)){
29456 				return i | SRC_SHIP_ARRIVAL;
29457 			}
29458 			if (query_node_in_sexp(n, Ships[i].departure_cue)){
29459 				return i | SRC_SHIP_DEPARTURE;
29460 			}
29461 		}
29462 
29463 	for (i=0; i<MAX_WINGS; i++){
29464 		if (Wings[i].wave_count) {
29465 			if (query_node_in_sexp(n, Wings[i].arrival_cue)){
29466 				return i | SRC_WING_ARRIVAL;
29467 			}
29468 			if (query_node_in_sexp(n, Wings[i].departure_cue)){
29469 				return i | SRC_WING_DEPARTURE;
29470 			}
29471 		}
29472 	}
29473 
29474 	for (i=0; i<Num_mission_events; i++){
29475 		if (query_node_in_sexp(n, Mission_events[i].formula)){
29476 			return i | SRC_EVENT;
29477 		}
29478 	}
29479 
29480 	for (i=0; i<Num_goals; i++){
29481 		if (query_node_in_sexp(n, Mission_goals[i].formula)){
29482 			return i | SRC_MISSION_GOAL;
29483 		}
29484 	}
29485 
29486 	for (j=0; j<Num_teams; j++) {
29487 		for (i=0; i<Debriefings[j].num_stages; i++) {
29488 			if (query_node_in_sexp(n, Debriefings[j].stages[i].formula)){
29489 				return i | SRC_DEBRIEFING;
29490 			}
29491 		}
29492 	}
29493 
29494 	for (j=0; j<Num_teams; j++) {
29495 		for (i=0; i<Briefings[j].num_stages; i++) {
29496 			if (query_node_in_sexp(n, Briefings[j].stages[i].formula)){
29497 				return i | SRC_BRIEFING;
29498 			}
29499 		}
29500 	}
29501 
29502 	return SRC_UNKNOWN;
29503 }
29504 
skip_white(const char ** str)29505 void skip_white(const char **str)
29506 {
29507 	if ((**str == ' ') || (**str == '\t')) {
29508 		(*str)++;
29509 	}
29510 }
29511 
validate_float(const char ** str)29512 int validate_float(const char **str)
29513 {
29514 	int count = 0, dot = 0;
29515 
29516 	while (isdigit(**str) || **str == '.') {
29517 		if (**str == '.') {
29518 			if (dot) {
29519 				return -1;
29520 			}
29521 
29522 			dot = 1;
29523 		}
29524 
29525 		(*str)++;
29526 		count++;
29527 	}
29528 
29529 	if (!count) {
29530 		return -1;
29531 	}
29532 
29533 	return 0;
29534 }
29535 
verify_vector(const char * text)29536 int verify_vector(const char *text)
29537 {
29538 	const char *str;
29539 
29540 	if (text == nullptr)
29541 		return -1;
29542 
29543 	auto len = strlen(text);
29544 	if (text[0] != '(' || text[len - 1] != ')'){
29545 		return -1;
29546 	}
29547 
29548 	str = &text[0];
29549 	skip_white(&str);
29550 	if (*str != '('){
29551 		return -1;
29552 	}
29553 
29554 	str++;
29555 	skip_white(&str);
29556 	if (validate_float(&str)){
29557 		return -1;
29558 	}
29559 
29560 	skip_white(&str);
29561 	if (validate_float(&str)){
29562 		return -1;
29563 	}
29564 
29565 	skip_white(&str);
29566 	if (validate_float(&str)){
29567 		return -1;
29568 	}
29569 
29570 	skip_white(&str);
29571 	if (*str != ')'){
29572 		return -1;
29573 	}
29574 
29575 	str++;
29576 	skip_white(&str);
29577 	if (*str){
29578 		return -1;
29579 	}
29580 
29581 	return 0;
29582 }
29583 
29584 /**
29585  * Check if operator return type opr is a valid match for operator argument type opf
29586  */
sexp_query_type_match(int opf,int opr)29587 int sexp_query_type_match(int opf, int opr)
29588 {
29589 	switch (opf) {
29590 		case OPF_NUMBER:
29591 			return ((opr == OPR_NUMBER) || (opr == OPR_POSITIVE));
29592 
29593 		case OPF_POSITIVE:
29594 			// Goober5000's number hack
29595 			return ((opr == OPR_POSITIVE) || (opr == OPR_NUMBER));
29596 
29597 		case OPF_BOOL:
29598 			return (opr == OPR_BOOL);
29599 
29600 		case OPF_NULL:
29601 			return (opr == OPR_NULL);
29602 
29603 		// Goober5000
29604 		case OPF_FLEXIBLE_ARGUMENT:
29605 			return (opr == OPR_FLEXIBLE_ARGUMENT);
29606 
29607 		// Goober5000
29608 		case OPF_ANYTHING:
29609 			// don't match any operators, only data
29610 			return 0;
29611 
29612 		case OPF_AI_GOAL:
29613 			return (opr == OPR_AI_GOAL);
29614 	}
29615 
29616 	return 0;
29617 }
29618 
sexp_error_message(int num)29619 const char *sexp_error_message(int num)
29620 {
29621 	switch (num) {
29622 		case SEXP_CHECK_NONOP_ARGS:
29623 			return "Data shouldn't have arguments";
29624 
29625 		case SEXP_CHECK_OP_EXPECTED:
29626 			return "Operator expected instead of data";
29627 
29628 		case SEXP_CHECK_UNKNOWN_OP:
29629 			return "Unrecognized operator";
29630 
29631 		case SEXP_CHECK_TYPE_MISMATCH:
29632 			return "Argument type mismatch";
29633 
29634 		case SEXP_CHECK_BAD_ARG_COUNT:
29635 			return "Argument count is illegal";
29636 
29637 		case SEXP_CHECK_UNKNOWN_TYPE:
29638 			return "Unknown operator argument type";
29639 
29640 		case SEXP_CHECK_INVALID_NUM:
29641 			return "Not a number";
29642 
29643 		case SEXP_CHECK_INVALID_SHIP:
29644 			return "Invalid ship name";
29645 
29646 		case SEXP_CHECK_INVALID_WING:
29647 			return "Invalid wing name";
29648 
29649 		case SEXP_CHECK_INVALID_SUBSYS:
29650 			return "Invalid subsystem name";
29651 
29652 		case SEXP_CHECK_INVALID_SUBSYS_TYPE:
29653 			return "Invalid subsystem type";
29654 
29655 		case SEXP_CHECK_INVALID_IFF:
29656 			return "Invalid team name";
29657 
29658 		case SEXP_CHECK_INVALID_AI_CLASS:
29659 			return "Invalid AI class name";
29660 
29661 		case SEXP_CHECK_INVALID_POINT:
29662 			return "Invalid waypoint";
29663 
29664 		case SEXP_CHECK_NEGATIVE_NUM:
29665 			return "Negative number not allowed";
29666 
29667 		case SEXP_CHECK_INVALID_SHIP_WING:
29668 			return "Invalid ship/wing name";
29669 
29670 		case SEXP_CHECK_INVALID_SHIP_TYPE:
29671 			return "Invalid ship type";
29672 
29673 		case SEXP_CHECK_UNKNOWN_MESSAGE:
29674 			return "Invalid message name";
29675 
29676 		case SEXP_CHECK_INVALID_PRIORITY:
29677 			return "Invalid priority";
29678 
29679 		case SEXP_CHECK_INVALID_MISSION_NAME:
29680 			return "Invalid mission filename";
29681 
29682 		case SEXP_CHECK_INVALID_GOAL_NAME:
29683 			return "Invalid goal name";
29684 
29685 		case SEXP_CHECK_INVALID_LEVEL:
29686 			return "Mission level too low in tree";
29687 
29688 		case SEXP_CHECK_INVALID_MSG_SOURCE:
29689 			return "Invalid message source";
29690 
29691 		case SEXP_CHECK_INVALID_DOCKER_POINT:
29692 			return "Invalid docker point";
29693 
29694 		case SEXP_CHECK_INVALID_DOCKEE_POINT:
29695 			return "Invalid dockee point";
29696 
29697 		case SEXP_CHECK_ORDER_NOT_ALLOWED:
29698 			return "Ship not allowed to have this order";
29699 
29700 		case SEXP_CHECK_DOCKING_NOT_ALLOWED:
29701 			return "Ship can't dock with target ship";
29702 
29703 		case SEXP_CHECK_NUM_RANGE_INVALID:
29704 			return "Number is out of range";
29705 
29706 		case SEXP_CHECK_INVALID_EVENT_NAME:
29707 			return "Event name is invalid (not known)";
29708 
29709 		case SEXP_CHECK_INVALID_SKILL_LEVEL:
29710 			return "Skill level name is invalid (not known)";
29711 
29712 		case SEXP_CHECK_INVALID_MEDAL_NAME:
29713 			return "Invalid medal name";
29714 
29715 		case SEXP_CHECK_INVALID_WEAPON_NAME:
29716 			return "Invalid weapon name";
29717 
29718 		case SEXP_CHECK_INVALID_INTEL_NAME:
29719 			return "Invalid intel name";
29720 
29721 		case SEXP_CHECK_INVALID_SHIP_CLASS_NAME:
29722 			return "Invalid ship class name";
29723 
29724 		case SEXP_CHECK_INVALID_SKYBOX_NAME:
29725 			return "Invalid skybox name";
29726 
29727 		case SEXP_CHECK_INVALID_SKYBOX_FLAG:
29728 			return "Invalid skybox flag";
29729 
29730 		case SEXP_CHECK_INVALID_JUMP_NODE:
29731 			return "Invalid jump node";
29732 
29733 		case SEXP_CHECK_UNKNOWN_ERROR:
29734 			return "Unknown error";
29735 
29736 		case SEXP_CHECK_INVALID_SUPPORT_SHIP_CLASS:
29737 			return "Invalid support ship class";
29738 
29739 		case SEXP_CHECK_INVALID_SHIP_WITH_BAY:
29740 			return "Ship does not have a hangar bay";
29741 
29742 		case SEXP_CHECK_INVALID_ARRIVAL_LOCATION:
29743 			return "Invalid arrival location";
29744 
29745 		case SEXP_CHECK_INVALID_DEPARTURE_LOCATION:
29746 			return "Invalid departure location";
29747 
29748 		case SEXP_CHECK_INVALID_ARRIVAL_ANCHOR_ALL:
29749 			return "Invalid universal arrival anchor";
29750 
29751 		case SEXP_CHECK_INVALID_SOUNDTRACK_NAME:
29752 			return "Invalid soundtrack name";
29753 
29754 		case SEXP_CHECK_INVALID_PERSONA_NAME:
29755 			return "Invalid persona name";
29756 
29757 		case SEXP_CHECK_INVALID_VARIABLE:
29758 			return "Invalid variable name";
29759 
29760 		case SEXP_CHECK_INVALID_VARIABLE_TYPE:
29761 			return "Invalid variable type";
29762 
29763 		case SEXP_CHECK_INVALID_FONT:
29764 			return "Invalid font";
29765 
29766 		case SEXP_CHECK_INVALID_HUD_ELEMENT:
29767 			return "Invalid HUD element magic name";
29768 
29769 		case SEXP_CHECK_INVALID_SOUND_ENVIRONMENT:
29770 			return "Invalid sound environment";
29771 
29772 		case SEXP_CHECK_INVALID_SOUND_ENVIRONMENT_OPTION:
29773 			return "Invalid sound environment option";
29774 
29775 		case SEXP_CHECK_INVALID_AUDIO_VOLUME_OPTION:
29776 			return "Invalid audio volume option";
29777 
29778 		case SEXP_CHECK_INVALID_EXPLOSION_OPTION:
29779 			return "Invalid explosion option";
29780 
29781 		case SEXP_CHECK_INVALID_SHIP_EFFECT:
29782 			return "Invalid ship effect name";
29783 
29784 		case SEXP_CHECK_INVALID_TURRET_TARGET_ORDER:
29785 			return "Invalid turret target order";
29786 
29787 		case SEXP_CHECK_INVALID_ARMOR_TYPE:
29788 			return "Invalid armor type";
29789 
29790 		case SEXP_CHECK_INVALID_DAMAGE_TYPE:
29791 			return "Invalid damage type";
29792 
29793 		case SEXP_CHECK_INVALID_BUILTIN_HUD_GAUGE:
29794 			return "Invalid builtin HUD gauge";
29795 
29796 		case SEXP_CHECK_INVALID_CUSTOM_HUD_GAUGE:
29797 			return "Invalid custom HUD gauge";
29798 
29799 		case SEXP_CHECK_INVALID_TARGET_PRIORITIES:
29800 			return "Invalid target priorities";
29801 
29802 		case SEXP_CHECK_INVALID_ANIMATION_TYPE:
29803 			return "Invalid animation type";
29804 
29805 		case SEXP_CHECK_INVALID_MISSION_MOOD:
29806 			return "Invalid mission mood";
29807 
29808 		case SEXP_CHECK_INVALID_SHIP_FLAG:
29809 			return "Invalid ship flag";
29810 
29811 		case SEXP_CHECK_INVALID_TEAM_COLOR:
29812 			return "Not a valid Team Color setting";
29813 
29814 		case SEXP_CHECK_INVALID_GAME_SND:
29815 			return "Invalid game sound";
29816 
29817 		case SEXP_CHECK_INVALID_SSM_CLASS:
29818 			return "Invalid SSM class";
29819 
29820 		case SEXP_CHECK_INVALID_FIREBALL:
29821 			return "Invalid fireball";
29822 
29823 		case SEXP_CHECK_INVALID_SPECIES:
29824 			return "Invalid species";
29825 
29826 		default:
29827 			Warning(LOCATION, "Unhandled sexp error code %d!", num);
29828 			return "Unhandled sexp error code!";
29829 	}
29830 }
29831 
query_sexp_ai_goal_valid(int sexp_ai_goal,int ship_num)29832 int query_sexp_ai_goal_valid(int sexp_ai_goal, int ship_num)
29833 {
29834 	int i, op;
29835 
29836 	for (op=0; op<(int)Operators.size(); op++)
29837 		if (Operators[op].value == sexp_ai_goal)
29838 			break;
29839 
29840 	Assert(op < (int)Operators.size());
29841 	for (i=0; i<Num_sexp_ai_goal_links; i++)
29842 		if (Sexp_ai_goal_links[i].op_code == sexp_ai_goal)
29843 			break;
29844 
29845 	Assert(i < Num_sexp_ai_goal_links);
29846 	return ai_query_goal_valid(ship_num, Sexp_ai_goal_links[i].ai_goal);
29847 }
29848 
check_text_for_variable_name(const char * text)29849 int check_text_for_variable_name(const char *text)
29850 {
29851 	char variable_name[TOKEN_LENGTH];
29852 
29853 	// if the text is a variable name, get the variable index
29854 	int sexp_variable_index = get_index_sexp_variable_name(text);
29855 
29856 	// if the text is a formatted variable name, get the variable index
29857 	if (sexp_variable_index < 0 && text[0] == SEXP_VARIABLE_CHAR)
29858 	{
29859 		get_unformatted_sexp_variable_name(variable_name, text);
29860 		sexp_variable_index = get_index_sexp_variable_name(variable_name);
29861 	}
29862 
29863 	return sexp_variable_index;
29864 }
29865 
29866 /**
29867  * Wrapper around Sexp_node[xx].text for normal and variable
29868  */
CTEXT(int n)29869 const char *CTEXT(int n)
29870 {
29871 	int sexp_variable_index = -1;
29872 
29873 	Assertion(n >= 0 && n < Num_sexp_nodes, "Passed an out-of-range node index (%d) to CTEXT!", n);
29874 	if ( n < 0 || n >= Num_sexp_nodes ) {
29875 		return "!INVALID CTEXT NODE INDEX!";
29876 	}
29877 
29878 	// Goober5000 - MWAHAHAHAHAHAHAHA!  Thank you, Volition programmers!  Without
29879 	// the CTEXT wrapper, when-argument would probably be infeasibly difficult to code.
29880 	if (Sexp_nodes[n].flags & SNF_SPECIAL_ARG_IN_NODE)
29881 	{
29882 		if (Fred_running)
29883 		{
29884 			// CTEXT is used when writing sexps to savefiles, so don't translate the argument
29885 			return Sexp_nodes[n].text;
29886 		}
29887 		else
29888 		{
29889 			// make sure we have an argument to replace it with
29890 			if (Sexp_replacement_arguments.empty())
29891 				return Sexp_nodes[n].text;
29892 		}
29893 
29894 		auto current_argument = Sexp_replacement_arguments.back();
29895 		auto text = current_argument.first;
29896 
29897 		// check referenced node for a variable
29898 		if (current_argument.second >= 0)
29899 		{
29900 			int arg_n = current_argument.second;
29901 
29902 			if (!(Sexp_nodes[arg_n].flags & SNF_CHECKED_ARG_FOR_VAR))
29903 			{
29904 				Sexp_nodes[arg_n].cached_variable_index = check_text_for_variable_name(text);
29905 				Sexp_nodes[arg_n].flags |= SNF_CHECKED_ARG_FOR_VAR;
29906 			}
29907 
29908 			sexp_variable_index = Sexp_nodes[arg_n].cached_variable_index;
29909 		}
29910 		// just check the text of the argument for a variable
29911 		else
29912 		{
29913 			sexp_variable_index = check_text_for_variable_name(text);
29914 		}
29915 
29916 		// if we have a variable, return the variable value, else return the regular argument
29917 		if (sexp_variable_index >= 0)
29918 			return Sexp_variables[sexp_variable_index].text;
29919 		else
29920 			return text;
29921 	}
29922 
29923 	// Goober5000 - if not special argument, proceed as normal
29924 	if (Sexp_nodes[n].type & SEXP_FLAG_VARIABLE)
29925 	{
29926 		if (Fred_running)
29927 		{
29928 			sexp_variable_index = get_index_sexp_variable_name(Sexp_nodes[n].text);
29929 			Assert(sexp_variable_index != -1);
29930 		}
29931 		else
29932 		{
29933 			sexp_variable_index = sexp_get_variable_index(n);
29934 		}
29935 		// Reference a Sexp_variable
29936 		// string format -- "Sexp_variables[xx]=number" or "Sexp_variables[xx]=string", where xx is the index
29937 
29938 		Assert( !(Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_NOT_USED) );
29939 		Assert(Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_SET);
29940 
29941 		if (Log_event) {
29942 			Current_event_log_variable_buffer->push_back(Sexp_variables[sexp_variable_index].text);
29943 			Current_event_log_variable_buffer->push_back(Sexp_variables[sexp_variable_index].variable_name);
29944 		}
29945 
29946 		return Sexp_variables[sexp_variable_index].text;
29947 	}
29948 	else
29949 	{
29950 		return Sexp_nodes[n].text;
29951 	}
29952 }
29953 
29954 
29955 /**
29956  * Set all Sexp_variables to type uninitialized
29957  */
init_sexp_vars()29958 void init_sexp_vars()
29959 {
29960 	for (int i=0; i<MAX_SEXP_VARIABLES; i++) {
29961 		Sexp_variables[i].type = SEXP_VARIABLE_NOT_USED;
29962 		Block_variables[i].type = SEXP_VARIABLE_NOT_USED;
29963 	}
29964 }
29965 
29966 /**
29967  * Add a variable to the block variable array rather than the Sexp_variables array
29968  */
add_block_variable(const char * text,const char * var_name,int type,int index)29969 void add_block_variable(const char *text, const char *var_name, int type, int index)
29970 {
29971 	Assert( (index >= 0) && (index < MAX_SEXP_VARIABLES) );
29972 
29973 	strcpy_s(Block_variables[index].text, text);
29974 	strcpy_s(Block_variables[index].variable_name, var_name);
29975 	Block_variables[index].type &= ~SEXP_VARIABLE_NOT_USED;
29976 	Block_variables[index].type = (type | SEXP_VARIABLE_SET);
29977 
29978 }
29979 
29980 /**
29981  * Add a Sexp_variable to be used in a mission.
29982  *
29983  * This should be called from within mission parse.
29984  */
sexp_add_variable(const char * text,const char * var_name,int type,int index)29985 int sexp_add_variable(const char *text, const char *var_name, int type, int index)
29986 {
29987 	// if index == -1, find next open slot
29988 	if (index == -1) {
29989 		for (int i=0; i<MAX_SEXP_VARIABLES; i++) {
29990 			if (Sexp_variables[i].type == SEXP_VARIABLE_NOT_USED) {
29991 				index = i;
29992 				break;
29993 			}
29994 		}
29995 	} else {
29996 		Assert( (index >= 0) && (index < MAX_SEXP_VARIABLES) );
29997 	}
29998 
29999 	if (index >= 0) {
30000 		strcpy_s(Sexp_variables[index].text, text);
30001 		strcpy_s(Sexp_variables[index].variable_name, var_name);
30002 		Sexp_variables[index].type &= ~SEXP_VARIABLE_NOT_USED;
30003 		Sexp_variables[index].type = (type | SEXP_VARIABLE_SET);
30004 	}
30005 
30006 	return index;
30007 }
30008 
30009 // Goober5000 - minor variant of the above that is now required for variable arrays
sexp_add_array_block_variable(int index,bool is_numeric)30010 void sexp_add_array_block_variable(int index, bool is_numeric)
30011 {
30012 	Assert(index >= 0 && index < MAX_SEXP_VARIABLES);
30013 
30014 	strcpy_s(Sexp_variables[index].text, "");
30015 	strcpy_s(Sexp_variables[index].variable_name, "variable array block");
30016 
30017 	if (is_numeric)
30018 		Sexp_variables[index].type = SEXP_VARIABLE_NUMBER | SEXP_VARIABLE_SET;
30019 	else
30020 		Sexp_variables[index].type = SEXP_VARIABLE_STRING | SEXP_VARIABLE_SET;
30021 }
30022 
30023 /**
30024  * Modify a Sexp_variable to be used in a mission
30025  *
30026  * This should be called in mission when an sexp_variable is to be modified
30027  */
sexp_modify_variable(const char * text,int index,bool sexp_callback)30028 void sexp_modify_variable(const char *text, int index, bool sexp_callback)
30029 {
30030 	Assert(index >= 0 && index < MAX_SEXP_VARIABLES);
30031 	Assert(Sexp_variables[index].type & SEXP_VARIABLE_SET);
30032 	Assert( !MULTIPLAYER_CLIENT );
30033 
30034 	if (strchr(text, '$') != nullptr)
30035 	{
30036 		// we want to use the same variable substitution that's in messages etc.
30037 		SCP_string temp_text = text;
30038 		sexp_replace_variable_names_with_values(temp_text);
30039 
30040 		// copy to original buffer
30041 		auto len = temp_text.copy(Sexp_variables[index].text, TOKEN_LENGTH);
30042 		Sexp_variables[index].text[len] = 0;
30043 	}
30044 	else
30045 	{
30046 		// no variables, so no substitution
30047 		strcpy_s(Sexp_variables[index].text, text);
30048 	}
30049 	Sexp_variables[index].type |= SEXP_VARIABLE_MODIFIED;
30050 
30051 	// do multi_callback_here
30052 	// if we're called from the sexp code send a SEXP packet (more efficient)
30053 	if( MULTIPLAYER_MASTER && (Sexp_variables[index].type & SEXP_VARIABLE_NETWORK) && sexp_callback) {
30054 		 Current_sexp_network_packet.start_callback();
30055 		 Current_sexp_network_packet.send_int(index);
30056 		Current_sexp_network_packet.send_string(Sexp_variables[index].text);
30057 		 Current_sexp_network_packet.end_callback();
30058 	}
30059 	// otherwise send a SEXP variable packet
30060 	else if ( (Game_mode & GM_MULTIPLAYER) && (Sexp_variables[index].type & SEXP_VARIABLE_NETWORK) ) {
30061 		send_variable_update_packet(index, Sexp_variables[index].text);
30062 	}
30063 }
30064 
multi_sexp_modify_variable()30065 void multi_sexp_modify_variable()
30066 {
30067 	char value[TOKEN_LENGTH];
30068 	int variable_index = -1;
30069 
30070 	// get the data
30071 	Current_sexp_network_packet.get_int(variable_index);
30072 	if (!Current_sexp_network_packet.get_string(value)) {
30073 		return;
30074 	}
30075 
30076 	// set the sexp_variable
30077 	if ( (variable_index >= 0) && (variable_index < MAX_SEXP_VARIABLES) ) {
30078 		// maybe create it first
30079 		if (!(Sexp_variables[variable_index].type & SEXP_VARIABLE_SET)) {
30080 			mprintf(("Warning; received multi packet for variable index which is not set!  Assuming this should be an array block variable...\n"));
30081 			sexp_add_array_block_variable(variable_index, can_construe_as_integer(value));
30082 		}
30083 
30084 		strcpy_s(Sexp_variables[variable_index].text, value);
30085 	}
30086 }
30087 
sexp_modify_variable(int n)30088 void sexp_modify_variable(int n)
30089 {
30090 	int sexp_variable_index;
30091 	int new_number;
30092 	char number_as_str[TOKEN_LENGTH];
30093 
30094 	Assert(n >= 0);
30095 
30096 	// Only do single player or multi host
30097 	if ( MULTIPLAYER_CLIENT )
30098 		return;
30099 
30100 	// get sexp_variable index
30101 	sexp_variable_index = sexp_get_variable_index(n);
30102 
30103 	if (Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_NUMBER)
30104 	{
30105 		// get new numerical value
30106 		new_number = eval_sexp(CDR(n));
30107 		sprintf(number_as_str, "%d", new_number);
30108 
30109 		// assign to variable
30110 		sexp_modify_variable(number_as_str, sexp_variable_index);
30111 	}
30112 	else if (Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_STRING)
30113 	{
30114 		// get new string
30115 		auto new_text = CTEXT(CDR(n));
30116 
30117 		// assign to variable
30118 		sexp_modify_variable(new_text, sexp_variable_index);
30119 	}
30120 	else
30121 	{
30122 		Error(LOCATION, "Invalid variable type.\n");
30123 	}
30124 }
30125 
is_sexp_node_numeric(int node)30126 bool is_sexp_node_numeric(int node)
30127 {
30128 	Assert(node >= 0);
30129 
30130 	// make the common case fast: if the node has a CAR node, that means it uses an operator;
30131 	// and operators cannot currently return strings
30132 	if (Sexp_nodes[node].first >= 0)
30133 		return true;
30134 
30135 	// if the node text is numeric, the node is too
30136 	if (can_construe_as_integer(Sexp_nodes[node].text))
30137 		return true;
30138 
30139 	// otherwise it's gotta be text
30140 	return false;
30141 }
30142 
30143 // By Goober5000. Very similar to sexp_modify_variable(). Even uses the same multi code! :)
sexp_set_variable_by_index(int node)30144 void sexp_set_variable_by_index(int node)
30145 {
30146 	int sexp_variable_index, new_number;
30147 	bool is_nan, is_nan_forever;
30148 	char number_as_str[TOKEN_LENGTH];
30149 
30150 	Assert(node >= 0);
30151 
30152 	// Only do single player or multi host
30153 	if ( MULTIPLAYER_CLIENT )
30154 		return;
30155 
30156 	// get sexp_variable index
30157 	sexp_variable_index = eval_num(node, is_nan, is_nan_forever);
30158 	if (is_nan || is_nan_forever)
30159 		return;
30160 
30161 	// check range
30162 	if (sexp_variable_index < 0 || sexp_variable_index >= MAX_SEXP_VARIABLES)
30163 	{
30164 		Warning(LOCATION, "set-variable-by-index: sexp variable index %d out of range!  min is 0; max is %d", sexp_variable_index, MAX_SEXP_VARIABLES - 1);
30165 		return;
30166 	}
30167 
30168 	// verify variable set
30169 	if (!(Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_SET))
30170 	{
30171 		// well phooey.  go ahead and create it
30172 		sexp_add_array_block_variable(sexp_variable_index, is_sexp_node_numeric(CDR(node)));
30173 	}
30174 
30175 	if (Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_NUMBER)
30176 	{
30177 		// get new numerical value
30178 		new_number = eval_num(CDR(node), is_nan, is_nan_forever);
30179 		if (is_nan || is_nan_forever)
30180 			strcpy_s(number_as_str, "NaN");
30181 		else
30182 			sprintf(number_as_str, "%d", new_number);
30183 
30184 		// assign to variable
30185 		sexp_modify_variable(number_as_str, sexp_variable_index);
30186 	}
30187 	else if (Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_STRING)
30188 	{
30189 		// get new string
30190 		auto new_text = CTEXT(CDR(node));
30191 
30192 		// assign to variable
30193 		sexp_modify_variable(new_text, sexp_variable_index);
30194 	}
30195 	else
30196 	{
30197 		Error(LOCATION, "Invalid variable type.\n");
30198 	}
30199 }
30200 
30201 // Goober5000
sexp_get_variable_by_index(int node)30202 int sexp_get_variable_by_index(int node)
30203 {
30204 	int sexp_variable_index;
30205 	bool is_nan, is_nan_forever;
30206 
30207 	Assert(node >= 0);
30208 
30209 	// get sexp_variable index
30210 	sexp_variable_index = eval_num(node, is_nan, is_nan_forever);
30211 	if (is_nan)
30212 		return SEXP_NAN;
30213 	if (is_nan_forever)
30214 		return SEXP_NAN_FOREVER;
30215 
30216 	// check range
30217 	if (sexp_variable_index < 0 || sexp_variable_index >= MAX_SEXP_VARIABLES)
30218 	{
30219 		Warning(LOCATION, "get-variable-by-index: sexp variable index %d out of range!  min is 0; max is %d", sexp_variable_index, MAX_SEXP_VARIABLES - 1);
30220 		return 0;
30221 	}
30222 
30223 	if (Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_NOT_USED)
30224 	{
30225 		mprintf(("warning: retrieving a value from a sexp variable which is not in use!\n"));
30226 	}
30227 	else if (!(Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_SET))
30228 	{
30229 		mprintf(("warning: retrieving a value from a sexp variable which is not set!\n"));
30230 	}
30231 
30232 	if (Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_STRING)
30233 	{
30234 		mprintf(("warning: variable %d is a string but it is not possible to return a string value through a sexp!\n", sexp_variable_index));
30235 		return SEXP_NAN_FOREVER;
30236 	}
30237 
30238 	return atoi(Sexp_variables[sexp_variable_index].text);
30239 }
30240 
30241 // Goober5000
30242 // (yes, this reuses a lot of code, but it's a major pain to consolidate it)
sexp_copy_variable_from_index(int node)30243 void sexp_copy_variable_from_index(int node)
30244 {
30245 	int from_index, to_index;
30246 	bool is_nan, is_nan_forever;
30247 
30248 	Assert(node >= 0);
30249 
30250 	// Only do single player or multi host
30251 	if ( MULTIPLAYER_CLIENT )
30252 		return;
30253 
30254 	// get sexp_variable index
30255 	from_index = eval_num(node, is_nan, is_nan_forever);
30256 	if (is_nan || is_nan_forever)
30257 		return;
30258 
30259 	// check range
30260 	if (from_index < 0 || from_index >= MAX_SEXP_VARIABLES)
30261 	{
30262 		Warning(LOCATION, "copy-variable-from-index: sexp variable index %d out of range!  min is 0; max is %d", from_index, MAX_SEXP_VARIABLES - 1);
30263 		return;
30264 	}
30265 
30266 	if (Sexp_variables[from_index].type & SEXP_VARIABLE_NOT_USED)
30267 	{
30268 		mprintf(("warning: retrieving a value from a sexp variable which is not in use!\n"));
30269 	}
30270 	else if (!(Sexp_variables[from_index].type & SEXP_VARIABLE_SET))
30271 	{
30272 		mprintf(("warning: retrieving a value from a sexp variable which is not set!\n"));
30273 	}
30274 
30275 	// now get the variable we are modifying
30276 	to_index = sexp_get_variable_index(CDR(node));
30277 
30278 	// verify matching types
30279 	if ( ((Sexp_variables[from_index].type & SEXP_VARIABLE_NUMBER) && !(Sexp_variables[to_index].type & SEXP_VARIABLE_NUMBER))
30280 		|| ((Sexp_variables[from_index].type & SEXP_VARIABLE_STRING) && !(Sexp_variables[to_index].type & SEXP_VARIABLE_STRING)) )
30281 	{
30282 		Warning(LOCATION, "copy-variable-from-index: cannot copy variables of different types!  source = '%s', destination = '%s'", Sexp_variables[from_index].variable_name, Sexp_variables[to_index].variable_name);
30283 		return;
30284 	}
30285 
30286 	// assign to variable
30287 	sexp_modify_variable(Sexp_variables[from_index].text, to_index);
30288 }
30289 
30290 // Goober5000
30291 // (yes, this reuses a lot of code, but it's a major pain to consolidate it)
30292 // (and yes, that reused a comment :p)
sexp_copy_variable_between_indexes(int node)30293 void sexp_copy_variable_between_indexes(int node)
30294 {
30295 	int from_index, to_index;
30296 	bool is_nan, is_nan_forever;
30297 
30298 	Assert(node >= 0);
30299 
30300 	// Only do single player or multi host
30301 	if ( MULTIPLAYER_CLIENT )
30302 		return;
30303 
30304 	// get sexp_variable indexes
30305 	eval_nums(node, is_nan, is_nan_forever, from_index, to_index);
30306 	if (is_nan || is_nan_forever)
30307 		return;
30308 
30309 	// check ranges
30310 	if (from_index < 0 || from_index >= MAX_SEXP_VARIABLES)
30311 	{
30312 		Warning(LOCATION, "copy-variable-between-indexes: sexp variable index %d out of range!  min is 0; max is %d", from_index, MAX_SEXP_VARIABLES - 1);
30313 		return;
30314 	}
30315 	if (to_index < 0 || to_index >= MAX_SEXP_VARIABLES)
30316 	{
30317 		Warning(LOCATION, "copy-variable-between-indexes: sexp variable index %d out of range!  min is 0; max is %d", to_index, MAX_SEXP_VARIABLES - 1);
30318 		return;
30319 	}
30320 
30321 	if (Sexp_variables[from_index].type & SEXP_VARIABLE_NOT_USED)
30322 	{
30323 		mprintf(("warning: retrieving a value from a sexp variable which is not in use!\n"));
30324 	}
30325 	else if (!(Sexp_variables[from_index].type & SEXP_VARIABLE_SET))
30326 	{
30327 		mprintf(("warning: retrieving a value from a sexp variable which is not set!\n"));
30328 	}
30329 
30330 	if (!(Sexp_variables[to_index].type & SEXP_VARIABLE_SET))
30331 	{
30332 		// well phooey.  go ahead and create it
30333 		sexp_add_array_block_variable(to_index, (Sexp_variables[from_index].type & SEXP_VARIABLE_NUMBER) != 0);
30334 	}
30335 
30336 	// verify matching types
30337 	if ( ((Sexp_variables[from_index].type & SEXP_VARIABLE_NUMBER) && !(Sexp_variables[to_index].type & SEXP_VARIABLE_NUMBER))
30338 		|| ((Sexp_variables[from_index].type & SEXP_VARIABLE_STRING) && !(Sexp_variables[to_index].type & SEXP_VARIABLE_STRING)) )
30339 	{
30340 		Warning(LOCATION, "copy-variable-between-indexes: cannot copy variables of different types!  source = '%s', destination = '%s'", Sexp_variables[from_index].variable_name, Sexp_variables[to_index].variable_name);
30341 		return;
30342 	}
30343 
30344 	// assign to variable
30345 	sexp_modify_variable(Sexp_variables[from_index].text, to_index);
30346 }
30347 
30348 // Different type needed for Fred (1) allow modification of type (2) no callback required
sexp_fred_modify_variable(const char * text,const char * var_name,int index,int type)30349 void sexp_fred_modify_variable(const char *text, const char *var_name, int index, int type)
30350 {
30351 	Assert(index >= 0 && index < MAX_SEXP_VARIABLES);
30352 	Assert(Sexp_variables[index].type & SEXP_VARIABLE_SET);
30353 	Assert( (type & SEXP_VARIABLE_NUMBER) || (type & SEXP_VARIABLE_STRING) );
30354 
30355 	strcpy_s(Sexp_variables[index].text, text);
30356 	strcpy_s(Sexp_variables[index].variable_name, var_name);
30357 	Sexp_variables[index].type = (SEXP_VARIABLE_SET | SEXP_VARIABLE_MODIFIED | type);
30358 }
30359 
30360 /**
30361  * Return index of sexp_variable_name, -1 if not found
30362  */
get_index_sexp_variable_name(const char * text)30363 int get_index_sexp_variable_name(const char *text)
30364 {
30365 	for (int i=0; i<MAX_SEXP_VARIABLES; i++) {
30366 		if (Sexp_variables[i].type & SEXP_VARIABLE_SET) {
30367 			// check case sensitive
30368 			if ( !strcmp(Sexp_variables[i].variable_name, text) ) {
30369 				return i;
30370 			}
30371 		}
30372 	}
30373 
30374 	// not found
30375 	return -1;
30376 }
30377 
30378 /**
30379  * Return index of sexp_variable_name, -1 if not found
30380  */
get_index_sexp_variable_name(SCP_string & text)30381 int get_index_sexp_variable_name(SCP_string &text)
30382 {
30383 	for (int i=0; i<MAX_SEXP_VARIABLES; i++) {
30384 		if (Sexp_variables[i].type & SEXP_VARIABLE_SET) {
30385 			// check case sensitive
30386 			if ( text == Sexp_variables[i].variable_name ) {
30387 				return i;
30388 			}
30389 		}
30390 	}
30391 
30392 	// not found
30393 	return -1;
30394 }
30395 
30396 // Goober5000 - tests whether a variable name starts here
30397 // return index of sexp_variable_name, -1 if not found
get_index_sexp_variable_name_special(const char * startpos)30398 int get_index_sexp_variable_name_special(const char *startpos)
30399 {
30400 	for (int i = MAX_SEXP_VARIABLES - 1; i >= 0; i--) {
30401 		if (Sexp_variables[i].type & SEXP_VARIABLE_SET) {
30402 			// check case sensitive
30403 			// check number of chars in variable name
30404 			if ( !strncmp(startpos, Sexp_variables[i].variable_name, strlen(Sexp_variables[i].variable_name)) ) {
30405 				return i;
30406 			}
30407 		}
30408 	}
30409 
30410 	// not found
30411 	return -1;
30412 }
30413 
30414 // Goober5000 - tests whether a variable name starts here
30415 // return index of sexp_variable_name, -1 if not found
get_index_sexp_variable_name_special(SCP_string & text,size_t startpos)30416 int get_index_sexp_variable_name_special(SCP_string &text, size_t startpos)
30417 {
30418 	for (int i = MAX_SEXP_VARIABLES - 1; i >= 0; i--) {
30419 		if (Sexp_variables[i].type & SEXP_VARIABLE_SET) {
30420 			// check case sensitive
30421 			// check that the variable name starts here, as opposed to farther down the string
30422 			size_t pos = text.find(Sexp_variables[i].variable_name, startpos);
30423 			if (pos != SCP_string::npos && pos == startpos) {
30424 				return i;
30425 			}
30426 		}
30427 	}
30428 
30429 	// not found
30430 	return -1;
30431 }
30432 
30433 // Goober5000
sexp_replace_variable_names_with_values(char * text,int max_len)30434 bool sexp_replace_variable_names_with_values(char *text, int max_len)
30435 {
30436 	Assert(text != nullptr);
30437 	Assert(max_len >= 0);
30438 
30439 	bool replaced_anything = false;
30440 	char *pos = text;
30441 	do {
30442 		// look for the meta-character
30443 		pos = strchr(pos, '$');
30444 
30445 		// found?
30446 		if (pos != nullptr)
30447 		{
30448 			// see if a variable starts at the next char
30449 			int var_index = get_index_sexp_variable_name_special(pos+1);
30450 			if (var_index >= 0)
30451 			{
30452 				// get the replacement string ($variable)
30453 				char what_to_replace[TOKEN_LENGTH+1];
30454 				memset(what_to_replace, 0, TOKEN_LENGTH+1);
30455 				strncpy(what_to_replace, pos, strlen(Sexp_variables[var_index].variable_name) + 1);
30456 
30457 				// replace it
30458 				pos = text + replace_one(text, what_to_replace, Sexp_variables[var_index].text, max_len);
30459 				replaced_anything = true;
30460 			}
30461 			// no match... so keep iterating along the string
30462 			else
30463 			{
30464 				pos++;
30465 			}
30466 		}
30467 	} while (pos != nullptr);
30468 
30469 	return replaced_anything;
30470 }
30471 
30472 // Goober5000
sexp_replace_variable_names_with_values(SCP_string & text)30473 bool sexp_replace_variable_names_with_values(SCP_string &text)
30474 {
30475 	bool replaced_anything = false;
30476 
30477 	size_t lookHere = 0;
30478 	size_t foundHere;
30479 
30480 	do {
30481 		// look for the meta-character
30482 		foundHere = text.find('$', lookHere);
30483 
30484 		// found?
30485 		if (foundHere != SCP_string::npos)
30486 		{
30487 			// see if a variable starts at the next char
30488 			int var_index = get_index_sexp_variable_name_special(text, foundHere+1);
30489 			if (var_index >= 0)
30490 			{
30491 				// replace $variable with the value
30492 				text.replace(foundHere, strlen(Sexp_variables[var_index].variable_name)+1, Sexp_variables[var_index].text);
30493 				replaced_anything = true;
30494 
30495 				lookHere = foundHere + strlen(Sexp_variables[var_index].text);
30496 			}
30497 			// no match... so keep iterating along the string
30498 			else
30499 			{
30500 				lookHere = foundHere + 1;
30501 			}
30502 		}
30503 	} while (foundHere != SCP_string::npos);
30504 
30505 	return replaced_anything;
30506 }
30507 
30508 // returns the index of the nth variable of the type given or -1 if there aren't that many
30509 // NOTE : Not 0th order. If you want the 4th string variable call it as get_nth_variable_index(4, SEXP_VARIABLE_STRING)
get_nth_variable_index(int nth,int variable_type)30510 int get_nth_variable_index(int nth, int variable_type)
30511 {
30512 	// Loop through Sexp_variables until we have found the one corresponding to the argument
30513 	Assert ((nth > 0) && (nth < MAX_SEXP_VARIABLES));
30514 	for (int i=0; i < MAX_SEXP_VARIABLES; i++)	{
30515 		if ((Sexp_variables[i].type & variable_type)) {
30516 			nth--;
30517 		}
30518 
30519 		if (!nth) {
30520 			return i;
30521 		}
30522 	}
30523 	return -1;
30524 }
30525 
30526 /**
30527  * Count number of sexp_variables that are set
30528  */
sexp_variable_count()30529 int sexp_variable_count()
30530 {
30531 	int count = 0;
30532 
30533 	for (int i=0; i<MAX_SEXP_VARIABLES; i++) {
30534 		if ( Sexp_variables[i].type & SEXP_VARIABLE_SET) {
30535 			count++;
30536 		}
30537 	}
30538 
30539 	return count;
30540 }
30541 
30542 /**
30543  * Count number of persistent sexp_variables that are set
30544  */
sexp_campaign_file_variable_count()30545 int sexp_campaign_file_variable_count()
30546 {
30547 	int count = 0;
30548 
30549 	for (int i=0; i<MAX_SEXP_VARIABLES; i++) {
30550 		if ( (Sexp_variables[i].type & SEXP_VARIABLE_SET) && (Sexp_variables[i].type & SEXP_VARIABLE_IS_PERSISTENT) && !(Sexp_variables[i].type & SEXP_VARIABLE_SAVE_TO_PLAYER_FILE) ) {
30551 			count++;
30552 		}
30553 	}
30554 
30555 	return count;
30556 }
30557 
30558 /**
30559  * Given an index in Sexp_variables, returns the number variables of a type in the array until this point
30560  */
sexp_variable_typed_count(int sexp_variables_index,int variable_type)30561 int sexp_variable_typed_count(int sexp_variables_index, int variable_type)
30562 {
30563 	Assert ((sexp_variables_index >= 0) && (sexp_variables_index < MAX_SEXP_VARIABLES));
30564 	// Loop through Sexp_variables until we have found the one corresponding to the argument
30565 	int count = 0;
30566 	for (int i=0; i < MAX_SEXP_VARIABLES; i++)	{
30567 		if (!(Sexp_variables[i].type & variable_type)) {
30568 			continue;
30569 		}
30570 
30571 		if (i == sexp_variables_index) {
30572 			return count ;
30573 		}
30574 		count++;
30575 	}
30576 	// shouldn't ever get here
30577 	Assert(false);
30578 	return -1;
30579 }
30580 
30581 /**
30582  * Delete sexp_variable from active
30583  */
sexp_variable_delete(int index)30584 void sexp_variable_delete(int index)
30585 {
30586 	Assert(Sexp_variables[index].type & SEXP_VARIABLE_SET);
30587 
30588 	Sexp_variables[index].type = SEXP_VARIABLE_NOT_USED;
30589 }
30590 
sexp_var_compare(const void * var1,const void * var2)30591 int sexp_var_compare(const void *var1, const void *var2)
30592 {
30593 	int set1, set2;
30594 	sexp_variable *sexp_var1, *sexp_var2;
30595 
30596 	sexp_var1 = (sexp_variable*) var1;
30597 	sexp_var2 = (sexp_variable*) var2;
30598 
30599 	set1 = sexp_var1->type & SEXP_VARIABLE_SET;
30600 	set2 = sexp_var2->type & SEXP_VARIABLE_SET;
30601 
30602 	if (!set1 && !set2) {
30603 		return 0;
30604 	} else if (set1 && !set2) {
30605 		return -1;
30606 	} else if (!set1 && set2) {
30607 		return 1;
30608 	} else {
30609 		return stricmp( sexp_var1->variable_name, sexp_var2->variable_name);
30610 	}
30611 }
30612 
30613 /**
30614  * Sort sexp_variable list lexigraphically, with set before unset
30615  */
sexp_variable_sort()30616 void sexp_variable_sort()
30617 {
30618 	insertion_sort( (void *)Sexp_variables, (size_t)(MAX_SEXP_VARIABLES), sizeof(sexp_variable), sexp_var_compare );
30619 }
30620 
30621 // Goober5000
get_category(int sexp_id)30622 int get_category(int sexp_id)
30623 {
30624 	int category = (sexp_id & OP_CATEGORY_MASK);
30625 
30626 	// hack so that CHANGE and CHANGE2 show up in the same menu
30627 	if (category == OP_CATEGORY_CHANGE2)
30628 		category = OP_CATEGORY_CHANGE;
30629 
30630 	return category;
30631 }
30632 
30633 // Goober5000
category_of_subcategory(int subcategory_id)30634 int category_of_subcategory(int subcategory_id)
30635 {
30636 	int category = (subcategory_id & OP_CATEGORY_MASK);
30637 
30638 	// hack so that CHANGE and CHANGE2 show up in the same menu
30639 	if (category == OP_CATEGORY_CHANGE2)
30640 		category = OP_CATEGORY_CHANGE;
30641 
30642 	return category;
30643 }
30644 
30645 // Goober5000 - for FRED2 menu subcategories
get_subcategory(int sexp_id)30646 int get_subcategory(int sexp_id)
30647 {
30648 	switch(sexp_id)
30649 	{
30650 		case OP_SEND_MESSAGE_LIST:
30651 		case OP_SEND_MESSAGE_CHAIN:
30652 		case OP_SEND_MESSAGE:
30653 		case OP_SEND_RANDOM_MESSAGE:
30654 		case OP_SCRAMBLE_MESSAGES:
30655 		case OP_UNSCRAMBLE_MESSAGES:
30656 		case OP_ENABLE_BUILTIN_MESSAGES:
30657 		case OP_DISABLE_BUILTIN_MESSAGES:
30658 		case OP_SET_DEATH_MESSAGE:
30659 		case OP_SET_PERSONA:
30660 		case OP_SET_MISSION_MOOD:
30661 			return CHANGE_SUBCATEGORY_MESSAGING;
30662 
30663 		case OP_ADD_GOAL:
30664 		case OP_REMOVE_GOAL:
30665 		case OP_CLEAR_GOALS:
30666 		case OP_GOOD_REARM_TIME:
30667 		case OP_GOOD_SECONDARY_TIME:
30668 		case OP_CHANGE_AI_CLASS:
30669 		case OP_PLAYER_USE_AI:
30670 		case OP_PLAYER_NOT_USE_AI:
30671 		case OP_SET_PLAYER_ORDERS:
30672 		case OP_CAP_WAYPOINT_SPEED:
30673 			return CHANGE_SUBCATEGORY_AI_CONTROL;
30674 
30675 		case OP_ALTER_SHIP_FLAG:
30676 		case OP_PROTECT_SHIP:
30677 		case OP_UNPROTECT_SHIP:
30678 		case OP_BEAM_PROTECT_SHIP:
30679 		case OP_BEAM_UNPROTECT_SHIP:
30680 		case OP_TURRET_PROTECT_SHIP:
30681 		case OP_TURRET_UNPROTECT_SHIP:
30682 		case OP_SHIP_INVISIBLE:
30683 		case OP_SHIP_VISIBLE:
30684 		case OP_SHIP_STEALTHY:
30685 		case OP_SHIP_UNSTEALTHY:
30686 		case OP_FRIENDLY_STEALTH_INVISIBLE:
30687 		case OP_FRIENDLY_STEALTH_VISIBLE:
30688 		case OP_PRIMITIVE_SENSORS_SET_RANGE:
30689 		case OP_SHIP_BOMB_TARGETABLE:
30690 		case OP_SHIP_BOMB_UNTARGETABLE:
30691 		case OP_KAMIKAZE:
30692 		case OP_CHANGE_IFF:
30693 		case OP_CHANGE_IFF_COLOR:
30694 		case OP_ADD_REMOVE_ESCORT:
30695 		case OP_SHIP_CHANGE_ALT_NAME:
30696 		case OP_SHIP_CHANGE_CALLSIGN:
30697 		case OP_SHIP_TAG:
30698 		case OP_SHIP_UNTAG:
30699 		case OP_SET_ARRIVAL_INFO:
30700 		case OP_SET_DEPARTURE_INFO:
30701 			return CHANGE_SUBCATEGORY_SHIP_STATUS;
30702 
30703 		case OP_SET_WEAPON_ENERGY:
30704 		case OP_SET_SHIELD_ENERGY:
30705 		case OP_SET_PLAYER_THROTTLE_SPEED:
30706 		case OP_SET_AFTERBURNER_ENERGY:
30707 		case OP_SET_SUBSPACE_DRIVE:
30708 		case OP_SET_SPECIAL_WARPOUT_NAME:
30709 		case OP_SET_PRIMARY_WEAPON:		// Karajorma
30710 		case OP_SET_SECONDARY_WEAPON:	// Karajorma
30711 		case OP_SET_PRIMARY_AMMO:		// Karajorma
30712 		case OP_SET_SECONDARY_AMMO:		// Karajorma
30713 		case OP_SET_NUM_COUNTERMEASURES: // Karajorma
30714 		case OP_LOCK_PRIMARY_WEAPON:
30715 		case OP_UNLOCK_PRIMARY_WEAPON:
30716 		case OP_LOCK_SECONDARY_WEAPON:
30717 		case OP_UNLOCK_SECONDARY_WEAPON:
30718 		case OP_LOCK_AFTERBURNER:	// KeldorKatarn
30719 		case OP_UNLOCK_AFTERBURNER:	// KeldorKatarn
30720 		case OP_SHIELDS_ON:
30721 		case OP_SHIELDS_OFF:
30722 		case OP_FORCE_GLIDE:
30723 		case OP_DISABLE_ETS:
30724 		case OP_ENABLE_ETS:
30725 		case OP_WARP_BROKEN:
30726 		case OP_WARP_NOT_BROKEN:
30727 		case OP_WARP_NEVER:
30728 		case OP_WARP_ALLOWED:
30729 		case OP_SET_ETS_VALUES:
30730 		case OP_GET_POWER_OUTPUT:
30731 			return CHANGE_SUBCATEGORY_SHIELDS_ENGINES_AND_WEAPONS;
30732 
30733 		case OP_SHIP_INVULNERABLE:
30734 		case OP_SHIP_VULNERABLE:
30735 		case OP_SHIP_GUARDIAN:
30736 		case OP_SHIP_NO_GUARDIAN:
30737 		case OP_SHIP_GUARDIAN_THRESHOLD:
30738 		case OP_SHIP_SUBSYS_GUARDIAN_THRESHOLD:
30739 		case OP_SELF_DESTRUCT:
30740 		case OP_DESTROY_INSTANTLY:
30741 		case OP_DESTROY_SUBSYS_INSTANTLY:
30742 		case OP_SABOTAGE_SUBSYSTEM:
30743 		case OP_REPAIR_SUBSYSTEM:
30744 		case OP_SHIP_COPY_DAMAGE:
30745 		case OP_SET_SUBSYSTEM_STRNGTH:
30746 		case OP_SUBSYS_SET_RANDOM:
30747 		case OP_LOCK_ROTATING_SUBSYSTEM:
30748 		case OP_FREE_ROTATING_SUBSYSTEM:
30749 		case OP_REVERSE_ROTATING_SUBSYSTEM:
30750 		case OP_ROTATING_SUBSYS_SET_TURN_TIME:
30751 		case OP_TRIGGER_SUBMODEL_ANIMATION:
30752 		case OP_CHANGE_SUBSYSTEM_NAME:
30753 		case OP_SHIP_SUBSYS_TARGETABLE:
30754 		case OP_SHIP_SUBSYS_UNTARGETABLE:
30755 		case OP_SHIP_SUBSYS_NO_REPLACE:
30756 		case OP_SHIP_SUBSYS_NO_LIVE_DEBRIS:
30757 		case OP_SHIP_SUBSYS_VANISHED:
30758 		case OP_SHIP_SUBSYS_IGNORE_IF_DEAD:
30759 		case OP_AWACS_SET_RADIUS:
30760 			return CHANGE_SUBCATEGORY_SUBSYSTEMS;
30761 
30762 		case OP_TRANSFER_CARGO:
30763 		case OP_EXCHANGE_CARGO:
30764 		case OP_SET_CARGO:
30765 		case OP_JETTISON_CARGO_DELAY:
30766 		case OP_JETTISON_CARGO_NEW:
30767 		case OP_SET_DOCKED:
30768 		case OP_CARGO_NO_DEPLETE:
30769 		case OP_SET_SCANNED:
30770 		case OP_SET_UNSCANNED:
30771 			return CHANGE_SUBCATEGORY_CARGO;
30772 
30773 		case OP_SET_ARMOR_TYPE:
30774 		case OP_WEAPON_SET_DAMAGE_TYPE:
30775 		case OP_SHIP_SET_DAMAGE_TYPE:
30776 		case OP_SHIP_SHOCKWAVE_SET_DAMAGE_TYPE:
30777 		case OP_FIELD_SET_DAMAGE_TYPE:
30778 			return CHANGE_SUBCATEGORY_ARMOR_AND_DAMAGE_TYPES;
30779 
30780 		case OP_BEAM_FIRE:
30781 		case OP_BEAM_FIRE_COORDS:
30782 		case OP_BEAM_FLOATING_FIRE:
30783 		case OP_BEAM_FREE:
30784 		case OP_BEAM_FREE_ALL:
30785 		case OP_BEAM_LOCK:
30786 		case OP_BEAM_LOCK_ALL:
30787 		case OP_TURRET_FREE:
30788 		case OP_TURRET_FREE_ALL:
30789 		case OP_TURRET_LOCK:
30790 		case OP_TURRET_LOCK_ALL:
30791 		case OP_TURRET_TAGGED_ONLY_ALL:
30792 		case OP_TURRET_TAGGED_CLEAR_ALL:
30793 		case OP_TURRET_TAGGED_SPECIFIC:
30794 		case OP_TURRET_TAGGED_CLEAR_SPECIFIC:
30795 		case OP_TURRET_CHANGE_WEAPON:
30796 		case OP_TURRET_SET_DIRECTION_PREFERENCE:
30797 		case OP_TURRET_SET_RATE_OF_FIRE:
30798 		case OP_TURRET_SET_OPTIMUM_RANGE:
30799 		case OP_TURRET_SET_FORCED_TARGET:
30800 		case OP_TURRET_SET_FORCED_SUBSYS_TARGET:
30801 		case OP_TURRET_CLEAR_FORCED_TARGET:
30802 		case OP_TURRET_SET_TARGET_PRIORITIES:
30803 		case OP_TURRET_SET_TARGET_ORDER:
30804 		case OP_TURRET_SET_INACCURACY:
30805 		case OP_SHIP_TURRET_TARGET_ORDER:
30806 		case OP_TURRET_SUBSYS_TARGET_DISABLE:
30807 		case OP_TURRET_SUBSYS_TARGET_ENABLE:
30808 		case OP_TURRET_SET_PRIMARY_AMMO:
30809 		case OP_TURRET_SET_SECONDARY_AMMO:
30810 		case OP_TURRET_GET_PRIMARY_AMMO:
30811 		case OP_TURRET_GET_SECONDARY_AMMO:
30812 			return CHANGE_SUBCATEGORY_BEAMS_AND_TURRETS;
30813 
30814 		case OP_CHANGE_SHIP_CLASS:
30815 		case OP_DEACTIVATE_GLOW_MAPS:
30816 		case OP_ACTIVATE_GLOW_MAPS:
30817 		case OP_DEACTIVATE_GLOW_POINTS:
30818 		case OP_ACTIVATE_GLOW_POINTS:
30819 		case OP_DEACTIVATE_GLOW_POINT_BANK:
30820 		case OP_ACTIVATE_GLOW_POINT_BANK:
30821 		case OP_SET_THRUSTERS:
30822 		case OP_DONT_COLLIDE_INVISIBLE:
30823 		case OP_COLLIDE_INVISIBLE:
30824 		case OP_ADD_TO_COLGROUP:
30825 		case OP_REMOVE_FROM_COLGROUP:
30826 		case OP_ADD_TO_COLGROUP2:
30827 		case OP_REMOVE_FROM_COLGROUP2:
30828 		case OP_GET_COLGROUP_ID:
30829 		case OP_CHANGE_TEAM_COLOR:
30830 		case OP_REPLACE_TEXTURE:
30831 			return CHANGE_SUBCATEGORY_MODELS_AND_TEXTURES;
30832 
30833 		case OP_SET_OBJECT_POSITION:
30834 		case OP_SET_OBJECT_ORIENTATION:
30835 		case OP_SET_OBJECT_FACING:
30836 		case OP_SET_OBJECT_FACING_OBJECT:
30837 		case OP_SET_OBJECT_SPEED_X:
30838 		case OP_SET_OBJECT_SPEED_Y:
30839 		case OP_SET_OBJECT_SPEED_Z:
30840 		case OP_SHIP_MANEUVER:
30841 		case OP_SHIP_ROT_MANEUVER:
30842 		case OP_SHIP_LAT_MANEUVER:
30843 		case OP_SET_MOBILE:
30844 		case OP_SET_IMMOBILE:
30845 			return CHANGE_SUBCATEGORY_COORDINATE_MANIPULATION;
30846 
30847 		case OP_INVALIDATE_GOAL:
30848 		case OP_VALIDATE_GOAL:
30849 		case OP_RED_ALERT:
30850 		case OP_END_MISSION:
30851 		case OP_FORCE_JUMP:
30852 		case OP_END_CAMPAIGN:
30853 		case OP_SET_DEBRIEFING_TOGGLED:
30854 		case OP_SET_DEBRIEFING_PERSONA:
30855 		case OP_ALLOW_TREASON:
30856 		case OP_GRANT_PROMOTION:
30857 		case OP_GRANT_MEDAL:
30858 		case OP_ALLOW_SHIP:
30859 		case OP_ALLOW_WEAPON:
30860 		case OP_TECH_ADD_SHIP:
30861 		case OP_TECH_ADD_WEAPON:
30862 		case OP_TECH_ADD_INTEL:
30863 		case OP_TECH_REMOVE_INTEL:
30864 		case OP_TECH_ADD_INTEL_XSTR:
30865 		case OP_TECH_REMOVE_INTEL_XSTR:
30866 		case OP_TECH_RESET_TO_DEFAULT:
30867 		case OP_CHANGE_PLAYER_SCORE:
30868 		case OP_CHANGE_TEAM_SCORE:
30869 		case OP_SET_RESPAWNS:
30870 		case OP_ADD_REMOVE_HOTKEY:
30871 			return CHANGE_SUBCATEGORY_MISSION_AND_CAMPAIGN;
30872 
30873 		case OP_CHANGE_SOUNDTRACK:
30874 		case OP_PLAY_SOUND_FROM_TABLE:
30875 		case OP_PLAY_SOUND_FROM_FILE:
30876 		case OP_CLOSE_SOUND_FROM_FILE:
30877 		case OP_PAUSE_SOUND_FROM_FILE:
30878 		case OP_SET_SOUND_ENVIRONMENT:
30879 		case OP_UPDATE_SOUND_ENVIRONMENT:
30880 		case OP_ADJUST_AUDIO_VOLUME:
30881 			return CHANGE_SUBCATEGORY_MUSIC_AND_SOUND;
30882 
30883 		case OP_HUD_DISABLE:
30884 		case OP_HUD_DISABLE_EXCEPT_MESSAGES:
30885 		case OP_HUD_SET_CUSTOM_GAUGE_ACTIVE:
30886 		case OP_HUD_SET_BUILTIN_GAUGE_ACTIVE:
30887 		case OP_HUD_SET_TEXT:
30888 		case OP_HUD_SET_TEXT_NUM:
30889 		case OP_HUD_SET_MESSAGE:
30890 		case OP_HUD_SET_DIRECTIVE:
30891 		case OP_HUD_SET_FRAME:
30892 		case OP_HUD_SET_COLOR:
30893 		case OP_HUD_SET_COORDS:
30894 		case OP_HUD_DISPLAY_GAUGE:
30895 		case OP_HUD_GAUGE_SET_ACTIVE:
30896 		case OP_HUD_ACTIVATE_GAUGE_TYPE:
30897 		case OP_HUD_CLEAR_MESSAGES:
30898 		case OP_HUD_SET_MAX_TARGETING_RANGE:
30899 			return CHANGE_SUBCATEGORY_HUD;
30900 
30901 		case OP_NAV_ADD_WAYPOINT:
30902 		case OP_NAV_ADD_SHIP:
30903 		case OP_NAV_DEL:
30904 		case OP_NAV_HIDE:
30905 		case OP_NAV_RESTRICT:
30906 		case OP_NAV_UNHIDE:
30907 		case OP_NAV_UNRESTRICT:
30908 		case OP_NAV_SET_VISITED:
30909 		case OP_NAV_SET_CARRY:
30910 		case OP_NAV_UNSET_CARRY:
30911 		case OP_NAV_UNSET_VISITED:
30912 		case OP_NAV_SET_NEEDSLINK:
30913 		case OP_NAV_UNSET_NEEDSLINK:
30914 		case OP_NAV_USECINEMATICS:
30915 		case OP_NAV_USEAP:
30916 		case OP_NAV_SELECT:
30917 		case OP_NAV_UNSELECT:
30918 			return CHANGE_SUBCATEGORY_NAV;
30919 
30920 		case OP_CUTSCENES_SET_CUTSCENE_BARS:
30921 		case OP_CUTSCENES_UNSET_CUTSCENE_BARS:
30922 		case OP_CUTSCENES_FADE_IN:
30923 		case OP_CUTSCENES_FADE_OUT:
30924 		case OP_CUTSCENES_SET_CAMERA:
30925 		case OP_CUTSCENES_SET_CAMERA_POSITION:
30926 		case OP_CUTSCENES_SET_CAMERA_FACING:
30927 		case OP_CUTSCENES_SET_CAMERA_FACING_OBJECT:
30928 		case OP_CUTSCENES_SET_CAMERA_ROTATION:
30929 		case OP_CUTSCENES_SET_CAMERA_HOST:
30930 		case OP_CUTSCENES_SET_CAMERA_TARGET:
30931 		case OP_CUTSCENES_SET_CAMERA_FOV:
30932 		case OP_CUTSCENES_SET_FOV:
30933 		case OP_CUTSCENES_GET_FOV:
30934 		case OP_CUTSCENES_RESET_FOV:
30935 		case OP_CUTSCENES_RESET_CAMERA:
30936 		case OP_CUTSCENES_SHOW_SUBTITLE:
30937 		case OP_CUTSCENES_SHOW_SUBTITLE_TEXT:
30938 		case OP_CUTSCENES_SHOW_SUBTITLE_IMAGE:
30939 		case OP_CLEAR_SUBTITLES:
30940 		case OP_CUTSCENES_FORCE_PERSPECTIVE:
30941 		case OP_SET_CAMERA_SHUDDER:
30942 		case OP_SUPERNOVA_START:
30943 		case OP_SUPERNOVA_STOP:
30944 		case OP_SET_MOTION_DEBRIS:
30945 			return CHANGE_SUBCATEGORY_CUTSCENES;
30946 
30947 		case OP_SET_SKYBOX_MODEL:
30948 		case OP_SET_SKYBOX_ORIENT:
30949 		case OP_MISSION_SET_NEBULA:
30950 		case OP_MISSION_SET_SUBSPACE:
30951 		case OP_CHANGE_BACKGROUND:
30952 		case OP_ADD_BACKGROUND_BITMAP:
30953 		case OP_REMOVE_BACKGROUND_BITMAP:
30954 		case OP_ADD_SUN_BITMAP:
30955 		case OP_REMOVE_SUN_BITMAP:
30956 		case OP_NEBULA_CHANGE_STORM:
30957 		case OP_NEBULA_TOGGLE_POOF:
30958 		case OP_NEBULA_CHANGE_PATTERN:
30959 		case OP_NEBULA_CHANGE_FOG_COLOR:
30960 		case OP_SET_AMBIENT_LIGHT:
30961 			return CHANGE_SUBCATEGORY_BACKGROUND_AND_NEBULA;
30962 
30963 		case OP_JUMP_NODE_SET_JUMPNODE_NAME: //CommanderDJ
30964 		case OP_JUMP_NODE_SET_JUMPNODE_COLOR:
30965 		case OP_JUMP_NODE_SET_JUMPNODE_MODEL:
30966 		case OP_JUMP_NODE_SHOW_JUMPNODE:
30967 		case OP_JUMP_NODE_HIDE_JUMPNODE:
30968 			return CHANGE_SUBCATEGORY_JUMP_NODES;
30969 
30970 		case OP_SET_POST_EFFECT:
30971 		case OP_RESET_POST_EFFECTS:
30972 		case OP_SHIP_EFFECT:
30973 		case OP_SHIP_CREATE:
30974 		case OP_WEAPON_CREATE:
30975 		case OP_SHIP_VANISH:
30976 		case OP_SHIP_VAPORIZE:
30977 		case OP_SHIP_NO_VAPORIZE:
30978 		case OP_SET_EXPLOSION_OPTION:
30979 		case OP_EXPLOSION_EFFECT:
30980 		case OP_WARP_EFFECT:
30981 		case OP_CLEAR_WEAPONS:
30982 		case OP_CLEAR_DEBRIS:
30983 		case OP_CUTSCENES_SET_TIME_COMPRESSION:
30984 		case OP_CUTSCENES_RESET_TIME_COMPRESSION:
30985 		case OP_CALL_SSM_STRIKE:
30986 			return CHANGE_SUBCATEGORY_SPECIAL_EFFECTS;
30987 
30988 		case OP_MODIFY_VARIABLE:
30989 		case OP_GET_VARIABLE_BY_INDEX:
30990 		case OP_SET_VARIABLE_BY_INDEX:
30991 		case OP_COPY_VARIABLE_FROM_INDEX:
30992 		case OP_COPY_VARIABLE_BETWEEN_INDEXES:
30993 		case OP_INT_TO_STRING:
30994 		case OP_STRING_CONCATENATE:
30995 		case OP_STRING_CONCATENATE_BLOCK:
30996 		case OP_STRING_GET_SUBSTRING:
30997 		case OP_STRING_SET_SUBSTRING:
30998 		case OP_MODIFY_VARIABLE_XSTR:
30999 			return CHANGE_SUBCATEGORY_VARIABLES;
31000 
31001 		case OP_DAMAGED_ESCORT_LIST:
31002 		case OP_DAMAGED_ESCORT_LIST_ALL:
31003 		case OP_SET_SUPPORT_SHIP:
31004 		case OP_SCRIPT_EVAL_STRING:
31005 		case OP_SCRIPT_EVAL_BLOCK:
31006 		case OP_SCRIPT_EVAL:
31007 		case OP_SCRIPT_EVAL_MULTI:
31008 			return CHANGE_SUBCATEGORY_OTHER;
31009 
31010 		case OP_NUM_SHIPS_IN_BATTLE:
31011 		case OP_NUM_SHIPS_IN_WING:
31012 		case OP_DIRECTIVE_VALUE:
31013 		case OP_GET_HOTKEY:
31014 			return STATUS_SUBCATEGORY_MISSION;
31015 
31016 		case OP_WAS_PROMOTION_GRANTED:
31017 		case OP_WAS_MEDAL_GRANTED:
31018 		case OP_SKILL_LEVEL_AT_LEAST:
31019 		case OP_NUM_KILLS:
31020 		case OP_NUM_ASSISTS:
31021 		case OP_NUM_TYPE_KILLS:
31022 		case OP_NUM_CLASS_KILLS:
31023 		case OP_SHIP_SCORE:
31024 		case OP_LAST_ORDER_TIME:
31025 		case OP_PLAYER_IS_CHEATING_BASTARD:
31026 		case OP_IS_LANGUAGE:
31027 			return STATUS_SUBCATEGORY_PLAYER;
31028 
31029 		case OP_NUM_PLAYERS:
31030 		case OP_TEAM_SCORE:
31031 		case OP_SHIP_DEATHS:
31032 		case OP_RESPAWNS_LEFT:
31033 		case OP_IS_PLAYER:
31034 			return STATUS_SUBCATEGORY_MULTIPLAYER;
31035 
31036 		case OP_HAS_BEEN_TAGGED_DELAY:
31037 		case OP_IS_TAGGED:
31038 		case OP_IS_SHIP_VISIBLE:
31039 		case OP_IS_SHIP_STEALTHY:
31040 		case OP_IS_FRIENDLY_STEALTH_VISIBLE:
31041 		case OP_IS_IFF:
31042 		case OP_IS_AI_CLASS:
31043 		case OP_IS_SHIP_CLASS:
31044 		case OP_IS_SHIP_TYPE:
31045 		case OP_CURRENT_SPEED:
31046 		case OP_GET_THROTTLE_SPEED:
31047 		case OP_IS_FACING:
31048 		case OP_IS_IN_MISSION:
31049 		case OP_IS_DOCKED:
31050 		case OP_NAV_ISLINKED:
31051 		case OP_ARE_SHIP_FLAGS_SET:
31052 			return STATUS_SUBCATEGORY_SHIP_STATUS;
31053 
31054 		case OP_SHIELD_RECHARGE_PCT:
31055 		case OP_ENGINE_RECHARGE_PCT:
31056 		case OP_WEAPON_RECHARGE_PCT:
31057 		case OP_SHIELD_QUAD_LOW:
31058 		case OP_PRIMARY_AMMO_PCT:
31059 		case OP_SECONDARY_AMMO_PCT:
31060 		case OP_IS_PRIMARY_SELECTED:
31061 		case OP_IS_SECONDARY_SELECTED:
31062 		case OP_GET_PRIMARY_AMMO:
31063 		case OP_GET_SECONDARY_AMMO:
31064 		case OP_GET_NUM_COUNTERMEASURES:
31065 		case OP_AFTERBURNER_LEFT:
31066 		case OP_WEAPON_ENERGY_LEFT:
31067 		case OP_PRIMARY_FIRED_SINCE:
31068 		case OP_SECONDARY_FIRED_SINCE:
31069 		case OP_HAS_PRIMARY_WEAPON:
31070 		case OP_HAS_SECONDARY_WEAPON:
31071 		case OP_GET_ETS_VALUE:
31072 		case OP_IS_IN_TURRET_FOV:
31073 			return STATUS_SUBCATEGORY_SHIELDS_ENGINES_AND_WEAPONS;
31074 
31075 		case OP_CARGO_KNOWN_DELAY:
31076 		case OP_CAP_SUBSYS_CARGO_KNOWN_DELAY:
31077 		case OP_IS_CARGO:
31078 			return STATUS_SUBCATEGORY_CARGO;
31079 
31080 		case OP_SHIELDS_LEFT:
31081 		case OP_HITS_LEFT:
31082 		case OP_HITS_LEFT_SUBSYSTEM:
31083 		case OP_HITS_LEFT_SUBSYSTEM_GENERIC:
31084 		case OP_HITS_LEFT_SUBSYSTEM_SPECIFIC:
31085 		case OP_SIM_HITS_LEFT:
31086 		case OP_GET_DAMAGE_CAUSED:
31087 			return STATUS_SUBCATEGORY_DAMAGE;
31088 
31089 		case OP_DISTANCE:
31090 		case OP_DISTANCE_CENTER:
31091 		case OP_DISTANCE_BBOX:
31092 		case OP_DISTANCE_CENTER_SUBSYSTEM:
31093 		case OP_DISTANCE_BBOX_SUBSYSTEM:
31094 		case OP_NAV_DISTANCE:
31095 		case OP_GET_OBJECT_X:
31096 		case OP_GET_OBJECT_Y:
31097 		case OP_GET_OBJECT_Z:
31098 		case OP_GET_OBJECT_PITCH:
31099 		case OP_GET_OBJECT_BANK:
31100 		case OP_GET_OBJECT_HEADING:
31101 		case OP_GET_OBJECT_SPEED_X:
31102 		case OP_GET_OBJECT_SPEED_Y:
31103 		case OP_GET_OBJECT_SPEED_Z:
31104 		case OP_NUM_WITHIN_BOX:
31105 		case OP_SPECIAL_WARP_DISTANCE:
31106 		case OP_IS_IN_BOX:
31107 			return STATUS_SUBCATEGORY_DISTANCE_AND_COORDINATES;
31108 
31109 		case OP_STRING_TO_INT:
31110 		case OP_STRING_GET_LENGTH:
31111 			return STATUS_SUBCATEGORY_VARIABLES;
31112 
31113 		case OP_SCRIPT_EVAL_NUM:
31114 			return STATUS_SUBCATEGORY_OTHER;
31115 
31116 		default: {
31117 			// Check if we have a dynamic SEXP with this operator and if there is, execute that
31118 			auto dynamicSEXP = sexp::get_dynamic_sexp(sexp_id);
31119 			if (dynamicSEXP != nullptr) {
31120 				return dynamicSEXP->getSubcategory();
31121 			}
31122 			else {
31123 				return -1;		// sexp doesn't have a subcategory
31124 			}
31125 		}
31126 	}
31127 }
31128 
31129 // clang-format off
31130 SCP_vector<sexp_help_struct> Sexp_help = {
31131 	{ OP_PLUS, "Plus (Arithmetic operator)\r\n"
31132 		"\tAdds numbers and returns results.\r\n\r\n"
31133 		"Returns a number.  Takes 2 or more numeric arguments." },
31134 
31135 	{ OP_MINUS, "Minus (Arithmetic operator)\r\n"
31136 		"\tSubtracts numbers and returns results.\r\n\r\n"
31137 		"Returns a number.  Takes 2 or more numeric arguments." },
31138 
31139 	{ OP_MOD, "Mod (Arithmetic operator)\r\n"
31140 		"\tDivides numbers and returns the remainer.\r\n\r\n"
31141 		"Returns a number.  Takes 2 or more numeric arguments." },
31142 
31143 	{ OP_MUL, "Multiply (Arithmetic operator)\r\n"
31144 		"\tMultiplies numbers and returns results.\r\n\r\n"
31145 		"Returns a number.  Takes 2 or more numeric arguments." },
31146 
31147 	{ OP_DIV, "Divide (Arithmetic operator)\r\n"
31148 		"\tDivides numbers and returns results.\r\n\r\n"
31149 		"Returns a number.  Takes 2 or more numeric arguments." },
31150 
31151 	{ OP_RAND, "Rand (Arithmetic operator)\r\n"
31152 		"\tGets a random number.  This number will not change on successive calls to this sexp.\r\n\r\n"
31153 		"Returns a number.  Takes 2 or 3 numeric arguments...\r\n"
31154 		"\t1:\tLow range of random number.\r\n"
31155 		"\t2:\tHigh range of random number.\r\n"
31156 		"\t3:\t(optional) A seed to use when generating numbers. (Setting this to 0 is the same as having no seed at all)" },
31157 
31158 	// Goober5000
31159 	{ OP_RAND_MULTIPLE, "Rand-multiple (Arithmetic operator)\r\n"
31160 		"\tGets a random number.  This number can and will change between successive calls to this sexp.\r\n\r\n"
31161 		"Returns a number.  Takes 2 or 3 numeric arguments...\r\n"
31162 		"\t1:\tLow range of random number.\r\n"
31163 		"\t2:\tHigh range of random number.\r\n"
31164 		"\t3:\t(optional) A seed to use when generating numbers. (Setting this to 0 is the same as having no seed at all)" },
31165 
31166 	// -------------------------- Nav Points --- Kazan --------------------------
31167 	{ OP_NAV_IS_VISITED, "is-nav-visited\r\n"
31168 		"Returns true when the player has visited the given navpoint (Has closed to within 1000m of it). Takes 1 argument...\r\n"
31169 		"\t1:\tThe name of the navpoint" },
31170 
31171 	{ OP_NAV_DISTANCE, "distance-to-nav\r\n"
31172 		"Returns the distance from the center of the player ship to a nav point. Takes 1 argument..."
31173 		"\t1:\tThe name of the navpoint" },
31174 
31175 	{ OP_NAV_ADD_WAYPOINT, "add-nav-waypoint\r\n"
31176 		"Adds a Navpoint to a navpoint path. Takes 3 or 4 Arguments...\r\n"
31177 		"\t1:\tName of the new navpoint.\r\n"
31178 		"\t2:\tName of the navpoint path the new navpoint should be added to.\r\n"
31179 		"\t3:\tPosition where the new navpoint will be inserted. Note: This is 1-indexed, so the first waypoint in a path has position 1.\r\n"
31180 		"\t4:\t(Optional Argument) Controls the visibility of the new navpoint. Only entities belonging to the chosen category will be able to see it.\r\n" },
31181 
31182 	{ OP_NAV_ADD_SHIP, "add-nav-ship\r\n"
31183 		"Binds the named navpoint to the named ship - when the ship moves, the waypoint moves with it. Takes 2 Arguments...\r\n"
31184 		"\t1:\tThe NavPoint's Name\r\n"
31185 		"\t2:\tThe Ship's Name\r\n" },
31186 
31187 	{ OP_NAV_DEL, "del-nav\r\n"
31188 		"Deletes a nav point. Takes 1 Argument...\r\n"
31189 		"\t1:\tNavPoint Name" },
31190 
31191 	{ OP_NAV_HIDE, "hide-nav\r\n"
31192 		"Hides a nav point. Takes 1 Argument...\r\n"
31193 		"\t1:\tNavPoint Name, it then 'hides' that Nav Point\r\n" },
31194 
31195 	{ OP_NAV_RESTRICT, "restrict-nav\r\n"
31196 		"This causes the nav point to be unselectable. Takes 1 Argument...\r\n"
31197 		"\t1:\tThe Navpoint name\r\n" },
31198 
31199 	{ OP_NAV_UNHIDE, "unhide-nav\r\n"
31200 		"Restores a hidden navpoint. Takes 1 Argument...\r\n"
31201 		"\t1:\tThe Navpoint Name\r\n" },
31202 
31203 	{ OP_NAV_UNRESTRICT, "unrestrict-nav\r\n"
31204 		"Removes restrictions from a Navpoint. Takes 1 Argument...\r\n"
31205 		"\t1:\tThe Navpoint Name\r\n" },
31206 
31207 	{ OP_NAV_SET_VISITED, "set-nav-visited\r\n"
31208 		"Sets the status of the given Navpoint to \"visited\". Takes 1 Argument...\r\n"
31209 		"\t1:\tThe Navpoint Name\r\n" },
31210 
31211 	{ OP_NAV_UNSET_VISITED, "unset-nav-visited\r\n"
31212 		"Removes the \"visited\" status from a Navpoint. Takes 1 Argument...\r\n"
31213 		"\t1:\tThe Navpoint Name\r\n" },
31214 
31215 	{ OP_NAV_SET_CARRY, "nav-set-carry\r\n"
31216 		"Sets the Nav Carry flag for all listed ships. Vessels with this flag will follow the player into and out of autopilot.\r\n"
31217 		"Takes 1 or more arguments...\r\n"
31218 		"\t1:\tShips and Wings that should receive the Nav Carry flag.\r\n" },
31219 
31220 	{ OP_NAV_UNSET_CARRY, "unset-nav-carry\r\n"
31221 		"Removes the Nav Carry flag from all listed ships and wings. Takes 1 or more arguments...\r\n"
31222 		"\t1:\tShips and Wings to remove the Nav Carry flag from\r\n" },
31223 
31224 	{ OP_NAV_SET_NEEDSLINK, "set-nav-needslink\r\n"
31225 		"Marks all listed ships as needing to link up before entering autopilot.\r\n"
31226 		"Takes 1 or more arguments...\r\n"
31227 		"\t1:\tShips to mark\r\n" },
31228 
31229 	{ OP_NAV_UNSET_NEEDSLINK, "unset-nav-needslink\r\n"
31230 		"Removes the requirement for the listed ships to link up before entering autopilot.\r\n"
31231 		"Takes 1 or more arguments...\r\n"
31232 		"\t1:\tShips to mark\r\n" },
31233 
31234 	{ OP_NAV_ISLINKED, "is-nav-linked\r\n"
31235 		"Determines if a ship is linked for autopilot (\"set-nav-carry\" or \"set-nav-needslink\" + linked)"
31236 		"Takes 1 argument...\r\n"
31237 		"\t1:\tShip to check\r\n"},
31238 
31239 	{ OP_NAV_USECINEMATICS, "use-nav-cinematics\r\n"
31240 		"Controls the use of the cinematic autopilot camera. Takes 1 Argument..."
31241 		"\t1:\tSet to true to enable automatic cinematics, set to false to disable automatic cinematics." },
31242 
31243 	{ OP_NAV_USEAP, "use-autopilot\r\n"
31244 		"Takes 1 boolean argument.\r\n"
31245 		"\t1:\tSet to true to enable autopilot, set to false to disable autopilot." },
31246 
31247 	{ OP_NAV_SELECT, "nav-select (Action operator)\r\n"
31248 		"\tSelects a nav point.\r\n\r\n"
31249 		"Takes 1 argument...\r\n"
31250 		"\t1:\tName of the nav point." },
31251 
31252 	{ OP_NAV_UNSELECT, "nav-deselect (Action operator)\r\n"
31253 		"\tDeselects any navpoint selected.\r\n\r\n"
31254 		"Takes no arguments..." },
31255 
31256 	// -------------------------- -------------------------- --------------------------
31257 
31258 	// Goober5000
31259 	{ OP_ABS, "Absolute value (Arithmetic operator)\r\n"
31260 		"\tReturns the absolute value of a number.  Takes 1 numeric argument.\r\n" },
31261 
31262 	// Goober5000
31263 	{ OP_MIN, "Minimum value (Arithmetic operator)\r\n"
31264 		"\tReturns the minimum of a set of numbers.  Takes 1 or more numeric arguments.\r\n" },
31265 
31266 	// Goober5000
31267 	{ OP_MAX, "Maximum value (Arithmetic operator)\r\n"
31268 		"\tReturns the maximum of a set of numbers.  Takes 1 or more numeric arguments.\r\n" },
31269 
31270 	// Goober5000
31271 	{ OP_AVG, "Average value (Arithmetic operator)\r\n"
31272 		"\tReturns the average (rounded to the nearest integer) of a set of numbers.  Takes 1 or more numeric arguments.\r\n" },
31273 
31274 	// Goober5000
31275 	{ OP_POW, "Power (Arithmetic operator)\r\n"
31276 		"\tRaises one number to the power of the next number.  If the result will be larger than INT_MAX or smaller than INT_MIN, the appropriate limit will be returned.  Takes 2 numeric arguments.\r\n" },
31277 
31278 	// Goober5000
31279 	{ OP_SIGNUM, "Signum (Arithmetic operator)\r\n"
31280 		"\tReturns the sign of a number: -1 if it is negative, 0 if it is 0, and 1 if it is positive.  Takes one argument.\r\n" },
31281 
31282 	// Goober5000
31283 	{ OP_IS_NAN, "Is-NaN (Arithmetic operator)\r\n"
31284 		"\tReturns true if the argument is NaN (not-a-number).  This can happen e.g. when checking statistics for ships that have departed.  Takes one argument.\r\n" },
31285 
31286 	// Goober5000
31287 	{ OP_NAN_TO_NUMBER, "NaN-to-Number (Arithmetic operator)\r\n"
31288 		"\tUsed to safely filter NaN values from arithmetic operations, which would otherwise pass NaN up the sexp tree.  If the argument is a number, it is returned.  If the argument is NaN, a zero is returned.  Takes one argument.\r\n" },
31289 
31290 	// Goober5000
31291 	{ OP_SET_BIT, "set-bit\r\n"
31292 		"\tTurns on (sets to 1) a certain bit in the provided number, returning the result.  This allows numbers to store up to 32 boolean flags, from 2^0 to 2^31.  Takes 2 numeric arguments...\r\n"
31293 		"\t1: The number whose bit should be set\r\n"
31294 		"\t2: The index of the bit to set.  Valid indexes are between 0 and 31, inclusive.\r\n" },
31295 
31296 	// Goober5000
31297 	{ OP_UNSET_BIT, "unset-bit\r\n"
31298 		"\tTurns off (sets to 0) a certain bit in the provided number, returning the result.  This allows numbers to store up to 32 boolean flags, from 2^0 to 2^31.  Takes 2 numeric arguments...\r\n"
31299 		"\t1: The number whose bit should be unset\r\n"
31300 		"\t2: The index of the bit to unset.  Valid indexes are between 0 and 31, inclusive.\r\n" },
31301 
31302 	// Goober5000
31303 	{ OP_IS_BIT_SET, "is-bit-set\r\n"
31304 		"\tReturns true if the specified bit is set (equal to 1) in the provided number.  Takes 2 numeric arguments...\r\n"
31305 		"\t1: The number whose bit should be tested\r\n"
31306 		"\t2: The index of the bit to test.  Valid indexes are between 0 and 31, inclusive.\r\n" },
31307 
31308 	// Goober5000
31309 	{ OP_BITWISE_AND, "bitwise-and\r\n"
31310 		"\tPerforms the bitwise AND operator on its arguments.  This is the same as if the logical AND operator was performed on each successive bit.  Takes 2 or more numeric arguments.\r\n" },
31311 
31312 	// Goober5000
31313 	{ OP_BITWISE_OR, "bitwise-or\r\n"
31314 		"\tPerforms the bitwise OR operator on its arguments.  This is the same as if the logical OR operator was performed on each successive bit.  Takes 2 or more numeric arguments.\r\n" },
31315 
31316 	// Goober5000
31317 	{ OP_BITWISE_NOT, "bitwise-not\r\n"
31318 		"\tPerforms the bitwise NOT operator on its argument.  This is the same as if the logical NOT operator was performed on each successive bit.\r\n\r\n"
31319 		"Note that the operation is performed as if on an unsigned integer whose maximum value is INT_MAX.  In other words, there is no need to worry about the sign bit.\r\n\r\n"
31320 		"Takes only 1 argument.\r\n" },
31321 
31322 	// Goober5000
31323 	{ OP_BITWISE_XOR, "bitwise-xor\r\n"
31324 		"\tPerforms the bitwise XOR operator on its arguments.  This is the same as if the logical XOR operator was performed on each successive bit.  Takes 2 or more numeric arguments.\r\n" },
31325 
31326 	{ OP_SET_OBJECT_SPEED_X, "set-object-speed-x (deprecated in favor of ship-maneuver)\r\n"
31327 		"\tSets the X speed of a ship or wing."
31328 		"Takes 2 or 3 arguments...\r\n"
31329 		"\t1: The name of the object.\r\n"
31330 		"\t2: The speed to set.\r\n"
31331 		"\t3: Whether the speed on the axis should be set according to the universe grid (when false) or according to the object's facing (when true); You almost always want to set this to true; (optional; defaults to false).\r\n" },
31332 
31333 	{ OP_SET_OBJECT_SPEED_Y, "set-object-speed-y (deprecated in favor of ship-maneuver)\r\n"
31334 		"\tSets the Y speed of a ship or wing."
31335 		"Takes 2 or 3 arguments...\r\n"
31336 		"\t1: The name of the object.\r\n"
31337 		"\t2: The speed to set.\r\n"
31338 		"\t3: Whether the speed on the axis should be set according to the universe grid (when false) or according to the object's facing (when true); You almost always want to set this to true; (optional; defaults to false).\r\n" },
31339 
31340 	{ OP_SET_OBJECT_SPEED_Z, "set-object-speed-z (deprecated in favor of ship-maneuver)\r\n"
31341 		"\tSets the Z speed of a ship or wing."
31342 		"Takes 2 or 3 arguments...\r\n"
31343 		"\t1: The name of the object.\r\n"
31344 		"\t2: The speed to set.\r\n"
31345 		"\t3: Whether the speed on the axis should be set according to the universe grid (when false) or according to the object's facing (when true); You almost always want to set this to true; (optional; defaults to false).\r\n" },
31346 
31347 	{ OP_GET_OBJECT_SPEED_X, "get-object-speed-x\r\n"
31348 		"\tReturns the X speed of a ship or wing as an integer."
31349 		"Takes 2 or 3 arguments...\r\n"
31350 		"\t1: The name of the object.\r\n"
31351 		"\t2: Whether the speed on the axis should be set according to the universe grid (when false) or according to the object's facing (when true); You almost always want to set this to true; (optional; defaults to false).\r\n" },
31352 
31353 	{ OP_GET_OBJECT_SPEED_Y, "get-object-speed-y\r\n"
31354 		"\tReturns the Y speed of a ship or wing as an integer."
31355 		"Takes 2 or 3 arguments...\r\n"
31356 		"\t1: The name of the object.\r\n"
31357 		"\t2: Whether the speed on the axis should be set according to the universe grid (when false) or according to the object's facing (when true); You almost always want to set this to true; (optional; defaults to false).\r\n" },
31358 
31359 	{ OP_GET_OBJECT_SPEED_Z, "get-object-speed-z\r\n"
31360 		"\tReturns the Z speed of a ship or wing as an integer."
31361 		"Takes 2 or 3 arguments...\r\n"
31362 		"\t1: The name of the object.\r\n"
31363 		"\t2: Whether the speed on the axis should be set according to the universe grid (when false) or according to the object's facing (when true); You almost always want to set this to true; (optional; defaults to false).\r\n" },
31364 
31365 	// Goober5000
31366 	{ OP_GET_OBJECT_X, "get-object-x\r\n"
31367 		"\tReturns the absolute X coordinate of a set of coordinates relative to a particular object (or object's "
31368 		"subsystem).  The input coordinates are the coordinates relative to the object's position and orientation.  "
31369 		"If no input coordinates are specified, the coordinate returned is the coordinate of the object (or object's "
31370 		"subsystem) itself.  Takes 1 to 5 arguments...\r\n"
31371 		"\t1: The name of a ship, wing, or waypoint.\r\n"
31372 		"\t2: A ship subsystem (or \"" SEXP_NONE_STRING "\" if the first argument is not a ship - optional).\r\n"
31373 		"\t3: The relative X coordinate (optional).\r\n"
31374 		"\t4: The relative Y coordinate (optional).\r\n"
31375 		"\t5: The relative Z coordinate (optional).\r\n" },
31376 
31377 	// Goober5000
31378 	{ OP_GET_OBJECT_Y, "get-object-y\r\n"
31379 		"\tReturns the absolute Y coordinate of a set of coordinates relative to a particular object (or object's "
31380 		"subsystem).  The input coordinates are the coordinates relative to the object's position and orientation.  "
31381 		"If no input coordinates are specified, the coordinate returned is the coordinate of the object (or object's "
31382 		"subsystem) itself.  Takes 1 to 5 arguments...\r\n"
31383 		"\t1: The name of a ship, wing, or waypoint.\r\n"
31384 		"\t2: A ship subsystem (or \"" SEXP_NONE_STRING "\" if the first argument is not a ship - optional).\r\n"
31385 		"\t3: The relative X coordinate (optional).\r\n"
31386 		"\t4: The relative Y coordinate (optional).\r\n"
31387 		"\t5: The relative Z coordinate (optional).\r\n" },
31388 
31389 	// Goober5000
31390 	{ OP_GET_OBJECT_Z, "get-object-z\r\n"
31391 		"\tReturns the absolute Z coordinate of a set of coordinates relative to a particular object (or object's "
31392 		"subsystem).  The input coordinates are the coordinates relative to the object's position and orientation.  "
31393 		"If no input coordinates are specified, the coordinate returned is the coordinate of the object (or object's "
31394 		"subsystem) itself.  Takes 1 to 5 arguments...\r\n"
31395 		"\t1: The name of a ship, wing, or waypoint.\r\n"
31396 		"\t2: A ship subsystem (or \"" SEXP_NONE_STRING "\" if the first argument is not a ship - optional).\r\n"
31397 		"\t3: The relative X coordinate (optional).\r\n"
31398 		"\t4: The relative Y coordinate (optional).\r\n"
31399 		"\t5: The relative Z coordinate (optional).\r\n" },
31400 
31401 	// Goober5000
31402 	{ OP_SET_OBJECT_POSITION, "set-object-position\r\n"
31403 		"\tInstantaneously sets an object's spatial coordinates."
31404 		"Takes 4 arguments...\r\n"
31405 		"\t1: The name of a ship, wing, or waypoint.\r\n"
31406 		"\t2: The new X coordinate.\r\n"
31407 		"\t3: The new Y coordinate.\r\n"
31408 		"\t4: The new Z coordinate." },
31409 
31410 	// Goober5000
31411 	{ OP_GET_OBJECT_PITCH, "get-object-pitch\r\n"
31412 		"\tReturns the pitch angle, in degrees, of a particular object.  The returned value will be between 0 and 360.  Takes 1 argument...\r\n"
31413 		"\t1: The name of a ship or wing.\r\n" },
31414 
31415 	// Goober5000
31416 	{ OP_GET_OBJECT_BANK, "get-object-bank\r\n"
31417 		"\tReturns the bank angle, in degrees, of a particular object.  The returned value will be between 0 and 360.  Takes 1 argument...\r\n"
31418 		"\t1: The name of a ship or wing.\r\n" },
31419 
31420 	// Goober5000
31421 	{ OP_GET_OBJECT_HEADING, "get-object-heading\r\n"
31422 		"\tReturns the heading angle, in degrees, of a particular object.  The returned value will be between 0 and 360.  Takes 1 argument...\r\n"
31423 		"\t1: The name of a ship or wing.\r\n" },
31424 
31425 	// Goober5000
31426 	{ OP_SET_OBJECT_ORIENTATION, "set-object-orientation\r\n"
31427 		"\tInstantaneously sets an object's spatial orientation."
31428 		"Takes 4 arguments...\r\n"
31429 		"\t1: The name of a ship or wing.\r\n"
31430 		"\t2: The new pitch angle, in degrees.  The angle can be any number; it does not have to be between 0 and 360.\r\n"
31431 		"\t3: The new bank angle, in degrees.  The angle can be any can be any number; it does not have to be between 0 and 360.\r\n"
31432 		"\t4: The new heading angle, in degrees.  The angle can be any number; it does not have to be between 0 and 360." },
31433 
31434 	{ OP_TRUE, "True (Boolean operator)\r\n"
31435 		"\tA true boolean state\r\n\r\n"
31436 		"Returns a boolean value." },
31437 
31438 	{ OP_FALSE, "False (Boolean operator)\r\n"
31439 		"\tA false boolean state\r\n\r\n"
31440 		"Returns a boolean value." },
31441 
31442 	{ OP_AND, "And (Boolean operator)\r\n"
31443 		"\tAnd is true if all of its arguments are true.\r\n\r\n"
31444 		"Returns a boolean value.  Takes 2 or more boolean arguments." },
31445 
31446 	{ OP_OR, "Or (Boolean operator)\r\n"
31447 		"\tOr is true if any of its arguments are true.\r\n\r\n"
31448 		"Returns a boolean value.  Takes 2 or more boolean arguments." },
31449 
31450 	{ OP_XOR, "Xor (Boolean operator)\r\n"
31451 		"\tXor is true if exactly one of its arguments is true.\r\n\r\n"
31452 		"Returns a boolean value.  Takes 2 or more boolean arguments." },
31453 
31454 	{ OP_EQUALS, "Equals (Boolean operator)\r\n"
31455 		"\tIs true if all of its arguments are equal.\r\n\r\n"
31456 		"Returns a boolean value.  Takes 2 or more numeric arguments." },
31457 
31458 	{ OP_GREATER_THAN, "Greater Than (Boolean operator)\r\n"
31459 		"\tTrue if the first argument is greater than the subsequent argument(s).\r\n\r\n"
31460 		"Returns a boolean value.  Takes 2 numeric arguments." },
31461 
31462 	{ OP_LESS_THAN, "Less Than (Boolean operator)\r\n"
31463 		"\tTrue if the first argument is less than the subsequent argument(s).\r\n\r\n"
31464 		"Returns a boolean value.  Takes 2 numeric arguments." },
31465 
31466 	{ OP_NOT_EQUAL, "Not Equal To (Boolean operator)\r\n"
31467 		"\tIs true if the first argument is not equal to any of the subsequent arguments.\r\n\r\n"
31468 		"Returns a boolean value.  Takes 2 or more numeric arguments." },
31469 
31470 	{ OP_GREATER_OR_EQUAL, "Greater Than Or Equal To (Boolean operator)\r\n"
31471 		"\tTrue if the first argument is greater than or equal to the subsequent argument(s).\r\n\r\n"
31472 		"Returns a boolean value.  Takes 2 numeric arguments." },
31473 
31474 	{ OP_LESS_OR_EQUAL, "Less Than Or Equal To (Boolean operator)\r\n"
31475 		"\tTrue if the first argument is less than or equal to the subsequent argument(s).\r\n\r\n"
31476 		"Returns a boolean value.  Takes 2 numeric arguments." },
31477 
31478 	// Goober5000
31479 	{ OP_PERFORM_ACTIONS, "perform-actions\r\n"
31480 		"\tThis sexp allows actions to be performed as part of a conditional test.  It is most useful for assigning variables or performing some sort of pre-test action within the conditional part of \"when\", etc.  "
31481 		"It works well as the first branch of an \"and\" sexp, provided it returns true so as to not affect the return value of the \"and\".\r\n\r\n"
31482 		"Returns a boolean value.  Takes 2 or more arguments.\r\n"
31483 		"\t1:\tA boolean value to return after all successive actions have been performed.  NOTE: Even though this value is not returned until the actions are performed, it is evaluated BEFORE any of the actions.\r\n"
31484 		"\tRest:\tActions to perform, which would normally appear in a \"when\" sexp.\r\n" },
31485 
31486 	// Goober5000
31487 	{ OP_STRING_EQUALS, "String Equals (Boolean operator)\r\n"
31488 		"\tIs true if all of its arguments are equal.\r\n\r\n"
31489 		"Returns a boolean value.  Takes 2 or more string arguments." },
31490 
31491 	// Goober5000
31492 	{ OP_STRING_GREATER_THAN, "String Greater Than (Boolean operator)\r\n"
31493 		"\tTrue if the first argument is greater than the second argument.\r\n\r\n"
31494 		"Returns a boolean value.  Takes 2 string arguments." },
31495 
31496 	// Goober5000
31497 	{ OP_STRING_LESS_THAN, "String Less Than (Boolean operator)\r\n"
31498 		"\tTrue if the first argument is less than the second argument.\r\n\r\n"
31499 		"Returns a boolean value.  Takes 2 string arguments." },
31500 
31501 	// Goober5000 - added wing capability
31502 	{ OP_IS_IFF, "Is IFF (Boolean operator)\r\n"
31503 		"\tTrue if ship(s) or wing(s) are all of the specified team.\r\n\r\n"
31504 		"Returns a boolean value.  Takes 2 or more arguments...\r\n"
31505 		"\t1:\tTeam (\"friendly\", \"hostile\", \"neutral\", or \"unknown\").\r\n"
31506 		"\tRest:\tName of ship or wing to check." },
31507 
31508 	// Goober5000
31509 	{ OP_IS_AI_CLASS, "Is AI Class (Boolean operator)\r\n"
31510 		"\tTrue if ship or ship subsystem(s) is/are all of the specified AI class.\r\n\r\n"
31511 		"Returns a boolean value.  Takes 2 or more arguments...\r\n"
31512 		"\t1:\tAI class (\"None\", \"Coward\", \"Lieutenant\", etc.)\r\n"
31513 		"\t2:\tName of ship to check.\r\n"
31514 		"\tRest:\tName of ship subsystem(s) to check (optional)" },
31515 
31516 	// Goober5000
31517 	{ OP_IS_SHIP_TYPE, "Is Ship Type (Boolean operator)\r\n"
31518 		"\tTrue if ship or ships is/are all of the specified ship type.\r\n\r\n"
31519 		"Returns a boolean value.  Takes 2 or more arguments...\r\n"
31520 		"\t1:\tShip type (\"fighter\", \"bomber\", etc.)\r\n"
31521 		"\t2:\tName of ship to check.\r\n" },
31522 
31523 	// Goober5000
31524 	{ OP_IS_SHIP_CLASS, "Is Ship Class (Boolean operator)\r\n"
31525 		"\tTrue if ship or ships is/are all of the specified ship class.\r\n\r\n"
31526 		"Returns a boolean value.  Takes 2 or more arguments...\r\n"
31527 		"\t1:\tShip class\r\n"
31528 		"\t2:\tName of ship to check.\r\n" },
31529 
31530 	{ OP_HAS_TIME_ELAPSED, "Has time elapsed (Boolean operator)\r\n"
31531 		"\tBecomes true when the specified amount of time has elapsed (Mission time "
31532 		"becomes greater than the specified time).\r\n"
31533 		"Returns a boolean value.  Takes 1 numeric argument...\r\n"
31534 		"\t1:\tThe amount of time in seconds." },
31535 
31536 	{ OP_NOT, "Not (Boolean operator)\r\n"
31537 		"\tReturns opposite boolean value of argument (True becomes false, and "
31538 		"false becomes true).\r\n\r\n"
31539 		"Returns a boolean value.  Takes 1 boolean argument." },
31540 
31541 	{ OP_PREVIOUS_GOAL_TRUE, "Previous Mission Goal True (Boolean operator)\r\n"
31542 		"\tReturns true if the specified goal in the specified mission is true "
31543 		"(or succeeded).  It returns false otherwise.\r\n\r\n"
31544 		"Returns a boolean value.  Takes 2 required arguments and 1 optional argument...\r\n"
31545 		"\t1:\tName of the mission.\r\n"
31546 		"\t2:\tName of the goal in the mission.\r\n"
31547 		"\t3:\t(Optional) True/False which signifies what this sexpression should return when "
31548 		"this mission is played as a single mission." },
31549 
31550 	{ OP_PREVIOUS_GOAL_FALSE, "Previous Mission Goal False (Boolean operator)\r\n"
31551 		"\tReturns true if the specified goal in the specified mission "
31552 		"is false (or failed).  It returns false otherwise.\r\n\r\n"
31553 		"Returns a boolean value.  Takes 2 required arguments and 1 optional argument...\r\n"
31554 		"\t1:\tName of the mission.\r\n"
31555 		"\t2:\tName of the goal in the mission.\r\n"
31556 		"\t3:\t(Optional) True/False which signifies what this sexpression should return when "
31557 		"this mission is played as a single mission." },
31558 
31559 	{ OP_PREVIOUS_GOAL_INCOMPLETE, "Previous Mission Goal Incomplete (Boolean operator)\r\n"
31560 		"\tReturns true if the specified goal in the specified mission "
31561 		"is incomplete (not true or false).  It returns false otherwise.\r\n\r\n"
31562 		"Returns a boolean value.  Takes 2 required arguments and 1 optional argument...\r\n"
31563 		"\t1:\tName of the mission.\r\n"
31564 		"\t2:\tName of the goal in the mission.\r\n"
31565 		"\t3:\t(Optional) True/False which signifies what this sexpression should return when "
31566 		"this mission is played as a single mission." },
31567 
31568 	{ OP_PREVIOUS_EVENT_TRUE, "Previous Mission Event True (Boolean operator)\r\n"
31569 		"\tReturns true if the specified event in the specified mission is true "
31570 		"(or succeeded).  It returns false otherwise.\r\n\r\n"
31571 		"Returns a boolean value.  Takes 2 required arguments and 1 optional argument...\r\n"
31572 		"\t1:\tName of the mission.\r\n"
31573 		"\t2:\tName of the event in the mission.\r\n"
31574 		"\t3:\t(Optional) True/False which signifies what this sexpression should return when "
31575 		"this mission is played as a single mission." },
31576 
31577 	{ OP_PREVIOUS_EVENT_FALSE, "Previous Mission Event False (Boolean operator)\r\n"
31578 		"\tReturns true if the specified event in the specified mission "
31579 		"is false (or failed).  It returns false otherwise.\r\n\r\n"
31580 		"Returns a boolean value.  Takes 2 required arguments and 1 optional argument...\r\n"
31581 		"\t1:\tName of the mission.\r\n"
31582 		"\t2:\tName of the event in the mission.\r\n"
31583 		"\t3:\t(Optional) True/False which signifies what this sexpression should return when "
31584 		"this mission is played as a single mission." },
31585 
31586 	{ OP_PREVIOUS_EVENT_INCOMPLETE, "Previous Mission Event Incomplete (Boolean operator)\r\n"
31587 		"\tReturns true if the specified event in the specified mission "
31588 		"is incomplete (not true or false).  It returns false otherwise.\r\n\r\n"
31589 		"Returns a boolean value.  Takes 2 required arguments and 1 optional argument...\r\n"
31590 		"\t1:\tName of the mission.\r\n"
31591 		"\t2:\tName of the event in the mission.\r\n"
31592 		"\t3:\t(Optional) True/False which signifies what this sexpression should return when "
31593 		"this mission is played as a single mission." },
31594 
31595 	{ OP_GOAL_TRUE_DELAY, "Mission Goal True (Boolean operator)\r\n"
31596 		"\tReturns true N seconds after the specified goal in the this mission is true "
31597 		"(or succeeded).  It returns false otherwise.\r\n\r\n"
31598 		"Returns a boolean value.  Takes 2 required arguments and 1 optional argument...\r\n"
31599 		"\t1:\tName of the event in the mission.\r\n"
31600 		"\t2:\tNumber of seconds to delay before returning true."},
31601 
31602 	{ OP_GOAL_FALSE_DELAY, "Mission Goal False (Boolean operator)\r\n"
31603 		"\tReturns true N seconds after the specified goal in the this mission is false "
31604 		"(or failed).  It returns false otherwise.\r\n\r\n"
31605 		"Returns a boolean value.  Takes 2 required arguments and 1 optional argument...\r\n"
31606 		"\t1:\tName of the event in the mission.\r\n"
31607 		"\t2:\tNumber of seconds to delay before returning true."},
31608 
31609 	{ OP_GOAL_INCOMPLETE, "Mission Goal Incomplete (Boolean operator)\r\n"
31610 		"\tReturns true if the specified goal in the this mission is incomplete.  This "
31611 		"sexpression will only be useful in conjunction with another sexpression like"
31612 		"has-time-elapsed.  Used alone, it will return true upon misison startup."
31613 		"Returns a boolean value.  Takes 1 argument...\r\n"
31614 		"\t1:\tName of the event in the mission."},
31615 
31616 	{ OP_EVENT_TRUE_DELAY, "Mission Event True (Boolean operator)\r\n"
31617 		"\tReturns true N seconds after the specified event in the this mission is true "
31618 		"(or succeeded).  It returns false otherwise.\r\n\r\n"
31619 		"Returns a boolean value.  Takes 2 required arguments and 1 optional argument...\r\n"
31620 		"\t1:\tName of the event in the mission.\r\n"
31621 		"\t2:\tNumber of seconds to delay before returning true.\r\n"
31622 		"\t3:\t(Optional) Defaults to False. When set to false, directives will only appear as soon as the specified event is true.\r\n"
31623 		"\t\tWhen set to true, the event only affects whether the directive succeeds/fails, and has no effect on when it appears"},
31624 
31625 	{ OP_EVENT_FALSE_DELAY, "Mission Event False (Boolean operator)\r\n"
31626 		"\tReturns true N seconds after the specified event in the this mission is false "
31627 		"(or failed).  It returns false otherwise.\r\n\r\n"
31628 		"Returns a boolean value.  Takes 2 required arguments and 1 optional argument...\r\n"
31629 		"\t1:\tName of the event in the mission.\r\n"
31630 		"\t2:\tNumber of seconds to delay before returning true.\r\n"
31631 		"\t3:\t(Optional) Defaults to False. When set to false, directives will only appear as soon as the specified event is true.\r\n"
31632 		"\t\tWhen set to true, the event only affects whether the directive succeeds/fails, and has no effect on when it appears"},
31633 
31634 	{ OP_EVENT_TRUE_MSECS_DELAY, "Mission Event True (Boolean operator)\r\n"
31635 		"\tReturns true N milliseconds after the specified event in the this mission is true "
31636 		"(or succeeded).  It returns false otherwise.\r\n\r\n"
31637 		"Returns a boolean value.  Takes 2 required arguments and 1 optional argument...\r\n"
31638 		"\t1:\tName of the event in the mission.\r\n"
31639 		"\t2:\tNumber of milliseconds to delay before returning true.\r\n"
31640 		"\t3:\t(Optional) Defaults to False. When set to false, directives will only appear as soon as the specified event is true.\r\n"
31641 		"\t\tWhen set to true, the event only affects whether the directive succeeds/fails, and has no effect on when it appears"},
31642 
31643 	{ OP_EVENT_FALSE_MSECS_DELAY, "Mission Event False (Boolean operator)\r\n"
31644 		"\tReturns true N milliseconds after the specified event in the this mission is false "
31645 		"(or failed).  It returns false otherwise.\r\n\r\n"
31646 		"Returns a boolean value.  Takes 2 required arguments and 1 optional argument...\r\n"
31647 		"\t1:\tName of the event in the mission.\r\n"
31648 		"\t2:\tNumber of milliseconds to delay before returning true.\r\n"
31649 		"\t3:\t(Optional) Defaults to False. When set to false, directives will only appear as soon as the specified event is true.\r\n"
31650 		"\t\tWhen set to true, the event only affects whether the directive succeeds/fails, and has no effect on when it appears"},
31651 
31652 	{ OP_EVENT_INCOMPLETE, "Mission Event Incomplete (Boolean operator)\r\n"
31653 		"\tReturns true if the specified event in the this mission is incomplete.  This "
31654 		"sexpression will only be useful in conjunction with another sexpression like"
31655 		"has-time-elapsed.  Used alone, it will return true upon misison startup."
31656 		"Returns a boolean value.  Takes 1 argument...\r\n"
31657 		"\t1:\tName of the event in the mission."},
31658 
31659 	{ OP_IS_DESTROYED_DELAY, "Is destroyed delay (Boolean operator)\r\n"
31660 		"\tBecomes true <delay> seconds after all specified ships have been destroyed.\r\n"
31661 		"\tWARNING: If multiple is-destroyed-delay SEXPs are used in a directive event, unexpected results may be "
31662 		"observed. Instead, use a single is-destroyed-delay SEXP with multiple parameters.\r\n"
31663 		"Returns a boolean value.  Takes 2 or more arguments...\r\n"
31664 		"\t1:\tTime delay in seconds (see above).\r\n"
31665 		"\tRest:\tName of ship (or wing) to check status of." },
31666 
31667 	{ OP_WAS_DESTROYED_BY_DELAY, "Was destroyed by delay (Boolean operator)\r\n"
31668 		"\tBecomes true <delay> seconds after all specified ships have been destroyed by the specified first ship.\r\n\r\n"
31669 		"Returns a boolean value.  Takes 3 or more arguments...\r\n"
31670 		"\t1:\tTime delay in seconds (see above).\r\n"
31671 		"\t2:\tShip that should have destroyed the other ships (see below).\r\n"
31672 		"\tRest:\tName of ships to check status of." },
31673 
31674 	{ OP_IS_SUBSYSTEM_DESTROYED_DELAY, "Is subsystem destroyed delay (Boolean operator)\r\n"
31675 		"\tBecomes true <delay> seconds after the specified subsystem of the specified "
31676 		"ship is destroyed.\r\n\r\n"
31677 		"Returns a boolean value.  Takes 3 arguments...\r\n"
31678 		"\t1:\tName of ship the subsystem we are checking is on.\r\n"
31679 		"\t2:\tThe name of the subsystem we are checking status of.\r\n"
31680 		"\t3:\tTime delay in seconds (see above)." },
31681 
31682 	{ OP_IS_DISABLED_DELAY, "Is disabled delay (Boolean operator)\r\n"
31683 		"\tBecomes true <delay> seconds after the specified ship(s) are disabled.  A "
31684 		"ship is disabled when all of its engine subsystems are destroyed.  All "
31685 		"ships must be diabled for this function to return true.\r\n\r\n"
31686 		"Returns a boolean value.  Takes 2 or more arguments...\r\n"
31687 		"\t1:\tTime delay is seconds (see above).\r\n"
31688 		"\tRest:\tNames of ships to check disabled status of." },
31689 
31690 	{ OP_IS_DISARMED_DELAY, "Is disarmed delay (Boolean operator)\r\n"
31691 		"\tBecomes true <delay> seconds after the specified ship(s) are disarmed.  A "
31692 		"ship is disarmed when all of its turret subsystems are destroyed.  All "
31693 		"ships must be disarmed for this function to return true.\r\n\r\n"
31694 		"Returns a boolean value.  Takes 2 or more arguments...\r\n"
31695 		"\t1:\tTime delay is seconds (see above).\r\n"
31696 		"\tRest:\tNames of ships to check disarmed status of." },
31697 
31698 	{ OP_HAS_DOCKED_DELAY, "Has docked delay (Boolean operator)\r\n"
31699 		"\tBecomes true <delay> seconds after the specified ships have docked the "
31700 		"specified number of times.\r\n\r\n"
31701 		"Returns a boolean value.  Takes 4 arguments...\r\n"
31702 		"\t1:\tThe name of the docker ship\r\n"
31703 		"\t2:\tThe name of the dockee ship\r\n"
31704 		"\t3:\tThe number of times they have to have docked\r\n"
31705 		"\t4:\tTime delay in seconds (see above)." },
31706 
31707 	{ OP_HAS_UNDOCKED_DELAY, "Has undocked delay (Boolean operator)\r\n"
31708 		"\tBecomes true <delay> seconds after the specified ships have undocked the "
31709 		"specified number of times.\r\n\r\n"
31710 		"Returns a boolean value.  Takes 4 arguments...\r\n"
31711 		"\t1:\tThe name of the docker ship\r\n"
31712 		"\t2:\tThe name of the dockee ship\r\n"
31713 		"\t3:\tThe number of times they have to have undocked\r\n"
31714 		"\t4:\tTime delay in seconds (see above)." },
31715 
31716 	{ OP_HAS_ARRIVED_DELAY, "Has arrived delay (Boolean operator)\r\n"
31717 		"\tBecomes true <delay> seconds after the specified ship(s) have arrived into the mission\r\n\r\n"
31718 		"Returns a boolean value.  Takes 2 or more arguments...\r\n"
31719 		"\t1:\tTime delay in seconds (see above).\r\n"
31720 		"\tRest:\tName of ship (or wing) we want to check has arrived." },
31721 
31722 	{ OP_HAS_DEPARTED_DELAY, "Has departed delay (Boolean operator)\r\n"
31723 		"\tBecomes true <delay> seconds after the specified ship(s) or wing(s) have departed "
31724 		"from the mission by warping out.  If any ship was destroyed, this operator will "
31725 		"never be true.\r\n\r\n"
31726 		"Returns a boolean value.  Takes 2 or more arguments...\r\n"
31727 		"\t1:\tTime delay in seconds (see above).\r\n"
31728 		"\tRest:\tName of ship (or wing) we want to check has departed." },
31729 
31730 	{ OP_WAYPOINTS_DONE_DELAY, "Waypoints done delay (Boolean operator)\r\n"
31731 		"\tBecomes true <delay> seconds after the specified ship has completed flying the "
31732 		"specified waypoint path.\r\n\r\n"
31733 		"Returns a boolean value.  Takes 3 or 4 arguments...\r\n"
31734 		"\t1:\tName of ship we are checking.\r\n"
31735 		"\t2:\tWaypoint path we want to check if ship has flown.\r\n"
31736 		"\t3:\tTime delay in seconds (see above).\r\n"
31737 		"\t4:\tHow many times the ship has completed the waypoint path (optional)." },
31738 
31739 	{ OP_SHIP_TYPE_DESTROYED, "Ship Type Destroyed (Boolean operator)\r\n"
31740 		"\tBecomes true when the specified percentage of ship types in this mission "
31741 		"have been destroyed.  The ship type is a generic type such as fighter/bomber, "
31742 		"transport, etc.  Fighters and bombers count as the same type.\r\n\r\n"
31743 		"Returns a boolean value.  Takes 2 arguments...\r\n"
31744 		"\t1:\tPercentage of ships that must be destroyed.\r\n"
31745 		"\t2:\tShip type to check for." },
31746 
31747 	{ OP_TIME_SHIP_DESTROYED, "Time ship destroyed (Time operator)\r\n"
31748 		"\tReturns the time the specified ship was destroy.\r\n\r\n"
31749 		"Returns a numeric value.  Takes 1 argument...\r\n"
31750 		"\t1:\tName of ship we want to check." },
31751 
31752 	{ OP_TIME_SHIP_ARRIVED, "Time ship arrived (Time operator)\r\n"
31753 		"\tReturns the time the specified ship arrived into the mission.\r\n\r\n"
31754 		"Returns a numeric value.  Takes 1 argument...\r\n"
31755 		"\t1:\tName of ship we want to check." },
31756 
31757 	{ OP_TIME_SHIP_DEPARTED, "Time ship departed (Time operator)\r\n"
31758 		"\tReturns the time the specified ship departed the mission by warping out.  Being "
31759 		"destroyed doesn't count departed.\r\n\r\n"
31760 		"Returns a numeric value.  Takes 1 argument...\r\n"
31761 		"\t1:\tName of ship we want to check." },
31762 
31763 	{ OP_TIME_WING_DESTROYED, "Time wing destroyed (Time operator)\r\n"
31764 		"\tReturns the time the specified wing was destroy.\r\n\r\n"
31765 		"Returns a numeric value.  Takes 1 argument...\r\n"
31766 		"\t1:\tName of wing we want to check." },
31767 
31768 	{ OP_TIME_WING_ARRIVED, "Time wing arrived (Time operator)\r\n"
31769 		"\tReturns the time the specified wing arrived into the mission.\r\n\r\n"
31770 		"Returns a numeric value.  Takes 1 argument...\r\n"
31771 		"\t1:\tName of wing we want to check." },
31772 
31773 	{ OP_TIME_WING_DEPARTED, "Time wing departed (Time operator)\r\n"
31774 		"\tReturns the time the specified wing departed the mission by warping out.  All "
31775 		"ships in the wing have to have warped out.  If any are destroyed, the wing can "
31776 		"never be considered departed.\r\n\r\n"
31777 		"Returns a numeric value.  Takes 1 argument...\r\n"
31778 		"\t1:\tName of ship we want to check." },
31779 
31780 	{ OP_MISSION_TIME, "Mission time (Time operator)\r\n"
31781 		"\tReturns the current time into the mission.\r\n\r\n"
31782 		"Returns a numeric value.  Takes no arguments." },
31783 
31784 	{ OP_MISSION_TIME_MSECS, "Mission time, in milliseconds (Time operator)\r\n"
31785 		"\tReturns the current time into the mission, in milliseconds.  Useful for more fine-grained timing than possible with normal second-based sexps.  (Tip: when an event occurs, assign the result of mission-time-msecs to a variable.  Then, in another event, wait until mission-time-msecs is greater than the value of that variable plus some delay amount.  This second event should be chained or coupled with additional conditions so that it doesn't accidentally fire on an uninitialized variable!)\r\n\r\n"
31786 		"Returns a numeric value.  Takes no arguments." },
31787 
31788 	{ OP_TIME_DOCKED, "Time docked (Time operator)\r\n"
31789 		"\tReturns the time the specified ships docked.\r\n\r\n"
31790 		"Returns a numeric value.  Takes 3 arguments...\r\n"
31791 		"\t1:\tThe name of the docker ship.\r\n"
31792 		"\t2:\tThe name of the dockee ship.\r\n"
31793 		"\t3:\tThe number of times they must have docked to be true." },
31794 
31795 	{ OP_TIME_UNDOCKED, "Time undocked (Time operator)\r\n"
31796 		"\tReturns the time the specified ships undocked.\r\n\r\n"
31797 		"Returns a numeric value.  Takes 3 arguments...\r\n"
31798 		"\t1:\tThe name of the docker ship.\r\n"
31799 		"\t2:\tThe name of the dockee ship.\r\n"
31800 		"\t3:\tThe number of times they must have undocked to be true." },
31801 
31802 	{ OP_TIME_TO_GOAL, "Time-to-goal (Time operator)\r\n"
31803 		"\tReturns the number of seconds until a ship reaches its waypoint\r\n\r\n"
31804 		"Returns a number value.  Takes 1 argument...\r\n"
31805 		"\t1:\tName of ship to check waypoint time." },
31806 
31807 	{ OP_AFTERBURNER_LEFT, "Afterburner left\r\n"
31808 		"\tReturns a ship's current engine energy as a percentage.\r\n"
31809 		"\t1: Ship name\r\n" },
31810 
31811 	{ OP_WEAPON_ENERGY_LEFT, "Weapon energy left\r\n"
31812 		"\tReturns a ship's current weapon energy as a percentage.\r\n"
31813 		"\t1: Ship name\r\n" },
31814 
31815 	{ OP_SHIELDS_LEFT, "Shields left (Status operator)\r\n"
31816 		"\tReturns the current level of the specified ship's shields as a percentage.\r\n\r\n"
31817 		"Returns a numeric value.  Takes 1 argument...\r\n"
31818 		"\t1:\tName of ship to check." },
31819 
31820 	{ OP_HITS_LEFT, "Hits left (Status operator)\r\n"
31821 		"\tReturns the current level of the specified ship's hull as a percentage.\r\n\r\n"
31822 		"Returns a numeric value.  Takes 1 argument...\r\n"
31823 		"\t1:\tName of ship to check." },
31824 
31825 	{ OP_HITS_LEFT_SUBSYSTEM, "Hits left subsystem (status operator, deprecated)\r\n"
31826 		"\tReturns the current level of the specified ship's subsystem integrity as a percentage of the damage done to *all "
31827 		"subsystems of the same type*.  This operator provides the same functionality as the new hits-left-subsystem-generic "
31828 		"operator, except that it gets the subsystem type in a very misleading way.  Common consensus among SCP programmers is "
31829 		"that this operator was intended to behave like hits-left-subsystem-specific but was programmed incorrectly.  As such, "
31830 		"this operator is deprecated.  Mission designers are strongly encouraged to use hits-left-subsystem-specific rather than "
31831 		"the optional boolean parameter.\r\n\r\n"
31832 		"Returns a numeric value.  Takes 2 or 3 arguments...\r\n"
31833 		"\t1:\tName of ship to check.\r\n"
31834 		"\t2:\tName of subsystem on ship to check.\r\n"
31835 		"\t3:\t(Optional) True/False. When set to true only the subsystem supplied will be tested; when set to false (the default), "
31836 		"all subsystems of that type will be tested." },
31837 
31838 	{ OP_HITS_LEFT_SUBSYSTEM_GENERIC, "hits-left-subsystem-generic (status operator)\r\n"
31839 		"\tReturns the current level of integrity of a generic subsystem type, as a percentage.  A \"generic subsystem type\" "
31840 		"is a subsystem *category*, (for example, Engines), that includes one or more *individual* subsystems (for example, engine01, "
31841 		"engine02, and engine03) on a ship.\r\n\r\nThis is the way FreeSpace tests certain subsystem thresholds internally; for "
31842 		"example, if the integrity of all engine subsystems (that is, the combined strength of all engines divided by the maximum "
31843 		"total strength of all engines) is less than 30%, the player cannot warp out.\r\n\r\n"
31844 		"Returns a numeric value.  Takes 2 arguments...\r\n"
31845 		"\t1:\tName of ship to check\r\n"
31846 		"\t2:\tName of subsystem type to check\r\n" },
31847 
31848 	{ OP_HITS_LEFT_SUBSYSTEM_SPECIFIC, "hits-left-subsystem-specific (status operator)\r\n"
31849 		"\tReturns the current level of integrity of a specific subsystem, as a percentage.\r\n\r\n(If you were looking for the old "
31850 		"hits-left-subsystem operator, this is almost certainly the operator you want.  The hits-left-subsystem operator "
31851 		"suffers from a serious design flaw that causes it to behave like hits-left-subsystem-generic.  As such it has been deprecated "
31852 		"and will not appear in the operator list; it can only be used if you type it in manually.  Old missions using hits-left-subsystem "
31853 		"will still work, but mission designers are strongly encouraged to use the new operators instead.)\r\n\r\n"
31854 		"Returns a numeric value.  Takes 2 arguments...\r\n"
31855 		"\t1:\tName of ship to check\r\n"
31856 		"\t2:\tName of subsystem to check\r\n" },
31857 
31858 	{ OP_SIM_HITS_LEFT, "Simulated Hits left (Status operator)\r\n"
31859 		"\tReturns the current level of the specified ship's simulated hull as a percentage.\r\n\r\n"
31860 		"Returns a numeric value.  Takes 1 argument...\r\n"
31861 		"\t1:\tName of ship to check." },
31862 
31863 	{ OP_DISTANCE, "Distance (Status operator)\r\n"
31864 		"\tReturns the distance between two objects.  These can be ships, wings, or waypoints.\r\n"
31865 		"When a wing or team is given (for either argument), the result will be the closest distance.\r\n\r\n"
31866 		"NOTE: The standard retail SEXP measures between the center of one ship and the bounding box of another.  It is "
31867 		"recommended to use center-distance or bbox-distance.\r\n\r\n"
31868 		"Returns a numeric value.  Takes 2 arguments...\r\n"
31869 		"\t1:\tThe name of one of the objects.\r\n"
31870 		"\t2:\tThe name of the other object."
31871 	},
31872 
31873 	{ OP_DISTANCE_CENTER, "Distance-To-Center (Status operator)\r\n"
31874 		"\tReturns the distance between the centers of two objects.  These can be ships, wings, or waypoints.\r\n"
31875 		"When a wing or team is given (for either argument), the result will be the closest distance.\r\n\r\n"
31876 		"Returns a numeric value.  Takes 2 arguments...\r\n"
31877 		"\t1:\tThe name of one of the objects.\r\n"
31878 		"\t2:\tThe name of the other object."
31879 	},
31880 
31881 	{ OP_DISTANCE_BBOX, "Distance-To-BBox (Status operator)\r\n"
31882 		"\tReturns the distance between the bounding boxes of two objects.  These can be ships, wings, or waypoints.\r\n"
31883 		"When a wing or team is given (for either argument), the result will be the closest distance.\r\n\r\n"
31884 		"Returns a numeric value.  Takes 2 arguments...\r\n"
31885 		"\t1:\tThe name of one of the objects.\r\n"
31886 		"\t2:\tThe name of the other object."
31887 	},
31888 
31889 	{ OP_DISTANCE_CENTER_SUBSYSTEM, "Center distance from ship subsystem (Status operator)\r\n"
31890 		"\tReturns the distance between the center of an object and the center of a ship subsystem.  The object can be a ship, wing, or waypoint.\r\n"
31891 		"When a wing or team is given, the result will be the closest distance.\r\n\r\n"
31892 		"Returns a numeric value.  Takes 3 arguments...\r\n"
31893 		"\t1:\tThe name of the object.\r\n"
31894 		"\t2:\tThe name of the ship which houses the subsystem.\r\n"
31895 		"\t3:\tThe name of the subsystem." },
31896 
31897 	{ OP_DISTANCE_BBOX_SUBSYSTEM, "BBox distance from ship subsystem (Status operator)\r\n"
31898 		"\tReturns the distance between the bounding box of an object and the center of a ship subsystem.  The object can be a ship, wing, or waypoint.\r\n"
31899 		"When a wing or team is given, the result will be the closest distance.\r\n\r\n"
31900 		"Returns a numeric value.  Takes 3 arguments...\r\n"
31901 		"\t1:\tThe name of the object.\r\n"
31902 		"\t2:\tThe name of the ship which houses the subsystem.\r\n"
31903 		"\t3:\tThe name of the subsystem." },
31904 
31905 	{ OP_NUM_WITHIN_BOX, "Number of specified objects in the box specified\r\n"
31906 		"\t1: Box center (X)\r\n"
31907 		"\t2: Box center (Y)\r\n"
31908 		"\t3: Box center (Z)\r\n"
31909 		"\t4: Box half-width (distance +/- away from center X)\r\n"
31910 		"\t5: Box half-height (distance +/- away from center Y)\r\n"
31911 		"\t6: Box half-depth (distance +/- away from center Z)\r\n"
31912 		"\tRest:\tShips or wings to check" },
31913 
31914 	{ OP_IS_IN_BOX, "Whether an object is in the box specified. If a second ship is specified, "
31915 		"the box is relative to that ship's reference frame. \r\n"
31916 		"\t1: Ships, wings, or points to check\r\n"
31917 		"\t2: Min X\r\n"
31918 		"\t3: Max X\r\n"
31919 		"\t4: Min Y\r\n"
31920 		"\t5: Max Y\r\n"
31921 		"\t6: Min Z\r\n"
31922 		"\t7: Max Z\r\n"
31923 		"\t8: Ship to use as reference frame (optional)." },
31924 
31925 	{ OP_IS_IN_MISSION, "Is-In-Mission (Status operator)\r\n"
31926 		"\tChecks whether a given ship is presently in the mission.  This sexp doesn't check the arrival list or exited status; it only tests to see if the "
31927 		"ship is active.  This means that internally the sexp only returns SEXP_TRUE or SEXP_FALSE and does not use any of the special shortcut values.  This is useful "
31928 		"for ships created with ship-create, as those ships will not have used the conventional ship arrival list.\r\n\r\n"
31929 		"Takes 1 or more string arguments, which are checked against the ship list.  (If more than 1 argument is specified, the sexp will only evaluate to true if all ships "
31930 		"are in the mission simultaneously.)" },
31931 
31932 	{ OP_IS_DOCKED, "Is-Docked (Status operator)\r\n"
31933 		"\tChecks whether the specified ships are currently docked.  This sexp is different from has-docked-delay, which will return true if the ships have docked at "
31934 		"any point in the past.  The has-docked-delay sexp checks the mission log, whereas the is-docked sexp examines the actual dockpoints.\r\n\r\n"
31935 		"Takes 1 or more arguments.  (If more than 2 arguments are specified, the sexp will only evaluate to true if all ships are docked simultaneously.)\r\n"
31936 		"If a single argument is supplied, the sexp will evaluate to true if anything is docked to the host ship. \r\n"
31937 		"\t1:\tThe host ship.\r\n"
31938 		"\tRest:\tShip to check as docked to the host ship." },
31939 
31940 	{ OP_GET_DAMAGE_CAUSED, "Get damage caused (Status operator)\r\n"
31941 		"\tReturns the amount of damage one or more ships have done to a ship.\r\n\r\n"
31942 		"Takes 2 or more arguments...\r\n"
31943 		"\t1:\tShip that has been damaged.\r\n"
31944 		"\t2:\tName of ships that may have damaged it." },
31945 
31946 	{ OP_LAST_ORDER_TIME, "Last order time (Status operator)\r\n"
31947 		"\tReturns true if <count> seconds have elapsed since one or more ships have received "
31948 		"a meaningful order from the player.  A meaningful order is currently any order that "
31949 		"is not the warp out order.\r\n\r\n"
31950 		"Returns a boolean value.  Takes 2 or more arguments...\r\n"
31951 		"\t1:\tTime in seconds that must elapse.\r\n"
31952 		"\tRest:\tName of ship or wing to check for having received orders." },
31953 
31954 	{ OP_WHEN, "When (Conditional operator)\r\n"
31955 		"\tPerforms specified actions when a condition becomes true\r\n\r\n"
31956 		"Takes 2 or more arguments...\r\n"
31957 		"\t1:\tBoolean expression that must be true for actions to take place.\r\n"
31958 		"\tRest:\tActions to take when boolean expression becomes true." },
31959 
31960 	{ OP_COND, "Blah" },
31961 
31962 	// Goober5000
31963 	{ OP_WHEN_ARGUMENT, "When-argument (Conditional operator)\r\n"
31964 		"\tPerforms specified actions when a condition, given a set of arguments, becomes true.\r\n\r\n"
31965 		"Takes 3 or more arguments...\r\n"
31966 		"\t1:\tThe arguments to evaluate (see any-of, every-of, random-of, etc.).\r\n"
31967 		"\t2:\tBoolean expression that must be true for actions to take place.\r\n"
31968 		"\tRest:\tActions to take when the boolean expression becomes true." },
31969 
31970 	// Goober5000
31971 	{ OP_EVERY_TIME, "Every-time (Conditional operator)\r\n"
31972 		"\tThis is a version of \"when\" that will always evaluate its arguments.  It's useful "
31973 		"in situations where you need to repeatedly check things that may become true more than "
31974 		"once.  Since this sexp will execute every time it's evaluated, you may need to use it as "
31975 		"an argument to \"when\" if you want to impose restrictions on how it's called.\r\n\r\n"
31976 		"Takes 2 or more arguments...\r\n"
31977 		"\t1:\tBoolean expression that must be true for actions to take place.\r\n"
31978 		"\tRest:\tActions to take when boolean expression is true." },
31979 
31980 	// Goober5000
31981 	{ OP_EVERY_TIME_ARGUMENT, "Every-time-argument (Conditional operator)\r\n"
31982 		"\tThis is a version of \"when-argument\" that will always evaluate its arguments.  It's useful "
31983 		"in situations where you need to repeatedly check things that may become true more than "
31984 		"once.  Since this sexp will execute every time it's evaluated, you may need to use it as "
31985 		"an argument to \"when\" (not \"when-argument\") if you want to impose restrictions on how it's called.\r\n\r\n"
31986 		"Takes 3 or more arguments...\r\n"
31987 		"\t1:\tThe arguments to evaluate (see any-of, all-of, random-of, etc.).\r\n"
31988 		"\t2:\tBoolean expression that must be true for actions to take place.\r\n"
31989 		"\tRest:\tActions to take when the boolean expression becomes true." },
31990 
31991 	// Goober5000
31992 	{ OP_IF_THEN_ELSE, "If-then-else (Conditional operator)\r\n"
31993 		"\tPerforms one action if a condition is true (like \"when\"), or another action (or set of actions) if the condition is false.  "
31994 		"Note that this sexp only completes one of its branches once the condition has been determined; "
31995 		"it does not come back later and evaluate the other branch if the condition happens to switch truth values.\r\n\r\n"
31996 		"Takes 3 or more arguments...\r\n"
31997 		"\t1:\tBoolean expression to evaluate.\r\n"
31998 		"\t2:\tActions to take if that expression becomes true.\r\n"
31999 		"\tRest:\tActions to take if that expression becomes false.\r\n" },
32000 
32001 	// Goober5000
32002 	{ OP_SWITCH, "Switch (Conditional operator)\r\n"
32003 		"\tPerforms one action according to the value of the first argument.  If the argument evaluates to 0, the first action is performed; if the argument is 1, the next action is performed, etc.  "
32004 		"If the value is out of range, no action is performed at all.\r\n\r\n"
32005 		"Takes 2 or more arguments...\r\n"
32006 		"\t1:\tNumeric expression to evaluate.\r\n"
32007 		"\tRest:\tList of actions, one of which (at most) will be taken according to the value of the expression.\r\n" },
32008 
32009 	// Goober5000
32010 	{ OP_FUNCTIONAL_IF_THEN_ELSE, "Functional If-then-else (Conditional operator)\r\n"
32011 		"\tReturns the first number if the condition is true, and the second number otherwise.\r\n\r\n"
32012 		"This sexp is very similar to the ?: ternary operator in C and other programming languages.  Unfortunately, "
32013 		"due to limitations of the sexp system, it is not possible to create an equivalent sexp for string values.\r\n\r\n"
32014 		"Note that all arguments will be evaluated, even though only one will be returned.\r\n\r\n"
32015 		"Takes exactly 3 arguments...\r\n"
32016 		"\t1:\tBoolean expression to evaluate.\r\n"
32017 		"\t2:\tNumber to return if the expression is currently true.\r\n"
32018 		"\t3:\tNumber to return if the expression is not currently true..\r\n" },
32019 
32020 	// Goober5000
32021 	{ OP_FUNCTIONAL_SWITCH, "Functional Switch (Conditional operator)\r\n"
32022 		"\tReturns a number according to the value of the first argument.  If the argument evaluates to 0, the first of the following numbers is returned; if the argument is 1, the next number is performed, etc.  "
32023 		"If the value is out of range, zero is returned.\r\n\r\n"
32024 		"Note that all arguments will be evaluated, even though only one will be returned.\r\n\r\n"
32025 		"Takes 2 or more arguments...\r\n"
32026 		"\t1:\tNumeric expression to evaluate.\r\n"
32027 		"\tRest:\tList of numeric expressions, one of which will be returned according to the current value of the first argument.\r\n" },
32028 
32029 	// Karajorma
32030 	{ OP_DO_FOR_VALID_ARGUMENTS, "Do-for-valid-arguments (Conditional operator)\r\n"
32031 		"\tPerforms specified actions once for each valid " SEXP_ARGUMENT_STRING " in the parent conditional. For example, a FREDder may use (do-for-valid-arguments (modify-variable @var (+ @var 1))) to count the number of valid arguments.\r\n\r\n"
32032 		"\tThis operator must not be used to execute any SEXP that actually contains " SEXP_ARGUMENT_STRING ", as these are already being executed\r\n"
32033 		"\tmultiple times without using Do-for-valid-arguments. Any use of "  SEXP_ARGUMENT_STRING " will \r\n"
32034 		"\tprevent execution of the entire SEXP unless it is nested inside another when(or every-time)-argument SEXP.\r\n\r\n"
32035 		"Takes 1 or more arguments...\r\n"
32036 		"\tAll:\tActions to take." },
32037 
32038 	// Karajorma
32039 	{ OP_NUM_VALID_ARGUMENTS, "num-valid-arguments (Conditional operator)\r\n"
32040 		"\tReturns the number of valid arguments in the argument list.\r\n\r\n"
32041 		"Takes no arguments...\r\n"},
32042 
32043 	// Goober5000
32044 	{ OP_ANY_OF, "Any-of (Conditional operator)\r\n"
32045 		"\tSupplies arguments for the " SEXP_ARGUMENT_STRING " special data item.  Any of the supplied arguments can satisfy the expression(s) "
32046 		"in which " SEXP_ARGUMENT_STRING " is used.\r\n\r\n"
32047 		"In practice, this will behave like a standard \"for-each\" statement, evaluating the action operators for each argument that satisfies the condition.\r\n\r\n"
32048 		"Takes 1 or more arguments...\r\n"
32049 		"\tAll:\tAnything that could be used in place of " SEXP_ARGUMENT_STRING "." },
32050 
32051 	// Goober5000
32052 	{ OP_EVERY_OF, "Every-of (Conditional operator)\r\n"
32053 		"\tSupplies arguments for the " SEXP_ARGUMENT_STRING " special data item.  Every one of the supplied arguments will be evaluated to satisfy the expression(s) "
32054 		"in which " SEXP_ARGUMENT_STRING " is used.\r\n\r\n"
32055 		"Takes 1 or more arguments...\r\n"
32056 		"\tAll:\tAnything that could be used in place of " SEXP_ARGUMENT_STRING "." },
32057 
32058 	// Goober5000
32059 	{ OP_RANDOM_OF, "Random-of (Conditional operator)\r\n"
32060 		"\tSupplies arguments for the " SEXP_ARGUMENT_STRING " special data item.  A random supplied argument will be selected to satisfy the expression(s) "
32061 		"in which " SEXP_ARGUMENT_STRING " is used. The same argument will be returned by all subsequent calls\r\n\r\n"
32062 		"Takes 1 or more arguments...\r\n"
32063 		"\tAll:\tAnything that could be used in place of " SEXP_ARGUMENT_STRING "." },
32064 
32065 	// Karajorma
32066 	{ OP_RANDOM_MULTIPLE_OF, "Random-multiple-of (Conditional operator)\r\n"
32067 		"\tSupplies arguments for the " SEXP_ARGUMENT_STRING " special data item.  A random supplied argument will be selected to satisfy the expression(s) "
32068 		"in which " SEXP_ARGUMENT_STRING " is used.\r\n\r\n"
32069 		"Takes 1 or more arguments...\r\n"
32070 		"\tAll:\tAnything that could be used in place of " SEXP_ARGUMENT_STRING "." },
32071 
32072 	// Goober5000
32073 	{ OP_NUMBER_OF, "Number-of (Conditional operator)\r\n"
32074 		"\tSupplies arguments for the " SEXP_ARGUMENT_STRING " special data item.  Any [number] of the supplied arguments can satisfy the expression(s) "
32075 		"in which " SEXP_ARGUMENT_STRING " is used.\r\n\r\n"
32076 		"Takes 2 or more arguments...\r\n"
32077 		"\t1:\tNumber of arguments, as above\r\n"
32078 		"\tRest:\tAnything that could be used in place of " SEXP_ARGUMENT_STRING "." },
32079 
32080 	// Karajorma
32081 	{ OP_IN_SEQUENCE, "In-Sequence (Conditional operator)\r\n"
32082 		"\tSupplies arguments for the " SEXP_ARGUMENT_STRING " special data item.  The first argument in the list will be selected to satisfy the expression(s) "
32083 		"in which " SEXP_ARGUMENT_STRING " is used. The same argument will be returned by all subsequent calls\r\n\r\n"
32084 		"Takes 1 or more arguments...\r\n"
32085 		"\tAll:\tAnything that could be used in place of " SEXP_ARGUMENT_STRING "." },
32086 
32087 	// Goober5000
32088 	{ OP_FOR_COUNTER, "For-Counter (Conditional operator)\r\n"
32089 		"\tSupplies counter values for the " SEXP_ARGUMENT_STRING " special data item.  This sexp will count up from the start value to the stop value, and each value will be provided as an argument to the action operators.  "
32090 		"The default increment is 1, but if the optional increment parameter is provided, the counter will increment by that number.  The stop value will be supplied if appropriate; e.g. counting from 0 to 10 by 2 will supply 0, 2, 4, 6, 8, and 10; "
32091 		"but counting by 3 will supply 0, 3, 6, and 9.\r\n\r\n"
32092 		"Note that the counter values are all treated as valid arguments, and it is impossible to invalidate a counter argument.  If you want to invalidate a counter value, use Any-of and list the values explicitly.\r\n\r\n"
32093 		"This sexp will usually need to be accompanied by the string-to-int sexp, as the counter variables are provided in string format but are most useful in integer format.\r\n\r\n"
32094 		"Takes 2 or 3 arguments...\r\n"
32095 		"\t1:\tCounter start value\r\n"
32096 		"\t2:\tCounter stop value\r\n"
32097 		"\t3:\tCounter increment (optional)" },
32098 
32099 	// Goober5000
32100 	{ OP_FOR_SHIP_CLASS, "For-Ship-Class (Conditional operator)\r\n"
32101 		"\tSupplies values for the " SEXP_ARGUMENT_STRING " special data item.  This sexp will list all the ships of a certain class (or classes) in the mission, and each ship will be provided as an argument to the action operators.  "
32102 		"Note that the ships are all treated as valid arguments, and it is impossible to invalidate a ship argument.  If you want to invalidate a ship, use Any-of and list the ships explicitly.\r\n\r\n"
32103 		"Takes 1 or more arguments...\r\n"
32104 		"\tAll:\tShip class from which to list ships\r\n" },
32105 
32106 	// Goober5000
32107 	{ OP_FOR_SHIP_TYPE, "For-Ship-Type (Conditional operator)\r\n"
32108 		"\tSupplies values for the " SEXP_ARGUMENT_STRING " special data item.  This sexp will list all the ships of a certain type (or types) in the mission, and each ship will be provided as an argument to the action operators.  "
32109 		"Note that the ships are all treated as valid arguments, and it is impossible to invalidate a ship argument.  If you want to invalidate a ship, use Any-of and list the ships explicitly.\r\n\r\n"
32110 		"Takes 1 or more arguments...\r\n"
32111 		"\tAll:\tShip type from which to list ships\r\n" },
32112 
32113 	// Goober5000
32114 	{ OP_FOR_SHIP_TEAM, "For-Ship-Team (Conditional operator)\r\n"
32115 		"\tSupplies values for the " SEXP_ARGUMENT_STRING " special data item.  This sexp will list all the ships of a certain team (or teams) in the mission, and each ship will be provided as an argument to the action operators.  "
32116 		"Note that the ships are all treated as valid arguments, and it is impossible to invalidate a ship argument.  If you want to invalidate a ship, use Any-of and list the ships explicitly.\r\n\r\n"
32117 		"Takes 1 or more arguments...\r\n"
32118 		"\tAll:\tTeam (IFF) from which to list ships\r\n" },
32119 
32120 	// Goober5000
32121 	{ OP_FOR_SHIP_SPECIES, "For-Ship-Species (Conditional operator)\r\n"
32122 		"\tSupplies values for the " SEXP_ARGUMENT_STRING " special data item.  This sexp will list all the ships of a certain species (or multiple species) in the mission, and each ship will be provided as an argument to the action operators.  "
32123 		"Note that the ships are all treated as valid arguments, and it is impossible to invalidate a ship argument.  If you want to invalidate a ship, use Any-of and list the ships explicitly.\r\n\r\n"
32124 		"Takes 1 or more arguments...\r\n"
32125 		"\tAll:\tSpecies from which to list ships\r\n" },
32126 
32127 	// Goober5000
32128 	{ OP_FOR_PLAYERS, "For-Players (Conditional operator)\r\n"
32129 		"\tSupplies values for the " SEXP_ARGUMENT_STRING " special data item.  This sexp will list all the ships corresponding to valid players, and each ship will be provided as an argument to the action operators.  "
32130 		"Note that the ships are all treated as valid arguments, and it is impossible to invalidate a ship argument.  If you want to invalidate a ship, use Any-of and list the ships explicitly.\r\n\r\n"
32131 		"Takes no arguments.  Works in both single-player and multiplayer.\r\n" },
32132 
32133 	// MageKing17
32134 	{ OP_FIRST_OF, "First-of (Conditional operator)\r\n"
32135 		"\tSupplies arguments for the " SEXP_ARGUMENT_STRING " special data item.  Up to [number] of the supplied arguments can satisfy the expression(s) "
32136 		"in which " SEXP_ARGUMENT_STRING " is used. Essentially the same as any-of, but with a cap on the number of arguments that can be evaluated in a single call.\r\n\r\n"
32137 		"Takes 2 or more arguments...\r\n"
32138 		"\t1:\tMaximum number of arguments that can be evaluated in a call.\r\n"
32139 		"\tRest:\tAnything that could be used in place of " SEXP_ARGUMENT_STRING "." },
32140 
32141 	// Goober5000
32142 	{ OP_INVALIDATE_ARGUMENT, "Invalidate-argument (Conditional operator)\r\n"
32143 		"\tRemoves an argument from future consideration as a " SEXP_ARGUMENT_STRING " special data item.\r\n"
32144 		"Takes 1 or more arguments...\r\n"
32145 		"\tAll:\tThe argument to remove from the preceding argument list." },
32146 
32147 	// Karajorma
32148 	{ OP_VALIDATE_ARGUMENT, "Validate-argument (Conditional operator)\r\n"
32149 		"\tRestores an argument for future consideration as a " SEXP_ARGUMENT_STRING " special data item.\r\n"
32150 		"\tIf the argument hasn't been previously invalidated, it will do nothing.\r\n"
32151 		"Takes 1 or more arguments...\r\n"
32152 		"\tAll:\tThe argument to restore to the preceding argument list." },
32153 
32154 	// Karajorma
32155 	{ OP_INVALIDATE_ALL_ARGUMENTS, "Invalidate-all-arguments (Conditional operator)\r\n"
32156 		"\tRemoves all argument from future consideration as " SEXP_ARGUMENT_STRING " special data items.\r\n"
32157 		"Takes no arguments." },
32158 
32159 	// Karajorma
32160 	{ OP_VALIDATE_ALL_ARGUMENTS, "Validate-all-arguments (Conditional operator)\r\n"
32161 		"\tRestores all arguments for future consideration as " SEXP_ARGUMENT_STRING " special data items.\r\n"
32162 		"\tIf the argument hasn't been previously invalidated, it will do nothing.\r\n"
32163 		"Takes no arguments." },
32164 
32165 	// Goober5000 - added wing capability
32166 	{ OP_CHANGE_IFF, "Change IFF (Action operator)\r\n"
32167 		"\tSets the specified ship(s) or wing(s) to the specified team.\r\n"
32168 		"Takes 2 or more arguments...\r\n"
32169 		"\t1:\tTeam to change to (\"friendly\", \"hostile\" or \"unknown\").\r\n"
32170 		"\tRest:\tName of ship or wing to change team status of." },
32171 
32172 	// Wanderer
32173 	{ OP_CHANGE_IFF_COLOR, "Change IFF Color (Action operator)\r\n"
32174 		"\tSets the specified ship(s) or wing(s) apparent color.\r\n"
32175 		"Takes 6 or more arguments...\r\n"
32176 		"\t1:\tName of the team from which target is observed from.\r\n"
32177 		"\t2:\tName of the team of the observed target to receive the alternate color.\r\n"
32178 		"\t3:\tRed color (value from 0 to 255).\r\n"
32179 		"\t4:\tGreen color (value from 0 to 255).\r\n"
32180 		"\t5:\tBlue color (value from 0 to 255).\r\n"
32181 		"\tRest:\tName of ship or wing to change team status of." },
32182 
32183 	// Goober5000
32184 	{ OP_CHANGE_AI_CLASS, "Change AI Class (Action operator)\r\n"
32185 		"\tSets the specified ship or ship subsystem(s) to the specified ai class.\r\n"
32186 		"Takes 2 or more arguments...\r\n"
32187 		"\t1:\tAI Class to change to (\"None\", \"Coward\", \"Lieutenant\", etc.)\r\n"
32188 		"\t2:\tName of ship to change AI class of\r\n"
32189 		"\tRest:\tName of subsystem to change AI class of (optional)" },
32190 
32191 	{ OP_MODIFY_VARIABLE, "Modify-variable (Misc. operator)\r\n"
32192 		"\tModifies variable to specified value\r\n\r\n"
32193 		"Takes 2 arguments...\r\n"
32194 		"\t1:\tName of Variable.\r\n"
32195 		"\t2:\tValue to be set." },
32196 
32197 	{ OP_GET_VARIABLE_BY_INDEX, "get-variable-by-index (originally variable-array-get)\r\n"
32198 		"\tGets the value of the variable specified by the given index.  This is an alternate way "
32199 		"to access variables rather than by their names, and it enables cool features such as "
32200 		"arrays and pointers.\r\n\r\nPlease note that only numeric variables are supported.  Any "
32201 		"attempt to access a string variable will result in a value of SEXP_NAN_FOREVER being returned.\r\n\r\n"
32202 		"Takes 1 argument...\r\n"
32203 		"\t1:\tIndex of variable, from 0 to MAX_SEXP_VARIABLES - 1." },
32204 
32205 	{ OP_SET_VARIABLE_BY_INDEX, "set-variable-by-index (originally variable-array-set)\r\n"
32206 		"\tSets the value of the variable specified by the given index.  This is an alternate way "
32207 		"to modify variables rather than by their names, and it enables cool features such as "
32208 		"arrays and pointers.\r\n\r\nIn contrast to get-variable-by-index, note that this sexp "
32209 		"*does* allow the modification of string variables.\r\n\r\n"
32210 		"Takes 2 arguments...\r\n"
32211 		"\t1:\tIndex of variable, from 0 to MAX_SEXP_VARIABLES - 1.\r\n"
32212 		"\t2:\tValue to be set." },
32213 
32214 	{ OP_COPY_VARIABLE_FROM_INDEX, "copy-variable-from-index\r\n"
32215 		"\tRetrieves the value of the variable specified by the given index and stores it in another variable.  "
32216 		"This is very similar to get-variable-by-index, except the result is stored in a new variable rather than "
32217 		"being returned by value.  One important difference is that this sexp can be used to copy string variables as well as numeric variables.\r\n\r\n"
32218 		"Takes 2 arguments...\r\n"
32219 		"\t1:\tIndex of source variable, from 0 to MAX_SEXP_VARIABLES - 1.\r\n"
32220 		"\t2:\tDestination variable.  The type of this variable must match the type of the variable referenced by the index." },
32221 
32222 	{ OP_COPY_VARIABLE_BETWEEN_INDEXES, "copy-variable-between-indexes\r\n"
32223 		"\tRetrieves the value of the variable specified by the first index and stores it in the variable specified by the second index.  The first variable is not modified.\r\n\r\n"
32224 		"Takes 2 arguments...\r\n"
32225 		"\t1:\tIndex of source variable, from 0 to MAX_SEXP_VARIABLES - 1.\r\n"
32226 		"\t2:\tIndex of destination variable, from 0 to MAX_SEXP_VARIABLES - 1.  The types of both variables must match." },
32227 
32228 	{ OP_PROTECT_SHIP, "Protect ship (Action operator)\r\n"
32229 		"\tProtects a ship from being attacked by any enemy ship.  Any ship "
32230 		"that is protected will not come under enemy fire.\r\n\r\n"
32231 		"Takes 1 or more arguments...\r\n"
32232 		"\tAll:\tName of ship(s) to protect." },
32233 
32234 	{ OP_UNPROTECT_SHIP, "Unprotect ship (Action operator)\r\n"
32235 		"\tUnprotects a ship from being attacked by any enemy ship.  Any ship "
32236 		"that is not protected can come under enemy fire.  This function is the opposite "
32237 		"of protect-ship.\r\n\r\n"
32238 		"Takes 1 or more arguments...\r\n"
32239 		"\tAll:\tName of ship(s) to unprotect." },
32240 
32241 	{ OP_BEAM_PROTECT_SHIP, "Beam Protect ship (Action operator)\r\n"
32242 		"\tProtects a ship from being attacked with beam weapon.  Any ship "
32243 		"that is beam protected will not come under enemy beam fire.\r\n\r\n"
32244 		"Takes 1 or more arguments...\r\n"
32245 		"\tAll:\tName of ship(s) to protect." },
32246 
32247 	{ OP_BEAM_UNPROTECT_SHIP, "Beam Unprotect ship (Action operator)\r\n"
32248 		"\tUnprotects a ship from being attacked with beam weapon.  Any ship "
32249 		"that is not beam protected can come under enemy beam fire.  This function is the opposite "
32250 		"of beam-protect-ship.\r\n\r\n"
32251 		"Takes 1 or more arguments...\r\n"
32252 		"\tAll:\tName of ship(s) to unprotect." },
32253 
32254 	// Goober5000
32255 	{ OP_TURRET_PROTECT_SHIP, "Turret Protect ship (Action operator)\r\n"
32256 		"\tProtects a ship from being attacked with a turret weapon of a given type.  Any ship "
32257 		"that is turret protected will not come under enemy fire from that type of turret, though it may come under fire by other turrets.\r\n\r\n"
32258 		"Takes 2 or more arguments...\r\n"
32259 		"\t1:\tType of turret (currently supported types are \"beam\", \"flak\", \"laser\", and \"missile\")\r\n"
32260 		"\tRest:\tName of ship(s) to protect." },
32261 
32262 	// Goober5000
32263 	{ OP_TURRET_UNPROTECT_SHIP, "Turret Unprotect ship (Action operator)\r\n"
32264 		"\tUnprotects a ship from being attacked with a turret weapon of a given type.  Any ship "
32265 		"that is not turret protected can come under enemy fire from that type of turret.  This function is the opposite "
32266 		"of turret-protect-ship.\r\n\r\n"
32267 		"Takes 2 or more arguments...\r\n"
32268 		"\t1:\tType of turret (currently supported types are \"beam\", \"flak\", \"laser\", and \"missile\")\r\n"
32269 		"\tRest:\tName of ship(s) to unprotect." },
32270 
32271 	{ OP_SEND_MESSAGE, "Send message (Action operator)\r\n"
32272 		"\tSends a message to the player.  Can be send by a ship, wing, or special "
32273 		"source.  To send it from a special source, make the first character of the first "
32274 		"argument a \"#\".\r\n\r\n"
32275 		"\tHigh priority are not interrupted by anything, and will be sent by Command if the sender is destroyed.\r\n"
32276 		"\tNormal priority takes precedence over builtin messages, but will not be sent if the sender is destroyed.\r\n"
32277 		"\tLow priority are not sent if the sender's communication subsystem is destroyed, and may be interrupted.\r\n"
32278 		"Takes 3 arguments...\r\n"
32279 		"\t1:\tName of who the message is from.\r\n"
32280 		"\t2:\tPriority of message (\"Low\", \"Normal\" or \"High\").\r\n"
32281 		"\t3:\tName of message (from message editor)." },
32282 
32283 	// Karajorma
32284 	{ OP_ENABLE_BUILTIN_MESSAGES, "Enable builtin messages (Action operator)\r\n"
32285 		"\tTurns the built in messages sent by command or pilots on\r\n"
32286 		"Takes 0 or more arguments...\r\n"
32287 		"If no arguments are supplied any ships not given individual silence orders will be able\r\n"
32288 		"to send built in messages. Command will also be unsilenced\r\n"
32289 		"Using the Any Wingman option cancels radio silence for all ships in wings.\r\n"
32290 		"\tAll:\tName of ship to allow to talk." },
32291 
32292 	// Karajorma
32293 	{ OP_DISABLE_BUILTIN_MESSAGES, "Disable builtin messages (Action operator)\r\n"
32294 		"\tTurns the built in messages sent by command or pilots off\r\n"
32295 		"Takes 0 or more arguments....\r\n"
32296 		"If no arguments are supplied all built in messages are disabled.\r\n"
32297 		"Using the Any Wingman option silences for all ships in wings.\r\n"
32298 		"\tAll:\tName of ship to be silenced." },
32299 
32300 	// Karajorma
32301 	{ OP_SET_MISSION_MOOD, "set Mission Mood (Action operator)\r\n"
32302 		"\tSets the mood of the mission, this affects the choice of builtin messages sent by wingmen\r\n"
32303 		"Takes 1 argument...\r\n"
32304 		"\t1:\tMission mood (from messages.tbl) to use." },
32305 
32306 	// Karajorma
32307 	{ OP_SET_PERSONA, "Set Persona (Action operator)\r\n"
32308 		"\tSets the persona of the supplied ship to the persona supplied\r\n"
32309 		"Takes 2 or more arguments...\r\n"
32310 		"\t1:\tPersona to use."
32311 		"\tRest:\tName of the ship." },
32312 
32313 	{ OP_SELF_DESTRUCT, "Self destruct (Action operator)\r\n"
32314 		"\tCauses the specified ship(s) to self destruct.\r\n\r\n"
32315 		"Takes 1 or more arguments...\r\n"
32316 		"\tAll:\tName of ship to self destruct." },
32317 
32318 	{ OP_NEXT_MISSION, "Next Mission (Action operator)\r\n"
32319 		"\tThe next mission operator is used for campaign branching in the campaign editor.  "
32320 		"It specifies which mission should played be next in the campaign.  This operator "
32321 		"generally follows a 'when' or 'cond' statment in the campaign file.\r\n\r\n"
32322 		"Takes 1 argument...\r\n"
32323 		"\t1:\tName of mission (filename) to proceed to." },
32324 
32325 	{ OP_CLEAR_GOALS, "Clear goals (Action operator)\r\n"
32326 		"\tClears the goals for the specified ships and/or wings.\r\n\r\n"
32327 		"Takes 1 or more arguments...\r\n"
32328 		"\tAll:\tName of ship or wing." },
32329 
32330 	{ OP_ADD_GOAL, "Add goal (Action operator)\r\n"
32331 		"\tAdds a goal to a ship or wing.\r\n\r\n"
32332 		"Takes 2 arguments...\r\n"
32333 		"\t1:\tName of ship or wing to add goal to.\r\n"
32334 		"\t2:\tGoal to add." },
32335 
32336 	// Goober5000
32337 	{ OP_REMOVE_GOAL, "Remove goal (Action operator)\r\n"
32338 		"\tRemoves a goal from a ship or wing.\r\n\r\n"
32339 		"Takes 2 arguments...\r\n"
32340 		"\t1:\tName of ship or wing to remove goal from.\r\n"
32341 		"\t2:\tGoal to remove." },
32342 
32343 	{ OP_SABOTAGE_SUBSYSTEM, "Sabotage subystem (Action operator)\r\n"
32344 		"\tReduces the specified subsystem integrity by the specified percentage."
32345 		"If the percntage strength of the subsystem (after completion) is less than 0%,"
32346 		"subsystem strength is set to 0%.\r\n\r\n"
32347 		"Takes 3 arguments...\r\n"
32348 		"\t1:\tName of ship subsystem is on.\r\n"
32349 		"\t2:\tName of subsystem to sabotage.\r\n"
32350 		"\t3:\tPercentage to reduce subsystem integrity by." },
32351 
32352 	{ OP_REPAIR_SUBSYSTEM, "Repair Subystem (Action operator)\r\n"
32353 		"\tIncreases the specified subsystem integrity by the specified percentage."
32354 		"If the percentage strength of the subsystem (after completion) is greater than 100%,"
32355 		"subsystem strength is set to 100%.\r\n\r\n"
32356 		"Takes 3 to 5 arguments...\r\n"
32357 		"\t1:\tName of ship subsystem is on.\r\n"
32358 		"\t2:\tName of subsystem to repair.\r\n"
32359 		"\t3:\tPercentage to increase subsystem integrity by.\r\n"
32360 		"\t4:\tRepair submodel if it exists.  Optional argument that defaults to true.\r\n"
32361 		"\t5:\tIf we are repairing submodels and an ancestor submodel was totally destroyed, repair that too.  Optional argument that defaults to true.\r\n" },
32362 
32363 	{ OP_SET_SUBSYSTEM_STRNGTH, "Set Subsystem Strength (Action operator)\r\n"
32364 		"\tSets the specified subsystem to the the specified percentage."
32365 		"If the percentage specified is < 0, strength is set to 0.  If the percentage is "
32366 		"> 100 % the subsystem strength is set to 100%.\r\n\r\n"
32367 		"Takes 3 to 5 arguments...\r\n"
32368 		"\t1:\tName of ship subsystem is on.\r\n"
32369 		"\t2:\tName of subsystem to set strength.\r\n"
32370 		"\t3:\tPercentage to set subsystem integrity to.\r\n"
32371 		"\t4:\tRepair submodel if it exists.  Optional argument that defaults to true.\r\n"
32372 		"\t5:\tIf we are repairing submodels and an ancestor submodel was totally destroyed, repair that too.  Optional argument that defaults to true.\r\n" },
32373 
32374 	{ OP_DESTROY_SUBSYS_INSTANTLY, "destroy-subsys-instantly\r\n"
32375 		"\tDestroys the specified subsystems without effects."
32376 		"Takes 2 or more arguments...\r\n"
32377 		"\t1:\tName of ship subsystem is on.\r\n"
32378 		"\tRest:\tName of subsystem to destroy.\r\n"},
32379 
32380 	{ OP_INVALIDATE_GOAL, "Invalidate goal (Action operator)\r\n"
32381 		"\tMakes a mission goal invalid, which causes it to not show up on mission goals "
32382 		"screen, or be evaluated.\r\n"
32383 		"Takes 1 or more arguments...\r\n"
32384 		"\tAll:\tName of mission goal to invalidate." },
32385 
32386 	{ OP_VALIDATE_GOAL, "Validate goal (Action operator)\r\n"
32387 		"\tMakes a mission goal valid again, so it shows up on mission goals screen.\r\n"
32388 		"Takes 1 or more arguments...\r\n"
32389 		"\tAll:\tName of mission goal to validate." },
32390 
32391 	{ OP_SEND_RANDOM_MESSAGE, "Send random message (Action operator)\r\n"
32392 		"\tSends a random message to the player from those supplied.  Can be send by a "
32393 		"ship, wing, or special source.  To send it from a special source, make the first "
32394 		"character of the first argument a \"#\".\r\n\r\n"
32395 		"Takes 3 or more arguments...\r\n"
32396 		"\t1:\tName of who the message is from.\r\n"
32397 		"\t2:\tPriority of message (\"Low\", \"Normal\" or \"High\")."
32398 		"\tRest:\tName of message (from message editor)." },
32399 
32400 	{ OP_TRANSFER_CARGO, "Transfer Cargo (Action operator)\r\n"
32401 		"\tTransfers the cargo from one ship to another ship.\r\n\r\n"
32402 		"Takes 2 arguments...\r\n"
32403 		"\t1:\tName of ship that cargo is being transferred from.\r\n"
32404 		"\t2:\tName of ship that cargo is being transferred to." },
32405 
32406 	{ OP_EXCHANGE_CARGO, "Exchange Cargo (Action operator)\r\n"
32407 		"\tExchanges the cargos of two ships.  If one of the two ships contains no cargo, "
32408 		"the cargo is transferred instead.\r\n"
32409 		"Takes 2 arguments...\r\n"
32410 		"\t1:\tName of one of the ships.\r\n"
32411 		"\t2:\tName of the other ship." },
32412 
32413 	// Goober5000
32414 	{ OP_SET_CARGO, "Set Cargo (Action operator)\r\n"
32415 		"\tSets the cargo on a ship or ship subsystem.  The cargo-no-deplete flag status is carried through to the new cargo.\r\n"
32416 		"Takes 2 or 3 arguments...\r\n"
32417 		"\t1:\tName of the cargo\r\n"
32418 		"\t2:\tName of the ship\r\n"
32419 		"\t3:\tName of the ship subsystem (optional)" },
32420 
32421 	// Goober5000
32422 	{ OP_IS_CARGO, "Is Cargo (Status operator)\r\n"
32423 		"\tChecks whether the specified ship or ship subsystem contains a particular cargo.\r\n"
32424 		"Takes 2 or 3 arguments...\r\n"
32425 		"\t1:\tName of the cargo\r\n"
32426 		"\t2:\tName of the ship\r\n"
32427 		"\t3:\tName of the ship subsystem (optional)" },
32428 
32429 	// Goober5000
32430 	{ OP_CHANGE_SOUNDTRACK, "change-soundtrack\r\n"
32431 		"\tChanges the mission music.  Takes 1 argument...\r\n"
32432 		"\t1: Name of the music selection (taken from music.tbl)" },
32433 
32434 	// Goober5000
32435 	{ OP_PLAY_SOUND_FROM_TABLE, "play-sound-from-table\r\n"
32436 		"\tPlays a sound listed in the Game Sounds section of sounds.tbl.  Note that if the sound is a non-3D sound (if the min and max radius are 0, or unspecified), the sound will just play without being fixed at a particular position in space.  "
32437 		"In this case, the origin coordinates will be ignored.  (A better design would have put the sound index first and made the origin arguments optional, but the difference between 2D and 3D sounds was not understood at the time.  C'est la vie.)\r\n\r\n"
32438 		"Takes 4 arguments...\r\n"
32439 		"\t1: Origin X\r\n"
32440 		"\t2: Origin Y\r\n"
32441 		"\t3: Origin Z\r\n"
32442 		"\t4: Sound (index into sounds.tbl or name of the sound entry)" },
32443 
32444 	// Goober5000
32445 	{ OP_PLAY_SOUND_FROM_FILE, "play-sound-from-file\r\n"
32446 		"\tPlays a sound, such as a music soundtrack, from a file.  Multiple sounds can be played with this sexp by assigning each sound to a music handle stored in a variable.\r\n"
32447 		"Takes 1 to 4 arguments...\r\n"
32448 		"\t1: Sound (file name)\r\n"
32449 		"\t2: Enter a non-zero number to loop. default is off (optional).\r\n"
32450 		"\t3: Enter a non-zero number to use environment effects. default is off (optional).\r\n"
32451 		"\t4: Numeric variable in which to store the music handle (optional).  If no variable is specified, the 'default' handle is used.  "
32452 		"Only one 'default' track can be played at a time, but multiple variable-managed tracks can be played.\r\n"
32453 	},
32454 
32455 	// Goober5000
32456 	{ OP_CLOSE_SOUND_FROM_FILE, "close-sound-from-file\r\n"
32457 		"\tCloses the currently playing sound started by play-sound-from-file, if there is any.  Takes 0 to 2 arguments...\r\n"
32458 		"\t1: Fade (optional; default is true)\r\n"
32459 		"\t2: Numeric variable containing a music handle (optional).  If no variable is specified, the 'default' handle is used."
32460 	},
32461 
32462 	// Goober5000
32463 	{ OP_PAUSE_SOUND_FROM_FILE, "pause-sound-from-file\r\n"
32464 		"\tPauses or unpauses the currently playing sound started by play-sound-from-file, if there is any.  Takes 1 or 2 arguments...\r\n"
32465 		"\t1: Boolean - True to pause, False to unpause\r\n"
32466 		"\t2: Numeric variable containing a music handle (optional).  If no variable is specified, the 'default' handle is used."
32467 	},
32468 
32469 	// Taylor
32470 	{ OP_SET_SOUND_ENVIRONMENT, "set-sound-environment\r\n"
32471 		"Sets the EAX environment for all sound effects.  Optionally sets one or more parameters specific to the environment.  Takes 1 or more arguments...\r\n"
32472 		"\t1:\tSound environment name (a value of \"" SEXP_NONE_STRING "\" will disable the effects)\r\n"
32473 		"\t2:\tEnvironment option (optional)\r\n"
32474 		"\t3:\tEnvironment value x 1000, e.g. 10 is 0.01 (optional)\r\n"
32475 		"Use Add-Data to specify additional environment options in repeating option-value pairs, just like Send-Message-List can have additional messages in source-priority-message-delay groups.\r\n\r\n"
32476 		"IMPORTANT: each additional option in the list MUST HAVE two entries; any option without the two proper fields will be ignored, as will any successive options." },
32477 
32478 	// Taylor
32479 	{ OP_UPDATE_SOUND_ENVIRONMENT, "update-sound-environment\r\n"
32480 		"Updates the current EAX environment with new values.  Takes 2 or more arguments...\r\n"
32481 		"\t1:\tEnvironment option\r\n"
32482 		"\t2:\tEnvironment value x 1000, e.g. 10 is 0.01\r\n"
32483 		"Use Add-Data to specify additional environment options in repeating option-value pairs, just like Send-Message-List can have additional messages in source-priority-message-delay groups.\r\n\r\n"
32484 		"IMPORTANT: Each additional option in the list MUST HAVE two entries; any option without the two proper fields will be ignored, as will any successive options." },
32485 
32486 	//The E
32487 	{ OP_ADJUST_AUDIO_VOLUME, "adjust-audio-volume\r\n"
32488 		"Adjusts the relative volume of one sound type. Takes 1 to 3 arguments....\r\n"
32489 		"\t1:\tSound Type to adjust, either Music, Voice or Effects. Will act as a reset for the given category, if no other argument present\r\n"
32490 		"\t2:\tPercentage of the users' settings to adjust to (optional), 0 will be silence, 100 means the maximum volume as set by the user\r\n"
32491 		"\t3:\tFade time (optional), time in milliseconds to adjust the volume"},
32492 
32493 	// Goober5000
32494 	{ OP_SET_EXPLOSION_OPTION, "set-explosion-option\r\n"
32495 		"Sets an explosion option on a particular ship.  Takes 3 or more arguments...\r\n"
32496 		"\t1:\tShip name\r\n"
32497 		"\t2:\tExplosion option\r\n"
32498 		"\t3:\tExplosion value (for shockwave speed, 0 will produce no shockwave; for death roll time, 0 will use the default time)\r\n"
32499 		"Use Add-Data to specify additional explosion options in repeating option-value pairs, just like Send-Message-List can have additional messages in source-priority-message-delay groups.\r\n\r\n"
32500 		"IMPORTANT: Each additional option in the list MUST HAVE two entries; any option without the two proper fields will be ignored, as will any successive options." },
32501 
32502 	// Goober5000
32503 	{ OP_EXPLOSION_EFFECT, "explosion-effect\r\n"
32504 		"\tCauses an explosion at a given origin, with the given parameters.  "
32505 		"Takes 11 to 14 arguments...\r\n"
32506 		"\t1:  Origin X\r\n"
32507 		"\t2:  Origin Y\r\n"
32508 		"\t3:  Origin Z\r\n"
32509 		"\t4:  Damage\r\n"
32510 		"\t5:  Blast force\r\n"
32511 		"\t6:  Size of explosion (if 0, explosion will not be visible)\r\n"
32512 		"\t7:  Inner radius to apply damage (if 0, explosion will not be visible)\r\n"
32513 		"\t8:  Outer radius to apply damage (if 0, explosion will not be visible)\r\n"
32514 		"\t9:  Shockwave speed (if 0, there will be no shockwave)\r\n"
32515 		"\t10: Fireball Type (unique ID of a fireball entry, OR a number: 0 = medium explosion [default, 1st in table], 1 = large explosion 1 [4th in table], 2 = large explosion 2 [5th in table], 3 or greater the respective entry in fireball.tbl)\r\n"
32516 		"\t11: Sound (index into sounds.tbl or name of the sound entry)\r\n"
32517 		"\t12: EMP intensity (optional)\r\n"
32518 		"\t13: EMP duration in seconds (optional)\r\n"
32519 		"\t14: Whether to use the full EMP time for capship turrets (optional, defaults to false)" },
32520 
32521 	// Goober5000
32522 	{ OP_WARP_EFFECT, "warp-effect\r\n"
32523 		"\tCauses a subspace warp effect at a given origin, facing toward a given location, with the given parameters.\r\n"
32524 		"Takes 12 to 14 arguments...\r\n"
32525 		"\t1:  Origin X\r\n"
32526 		"\t2:  Origin Y\r\n"
32527 		"\t3:  Origin Z\r\n"
32528 		"\t4:  Location X\r\n"
32529 		"\t5:  Location Y\r\n"
32530 		"\t6:  Location Z\r\n"
32531 		"\t7:  Radius\r\n"
32532 		"\t8:  Duration in seconds (values smaller than 4 are ignored)\r\n"
32533 		"\t9:  Warp opening sound (index into sounds.tbl or name of the sound entry)\r\n"
32534 		"\t10: Warp closing sound (index into sounds.tbl or name of the sound entry)\r\n"
32535 		"\t11: Fireball Type (unique ID of a fireball entry, OR a number: 0 for standard blue [default], 1 for Knossos green, 2 or greater the respective entry in fireball.tbl)\r\n"
32536 		"\t12: Shape (0 for 2-D [default], 1 for 3-D)\r\n"
32537 		"\t13: Duration of the opening effect, in milliseconds (and also the closing effect if the next argument is not specified)\r\n"
32538 		"\t14: Duration of the closing effect, in milliseconds\r\n"	},
32539 
32540 	// Goober5000
32541 	{ OP_SET_OBJECT_FACING, "set-object-facing\r\n"
32542 		"\tSets an object's orientation to face the specified coordinates.  "
32543 		"Takes 4 arguments...\r\n"
32544 		"\t1: The name of a ship or wing.\r\n"
32545 		"\t2: The X coordinate to face.\r\n"
32546 		"\t3: The Y coordinate to face.\r\n"
32547 		"\t4: The Z coordinate to face.\r\n"
32548 		"\t5: Turn time in milliseconds (optional)\r\n"
32549 		"\t6: Bank (optional). Enter a non-zero value to enable banking.\r\n"
32550 		"Note that if Turn time is specified, this needs to be called once per frame, as the rotation is reset every frame.\r\n"
32551 		"Note that Turn time is the time for the object to rotate a full revolution, *not* how long it will take to do this turn." },
32552 
32553 	// Goober5000
32554 	{ OP_SET_OBJECT_FACING_OBJECT, "set-object-facing-object\r\n"
32555 		"\tSets an object's orientation to face the specified object.  "
32556 		"Takes 2 arguments...\r\n"
32557 		"\t1: The name of a ship or wing.\r\n"
32558 		"\t2: The object to face.\r\n"
32559 		"\t3: Turn time in milliseconds (optional)\r\n"
32560 		"\t4: Bank (optional). Enter a non-zero value to enable banking.\r\n"
32561 		"Note that if Turn time is specified, this needs to be called once per frame, as the rotation is reset every frame.\r\n"
32562 		"Note that Turn time is the time for the object to rotate a full revolution, *not* how long it will take to do this turn." },
32563 
32564 	// Wanderer
32565 	{ OP_SHIP_MANEUVER, "ship-maneuver\r\n"
32566 		"\tCombines the effects of the ship-rot-maneuver and ship-lat-maneuver sexps.  Takes 10 or 11 arguments:\r\n"
32567 		"\t1: The name of a ship or wing\r\n"
32568 		"\t2: Duration of the maneuver, in milliseconds.  Specifying 0 or 1 indicates infinite duration.\r\n"
32569 		"\t3: Heading movement velocity, as a percentage (-100 to 100) of the tabled maximum heading velocity, or 0 to not modify the ship's current value\r\n"
32570 		"\t4: Pitch movement velocity, as a percentage (-100 to 100) of the tabled maximum pitch velocity, or 0 to not modify the ship's current value\r\n"
32571 		"\t5: Bank movement velocity, as a percentage (-100 to 100) of the tabled maximum bank velocity, or 0 to not modify the ship's current value\r\n"
32572 		"\t6: Whether to apply all of the rotational velocity values even if any of them are 0\r\n"
32573 		"\t7: Vertical movement velocity, as a percentage (-100 to 100) of the tabled maximum vertical velocity, or 0 to not modify the ship's current value\r\n"
32574 		"\t8: Sideways movement velocity, as a percentage (-100 to 100) of the tabled maximum sideways velocity, or 0 to not modify the ship's current value\r\n"
32575 		"\t9: Forward movement velocity, as a percentage (-100 to 100) of the tabled maximum forward velocity, or 0 to not modify the ship's current value\r\n"
32576 		"\t10: Whether to apply all of the lateral velocity values even if any of them are 0\r\n"
32577 		"\t11: Maneuver flags (optional): a bitfield with any of the following options:\r\n"
32578 		"\t\t1 or 2^0: don't bank when changing heading\r\n"
32579 		"\t\t2 or 2^1: allow maneuvers exceeding tabled maximums (outside the -100 to 100 range)\r\n"
32580 		"\t\t4 or 2^2: instantaneously jump to the goal velocity\r\n"
32581 		"\t\t8 or 2^3: keeps old, but still active, maneuver values that were not overwritten\r\n"
32582 	},
32583 
32584 	// Wanderer
32585 	{ OP_SHIP_ROT_MANEUVER, "ship-rot-maneuver\r\n"
32586 		"\tCauses a ship to move in a rotational direction.  For the purposes of this sexp, this means the ship rotates along its own heading, pitch, or bank axis (or a combination of axes) without regard to normal ship rotation rules.  "
32587 		"You may find it necessary to disable the ship AI (e.g. by issuing a play-dead order) before running this sexp.\r\n\r\n"
32588 		"Takes 6 or 7 arguments:\r\n"
32589 		"\t1: The name of a ship or wing\r\n"
32590 		"\t2: Duration of the maneuver, in milliseconds.  Specifying 0 or 1 indicates infinite duration.\r\n"
32591 		"\t3: Heading movement velocity, as a percentage (-100 to 100) of the tabled maximum heading velocity, or 0 to not modify the ship's current value\r\n"
32592 		"\t4: Pitch movement velocity, as a percentage (-100 to 100) of the tabled maximum pitch velocity, or 0 to not modify the ship's current value\r\n"
32593 		"\t5: Bank movement velocity, as a percentage (-100 to 100) of the tabled maximum bank velocity, or 0 to not modify the ship's current value\r\n"
32594 		"\t6: Whether to apply all of the above velocity values even if any of them are 0\r\n"
32595 		"\t7: Maneuver flags (optional): a bitfield with any of the following options:\r\n"
32596 		"\t\t1 or 2^0: don't bank when changing heading\r\n"
32597 		"\t\t2 or 2^1: allow maneuvers exceeding tabled maximums (outside the -100 to 100 range)\r\n"
32598 		"\t\t4 or 2^2: instantaneously jump to the goal velocity\r\n"
32599 		"\t\t8 or 2^3: keeps old, but still active, maneuver values that were not overwritten\r\n"
32600 	},
32601 
32602 	// Wanderer
32603 	{ OP_SHIP_LAT_MANEUVER, "ship-lat-maneuver\r\n"
32604 		"\tCauses a ship to move in a lateral direction.  For the purposes of this sexp, this means the ship translates along its own X, Y, or Z axis (or a combination of axes) without regard to normal ship movement rules.  "
32605 		"You may find it necessary to disable the ship AI (e.g. by issuing a play-dead order) before running this sexp.\r\n\r\n"
32606 		"Takes 6 or 7 arguments:\r\n"
32607 		"\t1: The name of a ship or wing\r\n"
32608 		"\t2: Duration of the maneuver, in milliseconds.  Specifying 0 or 1 indicates infinite duration.\r\n"
32609 		"\t3: Vertical movement velocity, as a percentage (-100 to 100) of the tabled maximum vertical velocity, or 0 to not modify the ship's current value\r\n"
32610 		"\t4: Sideways movement velocity, as a percentage (-100 to 100) of the tabled maximum sideways velocity, or 0 to not modify the ship's current value\r\n"
32611 		"\t5: Forward movement velocity, as a percentage (-100 to 100) of the tabled maximum forward velocity, or 0 to not modify the ship's current value\r\n"
32612 		"\t6: Whether to apply all of the above velocity values even if any of them are 0\r\n"
32613 		"\t7: Maneuver flags (optional): a bitfield with any of the following options:\r\n"
32614 		"\t\t1 or 2^0: don't bank when changing heading (which won't have any affect for lat-maneuver)\r\n"
32615 		"\t\t2 or 2^1: allow maneuvers exceeding tabled maximums (outside the -100 to 100 range)\r\n"
32616 		"\t\t4 or 2^2: instantaneously jump to the goal velocity\r\n"
32617 		"\t\t8 or 2^3: keeps old, but still active, maneuver values that were not overwritten\r\n"
32618 	},
32619 
32620 	// Goober5000
32621 	{ OP_SHIP_TAG, "ship-tag\r\n"
32622 		"\tTags a ship.  Takes 3 or 8 arguments...\r\n"
32623 		"\t1: The name of a ship.\r\n"
32624 		"\t2: The tag level (currently 1, 2, or 3).\r\n"
32625 		"\t3: The tag time (in seconds).\r\n"
32626 		"\t4: A SSM missile (optional - used only for TAG-C).\r\n"
32627 		"\t5: The X origin of the SSM missile (optional - used only for TAG-C).\r\n"
32628 		"\t6: The Y origin of the SSM missile (optional - used only for TAG-C).\r\n"
32629 		"\t7: The Z origin of the SSM missile (optional - used only for TAG-C).\r\n"
32630 		"\t8: The team the SSM missile belongs to (optional - used only for TAG-C).\r\n" },
32631 
32632 /*	made obsolete by Goober5000 - it only works in debug builds anyway
32633 	{ OP_INT3, "Error (Debug directive)\r\n"
32634 		"Causes the game to halt with an error." },
32635 */
32636 
32637 	{ OP_AI_CHASE, "Ai-chase (Ship goal)\r\n"
32638 		"\tCauses the specified ship to chase and attack the specified target.\r\n\r\n"
32639 		"Takes 2 or 3 arguments...\r\n"
32640 		"\t1:\tName of ship to chase.\r\n"
32641 		"\t2:\tGoal priority (number between 0 and 200. Player orders have a priority of 90-100).\r\n"
32642 		"\t3 (optional):\tWhether to attack the target even if it is on the same team; defaults to false."
32643 	},
32644 
32645 	{ OP_AI_CHASE_WING, "Ai-chase wing (Ship goal)\r\n"
32646 		"\tCauses the specified ship to chase and attack the specified target.\r\n\r\n"
32647 		"Takes 2 or 3 arguments...\r\n"
32648 		"\t1:\tName of wing to chase.\r\n"
32649 		"\t2:\tGoal priority (number between 0 and 200. Player orders have a priority of 90-100).\r\n"
32650 		"\t3 (optional):\tWhether to attack the target even if it is on the same team; defaults to false."
32651 	},
32652 
32653 	{ OP_AI_CHASE_SHIP_CLASS, "Ai-chase ship class (Ship goal)\r\n"
32654 		"\tCauses the specified ship to chase and attack the specified target ship class.\r\n\r\n"
32655 		"Takes 2 or 3 arguments...\r\n"
32656 		"\t1:\tName of ship class to chase.\r\n"
32657 		"\t2:\tGoal priority (number between 0 and 200. Player orders have a priority of 90-100).\r\n"
32658 		"\t3 (optional):\tWhether to attack the target even if it is on the same team; defaults to false."
32659 	},
32660 
32661 	{ OP_AI_CHASE_ANY, "Ai-chase-any (Ship goal)\r\n"
32662 		"\tCauses the specified ship to chase and attack any ship on the opposite team.\r\n\r\n"
32663 		"Takes 1 argument...\r\n"
32664 		"\t1:\tGoal priority (number between 0 and 200. Player orders have a priority of 90-100)."
32665 	},
32666 
32667 	{ OP_AI_DOCK, "Ai-dock (Ship goal)\r\n"
32668 		"\tCauses one ship to dock with another ship.\r\n\r\n"
32669 		"Takes 4 arguments...\r\n"
32670 		"\t1:\tName of dockee ship (The ship that \"docker\" will dock with).\r\n"
32671 		"\t2:\tDocker's docking point - Which dock point docker uses to dock.\r\n"
32672 		"\t3:\tDockee's docking point - Which dock point on dockee docker will move to.\r\n"
32673 		"\t4:\tGoal priority (number between 0 and 200. Player orders have a priority of 90-100)." },
32674 
32675 	{ OP_AI_UNDOCK, "Ai-undock (Ship goal)\r\n"
32676 		"\tCauses the specified ship to undock from who it is currently docked with.\r\n\r\n"
32677 		"Takes 1 or 2 arguments...\r\n"
32678 		"\t1:\tGoal priority (number between 0 and 89).\r\n"
32679 		"\t2 (optional):\tShip to undock from.  If none is specified, the code will pick the first docked ship." },
32680 
32681 	{ OP_AI_WARP_OUT, "Ai-warp-out (Ship/Wing Goal)\r\n"
32682 		"\tCauses the specified ship/wing to immediately warp out of the mission, from its current location.  "
32683 		"It will warp even if its departure cue is specified as a hangar bay.\r\n\r\n"
32684 		"Takes 2 arguments...\r\n"
32685 		"\t1:\tName of waypoint path to follow to warp out (not used).\r\n"
32686 		"\t2:\tGoal priority (number between 0 and 200. Player orders have a priority of 90-100)." },
32687 
32688 	{ OP_AI_WAYPOINTS, "Ai-waypoints (Ship goal)\r\n"
32689 		"\tCauses the specified ship to fly a waypoint path continuously.\r\n\r\n"
32690 		"Takes 2 arguments...\r\n"
32691 		"\t1:\tName of waypoint path to fly.\r\n"
32692 		"\t2:\tGoal priority (number between 0 and 200. Player orders have a priority of 90-100)." },
32693 
32694 	{ OP_AI_WAYPOINTS_ONCE, "Ai-waypoints once (Ship goal)\r\n"
32695 		"\tCauses the specified ship to fly a waypoint path.\r\n\r\n"
32696 		"Takes 2 arguments...\r\n"
32697 		"\t1:\tName of waypoint path to fly.\r\n"
32698 		"\t2:\tGoal priority (number between 0 and 200. Player orders have a priority of 90-100)." },
32699 
32700 	{ OP_AI_DESTROY_SUBSYS, "Ai-destroy subsys (Ship goal)\r\n"
32701 		"\tCauses the specified ship to attack and try and destroy the specified subsystem "
32702 		"on the specified ship.\r\n\r\n"
32703 		"Takes 3 or 4 arguments...\r\n"
32704 		"\t1:\tName of ship subsystem is on.\r\n"
32705 		"\t2:\tName of subsystem on the ship to attack and destroy.\r\n"
32706 		"\t3:\tGoal priority (number between 0 and 200. Player orders have a priority of 90-100).\r\n"
32707 		"\t4 (optional):\tWhether to attack the target even if it is on the same team; defaults to false."
32708 	},
32709 
32710 	{ OP_AI_DISABLE_SHIP, "Ai-disable-ship (Ship/wing goal)\r\n"
32711 		"\tThis AI goal causes a ship/wing to destroy all of the engine subsystems on "
32712 		"the specified ship.  This goal is different than ai-destroy-subsystem since a ship "
32713 		"may have multiple engine subsystems requiring the use of > 1 ai-destroy-subsystem "
32714 		"goals.\r\n"
32715 		"Please note that this goal may call \"protect-ship\" on the target "
32716 		"to prevent overzealous AI ships from destroying it in the process of disabling it.  "
32717 		"If the ship must be destroyed later on, be sure to call an \"unprotect-ship\" sexp.\r\n\r\n"
32718 		"Takes 2 or 3 arguments...\r\n"
32719 		"\t1:\tName of ship whose engine subsystems should be destroyed\r\n"
32720 		"\t2:\tGoal priority (number between 0 and 200. Player orders have a priority of 90-100).\r\n"
32721 		"\t3 (optional):\tWhether to attack the target even if it is on the same team; defaults to false."
32722 	},
32723 
32724 	{ OP_AI_DISARM_SHIP, "Ai-disarm-ship (Ship/wing goal)\r\n"
32725 		"\tThis AI goal causes a ship/wing to destroy all of the turret subsystems on "
32726 		"the specified ship.  This goal is different than ai-destroy-subsystem since a ship "
32727 		"may have multiple turret subsystems requiring the use of > 1 ai-destroy-subsystem "
32728 		"goals.\r\n"
32729 		"Please note that this goal may call \"protect-ship\" on the target "
32730 		"to prevent overzealous AI ships from destroying it in the process of disarming it.  "
32731 		"If the ship must be destroyed later on, be sure to call an \"unprotect-ship\" sexp.\r\n\r\n"
32732 		"Takes 2 arguments...\r\n"
32733 		"\t1:\tName of ship whose turret subsystems should be destroyed\r\n"
32734 		"\t2:\tGoal priority (number between 0 and 200. Player orders have a priority of 90-100).\r\n"
32735 		"\t3 (optional):\tWhether to attack the target even if it is on the same team; defaults to false."
32736 	},
32737 
32738 	{ OP_AI_GUARD, "Ai-guard (Ship goal)\r\n"
32739 		"\tCauses the specified ship to guard a ship from other ships not on the same team.\r\n\r\n"
32740 		"Takes 2 arguments...\r\n"
32741 		"\t1:\tName of ship to guard.\r\n"
32742 		"\t2:\tGoal priority (number between 0 and 200. Player orders have a priority of 90-100)." },
32743 
32744 	{ OP_AI_GUARD_WING, "Ai-guard wing (Ship goal)\r\n"
32745 		"\tCauses the specified ship to guard a wing of ships from other ships not on the "
32746 		"same team.\r\n\r\n"
32747 		"Takes 2 arguments...\r\n"
32748 		"\t1:\tName of wing to guard.\r\n"
32749 		"\t2:\tGoal priority (number between 0 and 200. Player orders have a priority of 90-100)." },
32750 
32751 	{ OP_NOP, "Do-nothing (Action operator)\r\n"
32752 		"\tDoes nothing.  This is used as the default for any required action arguments "
32753 		"of an operator." },
32754 
32755 	{ OP_KEY_PRESSED, "Key-pressed (Boolean training operator)\r\n"
32756 		"\tBecomes true when the specified default key has been pressed.  Default key "
32757 		"refers to the what the key normally is when not remapped.  FreeSpace will "
32758 		"automatically account for any keys that have been remapped.  If the optional "
32759 		"delay is specified, becomes true that many seconds after the key has been pressed.\r\n\r\n"
32760 		"Returns a boolean value.  Takes 1 or 2 arguments...\r\n"
32761 		"\t1:\tDefault key to check for.\r\n"
32762 		"\t2:\tDelay before operator registers as true (optional).\r\n" },
32763 
32764 	{ OP_KEY_RESET, "Key-reset (Training operator)\r\n"
32765 		"\tMarks the specified default key as having not been pressed, so key-pressed will be false "
32766 		"until the player presses it again.  See key-pressed help for more information about "
32767 		"what a default key is.\r\n\r\n"
32768 		"\tNote that this sexp will not work properly in repeating events.  Use key-reset-multiple "
32769 		"if this is to be called multiple times in one event.\r\n\r\n"
32770 		"Returns a boolean value.  Takes 1 or more arguments...\r\n"
32771 		"\tAll:\tDefault key to reset." },
32772 
32773 	// Goober5000
32774 	{ OP_KEY_RESET_MULTIPLE, "Key-reset-multiple (Training operator)\r\n"
32775 		"\tMarks the specified default key as having not been pressed, so key-pressed will be false "
32776 		"until the player presses it again.  See key-pressed help for more information about "
32777 		"what a default key is.\r\n\r\n"
32778 		"\tThis sexp, unlike key-reset, will work properly if called multiple times in one event.\r\n\r\n"
32779 		"Returns a boolean value.  Takes 1 or more arguments...\r\n"
32780 		"\tAll:\tDefault key to reset." },
32781 
32782 	{ OP_TARGETED, "Targeted (Boolean training operator)\r\n"
32783 		"\tIs true as long as the player has the specified ship (or ship's subsystem) targeted, "
32784 		"or has been targeted for the specified amount of time.\r\n\r\n"
32785 		"Returns a boolean value.  Takes 1 to 3 arguments (first required, rest optional):\r\n"
32786 		"\t1:\tName of ship to check if targeted by player.\r\n"
32787 		"\t2:\tLength of time target should have been kept for (optional).\r\n"
32788 		"\t3:\tName of subsystem on ship to check if targeted (optional)." },
32789 
32790 	{ OP_NODE_TARGETED, "Node-Targeted (Boolean training operator)\r\n"
32791 		"\tIs true as long as the player has the specified jump node targeted, "
32792 		"or has been targeted for the specified amount of time.\r\n\r\n"
32793 		"Returns a boolean value.  Takes 1 to 2 arguments (first required, rest optional):\r\n"
32794 		"\t1:\tName of Jump Node to check if targeted by player.\r\n"
32795 		"\t2:\tLength of time target should have been kept for (optional)."},
32796 
32797 	// Sesquipedalian
32798 	{ OP_MISSILE_LOCKED, "Missile-locked (Boolean training operator)\r\n"
32799 		"\tIs true as long as the player has had a missile lock for the specified amount of time. "
32800 		"Optional arguments require lock to be maintained on a specified ship (or ship's subsystem).\r\n\r\n"
32801 		"Returns a boolean value.  Takes 1 to 3 arguments (first required, rest optional):\r\n"
32802 		"\t1:\tLength of time missile lock should have been kept for.\r\n"
32803 		"\t2:\tName of ship to check if locked onto by player (optional).\r\n"
32804 		"\t3:\tName of subsystem on ship to check if locked onto (optional)." },
32805 
32806 	{ OP_SPEED, "Speed (Boolean training operator)\r\n"
32807 		"\tBecomes true when the player has been within the specified speed range set by "
32808 		"set-training-context-speed for the specified amount of time.\r\n\r\n"
32809 		"Returns a boolean value.  Takes 1 argument...\r\n"
32810 		"\t1:\tTime in seconds." },
32811 
32812 	{ OP_GET_THROTTLE_SPEED, "Get-Throttle-Speed (Training operator)\r\n"
32813 		"\tReturns the current throttle speed that the ship has been set to. Reverse speeds are returned as a negative value. "
32814 		"Takes 1 argument...\r\n"
32815 		"\t1:\tName of the player ship to check the throttle value for." },
32816 
32817 	{ OP_FACING, "Facing (Boolean training operator)\r\n"
32818 		"\tIs true as long as the specified ship is within the player's specified "
32819 		"forward cone.  A forward cone is defined as any point that the angle between the "
32820 		"vector of the point and the player, and the forward facing vector is within the "
32821 		"given angle.\r\n\r\n"
32822 		"Returns a boolean value.  Takes 2 argument...\r\n"
32823 		"\t1:\tShip to check is within forward cone.\r\n"
32824 		"\t2:\tAngle in degrees of the forward cone." },
32825 
32826 	{ OP_IS_FACING, "Is Facing (Boolean training operator)\r\n"
32827 		"\tIs true as long as the second object is within the first ship's specified "
32828 		"forward cone.  A forward cone is defined as any point that the angle between the "
32829 		"vector of the ship and point, and the forward facing vector is within the "
32830 		"given angle. If the distance between the two is greater than the fourth"
32831 		"parameter, this will return false.\r\n\r\n"
32832 		"Returns a boolean value.  Takes 3 or 4 argument...\r\n"
32833 		"\t1:\tShip to check from.\r\n"
32834 		"\t2:\tObject to check is within forward cone.\r\n"
32835 		"\t3:\tAngle in degrees of the forward cone.\r\n"
32836 		"\t4:\tRange in meters (optional)."},
32837 
32838 	{ OP_FACING2, "Facing Waypoint(Boolean training operator)\r\n"
32839 		"\tIs true as long as the specified first waypoint is within the player's specified "
32840 		"forward cone.  A forward cone is defined as any point that the angle between the "
32841 		"vector of the point and the player, and the forward facing vector is within the "
32842 		"given angle.\r\n\r\n"
32843 		"Returns a boolean value.  Takes 2 argument...\r\n"
32844 		"\t1:\tName of waypoint path whose first point is within forward cone.\r\n"
32845 		"\t2:\tAngle in degrees of the forward cone." },
32846 
32847 	// fixed by Goober5000 and then deprecated by Karajorma
32848 	{ OP_ORDER, "Order (Boolean training operator)\r\n"
32849 		"\tDeprecated - Use Query-Orders in any new mission.\r\n\r\n"
32850 		"\tBecomes true when the player had given the specified ship or wing the specified order.\r\n\r\n"
32851 		"Returns a boolean value.  Takes 2 or 3 arguments...\r\n"
32852 		"\t1:\tName of ship or wing to check if given order to.\r\n"
32853 		"\t2:\tName of order to check if player has given.\r\n"
32854 		"\t3:\tName of the target of the order (optional)." },
32855 
32856 	{ OP_QUERY_ORDERS, "Query-Orders (Boolean training operator)\r\n"
32857 		"\tBecomes true when the player had given the specified ship or wing the specified order.\r\n\r\n"
32858 		"Returns a boolean value.  Takes 2 or more arguments...\r\n"
32859 		"\t1:\tName of ship or wing to check if given order to.\r\n"
32860 		"\t2:\tName of order to check if player has given.\r\n"
32861 		"\t3:\tMaximum length of time since order was given. Use 0 for any time in the mission.\r\n"
32862 		"\t4:\tName of the target of the order (optional).\r\n"
32863 		"\t5:\tName of player ship giving the order(optional).\r\n"
32864 		"\t6:\tName of the subsystem for Destroy Subsystem orders.(optional)" },
32865 
32866 	// Karajorma
32867 	{ OP_RESET_ORDERS, "Reset-Orders (Action training operator)\r\n"
32868 		"\tResets the list of orders the player has given.\r\n"
32869 		"Takes no arguments." },
32870 
32871 	{ OP_WAYPOINT_MISSED, "Waypoint-missed (Boolean training operator)\r\n"
32872 		"\tBecomes true when a waypoint is flown, but the waypoint is ahead of the one "
32873 		"they are supposed to be flying.  The one they are supposed to be flying is the "
32874 		"next one in sequence in the path after the last one they have hit.\r\n\r\n"
32875 		"Returns a boolean value.  Takes no arguments." },
32876 
32877 	{ OP_PATH_FLOWN, "Path-flown (Boolean training operator)\r\n"
32878 		"\tBecomes true when all the waypoints in the path have been flown, in sequence.\r\n\r\n"
32879 		"Returns a boolean value.  Takes no arguments." },
32880 
32881 	{ OP_WAYPOINT_TWICE, "Waypoint-twice (Boolean training operator)\r\n"
32882 		"\tBecomes true when a waypoint is hit that is before the last one hit, which "
32883 		"indicates they have flown a waypoint twice.\r\n\r\n"
32884 		"Returns a boolean value.  Takes no arguments." },
32885 
32886 	{ OP_TRAINING_MSG, "Training-msg (Action training operator)\r\n"
32887 		"\tSends the player a training message.  Uses the same messages as normal messages, "
32888 		"only they get displayed differently using this operator.  If a secondary message "
32889 		"is specified, it is sent the last time, while the primary message is sent all other "
32890 		"times (event should have a repeat count greater than 1).\r\n\r\n"
32891 		"Takes 1-4 arguments...\r\n"
32892 		"\t1:\tName of primary message to send.\r\n"
32893 		"\t2:\tName of secondary message to send (optional).\r\n"
32894 		"\t3:\tDelay (in seconds) to wait before sending message. (optional)\r\n"
32895 		"\t4:\tAmount of Time (in seconds) to display message (optional)." },
32896 
32897 	{ OP_SET_TRAINING_CONTEXT_FLY_PATH, "Set-training-context-fly-path (Training context operator)\r\n"
32898 		"\tTells FreeSpace that the player is expected to fly a waypoint path.  This must be "
32899 		"executed before waypoint-missed, waypoint-twice and path-flown operators become valid.\r\n\r\n"
32900 		"Takes 2 arguments...\r\n"
32901 		"\t1:\tName of waypoint path player should fly.\r\n"
32902 		"\t2:\tDistance away a player needs to be from a waypoint for it to be registered as flown." },
32903 
32904 	{ OP_SET_TRAINING_CONTEXT_SPEED, "Set-training-context-speed (Training context operator)\r\n"
32905 		"\tTells FreeSpace that the player is expected to fly within a certain speed range.  Once "
32906 		"this operator has been executed, you can measure how long they have been within this "
32907 		"speed range with the speed operator.\r\n\r\n"
32908 		"Takes 2 arguments...\r\n"
32909 		"\t1:\tMinimum speed of range player is to fly between.\r\n"
32910 		"\t2:\tMaximum speed of range player is to fly between." },
32911 
32912 	// Karajorma
32913 	{ OP_STRING_TO_INT, "string-to-int\r\n"
32914 		"\tConverts a string into an integer.  All non-numeric characters (except for the negative sign) will be ignored, as will any fractional part of a decimal number.  This behavior is somewhat different than the atoi() function in C or C++, which will abort if it encounters any non-numeric character.  For a string like \"turret31\", this sexp will return 31, but atoi() will return 0.\r\n\r\n"
32915 		"Takes 1 argument...\r\n"
32916 		"\t1:\tString to convert" },
32917 
32918 	// Goober5000
32919 	{ OP_STRING_GET_LENGTH, "string-get-length\r\n"
32920 		"\tReturns the length of the specified string.  Takes 1 argument." },
32921 
32922 	// Goober5000
32923 	{ OP_INT_TO_STRING, "int-to-string\r\n"
32924 		"\tConverts an integer into a string.  The destination must be a string variable.\r\n"
32925 		"Takes 2 argument...\r\n"
32926 		"\t1:\tInteger to convert\r\n"
32927 		"\t2:\tString variable to contain the result\r\n" },
32928 
32929 	// Goober5000
32930 	{ OP_STRING_CONCATENATE, "string-concatenate (deprecated in favor of string-concatenate-block)\r\n"
32931 		"\tConcatenates two strings, putting the result into a string variable.  If the length of the string will "
32932 		"exceed the sexp variable token limit (currently 32), it will be truncated.\r\n\r\n"
32933 		"Takes 3 arguments...\r\n"
32934 		"\t1: First string\r\n"
32935 		"\t2: Second string\r\n"
32936 		"\t3: String variable to hold the result\r\n" },
32937 
32938 	// Goober5000
32939 	{ OP_STRING_CONCATENATE_BLOCK, "string-concatenate-block\r\n"
32940 		"\tConcatenates two or more strings, putting the result into a string variable.  If the length of the string will "
32941 		"exceed the sexp variable token limit (currently 32), it will be truncated.\r\n\r\n"
32942 		"Takes 3 or more arguments...\r\n"
32943 		"\t1: String variable to hold the result\r\n"
32944 		"\tRest: Strings to concatenate.  At least two of these are required; the rest are optional.\r\n" },
32945 
32946 	// Goober5000
32947 	{ OP_STRING_GET_SUBSTRING, "string-get-substring\r\n"
32948 		"\tExtracts a substring from a parent string, putting the result into a string variable.  If the length of the string will "
32949 		"exceed the sexp variable token limit (currently 32), it will be truncated.\r\n\r\n"
32950 		"Takes 3 arguments...\r\n"
32951 		"\t1: Parent string\r\n"
32952 		"\t2: Index at which the substring begins (0-based)\r\n"
32953 		"\t3: Length of the substring\r\n"
32954 		"\t4: String variable to hold the result\r\n" },
32955 
32956 	// Goober5000
32957 	{ OP_STRING_SET_SUBSTRING, "string-set-substring\r\n"
32958 		"\tReplaces a substring from a parent string with a new string, putting the result into a string variable.  If the length of the string will "
32959 		"exceed the sexp variable token limit (currently 32), it will be truncated.\r\n\r\n"
32960 		"Takes 3 arguments...\r\n"
32961 		"\t1: Parent string\r\n"
32962 		"\t2: Index at which the substring begins (0-based)\r\n"
32963 		"\t3: Length of the substring\r\n"
32964 		"\t4: New substring (which can be a different length than the old substring)\r\n"
32965 		"\t5: String variable to hold the result\r\n" },
32966 
32967 	// Karajorma
32968 	{ OP_DEBUG, "debug\r\n"
32969 		"\tPops up a warning on debug builds (and optionally on release builds)\r\n"
32970 		"Takes 1 or more arguments...\r\n"
32971 		"\t1:\tLimit warning to debug builds.  If false, display warning in release builds too. Defaults to true.\r\n"
32972 		"\t2:\tA short string which will appear in the warning, or a message if the string matches a message name.\r\n"},
32973 
32974 	{ OP_GRANT_PROMOTION, "Grant promotion (Action operator)\r\n"
32975 		"\tIn a single player game, this function grants a player an automatic promotion to the "
32976 		"next rank which the player can obtain.  If he is already at the highest rank, this "
32977 		"operator has no effect.  It takes no arguments." },
32978 
32979 	{ OP_GRANT_MEDAL, "Grant medal (Action operator)\r\n"
32980 		"\tIn single player missions, this function grants the given medal to the player.  "
32981 		"Currently, only 1 medal will be allowed to be given per mission.\r\n\r\n"
32982 		"Takes 1 argument...\r\n"
32983 		"\t1:\tName of medal to grant to player." },
32984 
32985 	{ OP_GOOD_SECONDARY_TIME, "Set preferred secondary weapons\r\n"
32986 		"\tThis sexpression is used to inform the AI about preferred secondary weapons to "
32987 		"fire during combat.  When this expression is evaluated, any AI ships of the given "
32988 		"team prefer to fire the given weapon at the given ship. (Preferred over other "
32989 		"secondary weapons)\r\n\r\n"
32990 		"Takes 4 argument...\r\n"
32991 		"\t1:\tTeam name which will prefer firing given weapon\r\n"
32992 		"\t2:\tMaximum number of this type of weapon above team can fire.\r\n"
32993 		"\t3:\tWeapon name (list includes only the valid weapons for this expression\r\n"
32994 		"\t4:\tShip name at which the above named team should fire the above named weapon." },
32995 
32996 	{ OP_AND_IN_SEQUENCE, "And in sequence (Boolean operator)\r\n"
32997 		"\tReturns true if all of its arguments have become true in the order they are "
32998 		"listed in.\r\n\r\n"
32999 		"Returns a boolean value.  Takes 2 or more boolean arguments." },
33000 
33001 	{ OP_SKILL_LEVEL_AT_LEAST, "Skill level at least (Boolean operator)\r\n"
33002 		"\tReturns true if the player has selected the given skill level or higher.\r\n\r\n"
33003 		"Returns a boolean value.  Takes 1 argument...\r\n"
33004 		"\t1:\tName of the skill level to check." },
33005 
33006 	{ OP_NUM_PLAYERS, "Num players (Status operator)\r\n"
33007 		"\tReturns the current number of players (multiplayer) playing in the current mission.\r\n\r\n"
33008 		"Returns a numeric value.  Takes no arguments." },
33009 
33010 	{ OP_IS_CARGO_KNOWN, "Is cargo known (Boolean operator)\r\n"
33011 		"\tReturns true if all of the specified objects' cargo is known by the player (i.e. they "
33012 		"have scanned each one.\r\n\r\n"
33013 		"Returns a boolean value.  Takes 1 or more arguments...\r\n"
33014 		"\tAll:\tName of ship to check if its cargo is known." },
33015 
33016 	{ OP_HAS_BEEN_TAGGED_DELAY, "Has ship been tagged (delay) (Boolean operator)\r\n"
33017 		"\tReturns true if all of the specified ships have been tagged.\r\n\r\n"
33018 		"Returns a boolean value after <delay> seconds when all ships have been tagged.  Takes 2 or more arguments...\r\n"
33019 		"\t1:\tDelay in seconds after which sexpression will return true when all cargo scanned."
33020 		"\tRest:\tNames of ships to check if tagged.." },
33021 
33022 	{ OP_ARE_SHIP_FLAGS_SET, "Are ship flags set (Boolean operator)\r\n"
33023 		"\tReturns true if all of the specified flags have been set for this particular ship.\r\n\r\n"
33024 		"Takes 2 or more arguments...\r\n"
33025 		"\t1:\tName of the ship."
33026 		"\tRest:\tShip, object or ai flags which might be set for this ship.." },
33027 
33028 	{ OP_CAP_SUBSYS_CARGO_KNOWN_DELAY, "Is capital ship subsystem cargo known (delay) (Boolean operator)\r\n"
33029 		"\tReturns true if all of the specified subsystem cargo is known by the player.\r\n"
33030 		"\tNote: Cargo must be explicitly named.\r\n\r\n"
33031 		"Returns a boolean value after <delay> seconds when all cargo is known.  Takes 3 or more arguments...\r\n"
33032 		"\t1:\tDelay in seconds after which sexpression will return true when all cargo scanned.\r\n"
33033 		"\t2:\tName of capital ship\r\n"
33034 		"\tRest:\tNames of subsystems to check for cargo known.." },
33035 
33036 	{ OP_CARGO_KNOWN_DELAY, "Is cargo known (delay) (Boolean operator)\r\n"
33037 		"\tReturns true if all of the specified objects' cargo is known by the player (i.e. they "
33038 		"have scanned each one.\r\n\r\n"
33039 		"Returns a boolean value after <delay> seconds when all cargo is known.  Takes 2 or more arguments...\r\n"
33040 		"\t1:\tDelay in seconds after which sexpression will return true when all cargo scanned."
33041 		"\tRest:\tNames of ships/cargo to check for cargo known." },
33042 
33043 	{ OP_WAS_PROMOTION_GRANTED, "Was promotion granted (Boolean operator)\r\n"
33044 		"\tReturns true if a promotion was granted via the 'Grant promotion' operator in the mission.\r\n\r\n"
33045 		"Returns a boolean value.  Takes no arguments." },
33046 
33047 	{ OP_WAS_MEDAL_GRANTED, "Was medal granted (Boolean operator)\r\n"
33048 		"\tReturns true if a medal was granted via via the 'Grant medal' operator in the mission.  "
33049 		"If you provide the optional argument to this operator, then true is only returned if the "
33050 		"specified medal was granted.\r\n\r\n"
33051 		"Returns a boolean value.  Takes 0 or 1 arguments...\r\n"
33052 		"\t1:\tName of medal to specifically check for (optional)." },
33053 
33054 	{ OP_GOOD_REARM_TIME, "Good rearm time (Action operator)\r\n"
33055 		"\tInforms the game logic that right now is a good time for a given team to attempt to "
33056 		"rearm their ships.  The time parameter specified how long the \"good time\" will last.\r\n\r\n"
33057 		"Takes 2 arguments...\r\n"
33058 		"\t1:\tTeam Name\r\n"
33059 		"\t2:\tTime in seconds rearm window should last" },
33060 
33061 	{ OP_ALLOW_SHIP, "Allow ship (Action operator)\r\n"
33062 		"\tThis operator makes the given ship type available to the Terran team.  Players will be "
33063 		"able to have ships of this type in their starting wings in all future missions of this "
33064 		"campaign.\r\n\r\n"
33065 		"Takes 1 argument...\r\n"
33066 		"\t1:\tName of ship type (or ship class) to allow." },
33067 
33068 	{ OP_ALLOW_WEAPON, "Allow weapon (Action operator)\r\n"
33069 		"\tThis operator makes the given weapon available to the Terran team.  Players will be "
33070 		"able to equip ships with in all future missions of this campaign.\r\n\r\n"
33071 		"Takes 1 argument...\r\n"
33072 		"\t1:\tName of weapon (primary or secondary) to allow." },
33073 
33074 	{ OP_TECH_ADD_SHIP, "Tech add ship (Action operator)\r\n"
33075 		"\tThis operator makes the given ship type available in the techroom database.  Players will "
33076 		"then be able to view this ship's specs there.\r\n\r\n"
33077 		"Takes 1 or more arguments...\r\n"
33078 		"\tAll:\tName of ship type (or ship class) to add." },
33079 
33080 	{ OP_TECH_ADD_WEAPON, "Tech add weapon (Action operator)\r\n"
33081 		"\tThis operator makes the given weapon available in the techroom database.  Players will "
33082 		"then be able to view this weapon's specs there.\r\n\r\n"
33083 		"Takes 1 or more arguments...\r\n"
33084 		"\tAll:\tName of weapon (primary or secondary) to add." },
33085 
33086 	{ OP_TECH_ADD_INTEL, "Tech add intel (Action operator, deprecated in favor of tech-add-intel-xstr)\r\n"
33087 		"\tThis operator makes the given intel entry available in the techroom database.  Players will "
33088 		"then be able to view this intel entry there.\r\n\r\n"
33089 		"Takes 1 or more arguments...\r\n"
33090 		"\tAll:\tName of intel entry to add." },
33091 
33092 	{ OP_TECH_REMOVE_INTEL, "Tech remove intel (Action operator, deprecated in favor of tech-remove-intel-xstr)\r\n"
33093 		"\tThis operator removes the given intel entry in the techroom database.  Players will "
33094 		"then not be able to view this intel entry there.\r\n\r\n"
33095 		"Takes 1 or more arguments...\r\n"
33096 		"\tAll:\tName of intel entry to remove." },
33097 
33098 	{ OP_TECH_ADD_INTEL_XSTR, "Tech add intel XSTR (Action operator)\r\n"
33099 		"\tThis operator makes the given intel entry available in the techroom database.  Players will "
33100 		"then be able to view this intel entry there.\r\n\r\n"
33101 		"Takes 2 or more arguments...\r\n"
33102 		"\t1:\tName of intel entry to add.\r\n"
33103 		"\t2:\tXSTR ID of intel entry, or -1 if there is no XSTR entry.\r\n"
33104 		"Use Add-Data for multiple entries.\r\n\r\n"
33105 		"IMPORTANT: Each additional entry in the list MUST HAVE two fields; "
33106 		"any entry without both fields will be ignored, as will any successive entries." },
33107 
33108 	{ OP_TECH_REMOVE_INTEL_XSTR, "Tech remove intel XSTR (Action operator)\r\n"
33109 		"\tThis operator removes the given intel entry, so it is no longer available in the techroom database.  Players will "
33110 		"then not be able to view this intel entry there.\r\n\r\n"
33111 		"Takes 2 or more arguments...\r\n"
33112 		"\t1:\tName of intel entry to remove.\r\n"
33113 		"\t2:\tXSTR ID of intel entry, or -1 if there is no XSTR entry.\r\n"
33114 		"Use Add-Data for multiple entries.\r\n\r\n"
33115 		"IMPORTANT: Each additional entry in the list MUST HAVE two fields; "
33116 		"any entry without both fields will be ignored, as will any successive entries." },
33117 
33118 	{ OP_TECH_RESET_TO_DEFAULT, "Tech reset to default (Action operator)\r\n"
33119 		"\tThis operator resets the tech room to the default represented in the tables.  This is "
33120 		"useful for starting new campaigns, so that the player will not see tech entries carried over "
33121 		"from previous campaigns.\r\n\r\n"
33122 		"Takes no arguments." },
33123 
33124 	{ OP_CHANGE_PLAYER_SCORE, "Change Player Score (Action operator)\r\n"
33125 		"\tThis operator allows direct alteration of the player's score for this mission.\r\n\r\n"
33126 		"Takes 2 or more arguments."
33127 		"\t1:\tAmount to alter the player's score by.\r\n"
33128 		"\tRest:\tName of ship the player is flying."},
33129 
33130 	{ OP_CHANGE_TEAM_SCORE, "Change Team Score (Action operator)\r\n"
33131 		"\tThis operator allows direct alteration of the team's score for a TvT mission (Does nothing otherwise).\r\n\r\n"
33132 		"Takes 2 arguments."
33133 		"\t1:\tAmount to alter the team's score by.\r\n"
33134 		"\t2:\tThe team to alter the score for. (0 will add the score to all teams!)"},
33135 
33136 	{ OP_AI_EVADE_SHIP, "Ai-evade ship (Ship goal)\r\n"
33137 		"\tCauses the specified ship to go into evade mode and run away like the weak "
33138 		"sally-boy it is.\r\n\r\n"
33139 		"Takes 2 arguments...\r\n"
33140 		"\t1:\tName of ship to evade from.\r\n"
33141 		"\t2:\tGoal priority (number between 0 and 89)." },
33142 
33143 	{ OP_AI_STAY_NEAR_SHIP, "Ai-stay near ship (Ship goal)\r\n"
33144 		"\tCauses the specified ship to keep itself near the given ship and not stray too far "
33145 		"away from it.\r\n\r\n"
33146 		"Takes 2 arguments...\r\n"
33147 		"\t1:\tName of ship to stay near.\r\n"
33148 		"\t2:\tGoal priority (number between 0 and 89)." },
33149 
33150 	{ OP_AI_KEEP_SAFE_DISTANCE, "Ai-keep safe distance (Ship goal)\r\n"
33151 		"\tTells the specified ship to stay a safe distance away from any ship that isn't on the "
33152 		"same team as it.\r\n\r\n"
33153 		"Takes 1 argument...\r\n"
33154 		"\t1:\tGoal priority (number between 0 and 89)." },
33155 
33156 	{ OP_AI_IGNORE, "Ai-ignore (Ship goal)\r\n"
33157 		"\tTells the specified ship to ignore the given target and not consider it when picking something "
33158 		"to attack, but ALSO sets the protect-ship flag on the target so that NO ship can attack it.  Only 1 target "
33159 		"can be ignored at a time, and the protect-ship flag will remain set on the target unless some other sexp "
33160 		"explicitly removes it or instructs a ship to attack the target.  In most cases mission designers will want to use "
33161 		"ai-ignore-new instead.\r\n\r\n"
33162 		"Takes 2 arguments...\r\n"
33163 		"\t1:\tName of target to ignore.\r\n"
33164 		"\t2:\tGoal priority (number between 0 and 89) - note, this does not imply any ranking of ignored targets." },
33165 
33166 	// Goober5000
33167 	{ OP_AI_IGNORE_NEW, "Ai-ignore-new (Ship goal)\r\n"
33168 		"\tTells the specified ship to ignore the given target and not consider it when picking something "
33169 		"to attack.  Up to " STR(MAX_IGNORE_NEW_OBJECTS) " targets can be ignored at a time.  Targets ignored by "
33170 		"any given ship do not affect targets ignored by any other ship.\r\n\r\n"
33171 		"Takes 2 arguments...\r\n"
33172 		"\t1:\tName of target to ignore.\r\n"
33173 		"\t2:\tGoal priority (number between 0 and 89) - note, this does not imply any ranking of ignored targets." },
33174 
33175 	{ OP_AI_STAY_STILL, "Ai-stay still (Ship goal)\r\n"
33176 		"\tCauses the specified ship to stay still.  The ship will do nothing until attacked at "
33177 		"which time the ship will come to life and defend itself.\r\n\r\n"
33178 		"Takes 2 arguments...\r\n"
33179 		"\t1:\tShip or waypoint the ship staying still will directly face (currently not implemented)\r\n"
33180 		"\t2:\tGoal priority (number between 0 and 89)." },
33181 
33182 	{ OP_AI_PLAY_DEAD, "Ai-play-dead (Ship goal)\r\n"
33183 		"\tCauses the specified ship to pretend that it is dead and not do anything.  This "
33184 		"expression should be used to indicate that a ship has no pilot and cannot respond "
33185 		"to any enemy threats.  A ship playing dead will not respond to any attack.\r\n\r\n"
33186 		"Do note that the ship's goal list is cleared, which means that if it receives any other goal in any way, "
33187 		"it will immediately come back to life.  Use ai-play-dead-persistent to prevent this from happening.\r\n\r\n"
33188 		"Takes 1 argument...\r\n"
33189 		"\t1:\tGoal priority (number between 0 and 89)." },
33190 
33191 	{ OP_AI_PLAY_DEAD_PERSISTENT, "Ai-play-dead-persistent (Ship goal)\r\n"
33192 		"\tCauses the specified ship to pretend that it is dead and not do anything.  This "
33193 		"goal behaves exactly like ai-play-dead, with the important difference that the ship "
33194 		"will not immediately come back to life whenever it is given an order or a new goal "
33195 		"of any priority.  The only ways this goal can be removed are via remove-goal, "
33196 		"clear-goals, or a new goal of a *higher* priority.\r\n\r\n"
33197 		"Takes 1 argument...\r\n"
33198 		"\t1:\tGoal priority (number between 0 and 89)." },
33199 
33200 	{ OP_AI_FORM_ON_WING, "Ai-form-on-wing (Ship Goal)\r\n"
33201 		"\tCauses the ship to form on the specified ship's wing. This works analogous to the "
33202 		"player order, and will cause all other goals specified for the ship to be purged.\r\n\r\n"
33203 		"Takes 1 argument...\r\n"
33204 		"\t1:\tShip to form on." },
33205 
33206 	{ OP_FLASH_HUD_GAUGE, "Ai-flash hud gauge (Training goal)\r\n"
33207 		"\tCauses the specified hud gauge to flash to draw the player's attention to it.\r\n\r\n"
33208 		"Takes 1 argument...\r\n"
33209 		"\t1:\tName of hud gauge to flash." },
33210 
33211 	{ OP_ALTER_SHIP_FLAG, "alter-ship-flag\r\n"
33212 		"\tSets a ships flag and/or parse flag.\r\n\r\n"
33213 		"Takes 4 or more arguments...\r\n"
33214 		"\t1:\tShip flag name\r\n"
33215 		"\t2:\tTrue if turning on, false if turning off\r\n"
33216 		"\t3:\tTrue\\False - Apply this flag to future waves of this wing. Apply to ship if not present\r\n"
33217 		"\tRest:\t (optional) Name of ships, wings, or entire teams. If not supplied, will work on all ships in the mission\r\n\r\n"
33218 		"Ship Flags:\r\n"
33219 		"invulnerable - Stops ship from taking any damage\r\n"
33220 		"protect-ship - Ship and Turret AI will ignore and not attack ship\r\n"
33221 		"beam-protect-ship - Turrets with beam weapons will ignore and not attack ship\r\n"
33222 		"no-shields - Ship will have no shields (ETS will be rebalanced if shields were off and are enabled)\r\n"
33223 		"targetable-as-bomb - Allows ship to be targetted with the bomb targetting key\r\n"
33224 		"flak-protect-ship - Turrets with flak weapons will ignore and not attack ship\r\n"
33225 		"laser-protect-ship - Turrets with laser weapons will ignore and not attack ship\r\n"
33226 		"missile-protect-ship - Turrets with missile weapons will ignore and not attack ship\r\n"
33227 		"immobile - Will not let a ship move or rotate in any fashion\r\n"
33228 		"vaporize - Causes a ship to vanish (no deathroll, no debris, no explosion) when destroyed\r\n"
33229 		"break-warp - Causes a ship's subspace drive to break. Can be repaired by a support ship\r\n"
33230 		"never-warp - Causes a ship's subspace drive to never work. Cannot be repaired by a support ship\r\n"
33231 		"afterburner-locked - Will stop a ship from firing their afterburner\r\n"
33232 		"primaries-locked - Will stop a ship from firing their primary weapons\r\n"
33233 		"secondaries-locked - Will stop a ship from firing their secondary weapons\r\n"
33234 		"no-subspace-drive - Will not allow a ship to jump into subspace\r\n"
33235 		"don't-collide-invisible - Will cause polygons with an invisible texture to stop colliding with objects\r\n"
33236 		"no-ets - Will not allow a ship to alter its ETS system\r\n"
33237 		"toggle-subsystem-scanning - Switches between being able to scan a whole ship or individual subsystems\r\n"
33238 		"scannable - Whether or not the ship can be scanned\r\n"
33239 		"cargo-known - If set, the ships cargo can be seen without scanning the ship\r\n"
33240 		"stealth - If set, the ship can't be targeted, is invisible on radar, and is ignored by AI unless firing\r\n"
33241 		"friendly-stealth-invisible - If set, the ship can't be targeted even by ships on the same team\r\n"
33242 		"hide-ship-name - If set, the ship name can't be seen when the ship is targeted\r\n"
33243 		"hidden-from-sensors - If set, the ship can't be targeted and appears on radar as a blinking dot\r\n"
33244 		"no-dynamic - Will stop allowing the AI to persue dynamic goals (eg: chasing ships it was not ordered to)\r\n"
33245 		"no-secondary-lock-on - Will disable target acquisition for secondaries of all types (does not affect turrets)\r\n"},
33246 
33247 	{ OP_SHIP_VISIBLE, "ship-visible\r\n"
33248 		"\tCauses the ships listed in this sexpression to be visible with player sensors.\r\n\r\n"
33249 		"Takes 1 or more arguments...\r\n"
33250 		"\t1+:\tName of ships to make visible to sensors." },
33251 
33252 	{ OP_SHIP_INVISIBLE, "ship-invisible\r\n"
33253 		"\tCauses the ships listed in this sexpression to be invisible to player sensors.\r\n\r\n"
33254 		"Takes 1 or more arguments...\r\n"
33255 		"\t1+:\tName of ships to make invisible to sensors." },
33256 
33257 	{ OP_SHIP_VULNERABLE, "ship-vulnerable\r\n"
33258 		"\tCauses the ship listed in this sexpression to be vulnerable to weapons.\r\n\r\n"
33259 		"Takes 1 or more arguments...\r\n"
33260 		"\t1+:\tName of ships to make vulnerable to weapons." },
33261 
33262 	{ OP_SHIP_INVULNERABLE, "ship-invulnerable\r\n"
33263 		"\tCauses the ships listed in this sexpression to be invulnerable to weapons.  Use with caution!!!!\r\n\r\n"
33264 		"Takes 1 or more arguments...\r\n"
33265 		"\t1+:\tName of ships to make invulnerable to weapons." },
33266 
33267 	{ OP_SHIP_BOMB_TARGETABLE, "ship-targetable-as-bomb\r\n"
33268 		"\tCauses the ships listed in this sexpression to be targetable with bomb targeting key.\r\n\r\n"
33269 		"Takes 1 or more arguments...\r\n"
33270 		"\t1+:\tName of ships to make targetable with bomb targeting key." },
33271 
33272 	{ OP_SHIP_BOMB_UNTARGETABLE, "ship-untargetable-as-bomb\r\n"
33273 		"\tCauses the ships listed in this sexpression to not be targetable with bomb targeting key.\r\n\r\n"
33274 		"Takes 1 or more arguments...\r\n"
33275 		"\t1+:\tName of ships to make nontargetable with bomb targeting key." },
33276 
33277 	{ OP_SHIELDS_ON, "shields-on\r\n" //-Sesquipedalian
33278 		"\tCauses the ship listed in this sexpression to have their shields activated.\r\n"
33279 		"If the ship had no-shields prior to the sexp being called, the ETS will be rebalanced to default.\r\n"
33280 		"Takes 1 or more arguments...\r\n"
33281 		"\t1+:\tName of ships to activate shields on." },
33282 
33283 	{ OP_SHIELDS_OFF, "shields-off\r\n" //-Sesquipedalian
33284 		"\tCauses the ships listed in this sexpression to have their shields deactivated.  \r\n\r\n"
33285 		"Takes 1 or more arguments...\r\n"
33286 		"\t1+:\tName of ships to deactivate shields on." },
33287 
33288 	{ OP_SHIP_GUARDIAN, "ship-guardian\r\n"
33289 		"\tCauses the ships listed in this sexpression to not take any damage below 1% of hull strength.  As such, the ship will not be killable by weapons.  Use with caution!!!!\r\n\r\n"
33290 		"Takes 1 or more arguments...\r\n"
33291 		"\t1+:\tName of ships to make unkillable." },
33292 
33293 	{ OP_SHIP_NO_GUARDIAN, "ship-no-guardian\r\n"
33294 		"\tCauses the ships listed in this sexpression to be killable by weapons, if not invulnerable.\r\n\r\n"
33295 		"Takes 1 or more arguments...\r\n"
33296 		"\t1+:\tName of ships to make killable." },
33297 
33298 	// Goober5000
33299  	{ OP_SHIP_GUARDIAN_THRESHOLD, "ship-guardian-threshold\r\n"
33300  		"\tSame as ship-guardian, except the lowest possible hull value (as a percentage) is specified by the sexp rather than defaulting to 1.\r\n"
33301  		"Call with a threshold of 0 (or use ship-no-guardian) to deactivate.\r\n\r\n"
33302  		"Takes 2 or more arguments...\r\n"
33303  		"\t1:\tThreshold value.\r\n"
33304  		"\t2+:\tName of ships to make unkillable." },
33305 
33306  	// Goober5000
33307 	{ OP_SHIP_SUBSYS_GUARDIAN_THRESHOLD, "ship-subsys-guardian-threshold\r\n"
33308 		"\tSame as ship-guardian-threshold, but works on subsystems.\r\n"
33309 		"Call with a threshold of 0 to deactivate.\r\n\r\n"
33310  		"Takes 3 or more arguments...\r\n"
33311 		"\t1:\tThreshold value.\r\n"
33312 		"\t2:\tShip housing the subsystem(s).\r\n"
33313 		"\t3+:\tSubsystems to make unkillable." },
33314 
33315 	// Goober5000
33316 	{ OP_SHIP_STEALTHY, "ship-stealthy\r\n"
33317 		"\tCauses the ships listed in this sexpression to become stealth ships (i.e. invisible to radar).\r\n\r\n"
33318 		"Takes 1 or more arguments...\r\n"
33319 		"\tAll:\tName of ships to make stealthy." },
33320 
33321 	// Goober5000
33322 	{ OP_SHIP_UNSTEALTHY, "ship-unstealthy\r\n"
33323 		"\tCauses the ships listed in this sexpression to become non-stealth ships (i.e. visible to radar).\r\n\r\n"
33324 		"Takes 1 or more arguments...\r\n"
33325 		"\tAll:\tName of ships to make non-stealthy." },
33326 
33327 	// Goober5000
33328 	{ OP_FRIENDLY_STEALTH_INVISIBLE, "friendly-stealth-invisible\r\n"
33329 		"\tCauses the friendly ships listed in this sexpression to be invisible to radar, just like hostile stealth ships."
33330 		"It doesn't matter if the ship is friendly at the time this sexp executes: as long as it is a stealth ship, it will"
33331 		"be invisible to radar both as hostile and as friendly.\r\n\r\n"
33332 		"Takes 1 or more arguments...\r\n"
33333 		"\tAll:\tName of ships" },
33334 
33335 	// Goober5000
33336 	{ OP_FRIENDLY_STEALTH_VISIBLE, "friendly-stealth-visible\r\n"
33337 		"\tCauses the friendly ships listed in this sexpression to resume their normal behavior of being visible to radar as"
33338 		"stealth friendlies.  Does not affect their visibility as stealth hostiles.\r\n\r\n"
33339 		"Takes 1 or more arguments...\r\n"
33340 		"\tAll:\tName of ships" },
33341 
33342 	// Goober5000
33343 	{ OP_SHIP_SUBSYS_TARGETABLE, "ship-subsys-targetable\r\n"
33344 		"\tCauses the specified ship subsystem(s) to be targetable on radar.\r\n"
33345 		"Takes 2 or more arguments...\r\n"
33346 		"\t1:\tName of a ship\r\n"
33347 		"\tRest: Name of the ship's subsystem(s)" },
33348 
33349 	// Goober5000
33350 	{ OP_SHIP_SUBSYS_UNTARGETABLE, "ship-subsys-untargetable\r\n"
33351 		"\tCauses the specified ship subsystem(s) to not be targetable on radar.\r\n"
33352 		"Takes 2 or more arguments...\r\n"
33353 		"\t1:\tName of a ship\r\n"
33354 		"\tRest: Name of the ship's subsystem(s)" },
33355 
33356 	// FUBAR
33357 	{ OP_SHIP_SUBSYS_NO_REPLACE, "ship-subsys-no-replace\r\n"
33358 		"\tCauses the -destroyed version of specified ship subsystem(s) to not render when it's destroyed.\r\n"
33359 		"Takes 3 or more arguments...\r\n"
33360 		"\t1:\tName of a ship\r\n"
33361 		"\t2:\tTrue = Do not render or False = render if exists\r\n"
33362 		"\tRest: Name of the ship's subsystem(s)"
33363 		"\tNote: If subsystem is already dead it will vanish or reappear out of thin air" },
33364 
33365 
33366 	// FUBAR
33367 	{ OP_SHIP_SUBSYS_NO_LIVE_DEBRIS, "ship-subsys-no-live-debris\r\n"
33368 		"\tCauses the specified ship subsystem(s) to not render live debris when it's destroyed.\r\n"
33369 		"Takes 3 or more arguments...\r\n"
33370 		"\t1:\tName of a ship\r\n"
33371 		"\t2:\tTrue = Do not render or False = render if exists\r\n"
33372 		"\tRest: Name of the ship's subsystem(s)" },
33373 
33374 	// FUBAR
33375 	{ OP_SHIP_SUBSYS_VANISHED, "ship-subsys-vanish\r\n"
33376 		"\tCauses the subsystem to vanish without a trace - no fanfare, notification, or special effects.  See also ship-vanish.\r\n"
33377 		"\tSingle Player Only!  Warning: This will cause subsystem destruction not to be logged, so 'has-departed', etc. will not work\r\n"
33378 		"Takes 3 or more arguments...\r\n"
33379 		"\t1:\tName of a ship\r\n"
33380 		"\t2:\tTrue = vanish or False = don't vanish\r\n"
33381 		"\tRest: Name of the ship's subsystem(s)"
33382 		"\tNote: Useful for replacing subsystems with actual docked models." },
33383 
33384 	// FUBAR
33385 	{ OP_SHIP_SUBSYS_IGNORE_IF_DEAD, "ship-subsys-ignore-if-dead\r\n"
33386 		"\tCauses secondary weapons to ignore dead ship subsystem(s)and home on hull instead.\r\n"
33387 		"Takes 3 or more arguments...\r\n"
33388 		"\t1:\tName of a ship\r\n"
33389 		"\t2:\tTrue = Ignore dead or False = don't ignore\r\n"
33390 		"\tRest: Name of the ship's subsystem(s)" },
33391 
33392 	// Goober5000
33393 	{ OP_SHIP_CHANGE_ALT_NAME, "ship-change-alt-name\r\n"
33394 		"\tChanges the alternate ship class name displayed in the HUD target window.  Takes 2 or more arguments...\r\n"
33395 		"\t1:\tThe ship class name to display\r\n"
33396 		"\tRest:\tThe ships to display the new class name" },
33397 
33398 	// FUBAR
33399 	{ OP_SHIP_CHANGE_CALLSIGN, "ship-change-callsign\r\n"
33400 		"\tChanges the callsign of a ship.  Takes 2 or more arguments...\r\n"
33401 		"\t1:\tThe callsign to display or empty to remove\r\n"
33402 		"\tRest:\tThe ships to display the new callsign" },
33403 
33404 	// Goober5000
33405 	{ OP_SET_DEATH_MESSAGE, "set-death-message\r\n"
33406 		"\tSets the message displayed when the specified players are killed.  Takes 1 or more arguments...\r\n"
33407 		"\t1:\tThe message\r\n"
33408 		"\tRest:\tThe players for whom this message is displayed (optional) (currently not implemented)" },
33409 
33410 	{ OP_PERCENT_SHIPS_ARRIVED, "percent-ships-arrived\r\n"
33411 		"\tBoolean function which returns true if the percentage of ships in the listed ships and wings "
33412 		"which have arrived is greater or equal to the given percentage.  For wings, all ships of all waves "
33413 		"are used for calculation for the total possible ships to arrive.\r\n\r\n"
33414 		"Takes 2 or more arguments...\r\n"
33415 		"\t1:\tPercentge of arriving ships at which this function will return true.\r\n"
33416 		"\t2+:\tList of ships/wings whose arrival status should be determined." },
33417 
33418 	{ OP_PERCENT_SHIPS_DEPARTED, "percent-ships-departed\r\n"
33419 		"\tBoolean function which returns true if the percentage of ships in the listed ships and wings "
33420 		"which have departed is greater or equal to the given percentage.  For wings, all ships of all waves "
33421 		"are used for calculation for the total possible ships to depart.\r\n\r\n"
33422 		"Takes 2 or more arguments...\r\n"
33423 		"\t1:\tPercentge of departed ships at which this function will return true.\r\n"
33424 		"\t2+:\tList of ships/wings whose departure status should be determined." },
33425 
33426 	{ OP_PERCENT_SHIPS_DESTROYED, "percent-ships-destroyed\r\n"
33427 		"\tBoolean function which returns true if the percentage of ships in the listed ships and wings "
33428 		"which have been destroyed is greater or equal to the given percentage.  For wings, all ships of all waves "
33429 		"are used for calculation for the total possible ships to be destroyed.\r\n\r\n"
33430 		"Takes 2 or more arguments...\r\n"
33431 		"\t1:\tPercentge of destroyed ships at which this function will return true.\r\n"
33432 		"\t2+:\tList of ships/wings whose destroyed status should be determined." },
33433 
33434 	// Goober5000
33435 	{ OP_PERCENT_SHIPS_DISARMED, "percent-ships-disarmed\r\n"
33436 		"\tBoolean function which returns true if the percentage of ships in the listed ships "
33437 		"which have been disarmed is greater or equal to the given percentage.\r\n\r\n"
33438 		"Takes 2 or more arguments...\r\n"
33439 		"\t1:\tPercentge of disarmed ships at which this function will return true.\r\n"
33440 		"\t2+:\tList of ships whose disarmed status should be determined." },
33441 
33442 	// Goober5000
33443 	{ OP_PERCENT_SHIPS_DISABLED, "percent-ships-disabled\r\n"
33444 		"\tBoolean function which returns true if the percentage of ships in the listed ships "
33445 		"which have been disabled is greater or equal to the given percentage.\r\n\r\n"
33446 		"Takes 2 or more arguments...\r\n"
33447 		"\t1:\tPercentge of disabled ships at which this function will return true.\r\n"
33448 		"\t2+:\tList of ships whose disabled status should be determined." },
33449 
33450 	{ OP_RED_ALERT, "red-alert\r\n"
33451 		"\tCauses Red Alert status in a mission.  This function ends the current mission, and moves to "
33452 		"the next mission in the campaign under red alert status.  There should only be one branch from "
33453 		"a mission that uses this expression\r\n\r\n"
33454 		"Takes no arguments."},
33455 
33456 	{ OP_MISSION_SET_NEBULA, "mission-set-nebula\r\n"
33457 		"\tTurns nebula on/off\r\n"
33458 		"\tTakes 1 or 2 arguments...\r\n"
33459 		"\t1:\t0 for nebula off, 1 for nebula on\r\n"
33460 		"\t2:\tNebula range (optional; defaults to 3000)\r\n" },
33461 
33462 	{ OP_MISSION_SET_SUBSPACE, "mission-set-subspace\r\n"
33463 		"\tTurns subspace on/off\r\n"
33464 		"\tTakes 1 argument...\r\n"
33465 		"\r1:\t0 for subspace off, 1 for subspace on" },
33466 
33467 	//-Sesquipedalian
33468 	{ OP_END_MISSION, "end-mission\r\n"
33469 		"\tEnds the mission as if the player had engaged his subspace drive, but without him doing so.  Dumps the player back into a normal debriefing.  Does not invoke red-alert status.\r\n"
33470 		"\t1:\tEnd Mission even if the player is dead (optional; defaults to true)\r\n"
33471 		"\t2:\tBoot the player out into the main hall instead of going to the debriefing (optional; defaults to false; not supported in multi)\r\n"
33472 		"\t3:\tGo to the mainhall instead of starting the next mission (optional; defaults to false; not yet tested in multi)"
33473 	},
33474 
33475 	// Goober5000
33476 	{ OP_SET_DEBRIEFING_TOGGLED, "set-debriefing-toggled\r\n"
33477 		"\tSets or clears the \"toggle debriefing\" mission flag.  If set, the mission will have its debriefing turned off, unless it is a multiplayer dogfight mission, in which case its debriefing will be turned on.  Takes 1 argument.\r\n"
33478 	},
33479 
33480 	// Goober5000
33481 	{ OP_SET_DEBRIEFING_PERSONA, "set-debriefing-persona\r\n"
33482 		"\tSets the numeric prefix to be used for debriefing voice files in automatic debriefing stages (promotions, ace badges, traitor).  This is usually specified in the campaign editor, but it can be overridden mid-mission with this sexp.  Takes 1 argument.\r\n"
33483 	},
33484 
33485 	// Goober5000
33486 	{ OP_FORCE_JUMP, "force-jump\r\n"
33487 		"\tForces activation of the player's subspace drive, thus ending the mission.  Takes no arguments."
33488 	},
33489 
33490 	// Goober5000
33491 	{ OP_SET_SCANNED, "set-scanned\r\n"
33492 		"\tSets the cargo on the specified ship or ship subsystem as known or scanned.  Takes 1 or more arguments...\r\n"
33493 		"\t1:\tName of a ship\r\n"
33494 		"\tRest:\tName of a subsystem on that ship (optional)\r\n" },
33495 
33496 	// Goober5000
33497 	{ OP_SET_UNSCANNED, "set-unscanned\r\n"
33498 		"\tSets the cargo on the specified ship or ship subsystem as unknown or unscanned.  Takes 1 or more arguments...\r\n"
33499 		"\t1:\tName of a ship\r\n"
33500 		"\tRest:\tName of a subsystem on that ship (optional)\r\n" },
33501 
33502 	{ OP_DEPART_NODE_DELAY, "depart-node-delay\r\n"
33503 		"\tReturns true N seconds after the listed ships depart, if those ships depart within the "
33504 		"radius of the given jump node.  The delay value is given in seconds.\r\n\r\n"
33505 		"Takes 3 or more arguments...r\n"
33506 		"\t1:\tDelay in seconds after the last ship listed departed before this expression can return true.\r\n"
33507 		"\t2:\tName of a jump node\r\n"
33508 		"\t3+:\tList of ships to check for departure within radius of the jump node." },
33509 
33510 	{ OP_DESTROYED_DEPARTED_DELAY, "destroyed-or-departed-delay\r\n"
33511 		"\tReturns true N seconds after all the listed ships or wings have been destroyed or have "
33512 		"departed.\r\n\r\n"
33513 		"Takes 2 or more arguments...\r\n"
33514 		"\t1:\tDelay in seconds after the last ship/wing is destroyed or departed before this expression can return true.\r\n"
33515 		"\t2+:\tName of a ship or wing" },
33516 
33517 	// description added by Goober5000
33518 	{ OP_SPECIAL_CHECK, "Special-check\r\n"
33519 		"\tMike K.'s special training sexp.  Returns a boolean value.  Takes 1 argument as follows:\r\n"
33520 		"\t0:    Ship \"Freighter 1\" is aspect locked by the player\r\n"
33521 		"\t1:    Player has fired Interceptor#Weak at Freighter 1\r\n"
33522 		"\t2:    Ship \"Freighter 1\", subsystem \"Weapons\" is aspect locked by the player\r\n"
33523 		"\t3:    Apply 10 points of damage to player's forward shields (action operator)\r\n"
33524 		"\t4:    Player's front shields are nearly gone\r\n"
33525 		"\t5:    Quickly recharge player's front shields (action operator)\r\n"
33526 		"\t6:    Reduce all shield quadrants except front to 0 (action operator)\r\n"
33527 		"\t7:    Front shield quadrant is near maximum strength\r\n"
33528 		"\t8:    Rear shield quadrant is near maximum strength\r\n"
33529 		"\t9:    Reduce left and right shield quadrants to 0 (action operator)\r\n"
33530 		"\t10:   Player has fewer than 8 missiles left\r\n"
33531 		"\t11:   Player has 8 or more missiles left\r\n"
33532 		"\t12:   Player has fewer than 4 missiles left\r\n"
33533 		"\t13:   Reduce front shield quadrant to 0 (action operator)\r\n"
33534 		"\t100:  Player is out of countermeasures\r\n"
33535 		"\t2000: Training failed"
33536 	},
33537 
33538 	{ OP_END_CAMPAIGN, "end-campaign\r\n"
33539 		"\tEnds the builtin campaign.  Should only be used by the main FreeSpace campaign\r\n"
33540 		"\t1:\tEnd Campaign even if the player is dead (optional; defaults to true)\r\n" },
33541 
33542 	{ OP_SHIP_VAPORIZE, "ship-vaporize\r\n"
33543 		"\tSets the ship to vaporize when it is destroyed.  Does not actually destroy the ship - use self-destruct for that.\r\n"
33544 		"Takes 1 or more arguments...\r\n"
33545 		"\tAll:\tList of ships on which to set the vaporize flag" },
33546 
33547 	{ OP_SHIP_NO_VAPORIZE, "ship-no-vaporize\r\n"
33548 		"\tSets the ship to not vaporize when it is destroyed.  Does not actually destroy the ship - use self-destruct for that.\r\n"
33549 		"Takes 1 or more arguments...\r\n"
33550 		"\tAll:\tList of ships on which to unset the vaporize flag" },
33551 
33552 	{ OP_DONT_COLLIDE_INVISIBLE, "don't-collide-invisible\r\n"
33553 		"\tSets the \"don't collide invisible\" flag on a list of ships.\r\n"
33554 		"Takes 1 or more arguments...\r\n"
33555 		"\tAll:\tList of ships on which to set the \"don't collide invisible\" flag" },
33556 
33557 	{ OP_COLLIDE_INVISIBLE, "collide-invisible\r\n"
33558 		"\tUnsets the \"don't collide invisible\" flag on a list of ships.\r\n"
33559 		"Takes 1 or more arguments...\r\n"
33560 		"\tAll:\tList of ships on which to unset the \"don't collide invisible\" flag" },
33561 
33562 	{ OP_SET_MOBILE, "set-mobile\r\n"
33563 		"\tAllows the specified ship(s) to move.  Opposite of set-immobile.\r\n"
33564 		"Takes 1 or more arguments...\r\n"
33565 		"\tAll:\tList of ships on which to unset the \"immobile\" flag" },
33566 
33567 	{ OP_SET_IMMOBILE, "set-immobile\r\n"
33568 		"\tPrevents the specified ship(s) from moving in any way.\r\n"
33569 		"Takes 1 or more arguments...\r\n"
33570 		"\tAll:\tList of ships on which to set the \"immobile\" flag" },
33571 
33572 	{ OP_IGNORE_KEY, "ignore-key\r\n"
33573 		"\tCauses the game to ignore (or stop ignoring) a certain key.\r\n"
33574 		"Takes 2 or more arguments...\r\n"
33575 		"\t1: Number of times to ignore this key (-1 = forever, 0 = stop ignoring). \r\n"
33576 		"\tRest: Which key(s) to ignore.\r\n"
33577 	},
33578 
33579 	{ OP_WARP_BROKEN, "break-warp\r\n"
33580 		"\tBreak the warp drive on the specified ship.  A broken warp drive can be repaired by "
33581 		"a repair ship.  Takes 1 or more arguments...\r\n"
33582 		"\tAll:\tList of ships to break the warp drive on" },
33583 
33584 	{ OP_WARP_NOT_BROKEN, "fix-warp\r\n"
33585 		"\tFixes a broken warp drive instantaneously.  This option applies to warp drives broken with "
33586 		"the break-warp sepxression.  Takes 1 or more arguments...\r\n"
33587 		"\tAll:\tList of ships whose warp drive should be fixed"},
33588 
33589 	{ OP_WARP_NEVER, "never-warp\r\n"
33590 		"\tNever allows a ship to warp out.  When this sexpression is used, the given ships will "
33591 		"never be able to warp out.  The warp drive cannot be repaired.  Takes 1 or more arguments...\r\n"
33592 		"\tAll:\tList of ships whose are not allowed to warp out under any condition"},
33593 
33594 	{ OP_WARP_ALLOWED, "allow-warp\r\n"
33595 		"\tAllows a ship which was previously not allowed to warp out to do so.  When this sexpression is "
33596 		"used, the given ships will be able to warp out again.  Takes 1 or more arguments...\r\n"
33597 		"\tAll:\tList of ships whose are allowed to warp out"},
33598 
33599 	{ OP_SET_SUBSPACE_DRIVE, "set-subspace-drive\r\n"
33600 		"\tTurns on or off the subspace edrive for the given ships.  A ship with no subspace drive will act "
33601 		"as though it doesn't even occur to him to depart via subspace, and if ordered to do so, he will look "
33602 		"for a friendly ship with a hangar bay.  If the ship is the player, pressing Alt-J will not not initiate "
33603 		"a jump, nor give any indication that a jump failed.  Takes 2 or more arguments...\r\n"
33604 		"\t1:\tTrue if the ship should have a drive; false otherwise\r\n"
33605 		"\tRest:\tList of ships" },
33606 
33607 	{ OP_JETTISON_CARGO_DELAY, "jettison-cargo-delay (deprecated)\r\n"
33608 		"\tCauses a cargo carrying ship to jettison its cargo without the undocking procedure.  Takes 2 or more arguments...\r\n"
33609 		"\t1: Ship to jettison cargo\r\n"
33610 		"\t2: Delay after which to jettison cargo (note that this isn't actually used)\r\n"
33611 		"\tRest (optional): Cargo to jettison.  If no optional arguments are specified, the ship jettisons all cargo.\r\n"
33612 	},
33613 
33614 	{ OP_JETTISON_CARGO_NEW, "jettison-cargo\r\n"
33615 		"\tCauses a cargo carrying ship to jettison its cargo without the undocking procedure.  This is an upgrade of the old "
33616 		"jettison-cargo-delay sexp which a) didn't use a delay, and b) botched the physics calculations.  Takes 1 or more arguments...\r\n"
33617 		"\t1: Ship to jettison cargo\r\n"
33618 		"\t2 (optional): Speed with which to jettison cargo (defaults to 25)\r\n"
33619 		"\tRest (optional): Cargo to jettison.  If no cargo arguments are specified, the ship jettisons all cargo.\r\n"
33620 	},
33621 
33622 	{ OP_SET_DOCKED, "set-docked\r\n"
33623 		"\tCauses one ship to become instantly docked to another at the specified docking ports.  Takes 4 arguments...\r\n"
33624 		"\t1: Docker ship\r\n"
33625 		"\t1: Docker point\r\n"
33626 		"\t1: Dockee ship\r\n"
33627 		"\t1: Dockee point\r\n"
33628 	},
33629 
33630 	{ OP_BEAM_FIRE, "fire-beam\r\n"
33631 		"\tFire a beam weapon from a specified subsystem\r\n"
33632 		"\t1:\tShip which will be firing\r\n"
33633 		"\t2:\tTurret which will fire the beam (note, this turret must have at least 1 beam weapon on it)\r\n"
33634 		"\t3:\tShip which will be targeted\r\n"
33635 		"\t4:\tSubsystem to target (optional)\r\n"
33636 		"\t5:\tWhether to force the beam to fire (disregarding FOV and subsystem status) (optional)\r\n" },
33637 
33638 	{ OP_BEAM_FIRE_COORDS, "fire-beam-at-coordinates\r\n"
33639 		"\tFire a beam weapon from a specified subsystem at a set of coordinates.  Not compatible with multiplayer.\r\n"
33640 		"\t1:\tShip which will be firing\r\n"
33641 		"\t2:\tTurret which will fire the beam (note, this turret must have at least 1 beam weapon on it)\r\n"
33642 		"\t3:\tx coordinate to be targeted\r\n"
33643 		"\t4:\ty coordinate to be targeted\r\n"
33644 		"\t5:\tz coordinate to be targeted\r\n"
33645 		"\t6:\t(Optional Operator) Whether to force the beam to fire (disregarding FOV and subsystem status). Defaults to False\r\n"
33646 		"\t7:\tsecond x coordinate to be targeted (optional; only used for slash beams)\r\n"
33647 		"\t8:\tsecond y coordinate to be targeted (optional; only used for slash beams)\r\n"
33648 		"\t9:\tsecond z coordinate to be targeted (optional; only used for slash beams)\r\n" },
33649 
33650 	{ OP_BEAM_FLOATING_FIRE, "beam-create\r\n"
33651 		"\tFire a beam weapon from the specified coordinates to the specified target. Not compatible with multiplayer.\r\n"
33652 		"\t1:\tBeam weapon to fire\r\n"
33653 		"\t2:\tParent ship (for kill credit, if applicable; can be none)\r\n"
33654 		"\t3:\tTeam for this beam to be on (related to difficulty-based damage)\r\n"
33655 		"\t4:\tX coordinate to fire from\r\n"
33656 		"\t5:\tY coordinate to fire from\r\n"
33657 		"\t6:\tZ coordinate to fire from\r\n"
33658 		"\t7:\tTarget ship (can be none)\r\n"
33659 		"\t8:\tTarget subsystem (optional, can be none)\r\n"
33660 		"\t9:\tX coordinate to fire at (optional)\r\n"
33661 		"\t10:\tY coordinate to fire at (optional)\r\n"
33662 		"\t11:\tZ coordinate to fire at (optional)\r\n"
33663 		"\t12:\tSecond X coordinate to fire at (optional; used for slash beams)\r\n"
33664 		"\t13:\tSecond Y coordinate to fire at (optional; used for slash beams)\r\n"
33665 		"\t14:\tSecond Z coordinate to fire at (optional; used for slash beams)\r\n" },
33666 
33667 	{ OP_IS_TAGGED, "is-tagged\r\n"
33668 		"\tReturns whether a given ship is tagged or not\r\n"},
33669 
33670 	{ OP_IS_PLAYER, "is-player\r\n"
33671 		"\tReturns true if all the ships specified are currently under control of a player.\r\n"
33672 		"\t1 \t(SinglePlayer) When true ships under AI control return false even if they are the player ship\r\n"
33673 		"\t \t(Multiplayer) When true ships that are respawning return true if the player is still connected\r\n"
33674 		"\tRest \tList of ships to test"},
33675 
33676 	{ OP_NUM_KILLS, "num-kills\r\n"
33677 		"\tReturns the # of kills a player has. The ship specified in the first field should be the ship the player is in.\r\n"
33678 		"\tSo, for single player, this would be Alpha 1. For multiplayer, it can be any ship with a player in it. If, at any\r\n"
33679 		"\ttime there is no player in a given ship, this sexpression will return 0"},
33680 
33681 	{ OP_NUM_ASSISTS, "num-assists\r\n"
33682 		"\tReturns the # of assists a player has. The ship specified in the first field should be the ship the player is in.\r\n"
33683 		"\tSo, for single player, this would be Alpha 1. For multiplayer, it can be any ship with a player in it. If, at any\r\n"
33684 		"\ttime there is no player in a given ship, this sexpression will return 0"},
33685 
33686 	{ OP_SHIP_SCORE, "ship-score\r\n"
33687 		"\tReturns the score a player has. The ship specified in the first field should be the ship the player is in.\r\n"
33688 		"\tSo, for single player, this would be Alpha 1. For multiplayer, it can be any ship with a player in it. If, at any\r\n"
33689 		"\ttime there is no player in a given ship, this sexpression will return 0"},
33690 
33691 	{ OP_SHIP_DEATHS, "ship-deaths\r\n"
33692 		"\tReturns the # times a ship that is a player start has died.\r\n"
33693 		"\tThe ship specified in the first field should be the ship that could have a player in.it\r\n"
33694 		"\tOnly really useful for multiplayer."},
33695 
33696 	{ OP_RESPAWNS_LEFT, "respawns-left\r\n"
33697 		"\tReturns the # respawns a player (or AI that could have been a player) has remaining.\r\n"
33698 		"\tThe ship specified in the first field should be the player start.\r\n"
33699 		"\tOnly really useful for multiplayer."},
33700 
33701 	{ OP_CLEAR_WEAPONS, "clear-weapons\r\n"
33702 		"\tRemoves all live weapons currently in the mission"
33703 		"\t1: (Optional) Remove only this specific class of weapon\r\n"},
33704 
33705 	{ OP_CLEAR_DEBRIS, "clear-debris\r\n"
33706 		"\tRemoves all ship debris currently in the mission"
33707 		"\t1: (Optional) Remove only debris from this specific class of ship\r\n"},
33708 
33709 	{ OP_SET_RESPAWNS, "set-respawns\r\n"
33710 		"\tSet the # respawns a player (or AI that could have been a player) has used.\r\n"
33711 		"\t1: Number of respawns used up\r\n"
33712 		"\tRest: The player start ship to operate on.\r\n"
33713 		"\tOnly really useful for multiplayer."},
33714 
33715 	{ OP_ADD_REMOVE_HOTKEY, "add-remove-hotkey\r\n"
33716 		"\tAdd or remove hotkey for the specified ship(s) or wing(s)\r\n"
33717 		"\tTakes 3 or more arguments\r\n"
33718 		"\t1: Boolean. True adds hotkey and false removes hotkey. \r\n"
33719 		"\t2: Integer of hotkey to add or remove. 0=F5, 1=F6, ... \r\n"
33720 		"\t(rest): Name(s) of ship(s) or wing(s)"},
33721 
33722 	{ OP_NUM_TYPE_KILLS, "num-type-kills\r\n"
33723 		"\tReturns the # of kills a player has on a given ship type (fighter, bomber, cruiser, etc).\r\n"
33724 		"The ship specified in the first field should be the ship the player is in.\r\n"
33725 		"\tSo, for single player, this would be Alpha 1. For multiplayer, it can be any ship with a player in it. If, at any\r\n"
33726 		"\ttime there is no player in a given ship, this sexpression will return 0"},
33727 
33728 	{ OP_NUM_CLASS_KILLS, "num-class-kills\r\n"
33729 		"\tReturns the # of kills a player has on a specific ship class (Ulysses, Hercules, etc).\r\n"
33730 		"The ship specified in the first field should be the ship the player is in.\r\n"
33731 		"\tSo, for single player, this would be Alpha 1. For multiplayer, it can be any ship with a player in it. If, at any\r\n"
33732 		"\ttime there is no player in a given ship, this sexpression will return 0"},
33733 
33734 	{ OP_BEAM_FREE, "beam-free\r\n"
33735 		"\tSets one or more beam weapons to allow firing for a given ship\r\n"
33736 		"\t1: Ship to be operated on\r\n"
33737 		"\t2, 3, etc : List of turrets to activate\r\n"},
33738 
33739 	{ OP_BEAM_FREE_ALL, "beam-free-all\r\n"
33740 		"\tSets all beam weapons on the specified ship to be active\r\n"},
33741 
33742 	{ OP_BEAM_LOCK, "beam-lock\r\n"
33743 		"\tSets one or more beam weapons to NOT allow firing for a given ship\r\n"
33744 		"\t1: Ship to be operated on\r\n"
33745 		"\t2, 3, etc : List of turrets to deactivate\r\n"},
33746 
33747 	{ OP_BEAM_LOCK_ALL, "beam-lock-all\r\n"
33748 		"\tSets all beam weapons on the specified ship to be deactivated\r\n"},
33749 
33750 	{ OP_TURRET_FREE, "turret-free\r\n"
33751 		"\tSets one or more turret weapons to allow firing for a given ship\r\n"
33752 		"\t1: Ship to be operated on\r\n"
33753 		"\t2, 3, etc : List of turrets to activate\r\n"},
33754 
33755 	{ OP_TURRET_FREE_ALL, "turret-free-all\r\n"
33756 		"\tSets all turret weapons on the specified ship to be active\r\n"},
33757 
33758 	{ OP_TURRET_LOCK, "turret-lock\r\n"
33759 		"\tSets one or more turret weapons to NOT allow firing for a given ship\r\n"
33760 		"\t1: Ship to be operated on\r\n"
33761 		"\t2, 3, etc : List of turrets to deactivate\r\n"},
33762 
33763 	{ OP_TURRET_LOCK_ALL, "turret-lock-all\r\n"
33764 		"\tSets all turret weapons on the specified ship to be deactivated\r\n"},
33765 
33766 	{ OP_TURRET_CHANGE_WEAPON, "turret-change-weapon\r\n"
33767 		"\tSets a given turret weapon slot to the specified weapon\r\n"
33768 		"\t1: Ship turret is on\r\n"
33769 		"\t2: Turret\r\n"
33770 		"\t3: Weapon to set slot to\r\n"
33771 		"\t4: Primary slot (or 0 to use secondary)\r\n"
33772 		"\t5: Secondary slot (or 0 to use primary)"},
33773 
33774 	{ OP_TURRET_SET_DIRECTION_PREFERENCE, "turret-set-direction-preference\r\n"
33775 		"\tSets specified ship turrets direction preference to the specified value\r\n"
33776 		"\t1: Ship turrets are on\r\n"
33777 		"\t2: Preference to set, 0 to disable, or negative to reset to default\r\n"
33778 		"\trest: Turrets to set\r\n"},
33779 
33780 	{ OP_TURRET_SET_RATE_OF_FIRE, "turret-set-rate-of-fire\r\n"
33781 		"\tSets specified ship turrets rate of fire to the specified value\r\n"
33782 		"\t1: Ship turrets are on\r\n"
33783 		"\t2: Rate to set in percentage format (200 = 2x, 50 = .5x), 0 to set to number of fire points, or negative to reset to default\r\n"
33784 		"\trest: Turrets to set\r\n"},
33785 
33786 	{ OP_TURRET_SET_OPTIMUM_RANGE, "turret-set-optimum-range\r\n"
33787 		"\tSets specified ship turrets optimum range to the specified value\r\n"
33788 		"\t1: Ship turrets are on\r\n"
33789 		"\t2: Priority to set, 0 to disable, or negative to reset to default\r\n"
33790 		"\trest: Turrets to set\r\n"},
33791 
33792 	{ OP_TURRET_SET_FORCED_TARGET, "turret-set-forced-target\r\n"
33793 		"\tForces one or more turrets to target a specific ship. Will hang on to this target until dead or cleared by turret-clear-forced-target\r\n"
33794 		"\tWill still not fire if the target is protected or otherwise an unsuitable target for the turret weapons\r\n"
33795 		"\t1: Ship to target\r\n"
33796 		"\t2: Ship turrets are on\r\n"
33797 		"\trest: Turrets to set\r\n" },
33798 
33799 	{ OP_TURRET_SET_FORCED_SUBSYS_TARGET, "turret-set-forced-subsys-target\r\n"
33800 		"\tForces one or more turrets to target a specific subsystem on a particular ship. Will hang on to this subsystem target until destroyed or cleared by turret-clear-forced-target\r\n"
33801 		"\tWill still not fire if the target is protected or otherwise an unsuitable target for the turret weapons\r\n"
33802 		"\t1: Ship to target\r\n"
33803 		"\t2: Subsystem to target\r\n"
33804 		"\t3: Ship turrets are on\r\n"
33805 		"\trest: Turrets to set\r\n" },
33806 
33807 	{ OP_TURRET_CLEAR_FORCED_TARGET, "turret-clear-forced-target\r\n"
33808 		"\tClears any targets set by turret-set-forced-target\r\n"
33809 		"\t1: Ship turrets are on\r\n"
33810 		"\trest: Turrets to have their targets cleared\r\n" },
33811 
33812 	{ OP_TURRET_SET_TARGET_PRIORITIES, "turret-set-target-priorities\r\n"
33813 		"\tSets target priorities for the specified ship turret\r\n"
33814 		"\t1: Ship turret is on\r\n"
33815 		"\t2: Turret to set\r\n"
33816 		"\t3: True = Set new list, False = Reset to turret default\r\n"
33817 		"\trest: Priorities to set (max 32) or blank for no priorities\r\n"},
33818 
33819 	{ OP_TURRET_SET_INACCURACY, "turret-set-inaccuracy\r\n"
33820 		"\tMakes the specified turrets more inaccurate by firing their shots in a cone, like field of fire."
33821 		"This will only decrease their accuracy, it cannot make the weapons more accurate than normal.\r\n"
33822 		"\tDoes not work on beams.\r\n"
33823 		"\t1: Ship turret(s) are on\r\n"
33824 		"\t2: TENTHS of a degree. (i.e. 100 = 10 degrees)\r\n"
33825 		"\trest: (optional) Turrets to set. If omitted affects all turrets on the ship.\r\n" },
33826 
33827 	{ OP_TURRET_GET_PRIMARY_AMMO, "turret-get-primary-ammo\r\n"
33828 		"\tGets the turret's primary bank ammo, only works with ballistic weapons\r\n"
33829 		"\t1: Ship turret is on\r\n"
33830 		"\t2: Turret the bank is on\r\n"
33831 		"\t3: Bank to check ammo\r\n"},
33832 
33833 	{ OP_TURRET_GET_SECONDARY_AMMO, "turret-get-secondary-ammo\r\n"
33834 		"\tGets the turret's secondary bank ammo\r\n"
33835 		"\t1: Ship turret is on\r\n"
33836 		"\t2: Turret to check ammo\r\n"
33837 		"\t3: Bank to check ammo\r\n" },
33838 
33839 	{ OP_TURRET_SET_PRIMARY_AMMO, "turret-set-primary-ammo\r\n"
33840 		"\tSets the turret's primary bank ammo, only works with ballistic weapons\r\n"
33841 		"\t1: Ship turret is on\r\n"
33842 		"\t2: Turret the bank is on\r\n"
33843 		"\t3: Bank to add ammo to\r\n"
33844 		"\t4: Amount to add" },
33845 
33846 	{ OP_TURRET_SET_SECONDARY_AMMO, "turret-set-secondary-ammo\r\n"
33847 		"\tSets the turret's secondary bank ammo\r\n"
33848 		"\t1: Ship turret is on\r\n"
33849 		"\t2: Turret the bank is on\r\n"
33850 		"\t3: Bank to add ammo to\r\n"
33851 		"\t4: Amount to add" },
33852 
33853 	{ OP_IS_IN_TURRET_FOV, "is-in-turret-fov\r\n"
33854 		"\tChecks whether the given craft is within the field of view of a turret, and optionally within a specified range.  Takes 3 to 4 arguments...\r\n"
33855 		"\t1: Ship being targeted\r\n"
33856 		"\t2: Ship which carries a turret\r\n"
33857 		"\t3: Turret to check\r\n"
33858 		"\t4: Range in meters (optional)\r\n" },
33859 
33860 	{ OP_SET_ARMOR_TYPE, "set-armor-type\r\n"
33861 		"\tSets the armor type for a ship or subsystem\r\n"
33862 		"\t1: Ship subsystem is on\r\n"
33863 		"\t2: Set = true/Reset to default = false\r\n"
33864 		"\t3: Armor type to set or <none>\r\n"
33865 		"\trest: Subsystems to set (hull for ship, shield for shields)\r\n"},
33866 
33867 	{ OP_WEAPON_SET_DAMAGE_TYPE, "weapon-set-damage-type\r\n"
33868 		"\tSets the damage type for weapons or their shockwaves\r\n"
33869 		"\t1: True = set weapon, False = set shockwave\r\n"
33870 		"\t2: damage type to set or <none>\r\n"
33871 		"\t3: Set = true/Reset to default = false\r\n"
33872 		"\trest: Weapons to set\r\n"},
33873 
33874 	{ OP_SHIP_SET_DAMAGE_TYPE, "ship-set-damage-type\r\n"
33875 		"\tSets the damage type for ships collision or debris\r\n"
33876 		"\t1: true = set collision, False = set debris\r\n"
33877 		"\t2: Damage type to set or <none>\r\n"
33878 		"\t3: Set = true/Reset to default = false\r\n"
33879 		"\trest: Ships to set\r\n"},
33880 
33881 	{ OP_SHIP_SHOCKWAVE_SET_DAMAGE_TYPE, "ship-set-shockwave-damage-type\r\n"
33882 		"\tSets the shockwave damage type for a class of ship.  All ships of that class are changed.\r\n"
33883 		"\t1: Damage type to set or <none>\r\n"
33884 		"\t2: Set = true/Reset to default = false\r\n"
33885 		"\trest: Ship classes to set\r\n"},
33886 
33887 	{ OP_FIELD_SET_DAMAGE_TYPE, "field-set-damage-type\r\n"
33888 		"\tSets the damage type for asteroid/debris fields\r\n"
33889 		"\t1: Damage type to set or <none>\r\n"
33890 		"\t2: Set = true/Reset to default = false\r\n"},
33891 
33892 	{ OP_TURRET_SET_TARGET_ORDER, "turret-set-target-order\r\n"
33893 		"\tSets targeting order of a given turret\r\n"
33894 		"\t1: Ship turret is on\r\n"
33895 		"\t2: Turret\r\n"
33896 		"\trest: Target order type (Bombs,ships,asteroids)"},
33897 
33898 	{ OP_SHIP_TURRET_TARGET_ORDER, "ship-turret-target-order\r\n"
33899 		"\tSets targeting order of all turrets on a given ship\r\n"
33900 		"\t1: Ship turrets are on\r\n"
33901 		"\trest: Target order type (Bombs,ships,asteroids)"},
33902 
33903 	{ OP_TURRET_SUBSYS_TARGET_DISABLE, "turret-subsys-target-disable\r\n"
33904 		"\tPrevents turrets from targeting only the subsystems when targeting large targets\r\n"
33905 		"\t1: Ship to be operated on\r\n"
33906 		"\trest: List of turrets that are affected\r\n"},
33907 
33908 	{ OP_TURRET_SUBSYS_TARGET_ENABLE, "turret-subsys-target-enable\r\n"
33909 		"\tSets turret to target the subsystems when targeting large targets\r\n"
33910 		"\t1: Ship to be operated on\r\n"
33911 		"\trest: List of turrets that are affected\r\n"},
33912 
33913 	{ OP_ADD_REMOVE_ESCORT, "add-remove-escort\r\n"
33914 		"\tAdds or removes a ship from an escort list.\r\n"
33915 		"\t1: Ship to be added or removed\r\n"
33916 		"\t2: 0 to remove from the list, any positive value will be used as the escort priority\r\n"
33917 		"NOTE : it _IS_ safe to add a ship which may already be on the list or remove\r\n"
33918 		"a ship which is not on the list\r\n"},
33919 
33920 	{ OP_AWACS_SET_RADIUS, "awacs-set-radius\r\n"
33921 		"\tSets the awacs radius for a given ship subsystem. NOTE : does not work properly in multiplayer\r\n"
33922 		"\t1: Ship which has the awacs subsystem\r\n"
33923 		"\t2: Awacs subsystem\r\n"
33924 		"\t3: New radius\r\n"},
33925 
33926 	// Goober5000
33927 	{ OP_PRIMITIVE_SENSORS_SET_RANGE, "primitive-sensors-set-range\r\n"
33928 		"\tSets the range of the primitive sensors on a ship.  Ships outside of this range will not appear on "
33929 		"sensors.  Has no effect on ships that do not have the \"primitive-sensors\" flag2 set.  Takes 2 arguments...\r\n"
33930 		"\t1: Ship on which to set range\r\n"
33931 		"\t2: Range, in meters\r\n" },
33932 
33933 	{ OP_SEND_MESSAGE_LIST, "send-message-list\r\n"
33934 		"\tSends a series of delayed messages. All times are accumulated. Takes arguments in multiples of 4.\r\n"
33935 		"\t1:\tName of who the message is from.\r\n"
33936 		"\t2:\tPriority of message (\"Low\", \"Normal\" or \"High\").\r\n"
33937 		"\t3:\tName of message (from message editor).\r\n"
33938 		"\t4:\tDelay from previous message in list (if any) in ms\r\n"
33939 		"Use Add-Data for multiple messages.\r\n\r\n"
33940 		"IMPORTANT: Each additional message in the list MUST HAVE four entries; "
33941 		"any message without the four proper fields will be ignored, as will any "
33942 		"successive messages.\r\n\r\n"
33943 		"\tHigh priority are not interrupted by anything, and will be sent by Command if the sender is destroyed.\r\n"
33944 		"\tNormal priority takes precedence over builtin messages, but will not be sent if the sender is destroyed.\r\n"
33945 		"\tLow priority are not sent if the sender's communication subsystem is destroyed, and may be interrupted.\r\n"
33946 	},
33947 
33948 	{ OP_SEND_MESSAGE_CHAIN, "send-message-chain\r\n"
33949 		"\tThis works just like send-message-list, but if the event specified in the first argument becomes true, all un-played messages are cancelled.\r\n"
33950 		"\t1:\tThe event that will cancel the message chain.\r\n"
33951 		"\t2:\tName of who the message is from.\r\n"
33952 		"\t3:\tPriority of message (\"Low\", \"Normal\" or \"High\").\r\n"
33953 		"\t4:\tName of message (from message editor).\r\n"
33954 		"\t5:\tDelay from previous message in list (if any) in ms\r\n"
33955 		"Use Add-Data for multiple messages.\r\n\r\n"
33956 		"IMPORTANT: Each additional message in the list MUST HAVE four entries; "
33957 		"any message without the four proper fields will be ignored, as will any "
33958 		"successive messages.\r\n\r\n"
33959 		"\tHigh priority are not interrupted by anything, and will be sent by Command if the sender is destroyed.\r\n"
33960 		"\tNormal priority takes precedence over builtin messages, but will not be sent if the sender is destroyed.\r\n"
33961 		"\tLow priority are not sent if the sender's communication subsystem is destroyed, and may be interrupted.\r\n"
33962 	},
33963 
33964 	{ OP_CAP_WAYPOINT_SPEED, "cap-waypoint-speed\r\n"
33965 		"\tSets the maximum speed of a ship while flying waypoints.\r\n"
33966 		"\t1: Ship name\r\n"
33967 		"\t2: Maximum speed while flying waypoints (must be greater than 0)\r\n"
33968 		"\tNOTE: This will only work if the ship is already in the game\r\n"
33969 		"\tNOTE: Set to -1 to reset\r\n"},
33970 
33971 	{ OP_TURRET_TAGGED_ONLY_ALL, "turret-tagged-only\r\n"
33972 		"\tMakes turrets target and hence fire strictly at tagged objects\r\n"
33973 		"\tAll: Ship name\r\n"
33974 		"\tNOTE: Will not stop a turret already firing at an untagged ship\r\n"},
33975 
33976 	{ OP_TURRET_TAGGED_CLEAR_ALL, "turret-tagged-clear\r\n"
33977 		"\tRelaxes restriction on turrets targeting only tagged ships\r\n"
33978 		"\tAll: Ship name\r\n"},
33979 
33980 	{ OP_PRIMARIES_DEPLETED, "primaries-depleted\r\n"
33981 		"\tReturns true if ship is out of primary weapons\r\n"
33982 		"\t1: Ship name\r\n"},
33983 
33984 	{ OP_SECONDARIES_DEPLETED, "secondaries-depleted\r\n"
33985 		"\tReturns true if ship is out of secondary weapons\r\n"
33986 		"\t1: Ship name\r\n"},
33987 
33988 	{ OP_SUBSYS_SET_RANDOM, "subsys-set-random\r\n"
33989 		"\tSets ship subsystem strength in a given range\r\n"
33990 		"\t1: Ship name\r\n"
33991 		"\t2: Low range\r\n"
33992 		"\t3: High range\r\n"
33993 		"\t4: List of subsys names not to be randomized\r\n"},
33994 
33995 	{ OP_SUPERNOVA_START, "supernova-start\r\n"
33996 		"\t1: Time in seconds until the supernova shockwave hits the player\r\n"},
33997 
33998 	{ OP_SUPERNOVA_STOP, "supernova-stop\r\n"
33999 		"\t Stops a supernova in progress.\r\n"
34000 		"\t Note this only works if the camera hasn't cut to the player's death animation yet.\r\n"},
34001 
34002 	{ OP_WEAPON_RECHARGE_PCT, "weapon-recharge-pct\r\n"
34003 		"\tReturns a percentage from 0 to 100\r\n"
34004 		"\t1: Ship name\r\n" },
34005 
34006 	{ OP_SHIELD_RECHARGE_PCT, "shield-recharge-pct\r\n"
34007 		"\tReturns a percentage from 0 to 100\r\n"
34008 		"\t1: Ship name\r\n" },
34009 
34010 	{ OP_ENGINE_RECHARGE_PCT, "engine-recharge-pct\r\n"
34011 		"\tReturns a percentage from 0 to 100\r\n"
34012 		"\t1: Ship name\r\n" },
34013 
34014 	{ OP_GET_ETS_VALUE, "get-ets-value\r\n"
34015 		"\tGets one ETS index for a ship\r\n"
34016 		"\tIndex values are used in a hard-coded array to lookup recharge percentages\r\n"
34017 		"\tEach index has a valid range of 0 to 12\r\n"
34018 		"\t1: ETS index to get, Engine|Shield|Weapon\r\n"
34019 		"\t2: Ship name\r\n"},
34020 
34021 	{ OP_SET_ETS_VALUES, "set-ets-values\r\n"
34022 		"\tSets ETS indexes for a ship\r\n"
34023 		"\tIndex values are used in a hard-coded array to lookup recharge percentages\r\n"
34024 		"\tEach index has a valid range of 0 to 12\r\n"
34025 		"\tUse index values retrieved with get-ets-value\r\n"
34026 		"\tOr if you use your own values, ensure they add up to 12 (e.g. 0,3,9)\r\n"
34027 		"\t1: Engine index\r\n"
34028 		"\t2: Shields index\r\n"
34029 		"\t3: Weapons index\r\n"
34030 		"\t4: Ship name\r\n"},
34031 
34032 	{ OP_GET_POWER_OUTPUT, "get-power-output\r\n"
34033 		"\tGets the power output rating of a ship\r\n"
34034 		"\t1: Ship name"
34035 	},
34036 
34037 	{ OP_CARGO_NO_DEPLETE, "cargo-no-deplete\r\n"
34038 		"\tCauses the named ship to have unlimited cargo.\r\n"
34039 		"\tNote:  only applies to BIG or HUGE ships\r\n"
34040 		"Takes 1 or more arguments...\r\n"
34041 		"\t1:\tName of one of the ships.\r\n"
34042 		"\t2:\toptional: 1 disallow depletion, 0 allow depletion." },
34043 
34044 	{ OP_SHIELD_QUAD_LOW, "shield-quad-low\r\n"
34045 		"\tReturns true if the specified ship has a shield quadrant below\r\n"
34046 		"\tthe specified threshold percentage\r\n"
34047 		"\t1: Ship name\r\n"
34048 		"\t2: Percentage\r\n" },
34049 
34050 	{ OP_PRIMARY_AMMO_PCT, "primary-ammo-pct\r\n"
34051 		"\tReturns the percentage of ammo remaining in the specified ballistic primary bank (0 to 100).  Non-ballistic primary banks return as 100%.\r\n"
34052 		"\t1: Ship name\r\n"
34053 		"\t2: Bank to check (from 0 to N-1, where N is the number of primary banks in the ship; N or higher will return the cumulative percentage for all ballistic banks)" },
34054 
34055 	// Karajorma
34056 	{ OP_GET_PRIMARY_AMMO, "get-primary-ammo\r\n"
34057 		"\tReturns the amount of ammo remaining in the specified ballistic primary bank.  Non-ballistic primary banks return as 0.\r\n"
34058 		"\t1: Ship name\r\n"
34059 		"\t2: Bank to check (from 0 to N-1, where N is the number of primary banks in the ship; N or higher will return the cumulative total for all ballistic banks)" },
34060 
34061 	{ OP_SECONDARY_AMMO_PCT, "secondary-ammo-pct\r\n"
34062 		"\tReturns the percentage of ammo remaining in the specified bank (0 to 100)\r\n"
34063 		"\t1: Ship name\r\n"
34064 		"\t2: Bank to check (from 0 to N-1, where N is the number of secondary banks in the ship; N or higher will return the cumulative percentage for all banks)" },
34065 
34066 	// Karajorma
34067 	{ OP_GET_SECONDARY_AMMO, "get-secondary-ammo\r\n"
34068 		"\tReturns the amount of ammo remaining in the specified bank\r\n"
34069 		"\t1: Ship name\r\n"
34070 		"\t2: Bank to check (from 0 to N-1, where N is the number of secondary banks in the ship; N or higher will return the cumulative total for all banks)" },
34071 
34072 	// Karajorma
34073 	{ OP_GET_NUM_COUNTERMEASURES, "get-num-countermeasures\r\n"
34074 		"\tReturns the amount of countermeasures remaining\r\n"
34075 		"\t1: Ship name\r\n" },
34076 
34077 	{ OP_IS_SECONDARY_SELECTED, "is-secondary-selected\r\n"
34078 		"\tReturns true if the specified bank is selected\r\n"
34079 		"\t1: Ship name\r\n"
34080 		"\t2: Bank to check (This is a zero-based index. The first bank is numbered 0.)\r\n"},
34081 
34082 	{ OP_IS_PRIMARY_SELECTED, "is-primary-selected\r\n"
34083 		"\tReturns true if the specified bank is selected\r\n"
34084 		"\t1: Ship name\r\n"
34085 		"\t2: Bank to check (This is a zero-based index. The first bank is numbered 0.)\r\n"},
34086 
34087 	{ OP_SPECIAL_WARP_DISTANCE, "special-warp-dist\r\n"
34088 		"\tReturns distance to the plane of the knossos device in percent length of ship\r\n"
34089 		"\t(ie, 100 means front of ship is 1 ship length from plane of knossos device)\r\n"
34090 		"\t1: Ship name\r\n"},
34091 
34092 	{ OP_SET_SPECIAL_WARPOUT_NAME, "special-warpout-name\r\n"
34093 		"\tSets the name of the knossos device to be used for warpout\r\n"
34094 		"\t1: Ship name to exit\r\n"
34095 		"\t2: Name of knossos device\r\n"},
34096 
34097 	{ OP_SHIP_VANISH, "ship-vanish\r\n"
34098 		"\tMakes the named ship vanish (no log and vanish)\r\n"
34099 		"\tSingle Player Only!  Warning: This will cause ship exit not to be logged, so 'has-departed', etc. will not work\r\n"
34100 		"\t1: List of ship names to vanish\r\n"},
34101 
34102 	{ OP_DESTROY_INSTANTLY, "destroy-instantly\r\n"
34103 		"\tSelf-destructs the named ship without explosion, death roll, or debris.  That is, the ship is instantly gone from the mission and the only indication of what happened is a mission log entry.\r\n"
34104 		"\tNon-player ship only!\r\n"
34105 		"\tAll: List of ship names to destroy.\r\n"},
34106 
34107 	{ OP_SHIP_CREATE, "ship-create\r\n"
34108 		"\tCreates a new ship\r\n"
34109 		"\tTakes 5 to 8 arguments...\r\n"
34110 		"\t1: Name of new ship (use \"" SEXP_NONE_STRING "\" for a default name)\r\n"
34111 		"\t2: Class of new ship\r\n"
34112 		"\t3: X position\r\n"
34113 		"\t4: Y position\r\n"
34114 		"\t5: Z position\r\n"
34115 		"\t6: Pitch (optional)\r\n"
34116 		"\t7: Bank (optional)\r\n"
34117 		"\t8: Heading (optional)\r\n"
34118 		"\t9: Team (optional)\r\n"
34119 	},
34120 
34121 	// Goober5000
34122 	{ OP_WEAPON_CREATE, "weapon-create\r\n"
34123 		"\tCreates a new weapon\r\n"
34124 		"\tTakes 5 to 10 arguments...\r\n"
34125 		"\t 1: Name of parent ship (or \"" SEXP_NONE_STRING "\" for no parent)\r\n"
34126 		"\t 2: Class of new weapon\r\n"
34127 		"\t 3: X position\r\n"
34128 		"\t 4: Y position\r\n"
34129 		"\t 5: Z position\r\n"
34130 		"\t 6: Pitch (optional)\r\n"
34131 		"\t 7: Bank (optional)\r\n"
34132 		"\t 8: Heading (optional)\r\n"
34133 		"\t 9: Targeted ship (optional)\r\n"
34134 		"\t10: Targeted subsystem (optional)\r\n"
34135 	},
34136 
34137 	{ OP_IS_SHIP_VISIBLE, "is-ship-visible\r\n"
34138 		"\tCheck whether ship is visible on a certain ship's radar.  Returns 0 - not visible, 1 - partially visible, 2 - fully visible.\r\n"
34139 		"\tNote: In multiplayer, the second argument *must* be supplied or this SEXP will default to the first player.\r\n"
34140 		"\tTakes 1 or 2 arguments...\r\n"
34141 		"\t1: Name of ship to check\r\n"
34142 		"\t2 (optional): Name of the viewing ship.  Defaults to the player's ship in single-player mode.  If this ship is not in-mission, the SEXP will return 0.\r\n" },
34143 
34144 	// Goober5000
34145 	{ OP_IS_SHIP_STEALTHY, "is-ship-stealthy\r\n"
34146 		"\tCheck whether ship is currently stealthy.\r\n"
34147 		"\tTrue if stealth flag set, false otherwise.  Takes 1 argument...\r\n"
34148 		"\t1: Name of ship to check\r\n"},
34149 
34150 	// Goober5000
34151 	{ OP_IS_FRIENDLY_STEALTH_VISIBLE, "is-friendly-stealth-visible\r\n"
34152 		"\tCheck whether ship will be visible to radar as a stealth friendly.\r\n"
34153 		"\tTakes 1 argument...\r\n"
34154 		"\t1: Name of ship to check\r\n"},
34155 
34156 	{ OP_TEAM_SCORE, "team-score\r\n"
34157 		"\tGet the score of a multi team vs team game.\r\n"
34158 		"\t1: Team index (1 for team 1 and 2 for team 2)\r\n"},
34159 
34160 	// phreak
34161 	{ OP_DAMAGED_ESCORT_LIST, "damaged-escort-list\r\n"
34162 		"\tSets the most damaged ship in <ship list> to <priority1>, sets the others to <priority2>.  Don't use this sexp in the same mission as damaged-escort-list-all, or strange results might occur.\r\n"
34163 		"\t1: Priority1\r\n"
34164 		"\t2: Priority2\r\n"
34165 		"\tRest: <ship_list>\r\n\r\n"
34166 	},
34167 
34168 	// Goober5000
34169 	{ OP_DAMAGED_ESCORT_LIST_ALL, "damaged-escort-list-all\r\n"
34170 		"\tSets the most damaged ship in the entire existing escort list (even what's not shown onscreen) to <priority1>, the next most damaged to <priority2>, and so on.  "
34171 		"If there are more ships than priorities, the least most damaged ships are all set to the last priority in the list.  Don't use this sexp in the same mission as damaged-escort-list, or strange results might occur.\r\n"
34172 		"\tTakes between 1 and MAX_COMPLETE_ESCORT_LIST (currently 20) arguments...\r\n"
34173 		"\t1: Priority 1\r\n"
34174 		"\tRest: Priorities 2 through 20 (optional)\r\n\r\n"
34175 	},
34176 
34177 	// Goober5000
34178 	{ OP_CHANGE_SHIP_CLASS, "change-ship-class\r\n"
34179 		"\tCauses the listed ships' classes to be changed to the specified ship class.  Takes 2 or more arguments...\r\n"
34180 		"\t1: The name of the new ship class\r\n"
34181 		"\tRest: The list of ships to change the classes of"
34182 	},
34183 
34184 	// Goober5000
34185 	{ OP_SHIP_COPY_DAMAGE, "ship-copy-damage\r\n"
34186 		"\tCopies the damage (hull, shields, and subsystems) from the first ship in the list to the rest.  The initial ship must be currently "
34187 		"present in the mission, but the target ships may be either in-mission or on the arrival list.  Takes 2 or more arguments...\r\n"
34188 		"\t1: The name of the ship that supplies the damage stats\r\n"
34189 		"\tRest: The list of ships to be modified"
34190 	},
34191 
34192 	// Goober5000
34193 	{ OP_SET_SUPPORT_SHIP, "set-support-ship\r\n"
34194 		"\tSets information for all support ships in a mission.  Takes 6 or 7 arguments...\r\n"
34195 		"\t1: Arrival location\r\n"
34196 		"\t2: Arrival anchor\r\n"
34197 		"\t3: Departure location\r\n"
34198 		"\t4: Departure anchor\r\n"
34199 		"\t5: Ship class\r\n"
34200 		"\t6: Maximum number of support ships consecutively in this mission (use a negative number for infinity)\r\n"
34201 		"\t7: Maximum number of support ships concurrently in this mission (optional, default 1)\r\n"
34202 		"\r\n"
34203 		"Note: The support ship will emerge from or depart into hyperspace if the location is set as hyperspace *or* the anchor is set as <no anchor>."
34204 	},
34205 
34206 	// Goober5000
34207 	{ OP_SET_ARRIVAL_INFO, "set-arrival-info\r\n"
34208 		"\tSets arrival information for a ship or wing.  Takes 2 to 7 arguments...\r\n"
34209 		"\t1: Ship or wing name\r\n"
34210 		"\t2: Arrival location\r\n"
34211 		"\t3: Arrival anchor (optional; only required for certain locations)\r\n"
34212 		"\t4: Arrival path mask (optional; this is a bitfield where the bits set to 1 correspond to the paths to use; defaults to 0 which is a special case that means all paths can be used)\r\n"
34213 		"\t5: Arrival distance (optional; defaults to 0)\r\n"
34214 		"\t6: Arrival delay (optional; defaults to 0)\r\n"
34215 		"\t7: Whether to show a jump effect if arriving from subspace (optional; defaults to true)\r\n"
34216 	},
34217 
34218 	// Goober5000
34219 	{ OP_SET_DEPARTURE_INFO, "set-departure-info\r\n"
34220 		"\tSets departure information for a ship or wing.  Takes 2 to 6 arguments...\r\n"
34221 		"\t1: Ship or wing name\r\n"
34222 		"\t2: Departure location\r\n"
34223 		"\t3: Departure anchor (optional; only required for certain locations)\r\n"
34224 		"\t4: Departure path mask (optional; this is a bitfield where the bits set to 1 correspond to the paths to use; defaults to 0 which is a special case that means all paths can be used)\r\n"
34225 		"\t5: Departure delay (optional; defaults to 0)\r\n"
34226 		"\t6: Whether to show a jump effect if departing to subspace (optional; defaults to true)\r\n"
34227 	},
34228 
34229 	// Bobboau
34230 	{ OP_DEACTIVATE_GLOW_POINTS, "deactivate-glow-points\r\n"
34231 		"\tDeactivates all glow points on a ship.  Takes 1 or more arguments...\r\n"
34232 		"\tAll: Name of ship on which to deactivate glow points\r\n"
34233 	},
34234 
34235 	// Bobboau
34236 	{ OP_ACTIVATE_GLOW_POINTS, "activate-glow-points\r\n"
34237 		"\tActivates all glow points on a ship.  Takes 1 or more arguments...\r\n"
34238 		"\tAll: Name of ship on which to activate glow points\r\n"
34239 	},
34240 
34241 	// Bobboau
34242 	{ OP_DEACTIVATE_GLOW_MAPS, "deactivate-glow-maps\r\n"
34243 		"\tDeactivates the glow maps for a ship.  Takes 1 or more arguments...\r\n"
34244 		"\tAll: Name of ship on which to deactivate glow maps\r\n"
34245 	},
34246 
34247 	// Bobboau
34248 	{ OP_ACTIVATE_GLOW_MAPS, "activate-glow-maps\r\n"
34249 		"\tActivates the glow maps for a ship.  Takes 1 or more arguments...\r\n"
34250 		"\tAll: Name of ship on which to activate glow maps\r\n"
34251 	},
34252 
34253 	// Bobboau
34254 	{ OP_DEACTIVATE_GLOW_POINT_BANK, "deactivate-glow-point-bank\r\n"
34255 		"\tDeactivates one or more glow point bank(s) on a ship.  Takes 2 or more arguments...\r\n"
34256 		"\t1: Name of ship on which to deactivate glow point bank(s)\r\n"
34257 		"\tRest: Name of glow point bank to deactivate\r\n"
34258 	},
34259 
34260 	// Bobboau
34261 	{ OP_ACTIVATE_GLOW_POINT_BANK, "activate-glow-point-bank\r\n"
34262 		"\tActivates one or more glow point bank(s) on a ship.  Takes 2 or more arguments...\r\n"
34263 		"\t1: Name of ship on which to activate glow point bank(s)\r\n"
34264 		"\tRest: Name of glow point bank to activate\r\n"
34265 	},
34266 
34267 	//-Sesquipedalian
34268 	{ OP_KAMIKAZE, "kamikaze\r\n"
34269 		"\tTells ships to perform a kamikaze on its current target. Takes 2 or more arguments...\r\n"
34270 		"\t1: Damage dealt when kamikaze is done\r\n"
34271 		"\tRest: Names of ships to perform kamikaze\r\n"
34272 	},
34273 
34274 	//phreak
34275 	{ OP_TURRET_TAGGED_SPECIFIC, "turret-tagged-specific\r\n"
34276 		"\tSpecific turrets on a ship only fire at tagged targets, as opposed to all turrets doing this using turret-tagged-only\r\n"
34277 		"\tIt is safe to slave turrets already slaved\r\n"
34278 		"\tTakes 2 or more arguments...\r\n"
34279 		"\t1: Name of ship to slave some turrets to target only tagged ships\r\n"
34280 		"\tRest: Turrets to slave\r\n"
34281 	},
34282 
34283 	//phreak
34284 	{ OP_TURRET_TAGGED_CLEAR_SPECIFIC, "turret-tagged-clear-specific\r\n"
34285 		"\tSpecific turrets on a ship are free to fire on untagged ships, as opposed to all turrets doing this using turret-tagged-clear\r\n"
34286 		"\tIt is safe to unslave turrets already free\r\n"
34287 		"\tTakes 2 or more arguments...\r\n"
34288 		"\t1: Name of ship to unslave some turrets to target any hostile ship\r\n"
34289 		"\tRest: Turrets to unslave\r\n"
34290 	},
34291 
34292 	// Goober5000
34293 	{ OP_LOCK_ROTATING_SUBSYSTEM, "lock-rotating-subsystem\r\n"
34294 		"\tInstantaneously locks a rotating subsystem so that it cannot rotate unless freed by free-rotating-subsystem.  "
34295 		"Takes 2 or more arguments...\r\n"
34296 		"\t1:\tName of the ship housing the subsystem\r\n"
34297 		"\tRest:\tName of the rotating subsystem to lock"
34298 	},
34299 
34300 	// Goober5000
34301 	{ OP_FREE_ROTATING_SUBSYSTEM, "free-rotating-subsystem\r\n"
34302 		"\tInstantaneously frees a rotating subsystem previously locked by lock-rotating-subsystem.  "
34303 		"Takes 2 or more arguments...\r\n"
34304 		"\t1:\tName of the ship housing the subsystem\r\n"
34305 		"\tRest:\tName of the rotating subsystem to free"
34306 	},
34307 
34308 	// Goober5000
34309 	{ OP_REVERSE_ROTATING_SUBSYSTEM, "reverse-rotating-subsystem\r\n"
34310 		"\tInstantaneously reverses the rotation direction of a rotating subsystem.  "
34311 		"Takes 2 or more arguments...\r\n"
34312 		"\t1:\tName of the ship housing the subsystem\r\n"
34313 		"\tRest:\tName of the rotating subsystem to reverse"
34314 	},
34315 
34316 	// Goober5000
34317 	{ OP_ROTATING_SUBSYS_SET_TURN_TIME, "rotating-subsys-set-turn-time\r\n"
34318 		"\tSets the turn time of a rotating subsystem.  "
34319 		"Takes 3 or 4 arguments...\r\n"
34320 		"\t1:\tName of the ship housing the subsystem\r\n"
34321 		"\t2:\tName of the rotating subsystem to configure\r\n"
34322 		"\t3:\tThe time for one complete rotation, in milliseconds (positive is counterclockwise, negative is clockwise)\r\n"
34323 		"\t4:\tThe acceleration (x1000, just as #3 is seconds x1000) to change from the current turn rate to the desired turn rate.  "
34324 		"This is actually the time to complete one rotation that changes in one second, or the reciprocal of what you might expect, "
34325 		"meaning that larger numbers cause slower acceleration.  (FS2 defaults to 2pi/0.5, or about 12.566, which would be 12566 in this sexp.)  "
34326 		"The advantage of this method is so that this argument can be directly compared to the previous argument using a ratio, without worrying about pi.  "
34327 		"Omit this argument if you want an instantaneous change."
34328 	},
34329 
34330 	// Goober5000
34331 	{ OP_TRIGGER_SUBMODEL_ANIMATION, "trigger-submodel-animation\r\n"
34332 		"\tActivates a submodel animation trigger for a given ship.  Takes 4 to 6 arguments...\r\n"
34333 		"\t1: The ship on which the animation should run\r\n"
34334 		"\t2: The type of animation (named as one would see them in ships.tbl)\r\n"
34335 		"\t3: The subtype of animation, which is type-dependent.  For docking animations this is the dock index.\r\n"
34336 		"\t4: The animation direction: 1 for forward, or -1 for reverse\r\n"
34337 		"\t5: (Optional) Whether the animation should instantly snap to its final position\r\n"
34338 		"\t6: (Optional) A subsystem, if the animation should trigger on only a specific subsystem as opposed to all applicable subsystems\r\n"
34339 	},
34340 
34341 	// Karajorma
34342 	{ OP_SET_PRIMARY_AMMO, "set-primary-ammo\r\n"
34343 		"\tSets the amount of ammo for the specified ballistic bank\r\n"
34344 		"\t1: Ship name\r\n"
34345 		"\t2: Bank to check (0, 1, and 2 are legal banks)\r\n"
34346 		"\t3: Number to set this bank to (If this is larger than the maximum, bank will be set to maximum).\r\n"
34347 		"\t4: Rearm Limit. Support ships will only supply this number of weapons (If this is larger than the maximum, bank will be set to maximum)"
34348 	},
34349 
34350 	// Karajorma
34351 	{ OP_SET_SECONDARY_AMMO, "set-secondary-ammo\r\n"
34352 		"\tSets the amount of ammo for the specified bank\r\n"
34353 		"\t1: Ship name\r\n"
34354 		"\t2: Bank to check (0, 1, 2 and 3 are legal banks)\r\n"
34355 		"\t3: Number to set this bank to (If this is larger than the maximum, bank will be set to maximum).\r\n"
34356 		"\t4: Rearm Limit. Support ships will only supply this number of weapons (If this is larger than the maximum, bank will be set to maximum)"
34357 	},
34358 
34359 	// Karajorma
34360 	{ OP_SET_PRIMARY_WEAPON, "set-primary-weapon\r\n"
34361 		"\tSets the weapon for the specified bank\r\n"
34362 		"\t1: Ship name\r\n"
34363 		"\t2: Bank to check (0, 1 and 2 are legal banks)\r\n"
34364 		"\t3: Name of the primary weapon \r\n"
34365 		"\t4: Number to set ammo of this bank to (If this is larger than the maximum, bank will be set to maximum)\r\n"
34366 		"\t5: Rearm Limit. Support ships will only supply this number of weapons (If this is larger than the maximum, bank will be set to maximum)"
34367 	},
34368 
34369 	// Karajorma
34370 	{ OP_SET_SECONDARY_WEAPON, "set-secondary-weapon\r\n"
34371 		"\tSets the weapon for the specified bank\r\n"
34372 		"\t1: Ship name\r\n"
34373 		"\t2: Bank to check (0, 1, 2 and 3 are legal banks)\r\n"
34374 		"\t3: Name of the secondary weapon \r\n"
34375 		"\t4: Number to set ammo of this bank to (If this is larger than the maximum, bank will be set to maximum)\r\n"
34376 		"\t5: Rearm Limit. Support ships will only supply this number of weapons (If this is larger than the maximum, bank will be set to maximum)"
34377 	},
34378 
34379 
34380 
34381 	// Karajorma
34382 	{ OP_SET_NUM_COUNTERMEASURES, "set-num-countermeasures\r\n"
34383 		"\tSets the number of countermeasures the ship has\r\n"
34384 		"\tValues greater than the maximum a ship can carry are set to the maximum\r\n"
34385 		"\t1: Ship name\r\n"
34386 		"\t2: Number to set"
34387 	},
34388 
34389 	// Karajorma
34390 	{ OP_LOCK_PRIMARY_WEAPON, "lock-primary-weapon\r\n"
34391 		"\tLocks the primary banks for the specified ship(s)\r\n"
34392 		"\tTakes 1 or more arguments\r\n"
34393 		"\t(all): Name(s) of ship(s) to lock"
34394 	},
34395 
34396 	// Karajorma
34397 	{ OP_UNLOCK_PRIMARY_WEAPON, "unlock-primary-weapon\r\n"
34398 		"\tUnlocks the primary banks for the specified ship(s)\r\n"
34399 		"\tTakes 1 or more arguments\r\n"
34400 		"\t(all): Name(s) of ship(s) to lock"
34401 	},
34402 
34403 	// Karajorma
34404 	{ OP_LOCK_SECONDARY_WEAPON, "lock-secondary-weapon\r\n"
34405 		"\tLocks the secondary banks for the specified ship(s)\r\n"
34406 		"\tTakes 1 or more arguments\r\n"
34407 		"\t(all): Name(s) of ship(s) to lock"
34408 	},
34409 
34410 	// Karajorma
34411 	{ OP_UNLOCK_SECONDARY_WEAPON, "unlock-secondary-weapon\r\n"
34412 		"\tUnlocks the secondary banks for the specified ship(s)\r\n"
34413 		"\tTakes 1 or more arguments\r\n"
34414 		"\t(all): Name(s) of ship(s) to lock"
34415 	},
34416 
34417 	// KeldorKatarn
34418 	{ OP_LOCK_AFTERBURNER, "lock-afterburner\r\n"
34419 		"\tLocks the afterburners on the specified ship(s)\r\n"
34420 		"\tTakes 1 or more arguments\r\n"
34421 		"\t(all): Name(s) of ship(s) to lock"
34422 	},
34423 
34424 	// KeldorKatarn
34425 	{ OP_UNLOCK_AFTERBURNER, "unlock-afterburner\r\n"
34426 		"\tUnlocks the afterburners on the specified ship(s)\r\n"
34427 		"\tTakes 1 or more arguments\r\n"
34428 		"\t(all): Name(s) of ship(s) to lock"
34429 	},
34430 
34431 	// Karajorma
34432 	{ OP_SET_AFTERBURNER_ENERGY, "set-afterburner-energy\r\n"
34433 		"\tSets the afterburner energy for the specified ship(s)\r\n"
34434 		"\tTakes 2 or more arguments\r\n"
34435 		"\t1: percentage of maximum afterburner energy.\r\n"
34436 		"\t(rest): Name(s) of ship(s)"
34437 	},
34438 
34439 	{ OP_SET_WEAPON_ENERGY, "set-weapon-energy\r\n"
34440 		"\tSets the weapon energy for the specified ship(s)\r\n"
34441 		"\tTakes 2 or more arguments\r\n"
34442 		"\t1: percentage of maximum weapon energy.\r\n"
34443 		"\t(rest): Name(s) of ship(s)"
34444 	},
34445 
34446 	{ OP_SET_SHIELD_ENERGY, "set-shield-energy\r\n"
34447 		"\tSets the shield energy for the specified ship(s)\r\n"
34448 		"\tTakes 2 or more arguments\r\n"
34449 		"\t1: percentage of maximum shield energy.\r\n"
34450 		"\t(rest): Name(s) of ship(s)"
34451 	},
34452 
34453 	{ OP_SET_AMBIENT_LIGHT, "set-ambient-light\r\n"
34454 		"\tSets the ambient light level for the mission\r\n"
34455 		"\tTakes 3 arguments\r\n"
34456 		"\t1: Red (0 - 255).\r\n"
34457 		"\t2: Green (0 - 255).\r\n"
34458 		"\t3: Blue (0 - 255)."
34459 	},
34460 
34461 	{ OP_SET_POST_EFFECT, "set-post-effect\r\n"
34462 		"\tConfigures a post-processing effect.  Takes 2 arguments...\r\n"
34463 		"\t1: Effect type\r\n"
34464 		"\t2: Effect intensity (0 - 100)."
34465 		"\t3: (Optional) Red (0 - 255)."
34466 		"\t4: (Optional) Green (0 - 255)."
34467 		"\t5: (Optional) Blue (0 - 255)."
34468 	},
34469 
34470 	{ OP_RESET_POST_EFFECTS, "reset-post-effects\r\n"
34471 		"\tResets all post-processing effects to their default values.  Takes no arguments.\r\n"
34472 	},
34473 
34474 	{ OP_CHANGE_SUBSYSTEM_NAME, "change-subsystem-name\r\n"
34475 		"\tChanges the name of the specified subsystem on the specified ship\r\n"
34476 		"\tTakes 3 or more arguments\r\n"
34477 		"\t1: Name of the ship.\r\n"
34478 		"\t2: New name for the subsystem (names larger than the maximum display size will be truncated\r\n"
34479 		"\t3: Name(s) of subsystem(s) to rename\r\n"
34480 	},
34481 
34482 	//phreak
34483 	{ OP_NUM_SHIPS_IN_BATTLE, "num-ships-in-battle\r\n"
34484 		"\tReturns the number of ships in battle or the number of ships in battle out of a list of teams, wings, and ships.  Takes 1 or more arguments...\r\n"
34485 		"\t(all):\tTeams, Wings, and Ships to query (optional)"
34486 	},
34487 
34488 	// Karajorma
34489 	{ OP_NUM_SHIPS_IN_WING, "num-ships-in-wing\r\n"
34490 		"\tReturns the number of ships in battle which belong to a given wing.  Takes 1 or more arguments...\r\n"
34491 		"\t(all):\tName of wing(s) to check"
34492 	},
34493 
34494 	// Goober5000
34495 	{ OP_HUD_DISABLE, "hud-disable\r\n"
34496 		"\tSets whether the hud is disabled.  Takes 1 argument...\r\n"
34497 		"\t1: Flag (1 to disable, 0 to re-enable)"
34498 	},
34499 
34500 	// Goober5000
34501 	{ OP_HUD_DISABLE_EXCEPT_MESSAGES, "hud-disable-except-messages\r\n"
34502 		"\tSets whether the hud (except for messages) is disabled.  Takes 1 argument...\r\n"
34503 		"\t1: Flag (1 to disable, 0 to re-enable)"
34504 	},
34505 
34506 	// Goober5000
34507 	{ OP_HUD_SET_MAX_TARGETING_RANGE, "hud-set-max-targeting-range\r\n"
34508 		"\tSets the farthest distance at which an object can be targeted.  Takes 1 argument...\r\n"
34509 		"\1: Maximum targeting distance (0 for infinite)\r\n"
34510 	},
34511 
34512 	{ OP_HUD_DISPLAY_GAUGE, "hud-display-gauge <milliseconds> <gauge>\r\n"
34513 		"\tCauses specified hud gauge to appear or disappear for so many milliseconds.  Takes 1 argument...\r\n"
34514 		"\t1: Number of milliseconds that the warpout gauge should appear on the HUD."
34515 		" 0 will immediately cause the gauge to disappear.\r\n"
34516 		"\t2: Name of HUD element.  Must be one of:\r\n"
34517 		"\t\t" SEXP_HUD_GAUGE_WARPOUT " - the \"Subspace drive active\" box that appears above the viewscreen.\r\n"
34518 	},
34519 
34520 	// Goober5000
34521 	{ OP_PLAYER_USE_AI, "player-use-ai\r\n"
34522 		"\tCauses the player's ship to be controlled by the FreeSpace AI.  Takes 0 arguments.\r\n"
34523 	},
34524 
34525 	// Goober5000
34526 	{ OP_PLAYER_NOT_USE_AI, "player-not-use-ai\r\n"
34527 		"\tCauses the player's ship to not be controlled by the FreeSpace AI.  Takes 0 arguments.\r\n"
34528 	},
34529 
34530 	// Karajorma
34531 	{ OP_ALLOW_TREASON, "allow-treason\r\n"
34532 		"\tTurns the Allow Traitor switch on or off in mission. Takes 0 arguments.\r\n"
34533 		"\t1:\tTrue/False."
34534 	},
34535 
34536 	// Karajorma
34537 	{ OP_SET_PLAYER_ORDERS, "set-player-orders\r\n"
34538 		"\tChanges the orders friendly AI will accept. Takes 3 or more arguments.\r\n"
34539 		"\t1:\tShip Name\r\n"
34540 		"\t2:\tTrue/False as to whether this order is allowed or not\r\n"
34541 		"\tRest:\tOrder"
34542 	},
34543 
34544 	//WMC
34545 	{ OP_HUD_SET_TEXT, "hud-set-text\r\n"
34546 		"\tSets the text value of a given HUD gauge. Works for custom and certain retail gauges. Takes 2 arguments...\r\n"
34547 		"\t1:\tHUD gauge to be modified\r\n"
34548 		"\t2:\tText to be set"
34549 	},
34550 
34551 	{ OP_HUD_SET_TEXT_NUM, "hud-set-text-num\r\n"
34552 		"\tSets the text value of a given HUD gauge to a number. Works for custom and certain retail gauges. Takes 2 arguments...\r\n"
34553 		"\t1:\tHUD gauge to be modified\r\n"
34554 		"\t2:\tNumber to be set"
34555 	},
34556 
34557 	{ OP_HUD_SET_MESSAGE, "hud-set-message\r\n"
34558 		"\tSets the text value of a given HUD gauge to a message from the mission's message list. Works for custom and certain retail gauges. Takes 2 arguments...\r\n"
34559 		"\t1:\tHUD gauge to be modified\r\n"
34560 		"\t2:\tMessage"
34561 	},
34562 
34563 	//WMC
34564 	{ OP_HUD_SET_COORDS, "hud-set-coord\r\n"
34565 		"\tSets the coordinates of a given HUD gauge. Works for custom and retail gauges. Takes 3 arguments...\r\n"
34566 		"\t1:\tHUD gauge to be modified\r\n"
34567 		"\t2:\tCoordinate X component\r\n"
34568 		"\t2:\tCoordinate Y component"
34569 	},
34570 
34571 	//WMC
34572 	{ OP_HUD_SET_FRAME, "hud-set-frame\r\n"
34573 		"\tSets the frame of a given HUD gauge's associated image. Works for custom and certain retail gauges. Takes 2 arguments...\r\n"
34574 		"\t1:\tHUD gauge to be modified\r\n"
34575 		"\t2:\tFrame number to be changed to"
34576 	},
34577 
34578 	//WMC
34579 	{ OP_HUD_SET_COLOR, "hud-set-color\r\n"
34580 		"\tSets the color of a given HUD gauge. Works only for custom gauges Takes 4 arguments...\r\n"
34581 		"\t1:\tHUD gauge to be modified\r\n"
34582 		"\t2:\tRed component (0-255)\r\n"
34583 		"\t3:\tGreen component (0-255)\r\n"
34584 		"\t4:\tBlue component (0-255)"
34585 	},
34586 
34587 	//WMC
34588 	{ OP_CURRENT_SPEED, "current-speed\r\n"
34589 		"\tReturns the speed of the given object. Takes 1 argument...\r\n"
34590 		"\t1:\tName of the object"
34591 	},
34592 
34593 	// Karajora
34594 	{ OP_PRIMARY_FIRED_SINCE, "primary-fired-since\r\n"
34595 		"\tReturns true if the primary weapon bank specified has been fired within the supplied window of time. Takes 3 arguments...\r\n\r\n"
34596 		"\t1:\tShip name\r\n"
34597 		"\t2:\tWeapon bank number (This is a zero-based index. The first bank is numbered 0.)\r\n"
34598 		"\t3:\tTime period to check if the weapon was fired (in milliseconds)\r\n"
34599 	},
34600 
34601 	// Karajora
34602 	{ OP_SECONDARY_FIRED_SINCE, "secondary-fired-since\r\n"
34603 		"\tReturns true if the secondary weapon bank specified has been fired within the supplied window of time. Takes 3 arguments...\r\n\r\n"
34604 		"\t1:\tShip name\r\n"
34605 		"\t2:\tWeapon bank number (This is a zero-based index. The first bank is numbered 0.)\r\n"
34606 		"\t3:\tTime period to check if the weapon was fired (in milliseconds)\r\n"
34607 	},
34608 
34609 	// Karajora
34610 	{ OP_HAS_PRIMARY_WEAPON, "has-primary-weapon\r\n"
34611 		"\tReturns true if the primary weapon bank specified has any of the weapons listed. Takes 3 or more arguments...\r\n\r\n"
34612 		"\t1:\tShip name\r\n"
34613 		"\t2:\tWeapon bank number (This is a zero-based index. The first bank is numbered 0.)\r\n"
34614 		"\tRest:\tWeapon name\r\n"
34615 	},
34616 
34617 	// Karajora
34618 	{ OP_HAS_SECONDARY_WEAPON, "has-secondary-weapon\r\n"
34619 		"\tReturns true if the secondary weapon bank specified has any of the weapons listed. Takes 3 or more arguments...\r\n\r\n"
34620 		"\t1:\tShip name\r\n"
34621 		"\t2:\tWeapon bank number (This is a zero-based index. The first bank is numbered 0.)\r\n"
34622 		"\tRest:\tWeapon name\r\n"
34623 	},
34624 
34625 	// Karajora
34626 	{ OP_DIRECTIVE_VALUE, "directive-value\r\n"
34627 		"\tCauses a value to appear in the directive count\r\n"
34628 		"\tAlways returns true. Takes 1 or more arguments...\r\n\r\n"
34629 		"\t1:\tValue\r\n"
34630 		"\t2:\t(Optional) Ignore the directive count set by any earlier SEXPs in the event. If set to false it will add instead\r\n"
34631 	},
34632 
34633 	// wookieejedi
34634 	{ OP_GET_HOTKEY, "get-hotkey\r\n"
34635 		"\tReturns the hotkey integer of the ship or wing. Return values range from -1 to 7.\r\n"
34636 		"\tReturn of -1 is no hotkey, return of 0 is F5, return of 1 is F6, ..., return of 7 is F12.\r\n\r\n"
34637 		"\tTakes 1 argument.\r\n\r\n"
34638 		"\t1:\tShip or wing to get hotkey.\r\n"
34639 	},
34640 
34641 	//phreak
34642 	{ OP_SCRAMBLE_MESSAGES, "scramble-messages\r\n"
34643 		"\tCauses messages to be sent as if the player has sustained communications subsystem or EMP damage.  This effect can be reversed using unscramble-messages.  Takes zero or more arguments.\r\n"
34644 		"\tAll (Optional):\tName of the ship for which to scramble messages.  If no ships are specified, message scrambling will be turned on for all messages the player receives.\r\n"
34645 	},
34646 
34647 	//phreak
34648 	{ OP_UNSCRAMBLE_MESSAGES, "unscramble-messages\r\n"
34649 		"\tUndoes the effects of scramble-messages, causing messages to be sent clearly.  Takes zero or more arguments.\r\n"
34650 		"\tAll (Optional):\tName of the ship for which to scramble messages.  If no ships are specified, message scrambling will be turned on for all messages the player receives.\r\n"
34651 	},
34652 
34653 	{ OP_CUTSCENES_SET_CUTSCENE_BARS, "set-cutscene-bars\r\n"
34654 		"\tShows bars at the top and bottom of screen.  "
34655 		"Takes 0 or 1 arguments...\r\n"
34656 		"\t1:\tMilliseconds for bars to slide in\r\n"
34657 	},
34658 
34659 	{ OP_CUTSCENES_UNSET_CUTSCENE_BARS, "unset-cutscene-bars\r\n"
34660 		"\tRemoves cutscene bars.  "
34661 		"Takes 0 or 1 arguments...\r\n"
34662 		"\t1:\tMilliseconds for bars to slide out\r\n"
34663 	},
34664 
34665 	{ OP_CUTSCENES_FADE_IN, "fade-in\r\n"
34666 		"\tFades in.  "
34667 		"Takes 0 to 4 arguments...\r\n"
34668 		"\t1:\tTime to fade in (in milliseconds)\r\n"
34669 		"\t2:\tColor to fade from (optional).  If arguments 3 and 4 are specified, this is the R component of an RGB color.  Otherwise it is 1 for white, 2 for red, and any other number for black.\r\n"
34670 		"\t3:\tG component of an RGB color (optional)\r\n"
34671 		"\t4:\tB component of an RGB color (optional)\r\n"
34672 	},
34673 
34674 	{ OP_CUTSCENES_FADE_OUT, "fade-out\r\n"
34675 		"\tFades out.  "
34676 		"Takes 0 to 4 arguments...\r\n"
34677 		"\t1:\tTime to fade out (in milliseconds)\r\n"
34678 		"\t2:\tColor to fade to (optional).  If arguments 3 and 4 are specified, this is the R component of an RGB color.  Otherwise it is 1 for white, 2 for red, and any other number for black.\r\n"
34679 		"\t3:\tG component of an RGB color (optional)\r\n"
34680 		"\t4:\tB component of an RGB color (optional)\r\n"
34681 	},
34682 
34683 	{ OP_CUTSCENES_SET_CAMERA, "set-camera\r\n"
34684 		"\tSets SEXP camera, or another specified cutscene camera.  "
34685 		"Takes 0 to 1 arguments...\r\n"
34686 		"\t(optional)\r\n"
34687 		"\t1:\tCamera name (created if nonexistent)\r\n"
34688 	},
34689 
34690 	{ OP_CUTSCENES_SET_CAMERA_FACING, "set-camera-facing\r\n"
34691 		"\tMakes the camera face the given point.  "
34692 		"Takes 3 to 6 arguments...\r\n"
34693 		"\t1:\tX position to face\r\n"
34694 		"\t2:\tY position to face\r\n"
34695 		"\t3:\tZ position to face\r\n"
34696 		"\t(optional)\r\n"
34697 		"\t4:\tTotal turn time (milliseconds)\r\n"
34698 		"\t5:\tTime to spend accelerating/decelerating (milliseconds)\r\n"
34699 		"\t6:\tTime to spend decelerating (milliseconds)\r\n"
34700 	},
34701 
34702 	{ OP_CUTSCENES_SET_CAMERA_FACING_OBJECT, "set-camera-facing-object\r\n"
34703 		"\tMakes the camera face the given object.  "
34704 		"Takes 1 to 4 arguments...\r\n"
34705 		"\t1:\tObject to face\r\n"
34706 		"\t(optional)\r\n"
34707 		"\t2:\tTotal turn time (milliseconds)\r\n"
34708 		"\t3:\tTime to spend accelerating/decelerating (milliseconds)\r\n"
34709 		"\t4:\tTime to spend decelerating (milliseconds)\r\n"
34710 	},
34711 
34712 	{ OP_CUTSCENES_SET_CAMERA_FOV, "set-camera-fov\r\n"
34713 		"\tSets the camera field of view.  "
34714 		"Takes 1 to 4 arguments...\r\n"
34715 		"\t1:\tNew FOV (degrees)\r\n"
34716 		"\t(optional)\r\n"
34717 		"\t2:\tTotal zoom time (milliseconds)\r\n"
34718 		"\t3:\tTime to spend accelerating/decelerating (milliseconds)\r\n"
34719 		"\t4:\tTime to spend decelerating (milliseconds)\r\n"
34720 	},
34721 
34722 	{ OP_CUTSCENES_SET_CAMERA_HOST, "set-camera-host\r\n"
34723 		"\tSets the object and subystem camera should view from. Camera position is offset from the host. "
34724 		"If the selected subsystem or one of its children has an eyepoint bound to it it will be used for the camera position and orientation."
34725 		"If the selected subsystem is a turret and has no eyepoint the camera will be at the first firing point and look along the firing direction."
34726 		"If a valid camera target is set the direction to the target will override any other orientation."
34727 		"Takes 1 to 2 arguments...\r\n"
34728 		"\t1:\tShip to mount camera on\r\n"
34729 		"\t(optional)\r\n"
34730 		"\t2:\tSubystem to mount camera on\r\n"
34731 	},
34732 
34733 	{ OP_CUTSCENES_SET_CAMERA_POSITION, "set-camera-position\r\n"
34734 		"\tSets the camera position to a spot in mission space.  "
34735 		"Takes 3 to 6 arguments...\r\n"
34736 		"\t1:\tX position\r\n"
34737 		"\t2:\tY position\r\n"
34738 		"\t3:\tZ position\r\n"
34739 		"\t(optional)\r\n"
34740 		"\t4:\tTotal turn time (milliseconds)\r\n"
34741 		"\t5:\tTime to spend accelerating/decelerating (milliseconds)\r\n"
34742 		"\t6:\tTime to spend decelerating (milliseconds)\r\n"
34743 	},
34744 
34745 	{ OP_CUTSCENES_SET_CAMERA_ROTATION, "set-camera-rotation\r\n"
34746 		"\tSets the camera rotation.  "
34747 		"Takes 3 to 6 arguments...\r\n"
34748 		"\t1:\tPitch (degrees)\r\n"
34749 		"\t2:\tBank (degrees)\r\n"
34750 		"\t3:\tHeading (degrees)\r\n"
34751 		"\t(optional)\r\n"
34752 		"\t4:\tTotal turn time (milliseconds)\r\n"
34753 		"\t5:\tTime to spend accelerating/decelerating (milliseconds)\r\n"
34754 		"\t6:\tTime to spend decelerating (milliseconds)\r\n"
34755 	},
34756 
34757 	{ OP_CUTSCENES_SET_CAMERA_TARGET, "set-camera-target\r\n"
34758 		"\tSets the object and subystem camera should track. Camera orientation is offset from the target.  "
34759 		"Takes 1 to 2 arguments...\r\n"
34760 		"\t1:\tShip to track\r\n"
34761 		"\t(optional)\r\n"
34762 		"\t2:\tSubystem to track\r\n"
34763 	},
34764 
34765 	{ OP_CUTSCENES_SET_FOV, "set-fov\r\n"
34766 		"\tSets the field of view - overrides all camera settings.  "
34767 		"Takes 1 argument...\r\n"
34768 		"\t1:\tNew FOV (degrees)\r\n"
34769 	},
34770 
34771 	// Echelon9
34772 	{ OP_CUTSCENES_GET_FOV, "get-fov\r\n"
34773 		"\tReturns the current field of view (in degrees) from the Main camera.\r\n\r\n"
34774 		"Returns a numeric value.  Takes no arguments."
34775 	},
34776 
34777 	{ OP_CUTSCENES_RESET_FOV, "reset-fov\r\n"
34778 		"\tResets the field of view.  "
34779 	},
34780 
34781 	{ OP_CUTSCENES_RESET_CAMERA, "reset-camera\r\n"
34782 		"\tReleases cutscene camera control.  "
34783 		"Takes 1 optional argument...\r\n"
34784 		"\t(optional)\r\n"
34785 		"\t1:\tReset camera data (Position, facing, FOV...) (default: false)"
34786 	},
34787 
34788 	{ OP_CUTSCENES_SHOW_SUBTITLE, "show-subtitle (deprecated)\r\n"
34789 		"\tDisplays a subtitle, either an image or a string of text.  Please note that, in images without an alpha channel, black pixels will be treated as transparent.  In images with an alpha channel, black pixels will be drawn as expected.\r\n\r\n"
34790 		"As this operator tries to combine two functions into one and does not adjust coordinates for screen formats, it has been deprecated.\r\n\r\n"
34791 		"Takes 4 to 13 arguments...\r\n"
34792 		"\t1:\tX position (negative value to be from right of screen)\r\n"
34793 		"\t2:\tY position (negative value to be from bottom of screen)\r\n"
34794 		"\t3:\tText to display\r\n"
34795 		"\t4:\tTime to be displayed, not including fadein/out\r\n"
34796 		"\t5:\tImage name (optional)\r\n"
34797 		"\t6:\tFade in time (optional)\r\n"
34798 		"\t7:\tCenter horizontally? (optional)\r\n"
34799 		"\t8:\tCenter vertically? (optional)\r\n"
34800 		"\t9:\tWidth (optional)\r\n"
34801 		"\t10:\tText red component (0-255) (optional)\r\n"
34802 		"\t11:\tText green component (0-255) (optional)\r\n"
34803 		"\t12:\tText blue component (0-255) (optional)\r\n"
34804 		"\t13:\tDrawn after shading? (optional)"
34805 	},
34806 
34807 	{ OP_CUTSCENES_SHOW_SUBTITLE_TEXT, "show-subtitle-text\r\n"
34808 		"\tDisplays a subtitle in the form of text.  Note that because of the constraints of the subtitle system, textual subtitles are currently limited to 255 characters or fewer.\r\n"
34809 		"Takes 6 to 13 arguments...\r\n"
34810 		"\t1:\tText to display, or the name of a message containing text\r\n"
34811 		"\t2:\tX position, from 0 to 100% (positive measures from the left; negative measures from the right)\r\n"
34812 		"\t3:\tY position, from 0 to 100% (positive measures from the top; negative measures from the bottom)\r\n"
34813 		"\t4:\tCenter horizontally? (if true, overrides argument #2)\r\n"
34814 		"\t5:\tCenter vertically? (if true, overrides argument #3)\r\n"
34815 		"\t6:\tTime (in milliseconds) to be displayed, not including fade-in/fade-out\r\n"
34816 		"\t7:\tFade time (in milliseconds) to be used for both fade-in and fade-out (optional)\r\n"
34817 		"\t8:\tParagraph width, from 1 to 100% (optional; 0 uses default 200 pixels)\r\n"
34818 		"\t9:\tText red component (0-255) (optional)\r\n"
34819 		"\t10:\tText green component (0-255) (optional)\r\n"
34820 		"\t11:\tText blue component (0-255) (optional)\r\n"
34821 		"\t12:\tText font (optional)\r\n"
34822 		"\t13:\tDrawn after shading? (optional)"
34823 	},
34824 
34825 	{ OP_CUTSCENES_SHOW_SUBTITLE_IMAGE, "show-subtitle-image\r\n"
34826 		"\tDisplays a subtitle in the form of an image.  Please note that, in images without an alpha channel, black pixels will be treated as transparent.  In images with an alpha channel, black pixels will be drawn as expected.\r\n\r\n"
34827 		"Takes 8 to 10 arguments...\r\n"
34828 		"\t1:\tImage to display\r\n"
34829 		"\t2:\tX position, from 0 to 100% (positive measures from the left; negative measures from the right)\r\n"
34830 		"\t3:\tY position, from 0 to 100% (positive measures from the top; negative measures from the bottom)\r\n"
34831 		"\t4:\tCenter horizontally? (if true, overrides argument #2)\r\n"
34832 		"\t5:\tCenter vertically? (if true, overrides argument #3)\r\n"
34833 		"\t6:\tImage width, from 1 to 100% (0 uses original width)\r\n"
34834 		"\t7:\tImage height, from 1 to 100% (0 uses original height)\r\n"
34835 		"\t8:\tTime (in milliseconds) to be displayed, not including fade-in/fade-out\r\n"
34836 		"\t9:\tFade time (in milliseconds) to be used for both fade-in and fade-out (optional)\r\n"
34837 		"\t10:\tDrawn after shading? (optional)"
34838 	},
34839 
34840 	{ OP_CUTSCENES_SET_TIME_COMPRESSION, "set-time-compression\r\n"
34841 		"\tSets the time compression and prevents it from being changed by the user.  "
34842 		"Takes 1 to 3 arguments...\r\n"
34843 		"\t1:\tNew time compression (% of 1x)\r\n"
34844 		"\t2:\tTime in ms for change to take\r\n"
34845 		"\t3:\tTime compression to start from\r\n"
34846 	},
34847 
34848 	{ OP_CUTSCENES_RESET_TIME_COMPRESSION, "reset-time-compression\r\n"
34849 		"\tResets the time compression - that is to say, time compression is set back to 1x, "
34850 		"and the time compression controls are unlocked.  Always call this when done with set-time-compression.  "
34851 	},
34852 
34853 	{ OP_CUTSCENES_FORCE_PERSPECTIVE, "lock-perspective\r\n"
34854 		"\tPrevents or allows the player from changing the view mode.  "
34855 		"Takes 1 or 2 arguments...\r\n"
34856 		"\t1:\tTrue to lock the view mode, false to unlock it\r\n"
34857 		"\t2:\tWhat view mode to lock; 0 for first-person, 1 for chase, 2 for external, 3 for top-down"
34858 	},
34859 
34860 	{ OP_SET_CAMERA_SHUDDER, "set-camera-shudder\r\n"
34861 		"\tCauses the camera to shudder.  Currently this will only work if the camera is showing the player's viewpoint (i.e. the HUD).\r\n\r\n"
34862 		"Takes 2 arguments...\r\n"
34863 		"\t1: Time (in milliseconds)\r\n"
34864 		"\t2: Intensity.  For comparison, the Maxim has an intensity of 1440."
34865 	},
34866 
34867 	{ OP_JUMP_NODE_SET_JUMPNODE_NAME, "set-jumpnode-name\r\n"
34868 		"\tSets the name of a jump node. Takes 2 arguments...\r\n"
34869 		"\t1: Name of jump node to change name for\r\n"
34870 		"\t2: New name for jump node\r\n\r\n"
34871 		"\tNote: SEXPs referencing the old name will not work after the name change.\r\n"
34872 	},
34873 
34874 	{ OP_JUMP_NODE_SET_JUMPNODE_COLOR, "set-jumpnode-color\r\n"
34875 		"\tSets the color of a jump node.  "
34876 		"Takes 5 arguments...\r\n"
34877 		"\t1:\tJump node to change color for\r\n"
34878 		"\t2:\tRed value\r\n"
34879 		"\t3:\tGreen value\r\n"
34880 		"\t4:\tBlue value\r\n"
34881 		"\t5:\tAlpha value\r\n"
34882 	},
34883 
34884 	{ OP_JUMP_NODE_SET_JUMPNODE_MODEL, "set-jumpnode-model\r\n"
34885 		"\tSets the model of a jump node.  "
34886 		"Takes 3 arguments...\r\n"
34887 		"\t1:\tJump node to change model for\r\n"
34888 		"\t2:\tModel filename\r\n"
34889 		"\t3:\tShow as normal model. When this is true, the jumpnode will be rendered like a normal model.\r\n"
34890 	},
34891 
34892 	{ OP_JUMP_NODE_SHOW_JUMPNODE, "show-jumpnode\r\n"
34893 		"\tSets a jump node to display on the screen.\r\n"
34894 		"\tAny:\tJump node to show\r\n"
34895 	},
34896 
34897 	{ OP_JUMP_NODE_HIDE_JUMPNODE, "hide-jumpnode\r\n"
34898 		"\tSets a jump node to not display on the screen.\r\n"
34899 		"\tAny:\tJump node to hide\r\n"
34900 	},
34901 
34902 	// taylor, with modifications by niffiwan and MageKing17
34903 	{ OP_SET_SKYBOX_MODEL, "set-skybox-model\r\n"
34904 		"\tSets the current skybox model.  Takes 1-7 arguments\r\n"
34905 		"\t1:\tModel filename (with .pof extension) to switch to\r\n"
34906 		"\t2:\tRestart animated textures, if they exist (optional, defaults to true)\r\n"
34907 		"\t3-8:\tSet or unset the following skyboxes flags (optional)\r\n"
34908 		"\t\t\tadd-lighting, no-transparency, add-zbuffer\r\n"
34909 		"\t\t\tadd-culling, no-glowmaps, force-clamp\r\n\r\n"
34910 		"Note: If the model filename is set to \"default\" with no extension then it will switch to the mission supplied default skybox."
34911 	},
34912 
34913 	// Goober5000
34914 	{ OP_SET_SKYBOX_ORIENT, "set-skybox-orientation\r\n"
34915 		"\tSets the current skybox orientation.  Takes 3 arguments...\r\n"
34916 		"\t1:\tPitch\r\n"
34917 		"\t2:\tBank\r\n"
34918 		"\t3:\tHeading\r\n"
34919 	},
34920 
34921 	// Goober5000
34922 	{ OP_CHANGE_BACKGROUND, "change-background\r\n"
34923 		"\tSets the displayed suns and bitmaps to one of the stored mission backgrounds.  Takes 1 argument...\r\n"
34924 		"\t1:\tBackground number (starting from 1)\r\n"
34925 	},
34926 
34927 	{ OP_ADD_BACKGROUND_BITMAP, "add-background-bitmap\r\n"
34928 		"\tAdds a background bitmap to the sky.  Returns an integer that is stored in a variable so it can be deleted using remove-background-bitmap\r\n\r\n"
34929 		"Takes 8 to 9 arguments...\r\n"
34930 		"\t1:\tBackground bitmap name\r\n"
34931 		"\t2:\tPitch\r\n"
34932 		"\t3:\tBank\r\n"
34933 		"\t4:\tHeading\r\n"
34934 		"\t5:\tX scale (expressed as a percentage of the original size of the bitmap)\r\n"
34935 		"\t6:\tY scale (expressed as a percentage of the original size of the bitmap)\r\n"
34936 		"\t7:\tX divisions.\r\n"
34937 		"\t8:\tY divisions.\r\n"
34938 		"\t9:\tNumeric variable in which to store the result (optional)\r\n"
34939 	},
34940 
34941 	{ OP_REMOVE_BACKGROUND_BITMAP, "remove-background-bitmap\r\n"
34942 		"\tRemoves the nth background bitmap from the mission\r\n\r\n"
34943 		"Takes 1 argument...\r\n"
34944 		"\t1:\tZero based bitmap index from the \'Bitmap\' box in the background editor\r\n"
34945 		"\t\t\tYou can also use the result of a previous call to add-background-bitmap to remove that added bitmap\r\n"
34946 	},
34947 
34948 	{ OP_ADD_SUN_BITMAP, "add-sun-bitmap\r\n"
34949 		"\tAdds a sun bitmap to the sky.  Returns an integer that is stored in a variable so it can be deleted using remove-sun-bitmap\r\n\r\n"
34950 		"Takes 5 to 6 arguments...\r\n"
34951 		"\t1:\tSun bitmap name\r\n"
34952 		"\t2:\tPitch\r\n"
34953 		"\t3:\tBank\r\n"
34954 		"\t4:\tHeading\r\n"
34955 		"\t5:\tScale (expressed as a percentage of the original size of the bitmap)\r\n"
34956 		"\t6:\tNumeric variable in which to store the result (optional)\r\n"
34957 	},
34958 
34959 	{ OP_REMOVE_SUN_BITMAP, "remove-sun-bitmap\r\n"
34960 		"\tRemoves the nth sun from the mission\r\n\r\n"
34961 		"Takes 1 argument...\r\n"
34962 		"\t1:\tZero based sun index from the \'Suns\' box in the background editor\r\n"
34963 		"\t\t\tYou can also use the result of a previous call to add-sun-bitmap to remove that added sun\r\n"
34964 	},
34965 
34966 	{ OP_NEBULA_CHANGE_STORM, "nebula-change-storm\r\n"
34967 		"\tChanges the current nebula storm\r\n\r\n"
34968 		"Takes 1 argument...\r\n"
34969 		"\t1:\tNebula storm to change to\r\n"
34970 	},
34971 
34972 	{ OP_NEBULA_TOGGLE_POOF, "nebula-toggle-poof\r\n"
34973 		"\tToggles the state of a nebula poof\r\n\r\n"
34974 		"Takes 2 arguments...\r\n"
34975 		"\t1:\tName of nebula poof to toggle\r\n"
34976 		"\t2:\tA True boolean expression will toggle this poof on.  A false one will do the opposite."
34977 	},
34978 
34979 	{ OP_NEBULA_CHANGE_PATTERN, "nebula-change-pattern\r\n"
34980 	"\tChanges the current nebula background pattern (as defined in nebula.tbl)\r\n\r\n"
34981 		"Takes 1 argument...\r\n"
34982 		"\t1:\tNebula background pattern to change to\r\n"
34983 	},
34984 
34985 	{ OP_NEBULA_CHANGE_FOG_COLOR, "nebula-change-fog-color\r\n"
34986 	"\tChanges the current nebula fog color\r\n\r\n"
34987 		"Takes 3 arguments...\r\n"
34988 		"\t1:\tRed (0 - 255)\r\n"
34989 		"\t2:\tGreen (0 - 255)\r\n"
34990 		"\t3:\tBlue (0 - 255)\r\n"
34991 	},
34992 
34993 	{OP_SCRIPT_EVAL_NUM, "script-eval-num\r\n"
34994 		"\tEvaluates script to return a number"
34995 		"Takes 1 argument...\r\n"
34996 		"\t1:\tScript\r\n"
34997 	},
34998 
34999 	{ OP_DISABLE_ETS, "disable-ets\r\n"
35000 		"\tSwitches a ships' ETS system off\r\n\r\n"
35001 		"Takes at least 1 argument...\r\n"
35002 		"\tAll:\tList of ships this sexp applies to\r\n"
35003 	},
35004 
35005 	{ OP_ENABLE_ETS, "enable-ets\r\n"
35006 		"\tSwitches a ships' ETS system on\r\n\r\n"
35007 		"Takes at least 1 argument...\r\n"
35008 		"\tAll:\tList of ships this sexp applies to\r\n"
35009 	},
35010 
35011 	{OP_SCRIPT_EVAL_STRING, "script-eval-string\r\n"
35012 		"\tEvaluates script to return a string\r\n\r\n"
35013 		"Takes a multiple of 2 arguments...\r\n"
35014 		"\t1:\tScript (without a leading 'return')\r\n"
35015 		"\t2:\tString variable to hold the result\r\n"
35016 	},
35017 
35018 	{OP_SCRIPT_EVAL, "script-eval (deprecated in favor of script-eval-block)\r\n"
35019 		"\tEvaluates the given scripts, one script per argument\r\n"
35020 		"Takes at least 1 argument...\r\n"
35021 		"\tAll:\tScript to evaluate\r\n"
35022 	},
35023 
35024 	{OP_SCRIPT_EVAL_BLOCK, "script-eval-block\r\n"
35025 		"\tEvaluates the concatenation of all arguments as a single script\r\n"
35026 		"Takes at least 1 argument...\r\n"
35027 		"\tAll:\tScript to evaluate\r\n"
35028 	},
35029 
35030 	{OP_SCRIPT_EVAL_MULTI, "multi-eval\r\n"
35031 		"\tEvaluates script\r\n\r\n"
35032 		"Takes at least 2 arguments...\r\n"
35033 		"\t1:\tScript to evaluate\r\n"
35034 		"\t2:\tTrue/False - Should the script evaluate on the server?\r\n"
35035 		"\t(rest):\tList of players who should evaluate this script. If no player is given, all clients will execute the script\r\n"
35036 	},
35037 
35038 	{OP_FORCE_GLIDE, "force-glide\r\n"
35039 		"\tForces a given ship into glide mode, provided it is capable of gliding. Note that the player will not be able to leave glide mode on his own,, and that a ship in glide mode cannot warp out or enter autopilot."
35040 		"Takes 2 Arguments...\r\n"
35041 		"\t1:\tShip to force\r\n"
35042 		"\t2:\tTrue to activate glide, False to deactivate\r\n"
35043 	},
35044 
35045 	{OP_HUD_SET_DIRECTIVE, "hud-set-directive\r\n"
35046 		"\tSets the text of a given custom hud gauge to the provided text."
35047 		"Takes 2 Arguments...\r\n"
35048 		"\t1:\tHUD Gauge name\r\n"
35049 		"\t2:\tText that will be displayed. This text will be treated as directive text, meaning that references to mapped keys will be replaced with the user's preferences.\r\n"
35050 	},
35051 
35052 	{OP_HUD_GAUGE_SET_ACTIVE, "hud-gauge-set-active (deprecated)\r\n"
35053 		"\tActivates or deactivates a given custom gauge."
35054 		"Takes 2 Arguments...\r\n"
35055 		"\t1:\tHUD Gauge name\r\n"
35056 		"\t2:\tBoolean, whether or not to display this gauge\r\n"
35057 	},
35058 
35059 	{OP_HUD_CLEAR_MESSAGES, "hud-clear-messages\r\n"
35060 		"\tClears active messages displayed on the HUD."
35061 		"Takes no arguments\r\n"
35062 	},
35063 
35064 	{OP_HUD_ACTIVATE_GAUGE_TYPE, "hud-activate-gauge-type (deprecated)\r\n"
35065 		"\tActivates or deactivates all hud gauges of a given type."
35066 		"Takes 2 Arguments...\r\n"
35067 		"\t1:\tGauge Type\r\n"
35068 		"\t2:\tBoolean, whether or not to display this gauge\r\n"
35069 	},
35070 
35071 	{OP_HUD_SET_CUSTOM_GAUGE_ACTIVE, "hud-set-custom-gauge-active\r\n"
35072 		"\tActivates or deactivates a custom hud gauge defined in hud_gauges.tbl."
35073 		"Takes 2 Arguments...\r\n"
35074 		"\t1:\tBoolean, whether or not to display this gauge\r\n"
35075 		"\tRest:\tHUD Gauge name\r\n"
35076 	},
35077 
35078 	{OP_HUD_SET_BUILTIN_GAUGE_ACTIVE, "hud-set-builtin-gauge-active\r\n"
35079 		"\tActivates or deactivates a builtin hud gauge grouping."
35080 		"Takes 2 Arguments...\r\n"
35081 		"\t1:\tBoolean, whether or not to display this gauge\r\n"
35082 		"\tRest:\tHUD Gauge Group name\r\n"
35083 	},
35084 
35085 	{OP_ADD_TO_COLGROUP, "add-to-collision-group\r\n"
35086 		"\tAdds a ship to the specified collision group(s). Note that there are 32 collision groups, "
35087 		"and that an object may be in several collision groups at the same time\r\n"
35088 		"Takes 2 or more arguments...\r\n"
35089 		"\t1:\tShip to add.\r\n"
35090 		"\t2+:\tGroup IDs. Valid IDs are 0 through 31 inclusive.\r\n"
35091 	},
35092 
35093 	{OP_REMOVE_FROM_COLGROUP, "remove-from-collision-group\r\n"
35094 		"\tRemoves a ship from the specified collision group(s). Note that there are 32 collision groups, "
35095 		"and that an object may be in several collision groups at the same time\r\n"
35096 		"Takes 2 or more arguments...\r\n"
35097 		"\t1:\tShip to add.\r\n"
35098 		"\t2+:\tGroup IDs. Valid IDs are 0 through 31 inclusive.\r\n"
35099 	},
35100 
35101 	{OP_ADD_TO_COLGROUP2, "add-to-collision-group2\r\n"
35102 		"\tAdds one or more ships to the specified collision group. There are 32 collision groups, "
35103 		"and an object may be in several collision groups at the same time. This sexp functions identically to "
35104 		"add-to-collision-group, except that the arguments are one group and many ships, rather than one ship and many groups.\r\n"
35105 		"Takes 2 or more arguments...\r\n"
35106 		"\t1:\tGroup ID. Valid IDs are 0 through 31 inclusive.\r\n"
35107 		"\t2+:\tShip to add.\r\n"
35108 	},
35109 
35110 	{OP_REMOVE_FROM_COLGROUP2, "remove-from-collision-group2\r\n"
35111 		"\tRemoves one or more ships from the specified collision group. There are 32 collision groups, "
35112 		"and an object may be in several collision groups at the same time. This sexp functions identically to "
35113 		"remove-from-collision-group, except that the arguments are one group and many ships, rather than one ship and many groups.\r\n"
35114 		"Takes 2 or more arguments...\r\n"
35115 		"\t1:\tGroup ID. Valid IDs are 0 through 31 inclusive.\r\n"
35116 		"\t2+:\tShip to remove.\r\n"
35117 	},
35118 
35119 	{OP_GET_COLGROUP_ID, "get-collision-group\r\n"
35120 		"\tReturns an objects' collision group ID. Note that this ID is a bitfield.\r\n"
35121 		"Takes 1 Argument...\r\n"
35122 		"\t1:\tObject name\r\n"
35123 	},
35124 
35125 	//Valathil
35126 	{OP_SHIP_EFFECT, "ship-effect\r\n"
35127 		"\tPlays an animated shader effect on the ship(s) or wing(s).\r\n"
35128 		"Takes 3 or more arguments...\r\n"
35129 		"\t1:\tEffect name (as defined in post_processing.tbl)\r\n"
35130 		"\t2:\tHow long the effect should take in milliseconds\r\n"
35131 		"\tRest:\tShip or wing name\r\n"
35132 	},
35133 
35134 	{OP_CLEAR_SUBTITLES, "clear-subtitles\r\n"
35135 		"\tClears the subtitle queue completely.\r\n"
35136 	},
35137 
35138 	{OP_SET_THRUSTERS, "set-thrusters-status\r\n"
35139 		"\tManipulates the thrusters on a ship.\r\n"
35140 		"Takes 2 or more arguments...\r\n"
35141 		"\t1:\tBoolean, true sets thrusters to visible, false deactivates them.\r\n"
35142 		"\t2:\tRest: List of ships this sexp will work on.\r\n"
35143 	},
35144 
35145 	{OP_SET_PLAYER_THROTTLE_SPEED, "set-player-throttle-speed\r\n"
35146 		"\tSets a player's throttle to a percentage of their maximum speed.\r\n"
35147 		"\tThis SEXP has no effect if used on an AI ship, however it will still work on an AI-controlled player ship.\r\n"
35148 		"Takes 2 arguments...\r\n"
35149 		"\t1:\tThe player ship to set the throttle of.\r\n"
35150 		"\t2:\tThe percentage of the player's maximum speed to set their throttle to.\r\n"
35151 		"\t\tThis is capped to either 0 or 100 if outside the valid range."
35152 	},
35153 
35154 	{OP_CHANGE_TEAM_COLOR, "change-team-color\r\n"
35155 		"\tChanges the team color setting for one or several ships.\r\n"
35156 		"\tThis sexp has no effect on ships that don't have team colors enabled for them.\r\n"
35157 		"\tTakes 3 or more arguments...\r\n"
35158 		"\t1:\tThe new team color name. Name must be defined in colors.tbl.\r\n"
35159 		"\t2:\tCrossfade time in milliseconds. During this time, colors will be mixed.\r\n"
35160 		"\t3:\tRest: List of ships this sexp will operate on."
35161 	},
35162 
35163 	{OP_CALL_SSM_STRIKE, "call-ssm-strike\r\n"
35164 		"\tCalls a subspace missile strike on the specified ship.\r\n"
35165 		"\tRequires a ssm table (ssm.tbl).\r\n"
35166 		"Takes 3 arguments...\r\n"
35167 		"\t1:\tStrike name.\r\n"
35168 		"\t2:\tCalling team.\r\n"
35169 		"\tRest:\tList of ships the strike will be called on."
35170 	},
35171 
35172 	{OP_PLAYER_IS_CHEATING_BASTARD, "player-is-cheating\r\n"
35173 		"\tReturns true if the player is or has been cheating in this mission.\r\n"
35174 	},
35175 
35176 	{ OP_IS_LANGUAGE, "is-language\r\n"
35177 		"\tReturns whether the game is running in the specified language.  Takes 1 argument...\r\n"
35178 		"\t1:\tA language.  This can be any string; the SEXP will return true if and only if the string matches the current language.  "
35179 		"Builtin languages are English, German, French, and Polish, and others can be defined in strings.tbl.\r\n"
35180 	},
35181 
35182 	{ OP_SET_MOTION_DEBRIS, "set-motion-debris-override\r\n"
35183 		"\tControls whether or not motion debris should be active.\r\n"
35184 		"\tThis overrides any choice made by the user through the -nomotiondebris commandline flag."
35185 		"Takes 1 argument...\r\n"
35186 		"\t1:\tBoolean: True will disable motion debris, False reenable it.\r\n"
35187 	},
35188 
35189 	{ OP_MODIFY_VARIABLE_XSTR, "modify-variable-xstr\r\n"
35190 		"\tSets a variable to a localized string.\r\n\r\n"
35191 		"Takes 3 arguments...\r\n"
35192 		"\t1:\tName of Variable.\r\n"
35193 		"\t2:\tThe default text if no localized version is available.\r\n"
35194 		"\t3:\tThe XSTR index. If set to -1 then the default value will be used\r\n"
35195 	},
35196 
35197 	{ OP_REPLACE_TEXTURE, "replace-texture\r\n"
35198 		"\tChanges a texture of a ship to a different texture, similar to the FRED texture replace.\r\n"
35199 		"Takes 3 or more arguments...\r\n"
35200 		"\t1: Name of the texture to be replaced.\r\n"
35201 		"\t2: Name of the texture to be changed to.\r\n"
35202 		"\tRest: Name of the ship or wing.\r\n"
35203 	},
35204 };
35205 // clang-format on
35206 
35207 
35208 
35209 SCP_vector<op_menu_struct> op_menu =
35210 {
35211 	{ "Objectives",		OP_CATEGORY_OBJECTIVE	},
35212 	{ "Time",			OP_CATEGORY_TIME		},
35213 	{ "Logical",		OP_CATEGORY_LOGICAL		},
35214 	{ "Arithmetic",		OP_CATEGORY_ARITHMETIC	},
35215 	{ "Status",			OP_CATEGORY_STATUS		},
35216 	{ "Change",			OP_CATEGORY_CHANGE		},
35217 	{ "Conditionals",	OP_CATEGORY_CONDITIONAL	},
35218 	{ "Ai goals",		OP_CATEGORY_AI			},
35219 	{ "Event/Goals",	OP_CATEGORY_GOAL_EVENT	},
35220 	{ "Training",		OP_CATEGORY_TRAINING	},
35221 };
35222 
35223 // Goober5000's subcategorization of the Change menu (and possibly other menus in the future,
35224 // if people so choose - see sexp.h)
35225 SCP_vector<op_menu_struct> op_submenu =
35226 {
35227 	{	"Messages and Personas",		CHANGE_SUBCATEGORY_MESSAGING						},
35228 	{	"AI Control",					CHANGE_SUBCATEGORY_AI_CONTROL						},
35229 	{	"Ship Status",					CHANGE_SUBCATEGORY_SHIP_STATUS						},
35230 	{	"Weapons, Shields, and Engines",CHANGE_SUBCATEGORY_SHIELDS_ENGINES_AND_WEAPONS		},
35231 	{	"Subsystems and Health",		CHANGE_SUBCATEGORY_SUBSYSTEMS						},
35232 	{	"Cargo",						CHANGE_SUBCATEGORY_CARGO							},
35233 	{	"Armor and Damage Types",		CHANGE_SUBCATEGORY_ARMOR_AND_DAMAGE_TYPES			},
35234 	{	"Beams and Turrets",			CHANGE_SUBCATEGORY_BEAMS_AND_TURRETS				},
35235 	{	"Models and Textures",			CHANGE_SUBCATEGORY_MODELS_AND_TEXTURES				},
35236 	{	"Coordinate Manipulation",		CHANGE_SUBCATEGORY_COORDINATE_MANIPULATION			},
35237 	{	"Mission and Campaign",			CHANGE_SUBCATEGORY_MISSION_AND_CAMPAIGN				},
35238 	{	"Music and Sound",				CHANGE_SUBCATEGORY_MUSIC_AND_SOUND					},
35239 	{	"HUD",							CHANGE_SUBCATEGORY_HUD								},
35240 	{	"Nav Points",					CHANGE_SUBCATEGORY_NAV								},
35241 	{	"Cutscenes",					CHANGE_SUBCATEGORY_CUTSCENES						},
35242 	{	"Backgrounds and Nebulae",		CHANGE_SUBCATEGORY_BACKGROUND_AND_NEBULA			},
35243 	{	"Jump Nodes",					CHANGE_SUBCATEGORY_JUMP_NODES						},
35244 	{	"Special Effects",				CHANGE_SUBCATEGORY_SPECIAL_EFFECTS					},
35245 	{	"Variables",					CHANGE_SUBCATEGORY_VARIABLES						},
35246 	{	"Other",						CHANGE_SUBCATEGORY_OTHER							},
35247 	{	"Mission",						STATUS_SUBCATEGORY_MISSION							},
35248 	{	"Player",						STATUS_SUBCATEGORY_PLAYER							},
35249 	{	"Multiplayer",					STATUS_SUBCATEGORY_MULTIPLAYER						},
35250 	{	"Ship Status",					STATUS_SUBCATEGORY_SHIP_STATUS						},
35251 	{	"Weapons, Shields, and Engines",STATUS_SUBCATEGORY_SHIELDS_ENGINES_AND_WEAPONS		},
35252 	{	"Cargo",						STATUS_SUBCATEGORY_CARGO							},
35253 	{	"Damage",						STATUS_SUBCATEGORY_DAMAGE							},
35254 	{	"Distance and Coordinates",		STATUS_SUBCATEGORY_DISTANCE_AND_COORDINATES			},
35255 	{	"Variables",					STATUS_SUBCATEGORY_VARIABLES						},
35256 	{	"Other",						STATUS_SUBCATEGORY_OTHER							}
35257 };
35258 
35259 /**
35260  * Internal file used by output_sexps, should not be called from output_sexps
35261  */
output_sexp_html(int sexp_idx,FILE * fp)35262 static void output_sexp_html(int sexp_idx, FILE *fp)
35263 {
35264 	if(sexp_idx < 0 || sexp_idx > (int)Operators.size())
35265 		return;
35266 
35267 	bool printed=false;
35268 
35269 	for(auto& help : Sexp_help)
35270 	{
35271 		if(help.id == Operators[sexp_idx].value)
35272 		{
35273 			char* new_buf = new char[2 * help.help.size()];
35274 			char* dest_ptr = new_buf;
35275 			const char* curr_ptr = help.help.c_str();
35276 			const char* end_ptr = curr_ptr + help.help.size();
35277 			while(curr_ptr < end_ptr)
35278 			{
35279 				if(*curr_ptr == '\n')
35280 				{
35281 					strcpy(dest_ptr, "\n<br>");
35282 					dest_ptr+=5;
35283 				// for html compatibility
35284 				} else if (*curr_ptr == '<') {
35285 					strcpy(dest_ptr, "&lt;");
35286 					dest_ptr += 4;
35287 				// for html compatibility
35288 				} else if (*curr_ptr == '>') {
35289 					strcpy(dest_ptr, "&gt;");
35290 					dest_ptr += 4;
35291 				} else {
35292 					*dest_ptr++ = *curr_ptr;
35293 				}
35294 				curr_ptr++;
35295 			}
35296 			*dest_ptr = '\0';
35297 
35298 			fprintf(fp, "<dt><b>%s</b></dt>\n<dd>%s</dd>\n", Operators[sexp_idx].text.c_str(), new_buf);
35299 			delete[] new_buf;
35300 
35301 			printed = true;
35302 		}
35303 	}
35304 
35305 	if(!printed)
35306 		fprintf(fp, "<dt><b>%s</b></dt>\n<dd>Min arguments: %d, Max arguments: %d</dd>\n", Operators[sexp_idx].text.c_str(), Operators[sexp_idx].min, Operators[sexp_idx].max);
35307 }
35308 
35309 /**
35310  * Output sexp.html file
35311  */
output_sexps(const char * filepath)35312 bool output_sexps(const char *filepath)
35313 {
35314 	FILE *fp = fopen(filepath,"w");
35315 
35316 	if(fp == nullptr)
35317 	{
35318 		os::dialogs::Message(os::dialogs::MESSAGEBOX_ERROR, "Error creating SEXP operator list");
35319 		return false;
35320 	}
35321 
35322 	//Header
35323 	fprintf(fp, "<html>\n<head>\n\t<title>SEXP Output - FSO v%s</title>\n</head>\n", FS_VERSION_FULL);
35324 	fputs("<body>", fp);
35325 	fprintf(fp,"\t<h1>SEXP Output - FSO v%s</h1>\n", FS_VERSION_FULL);
35326 
35327 	SCP_vector<int> done_sexp_ids;
35328 	int x,y,z;
35329 
35330 	//Output an overview
35331 	fputs("<dl>", fp);
35332 	for(x = 0; x < (int)op_menu.size(); x++)
35333 	{
35334 		fprintf(fp, "<dt><a href=\"#%d\">%s</a></dt>", (op_menu[x].id & OP_CATEGORY_MASK), op_menu[x].name.c_str());
35335 		for(y = 0; y < (int)op_submenu.size(); y++)
35336 		{
35337 			if(((op_submenu[y].id & OP_CATEGORY_MASK) == op_menu[x].id))
35338 			{
35339 				fprintf(fp, "<dd><a href=\"#%d\">%s</a></dd>", op_submenu[y].id & (OP_CATEGORY_MASK | SUBCATEGORY_MASK), op_submenu[y].name.c_str());
35340 			}
35341 		}
35342 	}
35343 	fputs("</dl>", fp);
35344 
35345 	//Output the full descriptions
35346 	fputs("<dl>", fp);
35347 	for(x = 0; x < (int)op_menu.size(); x++)
35348 	{
35349 		fprintf(fp, "<dt id=\"%d\"><h2>%s</h2></dt>\n", (op_menu[x].id & OP_CATEGORY_MASK), op_menu[x].name.c_str());
35350 		fputs("<dd>", fp);
35351 		fputs("<dl>", fp);
35352 		for(y = 0; y < (int)op_submenu.size(); y++)
35353 		{
35354 			if(((op_submenu[y].id & OP_CATEGORY_MASK) == op_menu[x].id))
35355 			{
35356 				fprintf(fp, "<dt id=\"%d\"><h3>%s</h3></dt>\n", op_submenu[y].id & (OP_CATEGORY_MASK | SUBCATEGORY_MASK), op_submenu[y].name.c_str());
35357 				fputs("<dd>", fp);
35358 				fputs("<dl>", fp);
35359 				for(z = 0; z < (int)Operators.size(); z++)
35360 				{
35361 					if((get_category(Operators[z].value) == op_menu[x].id)
35362 						&& (get_subcategory(Operators[z].value) != -1)
35363 						&& (get_subcategory(Operators[z].value) == op_submenu[y].id))
35364 					{
35365 						output_sexp_html(z, fp);
35366 					}
35367 				}
35368 				fputs("</dl>", fp);
35369 				fputs("</dd>", fp);
35370 			}
35371 		}
35372 		for(z = 0; z < (int)Operators.size(); z++)
35373 		{
35374 			if((get_category(Operators[z].value) == op_menu[x].id)
35375 				&& (get_subcategory(Operators[z].value) == -1))
35376 			{
35377 				output_sexp_html(z, fp);
35378 			}
35379 		}
35380 		fputs("</dl>", fp);
35381 		fputs("</dd>", fp);
35382 	}
35383 	for(z = 0; z < (int)Operators.size(); z++)
35384 	{
35385 		if(!get_category(Operators[z].value))
35386 		{
35387 			output_sexp_html(z, fp);
35388 		}
35389 	}
35390 	fputs("</dl>", fp);
35391 	fputs("</body>\n</html>\n", fp);
35392 
35393 	fclose(fp);
35394 
35395 	return true;
35396 }
35397