Skip to content

Commit 33d5f7e

Browse files
committed
refactor ship anchors
Testing in FotG exposed a bug that dates back to retail. During mission parsing, arrival and departure anchors were represented by indexes into `Parse_names`. After parsing, they were converted to ship indexes -- but only in FRED. Since some functions are shared between FRED and FS2, this can produce inconsistent results, which can be hard to detect since parse indexes and ship indexes are usually the same. This refactors arrival and departure anchors to be represented by ship registry indexes in all cases. It also adds conversion functions for the benefit of FRED and QtFRED since those dialogs were designed with ship indexes in mind.
1 parent 14a900e commit 33d5f7e

18 files changed

Lines changed: 211 additions & 185 deletions

File tree

code/ai/aicode.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14060,7 +14060,7 @@ void ai_bay_depart()
1406014060

1406114061
// check if parent ship valid; if not, abort depart
1406214062
if (gameseq_get_state() != GS_STATE_LAB) {
14063-
auto anchor_ship_entry = ship_registry_get(Parse_names[Ships[Pl_objp->instance].departure_anchor]);
14063+
auto anchor_ship_entry = ship_registry_get(Ships[Pl_objp->instance].departure_anchor);
1406414064
if (!anchor_ship_entry ||
1406514065
!ship_useful_for_departure(anchor_ship_entry->shipnum, Ships[Pl_objp->instance].departure_path_mask)) {
1406614066
mprintf(("Aborting bay departure!\n"));

code/mission/missionparse.cpp

Lines changed: 84 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -4437,7 +4437,7 @@ int parse_wing_create_ships( wing *wingp, int num_to_create, bool force_create,
44374437
// (or will exist).
44384438
if ( wingp->arrival_location == ArrivalLocation::FROM_DOCK_BAY ) {
44394439
Assert( wingp->arrival_anchor >= 0 );
4440-
auto anchor_ship_entry = ship_registry_get(Parse_names[wingp->arrival_anchor]);
4440+
auto anchor_ship_entry = ship_registry_get(wingp->arrival_anchor);
44414441

44424442
// see if ship is yet to arrive. If so, then return 0 so we can evaluate again later.
44434443
if (!anchor_ship_entry || anchor_ship_entry->status == ShipStatus::NOT_YET_PRESENT)
@@ -5216,6 +5216,60 @@ void parse_props(mission* pm)
52165216
}
52175217
}
52185218

5219+
// Goober5000
5220+
void resolve_and_check_anchor(bool check_for_hangar, SCP_set<int> &anchors_checked, int &anchor, const char *other_name, bool other_is_ship, bool is_arrival)
5221+
{
5222+
if ((anchor < 0) || (anchor & SPECIAL_ARRIVAL_ANCHOR_FLAG))
5223+
return;
5224+
5225+
if (Parse_names.in_bounds(anchor))
5226+
anchor = ship_registry_get_index(Parse_names[anchor]);
5227+
else
5228+
anchor = -1;
5229+
5230+
if (check_for_hangar && anchor >= 0)
5231+
{
5232+
SCP_string message;
5233+
check_anchor_for_hangar_bay(message, anchors_checked, anchor, other_name, other_is_ship, is_arrival);
5234+
if (!message.empty())
5235+
Warning(LOCATION, "%s", message.c_str());
5236+
}
5237+
}
5238+
5239+
/**
5240+
* Resolve parse names, particularly for arrival/departure anchors
5241+
* NB: between parsing and the time this function is run, the anchors store the index into Parse_names;
5242+
* at all other times, they store the index into the ship registry
5243+
*/
5244+
void post_process_parse_names()
5245+
{
5246+
SCP_set<int> anchors_checked;
5247+
5248+
// check the parse names
5249+
for (const auto &parse_name : Parse_names)
5250+
{
5251+
auto ship_entry = ship_registry_get(parse_name);
5252+
if (!ship_entry)
5253+
Warning(LOCATION, "Ship name \"%s\" was referenced, but this ship doesn't exist!", parse_name.c_str());
5254+
}
5255+
5256+
// resolve anchors for parse objects (ships)
5257+
for (auto &pobj: Parse_objects)
5258+
{
5259+
resolve_and_check_anchor(pobj.arrival_location == ArrivalLocation::FROM_DOCK_BAY, anchors_checked, pobj.arrival_anchor, pobj.name, true, true);
5260+
resolve_and_check_anchor(pobj.departure_location == DepartureLocation::TO_DOCK_BAY, anchors_checked, pobj.departure_anchor, pobj.name, true, false);
5261+
}
5262+
5263+
// resolve anchors for wings
5264+
for (int i = 0; i < Num_wings; ++i)
5265+
{
5266+
auto wingp = &Wings[i];
5267+
5268+
resolve_and_check_anchor(wingp->arrival_location == ArrivalLocation::FROM_DOCK_BAY, anchors_checked, wingp->arrival_anchor, wingp->name, false, true);
5269+
resolve_and_check_anchor(wingp->departure_location == DepartureLocation::TO_DOCK_BAY, anchors_checked, wingp->departure_anchor, wingp->name, false, false);
5270+
}
5271+
}
5272+
52195273
// Goober5000
52205274
void resolve_path_masks(int anchor, int *path_mask)
52215275
{
@@ -5235,16 +5289,14 @@ void resolve_path_masks(int anchor, int *path_mask)
52355289
if (prp->cached_mask & (1 << MAX_SHIP_BAY_PATHS))
52365290
{
52375291
int j, bay_path, modelnum;
5238-
p_object *parent_pobjp;
52395292

52405293
// get anchor ship
52415294
Assert(!(anchor & SPECIAL_ARRIVAL_ANCHOR_FLAG));
5242-
auto parent_ship_entry = ship_registry_get(Parse_names[anchor]);
5243-
parent_pobjp = parent_ship_entry->p_objp();
5295+
auto anchor_ship_entry = ship_registry_get(anchor);
52445296

52455297
// Load the anchor ship model with subsystems and all; it'll need to be done for this mission anyway
5246-
ship_info *sip = &Ship_info[parent_pobjp->ship_class];
5247-
modelnum = model_load(sip->pof_file, sip);
5298+
auto anchor_sip = anchor_ship_entry->sip();
5299+
modelnum = model_load(anchor_sip->pof_file, anchor_sip);
52485300

52495301
// resolve names to indexes
52505302
*path_mask = 0;
@@ -5351,6 +5403,9 @@ void post_process_ships_wings()
53515403
Ship_registry_map[p_obj.name] = static_cast<int>(Ship_registry.size() - 1);
53525404
}
53535405

5406+
// Goober5000 - resolve the parse names. Needs to be done once the ship registry is valid but before the path masks are resolved.
5407+
post_process_parse_names();
5408+
53545409
// Goober5000 - resolve the path masks. Needs to be done early because
53555410
// mission_parse_maybe_create_parse_object relies on it.
53565411
post_process_path_stuff();
@@ -6669,9 +6724,8 @@ bool parse_mission(mission *pm, int flags)
66696724

66706725
bool post_process_mission(mission *pm)
66716726
{
6672-
int i;
6673-
int indices[MAX_SHIPS], objnum;
6674-
ship_weapon *swp;
6727+
int i, objnum;
6728+
ship_weapon *swp;
66756729
ship_obj *so;
66766730

66776731
post_process_mission_props();
@@ -6738,55 +6792,6 @@ bool post_process_mission(mission *pm)
67386792
Arriving_support_ship = nullptr;
67396793
Num_arriving_repair_targets = 0;
67406794

6741-
// convert all ship name indices to ship indices now that mission has been loaded
6742-
if (Fred_running) {
6743-
// lambda for seeing whether the anchors actually work for arrival/departure
6744-
SCP_string message;
6745-
SCP_set<int> anchors_checked;
6746-
auto check_anchor = [&message, &anchors_checked](int anchor_shipnum, const char *other_name, bool other_is_ship, bool is_arrival) {
6747-
check_anchor_for_hangar_bay(message, anchors_checked, anchor_shipnum, other_name, other_is_ship, is_arrival);
6748-
if (!message.empty())
6749-
Warning(LOCATION, "%s", message.c_str());
6750-
};
6751-
6752-
i = 0;
6753-
for (const auto &parse_name: Parse_names) {
6754-
auto ship_entry = ship_registry_get(parse_name);
6755-
indices[i] = ship_entry ? ship_entry->shipnum : -1;
6756-
if (indices[i] < 0)
6757-
Warning(LOCATION, "Ship name \"%s\" referenced, but this ship doesn't exist", parse_name.c_str());
6758-
i++;
6759-
}
6760-
6761-
for (i=0; i<MAX_SHIPS; i++) {
6762-
if ((Ships[i].objnum >= 0) && (Ships[i].arrival_anchor >= 0) && (Ships[i].arrival_anchor < SPECIAL_ARRIVAL_ANCHOR_FLAG)) {
6763-
Ships[i].arrival_anchor = indices[Ships[i].arrival_anchor];
6764-
if (Ships[i].arrival_location == ArrivalLocation::FROM_DOCK_BAY)
6765-
check_anchor(Ships[i].arrival_anchor, Ships[i].ship_name, true, true);
6766-
}
6767-
6768-
if ((Ships[i].objnum >= 0) && (Ships[i].departure_anchor >= 0)) {
6769-
Ships[i].departure_anchor = indices[Ships[i].departure_anchor];
6770-
if (Ships[i].departure_location == DepartureLocation::TO_DOCK_BAY)
6771-
check_anchor(Ships[i].departure_anchor, Ships[i].ship_name, true, false);
6772-
}
6773-
}
6774-
6775-
for (i=0; i<MAX_WINGS; i++) {
6776-
if (Wings[i].wave_count && (Wings[i].arrival_anchor >= 0) && (Wings[i].arrival_anchor < SPECIAL_ARRIVAL_ANCHOR_FLAG)) {
6777-
Wings[i].arrival_anchor = indices[Wings[i].arrival_anchor];
6778-
if (Wings[i].arrival_location == ArrivalLocation::FROM_DOCK_BAY)
6779-
check_anchor(Wings[i].arrival_anchor, Wings[i].name, false, true);
6780-
}
6781-
6782-
if (Wings[i].wave_count && (Wings[i].departure_anchor >= 0)) {
6783-
Wings[i].departure_anchor = indices[Wings[i].departure_anchor];
6784-
if (Wings[i].departure_location == DepartureLocation::TO_DOCK_BAY)
6785-
check_anchor(Wings[i].departure_anchor, Wings[i].name, false, false);
6786-
}
6787-
}
6788-
}
6789-
67906795
// before doing anything else, we must validate all of the sexpressions that were loaded into the mission.
67916796
// Loop through the Sexp_nodes array and send the top level functions to the check_sexp_syntax parser
67926797
// Cyborg -- If you are a ingame joiner, your sexps will be taken care of by the server, and checking will
@@ -7817,11 +7822,10 @@ int mission_set_arrival_location(int anchor, ArrivalLocation location, int dist,
78177822
// get ship
78187823
shipnum = ship_get_random_team_ship(iff_get_mask(iff_index), get_players ? SHIP_GET_ONLY_PLAYERS : SHIP_GET_ANY_SHIP);
78197824
}
7820-
// if we didn't find the arrival anchor in the list of special nodes, then do a
7821-
// ship name lookup on the anchor
7825+
// if we didn't find the arrival anchor in the list of special nodes, then it must be a ship registry index
78227826
else
78237827
{
7824-
auto anchor_entry = ship_registry_get(Parse_names[anchor]);
7828+
auto anchor_entry = ship_registry_get(anchor);
78257829
shipnum = anchor_entry ? anchor_entry->shipnum : -1;
78267830
}
78277831

@@ -8019,7 +8023,7 @@ int mission_did_ship_arrive(p_object *objp, bool force_arrival)
80198023
// doesn't exist, don't create.
80208024
if ( objp->arrival_location == ArrivalLocation::FROM_DOCK_BAY ) {
80218025
Assert( objp->arrival_anchor >= 0 );
8022-
auto anchor_ship_entry = ship_registry_get(Parse_names[objp->arrival_anchor]);
8026+
auto anchor_ship_entry = ship_registry_get(objp->arrival_anchor);
80238027

80248028
// see if ship is yet to arrive. If so, then return -1 so we can evaluate again later.
80258029
if (!anchor_ship_entry || anchor_ship_entry->status == ShipStatus::NOT_YET_PRESENT)
@@ -8430,13 +8434,13 @@ int mission_do_departure(object *objp, bool goal_is_to_warp)
84308434
{
84318435
Assert(anchor >= 0);
84328436
auto anchor_ship_entry = (anchor >= 0)
8433-
? ship_registry_get(Parse_names[anchor])
8437+
? ship_registry_get(anchor)
84348438
: nullptr; // should never happen, but if it does, fail gracefully
84358439

84368440
// see if ship is yet to arrive. If so, then warp.
84378441
if (!anchor_ship_entry || anchor_ship_entry->status == ShipStatus::NOT_YET_PRESENT)
84388442
{
8439-
mprintf(("Anchor ship %s hasn't arrived yet! Trying to warp...\n", Parse_names[anchor].c_str()));
8443+
mprintf(("Anchor ship %s hasn't arrived yet! Trying to warp...\n", anchor_ship_entry ? anchor_ship_entry->name : "<unknown>"));
84408444
goto try_to_warp;
84418445
}
84428446

@@ -8822,21 +8826,29 @@ int get_anchor(const char *name)
88228826
/**
88238827
* See if an arrival/departure anchor is missing a hangar bay. If it is, the message parameter will be populated with an appropriate error.
88248828
*/
8825-
void check_anchor_for_hangar_bay(SCP_string &message, SCP_set<int> &anchor_shipnums_checked, int anchor_shipnum, const char *other_name, bool other_is_ship, bool is_arrival)
8829+
void check_anchor_for_hangar_bay(SCP_string &message, SCP_set<int> &anchors_checked, int anchor, const char *other_name, bool other_is_ship, bool is_arrival)
88268830
{
88278831
message.clear();
88288832

8829-
if (anchor_shipnum < 0)
8833+
if (anchor < 0)
88308834
return;
8831-
if (anchor_shipnums_checked.contains(anchor_shipnum))
8835+
if (anchors_checked.contains(anchor))
88328836
return;
8833-
anchor_shipnums_checked.insert(anchor_shipnum);
8837+
anchors_checked.insert(anchor);
88348838

8835-
if (!ship_has_hangar_bay(anchor_shipnum))
8839+
auto anchor_ship_entry = ship_registry_get(anchor);
8840+
if (anchor_ship_entry)
88368841
{
8837-
auto shipp = &Ships[anchor_shipnum];
8838-
sprintf(message, "%s (%s) is used as a%s anchor by %s %s (and possibly elsewhere too), but it does not have a hangar bay!", shipp->ship_name,
8839-
Ship_info[shipp->ship_info_index].name, is_arrival ? "n arrival" : " departure", other_is_ship ? "ship" : "wing", other_name);
8842+
// Load the anchor ship model with subsystems and all; it'll need to be done for this mission anyway
8843+
auto anchor_sip = anchor_ship_entry->sip();
8844+
int modelnum = model_load(anchor_sip->pof_file, anchor_sip);
8845+
8846+
// Check if this model has a hangar bay
8847+
if (!model_has_hangar_bay(modelnum))
8848+
{
8849+
sprintf(message, "%s (%s) is used as a%s anchor by %s %s (and possibly elsewhere too), but it does not have a hangar bay!", anchor_ship_entry->name,
8850+
anchor_sip->name, is_arrival ? "n arrival" : " departure", other_is_ship ? "ship" : "wing", other_name);
8851+
}
88408852
}
88418853
};
88428854

code/mission/missionparse.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ enum class DepartureLocation;
4949
#define MIN_TARGET_ARRIVAL_MULTIPLIER 2.0f // minimum distance is 2 * target radius, but at least 500
5050

5151
int get_special_anchor(const char *name);
52-
void check_anchor_for_hangar_bay(SCP_string &message, SCP_set<int> &anchor_shipnums_checked, int anchor_shipnum, const char *other_name, bool other_is_ship, bool is_arrival);
52+
void check_anchor_for_hangar_bay(SCP_string &message, SCP_set<int> &anchors_checked, int anchor, const char *other_name, bool other_is_ship, bool is_arrival);
5353

5454
// MISSION_VERSION should be the earliest version of FSO that can load the current mission format without
5555
// requiring version-specific comments. It should be updated whenever the format changes, but it should
@@ -448,7 +448,7 @@ class p_object
448448

449449
ArrivalLocation arrival_location = ArrivalLocation::AT_LOCATION;
450450
int arrival_distance = 0; // used when arrival location is near or in front of some ship
451-
int arrival_anchor = -1; // ship used for anchoring an arrival point
451+
int arrival_anchor = -1; // ship registry entry used for anchoring an arrival point
452452
int arrival_path_mask = 0; // Goober5000
453453
int arrival_cue = -1; // Index in Sexp_nodes of this sexp.
454454
int arrival_delay = 0;

code/missioneditor/common.cpp

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,26 @@ void stuff_special_arrival_anchor_name(char* buf, int anchor_num, bool retail_fo
6060
stuff_special_arrival_anchor_name(buf, iff_index, restrict_to_players, retail_format);
6161
}
6262

63+
// Ship and wing arrival and departure anchors should always be ship registry entry indexes, except for a very brief window during mission parsing.
64+
// But FRED and QtFRED dialogs use ship indexes instead. So, rather than refactor all the dialogs, this converts between the two. If an anchor
65+
// is a valid ship registry index, the equivalent ship index is returned; otherwise the special value (-1 or a flag) is returned instead.
66+
int anchor_to_target(int anchor)
67+
{
68+
auto anchor_entry = ship_registry_get(anchor);
69+
return anchor_entry ? anchor_entry->shipnum : anchor;
70+
}
71+
72+
// Ship and wing arrival and departure anchors should always be ship registry entry indexes, except for a very brief window during mission parsing.
73+
// But FRED and QtFRED dialogs use ship indexes instead. So, rather than refactor all the dialogs, this converts between the two. If a target
74+
// is a valid ship index, the equivalent ship registry index is returned; otherwise the special value (-1 or a flag) is returned instead.
75+
int target_to_anchor(int target)
76+
{
77+
if (target >= 0 && target < MAX_SHIPS)
78+
return ship_registry_get_index(Ships[target].ship_name);
79+
else
80+
return target;
81+
}
82+
6383
void generate_weaponry_usage_list_team(int team, int* arr)
6484
{
6585
int i;
@@ -112,4 +132,4 @@ void generate_weaponry_usage_list_wing(int wing_num, int* arr)
112132
}
113133
}
114134
}
115-
}
135+
}

code/missioneditor/common.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ void stuff_special_arrival_anchor_name(char* buf, int iff_index, int restrict_to
3131

3232
void stuff_special_arrival_anchor_name(char* buf, int anchor_num, bool retail_format);
3333

34+
int anchor_to_target(int anchor);
35+
36+
int target_to_anchor(int target);
37+
3438
void generate_weaponry_usage_list_team(int team, int* arr);
3539

36-
void generate_weaponry_usage_list_wing(int wing_num, int* arr);
40+
void generate_weaponry_usage_list_wing(int wing_num, int* arr);

0 commit comments

Comments
 (0)