mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-10-30 08:01:28 +00:00
333 lines
7.2 KiB
C++
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];
|
|
}
|