Skip to content
Open
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
29 changes: 19 additions & 10 deletions code/ai/aicode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -323,12 +323,17 @@ void ai_cleanup_dock_mode_objective(object *objp);
// the "autopilot"
object *Autopilot_flight_leader = NULL;

static inline float ai_guard_threshold(const object* guarded_objp, float threshold)
static inline float ai_guard_threshold(const object* guarded_objp, float threshold, const ship* guarder_ship)
{
if (guarded_objp != nullptr && guarded_objp->type == OBJ_SHIP && guarded_objp->instance >= 0) {
const float configured = Ships[guarded_objp->instance].max_guard_radius;
if (configured > 0.0f) {
return configured;
for (const auto& range_entry : guarder_ship->max_guard_ranges) {
const int regester = ship_registry_get_index(Ships[guarded_objp->instance].ship_name);
if (regester == range_entry.shipnum) {
const float configured = range_entry.range;
if (configured > 0.0f) {
return configured;
}
}
}
}

Expand Down Expand Up @@ -5161,10 +5166,13 @@ int maybe_resume_previous_mode(object *objp, ai_info *aip)

// If guarding ship is far away from guardee and enemy is far away from guardee,
// then stop chasing and resume guarding.
if (dist > ai_guard_threshold(guard_objp, (MAX_GUARD_DIST + guard_objp->radius) * 6))
if (dist > ai_guard_threshold(guard_objp, (MAX_GUARD_DIST + guard_objp->radius) * 6, &Ships[aip->shipnum]))
{
if ((En_objp != NULL) && (En_objp->type == OBJ_SHIP)) {
if (vm_vec_dist_quick(&guard_objp->pos, &En_objp->pos) > ai_guard_threshold(guard_objp, (MAX_GUARD_DIST + guard_objp->radius) * 6)) {
if (vm_vec_dist_quick(&guard_objp->pos, &En_objp->pos) >
ai_guard_threshold(guard_objp,
(MAX_GUARD_DIST + guard_objp->radius) * 6,
&Ships[aip->shipnum])) {
Assert(aip->previous_mode == AIM_GUARD);
aip->mode = aip->previous_mode;
aip->submode = AIS_GUARD_PATROL;
Expand Down Expand Up @@ -10637,7 +10645,7 @@ int ai_guard_find_nearby_bomb(object *guarding_objp, object *guarded_objp)

dist = vm_vec_dist_quick(&bomb_objp->pos, &guarded_objp->pos);

if (dist < ai_guard_threshold(guarded_objp, (MAX_GUARD_DIST + guarded_objp->radius) * 3)) {
if (dist < ai_guard_threshold(guarded_objp, (MAX_GUARD_DIST + guarded_objp->radius) * 3, &Ships[guarding_objp->instance])) {
dist_to_guarding_obj = vm_vec_dist_quick(&bomb_objp->pos, &guarding_objp->pos);
if ( dist_to_guarding_obj < closest_dist_to_guarding_obj ) {
closest_dist_to_guarding_obj = dist_to_guarding_obj;
Expand Down Expand Up @@ -10688,10 +10696,11 @@ void ai_guard_find_nearby_ship(object *guarding_objp, object *guarded_objp)
if (Ship_info[eshipp->ship_info_index].class_type >= 0 && (Ship_types[Ship_info[eshipp->ship_info_index].class_type].flags[Ship::Type_Info_Flags::AI_guards_attack]))
{
dist = vm_vec_dist_quick(&enemy_objp->pos, &guarded_objp->pos);
if (dist < ai_guard_threshold(guarded_objp, (MAX_GUARD_DIST + guarded_objp->radius) * 3))
if (dist <
ai_guard_threshold(guarded_objp, (MAX_GUARD_DIST + guarded_objp->radius) * 3, guarding_shipp))
{
guard_object_was_hit(guarding_objp, enemy_objp);
} else if ((dist < ai_guard_threshold(guarded_objp, 3000.0f)) &&
} else if ((dist < ai_guard_threshold(guarded_objp, 3000.0f, guarding_shipp)) &&
(Ai_info[eshipp->ai_index].target_objnum == guarding_aip->guard_objnum))
{
guard_object_was_hit(guarding_objp, enemy_objp);
Expand Down Expand Up @@ -10719,7 +10728,7 @@ void ai_guard_find_nearby_asteroid(object *guarding_objp, object *guarded_objp)
if ( asteroid_objp->type == OBJ_ASTEROID ) {
// Attack asteroid if near guarded ship
dist = vm_vec_dist_quick(&asteroid_objp->pos, &guarded_objp->pos);
if (dist < ai_guard_threshold(guarded_objp, (MAX_GUARD_DIST + guarded_objp->radius) * 2)) {
if (dist < ai_guard_threshold(guarded_objp, (MAX_GUARD_DIST + guarded_objp->radius) * 2, &Ships[guarding_objp->instance])) {
dist_to_self = vm_vec_dist_quick(&asteroid_objp->pos, &guarding_objp->pos);
if ( OBJ_INDEX(guarded_objp) == asteroid_collide_objnum(asteroid_objp) ) {
if( dist_to_self < closest_danger_asteroid_dist ) {
Expand Down
1 change: 1 addition & 0 deletions code/missioneditor/sexp_tree_model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1248,6 +1248,7 @@ bool SexpTreeModel::is_operator_hidden(int op_value)
case OP_SET_DEBRIS_FIELD:
case OP_NEBULA_TOGGLE_POOF:
case OP_NEBULA_FADE_POOF:
case OP_SET_GUARD_RANGE:
return true;
default:
return false;
Expand Down
57 changes: 40 additions & 17 deletions code/parse/sexp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19537,27 +19537,47 @@ void sexp_ship_guardian_threshold(int node)
ship_entry->shipp()->ship_guardian_threshold = threshold;
}
}

// MjnMixael
void sexp_set_guard_range(int node)
{
int range, n = node;
bool is_nan, is_nan_forever;

auto ship_entry = eval_ship(n);
if (!ship_entry || !ship_entry->has_shipp()) {
return;
}
n = CDR(n);
range = eval_num(n, is_nan, is_nan_forever);
if (is_nan || is_nan_forever)
if (is_nan || is_nan_forever) {
return;
}
auto true_range = static_cast<float>(range);
n = CDR(n);

for (; n != -1; n = CDR(n)) {
auto ship_entry = eval_ship(n);
if (!ship_entry || !ship_entry->has_shipp()) {
object_ship_wing_point_team oswpt;
eval_object_ship_wing_point_team(&oswpt, n);
if (oswpt.type == OSWPT_TYPE_SHIP) {
auto shipp = oswpt.shipp();
set_guard_range_ship(true_range, ship_entry, shipp);
} else if (oswpt.type == OSWPT_TYPE_WING) {
for (int i = 0; i < oswpt.wingp()->current_count; ++i) {
auto shipp = &Ships[oswpt.wingp()->ship_index[i]];
set_guard_range_ship(true_range, ship_entry, shipp);
}
} else if (oswpt.type == OSWPT_TYPE_WHOLE_TEAM) {
ship_obj* so;
for (so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so)) {
if (Objects[so->objnum].flags[Object::Object_Flags::Should_be_dead])
continue;

auto shipp = &Ships[Objects[so->objnum].instance];
if (shipp->team == oswpt.team) {
set_guard_range_ship(true_range, ship_entry, shipp);
}
}
} else {
continue;
}

// Intentionally no lower bound validation beyond disabling at <= 0.
// Mission authors may choose very small positive values for highly restrictive escort behavior.
ship_entry->shipp()->max_guard_radius = (range > 0) ? static_cast<float>(range) : -1.0f;
}
}

Expand Down Expand Up @@ -28982,7 +29002,6 @@ int eval_sexp(int cur_node, int referenced_node)
sexp_set_guard_range(node);
sexp_val = SEXP_TRUE;
break;

case OP_SHIP_SUBSYS_TARGETABLE:
sexp_ship_deal_with_subsystem_flag(cur_node, node, Ship::Subsystem_Flags::Untargetable, true, false);
sexp_val = SEXP_TRUE;
Expand Down Expand Up @@ -32457,9 +32476,11 @@ int query_operator_argument_type(int op_index, int argnum)

case OP_SET_GUARD_RANGE:
if (argnum == 0)
return OPF_SHIP;
else if (argnum == 1)
return OPF_NUMBER;
else
return OPF_SHIP;
return OPF_SHIP_WING_WHOLETEAM;

case OP_SHIP_SUBSYS_TARGETABLE:
case OP_SHIP_SUBSYS_UNTARGETABLE:
Expand Down Expand Up @@ -40613,12 +40634,14 @@ SCP_vector<sexp_help_struct> Sexp_help = {

// MjnMixael
{ OP_SET_GUARD_RANGE, "set-guard-range\r\n"
"\tSets the max range in meters at which any ships guarding this ship will engage with threats.\r\n"
"\tLimits the range that selected ships or wings can move when guarding a specific ship\r\n"
"This range will override the default dynamic range behavior for ships obeying a guard order.\r\n"
"If the value is <= 0, regular dynamic guard range behavior will resume. Positive values are used as is with no size validation based on ship class.\r\n\r\n"
"Takes 2 or more arguments...\r\n"
"\t1:\tGuard range cap in meters (<= 0 disables cap).\r\n"
"\t2+:\tShip(s) to apply the cap to (ships must be in-mission)." },
"If the value is <= 0, regular dynamic guard range behavior will resume. Positive values are used as is with no size validation based on ship class.\r\n"
"Warning: Will not apply to future waves of wings or ships not currently in mission.\r\n\r\n"
"Takes 3 or more arguments...\r\n"
"\t1:\tShip the escorts won't leave the range of if guarding (Ship must be in mission)\r\n"
"\t2:\tGuard range cap in meters (<= 0 disables cap)\r\n"
"\t3+:\tEscort ships and wings that the limit appies too" },

// Goober5000
{ OP_SHIP_STEALTHY, "ship-stealthy\r\n"
Expand Down
31 changes: 24 additions & 7 deletions code/scripting/api/objs/ship.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1162,22 +1162,39 @@ ADE_VIRTVAR(Orders, l_Ship, "shiporders", "Array of ship orders", "shiporders",
return ade_set_args(L, "o", l_ShipOrders.Set(object_h(objh->objp())));
}

ADE_VIRTVAR(MaxGuardRadius, l_Ship, "number", "Sets the max range in meters at which any ships guarding this ship will engage with threats. If the value is <= 0, regular dynamic guard range behavior will resume.", "number", "Max range in meters, or 0 if handle is invalid")
ADE_VIRTVAR(MaxGuardRadius,
l_Ship,
"number",
"Sets the max range in meters at which any ships guarding this ship will engage with threats. If the value is <= "
"0, regular dynamic guard range behavior will resume.", nullptr,
nullptr)
{
object_h *objh;
float new_max_guard_radius = -1;
if (!ade_get_args(L, "o|f", l_Ship.GetPtr(&objh), &new_max_guard_radius))
return ade_set_error(L, "f", 0.0f);
return ADE_RETURN_NIL;

if(!objh->isValid())
return ade_set_error(L, "f", 0.0f);
return ADE_RETURN_NIL;

ship *shipp = &Ships[objh->objp()->instance];
auto ship_entry = ship_registry_get(shipp->ship_name);
if (!ship_entry)
return ADE_RETURN_NIL;
if (ADE_SETTING_VAR) {
// Apply to every ship on this ship's team, mirroring sexp_set_guard_range's
// "all potential guardians" behavior.
for (ship_obj* so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so)) {
if (Objects[so->objnum].flags[Object::Object_Flags::Should_be_dead])
continue;

ship* guarding_shipp = &Ships[Objects[so->objnum].instance];
if (guarding_shipp->team == shipp->team)
set_guard_range_ship(new_max_guard_radius, ship_entry, guarding_shipp);
}
}

if (ADE_SETTING_VAR)
shipp->max_guard_radius = new_max_guard_radius;

return ade_set_args(L, "f", shipp->max_guard_radius);
return ADE_RETURN_NIL;
}

ADE_VIRTVAR(WaypointSpeedCap, l_Ship, "number", "Waypoint speed cap", "number", "The limit on the ship's speed for traversing waypoints. -1 indicates no speed cap. 0 will be returned if handle is invalid.")
Expand Down
30 changes: 28 additions & 2 deletions code/ship/ship.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7249,7 +7249,7 @@ void ship::clear()
ship_max_hull_strength = 0.0f;

ship_guardian_threshold = 0;
max_guard_radius = -1.0f;
max_guard_ranges.clear();

ship_name[0] = 0;
display_name.clear();
Expand Down Expand Up @@ -21755,7 +21755,33 @@ int get_nearest_bbox_point(const object *ship_objp, const vec3d *start, vec3d *b

return inside;
}

void set_guard_range_ship(float range, const ship_registry_entry* ship_entry, ship* shipp)
{
bool done = false;
if (!range <= 0) {
for (auto& exist : shipp->max_guard_ranges) {
if (exist.shipnum == ship_registry_get_index(ship_entry->shipp()->ship_name)) {
if (range > 0) {
exist.range = range;
} else {
exist.range = -1.0f;
}
done = true;
break;
}
}
if (!done) {
auto item = guard_range_entry(range, ship_registry_get_index(ship_entry->shipp()->ship_name));
shipp->max_guard_ranges.push_back(item);
}
} else {
for (auto& exist : shipp->max_guard_ranges) {
if (exist.shipnum == ship_registry_get_index(ship_entry->shipp()->ship_name)) {
exist.range = -1.0f;
}
}
}
}
void ship_set_thruster_info(mst_info *mst, object *obj, ship *shipp, ship_info *sip)
{
mst->length = obj->phys_info.linear_thrust;
Expand Down
15 changes: 10 additions & 5 deletions code/ship/ship.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ class WarpEffect;

// Part of the player died system.
extern vec3d Original_vec_to_deader;

// States for player death sequence, stuffed in Player_died_state.
#define PDS_NONE 1
#define PDS_DIED 2
Expand Down Expand Up @@ -254,7 +253,8 @@ class ArmorType

extern SCP_vector<ArmorType> Armor_types;

//**************************************************************
void set_guard_range_ship(float range, const ship_registry_entry* ship_entry, ship* shipp);
//**************************************************************
//WMC - Damage type handling code

typedef struct DamageTypeStruct
Expand Down Expand Up @@ -336,7 +336,11 @@ typedef struct lock_info {
float lock_gauge_time_elapsed;
float lock_anim_time_elapsed;
} lock_info;

struct guard_range_entry {
float range;
int shipnum;
guard_range_entry(float _range, int _shipnum) : range(_range), shipnum(_shipnum) {}
};
// structure definition for a linked list of subsystems for a ship. Each subsystem has a pointer
// to the static data for the subsystem. The obj_subsystem data is defined and read in the model
// code. Other dynamic data (such as current_hits) should remain in this structure.
Expand Down Expand Up @@ -623,7 +627,9 @@ class ship
float max_weapon_regen_per_second; // wookieejedi - make this a ship object variable

int ship_guardian_threshold; // Goober5000 - now also determines whether ship is guardian'd
float max_guard_radius; // Optional clamp for guard engagement/resume ranges; <= 0 means unused

SCP_vector<guard_range_entry>
max_guard_ranges; // Optional clamp for guard engagement/resume ranges; <= 0 means unused


char ship_name[NAME_LENGTH];
Expand Down Expand Up @@ -877,7 +883,6 @@ struct ai_target_priority {
flagset<Ship::Info_Flags> sif_flags;
flagset<Weapon::Info_Flags> wif_flags;
};

extern SCP_vector <ai_target_priority> Ai_tp_list;

void parse_ai_target_priorities();
Expand Down
Loading