Skip to content

Draft: Implementation for an AI order to chase ship types #6620

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions code/ai/ai.h
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,7 @@ const char *ai_get_goal_target_name(const char *name, int *index);
void ai_clear_goal_target_names();

extern void init_ai_system(void);
extern void ai_attack_object(object *attacker, object *attacked, int ship_info_index = -1);
extern void ai_attack_object(object *attacker, object *attacked, int ship_info_index = -1, int class_type = -1);
extern void ai_evade_object(object *evader, object *evaded);
extern void ai_ignore_object(object *ignorer, object *ignored, int ignore_new);
extern void ai_ignore_wing(object *ignorer, int wingnum);
Expand Down Expand Up @@ -559,7 +559,7 @@ extern void ai_set_guard_wing(object *objp, int wingnum);
extern void ai_warp_out(object *objp, vec3d *vp);
extern void ai_attack_wing(object *attacker, int wingnum);
extern void ai_deathroll_start(object *ship_obj);
extern int set_target_objnum(ai_info *aip, int objnum);
extern int set_target_objnum(ai_info* aip, int objnum);
extern void ai_form_on_wing(object *objp, object *goal_objp);
extern void ai_do_stay_near(object *objp, object *other_obj, float dist, int additional_data);
extern ship_subsys *set_targeted_subsys(ai_info *aip, ship_subsys *new_subsys, int parent_objnum);
Expand Down Expand Up @@ -596,7 +596,7 @@ extern int ai_maybe_fire_afterburner(object *objp, ai_info *aip);
extern void set_predicted_enemy_pos(vec3d *predicted_enemy_pos, object *pobjp, vec3d *enemy_pos, vec3d *enemy_vel, ai_info *aip);

extern int is_instructor(object *objp);
extern int find_enemy(int objnum, float range, int max_attackers, int ship_info_index = -1);
extern int find_enemy(int objnum, float range, int max_attackers, int ship_info_index = -1, int class_type = -1);

