|
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 | + |
7 | 12 | // to keep track of data |
8 | 13 | char Voice_abbrev_briefing[NAME_LENGTH]; |
9 | 14 | char Voice_abbrev_campaign[NAME_LENGTH]; |
@@ -145,3 +150,209 @@ void generate_weaponry_usage_list_wing(int wing_num, int* arr) |
145 | 150 | } |
146 | 151 | } |
147 | 152 | } |
| 153 | + |
| 154 | +void reassign_ship_slot(int from, int to, const FredShipSlotConfig& cfg, bool resort_obj_list) |
| 155 | +{ |
| 156 | + Assertion(from != to, "reassign_ship_slot: from == to (%d)", from); |
| 157 | + Assertion(from >= 0 && from < MAX_SHIPS, "reassign_ship_slot: 'from' slot %d out of range", from); |
| 158 | + Assertion(to >= 0 && to < MAX_SHIPS, "reassign_ship_slot: 'to' slot %d out of range", to); |
| 159 | + Assertion(Ships[from].objnum >= 0, "reassign_ship_slot: source slot %d is empty", from); |
| 160 | + Assertion(Ships[to].objnum < 0, "reassign_ship_slot: destination slot %d is occupied", to); |
| 161 | + |
| 162 | + // Move the ship struct itself. Per the engine's convention, a slot with |
| 163 | + // objnum < 0 is considered empty; other fields in the vacated slot are |
| 164 | + // left as-is (unreachable through the "is this slot used" guard). |
| 165 | + // Move (not copy) because ship contains a unique_ptr member. |
| 166 | + Ships[to] = std::move(Ships[from]); |
| 167 | + Ships[from].objnum = -1; |
| 168 | + |
| 169 | + // subsys_list is an intrusive doubly-linked list whose head sentinel's |
| 170 | + // address is meaningful: real nodes' prev/next bookend back to &head, and |
| 171 | + // an empty list is self-referential. The move copied those pointers |
| 172 | + // verbatim, so they still reference the old (vacated) sentinel address. |
| 173 | + { |
| 174 | + auto old_head = &Ships[from].subsys_list; |
| 175 | + auto new_head = &Ships[to].subsys_list; |
| 176 | + if (new_head->next == old_head) |
| 177 | + { |
| 178 | + // Empty list: re-init self-referential on the new head. |
| 179 | + new_head->next = new_head; |
| 180 | + new_head->prev = new_head; |
| 181 | + } |
| 182 | + else |
| 183 | + { |
| 184 | + // Non-empty: repoint the first node's prev and the last node's next. |
| 185 | + new_head->next->prev = new_head; |
| 186 | + new_head->prev->next = new_head; |
| 187 | + } |
| 188 | + } |
| 189 | + |
| 190 | + // Move FRED-side parallel arrays if the caller supplied them. |
| 191 | + if (cfg.fred_alt_names != nullptr) |
| 192 | + { |
| 193 | + strcpy_s(cfg.fred_alt_names[to], cfg.fred_alt_names[from]); |
| 194 | + cfg.fred_alt_names[from][0] = '\0'; |
| 195 | + } |
| 196 | + if (cfg.fred_callsigns != nullptr) |
| 197 | + { |
| 198 | + strcpy_s(cfg.fred_callsigns[to], cfg.fred_callsigns[from]); |
| 199 | + cfg.fred_callsigns[from][0] = '\0'; |
| 200 | + } |
| 201 | + |
| 202 | + // Object back-reference. |
| 203 | + Objects[Ships[to].objnum].instance = to; |
| 204 | + |
| 205 | + // Keep obj_used_list iteration order in sync with Ships[] slot order. The |
| 206 | + // re-sort is a total rebuild rather than an incremental fix, so a caller |
| 207 | + // making a batch of reassignments may defer it to the final call. |
| 208 | + if (resort_obj_list) |
| 209 | + resort_ships_in_obj_used_list(); |
| 210 | + |
| 211 | + // AI back-reference (the one invariant codified by internal_integrity_check). |
| 212 | + Ai_info[Ships[to].ai_index].shipnum = to; |
| 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 (auto &w: Wings) |
| 218 | + { |
| 219 | + if (w.wave_count == 0) |
| 220 | + continue; |
| 221 | + for (int k = 0; k < w.wave_count; ++k) |
| 222 | + { |
| 223 | + if (w.ship_index[k] == from) |
| 224 | + 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 | +static bool ship_slot_is_empty(int i) |
| 244 | +{ |
| 245 | + return Ships[i].objnum < 0; |
| 246 | +} |
| 247 | + |
| 248 | +static int find_free_slot(int max_slots, bool (*slot_is_empty)(int), const char* caller) |
| 249 | +{ |
| 250 | + for (int i = 0; i < max_slots; ++i) |
| 251 | + { |
| 252 | + if (slot_is_empty(i)) |
| 253 | + return i; |
| 254 | + } |
| 255 | + Assertion(false, "%s: no free slot available for the temporary leg", caller); |
| 256 | + return -1; |
| 257 | +} |
| 258 | + |
| 259 | +template <typename TConfig> |
| 260 | +static void swap_slots(int a, int b, const TConfig& cfg, int max_slots, |
| 261 | + bool (*slot_is_empty)(int), void (*reassign)(int, int, const TConfig&, bool), const char* caller) |
| 262 | +{ |
| 263 | + if (a == b) |
| 264 | + return; |
| 265 | + |
| 266 | + Assertion(a >= 0 && a < max_slots, "%s: slot 'a' %d out of range", caller, a); |
| 267 | + Assertion(b >= 0 && b < max_slots, "%s: slot 'b' %d out of range", caller, b); |
| 268 | + Assertion(!slot_is_empty(a) && !slot_is_empty(b), |
| 269 | + "%s: both slots must be valid (a=%d, b=%d)", caller, a, b); |
| 270 | + |
| 271 | + // Find a free temporary slot. |
| 272 | + int tmp = find_free_slot(max_slots, slot_is_empty, caller); |
| 273 | + |
| 274 | + // Three-leg swap; each call's preconditions hold by construction. The |
| 275 | + // total-rebuild fixups are deferred to the final leg. |
| 276 | + reassign(a, tmp, cfg, false); |
| 277 | + reassign(b, a, cfg, false); |
| 278 | + reassign(tmp, b, cfg, true); |
| 279 | +} |
| 280 | + |
| 281 | +template <typename TConfig> |
| 282 | +static void rotate_slots(const SCP_vector<int>& slots, int from_pos, int to_pos, const TConfig& cfg, |
| 283 | + int max_slots, bool (*slot_is_empty)(int), void (*reassign)(int, int, const TConfig&, bool), const char* caller) |
| 284 | +{ |
| 285 | + if (from_pos == to_pos) |
| 286 | + return; |
| 287 | + |
| 288 | + int count = (int)slots.size(); |
| 289 | + Assertion(from_pos >= 0 && from_pos < count, "%s: 'from' position %d out of range", caller, from_pos); |
| 290 | + Assertion(to_pos >= 0 && to_pos < count, "%s: 'to' position %d out of range", caller, to_pos); |
| 291 | + |
| 292 | + // Find a free temporary slot. |
| 293 | + int tmp = find_free_slot(max_slots, slot_is_empty, caller); |
| 294 | + |
| 295 | + // Park the moving item in the free slot, shift everything between the two |
| 296 | + // positions over by one, then drop the item into the slot vacated at the |
| 297 | + // far end. Preserves the relative order of everything else, in K+2 |
| 298 | + // reassignments for a move of K positions (vs 3K for a bubble of swaps). |
| 299 | + // The total-rebuild fixups are deferred to the final leg. |
| 300 | + int step = (to_pos > from_pos) ? 1 : -1; |
| 301 | + reassign(slots[from_pos], tmp, cfg, false); |
| 302 | + for (int j = from_pos; j != to_pos; j += step) |
| 303 | + reassign(slots[j + step], slots[j], cfg, false); |
| 304 | + reassign(tmp, slots[to_pos], cfg, true); |
| 305 | +} |
| 306 | + |
| 307 | +void swap_ship_slots(int a, int b, const FredShipSlotConfig& cfg) |
| 308 | +{ |
| 309 | + swap_slots(a, b, cfg, MAX_SHIPS, ship_slot_is_empty, reassign_ship_slot, "swap_ship_slots"); |
| 310 | +} |
| 311 | + |
| 312 | +void rotate_ship_slots(const SCP_vector<int>& slots, int from_pos, int to_pos, const FredShipSlotConfig& cfg) |
| 313 | +{ |
| 314 | + rotate_slots(slots, from_pos, to_pos, cfg, MAX_SHIPS, ship_slot_is_empty, reassign_ship_slot, "rotate_ship_slots"); |
| 315 | +} |
| 316 | + |
| 317 | +// Bulk-re-sort one type's subset of obj_used_list while keeping non-matching |
| 318 | +// entries in their original relative positions. Each callsite supplies a |
| 319 | +// type matcher and a key function; the i-th matching slot (in original list |
| 320 | +// order) receives the i-th smallest matching node by key. |
| 321 | +static void resort_obj_used_list_subset( |
| 322 | + bool (*matches_type)(int), |
| 323 | + int (*key)(const object*)) |
| 324 | +{ |
| 325 | + SCP_vector<object*> all; |
| 326 | + SCP_vector<object*> matched; |
| 327 | + for (auto o : list_range(&obj_used_list)) |
| 328 | + { |
| 329 | + all.push_back(o); |
| 330 | + if (matches_type(o->type)) |
| 331 | + matched.push_back(o); |
| 332 | + } |
| 333 | + |
| 334 | + std::sort(matched.begin(), matched.end(), |
| 335 | + [&](const object* a, const object* b) { return key(a) < key(b); }); |
| 336 | + |
| 337 | + list_init(&obj_used_list); |
| 338 | + auto it = matched.begin(); |
| 339 | + for (auto o : all) |
| 340 | + { |
| 341 | + if (matches_type(o->type)) |
| 342 | + { |
| 343 | + list_append(&obj_used_list, *it); |
| 344 | + ++it; |
| 345 | + } |
| 346 | + else |
| 347 | + { |
| 348 | + list_append(&obj_used_list, o); |
| 349 | + } |
| 350 | + } |
| 351 | +} |
| 352 | + |
| 353 | +void resort_ships_in_obj_used_list() |
| 354 | +{ |
| 355 | + resort_obj_used_list_subset( |
| 356 | + [](int t) { return t == OBJ_SHIP || t == OBJ_START; }, |
| 357 | + [](const object* o) { return o->instance; }); |
| 358 | +} |
0 commit comments