text: /*
   * 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.
   *
  */
  
  
  
  
  #include "ai/aigoals.h"
  #include "ai/ailua.h"
  #include "globalincs/linklist.h"
  #include "mission/missionlog.h"
  #include "mission/missionparse.h"
  #include "network/multi.h"
  #include "network/multimsgs.h"
  #include "object/object.h"
  #include "object/objectdock.h"
  #include "object/waypoint.h"
  #include "parse/sexp.h"
  #include "playerman/player.h"
  #include "scripting/scripting.h"
  #include "ship/ship.h"
  #include "ship/awacs.h"
  #include "weapon/weapon.h"
  
  // Just a reference to this struct being for looking up a ship's team in TVT
  struct ship_registry_entry;
  
  // all ai goals dealt with in this code are goals that are specified through
  // sexpressions in the mission file.  They are either specified as part of a
  // ships goals in the #Object section of the mission file, or are created (and
  // removed) dynamically using the #Events section.  Default goal behaviour and
  // dynamic goals are not handled here.
  
  // defines for player issued goal priorities
  #define PLAYER_PRIORITY_MIN				90
  #define PLAYER_PRIORITY_SHIP				100
  #define PLAYER_PRIORITY_WING				95
  #define PLAYER_PRIORITY_SUPPORT_LOW		10
  
  #define MAX_GOAL_PRIORITY				200
  
  // define for which goals cause other goals to get purged
  // Goober5000 - okay, this seems really stupid.  If any ship in the mission is assigned a goal
  // in PURGE_GOALS_ALL_SHIPS, *every* other ship will have certain goals purged.  So I added
  // PURGE_GOALS_ONE_SHIP for goals which should only purge other goals in the one ship.
  #define PURGE_GOALS_ALL_SHIPS		(AI_GOAL_IGNORE | AI_GOAL_DISABLE_SHIP | AI_GOAL_DISARM_SHIP)
  #define PURGE_GOALS_ONE_SHIP		(AI_GOAL_IGNORE_NEW)
  
  // goals given from the player to other ships in the game are also handled in this
  // code
  
  int	Ai_goal_signature;
  int	Num_ai_dock_names = 0;
  char	Ai_dock_names[MAX_AI_DOCK_NAMES][NAME_LENGTH];
  
  // Used in objecttypes.tbl to define custom ship types
  ai_goal_list Ai_goal_names[] =
  {
  	{ "Attack ship",			AI_GOAL_CHASE,					0 },
  	{ "Dock",					AI_GOAL_DOCK,					0 },
  	{ "Waypoints",				AI_GOAL_WAYPOINTS,				0 },
  	{ "Waypoints once",			AI_GOAL_WAYPOINTS_ONCE,			0 },
  	{ "Depart",					AI_GOAL_WARP,					0 },
  	{ "Attack subsys",			AI_GOAL_DESTROY_SUBSYSTEM,		0 },
  	{ "Form on wing",			AI_GOAL_FORM_ON_WING,			0 },
  	{ "Undock",					AI_GOAL_UNDOCK,					0 },
  	{ "Attack wing",			AI_GOAL_CHASE_WING,				0 },
  	{ "Guard ship",				AI_GOAL_GUARD,					0 },
  	{ "Disable ship",			AI_GOAL_DISABLE_SHIP,			0 },
  	{ "Disarm ship",			AI_GOAL_DISARM_SHIP,			0 },
  	{ "Attack any",				AI_GOAL_CHASE_ANY,				0 },
  	{ "Ignore ship",			AI_GOAL_IGNORE,					0 },
  	{ "Ignore ship (new)",		AI_GOAL_IGNORE_NEW,				0 },
  	{ "Guard wing",				AI_GOAL_GUARD_WING,				0 },
  	{ "Evade ship",				AI_GOAL_EVADE_SHIP,				0 },
  	{ "Stay near ship",			AI_GOAL_STAY_NEAR_SHIP,			0 },
  	{ "keep safe dist",			AI_GOAL_KEEP_SAFE_DISTANCE,		0 },
  	{ "Rearm ship",				AI_GOAL_REARM_REPAIR,			0 },
  	{ "Stay still",				AI_GOAL_STAY_STILL,				0 },
  	{ "Play dead",				AI_GOAL_PLAY_DEAD,				0 },
  	{ "Play dead (persistent)",	AI_GOAL_PLAY_DEAD_PERSISTENT,	0 },
  	{ "Attack weapon",			AI_GOAL_CHASE_WEAPON,			0 },
  	{ "Fly to ship",			AI_GOAL_FLY_TO_SHIP,			0 },
  	{ "Attack ship class",		AI_GOAL_CHASE_SHIP_CLASS,		0 },
  };
  
  int Num_ai_goals = sizeof(Ai_goal_names) / sizeof(ai_goal_list);
  
  // AL 11-17-97: A text description of the AI goals.  This is used for printing out on the
  // HUD what a ship's current orders are.  If the AI goal doesn't correspond to something that
  // ought to be printable, then NULL is used.
  // JAS: Converted to a function in order to externalize the strings
  const char *Ai_goal_text(int goal, int submode)
  {
  	switch(goal)	{
  	case AI_GOAL_CHASE:
  	case AI_GOAL_CHASE_WING:
  	case AI_GOAL_CHASE_SHIP_CLASS:
  		return XSTR( "attack ", 474);
  	case AI_GOAL_DOCK:
  		return XSTR( "dock ", 475);
  	case AI_GOAL_WAYPOINTS:
  	case AI_GOAL_WAYPOINTS_ONCE:
  		return XSTR( "waypoints", 476);
  	case AI_GOAL_DESTROY_SUBSYSTEM:
  		return XSTR( "destroy ", 477);
  	case AI_GOAL_FORM_ON_WING:
  		return XSTR( "form on ", 478);
  	case AI_GOAL_UNDOCK:
  		return XSTR( "undock ", 479);
  	case AI_GOAL_GUARD:
  	case AI_GOAL_GUARD_WING:
  		return XSTR( "guard ", 480);
  	case AI_GOAL_DISABLE_SHIP:
  		return XSTR( "disable ", 481);
  	case AI_GOAL_DISARM_SHIP:
  		return XSTR( "disarm ", 482);
  	case AI_GOAL_EVADE_SHIP:
  		return XSTR( "evade ", 483);
  	case AI_GOAL_REARM_REPAIR:
  		return XSTR( "rearm ", 484);
  	case AI_GOAL_FLY_TO_SHIP:
  		return XSTR( "rendezvous with ", 1597);
  	case AI_GOAL_LUA:
  		auto mode = ai_lua_find_mode(submode);
  		if (mode == nullptr)
  			return nullptr;
  		return mode->hudText;
  	}
  
  	// Avoid compiler warning
  	return nullptr;
  }
  
  void ai_maybe_add_form_goal(wing* wingp)
  {
  	// Cyborg17 - Changes from the client would just get overridden by the server anyway
  	// might as well keep them more closely in sync.
  	if (MULTIPLAYER_CLIENT) {
  		return;
  	}
  
  	int j;
  
  	// iterate through the ship_index list of this wing and check for orders.  We will do
  	// this for all ships in the wing instead of on a wing only basis in case some ships
  	// in the wing actually have different orders than others
  	for (j = 0; j < wingp->current_count; j++) {
  		ai_info* aip;
  
  		Assert(wingp->ship_index[j] != -1);						// get Allender
  
  		aip = &Ai_info[Ships[wingp->ship_index[j]].ai_index];
  		// don't process a Player_ship
  		if (Objects[Ships[aip->shipnum].objnum].flags[Object::Object_Flags::Player_ship]) {
  			continue;
  		}
  
  		// need to add a form on my wing goal here.  Ships are always forming on the player's wing.
  		// it is sufficient enough to check the first goal entry to see if it has a valid goal
  		if (aip->goals[0].ai_mode == AI_GOAL_NONE) {
  			// Need to have a more specific target in multi, or they may end up trying to target standalone placeholder.
  			// So form on their team leader.  In dogfight, all player-slot ai die, so just exclude.
  			if (MULTIPLAYER_MASTER && !(Netgame.type_flags & NG_TYPE_DOGFIGHT)) {
  				int wingnum;
  				if (Netgame.type_flags & NG_TYPE_TEAM) {
  					const ship_registry_entry* ship_regp = ship_registry_get(Ships[wingp->ship_index[j]].ship_name);
  					wingnum = TVT_wings[ship_regp->p_objp->team];
  					ai_add_ship_goal_player(AIG_TYPE_PLAYER_SHIP, AI_GOAL_FORM_ON_WING, -1, Ships[Wings[wingnum].ship_index[Wings[wingnum].special_ship]].ship_name, aip);
  				} else {
  					wingnum = Starting_wings[0];
  					ai_add_ship_goal_player(AIG_TYPE_PLAYER_SHIP, AI_GOAL_FORM_ON_WING, -1, Ships[Wings[wingnum].ship_index[Wings[wingnum].special_ship]].ship_name, aip);
  				}
  			} else if (!(Game_mode & GM_MULTIPLAYER)) {
  				ai_add_ship_goal_player(AIG_TYPE_PLAYER_SHIP, AI_GOAL_FORM_ON_WING, -1, Player_ship->ship_name, aip);
  			}
  		}
  	}
  }
  
  void ai_post_process_mission()
  {
  	object *objp;
  	int i;
  
  	// make sure team visibility is updated first
  	if ( !Fred_running ) {
  		awacs_process();
  	}
  
  	// Check ships in player starting wings.  Those ships should follow these rules:
  	// (1) if they have no orders, they should get a form on my wing order
  	// (2) if they have an order, they are free to act on it.
  	//
  	// So basically, we are checking for (1)
  	if ( !Fred_running ) {
  		//	MK, 5/9/98: Used to iterate through MAX_STARTING_WINGS, but this was too many ships forming on player.
  		// Goober5000 - MK originally iterated on only the first wing; now we iterate on only the player wing
  		// because the player wing may not be first
  		for ( i = 0; i < MAX_STARTING_WINGS; i++ ) {	
  			if (Starting_wings[i] >= 0 && Starting_wings[i] == Player_ship->wingnum) {
  				wing *wingp;
  
  				wingp = &Wings[Starting_wings[i]];
  
  				ai_maybe_add_form_goal( wingp );
  			}
  		}
  	}
  
  	// for every valid ship object, call process_mission_orders to be sure that ships start the
  	// mission following the orders in the mission file right away instead of waiting N seconds
  	// before following them.  Do both the created list and the object list for safety
  	for ( objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
  		if ( objp->type != OBJ_SHIP )
  			continue;
  		ai_process_mission_orders( OBJ_INDEX(objp), &Ai_info[Ships[objp->instance].ai_index] );
  	}
  	for ( objp = GET_FIRST(&obj_create_list); objp != END_OF_LIST(&obj_create_list); objp = GET_NEXT(objp) ) {
  		if ( (objp->type != OBJ_SHIP) || Fred_running )
  			continue;
  		ai_process_mission_orders( OBJ_INDEX(objp), &Ai_info[Ships[objp->instance].ai_index] );
  	}
  
  	return;
  }
  
  /**
   * Determines if a goal is valid for a particular type of ship
   *
   * @param ship Ship type to test
   * @param ai_goal_type Goal type to test
   */
  int ai_query_goal_valid( int ship, int ai_goal_type )
  {
  	int accepted;
  
  	if (ai_goal_type == AI_GOAL_NONE || ai_goal_type == AI_GOAL_LUA)
  		return 1;  // anything can have no orders or Lua'd orders.
  
  	accepted = 0;
  
  	//WMC - This is much simpler with shiptypes.tbl
  	//Except you have to add the orders into it by hand.
  	int ship_type = Ship_info[Ships[ship].ship_info_index].class_type;
  	if(ship_type > -1)
  	{
  		if(ai_goal_type & Ship_types[ship_type].ai_valid_goals) {
  			accepted = 1;
  		}
  	}
  
  	return accepted;
  }
  
  // remove an ai goal from it's list.  Uses the active_goal member as the goal to remove
  void ai_remove_ship_goal( ai_info *aip, int index )
  {
  	// only need to set the ai_mode for the particular goal to AI_GOAL_NONE
  	// reset ai mode to default behavior.  Might get changed next time through
  	// ai goal code look
  	Assert ( index >= 0 );			// must have a valid goal
  
  	if (index == aip->active_goal)
  	{
  		// rearm/repair needs a bit of extra cleanup
  		if (aip->goals[index].ai_mode == AI_GOAL_REARM_REPAIR)
  			ai_abort_rearm_request(&Objects[Ships[aip->shipnum].objnum]);
  
  		aip->active_goal = AI_GOAL_NONE;
  	}
  
  	aip->goals[index].ai_mode = AI_GOAL_NONE;
  	aip->goals[index].signature = -1;
  	aip->goals[index].priority = -1;
  	aip->goals[index].flags.reset(); // must reset the flags since not doing so will screw up goal sorting.
  
  	// mwa -- removed this line 8/5/97.  Just because we remove a goal doesn't mean to do the default
  	// behavior.  We will make the call commented out below in a more reasonable location
  	//ai_do_default_behavior( &Objects[Ships[aip->shipnum].objnum] );
  }
  
  void ai_clear_ship_goals( ai_info *aip )
  {
  	int i;
  
  	for (i = 0; i < MAX_AI_GOALS; i++)
  		ai_remove_ship_goal( aip, i );			// resets active_goal and default behavior
  
  	aip->active_goal = AI_GOAL_NONE;					// for good measure
  
  	// next line moved here on 8/5/97 by MWA
  	// Don't reset player ai (and hence target)
  	// Goober5000 - account for player ai
  	//if ( !((Player_ship != NULL) && (&Ships[aip->shipnum] == Player_ship)) || Player_use_ai ) {
  	if ( (Player_ship == NULL) || (&Ships[aip->shipnum] != Player_ship) || Player_use_ai )
  	{
  		ai_do_default_behavior( &Objects[Ships[aip->shipnum].objnum] );
  	}
  
  	// add scripting hook for 'On Goals Cleared' --wookieejedi
  	if (Script_system.IsActiveAction(CHA_ONGOALSCLEARED)) {
  		Script_system.SetHookObject("Ship", &Objects[Ships[aip->shipnum].objnum]);
  		Script_system.RunCondition(CHA_ONGOALSCLEARED, &Objects[Ships[aip->shipnum].objnum]);
  		Script_system.RemHookVars({"Ship"});
  	}
  }
  
  void ai_clear_wing_goals( wing *wingp )
  {
  	int i;
  
  	// clear the goals for all ships in the wing
  	for (i = 0; i < wingp->current_count; i++) {
  		int num = wingp->ship_index[i];
  
  		if ( num > -1 )
  			ai_clear_ship_goals( &Ai_info[Ships[num].ai_index] );
  
  	}
  
  	// clear out the goals for the wing now
  	for (i = 0; i < MAX_AI_GOALS; i++) {
  		wingp->ai_goals[i].ai_mode = AI_GOAL_NONE;
  		wingp->ai_goals[i].signature = -1;
  		wingp->ai_goals[i].priority = -1;
  		wingp->ai_goals[i].flags.reset();
  	}
  }
  
  // routine which marks a wing goal as being complete.  We get the wingnum and a pointer to the goal
  // structure of the goal to be removed.  This process is slightly tricky since some member of the wing
  // might be pursuing a different goal.  We will have to compare based on mode, submode, priority,
  // and name.. This routine is only currently called from waypoint code!!!
  void ai_mission_wing_goal_complete( int wingnum, ai_goal *remove_goalp )
  {
  	int mode, submode, priority, i;
  	const char *name;
  	ai_goal *aigp;
  	wing *wingp;
  
  	wingp = &Wings[wingnum];
  
  	// set up locals for faster access.
  	mode = remove_goalp->ai_mode;
  	submode = remove_goalp->ai_submode;
  	priority = remove_goalp->priority;
  	name = remove_goalp->target_name;
  
  	Assert ( name );			// should not be NULL!!!!
  
  	// remove the goal from all the ships currently in the wing
  	for (i = 0; i < wingp->current_count; i++ ) {
  		int num, j;
  		ai_info *aip;
  
  		num = wingp->ship_index[i];
  		Assert ( num >= 0 );
  		aip = &Ai_info[Ships[num].ai_index];
  		for ( j = 0; j < MAX_AI_GOALS; j++ ) {
  			aigp = &(aip->goals[j]);
  
  			// don't need to worry about these types of goals since they can't possibly be a goal we are looking for.
  			if ( (aigp->ai_mode == AI_GOAL_NONE) || !aigp->target_name )
  				continue;
  
  			if ( (aigp->ai_mode == mode) && (aigp->ai_submode == submode) && (aigp->priority == priority) && !stricmp(name, aigp->target_name) ) {
  				ai_remove_ship_goal( aip, j );
  				ai_do_default_behavior( &Objects[Ships[aip->shipnum].objnum] );		// do the default behavior
  				break;			// we are all done
  			}
  		}
  	}
  
  	// now remove the goal from the wing
  	for (i = 0; i < MAX_AI_GOALS; i++ ) {
  		aigp = &(wingp->ai_goals[i]);
  		if ( (aigp->ai_mode == AI_GOAL_NONE) || !aigp->target_name )
  			continue;
  
  		if ( (aigp->ai_mode == mode) && (aigp->ai_submode == submode) && (aigp->priority == priority) && !stricmp(name, aigp->target_name) ) {
  			wingp->ai_goals[i].ai_mode = AI_GOAL_NONE;
  			wingp->ai_goals[i].signature = -1;
  			wingp->ai_goals[i].priority = -1;
  			wingp->ai_goals[i].flags.reset();
  			break;
  		}
  	}
  			
  }
  
  // routine which is called with an ai object complete it's goal.  Do some action
  // based on the goal what was just completed
  
  void ai_mission_goal_complete( ai_info *aip )
  {
  	// if the active goal is dynamic or none, just return.  (AI_GOAL_NONE is probably an error, but
  	// I don't think that this is a problem)
  	if ( (aip->active_goal == AI_GOAL_NONE) || (aip->active_goal == AI_ACTIVE_GOAL_DYNAMIC) )
  		return;
  
  	ai_remove_ship_goal( aip, aip->active_goal );
  	ai_do_default_behavior( &Objects[Ships[aip->shipnum].objnum] );		// do the default behavior
  
  }
  
  // function to prune out goals which are no longer valid, based on a goal pointer passed in.
  // for instance, if we get passed a goal of "disable X", then any goals in the given goal array
  // which are destroy, etc, should get removed.  goal list is the list of goals to purge.  It is
  // always MAX_AI_GOALS in length.  This function will only get called when the goal which causes
  // purging becomes valid.
  void ai_goal_purge_invalid_goals( ai_goal *aigp, ai_goal *goal_list, ai_info *aip, int ai_wingnum )
  {
  	int i, j;
  	ai_goal *purge_goal;
  	const char *name;
  	int mode, ship_index, wingnum;
  
  	// get locals for easer access
  	name = aigp->target_name;
  	mode = aigp->ai_mode;
  
  	// these goals cannot be associated to wings, but can to a ship in a wing.  So, we should find out
  	// if the ship is in a wing so we can purge goals which might operate on that wing
  	ship_index = ship_name_lookup(name);
  	if ( ship_index == -1 ) {
  		Int3();						// get allender -- this is sort of odd
  		return;
  	}
  	wingnum = Ships[ship_index].wingnum;
  
  	purge_goal = goal_list;
  	for ( i = 0; i < MAX_AI_GOALS; purge_goal++, i++ ) {
  		int purge_ai_mode, purge_wing;
  
  		purge_ai_mode = purge_goal->ai_mode;
  
  		// don't need to process AI_GOAL_NONE
  		if ( purge_ai_mode == AI_GOAL_NONE )
  			continue;
  
  		// goals must operate on something to be purged.
  		if ( purge_goal->target_name == NULL )
  			continue;
  
  		// goals operating on ship classes are handled slightly differently
  		if ( purge_ai_mode == AI_GOAL_CHASE_SHIP_CLASS ) {
  			// if the target of the purge goal is the same class of ship we are concerned about, then we have a match;
  			// if it is not, then we can continue (see standard ship check below)
  			if ( stricmp(purge_goal->target_name, Ship_info[Ships[ship_index].ship_info_index].name) != 0 )
  				continue;
  		}
  		// standard goals operating on either wings or ships
  		else {
  			// determine if the purge goal is acting either on the ship or the ship's wing.
  			purge_wing = wing_name_lookup( purge_goal->target_name, 1 );
  
  			// if the target of the purge goal is a ship (purge_wing will be -1), then if the names
  			// don't match, we can continue;  if the wing is valid, don't process if the wing numbers
  			// are different.
  			if ( purge_wing == -1 ) {
  				if ( stricmp(purge_goal->target_name, name ) != 0 )
  					continue;
  			} else if ( purge_wing != wingnum )
  				continue;
  		}
  
  		switch (mode)
  		{
  			// ignore goals should get rid of any kind of attack goal
  			case AI_GOAL_IGNORE:
  			case AI_GOAL_IGNORE_NEW:
  				if ( purge_ai_mode & (AI_GOAL_DISABLE_SHIP | AI_GOAL_DISARM_SHIP | AI_GOAL_CHASE | AI_GOAL_CHASE_WING | AI_GOAL_CHASE_SHIP_CLASS | AI_GOAL_DESTROY_SUBSYSTEM) )
  					purge_goal->flags.set(AI::Goal_Flags::Purge);
  				break;
  
  			// disarm/disable goals should remove attacks from certain ships types
  			case AI_GOAL_DISARM_SHIP:
  			case AI_GOAL_DISABLE_SHIP:
  				if ( purge_ai_mode & (AI_GOAL_CHASE | AI_GOAL_CHASE_WING | AI_GOAL_CHASE_SHIP_CLASS) ) {
  					int ai_ship_type;
  
  					// for wings we grab the ship type of the wing leader
  					if (ai_wingnum >= 0) {
  						ai_ship_type = Ship_info[Ships[Wings[ai_wingnum].special_ship].ship_info_index].class_type;
  					}
  					// otherwise we simply grab it from the ship itself
  					else {
  						Assert(aip != NULL);
  						ai_ship_type = Ship_info[Ships[aip->shipnum].ship_info_index].class_type;
  					}
  
  					// grab the ship type of the ship that is being disarmed/disabled
  					ship_type_info *crippled_ships_type = &Ship_types[Ship_info[Ships[ship_index].ship_info_index].class_type];
  
  					// work through all the ship types which to see if the class matching our ai ship must ignore the ship 
  					// being disarmed/disabled
  					for ( j=0 ; j < (int)crippled_ships_type->ai_cripple_ignores.size(); j++) {
  						if (crippled_ships_type->ai_cripple_ignores[j] == ai_ship_type) {
  							purge_goal->flags.set(AI::Goal_Flags::Purge);
  						}
  					}
  				}	
  				break;
  		}
  	}
  }
  
  // function to purge the goals of *all* ships in the game based on the incoming goal structure
  void ai_goal_purge_all_invalid_goals(ai_goal *aigp)
  {
  	int i;
  	ship_obj *sop;
  
  	// only purge goals if a new goal is one of the types in next statement
  	if (!(aigp->ai_mode & PURGE_GOALS_ALL_SHIPS))
  		return;
  	
  	for (sop = GET_FIRST(&Ship_obj_list); sop != END_OF_LIST(&Ship_obj_list); sop = GET_NEXT(sop))
  	{
  		ship *shipp = &Ships[Objects[sop->objnum].instance];
  		ai_goal_purge_invalid_goals(aigp, Ai_info[shipp->ai_index].goals, &Ai_info[shipp->ai_index], -1);
  	}
  
  	// we must do the same for the wing goals
  	for (i = 0; i < Num_wings; i++)
  	{
  		ai_goal_purge_invalid_goals(aigp, Wings[i].ai_goals, NULL, i);
  	}
  }
  
  // Goober5000
  int ai_goal_find_dockpoint(int shipnum, int dock_type)
  {
  	int dock_index = -1;
  	int loop_count = 0;
  
  	ship *shipp = &Ships[shipnum];
  	object *objp = &Objects[shipp->objnum];
  
  	// only check 100 points for sanity's sake
  	while (loop_count < 100)
  	{
  		dock_index = model_find_dock_index(Ship_info[shipp->ship_info_index].model_num, dock_type, dock_index+1);
  
  		// not found?
  		if (dock_index == -1)
  		{
  			if (loop_count == 0)
  			{
  				// first time around... there are no slots fitting this description
  				return -1;
  			}
  			else
  			{
  				// every slot is full
  				break;
  			}
  		}
  
  		// we've found something... check if it's occupied
  		if (dock_find_object_at_dockpoint(objp, dock_index) == NULL)
  		{
  			// not occupied... yay, we've found an index
  			return dock_index;
  		}
  
  		// keep track
  		loop_count++;
  	}
  
  	// insanity?
  	if (loop_count >= 100)
  		Warning(LOCATION, "Too many iterations while looking for a dockpoint on %s.\n", shipp->ship_name);
  
  	// if we're here, just return the first dockpoint
  	return model_find_dock_index(Ship_info[shipp->ship_info_index].model_num, dock_type);
  }
  
  // function to fix up dock point references for objects.
  // passed are the pointer to goal we are working with.  aip is the ai_info pointer
  // of the ship with the order.  aigp is a pointer to the goal (of aip) of which we are
  // fixing up the docking points
  void ai_goal_fixup_dockpoints(ai_info *aip, ai_goal *aigp)
  {
  	int shipnum, docker_index, dockee_index;
  
  	Assert ( aip->shipnum != -1 );
  	shipnum = ship_name_lookup( aigp->target_name );
  	Assertion ( shipnum != -1, "Couldn't find ai goal's target_name (%s); get a coder!\n", aigp->target_name );
  	docker_index = -1;
  	dockee_index = -1;
  
  	//WMC - This gets a bit complex with shiptypes.tbl
  	//Basically this finds the common dockpoint.
  	//For this, the common flags for aip's active point (ie point it wants to dock with)
  	//and aigp's passive point (point it wants to be docked with) are combined
  	//and the common ones are used, in order of precedence.
  	//Yes, it does sound vaguely like a double-entree.
  
  	int aip_type_dock = Ship_info[Ships[aip->shipnum].ship_info_index].class_type;
  	int aigp_type_dock = Ship_info[Ships[shipnum].ship_info_index].class_type;
  
  	int common_docks = 0;
  
  	if(aip_type_dock > -1) {
  		aip_type_dock = Ship_types[aip_type_dock].ai_active_dock;
  	} else {
  		aip_type_dock = 0;
  	}
  
  	if(aigp_type_dock > -1) {
  		aigp_type_dock = Ship_types[aigp_type_dock].ai_passive_dock;
  	} else {
  		aigp_type_dock = 0;
  	}
  
  	common_docks = aip_type_dock & aigp_type_dock;
  
  	//Now iterate through types.
  	for(int i = 0; i < Num_dock_type_names; i++)
  	{
  		if(common_docks & Dock_type_names[i].def) {
  			docker_index = ai_goal_find_dockpoint(aip->shipnum, Dock_type_names[i].def);
  			dockee_index = ai_goal_find_dockpoint(shipnum, Dock_type_names[i].def);
  			break;
  		}
  	}
  
  	// look for docking points of the appriopriate type.  Use cargo docks for cargo ships.
  	/*
  	if (Ship_info[Ships[shipnum].ship_info_index].flags[Ship::Info_Flags::Cargo]) {
  		docker_index = ai_goal_find_dockpoint(aip->shipnum, DOCK_TYPE_CARGO);
  		dockee_index = ai_goal_find_dockpoint(shipnum, DOCK_TYPE_CARGO);
  	} else if (Ship_info[Ships[aip->shipnum].ship_info_index].flags[Ship::Info_Flags::Support]) {
  		docker_index = ai_goal_find_dockpoint(aip->shipnum, DOCK_TYPE_REARM);
  		dockee_index = ai_goal_find_dockpoint(shipnum, DOCK_TYPE_REARM);
  	}
  	*/
  
  	// if we didn't find dockpoints above, then we should just look for generic docking points
  	if ( docker_index == -1 )
  		docker_index = ai_goal_find_dockpoint(aip->shipnum, DOCK_TYPE_GENERIC);
  	if ( dockee_index == -1 )
  		dockee_index = ai_goal_find_dockpoint(shipnum, DOCK_TYPE_GENERIC);
  		
  	aigp->docker.index = docker_index;
  	aigp->dockee.index = dockee_index;
  	aigp->flags.set(AI::Goal_Flags::Dockee_index_valid);
  	aigp->flags.set(AI::Goal_Flags::Docker_index_valid);
  }
  
  // these functions deal with adding goals sent from the player.  They are slightly different
  // from the mission goals (i.e. those goals which come from events) in that we don't
  // use sexpressions for goals from the player...so we enumerate all the parameters
  
  void ai_add_goal_sub_player(int type, int mode, int submode, char *target_name, ai_goal *aigp, const object_ship_wing_point_team& lua_target )
  {
  	Assert ( (type == AIG_TYPE_PLAYER_WING) || (type == AIG_TYPE_PLAYER_SHIP) );
  
  	aigp->time = Missiontime;
  	aigp->type = type;										// from player for sure -- could be to ship or to wing
  	aigp->ai_mode = mode;									// major mode for this goal
  	aigp->ai_submode = submode;								// could mean different things depending on mode
  	aigp->lua_ai_target = lua_target;
  
  	if ( mode == AI_GOAL_WARP ) {
  		if (submode >= 0) {
  			aigp->wp_list = find_waypoint_list_at_index(submode);
  			Assert(aigp->wp_list != NULL);
  		} else {
  			aigp->wp_list = NULL;
  		}
  	}
  
  	if ( mode == AI_GOAL_CHASE_WEAPON ) {
  		aigp->target_instance = submode;				// submode contains the instance of the weapon
  		aigp->target_signature = Objects[Weapons[submode].objnum].signature;
  	} else {
  		aigp->target_instance = -1;
  		aigp->target_signature = -1;
  	}
  
  	if ( target_name != NULL )
  		aigp->target_name = ai_get_goal_target_name( target_name, &aigp->target_name_index );
  	else
  		aigp->target_name = NULL;
  
  	// special case certain orders from player so that ships continue to do the right thing
  
  	// make priority for these two support ship orders low so that they will prefer repairing
  	// a ship over staying near a ship.
  	if ( (mode == AI_GOAL_STAY_NEAR_SHIP) || (mode == AI_GOAL_KEEP_SAFE_DISTANCE) )
  		aigp->priority = PLAYER_PRIORITY_SUPPORT_LOW;
  
  	// Goober5000 - same with form-on-wing, since it's a type of staying near
  	else if ( mode == AI_GOAL_FORM_ON_WING )
  		aigp->priority = PLAYER_PRIORITY_SUPPORT_LOW;
  
  	else if ( aigp->type == AIG_TYPE_PLAYER_WING )
  		aigp->priority = PLAYER_PRIORITY_WING;			// player wing goals not as high as ship goals
  	else
  		aigp->priority = PLAYER_PRIORITY_SHIP;
  }
  
  // Goober5000 - Modified this function for clarity and to avoid returning the active goal's index
  // as the empty slot.  This avoids overwriting the active goal while it's being executed.  So far
  // the only time I've noticed it being a problem is during a rare situation where more than five
  // friendlies want to rearm at the same time.  The support ship forgets what it's doing and flies
  // off to repair somebody while still docked.  I reproduced this with retail, so it's not a bug in
  // my new docking code. :)
  int ai_goal_find_empty_slot( ai_goal *goals, int active_goal )
  {
  	int gindex, oldest_index;
  
  	oldest_index = -1;
  	for ( gindex = 0; gindex < MAX_AI_GOALS; gindex++ )
  	{
  		// get the index for the first unused goal
  		if (goals[gindex].ai_mode == AI_GOAL_NONE)
  			return gindex;
  
  		// if this is the active goal, don't consider it for pre-emption!!
  		if (gindex == active_goal)
  			continue;
  
  		// store the index of the oldest goal
  		if (oldest_index < 0)
  			oldest_index = gindex;
  		else if (goals[gindex].time < goals[oldest_index].time)
  			oldest_index = gindex;
  	}
  
  	// if we didn't find an empty slot, use the oldest goal's slot
  	return oldest_index;
  }
  
  int ai_goal_num(ai_goal *goals)
  {
  	int gindex = 0;
  	int num_goals = 0;
  	for(gindex = 0; gindex < MAX_AI_GOALS; gindex++)
  	{
  		if(goals[gindex].ai_mode != AI_GOAL_NONE)
  			num_goals++;
  	}
  
  	return num_goals;
  }
  
  
  void ai_add_goal_sub_scripting(int type, int mode, int submode, int priority, char *target_name, ai_goal *aigp )
  {
  	Assert ( (type == AIG_TYPE_PLAYER_WING) || (type == AIG_TYPE_PLAYER_SHIP) );
  
  	aigp->time = Missiontime;
  	aigp->type = type;											// from player for sure -- could be to ship or to wing
  	aigp->ai_mode = mode;										// major mode for this goal
  	aigp->ai_submode = submode;								// could mean different things depending on mode
  
  	if ( mode == AI_GOAL_WARP )
  		aigp->wp_list = NULL;
  
  	if ( mode == AI_GOAL_CHASE_WEAPON ) {
  		aigp->target_instance = submode;								// submode contains the instance of the weapon
  		aigp->target_signature = Objects[Weapons[submode].objnum].signature;
  	}
  
  	if ( mode == AI_GOAL_WAYPOINTS_ONCE || mode == AI_GOAL_WAYPOINTS ) {
  		// should be the same as setting via sexp, see ai_add_goal_sub_sexp
  		// set to null so the AI code can setup the new waypoint list, see ai_mission_goal_achievable
  		aigp->wp_list = nullptr;
  	}
  
  	if ( target_name != NULL )
  		aigp->target_name = ai_get_goal_target_name( target_name, &aigp->target_name_index );
  	else
  		aigp->target_name = NULL;
  
  	aigp->priority = priority;
  }
  
  void ai_add_ship_goal_scripting(int mode, int submode, int priority, char *shipname, ai_info *aip)
  {
  	int empty_index;
  	ai_goal *aigp;
  
  	empty_index = ai_goal_find_empty_slot(aip->goals, aip->active_goal);
  	aigp = &aip->goals[empty_index];
  
  	//WMC - hack to get docking setup correctly
  	if ( mode == AI_GOAL_DOCK ) {
  		aigp->docker.name = Ships[aip->shipnum].ship_name;
  		aigp->dockee.name = shipname;
  		aigp->flags.remove(AI::Goal_Flags::Dockee_index_valid);
  		aigp->flags.remove(AI::Goal_Flags::Docker_index_valid);
  	}
  	ai_add_goal_sub_scripting(AIG_TYPE_PLAYER_SHIP, mode, submode, priority, shipname, aigp);
  
  	if ( (mode == AI_GOAL_REARM_REPAIR) || ((mode == AI_GOAL_DOCK) && (submode == AIS_DOCK_0)) ) {
  		ai_goal_fixup_dockpoints( aip, aigp );
  	}
  
  	aigp->signature = Ai_goal_signature++;
  }
  
  // adds a goal from a player to the given ship's ai_info structure.  'type' tells us if goal
  // is issued to ship or wing (from player),  mode is AI_GOAL_*. submode is the submode the
  // ship should go into.  shipname is the object of the action.  aip is the ai_info pointer
  // of the ship receiving the order
  void ai_add_ship_goal_player( int type, int mode, int submode, char *shipname, ai_info *aip, const object_ship_wing_point_team& lua_target)
  {
  	int empty_index;
  	ai_goal *aigp;
  
  	empty_index = ai_goal_find_empty_slot( aip->goals, aip->active_goal );
  
  	// get a pointer to the goal structure
  	aigp = &aip->goals[empty_index];
  	ai_add_goal_sub_player( type, mode, submode, shipname, aigp, lua_target );
  
  	// if the goal is to dock, then we must determine which dock points on the two ships to use.
  	// If the target of the dock is a cargo type container, then we should use DOCK_TYPE_CARGO
  	// on both ships.  Code is here instead of in ai_add_goal_sub_player() since a dock goal
  	// should only occur to a specific ship.
  
  	if ( (mode == AI_GOAL_REARM_REPAIR) || ((mode == AI_GOAL_DOCK) && (submode == AIS_DOCK_0)) ) {
  		ai_goal_fixup_dockpoints( aip, aigp );
  	}
  
  	aigp->signature = Ai_goal_signature++;
  
  }
  
  // adds a goal from the player to the given wing (which in turn will add it to the proper
  // ships in the wing
  void ai_add_wing_goal_player( int type, int mode, int submode, char *shipname, int wingnum, const object_ship_wing_point_team& lua_target)
  {
  	int i, empty_index;
  	wing *wingp = &Wings[wingnum];
  
  	// add the ai goal for any ship that is currently arrived in the game.
  	if ( !Fred_running ) {										// only add goals to ships if fred isn't running
  		for (i = 0; i < wingp->current_count; i++) {
  			int num = wingp->ship_index[i];
  			if ( num == -1 )			// ship must have been destroyed or departed
  				continue;
  			ai_add_ship_goal_player( type, mode, submode, shipname, &Ai_info[Ships[num].ai_index], lua_target );
  		}
  	}
  
  	// add the sexpression index into the wing's list of goal sexpressions if
  	// there are more waves to come.  We use the same method here as when adding a goal to
  	// a ship -- find the first empty entry.  If none exists, take the oldest entry and replace it.
  	empty_index = ai_goal_find_empty_slot( wingp->ai_goals, -1 );
  	ai_add_goal_sub_player( type, mode, submode, shipname, &wingp->ai_goals[empty_index], lua_target );
  }
  
  
  // common routine to add a sexpression mission goal to the appropriate goal structure.
  void ai_add_goal_sub_sexp( int sexp, int type, ai_goal *aigp, char *actor_name )
  {
  	int node, dummy, op;
  
  	Assert ( Sexp_nodes[sexp].first != -1 );
  	node = Sexp_nodes[sexp].first;
  	op = get_operator_const( node );
  
  	aigp->signature = Ai_goal_signature++;
  
  	aigp->target_name = NULL;
  	aigp->time = Missiontime;
  	aigp->type = type;
  	aigp->flags.reset();
  
  	switch (op) {
  
  	case OP_AI_WAYPOINTS_ONCE:
  	case OP_AI_WAYPOINTS: {
  		int ref_type;
  
  		ref_type = Sexp_nodes[CDR(node)].subtype;
  		if (ref_type == SEXP_ATOM_STRING) {  // referenced by name
  			// save the waypoint path name -- the list will get resolved when the goal is checked
  			// for achievability.
  			aigp->target_name = ai_get_goal_target_name(CTEXT(CDR(node)), &aigp->target_name_index);  // waypoint path name;
  			aigp->wp_list = NULL;
  
  		} else
  			Int3();
  
  		aigp->priority = atoi( CTEXT(CDR(CDR(node))) );
  		aigp->ai_mode = AI_GOAL_WAYPOINTS;
  		if ( op == OP_AI_WAYPOINTS_ONCE )
  			aigp->ai_mode = AI_GOAL_WAYPOINTS_ONCE;
  		break;
  	}
  
  	case OP_AI_DESTROY_SUBSYS:
  		aigp->ai_mode = AI_GOAL_DESTROY_SUBSYSTEM;
  		aigp->target_name = ai_get_goal_target_name( CTEXT(CDR(node)), &aigp->target_name_index );
  		// store the name of the subsystem in the docker.name field for now -- this field must get
  		// fixed up when the goal is valid since we need to locate the subsystem on the ship's model
  		aigp->docker.name = ai_get_goal_target_name(CTEXT(CDR(CDR(node))), &dummy);
  		aigp->flags.set(AI::Goal_Flags::Subsys_needs_fixup);
  		aigp->priority = atoi( CTEXT(CDR(CDR(CDR(node)))) );
  		break;
  
  	case OP_AI_DISABLE_SHIP:
  		aigp->ai_mode = AI_GOAL_DISABLE_SHIP;
  		aigp->target_name = ai_get_goal_target_name( CTEXT(CDR(node)), &aigp->target_name_index );
  		aigp->ai_submode = -SUBSYSTEM_ENGINE;
  		aigp->priority = atoi( CTEXT(CDR(CDR(node))) );
  		break;
  
  	case OP_AI_DISARM_SHIP:
  		aigp->ai_mode = AI_GOAL_DISARM_SHIP;
  		aigp->target_name = ai_get_goal_target_name( CTEXT(CDR(node)), &aigp->target_name_index );
  		aigp->ai_submode = -SUBSYSTEM_TURRET;
  		aigp->priority = atoi( CTEXT(CDR(CDR(node))) );
  		break;
  
  	case OP_AI_WARP_OUT:
  		aigp->ai_mode = AI_GOAL_WARP;
  		aigp->priority = atoi( CTEXT(CDR(node)) );
  		break;
  
  		// the following goal is obsolete, but here for compatibility
  	case OP_AI_WARP:
  		aigp->ai_mode = AI_GOAL_WARP;
  		aigp->target_name = ai_get_goal_target_name(CTEXT(CDR(node)), &aigp->target_name_index);  // waypoint path name;
  		aigp->wp_list = NULL;
  		aigp->priority = atoi( CTEXT(CDR(CDR(node))) );
  		break;
  
  	case OP_AI_UNDOCK:
  		aigp->priority = atoi( CTEXT(CDR(node)) );
  
  		// Goober5000 - optional undock with something
  		if (CDR(CDR(node)) != -1)
  			aigp->target_name = ai_get_goal_target_name( CTEXT(CDR(CDR(node))), &aigp->target_name_index );
  		else
  			aigp->target_name = NULL;
  
  		aigp->ai_mode = AI_GOAL_UNDOCK;
  		aigp->ai_submode = AIS_UNDOCK_0;
  		break;
  
  	case OP_AI_STAY_STILL:
  		aigp->ai_mode = AI_GOAL_STAY_STILL;
  		aigp->target_name = ai_get_goal_target_name(CTEXT(CDR(node)), &aigp->target_name_index);  // waypoint path name;
  		aigp->priority = atoi( CTEXT(CDR(CDR(node))) );
  		break;
  
  	case OP_AI_DOCK:
  		aigp->target_name = ai_get_goal_target_name( CTEXT(CDR(node)), &aigp->target_name_index );
  		aigp->docker.name = ai_add_dock_name(CTEXT(CDR(CDR(node))));
  		aigp->dockee.name = ai_add_dock_name(CTEXT(CDR(CDR(CDR(node)))));
  		aigp->flags.remove(AI::Goal_Flags::Dockee_index_valid);
  		aigp->flags.remove(AI::Goal_Flags::Docker_index_valid);
  		aigp->priority = atoi( CTEXT(CDR(CDR(CDR(CDR(node))))) );
  
  		aigp->ai_mode = AI_GOAL_DOCK;
  		aigp->ai_submode = AIS_DOCK_0;		// be sure to set the submode
  		break;
  
  	case OP_AI_CHASE_ANY:
  		aigp->priority = atoi( CTEXT(CDR(node)) );
  		aigp->ai_mode = AI_GOAL_CHASE_ANY;
  		break;
  
  	case OP_AI_PLAY_DEAD:
  		aigp->priority = atoi( CTEXT(CDR(node)) );
  		aigp->ai_mode = AI_GOAL_PLAY_DEAD;
  		break;
  
  	case OP_AI_PLAY_DEAD_PERSISTENT:
  		aigp->priority = atoi( CTEXT(CDR(node)) );
  		aigp->ai_mode = AI_GOAL_PLAY_DEAD_PERSISTENT;
  		break;
  
  	case OP_AI_KEEP_SAFE_DISTANCE:
  		aigp->priority = atoi( CTEXT(CDR(node)) );
  		aigp->ai_mode = AI_GOAL_KEEP_SAFE_DISTANCE;
  		break;
  
  	case OP_AI_FORM_ON_WING:
  		aigp->priority = 99;
  		aigp->target_name = ai_get_goal_target_name(CTEXT(CDR(node)), &aigp->target_name_index);
  		aigp->ai_mode = AI_GOAL_FORM_ON_WING;
  		break;
  
  	case OP_AI_CHASE:
  	case OP_AI_CHASE_WING:
  	case OP_AI_CHASE_SHIP_CLASS:
  	case OP_AI_GUARD:
  	case OP_AI_GUARD_WING:
  	case OP_AI_EVADE_SHIP:
  	case OP_AI_STAY_NEAR_SHIP:
  	case OP_AI_IGNORE:
  	case OP_AI_IGNORE_NEW:
  		aigp->target_name = ai_get_goal_target_name( CTEXT(CDR(node)), &aigp->target_name_index );
  		aigp->priority = atoi( CTEXT(CDR(CDR(node))) );
  
  		if ( op == OP_AI_CHASE ) {
  			aigp->ai_mode = AI_GOAL_CHASE;
  
  			// in the case of ai_chase (and ai_guard) we must do a wing_name_lookup on the name
  			// passed here to see if we could be chasing a wing.  Hoffoss and I have consolidated
  			// sexpression operators which makes this step necessary
  			if ( wing_name_lookup(aigp->target_name, 1) != -1 )
  				aigp->ai_mode = AI_GOAL_CHASE_WING;
  
  		} else if ( op == OP_AI_GUARD ) {
  			aigp->ai_mode = AI_GOAL_GUARD;
  			if ( wing_name_lookup(aigp->target_name, 1) != -1 )
  				aigp->ai_mode = AI_GOAL_GUARD_WING;
  
  		} else if ( op == OP_AI_EVADE_SHIP ) {
  			aigp->ai_mode = AI_GOAL_EVADE_SHIP;
  
  		} else if ( op == OP_AI_GUARD_WING ) {
  			aigp->ai_mode = AI_GOAL_GUARD_WING;
  		} else if ( op == OP_AI_CHASE_WING ) {
  			aigp->ai_mode = AI_GOAL_CHASE_WING;
  		} else if (op == OP_AI_CHASE_SHIP_CLASS) {
  			aigp->ai_mode = AI_GOAL_CHASE_SHIP_CLASS;
  		} else if ( op == OP_AI_STAY_NEAR_SHIP ) {
  			aigp->ai_mode = AI_GOAL_STAY_NEAR_SHIP;
  		} else if ( op == OP_AI_IGNORE ) {
  			aigp->ai_mode = AI_GOAL_IGNORE;
  		} else if ( op == OP_AI_IGNORE_NEW ) {
  			aigp->ai_mode = AI_GOAL_IGNORE_NEW;
  		} else
  			Int3();		// this is impossible
  
  		break;
  
  	default:
  		const ai_mode_lua* luaAIMode = ai_lua_find_mode(op);
  		if(luaAIMode != nullptr){
  			//Found a LuaAI mode sexp for this
  			int localnode = CDR(node);
  			
  			aigp->ai_mode = AI_GOAL_LUA;
  			aigp->ai_submode = op;
  			
  			if(luaAIMode->needsTarget) {
  				eval_object_ship_wing_point_team(&aigp->lua_ai_target, localnode);
  				localnode = CDR(localnode);
  			}
  
  			aigp->priority = atoi( CTEXT(localnode) );
  		}
  		else {
  			UNREACHABLE("Invalid SEXP-OP number %d for an AI goal!", op);
  		}
  	}
  
  	if ( aigp->priority > MAX_GOAL_PRIORITY ) {
  		nprintf (("AI", "bashing sexpression priority of goal %s from %d to %d.\n", CTEXT(node), aigp->priority, MAX_GOAL_PRIORITY));
  		aigp->priority = MAX_GOAL_PRIORITY;
  	}
  
  	// Goober5000 - we now have an extra optional chase argument to allow chasing our own team
  	if ( op == OP_AI_CHASE || op == OP_AI_CHASE_WING || op == OP_AI_CHASE_SHIP_CLASS || op == OP_AI_DISABLE_SHIP || op == OP_AI_DISARM_SHIP ) {
  		if ((CDDDR(node) != -1) && is_sexp_true(CDDDR(node)))
  			aigp->flags.set(AI::Goal_Flags::Target_own_team);
  	}
  	if ( op == OP_AI_DESTROY_SUBSYS ) {
  		if ((CDDDDR(node) != -1) && is_sexp_true(CDDDDR(node)))
  			aigp->flags.set(AI::Goal_Flags::Target_own_team);
  	}
  
  	// Goober5000 - since none of the goals act on the actor,
  	// don't assign the goal if the actor's goal target is itself
  	if (aigp->target_name != NULL && !strcmp(aigp->target_name, actor_name))
  	{
  		// based on ai_remove_ship_goal, these seem to be the only statements
  		// necessary to cause this goal to "never have been assigned"
  		aigp->ai_mode = AI_GOAL_NONE;
  		aigp->signature = -1;
  		aigp->priority = -1;
  		aigp->flags.reset();
  	}
  }
  
  /* Find the index of the goal in the passed ai_goal array
   * Call something like ai_find_goal_index( aiip->goals, AIM_* );
   * Pass -1 in submode to ignore ai_submode when searching
   * Pass -1 in priority to ignore priority when searching
   * Returns -1 if not found, or [0, MAX_AI_GOALS)
   */
  int ai_find_goal_index( ai_goal* aigp, int mode, int submode, int priority )
  {
  	Assert( aigp != NULL );
  	for ( int i = 0; i < MAX_AI_GOALS; i++ )
  	{
  		if ( aigp[ i ].ai_mode == mode &&
  			 ( submode == -1 || aigp[ i ].ai_submode == submode ) &&
  			 ( priority == -1 || aigp[ i ].priority == priority ) )
  		{
  			return i;
  		}
  	}
  
  	return -1;
  }
  
  /* Remove a goal from the given goals structure
   * Returns the index of the goal that it clears out.
   * This is important so that if active_goal == index you can set AI_GOAL_NONE
   */
  int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp )
  {
  	/* Sanity check */
  	Assert( Sexp_nodes[ sexp ].first != -1 );
  
  	/* The bits we're searching for in the goals list */
  	int priority = -1;
  
  	int goalmode = -1;
  	int goalsubmode = -1;
  
  	/* Sexp node */
  	int node = Sexp_nodes[sexp].first;
  	/* The operator to use */
  	int op = get_operator_const( node );
  
  	/* We now need to determine what the mode and submode values are*/
  	switch( op )
  	{
  	case OP_AI_WAYPOINTS_ONCE:
  		goalmode = AI_GOAL_WAYPOINTS_ONCE;
  		priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1;
  		break;
  	case OP_AI_WAYPOINTS:
  		goalmode = AI_GOAL_WAYPOINTS;
  		priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1;
  		break;
  	case OP_AI_DESTROY_SUBSYS:
  		goalmode = AI_GOAL_DESTROY_SUBSYSTEM;
  		priority = ( CDR( CDR( CDR(node) ) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( CDR( node ) ) ) ) ) : -1;
  		break;
  	case OP_AI_DISABLE_SHIP:
  		goalmode = AI_GOAL_DISABLE_SHIP;
  		priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1;
  		break;
  	case OP_AI_DISARM_SHIP:
  		goalmode = AI_GOAL_DISABLE_SHIP;
  		priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1;
  		break;
  	case OP_AI_WARP_OUT:
  		goalmode = AI_GOAL_WARP;
  		priority = ( CDR(node) >= 0 ) ? atoi( CTEXT( CDR( node ) ) ) : -1;
  		break;
  	case OP_AI_WARP:
  		goalmode = AI_GOAL_WARP;
  		priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1;
  		break;
  	case OP_AI_UNDOCK:
  		goalmode = AI_GOAL_UNDOCK;
  		goalsubmode = AIS_UNDOCK_0;
  		priority = ( CDR(node) >= 0 ) ? atoi( CTEXT( CDR( node ) ) ) : -1;
  		break;
  	case OP_AI_STAY_STILL:
  		goalmode = AI_GOAL_STAY_STILL;
  		priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1;
  		break;
  	case OP_AI_DOCK:
  		goalmode = AI_GOAL_DOCK;
  		goalsubmode = AIS_DOCK_0;
  		priority = ( CDR( CDR( CDR( CDR(node) ) ) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( CDR( CDR( node ) ) ) ) ) ) : -1;
  		break;
  	case OP_AI_CHASE_ANY:
  		goalmode = AI_GOAL_CHASE_ANY;
  		priority = ( CDR(node) >= 0 ) ? atoi( CTEXT( CDR( node ) ) ) : -1;
  		break;
  	case OP_AI_PLAY_DEAD:
  		goalmode = AI_GOAL_PLAY_DEAD;
  		priority = ( CDR(node) >= 0 ) ? atoi( CTEXT( CDR( node ) ) ) : -1;
  		break;
  	case OP_AI_PLAY_DEAD_PERSISTENT:
  		goalmode = AI_GOAL_PLAY_DEAD_PERSISTENT;
  		priority = ( CDR(node) >= 0 ) ? atoi( CTEXT( CDR( node ) ) ) : -1;
  		break;
  	case OP_AI_KEEP_SAFE_DISTANCE:
  		priority = ( CDR(node) >= 0 ) ? atoi( CTEXT( CDR( node ) ) ) : -1;
  		goalmode = AI_GOAL_KEEP_SAFE_DISTANCE;
  		break;
  	case OP_AI_CHASE:
  		priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1;
  		if ( wing_name_lookup( CTEXT( CDR( node ) ), 1 ) != -1 )
  			goalmode = AI_GOAL_CHASE_WING;
  		else
  			goalmode = AI_GOAL_CHASE;
  		break;
  	case OP_AI_GUARD:
  		priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1;
  		if ( wing_name_lookup( CTEXT( CDR( node ) ), 1 ) != -1 )
  			goalmode = AI_GOAL_GUARD_WING;
  		else
  			goalmode = AI_GOAL_GUARD;
  		break;
  	case OP_AI_GUARD_WING:
  		priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1;
  		goalmode = AI_GOAL_GUARD_WING;
  		break;
  	case OP_AI_CHASE_WING:
  		priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1;
  		goalmode = AI_GOAL_CHASE_WING;
  		break;
  	case OP_AI_CHASE_SHIP_CLASS:
  		priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1;
  		goalmode = AI_GOAL_CHASE_SHIP_CLASS;
  		break;
  	case OP_AI_EVADE_SHIP:
  		priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1;
  		goalmode = AI_GOAL_EVADE_SHIP;
  		break;
  	case OP_AI_STAY_NEAR_SHIP:
  		priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1;
  		goalmode = AI_GOAL_STAY_NEAR_SHIP;
  		break;
  	case OP_AI_IGNORE:
  		priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1;
  		goalmode = AI_GOAL_IGNORE;
  		break;
  	case OP_AI_IGNORE_NEW:
  		priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1;
  		goalmode = AI_GOAL_IGNORE_NEW;
  		break;
  	default:
  		const ai_mode_lua* luaAIMode = ai_lua_find_mode(op);
  		if(luaAIMode != nullptr){
  			//Found a LuaAI mode sexp for this
  			int localnode = CDR(node);
  
  			goalmode = AI_GOAL_LUA;
  			goalsubmode = op;
  
  			if(luaAIMode->needsTarget) {
  				localnode = CDR(localnode);
  			}
  
  			priority = localnode >= 0 ? atoi( CTEXT(localnode) ) : -1;
  		}
  		else {
  			UNREACHABLE("Invalid SEXP-OP number %d for an AI goal!", op);
  		}
  		break;
  	};
  	
  	/* Attempt to find the goal */
  	int goalindex = ai_find_goal_index( aigp, goalmode, goalsubmode, priority );
  
  	if ( goalindex == -1 )
  		return -1; /* no more to do; */
  
  	/* Clear out the contents of the goal. We can't use ai_remove_ship_goal since it needs ai_info and
  	 * we've only got ai_goals */
  	aigp[goalindex].ai_mode = AI_GOAL_NONE;
  	aigp[goalindex].signature = -1;
  	aigp[goalindex].priority = -1;
  	aigp[goalindex].flags.reset();				// must reset the flags since not doing so will screw up goal sorting.
  
  	return goalindex;
  }
  
  // code to remove ai goals from wings.
  void ai_remove_wing_goal_sexp(int sexp, wing *wingp)
  {
  	int i;
  	int goalindex = -1;
  
  	// remove the ai goal for any ship that is currently arrived in the game (only if fred isn't running)
  	if ( !Fred_running ) {
  		for (i = 0; i < wingp->current_count; i++) {
  			int num = wingp->ship_index[i];
  			if ( num == -1 )			// ship must have been destroyed or departed
  				continue;
  			goalindex = ai_remove_goal_sexp_sub( sexp, Ai_info[Ships[num].ai_index].goals );
  			if ( Ai_info[Ships[num].ai_index].active_goal == goalindex )
  				Ai_info[Ships[num].ai_index].active_goal = AI_GOAL_NONE;
  		}
  	}
  
  	// remove the sexpression index from the wing's list of goal sexpressions if
  	// there are more waves to come
  	if ((wingp->num_waves - wingp->current_wave > 0) || Fred_running) 
  	{
  		ai_remove_goal_sexp_sub( sexp, wingp->ai_goals );
  	}
  }
  
  // adds an ai goal for an individual ship
  // type determines who has issues this ship a goal (i.e. the player/mission event/etc)
  void ai_add_ship_goal_sexp( int sexp, int type, ai_info *aip )
  {
  	int gindex;
  
  	gindex = ai_goal_find_empty_slot( aip->goals, aip->active_goal );
  	ai_add_goal_sub_sexp( sexp, type, &aip->goals[gindex], Ships[aip->shipnum].ship_name );
  }
  
  // code to add ai goals to wings.
  void ai_add_wing_goal_sexp(int sexp, int type, wing *wingp)
  {
  	int i;
  
  	// add the ai goal for any ship that is currently arrived in the game (only if fred isn't running)
  	if ( !Fred_running ) {
  		for (i = 0; i < wingp->current_count; i++) {
  			int num = wingp->ship_index[i];
  			if ( num == -1 )			// ship must have been destroyed or departed
  				continue;
  			ai_add_ship_goal_sexp( sexp, type, &Ai_info[Ships[num].ai_index] );
  		}
  	}
  
  	// add the sexpression index into the wing's list of goal sexpressions if
  	// there are more waves to come
  	if ((wingp->num_waves - wingp->current_wave > 0) || Fred_running) {
  		int gindex;
  
  		gindex = ai_goal_find_empty_slot( wingp->ai_goals, -1 );
  		ai_add_goal_sub_sexp( sexp, type, &wingp->ai_goals[gindex], wingp->name );
  	}
  }
  
  // function for internal code to add a goal to a ship.  Needed when the AI finds itself in a situation
  // that it must get out of by issuing itself an order.
  //
  // objp is the object getting the goal
  // goal_type is one of AI_GOAL_*
  // other_name is a character string objp might act on (for docking, this is a shipname, for guarding
  // this name can be a shipname or a wingname)
  // docker_point and dockee_point are used for the AI_GOAL_DOCK command to tell two ships where to dock
  // immediate means to process this order right away
  void ai_add_goal_ship_internal( ai_info *aip, int goal_type, char *name, int  /*docker_point*/, int  /*dockee_point*/, int immediate )
  {
  	int gindex;
  	ai_goal *aigp;
  
  #ifndef NDEBUG
  	// Goober5000 - none of the goals act on the actor, as in ai_add_goal_sub_sexp
  	if (!strcmp(name, Ships[aip->shipnum].ship_name))
  	{
  		// not good
  		Int3();
  		return;
  	}
  #endif
  
  	// find an empty slot to put this goal in.
  	gindex = ai_goal_find_empty_slot( aip->goals, aip->active_goal );
  	aigp = &(aip->goals[gindex]);
  
  	aigp->signature = Ai_goal_signature++;
  
  	aigp->time = Missiontime;
  	aigp->type = AIG_TYPE_DYNAMIC;
  	aigp->flags.reset();
  	aigp->flags.set(AI::Goal_Flags::Goal_override);
  
  	switch ( goal_type ) {
  
  /* Goober5000 - this seems to not be used
  	case AI_GOAL_DOCK:
  		aigp->ship_name = name;
  		aigp->docker.index = docker_point;
  		aigp->dockee.index = dockee_point;
  		aigp->priority = 100;
  
  		aigp->ai_mode = AI_GOAL_DOCK;
  		aigp->ai_submode = AIS_DOCK_0;		// be sure to set the submode
  		break;
  */
  
  	case AI_GOAL_UNDOCK:
  		aigp->target_name = name;
  		aigp->priority = MAX_GOAL_PRIORITY;
  		aigp->ai_mode = AI_GOAL_UNDOCK;
  		aigp->ai_submode = AIS_UNDOCK_0;
  		break;
  
  /* Goober5000 - this seems to not be used
  	case AI_GOAL_GUARD:
  		if ( wing_name_lookup(name, 1) != -1 )
  			aigp->ai_mode = AI_GOAL_GUARD_WING;
  		else
  			aigp->ai_mode = AI_GOAL_GUARD;
  		aigp->priority = PLAYER_PRIORITY_MIN-1;		// make the priority always less than what the player's is
  		break;
  */
  
  	case AI_GOAL_REARM_REPAIR:
  		aigp->ai_mode = AI_GOAL_REARM_REPAIR;
  		aigp->ai_submode = 0;
  		aigp->target_name = name;
  		aigp->priority = PLAYER_PRIORITY_MIN-1;		// make the priority always less than what the player's is
  		aigp->flags.remove(AI::Goal_Flags::Goal_override);	// don't override this goal.  rearm repair requests should happen in order
  		ai_goal_fixup_dockpoints( aip, aigp );
  		break;
  
  	default:
  		Int3();		// unsupported internal goal -- see Mike K or Mark A.
  		return;
  	}
  
  
  	// process the orders immediately so that these goals take effect right away
  	if ( immediate )
  		ai_process_mission_orders( Ships[aip->shipnum].objnum, aip );
  }
  
  // this function copies goals from a wing to an ai_info * from a ship.
  void ai_copy_mission_wing_goal( ai_goal *aigp, ai_info *aip )
  {
  	int j;
  
  	for ( j = 0; j < MAX_AI_GOALS; j++ ) {
  		if ( aip->goals[j].ai_mode == AI_GOAL_NONE ) {
  			aip->goals[j] = *aigp;
  			break;
  		}
  	}
  
  	if (j >= MAX_AI_GOALS) {
  		mprintf(("Unable to assign wing goal to ship %s; the ship goals are already filled to capacity\n", Ships[aip->shipnum].ship_name));
  	}
  }
  
  
  #define SHIP_STATUS_GONE				1
  #define SHIP_STATUS_NOT_ARRIVED		2
  #define SHIP_STATUS_ARRIVED			3
  #define SHIP_STATUS_UNKNOWN			4
  
  // function to determine if an ai goal is achieveable or not.  Will return
  // one of the AI_GOAL_* values.  Also determines is a goal was successful.
  
  ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
  {
  	int status;
  	ai_achievability return_val;
  	object *objp;
  	ai_info *aip;
  	int index = -1, sindex = -1;
  	int modelnum = -1;
  
  	objp = &Objects[objnum];
  	Assert( objp->instance != -1 );
  	aip = &Ai_info[Ships[objp->instance].ai_index];
  
  	//  these orders are always achievable.
  	if ( (aigp->ai_mode == AI_GOAL_KEEP_SAFE_DISTANCE)
  		|| (aigp->ai_mode == AI_GOAL_CHASE_ANY) || (aigp->ai_mode == AI_GOAL_STAY_STILL)
  		|| (aigp->ai_mode == AI_GOAL_PLAY_DEAD) || (aigp->ai_mode == AI_GOAL_PLAY_DEAD_PERSISTENT))
  		return ai_achievability::ACHIEVABLE;
  
  	if (aigp->ai_mode == AI_GOAL_LUA)
  		return ai_lua_is_achievable(aigp, objnum);
  
  	// warp (depart) only achievable if there's somewhere to depart to
  	if (aigp->ai_mode == AI_GOAL_WARP)
  	{
  		ship *shipp = &Ships[objp->instance];
  
  		// always valid if has working subspace drive, not disabled, and not limited by subsystem strength
  		if ( ship_can_warp_full_check(shipp) )
  			return ai_achievability::ACHIEVABLE;
  
  		// if we can't warp, we can only depart if the ship (or its wing) departs to a fighter bay and the mothership is present
  		if (ship_can_bay_depart(shipp)) {
  			return ai_achievability::ACHIEVABLE;
  		}
  		else {
  			return ai_achievability::NOT_KNOWN;
  		}
  	}
  
  
  	// form on wing is always achievable if we are forming on Player, but it's up for grabs otherwise
  	// if the wing target is valid then be sure to set the override bit so that it always
  	// gets executed next
  	if ( aigp->ai_mode == AI_GOAL_FORM_ON_WING ) {
  		sindex = ship_name_lookup( aigp->target_name );
  
  		if (sindex < 0)
  			return ai_achievability::NOT_ACHIEVABLE;
  
  		aigp->flags.set(AI::Goal_Flags::Goal_override);
  		return ai_achievability::ACHIEVABLE;
  	}
  
  	// check to see if we have a valid list.  If not, then try to set one up.  If that
  	// fails, then we must pitch this order
  	if ( (aigp->ai_mode == AI_GOAL_WAYPOINTS_ONCE) || (aigp->ai_mode == AI_GOAL_WAYPOINTS) ) {
  		if ( aigp->wp_list == NULL ) {
  			aigp->wp_list = find_matching_waypoint_list(aigp->target_name);
  
  			if ( aigp->wp_list == NULL ) {
  				Warning(LOCATION, "Unknown waypoint list %s - not found in mission file.  Killing ai goal", aigp->target_name );
  				return ai_achievability::NOT_ACHIEVABLE;
  			}
  		}
  		return ai_achievability::ACHIEVABLE;
  	}
  
  	// chasing all ships of a certain ship class is achievable if there are ships of that class present;
  	// if not, the status is not known because more ships of that class could arrive in the future
  	// (c.f. AI_GOAL_CHASE_WING subsequent to the next switch statement)
  	if ( aigp->ai_mode == AI_GOAL_CHASE_SHIP_CLASS ) {
  		for (objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) {
  			if ((objp->type == OBJ_SHIP) && !strcmp(aigp->target_name, Ship_info[Ships[objp->instance].ship_info_index].name)) {
  				return ai_achievability::ACHIEVABLE;
  			}
  		}
  		return ai_achievability::NOT_KNOWN;
  	}
  
  
  	return_val = ai_achievability::SATISFIED;
  
  	// next, determine if the goal has been completed successfully
  	switch ( aigp->ai_mode )
  	{
  		case AI_GOAL_DOCK:
  		case AI_GOAL_UNDOCK:
  			//MWA 3/20/97 -- cannot short circuit a dock or undock goal already succeeded -- we must
  			// rely on the goal removal code to just remove this goal.  This is because docking/undock
  			// can happen > 1 time per mission per pair of ships.  The above checks will find only
  			// if the ships docked or undocked at all, which is not what we want.
  			status = 0;
  			break;
  
  		case AI_GOAL_DESTROY_SUBSYSTEM:
  		{
  			ship_subsys *ssp;
  
  			// shipnum could be -1 depending on if the ship hasn't arrived or died.  only look for subsystem
  			// destroyed when shipnum is valid
  			sindex = ship_name_lookup( aigp->target_name );
  
  			// can't determine the status of this goal if ship not valid
  			// or we haven't found a valid subsystem index yet
  			if ( (sindex == -1) || (aigp->flags[AI::Goal_Flags::Subsys_needs_fixup]) ) {
  				status = 0;
  				break;
  			}
  
  			// if the ship is not in the mission or the subsystem name is still being stored, mark the status
  			// as 0 so we can continue.  (The subsystem name must be turned into an index into the ship's subsystems
  			// for this goal to be valid).
  			Assert ( aigp->ai_submode >= 0 );
  			ssp = ship_get_indexed_subsys( &Ships[sindex], aigp->ai_submode );
  			if (ssp != NULL) {
  				// see MWA 3/20/97 comment above - instead of checking the mission log, check the current hits
  				status = (ssp->current_hits <= 0.0f) ? 1 : 0;
  			} else {
  				// not supposed to ever happen, but could if there is a mismatch between the table and model subsystems
  				nprintf(("AI", "Couldn't find subsystem %d for ship %s\n", aigp->ai_submode, Ships[sindex].ship_name));
  				status = 0;
  			}
  			break;
  		}
  
  		case AI_GOAL_DISABLE_SHIP:
  		{
  			// shipnum could be -1 depending on if the ship hasn't arrived or died.  only look for subsystem
  			// destroyed when shipnum is valid
  			sindex = ship_name_lookup(aigp->target_name);
  
  			// can't determine the status of this goal if ship not valid
  			if (sindex < 0) {
  				status = 0;
  			} else {
  				// see MWA 3/20/97 comment above - instead of checking the mission log, check the current hits
  				status = (Ships[sindex].subsys_info[SUBSYSTEM_ENGINE].aggregate_current_hits <= 0.0f) ? 1 : 0;
  			}
  			break;
  		}
  
  		case AI_GOAL_DISARM_SHIP:
  		{
  			// shipnum could be -1 depending on if the ship hasn't arrived or died.  only look for subsystem
  			// destroyed when shipnum is valid
  			sindex = ship_name_lookup(aigp->target_name);
  
  			// can't determine the status of this goal if ship not valid
  			if (sindex < 0) {
  				status = 0;
  			} else {
  				// see MWA 3/20/97 comment above - instead of checking the mission log, check the current hits
  				status = (Ships[sindex].subsys_info[SUBSYSTEM_TURRET].aggregate_current_hits <= 0.0f) ? 1 : 0;
  			}
  			break;
  		}
  
  		// to guard or ignore a ship, the goal cannot continue if the ship being guarded is either destroyed
  		// or has departed.
  		case AI_GOAL_CHASE:
  		case AI_GOAL_GUARD:
  		case AI_GOAL_IGNORE:
  		case AI_GOAL_IGNORE_NEW:
  		case AI_GOAL_EVADE_SHIP:
  		case AI_GOAL_STAY_NEAR_SHIP:
  		case AI_GOAL_FLY_TO_SHIP:
  		case AI_GOAL_REARM_REPAIR:
  		{
  			// MWA -- 4/22/98.  Check for the ship actually being in the mission before
  			// checking departure and destroyed.  In multiplayer, since ships can respawn,
  			// they get log entries for being destroyed even though they have respawned.
  			sindex = ship_name_lookup( aigp->target_name );
  			if ( sindex < 0 ) {
  				status = mission_log_get_time( LOG_SHIP_DEPARTED, aigp->target_name, NULL, NULL);
  				if ( !status ) {
  					status = mission_log_get_time( LOG_SHIP_DESTROYED, aigp->target_name, NULL, NULL);
  					if ( !status ) {
  						status = mission_log_get_time( LOG_SELF_DESTRUCTED, aigp->target_name, NULL, NULL);
  					}
  				}
  
  				if ( status )
  					return_val = ai_achievability::NOT_ACHIEVABLE;
  			} else {
  				status = 0;
  			}
  
  			break;
  		}
  
  		case AI_GOAL_CHASE_WING:
  		case AI_GOAL_GUARD_WING:
  		{
  			status = mission_log_get_time( LOG_WING_DEPARTED, aigp->target_name, NULL, NULL );
  			if ( !status ) {
  				status = mission_log_get_time( LOG_WING_DESTROYED, aigp->target_name, NULL, NULL);
  				if ( status )
  					return_val = ai_achievability::NOT_ACHIEVABLE;
  			}
  
  			break;
  		}
  
  		// the following case statement returns control to caller on all paths!!!!
  		case AI_GOAL_CHASE_WEAPON:
  		{
  			// for chase weapon, we simply need to look at the weapon instance that we are trying to
  			// attack and see if the object still exists, and has the same signature that we expect.
  			Assert( aigp->target_instance >= 0 );
  
  			if ( Weapons[aigp->target_instance].objnum == -1 )
  				return ai_achievability::NOT_ACHIEVABLE;
  
  			// if the signatures don't match, then goal isn't achievable.
  			if ( Objects[Weapons[aigp->target_instance].objnum].signature != aigp->target_signature )
  				return ai_achievability::NOT_ACHIEVABLE;
  
  			// otherwise, we should be good to go
  			return ai_achievability::ACHIEVABLE;
  
  			break;
  		}
  
  		default:
  			Int3();
  			status = 0;
  			break;
  	}
  
  	// if status is true, then the mission log event was found and the goal was satisfied.  return
  	// AI_GOAL_SATISFIED which should allow this ai object to move onto the next order
  	if ( status )
  		return return_val;
  
  	// determine the status of the shipname that this object is acting on.  There are a couple of
  	// special cases to deal with.  Both the chase wing and undock commands will return from within
  	// the if statement.
  	if ( (aigp->ai_mode == AI_GOAL_CHASE_WING) || (aigp->ai_mode == AI_GOAL_GUARD_WING) )
  	{
  		sindex = wing_name_lookup( aigp->target_name );
  
  		if (sindex < 0)
  			return ai_achievability::NOT_KNOWN;
  
  		wing *wingp = &Wings[sindex];
  
  		if ( wingp->flags[Ship::Wing_Flags::Gone] )
  			return ai_achievability::NOT_ACHIEVABLE;
  		else if ( wingp->total_arrived_count == 0 )
  			return ai_achievability::NOT_KNOWN;
  		else
  			return ai_achievability::ACHIEVABLE;
  	}
  	// Goober5000 - undocking from an unspecified object is always achievable;
  	// undocking from a specified object is handled below
  	else if ( (aigp->ai_mode == AI_GOAL_UNDOCK) && (aigp->target_name == NULL) )
  	{
  			return ai_achievability::ACHIEVABLE;
  	}
  	else
  	{
  		auto ship_entry = ship_registry_get(aigp->target_name);
  
  		if (!ship_entry)
  		{
  			status = SHIP_STATUS_UNKNOWN;
  		}
  		// goal ship is currently in mission
  		else if (ship_entry->status == ShipStatus::PRESENT)
  		{
  			status = SHIP_STATUS_ARRIVED;
  		}
  		// goal ship is still on the arrival list
  		else if (ship_entry->status == ShipStatus::NOT_YET_PRESENT)
  		{
  			status = SHIP_STATUS_NOT_ARRIVED;
  		}
  		// goal ship has left the area
  		else if (ship_entry->status == ShipStatus::EXITED)
  		{
  			status = SHIP_STATUS_GONE;
  		}
  		else
  		{
  			status = SHIP_STATUS_UNKNOWN;
  		}
  
  		if (status == SHIP_STATUS_UNKNOWN)
  		{
  			mprintf(("Potentially incorrect behaviour in AI goal for ship %s: Ship %s could not be found among currently active, departed, or yet-to-arrive ships.\nPlease check the mission file.\n", Ships[objp->instance].ship_name, aigp->target_name));
  		}
  	}
  
  	// Goober5000 - before doing anything else, check if this is a disarm goal for an arrived ship...
  	if ((status == SHIP_STATUS_ARRIVED) && (aigp->ai_mode == AI_GOAL_DISARM_SHIP))
  	{
  		// if the ship has no turrets, we can't disarm it!
  		if (Ships[ship_name_lookup(aigp->target_name)].subsys_info[SUBSYSTEM_TURRET].type_count == 0)
  			return ai_achievability::NOT_ACHIEVABLE;
  	}
  
  	// if the goal is an ignore/disable/disarm goal, then 
  	// Goober5000 - see note at PURGE_GOALS_ALL_SHIPS... this is bizarre
  	if ((status == SHIP_STATUS_ARRIVED) && !(aigp->flags[AI::Goal_Flags::Goals_purged]))
  	{
  		if (aigp->ai_mode & PURGE_GOALS_ALL_SHIPS) {
  			ai_goal_purge_all_invalid_goals(aigp);
  			aigp->flags.set(AI::Goal_Flags::Goals_purged);
  		}
  		else if (aigp->ai_mode & PURGE_GOALS_ONE_SHIP) {
  			ai_goal_purge_invalid_goals(aigp, aip->goals, aip, -1);
  			aigp->flags.set(AI::Goal_Flags::Goals_purged);
  		}
  	}	
  
  	// if we are docking, validate the docking indices on both ships.  We might have to change names to indices.
  	// only enter this calculation if the ship we are docking with has arrived.  If the ship is gone, then
  	// this goal will get removed.
  	if ( (aigp->ai_mode == AI_GOAL_DOCK) && (status == SHIP_STATUS_ARRIVED) ) {
  		if (!(aigp->flags[AI::Goal_Flags::Docker_index_valid])) {
  			modelnum = Ship_info[Ships[objp->instance].ship_info_index].model_num;
  			Assert( modelnum >= 0 );
  			index = model_find_dock_name_index(modelnum, aigp->docker.name);
  			aigp->docker.index = index;
  			aigp->flags.set(AI::Goal_Flags::Docker_index_valid);
  		}
  
  		if (!(aigp->flags[AI::Goal_Flags::Dockee_index_valid])) {
  			sindex = ship_name_lookup(aigp->target_name);
  			if ( sindex != -1 ) {
  				modelnum = Ship_info[Ships[sindex].ship_info_index].model_num;
  				index = model_find_dock_name_index(modelnum, aigp->dockee.name);
  				aigp->dockee.index = index;
  				aigp->flags.set(AI::Goal_Flags::Dockee_index_valid);
  			} else
  				aigp->dockee.index = -1;		// this will force code into if statement below making goal not achievable.
  		}
  
  		if ( (aigp->dockee.index == -1) || (aigp->docker.index == -1) ) {
  			Int3();			// for now, allender wants to know about these things!!!!
  			return ai_achievability::NOT_ACHIEVABLE;
  		}
  
  		// if ship is disabled, don't know if it can dock or not
  		if ( Ships[objp->instance].flags[Ship::Ship_Flags::Disabled] )
  			return ai_achievability::NOT_KNOWN;
  
  		// we must also determine if we're prevented from docking for any reason
  		sindex = ship_name_lookup(aigp->target_name);
  		Assert( sindex >= 0 );
  		object *goal_objp = &Objects[Ships[sindex].objnum];
  
  		// if the ship that I am supposed to dock with is docked with something else, then I need to put my goal on hold
  		//	[MK, 4/23/98: With Mark, we believe this fixes the problem of Comet refusing to warp out after docking with Omega.
  		//	This bug occurred only when mission goals were validated in the frame in which Comet docked, which happened about
  		// once in 10-20 tries.]
  		if ( object_is_docked(goal_objp) )
  		{
  			// if the dockpoint I need to dock to is occupied by someone other than me
  			object *obstacle_objp = dock_find_object_at_dockpoint(goal_objp, aigp->dockee.index);
  			if (obstacle_objp == NULL)
  			{
  				// nobody in the way... we're good
  			}
  			else if (obstacle_objp != objp)
  			{
  				// return NOT_KNOWN which will place the goal on hold until the dockpoint is clear
  				return ai_achievability::NOT_KNOWN;
  			}
  		}
  
  		// if this ship is docked and needs to get docked with something else, then undock this ship
  		if ( object_is_docked(objp) )
  		{
  			// if the dockpoint I need to dock with is occupied by someone other than the guy I need to dock to
  			object *obstacle_objp = dock_find_object_at_dockpoint(objp, aigp->docker.index);
  			if (obstacle_objp == NULL)
  			{
  				// nobody in the way... we're good
  			}
  			else if (obstacle_objp != goal_objp)
  			{
  				// if this goal isn't on hold yet, then issue the undock goal
  				if ( !(aigp->flags[AI::Goal_Flags::Goal_on_hold]) )
  					ai_add_goal_ship_internal( aip, AI_GOAL_UNDOCK, Ships[obstacle_objp->instance].ship_name, -1, -1, 0 );
  
  				// return NOT_KNOWN which will place the goal on hold until the undocking is complete.
  				return ai_achievability::NOT_KNOWN;
  			}
  		}
  
  	// Goober5000 - necessitated by the multiple ship docking
  	} else if ( (aigp->ai_mode == AI_GOAL_UNDOCK) && (status == SHIP_STATUS_ARRIVED) ) {
  		// Put this goal on hold if we're already undocking.  Otherwise the new goal will pre-empt
  		// the current goal and strange things might happen.  One is that the object movement code
  		// forgets the previous undocking and "re-docks" the previous goal's ship.  Other problems
  		// might happen too, so err on the safe side.  (Yay for emergent paragraph justification!)
  		if ((aip->mode == AIM_DOCK) && (aip->submode >= AIS_UNDOCK_0))
  		{
  			// only put it on hold if it's someone other than the guy we're undocking from right now!!
  			if (aip->goal_objnum != Ships[ship_name_lookup(aigp->target_name)].objnum)
  				return ai_achievability::NOT_KNOWN;
  		}
  
  	} else if ( (aigp->ai_mode == AI_GOAL_DESTROY_SUBSYSTEM) && (status == SHIP_STATUS_ARRIVED) ) {
  		// if the ship has arrived, and the goal is destroy subsystem, then check to see that we
  		// have fixed up the subsystem name (of the subsystem to destroy) into an index into
  		// the ship's subsystem list
  		if ( aigp->flags[AI::Goal_Flags::Subsys_needs_fixup] ) {
  			sindex = ship_name_lookup( aigp->target_name );
  
  			if ( sindex != -1 ) {
  				aigp->ai_submode = ship_find_subsys( &Ships[sindex], aigp->docker.name );
  				aigp->flags.remove(AI::Goal_Flags::Subsys_needs_fixup);
  			} else {
  				Int3();
  				return ai_achievability::NOT_ACHIEVABLE;			// force this goal to be invalid
  			}
  		}
  	} else if ( ((aigp->ai_mode == AI_GOAL_IGNORE) || (aigp->ai_mode == AI_GOAL_IGNORE_NEW)) && (status == SHIP_STATUS_ARRIVED) ) {
  		object *ignored;
  
  		// for ignoring a ship, call the ai_ignore object function, then declare the goal satisfied
  		sindex = ship_name_lookup( aigp->target_name );
  		Assert( sindex != -1 );		// should be true because of above status
  		ignored = &Objects[Ships[sindex].objnum];
  
  		ai_ignore_object(objp, ignored, (aigp->ai_mode == AI_GOAL_IGNORE_NEW));
  
  		return ai_achievability::SATISFIED;
  	}
  
  	switch ( aigp->ai_mode )
  	{
  		case AI_GOAL_CHASE:
  		case AI_GOAL_CHASE_WING:
  		case AI_GOAL_DOCK:
  		case AI_GOAL_UNDOCK:
  		case AI_GOAL_GUARD:
  		case AI_GOAL_GUARD_WING:
  		case AI_GOAL_DISABLE_SHIP:
  		case AI_GOAL_DISARM_SHIP:
  		case AI_GOAL_DESTROY_SUBSYSTEM:
  		case AI_GOAL_IGNORE:
  		case AI_GOAL_IGNORE_NEW:
  		case AI_GOAL_EVADE_SHIP:
  		case AI_GOAL_STAY_NEAR_SHIP:
  		case AI_GOAL_FLY_TO_SHIP:
  		{
  			if ( status == SHIP_STATUS_ARRIVED )
  				return ai_achievability::ACHIEVABLE;
  			else if ( status == SHIP_STATUS_NOT_ARRIVED )
  				return ai_achievability::NOT_KNOWN;
  			else if ( status == SHIP_STATUS_GONE )
  				return ai_achievability::NOT_ACHIEVABLE;
  			else if ( status == SHIP_STATUS_UNKNOWN )
  				return ai_achievability::NOT_KNOWN;
  
  			Int3();		// get allender -- bad logic
  			break;
  		}
  
  		// for rearm repair ships, a goal is only achievable if the support ship isn't repairing anything
  		// else at the time, or is set to repair the ship for this goal.  All other goals should be placed
  		// on hold by returning GOAL_NOT_KNOWN.
  		case AI_GOAL_REARM_REPAIR:
  		{
  			// short circuit a couple of cases.  Ship not arrived shouldn't happen.  Ship gone means
  			// we mark the goal as not achievable.
  			if ( status == SHIP_STATUS_NOT_ARRIVED ) {
  				Int3();										// get Allender.  this shouldn't happen!!!
  				return ai_achievability::NOT_ACHIEVABLE;
  			}
  
  			if ( status == SHIP_STATUS_GONE )
  				return ai_achievability::NOT_ACHIEVABLE;
  
  			sindex = ship_name_lookup( aigp->target_name );
  
  			if ( sindex < 0 ) {
  				Int3();
  				return ai_achievability::NOT_ACHIEVABLE;
  			}
  
  			// if desitnation currently being repaired, then goal is stil active
  			if ( Ai_info[Ships[sindex].ai_index].ai_flags[AI::AI_Flags::Being_repaired] )
  				return ai_achievability::ACHIEVABLE;
  
  			// if the destination ship is dying or departing (but not completed yet), the mark goal as
  			// not achievable.
  			if ( Ships[sindex].is_dying_or_departing())
  				return ai_achievability::NOT_ACHIEVABLE;
  
  			// if the destination object is no longer awaiting repair, then remove the item
  			if ( !(Ai_info[Ships[sindex].ai_index].ai_flags[AI::AI_Flags::Awaiting_repair]) )
  				return ai_achievability::NOT_ACHIEVABLE;
  
  			// not repairing anything means that he can do this goal!!!
  			if ( !(aip->ai_flags[AI::AI_Flags::Repairing]) )
  				return ai_achievability::ACHIEVABLE;
  
  			// test code!!!
  			if ( aip->goal_objnum == -1 ) {
  				return ai_achievability::ACHIEVABLE;
  			}
  
  			// if he is repairing something, he can satisfy his repair goal (his goal_objnum)
  			// return GOAL_NOT_KNOWN which is kind of a hack which puts the goal on hold until it can be
  			// satisfied.  
  			if ( aip->goal_objnum != Ships[sindex].objnum )
  				return ai_achievability::NOT_KNOWN;
  
  			return ai_achievability::ACHIEVABLE;
  		}
  
  		default:
  			Int3();			// invalid case in switch:
  	}
  
  	return ai_achievability::NOT_KNOWN;
  }
  
  //	Compare function for sorting ai_goals based on priority.
  //	Return values set to sort array in _decreasing_ order.
  int ai_goal_priority_compare(const ai_goal *ga, const ai_goal *gb)
  {
  	// first, sort based on whether or not the ON_HOLD flag is set for the goal.
  	// If the flag is set, don't push the goal higher in the list even if priority
  	// is higher since goal cannot currently be achieved.
  
  	if ( (ga->flags[AI::Goal_Flags::Goal_on_hold]) && !(gb->flags[AI::Goal_Flags::Goal_on_hold]) )
  		return 1;
  	else if ( !(ga->flags[AI::Goal_Flags::Goal_on_hold]) && (gb->flags[AI::Goal_Flags::Goal_on_hold]) )
  		return -1;
  
  	// check whether or not the goal override flag is set.  If it is set, then push this goal higher
  	// in the list
  
  	// Goober5000: the ONLY goals we do not override are play-dead and play-dead-persistent,
  	// because the override flag needs to work in any other situation
  
  	else if ( (ga->flags[AI::Goal_Flags::Goal_override]) && !(gb->flags[AI::Goal_Flags::Goal_override]) && !(gb->ai_mode == AI_GOAL_PLAY_DEAD) && !(gb->ai_mode == AI_GOAL_PLAY_DEAD_PERSISTENT) )
  		return -1;
  	else if ( !(ga->flags[AI::Goal_Flags::Goal_override]) && (gb->flags[AI::Goal_Flags::Goal_override]) && !(ga->ai_mode == AI_GOAL_PLAY_DEAD) && !(ga->ai_mode == AI_GOAL_PLAY_DEAD_PERSISTENT))
  		return 1;
  
  	// now normal priority processing
  
  	if (ga->priority > gb->priority)
  		return -1;
  	else if ( ga->priority < gb->priority )
  		return 1;
  
  	// check based on time goal was issued
  
  	if ( ga->time > gb->time )
  		return -1;
  	// V had this check commented out and would always return 1 here, that messes up where multiple goals 
  	// get assigned at the same time though, when the priorities are also the same (Enif station bug) - taylor
  	else if ( ga->time < gb->time )
  		return 1;
  
  	// the two are equal
  	return 0;
  }
  
  //	Prioritize goal list.
  //	First sort on priority.
  //	Then sort on time for goals of equivalent priority.
  //	*aip		The AI info to act upon.  Goals are stored at aip->goals
  void prioritize_goals(ai_info *aip)
  {
  	//	First sort based on priority field.
  	insertion_sort(aip->goals, MAX_AI_GOALS, ai_goal_priority_compare);
  }
  
  //	Scan the list of goals at aip->goals.
  //	Remove obsolete goals.
  //	objnum	Object of interest.  Redundant with *aip.
  //	*aip		contains goals at aip->goals.
  void validate_mission_goals(int objnum, ai_info *aip)
  {
  	int	i;
  	
  	// loop through all of the goals to determine which goal should be followed.
  	// This determination will be based on priority, and the time at which it was issued.
  	for ( i = 0; i < MAX_AI_GOALS; i++ ) {
  		ai_achievability state;
  		ai_goal	*aigp;
  
  		aigp = &aip->goals[i];
  
  		// quick check to see if this goal is valid or not, or if we are trying to process the
  		// current goal
  		if (aigp->ai_mode == AI_GOAL_NONE)
  			continue;
  
  		// purge any goals which should get purged
  		if ( aigp->flags[AI::Goal_Flags::Purge] ) {
  			ai_remove_ship_goal( aip, i );
  			continue;
  		}
  
  		state = ai_mission_goal_achievable( objnum, aigp );
  
  		// if this order is no longer a valid one, remove it
  		if ( (state == ai_achievability::NOT_ACHIEVABLE) || (state == ai_achievability::SATISFIED) ) {
  			ai_remove_ship_goal( aip, i );
  			continue;
  		}
  
  		// if the status is achievable, and the on_hold flag is set, clear the flagb
  		if ( (state == ai_achievability::ACHIEVABLE) && (aigp->flags[AI::Goal_Flags::Goal_on_hold]) )
  			aigp->flags.remove(AI::Goal_Flags::Goal_on_hold);
  
  		// if the goal is not known, then set the ON_HOLD flag so that it doesn't get counted as
  		// a goal to be pursued
  		if (state == ai_achievability::NOT_KNOWN)
  			aigp->flags.set(AI::Goal_Flags::Goal_on_hold);		// put this goal on hold until it becomes true
  	}
  
  	// if we had an active goal, and that goal is now in hold, make the mode AIM_NONE.  If a new valid
  	// goal is produced after prioritizing, then the mode will get reset immediately.  Otherwise, setting
  	// the mode to none will force ship to do default behavior.
  	if ( (aip->goals[0].ai_mode != AI_GOAL_NONE) && (aip->goals[0].flags[AI::Goal_Flags::Goal_on_hold]) )
  		aip->mode = AIM_NONE;
  
  	// if the active goal is a rearm/repair goal, the put all other valid goals (which are not repair goals)
  	// on hold
  	if ( (aip->goals[0].ai_mode == AI_GOAL_REARM_REPAIR) && object_is_docked(&Objects[objnum]) ) {
  		for ( i = 1; i < MAX_AI_GOALS; i++ ) {
  			if ( (aip->goals[i].ai_mode == AI_GOAL_NONE) || (aip->goals[i].ai_mode == AI_GOAL_REARM_REPAIR) )
  				continue;
  			aip->goals[i].flags.set(AI::Goal_Flags::Goal_on_hold);
  		}
  	}
  }
  
  //XSTR:OFF
  /*
  static char *Goal_text[5] = {
  "EVENT_SHIP",
  "EVENT_WING",
  "PLAYER_SHIP",
  "PLAYER_WING",
  "DYNAMIC",
  };
  */
  //XSTR:ON
  
  extern char *Mode_text[MAX_AI_BEHAVIORS];
  
  // code to process ai "orders".  Orders include those determined from the mission file and those
  // given by the player to a ship that is under his control.  This function gets called for every
  // AI object every N seconds through the ai loop.
  void ai_process_mission_orders( int objnum, ai_info *aip )
  {
  	object	*objp = &Objects[objnum];
  	object	*other_obj;
  	ai_goal	*current_goal;
  	int		wingnum, shipnum;
  	int		original_signature;
  
  /*	if (!stricmp(Ships[objp->instance].ship_name, "gtt comet")) {
  		for (int i=0; i<MAX_AI_GOALS; i++) {
  			if (aip->goals[i].signature != -1) {
  				nprintf(("AI", "%6.1f: mode=%s, type=%s, ship=%s\n", f2fl(Missiontime), Mode_text[aip->goals[i].ai_mode], Goal_text[aip->goals[i].type], aip->goals[i].ship_name));
  			}
  		}
  		nprintf(("AI", "\n"));
  	}
  */
  
  	// AL 12-12-97: If a ship is entering/leaving a docking bay, wait until path
  	//					 following is finished before pursuing goals.
  	if ( aip->mode == AIM_BAY_EMERGE || aip->mode == AIM_BAY_DEPART ) {
  		return;
  	}
  
  	//	Goal #0 is always the active goal, as we maintain a sorted list.
  	//	Get the signature to see if sorting it again changes it.
  	original_signature = aip->goals[0].signature;
  
  	validate_mission_goals(objnum, aip);
  
  	//	Sort the goal array by priority and other factors.
  	prioritize_goals(aip);
  
  	//	Make sure there's a goal to pursue, else return.
  	if (aip->goals[0].signature == -1) {
  		if (aip->mode == AIM_NONE)
  			ai_do_default_behavior(objp);
  		return;
  	}
  
  	//	If goal didn't change, return.
  	if ((aip->active_goal != -1) && (aip->goals[0].signature == original_signature))
  		return;
  
  	// if the first goal in the list has the ON_HOLD flag, set, there is no current valid goal
  	// to pursue.
  	if ( aip->goals[0].flags[AI::Goal_Flags::Goal_on_hold] )
  		return;
  
  	//	Kind of a hack for now.  active_goal means the goal currently being pursued.
  	//	It will always be #0 since the list is prioritized.
  	aip->active_goal = 0;
  
  	//nprintf(("AI", "New goal for %s = %i\n", Ships[objp->instance].ship_name, aip->goals[0].ai_mode));
  
  	current_goal = &aip->goals[0];
  
  	if ( MULTIPLAYER_MASTER ){
  		send_ai_info_update_packet( objp, AI_UPDATE_ORDERS );
  	}
  
  	// if this object was flying in formation off of another object, remove the flag that tells him
  	// to do this.  The form-on-my-wing command is removed from the goal list as soon as it is called, so
  	// we are safe removing this bit here.
  	int old_form_objnum = -1;
  	if (aip->ai_flags[AI::AI_Flags::Formation_object]) {
  		// save who he was following so we can tell any others 
  		// who are following to re-organize
  		old_form_objnum = aip->goal_objnum;
  		aip->ai_flags.remove(AI::AI_Flags::Formation_object);
  	}
  
  	// Goober5000 - we may want to use AI for the player
  	// AL 3-7-98: If this is a player ship, and the goal is not a formation goal, then do a quick out
  	if ( !(Player_use_ai) && (objp->flags[Object::Object_Flags::Player_ship]) && (current_goal->ai_mode != AI_GOAL_FORM_ON_WING) )
  	{
  		return;
  	}	
  
  
  
  	switch ( current_goal->ai_mode ) {
  
  	case AI_GOAL_CHASE:
  		if ( current_goal->target_name ) {
  			shipnum = ship_name_lookup( current_goal->target_name );
  			Assert (shipnum != -1 );			// shouldn't get here if this is false!!!!
  			other_obj = &Objects[Ships[shipnum].objnum];
  		} else
  			other_obj = NULL;						// we get this case when we tell ship to engage enemy!
  
  		//	Mike -- debug code!
  		//	If a ship has a subobject on it, attack that instead of the main ship!
  		ai_attack_object( objp, other_obj);
  		break;
  
  	case AI_GOAL_CHASE_WEAPON:
  		Assert( Weapons[current_goal->target_instance].objnum >= 0 );
  		other_obj = &Objects[Weapons[current_goal->target_instance].objnum];
  		Assert( other_obj->signature == current_goal->target_signature );
  		ai_attack_object( objp, other_obj);
  		break;
  
  	case AI_GOAL_GUARD:
  		shipnum = ship_name_lookup( current_goal->target_name );
  		Assert (shipnum != -1 );			// shouldn't get here if this is false!!!!
  		other_obj = &Objects[Ships[shipnum].objnum];
  		// shipnum and other_obj are the shipnumber and object pointer of the object that you should
  		// guard.
  		if (objp != other_obj) {
  			ai_set_guard_object(objp, other_obj);
  			aip->submode_start_time = Missiontime;
  		} else {
  			mprintf(("Warning: Ship %s told to guard itself.  Goal ignored.\n", Ships[objp->instance].ship_name));
  		}
  		// -- What is this doing here?? -- MK, 7/30/97 -- ai_do_default_behavior( objp );
  		break;
  
  	case AI_GOAL_GUARD_WING:
  		wingnum = wing_name_lookup( current_goal->target_name );
  		Assert (wingnum != -1 );			// shouldn't get here if this is false!!!!
  		ai_set_guard_wing(objp, wingnum);
  		aip->submode_start_time = Missiontime;
  		break;
  
  	case AI_GOAL_WAYPOINTS:				// do nothing for waypoints
  	case AI_GOAL_WAYPOINTS_ONCE: {
  		int flags = 0;
  
  		if ( current_goal->ai_mode == AI_GOAL_WAYPOINTS)
  			flags |= WPF_REPEAT;
  		ai_start_waypoints(objp, current_goal->wp_list, flags);
  		break;
  	}
  
  	case AI_GOAL_FLY_TO_SHIP:
  		shipnum = ship_name_lookup( current_goal->target_name );
  		Assert (shipnum != -1 );			// shouldn't get here if this is false!!!!
  		ai_start_fly_to_ship(objp, shipnum);
  		break;
  
  	case AI_GOAL_DOCK: {
  		shipnum = ship_name_lookup( current_goal->target_name );
  		Assert (shipnum != -1 );			// shouldn't get here if this is false!!!!
  		other_obj = &Objects[Ships[shipnum].objnum];
  
  		// be sure that we have indices for docking points here!  If we ever had names, they should
  		// get fixed up in goal_achievable so that the points can be checked there for validity
  		Assert (current_goal->flags[AI::Goal_Flags::Dockee_index_valid] && current_goal->flags[AI::Goal_Flags::Docker_index_valid]);
  		ai_dock_with_object( objp, current_goal->docker.index, other_obj, current_goal->dockee.index, AIDO_DOCK );
  		break;
  	}
  
  	case AI_GOAL_UNDOCK:
  		// try to find the object which which this object is docked with.  Use that object as the
  		// "other object" for the undocking proceedure.  If "other object" isn't found, then the undock
  		// goal cannot continue.  Spit out a warning and remove the goal.
  
  		// Goober5000 - do we have a specific ship to undock from?
  		if ( current_goal->target_name != NULL )
  		{
  			shipnum = ship_name_lookup( current_goal->target_name );
  
  			// hmm, perhaps he was destroyed
  			if (shipnum == -1)
  			{
  				other_obj = NULL;
  			}
  			// he exists... let's undock from him
  			else
  			{
  				other_obj = &Objects[Ships[shipnum].objnum];
  			}
  		}
  		// no specific ship
  		else
  		{
  			// are we docked?
  			if (object_is_docked(objp))
  			{
  				// just pick the first guy we're docked to
  				other_obj = dock_get_first_docked_object( objp );
  
  				// and add the ship name so it displays on the HUD
  				current_goal->target_name = Ships[other_obj->instance].ship_name;
  			}
  			// hmm, nobody exists that we can undock from
  			else
  			{
  				other_obj = NULL;
  			}
  		}
  
  		if ( other_obj == NULL ) {
  			// assume that the guy he was docked with doesn't exist anymore.  (i.e. a cargo containuer
  			// can get destroyed while docked with a freighter.)  We should just remove this goal and
  			// let this ship pick up it's next goal.
  			ai_mission_goal_complete( aip );		// mark as complete, so we can remove it and move on!!!
  			break;
  		}
  
  		// Goober5000 - Sometimes a ship will be assigned a new goal before it can finish undocking.  Later,
  		// when the ship returns to this goal, it will try to resume undocking from a ship it's not attached
  		// to.  If this happens, remove the goal as above.
  		if (!dock_check_find_direct_docked_object(objp, other_obj))
  		{
  			ai_mission_goal_complete( aip );
  			break;
  		}
  
  		// passing 0, 0 is okay because the undock code will figure out where to undock from
  		ai_dock_with_object( objp, 0, other_obj, 0, AIDO_UNDOCK );
  		break;
  
  
  		// when destroying a subsystem, we can destroy a specific instance of a subsystem
  		// or all instances of a type of subsystem (i.e. a specific engine or all engines).
  		// the ai_submode value is > 0 for a specific instance of subsystem and < 0 for all
  		// instances of a specific type
  	case AI_GOAL_DESTROY_SUBSYSTEM:
  	case AI_GOAL_DISABLE_SHIP:
  	case AI_GOAL_DISARM_SHIP: {
  		shipnum = ship_name_lookup( current_goal->target_name );
  		Assert( shipnum >= 0 );
  		other_obj = &Objects[Ships[shipnum].objnum];
  		ai_attack_object( objp, other_obj);
  		ai_set_attack_subsystem( objp, current_goal->ai_submode );		// submode stored the subsystem type
  		if (current_goal->ai_mode != AI_GOAL_DESTROY_SUBSYSTEM) {
  			if (aip->target_objnum != -1) {
  				//	Only protect if _not_ a capital ship.  We don't want the Lucifer accidentally getting protected.
  				if (Ship_types[Ship_info[Ships[shipnum].ship_info_index].class_type].flags[Ship::Type_Info_Flags::AI_protected_on_cripple])
  					Objects[aip->target_objnum].flags.set(Object::Object_Flags::Protected);
  			}
  		} else	//	Just in case this ship had been protected, unprotect it.
  			if (aip->target_objnum != -1)
  				Objects[aip->target_objnum].flags.remove(Object::Object_Flags::Protected);
  
  		break;
  	}
  
  	case AI_GOAL_CHASE_WING:
  		wingnum = wing_name_lookup( current_goal->target_name );
  		Assertion( wingnum >= 0, "The target of AI_GOAL_CHASE_WING must refer to a valid wing!" );
  		ai_attack_wing(objp, wingnum);
  		break;
  
  	case AI_GOAL_CHASE_ANY:
  		ai_attack_object( objp, nullptr);
  		break;
  
  	// chase-ship-class is chase-any but restricted to a subset of ships
  	case AI_GOAL_CHASE_SHIP_CLASS:
  		shipnum = ship_info_lookup( current_goal->target_name );
  		Assertion( shipnum >= 0, "The target of AI_GOAL_CHASE_SHIP_CLASS must refer to a valid ship class!" );
  		ai_attack_object( objp, nullptr, shipnum );
  		break;
  
  	case AI_GOAL_WARP: {
  		mission_do_departure( objp, true );
  		break;
  	}
  
  	case AI_GOAL_EVADE_SHIP:
  		shipnum = ship_name_lookup( current_goal->target_name );
  		Assert( shipnum >= 0 );
  		other_obj = &Objects[Ships[shipnum].objnum];
  		ai_evade_object( objp, other_obj);
  		break;
  
  	case AI_GOAL_STAY_STILL:
  		// for now, ignore any other parameters!!!!
  		// clear out the object's goals.  Seems to me that if a ship is staying still for a purpose
  		// then we need to clear everything out since there is not a real way to get rid of this goal
  		// clearing out goals is okay here since we are now what mode to set this AI object to.
  		ai_clear_ship_goals( aip );
  		ai_stay_still( objp, NULL );
  		break;
  
  	case AI_GOAL_PLAY_DEAD:
  		// if a ship is playing dead, MWA says that it shouldn't try to do anything else.
  		// clearing out goals is okay here since we are now what mode to set this AI object to.
  		ai_clear_ship_goals( aip );
  		aip->mode = AIM_PLAY_DEAD;
  		aip->submode = -1;
  		aip->submode_start_time = Missiontime;
  		break;
  
  	case AI_GOAL_PLAY_DEAD_PERSISTENT:
  		// same as above, but we don't clear out ship goals
  		aip->mode = AIM_PLAY_DEAD;
  		aip->submode = -1;
  		aip->submode_start_time = Missiontime;
  		break;
  
  	case AI_GOAL_FORM_ON_WING:
  		// for form on wing, we need to clear out all goals for this ship, and then call the form
  		// on wing AI code
  		// clearing out goals is okay here since we are now what mode to set this AI object to.
  		ai_clear_ship_goals( aip );
  		shipnum = ship_name_lookup( current_goal->target_name );
  		Assert( shipnum >= 0 );
  		other_obj = &Objects[Ships[shipnum].objnum];
  		ai_form_on_wing( objp, other_obj );
  		break;
  
  // labels for support ship commands
  
  	case AI_GOAL_STAY_NEAR_SHIP: {
  		shipnum = ship_name_lookup( current_goal->target_name );
  		Assert( shipnum >= 0 );
  		other_obj = &Objects[Ships[shipnum].objnum];
  		// todo MK:  hook to keep support ship near other_obj -- other_obj could be the player object!!!
  		float	dist = 300.0f;		//	How far away to stay from ship.  Should be set in SEXP?
  		ai_do_stay_near(objp, other_obj, dist);
  		break;
  										  }
  
  	case AI_GOAL_KEEP_SAFE_DISTANCE:
  		// todo MK: hook to keep support ship at a safe distance
  
  		// Goober5000 - hmm, never implemented - let's see about that
  		ai_do_safety(objp);
  		break;
  
  	case AI_GOAL_REARM_REPAIR:
  		shipnum = ship_name_lookup( current_goal->target_name );
  		Assert( shipnum >= 0 );
  		other_obj = &Objects[Ships[shipnum].objnum];
  		ai_rearm_repair( objp, current_goal->docker.index, other_obj, current_goal->dockee.index );
  		break;
  		
  	case AI_GOAL_LUA:
  		ai_lua_start(current_goal, objp);
  		break;
  
  	default:
  		Int3();
  		break;
  	}
  
  	if (old_form_objnum >= 0)
  		ai_formation_object_recalculate_slotnums(old_form_objnum);
  }
  
  void ai_update_goal_references(ai_goal *goals, int type, const char *old_name, const char *new_name)
  {
  	int i, mode, flag, dummy;
  
  	for (i=0; i<MAX_AI_GOALS; i++)  // loop through all the goals in the Ai_info entry
  	{
  		mode = goals[i].ai_mode;
  		flag = 0;
  		switch (type)
  		{
  			case REF_TYPE_SHIP:
  			case REF_TYPE_PLAYER:
  				switch (mode)
  				{
  					case AI_GOAL_CHASE:
  					case AI_GOAL_DOCK:
  					case AI_GOAL_DESTROY_SUBSYSTEM:
  					case AI_GOAL_GUARD:
  					case AI_GOAL_DISABLE_SHIP:
  					case AI_GOAL_DISARM_SHIP:
  					case AI_GOAL_IGNORE:
  					case AI_GOAL_IGNORE_NEW:
  					case AI_GOAL_EVADE_SHIP:
  					case AI_GOAL_STAY_NEAR_SHIP:
  						flag = 1;
  				}
  				break;
  
  			case REF_TYPE_WING:
  				switch (mode)
  				{
  					case AI_GOAL_CHASE_WING:
  					case AI_GOAL_GUARD_WING:
  						flag = 1;
  				}
  				break;
  
  			case REF_TYPE_WAYPOINT:
  				switch (mode)
  				{
  					case AI_GOAL_WAYPOINTS:
  					case AI_GOAL_WAYPOINTS_ONCE:
  						flag = 1;
  				}
  				break;
  
  			case REF_TYPE_PATH:
  				switch (mode)
  				{
  					case AI_GOAL_WAYPOINTS:
  					case AI_GOAL_WAYPOINTS_ONCE:
  					
  						flag = 1;
  				}
  				break;
  
  			default:
  				Warning(LOCATION, "unhandled FRED reference type %d in ai_update_goal_references", type);
  				break;
  		}
  
  		if (flag)  // is this a valid goal to parse for this conversion?
  		{
  			if (!stricmp(goals[i].target_name, old_name))
  			{
  				if (*new_name == '<')  // target was just deleted..
  					goals[i].ai_mode = AI_GOAL_NONE;
  				else
  					goals[i].target_name = ai_get_goal_target_name(new_name, &dummy);
  			}
  		}
  	}
  }
  
  int query_referenced_in_ai_goals(ai_goal *goals, int type, const char *name)
  {
  	int i, mode, flag;
  
  	for (i=0; i<MAX_AI_GOALS; i++)  // loop through all the goals in the Ai_info entry
  	{
  		mode = goals[i].ai_mode;
  		flag = 0;
  		switch (type)
  		{
  			case REF_TYPE_SHIP:
  				switch (mode)
  				{
  					case AI_GOAL_CHASE:
  					case AI_GOAL_DOCK:
  					case AI_GOAL_DESTROY_SUBSYSTEM:
  					case AI_GOAL_GUARD:
  					case AI_GOAL_DISABLE_SHIP:
  					case AI_GOAL_DISARM_SHIP:
  					case AI_GOAL_IGNORE:
  					case AI_GOAL_IGNORE_NEW:
  					case AI_GOAL_EVADE_SHIP:
  					case AI_GOAL_STAY_NEAR_SHIP:
  						flag = 1;
  				}
  				break;
  
  			case REF_TYPE_WING:
  				switch (mode)
  				{
  					case AI_GOAL_CHASE_WING:
  					case AI_GOAL_GUARD_WING:
  						flag = 1;
  				}
  				break;
  
  			case REF_TYPE_WAYPOINT:
  				switch (mode)
  				{
  					case AI_GOAL_WAYPOINTS:
  					case AI_GOAL_WAYPOINTS_ONCE:
  						flag = 1;
  				}
  				break;
  
  			case REF_TYPE_PATH:
  				switch (mode)
  				{
  					case AI_GOAL_WAYPOINTS:
  					case AI_GOAL_WAYPOINTS_ONCE:
  						flag = 1;
  				}
  				break;
  		}
  
  		if (flag)  // is this a valid goal to parse for this conversion?
  		{
  			if (!stricmp(goals[i].target_name, name))
  				return 1;
  		}
  	}
  
  	return 0;
  }
  
  char *ai_add_dock_name(const char *str)
  {
  	char *ptr;
  	int i;
  
  	Assert(strlen(str) <= NAME_LENGTH - 1);
  	for (i=0; i<Num_ai_dock_names; i++)
  		if (!stricmp(Ai_dock_names[i], str))
  			return Ai_dock_names[i];
  
  	Assert(Num_ai_dock_names < MAX_AI_DOCK_NAMES);
  	ptr = Ai_dock_names[Num_ai_dock_names++];
  	strcpy(ptr, str);
  	return ptr;
  }
  