Skip to content
Closed
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
5 changes: 3 additions & 2 deletions code/asteroid/asteroid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include "ship/shipfx.h"
#include "ship/shiphit.h"
#include "stats/scoring.h"
#include "utils/Random.h"
#include "weapon/weapon.h"

#include <algorithm>
Expand Down Expand Up @@ -1900,8 +1901,8 @@ static void asteroid_maybe_break_up(object *pasteroid_obj)
roids_to_create.push_back(split->asteroid_type);
}

std::random_device rd;
std::shuffle(roids_to_create.begin(), roids_to_create.end(), std::mt19937(rd()));
std::mt19937 shuffle_rng(util::Random::shuffle_seed());
std::shuffle(roids_to_create.begin(), roids_to_create.end(), shuffle_rng);

size_t total_roids = roids_to_create.size();
for (size_t i = 0; i < total_roids; i++) {
Expand Down
62 changes: 62 additions & 0 deletions code/cmdline/cmdline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,11 @@ cmdline_parm frame_profile_write_file("-profile_write_file", NULL, AT_NONE); //
cmdline_parm no_unfocused_pause_arg("-no_unfocused_pause", NULL, AT_NONE); //Cmdline_no_unfocus_pause
cmdline_parm retail_time_compression_range_arg("-orig_speedx_range", NULL, AT_NONE); //Cmdline_retail_time_compression_range
cmdline_parm benchmark_mode_arg("-benchmark_mode", NULL, AT_NONE); //Cmdline_benchmark_mode
cmdline_parm headless_arg("-headless", NULL, AT_NONE);
cmdline_parm unthrottled_arg("-unthrottled", NULL, AT_NONE);
cmdline_parm fixed_dt_arg("-fixed-dt", "Authoritative simulation step in seconds", AT_FLOAT);
cmdline_parm max_sim_time_arg("-max-sim-time", "End after this many simulated seconds", AT_FLOAT);
cmdline_parm sim_result_arg("-sim-result", "Write the simulation result JSON to this path", AT_STRING);
cmdline_parm pilot_arg("-pilot", nullptr, AT_STRING); //Cmdline_pilot
cmdline_parm noninteractive_arg("-noninteractive", NULL, AT_NONE); //Cmdline_noninteractive
cmdline_parm json_profiling("-json_profiling", NULL, AT_NONE); //Cmdline_json_profiling
Expand Down Expand Up @@ -565,6 +570,12 @@ bool Cmdline_profile_write_file = false;
bool Cmdline_no_unfocus_pause = false;
bool Cmdline_retail_time_compression_range = false;
bool Cmdline_benchmark_mode = false;
bool Cmdline_headless = false;
bool Cmdline_unthrottled = false;
float Cmdline_fixed_dt = 1.0f / 30.0f;
float Cmdline_max_sim_time = 0.0f;
char *Cmdline_sim_result = nullptr;
bool Cmdline_sim_time_limit_reached = false;
const char *Cmdline_pilot = nullptr;
bool Cmdline_noninteractive = false;
bool Cmdline_json_profiling = false;
Expand Down Expand Up @@ -2378,6 +2389,57 @@ bool SetCmdlineParams()
Cmdline_benchmark_mode = true;
}

if (headless_arg.found())
{
Cmdline_headless = true;
Cmdline_unthrottled = true;
Cmdline_freespace_no_sound = 1;
Cmdline_freespace_no_music = 1;
Cmdline_nomovies = 1;
// nobody can dismiss an error or warning dialog, so report and exit instead (as -standalone does)
Cmdline_noninteractive = true;
}

if (unthrottled_arg.found())
{
Cmdline_unthrottled = true;
}

if (fixed_dt_arg.found())
{
const float dt = fixed_dt_arg.get_float();
// a step below the fixed-point resolution (1/65536 s) would round to zero; written as a positive range
// test so that NaN is rejected too
if (!(dt >= 1.0f / 65536.0f && dt <= 0.25f))
{
Error(LOCATION, "-fixed-dt must be finite and in the range [1/65536, 0.25] seconds.");
return false;
}
Cmdline_fixed_dt = dt;
}

if (max_sim_time_arg.found())
{
const float max_sim_time = max_sim_time_arg.get_float();
// mission time is 16.16 fixed point, so it cannot count past 32767 seconds
if (!(max_sim_time > 0.0f && max_sim_time <= 32767.0f))
{
Error(LOCATION, "-max-sim-time must be finite, greater than zero and at most 32767 seconds.");
return false;
}
Cmdline_max_sim_time = max_sim_time;
}

if (sim_result_arg.found())
{
Cmdline_sim_result = sim_result_arg.str();
}

if (Cmdline_unthrottled)
{
Cmdline_NoFPSCap = 1;
}

