From 846021870720240dfafe4e0df90833eba9ae9573 Mon Sep 17 00:00:00 2001 From: Asteroth Date: Wed, 20 Sep 2023 12:20:38 -0400 Subject: [PATCH 01/63] first draft --- code/hud/hud.cpp | 2 ++ code/hud/hudparse.cpp | 7 +++++- code/hud/hudreticle.cpp | 37 +++++++++++++++++++++++++++----- code/hud/hudreticle.h | 3 +++ code/playerman/player.h | 2 ++ code/playerman/playercontrol.cpp | 20 +++++++++++++++++ code/ship/ship.cpp | 16 ++++++++------ 7 files changed, 74 insertions(+), 13 deletions(-) diff --git a/code/hud/hud.cpp b/code/hud/hud.cpp index 495e8bcf455..265ddc90422 100644 --- a/code/hud/hud.cpp +++ b/code/hud/hud.cpp @@ -3788,6 +3788,8 @@ void HUD_set_offsets(object *viewer_obj, int wiggedy_wack, matrix *eye_orient) } else { HUD_get_nose_coordinates(&HUD_nose_x, &HUD_nose_y); } + + hud_reticle_set_aim_cursor_offset(); } /** diff --git a/code/hud/hudparse.cpp b/code/hud/hudparse.cpp index 290bc48f67a..7153a4c8359 100644 --- a/code/hud/hudparse.cpp +++ b/code/hud/hudparse.cpp @@ -2118,6 +2118,7 @@ void load_gauge_center_reticle(gauge_settings* settings) int scaleY = 10; int size = 5; int autoaim_frame = -1; + int aim_cursor_frame = -1; settings->origin[0] = 0.5f; settings->origin[1] = 0.5f; @@ -2167,12 +2168,16 @@ void load_gauge_center_reticle(gauge_settings* settings) if (optional_string("Firepoint Y coordinate multiplier:")) stuff_int(&scaleY); - if(optional_string("Autoaim Frame:")) + if (optional_string("Autoaim Frame:")) + stuff_int(&autoaim_frame); + + if (optional_string("Aim Cursor Frame:")) stuff_int(&autoaim_frame); hud_gauge->initBitmaps(fname); hud_gauge->initFirepointDisplay(firepoints, scaleX, scaleY, size); hud_gauge->setAutoaimFrame(autoaim_frame); + hud_gauge->setAimCursorFrame(aim_cursor_frame); gauge_assign_common(settings, std::move(hud_gauge)); } diff --git a/code/hud/hudreticle.cpp b/code/hud/hudreticle.cpp index d2c9dcad9f4..c4e51e76aa6 100644 --- a/code/hud/hudreticle.cpp +++ b/code/hud/hudreticle.cpp @@ -225,6 +225,8 @@ int Reticle_launch_coords[GR_NUM_RESOLUTIONS][2] = { static int Threat_lock_timer; // timestamp for when to show next flashing frame for lock threat static int Threat_lock_frame; // frame offset of current lock flashing warning +static vertex Player_aim_cursor_offset; + HudGaugeReticle::HudGaugeReticle(): HudGauge(HUD_OBJECT_CENTER_RETICLE, HUD_CENTER_RETICLE, true, false, (VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY | VM_TOPDOWN | VM_OTHER_SHIP), 255, 255, 255) @@ -234,6 +236,7 @@ HudGauge(HUD_OBJECT_CENTER_RETICLE, HUD_CENTER_RETICLE, true, false, (VM_EXTERNA void HudGaugeReticle::initBitmaps(char *fname) { + Warning(LOCATION, "Cannot load hud ani: %s\n", fname); crosshair.first_frame = bm_load_animation(fname, &crosshair.num_frames); if (crosshair.first_frame < 0) { Warning(LOCATION, "Cannot load hud ani: %s\n", fname); @@ -254,6 +257,8 @@ void HudGaugeReticle::render(float /*frametime*/) } ship_info *sip = &Ship_info[Player_ship->ship_info_index]; + int bitmap_size_x, bitmap_size_y; + bm_get_info(crosshair.first_frame, &bitmap_size_x, &bitmap_size_y); if (autoaim_frame_offset > 0 || sip->autoaim_lock_snd.isValid() || sip->autoaim_lost_snd.isValid()) { ship *shipp = &Ships[Objects[Player->objnum].instance]; @@ -306,15 +311,18 @@ void HudGaugeReticle::render(float /*frametime*/) else renderBitmap(crosshair.first_frame, position[0], position[1]); + if (aim_cursor_frame_offset > 0) { + int x = Player_aim_cursor_offset.screen.xyw.x; + int y = Player_aim_cursor_offset.screen.xyw.y; + unsize(&x, &y); + renderBitmap(crosshair.first_frame + aim_cursor_frame_offset, x - (bitmap_size_x / 2), y - (bitmap_size_y / 2)); + } + if (firepoint_display) { fp.clear(); getFirepointStatus(); if (!fp.empty()) { - int ax, ay; - bm_get_info(crosshair.first_frame, &ax, &ay); - int centerX = position[0] + (ax / 2); - int centerY = position[1] + (ay / 2); for (SCP_vector::iterator fpi = fp.begin(); fpi != fp.end(); ++fpi) { if (fpi->active == 2) @@ -323,7 +331,9 @@ void HudGaugeReticle::render(float /*frametime*/) setGaugeColor(HUD_C_NORMAL); else setGaugeColor(HUD_C_DIM); - + + int centerX = position[0] + (bitmap_size_x / 2); + int centerY = position[1] + (bitmap_size_y / 2); renderCircle((int) (centerX + (fpi->xy.x * firepoint_scale_x)), (int) (centerY + (fpi->xy.y * firepoint_scale_y)), firepoint_size); } } @@ -415,6 +425,13 @@ void HudGaugeReticle::getFirepointStatus() { } } +void HudGaugeReticle::setAimCursorFrame(int framenum) { + if (framenum < 0 || framenum > crosshair.num_frames - 1) + aim_cursor_frame_offset = 1; + else + aim_cursor_frame_offset = framenum; +} + void HudGaugeReticle::setAutoaimFrame(int framenum) { if (framenum < 0 || framenum > crosshair.num_frames - 1) autoaim_frame_offset = 0; @@ -1140,3 +1157,13 @@ void hud_update_reticle( player *pp ) } } } + +void hud_reticle_set_aim_cursor_offset() { + matrix view_mat; + vm_angles_2_matrix(&view_mat, &Player_aim_cursor); + view_mat = view_mat * Player_obj->orient; + + vec3d view_pos = Eye_position + view_mat.vec.fvec * 100.0f; + g3_rotate_vertex(&Player_aim_cursor_offset, &view_pos); + g3_project_vertex(&Player_aim_cursor_offset); +} diff --git a/code/hud/hudreticle.h b/code/hud/hudreticle.h index 56a677b52af..054b1367fe2 100644 --- a/code/hud/hudreticle.h +++ b/code/hud/hudreticle.h @@ -44,6 +44,7 @@ class HudGaugeReticle: public HudGauge int firepoint_scale_y; int autoaim_frame_offset; bool has_autoaim_lock; + int aim_cursor_frame_offset; public: HudGaugeReticle(); void render(float frametime) override; @@ -52,6 +53,7 @@ class HudGaugeReticle: public HudGauge void initFirepointDisplay(bool firepoint, int scaleX, int scaleY, int size); void getFirepointStatus(); void setAutoaimFrame(int framenum); + void setAimCursorFrame(int framenum); }; class HudGaugeThrottle: public HudGauge @@ -168,5 +170,6 @@ class HudGaugeWeaponLinking: public HudGauge void hud_init_reticle(); void hud_update_reticle( player *pp ); +void hud_reticle_set_aim_cursor_offset(); #endif diff --git a/code/playerman/player.h b/code/playerman/player.h index ae0f2d93737..c7ed5b99bee 100644 --- a/code/playerman/player.h +++ b/code/playerman/player.h @@ -226,6 +226,8 @@ extern player *Player; // pointer to my information extern int Player_use_ai; extern angles chase_slew_angles; // The viewing angles in which viewer_slew_angles will chase to. +extern angles Player_aim_cursor; + extern void player_init(); // initialization per level extern void player_level_init(); extern void player_controls_init(); // initialize Descent style controls for use in various places diff --git a/code/playerman/playercontrol.cpp b/code/playerman/playercontrol.cpp index 0c9879ac56f..c099a4ddd93 100644 --- a/code/playerman/playercontrol.cpp +++ b/code/playerman/playercontrol.cpp @@ -60,6 +60,8 @@ physics_info Descent_physics; // used when we want to control the player like angles chase_slew_angles; +angles Player_aim_cursor; + int toggle_glide = 0; int press_glide = 0; @@ -1052,6 +1054,22 @@ void read_player_controls(object *objp, float frametime) } } + if (true) { + float max_aim_angle = 0.35f; + + Player_aim_cursor.p += Player->ci.pitch * 0.015f; + Player_aim_cursor.h += Player->ci.heading * 0.015f; + + float mag = powf(powf(Player_aim_cursor.p, 2.0f) + powf(Player_aim_cursor.h, 2.0f), 0.5f); + if (mag > max_aim_angle) { + Player_aim_cursor.p *= max_aim_angle / mag; + Player_aim_cursor.h *= max_aim_angle / mag; + } + + Player->ci.pitch = Player_aim_cursor.p / max_aim_angle; + Player->ci.heading = Player_aim_cursor.h / max_aim_angle; + } + if(Player_obj->type == OBJ_SHIP && !Player_use_ai){ // only read player control info if player ship is not dead // or if Player_use_ai is disabed @@ -1308,6 +1326,8 @@ void player_level_init() Viewer_external_info.preferred_distance = 0.0f; Viewer_external_info.current_distance = 0.0f; + Player_aim_cursor = vmd_zero_angles; + if (Default_start_chase_view != The_mission.flags[Mission::Mission_Flags::Toggle_start_chase_view]) { diff --git a/code/ship/ship.cpp b/code/ship/ship.cpp index 7da2f2c9d99..b6fb0d32930 100644 --- a/code/ship/ship.cpp +++ b/code/ship/ship.cpp @@ -12332,7 +12332,12 @@ int ship_fire_primary(object * obj, int force, bool rollback_shot) if ( pm->n_guns > 0 ) { vec3d predicted_target_pos, plr_to_target_vec; - vec3d player_forward_vec = obj->orient.vec.fvec; + matrix firing_orient = obj->orient; + if (obj == Player_obj) { + vm_angles_2_matrix(&firing_orient, &Player_aim_cursor); + firing_orient = firing_orient * obj->orient; + } + bool in_automatic_aim_fov = false; float dist_to_aim = 0; @@ -12359,7 +12364,7 @@ int ship_fire_primary(object * obj, int force, bool rollback_shot) vm_vec_sub(&plr_to_target_vec, &predicted_target_pos, &obj->pos); if (has_autoaim) { - angle_to_target = vm_vec_delta_ang(&player_forward_vec, &plr_to_target_vec, NULL); + angle_to_target = vm_vec_delta_ang(&firing_orient.vec.fvec, &plr_to_target_vec, NULL); if (angle_to_target < autoaim_fov) in_automatic_aim_fov = true; } @@ -12593,7 +12598,7 @@ int ship_fire_primary(object * obj, int force, bool rollback_shot) vm_vec_unrotate(&gun_point, &pnt, &obj->orient); vm_vec_add(&firing_pos, &gun_point, &obj->pos); - matrix firing_orient; + matrix firing_orient2; /* I AIM autoaim convergence II AIM autoaim @@ -12619,7 +12624,7 @@ int ship_fire_primary(object * obj, int force, bool rollback_shot) vec3d target_vec, firing_vec, convergence_offset; // make sure vector is of the set length - vm_vec_copy_normalize(&target_vec, &player_forward_vec); + vm_vec_copy_normalize(&target_vec, &firing_orient.vec.fvec); if ((sip->aiming_flags[Ship::Aiming_Flags::Auto_convergence]) && (aip->target_objnum != -1)) { // auto convergence vm_vec_scale(&target_vec, dist_to_aim); @@ -12644,9 +12649,6 @@ int ship_fire_primary(object * obj, int force, bool rollback_shot) vec3d firing_vec; vm_vec_unrotate(&firing_vec, &pm->gun_banks[bank_to_fire].norm[pt], &obj->orient); vm_vector_2_matrix(&firing_orient, &firing_vec, NULL, NULL); - } else { - // no autoaim or convergence - firing_orient = obj->orient; } if (winfo_p->wi_flags[Weapon::Info_Flags::Apply_Recoil]){ // Function to add recoil functionality - DahBlount From 46226a6cbe1240b7a914c5cb22b88e12f84e8172 Mon Sep 17 00:00:00 2001 From: Asteroth Date: Mon, 29 Jan 2024 19:34:36 -0500 Subject: [PATCH 02/63] tweaks --- code/hud/hud.cpp | 4 ---- code/hud/hudparse.cpp | 2 +- code/hud/hudreticle.cpp | 3 +-- code/playerman/playercontrol.cpp | 14 ++++++++++++-- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/code/hud/hud.cpp b/code/hud/hud.cpp index 81a007e1c28..6679f6faa41 100644 --- a/code/hud/hud.cpp +++ b/code/hud/hud.cpp @@ -3745,10 +3745,6 @@ int hud_objective_notify_active() * @details Since the player's view vector may be different from the ship's forward vector, * we calculate the offset of those two in pixels and store the x and y offsets in * variables HUD_nose_x and HUD_nose_y (Swifty) - * - * @param viewer_obj Object, likely to be player - * @param wiggedy_wack - * @param eye_orient */ void HUD_set_offsets() { diff --git a/code/hud/hudparse.cpp b/code/hud/hudparse.cpp index eef76989d7c..847c10bd9ba 100644 --- a/code/hud/hudparse.cpp +++ b/code/hud/hudparse.cpp @@ -2179,7 +2179,7 @@ void load_gauge_center_reticle(gauge_settings* settings) stuff_int(&autoaim_frame); if (optional_string("Aim Cursor Frame:")) - stuff_int(&autoaim_frame); + stuff_int(&aim_cursor_frame); hud_gauge->initBitmaps(fname); hud_gauge->initFirepointDisplay(firepoints, scaleX, scaleY, size); diff --git a/code/hud/hudreticle.cpp b/code/hud/hudreticle.cpp index c4e51e76aa6..0f5648c9a4c 100644 --- a/code/hud/hudreticle.cpp +++ b/code/hud/hudreticle.cpp @@ -236,7 +236,6 @@ HudGauge(HUD_OBJECT_CENTER_RETICLE, HUD_CENTER_RETICLE, true, false, (VM_EXTERNA void HudGaugeReticle::initBitmaps(char *fname) { - Warning(LOCATION, "Cannot load hud ani: %s\n", fname); crosshair.first_frame = bm_load_animation(fname, &crosshair.num_frames); if (crosshair.first_frame < 0) { Warning(LOCATION, "Cannot load hud ani: %s\n", fname); @@ -1163,7 +1162,7 @@ void hud_reticle_set_aim_cursor_offset() { vm_angles_2_matrix(&view_mat, &Player_aim_cursor); view_mat = view_mat * Player_obj->orient; - vec3d view_pos = Eye_position + view_mat.vec.fvec * 100.0f; + vec3d view_pos = Eye_position + view_mat.vec.fvec * 10000.0f; g3_rotate_vertex(&Player_aim_cursor_offset, &view_pos); g3_project_vertex(&Player_aim_cursor_offset); } diff --git a/code/playerman/playercontrol.cpp b/code/playerman/playercontrol.cpp index 196c71ed527..69099124a95 100644 --- a/code/playerman/playercontrol.cpp +++ b/code/playerman/playercontrol.cpp @@ -1064,10 +1064,20 @@ void read_player_controls(object *objp, float frametime) if (mag > max_aim_angle) { Player_aim_cursor.p *= max_aim_angle / mag; Player_aim_cursor.h *= max_aim_angle / mag; + mag = max_aim_angle; } - Player->ci.pitch = Player_aim_cursor.p / max_aim_angle; - Player->ci.heading = Player_aim_cursor.h / max_aim_angle; + float deadzone = 0.02; + if (mag > deadzone) { + float p = Player_aim_cursor.p * ((mag - deadzone) / mag); + float h = Player_aim_cursor.h * ((mag - deadzone) / mag); + + Player->ci.pitch = p / (max_aim_angle - deadzone); + Player->ci.heading = h / (max_aim_angle - deadzone); + } else { + Player->ci.pitch = 0.0f; + Player->ci.heading = 0.0f; + } } if(Player_obj->type == OBJ_SHIP && !Player_use_ai){ From 89f4fe54c3214ba798a7bfb53450440c5885ca74 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Thu, 11 Apr 2024 15:14:47 -0400 Subject: [PATCH 03/63] convert model_h and submodel_h to use indexes Indexes are a more robust way than pointers to manage model and submodel handles, and in the event that models are made dynamic in the future, indexes will be necessary anyway. --- code/scripting/api/objs/model.cpp | 55 +++++++++++++++---------------- code/scripting/api/objs/model.h | 5 ++- 2 files changed, 28 insertions(+), 32 deletions(-) diff --git a/code/scripting/api/objs/model.cpp b/code/scripting/api/objs/model.cpp index 2d30e194b2a..60dd19c2871 100644 --- a/code/scripting/api/objs/model.cpp +++ b/code/scripting/api/objs/model.cpp @@ -15,54 +15,51 @@ ADE_OBJ(l_Model, model_h, "model", "3D Model (POF) handle"); polymodel *model_h::Get() const { - return model; + return isValid() ? model_get(model_num) : nullptr; } int model_h::GetID() const { - return model ? model->id : -1; + return isValid() ? model_num : -1; } bool model_h::isValid() const { - return (model != nullptr); + return (model_num >= 0) && (model_get(model_num) != nullptr); } model_h::model_h(int n_modelnum) -{ - if (n_modelnum >= 0) - model = model_get(n_modelnum); - else - model = nullptr; -} + : model_num(n_modelnum) +{} model_h::model_h(polymodel *n_model) - : model(n_model) -{ -} + : model_h(n_model->id) +{} model_h::model_h() - : model(nullptr) -{ -} + : model_h(-1) +{} ADE_OBJ(l_Submodel, submodel_h, "submodel", "Handle to a submodel"); -submodel_h::submodel_h() - : model(nullptr), submodel_num(-1) +submodel_h::submodel_h(int n_modelnum, int n_submodelnum) + : model_num(n_modelnum), submodel_num(n_submodelnum) {} submodel_h::submodel_h(polymodel *n_model, int n_submodelnum) - : model(n_model), submodel_num(n_submodelnum) -{ -} -submodel_h::submodel_h(int n_modelnum, int n_submodelnum) - : submodel_num(n_submodelnum) -{ - model = model_get(n_modelnum); -} -polymodel *submodel_h::GetModel() const { return isValid() ? model : nullptr; } -int submodel_h::GetModelID() const { return isValid() ? model->id : -1; } -bsp_info* submodel_h::GetSubmodel() const { return isValid() ? &model->submodel[submodel_num] : nullptr; } + : submodel_h(n_model->id, n_submodelnum) +{} +submodel_h::submodel_h() + : submodel_h(-1, -1) +{} +polymodel *submodel_h::GetModel() const { return isValid() ? model_get(model_num) : nullptr; } +int submodel_h::GetModelID() const { return isValid() ? model_num : -1; } +bsp_info* submodel_h::GetSubmodel() const { return isValid() ? &model_get(model_num)->submodel[submodel_num] : nullptr; } int submodel_h::GetSubmodelIndex() const { return isValid() ? submodel_num : -1; } bool submodel_h::isValid() const { - return model != nullptr && submodel_num >= 0 && submodel_num < model->n_models; + if (model_num >= 0 && submodel_num >= 0) + { + auto model = model_get(model_num); + if (model != nullptr) + return submodel_num < model->n_models; + } + return false; } diff --git a/code/scripting/api/objs/model.h b/code/scripting/api/objs/model.h index 038f646ed58..993f54eb23e 100644 --- a/code/scripting/api/objs/model.h +++ b/code/scripting/api/objs/model.h @@ -11,7 +11,7 @@ namespace api { class model_h { protected: - polymodel *model; + int model_num; public: explicit model_h(int n_modelnum); @@ -19,7 +19,6 @@ class model_h model_h(); polymodel *Get() const; - int GetID() const; bool isValid() const; @@ -29,7 +28,7 @@ DECLARE_ADE_OBJ(l_Model, model_h); class submodel_h { protected: - polymodel *model; + int model_num; int submodel_num; public: From 7ebfa52bf5959ec4fa8cc7669f26c69cf6acd4d9 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Wed, 1 May 2024 16:11:21 -0400 Subject: [PATCH 04/63] standardize file modification time formatting between FRED and qtFRED --- fred2/management.cpp | 12 ++++++++++-- fred2/management.h | 2 ++ fred2/missionsave.cpp | 7 ++++--- qtfred/src/mission/Editor.cpp | 7 +++---- qtfred/src/mission/missionsave.cpp | 8 ++++---- qtfred/src/mission/util.cpp | 7 +++++++ qtfred/src/mission/util.h | 2 ++ 7 files changed, 32 insertions(+), 13 deletions(-) diff --git a/fred2/management.cpp b/fred2/management.cpp index 701bcf8d7a6..be5dced82c7 100644 --- a/fred2/management.cpp +++ b/fred2/management.cpp @@ -810,11 +810,14 @@ void clear_mission(bool fast_reload) } } } - t = CTime::GetCurrentTime(); + + time_t currentTime; + time(¤tTime); + auto timeinfo = localtime(¤tTime); strcpy_s(The_mission.name, "Untitled"); The_mission.author = str; - strcpy_s(The_mission.created, t.Format("%x at %X")); + time_to_mission_info_string(timeinfo, The_mission.created, DATE_TIME_LENGTH - 1); strcpy_s(The_mission.modified, The_mission.created); strcpy_s(The_mission.notes, "This is a FRED2_OPEN created mission."); strcpy_s(The_mission.mission_desc, "Put mission description here"); @@ -2535,3 +2538,8 @@ void update_texture_replacements(const char *old_name, const char *new_name) strcpy_s(ii->ship_name, new_name); } } + +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); +} diff --git a/fred2/management.h b/fred2/management.h index 1cb250f3774..49f85514c3a 100644 --- a/fred2/management.h +++ b/fred2/management.h @@ -143,4 +143,6 @@ extern void stuff_special_arrival_anchor_name(char* buf, int iff_index, int rest extern void stuff_special_arrival_anchor_name(char* buf, int anchor_num, int retail_format); extern void update_texture_replacements(const char* old_name, const char* new_name); +extern void time_to_mission_info_string(const std::tm* src, char* dest, size_t dest_max_len); + #endif diff --git a/fred2/missionsave.cpp b/fred2/missionsave.cpp index 6e6544a34b8..6a3ca9a448c 100644 --- a/fred2/missionsave.cpp +++ b/fred2/missionsave.cpp @@ -3126,10 +3126,11 @@ int CFred_mission_save::save_mission_info() void CFred_mission_save::save_mission_internal(const char *pathname) { - CTime t; + time_t currentTime; + time(¤tTime); + auto timeinfo = localtime(¤tTime); - t = CTime::GetCurrentTime(); - strcpy_s(The_mission.modified, t.Format("%x at %X")); + time_to_mission_info_string(timeinfo, The_mission.modified, DATE_TIME_LENGTH - 1); // Migrate the version! The_mission.required_fso_version = MISSION_VERSION; diff --git a/qtfred/src/mission/Editor.cpp b/qtfred/src/mission/Editor.cpp index 5c3b1b1e12b..640545aee0f 100644 --- a/qtfred/src/mission/Editor.cpp +++ b/qtfred/src/mission/Editor.cpp @@ -33,6 +33,7 @@ #include "object.h" #include "management.h" +#include "util.h" #include "FredApplication.h" namespace { @@ -418,13 +419,11 @@ void Editor::clearMission(bool fast_reload) { time_t currentTime; time(¤tTime); - auto tm_info = localtime(¤tTime); - char time_buffer[26]; - strftime(time_buffer, 26, "%Y-%m-%d %H:%M:%S", tm_info); + auto timeinfo = localtime(¤tTime); strcpy_s(The_mission.name, "Untitled"); The_mission.author = userName; - strcpy_s(The_mission.created, time_buffer); + time_to_mission_info_string(timeinfo, The_mission.created, DATE_TIME_LENGTH - 1); strcpy_s(The_mission.modified, The_mission.created); strcpy_s(The_mission.notes, "This is a FRED2_OPEN created mission."); strcpy_s(The_mission.mission_desc, "Put mission description here"); diff --git a/qtfred/src/mission/missionsave.cpp b/qtfred/src/mission/missionsave.cpp index 9dac2eb9ac7..20fd5200d0c 100644 --- a/qtfred/src/mission/missionsave.cpp +++ b/qtfred/src/mission/missionsave.cpp @@ -3035,11 +3035,11 @@ int CFred_mission_save::save_mission_info() void CFred_mission_save::save_mission_internal(const char* pathname) { - time_t rawtime; + time_t currentTime; + time(¤tTime); + auto timeinfo = localtime(¤tTime); - time(&rawtime); - auto timeinfo = localtime(&rawtime); - strftime(The_mission.modified, sizeof(The_mission.modified), "%x at %X", timeinfo); + time_to_mission_info_string(timeinfo, The_mission.modified, DATE_TIME_LENGTH - 1); // Migrate the version! The_mission.required_fso_version = MISSION_VERSION; diff --git a/qtfred/src/mission/util.cpp b/qtfred/src/mission/util.cpp index e7108578ca3..3dfb5101cc0 100644 --- a/qtfred/src/mission/util.cpp +++ b/qtfred/src/mission/util.cpp @@ -25,6 +25,7 @@ void stuff_special_arrival_anchor_name(char *buf, int iff_index, int restrict_to strlwr(buf); } + void stuff_special_arrival_anchor_name(char* buf, int anchor_num, int retail_format) { // filter out iff int iff_index = anchor_num; @@ -57,6 +58,7 @@ void generate_weaponry_usage_list_team(int team, int* arr) { } } } + void generate_weaponry_usage_list_wing(int wing_num, int* arr) { int i, j; ship_weapon* swp; @@ -85,3 +87,8 @@ void generate_weaponry_usage_list_wing(int wing_num, int* arr) { } } } + +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); +} diff --git a/qtfred/src/mission/util.h b/qtfred/src/mission/util.h index 461917a80a9..c4ddd5ae818 100644 --- a/qtfred/src/mission/util.h +++ b/qtfred/src/mission/util.h @@ -8,3 +8,5 @@ void stuff_special_arrival_anchor_name(char *buf, int anchor_num, int retail_for void generate_weaponry_usage_list_team(int team, int *arr); void generate_weaponry_usage_list_wing(int wing_num, int *arr); + +void time_to_mission_info_string(const std::tm* src, char* dest, size_t dest_max_len); From 8d6a8721f95fc754582b6ef5e593678a92b2fa03 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Wed, 1 May 2024 20:24:15 -0400 Subject: [PATCH 05/63] taylor's patch for file modification time --- code/cfile/cfile.h | 1 + code/cfile/cfilesystem.cpp | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/code/cfile/cfile.h b/code/cfile/cfile.h index 5ec3e04dbb7..112957f78ab 100644 --- a/code/cfile/cfile.h +++ b/code/cfile/cfile.h @@ -377,6 +377,7 @@ struct CFileLocation { SCP_string full_name; size_t size = 0; size_t offset = 0; + time_t m_time = 0; const void* data_ptr = nullptr; explicit CFileLocation(bool found_in = false) : found(found_in) {} diff --git a/code/cfile/cfilesystem.cpp b/code/cfile/cfilesystem.cpp index a1810b045d1..a130fa36268 100644 --- a/code/cfile/cfilesystem.cpp +++ b/code/cfile/cfilesystem.cpp @@ -1236,6 +1236,22 @@ static bool sub_path_match(const SCP_string &search, const SCP_string &index) return !stricmp(search.c_str(), index.c_str()); } +static time_t get_mtime(int fd) +{ +#ifdef _WIN32 + struct _stat buf; + #define fstat _fstat +#else + struct stat buf; +#endif + + if (fstat(fd, &buf) != 0) { + return 0; + } + + return buf.st_mtime; +} + /** * Searches for a file. * @@ -1270,6 +1286,7 @@ CFileLocation cf_find_file_location(const char* filespec, int pathtype, uint32_t if (fp) { CFileLocation res(true); res.size = static_cast(filelength(fileno(fp))); + res.m_time = get_mtime(fileno(fp)); fclose(fp); @@ -1332,6 +1349,7 @@ CFileLocation cf_find_file_location(const char* filespec, int pathtype, uint32_t if (fp) { CFileLocation res(true); res.size = static_cast(filelength( fileno(fp) )); + res.m_time = get_mtime(fileno(fp)); fclose(fp); @@ -1388,6 +1406,7 @@ CFileLocation cf_find_file_location(const char* filespec, int pathtype, uint32_t res.offset = (size_t)f->pack_offset; res.data_ptr = f->data; res.name_ext = f->name_ext; + res.m_time = f->write_time; if (f->data != nullptr) { // This is an in-memory file so we just copy the pathtype name + file name @@ -1523,6 +1542,7 @@ CFileLocationExt cf_find_file_location_ext(const char *filename, const int ext_n CFileLocationExt res(cur_ext); res.found = true; res.size = static_cast(filelength( fileno(fp) )); + res.m_time = get_mtime(fileno(fp)); fclose(fp); @@ -1611,6 +1631,7 @@ CFileLocationExt cf_find_file_location_ext(const char *filename, const int ext_n res.offset = (size_t)f->pack_offset; res.data_ptr = f->data; res.name_ext = f->name_ext; + res.m_time = f->write_time; if (f->data != nullptr) { // This is an in-memory file so we just copy the pathtype name + file name From 2f5e7a4de24a020ac4d9d8c5778ed98a4d6edd70 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Wed, 1 May 2024 20:20:28 -0400 Subject: [PATCH 06/63] tweak MessageBox call --- fred2/freddoc.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fred2/freddoc.cpp b/fred2/freddoc.cpp index 4bfcaad6b71..b9cba67da7d 100644 --- a/fred2/freddoc.cpp +++ b/fred2/freddoc.cpp @@ -535,7 +535,7 @@ void CFREDDoc::OnFileImportFSM() { if (num_files > 1) { create_new_mission(); - MessageBox(NULL, "Import complete. Please check the destination folder to verify all missions were imported successfully.", "Status", MB_OK); + Fred_view_wnd->MessageBox("Import complete. Please check the destination folder to verify all missions were imported successfully.", "Status", MB_OK); } else if (num_files == 1) { @@ -816,7 +816,7 @@ Assert((flag == 0) || (flag == 1)); // fp = cfopen(filename, flag ? "wb" : "rb"); // if (!fp) -// MessageBox(NULL, strerror(errno), "File Open Error!", MB_ICONSTOP); +// Fred_view_wnd->MessageBox(strerror(errno), "File Open Error!", MB_ICONSTOP); // Find highest used object if writing. if (flag == 1) { From 9edcf5f4505eb46552ec0111638565138d109157 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Wed, 1 May 2024 20:20:54 -0400 Subject: [PATCH 07/63] add routine to recover autosaved mission --- fred2/freddoc.cpp | 56 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/fred2/freddoc.cpp b/fred2/freddoc.cpp index b9cba67da7d..de7f0119304 100644 --- a/fred2/freddoc.cpp +++ b/fred2/freddoc.cpp @@ -568,7 +568,8 @@ BOOL CFREDDoc::OnNewDocument() { return TRUE; } -BOOL CFREDDoc::OnOpenDocument(LPCTSTR pathname) { +BOOL CFREDDoc::OnOpenDocument(LPCTSTR pathname) +{ if (Briefing_dialog) Briefing_dialog->icon_select(-1); // clean things up first @@ -585,7 +586,58 @@ BOOL CFREDDoc::OnOpenDocument(LPCTSTR pathname) { strncpy(Mission_filename, filename, len); Mission_filename[len] = 0; - if (!load_mission(pathname)) { + // first, just grab the info of this mission + if (!parse_main(pathname, MPF_ONLY_MISSION_INFO)) + { + *Mission_filename = 0; + return FALSE; + } + SCP_string created = The_mission.created; + CFileLocation res = cf_find_file_location(pathname, CF_TYPE_ANY); + time_t modified = res.m_time; + if (!res.found) + { + UNREACHABLE("Couldn't find path '%s' even though parse_main() succeeded!", pathname); + created = ""; // prevent any backup check from succeeding so we just load the actual specified file + } + + // now check all the autosaves + SCP_string backup_name; + CFileLocation backup_res; + for (int i = 1; i <= BACKUP_DEPTH; ++i) + { + backup_name = MISSION_BACKUP_NAME; + char extension[5]; + sprintf(extension, ".%.3d", i); + backup_name += extension; + + backup_res = cf_find_file_location(backup_name.c_str(), CF_TYPE_MISSIONS); + if (backup_res.found && parse_main(backup_res.full_name.c_str(), MPF_ONLY_MISSION_INFO)) + { + SCP_string this_created = The_mission.created; + time_t this_modified = backup_res.m_time; + + if (created == this_created && this_modified > modified) + break; + } + + backup_name.clear(); + } + + // maybe load from the backup instead + if (!backup_name.empty()) + { + SCP_string prompt = "Autosaved file "; + prompt += backup_name; + prompt += " has a file modification time more recent than the specified file. Do you want to load the autosave instead?"; + int z = Fred_view_wnd->MessageBox(prompt.c_str(), "Recover from autosave", MB_ICONQUESTION | MB_YESNO); + if (z == IDYES) + pathname = backup_res.full_name.c_str(); + } + + // now we can actually load either the original path or the backup path + if (!load_mission(pathname)) + { *Mission_filename = 0; return FALSE; } From 3d92d469ac7b0aa82759a165fa5b505d2e1ce9b0 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Wed, 1 May 2024 22:22:24 -0400 Subject: [PATCH 08/63] move backup info and make them consistent --- fred2/freddoc.cpp | 8 ++++---- fred2/freddoc.h | 5 +++-- fred2/missionsave.cpp | 4 ++-- fred2/missionsave.h | 2 -- qtfred/src/mission/Editor.h | 3 +++ qtfred/src/mission/missionsave.cpp | 4 ++-- qtfred/src/mission/missionsave.h | 2 -- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/fred2/freddoc.cpp b/fred2/freddoc.cpp index de7f0119304..f71a7694e9b 100644 --- a/fred2/freddoc.cpp +++ b/fred2/freddoc.cpp @@ -77,7 +77,7 @@ CFREDDoc::CFREDDoc() { FREDDoc_ptr = this; - for (i = 0; i < BACKUP_DEPTH; i++) + for (i = 0; i < MISSION_BACKUP_DEPTH; i++) undo_desc[i].Empty(); } @@ -124,7 +124,7 @@ bool CFREDDoc::autoload() { cf_delete(backup_name, CF_TYPE_MISSIONS); // Rename Backups. .002 becomes .001, .003 becomes .002, etc. - for (i = 1; i < BACKUP_DEPTH; i++) { + for (i = 1; i < MISSION_BACKUP_DEPTH; i++) { sprintf(backup_name + len, ".%.3d", i + 1); sprintf(name + len, ".%.3d", i); cf_rename(backup_name, name, CF_TYPE_MISSIONS); @@ -155,7 +155,7 @@ int CFREDDoc::autosave(char *desc) { return -1; } - for (i = BACKUP_DEPTH; i > 1; i--) { + for (i = MISSION_BACKUP_DEPTH; i > 1; i--) { undo_desc[i] = undo_desc[i - 1]; } @@ -604,7 +604,7 @@ BOOL CFREDDoc::OnOpenDocument(LPCTSTR pathname) // now check all the autosaves SCP_string backup_name; CFileLocation backup_res; - for (int i = 1; i <= BACKUP_DEPTH; ++i) + for (int i = 1; i <= MISSION_BACKUP_DEPTH; ++i) { backup_name = MISSION_BACKUP_NAME; char extension[5]; diff --git a/fred2/freddoc.h b/fred2/freddoc.h index 387732ebd77..58a81b2270d 100644 --- a/fred2/freddoc.h +++ b/fred2/freddoc.h @@ -10,7 +10,8 @@ */ #include "MissionSave.h" -#define MISSION_BACKUP_NAME "Backup" +#define MISSION_BACKUP_NAME "Backup" +#define MISSION_BACKUP_DEPTH 9 #define US_WORLD_CHANGED 0x01 #define US_VIEW_CHANGED 0x02 @@ -145,7 +146,7 @@ class CFREDDoc : public CDocument virtual void Dump(CDumpContext &dc) const; #endif - CString undo_desc[BACKUP_DEPTH + 1]; //!< String array of the undo descriptions + CString undo_desc[MISSION_BACKUP_DEPTH + 1]; //!< String array of the undo descriptions protected: /** diff --git a/fred2/missionsave.cpp b/fred2/missionsave.cpp index 6a3ca9a448c..0e08c75b29e 100644 --- a/fred2/missionsave.cpp +++ b/fred2/missionsave.cpp @@ -92,9 +92,9 @@ int CFred_mission_save::autosave_mission_file(char *pathname) auto len = strlen(pathname); strcpy_s(backup_name, pathname); strcpy_s(name2, pathname); - sprintf(backup_name + len, ".%.3d", BACKUP_DEPTH); + sprintf(backup_name + len, ".%.3d", MISSION_BACKUP_DEPTH); cf_delete(backup_name, CF_TYPE_MISSIONS); - for (i = BACKUP_DEPTH; i > 1; i--) { + for (i = MISSION_BACKUP_DEPTH; i > 1; i--) { sprintf(backup_name + len, ".%.3d", i - 1); sprintf(name2 + len, ".%.3d", i); cf_rename(backup_name, name2, CF_TYPE_MISSIONS); diff --git a/fred2/missionsave.h b/fred2/missionsave.h index f8334695f1a..8b5e5e578c0 100644 --- a/fred2/missionsave.h +++ b/fred2/missionsave.h @@ -24,8 +24,6 @@ struct sexp_container; -#define BACKUP_DEPTH 9 - /** * @class CFred_mission_save * diff --git a/qtfred/src/mission/Editor.h b/qtfred/src/mission/Editor.h index 9224a903f90..d14c25b942d 100644 --- a/qtfred/src/mission/Editor.h +++ b/qtfred/src/mission/Editor.h @@ -15,6 +15,9 @@ #include #include +#define MISSION_BACKUP_NAME "Backup" +#define MISSION_BACKUP_DEPTH 9 + namespace fso { namespace fred { diff --git a/qtfred/src/mission/missionsave.cpp b/qtfred/src/mission/missionsave.cpp index 20fd5200d0c..30e27518883 100644 --- a/qtfred/src/mission/missionsave.cpp +++ b/qtfred/src/mission/missionsave.cpp @@ -79,9 +79,9 @@ int CFred_mission_save::autosave_mission_file(char* pathname) auto len = strlen(pathname); strcpy_s(backup_name, pathname); strcpy_s(name2, pathname); - sprintf(backup_name + len, ".%.3d", BACKUP_DEPTH); + sprintf(backup_name + len, ".%.3d", MISSION_BACKUP_DEPTH); cf_delete(backup_name, CF_TYPE_MISSIONS); - for (i = BACKUP_DEPTH; i > 1; i--) { + for (i = MISSION_BACKUP_DEPTH; i > 1; i--) { sprintf(backup_name + len, ".%.3d", i - 1); sprintf(name2 + len, ".%.3d", i); cf_rename(backup_name, name2, CF_TYPE_MISSIONS); diff --git a/qtfred/src/mission/missionsave.h b/qtfred/src/mission/missionsave.h index 53f8876cef0..845627a20f2 100644 --- a/qtfred/src/mission/missionsave.h +++ b/qtfred/src/mission/missionsave.h @@ -27,8 +27,6 @@ struct sexp_container; namespace fso { namespace fred { -#define BACKUP_DEPTH 9 - enum class MissionFormat { RETAIL = 0, STANDARD = 1, COMPATIBILITY_MODE = 2 }; From bc4cc598c9f047c592be9804c460e3c55f2435a2 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Wed, 1 May 2024 22:42:06 -0400 Subject: [PATCH 09/63] qtfred too --- qtfred/src/mission/Editor.cpp | 55 +++++++++++++++++++++++++++++++++++ qtfred/src/mission/Editor.h | 2 ++ qtfred/src/ui/FredView.cpp | 4 ++- 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/qtfred/src/mission/Editor.cpp b/qtfred/src/mission/Editor.cpp index 640545aee0f..6a5b7ec420b 100644 --- a/qtfred/src/mission/Editor.cpp +++ b/qtfred/src/mission/Editor.cpp @@ -127,6 +127,61 @@ void Editor::update() { } } +std::string Editor::maybeUseAutosave(const std::string& filepath) +{ + // first, just grab the info of this mission + if (!parse_main(filepath.c_str(), MPF_ONLY_MISSION_INFO)) + return filepath; + SCP_string created = The_mission.created; + CFileLocation res = cf_find_file_location(filepath.c_str(), CF_TYPE_ANY); + time_t modified = res.m_time; + if (!res.found) + { + UNREACHABLE("Couldn't find path '%s' even though parse_main() succeeded!", filepath.c_str()); + return filepath; // just load the actual specified file + } + + // now check all the autosaves + SCP_string backup_name; + CFileLocation backup_res; + for (int i = 1; i <= MISSION_BACKUP_DEPTH; ++i) + { + backup_name = MISSION_BACKUP_NAME; + char extension[5]; + sprintf(extension, ".%.3d", i); + backup_name += extension; + + backup_res = cf_find_file_location(backup_name.c_str(), CF_TYPE_MISSIONS); + if (backup_res.found && parse_main(backup_res.full_name.c_str(), MPF_ONLY_MISSION_INFO)) + { + SCP_string this_created = The_mission.created; + time_t this_modified = backup_res.m_time; + + if (created == this_created && this_modified > modified) + break; + } + + backup_name.clear(); + } + + // maybe load from the backup instead + if (!backup_name.empty()) + { + SCP_string prompt = "Autosaved file "; + prompt += backup_name; + prompt += " has a file modification time more recent than the specified file. Do you want to load the autosave instead?"; + + auto z = _lastActiveViewport->dialogProvider->showButtonDialog(DialogType::Question, + "Recover from autosave", + prompt.c_str(), + { DialogButton::Yes, DialogButton::No }); + if (z == DialogButton::Yes) + return backup_res.full_name.c_str(); + } + + return filepath; +} + bool Editor::loadMission(const std::string& mission_name, int flags) { char name[512], * old_name; int i, j, k, ob; diff --git a/qtfred/src/mission/Editor.h b/qtfred/src/mission/Editor.h index d14c25b942d..fb6641fccba 100644 --- a/qtfred/src/mission/Editor.h +++ b/qtfred/src/mission/Editor.h @@ -39,6 +39,8 @@ class Editor : public QObject { void createNewMission(); + std::string maybeUseAutosave(const std::string& filepath); + /*! Load a mission. */ bool loadMission(const std::string& filepath, int flags = 0); diff --git a/qtfred/src/ui/FredView.cpp b/qtfred/src/ui/FredView.cpp index 75fedfdb87a..a3d698c377d 100644 --- a/qtfred/src/ui/FredView.cpp +++ b/qtfred/src/ui/FredView.cpp @@ -143,7 +143,9 @@ void FredView::loadMissionFile(const QString& pathName) { try { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - fred->loadMission(pathName.toStdString()); + auto pathToLoad = fred->maybeUseAutosave(pathName.toStdString()); + + fred->loadMission(pathToLoad); QApplication::restoreOverrideCursor(); } catch (const fso::fred::mission_load_error&) { From 49a1e8458fe9962e011ce8328f2ac9a558ac13e7 Mon Sep 17 00:00:00 2001 From: Asteroth Date: Sat, 11 May 2024 12:19:14 -0400 Subject: [PATCH 10/63] add in game options and adjustments --- code/hud/hud.cpp | 2 +- code/hud/hudparse.cpp | 6 +- code/hud/hudreticle.cpp | 84 +++++++++++++++-------- code/hud/hudreticle.h | 6 +- code/localization/localize.cpp | 2 +- code/playerman/player.h | 9 ++- code/playerman/playercontrol.cpp | 114 +++++++++++++++++++++++-------- code/ship/ship.cpp | 25 ++++++- code/ship/ship.h | 3 + 9 files changed, 182 insertions(+), 69 deletions(-) diff --git a/code/hud/hud.cpp b/code/hud/hud.cpp index 19047152855..f15654a527b 100644 --- a/code/hud/hud.cpp +++ b/code/hud/hud.cpp @@ -3764,7 +3764,7 @@ void HUD_set_offsets() HUD_get_nose_coordinates(&HUD_nose_x, &HUD_nose_y); } - hud_reticle_set_aim_cursor_offset(); + hud_reticle_set_flight_cursor_offset(); } /** diff --git a/code/hud/hudparse.cpp b/code/hud/hudparse.cpp index a72a4adaa1d..c0f97c35ebe 100644 --- a/code/hud/hudparse.cpp +++ b/code/hud/hudparse.cpp @@ -2132,7 +2132,7 @@ void load_gauge_center_reticle(gauge_settings* settings) int scaleY = 10; int size = 5; int autoaim_frame = -1; - int aim_cursor_frame = -1; + int flight_cursor_frame = -1; settings->origin[0] = 0.5f; settings->origin[1] = 0.5f; @@ -2186,13 +2186,13 @@ void load_gauge_center_reticle(gauge_settings* settings) stuff_int(&autoaim_frame); if (optional_string("Aim Cursor Frame:")) - stuff_int(&aim_cursor_frame); + stuff_int(&flight_cursor_frame); hud_gauge->initBitmaps(fname); hud_gauge->initHiRes(fname); hud_gauge->initFirepointDisplay(firepoints, scaleX, scaleY, size); hud_gauge->setAutoaimFrame(autoaim_frame); - hud_gauge->setAimCursorFrame(aim_cursor_frame); + hud_gauge->setFlightCursorFrame(flight_cursor_frame); gauge_assign_common(settings, std::move(hud_gauge)); } diff --git a/code/hud/hudreticle.cpp b/code/hud/hudreticle.cpp index 0f5648c9a4c..81183bf7d62 100644 --- a/code/hud/hudreticle.cpp +++ b/code/hud/hudreticle.cpp @@ -225,7 +225,7 @@ int Reticle_launch_coords[GR_NUM_RESOLUTIONS][2] = { static int Threat_lock_timer; // timestamp for when to show next flashing frame for lock threat static int Threat_lock_frame; // frame offset of current lock flashing warning -static vertex Player_aim_cursor_offset; +static vertex Player_flight_cursor_offset; HudGaugeReticle::HudGaugeReticle(): @@ -287,34 +287,59 @@ void HudGaugeReticle::render(float /*frametime*/) setGaugeColor(HUD_C_BRIGHT); - if (HUD_shadows){ - gr_set_color_fast(&Color_black); + // the typical reticle indicating the direction of shooting + int shoot_reticle = 0; + if (has_autoaim_lock) + shoot_reticle = crosshair.first_frame + autoaim_frame_offset; + else + shoot_reticle = crosshair.first_frame; + + // a secondary 'reticle' for distinguishing the flight cursor from the above + int flight_reticle = flight_cursor_frame_offset >= 0 ? crosshair.first_frame + flight_cursor_frame_offset : -1; + + int mobile_reticle = flight_reticle; + int fixed_reticle = shoot_reticle; + // depending on the parameters of the ship, if theyre using the flight cursor mode, the 'mobile' reticle may be the one indicating the shoot direction + if (sip->aims_at_flight_cursor && Player_flight_mode == FlightMode::FlightCursor) { + mobile_reticle = shoot_reticle; + fixed_reticle = flight_reticle; + } + + + if (fixed_reticle == shoot_reticle) + setGaugeColor(HUD_C_BRIGHT); + else + setGaugeColor(HUD_C_NORMAL); + + if (fixed_reticle >= 0) { + if (HUD_shadows) { + gr_set_color_fast(&Color_black); - if (has_autoaim_lock) - { - // Render the shadow twice to increase visibility - renderBitmap(crosshair.first_frame + autoaim_frame_offset, position[0] + 1, position[1] + 1); - renderBitmap(crosshair.first_frame + autoaim_frame_offset, position[0] + 1, position[1] + 1); - } - else - { // Render the shadow twice to increase visibility - renderBitmap(crosshair.first_frame, position[0] + 1, position[1] + 1); - renderBitmap(crosshair.first_frame, position[0] + 1, position[1] + 1); + renderBitmap(fixed_reticle, position[0] + 1, position[1] + 1); + renderBitmap(fixed_reticle, position[0] + 1, position[1] + 1); + gr_set_color_fast(&gauge_color); } - gr_set_color_fast(&gauge_color); + + renderBitmap(fixed_reticle, position[0], position[1]); + } else { + renderCircle(base_w * 0.5, base_h * 0.5, base_h * 0.03, false); } - if (has_autoaim_lock) - renderBitmap(crosshair.first_frame + autoaim_frame_offset, position[0], position[1]); - else - renderBitmap(crosshair.first_frame, position[0], position[1]); + if (Player_flight_mode == FlightMode::FlightCursor) { + if (mobile_reticle == shoot_reticle) + setGaugeColor(HUD_C_BRIGHT); + else + setGaugeColor(HUD_C_NORMAL); - if (aim_cursor_frame_offset > 0) { - int x = Player_aim_cursor_offset.screen.xyw.x; - int y = Player_aim_cursor_offset.screen.xyw.y; + int x = Player_flight_cursor_offset.screen.xyw.x; + int y = Player_flight_cursor_offset.screen.xyw.y; unsize(&x, &y); - renderBitmap(crosshair.first_frame + aim_cursor_frame_offset, x - (bitmap_size_x / 2), y - (bitmap_size_y / 2)); + if (mobile_reticle >= 0) + renderBitmap(mobile_reticle, (x - base_w * 0.5) + position[0], (y - base_h * 0.5) + position[1]); + else { + renderCircle(x, y, base_h * 0.03, false); + } } if (firepoint_display) { @@ -424,11 +449,11 @@ void HudGaugeReticle::getFirepointStatus() { } } -void HudGaugeReticle::setAimCursorFrame(int framenum) { +void HudGaugeReticle::setFlightCursorFrame(int framenum) { if (framenum < 0 || framenum > crosshair.num_frames - 1) - aim_cursor_frame_offset = 1; + flight_cursor_frame_offset = -1; else - aim_cursor_frame_offset = framenum; + flight_cursor_frame_offset = framenum; } void HudGaugeReticle::setAutoaimFrame(int framenum) { @@ -1157,12 +1182,13 @@ void hud_update_reticle( player *pp ) } } -void hud_reticle_set_aim_cursor_offset() { +// calculates what the screen position of the aim cursor should be +void hud_reticle_set_flight_cursor_offset() { matrix view_mat; - vm_angles_2_matrix(&view_mat, &Player_aim_cursor); + vm_angles_2_matrix(&view_mat, &Player_flight_cursor); view_mat = view_mat * Player_obj->orient; vec3d view_pos = Eye_position + view_mat.vec.fvec * 10000.0f; - g3_rotate_vertex(&Player_aim_cursor_offset, &view_pos); - g3_project_vertex(&Player_aim_cursor_offset); + g3_rotate_vertex(&Player_flight_cursor_offset, &view_pos); + g3_project_vertex(&Player_flight_cursor_offset); } diff --git a/code/hud/hudreticle.h b/code/hud/hudreticle.h index 054b1367fe2..f6424b092c5 100644 --- a/code/hud/hudreticle.h +++ b/code/hud/hudreticle.h @@ -44,7 +44,7 @@ class HudGaugeReticle: public HudGauge int firepoint_scale_y; int autoaim_frame_offset; bool has_autoaim_lock; - int aim_cursor_frame_offset; + int flight_cursor_frame_offset; public: HudGaugeReticle(); void render(float frametime) override; @@ -53,7 +53,7 @@ class HudGaugeReticle: public HudGauge void initFirepointDisplay(bool firepoint, int scaleX, int scaleY, int size); void getFirepointStatus(); void setAutoaimFrame(int framenum); - void setAimCursorFrame(int framenum); + void setFlightCursorFrame(int framenum); }; class HudGaugeThrottle: public HudGauge @@ -170,6 +170,6 @@ class HudGaugeWeaponLinking: public HudGauge void hud_init_reticle(); void hud_update_reticle( player *pp ); -void hud_reticle_set_aim_cursor_offset(); +void hud_reticle_set_flight_cursor_offset(); #endif diff --git a/code/localization/localize.cpp b/code/localization/localize.cpp index 4fc0e5f0a3f..11f15cec039 100644 --- a/code/localization/localize.cpp +++ b/code/localization/localize.cpp @@ -64,7 +64,7 @@ bool *Lcl_unexpected_tstring_check = nullptr; // NOTE: with map storage of XSTR strings, the indexes no longer need to be contiguous, // but internal strings should still increment XSTR_SIZE to avoid collisions. // retail XSTR_SIZE = 1570 -// #define XSTR_SIZE 1842 // This is the next available ID +// #define XSTR_SIZE 1850 // This is the next available ID // struct to allow for strings.tbl-determined x offset diff --git a/code/playerman/player.h b/code/playerman/player.h index c7ed5b99bee..5a0ce8fb408 100644 --- a/code/playerman/player.h +++ b/code/playerman/player.h @@ -226,7 +226,14 @@ extern player *Player; // pointer to my information extern int Player_use_ai; extern angles chase_slew_angles; // The viewing angles in which viewer_slew_angles will chase to. -extern angles Player_aim_cursor; +extern angles Player_flight_cursor; + +enum class FlightMode { + ShipLocked = 0, + FlightCursor = 1, +}; + +extern FlightMode Player_flight_mode; extern void player_init(); // initialization per level extern void player_level_init(); diff --git a/code/playerman/playercontrol.cpp b/code/playerman/playercontrol.cpp index 69099124a95..15b2f2afcc4 100644 --- a/code/playerman/playercontrol.cpp +++ b/code/playerman/playercontrol.cpp @@ -32,6 +32,7 @@ #include "object/object.h" #include "object/objectdock.h" #include "observer/observer.h" +#include "options/Option.h" #include "parse/parselo.h" #include "playerman/player.h" #include "ship/ship.h" @@ -60,7 +61,58 @@ physics_info Descent_physics; // used when we want to control the player like angles chase_slew_angles; -angles Player_aim_cursor; +angles Player_flight_cursor; + +FlightMode Player_flight_mode = FlightMode::ShipLocked; + +auto FlightModeOption = options::OptionBuilder("Game.FlightMode", + std::pair{"Flight Mode", 1842}, + std::pair{"Choose the flying style to use during gameplay.", 1843}) + .category(std::make_pair("Game", 1824)) + .level(options::ExpertLevel::Advanced) + .values({ {FlightMode::ShipLocked, {"Ship Locked", 1844}}, + {FlightMode::FlightCursor, {"Flight Cursor", 1845}} }) + .default_val(FlightMode::ShipLocked) + .bind_to(&Player_flight_mode) + .flags({ options::OptionFlags::ForceMultiValueSelection }) + .importance(45) + .finish(); + +static SCP_string degrees_display(float val) +{ + auto degrees = fl_degrees(val); + SCP_string out; + sprintf(out, u8"%.1f\u00B0", degrees); + return out; +} + +float flight_cursor_extent; + +auto FlightCursorExtentOption = options::OptionBuilder("Game.FlightCursorExtent", + std::pair{"Flight Cursor Extent", 1846}, + std::pair{"How far from the center the cursor can go.", 1847}) + .category(std::make_pair("Game", 1824)) + .range(0.0f, 0.698f) + .display(degrees_display) + .default_val(0.348f) + .level(options::ExpertLevel::Advanced) + .bind_to(&flight_cursor_extent) + .importance(44) + .finish(); + +float flight_cursor_deadzone; + +auto FlightCursorDeadzoneOption = options::OptionBuilder("Game.FlightCursorDeadzone", + std::pair{"Flight Cursor Deadzone", 1848}, + std::pair{"How far from the center the cursor needs to go before registering.", 1849}) + .category(std::make_pair("Game", 1824)) + .range(0.0f, 0.349f) + .display(degrees_display) + .default_val(0.02f) + .level(options::ExpertLevel::Advanced) + .bind_to(&flight_cursor_deadzone) + .importance(43) + .finish(); int toggle_glide = 0; int press_glide = 0; @@ -970,10 +1022,40 @@ void read_player_controls(object *objp, float frametime) case PCM_NORMAL: read_keyboard_controls(&(Player->ci), frametime, &objp->phys_info ); - // this is similar to ai_control_info_check if (Player_obj->type == OBJ_SHIP) { auto sip = &Ship_info[Ships[Player_obj->instance].ship_info_index]; + if (Player_flight_mode == FlightMode::FlightCursor) { + float max_aim_angle = flight_cursor_extent; + + if (sip->aims_at_flight_cursor) + max_aim_angle = sip->flight_cursor_aim_extent; + + Player_flight_cursor.p += Player->ci.pitch * 0.015f; + Player_flight_cursor.h += Player->ci.heading * 0.015f; + + float mag = powf(powf(Player_flight_cursor.p, 2.0f) + powf(Player_flight_cursor.h, 2.0f), 0.5f); + if (mag > max_aim_angle) { + Player_flight_cursor.p *= max_aim_angle / mag; + Player_flight_cursor.h *= max_aim_angle / mag; + mag = max_aim_angle; + } + + float deadzone = flight_cursor_deadzone; + if (mag > deadzone) { + float p = Player_flight_cursor.p * ((mag - deadzone) / mag); + float h = Player_flight_cursor.h * ((mag - deadzone) / mag); + + Player->ci.pitch = p / (max_aim_angle - deadzone); + Player->ci.heading = h / (max_aim_angle - deadzone); + } + else { + Player->ci.pitch = 0.0f; + Player->ci.heading = 0.0f; + } + } + + // this is similar to ai_control_info_check if (sip->flags[Ship::Info_Flags::Dont_bank_when_turning]) Player->ci.control_flags |= CIF_DONT_BANK_WHEN_TURNING; if (sip->flags[Ship::Info_Flags::Dont_clamp_max_velocity]) @@ -1054,32 +1136,6 @@ void read_player_controls(object *objp, float frametime) } } - if (true) { - float max_aim_angle = 0.35f; - - Player_aim_cursor.p += Player->ci.pitch * 0.015f; - Player_aim_cursor.h += Player->ci.heading * 0.015f; - - float mag = powf(powf(Player_aim_cursor.p, 2.0f) + powf(Player_aim_cursor.h, 2.0f), 0.5f); - if (mag > max_aim_angle) { - Player_aim_cursor.p *= max_aim_angle / mag; - Player_aim_cursor.h *= max_aim_angle / mag; - mag = max_aim_angle; - } - - float deadzone = 0.02; - if (mag > deadzone) { - float p = Player_aim_cursor.p * ((mag - deadzone) / mag); - float h = Player_aim_cursor.h * ((mag - deadzone) / mag); - - Player->ci.pitch = p / (max_aim_angle - deadzone); - Player->ci.heading = h / (max_aim_angle - deadzone); - } else { - Player->ci.pitch = 0.0f; - Player->ci.heading = 0.0f; - } - } - if(Player_obj->type == OBJ_SHIP && !Player_use_ai){ // only read player control info if player ship is not dead // or if Player_use_ai is disabed @@ -1336,7 +1392,7 @@ void player_level_init() Viewer_external_info.preferred_distance = 0.0f; Viewer_external_info.current_distance = 0.0f; - Player_aim_cursor = vmd_zero_angles; + Player_flight_cursor = vmd_zero_angles; if (Default_start_chase_view != The_mission.flags[Mission::Mission_Flags::Toggle_start_chase_view]) diff --git a/code/ship/ship.cpp b/code/ship/ship.cpp index 25b837a7b95..65625beba6e 100644 --- a/code/ship/ship.cpp +++ b/code/ship/ship.cpp @@ -1263,6 +1263,9 @@ void ship_info::clone(const ship_info& other) autoaim_lock_snd = other.autoaim_lock_snd; autoaim_lost_snd = other.autoaim_lost_snd; + aims_at_flight_cursor = other.aims_at_flight_cursor; + flight_cursor_aim_extent = other.flight_cursor_aim_extent; + topdown_offset_def = other.topdown_offset_def; topdown_offset = other.topdown_offset; @@ -1592,6 +1595,9 @@ void ship_info::move(ship_info&& other) autoaim_lock_snd = other.autoaim_lock_snd; autoaim_lost_snd = other.autoaim_lost_snd; + aims_at_flight_cursor = other.aims_at_flight_cursor; + flight_cursor_aim_extent = other.flight_cursor_aim_extent; + topdown_offset_def = other.topdown_offset_def; std::swap(topdown_offset, other.topdown_offset); @@ -2040,6 +2046,9 @@ ship_info::ship_info() autoaim_lock_snd = gamesnd_id(); autoaim_lost_snd = gamesnd_id(); + aims_at_flight_cursor = false; + flight_cursor_aim_extent = -1.0f; + topdown_offset_def = false; vm_vec_zero(&topdown_offset); @@ -3563,6 +3572,18 @@ static void parse_ship_values(ship_info* sip, const bool is_template, const bool } } + if (optional_string("$Aims at Flight Cursor:")) { + stuff_boolean(&sip->aims_at_flight_cursor); + + if (optional_string("+Extent:")) { + stuff_float(&sip->flight_cursor_aim_extent); + sip->flight_cursor_aim_extent = fl_radians(sip->flight_cursor_aim_extent); + } else if (sip->aims_at_flight_cursor && sip->flight_cursor_aim_extent < 0.0f) { + error_display(0, "Ship %s needs to have an +Extent defined if $Aims at Flight Cursor is true.", sip->name); + sip->aims_at_flight_cursor = false; + } + } + if(optional_string("$Convergence:")) { if(optional_string("+Automatic")) @@ -12583,8 +12604,8 @@ int ship_fire_primary(object * obj, int force, bool rollback_shot) if ( pm->n_guns > 0 ) { vec3d predicted_target_pos, plr_to_target_vec; matrix firing_orient = obj->orient; - if (obj == Player_obj) { - vm_angles_2_matrix(&firing_orient, &Player_aim_cursor); + if (obj == Player_obj && Player_flight_mode == FlightMode::FlightCursor && sip->aims_at_flight_cursor) { + vm_angles_2_matrix(&firing_orient, &Player_flight_cursor); firing_orient = firing_orient * obj->orient; } diff --git a/code/ship/ship.h b/code/ship/ship.h index c0bebe2816d..11e508caefb 100644 --- a/code/ship/ship.h +++ b/code/ship/ship.h @@ -1445,6 +1445,9 @@ class ship_info float autoaim_fov; float bank_autoaim_fov[MAX_SHIP_PRIMARY_BANKS]; + bool aims_at_flight_cursor; + float flight_cursor_aim_extent; + bool topdown_offset_def; vec3d topdown_offset; From 5cb0633314ce1902ed53ac75c35bcb45c71fddc0 Mon Sep 17 00:00:00 2001 From: Asteroth Date: Sat, 11 May 2024 13:38:15 -0400 Subject: [PATCH 11/63] fixes --- code/hud/hudparse.cpp | 2 +- code/hud/hudreticle.cpp | 2 +- code/ship/ship.cpp | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/code/hud/hudparse.cpp b/code/hud/hudparse.cpp index c0f97c35ebe..cd457ea77b2 100644 --- a/code/hud/hudparse.cpp +++ b/code/hud/hudparse.cpp @@ -2185,7 +2185,7 @@ void load_gauge_center_reticle(gauge_settings* settings) if (optional_string("Autoaim Frame:")) stuff_int(&autoaim_frame); - if (optional_string("Aim Cursor Frame:")) + if (optional_string("Flight Cursor Frame:")) stuff_int(&flight_cursor_frame); hud_gauge->initBitmaps(fname); diff --git a/code/hud/hudreticle.cpp b/code/hud/hudreticle.cpp index 81183bf7d62..c4a8d52c122 100644 --- a/code/hud/hudreticle.cpp +++ b/code/hud/hudreticle.cpp @@ -1182,7 +1182,7 @@ void hud_update_reticle( player *pp ) } } -// calculates what the screen position of the aim cursor should be +// calculates what the screen position of the flight cursor should be void hud_reticle_set_flight_cursor_offset() { matrix view_mat; vm_angles_2_matrix(&view_mat, &Player_flight_cursor); diff --git a/code/ship/ship.cpp b/code/ship/ship.cpp index 65625beba6e..126894dab5d 100644 --- a/code/ship/ship.cpp +++ b/code/ship/ship.cpp @@ -12868,8 +12868,6 @@ int ship_fire_primary(object * obj, int force, bool rollback_shot) vm_vec_unrotate(&gun_point, &pnt, &obj->orient); vm_vec_add(&firing_pos, &gun_point, &obj->pos); - - matrix firing_orient2; /* I AIM autoaim convergence II AIM autoaim From 5d6e68b8fd4d3b32d8946f4ec8523f41650eedad Mon Sep 17 00:00:00 2001 From: Asteroth Date: Sun, 12 May 2024 13:09:59 -0400 Subject: [PATCH 12/63] tweaks and fixes --- code/hud/hudreticle.cpp | 18 ++++++------ code/playerman/playercontrol.cpp | 47 ++++++++++++++++++-------------- code/ship/ship.cpp | 2 +- 3 files changed, 37 insertions(+), 30 deletions(-) diff --git a/code/hud/hudreticle.cpp b/code/hud/hudreticle.cpp index c4a8d52c122..136cb59eb24 100644 --- a/code/hud/hudreticle.cpp +++ b/code/hud/hudreticle.cpp @@ -299,8 +299,8 @@ void HudGaugeReticle::render(float /*frametime*/) int mobile_reticle = flight_reticle; int fixed_reticle = shoot_reticle; - // depending on the parameters of the ship, if theyre using the flight cursor mode, the 'mobile' reticle may be the one indicating the shoot direction - if (sip->aims_at_flight_cursor && Player_flight_mode == FlightMode::FlightCursor) { + // depending on the parameters of the ship the 'mobile' reticle may be the one indicating the shoot direction + if (sip->aims_at_flight_cursor) { mobile_reticle = shoot_reticle; fixed_reticle = flight_reticle; } @@ -323,22 +323,22 @@ void HudGaugeReticle::render(float /*frametime*/) renderBitmap(fixed_reticle, position[0], position[1]); } else { - renderCircle(base_w * 0.5, base_h * 0.5, base_h * 0.03, false); + renderCircle((int)(base_w * 0.5f), (int)(base_h * 0.5f), (int)(base_h * 0.03f), false); } - if (Player_flight_mode == FlightMode::FlightCursor) { + if (Player_flight_mode == FlightMode::FlightCursor || sip->aims_at_flight_cursor) { if (mobile_reticle == shoot_reticle) setGaugeColor(HUD_C_BRIGHT); else setGaugeColor(HUD_C_NORMAL); - int x = Player_flight_cursor_offset.screen.xyw.x; - int y = Player_flight_cursor_offset.screen.xyw.y; + int x = (int)(Player_flight_cursor_offset.screen.xyw.x + 0.5f); + int y = (int)(Player_flight_cursor_offset.screen.xyw.y + 0.5f); unsize(&x, &y); if (mobile_reticle >= 0) - renderBitmap(mobile_reticle, (x - base_w * 0.5) + position[0], (y - base_h * 0.5) + position[1]); + renderBitmap(mobile_reticle, (int)(x - base_w * 0.5f) + position[0], (int)(y - base_h * 0.5f) + position[1]); else { - renderCircle(x, y, base_h * 0.03, false); + renderCircle(x, y, (int)(base_h * 0.03f), false); } } @@ -1186,7 +1186,7 @@ void hud_update_reticle( player *pp ) void hud_reticle_set_flight_cursor_offset() { matrix view_mat; vm_angles_2_matrix(&view_mat, &Player_flight_cursor); - view_mat = view_mat * Player_obj->orient; + view_mat = view_mat * Eye_matrix; vec3d view_pos = Eye_position + view_mat.vec.fvec * 10000.0f; g3_rotate_vertex(&Player_flight_cursor_offset, &view_pos); diff --git a/code/playerman/playercontrol.cpp b/code/playerman/playercontrol.cpp index 15b2f2afcc4..af468d31291 100644 --- a/code/playerman/playercontrol.cpp +++ b/code/playerman/playercontrol.cpp @@ -1025,31 +1025,38 @@ void read_player_controls(object *objp, float frametime) if (Player_obj->type == OBJ_SHIP) { auto sip = &Ship_info[Ships[Player_obj->instance].ship_info_index]; - if (Player_flight_mode == FlightMode::FlightCursor) { - float max_aim_angle = flight_cursor_extent; + if ((Player_flight_mode == FlightMode::FlightCursor || sip->aims_at_flight_cursor)) { - if (sip->aims_at_flight_cursor) - max_aim_angle = sip->flight_cursor_aim_extent; + if (Viewer_mode & VM_CAMERA_LOCKED) { + float max_aim_angle = flight_cursor_extent; - Player_flight_cursor.p += Player->ci.pitch * 0.015f; - Player_flight_cursor.h += Player->ci.heading * 0.015f; + if (sip->aims_at_flight_cursor) + max_aim_angle = sip->flight_cursor_aim_extent; - float mag = powf(powf(Player_flight_cursor.p, 2.0f) + powf(Player_flight_cursor.h, 2.0f), 0.5f); - if (mag > max_aim_angle) { - Player_flight_cursor.p *= max_aim_angle / mag; - Player_flight_cursor.h *= max_aim_angle / mag; - mag = max_aim_angle; - } + Player_flight_cursor.p += Player->ci.pitch * 0.015f; + Player_flight_cursor.h += Player->ci.heading * 0.015f; + + float mag = powf(powf(Player_flight_cursor.p, 2.0f) + powf(Player_flight_cursor.h, 2.0f), 0.5f); + if (mag > max_aim_angle) { + Player_flight_cursor.p *= max_aim_angle / mag; + Player_flight_cursor.h *= max_aim_angle / mag; + mag = max_aim_angle; + } - float deadzone = flight_cursor_deadzone; - if (mag > deadzone) { - float p = Player_flight_cursor.p * ((mag - deadzone) / mag); - float h = Player_flight_cursor.h * ((mag - deadzone) / mag); + float deadzone = flight_cursor_deadzone; + if (mag > deadzone) { + float p = Player_flight_cursor.p * ((mag - deadzone) / mag); + float h = Player_flight_cursor.h * ((mag - deadzone) / mag); - Player->ci.pitch = p / (max_aim_angle - deadzone); - Player->ci.heading = h / (max_aim_angle - deadzone); - } - else { + Player->ci.pitch = p / (max_aim_angle - deadzone); + Player->ci.heading = h / (max_aim_angle - deadzone); + } + else { + Player->ci.pitch = 0.0f; + Player->ci.heading = 0.0f; + } + } else { + Player_flight_cursor = vmd_zero_angles; Player->ci.pitch = 0.0f; Player->ci.heading = 0.0f; } diff --git a/code/ship/ship.cpp b/code/ship/ship.cpp index 126894dab5d..bb2bec55924 100644 --- a/code/ship/ship.cpp +++ b/code/ship/ship.cpp @@ -12604,7 +12604,7 @@ int ship_fire_primary(object * obj, int force, bool rollback_shot) if ( pm->n_guns > 0 ) { vec3d predicted_target_pos, plr_to_target_vec; matrix firing_orient = obj->orient; - if (obj == Player_obj && Player_flight_mode == FlightMode::FlightCursor && sip->aims_at_flight_cursor) { + if (obj == Player_obj && (Player_flight_mode == FlightMode::FlightCursor || sip->aims_at_flight_cursor)) { vm_angles_2_matrix(&firing_orient, &Player_flight_cursor); firing_orient = firing_orient * obj->orient; } From 3b310346887809f72b28cb1b3a7ed7e679161251 Mon Sep 17 00:00:00 2001 From: Kestrellius <63537900+Kestrellius@users.noreply.github.com> Date: Wed, 15 May 2024 11:33:41 -0700 Subject: [PATCH 13/63] Removes const keyword from a couple things --- code/particle/ParticleSourceWrapper.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/particle/ParticleSourceWrapper.h b/code/particle/ParticleSourceWrapper.h index 5945df08960..fe0c911cc31 100644 --- a/code/particle/ParticleSourceWrapper.h +++ b/code/particle/ParticleSourceWrapper.h @@ -59,9 +59,9 @@ namespace particle void setOrientationFromNormalizedVec(const vec3d* normalizedDir, bool relative = false); - void setOrientationFromVec(const vec3d* dir, const bool relative = false); + void setOrientationFromVec(const vec3d* dir, bool relative = false); - void setOrientationMatrix(const matrix* mtx, const bool relative = false); + void setOrientationMatrix(const matrix* mtx, bool relative = false); void setOrientationNormal(const vec3d* normal); From f1c521413cb179c167497494c4c12b3951ab78c9 Mon Sep 17 00:00:00 2001 From: Kestrellius <63537900+Kestrellius@users.noreply.github.com> Date: Wed, 15 May 2024 12:34:10 -0700 Subject: [PATCH 14/63] Makes headon transition rate consistent. --- code/weapon/weapons.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/code/weapon/weapons.cpp b/code/weapon/weapons.cpp index 65dde15f456..a5ec86cea34 100644 --- a/code/weapon/weapons.cpp +++ b/code/weapon/weapons.cpp @@ -8740,6 +8740,16 @@ float weapon_render_headon_bitmap(object* wep_objp, vec3d* headp, vec3d* tailp, return side_alpha; } +// renders another glow bitmap on top of the regular bitmap based on the angle of the camera to the front of the laser +// uses the alpha data already gathered by weapon_render_headon_bitmap, because the alpha of the glow should always match the alpha of the laser +void weapon_render_headon_glow_bitmap(float side_alpha, vec3d* headp, vec3d* tailp, int bitmap, float width1, float width2, int r, int g, int b){ + float head_alpha = 1.0 - side_alpha; + + r = (int)(r * head_alpha); g = (int)(g * head_alpha); b = (int)(b * head_alpha); + + batching_add_laser(bitmap, headp, width1, tailp, width2, r, g, b); +} + void weapon_render(object* obj, model_draw_list *scene) { int num; @@ -8812,6 +8822,8 @@ void weapon_render(object* obj, model_draw_list *scene) vm_vec_scale_add(&tailp, &obj->pos, &rotated_offset, laser_length); vm_vec_scale_add(&headp, &tailp, &obj->orient.vec.fvec, laser_length); + float main_bitmap_alpha_mult = 1.0; + if (wip->laser_bitmap.first_frame >= 0) { gr_set_color_fast(&wip->laser_color_1); @@ -8848,7 +8860,7 @@ void weapon_render(object* obj, model_draw_list *scene) // render the head-on bitmap if appropriate and maybe adjust the main bitmap's alpha if (wip->laser_headon_bitmap.first_frame >= 0) { - float main_bitmap_alpha_mult = weapon_render_headon_bitmap(obj, &headp, &tailp, + main_bitmap_alpha_mult = weapon_render_headon_bitmap(obj, &headp, &tailp, wip->laser_headon_bitmap.first_frame + headon_framenum, scaled_head_radius, scaled_tail_radius, @@ -8943,7 +8955,7 @@ void weapon_render(object* obj, model_draw_list *scene) // render the head-on bitmap if appropriate and maybe adjust the main bitmap's alpha if (wip->laser_glow_headon_bitmap.first_frame >= 0) { - float main_bitmap_alpha_mult = weapon_render_headon_bitmap(obj, &headp2, &tailp2, + weapon_render_headon_glow_bitmap(main_bitmap_alpha_mult, &headp2, &tailp2, wip->laser_glow_headon_bitmap.first_frame + headon_framenum, scaled_head_radius * wip->laser_glow_head_scale, scaled_tail_radius * wip->laser_glow_tail_scale, From 3f5a154e7a47bb65c7f23458223b8c784c28ded1 Mon Sep 17 00:00:00 2001 From: Kestrellius <63537900+Kestrellius@users.noreply.github.com> Date: Fri, 17 May 2024 19:58:57 -0700 Subject: [PATCH 15/63] Updates beam-source particle velocity each frame. --- code/particle/ParticleSource.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/code/particle/ParticleSource.cpp b/code/particle/ParticleSource.cpp index 177db12a607..e2a3768114a 100644 --- a/code/particle/ParticleSource.cpp +++ b/code/particle/ParticleSource.cpp @@ -128,6 +128,13 @@ vec3d SourceOrigin::getVelocity() const { return m_origin.m_object.objp->phys_info.vel; case SourceOriginType::PARTICLE: return m_origin.m_particle.lock()->velocity; + case SourceOriginType::BEAM: { + beam* bm = &Beams[m_origin.m_object.objp->instance]; + vec3d vel; + vm_vec_normalized_dir(&vel, &bm->last_shot, &bm->last_start); + vm_vec_scale(&vel, Weapon_info[bm->weapon_info_index].max_speed); + return vel; + } default: return m_velocity; } From e3bdc106f397a2d59d794776192d399b95dafd32 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Tue, 23 Apr 2024 01:03:23 -0400 Subject: [PATCH 16/63] fix ballistic primary banks exploding When game sounds were changed to allow named entries, it became possible for named entries to unexpectedly replace numbered entries. This was noticed most obviously in the ballistic rearm sound effect, which started using explosions rather than the standard rearm sound effects. This occurred because MV_Tiny and MV_Small were added into indexes 200 and 201. To restore the original behavior, the code now prevents sound effects added via TBM from being placed into reserved indexes. The reserved indexes are instead assigned placeholders, which can subsequently be updated or left alone as the modder prefers. This also makes the code a bit more robust including patching some sound validity checks in `gamesnd_game_sound_try_load` and `snd_load`. --- code/gamesnd/gamesnd.cpp | 98 +++++++++++++++++++++++++++++++++++++--- code/gamesnd/gamesnd.h | 11 ++++- code/sound/sound.cpp | 7 ++- code/sound/sound.h | 3 +- 4 files changed, 107 insertions(+), 12 deletions(-) diff --git a/code/gamesnd/gamesnd.cpp b/code/gamesnd/gamesnd.cpp index 2aad6b165f2..28a68addc1a 100644 --- a/code/gamesnd/gamesnd.cpp +++ b/code/gamesnd/gamesnd.cpp @@ -222,7 +222,6 @@ static EnhancedSoundData Default_sound_priorities[NUM_RETAIL_GAMEPLAY_SOUNDS] = static const EnhancedSoundData default_enhanced_sound_data(SND_ENHANCED_PRIORITY_MEDIUM_HIGH, 1); - /* * Update any uninitialized EnhancedSoundData in Snds * with hardcoded defaults for retail. @@ -545,7 +544,7 @@ void gamesnd_preload_common_sounds() Assert( Snds.size() <= INT_MAX ); for (auto& gs: Snds) { - if ( gs.preload ) { + if ( gs.flags & GAME_SND_PRELOAD ) { for (auto& entry : gs.sound_entries) { if ( entry.filename[0] != 0 && strnicmp(entry.filename, NOX("none.wav"), 4) != 0 ) { game_busy( NOX("** preloading common game sounds **") ); // Animate loading cursor... does nothing if loading screen not active. @@ -566,7 +565,7 @@ void gamesnd_load_gameplay_sounds() Assert( Snds.size() <= INT_MAX ); for (auto& gs: Snds) { - if ( !gs.preload ) { // don't try to load anything that's already preloaded + if ( !(gs.flags & GAME_SND_PRELOAD) ) { // don't try to load anything that's already preloaded for (auto& entry : gs.sound_entries) { if (entry.filename[0] != 0 && strnicmp(entry.filename, NOX("none.wav"), 4) != 0) { game_busy(NOX("** preloading gameplay sounds **")); // Animate loading cursor... does nothing if loading screen not active. @@ -641,6 +640,7 @@ void parse_gamesnd_old(game_snd* gs) gs->pitch_range = util::UniformFloatRange(1.0f); stuff_string(entry.filename, F_NAME, MAX_FILENAME_LEN, ","); + gs->flags |= GAME_SND_RETAIL_STYLE; // since we have a new filename, first assume it's valid gs->flags &= ~GAME_SND_NOT_VALID; @@ -658,7 +658,7 @@ void parse_gamesnd_old(game_snd* gs) if (temp > 0) { - gs->preload = true; + gs->flags |= GAME_SND_PRELOAD; } float default_volume; @@ -783,7 +783,13 @@ void parse_gamesnd_soundset(game_snd* gs, bool no_create) { if (required_string_no_create("+Preload:", no_create)) { - stuff_boolean(&gs->preload); + bool temp; + stuff_boolean(&temp); + + if (temp) + gs->flags |= GAME_SND_PRELOAD; + else + gs->flags &= ~GAME_SND_PRELOAD; } if (required_string_no_create("+Volume:", no_create)) @@ -888,7 +894,13 @@ void parse_gamesnd_new(game_snd* gs, bool no_create) if (required_string_no_create("+Preload:", no_create)) { - stuff_boolean(&gs->preload); + bool temp; + stuff_boolean(&temp); + + if (temp) + gs->flags |= GAME_SND_PRELOAD; + else + gs->flags &= ~GAME_SND_PRELOAD; } if (required_string_no_create("+Volume:", no_create)) @@ -1189,6 +1201,26 @@ void parse_sound_environments() required_string("#Sound Environments End"); } +bool gamesnd_is_reserved_game_index(int index) +{ + if (index >= 0 && index <= 161) + return true; + if (index >= 173 && index <= 191) + return true; + if (index == 200 || index == 201) + return true; + + return false; +} + +bool gamesnd_is_reserved_interface_index(int index) +{ + if (index >= 0 && index <= 64) + return true; + + return false; +} + // Due to the cyclic depdendency between sounds and species, the parsing is now broken up into two stages. // First, just the sounds are parsed; and second, just the flyby sounds are parsed (or assigned). static bool Sound_table_first_stage = false; @@ -1212,6 +1244,26 @@ void parse_sound_table(const char* filename) game_snd tempSound; if (gamesnd_parse_line(&tempSound, "$Name:", &Snds)) { + int tempIndex = static_cast(Snds.size()); + + if (tempSound.flags & GAME_SND_RETAIL_STYLE) + { + // retail sounds must have names that match their indexes + if ((atoi(tempSound.name.c_str()) != tempIndex) && !tempSound.sound_entries.empty() && (tempSound.sound_entries[0].filename[0] != '\0')) + Warning(LOCATION, "Retail-style sound %s has a name that does not match its index %d!", tempSound.name.c_str(), tempIndex); + } + else + { + // prevent new sounds from colliding with reserved indexes + while (gamesnd_is_reserved_game_index(tempIndex)) + { + Snds.emplace_back(); + sprintf(Snds.back().name, "%d", tempIndex); + Snds.back().sound_entries.emplace_back(); + tempIndex = static_cast(Snds.size()); + } + } + Snds.push_back(game_snd(tempSound)); } } @@ -1232,6 +1284,27 @@ void parse_sound_table(const char* filename) game_snd tempSound; if (gamesnd_parse_line(&tempSound, "$Name:", &Snds_iface)) { + int tempIndex = static_cast(Snds_iface.size()); + + if (tempSound.flags & GAME_SND_RETAIL_STYLE) + { + // retail sounds must have names that match their indexes + if ((atoi(tempSound.name.c_str()) != tempIndex) && !tempSound.sound_entries.empty() && (tempSound.sound_entries[0].filename[0] != '\0')) + Warning(LOCATION, "Retail-style sound %s has a name that does not match its index %d!", tempSound.name.c_str(), tempIndex); + } + else + { + // prevent new sounds from colliding with reserved indexes + while (gamesnd_is_reserved_interface_index(tempIndex)) + { + Snds_iface.emplace_back(); + sprintf(Snds_iface.back().name, "%d", tempIndex); + Snds_iface.back().sound_entries.emplace_back(); + Snds_iface_handle.push_back(sound_handle::invalid()); + tempIndex = static_cast(Snds_iface.size()); + } + } + Snds_iface.push_back(game_snd(tempSound)); Snds_iface_handle.push_back(sound_handle::invalid()); } @@ -1377,6 +1450,17 @@ bool gamesnd_game_sound_try_load(gamesnd_id sound) } auto gs = gamesnd_get_game_sound(sound); + // check flag the first time + if (gs->flags & GAME_SND_NOT_VALID) { + return false; + } + + if (gs->sound_entries.empty()) { + Warning(LOCATION, "No sound entries found in game sound %s!", gs->name.c_str()); + gs->flags |= GAME_SND_NOT_VALID; + return false; + } + for (auto& entry : gs->sound_entries) { if (!entry.id.isValid()) { // Lazily load unloaded sound entries when required @@ -1384,7 +1468,7 @@ bool gamesnd_game_sound_try_load(gamesnd_id sound) } } - // check flag + // check flag again return (gs->flags & GAME_SND_NOT_VALID) == 0; } diff --git a/code/gamesnd/gamesnd.h b/code/gamesnd/gamesnd.h index b46e0e56c5c..e64f0827b1e 100644 --- a/code/gamesnd/gamesnd.h +++ b/code/gamesnd/gamesnd.h @@ -237,6 +237,8 @@ enum class GameSounds { BALLISTIC_START_LOAD = 200,//!< (SCP) BALLISTIC_LOAD = 201,//!< (SCP) + // NOTE: If any other indexes become reserved, be sure to update gamesnd_is_reserved_game_index() + /** * Keep this below all defined enum values */ @@ -310,8 +312,10 @@ enum class InterfaceSounds { INTERFACE_UNDEFINED_60 =60,//!< vasudan hall - vasudan pa 3 VASUDAN_BUP =61,//!< bup bup bup-bup bup bup INTERFACE_UNDEFINED_62 =62,//!< thankyou - INTERFACE_UNDEFINED_63 =62,//!< vasudan hall - exit open - INTERFACE_UNDEFINED_64 =62,//!< vasudan hall - exit close + INTERFACE_UNDEFINED_63 =63,//!< vasudan hall - exit open + INTERFACE_UNDEFINED_64 =64,//!< vasudan hall - exit close + + // NOTE: If any other indexes become reserved, be sure to update gamesnd_is_reserved_interface_index() /** * Keep this below all defined enum values @@ -352,6 +356,9 @@ class interface_snd_id : public util::ID { } }; +bool gamesnd_is_reserved_game_index(int index); +bool gamesnd_is_reserved_interface_index(int index); + void gamesnd_parse_soundstbl(bool first_stage); // Loads in general game sounds from sounds.tbl void gamesnd_close(); // close out gamesnd... only call from game_shutdown()! void gamesnd_load_gameplay_sounds(); diff --git a/code/sound/sound.cpp b/code/sound/sound.cpp index 1547abb3411..348f587775b 100644 --- a/code/sound/sound.cpp +++ b/code/sound/sound.cpp @@ -322,11 +322,14 @@ sound_load_id snd_load(game_snd_entry* entry, int *flags, int /*allow_hardware_l if (!ds_initialized) return sound_load_id::invalid(); - if (!VALID_FNAME(entry->filename)) + if (flags && *flags & GAME_SND_NOT_VALID) return sound_load_id::invalid(); - if (flags && *flags & GAME_SND_NOT_VALID) + if (!VALID_FNAME(entry->filename)) { + if (flags) + *flags |= GAME_SND_NOT_VALID; return sound_load_id::invalid(); + } for (n = 0; n < Sounds.size(); n++) { if ( !(Sounds[n].flags & SND_F_USED) ) { diff --git a/code/sound/sound.h b/code/sound/sound.h index 6a86ad9eb7e..41414c7b53b 100644 --- a/code/sound/sound.h +++ b/code/sound/sound.h @@ -24,6 +24,8 @@ #define GAME_SND_USE_DS3D (1<<0) #define GAME_SND_VOICE (1<<1) #define GAME_SND_NOT_VALID (1<<2) +#define GAME_SND_PRELOAD (1<<3) //!< preload sound (ie read from disk before mission starts) +#define GAME_SND_RETAIL_STYLE (1<<4) // Priorities that can be passed to snd_play() functions to limit how many concurrent sounds of a // given type are played. @@ -96,7 +98,6 @@ struct game_snd util::UniformFloatRange pitch_range; //!< The range of possible pitch values used randomly for this sound util::UniformFloatRange volume_range; //!< The possible range of the default volume (range is (0, 1]). - bool preload = false; //!< preload sound (ie read from disk before mission starts) EnhancedSoundData enhanced_sound_data; game_snd( ); From 27caca5d2f3dec4a84dcb36d0469872acaf71f56 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Tue, 23 Apr 2024 01:43:08 -0400 Subject: [PATCH 17/63] handle modification of placeholder sounds --- code/gamesnd/gamesnd.cpp | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/code/gamesnd/gamesnd.cpp b/code/gamesnd/gamesnd.cpp index 28a68addc1a..fc9070085a2 100644 --- a/code/gamesnd/gamesnd.cpp +++ b/code/gamesnd/gamesnd.cpp @@ -956,17 +956,32 @@ void parse_gamesnd_new(game_snd* gs, bool no_create) } } -void gamesnd_parse_entry(game_snd *gs, bool no_create, SCP_vector *lookupVector) +void gamesnd_parse_entry(game_snd *gs, bool &orig_no_create, SCP_vector *lookupVector) { SCP_string name; - stuff_string(name, F_NAME, "\t \n"); + int vectorIndex; + if (lookupVector) + vectorIndex = gamesnd_lookup_name(name.c_str(), *lookupVector); + else + vectorIndex = -1; + + bool no_create = orig_no_create; + if (!no_create) { - if (lookupVector != NULL) + if (vectorIndex >= 0) { - if (gamesnd_lookup_name(name.c_str(), *lookupVector) >= 0) + auto existing_gs = &lookupVector->at(vectorIndex); + + // if the existing sound was an empty or placeholder sound, replace it, don't warn + if (existing_gs->sound_entries.empty() || existing_gs->sound_entries[0].filename[0] == '\0') + { + gs = existing_gs; + no_create = orig_no_create = true; + } + else { Warning(LOCATION, "Duplicate sound name \"%s\" found!", name.c_str()); } @@ -976,8 +991,6 @@ void gamesnd_parse_entry(game_snd *gs, bool no_create, SCP_vector *loo } else { - int vectorIndex = gamesnd_lookup_name(name.c_str(), *lookupVector); - if (vectorIndex < 0) { Warning(LOCATION, "No existing sound entry with name \"%s\" found!", name.c_str()); @@ -993,7 +1006,9 @@ void gamesnd_parse_entry(game_snd *gs, bool no_create, SCP_vector *loo if (optional_string("+Filename:")) { parse_gamesnd_new(gs, no_create); - } else if (optional_string("+Entry:")) { + } + else if (optional_string("+Entry:")) + { parse_gamesnd_soundset(gs, no_create); } else From 0f67b85ac00eac3ad3ec32406603c1dd57c1229d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 May 2024 05:43:09 +0000 Subject: [PATCH 18/63] --- updated-dependencies: - dependency-name: requests dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- ci/post/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/post/requirements.txt b/ci/post/requirements.txt index 0497f9f2deb..15fb6f34ffb 100644 --- a/ci/post/requirements.txt +++ b/ci/post/requirements.txt @@ -1,3 +1,3 @@ Mako==1.2.2 -requests==2.31.0 +requests==2.32.0 semantic-version==2.8.5 From 87704fc81794300e5b5df6ae91f21cbe6725716d Mon Sep 17 00:00:00 2001 From: BMagnu <6238428+BMagnu@users.noreply.github.com> Date: Wed, 22 May 2024 00:18:32 +0900 Subject: [PATCH 19/63] Refactor replacement texture to a modelinstance parameter (#6154) * Move replacement textures from ship to modelinstance * Actually set pmi * Fix texture replace in fred as well * Fix bad indent * incorporate feedback --- code/asteroid/asteroid.cpp | 2 + code/debris/debris.cpp | 3 + code/hud/hudshield.cpp | 2 +- code/hud/hudtargetbox.cpp | 16 +- code/model/model.h | 43 +++-- code/model/modelrender.cpp | 70 ++++---- code/model/modelrender.h | 10 +- code/scripting/api/objs/cockpit_display.cpp | 2 +- code/scripting/api/objs/modelinstance.cpp | 121 ++++++++++++++ code/scripting/api/objs/modelinstance.h | 1 + code/scripting/api/objs/ship.cpp | 123 ++------------ code/scripting/api/objs/ship.h | 2 - code/ship/ship.cpp | 173 ++++++++------------ code/ship/ship.h | 5 +- code/ship/shipfx.cpp | 4 +- code/starfield/starfield.cpp | 3 + code/weapon/weapons.cpp | 3 + fred2/fredrender.cpp | 2 +- qtfred/src/mission/FredRenderer.cpp | 2 +- 19 files changed, 293 insertions(+), 294 deletions(-) diff --git a/code/asteroid/asteroid.cpp b/code/asteroid/asteroid.cpp index 2b3e56d2637..f3378e4ae1e 100644 --- a/code/asteroid/asteroid.cpp +++ b/code/asteroid/asteroid.cpp @@ -1523,6 +1523,8 @@ void asteroid_render(object * obj, model_draw_list *scene) render_info.set_object_number( OBJ_INDEX(obj) ); render_info.set_flags(MR_IS_ASTEROID); + if (asp->model_instance_num >= 0) + render_info.set_replacement_textures(model_get_instance(asp->model_instance_num)->texture_replace); model_render_queue(&render_info, scene, Asteroid_info[asp->asteroid_type].model_num[asp->asteroid_subtype], &obj->orient, &obj->pos); // Replace MR_NORMAL with 0x07 for big yellow blobs } diff --git a/code/debris/debris.cpp b/code/debris/debris.cpp index 8405d18f5ff..64320568b9c 100644 --- a/code/debris/debris.cpp +++ b/code/debris/debris.cpp @@ -1197,6 +1197,9 @@ void debris_render(object * obj, model_draw_list *scene) // render_info.set_flags(MR_NO_LIGHTING); } + if (db->model_instance_num >= 0) + render_info.set_replacement_textures(model_get_instance(db->model_instance_num)->texture_replace); + submodel_render_queue( &render_info, scene, pm, pmi, db->submodel_num, &obj->orient, &obj->pos ); if (tbase != NULL && (swapped!=-1) && pm) { diff --git a/code/hud/hudshield.cpp b/code/hud/hudshield.cpp index 7f0904506c4..f262056505c 100644 --- a/code/hud/hudshield.cpp +++ b/code/hud/hudshield.cpp @@ -641,7 +641,7 @@ void HudGaugeShield::showShields(object *objp, int mode) model_render_params render_info; render_info.set_flags(MR_NO_LIGHTING | MR_AUTOCENTER | MR_NO_FOGGING); - render_info.set_replacement_textures(sp->ship_replacement_textures); + render_info.set_replacement_textures(model_get_instance(sp->model_instance_num)->texture_replace); render_info.set_detail_level_lock(1); render_info.set_object_number(OBJ_INDEX(objp)); diff --git a/code/hud/hudtargetbox.cpp b/code/hud/hudtargetbox.cpp index 175a7931462..83889ffb30a 100644 --- a/code/hud/hudtargetbox.cpp +++ b/code/hud/hudtargetbox.cpp @@ -677,7 +677,7 @@ void HudGaugeTargetBox::renderTargetShip(object *target_objp) if(target_sip->model_num_hud >= 0){ model_render_immediate( &render_info, target_sip->model_num_hud, &target_objp->orient, &obj_pos); } else { - render_info.set_replacement_textures(target_shipp->ship_replacement_textures); + render_info.set_replacement_textures(model_get_instance(target_shipp->model_instance_num)->texture_replace); model_render_immediate( &render_info, target_sip->model_num, &target_objp->orient, &obj_pos); } @@ -777,6 +777,9 @@ void HudGaugeTargetBox::renderTargetDebris(object *target_objp) model_render_params render_info; + if (debrisp->model_instance_num >= 0) + render_info.set_replacement_textures(model_get_instance(debrisp->model_instance_num)->texture_replace); + color thisColor = GaugeWirecolor; bool thisOverride = GaugeWirecolorOverride; @@ -863,7 +866,7 @@ void HudGaugeTargetBox::renderTargetWeapon(object *target_objp) weapon_info *target_wip = NULL; weapon *wp = NULL; object *viewer_obj, *viewed_obj; - int *replacement_textures = NULL; + std::shared_ptr replacement_textures = nullptr; int target_team, is_homing, is_player_missile, missile_view, viewed_model_num, hud_target_lod, w, h; int flags=0; @@ -902,10 +905,13 @@ void HudGaugeTargetBox::renderTargetWeapon(object *target_objp) viewed_obj = wp->homing_object; missile_view = TRUE; viewed_model_num = homing_sip->model_num; - replacement_textures = homing_shipp->ship_replacement_textures; hud_target_lod = homing_sip->hud_target_lod; } + int pmi_id = object_get_model_instance(viewed_obj); + if (pmi_id >= 0) + replacement_textures = model_get_instance(pmi_id)->texture_replace; + // take the forward orientation to be the vector from the player to the current target vm_vec_sub(&orient_vec, &viewed_obj->pos, &viewer_obj->pos); vm_vec_normalize(&orient_vec); @@ -1068,7 +1074,6 @@ void HudGaugeTargetBox::renderTargetWeapon(object *target_objp) model_render_immediate( &render_info, homing_sip->model_num_hud, &viewed_obj->orient, &obj_pos); } else { render_info.set_flags(flags | MR_NO_FOGGING); - render_info.set_replacement_textures(homing_shipp->ship_replacement_textures); model_render_immediate( &render_info, homing_sip->model_num, &viewed_obj->orient, &obj_pos ); } @@ -1170,6 +1175,9 @@ void HudGaugeTargetBox::renderTargetAsteroid(object *target_objp) model_render_params render_info; + if (asteroidp->model_instance_num >= 0) + render_info.set_replacement_textures(model_get_instance(asteroidp->model_instance_num)->texture_replace); + color thisColor = GaugeWirecolor; bool thisOverride = GaugeWirecolorOverride; diff --git a/code/model/model.h b/code/model/model.h index 2fdfb9a44e9..7bda9ddee77 100644 --- a/code/model/model.h +++ b/code/model/model.h @@ -140,6 +140,31 @@ struct submodel_instance } }; +#define TM_BASE_TYPE 0 // the standard base map +#define TM_GLOW_TYPE 1 // optional glow map +#define TM_SPECULAR_TYPE 2 // optional specular map +#define TM_NORMAL_TYPE 3 // optional normal map +#define TM_HEIGHT_TYPE 4 // optional height map (for parallax mapping) +#define TM_MISC_TYPE 5 // optional utility map +#define TM_SPEC_GLOSS_TYPE 6 // optional reflectance map (specular and gloss) +#define TM_AMBIENT_TYPE 7 // optional ambient occlusion map with ambient occlusion and cavity occlusion factors for red and green channels. +#define TM_NUM_TYPES 8 //WMC - Number of texture_info objects in texture_map + //Used by scripting - if you change this, do a search + //to update switch() statement in lua.cpp + +#define MAX_REPLACEMENT_TEXTURES MAX_MODEL_TEXTURES * TM_NUM_TYPES + +// Goober5000 - since we need something < 0 +#define REPLACE_WITH_INVISIBLE -47 + +class model_texture_replace : public std::array { +public: + model_texture_replace() : std::array() { + for (int& tex : *this) + tex = -1; + } +}; + // Data specific to a particular instance of a model. struct polymodel_instance { @@ -147,6 +172,8 @@ struct polymodel_instance int model_num = -1; // global model num index, same as polymodel->id submodel_instance *submodel = nullptr; // array of submodel instances; mirrors the polymodel->submodel array + std::shared_ptr texture_replace = nullptr; + int objnum; // id of the object using this pmi, or -1 if no object (e.g. skybox) }; @@ -730,17 +757,6 @@ class texture_info int SetTexture(int n_tex); }; -#define TM_BASE_TYPE 0 // the standard base map -#define TM_GLOW_TYPE 1 // optional glow map -#define TM_SPECULAR_TYPE 2 // optional specular map -#define TM_NORMAL_TYPE 3 // optional normal map -#define TM_HEIGHT_TYPE 4 // optional height map (for parallax mapping) -#define TM_MISC_TYPE 5 // optional utility map -#define TM_SPEC_GLOSS_TYPE 6 // optional reflectance map (specular and gloss) -#define TM_AMBIENT_TYPE 7 // optional ambient occlusion map with ambient occlusion and cavity occlusion factors for red and green channels. -#define TM_NUM_TYPES 8 //WMC - Number of texture_info objects in texture_map - //Used by scripting - if you change this, do a search - //to update switch() statement in lua.cpp // taylor //WMC - OOPified class texture_map @@ -765,11 +781,6 @@ class texture_map {} }; -#define MAX_REPLACEMENT_TEXTURES MAX_MODEL_TEXTURES * TM_NUM_TYPES - -// Goober5000 - since we need something < 0 -#define REPLACE_WITH_INVISIBLE -47 - //used to describe a polygon model // NOTE: Because WMC OOPified the textures, this must now be treated as a class, rather than a struct. // Additionally, a lot of model initialization and de-initialization is currently done in model_load or model_unload. diff --git a/code/model/modelrender.cpp b/code/model/modelrender.cpp index 81ba803b380..2439c2f8020 100644 --- a/code/model/modelrender.cpp +++ b/code/model/modelrender.cpp @@ -64,8 +64,7 @@ model_render_params::model_render_params() : Xparent_alpha(1.0f), Forced_bitmap(-1), Insignia_bitmap(-1), - Replacement_textures(NULL), - Manage_replacement_textures(false), + Replacement_textures(nullptr), Team_color_set(false), Clip_plane_set(false), Animated_effect(-1), @@ -91,12 +90,6 @@ model_render_params::model_render_params() : gr_init_color(&Color, 0, 0, 0); } -model_render_params::~model_render_params() -{ - if (Manage_replacement_textures) - vm_free(const_cast(Replacement_textures)); -} - uint model_render_params::get_model_flags() const { return Model_flags; @@ -157,7 +150,7 @@ int model_render_params::get_insignia_bitmap() const return Insignia_bitmap; } -const int* model_render_params::get_replacement_textures() const +std::shared_ptr model_render_params::get_replacement_textures() const { return Replacement_textures; } @@ -223,19 +216,14 @@ bool model_render_params::is_team_color_set() const return Team_color_set; } -void model_render_params::set_replacement_textures(const int *textures) +void model_render_params::set_replacement_textures(std::shared_ptr textures) { - Replacement_textures = textures; + Replacement_textures = std::move(textures); } void model_render_params::set_replacement_textures(int modelnum, const SCP_vector& replacement_textures) { - auto textures = (int*)vm_malloc(MAX_REPLACEMENT_TEXTURES * sizeof(int)); - - for (int i = 0; i < MAX_REPLACEMENT_TEXTURES; i++) - textures[i] = -1; - - Manage_replacement_textures = true; + auto textures = make_shared(); polymodel* pm = model_get(modelnum); @@ -247,11 +235,11 @@ void model_render_params::set_replacement_textures(int modelnum, const SCP_vecto int tnum = tmap->FindTexture(tr.old_texture); if (tnum > -1) - textures[i * TM_NUM_TYPES + tnum] = bm_load(tr.new_texture); + (*textures)[i * TM_NUM_TYPES + tnum] = bm_load(tr.new_texture); } } - Replacement_textures = textures; + Replacement_textures = std::move(textures); } void model_render_params::set_insignia_bitmap(int bitmap) @@ -1054,7 +1042,7 @@ void model_render_buffers(model_draw_list* scene, model_material *rendering_mate int texture_maps[TM_NUM_TYPES] = { -1 }; size_t buffer_size = buffer->tex_buf.size(); - const int *replacement_textures = interp->get_replacement_textures(); + const auto& replacement_textures = interp->get_replacement_textures(); for ( size_t i = 0; i < buffer_size; i++ ) { int tmap_num = buffer->tex_buf[i].texture; @@ -1081,12 +1069,12 @@ void model_render_buffers(model_draw_list* scene, model_material *rendering_mate } else if ( !no_texturing ) { // pick the texture, animating it if necessary - if ( (replacement_textures != NULL) && (replacement_textures[rt_begin_index + TM_BASE_TYPE] == REPLACE_WITH_INVISIBLE) ) { + if ( (replacement_textures != nullptr) && ((*replacement_textures)[rt_begin_index + TM_BASE_TYPE] == REPLACE_WITH_INVISIBLE) ) { // invisible textures aren't rendered, but we still have to skip assigning the underlying model texture texture_maps[TM_BASE_TYPE] = -1; - } else if ( (replacement_textures != NULL) && (replacement_textures[rt_begin_index + TM_BASE_TYPE] >= 0) ) { + } else if ( (replacement_textures != nullptr) && ((*replacement_textures)[rt_begin_index + TM_BASE_TYPE] >= 0) ) { // an underlying texture is replaced with a real new texture - tex_replace[TM_BASE_TYPE] = texture_info(replacement_textures[rt_begin_index + TM_BASE_TYPE]); + tex_replace[TM_BASE_TYPE] = texture_info((*replacement_textures)[rt_begin_index + TM_BASE_TYPE]); texture_maps[TM_BASE_TYPE] = model_interp_get_texture(&tex_replace[TM_BASE_TYPE], elapsed_time); } else { // we just use the underlying texture @@ -1101,8 +1089,8 @@ void model_render_buffers(model_draw_list* scene, model_material *rendering_mate if ( !(model_flags & MR_NO_GLOWMAPS) ) { auto tglow = &tmap->textures[TM_GLOW_TYPE]; - if ( (replacement_textures != NULL) && (replacement_textures[rt_begin_index + TM_GLOW_TYPE] >= 0) ) { - tex_replace[TM_GLOW_TYPE] = texture_info(replacement_textures[rt_begin_index + TM_GLOW_TYPE]); + if ( (replacement_textures != nullptr) && ((*replacement_textures)[rt_begin_index + TM_GLOW_TYPE] >= 0) ) { + tex_replace[TM_GLOW_TYPE] = texture_info((*replacement_textures)[rt_begin_index + TM_GLOW_TYPE]); texture_maps[TM_GLOW_TYPE] = model_interp_get_texture(&tex_replace[TM_GLOW_TYPE], elapsed_time); } else if (tglow->GetTexture() >= 0) { // shockwaves are special, their current frame has to come out of the shockwave code to get the timing correct @@ -1115,8 +1103,8 @@ void model_render_buffers(model_draw_list* scene, model_material *rendering_mate } if (!(debug_flags & MR_DEBUG_NO_SPEC)) { - if (replacement_textures != NULL && replacement_textures[rt_begin_index + TM_SPECULAR_TYPE] >= 0) { - tex_replace[TM_SPECULAR_TYPE] = texture_info(replacement_textures[rt_begin_index + TM_SPECULAR_TYPE]); + if (replacement_textures != nullptr && (*replacement_textures)[rt_begin_index + TM_SPECULAR_TYPE] >= 0) { + tex_replace[TM_SPECULAR_TYPE] = texture_info((*replacement_textures)[rt_begin_index + TM_SPECULAR_TYPE]); texture_maps[TM_SPECULAR_TYPE] = model_interp_get_texture(&tex_replace[TM_SPECULAR_TYPE], elapsed_time); } else { @@ -1124,8 +1112,8 @@ void model_render_buffers(model_draw_list* scene, model_material *rendering_mate } } - if ( replacement_textures != NULL && replacement_textures[rt_begin_index + TM_SPEC_GLOSS_TYPE] >= 0 ) { - tex_replace[TM_SPEC_GLOSS_TYPE] = texture_info(replacement_textures[rt_begin_index + TM_SPEC_GLOSS_TYPE]); + if ( replacement_textures != nullptr && (*replacement_textures)[rt_begin_index + TM_SPEC_GLOSS_TYPE] >= 0 ) { + tex_replace[TM_SPEC_GLOSS_TYPE] = texture_info((*replacement_textures)[rt_begin_index + TM_SPEC_GLOSS_TYPE]); texture_maps[TM_SPEC_GLOSS_TYPE] = model_interp_get_texture(&tex_replace[TM_SPEC_GLOSS_TYPE], elapsed_time); } else { texture_maps[TM_SPEC_GLOSS_TYPE] = model_interp_get_texture(&tmap->textures[TM_SPEC_GLOSS_TYPE], elapsed_time); @@ -1138,24 +1126,25 @@ void model_render_buffers(model_draw_list* scene, model_material *rendering_mate auto ambient_map = &tmap->textures[TM_AMBIENT_TYPE]; auto misc_map = &tmap->textures[TM_MISC_TYPE]; - if (replacement_textures != NULL) { - if (replacement_textures[rt_begin_index + TM_NORMAL_TYPE] >= 0) { - tex_replace[TM_NORMAL_TYPE] = texture_info(replacement_textures[rt_begin_index + TM_NORMAL_TYPE]); + if (replacement_textures != nullptr) { + const auto& replacement_textures_deref = *replacement_textures; + if (replacement_textures_deref[rt_begin_index + TM_NORMAL_TYPE] >= 0) { + tex_replace[TM_NORMAL_TYPE] = texture_info(replacement_textures_deref[rt_begin_index + TM_NORMAL_TYPE]); norm_map = &tex_replace[TM_NORMAL_TYPE]; } - if (replacement_textures[rt_begin_index + TM_HEIGHT_TYPE] >= 0) { - tex_replace[TM_HEIGHT_TYPE] = texture_info(replacement_textures[rt_begin_index + TM_HEIGHT_TYPE]); + if (replacement_textures_deref[rt_begin_index + TM_HEIGHT_TYPE] >= 0) { + tex_replace[TM_HEIGHT_TYPE] = texture_info(replacement_textures_deref[rt_begin_index + TM_HEIGHT_TYPE]); height_map = &tex_replace[TM_HEIGHT_TYPE]; } - if (replacement_textures[rt_begin_index + TM_AMBIENT_TYPE] >= 0) { - tex_replace[TM_AMBIENT_TYPE] = texture_info(replacement_textures[rt_begin_index + TM_AMBIENT_TYPE]); + if (replacement_textures_deref[rt_begin_index + TM_AMBIENT_TYPE] >= 0) { + tex_replace[TM_AMBIENT_TYPE] = texture_info(replacement_textures_deref[rt_begin_index + TM_AMBIENT_TYPE]); ambient_map = &tex_replace[TM_AMBIENT_TYPE]; } - if (replacement_textures[rt_begin_index + TM_MISC_TYPE] >= 0) { - tex_replace[TM_MISC_TYPE] = texture_info(replacement_textures[rt_begin_index + TM_MISC_TYPE]); + if (replacement_textures_deref[rt_begin_index + TM_MISC_TYPE] >= 0) { + tex_replace[TM_MISC_TYPE] = texture_info(replacement_textures_deref[rt_begin_index + TM_MISC_TYPE]); misc_map = &tex_replace[TM_MISC_TYPE]; } } @@ -1174,8 +1163,8 @@ void model_render_buffers(model_draw_list* scene, model_material *rendering_mate //Check for invisible or transparent textures so they don't show up in the shadow maps - Valathil if ( Rendering_to_shadow_map ) { - if ( (replacement_textures != NULL) && (replacement_textures[rt_begin_index + TM_BASE_TYPE] >= 0) ) { - tex_replace[TM_BASE_TYPE] = texture_info(replacement_textures[rt_begin_index + TM_BASE_TYPE]); + if ( (replacement_textures != nullptr) && ((*replacement_textures)[rt_begin_index + TM_BASE_TYPE] >= 0) ) { + tex_replace[TM_BASE_TYPE] = texture_info((*replacement_textures)[rt_begin_index + TM_BASE_TYPE]); texture_maps[TM_BASE_TYPE] = model_interp_get_texture(&tex_replace[TM_BASE_TYPE], elapsed_time); } else { texture_maps[TM_BASE_TYPE] = model_interp_get_texture(&tmap->textures[TM_BASE_TYPE], elapsed_time); @@ -3113,6 +3102,7 @@ bool render_tech_model(tech_render_type model_type, int x1, int y1, int x2, int // Make sure model is loaded model_num = model_load(sip->pof_file, sip->n_subsystems, &sip->subsystems[0], 0); + render_info.set_replacement_textures(model_num, sip->replacement_textures); break; diff --git a/code/model/modelrender.h b/code/model/modelrender.h index b41ce78f5a0..8cc3040733e 100644 --- a/code/model/modelrender.h +++ b/code/model/modelrender.h @@ -83,10 +83,7 @@ class model_render_params int Insignia_bitmap; - const int *Replacement_textures; - bool Manage_replacement_textures; // This is set when we are rendering a model without an associated ship object; - // in that case, model_render_params is responsible for allocating and destroying - // the Replacement_textures array (this is handled elsewhere otherwise) + std::shared_ptr Replacement_textures; bool Team_color_set; team_color Current_team_color; @@ -109,7 +106,6 @@ class model_render_params model_render_params& operator=(const model_render_params&) = delete; public: model_render_params(); - ~model_render_params(); void set_flags(uint flags); void set_debug_flags(uint flags); @@ -122,7 +118,7 @@ class model_render_params void set_alpha(float alpha); void set_forced_bitmap(int bitmap); void set_insignia_bitmap(int bitmap); - void set_replacement_textures(const int *textures); + void set_replacement_textures(std::shared_ptr textures); void set_replacement_textures(int modelnum, const SCP_vector& replacement_textures); void set_team_color(const team_color &clr); void set_team_color(const SCP_string &team, const SCP_string &secondaryteam, fix timestamp, int fadetime); @@ -149,7 +145,7 @@ class model_render_params float get_alpha() const; int get_forced_bitmap() const; int get_insignia_bitmap() const; - const int* get_replacement_textures() const; + std::shared_ptr get_replacement_textures() const; const team_color& get_team_color() const; const vec3d& get_clip_plane_pos() const; const vec3d& get_clip_plane_normal() const; diff --git a/code/scripting/api/objs/cockpit_display.cpp b/code/scripting/api/objs/cockpit_display.cpp index 37999b4b822..02cd6dfb6a6 100644 --- a/code/scripting/api/objs/cockpit_display.cpp +++ b/code/scripting/api/objs/cockpit_display.cpp @@ -512,7 +512,7 @@ bool cockpit_displays_h::isValid() const { return false; } - if ( Player_cockpit_textures == NULL ) { + if ( Player_cockpit_textures == nullptr ) { return false; } diff --git a/code/scripting/api/objs/modelinstance.cpp b/code/scripting/api/objs/modelinstance.cpp index ba541393bab..a4f5aeb3c51 100644 --- a/code/scripting/api/objs/modelinstance.cpp +++ b/code/scripting/api/objs/modelinstance.cpp @@ -5,10 +5,108 @@ #include "modelinstance.h" #include "object.h" #include "vecmath.h" +#include "texture.h" namespace scripting { namespace api { +//**********HANDLE: modelinstancetextures (compatible with preceding shiptextures) +ADE_OBJ(l_ModelInstanceTextures, modelinstance_h, "modelinstancetextures", "Model instance textures handle"); + +ADE_FUNC(__len, l_ModelInstanceTextures, nullptr, "Number of textures on a model instance", "number", "Number of textures on the model instance, or 0 if handle is invalid") +{ + modelinstance_h *mih; + if(!ade_get_args(L, "o", l_ModelInstanceTextures.GetPtr(&mih))) + return ade_set_error(L, "i", 0); + + if(!mih->isValid()) + return ade_set_error(L, "i", 0); + + polymodel *pm = model_get(mih->Get()->model_num); + + if(pm == nullptr) + return ade_set_error(L, "i", 0); + + return ade_set_args(L, "i", pm->n_textures*TM_NUM_TYPES); +} + +ADE_INDEXER(l_ModelInstanceTextures, "number/string IndexOrTextureFilename", "Array of model instance textures", "texture", "Texture, or invalid texture handle on failure") +{ + modelinstance_h *mih; + const char* s; + texture_h* tdx = nullptr; + if (!ade_get_args(L, "os|o", l_ModelInstanceTextures.GetPtr(&mih), &s, l_Texture.GetPtr(&tdx))) + return ade_set_error(L, "o", l_Texture.Set(texture_h())); + + if (!mih->isValid() || s == nullptr) + return ade_set_error(L, "o", l_Texture.Set(texture_h())); + + polymodel_instance *pmi = mih->Get(); + polymodel *pm = model_get(pmi->model_num); + int final_index = -1; + int i; + + char fname[MAX_FILENAME_LEN]; + if (pmi->texture_replace != nullptr) + { + for(i = 0; i < MAX_REPLACEMENT_TEXTURES; i++) + { + bm_get_filename((*pmi->texture_replace)[i], fname); + + if(!strextcmp(fname, s)) { + final_index = i; + break; + } + } + } + + if(final_index < 0) + { + for (i = 0; i < pm->n_textures; i++) + { + int tm_num = pm->maps[i].FindTexture(s); + if(tm_num > -1) + { + final_index = i*TM_NUM_TYPES+tm_num; + break; + } + } + } + + if (final_index < 0) + { + final_index = atoi(s) - 1; //Lua->FS2 + + if (final_index < 0 || final_index >= MAX_REPLACEMENT_TEXTURES) + return ade_set_error(L, "o", l_Texture.Set(texture_h())); + } + + if (ADE_SETTING_VAR) { + if (pmi->texture_replace == nullptr) { + pmi->texture_replace = make_shared(); + } + + if (tdx != nullptr) { + (*pmi->texture_replace)[final_index] = tdx->isValid() ? tdx->handle : -1; + } + } + + if (pmi->texture_replace != nullptr && (*pmi->texture_replace)[final_index] >= 0) + return ade_set_args(L, "o", l_Texture.Set(texture_h((*pmi->texture_replace)[final_index]))); + else + return ade_set_args(L, "o", l_Texture.Set(texture_h(pm->maps[final_index / TM_NUM_TYPES].textures[final_index % TM_NUM_TYPES].GetTexture()))); +} + +ADE_FUNC(isValid, l_ModelInstanceTextures, nullptr, "Detects whether handle is valid", "boolean", "true if valid, false if handle is invalid, nil if a syntax/type error occurs") +{ + modelinstance_h *mih; + if(!ade_get_args(L, "o", l_ModelInstanceTextures.GetPtr(&mih))) + return ADE_RETURN_NIL; + + return ade_set_args(L, "b", mih->isValid()); +} + + ADE_OBJ(l_ModelInstance, modelinstance_h, "model_instance", "Model instance handle"); modelinstance_h::modelinstance_h(int pmi_id) @@ -94,6 +192,29 @@ ADE_FUNC(getObject, l_ModelInstance, nullptr, "Returns the object that this inst return ade_set_object_with_breed(L, mih->Get()->objnum); } +ADE_VIRTVAR(Textures, l_ModelInstance, "modelinstancetextures", "Gets model instance textures", "modelinstancetextures", "Model instance textures, or invalid modelinstancetextures handle if modelinstance handle is invalid") +{ + modelinstance_h *sh = nullptr; + modelinstance_h *dh; + if(!ade_get_args(L, "o|o", l_ModelInstance.GetPtr(&dh), l_ModelInstance.GetPtr(&sh))) + return ade_set_error(L, "o", l_ModelInstanceTextures.Set(modelinstance_h())); + + if(!dh->isValid()) + return ade_set_error(L, "o", l_ModelInstanceTextures.Set(modelinstance_h())); + + if(ADE_SETTING_VAR && sh && sh->isValid()) { + polymodel_instance *src = sh->Get(); + polymodel_instance *dest = dh->Get(); + + if (src->texture_replace != nullptr) + { + dest->texture_replace = std::make_shared(*src->texture_replace); + } + } + + return ade_set_args(L, "o", l_ModelInstanceTextures.Set(modelinstance_h(dh->Get()))); +} + ADE_VIRTVAR(SubmodelInstances, l_ModelInstance, nullptr, "Submodel instances", "submodel_instances", "Model submodel instances, or an invalid modelsubmodelinstances handle if the model instance handle is invalid") { modelinstance_h *mih = nullptr; diff --git a/code/scripting/api/objs/modelinstance.h b/code/scripting/api/objs/modelinstance.h index eb6a64abb68..a8a2d6a92ca 100644 --- a/code/scripting/api/objs/modelinstance.h +++ b/code/scripting/api/objs/modelinstance.h @@ -21,6 +21,7 @@ class modelinstance_h bool isValid() const; }; +DECLARE_ADE_OBJ(l_ModelInstanceTextures, modelinstance_h); DECLARE_ADE_OBJ(l_ModelInstance, modelinstance_h); class submodelinstance_h diff --git a/code/scripting/api/objs/ship.cpp b/code/scripting/api/objs/ship.cpp index d8a87839e6c..390b039f22d 100644 --- a/code/scripting/api/objs/ship.cpp +++ b/code/scripting/api/objs/ship.cpp @@ -6,6 +6,7 @@ #include "cockpit_display.h" #include "enums.h" #include "message.h" +#include "modelinstance.h" #include "object.h" #include "order.h" #include "parse_object.h" @@ -41,108 +42,6 @@ extern void sexp_alter_ship_flag_helper(object_ship_wing_point_team &oswpt, bool namespace scripting { namespace api { -//**********HANDLE: shiptextures -ADE_OBJ(l_ShipTextures, object_h, "shiptextures", "Ship textures handle"); - -ADE_FUNC(__len, l_ShipTextures, NULL, "Number of textures on ship", "number", "Number of textures on ship, or 0 if handle is invalid") -{ - object_h *objh; - if(!ade_get_args(L, "o", l_ShipTextures.GetPtr(&objh))) - return ade_set_error(L, "i", 0); - - if(!objh->isValid()) - return ade_set_error(L, "i", 0); - - polymodel *pm = model_get(Ship_info[Ships[objh->objp->instance].ship_info_index].model_num); - - if(pm == NULL) - return ade_set_error(L, "i", 0); - - return ade_set_args(L, "i", pm->n_textures*TM_NUM_TYPES); -} - -ADE_INDEXER(l_ShipTextures, "number/string IndexOrTextureFilename", "Array of ship textures", "texture", "Texture, or invalid texture handle on failure") -{ - object_h *oh; - const char* s; - texture_h* tdx = nullptr; - if (!ade_get_args(L, "os|o", l_ShipTextures.GetPtr(&oh), &s, l_Texture.GetPtr(&tdx))) - return ade_set_error(L, "o", l_Texture.Set(texture_h())); - - if (!oh->isValid() || s==NULL) - return ade_set_error(L, "o", l_Texture.Set(texture_h())); - - ship *shipp = &Ships[oh->objp->instance]; - polymodel *pm = model_get(Ship_info[shipp->ship_info_index].model_num); - int final_index = -1; - int i; - - char fname[MAX_FILENAME_LEN]; - if (shipp->ship_replacement_textures != NULL) - { - for(i = 0; i < MAX_REPLACEMENT_TEXTURES; i++) - { - bm_get_filename(shipp->ship_replacement_textures[i], fname); - - if(!strextcmp(fname, s)) { - final_index = i; - break; - } - } - } - - if(final_index < 0) - { - for (i = 0; i < pm->n_textures; i++) - { - int tm_num = pm->maps[i].FindTexture(s); - if(tm_num > -1) - { - final_index = i*TM_NUM_TYPES+tm_num; - break; - } - } - } - - if (final_index < 0) - { - final_index = atoi(s) - 1; //Lua->FS2 - - if (final_index < 0 || final_index >= MAX_REPLACEMENT_TEXTURES) - return ade_set_error(L, "o", l_Texture.Set(texture_h())); - } - - if (ADE_SETTING_VAR) { - if (shipp->ship_replacement_textures == NULL) { - shipp->ship_replacement_textures = (int *) vm_malloc(MAX_REPLACEMENT_TEXTURES * sizeof(int)); - - for (i = 0; i < MAX_REPLACEMENT_TEXTURES; i++) - shipp->ship_replacement_textures[i] = -1; - } - - if (tdx != nullptr) { - if (tdx->isValid()) - shipp->ship_replacement_textures[final_index] = tdx->handle; - else - shipp->ship_replacement_textures[final_index] = -1; - } - } - - if (shipp->ship_replacement_textures != NULL && shipp->ship_replacement_textures[final_index] >= 0) - return ade_set_args(L, "o", l_Texture.Set(texture_h(shipp->ship_replacement_textures[final_index]))); - else - return ade_set_args(L, "o", l_Texture.Set(texture_h(pm->maps[final_index / TM_NUM_TYPES].textures[final_index % TM_NUM_TYPES].GetTexture()))); -} - -ADE_FUNC(isValid, l_ShipTextures, NULL, "Detects whether handle is valid", "boolean", "true if valid, false if handle is invalid, nil if a syntax/type error occurs") -{ - object_h *oh; - if(!ade_get_args(L, "o", l_ShipTextures.GetPtr(&oh))) - return ADE_RETURN_NIL; - - return ade_set_args(L, "b", oh->isValid()); -} - //**********HANDLE: Ship ADE_OBJ_DERIV(l_Ship, object_h, "ship", "Ship handle", l_Object); @@ -919,30 +818,28 @@ ADE_VIRTVAR(PersonaIndex, l_Ship, "number", "Persona index", "number", "The inde return ade_set_args(L, "i", shipp->persona_index + 1); } -ADE_VIRTVAR(Textures, l_Ship, "shiptextures", "Gets ship textures", "shiptextures", "Ship textures, or invalid shiptextures handle if ship handle is invalid") +ADE_VIRTVAR(Textures, l_Ship, "modelinstancetextures", "Gets ship textures", "modelinstancetextures", "Ship textures, or invalid shiptextures handle if ship handle is invalid") { object_h *sh = nullptr; object_h *dh; if(!ade_get_args(L, "o|o", l_Ship.GetPtr(&dh), l_Ship.GetPtr(&sh))) - return ade_set_error(L, "o", l_ShipTextures.Set(object_h())); + return ade_set_error(L, "o", l_ModelInstanceTextures.Set(modelinstance_h())); if(!dh->isValid()) - return ade_set_error(L, "o", l_ShipTextures.Set(object_h())); + return ade_set_error(L, "o", l_ModelInstanceTextures.Set(modelinstance_h())); + + polymodel_instance *dest = model_get_instance(Ships[dh->objp->instance].model_instance_num); if(ADE_SETTING_VAR && sh && sh->isValid()) { - ship *src = &Ships[sh->objp->instance]; - ship *dest = &Ships[dh->objp->instance]; + polymodel_instance *src = model_get_instance(Ships[sh->objp->instance].model_instance_num); - if (src->ship_replacement_textures != NULL) + if (src->texture_replace != nullptr) { - if (dest->ship_replacement_textures == NULL) - dest->ship_replacement_textures = (int *) vm_malloc(MAX_REPLACEMENT_TEXTURES * sizeof(int)); - - memcpy(dest->ship_replacement_textures, src->ship_replacement_textures, MAX_REPLACEMENT_TEXTURES * sizeof(int)); + dest->texture_replace = std::make_shared(*src->texture_replace); } } - return ade_set_args(L, "o", l_ShipTextures.Set(object_h(dh->objp))); + return ade_set_args(L, "o", l_ModelInstanceTextures.Set(modelinstance_h(dest))); } ADE_VIRTVAR(FlagAffectedByGravity, l_Ship, "boolean", "Checks for the \"affected-by-gravity\" flag", "boolean", "True if flag is set, false if flag is not set and nil on error") diff --git a/code/scripting/api/objs/ship.h b/code/scripting/api/objs/ship.h index 195a45bda77..cc723367339 100644 --- a/code/scripting/api/objs/ship.h +++ b/code/scripting/api/objs/ship.h @@ -6,8 +6,6 @@ namespace scripting { namespace api { -DECLARE_ADE_OBJ(l_ShipTextures, object_h); - //**********HANDLE: Ship DECLARE_ADE_OBJ(l_Ship, object_h); } diff --git a/code/ship/ship.cpp b/code/ship/ship.cpp index 8c150a6ad30..2b9d454afaa 100644 --- a/code/ship/ship.cpp +++ b/code/ship/ship.cpp @@ -132,7 +132,7 @@ int Num_reinforcements = 0; ship Ships[MAX_SHIPS]; ship *Player_ship; -int *Player_cockpit_textures; +std::shared_ptr Player_cockpit_textures; SCP_vector Player_displays; bool Disable_cockpits = false; bool Disable_cockpit_sway = false; @@ -6831,11 +6831,6 @@ void ship::clear() primitive_sensor_range = DEFAULT_SHIP_PRIMITIVE_SENSOR_RANGE; - if (ship_replacement_textures != nullptr) { - vm_free(ship_replacement_textures); - } - ship_replacement_textures = nullptr; - current_viewpoint = -1; for (int i = 0; i < MAX_SHIP_CONTRAILS; i++) @@ -6913,13 +6908,12 @@ const char* ship::get_display_name() const { void ship::apply_replacement_textures(const SCP_vector &replacements) { - if (!replacements.empty()) - { - ship_replacement_textures = (int *) vm_malloc( MAX_REPLACEMENT_TEXTURES * sizeof(int)); + if (replacements.empty()) + return; - for (auto i = 0; i < MAX_REPLACEMENT_TEXTURES; i++) - ship_replacement_textures[i] = -1; - } + polymodel_instance* pmi = model_get_instance(model_instance_num); + + pmi->texture_replace = make_shared(); auto pm = model_get(Ship_info[ship_info_index].model_num); @@ -6932,8 +6926,8 @@ void ship::apply_replacement_textures(const SCP_vector &replace texture_map *tmap = &pm->maps[j]; int tnum = tmap->FindTexture(tr.old_texture); - if(tnum > -1) - ship_replacement_textures[j * TM_NUM_TYPES + tnum] = tr.new_texture_id; + if (tnum > -1) + (*pmi->texture_replace)[j * TM_NUM_TYPES + tnum] = tr.new_texture_id; } } } @@ -7883,6 +7877,7 @@ extern bool Rendering_to_shadow_map; void ship_render_player_ship(object* objp, const vec3d* cam_offset, const matrix* rot_offset, const fov_t* fov_override) { ship* shipp = &Ships[objp->instance]; ship_info* sip = &Ship_info[shipp->ship_info_index]; + polymodel_instance* pmi = model_get_instance(shipp->model_instance_num); const bool hasCockpitModel = sip->cockpit_model_num >= 0; @@ -7939,7 +7934,7 @@ void ship_render_player_ship(object* objp, const vec3d* cam_offset, const matrix render_info.set_object_number(OBJ_INDEX(objp)); // update any replacement and/or team color textures (wookieejedi), then render - render_info.set_replacement_textures(shipp->ship_replacement_textures); + render_info.set_replacement_textures(pmi->texture_replace); if (sip->uses_team_colors) render_info.set_team_color(shipp->team_name, shipp->secondary_team_name, 0, 0); @@ -8032,7 +8027,7 @@ void ship_render_player_ship(object* objp, const vec3d* cam_offset, const matrix model_render_params render_info; render_info.set_detail_level_lock(0); render_info.set_flags(render_flags); - render_info.set_replacement_textures(shipp->ship_replacement_textures); + render_info.set_replacement_textures(pmi->texture_replace); render_info.set_object_number(OBJ_INDEX(objp)); if (sip->uses_team_colors) render_info.set_team_color(shipp->team_name, shipp->secondary_team_name, 0, 0); @@ -8103,21 +8098,15 @@ void ship_init_cockpit_displays(ship *shipp) return; } - if ( Player_cockpit_textures != NULL) { + if ( Player_cockpit_textures != nullptr) { return; } // ship's cockpit texture replacements haven't been setup yet, so do it. - Player_cockpit_textures = (int *) vm_malloc(MAX_REPLACEMENT_TEXTURES * sizeof(int)); - - int i; - - for ( i = 0; i < MAX_REPLACEMENT_TEXTURES; i++ ) { - Player_cockpit_textures[i] = -1; - } + Player_cockpit_textures = make_shared(); - for ( i = 0; i < (int)sip->displays.size(); i++ ) { - ship_add_cockpit_display(&sip->displays[i], cockpit_model_num); + for ( auto& display : sip->displays ) { + ship_add_cockpit_display(&display, cockpit_model_num); } ship_set_hud_cockpit_targets(); @@ -8144,11 +8133,7 @@ void ship_close_cockpit_displays(ship* shipp) } Player_displays.clear(); - - if ( Player_cockpit_textures != NULL ) { - vm_free(Player_cockpit_textures); - Player_cockpit_textures = NULL; - } + Player_cockpit_textures.reset(); } static void ship_add_cockpit_display(cockpit_display_info *display, int cockpit_model_num) @@ -8181,13 +8166,13 @@ static void ship_add_cockpit_display(cockpit_display_info *display, int cockpit_ } // create a render target for this cockpit texture - if ( Player_cockpit_textures[glow_target] < 0) { - + auto& glow_texture = (*Player_cockpit_textures)[glow_target]; + if ( glow_texture == -1) { bm_get_info(diffuse_handle, &w, &h); - Player_cockpit_textures[glow_target] = bm_make_render_target(w, h, BMP_FLAG_RENDER_TARGET_DYNAMIC); + glow_texture = bm_make_render_target(w, h, BMP_FLAG_RENDER_TARGET_DYNAMIC); // if no render target was made, bail - if ( Player_cockpit_textures[glow_target] < 0 ) { + if ( glow_texture < 0 ) { return; } } @@ -8216,7 +8201,7 @@ static void ship_add_cockpit_display(cockpit_display_info *display, int cockpit_ new_display.size[0] = display->size[0]; new_display.size[1] = display->size[1]; new_display.source = glow_handle; - new_display.target = Player_cockpit_textures[glow_target]; + new_display.target = glow_texture; Player_displays.push_back(new_display); } @@ -8243,7 +8228,7 @@ int ship_start_render_cockpit_display(size_t cockpit_display_num) return -1; } - if ( Player_cockpit_textures == NULL ) { + if ( Player_cockpit_textures == nullptr ) { return -1; } @@ -8289,7 +8274,7 @@ void ship_end_render_cockpit_display(size_t cockpit_display_num) return; } - if ( Player_cockpit_textures == NULL ) { + if ( Player_cockpit_textures == nullptr ) { return; } @@ -8357,11 +8342,6 @@ void ship_delete( object * obj ) animation::ModelAnimationSet::stopAnimations(model_get_instance(shipp->model_instance_num)); - if (shipp->ship_replacement_textures != NULL) { - vm_free(shipp->ship_replacement_textures); - shipp->ship_replacement_textures = NULL; - } - // glow point banks shipp->glow_point_bank_active.clear(); @@ -11089,15 +11069,14 @@ static void ship_model_change(int n, int ship_type) ship_info *sip; ship *sp; polymodel * pm; + polymodel_instance * pmi; object *objp; Assert( n >= 0 && n < MAX_SHIPS ); sp = &Ships[n]; sip = &(Ship_info[ship_type]); objp = &Objects[sp->objnum]; - - //Stop Animation on the old model - animation::ModelAnimationSet::stopAnimations(model_get_instance(sp->model_instance_num)); + pmi = model_get_instance(sp->model_instance_num); // get new model if (sip->model_num == -1) { @@ -11113,41 +11092,6 @@ static void ship_model_change(int n, int ship_type) pm = model_get(sip->model_num); Objects[sp->objnum].radius = model_get_radius(pm->id); - // Goober5000 - deal with texture replacement by re-applying the same code we used during parsing - // wookieejedi - replacement textures are loaded in mission parse, so need to load any new textures here - if ( !sip->replacement_textures.empty() ) { - - // clear and reset replacement textures because the new positions may be different - if (sp->ship_replacement_textures == nullptr) - sp->ship_replacement_textures = (int*)vm_malloc(MAX_REPLACEMENT_TEXTURES * sizeof(int)); - for (auto k = 0; k < MAX_REPLACEMENT_TEXTURES; k++) - sp->ship_replacement_textures[k] = -1; - - // now fill them in according to texture name - for (const auto& tr : sip->replacement_textures) { - // look for textures - for (auto j = 0; j < pm->n_textures; j++) { - - texture_map* tmap = &pm->maps[j]; - int tnum = tmap->FindTexture(tr.old_texture); - - if (tnum > -1) { - // load new texture - int new_tex = bm_load_either(tr.new_texture); - if (new_tex > -1) { - sp->ship_replacement_textures[j * TM_NUM_TYPES + tnum] = new_tex; - } - } - } - } - } else { - // ensure that any texture replacements are cleared from old ship - if (sp->ship_replacement_textures != nullptr) { - vm_free(sp->ship_replacement_textures); - sp->ship_replacement_textures = nullptr; - } - } - // page in nondims in game if ( !Fred_running ) model_page_in_textures(sip->model_num, ship_type); @@ -11208,6 +11152,41 @@ static void ship_model_change(int n, int ship_type) // reset texture animations sp->base_texture_anim_timestamp = _timestamp(); + + model_delete_instance(sp->model_instance_num); + + // create new model instance data + // note: this is needed for both subsystem stuff and submodel animation stuff + sp->model_instance_num = model_create_instance(OBJ_INDEX(objp), sip->model_num); + pmi = model_get_instance(sp->model_instance_num); + + // Goober5000 - deal with texture replacement by re-applying the same code we used during parsing + // wookieejedi - replacement textures are loaded in mission parse, so need to load any new textures here + // Lafiel - this now has to happen last, as the texture replacement stuff is stored in the pmi + if ( !sip->replacement_textures.empty() ) { + + // clear and reset replacement textures because the new positions may be different + pmi->texture_replace = make_shared(); + auto& texture_replace_deref = *pmi->texture_replace; + + // now fill them in according to texture name + for (const auto& tr : sip->replacement_textures) { + // look for textures + for (auto j = 0; j < pm->n_textures; j++) { + + texture_map* tmap = &pm->maps[j]; + int tnum = tmap->FindTexture(tr.old_texture); + + if (tnum > -1) { + // load new texture + int new_tex = bm_load_either(tr.new_texture); + if (new_tex > -1) { + texture_replace_deref[j * TM_NUM_TYPES + tnum] = new_tex; + } + } + } + } + } } /** @@ -11475,10 +11454,6 @@ void change_ship_type(int n, int ship_type, int by_sexp) ship_model_change(n, ship_type); sp->ship_info_index = ship_type; - // create new model instance data - // note: this is needed for both subsystem stuff and submodel animation stuff - sp->model_instance_num = model_create_instance(objnum, sip->model_num); - // if we have the same warp parameters as the ship class, we will need to update them to point to the new class if (sp->warpin_params_index == sip_orig->warpin_params_index) { sp->warpin_params_index = sip->warpin_params_index; @@ -15734,11 +15709,6 @@ void ship_close() for (i=0; iship_replacement_textures != NULL) { - vm_free(shipp->ship_replacement_textures); - shipp->ship_replacement_textures = NULL; - } - if(shipp->warpin_effect != NULL) delete shipp->warpin_effect; shipp->warpin_effect = NULL; @@ -18314,15 +18284,15 @@ void ship_page_in() // is this a valid ship? if (Ships[i].objnum >= 0) { + polymodel_instance* pmi = model_get_instance(object_get_model_instance(&Objects[Ships[i].objnum])); // do we have any textures? - if (Ships[i].ship_replacement_textures != NULL) + if (pmi->texture_replace != nullptr) { // page in replacement textures - for (j=0; jtexture_replace) { - if (Ships[i].ship_replacement_textures[j] > -1) - { - bm_page_in_texture( Ships[i].ship_replacement_textures[j] ); + if (texture >= 0) { + bm_page_in_texture(texture); } } } @@ -18451,6 +18421,8 @@ void ship_replace_active_texture(int ship_index, const char* old_name, const cha { ship* shipp = &Ships[ship_index]; polymodel* pm = model_get(Ship_info[shipp->ship_info_index].model_num); + polymodel_instance* pmi = model_get_instance(shipp->model_instance_num); + int final_index = -1; for (int i = 0; i < pm->n_textures; i++) @@ -18472,14 +18444,11 @@ void ship_replace_active_texture(int ship_index, const char* old_name, const cha else texture = bm_load_either(new_name); - if (shipp->ship_replacement_textures == nullptr) { - shipp->ship_replacement_textures = (int*)vm_malloc(MAX_REPLACEMENT_TEXTURES * sizeof(int)); - - for (int i = 0; i < MAX_REPLACEMENT_TEXTURES; i++) - shipp->ship_replacement_textures[i] = -1; + if (pmi->texture_replace == nullptr) { + pmi->texture_replace = make_shared(); } - shipp->ship_replacement_textures[final_index] = texture; + (*pmi->texture_replace)[final_index] = texture; } else Warning(LOCATION, "Invalid texture '%s' used for replacement texture", old_name); } @@ -20999,7 +20968,7 @@ void ship_render(object* obj, model_draw_list* scene) ship_render_weapon_models(&render_info, scene, obj, render_flags); render_info.set_object_number(OBJ_INDEX(obj)); - render_info.set_replacement_textures(shipp->ship_replacement_textures); + render_info.set_replacement_textures(pmi->texture_replace); // small ships if ( !( shipp->flags[Ship_Flags::Cloaked] ) ) { diff --git a/code/ship/ship.h b/code/ship/ship.h index 077f6192013..7d5d680c0b7 100644 --- a/code/ship/ship.h +++ b/code/ship/ship.h @@ -761,9 +761,6 @@ class ship // Goober5000 - range of primitive sensors int primitive_sensor_range; - - // Goober5000 - revised nameplate implementation - int *ship_replacement_textures; // Goober5000 - index into pm->view_positions[] // apparently, early in FS1 development, there was a field called current_eye_index @@ -1538,7 +1535,7 @@ extern const size_t Num_subsystem_flags; extern int Num_wings; extern ship Ships[MAX_SHIPS]; extern ship *Player_ship; -extern int *Player_cockpit_textures; +extern std::shared_ptr Player_cockpit_textures; // Data structure to track the active missiles typedef struct ship_obj { diff --git a/code/ship/shipfx.cpp b/code/ship/shipfx.cpp index 345af20266a..f10b495817c 100644 --- a/code/ship/shipfx.cpp +++ b/code/ship/shipfx.cpp @@ -1662,7 +1662,7 @@ void shipfx_queue_render_ship_halves_and_debris(model_draw_list *scene, clip_shi model_render_params render_info; render_info.set_clip_plane(debris_clip_plane_pt, clip_plane_norm); - render_info.set_replacement_textures(shipp->ship_replacement_textures); + render_info.set_replacement_textures(pmi->texture_replace); render_info.set_flags(render_flags); submodel_render_queue(&render_info, scene, pm, pmi, pm->debris_objects[i], &half_ship->orient, &tmp); @@ -1706,7 +1706,7 @@ void shipfx_queue_render_ship_halves_and_debris(model_draw_list *scene, clip_shi render_info.set_flags(render_flags); render_info.set_clip_plane(model_clip_plane_pt, clip_plane_norm); - render_info.set_replacement_textures(shipp->ship_replacement_textures); + render_info.set_replacement_textures(pmi->texture_replace); render_info.set_object_number(shipp->objnum); if (Ship_info[shipp->ship_info_index].uses_team_colors && !shipp->flags[Ship::Ship_Flags::Render_without_miscmap]) { diff --git a/code/starfield/starfield.cpp b/code/starfield/starfield.cpp index bd7f71b777d..4ccd48e99a6 100644 --- a/code/starfield/starfield.cpp +++ b/code/starfield/starfield.cpp @@ -2311,6 +2311,9 @@ void stars_draw_background() render_info.set_alpha_mult(Nmodel_alpha); render_info.set_flags(Nmodel_flags | MR_SKYBOX); + if (Nmodel_instance_num >= 0) + render_info.set_replacement_textures(model_get_instance(Nmodel_instance_num)->texture_replace); + model_render_immediate(&render_info, Nmodel_num, Nmodel_instance_num, &Nmodel_orient, &Eye_position, MODEL_RENDER_ALL, false); } diff --git a/code/weapon/weapons.cpp b/code/weapon/weapons.cpp index 65dde15f456..c7b233e78c2 100644 --- a/code/weapon/weapons.cpp +++ b/code/weapon/weapons.cpp @@ -9021,6 +9021,9 @@ void weapon_render(object* obj, model_draw_list *scene) render_info.set_flags(render_flags); + if (wp->model_instance_num >= 0) + render_info.set_replacement_textures(model_get_instance(wp->model_instance_num)->texture_replace); + model_render_queue(&render_info, scene, wip->model_num, &obj->orient, &obj->pos); break; diff --git a/fred2/fredrender.cpp b/fred2/fredrender.cpp index b1aaf9b12e4..b05b00559c1 100644 --- a/fred2/fredrender.cpp +++ b/fred2/fredrender.cpp @@ -1876,7 +1876,7 @@ void render_one_model_htl(object *objp) { model_render_params render_info; render_info.set_debug_flags(debug_flags); - render_info.set_replacement_textures(Ships[z].ship_replacement_textures); + render_info.set_replacement_textures(model_get_instance(Ships[z].model_instance_num)->texture_replace); if (Fred_outline) { render_info.set_color(Fred_outline >> 16, (Fred_outline >> 8) & 0xff, Fred_outline & 0xff); diff --git a/qtfred/src/mission/FredRenderer.cpp b/qtfred/src/mission/FredRenderer.cpp index a897fd56622..1eb24d68950 100644 --- a/qtfred/src/mission/FredRenderer.cpp +++ b/qtfred/src/mission/FredRenderer.cpp @@ -874,7 +874,7 @@ void FredRenderer::render_one_model_htl(object* objp, model_render_params render_info; render_info.set_debug_flags(debug_flags); render_info.set_color(Fred_outline >> 16, (Fred_outline >> 8) & 0xff, Fred_outline & 0xff); - render_info.set_replacement_textures(Ships[z].ship_replacement_textures); + render_info.set_replacement_textures(model_get_instance(Ships[z].model_instance_num)->texture_replace); render_info.set_flags(j); g3_done_instance(0); From 8b3e36b3c92d59be3ff48c2e8e67e3332115f8b4 Mon Sep 17 00:00:00 2001 From: BMagnu <6238428+BMagnu@users.noreply.github.com> Date: Wed, 22 May 2024 16:26:18 +0900 Subject: [PATCH 20/63] Allow replacement of skybox textures (#6161) * Add skybox texture replace sexp * Expose Skybox and Skybox instance to scripting * first batch of comments * change sexp name * remove code repetition * rebake skybox if required --- code/model/model.h | 2 ++ code/model/modelrender.cpp | 35 ++++++++++++++++++++++++++ code/parse/sexp.cpp | 31 +++++++++++++++++++++-- code/parse/sexp.h | 1 + code/scripting/api/libs/mission.cpp | 19 +++++++++++++++ code/ship/ship.cpp | 34 ++------------------------ code/starfield/starfield.cpp | 38 ++++++++++++++++++----------- code/starfield/starfield.h | 1 + 8 files changed, 113 insertions(+), 48 deletions(-) diff --git a/code/model/model.h b/code/model/model.h index 7bda9ddee77..db6414063d6 100644 --- a/code/model/model.h +++ b/code/model/model.h @@ -1426,6 +1426,8 @@ void model_page_out_textures(int model_num, bool release = false); // given a model, without respect to usage state of the polymodel void model_page_out_textures(polymodel* pm, bool release = false, const SCP_set& skipTextures = {}, const SCP_set& skipGlowBanks = {}); +void modelinstance_replace_active_texture(polymodel_instance* pmi, const char* old_name, const char* new_name); + void model_do_intrinsic_motions(object *objp); int model_should_render_engine_glow(int objnum, int bank_obj); diff --git a/code/model/modelrender.cpp b/code/model/modelrender.cpp index 2439c2f8020..db723959dfd 100644 --- a/code/model/modelrender.cpp +++ b/code/model/modelrender.cpp @@ -3072,6 +3072,41 @@ void model_render_set_wireframe_color(const color* clr) Wireframe_color = *clr; } +void modelinstance_replace_active_texture(polymodel_instance* pmi, const char* old_name, const char* new_name) +{ + Assert(pmi != nullptr); + polymodel* pm = model_get(pmi->model_num); + + int final_index = -1; + + for (int i = 0; i < pm->n_textures; i++) + { + int tm_num = pm->maps[i].FindTexture(old_name); + if (tm_num > -1) + { + final_index = i * TM_NUM_TYPES + tm_num; + break; + } + } + + if (final_index >= 0) + { + int texture; + + if (!stricmp(new_name, "invisible")) + texture = REPLACE_WITH_INVISIBLE; + else + texture = bm_load_either(new_name); + + if (pmi->texture_replace == nullptr) { + pmi->texture_replace = make_shared(); + } + + (*pmi->texture_replace)[final_index] = texture; + } else + Warning(LOCATION, "Invalid texture '%s' used for replacement texture", old_name); +} + // renders a model as if in the tech room or briefing UI // model_type 1 for ship class, 2 for weapon class, 3 for pof bool render_tech_model(tech_render_type model_type, int x1, int y1, int x2, int y2, float zoom, bool lighting, int class_idx, const matrix* orient, const SCP_string &pof_filename, float close_zoom, const vec3d *close_pos) diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index 3506f3c6bf8..11d33261821 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -615,6 +615,7 @@ SCP_vector Operators = { { "get-collision-group", OP_GET_COLGROUP_ID, 1, 1, SEXP_ACTION_OPERATOR, }, { "change-team-color", OP_CHANGE_TEAM_COLOR, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // The E { "replace-texture", OP_REPLACE_TEXTURE, 3, INT_MAX, SEXP_ACTION_OPERATOR, }, // Lafiel + { "replace-skybox-texture", OP_REPLACE_TEXTURE_SKYBOX, 2, 2, SEXP_ACTION_OPERATOR, }, // Lafiel { "set-alpha-multiplier", OP_SET_ALPHA_MULT, 2, INT_MAX, SEXP_ACTION_OPERATOR, }, //Lafiel { "trigger-ship-animation", OP_TRIGGER_ANIMATION_NEW, 3, 7, SEXP_ACTION_OPERATOR, }, //Lafiel { "stop-looping-animation", OP_STOP_LOOPING_ANIMATION, 3, 3, SEXP_ACTION_OPERATOR, }, //Lafiel @@ -4832,6 +4833,7 @@ int get_sexp() break; case OP_REPLACE_TEXTURE: + case OP_REPLACE_TEXTURE_SKYBOX: //Texture name is argument 2 n = CDDR(start); do_preload_for_arguments(preload_texture, n, arg_handler); @@ -20775,7 +20777,7 @@ void ship_copy_damage(ship *target_shipp, ship *source_shipp) } } -void sexp_replace_texture(int n) +void sexp_replace_texture(int n, bool skybox) { auto old_name = CTEXT(n); n = CDR(n); @@ -20783,6 +20785,17 @@ void sexp_replace_texture(int n) auto new_name = CTEXT(n); n = CDR(n); + if (skybox) { + if (Nmodel_instance_num < 0) { + mprintf(("Tried to replace texture of a non-existant skybox\n")); + return; + } + polymodel_instance* skybox_pmi = model_get_instance(Nmodel_instance_num); + modelinstance_replace_active_texture(skybox_pmi, old_name, new_name); + stars_invalidate_environment_map(); + return; + } + for (; n != -1; n = CDR(n)) { @@ -29524,8 +29537,9 @@ int eval_sexp(int cur_node, int referenced_node) break; case OP_REPLACE_TEXTURE: + case OP_REPLACE_TEXTURE_SKYBOX: sexp_val = SEXP_TRUE; - sexp_replace_texture(node); + sexp_replace_texture(node, op_num == OP_REPLACE_TEXTURE_SKYBOX); break; case OP_SET_ALPHA_MULT: @@ -30783,6 +30797,7 @@ int query_operator_return_type(int op) case OP_TURRET_CLEAR_FORCED_TARGET: case OP_TURRET_SET_INACCURACY: case OP_REPLACE_TEXTURE: + case OP_REPLACE_TEXTURE_SKYBOX: case OP_NEBULA_CHANGE_FOG_COLOR: case OP_SET_ALPHA_MULT: case OP_TRIGGER_ANIMATION_NEW: @@ -33540,6 +33555,9 @@ int query_operator_argument_type(int op, int argnum) return OPF_STRING; else return OPF_SHIP_WING; + + case OP_REPLACE_TEXTURE_SKYBOX: + return OPF_STRING; case OP_SET_ALPHA_MULT: if (argnum == 0) @@ -35812,6 +35830,7 @@ int get_category(int op_id) case OP_SEND_MESSAGE_CHAIN: case OP_TURRET_SET_INACCURACY: case OP_REPLACE_TEXTURE: + case OP_REPLACE_TEXTURE_SKYBOX: case OP_NEBULA_CHANGE_FOG_COLOR: case OP_SET_ALPHA_MULT: case OP_DESTROY_INSTANTLY_WITH_DEBRIS: @@ -36139,6 +36158,7 @@ int get_subcategory(int op_id) case OP_GET_COLGROUP_ID: case OP_CHANGE_TEAM_COLOR: case OP_REPLACE_TEXTURE: + case OP_REPLACE_TEXTURE_SKYBOX: case OP_SET_ALPHA_MULT: case OP_TRIGGER_ANIMATION_NEW: case OP_UPDATE_MOVEABLE: @@ -41200,6 +41220,13 @@ SCP_vector Sexp_help = { "\tRest: Name of the ship or wing (ship/wing does not need to be in-mission).\r\n" }, + { OP_REPLACE_TEXTURE_SKYBOX, "replace-skybox-texture\r\n" + "\tChanges a texture of the skybox to a different texture, similar to the FRED texture replace.\r\n" + "Takes 2 arguments...\r\n" + "\t1: Name of the texture to be replaced.\r\n" + "\t2: Name of the texture to be changed to.\r\n" + }, + { OP_SET_ALPHA_MULT, "set-alpha-multiplier\r\n" "\tSets the opacity of a ship.\r\n" "Takes 2 or more arguments...\r\n" diff --git a/code/parse/sexp.h b/code/parse/sexp.h index cb4f97c5821..04b5dc9e29b 100644 --- a/code/parse/sexp.h +++ b/code/parse/sexp.h @@ -869,6 +869,7 @@ enum : int { OP_TURRET_SET_INACCURACY, // Asteroth OP_REPLACE_TEXTURE, // Lafiel + OP_REPLACE_TEXTURE_SKYBOX, // Lafiel OP_NEBULA_CHANGE_FOG_COLOR, // Asteroth OP_SET_ALPHA_MULT, // Lafiel OP_DESTROY_INSTANTLY_WITH_DEBRIS, // Asteroth diff --git a/code/scripting/api/libs/mission.cpp b/code/scripting/api/libs/mission.cpp index 62029d8da9c..166929327e1 100644 --- a/code/scripting/api/libs/mission.cpp +++ b/code/scripting/api/libs/mission.cpp @@ -50,6 +50,7 @@ #include "scripting/api/objs/fireballclass.h" #include "scripting/api/objs/message.h" #include "scripting/api/objs/model.h" +#include "scripting/api/objs/modelinstance.h" #include "scripting/api/objs/object.h" #include "scripting/api/objs/parse_object.h" #include "scripting/api/objs/promise.h" @@ -2177,6 +2178,24 @@ ADE_VIRTVAR(SkyboxAlpha, l_Mission, "number", "Sets or returns the current skybo return ade_set_args(L, "f", Nmodel_alpha); } +ADE_VIRTVAR(Skybox, l_Mission, "model", "Sets or returns the current skybox model", "model", "The skybox model") +{ + model_h* model = nullptr; + if (!ade_get_args(L, "*|o", l_Model.GetPtr(&model))) + return ade_set_error(L, "o", l_Model.Set(model_h())); + + if (ADE_SETTING_VAR && model && model->isValid()) { + stars_set_background_model(model->GetID(), -1, Nmodel_flags, Nmodel_alpha); + } + + return ade_set_args(L, "o", l_Model.Set(model_h(Nmodel_num))); +} + +ADE_FUNC(getSkyboxInstance, l_Mission, nullptr, "Returns the current skybox model instance", "model_instance", "The skybox model instance") +{ + return ade_set_args(L, "o", l_ModelInstance.Set(modelinstance_h(Nmodel_instance_num))); +} + ADE_FUNC(isRedAlertMission, l_Mission, nullptr, diff --git a/code/ship/ship.cpp b/code/ship/ship.cpp index 2b9d454afaa..d49fa816e85 100644 --- a/code/ship/ship.cpp +++ b/code/ship/ship.cpp @@ -18419,38 +18419,8 @@ void ship_page_out_textures(int ship_index, bool release) void ship_replace_active_texture(int ship_index, const char* old_name, const char* new_name) { - ship* shipp = &Ships[ship_index]; - polymodel* pm = model_get(Ship_info[shipp->ship_info_index].model_num); - polymodel_instance* pmi = model_get_instance(shipp->model_instance_num); - - int final_index = -1; - - for (int i = 0; i < pm->n_textures; i++) - { - int tm_num = pm->maps[i].FindTexture(old_name); - if (tm_num > -1) - { - final_index = i * TM_NUM_TYPES + tm_num; - break; - } - } - - if (final_index >= 0) - { - int texture; - - if (!stricmp(new_name, "invisible")) - texture = REPLACE_WITH_INVISIBLE; - else - texture = bm_load_either(new_name); - - if (pmi->texture_replace == nullptr) { - pmi->texture_replace = make_shared(); - } - - (*pmi->texture_replace)[final_index] = texture; - } else - Warning(LOCATION, "Invalid texture '%s' used for replacement texture", old_name); + polymodel_instance* pmi = model_get_instance(Ships[ship_index].model_instance_num); + modelinstance_replace_active_texture(pmi, old_name, new_name); } // function to return true if support ships are allowed in the mission for the given object. diff --git a/code/starfield/starfield.cpp b/code/starfield/starfield.cpp index 4ccd48e99a6..4d898476864 100644 --- a/code/starfield/starfield.cpp +++ b/code/starfield/starfield.cpp @@ -820,7 +820,7 @@ void stars_pre_level_init(bool clear_backgrounds) stars_clear_instances(); - stars_set_background_model(NULL, NULL); + stars_set_background_model(nullptr, nullptr); stars_set_background_orientation(); // mark all starfield and sun bitmaps as unused for this mission and release any current bitmaps @@ -2317,23 +2317,12 @@ void stars_draw_background() model_render_immediate(&render_info, Nmodel_num, Nmodel_instance_num, &Nmodel_orient, &Eye_position, MODEL_RENDER_ALL, false); } -// call this to set a specific model as the background model -void stars_set_background_model(const char* model_name, const char* texture_name, int flags, float alpha) +void stars_set_background_model(int new_model, int new_bitmap, int flags, float alpha) { - int new_model = -1; - int new_bitmap = -1; - if (gr_screen.mode == GR_STUB) { return; } - if (model_name != nullptr && *model_name != '\0' && stricmp(model_name, "none") != 0) { - new_model = model_load(model_name, 0, nullptr, -1); - - if (texture_name != nullptr && *texture_name != '\0') { - new_bitmap = bm_load(texture_name); - } - } CLAMP(alpha, 0.0f, 1.0f); // see if we are actually changing anything @@ -2345,7 +2334,7 @@ void stars_set_background_model(const char* model_name, const char* texture_name bm_unload(Nmodel_bitmap); Nmodel_bitmap = -1; } - + if (Nmodel_num >= 0) { model_unload(Nmodel_num); Nmodel_num = -1; @@ -2372,6 +2361,27 @@ void stars_set_background_model(const char* model_name, const char* texture_name stars_invalidate_environment_map(); } +// call this to set a specific model as the background model +void stars_set_background_model(const char* model_name, const char* texture_name, int flags, float alpha) +{ + int new_model = -1; + int new_bitmap = -1; + + if (gr_screen.mode == GR_STUB) { + return; + } + + if (model_name != nullptr && *model_name != '\0' && stricmp(model_name, "none") != 0) { + new_model = model_load(model_name, 0, nullptr, -1); + + if (texture_name != nullptr && *texture_name != '\0') { + new_bitmap = bm_load(texture_name); + } + } + + stars_set_background_model(new_model, new_bitmap, flags, alpha); +} + // call this to set a specific orientation for the background void stars_set_background_orientation(const matrix *orient) { diff --git a/code/starfield/starfield.h b/code/starfield/starfield.h index 2518984fa75..76e6dbab13a 100644 --- a/code/starfield/starfield.h +++ b/code/starfield/starfield.h @@ -151,6 +151,7 @@ void stars_draw_sun_glow(int sun_n); void stars_camera_cut(); // call this to set a specific model as the background model +void stars_set_background_model(int new_model, int new_bitmap = -1, int flags = DEFAULT_NMODEL_FLAGS, float alpha = 1.0f); void stars_set_background_model(const char *model_name, const char *texture_name, int flags = DEFAULT_NMODEL_FLAGS, float alpha = 1.0f); void stars_set_background_orientation(const matrix *orient = nullptr); void stars_set_background_alpha(float alpha = 1.0f); From 3b0d4558cabbd680300888343f2d25acc73549ac Mon Sep 17 00:00:00 2001 From: Asteroth Date: Wed, 22 May 2024 19:35:01 -0400 Subject: [PATCH 21/63] fix for decloaking --- code/ship/ship.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/ship/ship.cpp b/code/ship/ship.cpp index d49fa816e85..cd3ddf7d1cc 100644 --- a/code/ship/ship.cpp +++ b/code/ship/ship.cpp @@ -20737,6 +20737,8 @@ void ship_render_set_animated_effect(model_render_params *render_info, ship *shi if ( timestamp_elapsed(shipp->shader_effect_timestamp) ) { shipp->flags.set(Ship_Flags::Cloaked, sep->disables_rendering); shipp->shader_effect_timestamp = TIMESTAMP::invalid(); + } else { + shipp->flags.remove(Ship_Flags::Cloaked); } } From 73bbc94e5e1cd36410bfbf75ca4f26ef10d42cdb Mon Sep 17 00:00:00 2001 From: Asteroth Date: Fri, 24 May 2024 23:19:44 -0400 Subject: [PATCH 22/63] (int) -> fl2i --- code/hud/hudreticle.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/code/hud/hudreticle.cpp b/code/hud/hudreticle.cpp index 136cb59eb24..877573c04c8 100644 --- a/code/hud/hudreticle.cpp +++ b/code/hud/hudreticle.cpp @@ -323,7 +323,7 @@ void HudGaugeReticle::render(float /*frametime*/) renderBitmap(fixed_reticle, position[0], position[1]); } else { - renderCircle((int)(base_w * 0.5f), (int)(base_h * 0.5f), (int)(base_h * 0.03f), false); + renderCircle(fl2i(base_w * 0.5f), fl2i(base_h * 0.5f), fl2i(base_h * 0.03f), false); } if (Player_flight_mode == FlightMode::FlightCursor || sip->aims_at_flight_cursor) { @@ -332,13 +332,13 @@ void HudGaugeReticle::render(float /*frametime*/) else setGaugeColor(HUD_C_NORMAL); - int x = (int)(Player_flight_cursor_offset.screen.xyw.x + 0.5f); - int y = (int)(Player_flight_cursor_offset.screen.xyw.y + 0.5f); + int x = fl2i(Player_flight_cursor_offset.screen.xyw.x + 0.5f); + int y = fl2i(Player_flight_cursor_offset.screen.xyw.y + 0.5f); unsize(&x, &y); if (mobile_reticle >= 0) - renderBitmap(mobile_reticle, (int)(x - base_w * 0.5f) + position[0], (int)(y - base_h * 0.5f) + position[1]); + renderBitmap(mobile_reticle, fl2i(x - base_w * 0.5f) + position[0], fl2i(y - base_h * 0.5f) + position[1]); else { - renderCircle(x, y, (int)(base_h * 0.03f), false); + renderCircle(x, y, fl2i(base_h * 0.03f), false); } } From 42355b238463947f57b3c7c62542e8ea30de2bb3 Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Sun, 26 May 2024 10:48:20 +0100 Subject: [PATCH 23/63] Add Paths Editor --- qtfred/source_groups.cmake | 5 + .../ShipEditor/ShipPathsDialogModel.cpp | 93 +++++++++++++++++++ .../dialogs/ShipEditor/ShipPathsDialogModel.h | 31 +++++++ .../dialogs/ShipEditor/ShipEditorDialog.cpp | 22 ++++- .../ui/dialogs/ShipEditor/ShipEditorDialog.h | 1 + .../ui/dialogs/ShipEditor/ShipPathsDialog.cpp | 74 +++++++++++++++ .../ui/dialogs/ShipEditor/ShipPathsDialog.h | 34 +++++++ qtfred/ui/ShipPathsDialog.ui | 83 +++++++++++++++++ 8 files changed, 339 insertions(+), 4 deletions(-) create mode 100644 qtfred/src/mission/dialogs/ShipEditor/ShipPathsDialogModel.cpp create mode 100644 qtfred/src/mission/dialogs/ShipEditor/ShipPathsDialogModel.h create mode 100644 qtfred/src/ui/dialogs/ShipEditor/ShipPathsDialog.cpp create mode 100644 qtfred/src/ui/dialogs/ShipEditor/ShipPathsDialog.h create mode 100644 qtfred/ui/ShipPathsDialog.ui diff --git a/qtfred/source_groups.cmake b/qtfred/source_groups.cmake index 8a00be0f7e2..c46b7415130 100644 --- a/qtfred/source_groups.cmake +++ b/qtfred/source_groups.cmake @@ -86,6 +86,8 @@ add_file_folder("Source/Mission/Dialogs/ShipEditor" src/mission/dialogs/ShipEditor/ShipTextureReplacementDialogModel.cpp src/mission/dialogs/ShipEditor/ShipTBLViewerModel.cpp src/mission/dialogs/ShipEditor/ShipTBLViewerModel.h + src/mission/dialogs/ShipEditor/ShipPathsDialogModel.cpp + src/mission/dialogs/ShipEditor/ShipPathsDialogModel.h ) add_file_folder("Source/UI" @@ -152,6 +154,8 @@ add_file_folder("Source/UI/Dialogs/ShipEditor" src/ui/dialogs/ShipEditor/ShipTextureReplacementDialog.cpp src/ui/dialogs/ShipEditor/ShipTBLViewer.h src/ui/dialogs/ShipEditor/ShipTBLViewer.cpp + src/ui/dialogs/ShipEditor/ShipPathsDialog.h + src/ui/dialogs/ShipEditor/ShipPathsDialog.cpp ) add_file_folder("Source/UI/Util" @@ -201,6 +205,7 @@ add_file_folder("UI" ui/PlayerOrdersDialog.ui ui/ShipTextureReplacementDialog.ui ui/ShipTBLViewer.ui + ui/ShipPathsDialog.ui ) add_file_folder("Resources" diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipPathsDialogModel.cpp b/qtfred/src/mission/dialogs/ShipEditor/ShipPathsDialogModel.cpp new file mode 100644 index 00000000000..fc7dee0ea2f --- /dev/null +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipPathsDialogModel.cpp @@ -0,0 +1,93 @@ +#include "ShipPathsDialogModel.h" + +namespace fso { +namespace fred { +namespace dialogs { +ShipPathsDialogModel::ShipPathsDialogModel(QObject* parent, + EditorViewport* viewport, + const int ship, + int target_class, + const bool departure) + : AbstractDialogModel(parent, viewport) +{ + initalizeData(ship, target_class, departure); +} + +void ShipPathsDialogModel::initalizeData(const int ship, int target_class, const bool departure) +{ + departureMode = departure; + m_ship = ship; + m_model = model_get(Ship_info[target_class].model_num); + Assert(m_model->ship_bay); + m_num_paths = m_model->ship_bay->num_paths; + Assert(m_num_paths > 0); + if (!departure) { + m_path_mask = Ships[m_ship].arrival_path_mask; + } else { + m_path_mask = Ships[m_ship].departure_path_mask; + } + m_path_list.resize(m_num_paths, true); + for (int i = 0; i < m_num_paths; i++) { + bool allowed; + if (m_path_mask == 0) { + allowed = true; + } else { + allowed = (m_path_mask & (1 << i)) ? true : false; + } + m_path_list[i] = allowed; + } +} + +bool ShipPathsDialogModel::apply() +{ + if (!_modified) { + return true; + } else { + int num_allowed = 0; + m_path_mask = 0; + for (int i = 0; i < m_num_paths; i++) { + if (m_path_list[i] == true) { + m_path_mask |= (1 << i); + num_allowed++; + } + } + if (num_allowed == m_num_paths) { + m_path_mask = 0; + } + if (!departureMode) { + Ships[m_ship].arrival_path_mask = m_path_mask; + } else { + Ships[m_ship].departure_path_mask = m_path_mask; + } + return true; + } +} + +void ShipPathsDialogModel::reject() {} + +bool ShipPathsDialogModel::modify(const int index, const bool value) +{ + Assertion(index < m_num_paths, "Requsted index %d is larger than m_num_paths.\n", index); + if (index < m_num_paths) { + m_path_list[index] = value; + _modified = true; + return true; + } else { + return false; + } +} +const bool ShipPathsDialogModel::query_modified() const +{ + return _modified; +} +const SCP_vector ShipPathsDialogModel::getPathList() const +{ + return m_path_list; +} +const polymodel* ShipPathsDialogModel::getModel() const +{ + return m_model; +} +} // namespace dialogs +} // namespace fred +} // namespace fso \ No newline at end of file diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipPathsDialogModel.h b/qtfred/src/mission/dialogs/ShipEditor/ShipPathsDialogModel.h new file mode 100644 index 00000000000..20f775df2aa --- /dev/null +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipPathsDialogModel.h @@ -0,0 +1,31 @@ +#pragma once +#include "../AbstractDialogModel.h" +namespace fso { +namespace fred { +namespace dialogs { + +class ShipPathsDialogModel : public AbstractDialogModel { + private: + bool departureMode; + void initalizeData(const int ship, const int target_class, const bool departure); + polymodel* m_model; + int m_num_paths; + SCP_vector m_path_list; + int m_path_mask; + int m_ship; + bool _modified = false; + public: + ShipPathsDialogModel(QObject* parent, + EditorViewport* viewport, + const int ship, + const int target_class, const bool departure = false); + bool apply() override; + void reject() override; + bool modify(const int, const bool); + const bool query_modified() const; + const SCP_vector getPathList() const; + const polymodel* getModel() const; +}; +} // namespace dialogs +} // namespace fred +} // namespace fso \ No newline at end of file diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.cpp b/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.cpp index 2ec6512a3ed..beab9983726 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.cpp +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.cpp @@ -521,7 +521,11 @@ void ShipEditorDialog::enableDisable() ui->arrivalTargetCombo->setEnabled(false); } if (_model->getArrivalLocation() == ArrivalLocation::FROM_DOCK_BAY) { - ui->restrictArrivalPathsButton->setEnabled(_model->getUIEnable()); + if (_model->getArrivalTarget() >= 0) { + ui->restrictArrivalPathsButton->setEnabled(_model->getUIEnable()); + } else { + ui->restrictArrivalPathsButton->setEnabled(false); + } ui->customWarpinButton->setEnabled(false); } else { ui->restrictArrivalPathsButton->setEnabled(false); @@ -535,7 +539,11 @@ void ShipEditorDialog::enableDisable() ui->departureTargetCombo->setEnabled(false); } if (_model->getDepartureLocation() == DepartureLocation::TO_DOCK_BAY) { - ui->restrictDeparturePathsButton->setEnabled(_model->getUIEnable()); + if (_model->getDepartureTarget() >= 0) { + ui->restrictDeparturePathsButton->setEnabled(_model->getUIEnable()); + } else { + ui->restrictDeparturePathsButton->setEnabled(false); + } ui->customWarpoutButton->setEnabled(false); } else { ui->restrictDeparturePathsButton->setEnabled(false); @@ -863,7 +871,11 @@ void ShipEditorDialog::on_hideCuesButton_clicked() } void ShipEditorDialog::on_restrictArrivalPathsButton_clicked() { - // TODO:: Restrict Paths Dialog + int target_class = Ships[_model->getArrivalTarget()].ship_info_index; + auto dialog = new dialogs::ShipPathsDialog(this, _viewport, _model->getSingleShip(), target_class, false); + dialog->show(); + + } void ShipEditorDialog::on_customWarpinButton_clicked() { @@ -871,7 +883,9 @@ void ShipEditorDialog::on_customWarpinButton_clicked() } void ShipEditorDialog::on_restrictDeparturePathsButton_clicked() { - // TODO:: Restrict Paths Dialog + int target_class = Ships[_model->getDepartureTarget()].ship_info_index; + auto dialog = new dialogs::ShipPathsDialog(this, _viewport, _model->getSingleShip(), target_class, true); + dialog->show(); } void ShipEditorDialog::on_customWarpoutButton_clicked() { // TODO:: Custom warp Dialog diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.h b/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.h index 773e9b2665b..058850915ef 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.h +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.h @@ -11,6 +11,7 @@ #include "ShipSpecialStatsDialog.h" #include "ShipTextureReplacementDialog.h" #include "ShipTBLViewer.h" +#include "ShipPathsDialog.h" #include #include diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipPathsDialog.cpp b/qtfred/src/ui/dialogs/ShipEditor/ShipPathsDialog.cpp new file mode 100644 index 00000000000..f3e2de75fcc --- /dev/null +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipPathsDialog.cpp @@ -0,0 +1,74 @@ +#include "ShipPathsDialog.h" +#include "ui_ShipPathsDialog.h" +#include + +#include +namespace fso { +namespace fred { +namespace dialogs { +ShipPathsDialog::ShipPathsDialog(QWidget* parent, + EditorViewport* viewport, + const int ship, + const int target_class, + const bool departure) + : QDialog(parent), ui(new Ui::ShipPathsDialog()), _viewport(viewport), + _model(new ShipPathsDialogModel(this, viewport, ship, target_class, departure)) +{ + ui->setupUi(this); + connect(ui->pathList, &QListWidget::itemChanged, this, &ShipPathsDialog::changed); + connect(this, &QDialog::accepted, _model.get(), &ShipPathsDialogModel::apply); + connect(this, &QDialog::rejected, _model.get(), &ShipPathsDialogModel::reject); + if (departure) { + ui->instructLabel->setText("Restrict departure paths to the following:"); + } else { + ui->instructLabel->setText("Restrict arrival paths to the following:"); + } + updateUI(); + + // Resize the dialog to the minimum size + resize(QDialog::sizeHint()); +} +ShipPathsDialog::~ShipPathsDialog() = default; +void ShipPathsDialog::closeEvent(QCloseEvent* event) { + if (_model->query_modified()) { + auto button = _viewport->dialogProvider->showButtonDialog(DialogType::Question, + "Changes detected", + "Do you want to keep your changes?", + {DialogButton::Yes, DialogButton::No, DialogButton::Cancel}); + + if (button == DialogButton::Cancel) { + event->ignore(); + return; + } + + if (button == DialogButton::Yes) { + accept(); + return; + } + } + + QDialog::closeEvent(event); +} +void dialogs::ShipPathsDialog::updateUI() { + util::SignalBlockers blockers(this); + for (size_t i = 0; i < _model->getPathList().size(); i++) { + QString name = _model->getModel()->paths[_model->getModel()->ship_bay->path_indexes[i]].name; + auto item = new QListWidgetItem(name, ui->pathList); + item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); + Qt::CheckState state; + if (_model->getPathList()[i] == true) { + state = Qt::Checked; + } else { + state = Qt::Unchecked; + } + item->setCheckState(state); + } +} +void ShipPathsDialog::changed(QListWidgetItem* changeditem) { + bool checked = changeditem->checkState() == Qt::Checked; + int index = ui->pathList->row(changeditem); + _model->modify(index, checked); +} +} // namespace dialogs +} // namespace fred +} // namespace fso \ No newline at end of file diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipPathsDialog.h b/qtfred/src/ui/dialogs/ShipEditor/ShipPathsDialog.h new file mode 100644 index 00000000000..d32d4b8ef9e --- /dev/null +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipPathsDialog.h @@ -0,0 +1,34 @@ +#pragma once + +#include "mission/dialogs/ShipEditor/ShipPathsDialogModel.h" +#include +#include +namespace fso { +namespace fred { +namespace dialogs { +namespace Ui { +class ShipPathsDialog; +} +class ShipPathsDialog : public QDialog { + Q_OBJECT + public: + explicit ShipPathsDialog(QWidget* parent, + EditorViewport* viewport, + const int ship, + const int target_class, + const bool departure); + ~ShipPathsDialog() override; + + protected: + void closeEvent(QCloseEvent*) override; + + private: + std::unique_ptr ui; + std::unique_ptr _model; + EditorViewport* _viewport; + void updateUI(); + void changed(QListWidgetItem* changeditem); +}; +} // namespace dialogs +} // namespace fred +} // namespace fso \ No newline at end of file diff --git a/qtfred/ui/ShipPathsDialog.ui b/qtfred/ui/ShipPathsDialog.ui new file mode 100644 index 00000000000..a39d410658c --- /dev/null +++ b/qtfred/ui/ShipPathsDialog.ui @@ -0,0 +1,83 @@ + + + fso::fred::dialogs::ShipPathsDialog + + + + 0 + 0 + 370 + 110 + + + + Restrict Paths + + + true + + + + + + Restrict paths to the following: + + + + + + + + + + + + Qt::Vertical + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + + + buttonBox + accepted() + fso::fred::dialogs::ShipPathsDialog + accept() + + + 307 + 58 + + + 211 + 106 + + + + + buttonBox + rejected() + fso::fred::dialogs::ShipPathsDialog + reject() + + + 335 + 63 + + + 133 + 106 + + + + + From dbac7d1259c1a272abb4b20491358dd9e3c431b1 Mon Sep 17 00:00:00 2001 From: Asteroth Date: Sun, 26 May 2024 13:05:52 -0400 Subject: [PATCH 24/63] make the options beginner instead of advanced --- code/playerman/playercontrol.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/playerman/playercontrol.cpp b/code/playerman/playercontrol.cpp index af468d31291..6fb8a77e905 100644 --- a/code/playerman/playercontrol.cpp +++ b/code/playerman/playercontrol.cpp @@ -69,7 +69,7 @@ auto FlightModeOption = options::OptionBuilder("Game.FlightMode", std::pair{"Flight Mode", 1842}, std::pair{"Choose the flying style to use during gameplay.", 1843}) .category(std::make_pair("Game", 1824)) - .level(options::ExpertLevel::Advanced) + .level(options::ExpertLevel::Beginner) .values({ {FlightMode::ShipLocked, {"Ship Locked", 1844}}, {FlightMode::FlightCursor, {"Flight Cursor", 1845}} }) .default_val(FlightMode::ShipLocked) @@ -95,7 +95,7 @@ auto FlightCursorExtentOption = options::OptionBuilder("Game.FlightCursor .range(0.0f, 0.698f) .display(degrees_display) .default_val(0.348f) - .level(options::ExpertLevel::Advanced) + .level(options::ExpertLevel::Beginner) .bind_to(&flight_cursor_extent) .importance(44) .finish(); @@ -109,7 +109,7 @@ auto FlightCursorDeadzoneOption = options::OptionBuilder("Game.FlightCurs .range(0.0f, 0.349f) .display(degrees_display) .default_val(0.02f) - .level(options::ExpertLevel::Advanced) + .level(options::ExpertLevel::Beginner) .bind_to(&flight_cursor_deadzone) .importance(43) .finish(); From c6efd0a0a328c0e5d0f590aec75ed7b5352b940b Mon Sep 17 00:00:00 2001 From: Kestrellius <63537900+Kestrellius@users.noreply.github.com> Date: Sun, 26 May 2024 11:42:53 -0700 Subject: [PATCH 25/63] Makes random life factor of children configurable. (#6166) * Makes random life factor of children configurable. * Renames parameter and associated variables. --- code/weapon/weapon.h | 2 ++ code/weapon/weapons.cpp | 11 ++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/code/weapon/weapon.h b/code/weapon/weapon.h index d68f750fd8e..507cd31a0ec 100644 --- a/code/weapon/weapon.h +++ b/code/weapon/weapon.h @@ -418,6 +418,8 @@ struct weapon_info int maximum_children_spawned; // An upper bound for the total number of spawned children, used by multi spawn_weapon_info spawn_info[MAX_SPAWN_TYPES_PER_WEAPON]; + float lifetime_variation_factor_when_child; + // swarm count short swarm_count; // how many swarm missiles are fired for this weapon int SwarmWait; // *Swarm firewait, default is 150 -Et1 diff --git a/code/weapon/weapons.cpp b/code/weapon/weapons.cpp index c7b233e78c2..771b2238d30 100644 --- a/code/weapon/weapons.cpp +++ b/code/weapon/weapons.cpp @@ -2616,6 +2616,11 @@ int parse_weapon(int subtype, bool replace, const char *filename) } } + if (optional_string("$Lifetime Variation Factor When Child:")) + { + stuff_float(&wip->lifetime_variation_factor_when_child); + } + if (wip->wi_flags[Weapon::Info_Flags::Local_ssm] && optional_string("$Local SSM:")) { if(optional_string("+Warpout Delay:")) { @@ -6969,7 +6974,9 @@ void spawn_child_weapons(object *objp, int spawn_index_override) rand_val = static_randf(objp->net_signature + j); } - Weapons[Objects[weapon_objnum].instance].lifeleft *= rand_val*0.4f + 0.8f; + float child_factor = child_wip->lifetime_variation_factor_when_child; + + Weapons[Objects[weapon_objnum].instance].lifeleft *= std::max(0.01f, rand_val*(child_factor*2.0f) + (1.0f - child_factor)); if (child_wip->wi_flags[Weapon::Info_Flags::Remote]) { parent_shipp->weapons.detonate_weapon_time = timestamp((int)(DEFAULT_REMOTE_DETONATE_TRIGGER_WAIT * 1000)); parent_shipp->weapons.remote_detonaters_active++; @@ -9243,6 +9250,8 @@ void weapon_info::reset() this->spawn_info[i].spawn_chance = 1.f; } + this->lifetime_variation_factor_when_child = 0.2; + this->swarm_count = -1; // *Default is 150 -Et1 this->SwarmWait = SWARM_MISSILE_DELAY; From 120b76b21e3defb1b8fb48a6ba72c90791beb840 Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Mon, 27 May 2024 09:30:25 +0100 Subject: [PATCH 26/63] GCC --- .../src/mission/dialogs/ShipEditor/ShipPathsDialogModel.cpp | 6 +++--- .../src/mission/dialogs/ShipEditor/ShipPathsDialogModel.h | 6 +++--- qtfred/src/ui/dialogs/ShipEditor/ShipPathsDialog.cpp | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipPathsDialogModel.cpp b/qtfred/src/mission/dialogs/ShipEditor/ShipPathsDialogModel.cpp index fc7dee0ea2f..8b1d1a59a4e 100644 --- a/qtfred/src/mission/dialogs/ShipEditor/ShipPathsDialogModel.cpp +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipPathsDialogModel.cpp @@ -76,15 +76,15 @@ bool ShipPathsDialogModel::modify(const int index, const bool value) return false; } } -const bool ShipPathsDialogModel::query_modified() const +bool ShipPathsDialogModel::query_modified() const { return _modified; } -const SCP_vector ShipPathsDialogModel::getPathList() const +SCP_vector ShipPathsDialogModel::getPathList() const { return m_path_list; } -const polymodel* ShipPathsDialogModel::getModel() const +polymodel* ShipPathsDialogModel::getModel() const { return m_model; } diff --git a/qtfred/src/mission/dialogs/ShipEditor/ShipPathsDialogModel.h b/qtfred/src/mission/dialogs/ShipEditor/ShipPathsDialogModel.h index 20f775df2aa..9e1e5a149ea 100644 --- a/qtfred/src/mission/dialogs/ShipEditor/ShipPathsDialogModel.h +++ b/qtfred/src/mission/dialogs/ShipEditor/ShipPathsDialogModel.h @@ -22,9 +22,9 @@ class ShipPathsDialogModel : public AbstractDialogModel { bool apply() override; void reject() override; bool modify(const int, const bool); - const bool query_modified() const; - const SCP_vector getPathList() const; - const polymodel* getModel() const; + bool query_modified() const; + SCP_vector getPathList() const; + polymodel* getModel() const; }; } // namespace dialogs } // namespace fred diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipPathsDialog.cpp b/qtfred/src/ui/dialogs/ShipEditor/ShipPathsDialog.cpp index f3e2de75fcc..d939252f94f 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipPathsDialog.cpp +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipPathsDialog.cpp @@ -11,8 +11,8 @@ ShipPathsDialog::ShipPathsDialog(QWidget* parent, const int ship, const int target_class, const bool departure) - : QDialog(parent), ui(new Ui::ShipPathsDialog()), _viewport(viewport), - _model(new ShipPathsDialogModel(this, viewport, ship, target_class, departure)) + : QDialog(parent), ui(new Ui::ShipPathsDialog()), + _model(new ShipPathsDialogModel(this, viewport, ship, target_class, departure)), _viewport(viewport) { ui->setupUi(this); connect(ui->pathList, &QListWidget::itemChanged, this, &ShipPathsDialog::changed); From 316426115208c7cb16a2bf8e4cd26c8881eb40c5 Mon Sep 17 00:00:00 2001 From: Asteroth Date: Mon, 27 May 2024 13:27:46 -0400 Subject: [PATCH 27/63] update to better random vector --- code/scripting/api/libs/base.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/scripting/api/libs/base.cpp b/code/scripting/api/libs/base.cpp index 5f5e4311841..6f1b3a9612a 100644 --- a/code/scripting/api/libs/base.cpp +++ b/code/scripting/api/libs/base.cpp @@ -207,10 +207,10 @@ ADE_FUNC(createVector, l_Base, "[number x, number y, number z]", "Creates a vect return ade_set_args(L, "o", l_Vector.Set(v3)); } -ADE_FUNC(createRandomVector, l_Base, nullptr, "Creates a random normalized vector object.", "vector", "Vector object") +ADE_FUNC(createRandomVector, l_Base, nullptr, "Creates a uniformly random normalized vector object.", "vector", "Vector object") { vec3d v3; - vm_vec_rand_vec(&v3); + vm_vec_random_in_sphere(&v3, &vmd_zero_vector, 1.0f, true); return ade_set_args(L, "o", l_Vector.Set(v3)); } From f0f7eb513f69aa31e7eec640c393f05a1cf2ed22 Mon Sep 17 00:00:00 2001 From: Asteroth Date: Mon, 27 May 2024 13:28:13 -0400 Subject: [PATCH 28/63] fix --- code/scripting/api/libs/base.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/scripting/api/libs/base.cpp b/code/scripting/api/libs/base.cpp index 6f1b3a9612a..72a3c4beab6 100644 --- a/code/scripting/api/libs/base.cpp +++ b/code/scripting/api/libs/base.cpp @@ -207,7 +207,7 @@ ADE_FUNC(createVector, l_Base, "[number x, number y, number z]", "Creates a vect return ade_set_args(L, "o", l_Vector.Set(v3)); } -ADE_FUNC(createRandomVector, l_Base, nullptr, "Creates a uniformly random normalized vector object.", "vector", "Vector object") +ADE_FUNC(createRandomVector, l_Base, nullptr, "Creates a random normalized vector object.", "vector", "Vector object") { vec3d v3; vm_vec_random_in_sphere(&v3, &vmd_zero_vector, 1.0f, true); From e710b651ded40d33ee326f1b992114b53d5bf210 Mon Sep 17 00:00:00 2001 From: Kestrellius <63537900+Kestrellius@users.noreply.github.com> Date: Tue, 28 May 2024 10:58:59 -0700 Subject: [PATCH 29/63] Inlines headon glow render; converts some casts. --- code/weapon/weapons.cpp | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/code/weapon/weapons.cpp b/code/weapon/weapons.cpp index a5ec86cea34..8b04f617a5a 100644 --- a/code/weapon/weapons.cpp +++ b/code/weapon/weapons.cpp @@ -8740,16 +8740,6 @@ float weapon_render_headon_bitmap(object* wep_objp, vec3d* headp, vec3d* tailp, return side_alpha; } -// renders another glow bitmap on top of the regular bitmap based on the angle of the camera to the front of the laser -// uses the alpha data already gathered by weapon_render_headon_bitmap, because the alpha of the glow should always match the alpha of the laser -void weapon_render_headon_glow_bitmap(float side_alpha, vec3d* headp, vec3d* tailp, int bitmap, float width1, float width2, int r, int g, int b){ - float head_alpha = 1.0 - side_alpha; - - r = (int)(r * head_alpha); g = (int)(g * head_alpha); b = (int)(b * head_alpha); - - batching_add_laser(bitmap, headp, width1, tailp, width2, r, g, b); -} - void weapon_render(object* obj, model_draw_list *scene) { int num; @@ -8856,7 +8846,7 @@ void weapon_render(object* obj, model_draw_list *scene) float scaled_head_radius = model_render_get_diameter_clamped_to_min_pixel_size(&headp, wip->laser_head_radius * radius_mult, wip->laser_min_pixel_size); float scaled_tail_radius = model_render_get_diameter_clamped_to_min_pixel_size(&tailp, wip->laser_tail_radius * radius_mult, wip->laser_min_pixel_size); - int alpha = static_cast(alphaf * 255.0f); + int alpha = fl2i(alphaf * 255.0f); // render the head-on bitmap if appropriate and maybe adjust the main bitmap's alpha if (wip->laser_headon_bitmap.first_frame >= 0) { @@ -8865,7 +8855,7 @@ void weapon_render(object* obj, model_draw_list *scene) scaled_head_radius, scaled_tail_radius, alpha, alpha, alpha); - alpha = static_cast(alphaf * main_bitmap_alpha_mult * 255.0); + alpha = fl2i(alphaf * main_bitmap_alpha_mult * 255.0); } batching_add_laser( @@ -8949,20 +8939,26 @@ void weapon_render(object* obj, model_draw_list *scene) float scaled_head_radius = model_render_get_diameter_clamped_to_min_pixel_size(&headp2, wip->laser_head_radius * radius_mult, wip->laser_min_pixel_size); float scaled_tail_radius = model_render_get_diameter_clamped_to_min_pixel_size(&tailp2, wip->laser_tail_radius * radius_mult, wip->laser_min_pixel_size); - int r = static_cast(static_cast(c.red) * alphaf); - int g = static_cast(static_cast(c.green) * alphaf); - int b = static_cast(static_cast(c.blue) * alphaf); + int r = fl2i(i2fl(c.red) * alphaf); + int g = fl2i(i2fl(c.green) * alphaf); + int b = fl2i(i2fl(c.blue) * alphaf); // render the head-on bitmap if appropriate and maybe adjust the main bitmap's alpha if (wip->laser_glow_headon_bitmap.first_frame >= 0) { - weapon_render_headon_glow_bitmap(main_bitmap_alpha_mult, &headp2, &tailp2, + float head_alpha = 1.0 - main_bitmap_alpha_mult; + + r = (int)(r * head_alpha); g = (int)(g * head_alpha); b = (int)(b * head_alpha); + + batching_add_laser( wip->laser_glow_headon_bitmap.first_frame + headon_framenum, + &headp2, scaled_head_radius * wip->laser_glow_head_scale, + &tailp2, scaled_tail_radius * wip->laser_glow_tail_scale, r, g, b); - r = static_cast(static_cast(c.red) * alphaf * main_bitmap_alpha_mult); - g = static_cast(static_cast(c.green) * alphaf * main_bitmap_alpha_mult); - b = static_cast(static_cast(c.blue) * alphaf * main_bitmap_alpha_mult); + r = fl2i(i2fl(c.red) * alphaf * main_bitmap_alpha_mult); + g = fl2i(i2fl(c.green) * alphaf * main_bitmap_alpha_mult); + b = fl2i(i2fl(c.blue) * alphaf * main_bitmap_alpha_mult); } batching_add_laser( From e9a91f9c1ffdf857bedd9bb9a3a349ae853ff054 Mon Sep 17 00:00:00 2001 From: Kestrellius <63537900+Kestrellius@users.noreply.github.com> Date: Tue, 28 May 2024 17:46:32 -0700 Subject: [PATCH 30/63] Sets type 5 end point rand to use correct values. --- code/weapon/beam.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/weapon/beam.cpp b/code/weapon/beam.cpp index 81f6e6cbcbe..4c1c0abcace 100644 --- a/code/weapon/beam.cpp +++ b/code/weapon/beam.cpp @@ -2654,9 +2654,9 @@ void beam_get_binfo(beam *b, float accuracy, int num_shots, int burst_seed, floa // randomness vm_vec_random_in_sphere(&random_offset, &vmd_zero_vector, 1.f, false, true); random_offset *= scale_factor; - random_offset.xyz.x *= bwi->t5info.start_pos_rand.xyz.x; - random_offset.xyz.y *= bwi->t5info.start_pos_rand.xyz.y; - random_offset.xyz.z *= bwi->t5info.start_pos_rand.xyz.z; + random_offset.xyz.x *= bwi->t5info.end_pos_rand.xyz.x; + random_offset.xyz.y *= bwi->t5info.end_pos_rand.xyz.y; + random_offset.xyz.z *= bwi->t5info.end_pos_rand.xyz.z; offset += random_offset; // rotate From 01bb406b3d13143763e06ca1e21bef0d8b30dbbc Mon Sep 17 00:00:00 2001 From: BMagnu <6238428+BMagnu@users.noreply.github.com> Date: Fri, 31 May 2024 21:53:39 +0900 Subject: [PATCH 31/63] Allow overriding low-level keypresses (#6174) * Clean unneeded properties * properly queue-ify keys * Refactor key checking to use functions instead of globals * Update hooks and cache checked-against key * Documentation and Naming * match tilde * only remove control not debug --- code/controlconfig/controlsconfig.cpp | 8 +- code/hud/hudsquadmsg.cpp | 4 +- code/io/key.cpp | 247 ++++++++++++++------------ code/io/key.h | 7 +- code/io/keycontrol.cpp | 16 +- code/menuui/credits.cpp | 2 +- code/model/modelinterp.cpp | 2 +- code/network/multi_voice.cpp | 4 +- code/playerman/playercontrol.cpp | 2 +- code/scripting/global_hooks.cpp | 20 ++- code/scripting/global_hooks.h | 4 +- code/scripting/hook_conditions.cpp | 33 ++++ code/scripting/hook_conditions.h | 5 + code/scripting/scripting.cpp | 73 +++++++- code/scripting/scripting.h | 1 + code/ui/checkbox.cpp | 2 +- code/ui/radio.cpp | 2 +- 17 files changed, 281 insertions(+), 151 deletions(-) diff --git a/code/controlconfig/controlsconfig.cpp b/code/controlconfig/controlsconfig.cpp index c2e6a3f9e73..a372ec377c7 100644 --- a/code/controlconfig/controlsconfig.cpp +++ b/code/controlconfig/controlsconfig.cpp @@ -2091,7 +2091,7 @@ int control_config_bind_key_on_frame(int ctrl, selItem item, bool API_Access) } if ((ctrl == BANK_WHEN_PRESSED || ctrl == GLIDE_WHEN_PRESSED) && (Last_key >= 0) && (k <= 0) && - !keyd_pressed[Last_key]) { + !key_is_pressed(Last_key)) { // If the selected cc_item is BANK_WHEN_PRESSED or GLIDE_WHEN_PRESSED, and // If the polled key is a modifier, and // k was consumed, and @@ -2820,12 +2820,12 @@ int check_control_used(int id, int key) // check what current modifiers are pressed mask = 0; - if (keyd_pressed[KEY_LSHIFT] || key_down_count(KEY_LSHIFT) || keyd_pressed[KEY_RSHIFT] || key_down_count(KEY_RSHIFT)) { + if (key_is_pressed(KEY_LSHIFT, true) || key_is_pressed(KEY_RSHIFT, true)) { // Any shift key is pressed, add KEY_SHIFTED mask mask |= KEY_SHIFTED; } - if (keyd_pressed[KEY_LALT] || key_down_count(KEY_LALT) || keyd_pressed[KEY_RALT] || key_down_count(KEY_RALT)) { + if (key_is_pressed(KEY_LALT, true) || key_is_pressed(KEY_RALT, true)) { // Any alt key is pressed, add KEY_ALTED to the mask mask |= KEY_ALTED; } @@ -2841,7 +2841,7 @@ int check_control_used(int id, int key) z &= KEY_MASK; - if (keyd_pressed[z] || key_down_count(z)) { + if (key_is_pressed(z, true)) { // Key combo is pressed, control activated control_used(id); return 1; diff --git a/code/hud/hudsquadmsg.cpp b/code/hud/hudsquadmsg.cpp index 6f216bb92da..82964eba021 100644 --- a/code/hud/hudsquadmsg.cpp +++ b/code/hud/hudsquadmsg.cpp @@ -508,7 +508,7 @@ int hud_squadmsg_read_key( int k ) // after messaging is over. Return true for a while. if ( !timestamp_elapsed(Msg_eat_key_timestamp) ) { for (i = 0; i < num_keys_used; i++ ) { - if ( keyd_pressed[keys_used[i]] ) + if ( key_is_pressed(keys_used[i]) ) return 1; } } @@ -524,7 +524,7 @@ int hud_squadmsg_read_key( int k ) key_found = 1; } - if ( keyd_pressed[k] ) { + if ( key_is_pressed(k) ) { key_found = 1; } diff --git a/code/io/key.cpp b/code/io/key.cpp index 05dbdf3c511..b10c3d629a7 100644 --- a/code/io/key.cpp +++ b/code/io/key.cpp @@ -21,27 +21,24 @@ #define THREADED // to use the proper set of macros #include "osapi/osapi.h" - -#define KEY_BUFFER_SIZE 16 - //-------- Variable accessed by outside functions --------- -ubyte keyd_buffer_type; // 0=No buffer, 1=buffer ASCII, 2=buffer scans -ubyte keyd_repeat; -uint keyd_last_pressed; -uint keyd_last_released; -ubyte keyd_pressed[NUM_KEYS]; -int keyd_time_when_last_pressed; +bool key_allow_repeat; typedef struct keyboard { - ushort keybuffer[KEY_BUFFER_SIZE]; - uint time_pressed[KEY_BUFFER_SIZE]; - uint TimeKeyWentDown[NUM_KEYS]; - uint TimeKeyHeldDown[NUM_KEYS]; - uint TimeKeyDownChecked[NUM_KEYS]; - uint NumDowns[NUM_KEYS]; - uint NumUps[NUM_KEYS]; - int down_check[NUM_KEYS]; // nonzero if has been pressed yet this mission - uint keyhead, keytail; + enum class key_state : uint8_t { RELEASED, PRESSED, PRESSED_OVERRIDDEN }; + std::array state; + SCP_queue key_queue; + + //The time that the key was pressed. Tracks actual presses, even those overridden + uint TimeKeyWentDown[NUM_KEYS]; + + //The cumulative time that the key has been held down. + //This explicitly excludes any time the button has been down in the current press if the button is pressed. + uint TimeKeyHeldDown[NUM_KEYS]; + uint TimeKeyDownChecked[NUM_KEYS]; + uint NumDowns[NUM_KEYS]; + uint NumUps[NUM_KEYS]; + int down_check[NUM_KEYS]; // nonzero if has been pressed yet this mission } keyboard; keyboard key_data; @@ -299,21 +296,15 @@ void key_flush() SDL_LockMutex( key_lock ); - key_data.keyhead = key_data.keytail = 0; - //Clear the keyboard buffer - for (i=0; i= KEY_BUFFER_SIZE ) n=0; - return n; -} - // Returns 1 if character waiting... 0 otherwise -int key_checkch() +bool key_checkch() { - int is_one_waiting = 0; - if ( !key_inited ) return 0; - SDL_LockMutex( key_lock ); + SDL_LockMutex( key_lock ); - if (key_data.keytail != key_data.keyhead){ - is_one_waiting = 1; - } + bool is_one_waiting = !key_data.key_queue.empty(); SDL_UnlockMutex( key_lock ); @@ -366,18 +343,23 @@ int key_inkey() SDL_LockMutex( key_lock ); - if (key_data.keytail!=key_data.keyhead) { - key = key_data.keybuffer[key_data.keyhead]; - key_data.keyhead = add_one(key_data.keyhead); + if (!key_data.key_queue.empty()) { + key = key_data.key_queue.front(); + key_data.key_queue.pop(); } - SDL_UnlockMutex( key_lock ); + SDL_UnlockMutex( key_lock ); Current_key_down = key; return key; } +bool key_is_pressed(int keycode, bool include_since_last_count) { + Assertion(keycode >= 0 && keycode < NUM_KEYS, "Checked status for invalid keycode!"); + return key_data.state[keycode] == keyboard::key_state::PRESSED || (include_since_last_count && key_down_count(keycode) > 0); +} + // If not installed, uses BIOS and returns getch(); // Else returns pending key (or waits for one if none waiting). int key_getch() @@ -403,20 +385,20 @@ uint key_get_shift_status() SDL_LockMutex( key_lock ); - if ( keyd_pressed[KEY_LSHIFT] || keyd_pressed[KEY_RSHIFT] ) + if ( key_is_pressed(KEY_LSHIFT) || key_is_pressed(KEY_RSHIFT) ) shift_status |= KEY_SHIFTED; - if ( keyd_pressed[KEY_LALT] || keyd_pressed[KEY_RALT] ) + if ( key_is_pressed(KEY_LALT) || key_is_pressed(KEY_RALT) ) shift_status |= KEY_ALTED; - if ( keyd_pressed[KEY_LCTRL] || keyd_pressed[KEY_RCTRL] ) + if ( key_is_pressed(KEY_LCTRL) || key_is_pressed(KEY_RCTRL) ) shift_status |= KEY_CTRLED; #ifndef NDEBUG - if (keyd_pressed[KEY_DEBUG_KEY]) + if (key_is_pressed(KEY_DEBUG_KEY)) shift_status |= KEY_DEBUGGED; #else - if (keyd_pressed[KEY_DEBUG_KEY]) { + if (key_is_pressed(KEY_DEBUG_KEY)) { mprintf(("Cheats_enabled = %i, Key_normal_game = %i\n", Cheats_enabled, Key_normal_game)); if ((Cheats_enabled) && Key_normal_game) { mprintf(("Debug key\n")); @@ -431,11 +413,8 @@ uint key_get_shift_status() // Returns amount of time key (specified by "code") has been down since last call. // Returns float, unlike key_down_time() which returns a fix. -float key_down_timef(uint scancode) +float key_down_timef(uint scancode) { - uint time_down, time; - uint delta_time; - if ( !key_inited ) { return 0.0f; } @@ -446,13 +425,14 @@ float key_down_timef(uint scancode) SDL_LockMutex( key_lock ); - time = timer_get_milliseconds(); - delta_time = time - key_data.TimeKeyDownChecked[scancode]; + uint time = timer_get_milliseconds(); + uint last_check_time = key_data.TimeKeyDownChecked[scancode]; + uint delta_time = time - last_check_time; key_data.TimeKeyDownChecked[scancode] = time; if ( delta_time <= 1 ) { - key_data.TimeKeyWentDown[scancode] = time; - if (keyd_pressed[scancode]) { + key_data.TimeKeyHeldDown[scancode] = 0; + if (key_is_pressed(scancode)) { SDL_UnlockMutex( key_lock ); return 1.0f; } else { @@ -461,13 +441,13 @@ float key_down_timef(uint scancode) } } - if ( !keyd_pressed[scancode] ) { - time_down = key_data.TimeKeyHeldDown[scancode]; - key_data.TimeKeyHeldDown[scancode] = 0; - } else { - time_down = time - key_data.TimeKeyWentDown[scancode]; - key_data.TimeKeyWentDown[scancode] = time; + uint time_down = key_data.TimeKeyHeldDown[scancode]; + if ( key_is_pressed(scancode) ) { + //Since the stored time only updates on button release and reset on this check, + //the time the button has been held down this time needs to be added + time_down += time - MAX(key_data.TimeKeyWentDown[scancode], last_check_time); } + key_data.TimeKeyHeldDown[scancode] = 0; SDL_UnlockMutex( key_lock ); @@ -497,7 +477,7 @@ int key_down_count(int scancode) //void key_mark( uint code, int state ) void key_mark( uint code, int state, uint latency ) { - uint scancode, breakbit, temp, event_time; + uint scancode, breakbit, event_time; ushort keycode; if ( !key_inited ) return; @@ -520,70 +500,116 @@ void key_mark( uint code, int state, uint latency ) if (breakbit) { // Key going up - keyd_last_released = scancode; - keyd_pressed[scancode] = 0; - key_data.NumUps[scancode]++; - - // What is the point of this code? "temp" is never used! - temp = 0; - temp |= keyd_pressed[KEY_LSHIFT] || keyd_pressed[KEY_RSHIFT]; - temp |= keyd_pressed[KEY_LALT] || keyd_pressed[KEY_RALT]; - temp |= keyd_pressed[KEY_LCTRL] || keyd_pressed[KEY_RCTRL]; - temp |= keyd_pressed[KEY_DEBUG_KEY]; - - if (event_time < key_data.TimeKeyWentDown[scancode]) { - key_data.TimeKeyHeldDown[scancode] = 0; - } else { - key_data.TimeKeyHeldDown[scancode] += event_time - key_data.TimeKeyWentDown[scancode]; + + int time_held = 0; + if (event_time >= key_data.TimeKeyWentDown[scancode]) { + //If the suspected key lift is "before" the key was pressed (i.e. fluctuating latency) we don't add any time to the pressed time since last poll + time_held = event_time - key_data.TimeKeyWentDown[scancode]; } Current_key_down = scancode; - if ( keyd_pressed[KEY_LSHIFT] || keyd_pressed[KEY_RSHIFT] ) { + if ( key_is_pressed(KEY_LSHIFT) || key_is_pressed(KEY_RSHIFT) ) { Current_key_down |= KEY_SHIFTED; } - if ( keyd_pressed[KEY_LALT] || keyd_pressed[KEY_RALT] ) { + if ( key_is_pressed(KEY_LALT) || key_is_pressed(KEY_RALT) ) { Current_key_down |= KEY_ALTED; } - if ( keyd_pressed[KEY_LCTRL] || keyd_pressed[KEY_RCTRL] ) { + if ( key_is_pressed(KEY_LCTRL) || key_is_pressed(KEY_RCTRL) ) { Current_key_down |= KEY_CTRLED; } +#ifndef NDEBUG + if ( key_is_pressed(KEY_DEBUG_KEY) ) { + Current_key_down |= KEY_DEBUGGED; + } +#else + if ( key_is_pressed(KEY_DEBUG_KEY) ) { + mprintf(("Cheats_enabled = %i, Key_normal_game = %i\n", Cheats_enabled, Key_normal_game)); + if (Cheats_enabled && Key_normal_game) { + Current_key_down |= KEY_DEBUGGED1; + } + } + +#endif + if (scripting::hooks::OnKeyReleased->isActive()) { - scripting::hooks::OnKeyReleased->run(scripting::hook_param_list( - scripting::hook_param("Key", 's', textify_scancode_universal(Current_key_down)))); + scripting::hooks::OnKeyReleased->run(scripting::hooks::KeyPressConditions{ static_cast(scancode) }, + scripting::hook_param_list( + scripting::hook_param("Key", 's', textify_scancode_universal(Current_key_down)), + scripting::hook_param("RawKey", 's', textify_scancode_universal(scancode)), + scripting::hook_param("TimeHeld", 'i', time_held), + scripting::hook_param("WasOverridden", 'b', key_data.state[scancode] == keyboard::key_state::PRESSED_OVERRIDDEN) + )); } + + // Don't increment counters if was overridden + if (key_data.state[scancode] == keyboard::key_state::PRESSED) { + key_data.NumUps[scancode]++; + key_data.TimeKeyHeldDown[scancode] += time_held; + } + + key_data.state[scancode] = keyboard::key_state::RELEASED; } else { // Key going down - keyd_last_pressed = scancode; - keyd_time_when_last_pressed = event_time; - if (!keyd_pressed[scancode]) { + if (!key_is_pressed(scancode)) { // First time down key_data.TimeKeyWentDown[scancode] = event_time; - keyd_pressed[scancode] = 1; - key_data.NumDowns[scancode]++; - key_data.down_check[scancode]++; //WMC - For scripting Current_key_down = scancode; - if ( keyd_pressed[KEY_LSHIFT] || keyd_pressed[KEY_RSHIFT] ) { + if ( key_is_pressed(KEY_LSHIFT) || key_is_pressed(KEY_RSHIFT) ) { Current_key_down |= KEY_SHIFTED; } - if ( keyd_pressed[KEY_LALT] || keyd_pressed[KEY_RALT] ) { + if ( key_is_pressed(KEY_LALT) || key_is_pressed(KEY_RALT) ) { Current_key_down |= KEY_ALTED; } - if ( keyd_pressed[KEY_LCTRL] || keyd_pressed[KEY_RCTRL] ) { + if ( key_is_pressed(KEY_LCTRL) || key_is_pressed(KEY_RCTRL) ) { Current_key_down |= KEY_CTRLED; } +#ifndef NDEBUG + if ( key_is_pressed(KEY_DEBUG_KEY) ) { + Current_key_down |= KEY_DEBUGGED; + } +#else + if ( key_is_pressed(KEY_DEBUG_KEY) ) { + mprintf(("Cheats_enabled = %i, Key_normal_game = %i\n", Cheats_enabled, Key_normal_game)); + if (Cheats_enabled && Key_normal_game) { + Current_key_down |= KEY_DEBUGGED1; + } + } + +#endif + + bool overrideKey = false; if (scripting::hooks::OnKeyPressed->isActive()) { - scripting::hooks::OnKeyPressed->run(scripting::hook_param_list( - scripting::hook_param("Key", 's', textify_scancode_universal(Current_key_down)))); + scripting::hooks::OnKeyPressed->run(scripting::hooks::KeyPressConditions{ static_cast(scancode) }, + scripting::hook_param_list( + scripting::hook_param("Key", 's', textify_scancode_universal(Current_key_down)), + scripting::hook_param("RawKey", 's', textify_scancode_universal(scancode)) + )); + + overrideKey = scripting::hooks::OnKeyPressed->isOverride(scripting::hooks::KeyPressConditions{ static_cast(scancode) }, + scripting::hook_param_list( + scripting::hook_param("Key", 's', textify_scancode_universal(Current_key_down)), + scripting::hook_param("RawKey", 's', textify_scancode_universal(scancode)) + )); + } + + if (overrideKey) { + key_data.state[scancode] = keyboard::key_state::PRESSED_OVERRIDDEN; + scancode = 0xAA; //Skip queueing this key } - } else if (!keyd_repeat) { + else { + key_data.state[scancode] = keyboard::key_state::PRESSED; + key_data.NumDowns[scancode]++; + key_data.down_check[scancode]++; + } + } else if (!key_allow_repeat) { // Don't buffer repeating key if repeat mode is off scancode = 0xAA; } @@ -591,24 +617,24 @@ void key_mark( uint code, int state, uint latency ) if ( scancode!=0xAA ) { keycode = (unsigned short)scancode; - if ( keyd_pressed[KEY_LSHIFT] || keyd_pressed[KEY_RSHIFT] ) { + if ( key_is_pressed(KEY_LSHIFT) || key_is_pressed(KEY_RSHIFT) ) { keycode |= KEY_SHIFTED; } - if ( keyd_pressed[KEY_LALT] || keyd_pressed[KEY_RALT] ) { + if ( key_is_pressed(KEY_LALT) || key_is_pressed(KEY_RALT) ) { keycode |= KEY_ALTED; } - if ( keyd_pressed[KEY_LCTRL] || keyd_pressed[KEY_RCTRL] ) { + if ( key_is_pressed(KEY_LCTRL) || key_is_pressed(KEY_RCTRL) ) { keycode |= KEY_CTRLED; } #ifndef NDEBUG - if ( keyd_pressed[KEY_DEBUG_KEY] ) { + if ( key_is_pressed(KEY_DEBUG_KEY) ) { keycode |= KEY_DEBUGGED; } #else - if ( keyd_pressed[KEY_DEBUG_KEY] ) { + if ( key_is_pressed(KEY_DEBUG_KEY) ) { mprintf(("Cheats_enabled = %i, Key_normal_game = %i\n", Cheats_enabled, Key_normal_game)); if (Cheats_enabled && Key_normal_game) { keycode |= KEY_DEBUGGED1; @@ -618,14 +644,7 @@ void key_mark( uint code, int state, uint latency ) #endif if ( keycode ) { - temp = key_data.keytail+1; - if ( temp >= KEY_BUFFER_SIZE ) temp=0; - - if (temp!=key_data.keyhead) { - key_data.keybuffer[key_data.keytail] = keycode; - key_data.time_pressed[key_data.keytail] = keyd_time_when_last_pressed; - key_data.keytail = temp; - } + key_data.key_queue.push(keycode); } } } @@ -659,9 +678,7 @@ void key_init() FillSDLArray(); - keyd_time_when_last_pressed = timer_get_milliseconds(); - keyd_buffer_type = 1; - keyd_repeat = 1; + key_allow_repeat = true; // Clear the keyboard array key_flush(); diff --git a/code/io/key.h b/code/io/key.h index 6e5fd91f86d..37a9fd17771 100644 --- a/code/io/key.h +++ b/code/io/key.h @@ -20,9 +20,6 @@ const size_t SIZE_OF_ASCII_TABLE = 128; extern int shifted_ascii_table[SIZE_OF_ASCII_TABLE]; extern int ascii_table[SIZE_OF_ASCII_TABLE]; -extern ubyte keyd_pressed[NUM_KEYS]; - - // O/S level hooks... void key_init(); void key_level_init(); @@ -39,12 +36,14 @@ SDL_Scancode fs2_to_sdl( int scancode ); int key_to_ascii(int keycode ); int key_inkey(); +bool key_is_pressed(int keycode, bool include_since_last_count = false); + // global flag that will enable/disable the backspace key from stopping execution //extern int Backspace_debug; uint key_get_shift_status(); int key_down_count(int scancode); -int key_checkch(); +bool key_checkch(); extern SCP_string CheatUsed; extern int Cheats_enabled; diff --git a/code/io/keycontrol.cpp b/code/io/keycontrol.cpp index 781704b167b..422ce43ffee 100644 --- a/code/io/keycontrol.cpp +++ b/code/io/keycontrol.cpp @@ -1265,17 +1265,17 @@ void process_debug_keys(int k) case KEY_PADMINUS: { int init_flag = 0; - if ( keyd_pressed[KEY_1] ) { + if ( key_is_pressed(KEY_1) ) { init_flag = 1; HUD_color_red -= 4; } - if ( keyd_pressed[KEY_2] ) { + if ( key_is_pressed(KEY_2) ) { init_flag = 1; HUD_color_green -= 4; - } + } - if ( keyd_pressed[KEY_3] ) { + if ( key_is_pressed(KEY_3) ) { init_flag = 1; HUD_color_blue -= 4; } @@ -1294,17 +1294,17 @@ void process_debug_keys(int k) case KEY_PADPLUS: { int init_flag = 0; - if ( keyd_pressed[KEY_1] ) { + if ( key_is_pressed(KEY_1) ) { init_flag = 1; HUD_color_red += 4; } - if ( keyd_pressed[KEY_2] ) { + if ( key_is_pressed(KEY_2) ) { init_flag = 1; HUD_color_green += 4; } - if ( keyd_pressed[KEY_3] ) { + if ( key_is_pressed(KEY_3) ) { init_flag = 1; HUD_color_blue += 4; } @@ -1458,7 +1458,7 @@ void process_player_ship_keys(int k) // moved this line to beginning of function since hotkeys now encompass // F5 - F12. We can return after using F11 as a hotkey. ppsk_hotkeys(masked_k); - if (keyd_pressed[KEY_DEBUG_KEY]){ + if (key_is_pressed(KEY_DEBUG_KEY)){ return; } diff --git a/code/menuui/credits.cpp b/code/menuui/credits.cpp index fe65a44540a..c5e3c068048 100644 --- a/code/menuui/credits.cpp +++ b/code/menuui/credits.cpp @@ -875,7 +875,7 @@ void credits_do_frame(float /*frametime*/) Credits_last_time = temp_time; float fl_frametime = i2fl(Credits_frametime) / 1000.f; - if (keyd_pressed[KEY_LSHIFT]) { + if (key_is_pressed(KEY_LSHIFT)) { Credit_position -= fl_frametime * Credits_scroll_rate * 4.0f; } else { Credit_position -= fl_frametime * Credits_scroll_rate; diff --git a/code/model/modelinterp.cpp b/code/model/modelinterp.cpp index 00b2ce00ce2..2a96c3728cb 100644 --- a/code/model/modelinterp.cpp +++ b/code/model/modelinterp.cpp @@ -824,7 +824,7 @@ DCF_BOOL( Arcs, Interp_lightning ) int interp_box_offscreen( vec3d *min, vec3d *max ) { - if ( keyd_pressed[KEY_LSHIFT] ) { + if ( key_is_pressed(KEY_LSHIFT) ) { return IBOX_ALL_ON; } diff --git a/code/network/multi_voice.cpp b/code/network/multi_voice.cpp index 67be34a0569..73ca67c5fcf 100644 --- a/code/network/multi_voice.cpp +++ b/code/network/multi_voice.cpp @@ -742,12 +742,12 @@ int multi_voice_keydown() // if we're pre-game, we should just be checking the keyboard bitflags if(!(Game_mode & GM_IN_MISSION)){ - return (keyd_pressed[MULTI_VOICE_KEY] && !(keyd_pressed[KEY_LSHIFT] || keyd_pressed[KEY_RSHIFT])) ? 1 : 0; + return (key_is_pressed(MULTI_VOICE_KEY) && !(key_is_pressed(KEY_LSHIFT) || key_is_pressed(KEY_RSHIFT))) ? 1 : 0; } // in-mission, paused - treat just like any other "chattable" screen. if(gameseq_get_state() == GS_STATE_MULTI_PAUSED){ - return (keyd_pressed[MULTI_VOICE_KEY] && !(keyd_pressed[KEY_LSHIFT] || keyd_pressed[KEY_RSHIFT])) ? 1 : 0; + return (key_is_pressed(MULTI_VOICE_KEY) && !(key_is_pressed(KEY_LSHIFT) || key_is_pressed(KEY_RSHIFT))) ? 1 : 0; } // ingame, unpaused, rely on the multi-messaging system (ingame) diff --git a/code/playerman/playercontrol.cpp b/code/playerman/playercontrol.cpp index 6fb8a77e905..10f730df7f4 100644 --- a/code/playerman/playercontrol.cpp +++ b/code/playerman/playercontrol.cpp @@ -841,7 +841,7 @@ void read_keyboard_controls( control_info * ci, float frame_time, physics_info * // for debugging, check to see if the debug key is down -- if so, make fire the debug laser instead #ifndef NDEBUG - if ( keyd_pressed[KEY_DEBUG_KEY] ) { + if ( key_is_pressed(KEY_DEBUG_KEY) ) { ci->fire_debug_count = ci->fire_primary_count; ci->fire_primary_count = 0; } diff --git a/code/scripting/global_hooks.cpp b/code/scripting/global_hooks.cpp index 37e482a0849..070a03db827 100644 --- a/code/scripting/global_hooks.cpp +++ b/code/scripting/global_hooks.cpp @@ -61,13 +61,23 @@ const std::shared_ptr> OnActionStopped = Hook> OnKeyPressed = Hook<>::Factory("On Key Pressed", - "Invoked whenever a key is pressed.", - { {"Key", "string", "The scancode of the key that has been pressed."} }); +const std::shared_ptr> OnKeyPressed = OverridableHook::Factory("On Key Pressed", + "Invoked whenever a key is pressed. If overridden, FSO behaves as if this key has simply not been pressed. " + "The only thing that FSO will do with this key if overridden is fire the corresponding OnKeyReleased hook once the key is released. " + "Be especially careful if overriding modifier keys (such as Alt and Shift) with this.", + { + {"Key", "string", "The scancode of the key that has been pressed."}, + {"RawKey", "string", "The scancode of the key that has been pressed, without modifiers applied."} + }); -const std::shared_ptr> OnKeyReleased = Hook<>::Factory("On Key Released", +const std::shared_ptr> OnKeyReleased = Hook::Factory("On Key Released", "Invoked whenever a key is released.", - { {"Key", "string", "The scancode of the key that has been released."} }); + { + {"Key", "string", "The scancode of the key that has been pressed."}, + {"RawKey", "string", "The scancode of the key that has been pressed, without modifiers applied."}, + {"TimeHeld", "number", "The time that this key has been held down in milliseconds. Can be 0 if input latency fluctuates."}, + {"WasOverridden", "boolean", "Whether or not the key press corresponding to this release was overridden."} + }); const std::shared_ptr> OnMouseMoved = Hook<>::Factory("On Mouse Moved", "Invoked whenever the mouse is moved.", diff --git a/code/scripting/global_hooks.h b/code/scripting/global_hooks.h index ed40bffaf72..7c623fbd302 100644 --- a/code/scripting/global_hooks.h +++ b/code/scripting/global_hooks.h @@ -20,8 +20,8 @@ extern const std::shared_ptr> OnGameplayStart; extern const std::shared_ptr> OnAction; extern const std::shared_ptr> OnActionStopped; -extern const std::shared_ptr> OnKeyPressed; -extern const std::shared_ptr> OnKeyReleased; +extern const std::shared_ptr> OnKeyPressed; +extern const std::shared_ptr> OnKeyReleased; extern const std::shared_ptr> OnMouseMoved; extern const std::shared_ptr> OnMousePressed; extern const std::shared_ptr> OnMouseReleased; diff --git a/code/scripting/hook_conditions.cpp b/code/scripting/hook_conditions.cpp index 084244d5ef9..f3654a94863 100644 --- a/code/scripting/hook_conditions.cpp +++ b/code/scripting/hook_conditions.cpp @@ -6,6 +6,7 @@ #include "gamesequence/gamesequence.h" #include "ship/ship.h" #include "weapon/weapon.h" +#include "io/key.h" // ---- Hook Condition System Macro and Class defines ---- @@ -18,6 +19,8 @@ decltype(std::declval().argument), decltype(argumentParse(std::declval()))>> \ (documentation, &conditionsClassName::argument, argumentParse, argumentValid)) +extern const char *Scan_code_text_english[]; + namespace scripting { template @@ -94,6 +97,24 @@ static bool conditionObjectIsWeaponDo(fnc_t fnc, const object* objp, const value return false; } +static int conditionCompareRawControl(int keypress, const int& cached_key) { + //For reasons only known to Volition, LCtrl and RCtrl are differentiated in name, while Alt and Shift are not. + //As only the first of these identical names will be matched, replace the R versions with the L versions + int key_down = keypress & KEY_MASK; + switch(key_down) { + case KEY_RALT: + key_down = KEY_LALT; + break; + case KEY_RSHIFT: + key_down = KEY_LSHIFT; + break; + default: + break; + } + + return cached_key == key_down; +} + static SCP_string conditionParseString(const SCP_string& name) { return name; @@ -127,6 +148,14 @@ static int conditionParseObjectType(const SCP_string& name) { return -1; } +static int conditionParseRawControl(const SCP_string& name) { + for (int key = 0; key < NUM_KEYS; key++){ + if (stricmp(Scan_code_text_english[key], name.c_str()) == 0) + return key & KEY_MASK; + } + return -1; +} + // ---- Hook Condition Helpers ---- #define HOOK_CONDITION_SHIPP(classname, documentationAddendum, shipp) \ @@ -272,5 +301,9 @@ HOOK_CONDITIONS_START(ObjectDrawConditions) HOOK_CONDITION(ObjectDrawConditions, "Object type", "Specifies the type of the object that was drawn / drawn from.", drawn_from_objp, conditionParseObjectType, conditionIsObjecttype); HOOK_CONDITIONS_END +HOOK_CONDITIONS_START(KeyPressConditions) + HOOK_CONDITION(KeyPressConditions, "Raw KeyPress", "The key that is pressed, with no consideration for any modifier keys.", keycode, conditionParseRawControl, conditionCompareRawControl); +HOOK_CONDITIONS_END + } } \ No newline at end of file diff --git a/code/scripting/hook_conditions.h b/code/scripting/hook_conditions.h index e6e0ab1c6c6..0d52eb4e8e8 100644 --- a/code/scripting/hook_conditions.h +++ b/code/scripting/hook_conditions.h @@ -129,6 +129,11 @@ struct ObjectDrawConditions { const object* drawn_from_objp; }; +struct KeyPressConditions { + HOOK_DEFINE_CONDITIONS; + int keycode; +}; + } } diff --git a/code/scripting/scripting.cpp b/code/scripting/scripting.cpp index c53732bbdf2..36f38f597b8 100644 --- a/code/scripting/scripting.cpp +++ b/code/scripting/scripting.cpp @@ -239,10 +239,29 @@ static bool global_condition_valid(const script_condition& condition) return false; if (Current_key_down == 0) return false; - // WMC - could be more efficient, but whatever. - if (stricmp(textify_scancode_universal(Current_key_down), condition.condition_string.c_str()) != 0) - return false; - break; + + //Remove key masks that the API does not check against + int key_down_modifier = ~KEY_CTRLED & ~KEY_MASK & Current_key_down; + + //Pretend that debug is the same as cheat + if (key_down_modifier & KEY_DEBUGGED) + key_down_modifier = (key_down_modifier & ~KEY_DEBUGGED) | KEY_DEBUGGED1; + + //For reasons only known to Volition, LCtrl and RCtrl are differentiated in name, while Alt and Shift are not. + //As only the first of these identical names will be matched, replace the R versions with the L versions + int key_down = Current_key_down & KEY_MASK; + switch(key_down) { + case KEY_RALT: + key_down = KEY_LALT; + break; + case KEY_RSHIFT: + key_down = KEY_LSHIFT; + break; + default: + break; + } + + return condition.condition_cached_value == (key_down | key_down_modifier); } case CHC_VERSION: { @@ -328,6 +347,52 @@ int cache_condition(ConditionalType type, const SCP_string& value){ return 0; } } + case CHC_KEYPRESS: + { + int keycode = 0; + //Technically, keys can be also CTRLED and DEBUGGED, but since the API never made a distinction, they will not be cached and filtered later + if (value.find("Cheat") != SCP_string::npos) + { + keycode |= KEY_DEBUGGED1; + } + if (value.find("Alt") != SCP_string::npos) + { + keycode |= KEY_ALTED; + } + if (value.find("Shift") != SCP_string::npos) + { + keycode |= KEY_SHIFTED; + } + + //Now, if Alt / Shift is ONLY the modifer, remove them here. If they are the only key pressed, the modifier still needs to be enabled, but the key also needs matching + SCP_string key_copy = value; + if (key_copy.rfind("Cheat-", 0) == 0){ + key_copy = key_copy.substr(6); + } + if (key_copy.rfind("Alt-", 0) == 0){ + key_copy = key_copy.substr(4); + } + if (key_copy.rfind("Shift-", 0) == 0){ + key_copy = key_copy.substr(6); + } + + bool foundKey = false; + for (int key = 0; key < NUM_KEYS; key++){ + extern const char *Scan_code_text_english[]; + if (stricmp(Scan_code_text_english[key], key_copy.c_str()) == 0) { + keycode |= key & KEY_MASK; + foundKey = true; + break; + } + } + + if (!foundKey) { + Warning(LOCATION, "No key %s found for %s in conditional hook! The hook will not trigger!", key_copy.c_str(), value.c_str()); + return -1; + } + + return keycode; + } default: return -1; } diff --git a/code/scripting/scripting.h b/code/scripting/scripting.h index 077174bc6d9..581cf143701 100644 --- a/code/scripting/scripting.h +++ b/code/scripting/scripting.h @@ -101,6 +101,7 @@ struct script_condition // CHC_STATE, CHC_OBJECTTYPE - stores the value of enum matching the name requested by the condition string. // CHC_SHIPCLASS, CHC_WEAPONCLASS - stores the index of the info object requested by the condition // CHC_VERSION, CHC_APPLICATION - stores validity of the check in 1 for true or 0 for false, as the condition will not change after load. + // CHC_KEYPRESS - stores the keycode // see ConditionedHook::AddCondition for exact implimentation int condition_cached_value; }; diff --git a/code/ui/checkbox.cpp b/code/ui/checkbox.cpp index 12ef5216200..60be870bb14 100644 --- a/code/ui/checkbox.cpp +++ b/code/ui/checkbox.cpp @@ -156,7 +156,7 @@ void UI_CHECKBOX::process(int focus) position = 2; if (focus) - if ( (oldposition == 2) && (keyd_pressed[KEY_SPACEBAR] || keyd_pressed[KEY_ENTER]) ) + if ( (oldposition == 2) && (key_is_pressed(KEY_SPACEBAR) || key_is_pressed(KEY_ENTER)) ) position = 2; pressed_down = 0; diff --git a/code/ui/radio.cpp b/code/ui/radio.cpp index 086ad345a73..ab10e9f11ac 100644 --- a/code/ui/radio.cpp +++ b/code/ui/radio.cpp @@ -167,7 +167,7 @@ void UI_RADIO::process(int focus) position = 2; if (focus) - if ( (oldposition == 2) && (keyd_pressed[KEY_SPACEBAR] || keyd_pressed[KEY_ENTER]) ) + if ( (oldposition == 2) && (key_is_pressed(KEY_SPACEBAR) || key_is_pressed(KEY_ENTER)) ) position = 2; pressed_down = 0; From 6a83875d93b6a7c38550452b5bd96b99d4c48829 Mon Sep 17 00:00:00 2001 From: BMagnu <6238428+BMagnu@users.noreply.github.com> Date: Fri, 31 May 2024 21:53:50 +0900 Subject: [PATCH 32/63] Fix effect framebuffer blit (#6175) --- code/graphics/opengl/gropengldeferred.cpp | 6 ++-- code/graphics/opengl/gropengldraw.cpp | 36 ++++------------------- code/graphics/opengl/gropengldraw.h | 2 -- code/graphics/opengl/gropengltnl.cpp | 4 +-- 4 files changed, 10 insertions(+), 38 deletions(-) diff --git a/code/graphics/opengl/gropengldeferred.cpp b/code/graphics/opengl/gropengldeferred.cpp index fc03d4755c6..3d46b066c7c 100644 --- a/code/graphics/opengl/gropengldeferred.cpp +++ b/code/graphics/opengl/gropengldeferred.cpp @@ -68,7 +68,7 @@ void gr_opengl_deferred_lighting_begin(bool clearNonColorBufs) Deferred_lighting = true; GL_state.ColorMask(true, true, true, true); - GLenum buffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3, GL_COLOR_ATTACHMENT4, GL_COLOR_ATTACHMENT6 }; + GLenum buffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3, GL_COLOR_ATTACHMENT4, GL_COLOR_ATTACHMENT5 }; if (Cmdline_msaa_enabled > 0) { //Ensure MSAA Mode if necessary @@ -226,7 +226,7 @@ void gr_opengl_deferred_lighting_finish() opengl_shader_set_current(gr_opengl_maybe_create_shader(SDR_TYPE_DEFERRED_LIGHTING, 0)); // Render on top of the composite buffer texture - glDrawBuffer(GL_COLOR_ATTACHMENT6); + glDrawBuffer(GL_COLOR_ATTACHMENT5); glReadBuffer(GL_COLOR_ATTACHMENT4); glBlitFramebuffer(0, 0, @@ -598,7 +598,7 @@ void gr_opengl_deferred_lighting_finish() else { // Transfer the resolved lighting back to the color texture // TODO: Maybe this could be improved so that it doesn't require the copy back operation? - glReadBuffer(GL_COLOR_ATTACHMENT6); + glReadBuffer(GL_COLOR_ATTACHMENT5); glBlitFramebuffer(0, 0, gr_screen.max_w, diff --git a/code/graphics/opengl/gropengldraw.cpp b/code/graphics/opengl/gropengldraw.cpp index 84a784f82dc..34183963b35 100644 --- a/code/graphics/opengl/gropengldraw.cpp +++ b/code/graphics/opengl/gropengldraw.cpp @@ -44,7 +44,6 @@ GLuint Scene_specular_texture_ms; GLuint Scene_emissive_texture_ms; GLuint Scene_composite_texture; GLuint Scene_luminance_texture; -GLuint Scene_effect_texture; GLuint Scene_depth_texture; GLuint Scene_depth_texture_ms; GLuint Cockpit_depth_texture; @@ -105,7 +104,6 @@ void opengl_setup_scene_textures() Scene_ldr_texture = 0; Scene_color_texture = 0; - Scene_effect_texture = 0; Scene_depth_texture = 0; return; } @@ -252,7 +250,7 @@ void opengl_setup_scene_textures() glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, Scene_texture_width, Scene_texture_height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL); opengl_set_object_label(GL_TEXTURE, Scene_composite_texture, "Scene Composite texture"); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT6, GL_TEXTURE_2D, Scene_composite_texture, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT5, GL_TEXTURE_2D, Scene_composite_texture, 0); //Set up luminance texture (used as input for FXAA) // also used as a light accumulation buffer during the deferred pass @@ -271,24 +269,6 @@ void opengl_setup_scene_textures() glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, Scene_texture_width, Scene_texture_height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL); opengl_set_object_label(GL_TEXTURE, Scene_luminance_texture, "Scene Luminance texture"); - // setup effect texture - glGenTextures(1, &Scene_effect_texture); - - GL_state.Texture.SetActiveUnit(0); - GL_state.Texture.SetTarget(GL_TEXTURE_2D); - GL_state.Texture.Enable(Scene_effect_texture); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); - - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, Scene_texture_width, Scene_texture_height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL); - opengl_set_object_label(GL_TEXTURE, Scene_effect_texture, "Scene Effect texture"); - - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT5, GL_TEXTURE_2D, Scene_effect_texture, 0); - // setup cockpit depth texture glGenTextures(1, &Cockpit_depth_texture); @@ -356,9 +336,6 @@ void opengl_setup_scene_textures() glDeleteTextures(1, &Scene_emissive_texture); Scene_emissive_texture = 0; - glDeleteTextures(1, &Scene_effect_texture); - Scene_effect_texture = 0; - glDeleteTextures(1, &Scene_depth_texture); Scene_depth_texture = 0; @@ -667,11 +644,6 @@ void opengl_scene_texture_shutdown() Scene_emissive_texture = 0; } - if ( Scene_effect_texture ) { - glDeleteTextures(1, &Scene_effect_texture); - Scene_effect_texture = 0; - } - if ( Scene_depth_texture ) { glDeleteTextures(1, &Scene_depth_texture); Scene_depth_texture = 0; @@ -729,7 +701,7 @@ void gr_opengl_scene_texture_begin() glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } else { - GLenum buffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3, GL_COLOR_ATTACHMENT4, GL_COLOR_ATTACHMENT6 }; + GLenum buffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3, GL_COLOR_ATTACHMENT4, GL_COLOR_ATTACHMENT5 }; glDrawBuffers(6, buffers); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); @@ -832,6 +804,8 @@ void gr_opengl_copy_effect_texture() return; } + //Make sure we're reading from the up-to-date color texture + glReadBuffer(GL_COLOR_ATTACHMENT0); glDrawBuffer(GL_COLOR_ATTACHMENT5); glBlitFramebuffer(0, 0, gr_screen.max_w, gr_screen.max_h, 0, 0, gr_screen.max_w, gr_screen.max_h, GL_COLOR_BUFFER_BIT, GL_NEAREST); glDrawBuffer(GL_COLOR_ATTACHMENT0); @@ -1199,7 +1173,7 @@ void gr_opengl_stop_decal_pass() { GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3, GL_COLOR_ATTACHMENT4, - GL_COLOR_ATTACHMENT6, + GL_COLOR_ATTACHMENT5, }; glDrawBuffers(6, buffers2); } diff --git a/code/graphics/opengl/gropengldraw.h b/code/graphics/opengl/gropengldraw.h index 06cce2665d8..17634cd7a37 100644 --- a/code/graphics/opengl/gropengldraw.h +++ b/code/graphics/opengl/gropengldraw.h @@ -31,7 +31,6 @@ extern GLuint Scene_normal_texture_ms; extern GLuint Scene_specular_texture_ms; extern GLuint Scene_emissive_texture_ms; extern GLuint Scene_luminance_texture; -extern GLuint Scene_effect_texture; extern GLuint Scene_composite_texture; extern GLuint Scene_depth_texture; extern GLuint Scene_depth_texture_ms; @@ -156,7 +155,6 @@ extern int Scene_texture_initialized; extern GLuint Scene_color_texture; extern GLuint Scene_ldr_texture; extern GLuint Scene_luminance_texture; -extern GLuint Scene_effect_texture; extern int Scene_texture_width; extern int Scene_texture_height; diff --git a/code/graphics/opengl/gropengltnl.cpp b/code/graphics/opengl/gropengltnl.cpp index 6799e172df4..fe680cd189e 100644 --- a/code/graphics/opengl/gropengltnl.cpp +++ b/code/graphics/opengl/gropengltnl.cpp @@ -959,7 +959,7 @@ void opengl_tnl_set_model_material(model_material *material_info) if (material_info->get_animated_effect() > 0) { if (Scene_framebuffer_in_frame) { - GL_state.Texture.Enable(9, GL_TEXTURE_2D, Scene_effect_texture); + GL_state.Texture.Enable(9, GL_TEXTURE_2D, Scene_composite_texture); glDrawBuffer(GL_COLOR_ATTACHMENT0); } else { GL_state.Texture.Enable(9, GL_TEXTURE_2D, Framebuffer_fallback_texture_id); @@ -1050,7 +1050,7 @@ void opengl_tnl_set_material_distortion(distortion_material* material_info) }); Current_shader->program->Uniforms.setTextureUniform("frameBuffer", 2); - GL_state.Texture.Enable(2, GL_TEXTURE_2D, Scene_effect_texture); + GL_state.Texture.Enable(2, GL_TEXTURE_2D, Scene_composite_texture); Current_shader->program->Uniforms.setTextureUniform("distMap", 3); if (material_info->get_thruster_rendering()) { From b1d4e0a5e2218e03db242d8aa71afbfb970deea2 Mon Sep 17 00:00:00 2001 From: Asteroth Date: Fri, 31 May 2024 09:05:33 -0400 Subject: [PATCH 33/63] better option ordering comparison (#6133) --- code/options/Option.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/code/options/Option.cpp b/code/options/Option.cpp index 9bce6c7f663..ac9cd3e7c67 100644 --- a/code/options/Option.cpp +++ b/code/options/Option.cpp @@ -181,9 +181,10 @@ void OptionBase::setRangeValues(float min, float max) } bool operator<(const OptionBase& lhs, const OptionBase& rhs) { - if (lhs._category < rhs._category) + auto val = stricmp(lhs._category.first, rhs._category.first); + if (val < 0) return true; - if (rhs._category < lhs._category) + if (val > 0) return false; return lhs._importance > rhs._importance; // Importance is sorted from highest to lowest } From 1fa0f43c6f8703c07314f4ff859a3fe166aba7ac Mon Sep 17 00:00:00 2001 From: wookieejedi Date: Fri, 31 May 2024 22:47:03 -0400 Subject: [PATCH 34/63] Exposes the New Flight Cursor Mode to Scripting and Fixed Aiming Setting (#6173) * Exposes the New Flight Cursor Mode to Scripting Thanks to #6136, FSO supports switching between two methods of flying. This PR exposes those settings and values to scripters. Tested and works as expected. * fix aiming setting * add reset position function * cleanup setting pitch and heading * one more cleanup * update to enum support * remove older version * one more fix for setting flight mode --- code/playerman/player.h | 2 + code/playerman/playercontrol.cpp | 12 ++-- code/scripting/api/libs/controls.cpp | 89 ++++++++++++++++++++++++++++ code/scripting/api/objs/enums.cpp | 2 + code/scripting/api/objs/enums.h | 2 + code/ship/ship.cpp | 2 +- 6 files changed, 102 insertions(+), 7 deletions(-) diff --git a/code/playerman/player.h b/code/playerman/player.h index 5a0ce8fb408..4fe8ee6d31e 100644 --- a/code/playerman/player.h +++ b/code/playerman/player.h @@ -234,6 +234,8 @@ enum class FlightMode { }; extern FlightMode Player_flight_mode; +extern float Flight_cursor_extent; +extern float Flight_cursor_deadzone; extern void player_init(); // initialization per level extern void player_level_init(); diff --git a/code/playerman/playercontrol.cpp b/code/playerman/playercontrol.cpp index 10f730df7f4..d2713aeae42 100644 --- a/code/playerman/playercontrol.cpp +++ b/code/playerman/playercontrol.cpp @@ -86,7 +86,7 @@ static SCP_string degrees_display(float val) return out; } -float flight_cursor_extent; +float Flight_cursor_extent; auto FlightCursorExtentOption = options::OptionBuilder("Game.FlightCursorExtent", std::pair{"Flight Cursor Extent", 1846}, @@ -96,11 +96,11 @@ auto FlightCursorExtentOption = options::OptionBuilder("Game.FlightCursor .display(degrees_display) .default_val(0.348f) .level(options::ExpertLevel::Beginner) - .bind_to(&flight_cursor_extent) + .bind_to(&Flight_cursor_extent) .importance(44) .finish(); -float flight_cursor_deadzone; +float Flight_cursor_deadzone; auto FlightCursorDeadzoneOption = options::OptionBuilder("Game.FlightCursorDeadzone", std::pair{"Flight Cursor Deadzone", 1848}, @@ -110,7 +110,7 @@ auto FlightCursorDeadzoneOption = options::OptionBuilder("Game.FlightCurs .display(degrees_display) .default_val(0.02f) .level(options::ExpertLevel::Beginner) - .bind_to(&flight_cursor_deadzone) + .bind_to(&Flight_cursor_deadzone) .importance(43) .finish(); @@ -1028,7 +1028,7 @@ void read_player_controls(object *objp, float frametime) if ((Player_flight_mode == FlightMode::FlightCursor || sip->aims_at_flight_cursor)) { if (Viewer_mode & VM_CAMERA_LOCKED) { - float max_aim_angle = flight_cursor_extent; + float max_aim_angle = Flight_cursor_extent; if (sip->aims_at_flight_cursor) max_aim_angle = sip->flight_cursor_aim_extent; @@ -1043,7 +1043,7 @@ void read_player_controls(object *objp, float frametime) mag = max_aim_angle; } - float deadzone = flight_cursor_deadzone; + float deadzone = Flight_cursor_deadzone; if (mag > deadzone) { float p = Player_flight_cursor.p * ((mag - deadzone) / mag); float h = Player_flight_cursor.h * ((mag - deadzone) / mag); diff --git a/code/scripting/api/libs/controls.cpp b/code/scripting/api/libs/controls.cpp index e10d1ef002f..415b36ff01f 100644 --- a/code/scripting/api/libs/controls.cpp +++ b/code/scripting/api/libs/controls.cpp @@ -12,6 +12,7 @@ #include "gamesequence/gamesequence.h" #include "controlconfig/controlsconfig.h" #include "headtracking/headtracking.h" +#include "playerman/player.h" extern int mouse_inited; @@ -314,6 +315,94 @@ ADE_FUNC(AxisInverted, l_Mouse, "number cid, number axis, boolean inverted", "Ge return ADE_RETURN_FALSE; } +ADE_VIRTVAR(FlightCursorMode, l_Mouse, "enumeration FlightMode", "Flight Mode; uses LE_FLIGHTMODE_* enumerations.", "enumeration", "enumeration flight mode") +{ + enum_h flightmode_arg; + + if (!ade_get_args(L, "*|o", l_Enum.Get(&flightmode_arg))) { + return ade_set_error(L, "o", l_Enum.Set(enum_h())); + } + + if (ADE_SETTING_VAR && flightmode_arg.isValid()) { + switch (flightmode_arg.index) { + case LE_FLIGHTMODE_FLIGHTCURSOR: + Player_flight_mode = FlightMode::FlightCursor; + break; + case LE_FLIGHTMODE_SHIPLOCKED: + Player_flight_mode = FlightMode::ShipLocked; + break; + default: + Warning(LOCATION, "Invalid flight mode index %d in io.FlightCursorMode", flightmode_arg.index); + break; + } + } + + switch (Player_flight_mode) { + case FlightMode::FlightCursor: + return ade_set_args(L, "o", l_Enum.Set(enum_h(LE_FLIGHTMODE_FLIGHTCURSOR))); + case FlightMode::ShipLocked: + return ade_set_args(L, "o", l_Enum.Set(enum_h(LE_FLIGHTMODE_SHIPLOCKED))); + default: + return ade_set_error(L, "o", l_Enum.Set(enum_h())); + } + +} + +ADE_VIRTVAR(FlightCursorExtent, l_Mouse, "number angle", "How far from the center the cursor can go.", "number", "Flight cursor extent in radians") +{ + float extent_angle; + + if (ADE_SETTING_VAR && ade_get_args(L, "*f", &extent_angle)) + { + Flight_cursor_extent = extent_angle; + } + + return ade_set_args(L, "f", Flight_cursor_extent); +} + +ADE_VIRTVAR(FlightCursorDeadzone, l_Mouse, "number angle", "How far from the center the cursor needs to go before registering.", "number", "Flight cursor deadzone in radians") +{ + float deadzone_angle; + + if (ADE_SETTING_VAR && ade_get_args(L, "*f", &deadzone_angle)) + { + Flight_cursor_deadzone = deadzone_angle; + } + + return ade_set_args(L, "f", Flight_cursor_deadzone); +} + +ADE_VIRTVAR(FlightCursorPitch, l_Mouse, "number", "Flight cursor pitch value", "number", "Flight cursor pitch value") +{ + float val_pitch; + + if (ADE_SETTING_VAR && ade_get_args(L, "*f", &val_pitch)) { + Player_flight_cursor.p = val_pitch; + } + + return ade_set_args(L, "f", Player_flight_cursor.p); +} + +ADE_VIRTVAR(FlightCursorHeading, l_Mouse, "number", "Flight cursor heading value", "number", "Flight cursor heading value") +{ + float val_heading; + + if (ADE_SETTING_VAR && ade_get_args(L, "*f", &val_heading)) { + Player_flight_cursor.h = val_heading; + } + + return ade_set_args(L, "f", Player_flight_cursor.h); +} + +ADE_FUNC(resetFlightCursor, l_Mouse, nullptr, "Resets flight cursor position to the center of the screen.", nullptr, nullptr) +{ + SCP_UNUSED(L); + Player_flight_cursor.p = 0.0f; + Player_flight_cursor.h = 0.0f; + + return ADE_RETURN_NIL; +} + ADE_FUNC(setCursorImage, l_Mouse, "string filename", "Sets mouse cursor image, and allows you to lock/unlock the image. (A locked cursor may only be changed with the unlock parameter)", "boolean", "true if successful, false otherwise") { using namespace io::mouse; diff --git a/code/scripting/api/objs/enums.cpp b/code/scripting/api/objs/enums.cpp index 37244ac8efa..65bf16852bf 100644 --- a/code/scripting/api/objs/enums.cpp +++ b/code/scripting/api/objs/enums.cpp @@ -23,6 +23,8 @@ const lua_enum_def_list Enumerations[] = { {"MOUSE_MIDDLE_BUTTON", LE_MOUSE_MIDDLE_BUTTON, MOUSE_MIDDLE_BUTTON, true}, {"MOUSE_X1_BUTTON", LE_MOUSE_X1_BUTTON, MOUSE_X1_BUTTON, true}, {"MOUSE_X2_BUTTON", LE_MOUSE_X2_BUTTON, MOUSE_X2_BUTTON, true}, + {"FLIGHTMODE_FLIGHTCURSOR", LE_FLIGHTMODE_FLIGHTCURSOR, true}, + {"FLIGHTMODE_SHIPLOCKED", LE_FLIGHTMODE_SHIPLOCKED, true}, {"ORDER_ATTACK", LE_ORDER_ATTACK, true}, {"ORDER_ATTACK_ANY", LE_ORDER_ATTACK_ANY, true}, {"ORDER_DEPART", LE_ORDER_DEPART, true}, diff --git a/code/scripting/api/objs/enums.h b/code/scripting/api/objs/enums.h index 5ae909f8958..ca5667945bd 100644 --- a/code/scripting/api/objs/enums.h +++ b/code/scripting/api/objs/enums.h @@ -22,6 +22,8 @@ enum lua_enum : int32_t { LE_MOUSE_MIDDLE_BUTTON, LE_MOUSE_X1_BUTTON, LE_MOUSE_X2_BUTTON, + LE_FLIGHTMODE_FLIGHTCURSOR, + LE_FLIGHTMODE_SHIPLOCKED, LE_ORDER_ATTACK, LE_ORDER_ATTACK_ANY, LE_ORDER_DEPART, diff --git a/code/ship/ship.cpp b/code/ship/ship.cpp index b25700ea601..29ce6f0d724 100644 --- a/code/ship/ship.cpp +++ b/code/ship/ship.cpp @@ -12589,7 +12589,7 @@ int ship_fire_primary(object * obj, int force, bool rollback_shot) if ( pm->n_guns > 0 ) { vec3d predicted_target_pos, plr_to_target_vec; matrix firing_orient = obj->orient; - if (obj == Player_obj && (Player_flight_mode == FlightMode::FlightCursor || sip->aims_at_flight_cursor)) { + if (obj == Player_obj && sip->aims_at_flight_cursor) { vm_angles_2_matrix(&firing_orient, &Player_flight_cursor); firing_orient = firing_orient * obj->orient; } From b831c773dd169c06eb50650a90d31d66209d1546 Mon Sep 17 00:00:00 2001 From: wookieejedi Date: Mon, 3 Jun 2024 21:59:35 -0400 Subject: [PATCH 35/63] Expose Impact Damage Type to Scripting (#6178) * Expose Impact Damage Type to Scripting There's already a sexp that does this, but having the script version for ships is quite useful, too. For example, scripters can set impact damage types for just the player at various difficulties. Tested and works as expected. * always set damage type --- code/scripting/api/objs/ship.cpp | 35 ++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/code/scripting/api/objs/ship.cpp b/code/scripting/api/objs/ship.cpp index 390b039f22d..0d3bf627abf 100644 --- a/code/scripting/api/objs/ship.cpp +++ b/code/scripting/api/objs/ship.cpp @@ -245,6 +245,41 @@ ADE_VIRTVAR(ShieldArmorClass, l_Ship, "string", "Current Armor class of the ship return ade_set_args(L, "s", name); } +ADE_VIRTVAR(ImpactDamageClass, l_Ship, "string", "Current Impact Damage class", "string", "Impact Damage class name, or empty string if none is set") +{ + object_h *objh; + const char* s = nullptr; + const char *name = nullptr; + + if(!ade_get_args(L, "o|s", l_Ship.GetPtr(&objh), &s)) + return ade_set_error(L, "s", ""); + + if(!objh->isValid()) + return ade_set_error(L, "s", ""); + + ship *shipp = &Ships[objh->objp->instance]; + int damage_index = -1; + + if (ADE_SETTING_VAR && s != nullptr) { + for (size_t i = 0; i < Damage_types.size(); i++) { + if (!stricmp(Damage_types[i].name, s)) { + damage_index = (int)i; + break; + } + } + shipp->collision_damage_type_idx = damage_index; + } else { + damage_index = shipp->collision_damage_type_idx; + } + + if (damage_index != -1) + name = Damage_types[damage_index].name; + else + name = ""; + + return ade_set_args(L, "s", name); +} + ADE_VIRTVAR(ArmorClass, l_Ship, "string", "Current Armor class", "string", "Armor class name, or empty string if none is set") { object_h *objh; From 64c17f48301bb67b7a05a6d0bd3d0413aa7a38f3 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Sat, 1 Jun 2024 23:20:10 -0400 Subject: [PATCH 36/63] fix compilation warnings Fixes the few remaining compilation warnings in the current codebase. Also adds comments to the various casting macros for clarity. --- code/graphics/opengl/gropenglopenxr.cpp | 19 ++++++++++++------- code/math/floating.h | 15 +++++++++------ code/network/multi_log.cpp | 4 ++-- code/weapon/weapons.cpp | 8 +++++--- 4 files changed, 28 insertions(+), 18 deletions(-) diff --git a/code/graphics/opengl/gropenglopenxr.cpp b/code/graphics/opengl/gropenglopenxr.cpp index 76a75861804..44972df2ca8 100644 --- a/code/graphics/opengl/gropenglopenxr.cpp +++ b/code/graphics/opengl/gropenglopenxr.cpp @@ -1,5 +1,16 @@ #include "gropenglopenxr.h" +// this block should go before the #includes otherwise MSVC will sometimes warn about APIENTRY redefinition +// (glad.h checks for redefinition of the symbol, but minwindef.h does not) +#ifdef WIN32 + +#define XR_USE_PLATFORM_WIN32 +#include + +#endif +// the other platforms do not go before the #includes because this will cause conflicts from symbols defined in XLib, +// specifically None and Always; see https://stackoverflow.com/questions/22476110/c-compiling-error-including-x11-x-h-x11-xlib-h + #include "io/cursor.h" #include "io/mouse.h" #include "graphics/matrix.h" @@ -10,13 +21,7 @@ #include "graphics/opengl/ShaderProgram.h" #include "osapi/osapi.h" - -#ifdef WIN32 - -#define XR_USE_PLATFORM_WIN32 -#include - -#elif defined __APPLE_CC__ +#if defined __APPLE_CC__ //Not supported diff --git a/code/math/floating.h b/code/math/floating.h index d5fae7637a5..d5bd895be61 100644 --- a/code/math/floating.h +++ b/code/math/floating.h @@ -30,12 +30,15 @@ inline bool fl_is_nan(float fl) { #define fl_sqrt(fl) sqrtf(fl) #define fl_isqrt(fl) (1.0f/sqrtf(fl)) #define fl_abs(fl) fabsf(fl) -#define i2fl(i) (static_cast(i)) -#define fl2i(fl) (static_cast(fl)) -#define fl2ir(fl) (static_cast(fl + (((fl) < 0.0f) ? -0.5f : 0.5f))) -#define f2fl(fx) (static_cast(fx)/65536.0f) -#define f2d(fx) (static_cast(fx)/65536.0) -#define fl2f(fl) (static_cast((fl)*65536.0f)) +#define i2fl(i) (static_cast(i)) // int to float +#define l2d(l) (static_cast(l)) // long to double +#define fl2i(fl) (static_cast(fl)) // float to int +#define d2l(d) (static_cast(d)) // double to long +#define fl2ir(fl) (static_cast(fl + (((fl) < 0.0f) ? -0.5f : 0.5f))) // float to int, rounding +#define d2lr(d) (static_cast(d + (((d) < 0.0) ? -0.5 : 0.5))) // double to long, rounding +#define f2fl(fx) (static_cast(fx)/65536.0f) // fix to float +#define f2d(fx) (static_cast(fx)/65536.0) // fix to double +#define fl2f(fl) (static_cast((fl)*65536.0f)) // float to fix #define fl_tan(fl) tanf(fl) // convert a measurement in degrees to radians diff --git a/code/network/multi_log.cpp b/code/network/multi_log.cpp index 7f7aee5b0b4..cf795adb704 100644 --- a/code/network/multi_log.cpp +++ b/code/network/multi_log.cpp @@ -66,8 +66,8 @@ void multi_log_write_trailer() // write out some info about stuff void multi_log_write_update() { - time_t diff = difftime(time(NULL), Multi_log_open_systime); - time_t hours, mins, seconds; + long diff = d2lr(difftime(time(nullptr), Multi_log_open_systime)); + long hours, mins, seconds; // figure out some time values hours = diff / 3600; diff --git a/code/weapon/weapons.cpp b/code/weapon/weapons.cpp index 4d71f780f08..57dbea70d80 100644 --- a/code/weapon/weapons.cpp +++ b/code/weapon/weapons.cpp @@ -8952,9 +8952,11 @@ void weapon_render(object* obj, model_draw_list *scene) // render the head-on bitmap if appropriate and maybe adjust the main bitmap's alpha if (wip->laser_glow_headon_bitmap.first_frame >= 0) { - float head_alpha = 1.0 - main_bitmap_alpha_mult; + float head_alpha = 1.0f - main_bitmap_alpha_mult; - r = (int)(r * head_alpha); g = (int)(g * head_alpha); b = (int)(b * head_alpha); + r = fl2i(r * head_alpha); + g = fl2i(g * head_alpha); + b = fl2i(b * head_alpha); batching_add_laser( wip->laser_glow_headon_bitmap.first_frame + headon_framenum, @@ -9258,7 +9260,7 @@ void weapon_info::reset() this->spawn_info[i].spawn_chance = 1.f; } - this->lifetime_variation_factor_when_child = 0.2; + this->lifetime_variation_factor_when_child = 0.2f; this->swarm_count = -1; // *Default is 150 -Et1 From 3529e49466e36637f6a1f3c2fe84bf269a92d5a5 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Mon, 3 Jun 2024 23:09:32 -0400 Subject: [PATCH 37/63] utility functions for counting and searching Inspired by #6178, this moves the existing `count_items_with_name()` functions into utility.h and adds additional functions for finding specific items with a given `.name` or, more generally, a given field. The `Damage_types` search has been converted to illustrate how these functions can be used. --- code/globalincs/utility.h | 141 +++++++++++++++++++++++++++++++ code/parse/sexp.cpp | 39 --------- code/scripting/api/objs/ship.cpp | 10 +-- 3 files changed, 144 insertions(+), 46 deletions(-) diff --git a/code/globalincs/utility.h b/code/globalincs/utility.h index 0256344eda3..4a908cabdfc 100644 --- a/code/globalincs/utility.h +++ b/code/globalincs/utility.h @@ -147,4 +147,145 @@ typename T::size_type stringcost(const T& op, const T& input, typename T::size_t return cost; } +template +int count_items_with_name(const char* name, const T* item_array, int num_items) +{ + Assert(name != nullptr && item_array != nullptr); + + int count = 0; + for (int i = 0; i < num_items; ++i) + if (!stricmp(name, item_array[i].name)) + ++count; + + return count; +} + +template +int count_items_with_name(const char* name, const T& item_vector) +{ + Assert(name != nullptr); + + int count = 0; + for (const auto& item : item_vector) + if (!stricmp(name, item.name)) + ++count; + + return count; +} + +template +int count_items_with_scp_string_name(const char* name, const T& item_vector) +{ + Assert(name != nullptr); + + int count = 0; + for (const auto& item : item_vector) + if (!stricmp(name, item.name.c_str())) + ++count; + + return count; +} + +template +int find_item_with_field(const VECTOR_T& item_vector, FIELD_T ITEM_T::* field, const char* str) +{ + Assert(str != nullptr); + + int index = 0; + for (const ITEM_T& item : item_vector) + { + if (!stricmp(item.*field, str)) + return index; + else + ++index; + } + + return -1; +} + +template +int find_item_with_field(const VECTOR_T& item_vector, FIELD_T ITEM_T::* field, const SCP_string& str) +{ + int index = 0; + for (const ITEM_T& item : item_vector) + { + if (lcase_equal(item.*field, str)) + return index; + else + ++index; + } + + return -1; +} + +template +int find_item_with_field(const VECTOR_T& item_vector, FIELD_T ITEM_T::* field, const FIELD_T& search) +{ + int index = 0; + for (const ITEM_T& item : item_vector) + { + if (item.*field == search) + return index; + else + ++index; + } + + return -1; +} + +template +int find_item_with_field(const ITEM_T* item_array, int num_items, FIELD_T ITEM_T::* field, const char* str) +{ + Assert(str != nullptr); + + for (int i = 0; i < num_items; ++i) + if (!stricmp(item_array[i].*field, str)) + return i; + + return -1; +} + +template +int find_item_with_field(const ITEM_T* item_array, int num_items, FIELD_T ITEM_T::* field, const SCP_string& str) +{ + for (int i = 0; i < num_items; ++i) + if (lcase_equal(item_array[i].*field, str)) + return i; + + return -1; +} + +template +int find_item_with_field(const ITEM_T* item_array, int num_items, FIELD_T ITEM_T::* field, const FIELD_T& search) +{ + for (int i = 0; i < num_items; ++i) + if (item_array[i].*field == search) + return i; + + return -1; +} + +template +int find_item_with_name(const VECTOR_T& item_vector, const char* str) +{ + Assert(str != nullptr); + + int index = 0; + for (const auto& item : item_vector) + { + if (!stricmp(item.name, str)) + return index; + else + ++index; + } + + return -1; +} + +template +int find_item_with_name(const ITEM_T* item_array, int num_items, const char* str) +{ + return find_item_with_field(item_array, num_items, &ITEM_T::name, str); +} + #endif diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index 11d33261821..9397b04b195 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -1986,45 +1986,6 @@ int check_operator_argument_count(int count, int op) return 1; } -template -int count_items_with_name(const char *name, const T* item_array, int num_items) -{ - Assert(name != nullptr && item_array != nullptr); - - int count = 0; - for (int i = 0; i < num_items; ++i) - if (!stricmp(name, item_array[i].name)) - ++count; - - return count; -} - -template -int count_items_with_name(const char *name, const T& item_vector) -{ - Assert(name != nullptr); - - int count = 0; - for (const auto &item: item_vector) - if (!stricmp(name, item.name)) - ++count; - - return count; -} - -template -int count_items_with_scp_string_name(const char *name, const T& item_vector) -{ - Assert(name != nullptr); - - int count = 0; - for (const auto &item: item_vector) - if (!stricmp(name, item.name.c_str())) - ++count; - - return count; -} - // helper functions for check_container_value_data_type() bool check_container_data_sexp_arg_type(ContainerType con_type, bool is_string, bool is_number) { diff --git a/code/scripting/api/objs/ship.cpp b/code/scripting/api/objs/ship.cpp index 0d3bf627abf..f33056bbefa 100644 --- a/code/scripting/api/objs/ship.cpp +++ b/code/scripting/api/objs/ship.cpp @@ -21,6 +21,7 @@ #include "wing.h" #include "ai/aigoals.h" +#include "globalincs/utility.h" #include "hud/hudets.h" #include "hud/hudshield.h" #include "mission/missionlog.h" @@ -258,15 +259,10 @@ ADE_VIRTVAR(ImpactDamageClass, l_Ship, "string", "Current Impact Damage class", return ade_set_error(L, "s", ""); ship *shipp = &Ships[objh->objp->instance]; - int damage_index = -1; + int damage_index; if (ADE_SETTING_VAR && s != nullptr) { - for (size_t i = 0; i < Damage_types.size(); i++) { - if (!stricmp(Damage_types[i].name, s)) { - damage_index = (int)i; - break; - } - } + damage_index = find_item_with_name(Damage_types, s); shipp->collision_damage_type_idx = damage_index; } else { damage_index = shipp->collision_damage_type_idx; From 0da93ec5fd0b83362350c6b0864114384549409a Mon Sep 17 00:00:00 2001 From: Kestrellius <63537900+Kestrellius@users.noreply.github.com> Date: Tue, 4 Jun 2024 13:10:54 -0700 Subject: [PATCH 38/63] Adds multishot for beams. --- code/ai/aiturret.cpp | 78 +++++++++++++++++---------------- code/parse/sexp.cpp | 25 ++++++----- code/ship/ship.cpp | 101 ++++++++++++++++++++++--------------------- code/weapon/beam.cpp | 2 +- 4 files changed, 107 insertions(+), 99 deletions(-) diff --git a/code/ai/aiturret.cpp b/code/ai/aiturret.cpp index 249616dbfb2..0e201d71100 100644 --- a/code/ai/aiturret.cpp +++ b/code/ai/aiturret.cpp @@ -1781,45 +1781,49 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum, if (!(turret->weapons.flags[Ship::Weapon_Flags::Beam_Free])) { return false; } - beam_fire_info fire_info; - - // stuff beam firing info - memset(&fire_info, 0, sizeof(beam_fire_info)); - fire_info.accuracy = 1.0f; - fire_info.beam_info_index = turret_weapon_class; - fire_info.beam_info_override = nullptr; - fire_info.shooter = &Objects[parent_objnum]; - fire_info.target = turret_enemy_objp; - if (wip->wi_flags[Weapon::Info_Flags::Antisubsysbeam]) - fire_info.target_subsys = turret->targeted_subsys; - else - fire_info.target_subsys = nullptr; - fire_info.turret = turret; - fire_info.burst_seed = old_burst_seed; - fire_info.fire_method = BFM_TURRET_FIRED; - fire_info.per_burst_rotation = swp->per_burst_rot; - fire_info.burst_index = old_burst_counter; - - // fire a beam weapon - weapon_objnum = beam_fire(&fire_info); - - if (weapon_objnum != -1) { - objp = &Objects[weapon_objnum]; - - parent_ship->last_fired_turret = turret; - turret->last_fired_weapon_info_index = turret_weapon_class; - turret->turret_last_fired = _timestamp(); - - if (scripting::hooks::OnTurretFired->isActive()) { - scripting::hooks::OnTurretFired->run(scripting::hooks::WeaponUsedConditions{ parent_ship , turret_enemy_objp, SCP_vector{ turret_weapon_class }, true }, - scripting::hook_param_list( - scripting::hook_param("Ship", 'o', &Objects[parent_objnum]), - scripting::hook_param("Beam", 'o', objp), - scripting::hook_param("Target", 'o', turret_enemy_objp) - )); + + int current_burst_index = old_burst_counter * wip->shots; + for (int i = 0; i < wip->shots; i++) { + beam_fire_info fire_info; + + // stuff beam firing info + memset(&fire_info, 0, sizeof(beam_fire_info)); + fire_info.accuracy = 1.0f; + fire_info.beam_info_index = turret_weapon_class; + fire_info.beam_info_override = nullptr; + fire_info.shooter = &Objects[parent_objnum]; + fire_info.target = turret_enemy_objp; + if (wip->wi_flags[Weapon::Info_Flags::Antisubsysbeam]) + fire_info.target_subsys = turret->targeted_subsys; + else + fire_info.target_subsys = nullptr; + fire_info.turret = turret; + fire_info.burst_seed = old_burst_seed; + fire_info.fire_method = BFM_TURRET_FIRED; + fire_info.per_burst_rotation = swp->per_burst_rot; + fire_info.burst_index = current_burst_index; + + // fire a beam weapon + weapon_objnum = beam_fire(&fire_info); + + if (weapon_objnum != -1) { + objp = &Objects[weapon_objnum]; + + parent_ship->last_fired_turret = turret; + turret->last_fired_weapon_info_index = turret_weapon_class; + turret->turret_last_fired = _timestamp(); + + if (scripting::hooks::OnTurretFired->isActive()) { + scripting::hooks::OnTurretFired->run(scripting::hooks::WeaponUsedConditions{ parent_ship , turret_enemy_objp, SCP_vector{ turret_weapon_class }, true }, + scripting::hook_param_list( + scripting::hook_param("Ship", 'o', &Objects[parent_objnum]), + scripting::hook_param("Beam", 'o', objp), + scripting::hook_param("Target", 'o', turret_enemy_objp) + )); + } } + current_burst_index++; } - turret->flags.set(Ship::Subsystem_Flags::Has_fired); //set fired flag for scripting -nike return true; } diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index 11d33261821..e5f41b0d0f8 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -21289,7 +21289,6 @@ void sexp_beam_fire(int node, bool at_coords) // zero stuff out memset(&fire_info, 0, sizeof(beam_fire_info)); fire_info.accuracy = 0.000001f; // this will guarantee a hit - fire_info.burst_index = 0; // get the firing ship auto shooter = eval_ship(n); @@ -21375,16 +21374,20 @@ void sexp_beam_fire(int node, bool at_coords) } } - // fire the beam - if (fire_info.beam_info_index != -1) { - fire_info.fire_method = BFM_TURRET_FORCE_FIRED; - - beam_fire(&fire_info); - fire_info.turret->turret_next_fire_pos++; - } else { - // it would appear the turret doesn't have any beam weapons - Warning(LOCATION, "Couldn't fire turret on ship %s; subsystem %s has no beam weapons", CTEXT(node), CTEXT(CDR(node))); - } + // fire the beam + if (fire_info.beam_info_index != -1) { + fire_info.fire_method = BFM_TURRET_FORCE_FIRED; + int current_burst_index = 0; + for ( int i = 0; i < Weapon_info[fire_info.beam_info_index].shots; i++ ) { + fire_info.burst_index = current_burst_index; + beam_fire(&fire_info); + fire_info.turret->turret_next_fire_pos++; + current_burst_index++; + } + } else { + // it would appear the turret doesn't have any beam weapons + Warning(LOCATION, "Couldn't fire turret on ship %s; subsystem %s has no beam weapons", CTEXT(node), CTEXT(CDR(node))); + } } void sexp_beam_floating_fire(int n) diff --git a/code/ship/ship.cpp b/code/ship/ship.cpp index 29ce6f0d724..2d05fc8c83f 100644 --- a/code/ship/ship.cpp +++ b/code/ship/ship.cpp @@ -12636,23 +12636,21 @@ int ship_fire_primary(object * obj, int force, bool rollback_shot) } } - if(winfo_p->wi_flags[Weapon::Info_Flags::Beam]){ // the big change I made for fighter beams, if there beams fill out the Fire_Info for a targeting laser then fire it, for each point in the weapon bank -Bobboau - beam_fire_info fbfire_info; + if(winfo_p->wi_flags[Weapon::Info_Flags::Beam]){ // the big change I made for fighter beams, if there beams fill out the Fire_Info for a targeting laser then fire it, for each point in the weapon bank -Bobboau - int points; + int points = 0, numtimes = 1; if (winfo_p->b_info.beam_shots){ - if (winfo_p->b_info.beam_shots > num_slots){ - points = num_slots; - }else{ - points = winfo_p->b_info.beam_shots; - } + numtimes = winfo_p->shots; + points = MIN(winfo_p->b_info.beam_shots, num_slots); } else if (winfo_p->wi_flags[Weapon::Info_Flags::Cycle]) { - points = 1; + numtimes = 1; + points = MIN(num_slots, winfo_p->shots); } else { + numtimes = winfo_p->shots; points = num_slots; } - bool no_energy = shipp->weapon_energy < points * winfo_p->energy_consumed * flFrametime; + bool no_energy = shipp->weapon_energy < points * numtimes * winfo_p->energy_consumed * flFrametime; if (no_energy || (winfo_p->wi_flags[Weapon::Info_Flags::Ballistic] && shipp->weapons.primary_bank_ammo[bank_to_fire] <= 0)) { swp->next_primary_fire_stamp[bank_to_fire] = timestamp((int)(next_fire_delay)); @@ -12663,36 +12661,6 @@ int ship_fire_primary(object * obj, int force, bool rollback_shot) ship_stop_fire_primary_bank(obj, bank_to_fire); continue; } - - shipp->beam_sys_info.turret_norm.xyz.x = 0.0f; - shipp->beam_sys_info.turret_norm.xyz.y = 0.0f; - shipp->beam_sys_info.turret_norm.xyz.z = 1.0f; - shipp->beam_sys_info.model_num = sip->model_num; - shipp->beam_sys_info.turret_gun_sobj = pm->detail[0]; - shipp->beam_sys_info.turret_num_firing_points = 1; // dummy turret info is used per firepoint - shipp->beam_sys_info.turret_fov = cosf(fl_radians((winfo_p->field_of_fire != 0.0f) ? winfo_p->field_of_fire : 180.0f) / 2.0f); - - shipp->fighter_beam_turret_data.disruption_timestamp = timestamp(0); - shipp->fighter_beam_turret_data.turret_next_fire_pos = 0; - shipp->fighter_beam_turret_data.current_hits = 1.0; - shipp->fighter_beam_turret_data.system_info = &shipp->beam_sys_info; - - fbfire_info.target_subsys = Ai_info[shipp->ai_index].targeted_subsys; - fbfire_info.beam_info_index = shipp->weapons.primary_bank_weapons[bank_to_fire]; - fbfire_info.beam_info_override = NULL; - fbfire_info.shooter = &Objects[shipp->objnum]; - - if (aip->target_objnum >= 0) { - fbfire_info.target = &Objects[aip->target_objnum]; - } else { - fbfire_info.target = NULL; - } - fbfire_info.turret = &shipp->fighter_beam_turret_data; - fbfire_info.bfi_flags = BFIF_IS_FIGHTER_BEAM; - fbfire_info.bank = bank_to_fire; - fbfire_info.burst_index = old_burst_counter; - fbfire_info.burst_seed = old_burst_seed; - fbfire_info.per_burst_rotation = swp->per_burst_rot; for ( v = 0; v < points; v++ ){ if(winfo_p->b_info.beam_shots || winfo_p->wi_flags[Weapon::Info_Flags::Cycle]){ @@ -12701,16 +12669,49 @@ int ship_fire_primary(object * obj, int force, bool rollback_shot) }else{ j=v; } - - fbfire_info.local_fire_postion = pm->gun_banks[bank_to_fire].pnt[j]; - shipp->beam_sys_info.pnt = pm->gun_banks[bank_to_fire].pnt[j]; - shipp->beam_sys_info.turret_firing_point[0] = pm->gun_banks[bank_to_fire].pnt[j]; - fbfire_info.point = j; - fbfire_info.fire_method = BFM_FIGHTER_FIRED; - - beam_fire(&fbfire_info); - has_fired = true; - num_fired++; + int current_burst_index = old_burst_counter * numtimes; + for ( w = 0; w < numtimes; w++ ) { + beam_fire_info fbfire_info; + shipp->beam_sys_info.turret_norm.xyz.x = 0.0f; + shipp->beam_sys_info.turret_norm.xyz.y = 0.0f; + shipp->beam_sys_info.turret_norm.xyz.z = 1.0f; + shipp->beam_sys_info.model_num = sip->model_num; + shipp->beam_sys_info.turret_gun_sobj = pm->detail[0]; + shipp->beam_sys_info.turret_num_firing_points = 1; // dummy turret info is used per firepoint + shipp->beam_sys_info.turret_fov = cosf(fl_radians((winfo_p->field_of_fire != 0.0f) ? winfo_p->field_of_fire : 180.0f) / 2.0f); + + shipp->fighter_beam_turret_data.disruption_timestamp = timestamp(0); + shipp->fighter_beam_turret_data.turret_next_fire_pos = 0; + shipp->fighter_beam_turret_data.current_hits = 1.0; + shipp->fighter_beam_turret_data.system_info = &shipp->beam_sys_info; + + fbfire_info.target_subsys = Ai_info[shipp->ai_index].targeted_subsys; + fbfire_info.beam_info_index = shipp->weapons.primary_bank_weapons[bank_to_fire]; + fbfire_info.beam_info_override = NULL; + fbfire_info.shooter = &Objects[shipp->objnum]; + + if (aip->target_objnum >= 0) { + fbfire_info.target = &Objects[aip->target_objnum]; + } else { + fbfire_info.target = NULL; + } + fbfire_info.turret = &shipp->fighter_beam_turret_data; + fbfire_info.bfi_flags = BFIF_IS_FIGHTER_BEAM; + fbfire_info.bank = bank_to_fire; + fbfire_info.burst_index = current_burst_index; + fbfire_info.burst_seed = old_burst_seed; + fbfire_info.per_burst_rotation = swp->per_burst_rot; + + fbfire_info.local_fire_postion = pm->gun_banks[bank_to_fire].pnt[j]; + shipp->beam_sys_info.pnt = pm->gun_banks[bank_to_fire].pnt[j]; + shipp->beam_sys_info.turret_firing_point[0] = pm->gun_banks[bank_to_fire].pnt[j]; + fbfire_info.point = j; + fbfire_info.fire_method = BFM_FIGHTER_FIRED; + beam_fire(&fbfire_info); + has_fired = true; + num_fired++; + current_burst_index++; + } } } else //if this isn't a fighter beam, do it normally -Bobboau diff --git a/code/weapon/beam.cpp b/code/weapon/beam.cpp index 4c1c0abcace..2bfdefdc8a6 100644 --- a/code/weapon/beam.cpp +++ b/code/weapon/beam.cpp @@ -525,7 +525,7 @@ int beam_fire(beam_fire_info *fire_info) } else { float burst_rot = 0.0f; if (new_item->type == BeamType::OMNI && !wip->b_info.t5info.burst_rot_pattern.empty()) { - burst_rot = wip->b_info.t5info.burst_rot_pattern[fire_info->burst_index]; + burst_rot = wip->b_info.t5info.burst_rot_pattern[fire_info->burst_index % wip->b_info.t5info.burst_rot_pattern.size()]; } beam_get_binfo(new_item, fire_info->accuracy, wip->b_info.beam_shots,fire_info->burst_seed, burst_rot, fire_info->per_burst_rotation); // to fill in b_info - the set of directional aim vectors } From 230fdf4d036db97f6b45a3b26f76c4b4721a9382 Mon Sep 17 00:00:00 2001 From: Asteroth Date: Tue, 4 Jun 2024 17:32:30 -0400 Subject: [PATCH 39/63] assorted beam fixes --- code/ai/ai_profiles.cpp | 1 + code/ship/ship.cpp | 2 +- code/weapon/beam.cpp | 36 +++++++++++++++++------------------- code/weapon/weapons.cpp | 2 +- 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/code/ai/ai_profiles.cpp b/code/ai/ai_profiles.cpp index ebb152693f0..129dcb0597a 100644 --- a/code/ai/ai_profiles.cpp +++ b/code/ai/ai_profiles.cpp @@ -836,5 +836,6 @@ void ai_profile_t::reset() } if (mod_supports_version(24, 2, 0)) { flags.set(AI::Profile_Flags::Debris_respects_big_damage); + flags.set(AI::Profile_Flags::Force_beam_turret_fov); } } diff --git a/code/ship/ship.cpp b/code/ship/ship.cpp index 29ce6f0d724..a1ddd1952fc 100644 --- a/code/ship/ship.cpp +++ b/code/ship/ship.cpp @@ -12670,7 +12670,7 @@ int ship_fire_primary(object * obj, int force, bool rollback_shot) shipp->beam_sys_info.model_num = sip->model_num; shipp->beam_sys_info.turret_gun_sobj = pm->detail[0]; shipp->beam_sys_info.turret_num_firing_points = 1; // dummy turret info is used per firepoint - shipp->beam_sys_info.turret_fov = cosf(fl_radians((winfo_p->field_of_fire != 0.0f) ? winfo_p->field_of_fire : 180.0f) / 2.0f); + shipp->beam_sys_info.turret_fov = -1.0f; shipp->fighter_beam_turret_data.disruption_timestamp = timestamp(0); shipp->fighter_beam_turret_data.turret_next_fire_pos = 0; diff --git a/code/weapon/beam.cpp b/code/weapon/beam.cpp index 4c1c0abcace..d4ca6df1648 100644 --- a/code/weapon/beam.cpp +++ b/code/weapon/beam.cpp @@ -2382,7 +2382,7 @@ void beam_get_binfo(beam *b, float accuracy, int num_shots, int burst_seed, floa // get a model # to work with model_num = beam_get_model(b->target); - if ((model_num < 0) && !(b->flags & BF_TARGETING_COORDS)) { + if ((model_num < 0) && !(b->flags & BF_TARGETING_COORDS) && b->type != BeamType::OMNI) { return; } @@ -2757,8 +2757,8 @@ void beam_aim(beam *b) if (!(b->flags & BF_TARGETING_COORDS)) { // targeting type beam weapons have no target if (b->target == NULL) { - Assert(b->type == BeamType::TARGETING); - if(b->type != BeamType::TARGETING){ + Assert(b->type == BeamType::TARGETING || b->type == BeamType::OMNI); + if(b->type != BeamType::TARGETING && b->type != BeamType::OMNI){ return; } } @@ -4167,25 +4167,23 @@ int beam_ok_to_fire(beam *b) vm_vec_sub(&aim_dir, &b->last_shot, &b->last_start); vm_vec_normalize(&aim_dir); - if (The_mission.ai_profile->flags[AI::Profile_Flags::Force_beam_turret_fov]) { - vec3d turret_normal; - - if (b->flags & BF_IS_FIGHTER_BEAM) { - turret_normal = b->objp->orient.vec.fvec; - } else { + if (!(b->flags & BF_IS_FIGHTER_BEAM)) { + if (The_mission.ai_profile->flags[AI::Profile_Flags::Force_beam_turret_fov]) { + vec3d turret_normal; model_instance_local_to_global_dir(&turret_normal, &b->subsys->system_info->turret_norm, Ships[b->objp->instance].model_instance_num, b->subsys->system_info->subobj_num, &b->objp->orient, true); - } - if (!(turret_fov_test(b->subsys, &turret_normal, &aim_dir))) { - nprintf(("BEAM", "BEAM : powering beam down because of FOV condition!\n")); - return 0; + if (!(turret_fov_test(b->subsys, &turret_normal, &aim_dir))) { + nprintf(("BEAM", "BEAM : powering beam down because of FOV condition!\n")); + return 0; + } } - } else { - vec3d turret_dir, turret_pos; - beam_get_global_turret_gun_info(b->objp, b->subsys, &turret_pos, false, &turret_dir, true, nullptr, (b->flags & BF_IS_FIGHTER_BEAM) != 0); - if (vm_vec_dot(&aim_dir, &turret_dir) < b->subsys->system_info->turret_fov) { - nprintf(("BEAM", "BEAM : powering beam down because of FOV condition!\n")); - return 0; + else { + vec3d turret_dir, turret_pos; + beam_get_global_turret_gun_info(b->objp, b->subsys, &turret_pos, false, &turret_dir, true, nullptr, (b->flags & BF_IS_FIGHTER_BEAM) != 0); + if (vm_vec_dot(&aim_dir, &turret_dir) < b->subsys->system_info->turret_fov) { + nprintf(("BEAM", "BEAM : powering beam down because of FOV condition!\n")); + return 0; + } } } diff --git a/code/weapon/weapons.cpp b/code/weapon/weapons.cpp index 57dbea70d80..5b18f6a3f24 100644 --- a/code/weapon/weapons.cpp +++ b/code/weapon/weapons.cpp @@ -3145,7 +3145,7 @@ int parse_weapon(int subtype, bool replace, const char *filename) if (optional_string("+Per Burst Rotation:")) { stuff_float(&t5info->per_burst_rot); t5info->per_burst_rot = fl_radians(t5info->per_burst_rot); - if (t5info->per_burst_rot < -PI2 || t5info->per_burst_rot > PI2) { + if (t5info->per_burst_rot > PI2) { Warning(LOCATION, "Per Burst Rotation on beam '%s' must not exceed 360 degrees.", wip->name); t5info->per_burst_rot = 0.0f; } From 900199d97f6e20c2ee858d80547d1d051f5ac8dc Mon Sep 17 00:00:00 2001 From: The Force <2040992+TheForce172@users.noreply.github.com> Date: Tue, 4 Jun 2024 22:57:10 +0100 Subject: [PATCH 40/63] Add confirmation on cancel --- .../ui/dialogs/ShipEditor/ShipPathsDialog.cpp | 32 +++++++++++++++++-- .../ui/dialogs/ShipEditor/ShipPathsDialog.h | 1 + qtfred/ui/ShipPathsDialog.ui | 16 ---------- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipPathsDialog.cpp b/qtfred/src/ui/dialogs/ShipEditor/ShipPathsDialog.cpp index d939252f94f..948eac6aca8 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipPathsDialog.cpp +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipPathsDialog.cpp @@ -17,7 +17,7 @@ ShipPathsDialog::ShipPathsDialog(QWidget* parent, ui->setupUi(this); connect(ui->pathList, &QListWidget::itemChanged, this, &ShipPathsDialog::changed); connect(this, &QDialog::accepted, _model.get(), &ShipPathsDialogModel::apply); - connect(this, &QDialog::rejected, _model.get(), &ShipPathsDialogModel::reject); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &ShipPathsDialog::rejectHandler); if (departure) { ui->instructLabel->setText("Restrict departure paths to the following:"); } else { @@ -45,11 +45,39 @@ void ShipPathsDialog::closeEvent(QCloseEvent* event) { accept(); return; } + if (button == DialogButton::No) { + _model->reject(); + } } QDialog::closeEvent(event); } -void dialogs::ShipPathsDialog::updateUI() { +void ShipPathsDialog::rejectHandler() +{ + if (_model->query_modified()) { + auto button = _viewport->dialogProvider->showButtonDialog(DialogType::Question, + "Changes detected", + "Do you want to keep your changes?", + {DialogButton::Yes, DialogButton::No, DialogButton::Cancel}); + + if (button == DialogButton::Cancel) { + return; + } + + if (button == DialogButton::Yes) { + accept(); + return; + } + if (button == DialogButton::No) { + _model->reject(); + QDialog::reject(); + } + } else { + _model->reject(); + QDialog::reject(); + } +} +void ShipPathsDialog::updateUI() { util::SignalBlockers blockers(this); for (size_t i = 0; i < _model->getPathList().size(); i++) { QString name = _model->getModel()->paths[_model->getModel()->ship_bay->path_indexes[i]].name; diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipPathsDialog.h b/qtfred/src/ui/dialogs/ShipEditor/ShipPathsDialog.h index d32d4b8ef9e..040069876c0 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipPathsDialog.h +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipPathsDialog.h @@ -21,6 +21,7 @@ class ShipPathsDialog : public QDialog { protected: void closeEvent(QCloseEvent*) override; + void rejectHandler(); private: std::unique_ptr ui; diff --git a/qtfred/ui/ShipPathsDialog.ui b/qtfred/ui/ShipPathsDialog.ui index a39d410658c..fcc78841d8f 100644 --- a/qtfred/ui/ShipPathsDialog.ui +++ b/qtfred/ui/ShipPathsDialog.ui @@ -63,21 +63,5 @@ - - buttonBox - rejected() - fso::fred::dialogs::ShipPathsDialog - reject() - - - 335 - 63 - - - 133 - 106 - - - From f4921b6d01b4a835e3ee668b05770e5c3232a25b Mon Sep 17 00:00:00 2001 From: Kestrellius <63537900+Kestrellius@users.noreply.github.com> Date: Wed, 5 Jun 2024 09:39:10 -0700 Subject: [PATCH 41/63] Makes minor structural changes as requested. --- code/ai/aiturret.cpp | 4 +--- code/parse/sexp.cpp | 4 +--- code/ship/ship.cpp | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/code/ai/aiturret.cpp b/code/ai/aiturret.cpp index 0e201d71100..a7727fd5d97 100644 --- a/code/ai/aiturret.cpp +++ b/code/ai/aiturret.cpp @@ -1782,7 +1782,6 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum, return false; } - int current_burst_index = old_burst_counter * wip->shots; for (int i = 0; i < wip->shots; i++) { beam_fire_info fire_info; @@ -1801,7 +1800,7 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum, fire_info.burst_seed = old_burst_seed; fire_info.fire_method = BFM_TURRET_FIRED; fire_info.per_burst_rotation = swp->per_burst_rot; - fire_info.burst_index = current_burst_index; + fire_info.burst_index = (old_burst_counter * wip->shots) + i; // fire a beam weapon weapon_objnum = beam_fire(&fire_info); @@ -1822,7 +1821,6 @@ bool turret_fire_weapon(int weapon_num, ship_subsys *turret, int parent_objnum, )); } } - current_burst_index++; } turret->flags.set(Ship::Subsystem_Flags::Has_fired); //set fired flag for scripting -nike return true; diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index e5f41b0d0f8..362195745cd 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -21377,12 +21377,10 @@ void sexp_beam_fire(int node, bool at_coords) // fire the beam if (fire_info.beam_info_index != -1) { fire_info.fire_method = BFM_TURRET_FORCE_FIRED; - int current_burst_index = 0; for ( int i = 0; i < Weapon_info[fire_info.beam_info_index].shots; i++ ) { - fire_info.burst_index = current_burst_index; + fire_info.burst_index = i; beam_fire(&fire_info); fire_info.turret->turret_next_fire_pos++; - current_burst_index++; } } else { // it would appear the turret doesn't have any beam weapons diff --git a/code/ship/ship.cpp b/code/ship/ship.cpp index 2d05fc8c83f..76b0ef05c03 100644 --- a/code/ship/ship.cpp +++ b/code/ship/ship.cpp @@ -12669,7 +12669,6 @@ int ship_fire_primary(object * obj, int force, bool rollback_shot) }else{ j=v; } - int current_burst_index = old_burst_counter * numtimes; for ( w = 0; w < numtimes; w++ ) { beam_fire_info fbfire_info; shipp->beam_sys_info.turret_norm.xyz.x = 0.0f; @@ -12698,7 +12697,7 @@ int ship_fire_primary(object * obj, int force, bool rollback_shot) fbfire_info.turret = &shipp->fighter_beam_turret_data; fbfire_info.bfi_flags = BFIF_IS_FIGHTER_BEAM; fbfire_info.bank = bank_to_fire; - fbfire_info.burst_index = current_burst_index; + fbfire_info.burst_index = (old_burst_counter * numtimes) + i; fbfire_info.burst_seed = old_burst_seed; fbfire_info.per_burst_rotation = swp->per_burst_rot; @@ -12710,7 +12709,6 @@ int ship_fire_primary(object * obj, int force, bool rollback_shot) beam_fire(&fbfire_info); has_fired = true; num_fired++; - current_burst_index++; } } } From 4cdc0b9c0bd9fa0238e56f9035914f915c9abfd8 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Thu, 6 Jun 2024 00:39:14 -0400 Subject: [PATCH 42/63] change Asserts --- code/globalincs/utility.h | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/code/globalincs/utility.h b/code/globalincs/utility.h index 4a908cabdfc..80e8a0904a0 100644 --- a/code/globalincs/utility.h +++ b/code/globalincs/utility.h @@ -150,7 +150,8 @@ typename T::size_type stringcost(const T& op, const T& input, typename T::size_t template int count_items_with_name(const char* name, const T* item_array, int num_items) { - Assert(name != nullptr && item_array != nullptr); + if (!name || !item_array) + return 0; int count = 0; for (int i = 0; i < num_items; ++i) @@ -163,7 +164,8 @@ int count_items_with_name(const char* name, const T* item_array, int num_items) template int count_items_with_name(const char* name, const T& item_vector) { - Assert(name != nullptr); + if (!name) + return 0; int count = 0; for (const auto& item : item_vector) @@ -176,7 +178,8 @@ int count_items_with_name(const char* name, const T& item_vector) template int count_items_with_scp_string_name(const char* name, const T& item_vector) { - Assert(name != nullptr); + if (!name) + return 0; int count = 0; for (const auto& item : item_vector) @@ -189,7 +192,8 @@ int count_items_with_scp_string_name(const char* name, const T& item_vector) template int find_item_with_field(const VECTOR_T& item_vector, FIELD_T ITEM_T::* field, const char* str) { - Assert(str != nullptr); + if (!str) + return -1; int index = 0; for (const ITEM_T& item : item_vector) @@ -236,7 +240,8 @@ int find_item_with_field(const VECTOR_T& item_vector, FIELD_T ITEM_T::* field, c template int find_item_with_field(const ITEM_T* item_array, int num_items, FIELD_T ITEM_T::* field, const char* str) { - Assert(str != nullptr); + if (!str) + return -1; for (int i = 0; i < num_items; ++i) if (!stricmp(item_array[i].*field, str)) @@ -268,7 +273,8 @@ int find_item_with_field(const ITEM_T* item_array, int num_items, FIELD_T ITEM_T template int find_item_with_name(const VECTOR_T& item_vector, const char* str) { - Assert(str != nullptr); + if (!str) + return -1; int index = 0; for (const auto& item : item_vector) From 8b43b5d325db503e998c73d0d7d41de0e07f8865 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Sat, 8 Jun 2024 14:16:06 -0500 Subject: [PATCH 43/63] =?UTF-8?q?=EF=BB=BFsplit=20up=20debris=20and=20aste?= =?UTF-8?q?roid=20type=20handling=20within=20the=20asteroid=20field=20stru?= =?UTF-8?q?ct=20(#6011)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/asteroid/asteroid.cpp | 58 +++++++++++-------- code/asteroid/asteroid.h | 1 + code/mission/missionparse.cpp | 20 ++++--- fred2/asteroideditordlg.cpp | 32 +++++----- fred2/dumpstats.cpp | 9 +-- fred2/missionsave.cpp | 4 +- .../dialogs/AsteroidEditorDialogModel.cpp | 12 ++-- qtfred/src/mission/missionsave.cpp | 4 +- 8 files changed, 78 insertions(+), 62 deletions(-) diff --git a/code/asteroid/asteroid.cpp b/code/asteroid/asteroid.cpp index f3378e4ae1e..79de44a2e59 100644 --- a/code/asteroid/asteroid.cpp +++ b/code/asteroid/asteroid.cpp @@ -602,7 +602,7 @@ void asteroid_create_all() int num_debris_types = 0; - // get number of ship debris types + // get number of debris types if (Asteroid_field.debris_genre == DG_DEBRIS) { for (idx=0; idx= 0) { + if (Asteroid_field.field_asteroid_type[j]) { if (counter == 0) { subtype = j; break; @@ -732,21 +732,25 @@ void asteroid_create_asteroid_field(int num_asteroids, int field_type, int aster Asteroid_field.speed = (float)asteroid_speed; Asteroid_field.debris_genre = DG_ASTEROID; - Asteroid_field.field_debris_type[0] = -1; - Asteroid_field.field_debris_type[1] = -1; - Asteroid_field.field_debris_type[2] = -1; + for (int j = 0; j < MAX_ACTIVE_DEBRIS_TYPES; j++) { + Asteroid_field.field_debris_type[j] = -1; + } + + for (int j = 0; j < NUM_ASTEROID_SIZES; j++) { + Asteroid_field.field_asteroid_type[j] = false; + } int count = 0; if (brown) { - Asteroid_field.field_debris_type[0] = 1; + Asteroid_field.field_asteroid_type[0] = true; count++; } if (blue) { - Asteroid_field.field_debris_type[1] = 1; + Asteroid_field.field_asteroid_type[1] = true; count++; } if (orange) { - Asteroid_field.field_debris_type[2] = 1; + Asteroid_field.field_asteroid_type[2] = true; count++; } @@ -792,9 +796,13 @@ void asteroid_create_debris_field(int num_asteroids, int asteroid_speed, int deb Asteroid_field.speed = (float)asteroid_speed; Asteroid_field.debris_genre = DG_DEBRIS; - Asteroid_field.field_debris_type[0] = -1; - Asteroid_field.field_debris_type[1] = -1; - Asteroid_field.field_debris_type[2] = -1; + for (int j = 0; j < MAX_ACTIVE_DEBRIS_TYPES; j++) { + Asteroid_field.field_debris_type[j] = -1; + } + + for (int j = 0; j < NUM_ASTEROID_SIZES; j++) { + Asteroid_field.field_asteroid_type[j] = false; + } int count = 0; for (int i = 0; i < MAX_ACTIVE_DEBRIS_TYPES; i++) { @@ -850,9 +858,13 @@ void asteroid_level_init() Asteroid_field.has_inner_bound = false; Asteroid_field.field_type = FT_ACTIVE; Asteroid_field.debris_genre = DG_ASTEROID; - Asteroid_field.field_debris_type[0] = -1; - Asteroid_field.field_debris_type[1] = -1; - Asteroid_field.field_debris_type[2] = -1; + for (int j = 0; j < MAX_ACTIVE_DEBRIS_TYPES; j++) { + Asteroid_field.field_debris_type[j] = -1; + } + + for (int j = 0; j < NUM_ASTEROID_SIZES; j++) { + Asteroid_field.field_asteroid_type[j] = false; + } Asteroid_field.num_used_field_debris_types = 0; Asteroid_field.target_names.clear(); @@ -1070,7 +1082,7 @@ static void maybe_throw_asteroid() int counter = Random::next(Asteroid_field.num_used_field_debris_types); int subtype = -1; for (int i = 0; i < NUM_ASTEROID_POFS; i++) { - if (Asteroid_field.field_debris_type[i] >= 0) { + if (Asteroid_field.field_asteroid_type[i]) { if (counter == 0) { subtype = i; break; @@ -2710,7 +2722,7 @@ void asteroid_page_in() Assertion(i < NUM_ASTEROID_SIZES, "Got invalid call to load a debris type instead of asteroid type!"); asip = &Asteroid_info[i]; } else { - // ship debris - always full until empty + // debris - always full until empty if (Asteroid_field.field_debris_type[i] != -1) { asip = &Asteroid_info[Asteroid_field.field_debris_type[i]]; } else { @@ -2721,14 +2733,14 @@ void asteroid_page_in() for (k=0; k 0) { break; } } else { - // ASTEROID DEBRIS - use subtype (Asteroid_field.field_debris_type[] != -1) - if (Asteroid_field.field_debris_type[k] == -1) { + // ASTEROID FIELDS - use subtype k, if active + if (!Asteroid_field.field_asteroid_type[k]) { continue; } } diff --git a/code/asteroid/asteroid.h b/code/asteroid/asteroid.h index 21447234df6..3490415ada9 100644 --- a/code/asteroid/asteroid.h +++ b/code/asteroid/asteroid.h @@ -144,6 +144,7 @@ typedef struct asteroid_field { field_type_t field_type; // active throws and wraps, passive does not debris_genre_t debris_genre; // type of debris (ship or asteroid) [generic type] int field_debris_type[MAX_ACTIVE_DEBRIS_TYPES]; // one of the debris type defines above + bool field_asteroid_type[NUM_ASTEROID_SIZES]; int num_used_field_debris_types; // how many of the above are used bool enhanced_visibility_checks; // if true then range checks are overridden for spawning and wrapping asteroids in the field diff --git a/code/mission/missionparse.cpp b/code/mission/missionparse.cpp index 409004dd25e..131165f0326 100644 --- a/code/mission/missionparse.cpp +++ b/code/mission/missionparse.cpp @@ -5845,9 +5845,13 @@ void parse_asteroid_fields(mission *pm) Asteroid_field.debris_genre = (debris_genre_t)type; } - Asteroid_field.field_debris_type[0] = -1; - Asteroid_field.field_debris_type[1] = -1; - Asteroid_field.field_debris_type[2] = -1; + for (int j = 0; j < MAX_ACTIVE_DEBRIS_TYPES; j++) { + Asteroid_field.field_debris_type[j] = -1; + } + + for (int j = 0; j < NUM_ASTEROID_SIZES; j++) { + Asteroid_field.field_asteroid_type[j] = false; + } // Debris types if (Asteroid_field.debris_genre == DG_DEBRIS) { @@ -5880,24 +5884,24 @@ void parse_asteroid_fields(mission *pm) } else { // Obsolete and only for backwards compatibility - for (int j = 0; j < MAX_ACTIVE_DEBRIS_TYPES; j++) { + for (int j = 0; j < NUM_ASTEROID_SIZES; j++) { if (optional_string("+Field Debris Type:")) { int subtype; stuff_int(&subtype); - Asteroid_field.field_debris_type[subtype] = 1; + Asteroid_field.field_asteroid_type[subtype] = true; count++; } } // Get asteroids by name - for (int j = 0; j < MAX_ACTIVE_DEBRIS_TYPES; j++) { + for (int j = 0; j < NUM_ASTEROID_SIZES; j++) { if (optional_string("+Field Debris Type Name:")) { SCP_string ast_name; stuff_string(ast_name, F_NAME); int subtype = get_asteroid_index(ast_name.c_str()); // If the returned index is valid but not one of the first three then it's a debris type instead of asteroid if ((subtype >= 0) && (subtype < NUM_ASTEROID_SIZES)) { - Asteroid_field.field_debris_type[subtype] = 1; + Asteroid_field.field_asteroid_type[subtype] = true; count++; } else { WarningEx(LOCATION, "Mission %s\n Invalid asteroid %s!", pm->name, ast_name.c_str()); @@ -5921,7 +5925,7 @@ void parse_asteroid_fields(mission *pm) // backward compatibility if ( (Asteroid_field.debris_genre == DG_ASTEROID) && (Asteroid_field.num_used_field_debris_types == 0) ) { - Asteroid_field.field_debris_type[0] = 0; + Asteroid_field.field_asteroid_type[0] = true; Asteroid_field.num_used_field_debris_types = 1; } diff --git a/fred2/asteroideditordlg.cpp b/fred2/asteroideditordlg.cpp index 05eda4473de..64f11f1a42f 100644 --- a/fred2/asteroideditordlg.cpp +++ b/fred2/asteroideditordlg.cpp @@ -279,7 +279,7 @@ int asteroid_editor::validate_data() } } - // check if passive, ship debris field, at least one speceis selected + // check if passive, ship debris field, at least one debris type selected if (a_field[0].field_type == FT_PASSIVE) { if (a_field[0].debris_genre == DG_DEBRIS) { if ( (a_field[0].field_debris_type[0] == -1) && (a_field[0].field_debris_type[1] == -1) && (a_field[0].field_debris_type[2] == -1) ) { @@ -291,7 +291,7 @@ int asteroid_editor::validate_data() // check at least one asteroid subtype is selected if (a_field[0].debris_genre == DG_ASTEROID) { - if ( (a_field[0].field_debris_type[0] == -1) && (a_field[0].field_debris_type[1] == -1) && (a_field[0].field_debris_type[2] == -1) ) { + if ( (!a_field[0].field_asteroid_type[0]) && (!a_field[0].field_asteroid_type[1]) && (!a_field[0].field_asteroid_type[2]) ) { MessageBox("You must choose one or more asteroid subtypes"); return 0; } @@ -335,7 +335,7 @@ BOOL asteroid_editor::OnInitDialog() void asteroid_editor::update_init() { - int num_asteroids, idx, cur_choice; + int num_asteroids, idx; CString str; UpdateData(TRUE); @@ -374,6 +374,7 @@ void asteroid_editor::update_init() for (idx=0; idxGetCurSel(); + int cur_choice; if (cur_sel != CB_ERR) { cur_choice = (int)((CComboBox*)GetDlgItem(Dlg_id[idx]))->GetItemData(cur_sel); } else { @@ -384,28 +385,29 @@ void asteroid_editor::update_init() } if ( m_debris_genre == DG_ASTEROID ) { + bool cur_choice; if ( ((CButton *)GetDlgItem(IDC_SUBTYPE1))->GetCheck() == 1) { - cur_choice = 1; + cur_choice = true; } else { - cur_choice = -1; + cur_choice = false; } - MODIFY(a_field[cur_field].field_debris_type[0], cur_choice); + MODIFY(a_field[cur_field].field_asteroid_type[0], cur_choice); if ( ((CButton *)GetDlgItem(IDC_SUBTYPE2))->GetCheck() == 1) { - cur_choice = 1; + cur_choice = true; } else { - cur_choice = -1; + cur_choice = false; } - MODIFY(a_field[cur_field].field_debris_type[1], cur_choice); + MODIFY(a_field[cur_field].field_asteroid_type[1], cur_choice); if ( ((CButton *)GetDlgItem(IDC_SUBTYPE3))->GetCheck() == 1) { - cur_choice = 1; + cur_choice = true; } else { - cur_choice = -1; + cur_choice = false; } - MODIFY(a_field[cur_field].field_debris_type[2], cur_choice); + MODIFY(a_field[cur_field].field_asteroid_type[2], cur_choice); } MODIFY(a_field[last_field].has_inner_bound, (bool)m_enable_inner_bounds); @@ -499,9 +501,9 @@ void asteroid_editor::update_init() } // set up asteroid subtype checkboxes - ((CButton*)GetDlgItem(IDC_SUBTYPE1))->SetCheck(a_field[cur_field].field_debris_type[0] == 1); - ((CButton*)GetDlgItem(IDC_SUBTYPE2))->SetCheck(a_field[cur_field].field_debris_type[1] == 1); - ((CButton*)GetDlgItem(IDC_SUBTYPE3))->SetCheck(a_field[cur_field].field_debris_type[2] == 1); + ((CButton*)GetDlgItem(IDC_SUBTYPE1))->SetCheck(a_field[cur_field].field_asteroid_type[0]); + ((CButton*)GetDlgItem(IDC_SUBTYPE2))->SetCheck(a_field[cur_field].field_asteroid_type[1]); + ((CButton*)GetDlgItem(IDC_SUBTYPE3))->SetCheck(a_field[cur_field].field_asteroid_type[2]); UpdateData(FALSE); OnEnableAsteroids(); diff --git a/fred2/dumpstats.cpp b/fred2/dumpstats.cpp index ade9b96b761..aad4d5fe852 100644 --- a/fred2/dumpstats.cpp +++ b/fred2/dumpstats.cpp @@ -242,12 +242,9 @@ void DumpStats::get_background_stats(CString &buffer) temp.Format("\tShip Debris\r\n"); buffer += temp; - // species - temp.Format("\t\tSpecies: "); - for (size_t i=0; i= 0) { - temp += CString(Species_info[(Asteroid_field.field_debris_type[i] / NUM_ASTEROID_SIZES) - 1].species_name) + " "; - } + temp.Format("\t\tTypes: "); + for (size_t j = 0; j < MAX_ACTIVE_DEBRIS_TYPES; j++) { + temp += CString(Asteroid_info[Asteroid_field.field_debris_type[j]].name) + ", "; } temp += "\r\n"; diff --git a/fred2/missionsave.cpp b/fred2/missionsave.cpp index 0e08c75b29e..8e358d630da 100644 --- a/fred2/missionsave.cpp +++ b/fred2/missionsave.cpp @@ -784,8 +784,8 @@ int CFred_mission_save::save_asteroid_fields() } } else { // asteroid subtypes stored in field_debris_type as -1 or 1 - for (int idx = 0; idx < MAX_ACTIVE_DEBRIS_TYPES; idx++) { - if (Asteroid_field.field_debris_type[idx] != -1) { + for (int idx = 0; idx < NUM_ASTEROID_SIZES; idx++) { + if (Asteroid_field.field_asteroid_type[idx] != false) { if (Mission_save_format == FSO_FORMAT_RETAIL) { if (optional_string_fred("+Field Debris Type:")) { diff --git a/qtfred/src/mission/dialogs/AsteroidEditorDialogModel.cpp b/qtfred/src/mission/dialogs/AsteroidEditorDialogModel.cpp index e439b38e619..6c109bd3728 100644 --- a/qtfred/src/mission/dialogs/AsteroidEditorDialogModel.cpp +++ b/qtfred/src/mission/dialogs/AsteroidEditorDialogModel.cpp @@ -335,9 +335,9 @@ bool AsteroidEditorDialogModel::validate_data() // check at least one asteroid subtype is selected if (_a_field.debris_genre == DG_ASTEROID) { - if ( (_a_field.field_debris_type[_AST_BROWN] == -1) && \ - (_a_field.field_debris_type[_AST_BLUE] == -1) && \ - (_a_field.field_debris_type[_AST_ORANGE] == -1) ) { + if ( (!_a_field.field_asteroid_type[_AST_BROWN]) && \ + (!_a_field.field_asteroid_type[_AST_BLUE]) && \ + (!_a_field.field_asteroid_type[_AST_ORANGE]) ) { showErrorDialogNoCancel("You must choose one or more asteroid subtypes\n"); return false; } @@ -393,9 +393,9 @@ void AsteroidEditorDialogModel::update_init() // asteroids if ( _debris_genre == DG_ASTEROID ) { - modify(_a_field.field_debris_type[_AST_BROWN], getAsteroidEnabled(_AST_BROWN) == true ? 1 : -1); - modify(_a_field.field_debris_type[_AST_BLUE], getAsteroidEnabled(_AST_BLUE) == true ? 1 : -1); - modify(_a_field.field_debris_type[_AST_ORANGE], getAsteroidEnabled(_AST_ORANGE) == true ? 1 : -1); + modify(_a_field.field_asteroid_type[_AST_BROWN], getAsteroidEnabled(_AST_BROWN)); + modify(_a_field.field_asteroid_type[_AST_BLUE], getAsteroidEnabled(_AST_BLUE)); + modify(_a_field.field_asteroid_type[_AST_ORANGE], getAsteroidEnabled(_AST_ORANGE)); } modify(_a_field.has_inner_bound, _enable_inner_bounds); diff --git a/qtfred/src/mission/missionsave.cpp b/qtfred/src/mission/missionsave.cpp index 30e27518883..54044d294e4 100644 --- a/qtfred/src/mission/missionsave.cpp +++ b/qtfred/src/mission/missionsave.cpp @@ -799,8 +799,8 @@ int CFred_mission_save::save_asteroid_fields() } } else { // asteroid subtypes stored in field_debris_type as -1 or 1 - for (int idx = 0; idx < MAX_ACTIVE_DEBRIS_TYPES; idx++) { - if (Asteroid_field.field_debris_type[idx] != -1) { + for (int idx = 0; idx < NUM_ASTEROID_SIZES; idx++) { + if (Asteroid_field.field_asteroid_type[idx] != false) { if (save_format != MissionFormat::RETAIL) { if (optional_string_fred("+Field Debris Type:")) { From 443234169465ac5b8842845f8d565e3daeeab4ee Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Sat, 8 Jun 2024 15:20:22 -0500 Subject: [PATCH 44/63] Scanning is chaos, let's make it sane (#6000) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Unify scanning behavior * Allow modifying the cargo title * update localization * update documentation * update localization * address feedback * fix conflict merge resolution error * fix oversights, missed subsys title # feature, additional xstrs * don't need sprintf here --- code/hud/hudtarget.cpp | 11 +- code/hud/hudtargetbox.cpp | 2 +- code/hud/hudtargetbox.h | 2 +- code/localization/localize.cpp | 2 +- code/mission/missionparse.cpp | 12 ++ code/mission/missionparse.h | 2 + code/mod_table/mod_table.cpp | 6 + code/mod_table/mod_table.h | 1 + code/playerman/player.h | 3 +- code/playerman/playercontrol.cpp | 236 ++++++++++++++++++++--------- code/ship/ship.cpp | 3 + code/ship/ship.h | 2 + code/ship/ship_flags.h | 1 + fred2/missionsave.cpp | 22 +++ qtfred/src/mission/missionsave.cpp | 22 +++ 15 files changed, 247 insertions(+), 80 deletions(-) diff --git a/code/hud/hudtarget.cpp b/code/hud/hudtarget.cpp index 64c62e8a5cf..e8bc5691c50 100644 --- a/code/hud/hudtarget.cpp +++ b/code/hud/hudtarget.cpp @@ -1587,13 +1587,14 @@ int hud_target_ship_can_be_scanned(ship *shipp) if (shipp->flags[Ship::Ship_Flags::Cargo_revealed]) return 0; - // allow ships with scannable flag set - if (shipp->flags[Ship::Ship_Flags::Scannable]) + // The old behavior treats some ship types as always scannable. The new behavior only checks the ship's Scannable flag. + if (shipp->flags[Ship::Ship_Flags::Scannable]) { return 1; - - // ignore ships that don't carry cargo - if ((sip->class_type < 0) || !(Ship_types[sip->class_type].flags[Ship::Type_Info_Flags::Scannable])) + } else if (Use_new_scanning_behavior) { + return 0; + } else if ((sip->class_type < 0) || !(Ship_types[sip->class_type].flags[Ship::Type_Info_Flags::Scannable])) { return 0; + } return 1; } diff --git a/code/hud/hudtargetbox.cpp b/code/hud/hudtargetbox.cpp index 83889ffb30a..138497fb554 100644 --- a/code/hud/hudtargetbox.cpp +++ b/code/hud/hudtargetbox.cpp @@ -57,7 +57,7 @@ static int Target_static_next; static int Target_static_playing; sound_handle Target_static_looping = sound_handle::invalid(); -int Target_display_cargo; +bool Target_display_cargo; char Cargo_string[256] = ""; #ifndef NDEBUG diff --git a/code/hud/hudtargetbox.h b/code/hud/hudtargetbox.h index b6571df0706..c41da77f854 100644 --- a/code/hud/hudtargetbox.h +++ b/code/hud/hudtargetbox.h @@ -30,7 +30,7 @@ class object; #define TBOX_FLASH_SUBSYS 4 extern sound_handle Target_static_looping; -extern int Target_display_cargo; +extern bool Target_display_cargo; extern char Cargo_string[256]; extern int Target_window_coords[GR_NUM_RESOLUTIONS][4]; diff --git a/code/localization/localize.cpp b/code/localization/localize.cpp index 11f15cec039..842336cdca7 100644 --- a/code/localization/localize.cpp +++ b/code/localization/localize.cpp @@ -64,7 +64,7 @@ bool *Lcl_unexpected_tstring_check = nullptr; // NOTE: with map storage of XSTR strings, the indexes no longer need to be contiguous, // but internal strings should still increment XSTR_SIZE to avoid collisions. // retail XSTR_SIZE = 1570 -// #define XSTR_SIZE 1850 // This is the next available ID +// #define XSTR_SIZE 1854 // This is the next available ID // struct to allow for strings.tbl-determined x offset diff --git a/code/mission/missionparse.cpp b/code/mission/missionparse.cpp index 131165f0326..e0e943e3e7f 100644 --- a/code/mission/missionparse.cpp +++ b/code/mission/missionparse.cpp @@ -2091,6 +2091,7 @@ int parse_create_object_sub(p_object *p_objp, bool standalone_ship) } shipp->cargo1 = p_objp->cargo1; + strcpy_s(shipp->cargo_title, p_objp->cargo_title); shipp->arrival_location = p_objp->arrival_location; shipp->arrival_distance = p_objp->arrival_distance; @@ -2422,6 +2423,7 @@ int parse_create_object_sub(p_object *p_objp, bool standalone_ship) } ptr->subsys_cargo_name = sssp->subsys_cargo_name; + strcpy_s(ptr->subsys_cargo_title, sssp->subsys_cargo_title); if (sssp->ai_class != SUBSYS_STATUS_NO_CHANGE) ptr->weapons.ai_class = sssp->ai_class; @@ -3149,6 +3151,11 @@ int parse_object(mission *pm, int /*flag*/, p_object *p_objp) stuff_string(name, F_NAME, NAME_LENGTH); } + if (optional_string("$Cargo Title:")) + { + stuff_string(p_objp->cargo_title, F_NAME, NAME_LENGTH); + } + parse_common_object_data(p_objp); // get initial conditions and subsys status find_and_stuff("$Arrival Location:", &temp, F_NAME, Arrival_location_names, Num_arrival_names, "Arrival Location"); @@ -3764,6 +3771,11 @@ void parse_common_object_data(p_object *p_objp) Subsys_status[i].subsys_cargo_name = index; } + Subsys_status[i].subsys_cargo_title[0] = '\0'; + if (optional_string("+Cargo Title:")) { + stuff_string(Subsys_status[i].subsys_cargo_title, F_NAME, NAME_LENGTH); + } + if (optional_string("+AI Class:")) { Subsys_status[i].ai_class = match_and_stuff(F_NAME, Ai_class_names, Num_ai_classes, "AI class"); diff --git a/code/mission/missionparse.h b/code/mission/missionparse.h index 2945ca67a10..3ed31f85ac4 100644 --- a/code/mission/missionparse.h +++ b/code/mission/missionparse.h @@ -319,6 +319,7 @@ typedef struct subsys_status { int secondary_ammo[MAX_SHIP_SECONDARY_BANKS]; int ai_class; int subsys_cargo_name; + char subsys_cargo_title[NAME_LENGTH]; } subsys_status; typedef struct texture_replace { @@ -354,6 +355,7 @@ class p_object int loadout_team = -1; // original team, should never be changed after being set!! int ai_goals = -1; // sexp of lists of goals that this ship should try and do char cargo1 = '\0'; + char cargo_title[NAME_LENGTH] = ""; SCP_string team_color_setting; int subsys_index = -1; // index into subsys_status array diff --git a/code/mod_table/mod_table.cpp b/code/mod_table/mod_table.cpp index e0790fc3e6a..54d72f3a34d 100644 --- a/code/mod_table/mod_table.cpp +++ b/code/mod_table/mod_table.cpp @@ -151,6 +151,7 @@ bool Randomize_particle_rotation; bool Calculate_subsystem_hitpoints_after_parsing; bool Disable_internal_loadout_restoration_system; bool Contrails_use_absolute_speed; +bool Use_new_scanning_behavior; bool Lua_API_returns_nil_instead_of_invalid_object; bool Dont_show_callsigns_in_escort_list; bool Fix_scripted_velocity; @@ -448,6 +449,10 @@ void parse_mod_table(const char *filename) stuff_boolean(&HUD_shadows); } + if (optional_string("$Unify scanning behavior:")) { + stuff_boolean(&Use_new_scanning_behavior); + } + if (optional_string("$Don't show callsigns in the escort list:")) { stuff_boolean(&Dont_show_callsigns_in_escort_list); } @@ -1609,6 +1614,7 @@ void mod_table_reset() Calculate_subsystem_hitpoints_after_parsing = false; Disable_internal_loadout_restoration_system = false; Contrails_use_absolute_speed = false; + Use_new_scanning_behavior = false; Lua_API_returns_nil_instead_of_invalid_object = false; Dont_show_callsigns_in_escort_list = false; Fix_scripted_velocity = false; diff --git a/code/mod_table/mod_table.h b/code/mod_table/mod_table.h index 85adde2fdd7..f1c9bdeed61 100644 --- a/code/mod_table/mod_table.h +++ b/code/mod_table/mod_table.h @@ -160,6 +160,7 @@ extern bool Randomize_particle_rotation; extern bool Calculate_subsystem_hitpoints_after_parsing; extern bool Disable_internal_loadout_restoration_system; extern bool Contrails_use_absolute_speed; +extern bool Use_new_scanning_behavior; extern bool Lua_API_returns_nil_instead_of_invalid_object; extern bool Dont_show_callsigns_in_escort_list; extern bool Fix_scripted_velocity; diff --git a/code/playerman/player.h b/code/playerman/player.h index 4fe8ee6d31e..69d2c7e11cd 100644 --- a/code/playerman/player.h +++ b/code/playerman/player.h @@ -265,7 +265,8 @@ void player_set_squad_bitmap(player *p, const char *fnamem, bool ismulti); // set squadron void player_set_squad(player *p, char *squad_name); -int player_inspect_cargo(float frametime, char *outstr); +bool player_inspect_cargo(float frametime, char *outstr); +bool better_player_inspect_cargo(float frametime, char* outstr); extern int use_descent; // player is using descent-style physics extern void toggle_player_object(); // toggles between descent-style ship and player ship diff --git a/code/playerman/playercontrol.cpp b/code/playerman/playercontrol.cpp index d2713aeae42..94bfba241f9 100644 --- a/code/playerman/playercontrol.cpp +++ b/code/playerman/playercontrol.cpp @@ -1594,7 +1594,7 @@ int player_process_pending_praise() return 1; } -int player_inspect_cap_subsys_cargo(float frametime, char *outstr); +bool player_inspect_cap_subsys_cargo(float frametime, char *outstr); /** * See if the player should be inspecting cargo, and update progress. @@ -1602,21 +1602,18 @@ int player_inspect_cap_subsys_cargo(float frametime, char *outstr); * @param frametime Time since last frame in seconds * @param outstr (output parm) holds string that HUD should display * - * @return 1 if player should display outstr on HUD; 0if don't display cargo on HUD + * @return true if player should display outstr on HUD; false if don't display cargo on HUD */ -int player_inspect_cargo(float frametime, char *outstr) +bool player_inspect_cargo(float frametime, char *outstr) { object *cargo_objp; - ship *cargo_sp; + ship *cargo_sp; ship_info *cargo_sip; - vec3d vec_to_cargo; - float dot; - int scan_subsys; outstr[0] = 0; if ( Player_ai->target_objnum < 0 || Player_ship->flags[Ship::Ship_Flags::Cannot_perform_scan] ) { - return 0; + return false; } cargo_objp = &Objects[Player_ai->target_objnum]; @@ -1624,39 +1621,64 @@ int player_inspect_cargo(float frametime, char *outstr) cargo_sp = &Ships[cargo_objp->instance]; cargo_sip = &Ship_info[cargo_sp->ship_info_index]; - // Goober5000 - possibly swap cargo scan behavior - scan_subsys = cargo_sip->is_huge_ship(); - if (cargo_sp->flags[Ship::Ship_Flags::Toggle_subsystem_scanning]) - scan_subsys = !scan_subsys; - if (scan_subsys) - return player_inspect_cap_subsys_cargo(frametime, outstr); - - // check if target is ship class that can be inspected - // MWA -- 1/27/98 -- added fighters/bombers to this list. For multiplayer, we - // want to show callsign of player - // G5K -- 10/20/08 -- moved the callsign code into hud_stuff_ship_callsign, where - // it makes more sense - - // scannable cargo behaves differently. Scannable cargo is either "scanned" or "not scanned". This flag - // can be set on any ship. Any ship with this set won't have "normal" cargo behavior - if ( !(cargo_sp->flags[Ship::Ship_Flags::Scannable]) ) { - if (!(cargo_sip->flags[Ship::Info_Flags::Cargo] || cargo_sip->flags[Ship::Info_Flags::Transport])) { - return 0; + if (Use_new_scanning_behavior) { + // If this flag is active, no matter the ship class, we do subsystem scanning + if (cargo_sp->flags[Ship::Ship_Flags::Toggle_subsystem_scanning]) { + return player_inspect_cap_subsys_cargo(frametime, outstr); + } + } else { + // Goober5000 - possibly swap cargo scan behavior + int scan_subsys = cargo_sip->is_huge_ship(); + if (cargo_sp->flags[Ship::Ship_Flags::Toggle_subsystem_scanning]) + scan_subsys = !scan_subsys; + if (scan_subsys) + return player_inspect_cap_subsys_cargo(frametime, outstr); + } + + if (Use_new_scanning_behavior) { + // If this flag is inactive then the ship cannot be scanned at all + if (!(cargo_sp->flags[Ship::Ship_Flags::Scannable])) { + return false; + } + } else { + // scannable cargo behaves differently. Scannable cargo is either "scanned" or "not scanned". This flag + // can be set on any ship. Any ship with this set won't have "normal" cargo behavior + if (!(cargo_sp->flags[Ship::Ship_Flags::Scannable])) { + if (!(cargo_sip->flags[Ship::Info_Flags::Cargo] || cargo_sip->flags[Ship::Info_Flags::Transport])) { + return false; + } } } + // Whether or not we are scanning in a mode that will reveal the cargo contents + bool reveal_cargo = false; + if (Use_new_scanning_behavior && !(cargo_sp->flags[Ship::Ship_Flags::No_scanned_cargo])) { + reveal_cargo = true; + } else if (!(cargo_sp->flags[Ship::Ship_Flags::Scannable])) { + reveal_cargo = true; + } + // if cargo is already revealed if ( cargo_sp->flags[Ship::Ship_Flags::Cargo_revealed] ) { - if ( !(cargo_sp->flags[Ship::Ship_Flags::Scannable]) ) { + if (reveal_cargo) { auto cargo_name = (cargo_sp->cargo1 & CARGO_INDEX_MASK) == 0 ? XSTR("Nothing", 1674) : Cargo_names[cargo_sp->cargo1 & CARGO_INDEX_MASK]; - Assert(cargo_sip->flags[Ship::Info_Flags::Cargo] || cargo_sip->flags[Ship::Info_Flags::Transport]); + //Why was this assert here? I'm not sure it makes much sense because any ship can be scanned and have cargo revealed... + //Assert(cargo_sip->flags[Ship::Info_Flags::Cargo] || cargo_sip->flags[Ship::Info_Flags::Transport]); - if ( cargo_name[0] == '#' ) { - sprintf(outstr, XSTR("passengers: %s", 83), cargo_name+1 ); + if (cargo_sp->cargo_title[0] != '\0') { + if (cargo_sp->cargo_title[0] == '#') { + sprintf(outstr, "%s", cargo_name); + } else { + sprintf(outstr, "%s: %s", cargo_sp->cargo_title, cargo_name); + } } else { - sprintf(outstr,XSTR("cargo: %s", 84), cargo_name ); + if (cargo_name[0] == '#') { + sprintf(outstr, XSTR("passengers: %s", 83), cargo_name + 1); + } else { + sprintf(outstr, XSTR("cargo: %s", 84), cargo_name); + } } } else { strcpy(outstr, XSTR( "Scanned", 85) ); @@ -1666,7 +1688,7 @@ int player_inspect_cargo(float frametime, char *outstr) // are in the process of scanning Player->cargo_inspect_time = 0; - return 1; + return true; } // see if player is within inspection range @@ -1675,18 +1697,28 @@ int player_inspect_cargo(float frametime, char *outstr) scan_dist *= player_sip->scanning_range_multiplier; if ( Player_ai->current_target_distance < scan_dist ) { + vec3d vec_to_cargo; // check if player is facing cargo, do not proceed with inspection if not vm_vec_normalized_dir(&vec_to_cargo, &cargo_objp->pos, &Player_obj->pos); - dot = vm_vec_dot(&vec_to_cargo, &Player_obj->orient.vec.fvec); + float dot = vm_vec_dot(&vec_to_cargo, &Player_obj->orient.vec.fvec); if ( dot < CARGO_MIN_DOT_TO_REVEAL ) { - if ( !(cargo_sp->flags[Ship::Ship_Flags::Scannable]) ) - strcpy(outstr,XSTR( "cargo: ", 86)); - else - strcpy(outstr,XSTR( "not scanned", 87)); - hud_targetbox_end_flash(TBOX_FLASH_CARGO); - Player->cargo_inspect_time = 0; - return 1; + if (reveal_cargo) { + if (cargo_sp->cargo_title[0] != '\0') { + if (cargo_sp->cargo_title[0] == '#') { + strcpy(outstr, XSTR("", 1852)); + } else { + sprintf(outstr, XSTR("%s: ", 1850), cargo_sp->cargo_title); + } + } else { + strcpy(outstr, XSTR("cargo: ", 86)); + } + } else { + strcpy(outstr, XSTR("not scanned", 87)); + hud_targetbox_end_flash(TBOX_FLASH_CARGO); + Player->cargo_inspect_time = 0; + return true; + } } // player is facing the cargo, and within range, so proceed with inspection @@ -1694,10 +1726,19 @@ int player_inspect_cargo(float frametime, char *outstr) Player->cargo_inspect_time += (int)std::lround(frametime*1000); } - if ( !(cargo_sp->flags[Ship::Ship_Flags::Scannable]) ) - strcpy(outstr,XSTR( "cargo: inspecting", 88)); - else - strcpy(outstr,XSTR( "scanning", 89)); + if (reveal_cargo) { + if (cargo_sp->cargo_title[0] != '\0') { + if (cargo_sp->cargo_title[0] == '#') { + strcpy(outstr, XSTR("inspecting", 1853)); + } else { + sprintf(outstr, XSTR("%s: inspecting", 1851), cargo_sp->cargo_title); + } + } else { + strcpy(outstr, XSTR("cargo: inspecting", 88)); + } + } else { + strcpy(outstr, XSTR("scanning", 89)); + } float scan_time = i2fl(cargo_sip->scan_time); scan_time *= player_sip->scanning_time_multiplier; @@ -1708,56 +1749,83 @@ int player_inspect_cargo(float frametime, char *outstr) Player->cargo_inspect_time = 0; } } else { - if ( !(cargo_sp->flags[Ship::Ship_Flags::Scannable]) ) - strcpy(outstr,XSTR( "cargo: ", 86)); - else - strcpy(outstr,XSTR( "not scanned", 87)); + if (reveal_cargo){ + if (cargo_sp->cargo_title[0] != '\0') { + if (cargo_sp->cargo_title[0] == '#') { + strcpy(outstr, XSTR("", 1852)); + } else { + sprintf(outstr, XSTR("%s: ", 1850), cargo_sp->cargo_title); + } + } else { + strcpy(outstr, XSTR("cargo: ", 86)); + } + } else { + strcpy(outstr, XSTR("not scanned", 87)); + } } - return 1; + return true; } /** * @return 1 if player should display outstr on HUD; 0 if don't display cargo on HUD */ -int player_inspect_cap_subsys_cargo(float frametime, char *outstr) +bool player_inspect_cap_subsys_cargo(float frametime, char *outstr) { object *cargo_objp; - ship *cargo_sp; + ship *cargo_sp; ship_info *cargo_sip; - vec3d vec_to_cargo; - float dot; ship_subsys *subsys; outstr[0] = 0; subsys = Player_ai->targeted_subsys; if ( subsys == NULL || Player_ship->flags[Ship::Ship_Flags::Cannot_perform_scan] ) { - return 0; - } + return false; + } cargo_objp = &Objects[Player_ai->target_objnum]; Assert(cargo_objp->type == OBJ_SHIP); cargo_sp = &Ships[cargo_objp->instance]; cargo_sip = &Ship_info[cargo_sp->ship_info_index]; + // If we're using the new scanning behavior then we have to check that the ship is actually scannable first + if (Use_new_scanning_behavior && !(cargo_sp->flags[Ship::Ship_Flags::Scannable])) { + return false; + } + // don't do any sort of scanning thing unless capship has a non-"nothing" cargo // this compensates for changing the "no display" index from -1 to 0 if (subsys->subsys_cargo_name == 0) { - return 0; + return false; + } + + // Whether or not we are scanning in a mode that will reveal the cargo contents + bool reveal_cargo = false; + if (Use_new_scanning_behavior && !(cargo_sp->flags[Ship::Ship_Flags::No_scanned_cargo])) { + reveal_cargo = true; + } else if (!(cargo_sp->flags[Ship::Ship_Flags::Scannable])) { + reveal_cargo = true; } // if cargo is already revealed if (subsys->flags[Ship::Subsystem_Flags::Cargo_revealed]) { - if ( !(cargo_sp->flags[Ship::Ship_Flags::Scannable]) ) { + if (reveal_cargo) { auto cargo_name = (subsys->subsys_cargo_name & CARGO_INDEX_MASK) == 0 ? XSTR("Nothing", 1674) : Cargo_names[subsys->subsys_cargo_name & CARGO_INDEX_MASK]; - - if ( cargo_name[0] == '#' ) { - sprintf(outstr, XSTR("passengers: %s", 83), cargo_name+1 ); + if (subsys->subsys_cargo_title[0] != '\0') { + if (subsys->subsys_cargo_title[0] == '#') { + sprintf(outstr, "%s", cargo_name); + } else { + sprintf(outstr, "%s: %s", subsys->subsys_cargo_title, cargo_name); + } } else { - sprintf(outstr,XSTR("cargo: %s", 84), cargo_name ); + if (cargo_name[0] == '#') { + sprintf(outstr, XSTR("passengers: %s", 83), cargo_name + 1); + } else { + sprintf(outstr, XSTR("cargo: %s", 84), cargo_name); + } } } else { strcpy(outstr, XSTR( "Scanned", 85) ); @@ -1767,7 +1835,7 @@ int player_inspect_cap_subsys_cargo(float frametime, char *outstr) // are in the process of scanning Player->cargo_inspect_time = 0; - return 1; + return true; } // see if player is within inspection range [ok for subsys] @@ -1789,20 +1857,30 @@ int player_inspect_cap_subsys_cargo(float frametime, char *outstr) scan_dist *= player_sip->scanning_range_multiplier; if ( Player_ai->current_target_distance < scan_dist ) { + vec3d vec_to_cargo; + // check if player is facing cargo, do not proceed with inspection if not vm_vec_normalized_dir(&vec_to_cargo, &subsys_pos, &Player_obj->pos); - dot = vm_vec_dot(&vec_to_cargo, &Player_obj->orient.vec.fvec); + float dot = vm_vec_dot(&vec_to_cargo, &Player_obj->orient.vec.fvec); int hud_targetbox_subsystem_in_view(object *target_objp, int *sx, int *sy); subsys_in_view = hud_targetbox_subsystem_in_view(cargo_objp, &x, &y); if ( (dot < CARGO_MIN_DOT_TO_REVEAL) || (!subsys_in_view) ) { - if ( !(cargo_sp->flags[Ship::Ship_Flags::Scannable]) ) - strcpy(outstr,XSTR( "cargo: ", 86)); + if (reveal_cargo) + if (subsys->subsys_cargo_title[0] != '\0') { + if (subsys->subsys_cargo_title[0] == '#') { + strcpy(outstr, XSTR("", 1852)); + } else { + sprintf(outstr, XSTR("%s: ", 1850), subsys->subsys_cargo_title); + } + } else { + strcpy(outstr, XSTR("cargo: ", 86)); + } else strcpy(outstr,XSTR( "not scanned", 87)); hud_targetbox_end_flash(TBOX_FLASH_CARGO); Player->cargo_inspect_time = 0; - return 1; + return true; } // player is facing the cargo, and within range, so proceed with inspection @@ -1810,8 +1888,16 @@ int player_inspect_cap_subsys_cargo(float frametime, char *outstr) Player->cargo_inspect_time += (int)std::lround(frametime*1000); } - if ( !(cargo_sp->flags[Ship::Ship_Flags::Scannable]) ) - strcpy(outstr,XSTR( "cargo: inspecting", 88)); + if (reveal_cargo) + if (subsys->subsys_cargo_title[0] != '\0') { + if (subsys->subsys_cargo_title[0] == '#') { + strcpy(outstr, XSTR("inspecting", 1853)); + } else { + sprintf(outstr, XSTR("%s: inspecting", 1851), subsys->subsys_cargo_title); + } + } else { + strcpy(outstr, XSTR("cargo: inspecting", 88)); + } else strcpy(outstr,XSTR( "scanning", 89)); @@ -1828,13 +1914,21 @@ int player_inspect_cap_subsys_cargo(float frametime, char *outstr) Player->cargo_inspect_time = 0; } } else { - if ( !(cargo_sp->flags[Ship::Ship_Flags::Scannable]) ) - strcpy(outstr,XSTR( "cargo: ", 86)); + if (reveal_cargo) + if (subsys->subsys_cargo_title[0] != '\0') { + if (subsys->subsys_cargo_title[0] == '#') { + strcpy(outstr, XSTR("", 1852)); + } else { + sprintf(outstr, XSTR("%s: ", 1850), subsys->subsys_cargo_title); + } + } else { + strcpy(outstr, XSTR("cargo: ", 86)); + } else strcpy(outstr,XSTR( "not scanned", 87)); } - return 1; + return true; } diff --git a/code/ship/ship.cpp b/code/ship/ship.cpp index a1ddd1952fc..da1e5de1cfb 100644 --- a/code/ship/ship.cpp +++ b/code/ship/ship.cpp @@ -621,6 +621,7 @@ ship_flag_name Ship_flag_names[] = { { Ship_Flags::No_builtin_messages, "no-builtin-messages"}, { Ship_Flags::Scramble_messages, "scramble-messages"}, { Ship_Flags::Maneuver_despite_engines, "maneuver-despite-engines" }, + { Ship_Flags::No_scanned_cargo, "no-scanned-cargo"}, { Ship_Flags::EMP_doesnt_scramble_messages, "emp-doesn't-scramble-messages" }, }; @@ -660,6 +661,7 @@ ship_flag_description Ship_flag_descriptions[] = { { Ship_Flags::No_builtin_messages, "Ship will not send any persona messages."}, { Ship_Flags::Scramble_messages, "All messages sent from or received by this ship will appear scrambled, as if the ship had been hit by an EMP." }, { Ship_Flags::Maneuver_despite_engines, "Ship can maneuver even if its engines are disabled or disrupted" }, + { Ship_Flags::No_scanned_cargo, "Ship cargo will never be revealed and will instead only show scanned or not scanned. Only available if using New Scanning Behavior in game_settings.tbl."}, { Ship_Flags::EMP_doesnt_scramble_messages, "EMP does not affect whether messages appear scrambled when sent from or received by this ship." }, }; @@ -7489,6 +7491,7 @@ void ship_subsys::clear() disruption_timestamp = timestamp(0); subsys_cargo_name = 0; + subsys_cargo_title[0] = '\0'; time_subsys_cargo_revealed = 0; triggered_rotation_index = -1; diff --git a/code/ship/ship.h b/code/ship/ship.h index e6e34b94f2f..5ebcffb2dc1 100644 --- a/code/ship/ship.h +++ b/code/ship/ship.h @@ -404,6 +404,7 @@ class ship_subsys int disruption_timestamp; // time at which subsystem isn't disrupted int subsys_cargo_name; // cap ship cargo on subsys + char subsys_cargo_title[NAME_LENGTH]; // cap ship cargo title (IE: Cargo: or Passengers:) fix time_subsys_cargo_revealed; // added by Goober5000 int triggered_rotation_index; //the actual currently running animation and assosiated states @@ -552,6 +553,7 @@ class ship ubyte pre_death_explosion_happened; // If set, it means the 4 or 5 smaller explosions ubyte wash_killed; char cargo1; + char cargo_title[NAME_LENGTH]; // ship wing status info char wing_status_wing_index; // wing index (0-4) in wingman status gauge diff --git a/code/ship/ship_flags.h b/code/ship/ship_flags.h index 4ba76e9545b..3906de02aa1 100644 --- a/code/ship/ship_flags.h +++ b/code/ship/ship_flags.h @@ -140,6 +140,7 @@ namespace Ship { No_targeting_limits, //MjnMixael -- Ship is always targetable regardless of AWACS or targeting range limits Maneuver_despite_engines, // Goober5000 - ship can move even when engines are disabled or disrupted Force_primary_unlinking, // plieblang - turned on when the ship is under good-primary-time + No_scanned_cargo, //MjnMixael -- The cargo will never be revealed, instead always returning "Scanned" or "Not Scanned" NUM_VALUES diff --git a/fred2/missionsave.cpp b/fred2/missionsave.cpp index 8e358d630da..dd82486990e 100644 --- a/fred2/missionsave.cpp +++ b/fred2/missionsave.cpp @@ -1808,6 +1808,17 @@ int CFred_mission_save::save_common_object_data(object *objp, ship *shipp) fout_ext(NULL, "%s", Cargo_names[ptr->subsys_cargo_name]); } + if (Mission_save_format != FSO_FORMAT_RETAIL) { + if (ptr->subsys_cargo_title[0] != '\0') { + if (optional_string_fred("+Cargo Title:", "$Name:", "+Subsystem:")) { + parse_comments(); + } else { + fout("\n+Cargo Title:"); + } + fout_ext(nullptr, "%s", ptr->subsys_cargo_title); + } + } + if (ptr->system_info->type == SUBSYSTEM_TURRET) save_turret_info(ptr, SHIP_INDEX(shipp)); @@ -3601,6 +3612,17 @@ int CFred_mission_save::save_objects() parse_comments(); fout_ext(" ", "%s", Cargo_names[shipp->cargo1]); + if (Mission_save_format != FSO_FORMAT_RETAIL) { + if (shipp->cargo_title[0] != '\0') { + if (optional_string_fred("$Cargo Title:", "$Name:")) { + parse_comments(); + } else { + fout("\n$Cargo Title:"); + } + fout_ext(nullptr, "%s", shipp->cargo_title); + } + } + save_common_object_data(&Objects[shipp->objnum], &Ships[i]); if (shipp->wingnum >= 0) { diff --git a/qtfred/src/mission/missionsave.cpp b/qtfred/src/mission/missionsave.cpp index 54044d294e4..aa110e1ecb5 100644 --- a/qtfred/src/mission/missionsave.cpp +++ b/qtfred/src/mission/missionsave.cpp @@ -1590,6 +1590,17 @@ int CFred_mission_save::save_common_object_data(object* objp, ship* shipp) fout_ext(NULL, "%s", Cargo_names[ptr->subsys_cargo_name]); } + if (save_format != MissionFormat::RETAIL) { + if (ptr->subsys_cargo_title[0] != '\0') { + if (optional_string_fred("+Cargo Title:", "$Name:", "+Subsystem:")) { + parse_comments(); + } else { + fout("\n+Cargo Title:"); + } + fout_ext(nullptr, "%s", ptr->subsys_cargo_title); + } + } + if (ptr->system_info->type == SUBSYSTEM_TURRET) { save_turret_info(ptr, SHIP_INDEX(shipp)); } @@ -3519,6 +3530,17 @@ int CFred_mission_save::save_objects() parse_comments(); fout_ext(" ", "%s", Cargo_names[shipp->cargo1]); + if (save_format != MissionFormat::RETAIL) { + if (shipp->cargo_title[0] != '\0') { + if (optional_string_fred("$Cargo Title:", "$Name:")) { + parse_comments(); + } else { + fout("\n$Cargo Title:"); + } + fout_ext(nullptr, "%s", shipp->cargo_title); + } + } + save_common_object_data(&Objects[shipp->objnum], &Ships[i]); if (shipp->wingnum >= 0) { From 574c39210f578c7dd6d39c44a7b7adb162b3a4bb Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Sat, 8 Jun 2024 17:26:51 -0500 Subject: [PATCH 45/63] Ingame options respect mods (#5917) * Save/Load player options to a mod-specific ini file if possible * use rfind instead of find to ensure it's the last dash character used for trimming * cleanup + comments + nullptr * A more robust way of getting the mod root using lambdas * prevent infinite loop for non Knossos/Knet mods * Notify the player if options may need updating and pull settings from fs2_open.ini if we can * proper localization * update xstr --- code/localization/localize.cpp | 2 +- code/menuui/playermenu.cpp | 4 + code/options/OptionsManager.cpp | 6 +- code/osapi/osregistry.cpp | 187 +++++++++++++++++++++++++++----- code/osapi/osregistry.h | 12 +- 5 files changed, 174 insertions(+), 37 deletions(-) diff --git a/code/localization/localize.cpp b/code/localization/localize.cpp index 842336cdca7..72215d55612 100644 --- a/code/localization/localize.cpp +++ b/code/localization/localize.cpp @@ -64,7 +64,7 @@ bool *Lcl_unexpected_tstring_check = nullptr; // NOTE: with map storage of XSTR strings, the indexes no longer need to be contiguous, // but internal strings should still increment XSTR_SIZE to avoid collisions. // retail XSTR_SIZE = 1570 -// #define XSTR_SIZE 1854 // This is the next available ID +// #define XSTR_SIZE 1855 // This is the next available ID // struct to allow for strings.tbl-determined x offset diff --git a/code/menuui/playermenu.cpp b/code/menuui/playermenu.cpp index e9160780bf9..228f002a128 100644 --- a/code/menuui/playermenu.cpp +++ b/code/menuui/playermenu.cpp @@ -406,6 +406,10 @@ void player_select_do() popup(PF_TITLE_BIG | PF_TITLE_RED | PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, text); Startup_warning_dialog_displayed = true; } + + if (!Ingame_options_save_found && Using_in_game_options) { + popup(PF_BODY_BIG | PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, XSTR("In-game Options are enabled but a save file could not be found. You may need to update your settings in the Options menu.", 1854)); + } // set the input box at the "virtual" line 0 to be active so the player can enter a callsign if (Player_select_input_mode) { diff --git a/code/options/OptionsManager.cpp b/code/options/OptionsManager.cpp index d04e50e17ef..a4cd4a38c32 100644 --- a/code/options/OptionsManager.cpp +++ b/code/options/OptionsManager.cpp @@ -57,7 +57,7 @@ std::unique_ptr OptionsManager::getValueFromConfig(const SCP_string& key throw std::runtime_error("Invalid key"); } - auto value = os_config_read_string(parts.first.c_str(), parts.second.c_str()); + auto value = os_config_read_string(parts.first.c_str(), parts.second.c_str(), (const char*)0, true); if (value == nullptr) { // TODO: This is not really an error but I would like to avoid return nullptr here... @@ -156,7 +156,7 @@ bool OptionsManager::persistOptionChanges(const options::OptionBase* option) auto val = json_dump_string(iter->second.get(), JSON_COMPACT | JSON_ENSURE_ASCII | JSON_ENCODE_ANY); - os_config_write_string(parts.first.c_str(), parts.second.c_str(), val.c_str()); + os_config_write_string(parts.first.c_str(), parts.second.c_str(), val.c_str(), true); auto changed = option->valueChanged(iter->second.get()); @@ -178,7 +178,7 @@ SCP_vector OptionsManager::persistChanges() auto val = json_dump_string(entry.second.get(), JSON_COMPACT | JSON_ENSURE_ASCII | JSON_ENCODE_ANY); - os_config_write_string(parts.first.c_str(), parts.second.c_str(), val.c_str()); + os_config_write_string(parts.first.c_str(), parts.second.c_str(), val.c_str(), true); } SCP_vector unchanged; diff --git a/code/osapi/osregistry.cpp b/code/osapi/osregistry.cpp index 60a918b53ad..66fcc373d20 100644 --- a/code/osapi/osregistry.cpp +++ b/code/osapi/osregistry.cpp @@ -23,6 +23,8 @@ #include #endif +bool Ingame_options_save_found = true; + namespace { // ------------------------------------------------------------------------------------------------------------ @@ -464,6 +466,7 @@ const char *Osreg_app_name = "FreeSpace2"; const char *Osreg_title = "FreeSpace 2"; const char *Osreg_config_file_name = "fs2_open.ini"; +SCP_string Mod_options_file_name = "data/retail_fs2_settings.ini"; #define DEFAULT_SECTION "Default" @@ -838,26 +841,122 @@ static void profile_save(Profile *profile, const char *file) // os registry functions ------------------------------------------------------------- static Profile* Osreg_profile = nullptr; +static Profile* Mod_settings_profile = nullptr; // initialize the registry. setup default keys to use -void os_init_registry_stuff(const char *company, const char *app) +void os_init_registry_stuff(const char* company, const char* app) { if (company) { strcpy_s(szCompanyName, company); - } - else { + } else { strcpy_s(szCompanyName, Osreg_company_name); } if (app) { strcpy_s(szAppName, app); - } - else { + } else { strcpy_s(szAppName, Osreg_app_name); } Osreg_profile = profile_read(Osreg_config_file_name); + // Handle mod specific settings and in-game options through a mod specific file + // This gets the mod file using the mod cmdline string, stripping Launcher version data + // so that mod settings are mod-version agnostic but still specific to a unique mod. + if (Cmdline_mod != nullptr) { + SCP_string str = Cmdline_mod; + // Trim any trailing folders so we get just the name of the root mod folder + str = str.substr(0, str.find_first_of(DIR_SEPARATOR_CHAR)); + + // Now trim off any Knossos versioning details so that settings are not mod version specific + // This is a little silly because Knossos and KNet sometimes append other stuff to the mod folder + // like "-DevEnv". So what we do here is go section by section across the string using "-" as a + // delimiter. If that section is not the semantic version for the mod we discard it. Once we find + // the semantic version, we know we're done. Drop the version and we have the full mod folder string. + // This allows for mods that have a "-" in the folder string while also handling any number of trailing + // data sections. + auto isSemanticVersion = [](const SCP_string& input) { + int dotCount = 0; + bool isNumeric = true; + + for (char c : input) { + if (c == '.') { + dotCount++; + } else if (!(c >= '0' && c <= '9')) { + isNumeric = false; + break; + } + } + + // I don't think true Semantic versioning allows for additional dots + // and anything after a dash would be handled in a previous run of this lambda + // but in our limited use-case here, allowing for 2 or more dots is probably fine. + // The point is to find the version so we have a reference point in the string + // so the exact format isn't super important here. + return (dotCount >= 2 && isNumeric); + }; + + auto getLastSection = [](const SCP_string& input) + { + // Find the position of the last dash in the string + size_t pos = input.rfind('-'); + // Extract the substring before the last dash (if found) + SCP_string result = (pos != SCP_string::npos) ? input.substr(pos + 1) : input; + + return result; + }; + + int count = 0; + + // The count is used here as a limiter. If we don't find the semantic version after a + // few tries then we are probably running the game outside of the Knossos/KNet environment. + // So after that we should give up and go with the string we have. + while (!isSemanticVersion(getLastSection(str)) && (count <= 4)) { + size_t dashPos = str.rfind("-"); + str = (dashPos != std::string::npos) ? str.substr(0, dashPos) : str; + count++; + } + + // Now we know we have just the mod root and the version. So drop the version and we're done! + size_t pos = str.rfind("-"); + str = (pos != std::string::npos) ? str.substr(0, pos) : str; + + // Make sure we have a usable string + if (str.length() > 0) { + // Append "_settings.ini" and use the data/ directory + Mod_options_file_name = "data/" + str + "_settings.ini"; + + // Test if the new save file exists + FILE* file = fopen(os_get_config_path(Mod_options_file_name).c_str(), "r"); + if (file != nullptr) { + fclose(file); + } else { + Ingame_options_save_found = false; + } + } else { + // If we can't find the mod specific string then fallback to the fs2_open ini + // We don't set Ingame_options_save_found to false here because if we do that + // then we are assuming we have a proper save file to use later during runtime, + // but in this case we do not. + Mod_options_file_name = Osreg_config_file_name; + } + } + + mprintf(("Setting local mod settings ini file to '%s'\n", Mod_options_file_name.c_str())); + + // If the mod settings file doesn't exist create it if it doesn't so that the + // Mod_options_profile can be written to later during runtime. + FILE* fp = fopen(os_get_config_path(Mod_options_file_name).c_str(), "a"); + fclose(fp); + + // Load the mod settings profile if we have one, otherwise pull the settings from the + // fs2_open.ini to start with. + if (Ingame_options_save_found) { + Mod_settings_profile = profile_read(Mod_options_file_name.c_str()); + } else { + Mod_settings_profile = profile_read(Osreg_config_file_name); + } + Os_reg_inited = 1; } void os_deinit_registry_stuff() @@ -866,39 +965,55 @@ void os_deinit_registry_stuff() profile_free(Osreg_profile); Osreg_profile = nullptr; } + + if (Mod_settings_profile != nullptr) { + profile_free(Mod_settings_profile); + Mod_settings_profile = nullptr; + } } -bool os_config_has_value(const char* section, const char* name) { +bool os_config_has_value(const char* section, const char* name, bool use_mod_file) +{ #ifdef WIN32 - if (Osreg_profile == nullptr) { + if (!use_mod_file && Osreg_profile == nullptr) { // No config file, fall back to registy return registry_read_string(section, name, nullptr) != nullptr; } #endif + Profile* profile = Osreg_profile; + if (use_mod_file) { + profile = Mod_settings_profile; + } + if (section == nullptr) section = DEFAULT_SECTION; - char *ptr = profile_get_value(Osreg_profile, section, name); + char* ptr = profile_get_value(profile, section, name); return ptr != nullptr; } -const char *os_config_read_string(const char *section, const char *name, const char *default_value) +const char* os_config_read_string(const char* section, const char* name, const char* default_value, bool use_mod_file) { #ifdef WIN32 - if (Osreg_profile == nullptr) { + if (!use_mod_file && Osreg_profile == nullptr) { // No config file, fall back to registy return registry_read_string(section, name, default_value); } #endif + Profile* profile = Osreg_profile; + if (use_mod_file) { + profile = Mod_settings_profile; + } + nprintf(("Registry", "os_config_read_string(): section = \"%s\", name = \"%s\", default value: \"%s\"\n", (section) ? section : DEFAULT_SECTION, name, (default_value) ? default_value : NOX("NULL"))); - if (section == NULL) + if (section == nullptr) section = DEFAULT_SECTION; - char *ptr = profile_get_value(Osreg_profile, section, name); + char* ptr = profile_get_value(profile, section, name); - if (ptr != NULL) { + if (ptr != nullptr) { strncpy(tmp_string_data, ptr, 1023); default_value = tmp_string_data; } @@ -906,64 +1021,80 @@ const char *os_config_read_string(const char *section, const char *name, const c return default_value; } -unsigned int os_config_read_uint(const char *section, const char *name, unsigned int default_value) +unsigned int os_config_read_uint(const char* section, const char* name, unsigned int default_value, bool use_mod_file) { #ifdef WIN32 - if (Osreg_profile == nullptr) { + if (!use_mod_file && Osreg_profile == nullptr) { // No config file, fall back to registy return registry_read_uint(section, name, default_value); } #endif + Profile* profile = Osreg_profile; + if (use_mod_file) { + profile = Mod_settings_profile; + } - if (section == NULL) + if (section == nullptr) section = DEFAULT_SECTION; - char *ptr = profile_get_value(Osreg_profile, section, name); + char* ptr = profile_get_value(profile, section, name); - if (ptr != NULL) { + if (ptr != nullptr) { default_value = atoi(ptr); } return default_value; } -void os_config_write_string(const char *section, const char *name, const char *value) +void os_config_write_string(const char* section, const char* name, const char* value, bool use_mod_file) { #ifdef WIN32 // When there is no config file then it shouldn't be created because that would "hide" all previous settings // Instead fall back to writing the settings to the config file - if (Osreg_profile == nullptr) { + if (!use_mod_file && Osreg_profile == nullptr) { registry_write_string(section, name, value); return; } #endif + Profile* profile = Osreg_profile; + const char* file = Osreg_config_file_name; + if (use_mod_file) { + profile = Mod_settings_profile; + file = Mod_options_file_name.c_str(); + } - if (section == NULL) + if (section == nullptr) section = DEFAULT_SECTION; - Osreg_profile = profile_update(Osreg_profile, section, name, value); - profile_save(Osreg_profile, Osreg_config_file_name); + profile = profile_update(profile, section, name, value); + profile_save(profile, file); } -void os_config_write_uint(const char *section, const char *name, unsigned int value) +void os_config_write_uint(const char* section, const char* name, unsigned int value, bool use_mod_file) { #ifdef WIN32 // When there is no config file then it shouldn't be created because that would "hide" all previous settings // Instead fall back to writing the settings to the config file - if (Osreg_profile == nullptr) { + if (!use_mod_file && Osreg_profile == nullptr) { registry_write_uint(section, name, value); return; } #endif + Profile* profile = Osreg_profile; + const char* file = Osreg_config_file_name; + if (use_mod_file) { + profile = Mod_settings_profile; + file = Mod_options_file_name.c_str(); + } - if (section == NULL) + if (section == nullptr) section = DEFAULT_SECTION; char buf[21]; snprintf(buf, 20, "%u", value); - Osreg_profile = profile_update(Osreg_profile, section, name, buf); - profile_save(Osreg_profile, Osreg_config_file_name); + profile = profile_update(profile, section, name, buf); + profile_save(profile, file); } diff --git a/code/osapi/osregistry.h b/code/osapi/osregistry.h index 8a197afde69..006c7836426 100644 --- a/code/osapi/osregistry.h +++ b/code/osapi/osregistry.h @@ -25,6 +25,8 @@ extern const char *Osreg_title; extern const char *Osreg_config_file_name; +extern bool Ingame_options_save_found; + // ------------------------------------------------------------------------------------------------------------ // REGISTRY FUNCTIONS // @@ -39,22 +41,22 @@ void os_init_registry_stuff( const char *company, const char *app); void os_deinit_registry_stuff(); // Writes a string to the registry -void os_config_write_string( const char *section, const char *name, const char *value ); +void os_config_write_string(const char* section, const char* name, const char* value, bool use_mod_file = false); // Writes an unsigned int to the INI file. -void os_config_write_uint( const char *section, const char *name, unsigned int value ); +void os_config_write_uint(const char* section, const char* name, unsigned int value, bool use_mod_file = false); -bool os_config_has_value(const char *section, const char *name); +bool os_config_has_value(const char* section, const char* name, bool use_mod_file = false); // Reads a string from the INI file. If default is passed, // and the string isn't found, returns ptr to default otherwise // returns NULL; Copy the return value somewhere before // calling os_read_string again, because it might reuse the // same buffer. -const char * os_config_read_string( const char *section, const char *name, const char *default_value=0 /*NULL*/ ); +const char * os_config_read_string( const char *section, const char *name, const char *default_value=0, bool use_mod_file = false /*NULL*/ ); // Reads a string from the INI file. Default_value must // be passed, and if 'name' isn't found, then returns default_value -unsigned int os_config_read_uint( const char *section, const char *name, unsigned int default_value ); +unsigned int os_config_read_uint( const char *section, const char *name, unsigned int default_value, bool use_mod_file = false ); #endif From d5e387479e33cc0ba6f299a27dc10cbb80ab9de7 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Wed, 17 Apr 2024 22:26:23 -0400 Subject: [PATCH 46/63] replace pointers with indexes in object_h This refactors the object_h struct to store objects as indexes rather than pointers. This is a prerequisite for making the collection of objects dynamic in the future. See also #6100. Follow-up to #5800 and #5829. --- code/actions/types/MoveToSubmodel.cpp | 2 +- code/actions/types/ParticleEffectAction.cpp | 4 +- code/actions/types/PlaySoundAction.cpp | 6 +- code/ai/ai.h | 2 +- code/ai/aicode.cpp | 6 +- code/camera/camera.cpp | 22 +- code/decals/decals.cpp | 12 +- code/object/object.cpp | 38 +++ code/object/object.h | 30 +- code/particle/ParticleSource.cpp | 22 +- code/particle/ParticleSource.h | 2 +- code/scripting/api/libs/graphics.cpp | 12 +- code/scripting/api/libs/hud.cpp | 2 +- code/scripting/api/libs/mission.cpp | 12 +- code/scripting/api/libs/testing.cpp | 4 +- code/scripting/api/objs/ai_helper.cpp | 6 +- code/scripting/api/objs/asteroid.cpp | 10 +- code/scripting/api/objs/beam.cpp | 62 ++-- code/scripting/api/objs/camera.cpp | 8 +- code/scripting/api/objs/debris.cpp | 16 +- code/scripting/api/objs/decaldefinition.cpp | 2 +- code/scripting/api/objs/fireball.cpp | 20 +- code/scripting/api/objs/model_path.cpp | 2 +- code/scripting/api/objs/object.cpp | 110 ++++--- code/scripting/api/objs/order.cpp | 71 ++-- code/scripting/api/objs/particle.cpp | 2 +- code/scripting/api/objs/physics_info.cpp | 44 ++- code/scripting/api/objs/shields.cpp | 14 +- code/scripting/api/objs/ship.cpp | 339 ++++++++++---------- code/scripting/api/objs/ship_bank.cpp | 18 +- code/scripting/api/objs/subsystem.cpp | 40 +-- code/scripting/api/objs/waypoint.cpp | 4 +- code/scripting/api/objs/weapon.cpp | 54 ++-- code/scripting/api/objs/wing.cpp | 2 +- 34 files changed, 509 insertions(+), 491 deletions(-) diff --git a/code/actions/types/MoveToSubmodel.cpp b/code/actions/types/MoveToSubmodel.cpp index db291a3152f..46a462509cb 100644 --- a/code/actions/types/MoveToSubmodel.cpp +++ b/code/actions/types/MoveToSubmodel.cpp @@ -26,7 +26,7 @@ ActionResult MoveToSubmodel::execute(ProgramLocals& locals) const // The calling code should ensure that this never happens Assertion(locals.hostSubobject >= 0, "Did not have a valid host subobject."); - auto instance = object_get_model_instance(locals.host.objp); + auto instance = object_get_model_instance(locals.host.objp()); Assertion(instance != -1, "Model instances are required if a host subobject is specified."); auto pmi = model_get_instance(instance); diff --git a/code/actions/types/ParticleEffectAction.cpp b/code/actions/types/ParticleEffectAction.cpp index 7f4f2c16922..0b524c159dc 100644 --- a/code/actions/types/ParticleEffectAction.cpp +++ b/code/actions/types/ParticleEffectAction.cpp @@ -37,7 +37,7 @@ ActionResult ParticleEffectAction::execute(ProgramLocals& locals) const vec3d local_pos; matrix local_orient; if (locals.hostSubobject != -1) { - auto instance = object_get_model_instance(locals.host.objp); + auto instance = object_get_model_instance(locals.host.objp()); Assertion(instance != -1, "Model instances are required if a host subobject is specified."); auto pmi = model_get_instance(instance); @@ -59,7 +59,7 @@ ActionResult ParticleEffectAction::execute(ProgramLocals& locals) const auto direction = locals.variables.getValue({"locals", "direction"}).getVector(); - source.moveToObject(locals.host.objp, &local_pos); + source.moveToObject(locals.host.objp(), &local_pos); source.setOrientationFromNormalizedVec(&direction, true); source.finish(); diff --git a/code/actions/types/PlaySoundAction.cpp b/code/actions/types/PlaySoundAction.cpp index b99e2458a38..73a76dce3cf 100644 --- a/code/actions/types/PlaySoundAction.cpp +++ b/code/actions/types/PlaySoundAction.cpp @@ -26,7 +26,7 @@ ActionResult PlaySoundAction::execute(ProgramLocals& locals) const vec3d local_pos; matrix local_orient; if (locals.hostSubobject != -1) { - auto instance = object_get_model_instance(locals.host.objp); + auto instance = object_get_model_instance(locals.host.objp()); Assertion(instance != -1, "Model instances are required if a host subobject is specified."); auto pmi = model_get_instance(instance); @@ -47,8 +47,8 @@ ActionResult PlaySoundAction::execute(ProgramLocals& locals) const local_pos += locals.variables.getValue({"locals", "position"}).getVector(); vec3d global_pos; - vm_vec_unrotate(&global_pos, &local_pos, &locals.host.objp->orient); - global_pos += locals.host.objp->pos; + vm_vec_unrotate(&global_pos, &local_pos, &locals.host.objp()->orient); + global_pos += locals.host.objp()->pos; const auto soundId = gamesnd_get_by_name(m_soundIdExpression.execute(locals.variables).c_str()); diff --git a/code/ai/ai.h b/code/ai/ai.h index 2f02a13016b..f5fc94b8a75 100644 --- a/code/ai/ai.h +++ b/code/ai/ai.h @@ -635,6 +635,6 @@ void do_random_sidethrust(ai_info *aip, ship_info *sip); void ai_formation_object_recalculate_slotnums(int form_objnum, int exiting_objnum = -1); -bool test_line_of_sight(vec3d* from, vec3d* to, std::unordered_set&& excluded_objects = {}, float threshold = 10.0f, bool test_for_shields = false, bool test_for_hull = true, float* first_intersect_dist = nullptr, object** first_intersect_obj = nullptr); +bool test_line_of_sight(vec3d* from, vec3d* to, std::unordered_set&& excluded_object_ids = {}, float threshold = 10.0f, bool test_for_shields = false, bool test_for_hull = true, float* first_intersect_dist = nullptr, object** first_intersect_obj = nullptr); #endif diff --git a/code/ai/aicode.cpp b/code/ai/aicode.cpp index 9f757908ae8..6e547e712d0 100644 --- a/code/ai/aicode.cpp +++ b/code/ai/aicode.cpp @@ -6703,7 +6703,7 @@ bool check_los(int objnum, int target_objnum, float threshold, int primary_bank, vec3d end = Objects[target_objnum].pos; //Don't collision check against ourselves or our target - return test_line_of_sight(&start, &end, {&Objects[objnum], &Objects[target_objnum]}, threshold); + return test_line_of_sight(&start, &end, {objnum, target_objnum}, threshold); } // -------------------------------------------------------------------------- @@ -16698,7 +16698,7 @@ void maybe_cheat_fire_synaptic(object *objp) } } -bool test_line_of_sight(vec3d* from, vec3d* to, std::unordered_set&& excluded_objects, float threshold, bool test_for_shields, bool test_for_hull, float* first_intersect_dist, object** first_intersect_obj) { +bool test_line_of_sight(vec3d* from, vec3d* to, std::unordered_set&& excluded_object_ids, float threshold, bool test_for_shields, bool test_for_hull, float* first_intersect_dist, object** first_intersect_obj) { bool collides = false; for (object* objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) { @@ -16706,7 +16706,7 @@ bool test_line_of_sight(vec3d* from, vec3d* to, std::unordered_set 0) + if (excluded_object_ids.count(OBJ_INDEX(objp)) > 0) continue; int model_num = 0; diff --git a/code/camera/camera.cpp b/code/camera/camera.cpp index 90bd4a5fb15..268e8946dc3 100644 --- a/code/camera/camera.cpp +++ b/code/camera/camera.cpp @@ -364,10 +364,7 @@ void camera::do_frame(float /*in_frametime*/) object *camera::get_object_host() { - if(object_host.isValid()) - return object_host.objp; - else - return NULL; + return object_host.objp_or_null(); } int camera::get_object_host_submodel() @@ -377,10 +374,7 @@ int camera::get_object_host_submodel() object *camera::get_object_target() { - if(object_target.isValid()) - return object_target.objp; - else - return NULL; + return object_target.objp_or_null(); } int camera::get_object_target_submodel() @@ -424,7 +418,7 @@ void camera::get_info(vec3d *position, matrix *orientation, bool apply_camera_or if(object_host.isValid()) { - object *objp = object_host.objp; + object *objp = object_host.objp(); int model_num = object_get_model(objp); polymodel *pm = nullptr; polymodel_instance *pmi = nullptr; @@ -438,8 +432,8 @@ void camera::get_info(vec3d *position, matrix *orientation, bool apply_camera_or if(object_host_submodel < 0 || pm == NULL) { - vm_vec_unrotate(&c_pos, &pt, &object_host.objp->orient); - vm_vec_add2(&c_pos, &object_host.objp->pos); + vm_vec_unrotate(&c_pos, &pt, &object_host.objp()->orient); + vm_vec_add2(&c_pos, &object_host.objp()->pos); } else { @@ -488,7 +482,7 @@ void camera::get_info(vec3d *position, matrix *orientation, bool apply_camera_or { if(object_target.isValid()) { - object *target_objp = object_target.objp; + object *target_objp = object_target.objp(); int model_num = object_get_model(target_objp); polymodel *target_pm = nullptr; polymodel_instance *target_pmi = nullptr; @@ -525,7 +519,7 @@ void camera::get_info(vec3d *position, matrix *orientation, bool apply_camera_or { if(eyep) { - vm_vector_2_matrix(&c_ori, &host_normal, vm_vec_same(&host_normal, &object_host.objp->orient.vec.uvec)?NULL:&object_host.objp->orient.vec.uvec, NULL); + vm_vector_2_matrix(&c_ori, &host_normal, vm_vec_same(&host_normal, &object_host.objp()->orient.vec.uvec)?NULL:&object_host.objp()->orient.vec.uvec, NULL); target_set = true; } else if (use_host_orient) @@ -534,7 +528,7 @@ void camera::get_info(vec3d *position, matrix *orientation, bool apply_camera_or } else { - c_ori = object_host.objp->orient; + c_ori = object_host.objp()->orient; } } else diff --git a/code/decals/decals.cpp b/code/decals/decals.cpp index d7647c566f2..b08f8ddd16c 100644 --- a/code/decals/decals.cpp +++ b/code/decals/decals.cpp @@ -224,13 +224,13 @@ struct Decal { if (!object.isValid()) { return false; } - if (object.objp->flags[Object::Object_Flags::Should_be_dead]) { + if (object.objp()->flags[Object::Object_Flags::Should_be_dead]) { return false; } - if (orig_obj_type != object.objp->type) { + if (orig_obj_type != object.objp()->type) { mprintf(("Decal object type for object %d has changed from %s to %s. Please let m!m know about this\n", - OBJ_INDEX(object.objp), Object_type_names[orig_obj_type], Object_type_names[object.objp->type])); + object.objnum, Object_type_names[orig_obj_type], Object_type_names[object.objp()->type])); return false; } @@ -241,7 +241,7 @@ struct Decal { } } - auto objp = object.objp; + auto objp = object.objp(); if (objp->type == OBJ_SHIP) { auto shipp = &Ships[objp->instance]; auto model_instance = model_get_instance(shipp->model_instance_num); @@ -393,9 +393,9 @@ void initializeMission() { } matrix4 getDecalTransform(Decal& decal) { - Assertion(decal.object.objp->type == OBJ_SHIP, "Only ships are currently supported for decals!"); + Assertion(decal.object.objp()->type == OBJ_SHIP, "Only ships are currently supported for decals!"); - auto objp = decal.object.objp; + auto objp = decal.object.objp(); auto ship = &Ships[objp->instance]; auto pmi = model_get_instance(ship->model_instance_num); auto pm = model_get(pmi->model_num); diff --git a/code/object/object.cpp b/code/object/object.cpp index 5d776ace41a..50b922e67fb 100644 --- a/code/object/object.cpp +++ b/code/object/object.cpp @@ -81,6 +81,44 @@ int Object_next_signature = 1; //0 is bogus, start at 1 int Object_inited = 0; int Show_waypoints = 0; +object_h::object_h(int in_objnum) + : objnum(in_objnum) +{ + if (objnum >= 0 && objnum < MAX_OBJECTS) + sig = Objects[objnum].signature; + else + objnum = -1; +} + +object_h::object_h(const object* in_objp) +{ + if (in_objp) + { + objnum = OBJ_INDEX(in_objp); + sig = in_objp->signature; + } +} + +object_h::object_h() +{} + +bool object_h::isValid() const +{ + // a signature of 0 is invalid, per obj_init() + if (objnum < 0 || sig <= 0 || objnum >= MAX_OBJECTS) + return false; + return Objects[objnum].signature == sig; +} + +object* object_h::objp() const +{ + return &Objects[objnum]; +} + +object* object_h::objp_or_null() const +{ + return isValid() ? &Objects[objnum] : nullptr; +} //WMC - Made these prettier const char *Object_type_names[MAX_OBJECT_TYPES] = { diff --git a/code/object/object.h b/code/object/object.h index 4d9db32d89b..870e1f197d2 100644 --- a/code/object/object.h +++ b/code/object/object.h @@ -179,26 +179,16 @@ extern object Objects[]; struct object_h final // prevent subclassing because classes which might use this should have their own isValid member function { - object *objp; - int sig; - - bool isValid() const {return (objp != nullptr && objp->signature == sig && sig > 0); } - object_h(object *in) {objp = in; sig = (in == nullptr) ? -1 : in->signature; } - object_h() { objp = nullptr; sig = -1; } - - object_h(int objnum) - { - if (objnum >= 0 && objnum < MAX_OBJECTS) - { - objp = &Objects[objnum]; - sig = objp->signature; - } - else - { - objp = nullptr; - sig = -1; - } - } + int objnum = -1; + int sig = -1; + + object_h(const object* in_objp); + object_h(int in_objnum); + object_h(); + + bool isValid() const; + object* objp() const; + object* objp_or_null() const; static void serialize(lua_State* L, const scripting::ade_table_entry& tableEntry, const luacpp::LuaValue& value, ubyte* data, int& packet_size); static void deserialize(lua_State* L, const scripting::ade_table_entry& tableEntry, char* data_ptr, ubyte* data, int& offset); diff --git a/code/particle/ParticleSource.cpp b/code/particle/ParticleSource.cpp index e2a3768114a..804807627eb 100644 --- a/code/particle/ParticleSource.cpp +++ b/code/particle/ParticleSource.cpp @@ -18,8 +18,8 @@ void SourceOrigin::getGlobalPosition(vec3d* posOut) const { vec3d offset; switch (m_originType) { case SourceOriginType::OBJECT: { - *posOut = m_origin.m_object.objp->pos; - vm_vec_unrotate(&offset, &m_offset, &m_origin.m_object.objp->orient); + *posOut = m_origin.m_object.objp()->pos; + vm_vec_unrotate(&offset, &m_offset, &m_origin.m_object.objp()->orient); break; } case SourceOriginType::PARTICLE: { @@ -41,7 +41,7 @@ void SourceOrigin::getGlobalPosition(vec3d* posOut) const { break; } case SourceOriginType::BEAM: { - auto beam = &Beams[m_origin.m_object.objp->instance]; + auto beam = &Beams[m_origin.m_object.objp()->instance]; *posOut = beam->last_start; // weight the random points towards the start linearly // proportion along the beam the beam stopped, of its total potential length @@ -67,14 +67,14 @@ void SourceOrigin::getHostOrientation(matrix* matOut) const { vec3d vec; switch (m_originType) { case SourceOriginType::OBJECT: - *matOut = m_origin.m_object.objp->orient; + *matOut = m_origin.m_object.objp()->orient; break; case SourceOriginType::PARTICLE: vm_vector_2_matrix(matOut, &m_origin.m_particle.lock()->velocity, nullptr, nullptr); break; case SourceOriginType::BEAM: vec = vmd_zero_vector; - vm_vec_normalized_dir(&vec, &Beams[m_origin.m_object.objp->instance].last_shot, &Beams[m_origin.m_object.objp->instance].last_start); + vm_vec_normalized_dir(&vec, &Beams[m_origin.m_object.objp()->instance].last_shot, &Beams[m_origin.m_object.objp()->instance].last_start); vm_vector_2_matrix(matOut, &vec, nullptr, nullptr); break; case SourceOriginType::VECTOR: // Intentional fall-through, plain vectors have no orientation @@ -90,8 +90,8 @@ void SourceOrigin::applyToParticleInfo(particle_info& info, bool allow_relative) switch (m_originType) { case SourceOriginType::OBJECT: { if (allow_relative) { - info.attached_objnum = static_cast(OBJ_INDEX(m_origin.m_object.objp)); - info.attached_sig = m_origin.m_object.objp->signature; + info.attached_objnum = m_origin.m_object.objnum; + info.attached_sig = m_origin.m_object.sig; info.pos = m_offset; } else { @@ -125,11 +125,11 @@ void SourceOrigin::applyToParticleInfo(particle_info& info, bool allow_relative) vec3d SourceOrigin::getVelocity() const { switch (this->m_originType) { case SourceOriginType::OBJECT: - return m_origin.m_object.objp->phys_info.vel; + return m_origin.m_object.objp()->phys_info.vel; case SourceOriginType::PARTICLE: return m_origin.m_particle.lock()->velocity; case SourceOriginType::BEAM: { - beam* bm = &Beams[m_origin.m_object.objp->instance]; + beam* bm = &Beams[m_origin.m_object.objp()->instance]; vec3d vel; vm_vec_normalized_dir(&vel, &bm->last_shot, &bm->last_start); vm_vec_scale(&vel, Weapon_info[bm->weapon_info_index].max_speed); @@ -214,7 +214,7 @@ bool SourceOrigin::isValid() const { return false; } - auto objp = m_origin.m_object.objp; + auto objp = m_origin.m_object.objp(); if (objp->type != OBJ_WEAPON && objp->type != OBJ_BEAM) { // The following checks are only relevant for weapons @@ -398,7 +398,7 @@ void ParticleSource::initializeThrusterOffset(weapon* /*wp*/, weapon_info* wip) void ParticleSource::finishCreation() { if (m_origin.m_originType == SourceOriginType::OBJECT) { if (IS_VEC_NULL(&m_origin.m_offset)) { - object* obj = m_origin.m_origin.m_object.objp; + object* obj = m_origin.m_origin.m_object.objp(); if (obj->type == OBJ_WEAPON) { weapon* wp = &Weapons[obj->instance]; diff --git a/code/particle/ParticleSource.h b/code/particle/ParticleSource.h index d2bfe9ede66..200f808680d 100644 --- a/code/particle/ParticleSource.h +++ b/code/particle/ParticleSource.h @@ -77,7 +77,7 @@ class SourceOrigin { inline SourceOriginType getType() const { return m_originType; } - inline object* getObjectHost() const { return m_origin.m_object.objp; } + inline object* getObjectHost() const { return m_origin.m_object.objp_or_null(); } /** * @brief Determines if the origin is valid diff --git a/code/scripting/api/libs/graphics.cpp b/code/scripting/api/libs/graphics.cpp index 447ff567906..798e6ab77f3 100644 --- a/code/scripting/api/libs/graphics.cpp +++ b/code/scripting/api/libs/graphics.cpp @@ -1061,7 +1061,7 @@ ADE_FUNC(drawTargetingBrackets, l_Graphics, "object Object, [boolean draw=true, return ADE_RETURN_NIL; } - object *targetp = objh->objp; + object *targetp = objh->objp(); int x1,x2,y1,y2; int bound_rc, pof; @@ -1218,7 +1218,7 @@ ADE_FUNC(drawOffscreenIndicator, l_Graphics, "object Object, [boolean draw=true, return ADE_RETURN_NIL; } - object *targetp = objh->objp; + object *targetp = objh->objp(); bool in_frame = g3_in_frame() > 0; if (!in_frame) @@ -2171,8 +2171,8 @@ ADE_FUNC(createPersistentParticle, pi.reverse = false; if (objh != nullptr && objh->isValid()) { - pi.attached_objnum = OBJ_INDEX(objh->objp); - pi.attached_sig = objh->objp->signature; + pi.attached_objnum = objh->objnum; + pi.attached_sig = objh->sig; } particle::WeakParticlePtr p = particle::createPersistent(&pi); @@ -2244,8 +2244,8 @@ ADE_FUNC(createParticle, pi.reverse = false; if (objh != nullptr && objh->isValid()) { - pi.attached_objnum = OBJ_INDEX(objh->objp); - pi.attached_sig = objh->objp->signature; + pi.attached_objnum = objh->objnum; + pi.attached_sig = objh->sig; } particle::create(&pi); diff --git a/code/scripting/api/libs/hud.cpp b/code/scripting/api/libs/hud.cpp index f9725578b27..81ee2984d30 100644 --- a/code/scripting/api/libs/hud.cpp +++ b/code/scripting/api/libs/hud.cpp @@ -320,7 +320,7 @@ ADE_FUNC(getTargetDistance, l_HUD, "object targetee, [vector targeter_position]" } } - auto dist = hud_find_target_distance(targetee_h->objp, targeter_pos); + auto dist = hud_find_target_distance(targetee_h->objp(), targeter_pos); return ade_set_args(L, "f", dist); } diff --git a/code/scripting/api/libs/mission.cpp b/code/scripting/api/libs/mission.cpp index 166929327e1..7273f1626c6 100644 --- a/code/scripting/api/libs/mission.cpp +++ b/code/scripting/api/libs/mission.cpp @@ -928,7 +928,7 @@ ADE_FUNC(sendMessage, if (ship_h == nullptr || !ship_h->isValid()) return ADE_RETURN_FALSE; - sender = &Ships[ship_h->objp->instance]; + sender = &Ships[ship_h->objp()->instance]; messageSource = MESSAGE_SOURCE_SHIP; } @@ -1159,7 +1159,7 @@ ADE_FUNC(createDebris, if (source_ship == nullptr || !source_ship->isValid()) return ade_set_args(L, "o", l_Debris.Set(object_h())); - source_shipp = &Ships[source_ship->objp->instance]; + source_shipp = &Ships[source_ship->objp()->instance]; source_objnum = source_shipp->objnum; source_class = source_shipp->ship_info_index; model_num = Ship_info[source_class].model_num; @@ -1304,7 +1304,7 @@ ADE_FUNC(createWeapon, real_orient = orient->GetMatrix(); } - int parent_idx = (parent && parent->isValid()) ? OBJ_INDEX(parent->objp) : -1; + int parent_idx = (parent && parent->isValid()) ? parent->objnum : -1; int obj_idx = weapon_create(&pos, real_orient, wclass, parent_idx, group); @@ -1412,7 +1412,7 @@ ADE_FUNC(createExplosion, int type = big ? FIREBALL_LARGE_EXPLOSION : FIREBALL_MEDIUM_EXPLOSION; - int parent_idx = (parent && parent->isValid()) ? OBJ_INDEX(parent->objp) : -1; + int parent_idx = (parent && parent->isValid()) ? parent->objnum : -1; int obj_idx = fireball_create(&pos, fireballclass, type, parent_idx, radius, false, &velocity); @@ -2296,7 +2296,7 @@ int testLineOfSight_internal(lua_State* L, bool returnDist_and_Obj) { return ADE_RETURN_TRUE; } - std::unordered_set excludedObjectIDs; + std::unordered_set excludedObjectIDs; if (excludedObjects.isValid()) { for (const auto& object : excludedObjects) { @@ -2305,7 +2305,7 @@ int testLineOfSight_internal(lua_State* L, bool returnDist_and_Obj) { try { object_h obj; object.second.getValue(l_Object.Get(&obj)); - excludedObjectIDs.emplace(obj.objp); + excludedObjectIDs.emplace(obj.objnum); } catch (const luacpp::LuaException& /*e*/) { // We were likely fed a userdata that was not an object. diff --git a/code/scripting/api/libs/testing.cpp b/code/scripting/api/libs/testing.cpp index 07a013053bf..9c28ec51266 100644 --- a/code/scripting/api/libs/testing.cpp +++ b/code/scripting/api/libs/testing.cpp @@ -179,8 +179,8 @@ ADE_FUNC_DEPRECATED(createParticle, if(objh != NULL && objh->isValid()) { - pi.attached_objnum = OBJ_INDEX(objh->objp); - pi.attached_sig = objh->objp->signature; + pi.attached_objnum = objh->objnum; + pi.attached_sig = objh->sig; } particle::WeakParticlePtr p = particle::createPersistent(&pi); diff --git a/code/scripting/api/objs/ai_helper.cpp b/code/scripting/api/objs/ai_helper.cpp index 0a707ab909d..9c1e17f203c 100644 --- a/code/scripting/api/objs/ai_helper.cpp +++ b/code/scripting/api/objs/ai_helper.cpp @@ -89,7 +89,7 @@ ADE_FUNC(turnTowardsPoint, return ADE_RETURN_NIL; } - ai_turn_towards_vector(target, ship.objp, nullptr, nullptr, bank, (diffTurn ? 0 : AITTV_FAST) | (argnum >= 5 ? AITTV_FORCE_DELTA_BANK : 0), nullptr, modifier); + ai_turn_towards_vector(target, ship.objp(), nullptr, nullptr, bank, (diffTurn ? 0 : AITTV_FAST) | (argnum >= 5 ? AITTV_FORCE_DELTA_BANK : 0), nullptr, modifier); return ADE_RETURN_NIL; } @@ -111,9 +111,9 @@ ADE_FUNC(turnTowardsOrientation, } matrix* mat = target->GetMatrix(); - vec3d targetVec = mat->vec.fvec + ship.objp->pos; + vec3d targetVec = mat->vec.fvec + ship.objp()->pos; - ai_turn_towards_vector(&targetVec, ship.objp, nullptr, nullptr, 0.0f, (diffTurn ? 0 : AITTV_FAST), &mat->vec.rvec, modifier); + ai_turn_towards_vector(&targetVec, ship.objp(), nullptr, nullptr, 0.0f, (diffTurn ? 0 : AITTV_FAST), &mat->vec.rvec, modifier); return ADE_RETURN_NIL; } diff --git a/code/scripting/api/objs/asteroid.cpp b/code/scripting/api/objs/asteroid.cpp index 9d756223285..9648c3f2e84 100644 --- a/code/scripting/api/objs/asteroid.cpp +++ b/code/scripting/api/objs/asteroid.cpp @@ -23,11 +23,11 @@ ADE_VIRTVAR(Target, l_Asteroid, "object", "Asteroid target object; may be object if(!oh->isValid()) return ade_set_error(L, "o", l_Object.Set(object_h())); - asteroid *asp = &Asteroids[oh->objp->instance]; + asteroid *asp = &Asteroids[oh->objp()->instance]; if(ADE_SETTING_VAR && th != NULL) { if(th->isValid()) - asp->target_objnum = OBJ_INDEX(th->objp); + asp->target_objnum = th->objnum; else asp->target_objnum = -1; } @@ -53,12 +53,12 @@ ADE_FUNC(kill, l_Asteroid, "[ship killer=nil, vector hitpos=nil]", "Kills the as return ADE_RETURN_NIL; if (!hitpos) - hitpos = &victim->objp->pos; + hitpos = &victim->objp()->pos; if (killer) - asteroid_hit(victim->objp, killer->objp, hitpos, victim->objp->hull_strength + 1, nullptr); + asteroid_hit(victim->objp(), killer->objp(), hitpos, victim->objp()->hull_strength + 1, nullptr); else - asteroid_hit(victim->objp, NULL, hitpos, victim->objp->hull_strength + 1, nullptr); + asteroid_hit(victim->objp(), NULL, hitpos, victim->objp()->hull_strength + 1, nullptr); return ADE_RETURN_TRUE; } diff --git a/code/scripting/api/objs/beam.cpp b/code/scripting/api/objs/beam.cpp index 79d62b308ee..3f1414df01a 100644 --- a/code/scripting/api/objs/beam.cpp +++ b/code/scripting/api/objs/beam.cpp @@ -31,7 +31,7 @@ ADE_VIRTVAR(Class, l_Beam, "weaponclass", "Weapon's class", "weaponclass", "Weap if(!oh->isValid()) return ade_set_error(L, "o", l_Weaponclass.Set(-1)); - beam *bp = &Beams[oh->objp->instance]; + beam *bp = &Beams[oh->objp()->instance]; if(ADE_SETTING_VAR && nc > -1) { bp->weapon_info_index = nc; @@ -50,7 +50,7 @@ ADE_VIRTVAR(LastShot, l_Beam, "vector", "End point of the beam", "vector", "vect if(!oh->isValid()) return ade_set_error(L, "o", l_Vector.Set(vmd_zero_vector)); - beam *bp = &Beams[oh->objp->instance]; + beam *bp = &Beams[oh->objp()->instance]; if(ADE_SETTING_VAR && vec3) { bp->last_shot = *vec3; @@ -69,7 +69,7 @@ ADE_VIRTVAR(LastStart, l_Beam, "vector", "Start point of the beam", "vector", "v if(!oh->isValid()) return ade_set_error(L, "o", l_Vector.Set(vmd_zero_vector)); - beam *bp = &Beams[oh->objp->instance]; + beam *bp = &Beams[oh->objp()->instance]; if(ADE_SETTING_VAR && v3) { bp->last_start = *v3; @@ -89,8 +89,8 @@ ADE_VIRTVAR(Target, l_Beam, "object", "Target of beam. Value may also be a deriv return ade_set_error(L, "o", l_Object.Set(object_h())); beam *bp = NULL; - if(objh->objp->instance > -1) - bp = &Beams[objh->objp->instance]; + if(objh->objp()->instance > -1) + bp = &Beams[objh->objp()->instance]; else return ade_set_error(L, "o", l_Object.Set(object_h())); @@ -100,7 +100,7 @@ ADE_VIRTVAR(Target, l_Beam, "object", "Target of beam. Value may also be a deriv { if(bp->target_sig != newh->sig) { - bp->target = newh->objp; + bp->target = newh->objp(); bp->target_sig = newh->sig; } } @@ -125,8 +125,8 @@ ADE_VIRTVAR(TargetSubsystem, l_Beam, "subsystem", "Subsystem that beam is target return ade_set_error(L, "o", l_Subsystem.Set(ship_subsys_h())); beam *bp = NULL; - if(objh->objp->instance > -1) - bp = &Beams[objh->objp->instance]; + if(objh->objp()->instance > -1) + bp = &Beams[objh->objp()->instance]; else return ade_set_error(L, "o", l_Subsystem.Set(ship_subsys_h())); @@ -136,7 +136,7 @@ ADE_VIRTVAR(TargetSubsystem, l_Beam, "subsystem", "Subsystem that beam is target { if(bp->target_sig != newh->objh.sig) { - bp->target = newh->objh.objp; + bp->target = newh->objh.objp(); bp->target_subsys = newh->ss; bp->target_sig = newh->objh.sig; } @@ -162,8 +162,8 @@ ADE_VIRTVAR(ParentShip, l_Beam, "object", "Parent of the beam.", "object", "Beam return ade_set_error(L, "o", l_Object.Set(object_h())); beam *bp = NULL; - if(objh->objp->instance > -1) - bp = &Beams[objh->objp->instance]; + if(objh->objp()->instance > -1) + bp = &Beams[objh->objp()->instance]; else return ade_set_error(L, "o", l_Object.Set(object_h())); @@ -173,7 +173,7 @@ ADE_VIRTVAR(ParentShip, l_Beam, "object", "Parent of the beam.", "object", "Beam { if(bp->sig != newh->sig) { - bp->objp = newh->objp; + bp->objp = newh->objp(); bp->sig = newh->sig; } } @@ -198,8 +198,8 @@ ADE_VIRTVAR(ParentSubsystem, l_Beam, "subsystem", "Subsystem that beam is fired return ade_set_error(L, "o", l_Subsystem.Set(ship_subsys_h())); beam *bp = NULL; - if(objh->objp->instance > -1) - bp = &Beams[objh->objp->instance]; + if(objh->objp()->instance > -1) + bp = &Beams[objh->objp()->instance]; else return ade_set_error(L, "o", l_Subsystem.Set(ship_subsys_h())); @@ -209,7 +209,7 @@ ADE_VIRTVAR(ParentSubsystem, l_Beam, "subsystem", "Subsystem that beam is fired { if(bp->sig != newh->objh.sig) { - bp->objp = newh->objh.objp; + bp->objp = newh->objh.objp(); bp->subsys = newh->ss; } } @@ -233,7 +233,7 @@ ADE_VIRTVAR(Team, l_Beam, "team", "Beam's team", "team", "Beam team, or invalid if (!oh->isValid()) return ade_set_error(L, "o", l_Team.Set(-1)); - beam* b = &Beams[oh->objp->instance]; + beam* b = &Beams[oh->objp()->instance]; if (ADE_SETTING_VAR && nt >= 0 && nt < (int)Iff_info.size()) b->team = (char)nt; @@ -248,8 +248,8 @@ ADE_FUNC(getCollisionCount, l_Beam, NULL, "Get the number of collisions in frame return ADE_RETURN_NIL; beam *bp = NULL; - if(objh->objp->instance > -1) - bp = &Beams[objh->objp->instance]; + if(objh->objp()->instance > -1) + bp = &Beams[objh->objp()->instance]; else return ADE_RETURN_NIL; @@ -269,8 +269,8 @@ ADE_FUNC(getCollisionPosition, l_Beam, "number", "Get the position of the define return ade_set_error(L, "o", l_Vector.Set(vmd_zero_vector)); beam *bp = NULL; - if(objh->objp->instance > -1) - bp = &Beams[objh->objp->instance]; + if(objh->objp()->instance > -1) + bp = &Beams[objh->objp()->instance]; else return ade_set_error(L, "o", l_Vector.Set(vmd_zero_vector)); @@ -292,8 +292,8 @@ ADE_FUNC(getCollisionInformation, l_Beam, "number", "Get the collision informati return ade_set_error(L, "o", l_ColInfo.Set(mc_info_h())); beam *bp = NULL; - if(objh->objp->instance > -1) - bp = &Beams[objh->objp->instance]; + if(objh->objp()->instance > -1) + bp = &Beams[objh->objp()->instance]; else return ade_set_error(L, "o", l_ColInfo.Set(mc_info_h())); @@ -314,8 +314,8 @@ ADE_FUNC(getCollisionObject, l_Beam, "number", "Get the target of the defined co return ade_set_error(L, "o", l_Object.Set(object_h())); beam *bp = NULL; - if(objh->objp->instance > -1) - bp = &Beams[objh->objp->instance]; + if(objh->objp()->instance > -1) + bp = &Beams[objh->objp()->instance]; else return ade_set_error(L, "o", l_Object.Set(object_h())); @@ -336,8 +336,8 @@ ADE_FUNC(isExitCollision, l_Beam, "number", "Checks if the defined collision was return ADE_RETURN_NIL; beam *bp = NULL; - if(objh->objp->instance > -1) - bp = &Beams[objh->objp->instance]; + if(objh->objp()->instance > -1) + bp = &Beams[objh->objp()->instance]; else return ADE_RETURN_NIL; @@ -355,8 +355,8 @@ ADE_FUNC(getStartDirectionInfo, l_Beam, NULL, "Gets the start information about return ade_set_error(L, "o", l_Vector.Set(vmd_zero_vector)); beam *bp = NULL; - if(objh->objp->instance > -1) - bp = &Beams[objh->objp->instance]; + if(objh->objp()->instance > -1) + bp = &Beams[objh->objp()->instance]; else return ade_set_error(L, "o", l_Vector.Set(vmd_zero_vector)); @@ -372,8 +372,8 @@ ADE_FUNC(getEndDirectionInfo, l_Beam, NULL, "Gets the end information about the return ade_set_error(L, "o", l_Vector.Set(vmd_zero_vector)); beam *bp = NULL; - if(objh->objp->instance > -1) - bp = &Beams[objh->objp->instance]; + if(objh->objp()->instance > -1) + bp = &Beams[objh->objp()->instance]; else return ade_set_error(L, "o", l_Vector.Set(vmd_zero_vector)); @@ -391,7 +391,7 @@ ADE_FUNC(vanish, l_Beam, nullptr, "Vanishes this beam from the mission.", "boole if (!objh->isValid()) return ADE_RETURN_FALSE; - beam_delete(&Beams[objh->objp->instance]); + beam_delete(&Beams[objh->objp()->instance]); return ADE_RETURN_TRUE; } diff --git a/code/scripting/api/objs/camera.cpp b/code/scripting/api/objs/camera.cpp index 21f669d35b6..789012ca13d 100644 --- a/code/scripting/api/objs/camera.cpp +++ b/code/scripting/api/objs/camera.cpp @@ -120,7 +120,7 @@ ADE_VIRTVAR(Self, l_Camera, "object", "New mount object", "object", "Camera obje return ade_set_error(L, "o", l_Object.Set(object_h())); if(ADE_SETTING_VAR && oh && oh->isValid()) { - cid.getCamera()->set_object_host(oh->objp); + cid.getCamera()->set_object_host(oh->objp()); } return ade_set_object_with_breed(L, OBJ_INDEX(cid.getCamera()->get_object_host())); @@ -137,7 +137,7 @@ ADE_VIRTVAR(SelfSubsystem, l_Camera, "subsystem", "New mount object subsystem", return ade_set_error(L, "o", l_Subsystem.Set(ship_subsys_h())); if(ADE_SETTING_VAR && sso && sso->isValid()) { - cid.getCamera()->set_object_host(sso->objh.objp, sso->ss->system_info->subobj_num); + cid.getCamera()->set_object_host(sso->objh.objp(), sso->ss->system_info->subobj_num); } object *objp = cid.getCamera()->get_object_host(); @@ -175,7 +175,7 @@ ADE_VIRTVAR(Target, l_Camera, "object", "New target object", "object", "Camera t return ade_set_error(L, "o", l_Object.Set(object_h())); if(ADE_SETTING_VAR && oh && oh->isValid()) { - cid.getCamera()->set_object_target(oh->objp); + cid.getCamera()->set_object_target(oh->objp()); } return ade_set_object_with_breed(L, OBJ_INDEX(cid.getCamera()->get_object_target())); @@ -192,7 +192,7 @@ ADE_VIRTVAR(TargetSubsystem, l_Camera, "subsystem", "New target subsystem", "sub return ade_set_error(L, "o", l_Subsystem.Set(ship_subsys_h())); if(ADE_SETTING_VAR && sso && sso->isValid()) { - cid.getCamera()->set_object_target(sso->objh.objp, sso->ss->system_info->subobj_num); + cid.getCamera()->set_object_target(sso->objh.objp(), sso->ss->system_info->subobj_num); } object *objp = cid.getCamera()->get_object_target(); diff --git a/code/scripting/api/objs/debris.cpp b/code/scripting/api/objs/debris.cpp index 3f07194eeac..7ff9e93a735 100644 --- a/code/scripting/api/objs/debris.cpp +++ b/code/scripting/api/objs/debris.cpp @@ -25,7 +25,7 @@ ADE_VIRTVAR(IsHull, l_Debris, "boolean", "Whether or not debris is a piece of hu if(!oh->isValid()) return ade_set_error(L, "b", false); - debris *db = &Debris[oh->objp->instance]; + debris *db = &Debris[oh->objp()->instance]; if(ADE_SETTING_VAR) { db->is_hull = b; @@ -45,7 +45,7 @@ ADE_VIRTVAR(OriginClass, l_Debris, "shipclass", "The shipclass of the ship this if(!oh->isValid()) return ade_set_error(L, "o", l_Shipclass.Set(-1)); - debris *db = &Debris[oh->objp->instance]; + debris *db = &Debris[oh->objp()->instance]; if(ADE_SETTING_VAR) { if (shipIdx >= 0 && shipIdx < ship_info_size()) @@ -66,7 +66,7 @@ ADE_VIRTVAR(DoNotExpire, l_Debris, "boolean", "Whether the debris should expire. if (!objh->isValid()) return ADE_RETURN_NIL; - debris *db = &Debris[objh->objp->instance]; + debris *db = &Debris[objh->objp()->instance]; if (ADE_SETTING_VAR) { @@ -100,7 +100,7 @@ ADE_VIRTVAR(LifeLeft, l_Debris, "number", "The time this debris piece will last. if (!objh->isValid()) return ADE_RETURN_NIL; - debris *db = &Debris[objh->objp->instance]; + debris *db = &Debris[objh->objp()->instance]; if (ADE_SETTING_VAR) db->lifeleft = lifeleft; @@ -117,7 +117,7 @@ ADE_FUNC(getDebrisRadius, l_Debris, NULL, "The radius of this debris piece", "nu if(!oh->isValid()) return ade_set_error(L, "f", -1.0f); - debris *db = &Debris[oh->objp->instance]; + debris *db = &Debris[oh->objp()->instance]; polymodel *pm = model_get(db->model_num); @@ -148,7 +148,7 @@ ADE_FUNC(isGeneric, l_Debris, nullptr, "Return if this debris is the generic deb if (!oh->isValid()) return ADE_RETURN_FALSE; - debris *db = &Debris[oh->objp->instance]; + debris *db = &Debris[oh->objp()->instance]; return ade_set_args(L, "b", debris_is_generic(db)); } @@ -162,7 +162,7 @@ ADE_FUNC(isVaporized, l_Debris, nullptr, "Return if this debris is the vaporized if (!oh->isValid()) return ADE_RETURN_FALSE; - debris *db = &Debris[oh->objp->instance]; + debris *db = &Debris[oh->objp()->instance]; return ade_set_args(L, "b", debris_is_vaporized(db)); } @@ -178,7 +178,7 @@ ADE_FUNC(vanish, l_Debris, nullptr, "Vanishes this piece of debris from the miss return ade_set_error(L, "b", false); //This skips all the fancy deathroll stuff, and just cleans it from the mission - oh->objp->flags.set(Object::Object_Flags::Should_be_dead); + oh->objp()->flags.set(Object::Object_Flags::Should_be_dead); return ade_set_args(L, "b", true); } diff --git a/code/scripting/api/objs/decaldefinition.cpp b/code/scripting/api/objs/decaldefinition.cpp index 559aa6a0c11..84bb6306eb5 100644 --- a/code/scripting/api/objs/decaldefinition.cpp +++ b/code/scripting/api/objs/decaldefinition.cpp @@ -101,7 +101,7 @@ ADE_FUNC(create, l_DecalDefinitionclass, "number width, number height, number mi else info.lifetime = util::UniformFloatRange(minLifetime, maxLifetime); - decals::addDecal(info, objh->objp, smh->GetSubmodelIndex(), local_pos == nullptr ? vmd_zero_vector : *local_pos, local_orient == nullptr ? vmd_identity_matrix : *local_orient->GetMatrix()); + decals::addDecal(info, objh->objp(), smh->GetSubmodelIndex(), local_pos == nullptr ? vmd_zero_vector : *local_pos, local_orient == nullptr ? vmd_identity_matrix : *local_orient->GetMatrix()); return ADE_RETURN_NIL; } diff --git a/code/scripting/api/objs/fireball.cpp b/code/scripting/api/objs/fireball.cpp index e7578aaf3da..4f5d42df770 100644 --- a/code/scripting/api/objs/fireball.cpp +++ b/code/scripting/api/objs/fireball.cpp @@ -23,10 +23,10 @@ ADE_VIRTVAR(Class, l_Fireball, "fireballclass", "Fireball's class", "fireballcla if(!oh->isValid()) return ade_set_error(L, "o", l_Fireballclass.Set(-1)); - if (oh->objp->instance < 0 || oh->objp->instance >= static_cast(Fireballs.size())) + if (oh->objp()->instance < 0 || oh->objp()->instance >= static_cast(Fireballs.size())) return ade_set_error(L, "o", l_Fireballclass.Set(-1)); - fireball *fb = &Fireballs[oh->objp->instance]; + fireball *fb = &Fireballs[oh->objp()->instance]; if(ADE_SETTING_VAR && nc > -1) { fb->fireball_info_index = nc; @@ -45,10 +45,10 @@ ADE_VIRTVAR(RenderType, l_Fireball, "enumeration", "Fireball's render type", "en if (!oh->isValid()) return ade_set_error(L, "o", l_Enum.Set(enum_h())); - if (oh->objp->instance < 0 || oh->objp->instance >= static_cast(Fireballs.size())) + if (oh->objp()->instance < 0 || oh->objp()->instance >= static_cast(Fireballs.size())) return ade_set_error(L, "o", l_Enum.Set(enum_h())); - fireball* fb = &Fireballs[oh->objp->instance]; + fireball* fb = &Fireballs[oh->objp()->instance]; if (ADE_SETTING_VAR && type.isValid()) { int nt = -1; @@ -93,10 +93,10 @@ ADE_VIRTVAR(TimeElapsed, l_Fireball, NULL, "Time this fireball exists in seconds if(!oh->isValid()) return ade_set_error(L, "f", 0.0f); - if (oh->objp->instance < 0 || oh->objp->instance <= static_cast(Fireballs.size())) + if (oh->objp()->instance < 0 || oh->objp()->instance <= static_cast(Fireballs.size())) return ade_set_error(L, "f", 0.0f); - fireball *fb = &Fireballs[oh->objp->instance]; + fireball *fb = &Fireballs[oh->objp()->instance]; return ade_set_args(L, "f", fb->time_elapsed); } @@ -111,10 +111,10 @@ ADE_VIRTVAR(TotalTime, l_Fireball, NULL, "Total lifetime of the fireball's anima if (!oh->isValid()) return ade_set_error(L, "f", 0.0f); - if (oh->objp->instance < 0 || oh->objp->instance >= static_cast(Fireballs.size())) + if (oh->objp()->instance < 0 || oh->objp()->instance >= static_cast(Fireballs.size())) return ade_set_error(L, "f", 0.0f); - fireball* fb = &Fireballs[oh->objp->instance]; + fireball* fb = &Fireballs[oh->objp()->instance]; return ade_set_args(L, "f", fb->total_time); } @@ -128,7 +128,7 @@ ADE_FUNC(isWarp, l_Fireball, NULL, "Checks if the fireball is a warp effect.", " if(!oh->isValid()) return ADE_RETURN_FALSE; - if(fireball_is_warp(oh->objp)) + if(fireball_is_warp(oh->objp())) return ADE_RETURN_TRUE; return ADE_RETURN_FALSE; @@ -146,7 +146,7 @@ ADE_FUNC(vanish, l_Fireball, nullptr, "Vanishes this fireball from the mission." return ade_set_error(L, "b", false); //Should be sufficient for Fireballs, as the fireball internal functions also call this, for example to free up a fireball if the limit is reached - obj_delete(OBJ_INDEX(oh->objp)); + obj_delete(oh->objnum); return ade_set_args(L, "b", true); } diff --git a/code/scripting/api/objs/model_path.cpp b/code/scripting/api/objs/model_path.cpp index 59351406688..c1effaa51bf 100644 --- a/code/scripting/api/objs/model_path.cpp +++ b/code/scripting/api/objs/model_path.cpp @@ -47,7 +47,7 @@ ADE_VIRTVAR(Position, l_ModelPathPoint, "vector", "The current, global position } // A submodel is only valid if the object is a ship so this is safe - auto objp = p->parent->subsys.objh.objp; + auto objp = p->parent->subsys.objh.objp(); auto shipp = &Ships[objp->instance]; auto pmi = model_get_instance(shipp->model_instance_num); diff --git a/code/scripting/api/objs/object.cpp b/code/scripting/api/objs/object.cpp index 33a4cfd6b47..5b8951f0225 100644 --- a/code/scripting/api/objs/object.cpp +++ b/code/scripting/api/objs/object.cpp @@ -29,7 +29,7 @@ void object_h::serialize(lua_State* /*L*/, const scripting::ade_table_entry& /*tableEntry*/, const luacpp::LuaValue& value, ubyte* data, int& packet_size) { object_h obj; value.getValue(scripting::api::l_Object.Get(&obj)); - const ushort& netsig = obj.isValid() ? obj.objp->net_signature : 0; + const ushort& netsig = obj.isValid() ? obj.objp()->net_signature : 0; ADD_USHORT(netsig); } @@ -72,16 +72,16 @@ ADE_FUNC(__tostring, l_Object, NULL, "Returns name of object (if any)", "string" char buf[512]; - switch(objh->objp->type) + switch(objh->objp()->type) { case OBJ_SHIP: - sprintf(buf, "%s", Ships[objh->objp->instance].ship_name); + sprintf(buf, "%s", Ships[objh->objp()->instance].ship_name); break; case OBJ_WEAPON: - sprintf(buf, "%s projectile", Weapon_info[Weapons[objh->objp->instance].weapon_info_index].get_display_name()); + sprintf(buf, "%s projectile", Weapon_info[Weapons[objh->objp()->instance].weapon_info_index].get_display_name()); break; default: - sprintf(buf, "Object %d [%d]", OBJ_INDEX(objh->objp), objh->sig); + sprintf(buf, "Object %d [%d]", objh->objnum, objh->sig); } return ade_set_args(L, "s", buf); @@ -101,18 +101,18 @@ ADE_VIRTVAR(Parent, l_Object, "object", "Parent of the object. Value may also be { if(newparenth != NULL && newparenth->isValid()) { - objh->objp->parent = OBJ_INDEX(newparenth->objp); - objh->objp->parent_sig = newparenth->sig; + objh->objp()->parent = newparenth->objnum; + objh->objp()->parent_sig = newparenth->sig; } else { - objh->objp->parent = -1; - objh->objp->parent_sig = 0; + objh->objp()->parent = -1; + objh->objp()->parent_sig = 0; } } - if(objh->objp->parent > -1) - return ade_set_object_with_breed(L, objh->objp->parent); + if(objh->objp()->parent > -1) + return ade_set_object_with_breed(L, objh->objp()->parent); else return ade_set_args(L, "o", l_Object.Set(object_h())); } @@ -128,10 +128,10 @@ ADE_VIRTVAR(Radius, l_Object, "number", "Radius of an object", "number", "Radius return ade_set_error(L, "f", 0.0f); if (ADE_SETTING_VAR) { - objh->objp->radius = f; + objh->objp()->radius = f; } - return ade_set_args(L, "f", objh->objp->radius); + return ade_set_args(L, "f", objh->objp()->radius); } ADE_VIRTVAR(Position, l_Object, "vector", "Object world position (World vector)", "vector", "World position, or null vector if handle is invalid") @@ -145,17 +145,17 @@ ADE_VIRTVAR(Position, l_Object, "vector", "Object world position (World vector)" return ade_set_error(L, "o", l_Vector.Set(vmd_zero_vector)); if(ADE_SETTING_VAR && v3 != NULL) { - objh->objp->pos = *v3; - if (objh->objp->type == OBJ_WAYPOINT) { - waypoint *wpt = find_waypoint_with_objnum(OBJ_INDEX(objh->objp)); + objh->objp()->pos = *v3; + if (objh->objp()->type == OBJ_WAYPOINT) { + waypoint *wpt = find_waypoint_with_objnum(objh->objnum); wpt->set_pos(v3); } - if (objh->objp->flags[Object::Object_Flags::Collides]) - obj_collide_obj_cache_stale(objh->objp); + if (objh->objp()->flags[Object::Object_Flags::Collides]) + obj_collide_obj_cache_stale(objh->objp()); } - return ade_set_args(L, "o", l_Vector.Set(objh->objp->pos)); + return ade_set_args(L, "o", l_Vector.Set(objh->objp()->pos)); } ADE_VIRTVAR(LastPosition, l_Object, "vector", "Object world position as of last frame (World vector)", "vector", "World position, or null vector if handle is invalid") @@ -169,10 +169,10 @@ ADE_VIRTVAR(LastPosition, l_Object, "vector", "Object world position as of last return ade_set_error(L, "o", l_Vector.Set(vmd_zero_vector)); if(ADE_SETTING_VAR && v3 != NULL) { - objh->objp->last_pos = *v3; + objh->objp()->last_pos = *v3; } - return ade_set_args(L, "o", l_Vector.Set(objh->objp->last_pos)); + return ade_set_args(L, "o", l_Vector.Set(objh->objp()->last_pos)); } ADE_VIRTVAR(Orientation, l_Object, "orientation", "Object world orientation (World orientation)", "orientation", "Orientation, or null orientation if handle is invalid") @@ -186,13 +186,13 @@ ADE_VIRTVAR(Orientation, l_Object, "orientation", "Object world orientation (Wor return ade_set_error(L, "o", l_Matrix.Set(matrix_h(&vmd_identity_matrix))); if(ADE_SETTING_VAR && mh != NULL) { - objh->objp->orient = *mh->GetMatrix(); + objh->objp()->orient = *mh->GetMatrix(); - if (objh->objp->flags[Object::Object_Flags::Collides]) - obj_collide_obj_cache_stale(objh->objp); + if (objh->objp()->flags[Object::Object_Flags::Collides]) + obj_collide_obj_cache_stale(objh->objp()); } - return ade_set_args(L, "o", l_Matrix.Set(matrix_h(&objh->objp->orient))); + return ade_set_args(L, "o", l_Matrix.Set(matrix_h(&objh->objp()->orient))); } ADE_VIRTVAR(LastOrientation, l_Object, "orientation", "Object world orientation as of last frame (World orientation)", "orientation", "Orientation, or null orientation if handle is invalid") @@ -206,10 +206,10 @@ ADE_VIRTVAR(LastOrientation, l_Object, "orientation", "Object world orientation return ade_set_error(L, "o", l_Matrix.Set(matrix_h(&vmd_identity_matrix))); if(ADE_SETTING_VAR && mh != NULL) { - objh->objp->last_orient = *mh->GetMatrix(); + objh->objp()->last_orient = *mh->GetMatrix(); } - return ade_set_args(L, "o", l_Matrix.Set(matrix_h(&objh->objp->last_orient))); + return ade_set_args(L, "o", l_Matrix.Set(matrix_h(&objh->objp()->last_orient))); } ADE_VIRTVAR(ModelInstance, l_Object, nullptr, "model instance used by this object", "model_instance", "Model instance, nil if this object does not have one, or invalid model instance handle if object handle is invalid") @@ -224,7 +224,7 @@ ADE_VIRTVAR(ModelInstance, l_Object, nullptr, "model instance used by this objec if (ADE_SETTING_VAR) LuaError(L, "Assigning model instances is not implemented"); - int id = object_get_model_instance(objh->objp); + int id = object_get_model_instance(objh->objp()); if (id < 0) return ADE_RETURN_NIL; @@ -242,10 +242,10 @@ ADE_VIRTVAR(Physics, l_Object, "physics", "Physics data used to move ship betwee return ade_set_error(L, "o", l_Physics.Set(physics_info_h())); if(ADE_SETTING_VAR && pih && pih->isValid()) { - objh->objp->phys_info = *pih->pi; + objh->objp()->phys_info = *pih->pi; } - return ade_set_args(L, "o", l_Physics.Set(physics_info_h(objh->objp))); + return ade_set_args(L, "o", l_Physics.Set(physics_info_h(objh->objp()))); } ADE_VIRTVAR(HitpointsLeft, l_Object, "number", "Hitpoints an object has left", "number", "Hitpoints left, or 0 if handle is invalid") @@ -260,10 +260,10 @@ ADE_VIRTVAR(HitpointsLeft, l_Object, "number", "Hitpoints an object has left", " //Set hull strength. if(ADE_SETTING_VAR) { - objh->objp->hull_strength = f; + objh->objp()->hull_strength = f; } - return ade_set_args(L, "f", objh->objp->hull_strength); + return ade_set_args(L, "f", objh->objp()->hull_strength); } ADE_VIRTVAR(SimHitpointsLeft, l_Object, "number", "Simulated hitpoints an object has left", "number", "Simulated hitpoints left, or 0 if handle is invalid") @@ -278,10 +278,10 @@ ADE_VIRTVAR(SimHitpointsLeft, l_Object, "number", "Simulated hitpoints an object //Set sim hull strength. if (ADE_SETTING_VAR) { - objh->objp->sim_hull_strength = f; + objh->objp()->sim_hull_strength = f; } - return ade_set_args(L, "f", objh->objp->sim_hull_strength); + return ade_set_args(L, "f", objh->objp()->sim_hull_strength); } ADE_VIRTVAR(Shields, l_Object, "shields", "Shields", "shields", "Shields handle, or invalid shields handle if object handle is invalid") @@ -297,11 +297,11 @@ ADE_VIRTVAR(Shields, l_Object, "shields", "Shields", "shields", "Shields handle, //WMC - copy shields if(ADE_SETTING_VAR && sobjh && sobjh->isValid()) { - for(int i = 0; i < objh->objp->n_quadrants; i++) - shield_set_quad(objh->objp, i, shield_get_quad(sobjh->objp, i)); + for(int i = 0; i < objh->objp()->n_quadrants; i++) + shield_set_quad(objh->objp(), i, shield_get_quad(sobjh->objp(), i)); } - return ade_set_args(L, "o", l_Shields.Set(object_h(objh->objp))); + return ade_set_args(L, "o", l_Shields.Set(*objh)); } ADE_FUNC(getSignature, l_Object, NULL, "Gets the object's unique signature", "number", "Returns the object's unique numeric signature, or -1 if invalid. Useful for creating a metadata system") @@ -334,7 +334,7 @@ ADE_FUNC(isExpiring, l_Object, nullptr, "Checks whether the object has the shoul if (!oh->isValid()) return ADE_RETURN_NIL; - return ade_set_args(L, "b", oh->objp->flags[Object::Object_Flags::Should_be_dead]); + return ade_set_args(L, "b", oh->objp()->flags[Object::Object_Flags::Should_be_dead]); } ADE_FUNC(getBreedName, l_Object, NULL, "Gets object type", "string", "Object type name, or empty string if handle is invalid") @@ -346,7 +346,7 @@ ADE_FUNC(getBreedName, l_Object, NULL, "Gets object type", "string", "Object typ if(!objh->isValid()) return ade_set_error(L, "s", ""); - return ade_set_args(L, "s", Object_type_names[objh->objp->type]); + return ade_set_args(L, "s", Object_type_names[objh->objp()->type]); } ADE_VIRTVAR(CollisionGroups, l_Object, "number", "Collision group data", "number", "Current set of collision groups. NOTE: This is a bitfield, NOT a normal number.") @@ -361,10 +361,10 @@ ADE_VIRTVAR(CollisionGroups, l_Object, "number", "Collision group data", "number //Set collision group data if(ADE_SETTING_VAR) { - objh->objp->collision_group_id = id; + objh->objp()->collision_group_id = id; } - return ade_set_args(L, "i", objh->objp->collision_group_id); + return ade_set_args(L, "i", objh->objp()->collision_group_id); } ADE_FUNC(addToCollisionGroup, l_Object, "number group", "Adds this object to the specified collision group. The group must be between 0 and 31, inclusive.", nullptr, "Returns nothing") @@ -379,7 +379,7 @@ ADE_FUNC(addToCollisionGroup, l_Object, "number group", "Adds this object to the return ADE_RETURN_NIL; if (group >= 0 && group <= 31) - objh->objp->collision_group_id |= (1 << group); + objh->objp()->collision_group_id |= (1 << group); else Warning(LOCATION, "In addToCollisionGroup, group %d must be between 0 and 31, inclusive", group); @@ -398,7 +398,7 @@ ADE_FUNC(removeFromCollisionGroup, l_Object, "number group", "Removes this objec return ADE_RETURN_NIL; if (group >= 0 && group <= 31) - objh->objp->collision_group_id &= ~(1 << group); + objh->objp()->collision_group_id &= ~(1 << group); else Warning(LOCATION, "In removeFromCollisionGroup, group %d must be between 0 and 31, inclusive", group); @@ -418,7 +418,7 @@ ADE_FUNC(getfvec, l_Object, "[boolean normalize]", "Returns the objects' current if(!objh->isValid()) return ADE_RETURN_NIL; - obj = objh->objp; + obj = objh->objp(); vec3d v1 = obj->orient.vec.fvec; if (normalize) vm_vec_normalize(&v1); @@ -439,7 +439,7 @@ ADE_FUNC(getuvec, l_Object, "[boolean normalize]", "Returns the objects' current if(!objh->isValid()) return ADE_RETURN_NIL; - obj = objh->objp; + obj = objh->objp(); vec3d v1 = obj->orient.vec.uvec; if (normalize) vm_vec_normalize(&v1); @@ -460,7 +460,7 @@ ADE_FUNC(getrvec, l_Object, "[boolean normalize]", "Returns the objects' current if(!objh->isValid()) return ADE_RETURN_NIL; - obj = objh->objp; + obj = objh->objp(); vec3d v1 = obj->orient.vec.rvec; if (normalize) vm_vec_normalize(&v1); @@ -486,7 +486,7 @@ ADE_FUNC( if(!objh->isValid()) return ADE_RETURN_NIL; - auto obj = objh->objp; + auto obj = objh->objp(); int flags = 0; switch(obj->type) { @@ -575,7 +575,7 @@ ADE_FUNC(addPreMoveHook, l_Object, "function(object object) => void callback", if (!objh->isValid()) return ADE_RETURN_NIL; - objh->objp->pre_move_event.add(make_lua_callback(callback)); + objh->objp()->pre_move_event.add(make_lua_callback(callback)); return ADE_RETURN_NIL; } @@ -599,7 +599,7 @@ ADE_FUNC(addPostMoveHook, l_Object, "function(object object) => void callback", if (!objh->isValid()) return ADE_RETURN_NIL; - objh->objp->post_move_event.add(make_lua_callback(callback)); + objh->objp()->post_move_event.add(make_lua_callback(callback)); return ADE_RETURN_NIL; } @@ -622,7 +622,6 @@ ADE_FUNC(assignSound, l_Object, "soundentry GameSnd, [vector Offset=nil, enumera if (!objh->isValid() || !seh->isValid() || (tgsh && !tgsh->isValid())) return ade_set_error(L, "i", -1); - auto objp = objh->objp; auto gs_id = seh->idx; auto subsys = tgsh ? tgsh->ss : nullptr; if (!offset) @@ -630,7 +629,7 @@ ADE_FUNC(assignSound, l_Object, "soundentry GameSnd, [vector Offset=nil, enumera if (enum_flags.value) flags = *enum_flags.value; - int snd_idx = obj_snd_assign(OBJ_INDEX(objp), gs_id, offset, flags, subsys); + int snd_idx = obj_snd_assign(objh->objnum, gs_id, offset, flags, subsys); return ade_set_args(L, "i", snd_idx); } @@ -643,7 +642,7 @@ ADE_FUNC(removeSoundByIndex, l_Object, "number index", "Removes an assigned soun if (!ade_get_args(L, "oi", l_Object.GetPtr(&objh), &snd_idx)) return ADE_RETURN_NIL; - auto objp = objh->objp; + auto objp = objh->objp(); snd_idx--; // Lua -> C++ if (snd_idx < 0 || snd_idx >= (int)objp->objsnd_num.size()) @@ -669,7 +668,7 @@ ADE_FUNC(getNumAssignedSounds, if (!ade_get_args(L, "o", l_Object.GetPtr(&objh))) return ADE_RETURN_NIL; - auto objp = objh->objp; + auto objp = objh->objp(); return ade_set_args(L, "i", static_cast(objp->objsnd_num.size())); } @@ -689,11 +688,10 @@ ADE_FUNC(removeSound, l_Object, "soundentry GameSnd, [subsystem Subsys=nil]", if (!objh->isValid() || !seh->isValid() || (tgsh && !tgsh->isValid())) return ADE_RETURN_NIL; - auto objp = objh->objp; auto gs_id = seh->idx; auto subsys = tgsh ? tgsh->ss : nullptr; - obj_snd_delete_type(OBJ_INDEX(objp), gs_id, subsys); + obj_snd_delete_type(objh->objnum, gs_id, subsys); return ADE_RETURN_NIL; } @@ -713,7 +711,7 @@ ADE_FUNC(getIFFColor, l_Object, "boolean ReturnType", if (!objh->isValid()) return ADE_RETURN_NIL; - auto objp = objh->objp; + auto objp = objh->objp(); color* cur = hud_get_iff_color(objp); if (!rc) { diff --git a/code/scripting/api/objs/order.cpp b/code/scripting/api/objs/order.cpp index 4019543f737..3c2017aeebc 100644 --- a/code/scripting/api/objs/order.cpp +++ b/code/scripting/api/objs/order.cpp @@ -14,19 +14,17 @@ namespace scripting { namespace api { -order_h::order_h() { - objh = object_h(); - odx = -1; - sig = -1; - aigp = NULL; -} -order_h::order_h(object* objp, int n_odx) { +order_h::order_h() + : objh(), odx(-1), sig(-1), aigp(nullptr) +{} +order_h::order_h(object* objp, int n_odx) +{ objh = object_h(objp); - if(objh.isValid() && objh.objp->type == OBJ_SHIP && n_odx > -1 && n_odx < MAX_AI_GOALS) + if(objh.isValid() && objh.objp()->type == OBJ_SHIP && n_odx > -1 && n_odx < MAX_AI_GOALS) { odx = n_odx; - sig = Ai_info[Ships[objh.objp->instance].ai_index].goals[odx].signature; - aigp = &Ai_info[Ships[objh.objp->instance].ai_index].goals[odx]; + sig = Ai_info[Ships[objh.objp()->instance].ai_index].goals[odx].signature; + aigp = &Ai_info[Ships[objh.objp()->instance].ai_index].goals[odx]; } else { @@ -35,11 +33,12 @@ order_h::order_h(object* objp, int n_odx) { aigp = NULL; } } -bool order_h::isValid() const { - if (objh.objp == NULL || aigp == NULL) +bool order_h::isValid() const +{ + if (!objh.isValid() || aigp == NULL) return false; - return objh.isValid() && objh.objp->type == OBJ_SHIP && odx > -1 && odx < MAX_AI_GOALS && sig == Ai_info[Ships[objh.objp->instance].ai_index].goals[odx].signature; + return objh.objp()->type == OBJ_SHIP && odx > -1 && odx < MAX_AI_GOALS && sig == Ai_info[Ships[objh.objp()->instance].ai_index].goals[odx].signature; } //**********HANDLE: order @@ -72,7 +71,7 @@ ADE_FUNC(remove, l_Order, NULL, "Removes the given order from the ship's priorit if(!ohp->isValid()) return ADE_RETURN_FALSE; - ai_info *aip = &Ai_info[Ships[ohp->objh.objp->instance].ai_index]; + ai_info *aip = &Ai_info[Ships[ohp->objh.objp()->instance].ai_index]; ai_remove_ship_goal(aip, ohp->odx); @@ -188,7 +187,7 @@ ADE_VIRTVAR(Target, l_Order, "object", "Target of the order. Value may also be a if(!ohp->isValid()) return ade_set_error(L, "o", l_Object.Set(object_h())); - aip = &Ai_info[Ships[ohp->objh.objp->instance].ai_index]; + aip = &Ai_info[Ships[ohp->objh.objp()->instance].ai_index]; if(ADE_SETTING_VAR){ if(newh && newh->isValid()){ @@ -208,23 +207,23 @@ ADE_VIRTVAR(Target, l_Order, "object", "Target of the order. Value may also be a case AI_GOAL_KEEP_SAFE_DISTANCE: case AI_GOAL_FLY_TO_SHIP: case AI_GOAL_STAY_STILL: - if ((newh->objp->type == OBJ_SHIP) && !stricmp(Ships[newh->objp->instance].ship_name, ohp->aigp->target_name)){ - ohp->aigp->target_name = Ships[newh->objp->instance].ship_name; + if ((newh->objp()->type == OBJ_SHIP) && !stricmp(Ships[newh->objp()->instance].ship_name, ohp->aigp->target_name)){ + ohp->aigp->target_name = Ships[newh->objp()->instance].ship_name; ohp->aigp->time = Missiontime; if(ohp->odx == 0) { aip->ok_to_target_timestamp = timestamp(0); - set_target_objnum(aip, OBJ_INDEX(newh->objp)); + set_target_objnum(aip, newh->objnum); } } break; case AI_GOAL_CHASE_WEAPON: - if ((newh->objp->type == OBJ_WEAPON) && (ohp->aigp->target_signature != newh->sig)){ - ohp->aigp->target_instance = newh->objp->instance; - ohp->aigp->target_signature = Weapons[newh->objp->instance].objnum; + if ((newh->objp()->type == OBJ_WEAPON) && (ohp->aigp->target_signature != newh->sig)){ + ohp->aigp->target_instance = newh->objp()->instance; + ohp->aigp->target_signature = Weapons[newh->objp()->instance].objnum; ohp->aigp->time = Missiontime; if(ohp->odx == 0) { aip->ok_to_target_timestamp = timestamp(0); - set_target_objnum(aip, OBJ_INDEX(newh->objp)); + set_target_objnum(aip, newh->objnum); } } break; @@ -233,8 +232,8 @@ ADE_VIRTVAR(Target, l_Order, "object", "Target of the order. Value may also be a return ade_set_error(L, "o", l_Object.Set(object_h())); case AI_GOAL_WAYPOINTS: case AI_GOAL_WAYPOINTS_ONCE: - if (newh->objp->type == OBJ_WAYPOINT){ - wpl = find_waypoint_list_with_instance(newh->objp->instance); + if (newh->objp()->type == OBJ_WAYPOINT){ + wpl = find_waypoint_list_with_instance(newh->objp()->instance); if (!stricmp(wpl->get_name(),ohp->aigp->target_name)){ ohp->aigp->target_name = wpl->get_name(); ohp->aigp->time = Missiontime; @@ -244,31 +243,31 @@ ADE_VIRTVAR(Target, l_Order, "object", "Target of the order. Value may also be a flags |= WPF_REPEAT; if (ohp->aigp->flags[AI::Goal_Flags::Waypoints_in_reverse]) flags |= WPF_BACKTRACK; - ai_start_waypoints(ohp->objh.objp, wpl, flags, ohp->aigp->int_data); + ai_start_waypoints(ohp->objh.objp(), wpl, flags, ohp->aigp->int_data); } } } break; case AI_GOAL_CHASE_WING: - if((newh->objp->type == OBJ_SHIP) && !stricmp(Ships[newh->objp->instance].ship_name, ohp->aigp->target_name)){ - ship *shipp = &Ships[newh->objp->instance]; + if((newh->objp()->type == OBJ_SHIP) && !stricmp(Ships[newh->objp()->instance].ship_name, ohp->aigp->target_name)){ + ship *shipp = &Ships[newh->objp()->instance]; if (shipp->wingnum != -1){ ohp->aigp->target_name = Wings[shipp->wingnum].name; if(ohp->odx == 0) { aip->ok_to_target_timestamp = timestamp(0); - ai_attack_wing(ohp->objh.objp,shipp->wingnum); + ai_attack_wing(ohp->objh.objp(),shipp->wingnum); } } } break; case AI_GOAL_GUARD_WING: - if((newh->objp->type == OBJ_SHIP) && !stricmp(Ships[newh->objp->instance].ship_name, ohp->aigp->target_name)){ - ship *shipp = &Ships[newh->objp->instance]; + if((newh->objp()->type == OBJ_SHIP) && !stricmp(Ships[newh->objp()->instance].ship_name, ohp->aigp->target_name)){ + ship *shipp = &Ships[newh->objp()->instance]; if (shipp->wingnum != -1){ ohp->aigp->target_name = Wings[shipp->wingnum].name; if(ohp->odx == 0) { aip->ok_to_target_timestamp = timestamp(0); - ai_set_guard_wing(ohp->objh.objp,shipp->wingnum); + ai_set_guard_wing(ohp->objh.objp(),shipp->wingnum); } } } @@ -347,7 +346,7 @@ ADE_VIRTVAR(TargetSubsystem, l_Order, "subsystem", "Target subsystem of the orde if(!ohp->isValid()) return ade_set_error(L, "o", l_Subsystem.Set(ship_subsys_h())); - aip = &Ai_info[Ships[ohp->objh.objp->instance].ai_index]; + aip = &Ai_info[Ships[ohp->objh.objp()->instance].ai_index]; if(ADE_SETTING_VAR) { @@ -397,10 +396,10 @@ ADE_FUNC(__len, l_ShipOrders, NULL, "Number of ship orders", "number", "Number o if(!ade_get_args(L, "o", l_ShipOrders.GetPtr(&objh))) return ade_set_error(L, "i", 0); - if(!objh->isValid() || objh->objp->type != OBJ_SHIP || Ships[objh->objp->instance].ai_index < 0) + if(!objh->isValid() || objh->objp()->type != OBJ_SHIP || Ships[objh->objp()->instance].ai_index < 0) return ade_set_error(L, "i", 0); - return ade_set_args(L, "i", ai_goal_num(&Ai_info[Ships[objh->objp->instance].ai_index].goals[0])); + return ade_set_args(L, "i", ai_goal_num(&Ai_info[Ships[objh->objp()->instance].ai_index].goals[0])); } ADE_INDEXER(l_ShipOrders, "number Index", "Array of ship orders", "order", "Order, or invalid order handle on failure") @@ -416,10 +415,10 @@ ADE_INDEXER(l_ShipOrders, "number Index", "Array of ship orders", "order", "Orde if (!objh->isValid() || i < 0 || i >= MAX_AI_GOALS) return ade_set_error(L, "o", l_Order.Set(order_h())); - ai_info *aip = &Ai_info[Ships[objh->objp->instance].ai_index]; + ai_info *aip = &Ai_info[Ships[objh->objp()->instance].ai_index]; if (aip->goals[i].ai_mode != AI_GOAL_NONE) - return ade_set_args(L, "o", l_Order.Set(order_h(objh->objp, i))); + return ade_set_args(L, "o", l_Order.Set(order_h(objh->objp(), i))); else return ade_set_args(L, "o", l_Order.Set(order_h())); } diff --git a/code/scripting/api/objs/particle.cpp b/code/scripting/api/objs/particle.cpp index a7f16e8312d..9b3b30eb883 100644 --- a/code/scripting/api/objs/particle.cpp +++ b/code/scripting/api/objs/particle.cpp @@ -189,7 +189,7 @@ ADE_VIRTVAR(AttachedObject, l_Particle, "object", "The object this particle is a if (ADE_SETTING_VAR) { if (newObj != nullptr && newObj->isValid()) - ph->Get().lock()->attached_objnum = newObj->objp->signature; + ph->Get().lock()->attached_objnum = newObj->sig; } return ade_set_object_with_breed(L, ph->Get().lock()->attached_objnum); diff --git a/code/scripting/api/objs/physics_info.cpp b/code/scripting/api/objs/physics_info.cpp index 858b161a900..32f764531d9 100644 --- a/code/scripting/api/objs/physics_info.cpp +++ b/code/scripting/api/objs/physics_info.cpp @@ -7,23 +7,21 @@ namespace scripting { namespace api { -physics_info_h::physics_info_h() { - objh = object_h(); - pi = NULL; -} -physics_info_h::physics_info_h(object* objp) { - objh = object_h(objp); - pi = &objp->phys_info; -} -physics_info_h::physics_info_h(physics_info* in_pi) { - pi = in_pi; -} -bool physics_info_h::isValid() const { - if (objh.objp != NULL) { - return objh.isValid(); - } else { - return (pi != NULL); - } +physics_info_h::physics_info_h() + : objh(), pi(nullptr) +{} +physics_info_h::physics_info_h(object* objp) + : objh(objp), pi(nullptr) +{ + if (objh.isValid()) + pi = &objh.objp()->phys_info; +} +physics_info_h::physics_info_h(physics_info* in_pi) + : objh(), pi(in_pi) +{} +bool physics_info_h::isValid() const +{ + return pi != nullptr && objh.isValid(); } @@ -297,7 +295,7 @@ ADE_VIRTVAR(Velocity, l_Physics, "vector", "Object world velocity (World vector) if(ADE_SETTING_VAR && v3 != NULL) { pih->pi->vel = *v3; pih->pi->speed = vm_vec_mag(&pih->pi->vel); - pih->pi->fspeed = vm_vec_dot(&pih->objh.objp->orient.vec.fvec, &pih->pi->vel); + pih->pi->fspeed = vm_vec_dot(&pih->objh.objp()->orient.vec.fvec, &pih->pi->vel); if (Fix_scripted_velocity) pih->pi->flags |= PF_SCRIPTED_VELOCITY; // set flag to ensure physics respects this new value } @@ -492,11 +490,11 @@ ADE_FUNC(applyWhack, l_Physics, "vector Impulse, [ vector Position]", "Applies a objh = pih->objh; if (offset == nullptr) - offset = &objh.objp->pos; + offset = &objh.objp()->pos; else - vm_vec_add2(offset, &objh.objp->pos); + vm_vec_add2(offset, &objh.objp()->pos); - ship_apply_whack(impulse, offset, objh.objp); + ship_apply_whack(impulse, offset, objh.objp()); return ADE_RETURN_TRUE; @@ -514,10 +512,10 @@ ADE_FUNC(applyWhackWorld, l_Physics, "vector Impulse, [ vector Position]", "Appl objh = pih->objh; if (!world_pos) { - world_pos = &objh.objp->pos; + world_pos = &objh.objp()->pos; } - ship_apply_whack(impulse, world_pos, objh.objp); + ship_apply_whack(impulse, world_pos, objh.objp()); return ADE_RETURN_TRUE; diff --git a/code/scripting/api/objs/shields.cpp b/code/scripting/api/objs/shields.cpp index aa81f6c6cf4..ef63b0be13a 100644 --- a/code/scripting/api/objs/shields.cpp +++ b/code/scripting/api/objs/shields.cpp @@ -19,7 +19,7 @@ ADE_FUNC(__len, l_Shields, NULL, "Number of shield segments", "number", "Number if(!objh->isValid()) return ade_set_error(L, "i", -1); - return ade_set_args(L, "i", objh->objp->n_quadrants); + return ade_set_args(L, "i", objh->objp()->n_quadrants); } ADE_INDEXER(l_Shields, "enumeration/number", "Gets or sets shield segment strength. Use \"SHIELD_*\" enumerations (for standard 4-quadrant shields) or index of a specific segment, or NONE for the entire shield", "number", "Segment/shield strength, or 0 if handle is invalid") @@ -39,7 +39,7 @@ ADE_INDEXER(l_Shields, "enumeration/number", "Gets or sets shield segment streng if(!objh->isValid()) return ade_set_error(L, "f", 0.0f); - objp = objh->objp; + objp = objh->objp(); //Which quadrant? int qdi; @@ -57,7 +57,7 @@ ADE_INDEXER(l_Shields, "enumeration/number", "Gets or sets shield segment streng if(!objh->isValid()) return ade_set_error(L, "f", 0.0f); - objp = objh->objp; + objp = objh->objp(); switch(qd->index) { @@ -111,10 +111,10 @@ ADE_VIRTVAR(CombinedLeft, l_Shields, "number", "Total shield hitpoints left (for return ade_set_error(L, "f", 0.0f); if(ADE_SETTING_VAR && nval >= 0.0f) { - shield_set_strength(objh->objp, nval); + shield_set_strength(objh->objp(), nval); } - return ade_set_args(L, "f", shield_get_strength(objh->objp)); + return ade_set_args(L, "f", shield_get_strength(objh->objp())); } ADE_VIRTVAR(CombinedMax, l_Shields, "number", "Maximum shield hitpoints (for all segments combined)", "number", "Combined maximum shield strength, or 0 if handle is invalid") @@ -128,10 +128,10 @@ ADE_VIRTVAR(CombinedMax, l_Shields, "number", "Maximum shield hitpoints (for all return ade_set_error(L, "f", 0.0f); if(ADE_SETTING_VAR && nval >= 0.0f) { - shield_set_max_strength(objh->objp, nval); + shield_set_max_strength(objh->objp(), nval); } - return ade_set_args(L, "f", shield_get_max_strength(objh->objp)); + return ade_set_args(L, "f", shield_get_max_strength(objh->objp())); } ADE_FUNC(isValid, l_Shields, NULL, "Detects whether handle is valid", "boolean", "true if valid, false if handle is invalid, nil if a syntax/type error occurs") diff --git a/code/scripting/api/objs/ship.cpp b/code/scripting/api/objs/ship.cpp index f33056bbefa..78213008553 100644 --- a/code/scripting/api/objs/ship.cpp +++ b/code/scripting/api/objs/ship.cpp @@ -57,7 +57,7 @@ ADE_INDEXER(l_Ship, "string/number NameOrIndex", "Array of ship subsystems", "su if(!objh->isValid()) return ade_set_error(L, "o", l_Subsystem.Set(ship_subsys_h())); - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; ship_subsys *ss = ship_get_subsys(shipp, s); if(ss == NULL) @@ -73,7 +73,7 @@ ADE_INDEXER(l_Ship, "string/number NameOrIndex", "Array of ship subsystems", "su if(ss == NULL) return ade_set_error(L, "o", l_Subsystem.Set(ship_subsys_h())); - return ade_set_args(L, "o", l_Subsystem.Set(ship_subsys_h(objh->objp, ss))); + return ade_set_args(L, "o", l_Subsystem.Set(ship_subsys_h(objh->objp(), ss))); } ADE_FUNC(__len, l_Ship, NULL, "Number of subsystems on ship", "number", "Subsystem number, or 0 if handle is invalid") @@ -85,7 +85,7 @@ ADE_FUNC(__len, l_Ship, NULL, "Number of subsystems on ship", "number", "Subsyst if(!objh->isValid()) return ade_set_error(L, "i", 0); - return ade_set_args(L, "i", ship_get_num_subsys(&Ships[objh->objp->instance])); + return ade_set_args(L, "i", ship_get_num_subsys(&Ships[objh->objp()->instance])); } ADE_FUNC(setFlag, l_Ship, "boolean set_it, string flag_name", "Sets or clears one or more flags - this function can accept an arbitrary number of flag arguments. The flag names can be any string that the alter-ship-flag SEXP operator supports.", nullptr, "Returns nothing") @@ -101,7 +101,7 @@ ADE_FUNC(setFlag, l_Ship, "boolean set_it, string flag_name", "Sets or clears on if (!objh->isValid()) return ADE_RETURN_NIL; - auto shipp = &Ships[objh->objp->instance]; + auto shipp = &Ships[objh->objp()->instance]; object_ship_wing_point_team oswpt(shipp); do { @@ -139,8 +139,8 @@ ADE_FUNC(getFlag, l_Ship, "string flag_name", "Checks whether one or more flags if (!objh->isValid()) return ADE_RETURN_NIL; - auto shipp = &Ships[objh->objp->instance]; - auto objp = objh->objp; + auto objp = objh->objp(); + auto shipp = &Ships[objp->instance]; auto aip = &Ai_info[shipp->ai_index]; do { @@ -201,7 +201,7 @@ static int ship_getset_helper(lua_State* L, int ship::* field, bool canSet = fal if (!objh->isValid()) return ADE_RETURN_NIL; - ship* shipp = &Ships[objh->objp->instance]; + ship* shipp = &Ships[objh->objp()->instance]; if (ADE_SETTING_VAR) { @@ -229,7 +229,7 @@ ADE_VIRTVAR(ShieldArmorClass, l_Ship, "string", "Current Armor class of the ship if(!objh->isValid()) return ade_set_error(L, "s", ""); - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; int atindex; if (ADE_SETTING_VAR && s != nullptr) { atindex = armor_type_get_idx(s); @@ -258,7 +258,7 @@ ADE_VIRTVAR(ImpactDamageClass, l_Ship, "string", "Current Impact Damage class", if(!objh->isValid()) return ade_set_error(L, "s", ""); - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; int damage_index; if (ADE_SETTING_VAR && s != nullptr) { @@ -288,7 +288,7 @@ ADE_VIRTVAR(ArmorClass, l_Ship, "string", "Current Armor class", "string", "Armo if(!objh->isValid()) return ade_set_error(L, "s", ""); - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; int atindex; if (ADE_SETTING_VAR && s != nullptr) { atindex = armor_type_get_idx(s); @@ -315,7 +315,7 @@ ADE_VIRTVAR(Name, l_Ship, "string", "Ship name. This is the actual name of the s if(!objh->isValid()) return ade_set_error(L, "s", ""); - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; if(ADE_SETTING_VAR && s != nullptr) { auto len = sizeof(shipp->ship_name); @@ -336,7 +336,7 @@ ADE_VIRTVAR(DisplayName, l_Ship, "string", "Ship display name", "string", "The d if(!objh->isValid()) return ade_set_error(L, "s", ""); - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; if(ADE_SETTING_VAR && s != nullptr) { shipp->display_name = s; @@ -360,7 +360,7 @@ ADE_FUNC(isPlayer, l_Ship, nullptr, "Checks whether the ship is a player ship", // singleplayer if (!(Game_mode & GM_MULTIPLAYER)) { - if (Player_obj == objh->objp) + if (Player_obj == objh->objp()) return ADE_RETURN_TRUE; else return ADE_RETURN_FALSE; @@ -369,7 +369,7 @@ ADE_FUNC(isPlayer, l_Ship, nullptr, "Checks whether the ship is a player ship", else { // try and find the player - int np_index = multi_find_player_by_object(objh->objp); + int np_index = multi_find_player_by_object(objh->objp()); if ((np_index >= 0) && (np_index < MAX_PLAYERS)) return ADE_RETURN_TRUE; else @@ -387,7 +387,7 @@ ADE_VIRTVAR(AfterburnerFuelLeft, l_Ship, "number", "Afterburner fuel left", "num if(!objh->isValid()) return ade_set_error(L, "f", 0.0f); - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; if(ADE_SETTING_VAR && fuel >= 0.0f) shipp->afterburner_fuel = fuel; @@ -405,7 +405,7 @@ ADE_VIRTVAR(AfterburnerFuelMax, l_Ship, "number", "Afterburner fuel capacity", " if(!objh->isValid()) return ade_set_error(L, "f", 0.0f); - ship_info *sip = &Ship_info[Ships[objh->objp->instance].ship_info_index]; + ship_info *sip = &Ship_info[Ships[objh->objp()->instance].ship_info_index]; if(ADE_SETTING_VAR && fuel >= 0.0f) sip->afterburner_fuel_capacity = fuel; @@ -423,10 +423,10 @@ ADE_VIRTVAR(Class, l_Ship, "shipclass", "Ship class", "shipclass", "Ship class, if(!objh->isValid()) return ade_set_error(L, "o", l_Shipclass.Set(-1)); - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; if(ADE_SETTING_VAR && idx > -1) { - change_ship_type(objh->objp->instance, idx, 1); + change_ship_type(objh->objp()->instance, idx, 1); if (shipp == Player_ship) { // update HUD and RTT cockpit gauges if applicable set_current_hud(); @@ -451,7 +451,7 @@ ADE_VIRTVAR(CountermeasuresLeft, l_Ship, "number", "Number of countermeasures le if(!objh->isValid()) return ade_set_error(L, "i", 0); - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; if(ADE_SETTING_VAR && newcm > -1) shipp->cmeasure_count = newcm; @@ -474,7 +474,7 @@ ADE_VIRTVAR(CockpitDisplays, l_Ship, "displays", "An array of the cockpit displa LuaError(L, "Attempted to use incomplete feature: Cockpit displays copy"); } - return ade_set_args(L, "o", l_CockpitDisplays.Set(cockpit_displays_h(OBJ_INDEX(objh->objp)))); + return ade_set_args(L, "o", l_CockpitDisplays.Set(cockpit_displays_h(objh->objnum))); } ADE_VIRTVAR(CountermeasureClass, l_Ship, "weaponclass", "Weapon class mounted on this ship's countermeasure point", "weaponclass", "Countermeasure hardpoint weapon class, or invalid weaponclass handle if no countermeasure class or ship handle is invalid") @@ -487,7 +487,7 @@ ADE_VIRTVAR(CountermeasureClass, l_Ship, "weaponclass", "Weapon class mounted on if(!objh->isValid()) return ade_set_error(L, "o", l_Weaponclass.Set(-1));; - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; if(ADE_SETTING_VAR) { shipp->current_cmeasure = newcm; @@ -509,7 +509,7 @@ ADE_VIRTVAR(HitpointsMax, l_Ship, "number", "Total hitpoints", "number", "Ship m if(!objh->isValid()) return ade_set_error(L, "f", 0.0f); - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; if(ADE_SETTING_VAR && newhits > -1) shipp->ship_max_hull_strength = newhits; @@ -527,7 +527,7 @@ ADE_VIRTVAR(ShieldRegenRate, l_Ship, "number", "Maximum percentage/100 of shield if(!objh->isValid()) return ade_set_error(L, "f", 0.0f); - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; if (ADE_SETTING_VAR && new_shield_regen > -1) shipp->max_shield_regen_per_second = new_shield_regen; @@ -545,7 +545,7 @@ ADE_VIRTVAR(WeaponRegenRate, l_Ship, "number", "Maximum percentage/100 of weapon if(!objh->isValid()) return ade_set_error(L, "f", 0.0f); - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; if (ADE_SETTING_VAR && new_weapon_regen > -1) shipp->max_weapon_regen_per_second = new_weapon_regen; @@ -563,7 +563,7 @@ ADE_VIRTVAR(WeaponEnergyLeft, l_Ship, "number", "Current weapon energy reserves" if(!objh->isValid()) return ade_set_error(L, "f", 0.0f); - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; if(ADE_SETTING_VAR && neweng > -1) shipp->weapon_energy = neweng; @@ -581,7 +581,7 @@ ADE_VIRTVAR(WeaponEnergyMax, l_Ship, "number", "Maximum weapon energy", "number" if(!objh->isValid()) return ade_set_error(L, "f", 0.0f); - ship_info *sip = &Ship_info[Ships[objh->objp->instance].ship_info_index]; + ship_info *sip = &Ship_info[Ships[objh->objp()->instance].ship_info_index]; if(ADE_SETTING_VAR && neweng > -1) sip->max_weapon_reserve = neweng; @@ -599,7 +599,7 @@ ADE_VIRTVAR(AutoaimFOV, l_Ship, "number", "FOV of ship's autoaim, if any", "numb if(!objh->isValid()) return ade_set_error(L, "f", 0.0f); - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; if(ADE_SETTING_VAR && fov >= 0.0f) { if (fov > 180.0) @@ -621,7 +621,7 @@ ADE_VIRTVAR(PrimaryTriggerDown, l_Ship, "boolean", "Determines if primary trigge if(!objh->isValid()) return ADE_RETURN_NIL; - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; if(ADE_SETTING_VAR) { @@ -647,10 +647,10 @@ ADE_VIRTVAR(PrimaryBanks, l_Ship, "weaponbanktype", "Array of primary weapon ban if(!objh->isValid()) return ade_set_error(L, "o", l_WeaponBankType.Set(ship_banktype_h())); - ship_weapon *dst = &Ships[objh->objp->instance].weapons; + ship_weapon *dst = &Ships[objh->objp()->instance].weapons; if(ADE_SETTING_VAR && swh && swh->isValid()) { - ship_weapon *src = &Ships[swh->objh.objp->instance].weapons; + ship_weapon *src = &Ships[swh->objh.objp()->instance].weapons; dst->current_primary_bank = src->current_primary_bank; dst->num_primary_banks = src->num_primary_banks; @@ -665,7 +665,7 @@ ADE_VIRTVAR(PrimaryBanks, l_Ship, "weaponbanktype", "Array of primary weapon ban memcpy(dst->primary_bank_weapons, src->primary_bank_weapons, sizeof(dst->primary_bank_weapons)); } - return ade_set_args(L, "o", l_WeaponBankType.Set(ship_banktype_h(objh->objp, dst, SWH_PRIMARY))); + return ade_set_args(L, "o", l_WeaponBankType.Set(ship_banktype_h(objh->objp(), dst, SWH_PRIMARY))); } ADE_VIRTVAR(SecondaryBanks, l_Ship, "weaponbanktype", "Array of secondary weapon banks", "weaponbanktype", "Secondary weapon banks, or invalid weaponbanktype handle if ship handle is invalid") @@ -678,10 +678,10 @@ ADE_VIRTVAR(SecondaryBanks, l_Ship, "weaponbanktype", "Array of secondary weapon if(!objh->isValid()) return ade_set_error(L, "o", l_WeaponBankType.Set(ship_banktype_h())); - ship_weapon *dst = &Ships[objh->objp->instance].weapons; + ship_weapon *dst = &Ships[objh->objp()->instance].weapons; if(ADE_SETTING_VAR && swh && swh->isValid()) { - ship_weapon *src = &Ships[swh->objh.objp->instance].weapons; + ship_weapon *src = &Ships[swh->objh.objp()->instance].weapons; dst->current_secondary_bank = src->current_secondary_bank; dst->num_secondary_banks = src->num_secondary_banks; @@ -697,7 +697,7 @@ ADE_VIRTVAR(SecondaryBanks, l_Ship, "weaponbanktype", "Array of secondary weapon memcpy(dst->secondary_next_slot, src->secondary_next_slot, sizeof(dst->secondary_next_slot)); } - return ade_set_args(L, "o", l_WeaponBankType.Set(ship_banktype_h(objh->objp, dst, SWH_SECONDARY))); + return ade_set_args(L, "o", l_WeaponBankType.Set(ship_banktype_h(objh->objp(), dst, SWH_SECONDARY))); } ADE_VIRTVAR(TertiaryBanks, l_Ship, "weaponbanktype", "Array of tertiary weapon banks", "weaponbanktype", "Tertiary weapon banks, or invalid weaponbanktype handle if ship handle is invalid") @@ -710,10 +710,10 @@ ADE_VIRTVAR(TertiaryBanks, l_Ship, "weaponbanktype", "Array of tertiary weapon b if(!objh->isValid()) return ade_set_error(L, "o", l_WeaponBankType.Set(ship_banktype_h())); - ship_weapon *dst = &Ships[objh->objp->instance].weapons; + ship_weapon *dst = &Ships[objh->objp()->instance].weapons; if(ADE_SETTING_VAR && swh && swh->isValid()) { - ship_weapon *src = &Ships[swh->objh.objp->instance].weapons; + ship_weapon *src = &Ships[swh->objh.objp()->instance].weapons; dst->current_tertiary_bank = src->current_tertiary_bank; dst->num_tertiary_banks = src->num_tertiary_banks; @@ -725,7 +725,7 @@ ADE_VIRTVAR(TertiaryBanks, l_Ship, "weaponbanktype", "Array of tertiary weapon b dst->tertiary_bank_start_ammo = src->tertiary_bank_start_ammo; } - return ade_set_args(L, "o", l_WeaponBankType.Set(ship_banktype_h(objh->objp, dst, SWH_TERTIARY))); + return ade_set_args(L, "o", l_WeaponBankType.Set(ship_banktype_h(objh->objp(), dst, SWH_TERTIARY))); } ADE_VIRTVAR(Target, l_Ship, "object", "Target of ship. Value may also be a deriviative of the 'object' class, such as 'ship'.", "object", "Target object, or invalid object handle if no target or ship handle is invalid") @@ -740,21 +740,21 @@ ADE_VIRTVAR(Target, l_Ship, "object", "Target of ship. Value may also be a deriv return ade_set_error(L, "o", l_Object.Set(object_h())); ai_info *aip = NULL; - if(Ships[objh->objp->instance].ai_index > -1) - aip = &Ai_info[Ships[objh->objp->instance].ai_index]; + if(Ships[objh->objp()->instance].ai_index > -1) + aip = &Ai_info[Ships[objh->objp()->instance].ai_index]; else return ade_set_error(L, "o", l_Object.Set(object_h())); if(ADE_SETTING_VAR && !(newh && aip->target_signature == newh->sig)) { // we have a different target, or are clearng the target if(newh && newh->isValid()) { - aip->target_objnum = OBJ_INDEX(newh->objp); + aip->target_objnum = newh->objnum; aip->target_signature = newh->sig; aip->target_time = 0.0f; set_targeted_subsys(aip, nullptr, -1); if (aip == Player_ai) - hud_shield_hit_reset(newh->objp); + hud_shield_hit_reset(newh->objp()); } else if (lua_isnil(L, 2)) { aip->target_objnum = -1; aip->target_signature = -1; @@ -778,8 +778,8 @@ ADE_VIRTVAR(TargetSubsystem, l_Ship, "subsystem", "Target subsystem of ship.", " return ade_set_error(L, "o", l_Subsystem.Set(ship_subsys_h())); ai_info *aip = NULL; - if(Ships[oh->objp->instance].ai_index > -1) - aip = &Ai_info[Ships[oh->objp->instance].ai_index]; + if(Ships[oh->objp()->instance].ai_index > -1) + aip = &Ai_info[Ships[oh->objp()->instance].ai_index]; else return ade_set_error(L, "o", l_Subsystem.Set(ship_subsys_h())); @@ -789,12 +789,12 @@ ADE_VIRTVAR(TargetSubsystem, l_Ship, "subsystem", "Target subsystem of ship.", " { if (aip == Player_ai) { if (aip->target_signature != newh->objh.sig) - hud_shield_hit_reset(newh->objh.objp); + hud_shield_hit_reset(newh->objh.objp()); Ships[Objects[newh->ss->parent_objnum].instance].last_targeted_subobject[Player_num] = newh->ss; } - aip->target_objnum = OBJ_INDEX(newh->objh.objp); + aip->target_objnum = newh->objh.objnum; aip->target_signature = newh->objh.sig; aip->target_time = 0.0f; set_targeted_subsys(aip, newh->ss, aip->target_objnum); @@ -822,7 +822,7 @@ ADE_VIRTVAR(Team, l_Ship, "team", "Ship's team", "team", "Ship team, or invalid if(!oh->isValid()) return ade_set_error(L, "o", l_Team.Set(-1)); - ship *shipp = &Ships[oh->objp->instance]; + ship *shipp = &Ships[oh->objp()->instance]; if(ADE_SETTING_VAR && nt > -1) { shipp->team = nt; @@ -841,7 +841,7 @@ ADE_VIRTVAR(PersonaIndex, l_Ship, "number", "Persona index", "number", "The inde if(!objh->isValid()) return ade_set_error(L, "i", 0); - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; if(ADE_SETTING_VAR && p_index > 0) shipp->persona_index = p_index - 1; @@ -859,10 +859,10 @@ ADE_VIRTVAR(Textures, l_Ship, "modelinstancetextures", "Gets ship textures", "mo if(!dh->isValid()) return ade_set_error(L, "o", l_ModelInstanceTextures.Set(modelinstance_h())); - polymodel_instance *dest = model_get_instance(Ships[dh->objp->instance].model_instance_num); + polymodel_instance *dest = model_get_instance(Ships[dh->objp()->instance].model_instance_num); if(ADE_SETTING_VAR && sh && sh->isValid()) { - polymodel_instance *src = model_get_instance(Ships[sh->objp->instance].model_instance_num); + polymodel_instance *src = model_get_instance(Ships[sh->objp()->instance].model_instance_num); if (src->texture_replace != nullptr) { @@ -884,7 +884,7 @@ ADE_VIRTVAR(FlagAffectedByGravity, l_Ship, "boolean", "Checks for the \"affected if (!objh->isValid()) return ADE_RETURN_NIL; - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; if(ADE_SETTING_VAR) { @@ -908,7 +908,7 @@ ADE_VIRTVAR(Disabled, l_Ship, "boolean", "The disabled state of this ship", "boo if (!objh->isValid()) return ADE_RETURN_FALSE; - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; if(ADE_SETTING_VAR) { @@ -940,7 +940,7 @@ ADE_VIRTVAR(Stealthed, l_Ship, "boolean", "Stealth status of this ship", "boolea if (!objh->isValid()) return ADE_RETURN_FALSE; - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; if(ADE_SETTING_VAR) { @@ -964,7 +964,7 @@ ADE_VIRTVAR(HiddenFromSensors, l_Ship, "boolean", "Hidden from sensors status of if (!objh->isValid()) return ADE_RETURN_FALSE; - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; if(ADE_SETTING_VAR) { @@ -988,7 +988,7 @@ ADE_VIRTVAR(Gliding, l_Ship, "boolean", "Specifies whether this ship is currentl if (!objh->isValid()) return ADE_RETURN_FALSE; - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; if(ADE_SETTING_VAR) { @@ -998,7 +998,7 @@ ADE_VIRTVAR(Gliding, l_Ship, "boolean", "Specifies whether this ship is currentl } } - if (objh->objp->phys_info.flags & PF_GLIDING || objh->objp->phys_info.flags & PF_FORCE_GLIDE) + if (objh->objp()->phys_info.flags & PF_GLIDING || objh->objp()->phys_info.flags & PF_FORCE_GLIDE) return ADE_RETURN_TRUE; else return ADE_RETURN_FALSE; @@ -1018,7 +1018,7 @@ ADE_VIRTVAR(EtsEngineIndex, l_Ship, "number", "(SET not implemented, see EtsSetI if(ADE_SETTING_VAR) LuaError(L, "Attempted to set incomplete feature: ETS Engine Index (see EtsSetIndexes)"); - return ade_set_args(L, "i", Ships[objh->objp->instance].engine_recharge_index); + return ade_set_args(L, "i", Ships[objh->objp()->instance].engine_recharge_index); } ADE_VIRTVAR(EtsShieldIndex, l_Ship, "number", "(SET not implemented, see EtsSetIndexes)", "number", "Ships ETS Shield index value, 0 to MAX_ENERGY_INDEX") @@ -1035,7 +1035,7 @@ ADE_VIRTVAR(EtsShieldIndex, l_Ship, "number", "(SET not implemented, see EtsSetI if(ADE_SETTING_VAR) LuaError(L, "Attempted to set incomplete feature: ETS Shield Index (see EtsSetIndexes)"); - return ade_set_args(L, "i", Ships[objh->objp->instance].shield_recharge_index); + return ade_set_args(L, "i", Ships[objh->objp()->instance].shield_recharge_index); } ADE_VIRTVAR(EtsWeaponIndex, l_Ship, "number", "(SET not implemented, see EtsSetIndexes)", "number", "Ships ETS Weapon index value, 0 to MAX_ENERGY_INDEX") @@ -1052,7 +1052,7 @@ ADE_VIRTVAR(EtsWeaponIndex, l_Ship, "number", "(SET not implemented, see EtsSetI if(ADE_SETTING_VAR) LuaError(L, "Attempted to set incomplete feature: ETS Weapon Index (see EtsSetIndexes)"); - return ade_set_args(L, "i", Ships[objh->objp->instance].weapon_recharge_index); + return ade_set_args(L, "i", Ships[objh->objp()->instance].weapon_recharge_index); } ADE_VIRTVAR(Orders, l_Ship, "shiporders", "Array of ship orders", "shiporders", "Ship orders, or invalid handle if ship handle is invalid") @@ -1070,7 +1070,7 @@ ADE_VIRTVAR(Orders, l_Ship, "shiporders", "Array of ship orders", "shiporders", LuaError(L, "Attempted to use incomplete feature: Ai orders copy. Use giveOrder instead"); } - return ade_set_args(L, "o", l_ShipOrders.Set(object_h(objh->objp))); + return ade_set_args(L, "o", l_ShipOrders.Set(object_h(objh->objp()))); } ADE_VIRTVAR(WaypointSpeedCap, l_Ship, "number", "Waypoint speed cap", "number", "The limit on the ship's speed for traversing waypoints. -1 indicates no speed cap. 0 will be returned if handle is invalid.") @@ -1083,7 +1083,7 @@ ADE_VIRTVAR(WaypointSpeedCap, l_Ship, "number", "Waypoint speed cap", "number", if (!objh->isValid()) return ade_set_error(L, "i", 0); - ship* shipp = &Ships[objh->objp->instance]; + ship* shipp = &Ships[objh->objp()->instance]; ai_info* aip = &Ai_info[shipp->ai_index]; if (ADE_SETTING_VAR) @@ -1108,7 +1108,7 @@ static int ship_getset_location_helper(lua_State* L, LOC ship::* field, const ch if (!objh->isValid()) return ADE_RETURN_NIL; - ship* shipp = &Ships[objh->objp->instance]; + ship* shipp = &Ships[objh->objp()->instance]; if (ADE_SETTING_VAR && s != nullptr) { @@ -1144,7 +1144,7 @@ static int ship_getset_anchor_helper(lua_State* L, int ship::* field) if (!objh->isValid()) return ADE_RETURN_NIL; - ship* shipp = &Ships[objh->objp->instance]; + ship* shipp = &Ships[objh->objp()->instance]; if (ADE_SETTING_VAR && s != nullptr) { @@ -1211,7 +1211,7 @@ ADE_FUNC(sendMessage, if (ship_h == nullptr || !ship_h->isValid()) return ADE_RETURN_FALSE; - return sendMessage_sub(L, &Ships[ship_h->objp->instance], MESSAGE_SOURCE_SHIP, messageIdx, delay, ehp); + return sendMessage_sub(L, &Ships[ship_h->objp()->instance], MESSAGE_SOURCE_SHIP, messageIdx, delay, ehp); } ADE_FUNC(turnTowardsPoint, @@ -1232,7 +1232,7 @@ ADE_FUNC(turnTowardsPoint, return ADE_RETURN_NIL; } - ai_turn_towards_vector(target, shiph.objp, nullptr, nullptr, bank, (diffTurn ? 0 : AITTV_FAST) | (argnum >= 5 ? AITTV_FORCE_DELTA_BANK : 0), nullptr, modifier); + ai_turn_towards_vector(target, shiph.objp(), nullptr, nullptr, bank, (diffTurn ? 0 : AITTV_FAST) | (argnum >= 5 ? AITTV_FORCE_DELTA_BANK : 0), nullptr, modifier); return ADE_RETURN_NIL; } @@ -1254,9 +1254,9 @@ ADE_FUNC(turnTowardsOrientation, } matrix* mat = target->GetMatrix(); - vec3d targetVec = mat->vec.fvec + ship.objp->pos; + vec3d targetVec = mat->vec.fvec + ship.objp()->pos; - ai_turn_towards_vector(&targetVec, ship.objp, nullptr, nullptr, 0.0f, (diffTurn ? 0 : AITTV_FAST), &mat->vec.rvec, modifier); + ai_turn_towards_vector(&targetVec, ship.objp(), nullptr, nullptr, 0.0f, (diffTurn ? 0 : AITTV_FAST), &mat->vec.rvec, modifier); return ADE_RETURN_NIL; } @@ -1269,11 +1269,11 @@ ADE_FUNC(getCenterPosition, l_Ship, nullptr, "Returns the position of the ship's return ADE_RETURN_NIL; // find local center - ship_class_get_actual_center(&Ship_info[Ships[shiph->objp->instance].ship_info_index], &actual_local_center); + ship_class_get_actual_center(&Ship_info[Ships[shiph->objp()->instance].ship_info_index], &actual_local_center); // find world position of the center - vm_vec_unrotate(¢er_pos, &actual_local_center, &shiph->objp->orient); - vm_vec_add2(¢er_pos, &shiph->objp->pos); + vm_vec_unrotate(¢er_pos, &actual_local_center, &shiph->objp()->orient); + vm_vec_add2(¢er_pos, &shiph->objp()->pos); return ade_set_args(L, "o", l_Vector.Set(center_pos)); } @@ -1293,9 +1293,9 @@ ADE_FUNC(kill, l_Ship, "[object Killer, vector Hitpos]", "Kills the ship. Set \" // use the current hull percentage for damage-after-death purposes // (note that this does not actually affect scoring) - float percent_killed = get_hull_pct(victim->objp); + float percent_killed = get_hull_pct(victim->objp()); - ship_hit_kill(victim->objp, killer ? killer->objp : nullptr, hitpos, percent_killed, (killer && victim->sig == killer->sig), true); + ship_hit_kill(victim->objp(), killer ? killer->objp() : nullptr, hitpos, percent_killed, (killer && victim->sig == killer->sig), true); return ADE_RETURN_TRUE; } @@ -1319,9 +1319,10 @@ ADE_FUNC(checkVisibility, ship* viewer_shipp = nullptr; ship* viewed_shipp = nullptr; - viewed_shipp = &Ships[v1->objp->instance]; + viewed_shipp = &Ships[v1->objp()->instance]; if (v2) - viewer_shipp = &Ships[v2->objp->instance]; + viewer_shipp = &Ships[v2->objp()->instance]; + return ade_set_args(L, "i", ship_check_visibility(viewed_shipp, viewer_shipp)); } @@ -1341,7 +1342,7 @@ ADE_FUNC(addShipEffect, l_Ship, "string name, number durationMillis", "Activates if (effect_num == -1) return ade_set_error(L, "b", false); - ship* shipp = &Ships[shiph->objp->instance]; + ship* shipp = &Ships[shiph->objp()->instance]; shipp->shader_effect_num = effect_num; shipp->shader_effect_duration = duration; @@ -1359,7 +1360,7 @@ ADE_FUNC(hasShipExploded, l_Ship, NULL, "Checks if the ship explosion event has if(!shiph->isValid()) return ade_set_error(L, "i", 0); - ship *shipp = &Ships[shiph->objp->instance]; + ship *shipp = &Ships[shiph->objp()->instance]; if (shipp->flags[Ship::Ship_Flags::Dying]) { if (shipp->final_death_time == 0) { @@ -1383,7 +1384,7 @@ ADE_FUNC(isArrivingWarp, l_Ship, nullptr, "Checks if the ship is arriving via wa if (!shiph->isValid()) return ade_set_error(L, "b", false); - ship *shipp = &Ships[shiph->objp->instance]; + ship *shipp = &Ships[shiph->objp()->instance]; return ade_set_args(L, "b", shipp->is_arriving(ship::warpstage::BOTH, false)); } @@ -1397,7 +1398,7 @@ ADE_FUNC(isDepartingWarp, l_Ship, nullptr, "Checks if the ship is departing via if (!shiph->isValid()) return ade_set_error(L, "b", false); - ship *shipp = &Ships[shiph->objp->instance]; + ship *shipp = &Ships[shiph->objp()->instance]; return ade_set_args(L, "b", shipp->flags[Ship::Ship_Flags::Depart_warp]); } @@ -1411,7 +1412,7 @@ ADE_FUNC(isDepartingDockbay, l_Ship, nullptr, "Checks if the ship is departing v if (!shiph->isValid()) return ade_set_error(L, "b", false); - ship *shipp = &Ships[shiph->objp->instance]; + ship *shipp = &Ships[shiph->objp()->instance]; return ade_set_args(L, "b", shipp->flags[Ship::Ship_Flags::Depart_dockbay]); } @@ -1425,7 +1426,7 @@ ADE_FUNC(isDying, l_Ship, nullptr, "Checks if the ship is dying (doing its death if (!shiph->isValid()) return ade_set_error(L, "b", false); - ship *shipp = &Ships[shiph->objp->instance]; + ship *shipp = &Ships[shiph->objp()->instance]; return ade_set_args(L, "b", shipp->flags[Ship::Ship_Flags::Dying]); } @@ -1439,7 +1440,7 @@ ADE_FUNC(fireCountermeasure, l_Ship, NULL, "Launches a countermeasure from the s if(!objh->isValid()) return ade_set_error(L, "b", false); - return ade_set_args(L, "b", ship_launch_countermeasure(objh->objp) != 0); + return ade_set_args(L, "b", ship_launch_countermeasure(objh->objp()) != 0); } ADE_FUNC(firePrimary, l_Ship, NULL, "Fires ship primary bank(s)", "number", "Number of primary banks fired") @@ -1452,7 +1453,7 @@ ADE_FUNC(firePrimary, l_Ship, NULL, "Fires ship primary bank(s)", "number", "Num return ade_set_error(L, "i", 0); int i = 0; - i += ship_fire_primary(objh->objp); + i += ship_fire_primary(objh->objp()); return ade_set_args(L, "i", i); } @@ -1466,7 +1467,7 @@ ADE_FUNC(fireSecondary, l_Ship, NULL, "Fires ship secondary bank(s)", "number", if(!objh->isValid()) return ade_set_error(L, "i", 0); - return ade_set_args(L, "i", ship_fire_secondary(objh->objp, 0)); + return ade_set_args(L, "i", ship_fire_secondary(objh->objp(), 0)); } ADE_FUNC_DEPRECATED(getAnimationDoneTime, l_Ship, "number Type, number Subtype", "Gets time that animation will be done", "number", "Time (seconds), or 0 if ship handle is invalid", @@ -1486,7 +1487,7 @@ ADE_FUNC_DEPRECATED(getAnimationDoneTime, l_Ship, "number Type, number Subtype", if(type == animation::ModelAnimationTriggerType::None) return ADE_RETURN_FALSE; - int time_ms = Ship_info[Ships[objh->objp->instance].ship_info_index].animations.getAll(model_get_instance(Ships[objh->objp->instance].model_instance_num), type, subtype).getTime(); + int time_ms = Ship_info[Ships[objh->objp()->instance].ship_info_index].animations.getAll(model_get_instance(Ships[objh->objp()->instance].model_instance_num), type, subtype).getTime(); float time_s = (float)time_ms / 1000.0f; return ade_set_args(L, "f", time_s); @@ -1501,7 +1502,7 @@ ADE_FUNC(clearOrders, l_Ship, NULL, "Clears a ship's orders list", "boolean", "T return ade_set_error(L, "b", false); //The actual clearing of the goals - ai_clear_ship_goals( &Ai_info[Ships[objh->objp->instance].ai_index]); + ai_clear_ship_goals( &Ai_info[Ships[objh->objp()->instance].ai_index]); return ADE_RETURN_TRUE; } @@ -1539,35 +1540,35 @@ ADE_FUNC(giveOrder, l_Ship, "enumeration Order, [object Target=nil, subsystem Ta if(tgsh_valid) { ai_mode = AI_GOAL_DESTROY_SUBSYSTEM; - ai_shipname = Ships[tgh->objp->instance].ship_name; - ai_submode = ship_find_subsys( &Ships[tgsh->objh.objp->instance], tgsh->ss->system_info->subobj_name ); + ai_shipname = Ships[tgh->objp()->instance].ship_name; + ai_submode = ship_find_subsys( &Ships[tgsh->objh.objp()->instance], tgsh->ss->system_info->subobj_name ); } - else if(tgh_valid && tgh->objp->type == OBJ_WEAPON) + else if(tgh_valid && tgh->objp()->type == OBJ_WEAPON) { ai_mode = AI_GOAL_CHASE_WEAPON; - ai_submode = tgh->objp->instance; + ai_submode = tgh->objp()->instance; } - else if(tgh_valid && tgh->objp->type == OBJ_SHIP) + else if(tgh_valid && tgh->objp()->type == OBJ_SHIP) { ai_mode = AI_GOAL_CHASE; - ai_shipname = Ships[tgh->objp->instance].ship_name; + ai_shipname = Ships[tgh->objp()->instance].ship_name; ai_submode = SM_ATTACK; } break; } case LE_ORDER_DOCK: { - ai_shipname = Ships[tgh->objp->instance].ship_name; + ai_shipname = Ships[tgh->objp()->instance].ship_name; ai_mode = AI_GOAL_DOCK; ai_submode = AIS_DOCK_0; break; } case LE_ORDER_WAYPOINTS: { - if(tgh_valid && tgh->objp->type == OBJ_WAYPOINT) + if(tgh_valid && tgh->objp()->type == OBJ_WAYPOINT) { ai_mode = AI_GOAL_WAYPOINTS; - waypoint_list *wp_list = find_waypoint_list_with_instance(tgh->objp->instance); + waypoint_list *wp_list = find_waypoint_list_with_instance(tgh->objp()->instance); if(wp_list != NULL) ai_shipname = wp_list->get_name(); } @@ -1575,10 +1576,10 @@ ADE_FUNC(giveOrder, l_Ship, "enumeration Order, [object Target=nil, subsystem Ta } case LE_ORDER_WAYPOINTS_ONCE: { - if(tgh_valid && tgh->objp->type == OBJ_WAYPOINT) + if(tgh_valid && tgh->objp()->type == OBJ_WAYPOINT) { ai_mode = AI_GOAL_WAYPOINTS_ONCE; - waypoint_list *wp_list = find_waypoint_list_with_instance(tgh->objp->instance); + waypoint_list *wp_list = find_waypoint_list_with_instance(tgh->objp()->instance); if(wp_list != NULL) ai_shipname = wp_list->get_name(); } @@ -1592,10 +1593,10 @@ ADE_FUNC(giveOrder, l_Ship, "enumeration Order, [object Target=nil, subsystem Ta } case LE_ORDER_FORM_ON_WING: { - if(tgh_valid && tgh->objp->type == OBJ_SHIP) + if(tgh_valid && tgh->objp()->type == OBJ_SHIP) { ai_mode = AI_GOAL_FORM_ON_WING; - ai_shipname = Ships[tgh->objp->instance].ship_name; + ai_shipname = Ships[tgh->objp()->instance].ship_name; ai_submode = 0; } break; @@ -1605,41 +1606,41 @@ ADE_FUNC(giveOrder, l_Ship, "enumeration Order, [object Target=nil, subsystem Ta ai_mode = AI_GOAL_UNDOCK; ai_submode = AIS_UNDOCK_0; - if(tgh_valid && tgh->objp->type == OBJ_SHIP) + if(tgh_valid && tgh->objp()->type == OBJ_SHIP) { - ai_shipname = Ships[tgh->objp->instance].ship_name; + ai_shipname = Ships[tgh->objp()->instance].ship_name; } break; } case LE_ORDER_GUARD: { - if(tgh_valid && tgh->objp->type == OBJ_SHIP) + if(tgh_valid && tgh->objp()->type == OBJ_SHIP) { ai_mode = AI_GOAL_GUARD; ai_submode = AIS_GUARD_PATROL; - ai_shipname = Ships[tgh->objp->instance].ship_name; + ai_shipname = Ships[tgh->objp()->instance].ship_name; } break; } case LE_ORDER_DISABLE: case LE_ORDER_DISABLE_TACTICAL: { - if(tgh_valid && tgh->objp->type == OBJ_SHIP) + if(tgh_valid && tgh->objp()->type == OBJ_SHIP) { ai_mode = (eh->index == LE_ORDER_DISABLE) ? AI_GOAL_DISABLE_SHIP : AI_GOAL_DISABLE_SHIP_TACTICAL; ai_submode = -SUBSYSTEM_ENGINE; - ai_shipname = Ships[tgh->objp->instance].ship_name; + ai_shipname = Ships[tgh->objp()->instance].ship_name; } break; } case LE_ORDER_DISARM: case LE_ORDER_DISARM_TACTICAL: { - if(tgh_valid && tgh->objp->type == OBJ_SHIP) + if(tgh_valid && tgh->objp()->type == OBJ_SHIP) { ai_mode = (eh->index == LE_ORDER_DISARM) ? AI_GOAL_DISARM_SHIP : AI_GOAL_DISARM_SHIP_TACTICAL; ai_submode = -SUBSYSTEM_TURRET; - ai_shipname = Ships[tgh->objp->instance].ship_name; + ai_shipname = Ships[tgh->objp()->instance].ship_name; } break; } @@ -1652,49 +1653,49 @@ ADE_FUNC(giveOrder, l_Ship, "enumeration Order, [object Target=nil, subsystem Ta case LE_ORDER_IGNORE: case LE_ORDER_IGNORE_NEW: { - if(tgh_valid && tgh->objp->type == OBJ_SHIP) + if(tgh_valid && tgh->objp()->type == OBJ_SHIP) { ai_mode = (eh->index == LE_ORDER_IGNORE) ? AI_GOAL_IGNORE : AI_GOAL_IGNORE_NEW; ai_submode = 0; - ai_shipname = Ships[tgh->objp->instance].ship_name; + ai_shipname = Ships[tgh->objp()->instance].ship_name; } break; } case LE_ORDER_EVADE: { - if(tgh_valid && tgh->objp->type == OBJ_SHIP) + if(tgh_valid && tgh->objp()->type == OBJ_SHIP) { ai_mode = AI_GOAL_EVADE_SHIP; - ai_shipname = Ships[tgh->objp->instance].ship_name; + ai_shipname = Ships[tgh->objp()->instance].ship_name; } break; } case LE_ORDER_STAY_NEAR: { - if(tgh_valid && tgh->objp->type == OBJ_SHIP) + if(tgh_valid && tgh->objp()->type == OBJ_SHIP) { ai_mode = AI_GOAL_STAY_NEAR_SHIP; - ai_shipname = Ships[tgh->objp->instance].ship_name; + ai_shipname = Ships[tgh->objp()->instance].ship_name; ai_submode = -1; } break; } case LE_ORDER_KEEP_SAFE_DISTANCE: { - if(tgh_valid && tgh->objp->type == OBJ_SHIP) + if(tgh_valid && tgh->objp()->type == OBJ_SHIP) { ai_mode = AI_GOAL_KEEP_SAFE_DISTANCE; - ai_shipname = Ships[tgh->objp->instance].ship_name; + ai_shipname = Ships[tgh->objp()->instance].ship_name; ai_submode = -1; } break; } case LE_ORDER_REARM: { - if(tgh_valid && tgh->objp->type == OBJ_SHIP) + if(tgh_valid && tgh->objp()->type == OBJ_SHIP) { ai_mode = AI_GOAL_REARM_REPAIR; - ai_shipname = Ships[tgh->objp->instance].ship_name; + ai_shipname = Ships[tgh->objp()->instance].ship_name; ai_submode = 0; } break; @@ -1702,9 +1703,9 @@ ADE_FUNC(giveOrder, l_Ship, "enumeration Order, [object Target=nil, subsystem Ta case LE_ORDER_STAY_STILL: { ai_mode = AI_GOAL_STAY_STILL; - if(tgh_valid && tgh->objp->type == OBJ_SHIP) + if(tgh_valid && tgh->objp()->type == OBJ_SHIP) { - ai_shipname = Ships[tgh->objp->instance].ship_name; + ai_shipname = Ships[tgh->objp()->instance].ship_name; } break; } @@ -1720,19 +1721,19 @@ ADE_FUNC(giveOrder, l_Ship, "enumeration Order, [object Target=nil, subsystem Ta } case LE_ORDER_FLY_TO: { - if(tgh_valid && tgh->objp->type == OBJ_SHIP) + if(tgh_valid && tgh->objp()->type == OBJ_SHIP) { ai_mode = AI_GOAL_FLY_TO_SHIP; - ai_shipname = Ships[tgh->objp->instance].ship_name; + ai_shipname = Ships[tgh->objp()->instance].ship_name; ai_submode = 0; } break; } case LE_ORDER_ATTACK_WING: { - if(tgh_valid && tgh->objp->type == OBJ_SHIP) + if(tgh_valid && tgh->objp()->type == OBJ_SHIP) { - ship *shipp = &Ships[tgh->objp->instance]; + ship *shipp = &Ships[tgh->objp()->instance]; if (shipp->wingnum != -1) { ai_mode = AI_GOAL_CHASE_WING; @@ -1744,9 +1745,9 @@ ADE_FUNC(giveOrder, l_Ship, "enumeration Order, [object Target=nil, subsystem Ta } case LE_ORDER_GUARD_WING: { - if(tgh_valid && tgh->objp->type == OBJ_SHIP) + if(tgh_valid && tgh->objp()->type == OBJ_SHIP) { - ship *shipp = &Ships[tgh->objp->instance]; + ship *shipp = &Ships[tgh->objp()->instance]; if (shipp->wingnum != -1) { ai_mode = AI_GOAL_GUARD_WING; @@ -1775,7 +1776,7 @@ ADE_FUNC(giveOrder, l_Ship, "enumeration Order, [object Target=nil, subsystem Ta return ade_set_error(L, "b", false); //Fire off the goal - ai_add_ship_goal_scripting(ai_mode, ai_submode, (int)(priority*100.0f), ai_shipname, &Ai_info[Ships[objh->objp->instance].ai_index]); + ai_add_ship_goal_scripting(ai_mode, ai_submode, (int)(priority*100.0f), ai_shipname, &Ai_info[Ships[objh->objp()->instance].ai_index]); return ADE_RETURN_TRUE; } @@ -1795,7 +1796,7 @@ ADE_FUNC(doManeuver, if(!ade_get_args(L, "oifffbfffb|i", l_Ship.GetPtr(&objh), &duration, &heading, &pitch, &bank, &apply_all_rotate, &up, &sideways, &forward, &apply_all_move, &maneuver_flags)) return ADE_RETURN_NIL; - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; ai_info *aip = &Ai_info[shipp->ai_index]; control_info *cip = &aip->ai_override_ci; @@ -1907,7 +1908,7 @@ ADE_FUNC_DEPRECATED(triggerAnimation, l_Ship, "string Type, [number Subtype, boo if(type == animation::ModelAnimationTriggerType::None) return ADE_RETURN_FALSE; - Ship_info[Ships[objh->objp->instance].ship_info_index].animations.getAll(model_get_instance(Ships[objh->objp->instance].model_instance_num), type, subtype).start(b ? animation::ModelAnimationDirection::FWD : animation::ModelAnimationDirection::RWD, instant, instant); + Ship_info[Ships[objh->objp()->instance].ship_info_index].animations.getAll(model_get_instance(Ships[objh->objp()->instance].model_instance_num), type, subtype).start(b ? animation::ModelAnimationDirection::FWD : animation::ModelAnimationDirection::RWD, instant, instant); return ADE_RETURN_TRUE; } @@ -1937,7 +1938,7 @@ ADE_FUNC(triggerSubmodelAnimation, l_Ship, "string type, string triggeredBy, [bo if (animtype == animation::ModelAnimationTriggerType::None) return ADE_RETURN_FALSE; - ship* shipp = &Ships[objh->objp->instance]; + ship* shipp = &Ships[objh->objp()->instance]; return Ship_info[shipp->ship_info_index].animations.parseScripted(model_get_instance(shipp->model_instance_num), animtype, trigger).start(forwards ? animation::ModelAnimationDirection::FWD : animation::ModelAnimationDirection::RWD, forced || instant, instant, pause) ? ADE_RETURN_TRUE : ADE_RETURN_FALSE; } @@ -1961,7 +1962,7 @@ ADE_FUNC(getSubmodelAnimation, l_Ship, "string type, string triggeredBy", if (animtype == animation::ModelAnimationTriggerType::None) return ade_set_args(L, "o", l_AnimationHandle.Set(animation::ModelAnimationSet::AnimationList{})); - ship* shipp = &Ships[objh->objp->instance]; + ship* shipp = &Ships[objh->objp()->instance]; return ade_set_args(L, "o", l_AnimationHandle.Set(Ship_info[shipp->ship_info_index].animations.parseScripted(model_get_instance(shipp->model_instance_num), animtype, trigger))); } @@ -1986,7 +1987,7 @@ ADE_FUNC(stopLoopingSubmodelAnimation, l_Ship, "string type, string triggeredBy" if (animtype == animation::ModelAnimationTriggerType::None) return ADE_RETURN_FALSE; - ship* shipp = &Ships[objh->objp->instance]; + ship* shipp = &Ships[objh->objp()->instance]; Ship_info[shipp->ship_info_index].animations.parseScripted(model_get_instance(shipp->model_instance_num), animtype, trigger).setFlag(animation::Animation_Instance_Flags::Stop_after_next_loop); return ADE_RETURN_TRUE; @@ -2013,7 +2014,7 @@ ADE_FUNC(setAnimationSpeed, l_Ship, "string type, string triggeredBy, [number sp if (animtype == animation::ModelAnimationTriggerType::None) return ADE_RETURN_NIL; - ship* shipp = &Ships[objh->objp->instance]; + ship* shipp = &Ships[objh->objp()->instance]; Ship_info[shipp->ship_info_index].animations.parseScripted(model_get_instance(shipp->model_instance_num), animtype, trigger).setSpeed(speed); @@ -2035,7 +2036,7 @@ ADE_FUNC(getSubmodelAnimationTime, l_Ship, "string type, string triggeredBy", "G if (animtype == animation::ModelAnimationTriggerType::None) return ade_set_error(L, "f", 0.0f); - ship* shipp = &Ships[objh->objp->instance]; + ship* shipp = &Ships[objh->objp()->instance]; int time_ms = Ship_info[shipp->ship_info_index].animations.parseScripted(model_get_instance(shipp->model_instance_num), animtype, trigger).getTime(); float time_s = (float)time_ms / 1000.0f; @@ -2090,7 +2091,7 @@ ADE_FUNC(updateSubmodelMoveable, l_Ship, "string name, table values", } } - ship* shipp = &Ships[objh->objp->instance]; + ship* shipp = &Ships[objh->objp()->instance]; return Ship_info[shipp->ship_info_index].animations.updateMoveable(model_get_instance(shipp->model_instance_num), name, valuesMoveable) ? ADE_RETURN_TRUE : ADE_RETURN_FALSE; } @@ -2103,7 +2104,7 @@ ADE_FUNC(warpIn, l_Ship, NULL, "Warps ship in", "boolean", "True if successful, if(!objh->isValid()) return ADE_RETURN_NIL; - shipfx_warpin_start(objh->objp); + shipfx_warpin_start(objh->objp()); return ADE_RETURN_TRUE; } @@ -2117,7 +2118,7 @@ ADE_FUNC(warpOut, l_Ship, NULL, "Warps ship out", "boolean", "True if successful if(!objh->isValid()) return ADE_RETURN_NIL; - shipfx_warpout_start(objh->objp); + shipfx_warpout_start(objh->objp()); return ADE_RETURN_TRUE; } @@ -2131,7 +2132,7 @@ ADE_FUNC(canWarp, l_Ship, nullptr, "Checks whether ship has a working subspace d if(!objh->isValid()) return ADE_RETURN_NIL; - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; if( ship_can_warp_full_check(shipp) ){ return ADE_RETURN_TRUE; } @@ -2148,7 +2149,7 @@ ADE_FUNC(canBayDepart, l_Ship, nullptr, "Checks whether ship has a bay departure if(!objh->isValid()) return ADE_RETURN_NIL; - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; if( ship_can_bay_depart(shipp) ){ return ADE_RETURN_TRUE; } @@ -2168,7 +2169,7 @@ ADE_FUNC_DEPRECATED(isWarpingIn, l_Ship, NULL, "Checks if ship is in stage 1 of if(!objh->isValid()) return ADE_RETURN_NIL; - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; if(shipp->is_arriving(ship::warpstage::STAGE1, false)){ return ADE_RETURN_TRUE; } @@ -2186,7 +2187,7 @@ ADE_FUNC(isWarpingStage1, l_Ship, NULL, "Checks if ship is in stage 1 of warping if(!objh->isValid()) return ADE_RETURN_NIL; - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; if(shipp->is_arriving(ship::warpstage::STAGE1, false)){ return ADE_RETURN_TRUE; } @@ -2203,7 +2204,7 @@ ADE_FUNC(isWarpingStage2, l_Ship, NULL, "Checks if ship is in stage 2 of warping if(!objh->isValid()) return ADE_RETURN_NIL; - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; if(shipp->is_arriving(ship::warpstage::STAGE2, false)){ return ADE_RETURN_TRUE; } @@ -2223,7 +2224,7 @@ ADE_FUNC(getEMP, l_Ship, NULL, "Returns the current emp effect strength acting o if(!objh->isValid()) return ADE_RETURN_NIL; - obj = objh->objp; + obj = objh->objp(); ship *shipp = &Ships[obj->instance]; @@ -2239,7 +2240,7 @@ ADE_FUNC(getTimeUntilExplosion, l_Ship, nullptr, "Returns the time in seconds un if(!objh->isValid()) return ade_set_error(L, "f", -1.0f); - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; if (!timestamp_valid(shipp->final_death_time)) return ade_set_args(L, "f", -1.0f); @@ -2259,7 +2260,7 @@ ADE_FUNC(setTimeUntilExplosion, l_Ship, "number Time", "Sets the time in seconds if (!objh->isValid()) return ade_set_error(L, "b", false); - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; if (!timestamp_valid(shipp->final_death_time)) return ade_set_args(L, "b", false); @@ -2284,7 +2285,7 @@ ADE_FUNC(getCallsign, l_Ship, NULL, "Gets the callsign of the ship in the curren if(!objh->isValid()) return ade_set_error(L, "s", ""); - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; if (shipp->callsign_index < 0) return ade_set_args(L, "s", ""); @@ -2311,7 +2312,7 @@ ADE_FUNC(getAltClassName, l_Ship, NULL, "Gets the alternate class name of the sh if(!objh->isValid()) return ade_set_error(L, "s", ""); - ship *shipp = &Ships[objh->objp->instance]; + ship *shipp = &Ships[objh->objp()->instance]; if (shipp->alt_type_index < 0) return ade_set_args(L, "s", ""); @@ -2346,7 +2347,7 @@ ADE_FUNC(getMaximumSpeed, l_Ship, "[number energy = 0.333]", "Gets the maximum s } else { - return ade_set_args(L, "f", ets_get_max_speed(objh->objp, energy)); + return ade_set_args(L, "f", ets_get_max_speed(objh->objp(), energy)); } } @@ -2366,9 +2367,9 @@ ADE_FUNC(EtsSetIndexes, l_Ship, "number EngineIndex, number ShieldIndex, number sanity_check_ets_inputs(ets_idx); - int sindex = objh->objp->instance; + int sindex = objh->objp()->instance; if (validate_ship_ets_indxes(sindex, ets_idx)) { - set_recharge_rates(objh->objp, ets_idx[SHIELDS], ets_idx[WEAPONS], ets_idx[ENGINES]); + set_recharge_rates(objh->objp(), ets_idx[SHIELDS], ets_idx[WEAPONS], ets_idx[ENGINES]); return ADE_RETURN_TRUE; } else { return ADE_RETURN_FALSE; @@ -2384,7 +2385,7 @@ ADE_FUNC(getParsedShip, l_Ship, nullptr, "Returns the parsed ship that was used if(!objh->isValid()) return ade_set_error(L, "o", l_ParseObject.Set(parse_object_h(nullptr))); - auto shipp = &Ships[objh->objp->instance]; + auto shipp = &Ships[objh->objp()->instance]; auto ship_entry = ship_registry_get(shipp->ship_name); if (!ship_entry || !ship_entry->has_p_objp()) return ade_set_args(L, "o", l_ParseObject.Set(parse_object_h(nullptr))); @@ -2403,7 +2404,7 @@ ADE_FUNC(getWing, l_Ship, NULL, "Returns the ship's wing", "wing", "Wing handle, if(!objh->isValid()) return ade_set_error(L, "o", l_Wing.Set(-1)); - shipp = &Ships[objh->objp->instance]; + shipp = &Ships[objh->objp()->instance]; return ade_set_args(L, "o", l_Wing.Set(shipp->wingnum)); } @@ -2418,7 +2419,7 @@ ADE_FUNC(getDisplayString, l_Ship, nullptr, "Returns the string which should be if(!objh->isValid()) return ade_set_error(L, "s", ""); - shipp = &Ships[objh->objp->instance]; + shipp = &Ships[objh->objp()->instance]; return ade_set_args(L, "s", shipp->get_display_name()); } @@ -2432,7 +2433,7 @@ ADE_FUNC(vanish, l_Ship, nullptr, "Vanishes this ship from the mission. Works in if (!objh->isValid()) return ade_set_error(L, "b", false); - ship_actually_depart(objh->objp->instance, SHIP_VANISHED); + ship_actually_depart(objh->objp()->instance, SHIP_VANISHED); return ade_set_args(L, "b", true); } @@ -2450,7 +2451,7 @@ ADE_FUNC(setGlowPointBankActive, l_Ship, "boolean active, [number bank]", "Activ if (!objh->isValid()) return ADE_RETURN_NIL; - auto shipp = &Ships[objh->objp->instance]; + auto shipp = &Ships[objh->objp()->instance]; // read as many bank numbers as we have while (internal::Ade_get_args_skip = ++skip_args, ade_get_args(L, "|i", &bank_num) > 0) @@ -2486,7 +2487,7 @@ ADE_FUNC(numDocked, l_Ship, nullptr, "Returns the number of ships this ship is d if (docker_objh == nullptr || !docker_objh->isValid()) return ADE_RETURN_NIL; - return ade_set_args(L, "i", dock_count_direct_docked_objects(docker_objh->objp)); + return ade_set_args(L, "i", dock_count_direct_docked_objects(docker_objh->objp())); } ADE_FUNC(isDocked, l_Ship, "[ship... dockee_ships]", "Returns whether this ship is docked to all of the specified dockee ships, or is docked at all if no ships are specified", "boolean", "Returns whether the ship is docked") @@ -2514,7 +2515,7 @@ ADE_FUNC(isDocked, l_Ship, "[ship... dockee_ships]", "Returns whether this ship continue; // see if we are docked to it - if (!dock_check_find_direct_docked_object(docker_objh->objp, dockee_objh->objp)) + if (!dock_check_find_direct_docked_object(docker_objh->objp(), dockee_objh->objp())) return ADE_RETURN_FALSE; } @@ -2523,7 +2524,7 @@ ADE_FUNC(isDocked, l_Ship, "[ship... dockee_ships]", "Returns whether this ship return ADE_RETURN_TRUE; // if we didn't find any specified ships, then see if we are docked at all - return object_is_docked(docker_objh->objp) ? ADE_RETURN_TRUE : ADE_RETURN_FALSE; + return object_is_docked(docker_objh->objp()) ? ADE_RETURN_TRUE : ADE_RETURN_FALSE; } ADE_FUNC(setDocked, l_Ship, "ship dockee_ship, [string | number docker_point, string | number dockee_point]", "Immediately docks this ship with another ship.", "boolean", "Returns whether the docking was successful, or nil if an input was invalid") @@ -2538,11 +2539,11 @@ ADE_FUNC(setDocked, l_Ship, "ship dockee_ship, [string | number docker_point, st return ADE_RETURN_NIL; // cannot dock an object to itself - if (docker_objh->objp->instance == dockee_objh->objp->instance) + if (docker_objh->objp()->instance == dockee_objh->objp()->instance) return ADE_RETURN_FALSE; - auto docker_shipp = &Ships[docker_objh->objp->instance]; - auto dockee_shipp = &Ships[dockee_objh->objp->instance]; + auto docker_shipp = &Ships[docker_objh->objp()->instance]; + auto dockee_shipp = &Ships[dockee_objh->objp()->instance]; auto docker_pm = model_get(Ship_info[docker_shipp->ship_info_index].model_num); auto dockee_pm = model_get(Ship_info[dockee_shipp->ship_info_index].model_num); @@ -2592,16 +2593,16 @@ ADE_FUNC(setDocked, l_Ship, "ship dockee_ship, [string | number docker_point, st } //Make sure that the specified dockpoints are all free (if not, do nothing) - if (dock_find_object_at_dockpoint(docker_objh->objp, docker_point) != nullptr || - dock_find_object_at_dockpoint(dockee_objh->objp, dockee_point) != nullptr) + if (dock_find_object_at_dockpoint(docker_objh->objp(), docker_point) != nullptr || + dock_find_object_at_dockpoint(dockee_objh->objp(), dockee_point) != nullptr) { // at least one of the specified dockpoints is occupied return ADE_RETURN_FALSE; } //Set docked - dock_orient_and_approach(docker_objh->objp, docker_point, dockee_objh->objp, dockee_point, DOA_DOCK_STAY); - ai_do_objects_docked_stuff(docker_objh->objp, docker_point, dockee_objh->objp, dockee_point, true); + dock_orient_and_approach(docker_objh->objp(), docker_point, dockee_objh->objp(), dockee_point, DOA_DOCK_STAY); + ai_do_objects_docked_stuff(docker_objh->objp(), docker_point, dockee_objh->objp(), dockee_point, true); return ADE_RETURN_TRUE; } @@ -2625,10 +2626,10 @@ static int jettison_helper(lua_State *L, object_h *docker_objh, float jettison_s continue; // make sure we are docked to it - if (!dock_check_find_direct_docked_object(docker_objh->objp, dockee_objh->objp)) + if (!dock_check_find_direct_docked_object(docker_objh->objp(), dockee_objh->objp())) continue; - object_jettison_cargo(docker_objh->objp, dockee_objh->objp, jettison_speed, true); + object_jettison_cargo(docker_objh->objp(), dockee_objh->objp(), jettison_speed, true); num_ships_undocked++; } @@ -2637,9 +2638,9 @@ static int jettison_helper(lua_State *L, object_h *docker_objh, float jettison_s { // Goober5000 - as with ai_deathroll_start, we can't simply iterate through the dock list while we're // undocking things. So just repeatedly jettison the first object. - while (object_is_docked(docker_objh->objp)) + while (object_is_docked(docker_objh->objp())) { - object_jettison_cargo(docker_objh->objp, dock_get_first_docked_object(docker_objh->objp), jettison_speed, true); + object_jettison_cargo(docker_objh->objp(), dock_get_first_docked_object(docker_objh->objp()), jettison_speed, true); num_ships_undocked++; } } @@ -2691,7 +2692,7 @@ ADE_FUNC(AddElectricArc, l_Ship, "vector firstPoint, vector secondPoint, number if (!objh->isValid()) return ade_set_error(L, "i", 0); - auto shipp = &Ships[objh->objp->instance]; + auto shipp = &Ships[objh->objp()->instance]; // spawn the arc in the first unused slot for (int i = 0; i < MAX_ARC_EFFECTS; i++) { @@ -2731,7 +2732,7 @@ ADE_FUNC(DeleteElectricArc, l_Ship, "number index", if (!objh->isValid()) return ADE_RETURN_NIL; - auto shipp = &Ships[objh->objp->instance]; + auto shipp = &Ships[objh->objp()->instance]; index--; // Lua -> FS2 if (index >= 0 && index < MAX_ARC_EFFECTS) @@ -2760,7 +2761,7 @@ ADE_FUNC(ModifyElectricArc, l_Ship, "number index, vector firstPoint, vector sec if (!objh->isValid()) return ADE_RETURN_NIL; - auto shipp = &Ships[objh->objp->instance]; + auto shipp = &Ships[objh->objp()->instance]; index--; // Lua -> FS2 if (index >= 0 && index < MAX_ARC_EFFECTS) diff --git a/code/scripting/api/objs/ship_bank.cpp b/code/scripting/api/objs/ship_bank.cpp index 592131e0d81..4bb2db06a35 100644 --- a/code/scripting/api/objs/ship_bank.cpp +++ b/code/scripting/api/objs/ship_bank.cpp @@ -77,7 +77,7 @@ ADE_INDEXER(l_WeaponBankType, "number Index", "Array of weapon banks", "weaponba return ade_set_error(L, "o", l_WeaponBank.Set(ship_bank_h())); //Invalid type } - return ade_set_args(L, "o", l_WeaponBank.Set(ship_bank_h(sb->objh.objp, sb->sw, sb->type, idx))); + return ade_set_args(L, "o", l_WeaponBank.Set(ship_bank_h(sb->objh.objp(), sb->sw, sb->type, idx))); } ADE_VIRTVAR(Linked, l_WeaponBankType, "boolean", "Whether bank is in linked or unlinked fire mode (Primary-only)", "boolean", "Link status, or false if handle is invalid") @@ -96,10 +96,10 @@ ADE_VIRTVAR(Linked, l_WeaponBankType, "boolean", "Whether bank is in linked or u { case SWH_PRIMARY: if(ADE_SETTING_VAR && numargs > 1) { - Ships[bth->objh.objp->instance].flags.set(Ship::Ship_Flags::Primary_linked, newlink); + Ships[bth->objh.objp()->instance].flags.set(Ship::Ship_Flags::Primary_linked, newlink); } - return ade_set_args(L, "b", (Ships[bth->objh.objp->instance].flags[Ship::Ship_Flags::Primary_linked])); + return ade_set_args(L, "b", (Ships[bth->objh.objp()->instance].flags[Ship::Ship_Flags::Primary_linked])); case SWH_SECONDARY: case SWH_TERTIARY: @@ -125,10 +125,10 @@ ADE_VIRTVAR(DualFire, l_WeaponBankType, "boolean", "Whether bank is in dual fire { case SWH_SECONDARY: if(ADE_SETTING_VAR && numargs > 1) { - Ships[bth->objh.objp->instance].flags.set(Ship::Ship_Flags::Secondary_dual_fire, newfire); + Ships[bth->objh.objp()->instance].flags.set(Ship::Ship_Flags::Secondary_dual_fire, newfire); } - return ade_set_args(L, "b", (Ships[bth->objh.objp->instance].flags[Ship::Ship_Flags::Secondary_dual_fire])); + return ade_set_args(L, "b", (Ships[bth->objh.objp()->instance].flags[Ship::Ship_Flags::Secondary_dual_fire])); case SWH_PRIMARY: case SWH_TERTIARY: @@ -290,9 +290,9 @@ ADE_VIRTVAR(AmmoMax, l_WeaponBank, "number", "Maximum ammo for the current bank< int weapon_class = bh->typeh.sw->primary_bank_weapons[bh->bank]; - Assert(bh->typeh.objh.objp->type == OBJ_SHIP); + Assert(bh->typeh.objh.objp()->type == OBJ_SHIP); - return ade_set_args(L, "i", get_max_ammo_count_for_primary_bank(Ships[bh->typeh.objh.objp->instance].ship_info_index, bh->bank, weapon_class)); + return ade_set_args(L, "i", get_max_ammo_count_for_primary_bank(Ships[bh->typeh.objh.objp()->instance].ship_info_index, bh->bank, weapon_class)); } case SWH_SECONDARY: { @@ -302,9 +302,9 @@ ADE_VIRTVAR(AmmoMax, l_WeaponBank, "number", "Maximum ammo for the current bank< int weapon_class = bh->typeh.sw->secondary_bank_weapons[bh->bank]; - Assert(bh->typeh.objh.objp->type == OBJ_SHIP); + Assert(bh->typeh.objh.objp()->type == OBJ_SHIP); - return ade_set_args(L, "i", get_max_ammo_count_for_bank(Ships[bh->typeh.objh.objp->instance].ship_info_index, bh->bank, weapon_class)); + return ade_set_args(L, "i", get_max_ammo_count_for_bank(Ships[bh->typeh.objh.objp()->instance].ship_info_index, bh->bank, weapon_class)); } case SWH_TERTIARY: if(ADE_SETTING_VAR && ammomax > -1) { diff --git a/code/scripting/api/objs/subsystem.cpp b/code/scripting/api/objs/subsystem.cpp index 0b298ee1803..239aa18d630 100644 --- a/code/scripting/api/objs/subsystem.cpp +++ b/code/scripting/api/objs/subsystem.cpp @@ -23,12 +23,12 @@ namespace api { ship_subsys_h::ship_subsys_h() : objh(), ss(nullptr) {} ship_subsys_h::ship_subsys_h(object* objp_in, ship_subsys* sub) : objh(objp_in), ss(sub) {} -bool ship_subsys_h::isValid() const { return objh.isValid() && objh.objp->type == OBJ_SHIP && ss != nullptr; } +bool ship_subsys_h::isValid() const { return objh.isValid() && objh.objp()->type == OBJ_SHIP && ss != nullptr; } void ship_subsys_h::serialize(lua_State* /*L*/, const scripting::ade_table_entry& /*tableEntry*/, const luacpp::LuaValue& value, ubyte* data, int& packet_size) { ship_subsys_h subsys; value.getValue(l_Subsystem.Get(&subsys)); - const ushort& netsig = subsys.objh.isValid() ? subsys.objh.objp->net_signature : 0; + const ushort& netsig = subsys.objh.isValid() ? subsys.objh.objp()->net_signature : 0; const int& subsys_index = subsys.isValid() ? ship_get_subsys_index(subsys.ss) : -1; ADD_USHORT(netsig); ADD_INT(subsys_index); @@ -221,7 +221,7 @@ ADE_VIRTVAR(HitpointsLeft, l_Subsystem, "number", "Subsystem hitpoints left", "n //Only go down to 0 hits sso->ss->current_hits = MAX(0.0f, f); - ship *shipp = &Ships[sso->objh.objp->instance]; + ship *shipp = &Ships[sso->objh.objp()->instance]; if (f <= -1.0f && sso->ss->current_hits <= 0.0f) { do_subobj_destroyed_stuff(shipp, sso->ss, NULL); } @@ -245,7 +245,7 @@ ADE_VIRTVAR(HitpointsMax, l_Subsystem, "number", "Subsystem hitpoints max", "num { sso->ss->max_hits = MIN(0.0f, f); - ship_recalc_subsys_strength(&Ships[sso->objh.objp->instance]); + ship_recalc_subsys_strength(&Ships[sso->objh.objp()->instance]); } return ade_set_args(L, "f", sso->ss->max_hits); @@ -282,7 +282,7 @@ ADE_VIRTVAR(WorldPosition, l_Subsystem, "vector", return ade_set_error(L, "o", l_Vector.Set(vmd_zero_vector)); vec3d world_pos; - get_subsystem_world_pos(sso->objh.objp, sso->ss, &world_pos); + get_subsystem_world_pos(sso->objh.objp(), sso->ss, &world_pos); return ade_set_args(L, "o", l_Vector.Set(world_pos)); } @@ -297,7 +297,7 @@ ADE_VIRTVAR(GunPosition, l_Subsystem, "vector", "Subsystem gun position with reg if (!sso->isValid()) return ade_set_error(L, "o", l_Vector.Set(vmd_zero_vector)); - polymodel *pm = model_get(Ship_info[Ships[sso->objh.objp->instance].ship_info_index].model_num); + polymodel *pm = model_get(Ship_info[Ships[sso->objh.objp()->instance].ship_info_index].model_num); Assert(pm != NULL); if(sso->ss->system_info->turret_gun_sobj < 0) @@ -407,7 +407,7 @@ ADE_VIRTVAR(PrimaryBanks, l_Subsystem, "weaponbanktype", "Array of primary weapo memcpy(dst->primary_bank_weapons, src->primary_bank_weapons, sizeof(dst->primary_bank_weapons)); } - return ade_set_args(L, "o", l_WeaponBankType.Set(ship_banktype_h(sso->objh.objp, dst, SWH_PRIMARY))); + return ade_set_args(L, "o", l_WeaponBankType.Set(ship_banktype_h(sso->objh.objp(), dst, SWH_PRIMARY))); } ADE_VIRTVAR(SecondaryBanks, l_Subsystem, "weaponbanktype", "Array of secondary weapon banks", "weaponbanktype", "Secondary banks, or invalid weaponbanktype handle if subsystem handle is invalid") @@ -438,7 +438,7 @@ ADE_VIRTVAR(SecondaryBanks, l_Subsystem, "weaponbanktype", "Array of secondary w memcpy(dst->secondary_next_slot, src->secondary_next_slot, sizeof(dst->secondary_next_slot)); } - return ade_set_args(L, "o", l_WeaponBankType.Set(ship_banktype_h(sso->objh.objp, dst, SWH_SECONDARY))); + return ade_set_args(L, "o", l_WeaponBankType.Set(ship_banktype_h(sso->objh.objp(), dst, SWH_SECONDARY))); } @@ -456,7 +456,7 @@ ADE_VIRTVAR(Target, l_Subsystem, "object", "Object targeted by this subsystem. I if(ADE_SETTING_VAR) { if (objh && objh->isValid()) { - ss->turret_enemy_objnum = OBJ_INDEX(objh->objp); + ss->turret_enemy_objnum = objh->objnum; ss->turret_enemy_sig = objh->sig; ss->targeted_subsys = nullptr; ss->scripting_target_override = true; @@ -795,9 +795,9 @@ ADE_FUNC(isTargetInFOV, l_Subsystem, "object Target", "Determines if the object return ADE_RETURN_NIL; vec3d tpos,tvec; - ship_get_global_turret_info(sso->objh.objp, sso->ss->system_info, &tpos, &tvec); + ship_get_global_turret_info(sso->objh.objp(), sso->ss->system_info, &tpos, &tvec); - int in_fov = object_in_turret_fov(newh->objp,sso->ss,&tvec,&tpos,vm_vec_dist(&newh->objp->pos,&tpos)); + int in_fov = object_in_turret_fov(newh->objp(),sso->ss,&tvec,&tpos,vm_vec_dist(&newh->objp()->pos,&tpos)); if (in_fov) return ADE_RETURN_TRUE; @@ -816,7 +816,7 @@ ADE_FUNC(isPositionInFOV, l_Subsystem, "vector Target", "Determines if a positio return ADE_RETURN_NIL; vec3d tpos, tvec; - ship_get_global_turret_info(sso->objh.objp, sso->ss->system_info, &tpos, &tvec); + ship_get_global_turret_info(sso->objh.objp(), sso->ss->system_info, &tpos, &tvec); vec3d v2e; vm_vec_normalized_dir(&v2e, &target, &tpos); @@ -862,11 +862,11 @@ ADE_FUNC(fireWeapon, l_Subsystem, "[number TurretWeaponIndex = 1, number FlakRan //Get default turret info vec3d gpos, gvec; - ship_get_global_turret_gun_info(sso->objh.objp, sso->ss, &gpos, false, &gvec, true, nullptr); + ship_get_global_turret_gun_info(sso->objh.objp(), sso->ss, &gpos, false, &gvec, true, nullptr); if (override_gvec != nullptr) gvec = *override_gvec; - bool rtn = turret_fire_weapon(wnum, sso->ss, OBJ_INDEX(sso->objh.objp), &gpos, &gvec, NULL, flak_range); + bool rtn = turret_fire_weapon(wnum, sso->ss, sso->objh.objnum, &gpos, &gvec, NULL, flak_range); sso->ss->turret_next_fire_pos++; @@ -888,7 +888,7 @@ ADE_FUNC(rotateTurret, l_Subsystem, "vector Pos, boolean reset=false", "Rotates return ADE_RETURN_NIL; //Get default turret info - object *objp = sso->objh.objp; + object *objp = sso->objh.objp(); auto pmi = model_get_instance(Ships[objp->instance].model_instance_num); auto pm = model_get(pmi->model_num); @@ -911,7 +911,7 @@ ADE_FUNC(getTurretHeading, l_Subsystem, NULL, "Returns the turrets forward vecto return ade_set_error(L, "o", l_Vector.Set(vmd_zero_vector)); vec3d gvec; - object *objp = sso->objh.objp; + object *objp = sso->objh.objp(); auto pmi = model_get_instance(Ships[objp->instance].model_instance_num); auto pm = model_get(pmi->model_num); @@ -958,7 +958,7 @@ ADE_FUNC( vec3d gpos, gvec; - ship_get_global_turret_gun_info(sso->objh.objp, sso->ss, &gpos, false, &gvec, true, nullptr); + ship_get_global_turret_gun_info(sso->objh.objp(), sso->ss, &gpos, false, &gvec, true, nullptr); return ade_set_args(L, "oo", l_Vector.Set(gpos), l_Vector.Set(gvec)); } @@ -991,7 +991,7 @@ ADE_FUNC(getParent, l_Subsystem, NULL, "The object parent of this subsystem, is if (!sso->isValid()) return ade_set_error(L, "o", l_Object.Set(object_h())); - return ade_set_object_with_breed(L, OBJ_INDEX(sso->objh.objp)); + return ade_set_object_with_breed(L, sso->objh.objnum); } ADE_FUNC(isInViewFrom, l_Subsystem, "vector from", @@ -1008,10 +1008,10 @@ ADE_FUNC(isInViewFrom, l_Subsystem, "vector from", return ADE_RETURN_FALSE; vec3d world_pos; - get_subsystem_world_pos(sso->objh.objp, sso->ss, &world_pos); + get_subsystem_world_pos(sso->objh.objp(), sso->ss, &world_pos); // Disable facing check since the HUD code does the same - bool in_sight = ship_subsystem_in_sight(sso->objh.objp, sso->ss, from, &world_pos, false); + bool in_sight = ship_subsystem_in_sight(sso->objh.objp(), sso->ss, from, &world_pos, false); return ade_set_args(L, "b", in_sight); } diff --git a/code/scripting/api/objs/waypoint.cpp b/code/scripting/api/objs/waypoint.cpp index fabf156564c..49bd22cca31 100644 --- a/code/scripting/api/objs/waypoint.cpp +++ b/code/scripting/api/objs/waypoint.cpp @@ -18,8 +18,8 @@ ADE_FUNC(getList, l_Waypoint, NULL, "Returns the waypoint list", "waypointlist", if(!ade_get_args(L, "o", l_Waypoint.GetPtr(&oh))) return ade_set_error(L, "o", l_WaypointList.Set(waypointlist_h())); - if(oh->isValid() && oh->objp->type == OBJ_WAYPOINT) { - wp_list = find_waypoint_list_with_instance(oh->objp->instance); + if(oh->isValid() && oh->objp()->type == OBJ_WAYPOINT) { + wp_list = find_waypoint_list_with_instance(oh->objp()->instance); if(wp_list != NULL) wpl = waypointlist_h(wp_list); } diff --git a/code/scripting/api/objs/weapon.cpp b/code/scripting/api/objs/weapon.cpp index 0cd230cffbb..3e9dc9d860c 100644 --- a/code/scripting/api/objs/weapon.cpp +++ b/code/scripting/api/objs/weapon.cpp @@ -27,7 +27,7 @@ ADE_VIRTVAR(Class, l_Weapon, "weaponclass", "Weapon's class", "weaponclass", "We if(!oh->isValid()) return ade_set_error(L, "o", l_Weaponclass.Set(-1)); - weapon *wp = &Weapons[oh->objp->instance]; + weapon *wp = &Weapons[oh->objp()->instance]; if(ADE_SETTING_VAR && nc > -1) { wp->weapon_info_index = nc; @@ -49,7 +49,7 @@ ADE_VIRTVAR(DestroyedByWeapon, l_Weapon, "boolean", "Whether weapon was destroye if(!oh->isValid()) return ade_set_error(L, "b", false); - weapon *wp = &Weapons[oh->objp->instance]; + weapon *wp = &Weapons[oh->objp()->instance]; if(ADE_SETTING_VAR && numargs > 1) { wp->weapon_flags.set(Weapon::Weapon_Flags::Destroyed_by_weapon, b); @@ -68,7 +68,7 @@ ADE_VIRTVAR(LifeLeft, l_Weapon, "number", "Weapon life left (in seconds)", "numb if(!oh->isValid()) return ade_set_error(L, "f", 0.0f); - weapon *wp = &Weapons[oh->objp->instance]; + weapon *wp = &Weapons[oh->objp()->instance]; if(ADE_SETTING_VAR && nll >= 0.0f) { wp->lifeleft = nll; @@ -87,7 +87,7 @@ ADE_VIRTVAR(FlakDetonationRange, l_Weapon, "number", "Range at which flak will d if(!oh->isValid()) return ade_set_error(L, "f", 0.0f); - weapon *wp = &Weapons[oh->objp->instance]; + weapon *wp = &Weapons[oh->objp()->instance]; if(ADE_SETTING_VAR && rng >= 0.0f) { wp->det_range = rng; @@ -107,8 +107,8 @@ ADE_VIRTVAR(Target, l_Weapon, "object", "Target of weapon. Value may also be a d return ade_set_error(L, "o", l_Object.Set(object_h())); weapon *wp = NULL; - if(objh->objp->instance > -1) - wp = &Weapons[objh->objp->instance]; + if(objh->objp()->instance > -1) + wp = &Weapons[objh->objp()->instance]; else return ade_set_error(L, "o", l_Object.Set(object_h())); @@ -118,12 +118,12 @@ ADE_VIRTVAR(Target, l_Weapon, "object", "Target of weapon. Value may also be a d { if(wp->target_sig != newh->sig || !weapon_has_homing_object(wp)) { - weapon_set_tracking_info(OBJ_INDEX(objh->objp), objh->objp->parent, OBJ_INDEX(newh->objp), 1); + weapon_set_tracking_info(objh->objnum, objh->objp()->parent, newh->objnum, 1); } } else { - weapon_set_tracking_info(OBJ_INDEX(objh->objp), objh->objp->parent, -1); + weapon_set_tracking_info(objh->objnum, objh->objp()->parent, -1); } } @@ -141,8 +141,8 @@ ADE_VIRTVAR(ParentTurret, l_Weapon, "subsystem", "Turret which fired this weapon return ade_set_error(L, "o", l_Subsystem.Set(ship_subsys_h())); weapon *wp = NULL; - if(objh->objp->instance > -1) - wp = &Weapons[objh->objp->instance]; + if(objh->objp()->instance > -1) + wp = &Weapons[objh->objp()->instance]; else return ade_set_error(L, "o", l_Subsystem.Set(ship_subsys_h())); @@ -178,8 +178,8 @@ ADE_VIRTVAR(HomingObject, l_Weapon, "object", "Object that weapon will home in o return ade_set_error(L, "o", l_Object.Set(object_h())); weapon *wp = NULL; - if(objh->objp->instance > -1) - wp = &Weapons[objh->objp->instance]; + if(objh->objp()->instance > -1) + wp = &Weapons[objh->objp()->instance]; else return ade_set_error(L, "o", l_Object.Set(object_h())); @@ -189,12 +189,12 @@ ADE_VIRTVAR(HomingObject, l_Weapon, "object", "Object that weapon will home in o { if (wp->target_sig != newh->sig) { - weapon_set_tracking_info(OBJ_INDEX(objh->objp), objh->objp->parent, OBJ_INDEX(newh->objp), 1); + weapon_set_tracking_info(objh->objnum, objh->objp()->parent, newh->objnum, 1); } } else { - weapon_set_tracking_info(OBJ_INDEX(objh->objp), objh->objp->parent, -1); + weapon_set_tracking_info(objh->objnum, objh->objp()->parent, -1); } } @@ -216,8 +216,8 @@ ADE_VIRTVAR(HomingPosition, l_Weapon, "vector", "Position that weapon will home return ade_set_error(L, "o", l_Vector.Set(vmd_zero_vector)); weapon *wp = NULL; - if(objh->objp->instance > -1) - wp = &Weapons[objh->objp->instance]; + if(objh->objp()->instance > -1) + wp = &Weapons[objh->objp()->instance]; else return ade_set_error(L, "o", l_Vector.Set(vmd_zero_vector)); @@ -252,8 +252,8 @@ ADE_VIRTVAR(HomingSubsystem, l_Weapon, "subsystem", "Subsystem that weapon will return ade_set_error(L, "o", l_Subsystem.Set(ship_subsys_h())); weapon *wp = NULL; - if(objh->objp->instance > -1) - wp = &Weapons[objh->objp->instance]; + if(objh->objp()->instance > -1) + wp = &Weapons[objh->objp()->instance]; else return ade_set_error(L, "o", l_Subsystem.Set(ship_subsys_h())); @@ -263,7 +263,7 @@ ADE_VIRTVAR(HomingSubsystem, l_Weapon, "subsystem", "Subsystem that weapon will { if(wp->target_sig != newh->objh.sig) { - weapon_set_tracking_info(OBJ_INDEX(objh->objp), objh->objp->parent, OBJ_INDEX(newh->objh.objp), 1, newh->ss); + weapon_set_tracking_info(objh->objnum, objh->objp()->parent, newh->objh.objnum, 1, newh->ss); } else { @@ -273,7 +273,7 @@ ADE_VIRTVAR(HomingSubsystem, l_Weapon, "subsystem", "Subsystem that weapon will } else { - weapon_set_tracking_info(OBJ_INDEX(objh->objp), objh->objp->parent, -1); + weapon_set_tracking_info(objh->objnum, objh->objp()->parent, -1); } // need to update the position for multiplayer. @@ -295,7 +295,7 @@ ADE_VIRTVAR(Team, l_Weapon, "team", "Weapon's team", "team", "Weapon team, or in if(!oh->isValid()) return ade_set_error(L, "o", l_Team.Set(-1)); - weapon *wp = &Weapons[oh->objp->instance]; + weapon *wp = &Weapons[oh->objp()->instance]; if(ADE_SETTING_VAR && nt > -1 && nt < (int)Iff_info.size()) { wp->team = nt; @@ -317,7 +317,7 @@ ADE_VIRTVAR(OverrideHoming, l_Weapon, "boolean", if (!oh->isValid()) return ade_set_error(L, "b", false); - weapon* wp = &Weapons[oh->objp->instance]; + weapon* wp = &Weapons[oh->objp()->instance]; if (ADE_SETTING_VAR) { wp->weapon_flags.set(Weapon::Weapon_Flags::Overridden_homing, new_val); @@ -336,7 +336,7 @@ ADE_FUNC(isArmed, l_Weapon, "[boolean HitTarget]", "Checks if the weapon is arme if(!oh->isValid()) return ADE_RETURN_FALSE; - weapon *wp = &Weapons[oh->objp->instance]; + weapon *wp = &Weapons[oh->objp()->instance]; if(weapon_armed(wp, hit_target)) return ADE_RETURN_TRUE; @@ -354,7 +354,7 @@ ADE_FUNC(getCollisionInformation, l_Weapon, nullptr, "Returns the collision info if(!oh->isValid()) return ADE_RETURN_NIL; - weapon *wp = &Weapons[oh->objp->instance]; + weapon *wp = &Weapons[oh->objp()->instance]; if (wp->collisionInfo != nullptr) return ade_set_args(L, "o", l_ColInfo.Set(mc_info_h(*wp->collisionInfo))); @@ -383,7 +383,7 @@ ADE_FUNC(triggerSubmodelAnimation, l_Weapon, "string type, string triggeredBy, [ if (!objh->isValid()) return ADE_RETURN_NIL; - weapon* wp = &Weapons[objh->objp->instance]; + weapon* wp = &Weapons[objh->objp()->instance]; weapon_info* wip = &Weapon_info[wp->weapon_info_index]; if(wip->render_type != WRT_POF || wp->model_instance_num < 0) return ADE_RETURN_FALSE; @@ -410,7 +410,7 @@ ADE_FUNC(getSubmodelAnimationTime, l_Weapon, "string type, string triggeredBy", if (animtype == animation::ModelAnimationTriggerType::None) return ade_set_error(L, "f", 0.0f); - weapon* wp = &Weapons[objh->objp->instance]; + weapon* wp = &Weapons[objh->objp()->instance]; weapon_info* wip = &Weapon_info[wp->weapon_info_index]; if (wip->render_type != WRT_POF || wp->model_instance_num < 0) return ade_set_error(L, "f", 0.0f); @@ -431,7 +431,7 @@ ADE_FUNC(vanish, l_Weapon, nullptr, "Vanishes this weapon from the mission.", "b if (!oh->isValid()) return ade_set_error(L, "b", false); - oh->objp->flags.set(Object::Object_Flags::Should_be_dead); + oh->objp()->flags.set(Object::Object_Flags::Should_be_dead); return ade_set_args(L, "b", true); } diff --git a/code/scripting/api/objs/wing.cpp b/code/scripting/api/objs/wing.cpp index d3e7e31622f..49576c3ddd4 100644 --- a/code/scripting/api/objs/wing.cpp +++ b/code/scripting/api/objs/wing.cpp @@ -32,7 +32,7 @@ ADE_INDEXER(l_Wing, "number Index", "Array of ships in the wing", "ship", "Ship sdx--; if(ADE_SETTING_VAR && ndx != NULL && ndx->isValid()) { - Wings[wdx].ship_index[sdx] = ndx->objp->instance; + Wings[wdx].ship_index[sdx] = ndx->objp()->instance; } return ade_set_args(L, "o", l_Ship.Set(object_h(&Objects[Ships[Wings[wdx].ship_index[sdx]].objnum]))); From aee012358782955f9debb4a04a7919e529674c12 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Sun, 9 Jun 2024 05:29:15 -0500 Subject: [PATCH 47/63] Allow player-specific options even when using In-Game Options (#6045) * Move these functions into OptionsManager for more global use * Make sure player options are preferred even when using OptionsManager * persist the newly loaded options to disk * Actually let's persist these only if the load is successful and also handle the multi option * Comment this out for now I guess --- code/menuui/optionsmenu.cpp | 94 +++++-------------- code/menuui/optionsmenu.h | 3 - code/menuui/optionsmenumulti.cpp | 17 ++-- code/options/OptionsManager.cpp | 61 +++++++++++++ code/options/OptionsManager.h | 5 + code/pilotfile/csg.cpp | 67 +++++++------- code/pilotfile/plr.cpp | 152 ++++++++++++++++--------------- 7 files changed, 209 insertions(+), 190 deletions(-) diff --git a/code/menuui/optionsmenu.cpp b/code/menuui/optionsmenu.cpp index 31dd7ed11df..feaa2cd2195 100644 --- a/code/menuui/optionsmenu.cpp +++ b/code/menuui/optionsmenu.cpp @@ -709,6 +709,9 @@ void options_change_gamma(float delta) os_config_write_string( NULL, NOX("GammaD3D"), tmp_gamma_string ); + // The Gamma option sets its display value differently to the serialized value itself + // so we'll leave this here instead of trying to make a global method that works just + // for this one specific case if (Using_in_game_options) { const options::OptionBase* thisOpt = options::OptionsManager::instance()->getOptionByKey("Graphics.Gamma"); if (thisOpt != nullptr) { @@ -719,59 +722,6 @@ void options_change_gamma(float delta) } } -void options_set_ingame_binary_option(SCP_string key, bool value) -{ - if (!Using_in_game_options) { - return; - } - - const options::OptionBase* thisOpt = options::OptionsManager::instance()->getOptionByKey(key); - if (thisOpt != nullptr) { - auto val = thisOpt->getCurrentValueDescription(); - SCP_string newVal = value ? "true" : "false"; // OptionsManager stores values as serialized strings - thisOpt->setValueDescription({val.display, newVal.c_str()}); - } -} - -void options_set_ingame_multi_option(SCP_string key, int value) -{ - if (!Using_in_game_options) { - return; - } - - const options::OptionBase* thisOpt = options::OptionsManager::instance()->getOptionByKey(key); - if (thisOpt != nullptr) { - auto values = thisOpt->getValidValues(); - thisOpt->setValueDescription(values[value]); - } -} - -void options_set_ingame_range_option(SCP_string key, int value) -{ - if (!Using_in_game_options) { - return; - } - - const options::OptionBase* thisOpt = options::OptionsManager::instance()->getOptionByKey(key); - if (thisOpt != nullptr) { - SCP_string newVal = std::to_string(value); // OptionsManager stores values as serialized strings - thisOpt->setValueDescription({newVal.c_str(), newVal.c_str()}); - } -} - -void options_set_ingame_range_option(SCP_string key, float value) -{ - if (!Using_in_game_options) { - return; - } - - const options::OptionBase* thisOpt = options::OptionsManager::instance()->getOptionByKey(key); - if (thisOpt != nullptr) { - SCP_string newVal = std::to_string(value); // OptionsManager stores values as serialized strings - thisOpt->setValueDescription({newVal.c_str(), newVal.c_str()}); - } -} - void options_button_pressed(int n) { int choice; @@ -891,25 +841,25 @@ void options_button_pressed(int n) case BRIEF_VOICE_ON: Briefing_voice_enabled = true; - options_set_ingame_binary_option("Audio.BriefingVoice", true); + options::OptionsManager::instance()->set_ingame_binary_option("Audio.BriefingVoice", true); gamesnd_play_iface(InterfaceSounds::USER_SELECT); break; case BRIEF_VOICE_OFF: Briefing_voice_enabled = false; - options_set_ingame_binary_option("Audio.BriefingVoice", false); + options::OptionsManager::instance()->set_ingame_binary_option("Audio.BriefingVoice", false); gamesnd_play_iface(InterfaceSounds::USER_SELECT); break; case MOUSE_ON: Use_mouse_to_fly = 1; - options_set_ingame_binary_option("Input.UseMouse", true); + options::OptionsManager::instance()->set_ingame_binary_option("Input.UseMouse", true); gamesnd_play_iface(InterfaceSounds::USER_SELECT); break; case MOUSE_OFF: Use_mouse_to_fly = 0; - options_set_ingame_binary_option("Input.UseMouse", false); + options::OptionsManager::instance()->set_ingame_binary_option("Input.UseMouse", false); gamesnd_play_iface(InterfaceSounds::USER_SELECT); break; } @@ -921,7 +871,7 @@ void options_sliders_update() if (Options_sliders[gr_screen.res][OPT_SOUND_VOLUME_SLIDER].slider.pos != Sound_volume_int) { Sound_volume_int = Options_sliders[gr_screen.res][OPT_SOUND_VOLUME_SLIDER].slider.pos; snd_set_effects_volume((float) (Sound_volume_int) / 9.0f); - options_set_ingame_range_option("Audio.Effects", Master_sound_volume); // Volume options save the global float, not the range slider position + options::OptionsManager::instance()->set_ingame_range_option("Audio.Effects", Master_sound_volume); // Volume options save the global float, not the range slider position gamesnd_play_iface(InterfaceSounds::USER_SELECT); } @@ -929,7 +879,7 @@ void options_sliders_update() if (Options_sliders[gr_screen.res][OPT_MUSIC_VOLUME_SLIDER].slider.pos != Music_volume_int) { Music_volume_int = Options_sliders[gr_screen.res][OPT_MUSIC_VOLUME_SLIDER].slider.pos; event_music_set_volume((float) (Music_volume_int) / 9.0f); - options_set_ingame_range_option("Audio.Music", Master_event_music_volume); // Volume options save the global float, not the range slider position + options::OptionsManager::instance()->set_ingame_range_option("Audio.Music", Master_event_music_volume); // Volume options save the global float, not the range slider position gamesnd_play_iface(InterfaceSounds::USER_SELECT); } @@ -937,31 +887,31 @@ void options_sliders_update() if (Options_sliders[gr_screen.res][OPT_VOICE_VOLUME_SLIDER].slider.pos != Voice_volume_int) { Voice_volume_int = Options_sliders[gr_screen.res][OPT_VOICE_VOLUME_SLIDER].slider.pos; snd_set_voice_volume((float) (Voice_volume_int) / 9.0f); - options_set_ingame_range_option("Audio.Voice", Master_voice_volume); // Volume options save the global float, not the range slider position + options::OptionsManager::instance()->set_ingame_range_option("Audio.Voice", Master_voice_volume); // Volume options save the global float, not the range slider position options_play_voice_clip(); } if (Mouse_sensitivity != Options_sliders[gr_screen.res][OPT_MOUSE_SENS_SLIDER].slider.pos) { Mouse_sensitivity = Options_sliders[gr_screen.res][OPT_MOUSE_SENS_SLIDER].slider.pos; - options_set_ingame_range_option("Input.MouseSensitivity", Mouse_sensitivity); + options::OptionsManager::instance()->set_ingame_range_option("Input.MouseSensitivity", Mouse_sensitivity); gamesnd_play_iface(InterfaceSounds::USER_SELECT); } if (Joy_sensitivity != Options_sliders[gr_screen.res][OPT_JOY_SENS_SLIDER].slider.pos) { Joy_sensitivity = Options_sliders[gr_screen.res][OPT_JOY_SENS_SLIDER].slider.pos; - options_set_ingame_range_option("Input.JoystickSensitivity", Joy_sensitivity); + options::OptionsManager::instance()->set_ingame_range_option("Input.JoystickSensitivity", Joy_sensitivity); gamesnd_play_iface(InterfaceSounds::USER_SELECT); } if (Joy_dead_zone_size != Options_sliders[gr_screen.res][OPT_JOY_DEADZONE_SLIDER].slider.pos * 5) { Joy_dead_zone_size = Options_sliders[gr_screen.res][OPT_JOY_DEADZONE_SLIDER].slider.pos * 5; - options_set_ingame_range_option("Input.JoystickDeadZone", Joy_dead_zone_size); + options::OptionsManager::instance()->set_ingame_range_option("Input.JoystickDeadZone", Joy_dead_zone_size); gamesnd_play_iface(InterfaceSounds::USER_SELECT); } if (Game_skill_level != Options_sliders[gr_screen.res][OPT_SKILL_SLIDER].slider.pos) { Game_skill_level = Options_sliders[gr_screen.res][OPT_SKILL_SLIDER].slider.pos; - options_set_ingame_range_option("Game.SkillLevel", Game_skill_level); + options::OptionsManager::instance()->set_ingame_range_option("Game.SkillLevel", Game_skill_level); gamesnd_play_iface(InterfaceSounds::USER_SELECT); } } @@ -969,14 +919,14 @@ void options_sliders_update() void options_detail_sliders_in_game_update() { // Save in-game options settings - options_set_ingame_multi_option("Graphics.Detail", Detail.detail_distance); - options_set_ingame_multi_option("Graphics.NebulaDetail", Detail.nebula_detail); - options_set_ingame_multi_option("Graphics.Texture", Detail.hardware_textures); - options_set_ingame_multi_option("Graphics.Particles", Detail.num_particles); - options_set_ingame_multi_option("Graphics.SmallDebris", Detail.num_small_debris); - options_set_ingame_multi_option("Graphics.ShieldEffects", Detail.shield_effects); - options_set_ingame_multi_option("Graphics.Stars", Detail.num_stars); - options_set_ingame_multi_option("Graphics.Lighting", Detail.lighting); + options::OptionsManager::instance()->set_ingame_multi_option("Graphics.Detail", Detail.detail_distance); + options::OptionsManager::instance()->set_ingame_multi_option("Graphics.NebulaDetail", Detail.nebula_detail); + options::OptionsManager::instance()->set_ingame_multi_option("Graphics.Texture", Detail.hardware_textures); + options::OptionsManager::instance()->set_ingame_multi_option("Graphics.Particles", Detail.num_particles); + options::OptionsManager::instance()->set_ingame_multi_option("Graphics.SmallDebris", Detail.num_small_debris); + options::OptionsManager::instance()->set_ingame_multi_option("Graphics.ShieldEffects", Detail.shield_effects); + options::OptionsManager::instance()->set_ingame_multi_option("Graphics.Stars", Detail.num_stars); + options::OptionsManager::instance()->set_ingame_multi_option("Graphics.Lighting", Detail.lighting); } void options_accept() diff --git a/code/menuui/optionsmenu.h b/code/menuui/optionsmenu.h index 80c1638cf6b..aca2da11c7b 100644 --- a/code/menuui/optionsmenu.h +++ b/code/menuui/optionsmenu.h @@ -39,9 +39,6 @@ void options_menu_init(); void options_menu_close(); void options_menu_do_frame(float frametime); -// For optionsmenumulti.cpp -void options_set_ingame_binary_option(SCP_string key, bool value); - // kill the options menu void options_cancel_exit(); diff --git a/code/menuui/optionsmenumulti.cpp b/code/menuui/optionsmenumulti.cpp index e4d622e038b..f7f65a1e8f2 100644 --- a/code/menuui/optionsmenumulti.cpp +++ b/code/menuui/optionsmenumulti.cpp @@ -21,6 +21,7 @@ #include "network/multi.h" #include "network/multiui.h" #include "network/multi_voice.h" +#include "options/OptionsManager.h" #include "osapi/osregistry.h" #include "parse/parselo.h" #include "playerman/player.h" @@ -1005,10 +1006,10 @@ void options_multi_protocol_button_pressed(int n) if(!Om_local_broadcast){ Om_local_broadcast = 1; - options_set_ingame_binary_option("Multi.LocalBroadcast", true); + options::OptionsManager::instance()->set_ingame_binary_option("Multi.LocalBroadcast", true); } else { Om_local_broadcast = 0; - options_set_ingame_binary_option("Multi.LocalBroadcast", false); + options::OptionsManager::instance()->set_ingame_binary_option("Multi.LocalBroadcast", false); } gamesnd_play_iface(InterfaceSounds::USER_SELECT); @@ -1050,12 +1051,12 @@ void options_multi_protocol_button_pressed(int n) Om_tracker_login.enable(); Om_tracker_passwd.enable(); Om_tracker_squad_name.enable(); - options_set_ingame_binary_option("Multi.TogglePXO", true); + options::OptionsManager::instance()->set_ingame_binary_option("Multi.TogglePXO", true); } else { Om_tracker_login.disable(); Om_tracker_passwd.disable(); Om_tracker_squad_name.disable(); - options_set_ingame_binary_option("Multi.TogglePXO", false); + options::OptionsManager::instance()->set_ingame_binary_option("Multi.TogglePXO", false); } // play a sound @@ -1543,7 +1544,7 @@ void options_multi_gen_button_pressed(int n) if(!Om_gen_xfer_multidata){ gamesnd_play_iface(InterfaceSounds::USER_SELECT); Om_gen_xfer_multidata = 1; - options_set_ingame_binary_option("Multi.TransferMissions", true); + options::OptionsManager::instance()->set_ingame_binary_option("Multi.TransferMissions", true); } else { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); } @@ -1554,7 +1555,7 @@ void options_multi_gen_button_pressed(int n) if(Om_gen_xfer_multidata){ gamesnd_play_iface(InterfaceSounds::USER_SELECT); Om_gen_xfer_multidata = 0; - options_set_ingame_binary_option("Multi.TransferMissions", false); + options::OptionsManager::instance()->set_ingame_binary_option("Multi.TransferMissions", false); } else { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); } @@ -1565,7 +1566,7 @@ void options_multi_gen_button_pressed(int n) if(!Om_gen_flush_cache){ gamesnd_play_iface(InterfaceSounds::USER_SELECT); Om_gen_flush_cache = 1; - options_set_ingame_binary_option("Multi.FlushCache", true); + options::OptionsManager::instance()->set_ingame_binary_option("Multi.FlushCache", true); } else { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); } @@ -1576,7 +1577,7 @@ void options_multi_gen_button_pressed(int n) if(Om_gen_flush_cache){ gamesnd_play_iface(InterfaceSounds::USER_SELECT); Om_gen_flush_cache = 0; - options_set_ingame_binary_option("Multi.FlushCache", false); + options::OptionsManager::instance()->set_ingame_binary_option("Multi.FlushCache", false); } else { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); } diff --git a/code/options/OptionsManager.cpp b/code/options/OptionsManager.cpp index a4cd4a38c32..9cb123ec959 100644 --- a/code/options/OptionsManager.cpp +++ b/code/options/OptionsManager.cpp @@ -224,4 +224,65 @@ void OptionsManager::printValues() } } +//Sets the value saved within the option, but does not actually change the variables tied to the option +//Used for persistence and UI updates +void OptionsManager::set_ingame_binary_option(SCP_string key, bool value) +{ + if (!Using_in_game_options) { + return; + } + + const OptionBase* thisOpt = getOptionByKey(key); + if (thisOpt != nullptr) { + auto val = thisOpt->getCurrentValueDescription(); + SCP_string newVal = value ? "true" : "false"; // OptionsManager stores values as serialized strings + thisOpt->setValueDescription({val.display, newVal.c_str()}); + } +} + +//Sets the value saved within the option, but does not actually change the variables tied to the option +//Used for persistence and UI updates +void OptionsManager::set_ingame_multi_option(SCP_string key, int value) +{ + if (!Using_in_game_options) { + return; + } + + const OptionBase* thisOpt = getOptionByKey(key); + if (thisOpt != nullptr) { + auto values = thisOpt->getValidValues(); + thisOpt->setValueDescription(values[value]); + } +} + +//Sets value saved within the option, but does not actually change the variables tied to the option +//Used for persistence and UI updates +void OptionsManager::set_ingame_range_option(SCP_string key, int value) +{ + if (!Using_in_game_options) { + return; + } + + const OptionBase* thisOpt = getOptionByKey(key); + if (thisOpt != nullptr) { + SCP_string newVal = std::to_string(value); // OptionsManager stores values as serialized strings + thisOpt->setValueDescription({newVal.c_str(), newVal.c_str()}); + } +} + +//Sets the value saved within the option, but does not actually change the variables tied to the option +//Used for persistence and UI updates +void OptionsManager::set_ingame_range_option(SCP_string key, float value) +{ + if (!Using_in_game_options) { + return; + } + + const OptionBase* thisOpt = getOptionByKey(key); + if (thisOpt != nullptr) { + SCP_string newVal = std::to_string(value); // OptionsManager stores values as serialized strings + thisOpt->setValueDescription({newVal.c_str(), newVal.c_str()}); + } +} + } // namespace options diff --git a/code/options/OptionsManager.h b/code/options/OptionsManager.h index c2c79f1fec9..51f5902479f 100644 --- a/code/options/OptionsManager.h +++ b/code/options/OptionsManager.h @@ -58,6 +58,11 @@ class OptionsManager { void loadInitialValues(); void printValues(); + + void set_ingame_binary_option(SCP_string key, bool value); + void set_ingame_multi_option(SCP_string key, int value); + void set_ingame_range_option(SCP_string key, int value); + void set_ingame_range_option(SCP_string key, float value); }; } diff --git a/code/pilotfile/csg.cpp b/code/pilotfile/csg.cpp index f8172897e0f..59d723dade1 100644 --- a/code/pilotfile/csg.cpp +++ b/code/pilotfile/csg.cpp @@ -12,6 +12,7 @@ #include "mission/missionload.h" #include "missionui/missionscreencommon.h" #include "missionui/missionshipchoice.h" +#include "options/OptionsManager.h" #include "parse/sexp_container.h" #include "pilotfile/pilotfile.h" #include "playerman/player.h" @@ -1175,55 +1176,47 @@ void pilotfile::csg_write_variables() void pilotfile::csg_read_settings() { clamped_range_warnings.clear(); + // sound/voice/music - if (!Using_in_game_options) { - float temp_volume = cfread_float(cfp); - clamp_value_with_warn(&temp_volume, 0.f, 1.f, "Effects Volume"); - snd_set_effects_volume(temp_volume); + float temp_volume = cfread_float(cfp); + clamp_value_with_warn(&temp_volume, 0.f, 1.f, "Effects Volume"); + snd_set_effects_volume(temp_volume); + options::OptionsManager::instance()->set_ingame_range_option("Audio.Effects", Master_sound_volume); - temp_volume = cfread_float(cfp); - clamp_value_with_warn(&temp_volume, 0.f, 1.f, "Music Volume"); - event_music_set_volume(temp_volume); + temp_volume = cfread_float(cfp); + clamp_value_with_warn(&temp_volume, 0.f, 1.f, "Music Volume"); + event_music_set_volume(temp_volume); + options::OptionsManager::instance()->set_ingame_range_option("Audio.Music", Master_event_music_volume); - temp_volume = cfread_float(cfp); - clamp_value_with_warn(&temp_volume, 0.f, 1.f, "Voice Volume"); - snd_set_voice_volume(temp_volume); + temp_volume = cfread_float(cfp); + clamp_value_with_warn(&temp_volume, 0.f, 1.f, "Voice Volume"); + snd_set_voice_volume(temp_volume); + options::OptionsManager::instance()->set_ingame_range_option("Audio.Voice", Master_voice_volume); - Briefing_voice_enabled = cfread_int(cfp) != 0; - } else { - // The values are set by the in-game menu but we still need to read the int from the file to maintain the - // correct offset - cfread_float(cfp); - cfread_float(cfp); - cfread_float(cfp); - - cfread_int(cfp); - } + Briefing_voice_enabled = cfread_int(cfp) != 0; + options::OptionsManager::instance()->set_ingame_binary_option("Audio.BriefingVoice", Briefing_voice_enabled); // skill level Game_skill_level = cfread_int(cfp); clamp_value_with_warn(&Game_skill_level, 0, 4, "Game Skill Level"); + options::OptionsManager::instance()->set_ingame_range_option("Game.SkillLevel", Game_skill_level); // input options - if (!Using_in_game_options) { - Use_mouse_to_fly = cfread_int(cfp) != 0; - Mouse_sensitivity = cfread_int(cfp); - clamp_value_with_warn(&Mouse_sensitivity, 0, 9, "Mouse Sensitivity"); + Use_mouse_to_fly = cfread_int(cfp) != 0; + options::OptionsManager::instance()->set_ingame_binary_option("Input.UseMouse", Use_mouse_to_fly); - Joy_sensitivity = cfread_int(cfp); - clamp_value_with_warn(&Joy_sensitivity, 0, 9, "Joystick Sensitivity"); + Mouse_sensitivity = cfread_int(cfp); + clamp_value_with_warn(&Mouse_sensitivity, 0, 9, "Mouse Sensitivity"); + options::OptionsManager::instance()->set_ingame_range_option("Input.MouseSensitivity", Mouse_sensitivity); - Joy_dead_zone_size = cfread_int(cfp); - clamp_value_with_warn(&Joy_dead_zone_size, 0, 45, "Joystick Deadzone"); + Joy_sensitivity = cfread_int(cfp); + clamp_value_with_warn(&Joy_sensitivity, 0, 9, "Joystick Sensitivity"); + options::OptionsManager::instance()->set_ingame_range_option("Input.JoystickSensitivity", Joy_sensitivity); - } else { - // The values are set by the in-game menu but we still need to read the int from the file to maintain the correct offset - cfread_int(cfp); - cfread_int(cfp); - cfread_int(cfp); - cfread_int(cfp); - } + Joy_dead_zone_size = cfread_int(cfp); + clamp_value_with_warn(&Joy_dead_zone_size, 0, 45, "Joystick Deadzone"); + options::OptionsManager::instance()->set_ingame_range_option("Input.JoystickDeadZone", Joy_dead_zone_size); if (csg_ver < 3) { // detail @@ -1770,6 +1763,10 @@ bool pilotfile::load_savefile(player *_p, const char *campaign) } } + // Probably don't need to persist these to disk but it'll make sure on next boot we start with these campaign options set + // The github tests don't know what to do with the ini file so I guess we'll skip this for now + //options::OptionsManager::instance()->persistChanges(); + // if the campaign (for whatever reason) doesn't have a squad image, use the multi one if (p->s_squad_filename[0] == '\0') { strcpy_s(p->s_squad_filename, p->m_squad_filename); diff --git a/code/pilotfile/plr.cpp b/code/pilotfile/plr.cpp index aeb85a6f60b..ebe3279526a 100644 --- a/code/pilotfile/plr.cpp +++ b/code/pilotfile/plr.cpp @@ -10,6 +10,7 @@ #include "localization/localize.h" #include "menuui/techmenu.h" #include "network/multi.h" +#include "options/OptionsManager.h" #include "osapi/osregistry.h" #include "parse/sexp_container.h" #include "pilotfile/pilotfile.h" @@ -415,6 +416,9 @@ void pilotfile::plr_read_multiplayer() p->m_local_options.flags = handler->readInt("local_flags"); p->m_local_options.obj_update_level = handler->readInt("obj_update_level"); + //Make sure the local games multi option is reflected by the OptionsManager + options::OptionsManager::instance()->set_ingame_binary_option("Multi.LocalBroadcast", (p->m_local_options.flags & MLO_FLAG_LOCAL_BROADCAST) != 0); + // netgame protocol Multi_options_g.protocol = handler->readInt("protocol"); @@ -797,87 +801,87 @@ void pilotfile::plr_read_settings() { clamped_range_warnings.clear(); // sound/voice/music - if (!Using_in_game_options) { - float temp_volume = handler->readFloat("master_sound_volume"); - clamp_value_with_warn(&temp_volume, 0.f, 1.f, "Effects Volume"); - snd_set_effects_volume(temp_volume); + float temp_volume = handler->readFloat("master_sound_volume"); + clamp_value_with_warn(&temp_volume, 0.f, 1.f, "Effects Volume"); + snd_set_effects_volume(temp_volume); + options::OptionsManager::instance()->set_ingame_range_option("Audio.Effects", Master_sound_volume); - temp_volume = handler->readFloat("master_event_music_volume"); - clamp_value_with_warn(&temp_volume, 0.f, 1.f, "Music Volume"); - event_music_set_volume(temp_volume); + temp_volume = handler->readFloat("master_event_music_volume"); + clamp_value_with_warn(&temp_volume, 0.f, 1.f, "Music Volume"); + event_music_set_volume(temp_volume); + options::OptionsManager::instance()->set_ingame_range_option("Audio.Music", Master_event_music_volume); - temp_volume = handler->readFloat("aster_voice_volume"); - clamp_value_with_warn(&temp_volume, 0.f, 1.f, "Voice Volume"); - snd_set_voice_volume(temp_volume); + temp_volume = handler->readFloat("aster_voice_volume"); + clamp_value_with_warn(&temp_volume, 0.f, 1.f, "Voice Volume"); + snd_set_voice_volume(temp_volume); + options::OptionsManager::instance()->set_ingame_range_option("Audio.Voice", Master_voice_volume); - Briefing_voice_enabled = handler->readInt("briefing_voice_enabled") != 0; - } else { - // The values are set by the in-game menu but we still need to read the int from the file to maintain the - // correct offset - handler->readFloat("master_sound_volume"); - handler->readFloat("master_event_music_volume"); - handler->readFloat("aster_voice_volume"); - - handler->readInt("briefing_voice_enabled"); - } + Briefing_voice_enabled = handler->readInt("briefing_voice_enabled") != 0; + options::OptionsManager::instance()->set_ingame_binary_option("Audio.BriefingVoice", Briefing_voice_enabled); // skill level Game_skill_level = handler->readInt("game_skill_level"); clamp_value_with_warn(&Game_skill_level, 0, 4, "Skill Level"); + options::OptionsManager::instance()->set_ingame_range_option("Game.SkillLevel", Game_skill_level); // input options - if (!Using_in_game_options) { - Use_mouse_to_fly = handler->readInt("use_mouse_to_fly") != 0; - Mouse_sensitivity = handler->readInt("mouse_sensitivity"); - clamp_value_with_warn(&Mouse_sensitivity, 0, 9, "Mouse Sensitivity"); - Joy_sensitivity = handler->readInt("joy_sensitivity"); - clamp_value_with_warn(&Joy_sensitivity, 0, 9, "Joystick Sensitivity"); - Joy_dead_zone_size = handler->readInt("joy_dead_zone_size"); - clamp_value_with_warn(&Joy_dead_zone_size, 0, 45, "Joystick Deadzone"); - - // detail - Detail.setting = handler->readInt("setting"); - clamp_value_with_warn(&Detail.setting, -1, NUM_DEFAULT_DETAIL_LEVELS - 1, "Detail Level Preset"); - Detail.nebula_detail = handler->readInt("nebula_detail"); - clamp_value_with_warn(&Detail.nebula_detail, 0, MAX_DETAIL_LEVEL, "Nebula Detail"); - Detail.detail_distance = handler->readInt("detail_distance"); - clamp_value_with_warn(&Detail.detail_distance, 0, MAX_DETAIL_LEVEL, "Model Detail"); - Detail.hardware_textures = handler->readInt("hardware_textures"); - clamp_value_with_warn(&Detail.hardware_textures, 0, MAX_DETAIL_LEVEL, "3D Hardware Textures"); - Detail.num_small_debris = handler->readInt("num_small_debris"); - clamp_value_with_warn(&Detail.num_small_debris, 0, MAX_DETAIL_LEVEL, "Impact Effects"); - Detail.num_particles = handler->readInt("num_particles"); - clamp_value_with_warn(&Detail.num_particles, 0, MAX_DETAIL_LEVEL, "Particles"); - Detail.num_stars = handler->readInt("num_stars"); - clamp_value_with_warn(&Detail.num_stars, 0, MAX_DETAIL_LEVEL, "Stars"); - Detail.shield_effects = handler->readInt("shield_effects"); - clamp_value_with_warn(&Detail.shield_effects, 0, MAX_DETAIL_LEVEL, "Shield Hit Effects"); - Detail.lighting = handler->readInt("lighting"); - clamp_value_with_warn(&Detail.lighting, 0, MAX_DETAIL_LEVEL, "Lighting"); - Detail.targetview_model = handler->readInt("targetview_model"); - Detail.planets_suns = handler->readInt("planets_suns"); - Detail.weapon_extras = handler->readInt("weapon_extras"); - } else { - // The values are set by the in-game menu but we still need to read the int from the file to maintain the correct offset - handler->readInt("use_mouse_to_fly"); - handler->readInt("mouse_sensitivity"); - handler->readInt("joy_sensitivity"); - handler->readInt("joy_dead_zone_size"); - - // detail - handler->readInt("setting"); - handler->readInt("nebula_detail"); - handler->readInt("detail_distance"); - handler->readInt("hardware_textures"); - handler->readInt("num_small_debris"); - handler->readInt("num_particles"); - handler->readInt("num_stars"); - handler->readInt("shield_effects"); - handler->readInt("lighting"); - handler->readInt("targetview_model"); - handler->readInt("planets_suns"); - handler->readInt("weapon_extras"); - } + Use_mouse_to_fly = handler->readInt("use_mouse_to_fly") != 0; + options::OptionsManager::instance()->set_ingame_binary_option("Input.UseMouse", Use_mouse_to_fly); + + Mouse_sensitivity = handler->readInt("mouse_sensitivity"); + clamp_value_with_warn(&Mouse_sensitivity, 0, 9, "Mouse Sensitivity"); + options::OptionsManager::instance()->set_ingame_range_option("Input.MouseSensitivity", Mouse_sensitivity); + + Joy_sensitivity = handler->readInt("joy_sensitivity"); + clamp_value_with_warn(&Joy_sensitivity, 0, 9, "Joystick Sensitivity"); + options::OptionsManager::instance()->set_ingame_range_option("Input.JoystickSensitivity", Joy_sensitivity); + + Joy_dead_zone_size = handler->readInt("joy_dead_zone_size"); + clamp_value_with_warn(&Joy_dead_zone_size, 0, 45, "Joystick Deadzone"); + options::OptionsManager::instance()->set_ingame_range_option("Input.JoystickDeadZome", Joy_dead_zone_size); + + // detail + //Preset not handled by OptionsManager + Detail.setting = handler->readInt("setting"); + clamp_value_with_warn(&Detail.setting, -1, NUM_DEFAULT_DETAIL_LEVELS - 1, "Detail Level Preset"); + + Detail.nebula_detail = handler->readInt("nebula_detail"); + clamp_value_with_warn(&Detail.nebula_detail, 0, MAX_DETAIL_LEVEL, "Nebula Detail"); + options::OptionsManager::instance()->set_ingame_multi_option("Graphics.NebulaDetail", Detail.nebula_detail); + + Detail.detail_distance = handler->readInt("detail_distance"); + clamp_value_with_warn(&Detail.detail_distance, 0, MAX_DETAIL_LEVEL, "Model Detail"); + options::OptionsManager::instance()->set_ingame_multi_option("Graphics.Detail", Detail.detail_distance); + + Detail.hardware_textures = handler->readInt("hardware_textures"); + clamp_value_with_warn(&Detail.hardware_textures, 0, MAX_DETAIL_LEVEL, "3D Hardware Textures"); + options::OptionsManager::instance()->set_ingame_multi_option("Graphics.Texture", Detail.hardware_textures); + + Detail.num_small_debris = handler->readInt("num_small_debris"); + clamp_value_with_warn(&Detail.num_small_debris, 0, MAX_DETAIL_LEVEL, "Impact Effects"); + options::OptionsManager::instance()->set_ingame_multi_option("Graphics.SmallDebris", Detail.num_small_debris); + + Detail.num_particles = handler->readInt("num_particles"); + clamp_value_with_warn(&Detail.num_particles, 0, MAX_DETAIL_LEVEL, "Particles"); + options::OptionsManager::instance()->set_ingame_multi_option("Graphics.Particles", Detail.num_particles); + + Detail.num_stars = handler->readInt("num_stars"); + clamp_value_with_warn(&Detail.num_stars, 0, MAX_DETAIL_LEVEL, "Stars"); + options::OptionsManager::instance()->set_ingame_multi_option("Graphics.Stars", Detail.num_stars); + + Detail.shield_effects = handler->readInt("shield_effects"); + clamp_value_with_warn(&Detail.shield_effects, 0, MAX_DETAIL_LEVEL, "Shield Hit Effects"); + options::OptionsManager::instance()->set_ingame_multi_option("Graphics.ShieldEffects", Detail.shield_effects); + + Detail.lighting = handler->readInt("lighting"); + clamp_value_with_warn(&Detail.lighting, 0, MAX_DETAIL_LEVEL, "Lighting"); + options::OptionsManager::instance()->set_ingame_multi_option("Graphics.Lighting", Detail.lighting); + + //Rest not handled by OptionsManager + Detail.targetview_model = handler->readInt("targetview_model"); + Detail.planets_suns = handler->readInt("planets_suns"); + Detail.weapon_extras = handler->readInt("weapon_extras"); + if (!clamped_range_warnings.empty()) { ReleaseWarning(LOCATION, "The following values in the pilot file were out of bounds and were automatically reset:\n%s\nPlease check your settings!\n", clamped_range_warnings.c_str()); clamped_range_warnings.clear(); @@ -1146,6 +1150,10 @@ bool pilotfile::load_player(const char* callsign, player* _p, bool force_binary) } handler->endSectionRead(); + // Probably don't need to persist these to disk but it'll make sure on next boot we start with these player options set + // The github tests don't know what to do with the ini file so I guess we'll skip this for now + //options::OptionsManager::instance()->persistChanges(); + // restore the callsign into the Player structure strcpy_s(p->callsign, callsign); From d87e997d790f80d8f1919d3162665cf31042b585 Mon Sep 17 00:00:00 2001 From: BMagnu <6238428+BMagnu@users.noreply.github.com> Date: Mon, 10 Jun 2024 00:43:54 +0900 Subject: [PATCH 48/63] Increase Lua safety (#6179) * disable popen * Check file type when opening with io lib * Compile on Windows * Windows include * Properly conserve function environment * Correctly copy fenv --- code/scripting/lua.cpp | 70 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/code/scripting/lua.cpp b/code/scripting/lua.cpp index 4a63bf8dd89..95fc13d6f59 100644 --- a/code/scripting/lua.cpp +++ b/code/scripting/lua.cpp @@ -7,6 +7,13 @@ extern "C" { #include "scripting/lua/lua_ext.h" + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#else +#include +#endif } /** @@ -106,6 +113,48 @@ static void *vm_lua_alloc(void*, void *ptr, size_t, size_t nsize) { } } +//kind of fake, prevents true file access (only allows pipes and stuff) and also returns nil on fail instead of error handling string +static int io_open_limited (lua_State *L) { + const char *filename = luaL_checkstring(L, 1); + const char *mode = luaL_optstring(L, 2, "r"); + + if (filename == nullptr) + return 0; + + //We allow fifo-pipes, and direct character access. Neither should be too risky, and they allow for features such as communicating with speedrun tools or specialized hardware + //This explicitly blocks access to files, directories, and block devices +#ifdef WIN32 + auto handle = CreateFileA(filename, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr); + bool file_allowed = false; + if (handle != INVALID_HANDLE_VALUE) { + auto file_type = GetFileType(handle); + CloseHandle(handle); + file_allowed = (file_type == FILE_TYPE_PIPE) || (file_type == FILE_TYPE_CHAR); + } +#else + struct stat file_stat_buffer; + int query_stat = stat( filename, &file_stat_buffer ); + bool file_allowed = (query_stat == 0) && (S_ISFIFO(file_stat_buffer.st_mode) || S_ISCHR(file_stat_buffer.st_mode)); +#endif + + //Check that our requested file is nice and not evil + if (file_allowed) { + FILE **pf = (FILE **) lua_newuserdata(L, sizeof(FILE *)); + *pf = nullptr; /* file handle is currently `closed' */ + luaL_getmetatable(L, LUA_FILEHANDLE); + lua_setmetatable(L, -2); + *pf = fopen(filename, mode); + + if (*pf != nullptr) { + return 1; + } + + lua_pop(L, 1); + } + + return 0; +} + //Inits LUA //Note that "libraries" must end with a {NULL, NULL} //element @@ -154,6 +203,27 @@ int script_state::CreateLuaState() } lua_pop(L, 1); //os table + lua_pushstring(L, "io"); + lua_rawget(L, LUA_GLOBALSINDEX); + int io_ldx = lua_gettop(L); + if(lua_istable(L, io_ldx)) + { + lua_pushstring(L, "popen"); + lua_pushnil(L); + lua_rawset(L, io_ldx); + + //Instead of just removing open alltogether. Make sure to copy over the original fenv, as to allow lua to have the correct close already associated + lua_pushstring(L, "open"); // [io, "open"] + lua_pushstring(L, "open"); // [io, "open", "open"] + lua_rawget(L, io_ldx); // [io, "open", open()] + lua_pushcfunction(L, io_open_limited); // [io, "open", open(), open_limited()] + lua_getfenv(L, -2); // [io, "open", open(), open_limited(), open_fenv] + lua_setfenv(L, -2); // [io, "open", open(), open_limited()] + lua_remove(L, -2); // [io, "open", open_limited()] + lua_rawset(L, io_ldx); // [io] + } + lua_pop(L, 1); //io table + //*****INITIALIZE ADE uint i; mprintf(("LUA: Beginning ADE initialization\n")); From 786a4eff428794a91377e87c7caa3d45f03ca82d Mon Sep 17 00:00:00 2001 From: wookieejedi Date: Sun, 9 Jun 2024 16:26:23 -0400 Subject: [PATCH 49/63] Follow-up to Unified Scanning (#6183) Fixes a bug with new unified scanning PR (#6000) and adds a bit of cleanup, including consistent brackets, whitespace cleanup, and removing vestigial function from the original PR. Tested and works as expected. --- code/playerman/player.h | 1 - code/playerman/playercontrol.cpp | 13 ++++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/code/playerman/player.h b/code/playerman/player.h index 69d2c7e11cd..fd8effb711a 100644 --- a/code/playerman/player.h +++ b/code/playerman/player.h @@ -266,7 +266,6 @@ void player_set_squad_bitmap(player *p, const char *fnamem, bool ismulti); void player_set_squad(player *p, char *squad_name); bool player_inspect_cargo(float frametime, char *outstr); -bool better_player_inspect_cargo(float frametime, char* outstr); extern int use_descent; // player is using descent-style physics extern void toggle_player_object(); // toggles between descent-style ship and player ship diff --git a/code/playerman/playercontrol.cpp b/code/playerman/playercontrol.cpp index 94bfba241f9..c90d8914c66 100644 --- a/code/playerman/playercontrol.cpp +++ b/code/playerman/playercontrol.cpp @@ -1715,10 +1715,11 @@ bool player_inspect_cargo(float frametime, char *outstr) } } else { strcpy(outstr, XSTR("not scanned", 87)); - hud_targetbox_end_flash(TBOX_FLASH_CARGO); - Player->cargo_inspect_time = 0; - return true; } + + hud_targetbox_end_flash(TBOX_FLASH_CARGO); + Player->cargo_inspect_time = 0; + return true; } // player is facing the cargo, and within range, so proceed with inspection @@ -1866,7 +1867,7 @@ bool player_inspect_cap_subsys_cargo(float frametime, char *outstr) subsys_in_view = hud_targetbox_subsystem_in_view(cargo_objp, &x, &y); if ( (dot < CARGO_MIN_DOT_TO_REVEAL) || (!subsys_in_view) ) { - if (reveal_cargo) + if (reveal_cargo) { if (subsys->subsys_cargo_title[0] != '\0') { if (subsys->subsys_cargo_title[0] == '#') { strcpy(outstr, XSTR("", 1852)); @@ -1876,8 +1877,10 @@ bool player_inspect_cap_subsys_cargo(float frametime, char *outstr) } else { strcpy(outstr, XSTR("cargo: ", 86)); } - else + } else { strcpy(outstr,XSTR( "not scanned", 87)); + } + hud_targetbox_end_flash(TBOX_FLASH_CARGO); Player->cargo_inspect_time = 0; return true; From 7f20b9cf64c4dcd8ced2a2b8f666ee438620d9c0 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Mon, 10 Jun 2024 15:06:10 -0500 Subject: [PATCH 50/63] Unlimited Asteroid/Debris Field Types & Subtypes (#6021) * base working version of unlimited debris types * Proper FRED support * make qtFRED handle asteroids properly * fix qtFRED assertion * further clean out MAX_RETAIL_DEBRIS_TYPES * These should be sizes not POFs * Move this to it's own method * convert asteroid subtypes to a vector * unlimit asteroid subtypes * fix some global shadowing * signed/unsigned mismatch * Address feedback --- code/ai/aicode.cpp | 2 +- code/ai/aiturret.cpp | 2 +- code/asteroid/asteroid.cpp | 377 ++++++++++-------- code/asteroid/asteroid.h | 38 +- code/graphics/shadows.cpp | 4 +- code/hud/hudbrackets.cpp | 2 +- code/hud/hudtarget.cpp | 2 +- code/hud/hudtargetbox.cpp | 4 +- code/mission/missionparse.cpp | 92 +++-- code/model/modelread.cpp | 2 +- code/object/collideshipship.cpp | 2 +- code/object/object.cpp | 2 +- code/object/objectsort.cpp | 4 +- code/parse/sexp.cpp | 15 +- code/scripting/api/libs/graphics.cpp | 2 +- code/scripting/api/objs/object.cpp | 2 +- code/ship/shipfx.cpp | 2 +- code/weapon/beam.cpp | 2 +- fred2/asteroideditordlg.cpp | 254 ++++++------ fred2/asteroideditordlg.h | 4 +- fred2/dumpstats.cpp | 2 +- fred2/fred.rc | 13 +- fred2/missionsave.cpp | 33 +- fred2/resource.h | 2 + fred2/sexp_tree.cpp | 2 +- .../dialogs/AsteroidEditorDialogModel.cpp | 103 +++-- .../dialogs/AsteroidEditorDialogModel.h | 3 +- qtfred/src/mission/missionsave.cpp | 35 +- .../src/ui/dialogs/AsteroidEditorDialog.cpp | 5 +- qtfred/src/ui/widgets/sexp_tree.cpp | 2 +- 30 files changed, 559 insertions(+), 455 deletions(-) diff --git a/code/ai/aicode.cpp b/code/ai/aicode.cpp index 6e547e712d0..7a59f6bcaed 100644 --- a/code/ai/aicode.cpp +++ b/code/ai/aicode.cpp @@ -16719,7 +16719,7 @@ bool test_line_of_sight(vec3d* from, vec3d* to, std::unordered_set&& exclud model_instance_num = -1; } else if (type == OBJ_ASTEROID) { - model_num = Asteroid_info[Asteroids[objp->instance].asteroid_type].model_num[Asteroids[objp->instance].asteroid_subtype]; + model_num = Asteroid_info[Asteroids[objp->instance].asteroid_type].subtypes[Asteroids[objp->instance].asteroid_subtype].model_number; model_instance_num = Asteroids[objp->instance].model_instance_num; } else if (type == OBJ_SHIP) { diff --git a/code/ai/aiturret.cpp b/code/ai/aiturret.cpp index a7727fd5d97..47e89e02227 100644 --- a/code/ai/aiturret.cpp +++ b/code/ai/aiturret.cpp @@ -133,7 +133,7 @@ bool object_in_turret_fov(const object *objp, const ship_subsys *ss, const vec3d model_num = Ship_info[Ships[objp->instance].ship_info_index].model_num; break; case OBJ_ASTEROID: - model_num = Asteroid_info[Asteroids[objp->instance].asteroid_type].model_num[Asteroids[objp->instance].asteroid_subtype]; + model_num = Asteroid_info[Asteroids[objp->instance].asteroid_type].subtypes[Asteroids[objp->instance].asteroid_subtype].model_number; break; default: vm_vec_normalized_dir(&v2e, &objp->pos, tpos); diff --git a/code/asteroid/asteroid.cpp b/code/asteroid/asteroid.cpp index 79de44a2e59..863b3653d94 100644 --- a/code/asteroid/asteroid.cpp +++ b/code/asteroid/asteroid.cpp @@ -262,6 +262,25 @@ static void inner_bound_pos_fixup(asteroid_field *asfieldp, vec3d *pos) } } +/** +* Randomly select an asteroid subtype +*/ +const SCP_string& pick_random_asteroid_type() +{ + return Asteroid_field.field_asteroid_type[Random::next(static_cast(Asteroid_field.field_asteroid_type.size()))]; +} + +int get_asteroid_subtype_index_by_name(const SCP_string &name, int asteroid_idx) { + asteroid_info* this_asteroid = &Asteroid_info[asteroid_idx]; + for (int i = 0; i < static_cast(this_asteroid->subtypes.size()); i++) { + if (this_asteroid->subtypes[i].type_name == name) { + return i; + } + } + + return -1; +} + /** * Create a single asteroid */ @@ -279,8 +298,8 @@ object *asteroid_create(asteroid_field *asfieldp, int asteroid_type, int asteroi int rand_base; // bogus - if(asfieldp == NULL) { - return NULL; + if(asfieldp == nullptr) { + return nullptr; } for (n=0; n= MAX_ASTEROIDS) { nprintf(("Warning","Could not create asteroid, no more slots left\n")); - return NULL; - } - - if((asteroid_type < 0) || (asteroid_type >= (int)Asteroid_info.size())) { - return NULL; + return nullptr; } - if((asteroid_subtype < 0) || (asteroid_subtype >= NUM_ASTEROID_POFS)) { - return NULL; + if (!SCP_vector_inbounds(Asteroid_info, asteroid_type)) { + return nullptr; } // HACK: multiplayer asteroid subtype always 0 to keep subtype in sync @@ -309,9 +324,13 @@ object *asteroid_create(asteroid_field *asfieldp, int asteroid_type, int asteroi asip = &Asteroid_info[asteroid_type]; - // bogus - if(asip->modelp[asteroid_subtype] == NULL) { - return NULL; + if (!SCP_vector_inbounds(asip->subtypes, asteroid_subtype)) { + return nullptr; + } + + // if the model is not loaded then abort + if(asip->subtypes[asteroid_subtype].model_pointer == nullptr) { + return nullptr; } asp = &Asteroids[n]; @@ -326,7 +345,7 @@ object *asteroid_create(asteroid_field *asfieldp, int asteroid_type, int asteroi asp->collide_objsig = -1; asp->target_objnum = -1; - radius = model_get_radius(asip->model_num[asteroid_subtype]); + radius = model_get_radius(asip->subtypes[asteroid_subtype].model_number); vm_vec_sub(&delta_bound, &asfieldp->max_bound, &asfieldp->min_bound); @@ -379,8 +398,8 @@ object *asteroid_create(asteroid_field *asfieldp, int asteroid_type, int asteroi asp->objnum = objnum; asp->model_instance_num = -1; - if (model_get(asip->model_num[asteroid_subtype])->flags & PM_FLAG_HAS_INTRINSIC_MOTION) { - asp->model_instance_num = model_create_instance(objnum, asip->model_num[asteroid_subtype]); + if (model_get(asip->subtypes[asteroid_subtype].model_number)->flags & PM_FLAG_HAS_INTRINSIC_MOTION) { + asp->model_instance_num = model_create_instance(objnum, asip->subtypes[asteroid_subtype].model_number); } // Add to Asteroid_used_list @@ -435,8 +454,8 @@ object *asteroid_create(asteroid_field *asfieldp, int asteroid_type, int asteroi objp->phys_info.max_vel.xyz.y = 0.0f; objp->phys_info.max_vel.xyz.z = vm_vec_mag(&objp->phys_info.desired_vel); - objp->phys_info.mass = asip->modelp[asteroid_subtype]->rad * 700.0f; - objp->phys_info.I_body_inv.vec.rvec.xyz.x = 1.0f / (objp->phys_info.mass*asip->modelp[asteroid_subtype]->rad); + objp->phys_info.mass = asip->subtypes[asteroid_subtype].model_pointer->rad * 700.0f; + objp->phys_info.I_body_inv.vec.rvec.xyz.x = 1.0f / (objp->phys_info.mass*asip->subtypes[asteroid_subtype].model_pointer->rad); objp->phys_info.I_body_inv.vec.uvec.xyz.y = objp->phys_info.I_body_inv.vec.rvec.xyz.x; objp->phys_info.I_body_inv.vec.fvec.xyz.z = objp->phys_info.I_body_inv.vec.rvec.xyz.x; objp->hull_strength = asip->initial_asteroid_strength * (0.8f + (float)Game_skill_level/NUM_SKILL_LEVELS)/2.0f; @@ -465,7 +484,8 @@ void asteroid_sub_create(object *parent_objp, int asteroid_type, vec3d *relvec) int subtype = Asteroids[parent_objp->instance].asteroid_subtype; new_objp = asteroid_create(&Asteroid_field, asteroid_type, subtype); - if (new_objp == NULL) + // New asteroid size doesn't have this subtype, so abort + if (new_objp == nullptr) return; if ( MULTIPLAYER_MASTER ){ @@ -502,27 +522,35 @@ static void asteroid_load(int asteroid_info_index, int asteroid_subtype) int i; asteroid_info *asip; - Assertion(asteroid_info_index < (int)Asteroid_info.size(), "Command to load asteroid at index %i, but only %i asteroids exist", asteroid_info_index, (int)Asteroid_info.size()); - Assertion(asteroid_subtype < NUM_ASTEROID_POFS, "Command to load asteroid pof %i, but only %i pofs are allowed", asteroid_subtype, NUM_ASTEROID_POFS); + Assertion(asteroid_info_index < static_cast(Asteroid_info.size()), + "Command to load asteroid at index %i, but only %i asteroids exist", + asteroid_info_index, + static_cast(Asteroid_info.size())); - if ( (asteroid_info_index >= (int)Asteroid_info.size()) || (asteroid_subtype >= NUM_ASTEROID_POFS) ) { + // invalid asteroid index + if (!SCP_vector_inbounds(Asteroid_info, asteroid_info_index)) { return; } asip = &Asteroid_info[asteroid_info_index]; - if ( !VALID_FNAME(asip->pof_files[asteroid_subtype]) ) + // This asteroid does not have this subtype so just return + if (!SCP_vector_inbounds(asip->subtypes, asteroid_subtype)) { + return; + } + + if ( !VALID_FNAME(asip->subtypes[asteroid_subtype].pof_filename) ) return; //Check if the model is already loaded - if (asip->model_num[asteroid_subtype] >= 0) + if (asip->subtypes[asteroid_subtype].model_number >= 0) return; - asip->model_num[asteroid_subtype] = model_load( asip->pof_files[asteroid_subtype], 0, NULL ); + asip->subtypes[asteroid_subtype].model_number = model_load( asip->subtypes[asteroid_subtype].pof_filename, 0, nullptr ); - if (asip->model_num[asteroid_subtype] >= 0) + if (asip->subtypes[asteroid_subtype].model_number >= 0) { - polymodel *pm = asip->modelp[asteroid_subtype] = model_get(asip->model_num[asteroid_subtype]); + polymodel *pm = asip->subtypes[asteroid_subtype].model_pointer = model_get(asip->subtypes[asteroid_subtype].model_number); if ( asip->num_detail_levels != pm->n_detail_levels ) { @@ -553,10 +581,11 @@ void asteroid_create_all() // each different type (size) of debris piece has a diffenent weight, smaller weighted more heavily than larger. // choose next type from table ship_debris_odds_table by Random::next()%max_weighted_range, // the threshold *below* which the debris type is selected. - struct { + struct ShipDebrisOdds { float random_threshold; int debris_type; - } ship_debris_odds_table[MAX_ACTIVE_DEBRIS_TYPES]; + }; + std::vector shipDebrisOddsTable(Asteroid_field.field_debris_type.size()); float max_weighted_range = 0.0f; @@ -567,7 +596,7 @@ void asteroid_create_all() return; } - if (Asteroid_field.num_used_field_debris_types <= 0) { + if (Asteroid_field.field_debris_type.size() <= 0) { Warning(LOCATION, "An asteroid field is enabled, but no asteroid types were enabled."); return; } @@ -604,17 +633,13 @@ void asteroid_create_all() // get number of debris types if (Asteroid_field.debris_genre == DG_DEBRIS) { - for (idx=0; idx(Asteroid_field.field_debris_type.size()); // Calculate the odds table for (idx=0; idx= 0) asteroid_create(&Asteroid_field, ASTEROID_TYPE_LARGE, subtype); @@ -669,10 +672,10 @@ void asteroid_create_all() float rand_choice = frand() * max_weighted_range; - for (idx=0; idx(Asteroid_field.field_debris_type.size()); idx++) { // for ship debris, choose type according to odds table - if (rand_choice < ship_debris_odds_table[idx].random_threshold) { - asteroid_create(&Asteroid_field, ship_debris_odds_table[idx].debris_type, 0); + if (rand_choice < shipDebrisOddsTable[idx].random_threshold) { + asteroid_create(&Asteroid_field, shipDebrisOddsTable[idx].debris_type, 0); break; } } @@ -732,30 +735,20 @@ void asteroid_create_asteroid_field(int num_asteroids, int field_type, int aster Asteroid_field.speed = (float)asteroid_speed; Asteroid_field.debris_genre = DG_ASTEROID; - for (int j = 0; j < MAX_ACTIVE_DEBRIS_TYPES; j++) { - Asteroid_field.field_debris_type[j] = -1; - } + Asteroid_field.field_debris_type.clear(); - for (int j = 0; j < NUM_ASTEROID_SIZES; j++) { - Asteroid_field.field_asteroid_type[j] = false; - } + Asteroid_field.field_asteroid_type.clear(); - int count = 0; if (brown) { - Asteroid_field.field_asteroid_type[0] = true; - count++; + Asteroid_field.field_asteroid_type.push_back("Brown"); } if (blue) { - Asteroid_field.field_asteroid_type[1] = true; - count++; + Asteroid_field.field_asteroid_type.push_back("Blue"); } if (orange) { - Asteroid_field.field_asteroid_type[2] = true; - count++; + Asteroid_field.field_asteroid_type.push_back("Orange"); } - Asteroid_field.num_used_field_debris_types = count; - Asteroid_field.min_bound = o_min; Asteroid_field.max_bound = o_max; @@ -775,13 +768,13 @@ void asteroid_create_asteroid_field(int num_asteroids, int field_type, int aster Asteroid_field.target_names = targets; // Only create asteroids if we have some to create - if ((count > 0) && (num_asteroids > 0)) { + if ((Asteroid_field.field_debris_type.size() > 0) && (num_asteroids > 0)) { asteroid_create_all(); } } // will replace any existing asteroid or debris field with a debris field -void asteroid_create_debris_field(int num_asteroids, int asteroid_speed, int debris1, int debris2, int debris3, vec3d o_min, vec3d o_max, bool enhanced) +void asteroid_create_debris_field(int num_asteroids, int asteroid_speed, SCP_vector debris_types, vec3d o_min, vec3d o_max, bool enhanced) { remove_all_asteroids(); @@ -796,37 +789,11 @@ void asteroid_create_debris_field(int num_asteroids, int asteroid_speed, int deb Asteroid_field.speed = (float)asteroid_speed; Asteroid_field.debris_genre = DG_DEBRIS; - for (int j = 0; j < MAX_ACTIVE_DEBRIS_TYPES; j++) { - Asteroid_field.field_debris_type[j] = -1; - } + Asteroid_field.field_debris_type.clear(); - for (int j = 0; j < NUM_ASTEROID_SIZES; j++) { - Asteroid_field.field_asteroid_type[j] = false; - } - - int count = 0; - for (int i = 0; i < MAX_ACTIVE_DEBRIS_TYPES; i++) { - if (debris1 >= 0) { - Asteroid_field.field_debris_type[i] = debris1; - debris1 = -1; - count++; - continue; - } - if (debris2 >= 0) { - Asteroid_field.field_debris_type[i] = debris2; - debris2 = -1; - count++; - continue; - } - if (debris3 >= 0) { - Asteroid_field.field_debris_type[i] = debris3; - debris3 = -1; - count++; - continue; - } - } + Asteroid_field.field_asteroid_type.clear(); - Asteroid_field.num_used_field_debris_types = count; + Asteroid_field.field_debris_type = std::move(debris_types); Asteroid_field.min_bound = o_min; Asteroid_field.max_bound = o_max; @@ -839,7 +806,7 @@ void asteroid_create_debris_field(int num_asteroids, int asteroid_speed, int deb Asteroid_field.bound_rad = MAX(3000.0f, b_rad); // Only create debris if we have some to create - if ((count > 0) && (num_asteroids > 0)) { + if ((Asteroid_field.field_debris_type.size() > 0) && (num_asteroids > 0)) { asteroid_create_all(); } } @@ -858,14 +825,8 @@ void asteroid_level_init() Asteroid_field.has_inner_bound = false; Asteroid_field.field_type = FT_ACTIVE; Asteroid_field.debris_genre = DG_ASTEROID; - for (int j = 0; j < MAX_ACTIVE_DEBRIS_TYPES; j++) { - Asteroid_field.field_debris_type[j] = -1; - } - - for (int j = 0; j < NUM_ASTEROID_SIZES; j++) { - Asteroid_field.field_asteroid_type[j] = false; - } - Asteroid_field.num_used_field_debris_types = 0; + Asteroid_field.field_debris_type.clear(); + Asteroid_field.field_asteroid_type.clear(); Asteroid_field.target_names.clear(); if (!Fred_running) @@ -1061,7 +1022,7 @@ bool asteroid_is_within_view(vec3d *pos, float range, bool range_override) */ static void maybe_throw_asteroid() { - Assertion(Asteroid_field.num_used_field_debris_types > 0, "maybe_throw_asteroid() called while num_used_field_debris_types was 0; this should never happen, get a coder!"); + Assertion(Asteroid_field.field_debris_type.size() > 0, "maybe_throw_asteroid() called while field_debris_type.size was 0; this should never happen, get a coder!"); for (asteroid_target& target : Asteroid_targets) { if (!timestamp_elapsed(target.throw_stamp)) @@ -1079,18 +1040,8 @@ static void maybe_throw_asteroid() target.throw_stamp = _timestamp(1000 + 1200 * target.incoming_asteroids / (Game_skill_level+1)); - int counter = Random::next(Asteroid_field.num_used_field_debris_types); - int subtype = -1; - for (int i = 0; i < NUM_ASTEROID_POFS; i++) { - if (Asteroid_field.field_asteroid_type[i]) { - if (counter == 0) { - subtype = i; - break; - } - else - counter--; - } - } + // get a valid subtype + int subtype = get_asteroid_subtype_index_by_name(pick_random_asteroid_type(), ASTEROID_TYPE_LARGE); // this really shouldn't happen but just in case... if (subtype < 0) @@ -1267,7 +1218,7 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos // asteroid weapon collision Assert( other_obj->type == OBJ_WEAPON ); mc.model_instance_num = Asteroids[num].model_instance_num; - mc.model_num = Asteroid_info[Asteroids[num].asteroid_type].model_num[asteroid_subtype]; // Fill in the model to check + mc.model_num = Asteroid_info[Asteroids[num].asteroid_type].subtypes[asteroid_subtype].model_number; // Fill in the model to check mc.orient = &pasteroid->orient; // The object's orient mc.pos = &pasteroid->pos; // The object's position mc.p0 = &other_obj->last_pos; // Point 1 of ray to check @@ -1448,7 +1399,7 @@ int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos } else { // Asteroid is heavier obj mc.model_instance_num = Asteroids[num].model_instance_num; - mc.model_num = Asteroid_info[Asteroids[num].asteroid_type].model_num[asteroid_subtype]; // Fill in the model to check + mc.model_num = Asteroid_info[Asteroids[num].asteroid_type].subtypes[asteroid_subtype].model_number; // Fill in the model to check mc.orient = &pasteroid->orient; // The object's orient mc.radius = model_get_core_radius(Ship_info[Ships[pship_obj->instance].ship_info_index].model_num); @@ -1529,7 +1480,7 @@ void asteroid_render(object * obj, model_draw_list *scene) Assert( asp->flags & AF_USED ); - model_clear_instance( Asteroid_info[asp->asteroid_type].model_num[asp->asteroid_subtype]); + model_clear_instance( Asteroid_info[asp->asteroid_type].subtypes[asp->asteroid_subtype].model_number); model_render_params render_info; @@ -1538,7 +1489,7 @@ void asteroid_render(object * obj, model_draw_list *scene) if (asp->model_instance_num >= 0) render_info.set_replacement_textures(model_get_instance(asp->model_instance_num)->texture_replace); - model_render_queue(&render_info, scene, Asteroid_info[asp->asteroid_type].model_num[asp->asteroid_subtype], &obj->orient, &obj->pos); // Replace MR_NORMAL with 0x07 for big yellow blobs + model_render_queue(&render_info, scene, Asteroid_info[asp->asteroid_type].subtypes[asp->asteroid_subtype].model_number, &obj->orient, &obj->pos); // Replace MR_NORMAL with 0x07 for big yellow blobs } } @@ -1772,9 +1723,10 @@ void asteroid_level_close() //when a level is closed, all models are cleared, so let's make sure that //is tracked for asteroids as well -Mjn - for (int i = 0; i < (int)Asteroid_info.size(); i++) { - for (int j = 0; j < NUM_ASTEROID_POFS; j++) { - Asteroid_info[i].model_num[j] = -1; + for (size_t i = 0; i < Asteroid_info.size(); i++) { + for (size_t j = 0; j < Asteroid_info[i].subtypes.size(); j++) { + Asteroid_info[i].subtypes[j].model_number = -1; + Asteroid_info[i].subtypes[j].model_pointer = nullptr; } } @@ -2233,15 +2185,71 @@ static void asteroid_parse_section() } if (optional_string("$POF file1:")) { - stuff_string(asteroid_p->pof_files[0], F_NAME, MAX_FILENAME_LEN); + asteroid_subtype_info thisType; + + thisType.type_name = "Brown"; + + stuff_string(thisType.pof_filename, F_NAME, MAX_FILENAME_LEN); + + thisType.model_number = -1; + thisType.model_pointer = nullptr; + + asteroid_p->subtypes.push_back(thisType); } if (optional_string("$POF file2:")) { - stuff_string(asteroid_p->pof_files[1], F_NAME, MAX_FILENAME_LEN); + asteroid_subtype_info thisType; + + thisType.type_name = "Blue"; + stuff_string(thisType.pof_filename, F_NAME, MAX_FILENAME_LEN); + + thisType.model_number = -1; + thisType.model_pointer = nullptr; + + asteroid_p->subtypes.push_back(thisType); } if (optional_string("$POF file3:")) { - stuff_string(asteroid_p->pof_files[2], F_NAME, MAX_FILENAME_LEN); + asteroid_subtype_info thisType; + + thisType.type_name = "Orange"; + stuff_string(thisType.pof_filename, F_NAME, MAX_FILENAME_LEN); + + thisType.model_number = -1; + thisType.model_pointer = nullptr; + + asteroid_p->subtypes.push_back(thisType); + } + + // For asteroid types we can have unlimited subtypes + if (asteroid_p->type != ASTEROID_TYPE_DEBRIS) { + while (optional_string("$Subtype:")) { + asteroid_subtype_info thisType; + + stuff_string(thisType.type_name, F_NAME); + + required_string("+POF file:"); + stuff_string(thisType.pof_filename, F_NAME, MAX_FILENAME_LEN); + + thisType.model_number = -1; + thisType.model_pointer = nullptr; + + bool exists = false; + for (const auto& type : asteroid_p->subtypes) { + if (type.type_name == thisType.type_name) { + exists = true; + } + } + + if (exists) { + error_display(0, + "Asteroid subtype %s already exists for asteroid %s", + thisType.type_name.c_str(), + asteroid_p->name); + } else { + asteroid_p->subtypes.push_back(thisType); + } + } } if (optional_string("$Detail distance:")) { @@ -2430,7 +2438,7 @@ static void asteroid_parse_tbl(const char* filename) // Gets the index of an asteroid by name from Asteroid_info int get_asteroid_index(const char* asteroid_name) { - for (int i = 0; i < (int)Asteroid_info.size(); i++) { + for (int i = 0; i < static_cast(Asteroid_info.size()); i++) { if (!stricmp(asteroid_name, Asteroid_info[i].name)) { return i; } @@ -2439,6 +2447,31 @@ int get_asteroid_index(const char* asteroid_name) return -1; } +// For FRED. Gets a list of unique asteroid subtype names +SCP_vector get_list_valid_asteroid_subtypes() +{ + SCP_vector list; + + for (const auto& this_asteroid : Asteroid_info) { + if (this_asteroid.type != ASTEROID_TYPE_DEBRIS) { + for (const auto& subtype : this_asteroid.subtypes) { + bool exists = false; + for (const auto& entry : list) { + if (subtype.type_name == entry) { + exists = true; + } + } + + if (!exists) { + list.push_back(subtype.type_name); + } + } + } + } + + return list; +} + static void verify_asteroid_splits() { @@ -2497,7 +2530,14 @@ static void verify_asteroid_list() } + // Rest should be debris type for (int i = 0; i < (int)asteroid_list.size(); i++) { + + // Remove all subtypes except the first one + asteroid_list[i].subtypes.resize(1); + // Make sure the first subtype is set as debris + asteroid_list[i].subtypes[0].type_name = "Debris"; + if (asteroid_list[i].type < 0) { Asteroid_info.push_back(asteroid_list[i]); } else { @@ -2511,14 +2551,8 @@ static void verify_asteroid_list() //Check that we have the appropriate POF files-Mjn for (int i = 0; i < (int)Asteroid_info.size(); i++) { - if (Asteroid_info[i].pof_files[0][0] == '\0') - Error(LOCATION, "Missing [POF File 1] for %s", Asteroid_info[i].name); - - if (Asteroid_info[i].type != ASTEROID_TYPE_DEBRIS) { - if (Asteroid_info[i].pof_files[1][0] == '\0') - Error(LOCATION, "Missing [POF File 2] for %s", Asteroid_info[i].name); - if (Asteroid_info[i].pof_files[2][0] == '\0') - Error(LOCATION, "Missing [POF File 3] for %s", Asteroid_info[i].name); + if (Asteroid_info[i].subtypes.size() == 0) { + Error(LOCATION, "Asteroid %s needs at least one subtype!", Asteroid_info[i].name); } } } @@ -2555,7 +2589,7 @@ void asteroid_init() } if (Asteroid_icon_closeup_model[0] == '\0') - strcpy_s(Asteroid_icon_closeup_model, Asteroid_info[ASTEROID_TYPE_LARGE].pof_files[0]); // magic file from retail + strcpy_s(Asteroid_icon_closeup_model, Asteroid_info[ASTEROID_TYPE_LARGE].subtypes[0].pof_filename); // magic file from retail } @@ -2596,7 +2630,7 @@ void asteroid_frame() } // If no asteroid types are defined for the field, abort. - if (Asteroid_field.num_used_field_debris_types <= 0) { + if (Asteroid_field.field_debris_type.size() <= 0) { return; } @@ -2708,18 +2742,23 @@ void asteroid_page_in() return; if (Asteroid_field.num_initial_asteroids > 0 ) { - int i, j, k; nprintf(( "Paging", "Paging in asteroids\n" )); + size_t num_debris_types = NUM_ASTEROID_SIZES; + if (Asteroid_field.debris_genre == DG_DEBRIS) { + num_debris_types = Asteroid_field.field_debris_type.size(); + } + - // max of MAX_ACTIVE_DEBRIS_TYPES possible debris field models - for (i=0; isubtypes.size(); k++) { // DEBRIS FIELDS - always use subtype 0 if (Asteroid_field.debris_genre == DG_DEBRIS) { @@ -2740,19 +2779,27 @@ void asteroid_page_in() } } else { // ASTEROID FIELDS - use subtype k, if active - if (!Asteroid_field.field_asteroid_type[k]) { + bool active = false; + for (const auto& active_type : Asteroid_field.field_asteroid_type) { + if (active_type == asip->subtypes[k].type_name) { + active = true; + } + } + + if (!active) { continue; } } - if (asip->model_num[k] < 0) + // model is already paged in + if (asip->subtypes[k].model_number < 0) continue; - asip->modelp[k] = model_get(asip->model_num[k]); + asip->subtypes[k].model_pointer = model_get(asip->subtypes[k].model_number); // Page in textures - for (j=0; jmodelp[k]->n_textures; j++ ) { - asip->modelp[k]->maps[j].PageIn(); + for (int j = 0; j < asip->subtypes[k].model_pointer->n_textures; j++ ) { + asip->subtypes[k].model_pointer->maps[j].PageIn(); } } diff --git a/code/asteroid/asteroid.h b/code/asteroid/asteroid.h index 3490415ada9..14b12ac2175 100644 --- a/code/asteroid/asteroid.h +++ b/code/asteroid/asteroid.h @@ -24,15 +24,14 @@ class model_draw_list; #define MAX_ASTEROIDS 2000 //Increased from 512 to 2000 in 2022 #define NUM_ASTEROID_SIZES 3 -#define NUM_ASTEROID_POFS 3 // Number of POFs per debris size #define ASTEROID_TYPE_DEBRIS -1 #define ASTEROID_TYPE_SMALL 0 #define ASTEROID_TYPE_MEDIUM 1 #define ASTEROID_TYPE_LARGE 2 -// these should always be equal for the benefit of generic asteroids (c.f. asteroid_page_in) -#define MAX_ACTIVE_DEBRIS_TYPES NUM_ASTEROID_SIZES +// Only used for parsing & saving retail compatible mission files +#define MAX_RETAIL_DEBRIS_TYPES 3 // Goober5000 - currently same as MAX_SHIP_DETAIL_LEVELS (put here to avoid an #include) #define MAX_ASTEROID_DETAIL_LEVELS 5 @@ -54,13 +53,20 @@ typedef struct asteroid_split_info { int max; //maximum asteroids to spawn } asteroid_split_info; +// Data structure for storing asteroid subtype info. POFs, model pointer, model num, etc. +typedef struct asteroid_subtype_info { + char pof_filename[MAX_FILENAME_LEN]; + polymodel* model_pointer; + int model_number; + SCP_string type_name; +} asteroid_subtype_info; + class asteroid_info { public: char name[NAME_LENGTH]; // name for the asteroid int type; // type of asteroid, 0 = small, 1 = medium, 2 = large, -1 = debris - char pof_files[NUM_ASTEROID_POFS][MAX_FILENAME_LEN]; // POF files to load/associate with ship - int num_detail_levels; // number of detail levels for this ship + int num_detail_levels; // number of detail levels for this asteroid int detail_distance[MAX_ASTEROID_DETAIL_LEVELS]; // distance to change detail levels at float max_speed; // cap on speed for asteroid float rotational_vel_multiplier; // rotational velocity multiplier for asteroid --wookieejedi @@ -72,13 +78,12 @@ class asteroid_info float blast; // maximum blast impulse from area effect explosion float initial_asteroid_strength; // starting strength of asteroid SCP_vector< asteroid_split_info > split_info; - polymodel *modelp[NUM_ASTEROID_POFS]; - int model_num[NUM_ASTEROID_POFS]; SCP_vector explosion_bitmap_anims; float fireball_radius_multiplier; // the model radius is multiplied by this to determine the fireball size - SCP_string display_name; // only used for hud targeting display and for 'ship' asteroids - float spawn_weight; // ship asteroids only, relative proportion to spawn compared to other types in its asteroid field + SCP_string display_name; // only used for hud targeting display and for debris + float spawn_weight; // debris only, relative proportion to spawn compared to other types in its asteroid field float gravity_const; // multiplier for mission gravity + SCP_vector subtypes; asteroid_info( ) : type(ASTEROID_TYPE_DEBRIS), num_detail_levels(0), max_speed(0), @@ -90,13 +95,6 @@ class asteroid_info name[ 0 ] = 0; display_name = ""; memset( detail_distance, 0, sizeof( detail_distance ) ); - - for (int i = 0; i < NUM_ASTEROID_POFS; i++) - { - modelp[i] = NULL; - model_num[i] = -1; - pof_files[i][0] = '\0'; - } } }; @@ -143,9 +141,8 @@ typedef struct asteroid_field { int num_initial_asteroids; // Number of asteroids at creation. field_type_t field_type; // active throws and wraps, passive does not debris_genre_t debris_genre; // type of debris (ship or asteroid) [generic type] - int field_debris_type[MAX_ACTIVE_DEBRIS_TYPES]; // one of the debris type defines above - bool field_asteroid_type[NUM_ASTEROID_SIZES]; - int num_used_field_debris_types; // how many of the above are used + SCP_vector field_debris_type; // one of the debris type defines above + SCP_vector field_asteroid_type; // one of the asteroid subtypes bool enhanced_visibility_checks; // if true then range checks are overridden for spawning and wrapping asteroids in the field SCP_vector target_names; // default retail behavior is to just throw at the first big ship in the field @@ -167,7 +164,7 @@ void asteroid_level_init(); void asteroid_level_close(); void asteroid_create_all(); void asteroid_create_asteroid_field(int num_asteroids, int field_type, int asteroid_speed, bool brown, bool blue, bool orange, vec3d o_min, vec3d o_max, bool inner_box, vec3d i_min, vec3d i_max, SCP_vector targets); -void asteroid_create_debris_field(int num_asteroids, int asteroid_speed, int debris1, int debris2, int debris3, vec3d o_min, vec3d o_max, bool enhanced); +void asteroid_create_debris_field(int num_asteroids, int asteroid_speed, SCP_vector debris_types, vec3d o_min, vec3d o_max, bool enhanced); void asteroid_render(object* obj, model_draw_list* scene); void asteroid_delete( object *asteroid_objp ); void asteroid_process_pre( object *asteroid_objp ); @@ -181,6 +178,7 @@ void asteroid_show_brackets(); void asteroid_target_closest_danger(); void asteroid_add_target(object* objp); int get_asteroid_index(const char* asteroid_name); +SCP_vector get_list_valid_asteroid_subtypes(); // need to extern for keycontrol debug commands object *asteroid_create(asteroid_field *asfieldp, int asteroid_type, int asteroid_subtype, bool check_visibility = false); diff --git a/code/graphics/shadows.cpp b/code/graphics/shadows.cpp index 30bc8f273b6..d62369c9c1d 100644 --- a/code/graphics/shadows.cpp +++ b/code/graphics/shadows.cpp @@ -494,8 +494,8 @@ void shadows_render_all(fov_t fov, matrix *eye_orient, vec3d *eye_pos) render_info.set_object_number(OBJ_INDEX(objp)); render_info.set_flags(MR_IS_ASTEROID | MR_NO_TEXTURING | MR_NO_LIGHTING); - model_clear_instance( Asteroid_info[Asteroids[objp->instance].asteroid_type].model_num[Asteroids[objp->instance].asteroid_subtype]); - model_render_queue(&render_info, &scene, Asteroid_info[Asteroids[objp->instance].asteroid_type].model_num[Asteroids[objp->instance].asteroid_subtype], &objp->orient, &objp->pos); + model_clear_instance( Asteroid_info[Asteroids[objp->instance].asteroid_type].subtypes[Asteroids[objp->instance].asteroid_subtype].model_number); + model_render_queue(&render_info, &scene, Asteroid_info[Asteroids[objp->instance].asteroid_type].subtypes[Asteroids[objp->instance].asteroid_subtype].model_number, &objp->orient, &objp->pos); } break; diff --git a/code/hud/hudbrackets.cpp b/code/hud/hudbrackets.cpp index da1314eaae8..94c14c11fd7 100644 --- a/code/hud/hudbrackets.cpp +++ b/code/hud/hudbrackets.cpp @@ -438,7 +438,7 @@ void HudGaugeBrackets::renderObjectBrackets(object *targetp, color *clr, int w_c { int pof = 0; pof = Asteroids[targetp->instance].asteroid_subtype; - modelnum = Asteroid_info[Asteroids[targetp->instance].asteroid_type].model_num[pof]; + modelnum = Asteroid_info[Asteroids[targetp->instance].asteroid_type].subtypes[pof].model_number; bound_rc = model_find_2d_bound_min( modelnum, &targetp->orient, &targetp->pos,&x1,&y1,&x2,&y2 ); } break; diff --git a/code/hud/hudtarget.cpp b/code/hud/hudtarget.cpp index e8bc5691c50..4724cce419a 100644 --- a/code/hud/hudtarget.cpp +++ b/code/hud/hudtarget.cpp @@ -2519,7 +2519,7 @@ void hud_target_in_reticle_new() { int pof = 0; pof = Asteroids[A->instance].asteroid_subtype; - mc.model_num = Asteroid_info[Asteroids[A->instance].asteroid_type].model_num[pof]; + mc.model_num = Asteroid_info[Asteroids[A->instance].asteroid_type].subtypes[pof].model_number; } break; case OBJ_JUMP_NODE: diff --git a/code/hud/hudtargetbox.cpp b/code/hud/hudtargetbox.cpp index 138497fb554..69b73a83414 100644 --- a/code/hud/hudtargetbox.cpp +++ b/code/hud/hudtargetbox.cpp @@ -1171,7 +1171,7 @@ void HudGaugeTargetBox::renderTargetAsteroid(object *target_objp) vm_vec_copy_scale(&obj_pos,&orient_vec,factor); renderTargetSetup(&camera_eye, &camera_orient, 0.5f); - model_clear_instance(Asteroid_info[asteroidp->asteroid_type].model_num[pof]); + model_clear_instance(Asteroid_info[asteroidp->asteroid_type].subtypes[pof].model_number); model_render_params render_info; @@ -1231,7 +1231,7 @@ void HudGaugeTargetBox::renderTargetAsteroid(object *target_objp) render_info.set_flags(flags | MR_NO_FOGGING); - model_render_immediate( &render_info, Asteroid_info[asteroidp->asteroid_type].model_num[pof], &target_objp->orient, &obj_pos ); + model_render_immediate( &render_info, Asteroid_info[asteroidp->asteroid_type].subtypes[pof].model_number, &target_objp->orient, &obj_pos ); if ( Monitor_mask >= 0 ) { gr_stencil_set(GR_STENCIL_NONE); diff --git a/code/mission/missionparse.cpp b/code/mission/missionparse.cpp index e0e943e3e7f..f37afffe8d4 100644 --- a/code/mission/missionparse.cpp +++ b/code/mission/missionparse.cpp @@ -5823,14 +5823,13 @@ void parse_bitmaps(mission *pm) void parse_asteroid_fields(mission *pm) { - int i, count; + int i; Assert(pm != NULL); Asteroid_field.num_initial_asteroids = 0; i = 0; - count = 0; if (!optional_string("#Asteroid Fields")) return; @@ -5857,73 +5856,82 @@ void parse_asteroid_fields(mission *pm) Asteroid_field.debris_genre = (debris_genre_t)type; } - for (int j = 0; j < MAX_ACTIVE_DEBRIS_TYPES; j++) { - Asteroid_field.field_debris_type[j] = -1; - } + Asteroid_field.field_debris_type.clear(); - for (int j = 0; j < NUM_ASTEROID_SIZES; j++) { - Asteroid_field.field_asteroid_type[j] = false; - } + Asteroid_field.field_asteroid_type.clear(); // Debris types if (Asteroid_field.debris_genre == DG_DEBRIS) { // Obsolete and only for backwards compatibility - for (int j = 0; j < MAX_ACTIVE_DEBRIS_TYPES; j++) { + for (int j = 0; j < MAX_RETAIL_DEBRIS_TYPES; j++) { if (optional_string("+Field Debris Type:")) { stuff_int(&Asteroid_field.field_debris_type[j]); - count++; } } // Get asteroids by name - for (int j = 0; j < MAX_ACTIVE_DEBRIS_TYPES; j++) { - if (optional_string("+Field Debris Type Name:")) { - SCP_string ast_name; - stuff_string(ast_name, F_NAME); - int subtype; - subtype = get_asteroid_index(ast_name.c_str()); - if (subtype >= 0) { - Asteroid_field.field_debris_type[j] = subtype; - count++; - } else { - WarningEx(LOCATION, "Mission %s\n Invalid asteroid debris %s!", pm->name, ast_name.c_str()); - } + while (optional_string("+Field Debris Type Name:")) { + SCP_string ast_name; + stuff_string(ast_name, F_NAME); + int subtype; + subtype = get_asteroid_index(ast_name.c_str()); + if (subtype >= 0) { + Asteroid_field.field_debris_type.push_back(subtype); + } else { + WarningEx(LOCATION, "Mission %s\n Invalid asteroid debris %s!", pm->name, ast_name.c_str()); + } } - } // Asteroid types } else { + // Retail asteroid subtypes + SCP_string colors[NUM_ASTEROID_SIZES] = {"Brown", "Blue", "Orange"}; + // Obsolete and only for backwards compatibility for (int j = 0; j < NUM_ASTEROID_SIZES; j++) { if (optional_string("+Field Debris Type:")) { int subtype; stuff_int(&subtype); - Asteroid_field.field_asteroid_type[subtype] = true; - count++; + Asteroid_field.field_asteroid_type.push_back(colors[subtype]); } } // Get asteroids by name - for (int j = 0; j < NUM_ASTEROID_SIZES; j++) { - if (optional_string("+Field Debris Type Name:")) { - SCP_string ast_name; - stuff_string(ast_name, F_NAME); - int subtype = get_asteroid_index(ast_name.c_str()); - // If the returned index is valid but not one of the first three then it's a debris type instead of asteroid - if ((subtype >= 0) && (subtype < NUM_ASTEROID_SIZES)) { - Asteroid_field.field_asteroid_type[subtype] = true; - count++; - } else { - WarningEx(LOCATION, "Mission %s\n Invalid asteroid %s!", pm->name, ast_name.c_str()); + while (optional_string("+Field Debris Type Name:")) { + SCP_string ast_name; + stuff_string(ast_name, F_NAME); + + // Old saving for asteroids was bugged and saved the asteroid size rather than the subtype color + // so we'll compensate for that here + for (size_t k = 0; k < NUM_ASTEROID_SIZES; k++) { + // if we get the name for small/medium/large asteroid then convert it to retail colors + if (stricmp(ast_name.c_str(), Asteroid_info[k].name) == 0) { + ast_name = colors[k]; + break; } } + + auto list = get_list_valid_asteroid_subtypes(); + + //validate the asteroid subtype name + bool valid = false; + for (const auto& entry : list) { + if (ast_name == entry) { + valid = true; + break; + } + } + + if (valid){ + Asteroid_field.field_asteroid_type.push_back(ast_name); + } else { + WarningEx(LOCATION, "Mission %s\n Invalid asteroid %s!", pm->name, ast_name.c_str()); + } } } - Asteroid_field.num_used_field_debris_types = count; - bool invalid_asteroids = false; for (int& ast_type : Asteroid_field.field_debris_type) { if (ast_type >= (int)Asteroid_info.size()) { @@ -5936,9 +5944,11 @@ void parse_asteroid_fields(mission *pm) Warning(LOCATION, "The Asteroid field contains invalid entries!"); // backward compatibility - if ( (Asteroid_field.debris_genre == DG_ASTEROID) && (Asteroid_field.num_used_field_debris_types == 0) ) { - Asteroid_field.field_asteroid_type[0] = true; - Asteroid_field.num_used_field_debris_types = 1; + // Is this a good idea? This doesn't seem like a good idea. What is this compatibility actually for?? + // If you've defined an asteroid field but didn't define any 'roids, then tough luck. Fix your mission. + // If this is for a retail mission then this needs to be hardcoded for that specific mission file probably. - Mjn + if ((Asteroid_field.debris_genre == DG_ASTEROID) && (Asteroid_field.field_debris_type.size() == 0)) { + Asteroid_field.field_asteroid_type.push_back("Brown"); } required_string("$Average Speed:"); diff --git a/code/model/modelread.cpp b/code/model/modelread.cpp index 66e0d24c56b..be4e3426599 100644 --- a/code/model/modelread.cpp +++ b/code/model/modelread.cpp @@ -4865,7 +4865,7 @@ void model_get_moving_submodel_list(SCP_vector &submodel_vector, const obje if (model_instance_num < 0) { return; } - model_num = Asteroid_info[Asteroids[objp->instance].asteroid_type].model_num[Asteroids[objp->instance].asteroid_subtype]; + model_num = Asteroid_info[Asteroids[objp->instance].asteroid_type].subtypes[Asteroids[objp->instance].asteroid_subtype].model_number; } else { return; diff --git a/code/object/collideshipship.cpp b/code/object/collideshipship.cpp index 718e9a544ea..8b20004b985 100644 --- a/code/object/collideshipship.cpp +++ b/code/object/collideshipship.cpp @@ -582,7 +582,7 @@ void calculate_ship_ship_collision_physics(collision_info_struct *ship_ship_hit_ model_instance_num = Ships[heavy->instance].model_instance_num; pmi = model_get_instance(model_instance_num); } else if (heavy->type == OBJ_ASTEROID) { - pm = Asteroid_info[Asteroids[heavy->instance].asteroid_type].modelp[Asteroids[heavy->instance].asteroid_subtype]; + pm = Asteroid_info[Asteroids[heavy->instance].asteroid_type].subtypes[Asteroids[heavy->instance].asteroid_subtype].model_pointer; model_instance_num = Asteroids[heavy->instance].model_instance_num; pmi = model_get_instance(model_instance_num); } else if (heavy->type == OBJ_DEBRIS) { diff --git a/code/object/object.cpp b/code/object/object.cpp index 50b922e67fb..a42677b5055 100644 --- a/code/object/object.cpp +++ b/code/object/object.cpp @@ -2057,7 +2057,7 @@ int object_get_model(const object *objp) case OBJ_ASTEROID: { asteroid *asp = &Asteroids[objp->instance]; - return Asteroid_info[asp->asteroid_type].model_num[asp->asteroid_subtype]; + return Asteroid_info[asp->asteroid_type].subtypes[asp->asteroid_subtype].model_number; } case OBJ_DEBRIS: { diff --git a/code/object/objectsort.cpp b/code/object/objectsort.cpp index 12c414a8467..73b44350a53 100644 --- a/code/object/objectsort.cpp +++ b/code/object/objectsort.cpp @@ -70,7 +70,7 @@ inline bool sorted_obj::operator < (const sorted_obj &other) const asteroid *asp; asp = &Asteroids[obj->instance]; - model_num_a = Asteroid_info[asp->asteroid_type].model_num[asp->asteroid_subtype]; + model_num_a = Asteroid_info[asp->asteroid_type].subtypes[asp->asteroid_subtype].model_number; } if ( other.obj->type == OBJ_SHIP ) { @@ -94,7 +94,7 @@ inline bool sorted_obj::operator < (const sorted_obj &other) const asteroid *asp; asp = &Asteroids[other.obj->instance]; - model_num_b = Asteroid_info[asp->asteroid_type].model_num[asp->asteroid_subtype]; + model_num_b = Asteroid_info[asp->asteroid_type].subtypes[asp->asteroid_subtype].model_number; } if ( model_num_a == model_num_b ) { diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index d8edce74535..d39050c26aa 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -16387,12 +16387,21 @@ void sexp_set_debris_field(int n) vec3d o_min = vm_vec_new((float)o_minx, (float)o_miny, (float)o_minz); vec3d o_max = vm_vec_new((float)o_maxx, (float)o_maxy, (float)o_maxz); + SCP_vector debris_types; + if (debris1 > 0) { + debris_types.push_back(debris1); + } + if (debris2 > 0) { + debris_types.push_back(debris2); + } + if (debris3 > 0) { + debris_types.push_back(debris3); + } + asteroid_create_debris_field( num_asteroids, asteroid_speed, - debris1, - debris2, - debris3, + debris_types, o_min, o_max, enhanced); diff --git a/code/scripting/api/libs/graphics.cpp b/code/scripting/api/libs/graphics.cpp index 798e6ab77f3..e8e14878d1d 100644 --- a/code/scripting/api/libs/graphics.cpp +++ b/code/scripting/api/libs/graphics.cpp @@ -1109,7 +1109,7 @@ ADE_FUNC(drawTargetingBrackets, l_Graphics, "object Object, [boolean draw=true, break; case OBJ_ASTEROID: pof = Asteroids[targetp->instance].asteroid_subtype; - modelnum = Asteroid_info[Asteroids[targetp->instance].asteroid_type].model_num[pof]; + modelnum = Asteroid_info[Asteroids[targetp->instance].asteroid_type].subtypes[pof].model_number; bound_rc = model_find_2d_bound_min( modelnum, &targetp->orient, &targetp->pos,&x1,&y1,&x2,&y2 ); break; case OBJ_JUMP_NODE: diff --git a/code/scripting/api/objs/object.cpp b/code/scripting/api/objs/object.cpp index 5b8951f0225..6e9fddc30a8 100644 --- a/code/scripting/api/objs/object.cpp +++ b/code/scripting/api/objs/object.cpp @@ -505,7 +505,7 @@ ADE_FUNC( break; case OBJ_ASTEROID: temp = Asteroids[obj->instance].asteroid_subtype; - model_num = Asteroid_info[Asteroids[obj->instance].asteroid_type].model_num[temp]; + model_num = Asteroid_info[Asteroids[obj->instance].asteroid_type].subtypes[temp].model_number; flags = (MC_CHECK_MODEL | MC_CHECK_RAY); break; default: diff --git a/code/ship/shipfx.cpp b/code/ship/shipfx.cpp index f10b495817c..a4514357b49 100644 --- a/code/ship/shipfx.cpp +++ b/code/ship/shipfx.cpp @@ -979,7 +979,7 @@ bool shipfx_eye_in_shadow( vec3d *eye_pos, object * src_obj, int light_n ) vm_vec_scale_add( &rp1, &rp0, &light_dir, objp->radius*10.0f ); mc.model_instance_num = -1; - mc.model_num = Asteroid_info[ast->asteroid_type].model_num[ast->asteroid_subtype]; // Fill in the model to check + mc.model_num = Asteroid_info[ast->asteroid_type].subtypes[ast->asteroid_subtype].model_number; // Fill in the model to check mc.submodel_num = -1; mc.orient = &objp->orient; // The object's orient mc.pos = &objp->pos; // The object's position diff --git a/code/weapon/beam.cpp b/code/weapon/beam.cpp index 1da8c66f3d3..c07d6f03518 100644 --- a/code/weapon/beam.cpp +++ b/code/weapon/beam.cpp @@ -2162,7 +2162,7 @@ int beam_get_model(object *objp) if(Asteroids[objp->instance].asteroid_type < 0){ return -1; } - return Asteroid_info[Asteroids[objp->instance].asteroid_type].model_num[pof]; + return Asteroid_info[Asteroids[objp->instance].asteroid_type].subtypes[pof].model_number; default: // this shouldn't happen too often diff --git a/fred2/asteroideditordlg.cpp b/fred2/asteroideditordlg.cpp index 64f11f1a42f..bb7acbb8523 100644 --- a/fred2/asteroideditordlg.cpp +++ b/fred2/asteroideditordlg.cpp @@ -18,6 +18,7 @@ #include "FREDDoc.h" #include "debris/debris.h" // Asteroid stuff. #include "species_defs/species_defs.h" +#include "checkboxlistdlg.h" #ifdef _DEBUG #undef THIS_FILE @@ -26,9 +27,6 @@ static char THIS_FILE[] = __FILE__; #define ID_FIELD_MENU 9000 -// helps in looping over combo boxes -int Dlg_id[MAX_ACTIVE_DEBRIS_TYPES] = {IDC_SHIP_DEBRIS1, IDC_SHIP_DEBRIS2, IDC_SHIP_DEBRIS3}; - ///////////////////////////////////////////////////////////////////////////// // asteroid_editor dialog @@ -97,7 +95,7 @@ BEGIN_MESSAGE_MAP(asteroid_editor, CDialog) ON_WM_CLOSE() ON_BN_CLICKED(IDC_ENABLE_INNER_BOX, OnEnableInnerBox) ON_BN_CLICKED(IDC_PASSIVE_FIELD, OnPassiveField) - ON_BN_CLICKED(IDC_FIELD_DEBRIS, OnFieldShip) + ON_BN_CLICKED(IDC_FIELD_DEBRIS, OnFieldDebris) ON_BN_CLICKED(IDC_ACTIVE_FIELD, OnActiveField) ON_BN_CLICKED(IDC_FIELD_ASTEROID, OnFieldAsteroid) ON_BN_CLICKED(IDC_ADD_FIELD, OnAddField) @@ -106,8 +104,10 @@ BEGIN_MESSAGE_MAP(asteroid_editor, CDialog) ON_BN_CLICKED(IDC_ADD_FIELD_TARGET, OnAddFieldTarget) ON_BN_CLICKED(IDC_REMOVE_FIELD_TARGET, OnRemoveFieldTarget) ON_BN_CLICKED(IDC_RANGE_OVERRIDE, OnEnableRangeOverride) + ON_BN_CLICKED(IDC_SELECT_DEBRIS, OnBnClickedSelectDebris) + ON_BN_CLICKED(IDC_SELECT_ASTEROID, OnBnClickedSelectAsteroid) //}}AFX_MSG_MAP -END_MESSAGE_MAP() + END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // asteroid_editor message handlers @@ -279,10 +279,20 @@ int asteroid_editor::validate_data() } } - // check if passive, ship debris field, at least one debris type selected + // Compress the debris field vector to remove "-1" values + if (a_field[cur_field].field_debris_type.size() > 0) { + a_field[cur_field].field_debris_type.erase( + std::remove_if(a_field[cur_field].field_debris_type.begin(), + a_field[cur_field].field_debris_type.end(), + [](int value) { return value < 0; } + ), + a_field[cur_field].field_debris_type.end()); + } + + // check if passive, debris field, at least one debris type selected if (a_field[0].field_type == FT_PASSIVE) { if (a_field[0].debris_genre == DG_DEBRIS) { - if ( (a_field[0].field_debris_type[0] == -1) && (a_field[0].field_debris_type[1] == -1) && (a_field[0].field_debris_type[2] == -1) ) { + if (a_field[cur_field].field_debris_type.size() == 0) { MessageBox("You must choose one or more types of ship debris"); return 0; } @@ -291,7 +301,7 @@ int asteroid_editor::validate_data() // check at least one asteroid subtype is selected if (a_field[0].debris_genre == DG_ASTEROID) { - if ( (!a_field[0].field_asteroid_type[0]) && (!a_field[0].field_asteroid_type[1]) && (!a_field[0].field_asteroid_type[2]) ) { + if (a_field[0].field_asteroid_type.size() == 0) { MessageBox("You must choose one or more asteroid subtypes"); return 0; } @@ -310,6 +320,13 @@ void asteroid_editor::OnOK() if (!validate_data()) { return; } + // If it's an asteroid field, then clear the debris types + // Otherwise clear the asteroid types + if (a_field->debris_genre == DG_ASTEROID) { + a_field->field_debris_type.clear(); + } else { + a_field->field_asteroid_type.clear(); + } for (i=0; i<1 /*MAX_ASTEROID_FIELDS*/; i++) Asteroid_field = a_field[i]; @@ -335,7 +352,7 @@ BOOL asteroid_editor::OnInitDialog() void asteroid_editor::update_init() { - int num_asteroids, idx; + int num_asteroids; CString str; UpdateData(TRUE); @@ -369,46 +386,6 @@ void asteroid_editor::update_init() // type of field MODIFY(a_field[last_field].field_type, m_field_type); MODIFY(a_field[last_field].debris_genre, m_debris_genre); - if ( (m_field_type == FT_PASSIVE) && (m_debris_genre == DG_DEBRIS) ) { - // we should have ship debris - for (idx=0; idxGetCurSel(); - int cur_choice; - if (cur_sel != CB_ERR) { - cur_choice = (int)((CComboBox*)GetDlgItem(Dlg_id[idx]))->GetItemData(cur_sel); - } else { - cur_choice = -1; - } - MODIFY(a_field[cur_field].field_debris_type[idx], cur_choice); - } - } - - if ( m_debris_genre == DG_ASTEROID ) { - bool cur_choice; - if ( ((CButton *)GetDlgItem(IDC_SUBTYPE1))->GetCheck() == 1) { - cur_choice = true; - } else { - cur_choice = false; - } - MODIFY(a_field[cur_field].field_asteroid_type[0], cur_choice); - - - if ( ((CButton *)GetDlgItem(IDC_SUBTYPE2))->GetCheck() == 1) { - cur_choice = true; - } else { - cur_choice = false; - } - MODIFY(a_field[cur_field].field_asteroid_type[1], cur_choice); - - - if ( ((CButton *)GetDlgItem(IDC_SUBTYPE3))->GetCheck() == 1) { - cur_choice = true; - } else { - cur_choice = false; - } - MODIFY(a_field[cur_field].field_asteroid_type[2], cur_choice); - } MODIFY(a_field[last_field].has_inner_bound, (bool)m_enable_inner_bounds); @@ -455,56 +432,6 @@ void asteroid_editor::update_init() m_field_targets = a_field[cur_field].target_names; - // set up combo boxes - uint i; - int k, box_index; - - // add "None" to each box - for (k = 0; k < MAX_ACTIVE_DEBRIS_TYPES; k++) - { - box_index = ((CComboBox*)GetDlgItem(Dlg_id[k]))->AddString("None"); - ((CComboBox*)GetDlgItem(Dlg_id[k]))->SetItemData(box_index, static_cast(-1)); - } - - // now add each kind of debris to each box - CString name; - char *debris_size[NUM_ASTEROID_SIZES] = { "Small", "Medium", "Large" }; - - // each species - if (Asteroid_info.size() > NUM_ASTEROID_SIZES) { - for (i = NUM_ASTEROID_SIZES; i < Asteroid_info.size(); i++) - { - // each box - for (k = 0; k < MAX_ACTIVE_DEBRIS_TYPES; k++) - { - box_index = ((CComboBox*)GetDlgItem(Dlg_id[k]))->AddString(Asteroid_info[i].name); - ((CComboBox*)GetDlgItem(Dlg_id[k]))->SetItemData(box_index, i); - } - } - } - - // set active debris type for each combo box - int box_count, cur_box_data; - for (idx=0;idxGetCount(); - for (box_index=0; box_indexGetItemData(box_index); - if (cur_box_data == a_field[cur_field].field_debris_type[idx]) { - // set cur sel - ((CComboBox*)GetDlgItem(Dlg_id[idx]))->SetCurSel(box_index); - break; - } - } - } - } - - // set up asteroid subtype checkboxes - ((CButton*)GetDlgItem(IDC_SUBTYPE1))->SetCheck(a_field[cur_field].field_asteroid_type[0]); - ((CButton*)GetDlgItem(IDC_SUBTYPE2))->SetCheck(a_field[cur_field].field_asteroid_type[1]); - ((CButton*)GetDlgItem(IDC_SUBTYPE3))->SetCheck(a_field[cur_field].field_asteroid_type[2]); - UpdateData(FALSE); OnEnableAsteroids(); @@ -530,12 +457,8 @@ void asteroid_editor::OnEnableAsteroids() GetDlgItem(IDC_FIELD_DEBRIS)->EnableWindow(m_enable_asteroids); GetDlgItem(IDC_FIELD_ASTEROID)->EnableWindow(m_enable_asteroids); - GetDlgItem(IDC_SHIP_DEBRIS1)->EnableWindow(m_enable_asteroids && (Asteroid_field.debris_genre == DG_DEBRIS)); - GetDlgItem(IDC_SHIP_DEBRIS2)->EnableWindow(m_enable_asteroids && (Asteroid_field.debris_genre == DG_DEBRIS)); - GetDlgItem(IDC_SHIP_DEBRIS3)->EnableWindow(m_enable_asteroids && (Asteroid_field.debris_genre == DG_DEBRIS)); - GetDlgItem(IDC_SUBTYPE1)->EnableWindow(m_enable_asteroids && (Asteroid_field.debris_genre == DG_ASTEROID)); - GetDlgItem(IDC_SUBTYPE2)->EnableWindow(m_enable_asteroids && (Asteroid_field.debris_genre == DG_ASTEROID)); - GetDlgItem(IDC_SUBTYPE3)->EnableWindow(m_enable_asteroids && (Asteroid_field.debris_genre == DG_ASTEROID)); + GetDlgItem(IDC_SELECT_DEBRIS)->EnableWindow(m_enable_asteroids && (Asteroid_field.debris_genre == DG_DEBRIS)); + GetDlgItem(IDC_SELECT_ASTEROID)->EnableWindow(m_enable_asteroids && (Asteroid_field.debris_genre == DG_ASTEROID)); GetDlgItem(IDC_FIELD_TARGET_LIST)->EnableWindow(m_enable_asteroids); GetDlgItem(IDC_ADD_FIELD_TARGET)->EnableWindow(m_enable_asteroids); @@ -627,7 +550,7 @@ void asteroid_editor::OnEnableField() // maybe enable species if ( m_enable_asteroids && (m_field_type == FT_PASSIVE) && (m_debris_genre == DG_DEBRIS) ) { - OnFieldShip(); + OnFieldDebris(); } } @@ -638,14 +561,10 @@ void asteroid_editor::OnActiveField() m_field_type = FT_ACTIVE; m_debris_genre = DG_ASTEROID; - // gray out ship and species + // gray out debris GetDlgItem(IDC_FIELD_DEBRIS)->EnableWindow(FALSE); - GetDlgItem(IDC_SHIP_DEBRIS1)->EnableWindow(FALSE); - GetDlgItem(IDC_SHIP_DEBRIS2)->EnableWindow(FALSE); - GetDlgItem(IDC_SHIP_DEBRIS3)->EnableWindow(FALSE); - GetDlgItem(IDC_SUBTYPE1)->EnableWindow(TRUE); - GetDlgItem(IDC_SUBTYPE2)->EnableWindow(TRUE); - GetDlgItem(IDC_SUBTYPE3)->EnableWindow(TRUE); + GetDlgItem(IDC_SELECT_DEBRIS)->EnableWindow(FALSE); + GetDlgItem(IDC_SELECT_ASTEROID)->EnableWindow(TRUE); // force check of asteroid ((CButton*)GetDlgItem(IDC_FIELD_ASTEROID))->SetCheck(1); @@ -667,15 +586,11 @@ void asteroid_editor::OnPassiveField() // acivate ship GetDlgItem(IDC_FIELD_DEBRIS)->EnableWindow(TRUE); - // maybe activate species - GetDlgItem(IDC_SHIP_DEBRIS1)->EnableWindow(m_debris_genre == DG_DEBRIS); - GetDlgItem(IDC_SHIP_DEBRIS2)->EnableWindow(m_debris_genre == DG_DEBRIS); - GetDlgItem(IDC_SHIP_DEBRIS3)->EnableWindow(m_debris_genre == DG_DEBRIS); + // maybe activate debris + GetDlgItem(IDC_SELECT_DEBRIS)->EnableWindow(m_debris_genre == DG_DEBRIS); // maybe activate asteroid subtype - GetDlgItem(IDC_SUBTYPE1)->EnableWindow(m_debris_genre == DG_ASTEROID); - GetDlgItem(IDC_SUBTYPE2)->EnableWindow(m_debris_genre == DG_ASTEROID); - GetDlgItem(IDC_SUBTYPE3)->EnableWindow(m_debris_genre == DG_ASTEROID); + GetDlgItem(IDC_SELECT_ASTEROID)->EnableWindow(m_debris_genre == DG_ASTEROID); // force check of current debris type @@ -690,42 +605,48 @@ void asteroid_editor::OnPassiveField() GetDlgItem(IDC_ENABLE_INNER_BOX)->EnableWindow(FALSE); } -void asteroid_editor::OnFieldShip() +void asteroid_editor::OnFieldDebris() { // set debris type m_debris_genre = DG_DEBRIS; - GetDlgItem(IDC_SHIP_DEBRIS1)->EnableWindow(TRUE); - GetDlgItem(IDC_SHIP_DEBRIS2)->EnableWindow(TRUE); - GetDlgItem(IDC_SHIP_DEBRIS3)->EnableWindow(TRUE); + GetDlgItem(IDC_SELECT_DEBRIS)->EnableWindow(TRUE); - GetDlgItem(IDC_SUBTYPE1)->EnableWindow(FALSE); - GetDlgItem(IDC_SUBTYPE2)->EnableWindow(FALSE); - GetDlgItem(IDC_SUBTYPE3)->EnableWindow(FALSE); + GetDlgItem(IDC_SELECT_ASTEROID)->EnableWindow(FALSE); - // force check of ship + // force check of debris ((CButton*)GetDlgItem(IDC_FIELD_ASTEROID))->SetCheck(0); ((CButton*)GetDlgItem(IDC_FIELD_DEBRIS))->SetCheck(1); + // force check of passive field + ((CButton*)GetDlgItem(IDC_ACTIVE_FIELD))->SetCheck(0); + ((CButton*)GetDlgItem(IDC_PASSIVE_FIELD))->SetCheck(1); + } void asteroid_editor::OnFieldAsteroid() { - // set debris type + // set asteroid type m_debris_genre = DG_ASTEROID; - GetDlgItem(IDC_SHIP_DEBRIS1)->EnableWindow(FALSE); - GetDlgItem(IDC_SHIP_DEBRIS2)->EnableWindow(FALSE); - GetDlgItem(IDC_SHIP_DEBRIS3)->EnableWindow(FALSE); + GetDlgItem(IDC_SELECT_DEBRIS)->EnableWindow(FALSE); - GetDlgItem(IDC_SUBTYPE1)->EnableWindow(TRUE); - GetDlgItem(IDC_SUBTYPE2)->EnableWindow(TRUE); - GetDlgItem(IDC_SUBTYPE3)->EnableWindow(TRUE); + GetDlgItem(IDC_SELECT_ASTEROID)->EnableWindow(TRUE); // force check of asteroid ((CButton*)GetDlgItem(IDC_FIELD_ASTEROID))->SetCheck(1); ((CButton*)GetDlgItem(IDC_FIELD_DEBRIS))->SetCheck(0); + + // force check of existing field type + if (m_field_type == FT_ACTIVE) { + ((CButton*)GetDlgItem(IDC_ACTIVE_FIELD))->SetCheck(1); + ((CButton*)GetDlgItem(IDC_PASSIVE_FIELD))->SetCheck(0); + } else { + ((CButton*)GetDlgItem(IDC_ACTIVE_FIELD))->SetCheck(0); + ((CButton*)GetDlgItem(IDC_PASSIVE_FIELD))->SetCheck(1); + } + } void asteroid_editor::OnAddField() @@ -790,3 +711,66 @@ void asteroid_editor::OnRemoveFieldTarget() OnFieldTargetChange(); } + +void asteroid_editor::OnBnClickedSelectDebris() +{ + // create a list of debris types + SCP_vector> debris; + for (size_t i = 0; i < Asteroid_info.size(); i++) { + if (Asteroid_info[i].type == -1) { + bool active = false; + for (size_t j = 0; j < a_field[cur_field].field_debris_type.size(); j++) { + if (static_cast(i) == a_field[cur_field].field_debris_type[j]) { + active = true; + break; + } + } + debris.emplace_back(Asteroid_info[i].name, active); + } + } + + // display the checklist + CheckBoxListDlg dlg; + dlg.SetCaption("Select Debris"); + dlg.SetOptions(debris); + dlg.DoModal(); + + // activate selected debris + a_field[cur_field].field_debris_type.clear(); + for (int i = 0; i < static_cast(debris.size()); i++) { + if (dlg.IsChecked(i)) { + a_field[cur_field].field_debris_type.push_back(get_asteroid_index(debris[i].first.GetString())); + } + } +} + +void asteroid_editor::OnBnClickedSelectAsteroid() +{ + // create a list of asteroid types + SCP_vector> asteroids; + auto list = get_list_valid_asteroid_subtypes(); + for (size_t i = 0; i < list.size(); i++) { + bool active = false; + for (size_t j = 0; j < a_field[cur_field].field_asteroid_type.size(); j++) { + if (list[i] == a_field[cur_field].field_asteroid_type[j]) { + active = true; + break; + } + } + asteroids.emplace_back(list[i].c_str(), active); + } + + // display the checklist + CheckBoxListDlg dlg; + dlg.SetCaption("Select Asteroid Types"); + dlg.SetOptions(asteroids); + dlg.DoModal(); + + // activate selected asteroids + a_field[cur_field].field_asteroid_type.clear(); + for (int i = 0; i < static_cast(asteroids.size()); i++) { + if (dlg.IsChecked(i)) { + a_field[cur_field].field_asteroid_type.push_back(asteroids[i].first.GetString()); + } + } +} diff --git a/fred2/asteroideditordlg.h b/fred2/asteroideditordlg.h index 28347475c46..027d3b27b8a 100644 --- a/fred2/asteroideditordlg.h +++ b/fred2/asteroideditordlg.h @@ -80,7 +80,7 @@ class asteroid_editor : public CDialog afx_msg void OnClose(); afx_msg void OnEnableInnerBox(); afx_msg void OnPassiveField(); - afx_msg void OnFieldShip(); + afx_msg void OnFieldDebris(); afx_msg void OnActiveField(); afx_msg void OnFieldAsteroid(); afx_msg void OnAddField(); @@ -89,6 +89,8 @@ class asteroid_editor : public CDialog afx_msg void OnAddFieldTarget(); afx_msg void OnRemoveFieldTarget(); afx_msg void OnEnableRangeOverride(); + afx_msg void OnBnClickedSelectDebris(); + afx_msg void OnBnClickedSelectAsteroid(); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; diff --git a/fred2/dumpstats.cpp b/fred2/dumpstats.cpp index aad4d5fe852..e28ba566d25 100644 --- a/fred2/dumpstats.cpp +++ b/fred2/dumpstats.cpp @@ -243,7 +243,7 @@ void DumpStats::get_background_stats(CString &buffer) buffer += temp; temp.Format("\t\tTypes: "); - for (size_t j = 0; j < MAX_ACTIVE_DEBRIS_TYPES; j++) { + for (size_t j = 0; j < Asteroid_field.field_debris_type.size(); j++) { temp += CString(Asteroid_info[Asteroid_field.field_debris_type[j]].name) + ", "; } diff --git a/fred2/fred.rc b/fred2/fred.rc index 079c318f3dc..86b052e9396 100644 --- a/fred2/fred.rc +++ b/fred2/fred.rc @@ -1668,13 +1668,7 @@ BEGIN CONTROL "Active Field",IDC_ACTIVE_FIELD,"Button",BS_AUTORADIOBUTTON,33,42,53,10 CONTROL "Passive Field",IDC_PASSIVE_FIELD,"Button",BS_AUTORADIOBUTTON,33,54,57,10 CONTROL "Asteroid",IDC_FIELD_ASTEROID,"Button",BS_AUTORADIOBUTTON,55,81,41,10 - CONTROL "Debris",IDC_FIELD_DEBRIS,"Button",BS_AUTORADIOBUTTON,55,92,30,10 - CONTROL "Brown",IDC_SUBTYPE1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,39,109,36,10 - COMBOBOX IDC_SHIP_DEBRIS1,92,107,74,95,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - CONTROL "Blue",IDC_SUBTYPE2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,39,124,30,10 - COMBOBOX IDC_SHIP_DEBRIS2,92,122,74,95,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - CONTROL "Orange",IDC_SUBTYPE3,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,39,139,39,10 - COMBOBOX IDC_SHIP_DEBRIS3,92,137,74,95,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "Debris",IDC_FIELD_DEBRIS,"Button",BS_AUTORADIOBUTTON,55,92,40,10 EDITTEXT IDC_MIN_X,230,57,40,14,ES_AUTOHSCROLL EDITTEXT IDC_MAX_X,230,73,40,14,ES_AUTOHSCROLL EDITTEXT IDC_MIN_Y,230,89,40,14,ES_AUTOHSCROLL @@ -1720,8 +1714,10 @@ BEGIN LISTBOX IDC_FIELD_TARGET_LIST,385,70,151,60,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP PUSHBUTTON "Add",IDC_ADD_FIELD_TARGET,385,136,41,14 PUSHBUTTON "Remove",IDC_REMOVE_FIELD_TARGET,430,136,40,14 - CONTROL "Use enhanced spawn checking",IDC_RANGE_OVERRIDE, "Button",BS_AUTOCHECKBOX | WS_TABSTOP,193,163,130,10 + CONTROL "Use enhanced spawn checking",IDC_RANGE_OVERRIDE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,193,163,130,10 LTEXT "(By default player range is checked in addition to the player view cone for spawning asteroids. In small fields you may want to override this behavior.)",IDC_STATIC,192,176,155,26 + PUSHBUTTON "Select Debris Types",IDC_SELECT_DEBRIS,98,114,75,28 + PUSHBUTTON "Select Asteroid Types",IDC_SELECT_ASTEROID,18,114,75,28 END IDD_CAMPAIGN DIALOGEX 0, 0, 270, 441 @@ -3386,7 +3382,6 @@ BEGIN 0 END - IDD_LOADOUT_EDITOR AFX_DIALOG_LAYOUT BEGIN 0 diff --git a/fred2/missionsave.cpp b/fred2/missionsave.cpp index dd82486990e..823301bf355 100644 --- a/fred2/missionsave.cpp +++ b/fred2/missionsave.cpp @@ -762,16 +762,18 @@ int CFred_mission_save::save_asteroid_fields() // field_debris_type (only if debris genre) if (Asteroid_field.debris_genre == DG_DEBRIS) { - for (int idx = 0; idx < MAX_ACTIVE_DEBRIS_TYPES; idx++) { + for (size_t idx = 0; idx < Asteroid_field.field_debris_type.size(); idx++) { if (Asteroid_field.field_debris_type[idx] != -1) { if (Mission_save_format == FSO_FORMAT_RETAIL) { - if (optional_string_fred("+Field Debris Type:")) { - parse_comments(); - } else { - fout("\n+Field Debris Type:"); + if (idx < MAX_RETAIL_DEBRIS_TYPES) { // Retail can only have 3! + if (optional_string_fred("+Field Debris Type:")) { + parse_comments(); + } else { + fout("\n+Field Debris Type:"); + } + fout(" %d", Asteroid_field.field_debris_type[idx]); } - fout(" %d", Asteroid_field.field_debris_type[idx]); } else { if (optional_string_fred("+Field Debris Type Name:")) { parse_comments(); @@ -783,25 +785,24 @@ int CFred_mission_save::save_asteroid_fields() } } } else { - // asteroid subtypes stored in field_debris_type as -1 or 1 - for (int idx = 0; idx < NUM_ASTEROID_SIZES; idx++) { - if (Asteroid_field.field_asteroid_type[idx] != false) { + for (size_t idx = 0; idx < Asteroid_field.field_asteroid_type.size(); idx++) { - if (Mission_save_format == FSO_FORMAT_RETAIL) { + if (Mission_save_format == FSO_FORMAT_RETAIL) { + if (idx < MAX_RETAIL_DEBRIS_TYPES) { // Retail can only have 3! if (optional_string_fred("+Field Debris Type:")) { parse_comments(); } else { fout("\n+Field Debris Type:"); } fout(" %d", idx); + } + } else { + if (optional_string_fred("+Field Debris Type Name:")) { + parse_comments(); } else { - if (optional_string_fred("+Field Debris Type Name:")) { - parse_comments(); - } else { - fout("\n+Field Debris Type Name:"); - } - fout(" %s", Asteroid_info[idx].name); + fout("\n+Field Debris Type Name:"); } + fout(" %s", Asteroid_field.field_asteroid_type[idx].c_str()); } } } diff --git a/fred2/resource.h b/fred2/resource.h index 1926f2b2622..8d89fb82aed 100644 --- a/fred2/resource.h +++ b/fred2/resource.h @@ -1258,6 +1258,8 @@ #define IDC_NOISE_RESOLUTION 1702 #define IDC_SPIN_NOISE_RESOLUTION 1703 #define IDC_REQUIRED_WEAPONS 1704 +#define IDC_SELECT_DEBRIS 1705 +#define IDC_SELECT_ASTEROID 1706 #define IDC_SEXP_POPUP_LIST 32770 #define ID_FILE_MISSIONNOTES 32771 #define ID_DUPLICATE 32774 diff --git a/fred2/sexp_tree.cpp b/fred2/sexp_tree.cpp index 0f67407fc1c..dadb57126db 100644 --- a/fred2/sexp_tree.cpp +++ b/fred2/sexp_tree.cpp @@ -3657,7 +3657,7 @@ int sexp_tree::query_default_argument_available(int op, int i) return 0; case OPF_ASTEROID_DEBRIS: - if ((Asteroid_info.size() - NUM_ASTEROID_POFS) > 0) { + if ((Asteroid_info.size() - NUM_ASTEROID_SIZES) > 0) { return 1; } return 0; diff --git a/qtfred/src/mission/dialogs/AsteroidEditorDialogModel.cpp b/qtfred/src/mission/dialogs/AsteroidEditorDialogModel.cpp index 6c109bd3728..74a86c03521 100644 --- a/qtfred/src/mission/dialogs/AsteroidEditorDialogModel.cpp +++ b/qtfred/src/mission/dialogs/AsteroidEditorDialogModel.cpp @@ -34,7 +34,7 @@ AsteroidEditorDialogModel::AsteroidEditorDialogModel(QObject* parent, EditorView debris_inverse_idx_lookup.emplace(ship_debris_idx_lookup[i], i); } // note that normal asteroids use the same index field! Need to add dummy entries for them as well - for (auto i = 0; i < MAX_ACTIVE_DEBRIS_TYPES; ++i) { + for (auto i = 0; i < NUM_ASTEROID_SIZES; ++i) { debris_inverse_idx_lookup.emplace(i, 0); } initializeData(); @@ -96,14 +96,52 @@ bool AsteroidEditorDialogModel::getEnhancedEnabled() void AsteroidEditorDialogModel::setAsteroidEnabled(_roid_types type, bool enabled) { - Assertion(type >=0 && type < MAX_ACTIVE_DEBRIS_TYPES, "Invalid Asteroid checkbox type: %i\n", type); - modify(_field_debris_type[type], enabled == true ? 1 : -1); + Assertion(type >=0 && type < NUM_ASTEROID_SIZES, "Invalid Asteroid checkbox type: %i\n", type); + + SCP_string name = "Brown"; + if (type == _AST_BLUE) { + name = "Blue"; + } else if (type == _AST_ORANGE) { + name = "Orange"; + } + + bool in_list = false; + for (const auto& asteroid : _field_asteroid_type) { + if (name == asteroid) { + in_list = true; + } + } + + // If enabling and it's not enabled then add it + if (enabled && !in_list) { + _field_asteroid_type.push_back(name); + } + + // If disabling and it's in the lsit then remove it + if (!enabled && in_list) { + _field_asteroid_type.erase(std::remove(_field_asteroid_type.begin(), _field_asteroid_type.end(), name), _field_asteroid_type.end()); + } } bool AsteroidEditorDialogModel::getAsteroidEnabled(_roid_types type) { - Assertion(type >=0 && type < MAX_ACTIVE_DEBRIS_TYPES, "Invalid Asteroid checkbox type: %i\n", type); - return (_field_debris_type[type] == 1); + Assertion(type >=0 && type < NUM_ASTEROID_SIZES, "Invalid Asteroid checkbox type: %i\n", type); + + SCP_string name = "Brown"; + if (type == _AST_BLUE) { + name = "Blue"; + } else if (type == _AST_ORANGE) { + name = "Orange"; + } + + bool enabled = false; + for (auto asteroid : _field_asteroid_type) { + if (name == asteroid) { + enabled = true; + } + } + + return (enabled); } void AsteroidEditorDialogModel::setNumAsteroids(int num_asteroids) @@ -180,14 +218,20 @@ field_type_t AsteroidEditorDialogModel::getFieldType() void AsteroidEditorDialogModel::setFieldDebrisType(int idx, int debris_type) { - Assertion(idx >= 0 && idx < MAX_ACTIVE_DEBRIS_TYPES, "Invalid debris index provided: %i\n", idx); - modify(_field_debris_type[idx], ship_debris_idx_lookup.at(debris_type)); + if (!SCP_vector_inbounds(_field_debris_type, idx)) { + _field_debris_type.push_back(ship_debris_idx_lookup.at(debris_type)); + } else { + modify(_field_debris_type[idx], ship_debris_idx_lookup.at(debris_type)); + } } int AsteroidEditorDialogModel::getFieldDebrisType(int idx) { - Assertion(idx >= 0 && idx < MAX_ACTIVE_DEBRIS_TYPES, "Invalid debris index provided: %i\n", idx); - return debris_inverse_idx_lookup.at(_field_debris_type[idx]); + if (!SCP_vector_inbounds(_field_debris_type, idx)) { + return 0; + } else { + return debris_inverse_idx_lookup.at(_field_debris_type[idx]); + } } void AsteroidEditorDialogModel::setAvgSpeed(int speed) @@ -321,12 +365,18 @@ bool AsteroidEditorDialogModel::validate_data() } } + // Compress the debris field vector + if (_a_field.field_debris_type.size() > 0) { + _a_field.field_debris_type.erase(std::remove_if(_a_field.field_debris_type.begin(), + _a_field.field_debris_type.end(), + [](int value) { return value < 0; }), + _a_field.field_debris_type.end()); + } + // for a ship debris (i.e. passive) field, need at least one debris type is selected if (_a_field.field_type == FT_PASSIVE) { if (_a_field.debris_genre == DG_DEBRIS) { - if ( (_a_field.field_debris_type[0] == -1) && \ - (_a_field.field_debris_type[1] == -1) && \ - (_a_field.field_debris_type[2] == -1) ) { + if (_a_field.field_debris_type.size() == 0) { showErrorDialogNoCancel("You must choose one or more types of ship debris\n"); return false; } @@ -335,9 +385,7 @@ bool AsteroidEditorDialogModel::validate_data() // check at least one asteroid subtype is selected if (_a_field.debris_genre == DG_ASTEROID) { - if ( (!_a_field.field_asteroid_type[_AST_BROWN]) && \ - (!_a_field.field_asteroid_type[_AST_BLUE]) && \ - (!_a_field.field_asteroid_type[_AST_ORANGE]) ) { + if (_a_field.field_asteroid_type.size() == 0) { showErrorDialogNoCancel("You must choose one or more asteroid subtypes\n"); return false; } @@ -384,18 +432,26 @@ void AsteroidEditorDialogModel::update_init() modify(_a_field.field_type, _field_type); modify(_a_field.debris_genre, _debris_genre); - // ship debris + // debris if ( (_field_type == FT_PASSIVE) && (_debris_genre == DG_DEBRIS) ) { - for (auto idx=0; idx _field_debris_type; // debris + SCP_vector _field_asteroid_type; // asteroid types field_type_t _field_type; // active or passive debris_genre_t _debris_genre; // ship or asteroid asteroid_field _a_field; // :v: had unfinished plans for multiple fields? diff --git a/qtfred/src/mission/missionsave.cpp b/qtfred/src/mission/missionsave.cpp index aa110e1ecb5..e7a06a2bbad 100644 --- a/qtfred/src/mission/missionsave.cpp +++ b/qtfred/src/mission/missionsave.cpp @@ -777,16 +777,18 @@ int CFred_mission_save::save_asteroid_fields() // field_debris_type (only if debris genre) if (Asteroid_field.debris_genre == DG_DEBRIS) { - for (int idx = 0; idx < MAX_ACTIVE_DEBRIS_TYPES; idx++) { + for (size_t idx = 0; idx < Asteroid_field.field_debris_type.size(); idx++) { if (Asteroid_field.field_debris_type[idx] != -1) { - if (save_format != MissionFormat::RETAIL) { - if (optional_string_fred("+Field Debris Type:")) { - parse_comments(); - } else { - fout("\n+Field Debris Type:"); + if (save_format == MissionFormat::RETAIL) { + if (idx < MAX_RETAIL_DEBRIS_TYPES) { // Retail can only have 3! + if (optional_string_fred("+Field Debris Type:")) { + parse_comments(); + } else { + fout("\n+Field Debris Type:"); + } + fout(" %d", Asteroid_field.field_debris_type[idx]); } - fout(" %d", Asteroid_field.field_debris_type[idx]); } else { if (optional_string_fred("+Field Debris Type Name:")) { parse_comments(); @@ -798,25 +800,24 @@ int CFred_mission_save::save_asteroid_fields() } } } else { - // asteroid subtypes stored in field_debris_type as -1 or 1 - for (int idx = 0; idx < NUM_ASTEROID_SIZES; idx++) { - if (Asteroid_field.field_asteroid_type[idx] != false) { + for (size_t idx = 0; idx < Asteroid_field.field_asteroid_type.size(); idx++) { - if (save_format != MissionFormat::RETAIL) { + if (save_format == MissionFormat::RETAIL) { + if (idx < MAX_RETAIL_DEBRIS_TYPES) { // Retail can only have 3! if (optional_string_fred("+Field Debris Type:")) { parse_comments(); } else { fout("\n+Field Debris Type:"); } fout(" %d", idx); + } + } else { + if (optional_string_fred("+Field Debris Type Name:")) { + parse_comments(); } else { - if (optional_string_fred("+Field Debris Type Name:")) { - parse_comments(); - } else { - fout("\n+Field Debris Type Name:"); - } - fout(" %s", Asteroid_info[idx].name); + fout("\n+Field Debris Type Name:"); } + fout(" %s", Asteroid_field.field_asteroid_type[idx].c_str()); } } } diff --git a/qtfred/src/ui/dialogs/AsteroidEditorDialog.cpp b/qtfred/src/ui/dialogs/AsteroidEditorDialog.cpp index 85583875cd6..10a3b4eef7c 100644 --- a/qtfred/src/ui/dialogs/AsteroidEditorDialog.cpp +++ b/qtfred/src/ui/dialogs/AsteroidEditorDialog.cpp @@ -50,8 +50,6 @@ AsteroidEditorDialog::AsteroidEditorDialog(FredView *parent, EditorViewport* vie // setup values in ship debris combo boxes // MFC let you set comboxbox item indexes, Qt doesn't so we'll need a lookup - static_assert(MAX_ACTIVE_DEBRIS_TYPES == 3, - "qtFRED only provides three combo boxes for debris type input"); debrisComboBoxes = ui->fieldProperties->findChildren(QString(), Qt::FindDirectChildrenOnly); std::sort(debrisComboBoxes.begin(), debrisComboBoxes.end(), sort_qcombobox_by_name); @@ -65,7 +63,8 @@ AsteroidEditorDialog::AsteroidEditorDialog(FredView *parent, EditorViewport* vie } } - for (auto i = 0; i < MAX_ACTIVE_DEBRIS_TYPES; ++i) { + // There are only 3 combo boxes.. FOR NOW + for (auto i = 0; i < 3; ++i) { debrisComboBoxes.at(i)->addItems(debris_names); // update debris combobox data on index changes connect(debrisComboBoxes.at(i), QOverload::of(&QComboBox::currentIndexChanged), this, \ diff --git a/qtfred/src/ui/widgets/sexp_tree.cpp b/qtfred/src/ui/widgets/sexp_tree.cpp index d1c6c092253..32680a74039 100644 --- a/qtfred/src/ui/widgets/sexp_tree.cpp +++ b/qtfred/src/ui/widgets/sexp_tree.cpp @@ -1667,7 +1667,7 @@ int sexp_tree::query_default_argument_available(int op, int i) { return 0; case OPF_ASTEROID_DEBRIS: - if ((Asteroid_info.size() - NUM_ASTEROID_POFS) > 0) { + if ((Asteroid_info.size() - NUM_ASTEROID_SIZES) > 0) { return 1; } return 0; From 559a2616f36b7e23c5f18d839ab7f31a31ed95e9 Mon Sep 17 00:00:00 2001 From: BMagnu <6238428+BMagnu@users.noreply.github.com> Date: Tue, 11 Jun 2024 20:41:19 +0900 Subject: [PATCH 51/63] Fix edge case in MSAA shader (#6186) --- code/def_files/data/effects/msaa-f.sdr | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/code/def_files/data/effects/msaa-f.sdr b/code/def_files/data/effects/msaa-f.sdr index d802a819b1a..b4565968be4 100644 --- a/code/def_files/data/effects/msaa-f.sdr +++ b/code/def_files/data/effects/msaa-f.sdr @@ -174,7 +174,8 @@ void main() for(int i = 0; i < samples; i++) { vec4 localPos = texelFetch(texPos, texel, i); //Calculate local weight from distance Voxel, but if the distance is 0 (i.e. no model at all), set weight to 1 to allow stuff like background emissive - float localWeight = max(step(-0.001, dist), + //However, if the median distance is 0, only deal with current texel if it's local distance is 0 as well + float localWeight = max(step(-0.001, dist) * step(-0.001, localPos.z), smoothstep(dist + dist * texelWidthFactor * (voxelDepth + voxelDepthFalloff), dist + dist * texelWidthFactor * voxelDepth, localPos.z) * smoothstep(dist - dist * texelWidthFactor * voxelDepth, dist + dist * texelWidthFactor * (voxelDepth + voxelDepthFalloff), localPos.z) ); @@ -190,7 +191,7 @@ void main() fragOut0 = color / weight; fragOut1 = pos / weight; - fragOut2 = normalize(normal); + fragOut2 = vec4(normalize(normal.xyz), normal.a / weight); fragOut3 = specular / weight; fragOut4 = emissive / weight; gl_FragDepth = depth / weight; From e1f0042e99bc0e18a70bef75a799db1419c0e7d2 Mon Sep 17 00:00:00 2001 From: mjn-mixael Date: Tue, 11 Jun 2024 09:25:00 -0500 Subject: [PATCH 52/63] prevent printing a warning every frame --- code/menuui/mainhallmenu.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/code/menuui/mainhallmenu.cpp b/code/menuui/mainhallmenu.cpp index 47cd7d3c51a..f50a3683796 100644 --- a/code/menuui/mainhallmenu.cpp +++ b/code/menuui/mainhallmenu.cpp @@ -475,6 +475,13 @@ void main_hall_init(const SCP_string &main_hall_name) return; } + extern bool Campaign_room_no_campaigns; + + // Log if we don't have a campaign set yet. + if (!(Player->flags & PLAYER_FLAGS_IS_MULTI) && Campaign_file_missing && !Campaign_room_no_campaigns) { + mprintf(("No valid campaign is currently selected for the active player!\n")); + } + // gameseq_post_event(GS_EVENT_SCRIPTING); int idx; @@ -1096,11 +1103,6 @@ void main_hall_do(float frametime) gr_flip(); gr_reset_screen_scale(); - // Log if we don't have a campaign set yet. - if (!(Player->flags & PLAYER_FLAGS_IS_MULTI) && Campaign_file_missing && !Campaign_room_no_campaigns) { - mprintf(("No valid campaign is currently selected for the active player!\n")); - } - // Display a popup if playermenu loaded a player file with a different version than expected bool popup_shown = player_tips_controls(); From f733414403753bcecea58bbabe65513851c5c7b1 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Wed, 12 Jun 2024 18:17:47 -0400 Subject: [PATCH 53/63] make mission log dynamic (#6111) * make mission log dynamic Change the mission log from a static array to a dynamic vector. This removes any chance of the mission log running into a limit (as regularly happened with the last mission of Shepherds before the limit bump) and also prevents strange mission bugs caused by culling obsolete entries. This also unifies the API a bit by renaming the public `message_log_` function prefixes to `mission_log_`, removing the redundant `Num_log_lines` variable, and removing `Log_scrollback_vec` from the header. Implements #5878. * feedback * refactor to reference rather than pointer --- code/hud/hudmessage.cpp | 12 +- code/mission/missionlog.cpp | 438 ++++++++----------------- code/mission/missionlog.h | 18 +- code/scripting/api/libs/ui.cpp | 12 +- code/scripting/api/objs/missionlog.cpp | 8 +- code/scripting/api/objs/missionlog.h | 4 +- 6 files changed, 165 insertions(+), 327 deletions(-) diff --git a/code/hud/hudmessage.cpp b/code/hud/hudmessage.cpp index 4acb5d2a666..8bed5a9d39b 100644 --- a/code/hud/hudmessage.cpp +++ b/code/hud/hudmessage.cpp @@ -812,7 +812,7 @@ void hud_scrollback_button_pressed(int n) case SHOW_EVENTS_BUTTON: Scrollback_mode = SCROLLBACK_MODE_EVENT_LOG; - Scroll_max = Num_log_lines * gr_get_font_height(); + Scroll_max = mission_log_scrollback_num_lines() * gr_get_font_height(); hud_scroll_reset(); break; @@ -913,9 +913,9 @@ void hud_scrollback_init() Background_bitmap = bm_load(Hud_mission_log_fname[gr_screen.res]); // Status_bitmap = bm_load(Hud_mission_log_status_fname[gr_screen.res]); - message_log_init_scrollback(Hud_mission_log_list_coords[gr_screen.res][2]); + mission_log_init_scrollback(Hud_mission_log_list_coords[gr_screen.res][2]); if (Scrollback_mode == SCROLLBACK_MODE_EVENT_LOG) - Scroll_max = Num_log_lines * gr_get_font_height(); + Scroll_max = mission_log_scrollback_num_lines() * gr_get_font_height(); else if (Scrollback_mode == SCROLLBACK_MODE_OBJECTIVES) Scroll_max = Num_obj_lines * gr_get_font_height(); else @@ -928,7 +928,7 @@ void hud_scrollback_init() void hud_scrollback_close() { ML_objectives_close(); - message_log_shutdown_scrollback(); + mission_log_shutdown_scrollback(); if (Background_bitmap >= 0) bm_release(Background_bitmap); //if (Status_bitmap >= 0) @@ -960,7 +960,7 @@ void hud_scrollback_do_frame(float /*frametime*/) } else if (Scrollback_mode == SCROLLBACK_MODE_MSGS_LOG) { Scrollback_mode = SCROLLBACK_MODE_EVENT_LOG; - Scroll_max = Num_log_lines * gr_get_font_height(); + Scroll_max = mission_log_scrollback_num_lines() * gr_get_font_height(); hud_scroll_reset(); } else { @@ -975,7 +975,7 @@ void hud_scrollback_do_frame(float /*frametime*/) case KEY_SHIFTED | KEY_TAB: if (Scrollback_mode == SCROLLBACK_MODE_OBJECTIVES) { Scrollback_mode = SCROLLBACK_MODE_EVENT_LOG; - Scroll_max = Num_log_lines * gr_get_font_height(); + Scroll_max = mission_log_scrollback_num_lines() * gr_get_font_height(); hud_scroll_reset(); } else if (Scrollback_mode == SCROLLBACK_MODE_MSGS_LOG) { diff --git a/code/mission/missionlog.cpp b/code/mission/missionlog.cpp index 7a97c1abe85..e0a006786b7 100644 --- a/code/mission/missionlog.cpp +++ b/code/mission/missionlog.cpp @@ -7,9 +7,6 @@ * */ - - - #include "globalincs/alphacolors.h" #include "graphics/font.h" #include "iff_defs/iff_defs.h" @@ -25,15 +22,6 @@ #include "ship/ship.h" - -#define MAX_LOG_ENTRIES 1500 - -// used for high water mark for culling out log entries -#define LOG_CULL_MARK ((int)(MAX_LOG_ENTRIES * 0.95f)) -#define LOG_CULL_DOORDIE_MARK ((int)(MAX_LOG_ENTRIES * 0.99f)) -#define LOG_LAST_DITCH_CULL_NUM ((int)(MAX_LOG_ENTRIES * 0.20f)) -#define LOG_HALFWAY_REPORT_NUM ((int)(MAX_LOG_ENTRIES * 0.50f)) - #define EMPTY_LOG_NAME "" // defines for X position offsets of different items for mission log @@ -46,112 +34,12 @@ static int X, P_width; SCP_vector Log_scrollback_vec; -int Num_log_lines; - -std::array log_entries; // static array because John says.... -int last_entry; +SCP_vector Log_entries; void mission_log_init() { - last_entry = 0; - // zero out all the memory so we don't get bogus information when playing across missions! - log_entries.fill({}); -} - -// function to clean up the mission log removing obsolete entries. Entries might get marked obsolete -// in several ways -- having to recycle entries, a ship's subsystem destroyed entries when a ship is -// fully destroyed, etc. -void mission_log_cull_obsolete_entries() -{ - int i, index; - - nprintf(("missionlog", "culling obsolete entries. starting last entry %d.\n", last_entry)); - // find the first obsolete entry - for (i = 0; i < last_entry; i++ ) - if ( log_entries[i].flags & MLF_OBSOLETE ) - break; - - // nothing to do if next if statement is true - if ( i == last_entry ) - return; - - // compact the log array, removing the obsolete entries. - index = i; // index is the first obsolete entry - - // 'index' should always point to the next element in the list - // which is getting compacted. 'i' points to the next array - // element to be replaced. - do { - // get to the next non-obsolete entry. The obsolete entry must not be essential either! - while ( (log_entries[index].flags & MLF_OBSOLETE) && !(log_entries[index].flags & MLF_ESSENTIAL) ) { - index++; - last_entry--; - } - - log_entries[i++] = log_entries[index++]; - } while ( i < last_entry ); - -#ifndef NDEBUG - nprintf(("missionlog", "Ending entry: %d.\n", last_entry)); -#endif -} - -// function to mark entries as obsolete. Passed is the type of entry that is getting added -// to the log. Some entries might get marked obsolete as a result of this type -void mission_log_obsolete_entries(LogType type, const char *pname) -{ - int i; - log_entry *entry = NULL; - - // before adding this entry, check to see if the entry type is a ship destroyed or destructed entry. - // If so, we can remove any subsystem destroyed entries from the log for this ship. - if ( type == LOG_SHIP_DESTROYED || type == LOG_SELF_DESTRUCTED ) { - for (i = 0; i < last_entry; i++) { - entry = &log_entries[i]; - - // check to see if the type is a subsystem destroyed entry, and that it belongs to the - // ship passed into this routine. If it matches, mark as obsolete. We'll clean up - // the log when it starts to get full - if ( !stricmp( pname, entry->pname ) ) { - if ( (entry->type == LOG_SHIP_SUBSYS_DESTROYED) || (entry->type == LOG_SHIP_DISARMED) || (entry->type == LOG_SHIP_DISABLED) ) - entry->flags |= MLF_OBSOLETE; - } - } - } - - // check to see if we are getting to about 80% of our log capacity. If so, cull the log. - if ( last_entry > LOG_CULL_MARK ) { - mission_log_cull_obsolete_entries(); - - // if we culled the entries, and we are still low on space, we need to take more drastic measures. - // these include removing all non-essential entries from the log. These entries are entries - // which has not been asked for by mission_log_get_time - if ( last_entry > LOG_CULL_MARK ) { - nprintf(("missionlog", "marking the first %d non-essential log entries as obsolete\n", LOG_LAST_DITCH_CULL_NUM)); - for (i = 0; i < LOG_LAST_DITCH_CULL_NUM; i++ ) { - entry = &log_entries[i]; - if ( !(entry->flags & MLF_ESSENTIAL) ){ - entry->flags |= MLF_OBSOLETE; - } - } - - // cull the obsolete entries again - mission_log_cull_obsolete_entries(); - - // if we get to this point, and there are no entries left -- we are in big trouble. We will simply - // mark the first 20% of the log as obsolete and compress. Don't do this unless we are *really* - // in trouble - if ( last_entry > LOG_CULL_DOORDIE_MARK ) { - nprintf(("missionlog", "removing the first %d entries in the mission log!!!!\n", LOG_LAST_DITCH_CULL_NUM)); - for (i = 0; i < LOG_LAST_DITCH_CULL_NUM; i++ ){ - entry->flags |= MLF_OBSOLETE; - } - - mission_log_cull_obsolete_entries(); - } - } - } + Log_entries.clear(); } // following function adds an entry into the mission log. @@ -159,49 +47,37 @@ void mission_log_obsolete_entries(LogType type, const char *pname) // that this event is for. Don't add entries with this function for multiplayer void mission_log_add_entry(LogType type, const char *pname, const char *sname, int info_index, int flags) { - __UNUSED int last_entry_save; - log_entry *entry; - // multiplayer clients don't use this function to add log entries -- they will get // all their info from the host if ( MULTIPLAYER_CLIENT ){ return; } - last_entry_save = last_entry; - - // mark any entries as obsolete. Part of the pruning is done based on the type (and name) passed - // for a new entry - mission_log_obsolete_entries(type, pname); - - entry = &log_entries[last_entry]; + Log_entries.emplace_back(); + auto &entry = Log_entries.back(); - if ( last_entry == MAX_LOG_ENTRIES ){ - return; - } - - entry->type = type; + entry.type = type; if ( pname ) { Assert (strlen(pname) < NAME_LENGTH); - strncpy_s(entry->pname, pname, NAME_LENGTH - 1); + strncpy_s(entry.pname, pname, NAME_LENGTH - 1); } else - strcpy_s( entry->pname, EMPTY_LOG_NAME ); + strcpy_s( entry.pname, EMPTY_LOG_NAME ); if ( sname ) { Assert (strlen(sname) < NAME_LENGTH); - strncpy_s(entry->sname, sname, NAME_LENGTH - 1); + strncpy_s(entry.sname, sname, NAME_LENGTH - 1); } else - strcpy_s( entry->sname, EMPTY_LOG_NAME ); + strcpy_s( entry.sname, EMPTY_LOG_NAME ); - entry->index = info_index; - entry->flags = flags; - entry->primary_team = -1; - entry->secondary_team = -1; - entry->pname_display = entry->pname; - entry->sname_display = entry->sname; + entry.index = info_index; + entry.flags = flags; + entry.primary_team = -1; + entry.secondary_team = -1; + entry.pname_display = entry.pname; + entry.sname_display = entry.sname; - end_string_at_first_hash_symbol(entry->pname_display); - end_string_at_first_hash_symbol(entry->sname_display); + end_string_at_first_hash_symbol(entry.pname_display); + end_string_at_first_hash_symbol(entry.sname_display); // determine the contents of the flags member based on the type of entry we added. We need to store things // like team for the primary and (possibly) secondary object for this entry. @@ -226,11 +102,11 @@ void mission_log_add_entry(LogType type, const char *pname, const char *sname, i } Assert (index >= 0); - entry->primary_team = Ships[index].team; - entry->pname_display = Ships[index].get_display_name(); + entry.primary_team = Ships[index].team; + entry.pname_display = Ships[index].get_display_name(); if (Ships[index].flags[Ship::Ship_Flags::Hide_mission_log]) { - entry->flags |= MLF_HIDDEN; + entry.flags |= MLF_HIDDEN; } // some of the entries have a secondary component. Figure out what is up with them. @@ -238,8 +114,8 @@ void mission_log_add_entry(LogType type, const char *pname, const char *sname, i if ( sname ) { index = ship_name_lookup( sname ); Assert (index >= 0); - entry->secondary_team = Ships[index].team; - entry->sname_display = Ships[index].get_display_name(); + entry.secondary_team = Ships[index].team; + entry.sname_display = Ships[index].get_display_name(); } } else if ( type == LOG_SHIP_DESTROYED ) { if ( sname ) { @@ -253,12 +129,12 @@ void mission_log_add_entry(LogType type, const char *pname, const char *sname, i { // argh. badness Int3(); - entry->secondary_team = Player_ship->team; + entry.secondary_team = Player_ship->team; } else { - entry->secondary_team = Ships[Objects[Net_players[np_index].m_player->objnum].instance].team; - entry->sname_display = Ships[Objects[Net_players[np_index].m_player->objnum].instance].get_display_name(); + entry.secondary_team = Ships[Objects[Net_players[np_index].m_player->objnum].instance].team; + entry.sname_display = Ships[Objects[Net_players[np_index].m_player->objnum].instance].get_display_name(); } } else @@ -270,11 +146,11 @@ void mission_log_add_entry(LogType type, const char *pname, const char *sname, i if ( index == -1 ) { break; } - entry->secondary_team = Ships_exited[index].team; - entry->sname_display = Ships_exited[index].display_name; + entry.secondary_team = Ships_exited[index].team; + entry.sname_display = Ships_exited[index].display_name; } else { - entry->secondary_team = Ships[index].team; - entry->sname_display = Ships[index].get_display_name(); + entry.secondary_team = Ships[index].team; + entry.sname_display = Ships[index].get_display_name(); } } } else { @@ -282,10 +158,10 @@ void mission_log_add_entry(LogType type, const char *pname, const char *sname, i } } else if ( (type == LOG_SHIP_SUBSYS_DESTROYED) && (Ship_info[Ships[index].ship_info_index].is_small_ship()) ) { // make subsystem destroyed entries for small ships hidden - entry->flags |= MLF_HIDDEN; + entry.flags |= MLF_HIDDEN; } else if ( (type == LOG_SHIP_ARRIVED) && (Ships[index].wingnum != -1 ) ) { // arrival of ships in wings don't display - entry->flags |= MLF_HIDDEN; + entry.flags |= MLF_HIDDEN; } break; @@ -312,9 +188,9 @@ void mission_log_add_entry(LogType type, const char *pname, const char *sname, i } } Assert( si != -1 ); - entry->primary_team = Ships[si].team; + entry.primary_team = Ships[si].team; } else { - entry->primary_team = info_index; + entry.primary_team = info_index; } #ifndef NDEBUG @@ -322,112 +198,95 @@ void mission_log_add_entry(LogType type, const char *pname, const char *sname, i // scan through all log entries and find at least one ship_depart entry for a ship // that was in this wing. if ( type == LOG_WING_DEPARTED ) { - int i; - // if all were destroyed, then don't do this debug code. if ( (Wings[index].total_destroyed + Wings[index].total_vanished) == Wings[index].total_arrived_count ){ break; } - for ( i = 0; i < last_entry; i++ ) { - if ( log_entries[i].type != LOG_SHIP_DEPARTED ){ - continue; - } - if( log_entries[i].index == index ){ - break; - } - } - if ( i == last_entry ){ - Int3(); // get Allender -- cannot find any departed ships from wing that supposedly departed. + if (std::find_if(Log_entries.begin(), Log_entries.end(), [index](const log_entry& le) { + return (le.type == LOG_SHIP_DEPARTED) && (le.index == index); + }) == Log_entries.end()) { + UNREACHABLE("cannot find any departed ships from wing %s that supposedly departed.", Wings[index].name); // get Allender } } #endif - break; // don't display waypoint done entries case LOG_WAYPOINTS_DONE: - entry->flags |= MLF_HIDDEN; + entry.flags |= MLF_HIDDEN; break; default: break; } - entry->timestamp = Missiontime; - entry->timer_padding = The_mission.HUD_timer_padding; + entry.timestamp = Missiontime; + entry.timer_padding = The_mission.HUD_timer_padding; // if in multiplayer and I am the master, send this log entry to everyone if ( MULTIPLAYER_MASTER ){ - send_mission_log_packet( &log_entries[last_entry] ); + send_mission_log_packet( &entry ); } - last_entry++; - #ifndef NDEBUG - if ( !(last_entry % 10) ) { - if ( (last_entry > LOG_HALFWAY_REPORT_NUM) && (last_entry > last_entry_save) ){ - nprintf(("missionlog", "new highwater point reached for mission log (%d entries).\n", last_entry)); - } - } - float mission_time = f2fl(Missiontime); int minutes = (int)(mission_time / 60); int seconds = (int)mission_time % 60; // record the entry to the debug log too - switch (entry->type) { + switch (entry.type) { case LOG_SHIP_DESTROYED: case LOG_WING_DESTROYED: - nprintf(("missionlog", "MISSION LOG: %s destroyed at %02d:%02d\n", entry->pname, minutes, seconds)); + nprintf(("missionlog", "MISSION LOG: %s destroyed at %02d:%02d\n", entry.pname, minutes, seconds)); break; case LOG_SHIP_ARRIVED: case LOG_WING_ARRIVED: - nprintf(("missionlog", "MISSION LOG: %s arrived at %02d:%02d\n", entry->pname, minutes, seconds)); + nprintf(("missionlog", "MISSION LOG: %s arrived at %02d:%02d\n", entry.pname, minutes, seconds)); break; case LOG_SHIP_DEPARTED: case LOG_WING_DEPARTED: - nprintf(("missionlog", "MISSION LOG: %s departed at %02d:%02d\n", entry->pname, minutes, seconds)); + nprintf(("missionlog", "MISSION LOG: %s departed at %02d:%02d\n", entry.pname, minutes, seconds)); break; case LOG_SHIP_DOCKED: case LOG_SHIP_UNDOCKED: - nprintf(("missionlog", "MISSION LOG: %s %sdocked with %s at %02d:%02d\n", entry->pname, entry->type == LOG_SHIP_UNDOCKED ? "un" : "", entry->sname, minutes, seconds)); + nprintf(("missionlog", "MISSION LOG: %s %sdocked with %s at %02d:%02d\n", entry.pname, entry.type == LOG_SHIP_UNDOCKED ? "un" : "", entry.sname, minutes, seconds)); break; case LOG_SHIP_SUBSYS_DESTROYED: - nprintf(("missionlog", "MISSION LOG: %s subsystem %s destroyed at %02d:%02d\n", entry->pname, entry->sname, minutes, seconds)); + nprintf(("missionlog", "MISSION LOG: %s subsystem %s destroyed at %02d:%02d\n", entry.pname, entry.sname, minutes, seconds)); break; case LOG_SHIP_DISABLED: - nprintf(("missionlog", "MISSION LOG: %s disabled at %02d:%02d\n", entry->pname, minutes, seconds)); + nprintf(("missionlog", "MISSION LOG: %s disabled at %02d:%02d\n", entry.pname, minutes, seconds)); break; case LOG_SHIP_DISARMED: - nprintf(("missionlog", "MISSION LOG: %s disarmed at %02d:%02d\n", entry->pname, minutes, seconds)); + nprintf(("missionlog", "MISSION LOG: %s disarmed at %02d:%02d\n", entry.pname, minutes, seconds)); break; case LOG_PLAYER_CALLED_FOR_REARM: - nprintf(("missionlog", "MISSION LOG: Player %s called for rearm at %02d:%02d\n", entry->pname, minutes, seconds)); + nprintf(("missionlog", "MISSION LOG: Player %s called for rearm at %02d:%02d\n", entry.pname, minutes, seconds)); break; case LOG_PLAYER_ABORTED_REARM: - nprintf(("missionlog", "MISSION LOG: Player %s aborted rearm at %02d:%02d\n", entry->pname, minutes, seconds)); + nprintf(("missionlog", "MISSION LOG: Player %s aborted rearm at %02d:%02d\n", entry.pname, minutes, seconds)); break; case LOG_PLAYER_CALLED_FOR_REINFORCEMENT: - nprintf(("missionlog", "MISSION LOG: A player called for reinforcement %s at %02d:%02d\n", entry->pname, minutes, seconds)); + nprintf(("missionlog", "MISSION LOG: A player called for reinforcement %s at %02d:%02d\n", entry.pname, minutes, seconds)); break; case LOG_GOAL_SATISFIED: - nprintf(("missionlog", "MISSION LOG: Goal %s satisfied at %02d:%02d\n", entry->pname, minutes, seconds)); + nprintf(("missionlog", "MISSION LOG: Goal %s satisfied at %02d:%02d\n", entry.pname, minutes, seconds)); break; case LOG_GOAL_FAILED: - nprintf(("missionlog", "MISSION LOG: Goal %s failed at %02d:%02d\n", entry->pname, minutes, seconds)); + nprintf(("missionlog", "MISSION LOG: Goal %s failed at %02d:%02d\n", entry.pname, minutes, seconds)); break; case LOG_WAYPOINTS_DONE: - nprintf(("missionlog", "MISSION LOG: %s completed waypoint path %s at %02d:%02d\n", entry->pname, entry->sname, minutes, seconds)); + nprintf(("missionlog", "MISSION LOG: %s completed waypoint path %s at %02d:%02d\n", entry.pname, entry.sname, minutes, seconds)); break; case LOG_CARGO_REVEALED: - nprintf(("missionlog", "MISSION LOG: %s cargo %s revealed at %02d:%02d\n", entry->pname, Cargo_names[entry->index], minutes, seconds)); + nprintf(("missionlog", "MISSION LOG: %s cargo %s revealed at %02d:%02d\n", entry.pname, Cargo_names[entry.index], minutes, seconds)); break; case LOG_CAP_SUBSYS_CARGO_REVEALED: - nprintf(("missionlog", "MISSION LOG: %s subsystem %s cargo %s revealed at %02d:%02d\n", entry->pname, entry->sname, Cargo_names[entry->index], minutes, seconds)); + nprintf(("missionlog", "MISSION LOG: %s subsystem %s cargo %s revealed at %02d:%02d\n", entry.pname, entry.sname, Cargo_names[entry.index], minutes, seconds)); break; case LOG_SELF_DESTRUCTED: - nprintf(("missionlog", "MISSION LOG: %s self-destructed at %02d:%02d\n", entry->pname, minutes, seconds)); + nprintf(("missionlog", "MISSION LOG: %s self-destructed at %02d:%02d\n", entry.pname, minutes, seconds)); break; } #endif @@ -438,57 +297,42 @@ void mission_log_add_entry(LogType type, const char *pname, const char *sname, i // the normal parameters used for adding an entry to the log void mission_log_add_entry_multi( LogType type, const char *pname, const char *sname, int index, fix timestamp, int flags ) { - log_entry *entry; - // we'd better be in multiplayer and not the master of the game Assert ( Game_mode & GM_MULTIPLAYER ); Assert ( !(Net_player->flags & NETINFO_FLAG_AM_MASTER) ); - // mark any entries as obsolete. Part of the pruning is done based on the type (and name) passed - // for a new entry - mission_log_obsolete_entries(type, pname); - - entry = &log_entries[last_entry]; - - if ( last_entry == MAX_LOG_ENTRIES ){ - return; - } + Log_entries.emplace_back(); + auto &entry = Log_entries.back(); - last_entry++; - - entry->type = type; + entry.type = type; if ( pname ) { Assert (strlen(pname) < NAME_LENGTH); - strcpy_s(entry->pname, pname); + strcpy_s(entry.pname, pname); } if ( sname ) { Assert (strlen(sname) < NAME_LENGTH); - strcpy_s(entry->sname, sname); + strcpy_s(entry.sname, sname); } - entry->index = index; + entry.index = index; - entry->flags = flags; - entry->timestamp = timestamp; - entry->timer_padding = The_mission.HUD_timer_padding; + entry.flags = flags; + entry.timestamp = timestamp; + entry.timer_padding = The_mission.HUD_timer_padding; - entry->pname_display = entry->pname; - entry->sname_display = entry->sname; + entry.pname_display = entry.pname; + entry.sname_display = entry.sname; } // function to determine is the given event has taken place count number of times. int mission_log_get_time_indexed( LogType type, const char *pname, const char *sname, int count, fix *time) { - int i, found; - log_entry *entry; Assertion(count > 0, "The count parameter is %d; it should be greater than 0!", count); - entry = &log_entries[0]; + for (const auto& entry: Log_entries) { + bool found = false; - for (i = 0; i < last_entry; i++, entry++) { - found = 0; - - if ( entry->type == type ) { + if ( entry.type == type ) { // if we are looking for a dock/undock entry, then we don't care about the order in which the names // were passed into this function. Count the entry as found if either name matches both in the other // set. @@ -498,8 +342,8 @@ int mission_log_get_time_indexed( LogType type, const char *pname, const char *s return 0; } - if ( (!stricmp(entry->pname, pname) && !stricmp(entry->sname, sname)) || (!stricmp(entry->pname, sname) && !stricmp(entry->sname, pname)) ) { - found = 1; + if ( (!stricmp(entry.pname, pname) && !stricmp(entry.sname, sname)) || (!stricmp(entry.pname, sname) && !stricmp(entry.sname, pname)) ) { + found = true; } } else { // for non dock/undock goals, then the names are important! @@ -508,18 +352,18 @@ int mission_log_get_time_indexed( LogType type, const char *pname, const char *s return 0; } - if ( stricmp(entry->pname, pname) != 0 ) { + if ( stricmp(entry.pname, pname) != 0 ) { continue; } // if we are looking for a subsystem entry, the subsystem names must be compared if ((type == LOG_SHIP_SUBSYS_DESTROYED || type == LOG_CAP_SUBSYS_CARGO_REVEALED)) { - if ( (sname == NULL) || !subsystem_stricmp(sname, entry->sname) ) { - found = 1; + if ( (sname == NULL) || !subsystem_stricmp(sname, entry.sname) ) { + found = true; } } else { - if ( (sname == NULL) || !stricmp(sname, entry->sname) ) { - found = 1; + if ( (sname == NULL) || !stricmp(sname, entry.sname) ) { + found = true; } } } @@ -528,10 +372,8 @@ int mission_log_get_time_indexed( LogType type, const char *pname, const char *s count--; if ( !count ) { - entry->flags |= MLF_ESSENTIAL; // since the goal code asked for this entry, mark it as essential - if (time) { - *time = entry->timestamp; + *time = entry.timestamp; } return 1; @@ -555,15 +397,10 @@ int mission_log_get_time( LogType type, const char *pname, const char *sname, fi int mission_log_get_count( LogType type, const char *pname, const char *sname ) { - int i; - log_entry *entry; - int count = 0; + int count = 0; - entry = &log_entries[0]; - - for (i = 0; i < last_entry; i++, entry++) { - - if ( entry->type == type ) { + for (const auto& entry : Log_entries) { + if ( entry.type == type ) { // if we are looking for a dock/undock entry, then we don't care about the order in which the names // were passed into this function. Count the entry as found if either name matches both in the other // set. @@ -573,7 +410,7 @@ int mission_log_get_count( LogType type, const char *pname, const char *sname ) return 0; } - if ( (!stricmp(entry->pname, pname) && !stricmp(entry->sname, sname)) || (!stricmp(entry->pname, sname) && !stricmp(entry->sname, pname)) ) { + if ( (!stricmp(entry.pname, pname) && !stricmp(entry.sname, sname)) || (!stricmp(entry.pname, sname) && !stricmp(entry.sname, pname)) ) { count++; } } else { @@ -583,11 +420,11 @@ int mission_log_get_count( LogType type, const char *pname, const char *sname ) return 0; } - if ( stricmp(entry->pname, pname) != 0 ) { + if ( stricmp(entry.pname, pname) != 0 ) { continue; } - if ( (sname == NULL) || !stricmp(sname, entry->sname) ) { + if ( (sname == NULL) || !stricmp(sname, entry.sname) ) { count++; } } @@ -661,35 +498,30 @@ void message_log_add_segs(const char* source_string, int msg_color, int flags = vm_free(dup_string); } -int message_log_color_get_team(int msg_color) +int mission_log_color_get_team(int msg_color) { return msg_color - NUM_LOG_COLORS; } -int message_log_team_get_color(int team) +int mission_log_team_get_color(int team) { return NUM_LOG_COLORS + team; } // pw = total pixel width -void message_log_init_scrollback(int pw, bool split_string) +void mission_log_init_scrollback(int pw, bool split_string) { P_width = pw; - mission_log_cull_obsolete_entries(); // compact array so we don't have gaps Log_scrollback_vec.clear(); - Num_log_lines = 0; - - log_entry* entry; - for (int i=0; iflags & MLF_HIDDEN) + for (const auto& entry : Log_entries) { + if (entry.flags & MLF_HIDDEN) continue; // don't display failed bonus goals - if (entry->type == LOG_GOAL_FAILED) { - int type = Mission_goals[entry->index].type & GOAL_TYPE_MASK; + if (entry.type == LOG_GOAL_FAILED) { + int type = Mission_goals[entry.index].type & GOAL_TYPE_MASK; if (type == BONUS_GOAL) { continue; @@ -699,35 +531,35 @@ void message_log_init_scrollback(int pw, bool split_string) log_line_complete thisEntry; // track time of event (normal Missiontime format) - thisEntry.timestamp = entry->timestamp; - thisEntry.timer_padding = entry->timer_padding; + thisEntry.timestamp = entry.timestamp; + thisEntry.timer_padding = entry.timer_padding; // keep track of base color for the entry int thisColor; // Goober5000 - if ((entry->type == LOG_GOAL_SATISFIED) || (entry->type == LOG_GOAL_FAILED)) + if ((entry.type == LOG_GOAL_SATISFIED) || (entry.type == LOG_GOAL_FAILED)) thisColor = LOG_COLOR_BRIGHT; - else if (entry->primary_team >= 0) - thisColor = message_log_team_get_color(entry->primary_team); + else if (entry.primary_team >= 0) + thisColor = mission_log_team_get_color(entry.primary_team); else thisColor = LOG_COLOR_OTHER; - if ( (Lcl_gr) && ((entry->type == LOG_GOAL_FAILED) || (entry->type == LOG_GOAL_SATISFIED)) ) { + if ( (Lcl_gr) && ((entry.type == LOG_GOAL_FAILED) || (entry.type == LOG_GOAL_SATISFIED)) ) { // in german goal events, just say "objective" instead of objective name // this is cuz we can't translate objective names message_log_add_seg(&thisEntry.objective, OBJECT_X, thisColor, "Einsatzziel"); - } else if ( (Lcl_pl) && ((entry->type == LOG_GOAL_FAILED) || (entry->type == LOG_GOAL_SATISFIED)) ) { + } else if ( (Lcl_pl) && ((entry.type == LOG_GOAL_FAILED) || (entry.type == LOG_GOAL_SATISFIED)) ) { // same thing for polish message_log_add_seg(&thisEntry.objective, OBJECT_X, thisColor, "Cel misji"); } else { - message_log_add_seg(&thisEntry.objective, OBJECT_X, thisColor, entry->pname_display.c_str()); + message_log_add_seg(&thisEntry.objective, OBJECT_X, thisColor, entry.pname_display.c_str()); } //Set the flags for objectives - if (entry->type == LOG_GOAL_SATISFIED) { + if (entry.type == LOG_GOAL_SATISFIED) { thisEntry.objective.flags = LOG_FLAG_GOAL_TRUE; - } else if (entry->type == LOG_GOAL_FAILED) { + } else if (entry.type == LOG_GOAL_FAILED) { thisEntry.objective.flags = LOG_FLAG_GOAL_FAILED; } else { thisEntry.objective.flags = 0; @@ -737,21 +569,21 @@ void message_log_init_scrollback(int pw, bool split_string) X = ACTION_X; // Goober5000 - if (entry->secondary_team >= 0) - thisColor = message_log_team_get_color(entry->secondary_team); + if (entry.secondary_team >= 0) + thisColor = mission_log_team_get_color(entry.secondary_team); else thisColor = LOG_COLOR_NORMAL; char text[256]; - switch (entry->type) { + switch (entry.type) { case LOG_SHIP_DESTROYED: message_log_add_segs(XSTR( "Destroyed", 404), LOG_COLOR_NORMAL, 0, &thisEntry.segments, split_string); - if (!entry->sname_display.empty()) { + if (!entry.sname_display.empty()) { message_log_add_segs(XSTR(" Kill: ", 405), LOG_COLOR_NORMAL, 0, &thisEntry.segments, split_string); - message_log_add_segs(entry->sname_display.c_str(), thisColor, 0, &thisEntry.segments, split_string); - if (entry->index >= 0) { - sprintf(text, NOX(" (%d%%)"), entry->index); + message_log_add_segs(entry.sname_display.c_str(), thisColor, 0, &thisEntry.segments, split_string); + if (entry.index >= 0) { + sprintf(text, NOX(" (%d%%)"), entry.index); message_log_add_segs(text, LOG_COLOR_BRIGHT, 0, &thisEntry.segments, split_string); } } @@ -770,8 +602,8 @@ void message_log_init_scrollback(int pw, bool split_string) break; case LOG_WING_ARRIVED: - if (entry->index > 1){ - sprintf(text, XSTR( "Arrived (wave %d)", 407), entry->index); + if (entry.index > 1){ + sprintf(text, XSTR( "Arrived (wave %d)", 407), entry.index); } else { strcpy_s(text, XSTR( "Arrived", 406)); } @@ -788,12 +620,12 @@ void message_log_init_scrollback(int pw, bool split_string) case LOG_SHIP_DOCKED: message_log_add_segs(XSTR("Docked with ", 409), LOG_COLOR_NORMAL, 0, &thisEntry.segments, split_string); - message_log_add_segs(entry->sname_display.c_str(), thisColor, 0, &thisEntry.segments, split_string); + message_log_add_segs(entry.sname_display.c_str(), thisColor, 0, &thisEntry.segments, split_string); break; case LOG_SHIP_SUBSYS_DESTROYED: { - ship_info* sip = &Ship_info[(int)((entry->index >> 16) & 0xffff)]; - int model_index = (int)(entry->index & 0xffff); + ship_info* sip = &Ship_info[(int)((entry.index >> 16) & 0xffff)]; + int model_index = (int)(entry.index & 0xffff); const char* subsys_name; if (strlen(sip->subsystems[model_index].alt_sub_name)) { subsys_name = sip->subsystems[model_index].alt_sub_name; @@ -809,7 +641,7 @@ void message_log_init_scrollback(int pw, bool split_string) case LOG_SHIP_UNDOCKED: message_log_add_segs(XSTR("Undocked with ", 412), LOG_COLOR_NORMAL, 0, &thisEntry.segments, split_string); - message_log_add_segs(entry->sname_display.c_str(), thisColor, 0, &thisEntry.segments, split_string); + message_log_add_segs(entry.sname_display.c_str(), thisColor, 0, &thisEntry.segments, split_string); break; case LOG_SHIP_DISABLED: @@ -833,30 +665,30 @@ void message_log_init_scrollback(int pw, bool split_string) break; case LOG_CARGO_REVEALED: - Assert( entry->index >= 0 ); - Assert(!(entry->index & CARGO_NO_DEPLETE)); + Assert( entry.index >= 0 ); + Assert(!(entry.index & CARGO_NO_DEPLETE)); message_log_add_segs(XSTR("Cargo revealed: ", 418), LOG_COLOR_NORMAL, 0, &thisEntry.segments, split_string); - strncpy(text, Cargo_names[entry->index], sizeof(text) - 1); + strncpy(text, Cargo_names[entry.index], sizeof(text) - 1); message_log_add_segs(text, LOG_COLOR_BRIGHT, 0, &thisEntry.segments, split_string); break; case LOG_CAP_SUBSYS_CARGO_REVEALED: - Assert( entry->index >= 0 ); - Assert(!(entry->index & CARGO_NO_DEPLETE)); + Assert( entry.index >= 0 ); + Assert(!(entry.index & CARGO_NO_DEPLETE)); - message_log_add_segs(entry->sname_display.c_str(), LOG_COLOR_NORMAL, 0, &thisEntry.segments, split_string); + message_log_add_segs(entry.sname_display.c_str(), LOG_COLOR_NORMAL, 0, &thisEntry.segments, split_string); message_log_add_segs(XSTR( " subsystem cargo revealed: ", 1488), LOG_COLOR_NORMAL, 0, &thisEntry.segments, split_string); - strncpy(text, Cargo_names[entry->index], sizeof(text) - 1); + strncpy(text, Cargo_names[entry.index], sizeof(text) - 1); message_log_add_segs(text, LOG_COLOR_BRIGHT, 0, &thisEntry.segments, split_string); break; case LOG_GOAL_SATISFIED: case LOG_GOAL_FAILED: { - int type = Mission_goals[entry->index].type & GOAL_TYPE_MASK; + int type = Mission_goals[entry.index].type & GOAL_TYPE_MASK; sprintf( text, XSTR( "%s objective ", 419), Goal_type_text(type) ); - if ( entry->type == LOG_GOAL_SATISFIED ) + if ( entry.type == LOG_GOAL_SATISFIED ) strcat_s(text, XSTR( "satisfied.", 420)); else strcat_s(text, XSTR( "failed.", 421)); @@ -870,14 +702,24 @@ void message_log_init_scrollback(int pw, bool split_string) } Log_scrollback_vec.push_back(std::move(thisEntry)); - Num_log_lines++; } } -void message_log_shutdown_scrollback() +void mission_log_shutdown_scrollback() { Log_scrollback_vec.clear(); - Num_log_lines = 0; +} + +int mission_log_scrollback_num_lines() +{ + return static_cast(Log_scrollback_vec.size()); +} + +const log_line_complete* mission_log_scrollback_get_line(int index) +{ + if (!SCP_vector_inbounds(Log_scrollback_vec, index)) + return nullptr; + return &Log_scrollback_vec[index]; } const color *log_line_get_color(int tag) @@ -890,7 +732,7 @@ const color *log_line_get_color(int tag) return &Color_normal; default: { - int team = message_log_color_get_team(tag); + int team = mission_log_color_get_team(tag); if (team < 0) return &Color_text_normal; else @@ -907,7 +749,7 @@ void mission_log_scrollback(int line_offset, int list_x, int list_y, int list_w, int y = 0; - for (int i = line_offset; i < (int)Log_scrollback_vec.size(); i++) { + for (int i = line_offset; i < mission_log_scrollback_num_lines(); i++) { // if we're beyond the printable area stop printing! if (y + font_h > list_h) diff --git a/code/mission/missionlog.h b/code/mission/missionlog.h index 31d49b16b14..dcba060111d 100644 --- a/code/mission/missionlog.h +++ b/code/mission/missionlog.h @@ -43,9 +43,7 @@ enum LogType { // structure definition for log entries -#define MLF_ESSENTIAL (1 << 0) // this entry is essential for goal checking code -#define MLF_OBSOLETE (1 << 1) // this entry is obsolete and will be removed -#define MLF_HIDDEN (1 << 2) // entry doesn't show up in displayed log. +#define MLF_HIDDEN (1 << 0) // entry doesn't show up in displayed log. // defines for log flags #define LOG_FLAG_GOAL_FAILED (1 << 0) @@ -87,9 +85,6 @@ struct log_line_complete { SCP_vector segments; }; -extern SCP_vector Log_scrollback_vec; -extern int Num_log_lines; - // function prototypes // to be called before each mission starts @@ -112,13 +107,16 @@ extern int mission_log_get_time_indexed(LogType type, const char *name, const ch extern int mission_log_get_count(LogType type, const char *pname, const char *sname); // get the team for a log item -extern int message_log_color_get_team(int msg_color); +extern int mission_log_color_get_team(int msg_color); // get the actual color for a line item extern const color *log_line_get_color(int tag); -void message_log_init_scrollback(int pw, bool split_string = true); -void message_log_shutdown_scrollback(); -void mission_log_scrollback(int line_offset, int list_x, int list_y, int list_w, int list_h); +extern void mission_log_init_scrollback(int pw, bool split_string = true); +extern void mission_log_shutdown_scrollback(); +extern void mission_log_scrollback(int line_offset, int list_x, int list_y, int list_w, int list_h); + +extern int mission_log_scrollback_num_lines(); +extern const log_line_complete* mission_log_scrollback_get_line(int index); #endif diff --git a/code/scripting/api/libs/ui.cpp b/code/scripting/api/libs/ui.cpp index 4fc78ff51ba..bd304df883d 100644 --- a/code/scripting/api/libs/ui.cpp +++ b/code/scripting/api/libs/ui.cpp @@ -2078,10 +2078,8 @@ ADE_FUNC(initMissionLog, l_UserInterface_MissionLog, nullptr, "Initializes the M { SCP_UNUSED(L); - Log_scrollback_vec.clear(); // Make sure the vector is empty before we start - - //explicitely do not split lines! - message_log_init_scrollback(0, false); + //explicitly do not split lines! + mission_log_init_scrollback(0, false); return ADE_RETURN_NIL; } @@ -2090,7 +2088,7 @@ ADE_FUNC(closeMissionLog, l_UserInterface_MissionLog, nullptr, "Clears the Missi { SCP_UNUSED(L); - message_log_shutdown_scrollback(); + mission_log_shutdown_scrollback(); return ADE_RETURN_NIL; } @@ -2107,7 +2105,7 @@ ADE_INDEXER(l_Log_Entries, return ade_set_error(L, "o", l_Log_Entry.Set(log_entry_h())); idx--; //Convert to Lua's 1 based index system - if ((idx < 0) || (idx >= (int)Log_scrollback_vec.size())) + if ((idx < 0) || (idx >= mission_log_scrollback_num_lines())) return ade_set_error(L, "o", l_Log_Entry.Set(log_entry_h())); return ade_set_args(L, "o", l_Log_Entry.Set(log_entry_h(idx))); @@ -2115,7 +2113,7 @@ ADE_INDEXER(l_Log_Entries, ADE_FUNC(__len, l_Log_Entries, nullptr, "The number of mission log entries", "number", "The number of log entries.") { - return ade_set_args(L, "i", (int)Log_scrollback_vec.size()); + return ade_set_args(L, "i", mission_log_scrollback_num_lines()); } ADE_LIB_DERIV(l_Log_Messages, "Log_Messages", nullptr, nullptr, l_UserInterface_MissionLog); diff --git a/code/scripting/api/objs/missionlog.cpp b/code/scripting/api/objs/missionlog.cpp index f7f15da8c6a..ca296163642 100644 --- a/code/scripting/api/objs/missionlog.cpp +++ b/code/scripting/api/objs/missionlog.cpp @@ -14,23 +14,23 @@ namespace api { log_entry_h::log_entry_h() : section(-1) {} log_entry_h::log_entry_h(int l_section) : section(l_section) {} -log_line_complete* log_entry_h::getSection() const +const log_line_complete* log_entry_h::getSection() const { if (!isValid()) return nullptr; - return &Log_scrollback_vec[section]; + return mission_log_scrollback_get_line(section); } bool log_entry_h::isValid() const { - return section >= 0 && section < (int)Log_scrollback_vec.size(); + return section >= 0 && section < mission_log_scrollback_num_lines(); } message_entry_h::message_entry_h() : section(-1) {} message_entry_h::message_entry_h(int l_section) : section(l_section) {} -line_node* message_entry_h::getSection() const +const line_node* message_entry_h::getSection() const { if (!isValid()) return nullptr; diff --git a/code/scripting/api/objs/missionlog.h b/code/scripting/api/objs/missionlog.h index 1551a1e8aec..266162d09ea 100644 --- a/code/scripting/api/objs/missionlog.h +++ b/code/scripting/api/objs/missionlog.h @@ -11,7 +11,7 @@ struct log_entry_h { int section; log_entry_h(); explicit log_entry_h(int l_section); - log_line_complete* getSection() const; + const log_line_complete* getSection() const; bool isValid() const; }; @@ -19,7 +19,7 @@ struct message_entry_h { int section; message_entry_h(); explicit message_entry_h(int l_section); - line_node* getSection() const; + const line_node* getSection() const; bool isValid() const; }; From 1bb34cc0ae42179f0966bec96d801ab7375d6ae3 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Thu, 13 Jun 2024 23:52:43 -0400 Subject: [PATCH 54/63] make sure 'tactical' variants of disable and disarm are allowed The new `ai-disable-ship-tactical` and `ai-disarm-ship-tactical` goals were inadvertently omitted from the ship type default table, causing these goals to not be available in the Event Editor drop-down menu or Ship Editor initial orders. Testing did not catch this because testing was done using the HUD squad message system which assigned the new goal (if the flag is enabled) without explicitly checking the validity. Follow-up to #5512. Fixes #6189. --- code/def_files/data/tables/objecttypes.tbl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/code/def_files/data/tables/objecttypes.tbl b/code/def_files/data/tables/objecttypes.tbl index bb2732b4d31..584b87b9903 100644 --- a/code/def_files/data/tables/objecttypes.tbl +++ b/code/def_files/data/tables/objecttypes.tbl @@ -109,9 +109,9 @@ $Fog: +Start dist: 10.0 +Compl dist: 500.0 $AI: - +Valid goals: ( "fly to ship" "attack ship" "waypoints" "waypoints once" "depart" "attack subsys" "attack wing" "guard ship" "disable ship" "disarm ship" "attack any" "attack ship class" "ignore ship" "ignore ship (new)" "guard wing" "evade ship" "stay still" "play dead" "play dead (persistent)" "stay near ship" "keep safe dist" ) + +Valid goals: ( "fly to ship" "attack ship" "waypoints" "waypoints once" "depart" "attack subsys" "attack wing" "guard ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "attack any" "attack ship class" "ignore ship" "ignore ship (new)" "guard wing" "evade ship" "stay still" "play dead" "play dead (persistent)" "stay near ship" "keep safe dist" ) +Accept Player Orders: YES - +Player Orders: ( "attack ship" "disable ship" "disarm ship" "guard ship" "ignore ship" "ignore ship (new)" "form on wing" "cover me" "attack any" "depart" "disable subsys" ) + +Player Orders: ( "attack ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "guard ship" "ignore ship" "ignore ship (new)" "form on wing" "cover me" "attack any" "depart" "disable subsys" ) +Auto attacks: YES +Actively Pursues: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "stealth" "fighter" "bomber" "fighter/bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" ) +Guards attack this: YES @@ -136,9 +136,9 @@ $Fog: +Start dist: 10.0 +Compl dist: 500.0 $AI: - +Valid goals: ( "fly to ship" "attack ship" "waypoints" "waypoints once" "depart" "attack subsys" "attack wing" "guard ship" "disable ship" "disarm ship" "attack any" "attack ship class" "ignore ship" "ignore ship (new)" "guard wing" "evade ship" "stay still" "play dead" "play dead (persistent)" "stay near ship" "keep safe dist" "form on wing") + +Valid goals: ( "fly to ship" "attack ship" "waypoints" "waypoints once" "depart" "attack subsys" "attack wing" "guard ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "attack any" "attack ship class" "ignore ship" "ignore ship (new)" "guard wing" "evade ship" "stay still" "play dead" "play dead (persistent)" "stay near ship" "keep safe dist" "form on wing") +Accept Player Orders: YES - +Player Orders: ( "attack ship" "disable ship" "disarm ship" "guard ship" "ignore ship" "ignore ship (new)" "form on wing" "cover me" "attack any" "depart" "disable subsys" ) + +Player Orders: ( "attack ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "guard ship" "ignore ship" "ignore ship (new)" "form on wing" "cover me" "attack any" "depart" "disable subsys" ) +Auto attacks: YES +Actively Pursues: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "stealth" "fighter" "bomber" "fighter/bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" ) +Guards attack this: YES @@ -164,9 +164,9 @@ $Fog: +Start dist: 10.0 +Compl dist: 500.0 $AI: - +Valid goals: ( "fly to ship" "attack ship" "waypoints" "waypoints once" "depart" "attack subsys" "attack wing" "guard ship" "disable ship" "disarm ship" "attack any" "attack ship class" "ignore ship" "ignore ship (new)" "guard wing" "evade ship" "stay still" "play dead" "play dead (persistent)" "stay near ship" "keep safe dist" "form on wing" ) + +Valid goals: ( "fly to ship" "attack ship" "waypoints" "waypoints once" "depart" "attack subsys" "attack wing" "guard ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "attack any" "attack ship class" "ignore ship" "ignore ship (new)" "guard wing" "evade ship" "stay still" "play dead" "play dead (persistent)" "stay near ship" "keep safe dist" "form on wing" ) +Accept Player Orders: YES - +Player Orders: ( "attack ship" "disable ship" "disarm ship" "guard ship" "ignore ship" "ignore ship (new)" "form on wing" "cover me" "attack any" "depart" "disable subsys" ) + +Player Orders: ( "attack ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "guard ship" "ignore ship" "ignore ship (new)" "form on wing" "cover me" "attack any" "depart" "disable subsys" ) +Auto attacks: YES +Actively Pursues: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "stealth" "fighter" "bomber" "fighter/bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" ) +Guards attack this: YES @@ -192,9 +192,9 @@ $Fog: +Start dist: 10.0 +Compl dist: 500.0 $AI: - +Valid goals: ( "fly to ship" "attack ship" "waypoints" "waypoints once" "depart" "attack subsys" "attack wing" "guard ship" "disable ship" "disarm ship" "attack any" "attack ship class" "ignore ship" "ignore ship (new)" "guard wing" "evade ship" "stay still" "play dead" "play dead (persistent)" "stay near ship" "keep safe dist" "form on wing" ) + +Valid goals: ( "fly to ship" "attack ship" "waypoints" "waypoints once" "depart" "attack subsys" "attack wing" "guard ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "attack any" "attack ship class" "ignore ship" "ignore ship (new)" "guard wing" "evade ship" "stay still" "play dead" "play dead (persistent)" "stay near ship" "keep safe dist" "form on wing" ) +Accept Player Orders: YES - +Player Orders: ( "attack ship" "disable ship" "disarm ship" "guard ship" "ignore ship" "ignore ship (new)" "form on wing" "cover me" "attack any" "depart" "disable subsys" ) + +Player Orders: ( "attack ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "guard ship" "ignore ship" "ignore ship (new)" "form on wing" "cover me" "attack any" "depart" "disable subsys" ) +Auto attacks: YES +Actively Pursues: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "stealth" "fighter" "bomber" "fighter/bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" ) +Guards attack this: YES From ce34f77780b377ce4fba96096e5b516b5311d89c Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Fri, 14 Jun 2024 00:08:47 -0400 Subject: [PATCH 55/63] add the attack-ship-class goal as valid for player orders --- code/def_files/data/tables/objecttypes.tbl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/code/def_files/data/tables/objecttypes.tbl b/code/def_files/data/tables/objecttypes.tbl index 584b87b9903..f7307102f83 100644 --- a/code/def_files/data/tables/objecttypes.tbl +++ b/code/def_files/data/tables/objecttypes.tbl @@ -111,7 +111,7 @@ $Fog: $AI: +Valid goals: ( "fly to ship" "attack ship" "waypoints" "waypoints once" "depart" "attack subsys" "attack wing" "guard ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "attack any" "attack ship class" "ignore ship" "ignore ship (new)" "guard wing" "evade ship" "stay still" "play dead" "play dead (persistent)" "stay near ship" "keep safe dist" ) +Accept Player Orders: YES - +Player Orders: ( "attack ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "guard ship" "ignore ship" "ignore ship (new)" "form on wing" "cover me" "attack any" "depart" "disable subsys" ) + +Player Orders: ( "attack ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "guard ship" "ignore ship" "ignore ship (new)" "form on wing" "cover me" "attack any" "attack ship class" "depart" "disable subsys" ) +Auto attacks: YES +Actively Pursues: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "stealth" "fighter" "bomber" "fighter/bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" ) +Guards attack this: YES @@ -138,7 +138,7 @@ $Fog: $AI: +Valid goals: ( "fly to ship" "attack ship" "waypoints" "waypoints once" "depart" "attack subsys" "attack wing" "guard ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "attack any" "attack ship class" "ignore ship" "ignore ship (new)" "guard wing" "evade ship" "stay still" "play dead" "play dead (persistent)" "stay near ship" "keep safe dist" "form on wing") +Accept Player Orders: YES - +Player Orders: ( "attack ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "guard ship" "ignore ship" "ignore ship (new)" "form on wing" "cover me" "attack any" "depart" "disable subsys" ) + +Player Orders: ( "attack ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "guard ship" "ignore ship" "ignore ship (new)" "form on wing" "cover me" "attack any" "attack ship class" "depart" "disable subsys" ) +Auto attacks: YES +Actively Pursues: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "stealth" "fighter" "bomber" "fighter/bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" ) +Guards attack this: YES @@ -166,7 +166,7 @@ $Fog: $AI: +Valid goals: ( "fly to ship" "attack ship" "waypoints" "waypoints once" "depart" "attack subsys" "attack wing" "guard ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "attack any" "attack ship class" "ignore ship" "ignore ship (new)" "guard wing" "evade ship" "stay still" "play dead" "play dead (persistent)" "stay near ship" "keep safe dist" "form on wing" ) +Accept Player Orders: YES - +Player Orders: ( "attack ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "guard ship" "ignore ship" "ignore ship (new)" "form on wing" "cover me" "attack any" "depart" "disable subsys" ) + +Player Orders: ( "attack ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "guard ship" "ignore ship" "ignore ship (new)" "form on wing" "cover me" "attack any" "attack ship class" "depart" "disable subsys" ) +Auto attacks: YES +Actively Pursues: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "stealth" "fighter" "bomber" "fighter/bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" ) +Guards attack this: YES @@ -194,7 +194,7 @@ $Fog: $AI: +Valid goals: ( "fly to ship" "attack ship" "waypoints" "waypoints once" "depart" "attack subsys" "attack wing" "guard ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "attack any" "attack ship class" "ignore ship" "ignore ship (new)" "guard wing" "evade ship" "stay still" "play dead" "play dead (persistent)" "stay near ship" "keep safe dist" "form on wing" ) +Accept Player Orders: YES - +Player Orders: ( "attack ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "guard ship" "ignore ship" "ignore ship (new)" "form on wing" "cover me" "attack any" "depart" "disable subsys" ) + +Player Orders: ( "attack ship" "disable ship" "disable ship (tactical)" "disarm ship" "disarm ship (tactical)" "guard ship" "ignore ship" "ignore ship (new)" "form on wing" "cover me" "attack any" "attack ship class" "depart" "disable subsys" ) +Auto attacks: YES +Actively Pursues: ( "navbuoy" "sentry gun" "escape pod" "cargo" "support" "stealth" "fighter" "bomber" "fighter/bomber" "transport" "freighter" "awacs" "gas miner" "cruiser" "corvette" "capital" "super cap" "drydock" "knossos device" ) +Guards attack this: YES From 3f9ec121ae67fd00861983481c949ef2c4004ec9 Mon Sep 17 00:00:00 2001 From: Taylor Richards Date: Sun, 16 Jun 2024 01:44:12 -0400 Subject: [PATCH 56/63] add support for PXO's UDP hole punch feature Should allow for most games, even those behind CG-NAT, to work without having to set up port forwarding. This set of changes is only needed for game servers to work with the new feature. No special handling is required for clients, so any build from the last few years should work for them. --- code/network/gtrack.cpp | 63 +++++++++++++++++++++++++++++++++++++++++ code/network/gtrack.h | 14 +++++++++ 2 files changed, 77 insertions(+) diff --git a/code/network/gtrack.cpp b/code/network/gtrack.cpp index ee39aeba315..ee678027c3a 100644 --- a/code/network/gtrack.cpp +++ b/code/network/gtrack.cpp @@ -63,6 +63,7 @@ static unsigned int FirstGameOverPacket; static int SendingGameOver; //End New 7-9-98 +static void SendClientHolePunch(sockaddr_in6 *addr); static int SerializeGamePacket(const game_packet_header *gph, ubyte *data) { @@ -82,6 +83,7 @@ static int SerializeGamePacket(const game_packet_header *gph, ubyte *data) // these have no other data case GNT_CLIENT_ACK: case GNT_GAMEOVER: + case GNT_NAT_HOLE_PUNCH_ACK: break; // this one may or may not have extra data @@ -259,6 +261,22 @@ static void DeserializeGamePacket(const ubyte *data, const int data_size, game_p break; } + case GNT_NAT_HOLE_PUNCH_REQ: { + if (gph->len == (GAME_HEADER_ONLY_SIZE+sizeof(hole_punch_addr_ip6))) { + auto ipv6 = reinterpret_cast(&gph->data); + + PXO_GET_DATA(ipv6->addr); + PXO_GET_USHORT(ipv6->port); + } else { + auto ipv4 = reinterpret_cast(&gph->data); + + PXO_GET_UINT(ipv4->addr); + PXO_GET_USHORT(ipv4->port); + } + + break; + } + default: break; } @@ -475,7 +493,31 @@ void IdleGameTracker() // tell user about it multi_fs_tracker_report_probe_status(flags, next_try); break; + + case GNT_NAT_HOLE_PUNCH_REQ: + sockaddr_in6 nataddr; + + nataddr.sin6_family = AF_INET6; + + if (inpacket.len == (GAME_HEADER_ONLY_SIZE+sizeof(hole_punch_addr_ip6))) { + auto ipv6 = reinterpret_cast(&inpacket.data); + + nataddr.sin6_addr = ipv6->addr; + nataddr.sin6_port = ipv6->port; + } else { + auto ipv4 = reinterpret_cast(&inpacket.data); + in_addr addr; + + addr.s_addr = ipv4->addr; + + psnet_map4to6(&addr, &nataddr.sin6_addr); + nataddr.sin6_port = ipv4->port; + } + + SendClientHolePunch(&nataddr); + break; } + AckPacket(inpacket.sig); } } @@ -627,3 +669,24 @@ void RequestGameCountWithFilter(void *filter) SENDTO(Psnet_socket, reinterpret_cast(&packet_data), packet_length, 0, reinterpret_cast(>rackaddr), sizeof(gtrackaddr), PSNET_TYPE_GAME_TRACKER); } + +static void SendClientHolePunch(sockaddr_in6 *addr) +{ + game_packet_header HolePunchAck; + ubyte packet_data[sizeof(game_packet_header)]; + int packet_length = 0; + + // if client is IPv6, and we can't do that, then just skip it + if ( !IN6_IS_ADDR_V4MAPPED(&addr->sin6_addr) && !(psnet_get_ip_mode() & PSNET_IP_MODE_V6) ) { + return; + } + + HolePunchAck.game_type = GT_FS2OPEN; + HolePunchAck.type = GNT_NAT_HOLE_PUNCH_ACK; + HolePunchAck.len = GAME_HEADER_ONLY_SIZE; + HolePunchAck.sig = 0; // to make sure tracker ignores this packet when it's ACK'd there + + packet_length = SerializeGamePacket(&HolePunchAck, packet_data); + SENDTO(Psnet_socket, reinterpret_cast(&packet_data), packet_length, 0, + reinterpret_cast(addr), sizeof(sockaddr_in6), PSNET_TYPE_GAME_TRACKER); +} \ No newline at end of file diff --git a/code/network/gtrack.h b/code/network/gtrack.h index b4be4e5ca55..675d577da04 100644 --- a/code/network/gtrack.h +++ b/code/network/gtrack.h @@ -50,6 +50,9 @@ #define GNT_GAMELIST_DATA_NEW 9 #define GNT_GAME_PROBE_STATUS 10 #define GNT_GAMEUPDATE_STATUS 11 +// NOTE: IDs 12-16 are special for Descent3 and shouldn't be used here!! +#define GNT_NAT_HOLE_PUNCH_REQ 17 +#define GNT_NAT_HOLE_PUNCH_ACK 18 #define GT_FREESPACE 1 #define GT_DESCENT3 2 @@ -111,6 +114,17 @@ typedef struct { char pad[3]; // 3-bytes padding for size/alignment } filter_game_list_struct; +#pragma pack(push, 1) +struct hole_punch_addr { + uint32_t addr; + uint16_t port; +}; + +struct hole_punch_addr_ip6 { + in6_addr addr; + uint16_t port; +}; +#pragma pack(pop) //Function prototypes From 1821df58cfe3cc0705f4c8278c6ba03349831de0 Mon Sep 17 00:00:00 2001 From: Taylor Richards Date: Sun, 16 Jun 2024 11:04:01 -0400 Subject: [PATCH 57/63] limit debug log spam from file shadows (#6027) Always log the possible shadow if: 1) it's a critical file 2) it's in the same root Otherwise log it behind a debug filter ("CFileSystem"). --- code/cfile/cfilesystem.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/cfile/cfilesystem.cpp b/code/cfile/cfilesystem.cpp index a130fa36268..52b9a009f2d 100644 --- a/code/cfile/cfilesystem.cpp +++ b/code/cfile/cfilesystem.cpp @@ -826,7 +826,8 @@ static void check_file_shadows(const int root_index __UNUSED, const int pathtype } // this log message occurs in the middle of an existing line, hence the extra new lines - mprintf(("\nWARNING! A %sfile being indexed may be shadowed by an existing file!\n New:\n %s\n Existing:\n %s\n", + // critical files and files in same root are always logged, files in different root are behind a filter + nprintf(((critical || !root->path.compare(r->path)) ? "General" : "CFileSystem", "\nWARNING! A %sfile being indexed may be shadowed by an existing file!\n New:\n %s\n Existing:\n %s\n", critical ? "critical " : "", newfile.c_str(), curfile.c_str())); From ebeec69a6d3ba4f27fe58631aac23047f6d6fc5f Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Mon, 17 Jun 2024 15:28:40 -0400 Subject: [PATCH 58/63] add API for glow point banks (#6107) * add API for glow point banks Previously, a model API existed for glow points mounted on thrusters, but not glow points mounted on glow point banks. This adds glow point bank support. Also, this refactors and cleans up the implementation of the model API a bit. * NULL -> nullptr --- code/scripting/api/objs/model.cpp | 494 ++++++++++++++++++------------ code/scripting/api/objs/model.h | 37 ++- 2 files changed, 333 insertions(+), 198 deletions(-) diff --git a/code/scripting/api/objs/model.cpp b/code/scripting/api/objs/model.cpp index 60dd19c2871..3558020abb2 100644 --- a/code/scripting/api/objs/model.cpp +++ b/code/scripting/api/objs/model.cpp @@ -74,80 +74,87 @@ ADE_VIRTVAR(Submodels, l_Model, nullptr, "Model submodels", "submodels", "Model return ade_set_error(L, "o", l_ModelSubmodels.Set(model_h())); if (ADE_SETTING_VAR) - LuaError(L, "Attempt to use Incomplete Feature: Modelsubmodels copy"); + LuaError(L, "Assigning submodels is not supported"); return ade_set_args(L, "o", l_ModelSubmodels.Set(model_h(pm))); } ADE_VIRTVAR(Textures, l_Model, nullptr, "Model textures", "textures", "Model textures, or an invalid textures handle if the model handle is invalid") { - model_h *mdl = NULL; - model_h *oth = NULL; - if(!ade_get_args(L, "o|o", l_Model.GetPtr(&mdl), l_ModelTextures.GetPtr(&oth))) + model_h *mdl = nullptr; + if (!ade_get_args(L, "o", l_Model.GetPtr(&mdl))) return ade_set_error(L, "o", l_ModelTextures.Set(model_h())); polymodel *pm = mdl->Get(); - if(pm == NULL) + if (!pm) return ade_set_error(L, "o", l_ModelTextures.Set(model_h())); - if(ADE_SETTING_VAR && oth && oth->isValid()) { - //WMC TODO: Copy code - LuaError(L, "Attempt to use Incomplete Feature: Modeltextures copy"); - } + if (ADE_SETTING_VAR) + LuaError(L, "Assigning textures is not supported"); return ade_set_args(L, "o", l_ModelTextures.Set(model_h(pm))); } ADE_VIRTVAR(Thrusters, l_Model, nullptr, "Model thrusters", "thrusters", "Model thrusters, or an invalid thrusters handle if the model handle is invalid") { - model_h *mdl = NULL; - model_h *oth = NULL; - if(!ade_get_args(L, "o|o", l_Model.GetPtr(&mdl), l_ModelThrusters.GetPtr(&oth))) + model_h *mdl = nullptr; + if (!ade_get_args(L, "o", l_Model.GetPtr(&mdl))) return ade_set_error(L, "o", l_ModelThrusters.Set(model_h())); polymodel *pm = mdl->Get(); - if(pm == NULL) + if (!pm) return ade_set_error(L, "o", l_ModelThrusters.Set(model_h())); - if(ADE_SETTING_VAR && oth && oth->isValid()) { - LuaError(L, "Attempt to use Incomplete Feature: Thrusters copy"); - } + if (ADE_SETTING_VAR) + LuaError(L, "Assigning thrusters is not supported"); return ade_set_args(L, "o", l_ModelThrusters.Set(model_h(pm))); } +ADE_VIRTVAR(GlowPointBanks, l_Model, nullptr, "Model glow point banks", "glowpointbanks", "Model glow point banks, or an invalid glowpointbanks handle if the model handle is invalid") +{ + model_h *mdl = nullptr; + if (!ade_get_args(L, "o", l_Model.GetPtr(&mdl))) + return ade_set_error(L, "o", l_ModelGlowpointbanks.Set(model_h())); + + polymodel *pm = mdl->Get(); + if (!pm) + return ade_set_error(L, "o", l_ModelGlowpointbanks.Set(model_h())); + + if (ADE_SETTING_VAR) + LuaError(L, "Assigning glow point banks is not supported"); + + return ade_set_args(L, "o", l_ModelGlowpointbanks.Set(model_h(pm))); +} + ADE_VIRTVAR(Eyepoints, l_Model, nullptr, "Model eyepoints", "eyepoints", "Array of eyepoints, or an invalid eyepoints handle if the model handle is invalid") { - model_h *mdl = NULL; - model_h *eph = NULL; - if(!ade_get_args(L, "o|o", l_Model.GetPtr(&mdl), l_ModelEyepoints.GetPtr(&eph))) + model_h *mdl = nullptr; + if (!ade_get_args(L, "o", l_Model.GetPtr(&mdl))) return ade_set_error(L, "o", l_ModelEyepoints.Set(model_h())); polymodel *pm = mdl->Get(); - if(pm == NULL) + if (!pm) return ade_set_error(L, "o", l_ModelEyepoints.Set(model_h())); - if(ADE_SETTING_VAR && eph && eph->isValid()) { - LuaError(L, "Attempt to use Incomplete Feature: Eyepoints copy"); - } + if (ADE_SETTING_VAR) + LuaError(L, "Assigning eye points is not supported"); return ade_set_args(L, "o", l_ModelEyepoints.Set(model_h(pm))); } ADE_VIRTVAR(Dockingbays, l_Model, nullptr, "Model docking bays", "dockingbays", "Array of docking bays, or an invalid dockingbays handle if the model handle is invalid") { - model_h *mdl = NULL; - model_h *dbh = NULL; - if(!ade_get_args(L, "o|o", l_Model.GetPtr(&mdl), l_ModelDockingbays.GetPtr(&dbh))) + model_h *mdl = nullptr; + if (!ade_get_args(L, "o", l_Model.GetPtr(&mdl))) return ade_set_error(L, "o", l_ModelDockingbays.Set(model_h())); polymodel *pm = mdl->Get(); - if(pm == NULL) + if (!pm) return ade_set_error(L, "o", l_ModelDockingbays.Set(model_h())); - if(ADE_SETTING_VAR && dbh && dbh->isValid()) { - LuaError(L, "Attempt to use Incomplete Feature: Docking bays copy"); - } + if (ADE_SETTING_VAR) + LuaError(L, "Assigning docking bays is not supported"); return ade_set_args(L, "o", l_ModelDockingbays.Set(model_h(pm))); } @@ -524,11 +531,7 @@ ADE_FUNC(__len, l_ModelSubmodels, nullptr, "Number of submodels on model", "numb if (!ade_get_args(L, "o", l_ModelSubmodels.GetPtr(&msh))) return ade_set_error(L, "i", 0); - if (!msh->isValid()) - return ade_set_error(L, "i", 0); - polymodel *pm = msh->Get(); - if (!pm) return ade_set_error(L, "i", 0); @@ -539,12 +542,17 @@ ADE_INDEXER(l_ModelSubmodels, "submodel", "number|string IndexOrName", "submodel { model_h *msh = nullptr; int index = -1; + polymodel *pm = nullptr; if (lua_isnumber(L, 2)) { if (!ade_get_args(L, "oi", l_ModelSubmodels.GetPtr(&msh), &index)) return ade_set_error(L, "o", l_Submodel.Set(submodel_h())); + pm = msh->Get(); + if (!pm) + return ade_set_error(L, "o", l_Submodel.Set(submodel_h())); + index--; // Lua --> C/C++ } else @@ -554,17 +562,19 @@ ADE_INDEXER(l_ModelSubmodels, "submodel", "number|string IndexOrName", "submodel if (!ade_get_args(L, "os", l_ModelSubmodels.GetPtr(&msh), &name)) return ade_set_error(L, "o", l_Submodel.Set(submodel_h())); - index = model_find_submodel_index(msh->GetID(), name); - } - - if (!msh->isValid()) - return ade_set_error(L, "o", l_Submodel.Set(submodel_h())); + pm = msh->Get(); + if (!pm) + return ade_set_error(L, "o", l_Submodel.Set(submodel_h())); - polymodel *pm = msh->Get(); + index = model_find_submodel_index(pm->id, name); + } if (index < 0 || index >= pm->n_models) return ade_set_error(L, "o", l_Submodel.Set(submodel_h())); + if (ADE_SETTING_VAR) + LuaError(L, "Setting submodels is not supported"); + return ade_set_args(L, "o", l_Submodel.Set(submodel_h(pm, index))); } @@ -584,18 +594,14 @@ ADE_OBJ(l_ModelTextures, model_h, "textures", "Array of textures"); ADE_FUNC(__len, l_ModelTextures, NULL, "Number of textures on model", "number", "Number of model textures") { model_h *mth; - if(!ade_get_args(L, "o", l_ModelTextures.GetPtr(&mth))) - return ade_set_error(L, "i", 0); - - if(!mth->isValid()) + if (!ade_get_args(L, "o", l_ModelTextures.GetPtr(&mth))) return ade_set_error(L, "i", 0); polymodel *pm = mth->Get(); - - if(pm == NULL) + if (!pm) return ade_set_error(L, "i", 0); - return ade_set_args(L, "i", TM_NUM_TYPES*pm->n_textures); + return ade_set_args(L, "i", TM_NUM_TYPES * pm->n_textures); } ADE_INDEXER(l_ModelTextures, "texture", "number Index/string TextureName", "texture", "Model textures, or invalid modeltextures handle if model handle is invalid") @@ -608,8 +614,7 @@ ADE_INDEXER(l_ModelTextures, "texture", "number Index/string TextureName", "text return ade_set_error(L, "o", l_Texture.Set(texture_h())); polymodel *pm = mth->Get(); - - if (!mth->isValid() || s == NULL || pm == NULL) + if (s == nullptr || pm == nullptr) return ade_set_error(L, "o", l_Texture.Set(texture_h())); texture_info *tinfo = NULL; @@ -664,63 +669,37 @@ ADE_OBJ(l_ModelEyepoints, model_h, "eyepoints", "Array of model eye points"); ADE_FUNC(__len, l_ModelEyepoints, NULL, "Gets the number of eyepoints on this model", "number", "Number of eyepoints on this model or 0 on error") { - model_h *eph = NULL; + model_h *eph = nullptr; if (!ade_get_args(L, "o", l_ModelEyepoints.GetPtr(&eph))) - { - return ade_set_error(L, "i", 0); - } - - if (!eph->isValid()) - { return ade_set_error(L, "i", 0); - } polymodel *pm = eph->Get(); - - if (pm == NULL) - { + if (!pm) return ade_set_error(L, "i", 0); - } return ade_set_args(L, "i", pm->n_view_positions); } ADE_INDEXER(l_ModelEyepoints, "eyepoint", "Gets an eyepoint handle", "eyepoint", "eye handle or invalid handle on error") { - model_h *eph = NULL; + model_h *mdl = nullptr; int index = -1; - eye_h *eh = NULL; - if (!ade_get_args(L, "oi|o", l_ModelEyepoints.GetPtr(&eph), &index, l_Eyepoint.GetPtr(&eh))) - { + if (!ade_get_args(L, "oi", l_ModelEyepoints.GetPtr(&mdl), &index)) return ade_set_error(L, "o", l_Eyepoint.Set(eye_h())); - } - if (!eph->isValid()) - { - return ade_set_error(L, "o", l_Eyepoint.Set(eye_h())); - } - - polymodel *pm = eph->Get(); - - if (pm == NULL) - { + polymodel *pm = mdl->Get(); + if (!pm) return ade_set_error(L, "o", l_Eyepoint.Set(eye_h())); - } index--; // Lua -> FS2 - if (index < 0 || index >= pm->n_view_positions) - { return ade_set_error(L, "o", l_Eyepoint.Set(eye_h())); - } - if (ADE_SETTING_VAR && eh && eh->isValid()) - { - LuaError(L, "Attempted to use incomplete feature: Eyepoint copy"); - } + if (ADE_SETTING_VAR) + LuaError(L, "Assigning eye points is not supported"); - return ade_set_args(L, "o", l_Eyepoint.Set(eye_h(eph->GetID(), index))); + return ade_set_args(L, "o", l_Eyepoint.Set(eye_h(pm->id, index))); } ADE_FUNC(isValid, l_ModelEyepoints, NULL, "Detects whether handle is valid or not", "boolean", "true if valid false otherwise") @@ -737,16 +716,12 @@ ADE_OBJ(l_ModelThrusters, model_h, "thrusters", "The thrusters of a model"); ADE_FUNC(__len, l_ModelThrusters, NULL, "Number of thruster banks on the model", "number", "Number of thrusterbanks") { - model_h *trh; - if(!ade_get_args(L, "o", l_ModelThrusters.GetPtr(&trh))) - return ade_set_error(L, "i", -1); - - if(!trh->isValid()) + model_h *mdl; + if (!ade_get_args(L, "o", l_ModelThrusters.GetPtr(&mdl))) return ade_set_error(L, "i", -1); - polymodel *pm = trh->Get(); - - if(pm == NULL) + polymodel *pm = mdl->Get(); + if (!pm) return ade_set_error(L, "i", -1); return ade_set_args(L, "i", pm->n_thrusters); @@ -754,35 +729,24 @@ ADE_FUNC(__len, l_ModelThrusters, NULL, "Number of thruster banks on the model", ADE_INDEXER(l_ModelThrusters, "number Index", "Array of all thrusterbanks on this thruster", "thrusterbank", "Handle to the thrusterbank or invalid handle if index is invalid") { - model_h *trh = NULL; - const char* s = nullptr; - thrusterbank_h newThr; + model_h *mdl = nullptr; + int idx = -1; - if (!ade_get_args(L, "os|o", l_ModelThrusters.GetPtr(&trh), &s, l_Thrusterbank.Get(&newThr))) + if (!ade_get_args(L, "oi", l_ModelThrusters.GetPtr(&mdl), &idx)) return ade_set_error(L, "o", l_Thrusterbank.Set(thrusterbank_h())); - polymodel *pm = trh->Get(); - - if (!trh->isValid() || s == NULL || pm == NULL) + polymodel *pm = mdl->Get(); + if (!pm) return ade_set_error(L, "o", l_Thrusterbank.Set(thrusterbank_h())); - //Determine index - int idx = atoi(s) - 1; //Lua->FS2 - + idx--; //Lua->FS2 if (idx < 0 || idx >= pm->n_thrusters) return ade_set_error(L, "o", l_Thrusterbank.Set(thrusterbank_h())); - thruster_bank* bank = &pm->thrusters[idx]; - - if (ADE_SETTING_VAR && trh != NULL) - { - if (newThr.isValid()) - { - pm->thrusters[idx] = *(newThr.Get()); - } - } + if (ADE_SETTING_VAR) + LuaError(L, "Assigning thruster banks is not supported"); - return ade_set_args(L, "o", l_Thrusterbank.Set(bank)); + return ade_set_args(L, "o", l_Thrusterbank.Set(thrusterbank_h(pm->id, idx))); } ADE_FUNC(isValid, l_ModelThrusters, NULL, "Detects whether handle is valid", "boolean", "true if valid, false if handle is invalid, nil if a syntax/type error occurs") @@ -797,20 +761,26 @@ ADE_FUNC(isValid, l_ModelThrusters, NULL, "Detects whether handle is valid", "bo //**********HANDLE: thrusterbank ADE_OBJ(l_Thrusterbank, thrusterbank_h, "thrusterbank", "A model thrusterbank"); -thrusterbank_h::thrusterbank_h() { - bank = NULL; -} -thrusterbank_h::thrusterbank_h(thruster_bank* ba) { - bank = ba; -} -thruster_bank* thrusterbank_h::Get() const { +thrusterbank_h::thrusterbank_h(int in_model_num, int in_thrusterbank_index) + : modelh(in_model_num), thrusterbank_index(in_thrusterbank_index) +{} +thrusterbank_h::thrusterbank_h() + : modelh(), thrusterbank_index(-1) +{} +thruster_bank* thrusterbank_h::Get() const +{ if (!isValid()) - return NULL; - - return bank; + return nullptr; + return &modelh.Get()->thrusters[thrusterbank_index]; } -bool thrusterbank_h::isValid() const { - return bank != NULL; +bool thrusterbank_h::isValid() const +{ + if (thrusterbank_index < 0) + return false; + auto model = modelh.Get(); + if (!model) + return false; + return thrusterbank_index < model->n_thrusters; } ADE_FUNC(__len, l_Thrusterbank, NULL, "Number of thrusters on this thrusterbank", "number", "Number of thrusters on this bank or 0 if handle is invalid") @@ -829,38 +799,27 @@ ADE_FUNC(__len, l_Thrusterbank, NULL, "Number of thrusters on this thrusterbank" ADE_INDEXER(l_Thrusterbank, "number Index", "Array of glowpoint", "glowpoint", "Glowpoint, or invalid glowpoint handle on failure") { - thrusterbank_h *tbh = NULL; - const char* s = nullptr; - glowpoint_h *glh = NULL; - - if (!ade_get_args(L, "os|o", l_Thrusterbank.GetPtr(&tbh), &s, l_Glowpoint.GetPtr(&glh))) - return ade_set_error(L, "o", l_Glowpoint.Set(glowpoint_h())); + thrusterbank_h *tbh = nullptr; + int idx = -1; - if (!tbh->isValid() || s==NULL) + if (!ade_get_args(L, "oi", l_Thrusterbank.GetPtr(&tbh), &idx)) return ade_set_error(L, "o", l_Glowpoint.Set(glowpoint_h())); thruster_bank* bank = tbh->Get(); + if (!bank) + return ade_set_error(L, "o", l_Glowpoint.Set(glowpoint_h())); - //Determine index - int idx = atoi(s) - 1; //Lua->FS2 - + idx--; // Lua -> FS2 if (idx < 0 || idx >= bank->num_points) return ade_set_error(L, "o", l_Glowpoint.Set(glowpoint_h())); - glow_point* glp = &bank->points[idx]; - - if (ADE_SETTING_VAR && glh != NULL) - { - if (glh->isValid()) - { - bank->points[idx] = *(glh->Get()); - } - } + if (ADE_SETTING_VAR) + LuaError(L, "Assigning glow points is not supported"); - return ade_set_args(L, "o", l_Glowpoint.Set(glp)); + return ade_set_args(L, "o", l_Glowpoint.Set(glowpoint_h(tbh->modelh.GetID(), -1, tbh->thrusterbank_index, idx))); } -ADE_FUNC(isValid, l_Thrusterbank, NULL, "Detectes if this handle is valid", "boolean", "true if this handle is valid, false otherwise") +ADE_FUNC(isValid, l_Thrusterbank, nullptr, "Detects if this handle is valid", "boolean", "true if this handle is valid, false otherwise") { thrusterbank_h* trh; if(!ade_get_args(L, "o", l_Thrusterbank.GetPtr(&trh))) @@ -872,69 +831,229 @@ ADE_FUNC(isValid, l_Thrusterbank, NULL, "Detectes if this handle is valid", "boo return ade_set_args(L, "b", trh->isValid()); } -//**********HANDLE: glowpoint -ADE_OBJ(l_Glowpoint, glowpoint_h, "glowpoint", "A model glowpoint"); +//**********HANDLE: glow point banks +ADE_OBJ(l_ModelGlowpointbanks, model_h, "glowpointbanks", "Array of model glow point banks"); + +ADE_FUNC(__len, l_ModelGlowpointbanks, nullptr, "Gets the number of glow point banks on this model", "number", "Number of glow point banks on this model or 0 on error") +{ + model_h *modelh = nullptr; + if (!ade_get_args(L, "o", l_ModelGlowpointbanks.GetPtr(&modelh))) + return ade_set_error(L, "i", 0); -glowpoint_h::glowpoint_h() { + polymodel *pm = modelh->Get(); + if (!pm) + return ade_set_error(L, "i", 0); + + return ade_set_args(L, "i", pm->n_glow_point_banks); } -glowpoint_h::glowpoint_h(glow_point* np) { - point = np; + +ADE_INDEXER(l_ModelGlowpointbanks, "glowpointbank", "Gets a glow point bank handle", "glowpointbank", "glowpointbank handle or invalid handle on error") +{ + model_h *modelh = nullptr; + int index = -1; + + if (!ade_get_args(L, "oi", l_ModelGlowpointbanks.GetPtr(&modelh), &index)) + return ade_set_error(L, "o", l_Glowpointbank.Set(glowpointbank_h())); + + polymodel *pm = modelh->Get(); + if (!pm) + return ade_set_error(L, "o", l_Glowpointbank.Set(glowpointbank_h())); + + index--; // Lua -> FS2 + if (index < 0 || index >= pm->n_glow_point_banks) + return ade_set_error(L, "o", l_Glowpointbank.Set(glowpointbank_h())); + + if (ADE_SETTING_VAR) + LuaError(L, "Assigning glow point banks is not supported"); + + return ade_set_args(L, "o", l_Glowpointbank.Set(glowpointbank_h(pm->id, index))); +} + +ADE_FUNC(isValid, l_ModelGlowpointbanks, nullptr, "Detects whether handle is valid or not", "boolean", "true if valid false otherwise") +{ + model_h *modelh; + if(!ade_get_args(L, "o", l_ModelGlowpointbanks.GetPtr(&modelh))) + return ADE_RETURN_FALSE; + + return ade_set_args(L, "b", modelh->isValid()); } -glow_point* glowpoint_h::Get() const { + +//**********HANDLE: glow point bank +ADE_OBJ(l_Glowpointbank, glowpointbank_h, "glowpointbank", "A model glow point bank"); + +glowpointbank_h::glowpointbank_h(int in_model_num, int in_glowpointbank_index) + : modelh(in_model_num), glowpointbank_index(in_glowpointbank_index) +{} +glowpointbank_h::glowpointbank_h() + : modelh(), glowpointbank_index(-1) +{} +glow_point_bank* glowpointbank_h::Get() const +{ if (!isValid()) - return NULL; + return nullptr; + return &modelh.Get()->glow_point_banks[glowpointbank_index]; +} +bool glowpointbank_h::isValid() const +{ + if (glowpointbank_index < 0) + return false; + auto model = modelh.Get(); + if (!model) + return false; + return glowpointbank_index < model->n_glow_point_banks; +} + +ADE_FUNC(__len, l_Glowpointbank, nullptr, "Gets the number of glow points in this bank", "number", "Number of glow points in this bank or 0 on error") +{ + glowpointbank_h *gpbh = nullptr; + if (!ade_get_args(L, "o", l_Glowpointbank.GetPtr(&gpbh))) + return ade_set_error(L, "i", 0); - return point; + auto gpb = gpbh->Get(); + if (!gpb) + return ade_set_error(L, "i", 0); + + return ade_set_args(L, "i", gpb->num_points); } -bool glowpoint_h::isValid() const { - return point != NULL; + +ADE_INDEXER(l_Glowpointbank, "glowpoint", "Gets a glow point handle", "glowpoint", "glowpoint handle or invalid handle on error") +{ + glowpointbank_h *gpbh = nullptr; + int index = -1; + + if (!ade_get_args(L, "oi", l_Glowpointbank.GetPtr(&gpbh), &index)) + return ade_set_error(L, "o", l_Glowpoint.Set(glowpoint_h())); + + auto gpb = gpbh->Get(); + if (!gpb) + return ade_set_error(L, "o", l_Glowpoint.Set(glowpoint_h())); + + index--; // Lua -> FS2 + if (index < 0 || index >= gpb->num_points) + return ade_set_error(L, "o", l_Glowpoint.Set(glowpoint_h())); + + if (ADE_SETTING_VAR) + LuaError(L, "Assigning glow points is not supported"); + + return ade_set_args(L, "o", l_Glowpoint.Set(glowpoint_h(gpbh->modelh.GetID(), gpbh->glowpointbank_index, -1, index))); +} + +// ********** +// NOTE: Any fields or functions of glowpointbank will need to take glowpoint_bank_overrides into account +// ********** + +ADE_FUNC(isValid, l_Glowpointbank, nullptr, "Detects whether handle is valid or not", "boolean", "true if valid false otherwise") +{ + glowpointbank_h* gpbh = nullptr; + if (!ade_get_args(L, "o", l_Glowpointbank.GetPtr(&gpbh))) + return ADE_RETURN_FALSE; + + return ade_set_args(L, "b", gpbh->isValid()); +} + +//**********HANDLE: glowpoint +ADE_OBJ(l_Glowpoint, glowpoint_h, "glowpoint", "A model glowpoint"); + +glowpoint_h::glowpoint_h(int in_model_num, int in_glowpointbank_index, int in_thrusterbank_index, int in_glowpoint_index) + : glowpointbankh(in_model_num, in_glowpointbank_index), thrusterbankh(in_model_num, in_thrusterbank_index), glowpoint_index(in_glowpoint_index) +{} +glowpoint_h::glowpoint_h() + : glowpoint_h(-1, -1, -1, -1) +{} +glow_point* glowpoint_h::Get() const +{ + if (glowpoint_index < 0) + return nullptr; + + auto gbank = glowpointbankh.Get(); + if (gbank) + { + if (glowpoint_index < gbank->num_points) + return &gbank->points[glowpoint_index]; + else + return nullptr; + } + auto tbank = thrusterbankh.Get(); + if (tbank) + { + if (glowpoint_index < tbank->num_points) + return &tbank->points[glowpoint_index]; + else + return nullptr; + } + + return nullptr; +} +bool glowpoint_h::isValid() const +{ + if (glowpoint_index < 0) + return false; + + auto gbank = glowpointbankh.Get(); + if (gbank) + return (glowpoint_index < gbank->num_points); + + auto tbank = thrusterbankh.Get(); + if (tbank) + return (glowpoint_index < tbank->num_points); + + return false; } ADE_VIRTVAR(Position, l_Glowpoint, nullptr, "The (local) vector to the position of the glowpoint", "vector", "The local vector to the glowpoint or nil if invalid") { - glowpoint_h *glh = NULL; - vec3d newVec; + glowpoint_h *glh = nullptr; - if(!ade_get_args(L, "o|o", l_Glowpoint.GetPtr(&glh), l_Vector.Get(&newVec))) + if (!ade_get_args(L, "o", l_Glowpoint.GetPtr(&glh))) return ADE_RETURN_NIL; - if (!glh->isValid()) + auto point = glh->Get(); + if (!point) return ADE_RETURN_NIL; - vec3d vec = glh->point->pnt; + if (ADE_SETTING_VAR) + LuaError(L, "This property is read-only"); + + return ade_set_args(L, "o", l_Vector.Set(point->pnt)); +} + +ADE_VIRTVAR(Normal, l_Glowpoint, nullptr, "The normal of the glowpoint", "vector", "The normal of the glowpoint or nil if invalid") +{ + glowpoint_h *glh = nullptr; + + if (!ade_get_args(L, "o", l_Glowpoint.GetPtr(&glh))) + return ADE_RETURN_NIL; + + auto point = glh->Get(); + if (!point) + return ADE_RETURN_NIL; if (ADE_SETTING_VAR) - { - glh->point->pnt = newVec; - } + LuaError(L, "This property is read-only"); - return ade_set_args(L, "o", l_Vector.Set(vec)); + return ade_set_args(L, "o", l_Vector.Set(point->norm)); } ADE_VIRTVAR(Radius, l_Glowpoint, nullptr, "The radius of the glowpoint", "number", "The radius of the glowpoint or -1 if invalid") { - glowpoint_h* glh = NULL; - float newVal; + glowpoint_h* glh = nullptr; - if(!ade_get_args(L, "o|f", l_Glowpoint.GetPtr(&glh), &newVal)) + if (!ade_get_args(L, "o", l_Glowpoint.GetPtr(&glh))) return ade_set_error(L, "f", -1.0f); - if (!glh->isValid()) + auto point = glh->Get(); + if (!point) return ade_set_error(L, "f", -1.0f); - float radius = glh->point->radius; - if (ADE_SETTING_VAR) - { - glh->point->radius = newVal; - } + LuaError(L, "This property is read-only"); - return ade_set_args(L, "f", radius); + return ade_set_args(L, "f", point->radius); } ADE_FUNC(isValid, l_Glowpoint, NULL, "Returns whether this handle is valid or not", "boolean", "True if handle is valid, false otherwise") { - glowpoint_h glh = NULL; + glowpoint_h glh; if(!ade_get_args(L, "o", l_Glowpoint.Get(&glh))) return ADE_RETURN_FALSE; @@ -947,16 +1066,17 @@ ADE_OBJ(l_ModelDockingbays, model_h, "dockingbays", "The docking bays of a model ADE_INDEXER(l_ModelDockingbays, "dockingbay", "Gets a dockingbay handle from this model. If a string is given then a dockingbay with that name is searched.", "dockingbay", "Handle or invalid handle on error") { - model_h *dbhp = NULL; + model_h *dbhp = nullptr; int index = -1; - dockingbay_h *newVal = NULL; + polymodel *pm = nullptr; if (lua_isnumber(L, 2)) { - if (!ade_get_args(L, "oi|o", l_ModelDockingbays.GetPtr(&dbhp), &index, l_Dockingbay.GetPtr(&newVal))) + if (!ade_get_args(L, "oi", l_ModelDockingbays.GetPtr(&dbhp), &index)) return ade_set_error(L, "o", l_Dockingbay.Set(dockingbay_h())); - if (!dbhp->isValid()) + pm = dbhp->Get(); + if (!pm) return ade_set_error(L, "o", l_Dockingbay.Set(dockingbay_h())); index--; // Lua --> C/C++ @@ -965,23 +1085,21 @@ ADE_INDEXER(l_ModelDockingbays, "dockingbay", "Gets a dockingbay handle from thi { const char* name = nullptr; - if (!ade_get_args(L, "os|o", l_ModelDockingbays.GetPtr(&dbhp), &name, l_Dockingbay.GetPtr(&newVal))) - { + if (!ade_get_args(L, "os", l_ModelDockingbays.GetPtr(&dbhp), &name)) return ade_set_error(L, "o", l_Dockingbay.Set(dockingbay_h())); - } - if (!dbhp->isValid() && name != NULL) + pm = dbhp->Get(); + if (!pm || !name) return ade_set_error(L, "o", l_Dockingbay.Set(dockingbay_h())); - index = model_find_dock_name_index(dbhp->GetID(), name); + index = model_find_dock_name_index(pm->id, name); } - polymodel *pm = dbhp->Get(); - if (index < 0 || index >= pm->n_docks) - { return ade_set_error(L, "o", l_Dockingbay.Set(dockingbay_h())); - } + + if (ADE_SETTING_VAR) + LuaError(L, "Assigning docking bays is not supported"); return ade_set_args(L, "o", l_Dockingbay.Set(dockingbay_h(pm, index))); } diff --git a/code/scripting/api/objs/model.h b/code/scripting/api/objs/model.h index 993f54eb23e..57d6ea31261 100644 --- a/code/scripting/api/objs/model.h +++ b/code/scripting/api/objs/model.h @@ -58,11 +58,11 @@ DECLARE_ADE_OBJ(l_ModelThrusters, model_h); // Thrusterbank: struct thrusterbank_h { - thruster_bank *bank; + model_h modelh; + int thrusterbank_index; thrusterbank_h(); - - thrusterbank_h(thruster_bank* ba); + thrusterbank_h(int in_model_num, int in_thrusterbank_index); thruster_bank *Get() const; @@ -70,19 +70,37 @@ struct thrusterbank_h }; DECLARE_ADE_OBJ(l_Thrusterbank, thrusterbank_h); +// Glow points: +DECLARE_ADE_OBJ(l_ModelGlowpointbanks, model_h); + +// Glowpointbank: +struct glowpointbank_h +{ + model_h modelh; + int glowpointbank_index; + + glowpointbank_h(); + glowpointbank_h(int in_model_num, int in_glowpointbank_index); + + glow_point_bank *Get() const; + + bool isValid() const; +}; +DECLARE_ADE_OBJ(l_Glowpointbank, glowpointbank_h); + // Glowpoint: struct glowpoint_h { - glow_point *point; + glowpointbank_h glowpointbankh; + thrusterbank_h thrusterbankh; + int glowpoint_index; glowpoint_h(); - - glowpoint_h(glow_point* np); + glowpoint_h(int in_model_num, int in_glowpointbank_index, int in_thrusterbank_index, int in_glowpoint_index); glow_point* Get() const; bool isValid() const; - }; DECLARE_ADE_OBJ(l_Glowpoint, glowpoint_h); @@ -99,10 +117,9 @@ class dockingbay_h dockingbay_h(polymodel *pm, int dock_idx); dockingbay_h(); - bool isValid() const; - - model_h* getModelH() const; dock_bay* getDockingBay() const; + + bool isValid() const; }; DECLARE_ADE_OBJ(l_Dockingbay, dockingbay_h); From a390b5a01ae2532e16e8d0bb16f1a15f3e120188 Mon Sep 17 00:00:00 2001 From: John Fernandez Date: Tue, 18 Jun 2024 14:10:01 -0400 Subject: [PATCH 59/63] Generalize linear interpolation (#5003) * Remove old interpolate angles quick * Remove old interpolate functions These are no longer used. * Add linear interpolate vm function More clearing functions * Add physics snapshot struct to silo interpolation Two of these two functions require an object pointer, so they cannot be in physics_state.cpp With doxygen documentation * Try to placate Linux * Address Feedback * And update declaration, of course * Attempt to appease linter --- code/math/vecmat.cpp | 72 ++++--------------------- code/math/vecmat.h | 11 ++-- code/model/model.h | 3 +- code/network/multi_ingame.cpp | 2 +- code/network/multi_interpolate.cpp | 87 ++++++++++++------------------ code/network/multi_interpolate.h | 31 ++++++----- code/network/multi_obj.cpp | 32 +++++------ code/network/multi_obj.h | 1 - code/object/collideshipship.cpp | 5 +- code/object/object.cpp | 34 +++++++++++- code/object/object.h | 33 ++++++++++-- code/object/waypoint.cpp | 1 + code/physics/physics_state.cpp | 44 +++++++++++++++ code/physics/physics_state.h | 82 ++++++++++++++++++++++++++++ code/ship/ship.cpp | 3 +- code/source_groups.cmake | 2 + freespace2/freespace.cpp | 2 + 17 files changed, 284 insertions(+), 161 deletions(-) create mode 100644 code/physics/physics_state.cpp create mode 100644 code/physics/physics_state.h diff --git a/code/math/vecmat.cpp b/code/math/vecmat.cpp index a1b0f4899e8..679d86bab8d 100644 --- a/code/math/vecmat.cpp +++ b/code/math/vecmat.cpp @@ -417,6 +417,16 @@ void vm_vec_scale2(vec3d *dest, float n, float d) dest->xyz.z = dest->xyz.z* n * d; } +// interpolate between two vectors +// dest = src0 + (k * (src1 - src0)) +// Might be helpful to think of vec0 as the before, and vec1 as the after +void vm_vec_linear_interpolate(vec3d* dest, const vec3d* src0, const vec3d* src1, const float k) +{ + dest->xyz.x = ((src1->xyz.x - src0->xyz.x) * k) + src0->xyz.x; + dest->xyz.y = ((src1->xyz.y - src0->xyz.y) * k) + src0->xyz.y; + dest->xyz.z = ((src1->xyz.z - src0->xyz.z) * k) + src0->xyz.z; +} + //returns dot product of 2 vectors float vm_vec_dot(const vec3d *v0, const vec3d *v1) { @@ -2931,68 +2941,6 @@ void vm_match_bank(vec3d* out_rvec, const vec3d* goal_fvec, const matrix* match_ vm_vec_unrotate(out_rvec, &temp, &dest_frame); } -// Cyborg17 - Rotational interpolation between two angle structs in radians. Assumes that the rotation direction is the smaller arc difference. -// src0 is the starting angle struct, src1 is the ending angle struct, interp_perc must be a float between 0.0f and 1.0f inclusive. -// rot_vel is only used to determine the rotation direction. This functions assumes a <= 2PI rotation in any axis. -// You will get inaccurate results otherwise. -void vm_interpolate_angles_quick(angles *dest0, angles *src0, angles *src1, float interp_perc) { - - Assertion((interp_perc >= 0.0f) && (interp_perc <= 1.0f), "Interpolation percentage, %f, sent to vm_interpolate_angles is invalid. The valid range is [0,1], go find a coder!", interp_perc); - - angles arc_measures; - - arc_measures.p = src1->p - src0->p; - arc_measures.h = src1->h - src0->h; - arc_measures.b = src1->b - src0->b; - - // pitch - // if start and end are basically the same, assume we can basically jump to the end. - if ( (fabs(arc_measures.p) < 0.00001f) ) { - arc_measures.p = 0.0f; - - } // Test if we actually need to go in the other direction - else if (arc_measures.p > (PI*1.5f)) { - arc_measures.p = PI2 - arc_measures.p; - - } // Test if we actually need to go in the other direction for negative values - else if (arc_measures.p < -PI_2) { - arc_measures.p = -PI2 - arc_measures.p; - } - - // heading - // if start and end are basically the same, assume we can basically jump to the end. - if ( (fabs(arc_measures.h) < 0.00001f) ) { - arc_measures.h = 0.0f; - - } // Test if we actually need to go in the other direction - else if (arc_measures.h > (PI*1.5f)) { - arc_measures.h = PI2 - arc_measures.h; - - } // Test if we actually need to go in the other direction for negative values - else if (arc_measures.h < -PI_2) { - arc_measures.h = -PI2 - arc_measures.h; - } - - // bank - // if start and end are basically the same, assume we can basically jump to the end. - if ( (fabs(arc_measures.b) < 0.00001f) ) { - arc_measures.b = 0.0f; - - } // Test if we actually need to go in the other direction - else if (arc_measures.b > (PI*1.5f)) { - arc_measures.b = PI2 - arc_measures.b; - - } // Test if we actually need to go in the other direction for negative values - else if (arc_measures.b < -PI_2) { - arc_measures.b = -PI2 - arc_measures.b; - } - - // Now just multiply the difference in angles by the given percentage, and then subtract it from the destination angles. - // If arc_measures is 0.0f, then we are basically bashing to the ending orientation without worrying about the inbetween. - dest0->p = src0->p + (arc_measures.p * interp_perc); - dest0->h = src0->h + (arc_measures.h * interp_perc); - dest0->b = src0->b + (arc_measures.b * interp_perc); -} // Interpolate between two matrices, using t as a percentage progress between them. // Intended values for t are [0.0f, 1.0f], but values outside this range are allowed, diff --git a/code/math/vecmat.h b/code/math/vecmat.h index 543211a9a2e..6b752364d54 100755 --- a/code/math/vecmat.h +++ b/code/math/vecmat.h @@ -179,6 +179,11 @@ void vm_vec_scale_add2(vec3d *dest, const vec3d *src, float k); //dest *= n/d void vm_vec_scale2(vec3d *dest, float n, float d); +// interpolate between two vectors +// dest = k * (src1 - src0) +// Might be helpful to think of vec0 as the before, and vec1 as the after +void vm_vec_linear_interpolate(vec3d* dest, const vec3d* src0, const vec3d* src1, float k); + bool vm_vec_equal(const vec2d &self, const vec2d &other); bool vm_vec_equal(const vec3d &self, const vec3d &other); @@ -595,12 +600,6 @@ vec4 vm_vec3_to_ve4(const vec3d& vec, float w = 1.0f); // calculates the best rvec to match another orient while maintaining a given fvec void vm_match_bank(vec3d* out_rvec, const vec3d* goal_fvec, const matrix* match_orient); -// Cyborg17 - Rotational interpolation between two angle structs in radians, given a rotational velocity, in radians. -// src0 is the starting angle struct, src1 is the ending angle struct, interp_perc must be a float between 0.0f and 1.0f. -// rot_vel is only used to determine the rotation direction. Assumes that it is not a full 2PI rotation in any axis. -// You will get strange results otherwise. -void vm_interpolate_angles_quick(angles* dest0, angles* src0, angles* src1, float interp_perc); - // Interpolate between two matrices, using t as a percentage progress between them. // Intended values for t are [0.0f, 1.0f], but values outside this range are allowed, // as you could conceivably use these calculations to find a rotation that is outside diff --git a/code/model/model.h b/code/model/model.h index db6414063d6..a26d0844e28 100644 --- a/code/model/model.h +++ b/code/model/model.h @@ -14,7 +14,8 @@ #include "globalincs/globals.h" // for NAME_LENGTH #include "globalincs/pstypes.h" - +#include + #include "actions/Program.h" #include "gamesnd/gamesnd.h" #include "graphics/2d.h" diff --git a/code/network/multi_ingame.cpp b/code/network/multi_ingame.cpp index 630e3c049da..5007b873f3f 100644 --- a/code/network/multi_ingame.cpp +++ b/code/network/multi_ingame.cpp @@ -44,7 +44,7 @@ #include "io/timer.h" #include "playerman/player.h" #include "network/multi_log.h" - +#include "network/multi_time_manager.h" // -------------------------------------------------------------------------------------------------- // DAVE's BIGASS INGAME JOIN WARNING/DISCLAIMER diff --git a/code/network/multi_interpolate.cpp b/code/network/multi_interpolate.cpp index 974da2720aa..f8fa9416330 100644 --- a/code/network/multi_interpolate.cpp +++ b/code/network/multi_interpolate.cpp @@ -1,7 +1,8 @@ #include "network/multi_interpolate.h" -#include "globalincs/pstypes.h" #include "freespace.h" +SCP_unordered_map Interp_info; + extern void multi_ship_record_signal_update(int objnum, TIMESTAMP lower_time_limit, TIMESTAMP upper_time_limit, int prev_packet_index, int current_packet_index); /////////////////////////////////////////// // interpolation info management functions @@ -38,6 +39,11 @@ void interpolation_manager::reassess_packet_index(vec3d* pos, matrix* ori, physi _simulation_mode = true; } +void interpolate_main_helper(int objnum, vec3d* pos, matrix* ori, physics_info* pip, vec3d* last_pos, matrix* last_orient, vec3d* gravity, bool player_ship) +{ + Interp_info[objnum].interpolate_main(pos, ori, pip, last_pos, last_orient, gravity, player_ship); +} + // the meat and potatoes. Basically, this figures out if we should interpolate, and then interpolates or sims void interpolation_manager::interpolate_main(vec3d* pos, matrix* ori, physics_info* pip, vec3d* last_pos, matrix* last_orient, vec3d * gravity, bool player_ship) { @@ -70,12 +76,7 @@ void interpolation_manager::interpolate_main(vec3d* pos, matrix* ori, physics_in // we need to push this ship up to the limit of where we were on the remote instance, if we haven't already. // then we need to adjust our timing since some of the sim time is used up getting to that last packet. if (!_packets_expended && !_packets.empty()) { - *pos = _packets.front().position; - *ori = _packets.front().orientation; - pip->vel = _packets.front().velocity; - pip->desired_vel = _packets.front().desired_velocity; - pip->rotvel = _packets.front().rotational_velocity; - pip->desired_rotvel = _packets.front().desired_rotational_velocity; + physics_apply_snapshot_manual(*pos, *ori, pip->vel, pip->desired_vel, pip->rotvel, pip->desired_rotvel, _packets.front().snapshot); sim_time -= (static_cast(_packets.front().remote_missiontime) - static_cast(Multi_Timing_Info.get_last_time())) / TIMESTAMP_FREQUENCY; _packets_expended = true; @@ -107,7 +108,7 @@ void interpolation_manager::interpolate_main(vec3d* pos, matrix* ori, physics_in pip->speed = vm_vec_mag(&pip->vel); pip->fspeed = vm_vec_dot(&ori->vec.fvec, &pip->vel); - return; // we should not try interpolating and siming, so return. + return; // we should not try interpolating and siming on the same call, so return. } // calc what the current timing should be. @@ -123,41 +124,22 @@ void interpolation_manager::interpolate_main(vec3d* pos, matrix* ori, physics_in CLAMP(scale, 0.001f, 0.999f); // one by one interpolate the vectors to get the desired results. - vec3d temp_vector; - - // set new position. - vm_vec_sub(&temp_vector, &_packets[_upcoming_packet_index].position, &_packets[_prev_packet_index].position); - vm_vec_scale_add(pos, &_packets[_prev_packet_index].position, &temp_vector, scale); + physics_snapshot temp_state; - // set new velocity - vm_vec_sub(&temp_vector, &_packets[_upcoming_packet_index].velocity, &_packets[_prev_packet_index].velocity); - vm_vec_scale_add(&pip->vel, &_packets[_prev_packet_index].velocity, &temp_vector, scale); + // Interpolation in just two lines! Who'da thunk? + physics_interpolate_snapshots(temp_state, _packets[_prev_packet_index].snapshot, _packets[_upcoming_packet_index].snapshot, scale); + physics_apply_snapshot_manual(*pos, *ori, pip->vel, pip->desired_vel, pip->rotvel, pip->desired_rotvel, temp_state); // we can't trust what the last position was on the local instance, so figure out what it should have been // use flFrametime here because we need to know what the last position would have been if it was accurate in the last frame. vm_vec_scale_add(last_pos, pos, &pip->vel, -flFrametime); - // set new desired velocity - vm_vec_sub(&temp_vector, &_packets[_upcoming_packet_index].desired_velocity, &_packets[_prev_packet_index].desired_velocity); - vm_vec_scale_add(&pip->desired_vel, &_packets[_prev_packet_index].desired_velocity, &temp_vector, scale); - - // set new rotational velocity - vm_vec_sub(&temp_vector, &_packets[_upcoming_packet_index].rotational_velocity, &_packets[_prev_packet_index].rotational_velocity); - vm_vec_scale_add(&pip->rotvel, &_packets[_prev_packet_index].rotational_velocity, &temp_vector, scale); - - // we only do desired rotational velocity if this is a player ship. - if (player_ship) { - vm_vec_sub(&temp_vector, &_packets[_upcoming_packet_index].desired_rotational_velocity, &_packets[_prev_packet_index].desired_rotational_velocity); - vm_vec_scale_add(&pip->desired_rotvel, &_packets[_prev_packet_index].desired_rotational_velocity, &temp_vector, scale); - } // So if AI, just set them to the same value. - else { + // AI ships do not really use desired velocity, so undo that calc for AI ships. + if (!player_ship) { pip->desired_rotvel = pip->rotvel; } - // calculate the new orientation. - vm_interpolate_matrices(ori, &_packets[_prev_packet_index].orientation, &_packets[_upcoming_packet_index].orientation, scale); - - // a quick calculation for the last orientation, courtesy Asteroth + // finally, a quick calculation for the last orientation, courtesy Asteroth if (!IS_VEC_NULL(&pip->rotvel)) { vec3d normalized_rotvel; @@ -177,7 +159,7 @@ void interpolation_manager::interpolate_main(vec3d* pos, matrix* ori, physics_in } // correct the ship record for player ships when an up to date packet comes in. -void interpolation_manager::reinterpolate_previous(TIMESTAMP stamp, int prev_packet_index, int next_packet_index, vec3d* position, matrix* orientation, vec3d* velocity, vec3d* rotational_velocity) +void interpolation_manager::reinterpolate_previous(TIMESTAMP stamp, int prev_packet_index, int next_packet_index, vec3d& position, matrix& orientation, vec3d& velocity, vec3d& rotational_velocity) { // calc what the timing was previously. float numerator = static_cast(_packets[next_packet_index].remote_missiontime) - static_cast(stamp.value()); @@ -190,19 +172,10 @@ void interpolation_manager::reinterpolate_previous(TIMESTAMP stamp, int prev_pac // protect against bad floating point arithmetic making orientation or position look off CLAMP(scale, 0.001f, 0.999f); - // calc the corrected physics data - vec3d temp_vector; - - vm_vec_sub(&temp_vector, &_packets[next_packet_index].position, &_packets[prev_packet_index].position); - vm_vec_scale_add(position, &_packets[prev_packet_index].position, &temp_vector, scale); + physics_snapshot temp_snapshot; - vm_vec_sub(&temp_vector, &_packets[next_packet_index].velocity, &_packets[prev_packet_index].velocity); - vm_vec_scale_add(velocity, &_packets[prev_packet_index].velocity, &temp_vector, scale); - - vm_vec_sub(&temp_vector, &_packets[next_packet_index].rotational_velocity, &_packets[prev_packet_index].rotational_velocity); - vm_vec_scale_add(rotational_velocity, &_packets[prev_packet_index].rotational_velocity, &temp_vector, scale); - - vm_interpolate_matrices(orientation, &_packets[prev_packet_index].orientation, &_packets[next_packet_index].orientation, scale); + physics_interpolate_snapshots(temp_snapshot, _packets[_prev_packet_index].snapshot, _packets[_upcoming_packet_index].snapshot, scale); + physics_apply_snapshot_manual(position, orientation, velocity, rotational_velocity, temp_snapshot); } // add a packet to the vector, remove the last one if necessary. @@ -273,10 +246,18 @@ void interpolation_manager::replace_packet(int index, vec3d* pos, matrix* orient _packets[index].frame = _packets[index - 1].frame - 1; _packets[index].remote_missiontime = Multi_Timing_Info.get_last_time(); - _packets[index].position = *pos; - _packets[index].velocity = pip->vel; - _packets[index].desired_velocity = pip->desired_vel; - _packets[index].rotational_velocity = pip->rotvel; - _packets[index].desired_rotational_velocity = pip->desired_rotvel; - _packets[index].orientation = *orient; + + physics_populate_snapshot_manual(_packets[index].snapshot, *pos, *orient, pip->vel, pip->desired_vel, pip->rotvel, pip->desired_rotvel); +} + +// the contained vectors have been cleared during object shut down. +void multi_interpolate_clear_all() +{ + // clear the main container. + Interp_info.clear(); +} + +// helper functiont that helps avoid include issues. +void multi_interpolate_clear_helper(int objnum) { + Interp_info[objnum].clean_up(); } diff --git a/code/network/multi_interpolate.h b/code/network/multi_interpolate.h index 139bae1d5cf..e600f02fb9a 100644 --- a/code/network/multi_interpolate.h +++ b/code/network/multi_interpolate.h @@ -1,6 +1,8 @@ #pragma once +#include "globalincs/pstypes.h" #include "network/multi_time_manager.h" +#include "physics/physics_state.h" struct physics_info; @@ -10,12 +12,7 @@ typedef struct packet_info { int frame; // this allows us to directly compare one packet to another. int remote_missiontime; // the remote timestamp that matches this packet. - vec3d position; // what it says on the tin - vec3d velocity; // what it says on the tin - vec3d rotational_velocity; // what it says on the tin - vec3d desired_velocity; // what it says on the tin - vec3d desired_rotational_velocity; // this one is only actually from the packet when we are dealing with a player ship. - matrix orientation; // the orientation as transmitted by the other instance + physics_snapshot snapshot; // the received physics info translated into the physics snapshot type for easy interpolation packet_info(int frame_in = 0, int time_in = 0, const vec3d* position_in = &vmd_zero_vector, const vec3d* velocity_in = &vmd_zero_vector, const vec3d* rotational_velocity_in = &vmd_zero_vector, const vec3d* desired_velocity_in = &vmd_zero_vector, const vec3d* desired_rotational_velocity_in = &vmd_zero_vector, @@ -23,12 +20,12 @@ typedef struct packet_info { { frame = frame_in; remote_missiontime = time_in; - position = *position_in; - velocity = *velocity_in; - rotational_velocity = *rotational_velocity_in; - desired_velocity = *desired_velocity_in; - desired_rotational_velocity = *desired_rotational_velocity_in; - vm_angles_2_matrix(&orientation, angles_in); + snapshot.position = *position_in; + snapshot.velocity = *velocity_in; + snapshot.rotational_velocity = *rotational_velocity_in; + snapshot.desired_velocity = *desired_velocity_in; + snapshot.desired_rotational_velocity = *desired_rotational_velocity_in; + vm_angles_2_matrix(&snapshot.orientation, angles_in); } } packet_info; @@ -60,7 +57,7 @@ class interpolation_manager { // adds a new packet, whilst also manually sorting the relevant entries void add_packet(int objnum, int frame, int time_delta, vec3d* position, vec3d* velocity, vec3d* rotational_velocity, vec3d* desired_velocity, vec3d* desired_rotational_velocity, angles* angles, int player_index); void interpolate_main(vec3d* pos, matrix* ori, physics_info* pip, vec3d* last_pos, matrix* last_orient, vec3d* gravity, bool player_ship); - void reinterpolate_previous(TIMESTAMP stamp, int prev_packet_index, int next_packet_index, vec3d* position, matrix* orientation, vec3d* velocity, vec3d* rotational_velocity); + void reinterpolate_previous(TIMESTAMP stamp, int prev_packet_index, int next_packet_index, vec3d& position, matrix& orientation, vec3d& velocity, vec3d& rotational_velocity); int get_hull_comparison_frame() { return _hull_comparison_frame; } int get_shields_comparison_frame() { return _shields_comparison_frame; } @@ -156,3 +153,11 @@ class interpolation_manager { _source_player_index = -1; } }; + +void multi_interpolate_clear_all(); + +void multi_interpolate_clear_helper(int objnum); + +void interpolate_main_helper(int objnum, vec3d* pos, matrix* ori, physics_info* pip, vec3d* last_pos, matrix* last_orient, vec3d* gravity, bool player_ship); + +extern SCP_unordered_map Interp_info; diff --git a/code/network/multi_obj.cpp b/code/network/multi_obj.cpp index b7e494d78ab..7749af56faf 100644 --- a/code/network/multi_obj.cpp +++ b/code/network/multi_obj.cpp @@ -314,7 +314,7 @@ void multi_rollback_ship_record_add_ship(int obj_num) if (objp->type == OBJ_SHIP) { int subsystem_count = Ship_info[Ships[objp->instance].ship_info_index].n_subsystems; - objp->interp_info.reset(subsystem_count); + Interp_info[obj_num].reset(subsystem_count); } // if we're right where we should be. @@ -915,9 +915,9 @@ void multi_ship_record_signal_update(int objnum, TIMESTAMP lower_time_limit, TIM // now that we have valid values, we need to fix the affected values in the record. do { - Objects[objnum].interp_info.reinterpolate_previous( + Interp_info[objnum].reinterpolate_previous( Oo_info.timestamps[prev_index], prev_packet_index, current_packet_index, - &info->positions[prev_index], &info->orientations[prev_index], &info->velocities[prev_index], &info->rotational_velocities[prev_index] + info->positions[prev_index], info->orientations[prev_index], info->velocities[prev_index], info->rotational_velocities[prev_index] ); ++prev_index; @@ -981,7 +981,7 @@ void multi_oo_respawn_reset_info(object* objp) // To ensure clean interpolation, we should probably just reset everything. int subsystem_count = Ship_info[Ships[objp->instance].ship_info_index].n_subsystems; - objp->interp_info.reset(subsystem_count); + Interp_info[OBJ_INDEX(objp)].reset(subsystem_count); } // --------------------------------------------------------------------------------------------------- @@ -1797,6 +1797,8 @@ int multi_oo_unpack_data(net_player* pl, ubyte* data, int seq_num, int time_delt return offset; } + int objnum = OBJ_INDEX(pobjp); + // ship pointer shipp = &Ships[pobjp->instance]; sip = &Ship_info[shipp->ship_info_index]; @@ -1864,7 +1866,7 @@ int multi_oo_unpack_data(net_player* pl, ubyte* data, int seq_num, int time_delt new_phys_info.desired_rotvel = new_phys_info.rotvel; } - pobjp->interp_info.add_packet(OBJ_INDEX(pobjp), seq_num, time_delta, &new_pos, &new_phys_info.vel, &new_phys_info.rotvel, &new_phys_info.desired_vel, &new_phys_info.desired_rotvel, &new_angles, pl->player_id); + Interp_info[objnum].add_packet(objnum, seq_num, time_delta, &new_pos, &new_phys_info.vel, &new_phys_info.rotvel, &new_phys_info.desired_vel, &new_phys_info.desired_rotvel, &new_angles, pl->player_id); } // Packet processing needs to stop here if the ship is still arriving, leaving, dead or dying to prevent bugs. @@ -1881,9 +1883,9 @@ int multi_oo_unpack_data(net_player* pl, ubyte* data, int seq_num, int time_delt // hull info if ( oo_flags & OO_HULL_NEW ){ UNPACK_PERCENT(fpct); - if (seq_num > pobjp->interp_info.get_hull_comparison_frame()) { + if (seq_num > Interp_info[objnum].get_hull_comparison_frame()) { pobjp->hull_strength = fpct * Ships[pobjp->instance].ship_max_hull_strength; - pobjp->interp_info.set_hull_comparison_frame(seq_num); + Interp_info[objnum].set_hull_comparison_frame(seq_num); } } @@ -1892,12 +1894,12 @@ int multi_oo_unpack_data(net_player* pl, ubyte* data, int seq_num, int time_delt float quad = shield_get_max_quad(pobjp); // check before unpacking here so we don't have to recheck for each quadrant. - if (seq_num > pobjp->interp_info.get_shields_comparison_frame()) { + if (seq_num > Interp_info[objnum].get_shields_comparison_frame()) { for (int i = 0; i < pobjp->n_quadrants; i++) { UNPACK_PERCENT(fpct); pobjp->shield_quadrant[i] = fpct * quad; } - pobjp->interp_info.set_shields_comparison_frame(seq_num); + Interp_info[objnum].set_shields_comparison_frame(seq_num); } else { for (int i = 0; i < pobjp->n_quadrants; i++) { @@ -1937,8 +1939,8 @@ int multi_oo_unpack_data(net_player* pl, ubyte* data, int seq_num, int time_delt // update health if (flags[i] & OO_SUBSYS_HEALTH) { - if (seq_num > pobjp->interp_info.get_subsystem_health_frame(i)) { - pobjp->interp_info.set_subsystem_health_frame(i, seq_num); + if (seq_num > Interp_info[objnum].get_subsystem_health_frame(i)) { + Interp_info[objnum].set_subsystem_health_frame(i, seq_num); subsysp->current_hits = subsys_data[data_idx] * subsysp->max_hits; // Aggregate if necessary. @@ -1954,9 +1956,9 @@ int multi_oo_unpack_data(net_player* pl, ubyte* data, int seq_num, int time_delt bool animations_valid = false; - if (seq_num > pobjp->interp_info.get_subsystem_animation_frame(i)) { + if (seq_num > Interp_info[objnum].get_subsystem_animation_frame(i)) { animations_valid = true; - pobjp->interp_info.set_subsystem_animation_frame(i, seq_num); + Interp_info[objnum].set_subsystem_animation_frame(i, seq_num); } angles prev_angs_1 = vmd_zero_angles; @@ -2097,7 +2099,7 @@ int multi_oo_unpack_data(net_player* pl, ubyte* data, int seq_num, int time_delt float weapon_energy_pct; UNPACK_PERCENT(weapon_energy_pct); - if( seq_num > pobjp->interp_info.get_ai_comparison_frame() ){ + if( seq_num > Interp_info[objnum].get_ai_comparison_frame() ){ if ( shipp->ai_index >= 0 ){ // make sure to undo the wrap if it occurred during compression for unset ai mode. if (umode == 255) { @@ -2137,7 +2139,7 @@ int multi_oo_unpack_data(net_player* pl, ubyte* data, int seq_num, int time_delt shipp->weapon_energy = sip->max_weapon_reserve * weapon_energy_pct; - pobjp->interp_info.set_ai_comparison_frame(seq_num); + Interp_info[objnum].set_ai_comparison_frame(seq_num); } } diff --git a/code/network/multi_obj.h b/code/network/multi_obj.h index 26f133065de..ed043f05023 100644 --- a/code/network/multi_obj.h +++ b/code/network/multi_obj.h @@ -17,7 +17,6 @@ // #include "globalincs/pstypes.h" -struct interp_info; class object; struct header; struct net_player; diff --git a/code/object/collideshipship.cpp b/code/object/collideshipship.cpp index 8b20004b985..c35b3369c4e 100644 --- a/code/object/collideshipship.cpp +++ b/code/object/collideshipship.cpp @@ -20,6 +20,7 @@ #include "io/joy_ff.h" #include "io/timer.h" #include "network/multi.h" +#include "network/multi_interpolate.h" #include "object/objcollide.h" #include "object/object.h" #include "object/objectdock.h" @@ -1397,8 +1398,10 @@ int collide_ship_ship( obj_pair * pair ) if ((LightOne == &Objects[current_player.m_player->objnum]) || (HeavyOne == &Objects[current_player.m_player->objnum])) { // finally if the host is also a player, ignore making these adjustments for him because he is in a pure simulation. if (&Ships[Objects[current_player.m_player->objnum].instance] != Player_ship) { + Assertion(Interp_info.find(current_player.m_player->objnum) != Interp_info.end(), "Somehow the collision code thinks there is not a player ship interp record in multi when there really *should* be. This is a coder mistake, please report!"); + // temp set this as an uninterpolated ship, to make the collision look more natural until the next update comes in. - Objects[current_player.m_player->objnum].interp_info.force_interpolation_mode(); + Interp_info[current_player.m_player->objnum].force_interpolation_mode(); // check to see if it has been long enough since the last collision, if not, negate the damage if (!timestamp_elapsed(current_player.s_info.player_collision_timestamp)) { diff --git a/code/object/object.cpp b/code/object/object.cpp index a42677b5055..e09d1f5addf 100644 --- a/code/object/object.cpp +++ b/code/object/object.cpp @@ -695,8 +695,11 @@ void obj_delete(int objnum) Error( LOCATION, "Unhandled object type %d in obj_delete_all_that_should_be_dead", objp->type ); } + // this avoids include issues from physics state, multi interpolate and object code + extern void multi_interpolate_clear_helper(int objnum); + // clean up interpolation info - objp->interp_info.clean_up(); + multi_interpolate_clear_helper(objnum); // delete any dock information we still have dock_free_dock_list(objp); @@ -1583,7 +1586,9 @@ void obj_move_all(float frametime) if (!(objp->flags[Object::Object_Flags::Immobile] && objp->hull_strength > 0.0f)) { // if this is an object which should be interpolated in multiplayer, do so if (interpolation_object) { - objp->interp_info.interpolate_main(&objp->pos, &objp->orient, &objp->phys_info, &objp->last_pos, &objp->last_orient, &The_mission.gravity, objp->flags[Object::Object_Flags::Player_ship]); + extern void interpolate_main_helper(int objnum, vec3d* pos, matrix* ori, physics_info* pip, vec3d* last_pos, matrix* last_orient, vec3d* gravity, bool player_ship); + + interpolate_main_helper(OBJ_INDEX(objp), &objp->pos, &objp->orient, &objp->phys_info, &objp->last_pos, &objp->last_orient, &The_mission.gravity, objp->flags[Object::Object_Flags::Player_ship]); } else { // physics obj_move_call_physics(objp, frametime); @@ -2133,3 +2138,28 @@ bool obj_compare(object* left, object* right) { return OBJ_INDEX(left) == OBJ_INDEX(right); } + +void physics_populate_snapshot(physics_snapshot& snapshot, const object* objp) +{ + Assertion(objp != nullptr, "Bad object (nullptr) passed to physics_overwrite_snapshot, please report to the SCP!"); + + snapshot.position = objp->pos; + snapshot.orientation = objp->orient; + snapshot.velocity = objp->phys_info.vel; + snapshot.desired_velocity = objp->phys_info.desired_vel; + snapshot.rotational_velocity = objp->phys_info.rotvel; + snapshot.desired_rotational_velocity = objp->phys_info.desired_rotvel; +} + +void physics_apply_pstate_to_object(object* objp, const physics_snapshot& source) +{ + Assertion(objp != nullptr, "Bad object passed to phsyics snapshot application code. This is a coder mistake, please report!"); + + objp->pos = source.position; + objp->orient = source.orientation; + objp->phys_info.vel = source.velocity; + objp->phys_info.desired_vel = source.desired_velocity; + objp->phys_info.rotvel = source.rotational_velocity; + objp->phys_info.desired_rotvel = source.desired_rotational_velocity; +} + diff --git a/code/object/object.h b/code/object/object.h index 870e1f197d2..43701fd012a 100644 --- a/code/object/object.h +++ b/code/object/object.h @@ -18,8 +18,9 @@ #include "object/object.h" #include "object/object_flags.h" #include "physics/physics.h" +#include "physics/physics_state.h" +#include "io/timer.h" // prevents some include issues with files in the actions folder #include "utils/event.h" -#include "network/multi_interpolate.h" #include @@ -154,8 +155,6 @@ class object util::event pre_move_event; util::event post_move_event; - interpolation_manager interp_info; - object(); ~object(); void clear(); @@ -319,8 +318,6 @@ void obj_move_all_post(object *objp, float frametime); void obj_move_call_physics(object *objp, float frametime); -// multiplayer object update stuff begins ------------------------------------------- - // move an observer object in multiplayer void obj_observer_move(float frame_time); @@ -378,4 +375,30 @@ void obj_render_queue_all(); */ bool obj_compare(object *left, object *right); +//////////////////////////////////////////////////////////// +// physics_state api functions that require the object type + +/** + * @brief Populate a physics snapshot directly from the info in an object + * + * @param[in,out] snapshot Destination physics snapshot + * @param[in] objp The object pointer that we are pulling information from + * + * @author J Fernandez + */ +void physics_populate_snapshot(physics_snapshot& snapshot, const object* objp); + +/** + * @brief Change the object's physics info to match the info contained in a snapshot. + * + * @param[in,out] objp Destination object pointer + * @param[in] source The physics snapshot we are pulling information from + * + * @details To be used when interpolating or restoring a game state. + * + * @author J Fernandez + */ +void physics_apply_pstate_to_object(object* objp, const physics_snapshot& source); + + #endif diff --git a/code/object/waypoint.cpp b/code/object/waypoint.cpp index 1e077699b57..f02f1fcc370 100644 --- a/code/object/waypoint.cpp +++ b/code/object/waypoint.cpp @@ -5,6 +5,7 @@ #include "object/object.h" #include "object/waypoint.h" #include "network/multiutil.h" +#include //********************GLOBALS******************** SCP_vector Waypoint_lists; diff --git a/code/physics/physics_state.cpp b/code/physics/physics_state.cpp new file mode 100644 index 00000000000..35dbd6e3943 --- /dev/null +++ b/code/physics/physics_state.cpp @@ -0,0 +1,44 @@ +#include "physics/physics_state.h" + +// There are additional functions in object.cpp + +void physics_interpolate_snapshots(physics_snapshot& result, const physics_snapshot& before, const physics_snapshot& after, const float percent) +{ + // just interpolate each item in turn. + vm_vec_linear_interpolate(&result.position, &before.position, &after.position, percent); + vm_vec_linear_interpolate(&result.velocity, &before.velocity, &after.velocity, percent); + vm_vec_linear_interpolate(&result.desired_velocity, &before.desired_velocity, &after.desired_velocity, percent); + vm_vec_linear_interpolate(&result.rotational_velocity, &before.rotational_velocity, &after.rotational_velocity, percent); + vm_vec_linear_interpolate(&result.desired_rotational_velocity, &before.desired_rotational_velocity, &after.desired_rotational_velocity, percent); + vm_interpolate_matrices(&result.orientation, &before.orientation, &after.orientation, percent); +} + +void physics_apply_snapshot_manual(vec3d& position, matrix& orient, vec3d& velocity, vec3d& desired_velocity, const physics_snapshot& source) +{ + position = source.position; + orient = source.orientation; + velocity = source.velocity; + desired_velocity = source.desired_velocity; +} + +void physics_apply_snapshot_manual(vec3d& position, matrix& orient, vec3d& velocity, vec3d& desired_velocity, + vec3d& rotational_velocity, vec3d& desired_rotational_velocity, const physics_snapshot& source) +{ + position = source.position; + orient = source.orientation; + velocity = source.velocity; + desired_velocity = source.desired_velocity; + rotational_velocity = source.rotational_velocity; + desired_rotational_velocity = source.desired_rotational_velocity; +} + +void physics_populate_snapshot_manual(physics_snapshot& destination, const vec3d& position, const matrix& orient, const vec3d& velocity, const vec3d& desired_velocity, + const vec3d& rotational_velocity, const vec3d& desired_rotational_velocity) +{ + destination.position = position; + destination.orientation = orient; + destination.velocity = velocity; + destination.desired_velocity = desired_velocity; + destination.rotational_velocity = rotational_velocity; + destination.desired_rotational_velocity = desired_rotational_velocity; +} diff --git a/code/physics/physics_state.h b/code/physics/physics_state.h new file mode 100644 index 00000000000..4cb609eb886 --- /dev/null +++ b/code/physics/physics_state.h @@ -0,0 +1,82 @@ +#pragma once + +#include "globalincs/pstypes.h" +#include "math/vecmat.h" + +// This is designed to be a small API that generalizes the FSO physics state of an object +// and allows interpolation between two states. Timing is not handled here however. + +struct physics_snapshot { + vec3d position; // the position at that moment. + matrix orientation; // the orientation at that moment. + + vec3d velocity; // the velocity at that moment. + vec3d desired_velocity; // the desired velocity at that moment. + + vec3d rotational_velocity; // the rotational velocity at that moment. + vec3d desired_rotational_velocity; // the desired rotational velocity at that moment. +}; + +/** + * @brief Interpolate two physics snapshots and get the result + * + * @param[out] result Destination of the interpolated info + * @param[in] before The physics information that happened earlier in the simulation + * @param[in] after The physics information that happened later in the simulation + * @param[in] percent How much time has passed between the two states, in percent. + * 0.00 means use only before, 1.00 means use only after, 0.50 would interpolate halfway between them. + * + * @details This takes two physics snapshots of objects and uses linear interpolation to figure out + * where the objects would be given a specific time factor (percent "progress" towards after). + * + * @author J Fernandez + */ +void physics_interpolate_snapshots(physics_snapshot& result, const physics_snapshot& before, const physics_snapshot& after, const float percent); + +/** + * @brief Apply the contents of a physics state to manually specified variables + * + * @param[out] position Destination of the snapshot's position values + * @param[out] orient Destination of the snapshot's orientation values + * @param[out] velocity Destination of the snapshot's velocity values + * @param[out] desired_velocity Destination of the snapshot's desired velocity + * @param[in] source Physics object that we are copying the values from + * + * @details Apply a physics state to manually specified vectors and matrices. + * This overload does not include rotational velocity or desired rotational velocity. + * + * @author J Fernandez + */ +void physics_apply_snapshot_manual(vec3d& position, matrix& orient, vec3d& velocity, vec3d& desired_velocity, const physics_snapshot& source); + +/** + * @brief Apply the contents of a physics state to manually specified variables + * + * @param[out] position Destination of the snapshot's position values + * @param[out] orient Destination of the snapshot's orientation values + * @param[out] velocity Destination of the snapshot's velocity values + * @param[out] desired_velocity Destination of the snapshot's desired velocity + * @param[out] rotational_velocity Destination of the snapshot's rotational velocity + * @param[out] desired_rotaional_velocity Destination of the snapshot's desired rotational velocity + * @param[in] source Physics object that we are copying the values from + * + * @details Apply a physics state to manually specified vectors and matrices. + * + * @author J Fernandez + */ +void physics_apply_snapshot_manual(vec3d& position, matrix& orient, vec3d& velocity, vec3d& desired_velocity, vec3d& rotational_velocity, vec3d& desired_rotational_velocity, const physics_snapshot& source); + +/** + * @brief Populate a physics snapshot by specifying each input vector and matrix + * + * @param[out] destination Destination physics snapshot + * @param[in] position Position vector for populating the snapshot + * @param[in] orient Matrix orientation for populating the snapshot + * @param[in] velocity Velocity vector for populating the snapshot + * @param[in] desired_velocity The desired velocity vector for populating the snapshot + * @param[in] rotational_velocity The rotational velocity vector for populating the snapshot + * @param[in] desired_rotational_velocity The desired rotational velocity vector for populating the snapshot + * + * @author J Fernandez + */ +void physics_populate_snapshot_manual(physics_snapshot& destination, const vec3d& position, const matrix& orient, const vec3d& velocity, const vec3d& desired_velocity, const vec3d& rotational_velocity, const vec3d& desired_rotational_velocity); diff --git a/code/ship/ship.cpp b/code/ship/ship.cpp index 926e2d6d172..3b98895920e 100644 --- a/code/ship/ship.cpp +++ b/code/ship/ship.cpp @@ -59,6 +59,7 @@ #include "nebula/neb.h" #include "network/multimsgs.h" #include "network/multiutil.h" +#include "network/multi_interpolate.h" #include "object/deadobjectdock.h" #include "object/objcollide.h" #include "object/object.h" @@ -10917,7 +10918,7 @@ int ship_create(matrix* orient, vec3d* pos, int ship_type, const char* ship_name Assert( objnum >= 0 ); // Init multiplayer interpolation info - Objects[objnum].interp_info.reset(sip->n_subsystems); + Interp_info[objnum].reset(sip->n_subsystems); shipp->model_instance_num = model_create_instance(objnum, sip->model_num); diff --git a/code/source_groups.cmake b/code/source_groups.cmake index ccfefff7819..2d6988ff0bc 100644 --- a/code/source_groups.cmake +++ b/code/source_groups.cmake @@ -1143,6 +1143,8 @@ add_file_folder("PcxUtils" add_file_folder("Physics" physics/physics.cpp physics/physics.h + physics/physics_state.cpp + physics/physics_state.h ) # PilotFile files diff --git a/freespace2/freespace.cpp b/freespace2/freespace.cpp index b82c39d0f77..c379d1ced78 100644 --- a/freespace2/freespace.cpp +++ b/freespace2/freespace.cpp @@ -130,6 +130,7 @@ #include "network/multi_endgame.h" #include "network/multi_fstracker.h" #include "network/multi_ingame.h" +#include "network/multi_interpolate.h" #include "network/multi_log.h" #include "network/multi_pause.h" #include "network/multi_pxo.h" @@ -891,6 +892,7 @@ void game_level_close() // De-Initialize the game subsystems obj_delete_all(); obj_reset_colliders(); + multi_interpolate_clear_all(); // object related sexp_music_close(); // Goober5000 event_music_level_close(); game_stop_looped_sounds(); From 1b855044c76ef275bae0693304fe33137f6e047a Mon Sep 17 00:00:00 2001 From: Taylor Richards Date: Tue, 18 Jun 2024 16:39:43 -0400 Subject: [PATCH 60/63] fix data length bug in PXO hole punch packet (#6195) The header length from the tracker is 1 byte larger than the size calculated by the client due to psnet removing that byte internally. The header length is adjusted to compensate for this, but only *after* the deserialization process, so we have to use the received packet size instead. --- code/network/gtrack.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/network/gtrack.cpp b/code/network/gtrack.cpp index ee678027c3a..40c5d501204 100644 --- a/code/network/gtrack.cpp +++ b/code/network/gtrack.cpp @@ -262,7 +262,8 @@ static void DeserializeGamePacket(const ubyte *data, const int data_size, game_p } case GNT_NAT_HOLE_PUNCH_REQ: { - if (gph->len == (GAME_HEADER_ONLY_SIZE+sizeof(hole_punch_addr_ip6))) { + // using data_size here since gph.len hasn't been adjusted yet + if (data_size == (GAME_HEADER_ONLY_SIZE+sizeof(hole_punch_addr_ip6))) { auto ipv6 = reinterpret_cast(&gph->data); PXO_GET_DATA(ipv6->addr); From d11003a869c87fae57c867acf9a90da880f3c8d3 Mon Sep 17 00:00:00 2001 From: John Fernandez Date: Tue, 12 Jul 2022 20:52:28 -0400 Subject: [PATCH 61/63] Simply logic here Break when you've found a match, instead of just mindlessly comparing the rest. This is technically just an optimization and safety change, but potentially, before, if the loops had been switched, it would have caused unexpected behavior if two copies of the same flag were listed. --- code/mission/missionparse.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/code/mission/missionparse.cpp b/code/mission/missionparse.cpp index 1778800b9d2..7b0f9d7da43 100644 --- a/code/mission/missionparse.cpp +++ b/code/mission/missionparse.cpp @@ -5146,13 +5146,12 @@ void parse_event(mission * /*pm*/) stuff_string_list(buffer); for (int i = 0; i < (int)buffer.size(); i++) { - int add_flag = 1; for (int j = 0; j < MAX_MISSION_EVENT_LOG_FLAGS; j++) { if (!stricmp(buffer[i].c_str(), Mission_event_log_flags[j])) { - // bitshift add_flag so that it equals the index of the flag in Mission_event_log_flags[] - add_flag = add_flag << j; - event->mission_log_flags |= add_flag; + // add the flag to the variable, bitshifted by the index used in Mission_event_log_flags[] + event->mission_log_flags |= 1 << j; + break; } } } From 7403f4a1c90bc0e31afb9963dc49fc2734382740 Mon Sep 17 00:00:00 2001 From: BMagnu <6238428+BMagnu@users.noreply.github.com> Date: Sat, 22 Jun 2024 00:10:14 +0900 Subject: [PATCH 62/63] Improve Volumetrics Performance (#6159) * properly trace volumetrics and explicitly set texture setting * UDF-ify volumetrics * Fix warning * only apply lower bound when actually correct --- code/def_files/data/effects/volumetric-f.sdr | 20 +-- code/globalincs/pstypes.h | 8 ++ code/graphics/opengl/gropengldeferred.cpp | 10 +- code/graphics/util/uniform_structs.h | 5 +- code/nebula/volumetrics.cpp | 131 +++++++++++++++++-- code/nebula/volumetrics.h | 5 +- code/tracing/categories.cpp | 2 + code/tracing/categories.h | 2 + 8 files changed, 158 insertions(+), 25 deletions(-) diff --git a/code/def_files/data/effects/volumetric-f.sdr b/code/def_files/data/effects/volumetric-f.sdr index b7d5dc19a07..010707e6a67 100644 --- a/code/def_files/data/effects/volumetric-f.sdr +++ b/code/def_files/data/effects/volumetric-f.sdr @@ -21,9 +21,11 @@ layout (std140) uniform genericData { vec3 globalLightDiffuse; float stepsize; vec3 nebPos; - float globalstepalpha; + float opacitydistance; vec3 nebSize; float alphalim; + vec3 nebulaColor; + float udfScale; float emissiveSpreadFactor; float emissiveIntensity; float emissiveFalloff; @@ -92,12 +94,14 @@ void main() vec3 sidestep = 1.0 / vec3(textureSize(volume_tex, 0)); - for(float stept = maxtMin; stept < mintMax; stept += stepsize) { + for(float stept = maxtMin; stept < mintMax;) { //Step setup vec3 position = camera + rayDirection * stept - nebPos; vec3 sampleposition = position / nebSize + 0.5; vec4 volume_sample = textureGrad(volume_tex, sampleposition, gradX, gradY); + float stepsize_current = min(max(stepsize, volume_sample.x * udfScale), mintMax - stept); + #ifdef DO_EDGE_SMOOTHING //Average 3D texel with texels on corner, in an attempt to reduce jagged edges. float stepcolor_alpha = volume_sample.a; @@ -117,7 +121,7 @@ void main() float stepcolor_alpha = volume_sample.a; #endif - float stepalpha = globalstepalpha * stepcolor_alpha; + float stepalpha = -(pow(alphalim, 1.0 / (opacitydistance / stepsize_current)) - 1.0f) * stepcolor_alpha; //All the following computations are just required if we have a stepcoloralpha that is non-zero. if(stepcolor_alpha > 0.01) { @@ -127,10 +131,10 @@ void main() //Diffuse #ifdef NOISE //Mix the actual nebula color with the noise color depending on the product of the noise channels multiplied with intensity, smooth-clamped from 0 to 1 - vec3 stepcolor_neb = mix(volume_sample.rgb, noiseColor, + vec3 stepcolor_neb = mix(nebulaColor, noiseColor, smoothstep(0, 1, (textureGrad(noise_volume_tex, position / noiseColorScale1, gradX, gradY).r + textureGrad(noise_volume_tex, position / noiseColorScale2, gradX, gradY).g) / 2.0 * noiseIntensity)); #else - vec3 stepcolor_neb = volume_sample.rgb; + vec3 stepcolor_neb = nebulaColor; #endif vec3 stepcolor_diffuse = stepcolor_neb * henyey_greenstein(dot(rayDirection, globalLightDirection)); float directionalLightStep = 4.0 / float(directionalLightSampleSteps); @@ -145,17 +149,17 @@ void main() stepcolor_diffuse *= beer_powder_norm * (1 - exp(-directionalLightDepth * 2.0)) * exp(-directionalLightDepth); //Emissive - cumnebdist += stepcolor_alpha * stepsize; + cumnebdist += stepcolor_alpha * stepsize_current; vec3 emissive_lod = textureLod(emissive, fragTexCoord.xy, clamp(cumnebdist * emissiveSpreadFactor, 0, float(textureQueryLevels(emissive) - 1))).rgb; - vec3 stepcolor_emissive = emissive_lod.rgb * pow(1 - globalstepalpha, (depth - stept) * emissiveFalloff) * emissiveIntensity; + vec3 stepcolor_emissive = emissive_lod.rgb * pow(alphalim, 1.0 / (opacitydistance / ((depth - stept) * emissiveFalloff + 0.01))) * emissiveIntensity; //Step finish vec3 stepcolor = clamp(stepcolor_diffuse + stepcolor_emissive, 0, 1); - float stepalpha = globalstepalpha * stepcolor_alpha; cumcolor += stepalpha * cumOMAlpha * stepcolor; } cumOMAlpha *= 1.0 - stepalpha; + stept += stepsize_current; if(cumOMAlpha < alphalim) break; diff --git a/code/globalincs/pstypes.h b/code/globalincs/pstypes.h index 8a7f3a6fece..59d5a73230d 100644 --- a/code/globalincs/pstypes.h +++ b/code/globalincs/pstypes.h @@ -99,6 +99,14 @@ struct ivec2 { int x, y; }; +inline bool operator<(const ivec3& l, const ivec3& r){ + return l.x < r.x || (l.x == r.x && (l.y < r.y || (l.y == r.y && l.z < r.z))); +} + +inline bool operator<(const ivec2& l, const ivec2& r){ + return l.x < r.x || (l.x == r.x && l.y < r.y); +} + namespace scripting { class ade_table_entry; } diff --git a/code/graphics/opengl/gropengldeferred.cpp b/code/graphics/opengl/gropengldeferred.cpp index 3d46b066c7c..43b811f8940 100644 --- a/code/graphics/opengl/gropengldeferred.cpp +++ b/code/graphics/opengl/gropengldeferred.cpp @@ -525,6 +525,8 @@ void gr_opengl_deferred_lighting_finish() } else if (The_mission.volumetrics && !override_fog) { GR_DEBUG_SCOPE("Volumetric Nebulae"); + TRACE_SCOPE(tracing::Volumetrics); + const volumetric_nebula& neb = *The_mission.volumetrics; Assertion(neb.isVolumeBitmapValid(), "The volumetric nebula was not properly initialized!"); @@ -550,9 +552,11 @@ void gr_opengl_deferred_lighting_finish() uint32_t array_index; gr_set_texture_addressing(TMAP_ADDRESS_CLAMP); gr_opengl_tcache_set(neb.getVolumeBitmapHandle(), TCACHE_TYPE_3DTEX, &u_scale, &v_scale, &array_index, 3); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); if (neb.getNoiseActive()) { gr_set_texture_addressing(TMAP_ADDRESS_WRAP); gr_opengl_tcache_set(neb.getNoiseVolumeBitmapHandle(), TCACHE_TYPE_3DTEX, &u_scale, &v_scale, &array_index, 4); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } } @@ -567,8 +571,12 @@ void gr_opengl_deferred_lighting_finish() data->nebPos = neb.getPos(); data->nebSize = neb.getSize(); data->stepsize = neb.getStepsize(); - data->globalstepalpha = neb.getStepalpha(); + data->opacitydistance = neb.getOpacityDistance(); data->alphalimit = neb.getAlphaLim(); + data->nebColor[0] = std::get<0>(neb.getNebulaColor()); + data->nebColor[1] = std::get<1>(neb.getNebulaColor()); + data->nebColor[2] = std::get<2>(neb.getNebulaColor()); + data->udfScale = neb.getUDFScale(); data->emissiveSpreadFactor = neb.getEmissiveSpread(); data->emissiveIntensity = neb.getEmissiveIntensity(); data->emissiveFalloff = neb.getEmissiveFalloff(); diff --git a/code/graphics/util/uniform_structs.h b/code/graphics/util/uniform_structs.h index 9fb99b39d23..4bf861d44ab 100644 --- a/code/graphics/util/uniform_structs.h +++ b/code/graphics/util/uniform_structs.h @@ -319,10 +319,13 @@ struct volumetric_fog_data { float stepsize; vec3d nebPos; - float globalstepalpha; + float opacitydistance; vec3d nebSize; float alphalimit; + + float nebColor[3]; + float udfScale; float emissiveSpreadFactor; float emissiveIntensity; diff --git a/code/nebula/volumetrics.cpp b/code/nebula/volumetrics.cpp index afb1ee6b157..aa50c498169 100644 --- a/code/nebula/volumetrics.cpp +++ b/code/nebula/volumetrics.cpp @@ -151,6 +151,10 @@ const vec3d& volumetric_nebula::getSize() const { return size; } +const std::tuple& volumetric_nebula::getNebulaColor() const { + return nebulaColor; +} + bool volumetric_nebula::getEdgeSmoothing() const { return Detail.nebula_detail == MAX_DETAIL_LEVEL || doEdgeSmoothing; //Only for highest setting, or when the lab has an override. } @@ -179,10 +183,6 @@ float volumetric_nebula::getStepsize() const { return getOpacityDistance() / static_cast(getSteps()); } -float volumetric_nebula::getStepalpha() const { - return -(powf(getAlphaLim(), 1.0f / static_cast(getSteps())) - 1.0f); -} - float volumetric_nebula::getAlphaLim() const { return alphaLim; } @@ -246,6 +246,32 @@ static anl::CInstructionIndex getCustomNoise(anl::CKernel& kernel, const SCP_str return builder.eval(expression); } +static inline std::array getNeighbors(const ivec3& pnt){ + return { + ivec3{pnt.x+1, pnt.y, pnt.z}, + ivec3{pnt.x-1, pnt.y, pnt.z}, + ivec3{pnt.x, pnt.y+1, pnt.z}, + ivec3{pnt.x, pnt.y-1, pnt.z}, + ivec3{pnt.x, pnt.y, pnt.z+1}, + ivec3{pnt.x, pnt.y, pnt.z-1} + }; +} + +//Nebula distance must be a lower bound to avoid errors, so subtract sqrt(2) in each dimension +static inline float getNebDistSquared(const ivec3& l, const ivec3& r, const vec3d& scale, bool lowerBound) { + int dx = (l.x - r.x) * (l.x - r.x); + int dy = (l.y - r.y) * (l.y - r.y); + int dz = (l.z - r.z) * (l.z - r.z); + + if (lowerBound){ + dx -= 2; + dy -= 2; + dz -= 2; + } + + return (dx < 0 ? 0 : dx) * scale.xyz.x * scale.xyz.x + (dy < 0 ? 0 : dy) * scale.xyz.y * scale.xyz.y + (dz < 0 ? 0 : dz) * scale.xyz.z * scale.xyz.z; +} + void volumetric_nebula::renderVolumeBitmap() { Assertion(!hullPof.empty(), "Volumetric Nebula was not properly configured. Did you call parse_volumetric_nebula()?"); Assertion(!isVolumeBitmapValid(), "Volume bitmap was already rendered!"); @@ -277,6 +303,7 @@ void volumetric_nebula::renderVolumeBitmap() { //Calculate minimum "bottom left" corner of scaled size box vec3d bl = pm->mins - (size * ((scaleFactor - 1.0f) / 2.0f / scaleFactor)); + //Go through sampling procedure to test where the nebula even is for (int x = 0; x < nSample; x++) { for (int y = 0; y < nSample; y++) { vec3d start = bl; @@ -321,26 +348,98 @@ void volumetric_nebula::renderVolumeBitmap() { model_unload(modelnum); + //Sample the nebula values from the binary cubegrid. volumeBitmapData = make_unique(n * n * n * 4); - float oversamplingDivisor = 255.0f / static_cast((1 << (oversampling - 1)) + 1); + int oversamplingCount = (1 << (oversampling - 1)) + 1; + float oversamplingDivisor = 255.1f / static_cast(oversamplingCount); for (int x = 0; x < n; x++) { for (int y = 0; y < n; y++) { for (int z = 0; z < n; z++) { - volumeBitmapData[COLOR_3D_ARRAY_POS(n, R, x, y, z)] = static_cast(std::get<0>(nebulaColor) * 255.0f); - volumeBitmapData[COLOR_3D_ARRAY_POS(n, G, x, y, z)] = static_cast(std::get<1>(nebulaColor) * 255.0f); - volumeBitmapData[COLOR_3D_ARRAY_POS(n, B, x, y, z)] = static_cast(std::get<2>(nebulaColor) * 255.0f); - - float sum = 0.0f; + int sum = 0; for (int sx = x * oversampling; sx <= (x + 1) * oversampling; sx++) { for (int sy = y * oversampling; sy <= (y + 1) * oversampling; sy++) { for (int sz = z * oversampling; sz <= (z + 1) * oversampling; sz++) { if (volumeSampleCache[sx * nSample * nSample + sy * nSample + sz]) - sum += 1.0f; + sum++; } } } - - volumeBitmapData[COLOR_3D_ARRAY_POS(n, A, x, y, z)] = static_cast(sum * oversamplingDivisor); + + volumeBitmapData[COLOR_3D_ARRAY_POS(n, A, x, y, z)] = static_cast(static_cast(sum) * oversamplingDivisor); + } + } + } + + // Test for edges in the nebula to compute the UDF + auto volumeEdgeCache = make_unique(n * n * n); + SCP_set udfBFS_checking, udfBFS_to_check; + + for (int x = 0; x < n; x++) { + for (int y = 0; y < n; y++) { + for (int z = 0; z < n; z++) { + const ubyte& nebula_density = volumeBitmapData[COLOR_3D_ARRAY_POS(n, A, x, y, z)]; + + //If we have neither full nor no nebula presence, it's an edge. + if (nebula_density > 0 && nebula_density < 255) { + udfBFS_to_check.emplace(ivec3{x, y, z}); + volumeEdgeCache[x * n * n + y * n + z] = ivec3{x, y, z}; + } + else { + bool found_edge = false; + //it's possible that we get completely sharp edges. So test for that. + for (const ivec3& neighbor : getNeighbors({x, y, z})){ + if (neighbor.x < 0 || neighbor.x >= n || neighbor.y < 0 || neighbor.y >= n || neighbor.z < 0 || neighbor.z >= n) + continue; + + if (nebula_density != volumeBitmapData[COLOR_3D_ARRAY_POS(n, A, neighbor.x, neighbor.y, neighbor.z)]){ + found_edge = true; + break; + } + } + + if (found_edge) { + udfBFS_to_check.emplace(ivec3{x, y, z}); + volumeEdgeCache[x * n * n + y * n + z] = ivec3{x, y, z}; + } + else + volumeEdgeCache[x * n * n + y * n + z] = ivec3{-1, -1, -1}; + } + } + } + } + + //BFS from the known nebula edges to find the distance to the closest edge + while(!udfBFS_to_check.empty()){ + udfBFS_checking = udfBFS_to_check; + udfBFS_to_check.clear(); + + for (const ivec3& toCheck : udfBFS_checking){ + const ivec3& closestEdgeTile = volumeEdgeCache[toCheck.x * n * n + toCheck.y * n + toCheck.z]; + + for (const ivec3& neighbor : getNeighbors(toCheck)) { + if (neighbor.x < 0 || neighbor.x >= n || neighbor.y < 0 || neighbor.y >= n || neighbor.z < 0 || neighbor.z >= n) + continue; + + ivec3& neighborClosestEdgeTile = volumeEdgeCache[neighbor.x * n * n + neighbor.y * n + neighbor.z]; + + if (neighborClosestEdgeTile.x < 0 || getNebDistSquared(neighbor, closestEdgeTile, size, false) < getNebDistSquared(neighbor, neighborClosestEdgeTile, size, false)) { + neighborClosestEdgeTile = closestEdgeTile; + udfBFS_to_check.emplace(neighbor); + } + } + } + } + + //Compute the actual UDF from the BFS + //scale is the maximal distance possible. + udfScale = vm_vec_mag(&size); + for (int x = 0; x < n; x++) { + for (int y = 0; y < n; y++) { + for (int z = 0; z < n; z++) { + float dist = sqrtf(getNebDistSquared(ivec3{x, y, z}, volumeEdgeCache[x * n * n + y * n + z], size, true)) / static_cast(n); //in meters + volumeBitmapData[COLOR_3D_ARRAY_POS(n, R, x, y, z)] = static_cast(dist / udfScale * 255.0f); //UDF + volumeBitmapData[COLOR_3D_ARRAY_POS(n, G, x, y, z)] = 0; // Reserved + volumeBitmapData[COLOR_3D_ARRAY_POS(n, B, x, y, z)] = 0; // Reserved } } } @@ -392,6 +491,10 @@ int volumetric_nebula::getNoiseVolumeBitmapHandle() const { return noiseVolumeBitmapHandle; } +float volumetric_nebula::getUDFScale() const { + return udfScale; +} + float volumetric_nebula::getAlphaToPos(const vec3d& pnt, float distance_mult) const { // This pretty much emulates the volumetric shader. This could be slow, so I hope it's not needed too often vec3d ray_direction; @@ -411,7 +514,7 @@ float volumetric_nebula::getAlphaToPos(const vec3d& pnt, float distance_mult) co } float alpha = 1.0f; - const float stepalpha = getStepalpha(); + const float stepalpha = -(powf(getAlphaLim(), 1.0f / (getOpacityDistance() / getStepsize())) - 1.0f); const int n = 1 << resolution; for (float stept = maxTmin; stept < minTmax; stept += getStepsize()) { vec3d localpos = (Eye_position + (ray_direction * stept) - bb_min) / size * static_cast(n); diff --git a/code/nebula/volumetrics.h b/code/nebula/volumetrics.h index 89765bde3de..571e87172d4 100644 --- a/code/nebula/volumetrics.h +++ b/code/nebula/volumetrics.h @@ -76,6 +76,8 @@ class volumetric_nebula { int noiseVolumeBitmapHandle = -1; std::unique_ptr noiseVolumeBitmapData = nullptr; + float udfScale = 1.0f; + //Friend things that are allowed to directly manipulate "current" volumetrics. Only FRED and the Lab. In all other cases, "sensibly constant" values behave properly RAII and stay constant afterwards. friend class LabUi; //Lab friend class CFred_mission_save; //FRED @@ -89,6 +91,7 @@ class volumetric_nebula { const vec3d& getPos() const; const vec3d& getSize() const; + const std::tuple& getNebulaColor() const; bool getEdgeSmoothing() const; int getSteps() const; @@ -96,7 +99,6 @@ class volumetric_nebula { float getOpacityDistance() const; float getStepsize() const; - float getStepalpha() const; float getAlphaLim() const; float getEmissiveSpread() const; @@ -116,6 +118,7 @@ class volumetric_nebula { void renderVolumeBitmap(); int getVolumeBitmapHandle() const; int getNoiseVolumeBitmapHandle() const; + float getUDFScale() const; float getAlphaToPos(const vec3d& pnt, float distance_mult) const; }; diff --git a/code/tracing/categories.cpp b/code/tracing/categories.cpp index f9561e0e9a1..d297bcda4b0 100644 --- a/code/tracing/categories.cpp +++ b/code/tracing/categories.cpp @@ -96,6 +96,8 @@ Category RenderNavBracket("Render Nav bracket", true); Category MainFrame("Main Frame", true); Category PageFlip("Page flip", true); +Category Volumetrics("Volumetrics", true); + Category NanoVGFlushFrame("NanoVG flush frame", true); Category NanoVGDrawFill("NanoVG Draw fill", true); Category NanoVGDrawConvexFill("NanoVG Draw convex fill", true); diff --git a/code/tracing/categories.h b/code/tracing/categories.h index 217b3fc0107..892f9414a4f 100644 --- a/code/tracing/categories.h +++ b/code/tracing/categories.h @@ -109,6 +109,8 @@ extern Category RenderNavBracket; extern Category MainFrame; extern Category PageFlip; +extern Category Volumetrics; + extern Category NanoVGFlushFrame; extern Category NanoVGDrawFill; extern Category NanoVGDrawConvexFill; From 3d285f2f42215606d5e1501a675a2202de7083c8 Mon Sep 17 00:00:00 2001 From: BMagnu <6238428+BMagnu@users.noreply.github.com> Date: Sat, 22 Jun 2024 00:38:47 +0900 Subject: [PATCH 63/63] Fix transparent cockpit by ensuring ordering (#6187) --- code/ship/ship.cpp | 63 ++++++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/code/ship/ship.cpp b/code/ship/ship.cpp index 3b98895920e..3480a71bcfe 100644 --- a/code/ship/ship.cpp +++ b/code/ship/ship.cpp @@ -7965,7 +7965,8 @@ void ship_render_player_ship(object* objp, const vec3d* cam_offset, const matrix render_info.set_team_color(shipp->team_name, shipp->secondary_team_name, 0, 0); render_info.set_detail_level_lock(0); - model_render_immediate(&render_info, sip->model_num, shipp->model_instance_num, &objp->orient, &eye_offset); + model_render_immediate(&render_info, sip->model_num, shipp->model_instance_num, &objp->orient, &eye_offset, MODEL_RENDER_OPAQUE); + model_render_immediate(&render_info, sip->model_num, shipp->model_instance_num, &objp->orient, &eye_offset, MODEL_RENDER_TRANS); gr_end_view_matrix(); gr_end_proj_matrix(); @@ -8009,7 +8010,7 @@ void ship_render_player_ship(object* objp, const vec3d* cam_offset, const matrix //If we just want to recieve, we still have to write to the color buffer but not to the zbuffer, otherwise shadow recieving breaks shadow_render_info.set_flags(MR_NO_TEXTURING | MR_NO_LIGHTING | (Show_ship_casts_shadow ? 0 : MR_NO_ZBUFFER)); shadow_render_info.set_object_number(OBJ_INDEX(objp)); - model_render_immediate(&shadow_render_info, sip->model_num, shipp->model_instance_num, &objp->orient, &eye_offset); + model_render_immediate(&shadow_render_info, sip->model_num, shipp->model_instance_num, &objp->orient, &eye_offset, MODEL_RENDER_OPAQUE); } if (renderCockpitModel) { model_render_params shadow_render_info; @@ -8020,7 +8021,7 @@ void ship_render_player_ship(object* objp, const vec3d* cam_offset, const matrix vm_vec_unrotate(&offset, &offset, &objp->orient); if (!Disable_cockpit_sway) offset += sip->cockpit_sway_val * objp->phys_info.acceleration; - model_render_immediate(&shadow_render_info, sip->cockpit_model_num, shipp->cockpit_model_instance, &objp->orient, &offset); + model_render_immediate(&shadow_render_info, sip->cockpit_model_num, shipp->cockpit_model_instance, &objp->orient, &offset, MODEL_RENDER_OPAQUE); } shadows_end_render(); @@ -8047,44 +8048,58 @@ void ship_render_player_ship(object* objp, const vec3d* cam_offset, const matrix render_flags |= MR_NO_GLOWMAPS; } + model_render_params ship_render_info; + model_render_params cockpit_render_info; + vec3d cockpit_offset = sip->cockpit_offset; + //Properly render ship and cockpit model if (deferredRenderShipModel) { - model_render_params render_info; - render_info.set_detail_level_lock(0); - render_info.set_flags(render_flags); - render_info.set_replacement_textures(pmi->texture_replace); - render_info.set_object_number(OBJ_INDEX(objp)); + ship_render_info.set_detail_level_lock(0); + ship_render_info.set_flags(render_flags); + ship_render_info.set_replacement_textures(pmi->texture_replace); + ship_render_info.set_object_number(OBJ_INDEX(objp)); if (sip->uses_team_colors) - render_info.set_team_color(shipp->team_name, shipp->secondary_team_name, 0, 0); + ship_render_info.set_team_color(shipp->team_name, shipp->secondary_team_name, 0, 0); - model_render_immediate(&render_info, sip->model_num, shipp->model_instance_num, &objp->orient, &eye_offset); + model_render_immediate(&ship_render_info, sip->model_num, shipp->model_instance_num, &objp->orient, &eye_offset, MODEL_RENDER_OPAQUE); gr_zbuffer_clear(true); } if (renderCockpitModel) { - model_render_params render_info; - render_info.set_detail_level_lock(0); - render_info.set_flags(render_flags); - render_info.set_replacement_textures(Player_cockpit_textures); - vec3d offset = sip->cockpit_offset; - vm_vec_unrotate(&offset, &offset, &objp->orient); + cockpit_render_info.set_detail_level_lock(0); + cockpit_render_info.set_flags(render_flags); + cockpit_render_info.set_replacement_textures(Player_cockpit_textures); + vm_vec_unrotate(&cockpit_offset, &cockpit_offset, &objp->orient); if (!Disable_cockpit_sway) - offset += sip->cockpit_sway_val * objp->phys_info.acceleration; - model_render_immediate(&render_info, sip->cockpit_model_num, shipp->cockpit_model_instance, &objp->orient, &offset); + cockpit_offset += sip->cockpit_sway_val * objp->phys_info.acceleration; + model_render_immediate(&cockpit_render_info, sip->cockpit_model_num, shipp->cockpit_model_instance, &objp->orient, &cockpit_offset, MODEL_RENDER_OPAQUE); } - if (Cmdline_deferred_lighting_cockpit) { + gr_deferred_lighting_msaa(); + gr_deferred_lighting_end(); + gr_deferred_lighting_finish(); + + gr_reset_lighting(); + gr_end_view_matrix(); gr_end_proj_matrix(); gr_set_proj_matrix(Proj_fov, gr_screen.clip_aspect, Min_draw_distance_cockpit, Max_draw_distance); - gr_set_view_matrix(&Eye_position, &Eye_matrix); + gr_set_view_matrix(&leaning_position, &eye_orient); + } - gr_deferred_lighting_msaa(); - gr_deferred_lighting_end(); - gr_deferred_lighting_finish(); + //Transparent stuff has to come after deferred closes + gr_zbuffer_set(ZBUFFER_TYPE_READ); - gr_reset_lighting(); + if (deferredRenderShipModel) { + model_render_immediate(&ship_render_info, sip->model_num, shipp->model_instance_num, &objp->orient, &eye_offset, MODEL_RENDER_TRANS); + } + + if (renderCockpitModel) { + model_render_immediate(&cockpit_render_info, sip->cockpit_model_num, shipp->cockpit_model_instance, &objp->orient, &cockpit_offset, MODEL_RENDER_TRANS); + } + + if (Cmdline_deferred_lighting_cockpit) { gr_set_lighting(); }