mirror of
https://github.com/Cesarus85/ringfall-mjolnir-vr.git
synced 2026-10-17 12:00:26 +02:00
Co-op: every machine stays on the host's BSP
Players in big sessions ended up on different BSPs: - The host told clients about a switch only every half second, and not reliably, so a client could predict its player for a while in a BSP it hadn't loaded. A switch is now sent at once, reliably, ahead of the players' new positions. - A client still loading reports so: its input carries the BSP it has loaded. Until it has the host's, the host takes none of its movement (which falls there with no floor) and none of its loading zones. - After a switch, by a trigger, a script or a skipped cutscene, no trigger can switch again until every machine has loaded the new BSP (at most ten seconds), so no client falls two switches behind. - A player the host can't place beside whoever crossed, when a big team runs out of room there, is placed beside another teammate instead of being left where they were.
This commit is contained in:
1 parent
24957ff6a0
commit
087ca8a875
5 files changed
+114
-22
No files matched your search
@@ -945,6 +945,10 @@ static struct
|
||||
real power;
|
||||
} host_sent_devices[MAXIMUM_OBJECTS_PER_MAP];
|
||||
|
||||
/* host: the structure BSP each client player's machine last said it has,
|
||||
NONE before its first input */
|
||||
static short host_player_structure_bsps[MAXIMUM_TRACKED_PLAYERS];
|
||||
|
||||
/* host, each tick: the devices that moved or changed power (and, with the
|
||||
resent state, all that ever have) */
|
||||
static void host_send_device_states(
|
||||
@@ -1903,6 +1907,7 @@ void network_coop_new_game(
|
||||
csmemset(host_sent_transforms, 0, sizeof(host_sent_transforms));
|
||||
csmemset(host_sent_looks, 0, sizeof(host_sent_looks));
|
||||
csmemset(host_sent_devices, 0, sizeof(host_sent_devices));
|
||||
csmemset(host_player_structure_bsps, NONE, sizeof(host_player_structure_bsps));
|
||||
csmemset(&host_sent_screen_effect, 0, sizeof(host_sent_screen_effect));
|
||||
csmemset(&host_resend, 0, sizeof(host_resend));
|
||||
csmemset(&players_vitality, 0, sizeof(players_vitality));
|
||||
@@ -2582,6 +2587,21 @@ word network_coop_device_group_entry_size(
|
||||
return sizeof(struct distributed_coop_device_group);
|
||||
}
|
||||
|
||||
void network_coop_note_player_structure_bsp(
|
||||
short player_index,
|
||||
short structure_bsp_index)
|
||||
{
|
||||
if (player_index >= 0 && player_index < MAXIMUM_TRACKED_PLAYERS)
|
||||
host_player_structure_bsps[player_index] = structure_bsp_index;
|
||||
}
|
||||
|
||||
boolean network_coop_player_has_structure_bsp(
|
||||
long player_index)
|
||||
{
|
||||
return !network_coop_active() || player_get(player_index)->local_player_index != NONE ||
|
||||
host_player_structure_bsps[DATUM_INDEX_TO_ABSOLUTE_INDEX(player_index)] == global_structure_bsp_index_get();
|
||||
}
|
||||
|
||||
word network_coop_device_state_entry_size(
|
||||
void)
|
||||
{
|
||||
|
||||
@@ -52,6 +52,13 @@ void network_coop_note_device_snap(short group_index);
|
||||
/* unit_scripting_commands.c: a script set a unit's maximum or current
|
||||
vitality; TRUE if it was a co-op player's, now set on every player's */
|
||||
boolean network_coop_set_players_vitality(long unit_index, boolean maximum, real body, real shield);
|
||||
/* host: whether the player's machine has the host's structure BSP loaded
|
||||
(always outside co-op, and for the host's own players). Until a client has,
|
||||
the host takes none of its players' movement, which falls there with no
|
||||
floor, and lets them trigger no BSP switch (players.c). */
|
||||
boolean network_coop_player_has_structure_bsp(long player_index);
|
||||
/* network_distributed.c: the structure BSP a client's input says it has */
|
||||
void network_coop_note_player_structure_bsp(short player_index, short structure_bsp_index);
|
||||
/* units.c: a vehicle left the vehicle carrying it (a Pelican's Warthog drop) */
|
||||
void network_coop_vehicle_dropped(long vehicle_index, long carrier_index);
|
||||
|
||||
|
||||
@@ -320,7 +320,9 @@ ticks before it */
|
||||
struct distributed_player_input
|
||||
{
|
||||
byte player_index;
|
||||
byte pad[3];
|
||||
/* co-op: the structure BSP the client has loaded */
|
||||
byte structure_bsp_index;
|
||||
byte pad[2];
|
||||
/* the client's tick */
|
||||
long tick;
|
||||
/* the latest host tick the client has had a message of (the host
|
||||
@@ -2083,9 +2085,14 @@ static void distributed_apply_predictions(
|
||||
{
|
||||
distributed_predictions[player_index].valid = FALSE;
|
||||
distributed_predictions[player_index].taken_host_time = NONE;
|
||||
/* (of the unit it has now: not one of a life before) */
|
||||
if (unit_index != NONE && unit_index == state->unit_index)
|
||||
/* (of the unit it has now: not one of a life before; and not
|
||||
from a co-op client still loading the host's BSP, whose player
|
||||
falls there with no floor under it) */
|
||||
if (unit_index != NONE && unit_index == state->unit_index &&
|
||||
network_coop_player_has_structure_bsp(unit_get(unit_index)->unit.player_index))
|
||||
{
|
||||
distributed_take_prediction(player_index, unit_index, &bound);
|
||||
}
|
||||
}
|
||||
/* (what the host's next tick starts from) */
|
||||
if (unit_index != NONE)
|
||||
@@ -2257,6 +2264,7 @@ static void distributed_client_send_inputs(
|
||||
if (!update_client_distributed_input(local_player_index, &input->tick, &input->action, input->control_flags))
|
||||
continue;
|
||||
input->player_index = distributed_player_to_byte(player_index);
|
||||
input->structure_bsp_index = (byte)global_structure_bsp_index_get();
|
||||
input->host_time = distributed_host_time;
|
||||
count++;
|
||||
}
|
||||
@@ -2330,6 +2338,7 @@ static void distributed_handle_inputs(
|
||||
/* (a spectator's buttons pick whom it watches: not counted) */
|
||||
if (network_coop_active() && player->unit_index != NONE)
|
||||
distributed_note_input_actions(input->player_index, &action);
|
||||
network_coop_note_player_structure_bsp(input->player_index, input->structure_bsp_index);
|
||||
update_server_handle_distributed_input(DATUM_INDEX_NEW(input->player_index, player->identifier), input->tick,
|
||||
&action, input->control_flags, DISTRIBUTED_INPUT_HISTORY);
|
||||
if (input->host_time != NONE && (host_time == NONE || input->host_time > host_time))
|
||||
@@ -3001,10 +3010,13 @@ static void distributed_send_pings(
|
||||
}
|
||||
|
||||
/* Co-op: the host's structure BSP. Clients switch BSP only when told
|
||||
(their own trigger volumes don't, players.c). It is resent regularly so a
|
||||
client that lost a message or joined late catches up. */
|
||||
(their own trigger volumes don't, players.c). A switch is sent at once and
|
||||
reliably; it is also resent regularly so a client that joined late catches
|
||||
up. */
|
||||
#define STRUCTURE_BSP_INTERVAL_TICKS (TICKS_PER_SECOND / 2)
|
||||
|
||||
static short distributed_sent_structure_bsp_index = NONE;
|
||||
|
||||
struct distributed_structure_bsp
|
||||
{
|
||||
short structure_bsp_index;
|
||||
@@ -3021,10 +3033,14 @@ static void distributed_send_structure_bsp(
|
||||
void)
|
||||
{
|
||||
struct distributed_structure_bsp_message message;
|
||||
short index = global_structure_bsp_index_get();
|
||||
boolean switched = index != distributed_sent_structure_bsp_index;
|
||||
|
||||
message.structure_bsp.structure_bsp_index = global_structure_bsp_index_get();
|
||||
message.structure_bsp.structure_bsp_index = index;
|
||||
message.structure_bsp.pad = 0;
|
||||
distributed_send(&message, _distributed_message_structure_bsp, 1, (word)sizeof(message), _distributed_to_clients);
|
||||
distributed_send(&message, _distributed_message_structure_bsp, 1, (word)sizeof(message),
|
||||
switched ? _distributed_to_clients_reliably : _distributed_to_clients);
|
||||
distributed_sent_structure_bsp_index = index;
|
||||
}
|
||||
|
||||
static void distributed_handle_structure_bsp(
|
||||
@@ -3287,10 +3303,15 @@ void network_distributed_tick(
|
||||
distributed_statistics_due = FALSE;
|
||||
if (game_time_get() % PING_INTERVAL_TICKS == 0)
|
||||
distributed_send_pings();
|
||||
/* (before the players, so clients load a new BSP before they hear
|
||||
where the host moved everyone into it) */
|
||||
if (network_coop_active() && (global_structure_bsp_index_get() != distributed_sent_structure_bsp_index ||
|
||||
game_time_get() % STRUCTURE_BSP_INTERVAL_TICKS == 0))
|
||||
{
|
||||
distributed_send_structure_bsp();
|
||||
}
|
||||
distributed_host_send_players();
|
||||
network_actors_host_tick();
|
||||
if (network_coop_active() && game_time_get() % STRUCTURE_BSP_INTERVAL_TICKS == 0)
|
||||
distributed_send_structure_bsp();
|
||||
network_coop_host_tick();
|
||||
distributed_send_pickups();
|
||||
if (game_time_get() % GAME_STATE_INTERVAL_TICKS == 0)
|
||||
|
||||
@@ -1795,6 +1795,10 @@ void network_objects_apply_vehicle_predictions(
|
||||
{
|
||||
continue;
|
||||
}
|
||||
/* (not from a co-op client still loading the host's BSP: the
|
||||
vehicle falls there with no ground under it) */
|
||||
if (!network_coop_player_has_structure_bsp(driver->unit.player_index))
|
||||
continue;
|
||||
dx = state->position.x - vehicle->object.position.x;
|
||||
dy = state->position.y - vehicle->object.position.y;
|
||||
dz = state->position.z - vehicle->object.position.z;
|
||||
|
||||
+53
-13
@@ -470,9 +470,10 @@ static struct
|
||||
long respawn_wait_since;
|
||||
/* when each player's unit left the loaded structure BSP */
|
||||
long stranded_since[NETWORK_GAME_MAXIMUM_PLAYER_COUNT];
|
||||
/* no BSP switch trigger counts before this game time (one has just
|
||||
switched, and the players are being brought into the new BSP) */
|
||||
long bsp_switch_allowed_time;
|
||||
/* the structure BSP loaded, and when it last switched, by a trigger,
|
||||
a script or a skipped cutscene (players_coop_bsp_switch_ready) */
|
||||
short structure_bsp_index;
|
||||
long bsp_switch_time;
|
||||
/* the structure BSPs the players have been in this map, by bit */
|
||||
word visited_structure_bsps;
|
||||
/* where each player last stood on the ground, and on which BSP: where
|
||||
@@ -2771,8 +2772,10 @@ someone who is only crossing a seam or riding over a gap. */
|
||||
#define COOP_STRANDED_TICKS (2 * TICKS_PER_SECOND)
|
||||
|
||||
/* After a BSP switch, how long the co-op host lets no other trigger switch
|
||||
it while the players are brought into the new one */
|
||||
it while the players are brought into the new one, and the longest it then
|
||||
waits for every machine to have loaded it */
|
||||
#define COOP_BSP_SWITCH_SETTLE_TICKS (2 * TICKS_PER_SECOND)
|
||||
#define COOP_BSP_SWITCH_WAIT_TICKS (10 * TICKS_PER_SECOND)
|
||||
|
||||
/* Going back to a BSP the team has already been in needs two thirds of the
|
||||
living players at the trigger: inside it, or this near the player in it
|
||||
@@ -2987,10 +2990,37 @@ static boolean players_coop_in_bsp_switch_trigger(
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* port: whether a network co-op host lets any trigger switch the BSP yet.
|
||||
After a switch it waits COOP_BSP_SWITCH_SETTLE_TICKS while the players are
|
||||
brought in, then until every player's machine has loaded the new BSP, so no
|
||||
client falls two switches behind. A client stuck longer than
|
||||
COOP_BSP_SWITCH_WAIT_TICKS no longer holds the team. */
|
||||
static boolean players_coop_bsp_switch_ready(
|
||||
void)
|
||||
{
|
||||
long since = game_time_get() - players_coop_state.bsp_switch_time;
|
||||
struct data_iterator iterator;
|
||||
struct player_datum *player;
|
||||
|
||||
if (since < COOP_BSP_SWITCH_SETTLE_TICKS)
|
||||
return FALSE;
|
||||
if (since >= COOP_BSP_SWITCH_WAIT_TICKS)
|
||||
return TRUE;
|
||||
data_iterator_new(&iterator, player_data);
|
||||
while ((player = data_iterator_next(&iterator)) != NULL)
|
||||
{
|
||||
if (player->unit_index != NONE && !network_coop_player_has_structure_bsp(iterator.datum_index))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* port: whether a network co-op host lets the unit on this trigger switch
|
||||
the BSP, which brings the whole team along. Anyone can lead the team into a
|
||||
BSP it hasn't been in; going back to one needs two thirds of the team
|
||||
there, so one player can't drag everyone back through the level. */
|
||||
the BSP, which brings the whole team along. Not while the unit's machine
|
||||
is still loading the BSP the host has. Anyone can lead the team into a BSP
|
||||
it hasn't been in; going back to one needs two thirds of the team there,
|
||||
so one player can't drag everyone back through the level. */
|
||||
static boolean players_coop_bsp_switch_allowed(
|
||||
short bsp_switch_trigger_volume_index,
|
||||
long unit_index)
|
||||
@@ -3004,6 +3034,8 @@ static boolean players_coop_bsp_switch_allowed(
|
||||
short living_count = 0;
|
||||
short gathered_count = 0;
|
||||
|
||||
if (!network_coop_player_has_structure_bsp(unit_get(unit_index)->unit.player_index))
|
||||
return FALSE;
|
||||
if (!VALID_INDEX(volume->destination_structure_bsp_index, MAXIMUM_STRUCTURE_BSPS_PER_SCENARIO) ||
|
||||
!TEST_FLAG(players_coop_state.visited_structure_bsps, volume->destination_structure_bsp_index))
|
||||
{
|
||||
@@ -3053,7 +3085,7 @@ static void player_teleport_on_bsp_switch(
|
||||
whoever crossed it, as split screen does. Only one BSP is ever
|
||||
loaded, so anyone left elsewhere would stand outside it, or on a
|
||||
trigger that switches it straight back (players_update_after_game
|
||||
waits COOP_BSP_SWITCH_SETTLE_TICKS before another switch). */
|
||||
waits for players_coop_bsp_switch_ready before another switch). */
|
||||
if (network_coop_active())
|
||||
{
|
||||
outside_switch_trigger = TRUE;
|
||||
@@ -3093,6 +3125,10 @@ static void player_teleport_on_bsp_switch(
|
||||
player_index,
|
||||
source_unit_index,
|
||||
position);
|
||||
/* port: a big co-op team runs out of room around the
|
||||
crosser, so the rest go beside whoever is already in */
|
||||
if (!teleport_succeeded && network_coop_active())
|
||||
teleport_succeeded = player_place_beside_teammate(player_index);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -4550,6 +4586,11 @@ void players_update_after_game(
|
||||
}
|
||||
if (VALID_INDEX(global_structure_bsp_index, MAXIMUM_STRUCTURE_BSPS_PER_SCENARIO))
|
||||
SET_FLAG(players_coop_state.visited_structure_bsps, global_structure_bsp_index, TRUE);
|
||||
if (players_coop_state.structure_bsp_index != global_structure_bsp_index)
|
||||
{
|
||||
players_coop_state.structure_bsp_index = global_structure_bsp_index;
|
||||
players_coop_state.bsp_switch_time = game_time_get();
|
||||
}
|
||||
|
||||
data_iterator_new(&iterator, player_data);
|
||||
while (player = data_iterator_next(&iterator))
|
||||
@@ -4597,11 +4638,10 @@ void players_update_after_game(
|
||||
root_object_index = object_get_ultimate_parent(player->unit_index);
|
||||
root_object = object_get(root_object_index);
|
||||
/* port: a co-op client only switches BSP when the host does
|
||||
(network_distributed.c), and the host not again until the players
|
||||
are in the new one */
|
||||
(network_distributed.c), and the host not again until everyone
|
||||
has the new one */
|
||||
if (!TEST_FLAG(root_object->object.flags, _object_outside_of_map_bit) &&
|
||||
!(network_coop_active() &&
|
||||
(network_game_distributed_client() || game_time_get() < players_coop_state.bsp_switch_allowed_time)))
|
||||
!(network_coop_active() && (network_game_distributed_client() || !players_coop_bsp_switch_ready())))
|
||||
{
|
||||
scenario = global_scenario_get();
|
||||
for (bsp_switch_trigger_volume_index = 0;
|
||||
@@ -4637,7 +4677,7 @@ void players_update_after_game(
|
||||
bsp_switch_trigger_volume_index;
|
||||
main_switch_structure_bsp(
|
||||
bsp_switch_trigger_volume->destination_structure_bsp_index);
|
||||
players_coop_state.bsp_switch_allowed_time = game_time_get() + COOP_BSP_SWITCH_SETTLE_TICKS;
|
||||
players_coop_state.bsp_switch_time = game_time_get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user