diff --git a/code/mission/missionparse.cpp b/code/mission/missionparse.cpp index 301a229abf9..1cc2c6ea0cf 100644 --- a/code/mission/missionparse.cpp +++ b/code/mission/missionparse.cpp @@ -178,6 +178,9 @@ SCP_vector Parse_names; SCP_vector Mission_parse_warnings; +// true while a mission is being parsed and post-processed +bool Parsing_mission = false; + // Routes a parse-time auto-correction notice to the right surface for the app: // outside QtFRED, the existing Warning(LOCATION, ...) popup; inside QtFRED, the // Mission_parse_warnings queue so the ErrorChecker can present it without a popup. @@ -7534,6 +7537,8 @@ bool parse_main(const char *mission_name, int flags) Assert(Ship_info.size() <= MAX_SHIP_CLASSES); + Parsing_mission = true; + do { // don't do this for imports if (!(flags & MPF_IMPORT_FSM)) { @@ -7585,6 +7590,8 @@ bool parse_main(const char *mission_name, int flags) } } while (0); + Parsing_mission = false; + if (!Fred_running) strcpy_s(Mission_filename, mission_name); @@ -8026,6 +8033,17 @@ int mission_parse_get_multi_mission_info( const char *filename ) return The_mission.num_players; } +static p_object *mission_parse_get_arrival_ship_sub(const char *name) +{ + for (auto p_objp : list_range(&Ship_arrival_list)) + { + if (!stricmp(p_objp->name, name)) + return p_objp; // still on the arrival list + } + + return nullptr; +} + /** * @brief Returns the parse object on the ship arrival list associated with the given name. * @param[in] name The name of the object @@ -8036,18 +8054,18 @@ int mission_parse_get_multi_mission_info( const char *filename ) */ p_object *mission_parse_get_arrival_ship(const char *name) { - p_object *p_objp; - if (name == nullptr) return nullptr; - for (p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp)) - { - if (!stricmp(p_objp->name, name)) - { - return p_objp; // still on the arrival list - } - } + // try the normal lookup + auto p_objp = mission_parse_get_arrival_ship_sub(name); + if (p_objp) + return p_objp; + + // also search for ship names hashed using the legacy format + SCP_string legacy_hashed; + if (wing_bash_legacy_hashed_ship_name(legacy_hashed, name)) + return mission_parse_get_arrival_ship_sub(legacy_hashed.c_str()); return nullptr; } diff --git a/code/mission/missionparse.h b/code/mission/missionparse.h index 74850a0e140..7a5850b691e 100644 --- a/code/mission/missionparse.h +++ b/code/mission/missionparse.h @@ -588,6 +588,9 @@ extern SCP_vector Parse_names; // silently buried. Outside of QtFRED these sites still call Warning(LOCATION, ...). extern SCP_vector Mission_parse_warnings; +// true while a mission is being parsed and post-processed +extern bool Parsing_mission; + extern char Player_start_shipname[NAME_LENGTH]; extern int Player_start_shipnum; extern p_object *Player_start_pobject; diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index 90dcf10f95d..eea938359d5 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -5846,7 +5846,15 @@ const ship_registry_entry *eval_ship(int node) return eval_ship(arg_node); } - auto ship_it = Ship_registry_map.find(CTEXT(node)); + // look up the ship in the ship registry + auto ship_name = CTEXT(node); + auto ship_it = Ship_registry_map.find(ship_name); + if (ship_it == Ship_registry_map.end()) + { + SCP_string legacy_hashed; + if (wing_bash_legacy_hashed_ship_name(legacy_hashed, ship_name)) + ship_it = Ship_registry_map.find(legacy_hashed); + } if (ship_it != Ship_registry_map.end()) { // cache the value if it can't change later diff --git a/code/ship/ship.cpp b/code/ship/ship.cpp index 5f10428f44f..0293978c9f5 100644 --- a/code/ship/ship.cpp +++ b/code/ship/ship.cpp @@ -229,6 +229,19 @@ ship_info* ship_registry_entry::sip() const } } +int ship_registry_entry::ship_class_index() const +{ + if (shipnum >= 0) + return Ships[shipnum].ship_info_index; + else if (pobj_num >= 0) + return Parse_objects[pobj_num].ship_class; + else + { + Assertion(false, "A ship registry entry must have either a parse object or a ship!"); + return -1; + } +} + SCP_vector Ship_registry; SCP_unordered_map Ship_registry_map; @@ -268,6 +281,15 @@ bool ship_registry_exists(int index) const ship_registry_entry *ship_registry_get(const char *name) { auto ship_it = Ship_registry_map.find(name); + + // also search for ship names hashed using the legacy format + if (ship_it == Ship_registry_map.end()) + { + SCP_string legacy_hashed; + if (wing_bash_legacy_hashed_ship_name(legacy_hashed, name)) + ship_it = Ship_registry_map.find(legacy_hashed); + } + if (ship_it != Ship_registry_map.end()) return &Ship_registry[ship_it->second]; @@ -277,6 +299,15 @@ const ship_registry_entry *ship_registry_get(const char *name) const ship_registry_entry *ship_registry_get(const SCP_string &name) { auto ship_it = Ship_registry_map.find(name); + + // also search for ship names hashed using the legacy format + if (ship_it == Ship_registry_map.end()) + { + SCP_string legacy_hashed; + if (wing_bash_legacy_hashed_ship_name(legacy_hashed, name.c_str())) + ship_it = Ship_registry_map.find(legacy_hashed); + } + if (ship_it != Ship_registry_map.end()) return &Ship_registry[ship_it->second]; @@ -8645,6 +8676,15 @@ void ship_delete( object * obj ) shipp->weapons.primary_bank_external_model_instance[i] = -1; } } + + // In FRED, clean up the registry so that stale references don't stick around. Conversely, + // in FSO, we need to keep the registry entry so that ships will still be known in the debriefing. + if (Fred_running) + { + auto ship_it = Ship_registry_map.find(shipp->ship_name); + if (ship_it != Ship_registry_map.end()) + Ship_registry_map.erase(ship_it); // don't erase the vector entry to avoid clobbering other indexes + } } /** @@ -15028,6 +15068,34 @@ void wing_bash_ship_name(ship *shipp, const wing *wingp, int ordinal, bool reset } } +bool wing_bash_legacy_hashed_ship_name(SCP_string &dest, const char *src) +{ + // missions might have ships within wings that were saved using the legacy hash format, with the hash suffix at the end + auto hash = get_pointer_to_first_hash_symbol(src); + if (hash && *(hash + 1) != '\0') + { + // find the run of digits immediately preceding the hash + auto digits = hash; + while (digits > src && isdigit(static_cast(*(digits - 1)))) + digits--; + + // the ordinal must be at least one digit, preceded by a space, preceded by the wing name + if (digits < hash && digits > (src + 1) && *(digits - 1) == ' ') + { + // move the ordinal from before the hash to the end of the name + dest.assign(src, digits - 1); + dest += hash; + dest += ' '; + dest.append(digits, hash); + + // we changed it + return true; + } + } + + return false; +} + /** * Return the object index of the ship with name *name. */ @@ -15251,10 +15319,7 @@ int ship_info_lookup(const char *token) return ship_info_lookup_sub(name); } -/** - * Return the ship index of the ship with name *name. - */ -int ship_name_lookup(const char *name, int inc_players) +static int ship_name_lookup_sub(const char *name, int inc_players) { Assertion(name != nullptr, "NULL name passed to ship_name_lookup"); @@ -15272,7 +15337,26 @@ int ship_name_lookup(const char *name, int inc_players) return -1; } -int ship_type_name_lookup_sub(const char *name) +/** + * Return the ship index of the ship with name *name. + */ +int ship_name_lookup(const char *name, int inc_players) +{ + // try the normal lookup + auto idx = ship_name_lookup_sub(name, inc_players); + if (idx >= 0) + return idx; + + // also search for ship names hashed using the legacy format + SCP_string legacy_hashed; + if (wing_bash_legacy_hashed_ship_name(legacy_hashed, name)) + return ship_name_lookup_sub(legacy_hashed.c_str(), inc_players); + + // couldn't find it + return -1; +} + +static int ship_type_name_lookup_sub(const char *name) { Assertion(name != nullptr, "NULL name passed to ship_type_name_lookup"); diff --git a/code/ship/ship.h b/code/ship/ship.h index 0aaf086a2f7..1ca342b3470 100644 --- a/code/ship/ship.h +++ b/code/ship/ship.h @@ -1046,6 +1046,7 @@ struct ship_registry_entry ship* shipp_or_null() const; ship_info* sip() const; + int ship_class_index() const; }; extern SCP_vector Ship_registry; @@ -1876,6 +1877,8 @@ extern void wing_bash_ship_name(SCP_string &ship_name, const char *wing_name, in extern void wing_bash_ship_name(char *ship_name, const char *wing_name, int ordinal); extern void wing_bash_ship_name(p_object *p_objp, const wing *wingp, int ordinal, bool reset_display_name_if_normal = false); extern void wing_bash_ship_name(ship *shipp, const wing *wingp, int ordinal, bool reset_display_name_if_normal = false); +extern bool wing_bash_legacy_hashed_ship_name(SCP_string &dest, const char *src); + extern int Player_ship_class; // Do the special effect for energy dissipating into the shield for a hit. diff --git a/fred2/freddoc.cpp b/fred2/freddoc.cpp index 15aafbfa631..77de4badacc 100644 --- a/fred2/freddoc.cpp +++ b/fred2/freddoc.cpp @@ -331,15 +331,7 @@ bool CFREDDoc::load_mission(const char *pathname, int flags) { wing_bash_ship_name(name, Wings[i].name, j + 1); old_name = Ships[Wings[i].ship_index[j]].ship_name; if (stricmp(name, old_name) != 0) { // need to fix name - update_sexp_references(old_name, name); - ai_update_goal_references(sexp_ref_type::SHIP, old_name, name); - update_texture_replacements(old_name, name); - int k = find_item_with_string(Reinforcements, &reinforcements::name, old_name); - if (k >= 0) { - Assert(strlen(name) < NAME_LENGTH); - strcpy_s(Reinforcements[k].name, name); - } - + rename_ship(Wings[i].ship_index[j], name); // bash it again so that we handle display names if needed wing_bash_ship_name(&Ships[Wings[i].ship_index[j]], &Wings[i], j + 1, true); } diff --git a/qtfred/src/mission/Editor.cpp b/qtfred/src/mission/Editor.cpp index b4f147c9d1e..a92205ae0fc 100644 --- a/qtfred/src/mission/Editor.cpp +++ b/qtfred/src/mission/Editor.cpp @@ -337,15 +337,7 @@ bool Editor::loadMission(const std::string& mission_name, int flags) { wing_bash_ship_name(name, Wings[i].name, j + 1); old_name = Ships[Wings[i].ship_index[j]].ship_name; if (stricmp(name, old_name) != 0) { // need to fix name - update_sexp_references(old_name, name); - ai_update_goal_references(sexp_ref_type::SHIP, old_name, name); - update_texture_replacements(old_name, name); - int k = find_item_with_string(Reinforcements, &reinforcements::name, old_name); - if (k >= 0) { - Assert(strlen(name) < NAME_LENGTH); - strcpy_s(Reinforcements[k].name, name); - } - + rename_ship(Wings[i].ship_index[j], name); // bash it again so that we handle display names if needed wing_bash_ship_name(&Ships[Wings[i].ship_index[j]], &Wings[i], j + 1, true); }