Skip to content

Commit

Permalink
Recompute guidebot index on game load
Browse files Browse the repository at this point in the history
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 <#713>
  • Loading branch information
vLKp committed May 14, 2023
1 parent c21c317 commit b4e3d67
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 15 deletions.
6 changes: 0 additions & 6 deletions common/main/fwd-object.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
6 changes: 6 additions & 0 deletions common/main/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
9 changes: 5 additions & 4 deletions similar/main/net_udp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
Expand Down Expand Up @@ -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())
Expand Down
32 changes: 29 additions & 3 deletions similar/main/object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
}

Expand Down Expand Up @@ -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)
{
Expand Down
4 changes: 2 additions & 2 deletions similar/main/state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit b4e3d67

Please sign in to comment.