Files
Goober5000 dab690eab8 Merge pull request #7600 from Goober5000/fix/ship_demotion
fix a bug with player ships being demoted to common ships
2026-07-12 20:15:54 -04:00

489 lines
12 KiB
C++

/*
* Copyright (C) Volition, Inc. 1999. All rights reserved.
*
* All source code herein is the property of Volition, Inc. You may not sell
* or otherwise commercially exploit the source or things you created based on the
* source.
*
*/
#include "stdafx.h"
#include "FRED.h"
#include "CreateWingDlg.h"
#include "FREDDoc.h"
#include "FREDView.h"
#include "MainFrm.h"
#include "Management.h"
#include "Management.h"
#include "wing.h"
#include "globalincs/linklist.h"
#include "math/vecmat.h"
#include "object/object.h"
#include "physics/physics.h"
#include "render/3d.h"
#include "ship/ship.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
#define MULTI_WING 999999
int already_deleting_wing = 0;
int Wings_initialized = 0;
// Declared here
/**
* @brief Takes a player out of a wing, deleting wing if that was the only ship in it.
*/
void remove_player_from_wing(int player, int min = 1);
/**
* @brief Checks wing dependencies
*
* @TODO verify
*/
int check_wing_dependencies(int wing_num);
/**
* @brief Initializes the wings and formations
*
* @note Looks incomplete. Only init's the first formation
* @TODO verify
*/
void initialize_wings(void);
/**
* @brief Gets the index of the first available object slot
*
*
* @returns The objindex of the first available slot, or
* @returns -1 if no slots available
*
* @note Unused
* @TODO verify and maybe remove
*/
int get_free_objnum(void);
/**
* @brief Finds a free wing slot (i.e. unused)
*/
int find_free_wing();
int check_wing_dependencies(int wing_num) {
char *name;
name = Wings[wing_num].name;
return reference_handler(name, sexp_ref_type::WING, -1);
}
int create_wing() {
char msg[1024];
int i, ship, wing = -1, waypoints = 0, count = 0, illegal_ships = 0;
int leader, leader_team;
object *ptr;
create_wing_dlg dlg;
if (!query_valid_object())
return -1;
leader = cur_object_index;
ptr = GET_FIRST(&obj_used_list);
while (ptr != END_OF_LIST(&obj_used_list)) {
if (( (ptr->type == OBJ_SHIP) || (ptr->type == OBJ_START) ) && (ptr->flags[Object::Object_Flags::Marked])) {
count++;
i = -1;
switch (ptr->type) {
case OBJ_SHIP:
case OBJ_START:
i = Ships[ptr->instance].wingnum;
break;
}
if (i >= 0) {
if (wing < 0)
wing = i;
else if (wing != i)
wing = MULTI_WING;
}
}
ptr = GET_NEXT(ptr);
}
if (count > MAX_SHIPS_PER_WING) {
sprintf(msg, "You have too many ships marked!\n"
"A wing is limited to %d ships total", MAX_SHIPS_PER_WING);
Fred_main_wnd->MessageBox(msg, "Error", MB_ICONEXCLAMATION);
return -1;
}
if ((wing >= 0) && (wing != MULTI_WING)) {
sprintf(msg, "Do you want to reform wing \"%s\"?", Wings[wing].name);
i = Fred_main_wnd->MessageBox(msg, "Query", MB_YESNOCANCEL);
if (i == IDCANCEL)
return -1;
else if (i == IDNO)
wing = -1;
else { // must be IDYES
for (i = Wings[wing].wave_count - 1; i >= 0; i--) {
ptr = &Objects[wing_objects[wing][i]];
switch (ptr->type) {
case OBJ_SHIP:
remove_ship_from_wing(ptr->instance, 0);
break;
case OBJ_START:
remove_player_from_wing(ptr->instance, 0);
break;
default:
Int3(); // shouldn't be in a wing!
}
}
Assert(!Wings[wing].wave_count);
Num_wings--;
}
} else
wing = -1;
if (wing < 0) {
wing = find_free_wing();
if (wing < 0) {
Fred_main_wnd->MessageBox("Too many wings, can't create more!",
"Error", MB_ICONEXCLAMATION);
return -1;
}
Wings[wing].clear();
Wings[wing].arrival_cue = Locked_sexp_true;
Wings[wing].departure_cue = Locked_sexp_false;
if (dlg.DoModal() == IDCANCEL)
return -1;
dlg.m_name.TrimLeft();
dlg.m_name.TrimRight();
string_copy(Wings[wing].name, dlg.m_name, NAME_LENGTH - 1);
// if this name has a hash, create a default display name
if (get_pointer_to_first_hash_symbol(Wings[wing].name))
{
Wings[wing].display_name = Wings[wing].name;
end_string_at_first_hash_symbol(Wings[wing].display_name);
Wings[wing].flags.set(Ship::Wing_Flags::Has_display_name);
}
// otherwise reset the display name
else
{
Wings[wing].display_name = "";
Wings[wing].flags.remove(Ship::Wing_Flags::Has_display_name);
}
}
set_cur_indices(-1);
ptr = GET_FIRST(&obj_used_list);
while (ptr != END_OF_LIST(&obj_used_list)) {
if (ptr->flags[Object::Object_Flags::Marked]) {
// if ((ptr->type == OBJ_START) && (ptr->instance)) {
// starts++;
// unmark_object(OBJ_INDEX(ptr));
// } else if (ptr->type == OBJ_WAYPOINT) {
if (ptr->type == OBJ_WAYPOINT) {
waypoints++;
unmark_object(OBJ_INDEX(ptr));
} else if (ptr->type == OBJ_SHIP) {
int ship_type = ship_query_general_type(ptr->instance);
if (ship_type < 0 || !(Ship_types[ship_type].flags[Ship::Type_Info_Flags::AI_can_form_wing])) {
illegal_ships++;
unmark_object(OBJ_INDEX(ptr));
}
}
}
ptr = GET_NEXT(ptr);
}
// if this wing is a player starting wing, automatically set the hotkey for this wing
for (i = 0; i < MAX_STARTING_WINGS; i++) {
if (!stricmp(Wings[wing].name, Starting_wing_names[i])) {
Wings[wing].hotkey = i;
break;
}
}
count = 0;
if ((Player_start_shipnum >= 0) && (Player_start_shipnum < MAX_SHIPS) && (Ships[Player_start_shipnum].objnum >= 0)
&& Objects[Ships[Player_start_shipnum].objnum].flags[Object::Object_Flags::Marked])
count = 1;
ptr = GET_FIRST(&obj_used_list);
while (ptr != END_OF_LIST(&obj_used_list)) {
if (ptr->flags[Object::Object_Flags::Marked]) {
if ((ptr->type == OBJ_START) && (ptr->instance == Player_start_shipnum))
i = 0; // player 1 start always goes to front of the wing
else
i = count++;
Assert((ptr->type == OBJ_SHIP) || (ptr->type == OBJ_START));
ship = ptr->instance;
if (Ships[ship].wingnum != -1) {
if (ptr->type == OBJ_SHIP)
remove_ship_from_wing(ship);
else
remove_player_from_wing(ptr->instance);
}
wing_bash_ship_name(msg, Wings[wing].name, i + 1);
rename_ship(ship, msg);
// bash it again for the display name
wing_bash_ship_name(&Ships[ship], &Wings[wing], i + 1, true);
Wings[wing].ship_index[i] = ship;
Ships[ship].wingnum = wing;
if (Ships[ship].arrival_cue >= 0)
free_sexp2(Ships[ship].arrival_cue);
Ships[ship].arrival_cue = Locked_sexp_false;
if (Ships[ship].departure_cue >= 0)
free_sexp2(Ships[ship].departure_cue);
Ships[ship].departure_cue = Locked_sexp_false;
wing_objects[wing][i] = OBJ_INDEX(ptr);
if (OBJ_INDEX(ptr) == leader)
Wings[wing].special_ship = i;
}
ptr = GET_NEXT(ptr);
}
if (!count) // this should never happen, so if it does, needs to be fixed now.
Error(LOCATION, "No valid ships were selected to form wing from");
Wings[wing].wave_count = count;
Num_wings++;
// if (starts)
// Fred_main_wnd->MessageBox("Multi-player starting points can't be part of a wing!\n"
// "All marked multi-player starting points were ignored",
// "Error", MB_ICONEXCLAMATION);
if (waypoints)
Fred_main_wnd->MessageBox("Waypoints can't be part of a wing!\n"
"All marked waypoints were ignored",
"Error", MB_ICONEXCLAMATION);
if (illegal_ships)
Fred_main_wnd->MessageBox("Some ship types aren't allowed to be in a wing.\n"
"All marked ships of these types were ignored",
"Error", MB_ICONEXCLAMATION);
leader_team = Ships[Wings[wing].ship_index[Wings[wing].special_ship]].team;
for (i = 0; i < Wings[wing].wave_count; i++) {
if (Ships[Wings[wing].ship_index[i]].team != leader_team) {
Fred_main_wnd->MessageBox("Wing contains ships on different teams", "Warning");
break;
}
}
mark_wing(wing);
update_custom_wing_indexes();
return 0;
}
int delete_wing(int wing_num, int bypass) {
int i, r, total;
if (already_deleting_wing)
return 0;
r = check_wing_dependencies(wing_num);
if (r)
return r;
already_deleting_wing = 1;
delete_reinforcement(Wings[wing_num].name);
invalidate_references(Wings[wing_num].name, sexp_ref_type::WING);
if (!bypass) {
total = Wings[wing_num].wave_count;
for (i = 0; i<total; i++)
delete_object(wing_objects[wing_num][i]);
}
Wings[wing_num].wave_count = 0;
Wings[wing_num].wing_squad_filename[0] = '\0';
Wings[wing_num].wing_insignia_texture = -1;
if (cur_wing == wing_num)
set_cur_wing(cur_wing = -1); // yes, one '=' is correct.
free_sexp2(Wings[wing_num].arrival_cue);
free_sexp2(Wings[wing_num].departure_cue);
Num_wings--;
set_modified();
update_custom_wing_indexes();
already_deleting_wing = 0;
return 0;
}
int find_free_wing()
{
int i;
for (i=0; i<MAX_WINGS; i++)
if (!Wings[i].wave_count)
return i;
return -1;
}
int get_free_objnum(void) {
int i;
for (i = 1; i<MAX_OBJECTS; i++)
if (Objects[i].type == OBJ_NONE)
return i;
return -1;
}
void initialize_wings(void) {
if (Wings_initialized)
return;
Wings_initialized = 1;
}
void mark_wing(int wing)
{
int i;
unmark_all();
Assert(Wings[wing].special_ship >= 0 && Wings[wing].special_ship < Wings[wing].wave_count);
set_cur_object_index(wing_objects[wing][Wings[wing].special_ship]);
for (i=0; i<Wings[wing].wave_count; i++)
mark_object(wing_objects[wing][i]);
}
void remove_player_from_wing(int player, int min) {
remove_ship_from_wing(player, min);
}
void remove_ship_from_wing(int ship, int min) {
char buf[256];
int i, wing, end, obj;
wing = Ships[ship].wingnum;
if (wing != -1) {
if (Wings[wing].wave_count == min) {
Wings[wing].wave_count = 0;
Wings[wing].wing_squad_filename[0] = '\0';
Wings[wing].wing_insignia_texture = -1;
delete_wing(wing);
} else {
i = Wings[wing].wave_count;
end = i - 1;
while (i--)
if (wing_objects[wing][i] == Ships[ship].objnum)
break;
Assert(i != -1); // Error, object should be in wing.
if (Wings[wing].special_ship == i)
Wings[wing].special_ship = 0;
// if not last element, move last element to position to fill gap
if (i != end) {
obj = wing_objects[wing][i] = wing_objects[wing][end];
Wings[wing].ship_index[i] = Wings[wing].ship_index[end];
if (Objects[obj].type == OBJ_SHIP) {
wing_bash_ship_name(buf, Wings[wing].name, i + 1);
rename_ship(Wings[wing].ship_index[i], buf);
// bash it again for the display name
wing_bash_ship_name(&Ships[Wings[wing].ship_index[i]], &Wings[wing], i + 1, true);
}
}
Wings[wing].wave_count--;
if (Wings[wing].wave_count) {
const auto max_threshold = MAX_SHIPS_PER_WING - Wings[wing].wave_count;
if (Wings[wing].threshold > max_threshold)
Wings[wing].threshold = max_threshold;
}
}
Ships[ship].wingnum = -1;
}
set_modified();
// reset ship name to non-wing default ship name
sprintf(buf, "%s %d", Ship_info[Ships[ship].ship_info_index].name, ship);
rename_ship(ship, buf);
}
void disband_wing(int wing_num)
{
int i, total;
object *ptr;
if (check_wing_dependencies(wing_num))
return;
delete_reinforcement(Wings[wing_num].name);
Ship_editor_dialog.bypass_errors = Wing_editor_dialog.bypass_errors = 1;
Ship_editor_dialog.update_data(0);
total = Wings[wing_num].wave_count;
for (i=0; i<total; i++) {
ptr = &Objects[wing_objects[wing_num][i]];
if (ptr->type == OBJ_SHIP)
remove_ship_from_wing(ptr->instance, 0);
else if (ptr->type == OBJ_START)
remove_player_from_wing(ptr->instance, 0);
}
Assert(!Wings[wing_num].wave_count);
Wings[wing_num].wave_count = 0;
Wings[wing_num].wing_squad_filename[0] = '\0';
Wings[wing_num].wing_insignia_texture = -1;
Ship_editor_dialog.initialize_data(1);
Ship_editor_dialog.bypass_errors = Wing_editor_dialog.bypass_errors = 0;
if (cur_wing == wing_num) {
set_cur_wing(cur_wing = -1); // yes, one '=' is correct.
}
free_sexp2(Wings[wing_num].arrival_cue);
free_sexp2(Wings[wing_num].departure_cue);
Num_wings--;
update_custom_wing_indexes();
set_modified();
}