RingRacers/src/g_party.cpp
2025-02-13 15:32:26 -06:00

333 lines
7.2 KiB
C++

// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2025 by James Robert Roman
// Copyright (C) 2025 by Kart Krew
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include <algorithm>
#include <array>
#include <cstddef>
#include <functional>
#include <iterator>
#include "core/static_vec.hpp"
#include "cxxutil.hpp"
#include "d_clisrv.h" // playerconsole
#include "doomdef.h" // MAXPLAYERS
#include "doomstat.h" // consoleplayer
#include "g_game.h" // G_FixCamera
#include "g_party.h"
#include "g_state.h"
#include "p_local.h"
#include "r_fps.h"
#include "r_main.h" // R_ExecuteSetViewSize
namespace
{
using playernum_t = uint8_t;
class Party
{
public:
class Console
{
public:
// The Console class is basically analogous to
// a playernum except local splitscreen players only
// resolve to one playernum.
//
// Local splitscreen players are always joined with
// each other, so this lets just one party to refer to
// that group.
Console(playernum_t player)
{
SRB2_ASSERT(player >= 0 && player < MAXPLAYERS);
console_ = playerconsole[player];
SRB2_ASSERT(console_ >= 0 && console_ < MAXPLAYERS);
}
operator playernum_t() const { return console_; }
private:
playernum_t console_;
};
//
// Write Access Methods
//
// Add a single player.
void add(playernum_t player) { vec_.push_back(player); }
// Add every player from another party.
void add(const Party& party) { std::copy(party.vec_.begin(), party.vec_.end(), std::back_inserter(vec_)); }
// Remove every player whose console is the same.
void remove(Console console)
{
auto it = std::remove_if(vec_.begin(), vec_.end(), [console](Console other) { return other == console; });
while (it < vec_.end())
{
vec_.pop_back();
}
}
//
// Read Access Methods
//
std::size_t size() const { return vec_.size(); }
// The player at this position in the party.
playernum_t at(std::size_t i) const { return vec_[i]; }
playernum_t operator[](std::size_t i) const { return at(i); }
// C array access to the raw player numbers.
const playernum_t* data() const { return &vec_[0]; }
// True if the player is a member of this party.
bool contains(playernum_t player) const { return std::find(vec_.begin(), vec_.end(), player) != vec_.end(); }
// True if the consoleplayer is a member of this party.
bool local() const
{
// consoleplayer is not valid yet.
if (!addedtogame && !demo.playback)
{
return false;
}
return contains(consoleplayer);
}
// Returns a party composed of only the unique consoles
// from this party.
Party consoles() const
{
Party party;
std::unique_copy(vec_.begin(), vec_.end(), std::back_inserter(party.vec_), std::equal_to<Console>());
return party;
}
// If the party is local, set the correct viewports.
void rebuild_displayplayers() const
{
if (!local())
{
return;
}
for (std::size_t i = 0; i < size(); ++i)
{
displayplayers[i] = at(i);
// Camera is not valid outside of levels.
if (G_GamestateUsesLevel())
{
G_FixCamera(1 + i);
}
}
r_splitscreen = size() - 1;
R_ExecuteSetViewSize(); // present viewport
}
//
// Iterators
//
// Returns an iterator to the player within this party if
// they are a member. Else returns the end() iterator.
auto find(playernum_t player) const
{
return std::find(vec_.begin(), vec_.end(), player);
}
// Iterator to the beginning of the party.
auto begin() const { return vec_.begin(); }
// Iterator to the end of the party.
auto end() const { return vec_.end(); }
private:
srb2::StaticVec<playernum_t, MAXSPLITSCREENPLAYERS> vec_;
};
class PartyManager
{
public:
// To avoid copying the same party to each local
// splitscreen player, all lookups will use the
// consoleplayer.
Party& operator [](Party::Console console) { return pool_[console]; }
// Clears a single player's local party. This method
// accesses the playernum directly, instead of the
// consoleplayer.
void reset(playernum_t player) { pool_[player] = {}; }
protected:
std::array<Party, MAXPLAYERS> pool_;
}
local_party;
class FinalPartyManager : public PartyManager
{
public:
// Adds guest's entire local splitscreen party to the
// host's party. If the operation succeeds, host and guest
// parties are guaranteed to be identical and the
// viewports are updated for every player involved.
bool join(Party::Console host, Party::Console guest)
{
Party &party = pool_[host];
// Already in the same party.
if (party.contains(guest))
{
return false;
}
// Parties do not fit when merged.
if (party.size() + local_party[guest].size() > MAXSPLITSCREENPLAYERS)
{
return false;
}
// If the host party includes players from a local
// party, iterating the unique consoles avoids
// duplicate insertions of the guest.
for (Party::Console other : party.consoles())
{
pool_[other].add(local_party[guest]);
}
reset(guest, party); // assign new party to guest
return true;
}
// Removes a player from another party and assigns a new
// party. Viewports are updated for all players involved.
void reset(Party::Console player, const Party &party)
{
SRB2_ASSERT(party.size() > 0);
remove(player);
pool_[player] = party;
party.rebuild_displayplayers();
}
private:
// Removes a player from every party they're in. Updates
// viewports for the players left behind.
void remove(Party::Console player)
{
Party &party = pool_[player];
// Iterate a COPY of party because this very party
// will be modified.
for (Party::Console member : Party(party))
{
pool_[member].remove(player);
}
party.rebuild_displayplayers(); // restore viewports for left behind party
}
}
final_party;
}; // namespace
INT32 splitscreen_invitations[MAXPLAYERS];
void G_ObliterateParties(void)
{
final_party = {};
local_party = {};
}
void G_DestroyParty(UINT8 player)
{
local_party.reset(player);
final_party[player] = {};
}
void G_BuildLocalSplitscreenParty(UINT8 player)
{
local_party[player].add(player);
final_party[player] = local_party[player];
}
void G_JoinParty(UINT8 host, UINT8 guest)
{
final_party.join(host, guest);
}
void G_LeaveParty(UINT8 player)
{
final_party.reset(player, local_party[player]);
}
UINT8 G_LocalSplitscreenPartySize(UINT8 player)
{
return local_party[player].size();
}
UINT8 G_PartySize(UINT8 player)
{
return final_party[player].size();
}
boolean G_IsPartyLocal(UINT8 player)
{
return final_party[player].local();
}
UINT8 G_PartyMember(UINT8 player, UINT8 index)
{
SRB2_ASSERT(index < final_party[player].size());
return final_party[player][index];
}
const UINT8* G_PartyArray(UINT8 player)
{
return final_party[player].data();
}
UINT8 G_PartyPosition(UINT8 player)
{
const Party& party = final_party[player];
return party.find(player) - party.begin();
}
UINT8 G_LocalSplitscreenPartyPosition(UINT8 player)
{
const Party& party = local_party[player];
return party.find(player) - party.begin();
}
UINT8 G_LocalSplitscreenPartyMember(UINT8 player, UINT8 index)
{
SRB2_ASSERT(index < local_party[player].size());
return local_party[player][index];
}