if (pilot_arg.found())
{
Cmdline_pilot = pilot_arg.str();
Expand Down
6 changes: 6 additions & 0 deletions code/cmdline/cmdline.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,12 @@ extern bool Cmdline_profile_write_file;
extern bool Cmdline_no_unfocus_pause;
extern bool Cmdline_retail_time_compression_range;
extern bool Cmdline_benchmark_mode;
extern bool Cmdline_headless;
extern bool Cmdline_unthrottled;
extern float Cmdline_fixed_dt;
extern float Cmdline_max_sim_time;
extern char *Cmdline_sim_result;
extern bool Cmdline_sim_time_limit_reached;
extern const char *Cmdline_pilot;
extern bool Cmdline_noninteractive;
extern bool Cmdline_json_profiling;
Expand Down
35 changes: 26 additions & 9 deletions code/graphics/2d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2076,7 +2076,7 @@ bool gr_init(std::unique_ptr<os::GraphicsOperations>&& graphicsOps, GraphicsAPI
if (d_mode == GraphicsAPI::Default) {
d_mode = gr_get_configured_render_api();
}
} else if ( !Is_standalone ) {
} else if ( !Is_standalone && !Cmdline_headless ) {
// We cannot continue without this, quit, but try to help the user out first
ptr = os_config_read_string(nullptr, NOX("VideocardFs2open"), nullptr);

Expand Down Expand Up @@ -2228,7 +2228,7 @@ bool gr_init(std::unique_ptr<os::GraphicsOperations>&& graphicsOps, GraphicsAPI
}

// if we are in standalone mode then just use special defaults
if (Is_standalone) {
if (Is_standalone || Cmdline_headless) {
mode = GraphicsAPI::Stub;
width = 640;
height = 480;
Expand Down Expand Up @@ -2334,17 +2334,19 @@ bool gr_init(std::unique_ptr<os::GraphicsOperations>&& graphicsOps, GraphicsAPI
gr_is_capable(gr_capability::CAPABILITY_PERSISTENT_BUFFER_MAPPING) ? "Enabled" : "Disabled"));

mprintf(("Checking mod required rendering features...\n"));
for (gr_capability ext : Required_render_ext) {
if (!gr_is_capable(ext)) {
Error(LOCATION, "Feature %s required by mod not supported by system.\n",
gr_capability_to_human_readable_string(ext).c_str());
if (!Cmdline_headless) {
for (gr_capability ext : Required_render_ext) {
if (!gr_is_capable(ext)) {
Error(LOCATION, "Feature %s required by mod not supported by system.\n",
gr_capability_to_human_readable_string(ext).c_str());
}
}
}
mprintf((" All required features are supported.\n"));

bool missing_installation = false;
if (!running_unittests && Web_cursor == nullptr) {
if (Is_standalone) {
if (Is_standalone || Cmdline_headless) {
// Cursors don't work in standalone mode, just check if the animation exists.
auto handle = bm_load_animation("cursorweb");
if (handle < 0) {
Expand Down Expand Up @@ -2372,7 +2374,7 @@ bool gr_init(std::unique_ptr<os::GraphicsOperations>&& graphicsOps, GraphicsAPI

gr_set_shader(NULL);

if (!Is_standalone) {
if (!Is_standalone && !Cmdline_headless) {
if (Using_in_game_options) {
// The value should have been loaded into the variable already so we can use that here
gr_set_gamma(Gr_gamma);
Expand Down Expand Up @@ -3296,7 +3298,7 @@ void gr_imgui_begin_frame()
ImGui::NewFrame();
}

void gr_flip(bool execute_scripting)
void gr_process_frame_callbacks(bool execute_scripting)
{
// Execute external "On Frame" work items
executor::OnFrameExecutor->process();
Expand All @@ -3311,6 +3313,11 @@ void gr_flip(bool execute_scripting)
OnFrameHook->run();
}
}
}

void gr_flip(bool execute_scripting)
{
gr_process_frame_callbacks(execute_scripting);

model_process_cached_ui_render_instances();

Expand Down Expand Up @@ -3569,6 +3576,13 @@ static graphics::util::GPUMemoryHeap* get_gpu_heap(GpuHeap heap_type) {

void gr_heap_allocate(GpuHeap heap_type, size_t size, void* data, size_t& offset_out, gr_buffer_handle& handle_out) {
TRACE_SCOPE(tracing::GpuHeapAllocate);
if (gr_screen.mode == GraphicsAPI::Stub) {
// The stub renderer has no GPU heaps. Model geometry is not rendered in
// this mode, while authoritative model and collision data remain loaded.
offset_out = 0;
handle_out = gr_buffer_handle::invalid();
return;
}

auto gpuHeap = get_gpu_heap(heap_type);

Expand All @@ -3579,6 +3593,9 @@ void gr_heap_allocate(GpuHeap heap_type, size_t size, void* data, size_t& offset
void gr_heap_deallocate(GpuHeap heap_type, size_t data_offset)
{
TRACE_SCOPE(tracing::GpuHeapDeallocate);
if (gr_screen.mode == GraphicsAPI::Stub) {
return;
}

auto gpuHeap = get_gpu_heap(heap_type);

Expand Down
3 changes: 3 additions & 0 deletions code/graphics/2d.h
Original file line number Diff line number Diff line change
Expand Up @@ -1160,6 +1160,9 @@ bool gr_is_screenshot_requested();

//#define gr_flip GR_CALL(gr_screen.gf_flip)
void gr_flip(bool execute_scripting = true);
// Run per-frame scripting work without performing presentation. Headless
// simulation calls this once after each authoritative gameplay frame.
void gr_process_frame_callbacks(bool execute_scripting = true);

inline void gr_setup_frame() {
gr_screen.gf_setup_frame();
Expand Down
28 changes: 28 additions & 0 deletions code/io/timer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ static bool Timestamp_sudo_paused = false;

static uint64_t Timestamp_microseconds_at_mission_start = 0;

static bool Simulation_clock_enabled = false;
static uint64_t Simulation_clock_microseconds = 0;
static uint64_t Simulation_clock_fraction = 0;


static uint64_t timestamp_get_raw(bool start_frame = false);

Expand Down Expand Up @@ -137,6 +141,26 @@ std::uint64_t timer_get_nanoseconds()
return static_cast<uint64_t>(time * Timer_to_nanoseconds);
}

void timer_set_simulation_clock_enabled(bool enabled)
{
Simulation_clock_enabled = enabled;
// Start well past zero, as a real session's clock is by the time a mission starts: TIMESTAMP values 0 and 1
// mean "never" and "immediate", and cooldowns measured from a zero-initialised time assume a running clock.
Simulation_clock_microseconds = 3600 * MICROSECONDS_PER_SECOND;
Simulation_clock_fraction = 0;
}

void timer_advance_simulation_clock(fix delta)
{
if (!Simulation_clock_enabled || delta <= 0) {
return;
}

const auto fixed_microseconds = static_cast<uint64_t>(delta) * MICROSECONDS_PER_SECOND + Simulation_clock_fraction;
Simulation_clock_microseconds += fixed_microseconds / F1_0;
Simulation_clock_fraction = fixed_microseconds % F1_0;
}

static uint64_t timestamp_get_raw(bool start_frame)
{
static uint64_t timestamp_raw = 0;
Expand All @@ -158,6 +182,10 @@ static uint64_t timestamp_get_raw(bool start_frame)

static uint64_t timestamp_get_microseconds()
{
if (Simulation_clock_enabled) {
return Simulation_clock_microseconds;
}

return Timestamp_time_compression_microseconds_offset + static_cast<uint64_t>(timestamp_get_raw() * Timer_to_microseconds * Timestamp_time_compression_multiplier);
}

Expand Down
6 changes: 6 additions & 0 deletions code/io/timer.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ extern std::uint64_t timer_get_nanoseconds();
extern int timer_get_seconds(); // seconds since program started... not accurate, but good for long
// runtimes with second-based timeouts

// Optional authoritative mission clock for unthrottled experiment runs. Wall-clock
// timer APIs remain unchanged so profiling and UI deadlines keep real time. Like the
// real clock it only moves forward, across missions too.
void timer_set_simulation_clock_enabled(bool enabled);
void timer_advance_simulation_clock(fix delta);


//=================================================================
//=================================================================
Expand Down
4 changes: 2 additions & 2 deletions code/parse/sexp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15506,8 +15506,8 @@ void sexp_send_builtin_message(int n) {
}

if (shuffle) {
std::random_device rd;
std::shuffle(sender_names.begin(), sender_names.end(), std::mt19937(rd()));
std::mt19937 rng(util::Random::shuffle_seed());
std::shuffle(sender_names.begin(), sender_names.end(), rng);
}

auto subject = have_subject ? subject_entry->shipp() : nullptr;
Expand Down
13 changes: 12 additions & 1 deletion code/popup/popup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <cstdarg>

#include "anim/animplay.h"
#include "cmdline/cmdline.h"
#include "freespace.h"
#include "gamesequence/gamesequence.h"
#include "gamesnd/gamesnd.h"
Expand Down Expand Up @@ -1148,7 +1149,17 @@ int popup(int flags, int nchoices, ... )
vsnprintf(Popup_info.raw_text, sizeof(Popup_info.raw_text), format, args);
va_end(args);
Popup_info.raw_text[sizeof(Popup_info.raw_text)-1] = '\0';


// A headless run has nobody to answer, so log the popup and give the abort answer, unless a script answers dialogs.
if (Cmdline_headless && !scripting::hooks::OnDialogFrame->isActive()) {
mprintf(("Headless run: skipped popup \"%s\"\n", Popup_info.raw_text));
for (i = 0; i < nchoices; i++) {
vm_free(Popup_info.button_text[i]);
Popup_info.button_text[i] = nullptr;
}
return -1;
}

gamesnd_play_iface(InterfaceSounds::POPUP_APPEAR); // play sound when popup appears

io::mouse::CursorManager::get()->pushStatus();
Expand Down
5 changes: 5 additions & 0 deletions code/radar/radarsetup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@


#include "bmpman/bmpman.h"
#include "cmdline/cmdline.h"
#include "debugconsole/console.h"
#include "freespace.h"
#include "gamesnd/gamesnd.h"
Expand Down Expand Up @@ -174,6 +175,10 @@ void radar_stuff_blip_info(object *objp, int is_bright, color **blip_color, int

void radar_plot_object( object *objp )
{
if (Cmdline_headless) {
return;
}

vec3d pos, tempv;
float awacs_level, dist, max_radar_dist;
vec3d world_pos = objp->pos;
Expand Down
18 changes: 18 additions & 0 deletions code/scripting/global_hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,24 @@ const std::shared_ptr<Hook<>> OnCameraSetUpHook = Hook<>::Factory("On Camera Set
{"Camera", "camera", "The camera about to be used for rendering."},
});

const std::shared_ptr<Hook<ShipDamageConditions>> OnShipDamage = Hook<ShipDamageConditions>::Factory(
"On Ship Damage", "Called after resolved damage is applied; lethal hits run before the destruction callback.",
{
{"Target", "ship", "The ship that took the damage."},
{"Source", "object", "The weapon, beam, shockwave, or other object that caused the damage. Could be nil."},
{"Attacker", "object", "The source ship or parent object, if available."},
{"WeaponClass", "string", "The weapon class name, or NON_WEAPON if the source has no weapon class."},
{"WeaponSubtype", "string", "The weapon subtype, or UNKNOWN if the source has no weapon class."},
{"SourceKind", "string", "PROJECTILE, BEAM, SHOCKWAVE, COLLISION, or OTHER."},
{"DamageType", "string", "The resolved damage type, or UNKNOWN if none is defined."},
{"ShieldDamage", "number", "Actual total shield strength removed by this damage application."},
{"HullDamage", "number", "Actual hull strength removed, clamped to the target's remaining hull."},
{"Quadrant", "number", "Shield quadrant hit, or -1 when no single quadrant applies."},
{"Ballistic", "boolean", "Whether the weapon class is marked ballistic."},
{"BeamWeapon", "boolean", "Whether the weapon class is configured as a beam."},
{"LethalHit", "boolean", "Whether this hit reduced positive hull strength to zero or below."},
});

// ========== FRED HOOKS ==========

const std::shared_ptr<Hook<>> FredOnMissionLoad = Hook<>::Factory("FRED On Mission Load",
Expand Down
2 changes: 2 additions & 0 deletions code/scripting/global_hooks.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ extern const std::shared_ptr<Hook<>> OnStateAboutToEndHook;
extern const std::shared_ptr<OverridableHook<>> OnStateEndHook;
extern const std::shared_ptr<Hook<>> OnCameraSetUpHook;

extern const std::shared_ptr<Hook<ShipDamageConditions>> OnShipDamage;

// FRED Hooks
extern const std::shared_ptr<Hook<>> FredOnMissionLoad;
extern const std::shared_ptr<Hook<>> FredOnMissionSpecsSave;
Expand Down
4 changes: 4 additions & 0 deletions code/scripting/hook_conditions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,10 @@ HOOK_CONDITIONS_START(ShipDeathConditions)
HOOK_CONDITION_SHIPP(ShipDeathConditions, "", "that died.", dying_shipp);
HOOK_CONDITIONS_END

HOOK_CONDITIONS_START(ShipDamageConditions)
HOOK_CONDITION_SHIPP(ShipDamageConditions, "Target ", "that took resolved damage.", target_shipp);
HOOK_CONDITIONS_END

HOOK_CONDITIONS_START(SubsystemDeathConditions)
HOOK_CONDITION_SHIPP(SubsystemDeathConditions, "", "whose subsystem got destroyed.", affected_shipp);
HOOK_CONDITIONS_END
Expand Down
5 changes: 5 additions & 0 deletions code/scripting/hook_conditions.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ struct ShipDeathConditions {
const ship* dying_shipp;
};

struct ShipDamageConditions {
HOOK_DEFINE_CONDITIONS;
const ship* target_shipp;
};

struct SubsystemDeathConditions {
HOOK_DEFINE_CONDITIONS;
const ship* affected_shipp;
Expand Down
Loading