-
Notifications
You must be signed in to change notification settings - Fork 182
Expand file tree
/
Copy pathcommon.cpp
More file actions
429 lines (367 loc) · 14.3 KB
/
Copy pathcommon.cpp
File metadata and controls
429 lines (367 loc) · 14.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
// 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];
char Voice_abbrev_command_briefing[NAME_LENGTH];
char Voice_abbrev_debriefing[NAME_LENGTH];
char Voice_abbrev_message[NAME_LENGTH];
char Voice_abbrev_mission[NAME_LENGTH];
bool Voice_no_replace_filenames;
char Voice_script_entry_format[NOTES_LENGTH];
int Voice_export_selection; // 0=everything, 1=cmd brief, 2=brief, 3=debrief, 4=messages
bool Voice_group_messages;
SCP_string Voice_script_default_string = "Sender: $sender\r\nPersona: $persona\r\nFile: $filename\r\nMessage: $message";
SCP_string Voice_script_instructions_string = "$name - name of the message\r\n"
"$filename - name of the message file\r\n"
"$message - text of the message\r\n"
"$persona - persona of the sender\r\n"
"$sender - name of the sender\r\n"
"$note - message notes\r\n\r\n"
"Note that $persona and $sender will only appear for the Message section.";
float normalize_degrees(float deg)
{
while (deg < -180.0f)
deg += 360.0f;
while (deg > 180.0f)
deg -= 360.0f;
// check for negative zero
if (deg == -0.0f)
deg = 0.0f;
return deg;
}
void time_to_mission_info_string(const std::tm* src, char* dest, size_t dest_max_len)
{
std::strftime(dest, dest_max_len, "%x at %X", src);
}
void stuff_special_arrival_anchor_name(char* buf, int iff_index, int restrict_to_players, bool retail_format)
{
const char* iff_name = Iff_info[iff_index].iff_name;
// stupid retail hack
if (retail_format && !stricmp(iff_name, "hostile") && !restrict_to_players)
iff_name = "enemy";
if (restrict_to_players)
sprintf(buf, "<any %s player>", iff_name);
else
sprintf(buf, "<any %s>", iff_name);
strlwr(buf);
}
void stuff_special_arrival_anchor_name(char* buf, int anchor_num, bool retail_format)
{
// filter out iff
int iff_index = anchor_num;
iff_index &= ~ANCHOR_SPECIAL_ARRIVAL;
iff_index &= ~ANCHOR_SPECIAL_ARRIVAL_PLAYER;
// filter players
int restrict_to_players = (anchor_num & ANCHOR_SPECIAL_ARRIVAL_PLAYER);
// get name
stuff_special_arrival_anchor_name(buf, iff_index, restrict_to_players, retail_format);
}
// Ship and wing arrival and departure anchors should always be ship registry entry indexes, except for a very brief window during mission parsing.
// But FRED and QtFRED dialogs use ship indexes instead. So, rather than refactor all the dialogs, this converts between the two. If an anchor
// is a valid ship registry index, the equivalent ship index is returned; otherwise the special value (-1 or a flag) is returned instead.
int anchor_to_target(anchor_t anchor)
{
auto anchor_entry = ship_registry_get(anchor);
return anchor_entry ? anchor_entry->shipnum : anchor.value();
}
// Ship and wing arrival and departure anchors should always be ship registry entry indexes, except for a very brief window during mission parsing.
// But FRED and QtFRED dialogs use ship indexes instead. So, rather than refactor all the dialogs, this converts between the two. If a target
// is a valid ship index, the equivalent ship registry index is returned; otherwise the special value (-1 or a flag) is returned instead.
anchor_t target_to_anchor(int target)
{
if (target >= 0 && target < MAX_SHIPS)
return anchor_t(ship_registry_get_index(Ships[target].ship_name));
else
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;
for (i = 0; i < MAX_WEAPON_TYPES; i++) {
arr[i] = 0;
}
if (The_mission.game_type & MISSION_TYPE_MULTI_TEAMS) {
Assert(team >= 0 && team < MAX_TVT_TEAMS);
for (i = 0; i < MAX_TVT_WINGS_PER_TEAM; i++) {
generate_weaponry_usage_list_wing(TVT_wings[(team * MAX_TVT_WINGS_PER_TEAM) + i], arr);
}
} else {
for (i = 0; i < MAX_STARTING_WINGS; i++) {
generate_weaponry_usage_list_wing(Starting_wings[i], arr);
}
}
}
void generate_weaponry_usage_list_wing(int wing_num, int* arr)
{
int i, j;
ship_weapon* swp;
if (wing_num < 0) {
return;
}
i = Wings[wing_num].wave_count;
while (i--) {
swp = &Ships[Wings[wing_num].ship_index[i]].weapons;
j = swp->num_primary_banks;
while (j--) {
if (swp->primary_bank_weapons[j] >= 0 &&
swp->primary_bank_weapons[j] < static_cast<int>(Weapon_info.size())) {
arr[swp->primary_bank_weapons[j]]++;
}
}
j = swp->num_secondary_banks;
while (j--) {
if (swp->secondary_bank_weapons[j] >= 0 &&
swp->secondary_bank_weapons[j] < static_cast<int>(Weapon_info.size())) {
arr[swp->secondary_bank_weapons[j]] +=
(int)floor((swp->secondary_bank_ammo[j] * swp->secondary_bank_capacity[j] / 100.0f /
Weapon_info[swp->secondary_bank_weapons[j]].cargo_size) +
0.5f);
}
}
}
}
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; });
}