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
359 changes: 10 additions & 349 deletions code/missioneditor/common.cpp
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
// methods and members common to any mission editor FSO may have
#include "common.h"
#include "ai/ai.h"
#include "ai/aigoals.h"
#include "globalincs/linklist.h"
#include "mission/missionparse.h"
#include "iff_defs/iff_defs.h"
#include "jumpnode/jumpnode.h"
#include "object/object.h"
#include "object/waypoint.h"
#include "prop/prop.h"
#include "parse/sexp.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 @@ -318,352 +317,14 @@ SCP_string check_name_conflict(const char *entity_type, const char *name, int ex
return ""; // no error
}

void reassign_ship_slot(int from, int to, const FredShipSlotConfig& cfg, bool resort_obj_list)
{
Assertion(Fred_running, "reassign_ship_slot is FRED-only: it does not fix up game-context references like Player_ship or runtime Ai_info shipnums");
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. Ship's move operations handle the members
// that need care (the subsys_list sentinel re-links its bookend nodes, and
// the owning pointers are unique_ptrs); this function's job is the external
// back-references. Per the engine's convention, a slot with objnum < 0 is
// considered empty.
Ships[to] = std::move(Ships[from]);
Ships[from].objnum = -1;

// 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).
if (Ships[to].ai_index >= 0)
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)
// Update AI goal references in every Ai_info entry and every wing.
void ai_update_goal_references(sexp_ref_type type, const char *old_name, const char *new_name)
{
return Wings[i].wave_count == 0;
}

static int find_free_slot(int max_slots, bool (*slot_is_empty)(int))
{
for (int i = 0; i < max_slots; ++i)
{
if (slot_is_empty(i))
return i;
}
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);
if (tmp < 0)
{
ReleaseWarning(LOCATION, "%s: no free slot available for the temporary leg", caller);
return;
}

// 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 = static_cast<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);
if (tmp < 0)
{
ReleaseWarning(LOCATION, "%s: no free slot available for the temporary leg", caller);
return;
}

// 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(Fred_running, "reassign_wing_slot is FRED-only");
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; });
}

void resort_props_in_obj_used_list()
{
resort_obj_used_list_subset(
[](int t) { return t == OBJ_PROP; },
[](const object* o) { return o->instance; });
}

void rotate_prop_slots(const SCP_vector<int>& slots, int from_pos, int to_pos)
{
Assertion(Fred_running, "rotate_prop_slots is FRED-only: it re-points object instances only, not any game-context state");
if (from_pos == to_pos)
return;

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

// Permute the prop occupants among the slot indices, leaving any empty
// holes where they are. Park the mover, shift the rest over by one, then drop
// the mover into the slot vacated at the far end.
std::optional<prop> moving = std::move(Props[slots[from_pos]]);
int step = (to_pos > from_pos) ? 1 : -1;
for (int j = from_pos; j != to_pos; j += step)
Props[slots[j]] = std::move(Props[slots[j + step]]);
Props[slots[to_pos]] = std::move(moving);

// Re-point each moved prop's object instance to its new Props[] index. Only
// the occupants between the two positions changed slots.
int lo = std::min(from_pos, to_pos);
int hi = std::max(from_pos, to_pos);
for (int j = lo; j <= hi; ++j)
{
if (Props[slots[j]].has_value())
Objects[Props[slots[j]]->objnum].instance = slots[j];
}

// Keep obj_used_list prop order in sync with Props[] index order, so
// the Scene Browser reflects the new order too.
resort_props_in_obj_used_list();
}

void rotate_waypoint_lists(int from_pos, int to_pos)
{
Assertion(Fred_running, "rotate_waypoint_lists is FRED-only: it re-points object instances only, not any game-context state");
if (from_pos == to_pos)
return;

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

// Reorder the lists themselves, preserving the relative order of the rest.
if (from_pos < to_pos)
std::rotate(Waypoint_lists.begin() + from_pos, Waypoint_lists.begin() + from_pos + 1, Waypoint_lists.begin() + to_pos + 1);
else
std::rotate(Waypoint_lists.begin() + to_pos, Waypoint_lists.begin() + from_pos, Waypoint_lists.begin() + from_pos + 1);

// A waypoint object encodes its list index, point index in its instance, so
// every waypoint in the shifted lists now sits at a new list index. Rebuild
// all instances from the lists' current positions.
int li = 0;
for (auto& list : Waypoint_lists)
{
int wi = 0;
for (auto& wpt : list.get_waypoints())
{
int objnum = wpt.get_objnum();
if (objnum >= 0)
Objects[objnum].instance = calc_waypoint_instance(li, wi);
++wi;
}
++li;
}
}

void rotate_jump_nodes(const SCP_vector<int>& slots, int from_pos, int to_pos)
{
Assertion(Fred_running, "rotate_jump_nodes is FRED-only: it reorders the editor's jump-node list only");
if (from_pos == to_pos)
return;
for (int i = 0; i < MAX_AI_INFO; i++) // loop through all Ai_info entries
if (Ai_info[i].shipnum >= 0) // skip if unused
ai_update_goal_references(Ai_info[i].goals, type, old_name, new_name);

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

// Permute the node occupants among the fixed slot indices, leaving any
// detached entries in place. Park the mover, shift the rest over by one, then
// drop the mover into the slot vacated at the far end.
CJumpNode moving = std::move(Jump_nodes[slots[from_pos]]);
int step = (to_pos > from_pos) ? 1 : -1;
for (int j = from_pos; j != to_pos; j += step)
Jump_nodes[slots[j]] = std::move(Jump_nodes[slots[j + step]]);
Jump_nodes[slots[to_pos]] = std::move(moving);
for (auto &w : Wings) // loop through all wings
if (w.wave_count > 0) // skip if unpopulated
ai_update_goal_references(w.ai_goals, type, old_name, new_name);
}
Loading
Loading