From 6d38a9915d52278ab156d35bc2ef768261b6eaa1 Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Tue, 14 Jul 2026 14:01:42 +0100 Subject: [PATCH 1/4] New guard-range sexp New guard-range sexp which allows different ranges for different guarders --- code/ai/aicode.cpp | 28 +++++---- code/missioneditor/sexp_tree_model.cpp | 1 + code/parse/sexp.cpp | 80 +++++++++++++++++++++++++- code/parse/sexp.h | 3 +- code/scripting/api/objs/ship.cpp | 31 +++++++--- code/ship/ship.cpp | 21 ++++++- code/ship/ship.h | 15 +++-- 7 files changed, 152 insertions(+), 27 deletions(-) diff --git a/code/ai/aicode.cpp b/code/ai/aicode.cpp index f171da4e701..753f2a9a929 100644 --- a/code/ai/aicode.cpp +++ b/code/ai/aicode.cpp @@ -323,12 +323,16 @@ 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, ship* guarder_objp) { 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 (auto range_entry : guarder_objp->max_guard_ranges) { + if (Ships[guarded_objp->instance].ship_list_index == range_entry.shipnum) { + const float configured = range_entry.range; + if (configured > 0.0f) { + return configured; + } + } } } @@ -5161,10 +5165,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; @@ -10637,7 +10644,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; @@ -10688,10 +10695,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); @@ -10719,7 +10727,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 ) { diff --git a/code/missioneditor/sexp_tree_model.cpp b/code/missioneditor/sexp_tree_model.cpp index 7b489d765c9..c5207ba8dbd 100644 --- a/code/missioneditor/sexp_tree_model.cpp +++ b/code/missioneditor/sexp_tree_model.cpp @@ -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; diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index 539fa03406c..bd96d08e386 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -455,6 +455,7 @@ SCP_vector Operators = { { "validate-general-orders", OP_VALIDATE_GENERAL_ORDERS, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // MjnMixael { "cap-waypoint-speed", OP_CAP_WAYPOINT_SPEED, 2, 2, SEXP_ACTION_OPERATOR, }, { "set-wing-formation", OP_SET_WING_FORMATION, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 + { "set-guard-range-new", OP_SET_GUARDER_RANGE, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // The Force //Ship Status Sub-Category { "protect-ship", OP_PROTECT_SHIP, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, @@ -19537,13 +19538,53 @@ void sexp_ship_guardian_threshold(int node) ship_entry->shipp()->ship_guardian_threshold = threshold; } } +void sexp_set_guarder_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) { + return; + } + n = CDR(n); + for (; n != -1; n = CDR(n)) { + object_ship_wing_point_team oswpt; + eval_object_ship_wing_point_team(&oswpt, n); + if (oswpt.type == OSWPT_TYPE_SHIP) { + auto shipp = oswpt.shipp(); + set_guard_range_ship(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(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(range, ship_entry, shipp); + } + } + } else { + continue; + } + } +} // MjnMixael void sexp_set_guard_range(int node) { int range, n = node; bool is_nan, is_nan_forever; - range = eval_num(n, is_nan, is_nan_forever); if (is_nan || is_nan_forever) return; @@ -19557,7 +19598,16 @@ void sexp_set_guard_range(int node) // 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(range) : -1.0f; + 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 == ship_entry->shipp()->team) { + set_guard_range_ship(range, ship_entry, shipp); + } + } } } @@ -28982,7 +29032,10 @@ int eval_sexp(int cur_node, int referenced_node) sexp_set_guard_range(node); sexp_val = SEXP_TRUE; break; - + case OP_SET_GUARDER_RANGE: + sexp_set_guarder_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; @@ -31740,6 +31793,7 @@ int query_operator_return_type(int op) case OP_SHIP_GUARDIAN_THRESHOLD: case OP_SHIP_SUBSYS_GUARDIAN_THRESHOLD: case OP_SET_GUARD_RANGE: + case OP_SET_GUARDER_RANGE: case OP_SHIP_VANISH: case OP_PROP_VANISH: case OP_DESTROY_INSTANTLY: @@ -32461,6 +32515,14 @@ int query_operator_argument_type(int op_index, int argnum) else return OPF_SHIP; + case OP_SET_GUARDER_RANGE: + if (argnum == 0) + return OPF_SHIP; + else if (argnum == 1) + return OPF_NUMBER; + else + return OPF_SHIP_WING_WHOLETEAM; + case OP_SHIP_SUBSYS_TARGETABLE: case OP_SHIP_SUBSYS_UNTARGETABLE: if (argnum == 0) @@ -37053,6 +37115,7 @@ int get_category(int op_id) case OP_SHIP_GUARDIAN_THRESHOLD: case OP_SHIP_SUBSYS_GUARDIAN_THRESHOLD: case OP_SET_GUARD_RANGE: + case OP_SET_GUARDER_RANGE: case OP_SET_SKYBOX_MODEL: case OP_SHIP_CREATE: case OP_PROP_CREATE: @@ -37409,6 +37472,7 @@ int get_subcategory(int op_id) case OP_SET_ORDER_ALLOWED_TARGET: case OP_ENABLE_GENERAL_ORDERS: case OP_VALIDATE_GENERAL_ORDERS: + case OP_SET_GUARDER_RANGE: return CHANGE_SUBCATEGORY_AI_CONTROL; case OP_ALTER_SHIP_FLAG: @@ -41308,6 +41372,16 @@ SCP_vector Sexp_help = { "\tRest: Wing that should fly this formation (wing does not need to be in-mission)\r\n" }, + // The Force + { OP_SET_GUARDER_RANGE, "set-guard-range-new\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 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 wing that the limit appies too" }, + { OP_TURRET_TAGGED_ONLY_ALL, "turret-tagged-only\r\n" "\tMakes turrets target and hence fire strictly at tagged objects\r\n" "\tAll: Ship name (ship must be in-mission)\r\n" diff --git a/code/parse/sexp.h b/code/parse/sexp.h index 68299082412..91d5ab2872c 100644 --- a/code/parse/sexp.h +++ b/code/parse/sexp.h @@ -699,7 +699,8 @@ enum : int { OP_JUMP_NODE_HIDE_JUMPNODE, // WMC OP_SHIP_GUARDIAN_THRESHOLD, // Goober5000 OP_SHIP_SUBSYS_GUARDIAN_THRESHOLD, // Goober5000 - OP_SET_GUARD_RANGE, //MjnMixael + OP_SET_GUARD_RANGE, //MjnMixael // Deprecated 10/07/2026 + OP_SET_GUARDER_RANGE, //The Force OP_SET_SKYBOX_MODEL, // taylor OP_SHIP_CREATE, OP_PROP_CREATE, // MjnMixael diff --git a/code/scripting/api/objs/ship.cpp b/code/scripting/api/objs/ship.cpp index 537a3f8c150..a979a93fe60 100644 --- a/code/scripting/api/objs/ship.cpp +++ b/code/scripting/api/objs/ship.cpp @@ -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.") diff --git a/code/ship/ship.cpp b/code/ship/ship.cpp index a18ada69b4c..10003cda068 100644 --- a/code/ship/ship.cpp +++ b/code/ship/ship.cpp @@ -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(); @@ -20851,6 +20851,25 @@ int damage_type_add(const char *name) return (int)(Damage_types.size() - 1); } +void set_guard_range_ship(float range, const ship_registry_entry* ship_entry, ship* shipp) { + bool done = false; + for (auto exist : shipp->max_guard_ranges) { + if (exist.shipnum == ship_entry->shipnum) { + if (range > 0) { + exist.range = static_cast(range); + } else { + exist.range = -1.0f; + } + done = true; + break; + } + } + if (!done) { + auto item = max_guard_radius(static_cast(range), ship_entry->shipp()->ship_list_index); + shipp->max_guard_ranges.push_back(item); + } +} + void ArmorDamageType::clear() { DamageTypeIndex = -1; diff --git a/code/ship/ship.h b/code/ship/ship.h index 636084b9186..4a6b973d869 100644 --- a/code/ship/ship.h +++ b/code/ship/ship.h @@ -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 @@ -254,7 +253,8 @@ class ArmorType extern SCP_vector 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 @@ -336,7 +336,11 @@ typedef struct lock_info { float lock_gauge_time_elapsed; float lock_anim_time_elapsed; } lock_info; - +struct max_guard_radius { + float range; + int shipnum; + max_guard_radius(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. @@ -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 + max_guard_ranges; // Optional clamp for guard engagement/resume ranges; <= 0 means unused char ship_name[NAME_LENGTH]; @@ -877,7 +883,6 @@ struct ai_target_priority { flagset sif_flags; flagset wif_flags; }; - extern SCP_vector Ai_tp_list; void parse_ai_target_priorities(); From c56edc3e6d224054754b326221b6e3c38274348f Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Tue, 14 Jul 2026 14:48:45 +0100 Subject: [PATCH 2/4] cast value --- code/parse/sexp.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index bd96d08e386..e1b8cbacf75 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -19551,18 +19551,19 @@ void sexp_set_guarder_range(int node) if (is_nan || is_nan_forever) { return; } + float true_range = static_cast(range); n = CDR(n); for (; n != -1; n = CDR(n)) { object_ship_wing_point_team oswpt; eval_object_ship_wing_point_team(&oswpt, n); if (oswpt.type == OSWPT_TYPE_SHIP) { auto shipp = oswpt.shipp(); - set_guard_range_ship(range, ship_entry, 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(range, ship_entry, shipp); + set_guard_range_ship(true_range, ship_entry, shipp); } } else if (oswpt.type == OSWPT_TYPE_WHOLE_TEAM) { ship_obj* so; @@ -19572,7 +19573,7 @@ void sexp_set_guarder_range(int node) auto shipp = &Ships[Objects[so->objnum].instance]; if (shipp->team == oswpt.team) { - set_guard_range_ship(range, ship_entry, shipp); + set_guard_range_ship(true_range, ship_entry, shipp); } } } else { @@ -19589,7 +19590,7 @@ void sexp_set_guard_range(int node) if (is_nan || is_nan_forever) return; n = CDR(n); - + float true_range = static_cast(range); for (; n != -1; n = CDR(n)) { auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) { @@ -19605,7 +19606,7 @@ void sexp_set_guard_range(int node) auto shipp = &Ships[Objects[so->objnum].instance]; if (shipp->team == ship_entry->shipp()->team) { - set_guard_range_ship(range, ship_entry, shipp); + set_guard_range_ship(true_range, ship_entry, shipp); } } } From 1844d19ef1c286827f372dddee85cea885f96c13 Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Tue, 14 Jul 2026 16:45:55 +0100 Subject: [PATCH 3/4] Clang --- code/parse/sexp.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index e1b8cbacf75..efbb0b8699f 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -19551,7 +19551,7 @@ void sexp_set_guarder_range(int node) if (is_nan || is_nan_forever) { return; } - float true_range = static_cast(range); + auto true_range = static_cast(range); n = CDR(n); for (; n != -1; n = CDR(n)) { object_ship_wing_point_team oswpt; @@ -19590,7 +19590,7 @@ void sexp_set_guard_range(int node) if (is_nan || is_nan_forever) return; n = CDR(n); - float true_range = static_cast(range); + auto true_range = static_cast(range); for (; n != -1; n = CDR(n)) { auto ship_entry = eval_ship(n); if (!ship_entry || !ship_entry->has_shipp()) { From 777f620a537c901fbb5ed7f211911e52dbe04559 Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Sat, 18 Jul 2026 10:05:00 +0100 Subject: [PATCH 4/4] bugfixes --- code/ai/aicode.cpp | 7 +++-- code/parse/sexp.cpp | 72 +++++++-------------------------------------- code/parse/sexp.h | 3 +- code/ship/ship.cpp | 47 ++++++++++++++++------------- code/ship/ship.h | 6 ++-- 5 files changed, 45 insertions(+), 90 deletions(-) diff --git a/code/ai/aicode.cpp b/code/ai/aicode.cpp index 753f2a9a929..817ea257ae8 100644 --- a/code/ai/aicode.cpp +++ b/code/ai/aicode.cpp @@ -323,11 +323,12 @@ 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, ship* guarder_objp) +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) { - for (auto range_entry : guarder_objp->max_guard_ranges) { - if (Ships[guarded_objp->instance].ship_list_index == range_entry.shipnum) { + 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; diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index efbb0b8699f..38ff21b69fe 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -455,7 +455,6 @@ SCP_vector Operators = { { "validate-general-orders", OP_VALIDATE_GENERAL_ORDERS, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, // MjnMixael { "cap-waypoint-speed", OP_CAP_WAYPOINT_SPEED, 2, 2, SEXP_ACTION_OPERATOR, }, { "set-wing-formation", OP_SET_WING_FORMATION, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // Goober5000 - { "set-guard-range-new", OP_SET_GUARDER_RANGE, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // The Force //Ship Status Sub-Category { "protect-ship", OP_PROTECT_SHIP, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, @@ -19538,7 +19537,8 @@ void sexp_ship_guardian_threshold(int node) ship_entry->shipp()->ship_guardian_threshold = threshold; } } -void sexp_set_guarder_range(int node) +// MjnMixael +void sexp_set_guard_range(int node) { int range, n = node; bool is_nan, is_nan_forever; @@ -19560,8 +19560,7 @@ void sexp_set_guarder_range(int node) 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) - { + 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); } @@ -19581,36 +19580,6 @@ void sexp_set_guarder_range(int node) } } } -// MjnMixael -void sexp_set_guard_range(int node) -{ - int range, n = node; - bool is_nan, is_nan_forever; - range = eval_num(n, is_nan, is_nan_forever); - if (is_nan || is_nan_forever) - return; - n = CDR(n); - auto true_range = static_cast(range); - for (; n != -1; n = CDR(n)) { - auto ship_entry = eval_ship(n); - if (!ship_entry || !ship_entry->has_shipp()) { - continue; - } - - // Intentionally no lower bound validation beyond disabling at <= 0. - // Mission authors may choose very small positive values for highly restrictive escort behavior. - 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 == ship_entry->shipp()->team) { - set_guard_range_ship(true_range, ship_entry, shipp); - } - } - } -} // Goober5000 void sexp_ship_subsys_guardian_threshold(int node) @@ -29033,10 +29002,6 @@ int eval_sexp(int cur_node, int referenced_node) sexp_set_guard_range(node); sexp_val = SEXP_TRUE; break; - case OP_SET_GUARDER_RANGE: - sexp_set_guarder_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; @@ -31794,7 +31759,6 @@ int query_operator_return_type(int op) case OP_SHIP_GUARDIAN_THRESHOLD: case OP_SHIP_SUBSYS_GUARDIAN_THRESHOLD: case OP_SET_GUARD_RANGE: - case OP_SET_GUARDER_RANGE: case OP_SHIP_VANISH: case OP_PROP_VANISH: case OP_DESTROY_INSTANTLY: @@ -32511,12 +32475,6 @@ int query_operator_argument_type(int op_index, int argnum) return OPF_SUBSYS_OR_GENERIC; case OP_SET_GUARD_RANGE: - if (argnum == 0) - return OPF_NUMBER; - else - return OPF_SHIP; - - case OP_SET_GUARDER_RANGE: if (argnum == 0) return OPF_SHIP; else if (argnum == 1) @@ -37116,7 +37074,6 @@ int get_category(int op_id) case OP_SHIP_GUARDIAN_THRESHOLD: case OP_SHIP_SUBSYS_GUARDIAN_THRESHOLD: case OP_SET_GUARD_RANGE: - case OP_SET_GUARDER_RANGE: case OP_SET_SKYBOX_MODEL: case OP_SHIP_CREATE: case OP_PROP_CREATE: @@ -37473,7 +37430,6 @@ int get_subcategory(int op_id) case OP_SET_ORDER_ALLOWED_TARGET: case OP_ENABLE_GENERAL_ORDERS: case OP_VALIDATE_GENERAL_ORDERS: - case OP_SET_GUARDER_RANGE: return CHANGE_SUBCATEGORY_AI_CONTROL; case OP_ALTER_SHIP_FLAG: @@ -40678,12 +40634,14 @@ SCP_vector 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" @@ -41373,16 +41331,6 @@ SCP_vector Sexp_help = { "\tRest: Wing that should fly this formation (wing does not need to be in-mission)\r\n" }, - // The Force - { OP_SET_GUARDER_RANGE, "set-guard-range-new\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 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 wing that the limit appies too" }, - { OP_TURRET_TAGGED_ONLY_ALL, "turret-tagged-only\r\n" "\tMakes turrets target and hence fire strictly at tagged objects\r\n" "\tAll: Ship name (ship must be in-mission)\r\n" diff --git a/code/parse/sexp.h b/code/parse/sexp.h index 91d5ab2872c..68299082412 100644 --- a/code/parse/sexp.h +++ b/code/parse/sexp.h @@ -699,8 +699,7 @@ enum : int { OP_JUMP_NODE_HIDE_JUMPNODE, // WMC OP_SHIP_GUARDIAN_THRESHOLD, // Goober5000 OP_SHIP_SUBSYS_GUARDIAN_THRESHOLD, // Goober5000 - OP_SET_GUARD_RANGE, //MjnMixael // Deprecated 10/07/2026 - OP_SET_GUARDER_RANGE, //The Force + OP_SET_GUARD_RANGE, //MjnMixael OP_SET_SKYBOX_MODEL, // taylor OP_SHIP_CREATE, OP_PROP_CREATE, // MjnMixael diff --git a/code/ship/ship.cpp b/code/ship/ship.cpp index 10003cda068..69684460dca 100644 --- a/code/ship/ship.cpp +++ b/code/ship/ship.cpp @@ -20851,25 +20851,6 @@ int damage_type_add(const char *name) return (int)(Damage_types.size() - 1); } -void set_guard_range_ship(float range, const ship_registry_entry* ship_entry, ship* shipp) { - bool done = false; - for (auto exist : shipp->max_guard_ranges) { - if (exist.shipnum == ship_entry->shipnum) { - if (range > 0) { - exist.range = static_cast(range); - } else { - exist.range = -1.0f; - } - done = true; - break; - } - } - if (!done) { - auto item = max_guard_radius(static_cast(range), ship_entry->shipp()->ship_list_index); - shipp->max_guard_ranges.push_back(item); - } -} - void ArmorDamageType::clear() { DamageTypeIndex = -1; @@ -21774,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; diff --git a/code/ship/ship.h b/code/ship/ship.h index 4a6b973d869..f5eb9bdca50 100644 --- a/code/ship/ship.h +++ b/code/ship/ship.h @@ -336,10 +336,10 @@ typedef struct lock_info { float lock_gauge_time_elapsed; float lock_anim_time_elapsed; } lock_info; -struct max_guard_radius { +struct guard_range_entry { float range; int shipnum; - max_guard_radius(float _range, int _shipnum) : range(_range), shipnum(_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 @@ -628,7 +628,7 @@ class ship int ship_guardian_threshold; // Goober5000 - now also determines whether ship is guardian'd - SCP_vector + SCP_vector max_guard_ranges; // Optional clamp for guard engagement/resume ranges; <= 0 means unused