Recompute guidebot index on game load

The index of the guidebot is set by loading the level data, and this is
usually sufficient.  However, if the user kills the guidebot, then uses
cheats to create a new one, the new guidebot will often have a different
index than the original guidebot.  If such a game is saved and then
reloaded, then after the reload, the computed guidebot index will be
reverted to the index of the original dead guidebot.  This causes
various problems, including potentially a crash.  Recompute the guidebot
index after loading the objects from the save file, so that it matches
the live guidebot.

Reported-by: GitInMotion <https://github.com/dxx-rebirth/dxx-rebirth/issues/713>
This commit is contained in:
Kp 2023-05-14 18:41:36 +00:00
parent c21c317441
commit b4e3d67725
5 changed files with 42 additions and 15 deletions

View file

@ -267,12 +267,6 @@ void clear_transient_objects(int clear_all);
[[nodiscard]]
imobjptridx_t obj_allocate(d_level_unique_object_state &);
// after calling init_object(), the network code has grabbed specific
// object slots without allocating them. Go though the objects &
// build the free list, then set the apporpriate globals Don't call
// this function if you don't know what you're doing.
void special_reset_objects(d_level_unique_object_state &);
// attaches an object, such as a fireball, to another object, such as
// a robot
void obj_attach(object_array &Objects, vmobjptridx_t parent, vmobjptridx_t sub);

View file

@ -727,6 +727,12 @@ struct d_level_unique_boss_state : ::dcx::d_level_unique_boss_state
const player &get_player_controlling_guidebot(const d_unique_buddy_state & /* reserved for future use */, const valptridx<player>::array_managed_type &Players);
#endif
// after calling init_object(), the network code has grabbed specific
// object slots without allocating them. Go though the objects &
// build the free list, then set the appropriate globals.
// Don't call this function unless you know what you're doing.
void special_reset_objects(d_level_unique_object_state &, const d_robot_info_array &Robot_info);
unsigned laser_parent_is_player(fvcobjptr &, const laser_parent &, const object_base &);
unsigned laser_parent_is_object(fvcobjptr &, const laser_parent &, const object_base &);
unsigned laser_parent_is_object(const laser_parent &, vcobjptridx_t);

View file

@ -2479,9 +2479,10 @@ static int net_udp_verify_objects(int remote, int local)
return(1);
}
static void net_udp_read_object_packet(const uint8_t *const data)
static void net_udp_read_object_packet(const d_level_shared_robot_info_state &LevelSharedRobotInfoState, const uint8_t *const data)
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
auto &vmobjptridx = Objects.vmptridx;
// Object from another net player we need to sync with
sbyte obj_owner;
@ -2512,7 +2513,7 @@ static void net_udp_read_object_packet(const uint8_t *const data)
// Special debug checksum marker for entire send
if (mode == 1)
{
special_reset_objects(LevelUniqueObjectState);
special_reset_objects(LevelUniqueObjectState, Robot_info);
mode = 0;
}
if (remote_objnum != object_count) {
@ -2538,7 +2539,7 @@ static void net_udp_read_object_packet(const uint8_t *const data)
else {
if (mode == 1)
{
special_reset_objects(LevelUniqueObjectState);
special_reset_objects(LevelUniqueObjectState, Robot_info);
mode = 0;
}
objnum = obj_allocate(LevelUniqueObjectState);
@ -3476,7 +3477,7 @@ static void net_udp_process_packet(const d_level_shared_robot_info_state &LevelS
case upid::object_data:
if (multi_i_am_master() || length > UPID_MAX_SIZE || Network_status != network_state::waiting)
break;
net_udp_read_object_packet(data);
net_udp_read_object_packet(LevelSharedRobotInfoState, data);
break;
case upid::ping:
if (multi_i_am_master())

View file

@ -914,8 +914,8 @@ void init_objects()
//after calling init_object(), the network code has grabbed specific
//object slots without allocating them. Go though the objects & build
//the free list, then set the apporpriate globals
void special_reset_objects(d_level_unique_object_state &LevelUniqueObjectState)
//the free list, then set the appropriate globals
void special_reset_objects(d_level_unique_object_state &LevelUniqueObjectState, const d_robot_info_array &Robot_info)
{
unsigned num_objects = MAX_OBJECTS;
@ -924,12 +924,34 @@ void special_reset_objects(d_level_unique_object_state &LevelUniqueObjectState)
assert(Objects.front().type != OBJ_NONE); //0 should be used
DXX_POISON_VAR(LevelUniqueObjectState.free_obj_list, 0xfd);
#if defined(DXX_BUILD_DESCENT_I)
/* Descent 1 does not have a guidebot, so there is nothing to fix up. For
* simplicity, both games pass the parameter.
*/
(void)Robot_info;
#elif defined(DXX_BUILD_DESCENT_II)
icobjidx_t Buddy_objnum = object_none;
#endif
for (objnum_t i = MAX_OBJECTS; i--;)
if (Objects.vcptr(i)->type == OBJ_NONE)
{
const auto &obj = *Objects.vcptr(i);
#if defined(DXX_BUILD_DESCENT_II)
if (obj.type == OBJ_ROBOT)
{
auto &robptr = Robot_info[get_robot_id(obj)];
if (robot_is_companion(robptr))
Buddy_objnum = i;
}
#endif
if (obj.type == OBJ_NONE)
LevelUniqueObjectState.free_obj_list[--num_objects] = i;
else
if (i > Highest_object_index)
Objects.set_count(i + 1);
}
#if defined(DXX_BUILD_DESCENT_II)
LevelUniqueObjectState.BuddyState.Buddy_objnum = Buddy_objnum;
#endif
LevelUniqueObjectState.num_objects = num_objects;
}
@ -2180,6 +2202,10 @@ void reset_objects(d_level_unique_object_state &LevelUniqueObjectState, const un
auto &Objects = LevelUniqueObjectState.get_objects();
assert(LevelUniqueObjectState.num_objects < Objects.size());
Objects.set_count(n_objs);
#if defined(DXX_BUILD_DESCENT_II)
if (LevelUniqueObjectState.BuddyState.Buddy_objnum.get_unchecked_index() >= n_objs)
LevelUniqueObjectState.BuddyState.Buddy_objnum = object_none;
#endif
for (objnum_t i = n_objs; i < MAX_OBJECTS; ++i)
{

View file

@ -2128,7 +2128,7 @@ int state_restore_all_sub(const d_level_shared_destructible_light_state &LevelSh
}
#endif
}
special_reset_objects(LevelUniqueObjectState);
special_reset_objects(LevelUniqueObjectState, LevelSharedRobotInfoState.Robot_info);
/* Reload plrobj reference. The player's object number may have
* been changed by the state_object_rw_to_object call.
*/
@ -2541,7 +2541,7 @@ int state_restore_all_sub(const d_level_shared_destructible_light_state &LevelSh
if (!coop_player_got[i] && vcplayerptr(i)->connected == player_connection_status::playing)
multi_disconnect_player(i);
Viewer = ConsoleObject = &get_local_plrobj(); // make sure Viewer and ConsoleObject are set up (which we skipped by not using InitPlayerObject but we need since objects changed while loading)
special_reset_objects(LevelUniqueObjectState); // since we juggled around with objects to remap coop players rebuild the index of free objects
special_reset_objects(LevelUniqueObjectState, LevelSharedRobotInfoState.Robot_info); // since we juggled around with objects to remap coop players rebuild the index of free objects
state_set_next_autosave(GameUniqueState, Netgame.MPGameplayOptions.AutosaveInterval);
}
else