Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
282 changes: 282 additions & 0 deletions code/missioneditor/common.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
// methods and members common to any mission editor FSO may have
#include "common.h"
#include "ai/ai.h"
#include "globalincs/linklist.h"
#include "mission/missionparse.h"
#include "iff_defs/iff_defs.h"
#include "object/object.h"
#include "ship/ship.h"

#include <algorithm>

// to keep track of data
char Voice_abbrev_briefing[NAME_LENGTH];
char Voice_abbrev_campaign[NAME_LENGTH];
Expand Down Expand Up @@ -92,6 +97,20 @@ anchor_t target_to_anchor(int target)
return anchor_t(target);
}

void update_custom_wing_indexes()
{
int i;

for (i = 0; i < MAX_STARTING_WINGS; i++)
Starting_wings[i] = wing_name_lookup(Starting_wing_names[i], 1);

for (i = 0; i < MAX_SQUADRON_WINGS; i++)
Squadron_wings[i] = wing_name_lookup(Squadron_wing_names[i], 1);

for (i = 0; i < MAX_TVT_WINGS; i++)
TVT_wings[i] = wing_name_lookup(TVT_wing_names[i], 1);
}

void generate_weaponry_usage_list_team(int team, int* arr)
{
int i;
Expand Down Expand Up @@ -145,3 +164,266 @@ void generate_weaponry_usage_list_wing(int wing_num, int* arr)
}
}
}

void reassign_ship_slot(int from, int to, const FredShipSlotConfig& cfg, bool resort_obj_list)
{
Assertion(from != to, "reassign_ship_slot: from == to (%d)", from);
Assertion(from >= 0 && from < MAX_SHIPS, "reassign_ship_slot: 'from' slot %d out of range", from);
Assertion(to >= 0 && to < MAX_SHIPS, "reassign_ship_slot: 'to' slot %d out of range", to);
Assertion(Ships[from].objnum >= 0, "reassign_ship_slot: source slot %d is empty", from);
Assertion(Ships[to].objnum < 0, "reassign_ship_slot: destination slot %d is occupied", to);

// Move the ship struct itself. Per the engine's convention, a slot with
// objnum < 0 is considered empty; other fields in the vacated slot are
// left as-is (unreachable through the "is this slot used" guard).
// Move (not copy) because ship contains a unique_ptr member.
Ships[to] = std::move(Ships[from]);
Ships[from].objnum = -1;

// subsys_list is an intrusive doubly-linked list whose head sentinel's
// address is meaningful: real nodes' prev/next bookend back to &head, and
// an empty list is self-referential. The move copied those pointers
// verbatim, so they still reference the old (vacated) sentinel address.
{
auto old_head = &Ships[from].subsys_list;
auto new_head = &Ships[to].subsys_list;
if (new_head->next == old_head)
{
// Empty list: re-init self-referential on the new head.
new_head->next = new_head;
new_head->prev = new_head;
}
else
{
// Non-empty: repoint the first node's prev and the last node's next.
new_head->next->prev = new_head;
new_head->prev->next = new_head;
}
}

// Move FRED-side parallel arrays if the caller supplied them.
if (cfg.fred_alt_names != nullptr)
{
strcpy_s(cfg.fred_alt_names[to], cfg.fred_alt_names[from]);
cfg.fred_alt_names[from][0] = '\0';
}
if (cfg.fred_callsigns != nullptr)
{
strcpy_s(cfg.fred_callsigns[to], cfg.fred_callsigns[from]);
cfg.fred_callsigns[from][0] = '\0';
}

// Object back-reference.
Objects[Ships[to].objnum].instance = to;

// Keep obj_used_list iteration order in sync with Ships[] slot order. The
// re-sort is a total rebuild rather than an incremental fix, so a caller
// making a batch of reassignments may defer it to the final call.
if (resort_obj_list)
resort_ships_in_obj_used_list();

// AI back-reference (the one invariant codified by internal_integrity_check).
Ai_info[Ships[to].ai_index].shipnum = to;

// Wing membership: scan every wing and re-point any reference to the old slot.
// (wing.special_ship is wing-relative, NOT a Ships[] index, so it is intentionally
// not touched here.)
for (auto &w: Wings)
{
if (w.wave_count == 0)
continue;
for (int k = 0; k < w.wave_count; ++k)
{
if (w.ship_index[k] == from)
w.ship_index[k] = to;
}
}

// Single-player start.
if (Player_start_shipnum == from)
Player_start_shipnum = to;

// Ship_registry caches the shipnum on its entries (lookup is by name, but the
// cached integer would otherwise go stale).
int reg = ship_registry_get_index(Ships[to].ship_name);
if (reg >= 0)
Ship_registry[reg].shipnum = to;

// FRED's current-ship pointer, if the caller is tracking one.
if (cfg.cur_ship != nullptr && *cfg.cur_ship == from)
*cfg.cur_ship = to;
}

static bool ship_slot_is_empty(int i)
{
return Ships[i].objnum < 0;
}

static bool wing_slot_is_empty(int i)
{
return Wings[i].wave_count == 0;
}

