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) 2024 by James Robert Roman
 | 
						|
// Copyright (C) 2024 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];
 | 
						|
}
 |