float ai_get_weapon_speed(const ship_weapon *swp);
void set_predicted_enemy_pos_turret(vec3d *predicted_enemy_pos, const vec3d *gun_pos, const object *pobjp, const vec3d *enemy_pos, const vec3d *enemy_vel, float weapon_speed, float time_enemy_in_range);
Expand All @@ -617,7 +617,7 @@ extern float dock_orient_and_approach(object *docker_objp, int docker_index, obj
void ai_set_mode_warp_out(object *objp, ai_info *aip);

// prototyped by Goober5000
int get_nearest_objnum(int objnum, int enemy_team_mask, int enemy_wing, float range, int max_attackers, int ship_info_index);
int get_nearest_objnum(int objnum, int enemy_team_mask, int enemy_wing, float range, int max_attackers, int ship_info_index, int class_type = -1);

// moved to header file by Goober5000
void ai_announce_ship_dying(object *dying_objp);
Expand Down
52 changes: 32 additions & 20 deletions code/ai/aicode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2214,6 +2214,7 @@ typedef struct eval_nearest_objnum {
object *trial_objp;
int enemy_team_mask;
int enemy_ship_info_index;
int enemy_class_type;
int enemy_wing;
float range;
int max_attackers;
Expand Down Expand Up @@ -2247,6 +2248,10 @@ void evaluate_object_as_nearest_objnum(eval_nearest_objnum *eno)
if ((eno->enemy_ship_info_index >= 0) && (shipp->ship_info_index != eno->enemy_ship_info_index))
return;

// If only supposed to attack ships of a certain ship type, don't attack other ships.
if ((eno->enemy_class_type >= 0) && (Ship_info[shipp->ship_info_index].class_type != eno->enemy_class_type))
return;

// Don't keep firing at a ship that is in its death throes.
if (shipp->flags[Ship::Ship_Flags::Dying])
return;
Expand Down Expand Up @@ -2335,8 +2340,9 @@ void evaluate_object_as_nearest_objnum(eval_nearest_objnum *eno)
* @param range Ship must be within range "range".
* @param max_attackers Don't attack a ship that already has at least max_attackers attacking it.
* @param ship_info_index If >=0, the enemy object must be of the specified ship class
* @param class_type If >=0, the enemy object must be of the specified ship type
*/
int get_nearest_objnum(int objnum, int enemy_team_mask, int enemy_wing, float range, int max_attackers, int ship_info_index)
int get_nearest_objnum(int objnum, int enemy_team_mask, int enemy_wing, float range, int max_attackers, int ship_info_index, int class_type)
{
object *danger_weapon_objp;
ai_info *aip;
Expand All @@ -2346,6 +2352,7 @@ int get_nearest_objnum(int objnum, int enemy_team_mask, int enemy_wing, float ra
eval_nearest_objnum eno;
eno.enemy_team_mask = enemy_team_mask;
eno.enemy_ship_info_index = ship_info_index;
eno.enemy_class_type = class_type;
eno.enemy_wing = enemy_wing;
eno.max_attackers = max_attackers;
eno.objnum = objnum;
Expand Down Expand Up @@ -2391,7 +2398,7 @@ int get_nearest_objnum(int objnum, int enemy_team_mask, int enemy_wing, float ra
// If only looking for target in certain wing and couldn't find anything in
// that wing, look for any object.
if ((eno.nearest_objnum == -1) && (enemy_wing != -1)) {
return get_nearest_objnum(objnum, enemy_team_mask, -1, range, max_attackers, ship_info_index);
return get_nearest_objnum(objnum, enemy_team_mask, -1, range, max_attackers, ship_info_index, class_type);
}

return eno.nearest_objnum;
Expand Down Expand Up @@ -2495,33 +2502,38 @@ int get_enemy_timestamp()
/**
* Return objnum if enemy found, else return -1;
*
* @param objnum Object number
* @param range Range within which to look
* @param max_attackers Don't attack a ship that already has at least max_attackers attacking it.
* @param objnum Object number
* @param range Range within which to look
* @param max_attackers Don't attack a ship that already has at least max_attackers attacking it.
* @param ship_info_index If specified, restrict the search to enemies with this ship class
* @param class_type If specified, restrict the search to enemies with this ship type
*/
int find_enemy(int objnum, float range, int max_attackers, int ship_info_index)
int find_enemy(int objnum, float range, int max_attackers, int ship_info_index, int class_type)
{
int enemy_team_mask;
int enemy_team_mask;

if (objnum < 0)
return -1;

enemy_team_mask = iff_get_attackee_mask(obj_team(&Objects[objnum]));

// if target_objnum != -1, use that as goal.
ai_info *aip = &Ai_info[Ships[Objects[objnum].instance].ai_index];
ai_info* aip = &Ai_info[Ships[Objects[objnum].instance].ai_index];
if (timestamp_elapsed(aip->choose_enemy_timestamp)) {
aip->choose_enemy_timestamp = timestamp(get_enemy_timestamp());
if (aip->target_objnum != -1) {
int target_objnum = aip->target_objnum;
int target_objnum = aip->target_objnum;
ship* target_shipp = &Ships[Objects[target_objnum].instance];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change this to:

ship* target_shipp = (Objects[target_objnum].type == OBJ_SHIP) ? &Ships[Objects[target_objnum].instance] : nullptr;

Because not every target is necessarily a ship. This was a bug in Volition's code too :-/


// DKA don't undo object as target in nebula missions.
// This could cause attack on ship on fringe on nebula to stop if attackee moves our of nebula range. (BAD)
if ( Objects[target_objnum].signature == aip->target_signature ) {
if (iff_matches_mask(Ships[Objects[target_objnum].instance].team, enemy_team_mask)) {
if (ship_info_index < 0 || ship_info_index == Ships[Objects[target_objnum].instance].ship_info_index) {
if (!(Objects[target_objnum].flags[Object::Object_Flags::Protected])) {
return target_objnum;
// This could cause attack on ship on fringe on nebula to stop if attackee moves out of nebula range. (BAD)
if (Objects[target_objnum].signature == aip->target_signature) {
if (iff_matches_mask(target_shipp->team, enemy_team_mask)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change this to

if (target_shipp && iff_matches_mask(target_shipp->team, enemy_team_mask)) {

because with my above review comment, it will be possible for target_shipp to be nullptr

if (ship_info_index < 0 || ship_info_index == target_shipp->ship_info_index) {
if (class_type < 0 || (target_shipp->ship_info_index >= 0 && class_type == Ship_info[target_shipp->ship_info_index].class_type)) {
if (!(Objects[target_objnum].flags[Object::Object_Flags::Protected])) {
return target_objnum;
}
}
}
}
Expand All @@ -2530,16 +2542,16 @@ int find_enemy(int objnum, float range, int max_attackers, int ship_info_index)
aip->target_signature = -1;
}
}

return get_nearest_objnum(objnum, enemy_team_mask, aip->enemy_wing, range, max_attackers, ship_info_index);

return get_nearest_objnum(objnum, enemy_team_mask,aip->enemy_wing, range, max_attackers, ship_info_index, class_type);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

put back the space between enemy_team_mask, and aip->enemy_wing


} else {
aip->target_objnum = -1;
aip->target_signature = -1;
return -1;
}
}


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This blank line can be removed

/**
* If issued an order to a ship that's awaiting repair, abort that process.
* However, do not abort process for an object that is currently being repaired -- let it finish.
Expand Down Expand Up @@ -2573,7 +2585,7 @@ void force_avoid_player_check(object *objp, ai_info *aip)
* If attacked == NULL, then attack any enemy object.
* Attack point *rel_pos on object. This is for supporting attacking subsystems.
*/
void ai_attack_object(object* attacker, object* attacked, int ship_info_index)
void ai_attack_object(object* attacker, object* attacked, int ship_info_index, int class_type)
{
int temp;
ai_info* aip;
Expand Down Expand Up @@ -2606,7 +2618,7 @@ void ai_attack_object(object* attacker, object* attacked, int ship_info_index)
if (attacked == nullptr) {
aip->choose_enemy_timestamp = timestamp(0);
// nebula safe
set_target_objnum(aip, find_enemy(OBJ_INDEX(attacker), 99999.9f, 4, ship_info_index));
set_target_objnum(aip, find_enemy(OBJ_INDEX(attacker), 99999.9f, 4, ship_info_index, class_type));
} else {
// check if we can see attacked in nebula
if (aip->target_objnum != OBJ_INDEX(attacked)) {
Expand Down
58 changes: 52 additions & 6 deletions code/ai/aigoals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ ai_goal_list Ai_goal_names[] =
{ "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 },
{ "Attack ship type", AI_GOAL_CHASE_SHIP_TYPE, 0 },
};

int Num_ai_goals = sizeof(Ai_goal_names) / sizeof(ai_goal_list);
Expand All @@ -114,6 +115,7 @@ const char *Ai_goal_text(ai_goal_mode goal, int submode)
case AI_GOAL_CHASE:
case AI_GOAL_CHASE_WING:
case AI_GOAL_CHASE_SHIP_CLASS:
case AI_GOAL_CHASE_SHIP_TYPE:
return XSTR( "attack ", 474);
case AI_GOAL_DOCK:
return XSTR( "dock ", 475);
Expand Down Expand Up @@ -484,7 +486,7 @@ void ai_goal_purge_invalid_goals( ai_goal *aigp, ai_goal *goal_list, ai_info *ai
int i, j;
ai_goal *purge_goal;
const char *name;
int mode, ship_index, wingnum;
int mode, ship_index, wingnum, ship_type;

// get locals for easer access
name = aigp->target_name;
Expand Down Expand Up @@ -513,13 +515,26 @@ void ai_goal_purge_invalid_goals( ai_goal *aigp, ai_goal *goal_list, ai_info *ai
if ( purge_goal->target_name == NULL )
continue;

// goals operating on ship classes are handled slightly differently
// goals operating on ship classes and ship types 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;
}
else if (purge_ai_mode == AI_GOAL_CHASE_SHIP_TYPE) {
// Get the ship type of the ship we're concerned about
ship_type = Ship_info[Ships[ship_index].ship_info_index].class_type;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of declaring ship_type at the top of the function, declare it here, at the beginning of the line where it is used.


// If the ship type is invalid, we can't match it, so continue
if (ship_type < 0)
continue;

// Check if the target name of the purge goal matches the ship type name
if (stricmp(purge_goal->target_name, Ship_types[ship_type].name) != 0)
continue;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the blank line here

// 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.
Expand Down Expand Up @@ -1092,6 +1107,7 @@ void ai_add_goal_sub_sexp( int sexp, ai_goal_type type, ai_info *aip, ai_goal *a
case OP_AI_CHASE:
case OP_AI_CHASE_WING:
case OP_AI_CHASE_SHIP_CLASS:
case OP_AI_CHASE_SHIP_TYPE:
case OP_AI_GUARD:
case OP_AI_GUARD_WING:
case OP_AI_EVADE_SHIP:
Expand Down Expand Up @@ -1123,6 +1139,8 @@ void ai_add_goal_sub_sexp( int sexp, ai_goal_type type, ai_info *aip, ai_goal *a
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_CHASE_SHIP_TYPE) {
aigp->ai_mode = AI_GOAL_CHASE_SHIP_TYPE;
} else if ( op == OP_AI_IGNORE ) {
aigp->ai_mode = AI_GOAL_IGNORE;
} else if ( op == OP_AI_IGNORE_NEW ) {
Expand Down Expand Up @@ -1167,7 +1185,7 @@ void ai_add_goal_sub_sexp( int sexp, ai_goal_type type, ai_info *aip, ai_goal *a
}

// 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
if (op == OP_AI_CHASE || op == OP_AI_CHASE_WING || op == OP_AI_CHASE_SHIP_CLASS || op == OP_AI_CHASE_SHIP_TYPE
|| op == OP_AI_DISABLE_SHIP || op == OP_AI_DISABLE_SHIP_TACTICAL || op == OP_AI_DISARM_SHIP || op == OP_AI_DISARM_SHIP_TACTICAL ) {
if (is_sexp_true(CDDDR(node)))
aigp->flags.set(AI::Goal_Flags::Target_own_team);
Expand All @@ -1192,6 +1210,7 @@ void ai_add_goal_sub_sexp( int sexp, ai_goal_type type, ai_info *aip, ai_goal *a
if (op == OP_AI_CHASE ||
op == OP_AI_CHASE_WING ||
op == OP_AI_CHASE_SHIP_CLASS ||
op == OP_AI_CHASE_SHIP_TYPE ||
op == OP_AI_DISABLE_SHIP ||
op == OP_AI_DISABLE_SHIP_TACTICAL ||
op == OP_AI_DISARM_SHIP ||
Expand Down Expand Up @@ -1378,6 +1397,10 @@ int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp, bool &remove_more )
priority = eval_priority_et_seq(CDDR(node));
goalmode = AI_GOAL_CHASE_SHIP_CLASS;
break;
case OP_AI_CHASE_SHIP_TYPE:
priority = eval_priority_et_seq(CDDR(node));
goalmode = AI_GOAL_CHASE_SHIP_TYPE;
break;
case OP_AI_EVADE_SHIP:
priority = eval_priority_et_seq(CDDR(node));
goalmode = AI_GOAL_EVADE_SHIP;
Expand Down Expand Up @@ -1688,6 +1711,19 @@ ai_achievability ai_mission_goal_achievable( int objnum, ai_goal *aigp )
return ai_achievability::NOT_KNOWN;
}

if (aigp->ai_mode == AI_GOAL_CHASE_SHIP_TYPE) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a comment above this line: // and similarly for chasing all ships of a certain ship type

for (auto so : list_range(&Ship_obj_list)) {
auto type_objp = &Objects[so->objnum];
if (type_objp->flags[Object::Object_Flags::Should_be_dead])
continue;
int class_type = Ship_info[Ships[type_objp->instance].ship_info_index].class_type;
if ((type_objp->type == OBJ_SHIP) && class_type >= 0 &&
Comment on lines +1719 to +1720
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not possible to get the class_type if the object is not a ship. So, you'll need to separate the if() condition. Check for OBJ_SHIP first, then get the class type, then check the class type.

!strcmp(aigp->target_name, Ship_types[class_type].name)) {
return ai_achievability::ACHIEVABLE;
}
}
return ai_achievability::NOT_KNOWN;
}

return_val = ai_achievability::SATISFIED;

Expand Down Expand Up @@ -2521,10 +2557,20 @@ void ai_process_mission_orders( int objnum, ai_info *aip )

// 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 );
{
int ship_info_index = ship_info_lookup(current_goal->target_name);
Assertion(ship_info_index >= 0, "The target of AI_GOAL_CHASE_SHIP_CLASS must refer to a valid ship class!");
ai_attack_object(objp, nullptr, ship_info_index);
break;
}

case AI_GOAL_CHASE_SHIP_TYPE:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a comment above this line: // similarly for chase-ship-type

{
int class_type = ship_type_name_lookup(current_goal->target_name);
Assertion(class_type >= 0, "The target of AI_GOAL_CHASE_SHIP_TYPE must refer to a valid ship type!");
ai_attack_object(objp, nullptr, -1, class_type);
break;
}

case AI_GOAL_WARP: {
mission_do_departure( objp, true );
Expand Down
3 changes: 2 additions & 1 deletion code/ai/aigoals.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ enum ai_goal_mode : uint8_t
AI_GOAL_FLY_TO_SHIP,
AI_GOAL_IGNORE_NEW,
AI_GOAL_CHASE_SHIP_CLASS,
AI_GOAL_CHASE_SHIP_TYPE,
AI_GOAL_PLAY_DEAD_PERSISTENT,
AI_GOAL_LUA,
AI_GOAL_DISARM_SHIP_TACTICAL,
Expand All @@ -103,7 +104,7 @@ inline bool ai_goal_is_disable_or_disarm(ai_goal_mode ai_mode)
}
inline bool ai_goal_is_specific_chase(ai_goal_mode ai_mode)
{
return ai_mode == AI_GOAL_CHASE || ai_mode == AI_GOAL_CHASE_WING || ai_mode == AI_GOAL_CHASE_SHIP_CLASS;
return ai_mode == AI_GOAL_CHASE || ai_mode == AI_GOAL_CHASE_WING || ai_mode == AI_GOAL_CHASE_SHIP_CLASS || ai_mode == AI_GOAL_CHASE_SHIP_TYPE;
}

enum class ai_achievability { ACHIEVABLE, NOT_ACHIEVABLE, NOT_KNOWN, SATISFIED };
Expand Down
21 changes: 21 additions & 0 deletions code/parse/sexp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,7 @@ SCP_vector<sexp_oper> Operators = {
{ "ai-chase", OP_AI_CHASE, 2, 4, SEXP_GOAL_OPERATOR, },
{ "ai-chase-wing", OP_AI_CHASE_WING, 2, 4, SEXP_GOAL_OPERATOR, },
{ "ai-chase-ship-class", OP_AI_CHASE_SHIP_CLASS, 2, 4, SEXP_GOAL_OPERATOR, },
{ "ai-chase-ship-type", OP_AI_CHASE_SHIP_TYPE, 2, 4, SEXP_GOAL_OPERATOR, },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can put your username in a comment at the end of this line, like other sexp authors do

{ "ai-chase-any", OP_AI_CHASE_ANY, 1, 2, SEXP_GOAL_OPERATOR, },
{ "ai-guard", OP_AI_GUARD, 2, 3, SEXP_GOAL_OPERATOR, },
{ "ai-guard-wing", OP_AI_GUARD_WING, 2, 3, SEXP_GOAL_OPERATOR, },
Expand Down Expand Up @@ -907,6 +908,7 @@ sexp_ai_goal_link Sexp_ai_goal_links[] = {
{ AI_GOAL_CHASE, OP_AI_CHASE },
{ AI_GOAL_CHASE_WING, OP_AI_CHASE_WING },
{ AI_GOAL_CHASE_SHIP_CLASS, OP_AI_CHASE_SHIP_CLASS },
{ AI_GOAL_CHASE_SHIP_TYPE, OP_AI_CHASE_SHIP_TYPE},
{ AI_GOAL_CHASE_ANY, OP_AI_CHASE_ANY },
{ AI_GOAL_DOCK, OP_AI_DOCK },
{ AI_GOAL_UNDOCK, OP_AI_UNDOCK },
Expand Down Expand Up @@ -31418,6 +31420,7 @@ int query_operator_return_type(int op)
case OP_AI_CHASE:
case OP_AI_CHASE_WING:
case OP_AI_CHASE_SHIP_CLASS:
case OP_AI_CHASE_SHIP_TYPE:
case OP_AI_CHASE_ANY:
case OP_AI_DOCK:
case OP_AI_UNDOCK:
Expand Down Expand Up @@ -32428,6 +32431,14 @@ int query_operator_argument_type(int op, int argnum)
else
return OPF_BOOL;

case OP_AI_CHASE_SHIP_TYPE:
if (argnum == 0)
return OPF_SHIP_TYPE;
else if (argnum == 1)
return OPF_POSITIVE;
else
return OPF_BOOL;

case OP_AI_GUARD:
if (argnum == 0)
return OPF_SHIP_WING;
Expand Down Expand Up @@ -36590,6 +36601,7 @@ int get_category(int op_id)
case OP_AI_IGNORE_NEW:
case OP_AI_FORM_ON_WING:
case OP_AI_CHASE_SHIP_CLASS:
case OP_AI_CHASE_SHIP_TYPE:
case OP_AI_PLAY_DEAD_PERSISTENT:
case OP_AI_FLY_TO_SHIP:
case OP_AI_REARM_REPAIR:
Expand Down Expand Up @@ -39069,6 +39081,15 @@ SCP_vector<sexp_help_struct> Sexp_help = {
"\t4 (optional):\tWhether to afterburn as hard as possible to the target; defaults to false."
},

{ OP_AI_CHASE_SHIP_TYPE, "Ai-chase ship type (Ship goal)\r\n"
"\tCauses the specified ship to chase and attack a target ship type.\r\n\r\n"
"Takes 2 to 4 arguments...\r\n"
"\t1:\tName of ship type to chase.\r\n"
"\t2:\tGoal priority (number between 0 and 200. Player orders have a priority of 90-100).\r\n"
"\t3 (optional):\tWhether to attack the target even if it is on the same team; defaults to false.\r\n"
"\t4 (optional):\tWhether to afterburn as hard as possible to the target; defaults to false."
},

{ OP_AI_CHASE_ANY, "Ai-chase-any (Ship goal)\r\n"
"\tCauses the specified ship to chase and attack any ship on the opposite team.\r\n\r\n"
"Takes 1 or 2 arguments...\r\n"
Expand Down
Loading
Loading