static int find_free_slot(int max_slots, bool (*slot_is_empty)(int), const char* caller)
{
for (int i = 0; i < max_slots; ++i)
{
if (slot_is_empty(i))
return i;
}
Assertion(false, "%s: no free slot available for the temporary leg", caller);
return -1;
}

template <typename TConfig>
static void swap_slots(int a, int b, const TConfig& cfg, int max_slots,
bool (*slot_is_empty)(int), void (*reassign)(int, int, const TConfig&, bool), const char* caller)
{
if (a == b)
return;

Assertion(a >= 0 && a < max_slots, "%s: slot 'a' %d out of range", caller, a);
Assertion(b >= 0 && b < max_slots, "%s: slot 'b' %d out of range", caller, b);
Assertion(!slot_is_empty(a) && !slot_is_empty(b),
"%s: both slots must be valid (a=%d, b=%d)", caller, a, b);

// Find a free temporary slot.
int tmp = find_free_slot(max_slots, slot_is_empty, caller);

// Three-leg swap; each call's preconditions hold by construction. The
// total-rebuild fixups are deferred to the final leg.
reassign(a, tmp, cfg, false);
reassign(b, a, cfg, false);
reassign(tmp, b, cfg, true);
}

template <typename TConfig>
static void rotate_slots(const SCP_vector<int>& slots, int from_pos, int to_pos, const TConfig& cfg,
int max_slots, bool (*slot_is_empty)(int), void (*reassign)(int, int, const TConfig&, bool), const char* caller)
{
if (from_pos == to_pos)
return;

int count = (int)slots.size();
Assertion(from_pos >= 0 && from_pos < count, "%s: 'from' position %d out of range", caller, from_pos);
Assertion(to_pos >= 0 && to_pos < count, "%s: 'to' position %d out of range", caller, to_pos);

// Find a free temporary slot.
int tmp = find_free_slot(max_slots, slot_is_empty, caller);

// Park the moving item in the free slot, shift everything between the two
// positions over by one, then drop the item into the slot vacated at the
// far end. Preserves the relative order of everything else, in K+2
// reassignments for a move of K positions (vs 3K for a bubble of swaps).
// The total-rebuild fixups are deferred to the final leg.
int step = (to_pos > from_pos) ? 1 : -1;
reassign(slots[from_pos], tmp, cfg, false);
for (int j = from_pos; j != to_pos; j += step)
reassign(slots[j + step], slots[j], cfg, false);
reassign(tmp, slots[to_pos], cfg, true);
}

void swap_ship_slots(int a, int b, const FredShipSlotConfig& cfg)
{
swap_slots(a, b, cfg, MAX_SHIPS, ship_slot_is_empty, reassign_ship_slot, "swap_ship_slots");
}

void rotate_ship_slots(const SCP_vector<int>& slots, int from_pos, int to_pos, const FredShipSlotConfig& cfg)
{
rotate_slots(slots, from_pos, to_pos, cfg, MAX_SHIPS, ship_slot_is_empty, reassign_ship_slot, "rotate_ship_slots");
}

void reassign_wing_slot(int from, int to, const FredWingSlotConfig& cfg, bool update_wing_indexes)
{
Assertion(from != to, "reassign_wing_slot: from == to (%d)", from);
Assertion(from >= 0 && from < MAX_WINGS, "reassign_wing_slot: 'from' slot %d out of range", from);
Assertion(to >= 0 && to < MAX_WINGS, "reassign_wing_slot: 'to' slot %d out of range", to);
Assertion(Wings[from].wave_count > 0, "reassign_wing_slot: source slot %d is empty", from);
Assertion(Wings[to].wave_count == 0, "reassign_wing_slot: destination slot %d is occupied", to);

// Move the wing struct itself; wave_count == 0 is the sentinel for an empty wing.
Wings[to] = std::move(Wings[from]);
Wings[from].wave_count = 0;

// Move FRED-side parallel array if the caller supplied it.
if (cfg.wing_objects != nullptr)
{
for (int k = 0; k < MAX_SHIPS_PER_WING; ++k)
{
cfg.wing_objects[to][k] = cfg.wing_objects[from][k];
cfg.wing_objects[from][k] = -1;
}
}

// Per-ship parent-wing back-reference.
for (auto &s: Ships)
{
if (s.objnum < 0)
continue;
if (s.wingnum == from)
s.wingnum = to;
}

// FRED's current-wing pointer, if the caller is tracking one.
if (cfg.cur_wing != nullptr && *cfg.cur_wing == from)
*cfg.cur_wing = to;

// Rebuild Starting/Squadron/TVT_wings caches from the parallel name arrays.
// The rebuild is total rather than incremental, so a caller making a batch
// of reassignments may defer it to the final call.
if (update_wing_indexes)
update_custom_wing_indexes();
}

void swap_wing_slots(int a, int b, const FredWingSlotConfig& cfg)
{
swap_slots(a, b, cfg, MAX_WINGS, wing_slot_is_empty, reassign_wing_slot, "swap_wing_slots");
}

