mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 08:15:59 -10:00
* avoid undefined behavior from UNREACHABLE on reachable code paths
UNREACHABLE expands to __assume(false) / __builtin_unreachable() in
release builds, which is undefined behavior if the code is ever actually
reached. Many UNREACHABLE sites only guard against unexpected-but-
possible runtime states (values from tables, mission/save files, network
packets, scripting, or user input), so reaching them should degrade
gracefully rather than invoke UB.
Replace UNREACHABLE with Assertion(false, ...) at all such sites. It
still pops an error in debug builds, but in release it is a harmless
no-op that simply falls through to the following code. Sites that are
genuinely unreachable by local invariants (e.g. a switch over a bounded
random number) are left as UNREACHABLE.
A few converted sites had no safe fall-through -- an uninitialized read,
null dereference, or null call would have followed -- so they also get a
minimal recovery: initialize the value, guard the dereference, return
early, or skip the offending item.
Where the converted site was a simple guard of the form
if (x) { Assertion(false, ...); return; }
hoist the check onto the invariant instead:
Assertion(!x, ...);
if (x) { return; }
This states the expectation directly and shows the offending value in
the debug popup, while keeping the same graceful runtime degradation.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* addendum
After consultation with Lafiel, use UNREACHABLE in situations that are effectively impossible even if not provably impossible. UNREACHABLE = "this shouldn't ever happen"; Assertion = "if this ever happens, it's a coding error". Also fix a few other sites and change a couple to Warnings.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
525 lines
14 KiB
C++
525 lines
14 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 "ai/ai.h"
|
|
#include "fireball/fireballs.h"
|
|
#include "gamesnd/gamesnd.h"
|
|
#include "globalincs/alphacolors.h"
|
|
#include "globalincs/linklist.h"
|
|
#include "hud/hudartillery.h"
|
|
#include "hud/hudmessage.h"
|
|
#include "io/timer.h"
|
|
#include "math/vecmat.h"
|
|
#include "network/multi.h"
|
|
#include "object/object.h"
|
|
#include "parse/parselo.h"
|
|
#include "ship/shipfx.h"
|
|
#include "sound/sound.h"
|
|
#include "utils/Random.h"
|
|
#include "weapon/beam.h"
|
|
#include "weapon/weapon.h"
|
|
|
|
// -----------------------------------------------------------------------------------------------------------------------
|
|
// ARTILLERY DEFINES/VARS
|
|
//
|
|
// Goober5000 - moved to hudartillery.h
|
|
|
|
// -----------------------------------------------------------------------------------------------------------------------
|
|
// ARTILLERY FUNCTIONS
|
|
//
|
|
|
|
// test code for subspace missile strike -------------------------------------------
|
|
|
|
// ssm_info, like ship_info etc.
|
|
SCP_vector<ssm_info> Ssm_info;
|
|
|
|
// list of active strikes
|
|
SCP_list<ssm_strike> Ssm_strikes;
|
|
|
|
// Goober5000
|
|
int ssm_info_lookup(const char *name)
|
|
{
|
|
if(name == NULL)
|
|
return -1;
|
|
|
|
for (auto it = Ssm_info.cbegin(); it != Ssm_info.cend(); ++it)
|
|
if (!stricmp(name, it->name))
|
|
return (int)std::distance(Ssm_info.cbegin(), it);
|
|
|
|
return -1;
|
|
}
|
|
|
|
void parse_ssm(const char *filename)
|
|
{
|
|
char ssm_name[NAME_LENGTH];
|
|
char weapon_name[NAME_LENGTH];
|
|
|
|
try
|
|
{
|
|
read_file_text(filename, CF_TYPE_TABLES);
|
|
reset_parse();
|
|
|
|
// parse the table
|
|
while(required_string_either("#end", "$SSM:"))
|
|
{
|
|
bool no_create = false;
|
|
int string_index;
|
|
ssm_info *s, new_ssm;
|
|
|
|
s = &new_ssm;
|
|
|
|
// name
|
|
required_string("$SSM:");
|
|
stuff_string(ssm_name, F_NAME, NAME_LENGTH);
|
|
if (*ssm_name == 0)
|
|
{
|
|
sprintf(ssm_name, "SSM " SIZE_T_ARG, Ssm_info.size());
|
|
mprintf(("Found an SSM entry without a name. Assigning \"%s\".\n", ssm_name));
|
|
}
|
|
|
|
if (optional_string("+nocreate"))
|
|
{
|
|
no_create = true;
|
|
|
|
int i = ssm_info_lookup(ssm_name);
|
|
if (i >= 0)
|
|
s = &Ssm_info[i];
|
|
}
|
|
else
|
|
{
|
|
strcpy_s(s->name, ssm_name);
|
|
}
|
|
|
|
// stuff data
|
|
if (optional_string("+Weapon:"))
|
|
{
|
|
stuff_string(weapon_name, F_NAME, NAME_LENGTH);
|
|
|
|
// see if we have a valid weapon
|
|
s->weapon_info_index = weapon_info_lookup(weapon_name);
|
|
if (s->weapon_info_index < 0)
|
|
error_display(0, "Unknown weapon [%s] for SSM strike [%s]; this SSM strike will be discarded.", weapon_name, s->name);
|
|
}
|
|
|
|
string_index = optional_string_either("+Count:", "+Min Count:");
|
|
if (string_index == 0)
|
|
{
|
|
stuff_int(&s->count);
|
|
s->max_count = -1;
|
|
}
|
|
else if (string_index == 1)
|
|
{
|
|
stuff_int(&s->count);
|
|
required_string("+Max Count:");
|
|
stuff_int(&s->max_count);
|
|
}
|
|
|
|
if (optional_string("+WarpEffect:"))
|
|
{
|
|
char unique_id[NAME_LENGTH];
|
|
stuff_string(unique_id, F_NAME, NAME_LENGTH);
|
|
|
|
if (can_construe_as_integer(unique_id))
|
|
{
|
|
int temp = atoi(unique_id);
|
|
|
|
if (!SCP_vector_inbounds(Fireball_info, temp))
|
|
error_display(0, "Fireball index [%d] out of range (should be 0-%d) for SSM strike [%s]", temp, static_cast<int>(Fireball_info.size()) - 1, s->name);
|
|
else
|
|
s->fireball_type = temp;
|
|
}
|
|
else
|
|
{
|
|
// We have a string to parse instead.
|
|
int fireball_type = fireball_info_lookup(unique_id);
|
|
if (fireball_type < 0)
|
|
error_display(0, "Unknown fireball [%s] to use as warp effect for SSM strike [%s]", unique_id, s->name);
|
|
else
|
|
s->fireball_type = fireball_type;
|
|
}
|
|
}
|
|
|
|
if (optional_string("+WarpRadius:"))
|
|
{
|
|
stuff_float(&s->warp_radius);
|
|
}
|
|
|
|
if (optional_string("+WarpTime:"))
|
|
{
|
|
stuff_float(&s->warp_time);
|
|
|
|
// According to fireballs.cpp, "Warp lifetime must be at least 4 seconds!"
|
|
if (s->warp_time < 4.0f)
|
|
{
|
|
// So let's warn them before they try to use it, shall we?
|
|
error_display(0, "Expected a '+WarpTime:' value equal or greater than 4.0, found '%f' in SSM strike [%s].\nSetting to 4.0, please check and set to a number 4.0 or greater!", s->warp_time, s->name);
|
|
// And then make the Assert obsolete -- Zacam
|
|
s->warp_time = 4.0f;
|
|
}
|
|
}
|
|
|
|
string_index = optional_string_either("+Radius:", "+Min Radius:");
|
|
if (string_index == 0)
|
|
{
|
|
stuff_float(&s->radius);
|
|
s->max_radius = -1.0f;
|
|
}
|
|
else if (string_index == 1)
|
|
{
|
|
stuff_float(&s->radius);
|
|
required_string("+Max Radius:");
|
|
stuff_float(&s->max_radius);
|
|
}
|
|
|
|
string_index = optional_string_either("+Offset:", "+Min Offset:");
|
|
if (string_index == 0)
|
|
{
|
|
stuff_float(&s->offset);
|
|
s->max_offset = -1.0f;
|
|
}
|
|
else if (string_index == 1)
|
|
{
|
|
stuff_float(&s->offset);
|
|
required_string("+Max Offset:");
|
|
stuff_float(&s->max_offset);
|
|
}
|
|
|
|
if (optional_string("+Shape:"))
|
|
{
|
|
switch (required_string_one_of(3, "Point", "Circle", "Sphere"))
|
|
{
|
|
case 0:
|
|
required_string("Point");
|
|
s->shape = SSM_SHAPE_POINT;
|
|
break;
|
|
case 1:
|
|
required_string("Circle");
|
|
FALLTHROUGH;
|
|
case -1: // If we're ignoring parse errors and can't identify the shape, go with a circle.
|
|
s->shape = SSM_SHAPE_CIRCLE;
|
|
break;
|
|
case 2:
|
|
required_string("Sphere");
|
|
s->shape = SSM_SHAPE_SPHERE;
|
|
break;
|
|
default:
|
|
UNREACHABLE("Impossible return value from required_string_one_of(); get a coder!");
|
|
}
|
|
}
|
|
|
|
if (optional_string("+HUD Message:"))
|
|
stuff_boolean(&s->send_message);
|
|
|
|
if (optional_string("+Custom Message:"))
|
|
{
|
|
stuff_string(s->custom_message, F_NAME, NAME_LENGTH);
|
|
s->use_custom_message = true;
|
|
}
|
|
|
|
parse_game_sound("+Alarm Sound:", &s->sound_index);
|
|
|
|
// don't add new entry if this is just a modified one
|
|
if (!no_create)
|
|
Ssm_info.push_back(new_ssm);
|
|
}
|
|
}
|
|
catch (const parse::ParseException& e)
|
|
{
|
|
mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", filename, e.what()));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// game init
|
|
void ssm_init()
|
|
{
|
|
if (cf_exists_full("ssm.tbl", CF_TYPE_TABLES)) {
|
|
mprintf(("TABLES => Starting parse of 'ssm.tbl'...\n"));
|
|
parse_ssm("ssm.tbl");
|
|
}
|
|
parse_modular_table(NOX("*-ssm.tbm"), parse_ssm);
|
|
|
|
// We already warned the modder that an SSM strike is invalid without a valid weapon, so now remove any that are in the list
|
|
Ssm_info.erase(
|
|
std::remove_if(Ssm_info.begin(), Ssm_info.end(), [](const ssm_info &ssm)
|
|
{
|
|
return ssm.weapon_info_index < 0;
|
|
}),
|
|
Ssm_info.end()
|
|
);
|
|
|
|
// Now that we've populated Ssm_info, let's validate weapon $SSM: entries.
|
|
validate_SSM_entries();
|
|
}
|
|
|
|
void ssm_get_random_start_pos(vec3d *out, const vec3d *start, const matrix *orient, size_t ssm_index)
|
|
{
|
|
vec3d temp;
|
|
ssm_info *s = &Ssm_info[ssm_index];
|
|
float radius, offset;
|
|
|
|
if (s->max_radius == -1.0f)
|
|
radius = s->radius;
|
|
else
|
|
radius = frand_range(s->radius, s->max_radius);
|
|
|
|
if (s->max_offset == -1.0f)
|
|
offset = s->offset;
|
|
else
|
|
offset = frand_range(s->offset, s->max_offset);
|
|
|
|
switch (s->shape) {
|
|
case SSM_SHAPE_SPHERE:
|
|
// get a random vector in a sphere around the target
|
|
vm_vec_random_in_sphere(&temp, start, radius, true);
|
|
break;
|
|
case SSM_SHAPE_CIRCLE:
|
|
// get a random vector in the circle of the firing plane
|
|
vm_vec_random_in_circle(&temp, start, orient, radius, true);
|
|
break;
|
|
case SSM_SHAPE_POINT:
|
|
// boooring
|
|
vm_vec_scale_add(&temp, start, &orient->vec.fvec, radius);
|
|
break;
|
|
default:
|
|
UNREACHABLE("Unknown shape '%d' in SSM type #" SIZE_T_ARG " ('%s'). This should not be possible; get a coder!\n", s->shape, ssm_index, s->name);
|
|
temp = *start;
|
|
break;
|
|
}
|
|
|
|
// offset it a bit
|
|
vm_vec_scale_add(out, &temp, &orient->vec.fvec, offset);
|
|
}
|
|
|
|
// level init
|
|
void ssm_level_init()
|
|
{
|
|
}
|
|
|
|
// start a subspace missile effect
|
|
// (it might be possible to make `target` const, but that would set off another const-cascade)
|
|
void ssm_create(object *target, const vec3d *start, size_t ssm_index, const ssm_firing_info *override, int team)
|
|
{
|
|
ssm_strike ssm;
|
|
matrix dir;
|
|
int idx, count;
|
|
|
|
// sanity
|
|
Assert(target != NULL);
|
|
if(target == NULL){
|
|
return;
|
|
}
|
|
Assert(start != NULL);
|
|
if(start == NULL){
|
|
return;
|
|
}
|
|
if (ssm_index >= Ssm_info.size()) {
|
|
return;
|
|
}
|
|
|
|
// Init the ssm data
|
|
|
|
count = Ssm_info[ssm_index].count;
|
|
if (Ssm_info[ssm_index].max_count != -1 && (Ssm_info[ssm_index].max_count - count) > 0) {
|
|
count = Random::next(count, Ssm_info[ssm_index].max_count);
|
|
}
|
|
|
|
// override in multiplayer
|
|
if(override != NULL){
|
|
ssm.sinfo = *override;
|
|
}
|
|
// single player or the server
|
|
else {
|
|
// forward orientation
|
|
vec3d temp;
|
|
|
|
vm_vec_normalized_dir(&temp, &target->pos, start);
|
|
vm_vector_2_matrix_norm(&dir, &temp, nullptr, nullptr);
|
|
|
|
// stuff info
|
|
ssm.sinfo.count = count;
|
|
ssm.sinfo.ssm_index = ssm_index;
|
|
ssm.sinfo.target = target;
|
|
ssm.sinfo.ssm_team = team;
|
|
|
|
// Instead of pushing them on one at a time, let's just grab all the memory we'll need at once
|
|
// (as a side effect, don't need to change the logic from the old array-based code)
|
|
ssm.sinfo.delay_stamp.resize(count);
|
|
ssm.sinfo.start_pos.resize(count);
|
|
|
|
for (idx = 0; idx < count; idx++) {
|
|
ssm.sinfo.delay_stamp[idx] = _timestamp(200 + Random::next(-199, 1000));
|
|
ssm_get_random_start_pos(&ssm.sinfo.start_pos[idx], start, &dir, ssm_index);
|
|
}
|
|
|
|
ssm_info *si = &Ssm_info[ssm_index];
|
|
weapon_info *wip = &Weapon_info[si->weapon_info_index];
|
|
if (wip->wi_flags[Weapon::Info_Flags::Beam]) {
|
|
ssm.sinfo.duration = ((si->warp_time - ((wip->b_info.beam_warmup / 1000.0f) + wip->b_info.beam_life + (wip->b_info.beam_warmdown / 1000.0f))) / 2.0f) / si->warp_time;
|
|
} else {
|
|
ssm.sinfo.duration = 0.5f;
|
|
}
|
|
|
|
// if we're the server, send a packet
|
|
if(MULTIPLAYER_MASTER){
|
|
//
|
|
}
|
|
}
|
|
|
|
ssm.done_flags.clear();
|
|
ssm.done_flags.resize(count);
|
|
ssm.fireballs.clear();
|
|
ssm.fireballs.resize(count, -1);
|
|
|
|
if(Ssm_info[ssm_index].send_message) {
|
|
if (!Ssm_info[ssm_index].use_custom_message)
|
|
HUD_printf("%s", XSTR("Firing artillery", 1570));
|
|
else
|
|
HUD_printf("%s", Ssm_info[ssm_index].custom_message);
|
|
}
|
|
if (Ssm_info[ssm_index].sound_index.isValid()) {
|
|
snd_play(gamesnd_get_game_sound(Ssm_info[ssm_index].sound_index));
|
|
}
|
|
|
|
Ssm_strikes.push_back(std::move(ssm));
|
|
}
|
|
|
|
// delete a finished ssm effect
|
|
void ssm_delete(SCP_list<ssm_strike>::iterator ssm)
|
|
{
|
|
Ssm_strikes.erase(ssm);
|
|
}
|
|
|
|
// process subspace missile stuff
|
|
void ssm_process()
|
|
{
|
|
int idx, finished;
|
|
SCP_list<ssm_strike>::iterator moveup, eraser;
|
|
ssm_info *si;
|
|
int weapon_objnum;
|
|
|
|
// process all strikes
|
|
moveup = Ssm_strikes.begin();
|
|
while ( moveup != Ssm_strikes.end() ) {
|
|
// get the type
|
|
Assertion(moveup->sinfo.ssm_index < Ssm_info.size(), "Invalid SSM index detected!");
|
|
si = &Ssm_info[moveup->sinfo.ssm_index];
|
|
|
|
// check all the individual missiles
|
|
finished = 1;
|
|
for(idx=0; idx<moveup->sinfo.count; idx++){
|
|
// if this guy is not marked as done
|
|
if(!moveup->done_flags[idx]){
|
|
finished = 0;
|
|
|
|
// if he already has the fireball effect
|
|
if(moveup->fireballs[idx] >= 0){
|
|
if ((1.0f - fireball_lifeleft_percent(&Objects[moveup->fireballs[idx]])) >= moveup->sinfo.duration) {
|
|
weapon_info *wip = &Weapon_info[si->weapon_info_index];
|
|
|
|
// get an orientation
|
|
vec3d temp;
|
|
matrix orient;
|
|
|
|
vm_vec_normalized_dir(&temp, &moveup->sinfo.target->pos, &moveup->sinfo.start_pos[idx]);
|
|
vm_vector_2_matrix_norm(&orient, &temp, nullptr, nullptr);
|
|
|
|
// are we a beam? -MageKing17
|
|
if (wip->wi_flags[Weapon::Info_Flags::Beam]) {
|
|
int num_beams = wip->b_info.beam_type == BeamType::OMNI && (int)wip->b_info.t5info.burst_rot_pattern.size() > 1 ?
|
|
(int)wip->b_info.t5info.burst_rot_pattern.size() : 1;
|
|
for (int i = 0; i < num_beams; i++) {
|
|
beam_fire_info fire_info;
|
|
memset(&fire_info, 0, sizeof(beam_fire_info));
|
|
|
|
fire_info.accuracy = 0.000001f; // this will guarantee a hit
|
|
fire_info.shooter = nullptr;
|
|
fire_info.turret = nullptr;
|
|
fire_info.target = moveup->sinfo.target;
|
|
fire_info.target_subsys = nullptr;
|
|
fire_info.bfi_flags |= BFIF_FLOATING_BEAM;
|
|
fire_info.starting_pos = moveup->sinfo.start_pos[idx];
|
|
fire_info.beam_info_index = si->weapon_info_index;
|
|
fire_info.team = static_cast<char>(moveup->sinfo.ssm_team);
|
|
fire_info.fire_method = BFM_SUBSPACE_STRIKE;
|
|
fire_info.burst_index = i;
|
|
|
|
// Fill target_pos but DONT turn on BFIF_TARGETING_COORDS
|
|
// It would mess up normal beams but type 5s can still make use of this
|
|
fire_info.target_pos1 = orient.vec.fvec + fire_info.starting_pos;
|
|
|
|
// fire the beam
|
|
beam_fire(&fire_info);
|
|
|
|
moveup->done_flags[idx] = true;
|
|
}
|
|
} else {
|
|
// fire the missile and flash the screen
|
|
weapon_objnum = weapon_create(&moveup->sinfo.start_pos[idx], &orient, si->weapon_info_index, -1, -1, true);
|
|
|
|
if (weapon_objnum >= 0) {
|
|
Weapons[Objects[weapon_objnum].instance].team = moveup->sinfo.ssm_team;
|
|
Weapons[Objects[weapon_objnum].instance].homing_object = moveup->sinfo.target;
|
|
Weapons[Objects[weapon_objnum].instance].target_sig = moveup->sinfo.target->signature;
|
|
}
|
|
|
|
// this makes this particular missile done
|
|
moveup->done_flags[idx] = true;
|
|
}
|
|
}
|
|
}
|
|
// maybe create his warpin effect
|
|
else if((moveup->sinfo.delay_stamp[idx].isValid()) && timestamp_elapsed(moveup->sinfo.delay_stamp[idx])){
|
|
// get an orientation
|
|
vec3d temp;
|
|
matrix orient;
|
|
|
|
vm_vec_normalized_dir(&temp, &moveup->sinfo.target->pos, &moveup->sinfo.start_pos[idx]);
|
|
vm_vector_2_matrix_norm(&orient, &temp, nullptr, nullptr);
|
|
moveup->fireballs[idx] = fireball_create(&moveup->sinfo.start_pos[idx], si->fireball_type, FIREBALL_WARP_EFFECT, -1, si->warp_radius, false, &vmd_zero_vector, si->warp_time, 0, &orient);
|
|
}
|
|
}
|
|
}
|
|
if(finished){
|
|
eraser = moveup;
|
|
++moveup;
|
|
ssm_delete(eraser);
|
|
continue;
|
|
}
|
|
|
|
++moveup;
|
|
}
|
|
}
|
|
|
|
|
|
// test code for subspace missile strike -------------------------------------------
|
|
|
|
// level init
|
|
void hud_init_artillery()
|
|
{
|
|
}
|
|
|
|
// update all hud artillery related stuff
|
|
void hud_artillery_update()
|
|
{
|
|
}
|
|
|
|
// render all hud artillery related stuff
|
|
void hud_artillery_render()
|
|
{
|
|
// render how long the player has been painting his target
|
|
if((Player_ai != NULL) && (Player_ai->artillery_objnum >= 0)){
|
|
gr_set_color_fast(&Color_bright_blue);
|
|
gr_printf_no_resize(gr_screen.center_offset_x + 10, gr_screen.center_offset_y + 50, "%f", Player_ai->artillery_lock_time);
|
|
}
|
|
}
|