Reworked player loading

Resolves a host of issues related to playing loading, both in singleplayer and multiplayer. Name-based player data selection now works meaning join order no longer has to be preserved.
This commit is contained in:
Boondorl 2024-11-03 16:15:41 -05:00 committed by Rachael Alexanderson
parent a899571d8c
commit 714eb8910c
2 changed files with 97 additions and 122 deletions

View file

@ -167,8 +167,8 @@ private:
void SerializePlayers(FSerializer &arc, bool skipload);
void CopyPlayer(player_t *dst, player_t *src, const char *name);
void ReadOnePlayer(FSerializer &arc, bool skipload);
void ReadMultiplePlayers(FSerializer &arc, int numPlayers, int numPlayersNow, bool skipload);
void ReadOnePlayer(FSerializer &arc, bool fromHub);
void ReadMultiplePlayers(FSerializer &arc, int numPlayers, bool fromHub);
void SerializeSounds(FSerializer &arc);
void PlayerSpawnPickClass (int playernum);

View file

@ -627,8 +627,8 @@ void FLevelLocals::SerializePlayers(FSerializer &arc, bool skipload)
{
if (arc.BeginObject(nullptr))
{
const char *n = Players[i]->userinfo.GetName();
arc.StringPtr("playername", n);
FString name = Players[i]->userinfo.GetName();
arc("playername", name);
Players[i]->Serialize(arc);
arc.EndObject();
}
@ -651,7 +651,7 @@ void FLevelLocals::SerializePlayers(FSerializer &arc, bool skipload)
}
else
{
ReadMultiplePlayers(arc, numPlayers, numPlayersNow, skipload);
ReadMultiplePlayers(arc, numPlayers, skipload);
}
arc.EndArray();
@ -680,53 +680,41 @@ void FLevelLocals::SerializePlayers(FSerializer &arc, bool skipload)
//
//==========================================================================
void FLevelLocals::ReadOnePlayer(FSerializer &arc, bool skipload)
void FLevelLocals::ReadOnePlayer(FSerializer &arc, bool fromHub)
{
int i;
const char *name = NULL;
bool didIt = false;
if (!arc.BeginObject(nullptr))
return;
if (arc.BeginObject(nullptr))
FString name = {};
arc("playername", name);
player_t temp = {};
temp.Serialize(arc);
for (int i = 0; i < MAXPLAYERS; ++i)
{
arc.StringPtr("playername", name);
if (!PlayerInGame(i))
continue;
for (i = 0; i < MAXPLAYERS; ++i)
if (!fromHub)
{
if (playeringame[i])
{
if (!didIt)
{
didIt = true;
player_t playerTemp;
playerTemp.Serialize(arc);
if (!skipload)
{
// This temp player has undefined pitch limits, so set them to something
// that should leave the pitch stored in the savegame intact when
// rendering. The real pitch limits will be set by P_SerializePlayers()
// via a net command, but that won't be processed in time for a screen
// wipe, so we need something here.
playerTemp.MaxPitch = playerTemp.MinPitch = playerTemp.mo->Angles.Pitch;
CopyPlayer(Players[i], &playerTemp, name);
}
else
{
// we need the player actor, so that G_FinishTravel can destroy it later.
Players[i]->mo = playerTemp.mo;
}
}
else
{
if (Players[i]->mo != NULL)
{
Players[i]->mo->Destroy();
Players[i]->mo = NULL;
}
}
}
// This temp player has undefined pitch limits, so set them to something
// that should leave the pitch stored in the savegame intact when
// rendering. The real pitch limits will be set by P_SerializePlayers()
// via a net command, but that won't be processed in time for a screen
// wipe, so we need something here.
temp.MaxPitch = temp.MinPitch = temp.mo->Angles.Pitch;
CopyPlayer(Players[i], &temp, name.GetChars());
}
arc.EndObject();
else
{
// we need the player actor, so that G_FinishTravel can destroy it later.
Players[i]->mo = temp.mo;
}
break;
}
arc.EndObject();
}
//==========================================================================
@ -735,109 +723,96 @@ void FLevelLocals::ReadOnePlayer(FSerializer &arc, bool skipload)
//
//==========================================================================
void FLevelLocals::ReadMultiplePlayers(FSerializer &arc, int numPlayers, int numPlayersNow, bool skipload)
struct NetworkPlayerInfo
{
// For two or more players, read each player into a temporary array.
int i, j;
const char **nametemp = new const char *[numPlayers];
player_t *playertemp = new player_t[numPlayers];
uint8_t *tempPlayerUsed = new uint8_t[numPlayers];
uint8_t playerUsed[MAXPLAYERS];
FString Name = {};
player_t Info = {};
bool bUsed = false;
};
for (i = 0; i < numPlayers; ++i)
void FLevelLocals::ReadMultiplePlayers(FSerializer &arc, int numPlayers, bool fromHub)
{
TArray<NetworkPlayerInfo> tempPlayers = {};
tempPlayers.Reserve(numPlayers);
TArray<bool> assignedPlayers = {};
assignedPlayers.Reserve(MAXPLAYERS);
// Read all the save game players into a temporary array
for (auto& p : tempPlayers)
{
nametemp[i] = NULL;
if (arc.BeginObject(nullptr))
{
arc.StringPtr("playername", nametemp[i]);
playertemp[i].Serialize(arc);
arc("playername", p.Name);
p.Info.Serialize(arc);
arc.EndObject();
}
tempPlayerUsed[i] = 0;
}
for (i = 0; i < MAXPLAYERS; ++i)
{
playerUsed[i] = playeringame[i] ? 0 : 2;
}
if (!skipload)
// Now try to match players from the savegame with players present
// based on their names. If two players in the savegame have the
// same name, then they are assigned to players in the current game
// on a first-come, first-served basis.
for (int i = 0; i < MAXPLAYERS; ++i)
{
// Now try to match players from the savegame with players present
// based on their names. If two players in the savegame have the
// same name, then they are assigned to players in the current game
// on a first-come, first-served basis.
for (i = 0; i < numPlayers; ++i)
{
for (j = 0; j < MAXPLAYERS; ++j)
{
if (playerUsed[j] == 0 && stricmp(players[j].userinfo.GetName(), nametemp[i]) == 0)
{ // Found a match, so copy our temp player to the real player
Printf("Found player %d (%s) at %d\n", i, nametemp[i], j);
CopyPlayer(Players[j], &playertemp[i], nametemp[i]);
playerUsed[j] = 1;
tempPlayerUsed[i] = 1;
break;
}
}
}
if (!PlayerInGame(i))
continue;
// Any players that didn't have matching names are assigned to existing
// players on a first-come, first-served basis.
for (i = 0; i < numPlayers; ++i)
for (auto& p : tempPlayers)
{
if (tempPlayerUsed[i] == 0)
if (!p.bUsed && !p.Name.Compare(Players[i]->userinfo.GetName()))
{
for (j = 0; j < MAXPLAYERS; ++j)
// Found a match, so copy our temp player to the real player
if (!fromHub)
{
if (playerUsed[j] == 0)
{
Printf("Assigned player %d (%s) to %d (%s)\n", i, nametemp[i], j, players[j].userinfo.GetName());
CopyPlayer(&players[j], &playertemp[i], nametemp[i]);
playerUsed[j] = 1;
tempPlayerUsed[i] = 1;
break;
}
Printf("Found %s's (%d) data\n", Players[i]->userinfo.GetName(), i);
CopyPlayer(Players[i], &p.Info, p.Name.GetChars());
}
}
}
// Make sure any extra players don't have actors spawned yet. Happens if the players
// present now got the same slots as they had in the save, but there are not as many
// as there were in the save.
for (j = 0; j < MAXPLAYERS; ++j)
{
if (playerUsed[j] == 0)
{
if (players[j].mo != NULL)
else
{
players[j].mo->Destroy();
players[j].mo = NULL;
Players[i]->mo = p.Info.mo;
}
}
}
// Remove any temp players that were not used. Happens if there are fewer players
// than there were in the save, and they got shuffled.
for (i = 0; i < numPlayers; ++i)
{
if (tempPlayerUsed[i] == 0)
{
playertemp[i].mo->Destroy();
playertemp[i].mo = NULL;
p.bUsed = true;
assignedPlayers[i] = true;
break;
}
}
}
else
// Any players that didn't have matching names are assigned to existing
// players on a first-come, first-served basis.
for (int i = 0; i < MAXPLAYERS; ++i)
{
for (i = 0; i < numPlayers; ++i)
if (!PlayerInGame(i) || assignedPlayers[i])
continue;
for (auto& p : tempPlayers)
{
players[i].mo = playertemp[i].mo;
if (!p.bUsed)
{
if (!fromHub)
{
Printf("Assigned %s (%d) to %s's data\n", Players[i]->userinfo.GetName(), i, p.Name.GetChars());
CopyPlayer(Players[i], &p.Info, p.Name.GetChars());
}
else
{
Players[i]->mo = p.Info.mo;
}
p.bUsed = true;
break;
}
}
}
delete[] tempPlayerUsed;
delete[] playertemp;
delete[] nametemp;
// Remove any temp players that were not used. Happens if there are now
// less players in the game than there were in the save
for (auto& p : tempPlayers)
{
if (!p.bUsed)
p.Info.mo->Destroy();
}
}
//==========================================================================