void rotate_wing_slots(const SCP_vector<int>& slots, int from_pos, int to_pos, const FredWingSlotConfig& cfg)
{
rotate_slots(slots, from_pos, to_pos, cfg, MAX_WINGS, wing_slot_is_empty, reassign_wing_slot, "rotate_wing_slots");
}

// Bulk-re-sort one type's subset of obj_used_list while keeping non-matching
// entries in their original relative positions. Each callsite supplies a
// type matcher and a key function; the i-th matching slot (in original list
// order) receives the i-th smallest matching node by key.
static void resort_obj_used_list_subset(
bool (*matches_type)(int),
int (*key)(const object*))
{
SCP_vector<object*> all;
SCP_vector<object*> matched;
for (auto o : list_range(&obj_used_list))
{
all.push_back(o);
if (matches_type(o->type))
matched.push_back(o);
}

std::sort(matched.begin(), matched.end(),
[&](const object* a, const object* b) { return key(a) < key(b); });

list_init(&obj_used_list);
auto it = matched.begin();
for (auto o : all)
{
if (matches_type(o->type))
{
list_append(&obj_used_list, *it);
++it;
}
else
{
list_append(&obj_used_list, o);
}
}
}

void resort_ships_in_obj_used_list()
{
resort_obj_used_list_subset(
[](int t) { return t == OBJ_SHIP || t == OBJ_START; },
[](const object* o) { return o->instance; });
}
57 changes: 57 additions & 0 deletions code/missioneditor/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,63 @@ int anchor_to_target(anchor_t anchor);

anchor_t target_to_anchor(int target);

// Rebuild Starting_wings[], Squadron_wings[], TVT_wings[] from their parallel
// name arrays via wing_name_lookup. Consolidated from FRED's and qtFRED's
// previously-duplicated copies.
void update_custom_wing_indexes();

void generate_weaponry_usage_list_team(int team, int* arr);

void generate_weaponry_usage_list_wing(int wing_num, int* arr);

struct FredShipSlotConfig
{
char (*fred_alt_names)[NAME_LENGTH + 1] = nullptr;
char (*fred_callsigns)[NAME_LENGTH + 1] = nullptr;

int *cur_ship = nullptr;
};

// Move the ship currently in Ships[from] into Ships[to], updating every
// back-reference (Objects, Ai_info, Wings, Player_start_shipnum, Ship_registry,
// and editor-side fields supplied via cfg). Leaves Ships[from] empty.
// Preconditions: from != to, Ships[from].objnum >= 0, Ships[to].objnum < 0.
// No caller may hold a ship* to either slot across this call.
// Fields in cfg whose pointers are nullptr are skipped.
void reassign_ship_slot(int from, int to, const FredShipSlotConfig& cfg, bool resort_obj_list = true);

// Swap the contents of two slots. Both must be valid (Ships[a].objnum >= 0
// and Ships[b].objnum >= 0). Implemented as three calls to reassign_ship_slot
// via a temporary empty slot.
void swap_ship_slots(int a, int b, const FredShipSlotConfig& cfg);

// Move the item at position from_pos in slots to position to_pos, shifting the
// items in between by one position.
void rotate_ship_slots(const SCP_vector<int>& slots, int from_pos, int to_pos, const FredShipSlotConfig& cfg);

struct FredWingSlotConfig
{
int (*wing_objects)[MAX_SHIPS_PER_WING] = nullptr;
int *cur_wing = nullptr;
};

// Move the wing currently in Wings[from] into Wings[to], updating every
// back-reference (Ships[i].wingnum, Starting/Squadron/TVT_wings caches, and
// editor-side fields supplied via cfg). Leaves Wings[from] empty.
// Preconditions: from != to, Wings[from].wave_count > 0, Wings[to].wave_count == 0.
// No caller may hold a wing* to either slot across this call.
// Fields in cfg whose pointers are nullptr are skipped.
void reassign_wing_slot(int from, int to, const FredWingSlotConfig& cfg, bool update_wing_indexes = true);

// Swap the contents of two slots. Both must be valid (Wings[a].wave_count > 0
// and Wings[b].wave_count > 0). Implemented as three calls to
// reassign_wing_slot via a temporary empty slot.
void swap_wing_slots(int a, int b, const FredWingSlotConfig& cfg);

// Move the item at position from_pos in slots to position to_pos, shifting the
// items in between by one position.
void rotate_wing_slots(const SCP_vector<int>& slots, int from_pos, int to_pos, const FredWingSlotConfig& cfg);

// Restore the obj_used_list invariant for the OBJ_SHIP/OBJ_START subset:
// among ship-type entries, list order matches Ships[] index order.
void resort_ships_in_obj_used_list();
2 changes: 2 additions & 0 deletions fred2/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ set(FRED2_SOURCES
propdlg.h
reinforcementeditordlg.cpp
reinforcementeditordlg.h
reorderdlg.cpp
reorderdlg.h
resource.h
restrictpaths.cpp
restrictpaths.h
Expand Down
Loading
Loading