/* * 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. * */ // 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 #include #include #include #include #include #include #include #include #include "ai/aigoals.h" #include "ai/ailua.h" #include "asteroid/asteroid.h" #include "autopilot/autopilot.h" #include "camera/camera.h" #include "camera/photomode.h" #include "cmdline/cmdline.h" #include "debris/debris.h" #include "debugconsole/console.h" #include "fireball/fireballs.h" // for explosion stuff #include "freespace.h" #include "gamesequence/gamesequence.h" #include "gamesnd/eventmusic.h" // for change-soundtrack #include "gamesnd/gamesnd.h" #include "globalincs/alphacolors.h" #include "globalincs/linklist.h" #include "globalincs/systemvars.h" #include "globalincs/utility.h" #include "globalincs/version.h" #include "graphics/2d.h" #include "graphics/font.h" #include "graphics/light.h" #include "hud/hud.h" #include "hud/hudartillery.h" #include "hud/hudescort.h" #include "hud/hudets.h" #include "hud/hudmessage.h" #include "hud/hudparse.h" #include "hud/hudshield.h" #include "hud/hudsquadmsg.h" // for the order sexp #include "hud/hudwingmanstatus.h" #include "iff_defs/iff_defs.h" #include "io/keycontrol.h" #include "io/timer.h" #include "jumpnode/jumpnode.h" #include "localization/localize.h" #include "math/fvi.h" #include "menuui/techmenu.h" // for intel stuff #include "mission/missionbriefcommon.h" #include "mission/missioncampaign.h" #include "mission/missiongoals.h" #include "mission/missionlog.h" #include "mission/missionmessage.h" #include "mission/missionparse.h" // for p_object definition #include "mission/missiontraining.h" #include "missionui/redalert.h" #include "mod_table/mod_table.h" #include "nebula/neb.h" #include "nebula/neblightning.h" #include "network/multi.h" #include "network/multi_obj.h" #include "network/multi_sexp.h" #include "network/multi_team.h" #include "network/multimsgs.h" #include "network/multiutil.h" #include "object/objcollide.h" #include "object/objectdock.h" #include "object/objectshield.h" #include "object/objectsnd.h" #include "object/waypoint.h" #include "parse/generic_log.h" #include "parse/parselo.h" #include "scripting/scripting.h" #include "parse/sexp.h" #include "parse/sexp_container.h" #include "playerman/player.h" #include "prop/prop.h" #include "render/3d.h" #include "scripting/global_hooks.h" #include "ship/afterburner.h" #include "ship/awacs.h" #include "ship/ship.h" #include "ship/shipfx.h" #include "ship/shiphit.h" #include "ship/ship_flags.h" #include "ship/subsysdamage.h" // for sensor effect sexp #include "sound/audiostr.h" #include "sound/ds.h" #include "sound/sound.h" #include "utils/unicode.h" #include "starfield/starfield.h" #include "starfield/supernova.h" #include "stats/medals.h" #include "utils/Random.h" #include "weapon/beam.h" #include "weapon/emp.h" #include "weapon/shockwave.h" #include "weapon/weapon.h" #include "parse/sexp/sexp_lookup.h" #ifndef NDEBUG #include "hud/hudmessage.h" #endif // Stupid windows workaround... #ifdef MessageBox #undef MessageBox #endif // legacy references // -------------------------------------------------------------------------------- #define SEXP_CONDITIONAL_OPERATOR sexp_oper_type::CONDITIONAL #define SEXP_ARGUMENT_OPERATOR sexp_oper_type::ARGUMENT #define SEXP_ACTION_OPERATOR sexp_oper_type::ACTION #define SEXP_ARITHMETIC_OPERATOR sexp_oper_type::ARITHMETIC #define SEXP_BOOLEAN_OPERATOR sexp_oper_type::BOOLEAN #define SEXP_INTEGER_OPERATOR sexp_oper_type::INTEGER #define SEXP_GOAL_OPERATOR sexp_oper_type::GOAL #define OSWPT_TYPE_NONE oswpt_type::NONE #define OSWPT_TYPE_SHIP oswpt_type::SHIP #define OSWPT_TYPE_WING oswpt_type::WING #define OSWPT_TYPE_WAYPOINT oswpt_type::WAYPOINT #define OSWPT_TYPE_SHIP_ON_TEAM oswpt_type::SHIP_ON_TEAM #define OSWPT_TYPE_WHOLE_TEAM oswpt_type::WHOLE_TEAM #define OSWPT_TYPE_PARSE_OBJECT oswpt_type::PARSE_OBJECT #define OSWPT_TYPE_EXITED oswpt_type::EXITED #define OSWPT_TYPE_WING_NOT_PRESENT oswpt_type::WING_NOT_PRESENT // -------------------------------------------------------------------------------- SCP_vector Operators = { // Operator, Identity, Min / Max arguments //Arithmetic Category { "+", OP_PLUS, 2, INT_MAX, SEXP_ARITHMETIC_OPERATOR, }, { "-", OP_MINUS, 2, INT_MAX, SEXP_ARITHMETIC_OPERATOR, }, { "*", OP_MUL, 2, INT_MAX, SEXP_ARITHMETIC_OPERATOR, }, { "/", OP_DIV, 2, INT_MAX, SEXP_ARITHMETIC_OPERATOR, }, { "mod", OP_MOD, 2, INT_MAX, SEXP_ARITHMETIC_OPERATOR, }, { "rand", OP_RAND, 2, 3, SEXP_ARITHMETIC_OPERATOR, }, { "rand-multiple", OP_RAND_MULTIPLE, 2, 3, SEXP_ARITHMETIC_OPERATOR, }, // Goober5000 { "abs", OP_ABS, 1, 1, SEXP_ARITHMETIC_OPERATOR, }, // Goober5000 { "min", OP_MIN, 1, INT_MAX, SEXP_ARITHMETIC_OPERATOR, }, // Goober5000 { "max", OP_MAX, 1, INT_MAX, SEXP_ARITHMETIC_OPERATOR, }, // Goober5000 { "avg", OP_AVG, 1, INT_MAX, SEXP_ARITHMETIC_OPERATOR, }, // Goober5000 { "pow", OP_POW, 2, 2, SEXP_ARITHMETIC_OPERATOR, }, // Goober5000 { "signum", OP_SIGNUM, 1, 1, SEXP_ARITHMETIC_OPERATOR, }, // Goober5000 { "is-nan", OP_IS_NAN, 1, 1, SEXP_ARITHMETIC_OPERATOR, }, // Goober5000 { "nan-to-number", OP_NAN_TO_NUMBER, 1, 1, SEXP_ARITHMETIC_OPERATOR, }, // Goober5000 { "set-bit", OP_SET_BIT, 2, 2, SEXP_ARITHMETIC_OPERATOR, }, // Goober5000 { "unset-bit", OP_UNSET_BIT, 2, 2, SEXP_ARITHMETIC_OPERATOR, }, // Goober5000 { "is-bit-set", OP_IS_BIT_SET, 2, 2, SEXP_BOOLEAN_OPERATOR, }, // Goober5000 { "bitwise-and", OP_BITWISE_AND, 2, INT_MAX, SEXP_ARITHMETIC_OPERATOR, }, // Goober5000 { "bitwise-or", OP_BITWISE_OR, 2, INT_MAX, SEXP_ARITHMETIC_OPERATOR, }, // Goober5000 { "bitwise-not", OP_BITWISE_NOT, 1, 1, SEXP_ARITHMETIC_OPERATOR, }, // Goober5000 { "bitwise-xor", OP_BITWISE_XOR, 2, 2, SEXP_ARITHMETIC_OPERATOR, }, // Goober5000 { "angle-vectors", OP_ANGLE_VECTORS, 6, 6, SEXP_ARITHMETIC_OPERATOR, }, // Lafiel //Logical Category { "true", OP_TRUE, 0, 0, SEXP_BOOLEAN_OPERATOR, }, { "false", OP_FALSE, 0, 0, SEXP_BOOLEAN_OPERATOR, }, { "and", OP_AND, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "and-in-sequence", OP_AND_IN_SEQUENCE, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "or", OP_OR, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "not", OP_NOT, 1, 1, SEXP_BOOLEAN_OPERATOR, }, { "xor", OP_XOR, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, // Goober5000 { "=", OP_EQUALS, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "!=", OP_NOT_EQUAL, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, // Goober5000 { ">", OP_GREATER_THAN, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { ">=", OP_GREATER_OR_EQUAL, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, // Goober5000 { "<", OP_LESS_THAN, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "<=", OP_LESS_OR_EQUAL, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, // Goober5000 { "string-equals", OP_STRING_EQUALS, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "string-greater-than", OP_STRING_GREATER_THAN, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "string-less-than", OP_STRING_LESS_THAN, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "perform-actions-bool-first", OP_PERFORM_ACTIONS_BOOL_FIRST, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, // Goober5000 { "perform-actions-bool-last", OP_PERFORM_ACTIONS_BOOL_LAST, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, // Goober5000 { "has-time-elapsed", OP_HAS_TIME_ELAPSED, 1, 1, SEXP_BOOLEAN_OPERATOR, }, { "has-time-elapsed-msecs", OP_HAS_TIME_ELAPSED_MSECS, 1, 1, SEXP_BOOLEAN_OPERATOR, }, // Goober5000 { "is-true-for-duration", OP_IS_TRUE_FOR_DURATION, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, // Goober5000 //Event/Goals Category { "is-goal-true-delay", OP_GOAL_TRUE_DELAY, 2, 2, SEXP_BOOLEAN_OPERATOR, }, { "is-goal-false-delay", OP_GOAL_FALSE_DELAY, 2, 2, SEXP_BOOLEAN_OPERATOR, }, { "is-goal-incomplete", OP_GOAL_INCOMPLETE, 1, 1, SEXP_BOOLEAN_OPERATOR, }, { "is-event-true", OP_EVENT_TRUE, 1, 1, SEXP_BOOLEAN_OPERATOR, }, { "is-event-true-delay", OP_EVENT_TRUE_DELAY, 2, 3, SEXP_BOOLEAN_OPERATOR, }, { "is-event-true-msecs-delay", OP_EVENT_TRUE_MSECS_DELAY, 2, 3, SEXP_BOOLEAN_OPERATOR, }, { "is-event-false", OP_EVENT_FALSE, 1, 1, SEXP_BOOLEAN_OPERATOR, }, { "is-event-false-delay", OP_EVENT_FALSE_DELAY, 2, 3, SEXP_BOOLEAN_OPERATOR, }, { "is-event-false-msecs-delay", OP_EVENT_FALSE_MSECS_DELAY, 2, 3, SEXP_BOOLEAN_OPERATOR, }, { "is-event-incomplete", OP_EVENT_INCOMPLETE, 1, 1, SEXP_BOOLEAN_OPERATOR, }, { "is-previous-goal-true", OP_PREVIOUS_GOAL_TRUE, 2, 3, SEXP_BOOLEAN_OPERATOR, }, { "is-previous-goal-false", OP_PREVIOUS_GOAL_FALSE, 2, 3, SEXP_BOOLEAN_OPERATOR, }, { "is-previous-goal-incomplete", OP_PREVIOUS_GOAL_INCOMPLETE, 2, 3, SEXP_BOOLEAN_OPERATOR, }, { "is-previous-event-true", OP_PREVIOUS_EVENT_TRUE, 2, 3, SEXP_BOOLEAN_OPERATOR, }, { "is-previous-event-false", OP_PREVIOUS_EVENT_FALSE, 2, 3, SEXP_BOOLEAN_OPERATOR, }, { "is-previous-event-incomplete", OP_PREVIOUS_EVENT_INCOMPLETE, 2, 3, SEXP_BOOLEAN_OPERATOR, }, { "reset-event", OP_RESET_EVENT, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "reset-goal", OP_RESET_GOAL, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 //Objectives Category { "is-destroyed", OP_IS_DESTROYED, 1, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "is-destroyed-delay", OP_IS_DESTROYED_DELAY, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "was-destroyed-by-delay", OP_WAS_DESTROYED_BY_DELAY, 3, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "is-subsystem-destroyed", OP_IS_SUBSYSTEM_DESTROYED, 2, 2, SEXP_BOOLEAN_OPERATOR, }, { "is-subsystem-destroyed-delay", OP_IS_SUBSYSTEM_DESTROYED_DELAY, 3, 3, SEXP_BOOLEAN_OPERATOR, }, { "is-disabled", OP_IS_DISABLED, 1, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "is-disabled-delay", OP_IS_DISABLED_DELAY, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "is-disarmed", OP_IS_DISARMED, 1, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "is-disarmed-delay", OP_IS_DISARMED_DELAY, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "has-docked", OP_HAS_DOCKED, 3, 3, SEXP_BOOLEAN_OPERATOR, }, { "has-docked-delay", OP_HAS_DOCKED_DELAY, 4, 4, SEXP_BOOLEAN_OPERATOR, }, { "has-undocked", OP_HAS_UNDOCKED, 3, 3, SEXP_BOOLEAN_OPERATOR, }, { "has-undocked-delay", OP_HAS_UNDOCKED_DELAY, 4, 4, SEXP_BOOLEAN_OPERATOR, }, { "has-arrived", OP_HAS_ARRIVED, 1, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "has-arrived-delay", OP_HAS_ARRIVED_DELAY, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "has-departed", OP_HAS_DEPARTED, 1, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "has-departed-delay", OP_HAS_DEPARTED_DELAY, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "are-waypoints-done", OP_WAYPOINTS_DONE, 2, 2, SEXP_BOOLEAN_OPERATOR, }, { "are-waypoints-done-delay", OP_WAYPOINTS_DONE_DELAY, 3, 4, SEXP_BOOLEAN_OPERATOR, }, { "is-nav-visited", OP_NAV_IS_VISITED, 1, 1, SEXP_BOOLEAN_OPERATOR, }, // Kazan { "ship-type-destroyed", OP_SHIP_TYPE_DESTROYED, 2, 2, SEXP_BOOLEAN_OPERATOR, }, { "percent-ships-destroyed", OP_PERCENT_SHIPS_DESTROYED, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "percent-ships-disabled", OP_PERCENT_SHIPS_DISABLED, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, // Goober5000 { "percent-ships-disarmed", OP_PERCENT_SHIPS_DISARMED, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, // Goober5000 { "percent-ships-departed", OP_PERCENT_SHIPS_DEPARTED, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "percent-ships-arrived", OP_PERCENT_SHIPS_ARRIVED, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "percent-ships-scanned", OP_PERCENT_SHIPS_SCANNED, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, // Goober5000 { "depart-node-delay", OP_DEPART_NODE_DELAY, 3, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "destroyed-or-departed-delay", OP_DESTROYED_DEPARTED_DELAY, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, //Status Category //Mission Sub-Category { "num-ships-in-battle", OP_NUM_SHIPS_IN_BATTLE, 0, INT_MAX, SEXP_INTEGER_OPERATOR, }, //phreak modified by FUBAR { "num-ships-in-wing", OP_NUM_SHIPS_IN_WING, 1, INT_MAX, SEXP_INTEGER_OPERATOR, }, // Karajorma { "directive-value", OP_DIRECTIVE_VALUE, 1, 2, SEXP_INTEGER_OPERATOR, }, // Karajorma { "get-hotkey", OP_GET_HOTKEY, 1, 1, SEXP_INTEGER_OPERATOR, }, // wookieejedi //Player Sub-Category { "was-promotion-granted", OP_WAS_PROMOTION_GRANTED, 0, 1, SEXP_BOOLEAN_OPERATOR, }, { "was-medal-granted", OP_WAS_MEDAL_GRANTED, 0, 1, SEXP_BOOLEAN_OPERATOR, }, { "skill-level-at-least", OP_SKILL_LEVEL_AT_LEAST, 1, 1, SEXP_BOOLEAN_OPERATOR, }, { "num_kills", OP_NUM_KILLS, 1, 1, SEXP_INTEGER_OPERATOR, }, { "num_assists", OP_NUM_ASSISTS, 1, 1, SEXP_INTEGER_OPERATOR, }, { "num_type_kills", OP_NUM_TYPE_KILLS, 2, 2, SEXP_INTEGER_OPERATOR, }, { "num_class_kills", OP_NUM_CLASS_KILLS, 2, 2, SEXP_INTEGER_OPERATOR, }, { "ship_score", OP_SHIP_SCORE, 1, 1, SEXP_INTEGER_OPERATOR, }, { "time-elapsed-last-order", OP_LAST_ORDER_TIME, 2, 2, SEXP_INTEGER_OPERATOR, }, { "player-is-cheating", OP_PLAYER_IS_CHEATING_BASTARD, 0, 0, SEXP_BOOLEAN_OPERATOR, }, { "is-language", OP_IS_LANGUAGE, 1, 1, SEXP_BOOLEAN_OPERATOR, }, // Goober5000 { "used-cheat", OP_USED_CHEAT, 1, 1, SEXP_BOOLEAN_OPERATOR, }, // Kiloku //Multiplayer Sub-Category { "num-players", OP_NUM_PLAYERS, 0, 0, SEXP_INTEGER_OPERATOR, }, { "team-score", OP_TEAM_SCORE, 1, 1, SEXP_INTEGER_OPERATOR, }, { "ship-deaths", OP_SHIP_DEATHS, 1, 1, SEXP_INTEGER_OPERATOR, }, { "respawns-left", OP_RESPAWNS_LEFT, 1, 1, SEXP_INTEGER_OPERATOR, }, { "is-player", OP_IS_PLAYER, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, // Karajorma //Ship Status Sub-Category { "is-in-mission", OP_IS_IN_MISSION, 1, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, // Goober5000 { "is-docked", OP_IS_DOCKED, 1, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, // Goober5000 { "is-ship-visible", OP_IS_SHIP_VISIBLE, 1, 2, SEXP_BOOLEAN_OPERATOR, }, { "is-ship-stealthy", OP_IS_SHIP_STEALTHY, 1, 1, SEXP_BOOLEAN_OPERATOR, }, { "is-friendly-stealth-visible", OP_IS_FRIENDLY_STEALTH_VISIBLE, 1, 1, SEXP_BOOLEAN_OPERATOR, }, { "is-iff", OP_IS_IFF, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "is-species", OP_IS_SPECIES, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "is-ai-class", OP_IS_AI_CLASS, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "is-ship-type", OP_IS_SHIP_TYPE, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "is-ship-class", OP_IS_SHIP_CLASS, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "is-facing", OP_IS_FACING, 3, 4, SEXP_BOOLEAN_OPERATOR, }, { "is_tagged", OP_IS_TAGGED, 1, 1, SEXP_BOOLEAN_OPERATOR, }, { "has-been-tagged-delay", OP_HAS_BEEN_TAGGED_DELAY, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "are-ship-flags-set", OP_ARE_SHIP_FLAGS_SET, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, // Karajorma { "are-wing-flags-set", OP_ARE_WING_FLAGS_SET, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, // Goober5000 { "has-armor-type", OP_HAS_ARMOR_TYPE, 3, 3, SEXP_BOOLEAN_OPERATOR, }, // MjnMixael { "is-ship-emp-active", OP_IS_SHIP_EMP_ACTIVE, 1, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, // MjnMixael //Shields, Engines and Weapons Sub-Category { "has-primary-weapon", OP_HAS_PRIMARY_WEAPON, 3, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, // Karajorma { "has-secondary-weapon", OP_HAS_SECONDARY_WEAPON, 3, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, // Karajorma { "is-primary-selected", OP_IS_PRIMARY_SELECTED, 2, 2, SEXP_BOOLEAN_OPERATOR, }, { "is-secondary-selected", OP_IS_SECONDARY_SELECTED, 2, 2, SEXP_BOOLEAN_OPERATOR, }, { "primary-fired-since", OP_PRIMARY_FIRED_SINCE, 3, 3, SEXP_INTEGER_OPERATOR, }, // Karajorma { "turret-fired-since", OP_TURRET_FIRED_SINCE, 3, 3, SEXP_INTEGER_OPERATOR, }, // Asteroth { "secondary-fired-since", OP_SECONDARY_FIRED_SINCE, 3, 3, SEXP_INTEGER_OPERATOR, }, // Karajorma { "primary-ammo-pct", OP_PRIMARY_AMMO_PCT, 2, 2, SEXP_INTEGER_OPERATOR, }, { "secondary-ammo-pct", OP_SECONDARY_AMMO_PCT, 2, 2, SEXP_INTEGER_OPERATOR, }, { "get-primary-ammo", OP_GET_PRIMARY_AMMO, 2, 2, SEXP_INTEGER_OPERATOR, }, // Karajorma { "get-secondary-ammo", OP_GET_SECONDARY_AMMO, 2, 2, SEXP_INTEGER_OPERATOR, }, // Karajorma { "turret-get-primary-ammo", OP_TURRET_GET_PRIMARY_AMMO, 3, 3, SEXP_INTEGER_OPERATOR, }, // DahBlount { "turret-get-secondary-ammo", OP_TURRET_GET_SECONDARY_AMMO, 3, 3, SEXP_INTEGER_OPERATOR, }, // DahBlount { "turret-has-primary-weapon", OP_TURRET_HAS_PRIMARY_WEAPON, 4, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, // MjnMixael { "turret-has-secondary-weapon", OP_TURRET_HAS_SECONDARY_WEAPON, 4, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, // MjnMixael { "get-num-countermeasures", OP_GET_NUM_COUNTERMEASURES, 1, 1, SEXP_INTEGER_OPERATOR, }, // Karajorma { "weapon-energy-pct", OP_WEAPON_ENERGY_LEFT, 1, 1, SEXP_INTEGER_OPERATOR, }, { "afterburner-energy-pct", OP_AFTERBURNER_LEFT, 1, 1, SEXP_INTEGER_OPERATOR, }, { "shield-recharge-pct", OP_SHIELD_RECHARGE_PCT, 1, 1, SEXP_INTEGER_OPERATOR, }, { "weapon-recharge-pct", OP_WEAPON_RECHARGE_PCT, 1, 1, SEXP_INTEGER_OPERATOR, }, { "engine-recharge-pct", OP_ENGINE_RECHARGE_PCT, 1, 1, SEXP_INTEGER_OPERATOR, }, { "shield-quad-low", OP_SHIELD_QUAD_LOW, 2, 2, SEXP_INTEGER_OPERATOR, }, { "get-throttle-speed", OP_GET_THROTTLE_SPEED, 1, 1, SEXP_INTEGER_OPERATOR, }, // Karajorma { "current-speed", OP_CURRENT_SPEED, 1, 1, SEXP_INTEGER_OPERATOR, }, //Cargo Sub-Category { "is-cargo-known", OP_IS_CARGO_KNOWN, 1, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "is-cargo-known-delay", OP_CARGO_KNOWN_DELAY, 2, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "cap-subsys-cargo-known-delay", OP_CAP_SUBSYS_CARGO_KNOWN_DELAY, 3, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "is-cargo", OP_IS_CARGO, 2, 3, SEXP_BOOLEAN_OPERATOR, }, //Damage Sub-Category { "shields-left", OP_SHIELDS_LEFT, 1, 1, SEXP_INTEGER_OPERATOR, }, { "hits-left", OP_HITS_LEFT, 1, 1, SEXP_INTEGER_OPERATOR, }, { "hits-left-subsystem", OP_HITS_LEFT_SUBSYSTEM, 2, 3, SEXP_INTEGER_OPERATOR, }, { "hits-left-subsystem-generic", OP_HITS_LEFT_SUBSYSTEM_GENERIC, 2, 2, SEXP_INTEGER_OPERATOR, }, // Goober5000 { "hits-left-subsystem-specific", OP_HITS_LEFT_SUBSYSTEM_SPECIFIC, 2, 2, SEXP_INTEGER_OPERATOR, }, // Goober5000 { "sim-hits-left", OP_SIM_HITS_LEFT, 1, 1, SEXP_INTEGER_OPERATOR, }, // Turey { "get-damage-caused", OP_GET_DAMAGE_CAUSED, 2, INT_MAX, SEXP_INTEGER_OPERATOR, }, //Distance and Coordinates Sub-Category { "distance", OP_DISTANCE, 2, 2, SEXP_INTEGER_OPERATOR, }, { "distance-to-center", OP_DISTANCE_CENTER, 2, 2, SEXP_INTEGER_OPERATOR, }, // Goober5000 { "distance-to-bbox", OP_DISTANCE_BBOX, 2, 2, SEXP_INTEGER_OPERATOR, }, // Goober5000 { "distance-center-to-subsystem", OP_DISTANCE_CENTER_SUBSYSTEM, 3, 3, SEXP_INTEGER_OPERATOR, }, // Goober5000 { "distance-bbox-to-subsystem", OP_DISTANCE_BBOX_SUBSYSTEM, 3, 3, SEXP_INTEGER_OPERATOR, }, // Goober5000 { "distance-to-nav", OP_NAV_DISTANCE, 1, 1, SEXP_INTEGER_OPERATOR, }, // Kazan { "num-within-box", OP_NUM_WITHIN_BOX, 7, INT_MAX, SEXP_INTEGER_OPERATOR, }, //WMC { "is-in-box", OP_IS_IN_BOX, 7, 8, SEXP_INTEGER_OPERATOR, }, //Sushi { "special-warp-dist", OP_SPECIAL_WARP_DISTANCE, 1, 1, SEXP_INTEGER_OPERATOR, }, { "get-object-x", OP_GET_OBJECT_X, 1, 5, SEXP_INTEGER_OPERATOR, }, // Goober5000 { "get-object-y", OP_GET_OBJECT_Y, 1, 5, SEXP_INTEGER_OPERATOR, }, // Goober5000 { "get-object-z", OP_GET_OBJECT_Z, 1, 5, SEXP_INTEGER_OPERATOR, }, // Goober5000 { "get-object-pitch", OP_GET_OBJECT_PITCH, 1, 1, SEXP_INTEGER_OPERATOR, }, // Goober5000 { "get-object-bank", OP_GET_OBJECT_BANK, 1, 1, SEXP_INTEGER_OPERATOR, }, // Goober5000 { "get-object-heading", OP_GET_OBJECT_HEADING, 1, 1, SEXP_INTEGER_OPERATOR, }, // Goober5000 { "get-object-speed-x", OP_GET_OBJECT_SPEED_X, 1, 2, SEXP_INTEGER_OPERATOR, }, { "get-object-speed-y", OP_GET_OBJECT_SPEED_Y, 1, 2, SEXP_INTEGER_OPERATOR, }, { "get-object-speed-z", OP_GET_OBJECT_SPEED_Z, 1, 2, SEXP_INTEGER_OPERATOR, }, { "angle-facing-object", OP_ANGLE_FVEC_TARGET, 2, 2, SEXP_INTEGER_OPERATOR, }, // Lafiel //Variables Sub-Category { "string-to-int", OP_STRING_TO_INT, 1, 1, SEXP_INTEGER_OPERATOR, }, // Karajorma { "string-get-length", OP_STRING_GET_LENGTH, 1, 1, SEXP_INTEGER_OPERATOR, }, // Goober5000 //Containers Sub-Category { "is-container-empty", OP_IS_CONTAINER_EMPTY, 1, 1, SEXP_INTEGER_OPERATOR, }, // Karajorma { "get-container-size", OP_GET_CONTAINER_SIZE, 1, 1, SEXP_INTEGER_OPERATOR, }, // Karajorma { "list-has-data", OP_LIST_HAS_DATA, 2, INT_MAX, SEXP_INTEGER_OPERATOR, }, // Karajorma { "list-data-index", OP_LIST_DATA_INDEX, 2, 2, SEXP_INTEGER_OPERATOR, }, // Karajorma { "map-has-key", OP_MAP_HAS_KEY, 2, INT_MAX, SEXP_INTEGER_OPERATOR, }, // Karajorma { "map-has-data-item", OP_MAP_HAS_DATA_ITEM, 2, 3, SEXP_INTEGER_OPERATOR, }, // Karajorma //Other Sub-Category { "script-eval-bool", OP_SCRIPT_EVAL_BOOL, 1, INT_MAX, SEXP_BOOLEAN_OPERATOR, }, { "script-eval-num", OP_SCRIPT_EVAL_NUM, 1, INT_MAX, SEXP_INTEGER_OPERATOR, }, //Time Category { "time-ship-destroyed", OP_TIME_SHIP_DESTROYED, 1, 1, SEXP_INTEGER_OPERATOR, }, { "time-ship-arrived", OP_TIME_SHIP_ARRIVED, 1, 1, SEXP_INTEGER_OPERATOR, }, { "time-ship-departed", OP_TIME_SHIP_DEPARTED, 1, 1, SEXP_INTEGER_OPERATOR, }, { "time-wing-destroyed", OP_TIME_WING_DESTROYED, 1, 1, SEXP_INTEGER_OPERATOR, }, { "time-wing-arrived", OP_TIME_WING_ARRIVED, 1, 1, SEXP_INTEGER_OPERATOR, }, { "time-wing-departed", OP_TIME_WING_DEPARTED, 1, 1, SEXP_INTEGER_OPERATOR, }, { "mission-time", OP_MISSION_TIME, 0, 0, SEXP_INTEGER_OPERATOR, }, { "mission-time-msecs", OP_MISSION_TIME_MSECS, 0, 0, SEXP_INTEGER_OPERATOR, }, // Goober5000 { "time-docked", OP_TIME_DOCKED, 3, 3, SEXP_INTEGER_OPERATOR, }, { "time-undocked", OP_TIME_UNDOCKED, 3, 3, SEXP_INTEGER_OPERATOR, }, { "time-to-goal", OP_TIME_TO_GOAL, 1, 1, SEXP_INTEGER_OPERATOR, }, // tcrayford { "set-hud-timer-padding", OP_SET_HUD_TIME_PAD, 1, 1, SEXP_ACTION_OPERATOR, }, // MjnMixael //Conditionals Category { "cond", OP_COND, 1, INT_MAX, SEXP_CONDITIONAL_OPERATOR,}, { "when", OP_WHEN, 2, INT_MAX, SEXP_CONDITIONAL_OPERATOR,}, { "when-argument", OP_WHEN_ARGUMENT, 3, INT_MAX, SEXP_CONDITIONAL_OPERATOR,}, // Goober5000 { "every-time", OP_EVERY_TIME, 2, INT_MAX, SEXP_CONDITIONAL_OPERATOR,}, // Goober5000 { "every-time-argument", OP_EVERY_TIME_ARGUMENT, 3, INT_MAX, SEXP_CONDITIONAL_OPERATOR,}, // Goober5000 { "on-mission-skip", OP_ON_MISSION_SKIP, 1, INT_MAX, SEXP_CONDITIONAL_OPERATOR, }, // Goober5000 { "functional-when", OP_FUNCTIONAL_WHEN, 4, INT_MAX, SEXP_CONDITIONAL_OPERATOR, }, // Goober5000 { "if-then-else", OP_IF_THEN_ELSE, 3, INT_MAX, SEXP_CONDITIONAL_OPERATOR,}, // Goober5000 { "functional-if-then-else", OP_FUNCTIONAL_IF_THEN_ELSE, 3, 3, SEXP_CONDITIONAL_OPERATOR, }, // Goober5000 { "switch", OP_SWITCH, 2, INT_MAX, SEXP_CONDITIONAL_OPERATOR, }, // Goober5000 { "functional-switch", OP_FUNCTIONAL_SWITCH, 2, INT_MAX, SEXP_CONDITIONAL_OPERATOR, }, // Goober5000 { "any-of", OP_ANY_OF, 1, INT_MAX, SEXP_ARGUMENT_OPERATOR, }, // Goober5000 { "every-of", OP_EVERY_OF, 1, INT_MAX, SEXP_ARGUMENT_OPERATOR, }, // Goober5000 { "random-of", OP_RANDOM_OF, 1, INT_MAX, SEXP_ARGUMENT_OPERATOR, }, // Goober5000 { "random-multiple-of", OP_RANDOM_MULTIPLE_OF, 1, INT_MAX, SEXP_ARGUMENT_OPERATOR, }, // Karajorma { "number-of", OP_NUMBER_OF, 2, INT_MAX, SEXP_ARGUMENT_OPERATOR, }, // Goober5000 { "first-of", OP_FIRST_OF, 2, INT_MAX, SEXP_ARGUMENT_OPERATOR, }, // MageKing17 { "in-sequence", OP_IN_SEQUENCE, 1, INT_MAX, SEXP_ARGUMENT_OPERATOR, }, // Karajorma { "for-counter", OP_FOR_COUNTER, 2, 3, SEXP_ARGUMENT_OPERATOR, }, // Goober5000 { "for-ship-class", OP_FOR_SHIP_CLASS, 1, INT_MAX, SEXP_ARGUMENT_OPERATOR, }, // Goober5000 { "for-ship-type", OP_FOR_SHIP_TYPE, 1, INT_MAX, SEXP_ARGUMENT_OPERATOR, }, // Goober5000 { "for-ship-team", OP_FOR_SHIP_TEAM, 1, INT_MAX, SEXP_ARGUMENT_OPERATOR, }, // Goober5000 { "for-ship-species", OP_FOR_SHIP_SPECIES, 1, INT_MAX, SEXP_ARGUMENT_OPERATOR, }, // Goober5000 { "for-players", OP_FOR_PLAYERS, 0, 0, SEXP_ARGUMENT_OPERATOR, }, // Goober5000 { "for-subsystems", OP_FOR_SUBSYSTEMS, 1, INT_MAX, SEXP_ARGUMENT_OPERATOR, }, // Goober5000 { "for-container-data", OP_FOR_CONTAINER_DATA, 1, INT_MAX, SEXP_ARGUMENT_OPERATOR, }, // jg18 { "for-map-container-keys", OP_FOR_MAP_CONTAINER_KEYS, 1, INT_MAX, SEXP_ARGUMENT_OPERATOR, }, // jg18 { "invalidate-argument", OP_INVALIDATE_ARGUMENT, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "invalidate-all-arguments", OP_INVALIDATE_ALL_ARGUMENTS, 0, 0, SEXP_ACTION_OPERATOR, }, // Karajorma { "validate-argument", OP_VALIDATE_ARGUMENT, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Karajorma { "validate-all-arguments", OP_VALIDATE_ALL_ARGUMENTS, 0, 0, SEXP_ACTION_OPERATOR, }, // Karajorma { "do-for-valid-arguments", OP_DO_FOR_VALID_ARGUMENTS, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Karajorma { "num-valid-arguments", OP_NUM_VALID_ARGUMENTS, 0, 0, SEXP_ACTION_OPERATOR, }, // Karajorma //Change Category //Messaging Sub-Category { "send-message", OP_SEND_MESSAGE, 3, 3, SEXP_ACTION_OPERATOR, }, { "send-builtin-message", OP_SEND_BUILTIN_MESSAGE, 4, INT_MAX, SEXP_ACTION_OPERATOR, }, { "send-message-list", OP_SEND_MESSAGE_LIST, 4, INT_MAX, SEXP_ACTION_OPERATOR, }, { "send-message-chain", OP_SEND_MESSAGE_CHAIN, 5, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "send-random-message", OP_SEND_RANDOM_MESSAGE, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, { "scramble-messages", OP_SCRAMBLE_MESSAGES, 0, INT_MAX, SEXP_ACTION_OPERATOR, }, { "unscramble-messages", OP_UNSCRAMBLE_MESSAGES, 0, INT_MAX, SEXP_ACTION_OPERATOR, }, { "disable-builtin-messages", OP_DISABLE_BUILTIN_MESSAGES, 0, INT_MAX, SEXP_ACTION_OPERATOR, }, // Karajorma { "enable-builtin-messages", OP_ENABLE_BUILTIN_MESSAGES, 0, INT_MAX, SEXP_ACTION_OPERATOR, }, // Karajorma { "set-persona", OP_SET_PERSONA, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Karajorma { "set-death-message", OP_SET_DEATH_MESSAGE, 1, 1, SEXP_ACTION_OPERATOR, }, // Goober5000 { "set-mission-mood", OP_SET_MISSION_MOOD, 1, 1, SEXP_ACTION_OPERATOR, }, // Karajorma //AI Control Sub-Category { "add-goal", OP_ADD_GOAL, 2, 2, SEXP_ACTION_OPERATOR, }, { "remove-goal", OP_REMOVE_GOAL, 2, 4, SEXP_ACTION_OPERATOR, }, // Goober5000 { "add-ship-goal", OP_ADD_SHIP_GOAL, 2, 2, SEXP_ACTION_OPERATOR, }, { "add-wing-goal", OP_ADD_WING_GOAL, 2, 2, SEXP_ACTION_OPERATOR, }, { "clear-goals", OP_CLEAR_GOALS, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "clear-ship-goals", OP_CLEAR_SHIP_GOALS, 1, 1, SEXP_ACTION_OPERATOR, }, { "clear-wing-goals", OP_CLEAR_WING_GOALS, 1, 1, SEXP_ACTION_OPERATOR, }, { "good-rearm-time", OP_GOOD_REARM_TIME, 2, 2, SEXP_ACTION_OPERATOR, }, { "bad-rearm-time", OP_BAD_REARM_TIME, 2, 2, SEXP_ACTION_OPERATOR, }, // Asteroth { "good-primary-time", OP_GOOD_PRIMARY_TIME, 4, 4, SEXP_ACTION_OPERATOR, }, // plieblang { "good-secondary-time", OP_GOOD_SECONDARY_TIME, 4, 4, SEXP_ACTION_OPERATOR, }, { "change-ai-class", OP_CHANGE_AI_CLASS, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, { "player-use-ai", OP_PLAYER_USE_AI, 0, 0, SEXP_ACTION_OPERATOR, }, // Goober5000 { "player-not-use-ai", OP_PLAYER_NOT_USE_AI, 0, 0, SEXP_ACTION_OPERATOR, }, // Goober5000 { "set-player-orders", OP_SET_PLAYER_ORDERS, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // Karajorma { "set-order-allowed-for-target", OP_SET_ORDER_ALLOWED_TARGET, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // MjnMixael { "enable-general-orders", OP_ENABLE_GENERAL_ORDERS, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // MjnMixael { "validate-general-orders", OP_VALIDATE_GENERAL_ORDERS, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // MjnMixael { "cap-waypoint-speed", OP_CAP_WAYPOINT_SPEED, 2, 2, SEXP_ACTION_OPERATOR, }, { "set-wing-formation", OP_SET_WING_FORMATION, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 //Ship Status Sub-Category { "protect-ship", OP_PROTECT_SHIP, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "unprotect-ship", OP_UNPROTECT_SHIP, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "beam-protect-ship", OP_BEAM_PROTECT_SHIP, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "beam-unprotect-ship", OP_BEAM_UNPROTECT_SHIP, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "turret-protect-ship", OP_TURRET_PROTECT_SHIP, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "turret-unprotect-ship", OP_TURRET_UNPROTECT_SHIP, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "ship-invisible", OP_SHIP_INVISIBLE, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "ship-visible", OP_SHIP_VISIBLE, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "ship-stealthy", OP_SHIP_STEALTHY, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "ship-unstealthy", OP_SHIP_UNSTEALTHY, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "friendly-stealth-invisible", OP_FRIENDLY_STEALTH_INVISIBLE, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "friendly-stealth-visible", OP_FRIENDLY_STEALTH_VISIBLE, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "primitive-sensors-set-range", OP_PRIMITIVE_SENSORS_SET_RANGE, 2, 2, SEXP_ACTION_OPERATOR, }, // Goober5000 { "ship-targetable-as-bomb", OP_SHIP_BOMB_TARGETABLE, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "ship-untargetable-as-bomb", OP_SHIP_BOMB_UNTARGETABLE, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "kamikaze", OP_KAMIKAZE, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Sesquipedalian { "change-iff", OP_CHANGE_IFF, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, { "change-iff-color", OP_CHANGE_IFF_COLOR, 6, INT_MAX, SEXP_ACTION_OPERATOR, }, { "add-remove-escort", OP_ADD_REMOVE_ESCORT, 2, 2, SEXP_ACTION_OPERATOR, }, { "ship-change-alt-name", OP_SHIP_CHANGE_ALT_NAME, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "ship-change-display-name", OP_SHIP_CHANGE_DISPLAY_NAME, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // DarkVisor { "ship-change-callsign", OP_SHIP_CHANGE_CALLSIGN, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // FUBAR { "ship-tag", OP_SHIP_TAG, 3, 8, SEXP_ACTION_OPERATOR, }, // Goober5000 { "ship-untag", OP_SHIP_UNTAG, 1, 1, SEXP_ACTION_OPERATOR, }, // Goober5000 { "set-arrival-info", OP_SET_ARRIVAL_INFO, 2, 8, SEXP_ACTION_OPERATOR, }, // Goober5000 { "set-departure-info", OP_SET_DEPARTURE_INFO, 2, 7, SEXP_ACTION_OPERATOR, }, // Goober5000 { "alter-ship-flag", OP_ALTER_SHIP_FLAG, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // Karajorma { "alter-wing-flag", OP_ALTER_WING_FLAG, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "cancel-future-waves", OP_CANCEL_FUTURE_WAVES, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // naomimyselfandi //Shields, Engines and Weapons Sub-Category { "set-weapon-energy", OP_SET_WEAPON_ENERGY, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Karajorma { "set-shield-energy", OP_SET_SHIELD_ENERGY, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Karajorma { "set-player-throttle-speed", OP_SET_PLAYER_THROTTLE_SPEED, 2, 2, SEXP_ACTION_OPERATOR, }, // CommanderDJ { "set-afterburner-energy", OP_SET_AFTERBURNER_ENERGY, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Karajorma { "set-subspace-drive", OP_SET_SUBSPACE_DRIVE, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, { "set-primary-weapon", OP_SET_PRIMARY_WEAPON, 3, 5, SEXP_ACTION_OPERATOR, }, // Karajorma { "set-secondary-weapon", OP_SET_SECONDARY_WEAPON, 3, 5, SEXP_ACTION_OPERATOR, }, // Karajorma { "set-primary-ammo", OP_SET_PRIMARY_AMMO, 3, 4, SEXP_ACTION_OPERATOR, }, // Karajorma { "set-secondary-ammo", OP_SET_SECONDARY_AMMO, 3, 4, SEXP_ACTION_OPERATOR, }, // Karajorma { "set-num-countermeasures", OP_SET_NUM_COUNTERMEASURES, 2, 2, SEXP_ACTION_OPERATOR, }, // Karajorma { "lock-primary-weapon", OP_LOCK_PRIMARY_WEAPON, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Karajorma { "unlock-primary-weapon", OP_UNLOCK_PRIMARY_WEAPON, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Karajorma { "lock-secondary-weapon", OP_LOCK_SECONDARY_WEAPON, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Karajorma { "unlock-secondary-weapon", OP_UNLOCK_SECONDARY_WEAPON, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Karajorma { "lock-afterburner", OP_LOCK_AFTERBURNER, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // KeldorKatarn { "unlock-afterburner", OP_UNLOCK_AFTERBURNER, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // KeldorKatarn { "shields-on", OP_SHIELDS_ON, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Sesquipedalian { "shields-off", OP_SHIELDS_OFF, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Sesquipedalian { "force-glide", OP_FORCE_GLIDE, 2, 2, SEXP_ACTION_OPERATOR, }, // The E { "disable-ets", OP_DISABLE_ETS, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // The E { "enable-ets", OP_ENABLE_ETS, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // The E { "break-warp", OP_WARP_BROKEN, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "fix-warp", OP_WARP_NOT_BROKEN, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "never-warp", OP_WARP_NEVER, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "allow-warp", OP_WARP_ALLOWED, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "special-warpout-name", OP_SET_SPECIAL_WARPOUT_NAME, 2, 2, SEXP_ACTION_OPERATOR, }, { "get-ets-value", OP_GET_ETS_VALUE, 2, 2, SEXP_ACTION_OPERATOR, }, // niffiwan { "set-ets-values", OP_SET_ETS_VALUES, 4, INT_MAX, SEXP_ACTION_OPERATOR, }, // niffiwan { "get-power-output", OP_GET_POWER_OUTPUT, 1, 1, SEXP_ACTION_OPERATOR, }, // The E //Subsystems and Health Sub-Category { "ship-invulnerable", OP_SHIP_INVULNERABLE, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "ship-vulnerable", OP_SHIP_VULNERABLE, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "ship-guardian", OP_SHIP_GUARDIAN, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "ship-no-guardian", OP_SHIP_NO_GUARDIAN, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "ship-guardian-threshold", OP_SHIP_GUARDIAN_THRESHOLD, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, { "ship-subsys-guardian-threshold", OP_SHIP_SUBSYS_GUARDIAN_THRESHOLD, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, { "set-guard-range", OP_SET_GUARD_RANGE, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // MjnMixael + The Force { "self-destruct", OP_SELF_DESTRUCT, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "destroy-instantly", OP_DESTROY_INSTANTLY, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Admiral MS { "destroy-instantly-with-debris", OP_DESTROY_INSTANTLY_WITH_DEBRIS, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Asteroth { "destroy-subsys-instantly", OP_DESTROY_SUBSYS_INSTANTLY, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Admiral MS { "sabotage-subsystem", OP_SABOTAGE_SUBSYSTEM, 3, 3, SEXP_ACTION_OPERATOR, }, { "repair-subsystem", OP_REPAIR_SUBSYSTEM, 3, 5, SEXP_ACTION_OPERATOR, }, { "ship-copy-damage", OP_SHIP_COPY_DAMAGE, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "set-subsystem-strength", OP_SET_SUBSYSTEM_STRNGTH, 3, 5, SEXP_ACTION_OPERATOR, }, { "subsys-set-random", OP_SUBSYS_SET_RANDOM, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, { "lock-rotating-subsystem", OP_LOCK_ROTATING_SUBSYSTEM, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "free-rotating-subsystem", OP_FREE_ROTATING_SUBSYSTEM, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "reverse-rotating-subsystem", OP_REVERSE_ROTATING_SUBSYSTEM, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "rotating-subsys-set-turn-time", OP_ROTATING_SUBSYS_SET_TURN_TIME, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "lock-translating-subsystem", OP_LOCK_TRANSLATING_SUBSYSTEM, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "free-translating-subsystem", OP_FREE_TRANSLATING_SUBSYSTEM, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "reverse-translating-subsystem", OP_REVERSE_TRANSLATING_SUBSYSTEM, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "translating-subsys-set-speed", OP_TRANSLATING_SUBSYS_SET_SPEED, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "trigger-submodel-animation", OP_TRIGGER_SUBMODEL_ANIMATION, 4, 6, SEXP_ACTION_OPERATOR, }, // Goober5000 { "change-subsystem-name", OP_CHANGE_SUBSYSTEM_NAME, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // Karajorma { "ship-subsys-targetable", OP_SHIP_SUBSYS_TARGETABLE, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "ship-subsys-untargetable", OP_SHIP_SUBSYS_UNTARGETABLE, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "ship-subsys-no-replace", OP_SHIP_SUBSYS_NO_REPLACE, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // FUBAR { "ship-subsys-no-live-debris", OP_SHIP_SUBSYS_NO_LIVE_DEBRIS, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // FUBAR { "ship-subsys-vanish", OP_SHIP_SUBSYS_VANISHED, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // FUBAR { "ship-subsys-ignore_if_dead", OP_SHIP_SUBSYS_IGNORE_IF_DEAD, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // FUBAR { "awacs-set-radius", OP_AWACS_SET_RADIUS, 3, 3, SEXP_ACTION_OPERATOR, }, //Cargo Sub-Category { "transfer-cargo", OP_TRANSFER_CARGO, 2, 2, SEXP_ACTION_OPERATOR, }, { "exchange-cargo", OP_EXCHANGE_CARGO, 2, 2, SEXP_ACTION_OPERATOR, }, { "set-cargo", OP_SET_CARGO, 2, 3, SEXP_ACTION_OPERATOR, }, { "jettison-cargo-delay", OP_JETTISON_CARGO_DELAY, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, { "jettison-cargo", OP_JETTISON_CARGO_NEW, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "set-docked", OP_SET_DOCKED, 4, 4, SEXP_ACTION_OPERATOR, }, // Sushi { "cargo-no-deplete", OP_CARGO_NO_DEPLETE, 1, 2, SEXP_ACTION_OPERATOR, }, { "set-scanned", OP_SET_SCANNED, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "set-unscanned", OP_SET_UNSCANNED, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, //Armor and Damage Types Sub-Category { "set-armor-type", OP_SET_ARMOR_TYPE, 4, INT_MAX, SEXP_ACTION_OPERATOR, }, // FUBAR { "weapon-set-damage-type", OP_WEAPON_SET_DAMAGE_TYPE, 4, INT_MAX, SEXP_ACTION_OPERATOR, }, // FUBAR { "ship-set-damage-type", OP_SHIP_SET_DAMAGE_TYPE, 4, INT_MAX, SEXP_ACTION_OPERATOR, }, // FUBAR { "ship-set-shockwave-damage-type", OP_SHIP_SHOCKWAVE_SET_DAMAGE_TYPE, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // FUBAR { "field-set-damage-type", OP_FIELD_SET_DAMAGE_TYPE, 2, 2, SEXP_ACTION_OPERATOR, }, // FUBAR //Beams and Turrets Sub-Category { "fire-beam", OP_BEAM_FIRE, 3, 5, SEXP_ACTION_OPERATOR, }, { "fire-beam-at-coordinates", OP_BEAM_FIRE_COORDS, 5, 9, SEXP_ACTION_OPERATOR, }, { "beam-create", OP_BEAM_FLOATING_FIRE, 7, 14, SEXP_ACTION_OPERATOR, }, { "beam-free", OP_BEAM_FREE, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, { "beam-free-all", OP_BEAM_FREE_ALL, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "beam-lock", OP_BEAM_LOCK, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, { "beam-lock-all", OP_BEAM_LOCK_ALL, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "turret-free", OP_TURRET_FREE, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, { "turret-free-all", OP_TURRET_FREE_ALL, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "turret-lock", OP_TURRET_LOCK, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, { "turret-lock-all", OP_TURRET_LOCK_ALL, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "turret-tagged-only", OP_TURRET_TAGGED_ONLY_ALL, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "turret-tagged-clear", OP_TURRET_TAGGED_CLEAR_ALL, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "turret-tagged-specific", OP_TURRET_TAGGED_SPECIFIC, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, //phreak { "turret-tagged-clear-specific", OP_TURRET_TAGGED_CLEAR_SPECIFIC, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, //phreak { "turret-change-weapon", OP_TURRET_CHANGE_WEAPON, 5, 5, SEXP_ACTION_OPERATOR, }, //WMC { "turret-set-direction-preference",OP_TURRET_SET_DIRECTION_PREFERENCE, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, //FUBAR { "turret-set-rate-of-fire", OP_TURRET_SET_RATE_OF_FIRE, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, //FUBAR { "turret-set-optimum-range", OP_TURRET_SET_OPTIMUM_RANGE, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, //FUBAR { "turret-set-target-priorities", OP_TURRET_SET_TARGET_PRIORITIES, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, //FUBAR { "turret-set-inaccuracy", OP_TURRET_SET_INACCURACY, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Asteroth { "turret-set-target-order", OP_TURRET_SET_TARGET_ORDER, 2, 2+ NUM_TURRET_ORDER_TYPES, SEXP_ACTION_OPERATOR, }, //WMC { "ship-turret-target-order", OP_SHIP_TURRET_TARGET_ORDER, 1, 1+ NUM_TURRET_ORDER_TYPES, SEXP_ACTION_OPERATOR, }, //WMC { "turret-subsys-target-disable", OP_TURRET_SUBSYS_TARGET_DISABLE, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, { "turret-subsys-target-enable", OP_TURRET_SUBSYS_TARGET_ENABLE, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, { "turret-set-forced-target", OP_TURRET_SET_FORCED_TARGET, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // Asteroth { "turret-set-forced-subsys-target",OP_TURRET_SET_FORCED_SUBSYS_TARGET, 4, INT_MAX, SEXP_ACTION_OPERATOR, }, // Asteroth { "turret-clear-forced-target", OP_TURRET_CLEAR_FORCED_TARGET, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Asteroth { "turret-set-primary-ammo", OP_TURRET_SET_PRIMARY_AMMO, 4, 4, SEXP_ACTION_OPERATOR, }, // DahBlount { "turret-set-secondary-ammo", OP_TURRET_SET_SECONDARY_AMMO, 4, 4, SEXP_ACTION_OPERATOR, }, // DahBlount { "is-in-turret-fov", OP_IS_IN_TURRET_FOV, 3, 4, SEXP_BOOLEAN_OPERATOR, }, // Goober5000 //Models and Textures Sub-Category { "change-ship-class", OP_CHANGE_SHIP_CLASS, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "deactivate-glow-maps", OP_DEACTIVATE_GLOW_MAPS, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, //-Bobboau { "activate-glow-maps", OP_ACTIVATE_GLOW_MAPS, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, //-Bobboau { "deactivate-glow-points", OP_DEACTIVATE_GLOW_POINTS, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, //-Bobboau { "activate-glow-points", OP_ACTIVATE_GLOW_POINTS, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, //-Bobboau { "deactivate-glow-point-bank", OP_DEACTIVATE_GLOW_POINT_BANK, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, //-Bobboau { "activate-glow-point-bank", OP_ACTIVATE_GLOW_POINT_BANK, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, //-Bobboau { "set-thrusters-status", OP_SET_THRUSTERS, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // The E { "don't-collide-invisible", OP_DONT_COLLIDE_INVISIBLE, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "collide-invisible", OP_COLLIDE_INVISIBLE, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "add-to-collision-group", OP_ADD_TO_COLGROUP, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // The E { "remove-from-collision-group", OP_REMOVE_FROM_COLGROUP, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // The E { "add-to-collision-group-new", OP_ADD_TO_COLGROUP_NEW, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "remove-from-collision-group-new", OP_REMOVE_FROM_COLGROUP_NEW, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "get-collision-group", OP_GET_COLGROUP_ID, 1, 1, SEXP_ACTION_OPERATOR, }, { "change-team-color", OP_CHANGE_TEAM_COLOR, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // The E { "replace-texture", OP_REPLACE_TEXTURE, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // Lafiel { "replace-skybox-texture", OP_REPLACE_TEXTURE_SKYBOX, 2, 2, SEXP_ACTION_OPERATOR, }, // Lafiel { "set-alpha-multiplier", OP_SET_ALPHA_MULT, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, //Lafiel { "trigger-ship-animation", OP_TRIGGER_ANIMATION_NEW, 3, 7, SEXP_ACTION_OPERATOR, }, //Lafiel { "stop-looping-animation", OP_STOP_LOOPING_ANIMATION, 3, 3, SEXP_ACTION_OPERATOR, }, //Lafiel { "update-moveable-animation", OP_UPDATE_MOVEABLE, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, //Lafiel { "advance-moveable-animation", OP_ADVANCE_MOVEABLE, 2, 2, SEXP_ACTION_OPERATOR, }, //Lafiel //Coordinate Manipulation Sub-Category { "set-object-position", OP_SET_OBJECT_POSITION, 4, 4, SEXP_ACTION_OPERATOR, }, // WMC { "set-object-orientation", OP_SET_OBJECT_ORIENTATION, 4, 4, SEXP_ACTION_OPERATOR, }, // Goober5000 { "set-object-facing", OP_SET_OBJECT_FACING, 4, 6, SEXP_ACTION_OPERATOR, }, // Goober5000 { "set-object-facing-object", OP_SET_OBJECT_FACING_OBJECT, 2, 4, SEXP_ACTION_OPERATOR, }, // Goober5000 { "set-object-speed-x", OP_SET_OBJECT_SPEED_X, 2, 3, SEXP_ACTION_OPERATOR, }, // WMC // Deprecated by wookieejedi { "set-object-speed-y", OP_SET_OBJECT_SPEED_Y, 2, 3, SEXP_ACTION_OPERATOR, }, // WMC // Deprecated by wookieejedi { "set-object-speed-z", OP_SET_OBJECT_SPEED_Z, 2, 3, SEXP_ACTION_OPERATOR, }, // WMC // Deprecated by wookieejedi { "ship-maneuver", OP_SHIP_MANEUVER, 10, 11, SEXP_ACTION_OPERATOR, }, // Wanderer { "ship-rot-maneuver", OP_SHIP_ROT_MANEUVER, 6, 7, SEXP_ACTION_OPERATOR, }, // Wanderer { "ship-lat-maneuver", OP_SHIP_LAT_MANEUVER, 6, 7, SEXP_ACTION_OPERATOR, }, // Wanderer { "set-immobile", OP_SET_IMMOBILE, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "set-mobile", OP_SET_MOBILE, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 //Mission and Campaign Sub-Category { "invalidate-goal", OP_INVALIDATE_GOAL, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "validate-goal", OP_VALIDATE_GOAL, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "red-alert", OP_RED_ALERT, 0, 0, SEXP_ACTION_OPERATOR, }, { "end-mission", OP_END_MISSION, 0, 3, SEXP_ACTION_OPERATOR, }, //-Sesquipedalian { "force-jump", OP_FORCE_JUMP, 0, 0, SEXP_ACTION_OPERATOR, }, // Goober5000 { "next-mission", OP_NEXT_MISSION, 1, 1, SEXP_ACTION_OPERATOR, }, { "end-campaign", OP_END_CAMPAIGN, 0, 1, SEXP_ACTION_OPERATOR, }, { "end-of-campaign", OP_END_OF_CAMPAIGN, 0, 0, SEXP_ACTION_OPERATOR, }, { "set-debriefing-toggled", OP_SET_DEBRIEFING_TOGGLED, 1, 1, SEXP_ACTION_OPERATOR, }, // Goober5000 { "set-debriefing-persona", OP_SET_DEBRIEFING_PERSONA, 1, 1, SEXP_ACTION_OPERATOR, }, // Goober5000 { "set-traitor-override", OP_SET_TRAITOR_OVERRIDE, 1, 1, SEXP_ACTION_OPERATOR, }, // MjnMixael { "set-friendly-damage-caps", OP_SET_FRIENDLY_DAMAGE_CAPS, 1, 3, SEXP_ACTION_OPERATOR, }, // Kestrellius { "allow-treason", OP_ALLOW_TREASON, 1, 1, SEXP_ACTION_OPERATOR, }, // Karajorma { "grant-promotion", OP_GRANT_PROMOTION, 0, 0, SEXP_ACTION_OPERATOR, }, { "grant-medal", OP_GRANT_MEDAL, 1, 1, SEXP_ACTION_OPERATOR, }, { "allow-ship", OP_ALLOW_SHIP, 1, 1, SEXP_ACTION_OPERATOR, }, { "allow-weapon", OP_ALLOW_WEAPON, 1, 1, SEXP_ACTION_OPERATOR, }, { "tech-add-ships", OP_TECH_ADD_SHIP, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "tech-add-weapons", OP_TECH_ADD_WEAPON, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "tech-add-intel", OP_TECH_ADD_INTEL, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "tech-remove-intel", OP_TECH_REMOVE_INTEL, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // wookieejedi { "tech-add-intel-xstr", OP_TECH_ADD_INTEL_XSTR, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "tech-remove-intel-xstr", OP_TECH_REMOVE_INTEL_XSTR, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // wookieejedi { "tech-reset-to-default", OP_TECH_RESET_TO_DEFAULT, 0, 0, SEXP_ACTION_OPERATOR, }, // Goober5000 { "change-player-score", OP_CHANGE_PLAYER_SCORE, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Karajorma { "change-team-score", OP_CHANGE_TEAM_SCORE, 2, 2, SEXP_ACTION_OPERATOR, }, // Karajorma { "set-respawns", OP_SET_RESPAWNS, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Karajorma { "add-remove-hotkey", OP_ADD_REMOVE_HOTKEY, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // wookieejedi //Music and Sound Sub-Category { "change-soundtrack", OP_CHANGE_SOUNDTRACK, 1, 1, SEXP_ACTION_OPERATOR, }, // Goober5000 { "play-sound-from-table", OP_PLAY_SOUND_FROM_TABLE, 4, 4, SEXP_ACTION_OPERATOR, }, // Goober5000 { "play-sound-from-file", OP_PLAY_SOUND_FROM_FILE, 1, 4, SEXP_ACTION_OPERATOR, }, // Goober5000 { "close-sound-from-file", OP_CLOSE_SOUND_FROM_FILE, 0, 2, SEXP_ACTION_OPERATOR, }, // Goober5000 { "pause-sound-from-file", OP_PAUSE_SOUND_FROM_FILE, 1, 2, SEXP_ACTION_OPERATOR, }, // Goober5000 { "set-sound-environment", OP_SET_SOUND_ENVIRONMENT, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Taylor { "update-sound-environment", OP_UPDATE_SOUND_ENVIRONMENT, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Taylor { "adjust-audio-volume", OP_ADJUST_AUDIO_VOLUME, 1, 3, SEXP_ACTION_OPERATOR, }, //HUD Sub-Category { "hud-disable", OP_HUD_DISABLE, 1, 1, SEXP_ACTION_OPERATOR, }, // Goober5000 { "hud-disable-except-messages", OP_HUD_DISABLE_EXCEPT_MESSAGES, 1, 1, SEXP_ACTION_OPERATOR, }, // Goober5000 { "hud-set-custom-gauge-active", OP_HUD_SET_CUSTOM_GAUGE_ACTIVE, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, { "hud-set-builtin-gauge-active", OP_HUD_SET_BUILTIN_GAUGE_ACTIVE, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, { "hud-set-text", OP_HUD_SET_TEXT, 2, 2, SEXP_ACTION_OPERATOR, }, //WMCoolmon { "hud-set-xstr", OP_HUD_SET_XSTR, 3, 3, SEXP_ACTION_OPERATOR, }, //MjnMixael { "hud-set-text-num", OP_HUD_SET_TEXT_NUM, 2, 2, SEXP_ACTION_OPERATOR, }, //WMCoolmon { "hud-set-message", OP_HUD_SET_MESSAGE, 2, 2, SEXP_ACTION_OPERATOR, }, //The E { "hud-set-directive", OP_HUD_SET_DIRECTIVE, 2, 2, SEXP_ACTION_OPERATOR, }, //The E { "hud-set-frame", OP_HUD_SET_FRAME, 2, 2, SEXP_ACTION_OPERATOR, }, //WMCoolmon { "hud-set-coords", OP_HUD_SET_COORDS, 3, 3, SEXP_ACTION_OPERATOR, }, //WMCoolmon { "hud-set-color", OP_HUD_SET_COLOR, 4, 4, SEXP_ACTION_OPERATOR, }, //WMCoolmon { "hud-reset-color", OP_HUD_RESET_COLOR, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "hud-display-gauge", OP_HUD_DISPLAY_GAUGE, 2, 2, SEXP_ACTION_OPERATOR, }, { "hud-gauge-set-active", OP_HUD_GAUGE_SET_ACTIVE, 2, 2, SEXP_ACTION_OPERATOR, }, //Deprecated { "hud-activate-gauge-type", OP_HUD_ACTIVATE_GAUGE_TYPE, 2, 2, SEXP_ACTION_OPERATOR, }, //Deprecated { "hud-clear-messages", OP_HUD_CLEAR_MESSAGES, 0, 0, SEXP_ACTION_OPERATOR, }, // swifty { "hud-set-max-targeting-range", OP_HUD_SET_MAX_TARGETING_RANGE, 1, 1, SEXP_ACTION_OPERATOR, }, // Goober5000 { "hud-force-sensor-static", OP_HUD_FORCE_SENSOR_STATIC, 1, 1, SEXP_ACTION_OPERATOR, }, // MjnMixael { "hud-force-emp-effect", OP_HUD_FORCE_EMP_EFFECT, 2, 3, SEXP_ACTION_OPERATOR, }, // MjnMixael { "set-squadron-wings", OP_SET_SQUADRON_WINGS, 1, MAX_SQUADRON_WINGS, SEXP_ACTION_OPERATOR, }, // Goober5000 { "set-player-target", OP_SET_PLAYER_TARGET, 1, 2, SEXP_ACTION_OPERATOR, }, // LuytenKy { "clear-player-target", OP_CLEAR_PLAYER_TARGET, 0, 0, SEXP_ACTION_OPERATOR, }, // LuytenKy //Nav Sub-Category { "add-nav-waypoint", OP_NAV_ADD_WAYPOINT, 3, 4, SEXP_ACTION_OPERATOR, }, //kazan { "add-nav-ship", OP_NAV_ADD_SHIP, 2, 2, SEXP_ACTION_OPERATOR, }, //kazan { "del-nav", OP_NAV_DEL, 1, 1, SEXP_ACTION_OPERATOR, }, //kazan { "hide-nav", OP_NAV_HIDE, 1, 1, SEXP_ACTION_OPERATOR, }, //kazan { "restrict-nav", OP_NAV_RESTRICT, 1, 1, SEXP_ACTION_OPERATOR, }, //kazan { "unhide-nav", OP_NAV_UNHIDE, 1, 1, SEXP_ACTION_OPERATOR, }, //kazan { "unrestrict-nav", OP_NAV_UNRESTRICT, 1, 1, SEXP_ACTION_OPERATOR, }, //kazan { "set-nav-visited", OP_NAV_SET_VISITED, 1, 1, SEXP_ACTION_OPERATOR, }, //kazan { "unset-nav-visited", OP_NAV_UNSET_VISITED, 1, 1, SEXP_ACTION_OPERATOR, }, //kazan { "set-nav-carry", OP_NAV_SET_CARRY, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, //kazan { "unset-nav-carry", OP_NAV_UNSET_CARRY, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, //kazan { "set-nav-needslink", OP_NAV_SET_NEEDSLINK, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, //kazan { "unset-nav-needslink", OP_NAV_UNSET_NEEDSLINK, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, //kazan { "is-nav-linked", OP_NAV_ISLINKED, 1, 1, SEXP_BOOLEAN_OPERATOR, }, //kazan { "use-nav-cinematics", OP_NAV_USECINEMATICS, 1, 1, SEXP_ACTION_OPERATOR, }, //kazan { "use-autopilot", OP_NAV_USEAP, 1, 1, SEXP_ACTION_OPERATOR, }, //kazan { "select-nav", OP_NAV_SELECT, 1, 1, SEXP_ACTION_OPERATOR, }, //Talon1024 { "unselect-nav", OP_NAV_UNSELECT, 0, 0, SEXP_ACTION_OPERATOR, }, //Talon1024 { "set-nav-color", OP_NAV_SET_COLOR, 4, INT_MAX, SEXP_ACTION_OPERATOR, }, //Goober5000 { "set-nav-visited-color", OP_NAV_SET_VISITED_COLOR, 4, INT_MAX, SEXP_ACTION_OPERATOR, }, //Goober5000 //Cutscene Sub-Category { "set-cutscene-bars", OP_CUTSCENES_SET_CUTSCENE_BARS, 0, 1, SEXP_ACTION_OPERATOR, }, { "unset-cutscene-bars", OP_CUTSCENES_UNSET_CUTSCENE_BARS, 0, 1, SEXP_ACTION_OPERATOR, }, { "fade-in", OP_CUTSCENES_FADE_IN, 0, 4, SEXP_ACTION_OPERATOR, }, { "fade-out", OP_CUTSCENES_FADE_OUT, 0, 4, SEXP_ACTION_OPERATOR, }, { "set-camera", OP_CUTSCENES_SET_CAMERA, 0, 1, SEXP_ACTION_OPERATOR, }, { "set-camera-position", OP_CUTSCENES_SET_CAMERA_POSITION, 3, 6, SEXP_ACTION_OPERATOR, }, { "set-camera-facing", OP_CUTSCENES_SET_CAMERA_FACING, 3, 6, SEXP_ACTION_OPERATOR, }, { "set-camera-facing-object", OP_CUTSCENES_SET_CAMERA_FACING_OBJECT, 1, 4, SEXP_ACTION_OPERATOR, }, { "set-camera-rotation", OP_CUTSCENES_SET_CAMERA_ROTATION, 3, 6, SEXP_ACTION_OPERATOR, }, { "set-camera-host", OP_CUTSCENES_SET_CAMERA_HOST, 1, 2, SEXP_ACTION_OPERATOR, }, { "set-camera-target", OP_CUTSCENES_SET_CAMERA_TARGET, 1, 2, SEXP_ACTION_OPERATOR, }, { "set-camera-fov", OP_CUTSCENES_SET_CAMERA_FOV, 1, 5, SEXP_ACTION_OPERATOR, }, { "set-fov", OP_CUTSCENES_SET_FOV, 1, 1, SEXP_ACTION_OPERATOR, }, { "get-fov", OP_CUTSCENES_GET_FOV, 0, 0, SEXP_INTEGER_OPERATOR, }, { "reset-fov", OP_CUTSCENES_RESET_FOV, 0, 0, SEXP_ACTION_OPERATOR, }, { "reset-camera", OP_CUTSCENES_RESET_CAMERA, 0, 1, SEXP_ACTION_OPERATOR, }, { "show-subtitle", OP_CUTSCENES_SHOW_SUBTITLE, 4, 14, SEXP_ACTION_OPERATOR, }, { "show-subtitle-text", OP_CUTSCENES_SHOW_SUBTITLE_TEXT, 6, 15, SEXP_ACTION_OPERATOR, }, { "show-subtitle-image", OP_CUTSCENES_SHOW_SUBTITLE_IMAGE, 8, 11, SEXP_ACTION_OPERATOR, }, { "clear-subtitles", OP_CLEAR_SUBTITLES, 0, 0, SEXP_ACTION_OPERATOR, }, { "lock-perspective", OP_CUTSCENES_FORCE_PERSPECTIVE, 1, 3, SEXP_ACTION_OPERATOR, }, { "allow-photo-mode", OP_ALLOW_PHOTO_MODE, 1, 1, SEXP_ACTION_OPERATOR, }, { "set-camera-shudder", OP_SET_CAMERA_SHUDDER, 2, 4, SEXP_ACTION_OPERATOR, }, { "supernova-start", OP_SUPERNOVA_START, 1, 1, SEXP_ACTION_OPERATOR, }, { "supernova-stop", OP_SUPERNOVA_STOP, 0, 0, SEXP_ACTION_OPERATOR, }, //CommanderDJ { "get-supernova-stage", OP_GET_SUPERNOVA_STAGE, 0, 0, SEXP_INTEGER_OPERATOR, }, // Goober5000 { "set-motion-debris-override", OP_OVERRIDE_MOTION_DEBRIS, 1, 1, SEXP_ACTION_OPERATOR, }, // The E //Background and Nebula Sub-Category { "mission-set-nebula", OP_MISSION_SET_NEBULA, 1, 2, SEXP_ACTION_OPERATOR, }, // Sesquipedalian { "mission-set-subspace", OP_MISSION_SET_SUBSPACE, 1, 1, SEXP_ACTION_OPERATOR, }, { "change-background", OP_CHANGE_BACKGROUND, 1, 1, SEXP_ACTION_OPERATOR, }, // Goober5000 { "add-background-bitmap", OP_ADD_BACKGROUND_BITMAP, 8, 9, SEXP_ACTION_OPERATOR, }, // phreak { "add-background-bitmap-new", OP_ADD_BACKGROUND_BITMAP_NEW, 8, 9, SEXP_ACTION_OPERATOR, }, // Goober5000 { "remove-background-bitmap", OP_REMOVE_BACKGROUND_BITMAP, 1, 1, SEXP_ACTION_OPERATOR, }, // phreak { "add-sun-bitmap", OP_ADD_SUN_BITMAP, 5, 6, SEXP_ACTION_OPERATOR, }, // phreak { "add-sun-bitmap-new", OP_ADD_SUN_BITMAP_NEW, 5, 6, SEXP_ACTION_OPERATOR, }, // Goober5000 { "remove-sun-bitmap", OP_REMOVE_SUN_BITMAP, 1, 1, SEXP_ACTION_OPERATOR, }, // phreak { "nebula-change-pattern", OP_NEBULA_CHANGE_PATTERN, 1, 1, SEXP_ACTION_OPERATOR, }, // Axem { "nebula-change-fog-color", OP_NEBULA_CHANGE_FOG_COLOR, 3, 3, SEXP_ACTION_OPERATOR, }, // Asteroth { "nebula-change-storm", OP_NEBULA_CHANGE_STORM, 1, 1, SEXP_ACTION_OPERATOR, }, // phreak { "nebula-toggle-poof", OP_NEBULA_TOGGLE_POOF, 2, 2, SEXP_ACTION_OPERATOR, }, // phreak { "nebula-set-poofs", OP_NEBULA_SET_POOFS, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "nebula-fade-poof", OP_NEBULA_FADE_POOF, 3, 3, SEXP_ACTION_OPERATOR, }, // MjnMixael { "nebula-fade-poofs", OP_NEBULA_FADE_POOFS, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "nebula-set-range", OP_NEBULA_SET_RANGE, 1, 1, SEXP_ACTION_OPERATOR, }, // Goober5000 { "volumetrics-toggle", OP_VOLUMETRICS_TOGGLE, 1, 1, SEXP_ACTION_OPERATOR, }, // Lafiel { "set-skybox-model", OP_SET_SKYBOX_MODEL, 1, 8, SEXP_ACTION_OPERATOR, }, // taylor { "set-skybox-orientation", OP_SET_SKYBOX_ORIENT, 3, 3, SEXP_ACTION_OPERATOR, }, // Goober5000 { "set-skybox-alpha", OP_SET_SKYBOX_ALPHA, 1, 1, SEXP_ACTION_OPERATOR, }, // Goober5000 { "set-ambient-light", OP_SET_AMBIENT_LIGHT, 3, 3, SEXP_ACTION_OPERATOR, }, // Karajorma { "toggle-asteroid-field", OP_TOGGLE_ASTEROID_FIELD, 1, 1, SEXP_ACTION_OPERATOR, }, // MjnMixael { "set-asteroid-field", OP_SET_ASTEROID_FIELD, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // MjnMixael - Deprecated { "set-debris-field", OP_SET_DEBRIS_FIELD, 1, 12, SEXP_ACTION_OPERATOR, }, // MjnMixael - Deprecated { "config-asteroid-field", OP_CONFIG_ASTEROID_FIELD, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // MjnMixael { "config-debris-field", OP_CONFIG_DEBRIS_FIELD, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // MjnMixael { "config-field-targets", OP_CONFIG_FIELD_TARGETS, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // MjnMixael { "set-motion-debris", OP_SET_MOTION_DEBRIS, 1, 1, SEXP_ACTION_OPERATOR, }, // MjnMixael //Jump Node Sub-Category { "set-jumpnode-name", OP_JUMP_NODE_SET_JUMPNODE_NAME, 2, 2, SEXP_ACTION_OPERATOR, }, //CommanderDJ { "set-jumpnode-display-name", OP_JUMP_NODE_SET_JUMPNODE_DISPLAY_NAME, 2, 2, SEXP_ACTION_OPERATOR, }, //MjnMixael { "set-jumpnode-color", OP_JUMP_NODE_SET_JUMPNODE_COLOR, 5, 5, SEXP_ACTION_OPERATOR, }, { "set-jumpnode-model", OP_JUMP_NODE_SET_JUMPNODE_MODEL, 3, 3, SEXP_ACTION_OPERATOR, }, { "show-jumpnode", OP_JUMP_NODE_SHOW_JUMPNODE, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "hide-jumpnode", OP_JUMP_NODE_HIDE_JUMPNODE, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, //Special Effects Sub-Category { "set-post-effect", OP_SET_POST_EFFECT, 2, 5, SEXP_ACTION_OPERATOR, }, // Hery { "reset-post-effects", OP_RESET_POST_EFFECTS, 0, 0, SEXP_ACTION_OPERATOR, }, // Goober5000 { "ship-effect", OP_SHIP_EFFECT, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // Valathil { "ship-create", OP_SHIP_CREATE, 5, 10, SEXP_ACTION_OPERATOR, }, // WMC { "prop-create", OP_PROP_CREATE, 5, 8, SEXP_ACTION_OPERATOR, }, // MjnMixael { "weapon-create", OP_WEAPON_CREATE, 5, 10, SEXP_ACTION_OPERATOR, }, // Goober5000 { "ship-vanish", OP_SHIP_VANISH, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "prop-vanish", OP_PROP_VANISH, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // MjnMixael { "ship-vaporize", OP_SHIP_VAPORIZE, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "ship-no-vaporize", OP_SHIP_NO_VAPORIZE, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "set-explosion-option", OP_SET_EXPLOSION_OPTION, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "create-bolt", OP_CREATE_BOLT, 7, 8, SEXP_ACTION_OPERATOR, }, // MjnMixael { "explosion-effect", OP_EXPLOSION_EFFECT, 11, 14, SEXP_ACTION_OPERATOR, }, // Goober5000 { "warp-effect", OP_WARP_EFFECT, 12, 14, SEXP_ACTION_OPERATOR, }, // Goober5000 { "clear-weapons", OP_CLEAR_WEAPONS, 0, 1, SEXP_ACTION_OPERATOR, }, // Karajorma { "clear-debris", OP_CLEAR_DEBRIS, 0, 1, SEXP_ACTION_OPERATOR, }, // Goober5000 { "set-time-compression", OP_CUTSCENES_SET_TIME_COMPRESSION, 1, 3, SEXP_ACTION_OPERATOR, }, { "reset-time-compression", OP_CUTSCENES_RESET_TIME_COMPRESSION, 0, 0, SEXP_ACTION_OPERATOR, }, { "call-ssm-strike", OP_CALL_SSM_STRIKE, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // X3N0-Life-Form { "set-gravity-accel", OP_SET_GRAVITY_ACCEL, 1, 1, SEXP_ACTION_OPERATOR, }, // Asteroth { "force-rearm", OP_FORCE_REARM, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // MjnMixael { "abort-rearm", OP_ABORT_REARM, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // MjnMixael //Variable Category { "modify-variable", OP_MODIFY_VARIABLE, 2, 2, SEXP_ACTION_OPERATOR, }, { "get-variable-by-index", OP_GET_VARIABLE_BY_INDEX, 1, 1, SEXP_INTEGER_OPERATOR, }, // Goober5000 { "set-variable-by-index", OP_SET_VARIABLE_BY_INDEX, 2, 2, SEXP_ACTION_OPERATOR, }, // Goober5000 { "copy-variable-from-index", OP_COPY_VARIABLE_FROM_INDEX, 2, 2, SEXP_ACTION_OPERATOR, }, // Goober5000 { "copy-variable-between-indexes", OP_COPY_VARIABLE_BETWEEN_INDEXES, 2, 2, SEXP_ACTION_OPERATOR, }, // Goober5000 { "int-to-string", OP_INT_TO_STRING, 2, 2, SEXP_ACTION_OPERATOR, }, // Goober5000 { "string-concatenate", OP_STRING_CONCATENATE, 3, 3, SEXP_ACTION_OPERATOR, }, // Goober5000 { "string-concatenate-block", OP_STRING_CONCATENATE_BLOCK, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 { "string-get-substring", OP_STRING_GET_SUBSTRING, 4, 4, SEXP_ACTION_OPERATOR, }, // Goober5000 { "string-set-substring", OP_STRING_SET_SUBSTRING, 5, 5, SEXP_ACTION_OPERATOR, }, // Goober5000 { "modify-variable-xstr", OP_MODIFY_VARIABLE_XSTR, 3, 3, SEXP_ACTION_OPERATOR, }, // m!m //Containers Category { "add-to-list", OP_CONTAINER_ADD_TO_LIST, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // Karajorma { "remove-from-list", OP_CONTAINER_REMOVE_FROM_LIST, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Karajorma { "add-to-map", OP_CONTAINER_ADD_TO_MAP, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // Karajorma { "remove-from-map", OP_CONTAINER_REMOVE_FROM_MAP, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Karajorma { "get-map-keys", OP_CONTAINER_GET_MAP_KEYS, 2, 3, SEXP_ACTION_OPERATOR, }, // Karajorma { "clear-container", OP_CLEAR_CONTAINER, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, // Karajorma { "copy-container", OP_COPY_CONTAINER, 2, 3, SEXP_ACTION_OPERATOR, }, // jg18 { "apply-container-filter", OP_APPLY_CONTAINER_FILTER, 2, 2, SEXP_ACTION_OPERATOR, }, // jg18 //Other Sub-Category { "damaged-escort-priority", OP_DAMAGED_ESCORT_LIST, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, //phreak { "damaged-escort-priority-all", OP_DAMAGED_ESCORT_LIST_ALL, 1, MAX_COMPLETE_ESCORT_LIST, SEXP_ACTION_OPERATOR, }, // Goober5000 { "set-support-ship", OP_SET_SUPPORT_SHIP, 6, 7, SEXP_ACTION_OPERATOR, }, // Goober5000 { "script-eval", OP_SCRIPT_EVAL, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "script-eval-block", OP_SCRIPT_EVAL_BLOCK, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "multi-eval", OP_SCRIPT_EVAL_MULTI, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, { "script-eval-string", OP_SCRIPT_EVAL_STRING, 2, 2, SEXP_ACTION_OPERATOR, }, { "debug", OP_DEBUG, 2, 2, SEXP_ACTION_OPERATOR, }, // Karajorma { "do-nothing", OP_NOP, 0, 0, SEXP_ACTION_OPERATOR, }, //AI Goals Category { "ai-chase", OP_AI_CHASE, 2, 4, SEXP_GOAL_OPERATOR, }, { "ai-chase-wing", OP_AI_CHASE_WING, 2, 4, SEXP_GOAL_OPERATOR, }, { "ai-chase-ship-class", OP_AI_CHASE_SHIP_CLASS, 2, 4, SEXP_GOAL_OPERATOR, }, { "ai-chase-ship-type", OP_AI_CHASE_SHIP_TYPE, 2, 4, SEXP_GOAL_OPERATOR, }, // LuytenKy { "ai-chase-any", OP_AI_CHASE_ANY, 1, 2, SEXP_GOAL_OPERATOR, }, { "ai-guard", OP_AI_GUARD, 2, 3, SEXP_GOAL_OPERATOR, }, { "ai-guard-wing", OP_AI_GUARD_WING, 2, 3, SEXP_GOAL_OPERATOR, }, { "ai-destroy-subsystem", OP_AI_DESTROY_SUBSYS, 3, 5, SEXP_GOAL_OPERATOR, }, { "ai-disable-ship", OP_AI_DISABLE_SHIP, 2, 4, SEXP_GOAL_OPERATOR, }, { "ai-disable-ship-tactical", OP_AI_DISABLE_SHIP_TACTICAL, 2, 4, SEXP_GOAL_OPERATOR, }, { "ai-disarm-ship", OP_AI_DISARM_SHIP, 2, 4, SEXP_GOAL_OPERATOR, }, { "ai-disarm-ship-tactical", OP_AI_DISARM_SHIP_TACTICAL, 2, 4, SEXP_GOAL_OPERATOR, }, { "ai-warp", OP_AI_WARP, 2, 2, SEXP_GOAL_OPERATOR, }, { "ai-warp-out", OP_AI_WARP_OUT, 1, 1, SEXP_GOAL_OPERATOR, }, { "ai-dock", OP_AI_DOCK, 4, 5, SEXP_GOAL_OPERATOR, }, { "ai-undock", OP_AI_UNDOCK, 1, 2, SEXP_GOAL_OPERATOR, }, { "ai-rearm-repair", OP_AI_REARM_REPAIR, 2, 2, SEXP_GOAL_OPERATOR, }, { "ai-waypoints", OP_AI_WAYPOINTS, 2, 5, SEXP_GOAL_OPERATOR, }, { "ai-waypoints-once", OP_AI_WAYPOINTS_ONCE, 2, 5, SEXP_GOAL_OPERATOR, }, { "ai-ignore", OP_AI_IGNORE, 2, 2, SEXP_GOAL_OPERATOR, }, { "ai-ignore-new", OP_AI_IGNORE_NEW, 2, 2, SEXP_GOAL_OPERATOR, }, { "ai-form-on-wing", OP_AI_FORM_ON_WING, 1, 5, SEXP_GOAL_OPERATOR, }, { "ai-fly-to-ship", OP_AI_FLY_TO_SHIP, 2, 5, SEXP_GOAL_OPERATOR, }, { "ai-stay-near-ship", OP_AI_STAY_NEAR_SHIP, 2, 5, SEXP_GOAL_OPERATOR, }, { "ai-evade-ship", OP_AI_EVADE_SHIP, 2, 2, SEXP_GOAL_OPERATOR, }, { "ai-keep-safe-distance", OP_AI_KEEP_SAFE_DISTANCE, 1, 1, SEXP_GOAL_OPERATOR, }, { "ai-stay-still", OP_AI_STAY_STILL, 2, 3, SEXP_GOAL_OPERATOR, }, { "ai-play-dead", OP_AI_PLAY_DEAD, 1, 1, SEXP_GOAL_OPERATOR, }, { "ai-play-dead-persistent", OP_AI_PLAY_DEAD_PERSISTENT, 1, 1, SEXP_GOAL_OPERATOR, }, { "goals", OP_GOALS_ID, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, //Training Category { "key-pressed", OP_KEY_PRESSED, 1, 2, SEXP_BOOLEAN_OPERATOR, }, { "key-reset", OP_KEY_RESET, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "key-reset-multiple", OP_KEY_RESET_MULTIPLE, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, { "ignore-key", OP_IGNORE_KEY, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // Karajorma { "targeted", OP_TARGETED, 1, 3, SEXP_BOOLEAN_OPERATOR, }, { "node-targeted", OP_NODE_TARGETED, 1, 2, SEXP_BOOLEAN_OPERATOR, }, // FUBAR { "missile-locked", OP_MISSILE_LOCKED, 1, 3, SEXP_BOOLEAN_OPERATOR, }, // Sesquipedalian { "speed", OP_SPEED, 1, 1, SEXP_BOOLEAN_OPERATOR, }, { "facing", OP_FACING, 2, 2, SEXP_BOOLEAN_OPERATOR, }, { "facing-waypoint", OP_FACING2, 2, 2, SEXP_BOOLEAN_OPERATOR, }, { "order", OP_ORDER, 2, 3, SEXP_BOOLEAN_OPERATOR, }, { "query-orders", OP_QUERY_ORDERS, 3, 6, SEXP_BOOLEAN_OPERATOR, }, // Karajorma { "reset-orders", OP_RESET_ORDERS, 0, 0, SEXP_ACTION_OPERATOR, }, // Karajorma { "waypoint-missed", OP_WAYPOINT_MISSED, 0, 0, SEXP_BOOLEAN_OPERATOR, }, { "waypoint-twice", OP_WAYPOINT_TWICE, 0, 0, SEXP_BOOLEAN_OPERATOR, }, { "path-flown", OP_PATH_FLOWN, 0, 0, SEXP_BOOLEAN_OPERATOR, }, { "training-msg", OP_TRAINING_MSG, 1, 4, SEXP_ACTION_OPERATOR, }, { "flash-hud-gauge", OP_FLASH_HUD_GAUGE, 1, 1, SEXP_ACTION_OPERATOR, }, { "primaries-depleted", OP_PRIMARIES_DEPLETED, 1, 1, SEXP_BOOLEAN_OPERATOR, }, { "secondaries-depleted", OP_SECONDARIES_DEPLETED, 1, 1, SEXP_BOOLEAN_OPERATOR, }, { "special-check", OP_SPECIAL_CHECK, 1, 1, SEXP_ACTION_OPERATOR, }, { "set-training-context-fly-path", OP_SET_TRAINING_CONTEXT_FLY_PATH, 2, 2, SEXP_ACTION_OPERATOR, }, { "set-training-context-speed", OP_SET_TRAINING_CONTEXT_SPEED, 2, 2, SEXP_ACTION_OPERATOR, }, }; SCP_vector Sorted_operator_indexes; size_t Max_operator_length = 0; sexp_ai_goal_link Sexp_ai_goal_links[] = { { AI_GOAL_CHASE, OP_AI_CHASE }, { AI_GOAL_CHASE_WING, OP_AI_CHASE_WING }, { AI_GOAL_CHASE_SHIP_CLASS, OP_AI_CHASE_SHIP_CLASS }, { AI_GOAL_CHASE_SHIP_TYPE, OP_AI_CHASE_SHIP_TYPE}, { AI_GOAL_CHASE_ANY, OP_AI_CHASE_ANY }, { 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_DISABLE_SHIP_TACTICAL, OP_AI_DISABLE_SHIP_TACTICAL }, { AI_GOAL_DISARM_SHIP, OP_AI_DISARM_SHIP }, { AI_GOAL_DISARM_SHIP_TACTICAL, OP_AI_DISARM_SHIP_TACTICAL }, { AI_GOAL_GUARD, OP_AI_GUARD }, { 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_IGNORE_NEW, OP_AI_IGNORE_NEW }, { AI_GOAL_STAY_STILL, OP_AI_STAY_STILL }, { AI_GOAL_PLAY_DEAD, OP_AI_PLAY_DEAD }, { AI_GOAL_PLAY_DEAD_PERSISTENT, OP_AI_PLAY_DEAD_PERSISTENT }, { AI_GOAL_FORM_ON_WING, OP_AI_FORM_ON_WING }, { AI_GOAL_FLY_TO_SHIP, OP_AI_FLY_TO_SHIP }, { AI_GOAL_REARM_REPAIR, OP_AI_REARM_REPAIR }, }; SCP_vector Dynamic_enums; SCP_vector Dynamic_parameters; SCP_vector Dynamic_enum_suffixes; int get_dynamic_parameter_index(const std::string& op_name, int param) { auto it = std::find_if(Dynamic_parameters.begin(), Dynamic_parameters.end(), [&op_name](const auto& this_param) { return lcase_equal(this_param.operator_name, op_name); }); if (it != Dynamic_parameters.end()) { for (const auto& param_pair : it->parameter_map) { if (param == param_pair.first) { // If it's a pos number return it, else return the previous param inde return param_pair.second >= 0 ? param_pair.second : param - 1; } } // If we don't have an exact match then we're probably in $Repeat territory so let's return the last parent // parameter in the list. const auto& last_param_pair = it->parameter_map.back(); // If it's a pos number return it, else return the previous param inde return last_param_pair.second >= 0 ? last_param_pair.second : param - 1; } // Didn't find anything. return -1; } // Gets the custom suffix to append when using child enums. If we have a match // for operator name and parameter index, then return the suffix. Otherwise return // an empty string SCP_string get_child_enum_suffix(const SCP_string& op_name, int param_index) { for (size_t i = 0; i < Dynamic_enum_suffixes.size(); i++) { if (lcase_equal(Dynamic_enum_suffixes[i].operator_name, op_name)) { if (Dynamic_enum_suffixes[i].param_index == param_index) { return Dynamic_enum_suffixes[i].suffix; } } } return ""; } int get_dynamic_enum_position(const SCP_string &enum_name) { for (int i = 0; i < (int)Dynamic_enums.size(); i++) { if (lcase_equal(enum_name, Dynamic_enums[i].name)) { return i; } } // Didn't find anything. return -1; } void sexp_set_skybox_model_preload(const char *name); // taylor int Num_skybox_flags = 6; const char *Skybox_flags[] = { "force-clamp", "add-lighting", "no-transparency", "add-zbuffer", "add-culling", "no-glowmaps", }; int Directive_count; int Sexp_useful_number = 1; // a variable to pass useful info in from external modules bool Assume_event_is_current = true; int Locked_sexp_true = -1; int Locked_sexp_false = -1; int Num_sexp_ai_goal_links = sizeof(Sexp_ai_goal_links) / sizeof(sexp_ai_goal_link); int Sexp_clipboard = -1; // used by Fred // If you edit this, make sure this is greater than zero, // so that we don't have to write pointless asserts. :) #define SEXP_NODE_INCREMENT 250 int Num_sexp_nodes = 0; sexp_node *Sexp_nodes = nullptr; sexp_variable Sexp_variables[MAX_SEXP_VARIABLES]; sexp_variable Block_variables[MAX_SEXP_VARIABLES]; // used for compatibility with retail. int Num_special_expl_blocks; SCP_vector Current_sexp_operator; // for sexp_fade static int Fade_out_r = 0; static int Fade_out_g = 0; static int Fade_out_b = 0; // for is-true-for-duration SCP_vector Sexp_is_true_for_duration_times; // for play-music - Goober5000 SCP_vector Sexp_music_handles; // All handles used by all invocations of play-sound-from-file. The default handle is in index 0. // for sound environments - Goober5000/Taylor #define SEO_VOLUME 0 #define SEO_DECAY_TIME 1 #define SEO_DAMPING 2 int sexp_sound_environment_option_lookup(const char *text); const char *Sound_environment_option[] = { "volume", "decay time", "damping" }; int Num_sound_environment_options = 3; // for adjust-audio-volume - The E const char *Adjust_audio_options[] = { "Music", "Voice", "Effects" }; int Num_adjust_audio_options = 3; int audio_volume_option_lookup(const char *text); int hud_gauge_type_lookup(const char* name); // for explosions - Goober5000 #define EO_DAMAGE 0 #define EO_BLAST 1 #define EO_INNER_RADIUS 2 #define EO_OUTER_RADIUS 3 #define EO_SHOCKWAVE_SPEED 4 #define EO_DEATH_ROLL_TIME 5 int sexp_explosion_option_lookup(const char *text); const char *Explosion_option[] = { "damage", "blast", "inner radius", "outer radius", "shockwave speed", "death roll time" }; int Num_explosion_options = 6; #define FWET_RETVAL_FIRST 0 #define FWET_RETVAL_LAST 1 int sexp_functional_when_eval_type_lookup(const char *text); const char *Functional_when_eval_type[] = { "return-value-first", "return-value-last" }; int Num_functional_when_eval_types = 2; int get_sexp(); void build_extended_sexp_string(SCP_string &accumulator, int cur_node, int level, int mode); void update_sexp_references(const char *old_name, const char *new_name, int format, int node); int sexp_determine_team(const char *subj); void init_sexp_vars(); // for handling variables void add_block_variable(const char *text, const char *var_name, int type, int index); void sexp_modify_variable(int node); int sexp_get_variable_by_index(int node); void sexp_set_variable_by_index(int node); void sexp_copy_variable_from_index(int node); void sexp_copy_variable_between_indexes(int node); int verify_vector(const char *text); bool is_descendant_of_when_argument_op(int node); #define ARG_ITEM_F_DUP (1<<0) // Goober5000 - adapted from sexp_list_item in Sexp_tree.h struct arg_item { const char *text = nullptr; int node = -1; arg_item *next = nullptr; int flags = 0; int nesting_level = 0; arg_item() = default; void add_data(const char *str, int n); void add_data(const std::pair &data); void add_data_dup(const char *str, int n); void add_data_dup(const std::pair &data); void add_data_set_dup(const char *str, int n); void add_data_set_dup(const std::pair &data); void expunge(); int is_empty(); arg_item *get_next(); void clear_nesting_level(); }; arg_item Sexp_applicable_argument_list; SCP_vector> Sexp_replacement_arguments; int Sexp_current_argument_nesting_level; //Karajorma int get_generic_subsys(const char *subsys_name); bool ship_class_unchanged(const ship_registry_entry *ship_entry); void multi_sexp_modify_variable(); // jg18 // container_value_index is for using one specific value from a container int copy_node_to_replacement_args(int node, int container_value_index = -1); // checks whether the node's value can change over the course of the mission bool is_node_value_dynamic(int node); // returns 0 on success or a SEXP_CHECK_* value on failure int check_dynamic_value_node_type(int node, bool is_string, bool is_number); #define NO_OPERATOR_INDEX_DEFINED -2 #define NOT_A_SEXP_OPERATOR -1 // hud-display-gauge magic values #define SEXP_HUD_GAUGE_WARPOUT "warpout" // event log stuff SCP_vector *Current_event_log_buffer; SCP_vector *Current_event_log_variable_buffer; SCP_vector *Current_event_log_container_buffer; SCP_vector *Current_event_log_argument_buffer; // Goober5000 - arg_item class stuff, borrowed from sexp_list_item class stuff ------------- void arg_item::add_data(const char *str, int n) { arg_item *item, *ptr; // create item item = new arg_item(); item->text = str; item->node = n; item->nesting_level = Sexp_current_argument_nesting_level; // prepend item to existing list ptr = this->next; this->next = item; item->next = ptr; } void arg_item::add_data(const std::pair &data) { add_data(data.first, data.second); } void arg_item::add_data_dup(const char *str, int n) { arg_item *item, *ptr; // create item item = new arg_item(); item->text = vm_strdup(str); item->flags |= ARG_ITEM_F_DUP; item->node = n; item->nesting_level = Sexp_current_argument_nesting_level; // prepend item to existing list ptr = this->next; this->next = item; item->next = ptr; } void arg_item::add_data_dup(const std::pair &data) { add_data_dup(data.first, data.second); } void arg_item::add_data_set_dup(const char *str, int n) { arg_item *item, *ptr; // create item item = new arg_item(); item->text = str; item->flags |= ARG_ITEM_F_DUP; item->node = n; item->nesting_level = Sexp_current_argument_nesting_level; // prepend item to existing list ptr = this->next; this->next = item; item->next = ptr; } void arg_item::add_data_set_dup(const std::pair &data) { add_data_set_dup(data.first, data.second); } arg_item* arg_item::get_next() { if (this->next != nullptr) { if (this->next->nesting_level >= Sexp_current_argument_nesting_level) { return this->next; } } return nullptr; } void arg_item::expunge() { arg_item *ptr; // contiually delete first item of list while (this->next != nullptr) { ptr = this->next->next; if (this->next->flags & ARG_ITEM_F_DUP) vm_free(const_cast(this->next->text)); delete this->next; this->next = ptr; } } void arg_item::clear_nesting_level() { arg_item *ptr; // contiually delete first item of list while (this->next != nullptr && this->next->nesting_level >= Sexp_current_argument_nesting_level ) { ptr = this->next->next; if (this->next->flags & ARG_ITEM_F_DUP) vm_free(const_cast(this->next->text)); delete this->next; this->next = ptr; } } int arg_item::is_empty() { return (this->next == nullptr); } //------------------------------------------------------------------------------------------------- void clear_cache(int node) { // free anything cached Sexp_nodes[node].cache.reset(); // note that cached_variable_index is not reset here because it is a parallel cache (c.f. sexp_get_variable_index) } void sexp_nodes_init() { if (Num_sexp_nodes == 0 || Sexp_nodes == nullptr) return; nprintf(("SEXP", "Reinitializing sexp nodes...\n")); nprintf(("SEXP", "Entered function with %d nodes.\n", Num_sexp_nodes)); // usually, the persistent nodes are grouped at the beginning of the array; // so we ought to be able to free all the subsequent nodes int i, last_persistent_node = -1; for (i = 0; i < Num_sexp_nodes; i++) { if (Sexp_nodes[i].type & SEXP_FLAG_PERSISTENT) last_persistent_node = i; // keep track of it else Sexp_nodes[i].type = SEXP_NOT_USED; // it's not needed // free anything cached clear_cache(i); } nprintf(("SEXP", "Last persistent node index is %d.\n", last_persistent_node)); // if all the persistent nodes are gone, free all the nodes if (last_persistent_node == -1) { vm_free(Sexp_nodes); Sexp_nodes = nullptr; Num_sexp_nodes = 0; } // if there's enough of a difference to make it worthwhile, free some nodes else if (Num_sexp_nodes - (last_persistent_node + 1) > 2 * SEXP_NODE_INCREMENT) { // round it up to the next evenly divisible size Num_sexp_nodes = (last_persistent_node + 1); Num_sexp_nodes += SEXP_NODE_INCREMENT - (Num_sexp_nodes % SEXP_NODE_INCREMENT); Sexp_nodes = (sexp_node *) vm_realloc(Sexp_nodes, sizeof(sexp_node) * Num_sexp_nodes); Verify(Sexp_nodes != nullptr); } nprintf(("SEXP", "Exited function with %d nodes.\n", Num_sexp_nodes)); } static void sexp_nodes_close() { // free all sexp nodes... should only be done on game shutdown if (Sexp_nodes != nullptr) { // free anything cached for (int i = 0; i < Num_sexp_nodes; i++) clear_cache(i); vm_free(Sexp_nodes); Sexp_nodes = nullptr; Num_sexp_nodes = 0; } } // done at the beginning of each mission void init_sexp() { // Goober5000 Sexp_replacement_arguments.clear(); Sexp_applicable_argument_list.expunge(); Sexp_current_argument_nesting_level = 0; Current_sexp_network_packet.initialize(); sexp_nodes_init(); init_sexp_vars(); init_sexp_containers(); 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 Sexp_nodes[Locked_sexp_false].value = SEXP_KNOWN_FALSE; 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 Sexp_nodes[Locked_sexp_true].value = SEXP_KNOWN_TRUE; // init fade_out colors Fade_out_r = 0; Fade_out_g = 0; Fade_out_b = 0; // init data structures used by certain operators // (note, Sexp_music_handles are not cleared here because sexp_music_close() handled that at the end of the previous mission) Sexp_is_true_for_duration_times.clear(); } // done at the beginning of the game void sexp_startup() { #ifndef NDEBUG // sanity check categories and subcategories for all operators for (const auto& op : Operators) { int subcategory = get_subcategory(op.value); if (subcategory != OP_SUBCATEGORY_NONE) { int category = get_category(op.value); int implied_category = category_of_subcategory(subcategory); Assertion(category == implied_category, "Operator %s has a category that is not a parent of its subcategory!", op.text.c_str()); } } #endif sexp::dynamic_sexp_init(); // sort all operators case-insensitively by name Sorted_operator_indexes.clear(); Sorted_operator_indexes.reserve(Operators.size()); Max_operator_length = 0; for (int i = 0; i < (int)Operators.size(); ++i) { Sorted_operator_indexes.push_back(i); auto len = Operators[i].text.length(); if (len > Max_operator_length) Max_operator_length = len; } std::sort(Sorted_operator_indexes.begin(), Sorted_operator_indexes.end(), [](const int &index_a, const int &index_b) { const auto &op_a = Operators[index_a]; const auto &op_b = Operators[index_b]; return lcase_lessthan(op_a.text, op_b.text); }); } void sexp_shutdown() { sexp_nodes_close(); sexp::dynamic_sexp_shutdown(); } /** * Allocate an sexp node. */ int alloc_sexp(const char *text, int type, int subtype, int first, int rest) { int node; int sexp_const = get_operator_const(text); if ((sexp_const == OP_TRUE) && (type == SEXP_ATOM) && (subtype == SEXP_ATOM_OPERATOR)) return Locked_sexp_true; else if ((sexp_const == OP_FALSE) && (type == SEXP_ATOM) && (subtype == SEXP_ATOM_OPERATOR)) return Locked_sexp_false; node = find_free_sexp(); // need more sexp nodes? if (node == Num_sexp_nodes || node == -1) { int old_size = Num_sexp_nodes; // allocate in blocks of SEXP_NODE_INCREMENT Num_sexp_nodes += SEXP_NODE_INCREMENT; Sexp_nodes = (sexp_node *) vm_realloc(Sexp_nodes, sizeof(sexp_node) * Num_sexp_nodes); Verify(Sexp_nodes != nullptr); nprintf(("SEXP", "Bumping dynamic sexp node limit from %d to %d...\n", old_size, Num_sexp_nodes)); // initialize all the new sexp nodes we just allocated for (int i = old_size; i < Num_sexp_nodes; i++) new (&Sexp_nodes[i]) sexp_node(); // our new sexp is the first out of the ones we just created node = old_size; } Assert(node != Locked_sexp_true); Assert(node != Locked_sexp_false); Assert(strlen(text) < TOKEN_LENGTH); Assert(type >= 0); strcpy_s(Sexp_nodes[node].text, text); Sexp_nodes[node].type = type; Sexp_nodes[node].subtype = subtype; Sexp_nodes[node].first = first; Sexp_nodes[node].rest = rest; Sexp_nodes[node].value = SEXP_UNKNOWN; Sexp_nodes[node].flags = SNF_DEFAULT_VALUE; Sexp_nodes[node].op_index = NO_OPERATOR_INDEX_DEFINED; Sexp_nodes[node].cache.reset(); Sexp_nodes[node].cached_variable_index = -1; Sexp_nodes[node].duration_index = -1; // special-arg? if (type == SEXP_ATOM && !strcmp(text, SEXP_ARGUMENT_STRING)) Sexp_nodes[node].flags |= SNF_SPECIAL_ARG_IN_NODE; return node; } static int Sexp_hwm = 0; int count_free_sexp_nodes() { int i, f = 0, p = 0; for (i = 0; i < Num_sexp_nodes; i++) { if (Sexp_nodes[i].type == SEXP_NOT_USED) f++; else if (Sexp_nodes[i].type & SEXP_FLAG_PERSISTENT) p++; } if (Num_sexp_nodes - f > Sexp_hwm) { nprintf(("Sexp", "Sexp nodes: Free=%d, Used=%d, Persistent=%d\n", f, Num_sexp_nodes - f, p)); Sexp_hwm = Num_sexp_nodes - f; } return f; } /** * Find the next free sexp and return its index. */ int find_free_sexp() { int i; // sanity if (Num_sexp_nodes == 0 || Sexp_nodes == nullptr) return -1; for (i = 0; i < Num_sexp_nodes; i++) { if (Sexp_nodes[i].type == SEXP_NOT_USED) return i; } return -1; } /** * Mark 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); } /** * Remove the persistent flag from all nodes in the tree */ void sexp_unmark_persistent(int n) { if (n == -1){ return; } // see sexp_mark_persistent 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); } /** * Free up the specified sexp node, Leaves link chains untouched. */ int free_one_sexp(int num) { Assert(Fred_running); Assert((num >= 0) && (num < Num_sexp_nodes)); Assert(Sexp_nodes[num].type != SEXP_NOT_USED); // make sure it is actually used Assert(!(Sexp_nodes[num].type & SEXP_FLAG_PERSISTENT)); // never free these nodes if ((num == Locked_sexp_true) || (num == Locked_sexp_false)) return 0; Sexp_nodes[num].type = SEXP_NOT_USED; clear_cache(num); return 1; } /** * Free 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). * calling_node defaults to -1 */ int free_sexp(int num, int calling_node) { int i, rest, count = 0; Assert((num >= 0) && (num < Num_sexp_nodes)); Assert(Sexp_nodes[num].type != SEXP_NOT_USED); // make sure it is actually used Assert(!(Sexp_nodes[num].type & SEXP_FLAG_PERSISTENT)); // never free these nodes if ((num == -1) || (num == Locked_sexp_true) || (num == Locked_sexp_false)) return 0; Sexp_nodes[num].type = SEXP_NOT_USED; clear_cache(num); count++; i = Sexp_nodes[num].first; while (i != -1) { rest = Sexp_nodes[i].rest; count += free_sexp(i, num); i = rest; } rest = Sexp_nodes[num].rest; if (calling_node >= 0) { if (Sexp_nodes[calling_node].first == num) Sexp_nodes[calling_node].first = rest; if (Sexp_nodes[calling_node].rest == num) Sexp_nodes[calling_node].rest = rest; } return count; // total elements freed up. } /** * 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 calling_node) { int i, rest, count = 0; Assert((num >= 0) && (num < Num_sexp_nodes)); Assert(Sexp_nodes[num].type != SEXP_NOT_USED); // make sure it is actually used Assert(!(Sexp_nodes[num].type & SEXP_FLAG_PERSISTENT)); // never free these nodes if ((num == -1) || (num == Locked_sexp_true) || (num == Locked_sexp_false)){ return 0; } i = Sexp_nodes[num].rest; while (i != -1) { rest = Sexp_nodes[i].rest; count += free_sexp(i, num); i = rest; } count += free_sexp(num, calling_node); return count; } /** * Reset the status of all the nodes in a tree, forcing them to all be evaluated again. */ void flush_sexp_tree(int node) { if (node < 0){ return; } Sexp_nodes[node].value = SEXP_UNKNOWN; clear_cache(node); Sexp_nodes[node].cached_variable_index = -1; Sexp_nodes[node].duration_index = -1; 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; } /** * @todo CASE OF SEXP VARIABLES - ONLY 1 COPY OF VARIABLE */ 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; } /** * Compare SEXP chains * @return 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) != 0){ 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 < Num_sexp_nodes; i++) { if (Sexp_nodes[i].first == num) return i; } // not found return -1; } /** * Find node of operator that item is an argument of. */ int find_parent_operator(int node) { int i; Assert((node >= 0) && (node < Num_sexp_nodes)); if (Sexp_nodes[node].subtype == SEXP_ATOM_OPERATOR) { node = find_sexp_list(node); // are we already at the top of the list? this will happen for non-standard sexps // (sexps that fire instantly instead of using a conditional) such as: // $Formula: ( do-nothing ) if (node < 0) return -1; } // iterate backwards through the sexps nodes (i.e. do the inverse of CDR) while (Sexp_nodes[node].subtype != SEXP_ATOM_OPERATOR) { for (i = 0; i < Num_sexp_nodes; i++) { if (Sexp_nodes[i].rest == node) break; } if (i == Num_sexp_nodes) return -1; // not found, probably at top node already. node = i; } return node; } /** * 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; Assert((node >= 0) && (node < Num_sexp_nodes)); if (Sexp_nodes[node].type == SEXP_NOT_USED) return 0; for (i = 0; i < Num_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; } /** * Find argument number */ int find_argnum(int parent_node, int arg_node) { int n, tally; Assertion((parent_node >= 0) && (parent_node < Num_sexp_nodes), "find_argnum was passed an invalid parent!"); Assertion((arg_node >= 0) && (arg_node < Num_sexp_nodes), "find_argnum was passed an invalid child!"); n = CDR(parent_node); tally = 0; while (n >= 0) { // check if there is an operator node at this position that matches our expected node if (CAR(n) == arg_node) return tally; // check if there is a regular node at this position that matches our expected node if (n == arg_node) return tally; tally++; n = CDR(n); } // argument node not found return -1; } /** * From an operator name, return its index in the array Operators */ int get_operator_index(const char *token) { Assertion(token != nullptr, "get_operator_index(char*) called with a null token; get a coder!\n"); for (size_t i=0; i < Operators.size(); i++){ if (Operators[i].text == token){ return (int)i; } } return NOT_A_SEXP_OPERATOR; } /** * From a sexp node, return the index in the array Operators or NOT_A_SEXP_OPERATOR if not an operator */ int get_operator_index(int node) { Assertion(node >= 0 && node < Num_sexp_nodes, "Passed an out-of-range node index (%d) to get_operator_index(int)!", node); if (node < 0 || node >= Num_sexp_nodes) return NOT_A_SEXP_OPERATOR; if (!Fred_running && (Sexp_nodes[node].op_index != NO_OPERATOR_INDEX_DEFINED) ) { return Sexp_nodes[node].op_index; } int index = get_operator_index(Sexp_nodes[node].text); Sexp_nodes[node].op_index = index; return index; } /** * From an operator name, return its constant (the number it was define'd with) */ int get_operator_const(const char *token) { int idx = get_operator_index(token); if (idx == NOT_A_SEXP_OPERATOR) return OP_NOT_AN_OP; return Operators[idx].value; } int get_operator_const(int node) { Assertion(node >= 0 && node < Num_sexp_nodes, "Passed an out-of-range node index (%d) to get_operator_const(int)!", node); if (node < 0 || node >= Num_sexp_nodes) return OP_NOT_AN_OP; if (!Fred_running && Sexp_nodes[node].op_index >= 0) { return Operators[Sexp_nodes[node].op_index].value; } int idx = get_operator_index(node); if (idx == NOT_A_SEXP_OPERATOR) return OP_NOT_AN_OP; return Operators[idx].value; } int find_operator_index(int op_const) { for (int i = 0; i < (int)Operators.size(); ++i) if (Operators[i].value == op_const) return i; return -1; } int query_sexp_args_count(int node, bool only_valid_args = false) { int count = 0; int n = CDR(node); for ( ; n != -1; n = CDR(n)) { if (only_valid_args && !(Sexp_nodes[n].flags & SNF_ARGUMENT_VALID)) continue; count++; } return count; } enum class ArgCountCheck { CORRECT, INCORRECT_BENIGN, INCORRECT_FATAL }; /** * Needed to fix bug with sexps like send-message list which have arguments that need to be supplied as a block * * @return whether the number of arguments for the supplied operation is correct */ static ArgCountCheck check_operator_argument_count(int count, int op_index) { Assertion(op_index >= 0 && op_index < sz2i(Operators.size()), "op_index is out of range!"); if (count < Operators[op_index].min || count > Operators[op_index].max) return ArgCountCheck::INCORRECT_FATAL; int op_const = Operators[op_index].value; // send-message-list has arguments as blocks of 4 // same with send-message-chain, but there's an extra argument if (op_const == OP_SEND_MESSAGE_CHAIN) count--; if (op_const == OP_SEND_MESSAGE_LIST || op_const == OP_SEND_MESSAGE_CHAIN) if (count % 4 != 0) return ArgCountCheck::INCORRECT_BENIGN; // historically, this check didn't work at all, and sexps gracefully recovered at runtime return ArgCountCheck::CORRECT; } // helper functions for check_container_value_data_type() bool check_container_data_sexp_arg_type(ContainerType con_type, bool is_string, bool is_number) { if (any(con_type & ContainerType::STRING_DATA)) { return is_string; } else if (any(con_type & ContainerType::NUMBER_DATA)) { return is_number; } else { UNREACHABLE("Unknown container data type %d", static_cast(con_type)); return false; } } bool check_map_container_key_sexp_arg_type(ContainerType con_type, bool is_string, bool is_number) { if (any(con_type & ContainerType::STRING_KEYS)) { return is_string; } else if (any(con_type & ContainerType::NUMBER_KEYS)) { return is_number; } else { UNREACHABLE("Unknown map container key type %d", static_cast(con_type)); return false; } } // Checks "container value" SEXP arg type // follows return value conventions of check_sexp_syntax() int check_container_value_data_type(int op, int argnum, ContainerType con_type, bool is_string, bool is_number) { switch (op) { case OP_LIST_HAS_DATA: case OP_LIST_DATA_INDEX: case OP_MAP_HAS_DATA_ITEM: case OP_CONTAINER_ADD_TO_LIST: case OP_CONTAINER_REMOVE_FROM_LIST: if (!check_container_data_sexp_arg_type(con_type, is_string, is_number)) { return SEXP_CHECK_WRONG_CONTAINER_DATA_TYPE; } break; case OP_MAP_HAS_KEY: case OP_CONTAINER_REMOVE_FROM_MAP: Assertion(any(con_type & ContainerType::MAP), "Attempt to check map SEXP with non-map container type %d. Please report!", (int)con_type); if (!check_map_container_key_sexp_arg_type(con_type, is_string, is_number)) { return SEXP_CHECK_WRONG_MAP_KEY_TYPE; } break; case OP_CONTAINER_ADD_TO_MAP: Assertion(any(con_type & ContainerType::MAP), "Attempt to check map SEXP with non-map container type %d. Please report!", (int)con_type); // since the first arg is a map container name, the first key is at argnum 1 if (argnum % 2 != 0) { // map key if (!check_map_container_key_sexp_arg_type(con_type, is_string, is_number)) { return SEXP_CHECK_WRONG_MAP_KEY_TYPE; } } else { // map data if (!check_container_data_sexp_arg_type(con_type, is_string, is_number)) { return SEXP_CHECK_WRONG_CONTAINER_DATA_TYPE; } } break; default: UNREACHABLE("Unhandled container value-based operator %d", op); break; } return 0; } // SEXP type checking for variables and container data bool check_data_type(int type, bool is_string, bool is_number) { switch (type) { case OPF_NUMBER: case OPF_POSITIVE: return is_number; case OPF_AMBIGUOUS: case OPF_GAME_SND: case OPF_FIREBALL: case OPF_WEAPON_BANK_NUMBER: // either type is ok return true; default: return is_string; } } bool check_container_data_type(int type, ContainerType con_type, int op, int argnum, const sexp_container *p_value_container) { const bool is_string = any(con_type & ContainerType::STRING_DATA); const bool is_number = any(con_type & ContainerType::NUMBER_DATA); if (type == OPF_CONTAINER_VALUE) { Assertion(p_value_container, "Attempt to check data type of null container for SEXP operator %d at arg %d. Please report!", op, argnum); return (check_container_value_data_type(op, argnum, p_value_container->type, is_string, is_number) == 0); } else { return check_data_type(type, is_string, is_number); } } bool check_variable_data_type(int type, int var_type, int op, int argnum, const sexp_container *p_value_container) { const bool is_string = (var_type & SEXP_VARIABLE_STRING); const bool is_number = (var_type & SEXP_VARIABLE_NUMBER); if (type == OPF_CONTAINER_VALUE) { Assertion(p_value_container, "Attempt to check data type of null container for SEXP operator %d for variable at arg %d. Please " "report!", op, argnum); return (check_container_value_data_type(op, argnum, p_value_container->type, is_string, is_number) == 0); } else { return check_data_type(type, is_string, is_number); } } // whether a variable reference node's format (quoted or not) contradicts the variable's declared type static bool is_variable_node_type_mismatched(int node) { if (!(Sexp_nodes[node].type & SEXP_FLAG_VARIABLE)) return false; if (Sexp_nodes[node].flags & SNF_VARIABLE_TYPE_MISMATCH) return true; int var_index = sexp_get_variable_index(node); if (var_index < 0) return false; // handled by SEXP_CHECK_INVALID_VARIABLE bool is_number = (Sexp_variables[var_index].type & SEXP_VARIABLE_NUMBER) != 0; return (Sexp_nodes[node].subtype == SEXP_ATOM_NUMBER) != is_number; } static bool is_special_sender(const char* name) { return name[0] == '#'; } /** * Check SEXP syntax * @return 0 if ok, negative if there's an error in expression.. * See the returns types in sexp.h */ int check_sexp_syntax(int node, int desired_return_type, int recursive, int *bad_node, sexp_mode mode) { int i = 0, z, argnum = 0, desired_argument_type = OPF_NONE, node_subtype = -1, node_return_type = OPR_NONE; size_t count; int op_node; int var_index = -1; size_t st; const sexp_container *p_container = nullptr; // for SEXPs that take container name as arg int deferred_error = SEXP_CHECK_NO_ERROR, deferred_bad_node = -1; // for recoverable errors, so that the rest of the tree is still checked Assertion(node >= 0 && node < Num_sexp_nodes, "Node %d must be a valid SEXP node!", node); Assertion(Sexp_nodes[node].type != SEXP_NOT_USED, "Node %d must be in use!", node); op_node = node; // save the node of the operator since we need to get to other args. if (bad_node) *bad_node = op_node; if (Sexp_nodes[node].subtype == SEXP_ATOM_NUMBER && desired_return_type == OPR_BOOL) { // special case Mark seems to want supported Assert(Sexp_nodes[node].first == -1); // only lists should have a first pointer if (Sexp_nodes[node].rest != -1) // anything after the number? return SEXP_CHECK_NONOP_ARGS; // if so, it's a syntax error return 0; } if (Sexp_nodes[op_node].subtype != SEXP_ATOM_OPERATOR) return SEXP_CHECK_OP_EXPECTED; // not an operator, which it should always be int op_index = get_operator_index(op_node); int op_const = SCP_vector_inbounds(Operators, op_index) ? Operators[op_index].value : OP_NOT_AN_OP; if (op_const == OP_NOT_AN_OP) return SEXP_CHECK_UNKNOWN_OP; // unrecognized operator // check that types match - except that OPR_AMBIGUOUS matches everything if (desired_return_type != OPR_AMBIGUOUS) { // get the return type of the next thing z = query_operator_return_type(op_const); if (z == OPR_POSITIVE && desired_return_type == OPR_NUMBER) { // positive data type can map to number data type just fine } // Goober5000's number hack else if (z == OPR_NUMBER && desired_return_type == OPR_POSITIVE) { // this isn't kosher, but we hack it to make it work } else if (z != desired_return_type) { // anything else is a mismatch return SEXP_CHECK_TYPE_MISMATCH; } } count = query_sexp_args_count(op_node); auto arg_count_result = check_operator_argument_count(sz2i(count), op_index); if (arg_count_result == ArgCountCheck::INCORRECT_FATAL) return SEXP_CHECK_BAD_ARG_COUNT; // incorrect number of arguments else if (arg_count_result == ArgCountCheck::INCORRECT_BENIGN) { // incorrect, but defer it and continue checking deferred_error = SEXP_CHECK_BAD_ARG_COUNT_BENIGN; deferred_bad_node = op_node; } node = Sexp_nodes[op_node].rest; while (node != -1) { desired_argument_type = query_operator_argument_type(op_index, argnum); Assertion(Sexp_nodes[node].type != SEXP_NOT_USED, "Node %d must be in use!", node); if (bad_node) *bad_node = node; node_subtype = Sexp_nodes[node].subtype; // check a node's variable type, but if there's a mismatch, defer it so that the rest of the tree is still checked if (deferred_error == SEXP_CHECK_NO_ERROR && is_variable_node_type_mismatched(node)) { deferred_error = SEXP_CHECK_VARIABLE_TYPE_MISMATCH; deferred_bad_node = node; } if (node_subtype == SEXP_ATOM_LIST) { i = Sexp_nodes[node].first; if (bad_node) *bad_node = 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) break; int op2_index = get_operator_index(i); int op2_const = SCP_vector_inbounds(Operators, op2_index) ? Operators[op2_index].value : OP_NOT_AN_OP; if (op2_const == OP_NOT_AN_OP) return SEXP_CHECK_UNKNOWN_OP; node_return_type = query_operator_return_type(op2_const); if (recursive) { sexp_opr_t opr; if (!map_opf_to_opr((sexp_opf_t)desired_argument_type, opr)) { return SEXP_CHECK_UNKNOWN_TYPE; } if ((z = check_sexp_syntax(i, (int)opr, recursive, bad_node)) != 0) { if (!sexp_recoverable_error(z)) return z; // defer recoverable errors so that the rest of the tree is still checked if (deferred_error == SEXP_CHECK_NO_ERROR) { deferred_error = z; deferred_bad_node = bad_node ? *bad_node : -1; } } } } else if (node_subtype == SEXP_ATOM_OPERATOR) { return SEXP_CHECK_DATA_EXPECTED; // operators should not be found here } else if (node_subtype == SEXP_ATOM_NUMBER) { node_return_type = OPR_POSITIVE; auto ptr = CTEXT(node); if (*ptr == '-') { node_return_type = OPR_NUMBER; ptr++; } else if (*ptr == '+') { ptr++; } if (desired_argument_type == OPF_BOOL) // allow numbers to be used where boolean is required. node_return_type = OPR_BOOL; // Only check that this is a number if it's not . if (!(Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_NODE)) { while (*ptr) { if (*ptr == '.' || *ptr == ',') return SEXP_CHECK_MUST_BE_INTEGER; if (!isdigit(*ptr)) return SEXP_CHECK_INVALID_NUM; // not a valid number ptr++; } i = atoi(CTEXT(node)); if ( (op_const == OP_HAS_DOCKED_DELAY) || (op_const == OP_HAS_UNDOCKED_DELAY) ) if ( (argnum == 2) && (i < 1) ) return SEXP_CHECK_NUM_RANGE_INVALID; // valid color range 0 to 255 - FUBAR if ((op_const == OP_CHANGE_IFF_COLOR) && ((argnum >= 2) && (argnum <= 4))) { if ( i < 0 || i > 255) { return SEXP_CHECK_NUM_RANGE_INVALID; } } if ( (query_operator_return_type(op_const) == OPR_AI_GOAL) && (argnum == Operators[op_index].min - 1) ) if ( (i < 0) || (i > 200) ) return SEXP_CHECK_NUM_RANGE_INVALID; } } else if (node_subtype == SEXP_ATOM_STRING) { ; // no special handling } else if (node_subtype == SEXP_ATOM_CONTAINER_NAME) { ; // no special handling } else if (node_subtype == SEXP_ATOM_CONTAINER_DATA) { // this is an instance of "Replace Container Data" // can't be used in special argument list if (is_argument_provider_op(op_const)) { return SEXP_CHECK_TYPE_MISMATCH; } const int modifier_node = Sexp_nodes[node].first; if (modifier_node == -1) { return SEXP_CHECK_MISSING_CONTAINER_MODIFIER; } const auto *p_data_container = get_sexp_container(Sexp_nodes[node].text); // name should have already been checked in get_sexp() Assertion(p_data_container, "Attempt to check type of container data for SEXP operator %s at arg %d for non-existent container %s. " "Please report!", Operators[op_index].text.c_str(), argnum, Sexp_nodes[node].text); const auto &data_container = *p_data_container; if (!check_container_data_type(desired_argument_type, data_container.type, op_const, argnum, p_container)) { return SEXP_CHECK_WRONG_CONTAINER_DATA_TYPE; } // the modifiers are not visited as arguments, so check any variables among them here for (int mod_node = modifier_node; mod_node != -1; mod_node = CDR(mod_node)) { if (deferred_error == SEXP_CHECK_NO_ERROR && is_variable_node_type_mismatched(mod_node)) { deferred_error = SEXP_CHECK_VARIABLE_TYPE_MISMATCH; deferred_bad_node = mod_node; } } // ignore nested "Replace" uses if (!(Sexp_nodes[modifier_node].type & SEXP_FLAG_VARIABLE) && (Sexp_nodes[modifier_node].subtype != SEXP_ATOM_CONTAINER_DATA)) { Assertion(Sexp_nodes[modifier_node].subtype != SEXP_ATOM_CONTAINER_NAME, "Attempt to use container name %s as modifier for container %s. Please report!", Sexp_nodes[modifier_node].text, Sexp_nodes[node].text); if (data_container.is_list()) { const auto modifier = get_list_modifier(Sexp_nodes[modifier_node].text); if ((Sexp_nodes[modifier_node].subtype != SEXP_ATOM_STRING) || (modifier == ListModifier::INVALID)) { if (bad_node) *bad_node = modifier_node; return SEXP_CHECK_INVALID_LIST_MODIFIER; } if (modifier == ListModifier::AT_INDEX) { const int list_index_node = CDR(modifier_node); if (list_index_node == -1) { if (bad_node) *bad_node = modifier_node; return SEXP_CHECK_INVALID_LIST_MODIFIER; } // we can't check that index < length because we don't know what the length will be then if (Sexp_nodes[list_index_node].subtype != SEXP_ATOM_NUMBER || atoi(Sexp_nodes[list_index_node].text) < 0) { if (bad_node) *bad_node = list_index_node; return SEXP_CHECK_INVALID_LIST_MODIFIER; } } } else if (data_container.is_map()) { if ((any(data_container.type & ContainerType::NUMBER_KEYS) && Sexp_nodes[modifier_node].subtype != SEXP_ATOM_NUMBER) || (any(data_container.type & ContainerType::STRING_KEYS) && Sexp_nodes[modifier_node].subtype != SEXP_ATOM_STRING)) { if (bad_node) *bad_node = modifier_node; return SEXP_CHECK_WRONG_MAP_KEY_TYPE; } } else { UNREACHABLE("Unknown container type %d", static_cast(data_container.type)); } } // not much else we can check here, since validity depends on runtime values, so continue node = Sexp_nodes[node].rest; argnum++; continue; } else { UNREACHABLE("SEXP subtype is %d when it should be SEXP_ATOM_LIST, SEXP_ATOM_OPERATOR, SEXP_ATOM_NUMBER, " "SEXP_ATOM_STRING, SEXP_ATOM_CONTAINER_NAME, or SEXP_ATOM_CONTAINER_DATA!", node_subtype); } // variables should only be typechecked. if ((Sexp_nodes[node].type & SEXP_FLAG_VARIABLE) && (desired_argument_type != OPF_VARIABLE_NAME)) { var_index = sexp_get_variable_index(node); if (var_index < 0) return SEXP_CHECK_INVALID_VARIABLE; if (!check_variable_data_type(desired_argument_type, Sexp_variables[var_index].type, op_const, argnum, p_container)) { return SEXP_CHECK_INVALID_VARIABLE_TYPE; } node = Sexp_nodes[node].rest; argnum++; continue; } // if this is the special argument, make sure it is being properly used if (Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_NODE) { bool found = is_descendant_of_when_argument_op(node); // if there is no argument operator higher in the tree, we have a problem if (!found) { return SEXP_CHECK_MISPLACED_SPECIAL_ARGUMENT; } else { // we don't know what the argument will be at runtime, // so assume it's valid and continue to the next node = Sexp_nodes[node].rest; argnum++; continue; } } switch (desired_argument_type) { case OPF_NAV_POINT: if (node_subtype != SEXP_ATOM_STRING){ return SEXP_CHECK_TYPE_MISMATCH; } break; case OPF_NUMBER: if ((node_return_type != OPR_NUMBER) && (node_return_type != OPR_POSITIVE)){ return SEXP_CHECK_TYPE_MISMATCH; } break; case OPF_POSITIVE: if (node_return_type == OPR_NUMBER){ // for numeric literals, check whether the number is negative if (node_subtype == SEXP_ATOM_NUMBER){ if (*Sexp_nodes[node].text == '-') return SEXP_CHECK_NEGATIVE_NUM; } // Goober5000's number hack - for numeric return values of operators, skip the check (assume all return values will be positive) break; // return SEXP_CHECK_NEGATIVE_NUM; } if (node_return_type != OPR_POSITIVE){ return SEXP_CHECK_TYPE_MISMATCH; } break; case OPF_SHIP_NOT_PLAYER: { if (node_subtype != SEXP_ATOM_STRING) return SEXP_CHECK_TYPE_MISMATCH; auto ship_entry = eval_ship(node); if (!ship_entry) return SEXP_CHECK_INVALID_SHIP; if (ship_entry->status == ShipStatus::PRESENT) { if (ship_entry->objp()->flags[Object::Object_Flags::Player_ship]) return SEXP_CHECK_INVALID_SHIP; } // if it's not present, just assume it's okay break; } case OPF_SHIP_OR_NONE: if (node_subtype != SEXP_ATOM_STRING) return SEXP_CHECK_TYPE_MISMATCH; if (stricmp(CTEXT(node), SEXP_NONE_STRING) == 0) // none is okay break; if (eval_ship(node)) break; return SEXP_CHECK_INVALID_SHIP; case OPF_SHIP: case OPF_SHIP_POINT: if (node_subtype != SEXP_ATOM_STRING) return SEXP_CHECK_TYPE_MISMATCH; if (!eval_ship(node)) { // return invalid ship if not also looking for point if (desired_argument_type == OPF_SHIP) return SEXP_CHECK_INVALID_SHIP; auto ctext = CTEXT(node); if (!find_matching_waypoint(ctext)) { if (verify_vector(ctext)) // verify return non-zero on invalid point return SEXP_CHECK_INVALID_SHIP_POINT; } } break; case OPF_PROP: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } if (eval_prop(node) == nullptr) { return SEXP_CHECK_INVALID_PROP; } break; case OPF_WING: if (node_subtype != SEXP_ATOM_STRING){ return SEXP_CHECK_TYPE_MISMATCH; } if (eval_wing(node) == nullptr) { return SEXP_CHECK_INVALID_WING; } break; case OPF_SHIP_WING: case OPF_SHIP_WING_WHOLETEAM: case OPF_SHIP_WING_SHIPONTEAM_POINT: case OPF_SHIP_WING_POINT: case OPF_SHIP_WING_POINT_OR_NONE: case OPF_ORDER_RECIPIENT: if ( node_subtype != SEXP_ATOM_STRING ){ return SEXP_CHECK_TYPE_MISMATCH; } if (desired_argument_type == OPF_ORDER_RECIPIENT) { if (!strcmp ("", CTEXT(node))) { break; } } // all of these have ships and wings in common if (eval_ship(node) || eval_wing(node)) { break; } // none is okay for _OR_NONE if (desired_argument_type == OPF_SHIP_WING_POINT_OR_NONE && !stricmp(CTEXT(node), SEXP_NONE_STRING)) { break; } // two different ways of checking teams if ((desired_argument_type == OPF_SHIP_WING_WHOLETEAM) && iff_lookup(CTEXT(node)) >= 0) { break; } if ((desired_argument_type == OPF_SHIP_WING_SHIPONTEAM_POINT) && sexp_determine_team(CTEXT(node)) >= 0) { break; } // only other possibility is waypoints if (desired_argument_type == OPF_SHIP_WING_SHIPONTEAM_POINT || desired_argument_type == OPF_SHIP_WING_POINT || desired_argument_type == OPF_SHIP_WING_POINT_OR_NONE) { if (find_matching_waypoint(CTEXT(node)) == nullptr) { if (verify_vector(CTEXT(node))) { // non-zero on verify vector mean invalid! return (desired_argument_type == OPF_SHIP_WING_SHIPONTEAM_POINT) ? SEXP_CHECK_INVALID_SHIP_WING_SHIPONTEAM_POINT : SEXP_CHECK_INVALID_SHIP_WING_POINT; } } break; } // nothing left if (desired_argument_type == OPF_ORDER_RECIPIENT) return SEXP_CHECK_INVALID_ORDER_RECIPIENT; else if (desired_argument_type == OPF_SHIP_WING_WHOLETEAM) return SEXP_CHECK_INVALID_SHIP_WING_WHOLETEAM; else return SEXP_CHECK_INVALID_SHIP_WING; case OPF_SHIP_PROP: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } if (eval_ship(node)) { break; } if (eval_prop(node)) { break; } return SEXP_CHECK_INVALID_SHIP_PROP; case OPF_AWACS_SUBSYSTEM: case OPF_ROTATING_SUBSYSTEM: case OPF_TRANSLATING_SUBSYSTEM: case OPF_SUBSYSTEM: case OPF_SUBSYSTEM_OR_NONE: case OPF_SUBSYS_OR_GENERIC: { int ship_node; if (node_subtype != SEXP_ATOM_STRING){ return SEXP_CHECK_TYPE_MISMATCH; } // none is okay for subsys_or_none if (desired_argument_type == OPF_SUBSYSTEM_OR_NONE && !stricmp(CTEXT(node), SEXP_NONE_STRING)) { break; } // subsys_or_generic can also accept generic types if (desired_argument_type == OPF_SUBSYS_OR_GENERIC && get_generic_subsys(CTEXT(node)) != SUBSYSTEM_NONE) { break; } // we must get the model of the ship that is part of this sexpression and find a subsystem // with that name. This code assumes by default that the ship is *always* the first name // in the sexpression. If this is ever not the case, the code here must be changed to // get the correct ship name. switch(op_const) { case OP_CAP_SUBSYS_CARGO_KNOWN_DELAY: case OP_DISTANCE_CENTER_SUBSYSTEM: case OP_DISTANCE_BBOX_SUBSYSTEM: case OP_SET_CARGO: case OP_IS_CARGO: case OP_CHANGE_AI_CLASS: case OP_IS_AI_CLASS: case OP_MISSILE_LOCKED: case OP_SHIP_SUBSYS_GUARDIAN_THRESHOLD: case OP_IS_IN_TURRET_FOV: case OP_TURRET_SET_FORCED_TARGET: ship_node = CDR(CDR(op_node)); break; case OP_BEAM_FIRE: case OP_TURRET_SET_FORCED_SUBSYS_TARGET: if(argnum == 1){ ship_node = CDR(op_node); } else { ship_node = CDR(CDR(CDR(op_node))); } break; case OP_BEAM_FLOATING_FIRE: ship_node = CDDDDDR(CDDR(op_node)); break; case OP_QUERY_ORDERS: ship_node = CDR(CDR(CDR(CDR(op_node)))); break; case OP_WEAPON_CREATE: ship_node = CDDDDDR(CDDDDR(op_node)); break; default: if (op_const < First_available_operator_id) { ship_node = CDR(op_node); } else { int r_count = get_dynamic_parameter_index(Sexp_nodes[op_node].text, argnum); if (r_count < 0) error_display(1, "Expected to find a dynamic lua parent parameter for node %i in operator %s but " "found nothing!", argnum, Sexp_nodes[op_node].text); ship_node = op_node; //initialize it I guess while (r_count >= 0) { ship_node = CDR(ship_node); r_count--; } } break; } if (ship_node < 0) { Warning(LOCATION, "Could not find ship node for operator %s!", Operators[op_index].text.c_str()); return SEXP_CHECK_INVALID_SHIP; } if (is_node_value_dynamic(ship_node)) { const int dyn_val_check = check_dynamic_value_node_type(ship_node, true, false); if (dyn_val_check) { return dyn_val_check; } else { // since the value changes at runtime // we can check only the type break; } } auto ship_entry = eval_ship(ship_node); if (!ship_entry) { // for subsystem-or-none, the target may legitimately be a wing, waypoint, or , // in which case there is no ship class to validate the subsystem against if (desired_argument_type == OPF_SUBSYSTEM_OR_NONE && (eval_wing(ship_node) || find_matching_waypoint(CTEXT(ship_node)) || !stricmp(CTEXT(ship_node), SEXP_NONE_STRING))) break; if (bad_node) *bad_node = ship_node; return SEXP_CHECK_INVALID_SHIP; } auto sip = ship_entry->sip(); // check for the special "hull" value if ( (op_const == OP_SABOTAGE_SUBSYSTEM) || (op_const == OP_REPAIR_SUBSYSTEM) || (op_const == OP_SET_SUBSYSTEM_STRNGTH) || (op_const == OP_SET_ARMOR_TYPE) || (op_const == OP_BEAM_FIRE)) { if ( !stricmp( CTEXT(node), SEXP_HULL_STRING) || !stricmp( CTEXT(node), SEXP_SIM_HULL_STRING) ){ break; } } // check for special "shields" value for armor types if (op_const == OP_SET_ARMOR_TYPE) { if ( !stricmp( CTEXT(node), SEXP_SHIELD_STRING) || !stricmp( CTEXT(node), SEXP_SIM_HULL_STRING) ){ break; } } for (i=0; in_subsystems; i++) { if (!subsystem_stricmp(sip->subsystems[i].subobj_name, CTEXT(node))) { break; } } if (i == sip->n_subsystems) { return SEXP_CHECK_INVALID_SUBSYS; } if(Fred_running) { // if we're checking for an AWACS subsystem and this is not an awacs subsystem if((desired_argument_type == OPF_AWACS_SUBSYSTEM) && !(sip->subsystems[i].flags[Model::Subsystem_Flags::Awacs])) { return SEXP_CHECK_INVALID_AWACS_SUBSYS; } // rotating subsystem, like above - Goober5000 if ((desired_argument_type == OPF_ROTATING_SUBSYSTEM) && !(sip->subsystems[i].flags[Model::Subsystem_Flags::Rotates])) { return SEXP_CHECK_INVALID_ROTATING_SUBSYS; } // translating subsystem, like above - Goober5000 if ((desired_argument_type == OPF_TRANSLATING_SUBSYSTEM) && !(sip->subsystems[i].flags[Model::Subsystem_Flags::Translates])) { return SEXP_CHECK_INVALID_TRANSLATING_SUBSYS; } } break; } case OPF_ANIMATION_NAME: { // OP 1 is always the ship int ship_node; if (node_subtype != SEXP_ATOM_STRING){ return SEXP_CHECK_TYPE_MISMATCH; } ship_node = CDR(op_node); if (is_node_value_dynamic(ship_node)) { const int dyn_val_check = check_dynamic_value_node_type(ship_node, true, false); if (dyn_val_check) { return dyn_val_check; } else { // since the value changes at runtime // we can check only the type break; } } auto ship_entry = eval_ship(ship_node); if (!ship_entry) { if (bad_node) *bad_node = ship_node; return SEXP_CHECK_INVALID_SHIP; } auto sip = ship_entry->sip(); const auto& animSet = sip->animations; switch(op_const) { case OP_TRIGGER_ANIMATION_NEW: case OP_STOP_LOOPING_ANIMATION: { //Second OP trigger type //Third OP triggered by auto triggerType = animation::anim_match_type(CTEXT(CDR(ship_node))); const auto& animations = animSet.getRegisteredTriggers(); SCP_string triggeredBy = CTEXT(CDDR(ship_node)); SCP_tolower(triggeredBy); if(std::find_if(animations.cbegin(), animations.cend(), [&triggerType, &triggeredBy](const std::remove_reference::type::value_type& animation) -> bool { if (animation.type != triggerType) return false; // Since Dock Bay animations can be tables as NOT on door x, technically doors not tabled for can be valid targets. Just allow anything here. if(animation.type == animation::ModelAnimationTriggerType::DockBayDoor) return true; if(animation.subtype != animation::ModelAnimationSet::SUBTYPE_DEFAULT){ if(!can_construe_as_integer(triggeredBy.c_str())) return false; int triggeredBySubtype = atoi(triggeredBy.c_str()); int animationSubtype = animation.subtype; return triggeredBySubtype == animationSubtype; } else{ return animation.name == triggeredBy; } }) == animations.cend()) return SEXP_CHECK_INVALID_ANIMATION; break; } case OP_UPDATE_MOVEABLE: case OP_ADVANCE_MOVEABLE:{ //Second OP name SCP_string name = CTEXT(CDR(ship_node)); SCP_tolower(name); const auto& moveables = animSet.getRegisteredMoveables(); if(std::find(moveables.cbegin(), moveables.cend(), name) == moveables.cend()) return SEXP_CHECK_INVALID_ANIMATION; break; } } break; } case OPF_SUBSYSTEM_TYPE: i = string_lookup(CTEXT(node), Subsystem_types, SUBSYSTEM_MAX); if (i < 0) return SEXP_CHECK_INVALID_SUBSYS_TYPE; break; case OPF_POINT: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } if (find_matching_waypoint(CTEXT(node)) == nullptr) { if (verify_vector(CTEXT(node))) { return SEXP_CHECK_INVALID_POINT; } } break; case OPF_IFF: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } if (iff_lookup(CTEXT(node)) < 0) { return SEXP_CHECK_INVALID_IFF; } break; case OPF_AI_CLASS: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } for (i=0; i, etc. if (get_special_anchor(CTEXT(node)).isValid()) { valid = 1; } if (eval_ship(node)) { valid = 1; } if (!valid) { return SEXP_CHECK_INVALID_ARRIVAL_ANCHOR_ALL; } } break; case OPF_SOUNDTRACK_NAME: if (node_subtype != SEXP_ATOM_STRING){ return SEXP_CHECK_TYPE_MISMATCH; } if (!stricmp(CTEXT(node), "")) break; if (Cmdline_freespace_no_music) break; if (event_music_get_soundtrack_index(CTEXT(node)) >= 0) break; return SEXP_CHECK_INVALID_SOUNDTRACK_NAME; case OPF_SHIP_WITH_BAY: { if (node_subtype != SEXP_ATOM_STRING) return SEXP_CHECK_TYPE_MISMATCH; if (!stricmp(CTEXT(node), "")) break; auto ship_entry = eval_ship(node); if (ship_entry) { if (ship_entry->status == ShipStatus::PRESENT) { // now determine if this ship has a hangar bay if (model_has_hangar_bay(ship_entry->sip()->model_num)) break; else return SEXP_CHECK_INVALID_SHIP_WITH_BAY; } else { // we may not have the model paged in yet, so just assume it's okay break; } } return SEXP_CHECK_INVALID_SHIP; } case OPF_SUPPORT_SHIP_CLASS: if (node_subtype != SEXP_ATOM_STRING){ return SEXP_CHECK_TYPE_MISMATCH; } if (!stricmp(CTEXT(node), "")) break; if (!stricmp(CTEXT(node), "")) break; i = -1; for (auto it = Ship_info.cbegin(); it != Ship_info.cend(); ++it) { if (!stricmp(CTEXT(node), it->name)) { if (it->flags[Ship::Info_Flags::Support]) { i = (int)std::distance(Ship_info.cbegin(), it); break; } } } if (i == -1) return SEXP_CHECK_INVALID_SUPPORT_SHIP_CLASS; break; case OPF_BOOL: if (node_return_type != OPR_BOOL){ return SEXP_CHECK_TYPE_MISMATCH; } break; case OPF_AI_ORDER: if ( node_subtype != SEXP_ATOM_STRING ){ return SEXP_CHECK_TYPE_MISMATCH; } break; case OPF_NULL: if (node_return_type != OPR_NULL){ return SEXP_CHECK_TYPE_MISMATCH; } break; case OPF_SSM_CLASS: if ( node_subtype != SEXP_ATOM_STRING ) { return SEXP_CHECK_TYPE_MISMATCH; } if (ssm_info_lookup(CTEXT(node)) < 0) { return SEXP_CHECK_INVALID_SSM_CLASS; } break; // Goober5000 case OPF_FLEXIBLE_ARGUMENT: if (node_return_type != OPR_FLEXIBLE_ARGUMENT) { return SEXP_CHECK_TYPE_MISMATCH; } break; // Goober5000 case OPF_ANYTHING: if (node_subtype == SEXP_ATOM_CONTAINER_NAME) { return SEXP_CHECK_TYPE_MISMATCH; } break; case OPF_AI_GOAL: { if (node_return_type != OPR_AI_GOAL){ return SEXP_CHECK_TYPE_MISMATCH; } // we should check the syntax of the actual goal!!!! z = Sexp_nodes[node].first; if ((z = check_sexp_syntax(z, OPR_AI_GOAL, recursive, bad_node)) != 0){ if (!sexp_recoverable_error(z)) return z; // defer recoverable errors so that the rest of the tree is still checked if (deferred_error == SEXP_CHECK_NO_ERROR) { deferred_error = z; deferred_bad_node = bad_node ? *bad_node : -1; } } if (Fred_running) { // if it's the "goals" operator, this is part of initial orders, so we can't grab the ship from it if (op_const == OP_GOALS_ID) { break; } auto ship_node = Sexp_nodes[op_node].rest; Assert(ship_node >= 0); if (is_node_value_dynamic(ship_node)) { const int dyn_val_check = check_dynamic_value_node_type(ship_node, true, false); if (dyn_val_check) { return dyn_val_check; } else { // since the value changes at runtime // we can check only the type break; } } auto ship_entry = eval_ship(ship_node); auto wingp = eval_wing(ship_node); if (!ship_entry && !wingp) { if (bad_node) *bad_node = ship_node; return SEXP_CHECK_INVALID_SHIP; // should have already been caught earlier, but just in case.. } Assert(node_subtype == SEXP_ATOM_LIST); z = Sexp_nodes[node].first; Assert(Sexp_nodes[z].subtype != SEXP_ATOM_LIST); z = get_operator_const(z); if (ship_entry) { if (!query_sexp_ai_goal_valid(z, ship_entry->shipnum)) { if (bad_node) *bad_node = ship_node; return SEXP_CHECK_ORDER_NOT_ALLOWED; } } else { for (i=0; iwave_count; i++){ if (!query_sexp_ai_goal_valid(z, wingp->ship_index[i])){ if (bad_node) *bad_node = ship_node; return SEXP_CHECK_ORDER_NOT_ALLOWED; } } } if ((z == OP_AI_DOCK) && (Sexp_nodes[node].rest >= 0)) { auto ship_entry2 = eval_ship(Sexp_nodes[node].rest); if (!ship_entry || !ship_entry2 || !ship_docking_valid(ship_entry->shipnum, ship_entry2->shipnum)){ if (bad_node) *bad_node = ship_node; return SEXP_CHECK_DOCKING_NOT_ALLOWED; } } } break; } case OPF_SHIP_TYPE: { if (node_subtype != SEXP_ATOM_STRING){ return SEXP_CHECK_TYPE_MISMATCH; } auto type_name = CTEXT(node); i = ship_type_name_lookup(type_name); if (i < 0){ if (Fighter_bomber_valid && !stricmp(type_name, Fighter_bomber_type_name)) { // this is allowed even though it's not an explicit type break; } return SEXP_CHECK_INVALID_SHIP_TYPE; } break; } case OPF_WAYPOINT_PATH: if (find_matching_waypoint_list(CTEXT(node)) == nullptr) { return SEXP_CHECK_TYPE_MISMATCH; } break; case OPF_MESSAGE: if (node_subtype != SEXP_ATOM_STRING) return SEXP_CHECK_TYPE_MISMATCH; if (Fred_running) { for (i=0; i= 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 ( (op_const == OP_PREVIOUS_EVENT_TRUE) || (op_const == OP_PREVIOUS_EVENT_FALSE) || (op_const == OP_PREVIOUS_EVENT_INCOMPLETE) || (op_const == OP_PREVIOUS_GOAL_TRUE) || (op_const == OP_PREVIOUS_GOAL_FALSE) || (op_const == OP_PREVIOUS_GOAL_INCOMPLETE) ) break; if (!(*Mission_filename) || stricmp(Mission_filename, CTEXT(node)) != 0) return SEXP_CHECK_INVALID_MISSION_NAME; } } break; case OPF_GOAL_NAME: case OPF_EVENT_NAME: { if (node_subtype != SEXP_ATOM_STRING) return SEXP_CHECK_TYPE_MISMATCH; count = 0; // we only need to check the campaign list if running in Fred and are in campaign mode. // otherwise, check the set of current goals/events if (Fred_running && (mode == sexp_mode::CAMPAIGN)) { z = find_parent_operator(node); Assert(z >= 0); z = Sexp_nodes[z].rest; // first argument of operator should be mission name Assert(z >= 0); if (is_node_value_dynamic(z)) { const int dyn_val_check = check_dynamic_value_node_type(z, true, false); if (dyn_val_check) { return dyn_val_check; } else { // since the value changes at runtime // we can check only the type break; } } // look for mission count = count_items_with_string(Campaign.missions, i2sz(Campaign.num_missions), &cmission::name, CTEXT(z)); // only check for a missing mission -- it's ok if the same mission appears multiple times in the campaign if (count == 0) { if (bad_node) *bad_node = z; return SEXP_CHECK_INVALID_MISSION_NAME; } // read the goal/event list from the mission file if we need to if (Campaign.missions[i].flags & CMISSION_FLAG_FRED_LOAD_PENDING) { read_mission_goal_list(i); Campaign.missions[i].flags &= ~CMISSION_FLAG_FRED_LOAD_PENDING; } if (desired_argument_type == OPF_GOAL_NAME) { count = count_items_with_string(Campaign.missions[i].goals, &mgoal::name, CTEXT(node)); } else if (desired_argument_type == OPF_EVENT_NAME) { count = count_items_with_string(Campaign.missions[i].events, &mevent::name, CTEXT(node)); } else { // constrained by the enclosing case UNREACHABLE("desired_argument_type == %d; expected OPF_GOAL_NAME or OPF_EVENT_NAME", desired_argument_type); } } else if (desired_argument_type == OPF_GOAL_NAME) { // neither the previous mission nor the previous goal is guaranteed to exist (missions can be developed out of sequence), so we don't need to check them if ((op_const == OP_PREVIOUS_GOAL_TRUE) || (op_const == OP_PREVIOUS_GOAL_FALSE) || (op_const == OP_PREVIOUS_GOAL_INCOMPLETE)) break; count = count_items_with_string(Mission_goals, &mission_goal::name, CTEXT(node)); } else if (desired_argument_type == OPF_EVENT_NAME) { // neither the previous mission nor the previous event is guaranteed to exist (missions can be developed out of sequence), so we don't need to check them if ((op_const == OP_PREVIOUS_EVENT_TRUE) || (op_const == OP_PREVIOUS_EVENT_FALSE) || (op_const == OP_PREVIOUS_EVENT_INCOMPLETE)) break; count = count_items_with_string(Mission_events, &mission_event::name, CTEXT(node)); } else { // constrained by the enclosing case UNREACHABLE("desired_argument_type == %d; expected OPF_GOAL_NAME or OPF_EVENT_NAME", desired_argument_type); } if (count == 0) return (desired_argument_type == OPF_GOAL_NAME) ? SEXP_CHECK_INVALID_GOAL_NAME : SEXP_CHECK_INVALID_EVENT_NAME; else if (count > 1) return (desired_argument_type == OPF_GOAL_NAME) ? SEXP_CHECK_AMBIGUOUS_GOAL_NAME : SEXP_CHECK_AMBIGUOUS_EVENT_NAME; break; } case OPF_DOCKER_POINT: case OPF_DOCKEE_POINT: if (node_subtype != SEXP_ATOM_STRING) return SEXP_CHECK_TYPE_MISMATCH; // This makes massive assumptions about the structure of the SEXP using it. If you add any // new SEXPs that use this OPF, you will probably need to edit this section to accommodate them. if (Fred_running) { int ship_node = -1, model; // Look for the node containing the docker/dockee ship. In most cases, we want // the current SEXP operator, but for ai-dock and the docker, we want its parent. if (op_const == OP_AI_DOCK && desired_argument_type == OPF_DOCKER_POINT) { z = find_parent_operator(op_node); // if it's the "goals" operator, this is part of initial orders, so we can't grab the ship from it if (get_operator_const(z) == OP_GOALS_ID) { break; } ship_node = CDR(z); } else { z = op_node; if (op_const == OP_AI_DOCK) { // ai-dock with dockee ship_node = CDR(z); } else if (desired_argument_type == OPF_DOCKER_POINT) { if (op_const >= First_available_operator_id) { int r_count = get_dynamic_parameter_index(Sexp_nodes[op_node].text, argnum); if (r_count < 0) error_display(1, "Expected to find a dynamic lua parent parameter for node %i in operator %s " "but found nothing!", argnum, Sexp_nodes[op_node].text); ship_node = op_node; // initialize it I guess while (r_count >= 0) { ship_node = CDR(ship_node); r_count--; } break; } else { ship_node = CDR(z); } } else if (desired_argument_type == OPF_DOCKEE_POINT) { ship_node = CDDDR(z); } else if (op_const >= First_available_operator_id) { int r_count = get_dynamic_parameter_index(Sexp_nodes[op_node].text, argnum); if (r_count < 0) error_display(1, "Expected to find a dynamic lua parent parameter for node %i in operator %s " "but found nothing!", argnum, Sexp_nodes[op_node].text); ship_node = op_node; // initialize it I guess while (r_count >= 0) { ship_node = CDR(ship_node); r_count--; } break; } else { Assertion(false, "Unhandled case for OPF_DOCKER_POINT/OPF_DOCKEE_POINT"); } } Assert(ship_node >= 0); if (is_node_value_dynamic(ship_node)) { const int dyn_val_check = check_dynamic_value_node_type(ship_node, true, false); if (dyn_val_check) { return dyn_val_check; } else { // since the value changes at runtime // we can check only the type break; } } // look for the ship that has this dockpoint auto ship_entry = eval_ship(ship_node); if (!ship_entry) { if (bad_node) *bad_node = ship_node; return SEXP_CHECK_INVALID_SHIP; // should have already been caught earlier, but just in case.. } model = ship_entry->sip()->model_num; z = model_get_num_dock_points(model); for (i=0; i") != 0) if (stricmp(CTEXT(node), "") != 0 ) // not a special token? if (!eval_ship(node) && !eval_wing(node)) // is it a ship or wing? return SEXP_CHECK_INVALID_MSG_SOURCE; } break; //Karajorma case OPF_PERSONA: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } for (i = 0; i < (int)Personas.size(); i++) { if (!strcmp(CTEXT(node), Personas[i].name)) { break; } } if (i == (int)Personas.size()) { return SEXP_CHECK_INVALID_PERSONA_NAME; } break; case OPF_MISSION_MOOD: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } for (i = 0; i < (int)Builtin_moods.size(); i++) { if (!strcmp(Builtin_moods[i].c_str(), CTEXT(node))) { break; } } if (i == (int)Builtin_moods.size()) { return SEXP_CHECK_INVALID_MISSION_MOOD; } break; case OPF_SHIP_FLAG: { Object::Object_Flags object_flag = Object::Object_Flags::NUM_VALUES; Ship::Ship_Flags ship_flag = Ship::Ship_Flags::NUM_VALUES; Mission::Parse_Object_Flags parse_obj_flag = Mission::Parse_Object_Flags::NUM_VALUES; AI::AI_Flags ai_flag = AI::AI_Flags::NUM_VALUES; sexp_check_flag_arrays(CTEXT(node), object_flag, ship_flag, parse_obj_flag, ai_flag); if (object_flag == Object::Object_Flags::NUM_VALUES && ship_flag == Ship::Ship_Flags::NUM_VALUES && parse_obj_flag == Mission::Parse_Object_Flags::NUM_VALUES && ai_flag == AI::AI_Flags::NUM_VALUES) { return SEXP_CHECK_INVALID_SHIP_FLAG; } break; } case OPF_WING_FLAG: { Ship::Wing_Flags wing_flag = Ship::Wing_Flags::NUM_VALUES; sexp_check_flag_array(CTEXT(node), wing_flag); if (wing_flag == Ship::Wing_Flags::NUM_VALUES) { return SEXP_CHECK_INVALID_WING_FLAG; } break; } case OPF_TEAM_COLOR: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } if (!stricmp(CTEXT(node), "none")) break; if (Team_Colors.find(CTEXT(node)) == Team_Colors.end()) return SEXP_CHECK_INVALID_TEAM_COLOR; break; case OPF_FONT: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } if (font::FontManager::getFont(CTEXT(node)) == nullptr) return SEXP_CHECK_INVALID_FONT; break; case OPF_SOUND_ENVIRONMENT: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } if (stricmp(CTEXT(node), SEXP_NONE_STRING) != 0 && ds_eax_get_preset_id(CTEXT(node)) < 0) { return SEXP_CHECK_INVALID_SOUND_ENVIRONMENT; } break; case OPF_AUDIO_VOLUME_OPTION: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } if (audio_volume_option_lookup(CTEXT(node)) == -1) return SEXP_CHECK_INVALID_AUDIO_VOLUME_OPTION; break; case OPF_BUILTIN_HUD_GAUGE: { if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } auto gauge_name = CTEXT(node); // for compatibility, since this operator now uses a different set of parameters if (op_const == OP_FLASH_HUD_GAUGE) { bool found = false; for (int legacy_idx = 0; legacy_idx < NUM_HUD_GAUGES; legacy_idx++) { if (stricmp(gauge_name, Legacy_HUD_gauges[legacy_idx].hud_gauge_text) == 0) { found = true; break; } } if (found) { break; } } if (hud_gauge_type_lookup(gauge_name) == -1) return SEXP_CHECK_INVALID_BUILTIN_HUD_GAUGE; break; } case OPF_CUSTOM_HUD_GAUGE: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } if (hud_get_custom_gauge(CTEXT(node), true) == nullptr) { return SEXP_CHECK_INVALID_CUSTOM_HUD_GAUGE; } break; case OPF_ANY_HUD_GAUGE: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } if (hud_get_gauge(CTEXT(node), true) == nullptr) { return SEXP_CHECK_INVALID_ANY_HUD_GAUGE; } break; case OPF_SOUND_ENVIRONMENT_OPTION: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } if (sexp_sound_environment_option_lookup(CTEXT(node)) < 0) { return SEXP_CHECK_INVALID_SOUND_ENVIRONMENT_OPTION; } break; case OPF_EXPLOSION_OPTION: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } if (sexp_explosion_option_lookup(CTEXT(node)) < 0) { return SEXP_CHECK_INVALID_EXPLOSION_OPTION; } break; case OPF_KEYPRESS: if (node_subtype != SEXP_ATOM_STRING) return SEXP_CHECK_TYPE_MISMATCH; break; case OPF_CARGO: case OPF_STRING: case OPF_MESSAGE_OR_STRING: if (node_subtype != SEXP_ATOM_STRING) return SEXP_CHECK_TYPE_MISMATCH; break; case OPF_SKILL_LEVEL: if ( node_subtype != SEXP_ATOM_STRING ) return SEXP_CHECK_TYPE_MISMATCH; for (i = 0; i < NUM_SKILL_LEVELS; i++) { if ( !stricmp(CTEXT(node), Skill_level_names(i, 0)) ) break; } if ( i == NUM_SKILL_LEVELS ) return SEXP_CHECK_INVALID_SKILL_LEVEL; break; case OPF_MEDAL_NAME: if ( node_subtype != SEXP_ATOM_STRING) return SEXP_CHECK_TYPE_MISMATCH; for (i = 0; i < (int)Medals.size(); i++) { if ( !stricmp(CTEXT(node), Medals[i].name) ) break; } if (i == (int)Medals.size()) return SEXP_CHECK_INVALID_MEDAL_NAME; break; case OPF_HUGE_WEAPON: case OPF_WEAPON_NAME: if ( node_subtype != SEXP_ATOM_STRING ) return SEXP_CHECK_TYPE_MISMATCH; i = weapon_info_lookup(CTEXT(node)); if ( i < 0 ) return SEXP_CHECK_INVALID_WEAPON_NAME; // we need to be sure that for huge weapons, the WIF_HUGE flag is set if (desired_argument_type == OPF_HUGE_WEAPON ) { if ( !(Weapon_info[i].wi_flags[Weapon::Info_Flags::Huge]) ) return SEXP_CHECK_INVALID_WEAPON_NAME; } break; // Goober5000 case OPF_INTEL_NAME: if ( node_subtype != SEXP_ATOM_STRING ) return SEXP_CHECK_TYPE_MISMATCH; for ( i = 0; i < intel_info_size(); i++ ) { if ( !stricmp(CTEXT(node), Intel_info[i].name) ) break; } if ( i == intel_info_size() ) return SEXP_CHECK_INVALID_INTEL_NAME; break; case OPF_TURRET_TARGET_ORDER: if ( node_subtype != SEXP_ATOM_STRING ) return SEXP_CHECK_TYPE_MISMATCH; for (i = 0; i < NUM_TURRET_ORDER_TYPES; i++ ) { if ( !stricmp(CTEXT(node), Turret_target_order_names[i]) ) break; } if ( i == NUM_TURRET_ORDER_TYPES ) return SEXP_CHECK_INVALID_TURRET_TARGET_ORDER; break; case OPF_TURRET_TYPE: if (node_subtype != SEXP_ATOM_STRING) return SEXP_CHECK_TYPE_MISMATCH; for (i = 0; i < NUM_TURRET_TYPES; i++) { if (!stricmp(CTEXT(node), Turret_valid_types[i])) break; } if (i == NUM_TURRET_TYPES) return SEXP_CHECK_INVALID_TURRET_TYPE; break; case OPF_ARMOR_TYPE: if ( node_subtype != SEXP_ATOM_STRING ) return SEXP_CHECK_TYPE_MISMATCH; if (!stricmp(CTEXT(node), SEXP_NONE_STRING)) break; for (st = 0; st < Armor_types.size(); st++ ) { if ( !stricmp(CTEXT(node), Armor_types[st].GetNamePtr()) ) break; } if ( st == Armor_types.size() ) return SEXP_CHECK_INVALID_ARMOR_TYPE; break; case OPF_DAMAGE_TYPE: if ( node_subtype != SEXP_ATOM_STRING ) return SEXP_CHECK_TYPE_MISMATCH; if (!stricmp(CTEXT(node), SEXP_NONE_STRING)) break; for (st = 0; st < Damage_types.size(); st++ ) { if ( !stricmp(CTEXT(node), Damage_types[st].name) ) break; } if ( st == Damage_types.size() ) return SEXP_CHECK_INVALID_DAMAGE_TYPE; break; case OPF_ANIMATION_TYPE: if ( node_subtype != SEXP_ATOM_STRING ) return SEXP_CHECK_TYPE_MISMATCH; if (animation::anim_match_type(CTEXT(node)) == animation::ModelAnimationTriggerType::None ) return SEXP_CHECK_INVALID_ANIMATION_TYPE; break; case OPF_TARGET_PRIORITIES: if ( node_subtype != SEXP_ATOM_STRING ) return SEXP_CHECK_TYPE_MISMATCH; for(st = 0; st < Ai_tp_list.size(); st++) { if ( !stricmp(CTEXT(node), Ai_tp_list[st].name) ) break; } if ( st == Ai_tp_list.size() ) return SEXP_CHECK_INVALID_TARGET_PRIORITIES; break; case OPF_SHIP_CLASS_NAME: if ( node_subtype != SEXP_ATOM_STRING ) return SEXP_CHECK_TYPE_MISMATCH; if (ship_info_lookup(CTEXT(node)) < 0) return SEXP_CHECK_INVALID_SHIP_CLASS_NAME; break; case OPF_SKYBOX_MODEL_NAME: if ( node_subtype != SEXP_ATOM_STRING ) return SEXP_CHECK_TYPE_MISMATCH; if ( stricmp(CTEXT(node), NOX("default")) != 0 && stricmp(CTEXT(node), NOX("none")) != 0 && !strstr(CTEXT(node), NOX(".pof")) ) return SEXP_CHECK_INVALID_SKYBOX_NAME; break; case OPF_SKYBOX_FLAGS: if ( node_subtype != SEXP_ATOM_STRING ) return SEXP_CHECK_TYPE_MISMATCH; for ( i = 0; i < Num_skybox_flags; ++i ) { if ( !stricmp( CTEXT(node), Skybox_flags[i]) ) break; } // if we reached the end of the list, then the flag is invalid if ( i == Num_skybox_flags ) return SEXP_CHECK_INVALID_SKYBOX_FLAG; break; case OPF_JUMP_NODE_NAME: if ( node_subtype != SEXP_ATOM_STRING ) return SEXP_CHECK_TYPE_MISMATCH; if (jumpnode_get_by_name(CTEXT(node)) == nullptr) return SEXP_CHECK_INVALID_JUMP_NODE; break; case OPF_VARIABLE_NAME: var_index = sexp_get_variable_index(node); if (var_index < 0) return SEXP_CHECK_INVALID_VARIABLE; switch (op_const) { // some SEXPs demand a number variable case OP_ADD_BACKGROUND_BITMAP: case OP_ADD_BACKGROUND_BITMAP_NEW: case OP_ADD_SUN_BITMAP: case OP_ADD_SUN_BITMAP_NEW: case OP_PLAY_SOUND_FROM_FILE: case OP_PAUSE_SOUND_FROM_FILE: case OP_CLOSE_SOUND_FROM_FILE: if (!(Sexp_variables[var_index].type & SEXP_VARIABLE_NUMBER)) return SEXP_CHECK_INVALID_VARIABLE_TYPE; break; // some demand a string variable case OP_STRING_CONCATENATE: case OP_STRING_CONCATENATE_BLOCK: case OP_INT_TO_STRING: case OP_STRING_GET_SUBSTRING: case OP_STRING_SET_SUBSTRING: case OP_SCRIPT_EVAL_STRING: if (!(Sexp_variables[var_index].type & SEXP_VARIABLE_STRING)) return SEXP_CHECK_INVALID_VARIABLE_TYPE; break; default: break; } // otherwise anything goes break; case OPF_AMBIGUOUS: // type checking for modify-variable // string or number -- anything goes break; case OPF_BACKGROUND_BITMAP: case OPF_SUN_BITMAP: case OPF_NEBULA_STORM_TYPE: case OPF_NEBULA_POOF: case OPF_NEBULA_PATTERN: case OPF_POST_EFFECT: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } break; case OPF_HUD_ELEMENT: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } else { auto gauge = CTEXT(node); if ( strcmp(SEXP_HUD_GAUGE_WARPOUT, gauge) == 0 ) { break; } } return SEXP_CHECK_INVALID_HUD_ELEMENT; case OPF_WEAPON_BANK_NUMBER: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } if (!stricmp(CTEXT(node), SEXP_ALL_BANKS_STRING)) { break; } // if we haven't specified all banks we need to check the number of the bank is legal else { int num_banks = atoi(CTEXT(node)); if ((num_banks >= MAX_SHIP_PRIMARY_BANKS) && (num_banks >= MAX_SHIP_SECONDARY_BANKS)) { // NOLINT return SEXP_CHECK_NUM_RANGE_INVALID; } } break; case OPF_SHIP_EFFECT: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } if (get_effect_from_name(CTEXT(node)) == -1 ) { return SEXP_CHECK_INVALID_SHIP_EFFECT; } break; case OPF_GAME_SND: if (node_subtype == SEXP_ATOM_NUMBER) { int node_num = atoi(CTEXT(node)); if (node_num == -1) break; // explicitly allow a sound of -1, indicating either "no sound" or "default" if (!gamesnd_get_by_tbl_index(node_num).isValid()) return SEXP_CHECK_INVALID_GAME_SND; } else if (node_subtype == SEXP_ATOM_STRING) { auto node_text = CTEXT(node); if (stricmp(node_text, SEXP_NONE_STRING) == 0) break; // explicitly allow "no sound", although some sexps might interpret it as "default" if (!gamesnd_get_by_name(node_text).isValid()) return SEXP_CHECK_INVALID_GAME_SND; } else if (node_subtype != SEXP_ATOM_LIST) return SEXP_CHECK_INVALID_GAME_SND; break; case OPF_FIREBALL: if (node_subtype == SEXP_ATOM_NUMBER || can_construe_as_integer(CTEXT(node))) { int num = atoi(CTEXT(node)); if (!SCP_vector_inbounds(Fireball_info, num)) return SEXP_CHECK_INVALID_FIREBALL; } else if (node_subtype == SEXP_ATOM_STRING) { if (fireball_info_lookup(CTEXT(node)) < 0) return SEXP_CHECK_INVALID_FIREBALL; } else if (node_subtype != SEXP_ATOM_LIST) return SEXP_CHECK_INVALID_FIREBALL; break; case OPF_SPECIES: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } if (species_info_lookup(CTEXT(node)) < 0 ) { return SEXP_CHECK_INVALID_SPECIES; } break; case OPF_LANGUAGE: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } // that's all we do, since there may be a language the game doesn't know about break; case OPF_FUNCTIONAL_WHEN_EVAL_TYPE: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } if (sexp_functional_when_eval_type_lookup(CTEXT(node)) < 0) { return SEXP_CHECK_INVALID_FUNCTIONAL_WHEN_EVAL_TYPE; } break; case OPF_CONTAINER_NAME: case OPF_LIST_CONTAINER_NAME: case OPF_MAP_CONTAINER_NAME: { if (node_subtype != SEXP_ATOM_CONTAINER_NAME) { return SEXP_CHECK_TYPE_MISMATCH; } p_container = get_sexp_container(Sexp_nodes[node].text); if (!p_container) { Warning(LOCATION, "Attempt to use unknown container %s. Please report!", Sexp_nodes[node].text); return SEXP_CHECK_TYPE_MISMATCH; } if ((desired_argument_type == OPF_LIST_CONTAINER_NAME && !p_container->is_list()) || (desired_argument_type == OPF_MAP_CONTAINER_NAME && !p_container->is_map())) { return SEXP_CHECK_WRONG_CONTAINER_TYPE; } break; } case OPF_CONTAINER_VALUE: if (!p_container) { Warning(LOCATION, "Attempt to check value arg for null container for SEXP operator %s at arg %d. Please report!", Operators[op_index].text.c_str(), argnum); return SEXP_CHECK_TYPE_MISMATCH; } z = check_container_value_data_type(op_const, argnum, p_container->type, (node_subtype == SEXP_ATOM_STRING), (node_return_type == OPR_NUMBER) || (node_return_type == OPR_POSITIVE)); if (z) { return z; } break; case OPF_DATA_OR_STR_CONTAINER: { if (node_subtype == SEXP_ATOM_CONTAINER_NAME) { // only list containers of strings or map containers with string keys are allowed const auto *p_str_container = get_sexp_container(Sexp_nodes[node].text); if (!p_str_container) { Warning(LOCATION, "Attempt to use unknown container %s. Please report!", Sexp_nodes[node].text); return SEXP_CHECK_TYPE_MISMATCH; } const auto &str_container = *p_str_container; if (str_container.is_list() && none(str_container.type & ContainerType::STRING_DATA)) { return SEXP_CHECK_WRONG_CONTAINER_DATA_TYPE; } else if (str_container.is_map() && none(str_container.type & ContainerType::STRING_KEYS)) { return SEXP_CHECK_WRONG_MAP_KEY_TYPE; } } break; } case OPF_ASTEROID_TYPES: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } else { auto list = get_list_valid_asteroid_subtypes(); bool valid = false; for (const auto& item : list) { if (!stricmp(CTEXT(node), item.c_str())) { valid = true; break; } } if (stricmp(CTEXT(node), SEXP_NONE_STRING) && !valid) { return SEXP_CHECK_INVALID_ASTEROID; } } break; case OPF_DEBRIS_TYPES: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } if (stricmp(CTEXT(node), SEXP_NONE_STRING) && (get_asteroid_index(CTEXT(node))) < 0) { return SEXP_CHECK_INVALID_ASTEROID; } break; case OPF_WING_FORMATION: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } if (!stricmp(CTEXT(node), "Default")) { break; } if (wing_formation_lookup(CTEXT(node)) < 0) { return SEXP_CHECK_INVALID_WING_FORMATION; } break; case OPF_MOTION_DEBRIS: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } if (stricmp(CTEXT(node), SEXP_NONE_STRING) && (get_motion_debris_by_name(CTEXT(node)) < 0)) { return SEXP_CHECK_INVALID_MOTION_DEBRIS; } break; case OPF_BOLT_TYPE: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } if (get_bolt_type_by_name(CTEXT(node)) < 0) { return SEXP_CHECK_INVALID_BOLT_TYPE; } break; case OPF_TRAITOR_OVERRIDE: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } if (stricmp(CTEXT(node), SEXP_NONE_STRING) && (get_traitor_override_pointer(CTEXT(node)) == nullptr)) { return SEXP_CHECK_INVALID_TRAITOR_OVERRIDE; } break; case OPF_LUA_GENERAL_ORDER: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } if (stricmp(CTEXT(node), SEXP_NONE_STRING) && (ai_lua_find_general_order_id(CTEXT(node)) < 0)) { return SEXP_CHECK_INVALID_LUA_GENERAL_ORDER; } break; case OPF_MISSION_CUSTOM_STRING: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } if (get_custom_string_by_name(CTEXT(node)) == nullptr) { return SEXP_CHECK_INVALID_CUSTOM_STRING; } break; case OPF_MESSAGE_TYPE: if (node_subtype != SEXP_ATOM_STRING) { return SEXP_CHECK_TYPE_MISMATCH; } if (get_builtin_message_type(CTEXT(node)) == MESSAGE_NONE) { return SEXP_CHECK_INVALID_MESSAGE_TYPE; } break; default: //This handles OPF_CHILD_LUA_ENUM as well if (!Dynamic_enums.empty()) { if ((desired_argument_type - First_available_opf_id) < sz2i(Dynamic_enums.size())) { if (node_subtype != SEXP_ATOM_STRING) return SEXP_CHECK_TYPE_MISMATCH; } else { Error(LOCATION, "Unhandled argument format"); } } else { Error(LOCATION, "Unhandled argument format"); } } node = Sexp_nodes[node].rest; argnum++; } // now that the rest of the tree has been checked, report any recoverable error that was noted along the way if (deferred_error != SEXP_CHECK_NO_ERROR) { if (bad_node) *bad_node = deferred_bad_node; return deferred_error; } return 0; } int check_sexp_potential_issues(int node, int *bad_node, SCP_string &issue_msg) { // empty tree if (node < 0) return 0; if (bad_node) *bad_node = node; // check operators for anything that might trip up a mission designer if (Sexp_nodes[node].subtype == SEXP_ATOM_OPERATOR) { auto op_num = get_operator_const(node); int first_arg_node = CDR(node); switch (op_num) { // examine all sexps that check for wing destruction case OP_IS_DESTROYED_DELAY: case OP_PERCENT_SHIPS_DESTROYED: first_arg_node = CDR(first_arg_node); FALLTHROUGH; case OP_IS_DESTROYED: case OP_TIME_WING_DESTROYED: { if (!The_mission.ai_profile->flags[AI::Profile_Flags::Cancel_future_waves_of_any_wing_launched_from_an_exited_ship]) { for (int n = first_arg_node; n >= 0; n = CDR(n)) { int wingnum = wing_lookup(CTEXT(n)); if (wingnum >= 0) { auto wingp = &Wings[wingnum]; if (wingp->num_waves > 1 && wingp->arrival_location == ArrivalLocation::FROM_DOCK_BAY) { issue_msg = "Wing "; issue_msg += wingp->name; issue_msg += " has more than one wave and arrives from a docking bay. Be careful when checking for this wing's destruction. If the " "mothership is destroyed before all waves have arrived, the wing will not be considered destroyed. Note that the " "$cancel future waves of any wing launched from an exited ship: flag can be used to fix this."; return SEXP_CHECK_POTENTIAL_ISSUE; } } } } break; } // examine uses of alter-ship-flag and are-ship-flags-set with "immobile" case OP_ALTER_SHIP_FLAG: case OP_ARE_SHIP_FLAGS_SET: { Object::Object_Flags object_flag = Object::Object_Flags::NUM_VALUES; Ship::Ship_Flags ship_flag = Ship::Ship_Flags::NUM_VALUES; Mission::Parse_Object_Flags parse_obj_flag = Mission::Parse_Object_Flags::NUM_VALUES; AI::AI_Flags ai_flag = AI::AI_Flags::NUM_VALUES; bool immobile_used = false; // check to see the flag is specified by the sexp (don't check to see what the flag may be applied to) if (op_num == OP_ALTER_SHIP_FLAG) { auto flag_name = CTEXT(first_arg_node); sexp_check_flag_arrays(flag_name, object_flag, ship_flag, parse_obj_flag, ai_flag); if (object_flag == Object::Object_Flags::Immobile || parse_obj_flag == Mission::Parse_Object_Flags::OF_Immobile) immobile_used = true; } else { for (int n = CDR(first_arg_node); n >= 0; n = CDR(n)) { auto flag_name = CTEXT(n); sexp_check_flag_arrays(flag_name, object_flag, ship_flag, parse_obj_flag, ai_flag); if (object_flag == Object::Object_Flags::Immobile || parse_obj_flag == Mission::Parse_Object_Flags::OF_Immobile) { immobile_used = true; break; } } } // now check if any ships are created with any of the flags if (immobile_used) { for (const auto so : list_range(&Ship_obj_list)) { const auto &obj = Objects[so->objnum]; if (obj.flags.any_of(Object::Object_Flags::Immobile,Object::Object_Flags::Dont_change_position,Object::Object_Flags::Dont_change_orientation)) { issue_msg = "At least one ship ("; issue_msg += Ships[obj.instance].ship_name; issue_msg += ") has \"Does Not Change Position\" and/or \"Does Not Change Orientation\" checked, while this "; issue_msg += Sexp_nodes[node].text; issue_msg += " operator uses the \"immobile\" flag. Be aware that all three flags are independent and setting/checking one flag will not " "set/check another. For convenience, the set-mobile and set-immobile operators will clear conflicting flags, but alter-ship-flag will not."; return SEXP_CHECK_POTENTIAL_ISSUE; } } } break; } default: break; } } // recurse down the tree int z = check_sexp_potential_issues(CAR(node), bad_node, issue_msg); if (z) return z; z = check_sexp_potential_issues(CDR(node), bad_node, issue_msg); if (z) return z; return 0; } // Goober5000 bool get_unformatted_sexp_variable_name(char *unformatted, const char *formatted) { // first character should be @ if (*formatted != SEXP_VARIABLE_CHAR) return false; auto intermediate = formatted + 1; size_t n; // get variable name (up to '[') auto l_bracket = strchr(intermediate, '['); if (l_bracket) { auto r_bracket = strchr(l_bracket, ']'); if (!r_bracket) { error_display(0, "Malformed variable token: %s", formatted); return false; } n = l_bracket - intermediate; } else { n = strlen(intermediate); } if (n >= TOKEN_LENGTH - 1) return false; strncpy(unformatted, intermediate, n); unformatted[n] = 0; return true; } // Goober5000 // This function specifically checks the token that is parsed from a mission file. // In this situation the variable would appear as @variable_name[variable_contents]. // See also check_sexp_node_text_for_sexp_variable(). int check_string_for_sexp_variable(const char *str, size_t n_chars) { if (*str != SEXP_VARIABLE_CHAR) return -1; constexpr size_t var_token_size = 2 * (TOKEN_LENGTH - 1) + 4; // @variable_token[contents_token]\0 char variable_token[var_token_size]; char variable_name[NAME_LENGTH]; // token is too long? if (n_chars >= var_token_size) return -1; strncpy(variable_token, str, n_chars); variable_token[n_chars] = 0; // see if it's really a variable if (!get_unformatted_sexp_variable_name(variable_name, variable_token)) return -1; return get_index_sexp_variable_name(variable_name); } /** * 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, int sexp_var_index) { Assertion(sexp_var_index >= 0 && sexp_var_index < MAX_SEXP_VARIABLES, "sexp_var_index out of range!"); if (Fred_running) { strcpy(text, Sexp_variables[sexp_var_index].variable_name); } else { sprintf(text, "%d", sexp_var_index); } } // Goober5000 void do_preload_for_arguments(void (*preloader)(const char *), int arg_node, int arg_handler_node) { if (arg_node < 0) return; // we have a special argument if (Sexp_nodes[arg_node].flags & SNF_SPECIAL_ARG_IN_NODE) { int n; // we might not be handling it, either because this is not a *_of operator // or because we've disabled preloading for special arguments if (arg_handler_node < 0) return; // preload for each argument for (n = CDR(arg_handler_node); n != -1; n = CDR(n)) { preloader(CTEXT(n)); } } // we don't else { // preload for just the one argument preloader(CTEXT(arg_node)); } } // Goober5000 void preload_change_ship_class(const char *text) { int idx; ship_info *sip; idx = ship_info_lookup(text); if (idx < 0) return; // preload the model, just in case there is no other ship of this class in the mission // (this eliminates the slight pause during a mission when changing to a previously unloaded model) sip = &Ship_info[idx]; sip->model_num = model_load(sip->pof_file, sip); if (sip->model_num >= 0) model_page_in_textures(sip->model_num, idx); } // MjnMixael void preload_asteroid_class(const char* text) { const auto& list = get_list_valid_asteroid_subtypes(); bool valid = std::any_of(list.begin(), list.end(), [&](const SCP_string& item) { return !stricmp(text, item.c_str()); }); if (!valid) return; asteroid_load(ASTEROID_TYPE_SMALL, get_asteroid_subtype_index_by_name(text, ASTEROID_TYPE_SMALL)); asteroid_load(ASTEROID_TYPE_MEDIUM, get_asteroid_subtype_index_by_name(text, ASTEROID_TYPE_MEDIUM)); asteroid_load(ASTEROID_TYPE_LARGE, get_asteroid_subtype_index_by_name(text, ASTEROID_TYPE_LARGE)); } // MjnMixael void preload_debris_class(const char* text) { auto idx = get_asteroid_index(text); if (idx < 0) return; asteroid_load(idx, 0); } // MjnMixael void preload_change_prop_class(const char* text) { int idx; prop_info* pip; idx = prop_info_lookup(text); if (idx < 0) return; pip = &Prop_info[idx]; pip->model_num = model_load(pip->pof_file.c_str()); if (pip->model_num >= 0) model_page_in_textures(pip->model_num, idx); } // Goober5000 void preload_turret_change_weapon(const char *text) { int idx; idx = weapon_info_lookup(text); if (idx < 0) return; weapon_mark_as_used(idx); } void preload_texture(const char* text) { if (!stricmp(text, "invisible")) return; int texture = bm_load_either(text); if (texture < 0) { Warning(LOCATION, "Could not preload texture %s", text); return; } bm_page_in_texture(texture); } // Goober5000 // we don't use the do_preload function because the preloader needs to access two nodes at a time // also we're not using CTEXT or eval_num here because XSTR should really be constant, and // also because we can't really run sexp stuff in a preloader void localize_sexp(int text_node, int id_node) { // we need both a name and an id if (text_node < 0 || id_node < 0) return; int id = atoi(Sexp_nodes[id_node].text); Assert(id < 10000000); SCP_string xstr; sprintf(xstr, "XSTR(\"%s\", %d)", Sexp_nodes[text_node].text, id); memset(Sexp_nodes[text_node].text, 0, TOKEN_LENGTH * sizeof(char)); lcl_ext_localize(xstr.c_str(), Sexp_nodes[text_node].text, TOKEN_LENGTH - 1); } // Advance to and consume the closing parenthesis of a sexp, in case of a parse error. // If a closing parenthesis is not found, this advances to the end of the string. void skip_sexp(bool within_quotes = false) { int level = 1; while (*Mp != '\0') { if (*Mp == '\"') within_quotes = !within_quotes; if (!within_quotes) { if (*Mp == ')') { level--; if (level == 0) { Mp++; return; } } else if (*Mp == '(') level++; } Mp++; } } /** * Allocates a variable reference node whose subtype comes from the variable's declared type. * If the reference was quoted contrary to that type, the node is flagged so check_sexp_syntax can report it. */ static int alloc_sexp_variable_node(int sexp_var_index, bool quoted) { char token[TOKEN_LENGTH]; get_sexp_text_for_variable(token, sexp_var_index); bool is_number = (Sexp_variables[sexp_var_index].type & SEXP_VARIABLE_NUMBER) != 0; int node = alloc_sexp(token, (SEXP_ATOM | SEXP_FLAG_VARIABLE), is_number ? SEXP_ATOM_NUMBER : SEXP_ATOM_STRING, -1, -1); if (quoted == is_number) Sexp_nodes[node].flags |= SNF_VARIABLE_TYPE_MISMATCH; return node; } /** * Returns the first sexp index of data this function allocates. (start of this sexp) * * NOTE: On entry into this function, Mp points to the first character past the opening parenthesis. */ int get_sexp() { int start, node, last, op; char token[TOKEN_LENGTH]; bool prune_extra_args = false; Assert(*(Mp-1) == '('); auto starting_Mp = Mp; // start - the node allocated in first instance of function // node - the node allocated in current instance of function // variable - whether string or number is a variable referencing Sexp_variables // initialization start = last = -1; ignore_white_space(); while (*Mp != ')') { // end of string or end of file if (*Mp == '\0') { char buf[512]; error_display(0, "Unexpected end of sexp!\n%s", three_dot_truncate(buf, starting_Mp, 512)); return Locked_sexp_false; } // Sexp list if (*Mp == '(') { Mp++; node = alloc_sexp("", SEXP_LIST, SEXP_ATOM_LIST, get_sexp(), -1); } // Sexp string else if (*Mp == '\"') { auto startp = Mp; auto len = strcspn(Mp + 1, "\""); // was closing quote not found? if (*(Mp + 1 + len) != '\"') { char buf[512]; error_display(0, "Unexpected end of quoted string embedded in sexp!\n%s", three_dot_truncate(buf, starting_Mp, 512)); skip_sexp(true); // this will have the effect of skipping to the end of the file or string return Locked_sexp_false; } // bump past closing quote Mp += (len + 2); // it could be a variable int sexp_var_index = check_string_for_sexp_variable(startp + 1, len); if (sexp_var_index >= 0) { node = alloc_sexp_variable_node(sexp_var_index, true); } // it's a regular string else { // token is too long? if (len >= TOKEN_LENGTH) { SCP_string long_token(startp + 1, len); error_display(0, "Token is too long. Needs to be %d characters or shorter:\n%s", TOKEN_LENGTH - 1, long_token.c_str()); // here we can just truncate; we don't need to return len = TOKEN_LENGTH - 1; } strncpy(token, startp + 1, len); token[len] = 0; node = alloc_sexp(token, SEXP_ATOM, SEXP_ATOM_STRING, -1, -1); } } // Sexp container else if (*Mp == sexp_container::DELIM) { auto startp = Mp; size_t len = 0; while (!is_parenthesis(*Mp) && !is_white_space(*Mp)) { // end of string or end of file if (*Mp == '\0') { char buf[512]; error_display(0, "Unexpected end of sexp!\n%s", three_dot_truncate(buf, starting_Mp, 512)); return Locked_sexp_false; } Mp++; len++; } // token is too long? if (len >= TOKEN_LENGTH) { SCP_string long_token(startp, len); error_display(0, "Token is too long. Needs to be %d characters or shorter:\n%s", TOKEN_LENGTH - 1, long_token.c_str()); skip_sexp(); return Locked_sexp_false; } strncpy(token, startp, len); token[len] = 0; // container name if (token[1] == sexp_container::DELIM) { auto container_name = token + 2; if (get_sexp_container(container_name) == nullptr) { error_display(0, "Attempt to use unknown container '%s'", token); skip_sexp(); return Locked_sexp_false; } node = alloc_sexp(container_name, SEXP_ATOM, SEXP_ATOM_CONTAINER_NAME, -1, -1); } // container data else { if (token[len - 1] != sexp_container::DELIM) { error_display(0, "Malformed container data token: %s", token); skip_sexp(); return Locked_sexp_false; } char container_name[TOKEN_LENGTH]; strcpy_s(container_name, token + 1); // skip first delimiter container_name[len - 2] = 0; // truncate last delimiter if (get_sexp_container(container_name) == nullptr) { error_display(0, "Attempt to use data from unknown container '%s'", token); skip_sexp(); return Locked_sexp_false; } // advance to the container modifier, since we'll read them when calling get_sexp() below ignore_gray_space(); // watch out for malformed input if (*Mp != '(') break; Mp++; node = alloc_sexp(container_name, SEXP_ATOM, SEXP_ATOM_CONTAINER_DATA, get_sexp(), -1); } } // Sexp operator or number else { auto startp = Mp; size_t len = 0; while (!is_parenthesis(*Mp) && !is_white_space(*Mp)) { // end of string or end of file if (*Mp == '\0') { char buf[512]; error_display(0, "Unexpected end of sexp!\n%s", three_dot_truncate(buf, starting_Mp, 512)); return Locked_sexp_false; } Mp++; len++; } // it could be a variable int sexp_var_index = check_string_for_sexp_variable(startp, len); if (sexp_var_index >= 0) { node = alloc_sexp_variable_node(sexp_var_index, false); } // it could be an operator else { // token is too long? if (len >= TOKEN_LENGTH) { SCP_string long_token(startp, len); error_display(0, "Token is too long. Needs to be %d characters or shorter:\n%s", TOKEN_LENGTH - 1, long_token.c_str()); skip_sexp(); return Locked_sexp_false; } strncpy(token, startp, len); token[len] = 0; // maybe replace deprecated names if (!stricmp(token, "set-ship-position")) strcpy_s(token, "set-object-position"); else if (!stricmp(token, "set-ship-facing")) strcpy_s(token, "set-object-facing"); else if (!stricmp(token, "set-ship-facing-object")) strcpy_s(token, "set-object-facing-object"); else if (!stricmp(token, "ai-chase-any-except")) { strcpy_s(token, "ai-chase-any"); prune_extra_args = true; } else if (!stricmp(token, "change-ship-model")) strcpy_s(token, "change-ship-class"); else if (!stricmp(token, "radar-set-max-range")) strcpy_s(token, "hud-set-max-targeting-range"); else if (!stricmp(token, "ship-subsys-vanished")) strcpy_s(token, "ship-subsys-vanish"); else if (!stricmp(token, "directive-is-variable")) strcpy_s(token, "directive-value"); else if (!stricmp(token, "variable-array-get")) strcpy_s(token, "get-variable-by-index"); else if (!stricmp(token, "variable-array-set")) strcpy_s(token, "set-variable-by-index"); else if (!stricmp(token, "distance-ship-subsystem")) strcpy_s(token, "distance-center-to-subsystem"); else if (!stricmp(token, "remove-weapons")) strcpy_s(token, "clear-weapons"); else if (!stricmp(token, "hud-set-retail-gauge-active")) strcpy_s(token, "hud-set-builtin-gauge-active"); else if (!stricmp(token, "perform-actions")) strcpy_s(token, "perform-actions-bool-first"); else if (!stricmp(token, "add-to-collision-group2")) strcpy_s(token, "add-to-collision-group-new"); else if (!stricmp(token, "remove-from-collision-group2")) strcpy_s(token, "remove-from-collision-group-new"); op = get_operator_index(token); if (op >= 0) { node = alloc_sexp(token, SEXP_ATOM, SEXP_ATOM_OPERATOR, -1, -1); } // it's not an operator, and we've checked for variables, so treat it as a number else { node = alloc_sexp(token, SEXP_ATOM, SEXP_ATOM_NUMBER, -1, -1); } } } // update links if (start == -1) { start = node; } else { const char *message = nullptr; Assert(last != -1); // Locked_sexp_true and Locked_sexp_false are only meant to represent operator // nodes with no arguments, i.e. (true) and (false). If they appear as "bare" // arguments to another operator, or have arguments of their own, the global // SEXP node structure can become corrupted; we hack around this by always // wrapping them in their own list in these cases and warning the user. if (last == start && (start == Locked_sexp_false || start == Locked_sexp_true)) { message = "Found true or false operator at the start of a list, likely in a handwritten SEXP."; start = alloc_sexp("", SEXP_LIST, SEXP_ATOM_LIST, start, -1); last = start; } if (node == Locked_sexp_false || node == Locked_sexp_true) { message = "Found true or false operator in non-operator position, likely in a handwritten SEXP."; node = alloc_sexp("", SEXP_LIST, SEXP_ATOM_LIST, node, -1); } Sexp_nodes[last].rest = node; if (message != nullptr) { SCP_string context; convert_sexp_to_string(context, start, SEXP_ERROR_CHECK_MODE); Warning(LOCATION, "%s\n\nCurrently parsed SEXP, with fixed operator:\n%s\n", message, context.c_str()); } } Assert(node != -1); // ran out of nodes. Time to raise the MAX! last = node; ignore_white_space(); } Mp++; // skip past the ')' if (start == -1) return -1; // Goober5000 - backwards compatibility for removed ai-chase-any-except if (get_operator_const(start) == OP_AI_CHASE_ANY && prune_extra_args) { // if there is more than one argument, free the extras int n = CDR(CDR(start)); if (n >= 0) { // free the entire rest of the argument list free_sexp2(n, CDR(start)); } } // Goober5000 - preload stuff for certain sexps if (!Fred_running) { int n, parent, arg_handler = -1; // see if we're using special arguments parent = find_parent_operator(start); if (parent >= 0 && is_when_argument_op(get_operator_const(parent))) { // get the first op of the parent, which should be a *_of operator arg_handler = CADR(parent); if (arg_handler >= 0 && !is_argument_provider_op(get_operator_const(arg_handler))) arg_handler = -1; } // DISABLE ARGUMENT PRELOADING FOR NOW // see Mantis #925 for discussion // Also, the preloader will have to be moved to a different function (after the parsing is finished) // in order to work properly with special arguments. The current node is an orphan until get_sexp // completes, at which time it will be linked into the sexp node list; this means that it is // impossible to get the parent node. arg_handler = -1; // preload according to sexp op = get_operator_const(start); switch (op) { case OP_CHANGE_SHIP_CLASS: // ship class is argument #1 n = CDR(start); do_preload_for_arguments(preload_change_ship_class, n, arg_handler); break; case OP_SHIP_CREATE: // ship class is argument #2 n = CDDR(start); // page in ship classes of dynamically created ships // preload_change_ship_class doesn't require a class change, so we can use that here -zookeeper do_preload_for_arguments(preload_change_ship_class, n, arg_handler); break; case OP_PROP_CREATE: // prop class is argument #2 n = CDDR(start); // page in prop classes of dynamically created props do_preload_for_arguments(preload_change_prop_class, n, arg_handler); break; case OP_SET_SPECIAL_WARPOUT_NAME: // set flag for taylor Knossos_warp_ani_used = true; break; case OP_MISSION_SET_NEBULA: // set flag for WMC Nebula_sexp_used = true; break; case OP_MISSION_SET_SUBSPACE: // set flag for Goober5000 The_mission.flags.set(Mission::Mission_Flags::Preload_subspace); break; case OP_WARP_EFFECT: // type of warp is argument #11 n = CDDDDDR(start); n = CDDDDDR(n); n = CDR(n); // set flag for taylor if (CAR(n) != -1 || Sexp_nodes[n].flags & SNF_SPECIAL_ARG_IN_NODE) // if it's evaluating a sexp or a special argument Knossos_warp_ani_used = true; // set flag just in case else if (atoi(CTEXT(n)) != 0) // if it's not the default 0 Knossos_warp_ani_used = true; // set flag just in case break; case OP_SET_SKYBOX_MODEL: // model is argument #1 n = CDR(start); do_preload_for_arguments(sexp_set_skybox_model_preload, n, arg_handler); break; case OP_CONFIG_ASTEROID_FIELD: // asteroid types start at argument #17 n = CDDDDDR(start); n = CDDDDDR(n); n = CDDDDDR(n); n = CDDR(n); // loop through all remaining arguments for (int arg = n; arg >= 0; arg = CDR(arg)) { do_preload_for_arguments(preload_asteroid_class, arg, arg_handler); } break; case OP_CONFIG_DEBRIS_FIELD: // debris types start at argument #10 n = CDDDDDR(start); n = CDDDDDR(n); // loop through all remaining arguments for (int arg = n; arg >= 0; arg = CDR(arg)) { do_preload_for_arguments(preload_debris_class, arg, arg_handler); } break; case OP_TURRET_CHANGE_WEAPON: // weapon to change to is arg #3 n = CDDDR(start); do_preload_for_arguments(preload_turret_change_weapon, n, arg_handler); break; case OP_WEAPON_CREATE: // weapon class is argument #2 n = CDDR(start); // just as with change-ship-class/ship-create, we can use the turret-change-weapon preloader for weapon-create do_preload_for_arguments(preload_turret_change_weapon, n, arg_handler); break; case OP_BEAM_FLOATING_FIRE: // beam weapon class is argument #1 n = CDR(start); // see above for weapon-create do_preload_for_arguments(preload_turret_change_weapon, n, arg_handler); break; case OP_CHANGE_BACKGROUND: n = CDR(start); do_preload_for_arguments(stars_preload_background, n, arg_handler); break; case OP_ADD_SUN_BITMAP: case OP_ADD_SUN_BITMAP_NEW: n = CDR(start); do_preload_for_arguments(stars_preload_sun_bitmap, n, arg_handler); break; case OP_ADD_BACKGROUND_BITMAP: case OP_ADD_BACKGROUND_BITMAP_NEW: n = CDR(start); do_preload_for_arguments(stars_preload_background_bitmap, n, arg_handler); break; case OP_TECH_ADD_INTEL_XSTR: case OP_TECH_REMOVE_INTEL_XSTR: // do XSTR translation for each entry in the list // we don't use the do_preload function because the preloader needs to access two nodes at a time // also we're not using CTEXT or eval_num here because XSTR should really be constant, and // also because we can't really run sexp stuff in a preloader n = CDR(start); while (n >= 0 && CDR(n) >= 0) { localize_sexp(n, CDR(n)); n = CDDR(n); } break; case OP_MODIFY_VARIABLE_XSTR: n = CDDR(start); // First parameter is the variable name so we need to get the second parameter localize_sexp(n, CDR(n)); break; case OP_REPLACE_TEXTURE: case OP_REPLACE_TEXTURE_SKYBOX: //Texture name is argument 2 n = CDDR(start); do_preload_for_arguments(preload_texture, n, arg_handler); break; } } 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]; char persistent[TOKEN_LENGTH]; char network[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); throw parse::ParseException("Syntax error"); } 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(); // determine type 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")) { // Goober5000 - This looks dangerous... these flags are needed for certain things, but it // looks like BLOCK_*_SIZE is the only thing that keeps a block from running off the end // of its boundary. type = SEXP_VARIABLE_BLOCK; } else { type = SEXP_VARIABLE_UNKNOWN; error_display(1, "SEXP variable '%s' is an unknown type!", var_name); throw parse::ParseException("Unknown variable type"); } // possibly get network-variable if (check_for_string("\"network-variable\"")) { // eat it get_string(network); ignore_white_space(); // set type type |= SEXP_VARIABLE_NETWORK; } // maybe this is an eternal persistent variable of some type if (check_for_string("\"eternal\"")) { // eat it get_string(persistent); ignore_white_space(); // set type type |= SEXP_VARIABLE_SAVE_TO_PLAYER_FILE; } // maybe this is a persistent variable of some type if (check_for_string("\"player-persistent\"") || check_for_string("\"save-on-mission-close\"")) { // eat it get_string(persistent); ignore_white_space(); // set type type |= SEXP_VARIABLE_SAVE_ON_MISSION_CLOSE; } else if (check_for_string("\"campaign-persistent\"") || check_for_string("\"save-on-mission-progress\"")) { // eat it get_string(persistent); ignore_white_space(); // set type type |= SEXP_VARIABLE_SAVE_ON_MISSION_PROGRESS; // trap error } else if (check_for_string("\"")) { // eat garbage get_string(persistent); ignore_white_space(); // notify of error error_display(0, "Error parsing sexp variables - unknown persistence type encountered. You can continue from here without trouble."); } // finished getting info; now add the variable if ( type & SEXP_VARIABLE_BLOCK ) { add_block_variable(default_value, var_name, type, index); } else { // check if variable name already exists // (block variables can be duplicated, but regular ones can't) if (get_index_sexp_variable_name(var_name) >= 0) { error_display(0, "Variable '%s' already exists! Skipping duplicate.", var_name); } else { count++; sexp_add_variable(default_value, var_name, type, index); } } } Mp++; return count; } bool has_special_explosion_block_index(ship *shipp, int *index) { int current_index; for ( current_index = (MAX_SEXP_VARIABLES - BLOCK_EXP_SIZE) ; current_index >= (MAX_SEXP_VARIABLES - (BLOCK_EXP_SIZE * Num_special_expl_blocks)) ; current_index = (current_index - BLOCK_EXP_SIZE) ) { if ( (atoi(Block_variables[current_index+INNER_RAD].text) == shipp->special_exp_inner) && (atoi(Block_variables[current_index+OUTER_RAD].text) == shipp->special_exp_outer) && (atoi(Block_variables[current_index+DAMAGE].text) == shipp->special_exp_damage) && (atoi(Block_variables[current_index+BLAST].text) == shipp->special_exp_blast) && (atoi(Block_variables[current_index+PROPAGATE].text) == (shipp->use_shockwave ? 1:0) ) && (atoi(Block_variables[current_index+SHOCK_SPEED].text) == shipp->special_exp_shockwave_speed) ) { *index = current_index; return true; } } // if we got here, this ship's special explosions aren't represented in the Block_variables array *index = current_index; return false; } bool generate_special_explosion_block_variables() { ship *shipp; int current_index; bool already_added = false; int num_sexp_variables; int i; // since we're (re)generating the block variable index, we don't start off with any block variables Num_special_expl_blocks = 0; // get the number of sexp_variables we currently have. We must not try to add a block variable with an index below this. num_sexp_variables = sexp_variable_count(); for ( auto sop = GET_FIRST(&Ship_obj_list); sop != END_OF_LIST(&Ship_obj_list); sop = GET_NEXT(sop) ) { if (Objects[sop->objnum].flags[Object::Object_Flags::Should_be_dead]) continue; shipp=&Ships[Objects[sop->objnum].instance]; if (!(shipp->use_special_explosion)) { continue; } already_added = has_special_explosion_block_index(shipp, ¤t_index); // if we can't add an index for this add this ship to the list of those which failed if (current_index < num_sexp_variables) { // fail list code goes here continue; } //if we haven't added this entry already, do so if (!already_added) { sprintf(Block_variables[current_index+INNER_RAD].text, "%d", shipp->special_exp_inner); sprintf(Block_variables[current_index+OUTER_RAD].text, "%d", shipp->special_exp_outer); sprintf(Block_variables[current_index+DAMAGE].text, "%d", shipp->special_exp_damage); sprintf(Block_variables[current_index+BLAST].text, "%d", shipp->special_exp_blast); sprintf(Block_variables[current_index+PROPAGATE].text, "%d", (shipp->use_shockwave ? 1:0) ); sprintf(Block_variables[current_index+SHOCK_SPEED].text, "%d", shipp->special_exp_shockwave_speed); // add the names for (i = current_index; i < (current_index + BLOCK_EXP_SIZE); i++ ) { sprintf(Block_variables[i].variable_name, "%s", shipp->ship_name); } Num_special_expl_blocks++; } } return true; } int num_block_variables() { return Num_special_expl_blocks * BLOCK_EXP_SIZE; } /** * Stuff this particular SEXP node (just the node, not the tree) into a string representation */ void stuff_sexp_text_string(SCP_string &dest, int node, int mode) { Assert((node >= 0) && (node < Num_sexp_nodes)); if (Sexp_nodes[node].subtype == SEXP_ATOM_CONTAINER_NAME) { Assertion(get_sexp_container(Sexp_nodes[node].text) != nullptr, "Couldn't find container: %s\n", Sexp_nodes[node].text); sprintf(dest, "%s%s ", sexp_container::NAME_NODE_PREFIX.c_str(), Sexp_nodes[node].text); } else if (Sexp_nodes[node].subtype == SEXP_ATOM_CONTAINER_DATA) { Assertion(get_sexp_container(Sexp_nodes[node].text) != nullptr, "Couldn't find container: %s\n", Sexp_nodes[node].text); sprintf(dest, "%c%s%c ", sexp_container::DELIM, Sexp_nodes[node].text, sexp_container::DELIM); } else if (Sexp_nodes[node].type & SEXP_FLAG_VARIABLE) { int sexp_variables_index = get_index_sexp_variable_name(Sexp_nodes[node].text); // during the last pass through error-reporting mode, sexp variables have already been transcoded to their indexes if (mode == SEXP_ERROR_CHECK_MODE && sexp_variables_index < 0) { if (can_construe_as_integer(Sexp_nodes[node].text)) sexp_variables_index = atoi(Sexp_nodes[node].text); } const char *var_name, *var_contents; if (sexp_variables_index < 0) { Warning(LOCATION, "Couldn't find variable: %s\n", Sexp_nodes[node].text); var_name = Sexp_nodes[node].text; var_contents = "undefined"; } else { var_name = (Fred_running) ? Sexp_nodes[node].text : Sexp_variables[sexp_variables_index].variable_name; var_contents = Sexp_variables[sexp_variables_index].text; Assertion((Sexp_variables[sexp_variables_index].type & SEXP_VARIABLE_NUMBER) || (Sexp_variables[sexp_variables_index].type & SEXP_VARIABLE_STRING), "Variable %s must be either a number or a string!", var_name); } // number if (Sexp_nodes[node].subtype == SEXP_ATOM_NUMBER) { sprintf(dest, "@%s[%s] ", var_name, var_contents); } // string else if (Sexp_nodes[node].subtype == SEXP_ATOM_STRING) { sprintf(dest, "\"@%s[%s]\" ", var_name, var_contents); } else Assertion(false, "SEXP variable nodes must be SEXP_ATOM_NUMBER or SEXP_ATOM_STRING!"); } // not a variable else { const char *ctext_string = CTEXT(node); // strings are enclosed in quotes if (Sexp_nodes[node].subtype == SEXP_ATOM_STRING) { sprintf(dest, "\"%s\" ", ctext_string); } // numbers and operators are printed as-is else { // do some sanity checking based on Github issue #2314 if (Sexp_nodes[node].subtype == SEXP_ATOM_NUMBER) { // if (for whatever reason) we have an empty string or an invalid number, print 0 // do not do this trick in error mode, because we want to know what the text actually is // note that we let strings pass here if ((mode != SEXP_ERROR_CHECK_MODE) && (!(Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_NODE)) && (*ctext_string == '\0' || !can_construe_as_integer(ctext_string))) { mprintf(("SEXP: '%s' is not a number; using '0' instead\n", ctext_string)); ctext_string = "0"; } } sprintf(dest, "%s ", ctext_string); } } } int build_sexp_string(SCP_string &accumulator, int cur_node, int level, int mode) { SCP_string buf; int node; auto old_length = accumulator.length(); accumulator += "( "; node = cur_node; while (node != -1) { Assert(node >= 0 && node < Num_sexp_nodes); if (Sexp_nodes[node].first == -1) { // build text to string stuff_sexp_text_string(buf, node, mode); accumulator += buf; } else if (Sexp_nodes[node].subtype == SEXP_ATOM_CONTAINER_DATA) { // build text to string stuff_sexp_text_string(buf, node, mode); accumulator += buf; build_sexp_string(accumulator, Sexp_nodes[node].first, level + 1, mode); } else { build_sexp_string(accumulator, Sexp_nodes[node].first, level + 1, mode); } node = Sexp_nodes[node].rest; } accumulator += ") "; if ((accumulator.length() - old_length) > 40) { accumulator.resize(old_length); build_extended_sexp_string(accumulator, cur_node, level, mode); return 1; } return 0; } void build_extended_sexp_string(SCP_string &accumulator, int cur_node, int level, int mode) { SCP_string buf; int i, flag = 0, node; accumulator += "( "; node = cur_node; while (node != -1) { // not the first line? if (flag) { for (i=0; i= 0 && node < Num_sexp_nodes); if (Sexp_nodes[node].first == -1) { stuff_sexp_text_string(buf, node, mode); accumulator += buf; } else if (Sexp_nodes[node].subtype == SEXP_ATOM_CONTAINER_DATA) { // build text to string stuff_sexp_text_string(buf, node, mode); accumulator += buf; build_sexp_string(accumulator, Sexp_nodes[node].first, level + 1, mode); } else { build_sexp_string(accumulator, Sexp_nodes[node].first, level + 1, mode); } accumulator += "\n"; node = Sexp_nodes[node].rest; } for (i=0; i= 0) { dest = ""; build_sexp_string(dest, cur_node, 0, mode); } else { dest = "( )"; } } // ----------------------------------------------------------------------------------- // Helper methods for getting data from nodes. Cause it's stupid to keep re-rolling this stuff for every single SEXP // ----------------------------------------------------------------------------------- bool is_node_opf_positive(int node) { if (node < 0) return false; if (!(Sexp_nodes[node].flags & SNF_CHECKED_NODE_FOR_OPF_POSITIVE)) { int parent_node = find_parent_operator(node); // if the SEXP has no parent, the point is moot if (parent_node >= 0) { int arg_num = find_argnum(parent_node, node); Assertion(arg_num >= 0, "Error finding sexp argument. The SEXP is not listed among its parent's children."); if (query_operator_argument_type(get_operator_index(parent_node), arg_num) == OPF_POSITIVE) Sexp_nodes[node].flags |= SNF_NODE_IS_OPF_POSITIVE; } Sexp_nodes[node].flags |= SNF_CHECKED_NODE_FOR_OPF_POSITIVE; } return (Sexp_nodes[node].flags & SNF_NODE_IS_OPF_POSITIVE); } // Goober5000's number hack - ensure negative numbers aren't sent to parameters that expect OPF_POSITIVE void ensure_opf_positive_is_positive(int node, int &val) { if (is_node_opf_positive(node) && (val < 0) && (val > SEXP_UNLIKELY_RETURN_VALUE_BOUND)) { // warn about it, but only once static bool Warned_about_opf_positive = false; if (!Warned_about_opf_positive) { int parent_node = find_parent_operator(node); // these calls are known to be valid because int arg_num = find_argnum(parent_node, node); // they are prerequisites to marking the node Warning(LOCATION, "Parent node \"%s\", argument %d (token \"%s\", value %d) is negative, but is required to be positive!", Sexp_nodes[parent_node].text, arg_num + 1, Sexp_nodes[node].text, val); Warned_about_opf_positive = true; } // clamp it to avoid an underflow val = 0; } } /** * Evaluate number which may result from an operator or may be text */ int eval_num(int n, bool &is_nan, bool &is_nan_forever) { is_nan = false; is_nan_forever = false; // see precedent in eval_sexp if (n == -1) return 0; Assert(n >= 0); int op_n = CAR(n); // CAR() of a container data node is the container modifier if (op_n >= 0 && Sexp_nodes[n].subtype != SEXP_ATOM_CONTAINER_DATA) // if argument is a sexp { int val = eval_sexp(op_n); // NaNs will propagate through operations, so let the calling function know if (Sexp_nodes[op_n].value == SEXP_NAN) { val = 0; is_nan = true; } else if (Sexp_nodes[op_n].value == SEXP_NAN_FOREVER) { val = 0; is_nan_forever = true; } else ensure_opf_positive_is_positive(op_n, val); return val; } else return sexp_atoi(n); // otherwise, just get the number (this function already contains an OPF_POSITIVE check) } template int eval_nums(int &n, bool &is_nan, bool &is_nan_forever, T &arg) { if (n >= 0) { int val = eval_num(n, is_nan, is_nan_forever); n = CDR(n); arg = (T)val; return 1; } else { is_nan = false; is_nan_forever = false; arg = (T)0; return 0; } } /** * Populate variadic arguments by running eval_num repeatedly. No custom converter function is used; all numbers are cast from int to the desired type. * The count of numbers actually found (which depending on the sexp may not be the count of parameters) is returned. * NOTE: in contrast to eval_num, the *n* parameter will be advanced along the CDR path */ template int eval_nums(int &n, bool &is_nan, bool &is_nan_forever, T& first, Args&... rest) { bool temp_nan, temp_nan_forever; int count = 0; is_nan = false; is_nan_forever = false; count += eval_nums(n, temp_nan, temp_nan_forever, first); if (temp_nan) is_nan = true; if (temp_nan_forever) is_nan_forever = true; count += eval_nums(n, temp_nan, temp_nan_forever, rest...); if (temp_nan) is_nan = true; if (temp_nan_forever) is_nan_forever = true; return count; } /** * Populate a numeric array by running eval_num repeatedly. The converter function/lambda can be used to adapt the numbers returned from eval_num, such as casting (the default) * or restricting to a range. The count of numbers actually found (which depending on the sexp may not be the size of the array) is returned. * NOTE: in contrast to eval_num, the *n* parameter will be advanced along the CDR path */ template int eval_array(std::array &numbers, int &n, bool &is_nan, bool &is_nan_forever, T(*converter)(int), const T &value_if_missing = (T)0) { bool temp_nan, temp_nan_forever; int count = 0; is_nan = false; is_nan_forever = false; // fill up the array for (std::size_t i = 0; i < SIZE; ++i) { // see if we have a number if (n >= 0) { int num = eval_num(n, temp_nan, temp_nan_forever); n = CDR(n); ++count; if (temp_nan) is_nan = true; if (temp_nan_forever) is_nan_forever = true; // populate the array with that number numbers[i] = converter(num); } // use the default else numbers[i] = value_if_missing; } return count; } /** * Certain compilers don't like a lambda as a default argument, so here's an extra function definition that supplies a standard converter. Thanks to Gasbow for the insight. */ template int eval_array(std::array &numbers, int &n, bool &is_nan, bool &is_nan_forever) { return eval_array(numbers, n, is_nan, is_nan_forever, [](int num) -> T { return (T)num; }); } /** * Populate a vector by running eval_num on up to three consecutive nodes. Returns the number of nodes processed. * NOTE: in contrast to eval_num, the *n* parameter will be advanced along the CDR path */ int eval_vec3d(vec3d *vec, int &n, bool &is_nan, bool &is_nan_forever) { Assertion(vec != nullptr, "Vec must not be null!"); // this is slightly more verbose than ideal, because one can't cast between C and C++ arrays std::array a1d; int count = eval_array(a1d, n, is_nan, is_nan_forever); std::copy(a1d.begin(), a1d.end(), std::begin(vec->a1d)); return count; } /** * Populate an angles struct by running eval_num on up to three consecutive nodes. Returns the number of nodes processed. * NOTE: in contrast to eval_num, the *n* parameter will be advanced along the CDR path */ int eval_angles(angles *a, int &n, bool &is_nan, bool &is_nan_forever) { Assertion(a != nullptr, "a must not be null!"); std::array a1d; int count = eval_array(a1d, n, is_nan, is_nan_forever); a->p = fl_radians(a1d[0] % 360); a->b = fl_radians(a1d[1] % 360); a->h = fl_radians(a1d[2] % 360); return count; } /** * Takes a ship entry and returns the player for that ship or NULL if it is an AI ship. * In singleplayer mode, this is *the* player, but in multiplayer mode it could be any player. */ player *get_player_from_ship_entry(const ship_registry_entry *ship_entry, bool test_respawns = false, int *netplayer_index = nullptr) { Assertion(ship_entry, "ship_entry cannot be null!"); if (netplayer_index) *netplayer_index = -1; // singleplayer if (!(Game_mode & GM_MULTIPLAYER)) { if (ship_entry->objnum >= 0) { if (OBJ_INDEX(Player_obj) == ship_entry->objnum) { return Player; } } if (ship_entry->has_p_objp()) { if (ship_entry->p_objp()->flags[Mission::Parse_Object_Flags::OF_Player_start]) { return Player; } } return nullptr; } // multiplayer else { int np_index = -1; if (ship_entry->has_objp()) { // try and find the player np_index = multi_find_player_by_object(ship_entry->objp()); } if (test_respawns && np_index < 0) { // Respawning ships don't have an objnum so we need to take a different approach if (ship_entry->has_p_objp()) { np_index = multi_find_player_by_parse_object(ship_entry->p_objp()); } } if ((np_index >= 0) && (np_index < MAX_PLAYERS)) { if (netplayer_index) *netplayer_index = np_index; return Net_players[np_index].m_player; } return nullptr; } } /** * Forwards to get_player_from_ship_entry using a ship_entry generated from this SEXP node. */ player *get_player_from_ship_node(int node, bool test_respawns = false, int *netplayer_index = nullptr) { auto ship_entry = eval_ship(node); if (!ship_entry) return nullptr; return get_player_from_ship_entry(ship_entry, test_respawns, netplayer_index); } enum class ProcessSubsystemType : uint8_t { SUBSYSTEM, HULL, SIM_HULL, SHIELDS }; const char *special_subsystem_string(ProcessSubsystemType type) { switch (type) { case ProcessSubsystemType::HULL: return SEXP_HULL_STRING; case ProcessSubsystemType::SIM_HULL: return SEXP_SIM_HULL_STRING; case ProcessSubsystemType::SHIELDS: return SEXP_SHIELD_STRING; case ProcessSubsystemType::SUBSYSTEM: default: return nullptr; } } /* * Iterates through a subsystem list and applies the given function to each subsystem, handling the "hull" subsystem as well as (optionally) generic subsystems. The function's prototype should be: * * void f(ProcessSubsystemType type, ship_subsys *ss); * * If any of the booleans are true, ss will be nullptr. */ template void process_ship_subsystems(const ship_registry_entry *ship_entry, bool allow_generic_subsystems, int node, bool iterate_nodes, const char *sexp_operator_name, Function f) { Assertion(sexp_operator_name, "sexp_operator_name must not be null!"); if (node < 0) return; if (!ship_entry || !ship_entry->has_shipp()) return; auto shipp = ship_entry->shipp(); // handle the subsystem node(s) for (int n = node; n >= 0; n = CDR(n)) { auto subsys_name = CTEXT(n); // check for HULL if (!stricmp(subsys_name, SEXP_HULL_STRING)) { f(ProcessSubsystemType::HULL, nullptr); } // check for SIM_HULL else if (!stricmp(subsys_name, SEXP_SIM_HULL_STRING)) { f(ProcessSubsystemType::SIM_HULL, nullptr); } // check for shields else if (!stricmp(subsys_name, SEXP_SHIELD_STRING)) { f(ProcessSubsystemType::SHIELDS, nullptr); } // some kind of subsystem... else { // check for a generic subsystem like int generic_type = get_generic_subsys(subsys_name); if (generic_type) { if (allow_generic_subsystems) { // search through all subsystems for (auto ss : list_range(&shipp->subsys_list)) { if (generic_type == SUBSYSTEM_ALL || generic_type == ss->system_info->type) f(ProcessSubsystemType::SUBSYSTEM, ss); } } else Warning(LOCATION, "Generic subsystem %s passed to %s. This operator does not support generic subsystems.", subsys_name, sexp_operator_name); } // it's a regular subsystem else { auto ss = ship_get_subsys(shipp, subsys_name); if (ss) f(ProcessSubsystemType::SUBSYSTEM, ss); else if (ship_class_unchanged(ship_entry)) Warning(LOCATION, "Invalid subsystem passed to %s: %s does not have a %s subsystem", sexp_operator_name, ship_entry->name, subsys_name); } } // some operators have only one subsystem node if (!iterate_nodes) break; } } // ----------------------------------------------------------------------------------- // SEXP caching // ----------------------------------------------------------------------------------- /** * Gets a ship from a sexp node. Returns the ship registry entry, or NULL if the ship is unknown. */ const ship_registry_entry *eval_ship(int node) { if (node < 0) return nullptr; // check cache if (Sexp_nodes[node].cache) { // have we cached something else? if (Sexp_nodes[node].cache->sexp_node_data_type != OPF_SHIP) return nullptr; return &Ship_registry[Sexp_nodes[node].cache->ship_registry_index]; } // maybe forward to a special-arg node if (Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_NODE) { auto current_argument = Sexp_replacement_arguments.back(); int arg_node = current_argument.second; if (arg_node >= 0) return eval_ship(arg_node); } // look up the ship in the ship registry auto ship_name = CTEXT(node); auto ship_it = Ship_registry_map.find(ship_name); if (ship_it == Ship_registry_map.end()) { SCP_string legacy_hashed; if (wing_bash_legacy_hashed_ship_name(legacy_hashed, ship_name)) ship_it = Ship_registry_map.find(legacy_hashed); } if (ship_it != Ship_registry_map.end()) { // cache the value if it can't change later and we're in-game if (!Fred_running && !is_node_value_dynamic(node)) Sexp_nodes[node].cache = std::make_unique(OPF_SHIP, -1, ship_it->second); return &Ship_registry[ship_it->second]; } // we know nothing about this ship, apparently return nullptr; } /** * Gets a prop from a sexp node. Returns the prop entry, or nullptr if the prop is unknown. * May be overkill for props. Research required. */ const prop *eval_prop(int node) { if (node < 0) return nullptr; // check cache if (Sexp_nodes[node].cache) { // have we cached something else? if (Sexp_nodes[node].cache->sexp_node_data_type != OPF_PROP) return nullptr; return prop_id_lookup(Sexp_nodes[node].cache->ship_registry_index); } // maybe forward to a special-arg node if (Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_NODE) { auto current_argument = Sexp_replacement_arguments.back(); int arg_node = current_argument.second; if (arg_node >= 0) return eval_prop(arg_node); } auto prop_idx = prop_name_lookup(CTEXT(node)); if (prop_idx >= 0) { // cache the value if it can't change later and we're in-game if (!Fred_running && !is_node_value_dynamic(node)) Sexp_nodes[node].cache = std::make_unique(OPF_PROP, -1, prop_idx); return prop_id_lookup(prop_idx); } // we know nothing about this prop, apparently return nullptr; } /** * Gets a wing from a sexp node. Returns the wing pointer, or NULL if the wing is unknown. */ wing *eval_wing(int node) { if (node < 0) return nullptr; // check cache if (Sexp_nodes[node].cache) { // have we cached something else? if (Sexp_nodes[node].cache->sexp_node_data_type != OPF_WING) return nullptr; return &Wings[Sexp_nodes[node].cache->other_index]; } // maybe forward to a special-arg node if (Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_NODE) { auto current_argument = Sexp_replacement_arguments.back(); int arg_node = current_argument.second; if (arg_node >= 0) return eval_wing(arg_node); } auto wing_name = CTEXT(node); int wing_num = Fred_running ? wing_name_lookup(wing_name) : wing_lookup(wing_name); if (wing_num >= 0) { // cache the value if it can't change later and we're in-game if (!Fred_running && !is_node_value_dynamic(node)) Sexp_nodes[node].cache = std::make_unique(OPF_WING, wing_num); return &Wings[wing_num]; } // it must not be a wing return nullptr; } /** * Returns a number parsed from the sexp node text. * NOTE: sexp_atoi() should only replace atoi(CTEXT(n)) - it should not replace atoi(Sexp_nodes[node].text) - see commit 9923c87bc1 */ int sexp_atoi(int node) { if (node < 0) return 0; // SEXP caching is not set up to work in FRED, so bypass all the caching code in that case if (!Fred_running) { // check cache if (Sexp_nodes[node].cache) { // have we cached something else? if (Sexp_nodes[node].cache->sexp_node_data_type != OPF_NUMBER) return 0; return Sexp_nodes[node].cache->numeric_literal; } // maybe forward to a special-arg node if (Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_NODE) { auto current_argument = Sexp_replacement_arguments.back(); int arg_node = current_argument.second; if (arg_node >= 0) return sexp_atoi(arg_node); } } int num = atoi(CTEXT(node)); ensure_opf_positive_is_positive(node, num); // cache the value if it can't change later and we're in-game if (!Fred_running && !is_node_value_dynamic(node)) Sexp_nodes[node].cache = std::make_unique(OPF_NUMBER, num, -1); return num; } /** * A version of can_construe_as_integer that knows about number caching. */ bool sexp_can_construe_as_integer(int node) { if (node < 0) return false; // SEXP caching is not set up to work in FRED, so bypass all the caching code in that case if (!Fred_running) { if (Sexp_nodes[node].cache && Sexp_nodes[node].cache->sexp_node_data_type == OPF_NUMBER) return true; // maybe forward to a special-arg node if (Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_NODE) { auto current_argument = Sexp_replacement_arguments.back(); int arg_node = current_argument.second; if (arg_node >= 0) return sexp_can_construe_as_integer(arg_node); } } return can_construe_as_integer(CTEXT(node)); } /* * This can be used by both FRED and FSO. When in FSO, it incorporates caching that runs parallel to the main data caching. * Note that this function does not do special-arg forwarding because it operates on the node text, not the CTEXT, and thus * there is no possibility of processing a special argument. */ int sexp_get_variable_index(int node) { if (node < 0) return -1; if (Sexp_nodes[node].cached_variable_index >= 0) return Sexp_nodes[node].cached_variable_index; if (!(Sexp_nodes[node].type & SEXP_FLAG_VARIABLE)) return -1; Assert(Sexp_nodes[node].first == -1); if (Fred_running) return get_index_sexp_variable_name(Sexp_nodes[node].text); // parse it int index = atoi(Sexp_nodes[node].text); // verify variable set Assert(Sexp_variables[index].type & SEXP_VARIABLE_SET); // cache and return Sexp_nodes[node].cached_variable_index = index; return index; } // ----------------------------------------------------------------------------------- // SEXP implementations // ----------------------------------------------------------------------------------- // arithmetic functions int add_sexps(int n) { int val, sum = 0; if (n != -1) { if (CAR(n) != -1) { val = eval_sexp(CAR(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; sum = val; } else sum = sexp_atoi(n); 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[CDR(n)].value == SEXP_NAN_FOREVER) return SEXP_NAN_FOREVER; sum += val; n = CDR(n); } } return sum; } int sub_sexps(int n) { int val, sum = 0; if (n != -1) { if (CAR(n) != -1) { val = eval_sexp(CAR(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; sum = val; } else sum = sexp_atoi(n); 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[CDR(n)].value == SEXP_NAN_FOREVER) return SEXP_NAN_FOREVER; sum -= val; n = CDR(n); } } return sum; } int mul_sexps(int n) { int val, sum = 0; if (n != -1) { if (CAR(n) != -1) { val = eval_sexp(CAR(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; sum = val; } else sum = sexp_atoi(n); 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[CDR(n)].value == SEXP_NAN_FOREVER) return SEXP_NAN_FOREVER; sum *= val; n = CDR(n); } } return sum; } int div_sexps(int n) { int val, sum = 0; if (n != -1) { if (CAR(n) != -1) { val = eval_sexp(CAR(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; sum = val; } else sum = sexp_atoi(n); 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[CDR(n)].value == SEXP_NAN_FOREVER) return SEXP_NAN_FOREVER; if (val == 0) { Warning(LOCATION, "Division by zero in sexp. Please check all uses of the / operator for possible causes.\n"); return SEXP_NAN; } sum /= val; n = CDR(n); } } return sum; } int mod_sexps(int n) { int val, sum = 0; if (n != -1) { if (CAR(n) != -1) { val = eval_sexp(CAR(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; sum = val; } else sum = sexp_atoi(n); 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[CDR(n)].value == SEXP_NAN_FOREVER) return SEXP_NAN_FOREVER; if (val == 0) { Warning(LOCATION, "Modulo by zero in sexp. Please check all uses of the %% operator for possible causes.\n"); return SEXP_NAN; } sum = sum % val; n = CDR(n); } } return sum; } int rand_internal(int low, int high, int seed = 0) { // maybe seed it if (seed > 0) Random::seed(seed); return Random::next(low, high); } // Goober5000 int abs_sexp(int n) { bool is_nan, is_nan_forever; int val = eval_num(n, is_nan, is_nan_forever); if (is_nan) return SEXP_NAN; if (is_nan_forever) return SEXP_NAN_FOREVER; return abs(val); } // Goober5000 int min_sexp(int n) { bool is_nan, is_nan_forever; int temp, min_val = INT_MAX; while (n != -1) { temp = eval_num(n, is_nan, is_nan_forever); if (is_nan) return SEXP_NAN; if (is_nan_forever) return SEXP_NAN_FOREVER; if (temp < min_val) min_val = temp; n = CDR(n); } return min_val; } // Goober5000 int max_sexp(int n) { bool is_nan, is_nan_forever; int temp, max_val = INT_MIN; while (n != -1) { temp = eval_num(n, is_nan, is_nan_forever); if (is_nan) return SEXP_NAN; if (is_nan_forever) return SEXP_NAN_FOREVER; if (temp > max_val) max_val = temp; n = CDR(n); } return max_val; } // Goober5000 int avg_sexp(int n) { bool is_nan, is_nan_forever; int num = 0, avg_val = 0; while (n != -1) { int val = eval_num(n, is_nan, is_nan_forever); if (is_nan) return SEXP_NAN; if (is_nan_forever) return SEXP_NAN_FOREVER; avg_val += val; num++; n = CDR(n); } return (int) floor(((double) avg_val / num) + 0.5); } // Goober5000 int pow_sexp(int node) { int num_1, num_2; bool is_nan, is_nan_forever; eval_nums(node, is_nan, is_nan_forever, num_1, num_2); if (is_nan) return SEXP_NAN; if (is_nan_forever) return SEXP_NAN_FOREVER; // this is disallowed in FRED, but can still happen through careless arithmetic if (num_2 < 0) { Warning(LOCATION, "Power function pow(%d, %d) attempted to raise to a negative power!", num_1, num_2); return 0; } // shortcuts if (num_1 == 0 && num_2 == 0) { Warning(LOCATION, "Zero raised to the power of zero in sexp. Please check all uses of the power operator for possible causes.\n"); return SEXP_NAN; } else if (num_1 == 0 || num_1 == 1 || num_2 == 1) return num_1; else if (num_2 == 0) return 1; double pow_result = pow(static_cast(num_1), num_2); if (pow_result > static_cast(INT_MAX)) { nprintf(("SEXP", "Power function pow(%d, %d) is greater than INT_MAX! Returning INT_MAX.", num_1, num_2)); return INT_MAX; } else if (pow_result < static_cast(INT_MIN)) { nprintf(("SEXP", "Power function pow(%d, %d) is less than INT_MIN! Returning INT_MIN.", num_1, num_2)); return INT_MIN; } return static_cast(pow_result); } // is there a better place to put this? seems useful... // credit to http://stackoverflow.com/questions/1903954/is-there-a-standard-sign-function-signum-sgn-in-c-c template T sign(T t) { if (t == 0) return T(0); else return (t < 0) ? T(-1) : T(1); } // Goober5000 int signum_sexp(int node) { bool is_nan, is_nan_forever; int num = eval_num(node, is_nan, is_nan_forever); if (is_nan) return SEXP_NAN; if (is_nan_forever) return SEXP_NAN_FOREVER; return sign(num); } // Goober5000 int sexp_is_nan(int n) { // if this sexp has an operator, evaluate it if (CAR(n) != -1) { eval_sexp(CAR(n)); // see whether the result was NaN if (Sexp_nodes[CAR(n)].value == SEXP_NAN) return SEXP_TRUE; if (Sexp_nodes[CAR(n)].value == SEXP_NAN_FOREVER) return SEXP_KNOWN_TRUE; // see whether the result won't change if (Sexp_nodes[CAR(n)].value == SEXP_KNOWN_TRUE || Sexp_nodes[CAR(n)].value == SEXP_KNOWN_FALSE) return SEXP_KNOWN_FALSE; // it's not NaN, for now return SEXP_FALSE; } // straight-up numeric or string arguments cannot be NaN else return SEXP_KNOWN_FALSE; } // Goober5000 int sexp_nan_to_number(int n) { // if this sexp has an operator, evaluate it if (CAR(n) != -1) { int val = eval_sexp(CAR(n)); // see whether the result was NaN if (Sexp_nodes[CAR(n)].value == SEXP_NAN || Sexp_nodes[CAR(n)].value == SEXP_NAN_FOREVER) return 0; // if not NaN, result is acceptable return val; } // straight-up numeric or string arguments cannot be NaN else return sexp_atoi(n); } // Goober5000 int sexp_set_bit(int node, bool set_it) { int val, bit_index; bool is_nan, is_nan_forever; eval_nums(node, is_nan, is_nan_forever, val, bit_index); if (is_nan) return SEXP_NAN; if (is_nan_forever) return SEXP_NAN_FOREVER; if (bit_index < 0 || bit_index > 31) { // out of range, so just return the original value // (we used to warn, but it's more convenient to FREDders to fail gracefully) return val; } if (set_it) return val | (1< 31) { // out of range, so just return false // (we used to warn, but it's more convenient to FREDders to fail gracefully) return SEXP_FALSE; } if (val & (1< high) { Warning(LOCATION, "rand%s was passed an invalid range [%d, %d]!", multiple ? "-multiple" : "", low, high); // preserve old behavior from before Random class was introduced return low; } // get the random number rand_num = rand_internal(low, high, seed); // when saving the value if (!multiple) { // set .value and .text so random number is generated only once. Sexp_nodes[node].value = SEXP_NUM_EVAL; sprintf(Sexp_nodes[node].text, "%d", rand_num); // any cached value is no longer relevant because we just changed the text clear_cache(node); } // if this is multiple with a nonzero seed provided else if (seed > 0) { // Set the seed to a new seeded random value. This will ensure that the next time the method // is called it will return a predictable but different number from the previous time. sprintf(Sexp_nodes[CDDR(node)].text, "%d", rand_internal(1, INT_MAX, seed)); // any cached value is no longer relevant because we just changed the text clear_cache(CDDR(node)); } 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) { bool all_false = true; bool result = false; if (n != -1) { if (CAR(n) != -1) { result = is_sexp_true(CAR(n)) || result; if (Sexp_nodes[CAR(n)].value == SEXP_KNOWN_TRUE) return SEXP_KNOWN_TRUE; // if one of the OR clauses is TRUE, whole clause is true if (Sexp_nodes[CAR(n)].value != SEXP_KNOWN_FALSE) // if the value is still unknown, they all can't be false all_false = false; } // this should never happen, because all arguments which return logical values are operators else result = (sexp_atoi(n) != 0) || result; while (CDR(n) != -1) { result = is_sexp_true(CDR(n)) || result; if ( Sexp_nodes[CDR(n)].value == SEXP_KNOWN_TRUE ) return SEXP_KNOWN_TRUE; // if one of the OR clauses is TRUE, whole clause is true if ( Sexp_nodes[CDR(n)].value != SEXP_KNOWN_FALSE ) // if the value is still unknown, they all can't be false all_false = false; n = CDR(n); } } if (all_false) return SEXP_KNOWN_FALSE; return result ? SEXP_TRUE : SEXP_FALSE; } // 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) { bool all_true = true; bool result = true; if (n != -1) { if (CAR(n) != -1) { result = is_sexp_true(CAR(n)) && result; if ( Sexp_nodes[CAR(n)].value == SEXP_KNOWN_FALSE || Sexp_nodes[CAR(n)].value == SEXP_NAN_FOREVER ) return SEXP_KNOWN_FALSE; // if one of the AND clauses is FALSE, whole clause is false if ( Sexp_nodes[CAR(n)].value != SEXP_KNOWN_TRUE ) // if the value is still unknown, they all can't be true all_true = false; } // this should never happen, because all arguments which return logical values are operators else result = (sexp_atoi(n) != 0) && result; while (CDR(n) != -1) { result = is_sexp_true(CDR(n)) && result; if ( Sexp_nodes[CDR(n)].value == SEXP_KNOWN_FALSE || Sexp_nodes[CDR(n)].value == SEXP_NAN_FOREVER ) return SEXP_KNOWN_FALSE; // if one of the AND clauses is FALSE, whole clause is false if ( Sexp_nodes[CDR(n)].value != SEXP_KNOWN_TRUE ) // if the value is still unknown, they all can't be true all_true = false; n = CDR(n); } } if (all_true) return SEXP_KNOWN_TRUE; return result ? SEXP_TRUE : SEXP_FALSE; } // this version of the 'and' operator determines whether or not its 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 evaluated to false int sexp_and_in_sequence(int n) { bool all_true = true; // represents whether or not all nodes we have seen so far are true bool result = true; if (n != -1) { if (CAR(n) != -1) { result = is_sexp_true(CAR(n)) && result; if ( Sexp_nodes[CAR(n)].value == SEXP_KNOWN_FALSE || Sexp_nodes[CAR(n)].value == SEXP_NAN_FOREVER ) return SEXP_KNOWN_FALSE; // if one of the AND clauses is FALSE, whole clause is false if ( Sexp_nodes[CAR(n)].value != SEXP_KNOWN_TRUE ) // if value is true, mark our all_true variable for later checking all_true = false; } // this should never happen, because all arguments which return logical values are operators else result = (sexp_atoi(n) != 0) && result; // 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[CAR(n)].value = SEXP_KNOWN_TRUE; while (CDR(n) != -1) { bool next_result = is_sexp_true(CDR(n)); if ( next_result && !result ) // if current result is true, and our running result is false, things didn't become true in order return SEXP_KNOWN_FALSE; result = next_result && result; if ( Sexp_nodes[CDR(n)].value == SEXP_KNOWN_FALSE || Sexp_nodes[CDR(n)].value == SEXP_NAN_FOREVER ) return SEXP_KNOWN_FALSE; // if one of the AND clauses is FALSE, whole clause is false if ( Sexp_nodes[CDR(n)].value != SEXP_KNOWN_TRUE ) // if the value is still unknown, they all can't be true all_true = false; // see comment above for explanation of next lines if ( result ) Sexp_nodes[CDR(n)].value = SEXP_KNOWN_TRUE; n = CDR(n); } } if ( all_true ) return SEXP_KNOWN_TRUE; return result ? SEXP_TRUE : SEXP_FALSE; } // 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) { bool result = false; if (n != -1) { if (CAR(n) != -1) { // The "known" property is sticky. It's not known-vs-not-known, it is known-[true-or-false]. // The opposite of known-true is not unknown, it is known-false. // // Think of it in the context of a ship exiting a mission. If a ship's cargo was scanned, // then after the ship exits, that sexp will always return known-true. There is no possibility // of it ever becoming something other than true, so it's known. Similarly, the inverse of that // is known-false, because there is no possibility of the inverse ever becoming something other // than false. result = is_sexp_true(CAR(n)); if ( Sexp_nodes[CAR(n)].value == SEXP_KNOWN_FALSE || Sexp_nodes[CAR(n)].value == SEXP_NAN_FOREVER ) 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 SEXP_TRUE; } // this should never happen, because all arguments which return logical values are operators else result = (sexp_atoi(n) != 0); } return result ? SEXP_FALSE : SEXP_TRUE; } int sexp_xor(int n) { bool result; int num_true = 0, num_known_true = 0, num_known_false = 0, num_args = 0; if (n != -1) { if (CAR(n) != -1) { num_args++; result = is_sexp_true(CAR(n)); if (Sexp_nodes[CAR(n)].value == SEXP_KNOWN_TRUE) num_known_true++; else if (Sexp_nodes[CAR(n)].value == SEXP_KNOWN_FALSE || Sexp_nodes[CAR(n)].value == SEXP_NAN_FOREVER) num_known_false++; else if (result) num_true++; } // this should never happen, because all arguments which return logical values are operators else result = (sexp_atoi(n) != 0); while (CDR(n) != -1) { num_args++; result = is_sexp_true(CDR(n)); if (Sexp_nodes[CDR(n)].value == SEXP_KNOWN_TRUE) num_known_true++; else if (Sexp_nodes[CDR(n)].value == SEXP_KNOWN_FALSE || Sexp_nodes[CDR(n)].value == SEXP_NAN_FOREVER) num_known_false++; else if (result) num_true++; n = CDR(n); } } if (num_known_true + num_known_false == num_args) { if (num_known_true == 1) return SEXP_KNOWN_TRUE; else return SEXP_KNOWN_FALSE; } if (num_true + num_known_true == 1) return SEXP_TRUE; else return SEXP_FALSE; } // Goober5000 int sexp_number_compare(int n, int op) { int first_node = n; int current_node; int first_number = eval_sexp(first_node); int current_number; // bail on NANs if (CAR(first_node) != -1) { if (Sexp_nodes[CAR(first_node)].value == SEXP_NAN) return SEXP_FALSE; if (Sexp_nodes[CAR(first_node)].value == SEXP_NAN_FOREVER) return SEXP_KNOWN_FALSE; } if (CDR(first_node) != -1) { if (Sexp_nodes[CDR(first_node)].value == SEXP_NAN) return SEXP_FALSE; if (Sexp_nodes[CDR(first_node)].value == SEXP_NAN_FOREVER) return SEXP_KNOWN_FALSE; } // compare first node with each of the others for (current_node = CDR(first_node); current_node != -1; current_node = CDR(current_node)) { // bail on NANs if (CAR(current_node) != -1) { if (Sexp_nodes[CAR(current_node)].value == SEXP_NAN) return SEXP_FALSE; if (Sexp_nodes[CAR(current_node)].value == SEXP_NAN_FOREVER) return SEXP_KNOWN_FALSE; } if (CDR(current_node) != -1) { if (Sexp_nodes[CDR(current_node)].value == SEXP_NAN) return SEXP_FALSE; if (Sexp_nodes[CDR(current_node)].value == SEXP_NAN_FOREVER) return SEXP_KNOWN_FALSE; } current_number = eval_sexp(current_node); // must satisfy our particular operator switch(op) { case OP_EQUALS: if (first_number != current_number) return SEXP_FALSE; break; case OP_NOT_EQUAL: if (first_number == current_number) return SEXP_FALSE; break; case OP_GREATER_THAN: if (first_number <= current_number) return SEXP_FALSE; break; case OP_GREATER_OR_EQUAL: if (first_number < current_number) return SEXP_FALSE; break; case OP_LESS_THAN: if (first_number >= current_number) return SEXP_FALSE; break; case OP_LESS_OR_EQUAL: if (first_number > current_number) return SEXP_FALSE; break; default: Warning(LOCATION, "Unhandled comparison case! Operator = %d", op); break; } } // it satisfies the operator for all the arguments return SEXP_TRUE; } // Goober5000 int sexp_string_compare(int n, int op) { int first_node = n; int current_node; int val; auto first_string = CTEXT(first_node); // compare first node with each of the others for (current_node = CDR(first_node); current_node != -1; current_node = CDR(current_node)) { val = strcmp(first_string, CTEXT(current_node)); // must satisfy our particular operator switch(op) { case OP_STRING_EQUALS: if (val != 0) return SEXP_FALSE; break; case OP_STRING_GREATER_THAN: if (val <= 0) return SEXP_FALSE; break; case OP_STRING_LESS_THAN: if (val >= 0) return SEXP_FALSE; break; } } // it satisfies the operator for all the arguments return SEXP_TRUE; } object_ship_wing_point_team::object_ship_wing_point_team(ship* sp) : type(OSWPT_TYPE_SHIP), objnum(sp->objnum) { strcpy_s(object_name, sp->ship_name); ship_registry_index = ship_registry_get_index(sp->ship_name); Assertion(ship_registry_index >= 0, "Ship %s was not found in the ship registry!", sp->ship_name); if (ship_registry_index < 0) type = OSWPT_TYPE_NONE; else if (Ship_registry[ship_registry_index].status == ShipStatus::EXITED) type = OSWPT_TYPE_EXITED; } object_ship_wing_point_team::object_ship_wing_point_team(p_object* pop) : type(OSWPT_TYPE_PARSE_OBJECT) { strcpy_s(object_name, pop->name); ship_registry_index = ship_registry_get_index(pop->name); Assertion(ship_registry_index >= 0, "Parse object %s was not found in the ship registry!", pop->name); if (ship_registry_index < 0) type = OSWPT_TYPE_NONE; } object_ship_wing_point_team::object_ship_wing_point_team(ship_obj* sop) : object_ship_wing_point_team(&Ships[Objects[sop->objnum].instance]) {} object_ship_wing_point_team::object_ship_wing_point_team(wing* wingp) : wingnum(WING_INDEX(wingp)) { strcpy_s(object_name, wingp->name); if (wingp->current_count > 0) type = OSWPT_TYPE_WING; else type = OSWPT_TYPE_WING_NOT_PRESENT; // point to wing leader if he is valid if ((wingp->special_ship >= 0) && (wingp->ship_index[wingp->special_ship] >= 0)) { objnum = Ships[wingp->ship_index[wingp->special_ship]].objnum; } // boo... well, just point to ship at index 0 else { objnum = Ships[wingp->ship_index[0]].objnum; Warning(LOCATION, "Substituting ship '%s' at index 0 for nonexistent wing leader at index %d!", Ships[Objects[objnum].instance].ship_name, wingp->special_ship); } } bool object_ship_wing_point_team::matches(const ship *shipp) const { if (!shipp) return false; switch (type) { case oswpt_type::SHIP: return ship_entry()->shipnum == SHIP_INDEX(shipp); case oswpt_type::SHIP_ON_TEAM: case oswpt_type::WHOLE_TEAM: return team == shipp->team; case oswpt_type::WING: return wingnum == shipp->wingnum; default: return false; } } void object_ship_wing_point_team::clear() { *object_name = '\0'; type = oswpt_type::NONE; ship_registry_index = -1; objnum = -1; wingnum = -1; wp_list = -1; wp_index = -1; team = -1; } bool object_ship_wing_point_team::operator==(const object_ship_wing_point_team &other) const { if (type != other.type) return false; switch (type) { case oswpt_type::NONE: return true; case oswpt_type::PARSE_OBJECT: case oswpt_type::SHIP: case oswpt_type::EXITED: return ship_registry_index == other.ship_registry_index; case oswpt_type::SHIP_ON_TEAM: case oswpt_type::WHOLE_TEAM: return team == other.team; case oswpt_type::WING: case oswpt_type::WING_NOT_PRESENT: return wingnum == other.wingnum; case oswpt_type::WAYPOINT: return wp_list == other.wp_list && wp_index == other.wp_index; default: return false; } } bool object_ship_wing_point_team::operator!=(const object_ship_wing_point_team &other) const { return !(operator==(other)); } const ship_registry_entry* object_ship_wing_point_team::ship_entry() const { Assertion(has_ship_entry(), "object_ship_wing_point_team::ship_entry() was called on an oswpt that does not have a ship entry"); return &Ship_registry[ship_registry_index]; } object* object_ship_wing_point_team::objp() const { Assertion(has_objp(), "object_ship_wing_point_team::objp() was called on an oswpt that does not have an object"); return &Objects[objnum]; } wing* object_ship_wing_point_team::wingp() const { Assertion(has_wingp(), "object_ship_wing_point_team::wingp() was called on an oswpt that does not have a wing"); return &Wings[wingnum]; } waypoint* object_ship_wing_point_team::waypointp() const { Assertion(has_waypointp(), "object_ship_wing_point_team::waypointp() was called on an oswpt that does not have a waypoint"); return &Waypoint_lists[wp_list].get_waypoints()[wp_index]; } const ship_registry_entry* object_ship_wing_point_team::ship_entry_or_null() const { return (ship_registry_index < 0) ? nullptr : &Ship_registry[ship_registry_index]; } object* object_ship_wing_point_team::objp_or_null() const { return (objnum < 0) ? nullptr : &Objects[objnum]; } wing* object_ship_wing_point_team::wingp_or_null() const { return (wingnum < 0) ? nullptr : &Wings[wingnum]; } waypoint* object_ship_wing_point_team::waypointp_or_null() const { return (wp_list < 0 || wp_index < 0) ? nullptr : &Waypoint_lists[wp_list].get_waypoints()[wp_index]; } bool object_ship_wing_point_team::has_p_objp() const { return has_ship_entry() && ship_entry()->has_p_objp(); } bool object_ship_wing_point_team::has_shipp() const { return has_ship_entry() && ship_entry()->has_shipp(); } p_object* object_ship_wing_point_team::p_objp() const { Assertion(has_p_objp(), "object_ship_wing_point_team::p_objp() was called on an oswpt that does not have a parse object"); return ship_entry()->p_objp(); } ship* object_ship_wing_point_team::shipp() const { Assertion(has_shipp(), "object_ship_wing_point_team::shipp() was called on an oswpt that does not have a ship"); return ship_entry()->shipp(); } p_object* object_ship_wing_point_team::p_objp_or_null() const { return (ship_registry_index < 0) ? nullptr : Ship_registry[ship_registry_index].p_objp_or_null(); } ship* object_ship_wing_point_team::shipp_or_null() const { return (ship_registry_index < 0) ? nullptr : Ship_registry[ship_registry_index].shipp_or_null(); } // Goober5000 void eval_object_ship_wing_point_team(object_ship_wing_point_team *oswpt, int node, const char *ctext_override) { int ship_registry_index = -1; int wingnum = -1; const char *node_ctext = nullptr; Assert(oswpt != nullptr); Assert(node >= 0 || ctext_override); oswpt->clear(); // this is mainly for multiplayer, where you can't cache something over the network if (ctext_override) { ship_registry_index = ship_registry_get_index(ctext_override); if (ship_registry_index < 0) { wingnum = wing_lookup(ctext_override); if (wingnum < 0) node_ctext = ctext_override; } } // check caching else if (Sexp_nodes[node].cache) { if (Sexp_nodes[node].cache->sexp_node_data_type == OPF_SHIP) { ship_registry_index = Sexp_nodes[node].cache->ship_registry_index; } else if (Sexp_nodes[node].cache->sexp_node_data_type == OPF_WING) { wingnum = Sexp_nodes[node].cache->other_index; } // TODO: other caching else { Assertion(false, "Only ship and wing caching are currently handled!"); } } // evaluate from the node itself else { // check each possibility auto ship_entry = eval_ship(node); if (ship_entry) ship_registry_index = SHIP_REGISTRY_INDEX(ship_entry); else { auto wingp = eval_wing(node); if (wingp) wingnum = WING_INDEX(wingp); else node_ctext = CTEXT(node); } } // see if this is a ship if (ship_registry_index >= 0) { auto ship_entry = &Ship_registry[ship_registry_index]; oswpt->ship_registry_index = ship_registry_index; strcpy_s(oswpt->object_name, ship_entry->name); oswpt->objnum = ship_entry->objnum; switch (ship_entry->status) { case ShipStatus::NOT_YET_PRESENT: oswpt->type = OSWPT_TYPE_PARSE_OBJECT; break; case ShipStatus::PRESENT: case ShipStatus::DEATH_ROLL: oswpt->type = OSWPT_TYPE_SHIP; break; case ShipStatus::EXITED: oswpt->type = OSWPT_TYPE_EXITED; break; default: UNREACHABLE("Unhandled ship registry entry status for %s: %d", ship_entry->name, static_cast(ship_entry->status)); } return; } // see if this is a wing if (wingnum >= 0) { auto wingp = &Wings[wingnum]; oswpt->wingnum = wingnum; strcpy_s(oswpt->object_name, wingp->name); if (wingp->flags[Ship::Wing_Flags::Gone]) { oswpt->type = OSWPT_TYPE_EXITED; } // make sure that at least one ship exists else if (wingp->current_count > 0) { oswpt->type = OSWPT_TYPE_WING; // point to wing leader if he is valid if ((wingp->special_ship >= 0) && (wingp->ship_index[wingp->special_ship] >= 0)) { oswpt->objnum = Ships[wingp->ship_index[wingp->special_ship]].objnum; } // boo... well, just point to ship at index 0 else { oswpt->objnum = Ships[wingp->ship_index[0]].objnum; Warning(LOCATION, "Substituting ship '%s' at index 0 for nonexistent wing leader at index %d!", Ships[Objects[oswpt->objnum].instance].ship_name, wingp->special_ship); } } // it's still a valid wing even if nobody is here else { oswpt->type = OSWPT_TYPE_WING_NOT_PRESENT; } return; } // check if we have nothing // (node_ctext will be non-NULL if there is no ship or wing - but that's hard to determine, Coverity misses it, and it could easily change in the future if this function is modified) if (!node_ctext || !stricmp(node_ctext, SEXP_NONE_STRING)) { oswpt->type = OSWPT_TYPE_NONE; return; } // check if we have a point for a target auto wpt = find_matching_waypoint(node_ctext); if ((wpt != nullptr) && (wpt->get_objnum() >= 0)) { oswpt->type = OSWPT_TYPE_WAYPOINT; oswpt->wp_list = wpt->get_parent_list_index(); oswpt->wp_index = wpt->get_index(); oswpt->objnum = wpt->get_objnum(); return; } // check if we have an "" type int team = sexp_determine_team(node_ctext); if (team >= 0) { oswpt->type = OSWPT_TYPE_SHIP_ON_TEAM; oswpt->team = team; return; } // check if we have a whole-team type team = iff_lookup(node_ctext); if (team >= 0) { oswpt->type = OSWPT_TYPE_WHOLE_TEAM; oswpt->team = team; return; } // we apparently don't have anything legal return; } /** * Return the number of ships of a given team in the area battle */ int sexp_num_ships_in_battle(int n) { int count = 0; ship_obj* so; if (n == -1) { for (so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so)) { if (Objects[so->objnum].flags[Object::Object_Flags::Should_be_dead]) continue; count++; } return count; } while (n != -1) { object_ship_wing_point_team oswpt1; eval_object_ship_wing_point_team(&oswpt1, n); switch (oswpt1.type) { case OSWPT_TYPE_WHOLE_TEAM: for (so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so)) { if (Objects[so->objnum].flags[Object::Object_Flags::Should_be_dead]) continue; auto shipp = &Ships[Objects[so->objnum].instance]; if (shipp->team == oswpt1.team) { count++; } } break; case OSWPT_TYPE_SHIP: count++; break; case OSWPT_TYPE_WING: count += oswpt1.wingp()->current_count; break; default: break; } n = CDR(n); } return count; } /** * Return the number of ships of a given wing or wings in the battle area */ int sexp_num_ships_in_wing(int n) { int num_ships = 0; // A wing name must be provided, Assert that there is one. Assert ( n != -1 ); //Cycle through the list of ships given while (n != -1) { // Get the name of the wing auto wingp = eval_wing(n); //If the wing exists add the number of ships in it to the total if (wingp) { num_ships += wingp->current_count; } //get the next node n = CDR (n) ; } return num_ships ; } /** * Gets the 'real' speed of an object, taking into account docking */ int sexp_get_real_speed(object *obj) { return fl2i(dock_calc_docked_speed(obj)); } /** * Gets the current speed of the specified object * * Uses a lot of code shamelessly ripped from get_object_coordinates */ int sexp_current_speed(int n) { object_ship_wing_point_team oswpt; eval_object_ship_wing_point_team(&oswpt, n); switch (oswpt.type) { case OSWPT_TYPE_EXITED: return SEXP_NAN; case OSWPT_TYPE_SHIP: case OSWPT_TYPE_WING: return sexp_get_real_speed(oswpt.objp()); default: break; } return 0; } int sexp_check_objective_delay(int delay_node, int objective_node, int(*objective_function)(int, fix*)) { fix delay, time; int val; bool is_nan, is_nan_forever; time = 0; delay = i2f(eval_num(delay_node, is_nan, is_nan_forever)); if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; // check value of function val = objective_function(objective_node, &time); if ((val == SEXP_TRUE) || (val == SEXP_KNOWN_TRUE)) { if ((Missiontime - time) >= delay) return val; else return SEXP_FALSE; } return val; } /** * 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) { int count, num_destroyed, wing_index; bool has_been_destroyed; fix time = 0; count = 0; num_destroyed = 0; wing_index = -1; while (n != -1) { ++count; auto ship_entry = eval_ship(n); wing *wingp = nullptr; has_been_destroyed = false; // this might be a ship if (ship_entry) { if (ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_CANT_EVAL; if (ship_entry->status == ShipStatus::DEATH_ROLL || ship_entry->status == ShipStatus::EXITED) { // check the mission log if (mission_log_get_time(LOG_SHIP_DESTROYED, ship_entry->name, nullptr, &time) || mission_log_get_time(LOG_SELF_DESTRUCTED, ship_entry->name, nullptr, &time)) { has_been_destroyed = true; } // exited without being destroyed else { return SEXP_KNOWN_FALSE; } } } else wingp = eval_wing(n); // this might be a wing if (wingp) { if (wing_has_yet_to_arrive(wingp)) return SEXP_CANT_EVAL; if (wingp->flags[Ship::Wing_Flags::Gone]) { // check the mission log if (mission_log_get_time(LOG_WING_DESTROYED, wingp->name, nullptr, &time)) { has_been_destroyed = true; } // exited without being destroyed else { return SEXP_KNOWN_FALSE; } } } if (has_been_destroyed) { ++num_destroyed; if (latest_time && (time > *latest_time)) *latest_time = time; } // at this point we assume the ship or wing is in-mission, even if no ship or wing actually exists else { // If a previous SEXP already had an empty wing then this code would expose the internal value as the // directive count. Instead, we reset the count to zero here to make sure that the wing or ship count is // correct. if (Directive_count == DIRECTIVE_WING_ZERO) { #ifndef NDEBUG static bool wing_zero_warning_shown = false; if (!wing_zero_warning_shown) { mprintf(("SEXP: is-destroyed-delay was used multiple times in a directive event! This might have " "unintended effects and should be replaced by a single use of is-destroyed-delay.\n")); wing_zero_warning_shown = true; } #endif Directive_count = 0; } // ship or wing isn't destroyed -- add to directive count if (wingp) { wing_index = WING_INDEX(wingp); 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 SEXP_FALSE; } int sexp_is_destroyed_delay(int n) { return sexp_check_objective_delay(n, CDR(n), sexp_is_destroyed); } /** * Return true if the subsystem of the given ship has been destroyed */ int sexp_is_subsystem_destroyed(int n, fix *time) { auto ship_entry = eval_ship(n); if (!ship_entry) return SEXP_FALSE; if (ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_CANT_EVAL; auto subsys_name = CTEXT(CDR(n)); if ( mission_log_get_time(LOG_SHIP_SUBSYS_DESTROYED, ship_entry->name, subsys_name, time) ) return SEXP_KNOWN_TRUE; // if the ship has exited, no way to destroy its subsystem. if (ship_entry->status == ShipStatus::EXITED) return SEXP_KNOWN_FALSE; return SEXP_FALSE; } int sexp_is_subsystem_destroyed_delay(int n) { return sexp_check_objective_delay(CDDR(n), n, sexp_is_subsystem_destroyed); } /** * Determine if a ship has arrived onto the scene */ int sexp_has_arrived(int n, fix *latest_time) { int count, num_arrived; bool has_arrived; fix time = 0; count = 0; num_arrived = 0; while ( n != -1 ) { ++count; auto ship_entry = eval_ship(n); wing *wingp = nullptr; has_arrived = false; // this might be a ship if (ship_entry) { if (ship_entry->status != ShipStatus::NOT_YET_PRESENT) { // check the mission log if (mission_log_get_time(LOG_SHIP_ARRIVED, ship_entry->name, nullptr, &time)) has_arrived = true; } } else wingp = eval_wing(n); // this might be a wing if (wingp) { if (!wing_has_yet_to_arrive(wingp)) { // check the mission log if (mission_log_get_time(LOG_WING_ARRIVED, wingp->name, nullptr, &time)) has_arrived = true; } } if (has_arrived) { ++num_arrived; if (latest_time && (time > *latest_time)) *latest_time = time; } // move to next ship/wing in list n = CDR(n); } if ( count == num_arrived ) return SEXP_KNOWN_TRUE; else return SEXP_FALSE; } int sexp_has_arrived_delay(int n) { return sexp_check_objective_delay(n, CDR(n), sexp_has_arrived); } /** * Determine if a ship/wing has departed */ int sexp_has_departed(int n, fix *latest_time) { int count, num_departed; bool has_departed; fix time = 0; count = 0; num_departed = 0; while ( n != -1 ) { ++count; auto ship_entry = eval_ship(n); wing *wingp = nullptr; has_departed = false; // this might be a ship if (ship_entry) { if (ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_CANT_EVAL; if (ship_entry->status == ShipStatus::EXITED) { // check the mission log if (mission_log_get_time(LOG_SHIP_DEPARTED, ship_entry->name, nullptr, &time)) { has_departed = true; } // exited without departing else { return SEXP_KNOWN_FALSE; } } } else wingp = eval_wing(n); // this might be a wing if (wingp) { if (wing_has_yet_to_arrive(wingp)) return SEXP_CANT_EVAL; if (wingp->flags[Ship::Wing_Flags::Gone]) { // check the mission log if (mission_log_get_time(LOG_WING_DEPARTED, wingp->name, nullptr, &time)) { has_departed = true; } // exited without departing else { return SEXP_KNOWN_FALSE; } } } if (has_departed) { ++num_departed; if (latest_time && (time > *latest_time)) *latest_time = time; } // move to next ship/wing in list n = CDR(n); } if ( count == num_departed ) return SEXP_KNOWN_TRUE; else return SEXP_FALSE; } int sexp_has_departed_delay(int n) { return sexp_check_objective_delay(n, CDR(n), sexp_has_departed); } /** * Determine if a ship is disabled or disarmed, but not both */ int sexp_is_disabled_xor_disarmed(int n, bool check_disabled, fix *latest_time) { int count, num_satisfied; fix time; count = 0; num_satisfied = 0; while ( n != -1 ) { ++count; auto ship_entry = eval_ship(n); if (!ship_entry) return SEXP_FALSE; if (ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_CANT_EVAL; // check mission log if (mission_log_get_time(check_disabled ? LOG_SHIP_DISABLED : LOG_SHIP_DISARMED, ship_entry->name, nullptr, &time)) { ++num_satisfied; if (latest_time && (time > *latest_time)) *latest_time = time; } // if the ship has exited, no way to destroy its subsystem. else if (ship_entry->status == ShipStatus::EXITED) return SEXP_KNOWN_FALSE; // move to next ship in list n = CDR(n); } if ( count == num_satisfied ) return SEXP_KNOWN_TRUE; else return SEXP_FALSE; } int sexp_is_disabled_xor_disarmed_delay(int n, bool check_disabled) { // since we can't use captured-value lambdas as function pointers, we need a stateless lambda for each value of the variable if (check_disabled) return sexp_check_objective_delay(n, CDR(n), [](int _n, fix *_time)->int { return sexp_is_disabled_xor_disarmed(_n, true, _time); }); else return sexp_check_objective_delay(n, CDR(n), [](int _n, fix *_time)->int { return sexp_is_disabled_xor_disarmed(_n, false, _time); }); } /** * Determine if a ship or wing is done flying waypoints */ int sexp_are_waypoints_done(int n, fix *time, int count = 1) { auto ship_entry = eval_ship(n); if (ship_entry) { if (ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_CANT_EVAL; auto waypoint_name = CTEXT(CDR(n)); if (mission_log_get_time_indexed(LOG_WAYPOINTS_DONE, ship_entry->name, waypoint_name, count, time)) return SEXP_KNOWN_TRUE; // if the ship has exited, no way to complete waypoints if (ship_entry->status == ShipStatus::EXITED) return SEXP_KNOWN_FALSE; return SEXP_FALSE; } auto wingp = eval_wing(n); if (wingp) { if (wing_has_yet_to_arrive(wingp)) return SEXP_CANT_EVAL; auto waypoint_name = CTEXT(CDR(n)); if (mission_log_get_time_indexed(LOG_WAYPOINTS_DONE, wingp->name, waypoint_name, count, time)) return SEXP_KNOWN_TRUE; // if the wing is gone, no way to complete waypoints if (wingp->flags[Ship::Wing_Flags::Gone]) return SEXP_KNOWN_FALSE; return SEXP_FALSE; } // neither a ship nor a wing return SEXP_FALSE; } // since we can't use lambda-captures as function pointers (see also disabled_xor_disarmed), we need a lot of code duplication from sexp_check_objective_delay int sexp_are_waypoints_done_delay(int n) { int delay_node = CDDR(n), objective_node = n, count_node = CDDDR(n); fix delay, time; int count, val; bool is_nan, is_nan_forever; time = 0; delay = i2f(eval_num(delay_node, is_nan, is_nan_forever)); if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; if (count_node >= 0) { count = i2f(eval_num(count_node, is_nan, is_nan_forever)); if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; if (count <= 0) { Warning(LOCATION, "Are-waypoints-done-delay count should be at least 1! This has been automatically adjusted."); count = 1; } } else count = 1; // check value of function val = sexp_are_waypoints_done(objective_node, &time, count); if ((val == SEXP_TRUE) || (val == SEXP_KNOWN_TRUE)) { if ((Missiontime - time) >= delay) return val; else return SEXP_FALSE; } return val; } // First ship is the destroyer, rest of the arguments are the destroyed ships. int sexp_was_destroyed_by(int n, fix *latest_time) { int count, num_destroyed; fix time; auto ship_entry = eval_ship(n); if (!ship_entry) return SEXP_FALSE; if (ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_CANT_EVAL; count = 0; num_destroyed = 0; for (n = CDR(n); n != -1; n = CDR(n)) { ++count; auto destroyed_ship_entry = eval_ship(n); if (!destroyed_ship_entry) return SEXP_FALSE; if (destroyed_ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_CANT_EVAL; // check the mission log if (mission_log_get_time(LOG_SHIP_DESTROYED, destroyed_ship_entry->name, ship_entry->name, &time)) { ++num_destroyed; if (latest_time && (time > *latest_time)) *latest_time = time; } // if the ship has exited, no way to destroy it else if (destroyed_ship_entry->status == ShipStatus::EXITED) return SEXP_KNOWN_FALSE; } if (count == num_destroyed) return SEXP_KNOWN_TRUE; else return SEXP_FALSE; } int sexp_was_destroyed_by_delay(int n) { return sexp_check_objective_delay(n, CDR(n), sexp_was_destroyed_by); } int sexp_has_docked_or_undocked(int n, int op_num) { bool is_nan, is_nan_forever; Assert(op_num == OP_HAS_DOCKED || op_num == OP_HAS_UNDOCKED || op_num == OP_HAS_DOCKED_DELAY || op_num == OP_HAS_UNDOCKED_DELAY); auto docker = eval_ship(n); if (!docker) return SEXP_FALSE; if (docker->status == ShipStatus::NOT_YET_PRESENT) return SEXP_CANT_EVAL; n = CDR(n); auto dockee = eval_ship(n); if (!dockee) return SEXP_FALSE; if (dockee->status == ShipStatus::NOT_YET_PRESENT) return SEXP_CANT_EVAL; n = CDR(n); // count of times that we should look for int count = eval_num(n, is_nan, is_nan_forever); if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; n = CDR(n); if (count <= 0) { Warning(LOCATION, "Has-%sdocked%s count should be at least 1! This has been automatically adjusted.", (op_num == OP_HAS_UNDOCKED || op_num == OP_HAS_UNDOCKED_DELAY ? "un" : ""), (op_num == OP_HAS_DOCKED_DELAY || op_num == OP_HAS_UNDOCKED_DELAY ? "-delay" : "")); count = 1; } if (op_num == OP_HAS_DOCKED_DELAY || op_num == OP_HAS_UNDOCKED_DELAY) { fix delay = i2f(eval_num(n, is_nan, is_nan_forever)); fix time; if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; if (mission_log_get_time_indexed(op_num == OP_HAS_DOCKED_DELAY ? LOG_SHIP_DOCKED : LOG_SHIP_UNDOCKED, docker->name, dockee->name, count, &time)) { if ((Missiontime - time) >= delay) return SEXP_KNOWN_TRUE; } // if either ship has exited, no way to dock else if (docker->status == ShipStatus::EXITED || dockee->status == ShipStatus::EXITED) return SEXP_KNOWN_FALSE; } else { if (mission_log_get_time_indexed(op_num == OP_HAS_DOCKED ? LOG_SHIP_DOCKED : LOG_SHIP_UNDOCKED, docker->name, dockee->name, count, nullptr)) { return SEXP_KNOWN_TRUE; } // if either ship has exited, no way to dock else if (docker->status == ShipStatus::EXITED || dockee->status == ShipStatus::EXITED) return SEXP_KNOWN_FALSE; } return SEXP_FALSE; } /** * Determine is all of a given ship type are destroyed */ int sexp_ship_type_destroyed(int n) { bool is_nan, is_nan_forever; int percent = eval_num(n, is_nan, is_nan_forever); if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; auto shiptype = CTEXT(CDR(n)); int type = ship_type_name_lookup(shiptype); if (type < 0 && Fighter_bomber_valid && !stricmp(shiptype, Fighter_bomber_type_name)) type = Ship_type_fighter_bomber; else if (!SCP_vector_inbounds(Ship_types, type)) { // bogus if we reach the end of this array!!!! Warning(LOCATION, "Invalid shiptype '%s' passed to ship-type-destroyed", shiptype); return SEXP_FALSE; } int killed, total; if (type == Ship_type_fighter_bomber) { killed = Ship_type_counts[Ship_type_fighter].killed + Ship_type_counts[Ship_type_bomber].killed; total = Ship_type_counts[Ship_type_fighter].total + Ship_type_counts[Ship_type_bomber].total; } else { //We are safe from array indexing probs b/c of previous if. killed = Ship_type_counts[type].killed; total = Ship_type_counts[type].total; } if (total == 0) return SEXP_FALSE; // determine if the percentage of killed/total is >= percentage given in the expression if ((killed * 100 / total) >= percent) return SEXP_KNOWN_TRUE; return SEXP_FALSE; } // following are time based functions /** * Returns the time into the mission */ int sexp_mission_time() { return f2i(Missiontime); } /** * Returns the time into the mission, in milliseconds */ int sexp_mission_time_msecs() { // multiplying by 1000 can go over the limit for LONG_MAX so cast to long long int first auto mission_time = (std::int64_t) Missiontime; // This hack is necessary since fix is a 32-bit integer which would overflow if f2i would be used return (int)((mission_time * 1000) / 65536); } int sexp_has_time_elapsed(int n, bool use_msecs) { bool is_nan, is_nan_forever; int time = eval_num(n, is_nan, is_nan_forever); if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; int mission_time = use_msecs ? sexp_mission_time_msecs() : sexp_mission_time(); if ( mission_time >= time ) return SEXP_KNOWN_TRUE; return SEXP_FALSE; } /** * Returns percent of length of distance to special warpout plane */ int sexp_special_warp_dist( int n) { vec3d center_pos, actual_local_center; float half_length, dist_to_plane; // get ship auto ship_entry = eval_ship(n); // ships which aren't present get NAN if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; if (ship_entry->status == ShipStatus::DEATH_ROLL || ship_entry->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; auto shipp = ship_entry->shipp(); // check that ship has warpout_objnum if (shipp->special_warpout_objnum < 0 || shipp->special_warpout_objnum >= MAX_OBJECTS) { return SEXP_NAN; } auto sip = &Ship_info[shipp->ship_info_index]; auto objp = ship_entry->objp(); object *knossos_objp = &Objects[shipp->special_warpout_objnum]; // check the special warpout device is valid if (knossos_objp->type != OBJ_SHIP || !Ship_info[Ships[knossos_objp->instance].ship_info_index].flags[Ship::Info_Flags::Knossos_device]) { return SEXP_NAN; } // check if within 45 degree half-angle cone of facing float dot = fl_abs(vm_vec_dot(&knossos_objp->orient.vec.fvec, &objp->orient.vec.fvec)); if (dot < 0.707f) { return SEXP_NAN; } // determine the correct center of the model (which may not be the model's origin) if (object_is_docked(objp)) dock_calc_docked_actual_center(&actual_local_center, objp); else ship_class_get_actual_center(sip, &actual_local_center); // find world position of the center of the ship assembly vm_vec_unrotate(¢er_pos, &actual_local_center, &objp->orient); vm_vec_add2(¢er_pos, &objp->pos); // determine the half-length if (object_is_docked(objp)) { // we need to get the longitudinal radius of our ship, so find the semilatus rectum along the Z-axis half_length = dock_calc_max_semilatus_rectum_parallel_to_axis(objp, Z_AXIS); } else half_length = 0.5f * ship_class_get_length(sip); // get distance dist_to_plane = fvi_ray_plane(nullptr, &knossos_objp->pos, &knossos_objp->orient.vec.fvec, ¢er_pos, &objp->orient.vec.fvec, 0.0f); dist_to_plane -= half_length; // return as a percent of length -- simplified from 100*dist/(2*half_length) return (int) (50.0f * dist_to_plane / half_length); } int sexp_time_exited(int n, int op_num) { bool ship = (op_num == OP_TIME_SHIP_DESTROYED || op_num == OP_TIME_SHIP_DEPARTED); bool destroyed = (op_num == OP_TIME_SHIP_DESTROYED || op_num == OP_TIME_WING_DESTROYED); fix time; if (ship) { auto ship_entry = eval_ship(n); if (!ship_entry) return SEXP_NAN; if (ship_entry->status == ShipStatus::DEATH_ROLL) { if (!destroyed) return SEXP_NAN; } else if (ship_entry->status != ShipStatus::EXITED) return SEXP_NAN; if (destroyed) { if (!mission_log_get_time(LOG_SHIP_DESTROYED, ship_entry->name, nullptr, &time) && !mission_log_get_time(LOG_SELF_DESTRUCTED, ship_entry->name, nullptr, &time)) { // returns 0 when not found return SEXP_NAN_FOREVER; // exited but not destroyed } } else { if (!mission_log_get_time(LOG_SHIP_DEPARTED, ship_entry->name, nullptr, &time)) { // returns 0 when not found return SEXP_NAN_FOREVER; // exited but not departed } } } else { auto wingp = eval_wing(n); if (!wingp || !wingp->flags[Ship::Wing_Flags::Gone]) return SEXP_NAN; if (!mission_log_get_time(destroyed ? LOG_WING_DESTROYED : LOG_WING_DEPARTED, wingp->name, nullptr, &time)) { return SEXP_NAN_FOREVER; // exited but not de[stroy|part]ed } } // if we're here we know that the ship/wing did its thing return f2i(time); } int sexp_time_arrived(int n, bool ship) { fix time; if (ship) { auto ship_entry = eval_ship(n); if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; if (!mission_log_get_time(LOG_SHIP_ARRIVED, CTEXT(n), nullptr, &time)) { return SEXP_NAN; } } else { auto wingp = eval_wing(n); if (!wingp || wing_has_yet_to_arrive(wingp)) return SEXP_NAN; if (!mission_log_get_time(LOG_WING_ARRIVED, CTEXT(n), nullptr, &time)) { return SEXP_NAN; } } // if we're here we know that the ship/wing has arrived return f2i(time); } int sexp_time_docked_or_undocked(int n, bool docked) { fix time; bool is_nan, is_nan_forever; auto docker = eval_ship(n); if (!docker || docker->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; n = CDR(n); auto dockee = eval_ship(n); if (!dockee || dockee->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; n = CDR(n); int count = eval_num(n, is_nan, is_nan_forever); if (is_nan) return SEXP_NAN; if (is_nan_forever) return SEXP_NAN_FOREVER; if (count <= 0) { Warning(LOCATION, "Time-%sdocked count should be at least 1! This has been automatically adjusted.", docked ? "" : "un"); count = 1; } if (mission_log_get_time_indexed(docked ? LOG_SHIP_DOCKED : LOG_SHIP_UNDOCKED, docker->name, dockee->name, count, &time)) { return f2i(time); } // if either ship has exited, no way to dock else if (docker->status == ShipStatus::EXITED || dockee->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; return SEXP_NAN; } void sexp_set_energy_pct (int node, int op_num) { float new_pct; bool is_nan, is_nan_forever; Assert (node >= 0); new_pct = eval_num(node, is_nan, is_nan_forever) / 100.0f; if (is_nan || is_nan_forever) return; // deal with ridiculous percentages CLAMP(new_pct, 0.0f, 1.0f); // only need to send a packet for afterburners because shields and weapon energy are sent from server to clients if (MULTIPLAYER_MASTER && (op_num == OP_SET_AFTERBURNER_ENERGY)) { Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_float(new_pct); } node = CDR(node); for (; node >= 0; node = CDR(node)) { // get the ship auto ship_entry = eval_ship(node); if (!ship_entry || !ship_entry->has_shipp()) { continue; } auto shipp = ship_entry->shipp(); auto sip = &Ship_info[shipp->ship_info_index]; switch (op_num) { case OP_SET_AFTERBURNER_ENERGY: shipp->afterburner_fuel = sip->afterburner_fuel_capacity * new_pct; break; case OP_SET_WEAPON_ENERGY: if (!(ship_has_energy_weapons(shipp)) ) { continue; } shipp->weapon_energy = sip->max_weapon_reserve * new_pct; break; case OP_SET_SHIELD_ENERGY: if (shipp->ship_max_shield_strength == 0.0f) { continue; } shield_set_strength(ship_entry->objp(), (shield_get_max_strength(shipp) * new_pct)); break; } if (MULTIPLAYER_MASTER && (op_num == OP_SET_AFTERBURNER_ENERGY)) { Current_sexp_network_packet.send_ship(shipp); } } if (MULTIPLAYER_MASTER && (op_num == OP_SET_AFTERBURNER_ENERGY)) { Current_sexp_network_packet.end_callback(); } } void multi_sexp_set_energy_pct() { ship *shipp; float new_pct; ship_info * sip; int op_num = Current_sexp_network_packet.get_operator(); Current_sexp_network_packet.get_float(new_pct); while (Current_sexp_network_packet.get_ship(shipp)) { sip = &Ship_info[shipp->ship_info_index]; switch (op_num) { case OP_SET_AFTERBURNER_ENERGY: shipp->afterburner_fuel = sip->afterburner_fuel_capacity * new_pct; break; case OP_SET_WEAPON_ENERGY: shipp->weapon_energy = sip->max_weapon_reserve * new_pct; break; case OP_SET_SHIELD_ENERGY: shield_set_strength(&Objects[shipp->objnum], (shield_get_max_strength(shipp) * new_pct)); break; } } } int sexp_get_energy_pct (int node, int op_num) { float maximum = 0.0f, current = 0.0f; // get the ship auto ship_entry = eval_ship(node); if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; if (ship_entry->status == ShipStatus::DEATH_ROLL || ship_entry->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; auto shipp = ship_entry->shipp(); auto sip = &Ship_info[shipp->ship_info_index]; switch (op_num) { case OP_AFTERBURNER_LEFT: maximum = sip->afterburner_fuel_capacity; current = shipp->afterburner_fuel; break; case OP_WEAPON_ENERGY_LEFT: if (ship_has_energy_weapons(shipp)) { maximum = sip->max_weapon_reserve; current = shipp->weapon_energy; } break; } if (maximum < WEAPON_RESERVE_THRESHOLD || current < WEAPON_RESERVE_THRESHOLD) { return 0; } return (int)(100 * (current/maximum)); } /** * Return the remaining shields as a percentage of the given ship. */ int sexp_shields_left(int node) { // get the ship auto ship_entry = eval_ship(node); if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; if (ship_entry->status == ShipStatus::DEATH_ROLL || ship_entry->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; // Goober5000: in case ship has no shields if (ship_entry->shipp()->ship_max_shield_strength == 0.0f) { return 0; } // now return the amount of shields left as a percentage of the whole. int percent = (int)std::lround(get_shield_pct(ship_entry) * 100.0f); return percent; } /** * 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 node, bool sim_hull) { // get the ship auto ship_entry = eval_ship(node); if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; if (ship_entry->status == ShipStatus::DEATH_ROLL || ship_entry->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; float hull_pct = sim_hull ? get_sim_hull_pct(ship_entry) : get_hull_pct(ship_entry); // now return the amount of hits left as a percentage of the whole. int percent = (int)std::lround(hull_pct * 100.0f); return percent; } /** * Determine if ship visible on radar * * @return 0 - not visible * @return 1 - marginally targetable (jiggly on radar) * @return 2 - fully targetable */ int sexp_is_ship_visible(int node) { int n = node; // get the ship auto ship_entry = eval_ship(n); if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; if (ship_entry->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; n = CDR(n); // the second argument is the viewer if (n >= 0) { auto viewer_entry = eval_ship(n); return ship_check_visibility(ship_entry->shipp(), viewer_entry ? viewer_entry->shipp_or_null() : nullptr); } else { return ship_check_visibility(ship_entry->shipp(), nullptr); } } /** * Determine if a flag is set on this ship */ int sexp_check_ship_flag(int node, Ship::Ship_Flags flag) { // get the ship auto ship_entry = eval_ship(node); if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; if (ship_entry->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; if (ship_entry->shipp()->flags[flag]) return SEXP_TRUE; else return SEXP_FALSE; } // 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) { // if multi t vs t if (Game_mode & GM_MULTIPLAYER) { if (Netgame.type_flags & NG_TYPE_TEAM) { bool is_nan, is_nan_forever; int team = eval_num(node, is_nan, is_nan_forever); if (is_nan) return SEXP_NAN; if (is_nan_forever) return SEXP_NAN_FOREVER; // Teams can only be 1 or 2 at the moment but we should use Num_teams in case more become possible in the future if (team <= 0 || team > Num_teams) { // invalid team index Warning(LOCATION, "sexp-team-score: team %d is not a valid team #", team); return 0; } return Multi_team_score[team - 1]; } } return 0; } /** * Return the remaining hits left on a subsystem as a percentage of the whole. * * Goober5000 - this sexp is DEPRECATED because it works just like the new hits-left-substem-generic */ int sexp_hits_left_subsystem(int n) { bool single_subsystem = false; int percent, type; // get the ship auto ship_entry = eval_ship(n); if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; if (ship_entry->status == ShipStatus::DEATH_ROLL || ship_entry->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; auto subsys_name = CTEXT(CDR(n)); auto ss = ship_get_subsys(ship_entry->shipp(), subsys_name); if (ss) type = ss->system_info->type; else type = SUBSYSTEM_NONE; if ( (type >= 0) && (type < SUBSYSTEM_MAX) ) { // check for the optional argument n = CDDR(n); if (n >= 0) { single_subsystem = is_sexp_true(n); } // if the third option is present or if this is an unknown subsystem type we only want to find the percentage of the // named subsystem if (single_subsystem || (type == SUBSYSTEM_UNKNOWN)) { if (ss) { percent = (int)std::lround(ss->current_hits / ss->max_hits * 100.0f); return percent; } // we reached end of ship subsys list without finding subsys_name if (ship_class_unchanged(ship_entry)) { auto problem = (Game_mode & GM_IN_MISSION) ? "subsystem was not found" : "subsystem information is not available when not in-mission"; Warning(LOCATION, "Attempted to check subsystem '%s' on ship '%s' in hits-left-subsystem -- %s", subsys_name, ship_entry->name, problem); } return SEXP_NAN; // by default we 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) } else { percent = (int)std::lround(ship_get_subsystem_strength(ship_entry->shipp(), type) * 100.0f); return percent; } } return SEXP_NAN; // if for some strange reason, the type field of the subsystem is bogus } // Goober5000 int sexp_hits_left_subsystem_generic(int node) { // get the ship auto ship_entry = eval_ship(node); if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; if (ship_entry->status == ShipStatus::DEATH_ROLL || ship_entry->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; auto subsys_type_name = CTEXT(CDR(node)); // find subsystem type int subsys_type = string_lookup(subsys_type_name, Subsystem_types, SUBSYSTEM_MAX); // error checking if (subsys_type < 0) { Warning(LOCATION, "Subsystem type '%s' not recognized in hits-left-subsystem-generic!", subsys_type_name); return SEXP_NAN_FOREVER; } else if (subsys_type == SUBSYSTEM_NONE) { // as you wish...? return 0; } else if (subsys_type == SUBSYSTEM_UNKNOWN) { Warning(LOCATION, "Cannot use SUBSYSTEM_UNKNOWN in hits-left-subsystem-generic!"); return SEXP_NAN_FOREVER; } // 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) return (int)std::lround(ship_get_subsystem_strength(ship_entry->shipp(), subsys_type) * 100.0f); } // Goober5000 int sexp_hits_left_subsystem_specific(int node) { // get the ship auto ship_entry = eval_ship(node); if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; if (ship_entry->status == ShipStatus::DEATH_ROLL || ship_entry->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; auto subsys_name = CTEXT(CDR(node)); // find subsystem auto ss = ship_get_subsys(ship_entry->shipp(), subsys_name); if (ss) { // return as a percentage the hits remaining on this subsystem only return (int)std::lround(ss->current_hits / ss->max_hits * 100.0f); } // we reached end of ship subsys list without finding subsys_name if (ship_class_unchanged(ship_entry)) { auto problem = (Game_mode & GM_IN_MISSION) ? "subsystem was not found" : "subsystem information is not available when not in-mission"; Warning(LOCATION, "Attempted to check subsystem '%s' on ship '%s' in hits-left-subsystem-specific -- %s", subsys_name, ship_entry->name, problem); } return SEXP_NAN; } int sexp_directive_value(int n) { int replace_current_value = SEXP_TRUE; int directive_value; bool is_nan, is_nan_forever; Assert(n >= 0); directive_value = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) { directive_value = 0; } n = CDR(n); if (n > -1) { replace_current_value = eval_sexp(n); } if (replace_current_value == SEXP_FALSE) { // note: any SEXP_KNOWN_FALSE result will return SEXP_FALSE Directive_count += directive_value; } else { Directive_count = directive_value; } return SEXP_TRUE; } int sexp_determine_team(const char *subj) { char team_name[NAME_LENGTH]; // quick check if (strnicmp(subj, "pos, &objp2->pos); } /** * Check distance between the bounding boxes of two given objects */ int sexp_bbox_distance3(object *objp1, object *objp2) { Assertion(objp1, "First object should be non-NULL based on check in sexp_distance2"); // if the object isn't present in the mission now if (!objp2) return SEXP_NAN; if (objp1->type == OBJ_SHIP && objp2->type == OBJ_SHIP) { vec3d temp1, temp2; int model_num1 = Ship_info[Ships[objp1->instance].ship_info_index].model_num; int model_num2 = Ship_info[Ships[objp2->instance].ship_info_index].model_num; return (int)model_find_closest_points(&temp1, model_num1, -1, &objp1->orient, &objp1->pos, &temp2, model_num2, -1, &objp2->orient, &objp2->pos); } else if (objp1->type == OBJ_SHIP) { return (int)hud_find_target_distance(objp1, &objp2->pos); } else if (objp2->type == OBJ_SHIP) { return (int)hud_find_target_distance(objp2, &objp1->pos); } else { return (int)vm_vec_dist(&objp1->pos, &objp2->pos); } } /** * Check distance between two given objects using the wonky retail method */ int sexp_retail_distance3(object *objp1, object *objp2) { Assertion(objp1, "First object should be non-NULL based on check in sexp_distance2"); // if the object isn't present in the mission now if (!objp2) return SEXP_NAN; if ((objp1->type == OBJ_SHIP) && (objp2->type == OBJ_SHIP)) { if (Player_obj == objp1) return (int) hud_find_target_distance(objp2, objp1); else return (int) hud_find_target_distance(objp1, objp2); } else { return (int) vm_vec_dist_quick(&objp1->pos, &objp2->pos); } } /** * Check distance between a given ship and a given subject (ship, wing, any \). */ int sexp_distance2(object *objp1, object_ship_wing_point_team *oswpt2, int(*distance_method)(object*, object*)) { int dist, dist_min = 0; bool inited = false; // if the object isn't present in the mission now if (!objp1) return SEXP_NAN; switch (oswpt2->type) { // we have a ship-on-team type, so check all ships of that type case OSWPT_TYPE_SHIP_ON_TEAM: { for (auto so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so)) { if (Objects[so->objnum].flags[Object::Object_Flags::Should_be_dead]) continue; if (Ships[Objects[so->objnum].instance].team == oswpt2->team) { dist = distance_method(objp1, &Objects[so->objnum]); if (dist != SEXP_NAN) { if (!inited || (dist < dist_min)) { dist_min = dist; inited = true; } } } } // no objects were checked if (!inited) return SEXP_NAN; return dist_min; } // check ships and points case OSWPT_TYPE_SHIP: case OSWPT_TYPE_WAYPOINT: { return distance_method(objp1, oswpt2->objp()); } // check wings case OSWPT_TYPE_WING: { for (int i = 0; i < oswpt2->wingp()->current_count; i++) { dist = distance_method(objp1, &Objects[Ships[oswpt2->wingp()->ship_index[i]].objnum]); if (dist != SEXP_NAN) { if (!inited || (dist < dist_min)) { dist_min = dist; inited = true; } } } // no objects were checked if (!inited) return SEXP_NAN; return dist_min; } default: break; } return SEXP_NAN; } /** * Returns the distance between two objects. * * If a wing is specified as one (or both) of the arguments to this function, we are looking for the closest distance */ int sexp_distance(int n, int(*distance_method)(object*, object*)) { int dist, dist_min = 0; bool inited = false; object_ship_wing_point_team oswpt1, oswpt2; eval_object_ship_wing_point_team(&oswpt1, n); eval_object_ship_wing_point_team(&oswpt2, CDR(n)); // check to see if either object was destroyed or departed if (oswpt1.type == OSWPT_TYPE_EXITED || oswpt2.type == OSWPT_TYPE_EXITED) return SEXP_NAN_FOREVER; switch (oswpt1.type) { // we have a ship-on-team type, so check all ships of that type case OSWPT_TYPE_SHIP_ON_TEAM: { for (auto so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so)) { if (Objects[so->objnum].flags[Object::Object_Flags::Should_be_dead]) continue; if (Ships[Objects[so->objnum].instance].team == oswpt1.team) { dist = sexp_distance2(&Objects[so->objnum], &oswpt2, distance_method); if (dist != SEXP_NAN) { if (!inited || (dist < dist_min)) { dist_min = dist; inited = true; } } } } // no objects were checked if (!inited) return SEXP_NAN; return dist_min; } // check ships and points case OSWPT_TYPE_SHIP: case OSWPT_TYPE_WAYPOINT: { return sexp_distance2(oswpt1.objp(), &oswpt2, distance_method); } // check wings case OSWPT_TYPE_WING: { for (int i = 0; i < oswpt1.wingp()->current_count; i++) { dist = sexp_distance2(&Objects[Ships[oswpt1.wingp()->ship_index[i]].objnum], &oswpt2, distance_method); if (dist != SEXP_NAN) { if (!inited || (dist < dist_min)) { dist_min = dist; inited = true; } } } // no objects were checked if (!inited) return SEXP_NAN; return dist_min; } default: break; } return SEXP_NAN; } /** * Locate the subsystem on a ship - Goober5000 * * Switched to a boolean so that it can report failure to do so */ bool sexp_get_subsystem_world_pos(vec3d *subsys_world_pos, const ship_registry_entry *ship_entry, const char *subsys_name) { Assert(subsys_world_pos && ship_entry && subsys_name); // find the ship subsystem ship_subsys *ss = ship_get_subsys(ship_entry->shipp(), subsys_name); if (ss) { // find world position of subsystem on this object (the ship) get_subsystem_world_pos(ship_entry->objp(), ss, subsys_world_pos); return true; } // we reached end of ship subsys list without finding subsys_name if (ship_class_unchanged(ship_entry)) { // this ship should have had the subsystem named as it shouldn't have changed class Warning(LOCATION, "sexp_get_subsystem_world_pos could not find subsystem '%s'", subsys_name); } return false; } /** * Check distance between the center of an object and a position */ int sexp_center_distance_point(object *objp1, vec3d *pos) { Assertion(objp1 && pos, "Parameters should be non-NULL!"); return (int)vm_vec_dist(&objp1->pos, pos); } /** * Check distance between the bounding box of an object and a position */ int sexp_bbox_distance_point(object *objp1, vec3d *pos) { Assertion(objp1 && pos, "Parameters should be non-NULL!"); if (objp1->type == OBJ_SHIP) { return (int)hud_find_target_distance(objp1, pos); } else { return (int)vm_vec_dist(&objp1->pos, pos); } } /** * Returns the distance between an object and a ship subsystem. * * If a wing is specified as the object argument to this function, we are looking for the closest distance */ int sexp_distance_subsystem(int n, int(*distance_method)(object*, vec3d*)) { int dist, dist_min = 0; bool inited = false; vec3d subsys_pos; object_ship_wing_point_team oswpt; eval_object_ship_wing_point_team(&oswpt, n); // quick out if (oswpt.type == OSWPT_TYPE_EXITED) return SEXP_NAN_FOREVER; // get the ship auto ship_with_subsys = eval_ship(CDR(n)); if (!ship_with_subsys || ship_with_subsys->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; if (ship_with_subsys->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; auto subsys_name = CTEXT(CDR(CDR(n))); // get the subsystem's coordinates or bail if we can't if (!sexp_get_subsystem_world_pos(&subsys_pos, ship_with_subsys, subsys_name)) return SEXP_NAN; switch (oswpt.type) { // we have a ship-on-team type, so check all ships of that type case OSWPT_TYPE_SHIP_ON_TEAM: { for (auto so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so)) { if (Objects[so->objnum].flags[Object::Object_Flags::Should_be_dead]) continue; if (Ships[Objects[so->objnum].instance].team == oswpt.team) { dist = distance_method(&Objects[so->objnum], &subsys_pos); if (!inited || (dist < dist_min)) { dist_min = dist; inited = true; } } } // no objects were checked if (!inited) return SEXP_NAN; return dist_min; } // check ships and points case OSWPT_TYPE_SHIP: case OSWPT_TYPE_WAYPOINT: { return distance_method(oswpt.objp(), &subsys_pos); } // check wings case OSWPT_TYPE_WING: { for (int i = 0; i < oswpt.wingp()->current_count; i++) { dist = distance_method(&Objects[Ships[oswpt.wingp()->ship_index[i]].objnum], &subsys_pos); if (!inited || (dist < dist_min)) { dist_min = dist; inited = true; } } // no objects were checked if (!inited) return SEXP_NAN; return dist_min; } default: break; } return SEXP_NAN; } bool sexp_helper_is_within_box(const std::array &box_vals, const vec3d *pos) { int i; for(i = 0; i < 3; i++) { if(pos->a1d[i] < (box_vals[i] - box_vals[i+3]) || pos->a1d[i] > (box_vals[i] + box_vals[i+3])) { return false; } } return true; } int sexp_num_within_box(int n) { std::array box_vals;//x,y,z,width,height,depth int retval = 0; bool is_nan, is_nan_forever; eval_array(box_vals, n, is_nan, is_nan_forever); // nobody can be within a NaN box if (is_nan || is_nan_forever) return 0; for(; n >= 0; n = CDR(n)) { // this might be a ship auto ship_entry = eval_ship(n); if (ship_entry) { if (ship_entry->has_objp() && sexp_helper_is_within_box(box_vals, &ship_entry->objp()->pos)) retval++; } else { // this might be a wing. if it is, someone in the wing must be present auto wingp = eval_wing(n); if (wingp && wingp->current_count > 0) { // every present member in the wing must be in the box bool wing_check = true; for (int i = 0; i < wingp->current_count; i++) { if (!sexp_helper_is_within_box(box_vals, &Objects[Ships[wingp->ship_index[i]].objnum].pos)) { wing_check = false; break; } } if (wing_check) retval++; } } } return retval; } // Goober5000 // wookieejedi - this sexp is deprecated in favor of sexp_set_ship_man void sexp_set_object_speed(object *objp, int speed, int axis, bool subjective) { Assert(axis >= 0 && axis <= 2); if (subjective) { vec3d subjective_vel; // translate objective into subjective velocity vm_vec_rotate(&subjective_vel, &objp->phys_info.vel, &objp->orient); // set it subjective_vel.a1d[axis] = i2fl(speed); // translate it back to objective vm_vec_unrotate(&objp->phys_info.vel, &subjective_vel, &objp->orient); } else { objp->phys_info.vel.a1d[axis] = i2fl(speed); } } // Goober5000 // wookieejedi - this sexp is deprecated in favor of sexp_set_ship_man void sexp_set_object_speed(int n, int axis) { Assert(n >= 0); bool is_nan, is_nan_forever; int speed; bool subjective = false; object_ship_wing_point_team oswpt; eval_object_ship_wing_point_team(&oswpt, n); n = CDR(n); speed = eval_num(n, is_nan, is_nan_forever); n = CDR(n); if (is_nan || is_nan_forever) return; if (n >= 0) { subjective = is_sexp_true(n); n = CDR(n); } Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_int(speed); Current_sexp_network_packet.send_int(axis); Current_sexp_network_packet.send_bool(subjective); switch (oswpt.type) { case OSWPT_TYPE_SHIP: sexp_set_object_speed(oswpt.objp(), speed, axis, subjective); Current_sexp_network_packet.send_object(oswpt.objp()); break; case OSWPT_TYPE_WING: { for (int i = 0; i < oswpt.wingp()->current_count; ++i) { auto shipp = &Ships[oswpt.wingp()->ship_index[i]]; sexp_set_object_speed(&Objects[shipp->objnum], speed, axis, subjective); Current_sexp_network_packet.send_object(&Objects[shipp->objnum]); } break; } default: break; } Current_sexp_network_packet.end_callback(); } //CommanderDJ void multi_sexp_set_object_speed() { object *objp; int speed = 0, axis = 0; bool subjective = false; Current_sexp_network_packet.get_int(speed); Current_sexp_network_packet.get_int(axis); Current_sexp_network_packet.get_bool(subjective); while (Current_sexp_network_packet.get_object(objp)) sexp_set_object_speed(objp, speed, axis, subjective); } int sexp_get_object_speed(object *objp, int axis, bool subjective) { Assertion(((axis >= 0) && (axis <= 2)), "Axis is out of range (%d)", axis); int speed; if (subjective) { // return the speed based on the orentation of the object vec3d subjective_vel; vm_vec_rotate(&subjective_vel, &objp->phys_info.vel, &objp->orient); speed = fl2i(subjective_vel.a1d[axis]); vm_vec_unrotate(&objp->phys_info.vel, &subjective_vel, &objp->orient); } else { // return the speed according to the grid speed = fl2i(objp->phys_info.vel.a1d[axis]); } return speed; } int sexp_get_object_speed(int n, int axis) { Assert(n >= 0); int speed; bool subjective = false; object_ship_wing_point_team oswpt; eval_object_ship_wing_point_team(&oswpt, n); n = CDR(n); if (n >= 0) { subjective = is_sexp_true(n); n = CDR(n); } switch (oswpt.type) { case OSWPT_TYPE_EXITED: return SEXP_NAN_FOREVER; case OSWPT_TYPE_SHIP: case OSWPT_TYPE_WING: speed = sexp_get_object_speed(oswpt.objp(), axis, subjective); break; default: return SEXP_NAN; } return speed; } // Goober5000 int sexp_calculate_coordinate(vec3d *origin, matrix *orient, vec3d *relative_location, int axis) { Assert(origin != nullptr); Assert(orient != nullptr); Assert(axis >= 0 && axis <= 2); if (relative_location == nullptr) { return fl2i(origin->a1d[axis]); } else { vec3d new_world_pos; vm_vec_unrotate(&new_world_pos, relative_location, orient); vm_vec_add2(&new_world_pos, origin); return fl2i(new_world_pos.a1d[axis]); } } // Goober5000 int sexp_calculate_angle(matrix *orient, int axis) { Assert(orient != nullptr); Assert(axis >= 0 && axis <= 2); angles a; vm_extract_angles_matrix_alternate(&a, orient); // blugh float rad; switch (axis) { case 0: rad = a.p; break; case 1: rad = a.b; break; case 2: rad = a.h; break; default: rad = 0.0f; break; } float deg = fl_degrees(rad); int deg2 = static_cast(deg < 0.0f ? deg - 0.5f : deg + 0.5f); if (deg2 < 0) deg2 += 360; return deg2; } // Goober5000 int sexp_get_object_coordinate(int n, int axis) { Assert(n >= 0); const char *subsystem_name = nullptr; vec3d *pos = nullptr, *relative_location = nullptr, relative_location_buf, subsys_pos_buf; object_ship_wing_point_team oswpt; eval_object_ship_wing_point_team(&oswpt, n); n = CDR(n); if (n >= 0) { subsystem_name = CTEXT(n); n = CDR(n); if (n >= 0) { relative_location = &relative_location_buf; bool is_nan, is_nan_forever; eval_vec3d(relative_location, n, is_nan, is_nan_forever); if (is_nan) return SEXP_NAN; if (is_nan_forever) return SEXP_NAN_FOREVER; } } switch (oswpt.type) { case OSWPT_TYPE_EXITED: return SEXP_NAN_FOREVER; case OSWPT_TYPE_SHIP: case OSWPT_TYPE_WING: case OSWPT_TYPE_WAYPOINT: pos = &oswpt.objp()->pos; break; default: return SEXP_NAN; } // see if we have a subsys if (oswpt.objp()->type == OBJ_SHIP) { if ((subsystem_name != nullptr) && stricmp(subsystem_name, SEXP_NONE_STRING) != 0 && stricmp(subsystem_name, SEXP_HULL_STRING) != 0) { pos = &subsys_pos_buf; // get the world pos but bail if we can't get one if (!sexp_get_subsystem_world_pos(pos, oswpt.ship_entry(), subsystem_name)) return SEXP_NAN; } } return sexp_calculate_coordinate(pos, &oswpt.objp()->orient, relative_location, axis); } // Goober5000 int sexp_get_object_angle(int n, int axis) { Assert(n >= 0); object_ship_wing_point_team oswpt; eval_object_ship_wing_point_team(&oswpt, n); switch (oswpt.type) { case OSWPT_TYPE_EXITED: return SEXP_NAN_FOREVER; case OSWPT_TYPE_SHIP: case OSWPT_TYPE_WING: return sexp_calculate_angle(&oswpt.objp()->orient, axis); default: return SEXP_NAN; } } int sexp_angle_vectors(int node) { vec3d v1, v2; bool is_nan1, is_nan_forever1, is_nan2, is_nan_forever2; eval_vec3d(&v1, node, is_nan1, is_nan_forever1); eval_vec3d(&v2, node, is_nan2, is_nan_forever2); if (is_nan_forever1 || is_nan_forever2) return SEXP_NAN_FOREVER; if (is_nan1 || is_nan2) return SEXP_NAN; // coverity[uninit_use_in_call:FALSE] - v1 and v2 are always populated by eval_vec3d if (IS_VEC_NULL(&v1) || IS_VEC_NULL(&v2)) return SEXP_NAN; float angle = fl_degrees(vm_vec_dot(&v1, &v2) / (vm_vec_mag(&v1) * vm_vec_mag(&v2))); return fl2ir(angle); } int sexp_angle_fvec_target(int node) { auto* ship_entry = eval_ship(node); if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; else if (ship_entry->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; const vec3d& pos1 = ship_entry->objp()->pos; const vec3d& v1 = ship_entry->objp()->orient.vec.fvec; object_ship_wing_point_team oswpt; eval_object_ship_wing_point_team(&oswpt, CDR(node)); switch (oswpt.type) { case OSWPT_TYPE_SHIP: case OSWPT_TYPE_WING: case OSWPT_TYPE_WAYPOINT: { const vec3d& pos2 = oswpt.objp()->pos; if (vm_vec_equal(pos1, pos2)) return SEXP_NAN; vec3d v2; vm_vec_normalized_dir(&v2, &pos2, &pos1); // No need to divide by product of magnitudes, both fvec and v2 are normalized float angle = fl_degrees(acosf_safe(vm_vec_dot(&v1, &v2))); return fl2ir(angle); } case OSWPT_TYPE_NONE: case OSWPT_TYPE_EXITED: return SEXP_NAN_FOREVER; default: return SEXP_NAN; } } void set_object_for_clients(object *objp) { if (!(Game_mode & GM_MULTIPLAYER)) { return; } // Tell the player (if this is a client) that they've moved. if ((objp->flags[Object::Object_Flags::Player_ship]) && (objp != Player_obj) ){ multi_oo_send_changed_object(objp); } } void sexp_set_object_position(int n) { vec3d target_vec, orig_leader_vec; object_ship_wing_point_team oswpt; eval_object_ship_wing_point_team(&oswpt, n); n = CDR(n); bool is_nan, is_nan_forever; eval_vec3d(&target_vec, n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; switch (oswpt.type) { case OSWPT_TYPE_SHIP: { oswpt.objp()->pos = target_vec; set_object_for_clients(oswpt.objp()); if (oswpt.objp()->flags[Object::Object_Flags::Collides]) obj_collide_obj_cache_stale(oswpt.objp()); break; } case OSWPT_TYPE_PARSE_OBJECT: { oswpt.p_objp()->pos = target_vec; break; } case OSWPT_TYPE_WAYPOINT: { oswpt.waypointp()->set_pos(&target_vec); Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_ushort(oswpt.objp()->net_signature); Current_sexp_network_packet.send_float(target_vec.xyz.x); Current_sexp_network_packet.send_float(target_vec.xyz.y); Current_sexp_network_packet.send_float(target_vec.xyz.z); Current_sexp_network_packet.end_callback(); break; } case OSWPT_TYPE_WING: { // move the wing leader first orig_leader_vec = oswpt.objp()->pos; oswpt.objp()->pos = target_vec; set_object_for_clients(oswpt.objp()); // move everything in the wing for (int i = 0; i < oswpt.wingp()->current_count; i++) { int objnum = Ships[oswpt.wingp()->ship_index[i]].objnum; object *objp = &Objects[objnum]; if (objnum != oswpt.objnum) { vm_vec_sub2(&objp->pos, &orig_leader_vec); vm_vec_add2(&objp->pos, &target_vec); set_object_for_clients(objp); } if (objp->flags[Object::Object_Flags::Collides]) obj_collide_obj_cache_stale(objp); } break; } case OSWPT_TYPE_WING_NOT_PRESENT: { // search the arrival list for the wing leader and move him first bool found = false; for (auto p_objp: list_range(&Ship_arrival_list)) { if (p_objp->wingnum == oswpt.wingnum && p_objp->pos_in_wing == 0) { orig_leader_vec = p_objp->pos; p_objp->pos = target_vec; found = true; break; } } // if we didn't find him, the wing will never arrive, so bail if (!found) break; // move everything in the wing for (auto p_objp: list_range(&Ship_arrival_list)) { if (p_objp->wingnum == oswpt.wingnum && p_objp->pos_in_wing != 0) { vm_vec_sub2(&p_objp->pos, &orig_leader_vec); vm_vec_add2(&p_objp->pos, &target_vec); } } break; } default: break; } } // only for waypoints cause they don't get transferred the normal way void multi_sexp_set_object_position() { object *objp; vec3d wp_vec; ushort obj_sig; Current_sexp_network_packet.get_ushort(obj_sig); Current_sexp_network_packet.get_float(wp_vec.xyz.x); Current_sexp_network_packet.get_float(wp_vec.xyz.y); Current_sexp_network_packet.get_float(wp_vec.xyz.z); objp = multi_get_network_object(obj_sig); if (objp->type == OBJ_WAYPOINT) { waypoint *wpt = find_waypoint_with_instance(objp->instance); wpt->set_pos(&wp_vec); } } // Goober5000 void sexp_set_object_orientation(int n) { angles a; matrix target_orient; object_ship_wing_point_team oswpt; eval_object_ship_wing_point_team(&oswpt, n); n = CDR(n); bool is_nan, is_nan_forever; eval_angles(&a, n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; vm_angles_2_matrix(&target_orient, &a); switch (oswpt.type) { case OSWPT_TYPE_SHIP: { oswpt.objp()->orient = target_orient; set_object_for_clients(oswpt.objp()); if (oswpt.objp()->flags[Object::Object_Flags::Collides]) obj_collide_obj_cache_stale(oswpt.objp()); break; } case OSWPT_TYPE_PARSE_OBJECT: { oswpt.p_objp()->orient = target_orient; break; } case OSWPT_TYPE_WING: { // move everything in the wing for (int i = 0; i < oswpt.wingp()->current_count; i++) { auto objp = &Objects[Ships[oswpt.wingp()->ship_index[i]].objnum]; objp->orient = target_orient; set_object_for_clients(objp); if (objp->flags[Object::Object_Flags::Collides]) obj_collide_obj_cache_stale(objp); } break; } case OSWPT_TYPE_WING_NOT_PRESENT: { // move everything in the wing for (auto p_objp: list_range(&Ship_arrival_list)) { if (p_objp->wingnum == oswpt.wingnum) p_objp->orient = target_orient; } break; } default: break; } } // Goober5000 // this is different from sexp_set_object_orientation // and now can be used for arbitrary orients (i.e. for parse objects), not just objects void sexp_set_orient_sub(matrix *orient_to_set, const vec3d *pos, const vec3d *location, int turn_time = 0, int bank = 0, object *objp = nullptr) { Assert(orient_to_set && pos && location); vec3d v_orient; matrix m_orient; // are we doing this via ai? ------------------- if (objp && turn_time) { // set flag int bankflag = 0; if (!bank) { bankflag = AITTV_IGNORE_BANK; } // slow down or speed up the ship's turnrate to match the given turn time vec3d turnrate_mod; float turnrate_adjust = Ship_info[Ships[objp->instance].ship_info_index].srotation_time / (turn_time / 1000.f); vm_vec_make(&turnrate_mod, turnrate_adjust, turnrate_adjust, turnrate_adjust); // turn ai_turn_towards_vector(location, objp, nullptr, nullptr, 0.0f, (AITTV_VIA_SEXP | bankflag), nullptr, &turnrate_mod); // return return; } // calculate orientation matrix ---------------- vm_vec_sub(&v_orient, location, pos); if (IS_VEC_NULL_SQ_SAFE(&v_orient)) { Warning(LOCATION, "error in sexp setting ship orientation: can't point to self; quitting...\n"); return; } vm_vector_2_matrix(&m_orient, &v_orient, nullptr, nullptr); // set orientation ----------------------------- *orient_to_set = m_orient; // Tell the player (assuming it's a client) that they've moved. if (objp) set_object_for_clients(objp); } // Goober5000 void sexp_stuff_oswpt_location(vec3d **location, object_ship_wing_point_team *oswpt) { switch (oswpt->type) { case OSWPT_TYPE_SHIP: case OSWPT_TYPE_WING: case OSWPT_TYPE_WAYPOINT: *location = &oswpt->objp()->pos; break; case OSWPT_TYPE_PARSE_OBJECT: *location = &oswpt->p_objp()->pos; break; case OSWPT_TYPE_WING_NOT_PRESENT: { for (auto p_objp: list_range(&Ship_arrival_list)) { // use the wing leader's position, same as if the wing were present if (p_objp->wingnum == oswpt->wingnum && p_objp->pos_in_wing == 0) { *location = &p_objp->pos; break; } } break; } default: break; } } // Goober5000 void sexp_set_oswpt_facing(object_ship_wing_point_team *oswpt, const vec3d *location, int turn_time = 0, int bank = 0) { Assert(oswpt && location); switch (oswpt->type) { case OSWPT_TYPE_SHIP: sexp_set_orient_sub(&oswpt->objp()->orient, &oswpt->objp()->pos, location, turn_time, bank, oswpt->objp()); break; case OSWPT_TYPE_PARSE_OBJECT: sexp_set_orient_sub(&oswpt->p_objp()->orient, &oswpt->p_objp()->pos, location); break; case OSWPT_TYPE_WING: { for (int i = 0; i < oswpt->wingp()->current_count; i++) { auto objp = &Objects[Ships[oswpt->wingp()->ship_index[i]].objnum]; sexp_set_orient_sub(&objp->orient, &objp->pos, location, turn_time, bank, objp); } break; } case OSWPT_TYPE_WING_NOT_PRESENT: { for (auto p_objp: list_range(&Ship_arrival_list)) { if (p_objp->wingnum == oswpt->wingnum) sexp_set_orient_sub(&p_objp->orient, &p_objp->pos, location); } break; } default: break; } } // Goober5000 void sexp_set_object_facing(int n, bool facing_object) { bool is_nan, is_nan_forever; vec3d *location = nullptr; vec3d location_buf; int turn_time, bank; object_ship_wing_point_team oswpt1, oswpt2; // get ship or wing eval_object_ship_wing_point_team(&oswpt1, n); n = CDR(n); // get location if (facing_object) { eval_object_ship_wing_point_team(&oswpt2, n); n = CDR(n); sexp_stuff_oswpt_location(&location, &oswpt2); // ensure it's valid if (location == nullptr) return; } else { location = &location_buf; eval_vec3d(location, n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; } // get optional turn time and bank eval_nums(n, is_nan, is_nan_forever, turn_time, bank); if (is_nan || is_nan_forever) return; sexp_set_oswpt_facing(&oswpt1, location, turn_time, bank); } void sexp_set_ship_man(ship *shipp, int duration, int heading, int pitch, int bank, bool apply_all_rotate, int up, int sideways, int forward, bool apply_all_lat, int maneuver_flags) { ai_info *aip = &Ai_info[shipp->ai_index]; if (!(maneuver_flags & CIF_DONT_OVERRIDE_OLD_MANEUVERS)) { aip->ai_override_flags.reset(); } bool applied_rot, applied_lat; applied_rot = applied_lat = false; if (apply_all_rotate) { aip->ai_override_flags.set(AI::Maneuver_Override_Flags::Full_rot); aip->ai_override_ci.bank = bank / 100.0f; aip->ai_override_ci.pitch = pitch / 100.0f; aip->ai_override_ci.heading = heading / 100.0f; applied_rot = true; } else { if (bank != 0) { aip->ai_override_flags.set(AI::Maneuver_Override_Flags::Roll); aip->ai_override_ci.bank = bank / 100.0f; applied_rot = true; } if (pitch != 0) { aip->ai_override_flags.set(AI::Maneuver_Override_Flags::Pitch); aip->ai_override_ci.pitch = pitch / 100.0f; applied_rot = true; } if (heading != 0) { aip->ai_override_flags.set(AI::Maneuver_Override_Flags::Heading); aip->ai_override_ci.heading = heading / 100.0f; applied_rot = true; } } if (apply_all_lat) { aip->ai_override_flags.set(AI::Maneuver_Override_Flags::Full_lat); aip->ai_override_ci.vertical = up / 100.0f; aip->ai_override_ci.sideways = sideways / 100.0f; aip->ai_override_ci.forward = forward / 100.0f; applied_lat = true; } else { if (up != 0) { aip->ai_override_flags.set(AI::Maneuver_Override_Flags::Up); aip->ai_override_ci.vertical = up / 100.0f; applied_lat = true; } if (sideways != 0) { aip->ai_override_flags.set(AI::Maneuver_Override_Flags::Sideways); aip->ai_override_ci.sideways = sideways / 100.0f; applied_lat = true; } if (forward != 0) { aip->ai_override_flags.set(AI::Maneuver_Override_Flags::Forward); aip->ai_override_ci.forward = forward / 100.0f; applied_lat = true; } } // handle infinite timestamps if (duration >= 2) { if (applied_rot) aip->ai_override_rot_timestamp = timestamp(duration); if (applied_lat) aip->ai_override_lat_timestamp = timestamp(duration); } else { if (applied_rot) { aip->ai_override_rot_timestamp = timestamp(10); aip->ai_override_flags.set(AI::Maneuver_Override_Flags::Rotational_never_expire); } if (applied_lat) { aip->ai_override_lat_timestamp = timestamp(10); aip->ai_override_flags.set(AI::Maneuver_Override_Flags::Lateral_never_expire); } } if (maneuver_flags & CIF_DONT_BANK_WHEN_TURNING) { aip->ai_override_flags.set(AI::Maneuver_Override_Flags::Dont_bank_when_turning); } if (maneuver_flags & CIF_DONT_CLAMP_MAX_VELOCITY) { aip->ai_override_flags.set(AI::Maneuver_Override_Flags::Dont_clamp_max_velocity); } if (maneuver_flags & CIF_INSTANTANEOUS_ACCELERATION) { aip->ai_override_flags.set(AI::Maneuver_Override_Flags::Instantaneous_acceleration); } } void sexp_set_oswpt_maneuver(object_ship_wing_point_team *oswpt, int duration, int heading, int pitch, int bank, bool apply_all_rotate, int up, int sideways, int forward, bool apply_all_lat, int maneuver_flags) { Assert(oswpt); switch (oswpt->type) { case OSWPT_TYPE_SHIP: sexp_set_ship_man(oswpt->shipp(), duration, heading, pitch, bank, apply_all_rotate, up, sideways, forward, apply_all_lat, maneuver_flags); break; case OSWPT_TYPE_WING: { for (int i = 0; i < oswpt->wingp()->current_count; i++) { auto shipp = &Ships[oswpt->wingp()->ship_index[i]]; sexp_set_ship_man(shipp, duration, heading, pitch, bank, apply_all_rotate, up, sideways, forward, apply_all_lat, maneuver_flags); } break; } default: break; } } void sexp_set_ship_maneuver(int n, int op_num) { int bank = 0, heading = 0, pitch = 0; int up = 0, sideways = 0, forward = 0; int duration, i, temp, maneuver_flags = 0; bool apply_all_rotate = false, apply_all_lat = false, is_nan, is_nan_forever; object_ship_wing_point_team oswpt; eval_object_ship_wing_point_team(&oswpt, n); n = CDR(n); duration = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; if (op_num == OP_SHIP_ROT_MANEUVER || op_num == OP_SHIP_MANEUVER) { for(i=0;i<3;i++) { n = CDR(n); temp = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; if (i == 0) heading = temp; else if (i == 1) pitch = temp; else bank = temp; } n = CDR(n); apply_all_rotate = is_sexp_true(n); } if (op_num == OP_SHIP_LAT_MANEUVER || op_num == OP_SHIP_MANEUVER) { for(i=0;i<3;i++) { n = CDR(n); temp = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; if (i == 0) up = temp; else if (i == 1) sideways = temp; else forward = temp; } n = CDR(n); apply_all_lat = is_sexp_true(n); } if ((bank == 0) && (pitch == 0) && (heading == 0) && !apply_all_rotate && (up == 0) && (sideways == 0) && (forward == 0) && !apply_all_lat) return; n = CDR(n); if (n >= 0) { maneuver_flags = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; } sexp_set_oswpt_maneuver(&oswpt, duration, heading, pitch, bank, apply_all_rotate, up, sideways, forward, apply_all_lat, maneuver_flags); } /** * Determine when the last meaningful order was given to one or more ships. * * @return true or false depending on whether or not a meaningful order was received */ int sexp_last_order_time(int n) { int i; fix time; ai_goal *aigp; bool is_nan, is_nan_forever; time = i2f(eval_num(n, is_nan, is_nan_forever)); if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; Assert ( time >= 0 ); n = CDR(n); while ( n != -1 ) { aigp = nullptr; auto ship_entry = eval_ship(n); if (ship_entry) { if (ship_entry->has_shipp()) { aigp = Ai_info[ship_entry->shipp()->ai_index].goals; } } else { auto wingp = eval_wing(n); if (wingp && !wing_has_yet_to_arrive(wingp) && !wingp->flags[Ship::Wing_Flags::Gone]) { aigp = wingp->ai_goals; } } // if we cannot find ship or wing, return SEXP_FALSE if (!aigp) { return SEXP_FALSE; } // check the ai_goals structure 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 SEXP_TRUE; n = CDR(n); } return SEXP_FALSE; } /** * Return the number of players in the mission */ int sexp_num_players() { int count = 0; for (auto so: list_range(&Ship_obj_list)) { auto objp = &Objects[so->objnum]; if (objp->flags[Object::Object_Flags::Should_be_dead]) continue; if (objp->flags[Object::Object_Flags::Player_ship]) count++; } return count; } /** * Determine if the current skill level of the game is at least the skill level given in the SEXP */ int sexp_skill_level_at_least(int n) { auto level_name = CTEXT(n); if (!level_name) return SEXP_KNOWN_FALSE; for (int i = 0; i < NUM_SKILL_LEVELS; ++i) { if ( !stricmp(level_name, Skill_level_names(i, 0)) ) { if ( Game_skill_level >= i ){ return SEXP_TRUE; } else { return SEXP_FALSE; } } } // return SEXP_FALSE if not found!!! return SEXP_FALSE; } int sexp_was_promotion_granted(int /*n*/) { if (Player->flags & PLAYER_FLAGS_PROMOTED) return SEXP_TRUE; return SEXP_FALSE; } int sexp_was_medal_granted(int n) { if (n < 0) { if (Player->stats.m_medal_earned >= 0) return SEXP_TRUE; return SEXP_FALSE; } auto medal_name = CTEXT(n); for (int i = 0; i < (int)Medals.size(); ++i) { if (!stricmp(medal_name, Medals[i].name)) { if (Player->stats.m_medal_earned == i) { return SEXP_TRUE; } else { break; } } } return SEXP_FALSE; } float get_damage_caused(const ship_registry_entry *ship_entry, int attacker_sig) { int idx; float damage_total = 0.0f; // is the ship that took damage on the exit list? if (ship_entry->exited_index >= 0) { for (idx = 0; idx < MAX_DAMAGE_SLOTS; idx++) { if (Ships_exited[ship_entry->exited_index].damage_ship_id[idx] == attacker_sig) { damage_total += Ships_exited[ship_entry->exited_index].damage_ship[idx]; break; } } } // is it referenceable? else if (ship_entry->has_shipp()) { for (idx = 0; idx < MAX_DAMAGE_SLOTS; idx++) { if (ship_entry->shipp()->damage_ship_id[idx] == attacker_sig) { damage_total += ship_entry->shipp()->damage_ship[idx]; break; } } } return damage_total; } // Karajorma int sexp_get_damage_caused(int node) { int ship_class, attacker_sig; float damage_caused = 0.0f; auto ship_entry = eval_ship(node); if (!ship_entry) return SEXP_NAN; // a ship which hasn't arrived can't have taken any damage yet if (ship_entry->status == ShipStatus::NOT_YET_PRESENT) { return 0; } // this ship may have exited already. if (ship_entry->exited_index >= 0) { ship_class = Ships_exited[ship_entry->exited_index].ship_class; } else if (ship_entry->has_shipp()) { ship_class = ship_entry->shipp()->ship_info_index; } else { // it probably vanished return SEXP_NAN_FOREVER; } node = CDR(node); Assert (node != -1); // go through the list of ships who we think may have attacked the ship for ( ; node != -1; node = CDR(node) ) { auto attacker = eval_ship(node); if (!attacker || attacker->status == ShipStatus::NOT_YET_PRESENT) { continue; } // this ship may have exited already. if (attacker->exited_index >= 0) { attacker_sig = Ships_exited[attacker->exited_index].obj_signature; } else if (attacker->has_objp()) { attacker_sig = attacker->objp()->signature; } else { // it probably vanished continue; } damage_caused += get_damage_caused (ship_entry, attacker_sig); } Assertion((ship_class > -1) && (ship_class < ship_info_size()), "Invalid ship class '%d' passed to sexp_get_damage_caused() (should be >= 0 and < %d); get a coder!\n", ship_class, ship_info_size()); return (int) ((damage_caused/Ship_info[ship_class].max_hull_strength) * 100.0f); } /** * Returns true if the percentage of ships (and ships in wings) that have met the given objective is at least the percentage given. * * "what" determines if we should check destroyed or departed status * Goober5000 - added disarm, disable, and scan */ int sexp_percent_ships_arrive_depart_destroy_disarm_disable_scan(int n, int what) { int percent; int total, count, impossible_count; bool is_nan, is_nan_forever; percent = eval_num(n, is_nan, is_nan_forever); if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; total = 0; count = 0; impossible_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) ) { // this might be a wing auto wingp = eval_wing(n); if (wingp) { // 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 += (wingp->wave_count * wingp->num_waves); if ( what == OP_PERCENT_SHIPS_DEPARTED ) { count += wingp->total_departed; impossible_count += (wingp->total_destroyed + wingp->total_vanished); } else if ( what == OP_PERCENT_SHIPS_DESTROYED ) { count += wingp->total_destroyed; impossible_count += (wingp->total_departed + wingp->total_vanished); } else if ( what == OP_PERCENT_SHIPS_ARRIVED ) { count += wingp->total_arrived_count; } else Warning(LOCATION, "Invalid status check '%d' for wing '%s' in sexp_percent_ships_arrive_depart_destroy_disarm_disable_scan", what, wingp->name); } else { auto ship_entry = eval_ship(n); // must be a ship, so increment the total by 1, then determine if this ship has departed total++; // no need to check invalid ships, or ones that haven't arrived if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) { continue; } auto name = ship_entry->name; int old_count = count; if ( what == OP_PERCENT_SHIPS_DEPARTED ) { if ( mission_log_get_time(LOG_SHIP_DEPARTED, name, nullptr, nullptr) ) count++; } else if ( what == OP_PERCENT_SHIPS_DESTROYED ) { if ( mission_log_get_time(LOG_SHIP_DESTROYED, name, nullptr, nullptr) || mission_log_get_time(LOG_SELF_DESTRUCTED, name, nullptr, nullptr) ) count++; } else if ( what == OP_PERCENT_SHIPS_DISABLED ) { if ( mission_log_get_time(LOG_SHIP_DISABLED, name, nullptr, nullptr) ) count++; } else if ( what == OP_PERCENT_SHIPS_DISARMED ) { if ( mission_log_get_time(LOG_SHIP_DISARMED, name, nullptr, nullptr) ) count++; } else if ( what == OP_PERCENT_SHIPS_ARRIVED ) { if ( mission_log_get_time(LOG_SHIP_ARRIVED, name, nullptr, nullptr) ) count++; } else if ( what == OP_PERCENT_SHIPS_SCANNED ) { if ( mission_log_get_time(LOG_CARGO_REVEALED, name, nullptr, nullptr) ) count++; } else Warning(LOCATION, "Invalid status check '%d' for ship '%s' in sexp_percent_ships_arrive_depart_destroy_disarm_disable_scan", what, name); if (old_count == count && ship_entry->status == ShipStatus::EXITED) impossible_count++; } } // if there is nothing to check, the percentage is meaningless; this can happen if, for example, a wing // arrives from a docking bay and its mothership is destroyed before the wing has a chance to arrive if ( total <= 0 ) return SEXP_FALSE; // now, look at the percentage if ( ((count * 100) / total) >= percent ) return SEXP_KNOWN_TRUE; // now see if the percentage can't be satisfied else if ( ((impossible_count * 100) / total) > (100 - percent) ) return SEXP_KNOWN_FALSE; else return SEXP_FALSE; } /** * Determine if a list of ships has departed from within a radius of a given jump node. * @return true N seconds after the list of ships have departed */ int sexp_depart_node_delay(int n) { int count, num_departed; fix delay, latest_time, this_time; bool is_nan, is_nan_forever; // get the delay delay = i2f(eval_num(n, is_nan, is_nan_forever)); n = CDR(n); if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; auto jump_node_name = CTEXT(n); n = CDR(n); // iterate through the list of ships latest_time = 0; count = 0; num_departed = 0; while ( n != -1 ) { count++; auto ship_entry = eval_ship(n); if (!ship_entry) return SEXP_FALSE; if (ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_CANT_EVAL; if (ship_entry->status == ShipStatus::EXITED) { if (mission_log_get_time(LOG_SHIP_DEPARTED, ship_entry->name, jump_node_name, &this_time)) { num_departed++; if (this_time > latest_time) latest_time = this_time; } else return SEXP_KNOWN_FALSE; // exited but not departed (or not through the node) } n = CDR(n); } if ( (count == num_departed) && ((Missiontime - latest_time) >= delay) ) return SEXP_KNOWN_TRUE; else return SEXP_FALSE; } /** * 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; bool is_nan, is_nan_forever; Assert( n >= 0 ); // get the delay delay = i2f(eval_num(n, is_nan, is_nan_forever)); n = CDR(n); if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; count = 0; // number destroyed or departed total = 0; // total number of ships/wings to check latest_time = 0; while ( n != -1 ) { fix time_gone = 0; total++; auto wingp = eval_wing(n); if (wingp) { if (wing_has_yet_to_arrive(wingp)) { return SEXP_CANT_EVAL; } // for wings, check the WF_GONE flag to see if there are no more ships in this wing to arrive. if (wingp->flags[Ship::Wing_Flags::Gone]) { // be sure to get the latest time of one of these if (wingp->time_gone > latest_time) { time_gone = wingp->time_gone; } count++; } } else { auto ship_entry = eval_ship(n); if (ship_entry) { if (ship_entry->status == ShipStatus::NOT_YET_PRESENT) { return SEXP_CANT_EVAL; } if (ship_entry->status == ShipStatus::DEATH_ROLL || ship_entry->status == ShipStatus::EXITED) { if (mission_log_get_time(LOG_SHIP_DEPARTED, ship_entry->name, nullptr, &time_gone)) { count++; } else if (mission_log_get_time(LOG_SHIP_DESTROYED, ship_entry->name, nullptr, &time_gone)) { count++; } else if (mission_log_get_time(LOG_SELF_DESTRUCTED, ship_entry->name, nullptr, &time_gone)) { count++; } // apparently we don't count vanished ships // (and we can't, because the mission log didn't record the time they left) else { return SEXP_KNOWN_FALSE; } } } } // 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 SEXP_FALSE; } void sexp_special_warpout_name( int node ) { auto ship_entry = eval_ship(node); if (!ship_entry || !ship_entry->has_shipp()) return; auto knossos_entry = eval_ship(CDR(node)); if (!knossos_entry || !knossos_entry->has_shipp()) return; // set special warpout objnum ship_entry->shipp()->special_warpout_objnum = knossos_entry->objnum; } /** * Determines if N seconds have elapsed since all discovery of all cargo of given ships * * Goober5000 - I reworked this function to allow for the set-scanned and set-unscanned sexps * to work multiple times in a row and also to fix the potential bug where exited ships are * checked against their departure time, not against their cargo known time */ int sexp_is_cargo_known( int n, bool check_delay ) { bool is_nan, is_nan_forever; int count, num_known, delay; Assert ( n >= 0 ); count = 0; num_known = 0; // get the delay value (if there is one) delay = 0; if ( check_delay ) { delay = eval_num(n, is_nan, is_nan_forever); n = CDR(n); if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; } while ( n != -1 ) { fix time_known; bool is_known = false; count++; auto ship_entry = eval_ship(n); if (!ship_entry) return SEXP_FALSE; if (ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_CANT_EVAL; // see if the ship has already exited the mission (either through departure or destruction) if (ship_entry->exited_index >= 0) { // if not known, the whole thing is known false if ( !(Ships_exited[ship_entry->exited_index].flags[Ship::Exit_Flags::Cargo_known]) ) return SEXP_KNOWN_FALSE; // check the delay of when we found out time_known = Missiontime - Ships_exited[ship_entry->exited_index].time_cargo_revealed; if ( f2i(time_known) >= delay ) is_known = true; } // ship is in mission else if (ship_entry->has_shipp()) { if ( ship_entry->shipp()->flags[Ship::Ship_Flags::Cargo_revealed] ) { time_known = Missiontime - ship_entry->shipp()->time_cargo_revealed; if ( f2i(time_known) >= delay ) is_known = true; } } // ship probably vanished else return SEXP_NAN_FOREVER; // if cargo is known, mark our variable if ( is_known ) num_known++; n = CDR(n); } Directive_count += count - num_known; if ( count == num_known ) return SEXP_TRUE; else return SEXP_FALSE; } void get_cap_subsys_cargo_flags(const ship *shipp, const char *subsys_name, int *known, fix *time_revealed) { // find the ship subsystem ship_subsys *ss = ship_get_subsys(shipp, subsys_name); if (ss) { // set the flags *known = (ss->flags[Ship::Subsystem_Flags::Cargo_revealed]); *time_revealed = ss->time_subsys_cargo_revealed; } // if we didn't find the subsystem, the ship hasn't arrived yet else { *known = -1; *time_revealed = 0; } } // reworked by Goober5000 to allow for set-scanned and set-unscanned to be used more than once int sexp_cap_subsys_cargo_known_delay(int n) { bool is_nan, is_nan_forever; int delay, count, num_known; num_known = 0; count = 0; Assert( n >= 0 ); // get delay delay = eval_num(n, is_nan, is_nan_forever); n = CDR(n); if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; // get ship auto ship_entry = eval_ship(n); if (!ship_entry) return SEXP_FALSE; if (ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_CANT_EVAL; n = CDR(n); while ( n != -1 ) { fix time_known; bool is_known = false; count++; // get subsys name auto subsys_name = CTEXT(n); // see if the ship has already exited the mission (either through departure or destruction) if (ship_entry->status == ShipStatus::EXITED) { // check the delay of when we found out... // Since there is no way to keep track of subsystem status once a ship has departed // or has been destroyed, check the mission log. This will work in 99.9999999% of // all cases; however, if the mission designer repeatedly sets and resets the scanned // status of the subsystem, the mission log will only return the first occurrence of the // subsystem cargo being revealed (regardless of whether it was first hidden using // set-unscanned). Normally, ships keep track of cargo data in the subsystem struct, // but once the ship has left the mission, the subsystem linked list is purged, // causing the loss of this information. I judged the significant rework of the // subsystem code not worth the rare instance that this sexp may be required to work // in this way, especially since this problem only occurs after the ship departs. If // the mission designer really needs this functionality, he or she can achieve the // same result with creative combinations of event chaining and is-event-true. if (!mission_log_get_time(LOG_CAP_SUBSYS_CARGO_REVEALED, ship_entry->name, subsys_name, &time_known)) { // if not known, the whole thing is known false return SEXP_KNOWN_FALSE; } if (f2i(Missiontime - time_known) >= delay) is_known = true; } // ship is in mission else { int cargo_revealed(0); fix time_revealed(0); // get flags get_cap_subsys_cargo_flags(ship_entry->shipp(), subsys_name, &cargo_revealed, &time_revealed); if (cargo_revealed) { time_known = Missiontime - time_revealed; if ( f2i(time_known) >= delay ) is_known = true; } } // if cargo is known, mark our variable if ( is_known ) num_known++; n = CDR(n); } Directive_count += count - num_known; if ( count == num_known ) return SEXP_TRUE; else return SEXP_FALSE; } // Goober5000 void sexp_set_scanned_unscanned(int n, int flag) { // get ship auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) return; // check for possible next optional argument: subsystem n = CDR(n); // if no subsystem specified, just do it for the ship and exit if (n == -1) { if (flag) ship_do_cargo_revealed(ship_entry->shipp()); else ship_do_cargo_hidden(ship_entry->shipp()); return; } // iterate through all subsystems while (n != -1) { auto subsys_name = CTEXT(n); // find the ship subsystem auto ss = ship_get_subsys(ship_entry->shipp(), subsys_name); if (ss) { // do it for the subsystem if (flag) ship_do_cap_subsys_cargo_revealed(ship_entry->shipp(), ss); else ship_do_cap_subsys_cargo_hidden(ship_entry->shipp(), ss); } // if we didn't find the subsystem -- bad if (!ss && ship_class_unchanged(ship_entry)) { Warning(LOCATION, "Couldn't find subsystem '%s' on ship '%s' in sexp_set_scanned_unscanned", subsys_name, ship_entry->name); } // but if it did, loop again n = CDR(n); } } int sexp_has_been_tagged_delay(int n) { bool is_nan, is_nan_forever; int count, num_known, delay; Assert ( n >= 0 ); count = 0; num_known = 0; // get the delay value delay = eval_num(n, is_nan, is_nan_forever); n = CDR(n); if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; while ( n != -1 ) { fix time_known; bool is_known = false; count++; auto ship_entry = eval_ship(n); if (!ship_entry) return SEXP_FALSE; if (ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_CANT_EVAL; // see if the ship has already exited the mission (either through departure or destruction). If so, // grab the status of the tag from this list if (ship_entry->exited_index >= 0) { if ( !(Ships_exited[ship_entry->exited_index].flags[Ship::Exit_Flags::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[ship_entry->exited_index].time; if ( f2i(time_known) >= delay ) is_known = true; } // ship is in mission else if (ship_entry->has_shipp()) { if ( ship_entry->shipp()->time_first_tagged != 0 ) { time_known = Missiontime - ship_entry->shipp()->time_first_tagged; if ( f2i(time_known) >= delay ) is_known = true; } } // ship probably vanished else return SEXP_NAN_FOREVER; // if ship is tagged, mark our variable if ( is_known ) num_known++; n = CDR(n); } Directive_count += count - num_known; if ( count == num_known ) return SEXP_KNOWN_TRUE; else return SEXP_FALSE; } // Karajorma void eval_when_for_each_special_argument( int cur_node ) { arg_item *ptr; // loop through all the supplied arguments ptr = Sexp_applicable_argument_list.get_next(); while (ptr != nullptr) { // acquire argument to be used Sexp_replacement_arguments.emplace_back(ptr->text, ptr->node); Sexp_current_argument_nesting_level++; Sexp_applicable_argument_list.add_data(ptr->text, ptr->node); // execute sexp... CTEXT will insert the argument as necessary eval_sexp(cur_node); // clean up any special sexp stuff Sexp_applicable_argument_list.clear_nesting_level(); Sexp_current_argument_nesting_level--; // remove the argument Sexp_replacement_arguments.pop_back(); // continue along argument list ptr = ptr->get_next(); } } // Goober5000 void do_action_for_each_special_argument( int cur_node ) { arg_item *ptr; // loop through all the supplied arguments ptr = Sexp_applicable_argument_list.get_next(); while (ptr != nullptr) { // acquire argument to be used Sexp_replacement_arguments.emplace_back(ptr->text, ptr->node); // execute sexp... CTEXT will insert the argument as necessary // (since these are all actions, they don't return any meaningful values) eval_sexp(cur_node); // remove the argument Sexp_replacement_arguments.pop_back(); // continue along argument list ptr = ptr->get_next(); } } bool is_descendant_of_when_argument_op(int node) { // empty tree if (node < 0) return false; // cached? if (Sexp_nodes[node].flags & SNF_DESCENDANT_OF_WHEN_ARG_OP) return true; if (Sexp_nodes[node].flags & SNF_NOT_DESCENDANT_OF_WHEN_ARG_OP) return false; // see whether this node or any parent node qualifies // (note that find_parent_operator() is expensive, so we want to cache any and all results) bool result; if (Sexp_nodes[node].type == SEXP_ATOM && Sexp_nodes[node].subtype == SEXP_ATOM_OPERATOR && is_when_argument_op(get_operator_const(node))) result = true; else result = is_descendant_of_when_argument_op(find_parent_operator(node)); // cache this result if (result) Sexp_nodes[node].flags |= SNF_DESCENDANT_OF_WHEN_ARG_OP; else Sexp_nodes[node].flags |= SNF_NOT_DESCENDANT_OF_WHEN_ARG_OP; return result; } // Goober5000 bool special_argument_appears_in_sexp_tree(int node) { // empty tree if (node < 0) return false; // cached? if (Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_TREE) return true; if (Sexp_nodes[node].flags & SNF_SPECIAL_ARG_NOT_IN_TREE) return false; // special argument? if (Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_NODE) return true; // we don't want to include special arguments if they are nested in a new argument SEXP if (Sexp_nodes[node].type == SEXP_ATOM && Sexp_nodes[node].subtype == SEXP_ATOM_OPERATOR) { if (is_when_argument_op(get_operator_const(node))) { return false; } } bool result = special_argument_appears_in_sexp_tree(CAR(node)) || special_argument_appears_in_sexp_tree(CDR(node)); // cache this result if (result) Sexp_nodes[node].flags |= SNF_SPECIAL_ARG_IN_TREE; else Sexp_nodes[node].flags |= SNF_SPECIAL_ARG_NOT_IN_TREE; return result; } // Goober5000 bool special_argument_appears_in_sexp_list(int node) { // look through list while (node != -1) { // special argument? if (Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_NODE) return true; node = CDR(node); } return false; } // conditional sexpressions follow // Goober5000 void eval_when_do_one_exp(int exp) { arg_item *ptr; int do_node; switch (get_operator_const(exp)) { // if the op is a conditional then we just evaluate it case OP_WHEN: case OP_EVERY_TIME: case OP_IF_THEN_ELSE: // need to account for the possibility this call uses if (special_argument_appears_in_sexp_tree(exp)) { ptr = Sexp_applicable_argument_list.get_next(); if (ptr != nullptr) { eval_when_for_each_special_argument(exp); } else { eval_sexp(exp); } } else { eval_sexp(exp); } break; case OP_DO_FOR_VALID_ARGUMENTS: if (special_argument_appears_in_sexp_tree(exp)) { Warning(LOCATION, " used within do-for-valid-arguments SEXP. Skipping entire SEXP"); break; } do_node = CDR(exp); while (do_node != -1) { do_action_for_each_special_argument(do_node); do_node = CDR(do_node); } break; case OP_WHEN_ARGUMENT: case OP_EVERY_TIME_ARGUMENT: eval_sexp(exp); break; // otherwise we need to check if arguments are used default: // if we're using the special argument in this action if (special_argument_appears_in_sexp_tree(exp)) { do_action_for_each_special_argument(exp); // these sexps eval'd only for side effects } // if not, just evaluate it once as-is else { // Goober5000 - possible bug? (see when val is used below) /*val = */eval_sexp(exp); // these sexps eval'd only for side effects } } } // Karajorma void eval_when_do_all_exp(int all_actions, int when_op_num) { arg_item *ptr; int exp; int actions; int op_num; bool first_loop = true; // loop through all the supplied arguments ptr = Sexp_applicable_argument_list.get_next(); while (ptr != nullptr) { // acquire argument to be used Sexp_replacement_arguments.emplace_back(ptr->text, ptr->node); actions = all_actions; while (actions != -1) { exp = CAR(actions); op_num = get_operator_const(exp); if (op_num == OP_DO_FOR_VALID_ARGUMENTS) { int do_node = CDR(exp); while (do_node != -1) { eval_sexp(do_node); do_node = CDR(do_node); } } else if ( first_loop || special_argument_appears_in_sexp_tree(exp) ) { switch (op_num) { // if the op is a conditional we have to make sure that it can access arguments case OP_WHEN: case OP_EVERY_TIME: case OP_IF_THEN_ELSE: Sexp_current_argument_nesting_level++; Sexp_applicable_argument_list.add_data(ptr->text, ptr->node); eval_sexp(exp); Sexp_applicable_argument_list.clear_nesting_level(); Sexp_current_argument_nesting_level--; break; default: eval_sexp(exp); } } // iterate actions = CDR(actions); // if-then-else only has one "if" action if (when_op_num == OP_IF_THEN_ELSE) break; } first_loop = false; // remove the argument Sexp_replacement_arguments.pop_back(); // continue along argument list ptr = ptr->get_next(); } } int sexp_functional_when_eval_type_lookup(const char *text) { for (int i = 0; i < Num_functional_when_eval_types; i++) if (!stricmp(text, Functional_when_eval_type[i])) return i; return -1; } /** * This is like using when, but it takes a lot of shortcuts. It's clearer just to separate it out into its own function, especially since it's not supposed to start * a new level of special argument handling, like eval_when would do. It's a lot like the original retail version of eval_when! */ int eval_perform_actions(int n, int op_num) { int return_cond, actions_cond, val = SEXP_NAN, actions; bool retval_first; Assert( n >= 0 ); if (op_num == OP_PERFORM_ACTIONS_BOOL_FIRST) { return_cond = CAR(n); retval_first = true; actions_cond = Locked_sexp_true; actions = CDR(n); } else if (op_num == OP_PERFORM_ACTIONS_BOOL_LAST) { return_cond = CAR(n); retval_first = false; actions_cond = Locked_sexp_true; actions = CDR(n); } else if (op_num == OP_FUNCTIONAL_WHEN) { return_cond = CAR(n); n = CDR(n); int eval_type = sexp_functional_when_eval_type_lookup(CTEXT(n)); if (eval_type == FWET_RETVAL_FIRST) retval_first = true; else if (eval_type == FWET_RETVAL_LAST) retval_first = false; else return SEXP_NAN; n = CDR(n); actions_cond = CAR(n); n = CDR(n); actions = n; } else if (op_num == OP_ON_MISSION_SKIP) { retval_first = true; actions = n; // if the player is skipping the mission, we'll unconditionally perform the actions if ((Game_mode & GM_CAMPAIGN_MODE) && (Campaign.current_mission >= 0) && (Campaign.missions[Campaign.current_mission].flags & CMISSION_FLAG_SKIPPED)) { return_cond = Locked_sexp_true; actions_cond = Locked_sexp_true; } // if the player isn't skipping the mission (which is most of the time), we'll return false else { return_cond = Locked_sexp_false; actions_cond = Locked_sexp_false; } } else { Assertion(false, "Unsupported SEXP %d!", op_num); return SEXP_NAN_FOREVER; } if (retval_first) { // evaluate the conditional to see what value we eventually return val = eval_sexp(return_cond); } if (is_sexp_true(actions_cond)) { // perform all the actions in the rest of the sexp // (Since we are technically inside a boolean condition already, no special argument handling is needed. The special argument, if any, // will have been provided by a higher level of nesting.) while (actions != -1) { // get the operator int exp = CAR(actions); if (exp != -1) eval_sexp(exp); // iterate actions = CDR(actions); } } if (!retval_first) { // evaluate the conditional to see what value we now return val = eval_sexp(return_cond); } // return whatever val was, but don't return known-* // note: SEXP_KNOWN_TRUE/SEXP_KNOWN_FALSE are never returned from eval_sexp return val; } void eval_switch(int n) { bool is_nan, is_nan_forever; int choice = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever || choice < 0) return; n = CDR(n); // iterate until we land on the action we want to perform while (choice > 0 && n >= 0) { --choice; n = CDR(n); } // out of range? if (n < 0) return; // do it int exp = CAR(n); if (exp >= 0) eval_sexp(exp); } /** * Evaluates the when conditional * * @note Goober5000 - added capability for arguments * @note Goober5000 - and also if-then-else and perform-actions */ int eval_when(int n, int when_op_num) { int arg_handler = -1, cond, val, actions; Assert( n >= 0 ); arg_item *ptr; // get the parts of the sexp and evaluate the conditional if (is_when_argument_op(when_op_num)) { arg_handler = CAR(n); cond = CADR(n); actions = CDDR(n); Sexp_current_argument_nesting_level++; sexp_container_set_special_arg_status(arg_handler, true); // evaluate for custom arguments val = eval_sexp(arg_handler, cond); } // normal evaluation else { cond = CAR(n); actions = CDR(n); // evaluate just as-is val = eval_sexp(cond); } // if value is true, perform the actions in the 'then' part if (val == SEXP_TRUE) // note: SEXP_KNOWN_TRUE is never returned from eval_sexp { // get the operator int exp = CAR(actions); // if the mod.tbl setting is in effect we want to each evaluate all the SEXPs for // each argument if (True_loop_argument_sexps && special_argument_appears_in_sexp_tree(actions)) { if (exp != -1) { eval_when_do_all_exp(actions, when_op_num); } } // without the mod.tbl setting (or if there are no arguments in this SEXP) we loop // through every action performing them for all arguments else { while (actions != -1) { // get the operator exp = CAR(actions); if (exp != -1) eval_when_do_one_exp(exp); // iterate actions = CDR(actions); // if-then-else only has one "if" action if (when_op_num == OP_IF_THEN_ELSE) break; } } } // if-then-else has actions to perform under "else" else if (val == SEXP_FALSE && when_op_num == OP_IF_THEN_ELSE) // note: SEXP_KNOWN_FALSE is never returned from eval_sexp { // skip past the "if" action actions = CDR(actions); // loop through every action while (actions != -1) { // get the operator int exp = CAR(actions); if (exp != -1) eval_when_do_one_exp(exp); // iterate actions = CDR(actions); } // invert val so that we behave like a when with opposite results // note: SEXP_KNOWN_FALSE is never returned from eval_sexp val = SEXP_TRUE; } if (is_when_argument_op(when_op_num)) { if (Log_event) { ptr = Sexp_applicable_argument_list.get_next(); while(ptr != nullptr) { // See if we have an argument. Current_event_log_argument_buffer->push_back(ptr->text); ptr = ptr->get_next(); } } // clean up any special sexp stuff Sexp_applicable_argument_list.clear_nesting_level(); Sexp_current_argument_nesting_level--; sexp_container_set_special_arg_status(arg_handler, false); } // thanks to MageKing17 for noticing that we need to short-circuit on the correct node! int short_circuit_node = (arg_handler >= 0) ? arg_handler : cond; if (Sexp_nodes[short_circuit_node].value == SEXP_KNOWN_FALSE || Sexp_nodes[short_circuit_node].value == SEXP_NAN_FOREVER) return SEXP_KNOWN_FALSE; // no need to waste time on this anymore // note: val can't be SEXP_KNOWN_FALSE at this point return val; } /** * Evaluate the conditional */ int eval_cond(int n) { int cond = 0, node, val = SEXP_FALSE; 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 == SEXP_TRUE) // note: any SEXP_KNOWN_TRUE result is returned as SEXP_TRUE { int actions, exp; val = SEXP_FALSE; 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; } // Goober5000 // NOTE: if you change this function, check to see if the following function should also be changed! int test_argument_nodes_for_condition(int n, int condition_node, int *num_true, int *num_false, int *num_known_true, int *num_known_false, int threshold = -1) { int val, num_valid_arguments; SCP_vector> Applicable_arguments_temp; Assert(n != -1 && condition_node != -1); Assert((num_true != nullptr) && (num_false != nullptr) && (num_known_true != nullptr) && (num_known_false != nullptr)); // ensure special argument list is empty Sexp_applicable_argument_list.clear_nesting_level(); Applicable_arguments_temp.clear(); // ditto for counters num_valid_arguments = 0; *num_true = 0; *num_false = 0; *num_known_true = 0; *num_known_false = 0; // loop through all arguments while (n != -1) { // only eval this argument if it's valid if (Sexp_nodes[n].flags & SNF_ARGUMENT_VALID) { const int num_args = copy_node_to_replacement_args(n); for (int i = 0; i < num_args; ++i) { // evaluate conditional for current argument val = eval_sexp(condition_node); if (Sexp_nodes[condition_node].value == SEXP_KNOWN_TRUE || Sexp_nodes[condition_node].value == SEXP_KNOWN_FALSE) { val = Sexp_nodes[condition_node].value; } else if (Sexp_nodes[condition_node].value == SEXP_NAN_FOREVER) { // In accordance with SEXP_NAN/SEXP_NAN_FOREVER becoming SEXP_FALSE in eval_sexp() val = SEXP_KNOWN_FALSE; } const auto &argument = Sexp_replacement_arguments.back(); switch (val) { case SEXP_TRUE: (*num_true)++; Applicable_arguments_temp.emplace_back(argument.first, argument.second); break; case SEXP_FALSE: (*num_false)++; break; case SEXP_KNOWN_TRUE: (*num_known_true)++; Applicable_arguments_temp.emplace_back(argument.first, argument.second); break; case SEXP_KNOWN_FALSE: (*num_known_false)++; break; } // clear argument, but not list, as we'll need it later Sexp_replacement_arguments.pop_back(); // increment num_valid_arguments++; } } // continue along argument list n = CDR(n); } // we may only want a subset of the argument list if (threshold >= 0 && Applicable_arguments_temp.size() > (size_t)threshold) Applicable_arguments_temp.resize(threshold); // now we write from the temporary store into the real one, reversing the order. We do this because // Sexp_applicable_argument_list is a stack and we want the first argument in the list to be the first one out while (!Applicable_arguments_temp.empty()) { Sexp_applicable_argument_list.add_data(Applicable_arguments_temp.back()); Applicable_arguments_temp.pop_back(); } return num_valid_arguments; } /** * Internal parameter type, only used with test_argument_vector_for_condition */ enum class STRDUP_STATUS { ALREADY_DUPPED, DUP_NEEDED, DUP_NOT_NEEDED }; // Goober5000 // NOTE: if you change this function, check to see if the previous function should also be changed! int test_argument_vector_for_condition(const SCP_vector> &argument_vector, STRDUP_STATUS strdup_status, int condition_node, int *num_true, int *num_false, int *num_known_true, int *num_known_false) { int val, num_valid_arguments; SCP_vector> Applicable_arguments_temp; Assert(condition_node != -1); Assert((num_true != nullptr) && (num_false != nullptr) && (num_known_true != nullptr) && (num_known_false != nullptr)); // ensure special argument list is empty Sexp_applicable_argument_list.clear_nesting_level(); Applicable_arguments_temp.clear(); // ditto for counters num_valid_arguments = 0; *num_true = 0; *num_false = 0; *num_known_true = 0; *num_known_false = 0; // loop through all arguments for (const auto &argument : argument_vector) { // since we can't see or modify the validity, assume all are valid { // evaluate conditional for current argument Sexp_replacement_arguments.push_back(argument); val = eval_sexp(condition_node); if ( Sexp_nodes[condition_node].value == SEXP_KNOWN_TRUE || Sexp_nodes[condition_node].value == SEXP_KNOWN_FALSE) { val = Sexp_nodes[condition_node].value; } else if ( Sexp_nodes[condition_node].value == SEXP_NAN_FOREVER ) { // In accordance with SEXP_NAN/SEXP_NAN_FOREVER becoming SEXP_FALSE in eval_sexp() val = SEXP_KNOWN_FALSE; } switch (val) { case SEXP_TRUE: (*num_true)++; Applicable_arguments_temp.push_back(argument); break; case SEXP_FALSE: (*num_false)++; break; case SEXP_KNOWN_TRUE: (*num_known_true)++; Applicable_arguments_temp.push_back(argument); break; case SEXP_KNOWN_FALSE: (*num_known_false)++; break; } // if the argument was already dup'd, but not added as an applicable argument, // we need to free it here before we cause a memory leak if ((val == SEXP_FALSE || val == SEXP_KNOWN_FALSE) && (strdup_status == STRDUP_STATUS::ALREADY_DUPPED)) vm_free(const_cast(argument.first)); // clear argument, but not list, as we'll need it later Sexp_replacement_arguments.pop_back(); // increment num_valid_arguments++; } } // now we write from the temporary store into the real one, reversing the order. We do this because // Sexp_applicable_argument_list is a stack and we want the first argument in the list to be the first one out while (!Applicable_arguments_temp.empty()) { // if we're using a temporary buffer for the string (as opposed to a permanent buffer like shipp->ship_name or Sexp_nodes[n].text) // then we need to dup the strings, but we need to know whether the calling function dup'd them, or whether we should dup them here if (strdup_status == STRDUP_STATUS::ALREADY_DUPPED) Sexp_applicable_argument_list.add_data_set_dup(Applicable_arguments_temp.back()); else if (strdup_status == STRDUP_STATUS::DUP_NEEDED) Sexp_applicable_argument_list.add_data_dup(Applicable_arguments_temp.back()); else Sexp_applicable_argument_list.add_data(Applicable_arguments_temp.back()); Applicable_arguments_temp.pop_back(); } return num_valid_arguments; } // Goober5000 int eval_any_of(int arg_handler_node, int condition_node) { int n, num_valid_arguments, num_true, num_false, num_known_true, num_known_false; Assert(arg_handler_node > -1 && condition_node > -1); // Don't use invalid indexes - Coverity 1523820 if(arg_handler_node < 0 || condition_node < 0) return SEXP_FALSE; // the arguments should just be data, not operators, so we can skip the CAR n = CDR(arg_handler_node); // test the whole argument list num_valid_arguments = test_argument_nodes_for_condition(n, condition_node, &num_true, &num_false, &num_known_true, &num_known_false); // use the sexp_or algorithm if (num_known_true || num_true) return SEXP_TRUE; else if (num_known_false == num_valid_arguments) return SEXP_KNOWN_FALSE; else return SEXP_FALSE; } // Goober5000 int eval_every_of(int arg_handler_node, int condition_node) { int n, num_valid_arguments, num_true, num_false, num_known_true, num_known_false; Assert(arg_handler_node > -1 && condition_node > -1); // Don't use invalid indexes - Coverity 1523820 if(arg_handler_node < 0 || condition_node < 0) return SEXP_FALSE; // the arguments should just be data, not operators, so we can skip the CAR n = CDR(arg_handler_node); // test the whole argument list num_valid_arguments = test_argument_nodes_for_condition(n, condition_node, &num_true, &num_false, &num_known_true, &num_known_false); // use the sexp_and algorithm if ((!num_valid_arguments) || num_known_false) return SEXP_KNOWN_FALSE; else if (num_false) return SEXP_FALSE; else return SEXP_TRUE; } // Goober5000 int eval_number_of(int arg_handler_node, int condition_node) { bool is_nan, is_nan_forever; int n, num_valid_arguments, num_true, num_false, num_known_true, num_known_false, threshold; Assert(arg_handler_node > -1 && condition_node > -1); // Don't use invalid indexes - Coverity 1523820 if(arg_handler_node < 0 || condition_node < 0) return SEXP_FALSE; // the arguments should just be data, not operators, so we can skip the CAR n = CDR(arg_handler_node); // the first argument is the number threshold threshold = eval_num(n, is_nan, is_nan_forever); n = CDR(n); // test the whole argument list num_valid_arguments = test_argument_nodes_for_condition(n, condition_node, &num_true, &num_false, &num_known_true, &num_known_false); // we check for NaN after the conditions are evaluated, just as the logical operators evaluate all their conditions if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; // use the sexp_or algorithm, modified // (true if at least threshold arguments are true) if (num_true + num_known_true >= threshold) return SEXP_TRUE; else if (num_valid_arguments - num_known_false < threshold) return SEXP_KNOWN_FALSE; else return SEXP_FALSE; } // Goober5000 // this works a little differently... we randomly pick one argument to use // for our condition, but this argument must be saved among sexp calls... // so we select an argument and set its flag int eval_random_of(int arg_handler_node, int condition_node) { int n = -1, i, val, num_valid_args, random_argument, num_known_false = 0; Assert(arg_handler_node > -1 && condition_node > -1); // Don't use invalid indexes - Coverity 1523820 if(arg_handler_node < 0 || condition_node < 0) return SEXP_FALSE; // get the number of valid arguments num_valid_args = query_sexp_args_count(arg_handler_node, true); Assert(num_valid_args >= 0); if (num_valid_args == 0) { return SEXP_KNOWN_FALSE; // Not much point in trying to evaluate it again. } // find which argument we picked, if we picked one n = CDR(arg_handler_node); // iterate to the argument we previously selected for ( ; n != -1; n = CDR(n)) { if (Sexp_nodes[n].flags & SNF_ARGUMENT_SELECT) break; } // if argument not found (or never specified in the first place), we have to pick one if (n == -1) { n = CDR(arg_handler_node); int temp_node = n; // pick an argument and iterate to it random_argument = rand_internal(1, num_valid_args); i = 0; for (int j = 0; j < num_valid_args; temp_node = CDR(temp_node)) { Assert(n >= 0); // count only valid arguments if (Sexp_nodes[temp_node].flags & SNF_ARGUMENT_VALID) { j++; if (i < random_argument && (++i == random_argument)) { // Found the node we want, store it for use n = temp_node; } if ((Sexp_nodes[temp_node].value == SEXP_KNOWN_FALSE) || (Sexp_nodes[temp_node].value == SEXP_NAN_FOREVER)) num_known_false++; } } if (num_known_false == num_valid_args) { return SEXP_KNOWN_FALSE; // We're going nowhere fast. } // save it Sexp_nodes[n].flags |= SNF_ARGUMENT_SELECT; } // only eval this argument if it's valid val = SEXP_FALSE; if (Sexp_nodes[n].flags & SNF_ARGUMENT_VALID) { // ensure special argument list is empty Sexp_applicable_argument_list.clear_nesting_level(); // evaluate conditional for current argument Sexp_replacement_arguments.emplace_back(Sexp_nodes[n].text, n); val = eval_sexp(condition_node); // true? if (val == SEXP_TRUE) { Sexp_applicable_argument_list.add_data(Sexp_nodes[n].text, n); } else if (Sexp_nodes[condition_node].value == SEXP_KNOWN_FALSE || Sexp_nodes[condition_node].value == SEXP_NAN_FOREVER) { val = SEXP_KNOWN_FALSE; // If we can't randomly pick another one and this one is guaranteed never to be true, then give up now. } // clear argument, but not list, as we'll need it later Sexp_replacement_arguments.pop_back(); } // true if our selected argument is true return val; } // originally part of eval_random_of() but pulled out as the code paths diverged int eval_random_multiple_of(int arg_handler_node, int condition_node) { Assertion(arg_handler_node != -1, "No argument handler provided to random-multiple-of. Please report!"); Assertion(condition_node != -1, "No condition provided to random-multiple-of. Please report!"); // Don't use invalid indexes - Coverity 1523820 if(arg_handler_node < 0 || condition_node < 0) return SEXP_FALSE; // get the number of valid arguments SCP_vector cumulative_arg_counts; const int num_valid_arg_nodes = sexp_container_query_sexp_args_count(arg_handler_node, cumulative_arg_counts, true); Assertion(num_valid_arg_nodes >= 0, "random-multiple-of found invalid number of valid arguments (%d). please report!", num_valid_arg_nodes); if (num_valid_arg_nodes == 0) { return SEXP_KNOWN_FALSE; // Not much point in trying to evaluate it again. } Assertion(!cumulative_arg_counts.empty(), "Attempt to count arguments for random-multiple-of SEXP failed. Please report!"); const int num_arg_values = cumulative_arg_counts.back(); Assertion(num_arg_values >= 0, "random-multiple-of found invalid number of valid argument values (%d). please report!", num_arg_values); if (num_arg_values == 0) { // valid args are one or more empty containers, whose data could change later return SEXP_FALSE; } int n = CDR(arg_handler_node); Assertion(n >= 0, "No arguments provided to random-multiple-of. Please report!"); int temp_node = n; // pick an argument and iterate to it const int random_arg_value = rand_internal(1, num_arg_values); int random_argument = 0; // skip the first entry, which is the initial zero value for (int k = 1, size = (int)cumulative_arg_counts.size(); k < size; ++k) { if (random_arg_value <= cumulative_arg_counts[k]) { random_argument = k; break; } } Assertion(random_argument > 0, "Attempt to find randomly selected argument in random-multiple-of failed. Please report!"); int i = 0; int num_known_false = 0; bool valid_container_arg_found = false; for (int j = 0; j < num_valid_arg_nodes; temp_node = CDR(temp_node)) { // count only valid arguments if (Sexp_nodes[temp_node].flags & SNF_ARGUMENT_VALID) { j++; if (Sexp_nodes[temp_node].subtype == SEXP_ATOM_CONTAINER_NAME) { valid_container_arg_found = true; } if (i < random_argument && (++i == random_argument)) { // Found the node we want, store it for use n = temp_node; } if ((Sexp_nodes[temp_node].value == SEXP_KNOWN_FALSE) || (Sexp_nodes[temp_node].value == SEXP_NAN_FOREVER)) num_known_false++; } } if (num_known_false == num_valid_arg_nodes) { return SEXP_KNOWN_FALSE; // We're going nowhere fast. } // only eval this argument if it's valid int val = SEXP_FALSE; if (Sexp_nodes[n].flags & SNF_ARGUMENT_VALID) { // ensure special argument list is empty Sexp_applicable_argument_list.clear_nesting_level(); const int arg_value_index = (Sexp_nodes[n].subtype == SEXP_ATOM_CONTAINER_NAME) ? random_arg_value - cumulative_arg_counts[random_argument - 1] - 1 : -1; copy_node_to_replacement_args(n, arg_value_index); // evaluate conditional for current argument val = eval_sexp(condition_node); // true? if (val == SEXP_TRUE) { const char *arg_text = Sexp_replacement_arguments.back().first; Sexp_applicable_argument_list.add_data(arg_text, n); } else if ((num_valid_arg_nodes == 1) && !valid_container_arg_found && (Sexp_nodes[condition_node].value == SEXP_KNOWN_FALSE || Sexp_nodes[condition_node].value == SEXP_NAN_FOREVER)) { val = SEXP_KNOWN_FALSE; // If the only valid arg is guaranteed never to be true, then give up now. } // clear argument, but not list, as we'll need it later Sexp_replacement_arguments.pop_back(); } // true if our selected argument is true return val; } // Karajorma - this conditional returns the first valid option on its list. int eval_in_sequence(int arg_handler_node, int condition_node) { int val = SEXP_FALSE; int n = -1 ; Assert(arg_handler_node > -1 && condition_node > -1); // Don't use invalid indexes - Coverity 1523820 if(arg_handler_node < 0 || condition_node < 0) return SEXP_FALSE; // get the first argument n = CDR(arg_handler_node); Assert (n != -1); // loop through the nodes until we find one that is holds a valid argument or run out of nodes for (int i=1 ; i> argument_vector; char buf[NAME_LENGTH]; Assert(arg_handler_node > -1 && condition_node > -1); // Don't use invalid indexes - Coverity 1523820 if(arg_handler_node < 0 || condition_node < 0) return SEXP_FALSE; n = CDR(arg_handler_node); // determine the counter parameters count = eval_nums(n, is_nan, is_nan_forever, counter_start, counter_stop, counter_step); if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; if (count < 3) counter_step = 1; // a bunch of error checking if (counter_step == 0) { Warning(LOCATION, "A counter increment of 0 is illegal! (start=%d, stop=%d, increment=%d)", counter_start, counter_stop, counter_step); return SEXP_KNOWN_FALSE; } else if (counter_start == counter_stop) { Warning(LOCATION, "The counter start and stop values are identical! (start=%d, stop=%d, increment=%d)", counter_start, counter_stop, counter_step); return SEXP_KNOWN_FALSE; } else if (sign(counter_stop - counter_start) != sign(counter_step)) { Warning(LOCATION, "The counter cannot complete with the given values! (start=%d, stop=%d, increment=%d)", counter_start, counter_stop, counter_step); return SEXP_KNOWN_FALSE; } // build a vector of counter values for (i = counter_start; ((counter_step > 0) ? i <= counter_stop : i >= counter_stop); i += counter_step) { // if we're just counting we should just avoid the memory management problem mentioned below, // since we return before calling test_argument_vector_for_condition if (just_count) num_valid_arguments++; else { sprintf(buf, "%d", i); argument_vector.emplace_back(vm_strdup(buf), -1); // Note: we do not call vm_free() on the contents of argument_vector, and we don't for the very good // reason that those pointers are then passed along by test_argument_vector_for_condition(), below. // The strings will then be freed by arg_item::expunge() or arg_item::clear_nesting_level(), or even // inside test_argument_vector_for_condition() (if the argument doesn't satisfy the condition). So // under no circumstances try to free these strings inside this function! It will cause a double-free // situation, resulting in a crash at best. -MageKing17 } } if (just_count) return num_valid_arguments; // test the whole argument vector num_valid_arguments = test_argument_vector_for_condition(argument_vector, STRDUP_STATUS::ALREADY_DUPPED, condition_node, &num_true, &num_false, &num_known_true, &num_known_false); // use the sexp_or algorithm if (num_known_true || num_true) return SEXP_TRUE; else if (num_known_false == num_valid_arguments) return SEXP_KNOWN_FALSE; else return SEXP_FALSE; } // Goober5000 int eval_for_ship_collection(int arg_handler_node, int condition_node, int op_const, bool just_count = false) { int n, num_valid_arguments = 0, num_true, num_false, num_known_true, num_known_false; SCP_vector> argument_vector; Assert(arg_handler_node > -1 && condition_node > -1); // Don't use invalid indexes - Coverity 1523820 if(arg_handler_node < 0 || condition_node < 0) return SEXP_FALSE; n = CDR(arg_handler_node); // handle each parameter for (; n >= 0; n = CDR(n)) { auto constraint = CTEXT(n); int constraint_index = -1; switch (op_const) { case OP_FOR_SHIP_CLASS: constraint_index = ship_info_lookup(constraint); break; case OP_FOR_SHIP_TYPE: constraint_index = ship_type_name_lookup(constraint); if (constraint_index < 0 && Fighter_bomber_valid && !stricmp(constraint, Fighter_bomber_type_name)) constraint_index = Ship_type_fighter_bomber; break; case OP_FOR_SHIP_TEAM: constraint_index = iff_lookup(constraint); break; case OP_FOR_SHIP_SPECIES: constraint_index = species_info_lookup(constraint); break; } if (constraint_index < 0) continue; // add all ships of this constraint which are present in the mission for (auto so: list_range(&Ship_obj_list)) { auto objp = &Objects[so->objnum]; if (objp->flags[Object::Object_Flags::Should_be_dead]) continue; auto shipp = &Ships[objp->instance]; int ship_index = -1; switch (op_const) { case OP_FOR_SHIP_CLASS: ship_index = shipp->ship_info_index; break; case OP_FOR_SHIP_TYPE: ship_index = ship_class_query_general_type(shipp->ship_info_index); break; case OP_FOR_SHIP_TEAM: ship_index = shipp->team; break; case OP_FOR_SHIP_SPECIES: ship_index = Ship_info[shipp->ship_info_index].species; break; } bool constraint_matches; if (op_const == OP_FOR_SHIP_TYPE && constraint_index == Ship_type_fighter_bomber) constraint_matches = (ship_index == Ship_type_fighter || ship_index == Ship_type_bomber); else constraint_matches = (ship_index == constraint_index); if (constraint_matches) { if (just_count) num_valid_arguments++; else argument_vector.emplace_back(shipp->ship_name, -1); } } } if (just_count) return num_valid_arguments; // test the whole argument vector num_valid_arguments = test_argument_vector_for_condition(argument_vector, STRDUP_STATUS::DUP_NOT_NEEDED, condition_node, &num_true, &num_false, &num_known_true, &num_known_false); SCP_UNUSED(num_valid_arguments); // use the sexp_or algorithm if (num_known_true || num_true) return SEXP_TRUE; // Don't short-circuit because ships can arrive later on, and this sexp's ship collection is intrinsically unknowable. // else if (num_known_false == num_valid_arguments) // return SEXP_KNOWN_FALSE; else return SEXP_FALSE; } // Goober5000 int eval_for_subsystems(int arg_handler_node, int condition_node, bool just_count = false) { int n, num_valid_arguments = 0, num_true, num_false, num_known_true, num_known_false; SCP_vector> argument_vector; Assert(arg_handler_node > -1 && condition_node > -1); // Don't use invalid indexes - Coverity 1523820 if(arg_handler_node < 0 || condition_node < 0) return SEXP_FALSE; n = CDR(arg_handler_node); // get the ship auto ship_entry = eval_ship(n); if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_FALSE; if (ship_entry->status == ShipStatus::EXITED) return SEXP_KNOWN_FALSE; n = CDR(n); bool subsystem_type_specified = (n >= 0); SCP_unordered_set subsystem_types; // get all the types we need to search for for (; n >= 0; n = CDR(n)) { auto type_index = string_lookup(CTEXT(n), Subsystem_types, SUBSYSTEM_MAX); if (type_index >= 0) subsystem_types.insert(type_index); } // go through all subsystems and add the relevant ones for (auto ss : list_range(&ship_entry->shipp()->subsys_list)) { if (!subsystem_type_specified || subsystem_types.find(ss->system_info->type) != subsystem_types.end()) { if (just_count) num_valid_arguments++; else argument_vector.emplace_back(ss->system_info->subobj_name, -1); } } if (just_count) return num_valid_arguments; // test the whole argument vector num_valid_arguments = test_argument_vector_for_condition(argument_vector, STRDUP_STATUS::DUP_NOT_NEEDED, condition_node, &num_true, &num_false, &num_known_true, &num_known_false); SCP_UNUSED(num_valid_arguments); // use the sexp_or algorithm if (num_known_true || num_true) return SEXP_TRUE; // ok to short-circuit here because a ship's subsystem collection is known and fixed else if (num_known_false == num_valid_arguments) return SEXP_KNOWN_FALSE; else return SEXP_FALSE; } // jg18 int eval_for_container(int arg_handler_node, int condition_node, int op_const, bool just_count = false) { Assertion(arg_handler_node != -1, "Attempt to use invalid argument handler with a for-container SEXP (%d). Please report!", op_const); Assertion(condition_node != -1, "Attempt to use invalid condition with a for-container SEXP (%d). Please report!", op_const); // Don't use invalid indexes - Coverity 1523820 if(arg_handler_node < 0 || condition_node < 0) return SEXP_FALSE; int num_arguments = 0; SCP_vector> argument_vector; const int arg_node = CDR(arg_handler_node); switch (op_const) { case OP_FOR_CONTAINER_DATA: num_arguments = sexp_container_collect_data_arguments(arg_node, argument_vector, just_count); break; case OP_FOR_MAP_CONTAINER_KEYS: num_arguments = sexp_container_collect_map_key_arguments(arg_node, argument_vector, just_count); break; default: Assertion(false, "Unhandled for-container SEXP (%d). Please report!", op_const); break; } if (just_count) { // cleaning up argument_vector isn't needed // because the "collect arguments" functions didn't touch it return num_arguments; } // test the whole argument vector int num_true, num_false, num_known_true, num_known_false; const int num_valid_arguments = test_argument_vector_for_condition(argument_vector, STRDUP_STATUS::ALREADY_DUPPED, condition_node, &num_true, &num_false, &num_known_true, &num_known_false); SCP_UNUSED(num_valid_arguments); // use the sexp_or algorithm if (num_known_true || num_true) { return SEXP_TRUE; // Don't short-circuit because container data/keys can change later on, // and this sexp's container is intrinsically unknowable. //} else if (num_valid_arguments > 0 && num_known_false == num_valid_arguments) { // return SEXP_KNOWN_FALSE; } else { return SEXP_FALSE; } } // Goober5000 int eval_for_players(int arg_handler_node, int condition_node, bool just_count = false) { int num_valid_arguments = 0, num_true, num_false, num_known_true, num_known_false; SCP_vector> argument_vector; Assert(arg_handler_node > -1 && condition_node > -1); // Don't use invalid indexes - Coverity 1523820 if(arg_handler_node < 0 || condition_node < 0) return SEXP_FALSE; if (Game_mode & GM_MULTIPLAYER) { // this operator has no parameters, so just generate a vector of players for (int i = 0; i < MAX_PLAYERS; ++i) { int shipnum = multi_get_player_ship(i); if (shipnum >= 0) { if (just_count) num_valid_arguments++; else argument_vector.emplace_back(vm_strdup(Ships[shipnum].ship_name), -1); } } } else { // in single-player it's just one ship if (Player_ship) { if (just_count) num_valid_arguments++; else argument_vector.emplace_back(Player_ship->ship_name, -1); } } if (just_count) return num_valid_arguments; // test the whole argument vector num_valid_arguments = test_argument_vector_for_condition(argument_vector, STRDUP_STATUS::DUP_NOT_NEEDED, condition_node, &num_true, &num_false, &num_known_true, &num_known_false); // use the sexp_or algorithm if (num_known_true || num_true) return SEXP_TRUE; else if (num_valid_arguments > 0 && num_known_false == num_valid_arguments) return SEXP_KNOWN_FALSE; else return SEXP_FALSE; } // MageKing17 - this is sort of a cross between any-of, number-of, and in-sequence int eval_first_of(int arg_handler_node, int condition_node) { bool is_nan, is_nan_forever; int n, num_valid_arguments, num_true, num_false, num_known_true, num_known_false, threshold; Assert(arg_handler_node > -1 && condition_node > -1); // Don't use invalid indexes - Coverity 1523820 if(arg_handler_node < 0 || condition_node < 0) return SEXP_FALSE; // the arguments should just be data, not operators, so we can skip the CAR n = CDR(arg_handler_node); // the first argument is the number threshold threshold = eval_num(n, is_nan, is_nan_forever); n = CDR(n); // test the whole argument list num_valid_arguments = test_argument_nodes_for_condition(n, condition_node, &num_true, &num_false, &num_known_true, &num_known_false, threshold); // we check for NaN after the conditions are evaluated, just as the logical operators evaluate all their conditions if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; // use the sexp_or algorithm if (num_known_true || num_true) return SEXP_TRUE; else if (num_known_false == num_valid_arguments) return SEXP_KNOWN_FALSE; else return SEXP_FALSE; } void sexp_change_all_argument_validity(int n, bool invalidate) { int arg_handler, arg_n; arg_handler = find_argument_provider(n); // prevent a crash if the SEXP is used somewhere it's not supposed to be if (arg_handler < 0) return; // can't change validity of for-* sexps auto op_const = get_operator_const(arg_handler); if (is_implicit_argument_provider_op(op_const)) return; while (n != -1) { arg_n = CDR(arg_handler); while (arg_n != -1) { if (invalidate) { Sexp_nodes[arg_n].flags &= ~SNF_ARGUMENT_VALID; } else { Sexp_nodes[arg_n].flags |= SNF_ARGUMENT_VALID; } // iterate arg_n = CDR(arg_n); } // iterate n = CDR(n); } } int sexp_num_valid_arguments( int n ) { int arg_handler, arg_n; int matches = 0; arg_handler = find_argument_provider(n); // prevent a crash if the SEXP is used somewhere it's not supposed to be if (arg_handler < 0) return 0; // the for-* sexps require special handling: they don't list their arguments explicitly but rather generate them on-the-fly auto op_const = get_operator_const(arg_handler); switch (op_const) { case OP_FOR_COUNTER: return eval_for_counter(arg_handler, Locked_sexp_true, true); case OP_FOR_PLAYERS: return eval_for_players(arg_handler, Locked_sexp_true, true); case OP_FOR_SHIP_CLASS: case OP_FOR_SHIP_TYPE: case OP_FOR_SHIP_TEAM: case OP_FOR_SHIP_SPECIES: return eval_for_ship_collection(arg_handler, Locked_sexp_true, op_const, true); case OP_FOR_SUBSYSTEMS: return eval_for_subsystems(arg_handler, Locked_sexp_true, true); case OP_FOR_CONTAINER_DATA: case OP_FOR_MAP_CONTAINER_KEYS: return eval_for_container(arg_handler, Locked_sexp_true, op_const, true); } // loop through arguments arg_n = CDR(arg_handler); while (arg_n != -1) { if (Sexp_nodes[arg_n].flags & SNF_ARGUMENT_VALID) { matches++; } // iterate arg_n = CDR(arg_n); } return matches; } // Goober5000 void sexp_change_argument_validity(int n, bool invalidate) { int arg_handler, arg_n; bool toggled; arg_handler = find_argument_provider(n); // prevent a crash if the SEXP is used somewhere it's not supposed to be // (thanks to woutersmits for finding this bug) if (arg_handler < 0) return; // can't change validity of for-* sexps auto op_const = get_operator_const(arg_handler); if (is_implicit_argument_provider_op(op_const)) return; // loop through arguments while (n != -1) { toggled = false; // first we must check if the arg_handler marks a selection. At the moment random-of is the only one that does this arg_n = CDR(arg_handler); while (invalidate && (arg_n != -1)) { Assertion(Sexp_nodes[arg_n].subtype != SEXP_ATOM_CONTAINER_NAME, "Attempt to use invalidate-argument with container %s. Please report!", Sexp_nodes[arg_n].text); Assertion(Sexp_nodes[arg_n].subtype != SEXP_ATOM_CONTAINER_DATA, "Attempt to use invalidate-argument with data from container %s. Please report!", Sexp_nodes[arg_n].text); if (Sexp_nodes[arg_n].flags & SNF_ARGUMENT_SELECT) { // now check if the selected argument matches the one we want to invalidate if (!strcmp(CTEXT(n), CTEXT(arg_n))) { // set it as invalid Sexp_nodes[arg_n].flags &= ~SNF_ARGUMENT_VALID; toggled = true; } } // iterate arg_n = CDR(arg_n); } if (!toggled) { // search for argument in arg_handler list arg_n = CDR(arg_handler); while (arg_n != -1) { Assertion(Sexp_nodes[arg_n].subtype != SEXP_ATOM_CONTAINER_NAME, "Attempt to change argument validity of container %s. Please report!", Sexp_nodes[arg_n].text); Assertion(Sexp_nodes[arg_n].subtype != SEXP_ATOM_CONTAINER_DATA, "Attempt to change argument validity of data from container %s. Please report!", Sexp_nodes[arg_n].text); // match? if (!strcmp(CTEXT(n), CTEXT(arg_n))) { if (invalidate) { // we need to check if the argument is already invalid as some argument lists may contain duplicates if (Sexp_nodes[arg_n].flags & SNF_ARGUMENT_VALID) { // set it as invalid Sexp_nodes[arg_n].flags &= ~SNF_ARGUMENT_VALID; // exit inner loop break; } } else { if (!(Sexp_nodes[arg_n].flags & SNF_ARGUMENT_VALID)) { // set it as valid Sexp_nodes[arg_n].flags |= SNF_ARGUMENT_VALID; // exit inner loop break; } } } // iterate arg_n = CDR(arg_n); } } // iterate n = CDR(n); } } /** * Given any SEXP node, return the node of the argument provider operator used in the enclosing when-argument-type SEXP, or -1 if not found. */ int find_argument_provider(int node) { int conditional, arg_handler; if (node < 0) { return -1; } conditional = node; do { // find the conditional sexp conditional = find_parent_operator(conditional); if (conditional == -1) { return -1; } } while (!is_when_argument_op(get_operator_const(conditional))); // get the first op of the parent, which should be a *_of operator arg_handler = CADR(conditional); if (arg_handler < 0 || !is_argument_provider_op(get_operator_const(arg_handler))) { return -1; } return arg_handler; } /** * Checks whether this operator is when-argument or every-time-argument. (Any future similar operators should be added here too.) */ bool is_when_argument_op(const int op_const) { switch (op_const) { case OP_WHEN_ARGUMENT: case OP_EVERY_TIME_ARGUMENT: return true; default: return false; } } /** * Checks whether this operator can provide values (whether explicitly or implicitly) to a when-argument-type operator. */ bool is_argument_provider_op(const int op_const) { // implicit operators are covered by their own function if (is_implicit_argument_provider_op(op_const)) { return true; } // these operators provide values by explicitly listing them switch (op_const) { case OP_ANY_OF: case OP_EVERY_OF: case OP_NUMBER_OF: case OP_RANDOM_OF: case OP_RANDOM_MULTIPLE_OF: case OP_IN_SEQUENCE: case OP_FIRST_OF: return true; default: return false; } } /** * Checks whether this operator provides values implicitly, that is, by generating them rather than listing them in the SEXP. */ bool is_implicit_argument_provider_op(const int op_const) { switch (op_const) { case OP_FOR_COUNTER: case OP_FOR_SHIP_CLASS: case OP_FOR_SHIP_TYPE: case OP_FOR_SHIP_TEAM: case OP_FOR_SHIP_SPECIES: case OP_FOR_PLAYERS: case OP_FOR_SUBSYSTEMS: case OP_FOR_CONTAINER_DATA: case OP_FOR_MAP_CONTAINER_KEYS: return true; default: return false; } } // Goober5000 int sexp_functional_if_then_else(int node) { int num1, num2, n = node; bool is_nan1, is_nan_forever1, is_nan2, is_nan_forever2; Assertion(CAR(n) >= 0, "The condition in functional-if-then-else must be an operator!"); // decision time int condition = eval_sexp(CAR(n)); n = CDR(n); // we need to evaluate both numbers regardless of which one we pick num1 = eval_num(n, is_nan1, is_nan_forever1); n = CDR(n); num2 = eval_num(n, is_nan2, is_nan_forever2); // pick one if (condition == SEXP_TRUE || condition == SEXP_KNOWN_TRUE) { if (is_nan1) return SEXP_NAN; else if (is_nan_forever1) return SEXP_NAN_FOREVER; else return num1; } else { if (is_nan2) return SEXP_NAN; else if (is_nan_forever2) return SEXP_NAN_FOREVER; else return num2; } } // Goober5000 int sexp_functional_switch(int node) { bool is_nan, is_nan_forever; int n = node, result = 0; int choice = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever || choice < 0) return 0; n = CDR(n); // evaluate all the numbers, but only keep the one we want while (n >= 0) { int temp = eval_num(n, is_nan, is_nan_forever); if (choice == 0) { if (is_nan) result = SEXP_NAN; else if (is_nan_forever) result = SEXP_NAN_FOREVER; else result = temp; } --choice; n = CDR(n); } return result; } // Goober500 int sexp_is_true_for_duration(int op_node, int node) { // find our start time using the duration slot, creating one if necessary if (Sexp_nodes[op_node].duration_index < 0) { Sexp_nodes[op_node].duration_index = static_cast(Sexp_is_true_for_duration_times.size()); Sexp_is_true_for_duration_times.push_back(Missiontime); } fix& start_time = Sexp_is_true_for_duration_times[Sexp_nodes[op_node].duration_index]; // get the duration we want bool is_nan, is_nan_forever; int duration = eval_num(node, is_nan, is_nan_forever); if (is_nan_forever) return SEXP_KNOWN_FALSE; if (is_nan || duration < 0) return SEXP_FALSE; // see if all the conditions are true bool all_true = true; for (int n = CDR(node); n >= 0; n = CDR(n)) { if (!is_sexp_true(n)) all_true = false; // don't break because we want to evaluate all the conditions -- see sexp_and() } // reset the clock? if (!all_true) { start_time = Missiontime; return SEXP_FALSE; } // figure out the elapsed delta in milliseconds int elapsed_delta = fl2i(f2fl(Missiontime - start_time) * MILLISECONDS_PER_SECOND); // compare to the duration if (elapsed_delta >= duration) return SEXP_TRUE; else return SEXP_FALSE; } // Goober5000 - added wing capability int sexp_is_iff_or_species(int n, bool iff) { int i, value; // iff/species value is the first parameter, second is a list of one or more ships/wings to check to see if the value matches if (iff) value = iff_lookup(CTEXT(n)); else value = species_info_lookup(CTEXT(n)); n = CDR(n); for ( ; n != -1; n = CDR(n) ) { object_ship_wing_point_team oswpt; eval_object_ship_wing_point_team(&oswpt, n); switch (oswpt.type) { case OSWPT_TYPE_SHIP: { // if the field doesn't match the value specified, return false immediately if (iff) { if (oswpt.shipp()->team != value) return SEXP_FALSE; } else { if (Ship_info[oswpt.shipp()->ship_info_index].species != value) return SEXP_FALSE; } break; } case OSWPT_TYPE_PARSE_OBJECT: { // if the field doesn't match the value specified, return false immediately if (iff) { if (oswpt.p_objp()->team != value) return SEXP_FALSE; } else { if (Ship_info[oswpt.p_objp()->ship_class].species != value) return SEXP_FALSE; } break; } case OSWPT_TYPE_WING: { for (i = 0; i < oswpt.wingp()->current_count; i++) { // if the field doesn't match the value specified, return false immediately if (iff) { if (Ships[oswpt.wingp()->ship_index[i]].team != value) return SEXP_FALSE; } else { if (Ship_info[Ships[oswpt.wingp()->ship_index[i]].ship_info_index].species != value) return SEXP_FALSE; } } break; } case OSWPT_TYPE_WING_NOT_PRESENT: { bool at_least_one = false; for (const auto& pobj : Parse_objects) { if (pobj.wingnum == oswpt.wingnum) { at_least_one = true; // if the field doesn't match the value specified, return false immediately if (iff) { if (pobj.team != value) return SEXP_FALSE; } else { if (Ship_info[pobj.ship_class].species != value) return SEXP_FALSE; } } } if (!at_least_one) return SEXP_FALSE; break; } case OSWPT_TYPE_EXITED: { // see if we can find information about the exited ship (if it is a ship) if (oswpt.has_ship_entry()) { // ship is properly exited if (oswpt.ship_entry()->exited_index >= 0) { // if the field doesn't match the value specified, return false immediately if (iff) { if (Ships_exited[oswpt.ship_entry()->exited_index].team != value) return SEXP_KNOWN_FALSE; } else { if (Ship_info[Ships_exited[oswpt.ship_entry()->exited_index].ship_class].species != value) return SEXP_KNOWN_FALSE; } } // ship is in the EXITED state but probably in the process of exploding else if (oswpt.has_shipp()) { Assertion(false, "With the addition of the ShipStatus::DEATH_ROLL state, this shouldn't happen"); return SEXP_KNOWN_FALSE; } // ship has vanished else return SEXP_NAN_FOREVER; } // it's probably an exited wing, which we don't store information about else { return SEXP_NAN_FOREVER; } break; } // we don't handle the other cases default: return SEXP_NAN; } } // got this far: we must be okay for all ships/wings return SEXP_TRUE; } // Goober5000 void sexp_ingame_ship_change_iff(ship *shipp, int new_team) { Assert(shipp != nullptr); shipp->team = new_team; } // Goober5000 void sexp_parse_ship_change_iff(p_object *parse_obj, int new_team) { Assert(parse_obj); parse_obj->team = new_team; } void sexp_change_iff_helper(object_ship_wing_point_team &oswpt, int new_team) { switch (oswpt.type) { // change ingame ship case OSWPT_TYPE_SHIP: { sexp_ingame_ship_change_iff(oswpt.shipp(), new_team); break; } // change ship yet to arrive case OSWPT_TYPE_PARSE_OBJECT: { sexp_parse_ship_change_iff(oswpt.p_objp(), new_team); break; } // change wing (we must set the flags for all ships present as well as all ships yet to arrive) case OSWPT_TYPE_WING: case OSWPT_TYPE_WING_NOT_PRESENT: { // current ships for (int i = 0; i < oswpt.wingp()->current_count; i++) sexp_ingame_ship_change_iff(&Ships[oswpt.wingp()->ship_index[i]], new_team); // ships yet to arrive for (auto p_objp: list_range(&Ship_arrival_list)) { if (p_objp->wingnum == oswpt.wingnum) sexp_parse_ship_change_iff(p_objp, new_team); } break; } case OSWPT_TYPE_WHOLE_TEAM: { for (auto so: list_range(&Ship_obj_list)) { if (Objects[so->objnum].flags[Object::Object_Flags::Should_be_dead]) continue; ship* shipp = &Ships[Objects[so->objnum].instance]; if (shipp->team == oswpt.team) sexp_ingame_ship_change_iff(shipp, new_team); } for (auto p_objp: list_range(&Ship_arrival_list)) { if (p_objp->team == oswpt.team) sexp_parse_ship_change_iff(p_objp, new_team); } break; } default: break; } } // Goober5000 - added wing capability void sexp_change_iff(int n) { int new_team; new_team = iff_lookup(CTEXT(n)); n = CDR(n); Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_int(new_team); for ( ; n != -1; n = CDR(n) ) { object_ship_wing_point_team oswpt; eval_object_ship_wing_point_team(&oswpt, n); Current_sexp_network_packet.send_string(CTEXT(n)); sexp_change_iff_helper(oswpt, new_team); } Current_sexp_network_packet.end_callback(); } void multi_sexp_change_iff() { int new_team; char name[TOKEN_LENGTH]; Current_sexp_network_packet.get_int(new_team); while (Current_sexp_network_packet.get_string(name)) { object_ship_wing_point_team oswpt; eval_object_ship_wing_point_team(&oswpt, -1, name); sexp_change_iff_helper(oswpt, new_team); } } void sexp_ingame_ship_change_iff_color(ship *shipp, int observer_team, int observed_team, int alternate_iff_color) { Assert(shipp != nullptr); shipp->ship_iff_color[{observer_team, observed_team}] = alternate_iff_color; } void sexp_parse_ship_change_iff_color(p_object *parse_obj, int observer_team, int observed_team, int alternate_iff_color) { Assert(parse_obj); parse_obj->alt_iff_color[{observer_team, observed_team}] = alternate_iff_color; } void sexp_change_iff_color_helper(object_ship_wing_point_team &oswpt, int observer_team, int observed_team, int alternate_iff_color) { int i; switch (oswpt.type) { // change ingame ship case OSWPT_TYPE_SHIP: { sexp_ingame_ship_change_iff_color(oswpt.shipp(), observer_team, observed_team, alternate_iff_color); break; } // change ship yet to arrive case OSWPT_TYPE_PARSE_OBJECT: { sexp_parse_ship_change_iff_color(oswpt.p_objp(), observer_team, observed_team, alternate_iff_color); break; } // change wing (we must set the flags for all ships present as well as all ships yet to arrive) case OSWPT_TYPE_WING: case OSWPT_TYPE_WING_NOT_PRESENT: { // current ships for (i = 0; i < oswpt.wingp()->current_count; i++) sexp_ingame_ship_change_iff_color(&Ships[oswpt.wingp()->ship_index[i]], observer_team, observed_team, alternate_iff_color); // ships yet to arrive for (auto p_objp: list_range(&Ship_arrival_list)) { if (p_objp->wingnum == oswpt.wingnum) sexp_parse_ship_change_iff_color(p_objp, observer_team, observed_team, alternate_iff_color); } break; } default: break; } } // Wanderer void sexp_change_iff_color(int n) { int observer_team, observed_team, alternate_iff_color; std::array rgb; bool is_nan, is_nan_forever; // First node if (n == -1) { Warning(LOCATION, "Detected missing observer team parameter in sexp-change_iff_color"); return; } observer_team = iff_lookup(CTEXT(n)); n = CDR(n); // Second node if (n == -1) { Warning(LOCATION, "Detected missing observed team parameter in sexp-change_iff_color"); return; } observed_team = iff_lookup(CTEXT(n)); n = CDR(n); // Three following nodes int count = eval_array(rgb, n, is_nan, is_nan_forever, [](int num)->int { if (num < 0 || num > 255) { Warning(LOCATION, "Invalid argument for iff color in sexp-change-iff-color. Valid range is 0 to 255.\n"); num = 255; } return num; }); if (count < 3) { Warning(LOCATION, "Detected incomplete color parameter list in sexp-change_iff_color\n"); return; } if (is_nan || is_nan_forever) { return; } alternate_iff_color = iff_init_color(rgb[0], rgb[1], rgb[2]); Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_int(observer_team); Current_sexp_network_packet.send_int(observed_team); Current_sexp_network_packet.send_int(alternate_iff_color); // Rest of the nodes for (; n >= 0; n = CDR(n)) { object_ship_wing_point_team oswpt; eval_object_ship_wing_point_team(&oswpt, n); Current_sexp_network_packet.send_string(CTEXT(n)); sexp_change_iff_color_helper(oswpt, observer_team, observed_team, alternate_iff_color); } Current_sexp_network_packet.end_callback(); } void multi_sexp_change_iff_color() { int observer_team, observed_team, alternate_iff_color; char name[TOKEN_LENGTH]; Current_sexp_network_packet.get_int(observer_team); Current_sexp_network_packet.get_int(observed_team); Current_sexp_network_packet.get_int(alternate_iff_color); while (Current_sexp_network_packet.get_string(name)) { object_ship_wing_point_team oswpt; eval_object_ship_wing_point_team(&oswpt, -1, name); sexp_change_iff_color_helper(oswpt, observer_team, observed_team, alternate_iff_color); } } // Goober5000 int sexp_is_ship_class_or_type(int n, bool ship_class) { Assert( n >= 0 ); // get class or type auto name = CTEXT(n); int constraint_index; if (ship_class) constraint_index = ship_info_lookup(name); else { constraint_index = ship_type_name_lookup(name); if (constraint_index < 0 && Fighter_bomber_valid && !stricmp(name, Fighter_bomber_type_name)) constraint_index = Ship_type_fighter_bomber; } n = CDR(n); // not valid; no need to check if (constraint_index < 0) return SEXP_FALSE; // eval ships while (n != -1) { // get ship auto ship_entry = eval_ship(n); if (!ship_entry) return SEXP_NAN; int ship_index; if (ship_entry->status == ShipStatus::NOT_YET_PRESENT) { ship_index = ship_entry->p_objp()->ship_class; } else if (ship_entry->exited_index >= 0) { ship_index = Ships_exited[ship_entry->exited_index].ship_class; } else if (ship_entry->has_shipp()) { ship_index = ship_entry->shipp()->ship_info_index; } else return SEXP_NAN_FOREVER; // maybe convert from class to type if (!ship_class) ship_index = ship_class_query_general_type(ship_index); // if it doesn't match, return false if (!ship_class && constraint_index == Ship_type_fighter_bomber) { if (ship_index != Ship_type_fighter && ship_index != Ship_type_bomber) return SEXP_FALSE; } else { if (ship_index != constraint_index) return SEXP_FALSE; } // increment n = CDR(n); } // we're this far; it must be true return SEXP_TRUE; } // Goober5000 // ai class value is the first parameter, second is a ship, rest are subsystems to check int sexp_is_ai_class(int n) { Assert ( n >= 0 ); auto ai_class_name = CTEXT(n); n = CDR(n); // find ai class int ai_class_to_check = -1; for (int i=0; istatus == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; if (ship_entry->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; n = CDR(n); // subsys? if (n >= 0) { // loopity-loop for ( ; n != -1; n = CDR(n) ) { auto subsystem = CTEXT(n); // find the ship subsystem // if it doesn't match, or subsys isn't found, return false immediately auto ss = ship_get_subsys(ship_entry->shipp(), subsystem); if (ss) { if (ss->weapons.ai_class != ai_class_to_check) return SEXP_FALSE; } else return SEXP_FALSE; } // we've come this far; it must all be true return SEXP_TRUE; } // just the ship else { if (Ai_info[ship_entry->shipp()->ai_index].ai_class == ai_class_to_check) return SEXP_TRUE; else return SEXP_FALSE; } } // Goober5000 void sexp_change_ai_class(int n) { Assert ( n >= 0 ); auto ai_class_name = CTEXT(n); n = CDR(n); // find ai class int new_ai_class = -1; for (int i=0; ihas_shipp()) return; n = CDR(n); Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_ship(ship_entry->shipp()); Current_sexp_network_packet.send_int(new_ai_class); // subsys? if (n >= 0) { // loopity-loop process_ship_subsystems(ship_entry, true, n, true, "change-ai-class", [&](ProcessSubsystemType type, ship_subsys *ss) { if (type == ProcessSubsystemType::SUBSYSTEM) { ship_subsystem_set_new_ai_class(ss, new_ai_class); Current_sexp_network_packet.send_string(ss->system_info->subobj_name); } else Warning(LOCATION, "change-ai-class does not support %s!", special_subsystem_string(type)); }); } // just the one ship else { ship_set_new_ai_class(ship_entry->shipp(), new_ai_class); } Current_sexp_network_packet.end_callback(); } void multi_sexp_change_ai_class() { int new_ai_class; ship *shipp; ship_subsys *ss; char subsystem[TOKEN_LENGTH]; Current_sexp_network_packet.get_ship(shipp); Current_sexp_network_packet.get_int(new_ai_class); // subsystem? if (Current_sexp_network_packet.get_string(subsystem)) { ss = ship_get_subsys(shipp, subsystem); ship_subsystem_set_new_ai_class(ss, new_ai_class); // deal with any other subsystems while (Current_sexp_network_packet.get_string(subsystem)) { ss = ship_get_subsys(shipp, subsystem); ship_subsystem_set_new_ai_class(ss, new_ai_class); } } else { ship_set_new_ai_class(shipp, new_ai_class); } } // 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. /** * Adds a goal to the specified entry (ships and wings have unique names between the two sets). */ void sexp_add_goal(int n) { Assert ( n >= 0 ); int goal_node = CDR(n); auto ship_entry = eval_ship(n); if (ship_entry) { if (!ship_entry->has_shipp()) return; // ship not around anymore???? then forget it! ai_add_ship_goal_sexp(goal_node, ai_goal_type::EVENT_SHIP, &(Ai_info[ship_entry->shipp()->ai_index])); return; } auto wingp = eval_wing(n); if (wingp) { if (wingp->flags[Ship::Wing_Flags::Gone]) return; // wing not around anymore???? then forget it! ai_add_wing_goal_sexp(goal_node, ai_goal_type::EVENT_WING, wingp); } } // Goober5000 void sexp_remove_goal(int n) { Assert( n >= 0 ); int goal_node = CDR(n); auto ship_entry = eval_ship(n); if (ship_entry) { if (!ship_entry->has_shipp()) return; // ship not around anymore???? then forget it! bool remove_more = false; auto aip = &Ai_info[ship_entry->shipp()->ai_index]; do { int goalindex = ai_remove_goal_sexp_sub(goal_node, aip->goals, remove_more); if (goalindex >= 0) { if (aip->active_goal == goalindex) aip->active_goal = AI_ACTIVE_GOAL_NONE; } } while (remove_more); return; } auto wingp = eval_wing(n); if (wingp) { if (wingp->flags[Ship::Wing_Flags::Gone]) return; // wing not around anymore???? then forget it! ai_remove_wing_goal_sexp(goal_node, wingp); } } /** * Clear all AI goals for the given ship or wing */ void sexp_clear_goals(int n) { for (; n >= 0; n = CDR(n)) { auto ship_entry = eval_ship(n); if (ship_entry) { if (!ship_entry->has_shipp()) continue; // ship not around anymore???? then forget it! ai_clear_ship_goals(&(Ai_info[ship_entry->shipp()->ai_index])); continue; } auto wingp = eval_wing(n); if (wingp) { if (wingp->flags[Ship::Wing_Flags::Gone]) continue; // wing not around anymore???? then forget it! ai_clear_wing_goals(wingp); } } } // Goober5000 void sexp_hud_disable(int n) { bool is_nan, is_nan_forever; int disable_hud = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; hud_set_draw(!disable_hud); Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_int(disable_hud); Current_sexp_network_packet.end_callback(); } void multi_sexp_hud_disable() { int disable_hud; if (Current_sexp_network_packet.get_int(disable_hud)) { hud_set_draw(!disable_hud); } } // Goober5000 void sexp_hud_disable_except_messages(int n) { bool is_nan, is_nan_forever; int disable_hud = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; hud_disable_except_messages(disable_hud); Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_int(disable_hud); Current_sexp_network_packet.end_callback(); } void multi_sexp_hud_disable_except_messages() { int disable_hud; if (Current_sexp_network_packet.get_int(disable_hud)) { hud_disable_except_messages(disable_hud); } } void sexp_hud_set_text_num(int n) { bool is_nan, is_nan_forever; auto gaugename = CTEXT(n); char tmp[16] = ""; HudGauge* cg = hud_get_custom_gauge(gaugename); if (cg) { int num = eval_num(CDR(n), is_nan, is_nan_forever); if (is_nan || is_nan_forever) { strcpy_s(tmp, "NaN"); } else { sprintf(tmp, "%d", num); } cg->updateCustomGaugeText(tmp); } else { WarningEx(LOCATION, "Could not find a custom hud gauge named %s\n", gaugename); } } void sexp_hud_set_text(int n) { auto gaugename = CTEXT(n); auto text = CTEXT(CDR(n)); HudGauge* cg = hud_get_custom_gauge(gaugename); if (cg) { cg->updateCustomGaugeText(text); } else { WarningEx(LOCATION, "Could not find a custom hud gauge named %s\n", gaugename); } } void sexp_hud_set_xstr(int n) { bool is_nan, is_nan_forever; auto gaugename = CTEXT(n); auto text = CTEXT(CDR(n)); auto id = eval_num(CDDR(n), is_nan, is_nan_forever); if (is_nan || is_nan_forever) { id = -1; } // Translate the string using tstrings SCP_string xstr; sprintf(xstr, "XSTR(\"%s\", %d)", text, id); SCP_string translated_string; lcl_ext_localize(xstr, translated_string); // Now replace tokens and variables sexp_replace_variable_names_with_values(translated_string); sexp_container_replace_refs_with_values(translated_string); message_translate_tokens(translated_string); HudGauge* cg = hud_get_custom_gauge(gaugename); if (cg) { cg->updateCustomGaugeText(translated_string.c_str()); } else { WarningEx(LOCATION, "Could not find a custom hud gauge named %s\n", gaugename); } } void sexp_hud_set_message(int n) { auto gaugename = CTEXT(n); auto text = CTEXT(CDR(n)); SCP_string message; for (int i = 0; i < Num_messages; i++) { if ( !stricmp(text, Messages[i].name) ) { message = Messages[i].message; sexp_replace_variable_names_with_values(message); sexp_container_replace_refs_with_values(message); message_translate_tokens(message); HudGauge* cg = hud_get_custom_gauge(gaugename); if (cg) { cg->updateCustomGaugeText(message); } else { WarningEx(LOCATION, "Could not find a hud gauge named %s\n", gaugename); } return; } } WarningEx(LOCATION, "sexp_hud_set_message couldn't find a message by the name of %s in the mission\n", text); } void sexp_hud_force_static(int n) { Sensor_static_forced = is_sexp_true(n); } void sexp_hud_force_emp(int n) { bool is_nan, is_nan_forever; int intensity = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; n = CDR(n); int duration = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; n = CDR(n); SCP_string text = ""; if (n >= 0) { // assume just text text = CTEXT(n); // but use an actual message if one exists for (int i = 0; i < Num_messages; i++) { if (!stricmp(Messages[i].name, text.c_str())) { text = Messages[i].message; break; } } text = message_translate_tokens(text.c_str()); } emp_start_local(i2fl(intensity), i2fl(duration) / 1000, text); } void sexp_hud_set_directive(int n) { auto gaugename = CTEXT(n); auto text = CTEXT(CDR(n)); SCP_string message = message_translate_tokens(text); if (message.size() >= MESSAGE_LENGTH) { WarningEx(LOCATION, "Message %s is too long for use in a HUD gauge. Please shorten it to %d characters or less.", message.c_str(), MESSAGE_LENGTH - 1); return; } HudGauge* cg = hud_get_custom_gauge(gaugename); if (cg) { cg->updateCustomGaugeText(message); } else { WarningEx(LOCATION, "Could not find a hud gauge named %s\n", gaugename); } } void sexp_hud_clear_messages() { if(!Ship_info[Player_ship->ship_info_index].hud_gauges.empty()) { size_t num_gauges = Ship_info[Player_ship->ship_info_index].hud_gauges.size(); for(size_t i = 0; i < num_gauges; i++) { if (Ship_info[Player_ship->ship_info_index].hud_gauges[i]->getObjectType() == HUD_OBJECT_MESSAGES) { auto* gauge = static_cast(Ship_info[Player_ship->ship_info_index].hud_gauges[i].get()); gauge->clearMessages(); } } } else { size_t num_gauges = default_hud_gauges.size(); for(size_t i = 0; i < num_gauges; i++) { if (default_hud_gauges[i]->getObjectType() == HUD_OBJECT_MESSAGES) { auto* gauge = static_cast(default_hud_gauges[i].get()); gauge->clearMessages(); } } } } void sexp_hud_set_coords(int n) { int coord_x, coord_y; bool is_nan, is_nan_forever; auto gaugename = CTEXT(n); n = CDR(n); eval_nums(n, is_nan, is_nan_forever, coord_x, coord_y); if (is_nan || is_nan_forever) return; HudGauge* hg = hud_get_gauge(gaugename); if (hg) { // we might need to adjust the coordinates if (HUD_set_coords_screen_base_res[0] > 0 && HUD_set_coords_screen_base_res[1] > 0) { coord_x = fl2i(i2fl(coord_x) * gr_screen.center_w / HUD_set_coords_screen_base_res[0]); coord_y = fl2i(i2fl(coord_y) * gr_screen.center_h / HUD_set_coords_screen_base_res[1]); } hg->setGaugeCoords(coord_x, coord_y); } else { WarningEx(LOCATION, "Could not find a hud gauge named %s\n", gaugename); } } void sexp_hud_set_frame(int n) { bool is_nan, is_nan_forever; auto gaugename = CTEXT(n); int frame_num = eval_num(CDR(n), is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; HudGauge* hg = hud_get_gauge(gaugename); if (hg) { hg->setGaugeFrame(frame_num); } else { WarningEx(LOCATION, "Could not find a hud gauge named %s\n", gaugename); } } void sexp_hud_set_color(int n) { auto gaugename = CTEXT(n); n = CDR(n); std::array rgb; bool is_nan, is_nan_forever; eval_array(rgb, n, is_nan, is_nan_forever, [](int num)->int { CLAMP(num, 0, 255); return num; }); if (is_nan || is_nan_forever) return; HudGauge* hg = hud_get_gauge(gaugename); if (hg) { hg->sexpLockConfigColor(false); hg->updateColor(rgb[0], rgb[1], rgb[2], (HUD_color_alpha + 1) * 16); hg->sexpLockConfigColor(true); } else { WarningEx(LOCATION, "Could not find a hud gauge named %s\n", gaugename); } } void sexp_hud_reset_color(int n) { for (; n >= 0; n = CDR(n)) { auto gaugename = CTEXT(n); HudGauge* hg = hud_get_gauge(gaugename); if (hg) { // only a subset of default HUD gauges have a configured color int type = hg->getConfigType(); auto ii = std::find_if(std::begin(Legacy_HUD_gauges), std::end(Legacy_HUD_gauges), [type](const Legacy_HUD_gauge_pair& pair) { return pair.hud_gauge_type == type; }); if (ii == std::end(Legacy_HUD_gauges)) { WarningEx(LOCATION, "HUD gauge %s does not have a built-in configured color!", gaugename); } else { // use the color as specified in the HUD config auto id = hg->getConfigId(); auto& c = HUD_config.gauge_colors[id]; hg->sexpLockConfigColor(false); hg->updateColor(c.red, c.green, c.blue, (HUD_color_alpha + 1) * 16); hg->sexpLockConfigColor(true); } } else { WarningEx(LOCATION, "Could not find a hud gauge named %s\n", gaugename); } } } // Goober5000 void sexp_hud_set_max_targeting_range(int n) { int val; bool is_nan, is_nan_forever; val = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; if (val < 0) { val = 0; } Hud_max_targeting_range = val; Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_int(Hud_max_targeting_range); Current_sexp_network_packet.end_callback(); } void multi_sexp_hud_set_max_targeting_range() { Current_sexp_network_packet.get_int(Hud_max_targeting_range); } /* Make sure that the Sexp_hud_display_* get added to the game_state transitions in freespace.cpp (game_enter_state()). */ int Sexp_hud_display_warpout = 0; void sexp_hud_display_gauge(int n) { bool is_nan, is_nan_forever; int show_for = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; auto gauge = CTEXT(CDR(n)); if ( stricmp(SEXP_HUD_GAUGE_WARPOUT, gauge) == 0 ) { Sexp_hud_display_warpout = (show_for > 1)? timestamp(show_for) : (show_for); } } void sexp_hud_gauge_set_active(int n) { HudGauge* hg; auto gaugename = CTEXT(n); bool active = is_sexp_true(CDR(n)); hg = hud_get_gauge(gaugename); if (hg) { hg->updateActive(active); } else { WarningEx(LOCATION, "Could not find a hud gauge named %s\n", gaugename); } } void sexp_hud_set_custom_gauge_active(int node) { HudGauge* hg; bool activate = is_sexp_true(node); node = CDR(node); for(; node >= 0; node = CDR(node)) { auto gaugename = CTEXT(node); hg = hud_get_custom_gauge(gaugename); if (hg) { hg->updateActive(activate); } else { WarningEx(LOCATION, "Could not find a hud gauge named %s\n", gaugename); } } } int hud_gauge_type_lookup(const char* name) { for(int i = 0; i < Num_hud_gauge_types; i++) { if(!stricmp(name, Hud_gauge_types[i].name)) return Hud_gauge_types[i].def; } return -1; } void sexp_hud_activate_gauge_type(int n) { int config_type = hud_gauge_type_lookup(CTEXT(n)); bool active = is_sexp_true(CDR(n)); if (config_type != -1) { if(!Ship_info[Player_ship->ship_info_index].hud_gauges.empty()) { size_t num_gauges = Ship_info[Player_ship->ship_info_index].hud_gauges.size(); for(size_t i = 0; i < num_gauges; i++) { if (Ship_info[Player_ship->ship_info_index].hud_gauges[i]->getObjectType() == config_type) Ship_info[Player_ship->ship_info_index].hud_gauges[i]->updateSexpOverride(!active); } } else { size_t num_gauges = default_hud_gauges.size(); for(size_t i = 0; i < num_gauges; i++) { if (default_hud_gauges[i]->getObjectType() == config_type) default_hud_gauges[i]->updateSexpOverride(!active); } } } } void sexp_hud_set_builtin_gauge_active(int node) { bool activate = is_sexp_true(node); node = CDR(node); for(; node >= 0; node = CDR(node)) { int config_type = hud_gauge_type_lookup(CTEXT(node)); if (config_type != -1) { if(!Ship_info[Player_ship->ship_info_index].hud_gauges.empty()) { size_t num_gauges = Ship_info[Player_ship->ship_info_index].hud_gauges.size(); for(size_t i = 0; i < num_gauges; i++) { if (Ship_info[Player_ship->ship_info_index].hud_gauges[i]->getObjectType() == config_type) Ship_info[Player_ship->ship_info_index].hud_gauges[i]->updateSexpOverride(!activate); } } else { size_t num_gauges = default_hud_gauges.size(); for(size_t i = 0; i < num_gauges; i++) { if (default_hud_gauges[i]->getObjectType() == config_type) default_hud_gauges[i]->updateSexpOverride(!activate); } } } } } void multi_sexp_hud_display_gauge() { int show_for; if (Current_sexp_network_packet.get_int(show_for)) { Sexp_hud_display_warpout = (show_for > 1)? timestamp(show_for) : (show_for); } } void sexp_set_squadron_wings(int node) { std::array new_squad_wingnums; // get the new set of squadron wings for (int i = 0, n = node; i < MAX_SQUADRON_WINGS; i++) { int wingnum = -1; if (n >= 0) { auto wingp = eval_wing(n); if (wingp) wingnum = WING_INDEX(wingp); n = CDR(n); } new_squad_wingnums[i] = wingnum; } hud_set_new_squadron_wings(new_squad_wingnums); } // Goober5000 /** * Trigger whether player uses the game AI for stuff */ void sexp_player_use_ai(bool use_ai) { // if turning off player AI, clean up the AI to prevent any stale state from causing trouble later if (Player_use_ai && !use_ai) { ai_clear_ship_goals(Player_ai); Player_ai->mode = AIM_NONE; set_target_objnum(Player_ai, -1); } Player_use_ai = use_ai; if (!use_ai) { Player_ai->ai_override_flags.reset(); Player_obj->phys_info.flags &= ~PF_MANEUVER_NO_DAMP; } } void sexp_set_friendly_damage_caps(int n) { bool is_nan; bool is_nan_forever; ai_profile_t& aip = *The_mission.ai_profile; float beam_friendly_cap = i2fl(eval_num(n, is_nan, is_nan_forever)); if (!is_nan && !is_nan_forever) { aip.beam_friendly_damage_cap[Game_skill_level] = beam_friendly_cap; } n = CDR(n); if (n < 0) { return; } float weapon_friendly_cap = i2fl(eval_num(n, is_nan, is_nan_forever)); if (!is_nan && !is_nan_forever) { aip.weapon_friendly_damage_cap[Game_skill_level] = weapon_friendly_cap; } n = CDR(n); if (n < 0) { return; } float weapon_self_cap = i2fl(eval_num(n, is_nan, is_nan_forever)); if (!is_nan && !is_nan_forever) { aip.weapon_self_damage_cap[Game_skill_level] = weapon_self_cap; } } // Luytenky /* * Sets the player's target to the specified, either ship, and or the subsystem on said ship. */ void sexp_set_player_target(int node) { const ship_registry_entry *ship_entry = eval_ship(node); if (ship_entry == nullptr) return; int shipnum = ship_entry->shipnum; if (shipnum < 0) return; ship* shipp = &Ships[shipnum]; int objnum = shipp->objnum; int n = CDR(node); ship_subsys * new_subsys = nullptr; if (n >= 0) { const char* subsys_name = CTEXT(n); if (stricmp(subsys_name, SEXP_NONE_STRING) != 0) { new_subsys = ship_get_subsys(shipp, subsys_name); } } set_target_objnum(Player_ai, objnum); set_targeted_subsys(Player_ai, new_subsys, new_subsys ? objnum : -1); shipp->last_targeted_subobject[Player_num] = new_subsys; } // Luytenky /* * Clears the player's targeting. */ void sexp_clear_player_target() { set_target_objnum(Player_ai, -1); set_targeted_subsys(Player_ai, nullptr, -1); } // Karajorma void sexp_allow_treason (int n) { if (n != -1) { The_mission.flags.set(Mission::Mission_Flags::No_traitor, is_sexp_true(n)); } } void sexp_set_player_orders(int n) { bool allow_order; SCP_set orders; auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) { return; } auto shipp = ship_entry->shipp(); // we need to know which orders this ship class can accept. const SCP_set &default_orders = ship_get_default_orders_accepted(&Ship_info[shipp->ship_info_index]); n = CDR(n); allow_order = is_sexp_true(n); n = CDR(n); do { for( size_t order : default_orders){ // Once we exceed the number of valid orders, break and warn if (order >= Player_orders.size()) { Warning(LOCATION, "Invalid order name %s found in sexp!", CTEXT(n)); break; } // OPF_AI_ORDER returns hud_name, so we must compare to that instead of parse_name if (!stricmp(CTEXT(n), Player_orders[order].hud_name.c_str())) { orders.insert(order); break; } } n = CDR(n); }while (n >= 0); // set or unset the orders if (allow_order) { shipp->orders_accepted.insert(orders.begin(), orders.end()); } else { SCP_set diff; std::set_difference(shipp->orders_accepted.begin(), shipp->orders_accepted.end(), orders.begin(), orders.end(), std::inserter(diff, diff.end())); shipp->orders_accepted = std::move(diff); } } void sexp_set_order_allowed_for_target(int n) { auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) { return; } auto shipp = ship_entry->shipp(); const auto default_orders = ship_get_default_orders_against(); n = CDR(n); bool allow_order = is_sexp_true(n); n = CDR(n); SCP_set orders; do { for( size_t order : default_orders){ //Once we exceed the number of valid orders, break and warn if (order >= Player_orders.size()) { Warning(LOCATION, "Invalid order name %s found in sexp!", CTEXT(n)); break; } // OPF_AI_ORDER returns hud_name, so we must compare to that instead of parse_name if (!stricmp(CTEXT(n), Player_orders[order].hud_name.c_str())) { orders.insert(order); break; } } n = CDR(n); }while (n >= 0); // set or unset the orders if (allow_order) { shipp->orders_allowed_against.insert(orders.begin(), orders.end()); } else { SCP_set diff; std::set_difference(shipp->orders_allowed_against.begin(), shipp->orders_allowed_against.end(), orders.begin(), orders.end(), std::inserter(diff, diff.end())); shipp->orders_allowed_against = std::move(diff); } } void sexp_enable_or_validate_general_orders(int n, bool enable) { bool choice = is_sexp_true(n); n = CDR(n); do { int order_num = ai_lua_find_general_order_id(CTEXT(n)); // if we got an invalid order number then skip if (order_num < 0) { Warning(LOCATION, "Invalid order name %s found in sexp!", CTEXT(n)); continue; } if (enable) { ai_lua_enable_general_order(order_num, choice); } else { ai_lua_validate_general_order(order_num, choice); } n = CDR(n); } while (n >= 0); } // Goober5000 void sexp_change_soundtrack(int n) { event_sexp_change_soundtrack(CTEXT(n)); if (MULTIPLAYER_MASTER) { Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_string(CTEXT(n)); Current_sexp_network_packet.end_callback(); } } void multi_sexp_change_soundtrack() { char new_track[NAME_LENGTH]; if (Current_sexp_network_packet.get_string(new_track)) { event_sexp_change_soundtrack(new_track); } } // Goober5000 int sexp_find_music_handle_index(int sexp_var) { // check the default case if (sexp_var < 0) { // we might not have any music at this point if (Sexp_music_handles.empty()) return -1; return 0; } // get the handle from the variable int music_handle = -1; try { music_handle = std::stoi(Sexp_variables[sexp_var].text); } catch (const std::exception&) { // not a number return -1; } // find it in our collection of handles (skip index 0) for (size_t i = 1; i < Sexp_music_handles.size(); ++i) { if (Sexp_music_handles[i] == music_handle) return (int)i; } return -1; } // Goober5000 void sexp_pause_unpause_music(bool pause, int sexp_var) { if (Cmdline_freespace_no_music) return; int index = sexp_find_music_handle_index(sexp_var); if (index < 0) { // bad variable, maybe if (sexp_var >= 0) Warning(LOCATION, "In sexp_pause_unpause_music, variable %s did not store a valid music handle!", Sexp_variables[sexp_var].variable_name); return; } int music_handle = Sexp_music_handles[index]; // actually pause the music if (music_handle >= 0) { if (pause && !audiostream_is_paused(music_handle)) audiostream_pause(music_handle, true); else if (!pause && audiostream_is_paused(music_handle)) audiostream_unpause(music_handle, true); } } // Goober5000 void sexp_stop_music(bool fade, int sexp_var) { if (Cmdline_freespace_no_music) return; int index = sexp_find_music_handle_index(sexp_var); if (index < 0) { // bad variable, maybe if (sexp_var >= 0) Warning(LOCATION, "In sexp_stop_music, variable %s did not store a valid music handle!", Sexp_variables[sexp_var].variable_name); return; } int music_handle = Sexp_music_handles[index]; // actually stop the music and free the handle if (music_handle >= 0) { audiostream_close_file(music_handle, fade); Sexp_music_handles[index] = -1; } // also clear the variable if (sexp_var >= 0) sexp_modify_variable("-1", sexp_var); } // Goober5000 void sexp_music_close() { if (Cmdline_freespace_no_music) return; // close all music used by this mission for (auto music_handle : Sexp_music_handles) audiostream_close_file(music_handle); Sexp_music_handles.clear(); } // Goober5000 void sexp_load_music(const char *filename, int type = -1, int sexp_var = -1) { if (Cmdline_freespace_no_music || filename == nullptr) return; if (type < 0) type = ASF_MENUMUSIC; // the default music handle is always index 0, which at this point may or may not exist if (Sexp_music_handles.empty()) Sexp_music_handles.push_back(-1); // if a variable is supplied, we'll be creating a new handle to be stored in the variable int index; if (sexp_var >= 0) { // if any handle has expired, it can be closed; and any closed or expired index can be reused // (note that handles are not unique across an entire mission; any variables pointing to handles // that have been closed will see those handles reused for new sounds) int found_i = -1, n = sz2i(Sexp_music_handles.size()); for (int i = 1; i < n; ++i) // skip the default handle { int this_handle = Sexp_music_handles[i]; if ((this_handle < 0) || (!audiostream_is_playing(this_handle) && !audiostream_is_paused(this_handle))) { audiostream_close_file(this_handle, false); found_i = i; break; } } // either reuse an index or choose a new index if (found_i >= 0) index = found_i; else { index = static_cast(Sexp_music_handles.size()); Sexp_music_handles.push_back(-1); } } // otherwise we'll be reusing the default handle, so close anything that's already playing else { index = 0; audiostream_close_file(Sexp_music_handles[index]); } // open the stream and save the handle in our list Sexp_music_handles[index] = audiostream_open(filename, type); if (Sexp_music_handles[index] < 0) Warning(LOCATION, "In sexp_load_music, could not create audio handle for '%s'! You might be trying to play too many sounds at once.", filename); // if we have a variable, save it there too if (sexp_var >= 0) { char number_as_str[TOKEN_LENGTH]; sprintf(number_as_str, "%d", Sexp_music_handles[index]); sexp_modify_variable(number_as_str, sexp_var); } } // Goober5000 void sexp_start_music(int loop, int sexp_var) { int index = sexp_find_music_handle_index(sexp_var); // shouldn't happen if (index < 0) { // really shouldn't happen if (sexp_var < 0) Warning(LOCATION, "In sexp_start_music, unable to find a default music index?!?!"); else Warning(LOCATION, "In sexp_start_music, no music handle index available for variable %s!", Sexp_variables[sexp_var].variable_name); return; } int music_handle = Sexp_music_handles[index]; // also shouldn't happen if (music_handle < 0) { Warning(LOCATION, "In sexp_start_music, music handle was unavailable!"); return; } // start playing if (!audiostream_is_playing(music_handle)) audiostream_play(music_handle, (Master_event_music_volume * aav_music_volume), loop); } gamesnd_id sexp_get_sound_index(int node) { Assert(node >= 0); bool is_nan, is_nan_forever; gamesnd_id sound_index; // this node is another SEXP operator or a plain number if (CAR(node) != -1 || Sexp_nodes[node].subtype == SEXP_ATOM_NUMBER) { int index = eval_num(node, is_nan, is_nan_forever); if (is_nan || is_nan_forever) index = -1; sound_index = gamesnd_get_by_tbl_index(index); } // it's gotta be a name else { const char *sound_name = CTEXT(node); // if it's not , try looking it up if (stricmp(sound_name, SEXP_NONE_STRING) != 0) { sound_index = gamesnd_get_by_name(sound_name); if (!sound_index.isValid()) Warning(LOCATION, "unrecognized sound name \"%s\"!", sound_name); } } return sound_index; } // Goober5000 void sexp_play_sound_from_table(int n) { bool is_nan, is_nan_forever; vec3d origin; Assert( n >= 0 ); // read in data -------------------------------- eval_vec3d(&origin, n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; auto sound_index = sexp_get_sound_index(n); // play sound effect --------------------------- if (sound_index.isValid()) { game_snd *snd = gamesnd_get_game_sound(sound_index); if (snd->min == 0 && snd->max == 0) { // if sound doesn't specify 3d range, don't play in 3d snd_play( snd, 0.0f, 1.0f, SND_PRIORITY_MUST_PLAY ); } else { snd_play_3d( snd, &origin, &View_position, 0.0f, nullptr, 0, 1.0f, SND_PRIORITY_MUST_PLAY ); } } if (MULTIPLAYER_MASTER) { Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_float(origin.xyz.x); Current_sexp_network_packet.send_float(origin.xyz.y); Current_sexp_network_packet.send_float(origin.xyz.z); Current_sexp_network_packet.send_int(sound_index.value()); Current_sexp_network_packet.end_callback(); } } void multi_sexp_play_sound_from_table() { vec3d origin; int sound_index = -1; Current_sexp_network_packet.get_float(origin.xyz.x); Current_sexp_network_packet.get_float(origin.xyz.y); Current_sexp_network_packet.get_float(origin.xyz.z); Current_sexp_network_packet.get_int(sound_index); auto sound = gamesnd_id(sound_index); // play sound effect --------------------------- if (sound.isValid()) { game_snd *snd = gamesnd_get_game_sound(sound); if (snd->min == 0 && snd->max == 0) { // if sound doesn't specify 3d range, don't play in 3d snd_play( snd, 0.0f, 1.0f, SND_PRIORITY_MUST_PLAY ); } else { snd_play_3d( snd, &origin, &View_position, 0.0f, nullptr, 0, 1.0f, SND_PRIORITY_MUST_PLAY ); } } } // Goober5000 void sexp_close_sound_from_file(int n) { // fade out? bool fade; if (n < 0) fade = true; else { fade = is_sexp_true(n); n = CDR(n); } // we might have a variable int sexp_var = -1; if (n >= 0) { sexp_var = sexp_get_variable_index(n); if (sexp_var < 0) { Warning(LOCATION, "close-sound-from-file: Variable %s does not exist!", Sexp_nodes[n].text); return; } if (!(Sexp_variables[sexp_var].type & SEXP_VARIABLE_NUMBER)) { Warning(LOCATION, "close-sound-from-file: Variable %s must be a number variable!", Sexp_variables[sexp_var].variable_name); return; } } // stop sound file sexp_stop_music(fade, sexp_var); if (MULTIPLAYER_MASTER) { Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_bool(fade); Current_sexp_network_packet.send_int(sexp_var); Current_sexp_network_packet.end_callback(); } } void multi_sexp_close_sound_from_file() { bool fade = true; int sexp_var = -1; Current_sexp_network_packet.get_bool(fade); Current_sexp_network_packet.get_int(sexp_var); sexp_stop_music(fade, sexp_var); } // Goober5000 void sexp_play_sound_from_file(int n) { // get the music track const char *filename = CTEXT(n); n = CDR(n); // we might loop it bool loop = false; if (n >= 0) { loop = (eval_sexp(n) != 0); n = CDR(n); } // we might use environmental effects int type = ASF_MENUMUSIC; if (n >= 0) { if (eval_sexp(n) != 0) type = ASF_SOUNDFX; n = CDR(n); } // we might have a variable int sexp_var = -1; if (n >= 0) { sexp_var = sexp_get_variable_index(n); if (sexp_var < 0) { Warning(LOCATION, "play-sound-from-file: Variable %s does not exist!", Sexp_nodes[n].text); return; } if (!(Sexp_variables[sexp_var].type & SEXP_VARIABLE_NUMBER)) { Warning(LOCATION, "play-sound-from-file: Variable %s must be a number variable!", Sexp_variables[sexp_var].variable_name); return; } } // load sound file sexp_load_music(filename, type, sexp_var); // play sound file sexp_start_music(loop, sexp_var); if (MULTIPLAYER_MASTER) { Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_string(filename); Current_sexp_network_packet.send_bool(loop); Current_sexp_network_packet.send_int(type); Current_sexp_network_packet.send_int(sexp_var); Current_sexp_network_packet.end_callback(); } } void multi_sexp_play_sound_from_file() { char filename[NAME_LENGTH]; bool loop = false; int type = -1; int sexp_var = -1; if (!Current_sexp_network_packet.get_string(filename)) return; Current_sexp_network_packet.get_bool(loop); Current_sexp_network_packet.get_int(type); Current_sexp_network_packet.get_int(sexp_var); sexp_load_music(filename, type, sexp_var); sexp_start_music(loop, sexp_var); } // Goober5000 void sexp_pause_sound_from_file(int n) { bool pause = is_sexp_true(n); n = CDR(n); // we might have a variable int sexp_var = -1; if (n >= 0) { sexp_var = sexp_get_variable_index(n); if (sexp_var < 0) { Warning(LOCATION, "pause-sound-from-file: Variable %s does not exist!", Sexp_nodes[n].text); return; } if (!(Sexp_variables[sexp_var].type & SEXP_VARIABLE_NUMBER)) { Warning(LOCATION, "pause-sound-from-file: Variable %s must be a number variable!", Sexp_variables[sexp_var].variable_name); return; } } if (MULTIPLAYER_MASTER) { Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_bool(pause); Current_sexp_network_packet.send_int(sexp_var); Current_sexp_network_packet.end_callback(); } sexp_pause_unpause_music(pause, sexp_var); } void multi_sexp_pause_sound_from_file() { bool pause; int sexp_var = -1; if (!Current_sexp_network_packet.get_bool(pause)) return; Current_sexp_network_packet.get_int(sexp_var); sexp_pause_unpause_music(pause, sexp_var); } int sexp_sound_environment_option_lookup(const char *text) { int i; Assert(text != nullptr); if (text == nullptr) { return -1; } for (i = 0; i < Num_sound_environment_options; i++) { if (!strcmp(text, Sound_environment_option[i])) { return i; } } return -1; } // Taylor void sexp_set_sound_environment(int node) { int n = node; sound_env env; int preset_id = -1; bool is_nan, is_nan_forever; auto preset = CTEXT(n); n = CDR(n); if ( preset && !stricmp(preset, SEXP_NONE_STRING) ) { sound_env_disable(); return; } preset_id = ds_eax_get_preset_id( preset ); if (preset_id < 0) { return; } // fill in defaults for this preset, in case we don't set everything if ( sound_env_get(&env, preset_id) ) { return; } while (n >= 0) { int option = sexp_sound_environment_option_lookup(CTEXT(n)); n = CDR(n); // watch out for bogus options if (n < 0) { break; } float val = (float)eval_num(n, is_nan, is_nan_forever) / 1000.0f; n = CDR(n); if (is_nan || is_nan_forever) continue; if ( option == SEO_VOLUME ) { env.volume = val; } else if ( option == SEO_DECAY_TIME ) { env.decay = val; } else if ( option == SEO_DAMPING ) { env.damping = val; } } sound_env_set(&env); } // Taylor void sexp_update_sound_environment(int node) { int n = node; bool is_nan, is_nan_forever; while (n >= 0) { int option = sexp_sound_environment_option_lookup(CTEXT(n)); n = CDR(n); // watch out for bogus options if (n < 0) { break; } float val = (float)eval_num(n, is_nan, is_nan_forever) / 1000.0f; n = CDR(n); if (is_nan || is_nan_forever) continue; if ( option == SEO_VOLUME ) { ds_eax_set_volume(val); } else if ( option == SEO_DECAY_TIME ) { ds_eax_set_decay_time(val); } else if ( option == SEO_DAMPING ) { ds_eax_set_damping(val); } } } //The E //From sexp help: //{ OP_ADJUST_AUDIO_VOLUME, "adjust-audio-volume\r\n" // "Adjusts the relative volume of one sound type. Takes 2 or 3 arguments....\r\n" // "\t1:\tSound Type to adjust, either Music, Voice or Effects\r\n" // "\t2:\tPercentage of the users' settings to adjust to, 0 will be silence, 100 means the maximum volume as set by the user\r\n" // "\t3:\tFade time (optional), time in milliseconds to adjust the volume"}, int audio_volume_option_lookup(const char *text) { int i; Assert(text != nullptr); if (text == nullptr) { return -1; } for (i = 0; i < Num_adjust_audio_options; i++) { if (!strcmp(text, Adjust_audio_options[i])) { return i; } } return -1; } void sexp_adjust_audio_volume(int node) { int n = node; bool is_nan, is_nan_forever; if (n > 0) { int option = audio_volume_option_lookup(CTEXT(n)); if (option >= 0) { n = CDR(n); float target_volume = 1.0f; if (n >= 0) { target_volume = (float)eval_num(n, is_nan, is_nan_forever) / 100.0f; CLAMP(target_volume, 0.0f, 1.0f); n = CDR(n); if (is_nan || is_nan_forever) return; } int time = 0; if (n >= 0) { time = eval_num(n, is_nan, is_nan_forever); n = CDR(n); if (is_nan || is_nan_forever) return; } snd_adjust_audio_volume(option, target_volume, time); } } } int sexp_explosion_option_lookup(const char *text) { int i; Assert(text != nullptr); if (text == nullptr) { return -1; } for (i = 0; i < Num_explosion_options; i++) { if (!strcmp(text, Explosion_option[i])) { return i; } } return -1; } // Goober5000 void sexp_set_explosion_option(int node) { int n = node; ship_info *sip; shockwave_create_info *sci; bool is_nan, is_nan_forever; // get ship auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) return; n = CDR(n); auto shipp = ship_entry->shipp(); sip = &Ship_info[shipp->ship_info_index]; sci = &sip->shockwave; // if we haven't changed anything yet, create a new special-exp with the same values as a standard exp if (!shipp->use_special_explosion) { shipp->special_exp_damage = fl2i(sci->damage); shipp->special_exp_blast = fl2i(sci->blast); shipp->special_exp_inner = fl2i(sci->inner_rad); shipp->special_exp_outer = fl2i(sci->outer_rad); shipp->special_exp_shockwave_speed = fl2i(sci->speed); shipp->special_exp_deathroll_time = 0; shipp->use_special_explosion = true; shipp->use_shockwave = (sci->speed > 0); } // process all options while (n >= 0) { int option = sexp_explosion_option_lookup(CTEXT(n)); n = CDR(n); // watch out for bogus options if (n < 0) break; int val = eval_num(n, is_nan, is_nan_forever); Assert(val >= 0); // should be true due to OPF_POSITIVE n = CDR(n); if (is_nan || is_nan_forever) { continue; } else if (option == EO_DAMAGE) { shipp->special_exp_damage = val; } else if (option == EO_BLAST) { shipp->special_exp_blast = val; } else if (option == EO_INNER_RADIUS) { shipp->special_exp_inner = val; } else if (option == EO_OUTER_RADIUS) { shipp->special_exp_outer = val; } else if (option == EO_SHOCKWAVE_SPEED) { shipp->special_exp_shockwave_speed = val; shipp->use_shockwave = (val > 0); } else if (option == EO_DEATH_ROLL_TIME) { shipp->special_exp_deathroll_time = val; // hmm, it would be cool to modify the explosion in progress if (shipp->flags[Ship::Ship_Flags::Dying] && val >= 2) { shipp->final_death_time = timestamp(val); } } } // if all our values are the same as a standard exp, turn off the special exp if ((shipp->special_exp_damage == sci->damage) && (shipp->special_exp_blast == sci->blast) && (shipp->special_exp_inner == sci->inner_rad) && (shipp->special_exp_outer == sci->outer_rad) && (shipp->special_exp_shockwave_speed == sci->speed) && (shipp->special_exp_deathroll_time == 0)) { shipp->use_special_explosion = false; shipp->use_shockwave = false; shipp->special_exp_damage = -1; shipp->special_exp_blast = -1; shipp->special_exp_inner = -1; shipp->special_exp_outer = -1; shipp->special_exp_shockwave_speed = -1; shipp->special_exp_deathroll_time = 0; } } void sexp_create_bolt(int n) { int bolt = get_bolt_type_by_name(CTEXT(n)); if (bolt < 0) return; float coords[6]; bool is_nan, is_nan_forever; for (int i = 0; i <= 5; i++) { n = CDR(n); int buf = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; coords[i] = i2fl(buf); } n = CDR(n); bool playSound = true; if (n >= 0) playSound = eval_sexp(CDR(n)); vec3d origin = vm_vec_new(coords[0], coords[1], coords[2]); vec3d dest = vm_vec_new(coords[3], coords[4], coords[5]); nebl_bolt(bolt, &origin, &dest, playSound); } // Goober5000 // Basically, this function pretends that there's a ship at the origin that's blowing up, and // it does stuff accordingly. In some places, it has to tiptoe around a little because the // code often expects a parent object when in fact there is none. <.< >.> void sexp_explosion_effect(int n) { vec3d origin; int max_damage, max_blast, explosion_size, inner_radius, outer_radius, shockwave_speed, num, fireball_type; int emp_intensity, emp_duration; bool use_emp_time_for_capship_turrets, is_nan, is_nan_forever; Assert( n >= 0 ); // read in data -------------------------------- std::array numbers; eval_array(numbers, n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; origin.xyz.x = (float)numbers[0]; origin.xyz.y = (float)numbers[1]; origin.xyz.z = (float)numbers[2]; max_damage = numbers[3]; max_blast = numbers[4]; explosion_size = numbers[5]; inner_radius = numbers[6]; outer_radius = numbers[7]; shockwave_speed = numbers[8]; // fireball type // ------------- // this node is another SEXP operator or a plain number num = -1; if (CAR(n) != -1 || Sexp_nodes[n].subtype == SEXP_ATOM_NUMBER) { num = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; } else if (sexp_can_construe_as_integer(n)) num = sexp_atoi(n); // is it a number? if (num >= 0) { if (num == 0) { fireball_type = FIREBALL_EXPLOSION_MEDIUM; } else if (num == 1) { fireball_type = FIREBALL_EXPLOSION_LARGE1; } else if (num == 2) { fireball_type = FIREBALL_EXPLOSION_LARGE2; } else if (!SCP_vector_inbounds(Fireball_info, num)) { Warning(LOCATION, "explosion-effect fireball type is out of range; quitting the explosion...\n"); return; } else { fireball_type = num; } } // it's gotta be a name else { const char *unique_id = CTEXT(n); fireball_type = fireball_info_lookup(unique_id); if (fireball_type < 0) { Warning(LOCATION, "unrecognized fireball entry \'%s\'; quitting the explosion...\n", unique_id); return; } } n = CDR(n); // ------------- auto sound_index = sexp_get_sound_index(n); n = CDR(n); // optional EMP eval_nums(n, is_nan, is_nan_forever, emp_intensity, emp_duration); if (is_nan || is_nan_forever) return; use_emp_time_for_capship_turrets = false; if (n != -1) { use_emp_time_for_capship_turrets = is_sexp_true(n); n = CDR(n); } // play sound effect --------------------------- if (sound_index.isValid()) { snd_play_3d( gamesnd_get_game_sound(sound_index), &origin, &View_position, 0.0f, nullptr, 0, 1.0f, SND_PRIORITY_MUST_PLAY ); } // create the fireball ------------------------- if (explosion_size && inner_radius && outer_radius) { if(fireball_type == FIREBALL_EXPLOSION_MEDIUM) fireball_create( &origin, fireball_type, FIREBALL_MEDIUM_EXPLOSION, -1, (float)explosion_size ); else fireball_create( &origin, fireball_type, FIREBALL_LARGE_EXPLOSION, -1, (float)explosion_size ); } // apply area affect damage -------------------- if (max_damage || max_blast) { if ( shockwave_speed > 0 ) { shockwave_create_info sci; shockwave_create_info_init(&sci); sci.inner_rad = (float)inner_radius; sci.outer_rad = (float)outer_radius; sci.blast = (float)max_blast; sci.damage = (float)max_damage; sci.speed = (float)shockwave_speed; sci.rot_angles.p = frand_range(0.0f, 1.99f*PI); sci.rot_angles.b = frand_range(0.0f, 1.99f*PI); sci.rot_angles.h = frand_range(0.0f, 1.99f*PI); shockwave_create(-1, &origin, &sci, SW_SHIP_DEATH); } else { float t_blast = 0.0f; float t_damage = 0.0f; for (auto objp: list_range(&obj_used_list)) { if (objp->flags[Object::Object_Flags::Should_be_dead]) continue; if ( (objp->type != OBJ_SHIP) && (objp->type != OBJ_ASTEROID) ) { continue; } // don't blast no-collide or navbuoys if ( !objp->flags[Object::Object_Flags::Collides] || (objp->type == OBJ_SHIP && ship_get_SIF(objp->instance)[Ship::Info_Flags::Navbuoy]) ) { continue; } if ( ship_explode_area_calc_damage( &origin, &objp->pos, (float)inner_radius, (float)outer_radius, (float)max_damage, (float)max_blast, &t_damage, &t_blast ) == -1 ) { continue; } vec3d force = vmd_zero_vector; vec3d vec_ship_to_impact = objp->pos - origin; switch ( objp->type ) { case OBJ_SHIP: ship_apply_global_damage( objp, nullptr, &origin, t_damage, -1 ); vec_ship_to_impact = objp->pos - origin; if (!IS_VEC_NULL_SQ_SAFE( &vec_ship_to_impact )) { vm_vec_copy_normalize( &force, &vec_ship_to_impact ); force *= (float)max_blast; ship_apply_whack( &force, &origin, objp ); } break; case OBJ_ASTEROID: if (!IS_VEC_NULL_SQ_SAFE(&vec_ship_to_impact)) { vm_vec_copy_normalize(&force, &vec_ship_to_impact); force *= (float)max_blast; } asteroid_hit(objp, nullptr, nullptr, t_damage, &force); break; default: UNREACHABLE("Object magically changed type to %d after exploding!", objp->type); break; } } // end for } } // apply emp damage if applicable -------------- if (emp_intensity && emp_duration) { emp_apply(&origin, (float)inner_radius, (float)outer_radius, (float)emp_intensity, (float)emp_duration, use_emp_time_for_capship_turrets); } } // Goober5000 void sexp_warp_effect(int n) { vec3d origin, location, v_orient; matrix m_orient; int num, shape, fireball_type, extra_flags = FBF_WARP_VIA_SEXP; float radius, duration, warp_open_duration, warp_close_duration; bool is_nan, is_nan_forever; // read in data -------------------------------- std::array numbers; eval_array(numbers, n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; origin.xyz.x = numbers[0]; origin.xyz.y = numbers[1]; origin.xyz.z = numbers[2]; location.xyz.x = numbers[3]; location.xyz.y = numbers[4]; location.xyz.z = numbers[5]; radius = numbers[6]; duration = numbers[7]; if (duration < 4.0f) duration = 4.0f; auto warp_open_sound_index = sexp_get_sound_index(n); n = CDR(n); auto warp_close_sound_index = sexp_get_sound_index(n); n = CDR(n); // fireball type // ------------- // this node is another SEXP operator or a plain number num = -1; if (CAR(n) != -1 || Sexp_nodes[n].subtype == SEXP_ATOM_NUMBER) { num = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; } else if (sexp_can_construe_as_integer(n)) num = sexp_atoi(n); // is it a number? if (num >= 0) { if (num == 0) { fireball_type = FIREBALL_WARP; } else if (num == 1) { fireball_type = FIREBALL_KNOSSOS; } else if (!SCP_vector_inbounds(Fireball_info, num)) { Warning(LOCATION, "warp-effect fireball type is out of range; quitting the warp...\n"); return; } else { fireball_type = num; } } // it's gotta be a name else { const char *unique_id = CTEXT(n); fireball_type = fireball_info_lookup(unique_id); if (fireball_type < 0) { Warning(LOCATION, "unrecognized fireball entry \'%s\'; quitting the warp...\n", unique_id); return; } } n = CDR(n); // ------------- // shape shape = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; if (shape == 0) { // do nothing; this is standard } else if (shape == 1) { extra_flags |= FBF_WARP_3D; } else { Warning(LOCATION, "warp-effect shape is out of range; quitting the warp...\n"); return; } n = CDR(n); // opening and closing durations warp_open_duration = warp_close_duration = -1.0f; if (n >= 0) { warp_open_duration = warp_close_duration = ((float)eval_num(n, is_nan, is_nan_forever)) / 1000.0f; n = CDR(n); if (is_nan || is_nan_forever) return; } if (n >= 0) { warp_close_duration = ((float)eval_num(n, is_nan, is_nan_forever)) / 1000.0f; n = CDR(n); if (is_nan || is_nan_forever) return; } // sanity check, if these were specified if (duration < warp_open_duration + warp_close_duration) { Warning(LOCATION, "Both warp opening and warp closing must occur within the duration of the warp effect. Adjusting opening and closing durations to fit."); warp_open_duration = warp_close_duration = duration / 2.0f; } // calculate orientation matrix ---------------- vm_vec_sub(&v_orient, &location, &origin); if (IS_VEC_NULL_SQ_SAFE(&v_orient)) { Warning(LOCATION, "error in warp-effect: warp can't point to itself; quitting the warp...\n"); return; } vm_vector_2_matrix(&m_orient, &v_orient, nullptr, nullptr); // create fireball ----------------------------- fireball_create(&origin, fireball_type, FIREBALL_WARP_EFFECT, -1, radius, false, nullptr, duration, -1, &m_orient, 0, extra_flags, warp_open_sound_index, warp_close_sound_index, warp_open_duration, warp_close_duration); } void sexp_set_gravity_accel(int node) { Assert(node >= 0); bool is_nan, is_nan_forever; int accel = eval_num(node, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; float fl_accel = (float)(-accel) / 100.0f; vec3d old_gravity = The_mission.gravity; The_mission.gravity = vm_vec_new(0.0f, fl_accel, 0.0f); if ((IS_VEC_NULL(&old_gravity) && !IS_VEC_NULL(&The_mission.gravity)) || (!IS_VEC_NULL(&old_gravity) && IS_VEC_NULL(&The_mission.gravity))) { // gravity was turned on or turned off collide_apply_gravity_flags_weapons(); } // do the multiplayer callback if (MULTIPLAYER_MASTER) { Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_float(fl_accel); Current_sexp_network_packet.end_callback(); } } void multi_sexp_set_gravity_accel() { float accel = 0.0f; Current_sexp_network_packet.get_float(accel); vec3d old_gravity = The_mission.gravity; The_mission.gravity = vm_vec_new(0.0f, accel, 0.0f); if ((IS_VEC_NULL(&old_gravity) && !IS_VEC_NULL(&The_mission.gravity)) || (!IS_VEC_NULL(&old_gravity) && IS_VEC_NULL(&The_mission.gravity))) { // gravity was turned on or turned off collide_apply_gravity_flags_weapons(); } } void sexp_force_rearm(int node) { // This is kiiiiinda hacky. All the related repair methods expect a docker and a dockee, but // nothing really special happens between them. It checks that they are same team and tells the docker // to undock when repair is completed. So with minimal changes we can skip the dock stuff and just tell // the code that the repairer is the repairee and all works as expected. Basically we simply created a // way for a ship to 'repair itself'. for (int n = node; n >= 0; n = CDR(n)) { auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_objp()) return; auto objp = ship_entry->objp(); // Set flags ai_info* aip = &Ai_info[Ships[objp->instance].ai_index]; aip->support_ship_signature = objp->signature; aip->support_ship_objnum = ship_entry->objnum; aip->submode_start_time = Missiontime; // Start the HUD stuff if (aip == Player_ai) { hud_support_view_start(); } // Begin the repair ai_do_objects_repairing_stuff(objp, objp, REPAIR_INFO_BEGIN); } } void sexp_abort_rearm(int node) { for (int n = node; n >= 0; n = CDR(n)) { auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_objp()) return; auto objp = ship_entry->objp(); ai_info* aip = &Ai_info[Ships[objp->instance].ai_index]; // Begin the repair ai_do_objects_repairing_stuff(objp, &Objects[aip->support_ship_objnum], REPAIR_INFO_ABORT); } } // 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( const char *name, const char *who_from, const char *priority, int group, int delay, int event_num = -1, bool do_hash_fallback = false ) { int ipriority, source; int ship_index = -1; int wingnum = -1; ship *shipp; if(physics_paused){ return; } Assert( (name != nullptr) && (who_from != nullptr) && (priority != nullptr) ); // 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 { Warning(LOCATION, "Encountered invalid priority \"%s\" in send-message", priority); ipriority = MESSAGE_PRIORITY_NORMAL; } // do some pre-checks auto ship_entry = ship_registry_get(who_from); if (!ship_entry) wingnum = wing_lookup(who_from); // 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 shipp = nullptr; source = MESSAGE_SOURCE_COMMAND; if (is_special_sender(who_from)) { message_send_unique( name, &(who_from[1]), MESSAGE_SOURCE_SPECIAL, ipriority, group, delay, event_num ); return; } else if ( !ship_entry && wingnum < 0 && do_hash_fallback ) { // ship/wing not found, so act as if this who_from has a hash prepended to it message_send_unique( name, who_from, MESSAGE_SOURCE_SPECIAL, ipriority, group, delay, event_num ); return; } else if (!stricmp(who_from, "")) { return; } else if ( wingnum >= 0 && Wings[wingnum].current_count > 0 ) { // message from a wing // 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( wingnum, SHIP_GET_UNSILENCED, 1 ); if ( ship_index == -1 ) { if ( ipriority != MESSAGE_PRIORITY_HIGH ) return; source = MESSAGE_SOURCE_COMMAND; } } else if ( ship_entry && ship_entry->shipnum >= 0 ) { // Message from a specific ship ship_index = ship_entry->shipnum; } else if ( ship_entry || wingnum >= 0 ) { // getting into this if statement means that the ship or wing (sender) is no longer (or not yet) 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, "") || (wing_name_lookup(who_from) != -1) ) { source = MESSAGE_SOURCE_WINGMAN; } else if ( !stricmp(who_from, "") ) { source = MESSAGE_SOURCE_NONE; } else { Warning(LOCATION, "Invalid message source '%s'. Unable to send message.", who_from); return; } if ( ship_index == -1 ){ shipp = nullptr; } else { shipp = &Ships[ship_index]; source = MESSAGE_SOURCE_SHIP; } message_send_unique( name, shipp, source, ipriority, group, delay, event_num ); } void sexp_send_message(int n) { if(physics_paused){ return; } Assert ( n != -1 ); auto who_from = CTEXT(n); auto priority = CTEXT(CDR(n)); auto 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") ) { auto tmp = name; name = priority; priority = tmp; } // we might override the sender bool do_hash_fallback = false; if (The_mission.flags[Mission::Mission_Flags::Override_hashcommand] && !stricmp(who_from, DEFAULT_HASHCOMMAND)) { who_from = The_mission.command_sender; do_hash_fallback = true; } sexp_send_one_message( name, who_from, priority, 0, 0, -1, do_hash_fallback ); } ship* get_builtin_message_sender(const char* name) { auto ship_entry = ship_registry_get(name); if (ship_entry) { return ship_entry->shipp_or_null(); } auto wing_index = wing_lookup(name); if (wing_index >= 0) { auto ship_index = ship_get_random_ship_in_wing(wing_index, SHIP_GET_UNSILENCED); return ship_index < 0 ? nullptr : &Ships[ship_index]; } if (!stricmp(name, "")) { auto ship_index = ship_get_random_player_wing_ship(SHIP_GET_UNSILENCED); return ship_index < 0 ? nullptr : &Ships[ship_index]; } return nullptr; } void sexp_send_builtin_message(int n) { // Argument 1: Message type auto type = get_builtin_message_type(CTEXT(n)); n = CDR(n); // Argument 2: Subject (or ) auto subject_name = CTEXT(n); auto need_subject = stricmp(subject_name, SEXP_NONE_STRING) != 0; auto subject_entry = ship_registry_get(subject_name); auto have_subject = subject_entry && subject_entry->has_shipp(); n = CDR(n); // Argument 3: Shuffle the senders? auto shuffle = is_sexp_true(n); n = CDR(n); // Argument 4+: Possible senders SCP_vector sender_names; while (n >= 0) { sender_names.push_back(CTEXT(n)); n = CDR(n); } // Done consuming arguments; maybe short-circuit? if (type == MESSAGE_NONE) { return; } if (need_subject && !have_subject) { return; } if (shuffle) { std::random_device rd; std::shuffle(sender_names.begin(), sender_names.end(), std::mt19937(rd())); } auto subject = have_subject ? subject_entry->shipp() : nullptr; for (auto sender_name : sender_names) { if (is_special_sender(sender_name)) { message_send_builtin(type, nullptr, subject); return; } else { auto sender = get_builtin_message_sender(sender_name); if (sender != nullptr) { message_send_builtin(type, sender, subject); return; } } } } void sexp_send_message_list(int n, bool send_message_chain) { int delay = 0, event_num = -1; bool is_nan, is_nan_forever; if(physics_paused){ return; } // find the event that will cancel the message chain if (send_message_chain) { auto name = CTEXT(n); n = CDR(n); for (int i = 0; i < (int)Mission_events.size(); ++i) { if (!stricmp(Mission_events[i].name.c_str(), name)) { event_num = i; break; } } if (event_num < 0) { return; } } // send a bunch of messages while(n != -1){ auto who_from = CTEXT(n); n = CDR(n); // next node if(n == -1){ Warning(LOCATION, "Detected incomplete parameter list in sexp-send-message-list"); return; } auto priority = CTEXT(n); n = CDR(n); // next node if(n == -1){ Warning(LOCATION, "Detected incomplete parameter list in sexp-send-message-list"); return; } auto name = CTEXT(n); n = CDR(n); // next node if(n == -1){ Warning(LOCATION, "Detected incomplete parameter list in sexp-send-message-list"); return; } delay += eval_num(n, is_nan, is_nan_forever); n = CDR(n); if (is_nan || is_nan_forever) { Warning(LOCATION, "Encountered a NaN in sexp-send-message-list"); return; } // we might override the sender bool do_hash_fallback = false; if (The_mission.flags[Mission::Mission_Flags::Override_hashcommand] && !stricmp(who_from, DEFAULT_HASHCOMMAND)) { who_from = The_mission.command_sender; do_hash_fallback = true; } // send the message sexp_send_one_message( name, who_from, priority, 1, delay, event_num, do_hash_fallback ); } } void sexp_send_random_message(int n) { int temp, num_messages, message_num; Assert ( n != -1 ); auto who_from = CTEXT(n); auto 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 = Random::next(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!!! auto name = CTEXT(n); // we might override the sender bool do_hash_fallback = false; if (The_mission.flags[Mission::Mission_Flags::Override_hashcommand] && !stricmp(who_from, DEFAULT_HASHCOMMAND)) { who_from = The_mission.command_sender; do_hash_fallback = true; } sexp_send_one_message( name, who_from, priority, 0, 0, -1, do_hash_fallback ); } void sexp_self_destruct(int node) { for (int n = node; n != -1; n = CDR(n)) { // get the ship auto ship_entry = eval_ship(n); // if it still exists, destroy it if (ship_entry && ship_entry->status == ShipStatus::PRESENT) { ship_self_destruct(ship_entry->objp()); } } } void sexp_cancel_future_waves(int node) { for (int n = node; n != -1; n = CDR(n)) { auto wingp = eval_wing(n); if (wingp) { // prevent any more waves from arriving by marking this as the last wave wingp->num_waves = wingp->current_wave; // we might need to clean up this wing if there are no ships currently in the mission wing_maybe_cleanup(wingp); } } } void sexp_next_mission(int n) { auto mission_name = CTEXT(n); if (mission_name == nullptr) { Error( LOCATION, "Mission name is NULL in campaign file for next-mission command!"); } for (int 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); } /** * 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. // Goober5000 - edited to only to the FS2-specific code when actually ending the FS2 main // campaign, and otherwise to do the conventional code void sexp_end_campaign(int n) { bool ignore_player_mortality = true; if (!(Game_mode & GM_CAMPAIGN_MODE)) { return; } if (n != -1) { ignore_player_mortality = is_sexp_true(n); } // if the player is dead we may want to let the death screen handle things if (!ignore_player_mortality && (Player_ship->flags[Ship::Ship_Flags::Dying])) { return; } // in FS2 our ending is a bit wacky. we'll just flag the mission as having ended the campaign // // changed this to check for an active supernova rather than a special campaign since the supernova // code needs special time to execute and will post GS_EVENT_END_CAMPAIGN with Game_mode check // or show death-popup when it's done - taylor if (supernova_active() /*&& !stricmp(Campaign.filename, "freespace2")*/) { Campaign_ending_via_supernova = 1; } else { // post and event to move us to the end-of-campaign state gameseq_post_event(GS_EVENT_END_CAMPAIGN); } } void set_subsys_strength_and_maybe_ancestors(ship *shipp, ship_subsys *ss, polymodel *pm, const int *assign_percent, const int *repair_percent, const int *sabotage_percent, bool do_submodel_repair, bool repair_ancestors) { Assertion(shipp != nullptr && ss != nullptr, "Ship and subsys must not be null!"); Assertion(assign_percent != nullptr || repair_percent != nullptr || sabotage_percent != nullptr, "Either assign_percent or repair_percent or sabotage_percent must not be null!"); bool originally_zero = (ss->current_hits <= 0); if (ss->submodel_instance_1 && ss->submodel_instance_1->blown_off) originally_zero = true; if (ss->submodel_instance_2 && ss->submodel_instance_2->blown_off) originally_zero = true; if (assign_percent != nullptr) { int percentage = *assign_percent; Assertion(percentage >= 0 && percentage <= 100, "Percentage must be in range [0, 100]"); // assign the hitpoints ss->current_hits = ss->max_hits * ((float)percentage / 100.0f); // maybe blow up subsys if (ss->current_hits <= 0 && !originally_zero) do_subobj_destroyed_stuff(shipp, ss, nullptr); } else if (repair_percent != nullptr) { int percentage = *repair_percent; Assertion(percentage > 0, "Repair percentage must be more than zero!"); // repair the hitpoints float repair_hits = ss->max_hits * ((float)percentage / 100.0f); ss->current_hits += repair_hits; if (ss->current_hits > ss->max_hits) ss->current_hits = ss->max_hits; } else if (sabotage_percent != nullptr) { int percentage = *sabotage_percent; Assertion(percentage > 0, "Sabotage percentage must be more than zero!"); // sabotage the hitpoints float sabotage_hits = ss->max_hits * ((float)percentage / 100.0f); ss->current_hits -= sabotage_hits; if (ss->current_hits < 0.0f) ss->current_hits = 0.0f; // maybe blow up subsys if (ss->current_hits <= 0 && !originally_zero) do_subobj_destroyed_stuff(shipp, ss, nullptr); } else return; // and now see if we are repairing from zero if (originally_zero && ss->current_hits > 0 && do_submodel_repair) { restore_subsystem_submodels(shipp, ss); // see if we are handling ancestors and if this subsystem has a submodel int subobj = ss->system_info->subobj_num; if (repair_ancestors && subobj >= 0) { if (pm == nullptr) pm = model_get(Ship_info[shipp->ship_info_index].model_num); // do we have a parent? int parent_subobj = pm->submodel[subobj].parent; if (parent_subobj >= 0) { // search for the subsystem for (ship_subsys *parent_ss = GET_FIRST(&shipp->subsys_list); parent_ss != END_OF_LIST(&shipp->subsys_list); parent_ss = GET_NEXT(parent_ss)) { // found parent? if (parent_ss->system_info->subobj_num == parent_subobj) { bool parent_destroyed = (parent_ss->current_hits <= 0); if (parent_ss->submodel_instance_1 && parent_ss->submodel_instance_1->blown_off) parent_destroyed = true; if (parent_ss->submodel_instance_2 && parent_ss->submodel_instance_2->blown_off) parent_destroyed = true; // if the parent subobject was destroyed... if (parent_destroyed) { // ...repair the parent in the same way set_subsys_strength_and_maybe_ancestors(shipp, parent_ss, pm, assign_percent, repair_percent, sabotage_percent, do_submodel_repair, repair_ancestors); } // only one parent break; } } } } } } /** * 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) { int percentage, subsystem_node; float sabotage_hits; bool is_nan, is_nan_forever; auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) return; auto shipp = ship_entry->shipp(); n = CDR(n); subsystem_node = n; n = CDR(n); percentage = eval_num(n, is_nan, is_nan_forever); n = CDR(n); if (is_nan || is_nan_forever) return; // abort if we're not even sabotaging anything if (percentage <= 0) { return; } bool any_subsystem = false; // process the single subsystem, which could be hull, sim hull, or a generic type like process_ship_subsystems(ship_entry, true, subsystem_node, false, "sabotage-subsystem", [&](ProcessSubsystemType type, ship_subsys *ss) { if (type == ProcessSubsystemType::HULL) { float ihs; auto objp = ship_entry->objp(); ihs = shipp->ship_max_hull_strength; sabotage_hits = ihs * (i2fl(percentage) / 100.0f); objp->hull_strength -= sabotage_hits; // self destruct the ship if <= 0. if (objp->hull_strength <= 0.0f) ship_self_destruct(objp); } else if (type == ProcessSubsystemType::SIM_HULL) { float ihs; auto objp = ship_entry->objp(); ihs = shipp->ship_max_hull_strength; sabotage_hits = ihs * (i2fl(percentage) / 100.0f); objp->sim_hull_strength -= sabotage_hits; } else if (type == ProcessSubsystemType::SHIELDS) { Warning(LOCATION, "sabotage-subsystem does not support " SEXP_SHIELD_STRING); } else { // get the pointer to the subsystem. Check its current hits against its max hits, and // set the strength to the given percentage if current strength is > given percentage set_subsys_strength_and_maybe_ancestors(shipp, ss, nullptr, nullptr, nullptr, &percentage, false, false); any_subsystem = true; } }); // recalculate when done if (any_subsystem) ship_recalc_subsys_strength(shipp); } /** * Adds some percentage of hits to a subsystem. * * Anything repaired about 100% is set to max hits */ void sexp_repair_subsystem(int n) { int percentage, subsystem_node; bool do_submodel_repair = true, do_ancestor_repair = true; float repair_hits; bool is_nan, is_nan_forever; auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) return; auto shipp = ship_entry->shipp(); n = CDR(n); subsystem_node = n; n = CDR(n); percentage = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; n = CDR(n); // abort if we're not even repairing anything if (percentage <= 0) { return; } if (n >= 0) { do_submodel_repair = is_sexp_true(n); n = CDR(n); if (n >= 0) do_ancestor_repair = is_sexp_true(n); } bool any_subsystem = false; // process the single subsystem, which could be hull, sim hull, or a generic type like process_ship_subsystems(ship_entry, true, subsystem_node, false, "repair-subsystem", [&](ProcessSubsystemType type, ship_subsys *ss) { if (type == ProcessSubsystemType::HULL) { float ihs; auto objp = ship_entry->objp(); ihs = shipp->ship_max_hull_strength; repair_hits = ihs * (i2fl(percentage) / 100.0f); objp->hull_strength += repair_hits; if (objp->hull_strength > ihs) objp->hull_strength = ihs; } else if (type == ProcessSubsystemType::SIM_HULL) { float ihs; auto objp = ship_entry->objp(); ihs = shipp->ship_max_hull_strength; repair_hits = ihs * (i2fl(percentage) / 100.0f); objp->sim_hull_strength += repair_hits; if (objp->sim_hull_strength > ihs) objp->sim_hull_strength = ihs; } else if (type == ProcessSubsystemType::SHIELDS) { Warning(LOCATION, "repair-subsystem does not support " SEXP_SHIELD_STRING); } else { // get the pointer to the subsystem. Check its current hits against its max hits, and // set the strength to the given percentage if current strength is < given percentage set_subsys_strength_and_maybe_ancestors(shipp, ss, nullptr, nullptr, &percentage, nullptr, do_submodel_repair, do_ancestor_repair); any_subsystem = true; } }); // recalculate when done if (any_subsystem) ship_recalc_subsys_strength(shipp); } /** * Set a subsystem of a ship at a specific percentage */ void sexp_set_subsystem_strength(int n) { int percentage, subsystem_node; bool do_submodel_repair = true, do_ancestor_repair = true; bool is_nan, is_nan_forever; auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) return; auto shipp = ship_entry->shipp(); n = CDR(n); subsystem_node = n; n = CDR(n); percentage = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; n = CDR(n); if (n >= 0) { do_submodel_repair = is_sexp_true(n); n = CDR(n); if (n >= 0) do_ancestor_repair = is_sexp_true(n); } if ( percentage > 100 ) { mprintf(("Percentage for set-subsystem-strength > 100 on ship %s for subsystem '%s' -- setting to 100\n", ship_entry->name, CTEXT(subsystem_node))); percentage = 100; } else if ( percentage < 0 ) { mprintf(("Percentage for set-subsystem-strength < 0 on ship %s for subsystem '%s' -- setting to 0\n", ship_entry->name, CTEXT(subsystem_node))); percentage = 0; } bool any_subsystem = false; // process the single subsystem, which could be hull, sim hull, or a generic type like process_ship_subsystems(ship_entry, true, subsystem_node, false, "set-subsystem-strength", [&](ProcessSubsystemType type, ship_subsys *ss) { if (type == ProcessSubsystemType::HULL) { float ihs; auto objp = ship_entry->objp(); // destroy the ship if percentage is 0 if (percentage == 0) { ship_self_destruct(objp); } else { ihs = shipp->ship_max_hull_strength; objp->hull_strength = ihs * (i2fl(percentage) / 100.0f); } } else if (type == ProcessSubsystemType::SIM_HULL) { float ihs; auto objp = ship_entry->objp(); ihs = shipp->ship_max_hull_strength; objp->sim_hull_strength = ihs * (i2fl(percentage) / 100.0f); } else if (type == ProcessSubsystemType::SHIELDS) { Warning(LOCATION, "set-subsystem-strength does not support " SEXP_SHIELD_STRING); } else { // get the pointer to the subsystem. Check its current hits against its max hits, and // set the strength to the given percentage set_subsys_strength_and_maybe_ancestors(shipp, ss, nullptr, &percentage, nullptr, nullptr, do_submodel_repair, do_ancestor_repair); any_subsystem = true; } }); // recalculate when done if (any_subsystem) ship_recalc_subsys_strength(shipp); } // destroys a subsystem without explosions void sexp_destroy_subsys_instantly(int n) { auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) return; auto shipp = ship_entry->shipp(); n = CDR(n); if (MULTIPLAYER_MASTER) { Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_ship(shipp); } bool any_subsystem = false; // process the subsystems, which could be hull, sim hull, or a generic type like process_ship_subsystems(ship_entry, true, n, true, "destroy-subsys-instantly", [&](ProcessSubsystemType type, ship_subsys *ss) { // this sexp is only for subsystems if (type == ProcessSubsystemType::SUBSYSTEM) { // do destruction stuff ss->current_hits = 0; do_subobj_destroyed_stuff(shipp, ss, nullptr, true); if (MULTIPLAYER_MASTER) { int subsys_index = ship_get_subsys_index(ss); Assert(subsys_index >= 0); Current_sexp_network_packet.send_int(subsys_index); } any_subsystem = true; } }); // recalculate when done if (any_subsystem) ship_recalc_subsys_strength(shipp); if (MULTIPLAYER_MASTER) Current_sexp_network_packet.end_callback(); } void multi_sexp_destroy_subsys_instantly() { ship *shipp; int subsys_index; ship_subsys *ss; Current_sexp_network_packet.get_ship(shipp); // destroy subsystems while (Current_sexp_network_packet.get_int(subsys_index)) { // find subsystem Assert(subsys_index >= 0); ss = ship_get_indexed_subsys(shipp, subsys_index); // do destruction stuff ss->current_hits = 0; do_subobj_destroyed_stuff(shipp, ss, nullptr, true); } // recalculate when done ship_recalc_subsys_strength(shipp); } /** * 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, bool flag ) { while ( n != -1 ) { auto name = CTEXT(n); mission_goal_mark_valid( name, flag ); n = CDR(n); } } // Goober5000 // yeesh - be careful of the cargo-no-deplete flag :p int sexp_is_cargo(int n) { auto cargo = CTEXT(n); n = CDR(n); auto ship_entry = eval_ship(n); if (!ship_entry) return SEXP_NAN; n = CDR(n); auto subsystem = (n >= 0) ? CTEXT(n) : nullptr; int cargo_index = -1; // exited (on the exited_ships list)? if (ship_entry->exited_index >= 0) { // can't check subsys of ships not in mission if (subsystem) return SEXP_NAN_FOREVER; cargo_index = Ships_exited[ship_entry->exited_index].cargo1; } // not arrived yet? else if (ship_entry->status == ShipStatus::NOT_YET_PRESENT) { // can't check subsys of ships not in mission if (subsystem) return SEXP_NAN; cargo_index = ship_entry->p_objp()->cargo1; } // in-mission? else if (ship_entry->has_shipp()) { if (subsystem) { // find the ship subsystem ship_subsys *ss = ship_get_subsys(ship_entry->shipp(), subsystem); if (ss) { // set cargo cargo_index = ss->subsys_cargo_name; } } else { cargo_index = ship_entry->shipp()->cargo1; } } // probably vanished else return SEXP_NAN_FOREVER; // did we get any cargo if (cargo_index < 0) return SEXP_FALSE; // check cargo if (!stricmp(Cargo_names[cargo_index & CARGO_INDEX_MASK], cargo)) return SEXP_TRUE; else return SEXP_FALSE; } // Goober5000 // yeesh - be careful of the cargo-no-deplete flag :p void sexp_set_cargo(int n) { auto cargo = CTEXT(n); n = CDR(n); auto ship_entry = eval_ship(n); if (!ship_entry) return; n = CDR(n); auto subsystem = (n >= 0) ? CTEXT(n) : nullptr; int cargo_index = -1; // find this cargo in the cargo list for (int i = 0; i < Num_cargo; ++i) { // found it? if (!stricmp(cargo, Cargo_names[i])) { cargo_index = i; break; } } // not found if (cargo_index == -1) { // make new entry if possible if (Num_cargo + 1 >= MAX_CARGO) { Warning(LOCATION, "set-cargo: Maximum number of cargo names (%d) reached. Ignoring new name.\n", MAX_CARGO); return; } Assert(strlen(cargo) <= NAME_LENGTH - 1); cargo_index = Num_cargo; Num_cargo++; strcpy(Cargo_names[cargo_index], cargo); } // exited (on the exited_ships list)? if (ship_entry->exited_index >= 0) { return; } // not arrived yet? else if (ship_entry->status == ShipStatus::NOT_YET_PRESENT) { // can't check subsys of ships not in mission if (subsystem) return; ship_entry->p_objp()->cargo1 = char(cargo_index | (ship_entry->p_objp()->cargo1 & CARGO_NO_DEPLETE)); } // in-mission? else if (ship_entry->has_shipp()) { if (subsystem) { ship_subsys *ss = ship_get_subsys(ship_entry->shipp(), subsystem); if (ss) { // set cargo ss->subsys_cargo_name = cargo_index | (ss->subsys_cargo_name & CARGO_NO_DEPLETE); } } else { // simply set the ship cargo ship_entry->shipp()->cargo1 = char(cargo_index | (ship_entry->shipp()->cargo1 & CARGO_NO_DEPLETE)); } } } /** * Transfer cargo from one ship to another */ void sexp_transfer_cargo(int n) { // find the ships -- if neither in the mission, abort auto ship1 = eval_ship(n); if (!ship1 || !ship1->has_shipp()) return; auto ship2 = eval_ship(CDR(n)); if (!ship2 || !ship2->has_shipp()) return; if ( !stricmp(Cargo_names[ship1->shipp()->cargo1 & CARGO_INDEX_MASK], "nothing") ) { return; } // transfer cargo from ship1 to ship2 #ifndef NDEBUG // Don't give warning for large ships (cruiser on up) if (! (Ship_info[ship2->shipp()->ship_info_index].is_big_or_huge()) ) { if ( stricmp(Cargo_names[ship2->shipp()->cargo1 & CARGO_INDEX_MASK], "nothing") != 0 ) { Warning(LOCATION, "Transferring cargo to %s which already\nhas cargo %s.\nCargo will be replaced", ship2->name, Cargo_names[ship2->shipp()->cargo1 & CARGO_INDEX_MASK] ); } } #endif ship2->shipp()->cargo1 = char((ship1->shipp()->cargo1 & CARGO_INDEX_MASK) | (ship2->shipp()->cargo1 & CARGO_NO_DEPLETE)); if ( !(ship1->shipp()->cargo1 & CARGO_NO_DEPLETE) ) { int i = 0; // need to set ship1's cargo to nothing. scan the cargo_names array looking for the string nothing. for (; i < Num_cargo; ++i) { if ( !stricmp(Cargo_names[i], NOX("nothing")) ) { ship1->shipp()->cargo1 = char(i); return; } } // add it if not found strcpy(Cargo_names[i], NOX("Nothing")); Num_cargo++; } } /** * Exchanges cargo between two ships */ void sexp_exchange_cargo(int n) { // find the ships -- if neither in the mission, abort auto ship1 = eval_ship(n); if (!ship1 || !ship1->has_shipp()) return; auto ship2 = eval_ship(CDR(n)); if (!ship2 || !ship2->has_shipp()) return; // we must be sure that these two objects are indeed docked if (!dock_check_find_direct_docked_object(ship1->objp(), ship2->objp())) { Warning(LOCATION, "Tried to exchange cargo between %s and %s although they aren't docked!", ship1->name, ship2->name); return; } int temp = (ship1->shipp()->cargo1 & CARGO_INDEX_MASK); ship1->shipp()->cargo1 = char(ship2->shipp()->cargo1 & CARGO_INDEX_MASK); ship2->shipp()->cargo1 = char(temp); } void sexp_cap_waypoint_speed(int n) { bool is_nan, is_nan_forever; auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) return; int speed = eval_num(CDR(n), is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; // cap speed to range (-1, 32767) to store within int if (speed < 0) { speed = -1; } if (speed > 32767) { speed = 32767; } Ai_info[ship_entry->shipp()->ai_index].waypoint_speed_cap = speed; } void sexp_set_wing_formation(int node) { bool is_nan, is_nan_forever; auto formation = wing_formation_lookup(CTEXT(node)); if (formation < 0) { if (stricmp(CTEXT(node), "Default") != 0) return; } int multiplier = eval_num(CDR(node), is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; if (multiplier == 0) { Warning(LOCATION, "Formation percent cannot be 0! Defaulting to 100."); multiplier = 100; } float factor = i2fl(multiplier) / 100; for (int n = CDDR(node); n >= 0; n = CDR(n)) { auto wingp = eval_wing(n); if (wingp) { wingp->formation = formation; wingp->formation_scale = factor; } } } /** * Causes a ship to jettison its cargo */ void sexp_jettison_cargo(int n, bool jettison_new) { float jettison_speed; bool is_nan, is_nan_forever; auto parent = eval_ship(n); if (!parent || !parent->has_shipp()) return; n = CDR(n); // in jettison-cargo-delay, this is the delay (which is unimplemented) // in jettison-cargo, this is the jettison speed, which is optional if (n >= 0) { jettison_speed = static_cast(eval_num(n, is_nan, is_nan_forever)); n = CDR(n); // it would be fun to break the physics engine, but let's not if (is_nan || is_nan_forever) return; } // per sexp help, if unspecified, default to 25 // (see also OP_JETTISON_CARGO_NEW in sexp_tree.cpp) else jettison_speed = 25.0f; // no arguments - jettison all docked objects if (n < 0) { // Goober5000 - as with ai_deathroll_start, we can't simply iterate through the dock list while we're // undocking things. So just repeatedly jettison the first object. while (object_is_docked(parent->objp())) { object_jettison_cargo(parent->objp(), dock_get_first_docked_object(parent->objp()), jettison_speed, jettison_new); } } // arguments - jettison only those objects else { for (; n != -1; n = CDR(n)) { // make sure ship exists auto child = eval_ship(n); if (!child || !child->has_objp()) continue; // make sure we are docked to it if (!dock_check_find_direct_docked_object(parent->objp(), child->objp())) continue; object_jettison_cargo(parent->objp(), child->objp(), jettison_speed, jettison_new); } } } void sexp_set_docked(int n) { // get some data auto docker = eval_ship(n); if (!docker || !docker->has_shipp()) return; n = CDR(n); auto docker_point_name = CTEXT(n); n = CDR(n); auto dockee = eval_ship(n); if (!dockee || !dockee->has_shipp()) return; n = CDR(n); auto dockee_point_name = CTEXT(n); //Get dockpoints by name int docker_point_index = model_find_dock_name_index(Ship_info[docker->shipp()->ship_info_index].model_num, docker_point_name); int dockee_point_index = model_find_dock_name_index(Ship_info[dockee->shipp()->ship_info_index].model_num, dockee_point_name); Assertion(docker_point_index >= 0, "Docker point '%s' not found on docker ship '%s'", docker_point_name, docker->name); Assertion(dockee_point_index >= 0, "Dockee point '%s' not found on dockee ship '%s'", dockee_point_name, dockee->name); //Make sure that the specified dockpoints are all free (if not, do nothing) if (dock_find_object_at_dockpoint(docker->objp(), docker_point_index) != nullptr || dock_find_object_at_dockpoint(dockee->objp(), dockee_point_index) != nullptr) { return; } //Set docked dock_orient_and_approach(docker->objp(), docker_point_index, dockee->objp(), dockee_point_index, DOA_DOCK_STAY); ai_do_objects_docked_stuff(docker->objp(), docker_point_index, dockee->objp(), dockee_point_index, true); } void sexp_cargo_no_deplete(int n) { int no_deplete = 1; bool is_nan, is_nan_forever; // get some data auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) return; n = CDR(n); if ( !(Ship_info[ship_entry->shipp()->ship_info_index].is_big_or_huge()) ) { Warning(LOCATION, "Trying to make non BIG or HUGE ship %s with non-depletable cargo.\n", ship_entry->name); return; } if (n != -1) { no_deplete = eval_num(n, is_nan, is_nan_forever); Assert((no_deplete == 0) || (no_deplete == 1)); if (is_nan || is_nan_forever) { no_deplete = 0; } else if ( (no_deplete != 0) && (no_deplete != 1) ) { no_deplete = 1; } } if (no_deplete) { ship_entry->shipp()->cargo1 |= CARGO_NO_DEPLETE; } else { ship_entry->shipp()->cargo1 &= (~CARGO_NO_DEPLETE); } } // Goober5000 void sexp_force_jump() { // Shouldn't be gliding now.... Player_obj->phys_info.flags &= ~PF_GLIDING; Player_obj->phys_info.flags &= ~PF_FORCE_GLIDE; if (Game_mode & GM_MULTIPLAYER) { multi_handle_end_mission_request(); } else { // forced warp, taken from training failure code gameseq_post_event( GS_EVENT_PLAYER_WARPOUT_START_FORCED ); // Force player to warp out. } } void sexp_mission_set_nebula(int n) { bool is_nan, is_nan_forever; int set_it, range; // range is optional, so if it isn't found, it will be set to 0, // which will in turn be set to a default in the next function // (this means the sexp cannot set the nebula range to 0, // but the same is true in the background editor) eval_nums(n, is_nan, is_nan_forever, set_it, range); if (is_nan || is_nan_forever) return; stars_set_nebula(set_it > 0, static_cast(range)); } /* freespace.cpp does not have these availiable externally, and we must call them so that the main simulation loop does not have to constantly check for Game_subspace_effect so that it could turn on the subspace sounds. Because these are in freespace.cpp there are also stubs of these functions in fred.cpp as it does not deal with the game loop (obviously) but still links against code.lib. */ extern void game_start_subspace_ambient_sound(); extern void game_stop_subspace_ambient_sound(); void sexp_mission_set_subspace(int n) { bool is_nan, is_nan_forever; int set_it = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; if (set_it > 0) { if (Game_subspace_effect) return; Game_subspace_effect = 1; game_start_subspace_ambient_sound(); } else { if (!Game_subspace_effect) return; Game_subspace_effect = 0; game_stop_subspace_ambient_sound(); } stars_set_dynamic_environment(Game_subspace_effect != 0); The_mission.flags.set(Mission::Mission_Flags::Subspace, Game_subspace_effect != 0); } void sexp_change_background(int node) { bool is_nan, is_nan_forever; int background_idx = eval_num(node, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; // human/computer offset background_idx--; // range check if (background_idx < 0 || background_idx >= (int)Backgrounds.size()) return; stars_load_background(background_idx); } void sexp_add_background_bitmap(int n, bool is_sun, bool uses_correct_angles) { int sexp_var, new_number, sanity; bool is_nan, is_nan_forever; char number_as_str[TOKEN_LENGTH]; starfield_list_entry sle; // filename strcpy_s(sle.filename, CTEXT(n)); n = CDR(n); // sanity checking sanity = is_sun ? stars_find_sun(sle.filename) : stars_find_bitmap(sle.filename); if (sanity < 0) { Warning(LOCATION, "sexp-add-%s-bitmap: '%s' not found!", is_sun ? "sun" : "background", sle.filename); return; } // angles eval_angles(&sle.ang, n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; if (!uses_correct_angles) { if (is_sun) stars_correct_background_sun_angles(&sle.ang); else stars_correct_background_bitmap_angles(&sle.ang); } if (is_sun) { int num = eval_num(n, is_nan, is_nan_forever); n = CDR(n); if (is_nan || is_nan_forever) return; // scale sle.scale_x = num / 100.0f; sle.scale_y = sle.scale_x; // div sle.div_x = 1; sle.div_y = 1; // restrict parameters if (sle.scale_x > 50) sle.scale_x = 50; if (sle.scale_x < 0.1f) sle.scale_x = 0.1f; if (sle.scale_y > 50) sle.scale_y = 50; if (sle.scale_y < 0.1f) sle.scale_y = 0.1f; } else { // next 4 std::array numbers; eval_array(numbers, n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; // scale sle.scale_x = numbers[0] / 100.0f; sle.scale_y = numbers[1] / 100.0f; // div sle.div_x = numbers[2]; sle.div_y = numbers[3]; // restrict parameters if (sle.scale_x > 18) sle.scale_x = 18; if (sle.scale_x < 0.1f) sle.scale_x = 0.1f; if (sle.scale_y > 18) sle.scale_y = 18; if (sle.scale_y < 0.1f) sle.scale_y = 0.1f; if (sle.div_x > 5) sle.div_x = 5; if (sle.div_x < 1) sle.div_x = 1; if (sle.div_y > 5) sle.div_y = 5; if (sle.div_y < 1) sle.div_y = 1; } if (n == -1) { if (is_sun) { stars_add_sun_entry(&sle); } else { stars_add_bitmap_entry(&sle); } } else { Assert((n >= 0) && (n < Num_sexp_nodes)); // ripped from sexp_modify_variable() sexp_var = sexp_get_variable_index(n); if (sexp_var < 0) { Warning(LOCATION, "sexp-add-%s-bitmap: Variable %s does not exist!", is_sun ? "sun" : "background", Sexp_nodes[n].text); return; } if (Sexp_variables[sexp_var].type & SEXP_VARIABLE_NUMBER) { // get new numerical value new_number = is_sun ? stars_add_sun_entry(&sle) : stars_add_bitmap_entry(&sle); if (new_number < 0) { Warning(LOCATION, "Unable to add %s: '%s'!", is_sun ? "sun" : "starfield bitmap", sle.filename); new_number = 0; } sprintf(number_as_str, "%d", new_number); // assign to variable sexp_modify_variable(number_as_str, sexp_var); } else { Warning(LOCATION, "sexp-add-%s-bitmap: Variable %s must be a number variable!", is_sun ? "sun" : "background", Sexp_variables[sexp_var].variable_name); return; } } } void sexp_remove_background_bitmap(int n, bool is_sun) { bool is_nan, is_nan_forever; int slot = eval_num(n, is_nan, is_nan_forever); if (slot >= 0 && !is_nan && !is_nan_forever) { int instances = is_sun ? stars_get_num_suns() : stars_get_num_bitmaps(); if (instances > slot) { if (is_sun) { stars_mark_sun_unused(slot); } else { stars_mark_bitmap_unused(slot); } } else { Warning(LOCATION, "remove-%s-bitmap: slot %d does not exist. Slot must be less than %d.", is_sun ? "sun" : "background", slot, instances); } } } void sexp_nebula_change_storm(int n) { if (!(The_mission.flags[Mission::Mission_Flags::Fullneb])) return; nebl_set_storm(CTEXT(n)); } void sexp_nebula_set_poofs(int node, bool legacy) { bool result; int n = node; // new and legacy sexps have different argument orders if (legacy) result = is_sexp_true(CDR(n)); else { result = is_sexp_true(n); n = CDR(n); } // toggle all the poofs if (n < 0) { int count = static_cast(Poof_info.size()); for (int i = 0; i < count; ++i) neb2_toggle_poof(i, result); } // toggle the selected poofs else { for (; n >= 0; n = CDR(n)) { auto name = CTEXT(n); int index = find_item_with_string(Poof_info, &poof_info::name, name); if (index >= 0) neb2_toggle_poof(index, result); if (legacy) break; // the legacy sexp affects only one poof } } // do this once at the end of all the toggling neb2_toggle_poof_finalize(); } void sexp_nebula_fade_poofs(int node, bool legacy) { bool result, is_nan, is_nan_forever; int duration; int n = node; // new and legacy sexps have different argument orders if (legacy) { duration = eval_num(CDR(n), is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; result = is_sexp_true(CDDR(n)); } else { result = is_sexp_true(n); n = CDR(n); duration = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; n = CDR(n); } // fade all the poofs if (n < 0) { int count = static_cast(Poof_info.size()); for (int i = 0; i < count; ++i) neb2_fade_poof(i, duration, result); } // fade the specified poofs else { for (; n >= 0; n = CDR(n)) { auto name = CTEXT(n); int index = find_item_with_string(Poof_info, &poof_info::name, name); if (index >= 0) neb2_fade_poof(index, duration, result); if (legacy) break; // the legacy sexp affects only one poof } } } void sexp_nebula_change_pattern(int n) { if (!(The_mission.flags[Mission::Mission_Flags::Fullneb])) return; strcpy_s(Neb2_texture_name,(CTEXT(n))); The_mission.flags.remove(Mission::Mission_Flags::Neb2_fog_color_override); neb2_post_level_init(The_mission.flags[Mission::Mission_Flags::Neb2_fog_color_override]); } void sexp_nebula_change_fog_color(int node) { if (!(The_mission.flags[Mission::Mission_Flags::Fullneb])) return; int red, green, blue = 0; bool is_nan, is_nan_forever; Assert(node >= 0); eval_nums(node, is_nan, is_nan_forever, red, green, blue); if (is_nan || is_nan_forever) return; CLAMP(red, 0, 255); CLAMP(green, 0, 255); CLAMP(blue, 0, 255); Neb2_fog_color[0] = (ubyte)red; Neb2_fog_color[1] = (ubyte)green; Neb2_fog_color[2] = (ubyte)blue; The_mission.flags |= Mission::Mission_Flags::Neb2_fog_color_override; } void sexp_nebula_set_range(int node) { bool is_nan, is_nan_forever; int range = eval_num(node, is_nan, is_nan_forever); if (is_nan || is_nan_forever || range < 1) return; Neb2_awacs = i2fl(range); } void sexp_volumetrics_toggle(int n) { if (!The_mission.volumetrics) return; The_mission.volumetrics->set_enabled(is_sexp_true(n)); } void sexp_toggle_asteroid_field(int n) { Asteroids_enabled = is_sexp_true(n); } void sexp_set_asteroid_field(int n, bool new_sexp) { bool is_nan, is_nan_forever; int field_type = 0, num_asteroids = 0, asteroid_speed = 0; for (int i = 0; i < 3; i++) { if (n >= 0) { int temp = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; if (i == 0) num_asteroids = temp; else if (i == 1) field_type = temp; else asteroid_speed = temp; n = CDR(n); } } SCP_vector asteroid_types; if (!new_sexp) { if (n >= 0) { if (is_sexp_true(n)) { asteroid_types.push_back("Brown"); } n = CDR(n); } if (n >= 0) { if (is_sexp_true(n)) { asteroid_types.push_back("Blue"); } n = CDR(n); } if (n >= 0) { if (is_sexp_true(n)) { asteroid_types.push_back("Orange"); } n = CDR(n); } } int o_minx = -1000, o_miny = -1000, o_minz = -1000; int o_maxx = 1000, o_maxy = 1000, o_maxz = 1000; for (int i = 0; i < 6; i++) { if (n >= 0) { int temp = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; if (i == 0) o_minx = temp; else if (i == 1) o_maxx = temp; else if (i == 2) o_miny = temp; else if (i == 3) o_maxy = temp; else if (i == 4) o_minz = temp; else o_maxz = temp; n = CDR(n); } } bool inner_box = false; if (n >= 0) { inner_box = is_sexp_true(n); n = CDR(n); } int i_minx = -500, i_miny = -500, i_minz = -500; int i_maxx = 500, i_maxy = 500, i_maxz = 500; for (int i = 0; i < 6; i++) { if (n >= 0) { int temp = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; if (i == 0) i_minx = temp; else if (i == 1) i_maxx = temp; else if (i == 2) i_miny = temp; else if (i == 3) i_maxy = temp; else if (i == 4) i_minz = temp; else i_maxz = temp; n = CDR(n); } } SCP_vector targets; if (!new_sexp) { if (n >= 0) { for (; n >= 0;n = CDR(n)) { auto ship_entry = eval_ship(n); if (!ship_entry) continue; targets.push_back(ship_entry->name); } } } else { if (n >= 0) { auto list = get_list_valid_asteroid_subtypes(); for (; n >= 0;n = CDR(n)) { // Verify that it's a valid asteroid type for (const auto& item : list) { if (stricmp(CTEXT(n), item.c_str())) { continue; } } asteroid_types.emplace_back(CTEXT(n)); } } } if (num_asteroids > MAX_ASTEROIDS) { num_asteroids = MAX_ASTEROIDS; } vec3d o_min = vm_vec_new((float)o_minx, (float)o_miny, (float)o_minz); vec3d o_max = vm_vec_new((float)o_maxx, (float)o_maxy, (float)o_maxz); vec3d i_min = vm_vec_new((float)i_minx, (float)i_miny, (float)i_minz); vec3d i_max = vm_vec_new((float)i_maxx, (float)i_maxy, (float)i_maxz); asteroid_create_asteroid_field( num_asteroids, field_type, asteroid_speed, o_min, o_max, inner_box, i_min, i_max, std::move(asteroid_types)); if (!new_sexp) { Asteroid_field.target_names = std::move(targets); } } void sexp_set_debris_field(int n, bool new_sexp) { bool is_nan, is_nan_forever; int num_asteroids = 0, asteroid_speed = 0; for (int i = 0; i < 2; i++) { if (n >= 0) { int temp = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; if (i == 0) num_asteroids = temp; else asteroid_speed = temp; n = CDR(n); } } SCP_vector debris_types; if (!new_sexp) { if (n >= 0) { int debris1 = get_asteroid_index(CTEXT(n)); if (debris1 >= 0) { debris_types.push_back(debris1); } n = CDR(n); } if (n >= 0) { int debris2 = get_asteroid_index(CTEXT(n)); if (debris2 >= 0) { debris_types.push_back(debris2); } n = CDR(n); } if (n >= 0) { int debris3 = get_asteroid_index(CTEXT(n)); if (debris3 >= 0) { debris_types.push_back(debris3); } n = CDR(n); } } int o_minx = -1000, o_miny = -1000, o_minz = -1000; int o_maxx = 1000, o_maxy = 1000, o_maxz = 1000; for (int i = 0; i < 6; i++) { if (n >= 0) { int temp = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; if (i == 0) o_minx = temp; else if (i == 1) o_maxx = temp; else if (i == 2) o_miny = temp; else if (i == 3) o_maxy = temp; else if (i == 4) o_minz = temp; else o_maxz = temp; n = CDR(n); } } bool enhanced = false; if (n >= 0) { enhanced = is_sexp_true(n); n = CDR(n); } if (num_asteroids > MAX_ASTEROIDS) { num_asteroids = MAX_ASTEROIDS; } vec3d o_min = vm_vec_new((float)o_minx, (float)o_miny, (float)o_minz); vec3d o_max = vm_vec_new((float)o_maxx, (float)o_maxy, (float)o_maxz); if (new_sexp) { if (n >= 0) { for (; n >= 0; n = CDR(n)) { // Verify that it's a valid debris type auto idx = get_asteroid_index(CTEXT(n)); if (idx < 0) continue; debris_types.push_back(idx); } } } asteroid_create_debris_field( num_asteroids, asteroid_speed, std::move(debris_types), o_min, o_max, enhanced); } void sexp_config_field_targets(int n) { // Do nothing for disabled fields if (Asteroid_field.num_initial_asteroids == 0) { return; } // Do nothing for passive fields if (Asteroid_field.field_type == FT_PASSIVE) { return; } bool toggle = false; if (n >= 0) { toggle = is_sexp_true(n); n = CDR(n); } SCP_vector targets; if (n >= 0) { for (; n >= 0; n = CDR(n)) { auto ship_entry = eval_ship(n); if (!ship_entry) continue; targets.push_back(ship_entry->name); } } if (targets.size() == 0) { if (toggle) { // No ships provided to add so abort return; } else { Asteroid_field.target_names.clear(); return; } } else { if (toggle) { // Add the targets for (const auto& ship : targets) { // Check if the ship is already in the target_names if (std::find(Asteroid_field.target_names.begin(), Asteroid_field.target_names.end(), ship) == Asteroid_field.target_names.end()) { Asteroid_field.target_names.push_back(ship); } } } else { // Remove the targets for (const auto& ship : targets) { Asteroid_field.target_names.erase(std::remove(Asteroid_field.target_names.begin(), Asteroid_field.target_names.end(), ship), Asteroid_field.target_names.end()); } } } } void sexp_set_motion_debris_type(int n) { SCP_string name = CTEXT(n); if (!stricmp(name.c_str(), SEXP_NONE_STRING)) { Motion_debris_override = true; } else { Motion_debris_override = false; stars_load_debris(false, name); } } /** * End the mission. * * Implemented by Sesquipedalian; fixed by EdrickV; enhanced by others */ void sexp_end_mission(int n) { bool ignore_player_mortality = true; bool boot_to_main_hall = false; bool from_debrief_to_main_hall = false; if (n != -1) { ignore_player_mortality = is_sexp_true(n); n = CDR(n); } if (n != -1) { boot_to_main_hall = is_sexp_true(n); n = CDR(n); } if (n != -1) { from_debrief_to_main_hall = is_sexp_true(n); n = CDR(n); } // if the player is dead we may want to let the death screen handle things if (!ignore_player_mortality && (Player_ship->flags[Ship::Ship_Flags::Dying])) { return; } // ending via debrief and then going to mainhall could maybe work with multiplayer? The_mission.flags.set(Mission::Mission_Flags::End_to_mainhall, from_debrief_to_main_hall != 0); // if we go straight to the main hall we have to clean up the mission without entering the debriefing if (boot_to_main_hall && !(Game_mode & GM_MULTIPLAYER)) { gameseq_post_event(GS_EVENT_END_GAME); } else { send_debrief_event(); } Current_sexp_network_packet.do_callback(); } void multi_sexp_end_mission() { // Signal process_endgame_packet() to skip the warp-out sequence by entering // debrief immediately when the MISSION_END packet arrives. We cannot call // send_debrief_event() here directly: on standalone servers the client has not // yet received mission stats (m_okKills etc.) at this point, so // scoring_level_close() would run with zeroed kill counts and // Pilot.update_stats() would create no kill entries. debrief_close() would // then try to back out entries that were never created and hit an UNREACHABLE. // // send_endgame_packet() broadcasts stats to clients *before* sending MISSION_END, // so by the time process_endgame_packet() fires, the stats are already present // and it is safe to enter debrief there. Multi_sexp_end_mission_pending = true; multi_handle_end_mission_request(); } // Goober5000 void sexp_set_debriefing_toggled(int node) { The_mission.flags.set(Mission::Mission_Flags::Toggle_debriefing, is_sexp_true(node)); } // Goober5000 void sexp_set_debriefing_persona(int node) { bool is_nan, is_nan_forever; int persona = eval_num(node, is_nan, is_nan_forever); if (is_nan || is_nan_forever || persona < 0) return; The_mission.debriefing_persona = persona; } // MjnMixael void sexp_set_traitor_override(int node) { SCP_string override = CTEXT(node); if (!stricmp(override.c_str(), SEXP_NONE_STRING)) { The_mission.traitor_override = nullptr; }else{ The_mission.traitor_override = get_traitor_override_pointer(override); } } /** * Toggle the status for the AI code which tells the AI if it is a good or bad time to rearm. */ void sexp_good_bad_time_to_rearm(int n, bool good) { int team, time; bool is_nan, is_nan_forever; team = iff_lookup(CTEXT(n)); time = eval_num(CDR(n), is_nan, is_nan_forever); // this is the time for how long a good rearm is active -- in seconds if (is_nan || is_nan_forever) return; if (good) ai_set_good_rearm_time(team, time); else ai_set_bad_rearm_time(team, time); } /** * 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; } } /** * Gives the named medal to the players in the mission */ void sexp_grant_medal(int n) { int i; // don't give medals in normal gameplay when not in campaign mode if ( (Game_mode & GM_NORMAL) && !(Game_mode & GM_CAMPAIGN_MODE) ) return; auto medal_name = CTEXT(n); if (Player->stats.m_medal_earned >= 0) { Warning(LOCATION, "Cannot grant more than one medal per mission! New medal '%s' will replace old medal '%s'!", medal_name, Medals[Player->stats.m_medal_earned].name); } for (i = 0; i < (int)Medals.size(); i++) { if ( !stricmp(medal_name, Medals[i].name) ) break; } if (i < (int)Medals.size()) { Player->stats.m_medal_earned = i; if ( Game_mode & GM_MULTIPLAYER ) { for ( int j = 0; j < MAX_PLAYERS; j++ ) { if ( MULTI_CONNECTED(Net_players[j]) ) { Net_players[j].m_player->stats.m_medal_earned = i; } } } } } void sexp_change_player_score(int node) { int score; bool is_nan, is_nan_forever; score = eval_num(node, is_nan, is_nan_forever); node = CDR(node); if (is_nan || is_nan_forever) return; while (node >= 0) { auto player = get_player_from_ship_node(node, true); if (player) { player->stats.m_score += score; if (player->stats.m_score < 0) { player->stats.m_score = 0; } } else { if (!(Game_mode & GM_MULTIPLAYER)) { Warning(LOCATION, "Can not award points to '%s'. Ship is not a player!", CTEXT(node)); } } node = CDR(node); } } void sexp_change_team_score(int node) { int i, score, team; bool is_nan, is_nan_forever; // since we only have a team score in TvT if ( !(MULTI_TEAM) ) { return; } eval_nums(node, is_nan, is_nan_forever, score, team); if (is_nan || is_nan_forever) return; if (team == 0) { for (i = 0; i < MAX_TVT_TEAMS; i++) { Multi_team_score[i] += score; } } else if (team > 0 && team <= MAX_TVT_TEAMS) { Multi_team_score[team - 1] += score; } else { Warning(LOCATION, "Invalid team number. Team %d does not exist", team); } } void sexp_red_alert() { // 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 red_alert_start_mission(); Current_sexp_network_packet.do_callback(); } void multi_sexp_red_alert() { red_alert_start_mission(); } void sexp_tech_add_ship(int node) { // this function doesn't mean anything when not in campaign mode if ( !(Game_mode & GM_CAMPAIGN_MODE) ) return; while (node >= 0) { auto name = CTEXT(node); int i = ship_info_lookup(name); if (i >= 0) { if (Player && (Player->flags & PLAYER_FLAGS_IS_MULTI)) Ship_info[i].flags.set(Ship::Info_Flags::In_tech_database_m); else Ship_info[i].flags.set(Ship::Info_Flags::In_tech_database); } else Warning(LOCATION, "In tech-add-ship, ship class \"%s\" invalid", name); node = CDR(node); } } void sexp_tech_add_weapon(int node) { // this function doesn't mean anything when not in campaign mode if ( !(Game_mode & GM_CAMPAIGN_MODE) ) return; while (node >= 0) { auto name = CTEXT(node); int i = weapon_info_lookup(name); if (i >= 0) Weapon_info[i].wi_flags.set(Weapon::Info_Flags::In_tech_database); else Warning(LOCATION, "In tech-add-weapon, weapon class \"%s\" invalid", name); node = CDR(node); } } // Goober5000 // expanded with remove parameter by wookieejedi void sexp_tech_toggle_intel(int node, bool add, bool xstr) { int id, n = node; // this function doesn't mean anything when not in campaign mode if ( !(Game_mode & GM_CAMPAIGN_MODE) ) return; while (n >= 0) { // don't use things like CTEXT or eval_num, since we didn't in the preloader auto name = Sexp_nodes[n].text; n = CDR(n); // the xstr variant must have an id if (xstr) { if (n < 0) break; id = atoi(Sexp_nodes[n].text); n = CDR(n); } else id = -1; // if we have an xstr, we already translated this node in the preloader, so just look it up int i = intel_info_lookup(name); if (i >= 0) { if (add) { Intel_info[i].flags |= IIF_IN_TECH_DATABASE; } else { Intel_info[i].flags &= ~ IIF_IN_TECH_DATABASE; } } else if (xstr) Warning(LOCATION, "In tech-%s-intel-xstr, entry XSTR(\"%s\", %d) invalid", (add ? "add" : "remove"), name, id); else Warning(LOCATION, "In tech-%s-intel, entry \"%s\" invalid", (add ? "add" : "remove"), name); } } // Goober5000 - reset all the tech entries to their default states void sexp_tech_reset_to_default() { // this function doesn't mean anything when not in campaign mode if ( !(Game_mode & GM_CAMPAIGN_MODE) ) return; tech_reset_to_default(); } /** * Set variables needed to grant a new ship/weapon to the player during the course * of a mission */ void sexp_allow_ship(int n) { // 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 auto name = CTEXT(n); int 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) { // 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 auto name = CTEXT(n); int sindex = weapon_info_lookup( name ); if ( sindex == -1 ) return; // now we have a valid index -- mission_campaign_save_persistent( CAMPAIGN_PERSISTENT_WEAPON, sindex ); } /** * generic function for all those sexps that set flags * For all flag type parameters: If a particular flag should not be set, use the ::NUM_VALUES member of that enum * * @note this function has a similar purpose to sexp_alter_ship_flag_helper; make sure you check/update both */ void sexp_deal_with_ship_flag(int node, bool process_subsequent_nodes, Object::Object_Flags object_flag, Ship::Ship_Flags ship_flag, Mission::Parse_Object_Flags p_object_flag, bool set_it, bool send_multiplayer = false) { int n = node; if (send_multiplayer && MULTIPLAYER_MASTER) { Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_int((int)object_flag); Current_sexp_network_packet.send_int((int)ship_flag); Current_sexp_network_packet.send_int((int)p_object_flag); Current_sexp_network_packet.send_bool(set_it); } // loop for all ships in the sexp // NB: if the flag is set, we will continue acting on nodes until we run out of them; // if not, we will only act on the first one for (; n >= 0; process_subsequent_nodes ? n = CDR(n) : n = -1) { // get ship auto ship_entry = eval_ship(n); if (!ship_entry) continue; // check to see if ship destroyed or departed. In either case, do nothing. if (ship_entry->status == ShipStatus::EXITED) continue; // if ship is in-mission if (ship_entry->has_shipp()) { // save flags for state change comparisons auto object_flag_orig = ship_entry->objp()->flags; // see if we have an object flag to set if (object_flag != Object::Object_Flags::NUM_VALUES) { // set or clear? ship_entry->objp()->flags.set((Object::Object_Flags)object_flag, set_it); } // handle ETS when modifying shields if (object_flag == Object::Object_Flags::No_shields) { if (set_it) { zero_one_ets(&ship_entry->shipp()->shield_recharge_index, &ship_entry->shipp()->weapon_recharge_index, &ship_entry->shipp()->engine_recharge_index); ets_update_max_speed(ship_entry->objp()); } else if (object_flag_orig[Object::Object_Flags::No_shields]) { set_default_recharge_rates(ship_entry->objp()); } } // see if we have a ship flag to set if (ship_flag != Ship::Ship_Flags::NUM_VALUES) { // set or clear? ship_entry->shipp()->flags.set((Ship::Ship_Flags)ship_flag, set_it); } // the lock afterburner SEXP also needs to set a physics flag if (ship_flag == Ship::Ship_Flags::Afterburner_locked) { if (set_it) { afterburners_stop(ship_entry->objp(), 1); } } if (send_multiplayer && MULTIPLAYER_MASTER) { Current_sexp_network_packet.send_bool(true); Current_sexp_network_packet.send_ship(ship_entry->shipp()); } } // if it hasn't arrived yet else if (ship_entry->status == ShipStatus::NOT_YET_PRESENT) { // see if we have a p_object flag to set if (p_object_flag != Mission::Parse_Object_Flags::NUM_VALUES) { // set or clear? ship_entry->p_objp()->flags.set((Mission::Parse_Object_Flags)p_object_flag, set_it); } if (send_multiplayer && MULTIPLAYER_MASTER) { Current_sexp_network_packet.send_bool(false); Current_sexp_network_packet.send_parse_object(ship_entry->p_objp()); } } } if (send_multiplayer && MULTIPLAYER_MASTER) { Current_sexp_network_packet.end_callback(); } } void multi_sexp_deal_with_ship_flag() { int object_flag = (int)Object::Object_Flags::NUM_VALUES; int ship_flag = (int)Ship::Ship_Flags::NUM_VALUES; int p_object_flag = (int)Mission::Parse_Object_Flags::NUM_VALUES; bool set_it = false; bool ship_arrived = true; ship *shipp = nullptr; p_object *pobjp = nullptr; Current_sexp_network_packet.get_int(object_flag); Current_sexp_network_packet.get_int(ship_flag); Current_sexp_network_packet.get_int(p_object_flag); Current_sexp_network_packet.get_bool(set_it); // if any of the above failed so will this loop while (Current_sexp_network_packet.get_bool(ship_arrived)) { if (ship_arrived) { Current_sexp_network_packet.get_ship(shipp); if (shipp == nullptr) { WarningEx(LOCATION, "Null ship pointer in multi_sexp_deal_with_ship_flag(), tell a coder.\n"); return; } // save flags for state change comparisons auto object_flag_orig = Objects[shipp->objnum].flags; if (object_flag != (int)Object::Object_Flags::NUM_VALUES) { Objects[shipp->objnum].flags.set((Object::Object_Flags)object_flag, set_it); } if (ship_flag != (int)Ship::Ship_Flags::NUM_VALUES) { shipp->flags.set((Ship::Ship_Flags)ship_flag, set_it); } // deal with side effects of these flags if (object_flag == (int)Object::Object_Flags::No_shields) { if (set_it) { zero_one_ets(&shipp->shield_recharge_index, &shipp->weapon_recharge_index, &shipp->engine_recharge_index); ets_update_max_speed(&Objects[shipp->objnum]); } else if (object_flag_orig[Object::Object_Flags::No_shields]) { set_default_recharge_rates(&Objects[shipp->objnum]); } } if (ship_flag == (int)Ship::Ship_Flags::Afterburner_locked) { if (set_it) { afterburners_stop(&Objects[shipp->objnum], 1); } } if ((ship_flag == (int)Ship::Ship_Flags::Stealth) && !set_it) { if (shipp->flags[Ship::Ship_Flags::Escort]) { hud_add_ship_to_escort(shipp->objnum, 1); } } if ((ship_flag == (int)Ship::Ship_Flags::Friendly_stealth_invis) && !set_it && (shipp->flags[Ship::Ship_Flags::Stealth]) && (shipp->team == Player_ship->team)) { if (shipp->flags[Ship::Ship_Flags::Escort]) { hud_add_ship_to_escort(shipp->objnum, 1); } } if (ship_flag == (int)Ship::Ship_Flags::Hidden_from_sensors) { if (set_it) { if (Player_ai->target_objnum == shipp->objnum) { hud_cease_targeting(); } } else { if (shipp->flags[Ship::Ship_Flags::Escort]) { hud_add_ship_to_escort(shipp->objnum, 1); } } } } else { Current_sexp_network_packet.get_parse_object(pobjp); if ((pobjp != nullptr) && (p_object_flag != (int)Mission::Parse_Object_Flags::NUM_VALUES)) { pobjp->flags.set((Mission::Parse_Object_Flags)p_object_flag, set_it); } } } } /** * sets flags on objects from alter-ship-flag * * @note this function has a similar purpose to sexp_deal_with_ship_flag; make sure you check/update both */ void sexp_alter_ship_flag_helper(object_ship_wing_point_team &oswpt, bool future_ships, Object::Object_Flags object_flag, Ship::Ship_Flags ship_flag, Mission::Parse_Object_Flags parse_obj_flag, AI::AI_Flags ai_flag, bool set_flag) { int i; flagset object_flag_orig; switch (oswpt.type) { case OSWPT_TYPE_NONE: case OSWPT_TYPE_EXITED: return; case OSWPT_TYPE_WHOLE_TEAM: Assert (oswpt.team >= 0); for (auto so: list_range(&Ship_obj_list)) { if (Objects[so->objnum].flags[Object::Object_Flags::Should_be_dead]) continue; if (Ships[Objects[so->objnum].instance].team == oswpt.team) { // recurse object_ship_wing_point_team oswpt2(so); sexp_alter_ship_flag_helper(oswpt2, future_ships, object_flag, ship_flag, parse_obj_flag, ai_flag, set_flag); } } if (future_ships) { for (auto p_objp: list_range(&Ship_arrival_list)) { if (p_objp->team == oswpt.team) { // recurse object_ship_wing_point_team oswpt2(p_objp); sexp_alter_ship_flag_helper(oswpt2, future_ships, object_flag, ship_flag, parse_obj_flag, ai_flag, set_flag); } } } break; case OSWPT_TYPE_WING: case OSWPT_TYPE_WING_NOT_PRESENT: // if the wing isn't here, and we're only dealing with ships which are, we're done. if (!future_ships){ if (oswpt.type == OSWPT_TYPE_WING_NOT_PRESENT) { return; } } else { for (auto p_objp: list_range(&Ship_arrival_list)) { if (p_objp->wingnum == oswpt.wingnum) { // recurse object_ship_wing_point_team oswpt2(p_objp); sexp_alter_ship_flag_helper(oswpt2, future_ships, object_flag, ship_flag, parse_obj_flag, ai_flag, set_flag); } } } for (i = 0; i < oswpt.wingp()->current_count; i++) { // recurse object_ship_wing_point_team oswpt2(&Ships[oswpt.wingp()->ship_index[i]]); sexp_alter_ship_flag_helper(oswpt2, future_ships, object_flag, ship_flag, parse_obj_flag, ai_flag, set_flag); } break; // finally! If we actually have a ship, we can set its flags! case OSWPT_TYPE_SHIP: // save flags for state change comparisons object_flag_orig = oswpt.objp()->flags; // see if we have an object flag to set if (object_flag != Object::Object_Flags::NUM_VALUES) { auto tmp_flagset = oswpt.objp()->flags; // set or clear? tmp_flagset.set(object_flag, set_flag); obj_set_flags(oswpt.objp(), tmp_flagset); } // handle ETS when modifying shields if (object_flag == Object::Object_Flags::No_shields) { if (set_flag) { zero_one_ets(&oswpt.shipp()->shield_recharge_index, &oswpt.shipp()->weapon_recharge_index, &oswpt.shipp()->engine_recharge_index); ets_update_max_speed(oswpt.objp()); } else if (object_flag_orig[Object::Object_Flags::No_shields]) { set_default_recharge_rates(oswpt.objp()); } } // see if we have a ship flag to set if (ship_flag != Ship::Ship_Flags::NUM_VALUES) { // set or clear? oswpt.shipp()->flags.set((Ship::Ship_Flags)ship_flag, set_flag); } // the lock afterburner SEXP also needs to set a physics flag if (ship_flag == Ship::Ship_Flags::Afterburner_locked) { if (set_flag) { afterburners_stop(oswpt.objp(), 1); } } // special case: the "no_collide" parse object flag is the same, but opposite, as the "collides" object flag if (parse_obj_flag == Mission::Parse_Object_Flags::OF_No_collide) { auto tmp_flagset = oswpt.objp()->flags; tmp_flagset.set(Object::Object_Flags::Collides, !set_flag); obj_set_flags(oswpt.objp(), tmp_flagset); } // see if we have an ai flag to set if (ai_flag != AI::AI_Flags::NUM_VALUES) { // set or clear? Ai_info[oswpt.shipp()->ai_index].ai_flags.set(ai_flag, set_flag); } // no break statement. We want to fall through. FALLTHROUGH; case OSWPT_TYPE_PARSE_OBJECT: // only apply the flag to future ships if we want to and we are able to if (!future_ships || !oswpt.has_p_objp()) { return; } // special case: the "collides" object flag is the same, but opposite, as the "no_collide" parse object flag if (object_flag == Object::Object_Flags::Collides) { oswpt.p_objp()->flags.set(Mission::Parse_Object_Flags::OF_No_collide, !set_flag); } // see if we have a p_object flag to set if (parse_obj_flag != Mission::Parse_Object_Flags::NUM_VALUES) { oswpt.p_objp()->flags.set(parse_obj_flag, set_flag); } break; default: break; } } void alter_flag_for_all_ships(bool future_ships, Object::Object_Flags object_flag, Ship::Ship_Flags ship_flag, Mission::Parse_Object_Flags parse_obj_flag, AI::AI_Flags ai_flag, bool set_flag) { // set all the ships present in the mission for (auto so: list_range(&Ship_obj_list)) { if (Objects[so->objnum].flags[Object::Object_Flags::Should_be_dead]) continue; object_ship_wing_point_team oswpt(so); sexp_alter_ship_flag_helper(oswpt, future_ships, object_flag, ship_flag, parse_obj_flag, ai_flag, set_flag); } // set up all the ships which have yet to arrive if (future_ships) { for (auto p_objp: list_range(&Ship_arrival_list)) { object_ship_wing_point_team oswpt(p_objp); sexp_alter_ship_flag_helper(oswpt, future_ships, object_flag, ship_flag, parse_obj_flag, ai_flag, set_flag); } } } bool sexp_check_flag_arrays(const char *flag_name, Object::Object_Flags &object_flag, Ship::Ship_Flags &ship_flag, Mission::Parse_Object_Flags &parse_obj_flag, AI::AI_Flags &ai_flag) { size_t i; bool send_multi = false; for ( i = 0; i < (size_t)Num_object_flag_names; i++) { if (!stricmp(Object_flag_names[i].flag_name, flag_name)) { object_flag = Object_flag_names[i].flag; break; } } for ( i = 0; i < Num_ship_flag_names; i++) { if (!stricmp(Ship_flag_names[i].flag_name, flag_name)) { ship_flag = Ship_flag_names[i].flag; send_multi = true; break; } } // parse files already have a list of names in the same order as the flags, so we can do something slightly different here. for (i = 0; i < Num_parse_object_flags; i++) { if (!stricmp(Parse_object_flags[i].name, flag_name)) { parse_obj_flag = Parse_object_flags[i].def; break; } } for ( i = 0; i < (size_t)Num_ai_flag_names; i++) { if (!stricmp(Ai_flag_names[i].flag_name, flag_name)) { ai_flag = Ai_flag_names[i].flag; break; } } // check for deprecated flags if (!stricmp("cannot-perform-scan", flag_name)) { ship_flag = Ship::Ship_Flags::Cannot_perform_scan_hide_cargo; parse_obj_flag = Mission::Parse_Object_Flags::SF_Cannot_perform_scan_hide_cargo; send_multi = true; } return send_multi; } bool sexp_check_flag_array(const char *flag_name, Ship::Wing_Flags &wing_flag) { size_t i; bool send_multi = false; for ( i = 0; i < Num_wing_flag_names; i++) { if (!stricmp(Wing_flag_names[i].flag_name, flag_name)) { wing_flag = Wing_flag_names[i].flag; send_multi = true; break; } } return send_multi; } int sexp_are_ship_flags_set(int node) { Object::Object_Flags object_flag = Object::Object_Flags::NUM_VALUES; Ship::Ship_Flags ship_flag = Ship::Ship_Flags::NUM_VALUES; Mission::Parse_Object_Flags parse_obj_flag = Mission::Parse_Object_Flags::NUM_VALUES; AI::AI_Flags ai_flag = AI::AI_Flags::NUM_VALUES; auto ship_entry = eval_ship(node); if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; if (ship_entry->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; node = CDR(node); auto shipp = ship_entry->shipp(); auto objp = ship_entry->objp(); auto aip = &Ai_info[shipp->ai_index]; while (node != -1) { auto flag_name = CTEXT(node); sexp_check_flag_arrays(flag_name, object_flag, ship_flag, parse_obj_flag, ai_flag); // now check the flags if (object_flag != Object::Object_Flags::NUM_VALUES) { if (!(objp->flags[object_flag])) return SEXP_FALSE; } if (ship_flag != Ship::Ship_Flags::NUM_VALUES) { if (!(shipp->flags[ship_flag])) return SEXP_FALSE; } // we don't check parse flags, except for one that can be an object flag in reverse if (parse_obj_flag == Mission::Parse_Object_Flags::OF_No_collide) { if (objp->flags[Object::Object_Flags::Collides]) return SEXP_FALSE; } if (ai_flag != AI::AI_Flags::NUM_VALUES) { if (!(aip->ai_flags[ai_flag])) return SEXP_FALSE; } node = CDR(node); } // if we're still here, all the flags we were looking for were present return SEXP_TRUE; } int sexp_are_wing_flags_set(int node) { Ship::Wing_Flags wing_flag = Ship::Wing_Flags::NUM_VALUES; auto wingp = eval_wing(node); if (!wingp) return SEXP_NAN_FOREVER; node = CDR(node); while (node != -1) { auto flag_name = CTEXT(node); sexp_check_flag_array(flag_name, wing_flag); // now check the flags if (wing_flag != Ship::Wing_Flags::NUM_VALUES) { if (!(wingp->flags[wing_flag])) return SEXP_FALSE; } node = CDR(node); } // if we're still here, all the flags we were looking for were present return SEXP_TRUE; } int sexp_is_ship_emp_active(int n) { Assert(n >= 0); while (n != -1) { auto ship_entry = eval_ship(n); if (!ship_entry) return SEXP_FALSE; if (ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_CANT_EVAL; // see if the ship has already exited the mission (either through departure or destruction) if (ship_entry->exited_index >= 0) { return SEXP_KNOWN_FALSE; } // ship is in mission else if (ship_entry->has_shipp()) { if (ship_entry->shipp()->emp_intensity <= 0.0f) { return SEXP_FALSE; } } // ship probably vanished else return SEXP_NAN_FOREVER; n = CDR(n); } return SEXP_TRUE; } void sexp_alter_ship_flag(int node) { Object::Object_Flags object_flag = Object::Object_Flags::NUM_VALUES; Ship::Ship_Flags ship_flag = Ship::Ship_Flags::NUM_VALUES; Mission::Parse_Object_Flags parse_obj_flag = Mission::Parse_Object_Flags::NUM_VALUES; AI::AI_Flags ai_flag = AI::AI_Flags::NUM_VALUES; bool set_flag = false; bool future_ships = false; object_ship_wing_point_team oswpt; auto flag_name = CTEXT(node); sexp_check_flag_arrays(flag_name, object_flag, ship_flag, parse_obj_flag, ai_flag); node = CDR(node); if (is_sexp_true(node)) { set_flag = true; } node = CDR(node); if (is_sexp_true(node)) { future_ships = true; } // start the multiplayer packet Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_flag(object_flag); Current_sexp_network_packet.send_flag(ship_flag); Current_sexp_network_packet.send_flag(parse_obj_flag); Current_sexp_network_packet.send_flag(ai_flag); Current_sexp_network_packet.send_bool(set_flag); Current_sexp_network_packet.send_bool(future_ships); node = CDR(node); // no 4th argument means do this to every ship in the mission (and if the flag is set, every ship that will be too). if (node == -1) { // send a message to the clients saying there were no more arguments Current_sexp_network_packet.send_bool(false); alter_flag_for_all_ships(future_ships, object_flag, ship_flag, parse_obj_flag, ai_flag, set_flag); } else { // send a message to the clients saying there are more arguments Current_sexp_network_packet.send_bool(true); for (; node != -1; node = CDR(node)) { eval_object_ship_wing_point_team(&oswpt, node); // no point in setting these flags at all if (oswpt.type == OSWPT_TYPE_NONE || oswpt.type == OSWPT_TYPE_EXITED ) { continue; } sexp_alter_ship_flag_helper(oswpt, future_ships, object_flag, ship_flag, parse_obj_flag, ai_flag, set_flag); Current_sexp_network_packet.send_int((int)oswpt.type); switch (oswpt.type) { case OSWPT_TYPE_SHIP: Current_sexp_network_packet.send_ship(oswpt.shipp()); break; case OSWPT_TYPE_PARSE_OBJECT: Current_sexp_network_packet.send_parse_object(oswpt.p_objp()); break; case OSWPT_TYPE_WING_NOT_PRESENT: case OSWPT_TYPE_WING: Current_sexp_network_packet.send_ushort(oswpt.wingp()->net_signature); break; case OSWPT_TYPE_WHOLE_TEAM: Current_sexp_network_packet.send_int(oswpt.team); break; default: break; } } } Current_sexp_network_packet.end_callback(); } void multi_sexp_alter_ship_flag() { Object::Object_Flags object_flag = Object::Object_Flags::NUM_VALUES; Ship::Ship_Flags ship_flag = Ship::Ship_Flags::NUM_VALUES; Mission::Parse_Object_Flags parse_obj_flag = Mission::Parse_Object_Flags::NUM_VALUES; AI::AI_Flags ai_flag = AI::AI_Flags::NUM_VALUES; bool set_flag = false; bool future_ships = true; bool process_data = false; Current_sexp_network_packet.get_flag(object_flag); Current_sexp_network_packet.get_flag(ship_flag); Current_sexp_network_packet.get_flag(parse_obj_flag); Current_sexp_network_packet.get_flag(ai_flag); Current_sexp_network_packet.get_bool(set_flag); Current_sexp_network_packet.get_bool(future_ships); // if any of the above failed so will this loop if (!Current_sexp_network_packet.get_bool(process_data)) { return; } // no more data means do this to every ship in the mission if (!process_data) { alter_flag_for_all_ships(future_ships, object_flag, ship_flag, parse_obj_flag, ai_flag, set_flag); } else { int type; while (Current_sexp_network_packet.get_int(type)) { std::unique_ptr oswptp; switch ((oswpt_type)type) { case OSWPT_TYPE_SHIP: { ship *shipp; if (Current_sexp_network_packet.get_ship(shipp)) oswptp.reset(new object_ship_wing_point_team(shipp)); else Warning(LOCATION, "OSWPT had an invalid ship in multi_sexp_alter_ship_flag(), skipping"); break; } case OSWPT_TYPE_PARSE_OBJECT: { p_object *p_objp; if (Current_sexp_network_packet.get_parse_object(p_objp)) oswptp.reset(new object_ship_wing_point_team(p_objp)); else Warning(LOCATION, "OSWPT had an invalid parse object in multi_sexp_alter_ship_flag(), skipping"); break; } case OSWPT_TYPE_WING_NOT_PRESENT: case OSWPT_TYPE_WING: { wing *wingp; if (Current_sexp_network_packet.get_wing(wingp)) oswptp.reset(new object_ship_wing_point_team(wingp)); else Warning(LOCATION, "OSWPT had an invalid wing in multi_sexp_alter_ship_flag(), skipping"); break; } case OSWPT_TYPE_WHOLE_TEAM: { int team; if (Current_sexp_network_packet.get_int(team)) { oswptp.reset(new object_ship_wing_point_team()); oswptp->type = OSWPT_TYPE_WHOLE_TEAM; oswptp->team = team; } else Warning(LOCATION, "OSWPT had an invalid team in multi_sexp_alter_ship_flag(), skipping"); break; } default: break; } if (oswptp) sexp_alter_ship_flag_helper(*oswptp, future_ships, object_flag, ship_flag, parse_obj_flag, ai_flag, set_flag); } } } void sexp_alter_wing_flag(int node) { Ship::Wing_Flags wing_flag = Ship::Wing_Flags::NUM_VALUES; bool set_flag = false; auto flag_name = CTEXT(node); node = CDR(node); sexp_check_flag_array(flag_name, wing_flag); if (is_sexp_true(node)) set_flag = true; node = CDR(node); // start the multiplayer packet Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_flag(wing_flag); Current_sexp_network_packet.send_bool(set_flag); // no 3rd argument means do this to every wing in the mission if (node < 0) { // send a message to the clients saying there were no more arguments Current_sexp_network_packet.send_bool(false); for (int i = 0; i < Num_wings; i++) Wings[i].flags.set(wing_flag, set_flag); } else { // send a message to the clients saying there are more arguments Current_sexp_network_packet.send_bool(true); for (; node != -1; node = CDR(node)) { auto wingp = eval_wing(node); if (!wingp) continue; wingp->flags.set(wing_flag, set_flag); Current_sexp_network_packet.send_wing(wingp); } } Current_sexp_network_packet.end_callback(); } void multi_sexp_alter_wing_flag() { Ship::Wing_Flags wing_flag = Ship::Wing_Flags::NUM_VALUES; bool set_flag = false; bool process_data = false; Current_sexp_network_packet.get_flag(wing_flag); Current_sexp_network_packet.get_bool(set_flag); // if any of the above failed so will this loop if (!Current_sexp_network_packet.get_bool(process_data)) { return; } // no more data means do this to every wing in the mission if (!process_data) { for (int i = 0; i < Num_wings; i++) Wings[i].flags.set(wing_flag, set_flag); } else { wing *wingp; while (Current_sexp_network_packet.get_wing(wingp)) { if (!wingp) continue; wingp->flags.set(wing_flag, set_flag); } } } // modified by Goober5000; now it should work properly // function to deal with breaking/fixing the warp engines on ships/wings. // --repairable is true when we are breaking the warp drive (can be repaired) // --damage_it is true when we are sabotaging it, one way or the other; false when fixing it void sexp_deal_with_warp(int n, bool repairable, bool damage_it) { Ship::Ship_Flags ship_flag; Mission::Parse_Object_Flags p_object_flag; if (repairable) { ship_flag = Ship::Ship_Flags::Warp_broken; p_object_flag = Mission::Parse_Object_Flags::SF_Warp_broken; } else { ship_flag = Ship::Ship_Flags::Warp_never; p_object_flag = Mission::Parse_Object_Flags::SF_Warp_never; } sexp_deal_with_ship_flag(n, true, Object::Object_Flags::NUM_VALUES, ship_flag, p_object_flag, damage_it); } // Goober5000 void sexp_set_subspace_drive(int node) { bool set_flag = !is_sexp_true(node); sexp_deal_with_ship_flag(CDR(node), true, Object::Object_Flags::NUM_VALUES, Ship::Ship_Flags::No_subspace_drive, Mission::Parse_Object_Flags::NUM_VALUES, set_flag); } //forward declaration extern void ai_set_or_clear_preferred_primary_weapon(bool set_it, const object_ship_wing_point_team *subject, const object_ship_wing_point_team *target, int weapon_idx); void sexp_good_primary_time(int n) { object_ship_wing_point_team subject; eval_object_ship_wing_point_team(&subject, n); //if we don't get a ship, wing, or team, bail if (subject.type != oswpt_type::SHIP && subject.type != oswpt_type::WING && subject.type != oswpt_type::WHOLE_TEAM) { return; } n = CDR(n); auto weapon_name = CTEXT(n); int weapon_index = weapon_info_lookup(weapon_name); if (weapon_index < 0) { nprintf(("Warning", "couldn't find weapon %s for good-secondary-time\n", weapon_name)); return; } n = CDR(n); object_ship_wing_point_team target; eval_object_ship_wing_point_team(&target, n); if (target.type != oswpt_type::SHIP && target.type != oswpt_type::WING && target.type != oswpt_type::WHOLE_TEAM) { return; } n = CDR(n); auto activate = is_sexp_true(n); ai_set_or_clear_preferred_primary_weapon(activate, &subject, &target, weapon_index); } /** * Tell the AI when it is okay to fire certain secondary weapons at other ships. */ void sexp_good_secondary_time(int n) { bool is_nan, is_nan_forever; auto team_name = CTEXT(n); int team = iff_lookup(team_name); if (team < 0) { nprintf(("Warning", "couldn't find team %s for good-secondary-time\n", team_name)); return; } n = CDR(n); int num_weapons = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; n = CDR(n); auto weapon_name = CTEXT(n); int weapon_index = weapon_info_lookup(weapon_name); if (weapon_index < 0) { nprintf(("Warning", "couldn't find weapon %s for good-secondary-time\n", weapon_name)); return; } n = CDR(n); auto ship_entry = eval_ship(n); if (!ship_entry) return; // see if the ship has exited. If so, then we don't need to set up the AI stuff if (ship_entry->status == ShipStatus::EXITED) return; ai_good_secondary_time( team, weapon_index, num_weapons, ship_entry->name ); } // Karajorma - Turns the built in messages for pilots and command on or off void sexp_toggle_builtin_messages (int node, bool enable_messages) { // If no arguments were supplied then turn off all messages then bail if (node < 0) { The_mission.flags.set(Mission::Mission_Flags::No_builtin_msgs, !enable_messages); return; } // iterate through all the nodes supplied while (node >= 0) { auto ship_name = CTEXT(node); // check that this isn't a request to silence command. if ( ((*ship_name == '#') && !stricmp(&ship_name[1], The_mission.command_sender)) || (The_mission.flags[Mission::Mission_Flags::Override_hashcommand] && !stricmp(ship_name, DEFAULT_HASHCOMMAND)) ) { // Either disable or enable messages from command The_mission.flags.set(Mission::Mission_Flags::No_builtin_command, !enable_messages); } else if (!stricmp(ship_name, "")) { // Since trying to determine whose wingman in a stand alone multiplayer game opens a can of worms // Any Wingman silences all ships in wings regardless of whose side they're on. for (int wingnum = 0; wingnum < Num_wings; ++wingnum) { for (int shipnum = 0; shipnum < Wings[wingnum].current_count; ++shipnum) { int ship_index = Wings[wingnum].ship_index[shipnum]; Assert( ship_index != -1 ); Ships[ship_index].flags.set(Ship::Ship_Flags::No_builtin_messages, !enable_messages); } } } // If it isn't command then assume that we're dealing with a ship else { sexp_deal_with_ship_flag(node, false, Object::Object_Flags::NUM_VALUES, Ship::Ship_Flags::No_builtin_messages, Mission::Parse_Object_Flags::SF_No_builtin_messages, !enable_messages); } node = CDR(node); } } void sexp_set_persona (int node) { int persona_index = -1; auto persona_name = CTEXT(node); for (int i = 0; i < (int)Personas.size(); ++i) { if (!strcmp(persona_name, Personas[i].name) && (Personas[i].flags & PERSONA_FLAG_WINGMAN)) { persona_index = i; break; } } if (persona_index == -1) { Warning(LOCATION, "Unable to change to persona type: '%s'. Persona is not a wingman!", persona_name); return; } node = CDR(node); Assert (node >=0); if (MULTIPLAYER_MASTER) { Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_int(persona_index); } // now loop through the list of ships for ( ; node >= 0; node = CDR(node) ) { auto ship_entry = eval_ship(node); if (!ship_entry || !ship_entry->has_shipp()) { continue; } ship_entry->shipp()->persona_index = persona_index; if (MULTIPLAYER_MASTER) { Current_sexp_network_packet.send_ship(ship_entry->shipp()); } } if (MULTIPLAYER_MASTER) { Current_sexp_network_packet.end_callback(); } } void multi_sexp_set_persona() { ship *shipp = nullptr; int persona_index = -1; if (! Current_sexp_network_packet.get_int(persona_index) ) { return; } while (Current_sexp_network_packet.get_ship(shipp)) { Assert(persona_index != -1); if (shipp != nullptr) { shipp->persona_index = persona_index; } } } void sexp_set_mission_mood (int node) { auto mood = CTEXT(node); for (SCP_vector::iterator iter = Builtin_moods.begin(); iter != Builtin_moods.end(); ++iter) { if (!strcmp(iter->c_str(), mood)) { Current_mission_mood = (int)std::distance(Builtin_moods.begin(), iter); return; } } Warning(LOCATION, "Sexp-mission-mood attempted to set mood %s which does not exist in messages.tbl", mood); } int sexp_turret_fired_delay(int node) { bool is_nan, is_nan_forever; auto ship_entry = eval_ship(node); if (!ship_entry) { return SEXP_FALSE; } if (ship_entry->status == ShipStatus::EXITED) { return SEXP_KNOWN_FALSE; } if (ship_entry->status == ShipStatus::NOT_YET_PRESENT) { return SEXP_CANT_EVAL; } auto shipp = ship_entry->shipp(); // get the subsystem node = CDR(node); auto turret = ship_get_subsys(shipp, CTEXT(node)); if (!turret || turret->system_info->type != SUBSYSTEM_TURRET) return SEXP_KNOWN_FALSE; // get the delay node = CDR(node); int delay = eval_num(node, is_nan, is_nan_forever); if (is_nan_forever) { return SEXP_KNOWN_FALSE; } else if (is_nan || delay <= 0) { return SEXP_FALSE; } if (turret->turret_last_fired.isNever()) { // weapon was never fired return SEXP_FALSE; } if (timestamp_since(turret->turret_last_fired) < delay) { return SEXP_TRUE; } return SEXP_FALSE; } int sexp_weapon_fired_delay(int node, int op_num) { int requested_bank, delay, last_fired = -1; bool is_nan, is_nan_forever; auto ship_entry = eval_ship(node); if (!ship_entry) { return SEXP_FALSE; } if (ship_entry->status == ShipStatus::EXITED) { return SEXP_KNOWN_FALSE; } if (ship_entry->status == ShipStatus::NOT_YET_PRESENT) { return SEXP_CANT_EVAL; } auto shipp = ship_entry->shipp(); // Get the bank to check node = CDR(node); requested_bank = eval_num(node, is_nan, is_nan_forever); if (is_nan_forever) { return SEXP_KNOWN_FALSE; } else if (is_nan || requested_bank < 0) { return SEXP_FALSE; } // get the delay node = CDR(node); delay = eval_num(node, is_nan, is_nan_forever); if (is_nan_forever) { return SEXP_KNOWN_FALSE; } else if (is_nan || delay <= 0) { return SEXP_FALSE; } switch (op_num) { case OP_PRIMARY_FIRED_SINCE: if (requested_bank >= shipp->weapons.num_primary_banks) { return SEXP_FALSE; } last_fired = shipp->weapons.last_primary_fire_stamp[requested_bank]; break; case OP_SECONDARY_FIRED_SINCE: if (requested_bank >= shipp->weapons.num_secondary_banks) { return SEXP_FALSE; } last_fired = shipp->weapons.last_secondary_fire_stamp[requested_bank]; break; } if (last_fired < 0) { // weapon was never fired return SEXP_FALSE; } if (timestamp() - delay < last_fired) { return SEXP_TRUE; } return SEXP_FALSE; } int sexp_has_weapon(int node, int op_num) { // Get the ship to check auto ship_entry = eval_ship(node); if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; if (ship_entry->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; node = CDR(node); auto shipp = ship_entry->shipp(); ship_subsys *turret = nullptr; switch (op_num){ case OP_TURRET_HAS_PRIMARY_WEAPON: case OP_TURRET_HAS_SECONDARY_WEAPON: // get the subsystem turret = ship_get_subsys(shipp, CTEXT(node)); if (!turret || turret->system_info->type != SUBSYSTEM_TURRET) return SEXP_NAN_FOREVER; node = CDR(node); break; default: break; } // Get the bank to check int requested_bank; bool is_nan, is_nan_forever; if (!strcmp(CTEXT(node), SEXP_ALL_BANKS_STRING)) { requested_bank = -1; } else { requested_bank = eval_num(node, is_nan, is_nan_forever); if (is_nan) { return SEXP_FALSE; } if (is_nan_forever) { return SEXP_KNOWN_FALSE; } } node = CDR(node); // Get the banks of the ship or turret int num_weapon_banks = 0; int* weapon_banks = nullptr; switch (op_num) { case OP_HAS_PRIMARY_WEAPON: weapon_banks = shipp->weapons.primary_bank_weapons; num_weapon_banks = shipp->weapons.num_primary_banks; break; case OP_HAS_SECONDARY_WEAPON: weapon_banks = shipp->weapons.secondary_bank_weapons; num_weapon_banks = shipp->weapons.num_secondary_banks; break; case OP_TURRET_HAS_PRIMARY_WEAPON: weapon_banks = turret->weapons.primary_bank_weapons; num_weapon_banks = turret->weapons.num_primary_banks; break; case OP_TURRET_HAS_SECONDARY_WEAPON: weapon_banks = turret->weapons.secondary_bank_weapons; num_weapon_banks = turret->weapons.num_secondary_banks; break; default: Warning(LOCATION, "Unrecognised bank type used in has-x-weapon. Returning false"); return SEXP_FALSE; } //loop through the weapons and test them int weapon_index; while (node > -1) { weapon_index = weapon_info_lookup(CTEXT(node)); Assertion (weapon_index >= 0, "Weapon name %s is unknown.", CTEXT(node)); // if we're checking every bank if (requested_bank == -1) { for (int i = 0; i < num_weapon_banks; i++) { if (weapon_index == weapon_banks[i]) { return SEXP_TRUE; } } } // if we're only checking one bank else { if (weapon_index == weapon_banks[requested_bank]) { return SEXP_TRUE; } } node = CDR(node); } return SEXP_FALSE; } /** * Gets status of goals for previous missions (in the current campaign). * * @param n Sexp node number * @param status 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 ) { int rval = 0; int i, mission_num; bool default_value = false, use_defaults = true; auto mission_name = CTEXT(n); auto goal_name = CTEXT(CDR(n)); // check for possible next optional argument n = CDR(CDR(n)); if ( n != -1 ) { default_value = is_sexp_true(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.\n", mission_name, (status==GOAL_COMPLETE)?"false":"true")); if ( status == GOAL_COMPLETE ) rval = SEXP_KNOWN_FALSE; else rval = SEXP_KNOWN_TRUE; use_defaults = false; } else if (Campaign.missions[i].flags & CMISSION_FLAG_SKIPPED) { use_defaults = true; } else { // now try and find the goal this mission mission_num = i; for (i = 0; i < (int)Campaign.missions[mission_num].goals.size(); i++) { if ( !stricmp(Campaign.missions[mission_num].goals[i].name, goal_name) ) break; } if ( i == (int)Campaign.missions[mission_num].goals.size() ) { 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 = false; } } 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, EventStatus status ) { int rval = 0; int i, mission_num; bool default_value = false, use_defaults = true; auto mission_name = CTEXT(n); auto name = CTEXT(CDR(n)); // check for possible optional parameter n = CDR(CDR(n)); if ( n != -1 ){ default_value = is_sexp_true(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.\n", mission_name, (status==EventStatus::SATISFIED)?"false":"true")); if ( status == EventStatus::SATISFIED ) { rval = SEXP_KNOWN_FALSE; } else { rval = SEXP_KNOWN_TRUE; } use_defaults = false; } else if (Campaign.missions[i].flags & CMISSION_FLAG_SKIPPED) { use_defaults = true; } else { // now try and find the goal this mission mission_num = i; for (i = 0; i < (int)Campaign.missions[mission_num].events.size(); i++) { if ( !stricmp(Campaign.missions[mission_num].events[i].name, name) ) break; } if ( i == (int)Campaign.missions[mission_num].events.size() ) { Warning(LOCATION, "Couldn't find event name \"%s\" in mission %s.\nReturning %s for event_status function.", name, mission_name, (status==EventStatus::SATISFIED)?"false":"true"); if ( status == EventStatus::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 == static_cast(status) ) rval = SEXP_KNOWN_TRUE; else rval = SEXP_KNOWN_FALSE; } use_defaults = false; } } if (use_defaults) { if ( n != -1 ) { if ( default_value ) rval = SEXP_KNOWN_TRUE; else rval = SEXP_KNOWN_FALSE; } else { if ( status == EventStatus::SATISFIED ) rval = SEXP_KNOWN_TRUE; else rval = SEXP_KNOWN_FALSE; } } return rval; } /** * Return the status of an event in the current mission. * * @param n Sexp node number * @param want_true indicates if we are checking whether the event is true or the event is false. */ int sexp_event_status( int n, int want_true ) { int rval = SEXP_FALSE; auto name = CTEXT(n); for (int i = 0; i < (int)Mission_events.size(); ++i) { // look for the event name; check its status. If formula is gone, we know the state won't ever change. if ( !stricmp(Mission_events[i].name.c_str(), name) ) { int result = Mission_events[i].result; if (Mission_events[i].flags & MEF_EVENT_IS_DONE) { if ( (want_true && result) || (!want_true && !result) ) rval = SEXP_KNOWN_TRUE; else rval = SEXP_KNOWN_FALSE; } else { if ( (want_true && result) || (!want_true && !result) ) rval = SEXP_TRUE; else rval = SEXP_FALSE; } break; } } // don't make the enclosing event current if this operator doesn't return true if ((rval != SEXP_TRUE) && (rval != SEXP_KNOWN_TRUE)) Assume_event_is_current = false; // indicate sexp isn't current yet return rval; } /** * 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, bool use_msecs = false) { bool is_nan, is_nan_forever; int delay; int rval = SEXP_FALSE; bool use_as_directive = false; auto name = CTEXT(n); delay = eval_num(CDR(n), is_nan, is_nan_forever); if (is_nan) { Assume_event_is_current = false; // indicate sexp isn't current yet return SEXP_FALSE; } else if (is_nan_forever) { Assume_event_is_current = false; // indicate sexp isn't current yet return SEXP_KNOWN_FALSE; } if (!use_msecs) { delay *= MILLISECONDS_PER_SECOND; } for (int i = 0; i < (int)Mission_events.size(); ++i) { // look for the event name; check its status. If formula is gone, we know the state won't ever change. if ( !stricmp(Mission_events[i].name.c_str(), name) ) { // deduct the interval using the same logic as in mission_process_event() if (Mission_events[i].flags & MEF_TIMESTAMP_HAS_INTERVAL) { if (Mission_events[i].flags & MEF_USE_MSECS) { delay -= Mission_events[i].interval; } else { delay -= Mission_events[i].interval * MILLISECONDS_PER_SECOND; } } // Events that have never fired are not subject to the delay check. This matches the behavior of the // original public source code release and also allows checks for these events to work in debriefings. // Addendum: Also do not check the delay if we're not in-mission. This allows debriefings to check // events that have fired on the same timestamp that the mission ends. These events would otherwise // not be checked due to the "one frame must elapse" rule. if (!Mission_events[i].timestamp.isValid() || !(Game_mode & GM_IN_MISSION)) { /* do not set rval */; } // Check that the event delay has elapsed, again using the same logic as in mission_process_event() else if (!Fixed_chaining_to_repeat && Mission_events[i].flags & MEF_TIMESTAMP_HAS_INTERVAL) { /* do not set rval */; } // Note that if the event and the timestamp happen simultaneously, at least one frame must elapse first; // this matches the delay check in the original public source code release else if (!timestamp_elapsed_last_frame(timestamp_delta(Mission_events[i].timestamp, delay))) { rval = SEXP_FALSE; break; } int result = Mission_events[i].result; if (Mission_events[i].flags & MEF_EVENT_IS_DONE) { if ( (want_true && result) || (!want_true && !result) ) { rval = SEXP_KNOWN_TRUE; break; } else { rval = SEXP_KNOWN_FALSE; break; } } else { if ( want_true && result ) { //) || (!want_true && !result) ) rval = SEXP_TRUE; break; } else { rval = SEXP_FALSE; break; } } } } // check for possible optional parameter n = CDDR(n); if (n != -1) use_as_directive = is_sexp_true(n); // don't make the enclosing event current if this operator doesn't return true and we don't want this for specific directive use if ( !use_as_directive && (rval != SEXP_TRUE) && (rval != SEXP_KNOWN_TRUE) ) Assume_event_is_current = false; // indicate sexp isn't current yet return rval; } /** * Returns true if the given event is still incomplete */ int sexp_event_incomplete(int n) { int rval = SEXP_FALSE; auto name = CTEXT(n); for (int i = 0; i < (int)Mission_events.size(); ++i) { if ( !stricmp(Mission_events[i].name.c_str(), name ) ) { // if the formula is still >= 0 (meaning it is still getting eval'ed), then // the event is incomplete // Goober5000 - check the flag instead if (!(Mission_events[i].flags & MEF_EVENT_IS_DONE)) rval = SEXP_TRUE; else rval = SEXP_KNOWN_FALSE; break; } } // don't make the enclosing event current if this operator doesn't return true if ((rval != SEXP_TRUE) && (rval != SEXP_KNOWN_TRUE)) Assume_event_is_current = false; // indicate sexp isn't current yet return rval; } /** * 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 ) { fix delay, time; bool is_nan, is_nan_forever; auto name = CTEXT(n); delay = i2f(eval_num(CDR(n), is_nan, is_nan_forever)); if (is_nan) { return SEXP_FALSE; } else if (is_nan_forever) { return SEXP_KNOWN_FALSE; } 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, nullptr, nullptr) ) return SEXP_KNOWN_FALSE; else if ( mission_log_get_time(LOG_GOAL_SATISFIED, name, nullptr, &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, nullptr, nullptr) ) return SEXP_KNOWN_FALSE; else if ( mission_log_get_time(LOG_GOAL_FAILED, name, nullptr, &time) ) { if ( (Missiontime - time) >= delay ) return SEXP_KNOWN_TRUE; } } return SEXP_FALSE; } /** * Returns true if the given goal is still incomplete */ int sexp_goal_incomplete(int n) { auto name = CTEXT(n); if ( mission_log_get_time( LOG_GOAL_SATISFIED, name, nullptr, nullptr) || mission_log_get_time( LOG_GOAL_FAILED, name, nullptr, nullptr) ) return SEXP_KNOWN_FALSE; else return SEXP_TRUE; } /** * Resets an event, its status, and its nodes so that it is as if the event had never been evaluated. */ void sexp_reset_event(int node) { for (int n = node; n >= 0; n = CDR(n)) { auto name = CTEXT(n); int event_num = mission_event_lookup(name); if (event_num >= 0) { auto eventp = &Mission_events[event_num]; eventp->result = 0; eventp->timestamp = TIMESTAMP::invalid(); // clear the flags that are changed over the course of the event's lifetime, // leaving alone the flags that define how the event is configured eventp->flags &= ~MEF_CURRENT; eventp->flags &= ~MEF_DIRECTIVE_SPECIAL; eventp->flags &= ~MEF_DIRECTIVE_TEMP_TRUE; eventp->flags &= ~MEF_TIMESTAMP_HAS_INTERVAL; eventp->flags &= ~MEF_EVENT_IS_DONE; eventp->count = 0; eventp->satisfied_time = TIMESTAMP::invalid(); eventp->born_on_date = TIMESTAMP::invalid(); eventp->previous_result = 0; flush_sexp_tree(eventp->formula); } else Warning(LOCATION, "Could not find event '%s'", name); } } /** * Resets a goal, its status, and its nodes so that it is as if the goal had never been evaluated. */ void sexp_reset_goal(int node) { for (int n = node; n >= 0; n = CDR(n)) { auto name = CTEXT(n); int goal_num = mission_goal_lookup(name); if (goal_num >= 0) { auto goalp = &Mission_goals[goal_num]; goalp->satisfied = GOAL_INCOMPLETE; flush_sexp_tree(goalp->formula); } else Warning(LOCATION, "Could not find goal '%s'", name); } } /** * Protects/unprotects a ship. * * @param n Sexp node number * @param flag Whether or not the protect bit should be set (flag==true) or cleared (flag==false) */ void sexp_protect_ships(int n, bool flag) { sexp_deal_with_ship_flag(n, true, Object::Object_Flags::Protected, Ship::Ship_Flags::NUM_VALUES, Mission::Parse_Object_Flags::OF_Protected, flag); } /** * Protects/unprotects a ship from beams. * * @param n Sexp node number * @param flag Whether or not the protect bit should be set (flag==true) or cleared (flag==false) */ void sexp_beam_protect_ships(int n, bool flag) { sexp_deal_with_ship_flag(n, true, Object::Object_Flags::Beam_protected, Ship::Ship_Flags::NUM_VALUES, Mission::Parse_Object_Flags::OF_Beam_protected, flag); } /** * Protects/unprotects a ship from various turrets. * * @param n Sexp node number * @param flag Whether or not the protect bit should be set (flag==true) or cleared (flag==false) */ void sexp_turret_protect_ships(int n, bool flag) { auto turret_type = CTEXT(n); n = CDR(n); if (!stricmp(turret_type, "beam")) sexp_deal_with_ship_flag(n, true, Object::Object_Flags::Beam_protected, Ship::Ship_Flags::NUM_VALUES, Mission::Parse_Object_Flags::OF_Beam_protected, flag); else if (!stricmp(turret_type, "flak")) sexp_deal_with_ship_flag(n, true, Object::Object_Flags::Flak_protected, Ship::Ship_Flags::NUM_VALUES, Mission::Parse_Object_Flags::OF_Flak_protected, flag); else if (!stricmp(turret_type, "laser")) sexp_deal_with_ship_flag(n, true, Object::Object_Flags::Laser_protected, Ship::Ship_Flags::NUM_VALUES, Mission::Parse_Object_Flags::OF_Laser_protected, flag); else if (!stricmp(turret_type, "missile")) sexp_deal_with_ship_flag(n, true, Object::Object_Flags::Missile_protected, Ship::Ship_Flags::NUM_VALUES, Mission::Parse_Object_Flags::OF_Missile_protected, flag); else Warning(LOCATION, "Invalid turret type '%s'!", turret_type); } // Goober5000 - sets the "don't collide invisible" flag on a list of ships void sexp_dont_collide_invisible(int n, bool dont_collide) { sexp_deal_with_ship_flag(n, true, Object::Object_Flags::NUM_VALUES, Ship::Ship_Flags::Dont_collide_invis, Mission::Parse_Object_Flags::SF_Dont_collide_invis, dont_collide); } // Goober5000 - sets the "immobile" flag on a list of ships; also always clears "don't-change-position" and "don't-change-orientation" to avoid conflicts void sexp_set_immobile(int n, bool immobile) { sexp_deal_with_ship_flag(n, true, Object::Object_Flags::Immobile, Ship::Ship_Flags::NUM_VALUES, Mission::Parse_Object_Flags::OF_Immobile, immobile); sexp_deal_with_ship_flag(n, true, Object::Object_Flags::Dont_change_position, Ship::Ship_Flags::NUM_VALUES, Mission::Parse_Object_Flags::OF_Dont_change_position, false); sexp_deal_with_ship_flag(n, true, Object::Object_Flags::Dont_change_orientation, Ship::Ship_Flags::NUM_VALUES, Mission::Parse_Object_Flags::OF_Dont_change_orientation, false); } // Goober5000 - sets the "no-ets" flag on a list of ships void sexp_disable_ets(int n, bool disable) { sexp_deal_with_ship_flag(n, true, Object::Object_Flags::NUM_VALUES, Ship::Ship_Flags::No_ets, Mission::Parse_Object_Flags::SF_No_ets, disable); } // Goober5000 - sets the vaporize flag on a list of ships void sexp_ships_vaporize(int n, bool vaporize) { sexp_deal_with_ship_flag(n, true, Object::Object_Flags::NUM_VALUES, Ship::Ship_Flags::Vaporize, Mission::Parse_Object_Flags::SF_Vaporize, vaporize); } /** * Make ships "visible" and "invisible" to sensors. * * @param n Sexp node number * @param visible Is true when making ships visible, false otherwise */ void sexp_ships_visible(int n, bool visible) { sexp_deal_with_ship_flag(n, true, Object::Object_Flags::NUM_VALUES, Ship::Ship_Flags::Hidden_from_sensors, Mission::Parse_Object_Flags::SF_Hidden_from_sensors, !visible, true); // we also have to add any escort ships that were made visible for (; n >= 0; n = CDR(n)) { auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) continue; if (!visible && Player_ai->target_objnum == ship_entry->objnum) { hud_cease_targeting(); } else if (visible && (ship_entry->shipp()->flags[Ship::Ship_Flags::Escort])) { hud_add_ship_to_escort(ship_entry->objnum, 1); } } } // Goober5000 void sexp_ships_stealthy(int n, bool stealthy) { sexp_deal_with_ship_flag(n, true, Object::Object_Flags::NUM_VALUES, Ship::Ship_Flags::Stealth, Mission::Parse_Object_Flags::SF_Stealth, stealthy, true); // we also have to add any escort ships that were made visible if (!stealthy) { for (; n >= 0; n = CDR(n)) { auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) continue; if (ship_entry->shipp()->flags[Ship::Ship_Flags::Escort]) hud_add_ship_to_escort(ship_entry->objnum, 1); } } } // Goober5000 void sexp_friendly_stealth_invisible(int n, bool invisible) { sexp_deal_with_ship_flag(n, true, Object::Object_Flags::NUM_VALUES, Ship::Ship_Flags::Friendly_stealth_invis, Mission::Parse_Object_Flags::SF_Friendly_stealth_invis, invisible, true); // we also have to add any escort ships that were made visible if (!invisible) { for (; n >= 0; n = CDR(n)) { auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) continue; if (ship_entry->shipp()->flags[Ship::Ship_Flags::Stealth] && Player_ship->team == ship_entry->shipp()->team) { if (ship_entry->shipp()->flags[Ship::Ship_Flags::Escort]) hud_add_ship_to_escort(ship_entry->objnum, 1); } } } } //FUBAR //generic function to deal with subsystem flag sexps. //setit only passed for backward compatibility with older sexps. void sexp_ship_deal_with_subsystem_flag(int op_node, int node, Ship::Subsystem_Flags ss_flag, bool sendit = false, bool setit = false) { // get ship auto ship_entry = eval_ship(node); if (!ship_entry || !ship_entry->has_shipp()) { return; } auto shipp = ship_entry->shipp(); node = CDR(node); //replace or not // OP_SHIP_SUBSYS_TARGETABLE/UNTARGETABLE, OP_SHIP_SUBSYS_TARGETABLE and OP_TURRET_SUBSYS_TARGET_ENABLE/DISABLE // will have already passed us this data we don't need to set it for them. // backward compatibility hack for older sexps if (!((ss_flag == Ship::Subsystem_Flags::Untargetable) || (ss_flag == Ship::Subsystem_Flags::No_SS_targeting))) { setit = is_sexp_true(node); node = CDR(node); } //multiplayer packet start if (sendit) { Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_ship(shipp); Current_sexp_network_packet.send_bool(setit); } //Process subsystems const char *op_name = Sexp_nodes[op_node].text; process_ship_subsystems(ship_entry, true, node, true, op_name, [&](ProcessSubsystemType type, ship_subsys *ss) { if (type == ProcessSubsystemType::SUBSYSTEM) { ss->flags.set(ss_flag, setit); // multiplayer send subsystem name if (sendit) Current_sexp_network_packet.send_string(ss->system_info->subobj_name); } else Warning(LOCATION, "%s does not support %s!", op_name, special_subsystem_string(type)); }); // multiplayer end of packet if (sendit) Current_sexp_network_packet.end_callback(); } void multi_sexp_deal_with_subsys_flag(Ship::Subsystem_Flags ss_flag) { bool setit = false; ship *shipp = nullptr; char ss_name[MAX_NAME_LEN]; Current_sexp_network_packet.get_ship(shipp); Current_sexp_network_packet.get_bool(setit); while (Current_sexp_network_packet.get_string(ss_name)) { // deal with generic subsystem names // (sexp_ship_deal_with_subsystem_flag will have resolved this already, but this keeps compatibility with earlier builds) int generic_type = get_generic_subsys(ss_name); if (generic_type) { for (auto ss: list_range(&shipp->subsys_list)) { if (generic_type == SUBSYSTEM_ALL || generic_type == ss->system_info->type) { ss->flags.set(ss_flag, setit); } } } else { auto ss = ship_get_subsys(shipp, ss_name); if (ss) { // set the flag ss->flags.set(ss_flag, setit); } } } } // Goober5000 void sexp_ship_tag( int n, int tag ) { int tag_level, tag_time, ssm_index(0), ssm_team(0); bool is_nan, is_nan_forever; auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) return; n = CDR(n); // if untag, then unset everything and leave if (!tag) { ship_entry->shipp()->tag_left = -1.0f; ship_entry->shipp()->level2_tag_left = -1.0f; return; } // get the tag level and time eval_nums(n, is_nan, is_nan_forever, tag_level, tag_time); if (is_nan || is_nan_forever) return; // get SSM info if needed vec3d start; if (tag_level == 3) { if (n < 0) return; ssm_index = ssm_info_lookup(CTEXT(n)); if (ssm_index < 0) return; n = CDR(n); eval_vec3d(&start, n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; if (n >= 0) ssm_team = iff_lookup(CTEXT(n)); } ship_apply_tag(ship_entry->shipp(), tag_level, (float)tag_time, ship_entry->objp(), &start, ssm_index, ssm_team); } // sexpression to toggle invulnerability flag of ships. void sexp_ships_invulnerable( int n, bool invulnerable ) { sexp_deal_with_ship_flag(n, true, Object::Object_Flags::Invulnerable, Ship::Ship_Flags::NUM_VALUES, Mission::Parse_Object_Flags::OF_Invulnerable, invulnerable); } void sexp_ships_bomb_targetable(int n, bool targetable) { sexp_deal_with_ship_flag(n, true, Object::Object_Flags::Targetable_as_bomb, Ship::Ship_Flags::NUM_VALUES, Mission::Parse_Object_Flags::OF_Targetable_as_bomb, targetable, true); } // Goober5000 void sexp_ship_guardian_threshold(int node) { int threshold, n = node; bool is_nan, is_nan_forever; threshold = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; n = CDR(n); // for all ships for ( ; n != -1; n = CDR(n) ) { auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) { continue; } ship_entry->shipp()->ship_guardian_threshold = threshold; } } // MjnMixael + The Force void sexp_set_guard_range(int node) { int n = node; bool is_nan, is_nan_forever; auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) { return; } n = CDR(n); float range = i2fl(eval_num(n, is_nan, is_nan_forever)); if (is_nan || is_nan_forever) { return; } n = CDR(n); // a general range if (n < 0) { if (range > 0.0f) { ship_entry->shipp()->max_guard_radius = range; } else { ship_entry->shipp()->max_guard_radius = -1.0f; clear_guard_ranges_for_ship(ship_entry->shipnum); } return; } // one or more specific ranges int shipnum = ship_entry->shipnum; for (; n >= 0; n = CDR(n)) { object_ship_wing_point_team oswpt; eval_object_ship_wing_point_team(&oswpt, n); if (oswpt.type == OSWPT_TYPE_SHIP) { auto shipp = oswpt.shipp(); set_guard_range_ship(range, shipnum, shipp); } else if (oswpt.type == OSWPT_TYPE_WING) { for (int i = 0; i < oswpt.wingp()->current_count; ++i) { auto shipp = &Ships[oswpt.wingp()->ship_index[i]]; set_guard_range_ship(range, shipnum, shipp); } } else if (oswpt.type == OSWPT_TYPE_WHOLE_TEAM) { ship_obj* so; for (so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so)) { if (Objects[so->objnum].flags[Object::Object_Flags::Should_be_dead]) continue; auto shipp = &Ships[Objects[so->objnum].instance]; if (shipp->team == oswpt.team) { set_guard_range_ship(range, shipnum, shipp); } } } else { continue; } } } // Goober5000 void sexp_ship_subsys_guardian_threshold(int node) { int threshold, n = node; bool is_nan, is_nan_forever; threshold = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; n = CDR(n); auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) return; n = CDR(n); process_ship_subsystems(ship_entry, true, n, true, "ship-subsys-guardian-threshold", [&](ProcessSubsystemType type, ship_subsys *ss) { if (type == ProcessSubsystemType::HULL) ship_entry->shipp()->ship_guardian_threshold = threshold; else if (type == ProcessSubsystemType::SUBSYSTEM) ss->subsys_guardian_threshold = threshold; else Warning(LOCATION, "ship-subsys-guardian-threshold does not support %s!", special_subsystem_string(type)); }); } // sexpression to toggle KEEP ALIVE flag of ship object void sexp_ships_guardian( int n, bool guardian ) { for ( ; n != -1; n = CDR(n) ) { auto ship_entry = eval_ship(n); if (!ship_entry || ship_entry->status == ShipStatus::EXITED) continue; if (ship_entry->has_shipp()) { ship_entry->shipp()->ship_guardian_threshold = guardian ? SHIP_GUARDIAN_THRESHOLD_DEFAULT : 0; } else if (ship_entry->has_p_objp()) { ship_entry->p_objp()->flags.set(Mission::Parse_Object_Flags::SF_Guardian, guardian); } } } void sexp_ship_create(int n) { int new_ship_class, angle_count, team = -1; vec3d new_ship_pos; angles new_ship_ang; matrix new_ship_ori; bool is_nan, is_nan_forever, show_in_mission_log = true; Assert( n >= 0 ); // get ship name auto new_ship_name = CTEXT(n); n = CDR(n); // none means don't specify it // if ship with this name already exists, ship_create will respond appropriately if (!stricmp(new_ship_name, SEXP_NONE_STRING)) new_ship_name = nullptr; //Get ship class new_ship_class = ship_info_lookup(CTEXT(n)); if (new_ship_class < 0) { Warning(LOCATION, "Invalid ship class passed to ship-create; ship type '%s' does not exist", CTEXT(n)); return; } n = CDR(n); eval_vec3d(&new_ship_pos, n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; angle_count = eval_angles(&new_ship_ang, n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; //This is a costly function, so only do it if needed if (angle_count > 0) vm_angles_2_matrix(&new_ship_ori, &new_ship_ang); else new_ship_ori = vmd_identity_matrix; if (n >= 0) { team = iff_lookup(CTEXT(n)); n = CDR(n); } if (n >= 0) { show_in_mission_log = is_sexp_true(n); n = CDR(n); } int objnum = ship_create(&new_ship_ori, &new_ship_pos, new_ship_class, new_ship_name); Assert(objnum != -1); // do some initialization that is usually handled by parse_create_object_sub int shipnum = Objects[objnum].instance; ship *shipp = &Ships[shipnum]; ship_info *sip = &Ship_info[shipp->ship_info_index]; if (team >= 0) shipp->team = team; // note: model_page_in_textures was called via the sexp preloader ship_set_warp_effects(&Objects[objnum]); // if this name has a hash, create a default display name if (get_pointer_to_first_hash_symbol(shipp->ship_name)) { shipp->display_name = shipp->ship_name; end_string_at_first_hash_symbol(shipp->display_name); shipp->flags.set(Ship::Ship_Flags::Has_display_name); } if (sip->flags[Ship::Info_Flags::Intrinsic_no_shields]) Objects[objnum].flags.set(Object::Object_Flags::No_shields); mission_log_add_entry(LOG_SHIP_ARRIVED, shipp->ship_name, nullptr, -1, show_in_mission_log ? 0 : MLF_HIDDEN); if (scripting::hooks::OnShipArrive->isActive()) { scripting::hooks::OnShipArrive->run(scripting::hooks::ShipArriveConditions{ shipp, ArrivalLocation::AT_LOCATION, nullptr }, scripting::hook_param_list( scripting::hook_param("Ship", 'o', &Objects[objnum]) )); } } // MjnMixael void sexp_prop_create(int n) { int new_prop_class, angle_count; vec3d new_prop_pos; angles new_prop_ang; matrix new_prop_ori; bool is_nan, is_nan_forever; Assert( n >= 0 ); // get ship name auto new_ship_name = CTEXT(n); n = CDR(n); // none means don't specify it // if ship with this name already exists, ship_create will respond appropriately if (!stricmp(new_ship_name, SEXP_NONE_STRING)) new_ship_name = nullptr; //Get ship class new_prop_class = prop_info_lookup(CTEXT(n)); if (new_prop_class < 0) { Warning(LOCATION, "Invalid prop class passed to prop-create; prop type '%s' does not exist", CTEXT(n)); return; } n = CDR(n); eval_vec3d(&new_prop_pos, n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; angle_count = eval_angles(&new_prop_ang, n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; //This is a costly function, so only do it if needed if (angle_count > 0) vm_angles_2_matrix(&new_prop_ori, &new_prop_ang); else new_prop_ori = vmd_identity_matrix; int objnum = prop_create(&new_prop_ori, &new_prop_pos, new_prop_class, new_ship_name); Assert(objnum != -1); // note: model_page_in_textures was called via the sexp preloader } // Goober5000 void sexp_weapon_create(int n) { int weapon_class, parent_objnum, target_objnum, weapon_objnum, angle_count, is_locked; ship_subsys *targeted_ss; vec3d weapon_pos; angles weapon_angles; matrix weapon_orient; bool is_nan, is_nan_forever; parent_objnum = -1; auto parent = eval_ship(n); if (parent) { if (parent->objnum >= 0) parent_objnum = parent->objnum; else return; // parent ship isn't present } else if (stricmp(CTEXT(n), SEXP_NONE_STRING) != 0) { Warning(LOCATION, "A nonexistent ship %s was specified as a parent for weapon-create!", CTEXT(n)); return; } n = CDR(n); weapon_class = weapon_info_lookup(CTEXT(n)); if (weapon_class < 0) { Warning(LOCATION, "Invalid weapon class passed to weapon-create; weapon type '%s' does not exist", CTEXT(n)); return; } n = CDR(n); eval_vec3d(&weapon_pos, n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; angle_count = eval_angles(&weapon_angles, n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; // This is a costly function, so only do it if needed if (angle_count > 0) vm_angles_2_matrix(&weapon_orient, &weapon_angles); else weapon_orient = vmd_identity_matrix; target_objnum = -1; if (n >= 0) { auto ship_entry = eval_ship(n); if (ship_entry && ship_entry->objnum >= 0) target_objnum = ship_entry->objnum; n = CDR(n); } targeted_ss = nullptr; if (n >= 0) { if (target_objnum >= 0) targeted_ss = ship_get_subsys(&Ships[Objects[target_objnum].instance], CTEXT(n)); n = CDR(n); } is_locked = (target_objnum >= 0) ? 1 : 0; // assume full lock; this lets lasers track if people want them to // create the weapon // coverity[uninit_use_in_call:FALSE] - weapon_pos is always populated by eval_vec3d weapon_objnum = weapon_create(&weapon_pos, &weapon_orient, weapon_class, parent_objnum, -1, is_locked); // maybe make the weapon track its target if (is_locked) weapon_set_tracking_info(weapon_objnum, parent_objnum, target_objnum, is_locked, targeted_ss); } // make ship vanish without a trace (and what it's docked to) void sexp_ship_vanish(int n) { for ( ; n != -1; n = CDR(n) ) { auto ship_entry = eval_ship(n); if (ship_entry && ship_entry->status == ShipStatus::PRESENT) ship_actually_depart(ship_entry->shipnum, SHIP_VANISHED); } } // make prop vanish without a trace void sexp_prop_vanish(int n) { for (; n != -1; n = CDR(n)) { auto prop_entry = eval_prop(n); if (prop_entry != nullptr) { Objects[prop_entry->objnum].flags.set(Object::Object_Flags::Should_be_dead); } } } void sexp_destroy_instantly(int n, bool with_debris) { if (MULTIPLAYER_MASTER) Current_sexp_network_packet.start_callback(); for ( ; n >= 0; n = CDR(n) ) { auto ship_entry = eval_ship(n); if (!ship_entry || ship_entry->status != ShipStatus::PRESENT) continue; // if it's the player don't destroy if (!get_player_from_ship_node(n)) { // multiplayer callback if (MULTIPLAYER_MASTER) Current_sexp_network_packet.send_ship(ship_entry->shipp()); ship_destroy_instantly(ship_entry->objp(), with_debris); } } if (MULTIPLAYER_MASTER) Current_sexp_network_packet.end_callback(); } void multi_sexp_destroy_instantly() { ship *shipp; // destroy ships while (Current_sexp_network_packet.get_ship(shipp)) ship_destroy_instantly(&Objects[shipp->objnum]); } void sexp_shields_off(int n, bool shields_off ) //-Sesquipedalian { sexp_deal_with_ship_flag(n, true, Object::Object_Flags::No_shields, Ship::Ship_Flags::NUM_VALUES, Mission::Parse_Object_Flags::OF_No_shields, shields_off, true); } // Goober5000 void sexp_ingame_ship_kamikaze(ship *shipp, int kdamage) { Assertion(shipp, "Invalid ship pointer passed to sexp_ingame_ship_kamikaze.\n"); Assertion(kdamage >= 0, "Invalid value passed to sexp_ingame_ship_kamikaze. Kamikaze damage must be >= 0, is %i.\n", kdamage); ai_info *aip = &Ai_info[shipp->ai_index]; aip->ai_flags.set(AI::AI_Flags::Kamikaze, kdamage > 0); aip->kamikaze_damage = kdamage; } // Goober5000 void sexp_parse_ship_kamikaze(p_object *parse_obj, int kdamage) { Assert(parse_obj); parse_obj->flags.set(Mission::Parse_Object_Flags::AIF_Kamikaze, kdamage > 0); if (kdamage > 0) { parse_obj->kamikaze_damage = kdamage; } else { parse_obj->kamikaze_damage = 0; } } // Goober5000 - redone, added wing stuff void sexp_kamikaze(int n, int kamikaze) { int kdamage; bool is_nan, is_nan_forever; kdamage = 0; if (kamikaze) { kdamage = eval_num(n, is_nan, is_nan_forever); n = CDR(n); if (is_nan || is_nan_forever) return; } for ( ; n != -1; n = CDR(n) ) { object_ship_wing_point_team oswpt; eval_object_ship_wing_point_team(&oswpt, n); switch (oswpt.type) { // change ingame ship case OSWPT_TYPE_SHIP: { sexp_ingame_ship_kamikaze(oswpt.shipp(), kdamage); break; } // change ship yet to arrive case OSWPT_TYPE_PARSE_OBJECT: { sexp_parse_ship_kamikaze(oswpt.p_objp(), kdamage); break; } // change wing (we must set the flags for all ships present as well as all ships yet to arrive) case OSWPT_TYPE_WING: case OSWPT_TYPE_WING_NOT_PRESENT: { // current ships for (int i = 0; i < oswpt.wingp()->current_count; i++) sexp_ingame_ship_kamikaze(&Ships[oswpt.wingp()->ship_index[i]], kdamage); // ships yet to arrive for (auto p_objp: list_range(&Ship_arrival_list)) { if (p_objp->wingnum == oswpt.wingnum) sexp_parse_ship_kamikaze(p_objp, kdamage); } break; } default: break; } } } // Goober5000 void sexp_ingame_ship_alt_name_or_display_name_or_callsign(ship *shipp, bool clear_it, int alt_index, const char *display_name, int op_num) { // we might be clearing it if (clear_it) { if (op_num == OP_SHIP_CHANGE_ALT_NAME) shipp->alt_type_index = -1; else if (op_num == OP_SHIP_CHANGE_CALLSIGN) shipp->callsign_index = -1; else if (op_num == OP_SHIP_CHANGE_DISPLAY_NAME) { shipp->display_name = ""; shipp->flags.remove(Ship::Ship_Flags::Has_display_name); } else Assertion(false, "Unhandled op_num %d!", op_num); return; } if (op_num == OP_SHIP_CHANGE_ALT_NAME) { // see if this is actually the ship class if (!stricmp(Ship_info[shipp->ship_info_index].name, Mission_alt_types[alt_index])) { shipp->alt_type_index = -1; return; } } if (op_num == OP_SHIP_CHANGE_ALT_NAME) shipp->alt_type_index = alt_index; else if (op_num == OP_SHIP_CHANGE_CALLSIGN) shipp->callsign_index = alt_index; else if (op_num == OP_SHIP_CHANGE_DISPLAY_NAME) { shipp->display_name = display_name; shipp->flags.set(Ship::Ship_Flags::Has_display_name); } else Assertion(false, "Unhandled op_num %d!", op_num); } // Goober5000 void sexp_parse_ship_alt_name_or_display_name_or_callsign(p_object *parse_obj, bool clear_it, int alt_index, const char *display_name, int op_num) { // we might be clearing it if (clear_it) { if (op_num == OP_SHIP_CHANGE_ALT_NAME) parse_obj->alt_type_index = -1; else if (op_num == OP_SHIP_CHANGE_CALLSIGN) parse_obj->callsign_index = -1; else if (op_num == OP_SHIP_CHANGE_DISPLAY_NAME) { parse_obj->display_name = ""; parse_obj->flags.remove(Mission::Parse_Object_Flags::SF_Has_display_name); } else Assertion(false, "Unhandled op_num %d!", op_num); return; } if (op_num == OP_SHIP_CHANGE_ALT_NAME) { // see if this is actually the ship class if (!stricmp(Ship_class_names[parse_obj->ship_class], Mission_alt_types[alt_index])) { parse_obj->alt_type_index = -1; return; } } if (op_num == OP_SHIP_CHANGE_ALT_NAME) parse_obj->alt_type_index = alt_index; else if (op_num == OP_SHIP_CHANGE_CALLSIGN) parse_obj->callsign_index = alt_index; else if (op_num == OP_SHIP_CHANGE_DISPLAY_NAME) { parse_obj->display_name = display_name; parse_obj->flags.set(Mission::Parse_Object_Flags::SF_Has_display_name); } else Assertion(false, "Unhandled op_num %d!", op_num); } // Goober5000 void sexp_ship_change_alt_name_or_display_name_or_callsign(int node, int op_num) { char new_string[TOKEN_LENGTH]; int n = node, new_index = -1; bool string_was_empty = false, clear_it = false; // get the string strcpy_s(new_string, CTEXT(n)); n = CDR(n); // packets for multi if (MULTIPLAYER_MASTER) { Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_string(new_string); } // if the hash is at the beginning, the string might be truncated to empty, so check this first string_was_empty = (*new_string == '\0'); if (op_num == OP_SHIP_CHANGE_ALT_NAME || op_num == OP_SHIP_CHANGE_DISPLAY_NAME) { // truncate at a single hash end_string_at_first_hash_symbol(new_string, true); // ## -> # consolidate_double_characters(new_string, '#'); } // get the string's index if (string_was_empty || !stricmp(new_string, SEXP_ANY_STRING)) { clear_it = true; } else { if (op_num == OP_SHIP_CHANGE_ALT_NAME) { new_index = mission_parse_lookup_alt(new_string); if (new_index < 0) new_index = mission_parse_add_alt(new_string); } else if (op_num == OP_SHIP_CHANGE_CALLSIGN) { new_index = mission_parse_lookup_callsign(new_string); if (new_index < 0) new_index = mission_parse_add_callsign(new_string); } } for ( ; n != -1; n = CDR(n) ) { object_ship_wing_point_team oswpt; eval_object_ship_wing_point_team(&oswpt, n); if (MULTIPLAYER_MASTER) Current_sexp_network_packet.send_int((int)oswpt.type); switch (oswpt.type) { // change ingame ship case OSWPT_TYPE_SHIP: { sexp_ingame_ship_alt_name_or_display_name_or_callsign(oswpt.shipp(), clear_it, new_index, new_string, op_num); if (MULTIPLAYER_MASTER) Current_sexp_network_packet.send_ship(oswpt.shipp()); break; } // change ship yet to arrive case OSWPT_TYPE_PARSE_OBJECT: { sexp_parse_ship_alt_name_or_display_name_or_callsign(oswpt.p_objp(), clear_it, new_index, new_string, op_num); if (MULTIPLAYER_MASTER) Current_sexp_network_packet.send_parse_object(oswpt.p_objp()); break; } // change wing (we must set the flags for all ships present as well as all ships yet to arrive) case OSWPT_TYPE_WING: case OSWPT_TYPE_WING_NOT_PRESENT: { // current ships for (int i = 0; i < oswpt.wingp()->current_count; i++) sexp_ingame_ship_alt_name_or_display_name_or_callsign(&Ships[oswpt.wingp()->ship_index[i]], clear_it, new_index, new_string, op_num); // ships yet to arrive for (auto p_objp: list_range(&Ship_arrival_list)) { if (p_objp->wingnum == oswpt.wingnum) sexp_parse_ship_alt_name_or_display_name_or_callsign(p_objp, clear_it, new_index, new_string, op_num); } if (MULTIPLAYER_MASTER) Current_sexp_network_packet.send_wing(oswpt.wingp()); break; } default: break; } } if (MULTIPLAYER_MASTER) Current_sexp_network_packet.end_callback(); } void multi_sexp_ship_change_alt_name_or_display_name_or_callsign(int op_num) { char new_string[TOKEN_LENGTH]; int type, new_index = -1; bool string_was_empty = false, clear_it = false; Current_sexp_network_packet.get_string(new_string); // if the hash is at the beginning, the string might be truncated to empty, so check this first string_was_empty = (*new_string == '\0'); if (op_num == OP_SHIP_CHANGE_ALT_NAME || op_num == OP_SHIP_CHANGE_DISPLAY_NAME) { // truncate at a single hash end_string_at_first_hash_symbol(new_string, true); // ## -> # consolidate_double_characters(new_string, '#'); } if (string_was_empty || !stricmp(new_string, SEXP_ANY_STRING)) { clear_it = true; } else { if (op_num == OP_SHIP_CHANGE_ALT_NAME) { new_index = mission_parse_lookup_alt(new_string); if (new_index < 0) new_index = mission_parse_add_alt(new_string); } else if (op_num == OP_SHIP_CHANGE_CALLSIGN) { new_index = mission_parse_lookup_callsign(new_string); if (new_index < 0) new_index = mission_parse_add_callsign(new_string); } } while (Current_sexp_network_packet.get_int(type)) { switch ((oswpt_type)type) { case OSWPT_TYPE_SHIP: { ship *shipp; if (Current_sexp_network_packet.get_ship(shipp)) sexp_ingame_ship_alt_name_or_display_name_or_callsign(shipp, clear_it, new_index, new_string, op_num); break; } case OSWPT_TYPE_PARSE_OBJECT: { p_object *pobjp; if (Current_sexp_network_packet.get_parse_object(pobjp)) sexp_parse_ship_alt_name_or_display_name_or_callsign(pobjp, clear_it, new_index, new_string, op_num); break; } case OSWPT_TYPE_WING: case OSWPT_TYPE_WING_NOT_PRESENT: { wing *wingp; if (Current_sexp_network_packet.get_wing(wingp)) { // current ships for (int i = 0; i < wingp->current_count; i++) sexp_ingame_ship_alt_name_or_display_name_or_callsign(&Ships[wingp->ship_index[i]], clear_it, new_index, new_string, op_num); // ships yet to arrive for (p_object *p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp)) { if (p_objp->wingnum == WING_INDEX(wingp)) sexp_parse_ship_alt_name_or_display_name_or_callsign(p_objp, clear_it, new_index, new_string, op_num); } } break; } default: break; } } } // Goober5000 void sexp_set_death_message(int n) { int i; // we'll suppose it's the string for now Player->death_message = CTEXT(n); // but use an actual message if one exists for (i=0; ideath_message.c_str())) { Player->death_message = Messages[i].message; break; } } // apply localization lcl_replace_stuff(Player->death_message); sexp_replace_variable_names_with_values(Player->death_message); sexp_container_replace_refs_with_values(Player->death_message); } int sexp_key_pressed(int node) { int z, t; bool is_nan, is_nan_forever; Assert(node != -1); z = translate_key_to_index(CTEXT(node), false); if (z < 0){ return SEXP_FALSE; } if (!Control_config[z].digital_used.isValid()){ return SEXP_FALSE; } if (CDR(node) < 0){ return SEXP_TRUE; } t = eval_num(CDR(node), is_nan, is_nan_forever); if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; return timestamp_since(Control_config[z].digital_used) >= t * MILLISECONDS_PER_SECOND; } void sexp_key_reset(int node) { int n, z; for (n = node; n != -1; n = CDR(n)) { z = translate_key_to_index(CTEXT(n), false); if (z >= 0) { // note: this is only for digital controls like button presses, // so we don't need to clear the analog value Control_config[z].digital_used = TIMESTAMP::invalid(); } } } void sexp_ignore_key(int node) { int ignore_count, ignored_key; bool is_nan, is_nan_forever; ignore_count = eval_num(node, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_int(ignore_count); node = CDR(node); while (node >= 0) { // get the key ignored_key = translate_key_to_index(CTEXT(node), false); if (ignored_key >= 0) { Ignored_keys[ignored_key] = ignore_count; } Current_sexp_network_packet.send_int(ignored_key); node = CDR(node); } Current_sexp_network_packet.end_callback(); } void multi_sexp_ignore_key() { int ignored_key, ignore_count; Current_sexp_network_packet.get_int(ignore_count); while (Current_sexp_network_packet.get_int(ignored_key)) { Ignored_keys[ignored_key] = ignore_count; } } int sexp_targeted(int node) { int z; bool is_nan, is_nan_forever; ship_subsys *ptr; auto ship_entry = eval_ship(node); if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_FALSE; if (ship_entry->status == ShipStatus::EXITED) return SEXP_KNOWN_FALSE; if (!Player_ai || (ship_entry->objnum != Players_target)){ return SEXP_FALSE; } if (CDR(node) >= 0) { z = eval_num(CDR(node), is_nan, is_nan_forever); if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; if (!Players_target_timestamp.isValid() || timestamp_since(Players_target_timestamp) < z * MILLISECONDS_PER_SECOND){ return SEXP_FALSE; } if (CDR(CDR(node)) >= 0) { ptr = Players_targeted_subsys; if (!ptr || subsystem_stricmp(ptr->system_info->subobj_name, CTEXT(CDR(CDR(node))))){ return SEXP_FALSE; } } } return SEXP_TRUE; } int sexp_node_targeted(int node) { int z; bool is_nan, is_nan_forever; auto jnp = jumpnode_get_by_name(CTEXT(node)); if (jnp==nullptr || !Player_ai || (jnp->GetSCPObjectNumber() != Players_target)){ return SEXP_FALSE; } if (CDR(node) >= 0) { z = eval_num(CDR(node), is_nan, is_nan_forever); if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; if (timestamp_since(Players_target_timestamp) < z * MILLISECONDS_PER_SECOND){ return SEXP_FALSE; } } return SEXP_TRUE; } int sexp_speed(int node) { int z; bool is_nan, is_nan_forever; if (Training_context & TRAINING_CONTEXT_SPEED) { if (Training_context_speed_set) { z = eval_num(node, is_nan, is_nan_forever); if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; if (timestamp_since(Training_context_speed_timestamp) >= z * MILLISECONDS_PER_SECOND){ return SEXP_KNOWN_TRUE; } } } return SEXP_FALSE; } int sexp_get_throttle_speed(int node) { auto the_player = get_player_from_ship_node(node); if (the_player) { float max_speed = Ship_info[Ships[Objects[the_player->objnum].instance].ship_info_index].max_speed; return (int)(the_player->ci.forward_cruise_percent / 100.0f * max_speed); } return 0; } // CommanderDJ void sexp_set_player_throttle_speed(int node) { bool is_nan, is_nan_forever; //get and sanity check the player first auto the_player = get_player_from_ship_node(node); if (the_player) { //now the throttle percentage node = CDR(node); int throttle_percent = eval_num(node, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; CLAMP(throttle_percent, 0, 100); //now actually set the throttle the_player->ci.forward_cruise_percent = (float) throttle_percent; } } // Goober5000 int sexp_weapons_depleted(int node, bool primary) { int num_banks, num_tested_banks, num_depleted_banks; int *ammo; // get ship auto ship_entry = eval_ship(node); if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; if (ship_entry->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; // get bank stuff if (primary) { num_banks = ship_entry->shipp()->weapons.num_primary_banks; ammo = ship_entry->shipp()->weapons.primary_bank_ammo; } else { num_banks = ship_entry->shipp()->weapons.num_secondary_banks; ammo = ship_entry->shipp()->weapons.secondary_bank_ammo; } num_tested_banks = 0; num_depleted_banks = 0; // get number of depleted banks for (int idx=0; idxshipp()->weapons.primary_bank_weapons[idx]].wi_flags[Weapon::Info_Flags::Ballistic])) { continue; } } else { if (Weapon_info[ship_entry->shipp()->weapons.secondary_bank_weapons[idx]].wi_flags[Weapon::Info_Flags::SecondaryNoAmmo]) { continue; } } num_tested_banks++; // is this bank out of ammo? if (ammo[idx] == 0) { num_depleted_banks++; } } // are they all depleted? return (num_depleted_banks == num_tested_banks) ? SEXP_TRUE : SEXP_FALSE; } int sexp_facing(int node) { int angle; bool is_nan, is_nan_forever; float a1, a2; vec3d v1, v2; if (!Player_obj) { return SEXP_CANT_EVAL; } auto ship_entry = eval_ship(node); if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_FALSE; if (ship_entry->status == ShipStatus::EXITED) return SEXP_KNOWN_FALSE; auto target_objp = ship_entry->objp(); node = CDR(node); angle = eval_num(node, is_nan, is_nan_forever); if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; v1 = Player_obj->orient.vec.fvec; vm_vec_normalize(&v1); vm_vec_sub(&v2, &target_objp->pos, &Player_obj->pos); vm_vec_normalize(&v2); a1 = vm_vec_dot(&v1, &v2); a2 = cosf(fl_radians(angle % 360)); if (a1 >= a2){ return SEXP_TRUE; } return SEXP_FALSE; } int sexp_is_facing(int node) { int angle; bool is_nan, is_nan_forever; float a1, a2; vec3d v1, v2; auto ship_entry = eval_ship(node); if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_FALSE; if (ship_entry->status == ShipStatus::EXITED) return SEXP_KNOWN_FALSE; auto origin_objp = ship_entry->objp(); node = CDR(node); object_ship_wing_point_team oswpt; eval_object_ship_wing_point_team(&oswpt, node); // check if target has departed or not yet arrived if (oswpt.type == OSWPT_TYPE_EXITED) { return SEXP_KNOWN_FALSE; } if (oswpt.type == OSWPT_TYPE_PARSE_OBJECT) { return SEXP_FALSE; } if (!oswpt.has_objp()) return SEXP_FALSE; auto target_objp = oswpt.objp(); node = CDR(node); angle = eval_num(node, is_nan, is_nan_forever); if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; node = CDR(node); // check optional distance argument if (node >= 0) { int threshold = eval_num(node, is_nan, is_nan_forever); if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; if (sexp_retail_distance3(origin_objp, target_objp) > threshold) { return SEXP_FALSE; } } v1 = origin_objp->orient.vec.fvec; vm_vec_normalize(&v1); vm_vec_sub(&v2, &target_objp->pos, &origin_objp->pos); vm_vec_normalize(&v2); a1 = vm_vec_dot(&v1, &v2); a2 = cosf(fl_radians(angle % 360)); if (a1 >= a2){ return SEXP_TRUE; } return SEXP_FALSE; } // is ship facing first waypoint in waypoint path int sexp_facing2(int node) { int angle; bool is_nan, is_nan_forever; float a1, a2; vec3d v1, v2; // bail if Player_obj is not good if (!Player_obj) { return SEXP_CANT_EVAL; } // get position of first waypoint waypoint_list *wp_list = find_matching_waypoint_list(CTEXT(node)); if (wp_list == nullptr) { return SEXP_KNOWN_FALSE; } // get angle angle = eval_num(CDR(node), is_nan, is_nan_forever); if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; v1 = Player_obj->orient.vec.fvec; vm_vec_normalize(&v1); vm_vec_sub(&v2, wp_list->get_waypoints().front().get_pos(), &Player_obj->pos); vm_vec_normalize(&v2); a1 = vm_vec_dot(&v1, &v2); a2 = cosf(fl_radians(angle % 360)); if (a1 >= a2){ return SEXP_TRUE; } return SEXP_FALSE; } int sexp_order(int n) { auto order_to = CTEXT(n); auto order = CTEXT(CDR(n)); const char *target = nullptr; //target n = CDDR(n); if (n != -1) target = CTEXT(n); return hud_query_order_issued(order_to, order, target); } int sexp_query_orders (int n) { auto order_to = CTEXT(n); auto order = CTEXT(CDR(n)); const char *target = nullptr; const char *order_from = nullptr; const char *special = nullptr; int timestamp = 0; bool is_nan, is_nan_forever; // delay n = CDDR(n); if (n != -1) { timestamp = eval_num(n, is_nan, is_nan_forever); n = CDR(n); if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; } //target if (n != -1) { target = CTEXT(n); n = CDR(n); } // player order comes from if (n != -1) { order_from = CTEXT(n); n = CDR(n); } // optional special argument if (n != -1) special = CTEXT(n); return hud_query_order_issued(order_to, order, target, timestamp, order_from, special); } int sexp_time_to_goal(int n) { auto ship_entry = eval_ship(n); if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; if (ship_entry->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; auto shipp = ship_entry->shipp(); int time = ship_return_seconds_to_goal(shipp); if (time < 0) { return SEXP_NAN; } return time; } void sexp_set_hud_time_pad(int n) { bool is_nan, is_nan_forever; int pad; pad = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; The_mission.HUD_timer_padding = pad; } // Karajorma void sexp_reset_orders (int /*n*/) { Squadmsg_history.clear(); } int sexp_waypoint_missed() { if (Training_context & TRAINING_CONTEXT_FLY_PATH) { if (Training_context_at_waypoint > Training_context_goal_waypoint){ return SEXP_TRUE; } } return SEXP_FALSE; } int sexp_waypoint_twice() { if (Training_context & TRAINING_CONTEXT_FLY_PATH) { if (Training_context_at_waypoint < Training_context_goal_waypoint - 1){ return SEXP_TRUE; } } return SEXP_FALSE; } int sexp_path_flown() { if (Training_context & TRAINING_CONTEXT_FLY_PATH) { auto wp_list = find_waypoint_list_at_index(Training_context_waypoint_path); if (wp_list && (Training_context_goal_waypoint == static_cast(wp_list->get_waypoints().size()))) { return SEXP_TRUE; } } return SEXP_FALSE; } void sexp_send_training_message(int node) { bool is_nan, is_nan_forever; int n = node, count, delay, duration; if(physics_paused){ return; } Assert(node >= 0); Assert(Event_index >= 0); auto primary_message = CTEXT(n); n = CDR(n); auto secondary_message = (n >= 0) ? CTEXT(n) : nullptr; n = CDR(n); count = eval_nums(n, is_nan, is_nan_forever, delay, duration); if (is_nan || is_nan_forever) return; if (count > 0) { delay *= 1000; } if (count < 2) { duration = -1; } Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_int(duration); Current_sexp_network_packet.send_int(delay); if ((Mission_events[Event_index].repeat_count > 1) || (secondary_message == nullptr)) { message_training_queue(primary_message, _timestamp(delay), duration); Current_sexp_network_packet.send_string(primary_message); } else { message_training_queue(secondary_message, _timestamp(delay), duration); Current_sexp_network_packet.send_string(secondary_message); } Current_sexp_network_packet.end_callback(); } void multi_sexp_send_training_message() { int duration, delay; char message[TOKEN_LENGTH]; Current_sexp_network_packet.get_int(duration); Current_sexp_network_packet.get_int(delay); if (Current_sexp_network_packet.get_string(message)) { message_training_queue(message, _timestamp(delay), duration); } } int sexp_gse_recharge_pct(int node, int op_num) { // get the ship auto ship_entry = eval_ship(node); if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; if (ship_entry->status == ShipStatus::DEATH_ROLL || ship_entry->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; int index; if (op_num == OP_WEAPON_RECHARGE_PCT) index = ship_entry->shipp()->weapon_recharge_index; else if (op_num == OP_SHIELD_RECHARGE_PCT) index = ship_entry->shipp()->shield_recharge_index; else if (op_num == OP_ENGINE_RECHARGE_PCT) index = ship_entry->shipp()->engine_recharge_index; else return SEXP_NAN_FOREVER; // recharge pct return (int)(100.0f * ets_power_factor(ship_entry->objp(), false) * Energy_levels[index]); } /* * Get a ship's power output */ int sexp_get_power_output(int node) { auto ship_entry = eval_ship(node); if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; if (ship_entry->status == ShipStatus::DEATH_ROLL || ship_entry->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; return (int)(std::lround(Ship_info[ship_entry->shipp()->ship_info_index].power_output)); } /** * retrieve one ETS index from a ship */ int sexp_get_ets_value(int node) { auto ets_type = CTEXT(node); node = CDR(node); auto ship_entry = eval_ship(node); if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; if (ship_entry->status == ShipStatus::DEATH_ROLL || ship_entry->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; if (!stricmp(ets_type, "engine")) { return ship_entry->shipp()->engine_recharge_index; } else if (!stricmp(ets_type, "shield")) { return ship_entry->shipp()->shield_recharge_index; } else if (!stricmp(ets_type, "weapon")) { return ship_entry->shipp()->weapon_recharge_index; } else { return SEXP_NAN_FOREVER; } } /** * set all ETS indexes for one or more ships */ void sexp_set_ets_values(int node) { bool is_nan, is_nan_forever; int ets_idx[num_retail_ets_gauges]; //get inputs eval_nums(node, is_nan, is_nan_forever, ets_idx[ENGINES], ets_idx[SHIELDS], ets_idx[WEAPONS]); if (is_nan || is_nan_forever) return; // sanity check inputs sanity_check_ets_inputs(ets_idx); Current_sexp_network_packet.start_callback(); // apply ETS settings to specified ships for ( ; node != -1; node = CDR(node)) { auto ship_entry = eval_ship(node); if (!ship_entry || !ship_entry->has_shipp()) return; if (validate_ship_ets_indxes(ship_entry->shipnum, ets_idx)) { ship_entry->shipp()->engine_recharge_index = ets_idx[ENGINES]; ship_entry->shipp()->shield_recharge_index = ets_idx[SHIELDS]; ship_entry->shipp()->weapon_recharge_index = ets_idx[WEAPONS]; Current_sexp_network_packet.send_ship(ship_entry->shipp()); Current_sexp_network_packet.send_int(ets_idx[ENGINES]); Current_sexp_network_packet.send_int(ets_idx[SHIELDS]); Current_sexp_network_packet.send_int(ets_idx[WEAPONS]); } } Current_sexp_network_packet.end_callback(); } void multi_sexp_set_ets_values() { int sindex; int ets_idx[num_retail_ets_gauges]; while (Current_sexp_network_packet.get_ship(sindex)) { Current_sexp_network_packet.get_int(ets_idx[ENGINES]); Current_sexp_network_packet.get_int(ets_idx[SHIELDS]); Current_sexp_network_packet.get_int(ets_idx[WEAPONS]); set_recharge_rates(&Objects[Ships[sindex].objnum], ets_idx[SHIELDS], ets_idx[WEAPONS], ets_idx[ENGINES]); } } int sexp_shield_quad_low(int node) { float max_quad, check; bool is_nan, is_nan_forever; // get the ship auto ship_entry = eval_ship(node); if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; if (ship_entry->status == ShipStatus::DEATH_ROLL || ship_entry->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; auto sip = &Ship_info[ship_entry->shipp()->ship_info_index]; if (!sip->is_small_ship()) { return SEXP_KNOWN_FALSE; } max_quad = shield_get_max_quad(ship_entry->objp()); // shield pct check = i2fl(eval_num(CDR(node), is_nan, is_nan_forever)); if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; // check his quadrants for (float quadrant: ship_entry->objp()->shield_quadrant) { if ((quadrant / max_quad * 100.0f) <= check) { return SEXP_TRUE; } } // all good return SEXP_FALSE; } int sexp_get_ammo_sub(ship_weapon *swp, int bank_to_check, bool primary, bool do_percent) { int sum = 0; int total = 0; int num_banks, *ammo, *start_ammo, *weapons; if (primary) { num_banks = swp->num_primary_banks; ammo = swp->primary_bank_ammo; start_ammo = swp->primary_bank_start_ammo; weapons = swp->primary_bank_weapons; } else { num_banks = swp->num_secondary_banks; ammo = swp->secondary_bank_ammo; start_ammo = swp->secondary_bank_start_ammo; weapons = swp->secondary_bank_weapons; } for (int i = 0; i < num_banks; ++i) { // if this is primary, skip non-ballistic weapons if (primary && !Weapon_info[weapons[i]].wi_flags[Weapon::Info_Flags::Ballistic]) continue; if (!primary && Weapon_info[weapons[i]].wi_flags[Weapon::Info_Flags::SecondaryNoAmmo]) continue; // if we are checking a specific bank, and this isn't it, skip it if (bank_to_check >= 0 && bank_to_check < num_banks && i != bank_to_check) continue; sum += ammo[i]; total += start_ammo[i]; } if (do_percent) { if (total == 0) return 100; return (int)(((float)sum / (float)total) * 100.0f); } return sum; } // Goober5000 & Karajorma int sexp_get_ammo(int node, bool for_turret, bool primary, bool do_percent) { ship_weapon *swp; bool is_nan, is_nan_forever; // get the ship auto ship_entry = eval_ship(node); if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; if (ship_entry->status == ShipStatus::DEATH_ROLL || ship_entry->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; node = CDR(node); // Get the turret if (for_turret) { auto turret = ship_get_subsys(ship_entry->shipp(), CTEXT(node)); if (!turret || !(turret->system_info->flags[Model::Subsystem_Flags::Turret_use_ammo])) return 0; node = CDR(node); swp = &turret->weapons; } else swp = &ship_entry->shipp()->weapons; // bank to check int check = eval_num(node, is_nan, is_nan_forever); // bogus check? if (is_nan || is_nan_forever || check < 0) return 0; return sexp_get_ammo_sub(swp, check, primary, do_percent); } //Karajorma - Helper function for the set-*-ammo and weapon functions void sexp_set_ammo_sub(ship_weapon *swp, int requested_bank, int requested_ammo, int rearm_limit, bool primary) { int num_banks, *ammo_capacity, *ammo, *start_ammo, *weapons; if (primary) { num_banks = swp->num_primary_banks; ammo_capacity = swp->primary_bank_capacity; ammo = swp->primary_bank_ammo; start_ammo = swp->primary_bank_start_ammo; weapons = swp->primary_bank_weapons; } else { num_banks = swp->num_secondary_banks; ammo_capacity = swp->secondary_bank_capacity; ammo = swp->secondary_bank_ammo; start_ammo = swp->secondary_bank_start_ammo; weapons = swp->secondary_bank_weapons; } // Can only set one bank at a time. Check that someone hasn't asked for the contents of all // the banks or a non-existant bank if ((requested_bank >= num_banks) || requested_bank < 0) return; if (primary) { // Check that this isn't a non-ballistic bank as it's pointless to set the amount of ammo for those if (!(Weapon_info[weapons[requested_bank]].wi_flags[Weapon::Info_Flags::Ballistic])) return; } else { if (Weapon_info[weapons[requested_bank]].wi_flags[Weapon::Info_Flags::SecondaryNoAmmo]) return; } int maximum_allowed = (int)std::lround(ammo_capacity[requested_bank] / Weapon_info[weapons[requested_bank]].cargo_size); //Check that a valid number of weapons have been specified. if (requested_ammo >= 0) { // Is the number requested larger than the maximum allowed for that particular bank? if (requested_ammo > maximum_allowed) requested_ammo = maximum_allowed; // Set the number of weapons ammo[requested_bank] = requested_ammo; } else if (ammo[requested_bank] > maximum_allowed) { // Make sure the current ammo doesn't exceed the maximum (this can otherwise happen with the set-*-weapon SEXPs) ammo[requested_bank] = maximum_allowed; } // Check rearm validity if (rearm_limit >= 0) { // Don't allow more weapons than the bank can actually hold. if (rearm_limit > maximum_allowed) rearm_limit = maximum_allowed; start_ammo[requested_bank] = rearm_limit; } else // Even if no rearm limit is explicitly set, don't allow more weapons than the bank can actually hold (this can otherwise happen with the set-*-weapon SEXPs). { start_ammo[requested_bank] = maximum_allowed; } } // Karajorma - sets the amount of ammo in a certain ballistic or secondary weapon bank to the specified value void sexp_set_ammo(int node, bool for_turret, bool primary) { const char *turret_name = nullptr; ship_weapon *swp; bool is_nan, is_nan_forever; // get the ship auto ship_entry = eval_ship(node); if (!ship_entry || !ship_entry->has_shipp()) return; node = CDR(node); // Get the turret if (for_turret) { turret_name = CTEXT(node); auto turret = ship_get_subsys(ship_entry->shipp(), turret_name); if (!turret || !(turret->system_info->flags[Model::Subsystem_Flags::Turret_use_ammo])) return; node = CDR(node); swp = &turret->weapons; } else swp = &ship_entry->shipp()->weapons; // Get the bank to set the number on int requested_bank = eval_num(node, is_nan, is_nan_forever); if (is_nan || is_nan_forever || requested_bank < 0) return; node = CDR(node); // Get the number of weapons requested int requested_weapons = eval_num(node, is_nan, is_nan_forever); if (is_nan || is_nan_forever || requested_weapons < 0) return; node = CDR(node); // If a rearm limit hasn't been specified simply change the ammo. Otherwise read in the rearm limit int rearm_limit = -1; if (node >= 0) { rearm_limit = eval_num(node, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; } sexp_set_ammo_sub(swp, requested_bank, requested_weapons, rearm_limit, primary); // do the multiplayer callback here if (MULTIPLAYER_MASTER) { Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_ship(ship_entry->shipp()); if (for_turret) Current_sexp_network_packet.send_string(turret_name); Current_sexp_network_packet.send_int(requested_bank); Current_sexp_network_packet.send_int(requested_weapons); Current_sexp_network_packet.send_int(rearm_limit); Current_sexp_network_packet.end_callback(); } } void multi_sexp_set_ammo(bool for_turret, bool primary) { ship *shipp; char turret_name[TOKEN_LENGTH]; ship_weapon *swp; int requested_bank = -1; int requested_weapons = -1; int rearm_limit = -1; Current_sexp_network_packet.get_ship(shipp); if (for_turret) Current_sexp_network_packet.get_string(turret_name); Current_sexp_network_packet.get_int(requested_bank); Current_sexp_network_packet.get_int(requested_weapons); Current_sexp_network_packet.get_int(rearm_limit); if (for_turret) { auto turret = ship_get_subsys(shipp, turret_name); if (!turret) return; swp = &turret->weapons; } else swp = &shipp->weapons; sexp_set_ammo_sub(swp, requested_bank, requested_weapons, rearm_limit, primary); } // Karajorma - Changes the weapon in the requested bank to the one supplied. Optionally sets the ammo and // rearm limit too. void sexp_set_weapon(int node, bool primary) { int requested_bank, windex, requested_ammo = -1, rearm_limit = -1; bool is_nan, is_nan_forever; Assert(node != -1); // Check that a ship has been supplied auto ship_entry = eval_ship(node); if (!ship_entry || !ship_entry->has_shipp()) return; node = CDR(node); // Get the bank to change the weapon of requested_bank = eval_num(node, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; node = CDR(node); // Get the weapon to change to windex = weapon_info_lookup(CTEXT(node)); if (windex < 0) return; node = CDR(node); if (primary) { // Be sure it's a valid bank if (requested_bank < 0 || requested_bank >= ship_entry->shipp()->weapons.num_primary_banks) return; // Be sure it's a valid type if (!Weapon_info[windex].is_primary()) return; } else { // Be sure it's a valid bank if (requested_bank < 0 || requested_bank >= ship_entry->shipp()->weapons.num_secondary_banks) return; // Be sure it's a valid type if (!Weapon_info[windex].is_secondary()) return; } // Change the weapon if (primary) ship_entry->shipp()->weapons.primary_bank_weapons[requested_bank] = windex; else ship_entry->shipp()->weapons.secondary_bank_weapons[requested_bank] = windex; if (MULTIPLAYER_MASTER) { Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_ship(ship_entry->shipp()); Current_sexp_network_packet.send_bool(primary); Current_sexp_network_packet.send_int(requested_bank); Current_sexp_network_packet.send_int(windex); } // Check to see if the optional ammo and rearm_limit settings were supplied if (node >= 0) { requested_ammo = eval_num(node, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; if (requested_ammo < 0) { requested_ammo = 0; } node = CDR(node); // If nothing was supplied then set the rearm limit to a negative value so that it is ignored if (node >= 0) { rearm_limit = eval_num(node, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; } } // Set the ammo -- No matter what - Cyborg17 sexp_set_ammo_sub(&ship_entry->shipp()->weapons, requested_bank, requested_ammo, rearm_limit, primary); // Now pass this info on to clients. if (MULTIPLAYER_MASTER) { Current_sexp_network_packet.send_int(requested_ammo); Current_sexp_network_packet.send_int(rearm_limit); Current_sexp_network_packet.end_callback(); } } void multi_sexp_set_weapon() { ship *shipp; int requested_bank; int windex; int requested_ammo; int rearm_limit; bool primary; Current_sexp_network_packet.get_ship(shipp); Current_sexp_network_packet.get_bool(primary); Current_sexp_network_packet.get_int(requested_bank); Current_sexp_network_packet.get_int(windex); Current_sexp_network_packet.get_int(requested_ammo); Current_sexp_network_packet.get_int(rearm_limit); // Change the weapon if (primary) shipp->weapons.primary_bank_weapons[requested_bank] = windex; else shipp->weapons.secondary_bank_weapons[requested_bank] = windex; // Set the ammo sexp_set_ammo_sub(&shipp->weapons, requested_bank, requested_ammo, rearm_limit, primary); } int sexp_get_countermeasures(int node) { auto ship_entry = eval_ship(node); if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; if (ship_entry->status == ShipStatus::DEATH_ROLL || ship_entry->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; return ship_entry->shipp()->cmeasure_count; } void sexp_set_countermeasures(int node) { ship *shipp; int num_cmeasures; bool is_nan, is_nan_forever; auto ship_entry = eval_ship(node); if (!ship_entry || !ship_entry->has_shipp()) { return; } shipp = ship_entry->shipp(); node = CDR(node); float cm_cargo_size = 1.0f; if (Countermeasures_use_capacity) cm_cargo_size = Weapon_info[shipp->current_cmeasure].cargo_size; num_cmeasures = eval_num(node, is_nan, is_nan_forever); if (is_nan || is_nan_forever) { return; } if (num_cmeasures < 0) { num_cmeasures = 0; } else { if (Countermeasures_use_capacity) { if (num_cmeasures > fl2i(Ship_info[shipp->ship_info_index].cmeasure_max / cm_cargo_size)) { num_cmeasures = fl2i(Ship_info[shipp->ship_info_index].cmeasure_max / cm_cargo_size); } } else { if (num_cmeasures > Ship_info[shipp->ship_info_index].cmeasure_max) { num_cmeasures = Ship_info[shipp->ship_info_index].cmeasure_max; } } } shipp->cmeasure_count = num_cmeasures; Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_ship(shipp); Current_sexp_network_packet.send_int(num_cmeasures); Current_sexp_network_packet.end_callback(); } void multi_sexp_set_countermeasures() { int num_cmeasures = 0; ship *shipp; Current_sexp_network_packet.get_ship(shipp); if (shipp == nullptr) { return; } if (Current_sexp_network_packet.get_int(num_cmeasures)) { shipp->cmeasure_count = num_cmeasures; } } // KeldorKatarn - Locks or unlocks the afterburner on the requested ship void sexp_deal_with_afterburner_lock (int node, bool lock) { Assert (node != -1); sexp_deal_with_ship_flag(node, true, Object::Object_Flags::NUM_VALUES, Ship::Ship_Flags::Afterburner_locked, Mission::Parse_Object_Flags::NUM_VALUES, lock, true); } // Karajorma - locks or unlocks primary weapons on the requested ship void sexp_deal_with_primary_lock (int node, bool lock) { Assert (node != -1); sexp_deal_with_ship_flag(node, true, Object::Object_Flags::NUM_VALUES, Ship::Ship_Flags::Primaries_locked, Mission::Parse_Object_Flags::SF_Primaries_locked, lock, true); } void sexp_deal_with_secondary_lock (int node, bool lock) { Assert (node != -1); sexp_deal_with_ship_flag(node, true, Object::Object_Flags::NUM_VALUES, Ship::Ship_Flags::Secondaries_locked, Mission::Parse_Object_Flags::SF_Secondaries_locked, lock, true); } //Karajorma - Changes the subsystem name displayed on the HUD. void sexp_change_subsystem_name(int node) { Assert (node != -1); // Check that a ship has been supplied auto ship_entry = eval_ship(node); if (!ship_entry || !ship_entry->has_shipp()) { return; } node = CDR(node); auto new_name = CTEXT(node); node = CDR(node); if (MULTIPLAYER_MASTER) { Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_ship(ship_entry->shipp()); Current_sexp_network_packet.send_string(new_name); } // loop through all the subsystems the SEXP has provided while (node >= 0) { //Get the new subsystem name auto subsystem_to_rename = ship_get_subsys(ship_entry->shipp(), CTEXT(node)); if (subsystem_to_rename != nullptr) { ship_subsys_set_name(subsystem_to_rename, new_name); if (MULTIPLAYER_MASTER) { Current_sexp_network_packet.send_string(CTEXT(node)); } } node = CDR(node); } if (MULTIPLAYER_MASTER) { Current_sexp_network_packet.end_callback(); } } void multi_sexp_change_subsystem_name() { ship *shipp = nullptr; char new_name[TOKEN_LENGTH]; char subsys_name[MAX_NAME_LEN]; ship_subsys *subsystem_to_rename; Current_sexp_network_packet.get_ship(shipp); Current_sexp_network_packet.get_string(new_name); while (Current_sexp_network_packet.get_string(subsys_name)) { subsystem_to_rename = ship_get_subsys(shipp, subsys_name); if (subsystem_to_rename != nullptr) { ship_subsys_set_name(subsystem_to_rename, new_name); } } } // Goober5000 void sexp_change_ship_class(int n) { int class_num = ship_info_lookup(CTEXT(n)); if (class_num < 0) return; n = CDR(n); if (MULTIPLAYER_MASTER) { Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_int(class_num); } // all ships in the sexp for ( ; n != -1; n = CDR(n)) { auto ship_entry = eval_ship(n); if (!ship_entry || ship_entry->status == ShipStatus::EXITED) continue; // If the ship hasn't arrived we still want the ability to change its class. if (ship_entry->status == ShipStatus::NOT_YET_PRESENT) { swap_parse_object(ship_entry->p_objp(), class_num); if (MULTIPLAYER_MASTER) { Current_sexp_network_packet.send_bool(false); Current_sexp_network_packet.send_parse_object(ship_entry->p_objp()); } } // If the ship is already in the mission else { // don't mess with a ship that's occupied if (!ship_entry->shipp()->is_arriving() && !ship_entry->shipp()->is_dying_or_departing()) { change_ship_type(ship_entry->shipnum, class_num, 1); if (ship_entry->shipnum == SHIP_INDEX(Player_ship)) { // update HUD and RTT cockpit gauges if applicable set_current_hud(); ship_close_cockpit_displays(Player_ship); ship_init_cockpit_displays(Player_ship); } if (MULTIPLAYER_MASTER) { Current_sexp_network_packet.send_bool(true); Current_sexp_network_packet.send_ship(ship_entry->shipp()); } } } } if (MULTIPLAYER_MASTER) { Current_sexp_network_packet.end_callback(); } } void multi_sexp_change_ship_class() { int class_num = -1; int ship_num = -1; bool ship_arrived; p_object *pobjp = nullptr; Current_sexp_network_packet.get_int(class_num); while (Current_sexp_network_packet.get_bool(ship_arrived)) { if (ship_arrived) { Current_sexp_network_packet.get_ship(ship_num); if ((class_num >= 0) && (ship_num >= 0)) { change_ship_type(ship_num, class_num, 1); if (&Ships[ship_num] == Player_ship) { // update HUD and RTT cockpit gauges if applicable set_current_hud(); ship_close_cockpit_displays(Player_ship); ship_init_cockpit_displays(Player_ship); } } } else { Current_sexp_network_packet.get_parse_object(pobjp); if ((class_num >= 0) && (pobjp != nullptr)) { swap_parse_object(pobjp, class_num); } } } } // Goober5000 void ship_copy_damage(const ship_registry_entry *target, const ship_registry_entry *source) { auto target_shipp = target->shipp(); auto target_objp = target->objp(); auto source_shipp = source->shipp(); auto source_objp = source->objp(); if (target_shipp->ship_info_index != source_shipp->ship_info_index) { nprintf(("SEXP", "Copying damage of ship %s to ship %s which has a different ship class. Strange results might occur.\n", source_shipp->ship_name, target_shipp->ship_name)); } // copy hull... target_shipp->special_hitpoints = source_shipp->special_hitpoints; target_shipp->ship_max_hull_strength = source_shipp->ship_max_hull_strength; target_objp->hull_strength = source_objp->hull_strength; // ...and shields target_shipp->special_shield = source_shipp->special_shield; target_shipp->ship_max_shield_strength = source_shipp->ship_max_shield_strength; int n_quadrants = static_cast(target_objp->shield_quadrant.size()); if (source_objp->shield_quadrant.size() == target_objp->shield_quadrant.size()) { for (int i = 0; i < n_quadrants; i++) target_objp->shield_quadrant[i] = source_objp->shield_quadrant[i]; } else { float quad_strength = target_shipp->ship_max_shield_strength / n_quadrants; for (int i = 0; i < n_quadrants; i++) target_objp->shield_quadrant[i] = quad_strength; } // search through all subsystems on source ship and map them onto target ship for (auto source_ss: list_range(&source_shipp->subsys_list)) { // find subsystem to configure auto target_ss = ship_get_subsys(target_shipp, source_ss->system_info->subobj_name); if (target_ss == nullptr) continue; // copy target_ss->max_hits = source_ss->max_hits; target_ss->current_hits = source_ss->current_hits; if (target_ss->submodel_instance_1 && source_ss->submodel_instance_1) target_ss->submodel_instance_1->blown_off = source_ss->submodel_instance_1->blown_off; if (target_ss->submodel_instance_2 && source_ss->submodel_instance_2) target_ss->submodel_instance_2->blown_off = source_ss->submodel_instance_2->blown_off; // special case for subsystems that don't correspond to a submodel; // copy what the source shows if it can show anything, since its hitpoints don't say whether it was blown off bool show_destroyed; if (source_ss->submodel_instance_1 || submodel_find_destroyed_form(Ship_info[source_shipp->ship_info_index].model_num, source_ss->system_info->subobj_name) >= 0) show_destroyed = subsystem_submodels_blown_off(source_shipp, source_ss); else show_destroyed = (source_ss->max_hits > 0.0f) && (source_ss->current_hits <= 0.0f); show_destroyed = show_destroyed && !(target_ss->flags[Ship::Subsystem_Flags::No_disappear]); check_subsystem_submodel_link(target_shipp, target_ss, show_destroyed); } } void sexp_replace_texture(int n, bool skybox) { auto old_name = CTEXT(n); n = CDR(n); auto new_name = CTEXT(n); n = CDR(n); if (skybox) { if (Nmodel_instance_num < 0) { mprintf(("Tried to replace texture of a non-existant skybox\n")); return; } polymodel_instance* skybox_pmi = model_get_instance(Nmodel_instance_num); modelinstance_replace_active_texture(skybox_pmi, old_name, new_name); stars_invalidate_environment_map(); return; } for (; n != -1; n = CDR(n)) { object_ship_wing_point_team oswpt; eval_object_ship_wing_point_team(&oswpt, n); switch (oswpt.type) { case OSWPT_TYPE_PARSE_OBJECT: case OSWPT_TYPE_SHIP: { auto ship_entry = oswpt.ship_entry(); if (ship_entry->status == ShipStatus::EXITED) continue; if (ship_entry->status == ShipStatus::NOT_YET_PRESENT) { p_object* pobjp = ship_entry->p_objp(); texture_replace replace; strcpy(replace.ship_name, ship_entry->name); strcpy(replace.old_texture, old_name); strcpy(replace.new_texture, new_name); replace.from_table = false; if (!stricmp(new_name, "invisible")) replace.new_texture_id = REPLACE_WITH_INVISIBLE; else replace.new_texture_id = bm_load_either(new_name); pobjp->replacement_textures.push_back(replace); } // If the ship is already in the mission else { ship_replace_active_texture(ship_entry->shipnum, old_name, new_name); } break; } case OSWPT_TYPE_WING: { auto wp = oswpt.wingp(); for (int i = 0; i < wp->current_count; ++i) { if (wp->ship_index[i] >= 0) { ship_replace_active_texture(wp->ship_index[i], old_name, new_name); } } break; } case OSWPT_TYPE_WING_NOT_PRESENT: { for (p_object* p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp)) { if (p_objp->wingnum == oswpt.wingnum) { texture_replace replace; strcpy(replace.ship_name, p_objp->name); strcpy(replace.old_texture, old_name); strcpy(replace.new_texture, new_name); replace.from_table = false; if (!stricmp(new_name, "invisible")) replace.new_texture_id = REPLACE_WITH_INVISIBLE; else replace.new_texture_id = bm_load_either(new_name); p_objp->replacement_textures.push_back(replace); } } break; } default: mprintf(("Invalid Shipname in SEXP ship-effect\n")); } } } void sexp_set_alphamult(int n) { bool is_nan, is_nan_forever; int newAlpha = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) { return; } for (n = CDR(n); n != -1; n = CDR(n)) { object_ship_wing_point_team oswpt; eval_object_ship_wing_point_team(&oswpt, n); // we only handle ships and wings that are present switch (oswpt.type) { case OSWPT_TYPE_SHIP: { auto ship_entry = oswpt.ship_entry(); auto shipp = ship_entry->shipp(); shipp->flags.remove(Ship::Ship_Flags::Cloaked); if (newAlpha >= 100) { shipp->alpha_mult = 1.0f; shipp->flags.remove(Ship::Ship_Flags::Render_with_alpha_mult); } else if (newAlpha <= 0) { shipp->alpha_mult = 0.0f; shipp->flags.set(Ship::Ship_Flags::Cloaked); } else { shipp->alpha_mult = ((float)newAlpha) / 100.f; shipp->flags.set(Ship::Ship_Flags::Render_with_alpha_mult); } break; } case OSWPT_TYPE_WING: { auto wp = oswpt.wingp(); for (int i = 0; i < wp->current_count; ++i) { if (wp->ship_index[i] >= 0) { ship* shipp = &Ships[wp->ship_index[i]]; shipp->flags.remove(Ship::Ship_Flags::Cloaked); if (newAlpha >= 100) { shipp->alpha_mult = 1.0f; shipp->flags.remove(Ship::Ship_Flags::Render_with_alpha_mult); } else if (newAlpha <= 0) { shipp->alpha_mult = 0.0f; shipp->flags.set(Ship::Ship_Flags::Cloaked); } else { shipp->alpha_mult = ((float)newAlpha) / 100.f; shipp->flags.set(Ship::Ship_Flags::Render_with_alpha_mult); } } } break; } case OSWPT_TYPE_PARSE_OBJECT: case OSWPT_TYPE_WING_NOT_PRESENT: default: mprintf(("Invalid Shipname in SEXP set-alpha-multiplier. Ship / Wing must be present.\n")); } } } extern int insert_subsys_status(p_object *pobjp); // Goober5000 void parse_copy_damage(const ship_registry_entry *target, const ship_registry_entry *source_entry) { auto target_pobjp = target->p_objp(); auto source_shipp = source_entry->shipp(); if (target_pobjp->ship_class != source_shipp->ship_info_index) { nprintf(("SEXP", "Copying damage of ship %s to ship %s which has a different ship class. Strange results might occur.\n", source_shipp->ship_name, target_pobjp->name)); } // copy hull... target_pobjp->special_hitpoints = source_shipp->special_hitpoints; target_pobjp->ship_max_hull_strength = source_shipp->ship_max_hull_strength; target_pobjp->initial_hull = fl2i(get_hull_pct(source_entry) * 100.0f); // ...and shields target_pobjp->ship_max_shield_strength = source_shipp->ship_max_shield_strength; target_pobjp->initial_shields = fl2i(get_shield_pct(source_entry) * 100.0f); target_pobjp->max_shield_recharge = source_shipp->max_shield_recharge; // search through all subsystems on source ship and map them onto target ship for (auto source_ss: list_range(&source_shipp->subsys_list)) { // find subsystem to configure auto target_sssp = parse_get_subsys_status(target_pobjp, source_ss->system_info->subobj_name); // gak... none allocated; we need to allocate one! if (target_sssp == nullptr) { // jam in the new subsystem at the end of the existing list for this parse object int new_idx = insert_subsys_status(target_pobjp); target_sssp = &Subsys_status[new_idx]; strcpy_s(target_sssp->name, source_ss->system_info->subobj_name); } // copy if (source_ss->max_hits == 0.0f) { target_sssp->percent = 100.0f; } else { target_sssp->percent = 100.0f - (source_ss->current_hits / source_ss->max_hits) * 100.0f; } } } // Goober5000 void sexp_ship_copy_damage(int node) { int n; // source ship must be present auto source = eval_ship(node); if (!source || !source->has_shipp()) return; // loop through all subsequent arguments for (n = CDR(node); n != -1; n = CDR(n)) { auto target = eval_ship(n); if (!target) continue; // maybe it's present in-mission if (target->has_shipp()) { ship_copy_damage(target, source); continue; } // maybe it's on the arrival list if (target->status == ShipStatus::NOT_YET_PRESENT) { parse_copy_damage(target, source); continue; } // must have departed or not even existed... do nothing } } //-Bobboau void sexp_activate_deactivate_glow_points(int n, bool activate) { for ( ; n != -1; n = CDR(n)) { auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) continue; for (auto &bank_active : ship_entry->shipp()->glow_point_bank_active) bank_active = activate; } } //-Bobboau void sexp_activate_deactivate_glow_point_bank(int n, bool activate) { auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) return; for ( n = CDR(n); n != -1; n = CDR(n)) { bool is_nan, is_nan_forever; int num = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) continue; if (num >= 0 && num < (int)ship_entry->shipp()->glow_point_bank_active.size()) ship_entry->shipp()->glow_point_bank_active[num] = activate; } } //-Bobboau void sexp_activate_deactivate_glow_maps(int n, bool activate) { for ( ; n != -1; n = CDR(n)) { auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) continue; ship_entry->shipp()->flags.set(Ship::Ship_Flags::Glowmaps_disabled, !activate); } } void sexp_set_ambient_light(int node) { int red, green, blue, level = 0; bool is_nan, is_nan_forever; Assert(node >= 0); eval_nums(node, is_nan, is_nan_forever, red, green, blue); if (is_nan || is_nan_forever) return; if (red < 0 || red > 255) red = 0; if (green < 0 || green > 255) green = 0; if (blue < 0 || blue > 255) blue = 0; level |= red; level |= green << 8; level |= blue << 16; // setting the ambient light level for the mission won't actually do anything as it is parsed at mission // start but it should be updated in case someone changes that later The_mission.ambient_light_level = level; // call the graphics function to actually set the level gr_set_ambient_light(red, green, blue); // do the multiplayer callback if (MULTIPLAYER_MASTER) { Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_int(level); Current_sexp_network_packet.end_callback(); } } void multi_sexp_set_ambient_light() { int level = 0; if (Current_sexp_network_packet.get_int(level)) { The_mission.ambient_light_level = level; gr_set_ambient_light((level & 0xff),((level >> 8) & 0xff), ((level >> 16) & 0xff)); } } void sexp_set_post_effect(int node) { std::array a1d; vec3d rgb; bool is_nan, is_nan_forever; auto name = CTEXT(node); if (name == nullptr || *name == '\0') return; node = CDR(node); int amount = eval_num(node, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; if (amount < 0 || amount > 100) amount = 0; node = CDR(node); eval_array(a1d, node, is_nan, is_nan_forever, [](int num)->float { float f = static_cast(num) / 255.0f; CAP(f, 0.0f, 1.0f); return f; }); if (is_nan || is_nan_forever) return; std::copy(a1d.begin(), a1d.end(), std::begin(rgb.a1d)); gr_post_process_set_effect(name, amount, &rgb); } void sexp_reset_post_effects() { gr_post_process_set_defaults(); } // Goober5000 void sexp_set_skybox_orientation(int n) { matrix m; angles a; bool is_nan, is_nan_forever; eval_angles(&a, n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; vm_angles_2_matrix(&m, &a); stars_set_background_orientation(&m); } // Goober5000 void sexp_set_skybox_alpha(int n) { int alphax1000; bool is_nan, is_nan_forever; alphax1000 = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; stars_set_background_alpha(alphax1000 / 1000.0f); } // taylor - load and set a skybox model void sexp_set_skybox_model(int n) { char new_skybox_model[TOKEN_LENGTH+1]; //max input is TOKEN_LENGTH, +1 for NUL strcpy_s(new_skybox_model, CTEXT(n)); int new_skybox_model_flags = DEFAULT_NMODEL_FLAGS; // check if we need to reset the animated texture timestamp n = CDR(n); if (n == -1 || is_sexp_true(n)) { Skybox_timestamp = _timestamp(); } if (n != -1) n = CDR(n); // gather any flags while (n != -1) { // this should check all entries in Skybox_flags if ( !stricmp("add-lighting", CTEXT(n) )) { new_skybox_model_flags &= ~MR_NO_LIGHTING; } else if ( !stricmp("no-transparency", CTEXT(n) )) { new_skybox_model_flags &= ~MR_ALL_XPARENT; } else if ( !stricmp("add-zbuffer", CTEXT(n) )) { new_skybox_model_flags &= ~MR_NO_ZBUFFER; } else if ( !stricmp("add-culling", CTEXT(n) )) { new_skybox_model_flags &= ~MR_NO_CULL; } else if ( !stricmp("no-glowmaps", CTEXT(n) )) { new_skybox_model_flags |= MR_NO_GLOWMAPS; } else if ( !stricmp("force-clamp", CTEXT(n) )) { new_skybox_model_flags |= MR_FORCE_CLAMP; } else { Warning(LOCATION, "Invalid flag passed to set-skybox-model: %s\n", CTEXT(n)); } n = CDR(n); } if ( !stricmp("default", new_skybox_model )) { stars_set_background_model( The_mission.skybox_model, nullptr, new_skybox_model_flags ); } else { // stars_level_init() will set the actual mission skybox after this gets // evaluated during parse. by setting it now we get everything loaded so // there is less slowdown when it actually swaps out - taylor stars_set_background_model( new_skybox_model, nullptr, new_skybox_model_flags ); } } // taylor - preload a skybox model. this doesn't set anything as viewable, just loads it into memory void sexp_set_skybox_model_preload(const char *name) { int i; if (!stricmp("default", name)) { name = The_mission.skybox_model; } // if there isn't a skybox model then don't load one if ( strlen(name) && stricmp(name, "none") != 0 ) { i = model_load( name ); model_page_in_textures( i ); } } void sexp_set_thrusters(int node) { bool activate = is_sexp_true(node); node = CDR(node); for (; node >= 0; node = CDR(node)) { auto ship_entry = eval_ship(node); if (!ship_entry || !ship_entry->has_shipp()) continue; ship_entry->shipp()->flags.set(Ship::Ship_Flags::No_thrusters, !activate); } } void sexp_beam_fire(int node, bool at_coords) { int idx, n = node; beam_fire_info fire_info; bool is_nan, is_nan_forever; // 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 auto shooter = eval_ship(n); if (!shooter || !shooter->has_shipp()) { return; } n = CDR(n); fire_info.shooter = shooter->objp(); // get the subsystem fire_info.turret = ship_get_subsys(shooter->shipp(), CTEXT(n)); if (fire_info.turret == nullptr) { return; } n = CDR(n); if (at_coords) { // get the target coordinates eval_vec3d(&fire_info.target_pos1, n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) { return; } fire_info.bfi_flags |= BFIF_TARGETING_COORDS; fire_info.target = nullptr; fire_info.target_subsys = nullptr; } else { // get the target auto target = eval_ship(n); if (!target || !target->has_shipp()) { return; } n = CDR(n); fire_info.target = target->objp(); // see if the optional subsystem can be found fire_info.target_subsys = nullptr; if (n >= 0) { fire_info.target_subsys = ship_get_subsys(target->shipp(), CTEXT(n)); n = CDR(n); } } // optionally force firing if (n >= 0 && is_sexp_true(n)) { fire_info.bfi_flags |= BFIF_FORCE_FIRING; n = CDR(n); } // get the second set of coordinates if (at_coords) { int count = eval_vec3d(&fire_info.target_pos2, n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) { return; } if (count == 0) { fire_info.target_pos2 = fire_info.target_pos1; } } // --- done getting arguments --- // if it has no primary weapons if (fire_info.turret->weapons.num_primary_banks <= 0) { Warning(LOCATION, "Couldn't fire turret on ship %s; subsystem %s has no primary weapons", CTEXT(node), CTEXT(CDR(node))); return; } // if the turret is destroyed if (!(fire_info.bfi_flags & BFIF_FORCE_FIRING) && 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; idxweapons.num_primary_banks; idx++) { Assertion(fire_info.turret->weapons.primary_bank_weapons[idx] >= 0 && fire_info.turret->weapons.primary_bank_weapons[idx] < weapon_info_size(), "sexp_beam_fire: found invalid weapon index (%i), get a coder\n!", fire_info.turret->weapons.primary_bank_weapons[idx]); // store the weapon info index if (Weapon_info[fire_info.turret->weapons.primary_bank_weapons[idx]].wi_flags[Weapon::Info_Flags::Beam]) { fire_info.beam_info_index = fire_info.turret->weapons.primary_bank_weapons[idx]; fire_info.bank = idx; } } // fire the beam if (fire_info.beam_info_index != -1) { fire_info.fire_method = BFM_TURRET_FORCE_FIRED; for ( int i = 0; i < Weapon_info[fire_info.beam_info_index].shots; i++ ) { fire_info.burst_index = i; beam_fire(&fire_info); fire_info.turret->turret_next_fire_pos++; } } else { // it would appear the turret doesn't have any beam weapons Warning(LOCATION, "Couldn't fire turret on ship %s; subsystem %s has no beam weapons", CTEXT(node), CTEXT(CDR(node))); } } void sexp_beam_floating_fire(int n) { bool is_nan, is_nan_forever; beam_fire_info fire_info; memset(&fire_info, 0, sizeof(beam_fire_info)); fire_info.accuracy = 0.000001f; // this will guarantee a hit fire_info.bfi_flags |= BFIF_FLOATING_BEAM; fire_info.turret = nullptr; // A free-floating beam isn't fired from a subsystem. fire_info.burst_index = 0; fire_info.beam_info_index = weapon_info_lookup(CTEXT(n)); n = CDR(n); if (fire_info.beam_info_index < 0) { Warning(LOCATION, "Invalid weapon class passed to beam-create; weapon type '%s' does not exist!\n", CTEXT(n)); return; } if (!(Weapon_info[fire_info.beam_info_index].wi_flags[Weapon::Info_Flags::Beam])) { Warning(LOCATION, "Invalid weapon class passed to beam-create; weapon type '%s' is not a beam!\n", CTEXT(n)); return; } fire_info.shooter = nullptr; if (stricmp(CTEXT(n), SEXP_NONE_STRING) != 0) { auto shooter = eval_ship(n); if (!shooter || !shooter->has_shipp()) return; fire_info.shooter = shooter->objp(); } n = CDR(n); fire_info.team = static_cast(iff_lookup(CTEXT(n))); n = CDR(n); eval_vec3d(&fire_info.starting_pos, n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; fire_info.target = nullptr; fire_info.target_subsys = nullptr; const ship_registry_entry *target = nullptr; if (stricmp(CTEXT(n), SEXP_NONE_STRING) != 0) { target = eval_ship(n); if (!target || !target->has_shipp()) return; fire_info.target = target->objp(); } else fire_info.bfi_flags |= BFIF_TARGETING_COORDS; n = CDR(n); if (n >= 0) { if (stricmp(CTEXT(n), SEXP_NONE_STRING) != 0) { if (target && target->has_shipp()) fire_info.target_subsys = ship_get_subsys(target->shipp(), CTEXT(n)); } n = CDR(n); } // if the nodes do not exist, the vector will be set to 0 eval_vec3d(&fire_info.target_pos1, n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; // if these nodes do not exist, the vector should be the same as the first int count = eval_vec3d(&fire_info.target_pos2, n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; if (count == 0) fire_info.target_pos2 = fire_info.target_pos1; fire_info.fire_method = BFM_SEXP_FLOATING_FIRED; beam_fire(&fire_info); } void sexp_beam_or_turret_free_one(ship_subsys *turret, bool is_beam, bool free) { if (is_beam) { if (free) { // flag it as beam free :) if (!(turret->weapons.flags[Ship::Weapon_Flags::Beam_Free])) { turret->weapons.flags.set(Ship::Weapon_Flags::Beam_Free); turret->turret_next_fire_stamp = timestamp(Random::next(50, 4000)); } } else { // flag it as not beam free turret->weapons.flags.remove(Ship::Weapon_Flags::Beam_Free); } } else { if (free) { // flag turret as no longer locked :) if (turret->weapons.flags[Ship::Weapon_Flags::Turret_Lock]) { turret->weapons.flags.remove(Ship::Weapon_Flags::Turret_Lock); turret->turret_next_fire_stamp = timestamp(Random::next(50, 4000)); } } else { // flag turret as locked turret->weapons.flags.set(Ship::Weapon_Flags::Turret_Lock); } } } void sexp_beam_or_turret_free_or_lock(int node, bool is_beam, bool free) { // get the firing ship auto shooter = eval_ship(node); if (!shooter || !shooter->has_shipp()) return; node = CDR(node); for ( ; node >= 0; node = CDR(node) ) { // get the subsystem auto turret = ship_get_subsys(shooter->shipp(), CTEXT(node)); if (!turret || turret->system_info->type != SUBSYSTEM_TURRET) continue; sexp_beam_or_turret_free_one(turret, is_beam, free); } } void sexp_beam_or_turret_free_or_lock_all(int node, bool is_beam, bool free) { for (int n = node; n >= 0; n = CDR(n)) { // get the firing ship auto shooter = eval_ship(n); if (!shooter || !shooter->has_shipp()) continue; // visit all beam weapons for (auto turret: list_range(&shooter->shipp()->subsys_list)) { if (turret->system_info->type != SUBSYSTEM_TURRET) continue; sexp_beam_or_turret_free_one(turret, is_beam, free); } } } void sexp_turret_tagged_or_clear_specific(int node, bool set_it) { // get the firing ship auto shooter = eval_ship(node); if (!shooter || !shooter->has_shipp()) return; node = CDR(node); for ( ; node >= 0; node = CDR(node)) { // get the subsystem auto turret = ship_get_subsys(shooter->shipp(), CTEXT(node)); if (!turret || turret->system_info->type != SUBSYSTEM_TURRET) continue; // flag turret as slaved to tag turret->weapons.flags.set(Ship::Weapon_Flags::Tagged_Only, set_it); } } void sexp_turret_tagged_only_or_clear_all(int node, bool set_it) { for (int n = node; n >= 0; n = CDR(n)) { // get the firing ship auto shooter = eval_ship(n); if (!shooter || !shooter->has_shipp()) continue; // visit all turrets for (auto turret: list_range(&shooter->shipp()->subsys_list)) { if (turret->system_info->type != SUBSYSTEM_TURRET) continue; turret->weapons.flags.set(Ship::Weapon_Flags::Tagged_Only, set_it); } } } void sexp_turret_change_weapon(int node) { int windex; //hehe ship_subsys *turret; ship_weapon *swp; // get the firing ship auto ship_entry = eval_ship(node); if (!ship_entry || !ship_entry->has_shipp()) { return; } node = CDR(node); //Get subsystem turret = ship_get_subsys(ship_entry->shipp(), CTEXT(node)); if (!turret) { return; } swp = &turret->weapons; node = CDR(node); windex = weapon_info_lookup(CTEXT(node)); if (windex < 0) { return; } node = CDR(node); //Get the slot float capacity, size; int prim_slot, sec_slot; bool is_nan, is_nan_forever; eval_nums(node, is_nan, is_nan_forever, prim_slot, sec_slot); if (is_nan || is_nan_forever) return; if (prim_slot) { if (prim_slot > MAX_SHIP_PRIMARY_BANKS) { return; } if (prim_slot > swp->num_primary_banks) { swp->num_primary_banks++; prim_slot = swp->num_primary_banks; } //Get an index prim_slot--; //Get the max capacity capacity = (float)swp->primary_bank_capacity[prim_slot]; size = (float)Weapon_info[windex].cargo_size; //Set various vars swp->primary_bank_start_ammo[prim_slot] = (int)(capacity / size); swp->primary_bank_ammo[prim_slot] = swp->primary_bank_start_ammo[prim_slot]; swp->primary_bank_weapons[prim_slot] = windex; swp->primary_bank_rearm_time[prim_slot] = timestamp(0); swp->primary_bank_fof_cooldown[prim_slot] = 0.0f; } else if (sec_slot) { if (sec_slot > MAX_SHIP_SECONDARY_BANKS) { return; } if (sec_slot > swp->num_secondary_banks) { swp->num_secondary_banks++; sec_slot = swp->num_secondary_banks; } //Get an index sec_slot--; //Get the max capacity capacity = (float)swp->secondary_bank_capacity[sec_slot]; size = (float)Weapon_info[windex].cargo_size; //Set various vars if (Weapon_info[windex].wi_flags[Weapon::Info_Flags::SecondaryNoAmmo]) swp->secondary_bank_start_ammo[sec_slot] = 0; else swp->secondary_bank_start_ammo[sec_slot] = (int)(capacity / size); swp->secondary_bank_ammo[sec_slot] = swp->secondary_bank_start_ammo[sec_slot]; swp->secondary_bank_weapons[sec_slot] = windex; swp->secondary_bank_rearm_time[sec_slot] = timestamp(0); } } void sexp_set_armor_type(int node) { int armor; bool rset; ship *shipp = nullptr; ship_info *sip = nullptr; // get ship auto ship_entry = eval_ship(node); if (!ship_entry || !ship_entry->has_shipp()) { return; } shipp = ship_entry->shipp(); sip = &Ship_info[shipp->ship_info_index]; node = CDR(node); // set or reset rset = is_sexp_true(node); node = CDR(node); // get armor auto armor_name = CTEXT(node); if (!stricmp(SEXP_NONE_STRING, armor_name)) { armor = -1; } else { armor = armor_type_get_idx(armor_name); } node = CDR(node); //Set armor process_ship_subsystems(ship_entry, true, node, true, "set-armor-type", [&](ProcessSubsystemType type, ship_subsys* ss) { if (type == ProcessSubsystemType::HULL || type == ProcessSubsystemType::SIM_HULL) { // we are setting the ship itself if (!rset) shipp->armor_type_idx = sip->armor_type_idx; else shipp->armor_type_idx = armor; } else if (type == ProcessSubsystemType::SHIELDS) { // we are setting the ships shields if (!rset) shipp->shield_armor_type_idx = sip->shield_armor_type_idx; else shipp->shield_armor_type_idx = armor; } else { // set the range if (!rset) ss->armor_type_idx = ss->system_info->armor_type_idx; else ss->armor_type_idx = armor; } }); } void sexp_weapon_set_damage_type(int node) { int windex, damage; bool swave, rset; size_t t; // weapon or shockwave swave = is_sexp_true(node); // get damage type node = CDR(node); if (!stricmp(SEXP_NONE_STRING, CTEXT(node))) damage = -1; else { for(t = 0; t < Damage_types.size(); t++) { if ( !stricmp(Damage_types[t].name, CTEXT(node))) break; } if (t == Damage_types.size()) return; damage = (int)t; } //Set or reset to default node = CDR(node); rset = is_sexp_true(node); //Set Damage node = CDR(node); while(node != -1) { // get the weapon windex = weapon_info_lookup(CTEXT(node)); if(windex >= 0) { // set the damage type if (swave) if (!rset) Weapon_info[windex].damage_type_idx = Weapon_info[windex].damage_type_idx_sav; else Weapon_info[windex].damage_type_idx = damage; else if (!rset) Weapon_info[windex].shockwave.damage_type_idx = Weapon_info[windex].shockwave.damage_type_idx_sav; else Weapon_info[windex].shockwave.damage_type_idx = damage; // next } node = CDR(node); } } void sexp_ship_set_damage_type(int node) { int damage; bool set_collision, rset; size_t t; // collision or debris set_collision = is_sexp_true(node); // get damage type node = CDR(node); if (!stricmp(SEXP_NONE_STRING, CTEXT(node))) damage = -1; else { for(t = 0; t < Damage_types.size(); t++) { if ( !stricmp(Damage_types[t].name, CTEXT(node))) break; } if (t == Damage_types.size()) return; damage = (int)t; } //Set or reset to default node = CDR(node); rset = is_sexp_true(node); //Set Damage node = CDR(node); while(node != -1) { // get the ship auto ship_entry = eval_ship(node); if (ship_entry && ship_entry->has_shipp()) { auto shipp = ship_entry->shipp(); // set the damage type if (set_collision) { if (!rset) shipp->collision_damage_type_idx = Ship_info[shipp->ship_info_index].collision_damage_type_idx; else shipp->collision_damage_type_idx = damage; } else { if (!rset) shipp->debris_damage_type_idx = Ship_info[shipp->ship_info_index].debris_damage_type_idx; else shipp->debris_damage_type_idx = damage; } } // next node = CDR(node); } } void sexp_ship_shockwave_set_damage_type(int node) { int sindex, damage; bool rset; size_t t; // get damage type if (!stricmp(SEXP_NONE_STRING, CTEXT(node))) damage = -1; else { for(t = 0; t < Damage_types.size(); t++) { if ( !stricmp(Damage_types[t].name, CTEXT(node))) break; } if (t == Damage_types.size()) return; damage = (int)t; } //Set or reset to default node = CDR(node); rset = is_sexp_true(node); //Set Damage node = CDR(node); while(node != -1) { // get the ship sindex = ship_info_lookup(CTEXT(node)); if(sindex >= 0) { // set the damage type if (!rset) Ship_info[sindex].shockwave.damage_type_idx = Ship_info[sindex].shockwave.damage_type_idx_sav; else Ship_info[sindex].shockwave.damage_type_idx = damage; } // next node = CDR(node); } } void sexp_field_set_damage_type(int node) { int damage, rset; size_t t; // get damage type if (!stricmp(SEXP_NONE_STRING, CTEXT(node))) damage = -1; else { for(t = 0; t < Damage_types.size(); t++) { if ( !stricmp(Damage_types[t].name, CTEXT(node))) break; } if (t == Damage_types.size()) return; damage = (int)t; } //Set or reset to default node = CDR(node); rset = is_sexp_true(node); //Set Damage node = CDR(node); for(t = 0; t < Asteroid_info.size(); t++) if (!rset) Asteroid_info[t].damage_type_idx = Asteroid_info[t].damage_type_idx_sav; else Asteroid_info[t].damage_type_idx = damage; } void sexp_turret_set_target_order(int node) { int i, oindex; // get ship auto ship_entry = eval_ship(node); if (!ship_entry || !ship_entry->has_shipp()) { return; } //Get turret subsys node = CDR(node); auto turret = ship_get_subsys(ship_entry->shipp(), CTEXT(node)); if(turret == nullptr){ return; } //Reset order for(i = 0; i < NUM_TURRET_ORDER_TYPES; i++) { turret->turret_targeting_order[i] = -1; } oindex = 0; node = CDR(node); while(node != -1) { if(oindex >= NUM_TURRET_ORDER_TYPES) { break; } for(i = 0; i < NUM_TURRET_ORDER_TYPES; i++) { if(!stricmp(Turret_target_order_names[i], CTEXT(node))) { turret->turret_targeting_order[oindex] = i; } } oindex++; node = CDR(node); } } void sexp_turret_set_direction_preference(int node) { bool is_nan, is_nan_forever; // get ship auto ship_entry = eval_ship(node); if (!ship_entry || !ship_entry->has_shipp()) { return; } //store direction preference node = CDR(node); int dirpref = eval_num(node, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; node = CDR(node); //Set range for (; node != -1; node = CDR(node)) { // get the subsystem auto turret = ship_get_subsys(ship_entry->shipp(), CTEXT(node)); if(turret == nullptr){ continue; } // set the range if(dirpref < 0) turret->optimum_range = turret->system_info->optimum_range; else if (dirpref == 0) { turret->favor_current_facing = 0.0f; } else { CAP(dirpref, 1, 100); turret->favor_current_facing = 1.0f + (((float) (100 - dirpref)) / 10.0f); } } } void sexp_turret_set_rate_of_fire(int node) { float rof; bool is_nan, is_nan_forever; // get ship auto ship_entry = eval_ship(node); if (!ship_entry || !ship_entry->has_shipp()) { return; } //store rof node = CDR(node); rof = (float)eval_num(node, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; node = CDR(node); //Set rof while (node >= 0) { // get the subsystem auto turret = ship_get_subsys(ship_entry->shipp(), CTEXT(node)); if (turret != nullptr) { // set the rate if (rof < 0) turret->rof_scaler = turret->system_info->turret_rof_scaler; else turret->rof_scaler = rof / 100; } // next node = CDR(node); } } void sexp_turret_set_optimum_range(int node) { float range; bool is_nan, is_nan_forever; // get ship auto ship_entry = eval_ship(node); if (!ship_entry || !ship_entry->has_shipp()) { return; } // store range node = CDR(node); range = (float)eval_num(node, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; node = CDR(node); //Set range while (node >= 0) { // get the subsystem auto turret = ship_get_subsys(ship_entry->shipp(), CTEXT(node)); if (turret != nullptr) { // set the range if (range < 0) turret->optimum_range = turret->system_info->optimum_range; else turret->optimum_range = range; } // next node = CDR(node); } } void sexp_turret_set_target_priorities(int node) { int i, j; // get ship auto ship_entry = eval_ship(node); if (!ship_entry || !ship_entry->has_shipp()) { return; } //Get turret subsys node = CDR(node); auto turret = ship_get_subsys(ship_entry->shipp(), CTEXT(node)); if(turret == nullptr){ return; } //Reset or new list node = CDR(node); if(!(is_sexp_true(node))) { //Reset turret->num_target_priorities = turret->system_info->num_target_priorities; for (j = 0; j < 32; j++) { turret->target_priority[j] = turret->system_info->target_priority[j]; } } else { //New List node = CDR(node); //clear the list turret->num_target_priorities = 0; for (i = 0; i < 32; i++) { turret->target_priority[i] = -1; } int num_groups = (int)Ai_tp_list.size(); // set the target priorities while(node != -1){ if(turret->num_target_priorities < 32){ for(j = 0; j < num_groups; j++) { if ( !stricmp(Ai_tp_list[j].name, CTEXT(node))) { turret->target_priority[turret->num_target_priorities] = j; turret->num_target_priorities++; } } } // next node = CDR(node); } } } void sexp_turret_set_forced_target(int node, bool targeting_subsys) { // get target auto target = eval_ship(node); if (!target || !target->has_shipp()) return; node = CDR(node); // maybe get target subsys ship_subsys* target_subsys = nullptr; if (targeting_subsys) { target_subsys = ship_get_subsys(target->shipp(), CTEXT(node)); if (target_subsys == nullptr) return; node = CDR(node); } // get ship auto shooter = eval_ship(node); if (!shooter || !shooter->has_shipp()) { return; } node = CDR(node); while (node >= 0) { // get the turret auto turret = ship_get_subsys(shooter->shipp(), CTEXT(node)); if (turret != nullptr) { turret->turret_enemy_objnum = target->objnum; turret->turret_enemy_sig = target->objp()->signature; turret->flags.set(Ship::Subsystem_Flags::Forced_target); turret->targeted_subsys = nullptr; if (targeting_subsys) { turret->targeted_subsys = target_subsys; turret->flags.set(Ship::Subsystem_Flags::Forced_subsys_target); } } // next node = CDR(node); } } void sexp_turret_clear_forced_target(int node) { // get ship auto ship_entry = eval_ship(node); if (!ship_entry || !ship_entry->has_shipp()) { return; } //Set range while (node >= 0) { // get the subsystem auto turret = ship_get_subsys(ship_entry->shipp(), CTEXT(node)); if (turret != nullptr) { turret->turret_enemy_objnum = -1; turret->targeted_subsys = nullptr; turret->flags.remove(Ship::Subsystem_Flags::Forced_target); turret->flags.remove(Ship::Subsystem_Flags::Forced_subsys_target); } // next node = CDR(node); } } void sexp_turret_set_inaccuracy(int node) { bool is_nan, is_nan_forever; // get ship auto shooter = eval_ship(node); if (!shooter || !shooter->has_shipp()) { return; } // get inaccuracy node = CDR(node); auto inaccuracy = (float)eval_num(node, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; if (inaccuracy < 0.0f) inaccuracy = 0.0f; // input is in tenths so divide by 10 inaccuracy /= 10.0f; node = CDR(node); if (node < 0) { // affect all turrets for (auto turret: list_range(&shooter->shipp()->subsys_list)) { if (turret->system_info->type != SUBSYSTEM_TURRET) continue; turret->turret_inaccuracy = inaccuracy; } } else { // affect just some particular turret(s) while (node >= 0) { // get the subsystem ship_subsys* turret = ship_get_subsys(shooter->shipp(), CTEXT(node)); if (turret != nullptr) turret->turret_inaccuracy = inaccuracy; // next node = CDR(node); } } } void sexp_ship_turret_target_order(int node) { int oindex; int i; int new_target_order[NUM_TURRET_ORDER_TYPES]; // get ship auto ship_entry = eval_ship(node); if (!ship_entry || !ship_entry->has_shipp()) { return; } //Reset order for(i = 0; i < NUM_TURRET_ORDER_TYPES; i++) { new_target_order[i] = -1; } oindex = 0; node = CDR(node); while(node != -1) { if(oindex >= NUM_TURRET_ORDER_TYPES) { break; } for(i = 0; i < NUM_TURRET_ORDER_TYPES; i++) { if(!stricmp(Turret_target_order_names[i], CTEXT(node))) { new_target_order[oindex] = i; } } oindex++; node = CDR(node); } for (auto turret: list_range(&ship_entry->shipp()->subsys_list)) { memcpy(turret->turret_targeting_order, new_target_order, NUM_TURRET_ORDER_TYPES*sizeof(int)); } } // Goober5000 int sexp_is_in_turret_fov(int node) { int n = node, range; bool is_nan, is_nan_forever; vec3d tpos, tvec; auto target_ship = eval_ship(n); if (!target_ship || target_ship->status == ShipStatus::NOT_YET_PRESENT) return SEXP_FALSE; if (target_ship->status == ShipStatus::EXITED) return SEXP_KNOWN_FALSE; n = CDR(n); auto turret_ship = eval_ship(n); if (!turret_ship || turret_ship->status == ShipStatus::NOT_YET_PRESENT) return SEXP_FALSE; if (turret_ship->status == ShipStatus::EXITED) return SEXP_KNOWN_FALSE; n = CDR(n); auto turret_subsys_name = CTEXT(n); n = CDR(n); if (n >= 0) { range = eval_num(n, is_nan, is_nan_forever); if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; } else range = -1; // find the turret auto turret_subsys = ship_get_subsys(turret_ship->shipp(), turret_subsys_name); if (turret_subsys == nullptr) { Warning(LOCATION, "Couldn't find turret subsystem '%s' on ship '%s' in sexp_is_in_turret_fov!", turret_subsys_name, turret_ship->name); return SEXP_KNOWN_FALSE; } // find out where the turret is ship_get_global_turret_info(turret_ship->objp(), turret_subsys->system_info, &tpos, &tvec); // see how far away is the target (this isn't used for a range check, only for vector math) float dist = vm_vec_dist(&target_ship->objp()->pos, &tpos); // but we can still use it for the range check if we are optionally checking that if (range >= 0 && dist > range) return SEXP_FALSE; // perform the check return object_in_turret_fov(target_ship->objp(), turret_subsys, &tvec, &tpos, dist) != 0 ? SEXP_TRUE : SEXP_FALSE; } // Goober5000 void sexp_set_subsys_motion_lock_free(int node, bool is_rotation, bool locked) { // get the ship auto ship_entry = eval_ship(node); if (!ship_entry || !ship_entry->has_shipp()) return; node = CDR(node); // loop for all specified subsystems for ( ; node >= 0; node = CDR(node) ) { // get the moving subsystem auto subsys = ship_get_subsys(ship_entry->shipp(), CTEXT(node)); if (subsys == nullptr) continue; // skip if it's already at the state we want; if not, set the flag if (is_rotation) { if (subsys->flags[Ship::Subsystem_Flags::Rotates] == !locked) continue; subsys->flags.set(Ship::Subsystem_Flags::Rotates, !locked); } else { if (subsys->flags[Ship::Subsystem_Flags::Translates] == !locked) continue; subsys->flags.set(Ship::Subsystem_Flags::Translates, !locked); } // set moving or not, depending on flag if (locked) { if (is_rotation && subsys->subsys_snd_flags[Ship::Subsys_Sound_Flags::Rotate]) { obj_snd_delete_type(ship_entry->objnum, subsys->system_info->rotation_snd, subsys); subsys->subsys_snd_flags.remove(Ship::Subsys_Sound_Flags::Rotate); } // bit of a hack... set the timestamp to just the delta so that we can restore it properly later auto &stamp = is_rotation ? subsys->submodel_instance_1->stepped_rotation_started : subsys->submodel_instance_1->stepped_translation_started; if (stamp.isValid()) stamp = TIMESTAMP(timestamp_since(stamp)); } else { if (is_rotation && subsys->system_info->rotation_snd.isValid()) { obj_snd_assign(ship_entry->objnum, subsys->system_info->rotation_snd, &subsys->system_info->pnt, OS_SUBSYS_ROTATION, subsys); subsys->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Rotate); } // and restore the timestamp from the delta auto &stamp = is_rotation ? subsys->submodel_instance_1->stepped_rotation_started : subsys->submodel_instance_1->stepped_translation_started; if (stamp.isValid()) stamp = timestamp_delta(_timestamp(), -stamp.value()); } } } // Goober5000 void sexp_reverse_moving_subsystem(int node, bool is_rotation) { // get the ship auto ship_entry = eval_ship(node); if (!ship_entry || !ship_entry->has_shipp()) return; node = CDR(node); // loop for all specified subsystems for ( ; node >= 0; node = CDR(node) ) { // get the moving subsystem auto subsys = ship_get_subsys(ship_entry->shipp(), CTEXT(node)); if (subsys == nullptr || subsys->submodel_instance_1 == nullptr) continue; // switch direction of motion if (is_rotation) { subsys->submodel_instance_1->current_turn_rate *= -1.0f; subsys->submodel_instance_1->desired_turn_rate *= -1.0f; } else { subsys->submodel_instance_1->current_shift_rate *= -1.0f; subsys->submodel_instance_1->desired_shift_rate *= -1.0f; } } } // Goober5000 void sexp_moving_subsys_set_turn_time_or_speed(int node, bool is_rotation) { int n = node; bool is_nan, is_nan_forever; float time_or_speed, accel; // get the ship auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) return; n = CDR(n); // get the moving subsystem auto subsys = ship_get_subsys(ship_entry->shipp(), CTEXT(n)); if (subsys == nullptr || subsys->submodel_instance_1 == nullptr) return; n = CDR(n); // get and set the value time_or_speed = eval_num(n, is_nan, is_nan_forever) / 1000.0f; if (is_nan || is_nan_forever) return; if (fl_near_zero(time_or_speed)) { Warning(LOCATION, "In %s, %s cannot be zero!", is_rotation ? "rotating-subsys-set-turn-time" : "translating-subsys-set-speed", is_rotation ? "turn time" : "speed"); return; } if (is_rotation) subsys->submodel_instance_1->desired_turn_rate = PI2 / time_or_speed; else subsys->submodel_instance_1->desired_shift_rate = time_or_speed; n = CDR(n); // maybe get and set the accel if (n != -1) { accel = eval_num(n, is_nan, is_nan_forever) / 1000.0f; if (is_nan || is_nan_forever) return; if (fl_near_zero(accel)) { Warning(LOCATION, "In %s, acceleration cannot be zero!", is_rotation ? "rotating-subsys-set-turn-time" : "translating-subsys-set-speed"); return; } if (is_rotation) subsys->submodel_instance_1->turn_accel = PI2 / accel; else subsys->submodel_instance_1->shift_accel = accel; } else { if (is_rotation) subsys->submodel_instance_1->current_turn_rate = PI2 / time_or_speed; else subsys->submodel_instance_1->current_shift_rate = time_or_speed; } } void sexp_trigger_submodel_animation(int node) { int animation_subtype, direction, n = node; bool instant, is_nan, is_nan_forever; // get the ship auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) return; n = CDR(n); // get the type auto animation_type = animation::anim_match_type(CTEXT(n)); if (animation_type == animation::ModelAnimationTriggerType::None) { Warning(LOCATION, "Unable to match animation type \"%s\"!", CTEXT(n)); return; } n = CDR(n); // get the subtype and direction eval_nums(n, is_nan, is_nan_forever, animation_subtype, direction); if (is_nan || is_nan_forever) return; if (direction != 1 && direction != -1) { Warning(LOCATION, "Direction is %d; it must be 1 or -1!", direction); return; } // instant or not if (n >= 0) { //TODO Respect in new animations instant = is_sexp_true(n); n = CDR(n); } else instant = false; // do we narrow it to a specific subsystem? if (n >= 0) { ship_subsys *ss = ship_get_subsys(ship_entry->shipp(), CTEXT(n)); if (!ss) { Warning(LOCATION, "Subsystem \"%s\" not found on ship \"%s\"!", CTEXT(n), CTEXT(node)); return; } Ship_info[ship_entry->shipp()->ship_info_index].animations.get(model_get_instance(ship_entry->shipp()->model_instance_num), animation_type, animation::anim_name_from_subsys(ss->system_info), animation_subtype) .start(direction == -1 ? animation::ModelAnimationDirection::RWD : animation::ModelAnimationDirection::FWD, instant, instant, false); } else { Ship_info[ship_entry->shipp()->ship_info_index].animations.getAll(model_get_instance(ship_entry->shipp()->model_instance_num), animation_type, animation_subtype) .start(direction == -1 ? animation::ModelAnimationDirection::RWD : animation::ModelAnimationDirection::FWD, instant, instant, false); } } void sexp_trigger_submodel_animation_new(int n) { auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) return; n = CDR(n); auto animation_type = animation::anim_match_type(CTEXT(n)); if (animation_type == animation::ModelAnimationTriggerType::None) { Warning(LOCATION, "Unable to match animation type \"%s\"!", CTEXT(n)); return; } n = CDR(n); SCP_string triggeredBy(CTEXT(n)); n = CDR(n); animation::ModelAnimationDirection direction; if (n >= 0) { direction = is_sexp_true(n) ? animation::ModelAnimationDirection::FWD : animation::ModelAnimationDirection::RWD; n = CDR(n); } else direction = animation::ModelAnimationDirection::FWD; bool forced; if (n >= 0) { forced = is_sexp_true(n); n = CDR(n); } else forced = false; bool instant; if (n >= 0) { instant = is_sexp_true(n); n = CDR(n); } else instant = false; bool pause; if (n >= 0) { pause = is_sexp_true(n); n = CDR(n); } else pause = false; const auto& list = Ship_info[ship_entry->shipp()->ship_info_index].animations.parseScripted(model_get_instance(ship_entry->shipp()->model_instance_num), animation_type, triggeredBy); list.start(direction, forced || instant, instant, pause); } void sexp_stop_looping_animation(int n) { auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) return; n = CDR(n); auto animation_type = animation::anim_match_type(CTEXT(n)); if (animation_type == animation::ModelAnimationTriggerType::None) { Warning(LOCATION, "Unable to match animation type \"%s\"!", CTEXT(n)); return; } n = CDR(n); SCP_string triggeredBy(CTEXT(n)); n = CDR(n); const auto& list = Ship_info[ship_entry->shipp()->ship_info_index].animations.parseScripted(model_get_instance(ship_entry->shipp()->model_instance_num), animation_type, triggeredBy); list.setFlag(animation::Animation_Instance_Flags::Stop_after_next_loop); } void sexp_update_moveable_animation(int node) { bool is_nan, is_nan_forever; auto ship_entry = eval_ship(node); if (!ship_entry || !ship_entry->has_shipp()) return; node = CDR(node); SCP_string name(CTEXT(node)); node = CDR(node); //For now this only contains integers. It is very much feasible though that certain moveables might be updateable with strings and other non-numbers. For this, the C-side of the moveable code already supports other types SCP_vector args; while(node >= 0) { args.emplace_back(eval_num(node, is_nan, is_nan_forever)); if(is_nan || is_nan_forever) Warning(LOCATION, "Value for moveable %s on ship %s was NaN!", name.c_str(), ship_entry->name); node = CDR(node); } Ship_info[ship_entry->shipp()->ship_info_index].animations.updateMoveable(model_get_instance(ship_entry->shipp()->model_instance_num), name, args); } void sexp_advance_moveable_animation(int node) { auto ship_entry = eval_ship(node); if (!ship_entry || !ship_entry->has_shipp()) return; node = CDR(node); SCP_string name(CTEXT(node)); Ship_info[ship_entry->shipp()->ship_info_index].animations.advanceMoveableToFinal(model_get_instance(ship_entry->shipp()->model_instance_num), name); } void sexp_add_remove_escort(int node) { int flag; bool is_nan, is_nan_forever; // get the firing ship auto ship_entry = eval_ship(node); if (!ship_entry || !ship_entry->has_shipp()) { return; } // determine whether to add or remove it flag = eval_num(CDR(node), is_nan, is_nan_forever); if (is_nan || is_nan_forever) { return; } // add/remove if (flag) { ship_entry->shipp()->escort_priority = flag; hud_add_ship_to_escort(ship_entry->objnum, 1); } else { hud_remove_ship_from_escort(ship_entry->objnum); } Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_ship(ship_entry->shipp()); Current_sexp_network_packet.send_int(flag); Current_sexp_network_packet.end_callback(); } void multi_sexp_add_remove_escort() { ship *shipp; int flag; Current_sexp_network_packet.get_ship(shipp); if (!(Current_sexp_network_packet.get_int(flag))) { return; } // add/remove if (flag) { shipp->escort_priority = flag; hud_add_ship_to_escort(shipp->objnum, 1); } else { hud_remove_ship_from_escort(shipp->objnum); } } //given: two escort priorities and a list of ships //do: sets the most damaged one to the first priority and the rest to the second. void sexp_damage_escort_list(int node) { int n = node; int priority1; //escort priority to set the most damaged ship int priority2; //"" "" to set the other ships bool is_nan, is_nan_forever; float smallest_hull_pct=1; //smallest hull pct found ship *small_shipp=nullptr; //entry in Ships[] of the above float current_hull_pct; //hull pct of current ship we are evaluating eval_nums(n, is_nan, is_nan_forever, priority1, priority2); if (is_nan || is_nan_forever) return; //loop through the ships for ( ; n != -1; n = CDR(n) ) { auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) continue; //calc hull integrity and compare current_hull_pct = get_hull_pct(ship_entry); if (current_hull_pct < smallest_hull_pct) { if (small_shipp) // avoid null during 1st loop iteration { small_shipp->escort_priority=priority2; //give the previous smallest the lower priority } smallest_hull_pct = current_hull_pct; small_shipp = ship_entry->shipp(); ship_entry->shipp()->escort_priority = priority1; //give the new smallest the higher priority hud_add_ship_to_escort(ship_entry->objnum, 1); } else //if its bigger to begin with give it lower priority { ship_entry->shipp()->escort_priority = priority2; hud_add_ship_to_escort(ship_entry->objnum, 1); } } } // Goober5000 - set stuff for mission support ship void sexp_set_support_ship(int n) { int temp_val; bool is_nan, is_nan_forever; // get arrival location auto arrival_location = ArrivalLocation::AT_LOCATION; int index = string_lookup(CTEXT(n), Arrival_location_names, MAX_ARRIVAL_NAMES); if (index >= 0) arrival_location = static_cast(index); else { Warning(LOCATION, "Support ship arrival location '%s' not found.\n", CTEXT(n)); return; } The_mission.support_ships.arrival_location = arrival_location; // get arrival anchor n = CDR(n); if (!stricmp(CTEXT(n), "")) { // if no anchor, set arrival location to hyperspace The_mission.support_ships.arrival_location = ArrivalLocation::AT_LOCATION; } else { // find the anchor The_mission.support_ships.arrival_anchor = anchor_t(ship_registry_get_index(CTEXT(n))); } // get departure location n = CDR(n); auto departure_location = DepartureLocation::AT_LOCATION; index = string_lookup(CTEXT(n), Departure_location_names, MAX_DEPARTURE_NAMES); if (index >= 0) departure_location = static_cast(index); else { Warning(LOCATION, "Support ship departure location '%s' not found.\n", CTEXT(n)); return; } The_mission.support_ships.departure_location = departure_location; // get departure anchor n = CDR(n); if (!stricmp(CTEXT(n), "")) { // if no anchor, set departure location to hyperspace The_mission.support_ships.departure_location = DepartureLocation::AT_LOCATION; } else { // find the anchor The_mission.support_ships.departure_anchor = anchor_t(ship_registry_get_index(CTEXT(n))); } // get ship class n = CDR(n); temp_val = ship_info_lookup(CTEXT(n)); if ((temp_val < 0) && ((stricmp(CTEXT(n), "") != 0) && (stricmp(CTEXT(n), "") != 0))) { Warning(LOCATION, "Support ship class '%s' not found.\n", CTEXT(n)); return; } if ((temp_val >= 0) && !(Ship_info[temp_val].flags[Ship::Info_Flags::Support])) { Warning(LOCATION, "Ship %s is not a support ship!", Ship_info[temp_val].name); return; } The_mission.support_ships.ship_class = temp_val; // get max number of ships allowed n = CDR(n); temp_val = eval_num(n, is_nan, is_nan_forever); if (!is_nan && !is_nan_forever) { The_mission.support_ships.max_support_ships = temp_val; } // get the number of concurrent ships allowed n = CDR(n); if ( n == -1 ) { // 7th arg not specified, set default The_mission.support_ships.max_concurrent_ships = 1; } else { temp_val = eval_num(n, is_nan, is_nan_forever); if (!is_nan && !is_nan_forever) { The_mission.support_ships.max_concurrent_ships = temp_val; } } } // Goober5000 - set stuff for arriving ships or wings void sexp_set_arrival_info(int node) { int arrival_mask, arrival_distance, arrival_delay, n = node; bool show_warp, adjust_warp_when_docked, is_nan, is_nan_forever; object_ship_wing_point_team oswpt; // get ship or wing eval_object_ship_wing_point_team(&oswpt, n); n = CDR(n); // get arrival location auto arrival_location = ArrivalLocation::AT_LOCATION; int index = string_lookup(CTEXT(n), Arrival_location_names, MAX_ARRIVAL_NAMES); if (index >= 0) arrival_location = static_cast(index); else { Warning(LOCATION, "Arrival location '%s' not found.\n", CTEXT(n)); return; } n = CDR(n); // get arrival anchor anchor_t arrival_anchor; if ((n < 0) || !stricmp(CTEXT(n), "")) { // if no anchor, set arrival location to hyperspace arrival_location = ArrivalLocation::AT_LOCATION; } else { // find the anchor arrival_anchor = anchor_t(ship_registry_get_index(CTEXT(n))); } n = CDR(n); // get arrival path mask, distance, and delay eval_nums(n, is_nan, is_nan_forever, arrival_mask, arrival_distance, arrival_delay); if (is_nan || is_nan_forever) return; // get warp effect show_warp = true; if (n >= 0) { show_warp = is_sexp_true(n); n = CDR(n); } // another flag adjust_warp_when_docked = true; if (n >= 0) { adjust_warp_when_docked = is_sexp_true(n); n = CDR(n); } // now set all that information depending on the first argument if (oswpt.type == OSWPT_TYPE_SHIP) { auto shipp = oswpt.shipp(); shipp->arrival_location = arrival_location; shipp->arrival_anchor = arrival_anchor; shipp->arrival_path_mask = arrival_mask; shipp->arrival_distance = arrival_distance; shipp->arrival_delay = arrival_delay; shipp->flags.set(Ship::Ship_Flags::No_arrival_warp, !show_warp); shipp->flags.set(Ship::Ship_Flags::Same_arrival_warp_when_docked, !adjust_warp_when_docked); } else if (oswpt.type == OSWPT_TYPE_WING || oswpt.type == OSWPT_TYPE_WING_NOT_PRESENT) { auto wingp = oswpt.wingp(); wingp->arrival_location = arrival_location; wingp->arrival_anchor = arrival_anchor; wingp->arrival_path_mask = arrival_mask; wingp->arrival_distance = arrival_distance; wingp->arrival_delay = arrival_delay; wingp->flags.set(Ship::Wing_Flags::No_arrival_warp, !show_warp); wingp->flags.set(Ship::Wing_Flags::Same_arrival_warp_when_docked, !adjust_warp_when_docked); } else if (oswpt.type == OSWPT_TYPE_PARSE_OBJECT) { auto p_objp = oswpt.p_objp(); p_objp->arrival_location = arrival_location; p_objp->arrival_anchor = arrival_anchor; p_objp->arrival_path_mask = arrival_mask; p_objp->arrival_distance = arrival_distance; p_objp->arrival_delay = arrival_delay; p_objp->flags.set(Mission::Parse_Object_Flags::SF_No_arrival_warp, !show_warp); p_objp->flags.set(Mission::Parse_Object_Flags::SF_Same_arrival_warp_when_docked, !adjust_warp_when_docked); } } // Goober5000 - set stuff for departing ships or wings void sexp_set_departure_info(int node) { int departure_mask, departure_delay, n = node; bool show_warp, adjust_warp_when_docked, is_nan, is_nan_forever; object_ship_wing_point_team oswpt; // get ship or wing eval_object_ship_wing_point_team(&oswpt, n); n = CDR(n); // get departure location auto departure_location = DepartureLocation::AT_LOCATION; int index = string_lookup(CTEXT(n), Departure_location_names, MAX_DEPARTURE_NAMES); if (index >= 0) departure_location = static_cast(index); else { Warning(LOCATION, "Departure location '%s' not found.\n", CTEXT(n)); return; } n = CDR(n); // get departure anchor anchor_t departure_anchor; if ((n < 0) || !stricmp(CTEXT(n), "")) { // if no anchor, set departure location to hyperspace departure_location = DepartureLocation::AT_LOCATION; } else { // find the anchor departure_anchor = anchor_t(ship_registry_get_index(CTEXT(n))); } n = CDR(n); // get departure path mask and delay eval_nums(n, is_nan, is_nan_forever, departure_mask, departure_delay); if (is_nan || is_nan_forever) return; // get warp effect show_warp = true; if (n >= 0) { show_warp = is_sexp_true(n); n = CDR(n); } // another flag adjust_warp_when_docked = true; if (n >= 0) { adjust_warp_when_docked = is_sexp_true(n); n = CDR(n); } // now set all that information depending on the first argument if (oswpt.type == OSWPT_TYPE_SHIP) { auto shipp = oswpt.shipp(); shipp->departure_location = departure_location; shipp->departure_anchor = departure_anchor; shipp->departure_path_mask = departure_mask; shipp->departure_delay = departure_delay; shipp->flags.set(Ship::Ship_Flags::No_departure_warp, !show_warp); shipp->flags.set(Ship::Ship_Flags::Same_departure_warp_when_docked, !adjust_warp_when_docked); } else if (oswpt.type == OSWPT_TYPE_WING || oswpt.type == OSWPT_TYPE_WING_NOT_PRESENT) { auto wingp = oswpt.wingp(); wingp->departure_location = departure_location; wingp->departure_anchor = departure_anchor; wingp->departure_path_mask = departure_mask; wingp->departure_delay = departure_delay; wingp->flags.set(Ship::Wing_Flags::No_departure_warp, !show_warp); wingp->flags.set(Ship::Wing_Flags::Same_departure_warp_when_docked, !adjust_warp_when_docked); } else if (oswpt.type == OSWPT_TYPE_PARSE_OBJECT) { auto p_objp = oswpt.p_objp(); p_objp->departure_location = departure_location; p_objp->departure_anchor = departure_anchor; p_objp->departure_path_mask = departure_mask; p_objp->departure_delay = departure_delay; p_objp->flags.set(Mission::Parse_Object_Flags::SF_No_departure_warp, !show_warp); p_objp->flags.set(Mission::Parse_Object_Flags::SF_Same_departure_warp_when_docked, !adjust_warp_when_docked); } } // Goober5000 // set *all* the escort priorities of ships in escort list as follows: most damaged ship gets // first priority in the argument list, next damaged gets next priority, etc.; if there are more // ships than priorities, all remaining ships get the final priority on the list // -- As indicated in the argument specification, there must be at least one argument but no more // than MAX_COMPLETE_ESCORT_LIST arguments void sexp_damage_escort_list_all(int n) { typedef struct { int index; float hull; } my_escort_ship; std::array priority; my_escort_ship escort_ship[MAX_COMPLETE_ESCORT_LIST]; int i, j, num_escort_ships, num_priorities, temp_i; bool is_nan, is_nan_forever; float temp_f; // build list of priorities num_priorities = eval_array(priority, n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; // build custom list of escort ships num_escort_ships = 0; for (auto so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so)) { auto objp = &Objects[so->objnum]; if (objp->flags[Object::Object_Flags::Should_be_dead]) continue; Assertion(objp->type == OBJ_SHIP, "All objects on Ship_obj_list must be ships!"); // make sure it's on the escort list if ( !(Ships[objp->instance].flags[Ship::Ship_Flags::Escort]) ) continue; // set index escort_ship[num_escort_ships].index = objp->instance; // calc and set hull integrity escort_ship[num_escort_ships].hull = get_hull_pct(objp); num_escort_ships++; } // sort it bubbly, lowest hull to highest hull for (i = 0; i < num_escort_ships; i++) { for (j = 0; j < i; j++) { if (escort_ship[i].hull < escort_ship[j].hull) { // swap temp_i = escort_ship[i].index; temp_f = escort_ship[i].hull; escort_ship[i].index = escort_ship[j].index; escort_ship[i].hull = escort_ship[j].hull; escort_ship[j].index = temp_i; escort_ship[j].hull = temp_f; } } } // loop through and assign priorities for (i = 0; i < num_escort_ships; i++) { if (i >= num_priorities) Ships[escort_ship[i].index].escort_priority = priority[num_priorities - 1]; else Ships[escort_ship[i].index].escort_priority = priority[i]; } // reorder the escort list hud_resort_escort_list(); } void sexp_awacs_set_radius(int node) { int radius; bool is_nan, is_nan_forever; // get the firing ship auto ship_entry = eval_ship(node); if (!ship_entry || !ship_entry->has_shipp()) return; // get the awacs subsystem auto awacs = ship_get_subsys(ship_entry->shipp(), CTEXT(CDR(node))); if (!awacs || !(awacs->system_info->flags[Model::Subsystem_Flags::Awacs])) return; // set the new awacs radius radius = eval_num(CDDR(node), is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; awacs->awacs_radius = (float)radius; } // Goober5000 void sexp_primitive_sensors_set_range(int n) { int range; bool is_nan, is_nan_forever; // get the ship auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) return; // set the new range range = eval_num(CDR(n), is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; ship_entry->shipp()->primitive_sensor_range = range; } //************************************************************************************************* // Kazan // AutoNav/AutoPilot system SEXPS //text: set-nav-carry //text: unset-nav-carry //args: 1+, Ship/Wing name void set_unset_nav_carry_status(int node, bool set_it) { for (int n = node; n >= 0; n = CDR(n)) { object_ship_wing_point_team oswpt; eval_object_ship_wing_point_team(&oswpt, n); switch (oswpt.type) { case OSWPT_TYPE_SHIP: oswpt.shipp()->flags.set(Ship::Ship_Flags::Navpoint_carry, set_it); break; case OSWPT_TYPE_WING: case OSWPT_TYPE_WING_NOT_PRESENT: oswpt.wingp()->flags.set(Ship::Wing_Flags::Nav_carry, set_it); break; default: break; } } } //text: set-nav-needslink //text: unset-nav-needslink //args: 1+, Ship name void set_unset_nav_needslink(int node, bool set_it) { for (int n = node; n >= 0; n = CDR(n)) { auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) continue; if (set_it) ship_entry->shipp()->flags.remove(Ship::Ship_Flags::Navpoint_carry); ship_entry->shipp()->flags.set(Ship::Ship_Flags::Navpoint_needslink, set_it); } } void add_nav_waypoint(object_ship_wing_point_team *oswpt, const char *nav, const char *WP_path, int vert) { bool add_for_this_player = true; if (oswpt) { // we can't assume this nav should be visible to the player any more add_for_this_player = false; switch (oswpt->type) { case OSWPT_TYPE_WHOLE_TEAM: if (oswpt->team == Player_ship->team) add_for_this_player = true; break; case OSWPT_TYPE_SHIP: if (oswpt->objnum == Player_ship->objnum) add_for_this_player = true; break; case OSWPT_TYPE_WING: for (int i = 0; i < oswpt->wingp()->current_count; ++i) if (Ships[oswpt->wingp()->ship_index[i]].objnum == Player_ship->objnum) add_for_this_player = true; break; // for all other oswpt types we simply ignore this default: break; } } AddNav_Waypoint(nav, WP_path, vert, add_for_this_player ? 0 : NP_HIDDEN); } //text: add-nav-waypoint //args: 4, Nav Name, Waypoint Path Name, Waypoint Path point, ShipWingTeam void add_nav_waypoint(int node) { object_ship_wing_point_team oswpt, *oswptp = nullptr; bool is_nan, is_nan_forever; auto nav_name = CTEXT(node); node = CDR(node); auto way_name = CTEXT(node); node = CDR(node); int vert = eval_num(node, is_nan, is_nan_forever); if (is_nan || is_nan_forever) { return; } node = CDR(node); if (node >= 0) { eval_object_ship_wing_point_team(&oswpt, node); oswptp = &oswpt; } add_nav_waypoint(oswptp, nav_name, way_name, vert); Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_string(nav_name); Current_sexp_network_packet.send_string(way_name); Current_sexp_network_packet.send_int(vert); if (oswptp) { Current_sexp_network_packet.send_string(oswptp->object_name); } Current_sexp_network_packet.end_callback(); } void multi_sexp_add_nav_waypoint() { char nav_name[TOKEN_LENGTH]; char way_name[TOKEN_LENGTH]; char oswpt_name[TOKEN_LENGTH]; int vert; object_ship_wing_point_team *oswptp = nullptr; if (!Current_sexp_network_packet.get_string(nav_name)) return; if (!Current_sexp_network_packet.get_string(way_name)) return; if (!Current_sexp_network_packet.get_int(vert)) return; object_ship_wing_point_team oswpt; if (Current_sexp_network_packet.get_string(oswpt_name)) { eval_object_ship_wing_point_team(&oswpt, -1, oswpt_name); oswptp = &oswpt; } add_nav_waypoint(oswptp, nav_name, way_name, vert); } //text: add-nav-ship //args: 2, Nav Name, Ship Name void add_nav_ship(int node) { auto nav_name = CTEXT(node); auto ship_name = CTEXT(CDR(node)); AddNav_Ship(nav_name, ship_name, 0); Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_string(nav_name); Current_sexp_network_packet.send_string(ship_name); Current_sexp_network_packet.end_callback(); } void multi_add_nav_ship() { char nav_name[TOKEN_LENGTH]; char ship_name[TOKEN_LENGTH]; if (!Current_sexp_network_packet.get_string(nav_name)) { return; } if (!Current_sexp_network_packet.get_string(ship_name)) { return; } AddNav_Ship(nav_name, ship_name, 0); } //text: del-nav //args: 1, Nav Name void del_nav(int node) { auto nav_name = CTEXT(node); DelNavPoint(nav_name); Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_string(nav_name); Current_sexp_network_packet.end_callback(); } void multi_del_nav() { char nav_name[TOKEN_LENGTH]; if (!Current_sexp_network_packet.get_string(nav_name)) { return; } DelNavPoint(nav_name); } //text: use-nav-cinematics //args: 1, boolean enable/disable void set_use_ap_cinematics(int node) { The_mission.flags.set(Mission::Mission_Flags::Use_ap_cinematics, is_sexp_true(node)); } //text: use-autopilot //args: 1, boolean enable/disable void set_use_ap(int node) { The_mission.flags.set(Mission::Mission_Flags::Deactivate_ap, !is_sexp_true(node)); } //text: hide-nav //text: unhide-nav //args: 1, Nav Name void hide_unhide_nav(int node, bool hide_it) { auto nav_name = CTEXT(node); if (hide_it) Nav_Set_Hidden(nav_name); else Nav_UnSet_Hidden(nav_name); } //text: restrict-nav //text: unrestrict-nav //args: 1, nav name void restrict_unrestrict_nav(int node, bool restrict_it) { auto nav_name = CTEXT(node); if (restrict_it) Nav_Set_NoAccess(nav_name); else Nav_UnSet_NoAccess(nav_name); } //text: set-nav-visited //text: unset-nav-visited //args: 1, Nav Name void set_unset_nav_visited(int node, bool set_it) { auto nav_name = CTEXT(node); if (set_it) Nav_Set_Visited(nav_name); else Nav_UnSet_Visited(nav_name); } //text: is-nav-visited //args: 1, Nav Name //rets: true/false int is_nav_visited(int node) { auto nav_name = CTEXT(node); return IsVisited(nav_name); } //text: is-nav_linked //args: 1, Ship name //rets: true/false int is_nav_linked(int node) { auto ship_entry = eval_ship(node); if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; if (ship_entry->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; return ship_entry->shipp()->flags[Ship::Ship_Flags::Navpoint_carry] ? SEXP_TRUE : SEXP_FALSE; } //text: distance-to-nav //args: 1, Nav Name //rets: distance to nav int distance_to_nav(int node) { auto nav_name = CTEXT(node); return DistanceTo(nav_name); } void select_unselect_nav(int node, bool select_it) { if (select_it) { auto nav_name = CTEXT(node); SelectNav(nav_name); } else DeselectNav(); } void set_nav_color(int node, bool visited) { std::array rgb; bool is_nan, is_nan_forever; eval_array(rgb, node, is_nan, is_nan_forever, [](int num)->ubyte { CLAMP(num, 0, 255); return static_cast(num); }); while (node >= 0) { auto nav_name = CTEXT(node); Nav_SetColor(nav_name, visited, rgb[0], rgb[1], rgb[2]); node = CDR(node); } } //************************************************************************************************* int sexp_is_tagged(int node) { auto ship_entry = eval_ship(node); if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; if (ship_entry->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; return ship_is_tagged(ship_entry->objp()) ? SEXP_TRUE : SEXP_FALSE; } // Joint effort of Sesquipedalian and Goober5000. Sesq found the code, mucked around making // sexps with it and learned things, Goober taught Sesq and made the sexp work properly. =D // Returns true so long as the player has held a missile lock for the specified time. // If the optional ship and/or ship's subsystem are specified, returns true when that // has been locked onto, but otherwise returns as long as anything has been locked onto. int sexp_missile_locked(int node) { int z; bool is_nan, is_nan_forever; // if we aren't targeting anything, it's false if ((Players_target == -1) || (Players_target == UNINITIALIZED)) return SEXP_FALSE; // if we aren't locked on to anything, it's false if (!Players_mlocked) return SEXP_FALSE; // do we have a specific ship? if (CDR(node) != -1) { // if we're not targeting the specific ship, it's false if (stricmp(Ships[Objects[Players_target].instance].ship_name, CTEXT(CDR(node))) != 0) return SEXP_FALSE; // do we have a specific subsystem? if (CDR(CDR(node)) != -1) { // if we aren't targeting a subsystem at all, it's false if (!Player_ai->targeted_subsys) return SEXP_FALSE; // if we're not targeting the specific subsystem, it's false if (subsystem_stricmp(Player_ai->targeted_subsys->system_info->subobj_name, CTEXT(CDR(CDR(node))))) return SEXP_FALSE; } } // if we've gotten this far, we must have satisfied whatever conditions the sexp imposed // finally, test if we've locked for a certain period of time z = eval_num(node, is_nan, is_nan_forever); if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; if (!Players_mlocked_timestamp.isValid() || timestamp_since(Players_mlocked_timestamp) < z * MILLISECONDS_PER_SECOND) return SEXP_FALSE; return SEXP_TRUE; } int sexp_is_player(int node) { bool standard_check = is_sexp_true(node); node = CDR(node); while (node >= 0) { auto player = get_player_from_ship_node(node, true); if (player) { // in single-player mode, maybe check for AI-controlled player ships if (!(Game_mode & GM_MULTIPLAYER) && standard_check && Player_use_ai) { return SEXP_FALSE; } } else { // ship is not a player return SEXP_FALSE; } node = CDR(node); } // if we reached this far they all checked out return SEXP_TRUE; } void sexp_set_respawns(int node) { int num_respawns; bool is_nan, is_nan_forever; // we're wasting our time if you can't respawn if (!(Game_mode & GM_MULTIPLAYER)) { return; } num_respawns = eval_num(node, is_nan, is_nan_forever); if (is_nan || is_nan_forever) { return; } node = CDR(node); // send the information to clients Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_int(num_respawns); for ( ; node >= 0; node = CDR(node)) { // get the parse object for the ship auto ship_entry = eval_ship(node); if (!ship_entry || !ship_entry->has_p_objp()) { continue; } ship_entry->p_objp()->respawn_count = num_respawns; Current_sexp_network_packet.send_parse_object(ship_entry->p_objp()); } Current_sexp_network_packet.end_callback(); } void multi_sexp_set_respawns() { int num_respawns; p_object *p_objp; Current_sexp_network_packet.get_int(num_respawns); while (Current_sexp_network_packet.get_parse_object(p_objp)) { p_objp->respawn_count = num_respawns; } } /** * get a hotkey for a one ship or wing */ int sexp_get_hotkey(int node) { object_ship_wing_point_team oswpt; eval_object_ship_wing_point_team(&oswpt, node); int hotkey; // returns the hotkey of the ship or wing // if argument is not ship or wing returns -1 if (oswpt.type == OSWPT_TYPE_SHIP) { hotkey = oswpt.shipp()->hotkey; } else if (oswpt.type == OSWPT_TYPE_WING) { hotkey = oswpt.wingp()->hotkey; } else { hotkey = -1; } return hotkey; } /** * set a hotkey for one or more ships/wings */ void sexp_add_remove_hotkey(int node) { int objnum, setnum, n = node; bool is_adding, is_nan, is_nan_forever; // True for add, False for remove is_adding = is_sexp_true(n); n = CDR(n); setnum = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) { return; } n = CDR(n); // first, only proceed if setnum number is a valid hotkey // look for ship name -- if found, then add or remove hotkey to ship // if not a ship, look for wing name, then add or remove hotkey to each ship in wing // note, hud_target_hotkey_add_remove() checks if the object has arrived or is destroyed or departed if (setnum >= 0 && setnum < MAX_KEYED_TARGETS) { for (; n != -1; n = CDR(n)) { object_ship_wing_point_team oswpt; eval_object_ship_wing_point_team(&oswpt, n); if (oswpt.type == OSWPT_TYPE_SHIP) { objnum = oswpt.objnum; // check if the ship already has this hot-key and if sexp is adding or removing it if (((oswpt.shipp()->hotkey == setnum) && !is_adding) || ((oswpt.shipp()->hotkey != setnum) && is_adding) ) { hud_target_hotkey_add_remove(setnum, &Objects[objnum], HOTKEY_USER_ADDED); } } else if (oswpt.type == OSWPT_TYPE_WING) { for (int i = 0; i < oswpt.wingp()->current_count; i++) { auto shipp = &Ships[oswpt.wingp()->ship_index[i]]; objnum = shipp->objnum; // check if the ship already has this hot-key and if sexp is adding or removing it if (((shipp->hotkey == setnum) && !is_adding) || ((shipp->hotkey != setnum) && is_adding)) { hud_target_hotkey_add_remove(setnum, &Objects[objnum], HOTKEY_USER_ADDED); } } } } } } // helper function for the clear-weapons and clear-debris SEXPs void actually_clear_weapons_or_debris(int op_num, int class_index) { for (auto objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) { if (objp->flags[Object::Object_Flags::Should_be_dead]) continue; if (op_num == OP_CLEAR_WEAPONS && objp->type != OBJ_WEAPON) continue; if (op_num == OP_CLEAR_DEBRIS && objp->type != OBJ_DEBRIS) continue; if (class_index >= 0) { // weapon doesn't match the class if (op_num == OP_CLEAR_WEAPONS && (Weapons[objp->instance].weapon_info_index != class_index)) continue; // debris doesn't match the class if (op_num == OP_CLEAR_DEBRIS && (Debris[objp->instance].ship_info_index != class_index)) continue; } objp->flags.set(Object::Object_Flags::Should_be_dead); } } void sexp_clear_weapons_or_debris(int node, int op_num) { int class_index = -1; // if we have the optional argument, read it in if (node >= 0) { if (op_num == OP_CLEAR_WEAPONS) { class_index = weapon_info_lookup(CTEXT(node)); if (class_index == -1) { Warning(LOCATION, "Clear-weapons attempted to remove %s. Weapon class not found. Clear-weapons will remove all weapons currently in the mission\n", CTEXT(node)); } } else if (op_num == OP_CLEAR_DEBRIS) { class_index = ship_info_lookup(CTEXT(node)); if (class_index == -1) { Warning(LOCATION, "Clear-debris attempted to remove %s. Ship class not found. Clear-debris will remove all debris currently in the mission\n", CTEXT(node)); } } } actually_clear_weapons_or_debris(op_num, class_index); // send the information to clients Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_int(class_index); Current_sexp_network_packet.end_callback(); } void multi_sexp_clear_weapons_or_debris(int op_num) { int class_index = -1; Current_sexp_network_packet.get_int(class_index); actually_clear_weapons_or_debris(op_num, class_index); } int sexp_return_player_data(int node, int type) { int np_index; auto p = get_player_from_ship_node(node, true, &np_index); // now, if we have a valid player, return his kills if (p) { switch (type) { case OP_NUM_KILLS: return p->stats.m_kill_count_ok; case OP_NUM_ASSISTS: return p->stats.m_assists; case OP_SHIP_SCORE: return p->stats.m_score; case OP_SHIP_DEATHS: return p->stats.m_player_deaths; case OP_RESPAWNS_LEFT: // Dogfight missions have no respawn limit if (MULTI_NOT_DOGFIGHT) { if ( Net_players[np_index].flags & NETINFO_FLAG_RESPAWNING ) { // since the player hasn't actually respawned yet he hasn't used up a number or spawns equal to his deaths // so add an extra life back. return Netgame.respawn - p->stats.m_player_deaths + 1; } else { return Netgame.respawn - p->stats.m_player_deaths; } } break; default: Error(LOCATION, "return-player-data was called with invalid type %d on node %d!", type, node); } } // AI ships also have a respawn count so we can return valid data for that at least else if ( (Game_mode & GM_MULTIPLAYER) && (type == OP_SHIP_DEATHS || type == OP_RESPAWNS_LEFT ) ) { auto ship_entry = eval_ship(node); if (!ship_entry || !ship_entry->has_p_objp()) { return 0; } if (ship_entry->p_objp()->flags[Mission::Parse_Object_Flags::OF_Player_start]) { switch (type) { case OP_SHIP_DEATHS: // when an AI ship is finally killed its respawn count won't be updated so get the number of deaths // from the log instead return mission_log_get_count(LOG_SHIP_DESTROYED, ship_entry->p_objp()->name, nullptr) + mission_log_get_count(LOG_SELF_DESTRUCTED, ship_entry->p_objp()->name, nullptr); case OP_RESPAWNS_LEFT: return Netgame.respawn - ship_entry->p_objp()->respawn_count; default: // We should never reach this. Assert(false); } } } // AI ships return 0; } int sexp_num_type_kills(int node) { auto p = get_player_from_ship_node(node, true); if (!p) return 0; // look up ship type name auto name = CTEXT(CDR(node)); int constraint_type = ship_type_name_lookup(name); if (constraint_type < 0) { if (Fighter_bomber_valid && !stricmp(name, Fighter_bomber_type_name)) constraint_type = Ship_type_fighter_bomber; else return 0; } // look stuff up int total = 0; for (int idx = 0; idx < ship_info_size(); idx++) { if (p->stats.m_okKills[idx] <= 0) continue; int ship_type = ship_class_query_general_type(idx); if (constraint_type == Ship_type_fighter_bomber) { if (ship_type != Ship_type_fighter && ship_type != Ship_type_bomber) continue; } else { if (ship_type != constraint_type) continue; } total += p->stats.m_okKills[idx]; } // total return total; } int sexp_num_class_kills(int node) { auto p = get_player_from_ship_node(node, true); if (!p) { return 0; } // get the ship type we're looking for int si_index = ship_info_lookup(CTEXT(CDR(node))); if ((si_index < 0) || (si_index >= ship_info_size())) { return 0; } // return the count return p->stats.m_okKills[si_index]; } void sexp_subsys_set_random(int node) { int low, high, n = node, idx, rand, exclusion_list[MAX_MODEL_SUBSYSTEMS]; bool is_nan, is_nan_forever; ship_subsys *subsys; ship *shipp; // get ship auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) { return; } shipp = ship_entry->shipp(); n = CDR(n); // get low and high eval_nums(n, is_nan, is_nan_forever, low, high); if (is_nan || is_nan_forever) { return; } if (low < 0) { low = 0; } if (high > 100) { high = 100; } if (low > high) { Warning(LOCATION, "subsys-set-random was passed an invalid range (%d ... %d)!", low, high); return; } // 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_find_subsys(shipp, CTEXT(n)); if (exclude_index >= 0) { exclusion_list[exclude_index] = 1; } n = CDR(n); } // apply to all others for (idx=0; idxship_info_index].n_subsystems; idx++) { if ( exclusion_list[idx] == 0 ) { // get non excluded subsystem subsys = ship_get_indexed_subsys(shipp, idx); if (subsys == nullptr) { nprintf(("Warning", "Nonexistent subsystem for index %d on ship %s for subsys set random\n", idx, shipp->ship_name)); continue; } // randomize its hit points rand = rand_internal(low, high); set_subsys_strength_and_maybe_ancestors(shipp, subsys, nullptr, &rand, nullptr, nullptr, true, true); } } // needed to keep aggregate info correct ship_recalc_subsys_strength(shipp); } void sexp_supernova_start(int node) { bool is_nan, is_nan_forever; int countdown = eval_num(node, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; supernova_start(countdown); } void sexp_supernova_stop(int /*node*/) { supernova_stop(); } int sexp_get_supernova_stage(int /*node*/) { return static_cast(supernova_stage()); } int sexp_is_weapon_selected(int node, bool primary) { int bank, num_banks; bool is_nan, is_nan_forever; // lookup ship auto ship_entry = eval_ship(node); if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; if (ship_entry->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; num_banks = primary ? ship_entry->shipp()->weapons.num_primary_banks : ship_entry->shipp()->weapons.num_secondary_banks; // bogus value? bank = eval_num(CDR(node), is_nan, is_nan_forever); if (is_nan_forever) return SEXP_KNOWN_FALSE; if (is_nan || bank >= num_banks) return SEXP_FALSE; // is this the bank currently selected if (primary) { if (ship_entry->shipp()->flags[Ship::Ship_Flags::Primary_linked]) return SEXP_TRUE; if (bank == ship_entry->shipp()->weapons.current_primary_bank) return SEXP_TRUE; } else { if (bank == ship_entry->shipp()->weapons.current_secondary_bank) return SEXP_TRUE; } // nope return SEXP_FALSE; } // Return SEXP_TRUE if quadrant quadnum is near max. int shield_quad_near_max(int quadnum) { int n_quadrants = static_cast(Player_obj->shield_quadrant.size()); if (quadnum < 0 || quadnum >= n_quadrants) return SEXP_FALSE; float remaining = 0.0f; for (int i=0; ishield_quadrant[i]; } if ((remaining < 2.0f) || (Player_obj->shield_quadrant[quadnum] > shield_get_max_quad(Player_obj) - 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; case 1: // Fired Interceptors for (auto mop: list_range(&Missile_obj_list)) { auto objp = &Objects[mop->objnum]; if (objp->flags[Object::Object_Flags::Should_be_dead]) continue; 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 (!(subsystem_stricmp(Player_ai->targeted_subsys->system_info->name, "Weapons"))) { if (Player_ai->current_target_is_locked){ return SEXP_TRUE; } } } } return SEXP_FALSE; case 3: // Player ship suffering shield damage on front. if (!(Ship_info[Player_ship->ship_info_index].flags[Ship::Info_Flags::Model_point_shields])) { shield_apply_damage(Player_obj, FRONT_QUAD, 10.0f); hud_shield_quadrant_hit(Player_obj, FRONT_QUAD); return SEXP_TRUE; } else { nprintf(("Warning", "Shield-related Special-check SEXPs do not work on ship %s because it uses model point shields.\n", Player_ship->ship_name)); return SEXP_FALSE; } break; case 4: // Player ship suffering much damage. if (!(Ship_info[Player_ship->ship_info_index].flags[Ship::Info_Flags::Model_point_shields])) { nprintf(("AI", "Frame %i\n", Framecount)); shield_apply_damage(Player_obj, FRONT_QUAD, 10.0f); hud_shield_quadrant_hit(Player_obj, FRONT_QUAD); if (Player_obj->shield_quadrant[FRONT_QUAD] < 2.0f) return SEXP_TRUE; else return SEXP_FALSE; } else { nprintf(("Warning", "Shield-related Special-check SEXPs do not work on ship %s because it uses model point shields.\n", Player_ship->ship_name)); return SEXP_FALSE; } break; case 5: // Player's shield is quick repaired if (!(Ship_info[Player_ship->ship_info_index].flags[Ship::Info_Flags::Model_point_shields])) { nprintf(("AI", "Frame %i, recharged to %7.3f\n", Framecount, Player_obj->shield_quadrant[FRONT_QUAD])); shield_apply_damage(Player_obj, FRONT_QUAD, -flFrametime*200.0f); if (Player_obj->shield_quadrant[FRONT_QUAD] > shield_get_max_quad(Player_obj)) Player_obj->shield_quadrant[FRONT_QUAD] = shield_get_max_quad(Player_obj); if (Player_obj->shield_quadrant[FRONT_QUAD] > Player_obj->shield_quadrant[(FRONT_QUAD+1)%DEFAULT_SHIELD_SECTIONS] - 2.0f) return SEXP_TRUE; else return SEXP_FALSE; } else { nprintf(("Warning", "Shield-related Special-check SEXPs do not work on ship %s because it uses model point shields.\n", Player_ship->ship_name)); return SEXP_FALSE; } break; case 6: // 3 of player's shield quadrants are reduced to 0. if (!(Ship_info[Player_ship->ship_info_index].flags[Ship::Info_Flags::Model_point_shields])) { Player_obj->shield_quadrant[1] = 1.0f; Player_obj->shield_quadrant[2] = 1.0f; Player_obj->shield_quadrant[3] = 1.0f; hud_shield_quadrant_hit(Player_obj, FRONT_QUAD); } else { nprintf(("Warning", "Shield-related Special-check SEXPs do not work on ship %s because it uses model point shields.\n", Player_ship->ship_name)); return SEXP_FALSE; } return SEXP_TRUE; case 7: // Make sure front quadrant has been maximized, or close to it. if (!(Ship_info[Player_ship->ship_info_index].flags[Ship::Info_Flags::Model_point_shields])) { if (shield_quad_near_max(FRONT_QUAD)) return SEXP_TRUE; else return SEXP_FALSE; } else { nprintf(("Warning", "Shield-related Special-check SEXPs do not work on ship %s because it uses model point shields.\n", Player_ship->ship_name)); return SEXP_FALSE; } break; case 8: // Make sure rear quadrant has been maximized, or close to it. if (!(Ship_info[Player_ship->ship_info_index].flags[Ship::Info_Flags::Model_point_shields])) { if (shield_quad_near_max(REAR_QUAD)) return SEXP_TRUE; else return SEXP_FALSE; } else { nprintf(("Warning", "Shield-related Special-check SEXPs do not work on ship %s because it uses model point shields.\n", Player_ship->ship_name)); return SEXP_FALSE; } break; case 9: // Zero left and right quadrants in preparation for maximizing rear quadrant. if (!(Ship_info[Player_ship->ship_info_index].flags[Ship::Info_Flags::Model_point_shields])) { Player_obj->shield_quadrant[LEFT_QUAD] = 0.0f; Player_obj->shield_quadrant[RIGHT_QUAD] = 0.0f; hud_shield_quadrant_hit(Player_obj, LEFT_QUAD); return SEXP_TRUE; } else { nprintf(("Warning", "Shield-related Special-check SEXPs do not work on ship %s because it uses model point shields.\n", Player_ship->ship_name)); return SEXP_FALSE; } 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. if (!(Ship_info[Player_ship->ship_info_index].flags[Ship::Info_Flags::Model_point_shields])) { Player_obj->shield_quadrant[FRONT_QUAD] = 0.0f; hud_shield_quadrant_hit(Player_obj, FRONT_QUAD); return SEXP_TRUE; } else { nprintf(("Warning", "Shield-related Special-check SEXPs do not work on ship %s because it uses model point shields.\n", Player_ship->ship_name)); return SEXP_FALSE; } 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: Warning(LOCATION, "special-check SEXP was given an unsupported check number (%d)!", index); } return SEXP_FALSE; } // Karajorma / Goober5000 int sexp_string_to_int(int n) { bool first_ch = true; char *buf_ch, buf[TOKEN_LENGTH]; Assert (n != -1); if (Sexp_nodes[n].cache) return Sexp_nodes[n].cache->numeric_literal; // maybe forward to a special-arg node if (Sexp_nodes[n].flags & SNF_SPECIAL_ARG_IN_NODE) { auto current_argument = Sexp_replacement_arguments.back(); int arg_node = current_argument.second; if (arg_node >= 0) return sexp_string_to_int(arg_node); } // copy all numeric characters to buf // also, copy a sign symbol if we haven't copied numbers yet buf_ch = buf; for (auto ch = CTEXT(n); *ch != 0; ch++) { if ((first_ch && (*ch == '-' || *ch == '+')) || strchr("0123456789", *ch)) { *buf_ch = *ch; buf_ch++; first_ch = false; } // don't save the fractional parts of decimal numbers if (*ch == '.') break; } // terminate string *buf_ch = '\0'; int num = atoi(buf); // cache the value if it can't change later and we're in-game if (!Fred_running && !is_node_value_dynamic(n)) Sexp_nodes[n].cache = std::make_unique(OPF_NUMBER, num, -1); return num; } // Goober5000 void sexp_int_to_string(int n) { int i, sexp_variable_index; bool is_nan, is_nan_forever; char new_text[TOKEN_LENGTH]; // Only do single player or multi host if (MULTIPLAYER_CLIENT) return; i = eval_num(n, is_nan, is_nan_forever); n = CDR(n); // get sexp_variable index sexp_variable_index = sexp_get_variable_index(n); if (sexp_variable_index < 0) { Warning(LOCATION, "int-to-string: Variable %s does not exist!", Sexp_nodes[n].text); return; } // check variable type if (!(Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_STRING)) { Warning(LOCATION, "int-to-string: Cannot assign a string to a non-string variable %s!", Sexp_variables[sexp_variable_index].variable_name); return; } // write string if (is_nan || is_nan_forever) sprintf(new_text, "NaN"); else sprintf(new_text, "%d", i); // assign to variable sexp_modify_variable(new_text, sexp_variable_index); } // Goober5000 void sexp_string_concatenate(int n) { int sexp_variable_index; char new_text[TOKEN_LENGTH * 2]; // Only do single player or multi host if ( MULTIPLAYER_CLIENT ) return; auto str1 = CTEXT(n); n = CDR(n); auto str2 = CTEXT(n); n = CDR(n); // get sexp_variable index sexp_variable_index = sexp_get_variable_index(n); if (sexp_variable_index < 0) { Warning(LOCATION, "string-concatenate: Variable %s does not exist!", Sexp_nodes[n].text); return; } // check variable type if (!(Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_STRING)) { Warning(LOCATION, "string-concatenate: Cannot assign a string to a non-string variable %s!", Sexp_variables[sexp_variable_index].variable_name); return; } // concatenate strings strcpy_s(new_text, str1); strcat_s(new_text, str2); // check length if (strlen(new_text) >= TOKEN_LENGTH) { Warning(LOCATION, "string-concatenate: Concatenated string '%s' has " SIZE_T_ARG " characters, but the maximum is %d. The string will be truncated.", new_text, strlen(new_text), TOKEN_LENGTH - 1); new_text[TOKEN_LENGTH] = 0; } // assign to variable sexp_modify_variable(new_text, sexp_variable_index); } // Goober5000 void sexp_string_concatenate_block(int n) { int sexp_variable_index; SCP_string new_text; // Only do single player or multi host if (MULTIPLAYER_CLIENT) return; // get sexp_variable index sexp_variable_index = sexp_get_variable_index(n); if (sexp_variable_index < 0) { Warning(LOCATION, "string-concatenate-block: Variable %s does not exist!", Sexp_nodes[n].text); return; } n = CDR(n); // check variable type if (!(Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_STRING)) { Warning(LOCATION, "string-concatenate-block: Cannot assign a string to a non-string variable %s!", Sexp_variables[sexp_variable_index].variable_name); return; } // concatenate strings while (n >= 0) { new_text.append(CTEXT(n)); n = CDR(n); } // check length if (new_text.length() >= TOKEN_LENGTH) { Warning(LOCATION, "string-concatenate-block: Concatenated string '%s' has " SIZE_T_ARG " characters, but the maximum is %d. The string will be truncated.", new_text.c_str(), new_text.length(), TOKEN_LENGTH - 1); new_text.resize(TOKEN_LENGTH - 1); } // assign to variable sexp_modify_variable(new_text.c_str(), sexp_variable_index); } // Goober5000 int sexp_string_get_length(int node) { auto text = CTEXT(node); return (int)unicode::num_codepoints(text, text + strlen(text)); } // Goober5000 void sexp_string_get_substring(int node) { int n = node, pos, len, sexp_variable_index; bool is_nan, is_nan_forever; char new_text[TOKEN_LENGTH]; memset(new_text, 0, TOKEN_LENGTH); // Only do single player or multi host if ( MULTIPLAYER_CLIENT ) return; auto parent = CTEXT(n); n = CDR(n); eval_nums(n, is_nan, is_nan_forever, pos, len); // get sexp_variable index sexp_variable_index = sexp_get_variable_index(n); if (sexp_variable_index < 0) { Warning(LOCATION, "string-get-substring: Variable %s does not exist!", Sexp_nodes[n].text); return; } // check variable type if (!(Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_STRING)) { Warning(LOCATION, "string-get-substring: Cannot assign a string to a non-string variable %s!", Sexp_variables[sexp_variable_index].variable_name); return; } // now actually get the substring if (!is_nan && !is_nan_forever) { auto parent_byte_len = strlen(parent); auto parent_end = parent + parent_byte_len; auto parent_len = (int)unicode::num_codepoints(parent, parent_end); // sanity if (pos >= parent_len) { Warning(LOCATION, "( string-get-substring %s %d %d ) failed: starting position is larger than the string length!", parent, pos, len); return; } // sanity if (pos + len > parent_len) len = parent_len - pos; // copy substring auto start_ptr = parent; // Advance the pointer by n codepoints to the start of our substring unicode::advance(start_ptr, static_cast(pos), parent_end); auto end_ptr = start_ptr; unicode::advance(end_ptr, static_cast(len), parent_end); auto byte_diff = end_ptr - start_ptr; strncpy(new_text, start_ptr, byte_diff); } // assign to variable sexp_modify_variable(new_text, sexp_variable_index); } // Goober5000 void sexp_string_set_substring(int node) { int n = node, pos, len, sexp_variable_index; bool is_nan, is_nan_forever; // Only do single player or multi host if ( MULTIPLAYER_CLIENT ) return; auto parent = CTEXT(n); n = CDR(n); eval_nums(n, is_nan, is_nan_forever, pos, len); if (is_nan || is_nan_forever) return; auto new_substring = CTEXT(n); n = CDR(n); // get sexp_variable index sexp_variable_index = sexp_get_variable_index(n); if (sexp_variable_index < 0) { Warning(LOCATION, "string-set-substring: Variable %s does not exist!", Sexp_nodes[n].text); return; } // check variable type if (!(Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_STRING)) { Warning(LOCATION, "string-set-substring: Cannot assign a string to a non-string variable %s!", Sexp_variables[sexp_variable_index].variable_name); return; } int parent_byte_len = (int)strlen(parent); auto parent_len = (int)unicode::num_codepoints(parent, parent + parent_byte_len); // sanity if (pos >= parent_len) { Warning(LOCATION, "( string-set-substring %s %d %d %s ) failed: starting position is larger than the string length!", parent, pos, len, new_substring); return; } SCP_string new_text = parent; auto range = unicode::codepoint_range(parent); auto end_iter = range.end(); size_t substring_begin_byte = 0; size_t substring_end_byte = 0; auto i = 0; for (auto iter = range.begin(); iter != end_iter; ++iter, ++i) { if (i == pos) { substring_begin_byte = iter.pos() - parent; } else if (i == pos + len) { substring_end_byte = iter.pos() - parent; // We have reached the end byte so we have done everything we need to break; } } // This shouldn't happen Assertion(substring_begin_byte < substring_end_byte, "string-set-substring: The begin position of the substring must be less than the end position!"); new_text.replace(substring_begin_byte, substring_end_byte - substring_begin_byte, new_substring); if (new_text.size() >= TOKEN_LENGTH) { Warning(LOCATION, "string-set-substring: Concatenated string is too long and will be truncated."); new_text.resize(TOKEN_LENGTH - 1); // This might have broken the UTF-8 sequence so that needs to be fixed in Unicode mode if (Unicode_text_mode) { auto invalid = utf8::find_invalid(new_text.begin(), new_text.end()); if (invalid != new_text.end()) { // Found an invalid sequence. End the string right before that to make sure the string is still valid new_text.erase(invalid, new_text.end()); } } } // assign to variable sexp_modify_variable(new_text.c_str(), sexp_variable_index); } void sexp_modify_variable_xstr(int n) { Assert(n >= 0); // Only do single player or multi host if ( MULTIPLAYER_CLIENT ) return; // get sexp_variable index auto sexp_variable_index = sexp_get_variable_index(n); if (sexp_variable_index < 0) { Warning(LOCATION, "modify-variable-xstr: Variable %s does not exist!", Sexp_nodes[n].text); return; } n = CDR(n); if (!(Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_STRING)) { Warning(LOCATION, "modify-variable-xstr: Variable %s must be a string variable!", Sexp_variables[sexp_variable_index].variable_name); return; } // get new string const char* new_text = Sexp_nodes[n].text; // assign to variable sexp_modify_variable(new_text, sexp_variable_index); } // breaks into the code or sends a warning void sexp_debug(int node) { int i; SCP_string warning_message; #ifdef NDEBUG bool no_release_message = is_sexp_true(node); #endif node = CDR(node); Assertion (node >= 0, "No message defined in debug SEXP"); // we'll suppose it's the string for now warning_message = CTEXT(node); // but use an actual message if one exists for (i=0; i= 0) { Warning(LOCATION, "HUD gauge '%s' is not a legacy gauge; flashing is not supported", name); } } void multi_sexp_flash_hud_gauge() { int i; if (Current_sexp_network_packet.get_int(i)) { hud_gauge_start_flash(i); } } void sexp_set_training_context_fly_path(int node) { bool is_nan, is_nan_forever; int wp_list = find_matching_waypoint_list_index(CTEXT(node)); if (wp_list < 0) return; int distance = eval_num(CDR(node), is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; Training_context |= TRAINING_CONTEXT_FLY_PATH; Training_context_waypoint_path = wp_list; Training_context_distance = static_cast(distance); Training_context_goal_waypoint = 0; Training_context_at_waypoint = -1; } void sexp_set_training_context_speed(int node) { int min, max; bool is_nan, is_nan_forever; eval_nums(node, is_nan, is_nan_forever, min, max); if (is_nan || is_nan_forever) return; Training_context |= TRAINING_CONTEXT_SPEED; Training_context_speed_min = min; Training_context_speed_max = max; Training_context_speed_set = 0; } void sexp_scramble_messages(int node, bool scramble) { if (node < 0) { // scramble messages on player ship... this isn't multi compatible, but neither was the old version of the sexp Player_ship->flags.set(Ship::Ship_Flags::Scramble_messages, scramble); return; } sexp_deal_with_ship_flag(node, true, Object::Object_Flags::NUM_VALUES, Ship::Ship_Flags::Scramble_messages, Mission::Parse_Object_Flags::SF_Scramble_messages, scramble, false); } void toggle_cutscene_bars(float delta_speed, int set) { //Do we want the bars? if (set) { Cutscene_bar_flags |= CUB_CUTSCENE; } else { Cutscene_bar_flags &= ~CUB_CUTSCENE; } if(delta_speed > 0.0f) { Cutscene_bars_progress = 0.0f; Cutscene_bar_flags |= CUB_GRADUAL; Cutscene_delta_time = delta_speed; } else { Cutscene_bar_flags &= ~CUB_GRADUAL; } } void sexp_toggle_cutscene_bars(int node, int set) { bool is_nan, is_nan_forever; float delta_speed = 0.0f; if (node != -1) { delta_speed = eval_num(node, is_nan, is_nan_forever) / 1000.0f; if (is_nan || is_nan_forever) { return; } } toggle_cutscene_bars(delta_speed, set); Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_float(delta_speed); Current_sexp_network_packet.end_callback(); } void multi_sexp_toggle_cutscene_bars(int set) { float delta_speed; if(Current_sexp_network_packet.get_float(delta_speed) ) { toggle_cutscene_bars(delta_speed, set); } } void sexp_fade(bool fade_in, int duration, ubyte R, ubyte G, ubyte B) { if (duration > 0) { Fade_start_timestamp = _timestamp(); Fade_end_timestamp = _timestamp(duration); Fade_type = fade_in ? FadeType::FI_FADEIN : FadeType::FI_FADEOUT; gr_create_shader(&Viewer_shader, R, G, B, Viewer_shader.c); } else { Fade_type = FadeType::FI_NONE; gr_create_shader(&Viewer_shader, R, G, B, fade_in ? 0 : 255); } } void sexp_fade(int n, bool fade_in) { int duration = 0, R, G, B; bool is_nan, is_nan_forever; std::array temp_rgb; // get duration if present if (n >= 0) { duration = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; n = CDR(n); } // get color if present, with range checks, defaulting to -1 eval_array(temp_rgb, n, is_nan, is_nan_forever, [](int num) { return (num < 0 || num > 255) ? -1 : num; }, -1); if (is_nan || is_nan_forever) return; R = temp_rgb[0]; G = temp_rgb[1]; B = temp_rgb[2]; // select legacy (or default) fade color if (R < 0 || G < 0 || B < 0) { // fade white if (R == 1) { R = G = B = 255; } // fade red else if (R == 2) { R = 255; G = B = 0; } // default: fade black or previous fadeout color else { // Mantis #2944: if we're fading in, use the same color we used to fade out with if (fade_in) { R = Fade_out_r; G = Fade_out_g; B = Fade_out_b; } else { R = G = B = 0; } } } // Mantis #2944 // If we're fading out, save its color so we can fade-in with the same color if (!fade_in) { Fade_out_r = R; Fade_out_g = G; Fade_out_b = B; } sexp_fade(fade_in, duration, (ubyte) R, (ubyte) G, (ubyte) B); Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_int(duration); Current_sexp_network_packet.send_int(R); Current_sexp_network_packet.send_int(G); Current_sexp_network_packet.send_int(B); Current_sexp_network_packet.end_callback(); } void multi_sexp_fade(bool fade_in) { int duration = 0; int R = 0; int G = 0; int B = 0; if (Current_sexp_network_packet.get_int(duration)) if (Current_sexp_network_packet.get_int(R)) if (Current_sexp_network_packet.get_int(G)) Current_sexp_network_packet.get_int(B); sexp_fade(fade_in, duration, (ubyte) R, (ubyte) G, (ubyte) B); } camera* sexp_get_set_camera(bool reset = false) { static camid sexp_camera; if (!reset) { if (Viewer_mode & VM_FREECAMERA) { camera *cam = cam_get_current().getCamera(); if (cam != nullptr) return cam; } } if (!sexp_camera.isValid()) { sexp_camera = cam_create("SEXP camera"); } cam_set_camera(sexp_camera); return sexp_camera.getCamera(); } void sexp_set_camera(int node) { game_set_photo_mode_allowed(false); if (node < 0) { sexp_get_set_camera(true); return; } auto cam_name = CTEXT(node); camid cid = cam_lookup(cam_name); if (!cid.isValid()) { cid = cam_create(cam_name); } cam_set_camera(cid); } void sexp_set_camera_position(int n) { camera *cam = sexp_get_set_camera(); if (cam == nullptr) return; bool is_nan, is_nan_forever; vec3d camera_vec; float camera_time, camera_acc_time, camera_dec_time; eval_vec3d(&camera_vec, n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; int count = eval_nums(n, is_nan, is_nan_forever, camera_time, camera_acc_time, camera_dec_time); if (is_nan || is_nan_forever) return; if (count == 2) camera_dec_time = camera_acc_time; camera_time /= 1000.0f; camera_acc_time /= 1000.0f; camera_dec_time /= 1000.0f; cam->set_position(&camera_vec, camera_time, camera_acc_time, camera_dec_time); //multiplayer callback Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_float(camera_vec.xyz.x); Current_sexp_network_packet.send_float(camera_vec.xyz.y); Current_sexp_network_packet.send_float(camera_vec.xyz.z); Current_sexp_network_packet.send_float(camera_time); Current_sexp_network_packet.send_float(camera_acc_time); Current_sexp_network_packet.send_float(camera_dec_time); Current_sexp_network_packet.end_callback(); } //CommanderDJ void multi_sexp_set_camera_position() { camera *cam = sexp_get_set_camera(); Assert(cam != nullptr); vec3d camera_vec; float camera_time = 0.0f; float camera_acc_time = 0.0f; float camera_dec_time = 0.0f; Current_sexp_network_packet.get_float(camera_vec.xyz.x); Current_sexp_network_packet.get_float(camera_vec.xyz.y); Current_sexp_network_packet.get_float(camera_vec.xyz.z); Current_sexp_network_packet.get_float(camera_time); Current_sexp_network_packet.get_float(camera_acc_time); Current_sexp_network_packet.get_float(camera_dec_time); cam->set_position(&camera_vec, camera_time, camera_acc_time, camera_dec_time); } void sexp_set_camera_rotation(int n) { camera *cam = sexp_get_set_camera(); if (cam == nullptr) return; bool is_nan, is_nan_forever; angles rot_angles; float rot_time, rot_acc_time, rot_dec_time; // Angles are in degrees eval_angles(&rot_angles, n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; int count = eval_nums(n, is_nan, is_nan_forever, rot_time, rot_acc_time, rot_dec_time); if (is_nan || is_nan_forever) return; if (count == 2) rot_dec_time = rot_acc_time; rot_time /= 1000.0f; rot_acc_time /= 1000.0f; rot_dec_time /= 1000.0f; cam->set_rotation(&rot_angles, rot_time, rot_acc_time, rot_dec_time); Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_float(rot_angles.b); Current_sexp_network_packet.send_float(rot_angles.h); Current_sexp_network_packet.send_float(rot_angles.p); Current_sexp_network_packet.send_float(rot_time); Current_sexp_network_packet.send_float(rot_acc_time); Current_sexp_network_packet.send_float(rot_dec_time); Current_sexp_network_packet.end_callback(); } void multi_sexp_set_camera_rotation() { camera *cam = sexp_get_set_camera(); if (cam == nullptr) return; angles rot_angles; float rot_time = 0.0f; float rot_acc_time = 0.0f; float rot_dec_time = 0.0f; Current_sexp_network_packet.get_float(rot_angles.b); Current_sexp_network_packet.get_float(rot_angles.h); Current_sexp_network_packet.get_float(rot_angles.p); Current_sexp_network_packet.get_float(rot_time); Current_sexp_network_packet.get_float(rot_acc_time); Current_sexp_network_packet.get_float(rot_dec_time); cam->set_rotation(&rot_angles, rot_time, rot_acc_time, rot_dec_time); } void sexp_set_camera_facing(int n) { camera *cam = sexp_get_set_camera(); if (cam == nullptr) return; bool is_nan, is_nan_forever; vec3d location; float rot_time, rot_acc_time, rot_dec_time; eval_vec3d(&location, n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; int count = eval_nums(n, is_nan, is_nan_forever, rot_time, rot_acc_time, rot_dec_time); if (is_nan || is_nan_forever) return; if (count == 2) rot_dec_time = rot_acc_time; rot_time /= 1000.0f; rot_acc_time /= 1000.0f; rot_dec_time /= 1000.0f; cam->set_rotation_facing(&location, rot_time, rot_acc_time, rot_dec_time); //multiplayer callback Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_float(location.xyz.x); Current_sexp_network_packet.send_float(location.xyz.y); Current_sexp_network_packet.send_float(location.xyz.z); Current_sexp_network_packet.send_float(rot_time); Current_sexp_network_packet.send_float(rot_acc_time); Current_sexp_network_packet.send_float(rot_dec_time); Current_sexp_network_packet.end_callback(); } void multi_sexp_set_camera_facing() { camera *cam = sexp_get_set_camera(); if (cam == nullptr) return; vec3d location; float rot_time = 0.0f; float rot_acc_time = 0.0f; float rot_dec_time = 0.0f; Current_sexp_network_packet.get_float(location.xyz.x); Current_sexp_network_packet.get_float(location.xyz.y); Current_sexp_network_packet.get_float(location.xyz.z); Current_sexp_network_packet.get_float(rot_time); Current_sexp_network_packet.get_float(rot_acc_time); Current_sexp_network_packet.get_float(rot_dec_time); cam->set_rotation_facing(&location, rot_time, rot_acc_time, rot_dec_time); } void sexp_set_camera_facing_object(int n) { camera *cam = sexp_get_set_camera(); if (cam == nullptr) return; object_ship_wing_point_team oswpt; vec3d *pos; eval_object_ship_wing_point_team(&oswpt, n); switch (oswpt.type) { case OSWPT_TYPE_EXITED: { Warning(LOCATION, "Camera tried to face destroyed/departed object %s", oswpt.object_name); return; } case OSWPT_TYPE_SHIP: case OSWPT_TYPE_WING: case OSWPT_TYPE_WAYPOINT: { pos = &oswpt.objp()->pos; break; } default: return; } n = CDR(n); bool is_nan, is_nan_forever; float rot_time, rot_acc_time, rot_dec_time; // Now get the rotation time values int count = eval_nums(n, is_nan, is_nan_forever, rot_time, rot_acc_time, rot_dec_time); if (is_nan || is_nan_forever) return; if (count == 2) rot_dec_time = rot_acc_time; rot_time /= 1000.0f; rot_acc_time /= 1000.0f; rot_dec_time /= 1000.0f; cam->set_rotation_facing(pos, rot_time, rot_acc_time, rot_dec_time); //multiplayer callback Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_vec3d(pos); Current_sexp_network_packet.send_float(rot_time); Current_sexp_network_packet.send_float(rot_acc_time); Current_sexp_network_packet.send_float(rot_dec_time); Current_sexp_network_packet.end_callback(); } //CommanderDJ void multi_sexp_set_camera_facing_object() { camera *cam = sexp_get_set_camera(); if (cam == nullptr) return; vec3d pos; float rot_time = 0.0f; float rot_acc_time = 0.0f; float rot_dec_time = 0.0f; Current_sexp_network_packet.get_vec3d(&pos); Current_sexp_network_packet.get_float(rot_time); Current_sexp_network_packet.get_float(rot_acc_time); Current_sexp_network_packet.get_float(rot_dec_time); cam->set_rotation_facing(&pos, rot_time, rot_acc_time, rot_dec_time); } void sexp_set_camera_fov(int n) { camera *cam = sexp_get_set_camera(); if (cam == nullptr) return; bool is_nan, is_nan_forever; int int_fov; float camera_fov, camera_time, camera_acc_time, camera_dec_time = 0.0f; int_fov = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; n = CDR(n); camera_fov = fl_radians(int_fov % 360); int count = eval_nums(n, is_nan, is_nan_forever, camera_time, camera_acc_time, camera_dec_time); if (is_nan || is_nan_forever) return; if (count == 2) camera_dec_time = camera_acc_time; camera_time /= 1000.0f; camera_acc_time /= 1000.0f; camera_dec_time /= 1000.0f; cam->set_fov(camera_fov, camera_time, camera_acc_time, camera_dec_time); Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_float(camera_fov); Current_sexp_network_packet.send_float(camera_time); Current_sexp_network_packet.send_float(camera_acc_time); Current_sexp_network_packet.send_float(camera_dec_time); Current_sexp_network_packet.end_callback(); } //CommanderDJ void multi_sexp_set_camera_fov() { camera *cam = sexp_get_set_camera(); if (cam == nullptr) return; float camera_fov = g3_get_hfov(VIEWER_ZOOM_DEFAULT); float camera_time = 0.0f; float camera_acc_time = 0.0f; float camera_dec_time = 0.0f; Current_sexp_network_packet.get_float(camera_fov); Current_sexp_network_packet.get_float(camera_time); Current_sexp_network_packet.get_float(camera_acc_time); Current_sexp_network_packet.get_float(camera_dec_time); cam->set_fov(camera_fov, camera_time, camera_acc_time, camera_dec_time); } //Internal helper function for set-target and set-host object *sexp_camera_get_objsub(int node, int *o_submodel) { //Important variables object *objp = nullptr; int submodel = -1; //Get arguments int n = node; object_ship_wing_point_team oswpt; eval_object_ship_wing_point_team(&oswpt, n); n = CDR(n); const char *sub_name = nullptr; if (n >= 0) sub_name = CTEXT(n); //*****Process object switch (oswpt.type) { case OSWPT_TYPE_SHIP: case OSWPT_TYPE_WING: case OSWPT_TYPE_WAYPOINT: objp = oswpt.objp(); break; default: objp = nullptr; } //*****Process submodel if(objp != nullptr && sub_name != nullptr && oswpt.type == OSWPT_TYPE_SHIP) { if(stricmp(sub_name, SEXP_NONE_STRING) != 0) { ship_subsys *ss = ship_get_subsys(&Ships[objp->instance], sub_name); if (ss != nullptr) { submodel = ss->system_info->subobj_num; } } } if(o_submodel != nullptr) *o_submodel = submodel; return objp; } void sexp_set_camera_host(int node) { //Try to get current camera camera *cam = sexp_get_set_camera(); if (cam == nullptr) return; //*****Get variables int submodel = -1; object *objp = sexp_camera_get_objsub(node, &submodel); //*****Set cam->set_object_host(objp, submodel); } void sexp_set_camera_target(int node) { //Try to get current camera camera *cam = sexp_get_set_camera(); if (cam == nullptr) return; //*****Get variables int submodel = -1; object *objp = sexp_camera_get_objsub(node, &submodel); //*****Set cam->set_object_target(objp, submodel); Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_object(objp); Current_sexp_network_packet.send_int(submodel); Current_sexp_network_packet.end_callback(); } void multi_sexp_set_camera_target() { int submodel; object *objp; //Try to get current camera camera *cam = sexp_get_set_camera(); if (cam == nullptr) return; Current_sexp_network_packet.get_object(objp); Current_sexp_network_packet.get_int(submodel); cam->set_object_target(objp, submodel); } void sexp_set_fov(int n) { camera *cam = Main_camera.getCamera(); if (cam == nullptr) { game_render_frame_setup(); cam = Main_camera.getCamera(); } bool is_nan, is_nan_forever; int int_fov = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; //Cap FOV to something reasonable. float new_fov = i2fl(int_fov % 360); Sexp_fov = fl_radians(new_fov); Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_float(new_fov); Current_sexp_network_packet.end_callback(); } void multi_sexp_set_fov() { float new_fov; camera *cam = Main_camera.getCamera(); if (cam == nullptr) { game_render_frame_setup(); cam = Main_camera.getCamera(); } Current_sexp_network_packet.get_float(new_fov); Sexp_fov = fl_radians(new_fov); } int sexp_get_fov() { camera *cam = Main_camera.getCamera(); if (cam == nullptr) return -1; else if (Sexp_fov > 0.0f) // SEXP override has been set return (int)fl_degrees(Sexp_fov); else return (int)fl_degrees(g3_get_hfov(cam->get_fov())); } /** * @todo Check VIEWER_ZOOM_DEFAULT */ void sexp_reset_fov() { camera *cam = Main_camera.getCamera(); if (cam == nullptr) return; Sexp_fov = 0.0; //cam->set_fov(VIEWER_ZOOM_DEFAULT); Current_sexp_network_packet.do_callback(); } void multi_sexp_reset_fov() { camera *cam = Main_camera.getCamera(); if (cam == nullptr) return; Sexp_fov = 0.0; } void sexp_reset_camera(int node) { game_set_photo_mode_allowed(true); bool cam_reset = false; camera *cam = cam_get_current().getCamera(); if (cam != nullptr) { if (is_sexp_true(node)) { cam->reset(); cam_reset = true; } } cam_reset_camera(); Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_bool(cam_reset); Current_sexp_network_packet.end_callback(); } void multi_sexp_reset_camera() { game_set_photo_mode_allowed(true); camera *cam = cam_get_current().getCamera(); bool cam_reset = false; Current_sexp_network_packet.get_bool(cam_reset); if ((cam != nullptr) && cam_reset) { cam->reset(); } cam_reset_camera(); } void sexp_show_subtitle(int node) { //These should be set to the default if not required to be explicitly defined int x_pos, y_pos, width = 0, line_height_modifier = 0; const char *text, *imageanim = nullptr; float display_time, fade_time = 0.0f; int n = node; std::array rgb = { 255, 255, 255 }; bool is_nan, is_nan_forever, center_x = false, center_y = false, post_shaded = false; eval_nums(n, is_nan, is_nan_forever, x_pos, y_pos); if (is_nan || is_nan_forever) return; if (gr_screen.center_w != 1024) x_pos = (int)((x_pos / 1024.0f) * gr_screen.center_w); if (gr_screen.center_h != 768) y_pos = (int)((y_pos / 768.0f) * gr_screen.center_h); text = CTEXT(n); n = CDR(n); display_time = eval_num(n, is_nan, is_nan_forever) / 1000.0f; //is in ms if (is_nan || is_nan_forever) return; n = CDR(n); if (n != -1) { imageanim = CTEXT(n); n = CDR(n); if (n != -1) { fade_time = eval_num(n, is_nan, is_nan_forever) / 1000.0f; //also in ms if (is_nan || is_nan_forever) return; n = CDR(n); if (n != -1) { center_x = is_sexp_true(n); n = CDR(n); if (n != -1) { center_y = is_sexp_true(n); n = CDR(n); if (n != -1) { width = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; n = CDR(n); if (n != -1) { eval_array(rgb, n, is_nan, is_nan_forever, [](int num)->int { CLAMP(num, 0, 255); return num; }, 255); if (is_nan || is_nan_forever) return; if (n != -1) { post_shaded = is_sexp_true(n); n = CDR(n); if (n != -1) { line_height_modifier = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; } } } } } } } } //FINALLY !! color new_color; gr_init_alphacolor(&new_color, rgb[0], rgb[1], rgb[2], 255); subtitle new_subtitle(x_pos, y_pos, text, imageanim, display_time, fade_time, &new_color, -1, center_x, center_y, width, 0, post_shaded, line_height_modifier); Subtitles.push_back(new_subtitle); } void sexp_clear_subtitles() { Subtitles.clear(); Current_sexp_network_packet.do_callback(); } void multi_sexp_clear_subtitles() { Subtitles.clear(); } void get_subtitle_screen_size(int& w, int& h) { // if we are doing screen scaling, then set the size to the base resolution // (it will be adjusted when the subtitle is constructed) if (Show_subtitle_screen_base_res[0] > 0 && Show_subtitle_screen_base_res[1] > 0) { w = Show_subtitle_screen_base_res[0]; h = Show_subtitle_screen_base_res[1]; } // otherwise use the actual screen size else { w = gr_screen.center_w; h = gr_screen.center_h; } } void sexp_show_subtitle_text(int node) { bool is_nan, is_nan_forever; int i, n = node, message_index = -1; SCP_string text; // we'll suppose it's the string for now auto ctext = CTEXT(n); n = CDR(n); // but use an actual message if one exists for (i=0; i xy_input; eval_array(xy_input, n, is_nan, is_nan_forever, [](int num)->int { if (!Show_subtitle_uses_pixels) CLAMP(num, -100, 100); return num; }); if (is_nan || is_nan_forever) return; bool center_x = is_sexp_true(n); n = CDR(n); bool center_y = is_sexp_true(n); n = CDR(n); float display_time, fade_time; int width_input; eval_nums(n, is_nan, is_nan_forever, display_time, fade_time, width_input); if (is_nan || is_nan_forever) return; display_time /= 1000.0f; fade_time /= 1000.0f; if (!Show_subtitle_uses_pixels) { // note: width_input is OPF_POSITIVE so checking for < 0 is not necessary if (width_input > 100) width_input = 100; } std::array rgb; eval_array(rgb, n, is_nan, is_nan_forever, [](int num)->int { CLAMP(num, 0, 255); return num; }, 255); if (is_nan || is_nan_forever) return; int fontnum = -1; if (n >= 0) { auto font_name = CTEXT(n); n = CDR(n); fontnum = font::FontManager::getFontIndex(font_name); } bool post_shaded = false; if (n >= 0) { post_shaded = is_sexp_true(n); n = CDR(n); } int line_height_modifier = 0; if (n >= 0) { line_height_modifier = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; n = CDR(n); } bool adjust_wh = true; if (n >= 0) { adjust_wh = is_sexp_true(n); n = CDR(n); } color new_color; gr_init_alphacolor(&new_color, rgb[0], rgb[1], rgb[2], 255); // calculate pixel positions int x_pos, y_pos, width; if (Show_subtitle_uses_pixels) { x_pos = xy_input[0]; y_pos = xy_input[1]; width = width_input; } else { int screen_w, screen_h; get_subtitle_screen_size(screen_w, screen_h); x_pos = fl2i(screen_w * (xy_input[0] / 100.0f)); y_pos = fl2i(screen_h * (xy_input[1] / 100.0f)); width = fl2i(screen_w * (width_input / 100.0f)); } // add the subtitle subtitle new_subtitle(x_pos, y_pos, text.c_str(), nullptr, display_time, fade_time, &new_color, fontnum, center_x, center_y, width, 0, post_shaded, line_height_modifier, adjust_wh); Subtitles.push_back(new_subtitle); Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_int(xy_input[0]); Current_sexp_network_packet.send_int(xy_input[1]); Current_sexp_network_packet.send_int(message_index); // only send the text if it is not a message. If it is a message, we've already sent the index anyway. if (message_index == -1) { Current_sexp_network_packet.send_string(text); } Current_sexp_network_packet.send_float(display_time); Current_sexp_network_packet.send_float(fade_time); Current_sexp_network_packet.send_int(rgb[0]); Current_sexp_network_packet.send_int(rgb[1]); Current_sexp_network_packet.send_int(rgb[2]); Current_sexp_network_packet.send_int(fontnum); Current_sexp_network_packet.send_bool(center_x); Current_sexp_network_packet.send_bool(center_y); Current_sexp_network_packet.send_int(width_input); Current_sexp_network_packet.send_bool(post_shaded); Current_sexp_network_packet.send_int(line_height_modifier); Current_sexp_network_packet.send_bool(adjust_wh); Current_sexp_network_packet.end_callback(); } void multi_sexp_show_subtitle_text() { std::array xy_input; int width_input, line_height_modifier = 0, fontnum, message_index = -1; SCP_string text; float display_time, fade_time=0.0f; int red=255, green=255, blue=255; bool center_x=false, center_y=false; bool post_shaded = false, adjust_wh = true; color new_color; Current_sexp_network_packet.get_int(xy_input[0]); Current_sexp_network_packet.get_int(xy_input[1]); Current_sexp_network_packet.get_int(message_index); if (message_index == -1) { Current_sexp_network_packet.get_string(text); } else { auto ctext = Messages[message_index].message; text = message_translate_tokens(ctext); } Current_sexp_network_packet.get_float(display_time); Current_sexp_network_packet.get_float(fade_time); Current_sexp_network_packet.get_int(red); Current_sexp_network_packet.get_int(green); Current_sexp_network_packet.get_int(blue); Current_sexp_network_packet.get_int(fontnum); Current_sexp_network_packet.get_bool(center_x); Current_sexp_network_packet.get_bool(center_y); Current_sexp_network_packet.get_int(width_input); Current_sexp_network_packet.get_bool(post_shaded); Current_sexp_network_packet.get_int(line_height_modifier); Current_sexp_network_packet.get_bool(adjust_wh); gr_init_alphacolor(&new_color, red, green, blue, 255); // calculate pixel positions int x_pos, y_pos, width; if (Show_subtitle_uses_pixels) { x_pos = xy_input[0]; y_pos = xy_input[1]; width = width_input; } else { int screen_w, screen_h; get_subtitle_screen_size(screen_w, screen_h); x_pos = fl2i(screen_w * (xy_input[0] / 100.0f)); y_pos = fl2i(screen_h * (xy_input[1] / 100.0f)); width = fl2i(screen_w * (width_input / 100.0f)); } // add the subtitle subtitle new_subtitle(x_pos, y_pos, text.c_str(), nullptr, display_time, fade_time, &new_color, fontnum, center_x, center_y, width, 0, post_shaded, line_height_modifier, adjust_wh); Subtitles.push_back(new_subtitle); } void sexp_show_subtitle_image(int node) { bool is_nan, is_nan_forever; int n = node; auto image = CTEXT(n); n = CDR(n); std::array xy_input; eval_array(xy_input, n, is_nan, is_nan_forever, [](int num)->int { if (!Show_subtitle_uses_pixels) CLAMP(num, -100, 100); return num; }); if (is_nan || is_nan_forever) return; bool center_x = is_sexp_true(n); n = CDR(n); bool center_y = is_sexp_true(n); n = CDR(n); int width_input, height_input; float display_time, fade_time; eval_nums(n, is_nan, is_nan_forever, width_input, height_input, display_time, fade_time); if (is_nan || is_nan_forever) return; if (!Show_subtitle_uses_pixels) { // note: width_input and height_input are OPF_POSITIVE so checking for < 0 is not necessary if (width_input > 100) width_input = 100; if (height_input > 100) height_input = 100; } display_time /= 1000.0f; fade_time /= 1000.0f; bool post_shaded = false; if (n >= 0) { post_shaded = is_sexp_true(n); n = CDR(n); } bool adjust_wh = true; if (n >= 0) { adjust_wh = is_sexp_true(n); n = CDR(n); } // calculate pixel positions int x_pos, y_pos, width, height; if (Show_subtitle_uses_pixels) { x_pos = xy_input[0]; y_pos = xy_input[1]; width = width_input; height = height_input; } else { int screen_w, screen_h; get_subtitle_screen_size(screen_w, screen_h); x_pos = fl2i(screen_w * (xy_input[0] / 100.0f)); y_pos = fl2i(screen_h * (xy_input[1] / 100.0f)); width = fl2i(screen_w * (width_input / 100.0f)); height = fl2i(screen_h * (height_input / 100.0f)); } // add the subtitle subtitle new_subtitle(x_pos, y_pos, nullptr, image, display_time, fade_time, nullptr, -1, center_x, center_y, width, height, post_shaded, 0, adjust_wh); Subtitles.push_back(new_subtitle); Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_int(x_pos); Current_sexp_network_packet.send_int(y_pos); Current_sexp_network_packet.send_string(image); Current_sexp_network_packet.send_float(display_time); Current_sexp_network_packet.send_float(fade_time); Current_sexp_network_packet.send_bool(center_x); Current_sexp_network_packet.send_bool(center_y); Current_sexp_network_packet.send_int(width); Current_sexp_network_packet.send_int(height); Current_sexp_network_packet.send_bool(post_shaded); Current_sexp_network_packet.send_bool(adjust_wh); Current_sexp_network_packet.end_callback(); } void multi_sexp_show_subtitle_image() { std::array xy_input; int width_input = 0, height_input = 0; char image[TOKEN_LENGTH]; float display_time, fade_time=0.0f; bool center_x=false, center_y=false; bool post_shaded = false, adjust_wh = true; Current_sexp_network_packet.get_int(xy_input[0]); Current_sexp_network_packet.get_int(xy_input[1]); Current_sexp_network_packet.get_string(image); Current_sexp_network_packet.get_float(display_time); Current_sexp_network_packet.get_float(fade_time); Current_sexp_network_packet.get_bool(center_x); Current_sexp_network_packet.get_bool(center_y); Current_sexp_network_packet.get_int(width_input); Current_sexp_network_packet.get_int(height_input); Current_sexp_network_packet.get_bool(post_shaded); Current_sexp_network_packet.get_bool(adjust_wh); // calculate pixel positions int x_pos, y_pos, width, height; if (Show_subtitle_uses_pixels) { x_pos = xy_input[0]; y_pos = xy_input[1]; width = width_input; height = height_input; } else { int screen_w, screen_h; get_subtitle_screen_size(screen_w, screen_h); x_pos = fl2i(screen_w * (xy_input[0] / 100.0f)); y_pos = fl2i(screen_h * (xy_input[1] / 100.0f)); width = fl2i(screen_w * (width_input / 100.0f)); height = fl2i(screen_h * (height_input / 100.0f)); } // add the subtitle subtitle new_subtitle(x_pos, y_pos, nullptr, image, display_time, fade_time, nullptr, -1, center_x, center_y, width, height, post_shaded, 0, adjust_wh); Subtitles.push_back(new_subtitle); } void sexp_set_time_compression(int n) { bool is_nan, is_nan_forever; float new_multiplier, new_change_time, starting_multiplier; int count = eval_nums(n, is_nan, is_nan_forever, new_multiplier, new_change_time, starting_multiplier); if (is_nan || is_nan_forever) return; Current_sexp_network_packet.start_callback(); new_multiplier /= 100.0f; //percent->decimal Current_sexp_network_packet.send_float(new_multiplier); //Time to change if (count > 1) { new_change_time /= 1000.0f; //ms->seconds Current_sexp_network_packet.send_float(new_change_time); } //Override current time compression with this value if (count > 2) { starting_multiplier /= 100.0f; //percent->decimal set_time_compression(starting_multiplier); Current_sexp_network_packet.send_float(starting_multiplier); } Current_sexp_network_packet.end_callback(); set_time_compression(new_multiplier, new_change_time); lock_time_compression(true); } void multi_sexp_set_time_compression() { float new_change_time = 0.0f; float new_multiplier = 0.0f; float current_multiplier = 0.0f; Current_sexp_network_packet.get_float(new_multiplier); Current_sexp_network_packet.get_float(new_change_time); if (Current_sexp_network_packet.get_float(current_multiplier)) { set_time_compression(current_multiplier); } set_time_compression(new_multiplier, new_change_time); lock_time_compression(true); } void sexp_reset_time_compression() { set_time_compression(1); lock_time_compression(false); Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.end_callback(); } void multi_sexp_reset_time_compression() { set_time_compression(1); lock_time_compression(false); } void sexp_force_perspective(int n) { Perspective_locked = is_sexp_true(n); n=CDR(n); if(n != -1) { bool is_nan, is_nan_forever; int option = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; switch(option) { case -1: break; case 0: Viewer_mode = 0; break; case 1: Viewer_mode = VM_CHASE; break; case 2: Viewer_mode = VM_EXTERNAL; break; case 3: Viewer_mode = VM_TOPDOWN; break; default: Warning(LOCATION, "Unhandled option %d supplied to lock-perspective!", option); break; } n = CDR(n); // optionally prevent hat/slew/free-look if (n != -1) { Slew_locked = is_sexp_true(n); if (Slew_locked) Viewer_mode |= VM_CENTERING; // start centering so that we don't get stuck in a slewed position } } } void sexp_allow_photo_mode(int n) { game_set_photo_mode_allowed(is_sexp_true(n)); } void sexp_set_camera_shudder(int n) { int time; float intensity; bool perpetual = false; bool everywhere = false; bool is_nan, is_nan_forever; eval_nums(n, is_nan, is_nan_forever, time, intensity); if (is_nan || is_nan_forever) return; intensity *= 0.01f; if (n >= 0) { perpetual = is_sexp_true(n); n = CDR(n); } if (n >= 0) { everywhere = is_sexp_true(n); n = CDR(n); } game_shudder_apply(time, intensity, perpetual, everywhere); Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_int(time); Current_sexp_network_packet.send_float(intensity); Current_sexp_network_packet.send_bool(perpetual); Current_sexp_network_packet.send_bool(everywhere); Current_sexp_network_packet.end_callback(); } void multi_sexp_set_camera_shudder() { int time; float intensity; bool perpetual = false; bool everywhere = false; Current_sexp_network_packet.get_int(time); if (Current_sexp_network_packet.get_float(intensity)) { Current_sexp_network_packet.get_bool(perpetual); Current_sexp_network_packet.get_bool(everywhere); game_shudder_apply(time, intensity, perpetual, everywhere); } } void sexp_set_jumpnode_name(int n) //CommanderDJ { auto old_name = CTEXT(n); n = CDR(n); auto jnp = jumpnode_get_by_name(old_name); if (!jnp) return; auto new_name = CTEXT(n); jnp->SetName(new_name); //multiplayer callback Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_string(old_name); Current_sexp_network_packet.send_string(new_name); Current_sexp_network_packet.end_callback(); } void sexp_set_jumpnode_display_name(int n) //MjnMixael { auto node = CTEXT(n); n = CDR(n); auto jnp = jumpnode_get_by_name(node); if (!jnp) return; auto display_name = CTEXT(n); jnp->SetDisplayName(display_name); //multiplayer callback Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_string(node); Current_sexp_network_packet.send_string(display_name); Current_sexp_network_packet.end_callback(); } void multi_sexp_set_jumpnode_name() //CommanderDJ { char old_name[TOKEN_LENGTH]; char new_name[TOKEN_LENGTH]; Current_sexp_network_packet.get_string(old_name); Current_sexp_network_packet.get_string(new_name); CJumpNode *jnp = jumpnode_get_by_name(old_name); if (jnp == nullptr) return; jnp->SetName(new_name); } void multi_sexp_set_jumpnode_display_name() { char node[TOKEN_LENGTH]; char display_name[TOKEN_LENGTH]; Current_sexp_network_packet.get_string(node); Current_sexp_network_packet.get_string(display_name); CJumpNode* jnp = jumpnode_get_by_name(node); if (jnp == nullptr) return; jnp->SetDisplayName(display_name); } void sexp_set_jumpnode_color(int n) { auto jumpnode_name = CTEXT(n); n = CDR(n); auto jnp = jumpnode_get_by_name(jumpnode_name); if (!jnp) return; bool is_nan, is_nan_forever; std::array rgba; eval_array(rgba, n, is_nan, is_nan_forever, [](int num)->int { CLAMP(num, 0, 255); return num; }); if (is_nan || is_nan_forever) return; jnp->SetAlphaColor(rgba[0], rgba[1], rgba[2], rgba[3]); Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_string(jumpnode_name); Current_sexp_network_packet.send_int(rgba[0]); Current_sexp_network_packet.send_int(rgba[1]); Current_sexp_network_packet.send_int(rgba[2]); Current_sexp_network_packet.send_int(rgba[3]); Current_sexp_network_packet.end_callback(); } //CommanderDJ void multi_sexp_set_jumpnode_color() { char jumpnode_name[TOKEN_LENGTH]; int red, blue, green, alpha; Current_sexp_network_packet.get_string(jumpnode_name); CJumpNode *jnp = jumpnode_get_by_name(jumpnode_name); if (jnp == nullptr) { Current_sexp_network_packet.discard_remaining_callback_data(); return; } Current_sexp_network_packet.get_int(red); Current_sexp_network_packet.get_int(green); Current_sexp_network_packet.get_int(blue); Current_sexp_network_packet.get_int(alpha); jnp->SetAlphaColor(red, green, blue, alpha); } void sexp_set_jumpnode_model(int n) { auto jumpnode_name = CTEXT(n); n = CDR(n); auto jnp = jumpnode_get_by_name(jumpnode_name); if (!jnp) return; auto model_name = CTEXT(n); n = CDR(n); bool show_polys = is_sexp_true(n); jnp->SetModel(model_name, show_polys); Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_string(jumpnode_name); Current_sexp_network_packet.send_string(model_name); Current_sexp_network_packet.send_bool(show_polys); Current_sexp_network_packet.end_callback(); } void multi_sexp_set_jumpnode_model() { char jumpnode_name[TOKEN_LENGTH]; char model_name[TOKEN_LENGTH]; bool show_polys; Current_sexp_network_packet.get_string(jumpnode_name); Current_sexp_network_packet.get_string(model_name); CJumpNode *jnp = jumpnode_get_by_name(jumpnode_name); if (jnp == nullptr) { Current_sexp_network_packet.discard_remaining_callback_data(); return; } show_polys = Current_sexp_network_packet.get_bool(show_polys); jnp->SetModel(model_name, show_polys); } void sexp_show_hide_jumpnode(int node, bool show) { Current_sexp_network_packet.start_callback(); for (int n = node; n >= 0; n = CDR(n)) { auto jnp = jumpnode_get_by_name(CTEXT(n)); if (jnp) { jnp->SetVisibility(show); Current_sexp_network_packet.send_string(CTEXT(n)); } } Current_sexp_network_packet.end_callback(); } void multi_sexp_show_hide_jumpnode(bool show) { char jumpnode_name[TOKEN_LENGTH]; while (Current_sexp_network_packet.get_string(jumpnode_name)) { CJumpNode *jnp = jumpnode_get_by_name(jumpnode_name); if (jnp != nullptr) jnp->SetVisibility(show); } } //WMC - This is a bit of a hack, however, it's easier than //coding in a whole new Script_system function. int sexp_script_eval(int node, int return_type, bool concat_args = false) { int n = node; switch(return_type) { case OPR_BOOL: { SCP_string script_cmd; for (; n != -1; n = CDR(n)) script_cmd.append(CTEXT(n)); bool r = false; bool success = Script_system.EvalStringWithReturn(script_cmd.c_str(), "|b", &r); if (!success) Warning(LOCATION, "sexp-script-eval failed to evaluate string \"%s\"; check your syntax", script_cmd.c_str()); return r ? SEXP_TRUE : SEXP_FALSE; } case OPR_NUMBER: { SCP_string script_cmd; for (; n != -1; n = CDR(n)) script_cmd.append(CTEXT(n)); int r = -1; bool success = Script_system.EvalStringWithReturn(script_cmd.c_str(), "|i", &r); if (!success) Warning(LOCATION, "sexp-script-eval failed to evaluate string \"%s\"; check your syntax", script_cmd.c_str()); return r; } case OPR_STRING: { const char* ret = nullptr; auto s = CTEXT(n); bool success = Script_system.EvalStringWithReturn(s, "|s", &ret); n = CDR(n); if(!success) Warning(LOCATION, "sexp-script-eval failed to evaluate string \"%s\"; check your syntax", s); if (n != -1 && success) { int variable_index = sexp_get_variable_index(n); if (variable_index < 0) { Warning(LOCATION, "script-eval: Variable %s does not exist!", Sexp_nodes[n].text); } else if (!(Sexp_variables[variable_index].type & SEXP_VARIABLE_STRING)) { Warning(LOCATION, "script-eval: Variable %s must be a string variable!", Sexp_variables[variable_index].variable_name); } else if (ret != nullptr) { // assign to variable sexp_modify_variable(ret, variable_index); } n = CDR(n); } break; } case OPR_NULL: { SCP_string script_cmd; while (n != -1) { auto s = CTEXT(n); if (concat_args) { script_cmd.append(s); } else { bool success = Script_system.EvalString(s); if (!success) Warning(LOCATION, "sexp-script-eval failed to evaluate string \"%s\"; check your syntax", s); } n = CDR(n); } if (concat_args) { bool success = Script_system.EvalString(script_cmd.c_str()); if (!success) Warning(LOCATION, "sexp-script-eval failed to evaluate string \"%s\"; check your syntax", script_cmd.c_str()); } break; } default: Error(LOCATION, "Bad type passed to sexp_script_eval - get a coder"); break; } return SEXP_TRUE; } void sexp_script_eval_multi(int node) { char script[TOKEN_LENGTH]; bool success = true; bool execute_on_server; player *p; strcpy_s(script, CTEXT(node)); node = CDR(node); execute_on_server = is_sexp_true(node); node = CDR(node); Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_string(script); // evalutate on all clients if (node == -1) { Current_sexp_network_packet.send_bool(true); execute_on_server = true; } // we have to send to all clients but we need to send a list of ships so that they know if they evaluate or not else { Current_sexp_network_packet.send_bool(false); for (; node != -1; node = CDR(node)) { p = get_player_from_ship_node(node, true); // not a player ship so skip it if (p == nullptr ){ continue; } else { // if this is me, flag that we should execute the script if (p == Player) { execute_on_server = true; } // otherwise notify the clients else { Current_sexp_network_packet.send_string(CTEXT(node)); } } } } Current_sexp_network_packet.end_callback(); if (execute_on_server) { success = Script_system.EvalString(script, script); } if(!success) { Warning(LOCATION, "sexp-script-eval failed to evaluate string \"%s\"; check your syntax", script); } } void multi_sexp_script_eval_multi() { char script[TOKEN_LENGTH]; char ship_name[TOKEN_LENGTH]; bool sent_to_all = false; bool success = true; Current_sexp_network_packet.get_string(script); Current_sexp_network_packet.get_bool(sent_to_all); if (sent_to_all) { success = Script_system.EvalString(script, script); } // go through all the ships that were sent and see if any of them match this client. else { while (Current_sexp_network_packet.get_string(ship_name)) { auto ship_entry = ship_registry_get(ship_name); Assertion(ship_entry, "Illegal value for the ship name sent in multi_sexp_script_eval_multi()! Ship %s does not exist!", ship_name); if (Player == get_player_from_ship_entry(ship_entry)) { success = Script_system.EvalString(script, script); } } } if(!success) { Warning(LOCATION, "sexp-script-eval failed to evaluate string \"%s\"; check your syntax", script); } } void sexp_force_glide(int node) { // get ship auto ship_entry = eval_ship(node); if (!ship_entry || !ship_entry->has_shipp()) return; //Can this ship glide? if (!Ship_info[ship_entry->shipp()->ship_info_index].can_glide) return; bool glide = is_sexp_true(CDR(node)); object_set_gliding(ship_entry->objp(), glide, true); } bool test_point_within_box(vec3d *test_point, vec3d *box_corner_1, vec3d *box_corner_2, object *reference_ship_obj) { vec3d tempv, test_point_buf; // If reference_ship is specified, rotate test_point into its reference frame if (reference_ship_obj != nullptr) { vm_vec_sub(&tempv, test_point, &reference_ship_obj->pos); vm_vec_rotate(&test_point_buf, &tempv, &reference_ship_obj->orient); test_point = &test_point_buf; } // Check to see if the test point is within the specified box as defined by two extreme corners return ((test_point->xyz.x >= box_corner_1->xyz.x && test_point->xyz.x <= box_corner_2->xyz.x) && (test_point->xyz.y >= box_corner_1->xyz.y && test_point->xyz.y <= box_corner_2->xyz.y) && (test_point->xyz.z >= box_corner_1->xyz.z && test_point->xyz.z <= box_corner_2->xyz.z)); } int sexp_is_in_box(int n) { int i; float x1, x2, y1, y2, z1, z2; bool is_nan, is_nan_forever; Assert(n >= 0); object_ship_wing_point_team oswpt; eval_object_ship_wing_point_team(&oswpt, n); n = CDR(n); // Get box corners eval_nums(n, is_nan, is_nan_forever, x1, x2, y1, y2, z1, z2); if (is_nan) return SEXP_FALSE; if (is_nan_forever) return SEXP_KNOWN_FALSE; vec3d box_corner_1; box_corner_1.xyz.x = MIN(x1, x2); box_corner_1.xyz.y = MIN(y1, y2); box_corner_1.xyz.z = MIN(z1, z2); vec3d box_corner_2; box_corner_2.xyz.x = MAX(x1, x2); box_corner_2.xyz.y = MAX(y1, y2); box_corner_2.xyz.z = MAX(z1, z2); // Ship to define reference frame is optional object* reference_ship_obj = nullptr; if (n != -1) { auto reference = eval_ship(n); if (!reference || reference->status == ShipStatus::NOT_YET_PRESENT) return SEXP_FALSE; if (reference->status == ShipStatus::EXITED) return SEXP_KNOWN_FALSE; reference_ship_obj = reference->objp(); } // Check position of object switch (oswpt.type) { case OSWPT_TYPE_EXITED: return SEXP_KNOWN_FALSE; case OSWPT_TYPE_WING: for (i = 0; i < oswpt.wingp()->current_count; i++) if (!test_point_within_box(&Objects[Ships[oswpt.wingp()->ship_index[i]].objnum].pos, &box_corner_1, &box_corner_2, reference_ship_obj)) return SEXP_FALSE; return SEXP_TRUE; case OSWPT_TYPE_SHIP: case OSWPT_TYPE_WAYPOINT: return test_point_within_box(&oswpt.objp()->pos, &box_corner_1, &box_corner_2, reference_ship_obj); default: return SEXP_FALSE; } } int sexp_is_in_mission(int node) { // For this sexp, we do not short-circuit known-true or known-false. for (int n = node; n != -1; n = CDR(n)) { auto ship_entry = eval_ship(n); if (ship_entry) { if (ship_entry->has_shipp()) continue; return SEXP_FALSE; // we know the ship isn't present } auto wingp = eval_wing(n); if (wingp) { if (wingp->current_count > 0) continue; return SEXP_FALSE; // we know no ships in the wing are present } // this is neither a ship nor a wing return SEXP_FALSE; } return SEXP_TRUE; } int sexp_has_armor_type(int node) { // get ship from sexp auto ship_entry = eval_ship(node); if (!ship_entry || ship_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; if (ship_entry->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; ship* shipp = ship_entry->shipp(); node = CDR(node); // get armor from sexp int armor; if (!stricmp(SEXP_NONE_STRING, CTEXT(node))) { armor = -1; } else { armor = armor_type_get_idx(CTEXT(node)); //warn and return if the armor is not found if (armor < 0) { Warning(LOCATION, "Armor %s is not a valid entry in armor.tbl!", CTEXT(node)); return SEXP_NAN; } } node = CDR(node); // get the armor to check against int currentArmor = -1; if (!stricmp(SEXP_HULL_STRING, CTEXT(node))) { currentArmor = shipp->armor_type_idx; } else if (!stricmp(SEXP_SHIELD_STRING, CTEXT(node))) { currentArmor = shipp->shield_armor_type_idx; } else { // get the subsystem ship_subsys* ss = ship_get_subsys(shipp, CTEXT(node)); if (ss == nullptr) { Warning(LOCATION, "Subsystem %s not found on ship %s", CTEXT(node), shipp->ship_name); return SEXP_NAN; } currentArmor = ss->armor_type_idx; } if (currentArmor == armor) return SEXP_TRUE; return SEXP_FALSE; } int sexp_is_docked(int node) { object *host_objp = nullptr; for (int n = node; n != -1; n = CDR(n)) { auto current_entry = eval_ship(n); if (!current_entry || current_entry->status == ShipStatus::NOT_YET_PRESENT) return SEXP_NAN; if (current_entry->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; // if host_objp is a nullptr then we're on the 1st loop iteration if (host_objp == nullptr) { // if the host isn't docked to anything, no need to check each ship individually if (!object_is_docked(current_entry->objp())) return SEXP_FALSE; host_objp = current_entry->objp(); continue; } // if we are not docked, do a quick out if (!dock_check_find_direct_docked_object(host_objp, current_entry->objp())) return SEXP_FALSE; } // all ships are docked return SEXP_TRUE; } void sexp_manipulate_colgroup(int node, bool add_to_group) { int colgroup_id = 0; const ship_registry_entry* ship_entry = nullptr; const prop* prop_entry = nullptr; SCP_string name; // Ship ship_entry = eval_ship(node); if (ship_entry) { if (ship_entry->status == ShipStatus::EXITED) return; colgroup_id = (ship_entry->has_objp()) ? ship_entry->objp()->collision_group_id : ship_entry->p_objp()->collision_group_id; name = ship_entry->name; // Prop } else { prop_entry = eval_prop(node); if (prop_entry) { colgroup_id = Objects[prop_entry->objnum].collision_group_id; name = prop_entry->prop_name; } else { // Not a prop or a ship so warn and return Warning(LOCATION, "Invalid object %s specified in manipulate-colgroup SEXP", CTEXT(node)); return; } } node = CDR(node); // Set the colgroup for (; node != -1; node = CDR(node)) { bool is_nan, is_nan_forever; int group = eval_num(node, is_nan, is_nan_forever); if (is_nan || is_nan_forever) { continue; } if (group < 0 || group > 31) { WarningEx(LOCATION, "Invalid collision group id %d specified for object %s. Valid IDs range from 0 to 31.\n", group, name.c_str()); } else { if (add_to_group) { colgroup_id |= (1 << group); } else { colgroup_id &= ~(1 << group); } } } // Apply it to ship if (ship_entry) { if (ship_entry->has_objp()) { ship_entry->objp()->collision_group_id = colgroup_id; } else { ship_entry->p_objp()->collision_group_id = colgroup_id; } return; } // Apply it to prop if (prop_entry) { Objects[prop_entry->objnum].collision_group_id = colgroup_id; } } void sexp_manipulate_colgroup_new(int node, bool add_to_group) { bool is_nan, is_nan_forever; int group = eval_num(node, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; node = CDR(node); if (group < 0 || group > 31) { WarningEx(LOCATION, "Invalid collision group id %d specified. Valid IDs range from 0 to 31.\n", group); return; } for (; node != -1; node = CDR(node)) { // Ship auto ship_entry = eval_ship(node); if (ship_entry) { if (ship_entry->status == ShipStatus::EXITED) continue; if (ship_entry->has_objp()) { if (add_to_group) ship_entry->objp()->collision_group_id |= (1 << group); else ship_entry->objp()->collision_group_id &= ~(1 << group); } else { if (add_to_group) ship_entry->p_objp()->collision_group_id |= (1 << group); else ship_entry->p_objp()->collision_group_id &= ~(1 << group); } // Prop } else { auto prop_entry = eval_prop(node); if (prop_entry) { object& obj = Objects[prop_entry->objnum]; if (add_to_group) obj.collision_group_id |= (1 << group); else obj.collision_group_id &= ~(1 << group); } } } } int sexp_get_colgroup(int node) { // Ship auto ship_entry = eval_ship(node); if (ship_entry) { if (ship_entry->status == ShipStatus::EXITED) return SEXP_NAN_FOREVER; if (ship_entry->has_objp()) return ship_entry->objp()->collision_group_id; else return ship_entry->p_objp()->collision_group_id; // Prop } else { auto prop_entry = eval_prop(node); if (prop_entry) { const object& obj = Objects[prop_entry->objnum]; return obj.collision_group_id; } } return SEXP_NAN; } int get_effect_from_name(const char* name) { int i = 0; for (SCP_vector::iterator sei = Ship_effects.begin(); sei != Ship_effects.end(); ++sei) { if (!stricmp(name, sei->name)) return i; i++; } return -1; } void sexp_ship_effect(int n) { bool is_nan, is_nan_forever; int effect_num = get_effect_from_name(CTEXT(n)); if (effect_num == -1) { WarningEx(LOCATION, "Invalid effect name passed to ship-effect\n"); return; } n = CDR(n); int effect_duration = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; n = CDR(n); for (; n != -1; n = CDR(n)) { object_ship_wing_point_team oswpt; eval_object_ship_wing_point_team(&oswpt, n); // we only handle ships and wings that are present switch (oswpt.type) { case OSWPT_TYPE_SHIP: { auto sp = oswpt.shipp(); sp->shader_effect_num = effect_num; sp->shader_effect_duration = effect_duration; sp->shader_effect_timestamp = _timestamp(effect_duration); break; } case OSWPT_TYPE_WING: { auto wp = oswpt.wingp(); for (int i = 0; i < wp->current_count; ++i) { if (wp->ship_index[i] >= 0) { auto sp = &Ships[wp->ship_index[i]]; sp->shader_effect_num = effect_num; sp->shader_effect_duration = effect_duration; sp->shader_effect_timestamp = _timestamp(effect_duration); } } break; } default: mprintf(("Invalid Shipname in SEXP ship-effect\n")); } } } void sexp_change_team_color(int n) { auto new_color = CTEXT(n); n = CDR(n); bool is_nan, is_nan_forever; int fade_time = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; n = CDR(n); Current_sexp_network_packet.start_callback(); Current_sexp_network_packet.send_string(new_color); Current_sexp_network_packet.send_int(fade_time); for ( ; n >= 0; n = CDR(n)) { auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) continue; auto shp = ship_entry->shipp(); Current_sexp_network_packet.send_ship(shp); if (fade_time == 0) { shp->team_name = new_color; } else { shp->secondary_team_name = new_color; shp->team_change_time = fade_time; shp->team_change_timestamp = Missiontime; } } Current_sexp_network_packet.end_callback(); } void multi_sexp_change_team_color() { SCP_string new_color = ""; int fade_time = 0; ship* shipp; Current_sexp_network_packet.get_string(new_color); Current_sexp_network_packet.get_int(fade_time); while (Current_sexp_network_packet.get_ship(shipp)) { if (fade_time == 0) { shipp->team_name = new_color; } else { shipp->secondary_team_name = new_color; shipp->team_change_time = fade_time; shipp->team_change_timestamp = Missiontime; } } } void sexp_call_ssm_strike(int node) { int ssm_index = ssm_info_lookup(CTEXT(node)); node = CDR(node); int calling_team = iff_lookup(CTEXT(node)); if (ssm_index < 0 || calling_team < 0) return; for (int n = node; n != -1; n = CDR(n)) { // don't do anything if the ship isn't there auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_objp()) continue; auto target_objp = ship_entry->objp(); auto start = &target_objp->pos; vm_vec_scale_add(start, start, &target_objp->orient.vec.fvec, -1); ssm_create(target_objp, start, ssm_index, nullptr, calling_team); } } extern int Cheats_enabled; int sexp_player_is_cheating_bastard() { if (Cheats_enabled) { return SEXP_KNOWN_TRUE; } return SEXP_FALSE; } int sexp_is_language(int node) { // we don't check the language for validity because it could be a language the engine doesn't know about auto lang = CTEXT(node); // easy lookup auto li = &Lcl_languages[lcl_get_current_lang_index()]; // easy comparison if (stricmp(li->lang_name, lang) == 0) return SEXP_KNOWN_TRUE; else return SEXP_KNOWN_FALSE; } extern SCP_string CheatUsed; int sexp_cheat_used(int node) { auto len = CheatUsed.length(); if (len <= 0) return SEXP_KNOWN_FALSE; auto text = CTEXT(node); if (strcmp(text, CheatUsed.c_str()) == 0) return SEXP_KNOWN_TRUE; return SEXP_UNKNOWN; } void sexp_set_motion_debris(int node) { if (Motion_debris_ptr != nullptr) Motion_debris_override = is_sexp_true(node); } /** * Returns the subsystem type if the name of a subsystem is actually a generic type (e.g \ or \ * NOTE: this can return SUBSYSTEM_ALL which is not in the normal range of subsystem types */ int get_generic_subsys(const char *subsys_name) { char buffer[NAME_LENGTH]; for (int i = 0; i < SUBSYSTEM_MAX; ++i) { sprintf(buffer, SEXP_ALL_GENERIC_SUBSYSTEM_STRING, Subsystem_types[i]); if (!stricmp(subsys_name, buffer)) return i; } if (!stricmp(subsys_name, SEXP_ALL_SUBSYSTEMS_STRING)) return SUBSYSTEM_ALL; return SUBSYSTEM_NONE; } // Karajorma - returns false if the ship class has changed since the mission was parsed in // Changes can be from use of the change-ship-class SEXP, loadout or any future method bool ship_class_unchanged(const ship_registry_entry *ship_entry) { Assertion(ship_entry && ship_entry->has_shipp(), "The ship_class_unchanged function requires an active ship!"); if (!ship_entry || !ship_entry->has_shipp() || !ship_entry->has_p_objp()) return false; return ship_entry->p_objp()->ship_class == ship_entry->shipp()->ship_info_index; } // Goober5000 - needed because any nonzero integer value is "true" bool is_sexp_true(int cur_node, int referenced_node) { int result = eval_sexp(cur_node, referenced_node); // any SEXP_KNOWN_TRUE result will return SEXP_TRUE from eval_sexp, but let's be defensive return (result == SEXP_TRUE) || (result == SEXP_KNOWN_TRUE); } /** * */ int generate_event_log_flags_mask(int result) { int matches = 0; mission_event *current_event = &Mission_events[Event_index]; switch (result) { case SEXP_TRUE: matches |= MLF_SEXP_TRUE; break; case SEXP_FALSE: matches |= MLF_SEXP_FALSE; break; default: Error(LOCATION, "SEXP has a value which isn't true or false."); } if (( result == SEXP_TRUE ) || (result == SEXP_KNOWN_TRUE)) { // now deal with the flags depending on repeat and trigger counts switch (current_event->mission_log_flags) { case MLF_FIRST_REPEAT_ONLY: if (current_event->repeat_count > 1) { matches |= MLF_FIRST_REPEAT_ONLY; } break; case MLF_LAST_REPEAT_ONLY: if (current_event->repeat_count == 1) { matches |= MLF_LAST_REPEAT_ONLY; } break; case MLF_FIRST_TRIGGER_ONLY: if (current_event->trigger_count > 1) { matches |= MLF_FIRST_TRIGGER_ONLY; } break; case MLF_LAST_TRIGGER_ONLY: if ((current_event->trigger_count == 1) && (current_event->flags & MEF_USING_TRIGGER_COUNT)) { matches |= MLF_LAST_TRIGGER_ONLY; } break; } } return matches; } void current_log_to_backup_log_buffer() { Mission_events[Event_index].backup_log_buffer.clear(); if (!(Mission_events[Event_index].mission_log_flags & MLF_STATE_CHANGE)){ return; } for (int i = 0; i < (int)Current_event_log_buffer->size(); i++) { Mission_events[Event_index].backup_log_buffer.push_back(Current_event_log_buffer->at(i)); } } void maybe_write_previous_event_to_log(int result) { mission_event *this_event = &Mission_events[Event_index]; // if the old log is empty, all we do is record the result for the next evaluation // the old log should only be empty at mission start if (this_event->backup_log_buffer.empty()) { this_event->previous_result = result; return; } // if there's no change in state, we don't write the previous state to the log if ((this_event->mission_log_flags & MLF_STATE_CHANGE) && (result == this_event->previous_result)) { current_log_to_backup_log_buffer(); return; } log_string(LOGFILE_EVENT_LOG, "Event has changed state. Old state"); while (!this_event->backup_log_buffer.empty()) { log_string(LOGFILE_EVENT_LOG, this_event->backup_log_buffer.back().c_str()); this_event->backup_log_buffer.pop_back(); } log_string(LOGFILE_EVENT_LOG, "New state"); // backup the current buffer as this may be a repeating event current_log_to_backup_log_buffer(); } /** * Checks the mission logs flags for this event and writes to the log if this has been asked for */ void maybe_write_to_event_log(int result) { char buffer [256]; int mask = generate_event_log_flags_mask(result); sprintf(buffer, "Event: %s at mission time %d seconds (%d milliseconds)", Mission_events[Event_index].name.c_str(), f2i(Missiontime), static_cast(f2fl(Missiontime) * MILLISECONDS_PER_SECOND)); Current_event_log_buffer->push_back(buffer); if (!Snapshot_all_events && (!(mask &= Mission_events[Event_index].mission_log_flags))) { current_log_to_backup_log_buffer(); Current_event_log_buffer->clear(); return; } // remove some of the flags if (mask & (MLF_FIRST_REPEAT_ONLY | MLF_FIRST_TRIGGER_ONLY)) { Mission_events[Event_index].mission_log_flags &= ~(MLF_FIRST_REPEAT_ONLY | MLF_FIRST_TRIGGER_ONLY) ; } if (Mission_events[Event_index].mission_log_flags & MLF_STATE_CHANGE) { maybe_write_previous_event_to_log(result); } while (!Current_event_log_buffer->empty()) { log_string(LOGFILE_EVENT_LOG, Current_event_log_buffer->back().c_str()); Current_event_log_buffer->pop_back(); } log_string(LOGFILE_EVENT_LOG, ""); } /** * Returns the constant used as a SEXP's result as text for printing to the event log */ const char *sexp_get_result_as_text(int result) { switch (result) { case SEXP_TRUE: return "TRUE"; case SEXP_FALSE: return "FALSE"; case SEXP_KNOWN_FALSE: return "ALWAYS FALSE"; case SEXP_KNOWN_TRUE: return "ALWAYS TRUE"; case SEXP_UNKNOWN: return "UNKNOWN"; case SEXP_NAN: return "NOT A NUMBER"; case SEXP_NAN_FOREVER: return "CAN NEVER BE A NUMBER"; case SEXP_CANT_EVAL: return "CAN'T EVALUATE"; default: return nullptr; } } /** * Checks the mission logs flags for this event and writes to the log if this has been asked for */ void add_to_event_log_buffer(int node, int op_num, int result) { Assertion ((Current_event_log_buffer != nullptr) && (Current_event_log_variable_buffer != nullptr)&& (Current_event_log_container_buffer != nullptr) && (Current_event_log_argument_buffer != nullptr), "Attempting to write to a non-existent log buffer"); if (op_num == -1) { nprintf(("SEXP", "ERROR: add_to_event_log_buffer() function called with op_num of %i; this should not happen. Contact a coder.\n", op_num)); return; //How does this happen? } char buffer[TOKEN_LENGTH]; SCP_string tmp; // indent the operators according to their depth int n = node; while (n >= 0) { tmp.append(" "); n = find_parent_operator(n); } tmp.append(Operators[op_num].text); tmp.append(" returned "); if ((Operators[op_num].type == sexp_oper_type::INTEGER) || (Operators[op_num].type == sexp_oper_type::ARITHMETIC) || (sexp_get_result_as_text(result) == nullptr)) { sprintf(buffer, "%d", result); tmp.append(buffer); } else { tmp.append(sexp_get_result_as_text(result)); } if (True_loop_argument_sexps && !Sexp_replacement_arguments.empty()) { tmp.append(" for argument "); tmp.append(Sexp_replacement_arguments.back().first); } if (!Current_event_log_argument_buffer->empty()) { tmp.append(" for the following arguments"); while (!Current_event_log_argument_buffer->empty()) { tmp.append("\n"); tmp.append(Current_event_log_argument_buffer->back()); Current_event_log_argument_buffer->pop_back(); } } if (!Current_event_log_variable_buffer->empty()) { tmp.append("\nVariables:"); while (!Current_event_log_variable_buffer->empty()) { tmp.append("\n"); tmp.append(Current_event_log_variable_buffer->back()); Current_event_log_variable_buffer->pop_back(); tmp.append("["); tmp.append(Current_event_log_variable_buffer->back()); Current_event_log_variable_buffer->pop_back(); tmp.append("]"); } } if (!Current_event_log_container_buffer->empty()) { tmp.append("\nContainers:"); while (!Current_event_log_container_buffer->empty()) { tmp.append("\n"); tmp.append(Current_event_log_container_buffer->back()); Current_event_log_container_buffer->pop_back(); } } Current_event_log_buffer->push_back(std::move(tmp)); } /** * High-level sexpression evaluator */ int eval_sexp(int cur_node, int referenced_node) { int node, type, sexp_val = UNINITIALIZED; if (cur_node == -1) // empty list, i.e. sexp: ( ) return SEXP_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 the sexp is part of a when-argument tree, // we can't 'know' its value since the sexp nodes may be evaluated in different ways for // different arguments, so we skip this behaviour. if (!is_descendant_of_when_argument_op(cur_node)) { // we want to log event values for KNOWN_X or FOREVER_X before returning if (Log_event && ((Sexp_nodes[cur_node].value == SEXP_KNOWN_TRUE) || (Sexp_nodes[cur_node].value == SEXP_KNOWN_FALSE) || (Sexp_nodes[cur_node].value == SEXP_NAN_FOREVER))) { // if this is a node that has been assigned the value by short-circuiting, // it might not be the operator that returned the value int op_index = get_operator_index(cur_node); if (op_index < 0) op_index = get_operator_index(CAR(cur_node)); // log the known value add_to_event_log_buffer(cur_node, op_index, Sexp_nodes[cur_node].value); } // now do a quick return whether or not we log, per the comment above about trapping known sexpressions if (Sexp_nodes[cur_node].value == SEXP_KNOWN_TRUE) { return SEXP_TRUE; } else if (Sexp_nodes[cur_node].value == SEXP_KNOWN_FALSE) { return SEXP_FALSE; } else if (Sexp_nodes[cur_node].value == SEXP_NAN_FOREVER) { return SEXP_FALSE; } } // ignore for container data, because their "first" is a container modifier if ((Sexp_nodes[cur_node].first != -1) && (Sexp_nodes[cur_node].subtype != SEXP_ATOM_CONTAINER_DATA)) { 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 = get_operator_const(cur_node); // add the op_num to the stack if it is an actual operator rather than a number if (op_num) { Current_sexp_operator.push_back(op_num); } 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: case OP_RAND_MULTIPLE: sexp_val = rand_sexp( node, (op_num == OP_RAND_MULTIPLE) ); break; case OP_ABS: sexp_val = abs_sexp(node); break; case OP_MIN: sexp_val = min_sexp(node); break; case OP_MAX: sexp_val = max_sexp(node); break; case OP_AVG: sexp_val = avg_sexp(node); break; case OP_POW: sexp_val = pow_sexp(node); break; case OP_SIGNUM: sexp_val = signum_sexp(node); break; case OP_IS_NAN: sexp_val = sexp_is_nan(node); break; case OP_NAN_TO_NUMBER: sexp_val = sexp_nan_to_number(node); break; case OP_SET_BIT: case OP_UNSET_BIT: sexp_val = sexp_set_bit(node, op_num == OP_SET_BIT); break; case OP_IS_BIT_SET: sexp_val = sexp_is_bit_set(node); break; case OP_BITWISE_AND: sexp_val = sexp_bitwise_and(node); break; case OP_BITWISE_OR: sexp_val = sexp_bitwise_or(node); break; case OP_BITWISE_NOT: sexp_val = sexp_bitwise_not(node); break; case OP_BITWISE_XOR: sexp_val = sexp_bitwise_xor(node); break; case OP_ANGLE_VECTORS: sexp_val = sexp_angle_vectors(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_XOR: sexp_val = sexp_xor(node); break; case OP_EQUALS: case OP_GREATER_THAN: case OP_LESS_THAN: case OP_NOT_EQUAL: case OP_GREATER_OR_EQUAL: case OP_LESS_OR_EQUAL: sexp_val = sexp_number_compare( node, op_num ); break; case OP_STRING_GREATER_THAN: case OP_STRING_LESS_THAN: case OP_STRING_EQUALS: sexp_val = sexp_string_compare( node, op_num ); break; case OP_IS_TRUE_FOR_DURATION: sexp_val = sexp_is_true_for_duration(cur_node, node); break; case OP_IS_IFF: sexp_val = sexp_is_iff_or_species(node, true); break; case OP_IS_SPECIES: sexp_val = sexp_is_iff_or_species(node, false); 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, EventStatus::SATISFIED ); break; case OP_PREVIOUS_EVENT_FALSE: sexp_val = sexp_previous_event_status( node, EventStatus::FAILED ); break; case OP_PREVIOUS_EVENT_INCOMPLETE: sexp_val = sexp_previous_event_status( node, EventStatus::INCOMPLETE ); break; case OP_EVENT_TRUE: case OP_EVENT_FALSE: sexp_val = sexp_event_status( node, (op_num == OP_EVENT_TRUE?1:0) ); 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) ); break; case OP_EVENT_TRUE_MSECS_DELAY: case OP_EVENT_FALSE_MSECS_DELAY: sexp_val = sexp_event_delay_status( node, (op_num == OP_EVENT_TRUE_MSECS_DELAY?1:0), true ); 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); break; case OP_GOAL_INCOMPLETE: sexp_val = sexp_goal_incomplete(node); break; case OP_RESET_EVENT: sexp_reset_event(node); sexp_val = SEXP_TRUE; break; case OP_RESET_GOAL: sexp_reset_goal(node); sexp_val = SEXP_TRUE; break; // destroy type sexpressions case OP_IS_DESTROYED: sexp_val = sexp_is_destroyed(node, nullptr); break; case OP_WAS_DESTROYED_BY_DELAY: sexp_val = sexp_was_destroyed_by_delay(node); break; case OP_IS_SUBSYSTEM_DESTROYED: sexp_val = sexp_is_subsystem_destroyed(node, nullptr); break; case OP_HAS_ARRIVED: sexp_val = sexp_has_arrived( node, nullptr ); break; case OP_HAS_DEPARTED: sexp_val = sexp_has_departed( node, nullptr ); break; case OP_IS_DISABLED: sexp_val = sexp_is_disabled_xor_disarmed( node, true, nullptr ); break; case OP_IS_DISARMED: sexp_val = sexp_is_disabled_xor_disarmed( node, false, nullptr ); break; case OP_WAYPOINTS_DONE: sexp_val = sexp_are_waypoints_done( node, nullptr ); 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: case OP_HAS_UNDOCKED: case OP_HAS_DOCKED_DELAY: case OP_HAS_UNDOCKED_DELAY: sexp_val = sexp_has_docked_or_undocked(node, op_num); 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_IS_DISABLED_DELAY: sexp_val = sexp_is_disabled_xor_disarmed_delay(node, true); break; case OP_IS_DISARMED_DELAY: sexp_val = sexp_is_disabled_xor_disarmed_delay(node, false); 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: case OP_HAS_TIME_ELAPSED_MSECS: sexp_val = sexp_has_time_elapsed(node, op_num == OP_HAS_TIME_ELAPSED_MSECS); break; case OP_MODIFY_VARIABLE: sexp_modify_variable(node); sexp_val = SEXP_TRUE; // SEXP_TRUE means only do once. break; case OP_MODIFY_VARIABLE_XSTR: sexp_modify_variable_xstr(node); sexp_val = SEXP_TRUE; // SEXP_TRUE means only do once. break; case OP_GET_VARIABLE_BY_INDEX: sexp_val = sexp_get_variable_by_index(node); break; case OP_SET_VARIABLE_BY_INDEX: sexp_set_variable_by_index(node); sexp_val = SEXP_TRUE; break; case OP_COPY_VARIABLE_FROM_INDEX: sexp_copy_variable_from_index(node); sexp_val = SEXP_TRUE; break; case OP_COPY_VARIABLE_BETWEEN_INDEXES: sexp_copy_variable_between_indexes(node); sexp_val = SEXP_TRUE; break; case OP_CONTAINER_ADD_TO_LIST: case OP_CONTAINER_REMOVE_FROM_LIST: case OP_CONTAINER_ADD_TO_MAP: case OP_CONTAINER_REMOVE_FROM_MAP: case OP_CONTAINER_GET_MAP_KEYS: case OP_CLEAR_CONTAINER: case OP_COPY_CONTAINER: case OP_APPLY_CONTAINER_FILTER: sexp_val = sexp_container_eval_change_sexp(op_num, node); break; case OP_TIME_SHIP_DESTROYED: case OP_TIME_WING_DESTROYED: case OP_TIME_SHIP_DEPARTED: case OP_TIME_WING_DEPARTED: sexp_val = sexp_time_exited(node, op_num); break; case OP_TIME_SHIP_ARRIVED: case OP_TIME_WING_ARRIVED: sexp_val = sexp_time_arrived(node, op_num == OP_TIME_SHIP_ARRIVED); break; case OP_MISSION_TIME: sexp_val = sexp_mission_time(); break; case OP_MISSION_TIME_MSECS: sexp_val = sexp_mission_time_msecs(); break; case OP_TIME_DOCKED: case OP_TIME_UNDOCKED: sexp_val = sexp_time_docked_or_undocked(node, op_num == OP_TIME_DOCKED); break; case OP_AFTERBURNER_LEFT: case OP_WEAPON_ENERGY_LEFT: sexp_val = sexp_get_energy_pct(node, op_num); break; case OP_SHIELDS_LEFT: sexp_val = sexp_shields_left(node); break; case OP_HITS_LEFT: case OP_SIM_HITS_LEFT: sexp_val = sexp_hits_left(node, op_num == OP_SIM_HITS_LEFT); break; case OP_HITS_LEFT_SUBSYSTEM: sexp_val = sexp_hits_left_subsystem(node); break; case OP_HITS_LEFT_SUBSYSTEM_GENERIC: sexp_val = sexp_hits_left_subsystem_generic(node); break; case OP_HITS_LEFT_SUBSYSTEM_SPECIFIC: sexp_val = sexp_hits_left_subsystem_specific(node); break; case OP_SPECIAL_WARP_DISTANCE: sexp_val = sexp_special_warp_dist(node); break; case OP_DISTANCE: sexp_val = sexp_distance(node, sexp_retail_distance3); break; case OP_DISTANCE_CENTER: sexp_val = sexp_distance(node, sexp_center_distance3); break; case OP_DISTANCE_BBOX: sexp_val = sexp_distance(node, sexp_bbox_distance3); break; case OP_DISTANCE_CENTER_SUBSYSTEM: sexp_val = sexp_distance_subsystem(node, sexp_center_distance_point); break; case OP_DISTANCE_BBOX_SUBSYSTEM: sexp_val = sexp_distance_subsystem(node, sexp_bbox_distance_point); break; case OP_ANGLE_FVEC_TARGET: sexp_val = sexp_angle_fvec_target(node); break; case OP_NUM_WITHIN_BOX: sexp_val = sexp_num_within_box(node); break; case OP_IS_IN_BOX: sexp_val = sexp_is_in_box(node); break; case OP_IS_IN_MISSION: sexp_val = sexp_is_in_mission(node); break; case OP_HAS_ARMOR_TYPE: sexp_val = sexp_has_armor_type(node); break; case OP_IS_DOCKED: sexp_val = sexp_is_docked(node); break; case OP_IS_SHIP_VISIBLE: sexp_val = sexp_is_ship_visible(node); break; case OP_IS_SHIP_STEALTHY: sexp_val = sexp_check_ship_flag(node, Ship::Ship_Flags::Stealth); break; case OP_IS_FRIENDLY_STEALTH_VISIBLE: sexp_val = sexp_check_ship_flag(node, Ship::Ship_Flags::Friendly_stealth_invis); 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_CARGO_KNOWN_DELAY ); break; case OP_HAS_BEEN_TAGGED_DELAY: sexp_val = sexp_has_been_tagged_delay(node); break; case OP_ARE_SHIP_FLAGS_SET: sexp_val = sexp_are_ship_flags_set(node); break; case OP_ARE_WING_FLAGS_SET: sexp_val = sexp_are_wing_flags_set(node); break; case OP_IS_SHIP_EMP_ACTIVE: sexp_val = sexp_is_ship_emp_active(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_GET_DAMAGE_CAUSED: sexp_val = sexp_get_damage_caused(node); break; case OP_PERCENT_SHIPS_ARRIVED: case OP_PERCENT_SHIPS_DEPARTED: case OP_PERCENT_SHIPS_DESTROYED: case OP_PERCENT_SHIPS_DISARMED: case OP_PERCENT_SHIPS_DISABLED: case OP_PERCENT_SHIPS_SCANNED: sexp_val = sexp_percent_ships_arrive_depart_destroy_disarm_disable_scan(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: case OP_WHEN_ARGUMENT: case OP_IF_THEN_ELSE: sexp_val = eval_when( node, op_num ); break; case OP_PERFORM_ACTIONS_BOOL_FIRST: case OP_PERFORM_ACTIONS_BOOL_LAST: case OP_FUNCTIONAL_WHEN: case OP_ON_MISSION_SKIP: sexp_val = eval_perform_actions( node, op_num ); break; case OP_SWITCH: eval_switch(node); sexp_val = SEXP_TRUE; break; case OP_COND: sexp_val = eval_cond(node); break; // Goober5000: special case; evaluate like when, but flush the sexp tree // and return SEXP_NAN so this will always be re-evaluated case OP_EVERY_TIME: case OP_EVERY_TIME_ARGUMENT: eval_when( node, op_num ); flush_sexp_tree(node); sexp_val = SEXP_NAN; break; // Goober5000 case OP_ANY_OF: sexp_val = eval_any_of( cur_node, referenced_node ); break; // Goober5000 case OP_EVERY_OF: sexp_val = eval_every_of( cur_node, referenced_node ); break; // Goober5000 case OP_RANDOM_OF: sexp_val = eval_random_of( cur_node, referenced_node ); break; // Goober5000 and Karajorma case OP_RANDOM_MULTIPLE_OF: sexp_val = eval_random_multiple_of( cur_node, referenced_node ); break; // Goober5000 case OP_NUMBER_OF: sexp_val = eval_number_of( cur_node, referenced_node ); break; // Karajorma case OP_IN_SEQUENCE: sexp_val = eval_in_sequence( cur_node, referenced_node ); break; // Goober5000 case OP_FOR_COUNTER: sexp_val = eval_for_counter( cur_node, referenced_node ); break; // Goober5000 case OP_FOR_SHIP_CLASS: case OP_FOR_SHIP_TYPE: case OP_FOR_SHIP_TEAM: case OP_FOR_SHIP_SPECIES: sexp_val = eval_for_ship_collection( cur_node, referenced_node, op_num ); break; // Goober5000 case OP_FOR_PLAYERS: sexp_val = eval_for_players( cur_node, referenced_node ); break; // Goober5000 case OP_FOR_SUBSYSTEMS: sexp_val = eval_for_subsystems( cur_node, referenced_node ); break; // jg18 case OP_FOR_CONTAINER_DATA: case OP_FOR_MAP_CONTAINER_KEYS: sexp_val = eval_for_container( cur_node, referenced_node, op_num ); break; // MageKing17 case OP_FIRST_OF: sexp_val = eval_first_of( cur_node, referenced_node ); break; // Karajorma case OP_INVALIDATE_ALL_ARGUMENTS: case OP_VALIDATE_ALL_ARGUMENTS: sexp_change_all_argument_validity(cur_node, (op_num == OP_INVALIDATE_ALL_ARGUMENTS)); sexp_val = SEXP_TRUE; break; // Goober5000 case OP_INVALIDATE_ARGUMENT: case OP_VALIDATE_ARGUMENT: sexp_change_argument_validity(node, (op_num == OP_INVALIDATE_ARGUMENT)); sexp_val = SEXP_TRUE; break; case OP_DO_FOR_VALID_ARGUMENTS: // do-for-valid-arguments should only ever be called within eval_when() Warning(LOCATION, "do-for-valid-arguments was encountered in eval_sexp()!"); break; case OP_NUM_VALID_ARGUMENTS: sexp_val = sexp_num_valid_arguments( cur_node ); break; // this is a "conditional", but it's not really treated the same way as when, etc. case OP_FUNCTIONAL_IF_THEN_ELSE: sexp_val = sexp_functional_if_then_else(node); break; // this too case OP_FUNCTIONAL_SWITCH: sexp_val = sexp_functional_switch(node); break; // sexpressions with side effects case OP_CHANGE_IFF: sexp_change_iff(node); sexp_val = SEXP_TRUE; break; case OP_ADD_SHIP_GOAL: case OP_ADD_WING_GOAL: case OP_ADD_GOAL: sexp_add_goal(node); sexp_val = SEXP_TRUE; break; case OP_REMOVE_GOAL: sexp_remove_goal(node); sexp_val = SEXP_TRUE; break; case OP_CLEAR_SHIP_GOALS: case OP_CLEAR_WING_GOALS: case OP_CLEAR_GOALS: sexp_clear_goals(node); sexp_val = SEXP_TRUE; break; case OP_PROTECT_SHIP: case OP_UNPROTECT_SHIP: sexp_protect_ships(node, (op_num == OP_PROTECT_SHIP)); sexp_val = SEXP_TRUE; break; case OP_BEAM_PROTECT_SHIP: case OP_BEAM_UNPROTECT_SHIP: sexp_beam_protect_ships(node, (op_num == OP_BEAM_PROTECT_SHIP)); sexp_val = SEXP_TRUE; break; case OP_TURRET_PROTECT_SHIP: case OP_TURRET_UNPROTECT_SHIP: sexp_turret_protect_ships(node, (op_num == OP_TURRET_PROTECT_SHIP)); sexp_val = SEXP_TRUE; break; case OP_SHIP_STEALTHY: case OP_SHIP_UNSTEALTHY: sexp_ships_stealthy(node, (op_num == OP_SHIP_STEALTHY)); sexp_val = SEXP_TRUE; break; case OP_FRIENDLY_STEALTH_INVISIBLE: case OP_FRIENDLY_STEALTH_VISIBLE: sexp_friendly_stealth_invisible(node, (op_num == OP_FRIENDLY_STEALTH_INVISIBLE)); sexp_val = SEXP_TRUE; break; case OP_SHIP_INVISIBLE: case OP_SHIP_VISIBLE: sexp_ships_visible(node, (op_num == OP_SHIP_VISIBLE)); sexp_val = SEXP_TRUE; break; // Goober5000 case OP_SHIP_TAG: case OP_SHIP_UNTAG: sexp_ship_tag(node, (op_num == OP_SHIP_TAG)); sexp_val = SEXP_TRUE; break; case OP_SHIP_CHANGE_ALT_NAME: case OP_SHIP_CHANGE_DISPLAY_NAME: case OP_SHIP_CHANGE_CALLSIGN: sexp_ship_change_alt_name_or_display_name_or_callsign(node, op_num); sexp_val = SEXP_TRUE; break; case OP_SET_DEATH_MESSAGE: sexp_set_death_message(node); sexp_val = SEXP_TRUE; break; case OP_ALTER_SHIP_FLAG: sexp_alter_ship_flag(node); sexp_val = SEXP_TRUE; break; case OP_ALTER_WING_FLAG: sexp_alter_wing_flag(node); sexp_val = SEXP_TRUE; break; case OP_CANCEL_FUTURE_WAVES: sexp_cancel_future_waves(node); sexp_val = SEXP_TRUE; break; case OP_SHIP_VULNERABLE: case OP_SHIP_INVULNERABLE: sexp_ships_invulnerable(node, (op_num == OP_SHIP_INVULNERABLE)); sexp_val = SEXP_TRUE; break; case OP_SHIP_BOMB_TARGETABLE: case OP_SHIP_BOMB_UNTARGETABLE: sexp_ships_bomb_targetable(node, (op_num == OP_SHIP_BOMB_TARGETABLE)); sexp_val = SEXP_TRUE; break; case OP_SHIP_GUARDIAN: case OP_SHIP_NO_GUARDIAN: sexp_ships_guardian(node, (op_num == OP_SHIP_GUARDIAN)); sexp_val = SEXP_TRUE; break; case OP_SHIP_GUARDIAN_THRESHOLD: sexp_ship_guardian_threshold(node); sexp_val = SEXP_TRUE; break; case OP_SHIP_SUBSYS_GUARDIAN_THRESHOLD: sexp_ship_subsys_guardian_threshold(node); sexp_val = SEXP_TRUE; break; case OP_SET_GUARD_RANGE: sexp_set_guard_range(node); sexp_val = SEXP_TRUE; break; case OP_SHIP_SUBSYS_TARGETABLE: sexp_ship_deal_with_subsystem_flag(cur_node, node, Ship::Subsystem_Flags::Untargetable, true, false); sexp_val = SEXP_TRUE; break; case OP_SHIP_SUBSYS_UNTARGETABLE: sexp_ship_deal_with_subsystem_flag(cur_node, node, Ship::Subsystem_Flags::Untargetable, true, true); sexp_val = SEXP_TRUE; break; case OP_TURRET_SUBSYS_TARGET_DISABLE: sexp_val = SEXP_TRUE; sexp_ship_deal_with_subsystem_flag(cur_node, node, Ship::Subsystem_Flags::No_SS_targeting, false, true); break; case OP_TURRET_SUBSYS_TARGET_ENABLE: sexp_val = SEXP_TRUE; sexp_ship_deal_with_subsystem_flag(cur_node, node, Ship::Subsystem_Flags::No_SS_targeting, false, false); break; case OP_SHIP_SUBSYS_NO_REPLACE: sexp_ship_deal_with_subsystem_flag(cur_node, node, Ship::Subsystem_Flags::No_replace, true); sexp_val = SEXP_TRUE; break; case OP_SHIP_SUBSYS_NO_LIVE_DEBRIS: sexp_ship_deal_with_subsystem_flag(cur_node, node, Ship::Subsystem_Flags::No_live_debris, true); sexp_val = SEXP_TRUE; break; case OP_SHIP_SUBSYS_VANISHED: sexp_ship_deal_with_subsystem_flag(cur_node, node, Ship::Subsystem_Flags::Vanished, true); sexp_val = SEXP_TRUE; break; case OP_SHIP_SUBSYS_IGNORE_IF_DEAD: sexp_ship_deal_with_subsystem_flag(cur_node, node, Ship::Subsystem_Flags::Missiles_ignore_if_dead, false); sexp_val = SEXP_TRUE; break; case OP_SHIP_CREATE: sexp_ship_create(node); sexp_val = SEXP_TRUE; break; case OP_PROP_CREATE: sexp_prop_create(node); sexp_val = SEXP_TRUE; break; case OP_WEAPON_CREATE: sexp_weapon_create(node); sexp_val = SEXP_TRUE; break; case OP_SHIP_VANISH: sexp_ship_vanish(node); sexp_val = SEXP_TRUE; break; case OP_PROP_VANISH: sexp_prop_vanish(node); sexp_val = SEXP_TRUE; break; case OP_DESTROY_INSTANTLY: case OP_DESTROY_INSTANTLY_WITH_DEBRIS: sexp_destroy_instantly(node, (op_num == OP_DESTROY_INSTANTLY_WITH_DEBRIS)); sexp_val = SEXP_TRUE; break; //-Sesquipedalian case OP_SHIELDS_ON: case OP_SHIELDS_OFF: sexp_shields_off(node, (op_num == OP_SHIELDS_OFF)); sexp_val = SEXP_TRUE; break; //-Sesquipedalian case OP_KAMIKAZE: sexp_kamikaze(node, (op_num == OP_KAMIKAZE)); sexp_val = SEXP_TRUE; break; case OP_SET_CARGO: sexp_set_cargo(node); sexp_val = SEXP_TRUE; break; case OP_IS_CARGO: sexp_val = sexp_is_cargo(node); break; case OP_CHANGE_AI_CLASS: sexp_change_ai_class(node); sexp_val = SEXP_TRUE; break; case OP_IS_AI_CLASS: sexp_val = sexp_is_ai_class(node); break; case OP_IS_SHIP_TYPE: case OP_IS_SHIP_CLASS: sexp_val = sexp_is_ship_class_or_type(node, op_num == OP_IS_SHIP_CLASS); break; case OP_CHANGE_SOUNDTRACK: sexp_change_soundtrack(node); sexp_val = SEXP_TRUE; break; case OP_PLAY_SOUND_FROM_TABLE: sexp_play_sound_from_table(node); sexp_val = SEXP_TRUE; break; case OP_PLAY_SOUND_FROM_FILE: sexp_play_sound_from_file(node); sexp_val = SEXP_TRUE; break; case OP_CLOSE_SOUND_FROM_FILE: sexp_close_sound_from_file(node); sexp_val = SEXP_TRUE; break; case OP_PAUSE_SOUND_FROM_FILE: sexp_pause_sound_from_file(node); sexp_val = SEXP_TRUE; break; case OP_SET_SOUND_ENVIRONMENT: sexp_set_sound_environment(node); sexp_val = SEXP_TRUE; break; case OP_UPDATE_SOUND_ENVIRONMENT: sexp_update_sound_environment(node); sexp_val = SEXP_TRUE; break; case OP_ADJUST_AUDIO_VOLUME: sexp_adjust_audio_volume(node); sexp_val = SEXP_TRUE; break; case OP_HUD_DISABLE: sexp_hud_disable(node); sexp_val = SEXP_TRUE; break; case OP_HUD_DISABLE_EXCEPT_MESSAGES: sexp_hud_disable_except_messages(node); sexp_val = SEXP_TRUE; break; case OP_HUD_SET_TEXT: sexp_hud_set_text(node); sexp_val = SEXP_TRUE; break; case OP_HUD_SET_XSTR: sexp_hud_set_xstr(node); sexp_val = SEXP_TRUE; break; case OP_HUD_SET_TEXT_NUM: sexp_hud_set_text_num(node); sexp_val = SEXP_TRUE; break; case OP_HUD_SET_COORDS: sexp_hud_set_coords(node); sexp_val = SEXP_TRUE; break; case OP_HUD_SET_FRAME: sexp_hud_set_frame(node); sexp_val = SEXP_TRUE; break; case OP_HUD_SET_COLOR: sexp_hud_set_color(node); sexp_val = SEXP_TRUE; break; case OP_HUD_RESET_COLOR: sexp_hud_reset_color(node); sexp_val = SEXP_TRUE; break; case OP_HUD_SET_MAX_TARGETING_RANGE: sexp_hud_set_max_targeting_range(node); sexp_val = SEXP_TRUE; break; case OP_HUD_DISPLAY_GAUGE: sexp_hud_display_gauge(node); sexp_val = SEXP_TRUE; break; case OP_HUD_SET_MESSAGE: sexp_hud_set_message(node); sexp_val = SEXP_TRUE; break; case OP_HUD_FORCE_SENSOR_STATIC: sexp_hud_force_static(node); sexp_val = SEXP_TRUE; break; case OP_HUD_FORCE_EMP_EFFECT: sexp_hud_force_emp(node); sexp_val = SEXP_TRUE; break; case OP_SET_SQUADRON_WINGS: sexp_set_squadron_wings(node); sexp_val = SEXP_TRUE; break; // Goober5000 case OP_PLAYER_USE_AI: case OP_PLAYER_NOT_USE_AI: sexp_player_use_ai(op_num == OP_PLAYER_USE_AI); sexp_val = SEXP_TRUE; break; // LuytenKy case OP_SET_PLAYER_TARGET: sexp_set_player_target(node); sexp_val = SEXP_TRUE; break; case OP_CLEAR_PLAYER_TARGET: sexp_clear_player_target(); sexp_val = SEXP_TRUE; break; // Kestrellius case OP_SET_FRIENDLY_DAMAGE_CAPS: sexp_set_friendly_damage_caps(node); sexp_val = SEXP_TRUE; break; //Karajorma case OP_ALLOW_TREASON: sexp_allow_treason(node); sexp_val = SEXP_TRUE; break; //Karajorma case OP_SET_PLAYER_ORDERS: sexp_set_player_orders(node); sexp_val = SEXP_TRUE; break; //MjnMixael case OP_SET_ORDER_ALLOWED_TARGET: sexp_set_order_allowed_for_target(node); sexp_val = SEXP_TRUE; break; //MjnMixael case OP_ENABLE_GENERAL_ORDERS: sexp_enable_or_validate_general_orders(node, true); sexp_val = SEXP_TRUE; break; //MjnMixael case OP_VALIDATE_GENERAL_ORDERS: sexp_enable_or_validate_general_orders(node, false); sexp_val = SEXP_TRUE; break; // MjnMixael case OP_CREATE_BOLT: sexp_create_bolt(node); sexp_val = SEXP_TRUE; break; // Goober5000 case OP_EXPLOSION_EFFECT: sexp_explosion_effect(node); sexp_val = SEXP_TRUE; break; // Goober5000 case OP_WARP_EFFECT: sexp_warp_effect(node); sexp_val = SEXP_TRUE; break; case OP_SEND_MESSAGE: sexp_send_message(node); sexp_val = SEXP_TRUE; break; case OP_SEND_BUILTIN_MESSAGE: sexp_send_builtin_message(node); sexp_val = SEXP_TRUE; break; // Karajorma case OP_ENABLE_BUILTIN_MESSAGES: case OP_DISABLE_BUILTIN_MESSAGES: sexp_toggle_builtin_messages (node, op_num == OP_ENABLE_BUILTIN_MESSAGES); sexp_val = SEXP_TRUE; break; case OP_SET_PERSONA: sexp_set_persona (node); sexp_val = SEXP_TRUE; break; case OP_SET_MISSION_MOOD: sexp_set_mission_mood (node); sexp_val = SEXP_TRUE; break; case OP_SEND_MESSAGE_LIST: case OP_SEND_MESSAGE_CHAIN: sexp_send_message_list(node, op_num == OP_SEND_MESSAGE_CHAIN); sexp_val = SEXP_TRUE; break; case OP_SEND_RANDOM_MESSAGE: sexp_send_random_message(node); sexp_val = SEXP_TRUE; break; case OP_SELF_DESTRUCT: sexp_self_destruct(node); sexp_val = SEXP_TRUE; break; case OP_NEXT_MISSION: sexp_next_mission(node); sexp_val = SEXP_TRUE; break; case OP_END_OF_CAMPAIGN: sexp_end_of_campaign(node); sexp_val = SEXP_TRUE; break; case OP_END_CAMPAIGN: sexp_end_campaign(node); sexp_val = SEXP_TRUE; break; case OP_SABOTAGE_SUBSYSTEM: sexp_sabotage_subsystem(node); sexp_val = SEXP_TRUE; break; case OP_REPAIR_SUBSYSTEM: sexp_repair_subsystem(node); sexp_val = SEXP_TRUE; break; case OP_SET_SUBSYSTEM_STRNGTH: sexp_set_subsystem_strength(node); sexp_val = SEXP_TRUE; break; case OP_DESTROY_SUBSYS_INSTANTLY: sexp_destroy_subsys_instantly(node); sexp_val = SEXP_TRUE; break; case OP_INVALIDATE_GOAL: case OP_VALIDATE_GOAL: sexp_change_goal_validity( node, (op_num==OP_INVALIDATE_GOAL?0:1) ); sexp_val = SEXP_TRUE; break; case OP_TRANSFER_CARGO: sexp_transfer_cargo(node); sexp_val = SEXP_TRUE; break; case OP_EXCHANGE_CARGO: sexp_exchange_cargo(node); sexp_val = SEXP_TRUE; break; case OP_JETTISON_CARGO_DELAY: case OP_JETTISON_CARGO_NEW: sexp_jettison_cargo(node, (op_num==OP_JETTISON_CARGO_NEW)); sexp_val = SEXP_TRUE; break; case OP_SET_DOCKED: sexp_set_docked(node); sexp_val = SEXP_TRUE; break; case OP_CARGO_NO_DEPLETE: sexp_cargo_no_deplete(node); sexp_val = SEXP_TRUE; break; case OP_SET_SCANNED: // Goober5000 case OP_SET_UNSCANNED: sexp_set_scanned_unscanned(node, op_num == OP_SET_SCANNED); sexp_val = SEXP_TRUE; break; case OP_SET_SPECIAL_WARPOUT_NAME: sexp_special_warpout_name(node); sexp_val = SEXP_TRUE; break; //-WMC case OP_MISSION_SET_NEBULA: sexp_mission_set_nebula(node); sexp_val = SEXP_TRUE; break; case OP_MISSION_SET_SUBSPACE: sexp_mission_set_subspace(node); sexp_val = SEXP_TRUE; break; case OP_CHANGE_BACKGROUND: sexp_change_background(node); sexp_val = SEXP_TRUE; break; case OP_ADD_SUN_BITMAP: case OP_ADD_SUN_BITMAP_NEW: case OP_ADD_BACKGROUND_BITMAP: case OP_ADD_BACKGROUND_BITMAP_NEW: sexp_add_background_bitmap(node, op_num == OP_ADD_SUN_BITMAP || op_num == OP_ADD_SUN_BITMAP_NEW, op_num == OP_ADD_SUN_BITMAP_NEW || op_num == OP_ADD_BACKGROUND_BITMAP_NEW); sexp_val = SEXP_TRUE; break; case OP_REMOVE_SUN_BITMAP: case OP_REMOVE_BACKGROUND_BITMAP: sexp_remove_background_bitmap(node, op_num == OP_REMOVE_SUN_BITMAP); sexp_val = SEXP_TRUE; break; case OP_NEBULA_CHANGE_STORM: sexp_nebula_change_storm(node); sexp_val = SEXP_TRUE; break; case OP_NEBULA_TOGGLE_POOF: case OP_NEBULA_SET_POOFS: sexp_nebula_set_poofs(node, op_num == OP_NEBULA_TOGGLE_POOF); sexp_val = SEXP_TRUE; break; case OP_NEBULA_FADE_POOF: case OP_NEBULA_FADE_POOFS: sexp_nebula_fade_poofs(node, op_num == OP_NEBULA_FADE_POOF); sexp_val = SEXP_TRUE; break; case OP_NEBULA_CHANGE_PATTERN: sexp_nebula_change_pattern(node); sexp_val = SEXP_TRUE; break; case OP_NEBULA_CHANGE_FOG_COLOR: sexp_nebula_change_fog_color(node); sexp_val = SEXP_TRUE; break; case OP_NEBULA_SET_RANGE: sexp_nebula_set_range(node); sexp_val = SEXP_TRUE; break; case OP_VOLUMETRICS_TOGGLE: sexp_volumetrics_toggle(node); sexp_val = SEXP_TRUE; break; case OP_TOGGLE_ASTEROID_FIELD: sexp_toggle_asteroid_field(node); sexp_val = SEXP_TRUE; break; case OP_SET_ASTEROID_FIELD: sexp_set_asteroid_field(node, false); sexp_val = SEXP_TRUE; break; case OP_SET_DEBRIS_FIELD: sexp_set_debris_field(node, false); sexp_val = SEXP_TRUE; break; case OP_CONFIG_ASTEROID_FIELD: sexp_set_asteroid_field(node, true); sexp_val = SEXP_TRUE; break; case OP_CONFIG_DEBRIS_FIELD: sexp_set_debris_field(node, true); sexp_val = SEXP_TRUE; break; case OP_CONFIG_FIELD_TARGETS: sexp_config_field_targets(node); sexp_val = SEXP_TRUE; break; case OP_SET_MOTION_DEBRIS: sexp_set_motion_debris_type(node); sexp_val = SEXP_TRUE; break; case OP_END_MISSION: sexp_end_mission(node); sexp_val = SEXP_TRUE; break; // Goober5000 case OP_SET_DEBRIEFING_TOGGLED: sexp_set_debriefing_toggled(node); sexp_val = SEXP_TRUE; break; // Goober5000 case OP_SET_DEBRIEFING_PERSONA: sexp_set_debriefing_persona(node); sexp_val = SEXP_TRUE; break; // MjnMixael case OP_SET_TRAITOR_OVERRIDE: sexp_set_traitor_override(node); sexp_val = SEXP_TRUE; break; // Goober5000 case OP_FORCE_JUMP: sexp_force_jump(); sexp_val = SEXP_TRUE; break; // sexpressions for setting flag for good/bad time for someone to rearm case OP_GOOD_REARM_TIME: case OP_BAD_REARM_TIME: sexp_good_bad_time_to_rearm(node, op_num == OP_GOOD_REARM_TIME); sexp_val = SEXP_TRUE; break; case OP_GRANT_PROMOTION: sexp_grant_promotion(); sexp_val = SEXP_TRUE; break; case OP_GRANT_MEDAL: sexp_grant_medal(node); sexp_val = SEXP_TRUE; break; case OP_SHIP_VAPORIZE: case OP_SHIP_NO_VAPORIZE: sexp_ships_vaporize( node, (op_num == OP_SHIP_VAPORIZE) ); sexp_val = SEXP_TRUE; break; case OP_SET_EXPLOSION_OPTION: sexp_set_explosion_option(node); sexp_val = SEXP_TRUE; break; case OP_DONT_COLLIDE_INVISIBLE: case OP_COLLIDE_INVISIBLE: sexp_dont_collide_invisible( node, (op_num == OP_DONT_COLLIDE_INVISIBLE) ); sexp_val = SEXP_TRUE; break; case OP_SET_MOBILE: case OP_SET_IMMOBILE: sexp_set_immobile(node, (op_num == OP_SET_IMMOBILE)); sexp_val = SEXP_TRUE; break; case OP_IGNORE_KEY: sexp_ignore_key(node); sexp_val = SEXP_TRUE; break; // Goober5000 - sigh, was this messed up all along? case OP_WARP_BROKEN: case OP_WARP_NOT_BROKEN: case OP_WARP_NEVER: case OP_WARP_ALLOWED: sexp_deal_with_warp( node, (op_num==OP_WARP_BROKEN) || (op_num==OP_WARP_NOT_BROKEN), (op_num==OP_WARP_BROKEN) || (op_num==OP_WARP_NEVER) ); sexp_val = SEXP_TRUE; break; // Goober5000 case OP_SET_SUBSPACE_DRIVE: sexp_set_subspace_drive(node); sexp_val = SEXP_TRUE; break; case OP_GOOD_PRIMARY_TIME: sexp_good_primary_time(node); sexp_val = SEXP_TRUE; break; case OP_GOOD_SECONDARY_TIME: sexp_good_secondary_time(node); sexp_val = SEXP_TRUE; break; // sexpressions to allow shpis/weapons during the course of a mission case OP_ALLOW_SHIP: sexp_allow_ship(node); sexp_val = SEXP_TRUE; break; case OP_ALLOW_WEAPON: sexp_allow_weapon(node); sexp_val = SEXP_TRUE; break; case OP_TECH_ADD_SHIP: sexp_tech_add_ship(node); sexp_val = SEXP_TRUE; break; case OP_TECH_ADD_WEAPON: sexp_tech_add_weapon(node); sexp_val = SEXP_TRUE; break; case OP_TECH_ADD_INTEL: case OP_TECH_ADD_INTEL_XSTR: sexp_tech_toggle_intel(node, true, op_num == OP_TECH_ADD_INTEL_XSTR); sexp_val = SEXP_TRUE; break; case OP_TECH_REMOVE_INTEL: case OP_TECH_REMOVE_INTEL_XSTR: sexp_tech_toggle_intel(node, false, op_num == OP_TECH_REMOVE_INTEL_XSTR); sexp_val = SEXP_TRUE; break; case OP_TECH_RESET_TO_DEFAULT: sexp_tech_reset_to_default(); sexp_val = SEXP_TRUE; break; case OP_CHANGE_PLAYER_SCORE: sexp_change_player_score(node); sexp_val = SEXP_TRUE; break; case OP_CHANGE_TEAM_SCORE: sexp_change_team_score(node); sexp_val = SEXP_TRUE; break; case OP_RED_ALERT: sexp_red_alert(); sexp_val = SEXP_TRUE; break; case OP_SET_OBJECT_SPEED_X: case OP_SET_OBJECT_SPEED_Y: case OP_SET_OBJECT_SPEED_Z: sexp_set_object_speed(node, op_num - OP_SET_OBJECT_SPEED_X); sexp_val = SEXP_TRUE; break; case OP_GET_OBJECT_SPEED_X: case OP_GET_OBJECT_SPEED_Y: case OP_GET_OBJECT_SPEED_Z: sexp_val = sexp_get_object_speed(node, op_num - OP_GET_OBJECT_SPEED_X); break; case OP_GET_OBJECT_X: case OP_GET_OBJECT_Y: case OP_GET_OBJECT_Z: sexp_val = sexp_get_object_coordinate(node, op_num - OP_GET_OBJECT_X); break; case OP_GET_OBJECT_PITCH: case OP_GET_OBJECT_BANK: case OP_GET_OBJECT_HEADING: sexp_val = sexp_get_object_angle(node, op_num - OP_GET_OBJECT_PITCH); break; case OP_SET_OBJECT_POSITION: sexp_set_object_position(node); sexp_val = SEXP_TRUE; break; case OP_SET_OBJECT_ORIENTATION: sexp_set_object_orientation(node); sexp_val = SEXP_TRUE; break; case OP_SET_OBJECT_FACING: case OP_SET_OBJECT_FACING_OBJECT: sexp_set_object_facing(node, op_num == OP_SET_OBJECT_FACING_OBJECT); sexp_val = SEXP_TRUE; break; case OP_SHIP_MANEUVER: case OP_SHIP_ROT_MANEUVER: case OP_SHIP_LAT_MANEUVER: sexp_set_ship_maneuver(node, op_num); sexp_val = SEXP_TRUE; 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_KEY_RESET_MULTIPLE: sexp_key_reset(node); sexp_val = SEXP_TRUE; break; case OP_MISSILE_LOCKED: sexp_val = sexp_missile_locked(node); break; case OP_TARGETED: sexp_val = sexp_targeted(node); break; case OP_NODE_TARGETED: sexp_val = sexp_node_targeted(node); break; case OP_SPEED: sexp_val = sexp_speed(node); break; case OP_GET_THROTTLE_SPEED: sexp_val = sexp_get_throttle_speed(node); break; case OP_SET_PLAYER_THROTTLE_SPEED: sexp_set_player_throttle_speed(node); sexp_val = SEXP_TRUE; break; case OP_PRIMARIES_DEPLETED: case OP_SECONDARIES_DEPLETED: sexp_val = sexp_weapons_depleted(node, op_num == OP_PRIMARIES_DEPLETED); break; case OP_FACING: sexp_val = sexp_facing(node); break; case OP_IS_FACING: sexp_val = sexp_is_facing(node); break; case OP_FACING2: sexp_val = sexp_facing2(node); break; case OP_ORDER: sexp_val = sexp_order(node); break; case OP_QUERY_ORDERS: sexp_val = sexp_query_orders(node); break; case OP_TIME_TO_GOAL: sexp_val = sexp_time_to_goal(node); break; case OP_SET_HUD_TIME_PAD: sexp_set_hud_time_pad(node); sexp_val = SEXP_TRUE; break; // Karajorma case OP_RESET_ORDERS: sexp_reset_orders(node); sexp_val = SEXP_TRUE; 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 = SEXP_TRUE; break; case OP_FLASH_HUD_GAUGE: sexp_flash_hud_gauge(node); sexp_val = SEXP_TRUE; break; case OP_SET_TRAINING_CONTEXT_FLY_PATH: sexp_set_training_context_fly_path(node); sexp_val = SEXP_TRUE; break; case OP_SET_TRAINING_CONTEXT_SPEED: sexp_set_training_context_speed(node); sexp_val = SEXP_TRUE; break; // Karajorma case OP_STRING_TO_INT: sexp_val = sexp_string_to_int(node); break; // Goober5000 case OP_INT_TO_STRING: sexp_int_to_string(node); sexp_val = SEXP_TRUE; break; // Goober5000 case OP_STRING_CONCATENATE: sexp_string_concatenate(node); sexp_val = SEXP_TRUE; break; // Goober5000 case OP_STRING_CONCATENATE_BLOCK: sexp_string_concatenate_block(node); sexp_val = SEXP_TRUE; break; // Goober5000 case OP_STRING_GET_SUBSTRING: sexp_string_get_substring(node); sexp_val = SEXP_TRUE; break; // Goober5000 case OP_STRING_SET_SUBSTRING: sexp_string_set_substring(node); sexp_val = SEXP_TRUE; break; // Goober5000 case OP_STRING_GET_LENGTH: sexp_val = sexp_string_get_length(node); break; // Karajorma/jg18 case OP_IS_CONTAINER_EMPTY: case OP_GET_CONTAINER_SIZE: case OP_LIST_HAS_DATA: case OP_LIST_DATA_INDEX: case OP_MAP_HAS_KEY: case OP_MAP_HAS_DATA_ITEM: sexp_val = sexp_container_eval_status_sexp(op_num, node); break; // Karajorma case OP_DEBUG: sexp_debug(node); sexp_val = SEXP_TRUE; break; case OP_NOT_AN_OP: // zero represents a non-operator return sexp_atoi(cur_node); case OP_NOP: sexp_val = SEXP_TRUE; break; case OP_BEAM_FIRE: case OP_BEAM_FIRE_COORDS: sexp_beam_fire(node, op_num == OP_BEAM_FIRE_COORDS); sexp_val = SEXP_TRUE; break; case OP_BEAM_FLOATING_FIRE: sexp_beam_floating_fire(node); sexp_val = SEXP_TRUE; break; case OP_IS_TAGGED: sexp_val = sexp_is_tagged(node); break; case OP_IS_PLAYER: sexp_val = sexp_is_player(node); break; case OP_NUM_KILLS: case OP_NUM_ASSISTS: case OP_SHIP_SCORE: case OP_SHIP_DEATHS: case OP_RESPAWNS_LEFT: sexp_val = sexp_return_player_data(node, op_num); break; case OP_SET_RESPAWNS: sexp_set_respawns(node); sexp_val = SEXP_TRUE; break; case OP_ADD_REMOVE_HOTKEY: sexp_add_remove_hotkey(node); sexp_val = SEXP_TRUE; break; case OP_CLEAR_WEAPONS: case OP_CLEAR_DEBRIS: sexp_clear_weapons_or_debris(node, op_num); sexp_val = SEXP_TRUE; 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: case OP_BEAM_LOCK: case OP_TURRET_FREE: case OP_TURRET_LOCK: sexp_beam_or_turret_free_or_lock(node, op_num == OP_BEAM_FREE || op_num == OP_BEAM_LOCK, op_num == OP_BEAM_FREE || op_num == OP_TURRET_FREE); sexp_val = SEXP_TRUE; break; case OP_BEAM_FREE_ALL: case OP_BEAM_LOCK_ALL: case OP_TURRET_FREE_ALL: case OP_TURRET_LOCK_ALL: sexp_beam_or_turret_free_or_lock_all(node, op_num == OP_BEAM_FREE_ALL || op_num == OP_BEAM_LOCK_ALL, op_num == OP_BEAM_FREE_ALL || op_num == OP_TURRET_FREE_ALL); sexp_val = SEXP_TRUE; break; case OP_TURRET_CHANGE_WEAPON: sexp_val = SEXP_TRUE; sexp_turret_change_weapon(node); break; case OP_TURRET_SET_DIRECTION_PREFERENCE: sexp_val = SEXP_TRUE; sexp_turret_set_direction_preference(node); break; case OP_TURRET_SET_RATE_OF_FIRE: sexp_val = SEXP_TRUE; sexp_turret_set_rate_of_fire(node); break; case OP_TURRET_SET_OPTIMUM_RANGE: sexp_val = SEXP_TRUE; sexp_turret_set_optimum_range(node); break; case OP_TURRET_SET_TARGET_PRIORITIES: sexp_val = SEXP_TRUE; sexp_turret_set_target_priorities(node); break; case OP_TURRET_SET_TARGET_ORDER: sexp_val = SEXP_TRUE; sexp_turret_set_target_order(node); break; case OP_TURRET_SET_FORCED_SUBSYS_TARGET: case OP_TURRET_SET_FORCED_TARGET: sexp_val = SEXP_TRUE; sexp_turret_set_forced_target(node, op_num == OP_TURRET_SET_FORCED_SUBSYS_TARGET); break; case OP_TURRET_CLEAR_FORCED_TARGET: sexp_val = SEXP_TRUE; sexp_turret_clear_forced_target(node); break; case OP_TURRET_SET_INACCURACY: sexp_val = SEXP_TRUE; sexp_turret_set_inaccuracy(node); break; case OP_SET_ARMOR_TYPE: sexp_val = SEXP_TRUE; sexp_set_armor_type(node); break; case OP_WEAPON_SET_DAMAGE_TYPE: sexp_val = SEXP_TRUE; sexp_weapon_set_damage_type(node); break; case OP_SHIP_SET_DAMAGE_TYPE: sexp_val = SEXP_TRUE; sexp_ship_set_damage_type(node); break; case OP_SHIP_SHOCKWAVE_SET_DAMAGE_TYPE: sexp_val = SEXP_TRUE; sexp_ship_shockwave_set_damage_type(node); break; case OP_FIELD_SET_DAMAGE_TYPE: sexp_val = SEXP_TRUE; sexp_field_set_damage_type(node); break; case OP_SHIP_TURRET_TARGET_ORDER: sexp_val = SEXP_TRUE; sexp_ship_turret_target_order(node); break; case OP_ADD_REMOVE_ESCORT: sexp_val = SEXP_TRUE; sexp_add_remove_escort(node); break; case OP_DAMAGED_ESCORT_LIST: sexp_val = SEXP_TRUE; sexp_damage_escort_list(node); break; case OP_DAMAGED_ESCORT_LIST_ALL: sexp_val = SEXP_TRUE; sexp_damage_escort_list_all(node); break; case OP_AWACS_SET_RADIUS: sexp_val = SEXP_TRUE; sexp_awacs_set_radius(node); break; case OP_PRIMITIVE_SENSORS_SET_RANGE: sexp_primitive_sensors_set_range(node); sexp_val = SEXP_TRUE; break; case OP_CAP_WAYPOINT_SPEED: sexp_val = SEXP_TRUE; sexp_cap_waypoint_speed(node); break; case OP_SET_WING_FORMATION: sexp_set_wing_formation(node); sexp_val = SEXP_TRUE; break; case OP_TURRET_TAGGED_ONLY_ALL: case OP_TURRET_TAGGED_CLEAR_ALL: sexp_turret_tagged_only_or_clear_all(node, op_num == OP_TURRET_TAGGED_ONLY_ALL); sexp_val = SEXP_TRUE; break; case OP_SUBSYS_SET_RANDOM: sexp_val = SEXP_TRUE; sexp_subsys_set_random(node); break; case OP_SUPERNOVA_START: sexp_val = SEXP_TRUE; sexp_supernova_start(node); break; case OP_SUPERNOVA_STOP: sexp_val = SEXP_TRUE; sexp_supernova_stop(node); break; case OP_GET_SUPERNOVA_STAGE: return sexp_get_supernova_stage(node); break; case OP_OVERRIDE_MOTION_DEBRIS: sexp_val = SEXP_TRUE; sexp_set_motion_debris(node); break; case OP_WEAPON_RECHARGE_PCT: case OP_SHIELD_RECHARGE_PCT: case OP_ENGINE_RECHARGE_PCT: sexp_val = sexp_gse_recharge_pct(node, op_num); break; case OP_GET_ETS_VALUE: sexp_val = sexp_get_ets_value(node); break; case OP_SET_ETS_VALUES: sexp_val = SEXP_TRUE; sexp_set_ets_values(node); break; case OP_GET_POWER_OUTPUT: sexp_val = sexp_get_power_output(node); break; case OP_SHIELD_QUAD_LOW: sexp_val = sexp_shield_quad_low(node); break; case OP_PRIMARY_AMMO_PCT: case OP_SECONDARY_AMMO_PCT: case OP_GET_PRIMARY_AMMO: case OP_GET_SECONDARY_AMMO: sexp_val = sexp_get_ammo(node, false, op_num == OP_PRIMARY_AMMO_PCT || op_num == OP_GET_PRIMARY_AMMO, op_num == OP_PRIMARY_AMMO_PCT || op_num == OP_SECONDARY_AMMO_PCT); break; case OP_TURRET_GET_PRIMARY_AMMO: case OP_TURRET_GET_SECONDARY_AMMO: sexp_val = sexp_get_ammo(node, true, op_num == OP_TURRET_GET_PRIMARY_AMMO, false); break; // Karajorma case OP_SET_PRIMARY_AMMO: case OP_SET_SECONDARY_AMMO: sexp_set_ammo(node, false, op_num == OP_SET_PRIMARY_AMMO); sexp_val = SEXP_TRUE; break; case OP_TURRET_SET_PRIMARY_AMMO: case OP_TURRET_SET_SECONDARY_AMMO: sexp_set_ammo(node, true, op_num == OP_TURRET_SET_PRIMARY_AMMO); sexp_val = SEXP_TRUE; break; // Karajorma case OP_GET_NUM_COUNTERMEASURES: sexp_val = sexp_get_countermeasures(node); break; case OP_IS_PRIMARY_SELECTED: case OP_IS_SECONDARY_SELECTED: sexp_val = sexp_is_weapon_selected(node, op_num == OP_IS_PRIMARY_SELECTED); break; // Goober5000 case OP_SET_SUPPORT_SHIP: sexp_set_support_ship(node); sexp_val = SEXP_TRUE; break; // Goober5000 case OP_SET_ARRIVAL_INFO: sexp_set_arrival_info(node); sexp_val = SEXP_TRUE; break; // Goober5000 case OP_SET_DEPARTURE_INFO: sexp_set_departure_info(node); sexp_val = SEXP_TRUE; break; // Goober5000 case OP_CHANGE_SHIP_CLASS: sexp_change_ship_class(node); sexp_val = SEXP_TRUE; break; // Goober5000 case OP_SHIP_COPY_DAMAGE: sexp_ship_copy_damage(node); sexp_val = SEXP_TRUE; break; //-Bobboau case OP_ACTIVATE_GLOW_POINTS: case OP_DEACTIVATE_GLOW_POINTS: sexp_activate_deactivate_glow_points(node, op_num == OP_ACTIVATE_GLOW_POINTS); sexp_val = SEXP_TRUE; break; //-Bobboau case OP_ACTIVATE_GLOW_MAPS: case OP_DEACTIVATE_GLOW_MAPS: sexp_activate_deactivate_glow_maps(node, op_num == OP_ACTIVATE_GLOW_MAPS); sexp_val = SEXP_TRUE; break; //-Bobboau case OP_ACTIVATE_GLOW_POINT_BANK: case OP_DEACTIVATE_GLOW_POINT_BANK: sexp_activate_deactivate_glow_point_bank(node, op_num == OP_ACTIVATE_GLOW_POINT_BANK); sexp_val = SEXP_TRUE; break; // taylor case OP_SET_SKYBOX_MODEL: sexp_set_skybox_model(node); sexp_val = SEXP_TRUE; break; case OP_SET_SKYBOX_ORIENT: sexp_set_skybox_orientation(node); sexp_val = SEXP_TRUE; break; case OP_SET_SKYBOX_ALPHA: sexp_set_skybox_alpha(node); sexp_val = SEXP_TRUE; break; case OP_TURRET_TAGGED_SPECIFIC: case OP_TURRET_TAGGED_CLEAR_SPECIFIC: sexp_turret_tagged_or_clear_specific(node, op_num == OP_TURRET_TAGGED_SPECIFIC); sexp_val = SEXP_TRUE; break; case OP_LOCK_ROTATING_SUBSYSTEM: case OP_FREE_ROTATING_SUBSYSTEM: case OP_LOCK_TRANSLATING_SUBSYSTEM: case OP_FREE_TRANSLATING_SUBSYSTEM: sexp_set_subsys_motion_lock_free(node, op_num == OP_LOCK_ROTATING_SUBSYSTEM || op_num == OP_FREE_ROTATING_SUBSYSTEM, op_num == OP_LOCK_ROTATING_SUBSYSTEM || op_num == OP_LOCK_TRANSLATING_SUBSYSTEM); sexp_val = SEXP_TRUE; break; case OP_REVERSE_ROTATING_SUBSYSTEM: case OP_REVERSE_TRANSLATING_SUBSYSTEM: sexp_reverse_moving_subsystem(node, op_num == OP_REVERSE_ROTATING_SUBSYSTEM); sexp_val = SEXP_TRUE; break; case OP_ROTATING_SUBSYS_SET_TURN_TIME: case OP_TRANSLATING_SUBSYS_SET_SPEED: sexp_moving_subsys_set_turn_time_or_speed(node, op_num == OP_ROTATING_SUBSYS_SET_TURN_TIME); sexp_val = SEXP_TRUE; break; case OP_TRIGGER_SUBMODEL_ANIMATION: sexp_trigger_submodel_animation(node); sexp_val = SEXP_TRUE; break; // Karajorma case OP_SET_PRIMARY_WEAPON: case OP_SET_SECONDARY_WEAPON: sexp_set_weapon(node, op_num == OP_SET_PRIMARY_WEAPON); sexp_val = SEXP_TRUE; break; // Karajorma case OP_SET_NUM_COUNTERMEASURES: sexp_set_countermeasures(node); sexp_val = SEXP_TRUE; break; // Karajorma case OP_LOCK_PRIMARY_WEAPON: case OP_UNLOCK_PRIMARY_WEAPON: sexp_deal_with_primary_lock(node, op_num == OP_LOCK_PRIMARY_WEAPON); sexp_val = SEXP_TRUE; break; case OP_LOCK_SECONDARY_WEAPON: case OP_UNLOCK_SECONDARY_WEAPON: sexp_deal_with_secondary_lock(node, op_num == OP_LOCK_SECONDARY_WEAPON); sexp_val = SEXP_TRUE; break; // KeldorKatarn case OP_LOCK_AFTERBURNER: case OP_UNLOCK_AFTERBURNER: sexp_deal_with_afterburner_lock(node, op_num == OP_LOCK_AFTERBURNER); sexp_val = SEXP_TRUE; break; case OP_SET_AFTERBURNER_ENERGY: case OP_SET_WEAPON_ENERGY: case OP_SET_SHIELD_ENERGY: sexp_set_energy_pct(node, op_num); sexp_val = SEXP_TRUE; break; case OP_SET_AMBIENT_LIGHT: sexp_set_ambient_light(node); sexp_val = SEXP_TRUE; break; case OP_SET_POST_EFFECT: sexp_set_post_effect(node); sexp_val = SEXP_TRUE; break; case OP_RESET_POST_EFFECTS: sexp_reset_post_effects(); sexp_val = SEXP_TRUE; break; case OP_TURRET_FIRED_SINCE: sexp_val = sexp_turret_fired_delay(node); break; case OP_PRIMARY_FIRED_SINCE: case OP_SECONDARY_FIRED_SINCE: sexp_val = sexp_weapon_fired_delay(node, op_num); break; case OP_HAS_PRIMARY_WEAPON: case OP_HAS_SECONDARY_WEAPON: case OP_TURRET_HAS_PRIMARY_WEAPON: case OP_TURRET_HAS_SECONDARY_WEAPON: sexp_val = sexp_has_weapon(node, op_num); break; case OP_DIRECTIVE_VALUE: sexp_val = sexp_directive_value(node); break; case OP_GET_HOTKEY: sexp_val = sexp_get_hotkey(node); break; case OP_CHANGE_SUBSYSTEM_NAME: sexp_change_subsystem_name(node); sexp_val = SEXP_TRUE; break; case OP_NUM_SHIPS_IN_BATTLE: // phreak sexp_val = sexp_num_ships_in_battle(node); break; // Karajorma case OP_NUM_SHIPS_IN_WING: sexp_val=sexp_num_ships_in_wing(node); break; case OP_CURRENT_SPEED: sexp_val = sexp_current_speed(node); break; case OP_NAV_IS_VISITED: //kazan sexp_val = is_nav_visited(node); break; case OP_NAV_DISTANCE: //kazan sexp_val = distance_to_nav(node); break; case OP_NAV_ADD_WAYPOINT: //kazan sexp_val = SEXP_TRUE; add_nav_waypoint(node); break; case OP_NAV_ADD_SHIP: //kazan sexp_val = SEXP_TRUE; add_nav_ship(node); break; case OP_NAV_DEL: //kazan sexp_val = SEXP_TRUE; del_nav(node); break; case OP_NAV_HIDE: //kazan case OP_NAV_UNHIDE: //kazan sexp_val = SEXP_TRUE; hide_unhide_nav(node, op_num == OP_NAV_HIDE); break; case OP_NAV_RESTRICT: //kazan case OP_NAV_UNRESTRICT: //kazan sexp_val = SEXP_TRUE; restrict_unrestrict_nav(node, op_num == OP_NAV_RESTRICT); break; case OP_NAV_SET_VISITED: //kazan case OP_NAV_UNSET_VISITED: //kazan sexp_val = SEXP_TRUE; set_unset_nav_visited(node, op_num == OP_NAV_SET_VISITED); break; case OP_NAV_SET_CARRY: //kazan case OP_NAV_UNSET_CARRY: //kazan sexp_val = SEXP_TRUE; set_unset_nav_carry_status(node, op_num == OP_NAV_SET_CARRY); break; case OP_NAV_SET_NEEDSLINK: case OP_NAV_UNSET_NEEDSLINK: sexp_val = SEXP_TRUE; set_unset_nav_needslink(node, op_num == OP_NAV_SET_NEEDSLINK); break; case OP_NAV_ISLINKED: sexp_val = is_nav_linked(node); break; case OP_NAV_USEAP: sexp_val = SEXP_TRUE; set_use_ap(node); break; case OP_NAV_USECINEMATICS: sexp_val = SEXP_TRUE; set_use_ap_cinematics(node); break; //Talon1024 case OP_NAV_SELECT: case OP_NAV_UNSELECT: sexp_val = SEXP_TRUE; select_unselect_nav(node, op_num == OP_NAV_SELECT); break; case OP_NAV_SET_COLOR: case OP_NAV_SET_VISITED_COLOR: set_nav_color(node, op_num == OP_NAV_SET_VISITED_COLOR); sexp_val = SEXP_TRUE; break; case OP_SCRAMBLE_MESSAGES: case OP_UNSCRAMBLE_MESSAGES: sexp_scramble_messages(node, op_num == OP_SCRAMBLE_MESSAGES ); sexp_val = SEXP_TRUE; break; case OP_CUTSCENES_SET_CUTSCENE_BARS: case OP_CUTSCENES_UNSET_CUTSCENE_BARS: sexp_val = SEXP_TRUE; sexp_toggle_cutscene_bars(node, op_num == OP_CUTSCENES_SET_CUTSCENE_BARS); break; case OP_CUTSCENES_FADE_IN: case OP_CUTSCENES_FADE_OUT: sexp_val = SEXP_TRUE; sexp_fade(node, op_num == OP_CUTSCENES_FADE_IN); break; case OP_CUTSCENES_SET_CAMERA: sexp_val = SEXP_TRUE; sexp_set_camera(node); break; case OP_CUTSCENES_SET_CAMERA_FACING: sexp_val = SEXP_TRUE; sexp_set_camera_facing(node); break; case OP_CUTSCENES_SET_CAMERA_FACING_OBJECT: sexp_val = SEXP_TRUE; sexp_set_camera_facing_object(node); break; case OP_CUTSCENES_SET_CAMERA_FOV: sexp_val = SEXP_TRUE; sexp_set_camera_fov(node); break; case OP_CUTSCENES_SET_CAMERA_HOST: sexp_val = SEXP_TRUE; sexp_set_camera_host(node); break; case OP_CUTSCENES_SET_CAMERA_POSITION: sexp_val = SEXP_TRUE; sexp_set_camera_position(node); break; case OP_CUTSCENES_SET_CAMERA_ROTATION: sexp_val = SEXP_TRUE; sexp_set_camera_rotation(node); break; case OP_CUTSCENES_SET_CAMERA_TARGET: sexp_val = SEXP_TRUE; sexp_set_camera_target(node); break; case OP_CUTSCENES_SET_FOV: sexp_val = SEXP_TRUE; sexp_set_fov(node); break; case OP_CUTSCENES_GET_FOV: sexp_val = sexp_get_fov(); break; case OP_CUTSCENES_RESET_FOV: sexp_val = SEXP_TRUE; sexp_reset_fov(); break; case OP_CUTSCENES_RESET_CAMERA: sexp_val = SEXP_TRUE; sexp_reset_camera(node); break; case OP_CUTSCENES_SHOW_SUBTITLE: sexp_val = SEXP_TRUE; sexp_show_subtitle(node); break; case OP_CUTSCENES_SHOW_SUBTITLE_TEXT: sexp_val = SEXP_TRUE; sexp_show_subtitle_text(node); break; case OP_CUTSCENES_SHOW_SUBTITLE_IMAGE: sexp_val = SEXP_TRUE; sexp_show_subtitle_image(node); break; case OP_CUTSCENES_SET_TIME_COMPRESSION: sexp_val = SEXP_TRUE; sexp_set_time_compression(node); break; case OP_CUTSCENES_RESET_TIME_COMPRESSION: sexp_val = SEXP_TRUE; sexp_reset_time_compression(); break; case OP_CUTSCENES_FORCE_PERSPECTIVE: sexp_val = SEXP_TRUE; sexp_force_perspective(node); break; case OP_ALLOW_PHOTO_MODE: sexp_val = SEXP_TRUE; sexp_allow_photo_mode(node); break; case OP_SET_CAMERA_SHUDDER: sexp_val = SEXP_TRUE; sexp_set_camera_shudder(node); break; case OP_JUMP_NODE_SET_JUMPNODE_NAME: //CommanderDJ sexp_val = SEXP_TRUE; sexp_set_jumpnode_name(node); break; case OP_JUMP_NODE_SET_JUMPNODE_DISPLAY_NAME: sexp_val = SEXP_TRUE; sexp_set_jumpnode_display_name(node); break; case OP_JUMP_NODE_SET_JUMPNODE_COLOR: sexp_val = SEXP_TRUE; sexp_set_jumpnode_color(node); break; case OP_JUMP_NODE_SET_JUMPNODE_MODEL: sexp_val = SEXP_TRUE; sexp_set_jumpnode_model(node); break; case OP_JUMP_NODE_SHOW_JUMPNODE: case OP_JUMP_NODE_HIDE_JUMPNODE: sexp_show_hide_jumpnode(node, op_num == OP_JUMP_NODE_SHOW_JUMPNODE); sexp_val = SEXP_TRUE; break; case OP_SCRIPT_EVAL_BOOL: sexp_val = sexp_script_eval(node, OPR_BOOL); break; case OP_SCRIPT_EVAL_NUM: sexp_val = sexp_script_eval(node, OPR_NUMBER); break; case OP_SCRIPT_EVAL_STRING: sexp_val = sexp_script_eval(node, OPR_STRING); break; case OP_SCRIPT_EVAL: sexp_val = sexp_script_eval(node, OPR_NULL); break; case OP_SCRIPT_EVAL_BLOCK: sexp_val = sexp_script_eval(node, OPR_NULL, true); break; case OP_SCRIPT_EVAL_MULTI: sexp_script_eval_multi(node); sexp_val = SEXP_TRUE; break; case OP_CHANGE_IFF_COLOR: sexp_change_iff_color(node); sexp_val = SEXP_TRUE; break; case OP_DISABLE_ETS: case OP_ENABLE_ETS: sexp_disable_ets(node, (op_num == OP_DISABLE_ETS)); sexp_val = SEXP_TRUE; break; case OP_FORCE_GLIDE: sexp_val = SEXP_TRUE; sexp_force_glide(node); break; case OP_HUD_SET_DIRECTIVE: sexp_val = SEXP_TRUE; sexp_hud_set_directive(node); break; case OP_HUD_GAUGE_SET_ACTIVE: sexp_val = SEXP_TRUE; sexp_hud_gauge_set_active(node); break; case OP_HUD_SET_CUSTOM_GAUGE_ACTIVE: sexp_val = SEXP_TRUE; sexp_hud_set_custom_gauge_active(node); break; case OP_HUD_CLEAR_MESSAGES: sexp_val = SEXP_TRUE; sexp_hud_clear_messages(); break; case OP_HUD_ACTIVATE_GAUGE_TYPE: sexp_val = SEXP_TRUE; sexp_hud_activate_gauge_type(node); break; case OP_HUD_SET_BUILTIN_GAUGE_ACTIVE: sexp_val = SEXP_TRUE; sexp_hud_set_builtin_gauge_active(node); break; case OP_ADD_TO_COLGROUP: case OP_REMOVE_FROM_COLGROUP: sexp_manipulate_colgroup(node, op_num == OP_ADD_TO_COLGROUP); sexp_val = SEXP_TRUE; break; case OP_ADD_TO_COLGROUP_NEW: case OP_REMOVE_FROM_COLGROUP_NEW: sexp_manipulate_colgroup_new(node, op_num == OP_ADD_TO_COLGROUP_NEW); sexp_val = SEXP_TRUE; break; case OP_GET_COLGROUP_ID: sexp_val = sexp_get_colgroup(node); break; case OP_SHIP_EFFECT: sexp_val = SEXP_TRUE; sexp_ship_effect(node); break; case OP_CLEAR_SUBTITLES: sexp_val = SEXP_TRUE; sexp_clear_subtitles(); break; case OP_SET_THRUSTERS: sexp_val = SEXP_TRUE; sexp_set_thrusters(node); break; case OP_CHANGE_TEAM_COLOR: sexp_val = SEXP_TRUE; sexp_change_team_color(node); break; case OP_CALL_SSM_STRIKE: sexp_val = SEXP_TRUE; sexp_call_ssm_strike(node); break; case OP_PLAYER_IS_CHEATING_BASTARD: sexp_val = sexp_player_is_cheating_bastard(); break; case OP_IS_LANGUAGE: sexp_val = sexp_is_language(node); break; case OP_USED_CHEAT: sexp_val = sexp_cheat_used(node); break; case OP_IS_IN_TURRET_FOV: sexp_val = sexp_is_in_turret_fov(node); break; case OP_REPLACE_TEXTURE: case OP_REPLACE_TEXTURE_SKYBOX: sexp_val = SEXP_TRUE; sexp_replace_texture(node, op_num == OP_REPLACE_TEXTURE_SKYBOX); break; case OP_SET_ALPHA_MULT: sexp_val = SEXP_TRUE; sexp_set_alphamult(node); break; case OP_TRIGGER_ANIMATION_NEW: sexp_val = SEXP_TRUE; sexp_trigger_submodel_animation_new(node); break; case OP_UPDATE_MOVEABLE: sexp_val = SEXP_TRUE; sexp_update_moveable_animation(node); break; case OP_ADVANCE_MOVEABLE: sexp_val = SEXP_TRUE; sexp_advance_moveable_animation(node); break; case OP_STOP_LOOPING_ANIMATION: sexp_val = SEXP_TRUE; sexp_stop_looping_animation(node); break; case OP_SET_GRAVITY_ACCEL: sexp_set_gravity_accel(node); sexp_val = SEXP_TRUE; break; case OP_FORCE_REARM: sexp_force_rearm(node); sexp_val = SEXP_TRUE; break; case OP_ABORT_REARM: sexp_abort_rearm(node); sexp_val = SEXP_TRUE; break; default:{ // Check if we have a dynamic SEXP with this operator and if there is, execute that auto dynamicSEXP = sexp::get_dynamic_sexp(op_num); if (dynamicSEXP != nullptr) { sexp_val = dynamicSEXP->execute(node, cur_node); } else { Error(LOCATION, "Looking for SEXP operator, found '%s'.\n", CTEXT(cur_node)); } } } if (Log_event) { add_to_event_log_buffer(cur_node, get_operator_index(cur_node), sexp_val); } Assert(!Current_sexp_operator.empty()); Current_sexp_operator.pop_back(); Assertion(sexp_val != UNINITIALIZED, "SEXP %s didn't return a value!", CTEXT(cur_node)); // 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. if (sexp_val == SEXP_KNOWN_TRUE) { Sexp_nodes[cur_node].value = SEXP_KNOWN_TRUE; return SEXP_TRUE; } if (sexp_val == SEXP_KNOWN_FALSE) { Sexp_nodes[cur_node].value = SEXP_KNOWN_FALSE; return SEXP_FALSE; } if ( sexp_val == SEXP_NAN ) { Sexp_nodes[cur_node].value = SEXP_NAN; // not a number values are false I would suspect return SEXP_FALSE; } if ( sexp_val == SEXP_NAN_FOREVER ) { Sexp_nodes[cur_node].value = SEXP_NAN_FOREVER; // Goober5000 changed from sexp_val to SEXP_FALSE on 2/21/2006 in accordance with above comment // NOTE: we return false rather than known-false to match the SEXP_KNOWN_FALSE case above return SEXP_FALSE; } if ( sexp_val == SEXP_CANT_EVAL ) { Sexp_nodes[cur_node].value = SEXP_CANT_EVAL; Assume_event_is_current = false; // indicate sexp isn't current yet return SEXP_FALSE; } 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; } } /** * Only runs on the client machines not the server. Evaluates the contents of a SEXP packet and calls the relevent multi_sexp_x * function(s). */ void multi_sexp_eval() { int op_num; Assert (MULTIPLAYER_CLIENT); while (Current_sexp_network_packet.sexp_bytes_left > 0) { op_num = Current_sexp_network_packet.get_next_operator(); Assert (Current_sexp_network_packet.sexp_bytes_left); if (op_num < 0) { Warning(LOCATION, "Received invalid operator number from host in multi_sexp_eval(). Entire packet may be corrupt. Discarding packet"); return; } switch(op_num) { case OP_CHANGE_SOUNDTRACK: multi_sexp_change_soundtrack(); break; case OP_SET_PERSONA: multi_sexp_set_persona(); break; case OP_CHANGE_SUBSYSTEM_NAME: multi_sexp_change_subsystem_name(); break; case OP_SHIP_SUBSYS_NO_REPLACE: multi_sexp_deal_with_subsys_flag(Ship::Subsystem_Flags::No_replace); break; case OP_SHIP_SUBSYS_NO_LIVE_DEBRIS: multi_sexp_deal_with_subsys_flag(Ship::Subsystem_Flags::No_live_debris); break; case OP_SHIP_SUBSYS_VANISHED: multi_sexp_deal_with_subsys_flag(Ship::Subsystem_Flags::Vanished); break; case OP_SHIP_SUBSYS_IGNORE_IF_DEAD: multi_sexp_deal_with_subsys_flag(Ship::Subsystem_Flags::Missiles_ignore_if_dead); break; case OP_SHIP_SUBSYS_TARGETABLE: case OP_SHIP_SUBSYS_UNTARGETABLE: multi_sexp_deal_with_subsys_flag(Ship::Subsystem_Flags::Untargetable); break; case OP_SHIP_CHANGE_ALT_NAME: case OP_SHIP_CHANGE_CALLSIGN: case OP_SHIP_CHANGE_DISPLAY_NAME: multi_sexp_ship_change_alt_name_or_display_name_or_callsign(op_num); break; case OP_SET_RESPAWNS: multi_sexp_set_respawns(); break; case OP_CLEAR_WEAPONS: case OP_CLEAR_DEBRIS: multi_sexp_clear_weapons_or_debris(op_num); break; case OP_CHANGE_SHIP_CLASS: multi_sexp_change_ship_class(); break; case OP_PLAY_SOUND_FROM_TABLE: multi_sexp_play_sound_from_table(); break; case OP_PLAY_SOUND_FROM_FILE: multi_sexp_play_sound_from_file(); break; case OP_CLOSE_SOUND_FROM_FILE: multi_sexp_close_sound_from_file(); break; case OP_PAUSE_SOUND_FROM_FILE: multi_sexp_pause_sound_from_file(); break; case OP_SHIP_BOMB_TARGETABLE: case OP_SHIP_BOMB_UNTARGETABLE: case OP_SHIP_INVISIBLE: case OP_SHIP_VISIBLE: case OP_SHIP_STEALTHY: case OP_SHIP_UNSTEALTHY: case OP_FRIENDLY_STEALTH_INVISIBLE: case OP_FRIENDLY_STEALTH_VISIBLE: case OP_LOCK_AFTERBURNER: case OP_UNLOCK_AFTERBURNER: case OP_LOCK_PRIMARY_WEAPON: case OP_UNLOCK_PRIMARY_WEAPON: case OP_LOCK_SECONDARY_WEAPON: case OP_UNLOCK_SECONDARY_WEAPON: case OP_SHIELDS_ON: case OP_SHIELDS_OFF: multi_sexp_deal_with_ship_flag(); break; case OP_ALTER_SHIP_FLAG: multi_sexp_alter_ship_flag(); break; case OP_ALTER_WING_FLAG: multi_sexp_alter_wing_flag(); break; case OP_SET_AFTERBURNER_ENERGY: multi_sexp_set_energy_pct(); break; case OP_SET_AMBIENT_LIGHT: multi_sexp_set_ambient_light(); break; case OP_MODIFY_VARIABLE: multi_sexp_modify_variable(); break; case OP_NAV_ADD_WAYPOINT: multi_sexp_add_nav_waypoint(); break; case OP_CUTSCENES_FADE_IN: case OP_CUTSCENES_FADE_OUT: multi_sexp_fade(op_num == OP_CUTSCENES_FADE_IN); break; case OP_NAV_ADD_SHIP: multi_add_nav_ship(); break; case OP_NAV_DEL: multi_del_nav(); break; case OP_ADD_REMOVE_ESCORT: multi_sexp_add_remove_escort(); break; case OP_CUTSCENES_SHOW_SUBTITLE_TEXT: multi_sexp_show_subtitle_text(); break; case OP_CUTSCENES_SHOW_SUBTITLE_IMAGE: multi_sexp_show_subtitle_image(); break; case OP_TRAINING_MSG: multi_sexp_send_training_message(); break; case OP_HUD_DISABLE: multi_sexp_hud_disable(); break; case OP_HUD_DISABLE_EXCEPT_MESSAGES: multi_sexp_hud_disable_except_messages(); break; case OP_FLASH_HUD_GAUGE: multi_sexp_flash_hud_gauge(); break; case OP_HUD_DISPLAY_GAUGE: multi_sexp_hud_display_gauge(); break; case OP_CUTSCENES_SET_CUTSCENE_BARS: case OP_CUTSCENES_UNSET_CUTSCENE_BARS: multi_sexp_toggle_cutscene_bars(op_num == OP_CUTSCENES_SET_CUTSCENE_BARS ); break; case OP_CUTSCENES_SET_CAMERA_FACING: multi_sexp_set_camera_facing(); break; case OP_CUTSCENES_SET_CAMERA_FACING_OBJECT: multi_sexp_set_camera_facing_object(); break; case OP_CUTSCENES_SET_CAMERA_TARGET : multi_sexp_set_camera_target(); break; case OP_CUTSCENES_SET_CAMERA_ROTATION: multi_sexp_set_camera_rotation(); break; case OP_CUTSCENES_SET_CAMERA_FOV: multi_sexp_set_camera_fov(); break; case OP_CUTSCENES_SET_CAMERA_POSITION: multi_sexp_set_camera_position(); break; case OP_SET_CAMERA_SHUDDER: multi_sexp_set_camera_shudder(); break; case OP_CUTSCENES_RESET_CAMERA: multi_sexp_reset_camera(); break; case OP_CUTSCENES_SET_FOV: multi_sexp_set_fov(); break; case OP_CUTSCENES_RESET_FOV: multi_sexp_reset_fov(); break; case OP_JUMP_NODE_SET_JUMPNODE_NAME: multi_sexp_set_jumpnode_name(); break; case OP_JUMP_NODE_SET_JUMPNODE_DISPLAY_NAME: multi_sexp_set_jumpnode_display_name(); break; case OP_IGNORE_KEY: multi_sexp_ignore_key(); break; case OP_JUMP_NODE_SET_JUMPNODE_COLOR: multi_sexp_set_jumpnode_color(); break; case OP_JUMP_NODE_SET_JUMPNODE_MODEL: multi_sexp_set_jumpnode_model(); break; case OP_JUMP_NODE_SHOW_JUMPNODE: case OP_JUMP_NODE_HIDE_JUMPNODE: multi_sexp_show_hide_jumpnode(op_num == OP_JUMP_NODE_SHOW_JUMPNODE); break; case OP_CLEAR_SUBTITLES: multi_sexp_clear_subtitles(); break; case OP_SET_OBJECT_SPEED_X: case OP_SET_OBJECT_SPEED_Y: case OP_SET_OBJECT_SPEED_Z: multi_sexp_set_object_speed(); break; case OP_SET_OBJECT_POSITION: multi_sexp_set_object_position(); break; case OP_CHANGE_TEAM_COLOR: multi_sexp_change_team_color(); break; case OP_HUD_SET_MAX_TARGETING_RANGE: multi_sexp_hud_set_max_targeting_range(); break; case OP_CUTSCENES_SET_TIME_COMPRESSION: multi_sexp_set_time_compression(); break; case OP_CUTSCENES_RESET_TIME_COMPRESSION: multi_sexp_reset_time_compression(); break; case OP_SET_ETS_VALUES: multi_sexp_set_ets_values(); break; case OP_SCRIPT_EVAL_MULTI: multi_sexp_script_eval_multi(); break; case OP_SET_PRIMARY_AMMO: case OP_SET_SECONDARY_AMMO: multi_sexp_set_ammo(false, op_num == OP_SET_PRIMARY_AMMO); break; case OP_TURRET_SET_PRIMARY_AMMO: case OP_TURRET_SET_SECONDARY_AMMO: multi_sexp_set_ammo(true, op_num == OP_TURRET_SET_PRIMARY_AMMO); break; case OP_SET_PRIMARY_WEAPON: case OP_SET_SECONDARY_WEAPON: multi_sexp_set_weapon(); break; case OP_SET_NUM_COUNTERMEASURES: multi_sexp_set_countermeasures(); break; case OP_CHANGE_AI_CLASS: multi_sexp_change_ai_class(); break; case OP_CHANGE_IFF: multi_sexp_change_iff(); break; case OP_CHANGE_IFF_COLOR: multi_sexp_change_iff_color(); break; case OP_DESTROY_INSTANTLY: multi_sexp_destroy_instantly(); break; case OP_DESTROY_SUBSYS_INSTANTLY: multi_sexp_destroy_subsys_instantly(); break; case OP_RED_ALERT: multi_sexp_red_alert(); break; case OP_END_MISSION: multi_sexp_end_mission(); break; // bad sexp in the packet default: // probably just a version error where the host supports a SEXP but a client does not if (Current_sexp_network_packet.sexp_discard_operator()) { Warning(LOCATION, "Received invalid SEXP operator number from host. Operator number %d is not supported by this version.", op_num); } // a more major problem else { Warning(LOCATION, "Received invalid SEXP packet from host. Function involving operator %d lacks termination. Entire packet may be corrupt. Discarding remaining packet", op_num); return; } } Current_sexp_network_packet.finished_callback(); } } // 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; ignore_white_space(); if (*Mp != '(') { char buf[512]; error_display(0, "Expected to find an open parenthesis in the following sexp:\n%s", three_dot_truncate(buf, Mp, 512)); return -1; } Mp++; start_node = get_sexp(); // only need to check syntax if we have a operator if (!Fred_running && (start_node >= 0)) { op = get_operator_index(start_node); if (op < 0) { error_display(0, "Can't find operator %s in operator list!\n", CTEXT(start_node)); return -1; } } return start_node; } int run_sexp(const char* sexpression, bool run_eval_num, bool *is_nan_or_nan_forever) { char* oldMp = Mp; int n, sexp_val = UNINITIALIZED; char buf[8192]; strcpy_s(buf, sexpression); // HACK: ! -> " for (auto ch = buf; *ch; ++ch) { // convert single ! to ", but don't convert !! if (*ch == '!') { if (*(ch + 1) == '!') ++ch; else *ch = '\"'; } } // !! -> ! consolidate_double_characters(buf, '!'); Mp = buf; n = get_sexp_main(); if (n != -1) { if (run_eval_num) { // if this sexp node is an operator, put it in another node so that CAR(node) will find it if (get_operator_index(n) >= 0) n = alloc_sexp("", 1, -1, n, -1); bool is_nan, is_nan_forever; sexp_val = eval_num(n, is_nan, is_nan_forever); if (is_nan_or_nan_forever != nullptr) *is_nan_or_nan_forever = (is_nan || is_nan_forever); } else sexp_val = eval_sexp(n); free_sexp2(n); } Mp = oldMp; return sexp_val; } DCF(sexpc, "Runs the given sexp command, wrapped as an action of a when-true condition") { SCP_string sexp; SCP_string sexp_wrapped; if (dc_optional_string_either("help", "--help")) { dc_printf( "Usage: sexpc \n. Runs the given sexp as '( when ( true ) ( sexpression ) )' .\n" ); return; } dc_stuff_string(sexp); sexp_wrapped = "( when ( true ) ( " + sexp + " ) )"; int sexp_val = run_sexp(sexp_wrapped.c_str()); dc_printf("SEXP '%s' run; sexp_val = %d\n", sexp_wrapped.c_str(), sexp_val); } DCF(sexp, "Runs the given sexp") { SCP_string sexp; if (dc_optional_string_either("help", "--help")) { dc_printf( "Usage: sexp \n. Runs the given sexp.\n"); return; } dc_stuff_string(sexp); int sexp_val = run_sexp(sexp.c_str()); dc_printf("Result = %d\n", sexp_val); } DCF(script, "Runs the given script, without regard to its return value") { SCP_string script; if (dc_optional_string_either("help", "--help")) { dc_printf( "Usage: script