|
1 | 1 | // methods and members common to any mission editor FSO may have |
2 | 2 | #include "common.h" |
| 3 | +#include "ai/ai.h" |
| 4 | +#include "globalincs/linklist.h" |
3 | 5 | #include "mission/missionparse.h" |
4 | 6 | #include "iff_defs/iff_defs.h" |
| 7 | +#include "object/object.h" |
5 | 8 | #include "ship/ship.h" |
6 | 9 |
|
| 10 | +#include <algorithm> |
| 11 | +#include <climits> |
| 12 | + |
7 | 13 | // to keep track of data |
8 | 14 | char Voice_abbrev_briefing[NAME_LENGTH]; |
9 | 15 | char Voice_abbrev_campaign[NAME_LENGTH]; |
@@ -145,3 +151,162 @@ void generate_weaponry_usage_list_wing(int wing_num, int* arr) |
145 | 151 | } |
146 | 152 | } |
147 | 153 | } |
| 154 | + |
| 155 | +void reassign_ship_slot(int from, int to, const FredShipSlotConfig& cfg) |
| 156 | +{ |
| 157 | + Assertion(from != to, "reassign_ship_slot: from == to (%d)", from); |
| 158 | + Assertion(from >= 0 && from < MAX_SHIPS, "reassign_ship_slot: 'from' slot %d out of range", from); |
| 159 | + Assertion(to >= 0 && to < MAX_SHIPS, "reassign_ship_slot: 'to' slot %d out of range", to); |
| 160 | + Assertion(Ships[from].objnum >= 0, "reassign_ship_slot: source slot %d is empty", from); |
| 161 | + Assertion(Ships[to].objnum < 0, "reassign_ship_slot: destination slot %d is occupied", to); |
| 162 | + |
| 163 | + // Move the ship struct itself. Per the engine's convention, a slot with |
| 164 | + // objnum < 0 is considered empty; other fields in the vacated slot are |
| 165 | + // left as-is (unreachable through the "is this slot used" guard). |
| 166 | + // Move (not copy) because ship contains a unique_ptr member. |
| 167 | + Ships[to] = std::move(Ships[from]); |
| 168 | + Ships[from].objnum = -1; |
| 169 | + |
| 170 | + // subsys_list is an intrusive doubly-linked list whose head sentinel's |
| 171 | + // address is meaningful: real nodes' prev/next bookend back to &head, and |
| 172 | + // an empty list is self-referential. The move copied those pointers |
| 173 | + // verbatim, so they still reference the old (vacated) sentinel address. |
| 174 | + { |
| 175 | + auto old_head = &Ships[from].subsys_list; |
| 176 | + auto new_head = &Ships[to].subsys_list; |
| 177 | + if (new_head->next == old_head) |
| 178 | + { |
| 179 | + // Empty list: re-init self-referential on the new head. |
| 180 | + new_head->next = new_head; |
| 181 | + new_head->prev = new_head; |
| 182 | + } |
| 183 | + else |
| 184 | + { |
| 185 | + // Non-empty: repoint the first node's prev and the last node's next. |
| 186 | + new_head->next->prev = new_head; |
| 187 | + new_head->prev->next = new_head; |
| 188 | + } |
| 189 | + } |
| 190 | + |
| 191 | + // Move FRED-side parallel arrays if the caller supplied them. |
| 192 | + if (cfg.fred_alt_names != nullptr) |
| 193 | + { |
| 194 | + strcpy_s(cfg.fred_alt_names[to], cfg.fred_alt_names[from]); |
| 195 | + cfg.fred_alt_names[from][0] = '\0'; |
| 196 | + } |
| 197 | + if (cfg.fred_callsigns != nullptr) |
| 198 | + { |
| 199 | + strcpy_s(cfg.fred_callsigns[to], cfg.fred_callsigns[from]); |
| 200 | + cfg.fred_callsigns[from][0] = '\0'; |
| 201 | + } |
| 202 | + |
| 203 | + // Object back-reference. |
| 204 | + Objects[Ships[to].objnum].instance = to; |
| 205 | + |
| 206 | + // Keep obj_used_list iteration order in sync with Ships[] slot order. |
| 207 | + resort_ships_in_obj_used_list(); |
| 208 | + |
| 209 | + // AI back-reference (the one invariant codified by internal_integrity_check). |
| 210 | + Ai_info[Ships[to].ai_index].shipnum = to; |
| 211 | + Assertion(Ai_info[Ships[to].ai_index].shipnum == to, |
| 212 | + "reassign_ship_slot: Ai_info[%d].shipnum invariant broken after fixup", Ships[to].ai_index); |
| 213 | + |
| 214 | + // Wing membership: scan every wing and re-point any reference to the old slot. |
| 215 | + // (wing.special_ship is wing-relative, NOT a Ships[] index, so it is intentionally |
| 216 | + // not touched here.) |
| 217 | + for (int w = 0; w < MAX_WINGS; ++w) |
| 218 | + { |
| 219 | + if (Wings[w].wave_count == 0) |
| 220 | + continue; |
| 221 | + for (int k = 0; k < Wings[w].wave_count; ++k) |
| 222 | + { |
| 223 | + if (Wings[w].ship_index[k] == from) |
| 224 | + Wings[w].ship_index[k] = to; |
| 225 | + } |
| 226 | + } |
| 227 | + |
| 228 | + // Single-player start. |
| 229 | + if (Player_start_shipnum == from) |
| 230 | + Player_start_shipnum = to; |
| 231 | + |
| 232 | + // Ship_registry caches the shipnum on its entries (lookup is by name, but the |
| 233 | + // cached integer would otherwise go stale). |
| 234 | + int reg = ship_registry_get_index(Ships[to].ship_name); |
| 235 | + if (reg >= 0) |
| 236 | + Ship_registry[reg].shipnum = to; |
| 237 | + |
| 238 | + // FRED's current-ship pointer, if the caller is tracking one. |
| 239 | + if (cfg.cur_ship != nullptr && *cfg.cur_ship == from) |
| 240 | + *cfg.cur_ship = to; |
| 241 | +} |
| 242 | + |
| 243 | +void swap_ship_slots(int a, int b, const FredShipSlotConfig& cfg) |
| 244 | +{ |
| 245 | + if (a == b) |
| 246 | + return; |
| 247 | + |
| 248 | + Assertion(a >= 0 && a < MAX_SHIPS, "swap_ship_slots: slot 'a' %d out of range", a); |
| 249 | + Assertion(b >= 0 && b < MAX_SHIPS, "swap_ship_slots: slot 'b' %d out of range", b); |
| 250 | + Assertion(Ships[a].objnum >= 0 && Ships[b].objnum >= 0, |
| 251 | + "swap_ship_slots: both slots must be valid (a=%d, b=%d)", a, b); |
| 252 | + |
| 253 | + // Find a free temporary slot. |
| 254 | + int tmp = -1; |
| 255 | + for (int i = 0; i < MAX_SHIPS; ++i) |
| 256 | + { |
| 257 | + if (Ships[i].objnum < 0) |
| 258 | + { |
| 259 | + tmp = i; |
| 260 | + break; |
| 261 | + } |
| 262 | + } |
| 263 | + Assertion(tmp >= 0, "swap_ship_slots: no free Ships[] slot available for the temporary leg"); |
| 264 | + |
| 265 | + // Three-leg swap; each call's preconditions hold by construction. |
| 266 | + reassign_ship_slot(a, tmp, cfg); |
| 267 | + reassign_ship_slot(b, a, cfg); |
| 268 | + reassign_ship_slot(tmp, b, cfg); |
| 269 | +} |
| 270 | + |
| 271 | +// Bulk-re-sort one type's subset of obj_used_list while keeping non-matching |
| 272 | +// entries in their original relative positions. Each callsite supplies a |
| 273 | +// type matcher and a key function; the i-th matching slot (in original list |
| 274 | +// order) receives the i-th smallest matching node by key. |
| 275 | +static void resort_obj_used_list_subset( |
| 276 | + bool (*matches_type)(int), |
| 277 | + int (*key)(const object*)) |
| 278 | +{ |
| 279 | + SCP_vector<object*> all; |
| 280 | + for (auto o : list_range(&obj_used_list)) |
| 281 | + all.push_back(o); |
| 282 | + |
| 283 | + SCP_vector<object*> matched; |
| 284 | + for (auto o : all) |
| 285 | + if (matches_type(o->type)) |
| 286 | + matched.push_back(o); |
| 287 | + |
| 288 | + std::sort(matched.begin(), matched.end(), |
| 289 | + [&](const object* a, const object* b) { return key(a) < key(b); }); |
| 290 | + |
| 291 | + list_init(&obj_used_list); |
| 292 | + auto it = matched.begin(); |
| 293 | + for (auto o : all) |
| 294 | + { |
| 295 | + if (matches_type(o->type)) |
| 296 | + { |
| 297 | + list_append(&obj_used_list, *it); |
| 298 | + ++it; |
| 299 | + } |
| 300 | + else |
| 301 | + { |
| 302 | + list_append(&obj_used_list, o); |
| 303 | + } |
| 304 | + } |
| 305 | +} |
| 306 | + |
| 307 | +void resort_ships_in_obj_used_list() |
| 308 | +{ |
| 309 | + resort_obj_used_list_subset( |
| 310 | + [](int t) { return t == OBJ_SHIP || t == OBJ_START; }, |
| 311 | + [](const object* o) { return o->instance; }); |
| 312 | +} |
0 commit comments