mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-08 07:28:22 -10:00
Compare commits
35
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d431ff824 | ||
|
|
28df181995 | ||
|
|
6dbe720fe0 | ||
|
|
7ad97097d5 | ||
|
|
2d1f4e2411 | ||
|
|
037c53d853 | ||
|
|
17ad536beb | ||
|
|
452814e9de | ||
|
|
9c78d15dd9 | ||
|
|
4ea8885df1 | ||
|
|
22b6eff659 | ||
|
|
8e308d4e34 | ||
|
|
bb4b8cf792 | ||
|
|
64f7b603e5 | ||
|
|
f8f337a195 | ||
|
|
3d706aa2d3 | ||
|
|
75a5a7e086 | ||
|
|
0342fc305e | ||
|
|
44b14d363e | ||
|
|
f671153a86 | ||
|
|
46b77b69fa | ||
|
|
ed581be4a3 | ||
|
|
303478fb58 | ||
|
|
fe922d2ec9 | ||
|
|
8544acc951 | ||
|
|
76e9522e5a | ||
|
|
cfc2156a12 | ||
|
|
4a8bcc45d7 | ||
|
|
570d6bcec9 | ||
|
|
5bb7eabbc7 | ||
|
|
9a952cc892 | ||
|
|
cff5b7327b | ||
|
|
631ef0ec82 | ||
|
|
fbd0b6704c | ||
|
|
9fb3b31a10 |
No files matched your search
+3
-3
@@ -6,9 +6,9 @@ if (EXISTS "${PROJECT_SOURCE_DIR}/version_override.cmake")
|
||||
include("${PROJECT_SOURCE_DIR}/version_override.cmake")
|
||||
endif()
|
||||
|
||||
set_if_not_defined(FSO_VERSION_MAJOR 25)
|
||||
set_if_not_defined(FSO_VERSION_MINOR 1)
|
||||
set_if_not_defined(FSO_VERSION_BUILD 0)
|
||||
set_if_not_defined(FSO_VERSION_MAJOR 26)
|
||||
set_if_not_defined(FSO_VERSION_MINOR 0)
|
||||
set_if_not_defined(FSO_VERSION_BUILD 1)
|
||||
|
||||
# A numeric revision e.g. the CI build number
|
||||
set_if_not_defined(FSO_VERSION_REVISION 0)
|
||||
|
||||
@@ -176,6 +176,7 @@ namespace AI {
|
||||
Dont_limit_change_in_speed_due_to_physics_whack,
|
||||
Guards_ignore_protected_attackers,
|
||||
Fix_standard_strafe,
|
||||
Fix_target_updating_with_strafe,
|
||||
Standard_strafe_used_more,
|
||||
Unify_usage_ai_shield_manage_delay,
|
||||
Fix_AI_shield_management_bug,
|
||||
@@ -191,6 +192,11 @@ namespace AI {
|
||||
Purge_player_issued_form_on_wing_after_subsequent_order,
|
||||
Cancel_future_waves_of_any_wing_launched_from_an_exited_ship,
|
||||
Fix_ignore_if_dead_flag,
|
||||
Kamikaze_no_collision_avoidance,
|
||||
Fix_big_ship_waypoint_completion, // a) big ships complete a waypoint within their radius rather than sqrt(radius);
|
||||
// b) completion no longer requires moving 0.1m in a single frame (framerate-dependent)
|
||||
Fix_shockwave_expire_before_do_damage, // shockwaves whose lifetime is shorter than one frame apply their area damage at least once before expiring
|
||||
Fix_small_ai_recover_after_engines_repaired, // ensure small ship AI can switch back to useful AI modes if engines get repaired
|
||||
|
||||
NUM_VALUES
|
||||
};
|
||||
|
||||
@@ -677,6 +677,8 @@ void parse_ai_profiles_tbl(const char *filename)
|
||||
|
||||
set_flag(profile, "$fix standard strafe:", AI::Profile_Flags::Fix_standard_strafe);
|
||||
|
||||
set_flag(profile, "$fix target updating with strafe:", AI::Profile_Flags::Fix_target_updating_with_strafe);
|
||||
|
||||
set_flag(profile, "$standard strafe used more:", AI::Profile_Flags::Standard_strafe_used_more);
|
||||
|
||||
if (optional_string("$standard strafe triggers under this speed:")) {
|
||||
@@ -749,6 +751,13 @@ void parse_ai_profiles_tbl(const char *filename)
|
||||
|
||||
set_flag(profile, "$fix 'ignore if dead' flag:", AI::Profile_Flags::Fix_ignore_if_dead_flag);
|
||||
|
||||
set_flag(profile, "$no collision avoidance for kamikaze fighters:", AI::Profile_Flags::Kamikaze_no_collision_avoidance);
|
||||
|
||||
set_flag(profile, "$fix big ship waypoint completion:", AI::Profile_Flags::Fix_big_ship_waypoint_completion);
|
||||
|
||||
set_flag(profile, "$fix shockwave expiring before dealing damage:", AI::Profile_Flags::Fix_shockwave_expire_before_do_damage);
|
||||
|
||||
set_flag(profile, "$fix fighter/bomber AI recovers after engines repaired:", AI::Profile_Flags::Fix_small_ai_recover_after_engines_repaired);
|
||||
|
||||
// end of options ----------------------------------------
|
||||
|
||||
@@ -975,4 +984,12 @@ void ai_profile_t::reset()
|
||||
if (mod_supports_version(26, 0, 0)) {
|
||||
flags.set(AI::Profile_Flags::Fix_ignore_if_dead_flag);
|
||||
}
|
||||
if (mod_supports_version(26, 2, 0)) {
|
||||
flags.set(AI::Profile_Flags::Kamikaze_no_collision_avoidance);
|
||||
flags.set(AI::Profile_Flags::Fix_big_ship_waypoint_completion);
|
||||
flags.set(AI::Profile_Flags::Fix_shockwave_expire_before_do_damage);
|
||||
flags.set(AI::Profile_Flags::Fix_small_ai_recover_after_engines_repaired);
|
||||
flags.set(AI::Profile_Flags::Fix_standard_strafe);
|
||||
flags.set(AI::Profile_Flags::Fix_target_updating_with_strafe);
|
||||
}
|
||||
}
|
||||
+63
-55
@@ -1198,70 +1198,78 @@ void ai_big_chase()
|
||||
}
|
||||
|
||||
// If a collision is expected, pull out!
|
||||
float dist_normal_to_enemy;
|
||||
if (!(The_mission.ai_profile->flags[AI::Profile_Flags::Kamikaze_no_collision_avoidance] && aip->ai_flags[AI::AI_Flags::Kamikaze])) {
|
||||
|
||||
if (vm_vec_mag_squared(&aip->big_attack_surface_normal) > 0.9) {
|
||||
if (aip->ai_profile_flags[AI::Profile_Flags::Improved_subsystem_attack_pathing])
|
||||
dist_normal_to_enemy = fl_abs((dist_to_enemy / vm_vec_dot(&vec_to_enemy, &aip->big_attack_surface_normal)));
|
||||
else // I'm fairly sure this retail code VVV meant to use / instead of *
|
||||
dist_normal_to_enemy = fl_abs((dist_to_enemy * vm_vec_dot(&vec_to_enemy, &aip->big_attack_surface_normal)));
|
||||
} else {
|
||||
// don;t have normal so use a conservative value here
|
||||
dist_normal_to_enemy = 0.3f * dist_to_enemy;
|
||||
}
|
||||
float dist_normal_to_enemy;
|
||||
|
||||
// float time_to_enemy = dist_normal_to_enemy / Pl_objp->phys_info.speed * fl_abs(vm_vec_dot(&Pl_objp->phys_info.vel, &aip->big_attack_surface_normal));
|
||||
// if (Framecount % 30 == 1) {
|
||||
// mprintf(("normal dist; %.1f, time: %.1f\n", dist_normal_to_enemy, time_to_enemy));
|
||||
// }
|
||||
|
||||
// since we're not in strafe and we may get a bad normal, cap dist_normal_to_enemy as MIN(0.3*dist_to_enemy, self)
|
||||
// this will allow us to get closer on a bad normal
|
||||
dist_normal_to_enemy = MAX(0.3f*dist_to_enemy, dist_normal_to_enemy);
|
||||
|
||||
if (dist_to_enemy < ATTACK_COLLIDE_BASE_DIST) {
|
||||
// within 50m or 1sec
|
||||
float time_to_enemy;
|
||||
if (vm_vec_mag_squared(&aip->big_attack_surface_normal) > 0.9) {
|
||||
if (Pl_objp->phys_info.speed > 0.1) {
|
||||
time_to_enemy = dist_normal_to_enemy / fl_abs(vm_vec_dot(&Pl_objp->phys_info.vel, &aip->big_attack_surface_normal));
|
||||
} else {
|
||||
// a big time
|
||||
time_to_enemy = 100.0f;
|
||||
}
|
||||
} else {
|
||||
if (Pl_objp->phys_info.speed > 0.1) {
|
||||
time_to_enemy = dist_normal_to_enemy / Pl_objp->phys_info.speed;
|
||||
} else {
|
||||
// a big time
|
||||
time_to_enemy = 100.0f;
|
||||
}
|
||||
if (aip->ai_profile_flags[AI::Profile_Flags::Improved_subsystem_attack_pathing])
|
||||
dist_normal_to_enemy = fl_abs((dist_to_enemy / vm_vec_dot(&vec_to_enemy, &aip->big_attack_surface_normal)));
|
||||
else // I'm fairly sure this retail code VVV meant to use / instead of *
|
||||
dist_normal_to_enemy = fl_abs((dist_to_enemy * vm_vec_dot(&vec_to_enemy, &aip->big_attack_surface_normal)));
|
||||
}
|
||||
else {
|
||||
// don;t have normal so use a conservative value here
|
||||
dist_normal_to_enemy = 0.3f * dist_to_enemy;
|
||||
}
|
||||
|
||||
float speed_dist = MAX(0.0f, (Pl_objp->phys_info.speed-50) * 2);
|
||||
if ((dist_normal_to_enemy < ATTACK_COLLIDE_AVOID_DIST + speed_dist) || (time_to_enemy < ATTACK_COLLIDE_AVOID_TIME) ) {
|
||||
// get away, simulate crsh recovery (don't use avoid)
|
||||
// accelerate_ship(aip, -1.0f);
|
||||
big_ship_collide_recover_start(Pl_objp, En_objp, nullptr);
|
||||
// aip->submode = SM_AVOID;
|
||||
// aip->submode_start_time = Missiontime;
|
||||
} else if ((dist_normal_to_enemy < ATTACK_COLLIDE_SLOW_DIST) || (time_to_enemy < ATTACK_COLLIDE_SLOW_TIME) ) {
|
||||
// slow down
|
||||
accelerate_ship(aip, -1.0f);
|
||||
}
|
||||
}
|
||||
// float time_to_enemy = dist_normal_to_enemy / Pl_objp->phys_info.speed * fl_abs(vm_vec_dot(&Pl_objp->phys_info.vel, &aip->big_attack_surface_normal));
|
||||
// if (Framecount % 30 == 1) {
|
||||
// mprintf(("normal dist; %.1f, time: %.1f\n", dist_normal_to_enemy, time_to_enemy));
|
||||
// }
|
||||
|
||||
/*
|
||||
if ((dot_to_enemy > 1.0f - 0.1f * En_objp->radius/(dist_to_enemy + 1.0f)) && (Pl_objp->phys_info.speed > dist_to_enemy/5.0f)) {
|
||||
if (might_collide_with_ship(Pl_objp, En_objp, dot_to_enemy, dist_to_enemy, (aip->targeted_subsys == NULL)*2.0f + 1.5f)) {
|
||||
if ((Missiontime - aip->last_hit_time > F1_0*4) && (dist_to_enemy < Pl_objp->radius*2 + En_objp->radius*2)) {
|
||||
// since we're not in strafe and we may get a bad normal, cap dist_normal_to_enemy as MIN(0.3*dist_to_enemy, self)
|
||||
// this will allow us to get closer on a bad normal
|
||||
dist_normal_to_enemy = MAX(0.3f * dist_to_enemy, dist_normal_to_enemy);
|
||||
|
||||
if (dist_to_enemy < ATTACK_COLLIDE_BASE_DIST) {
|
||||
// within 50m or 1sec
|
||||
float time_to_enemy;
|
||||
if (vm_vec_mag_squared(&aip->big_attack_surface_normal) > 0.9) {
|
||||
if (Pl_objp->phys_info.speed > 0.1) {
|
||||
time_to_enemy = dist_normal_to_enemy / fl_abs(vm_vec_dot(&Pl_objp->phys_info.vel, &aip->big_attack_surface_normal));
|
||||
}
|
||||
else {
|
||||
// a big time
|
||||
time_to_enemy = 100.0f;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (Pl_objp->phys_info.speed > 0.1) {
|
||||
time_to_enemy = dist_normal_to_enemy / Pl_objp->phys_info.speed;
|
||||
}
|
||||
else {
|
||||
// a big time
|
||||
time_to_enemy = 100.0f;
|
||||
}
|
||||
}
|
||||
|
||||
float speed_dist = MAX(0.0f, (Pl_objp->phys_info.speed - 50) * 2);
|
||||
if ((dist_normal_to_enemy < ATTACK_COLLIDE_AVOID_DIST + speed_dist) || (time_to_enemy < ATTACK_COLLIDE_AVOID_TIME)) {
|
||||
// get away, simulate crsh recovery (don't use avoid)
|
||||
// accelerate_ship(aip, -1.0f);
|
||||
big_ship_collide_recover_start(Pl_objp, En_objp, nullptr);
|
||||
// aip->submode = SM_AVOID;
|
||||
// aip->submode_start_time = Missiontime;
|
||||
}
|
||||
else if ((dist_normal_to_enemy < ATTACK_COLLIDE_SLOW_DIST) || (time_to_enemy < ATTACK_COLLIDE_SLOW_TIME)) {
|
||||
// slow down
|
||||
accelerate_ship(aip, -1.0f);
|
||||
} else {
|
||||
aip->submode = SM_AVOID;
|
||||
aip->submode_start_time = Missiontime;
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
/*
|
||||
if ((dot_to_enemy > 1.0f - 0.1f * En_objp->radius/(dist_to_enemy + 1.0f)) && (Pl_objp->phys_info.speed > dist_to_enemy/5.0f)) {
|
||||
if (might_collide_with_ship(Pl_objp, En_objp, dot_to_enemy, dist_to_enemy, (aip->targeted_subsys == NULL)*2.0f + 1.5f)) {
|
||||
if ((Missiontime - aip->last_hit_time > F1_0*4) && (dist_to_enemy < Pl_objp->radius*2 + En_objp->radius*2)) {
|
||||
accelerate_ship(aip, -1.0f);
|
||||
} else {
|
||||
aip->submode = SM_AVOID;
|
||||
aip->submode_start_time = Missiontime;
|
||||
}
|
||||
}
|
||||
} */
|
||||
}
|
||||
}
|
||||
|
||||
switch (aip->submode) {
|
||||
|
||||
+31
-5
@@ -4936,14 +4936,24 @@ void ai_fly_to_target_position(const vec3d* target_pos, bool* pl_done_p=NULL, bo
|
||||
}
|
||||
|
||||
float dist_to_cover_this_frame = (Pl_objp->phys_info.speed * flFrametime);
|
||||
if ( (dist_to_goal < MIN_DIST_TO_WAYPOINT_GOAL) || dist_to_cover_this_frame > 0.1f ) {
|
||||
|
||||
// Retail's completion distance of sqrt(radius) is only ~32m for a capital ship with a 1000m radius --
|
||||
// far smaller than such a ship's turning circle -- and retail also skipped the completion check
|
||||
// entirely unless the ship covered 0.1m in a single frame, a framerate-dependent gate that excludes
|
||||
// exactly the slow speeds at which a big ship could aim precisely. Together these could trap a
|
||||
// capital ship in an endless overshoot-turnaround-miss loop at its final waypoint. With the flag
|
||||
// set, big ships complete a waypoint anywhere within their own radius of it, and the check always runs.
|
||||
bool fix_waypoint_completion = The_mission.ai_profile->flags[AI::Profile_Flags::Fix_big_ship_waypoint_completion];
|
||||
float waypoint_tolerance = MIN_DIST_TO_WAYPOINT_GOAL + ((fix_waypoint_completion && sip->is_big_or_huge()) ? Pl_objp->radius : fl_sqrt(Pl_objp->radius));
|
||||
|
||||
if ( fix_waypoint_completion || (dist_to_goal < MIN_DIST_TO_WAYPOINT_GOAL) || dist_to_cover_this_frame > 0.1f ) {
|
||||
vec3d nearest_point;
|
||||
float r;
|
||||
|
||||
r = find_nearest_point_on_line(&nearest_point, &Pl_objp->last_pos, &Pl_objp->pos, target_pos);
|
||||
|
||||
if ( (dist_to_goal < (MIN_DIST_TO_WAYPOINT_GOAL + fl_sqrt(Pl_objp->radius) + dist_to_cover_this_frame))
|
||||
|| (((r >= 0.0f) && (r <= 1.0f)) && (vm_vec_dist_quick(&nearest_point, target_pos) < (MIN_DIST_TO_WAYPOINT_GOAL + fl_sqrt(Pl_objp->radius)))))
|
||||
if ( (dist_to_goal < (waypoint_tolerance + dist_to_cover_this_frame))
|
||||
|| (((r >= 0.0f) && (r <= 1.0f)) && (vm_vec_dist_quick(&nearest_point, target_pos) < waypoint_tolerance)))
|
||||
{
|
||||
int treat_as_ship;
|
||||
|
||||
@@ -12437,6 +12447,13 @@ void ai_process_subobjects(int objnum)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Goober5000 and wookieejedi - conversely, if the engines are no longer blown (e.g. after repair), resume normal chase behavior.
|
||||
// The rest of the AI assumes SM_ATTACK_FOREVER implies blown engines,
|
||||
// so don't leave the submode set after the engines recover, or the ship will never evade and will ignore being hit.
|
||||
else if (The_mission.ai_profile->flags[AI::Profile_Flags::Fix_small_ai_recover_after_engines_repaired] && ((aip->mode == AIM_CHASE) && (aip->submode == SM_ATTACK_FOREVER))) {
|
||||
aip->submode = SM_ATTACK;
|
||||
aip->submode_start_time = Missiontime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13789,6 +13806,10 @@ void ai_manage_bay_doors(object *pl_objp, ai_info *aip, bool done)
|
||||
|
||||
ship *parent_ship = &Ships[shipp->bay_doors_parent_shipnum];
|
||||
|
||||
// the parent may have already been destroyed/departed, leaving stale bay door state behind
|
||||
if (parent_ship->objnum < 0 || parent_ship->model_instance_num < 0)
|
||||
return;
|
||||
|
||||
if (done)
|
||||
parent_ship->bay_doors_wanting_open--;
|
||||
|
||||
@@ -15492,8 +15513,13 @@ void ai_frame(int objnum)
|
||||
En_objp = NULL;
|
||||
}
|
||||
|
||||
if (aip->mode == AIM_CHASE) {
|
||||
if (En_objp == NULL) {
|
||||
if (aip->mode == AIM_CHASE || ((The_mission.ai_profile->flags[AI::Profile_Flags::Fix_target_updating_with_strafe]) && aip->mode == AIM_STRAFE)) {
|
||||
// If we're chasing or strafing against large ship but have lost our target, clear the active goal
|
||||
// to ensure ai_process_mission_orders() re-evaluates our orders next frame.
|
||||
// Without this fighter/bomber whose strafe target is destroyed drops to AIM_NONE (see ai_execute_behavior)
|
||||
// with its active_goal still set, so a standing order like attack-any is never re-processed
|
||||
// and the ship slows to zero and remains still until another order is issued.
|
||||
if (En_objp == nullptr) {
|
||||
aip->active_goal = -1;
|
||||
}
|
||||
}
|
||||
|
||||
+26
-15
@@ -83,7 +83,12 @@ static bool bm_inited = false;
|
||||
static uint Bm_next_signature = 0x1234;
|
||||
static int Bm_low_mem = 0;
|
||||
|
||||
SCP_map<int,ubyte*> bm_lookup_cache;
|
||||
struct bm_lookup_cache_entry {
|
||||
ubyte* data;
|
||||
int width;
|
||||
int height;
|
||||
};
|
||||
SCP_map<int, bm_lookup_cache_entry> bm_lookup_cache;
|
||||
|
||||
/**
|
||||
* How much RAM bmpman can use for textures.
|
||||
@@ -144,30 +149,31 @@ bitmap_slot* bm_get_slot(int handle, bool separate_ani_frames) {
|
||||
// Declaration of private functions and templates(declared as static type func(type param);)
|
||||
|
||||
bitmap_lookup::bitmap_lookup(int bitmap_num):
|
||||
Bitmap_data(NULL)
|
||||
Bitmap_data(nullptr),
|
||||
Width(0),
|
||||
Height(0),
|
||||
Num_channels(3)
|
||||
{
|
||||
if ( !bm_is_valid(bitmap_num) ) return;
|
||||
|
||||
Num_channels = 3;
|
||||
|
||||
if ( bm_has_alpha_channel(bitmap_num) ) {
|
||||
Num_channels = 4;
|
||||
}
|
||||
|
||||
bitmap_entry *be = bm_get_entry(bitmap_num);
|
||||
|
||||
Width = be->bm.w;
|
||||
Height = be->bm.h;
|
||||
|
||||
|
||||
auto cache_search = bm_lookup_cache.find(bitmap_num);
|
||||
if (cache_search == bm_lookup_cache.end()) {
|
||||
Bitmap_data = (ubyte*)vm_malloc(Width * Height * Num_channels * sizeof(ubyte));
|
||||
// The texture in graphics memory may be smaller than the bitmap itself, e.g. when mipmap levels
|
||||
// are culled at lower texture detail settings, so the readback reports the dimensions of the
|
||||
// data it actually returns.
|
||||
Bitmap_data = gr_get_bitmap_from_texture(bitmap_num, &Width, &Height);
|
||||
|
||||
gr_get_bitmap_from_texture((void*)Bitmap_data, bitmap_num);
|
||||
bm_lookup_cache.insert({bitmap_num, Bitmap_data});
|
||||
if (Bitmap_data != nullptr) {
|
||||
bm_lookup_cache.insert({bitmap_num, {Bitmap_data, Width, Height}});
|
||||
}
|
||||
} else {
|
||||
Bitmap_data = cache_search->second;
|
||||
Bitmap_data = cache_search->second.data;
|
||||
Width = cache_search->second.width;
|
||||
Height = cache_search->second.height;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,6 +196,11 @@ float bitmap_lookup::get_channel_alpha(float u, float v)
|
||||
{
|
||||
Assert( Bitmap_data != NULL );
|
||||
|
||||
// without an alpha channel there is nothing to look up, and indexing channel 3 would read out of bounds
|
||||
if ( Num_channels < 4 ) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
int x = fl2i(map_texture_address(u) * (Width-1));
|
||||
int y = fl2i(map_texture_address(v) * (Height-1));
|
||||
|
||||
@@ -198,7 +209,7 @@ float bitmap_lookup::get_channel_alpha(float u, float v)
|
||||
|
||||
void clear_bm_lookup_cache() {
|
||||
for(auto &iter: bm_lookup_cache) {
|
||||
free(iter.second);
|
||||
vm_free(iter.second.data);
|
||||
}
|
||||
bm_lookup_cache.clear();
|
||||
}
|
||||
|
||||
+39
-20
@@ -56,6 +56,10 @@ APPLY_TO_FOV_T(-, sub)
|
||||
|
||||
// Used to set the default value for in-game options
|
||||
static float fov_default = DEFAULT_FOV;
|
||||
static float cockpit_fov_default = DEFAULT_FOV;
|
||||
static bool cockpit_fov_toggle_default = false;
|
||||
|
||||
bool Use_cockpit_fov = false;
|
||||
|
||||
static SCP_string fov_display(float val)
|
||||
{
|
||||
@@ -73,6 +77,21 @@ static void parse_fov_func()
|
||||
fov_default = value;
|
||||
}
|
||||
|
||||
static void parse_cockpit_fov_func()
|
||||
{
|
||||
float value;
|
||||
stuff_float(&value);
|
||||
CLAMP(value, 0.436332f, 1.5708f);
|
||||
cockpit_fov_default = value;
|
||||
}
|
||||
|
||||
static void parse_cockpit_fov_toggle_func()
|
||||
{
|
||||
bool value;
|
||||
stuff_boolean(&value);
|
||||
cockpit_fov_toggle_default = value;
|
||||
}
|
||||
|
||||
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
|
||||
auto FovOption = options::OptionBuilder<float>("Graphics.FOV",
|
||||
std::pair<const char*, int>{"Field Of View", 1703},
|
||||
@@ -90,25 +109,6 @@ auto FovOption = options::OptionBuilder<float>("Graphics.FOV",
|
||||
.parser(parse_fov_func)
|
||||
.finish();
|
||||
|
||||
bool Use_cockpit_fov = false;
|
||||
|
||||
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
|
||||
auto CockpitFOVToggleOption = options::OptionBuilder<bool>("Graphics.CockpitFOVToggle",
|
||||
std::pair<const char*, int>{"Cockpit FOV Toggle", 1838},
|
||||
std::pair<const char*, int>{"Whether or not to use a different FOV for cockpit rendering from normal rendering", 1839})
|
||||
.category(std::make_pair("Graphics", 1825))
|
||||
.default_val(false)
|
||||
.change_listener([](bool val, bool) {
|
||||
if (!val) {
|
||||
COCKPIT_ZOOM_DEFAULT = VIEWER_ZOOM_DEFAULT;
|
||||
}
|
||||
return true; // This option will always persist so we never return false
|
||||
})
|
||||
.level(options::ExpertLevel::Advanced)
|
||||
.bind_to(&Use_cockpit_fov)
|
||||
.importance(61)
|
||||
.finish();
|
||||
|
||||
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
|
||||
auto CockpitFovOption = options::OptionBuilder<float>("Graphics.CockpitFOV",
|
||||
std::pair<const char*, int>{"Cockpit Field Of View", 1840},
|
||||
@@ -124,9 +124,28 @@ auto CockpitFovOption = options::OptionBuilder<float>("Graphics.CockpitFOV",
|
||||
return true;
|
||||
})
|
||||
.display(fov_display)
|
||||
.default_val(fov_default)
|
||||
.default_func([]() { return cockpit_fov_default; })
|
||||
.level(options::ExpertLevel::Advanced)
|
||||
.importance(62)
|
||||
.parser(parse_cockpit_fov_func)
|
||||
.finish();
|
||||
|
||||
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
|
||||
auto CockpitFOVToggleOption = options::OptionBuilder<bool>("Graphics.CockpitFOVToggle",
|
||||
std::pair<const char*, int>{"Cockpit FOV Toggle", 1838},
|
||||
std::pair<const char*, int>{"Whether or not to use a different FOV for cockpit rendering from normal rendering", 1839})
|
||||
.category(std::make_pair("Graphics", 1825))
|
||||
.default_func([]() {return cockpit_fov_toggle_default;})
|
||||
.change_listener([](bool val, bool) {
|
||||
if (!val) {
|
||||
COCKPIT_ZOOM_DEFAULT = VIEWER_ZOOM_DEFAULT;
|
||||
}
|
||||
return true; // This option will always persist so we never return false
|
||||
})
|
||||
.level(options::ExpertLevel::Advanced)
|
||||
.bind_to(&Use_cockpit_fov)
|
||||
.importance(61)
|
||||
.parser(parse_cockpit_fov_toggle_func)
|
||||
.finish();
|
||||
|
||||
//*************************CLASS: camera*************************
|
||||
|
||||
@@ -249,7 +249,7 @@ void photo_mode_compute_hud_layout()
|
||||
if (param.type == photo_mode_param_type::INT_RANGE) {
|
||||
snprintf(buf, sizeof(buf), "%s: %d", XSTR(param.label, param.xstr_id), param.max_val);
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "%s: %s", XSTR(param.label, param.xstr_id), XSTR("Off", 1285));
|
||||
snprintf(buf, sizeof(buf), "%s: %s", XSTR(param.label, param.xstr_id), XSTR("Off", 1286));
|
||||
}
|
||||
measure(buf);
|
||||
}
|
||||
|
||||
+4
-1
@@ -831,7 +831,10 @@ typedef struct screen {
|
||||
std::function<void()> gf_clear_states;
|
||||
|
||||
std::function<void(int bitmap_handle, int bpp, const ubyte* data, int width, int height)> gf_update_texture;
|
||||
std::function<void(void* data_out, int bitmap_num)> gf_get_bitmap_from_texture;
|
||||
// Reads the texture uploaded for a bitmap back from graphics memory. Returns a vm_malloc'd buffer
|
||||
// (freed by the caller with vm_free) and reports the dimensions of the returned data, which may be
|
||||
// smaller than the bitmap if the texture was culled at upload; returns nullptr on failure.
|
||||
std::function<ubyte*(int bitmap_num, int* width_out, int* height_out)> gf_get_bitmap_from_texture;
|
||||
|
||||
std::function<void(matrix4* shadow_view_matrix, const matrix* light_matrix, vec3d* eye_pos)> gf_shadow_map_start;
|
||||
std::function<void()> gf_shadow_map_end;
|
||||
|
||||
@@ -259,9 +259,11 @@ void gr_stub_update_texture(int /*bitmap_handle*/, int /*bpp*/, const ubyte *
|
||||
{
|
||||
}
|
||||
|
||||
void gr_stub_get_bitmap_from_texture(void* /*data_out*/, int /*bitmap_num*/)
|
||||
ubyte* gr_stub_get_bitmap_from_texture(int /*bitmap_num*/, int* width_out, int* height_out)
|
||||
{
|
||||
|
||||
*width_out = 0;
|
||||
*height_out = 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int gr_stub_bm_make_render_target(int /*n*/, int * /*width*/, int * /*height*/, int * /*bpp*/, int * /*mm_lvl*/, int /*flags*/)
|
||||
|
||||
@@ -1413,15 +1413,20 @@ int opengl_get_texture( GLenum target, GLenum pixel_format, GLenum data_format,
|
||||
return m_offset;
|
||||
}
|
||||
|
||||
void gr_opengl_get_bitmap_from_texture(void* data_out, int bitmap_num)
|
||||
ubyte* gr_opengl_get_bitmap_from_texture(int bitmap_num, int* width_out, int* height_out)
|
||||
{
|
||||
*width_out = 0;
|
||||
*height_out = 0;
|
||||
|
||||
float u,v;
|
||||
|
||||
uint32_t array_index = 0;
|
||||
gr_opengl_tcache_set(bitmap_num, TCACHE_TYPE_NORMAL, &u, &v, &array_index);
|
||||
if ( !gr_opengl_tcache_set(bitmap_num, TCACHE_TYPE_NORMAL, &u, &v, &array_index) ) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto *ts = bm_get_gr_info<tcache_slot_opengl>(bitmap_num, true);
|
||||
|
||||
|
||||
GLenum pixel_format = GL_RGB;
|
||||
GLenum data_format = GL_UNSIGNED_BYTE;
|
||||
int bytes_per_pixel = 3 * sizeof(ubyte);
|
||||
@@ -1431,24 +1436,46 @@ void gr_opengl_get_bitmap_from_texture(void* data_out, int bitmap_num)
|
||||
bytes_per_pixel = 4 * sizeof(ubyte);
|
||||
}
|
||||
|
||||
// We can't read a specific layer of the texture so we need to read the entire texture and then memcpy the right part from that...
|
||||
int num_frames = 0;
|
||||
bm_get_info(bitmap_num, nullptr, nullptr, nullptr, &num_frames);
|
||||
if (!bm_is_texture_array(bitmap_num)) {
|
||||
num_frames = 1;
|
||||
Assertion(ts->texture_target == GL_TEXTURE_2D_ARRAY, "Unexpected texture target encountered!");
|
||||
|
||||
// The texture in graphics memory may not match the dimensions or frame count that bmpman reports for
|
||||
// this handle, e.g. when mipmap levels are culled at lower texture detail settings, so size the
|
||||
// readback from what OpenGL will actually write, not from the bitmap or the texture cache slot.
|
||||
GLint gl_width = 0, gl_height = 0, gl_layers = 0;
|
||||
glGetTexLevelParameteriv(ts->texture_target, 0, GL_TEXTURE_WIDTH, &gl_width);
|
||||
glGetTexLevelParameteriv(ts->texture_target, 0, GL_TEXTURE_HEIGHT, &gl_height);
|
||||
glGetTexLevelParameteriv(ts->texture_target, 0, GL_TEXTURE_DEPTH, &gl_layers);
|
||||
|
||||
if ( (gl_width < 1) || (gl_height < 1) || (gl_layers < 1) || (array_index >= static_cast<uint32_t>(gl_layers)) ) {
|
||||
mprintf(("Cannot read bitmap %d (%s) back from its texture; OpenGL reports level 0 as %dx%d with %d layer(s) but layer %u is needed.\n",
|
||||
bitmap_num, bm_get_filename(bitmap_num), gl_width, gl_height, gl_layers, array_index));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// The size of a single frame in the array
|
||||
auto slice_size = ts->w * ts->h * bytes_per_pixel;
|
||||
std::unique_ptr<std::uint8_t[]> buffer(new std::uint8_t[num_frames * slice_size]);
|
||||
// At maximum texture detail no culling occurs on upload, so the texture must match the dimensions
|
||||
// recorded in the texture cache slot; if this trips, we have another slot mismatch.
|
||||
Assertion((Detail.hardware_textures < 4) || ((gl_width == ts->w) && (gl_height == ts->h)),
|
||||
"Texture readback size mismatch for bitmap %d (%s): OpenGL reports level 0 as %dx%d but the texture cache slot expects %dx%d.",
|
||||
bitmap_num, bm_get_filename(bitmap_num), gl_width, gl_height, ts->w, ts->h);
|
||||
|
||||
Assertion(ts->texture_target == GL_TEXTURE_2D_ARRAY, "Unexpected texture target encountered!");
|
||||
// The size of a single layer in the array
|
||||
size_t slice_size = static_cast<size_t>(gl_width) * gl_height * bytes_per_pixel;
|
||||
|
||||
// We can't read a specific layer of the texture so we need to read the entire texture and then memcpy the right part from that...
|
||||
// Some drivers write tiny compressed textures back in whole 4x4 block granularity, so pad each layer
|
||||
// of the staging buffer to full blocks to keep any such overwrite within bounds.
|
||||
size_t padded_slice_size = static_cast<size_t>((gl_width + 3) & ~3) * ((gl_height + 3) & ~3) * bytes_per_pixel;
|
||||
std::unique_ptr<std::uint8_t[]> buffer(new std::uint8_t[padded_slice_size * gl_layers]);
|
||||
|
||||
// Copy the entire texture level into the bitmap
|
||||
glGetTexImage(ts->texture_target, 0, pixel_format, data_format, buffer.get());
|
||||
|
||||
auto buffer_offset = array_index * slice_size;
|
||||
memcpy(data_out, buffer.get() + buffer_offset, slice_size);
|
||||
auto data_out = reinterpret_cast<ubyte*>(vm_malloc(slice_size));
|
||||
memcpy(data_out, buffer.get() + array_index * slice_size, slice_size);
|
||||
|
||||
*width_out = gl_width;
|
||||
*height_out = gl_height;
|
||||
return data_out;
|
||||
}
|
||||
|
||||
void gr_opengl_get_texture_scale(int bitmap_handle, float *u_scale, float *v_scale)
|
||||
@@ -1494,7 +1521,7 @@ size_t opengl_export_render_target( int slot, int width, int height, int alpha,
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ( (ts->w != width) && (ts->h != height) ) {
|
||||
if ( (ts->w != width) || (ts->h != height) ) {
|
||||
mprintf(("OpenGL ERROR: Passed width and height do not match values for texture!\n"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ void opengl_preload_init();
|
||||
void opengl_kill_render_target(bitmap_slot* slot);
|
||||
int opengl_make_render_target(int handle, int *w, int *h, int *bpp, int *mm_lvl, int flags);
|
||||
int opengl_set_render_target(int slot, int face = -1, int is_static = 0);
|
||||
void gr_opengl_get_bitmap_from_texture(void* data_out, int bitmap_num);
|
||||
ubyte* gr_opengl_get_bitmap_from_texture(int bitmap_num, int* width_out, int* height_out);
|
||||
size_t opengl_export_render_target( int slot, int width, int height, int alpha, int num_mipmaps, ubyte *image_data );
|
||||
void opengl_set_texture_target(GLenum target = GL_TEXTURE_2D);
|
||||
void opengl_set_texture_face(GLenum face = GL_TEXTURE_2D);
|
||||
|
||||
@@ -106,7 +106,12 @@ void stub_clear_states() {}
|
||||
|
||||
void stub_update_texture(int /*bitmap_handle*/, int /*bpp*/, const ubyte* /*data*/, int /*width*/, int /*height*/) {}
|
||||
|
||||
void stub_get_bitmap_from_texture(void* /*data_out*/, int /*bitmap_num*/) {}
|
||||
ubyte* stub_get_bitmap_from_texture(int /*bitmap_num*/, int* width_out, int* height_out)
|
||||
{
|
||||
*width_out = 0;
|
||||
*height_out = 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int stub_bm_make_render_target(int /*n*/, int* /*width*/, int* /*height*/, int* /*bpp*/, int* /*mm_lvl*/, int /*flags*/)
|
||||
{
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
#include "hud/hud.h"
|
||||
#include "utils/unicode.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#define MAX_TEXT_STREAMS 2 // how many concurrent streams of text can be displayed
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
@@ -163,15 +165,54 @@ public:
|
||||
bool draw_grid;
|
||||
color grid_color;
|
||||
|
||||
brief_stage( )
|
||||
: camera_time( 0 ), flags( 0 ), formula( -1 ), num_icons(0), icons(nullptr), num_lines(0), lines(nullptr),
|
||||
draw_grid(true)
|
||||
{
|
||||
brief_stage( )
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
// Restore this stage to its default-constructed state. Does not free
|
||||
// the icons/lines buffers; the caller is responsible for those.
|
||||
void reset()
|
||||
{
|
||||
text.clear();
|
||||
voice[ 0 ] = 0;
|
||||
camera_pos = vmd_zero_vector;
|
||||
camera_orient = vmd_identity_matrix;
|
||||
camera_time = 0;
|
||||
flags = 0;
|
||||
formula = -1;
|
||||
num_icons = 0;
|
||||
icons = nullptr;
|
||||
num_lines = 0;
|
||||
lines = nullptr;
|
||||
draw_grid = true;
|
||||
grid_color = Color_briefing_grid;
|
||||
}
|
||||
|
||||
// icons and lines are raw owning pointers, so the memberwise copy/move
|
||||
// operations would alias those buffers across two stages; use swap() to
|
||||
// rearrange stages, or copy the buffer contents explicitly
|
||||
brief_stage(const brief_stage&) = delete;
|
||||
brief_stage& operator=(const brief_stage&) = delete;
|
||||
brief_stage(brief_stage&&) = delete;
|
||||
brief_stage& operator=(brief_stage&&) = delete;
|
||||
|
||||
friend void swap(brief_stage& a, brief_stage& b)
|
||||
{
|
||||
std::swap(a.text, b.text);
|
||||
std::swap(a.voice, b.voice);
|
||||
std::swap(a.camera_pos, b.camera_pos);
|
||||
std::swap(a.camera_orient, b.camera_orient);
|
||||
std::swap(a.camera_time, b.camera_time);
|
||||
std::swap(a.flags, b.flags);
|
||||
std::swap(a.formula, b.formula);
|
||||
std::swap(a.num_icons, b.num_icons);
|
||||
std::swap(a.icons, b.icons);
|
||||
std::swap(a.num_lines, b.num_lines);
|
||||
std::swap(a.lines, b.lines);
|
||||
std::swap(a.draw_grid, b.draw_grid);
|
||||
std::swap(a.grid_color, b.grid_color);
|
||||
}
|
||||
};
|
||||
|
||||
class debrief_stage
|
||||
|
||||
@@ -4810,6 +4810,12 @@ int parse_wing_create_ships( wing *wingp, int num_to_create, bool force_create,
|
||||
Ship_registry_map[p_objp->name] = static_cast<int>(Ship_registry.size() - 1);
|
||||
}
|
||||
}
|
||||
// make sure we still handle wing display names in a single-wave wing
|
||||
else if (wingp->has_display_name())
|
||||
{
|
||||
wing_bash_ship_name(p_objp->display_name, wingp->get_display_name(), wingp->total_arrived_count + wingp->red_alert_skipped_ships);
|
||||
p_objp->flags.set(Mission::Parse_Object_Flags::SF_Has_display_name);
|
||||
}
|
||||
|
||||
// also, if multiplayer, set the parse object's net signature to be wing's net signature
|
||||
// base + total_arrived_count (before adding 1)
|
||||
|
||||
@@ -268,7 +268,7 @@ int Fred_campaign_save::save_campaign_file(const char* pathname, const SCP_vecto
|
||||
fout(")");
|
||||
}
|
||||
if (mission_fork) {
|
||||
fout("Option: ", Campaign.missions[link.to].name);
|
||||
fout("\n+Option: %s", Campaign.missions[link.to].name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// methods and members common to any mission editor FSO may have
|
||||
#include "common.h"
|
||||
#include "globalincs/linklist.h"
|
||||
#include "mission/missionparse.h"
|
||||
#include "iff_defs/iff_defs.h"
|
||||
#include "object/object.h"
|
||||
#include "ship/ship.h"
|
||||
|
||||
// to keep track of data
|
||||
@@ -145,3 +147,22 @@ void generate_weaponry_usage_list_wing(int wing_num, int* arr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ensure_valid_player_start_shipnum()
|
||||
{
|
||||
// nothing to do if the current player start is still valid
|
||||
if (Player_start_shipnum >= 0 && Player_start_shipnum < MAX_SHIPS
|
||||
&& Ships[Player_start_shipnum].objnum >= 0
|
||||
&& Objects[Ships[Player_start_shipnum].objnum].type == OBJ_START) {
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise repoint to the first remaining player start, or -1 if there are none
|
||||
Player_start_shipnum = -1;
|
||||
for (auto *objp : list_range(&obj_used_list)) {
|
||||
if (objp->type == OBJ_START) {
|
||||
Player_start_shipnum = objp->instance;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,3 +48,8 @@ anchor_t target_to_anchor(int target);
|
||||
void generate_weaponry_usage_list_team(int team, int* arr);
|
||||
|
||||
void generate_weaponry_usage_list_wing(int wing_num, int* arr);
|
||||
|
||||
// If Player_start_shipnum no longer refers to a valid player start ship, repoint it to the
|
||||
// first remaining player start in the mission (or -1 if there are none). Call this after
|
||||
// changing a ship to or from an OBJ_START via demotion, deletion, etc.
|
||||
void ensure_valid_player_start_shipnum();
|
||||
@@ -2858,22 +2858,29 @@ int Fred_mission_save::save_mission_info()
|
||||
// this is compatible with non-SCP variants - Goober5000
|
||||
fout(" %d", (The_mission.support_ships.max_support_ships == 0) ? 1 : 0);
|
||||
|
||||
// version-gated support options; see also check_for_25_1_data()
|
||||
auto more_support_options_version = gameversion::version(25, 1);
|
||||
|
||||
if (save_config.save_format != MissionFormat::RETAIL) {
|
||||
if (optional_string_fred("+Disallow Support Rearm:")) {
|
||||
parse_comments(2);
|
||||
} else {
|
||||
fout("\n+Disallow Support Rearm:");
|
||||
if (The_mission.required_fso_version >= more_support_options_version) {
|
||||
if (optional_string_fred("+Disallow Support Rearm:")) {
|
||||
parse_comments(2);
|
||||
} else {
|
||||
fout("\n+Disallow Support Rearm:");
|
||||
}
|
||||
|
||||
fout(" %d", The_mission.support_ships.disallow_rearm ? 1 : 0);
|
||||
}
|
||||
|
||||
fout(" %d", The_mission.support_ships.disallow_rearm ? 1 : 0);
|
||||
if (The_mission.required_fso_version >= more_support_options_version) {
|
||||
if (optional_string_fred("+Allow Support Rearm Weapon Precedence:")) {
|
||||
parse_comments(2);
|
||||
} else {
|
||||
fout("\n+Allow Support Rearm Weapon Precedence:");
|
||||
}
|
||||
|
||||
if (optional_string_fred("+Allow Support Rearm Weapon Precedence:")) {
|
||||
parse_comments(2);
|
||||
} else {
|
||||
fout("\n+Allow Support Rearm Weapon Precedence:");
|
||||
fout(" %d", The_mission.support_ships.allow_rearm_weapon_precedence ? 1 : 0);
|
||||
}
|
||||
|
||||
fout(" %d", The_mission.support_ships.allow_rearm_weapon_precedence ? 1 : 0);
|
||||
}
|
||||
|
||||
// here be WMCoolmon's hull and subsys repair stuff
|
||||
@@ -2892,12 +2899,14 @@ int Fred_mission_save::save_mission_info()
|
||||
}
|
||||
fout(" %f", The_mission.support_ships.max_subsys_repair_val);
|
||||
|
||||
if (optional_string_fred("+Support Rearm Pool From Loadout:")) {
|
||||
parse_comments(1);
|
||||
} else {
|
||||
fout("\n+Support Rearm Pool From Loadout:");
|
||||
if (The_mission.required_fso_version >= more_support_options_version) {
|
||||
if (optional_string_fred("+Support Rearm Pool From Loadout:")) {
|
||||
parse_comments(1);
|
||||
} else {
|
||||
fout("\n+Support Rearm Pool From Loadout:");
|
||||
}
|
||||
fout(" %d", The_mission.support_ships.rearm_pool_from_loadout ? 1 : 0);
|
||||
}
|
||||
fout(" %d", The_mission.support_ships.rearm_pool_from_loadout ? 1 : 0);
|
||||
}
|
||||
|
||||
if (Mission_all_attack) {
|
||||
@@ -3602,7 +3611,7 @@ int Fred_mission_save::save_objects()
|
||||
fout(" %s", save_config.fred_alt_names[i]);
|
||||
}
|
||||
|
||||
Assertion(save_config.fred_callsigns != nullptr, "save_config.fred_alt_names is null!");
|
||||
Assertion(save_config.fred_callsigns != nullptr, "save_config.fred_callsigns is null!");
|
||||
|
||||
// optional callsign
|
||||
if (save_config.save_format != MissionFormat::RETAIL && strlen(save_config.fred_callsigns[i])) {
|
||||
@@ -4390,8 +4399,7 @@ int Fred_mission_save::save_players()
|
||||
for (i = 0; i < Num_teams; i++) {
|
||||
required_string_fred("$Starting Shipname:");
|
||||
parse_comments();
|
||||
Assert(Player_start_shipnum >= 0);
|
||||
fout(" %s", Ships[Player_start_shipnum].ship_name);
|
||||
fout(" %s", (Player_start_shipnum >= 0) ? Ships[Player_start_shipnum].ship_name : "<none>");
|
||||
|
||||
if (save_config.save_format != MissionFormat::RETAIL) {
|
||||
if (Team_data[i].do_not_validate) {
|
||||
@@ -4509,7 +4517,23 @@ int Fred_mission_save::save_players()
|
||||
|
||||
fout(")");
|
||||
|
||||
if (save_config.save_format != MissionFormat::RETAIL && !The_mission.support_ships.rearm_pool_from_loadout) {
|
||||
// version-gated support options; see also check_for_25_1_data()
|
||||
auto more_support_options_version = gameversion::version(25, 1);
|
||||
|
||||
bool has_rearm_pool_entries = false;
|
||||
if (save_config.save_format != MissionFormat::RETAIL && The_mission.required_fso_version >= more_support_options_version && !The_mission.support_ships.rearm_pool_from_loadout) {
|
||||
for (j = 0; j < weapon_info_size(); j++) {
|
||||
// mirror the skip conditions used when writing the pool below
|
||||
if (!Weapon_info[j].wi_flags[Weapon::Info_Flags::Player_allowed] || Weapon_info[j].disallow_rearm) {
|
||||
continue;
|
||||
}
|
||||
if (The_mission.support_ships.rearm_weapon_pool[i][j] != -1) {
|
||||
has_rearm_pool_entries = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (has_rearm_pool_entries) {
|
||||
if (optional_string_fred("+Support Rearm Pool:", "$Starting Shipname:")) {
|
||||
parse_comments(2);
|
||||
} else {
|
||||
|
||||
@@ -821,7 +821,7 @@ void brief_compact_stages()
|
||||
|
||||
Briefing->stages[num].num_icons = 0;
|
||||
for ( i = num+1; i < Briefing->num_stages; i++ ) {
|
||||
Briefing->stages[i-1] = Briefing->stages[i];
|
||||
swap(Briefing->stages[i-1], Briefing->stages[i]);
|
||||
}
|
||||
Briefing->num_stages--;
|
||||
continue;
|
||||
@@ -832,7 +832,7 @@ void brief_compact_stages()
|
||||
// completely clear out the old entries (if any) so we don't access them by mistake - taylor
|
||||
if ( before > Briefing->num_stages ) {
|
||||
for ( i = Briefing->num_stages; i < before; i++ ) {
|
||||
Briefing->stages[i] = brief_stage();
|
||||
Briefing->stages[i].reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1560,8 +1560,12 @@ int restore_wss_data(ubyte *data)
|
||||
|
||||
void draw_model_icon(int model_id, uint64_t flags, int x, int y, int w, int h, ship_info* sip, weapon_info* wip, float zoom_multiplier, int resize_mode)
|
||||
{
|
||||
// Can't draw a non-model
|
||||
if (model_id < 0)
|
||||
return;
|
||||
|
||||
lighting_profiles::set_non_mission_profile non_mission_lighting_profile;
|
||||
|
||||
|
||||
matrix object_orient = IDENTITY_MATRIX;
|
||||
angles rot_angles = vmd_zero_angles;
|
||||
|
||||
|
||||
@@ -1697,6 +1697,9 @@ void draw_ship_icon_with_number(int screen_offset, int ship_class)
|
||||
ss_icon->model_index = model_load(sip, true);
|
||||
mprintf(("SL WARNING: Had to attempt to page in model for %s paged in manually! Result: %d\n", sip->name, ss_icon->model_index));
|
||||
}
|
||||
// the icon bitmap can be invalid without a color having been chosen, e.g. when the icon ani is missing frames
|
||||
if (color_to_draw == nullptr)
|
||||
color_to_draw = &Icon_colors[ICON_FRAME_NORMAL];
|
||||
gr_set_color_fast(color_to_draw);
|
||||
|
||||
graphics::line_draw_list line_draw_list;
|
||||
@@ -2184,6 +2187,7 @@ void draw_wing_block(int wb_num, int hot_slot, int selected_slot, int class_sele
|
||||
GR_DEBUG_SCOPE("Single ship");
|
||||
|
||||
bitmap_to_draw = -1;
|
||||
color_to_draw = nullptr;
|
||||
ws = &wb->ss_slots[i];
|
||||
slot_index = wb_num*MAX_WING_SLOTS + i;
|
||||
|
||||
|
||||
@@ -3132,6 +3132,9 @@ void wl_render_icon(int index, int x, int y, int num, int draw_num_flag, int hot
|
||||
}
|
||||
else
|
||||
{
|
||||
// the icon bitmap can be invalid without a color having been chosen, e.g. when the icon ani is missing frames
|
||||
if (color_to_draw == nullptr)
|
||||
color_to_draw = &Icon_colors[ICON_FRAME_NORMAL];
|
||||
gr_set_color_fast(color_to_draw);
|
||||
|
||||
graphics::line_draw_list line_draw_list;
|
||||
|
||||
@@ -1300,7 +1300,7 @@ void determine_submodel_movement(bool is_rotation, const char *filename, bsp_inf
|
||||
|
||||
if (in(p, props, axis_string))
|
||||
{
|
||||
if (get_user_vec3d_value(p + 20, movement_axis, true, sm->name, filename))
|
||||
if (get_user_vec3d_value(p + strlen(axis_string), movement_axis, true, sm->name, filename))
|
||||
{
|
||||
if (!fl_near_zero(vm_vec_mag(movement_axis)))
|
||||
vm_vec_normalize(movement_axis);
|
||||
@@ -4075,7 +4075,7 @@ void submodel_canonicalize_translation(bsp_info *sm, submodel_instance *smi)
|
||||
smi->canonical_prev_offset = smi->canonical_offset;
|
||||
|
||||
// get the vector
|
||||
switch (sm->rotation_axis_id)
|
||||
switch (sm->translation_axis_id)
|
||||
{
|
||||
case MOVEMENT_AXIS_X:
|
||||
vm_vec_copy_scale(&smi->canonical_offset, &vmd_x_vector, smi->cur_offset);
|
||||
|
||||
+1
-1
@@ -12808,7 +12808,7 @@ int sexp_is_iff_or_species(int n, bool iff)
|
||||
// ship is in the EXITED state but probably in the process of exploding
|
||||
else if (oswpt.has_shipp())
|
||||
{
|
||||
UNREACHABLE("With the addition of the ShipStatus::DEATH_ROLL state, this shouldn't happen");
|
||||
Assertion(false, "With the addition of the ShipStatus::DEATH_ROLL state, this shouldn't happen");
|
||||
return SEXP_KNOWN_FALSE;
|
||||
}
|
||||
// ship has vanished
|
||||
|
||||
@@ -243,6 +243,15 @@ namespace internal {
|
||||
SCP_vector<int> handles;
|
||||
|
||||
for (auto const &name: bitmap_strings) {
|
||||
// FRED never renders or pages in particle effects,
|
||||
// so skip the (potentially expensive) animation loading here.
|
||||
// Still record placeholder handle so the effect stays "defined" and
|
||||
// can be resolved by name from the weapons/ships tables without spurious errors.
|
||||
if (Fred_running) {
|
||||
handles.push_back(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto handle = bm_load_animation(name.c_str());
|
||||
if (handle >= 0) {
|
||||
handles.push_back(handle);
|
||||
|
||||
+3
-6
@@ -473,9 +473,8 @@ int prop_create(const matrix* orient, const vec3d* pos, int prop_type, const cha
|
||||
Error(LOCATION, "Cannot create prop %s; pof file is not valid", pip->name.c_str());
|
||||
return -1;
|
||||
}
|
||||
if (pip->model_num == -1) {
|
||||
pip->model_num = model_load(pip->pof_file.c_str());
|
||||
}
|
||||
|
||||
pip->model_num = model_load(pip->pof_file.c_str());
|
||||
|
||||
polymodel* pm = model_get(pip->model_num);
|
||||
|
||||
@@ -613,9 +612,7 @@ static void prop_model_change(int n, int prop_type)
|
||||
//polymodel_instance* pmi = model_get_instance(sp->model_instance_num);
|
||||
|
||||
// get new model
|
||||
if (sip->model_num == -1) {
|
||||
sip->model_num = model_load(sip->pof_file.c_str());
|
||||
}
|
||||
sip->model_num = model_load(sip->pof_file.c_str());
|
||||
|
||||
polymodel* pm = model_get(sip->model_num);
|
||||
Objects[sp->objnum].radius = model_get_radius(pm->id);
|
||||
|
||||
@@ -55,11 +55,12 @@ ADE_INDEXER(l_WeaponBankType, "number Index", "Array of weapon banks", "weaponba
|
||||
idx--; //Lua->FS2
|
||||
|
||||
if(ADE_SETTING_VAR && newbank && newbank->isValid()) {
|
||||
sb->sw->primary_bank_weapons[idx] = newbank->typeh.sw->primary_bank_weapons[idx];
|
||||
sb->sw->next_primary_fire_stamp[idx] = timestamp(0);
|
||||
sb->sw->primary_bank_ammo[idx] = newbank->typeh.sw->primary_bank_ammo[idx];
|
||||
sb->sw->primary_bank_start_ammo[idx] = newbank->typeh.sw->primary_bank_start_ammo[idx];
|
||||
sb->sw->primary_bank_capacity[idx] = newbank->typeh.sw->primary_bank_capacity[idx];
|
||||
sb->sw->secondary_bank_weapons[idx] = newbank->typeh.sw->secondary_bank_weapons[idx];
|
||||
sb->sw->next_secondary_fire_stamp[idx] = timestamp(0);
|
||||
sb->sw->secondary_bank_ammo[idx] = newbank->typeh.sw->secondary_bank_ammo[idx];
|
||||
sb->sw->secondary_bank_start_ammo[idx] = newbank->typeh.sw->secondary_bank_start_ammo[idx];
|
||||
sb->sw->secondary_bank_capacity[idx] = newbank->typeh.sw->secondary_bank_capacity[idx];
|
||||
sb->sw->secondary_bank_rearm_time[idx] = timestamp(0);
|
||||
}
|
||||
break;
|
||||
case SWH_TERTIARY:
|
||||
|
||||
@@ -6914,6 +6914,25 @@ void ship_level_init()
|
||||
Man_thruster_reset_timestamp = timestamp(0);
|
||||
}
|
||||
|
||||
void ship_level_close()
|
||||
{
|
||||
// at this point ships have all gone through ship_delete;
|
||||
// clean up any ships that are still present so we don't
|
||||
// leave any stray references behind (e.g. for sexps)
|
||||
for (auto &ship_entry : Ship_registry)
|
||||
{
|
||||
if (ship_entry.status == ShipStatus::PRESENT || ship_entry.status == ShipStatus::DEATH_ROLL)
|
||||
{
|
||||
ship_entry.cleanup_mode = (ship_entry.status == ShipStatus::DEATH_ROLL) ? SHIP_DESTROYED : SHIP_PRESENT_AT_MISSION_END;
|
||||
ship_entry.status = ShipStatus::EXITED;
|
||||
ship_entry.objnum = -1;
|
||||
ship_entry.shipnum = -1;
|
||||
}
|
||||
}
|
||||
|
||||
ship_close_cockpit_displays(Player_ship);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a ship onto the exited ships list.
|
||||
*
|
||||
@@ -8745,6 +8764,7 @@ void ship_close_cockpit_displays(ship* shipp)
|
||||
{
|
||||
if (shipp && shipp->cockpit_model_instance >= 0) {
|
||||
model_delete_instance(shipp->cockpit_model_instance);
|
||||
shipp->cockpit_model_instance = -1;
|
||||
}
|
||||
|
||||
for ( int i = 0; i < (int)Player_displays.size(); i++ ) {
|
||||
|
||||
+9
-7
@@ -1688,6 +1688,7 @@ extern SCP_vector<wing_formation> Wing_formations;
|
||||
|
||||
extern void ship_init(); // called once at game start
|
||||
extern void ship_level_init(); // called before the start of each level
|
||||
extern void ship_level_close(); // called after the end of each level
|
||||
|
||||
//returns -1 if failed
|
||||
extern int ship_create(matrix* orient, vec3d* pos, int ship_type, const char* ship_name = nullptr, bool standalone_ship = false);
|
||||
@@ -1700,13 +1701,14 @@ extern void ship_delete( object * objp );
|
||||
extern int ship_check_collision_fast( object * obj, object * other_obj, vec3d * hitpos );
|
||||
extern int ship_get_num_ships();
|
||||
|
||||
#define SHIP_VANISHED (1<<0)
|
||||
#define SHIP_DESTROYED (1<<1)
|
||||
#define SHIP_DEPARTED_WARP (1<<2)
|
||||
#define SHIP_DEPARTED_BAY (1<<3)
|
||||
#define SHIP_DEPARTED ( SHIP_DEPARTED_BAY | SHIP_DEPARTED_WARP )
|
||||
#define SHIP_DESTROYED_REDALERT (1<<4)
|
||||
#define SHIP_DEPARTED_REDALERT (1<<5)
|
||||
#define SHIP_VANISHED (1<<0)
|
||||
#define SHIP_DESTROYED (1<<1)
|
||||
#define SHIP_DEPARTED_WARP (1<<2)
|
||||
#define SHIP_DEPARTED_BAY (1<<3)
|
||||
#define SHIP_DEPARTED ( SHIP_DEPARTED_BAY | SHIP_DEPARTED_WARP )
|
||||
#define SHIP_DESTROYED_REDALERT (1<<4)
|
||||
#define SHIP_DEPARTED_REDALERT (1<<5)
|
||||
#define SHIP_PRESENT_AT_MISSION_END (1<<6)
|
||||
|
||||
/**
|
||||
* @brief Deletes and de-inits a ship.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "Random.h"
|
||||
#include "RandomRange.h"
|
||||
|
||||
#include <limits>
|
||||
#include <random>
|
||||
@@ -8,6 +9,9 @@
|
||||
#include "globalincs/pstypes.h"
|
||||
|
||||
namespace util {
|
||||
|
||||
thread_local std::mt19937 seeder {std::random_device()()};
|
||||
|
||||
namespace {
|
||||
template <typename RngType>
|
||||
class RandomImpl {
|
||||
|
||||
@@ -54,6 +54,10 @@ size_t parse_number_list(T (&list)[N])
|
||||
}
|
||||
} // namespace
|
||||
|
||||
//Sampling a random_device is REALLY expensive.
|
||||
//Instead of sampling one for each seed, create a pseudorandom seeder which is initialized ONCE from a random_device.
|
||||
extern thread_local std::mt19937 seeder;
|
||||
|
||||
/**
|
||||
* @brief Generic class for generating numbers in a specific range
|
||||
*
|
||||
@@ -77,10 +81,6 @@ class RandomRange {
|
||||
ValueType m_minValue;
|
||||
ValueType m_maxValue;
|
||||
|
||||
//Sampling a random_device is REALLY expensive.
|
||||
//Instead of sampling one for each seed, create a pseudorandom seeder which is initialized ONCE from a random_device.
|
||||
inline static thread_local std::mt19937 seeder {std::random_device()()};
|
||||
|
||||
public:
|
||||
template <typename T, typename... Ts, typename = typename std::enable_if<(sizeof... (Ts) >=1 || !std::is_convertible<T, ValueType>::value) && !std::is_same_v<std::decay_t<T>, RandomRange>, int>::type>
|
||||
explicit RandomRange(T&& distributionFirstParameter, Ts&&... distributionParameters)
|
||||
|
||||
@@ -308,7 +308,13 @@ void shockwave_move(object *shockwave_objp, float frametime)
|
||||
CLAMP(sw->radius, 0.0f, sw->outer_radius);
|
||||
}
|
||||
|
||||
if ( sw->time_elapsed > sw->total_time ) {
|
||||
// It is possible the shockwave may have lifetime shorter than one frame (e.g. high speed, small radius),
|
||||
// which by default results in shockwave applying no damage.
|
||||
// Provide optional fix to ensure shockwave is not killed until after this frame's damage pass has run.
|
||||
bool sw_expired = sw->time_elapsed > sw->total_time;
|
||||
bool sw_expire_fix = The_mission.ai_profile->flags[AI::Profile_Flags::Fix_shockwave_expire_before_do_damage];
|
||||
|
||||
if ( sw_expired && !sw_expire_fix ) {
|
||||
shockwave_objp->flags.set(Object::Object_Flags::Should_be_dead);
|
||||
return;
|
||||
}
|
||||
@@ -443,6 +449,12 @@ void shockwave_move(object *shockwave_objp, float frametime)
|
||||
}
|
||||
|
||||
} // end for
|
||||
|
||||
// See the sw_expired check above: shockwave has now applied its final damage pass this frame, so let it die.
|
||||
// With the flag off we already returned before the loop.
|
||||
if ( sw_expired && sw_expire_fix ) {
|
||||
shockwave_objp->flags.set(Object::Object_Flags::Should_be_dead);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+87
-27
@@ -432,7 +432,18 @@ void briefing_editor_dlg::update_data(int update)
|
||||
|
||||
if (m_last_icon >= 0) {
|
||||
valid = (m_id != ptr->icons[m_last_icon].id);
|
||||
if (m_id >= 0) {
|
||||
if (m_id < 0) {
|
||||
// briefing icon ids must never be negative; revert to the previous id
|
||||
if (valid) {
|
||||
char msg[1024];
|
||||
|
||||
sprintf(msg, "Icon ID #%d is invalid. Briefing icon IDs cannot be negative.\n"
|
||||
"Icon ID has been reset back to %d", m_id, ptr->icons[m_last_icon].id);
|
||||
|
||||
m_id = ptr->icons[m_last_icon].id;
|
||||
MessageBox(msg);
|
||||
}
|
||||
} else {
|
||||
if (valid && !m_change_local) {
|
||||
for (i=m_last_stage+1; i<Briefing->num_stages; i++) {
|
||||
if (find_icon(m_id, i) >= 0) {
|
||||
@@ -473,7 +484,7 @@ void briefing_editor_dlg::update_data(int update)
|
||||
|
||||
string_copy(buf, m_icon_label, MAX_LABEL_LEN - 1);
|
||||
lcl_fred_replace_stuff(buf, MAX_LABEL_LEN - 1);
|
||||
if (stricmp(ptr->icons[m_last_icon].label, buf) && !m_change_local) {
|
||||
if (strcmp(ptr->icons[m_last_icon].label, buf) && !m_change_local) {
|
||||
set_modified();
|
||||
reset_icon_loop(m_last_stage);
|
||||
while (get_next_icon(m_id))
|
||||
@@ -483,7 +494,7 @@ void briefing_editor_dlg::update_data(int update)
|
||||
|
||||
string_copy(buf, m_icon_closeup_label, MAX_LABEL_LEN - 1);
|
||||
lcl_fred_replace_stuff(buf, MAX_LABEL_LEN - 1);
|
||||
if (stricmp(ptr->icons[m_last_icon].closeup_label, buf) && !m_change_local) {
|
||||
if (strcmp(ptr->icons[m_last_icon].closeup_label, buf) && !m_change_local) {
|
||||
set_modified();
|
||||
reset_icon_loop(m_last_stage);
|
||||
while (get_next_icon(m_id))
|
||||
@@ -491,28 +502,33 @@ void briefing_editor_dlg::update_data(int update)
|
||||
}
|
||||
strcpy_s(ptr->icons[m_last_icon].closeup_label, buf);
|
||||
|
||||
if (m_icon_scale > 0)
|
||||
ptr->icons[m_last_icon].scale_factor = m_icon_scale / 100.0f;
|
||||
if (m_icon_scale > 0) {
|
||||
float new_scale = m_icon_scale / 100.0f;
|
||||
if ((ptr->icons[m_last_icon].scale_factor != new_scale) && !m_change_local) {
|
||||
set_modified();
|
||||
reset_icon_loop(m_last_stage);
|
||||
while (get_next_icon(m_id))
|
||||
iconp->scale_factor = new_scale;
|
||||
}
|
||||
ptr->icons[m_last_icon].scale_factor = new_scale;
|
||||
}
|
||||
|
||||
if ( m_hilight )
|
||||
ptr->icons[m_last_icon].flags |= BI_HIGHLIGHT;
|
||||
else
|
||||
ptr->icons[m_last_icon].flags &= ~BI_HIGHLIGHT;
|
||||
// build the new flags for this icon from the checkboxes, preserving any non-editable bits
|
||||
const int editable_flags = BI_HIGHLIGHT | BI_MIRROR_ICON | BI_USE_WING_ICON | BI_USE_CARGO_ICON;
|
||||
int new_flags = ptr->icons[m_last_icon].flags;
|
||||
if (m_hilight) new_flags |= BI_HIGHLIGHT; else new_flags &= ~BI_HIGHLIGHT;
|
||||
if (m_flipicon) new_flags |= BI_MIRROR_ICON; else new_flags &= ~BI_MIRROR_ICON;
|
||||
if (m_use_wing) new_flags |= BI_USE_WING_ICON; else new_flags &= ~BI_USE_WING_ICON;
|
||||
if (m_use_cargo) new_flags |= BI_USE_CARGO_ICON; else new_flags &= ~BI_USE_CARGO_ICON;
|
||||
|
||||
if (m_flipicon)
|
||||
ptr->icons[m_last_icon].flags |= BI_MIRROR_ICON;
|
||||
else
|
||||
ptr->icons[m_last_icon].flags &= ~BI_MIRROR_ICON;
|
||||
|
||||
if (m_use_wing)
|
||||
ptr->icons[m_last_icon].flags |= BI_USE_WING_ICON;
|
||||
else
|
||||
ptr->icons[m_last_icon].flags &= ~BI_USE_WING_ICON;
|
||||
|
||||
if (m_use_cargo)
|
||||
ptr->icons[m_last_icon].flags |= BI_USE_CARGO_ICON;
|
||||
else
|
||||
ptr->icons[m_last_icon].flags &= ~BI_USE_CARGO_ICON;
|
||||
if ((ptr->icons[m_last_icon].flags != new_flags) && !m_change_local) {
|
||||
set_modified();
|
||||
reset_icon_loop(m_last_stage);
|
||||
// apply only the editable bits to later icons, preserving their other flags
|
||||
while (get_next_icon(m_id))
|
||||
iconp->flags = (iconp->flags & ~editable_flags) | (new_flags & editable_flags);
|
||||
}
|
||||
ptr->icons[m_last_icon].flags = new_flags;
|
||||
|
||||
if ((ptr->icons[m_last_icon].type != m_icon_image) && !m_change_local) {
|
||||
set_modified();
|
||||
@@ -942,6 +958,7 @@ void briefing_editor_dlg::copy_stage(int from, int to)
|
||||
Briefing->stages[to].camera_time = 500;
|
||||
Briefing->stages[to].num_icons = 0;
|
||||
Briefing->stages[to].formula = Locked_sexp_true;
|
||||
Briefing->stages[to].draw_grid = true;
|
||||
Briefing->stages[to].grid_color = Color_briefing_grid;
|
||||
return;
|
||||
}
|
||||
@@ -956,6 +973,7 @@ void briefing_editor_dlg::copy_stage(int from, int to)
|
||||
Briefing->stages[to].num_icons = Briefing->stages[from].num_icons;
|
||||
Briefing->stages[to].num_lines = Briefing->stages[from].num_lines;
|
||||
Briefing->stages[to].formula = Briefing->stages[from].formula;
|
||||
Briefing->stages[to].draw_grid = Briefing->stages[from].draw_grid;
|
||||
// For now let's just always set this back to default. Eventually when we have a UI color picker in qtFRED, we can copy from stage to stage
|
||||
Briefing->stages[to].grid_color = Color_briefing_grid;
|
||||
|
||||
@@ -1089,12 +1107,13 @@ void briefing_editor_dlg::OnMakeIcon()
|
||||
|
||||
strncpy(biconp->label, name, len);
|
||||
biconp->label[len] = 0;
|
||||
biconp->closeup_label[0] = 0;
|
||||
// iconp->text[0] = 0;
|
||||
biconp->type = 0;
|
||||
biconp->team = team;
|
||||
biconp->pos = pos;
|
||||
biconp->flags = 0;
|
||||
biconp->id = Cur_brief_id++;
|
||||
biconp->id = get_unused_briefing_icon_id();
|
||||
biconp->scale_factor = 1.0f;
|
||||
|
||||
biconp->modelnum = -1;
|
||||
@@ -1198,7 +1217,7 @@ void briefing_editor_dlg::OnDeleteIcon()
|
||||
|
||||
void briefing_editor_dlg::delete_icon(int num)
|
||||
{
|
||||
int i, z;
|
||||
int i, l, z;
|
||||
|
||||
if (num < 0)
|
||||
num = m_cur_icon;
|
||||
@@ -1217,6 +1236,24 @@ void briefing_editor_dlg::delete_icon(int num)
|
||||
m_cur_icon = -1;
|
||||
update_data(1);
|
||||
obj_delete(icon_obj[num]);
|
||||
|
||||
// remove any lines that reference the icon being deleted
|
||||
i = Briefing->stages[m_cur_stage].num_lines;
|
||||
while (i--)
|
||||
if ((Briefing->stages[m_cur_stage].lines[i].start_icon == num) || (Briefing->stages[m_cur_stage].lines[i].end_icon == num)) {
|
||||
Briefing->stages[m_cur_stage].num_lines--;
|
||||
for (l=i; l<Briefing->stages[m_cur_stage].num_lines; l++)
|
||||
Briefing->stages[m_cur_stage].lines[l] = Briefing->stages[m_cur_stage].lines[l+1];
|
||||
}
|
||||
|
||||
// fix the indexes of lines that reference the icons being shifted down
|
||||
for (i=0; i<Briefing->stages[m_cur_stage].num_lines; i++) {
|
||||
if (Briefing->stages[m_cur_stage].lines[i].start_icon > num)
|
||||
Briefing->stages[m_cur_stage].lines[i].start_icon--;
|
||||
if (Briefing->stages[m_cur_stage].lines[i].end_icon > num)
|
||||
Briefing->stages[m_cur_stage].lines[i].end_icon--;
|
||||
}
|
||||
|
||||
for (i=num+1; i<Briefing->stages[m_cur_stage].num_icons; i++) {
|
||||
Briefing->stages[m_cur_stage].icons[i-1] = Briefing->stages[m_cur_stage].icons[i];
|
||||
icon_obj[i-1] = icon_obj[i];
|
||||
@@ -1282,10 +1319,13 @@ int briefing_editor_dlg::check_mouse_hit(int x, int y)
|
||||
return -1;
|
||||
}
|
||||
|
||||
void briefing_editor_dlg::OnPropagateIcons()
|
||||
void briefing_editor_dlg::OnPropagateIcons()
|
||||
{
|
||||
object *ptr;
|
||||
|
||||
// commit any pending edits in the icon fields before propagating the icons forward
|
||||
update_data(1);
|
||||
|
||||
ptr = GET_FIRST(&obj_used_list);
|
||||
while (ptr != END_OF_LIST(&obj_used_list)) {
|
||||
if ((ptr->type == OBJ_POINT) && (ptr->flags[Object::Object_Flags::Marked])) {
|
||||
@@ -1325,6 +1365,26 @@ int briefing_editor_dlg::find_icon(int id, int stage)
|
||||
return -1;
|
||||
}
|
||||
|
||||
// is the given id already used by any icon in any stage of the current briefing?
|
||||
bool briefing_editor_dlg::briefing_icon_id_used(int id)
|
||||
{
|
||||
for (int s=0; s<Briefing->num_stages; s++)
|
||||
if (find_icon(id, s) >= 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// find an id that isn't already used by an icon in the current briefing, advancing
|
||||
// Cur_brief_id past any values that are already taken (e.g. from manual id edits)
|
||||
int briefing_editor_dlg::get_unused_briefing_icon_id()
|
||||
{
|
||||
while (briefing_icon_id_used(Cur_brief_id))
|
||||
Cur_brief_id++;
|
||||
|
||||
return Cur_brief_id++;
|
||||
}
|
||||
|
||||
void briefing_editor_dlg::reset_icon_loop(int stage)
|
||||
{
|
||||
stage_loop = stage + 1;
|
||||
@@ -1337,7 +1397,7 @@ int briefing_editor_dlg::get_next_icon(int id)
|
||||
icon_loop++;
|
||||
if (icon_loop >= Briefing->stages[stage_loop].num_icons) {
|
||||
stage_loop++;
|
||||
if (stage_loop > Briefing->num_stages)
|
||||
if (stage_loop >= Briefing->num_stages)
|
||||
return 0;
|
||||
|
||||
icon_loop = -1;
|
||||
|
||||
@@ -34,6 +34,8 @@ public:
|
||||
void OnOK();
|
||||
void OnCancel();
|
||||
int find_icon(int id, int stage);
|
||||
bool briefing_icon_id_used(int id);
|
||||
int get_unused_briefing_icon_id();
|
||||
void propagate_icon(int num);
|
||||
void reset_editor();
|
||||
int check_mouse_hit(int x, int y);
|
||||
|
||||
@@ -1674,6 +1674,21 @@ int event_annotation_lookup(HTREEITEM handle)
|
||||
return -1;
|
||||
}
|
||||
|
||||
// The tree recreated (moved) or deleted the item for a node. Re-point any
|
||||
// annotation on the old handle to the new one so it follows the node; a null
|
||||
// new_handle means the node was deleted, which clears the handle. The path is
|
||||
// left intact, so an annotation cleared this way is dropped at save (its path
|
||||
// can no longer be rebuilt) but survives a Cancel (the path still resolves).
|
||||
void event_sexp_tree::on_node_handle_changed(HTREEITEM old_handle, HTREEITEM new_handle)
|
||||
{
|
||||
if (!old_handle)
|
||||
return;
|
||||
|
||||
int i = event_annotation_lookup(old_handle);
|
||||
if (i >= 0)
|
||||
Event_annotations[i].handle = new_handle;
|
||||
}
|
||||
|
||||
void event_annotation_swap_image(event_sexp_tree *tree, HTREEITEM handle, int annotation_index)
|
||||
{
|
||||
event_annotation_swap_image(tree, handle, Event_annotations[annotation_index]);
|
||||
|
||||
@@ -31,6 +31,10 @@ protected:
|
||||
virtual void PreSubclassWindow();
|
||||
virtual void OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult);
|
||||
|
||||
// keep event annotations attached to their node when the tree recreates or
|
||||
// deletes its handle (new_handle == nullptr means the node was deleted)
|
||||
void on_node_handle_changed(HTREEITEM old_handle, HTREEITEM new_handle) override;
|
||||
|
||||
CStringA m_tooltiptextA;
|
||||
CStringW m_tooltiptextW;
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "localization/fhash.h"
|
||||
#include "localization/localize.h"
|
||||
#include "mission/missiongoals.h"
|
||||
#include "mission/missiongrid.h"
|
||||
#include "mission/missionparse.h"
|
||||
#include "object/object.h"
|
||||
#include "render/3d.h"
|
||||
@@ -233,6 +234,14 @@ bool CFREDDoc::load_mission(const char *pathname, int flags) {
|
||||
Parse_viewer_pos = view_pos;
|
||||
Parse_viewer_orient = view_orient;
|
||||
|
||||
// preserve the editor grid across the reload; it's display state, not part of the mission,
|
||||
// but clear_mission() recreates it via fred_render_init(). (See the view_pos/view_orient handling above and below.)
|
||||
matrix grid_orient = The_grid->gmatrix;
|
||||
vec3d grid_center = The_grid->center;
|
||||
int grid_nrows = The_grid->nrows;
|
||||
int grid_ncols = The_grid->ncols;
|
||||
float grid_square_size = The_grid->square_size;
|
||||
|
||||
// activate the localizer hash table
|
||||
fhash_flush();
|
||||
|
||||
@@ -392,6 +401,10 @@ bool CFREDDoc::load_mission(const char *pathname, int flags) {
|
||||
|
||||
view_pos = Parse_viewer_pos;
|
||||
view_orient = Parse_viewer_orient;
|
||||
|
||||
// restore the editor grid that was preserved above
|
||||
create_grid(The_grid, &grid_orient.vec.fvec, &grid_orient.vec.rvec, &grid_center, grid_nrows, grid_ncols, grid_square_size);
|
||||
|
||||
set_modified(0);
|
||||
stars_post_level_init();
|
||||
|
||||
|
||||
+14
-6
@@ -1424,21 +1424,29 @@ int object_check_collision(object *objp, vec3d *p0, vec3d *p1, vec3d *hitpos) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// PROP HERE?
|
||||
if (objp->type == OBJ_PROP) {
|
||||
if (!Props[objp->instance].has_value())
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (objp->flags[Object::Object_Flags::Hidden, Object::Object_Flags::Locked_from_editing])
|
||||
return 0;
|
||||
|
||||
if ((Show_ship_models || Show_outlines) && (objp->type == OBJ_SHIP)) {
|
||||
mc.model_num = Ship_info[Ships[objp->instance].ship_info_index].model_num; // Fill in the model to check
|
||||
mc.model_instance_num = -1;
|
||||
|
||||
} else if ((Show_ship_models || Show_outlines) && (objp->type == OBJ_START)) {
|
||||
mc.model_num = Ship_info[Ships[objp->instance].ship_info_index].model_num; // Fill in the model to check
|
||||
if ((Show_ship_models || Show_outlines) && (objp->type == OBJ_SHIP || objp->type == OBJ_START)) {
|
||||
auto& shp = Ships[objp->instance];
|
||||
mc.model_num = Ship_info[shp.ship_info_index].model_num; // Fill in the model to check
|
||||
mc.model_instance_num = shp.model_instance_num;
|
||||
|
||||
} else if ((Show_ship_models || Show_outlines) && (objp->type == OBJ_PROP)) {
|
||||
auto& prp = Props[objp->instance].value();
|
||||
mc.model_num = Prop_info[prp.prop_info_index].model_num; // Fill in the model to check
|
||||
mc.model_instance_num = prp.model_instance_num;
|
||||
|
||||
} else
|
||||
return fvi_ray_sphere(hitpos, p0, p1, &objp->pos, (objp->radius > 0.1f) ? objp->radius : LOLLIPOP_SIZE);
|
||||
|
||||
mc.model_instance_num = -1;
|
||||
mc.orient = &objp->orient; // The object's orient
|
||||
mc.pos = &objp->pos; // The object's position
|
||||
mc.p0 = p0; // Point 1 of ray to check
|
||||
|
||||
+8
-2
@@ -32,6 +32,7 @@
|
||||
#include "ai/aigoals.h"
|
||||
#include "ship/ship.h" // for ship names
|
||||
#include "prop/prop.h" // for prop names
|
||||
#include "missioneditor/common.h"
|
||||
#include "MissionGoalsDlg.h"
|
||||
#include "MissionCutscenesDlg.h"
|
||||
#include "wing.h"
|
||||
@@ -2466,7 +2467,7 @@ int query_single_wing_marked()
|
||||
void CFREDView::OnDisbandWing()
|
||||
{
|
||||
if (query_single_wing_marked()) {
|
||||
remove_wing(cur_wing);
|
||||
disband_wing(cur_wing);
|
||||
FREDDoc_ptr->autosave("wing disband");
|
||||
|
||||
} else
|
||||
@@ -2641,6 +2642,9 @@ int CFREDView::global_error_check()
|
||||
ptr->type = OBJ_SHIP;
|
||||
Player_starts--;
|
||||
t--;
|
||||
|
||||
ensure_valid_player_start_shipnum();
|
||||
|
||||
if (error("Invalid ship type for a player. Ship has been reset to non-player ship.")){
|
||||
return 1;
|
||||
}
|
||||
@@ -3130,7 +3134,9 @@ int CFREDView::global_error_check()
|
||||
return -1;
|
||||
}*/
|
||||
|
||||
Assert((Player_start_shipnum >= 0) && (Player_start_shipnum < MAX_SHIPS) && (Ships[Player_start_shipnum].objnum >= 0));
|
||||
if ((Player_start_shipnum < 0) || (Player_start_shipnum >= MAX_SHIPS) || (Ships[Player_start_shipnum].objnum < 0)){
|
||||
return internal_error("Mission has no valid player start ship");
|
||||
}
|
||||
i = global_error_check_player_wings(multi);
|
||||
if (i){
|
||||
return i;
|
||||
|
||||
+2
-13
@@ -775,7 +775,7 @@ int create_object(vec3d *pos, int waypoint_instance, bool prop)
|
||||
int obj, n;
|
||||
|
||||
if (prop) {
|
||||
int prop_class = m_new_prop_type_combo_box.GetCurSel();
|
||||
int prop_class = m_new_prop_type_combo_box.GetPropClass(m_new_prop_type_combo_box.GetCurSel());
|
||||
if (prop_class < 0 || prop_class >= prop_info_size())
|
||||
return -1;
|
||||
|
||||
@@ -1235,7 +1235,6 @@ int common_object_delete(int obj)
|
||||
char msg[255];
|
||||
const char *name;
|
||||
int i, z, r, type;
|
||||
object *objp;
|
||||
SCP_list<CJumpNode>::iterator jnp;
|
||||
|
||||
type = Objects[obj].type;
|
||||
@@ -1271,17 +1270,7 @@ int common_object_delete(int obj)
|
||||
ai_do_objects_undocked_stuff(&Objects[obj], dock_get_first_docked_object(&Objects[obj]));
|
||||
}
|
||||
|
||||
if (Player_start_shipnum == i) { // need a new single player start.
|
||||
objp = GET_FIRST(&obj_used_list);
|
||||
while (objp != END_OF_LIST(&obj_used_list)) {
|
||||
if (objp->type == OBJ_START) {
|
||||
Player_start_shipnum = objp->instance;
|
||||
break;
|
||||
}
|
||||
|
||||
objp = GET_NEXT(objp);
|
||||
}
|
||||
}
|
||||
ensure_valid_player_start_shipnum(); // may need a new single player start
|
||||
|
||||
Player_starts--;
|
||||
|
||||
|
||||
@@ -479,6 +479,11 @@ void sexp_tree::free_node2(int node)
|
||||
Assert(tree_nodes[node].type != SEXPT_UNUSED);
|
||||
Assert(total_nodes > 0);
|
||||
*modified = 1;
|
||||
|
||||
// the node is being deleted; let subclasses drop anything attached to its handle
|
||||
if (tree_nodes[node].handle)
|
||||
on_node_handle_changed(tree_nodes[node].handle, nullptr);
|
||||
|
||||
tree_nodes[node].type = SEXPT_UNUSED;
|
||||
total_nodes--;
|
||||
if (tree_nodes[node].child != -1)
|
||||
@@ -2640,6 +2645,9 @@ void sexp_tree::NodeDelete()
|
||||
|
||||
Assert(theNode >= 0);
|
||||
free_node2(theNode);
|
||||
// the event-root item is not a tree_nodes entry, so free_node2 above does
|
||||
// not cover an annotation attached to the event root itself
|
||||
on_node_handle_changed(item_handle, nullptr);
|
||||
DeleteItem(item_handle);
|
||||
*modified = 1;
|
||||
return;
|
||||
@@ -4668,6 +4676,10 @@ HTREEITEM sexp_tree::move_branch(HTREEITEM source, HTREEITEM parent, HTREEITEM a
|
||||
h = insert(GetItemText(source), image1, image2, parent, after);
|
||||
}
|
||||
|
||||
// the item was recreated with a new handle; let subclasses follow it
|
||||
// (covers both tree_nodes entries and the event-root item, which is not one)
|
||||
on_node_handle_changed(source, h);
|
||||
|
||||
SetItemData(h, GetItemData(source));
|
||||
child = GetChildItem(source);
|
||||
while (child) {
|
||||
|
||||
@@ -367,6 +367,11 @@ protected:
|
||||
virtual void NodeReplacePaste();
|
||||
virtual void NodeAddPaste();
|
||||
|
||||
// Notifies that the tree item for a node changed handle (moved/recreated), or
|
||||
// was deleted (new_handle == nullptr). The event tree overrides this to keep
|
||||
// event annotations attached to their node. Default: no-op.
|
||||
virtual void on_node_handle_changed(HTREEITEM /*old_handle*/, HTREEITEM /*new_handle*/) {}
|
||||
|
||||
void update_item(HTREEITEM handle);
|
||||
|
||||
int load_branch(int index, int parent);
|
||||
|
||||
+17
-12
@@ -1258,17 +1258,7 @@ int CShipEditorDlg::update_data(int redraw)
|
||||
}
|
||||
}
|
||||
|
||||
if (Player_start_shipnum < 0 || Objects[Ships[Player_start_shipnum].objnum].type != OBJ_START) { // need a new single player start.
|
||||
ptr = GET_FIRST(&obj_used_list);
|
||||
while (ptr != END_OF_LIST(&obj_used_list)) {
|
||||
if (ptr->type == OBJ_START) {
|
||||
Player_start_shipnum = ptr->instance;
|
||||
break;
|
||||
}
|
||||
|
||||
ptr = GET_NEXT(ptr);
|
||||
}
|
||||
}
|
||||
ensure_valid_player_start_shipnum(); // may need a new single player start
|
||||
|
||||
if (modified)
|
||||
set_modified();
|
||||
@@ -1518,7 +1508,9 @@ int CShipEditorDlg::update_ship(int ship)
|
||||
|
||||
Objects[Ships[ship].objnum].type = OBJ_SHIP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ensure_valid_player_start_shipnum();
|
||||
|
||||
Update_ship = 1;
|
||||
return 0;
|
||||
@@ -2448,12 +2440,22 @@ void CShipEditorDlg::OnSetAsPlayerShip()
|
||||
if (objp->flags[Object::Object_Flags::Marked]) // there should only be one selected ship
|
||||
{
|
||||
// set as player ship
|
||||
if (objp->type != OBJ_START)
|
||||
{
|
||||
Player_starts++;
|
||||
set_modified();
|
||||
}
|
||||
objp->type = OBJ_START;
|
||||
objp->flags.set(Object::Object_Flags::Player_ship);
|
||||
}
|
||||
else
|
||||
{
|
||||
// set as regular ship
|
||||
if (objp->type == OBJ_START)
|
||||
{
|
||||
Player_starts--;
|
||||
set_modified();
|
||||
}
|
||||
objp->type = OBJ_SHIP;
|
||||
objp->flags.remove(Object::Object_Flags::Player_ship);
|
||||
}
|
||||
@@ -2461,6 +2463,9 @@ void CShipEditorDlg::OnSetAsPlayerShip()
|
||||
objp = GET_NEXT(objp);
|
||||
}
|
||||
|
||||
// fix up Player_start_shipnum if it became invalid
|
||||
ensure_valid_player_start_shipnum();
|
||||
|
||||
// finally set editor dialog
|
||||
m_player_ship.SetCheck(1);
|
||||
update_map_window();
|
||||
|
||||
+5
-2
@@ -227,7 +227,8 @@ int create_wing() {
|
||||
}
|
||||
|
||||
count = 0;
|
||||
if (Objects[Ships[Player_start_shipnum].objnum].flags[Object::Object_Flags::Marked])
|
||||
if ((Player_start_shipnum >= 0) && (Player_start_shipnum < MAX_SHIPS) && (Ships[Player_start_shipnum].objnum >= 0)
|
||||
&& Objects[Ships[Player_start_shipnum].objnum].flags[Object::Object_Flags::Marked])
|
||||
count = 1;
|
||||
|
||||
ptr = GET_FIRST(&obj_used_list);
|
||||
@@ -442,7 +443,7 @@ void remove_ship_from_wing(int ship, int min) {
|
||||
rename_ship(ship, buf);
|
||||
}
|
||||
|
||||
void remove_wing(int wing_num)
|
||||
void disband_wing(int wing_num)
|
||||
{
|
||||
int i, total;
|
||||
object *ptr;
|
||||
@@ -450,6 +451,8 @@ void remove_wing(int wing_num)
|
||||
if (check_wing_dependencies(wing_num))
|
||||
return;
|
||||
|
||||
delete_reinforcement(Wings[wing_num].name);
|
||||
|
||||
Ship_editor_dialog.bypass_errors = Wing_editor_dialog.bypass_errors = 1;
|
||||
Ship_editor_dialog.update_data(0);
|
||||
total = Wings[wing_num].wave_count;
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ int create_wing();
|
||||
*
|
||||
* @param[in] wing_num Index of the wing
|
||||
*/
|
||||
void remove_wing(int wing_num);
|
||||
void disband_wing(int wing_num);
|
||||
|
||||
/**
|
||||
* @brief Takes a ship out of a wing, deleting the wing if that was the only ship in it.
|
||||
|
||||
@@ -1078,7 +1078,7 @@ void wing_editor::OnDeleteWing()
|
||||
void wing_editor::OnDisbandWing()
|
||||
{
|
||||
modified = 0; // no need to run update checks, since wing will be gone shortly anyway.
|
||||
remove_wing(cur_wing);
|
||||
disband_wing(cur_wing);
|
||||
}
|
||||
|
||||
void wing_editor::OnGoals2()
|
||||
@@ -1475,6 +1475,9 @@ void wing_editor::OnWingFormationAlign()
|
||||
|
||||
get_absolute_wing_pos(&objp->pos, leader_objp, cur_wing, i, false);
|
||||
objp->orient = leader_objp->orient;
|
||||
|
||||
// drag any docked partners along (no-op for undocked ships)
|
||||
object_moved(objp);
|
||||
}
|
||||
|
||||
// roll back temporary formation
|
||||
|
||||
@@ -984,7 +984,7 @@ void game_level_close()
|
||||
particle::ParticleManager::get()->clearSources();
|
||||
particle::close();
|
||||
trail_level_close();
|
||||
ship_close_cockpit_displays(Player_ship);
|
||||
ship_level_close();
|
||||
hud_level_close();
|
||||
hud_escort_clear_all();
|
||||
model_instance_free_all();
|
||||
|
||||
@@ -973,7 +973,6 @@ int Editor::common_object_delete(int obj) {
|
||||
char msg[255];
|
||||
const char *name;
|
||||
int i, z, r, type;
|
||||
object* objp;
|
||||
SCP_list<CJumpNode>::iterator jnp;
|
||||
|
||||
type = Objects[obj].type;
|
||||
@@ -1012,17 +1011,7 @@ int Editor::common_object_delete(int obj) {
|
||||
ai_do_objects_undocked_stuff(&Objects[obj], dock_get_first_docked_object(&Objects[obj]));
|
||||
}
|
||||
|
||||
if (Player_start_shipnum == i) { // need a new single player start.
|
||||
objp = GET_FIRST(&obj_used_list);
|
||||
while (objp != END_OF_LIST(&obj_used_list)) {
|
||||
if (objp->type == OBJ_START) {
|
||||
Player_start_shipnum = objp->instance;
|
||||
break;
|
||||
}
|
||||
|
||||
objp = GET_NEXT(objp);
|
||||
}
|
||||
}
|
||||
ensure_valid_player_start_shipnum(); // may need a new single player start
|
||||
|
||||
Player_starts--;
|
||||
|
||||
@@ -1562,7 +1551,7 @@ int Editor::set_reinforcement(const char* name, int state) {
|
||||
|
||||
return 0;
|
||||
}
|
||||
void Editor::remove_wing(int wing_num) {
|
||||
void Editor::disband_wing(int wing_num) {
|
||||
|
||||
int i, total;
|
||||
object* ptr;
|
||||
@@ -1571,6 +1560,8 @@ void Editor::remove_wing(int wing_num) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete_reinforcement(Wings[wing_num].name);
|
||||
|
||||
total = Wings[wing_num].wave_count;
|
||||
for (i = 0; i < total; i++) {
|
||||
ptr = &Objects[wing_objects[wing_num][i]];
|
||||
|
||||
@@ -233,7 +233,7 @@ class Editor : public QObject {
|
||||
*
|
||||
* @param[in] wing_num Index of the wing
|
||||
*/
|
||||
void remove_wing(int wing_num);
|
||||
void disband_wing(int wing_num);
|
||||
|
||||
void delete_marked();
|
||||
|
||||
|
||||
@@ -712,15 +712,19 @@ int EditorViewport::object_check_collision(object* objp, vec3d* p0, vec3d* p1, v
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((view.Show_ship_models || view.Show_outlines) && (objp->type == OBJ_SHIP)) {
|
||||
mc.model_num = Ship_info[Ships[objp->instance].ship_info_index].model_num; // Fill in the model to check
|
||||
} else if ((view.Show_ship_models || view.Show_outlines) && (objp->type == OBJ_START)) {
|
||||
mc.model_num = Ship_info[Ships[objp->instance].ship_info_index].model_num; // Fill in the model to check
|
||||
mc.model_instance_num = -1;
|
||||
|
||||
if ((view.Show_ship_models || view.Show_outlines) && (objp->type == OBJ_SHIP || objp->type == OBJ_START)) {
|
||||
auto& shp = Ships[objp->instance];
|
||||
mc.model_num = Ship_info[shp.ship_info_index].model_num; // Fill in the model to check
|
||||
mc.model_instance_num = shp.model_instance_num;
|
||||
} else if ((view.Show_ship_models || view.Show_outlines) && (objp->type == OBJ_PROP)) {
|
||||
auto& prp = Props[objp->instance].value();
|
||||
mc.model_num = Prop_info[prp.prop_info_index].model_num; // Fill in the model to check
|
||||
mc.model_instance_num = prp.model_instance_num;
|
||||
} else {
|
||||
return fvi_ray_sphere(hitpos, p0, p1, &objp->pos, (objp->radius > 0.1f) ? objp->radius : LOLLIPOP_SIZE);
|
||||
}
|
||||
|
||||
mc.model_instance_num = -1;
|
||||
mc.orient = &objp->orient; // The object's orient
|
||||
mc.pos = &objp->pos; // The object's position
|
||||
mc.p0 = p0; // Point 1 of ray to check
|
||||
|
||||
@@ -240,7 +240,8 @@ int Editor::create_wing()
|
||||
}
|
||||
|
||||
count = 0;
|
||||
if (Objects[Ships[Player_start_shipnum].objnum].flags[Object::Object_Flags::Marked]) {
|
||||
if ((Player_start_shipnum >= 0) && (Player_start_shipnum < MAX_SHIPS) && (Ships[Player_start_shipnum].objnum >= 0)
|
||||
&& Objects[Ships[Player_start_shipnum].objnum].flags[Object::Object_Flags::Marked]) {
|
||||
count = 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -695,9 +695,68 @@ int BriefingEditorDialogModel::getIconId() const
|
||||
return s.icons[_currentIcon].id;
|
||||
}
|
||||
|
||||
void BriefingEditorDialogModel::setIconId(int id)
|
||||
bool BriefingEditorDialogModel::setIconId(int id)
|
||||
{
|
||||
applyToSelectedIconsCurrentAndForward([&](brief_icon& ic) { modify(ic.id, id); });
|
||||
auto& briefing = _wipBriefings[_currentTeam];
|
||||
if (briefing.num_stages <= 0 || _currentStage < 0 || _currentStage >= briefing.num_stages)
|
||||
return true;
|
||||
|
||||
auto& stage = briefing.stages[_currentStage];
|
||||
if (_currentIcon < 0 || _currentIcon >= stage.num_icons)
|
||||
return true;
|
||||
|
||||
// an id applies to a single icon, so operate on the current icon rather than the
|
||||
// whole selection; applying one id to several icons would itself create a collision
|
||||
const int oldId = stage.icons[_currentIcon].id;
|
||||
if (id == oldId)
|
||||
return true; // no change
|
||||
|
||||
// briefing icon ids must never be negative (the spin box also enforces this)
|
||||
if (id < 0)
|
||||
return false;
|
||||
|
||||
// an icon id must be unique within its stage
|
||||
for (int i = 0; i < stage.num_icons; ++i) {
|
||||
if (i != _currentIcon && stage.icons[i].id == id) {
|
||||
QMessageBox::warning(nullptr,
|
||||
tr("Icon ID"),
|
||||
tr("Icon ID %1 is already used by another icon in this stage. The ID has not been changed.").arg(id));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// when propagating forward, the new id must not already be used by a different icon in a
|
||||
// later stage, or the global rename would merge two distinct icon chains into one
|
||||
if (!_changeLocally) {
|
||||
for (int st = _currentStage + 1; st < briefing.num_stages; ++st) {
|
||||
const auto& s = briefing.stages[st];
|
||||
for (int i = 0; i < s.num_icons; ++i) {
|
||||
if (s.icons[i].id == id) {
|
||||
QMessageBox::warning(nullptr,
|
||||
tr("Icon ID"),
|
||||
tr("Icon ID %1 is already used in a later stage. You can only change to that ID "
|
||||
"locally. The ID has not been changed.").arg(id));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// apply the change to the current icon, and (unless local-only) to the same icon in later stages
|
||||
stage.icons[_currentIcon].id = id;
|
||||
if (!_changeLocally) {
|
||||
for (int st = _currentStage + 1; st < briefing.num_stages; ++st) {
|
||||
auto& s = briefing.stages[st];
|
||||
for (int i = 0; i < s.num_icons; ++i) {
|
||||
if (s.icons[i].id == oldId)
|
||||
s.icons[i].id = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set_modified();
|
||||
modelChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
SCP_string BriefingEditorDialogModel::getIconLabel() const
|
||||
|
||||
@@ -70,7 +70,8 @@ class BriefingEditorDialogModel : public AbstractDialogModel {
|
||||
vec3d getIconPosition() const;
|
||||
void setIconPosition(const vec3d& pos);
|
||||
int getIconId() const;
|
||||
void setIconId(int id);
|
||||
// returns false if the requested id was rejected (e.g. it collides with another icon)
|
||||
bool setIconId(int id);
|
||||
SCP_string getIconLabel() const;
|
||||
void setIconLabel(const SCP_string& text);
|
||||
SCP_string getIconCloseupLabel() const;
|
||||
|
||||
@@ -609,8 +609,12 @@ void MissionEventsDialogModel::reorderByRootFormulaOrder(const SCP_vector<int>&
|
||||
// Keep selection reasonable (select the first event after reorder)
|
||||
setCurrentlySelectedEvent(m_events.empty() ? -1 : 0);
|
||||
|
||||
// Rebuild applied annotations against new handles/order if needed
|
||||
initializeEventAnnotations();
|
||||
// A root reorder moves top-level items via take/insert, which preserves their
|
||||
// handles (see sexp_tree::move_root), so the cached annotation handles are still
|
||||
// valid and still point at the correct nodes. Do NOT re-resolve handles from the
|
||||
// stored paths here: the paths carry the pre-reorder event index, so re-resolving
|
||||
// would re-point annotations at the wrong event. applyAnnotations() rebuilds each
|
||||
// path from its live handle at save time.
|
||||
|
||||
set_modified();
|
||||
}
|
||||
@@ -1138,6 +1142,20 @@ void MissionEventsDialogModel::setNodeBgColor(IEventTreeOps::Handle h, int r, in
|
||||
set_modified();
|
||||
}
|
||||
|
||||
// The tree recreated (moved) or deleted the item for a node. Re-point any
|
||||
// annotation on the old handle to the new one so it follows the node; a null
|
||||
// new_handle means the node was deleted, which clears the cached handle so
|
||||
// applyAnnotations() resolves it from the path (and drops it if the node is gone).
|
||||
void MissionEventsDialogModel::onNodeHandleChanged(IEventTreeOps::Handle old_handle, IEventTreeOps::Handle new_handle)
|
||||
{
|
||||
if (!old_handle)
|
||||
return;
|
||||
|
||||
for (auto& ea : m_event_annotations)
|
||||
if (ea.handle == old_handle)
|
||||
ea.handle = new_handle;
|
||||
}
|
||||
|
||||
void MissionEventsDialogModel::createMessage()
|
||||
{
|
||||
MMessage msg;
|
||||
|
||||
@@ -142,6 +142,10 @@ class MissionEventsDialogModel : public AbstractDialogModel {
|
||||
void setNodeAnnotation(IEventTreeOps::Handle h, const SCP_string& note);
|
||||
void setNodeBgColor(IEventTreeOps::Handle h, int r, int g, int b, bool has_color);
|
||||
|
||||
// Keep annotations attached to their node when the tree recreates (move) or
|
||||
// deletes (new_handle == nullptr) the item's handle.
|
||||
void onNodeHandleChanged(IEventTreeOps::Handle old_handle, IEventTreeOps::Handle new_handle);
|
||||
|
||||
// Message Management
|
||||
void createMessage();
|
||||
void insertMessage();
|
||||
|
||||
@@ -1138,15 +1138,8 @@ void ShipEditorDialogModel::setPlayer(const bool m_player)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fix up Player_start_shipnum if it became invalid
|
||||
if (Player_start_shipnum < 0 || Objects[Ships[Player_start_shipnum].objnum].type != OBJ_START) {
|
||||
for (auto* p = GET_FIRST(&obj_used_list); p != END_OF_LIST(&obj_used_list); p = GET_NEXT(p)) {
|
||||
if (p->type == OBJ_START) {
|
||||
Player_start_shipnum = p->instance;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// fix up Player_start_shipnum if it became invalid
|
||||
ensure_valid_player_start_shipnum();
|
||||
setModified();
|
||||
_editor->missionChanged();
|
||||
modelChanged();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "iff_defs/iff_defs.h"
|
||||
#include "mission/missionhotkey.h"
|
||||
#include "mission/missionparse.h"
|
||||
#include "mission/object.h"
|
||||
#include "missioneditor/common.h"
|
||||
#include <QObject>
|
||||
#include <QMessageBox>
|
||||
@@ -635,6 +636,9 @@ void WingEditorDialogModel::alignWingFormation()
|
||||
|
||||
get_absolute_wing_pos(&objp->pos, leader_objp, _currentWingIndex, i, false);
|
||||
objp->orient = leader_objp->orient;
|
||||
|
||||
// drag any docked partners along (no-op for undocked ships)
|
||||
object_moved(objp);
|
||||
}
|
||||
|
||||
// roll back temporary formation
|
||||
@@ -729,7 +733,7 @@ void WingEditorDialogModel::disbandCurrentWing()
|
||||
if (!wingIsValid())
|
||||
return;
|
||||
|
||||
_editor->remove_wing(_currentWingIndex);
|
||||
_editor->disband_wing(_currentWingIndex);
|
||||
reloadFromCurWing();
|
||||
}
|
||||
|
||||
|
||||
@@ -2688,7 +2688,7 @@ void FredView::on_actionWingForm_triggered(bool /*enabled*/) {
|
||||
}
|
||||
void FredView::on_actionWingDisband_triggered(bool /*enabled*/) {
|
||||
if (fred->query_single_wing_marked()) {
|
||||
fred->remove_wing(fred->cur_wing);
|
||||
fred->disband_wing(fred->cur_wing);
|
||||
} else {
|
||||
showButtonDialog(DialogType::Error,
|
||||
"Error",
|
||||
|
||||
@@ -89,6 +89,10 @@ BriefingEditorDialog::BriefingEditorDialog(FredView* parent, EditorViewport* vie
|
||||
ui->iconCloseupLabelLineEdit->setMaxLength(MAX_LABEL_LEN - 1);
|
||||
ui->voiceFileLineEdit->setMaxLength(MAX_FILENAME_LEN - 1);
|
||||
|
||||
// validate the icon id when the edit is committed (focus-out/Enter) rather than on every
|
||||
// keystroke, so a transient value while typing can't pop a collision warning
|
||||
ui->iconIdSpinBox->setKeyboardTracking(false);
|
||||
|
||||
setupMapWidget();
|
||||
initializeUi();
|
||||
updateUi();
|
||||
@@ -557,7 +561,12 @@ void BriefingEditorDialog::on_disableGridCheckBox_toggled(bool checked)
|
||||
|
||||
void BriefingEditorDialog::on_iconIdSpinBox_valueChanged(int arg1)
|
||||
{
|
||||
_model->setIconId(arg1);
|
||||
if (!_model->setIconId(arg1)) {
|
||||
// the id was rejected; reset the spin box to the model's current value
|
||||
ui->iconIdSpinBox->blockSignals(true);
|
||||
ui->iconIdSpinBox->setValue(_model->getIconId());
|
||||
ui->iconIdSpinBox->blockSignals(false);
|
||||
}
|
||||
}
|
||||
|
||||
void BriefingEditorDialog::on_iconLabelLineEdit_textChanged(const QString& string)
|
||||
|
||||
@@ -245,6 +245,10 @@ void MissionEventsDialog::initEventWidgets() {
|
||||
_model->setNodeBgColor(h, c.red(), c.green(), c.blue(), c.isValid());
|
||||
});
|
||||
|
||||
connect(ui->eventTree, &sexp_tree::nodeHandleChanged, this, [this](void* old_h, void* new_h) {
|
||||
_model->onNodeHandleChanged(old_h, new_h);
|
||||
});
|
||||
|
||||
connect(ui->eventTree, &sexp_tree::rootOrderChanged, this, [this] {
|
||||
SCP_vector<int> order;
|
||||
order.reserve(ui->eventTree->topLevelItemCount());
|
||||
|
||||
@@ -254,6 +254,8 @@ int ErrorChecker::checkObjectList() {
|
||||
ptr->type = OBJ_SHIP;
|
||||
Player_starts--;
|
||||
t--;
|
||||
|
||||
ensure_valid_player_start_shipnum();
|
||||
}
|
||||
warning("Invalid ship type for a player.%s",
|
||||
_viewport->Error_checker_apply_auto_corrections
|
||||
|
||||
@@ -673,6 +673,11 @@ void sexp_tree::free_node2(int node) {
|
||||
Assert(tree_nodes[node].type != SEXPT_UNUSED);
|
||||
Assert(total_nodes > 0);
|
||||
modified();
|
||||
|
||||
// the node is being deleted; let the model drop anything attached to its handle
|
||||
if (tree_nodes[node].handle)
|
||||
nodeHandleChanged(tree_nodes[node].handle, nullptr);
|
||||
|
||||
tree_nodes[node].type = SEXPT_UNUSED;
|
||||
total_nodes--;
|
||||
if (tree_nodes[node].child != -1) {
|
||||
@@ -2739,6 +2744,9 @@ QTreeWidgetItem* sexp_tree::move_branch(QTreeWidgetItem* source, QTreeWidgetItem
|
||||
h->setData(0, BgColorRole, source->data(0, BgColorRole));
|
||||
applyVisuals(h);
|
||||
|
||||
// the item was recreated with a new handle; let the model follow it
|
||||
nodeHandleChanged(source, h);
|
||||
|
||||
// Move children safely
|
||||
while (source->childCount() > 0) {
|
||||
auto* child = source->child(0);
|
||||
@@ -7297,6 +7305,10 @@ void sexp_tree::deleteActionHandler()
|
||||
free_node2(formulaNode);
|
||||
}
|
||||
|
||||
// the event-root item is not a tree_nodes entry, so free_node2 above does
|
||||
// not cover an annotation attached to the event root itself
|
||||
nodeHandleChanged(item, nullptr);
|
||||
|
||||
// Remove the UI item and reset selection/index
|
||||
delete item;
|
||||
setCurrentItemIndex(-1);
|
||||
|
||||
@@ -410,6 +410,11 @@ class sexp_tree: public QTreeWidget {
|
||||
void nodeAnnotationChanged(void* handle, const QString& note);
|
||||
void nodeBgColorChanged(void* handle, const QColor& color);
|
||||
|
||||
// Emitted when a node's tree item changed handle (moved/recreated), or was
|
||||
// deleted (new_handle == nullptr). Lets the model keep event annotations
|
||||
// attached to their node across tree mutations.
|
||||
void nodeHandleChanged(void* old_handle, void* new_handle);
|
||||
|
||||
// Generated message map functions
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent* e) override;
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
set(FSO_VERSION_MAJOR 26)
|
||||
set(FSO_VERSION_MINOR 0)
|
||||
set(FSO_VERSION_BUILD 1)
|
||||
set(FSO_VERSION_REVISION 0)
|
||||
set(FSO_VERSION_REVISION_STR RC1)
|
||||
Reference in new issue
Block a user