Skip to content

Commit a5bdef3

Browse files
Goober5000claude
andcommitted
add Ships[] slot reassignment utilities for FRED/qtFRED
reassign_ship_slot moves a ship between Ships[] slots, fixing up every back-reference: Objects[].instance, Ai_info[].shipnum, Wings[].ship_index[], Player_start_shipnum, Ship_registry's cached shipnum, and the FRED-side parallel arrays Fred_alt_names/Fred_callsigns plus cur_ship (passed via FredShipSlotConfig so non-FRED callers can opt out). swap_ship_slots wraps it as a three-leg swap through a temporary empty slot. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 0076cdb commit a5bdef3

2 files changed

Lines changed: 190 additions & 0 deletions

File tree

code/missioneditor/common.cpp

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
// methods and members common to any mission editor FSO may have
22
#include "common.h"
3+
#include "ai/ai.h"
4+
#include "globalincs/linklist.h"
35
#include "mission/missionparse.h"
46
#include "iff_defs/iff_defs.h"
7+
#include "object/object.h"
58
#include "ship/ship.h"
69

10+
#include <algorithm>
11+
#include <climits>
12+
713
// to keep track of data
814
char Voice_abbrev_briefing[NAME_LENGTH];
915
char Voice_abbrev_campaign[NAME_LENGTH];
@@ -145,3 +151,162 @@ void generate_weaponry_usage_list_wing(int wing_num, int* arr)
145151
}
146152
}
147153
}
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+
}

code/missioneditor/common.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,28 @@ anchor_t target_to_anchor(int target);
4848
void generate_weaponry_usage_list_team(int team, int* arr);
4949

5050
void generate_weaponry_usage_list_wing(int wing_num, int* arr);
51+
52+
struct FredShipSlotConfig
53+
{
54+
char (*fred_alt_names)[NAME_LENGTH + 1] = nullptr;
55+
char (*fred_callsigns)[NAME_LENGTH + 1] = nullptr;
56+
57+
int *cur_ship = nullptr;
58+
};
59+
60+
// Move the ship currently in Ships[from] into Ships[to], updating every
61+
// back-reference (Objects, Ai_info, Wings, Player_start_shipnum, Ship_registry,
62+
// and editor-side fields supplied via cfg). Leaves Ships[from] empty.
63+
// Preconditions: from != to, Ships[from].objnum >= 0, Ships[to].objnum < 0.
64+
// No caller may hold a ship* to either slot across this call.
65+
// Fields in cfg whose pointers are nullptr are skipped.
66+
void reassign_ship_slot(int from, int to, const FredShipSlotConfig& cfg);
67+
68+
// Swap the contents of two slots. Both must be valid (Ships[a].objnum >= 0
69+
// and Ships[b].objnum >= 0). Implemented as three calls to reassign_ship_slot
70+
// via a temporary empty slot.
71+
void swap_ship_slots(int a, int b, const FredShipSlotConfig& cfg);
72+
73+
// Restore the obj_used_list invariant for the OBJ_SHIP/OBJ_START subset:
74+
// among ship-type entries, list order matches Ships[] index order.
75+
void resort_ships_in_obj_used_list();

0 commit comments

Comments
 (0)