Files
fs2open.github.com/code/parse/sexp.cpp
T
2002-08-01 01:41:11 +00:00

9730 lines
238 KiB
C++

/*
* Copyright (C) Volition, Inc. 1999. All rights reserved.
*
* All source code herein is the property of Volition, Inc. You may not sell
* or otherwise commercially exploit the source or things you created based on the
* source.
*
*/
/*
* $Logfile: /Freespace2/code/parse/SEXP.CPP $
* $Revision: 2.2 $
* $Date: 2002-08-01 01:41:09 $
* $Author: penguin $
*
* main sexpression generator
*
* $Log: not supported by cvs2svn $
* Revision 2.1 2002/07/13 09:16:18 wmcoolmon
* Added initial code for "ship-lights-on" and "ship-lights-off" SEXPs
*
* Revision 2.0 2002/06/03 04:02:27 penguin
* Warpcore CVS sync
*
* Revision 1.3 2002/05/10 20:42:44 mharris
* use "ifndef NO_NETWORK" all over the place
*
* Revision 1.2 2002/05/03 22:07:09 mharris
* got some stuff to compile
*
* Revision 1.1 2002/05/02 18:03:12 mharris
* Initial checkin - converted filenames and includes to lower case
*
*
* 66 9/13/99 11:38a Andsager
* Stupid switch for sexp_team_score. Don't forget *break*
*
* 65 9/09/99 11:40p Dave
* Handle an Assert() in beam code. Added supernova sounds. Play the right
* 2 end movies properly, based upon what the player did in the mission.
*
* 64 9/07/99 1:05a Andsager
* Added team-score sexp for multi team vs team missions
*
* 63 9/06/99 9:46p Jefff
* skip mission support
*
* 62 8/29/99 2:33p Andsager
* Fix bug in sexp_is_ship_visible()
*
* 61 8/27/99 9:07p Dave
* LOD explosions. Improved beam weapon accuracy.
*
* 60 8/27/99 4:07p Andsager
* Add is-ship-visible sexp. Make ship-vanish sexp SINGLE player only
*
* 59 8/26/99 4:21p Mikek
* Add new special-check for JimB, zeroing front shield quadrant.
*
* 58 8/24/99 4:25p Andsager
* Add ship-vanish sexp
*
* 57 8/23/99 6:05p Johnson
* Oops. Self destruct log should also go into the is-destroyed
* sexpression.
*
* 56 8/23/99 3:23p Johnson
* Fix bug in sexp_facing2 when Player_obj does not yet existl
*
* 55 8/19/99 3:30p Andsager
* Make sexp_special_warp_dist check if ship if facing knossos device
*
* 54 8/19/99 9:20a Andsager
* Enable flashing for all guages
*
* 53 8/19/99 12:31a Andsager
* Add support guage as one that can flash
*
* 52 8/18/99 12:09p Andsager
* Add debug if message has no anim for message. Make messages come from
* wing leader.
*
* 51 8/16/99 10:04p Andsager
* Add special-warp-dist and special-warpout-name sexp for Knossos device
* warpout.
*
* 50 8/09/99 3:32p Andsager
* Make has_time_elapsed work with arithmatic args.
*
* 49 8/09/99 2:00p Dave
* 2 new sexpressions.
*
* 48 8/02/99 4:26p Dave
* Added 2 new sexpressions.
*
* 47 7/31/99 2:30p Dave
* Added nifty mission message debug viewing keys.
*
* 46 7/28/99 1:36p Andsager
* Modify cargo1 to include flag CARGO_NO_DEPLETE. Add sexp
* cargo-no-deplete (only for BIG / HUGE). Modify ship struct to pack
* better.
*
* 45 7/24/99 4:56p Dave
* Added 3 new sexpressions.
*
* 44 7/21/99 8:10p Dave
* First run of supernova effect.
*
* 43 7/20/99 9:19p Andsager
* Added facing waypoint sexp
*
* 42 7/20/99 9:54a Andsager
* Add subsys-set-random sexp
*
* 41 7/19/99 12:02p Andsager
* Allow AWACS on any ship subsystem. Fix sexp_set_subsystem_strength to
* only blow up subsystem if its strength is > 0
*
* 40 7/15/99 9:22p Andsager
* modify secondaries_depleted sexp to report true only when all
* secondaries gone
*
* 39 7/13/99 3:37p Andsager
* Add secondaries-depleted sexp
*
* 38 7/08/99 12:06p Andsager
* Add turret-tagged-only and turret-tagged-clear sexp.
*
* 37 6/28/99 4:51p Andsager
* Add ship-guardian sexp (does not allow ship to be killed)
*
* 36 6/25/99 2:51p Andsager
* Make event_delay, goal_delay, and subsys strength work with sexp_rand
*
* 35 6/23/99 5:51p Andsager
* Add waypoint-cap-speed. Checkin stealth ai - inactive.
*
* 34 6/16/99 10:21a Dave
* Added send-message-list sexpression.
*
* 33 6/15/99 2:53p Andsager
* Fixed subsystem checking for AWACS type subsystems.
*
* 32 6/01/99 8:35p Dave
* Finished lockarm weapons. Added proper supercap weapons/damage. Added
* awacs-set-radius sexpression.
*
* 31 5/27/99 12:14p Andsager
* Some fixes for live debris when more than one subsys on ship with live
* debris. Set subsys strength (when 0) blows off subsystem.
* sexp_hits_left_subsystem works for SUBSYSTEM_UNKNOWN.
*
* 30 5/24/99 11:28a Dave
* Sexpression for adding/removing ships from the hud escort list.
*
* 29 4/28/99 9:33a Andsager
* Add turret-free and turret-lock (and -all) sexp. Stargger start time
* of beam weapons beam-free and beam-free-all.
*
* 28 4/26/99 2:14p Andsager
* Add beam-protect-ship and beam-unprotect-ship sexp.
*
* 27 4/23/99 12:01p Johnson
* Added SIF_HUGE_SHIP
*
* 26 4/23/99 9:45a Andsager
* Modify rand_sexp to return the same value each time it is called on a
* particular node. This prevents random time delay defaulting to min.
*
* 25 4/21/99 6:15p Dave
* Did some serious housecleaning in the beam code. Made it ready to go
* for anti-fighter "pulse" weapons. Fixed collision pair creation. Added
* a handy macro for recalculating collision pairs for a given object.
*
* 24 4/19/99 2:28p Johnson
* DA: Use ship_info flags, not ship_flags for ship type. DOH!!
*
* 23 4/19/99 11:46a Johnson
* DA: DOH!
*
* 22 4/19/99 11:36a Johnson
* Don't display multiple warning messges when large ship gets more than 1
* cargo transfer.
*
* 21 4/02/99 9:55a Dave
* Added a few more options in the weapons.tbl for beam weapons. Attempt
* at putting "pain" packets into multiplayer.
*
* 20 3/31/99 2:22p Johnson
* Fix sexp_repair_subsystem() to accept sexp increments
*
* 19 3/28/99 5:58p Dave
* Added early demo code. Make objects move. Nice and framerate
* independant, but not much else. Don't use yet unless you're me :)
*
* 18 3/20/99 3:46p Dave
* Added support for model-based background nebulae. Added 3 new
* sexpressions.
*
* 17 3/08/99 7:03p Dave
* First run of new object update system. Looks very promising.
*
* 16 3/04/99 6:09p Dave
* Added in sexpressions for firing beams and checking for if a ship is
* tagged.
*
* 15 3/04/99 9:22a Andsager
* Make escort list work with ship-is-visible. When not visible, dump,
* when becoming visible, maybe add.
*
* 14 2/26/99 6:01p Andsager
* Add sexp has-been-tagged-delay and cap-subsys-cargo-known-delay
*
* 13 2/26/99 4:14p Dave
* Put in the ability to have multiple shockwaves for ships.
*
* 12 2/11/99 5:22p Andsager
* Fixed bugs, generalized block Sexp_variables
*
* 11 2/11/99 2:15p Andsager
* Add ship explosion modification to FRED
*
* 10 1/25/99 4:29p Andsager
* Modify sexp_modify_variable() to handle type string. added quick out
* for multiplayer_client
*
* 9 1/25/99 8:11a Andsager
* Add sexp_modify_variable(). Changed syntax checking to allow, adding
* operator return type ambiguous
*
* 8 1/24/99 11:37p Dave
* First full rev of beam weapons. Very customizable. Removed some bogus
* Int3()'s in low level net code.
*
* 7 1/20/99 9:37a Andsager
*
* 6 1/19/99 3:57p Andsager
* Round 2 of variables
*
* 5 1/07/99 1:52p Andsager
* Initial check in of Sexp_variables
*
* 4 11/05/98 5:55p Dave
* Big pass at reducing #includes
*
* 3 10/13/98 9:29a Dave
* Started neatening up freespace.h. Many variables renamed and
* reorganized. Added AlphaColors.[h,cpp]
*
* 2 10/07/98 10:53a Dave
* Initial checkin.
*
* 1 10/07/98 10:50a Dave
*
* 321 9/20/98 7:20p Dave
* Added CHANGE_IFF packet.
*
* 320 9/16/98 10:42a Hoffoss
* Added sexp node counting to fsm files for end user designers.
*
* 319 7/06/98 4:31p Hoffoss
* Fixed bug in sexp parsing when formula is ( ).
*
* 318 6/09/98 5:15p Lawrance
* French/German localization
*
* 317 5/26/98 11:54a Allender
* fix multiplayer problems and sexpression crash
*
* 316 5/23/98 3:16p Allender
* work on object update packet optimizations (a new updating system).
* Don't allow medals/promotions/badges when playing singple player
* missions through the simulator
*
* 315 5/20/98 1:34a Allender
* fixed observer problems. Removed some code offending BoundsCheccker.
* Fixed medals for multiplayer
*
* 314 5/17/98 12:46p Mike
* Change special-check functions for cueing rearm suggestion.
*
* 313 5/14/98 6:29p Hoffoss
* Fixed some warnings a release rebuild all turned up.
*
* 312 5/14/98 10:15a Allender
* add optional argument to prevous-goal/event operators to specify what
* sexpression should return when being played as a single mission
*
* 311 5/08/98 10:16a Lawrance
* Add new "ship attacking count" gauge
*
* 310 4/29/98 11:39p Allender
* fixed nasty problem with is-event-false always returning true right
* away
*
*
*/
// Parse a symbolic expression.
// These are identical to Lisp functions.
// It uses a very baggy format, allocating 16 characters per token, regardless
// of how many are used.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include <limits.h>
#include "parse/parselo.h"
#include "parse/sexp.h"
#include "ship/ship.h"
#include "freespace2/freespace.h"
#include "math/fix.h"
#include "weapon/weapon.h"
#include "mission/missionlog.h"
#include "mission/missionparse.h" // for p_object definition
#include "mission/missionmessage.h"
#include "mission/missiontraining.h"
#include "math/vecmat.h"
#include "globalincs/linklist.h"
#include "model/model.h" // for subsystem types
#include "ship/aigoals.h"
#include "mission/missioncampaign.h"
#include "mission/missiongoals.h"
#include "controlconfig/controlsconfig.h"
#include "io/timer.h"
#include "ship/shiphit.h"
#include "gamesequence/gamesequence.h"
#include "stats/scoring.h"
#include "stats/medals.h"
#include "playerman/player.h"
#include "hud/hudmessage.h"
#include "hud/hud.h"
#include "missionui/redalert.h"
#include "jumpnode/jumpnode.h"
#include "hud/hudshield.h"
#include "hud/hudescort.h"
#include "weapon/beam.h"
#include "starfield/supernova.h"
#include "hud/hudets.h"
#include "math/fvi.h"
#include "ship/awacs.h"
#ifndef NO_NETWORK
#include "network/multi.h"
#include "network/multimsgs.h"
#include "network/multiutil.h"
#include "network/multi_team.h"
#endif
#define TRUE 1
#define FALSE 0
sexp_oper Operators[] = {
// Operator, Identity, Min / Max arguments
{ "+", OP_PLUS, 2, INT_MAX },
{ "-", OP_MINUS, 2, INT_MAX },
{ "*", OP_MUL, 2, INT_MAX },
{ "/", OP_DIV, 2, INT_MAX },
{ "mod", OP_MOD, 2, INT_MAX },
{ "rand", OP_RAND, 2, 2 },
{ "true", OP_TRUE, 0, 0, },
{ "false", OP_FALSE, 0, 0, },
{ "and", OP_AND, 2, INT_MAX, },
{ "and-in-sequence", OP_AND_IN_SEQUENCE, 2, INT_MAX, },
{ "or", OP_OR, 2, INT_MAX, },
{ "not", OP_NOT, 1, 1, },
{ "=", OP_EQUALS, 2, INT_MAX, },
{ ">", OP_GREATER_THAN, 2, 2, },
{ "<", OP_LESS_THAN, 2, 2, },
{ "is-iff", OP_IS_IFF, 2, INT_MAX, },
{ "has-time-elapsed", OP_HAS_TIME_ELAPSED, 1, 1, },
{ "modify-variable", OP_MODIFY_VARIABLE, 2, 2, },
{ "is-goal-incomplete", OP_GOAL_INCOMPLETE, 1, 1, },
{ "is-goal-true-delay", OP_GOAL_TRUE_DELAY, 2, 2, },
{ "is-goal-false-delay", OP_GOAL_FALSE_DELAY, 2, 2, },
{ "is-event-incomplete", OP_EVENT_INCOMPLETE, 1, 1, },
{ "is-event-true-delay", OP_EVENT_TRUE_DELAY, 2, 2, },
{ "is-event-false-delay", OP_EVENT_FALSE_DELAY, 2, 2, },
{ "is-previous-goal-incomplete", OP_PREVIOUS_GOAL_INCOMPLETE, 2, 3, },
{ "is-previous-goal-true", OP_PREVIOUS_GOAL_TRUE, 2, 3, },
{ "is-previous-goal-false", OP_PREVIOUS_GOAL_FALSE, 2, 3, },
{ "is-previous-event-incomplete", OP_PREVIOUS_EVENT_INCOMPLETE, 2, 3, },
{ "is-previous-event-true", OP_PREVIOUS_EVENT_TRUE, 2, 3, },
{ "is-previous-event-false", OP_PREVIOUS_EVENT_FALSE, 2, 3, },
{ "is-destroyed", OP_IS_DESTROYED, 1, INT_MAX, },
{ "is-subsystem-destroyed", OP_IS_SUBSYSTEM_DESTROYED, 2, 2, },
{ "is-disabled", OP_IS_DISABLED, 1, INT_MAX, },
{ "is-disarmed", OP_IS_DISARMED, 1, INT_MAX, },
{ "has-docked", OP_HAS_DOCKED, 3, 3, },
{ "has-undocked", OP_HAS_UNDOCKED, 3, 3, },
{ "has-arrived", OP_HAS_ARRIVED, 1, INT_MAX, },
{ "has-departed", OP_HAS_DEPARTED, 1, INT_MAX, },
{ "is-destroyed-delay", OP_IS_DESTROYED_DELAY, 2, INT_MAX, },
{ "is-subsystem-destroyed-delay", OP_IS_SUBSYSTEM_DESTROYED_DELAY, 3, 3, },
{ "is-disabled-delay", OP_IS_DISABLED_DELAY, 2, INT_MAX, },
{ "is-disarmed-delay", OP_IS_DISARMED_DELAY, 2, INT_MAX, },
{ "has-docked-delay", OP_HAS_DOCKED_DELAY, 4, 4, },
{ "has-undocked-delay", OP_HAS_UNDOCKED_DELAY, 4, 4, },
{ "has-arrived-delay", OP_HAS_ARRIVED_DELAY, 2, INT_MAX, },
{ "has-departed-delay", OP_HAS_DEPARTED_DELAY, 2, INT_MAX, },
{ "are-waypoints-done", OP_WAYPOINTS_DONE, 2, 2, },
{ "are-waypoints-done-delay", OP_WAYPOINTS_DONE_DELAY, 3, 3, },
{ "ship-type-destroyed", OP_SHIP_TYPE_DESTROYED, 2, 2, },
{ "is-event-true", OP_EVENT_TRUE, 1, 1, },
{ "is-event-false", OP_EVENT_FALSE, 1, 1, },
{ "is-cargo-known", OP_IS_CARGO_KNOWN, 1, INT_MAX, },
{ "was-promotion-granted", OP_WAS_PROMOTION_GRANTED, 0, 1, },
{ "was-medal-granted", OP_WAS_MEDAL_GRANTED, 0, 1, },
{ "percent-ships-departed", OP_PERCENT_SHIPS_DEPARTED, 2, INT_MAX },
{ "percent-ships-destroyed", OP_PERCENT_SHIPS_DESTROYED, 2, INT_MAX },
{ "is-cargo-known-delay", OP_CARGO_KNOWN_DELAY, 2, INT_MAX, },
{ "cap-subsys-cargo-known-delay", OP_CAP_SUBSYS_CARGO_KNOWN_DELAY, 3, INT_MAX, },
{ "has-been-tagged-delay", OP_HAS_BEEN_TAGGED_DELAY, 2, INT_MAX, },
{ "depart-node-delay", OP_DEPART_NODE_DELAY, 3, INT_MAX, },
{ "destroyed-or-departed-delay", OP_DESTROYED_DEPARTED_DELAY, 2, INT_MAX, },
{ "is_tagged", OP_IS_TAGGED, 1, 1 },
{ "num_kills", OP_NUM_KILLS, 1, 1 },
{ "num_type_kills", OP_NUM_TYPE_KILLS, 2, 2 },
{ "num_class_kills", OP_NUM_CLASS_KILLS, 2, 2 },
{ "shield-recharge-pct", OP_SHIELD_RECHARGE_PCT, 1, 1 },
{ "engine-recharge-pct", OP_ENGINE_RECHARGE_PCT, 1, 1 },
{ "weapon-recharge-pct", OP_WEAPON_RECHARGE_PCT, 1, 1 },
{ "shield-quad-low", OP_SHIELD_QUAD_LOW, 2, 2 },
{ "secondary-ammo-pct", OP_SECONDARY_AMMO_PCT, 2, 2 },
{ "is-secondary-selected", OP_IS_SECONDARY_SELECTED, 2, 2 },
{ "is-primary-selected", OP_IS_PRIMARY_SELECTED, 2, 2 },
{ "time-ship-destroyed", OP_TIME_SHIP_DESTROYED, 1, 1, },
{ "time-ship-arrived", OP_TIME_SHIP_ARRIVED, 1, 1, },
{ "time-ship-departed", OP_TIME_SHIP_DEPARTED, 1, 1, },
{ "time-wing-destroyed", OP_TIME_WING_DESTROYED, 1, 1, },
{ "time-wing-arrived", OP_TIME_WING_ARRIVED, 1, 1, },
{ "time-wing-departed", OP_TIME_WING_DEPARTED, 1, 1, },
{ "mission-time", OP_MISSION_TIME, 0, 0, },
{ "time-docked", OP_TIME_DOCKED, 3, 3, },
{ "time-undocked", OP_TIME_UNDOCKED, 3, 3, },
{ "shields-left", OP_SHIELDS_LEFT, 1, 1, },
{ "hits-left", OP_HITS_LEFT, 1, 1, },
{ "hits-left-subsystem", OP_HITS_LEFT_SUBSYSTEM, 2, 2, },
{ "distance", OP_DISTANCE, 2, 2, },
{ "is-ship-visible", OP_IS_SHIP_VISIBLE, 1, 1, },
{ "team-score", OP_TEAM_SCORE, 1, 1, },
{ "time-elapsed-last-order", OP_LAST_ORDER_TIME, 2, 2, /*INT_MAX*/ },
{ "skill-level-at-least", OP_SKILL_LEVEL_AT_LEAST, 1, 1, },
{ "num-players", OP_NUM_PLAYERS, 0, 0, },
{ "special-warp-dist", OP_SPECIAL_WARP_DISTANCE, 1, 1, },
{ "special-warpout-name", OP_SET_SPECIAL_WARPOUT_NAME, 2, 2 },
{ "do-nothing", OP_NOP, 0, 0, },
{ "when", OP_WHEN, 2, INT_MAX, },
{ "cond", OP_COND, 1, INT_MAX, },
{ "add-goal", OP_ADD_GOAL, 2, 2, },
{ "add-ship-goal", OP_ADD_SHIP_GOAL, 2, 2, },
{ "add-wing-goal", OP_ADD_WING_GOAL, 2, 2, },
{ "clear-goals", OP_CLEAR_GOALS, 1, INT_MAX, },
{ "clear-ship-goals", OP_CLEAR_SHIP_GOALS, 1, 1, },
{ "clear-wing-goals", OP_CLEAR_WING_GOALS, 1, 1, },
{ "change-iff", OP_CHANGE_IFF, 2, INT_MAX, },
{ "protect-ship", OP_PROTECT_SHIP, 1, INT_MAX, },
{ "unprotect-ship", OP_UNPROTECT_SHIP, 1, INT_MAX, },
{ "beam-protect-ship", OP_BEAM_PROTECT_SHIP, 1, INT_MAX, },
{ "beam-unprotect-ship", OP_BEAM_UNPROTECT_SHIP, 1, INT_MAX, },
{ "send-message", OP_SEND_MESSAGE, 3, 3, },
{ "self-destruct", OP_SELF_DESTRUCT, 1, INT_MAX, },
{ "next-mission", OP_NEXT_MISSION, 1, 1, },
{ "end-campaign", OP_END_CAMPAIGN, 0, 0, },
{ "end-of-campaign", OP_END_OF_CAMPAIGN, 0, 0, },
{ "sabotage-subsystem", OP_SABOTAGE_SUBSYSTEM, 3, 3, },
{ "repair-subsystem", OP_REPAIR_SUBSYSTEM, 3, 3, },
{ "set-subsystem-strength", OP_SET_SUBSYSTEM_STRNGTH, 3, 3, },
{ "invalidate-goal", OP_INVALIDATE_GOAL, 1, INT_MAX, },
{ "validate-goal", OP_VALIDATE_GOAL, 1, INT_MAX, },
{ "send-random-message", OP_SEND_RANDOM_MESSAGE, 3, INT_MAX, },
{ "transfer-cargo", OP_TRANSFER_CARGO, 2, 2, },
{ "exchange-cargo", OP_EXCHANGE_CARGO, 2, 2, },
{ "end-misison-delay", OP_END_MISSION_DELAY, 1, 1, },
{ "good-rearm-time", OP_GOOD_REARM_TIME, 2, 2, },
{ "grant-promotion", OP_GRANT_PROMOTION, 0, 0, },
{ "grant-medal", OP_GRANT_MEDAL, 1, 1, },
{ "allow-ship", OP_ALLOW_SHIP, 1, 1, },
{ "allow-weapon", OP_ALLOW_WEAPON, 1, 1, },
{ "break-warp", OP_WARP_BROKEN, 1, INT_MAX, },
{ "fix-warp", OP_WARP_NOT_BROKEN, 1, INT_MAX, },
{ "never-warp", OP_WARP_NEVER, 1, INT_MAX, },
{ "allow-warp", OP_WARP_ALLOWED, 1, INT_MAX, },
{ "good-secondary-time", OP_GOOD_SECONDARY_TIME, 4, 4, },
{ "ship-invisible", OP_SHIP_INVISIBLE, 1, INT_MAX },
{ "ship-visible", OP_SHIP_VISIBLE, 1, INT_MAX },
{ "ship-invulnerable", OP_SHIP_INVULNERABLE, 1, INT_MAX },
{ "ship-vulnerable", OP_SHIP_VULNERABLE, 1, INT_MAX },
{ "red-alert", OP_RED_ALERT, 0, 0 },
{ "tech-add-ships", OP_TECH_ADD_SHIP, 1, INT_MAX },
{ "tech-add-weapons", OP_TECH_ADD_WEAPON, 1, INT_MAX },
{ "jettison-cargo-delay", OP_JETTISON_CARGO, 2, 2 },
{ "fire-beam", OP_BEAM_FIRE, 3, 4 },
{ "beam-free", OP_BEAM_FREE, 2, INT_MAX },
{ "beam-free-all", OP_BEAM_FREE_ALL, 1, 1 },
{ "beam-lock", OP_BEAM_LOCK, 2, INT_MAX },
{ "beam-lock-all", OP_BEAM_LOCK_ALL, 1, 1 },
{ "turret-free", OP_TURRET_FREE, 2, INT_MAX },
{ "turret-free-all", OP_TURRET_FREE_ALL, 1, 1 },
{ "turret-lock", OP_TURRET_LOCK, 2, INT_MAX },
{ "turret-lock-all", OP_TURRET_LOCK_ALL, 1, 1 },
{ "add-remove-escort", OP_ADD_REMOVE_ESCORT, 2, 2 },
{ "awacs-set-radius", OP_AWACS_SET_RADIUS, 3, 3 },
{ "send-message-list", OP_SEND_MESSAGE_LIST, 4, INT_MAX },
{ "cap-waypoint-speed", OP_CAP_WAYPOINT_SPEED, 2, 2 },
{ "ship-guardian", OP_SHIP_GUARDIAN, 1, INT_MAX },
{ "ship-no-guardian", OP_SHIP_NO_GUARDIAN, 1, INT_MAX },
{ "ship-vanish", OP_SHIP_VANISH, 1, INT_MAX },
{ "ship-lights-on", OP_SHIP_LIGHTS_ON, 1, 1 }, //-WMCoolmon
{ "ship-lights-off", OP_SHIP_LIGHTS_OFF, 1, 1 }, //-WMCoolmon
{ "turret-tagged-only", OP_TURRET_TAGGED_ONLY_ALL, 1, 1 },
{ "turret-tagged-clear", OP_TURRET_TAGGED_CLEAR_ALL, 1, 1 },
{ "subsys-set-random", OP_SUBSYS_SET_RANDOM, 3, INT_MAX },
{ "supernova-start", OP_SUPERNOVA_START, 1, 1 },
{ "cargo-no-deplete", OP_CARGO_NO_DEPLETE, 1, 2 },
{ "error", OP_INT3, 0, 0 },
{ "ai-chase", OP_AI_CHASE, 2, 2, },
{ "ai-chase-wing", OP_AI_CHASE_WING, 2, 2, },
{ "ai-dock", OP_AI_DOCK, 4, 4, },
{ "ai-undock", OP_AI_UNDOCK, 1, 1, },
{ "ai-warp", OP_AI_WARP, 2, 2, },
{ "ai-warp-out", OP_AI_WARP_OUT, 1, 1, },
{ "ai-waypoints", OP_AI_WAYPOINTS, 2, 2, },
{ "ai-waypoints-once", OP_AI_WAYPOINTS_ONCE, 2, 2, },
{ "ai-destroy-subsystem", OP_AI_DESTROY_SUBSYS, 3, 3, },
{ "ai-disable-ship", OP_AI_DISABLE_SHIP, 2, 2, },
{ "ai-disarm-ship", OP_AI_DISARM_SHIP, 2, 2, },
{ "ai-guard", OP_AI_GUARD, 2, 2, },
{ "ai-chase-any", OP_AI_CHASE_ANY, 1, 1, },
{ "ai-guard-wing", OP_AI_GUARD_WING, 2, 2, },
{ "ai-evade-ship", OP_AI_EVADE_SHIP, 2, 2, },
{ "ai-stay-near-ship", OP_AI_STAY_NEAR_SHIP, 2, 2, },
{ "ai-keep-safe-distance", OP_AI_KEEP_SAFE_DISTANCE, 1, 1, },
{ "ai-ignore", OP_AI_IGNORE, 2, 2, },
{ "ai-stay-still", OP_AI_STAY_STILL, 2, 2, },
{ "ai-play-dead", OP_AI_PLAY_DEAD, 1, 1, },
{ "goals", OP_GOALS_ID, 1, INT_MAX, },
{ "key-pressed", OP_KEY_PRESSED, 1, 2, },
{ "key-reset", OP_KEY_RESET, 1, INT_MAX, },
{ "targeted", OP_TARGETED, 1, 3, },
{ "speed", OP_SPEED, 1, 1, },
{ "facing", OP_FACING, 2, 2, },
{ "facing-waypoint", OP_FACING2, 2, 2, },
{ "order", OP_ORDER, 2, 2, },
{ "waypoint-missed", OP_WAYPOINT_MISSED, 0, 0, },
{ "waypoint-twice", OP_WAYPOINT_TWICE, 0, 0, },
{ "path-flown", OP_PATH_FLOWN, 0, 0, },
{ "training-msg", OP_TRAINING_MSG, 1, 4, },
{ "flash-hud-gauge", OP_FLASH_HUD_GAUGE, 1, 1, },
{ "special-check", OP_SPECIAL_CHECK, 1, 1, },
{ "secondaries-depleted", OP_SECONDARIES_DEPLETED, 1, 1, },
{ "set-training-context-fly-path", OP_SET_TRAINING_CONTEXT_FLY_PATH, 2, 2, },
{ "set-training-context-speed", OP_SET_TRAINING_CONTEXT_SPEED, 2, 2, },
};
sexp_ai_goal_link Sexp_ai_goal_links[] = {
{ AI_GOAL_CHASE, OP_AI_CHASE },
{ AI_GOAL_CHASE_WING, OP_AI_CHASE_WING },
{ AI_GOAL_DOCK, OP_AI_DOCK },
{ AI_GOAL_UNDOCK, OP_AI_UNDOCK },
{ AI_GOAL_WARP, OP_AI_WARP_OUT },
{ AI_GOAL_WARP, OP_AI_WARP },
{ AI_GOAL_WAYPOINTS, OP_AI_WAYPOINTS },
{ AI_GOAL_WAYPOINTS_ONCE, OP_AI_WAYPOINTS_ONCE },
{ AI_GOAL_DESTROY_SUBSYSTEM, OP_AI_DESTROY_SUBSYS },
{ AI_GOAL_DISABLE_SHIP, OP_AI_DISABLE_SHIP },
{ AI_GOAL_DISARM_SHIP, OP_AI_DISARM_SHIP },
{ AI_GOAL_GUARD, OP_AI_GUARD },
{ AI_GOAL_CHASE_ANY, OP_AI_CHASE_ANY },
{ AI_GOAL_GUARD_WING, OP_AI_GUARD_WING },
{ AI_GOAL_EVADE_SHIP, OP_AI_EVADE_SHIP },
{ AI_GOAL_STAY_NEAR_SHIP, OP_AI_STAY_NEAR_SHIP },
{ AI_GOAL_KEEP_SAFE_DISTANCE, OP_AI_KEEP_SAFE_DISTANCE },
{ AI_GOAL_IGNORE, OP_AI_IGNORE },
{ AI_GOAL_STAY_STILL, OP_AI_STAY_STILL },
{ AI_GOAL_PLAY_DEAD, OP_AI_PLAY_DEAD },
};
char *HUD_gauge_text[NUM_HUD_GAUGES] =
{
"LEAD_INDICATOR",
"ORIENTATION_TEE",
"HOSTILE_TRIANGLE",
"TARGET_TRIANGLE",
"MISSION_TIME",
"RETICLE_CIRCLE",
"THROTTLE_GAUGE",
"RADAR",
"TARGET_MONITOR",
"CENTER_RETICLE",
"TARGET_MONITOR_EXTRA_DATA",
"TARGET_SHIELD_ICON",
"PLAYER_SHIELD_ICON",
"ETS_GAUGE",
"AUTO_TARGET",
"AUTO_SPEED",
"WEAPONS_GAUGE",
"ESCORT_VIEW",
"DIRECTIVES_VIEW",
"THREAT_GAUGE",
"AFTERBURNER_ENERGY",
"WEAPONS_ENERGY",
"WEAPON_LINKING_GAUGE",
"TARGER_MINI_ICON",
"OFFSCREEN_INDICATOR",
"TALKING_HEAD",
"DAMAGE_GAUGE",
"MESSAGE_LINES",
"MISSILE_WARNING_ARROW",
"CMEASURE_GAUGE",
"OBJECTIVES_NOTIFY_GAUGE",
"WINGMEN_STATUS",
"OFFSCREEN RANGE",
"KILLS GAUGE",
"ATTACKING TARGET COUNT",
"TEXT FLASH",
"MESSAGE BOX",
"SUPPORT GUAGE",
"LAG GUAGE"
};
int Directive_count;
int Sexp_useful_number; // a variable to pass useful info in from external modules
int Locked_sexp_true, Locked_sexp_false;
int Num_operators = sizeof(Operators) / sizeof(sexp_oper);
int Num_sexp_ai_goal_links = sizeof(Sexp_ai_goal_links) / sizeof(sexp_ai_goal_link);
int Sexp_build_flag;
int Sexp_clipboard = -1; // used by Fred
int Training_context = 0;
int Training_context_speed_set;
int Training_context_speed_min;
int Training_context_speed_max;
int Training_context_speed_timestamp;
int Training_context_path;
int Training_context_goal_waypoint;
int Training_context_at_waypoint;
float Training_context_distance;
char Sexp_error_text[MAX_SEXP_TEXT];
char *Sexp_string; //[1024] = {0};
sexp_node Sexp_nodes[MAX_SEXP_NODES];
sexp_variable Sexp_variables[MAX_SEXP_VARIABLES];
int Players_target = UNINITIALIZED;
ship_subsys *Players_targeted_subsys;
int Players_target_timestamp;
int get_sexp(char *token);
int eval_sexp(int cur_node);
void build_extended_sexp_string(int cur_node, int level, int mode);
void update_sexp_references(char *old_name, char *new_name, int format, int node);
int sexp_determine_team(char *subj);
int sexp_distance2(int obj1, char *subj);
int sexp_distance3(int obj1, int obj2);
int extract_sexp_variable_index(int node);
void init_sexp_vars();
int num_eval(int node);
void init_sexp()
{
int i;
for (i=0; i<MAX_SEXP_NODES; i++) {
if ( !(Sexp_nodes[i].type & SEXP_FLAG_PERSISTENT) ){
Sexp_nodes[i].type = SEXP_NOT_USED;
}
}
init_sexp_vars();
Locked_sexp_false = Locked_sexp_true = -1;
Locked_sexp_false = alloc_sexp("false", SEXP_LIST, SEXP_ATOM_OPERATOR, -1, -1);
Assert(Locked_sexp_false != -1);
Sexp_nodes[Locked_sexp_false].type = SEXP_ATOM; // fix bypassing value
Locked_sexp_true = alloc_sexp("true", SEXP_LIST, SEXP_ATOM_OPERATOR, -1, -1);
Assert(Locked_sexp_true != -1);
Sexp_nodes[Locked_sexp_true].type = SEXP_ATOM; // fix bypassing value
}
// allocates an sexp node.
int alloc_sexp(char *text, int type, int subtype, int first, int rest)
{
int i;
i = find_operator(text);
if ((i == OP_TRUE) && (type == SEXP_ATOM) && (subtype == SEXP_ATOM_OPERATOR)) {
return Locked_sexp_true;
} else if ((i == OP_FALSE) && (type == SEXP_ATOM) && (subtype == SEXP_ATOM_OPERATOR)) {
return Locked_sexp_false;
}
i = find_free_sexp();
Assert(i != Locked_sexp_true);
Assert(i != Locked_sexp_false);
if (i == MAX_SEXP_NODES){
return -1;
}
Assert(strlen(text) < TOKEN_LENGTH);
strcpy(Sexp_nodes[i].text, text);
Assert(type >= 0);
Sexp_nodes[i].type = type;
Sexp_nodes[i].subtype = subtype;
Sexp_nodes[i].first = first;
Sexp_nodes[i].rest = rest;
Sexp_nodes[i].value = SEXP_UNKNOWN;
return i;
}
int Sexp_hwm = 0;
int count_free_sexp_nodes()
{
int i, f = 0, p = 0;
for (i=0; i<MAX_SEXP_NODES; i++) {
if (Sexp_nodes[i].type == SEXP_NOT_USED){
f++;
} else if (Sexp_nodes[i].type & SEXP_FLAG_PERSISTENT){
p++;
}
}
if (MAX_SEXP_NODES - f > Sexp_hwm) {
nprintf(("Sexp", "Sexp nodes: Free=%d, Used=%d, Persistent=%d\n", f, MAX_SEXP_NODES - f, p));
Sexp_hwm = MAX_SEXP_NODES - f;
}
return f;
}
// find the next free sexp and return it's index.
int find_free_sexp()
{
int i;
for (i=0; i<MAX_SEXP_NODES; i++){
if (Sexp_nodes[i].type == SEXP_NOT_USED){
break;
}
}
#ifndef NDEBUG
//count_free_sexp_nodes();
#endif
Assert(i != MAX_SEXP_NODES); // time to raise the limit..
if (i == MAX_SEXP_NODES){
return -1;
}
return i;
}
// sexp_mark_persistent() marks a whole sexp tree with the persistent flag so that it won't
// get re-used between missions
void sexp_mark_persistent( int n )
{
if (n == -1){
return;
}
// total hack because of the true/false locked sexps -- we should make those persistent as well
if ( (n == Locked_sexp_true) || (n == Locked_sexp_false) ){
return;
}
Assert( !(Sexp_nodes[n].type & SEXP_FLAG_PERSISTENT) );
Sexp_nodes[n].type |= SEXP_FLAG_PERSISTENT;
sexp_mark_persistent(Sexp_nodes[n].first);
sexp_mark_persistent(Sexp_nodes[n].rest);
}
// sexp_unmark_persistent() removes the persistent flag from all nodes in the tree
void sexp_unmark_persistent( int n )
{
if (n == -1){
return;
}
if ( (n == Locked_sexp_true) || (n == Locked_sexp_false) ){
return;
}
Assert( Sexp_nodes[n].type & SEXP_FLAG_PERSISTENT );
Sexp_nodes[n].type &= ~SEXP_FLAG_PERSISTENT;
sexp_unmark_persistent(Sexp_nodes[n].first);
sexp_unmark_persistent(Sexp_nodes[n].rest);
}
// just frees up the specified sexp node, Leaves link chains untouched.
int free_one_sexp(int num)
{
Assert(Sexp_nodes[num].type != SEXP_NOT_USED); // make sure it is actually used
Assert( !(Sexp_nodes[num].type & SEXP_FLAG_PERSISTENT) );
if ((num == Locked_sexp_true) || (num == Locked_sexp_false)){
return 0;
}
Sexp_nodes[num].type = SEXP_NOT_USED;
return 1;
}
// frees a used sexp node, so it can be reused later. Should only be called on
// an atom or a list, and not an operator. If on a list, the list and everything
// in it will be freed (including the operator).
int free_sexp(int num)
{
int i, rest, count = 0;
Assert(Sexp_nodes[num].type != SEXP_NOT_USED); // make sure it is actually used
Assert( !(Sexp_nodes[num].type & SEXP_FLAG_PERSISTENT) );
if ((num == Locked_sexp_true) || (num == Locked_sexp_false) || (num == -1) ){
return 0;
}
Sexp_nodes[num].type = SEXP_NOT_USED;
count++;
i = Sexp_nodes[num].first;
while (i != -1){
count += free_sexp(i);
i = Sexp_nodes[i].rest;
}
rest = Sexp_nodes[num].rest;
for (i=0; i<MAX_SEXP_NODES; i++) {
if (Sexp_nodes[i].first == num){
Sexp_nodes[i].first = rest;
}
if (Sexp_nodes[i].rest == num){
Sexp_nodes[i].rest = rest;
}
}
return count; // total elements freed up.
}
// used to free up an entire sexp tree. Because the root node is an operator, instead of
// a list, we can't simply call free_sexp(). This function should only be called on the
// root node of an sexp, otherwise the linking will get screwed up.
int free_sexp2(int num)
{
int i, count = 0;
if ((num == -1) || (num == Locked_sexp_true) || (num == Locked_sexp_false)){
return 0;
}
i = Sexp_nodes[num].rest;
while (i != -1) {
count += free_sexp(i);
i = Sexp_nodes[i].rest;
}
count += free_sexp(num);
return count;
}
// This function resets the status of all the nodes in a tree, forcing them to all be
// evaulated again.
void flush_sexp_tree(int node)
{
if (node < 0){
return;
}
Sexp_nodes[node].value = SEXP_UNKNOWN;
flush_sexp_tree(Sexp_nodes[node].first);
flush_sexp_tree(Sexp_nodes[node].rest);
}
int verify_sexp_tree(int node)
{
if (node == -1){
return 0;
}
if ((Sexp_nodes[node].type == SEXP_NOT_USED) ||
(Sexp_nodes[node].first == node) ||
(Sexp_nodes[node].rest == node)) {
Error(LOCATION, "Sexp node is corrupt");
return -1;
}
if (Sexp_nodes[node].first != -1){
verify_sexp_tree(Sexp_nodes[node].first);
}
if (Sexp_nodes[node].rest != -1){
verify_sexp_tree(Sexp_nodes[node].rest);
}
return 0;
}
int dup_sexp_chain(int node)
{
int cur, first, rest;
if (node == -1){
return -1;
}
// TODO - CASE OF SEXP VARIABLES - ONLY 1 COPY OF VARIABLE
first = dup_sexp_chain(Sexp_nodes[node].first);
rest = dup_sexp_chain(Sexp_nodes[node].rest);
cur = alloc_sexp(Sexp_nodes[node].text, Sexp_nodes[node].type, Sexp_nodes[node].subtype, first, rest);
if (cur == -1) {
if (first != -1){
free_sexp(first);
}
if (rest != -1){
free_sexp(rest);
}
}
return cur;
}
// returns 1 if they are the same, 0 if different
int cmp_sexp_chains(int node1, int node2)
{
if ((node1 == -1) && (node2 == -1)){
return 1;
}
if ((node1 == -1) || (node2 == -1)){
return 0;
}
// DA: 1/7/99 Need to check the actual Sexp_node.text, not possible variable, which can be equal
if (stricmp(Sexp_nodes[node1].text, Sexp_nodes[node2].text)){
return 0;
}
if (!cmp_sexp_chains(Sexp_nodes[node1].first, Sexp_nodes[node2].first)){
return 0;
}
if (!cmp_sexp_chains(Sexp_nodes[node1].rest, Sexp_nodes[node2].rest)){
return 0;
}
return 1;
}
// determine if an sexp node is within the given sexp chain.
int query_node_in_sexp(int node, int sexp)
{
if (sexp == -1){
return 0;
}
if (node == sexp){
return 1;
}
if (query_node_in_sexp(node, Sexp_nodes[sexp].first)){
return 1;
}
if (query_node_in_sexp(node, Sexp_nodes[sexp].rest)){
return 1;
}
return 0;
}
// find the index of the list associated with an operator
int find_sexp_list(int num)
{
int i;
for (i=0; i<MAX_SEXP_NODES; i++){
if (Sexp_nodes[i].first == num){
return i;
}
}
return -1;
}
// find index of operator that item is an argument of.
int find_parent_operator(int num)
{
int i;
if (Sexp_nodes[num].subtype == SEXP_ATOM_OPERATOR){
num = find_sexp_list(num);
}
while (Sexp_nodes[num].subtype != SEXP_ATOM_OPERATOR) {
for (i=0; i<MAX_SEXP_NODES; i++){
if (Sexp_nodes[i].rest == num){
break;
}
}
if (i == MAX_SEXP_NODES){
return -1; // not found, probably at top node already.
}
num = i;
}
return num;
}
// function to determine if an sexpression node is the top level node of an sexpression tree. Top
// level nodes do not have their node id in anyone elses first or rest index
int is_sexp_top_level( int node )
{
int i;
if ( Sexp_nodes[node].type == SEXP_NOT_USED ){
return 0;
}
for (i = 0; i < MAX_SEXP_NODES; i++ ) {
if ( (Sexp_nodes[i].type == SEXP_NOT_USED) || ( i == node ) ){ // don't check myself or unused nodes
continue;
}
if ( (Sexp_nodes[i].first == node) || (Sexp_nodes[i].rest == node) ){
return 0;
}
}
return 1;
}
int identify_operator(char *token)
{
int i;
for (i=0; i<Num_operators; i++){
if (!stricmp(token, Operators[i].text)){
return i;
}
}
return -1;
}
int find_operator(char *token)
{
int i;
for (i=0; i<Num_operators; i++){
if (!stricmp(token, Operators[i].text)){
return Operators[i].value;
}
}
return 0;
}
int query_sexp_args_count(int index)
{
int count = 0;
while (Sexp_nodes[index].rest != -1){
count++;
index = Sexp_nodes[index].rest;
}
return count;
}
// returns 0 if ok, negative if there's an error in expression..
// See the returns types in sexp.h
int check_sexp_syntax(int index, int return_type, int recursive, int *bad_index, int mode)
{
int i = 0, z, t, type, argnum = 0, count, op, type2 = 0, op2;
int op_index;
Assert(index >= 0 && index < MAX_SEXP_NODES);
Assert(Sexp_nodes[index].type != SEXP_NOT_USED);
if (Sexp_nodes[index].subtype == SEXP_ATOM_NUMBER && return_type == OPR_BOOL) {
// special case Mark seems to want supported
Assert(Sexp_nodes[index].first == -1); // only lists should have a first pointer
if (Sexp_nodes[index].rest != -1) // anything after the number?
return SEXP_CHECK_NONOP_ARGS; // if so, it's a syntax error
return 0;
}
op_index = index; // save the index of the operator since we need to get to other args.
if (bad_index)
*bad_index = op_index;
if (Sexp_nodes[op_index].subtype != SEXP_ATOM_OPERATOR)
return SEXP_CHECK_OP_EXPTECTED; // not an operator, which it should always be
op = identify_operator(CTEXT(op_index));
if (op == -1)
return SEXP_CHECK_UNKNOWN_OP; // unrecognized operator
//special case - OPR_AMBIGUOUS matches all
if (return_type != OPR_AMBIGUOUS) {
if (query_operator_return_type(op) != return_type) {
return SEXP_CHECK_TYPE_MISMATCH;
}
}
count = query_sexp_args_count(op_index);
if (count < Operators[op].min || count > Operators[op].max)
return SEXP_CHECK_BAD_ARG_COUNT; // incorrect number of arguments
index = Sexp_nodes[op_index].rest;
while (index != -1) {
type = query_operator_argument_type(op, argnum);
Assert(Sexp_nodes[index].type != SEXP_NOT_USED);
if (bad_index)
*bad_index = index;
if (Sexp_nodes[index].subtype == SEXP_ATOM_LIST) {
i = Sexp_nodes[index].first;
if (bad_index)
*bad_index = i;
// be sure to check to see if this node is a list of stuff and not an actual operator type
// thing. (i.e. in the case of a cond statement, the conditional will fall into this if
// statement. MORE TO DO HERE!!!!
if (Sexp_nodes[i].subtype == SEXP_ATOM_LIST)
return 0;
op2 = identify_operator(CTEXT(i));
if (op2 == -1)
return SEXP_CHECK_UNKNOWN_OP;
type2 = query_operator_return_type(op2);
if (recursive) {
switch (type) {
case OPF_NUMBER:
t = OPR_NUMBER;
break;
case OPF_POSITIVE:
t = OPR_POSITIVE;
break;
case OPF_BOOL:
t = OPR_BOOL;
break;
case OPF_NULL:
t = OPR_NULL;
break;
case OPF_AI_GOAL:
t = OPR_AI_GOAL;
break;
// special case for modify-variable"
case OPF_AMBIGUOUS:
t = OPR_AMBIGUOUS;
break;
default:
return SEXP_CHECK_UNKNOWN_TYPE; // no other return types available
}
if ((z = check_sexp_syntax(i, t, recursive, bad_index)) != 0) {
return z;
}
}
} else if (Sexp_nodes[index].subtype == SEXP_ATOM_NUMBER) {
char *ptr;
type2 = OPR_POSITIVE;
ptr = CTEXT(index);
if (*ptr == '-') {
type2 = OPR_NUMBER;
ptr++;
}
if (type == OPF_BOOL) // allow numbers to be used where boolean is required.
type2 = OPR_BOOL;
while (*ptr) {
if (!isdigit(*ptr))
return SEXP_CHECK_INVALID_NUM; // not a valid number
ptr++;
}
i = atoi(CTEXT(index));
z = find_operator(CTEXT(op_index));
if ( (z == OP_HAS_DOCKED_DELAY) || (z == OP_HAS_UNDOCKED_DELAY) )
if ( (argnum == 2) && (i < 1) )
return SEXP_CHECK_NUM_RANGE_INVALID;
z = identify_operator(CTEXT(op_index));
if ( (query_operator_return_type(z) == OPR_AI_GOAL) && (argnum == Operators[op].min - 1) )
if ( (i < 0) || (i > 89) )
return SEXP_CHECK_NUM_RANGE_INVALID;
} else if (Sexp_nodes[index].subtype == SEXP_ATOM_STRING) {
type2 = SEXP_ATOM_STRING;
} else {
Assert(0);
}
switch (type) {
case OPF_NUMBER:
if ((type2 != OPR_NUMBER) && (type2 != OPR_POSITIVE)){
return SEXP_CHECK_TYPE_MISMATCH;
}
break;
case OPF_POSITIVE:
if (type2 == OPR_NUMBER){
return SEXP_CHECK_NEGATIVE_NUM;
}
if (type2 != OPR_POSITIVE){
return SEXP_CHECK_TYPE_MISMATCH;
}
break;
case OPF_SHIP_NOT_PLAYER:
if (type2 != SEXP_ATOM_STRING){
return SEXP_CHECK_TYPE_MISMATCH;
}
if (ship_name_lookup(CTEXT(index), 0) < 0) {
if (Fred_running || mission_parse_ship_arrived(CTEXT(index))){ // == 0 when still on arrival list
return SEXP_CHECK_INVALID_SHIP;
}
}
break;
case OPF_SHIP:
case OPF_SHIP_POINT:
if (type2 != SEXP_ATOM_STRING){
return SEXP_CHECK_TYPE_MISMATCH;
}
if (ship_name_lookup(CTEXT(index), 1) < 0) {
if (Fred_running || mission_parse_ship_arrived(CTEXT(index))) { // == 0 when still on arrival list
if (type == OPF_SHIP){ // return invalid ship if not also looking for point
return SEXP_CHECK_INVALID_SHIP;
}
if (waypoint_lookup(CTEXT(index)) < 0){
if (verify_vector(CTEXT(index))){ // verify return non-zero on invalid point
return SEXP_CHECK_INVALID_POINT;
}
}
}
}
break;
case OPF_WING:
if (type2 != SEXP_ATOM_STRING){
return SEXP_CHECK_TYPE_MISMATCH;
}
if (wing_name_lookup(CTEXT(index), 1) < 0){
return SEXP_CHECK_INVALID_WING;
}
break;
case OPF_SHIP_WING:
case OPF_SHIP_WING_POINT:
if ( type2 != SEXP_ATOM_STRING ){
return SEXP_CHECK_TYPE_MISMATCH;
}
if ((ship_name_lookup(CTEXT(index), 1) < 0) && (wing_name_lookup(CTEXT(index), 1) < 0)) {
if (Fred_running || mission_parse_ship_arrived(CTEXT(index))) { // == 0 when still on arrival list
if (type != OPF_SHIP_WING_POINT){ // return invalid if not also looking for point
return SEXP_CHECK_INVALID_SHIP_WING;
}
if (waypoint_lookup(CTEXT(index)) < 0){
if (verify_vector(CTEXT(index))){ // non-zero on verify vector mean invalid!
if (!sexp_determine_team(CTEXT(index))){
return SEXP_CHECK_INVALID_POINT;
}
}
}
}
}
break;
case OPF_AWACS_SUBSYSTEM:
case OPF_SUBSYSTEM: {
char *shipname;
int shipnum,ship_class, i;
int ship_index;
if (type2 != SEXP_ATOM_STRING){
return SEXP_CHECK_TYPE_MISMATCH;
}
// we must get the model of the ship that is part of this sexpression and find a subsystem
// with that name. This code assumes that the ship *always* preceeds the subsystem in an sexpression.
// if this current fact is ever not the case, the next lines of code must be changes to get the correct
// shipname.
switch(Operators[identify_operator(CTEXT(op_index))].value){
case OP_CAP_SUBSYS_CARGO_KNOWN_DELAY:
ship_index = Sexp_nodes[Sexp_nodes[op_index].rest].rest;
break;
case OP_BEAM_FIRE:
if(argnum == 1){
ship_index = Sexp_nodes[op_index].rest;
} else {
ship_index = Sexp_nodes[Sexp_nodes[Sexp_nodes[op_index].rest].rest].rest;
}
break;
default :
ship_index = Sexp_nodes[op_index].rest;
break;
}
shipname = CTEXT(ship_index);
shipnum = ship_name_lookup(shipname);
if (shipnum >= 0){
ship_class = Ships[shipnum].ship_info_index;
} else {
// must try to find the ship in the ship_arrival_list
p_object *parse_obj;
parse_obj = mission_parse_get_arrival_ship( shipname );
if ( parse_obj == NULL ){
return SEXP_CHECK_INVALID_SHIP;
}
ship_class = parse_obj->ship_class;
}
// check for the special "hull" value
if ( (Operators[op].value == OP_SABOTAGE_SUBSYSTEM) || (Operators[op].value == OP_REPAIR_SUBSYSTEM) || (Operators[op].value == OP_SET_SUBSYSTEM_STRNGTH) ) {
if ( !stricmp( CTEXT(index), SEXP_HULL_STRING) ){
break;
}
}
for (i=0; i<Ship_info[ship_class].n_subsystems; i++){
if (!stricmp(Ship_info[ship_class].subsystems[i].subobj_name, CTEXT(index))){
break;
}
}
if (i == Ship_info[ship_class].n_subsystems){
return SEXP_CHECK_INVALID_SUBSYS;
}
// if we're checking for an AWACS subsystem and this is not an awacs subsystem
if(Fred_running){
if((type == OPF_AWACS_SUBSYSTEM) && !(Ship_info[ship_class].subsystems[i].flags & MSS_FLAG_AWACS)){
return SEXP_CHECK_INVALID_SUBSYS;
}
}
break;
}
case OPF_POINT:
if (waypoint_lookup(CTEXT(index)) < 0){
if (verify_vector(CTEXT(index))){
return SEXP_CHECK_INVALID_POINT;
}
}
if (type2 != SEXP_ATOM_STRING){
return SEXP_CHECK_TYPE_MISMATCH;
}
break;
case OPF_IFF:
if (type2 == SEXP_ATOM_STRING){
for (i=0; i<Num_team_names; i++){
if (!stricmp(Team_names[i], CTEXT(index))){
break;
}
}
}
if (i == Num_team_names){
return SEXP_CHECK_INVALID_IFF;
}
if (type2 != SEXP_ATOM_STRING){
return SEXP_CHECK_TYPE_MISMATCH;
}
break;
case OPF_BOOL:
if (type2 != OPR_BOOL){
return SEXP_CHECK_TYPE_MISMATCH;
}
break;
case OPF_NULL:
if (type2 != OPR_NULL){
return SEXP_CHECK_TYPE_MISMATCH;
}
break;
case OPF_AI_ORDER:
if ( type2 != SEXP_ATOM_STRING ){
return SEXP_CHECK_TYPE_MISMATCH;
}
break;
case OPF_AI_GOAL:
if (type2 != OPR_AI_GOAL){
return SEXP_CHECK_TYPE_MISMATCH;
}
if (Fred_running) {
int ship_num, ship2, i, w = 0, z;
ship_num = ship_name_lookup(CTEXT(Sexp_nodes[op_index].rest));
if (ship_num < 0) {
w = wing_name_lookup(CTEXT(Sexp_nodes[op_index].rest));
if (w < 0) {
if (bad_index){
*bad_index = Sexp_nodes[op_index].rest;
}
return SEXP_CHECK_INVALID_SHIP; // should have already been caught earlier, but just in case..
}
}
Assert(Sexp_nodes[index].subtype == SEXP_ATOM_LIST);
z = Sexp_nodes[index].first;
Assert(Sexp_nodes[z].subtype != SEXP_ATOM_LIST);
z = find_operator(CTEXT(z));
if (ship_num >= 0) {
if (!query_sexp_ai_goal_valid(z, ship_num)){
return SEXP_CHECK_ORDER_NOT_ALLOWED;
}
} else {
for (i=0; i<Wings[w].wave_count; i++){
if (!query_sexp_ai_goal_valid(z, Wings[w].ship_index[i])){
return SEXP_CHECK_ORDER_NOT_ALLOWED;
}
}
}
if ((z == OP_AI_DOCK) && (Sexp_nodes[index].rest >= 0)) {
ship2 = ship_name_lookup(CTEXT(Sexp_nodes[index].rest));
if ((ship_num < 0) || !ship_docking_valid(ship_num, ship2)){
return SEXP_CHECK_DOCKING_NOT_ALLOWED;
}
}
}
// we should check the syntax of the actual goal!!!!
z = Sexp_nodes[index].first;
if ((z = check_sexp_syntax(z, OPR_AI_GOAL, recursive, bad_index)) != 0){
return z;
}
break;
case OPF_SHIP_TYPE:
if (type2 != SEXP_ATOM_STRING){
return SEXP_CHECK_TYPE_MISMATCH;
}
for (i=0; i<MAX_SHIP_TYPE_COUNTS; i++){
if (!stricmp( Ship_type_names[i], CTEXT(index))){
break;
}
}
if (i == MAX_SHIP_TYPE_COUNTS){
return SEXP_CHECK_INVALID_SHIP_TYPE;
}
break;
case OPF_WAYPOINT_PATH:
for (i=0; i<Num_waypoint_lists; i++){
if (!stricmp(Waypoint_lists[i].name, CTEXT(index))){
break;
}
}
if (i == Num_waypoint_lists){
return SEXP_CHECK_TYPE_MISMATCH;
}
break;
case OPF_MESSAGE:
// code commented out because of order reversing of arguments to messaging code. Maybe
// I'll comment it back in someday when older missions get fixed.
if (type2 != SEXP_ATOM_STRING)
return SEXP_CHECK_TYPE_MISMATCH;
if (Fred_running) {
for (i=0; i<Num_messages; i++)
if (!stricmp(Messages[i].name, CTEXT(index)))
break;
if (i == Num_messages)
return SEXP_CHECK_UNKNOWN_MESSAGE;
}
break;
case OPF_PRIORITY: {
// following code must be removed since I changed the order of the operators in the send-message
// function to take priority second. Maybe someday, I'll require that all designers fix the
// old missions.
if (type2 != SEXP_ATOM_STRING)
return SEXP_CHECK_TYPE_MISMATCH;
if (Fred_running) { // should still check in Fred though..
char *name;
name = CTEXT(index);
if (!stricmp(name, "low") || !stricmp(name, "normal") || !stricmp(name, "high"))
break;
return SEXP_CHECK_INVALID_PRIORITY;
}
break;
}
case OPF_MISSION_NAME:
if (type2 != SEXP_ATOM_STRING)
return SEXP_CHECK_TYPE_MISMATCH;
if (Fred_running) {
if (mode == SEXP_MODE_CAMPAIGN) {
for (i=0; i<Campaign.num_missions; i++)
if (!stricmp(CTEXT(index), Campaign.missions[i].name)) {
if ((i != Sexp_useful_number) && (Campaign.missions[i].level >= Campaign.missions[Sexp_useful_number].level))
return SEXP_CHECK_INVALID_LEVEL;
break;
}
if (i == Campaign.num_missions)
return SEXP_CHECK_INVALID_MISSION_NAME;
} else {
// mwa -- put the following if statement to prevent Fred errors for possibly valid
// conditions. We should do something else here!!!
if ( (Operators[op].value == OP_PREVIOUS_EVENT_TRUE) || (Operators[op].value == OP_PREVIOUS_EVENT_FALSE) || (Operators[op].value == OP_PREVIOUS_EVENT_INCOMPLETE)
|| (Operators[op].value == OP_PREVIOUS_GOAL_TRUE) || (Operators[op].value == OP_PREVIOUS_GOAL_FALSE) || (Operators[op].value == OP_PREVIOUS_GOAL_INCOMPLETE) )
break;
if (!(*Mission_filename) || stricmp(Mission_filename, CTEXT(index)))
return SEXP_CHECK_INVALID_MISSION_NAME;
}
}
break;
case OPF_GOAL_NAME:
if (type2 != SEXP_ATOM_STRING)
return SEXP_CHECK_TYPE_MISMATCH;
// we only need to check the campaign list if running in Fred and are in campaign mode.
// otherwise, check the set of current goals
if ( Fred_running && (mode == SEXP_MODE_CAMPAIGN) ) {
z = find_parent_operator(index);
Assert(z >= 0);
z = Sexp_nodes[z].rest; // first argument of operator should be mission name
Assert(z >= 0);
for (i=0; i<Campaign.num_missions; i++)
if (!stricmp(CTEXT(z), Campaign.missions[i].name))
break;
// read the goal/event list from the mission file if both num_goals and num_events
// are < 0
if ((Campaign.missions[i].num_goals <= 0) && (Campaign.missions[i].num_events <= 0) )
read_mission_goal_list(i);
if (i < Campaign.num_missions) {
for (t=0; t<Campaign.missions[i].num_goals; t++)
if (!stricmp(CTEXT(index), Campaign.missions[i].goals[t].name))
break;
if (t == Campaign.missions[i].num_goals)
return SEXP_CHECK_INVALID_GOAL_NAME;
}
} else {
// MWA -- short circuit evaluation of these things for now.
if ( (Operators[op].value == OP_PREVIOUS_GOAL_TRUE) || (Operators[op].value == OP_PREVIOUS_GOAL_FALSE) || (Operators[op].value == OP_PREVIOUS_GOAL_INCOMPLETE) )
break;
for (i=0; i<Num_goals; i++)
if (!stricmp(CTEXT(index), Mission_goals[i].name))
break;
if (i == Num_goals)
return SEXP_CHECK_INVALID_GOAL_NAME;
}
break;
case OPF_EVENT_NAME:
if ( type2 != SEXP_ATOM_STRING )
return SEXP_CHECK_TYPE_MISMATCH;
// like above checking for goals, check events in the campaign only if in Fred
// and only if in campaign mode. Otherwise, check the current set of events
if ( Fred_running && (mode == SEXP_MODE_CAMPAIGN) ) {
z = find_parent_operator(index);
Assert(z >= 0);
z = Sexp_nodes[z].rest; // first argument of operator should be mission name
Assert(z >= 0);
for (i=0; i<Campaign.num_missions; i++)
if (!stricmp(CTEXT(z), Campaign.missions[i].name))
break;
// read the goal/event list from the mission file if both num_goals and num_events
// are < 0
if ((Campaign.missions[i].num_goals <= 0) && (Campaign.missions[i].num_events <= 0) )
read_mission_goal_list(i);
if (i < Campaign.num_missions) {
for (t=0; t<Campaign.missions[i].num_events; t++)
if (!stricmp(CTEXT(index), Campaign.missions[i].events[t].name))
break;
if (t == Campaign.missions[i].num_events)
return SEXP_CHECK_INVALID_EVENT_NAME;
}
} else {
// MWA -- short circuit evaluation of these things for now.
if ( (Operators[op].value == OP_PREVIOUS_EVENT_TRUE) || (Operators[op].value == OP_PREVIOUS_EVENT_FALSE) || (Operators[op].value == OP_PREVIOUS_EVENT_INCOMPLETE) )
break;
for ( i = 0; i < Num_mission_events; i++ ) {
if ( !stricmp(CTEXT(index), Mission_events[i].name) )
break;
}
if ( i == Num_mission_events )
return SEXP_CHECK_INVALID_EVENT_NAME;
}
break;
case OPF_DOCKER_POINT:
if (type2 != SEXP_ATOM_STRING)
return SEXP_CHECK_TYPE_MISMATCH;
if (Fred_running) {
int ship_num, model, i, z;
z = find_parent_operator(op_index);
ship_num = ship_name_lookup(CTEXT(Sexp_nodes[z].rest));
if (ship_num < 0) {
if (bad_index)
*bad_index = Sexp_nodes[z].rest;
return SEXP_CHECK_INVALID_SHIP; // should have already been caught earlier, but just in case..
}
model = Ships[ship_num].modelnum;
z = model_get_num_dock_points(model);
for (i=0; i<z; i++)
if (!stricmp(CTEXT(index), model_get_dock_name(model, i)))
break;
if (i == z)
return SEXP_CHECK_INVALID_DOCKER_POINT;
}
break;
case OPF_DOCKEE_POINT:
if (type2 != SEXP_ATOM_STRING)
return SEXP_CHECK_TYPE_MISMATCH;
if (Fred_running) {
int ship_num, model, i, z;
ship_num = ship_name_lookup(CTEXT(Sexp_nodes[op_index].rest));
if (ship_num < 0) {
if (bad_index)
*bad_index = Sexp_nodes[op_index].rest;
return SEXP_CHECK_INVALID_SHIP; // should have already been caught earlier, but just in case..
}
model = Ships[ship_num].modelnum;
z = model_get_num_dock_points(model);
for (i=0; i<z; i++)
if (!stricmp(CTEXT(index), model_get_dock_name(model, i)))
break;
if (i == z)
return SEXP_CHECK_INVALID_DOCKEE_POINT;
}
break;
case OPF_WHO_FROM:
if (type2 != SEXP_ATOM_STRING)
return SEXP_CHECK_TYPE_MISMATCH;
if (*CTEXT(index) != '#') { // not a manual source?
//if ( !stricmp(CTEXT(index), "<any allied>") )
// return SEXP_CHECK_INVALID_MSG_SOURCE;
if ( stricmp(CTEXT(index), "<any wingman>")) // not a special token?
if ((ship_name_lookup(CTEXT(index)) < 0) && (wing_name_lookup(CTEXT(index), 1) < 0)) // is it in the mission?
if (Fred_running || mission_parse_ship_arrived(CTEXT(index))) // == 0 when still on arrival list
return SEXP_CHECK_INVALID_MSG_SOURCE;
}
break;
case OPF_KEYPRESS:
if (type2 != SEXP_ATOM_STRING)
return SEXP_CHECK_TYPE_MISMATCH;
break;
case OPF_SKILL_LEVEL:
if ( type2 != SEXP_ATOM_STRING )
return SEXP_CHECK_TYPE_MISMATCH;
for (i = 0; i < NUM_SKILL_LEVELS; i++) {
if ( !stricmp(CTEXT(index), Skill_level_names(i, 0)) )
break;
}
if ( i == NUM_SKILL_LEVELS )
return SEXP_CHECK_INVALID_SKILL_LEVEL;
break;
case OPF_MEDAL_NAME:
if ( type2 != SEXP_ATOM_STRING)
return SEXP_CHECK_TYPE_MISMATCH;
for (i = 0; i < NUM_MEDALS; i++) {
if ( !stricmp(CTEXT(index), Medals[i].name) )
break;
}
if ( i == NUM_MEDALS )
return SEXP_CHECK_INVALID_MEDAL_NAME;
break;
case OPF_HUGE_WEAPON:
case OPF_WEAPON_NAME:
if ( type2 != SEXP_ATOM_STRING )
return SEXP_CHECK_TYPE_MISMATCH;
for (i = 0; i < Num_weapon_types; i++ ) {
if ( !stricmp(CTEXT(index), Weapon_info[i].name) )
break;
}
if ( i == Num_weapon_types )
return SEXP_CHECK_INVALID_WEAPON_NAME;
// we need to be sure that for huge weapons, the WIF_HUGE flag is set
if ( type == OPF_HUGE_WEAPON ) {
if ( !(Weapon_info[i].wi_flags & WIF_HUGE) )
return SEXP_CHECK_INVALID_WEAPON_NAME;
}
break;
case OPF_SHIP_CLASS_NAME:
if ( type2 != SEXP_ATOM_STRING )
return SEXP_CHECK_TYPE_MISMATCH;
for (i = 0; i < Num_ship_types; i++ ) {
if ( !stricmp(CTEXT(index), Ship_info[i].name) )
break;
}
if ( i == Num_ship_types )
return SEXP_CHECK_INVALID_SHIP_CLASS_NAME;
break;
case OPF_HUD_GAUGE_NAME:
if ( type2 != SEXP_ATOM_STRING )
return SEXP_CHECK_TYPE_MISMATCH;
for ( i = 0; i < NUM_HUD_GAUGES; i++ ) {
if ( !stricmp(CTEXT(index), HUD_gauge_text[i]) )
break;
}
// if we reached the end of the list, then the name is invalid
if ( i == NUM_HUD_GAUGES )
return SEXP_CHECK_INVALID_GAUGE_NAME;
break;
case OPF_JUMP_NODE_NAME:
if ( type2 != SEXP_ATOM_STRING )
return SEXP_CHECK_TYPE_MISMATCH;
for ( i = 0; i < Num_jump_nodes; i++ ) {
if ( !stricmp(Jump_nodes[i].name, CTEXT(index)) )
break;
}
if ( i == Num_jump_nodes )
return SEXP_CHECK_INVALID_JUMP_NODE;
break;
case OPF_VARIABLE_NAME:
if ( Fred_running ) {
if ( get_index_sexp_variable_name(Sexp_nodes[index].text) == -1) {
return SEXP_CHECK_INVALID_VARIABLE;
}
}
// if Fred not running anything goes
break;
case OPF_AMBIGUOUS:
// type checking for modify-variable
// string or number -- anything goes
break;
default:
Int3(); // currently unhandled argument format (so add it now)
}
index = Sexp_nodes[index].rest;
argnum++;
}
return 0;
}
// Stuff a string (" chars ") in *str, return length.
// Updates Mp.
int get_string(char *str)
{
int len;
len = strcspn(Mp + 1, "\"");
strncpy(str, Mp + 1, len);
str[len] = 0;
Mp += len + 2;
return len;
}
// get text to stuff into Sexp_node in case of variable
// if Fred_running - stuff Sexp_variables[].variable_name
// otherwise - stuff index into Sexp_variables array.
void get_sexp_text_for_variable(char *text, char *token)
{
int end_index;
int sexp_var_index;
// get variable name (up to '['
end_index = strcspn(token, "[");
Assert( (end_index != 0) && (end_index < TOKEN_LENGTH-1) );
strncpy(text, token, end_index);
text[end_index] = '\0';
if ( !Fred_running ) {
// freespace - get index into Sexp_variables array
sexp_var_index = get_index_sexp_variable_name(text);
Assert(sexp_var_index != -1);
sprintf(text, "%d", sexp_var_index);
}
}
// returns the first sexp index of data this function allocates. (start of this sexp)
// recursive function - always sets first and then rest
int get_sexp(char *token)
{
int start, node, last, len, op, count;
char variable_text[TOKEN_LENGTH];
// start - the node allocated in first instance of fuction
// node - the node allocated in current instance of function
// count - number of nodes allocated this instance of function [do we set last.rest or first]
// variable - whether string or number is a variable referencing Sexp_variables
// initialization
start = last = -1;
count = 0;
ignore_white_space();
while (*Mp != ')') {
Assert(*Mp != EOF_CHAR);
if (*Mp == '(') {
// Sexp list
Mp++;
node = alloc_sexp("", SEXP_LIST, SEXP_ATOM_LIST, get_sexp(token), -1);
} else if (*Mp == '\"') {
// Sexp string
len = strcspn(Mp + 1, "\"");
Assert(Mp[len + 1] == '\"'); // hit EOF first (unterminated string)
Assert(len < TOKEN_LENGTH); // token is too long.
// check if string variable
if ( *(Mp + 1) == SEXP_VARIABLE_CHAR ) {
// reduce length by 1 for end \"
int length = len - 1;
Assert(length < 2*TOKEN_LENGTH+2);
// start copying after skipping 1st char
strncpy(token, Mp + 2, length);
token[length] = 0;
get_sexp_text_for_variable(variable_text, token);
node = alloc_sexp(variable_text, (SEXP_ATOM | SEXP_FLAG_VARIABLE), SEXP_ATOM_STRING, -1, -1);
} else {
strncpy(token, Mp + 1, len);
token[len] = 0;
node = alloc_sexp(token, SEXP_ATOM, SEXP_ATOM_STRING, -1, -1);
}
// bump past closing \" by 1 char
Mp += len + 2;
} else {
// Sexp operator or number
len = 0;
bool variable = false;
while (*Mp != ')' && !is_white_space(*Mp)) {
if ( (len == 0) && (*Mp == SEXP_VARIABLE_CHAR) ) {
variable = true;
Mp++;
continue;
}
Assert(*Mp != EOF_CHAR);
Assert(len < TOKEN_LENGTH - 1);
token[len++] = *Mp++;
}
token[len] = 0;
len = 0;
op = identify_operator(token);
if (op != -1) {
node = alloc_sexp(token, SEXP_ATOM, SEXP_ATOM_OPERATOR, -1, -1);
} else {
if ( variable ) {
// convert token text for variable
get_sexp_text_for_variable(variable_text, token);
node = alloc_sexp(variable_text, (SEXP_ATOM | SEXP_FLAG_VARIABLE), SEXP_ATOM_NUMBER, -1, -1);
} else {
node = alloc_sexp(token, SEXP_ATOM, SEXP_ATOM_NUMBER, -1, -1);
}
}
}
// update links
if (count++) {
Assert(last != -1);
Sexp_nodes[last].rest = node;
} else {
start = node;
}
Assert(node != -1); // ran out of nodes. Time to raise the MAX!
last = node;
ignore_white_space();
}
Mp++; // skip past the ')'
return start;
}
// Stuffs a list of sexp variables
int stuff_sexp_variable_list()
{
int count;
char var_name[TOKEN_LENGTH];
char default_value[TOKEN_LENGTH];
char str_type[TOKEN_LENGTH];
int index;
int type;
count = 0;
required_string("$Variables:");
ignore_white_space();
// check for start of list
if (*Mp != '(') {
error_display(1, "Reading sexp variable list. Found [%c]. Expecting '('.\n", *Mp);
longjmp(parse_abort, 6);
}
Mp++;
ignore_white_space();
while (*Mp != ')') {
Assert(count < MAX_SEXP_VARIABLES);
// get index - for debug
stuff_int(&index);
ignore_gray_space();
// get var_name
get_string(var_name);
ignore_gray_space();
// get default_value;
get_string(default_value);
ignore_gray_space();
// get type
get_string(str_type);
ignore_white_space();
if (!stricmp(str_type, "number")) {
type = SEXP_VARIABLE_NUMBER;
} else if (!stricmp(str_type, "string")) {
type = SEXP_VARIABLE_STRING;
} else if (!stricmp(str_type, "block")) {
type = SEXP_VARIABLE_BLOCK | SEXP_VARIABLE_BLOCK_EXP;
} else {
type = SEXP_VARIABLE_UNKNOWN;
Int3();
}
count++;
// check if variable name already exists
if ( (type == SEXP_VARIABLE_NUMBER) || (type == SEXP_VARIABLE_STRING) ) {
Assert(get_index_sexp_variable_name(var_name) == -1);
}
sexp_add_variable(default_value, var_name, type, index);
}
Mp++;
return count;
}
//
void build_sexp_text_string(char *buffer, int node, int mode)
{
if (Sexp_nodes[node].type & SEXP_FLAG_VARIABLE) {
int sexp_variables_index = get_index_sexp_variable_name(Sexp_nodes[node].text);
Assert(sexp_variables_index != -1);
Assert( (Sexp_variables[sexp_variables_index].type & SEXP_VARIABLE_NUMBER) || (Sexp_variables[sexp_variables_index].type & SEXP_VARIABLE_STRING) );
// number
if (Sexp_nodes[node].subtype == SEXP_ATOM_NUMBER) {
Assert(Sexp_variables[sexp_variables_index].type & SEXP_VARIABLE_NUMBER);
// Error check - can be Fred or Freespace
if (mode == SEXP_ERROR_CHECK_MODE) {
if ( Fred_running ) {
sprintf(buffer, "%s[%s] ", Sexp_nodes[node].text, Sexp_variables[sexp_variables_index].text);
} else {
sprintf(buffer, "%s[%s] ", Sexp_variables[sexp_variables_index].variable_name, Sexp_variables[sexp_variables_index].text);
}
} else {
// Save as string - only Fred
Assert(mode == SEXP_SAVE_MODE);
sprintf(buffer, "@%s[%s] ", Sexp_nodes[node].text, Sexp_variables[sexp_variables_index].text);
}
} else {
// string
Assert(Sexp_nodes[node].subtype == SEXP_ATOM_STRING);
Assert(Sexp_variables[sexp_variables_index].type & SEXP_VARIABLE_STRING);
// Error check - can be Fred or Freespace
if (mode == SEXP_ERROR_CHECK_MODE) {
if ( Fred_running ) {
sprintf(buffer, "%s[%s] ", Sexp_variables[sexp_variables_index].variable_name, Sexp_variables[sexp_variables_index].text);
} else {
sprintf(buffer, "%s[%s] ", Sexp_nodes[node].text, Sexp_variables[sexp_variables_index].text);
}
} else {
// Save as string - only Fred
Assert(mode == SEXP_SAVE_MODE);
sprintf(buffer, "\"@%s[%s]\" ", Sexp_nodes[node].text, Sexp_variables[sexp_variables_index].text);
}
}
} else {
// not a variable
if (Sexp_nodes[node].subtype == SEXP_ATOM_STRING) {
sprintf(buffer, "\"%s\" ", CTEXT(node));
} else {
sprintf(buffer, "%s ", CTEXT(node));
}
}
}
int build_sexp_string(int cur_node, int level, int mode)
{
char pstr[128];
int len, offset, node;
Sexp_build_flag = 0;
offset = strlen(Sexp_string);
strcat(Sexp_string, "( ");
node = cur_node;
while (node != -1) {
Assert(node >= 0 && node < MAX_SEXP_NODES);
if (Sexp_nodes[node].first == -1) {
// build text to string
build_sexp_text_string(pstr, node, mode);
strcat(Sexp_string, pstr);
} else {
build_sexp_string(Sexp_nodes[node].first, level + 1, mode);
}
node = Sexp_nodes[node].rest;
}
strcat(Sexp_string, ") ");
len = strlen(Sexp_string) - offset;
if (len > 40) {
Sexp_string[offset] = 0;
build_extended_sexp_string(cur_node, level, mode);
return 1;
}
return 0;
}
void build_extended_sexp_string(int cur_node, int level, int mode)
{
char pstr[128];
int i, flag = 0, node;
strcat(Sexp_string, "( ");
node = cur_node;
while (node != -1) {
if (flag) // not the first line?
for (i=0; i<level + 1; i++)
strcat(Sexp_string, " ");
flag = 1;
Assert(node >= 0 && node < MAX_SEXP_NODES);
if (Sexp_nodes[node].first == -1) {
build_sexp_text_string(pstr,node, mode);
strcat(Sexp_string, pstr);
} else {
build_sexp_string(Sexp_nodes[node].first, level + 1, mode);
}
strcat(Sexp_string, "\n");
node = Sexp_nodes[node].rest;
}
for (i=0; i<level; i++)
strcat(Sexp_string, " ");
strcat(Sexp_string, ")");
}
void convert_sexp_to_string(int cur_node, char *outstr, int mode)
{
Sexp_string = outstr;
*outstr = 0;
if (cur_node >= 0)
build_sexp_string(cur_node, 0, mode);
else
strcpy(Sexp_string, "( )");
}
// determine if the named ship or wing hasn't arrived yet (wing or ship must be on arrival list)
int sexp_query_has_yet_to_arrive(char *name)
{
int i;
if (ship_query_state(name) < 0)
return 1;
i = wing_name_lookup(name, 1);
// has not arrived yet, and never will arrive
if ((i >= 0) && (Wings[i].num_waves >= 0) && (Wings[i].flags & WF_NEVER_EXISTED)){
return 1;
}
// has not arrived yet
if ((i >= 0) && (Wings[i].num_waves >= 0) && !Wings[i].total_arrived_count){
return 1;
}
return 0;
}
// arithmetic functions
int add_sexps(int n)
{
int sum = 0, val;
if (n != -1) {
if ( CAR(n) != -1)
sum = eval_sexp( CAR(n) );
else
sum = atoi( CTEXT(n) );
// be sure to check for the NAN value when doing arithmetic -- this value should
// get propagated to the next highest function.
if ( Sexp_nodes[CAR(n)].value == SEXP_NAN )
return SEXP_NAN;
else if ( Sexp_nodes[CAR(n)].value == SEXP_NAN_FOREVER )
return SEXP_NAN_FOREVER;
while (CDR(n) != -1) {
val = eval_sexp( CDR(n) );
// be sure to check for the NAN value when doing arithmetic -- this value should
// get propagated to the next highest function.
if ( Sexp_nodes[CDR(n)].value == SEXP_NAN )
return SEXP_NAN;
else if ( Sexp_nodes[CAR(n)].value == SEXP_NAN_FOREVER )
return SEXP_NAN_FOREVER;
sum += val;
n = CDR(n);
}
}
return sum;
}
int sub_sexps(int n)
{
int sum = 0;
if (n != -1) {
if (Sexp_nodes[n].first != -1)
sum = eval_sexp(CAR(n));
else
sum = atoi(CTEXT(n));
while (CDR(n) != -1) {
sum -= eval_sexp(CDR(n));
n = CDR(n);
}
}
return sum;
}
int mul_sexps(int n)
{
int sum = 0;
if (n != -1) {
if (Sexp_nodes[n].first != -1)
sum = eval_sexp(Sexp_nodes[n].first);
else
sum = atoi(CTEXT(n));
while (Sexp_nodes[n].rest != -1) {
sum *= eval_sexp(Sexp_nodes[n].rest);
n = Sexp_nodes[n].rest;
}
}
return sum;
}
int div_sexps(int n)
{
int sum = 0;
if (n != -1) {
if (Sexp_nodes[n].first != -1)
sum = eval_sexp(Sexp_nodes[n].first);
else
sum = atoi(CTEXT(n));
while (Sexp_nodes[n].rest != -1) {
sum /= eval_sexp(Sexp_nodes[n].rest);
n = Sexp_nodes[n].rest;
}
}
return sum;
}
int mod_sexps(int n)
{
int sum = 0;
if (n != -1) {
if (Sexp_nodes[n].first != -1)
sum = eval_sexp(Sexp_nodes[n].first);
else
sum = atoi(CTEXT(n));
while (Sexp_nodes[n].rest != -1) {
sum = sum % eval_sexp(Sexp_nodes[n].rest);
n = Sexp_nodes[n].rest;
}
}
return sum;
}
int rand_internal(int low, int high)
{
int diff;
// get diff - don't allow negative or zero
diff = high - low;
if (diff < 0) {
diff = 0;
}
return (low + rand() % (diff + 1));
}
int rand_sexp(int n, int multiple=0)
{
int low = 0;
int high = 0;
int rand_num = 0;
if (n != -1) {
if (Sexp_nodes[n].value == SEXP_NUM_EVAL) {
// don't regenerate new random number
rand_num = atoi(CTEXT(n));
} else {
// get low
// if (Sexp_nodes[n].first != -1) {
// low = eval_sexp(Sexp_nodes[n].first);
// } else {
// low = atoi(CTEXT(n));
// }
low = num_eval(n);
// get high
high = num_eval(CDR(n));
// get the random number
rand_num = rand_internal(low, high);
if (!multiple) {
// set .value and .text so random number is generated only once.
Sexp_nodes[n].value = SEXP_NUM_EVAL;
sprintf(Sexp_nodes[n].text, "%d", rand_num);
}
}
}
return rand_num;
}
// boolean evaluation functions. Evaluate all sexpressions in the 'or' operator. Needed to mark
// entries in the mission log as essential so when pruning the log, we know which entries we might
// need to keep.
int sexp_or(int n)
{
int all_false, result;
all_false = 1;
result = 0;
if (n != -1) {
if (Sexp_nodes[n].first != -1) {
result |= eval_sexp(Sexp_nodes[n].first);
if ( Sexp_nodes[Sexp_nodes[n].first].value == SEXP_KNOWN_TRUE )
return SEXP_KNOWN_TRUE; // if one of the OR clauses is TRUE, whole clause is true
if ( Sexp_nodes[Sexp_nodes[n].first].value != SEXP_KNOWN_FALSE ) // if the value is still unknown, they all can't be false
all_false = 0;
} else
result |= atoi(CTEXT(n));
// don't return on true value -- keep evaluating for mission log purposes
//if ( result )
// return result;
while (Sexp_nodes[n].rest != -1) {
result |= eval_sexp(Sexp_nodes[n].rest);
if ( Sexp_nodes[Sexp_nodes[n].rest].value == SEXP_KNOWN_TRUE )
return SEXP_KNOWN_TRUE; // if one of the OR clauses is TRUE, whole clause is true
if ( Sexp_nodes[Sexp_nodes[n].rest].value != SEXP_KNOWN_FALSE ) // if the value is still unknown, they all can't be false
all_false = 0;
n = Sexp_nodes[n].rest;
// don't return on true value -- keep evaluating for mission log purposes
//if ( result )
// return result;
}
}
if ( all_false )
return SEXP_KNOWN_FALSE;
return result;
}
// this function does the 'and' operator. It will short circuit evaluation *but* it will still
// evaluate other members of the and construct. I do this because I need events in the mission log
// to get marked as essential for goal purposes, and evaluation is pretty much the only way
int sexp_and(int n)
{
int all_true, result;
result = -1;
all_true = 1;
if (n != -1) {
if (Sexp_nodes[n].first != -1) {
result &= eval_sexp( CAR(n) );
if ( Sexp_nodes[Sexp_nodes[n].first].value == SEXP_KNOWN_FALSE )
return SEXP_KNOWN_FALSE; // if one of the AND clauses is FALSE, whole clause is false
if ( Sexp_nodes[Sexp_nodes[n].first].value != SEXP_KNOWN_TRUE ) // if the value is still unknown, they all can't be true
all_true = 0;
} else
result &= atoi(CTEXT(n));
// don't short circuit -- evaluate everything for purposes of marking mission log
//if ( !result )
// return result;
while (Sexp_nodes[n].rest != -1) {
int new_result;
new_result = eval_sexp( CDR(n) );
result &= new_result;
if ( Sexp_nodes[Sexp_nodes[n].rest].value == SEXP_KNOWN_FALSE )
return SEXP_KNOWN_FALSE; // if one of the OR clauses is TRUE, whole clause is true
if ( Sexp_nodes[Sexp_nodes[n].rest].value != SEXP_KNOWN_TRUE ) // if the value is still unknown, they all can't be false
all_true = 0;
// don't short circuit -- evaluate everything for purposes of marking mission log
//if ( !result )
//return result;
n = Sexp_nodes[n].rest;
}
}
if ( all_true )
return SEXP_KNOWN_TRUE;
return result;
}
// this version of the 'and' operator determines whether or not it's arguments become true
// in the order in which they are specified in the when statement. Should be a simple matter of
// seeing if anything evaluates to true later than something that evalueated to false
int sexp_and_in_sequence(int n)
{
int result = -1;
int all_true;
all_true = 1; // represents whether or not all nodes we have seen so far are true
if (n != -1) {
if (Sexp_nodes[n].first != -1) {
result &= eval_sexp( CAR(n) );
if ( Sexp_nodes[Sexp_nodes[n].first].value == SEXP_KNOWN_FALSE )
return SEXP_KNOWN_FALSE; // if one of the AND clauses is FALSE, whole clause is false
if ( Sexp_nodes[Sexp_nodes[n].first].value != SEXP_KNOWN_TRUE ) // if value is true, mark our all_true variable for later checking
all_true = 0;
} else
result &= atoi(CTEXT(n));
// a little test -- if the previous sexpressions was true, then mark the node itself as always
// true. I did this because of the distance function. It might become true, then when waiting for
// the second evalation, it might become false, rendering this function false. So, when one becomes
// true -- mark it true forever.
if ( result )
Sexp_nodes[Sexp_nodes[n].first].value = SEXP_KNOWN_TRUE;
while (Sexp_nodes[n].rest != -1) {
int next_result;
next_result = eval_sexp( CDR(n) );
if ( next_result && !result ) // if current result is true, and our running result is false, thngs didn't become true in order
return SEXP_KNOWN_FALSE;
result &= next_result;
if ( Sexp_nodes[Sexp_nodes[n].rest].value == SEXP_KNOWN_FALSE )
return SEXP_KNOWN_FALSE; // if one of the OR clauses is TRUE, whole clause is true
if ( Sexp_nodes[Sexp_nodes[n].rest].value != SEXP_KNOWN_TRUE ) // if the value is still unknown, they all can't be false
all_true = 0;
// see comment above for explanation of next lines
if ( result )
Sexp_nodes[Sexp_nodes[n].rest].value = SEXP_KNOWN_TRUE;
n = Sexp_nodes[n].rest;
}
}
if ( all_true )
return SEXP_KNOWN_TRUE;
return result;
}
// for these four basic boolean operations (not, <, >, and =), we have special cases that we must deal
// with. We have sexpressions operators that might return a NAN type return value (such as the distance
// between two ships when one of the ships is destroyed or departed). These operations need to check for
// this special NAN value and adjust their return types accordingly. NAN values represent false return values
int sexp_not( int n )
{
int result = 0;
if (n != -1) {
if (Sexp_nodes[n].first != -1) {
result = eval_sexp( CAR(n) );
if ( Sexp_nodes[CAR(n)].value == SEXP_KNOWN_FALSE )
return SEXP_KNOWN_TRUE; // not KNOWN_FALSE == KNOWN_TRUE;
else if ( Sexp_nodes[CAR(n)].value == SEXP_KNOWN_TRUE ) // not KNOWN_TRUE == KNOWN_FALSE
return SEXP_KNOWN_FALSE;
else if ( Sexp_nodes[CAR(n)].value == SEXP_NAN ) // not NAN == TRUE (I think)
return 1;
else if ( Sexp_nodes[CAR(n)].value == SEXP_NAN_FOREVER )
return 1;
} else
result = atoi(CTEXT(n));
}
return !result;
}
int sexp_gt(int n)
{
int exp1, exp2;
exp1 = eval_sexp( n );
exp2 = eval_sexp( CDR(n) );
// check for the NAN value
if ( ( Sexp_nodes[CAR(n)].value == SEXP_NAN ) || ( Sexp_nodes[CDR(n)].value == SEXP_NAN ) )
return 0;
else if ( ( Sexp_nodes[CAR(n)].value == SEXP_NAN_FOREVER ) || ( Sexp_nodes[CDR(n)].value == SEXP_NAN_FOREVER ) )
return SEXP_KNOWN_FALSE;
if ( exp1 > exp2 )
return 1;
return 0;
}
int sexp_lt(int n)
{
int exp1, exp2;
exp1 = eval_sexp( n );
exp2 = eval_sexp( CDR(n) );
// check for the NAN value
if ( ( Sexp_nodes[CAR(n)].value == SEXP_NAN ) || ( Sexp_nodes[CDR(n)].value == SEXP_NAN ) )
return 0;
else if ( ( Sexp_nodes[CAR(n)].value == SEXP_NAN_FOREVER ) || ( Sexp_nodes[CDR(n)].value == SEXP_NAN_FOREVER ) )
return SEXP_KNOWN_FALSE;
if ( exp1 < exp2 )
return 1;
return 0;
}
int sexp_equal(int n)
{
int exp1, exp2;
exp1 = eval_sexp( n );
exp2 = eval_sexp( CDR(n) );
// check for the NAN value
if ( ( Sexp_nodes[CAR(n)].value == SEXP_NAN ) || ( Sexp_nodes[CDR(n)].value == SEXP_NAN ) )
return 0;
else if ( ( Sexp_nodes[CAR(n)].value == SEXP_NAN_FOREVER ) || ( Sexp_nodes[CDR(n)].value == SEXP_NAN_FOREVER ) )
return SEXP_KNOWN_FALSE;
if ( exp1 == exp2 )
return 1;
return 0;
}
// Evaluate if given ship is destroyed.
// Return true if the ship in the expression has been destroyed.
int sexp_is_destroyed(int n, fix *latest_time)
{
char *name;
int count, num_destroyed, wing_index;
fix time;
Assert ( n != -1 );
count = 0;
num_destroyed = 0;
wing_index = -1;
while (n != -1) {
count++;
name = CTEXT(n);
if (sexp_query_has_yet_to_arrive(name))
return SEXP_CANT_EVAL;
// check to see if this ship/wing has departed. If so, then function is known false
if ( mission_log_get_time (LOG_SHIP_DEPART, name, NULL, NULL) || mission_log_get_time (LOG_WING_DEPART, name, NULL, NULL) )
return SEXP_KNOWN_FALSE;
// check the mission log. If ship/wing not destroyed, immediately return 0.
if ( mission_log_get_time(LOG_SHIP_DESTROYED, name, NULL, &time) || mission_log_get_time(LOG_WING_DESTROYED, name, NULL, &time) || mission_log_get_time(LOG_SELF_DESTRUCT, name, NULL, &time)) {
num_destroyed++;
if ( latest_time && (time > *latest_time) )
*latest_time = time;
} else {
// ship or wing isn't destroyed -- add to directive count
if ( (wing_index = wing_name_lookup( name, 1 )) >= 0 ) {
Directive_count += Wings[wing_index].current_count;
} else
Directive_count++;
}
// move to next ship/wing in list
n = CDR(n);
}
// special case to mark a directive for destroy wing objectives true after a short amount
// of time when there are more waves for this wing.
if ( (count == 1) && (wing_index >= 0) && (Directive_count == 0) ) {
if ( Wings[wing_index].current_wave < Wings[wing_index].num_waves )
Directive_count = DIRECTIVE_WING_ZERO;
}
if ( count == num_destroyed )
return SEXP_KNOWN_TRUE;
else
return 0;
}
// return true if the subsystem of the given ship has been destroyed or not
int sexp_is_subsystem_destroyed(int n)
{
char *ship_name, *subsys_name;
Assert( n != -1 );
ship_name = CTEXT(n);
subsys_name = CTEXT(CDR(n));
if (sexp_query_has_yet_to_arrive(ship_name))
return SEXP_CANT_EVAL;
// if the ship has departed, no way to destroy it's subsystem.
if ( mission_log_get_time(LOG_SHIP_DEPART, ship_name, NULL, NULL ))
return SEXP_KNOWN_FALSE;
if ( mission_log_get_time(LOG_SHIP_SUBSYS_DESTROYED, ship_name, subsys_name, NULL) )
return SEXP_KNOWN_TRUE;
return 0;
}
// determines if a ship has docked
int sexp_has_docked(int n)
{
char *docker = CTEXT(n);
char *dockee = CTEXT(CDR(n));
int count = atoi(CTEXT(CDR(CDR(n)))); // count of times that we should look for
if (sexp_query_has_yet_to_arrive(docker))
return SEXP_CANT_EVAL;
if (sexp_query_has_yet_to_arrive(dockee))
return SEXP_CANT_EVAL;
Assert ( count > 0 );
if ( mission_log_get_time(LOG_SHIP_DESTROYED, docker, NULL, NULL) || mission_log_get_time(LOG_SHIP_DESTROYED, dockee, NULL, NULL) )
return SEXP_KNOWN_FALSE;
if ( !mission_log_get_time_indexed(LOG_SHIP_DOCK, docker, dockee, count, NULL) )
return 0;
return SEXP_KNOWN_TRUE;
}
// determines if a ship has undocked
int sexp_has_undocked(int n)
{
char *docker = CTEXT(n);
char *dockee = CTEXT(CDR(n));
int count = atoi(CTEXT(CDR(CDR(n))));
if (sexp_query_has_yet_to_arrive(docker))
return SEXP_CANT_EVAL;
if (sexp_query_has_yet_to_arrive(dockee))
return SEXP_CANT_EVAL;
Assert ( count > 0 );
if ( !mission_log_get_time_indexed(LOG_SHIP_UNDOCK, docker, dockee, count, NULL) ) {
// if either ship destroyed before they dock, then sexp is known false
if ( mission_log_get_time(LOG_SHIP_DESTROYED, docker, NULL, NULL) || mission_log_get_time(LOG_SHIP_DESTROYED, dockee, NULL, NULL) )
return SEXP_KNOWN_FALSE;
else
return 0;
}
return SEXP_KNOWN_TRUE;
}
// determines if a ship has arrived onto the scene
int sexp_has_arrived(int n, fix *latest_time)
{
char *name;
int count, num_arrived;
fix time;
count = 0;
num_arrived = 0;
while ( n != -1 ) {
count++;
name = CTEXT(n);
// if there is no log entry for this ship/wing for arrival, sexpression is false
if ( mission_log_get_time(LOG_SHIP_ARRIVE, name, NULL, &time) || mission_log_get_time(LOG_WING_ARRIVE, name, NULL, &time) ) {
num_arrived++;
if ( latest_time && (time > *latest_time) )
*latest_time = time;
}
n = CDR(n);
}
if ( count == num_arrived )
return SEXP_KNOWN_TRUE;
else
return 0;
}
// determines if a ship/wing has departed
int sexp_has_departed(int n, fix *latest_time)
{
char *name;
int count, num_departed;
fix time;
count = 0;
num_departed = 0;
while ( n != -1 ) {
count++;
name = CTEXT(n);
if (sexp_query_has_yet_to_arrive(name))
return SEXP_CANT_EVAL;
// if ship/wing destroyed, sexpression is known false. Also, if there is no departure log entry, then
// the sexpression is not true.
if ( mission_log_get_time(LOG_SHIP_DESTROYED, name, NULL, NULL) || mission_log_get_time(LOG_WING_DESTROYED, name, NULL, NULL))
return SEXP_KNOWN_FALSE;
else if ( mission_log_get_time(LOG_SHIP_DEPART, name, NULL, &time) || mission_log_get_time(LOG_WING_DEPART, name, NULL, &time) ) {
num_departed++;
if ( latest_time && (time > *latest_time) )
*latest_time = time;
}
n = CDR(n);
}
if ( count == num_departed )
return SEXP_KNOWN_TRUE;
else
return 0;
}
// determines if ships are disabled
int sexp_is_disabled( int n, fix *latest_time )
{
char *name;
int count, num_disabled;
fix time;
count = 0;
num_disabled = 0;
while ( n != -1 ) {
count++;
name = CTEXT(n);
if (sexp_query_has_yet_to_arrive(name))
return SEXP_CANT_EVAL;
// if ship/wing destroyed, sexpression is known false. Also, if there is no disable log entry, then
// the sexpression is not true.
if ( mission_log_get_time(LOG_SHIP_DEPART, name, NULL, &time) || mission_log_get_time(LOG_SHIP_DESTROYED, name, NULL, &time) )
return SEXP_KNOWN_FALSE;
else if ( mission_log_get_time(LOG_SHIP_DISABLED, name, NULL, &time) ) {
num_disabled++;
if ( latest_time && (time > *latest_time) )
*latest_time = time;
}
n = CDR(n);
}
if ( count == num_disabled )
return SEXP_KNOWN_TRUE;
else
return 0;
}
// function to determine if a ship is done flying waypoints
int sexp_are_waypoints_done( int n )
{
char *ship_name, *waypoint_name;
ship_name = CTEXT(n);
waypoint_name = CTEXT(CDR(n));
if (sexp_query_has_yet_to_arrive(ship_name))
return SEXP_CANT_EVAL;
// a destroyed or departed ship will never reach their goal -- return known false
if ( mission_log_get_time(LOG_SHIP_DESTROYED, ship_name, NULL, NULL) )
return SEXP_KNOWN_FALSE;
else if ( mission_log_get_time(LOG_SHIP_DEPART, ship_name, NULL, NULL) )
return SEXP_KNOWN_FALSE;
// now check the log for the waypoints done entry
if ( mission_log_get_time(LOG_WAYPOINTS_DONE, ship_name, waypoint_name, NULL) )
return SEXP_KNOWN_TRUE;
return 0;
}
// determines if ships are disarmed
int sexp_is_disarmed( int n, fix *latest_time )
{
char *name;
int count, num_disarmed;
fix time;
count = 0;
num_disarmed = 0;
while ( n != -1 ) {
count++;
name = CTEXT(n);
if (sexp_query_has_yet_to_arrive(name))
return SEXP_CANT_EVAL;
// if ship/wing destroyed, sexpression is known false. Also, if there is no disarm log entry, then
// the sexpression is not true.
if ( mission_log_get_time(LOG_SHIP_DEPART, name, NULL, &time) || mission_log_get_time(LOG_SHIP_DESTROYED, name, NULL, &time) )
return SEXP_KNOWN_FALSE;
else if ( mission_log_get_time(LOG_SHIP_DISARMED, name, NULL, &time) ) {
num_disarmed++;
if ( latest_time && (time > *latest_time) )
*latest_time = time;
}
n = CDR(n);
}
if ( count == num_disarmed )
return SEXP_KNOWN_TRUE;
else
return 0;
}
// the following functions are similar to the above objective functions but return true/false
// if N seconds have elasped after the corresponding function is true.
int sexp_is_destroyed_delay(int n)
{
fix delay, time;
int val;
Assert ( n >= 0 );
time = 0;
delay = i2f(num_eval(n));
// check value of is_destroyed function. KNOWN_FALSE should be returned immediately
val = sexp_is_destroyed( CDR(n), &time );
if ( val == SEXP_KNOWN_FALSE )
return val;
if ( val == SEXP_CANT_EVAL )
return SEXP_CANT_EVAL;
if ( val ) {
if ( (Missiontime - time) >= delay )
return SEXP_KNOWN_TRUE;
}
return 0;
}
int sexp_is_subsystem_destroyed_delay( int n )
{
char *ship_name, *subsys_name;
fix delay, time;
Assert( n != -1 );
ship_name = CTEXT(n);
subsys_name = CTEXT(CDR(n));
delay = i2f(atoi(CTEXT(CDR(CDR(n)))));
if (sexp_query_has_yet_to_arrive(ship_name))
return SEXP_CANT_EVAL;
// if the ship has departed, no way to destroy it's subsystem.
if ( mission_log_get_time(LOG_SHIP_DEPART, ship_name, NULL, NULL ))
return SEXP_KNOWN_FALSE;
if ( mission_log_get_time(LOG_SHIP_SUBSYS_DESTROYED, ship_name, subsys_name, &time) ) {
if ( (Missiontime - time) >= delay )
return SEXP_KNOWN_TRUE;
}
return 0;
}
int sexp_is_disabled_delay( int n )
{
fix delay, time;
int val;
Assert ( n >= 0 );
time = 0;
delay = i2f(atoi(CTEXT(n)));
// check value of is_disable for known false and return immediately if it is.
val = sexp_is_disabled( CDR(n), &time );
if ( val == SEXP_KNOWN_FALSE )
return val;
if ( val == SEXP_CANT_EVAL )
return SEXP_CANT_EVAL;
if ( val ) {
if ( (Missiontime - time) >= delay )
return SEXP_KNOWN_TRUE;
}
return 0;
}
int sexp_is_disarmed_delay( int n )
{
fix delay, time;
int val;
Assert ( n >= 0 );
time = 0;
delay = i2f(atoi(CTEXT(n)));
// check value of is_disarmed for a known false value and return that immediately if it is
val = sexp_is_disarmed( CDR(n), &time );
if ( val == SEXP_KNOWN_FALSE )
return val;
if ( val == SEXP_CANT_EVAL )
return SEXP_CANT_EVAL;
if ( val ) {
if ( (Missiontime - time) >= delay )
return SEXP_KNOWN_TRUE;
}
return 0;
}
int sexp_has_docked_delay( int n )
{
char *docker = CTEXT(n);
char *dockee = CTEXT(CDR(n));
int count = atoi(CTEXT(CDR(CDR(n)))); // count of times that we should look for
fix delay = i2f(atoi(CTEXT(CDR(CDR(CDR(n))))));
fix time;
Assert ( count > 0 );
if ( mission_log_get_time(LOG_SHIP_DESTROYED, docker, NULL, NULL) || mission_log_get_time(LOG_SHIP_DESTROYED, dockee, NULL, NULL) )
return SEXP_KNOWN_FALSE;
if (sexp_query_has_yet_to_arrive(docker))
return SEXP_CANT_EVAL;
if (sexp_query_has_yet_to_arrive(dockee))
return SEXP_CANT_EVAL;
if ( !mission_log_get_time_indexed(LOG_SHIP_DOCK, docker, dockee, count, &time) )
return 0;
if ( (Missiontime - time) >= delay )
return SEXP_KNOWN_TRUE;
else
return 0;
}
int sexp_has_undocked_delay( int n )
{
char *docker = CTEXT(n);
char *dockee = CTEXT(CDR(n));
int count = atoi(CTEXT(CDR(CDR(n))));
fix delay = i2f(atoi(CTEXT(CDR(CDR(CDR(n))))));
fix time;
if (sexp_query_has_yet_to_arrive(docker))
return SEXP_CANT_EVAL;
if (sexp_query_has_yet_to_arrive(dockee))
return SEXP_CANT_EVAL;
Assert ( count > 0 );
if ( !mission_log_get_time_indexed(LOG_SHIP_UNDOCK, docker, dockee, count, &time) ) {
// if either ship destroyed before they dock, then sexp is known false
if ( mission_log_get_time(LOG_SHIP_DESTROYED, docker, NULL, NULL) || mission_log_get_time(LOG_SHIP_DESTROYED, dockee, NULL, NULL) )
return SEXP_KNOWN_FALSE;
else
return 0;
}
if ( (Missiontime - time) >= delay )
return SEXP_KNOWN_TRUE;
else
return 0;
}
int sexp_has_arrived_delay( int n )
{
fix delay, time;
int val;
Assert ( n >= 0 );
time = 0;
delay = i2f(atoi(CTEXT(n)));
// check return value from arrived function. if can never arrive, then return that value here as well
val = sexp_has_arrived( CDR(n), &time );
if ( val == SEXP_KNOWN_FALSE )
return val;
if ( val == SEXP_CANT_EVAL )
return SEXP_CANT_EVAL;
if ( val ) {
if ( (Missiontime - time) >= delay )
return SEXP_KNOWN_TRUE;
}
return 0;
}
int sexp_has_departed_delay( int n )
{
fix delay, time;
int val;
Assert ( n >= 0 );
time = 0;
delay = i2f(atoi(CTEXT(n)));
// must first check to see if the departed function could ever be true/false or is true or false.
// if it can never be true, return that value
val = sexp_has_departed( CDR(n), &time);
if ( val == SEXP_KNOWN_FALSE )
return val;
if ( val == SEXP_CANT_EVAL )
return SEXP_CANT_EVAL;
if ( val ) {
if ( (Missiontime - time) >= delay )
return SEXP_KNOWN_TRUE;
}
return 0;
}
// function to determine if a ship is done flying waypoints after N seconds
int sexp_are_waypoints_done_delay( int n )
{
char *ship_name, *waypoint_name;
fix time, delay;
ship_name = CTEXT(n);
waypoint_name = CTEXT(CDR(n));
delay = i2f(atoi(CTEXT(CDR(CDR(n)))));
if (sexp_query_has_yet_to_arrive(ship_name))
return SEXP_CANT_EVAL;
// a destroyed or departed ship will never reach their goal -- return known false
//
// Not checking the entries below. Ships which warp out after reaching their goal (or getting
// destroyed after their goal), but after reaching their waypoints, may have this goal incorrectly
// marked false!!!!
// now check the log for the waypoints done entry
if ( mission_log_get_time(LOG_WAYPOINTS_DONE, ship_name, waypoint_name, &time) ) {
if ( (Missiontime - time) >= delay )
return SEXP_KNOWN_TRUE;
} else {
if ( mission_log_get_time(LOG_SHIP_DESTROYED, ship_name, NULL, NULL) )
return SEXP_KNOWN_FALSE;
else if ( mission_log_get_time(LOG_SHIP_DEPART, ship_name, NULL, NULL) )
return SEXP_KNOWN_FALSE;
}
return 0;
}
// function to determine is all of a given ship type are destroyed
int sexp_ship_type_destroyed( int n )
{
int percent, type;
char *shiptype;
percent = atoi(CTEXT(n));
shiptype = CTEXT(CDR(n));
for ( type = 0; type < MAX_SHIP_TYPE_COUNTS; type++ ) {
if ( !stricmp( Ship_type_names[type], shiptype) )
break;
}
// bogus if we reach the end of this array!!!!
if ( type == MAX_SHIP_TYPE_COUNTS ) {
Int3();
return 0;
}
if ( Ship_counts[type].total == 0 )
return 0;
// determine if the percentage of killed/total is >= percentage given in the expression
if ( (Ship_counts[type].killed * 100 / Ship_counts[type].total) >= percent)
return SEXP_KNOWN_TRUE;
return 0;
}
// following are time based functions
int sexp_has_time_elapsed(int n)
{
int time = num_eval(n);
if ( f2i(Missiontime) >= time )
return SEXP_KNOWN_TRUE;
return 0;
}
// next function returns the time into the mission
int sexp_mission_time()
{
return f2i(Missiontime);
}
// returns percent of length of distance to special warpout plane
int sexp_special_warp_dist( int n)
{
char *ship_name;
int shipnum;
// get shipname
ship_name = CTEXT(n);
// check to see if either ship was destroyed or departed. If so, then make this node known
// false
if ( mission_log_get_time(LOG_SHIP_DESTROYED, ship_name, NULL, NULL) || mission_log_get_time( LOG_SHIP_DEPART, ship_name, NULL, NULL) ) {
return SEXP_NAN_FOREVER;
}
// get ship name
shipnum = ship_name_lookup(ship_name);
if (shipnum < 0) {
return SEXP_NAN;
}
// check that ship has warpout_objnum
if (Ships[shipnum].special_warp_objnum == -1) {
return SEXP_NAN;
}
Assert( (Ships[shipnum].special_warp_objnum >= 0) && (Ships[shipnum].special_warp_objnum < MAX_OBJECTS));
if ( (Ships[shipnum].special_warp_objnum < 0) && (Ships[shipnum].special_warp_objnum >= MAX_OBJECTS) ) {
return SEXP_NAN;
}
// check the special warpout device is valid
int valid = FALSE;
object *ship_objp = &Objects[Ships[shipnum].objnum];
object *warp_objp = &Objects[Ships[shipnum].special_warp_objnum];
if (warp_objp->type == OBJ_SHIP) {
if (Ship_info[Ships[warp_objp->instance].ship_info_index].flags & SIF_KNOSSOS_DEVICE) {
valid = TRUE;
}
}
if (!valid) {
return SEXP_NAN;
}
// check if within 45 degree half-angle cone of facing
float dot = fl_abs(vm_vec_dotprod(&warp_objp->orient.vec.fvec, &ship_objp->orient.vec.fvec));
if (dot < 0.707f) {
return SEXP_NAN;
}
// get distance
vector hit_pt;
float dist = fvi_ray_plane(&hit_pt, &warp_objp->pos, &warp_objp->orient.vec.fvec, &ship_objp->pos, &ship_objp->orient.vec.fvec, 0.0f);
polymodel *pm = model_get(Ships[shipnum].modelnum);
dist += pm->mins.xyz.z;
// return as a percent of length
return (int) (100.0f * dist / ship_get_length(&Ships[shipnum]));
}
int sexp_time_destroyed(int n)
{
fix time;
if ( !mission_log_get_time( LOG_SHIP_DESTROYED, CTEXT(n), NULL, &time) ){ // returns 0 when not found
return SEXP_NAN;
}
return f2i(time);
}
int sexp_time_wing_destroyed(int n)
{
fix time;
if ( !mission_log_get_time( LOG_WING_DESTROYED, CTEXT(n), NULL, &time) ){
return SEXP_NAN;
}
return f2i(time);
}
int sexp_time_docked(int n)
{
fix time;
char *docker = CTEXT(n);
char *dockee = CTEXT(CDR(n));
int count = atoi(CTEXT(CDR(CDR(n))));
Assert ( count > 0 );
if ( !mission_log_get_time_indexed(LOG_SHIP_DOCK, docker, dockee, count, &time) ){
return SEXP_NAN;
}
return f2i(time);
}
int sexp_time_undocked(int n)
{
fix time;
char *docker = CTEXT(n);
char *dockee = CTEXT(CDR(n));
int count = atoi(CTEXT(CDR(CDR(n))));
Assert ( count > 0 );
if ( !mission_log_get_time_indexed(LOG_SHIP_UNDOCK, docker, dockee, count, &time) ){
return SEXP_NAN;
}
return f2i(time);
}
int sexp_time_ship_arrived(int n)
{
fix time;
Assert( n != -1 );
if ( !mission_log_get_time( LOG_SHIP_ARRIVE, CTEXT(n), NULL, &time ) ){
return SEXP_NAN;
}
return f2i(time);
}
int sexp_time_wing_arrived(int n)
{
fix time;
Assert( n != -1 );
if ( !mission_log_get_time( LOG_WING_ARRIVE, CTEXT(n), NULL, &time ) ){
return SEXP_NAN;
}
return f2i(time);
}
int sexp_time_ship_departed(int n)
{
fix time;
Assert( n != -1 );
if ( !mission_log_get_time( LOG_SHIP_DEPART, CTEXT(n), NULL, &time ) ){
return SEXP_NAN;
}
return f2i(time);
}
int sexp_time_wing_departed(int n)
{
fix time;
Assert( n != -1 );
if ( !mission_log_get_time( LOG_WING_DEPART, CTEXT(n), NULL, &time ) ){
return SEXP_NAN;
}
return f2i(time);
}
// function to return the remaining shields as a percentage of the given ship.
int sexp_shields_left(int n)
{
int shipnum, percent;
char *shipname;
shipname = CTEXT(n);
// if ship is gone or departed, cannot ever evaluate properly. Return NAN_FOREVER
if ( mission_log_get_time(LOG_SHIP_DESTROYED, shipname, NULL, NULL) || mission_log_get_time( LOG_SHIP_DEPART, shipname, NULL, NULL) ){
return SEXP_NAN_FOREVER;
}
shipnum = ship_name_lookup( shipname );
if ( shipnum == -1 ){ // hmm.. if true, must not have arrived yet
return SEXP_NAN;
}
// now return the amount of shields left as a percentage of the whole.
percent = (int)(get_shield_strength(&Objects[Ships[shipnum].objnum]) / Ship_info[Ships[shipnum].ship_info_index].shields * 100.0f);
return percent;
}
// function to return the remaining hits left as a percentage of the whole. This hit amount counts for
// all hits on the ship (hull + subsystems). Use hits_left_hull to find hull hits remaining.
int sexp_hits_left(int n)
{
int shipnum, percent;
char *shipname;
shipname = CTEXT(n);
// if ship is gone or departed, cannot ever evaluate properly. Return NAN_FOREVER
if ( mission_log_get_time(LOG_SHIP_DESTROYED, shipname, NULL, NULL) || mission_log_get_time( LOG_SHIP_DEPART, shipname, NULL, NULL) ){
return SEXP_NAN_FOREVER;
}
shipnum = ship_name_lookup( shipname );
if ( shipnum == -1 ){ // hmm.. if true, must not have arrived yet
return SEXP_NAN;
}
// now return the amount of hits left as a percentage of the whole. Subtract the percentage from 100
// since we are working with total hit points taken, not total remaining.
ship *shipp = &Ships[shipnum];
ship_info *sip = &Ship_info[shipp->ship_info_index];
object *objp = &Objects[shipp->objnum];
percent = (int) (100.0f * objp->hull_strength / sip->initial_hull_strength);
return percent;
}
// is ship visible on radar
// returns 0 - not visible
// returns 1 - marginally targetable (jiggly on radar)
// returns 2 - fully targetable
int sexp_is_ship_visible(int n)
{
char *shipname;
int shipnum;
int ship_is_visible = 0;
// if multiplayer, bail
if (Game_mode & GM_MULTIPLAYER) {
return SEXP_NAN_FOREVER;
}
shipname = CTEXT(n);
// if ship is gone or departed, cannot ever evaluate properly. Return NAN_FOREVER
if ( mission_log_get_time(LOG_SHIP_DESTROYED, shipname, NULL, NULL) || mission_log_get_time( LOG_SHIP_DEPART, shipname, NULL, NULL) ){
return SEXP_NAN_FOREVER;
}
shipnum = ship_name_lookup( shipname );
if ( shipnum == -1 ){ // hmm.. if true, must not have arrived yet
return SEXP_NAN;
}
// get ship's *radar* visiblity
if (Player_ship != NULL) {
if (ship_is_visible_by_team(shipnum, Player_ship->team)) {
ship_is_visible = 2;
}
}
// only check awacs level if ship is not visible by team
if (Player_ship != NULL && !ship_is_visible) {
float awacs_level = awacs_get_level(&Objects[Ships[shipnum].objnum], Player_ship);
if (awacs_level >= 1.0f) {
ship_is_visible = 2;
} else if (awacs_level > 0) {
ship_is_visible = 1;
}
}
return ship_is_visible;
}
// get multi team v team score
// if not multi team v team return 0
// if invalid team return 0
int sexp_team_score(int node)
{
#ifndef NO_NETWORK
// if multi t vs t
if (Game_mode & GM_MULTIPLAYER) {
if (Netgame.type_flags & NG_TYPE_TEAM) {
int team = atoi(CTEXT(node));
if (team == 1) {
return Multi_team0_score;
} else if (team == 2) {
return Multi_team1_score;
} else {
// invalid team index
Int3();
return 0;
}
}
}
#endif
return 0;
}
// function to return the remaining hits left on a subsystem as a percentage of thw whole.
int sexp_hits_left_subsystem(int n)
{
int shipnum, percent, type;
char *shipname;
char *subsys_name;
shipname = CTEXT(n);
// if ship is gone or departed, cannot ever evaluate properly. Return NAN_FOREVER
if ( mission_log_get_time(LOG_SHIP_DESTROYED, shipname, NULL, NULL) || mission_log_get_time( LOG_SHIP_DEPART, shipname, NULL, NULL) ){
return SEXP_NAN_FOREVER;
}
shipnum = ship_name_lookup( shipname );
if ( shipnum == -1 ){ // hmm.. if true, must not have arrived yet
return SEXP_NAN;
}
subsys_name = CTEXT(CDR(n));
type = ai_get_subsystem_type( subsys_name );
if ( (type >= 0) && (type < SUBSYSTEM_MAX) ) {
// return as a percentage the hits remaining on the subsystem as a whole (i.e. for 3 engines,
// we are returning the sum of the hits on the 3 engines)
if (type == SUBSYSTEM_UNKNOWN) {
// find the ship subsystem by searching ship's subsys_list
ship_subsys *ss;
ss = GET_FIRST( &Ships[shipnum].subsys_list );
while ( ss != END_OF_LIST( &Ships[shipnum].subsys_list ) ) {
if ( !stricmp(ss->system_info->subobj_name, subsys_name)) {
percent = (int) (ss->current_hits / ss->system_info->max_hits * 100.0f);
return percent;
}
ss = GET_NEXT( ss );
}
// we reached end of ship subsys list without finding subsys_name
Int3();
} else {
percent = (int)(ship_get_subsystem_strength(&Ships[shipnum],type) * 100.0f);
return percent;
}
}
return SEXP_NAN; // if for some strange reason, the type field of the subsystem is bogus
}
int sexp_determine_team(char *subj)
{
int team = 0;
if (!stricmp(subj, "<any friendly>")){
team = TEAM_FRIENDLY;
} else if (!stricmp(subj, "<any hostile>")){
team = TEAM_HOSTILE;
} else if (!stricmp(subj, "<any neutral>")){
team = TEAM_NEUTRAL;
} else if (!stricmp(subj, "<any unknown>")){
team = TEAM_UNKNOWN;
} else if (!stricmp(subj, "<any traitor>")){
team = TEAM_TRAITOR;
}
return team;
}
// returns the distance between two objects. If a wing is specificed as one (or both) or the arguments
// to this function, we are looking for the closest distance
int sexp_distance(int n)
{
int i, team, obj, dist, dist_min = 0, inited = 0;
char *sname1, *sname2;
wing *wingp;
ship_obj *so;
sname1 = CTEXT(n);
sname2 = CTEXT(CDR(n));
// check to see if either ship was destroyed or departed. If so, then make this node known
// false
if ( mission_log_get_time(LOG_SHIP_DESTROYED, sname1, NULL, NULL) || mission_log_get_time( LOG_SHIP_DEPART, sname1, NULL, NULL) ||
mission_log_get_time(LOG_SHIP_DESTROYED, sname2, NULL, NULL) || mission_log_get_time( LOG_SHIP_DEPART, sname2, NULL, NULL) )
return SEXP_NAN_FOREVER;
// one of the names might be the name of a wing. Check to see if the wing is detroyed or departed
if ( mission_log_get_time(LOG_WING_DESTROYED, sname1, NULL, NULL) || mission_log_get_time( LOG_WING_DEPART, sname1, NULL, NULL) ||
mission_log_get_time(LOG_WING_DESTROYED, sname2, NULL, NULL) || mission_log_get_time( LOG_WING_DEPART, sname2, NULL, NULL) )
return SEXP_NAN_FOREVER;
team = sexp_determine_team(sname1);
if (team) { // we have a team type, so check all ships of that type
so = GET_FIRST(&Ship_obj_list);
while (so != END_OF_LIST(&Ship_obj_list)) {
if (Ships[Objects[so->objnum].instance].team == team) {
obj = so->objnum;
dist = sexp_distance2(obj, sname2);
if (dist != SEXP_NAN) {
if (!inited || (dist < dist_min)) {
dist_min = dist;
inited = 1;
}
}
}
so = GET_NEXT(so);
}
if (!inited) // no objects were checked
return SEXP_NAN;
return dist_min;
}
// at this point, we must have a wing, ship or point for a subj
obj = ship_name_lookup(sname1);
if (obj >= 0)
return sexp_distance2(Ships[obj].objnum, sname2);
// at this point, we must have a wing or point for a subj
obj = waypoint_lookup(sname1);
if (obj >= 0)
return sexp_distance2(obj, sname2);
// at this point, we must have a wing for a subj
obj = wing_name_lookup(sname1);
if (obj < 0)
return SEXP_NAN; // we apparently don't have anything legal
wingp = &Wings[obj];
for (i=0; i<wingp->current_count; i++) {
obj = Ships[wingp->ship_index[i]].objnum;
dist = sexp_distance2(obj, sname2);
if (dist != SEXP_NAN) {
if (!inited || (dist < dist_min)) {
dist_min = dist;
inited = 1;
}
}
}
if (!inited) // no objects were checked
return SEXP_NAN;
return dist_min;
}
// check distance between a given ship and a given subject (ship, wing, any <team>).
int sexp_distance2(int obj1, char *subj)
{
int i, team, obj2, dist, dist_min = 0, inited = 0;
wing *wingp;
ship_obj *so;
team = sexp_determine_team(subj);
if (team) { // we have a team type, so check all ships of that type
so = GET_FIRST(&Ship_obj_list);
while (so != END_OF_LIST(&Ship_obj_list)) {
if (Ships[Objects[so->objnum].instance].team == team) {
obj2 = so->objnum;
dist = sexp_distance3(obj1, obj2);
if (dist != SEXP_NAN) {
if (!inited || (dist < dist_min)) {
dist_min = dist;
inited = 1;
}
}
}
so = GET_NEXT(so);
}
if (!inited) // no objects were checked
return SEXP_NAN;
return dist_min;
}
// at this point, we must have a wing, ship or point for a subj
obj2 = ship_name_lookup(subj);
if (obj2 >= 0)
return sexp_distance3(obj1, Ships[obj2].objnum);
// at this point, we must have a wing or point for a subj
obj2 = waypoint_lookup(subj);
if (obj2 >= 0)
return sexp_distance3(obj1, obj2);
// at this point, we must have a wing for a subj
obj2 = wing_name_lookup(subj);
if (obj2 < 0)
return SEXP_NAN; // we apparently don't have anything legal
wingp = &Wings[obj2];
for (i=0; i<wingp->current_count; i++) {
obj2 = Ships[wingp->ship_index[i]].objnum;
dist = sexp_distance3(obj1, obj2);
if (dist != SEXP_NAN) {
if (!inited || (dist < dist_min)) {
dist_min = dist;
inited = 1;
}
}
}
if (!inited) // no objects were checked
return SEXP_NAN;
return dist_min;
}
// check distance between two given objects
int sexp_distance3(int obj1, int obj2)
{
if ( (obj1 == -1) || (obj2 == -1) ) // if either object isn't present in the mission now
return SEXP_NAN; // return a really small number
if ( (Objects[obj1].type == OBJ_SHIP) && (Objects[obj2].type == OBJ_SHIP) ) {
if (OBJ_INDEX(Player_obj) == obj1)
return (int) hud_find_target_distance( &Objects[obj2], &Objects[obj1] );
else
return (int) hud_find_target_distance( &Objects[obj1], &Objects[obj2] );
} else {
return (int) vm_vec_dist_quick( &Objects[obj1].pos, &Objects[obj2].pos );
}
}
// funciton to determine when the last meaningful order was given to one or more ships. Returns
// true or false depending on whether or not a meaningful order was received
int sexp_last_order_time( int n )
{
int instance, i;
fix time;
char *name;
ai_goals *aigp;
time = i2f(atoi(CTEXT(n)));
Assert ( time >= 0 );
n = CDR(n);
while ( n != -1 ) {
name = CTEXT(n);
instance = ship_name_lookup(name);
if ( instance != -1 ) {
aigp = Ai_info[Ships[instance].ai_index].goals;
} else {
instance = wing_name_lookup(name);
if ( instance == -1 ) // if we cannot find ship or wing, return 0
return 0;
aigp = Wings[instance].ai_goals;
}
// with the ship, check the ai_goals structure for this ship and determine if there are any
// orders which are < time seconds since current mission time
for ( i = 0; i < MAX_AI_GOALS; i++ ) {
int mode;
mode = aigp->ai_mode;
if ( (mode != AI_GOAL_NONE) && (mode != AI_GOAL_WARP) )
if ( (aigp->time + time) > Missiontime )
break;
aigp++;
}
if ( i == MAX_AI_GOALS )
return 1;
n = CDR(n);
}
return 0;
}
// sexpression to return the number of players in the mission
int sexp_num_players()
{
int count;
object *objp;
count = 0;
for ( objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
if ( (objp->type == OBJ_SHIP) && (objp->flags & OF_PLAYER_SHIP) )
count++;
}
return count;
}
// expression to determine if the current skill level of the game is at least
// the skill level given in the sexpression
int sexp_skill_level_at_least( int n )
{
int i;
char *level_name;
level_name = CTEXT(n);
for (i = 0; i < NUM_SKILL_LEVELS; i++ ) {
if ( !stricmp(level_name, Skill_level_names(i, 0)) ) {
if ( Game_skill_level >= i ){
return 1;
} else {
return 0;
}
}
}
// return 0 if not found!!!
return 0;
}
int sexp_was_promotion_granted(int n)
{
if (Player->flags & PLAYER_FLAGS_PROMOTED)
return 1;
return 0;
}
int sexp_was_medal_granted(int n)
{
int i;
char *medal_name;
if (n < 0) {
if (Player->stats.m_medal_earned >= 0)
return 1;
return 0;
}
medal_name = CTEXT(n);
for (i=0; i<NUM_MEDALS; i++) {
if (!stricmp(medal_name, Medals[i].name))
break;
}
if ( (i < NUM_MEDALS) && (Player->stats.m_medal_earned == i) )
return 1;
return 0;
}
// function which returns true if the percentage of ships (and ships in wings) departed is at
// least the percentage given. what determine if we should check destroyed or departed status
int sexp_percent_ships_depart_destroy(int n, int what)
{
int percent;
int total, count;
char *name;
percent = atoi(CTEXT(n));
total = 0;
count = 0;
// iterate through the rest of the ships/wings in the list and tally the departures and the
// total
for ( n = CDR(n); n != -1; n = CDR(n) ) {
int wingnum;
name = CTEXT(n);
wingnum = wing_name_lookup( name, 1 );
if ( wingnum != -1 ) {
// for wings, we can increment the total by the total number of ships that we expect for
// this wing, and the departures by the number of departures stored for this wing
total += (Wings[wingnum].wave_count * Wings[wingnum].num_waves);
if ( what == OP_PERCENT_SHIPS_DEPARTED )
count += Wings[wingnum].total_departed;
else if ( what == OP_PERCENT_SHIPS_DESTROYED )
count += Wings[wingnum].total_destroyed;
else
Int3(); // this would be very bogus!
} else {
// must be a ship, so increment the total by 1, then determine if this ship has departed
total++;
if ( what == OP_PERCENT_SHIPS_DEPARTED ) {
if ( mission_log_get_time(LOG_SHIP_DEPART, name, NULL, NULL) )
count++;
} else if ( what == OP_PERCENT_SHIPS_DESTROYED ) {
if ( mission_log_get_time(LOG_SHIP_DESTROYED, name, NULL, NULL) )
count++;
} else
Int3(); // this would be very bogus as well.
}
}
// now, look at the percentage
if ( ((count * 100) / total) >= percent )
return SEXP_KNOWN_TRUE;
else
return 0;
}
// function to tell is a list of ships has departed from within a radius of a given jump node.
// returns true N seconds after the list of ships have departed
int sexp_depart_node_delay( int n )
{
int delay, count, num_departed;
char *jump_node_name, *name;
fix latest_time, this_time;
delay = atoi( CTEXT(n) );
n = CDR(n);
jump_node_name = CTEXT(n);
// iterate through the list of ships
n = CDR(n);
latest_time = 0;
count = 0;
num_departed = 0;
while ( n != -1 ) {
count++;
name = CTEXT(n);
if (sexp_query_has_yet_to_arrive(name))
return SEXP_CANT_EVAL;
// if ship/wing destroyed, sexpression is known false. Also, if there is no departure log entry, then
// the sexpression is not true.
if ( mission_log_get_time(LOG_SHIP_DESTROYED, name, NULL, NULL) )
return SEXP_KNOWN_FALSE;
else if ( mission_log_get_time(LOG_SHIP_DEPART, name, jump_node_name, &this_time) ) {
num_departed++;
if ( this_time > latest_time )
latest_time = this_time;
}
n = CDR(n);
}
if ( (count == num_departed) && ((Missiontime - latest_time) >= delay) )
return SEXP_KNOWN_TRUE;
else
return 0;
}
// sexpression which returns true when the listed ships/wings have all been destroyed or
// have departed.
int sexp_destroyed_departed_delay( int n )
{
int count, total;
fix delay, latest_time;
char *name;
// get the delay
delay = i2f(atoi(CTEXT(n)));
n = CDR(n);
count = 0; // number destroyed or departed
total = 0; // total number of ships/wings to check
latest_time = 0;
while ( n != -1 ) {
int wingnum;
fix time_gone = 0;
total++;
name = CTEXT(n);
// for wings, check the WF_GONE flag to see if there are no more ships in this wing to arrive.
wingnum = wing_name_lookup(name, 1);
if ( wingnum != -1 ) {
if ( Wings[wingnum].flags & WF_WING_GONE ) {
// be sure to get the latest time of one of these
if ( Wings[wingnum].time_gone > latest_time ){
time_gone = Wings[wingnum].time_gone;
}
count++;
}
} else if ( mission_log_get_time(LOG_SHIP_DEPART, name, NULL, &time_gone) ) {
count++;
} else if ( mission_log_get_time(LOG_SHIP_DESTROYED, name, NULL, &time_gone) ) {
count++;
}
// check our latest time
if ( time_gone > latest_time ){
latest_time = time_gone;
}
n = CDR(n);
}
if ( (count == total) && (Missiontime > (latest_time + delay)) )
return SEXP_KNOWN_TRUE;
else
return 0;
}
int sexp_special_warpout_name( int node )
{
int shipnum, knossos_num;
char *ship_name, *knossos;
ship_name = CTEXT(node);
knossos = CTEXT(CDR(node));
// check to see if either ship was destroyed or departed. If so, then make this node known
// false
if ( mission_log_get_time(LOG_SHIP_DESTROYED, ship_name, NULL, NULL) || mission_log_get_time( LOG_SHIP_DEPART, ship_name, NULL, NULL) ||
mission_log_get_time(LOG_SHIP_DESTROYED, knossos, NULL, NULL) || mission_log_get_time( LOG_SHIP_DEPART, knossos, NULL, NULL) )
return SEXP_NAN_FOREVER;
// get ship name
shipnum = ship_name_lookup(ship_name);
if (shipnum < 0) {
return SEXP_NAN;
}
// get knossos ship
knossos_num = ship_name_lookup(knossos);
if (knossos_num < 0) {
return SEXP_NAN;
}
// set special warpout objnum
Ships[shipnum].special_warp_objnum = knossos_num;
return 0;
}
// function which determines if N seconds have elpased since all discovery of all cargo
// of given ships
int sexp_is_cargo_known( int n, int check_delay )
{
int count, shipnum, num_known, delay;
char *name;
Assert ( n >= 0 );
count = 0;
num_known = 0;
// get the delay value (if there is one)
delay = 0;
if ( check_delay ) {
delay = atoi(CTEXT(n) );
n = CDR(n);
}
while ( n != -1 ) {
fix time_known;
int is_known;
is_known = 0;
count++;
// see if we have already checked this entry
if ( Sexp_nodes[n].value == SEXP_KNOWN_TRUE ) {
num_known++;
} else {
int exited_index;
name = CTEXT(n);
// see if the ship has already exited the mission (either through departure or destruction). If so,
// grab the status of whether the cargo is known from this list
exited_index = ship_find_exited_ship_by_name( name );
if (exited_index != -1 ) {
if ( !(Ships_exited[exited_index].flags & SEF_CARGO_KNOWN) )
return SEXP_KNOWN_FALSE;
// check the delay of when we found out. We use the ship died time which isn't entirely accurate
// but won't cause huge delays.
time_known = Missiontime - Ships_exited[exited_index].time;
if ( f2i(time_known) >= delay )
is_known = 1;
} else {
// otherwise, ship should still be in the mission. If ship_name_lookup returns -1, then ship
// is yet to arrive.
shipnum = ship_name_lookup( name );
if ( shipnum != -1 ) {
if ( Ships[shipnum].flags & SF_CARGO_REVEALED ) {
time_known = Missiontime - Ships[shipnum].time_cargo_revealed;
if ( f2i(time_known) >= delay )
is_known = 1;
}
}
}
}
// if cargo is known, mark our variable and this sexpression.
if ( is_known ) {
num_known++;
Sexp_nodes[n].value = SEXP_KNOWN_TRUE;
}
n = CDR(n);
}
Directive_count += count - num_known;
if ( count == num_known )
return SEXP_KNOWN_TRUE;
else
return 0;
}
int sexp_has_been_tagged_delay(int n)
{
int count, shipnum, num_known, delay;
char *name;
Assert ( n >= 0 );
count = 0;
num_known = 0;
// get the delay value
delay = atoi(CTEXT(n) );
n = CDR(n);
while ( n != -1 ) {
fix time_known;
int is_known;
is_known = 0;
count++;
// see if we have already checked this entry
if ( Sexp_nodes[n].value == SEXP_KNOWN_TRUE ) {
num_known++;
} else {
int exited_index;
name = CTEXT(n);
// see if the ship has already exited the mission (either through departure or destruction). If so,
// grab the status of whether the cargo is known from this list
exited_index = ship_find_exited_ship_by_name( name );
if (exited_index != -1 ) {
if ( !(Ships_exited[exited_index].flags & SEF_BEEN_TAGGED) )
return SEXP_KNOWN_FALSE;
// check the delay of when we found out. We use the ship died time which isn't entirely accurate
// but won't cause huge delays.
time_known = Missiontime - Ships_exited[exited_index].time;
if ( f2i(time_known) >= delay )
is_known = 1;
} else {
// otherwise, ship should still be in the mission. If ship_name_lookup returns -1, then ship
// is yet to arrive.
shipnum = ship_name_lookup( name );
if ( shipnum != -1 ) {
if ( Ships[shipnum].time_first_tagged != 0 ) {
time_known = Missiontime - Ships[shipnum].time_first_tagged;
if ( f2i(time_known) >= delay )
is_known = 1;
}
}
}
}
// if cargo is known, mark our variable and this sexpression.
if ( is_known ) {
num_known++;
Sexp_nodes[n].value = SEXP_KNOWN_TRUE;
}
n = CDR(n);
}
Directive_count += count - num_known;
if ( count == num_known )
return SEXP_KNOWN_TRUE;
else
return 0;
}
int sexp_cap_subsys_cargo_known_delay(int n)
{
int delay, count, delta_time, num_known;
char *ship_name, *subsys_name;
num_known = 0;
count = 0;
// get delay
delay = atoi(CTEXT(n));
n = CDR(n);
// get shipname
ship_name = CTEXT(n);
n = CDR(n);
while ( n != -1 ) {
fix time_known;
int is_known;
int logged;
is_known = 0;
logged = 0;
count++;
// see if we have already checked this entry
if ( Sexp_nodes[n].value == SEXP_KNOWN_TRUE ) {
num_known++;
} else {
// get subsys name
subsys_name = CTEXT(n);
logged = mission_log_get_time(LOG_CAP_SUBSYS_CARGO_REVEALED, ship_name, subsys_name, &time_known);
if (logged) {
delta_time = f2i(Missiontime - time_known);
if (delta_time >= delay) {
is_known = 1;
}
}
// if (exited or destroyed) and not logged, known false
// otherwise, still out there and cargo not yet known
if ( (mission_log_get_time(LOG_SHIP_DEPART, ship_name, NULL, NULL ) || mission_log_get_time(LOG_SHIP_DESTROYED, ship_name, NULL, NULL )) && !logged ) {
return SEXP_KNOWN_FALSE;
}
}
if (is_known) {
num_known++;
Sexp_nodes[n].value = SEXP_KNOWN_TRUE;
}
n = CDR(n);
}
Directive_count += count - num_known;
if ( count == num_known )
return SEXP_KNOWN_TRUE;
else
return 0;
}
// return object index of waypoint or -1 if no such waypoint
int waypoint_lookup(char *name)
{
char buf[128];
int i;
object *ptr;
ptr = GET_FIRST(&obj_used_list);
while (ptr != END_OF_LIST(&obj_used_list)) {
if (ptr->type == OBJ_WAYPOINT) {
i = ptr->instance;
sprintf(buf, "%s:%d", Waypoint_lists[i / 65536].name, (i & 0xffff) + 1);
if ( !stricmp(buf, name) )
return OBJ_INDEX(ptr);
}
ptr = GET_NEXT(ptr);
}
return -1;
}
// conditional sexpressions follow
// eval_when evaluates the when conditional
int eval_when(int n)
{
int cond, val;
Assert( n >= 0 ); // must have valid sexp index
cond = CAR(n);
val = eval_sexp(cond); // get the value of the the conditional
if ( val ) { // if the value is true, perform the actions is the 'then' part of the if
int actions, exp;
actions = CDR(n);
while ( actions != -1 ) {
exp = CAR(actions);
if ( exp != -1 )
val = eval_sexp(exp); // these sexp evaled only for side effects
actions = CDR(actions);
}
}
if (Sexp_nodes[cond].value == SEXP_KNOWN_FALSE)
return SEXP_KNOWN_FALSE; // no need to waste time on this anymore
if (val == SEXP_KNOWN_FALSE)
return 0; // can't return known false, as this would bypass future actions under the when
return val;
}
// eval_cond() evaluates the cond conditional
int eval_cond( int n )
{
int cond = 0, node, val = 0;
Assert (n >= 0);
while (n >= 0) {
node = CAR(n);
cond = CAR(node);
val = eval_sexp(cond);
// if the conditional evaluated to true, then we must evaluate the rest of the expression returning
// the value of this evaluation
if (val) {
int actions, exp;
val = 0;
actions = CDR(node);
while (actions >= 0) {
exp = CAR(actions);
if (exp >= -1)
val = eval_sexp(exp); // these sexp evaled only for side effects
actions = CDR(actions);
}
break;
}
// move onto the next cond clause
n = CDR(n);
}
return val;
}
int sexp_is_iff( int n )
{
char *ship_name, *iff;
int num, team;
Assert ( n >= 0 );
// iff value is the first parameter, second is a list of one or more ships to check to see if the
// iff value matches
iff = CTEXT(n);
if ( !stricmp(iff, "friendly") )
team = TEAM_FRIENDLY;
else if ( !stricmp(iff, "hostile") )
team = TEAM_HOSTILE;
else if ( !stricmp(iff, "neutral") )
team = TEAM_NEUTRAL;
else if ( !stricmp(iff, "unknown") )
team = TEAM_UNKNOWN;
else if ( !stricmp(iff, "traitor") )
team = TEAM_TRAITOR;
else {
Int3();
mprintf(("Warning: Team %s no longer supported. Just Friendly and Hostile.\n", iff));
team = TEAM_HOSTILE;
}
n = CDR(n);
while ( n != -1 ) {
ship_name = CTEXT(n);
// find the ship and check to be sure that it is still around.
num = ship_name_lookup(ship_name);
if ( num < 0 ) // if the ship is gone, can't check it's iff.
continue;
// if the team doesn't match the team specified, return 0 immediately
if ( Ships[num].team != team)
return 0;
n = CDR(n);
}
return 1;
}
void sexp_change_iff( int n )
{
char *ship_name, *new_iff;
int num, new_team;
Assert ( n >= 0 );
new_iff = CTEXT(n);
if ( !stricmp(new_iff, "friendly") )
new_team = TEAM_FRIENDLY;
else if ( !stricmp(new_iff, "hostile") )
new_team = TEAM_HOSTILE;
else if ( !stricmp(new_iff, "neutral") )
new_team = TEAM_NEUTRAL;
else if ( !stricmp(new_iff, "unknown") )
new_team = TEAM_UNKNOWN;
else if ( !stricmp(new_iff, "traitor") )
new_team = TEAM_TRAITOR;
else {
mprintf(("Warning: Team %s no longer supported. Just Friendly and Hostile.\n", new_iff));
new_team = TEAM_HOSTILE;
Int3();
}
n = CDR(n);
while ( n != -1 ) {
ship_name = CTEXT(n);
// find the ship and check to be sure that it is still around.
num = ship_name_lookup(ship_name);
if ( num >= 0 ) { // only change iff if we found the ship
Ships[num].team = new_team;
#ifndef NO_NETWORK
// send a network packet if we need to
if((Game_mode & GM_MULTIPLAYER) && (Net_player != NULL) && (Net_player->flags & NETINFO_FLAG_AM_MASTER) && (Ships[num].objnum >= 0)){
send_change_iff_packet(Objects[Ships[num].objnum].net_signature, new_team);
}
#endif
}
n = CDR(n);
}
}
// following routine adds an ai goal to a ship structure. The sexpression index
// passed in should be an ai-goal of the proper form. The code in MissionGoal should
// check the syntax.
void sexp_add_ship_goal( int n )
{
int num, sindex;
char *ship_name;
Assert ( n >= 0 );
ship_name = CTEXT(n);
num = ship_name_lookup(ship_name);
if ( num < 0 ) // ship not around anymore???? then forget it!
return;
sindex = CDR(n);
ai_add_ship_goal_sexp( sindex, AIG_TYPE_EVENT_SHIP, &(Ai_info[Ships[num].ai_index]) );
}
// identical to above, except add a wing
void sexp_add_wing_goal( int n )
{
int num, sindex;
char *wing_name;
Assert ( n >= 0 );
wing_name = CTEXT(n);
num = wing_name_lookup(wing_name);
if ( num < 0 ) // ship not around anymore???? then forget it!
return;
sindex = CDR(n);
ai_add_wing_goal_sexp( sindex, AIG_TYPE_EVENT_WING, num );
}
// sexp_add_goal adds a goal to the specified entiry (ships and wings have unique names between
// the two sets).
void sexp_add_goal( int n )
{
int num, sindex;
char *name;
Assert ( n >= 0 );
name = CTEXT(n);
sindex = CDR(n);
// first, look for ship name -- if found, then add ship goal. else look for wing name -- if
// found, add wing goal
if ( (num = ship_name_lookup(name)) != -1 )
ai_add_ship_goal_sexp( sindex, AIG_TYPE_EVENT_SHIP, &(Ai_info[Ships[num].ai_index]) );
else if ( (num = wing_name_lookup(name)) != -1 )
ai_add_wing_goal_sexp( sindex, AIG_TYPE_EVENT_WING, num );
}
// clears out all ai goals for a ship
void sexp_clear_ship_goals( int n )
{
int num;
char *ship_name;
Assert ( n >= 0 );
ship_name = CTEXT(n);
num = ship_name_lookup(ship_name);
ai_clear_ship_goals( &(Ai_info[Ships[num].ai_index]) );
}
// clears out ai goals for a wing
void sexp_clear_wing_goals( int n )
{
int num;
char *wing_name;
Assert ( n >= 0 );
wing_name = CTEXT(n);
num = wing_name_lookup(wing_name);
if ( num < 0 )
return;
ai_clear_wing_goals( num );
}
// this function clears all ai goals for the given ship or wing
void sexp_clear_goals( int n )
{
int num;
char *name;
Assert ( n >= 0 );
while ( n != -1 ) {
name = CTEXT(n);
if ( (num = ship_name_lookup(name)) != -1 )
ai_clear_ship_goals( &(Ai_info[Ships[num].ai_index]) );
else if ( (num = wing_name_lookup(name)) != -1 )
ai_clear_wing_goals( num );
n = CDR(n);
}
}
// this function get called by send-message or send-message random with the name of the message, sender,
// and priority.
void sexp_send_one_message( char *name, char *who_from, char *priority, int group, int delay )
{
int ipriority, num, ship_index, source;
ship *shipp;
if(physics_paused){
return;
}
// determine the priority of the message
if ( !stricmp(priority, "low") )
ipriority = MESSAGE_PRIORITY_LOW;
else if ( !stricmp(priority, "normal") )
ipriority = MESSAGE_PRIORITY_NORMAL;
else if ( !stricmp(priority, "high") )
ipriority = MESSAGE_PRIORITY_HIGH;
else {
Int3();
ipriority = MESSAGE_PRIORITY_NORMAL;
}
// check to see if the 'who_from' string is a ship that had been destroyed or departed. If so,
// then don't send the message. We must look at 'who_from' to determine what to look for. who_from
// may be any allied person, any wingman, a wingman from a specific wing, or a specific ship
ship_index = -1;
shipp = NULL;
source = MESSAGE_SOURCE_COMMAND;
if ( who_from[0] == '#' ) {
message_send_unique_to_player( name, &(who_from[1]), MESSAGE_SOURCE_SPECIAL, ipriority, group, delay );
return;
} else if (!stricmp(who_from, "<any allied>")) {
//Int3(); // no longer supported
return;
} else if ( (num = wing_name_lookup(who_from)) != -1 ) {
// message from a wing
// this will be an invalid case soon
// Int3();
// choose wing leader to speak for wing (hence "1" at end of ship_get_random_ship_in_wing)
ship_index = ship_get_random_ship_in_wing( num, SHIP_GET_NO_PLAYERS, 1 );
if ( ship_index == -1 ) {
if ( ipriority != MESSAGE_PRIORITY_HIGH )
return;
}
} else if ( mission_log_get_time(LOG_SHIP_DESTROYED, who_from, NULL, NULL) || mission_log_get_time(LOG_SHIP_DEPART, who_from, NULL, NULL)
|| mission_log_get_time(LOG_WING_DESTROYED, who_from, NULL, NULL) || mission_log_get_time(LOG_WING_DEPART, who_from, NULL, NULL) ) {
// getting into this if statement means that the ship or wing (sender) is no longer in the mission
// if message is high priority, make it come from Terran Command
if ( ipriority != MESSAGE_PRIORITY_HIGH )
return;
source = MESSAGE_SOURCE_COMMAND;
} else if ( !stricmp(who_from, "<any wingman>") || (wing_name_lookup(who_from) != -1) ) {
source = MESSAGE_SOURCE_WINGMAN;
} else {
// Message from a apecific ship
// bail if not high priority, otherwise reroute to command
source = MESSAGE_SOURCE_SHIP;
ship_index = ship_name_lookup(who_from);
if ( ship_index == -1 ) {
if ( ipriority != MESSAGE_PRIORITY_HIGH )
return;
source = MESSAGE_SOURCE_COMMAND;
}
}
if ( ship_index == -1 ){
shipp = NULL;
} else {
shipp = &Ships[ship_index];
}
message_send_unique_to_player( name, shipp, source, ipriority, group, delay );
}
void sexp_send_message( int n )
{
char *name, *who_from, *priority, *tmp;
if(physics_paused){
return;
}
Assert ( n != -1 );
who_from = CTEXT(n);
priority = CTEXT(CDR(n));
name = CTEXT(CDR(CDR(n)));
// a temporary check to see if the name field matched a priority since I am in the process
// of reordering the arguments
if ( !stricmp(name, "low") || !stricmp(name, "normal") || !stricmp(name, "high") ) {
tmp = name;
name = priority;
priority = tmp;
}
sexp_send_one_message( name, who_from, priority, 0, 0 );
}
void sexp_send_message_list( int n )
{
char *name, *who_from, *priority;
int delay;
if(physics_paused){
return;
}
// send a bunch of messages
delay = 0;
while(n != -1){
who_from = CTEXT(n);
// next node
n = CDR(n);
if(n == -1){
Warning(LOCATION, "Detected incomplete parameter list in sexp-send-message-list");
return;
}
priority = CTEXT(n);
// next node
n = CDR(n);
if(n == -1){
Warning(LOCATION, "Detected incomplete parameter list in sexp-send-message-list");
return;
}
name = CTEXT(n);
// next node
n = CDR(n);
if(n == -1){
Warning(LOCATION, "Detected incomplete parameter list in sexp-send-message-list");
return;
}
delay += atoi(CTEXT(n));
// send the message
sexp_send_one_message(name, who_from, priority, 1, delay);
// next node
n = CDR(n);
}
}
void sexp_send_random_message( int n )
{
char *name, *who_from, *priority;
int temp, num_messages, message_num;
Assert ( n != -1 );
who_from = CTEXT(n);
priority = CTEXT(CDR(n));
if(physics_paused){
return;
}
// count the number of messages that we have
n = CDR(CDR(n));
temp = n;
num_messages = 0;
while ( n != -1 ) {
n = CDR(n);
num_messages++;
}
Assert ( num_messages >= 1 );
// get a random message, and pass the parameters to send_one_message
message_num = myrand() % num_messages;
n = temp;
while ( n != -1 ) {
if ( message_num == 0 )
break;
message_num--;
n = CDR(n);
}
Assert (n != -1); // should have found the message!!!
name = CTEXT(n);
sexp_send_one_message( name, who_from, priority, 0, 0 );
}
void sexp_self_destruct( int n )
{
char *ship_name;
int shipnum;
while ( n != -1 ) {
// get the ship name and be sure that it is still in the mission. Destroy it if we find it
ship_name = CTEXT(n);
shipnum = ship_name_lookup( ship_name );
if ( shipnum == -1 )
return;
ship_self_destruct( &Objects[Ships[shipnum].objnum] );
n = CDR(n);
}
}
void sexp_next_mission( int n )
{
char *mission_name;
int i;
mission_name = CTEXT(n);
for (i = 0; i < Campaign.num_missions; i++) {
if ( !stricmp(Campaign.missions[i].name, mission_name) ) {
Campaign.next_mission = i;
return;
}
}
Error(LOCATION, "Mission name %s not found in campaign file for next-mission command", mission_name);
}
// function to deal with the end-of-campaign sexpression.
void sexp_end_of_campaign( int n )
{
// this is really a do-nothing sexpression. It is pretty much a placeholder to allow
// campaigns to have repeat-mission branches at the end of the campaign. By not setting
// anything in this function, the higher level campaign code will see this as end-of-campaign
// since next_mission isn't set to anything. (To be safe, we'll set to -1).
Campaign.next_mission = -1;
}
// sexpression to end everything. One parameter is the movie to play when this is over.
void sexp_end_campaign( int n )
{
// post and event to move us to the end-of-campaign state. There we will play a movie, then
// go to debriefing.
// gameseq_post_event( GS_EVENT_END_CAMPAIGN );
// in FS2 our ending is a bit wacky. we'll just flag the mission as having ended the campaign
Campaign_ended_in_mission = 1;
}
// sabotage subsystem reduces the strength of a subsystem by the given percentage. If it is reduced to
// below 0%, then the hits of the subsystem are set to 0
void sexp_sabotage_subsystem( int n )
{
char *shipname, *subsystem;
int percentage, shipnum, index;
float sabotage_hits;
ship *shipp;
ship_subsys *ss;
shipname = CTEXT(n);
subsystem = CTEXT(CDR(n));
percentage = atoi(CTEXT(CDR(CDR(n))));
shipnum = ship_name_lookup(shipname);
// if no ship, then return immediately.
if ( shipnum == -1 )
return;
shipp = &Ships[shipnum];
// see if we are dealing with the HULL
if ( !stricmp( subsystem, SEXP_HULL_STRING) ) {
float ihs;
object *objp;
ihs = Ship_info[shipp->ship_info_index].initial_hull_strength;
sabotage_hits = ihs * ((float)percentage / 100.0f);
objp = &Objects[shipp->objnum];
objp->hull_strength -= sabotage_hits;
// self destruct the ship if <= 0.
if ( objp->hull_strength <= 0.0f )
ship_self_destruct( objp );
return;
}
// now find the given subsystem on the ship. The subsystem should be an actual subsystem name
// and not a generic type (generic type meaning SUBSYSTEM_ENGINE, etc).
index = ship_get_subsys_index(shipp, subsystem);
if ( index == -1 ) {
nprintf(("Warning", "Couldn't find subsystem %s on ship %s for sabotage subsystem\n", subsystem, shipp->ship_name));
return;
}
// get the pointer to the subsystem. Check it's current hits against it's max hits, and
// set the strength to the given percentage if current strength is > given percentage
ss = ship_get_indexed_subsys( shipp, index );
sabotage_hits = ss->system_info->max_hits * ((float)percentage / 100.0f);
ss->current_hits -= sabotage_hits;
if ( ss->current_hits < 0.0f )
ss->current_hits = 0.0f;
ship_recalc_subsys_strength( shipp );
}
// repair_subsystem adds some percentage of hits to a subsystem. Anything repaired about 100% is
// set to max hits
void sexp_repair_subsystem( int n )
{
char *shipname, *subsystem;
int percentage, shipnum, index;
float repair_hits;
ship *shipp;
ship_subsys *ss;
shipname = CTEXT(n);
subsystem = CTEXT(CDR(n));
shipnum = ship_name_lookup(shipname);
// if no ship, then return immediately.
if ( shipnum == -1 ) {
return;
}
shipp = &Ships[shipnum];
// check if we've got a number or an op
if ( CAR(CDR(CDR(n))) != -1) {
percentage = eval_sexp( CAR(CDR(CDR(n))) );
} else {
percentage = atoi(CTEXT(CDR(CDR(n))));
}
// see if we are dealing with the HULL
if ( !stricmp( subsystem, SEXP_HULL_STRING) ) {
float ihs;
object *objp;
ihs = Ship_info[shipp->ship_info_index].initial_hull_strength;
repair_hits = ihs * ((float)percentage / 100.0f);
objp = &Objects[shipp->objnum];
objp->hull_strength += repair_hits;
if ( objp->hull_strength > ihs )
objp->hull_strength = ihs;
return;
}
// now find the given subsystem on the ship. The subsystem should be an actual subsystem name
// and not a generic type (generic type meaning SUBSYSTEM_ENGINE, etc).
index = ship_get_subsys_index(shipp, subsystem);
if ( index == -1 ) {
nprintf(("Warning", "Couldn't find subsystem %s on ship %s for repair subsystem\n", subsystem, shipp->ship_name));
return;
}
// get the pointer to the subsystem. Check it's current hits against it's max hits, and
// set the strength to the given percentage if current strength is < given percentage
ss = ship_get_indexed_subsys( shipp, index );
repair_hits = ss->system_info->max_hits * ((float)percentage / 100.0f);
ss->current_hits += repair_hits;
if ( ss->current_hits > ss->system_info->max_hits )
ss->current_hits = ss->system_info->max_hits;
ship_recalc_subsys_strength( shipp );
}
// sexpression code to set a subsystem of a ship at a specific percentage
void sexp_set_subsystem_strength( int n )
{
char *shipname, *subsystem;
int percentage, shipnum, index;
ship *shipp;
ship_subsys *ss;
shipname = CTEXT(n);
subsystem = CTEXT(CDR(n));
percentage = num_eval(CDR(CDR(n)));
shipnum = ship_name_lookup(shipname);
// if no ship, then return immediately.
if ( shipnum == -1 )
return;
shipp = &Ships[shipnum];
if ( percentage > 100 ) {
nprintf(("Warning", "percentage for set_subsystem_strength > 100 -- setting to 100\n"));
percentage = 100;
} else if ( percentage < 0 ) {
nprintf(("Werning", "percantage for set_subsystem_strength < 0 -- setting to 0\n"));
percentage = 0;
}
// see if we are dealing with the HULL
if ( !stricmp( subsystem, SEXP_HULL_STRING) ) {
float ihs;
object *objp;
objp = &Objects[shipp->objnum];
// destroy the ship if percentage is 0
if ( percentage == 0 ) {
ship_self_destruct( objp );
} else {
ihs = Ship_info[shipp->ship_info_index].initial_hull_strength;
objp->hull_strength = ihs * ((float)percentage / 100.0f);
}
return;
}
// now find the given subsystem on the ship. The subsystem should be an actual subsystem name
// and not a generic type (generic type meaning SUBSYSTEM_ENGINE, etc).
index = ship_get_subsys_index(shipp, subsystem);
if ( index == -1 ) {
nprintf(("Warning", "Couldn't find subsystem %s on ship %s for repair subsystem\n", subsystem, shipp->ship_name));
return;
}
// get the pointer to the subsystem. Check it's current hits against it's max hits, and
// set the strength to the given percentage if current strength is < given percentage
ss = ship_get_indexed_subsys( shipp, index );
// maybe blow up subsys
if (ss->current_hits > 0) {
if (percentage < 1) {
do_subobj_destroyed_stuff(shipp, ss, NULL);
}
}
// set hit points
ss->current_hits = ss->system_info->max_hits * ((float)percentage / 100.0f);
ship_recalc_subsys_strength( shipp );
}
// function which changes the validity of a goal. The flag paramater tells us whether to mark the goals
// as valid or invalid
void sexp_change_goal_validity( int n, int flag )
{
char *name;
while ( n != -1 ) {
name = CTEXT(n);
if ( flag )
mission_goal_mark_valid( name );
else
mission_goal_mark_invalid( name );
n = CDR(n);
}
}
// function to transfer cargo from one ship to another
void sexp_transfer_cargo( int n )
{
char *shipname1, *shipname2;
int shipnum1, shipnum2, i;
object *objp;
shipname1 = CTEXT(n);
shipname2 = CTEXT(CDR(n));
// find the ships -- if neither in the mission, the abort
shipnum1 = ship_name_lookup(shipname1);
shipnum2 = ship_name_lookup(shipname2);
if ( (shipnum1 == -1) || (shipnum2 == -1) )
return;
// we must be sure that these two objects are indeed docked
objp = ai_find_docked_object( &Objects[Ships[shipnum1].objnum] );
if ( objp != &Objects[Ships[shipnum2].objnum] ) {
Int3(); // you are trying to transfer cargo between two ships not docked
return;
}
if ( !stricmp(Cargo_names[Ships[shipnum1].cargo1 & CARGO_INDEX_MASK], "nothing") ) {
Int3(); // you are transfering no cargo!!!!
return;
}
// transfer cargo from ship1 to ship2
#ifndef NDEBUG
// Don't give warning for large ships (cruiser on up)
if (! (Ship_info[Ships[shipnum2].ship_info_index].flags & (SIF_BIG_SHIP | SIF_HUGE_SHIP)) ) {
if ( stricmp(Cargo_names[Ships[shipnum2].cargo1 & CARGO_INDEX_MASK], "nothing") ) {
Warning(LOCATION, "Transfering cargo to %s which already\nhas cargo %s.\nCargo will be replaced", Ships[shipnum2].ship_name, Cargo_names[Ships[shipnum2].cargo1 & CARGO_INDEX_MASK] );
}
}
#endif
Ships[shipnum2].cargo1 = char(Ships[shipnum1].cargo1 & CARGO_INDEX_MASK);
if ( !(Ships[shipnum1].cargo1 & CARGO_NO_DEPLETE) ) {
// need to set ship1's cargo to nothing. scan the cargo_names array looking for the string nothing.
// add it if not found
for (i = 0; i < Num_cargo; i++ ) {
if ( !stricmp(Cargo_names[i], "nothing") ) {
Ships[shipnum1].cargo1 = char(i);
return;
}
}
strcpy(Cargo_names[i], "Nothing");
Num_cargo++;
}
}
// this function exchanges cargo between two ships
void sexp_exchange_cargo( int n )
{
char *shipname1, *shipname2;
int shipnum1, shipnum2, temp;
object *objp;
shipname1 = CTEXT(n);
shipname2 = CTEXT(CDR(n));
// find the ships -- if neither in the mission, the abort
shipnum1 = ship_name_lookup(shipname1);
shipnum2 = ship_name_lookup(shipname2);
if ( (shipnum1 == -1) || (shipnum2 == -1) )
return;
// we must be sure that these two objects are indeed docked
objp = ai_find_docked_object( &Objects[Ships[shipnum1].objnum] );
if ( objp != &Objects[Ships[shipnum2].objnum] ) {
Int3(); // you are trying to transfer cargo between two ships not docked
return;
}
temp = (Ships[shipnum1].cargo1 & CARGO_INDEX_MASK);
Ships[shipnum1].cargo1 = char(Ships[shipnum2].cargo1 & CARGO_INDEX_MASK);
Ships[shipnum2].cargo1 = char(temp);
}
void sexp_cap_waypont_speed(int n)
{
char *shipname;
int shipnum;
int speed;
shipname = CTEXT(n);
speed = atoi(CTEXT(CDR(n)));
shipnum = ship_name_lookup(shipname);
if (shipnum == -1) {
Int3(); // trying to set waypoint speed of ship not already in game
return;
}
// cap speed to range (-1, 127) to store within char
if (speed < 0) {
speed = -1;
}
if (speed > 127) {
speed = 127;
}
Ai_info[Ships[shipnum].ai_index].waypoint_speed_cap = (char) speed;
}
// this function causes a ship to jettison its cargo
void sexp_jettison_cargo( int n )
{
char *shipname;
int jettison_delay, ship_index;
// get some data
shipname = CTEXT(n);
jettison_delay = atoi(CTEXT(CDR(n)));
// lookup the ship
ship_index = ship_name_lookup(shipname);
if(ship_index < 0){
return;
}
// jettison cargo
ship_jettison_cargo(&Ships[ship_index]);
}
void sexp_cargo_no_deplete( int n )
{
char *shipname;
int ship_index, no_deplete = 1;
// get some data
shipname = CTEXT(n);
// lookup the ship
ship_index = ship_name_lookup(shipname);
if(ship_index < 0){
return;
}
if ( !(Ship_info[Ships[ship_index].ship_info_index].flags & (SIF_BIG_SHIP | SIF_HUGE_SHIP)) ) {
Warning(LOCATION, "Trying to make non BIG or HUGE ship %s with non-depletable cargo.\n", Ships[ship_index].ship_name);
return;
}
if (CDR(n) != -1) {
no_deplete = atoi(CTEXT(CDR(n)));
Assert((no_deplete == 0) || (no_deplete == 1));
if ( (no_deplete != 0) && (no_deplete != 1) ) {
no_deplete = 1;
}
}
if (no_deplete) {
Ships[ship_index].cargo1 |= CARGO_NO_DEPLETE;
} else {
Ships[ship_index].cargo1 &= (~CARGO_NO_DEPLETE);
}
}
// sexpression to end the mission after N seconds!
void sexp_end_mission_delay( int n )
{
//int delay;
//delay = atoi(CTEXT(n));
//mission_parse_set_end_time( delay );
mprintf(("Not ending mission -- end-mission sexpression no longer works!\n"));
}
// funciton to toggle the status bit for the AI code which tells the AI if it is a good time
// to rearm. The status being set means good time. Status not being set (unset), means bad time.
// designers must implement this.
void sexp_good_time_to_rearm( int n )
{
int i, time;
char *team_name;
team_name = CTEXT(n);
time = atoi(CTEXT(CDR(n))); // this is the time for how long a good rearm is active -- in seconds
for ( i = 0; i < Num_team_names; i++ ) {
if ( !stricmp(team_name, Team_names[i]) ) {
int team;
team = 1 << i;
ai_set_rearm_status( team, time );
}
}
}
// function which grants promotion to the player
void sexp_grant_promotion()
{
// short circuit multiplayer for now until we figure out what to do.
if ( Game_mode & GM_MULTIPLAYER )
return;
// set a bit to tell player should get promoted at the end of the mission. I suppose the other
// thing that we could do would be to set the players score to at least the amount of
// points for the next level, but this way is better I think.
if ( Game_mode & GM_CAMPAIGN_MODE ) {
Player->flags |= PLAYER_FLAGS_PROMOTED;
}
}
// function which gives the named medal to the players in the mission
void sexp_grant_medal( int n )
{
int i, j;
char *medal_name;
// don't give medals in normal gameplay when not in campaign mode
if ( (Game_mode & GM_NORMAL) && !(Game_mode & GM_CAMPAIGN_MODE) )
return;
Assert(Player->stats.m_medal_earned < 0); // Mission has problems. Tried to grant 2 medals in 1 mission.
medal_name = CTEXT(n);
for (i = 0; i < NUM_MEDALS; i++ ) {
if ( !stricmp(medal_name, Medals[i].name) )
break;
}
if ( i < NUM_MEDALS ) {
Player->stats.m_medal_earned = i;
#ifndef NO_NETWORK
if ( Game_mode & GM_MULTIPLAYER ) {
for ( j = 0; j < MAX_PLAYERS; j++ ) {
if ( MULTI_CONNECTED(Net_players[j]) ) {
Net_players[j].player->stats.m_medal_earned = i;
}
}
}
#endif
}
}
void sexp_tech_add_ship(int node)
{
int i;
char *name;
Assert(node >= 0);
// this function doesn't mean anything when not in campaign mode
if ( !(Game_mode & GM_CAMPAIGN_MODE) )
return;
while (node >= 0) {
name = CTEXT(node);
i = ship_info_lookup(name);
if (i >= 0)
Ship_info[i].flags |= SIF_IN_TECH_DATABASE;
else
Error(LOCATION, "Ship class \"%s\" invalid", name);
node = CDR(node);
}
}
void sexp_tech_add_weapon(int node)
{
int i;
char *name;
Assert(node >= 0);
// this function doesn't mean anything when not in campaign mode
if ( !(Game_mode & GM_CAMPAIGN_MODE) )
return;
while (node >= 0) {
name = CTEXT(node);
i = weapon_info_lookup(name);
if (i >= 0)
Weapon_info[i].wi_flags |= WIF_IN_TECH_DATABASE;
else
Error(LOCATION, "Ship class \"%s\" invalid", name);
node = CDR(node);
}
}
// function to set variables needed to grant a new ship/weapon to the player during the course
// of a mission
void sexp_allow_ship( int n )
{
int sindex;
char *name;
// this function doesn't mean anything when not in campaign mode
if ( !(Game_mode & GM_CAMPAIGN_MODE) )
return;
// get the name of the ship and lookup up the ship_info index for it
name = CTEXT(n);
sindex = ship_info_lookup( name );
if ( sindex == -1 )
return;
// now we have a valid index --
mission_campaign_save_persistent( CAMPAIGN_PERSISTENT_SHIP, sindex );
}
void sexp_allow_weapon( int n )
{
int sindex;
char *name;
// this function doesn't mean anything when not in campaign mode
if ( !(Game_mode & GM_CAMPAIGN_MODE) )
return;
// get the name of the weapon and lookup up the weapon_info index for it
name = CTEXT(n);
sindex = weapon_info_lookup( name );
if ( sindex == -1 )
return;
// now we have a valid index --
mission_campaign_save_persistent( CAMPAIGN_PERSISTENT_WEAPON, sindex );
}
// functions to deal with breaking/fixing the warp engines on ships/wings. should_break is true when
// we are breaking the warp drive (can be repaired). The parameter is 0 when is getting broken (i.e.
// can be fixed by repair). The repair parameter tells us whether we are clearing the destroyed or broken
// flag (1), or setting them (0).
void sexp_deal_with_warp( int n, int should_break, int nix )
{
int index, flag;
char *name;
for ( ; n != -1; n = CDR(n) ) {
name = CTEXT(n);
index = ship_name_lookup(name);
// check to see if ship destroyed or departed. In either case, do nothing.
if ( mission_log_get_time(LOG_SHIP_DEPART, name, NULL, NULL) || mission_log_get_time(LOG_SHIP_DESTROYED, name, NULL, NULL) )
continue;
// we can only operate on ships which are in the mission
if ( index != -1 ) {
// set the flag value accoring to whether we are destroying the warp or just breaking it
if ( should_break )
flag = SF_WARP_BROKEN;
else
flag = SF_WARP_NEVER;
if ( nix )
Ships[index].flags |= flag;
else
Ships[index].flags &= ~flag;
} else {
// maybe this ship has yet to arrive. Get a possible parse object and set the flag if found
p_object *pobjp;
pobjp = mission_parse_get_arrival_ship( name );
#ifndef NDEBUG
if ( pobjp == NULL ) {
Int3(); // warning, find allender -- should be impossible
continue;
}
#endif
if ( should_break )
flag = P_SF_WARP_BROKEN;
else
flag = P_SF_WARP_BROKEN;
if ( nix )
pobjp->flags |= flag;
else
pobjp->flags &= ~flag;
}
}
}
// function which is used to tell the AI when it is okay to fire certain secondary
// weapons at other ships.
void sexp_good_secondary_time( int n )
{
char *team_name, *weapon_name, *ship_name;
int num_weapons, weapon_index, team, i;
team_name = CTEXT(n);
num_weapons = atoi(CTEXT(CDR(n)));
weapon_name = CTEXT(CDR(CDR(n)));
ship_name = CTEXT(CDR(CDR(CDR(n))));
weapon_index = weapon_info_lookup(weapon_name);
if ( weapon_index == -1 ) {
nprintf(("Warning", "couldn't find weapon %s for good-secondary-time\n", weapon_name));
return;
}
// get the team type from the team_name
for ( i = 0; i < Num_team_names; i++ ) {
if ( !stricmp(Team_names[i], team_name) )
break;
}
if ( i == Num_team_names ) {
nprintf(("Warning", "couldn't find team %s for good-secondary-time\n", team_name ));
return;
}
team = (1<<i); // this is the magic formula to get to a team type.
// see if the ship has departed or has been destroyed. If so, then we don't need to set up the
// AI stuff
if ( mission_log_get_time(LOG_SHIP_DEPART, ship_name, NULL, NULL) || mission_log_get_time(LOG_SHIP_DESTROYED, ship_name, NULL, NULL) )
return;
ai_good_secondary_time( team, weapon_index, num_weapons, ship_name );
}
// function to deal with getting status of goals for previous missions (in the current campaign).
// the status parameter is used to tell this function if we are looking for a goal_satisfied, goal_failed,
// or goal incomplete event
int sexp_previous_goal_status( int n, int status )
{
char rval = 0, *mission_name;
char *goal_name;
int i, mission_num, default_value = 0, use_defaults = 1;
mission_name = CTEXT(n);
goal_name = CTEXT(CDR(n));
// check for possible next optional argument
n = CDR(CDR(n));
if ( n != -1 ) {
default_value = eval_sexp(n);
}
// try to find the given mission name in the current list of missions in the campaign.
if ( Game_mode & GM_CAMPAIGN_MODE ) {
i = mission_campaign_find_mission( mission_name );
if ( i == -1 ) {
// if mission not found, assume that goal was false (so previous-goal-false returns true)
nprintf(("General", "Couldn't find mission name %s in current campaign's list of missions.\nReturning %s for goal-status function.", mission_name, (status==GOAL_COMPLETE)?"false":"true"));
if ( status == GOAL_COMPLETE )
rval = SEXP_KNOWN_FALSE;
else
rval = SEXP_KNOWN_TRUE;
use_defaults = 0;
} else if (Campaign.missions[i].flags & CMISSION_FLAG_SKIPPED) {
use_defaults = 1;
} else {
// now try and find the goal this mission
mission_num = i;
for (i = 0; i < Campaign.missions[mission_num].num_goals; i++) {
if ( !stricmp(Campaign.missions[mission_num].goals[i].name, goal_name) )
break;
}
if ( i == Campaign.missions[mission_num].num_goals ) {
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");
if ( status == GOAL_COMPLETE )
rval = SEXP_KNOWN_FALSE;
else
rval = SEXP_KNOWN_TRUE;
} else {
// now return KNOWN_TRUE or KNOWN_FALSE based on the status field in the goal structure
if ( Campaign.missions[mission_num].goals[i].status == status )
rval = SEXP_KNOWN_TRUE;
else
rval = SEXP_KNOWN_FALSE;
}
use_defaults = 0;
}
}
if (use_defaults) {
// when not in campaign mode, always return KNOWN_TRUE when looking for goal complete, and KNOWN_FALSE
// otherwise
if ( n != -1 ) {
if ( default_value )
rval = SEXP_KNOWN_TRUE;
else
rval = SEXP_KNOWN_FALSE;
} else {
if ( status == GOAL_COMPLETE )
rval = SEXP_KNOWN_TRUE;
else
rval = SEXP_KNOWN_FALSE;
}
}
return rval;
}
// sexpression which gets the status of an event from a previous mission. Like the above function but
// dealing with events instead of goals. Again, the status parameter tells the code if we are looking
// for an event_true, event_false, or event_incomplete status
int sexp_previous_event_status( int n, int status )
{
char rval = 0, *mission_name;
char *name;
int i, mission_num, default_value = 0, use_defaults = 1;
mission_name = CTEXT(n);
name = CTEXT(CDR(n));
// check for possible optional parameter
n = CDR(CDR(n));
if ( n != -1 ){
default_value = eval_sexp(n);
}
if ( Game_mode & GM_CAMPAIGN_MODE ) {
// following function returns -1 when mission isn't found.
i = mission_campaign_find_mission( mission_name );
// if the mission name wasn't found -- make this return FALSE for the event status.
if ( i == -1 ) {
nprintf(("General", "Couldn't find mission name %s in current campaign's list of missions.\nReturning %s for event-status function.", mission_name, (status==EVENT_SATISFIED)?"false":"true"));
if ( status == EVENT_SATISFIED ) {
rval = SEXP_KNOWN_FALSE;
} else {
rval = SEXP_KNOWN_TRUE;
}
use_defaults = 0;
} else if (Campaign.missions[i].flags & CMISSION_FLAG_SKIPPED) {
use_defaults = 1;
} else {
// now try and find the goal this mission
mission_num = i;
for (i = 0; i < Campaign.missions[mission_num].num_events; i++) {
if ( !stricmp(Campaign.missions[mission_num].events[i].name, name) )
break;
}
if ( i == Campaign.missions[mission_num].num_events ) {
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");
if ( status == EVENT_SATISFIED )
rval = SEXP_KNOWN_FALSE;
else
rval = SEXP_KNOWN_TRUE;
} else {
// now return KNOWN_TRUE or KNOWN_FALSE based on the status field in the goal structure
if ( Campaign.missions[mission_num].events[i].status == status )
rval = SEXP_KNOWN_TRUE;
else
rval = SEXP_KNOWN_FALSE;
}
use_defaults = 0;
}
}
if (use_defaults) {
if ( n != -1 ) {
if ( default_value )
rval = SEXP_KNOWN_TRUE;
else
rval = SEXP_KNOWN_FALSE;
} else {
if ( status == EVENT_SATISFIED )
rval = SEXP_KNOWN_TRUE;
else
rval = SEXP_KNOWN_FALSE;
}
}
return rval;
}
// function to return the status of an event in the current mission. The passed parameter indicates
// if we are checking whether the event is true or the event is false.
int sexp_event_status( int n, int want_true )
{
char *name;
int i, result;
name = CTEXT(n);
for (i = 0; i < Num_mission_events; i++ ) {
// look for the event name, check it's status. If formula is gone, we know the state won't ever change.
if ( !stricmp(Mission_events[i].name, name) ) {
result = Mission_events[i].result;
if (Mission_events[i].formula < 0) {
if ( (want_true && result) || (!want_true && !result) )
return SEXP_KNOWN_TRUE;
else
return SEXP_KNOWN_FALSE;
} else {
if ( (want_true && result) || (!want_true && !result) )
return SEXP_TRUE;
else
return SEXP_FALSE;
}
}
}
return 0;
}
// function to return the status of an event N seconds after the event is true or false. Similar
// to above function but waits N seconds before returning true
int sexp_event_delay_status( int n, int want_true )
{
char *name;
int i, result;
fix delay;
name = CTEXT(n);
delay = i2f(num_eval(CDR(n)));
for (i = 0; i < Num_mission_events; i++ ) {
// look for the event name, check it's status. If formula is gone, we know the state won't ever change.
if ( !stricmp(Mission_events[i].name, name) ) {
if ( (fix) Mission_events[i].timestamp + delay >= Missiontime )
return SEXP_FALSE;
result = Mission_events[i].result;
if (Mission_events[i].formula < 0) {
if ( (want_true && result) || (!want_true && !result) )
return SEXP_KNOWN_TRUE;
else
return SEXP_KNOWN_FALSE;
} else {
if ( want_true && result ) //) || (!want_true && !result) )
return SEXP_TRUE;
else
return SEXP_FALSE;
}
}
}
return 0;
}
// function which returns true if the given event is still incomplete
int sexp_event_incomplete( int n )
{
char *name;
int i;
name = CTEXT(n);
for (i = 0; i < Num_mission_events; i++ ) {
if ( !stricmp(Mission_events[i].name, name ) ) {
// if the formula is still >= 0 (meaning it is still getting eval'ed), then
// the event is incomplete
if ( Mission_events[i].formula != -1 )
return SEXP_TRUE;
else
return SEXP_KNOWN_FALSE;
}
}
return 0;
}
// function to return the status of an goal N seconds after the goal is true or false. Similar
// to above function but operates on goals instead of events
int sexp_goal_delay_status( int n, int want_true )
{
char *name;
fix delay, time;
name = CTEXT(n);
delay = i2f(num_eval(CDR(n)));
if ( want_true ) {
// if we are looking for a goal true entry and we find a false, then return known false here
if ( mission_log_get_time(LOG_GOAL_FAILED, name, NULL, NULL) )
return SEXP_KNOWN_FALSE;
else if ( mission_log_get_time(LOG_GOAL_SATISFIED, name, NULL, &time) ) {
if ( (Missiontime - time) >= delay )
return SEXP_KNOWN_TRUE;
}
} else {
// if we are looking for a goal false entry and we find a true, then return known false here
if ( mission_log_get_time(LOG_GOAL_SATISFIED, name, NULL, NULL) )
return SEXP_KNOWN_FALSE;
else if ( mission_log_get_time(LOG_GOAL_FAILED, name, NULL, &time) ) {
if ( (Missiontime - time) >= delay )
return SEXP_KNOWN_TRUE;
}
}
return 0;
}
// function which returns true if the given goal is still incomplete
int sexp_goal_incomplete( int n )
{
char *name;
name = CTEXT(n);
if ( mission_log_get_time( LOG_GOAL_SATISFIED, name, NULL, NULL) || mission_log_get_time( LOG_GOAL_FAILED, name, NULL, NULL) )
return SEXP_KNOWN_FALSE;
else
return SEXP_TRUE;
}
// protects/unprotects a ship. The flag tells us whether or not the protect bit should be set (flag==1)
// or cleared (flag==0)
void sexp_protect_ships( int n, int flag )
{
char *ship_name;
int num;
while ( n != -1 ) {
ship_name = CTEXT(n);
// check to see if ship destroyed or departed. In either case, do nothing.
if ( mission_log_get_time(LOG_SHIP_DEPART, ship_name, NULL, NULL) || mission_log_get_time(LOG_SHIP_DESTROYED, ship_name, NULL, NULL) )
return;
// get the ship num. If we get a -1 for the number here, ship has yet to arrive. Store this ship
// in a list until created
num = ship_name_lookup(ship_name);
if ( num >= 0 ) {
if ( flag )
Objects[Ships[num].objnum].flags |= OF_PROTECTED;
else
Objects[Ships[num].objnum].flags &= ~OF_PROTECTED;
} else {
p_object *parse_obj;
parse_obj = mission_parse_get_arrival_ship( ship_name );
if ( parse_obj ) {
if ( flag )
parse_obj->flags |= P_OF_PROTECTED;
else
parse_obj->flags &= ~P_OF_PROTECTED;
break;
#ifndef NDEBUG
} else {
Int3(); // get allender -- could be a potential problem here
#endif
}
}
n = CDR(n);
}
}
// protects/unprotects a ship. The flag tells us whether or not the protect bit should be set (flag==1)
// or cleared (flag==0)
void sexp_beam_protect_ships( int n, int flag )
{
char *ship_name;
int num;
while ( n != -1 ) {
ship_name = CTEXT(n);
// check to see if ship destroyed or departed. In either case, do nothing.
if ( mission_log_get_time(LOG_SHIP_DEPART, ship_name, NULL, NULL) || mission_log_get_time(LOG_SHIP_DESTROYED, ship_name, NULL, NULL) )
return;
// get the ship num. If we get a -1 for the number here, ship has yet to arrive. Store this ship
// in a list until created
num = ship_name_lookup(ship_name);
if ( num >= 0 ) {
if ( flag )
Objects[Ships[num].objnum].flags |= OF_BEAM_PROTECTED;
else
Objects[Ships[num].objnum].flags &= ~OF_BEAM_PROTECTED;
} else {
p_object *parse_obj;
parse_obj = mission_parse_get_arrival_ship( ship_name );
if ( parse_obj ) {
if ( flag )
parse_obj->flags |= P_OF_BEAM_PROTECTED;
else
parse_obj->flags &= ~P_OF_BEAM_PROTECTED;
break;
#ifndef NDEBUG
} else {
Int3(); // get allender -- could be a potential problem here
#endif
}
}
n = CDR(n);
}
}
// sexpression to make ships "visible" and "invisible" to sensors. The visible parameter is true
// when making ships visible, false otherwise
void sexp_ships_visible( int n, int visible )
{
char *ship_name;
int num;
for ( ; n != -1; n = CDR(n) ) {
ship_name = CTEXT(n);
// check to see if ship destroyed or departed. In either case, do nothing.
if ( mission_log_get_time(LOG_SHIP_DEPART, ship_name, NULL, NULL) || mission_log_get_time(LOG_SHIP_DESTROYED, ship_name, NULL, NULL) )
continue;;
// get the ship num. If we get a -1 for the number here, ship has yet to arrive. Store this ship
// in a list until created
num = ship_name_lookup(ship_name);
if ( num != -1 ) {
if ( !visible ) {
Ships[num].flags |= SF_HIDDEN_FROM_SENSORS;
} else {
Ships[num].flags &= ~SF_HIDDEN_FROM_SENSORS;
if (Ships[num].flags & SF_ESCORT) {
// SEND add escort request
hud_add_ship_to_escort(Ships[num].objnum, 1);
}
}
} else {
p_object *parse_obj;
parse_obj = mission_parse_get_arrival_ship( ship_name );
if ( parse_obj ) {
if ( !visible )
parse_obj->flags |= P_SF_HIDDEN_FROM_SENSORS;
else
parse_obj->flags &= ~P_SF_HIDDEN_FROM_SENSORS;
break;
#ifndef NDEBUG
} else {
Int3(); // get allender -- could be a potential problem here
#endif
}
}
}
}
// sexpression to toggle invulnerability flag of ships.
void sexp_ships_invulnerable( int n, int invulnerable )
{
char *ship_name;
object *objp;
int num;
for ( ; n != -1; n = CDR(n) ) {
ship_name = CTEXT(n);
// check to see if ship destroyed or departed. In either case, do nothing.
if ( mission_log_get_time(LOG_SHIP_DEPART, ship_name, NULL, NULL) || mission_log_get_time(LOG_SHIP_DESTROYED, ship_name, NULL, NULL) )
continue;
// get the ship num. If we get a -1 for the number here, ship has yet to arrive. Store this ship
// in a list until created
num = ship_name_lookup(ship_name);
if ( num != -1 ) {
objp = &Objects[Ships[num].objnum];
if ( invulnerable )
objp->flags |= OF_INVULNERABLE;
else
objp->flags &= ~OF_INVULNERABLE;
} else {
p_object *parse_obj;
parse_obj = mission_parse_get_arrival_ship( ship_name );
if ( parse_obj ) {
if ( invulnerable )
parse_obj->flags |= P_SF_INVULNERABLE;
else
parse_obj->flags &= ~P_SF_INVULNERABLE;
break;
#ifndef NDEBUG
} else {
Int3(); // get allender -- could be a potential problem here
#endif
}
}
}
}
// sexpression to toggle KEEP ALIVE flag of ship object
void sexp_ships_guardian( int n, int guardian )
{
char *ship_name;
object *objp;
int num;
for ( ; n != -1; n = CDR(n) ) {
ship_name = CTEXT(n);
// check to see if ship destroyed or departed. In either case, do nothing.
if ( mission_log_get_time(LOG_SHIP_DEPART, ship_name, NULL, NULL) || mission_log_get_time(LOG_SHIP_DESTROYED, ship_name, NULL, NULL) )
continue;
// get the ship num. If we get a -1 for the number here, ship has yet to arrive. Store this ship
// in a list until created
num = ship_name_lookup(ship_name);
if ( num != -1 ) {
objp = &Objects[Ships[num].objnum];
if ( guardian )
objp->flags |= OF_GUARDIAN;
else
objp->flags &= ~OF_GUARDIAN;
} else {
p_object *parse_obj;
parse_obj = mission_parse_get_arrival_ship( ship_name );
if ( parse_obj ) {
if ( guardian )
parse_obj->flags |= P_SF_GUARDIAN;
else
parse_obj->flags &= ~P_SF_GUARDIAN;
break;
#ifndef NDEBUG
} else {
Int3(); // get allender -- could be a potential problem here
#endif
}
}
}
}
// make ship vanish without a trace (and what its docked to)
void ship_vanished(int);
void sexp_ship_vanish( int n )
{
char *ship_name;
object *objp, *docked_objp;
int num;
// if MULTIPLAYER bail
if (Game_mode & GM_MULTIPLAYER) {
return;
}
for ( ; n != -1; n = CDR(n) ) {
ship_name = CTEXT(n);
// check to see if ship destroyed or departed. In either case, do nothing.
if ( mission_log_get_time(LOG_SHIP_DEPART, ship_name, NULL, NULL) || mission_log_get_time(LOG_SHIP_DESTROYED, ship_name, NULL, NULL) )
continue;
// get the ship num. If we get a -1 for the number here, ship has yet to arrive. Store this ship
// in a list until created
num = ship_name_lookup(ship_name);
if ( num != -1 ) {
objp = &Objects[Ships[num].objnum];
// check if docked
docked_objp = ai_find_docked_object( objp );
// kill
objp->flags |= OF_SHOULD_BE_DEAD;
// make vanish
ship_vanished(num);
if (docked_objp) {
// kill
docked_objp->flags |= OF_SHOULD_BE_DEAD;
// vanish
ship_vanished(docked_objp->instance);
}
}
}
}
void sexp_ship_lights_on(int node) //-WMCoolmon
{
//Insert sexp here
}
void sexp_ship_lights_off(int node) //-WMCoolmon
{
//Insert sexp here
}
int sexp_key_pressed(int node)
{
int z, t;
Assert(node != -1);
z = translate_key_to_index(CTEXT(node));
if (z < 0){
return 0;
}
if (!Control_config[z].used){
return 0;
}
if (CDR(node) < 0){
return 1;
}
t = atoi(CTEXT(CDR(node)));
return timestamp_has_time_elapsed(Control_config[z].used, t * 1000);
}
void sexp_key_reset(int node)
{
int z;
Assert(node != -1);
z = translate_key_to_index(CTEXT(node));
if (z >= 0)
Control_config[z].used = 0;
}
int sexp_targeted(int node)
{
int z;
ship_subsys *ptr;
z = ship_query_state(CTEXT(node));
if (z == 1){
return SEXP_KNOWN_FALSE; // ship isn't around, nor will it ever be
} else if (z == -1) {
return SEXP_CANT_EVAL;
}
z = ship_name_lookup(CTEXT(node), 1);
if ((z < 0) || !Player_ai || (Ships[z].objnum != Player_ai->target_objnum)){
return 0;
}
if (CDR(node) >= 0) {
z = atoi(CTEXT(CDR(node))) * 1000;
if (!timestamp_has_time_elapsed(Players_target_timestamp, z)){
return 0;
}
if (CDR(CDR(node)) >= 0) {
ptr = Player_ai->targeted_subsys;
if (!ptr || stricmp(ptr->system_info->subobj_name, CTEXT(CDR(CDR(node))))){
return 0;
}
}
}
return 1;
}
int sexp_speed(int node)
{
if (Training_context & TRAINING_CONTEXT_SPEED) {
if (Training_context_speed_set){
if (timestamp_has_time_elapsed(Training_context_speed_timestamp, atoi(CTEXT(node)) * 1000)){
return SEXP_KNOWN_TRUE;
}
}
}
return 0;
}
int sexp_secondaries_depleted(int node)
{
int sindex, num_banks, num_depleted_banks;
ship *shipp;
// get ship
sindex = ship_name_lookup(CTEXT(node));
if (sindex < 0) {
return 0;
}
shipp = &Ships[sindex];
if (shipp->objnum < 0) {
return 0;
}
// get num secondary banks
num_banks = shipp->weapons.num_secondary_banks;
num_depleted_banks = 0;
// get number of depleted banks
for (int idx=0; idx<num_banks; idx++) {
if (shipp->weapons.secondary_bank_ammo[idx] == 0) {
num_depleted_banks++;
}
}
// are they all depleted?
return (num_depleted_banks == num_banks);
}
int sexp_facing(int node)
{
int obj, sh;
float a1, a2;
vector v1, v2;
if (ship_query_state(CTEXT(node)) < 0){
return SEXP_KNOWN_FALSE;
}
sh = ship_name_lookup(CTEXT(node));
if ((sh < 0) || !Player_obj){
return 0;
}
obj = Ships[sh].objnum;
v1 = Player_obj->orient.vec.fvec;
vm_vec_normalize(&v1);
vm_vec_sub(&v2, &Objects[obj].pos, &Player_obj->pos);
vm_vec_normalize(&v2);
a1 = vm_vec_dotprod(&v1, &v2);
a2 = (float) cos(ANG_TO_RAD(atof(CTEXT(CDR(node)))));
if (a1 >= a2){
return 1;
}
return 0;
}
// is ship facing first waypoint in waypoint path
int sexp_facing2(int node)
{
int i;
float a1, a2;
vector v1, v2;
// bail if Player_obj is not good
if (!Player_obj) {
return SEXP_CANT_EVAL;
}
// get player fvec
v1 = Player_obj->orient.vec.fvec;
vm_vec_normalize(&v1);
// get waypoint name
char *waypoint_name = CTEXT(node);
// get position of first waypoint
int wp_index = -1;
for (i=0; i<Num_waypoint_lists; i++) {
if (!stricmp(waypoint_name, Waypoint_lists[i].name)) {
wp_index = i;
break;
}
}
if (wp_index == -1) {
return SEXP_CANT_EVAL;
}
// Waypoint_lists[wp_index].waypoints[0]
vm_vec_sub(&v2, &Waypoint_lists[wp_index].waypoints[0], &Player_obj->pos);
vm_vec_normalize(&v2);
a1 = vm_vec_dotprod(&v1, &v2);
a2 = (float) cos(ANG_TO_RAD(atof(CTEXT(CDR(node)))));
if (a1 >= a2){
return 1;
}
return 0;
}
int sexp_order(int node)
{
return 0;
}
int sexp_waypoint_missed()
{
if (Training_context & TRAINING_CONTEXT_FLY_PATH) {
if (Training_context_at_waypoint > Training_context_goal_waypoint){
return 1;
}
}
return 0;
}
int sexp_waypoint_twice()
{
if (Training_context & TRAINING_CONTEXT_FLY_PATH) {
if (Training_context_at_waypoint < Training_context_goal_waypoint - 1){
return 1;
}
}
return 0;
}
int sexp_path_flown()
{
if (Training_context & TRAINING_CONTEXT_FLY_PATH) {
if (Training_context_goal_waypoint == Waypoint_lists[Training_context_path].count){
return 1;
}
}
return 0;
}
void sexp_send_training_message(int node)
{
int t = -1, delay = 0;
if(physics_paused){
return;
}
Assert(node >= 0);
Assert(Event_index >= 0);
if ((CDR(node) >= 0) && (CDR(CDR(node)) >= 0)) {
delay = atoi(CTEXT(CDR(CDR(node)))) * 1000;
t = CDR(CDR(CDR(node)));
if (t >= 0){
t = atoi(CTEXT(t));
}
}
if ((Mission_events[Event_index].repeat_count > 1) || (CDR(node) < 0)){
message_training_que(CTEXT(node), timestamp(delay), t);
} else {
message_training_que(CTEXT(CDR(node)), timestamp(delay), t);
}
// if (Training_msg_method)
// gameseq_post_event(GS_EVENT_TRAINING_PAUSE);
}
int sexp_shield_recharge_pct(int node)
{
int sindex;
// get the firing ship
sindex = ship_name_lookup(CTEXT(node));
if(sindex < 0){
return 0;
}
if(Ships[sindex].objnum < 0){
return 0;
}
// shield recharge pct
return (int)(100.0f * Energy_levels[Ships[sindex].shield_recharge_index]);
}
int sexp_engine_recharge_pct(int node)
{
int sindex;
// get the firing ship
sindex = ship_name_lookup(CTEXT(node));
if(sindex < 0){
return 0;
}
if(Ships[sindex].objnum < 0){
return 0;
}
// shield recharge pct
return (int)(100.0f * Energy_levels[Ships[sindex].engine_recharge_index]);
}
int sexp_weapon_recharge_pct(int node)
{
int sindex;
// get the firing ship
sindex = ship_name_lookup(CTEXT(node));
if(sindex < 0){
return 0;
}
if(Ships[sindex].objnum < 0){
return 0;
}
// shield recharge pct
return (int)(100.0f * Energy_levels[Ships[sindex].weapon_recharge_index]);
}
int sexp_shield_quad_low(int node)
{
int sindex, idx;
float max_quad, check;
ship_info *sip;
object *objp;
// get the ship
sindex = ship_name_lookup(CTEXT(node));
if(sindex < 0){
return 0;
}
if((Ships[sindex].objnum < 0) || (Ships[sindex].objnum >= MAX_OBJECTS)){
return 0;
}
if((Ships[sindex].ship_info_index < 0) || (Ships[sindex].ship_info_index >= Num_ship_types)){
return 0;
}
objp = &Objects[Ships[sindex].objnum];
sip = &Ship_info[Ships[sindex].ship_info_index];
if(!(sip->flags & SIF_SMALL_SHIP)){
return 0;
}
max_quad = sip->shields / (float)MAX_SHIELD_SECTIONS;
// shield pct
check = (float)atoi(CTEXT(CDR(node)));
// check his quadrants
for(idx=0; idx<MAX_SHIELD_SECTIONS; idx++){
if( ((objp->shields[idx] / max_quad) * 100.0f) <= check ){
return 1;
}
}
// all good
return 0;
}
int sexp_secondary_ammo_pct(int node)
{
ship *shipp;
int sindex;
int check, idx;
int ret_sum[4];
int ret = 0;
// get the ship
sindex = ship_name_lookup(CTEXT(node));
if(sindex < 0){
return 0;
}
if((Ships[sindex].objnum < 0) || (Ships[sindex].objnum >= MAX_OBJECTS)){
return 0;
}
shipp = &Ships[sindex];
// bank to check
check = atoi(CTEXT(CDR(node)));
// bogus check? (3 == cumulative sum of all banks)
if((check != 3) && (check > shipp->weapons.num_secondary_banks)){
return 0;
}
// cumulative sum?
if(check == 3){
for(idx=0; idx<shipp->weapons.num_secondary_banks; idx++){
ret_sum[idx] = (int)(((float)shipp->weapons.secondary_bank_ammo[idx] / (float)shipp->weapons.secondary_bank_start_ammo[idx]) * 100.0f);
}
// add it up
ret = 0;
for(idx=0; idx<shipp->weapons.num_secondary_banks; idx++){
ret += ret_sum[idx];
}
ret = (int)((float)ret / (float)shipp->weapons.num_secondary_banks);
} else {
ret = (int)(((float)shipp->weapons.secondary_bank_ammo[check] / (float)shipp->weapons.secondary_bank_start_ammo[check]) * 100.0f);
}
// return
return ret;
}
void sexp_beam_fire(int node)
{
int sindex;
beam_fire_info fire_info;
int idx;
// zero stuff out
memset(&fire_info, 0, sizeof(beam_fire_info));
fire_info.accuracy = 0.000001f; // this will guarantee a hit
// get the firing ship
sindex = ship_name_lookup(CTEXT(node));
if(sindex < 0){
return;
}
if(Ships[sindex].objnum < 0){
return;
}
fire_info.shooter = &Objects[Ships[sindex].objnum];
// get the subsystem
fire_info.turret = ship_get_subsys(&Ships[sindex], CTEXT(CDR(node)));
if(fire_info.turret == NULL){
return;
}
// get the target
sindex = ship_name_lookup(CTEXT(CDR(CDR(node))));
if(sindex < 0){
return;
}
if(Ships[sindex].objnum < 0){
return;
}
fire_info.target = &Objects[Ships[sindex].objnum];
// see if the optional subsystem can be found
fire_info.target_subsys = NULL;
fire_info.target_subsys = ship_get_subsys(&Ships[sindex], CTEXT(CDR(CDR(CDR(node)))));
// if it has no primary weapons
if(fire_info.turret->weapons.num_primary_banks <= 0){
Int3();
return;
}
// if the turret is destroyed
if(fire_info.turret->current_hits <= 0.0f){
return;
}
// hmm, this could be wacky. Let's just simply select the first beam weapon in the turret
fire_info.beam_info_index = -1;
for(idx=0; idx<fire_info.turret->weapons.num_primary_banks; idx++){
// store the weapon info index
if(Weapon_info[fire_info.turret->weapons.primary_bank_weapons[idx]].wi_flags & WIF_BEAM){
fire_info.beam_info_index = fire_info.turret->weapons.primary_bank_weapons[idx];
}
}
// fire the beam
if(fire_info.beam_info_index != -1){
beam_fire(&fire_info);
} else {
// it would appear the turret doesn't have any beam weapons, dumbass
Int3();
}
}
void sexp_beam_free(int node)
{
int sindex;
ship_subsys *turret = NULL;
// get the firing ship
sindex = ship_name_lookup(CTEXT(node));
if(sindex < 0){
return;
}
if(Ships[sindex].objnum < 0){
return;
}
node = CDR(node);
while(node != -1){
// get the subsystem
turret = ship_get_subsys(&Ships[sindex], CTEXT(node));
if(turret == NULL){
return;
}
// flag it as beam free :)
turret->weapons.flags |= SW_FLAG_BEAM_FREE;
turret->turret_next_fire_stamp = timestamp((int) frand_range(50.0f, 4000.0f));
// next
node = CDR(node);
}
}
void sexp_beam_free_all(int node)
{
ship_subsys *subsys;
int sindex;
// get the firing ship
sindex = ship_name_lookup(CTEXT(node));
if(sindex < 0){
return;
}
if(Ships[sindex].objnum < 0){
return;
}
// free all beam weapons
subsys = GET_FIRST(&Ships[sindex].subsys_list);
while(subsys != END_OF_LIST(&Ships[sindex].subsys_list)){
// just mark all turrets as beam free
if(subsys->system_info->type == SUBSYSTEM_TURRET){
subsys->weapons.flags |= SW_FLAG_BEAM_FREE;
subsys->turret_next_fire_stamp = timestamp((int) frand_range(50.0f, 4000.0f));
}
// next item
subsys = GET_NEXT(subsys);
}
}
void sexp_beam_lock(int node)
{
int sindex;
ship_subsys *turret = NULL;
// get the firing ship
sindex = ship_name_lookup(CTEXT(node));
if(sindex < 0){
return;
}
if(Ships[sindex].objnum < 0){
return;
}
node = CDR(node);
while(node != -1){
// get the subsystem
turret = ship_get_subsys(&Ships[sindex], CTEXT(node));
if(turret == NULL){
return;
}
// flag it as not beam free
turret->weapons.flags &= ~(SW_FLAG_BEAM_FREE);
// next
node = CDR(node);
}
}
void sexp_beam_lock_all(int node)
{
ship_subsys *subsys;
int sindex;
// get the firing ship
sindex = ship_name_lookup(CTEXT(node));
if(sindex < 0){
return;
}
if(Ships[sindex].objnum < 0){
return;
}
// free all beam weapons
subsys = GET_FIRST(&Ships[sindex].subsys_list);
while(subsys != END_OF_LIST(&Ships[sindex].subsys_list)){
// just mark all turrets as not beam free
if(subsys->system_info->type == SUBSYSTEM_TURRET){
subsys->weapons.flags &= ~(SW_FLAG_BEAM_FREE);
}
// next item
subsys = GET_NEXT(subsys);
}
}
void sexp_turret_free(int node)
{
int sindex;
ship_subsys *turret = NULL;
// get the firing ship
sindex = ship_name_lookup(CTEXT(node));
if(sindex < 0){
return;
}
if(Ships[sindex].objnum < 0){
return;
}
node = CDR(node);
while(node != -1){
// get the subsystem
turret = ship_get_subsys(&Ships[sindex], CTEXT(node));
if(turret == NULL){
return;
}
// flag turret as no longer locked :)
turret->weapons.flags &= (~SW_FLAG_TURRET_LOCK);
turret->turret_next_fire_stamp = timestamp((int) frand_range(50.0f, 4000.0f));
// next
node = CDR(node);
}
}
void sexp_turret_free_all(int node)
{
ship_subsys *subsys;
int sindex;
// get the firing ship
sindex = ship_name_lookup(CTEXT(node));
if(sindex < 0){
return;
}
if(Ships[sindex].objnum < 0){
return;
}
// free all turrets
subsys = GET_FIRST(&Ships[sindex].subsys_list);
while(subsys != END_OF_LIST(&Ships[sindex].subsys_list)){
// just mark all turrets as free
if(subsys->system_info->type == SUBSYSTEM_TURRET){
subsys->weapons.flags &= (~SW_FLAG_TURRET_LOCK);
subsys->turret_next_fire_stamp = timestamp((int) frand_range(50.0f, 4000.0f));
}
// next item
subsys = GET_NEXT(subsys);
}
}
void sexp_turret_lock(int node)
{
int sindex;
ship_subsys *turret = NULL;
// get the firing ship
sindex = ship_name_lookup(CTEXT(node));
if(sindex < 0){
return;
}
if(Ships[sindex].objnum < 0){
return;
}
node = CDR(node);
while(node != -1){
// get the subsystem
turret = ship_get_subsys(&Ships[sindex], CTEXT(node));
if(turret == NULL){
return;
}
// flag turret as locked
turret->weapons.flags |= SW_FLAG_TURRET_LOCK;
// next
node = CDR(node);
}
}
void sexp_turret_lock_all(int node)
{
ship_subsys *subsys;
int sindex;
// get the firing ship
sindex = ship_name_lookup(CTEXT(node));
if(sindex < 0){
return;
}
if(Ships[sindex].objnum < 0){
return;
}
// lcck all turrets
subsys = GET_FIRST(&Ships[sindex].subsys_list);
while(subsys != END_OF_LIST(&Ships[sindex].subsys_list)){
// just mark all turrets as locked
if(subsys->system_info->type == SUBSYSTEM_TURRET){
subsys->weapons.flags |= SW_FLAG_TURRET_LOCK;
}
// next item
subsys = GET_NEXT(subsys);
}
}
void sexp_turret_tagged_only_all(int node)
{
ship_subsys *subsys;
int sindex;
// get the firing ship
sindex = ship_name_lookup(CTEXT(node));
if(sindex < 0){
return;
}
if(Ships[sindex].objnum < 0){
return;
}
// mark all turrets to only target tagged ships
subsys = GET_FIRST(&Ships[sindex].subsys_list);
while(subsys != END_OF_LIST(&Ships[sindex].subsys_list)){
// just mark all turrets as locked
if(subsys->system_info->type == SUBSYSTEM_TURRET){
subsys->weapons.flags |= SW_FLAG_TAGGED_ONLY;
}
// next item
subsys = GET_NEXT(subsys);
}
}
void sexp_turret_tagged_clear_all(int node)
{
ship_subsys *subsys;
int sindex;
// get the firing ship
sindex = ship_name_lookup(CTEXT(node));
if(sindex < 0){
return;
}
if(Ships[sindex].objnum < 0){
return;
}
// mark all turrets so not restricted to only tagged ships
subsys = GET_FIRST(&Ships[sindex].subsys_list);
while(subsys != END_OF_LIST(&Ships[sindex].subsys_list)){
// just mark all turrets as locked
if(subsys->system_info->type == SUBSYSTEM_TURRET){
subsys->weapons.flags &= (~SW_FLAG_TAGGED_ONLY);
}
// next item
subsys = GET_NEXT(subsys);
}
}
void sexp_add_remove_escort(int node)
{
int sindex;
int flag;
char *whee;
// get the firing ship
sindex = ship_name_lookup(CTEXT(node));
if(sindex < 0){
return;
}
if(Ships[sindex].objnum < 0){
return;
}
// determine whether to add or remove it
whee = CTEXT(CDR(node));
flag = atoi(CTEXT(CDR(node)));
// add/remove
if(flag){
hud_add_ship_to_escort(Ships[sindex].objnum, 1);
} else {
hud_remove_ship_from_escort(Ships[sindex].objnum);
}
}
void sexp_awacs_set_radius(int node)
{
int sindex;
ship_subsys *awacs;
// get the firing ship
sindex = ship_name_lookup(CTEXT(node));
if(sindex < 0){
return;
}
if(Ships[sindex].objnum < 0){
return;
}
// get the awacs subsystem
awacs = ship_get_subsys(&Ships[sindex], CTEXT(CDR(node)));
if(awacs == NULL){
return;
}
// make sure this _is_ an awacs subsystem
Assert(awacs->system_info->flags & MSS_FLAG_AWACS);
if(awacs->system_info->flags & MSS_FLAG_AWACS){
return;
}
// set the new awacs radius
awacs->awacs_radius = (float)atoi(CTEXT(CDR(CDR(node))));
}
int sexp_is_tagged(int node)
{
int sindex;
// get the firing ship
sindex = ship_name_lookup(CTEXT(node));
if(sindex < 0){
return 0;
}
if(Ships[sindex].objnum < 0){
return 0;
}
if(Ships[sindex].tag_left > 0.0f){
return 1;
}
// not tagged
return 0;
}
int sexp_num_kills(int node)
{
int sindex, np_index;
player *p = NULL;
// get the ship we're interested in
sindex = ship_name_lookup(CTEXT(node));
if(sindex < 0){
return 0;
}
if(Ships[sindex].objnum < 0){
return 0;
}
#ifndef NO_NETWORK
// in multiplayer, search through all players
if(Game_mode & GM_MULTIPLAYER){
// try and find the player
np_index = multi_find_player_by_object(&Objects[Ships[sindex].objnum]);
if((np_index >= 0) && (np_index < MAX_PLAYERS)){
p = Net_players[np_index].player;
}
}
// if we're in single player, we're only concerned with ourself
else
#endif
{
// me
if(Player_obj == &Objects[Ships[sindex].objnum]){
p = Player;
}
}
// now, if we have a valid player, return his kills
if(p != NULL){
return p->stats.m_kill_count_ok;
}
// bad
return 0;
}
int sexp_num_type_kills(int node)
{
int sindex, np_index, st_index;
int idx, total;
player *p = NULL;
// get the ship we're interested in
sindex = ship_name_lookup(CTEXT(node));
if(sindex < 0){
return 0;
}
if(Ships[sindex].objnum < 0){
return 0;
}
#ifndef NO_NETWORK
// in multiplayer, search through all players
if(Game_mode & GM_MULTIPLAYER){
// try and find the player
np_index = multi_find_player_by_object(&Objects[Ships[sindex].objnum]);
if((np_index >= 0) && (np_index < MAX_PLAYERS)){
p = Net_players[np_index].player;
}
}
// if we're in single player, we're only concerned with ourself
else
#endif
{
// me
if(Player_obj == &Objects[Ships[sindex].objnum]){
p = Player;
}
}
// bad
if(p == NULL){
return 0;
}
// lookup ship type name
st_index = ship_type_name_lookup(CTEXT(CDR(node)));
if(st_index < 0){
return 0;
}
// look stuff up
total = 0;
for(idx=0; idx<Num_ship_types; idx++){
if((p->stats.m_okKills[idx] > 0) && (Ship_info[idx].flags & Ship_type_flags[st_index])){
total += p->stats.m_okKills[idx];
}
}
// total
return total;
}
int sexp_num_class_kills(int node)
{
int sindex, np_index, si_index;
player *p = NULL;
// get the ship we're interested in
sindex = ship_name_lookup(CTEXT(node));
if(sindex < 0){
return 0;
}
if(Ships[sindex].objnum < 0){
return 0;
}
#ifndef NO_NETWORK
// in multiplayer, search through all players
if(Game_mode & GM_MULTIPLAYER){
// try and find the player
np_index = multi_find_player_by_object(&Objects[Ships[sindex].objnum]);
if((np_index >= 0) && (np_index < MAX_PLAYERS)){
p = Net_players[np_index].player;
}
}
// if we're in single player, we're only concerned with ourself
else
#endif
{
// me
if(Player_obj == &Objects[Ships[sindex].objnum]){
p = Player;
}
}
// bad
if(p == NULL){
return 0;
}
// get the ship type we're looking for
si_index = ship_info_lookup(CTEXT(CDR(node)));
if((si_index < 0) || (si_index > Num_ship_types)){
return 0;
}
// return the count
return p->stats.m_okKills[si_index];
}
void sexp_subsys_set_random(int node)
{
int sindex, low, high, n, idx, rand, exclusion_list[MAX_MODEL_SUBSYSTEMS];
ship_subsys *subsys;
ship *shipp;
// get ship
sindex = ship_name_lookup(CTEXT(node));
if(sindex < 0){
return;
}
if(Ships[sindex].objnum < 0){
return;
}
shipp = &Ships[sindex];
// get low
low = num_eval(CDR(node));
if (low < 0) {
low = 0;
}
// get high
high = num_eval(CDR(CDR(node)));
if (high > 100) {
high = 100;
}
if (low > high) {
Int3();
return;
}
n = CDR(CDR(CDR(node)));
// init exclusion list
memset(exclusion_list, 0, sizeof(int) * Ship_info[shipp->ship_info_index].n_subsystems);
// get exclusion list
while( n != -1) {
int exclude_index = ship_get_subsys_index(shipp, CTEXT(n), 0);
if (exclude_index >= 0) {
exclusion_list[exclude_index] = 1;
}
n = CDR(n);
}
// apply to all others
for (idx=0; idx<Ship_info[shipp->ship_info_index].n_subsystems; idx++) {
if ( exclusion_list[idx] == 0 ) {
// get non excluded subsystem
subsys = ship_get_indexed_subsys(shipp, idx, NULL);
// randomize its hit points
rand = rand_internal(low, high);
subsys->current_hits = 0.01f * rand * subsys->system_info->max_hits;
}
}
}
void sexp_supernova_start(int node)
{
supernova_start(atoi(CTEXT(node)));
}
int sexp_is_secondary_selected(int node)
{
int sindex;
int bank;
ship *shipp;
// lookup ship
sindex = ship_name_lookup(CTEXT(node));
if(sindex < 0){
return 0;
}
if(Ships[sindex].objnum < 0){
return 0;
}
shipp = &Ships[sindex];
// bogus value?
bank = atoi(CTEXT(CDR(node)));
if(bank >= shipp->weapons.num_secondary_banks){
return 0;
}
// is this the bank currently selected
if(bank == shipp->weapons.current_secondary_bank){
return 1;
}
// nope
return 0;
}
int sexp_is_primary_selected(int node)
{
int sindex;
int bank;
ship *shipp;
// lookup ship
sindex = ship_name_lookup(CTEXT(node));
if(sindex < 0){
return 0;
}
if(Ships[sindex].objnum < 0){
return 0;
}
shipp = &Ships[sindex];
// bogus value?
bank = atoi(CTEXT(CDR(node)));
if(bank >= shipp->weapons.num_primary_banks){
return 0;
}
// is this the bank currently selected
if(bank == shipp->weapons.current_primary_bank){
return 1;
}
// nope
return 0;
}
#define RIGHT_QUAD 0
#define FRONT_QUAD 1
#define LEFT_QUAD 3
#define REAR_QUAD 2
// Return SEXP_TRUE if quadrant quadnum is near max.
int shield_quad_near_max(int quadnum)
{
float remaining = 0.0f;
for (int i=0; i<MAX_SHIELD_SECTIONS; i++) {
if (i == quadnum){
continue;
}
remaining += Player_obj->shields[i];
}
if ((remaining < 2.0f) || (Player_obj->shields[quadnum] > Ship_info[Player_ship->ship_info_index].shields/MAX_SHIELD_SECTIONS - 5.0f)) {
return SEXP_TRUE;
} else {
return SEXP_FALSE;
}
}
// Return truth value for special SEXP.
// Used in training#5, perhaps in other missions.
int process_special_sexps(int index)
{
switch (index) {
case 0: // Ship "Freighter 1" is aspect locked by player.
if (Player_ai->target_objnum != -1) {
if (!(stricmp(Ships[Objects[Player_ai->target_objnum].instance].ship_name, "Freighter 1"))) {
if (Player_ai->current_target_is_locked)
return SEXP_TRUE;
}
}
return SEXP_FALSE;
break;
case 1: // Fired Interceptors
object *objp;
for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
if (objp->type == OBJ_WEAPON) {
if (!stricmp(Weapon_info[Weapons[objp->instance].weapon_info_index].name, "Interceptor#weak")) {
int target = Weapons[objp->instance].target_num;
if (target != -1) {
if (Objects[target].type == OBJ_SHIP) {
if (!(stricmp(Ships[Objects[target].instance].ship_name, "Freighter 1")))
return SEXP_TRUE;
}
}
}
}
}
return SEXP_FALSE;
case 2: // Ship "Freighter 1", subsystem "Weapons" is aspect locked by player.
if (Player_ai->target_objnum != -1) {
if (!(stricmp(Ships[Objects[Player_ai->target_objnum].instance].ship_name, "Freighter 1"))) {
if (!(stricmp(Player_ai->targeted_subsys->system_info->name, "Weapons"))) {
if (Player_ai->current_target_is_locked){
return SEXP_TRUE;
}
}
}
}
return SEXP_FALSE;
break;
case 3: // Player ship suffering shield damage on front.
apply_damage_to_shield(Player_obj, FRONT_QUAD, 10.0f);
hud_shield_quadrant_hit(Player_obj, FRONT_QUAD);
return SEXP_TRUE;
break;
case 4: // Player ship suffering much damage.
nprintf(("AI", "Frame %i\n", Framecount));
apply_damage_to_shield(Player_obj, FRONT_QUAD, 10.0f);
hud_shield_quadrant_hit(Player_obj, FRONT_QUAD);
if (Player_obj->shields[FRONT_QUAD] < 2.0f)
return SEXP_TRUE;
else
return SEXP_FALSE;
break;
case 5: // Player's shield is quick repaired
nprintf(("AI", "Frame %i, recharged to %7.3f\n", Framecount, Player_obj->shields[FRONT_QUAD]));
apply_damage_to_shield(Player_obj, FRONT_QUAD, -flFrametime*200.0f);
if (Player_obj->shields[FRONT_QUAD] > Ship_info[Player_ship->ship_info_index].shields/4.0f)
Player_obj->shields[FRONT_QUAD] = Ship_info[Player_ship->ship_info_index].shields/4.0f;
//hud_shield_quadrant_hit(Player_obj, FRONT_QUAD);
if (Player_obj->shields[FRONT_QUAD] > Player_obj->shields[(FRONT_QUAD+1)%4] - 2.0f)
return SEXP_TRUE;
else
return SEXP_FALSE;
break;
case 6: // 3 of player's shield quadrants are reduced to 0.
Player_obj->shields[1] = 1.0f;
Player_obj->shields[2] = 1.0f;
Player_obj->shields[3] = 1.0f;
//apply_damage_to_shield(Player_obj, FRONT_QUAD, 1.0f);
hud_shield_quadrant_hit(Player_obj, FRONT_QUAD);
return SEXP_TRUE;
case 7: // Make sure front quadrant has been maximized, or close to it.
if (shield_quad_near_max(FRONT_QUAD)) return SEXP_TRUE; else return SEXP_FALSE;
break;
case 8: // Make sure rear quadrant has been maximized, or close to it.
if (shield_quad_near_max(REAR_QUAD)) return SEXP_TRUE; else return SEXP_FALSE;
break;
case 9: // Zero left and right quadrants in preparation for maximizing rear quadrant.
Player_obj->shields[LEFT_QUAD] = 0.0f;
Player_obj->shields[RIGHT_QUAD] = 0.0f;
hud_shield_quadrant_hit(Player_obj, LEFT_QUAD);
return SEXP_TRUE;
break;
case 10: // Return true if player is low on Interceptors.
if (Player_ship->weapons.secondary_bank_ammo[0] + Player_ship->weapons.secondary_bank_ammo[1] < 8)
return SEXP_TRUE;
else
return SEXP_FALSE;
break;
case 11: // Return true if player has plenty of Interceptors.
if (Player_ship->weapons.secondary_bank_ammo[0] + Player_ship->weapons.secondary_bank_ammo[1] >= 8)
return SEXP_TRUE;
else
return SEXP_FALSE;
break;
case 12: // Return true if player is low on Interceptors.
if (Player_ship->weapons.secondary_bank_ammo[0] + Player_ship->weapons.secondary_bank_ammo[1] < 4)
return SEXP_TRUE;
else
return SEXP_FALSE;
break;
case 13: // Zero front shield quadrant. Added for Jim Boone on August 26, 1999 by MK.
Player_obj->shields[FRONT_QUAD] = 0.0f;
hud_shield_quadrant_hit(Player_obj, FRONT_QUAD);
return SEXP_TRUE;
break;
case 100: // Return true if player is out of countermeasures.
if (Player_ship->cmeasure_count <= 0)
return SEXP_TRUE;
else
return SEXP_FALSE;
default:
Int3(); // Unsupported node type.
}
return SEXP_FALSE;
}
// custom sexp operator for handling misc training stuff
int sexp_special_training_check(int node)
{
int num, rtn;
num = atoi(CTEXT(node));
if (num == SPECIAL_CHECK_TRAINING_FAILURE)
return Training_failure ? SEXP_TRUE : SEXP_FALSE;
// To MK: do whatever you want with this number here.
rtn = process_special_sexps(atoi(CTEXT(node)));
return rtn;
}
// sexpression to flash a hud gauge. gauge name is text valud of node
void sexp_flash_hud_gauge( int node )
{
char *name;
int i;
name = CTEXT(node);
for (i = 0; i < NUM_HUD_GAUGES; i++ ) {
if ( !stricmp(HUD_gauge_text[i], name) ) {
hud_gauge_start_flash(i); // call HUD function to flash gauge
break;
}
}
}
void sexp_set_training_context_fly_path(int node)
{
int i;
for (i=0; i<Num_waypoint_lists; i++)
if (!stricmp(CTEXT(node), Waypoint_lists[i].name))
break;
if (i < Num_waypoint_lists) {
Training_context |= TRAINING_CONTEXT_FLY_PATH;
Training_context_path = i;
Training_context_distance = (float) atof(CTEXT(CDR(node)));
Training_context_goal_waypoint = 0;
Training_context_at_waypoint = -1;
}
}
void sexp_set_training_context_speed(int node)
{
Training_context |= TRAINING_CONTEXT_SPEED;
Training_context_speed_min = atoi(CTEXT(node));
Training_context_speed_max = atoi(CTEXT(CDR(node)));
Training_context_speed_set = 0;
}
// high-level sexpression evaluator
int eval_sexp(int cur_node)
{
int node, type, sexp_val = UNINITIALIZED;
if (cur_node == -1) // empty list, i.e. sexp: ( )
return FALSE;
Assert(cur_node >= 0); // we have special sexp nodes <= -1!!! MWA
// which should be intercepted before we get here. HOFFOSS
type = SEXP_NODE_TYPE(cur_node);
Assert( (type == SEXP_LIST) || (type == SEXP_ATOM) );
// trap known true and known false sexpressions. We don't trap on SEXP_NAN sexpressions since
// they may yet evaluate to true or false.
if (Sexp_nodes[cur_node].value == SEXP_KNOWN_TRUE)
return 1;
else if (Sexp_nodes[cur_node].value == SEXP_KNOWN_FALSE)
return 0;
if (Sexp_nodes[cur_node].first != -1) {
node = CAR(cur_node);
sexp_val = eval_sexp(node);
Sexp_nodes[cur_node].value = Sexp_nodes[node].value; // higher level node gets node value
return sexp_val;
} else {
int op_num;
node = CDR(cur_node); // makes reading the next bit of code a little easier.
op_num = find_operator(CTEXT(cur_node));
switch ( op_num ) {
// arithmetic operators will always return just their value
case OP_PLUS:
sexp_val = add_sexps( node );
break;
case OP_MINUS:
sexp_val = sub_sexps( node );
break;
case OP_MUL:
sexp_val = mul_sexps( node );
break;
case OP_MOD:
sexp_val = mod_sexps( node );
break;
case OP_DIV:
sexp_val = div_sexps( node );
break;
case OP_RAND:
sexp_val = rand_sexp( node );
break;
// boolean operators can have one of the special sexp values (known true, known false, unknown)
case OP_TRUE:
sexp_val = SEXP_KNOWN_TRUE;
break;
case OP_FALSE:
sexp_val = SEXP_KNOWN_FALSE;
break;
case OP_OR:
sexp_val = sexp_or( node );
break;
case OP_AND:
sexp_val = sexp_and( node );
break;
case OP_AND_IN_SEQUENCE:
sexp_val = sexp_and_in_sequence( node );
break;
case OP_GREATER_THAN:
sexp_val = sexp_gt( node );
break;
case OP_LESS_THAN:
sexp_val = sexp_lt( node );
break;
case OP_EQUALS:
sexp_val = sexp_equal( node );
break;
case OP_IS_IFF:
sexp_val = sexp_is_iff( node );
break;
case OP_NOT:
sexp_val = sexp_not( node );
break;
case OP_PREVIOUS_GOAL_TRUE:
sexp_val = sexp_previous_goal_status( node, GOAL_COMPLETE );
break;
case OP_PREVIOUS_GOAL_FALSE:
sexp_val = sexp_previous_goal_status( node, GOAL_FAILED );
break;
case OP_PREVIOUS_GOAL_INCOMPLETE:
sexp_val = sexp_previous_goal_status( node, GOAL_INCOMPLETE );
break;
case OP_PREVIOUS_EVENT_TRUE:
sexp_val = sexp_previous_event_status( node, EVENT_SATISFIED );
break;
case OP_PREVIOUS_EVENT_FALSE:
sexp_val = sexp_previous_event_status( node, EVENT_FAILED );
break;
case OP_PREVIOUS_EVENT_INCOMPLETE:
sexp_val = sexp_previous_event_status( node, EVENT_INCOMPLETE );
break;
case OP_EVENT_TRUE:
case OP_EVENT_FALSE:
sexp_val = sexp_event_status( node, (op_num == OP_EVENT_TRUE?1:0) );
if ((sexp_val != SEXP_TRUE) && (sexp_val != SEXP_KNOWN_TRUE))
Sexp_useful_number = 0; // indicate sexp isn't current yet
break;
case OP_EVENT_TRUE_DELAY:
case OP_EVENT_FALSE_DELAY:
sexp_val = sexp_event_delay_status( node, (op_num == OP_EVENT_TRUE_DELAY?1:0) );
if ((sexp_val != SEXP_TRUE) && (sexp_val != SEXP_KNOWN_TRUE))
Sexp_useful_number = 0; // indicate sexp isn't current yet
break;
case OP_GOAL_TRUE_DELAY:
case OP_GOAL_FALSE_DELAY:
sexp_val = sexp_goal_delay_status( node, (op_num == OP_GOAL_TRUE_DELAY?1:0) );
break;
case OP_EVENT_INCOMPLETE:
sexp_val = sexp_event_incomplete( node );
if ((sexp_val != SEXP_TRUE) && (sexp_val != SEXP_KNOWN_TRUE))
Sexp_useful_number = 0; // indicate sexp isn't current yet
break;
case OP_GOAL_INCOMPLETE:
sexp_val = sexp_goal_incomplete( node );
break;
// destroy type sexpressions
case OP_IS_DESTROYED:
sexp_val = sexp_is_destroyed( node, NULL );
break;
case OP_IS_SUBSYSTEM_DESTROYED:
sexp_val = sexp_is_subsystem_destroyed( node );
break;
case OP_HAS_DOCKED:
sexp_val = sexp_has_docked( node );
break;
case OP_HAS_ARRIVED:
sexp_val = sexp_has_arrived( node, NULL );
break;
case OP_HAS_DEPARTED:
sexp_val = sexp_has_departed( node, NULL );
break;
case OP_HAS_UNDOCKED:
sexp_val = sexp_has_undocked( node );
break;
case OP_IS_DISABLED:
sexp_val = sexp_is_disabled( node, NULL );
break;
case OP_IS_DISARMED:
sexp_val = sexp_is_disarmed( node, NULL );
break;
case OP_WAYPOINTS_DONE:
sexp_val = sexp_are_waypoints_done( node );
break;
// objective operators that use a delay
case OP_IS_DESTROYED_DELAY:
sexp_val = sexp_is_destroyed_delay( node );
break;
case OP_IS_SUBSYSTEM_DESTROYED_DELAY:
sexp_val = sexp_is_subsystem_destroyed_delay( node );
break;
case OP_HAS_DOCKED_DELAY:
sexp_val = sexp_has_docked_delay( node );
break;
case OP_HAS_ARRIVED_DELAY:
sexp_val = sexp_has_arrived_delay( node );
break;
case OP_HAS_DEPARTED_DELAY:
sexp_val = sexp_has_departed_delay( node );
break;
case OP_HAS_UNDOCKED_DELAY:
sexp_val = sexp_has_undocked_delay( node );
break;
case OP_IS_DISABLED_DELAY:
sexp_val = sexp_is_disabled_delay( node );
break;
case OP_IS_DISARMED_DELAY:
sexp_val = sexp_is_disarmed_delay( node );
break;
case OP_WAYPOINTS_DONE_DELAY:
sexp_val = sexp_are_waypoints_done_delay( node );
break;
case OP_SHIP_TYPE_DESTROYED:
sexp_val = sexp_ship_type_destroyed( node );
break;
// time based sexpressions
case OP_HAS_TIME_ELAPSED:
sexp_val = sexp_has_time_elapsed( node );
break;
case OP_MODIFY_VARIABLE:
sexp_modify_variable( node );
sexp_val = 1; // 1 means only do once.
break;
case OP_TIME_SHIP_DESTROYED:
sexp_val = sexp_time_destroyed( node );
break;
case OP_TIME_WING_DESTROYED:
sexp_val = sexp_time_wing_destroyed( node );
break;
case OP_TIME_SHIP_ARRIVED:
sexp_val = sexp_time_ship_arrived( node );
break;
case OP_TIME_WING_ARRIVED:
sexp_val = sexp_time_wing_arrived( node );
break;
case OP_TIME_SHIP_DEPARTED:
sexp_val = sexp_time_ship_departed( node );
break;
case OP_TIME_WING_DEPARTED:
sexp_val = sexp_time_wing_departed( node );
break;
case OP_MISSION_TIME:
sexp_val = sexp_mission_time();
break;
case OP_TIME_DOCKED:
sexp_val = sexp_time_docked( node );
break;
case OP_TIME_UNDOCKED:
sexp_val = sexp_time_undocked( node );
break;
// info based sexpressions (like shields, hits
case OP_SHIELDS_LEFT:
sexp_val = sexp_shields_left( node );
break;
case OP_HITS_LEFT:
sexp_val = sexp_hits_left( node );
break;
case OP_SPECIAL_WARP_DISTANCE:
sexp_val = sexp_special_warp_dist( node );
break;
case OP_HITS_LEFT_SUBSYSTEM:
sexp_val = sexp_hits_left_subsystem( node );
break;
case OP_DISTANCE:
sexp_val = sexp_distance( node );
break;
case OP_IS_SHIP_VISIBLE:
sexp_val = sexp_is_ship_visible( node );
break;
case OP_TEAM_SCORE:
sexp_val = sexp_team_score( node );
break;
case OP_LAST_ORDER_TIME:
sexp_val = sexp_last_order_time( node );
break;
case OP_NUM_PLAYERS:
sexp_val = sexp_num_players();
break;
case OP_SKILL_LEVEL_AT_LEAST:
sexp_val = sexp_skill_level_at_least( node );
break;
case OP_IS_CARGO_KNOWN:
case OP_CARGO_KNOWN_DELAY:
sexp_val = sexp_is_cargo_known( node, (op_num==OP_IS_CARGO_KNOWN)?0:1 );
break;
case OP_HAS_BEEN_TAGGED_DELAY:
sexp_val = sexp_has_been_tagged_delay(node);
break;
case OP_CAP_SUBSYS_CARGO_KNOWN_DELAY:
sexp_val = sexp_cap_subsys_cargo_known_delay(node);
break;
case OP_WAS_PROMOTION_GRANTED:
sexp_val = sexp_was_promotion_granted(node);
break;
case OP_WAS_MEDAL_GRANTED:
sexp_val = sexp_was_medal_granted(node);
break;
case OP_PERCENT_SHIPS_DEPARTED:
case OP_PERCENT_SHIPS_DESTROYED:
sexp_val = sexp_percent_ships_depart_destroy(node, op_num);
break;
case OP_DEPART_NODE_DELAY:
sexp_val = sexp_depart_node_delay( node );
break;
case OP_DESTROYED_DEPARTED_DELAY:
sexp_val = sexp_destroyed_departed_delay( node );
break;
// conditional sexpressions
case OP_WHEN:
sexp_val = eval_when( node );
break;
case OP_COND:
sexp_val = eval_cond( node );
break;
// sexpressions with side effects
case OP_CHANGE_IFF:
sexp_change_iff( node );
sexp_val = 1;
break;
case OP_ADD_SHIP_GOAL:
sexp_add_ship_goal( node );
sexp_val = 1;
break;
case OP_ADD_WING_GOAL:
sexp_add_wing_goal( node );
sexp_val = 1;
break;
case OP_ADD_GOAL:
sexp_add_goal( node );
sexp_val = 1;
break;
case OP_CLEAR_SHIP_GOALS:
sexp_clear_ship_goals( node );
sexp_val = 1;
break;
case OP_CLEAR_WING_GOALS:
sexp_clear_wing_goals( node );
sexp_val = 1;
break;
case OP_CLEAR_GOALS:
sexp_clear_goals( node );
sexp_val = 1;
break;
case OP_PROTECT_SHIP:
case OP_UNPROTECT_SHIP:
sexp_protect_ships( node, (op_num==OP_PROTECT_SHIP?1:0) );
sexp_val = 1;
break;
case OP_BEAM_PROTECT_SHIP:
case OP_BEAM_UNPROTECT_SHIP:
sexp_beam_protect_ships( node, (op_num==OP_BEAM_PROTECT_SHIP?1:0) );
sexp_val = 1;
break;
case OP_SHIP_INVISIBLE:
case OP_SHIP_VISIBLE:
sexp_ships_visible( node, (op_num==OP_SHIP_VISIBLE?1:0) );
sexp_val = 1;
break;
case OP_SHIP_VULNERABLE:
case OP_SHIP_INVULNERABLE:
sexp_ships_invulnerable( node, (op_num==OP_SHIP_INVULNERABLE?1:0) );
sexp_val = 1;
break;
case OP_SHIP_GUARDIAN:
case OP_SHIP_NO_GUARDIAN:
sexp_ships_guardian( node, (op_num==OP_SHIP_GUARDIAN?1:0) );
sexp_val = 1;
break;
case OP_SHIP_VANISH:
sexp_ship_vanish( node );
sexp_val = 1;
break;
case OP_SHIP_LIGHTS_ON: //-WMCoolmon
sexp_val = 1;
sexp_ship_lights_on(node);
case OP_SHIP_LIGHTS_OFF: //-WMCoolmon
sexp_val = 1;
sexp_ship_lights_off(node);
case OP_SEND_MESSAGE:
sexp_send_message( node );
sexp_val = 1;
break;
case OP_SEND_MESSAGE_LIST:
sexp_send_message_list( node );
sexp_val = 1;
break;
case OP_SEND_RANDOM_MESSAGE:
sexp_send_random_message( node );
sexp_val = 1;
break;
case OP_SELF_DESTRUCT:
sexp_self_destruct( node );
sexp_val = 1;
break;
case OP_NEXT_MISSION:
sexp_next_mission( node );
sexp_val = 1;
break;
case OP_END_OF_CAMPAIGN:
sexp_end_of_campaign( node );
sexp_val = 1;
break;
case OP_END_CAMPAIGN:
sexp_end_campaign( node );
sexp_val = 1;
break;
case OP_SABOTAGE_SUBSYSTEM:
sexp_sabotage_subsystem( node );
sexp_val = 1;
break;
case OP_REPAIR_SUBSYSTEM:
sexp_repair_subsystem( node );
sexp_val = 1;
break;
case OP_SET_SUBSYSTEM_STRNGTH:
sexp_set_subsystem_strength( node );
sexp_val = 1;
break;
case OP_INVALIDATE_GOAL:
case OP_VALIDATE_GOAL:
sexp_change_goal_validity( node, (op_num==OP_INVALIDATE_GOAL?0:1) );
sexp_val = 1;
break;
case OP_TRANSFER_CARGO:
sexp_transfer_cargo( node );
sexp_val = 1;
break;
case OP_EXCHANGE_CARGO:
sexp_exchange_cargo( node );
sexp_val = 1;
break;
case OP_JETTISON_CARGO:
sexp_jettison_cargo( node );
sexp_val = 1;
break;
case OP_CARGO_NO_DEPLETE:
sexp_cargo_no_deplete( node );
sexp_val = 1;
break;
case OP_SET_SPECIAL_WARPOUT_NAME:
sexp_special_warpout_name( node );
sexp_val = 1;
break;
case OP_END_MISSION_DELAY:
sexp_end_mission_delay( node );
sexp_val = 1;
break;
// sexpressions for setting flag for good/bad time for someone to reasm
case OP_GOOD_REARM_TIME:
sexp_good_time_to_rearm( node );
sexp_val = 1;
break;
case OP_GRANT_PROMOTION:
sexp_grant_promotion();
sexp_val = 1;
break;
case OP_GRANT_MEDAL:
sexp_grant_medal( node );
sexp_val = 1;
break;
case OP_WARP_BROKEN:
case OP_WARP_NOT_BROKEN:
sexp_deal_with_warp( node, 0, op_num==OP_WARP_BROKEN?1:0 );
sexp_val = 1;
break;
case OP_WARP_NEVER:
case OP_WARP_ALLOWED:
sexp_deal_with_warp( node, 1, op_num==OP_WARP_NEVER?1:0 );
sexp_val = 1;
break;
case OP_GOOD_SECONDARY_TIME:
sexp_good_secondary_time( node );
sexp_val = 1;
break;
// sexpressions to allow shpis/weapons during the course of a mission
case OP_ALLOW_SHIP:
sexp_allow_ship( node );
sexp_val = 1;
break;
case OP_ALLOW_WEAPON:
sexp_allow_weapon( node );
sexp_val = 1;
break;
case OP_TECH_ADD_SHIP:
sexp_tech_add_ship(node);
sexp_val = 1;
break;
case OP_TECH_ADD_WEAPON:
sexp_tech_add_weapon(node);
sexp_val = 1;
break;
// in the case of a red_alert mission, simply call the red alert function to close
// the current campaign's mission and move forward to the next mission
case OP_RED_ALERT:
red_alert_start_mission();
sexp_val = 1;
break;
// training operators
case OP_KEY_PRESSED:
sexp_val = sexp_key_pressed(node);
break;
case OP_SPECIAL_CHECK:
sexp_val = sexp_special_training_check(node);
break;
case OP_KEY_RESET:
sexp_key_reset(node);
sexp_val = SEXP_KNOWN_TRUE; // only do it first time in repeating events.
break;
case OP_TARGETED:
sexp_val = sexp_targeted(node);
break;
case OP_SPEED:
sexp_val = sexp_speed(node);
break;
case OP_SECONDARIES_DEPLETED:
sexp_val = sexp_secondaries_depleted(node);
break;
case OP_FACING:
sexp_val = sexp_facing(node);
break;
case OP_FACING2:
sexp_val = sexp_facing2(node);
break;
case OP_ORDER:
sexp_val = sexp_order(node);
break;
case OP_WAYPOINT_MISSED:
sexp_val = sexp_waypoint_missed();
break;
case OP_WAYPOINT_TWICE:
sexp_val = sexp_waypoint_twice();
break;
case OP_PATH_FLOWN:
sexp_val = sexp_path_flown();
break;
case OP_TRAINING_MSG:
sexp_send_training_message(node);
sexp_val = 1;
break;
case OP_FLASH_HUD_GAUGE:
sexp_flash_hud_gauge(node);
sexp_val = 1;
break;
case OP_SET_TRAINING_CONTEXT_FLY_PATH:
sexp_set_training_context_fly_path(node);
sexp_val = 1;
break;
case OP_SET_TRAINING_CONTEXT_SPEED:
sexp_set_training_context_speed(node);
sexp_val = 1;
break;
// debugging operators
case OP_INT3:
Int3();
sexp_val = 0;
break;
case 0: // zero represents a non-operator
return atoi(CTEXT(cur_node));
case OP_NOP:
sexp_val = 1;
break;
case OP_BEAM_FIRE:
sexp_beam_fire(node);
sexp_val = 1;
break;
case OP_IS_TAGGED:
sexp_val = sexp_is_tagged(node);
break;
case OP_NUM_KILLS:
sexp_val = sexp_num_kills(node);
break;
case OP_NUM_TYPE_KILLS:
sexp_val = sexp_num_type_kills(node);
break;
case OP_NUM_CLASS_KILLS:
sexp_val = sexp_num_class_kills(node);
break;
case OP_BEAM_FREE:
sexp_val = 1;
sexp_beam_free(node);
break;
case OP_BEAM_FREE_ALL:
sexp_val = 1;
sexp_beam_free_all(node);
break;
case OP_BEAM_LOCK:
sexp_val = 1;
sexp_beam_lock(node);
break;
case OP_BEAM_LOCK_ALL:
sexp_val = 1;
sexp_beam_lock_all(node);
break;
case OP_TURRET_FREE:
sexp_val = 1;
sexp_turret_free(node);
break;
case OP_TURRET_FREE_ALL:
sexp_val = 1;
sexp_turret_free_all(node);
break;
case OP_TURRET_LOCK:
sexp_val = 1;
sexp_turret_lock(node);
break;
case OP_TURRET_LOCK_ALL:
sexp_val = 1;
sexp_turret_lock_all(node);
break;
case OP_ADD_REMOVE_ESCORT:
sexp_val = 1;
sexp_add_remove_escort(node);
break;
case OP_AWACS_SET_RADIUS:
sexp_val = 1;
sexp_awacs_set_radius(node);
break;
case OP_CAP_WAYPOINT_SPEED:
sexp_val = 1;
sexp_cap_waypont_speed(node);
break;
case OP_TURRET_TAGGED_ONLY_ALL:
sexp_val = 1;
sexp_turret_tagged_only_all(node);
break;
case OP_TURRET_TAGGED_CLEAR_ALL:
sexp_val = 1;
sexp_turret_tagged_clear_all(node);
break;
case OP_SUBSYS_SET_RANDOM:
sexp_val = 1;
sexp_subsys_set_random(node);
break;
case OP_SUPERNOVA_START:
sexp_val = 1;
sexp_supernova_start(node);
break;
case OP_SHIELD_RECHARGE_PCT:
sexp_val = sexp_shield_recharge_pct(node);
break;
case OP_ENGINE_RECHARGE_PCT:
sexp_val = sexp_engine_recharge_pct(node);
break;
case OP_WEAPON_RECHARGE_PCT:
sexp_val = sexp_weapon_recharge_pct(node);
break;
case OP_SHIELD_QUAD_LOW:
sexp_val = sexp_shield_quad_low(node);
break;
case OP_SECONDARY_AMMO_PCT:
sexp_val = sexp_secondary_ammo_pct(node);
break;
case OP_IS_SECONDARY_SELECTED:
sexp_val = sexp_is_secondary_selected(node);
break;
case OP_IS_PRIMARY_SELECTED:
sexp_val = sexp_is_primary_selected(node);
break;
default:
Error(LOCATION, "Looking for SEXP operator, found '%s'.\n", CTEXT(cur_node));
break;
}
Assert(sexp_val != UNINITIALIZED);
// if we haven't returned, check the sexp value of the sexpression evaluation. A special
// value of known true or known false means that we should set the sexp.value field for
// short circuit eval (and return that special value as well).
if (sexp_val == SEXP_KNOWN_TRUE) {
Sexp_nodes[cur_node].value = SEXP_KNOWN_TRUE;
return 1;
}
if (sexp_val == SEXP_KNOWN_FALSE) {
Sexp_nodes[cur_node].value = SEXP_KNOWN_FALSE;
return 0;
}
if ( sexp_val == SEXP_NAN ) {
Sexp_nodes[cur_node].value = SEXP_NAN; // not a number values are false I would suspect
return 0;
}
if ( sexp_val == SEXP_NAN_FOREVER ) {
Sexp_nodes[cur_node].value = SEXP_NAN_FOREVER;
return sexp_val;
}
if ( sexp_val == SEXP_CANT_EVAL ) {
Sexp_nodes[cur_node].value = SEXP_CANT_EVAL;
Sexp_useful_number = 0; // indicate sexp isn't current yet
return 0;
}
if ( Sexp_nodes[cur_node].value == SEXP_NAN ) { // if we had a nan, but now don't, reset the value
Sexp_nodes[cur_node].value = SEXP_UNKNOWN;
return sexp_val;
}
if ( sexp_val ){
Sexp_nodes[cur_node].value = SEXP_TRUE;
} else {
Sexp_nodes[cur_node].value = SEXP_FALSE;
}
return sexp_val;
}
}
// Still a debug-level system.
// get_sexp_main reads and builds the internal representation for a
// symbolic expression.
// On entry:
// Mp points at first character in expression.
// The symbolic expression is built in Sexp_nodes beginning at node 0.
int get_sexp_main()
{
int start_node, op;
char token[TOKEN_LENGTH];
char *savep, ch;
ignore_white_space();
savep = Mp;
if (!strncmp(Mp, "( )", 3))
savep++;
Assert(*Mp == '(');
Mp++;
start_node = get_sexp(token);
// only need to check syntax if we have a operator
if ( /*Sexp_nodes[start_node].subtype != SEXP_ATOM_OPERATOR ||*/ Fred_running || (start_node == -1))
return start_node;
ch = *Mp;
*Mp = '\0';
op = identify_operator(CTEXT(start_node));
if (op == -1)
Error (LOCATION, "Can't find operator %s in operator list\n.", CTEXT(start_node) );
*Mp = ch;
return start_node;
}
void test_sexps()
{
Mp = Mission_text;
while (*Mp != '#') {
get_sexp_main();
diag_printf("\n----------------\n");
ignore_white_space();
}
exit(0);
}
// returns the data type returned by an operator
int query_operator_return_type(int op)
{
if (op < FIRST_OP)
{
Assert(op >= 0 && op < Num_operators);
op = Operators[op].value;
}
switch (op)
{
case OP_TRUE:
case OP_FALSE:
case OP_AND:
case OP_AND_IN_SEQUENCE:
case OP_OR:
case OP_NOT:
case OP_EQUALS:
case OP_GREATER_THAN:
case OP_LESS_THAN:
case OP_IS_DESTROYED:
case OP_IS_SUBSYSTEM_DESTROYED:
case OP_IS_DISABLED:
case OP_IS_DISARMED:
case OP_HAS_DOCKED:
case OP_HAS_UNDOCKED:
case OP_HAS_ARRIVED:
case OP_HAS_DEPARTED:
case OP_IS_DESTROYED_DELAY:
case OP_IS_SUBSYSTEM_DESTROYED_DELAY:
case OP_IS_DISABLED_DELAY:
case OP_IS_DISARMED_DELAY:
case OP_HAS_DOCKED_DELAY:
case OP_HAS_UNDOCKED_DELAY:
case OP_HAS_ARRIVED_DELAY:
case OP_HAS_DEPARTED_DELAY:
case OP_IS_IFF:
case OP_HAS_TIME_ELAPSED:
case OP_GOAL_INCOMPLETE:
case OP_GOAL_TRUE_DELAY:
case OP_GOAL_FALSE_DELAY:
case OP_EVENT_INCOMPLETE:
case OP_EVENT_TRUE_DELAY:
case OP_EVENT_FALSE_DELAY:
case OP_PREVIOUS_EVENT_TRUE:
case OP_PREVIOUS_EVENT_FALSE:
case OP_PREVIOUS_EVENT_INCOMPLETE:
case OP_PREVIOUS_GOAL_TRUE:
case OP_PREVIOUS_GOAL_FALSE:
case OP_PREVIOUS_GOAL_INCOMPLETE:
case OP_WAYPOINTS_DONE:
case OP_WAYPOINTS_DONE_DELAY:
case OP_SHIP_TYPE_DESTROYED:
case OP_LAST_ORDER_TIME:
case OP_KEY_PRESSED:
case OP_TARGETED:
case OP_SPEED:
case OP_FACING:
case OP_FACING2:
case OP_ORDER:
case OP_WAYPOINT_MISSED:
case OP_WAYPOINT_TWICE:
case OP_PATH_FLOWN:
case OP_EVENT_TRUE:
case OP_EVENT_FALSE:
case OP_SKILL_LEVEL_AT_LEAST:
case OP_IS_CARGO_KNOWN:
case OP_HAS_BEEN_TAGGED_DELAY:
case OP_CAP_SUBSYS_CARGO_KNOWN_DELAY:
case OP_CARGO_KNOWN_DELAY:
case OP_WAS_PROMOTION_GRANTED:
case OP_WAS_MEDAL_GRANTED:
case OP_PERCENT_SHIPS_DEPARTED:
case OP_PERCENT_SHIPS_DESTROYED:
case OP_DEPART_NODE_DELAY:
case OP_DESTROYED_DEPARTED_DELAY:
case OP_SPECIAL_CHECK:
case OP_IS_TAGGED:
case OP_SECONDARIES_DEPLETED:
case OP_SHIELD_QUAD_LOW:
case OP_IS_SECONDARY_SELECTED:
case OP_IS_PRIMARY_SELECTED:
return OPR_BOOL;
case OP_PLUS:
case OP_MINUS:
case OP_MOD:
case OP_MUL:
case OP_DIV:
case OP_RAND:
case OP_TIME_SHIP_DESTROYED:
case OP_TIME_SHIP_ARRIVED:
case OP_TIME_SHIP_DEPARTED:
case OP_TIME_WING_DESTROYED:
case OP_TIME_WING_ARRIVED:
case OP_TIME_WING_DEPARTED:
case OP_MISSION_TIME:
case OP_TIME_DOCKED:
case OP_TIME_UNDOCKED:
case OP_SHIELDS_LEFT:
case OP_HITS_LEFT:
case OP_HITS_LEFT_SUBSYSTEM:
case OP_DISTANCE:
case OP_NUM_PLAYERS:
case OP_NUM_KILLS:
case OP_NUM_TYPE_KILLS:
case OP_NUM_CLASS_KILLS:
case OP_SHIELD_RECHARGE_PCT:
case OP_ENGINE_RECHARGE_PCT:
case OP_WEAPON_RECHARGE_PCT:
case OP_SECONDARY_AMMO_PCT:
case OP_SPECIAL_WARP_DISTANCE:
case OP_IS_SHIP_VISIBLE:
case OP_TEAM_SCORE:
return OPR_POSITIVE;
case OP_COND:
case OP_WHEN:
case OP_CHANGE_IFF:
case OP_CLEAR_SHIP_GOALS:
case OP_CLEAR_WING_GOALS:
case OP_CLEAR_GOALS:
case OP_ADD_SHIP_GOAL:
case OP_ADD_WING_GOAL:
case OP_ADD_GOAL:
case OP_PROTECT_SHIP:
case OP_UNPROTECT_SHIP:
case OP_BEAM_PROTECT_SHIP:
case OP_BEAM_UNPROTECT_SHIP:
case OP_INT3:
case OP_NOP:
case OP_GOALS_ID:
case OP_SEND_MESSAGE:
case OP_SELF_DESTRUCT:
case OP_NEXT_MISSION:
case OP_END_CAMPAIGN:
case OP_END_OF_CAMPAIGN:
case OP_SABOTAGE_SUBSYSTEM:
case OP_REPAIR_SUBSYSTEM:
case OP_INVALIDATE_GOAL:
case OP_VALIDATE_GOAL:
case OP_SEND_RANDOM_MESSAGE:
case OP_TRANSFER_CARGO:
case OP_EXCHANGE_CARGO:
case OP_JETTISON_CARGO:
case OP_CARGO_NO_DEPLETE:
case OP_KEY_RESET:
case OP_TRAINING_MSG:
case OP_SET_TRAINING_CONTEXT_FLY_PATH:
case OP_SET_TRAINING_CONTEXT_SPEED:
case OP_END_MISSION_DELAY:
case OP_SET_SUBSYSTEM_STRNGTH:
case OP_GOOD_REARM_TIME:
case OP_GRANT_PROMOTION:
case OP_GRANT_MEDAL:
case OP_ALLOW_SHIP:
case OP_ALLOW_WEAPON:
case OP_TECH_ADD_SHIP:
case OP_TECH_ADD_WEAPON:
case OP_WARP_BROKEN:
case OP_WARP_NOT_BROKEN:
case OP_WARP_NEVER:
case OP_WARP_ALLOWED:
case OP_FLASH_HUD_GAUGE:
case OP_GOOD_SECONDARY_TIME:
case OP_SHIP_VISIBLE:
case OP_SHIP_INVISIBLE:
case OP_SHIP_VULNERABLE:
case OP_SHIP_INVULNERABLE:
case OP_SHIP_GUARDIAN:
case OP_SHIP_VANISH:
case OP_SHIP_LIGHTS_ON:
case OP_SHIP_LIGHTS_OFF:
case OP_SHIP_NO_GUARDIAN:
case OP_RED_ALERT:
case OP_MODIFY_VARIABLE:
case OP_BEAM_FIRE:
case OP_BEAM_FREE:
case OP_BEAM_FREE_ALL:
case OP_BEAM_LOCK:
case OP_BEAM_LOCK_ALL:
case OP_TURRET_FREE:
case OP_TURRET_FREE_ALL:
case OP_TURRET_LOCK:
case OP_TURRET_LOCK_ALL:
case OP_ADD_REMOVE_ESCORT:
case OP_AWACS_SET_RADIUS:
case OP_SEND_MESSAGE_LIST:
case OP_CAP_WAYPOINT_SPEED:
case OP_TURRET_TAGGED_ONLY_ALL:
case OP_TURRET_TAGGED_CLEAR_ALL:
case OP_SUBSYS_SET_RANDOM:
case OP_SUPERNOVA_START:
case OP_SET_SPECIAL_WARPOUT_NAME:
return OPR_NULL;
case OP_AI_CHASE:
case OP_AI_DOCK:
case OP_AI_UNDOCK:
case OP_AI_WARP: // this particular operator is obsolete
case OP_AI_WARP_OUT:
case OP_AI_WAYPOINTS:
case OP_AI_WAYPOINTS_ONCE:
case OP_AI_DESTROY_SUBSYS:
case OP_AI_CHASE_WING:
case OP_AI_DISABLE_SHIP:
case OP_AI_DISARM_SHIP:
case OP_AI_GUARD:
case OP_AI_GUARD_WING:
case OP_AI_CHASE_ANY:
case OP_AI_EVADE_SHIP:
case OP_AI_STAY_NEAR_SHIP:
case OP_AI_KEEP_SAFE_DISTANCE:
case OP_AI_IGNORE:
case OP_AI_STAY_STILL:
case OP_AI_PLAY_DEAD:
return OPR_AI_GOAL;
default:
Int3();
}
return 0;
}
// returns the data type of a specified argument to an operator. Argnum is 0 indexed.
int query_operator_argument_type(int op, int argnum)
{
int index = op;
if (op < FIRST_OP)
{
Assert(index >= 0 && index < Num_operators);
op = Operators[index].value;
} else {
for (index=0; index<Num_operators; index++)
if (Operators[index].value == op)
break;
Assert(index < Num_operators);
}
if (argnum >= Operators[index].max)
return OPF_NONE;
switch (op) {
case OP_TRUE:
case OP_FALSE:
case OP_MISSION_TIME:
case OP_INT3:
case OP_NOP:
case OP_WAYPOINT_MISSED:
case OP_WAYPOINT_TWICE:
case OP_PATH_FLOWN:
case OP_GRANT_PROMOTION:
case OP_WAS_PROMOTION_GRANTED:
case OP_RED_ALERT:
return OPF_NONE;
case OP_AND:
case OP_AND_IN_SEQUENCE:
case OP_OR:
case OP_NOT:
return OPF_BOOL;
case OP_PLUS:
case OP_MINUS:
case OP_MOD:
case OP_MUL:
case OP_DIV:
case OP_RAND:
case OP_EQUALS:
case OP_GREATER_THAN:
case OP_LESS_THAN:
case OP_HAS_TIME_ELAPSED:
case OP_SPEED:
case OP_SET_TRAINING_CONTEXT_SPEED:
case OP_END_MISSION_DELAY:
case OP_SPECIAL_CHECK:
case OP_AI_WARP_OUT:
case OP_TEAM_SCORE:
return OPF_POSITIVE;
case OP_AI_WARP: // this operator is obsolete
case OP_SET_TRAINING_CONTEXT_FLY_PATH:
if ( !argnum )
return OPF_WAYPOINT_PATH;
else
return OPF_NUMBER;
case OP_AI_WAYPOINTS:
case OP_AI_WAYPOINTS_ONCE:
if ( argnum == 0 )
return OPF_WAYPOINT_PATH;
else
return OPF_POSITIVE;
case OP_IS_DISABLED:
case OP_IS_DISARMED:
case OP_TIME_SHIP_DESTROYED:
case OP_TIME_SHIP_ARRIVED:
case OP_TIME_SHIP_DEPARTED:
case OP_SHIELDS_LEFT:
case OP_HITS_LEFT:
case OP_CLEAR_SHIP_GOALS:
case OP_PROTECT_SHIP:
case OP_UNPROTECT_SHIP:
case OP_BEAM_PROTECT_SHIP:
case OP_BEAM_UNPROTECT_SHIP:
case OP_TRANSFER_CARGO:
case OP_EXCHANGE_CARGO:
case OP_SHIP_INVISIBLE:
case OP_SHIP_VISIBLE:
case OP_SHIP_INVULNERABLE:
case OP_SHIP_VULNERABLE:
case OP_SHIP_GUARDIAN:
case OP_SHIP_VANISH:
case OP_SHIP_LIGHTS_ON:
case OP_SHIP_LIGHTS_OFF:
case OP_SHIP_NO_GUARDIAN:
case OP_SECONDARIES_DEPLETED:
case OP_SPECIAL_WARP_DISTANCE:
case OP_SET_SPECIAL_WARPOUT_NAME:
case OP_IS_SHIP_VISIBLE:
return OPF_SHIP;
case OP_IS_DESTROYED:
case OP_HAS_ARRIVED:
case OP_HAS_DEPARTED:
case OP_CLEAR_GOALS:
return OPF_SHIP_WING;
case OP_IS_DISABLED_DELAY:
case OP_IS_DISARMED_DELAY:
if ( argnum == 0 )
return OPF_POSITIVE;
else
return OPF_SHIP;
case OP_FACING:
if (argnum == 0)
return OPF_SHIP;
else
return OPF_POSITIVE;
case OP_FACING2:
if (argnum == 0) {
return OPF_WAYPOINT_PATH;
} else {
return OPF_POSITIVE;
}
case OP_ORDER:
if (argnum == 1)
return OPF_AI_ORDER;
else
return OPF_SHIP_WING;
case OP_IS_DESTROYED_DELAY:
case OP_HAS_ARRIVED_DELAY:
case OP_HAS_DEPARTED_DELAY:
case OP_LAST_ORDER_TIME:
if ( argnum == 0 )
return OPF_POSITIVE;
else
return OPF_SHIP_WING;
case OP_DISTANCE:
return OPF_SHIP_WING_POINT;
case OP_MODIFY_VARIABLE:
if (argnum == 0) {
return OPF_VARIABLE_NAME;
} else {
return OPF_AMBIGUOUS;
}
case OP_HAS_DOCKED:
case OP_HAS_UNDOCKED:
case OP_HAS_DOCKED_DELAY:
case OP_HAS_UNDOCKED_DELAY:
case OP_TIME_DOCKED:
case OP_TIME_UNDOCKED:
if ( argnum < 2 )
return OPF_SHIP;
else
return OPF_POSITIVE;
case OP_TIME_WING_DESTROYED:
case OP_TIME_WING_ARRIVED:
case OP_TIME_WING_DEPARTED:
case OP_CLEAR_WING_GOALS:
return OPF_WING;
case OP_IS_SUBSYSTEM_DESTROYED:
case OP_HITS_LEFT_SUBSYSTEM:
if (!argnum)
return OPF_SHIP;
else
return OPF_SUBSYSTEM;
case OP_TARGETED:
if (!argnum)
return OPF_SHIP;
else if (argnum == 1)
return OPF_POSITIVE;
else
return OPF_SUBSYSTEM;
case OP_IS_SUBSYSTEM_DESTROYED_DELAY:
if ( argnum == 0 )
return OPF_SHIP;
else if ( argnum == 1 )
return OPF_SUBSYSTEM;
else
return OPF_POSITIVE;
case OP_IS_IFF:
case OP_CHANGE_IFF:
if (!argnum)
return OPF_IFF;
else
return OPF_SHIP;
case OP_ADD_SHIP_GOAL:
if (!argnum)
return OPF_SHIP;
else
return OPF_AI_GOAL;
case OP_ADD_WING_GOAL:
if (!argnum)
return OPF_WING;
else
return OPF_AI_GOAL;
case OP_ADD_GOAL:
if ( argnum == 0 )
return OPF_SHIP_WING;
else
return OPF_AI_GOAL;
case OP_WHEN:
case OP_COND:
if (!argnum)
return OPF_BOOL;
else
return OPF_NULL;
case OP_AI_DISABLE_SHIP:
case OP_AI_DISARM_SHIP:
case OP_AI_EVADE_SHIP:
case OP_AI_STAY_NEAR_SHIP:
case OP_AI_IGNORE:
if (!argnum)
return OPF_SHIP;
else
return OPF_POSITIVE;
case OP_AI_CHASE:
case OP_AI_GUARD:
if (!argnum)
return OPF_SHIP_WING;
else
return OPF_POSITIVE;
case OP_AI_UNDOCK:
case OP_AI_KEEP_SAFE_DISTANCE:
return OPF_POSITIVE;
case OP_AI_DOCK:
if (!argnum)
return OPF_SHIP;
else if (argnum == 1)
return OPF_DOCKER_POINT;
else if (argnum == 2)
return OPF_DOCKEE_POINT;
else
return OPF_POSITIVE;
case OP_AI_CHASE_WING:
case OP_AI_GUARD_WING:
if (!argnum)
return OPF_WING;
else
return OPF_POSITIVE;
case OP_AI_DESTROY_SUBSYS:
if (!argnum)
return OPF_SHIP;
else if (argnum == 1)
return OPF_SUBSYSTEM;
else
return OPF_POSITIVE;
case OP_GOALS_ID:
return OPF_AI_GOAL;
case OP_SEND_MESSAGE:
case OP_SEND_RANDOM_MESSAGE:
if ( argnum == 0 )
return OPF_WHO_FROM;
else if ( argnum == 1 )
return OPF_PRIORITY;
else
return OPF_MESSAGE;
case OP_SEND_MESSAGE_LIST:
int a_mod;
// every four, the value repeats
a_mod = argnum % 4;
// who from
if(a_mod == 0)
return OPF_WHO_FROM;
else if(a_mod == 1)
return OPF_PRIORITY;
else if(a_mod == 2)
return OPF_MESSAGE;
else if(a_mod == 3)
return OPF_POSITIVE;
case OP_TRAINING_MSG:
if (argnum < 2)
return OPF_MESSAGE;
else
return OPF_POSITIVE;
case OP_SELF_DESTRUCT:
return OPF_SHIP;
case OP_NEXT_MISSION:
return OPF_MISSION_NAME;
case OP_END_CAMPAIGN:
case OP_END_OF_CAMPAIGN:
return OPF_NONE;
case OP_PREVIOUS_GOAL_TRUE:
case OP_PREVIOUS_GOAL_FALSE:
if ( argnum == 0 )
return OPF_MISSION_NAME;
else if (argnum == 1 )
return OPF_GOAL_NAME;
else
return OPF_BOOL;
case OP_PREVIOUS_GOAL_INCOMPLETE:
return OPF_GOAL_NAME;
case OP_PREVIOUS_EVENT_TRUE:
case OP_PREVIOUS_EVENT_FALSE:
case OP_PREVIOUS_EVENT_INCOMPLETE:
if (!argnum)
return OPF_MISSION_NAME;
else if ( argnum == 1 )
return OPF_EVENT_NAME;
else
return OPF_BOOL;
case OP_SABOTAGE_SUBSYSTEM:
case OP_REPAIR_SUBSYSTEM:
case OP_SET_SUBSYSTEM_STRNGTH:
if (!argnum)
return OPF_SHIP_NOT_PLAYER;
else if (argnum == 1 )
return OPF_SUBSYSTEM;
else
return OPF_POSITIVE;
case OP_WAYPOINTS_DONE:
if ( argnum == 0 )
return OPF_SHIP_WING;
else
return OPF_WAYPOINT_PATH;
case OP_WAYPOINTS_DONE_DELAY:
if ( argnum == 0 )
return OPF_SHIP_WING;
else if ( argnum == 1 )
return OPF_WAYPOINT_PATH;
else
return OPF_POSITIVE;
case OP_INVALIDATE_GOAL:
case OP_VALIDATE_GOAL:
return OPF_GOAL_NAME;
case OP_SHIP_TYPE_DESTROYED:
if ( argnum == 0 )
return OPF_POSITIVE;
else
return OPF_SHIP_TYPE;
case OP_KEY_PRESSED:
if (!argnum)
return OPF_KEYPRESS;
else
return OPF_POSITIVE;
case OP_KEY_RESET:
return OPF_KEYPRESS;
case OP_EVENT_TRUE:
case OP_EVENT_FALSE:
return OPF_EVENT_NAME;
case OP_EVENT_INCOMPLETE:
case OP_EVENT_TRUE_DELAY:
case OP_EVENT_FALSE_DELAY:
if (!argnum)
return OPF_EVENT_NAME;
else
return OPF_POSITIVE;
case OP_GOAL_INCOMPLETE:
case OP_GOAL_TRUE_DELAY:
case OP_GOAL_FALSE_DELAY:
if (!argnum)
return OPF_GOAL_NAME;
else
return OPF_POSITIVE;
case OP_AI_PLAY_DEAD:
case OP_AI_CHASE_ANY:
return OPF_POSITIVE;
case OP_AI_STAY_STILL:
if (!argnum)
return OPF_SHIP_POINT;
else
return OPF_POSITIVE;
case OP_GOOD_REARM_TIME:
if ( argnum == 0 )
return OPF_IFF;
else
return OPF_POSITIVE;
case OP_NUM_PLAYERS:
return OPF_POSITIVE;
case OP_SKILL_LEVEL_AT_LEAST:
return OPF_SKILL_LEVEL;
case OP_GRANT_MEDAL:
case OP_WAS_MEDAL_GRANTED:
return OPF_MEDAL_NAME;
case OP_IS_CARGO_KNOWN:
return OPF_SHIP;
case OP_CARGO_KNOWN_DELAY:
if ( argnum == 0 )
return OPF_POSITIVE;
else
return OPF_SHIP;
case OP_HAS_BEEN_TAGGED_DELAY:
if ( argnum == 0 ) {
return OPF_POSITIVE;
} else {
return OPF_SHIP;
}
case OP_CAP_SUBSYS_CARGO_KNOWN_DELAY:
if ( argnum == 0 ) {
return OPF_POSITIVE;
} else if ( argnum == 1 ) {
return OPF_SHIP;
} else {
return OPF_SUBSYSTEM;
}
case OP_ALLOW_SHIP:
case OP_TECH_ADD_SHIP:
return OPF_SHIP_CLASS_NAME;
case OP_ALLOW_WEAPON:
case OP_TECH_ADD_WEAPON:
return OPF_WEAPON_NAME;
case OP_WARP_BROKEN:
case OP_WARP_NOT_BROKEN:
case OP_WARP_NEVER:
case OP_WARP_ALLOWED:
return OPF_SHIP;
case OP_FLASH_HUD_GAUGE:
return OPF_HUD_GAUGE_NAME;
case OP_GOOD_SECONDARY_TIME:
if ( argnum == 0 )
return OPF_IFF;
else if ( argnum == 1 )
return OPF_POSITIVE;
else if ( argnum == 2 )
return OPF_HUGE_WEAPON;
else
return OPF_SHIP;
case OP_PERCENT_SHIPS_DEPARTED:
case OP_PERCENT_SHIPS_DESTROYED:
if ( argnum == 0 ){
return OPF_POSITIVE;
} else {
return OPF_SHIP_WING;
}
break;
case OP_DEPART_NODE_DELAY:
if ( argnum == 0 ){
return OPF_POSITIVE;
} else if ( argnum == 1 ){
return OPF_JUMP_NODE_NAME;
} else {
return OPF_SHIP;
}
case OP_DESTROYED_DEPARTED_DELAY:
if ( argnum == 0 ){
return OPF_POSITIVE;
} else {
return OPF_SHIP_WING;
}
case OP_JETTISON_CARGO:
if(argnum == 0){
return OPF_SHIP;
} else {
return OPF_POSITIVE;
}
case OP_CARGO_NO_DEPLETE:
if (argnum == 0) {
return OPF_SHIP;
} else {
return OPF_NUMBER;
}
case OP_BEAM_FIRE:
switch(argnum){
case 0:
return OPF_SHIP;
case 1:
return OPF_SUBSYSTEM;
case 2:
return OPF_SHIP;
case 3:
return OPF_SUBSYSTEM;
}
case OP_IS_TAGGED:
return OPF_SHIP;
case OP_NUM_KILLS:
return OPF_SHIP;
case OP_NUM_TYPE_KILLS:
if(argnum == 0){
return OPF_SHIP;
} else {
return OPF_SHIP_TYPE;
}
case OP_NUM_CLASS_KILLS:
if(argnum == 0){
return OPF_SHIP;
} else {
return OPF_SHIP_CLASS_NAME;
}
case OP_BEAM_FREE:
case OP_BEAM_LOCK:
case OP_TURRET_FREE:
case OP_TURRET_LOCK:
if(argnum == 0){
return OPF_SHIP;
} else {
return OPF_SUBSYSTEM;
}
case OP_BEAM_FREE_ALL:
case OP_BEAM_LOCK_ALL:
case OP_TURRET_FREE_ALL:
case OP_TURRET_LOCK_ALL:
case OP_TURRET_TAGGED_ONLY_ALL:
case OP_TURRET_TAGGED_CLEAR_ALL:
return OPF_SHIP;
case OP_ADD_REMOVE_ESCORT:
if(argnum == 0){
return OPF_SHIP;
} else {
return OPF_NUMBER;
}
case OP_AWACS_SET_RADIUS:
if(argnum == 0){
return OPF_SHIP;
} else if(argnum == 1){
return OPF_AWACS_SUBSYSTEM;
} else {
return OPF_NUMBER;
}
case OP_CAP_WAYPOINT_SPEED:
if (argnum == 0) {
return OPF_SHIP;
} else {
return OPF_NUMBER;
}
case OP_SUBSYS_SET_RANDOM:
if (argnum == 0) {
return OPF_SHIP;
} else if (argnum == 1 || argnum == 2) {
return OPF_NUMBER;
} else {
return OPF_SUBSYSTEM;
}
case OP_SUPERNOVA_START:
return OPF_NUMBER;
case OP_SHIELD_RECHARGE_PCT:
case OP_WEAPON_RECHARGE_PCT:
case OP_ENGINE_RECHARGE_PCT:
return OPF_SHIP;
case OP_SHIELD_QUAD_LOW:
if(argnum == 0){
return OPF_SHIP;
} else {
return OPF_NUMBER;
}
case OP_SECONDARY_AMMO_PCT:
if(argnum == 0){
return OPF_SHIP;
} else {
return OPF_NUMBER;
}
case OP_IS_SECONDARY_SELECTED:
case OP_IS_PRIMARY_SELECTED:
if(argnum == 0){
return OPF_SHIP;
} else {
return OPF_NUMBER;
}
default:
Int3();
}
return 0;
}
void update_block_names(const char *old_name, const char *new_name)
{
int i;
for (i=0; i<MAX_SEXP_VARIABLES; i++) {
if (Sexp_variables[i].type & SEXP_VARIABLE_BLOCK) {
if ( !stricmp(old_name, Sexp_variables[i].variable_name) ) {
strcpy(Sexp_variables[i].variable_name, new_name);
}
}
}
}
// DA: 1/7/99 Used to rename ships and waypoints, not variables
// Strictly used in FRED
void update_sexp_references(char *old_name, char *new_name)
{
int i;
// update_block_names
update_block_names(old_name, new_name);
Assert(strlen(new_name) < TOKEN_LENGTH);
for (i=0; i<MAX_SEXP_NODES; i++){
if ((SEXP_NODE_TYPE(i) == SEXP_ATOM) && (Sexp_nodes[i].subtype == SEXP_ATOM_STRING)){
if (!stricmp(CTEXT(i), old_name)){
strcpy(CTEXT(i), new_name);
}
}
}
}
// DA: 1/7/99 Used to rename event names, goal names, not variables
// Strictly used in FRED
void update_sexp_references(char *old_name, char *new_name, int format)
{
int i;
Assert(strlen(new_name) < TOKEN_LENGTH);
for (i=0; i<MAX_SEXP_NODES; i++){
if (is_sexp_top_level(i)){
update_sexp_references(old_name, new_name, format, i);
}
}
}
// DA: 1/7/99 Used to rename event names, goal names, not variables
// recursive function to update references to a certain type of data
void update_sexp_references(char *old_name, char *new_name, int format, int node)
{
int i, n, op;
if (node < 0){
return;
}
if ((SEXP_NODE_TYPE(node) == SEXP_LIST) && (Sexp_nodes[node].subtype == SEXP_ATOM_LIST)) {
if (Sexp_nodes[node].first){
update_sexp_references(old_name, new_name, format, Sexp_nodes[node].first);
}
if (Sexp_nodes[node].rest){
update_sexp_references(old_name, new_name, format, Sexp_nodes[node].rest);
}
return;
}
if (SEXP_NODE_TYPE(node) != SEXP_ATOM){
return;
}
if (Sexp_nodes[node].subtype != SEXP_ATOM_OPERATOR){
return;
}
op = identify_operator(CTEXT(node));
Assert(Sexp_nodes[node].first < 0);
n = Sexp_nodes[node].rest;
i = 0;
while (n >= 0) {
if (SEXP_NODE_TYPE(n) == SEXP_LIST){
update_sexp_references(old_name, new_name, format, Sexp_nodes[n].first);
} else {
Assert((SEXP_NODE_TYPE(n) == SEXP_ATOM) && ((Sexp_nodes[n].subtype == SEXP_ATOM_NUMBER) || (Sexp_nodes[n].subtype == SEXP_ATOM_STRING)));
if (query_operator_argument_type(op, i) == format) {
if (!stricmp(CTEXT(n), old_name)){
strcpy(CTEXT(n), new_name);
}
}
}
n = Sexp_nodes[n].rest;
i++;
}
}
int query_referenced_in_sexp(int mode, char *name, int *node)
{
int i, n, j;
for (n=0; n<MAX_SEXP_NODES; n++){
if ((SEXP_NODE_TYPE(n) == SEXP_ATOM) && (Sexp_nodes[n].subtype == SEXP_ATOM_STRING)){
if (!stricmp(CTEXT(n), name)){
break;
}
}
}
if (n == MAX_SEXP_NODES){
return 0;
}
if (node){
*node = n;
}
// so we know it's being used somewhere.. Time to find out where..
for (i=0; i<MAX_SHIPS; i++)
if (Ships[i].objnum >= 0) {
if (query_node_in_sexp(n, Ships[i].arrival_cue)){
return i | SRC_SHIP_ARRIVAL;
}
if (query_node_in_sexp(n, Ships[i].departure_cue)){
return i | SRC_SHIP_DEPARTURE;
}
}
for (i=0; i<MAX_WINGS; i++){
if (Wings[i].wave_count) {
if (query_node_in_sexp(n, Wings[i].arrival_cue)){
return i | SRC_WING_ARRIVAL;
}
if (query_node_in_sexp(n, Wings[i].departure_cue)){
return i | SRC_WING_DEPARTURE;
}
}
}
for (i=0; i<Num_mission_events; i++){
if (query_node_in_sexp(n, Mission_events[i].formula)){
return i | SRC_EVENT;
}
}
for (i=0; i<Num_goals; i++){
if (query_node_in_sexp(n, Mission_goals[i].formula)){
return i | SRC_MISSION_GOAL;
}
}
for (j=0; j<Num_teams; j++) {
for (i=0; i<Debriefings[j].num_stages; i++) {
if (query_node_in_sexp(n, Debriefings[j].stages[i].formula)){
return i | SRC_DEBRIEFING;
}
}
}
for (j=0; j<Num_teams; j++) {
for (i=0; i<Briefings[j].num_stages; i++) {
if (query_node_in_sexp(n, Briefings[j].stages[i].formula)){
return i | SRC_BRIEFING;
}
}
}
return SRC_UNKNOWN;
}
int verify_vector(char *text)
{
char *str;
int i, z, len = 0;
for (i=0; i<Num_waypoint_lists; i++) {
len = strlen(str = Waypoint_lists[i].name);
if (!strnicmp(str, text, len)){
if (!text[len] || text[len] == ':'){
break;
}
}
}
if (i < Num_waypoint_lists) {
if (!text[len]){
return 0; // a valid waypoint path
}
str = &text[len + 1];
while (*str){
if (!isdigit(*str++)){
return -1; // not a valid number
}
}
z = atoi(&text[len + 1]);
if (z < 1 || z > Waypoint_lists[i].count){
return -1; // out of range
}
return 0; // a valid waypoint
}
len = strlen(text);
if (text[0] != '(' || text[len - 1] != ')'){
return -1;
}
str = &text[0];
skip_white(&str);
if (*str != '('){
return -1;
}
str++;
skip_white(&str);
if (validate_float(&str)){
return -1;
}
skip_white(&str);
if (validate_float(&str)){
return -1;
}
skip_white(&str);
if (validate_float(&str)){
return -1;
}
skip_white(&str);
if (*str != ')'){
return -1;
}
str++;
skip_white(&str);
if (*str){
return -1;
}
return 0;
}
void skip_white(char **str)
{
if ((**str == ' ') || (**str == '\t')){
(*str)++;
}
}
int validate_float(char **str)
{
int count = 0, dot = 0;
while (isdigit(**str) || **str == '.') {
if (**str == '.') {
if (dot){
return -1;
}
dot = 1;
}
(*str)++;
count++;
}
if (!count){
return -1;
}
return 0;
}
// check if operator return type opr is a valid match for operator argument type opf
int sexp_query_type_match(int opf, int opr)
{
switch (opf) {
case OPF_NUMBER:
return ((opr == OPR_NUMBER) || (opr == OPR_POSITIVE));
case OPF_POSITIVE:
return (opr == OPR_POSITIVE);
case OPF_BOOL:
return (opr == OPR_BOOL);
case OPF_NULL:
return (opr == OPR_NULL);
case OPF_AI_GOAL:
return (opr == OPR_AI_GOAL);
}
return 0;
}
char *sexp_error_message(int num)
{
switch (num) {
case SEXP_CHECK_NONOP_ARGS:
return "Data shouldn't have arguments";
case SEXP_CHECK_OP_EXPTECTED:
return "Operator expected instead of data";
case SEXP_CHECK_UNKNOWN_OP:
return "Unrecognized operator";
case SEXP_CHECK_TYPE_MISMATCH:
return "Argument type mismatch";
case SEXP_CHECK_BAD_ARG_COUNT:
return "Argument count is illegal";
case SEXP_CHECK_UNKNOWN_TYPE:
return "Unknown operator argument type";
case SEXP_CHECK_INVALID_NUM:
return "Not a number";
case SEXP_CHECK_INVALID_SHIP:
return "Invalid ship name";
case SEXP_CHECK_INVALID_WING:
return "Invalid wing name";
case SEXP_CHECK_INVALID_SUBSYS:
return "Invalid subsystem name";
case SEXP_CHECK_INVALID_IFF:
return "Invalid team name";
case SEXP_CHECK_INVALID_POINT:
return "Invalid point";
case SEXP_CHECK_NEGATIVE_NUM:
return "Negative number not allowed";
case SEXP_CHECK_INVALID_SHIP_WING:
return "Invalid ship/wing name";
case SEXP_CHECK_INVALID_SHIP_TYPE:
return "Invalid ship type";
case SEXP_CHECK_UNKNOWN_MESSAGE:
return "Invalid message name";
case SEXP_CHECK_INVALID_PRIORITY:
return "Invalid priority";
case SEXP_CHECK_INVALID_MISSION_NAME:
return "Invalid mission filename";
case SEXP_CHECK_INVALID_GOAL_NAME:
return "Invalid goal name";
case SEXP_CHECK_INVALID_LEVEL:
return "Mission level too low in tree";
case SEXP_CHECK_INVALID_MSG_SOURCE:
return "Invalid message source";
case SEXP_CHECK_INVALID_DOCKER_POINT:
return "Invalid docker point";
case SEXP_CHECK_INVALID_DOCKEE_POINT:
return "Invalid dockee point";
case SEXP_CHECK_ORDER_NOT_ALLOWED:
return "Ship not allowed to have this order";
case SEXP_CHECK_DOCKING_NOT_ALLOWED:
return "Ship can't dock with target ship";
case SEXP_CHECK_NUM_RANGE_INVALID:
return "Number is out of range";
case SEXP_CHECK_INVALID_EVENT_NAME:
return "Event name is invalid (not known)";
case SEXP_CHECK_INVALID_SKILL_LEVEL:
return "Skill level name is invalid (not known)";
case SEXP_CHECK_INVALID_MEDAL_NAME:
return "Invalid medal name";
case SEXP_CHECK_INVALID_WEAPON_NAME:
return "Invalid weapon name";
case SEXP_CHECK_INVALID_SHIP_CLASS_NAME:
return "Invalid ship class name";
case SEXP_CHECK_INVALID_GAUGE_NAME:
return "Invalid HUD gauges name";
case SEXP_CHECK_INVALID_JUMP_NODE:
return "Invalid Jump Node name";
}
sprintf(Sexp_error_text, "Sexp error code %d", num);
return Sexp_error_text;
}
int query_sexp_ai_goal_valid(int sexp_ai_goal, int ship)
{
int i, op;
for (op=0; op<Num_operators; op++)
if (Operators[op].value == sexp_ai_goal)
break;
Assert(op < Num_operators);
for (i=0; i<Num_sexp_ai_goal_links; i++)
if (Sexp_ai_goal_links[i].op_code == sexp_ai_goal)
break;
Assert(i < Num_sexp_ai_goal_links);
return ai_query_goal_valid(ship, Sexp_ai_goal_links[i].ai_goal);
}
// Takes an Sexp_node.text pointing to a variable (of form "Sexp_variables[xx]=string" or "Sexp_variables[xx]=number")
// and returns the index into the Sexp_variables array of the actual value
int extract_sexp_variable_index(int node)
{
char *text = Sexp_nodes[node].text;
char char_index[8];
char *start_index;
int variable_index;
// get past the '['
start_index = text + 15;
Assert(isdigit(*start_index));
int len = 0;
while ( *start_index != ']' ) {
char_index[len++] = *(start_index++);
Assert(len < 3);
}
Assert(len > 0);
char_index[len] = 0; // append null termination to string
variable_index = atoi(char_index);
Assert( (variable_index >= 0) && (variable_index < MAX_SEXP_VARIABLES) );
return variable_index;
}
// wrapper around Sexp_node[xx].text for normal and variable
char *CTEXT(int n)
{
if (Sexp_nodes[n].type & SEXP_FLAG_VARIABLE) {
int sexp_variable_index;
if (Fred_running) {
sexp_variable_index = get_index_sexp_variable_name(Sexp_nodes[n].text);
Assert(sexp_variable_index != -1);
} else {
// sexp_variable_index = extract_sexp_variable_index(n);
sexp_variable_index = atoi(Sexp_nodes[n].text);
}
// Reference a Sexp_variable
// string format -- "Sexp_variables[xx]=number" or "Sexp_variables[xx]=string", where xx is the index
Assert( !(Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_NOT_USED) );
Assert(Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_SET);
return Sexp_variables[sexp_variable_index].text;
} else {
return Sexp_nodes[n].text;
}
}
// Set all Sexp_variables to type uninitialized
void init_sexp_vars()
{
for (int i=0; i<MAX_SEXP_VARIABLES; i++) {
// if ( !(Sexp_nodes[i].type & SEXP_FLAG_PERSISTENT) )
// Sexp_nodes[i].type = SEXP_NOT_USED;
Sexp_variables[i].type = SEXP_VARIABLE_NOT_USED;
}
}
// Adds an Sexp_variable to be used in a mission.
// This should be called from within mission parse.
int sexp_add_variable(const char *text, const char *var_name, int type, int index)
{
// if index == -1, find next open slot
if (index == -1) {
for (int i=0; i<MAX_SEXP_VARIABLES; i++) {
if (Sexp_variables[i].type == SEXP_VARIABLE_NOT_USED) {
index = i;
break;
}
}
} else {
Assert( (index >= 0) && (index < MAX_SEXP_VARIABLES) );
}
if (index >= 0) {
strcpy(Sexp_variables[index].text, text);
strcpy(Sexp_variables[index].variable_name, var_name);
Sexp_variables[index].type &= ~SEXP_VARIABLE_NOT_USED;
Sexp_variables[index].type = (type | SEXP_VARIABLE_SET);
}
return index;
}
// Modifies and Sexp_variable to be used in a mission
// This should be called in mission when an sexp_variable is to be modified
void sexp_modify_variable(char *text, int index)
{
Assert(index >= 0 && index < MAX_SEXP_VARIABLES);
Assert(Sexp_variables[index].type & SEXP_VARIABLE_SET);
#ifndef NO_NETWORK
Assert( !MULTIPLAYER_CLIENT );
#endif
strcpy(Sexp_variables[index].text, text);
Sexp_variables[index].type |= SEXP_VARIABLE_MODIFIED;
// do multi_callback_here
}
void sexp_modify_variable(int n)
{
int sexp_variable_index;
int new_number;
// char *new_char_var;
char number_as_str[TOKEN_LENGTH];
#ifndef NO_NETWORK
// Only do single player of multi host
if ( MULTIPLAYER_CLIENT ) {
return;
}
#endif
if (n != -1) {
// get sexp_variable index
Assert(Sexp_nodes[n].first == -1);
sexp_variable_index = atoi(Sexp_nodes[n].text);
// verify variable set
Assert(Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_SET);
if (Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_NUMBER) {
// get new numerical value
new_number = eval_sexp(Sexp_nodes[n].rest);
sprintf(number_as_str, "%d", new_number);
sexp_modify_variable(number_as_str, sexp_variable_index);
} else {
// get new string
Assert(Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_STRING);
char *new_text = Sexp_nodes[Sexp_nodes[n].rest].text;
sexp_modify_variable(new_text, sexp_variable_index);
}
}
}
// Different type needed for Fred (1) allow modification of type (2) no callback required
void sexp_fred_modify_variable(const char *text, const char *var_name, int index, int type)
{
Assert(index >= 0 && index < MAX_SEXP_VARIABLES);
Assert(Sexp_variables[index].type & SEXP_VARIABLE_SET);
Assert( (type & SEXP_VARIABLE_NUMBER) || (type & SEXP_VARIABLE_STRING) );
strcpy(Sexp_variables[index].text, text);
strcpy(Sexp_variables[index].variable_name, var_name);
Sexp_variables[index].type = (SEXP_VARIABLE_SET | SEXP_VARIABLE_MODIFIED | type);
}
// return index of sexp_variable_name, -1 if not found
int get_index_sexp_variable_name(const char* temp_name)
{
for (int i=0; i<MAX_SEXP_VARIABLES; i++) {
if (Sexp_variables[i].type & SEXP_VARIABLE_SET) {
// check case sensitive
if ( !strcmp(Sexp_variables[i].variable_name, temp_name) ) {
return i;
}
}
}
// not found
return -1;
}
// counts number of sexp_variables that are set
int sexp_variable_count()
{
int count = 0;
for (int i=0; i<MAX_SEXP_VARIABLES; i++) {
if ( (Sexp_variables[i].type & SEXP_VARIABLE_SET) && !(Sexp_variables[i].type & SEXP_VARIABLE_BLOCK) ) {
count++;
}
}
return count;
}
// deletes sexp_variable from active
void sexp_variable_delete(int index)
{
Assert(Sexp_variables[index].type & SEXP_VARIABLE_SET);
Sexp_variables[index].type = SEXP_VARIABLE_NOT_USED;
}
int sexp_var_compare(const void *var1, const void *var2)
{
int set1, set2;
sexp_variable *sexp_var1, *sexp_var2;
sexp_var1 = (sexp_variable*) var1;
sexp_var2 = (sexp_variable*) var2;
set1 = sexp_var1->type & SEXP_VARIABLE_SET;
set2 = sexp_var2->type & SEXP_VARIABLE_SET;
if (!set1 && !set2) {
return 0;
} else if (set1 && !set2) {
return -1;
} else if (!set1 && set2) {
return 1;
} else {
return stricmp( sexp_var1->variable_name, sexp_var2->variable_name);
}
}
// Count number of variables in block
int sexp_variable_block_count()
{
int count = 0;
for (int i=0; i<MAX_SEXP_VARIABLES; i++) {
if (Sexp_variables[i].type & SEXP_VARIABLE_BLOCK) {
count++;
}
}
return count;
}
// Sort sexp_variable list lexigraphically, with set before unset
void sexp_variable_sort()
{
qsort( (void *)Sexp_variables, (size_t)(MAX_SEXP_VARIABLES - sexp_variable_block_count()), sizeof(sexp_variable), sexp_var_compare );
}
int sexp_variable_allocate_block(const char* block_name, int block_type)
{
int num_blocks, block_count, var_count, start;
block_count = sexp_variable_block_count();
var_count = sexp_variable_count();
if (block_type & SEXP_VARIABLE_BLOCK_EXP) {
num_blocks = BLOCK_EXP_SIZE;
} else {
Int3(); // add new block type here with size
return -1;
}
if (block_count + var_count > (MAX_SEXP_VARIABLES - num_blocks)) {
// not enough free space
return -1;
}
// squeeze all variables to front of array
sexp_variable_sort();
// squeeze all block to end of array
sexp_variable_condense_block();
start = MAX_SEXP_VARIABLES - block_count - num_blocks;
for (int idx=start; idx<start+num_blocks; idx++) {
Assert(Sexp_variables[idx].type == SEXP_VARIABLE_NOT_USED);
Sexp_variables[idx].type = SEXP_VARIABLE_BLOCK | block_type;
strcpy(Sexp_variables[idx].variable_name, block_name);
}
return start;
}
// squeeze all blocks to top of array
void sexp_variable_condense_block()
{
int temp_idx, idx, var_count;
var_count = sexp_variable_count();
temp_idx = MAX_SEXP_VARIABLES-1;
for (idx=MAX_SEXP_VARIABLES-1; idx>var_count-1; idx--) {
if (Sexp_variables[idx].type & SEXP_VARIABLE_BLOCK) {
if (temp_idx > idx) {
Sexp_variables[temp_idx] = Sexp_variables[idx];
Sexp_variables[idx].type = SEXP_VARIABLE_NOT_USED;
}
temp_idx--;
}
}
}
void sexp_variable_block_free(const char *ship_name, int start_index, int block_type)
{
int num_blocks;
if (block_type & SEXP_VARIABLE_BLOCK_EXP) {
num_blocks = BLOCK_EXP_SIZE;
} else {
Int3(); // new type of block
return;
}
for (int i=start_index; i<(start_index + num_blocks); i++) {
Assert(!stricmp(Sexp_variables[i].variable_name, ship_name));
Assert(Sexp_variables[i].type & block_type);
Sexp_variables[i].type = SEXP_VARIABLE_NOT_USED;
}
sexp_variable_condense_block();
}
// evaluate number which may result from an operator or may be text
// only useful for first operand
int num_eval(int node)
{
if ( CAR(node) != -1 ) {
return eval_sexp(CAR(node));
} else {
return atoi(CTEXT(node));
}
}