NetPlay: Allow controller selection for host & clients + Spectator mode
This commit is contained in:
parent
361f4b8025
commit
3e798af865
27 changed files with 594 additions and 55 deletions
|
@ -12,8 +12,10 @@ public:
|
||||||
uint8_t* AvatarData;
|
uint8_t* AvatarData;
|
||||||
uint32_t AvatarSize;
|
uint32_t AvatarSize;
|
||||||
|
|
||||||
ClientConnectionData(string host, uint16_t port, string playerName, uint8_t* avatarData, uint32_t avatarSize) :
|
bool Spectator;
|
||||||
Host(host), Port(port), PlayerName(playerName), AvatarSize(avatarSize)
|
|
||||||
|
ClientConnectionData(string host, uint16_t port, string playerName, uint8_t* avatarData, uint32_t avatarSize, bool spectator) :
|
||||||
|
Host(host), Port(port), PlayerName(playerName), AvatarSize(avatarSize), Spectator(spectator)
|
||||||
{
|
{
|
||||||
if(avatarSize > 0) {
|
if(avatarSize > 0) {
|
||||||
AvatarData = new uint8_t[avatarSize];
|
AvatarData = new uint8_t[avatarSize];
|
||||||
|
|
|
@ -407,9 +407,11 @@
|
||||||
<ClInclude Include="MMC3_52.h" />
|
<ClInclude Include="MMC3_52.h" />
|
||||||
<ClInclude Include="MMC3_ChrRam.h" />
|
<ClInclude Include="MMC3_ChrRam.h" />
|
||||||
<ClInclude Include="ModChannel.h" />
|
<ClInclude Include="ModChannel.h" />
|
||||||
|
<ClInclude Include="PlayerListMessage.h" />
|
||||||
<ClInclude Include="RomData.h" />
|
<ClInclude Include="RomData.h" />
|
||||||
<ClInclude Include="NtdecTc112.h" />
|
<ClInclude Include="NtdecTc112.h" />
|
||||||
<ClInclude Include="Rambo1.h" />
|
<ClInclude Include="Rambo1.h" />
|
||||||
|
<ClInclude Include="SelectControllerMessage.h" />
|
||||||
<ClInclude Include="SoundMixer.h" />
|
<ClInclude Include="SoundMixer.h" />
|
||||||
<ClInclude Include="Namco108.h" />
|
<ClInclude Include="Namco108.h" />
|
||||||
<ClInclude Include="Namco108_154.h" />
|
<ClInclude Include="Namco108_154.h" />
|
||||||
|
|
|
@ -539,6 +539,12 @@
|
||||||
<ClInclude Include="BaseControlDevice.h">
|
<ClInclude Include="BaseControlDevice.h">
|
||||||
<Filter>Nes\Controllers</Filter>
|
<Filter>Nes\Controllers</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="PlayerListMessage.h">
|
||||||
|
<Filter>NetPlay\Messages</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="SelectControllerMessage.h">
|
||||||
|
<Filter>NetPlay\Messages</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="stdafx.cpp">
|
<ClCompile Include="stdafx.cpp">
|
||||||
|
|
|
@ -85,5 +85,34 @@ void GameClient::ProcessNotification(ConsoleNotificationType type, void* paramet
|
||||||
|
|
||||||
uint8_t GameClient::GetControllerState(uint8_t port)
|
uint8_t GameClient::GetControllerState(uint8_t port)
|
||||||
{
|
{
|
||||||
return Instance->_connection->GetControllerState(port);
|
if(Instance && Instance->_connection) {
|
||||||
|
return Instance->_connection->GetControllerState(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameClient::SelectController(uint8_t port)
|
||||||
|
{
|
||||||
|
if(Instance && Instance->_connection) {
|
||||||
|
Instance->_connection->SelectController(port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t GameClient::GetAvailableControllers()
|
||||||
|
{
|
||||||
|
if(Instance && Instance->_connection) {
|
||||||
|
return Instance->_connection->GetAvailableControllers();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t GameClient::GetControllerPort()
|
||||||
|
{
|
||||||
|
if(Instance && Instance->_connection) {
|
||||||
|
return Instance->_connection->GetControllerPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
return GameConnection::SpectatorPort;
|
||||||
}
|
}
|
|
@ -29,6 +29,10 @@ public:
|
||||||
static void Connect(shared_ptr<ClientConnectionData> connectionData);
|
static void Connect(shared_ptr<ClientConnectionData> connectionData);
|
||||||
static void Disconnect();
|
static void Disconnect();
|
||||||
|
|
||||||
|
static void SelectController(uint8_t port);
|
||||||
|
static uint8_t GetControllerPort();
|
||||||
|
static uint8_t GetAvailableControllers();
|
||||||
|
|
||||||
static uint8_t GetControllerState(uint8_t port);
|
static uint8_t GetControllerState(uint8_t port);
|
||||||
|
|
||||||
void ProcessNotification(ConsoleNotificationType type, void* parameter);
|
void ProcessNotification(ConsoleNotificationType type, void* parameter);
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
#include "ControlManager.h"
|
#include "ControlManager.h"
|
||||||
#include "ClientConnectionData.h"
|
#include "ClientConnectionData.h"
|
||||||
#include "StandardController.h"
|
#include "StandardController.h"
|
||||||
|
#include "SelectControllerMessage.h"
|
||||||
|
#include "PlayerListMessage.h"
|
||||||
|
|
||||||
GameClientConnection::GameClientConnection(shared_ptr<Socket> socket, shared_ptr<ClientConnectionData> connectionData) : GameConnection(socket, connectionData)
|
GameClientConnection::GameClientConnection(shared_ptr<Socket> socket, shared_ptr<ClientConnectionData> connectionData) : GameConnection(socket, connectionData)
|
||||||
{
|
{
|
||||||
|
@ -28,7 +30,13 @@ GameClientConnection::~GameClientConnection()
|
||||||
|
|
||||||
void GameClientConnection::SendHandshake()
|
void GameClientConnection::SendHandshake()
|
||||||
{
|
{
|
||||||
HandShakeMessage message(_connectionData->PlayerName, _connectionData->AvatarData, _connectionData->AvatarSize);
|
HandShakeMessage message(_connectionData->PlayerName, _connectionData->AvatarData, _connectionData->AvatarSize, _connectionData->Spectator);
|
||||||
|
SendNetMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameClientConnection::SendControllerSelection(uint8_t port)
|
||||||
|
{
|
||||||
|
SelectControllerMessage message(port);
|
||||||
SendNetMessage(message);
|
SendNetMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,21 +65,31 @@ void GameClientConnection::ProcessMessage(NetMessage* message)
|
||||||
case ControllerType::Zapper: _controlDevice = ControlManager::GetControlDevice(_controllerPort); break;
|
case ControllerType::Zapper: _controlDevice = ControlManager::GetControlDevice(_controllerPort); break;
|
||||||
}
|
}
|
||||||
Console::Resume();
|
Console::Resume();
|
||||||
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MessageType::MovieData:
|
case MessageType::MovieData:
|
||||||
if(_gameLoaded) {
|
if(_gameLoaded) {
|
||||||
PushControllerState(((MovieDataMessage*)message)->GetPortNumber(), ((MovieDataMessage*)message)->GetInputState());
|
PushControllerState(((MovieDataMessage*)message)->GetPortNumber(), ((MovieDataMessage*)message)->GetInputState());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case MessageType::PlayerList:
|
||||||
|
_playerList = ((PlayerListMessage*)message)->GetPlayerList();
|
||||||
|
break;
|
||||||
|
|
||||||
case MessageType::GameInformation:
|
case MessageType::GameInformation:
|
||||||
DisableControllers();
|
DisableControllers();
|
||||||
Console::Pause();
|
Console::Pause();
|
||||||
gameInfo = (GameInformationMessage*)message;
|
gameInfo = (GameInformationMessage*)message;
|
||||||
if(gameInfo->GetPort() != _controllerPort) {
|
if(gameInfo->GetPort() != _controllerPort) {
|
||||||
_controllerPort = gameInfo->GetPort();
|
_controllerPort = gameInfo->GetPort();
|
||||||
MessageManager::DisplayMessage("Net Play", string("Connected as player ") + std::to_string(_controllerPort + 1));
|
|
||||||
|
if(_controllerPort == GameConnection::SpectatorPort) {
|
||||||
|
MessageManager::DisplayMessage("Net Play", "Connected as spectator");
|
||||||
|
} else {
|
||||||
|
MessageManager::DisplayMessage("Net Play", string("Connected as player ") + std::to_string(_controllerPort + 1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ClearInputData();
|
ClearInputData();
|
||||||
|
@ -160,4 +178,25 @@ void GameClientConnection::SendInput()
|
||||||
_lastInputSent = inputState;
|
_lastInputSent = inputState;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameClientConnection::SelectController(uint8_t port)
|
||||||
|
{
|
||||||
|
SendControllerSelection(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t GameClientConnection::GetAvailableControllers()
|
||||||
|
{
|
||||||
|
uint8_t availablePorts = 0x0F;
|
||||||
|
for(PlayerInfo &playerInfo : _playerList) {
|
||||||
|
if(playerInfo.ControllerPort < 4) {
|
||||||
|
availablePorts &= ~(1 << playerInfo.ControllerPort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return availablePorts;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t GameClientConnection::GetControllerPort()
|
||||||
|
{
|
||||||
|
return _controllerPort;
|
||||||
}
|
}
|
|
@ -19,13 +19,16 @@ private:
|
||||||
atomic<bool> _enableControllers = false;
|
atomic<bool> _enableControllers = false;
|
||||||
atomic<uint32_t> _minimumQueueSize = 3;
|
atomic<uint32_t> _minimumQueueSize = 3;
|
||||||
|
|
||||||
|
vector<PlayerInfo> _playerList;
|
||||||
|
|
||||||
shared_ptr<BaseControlDevice> _controlDevice;
|
shared_ptr<BaseControlDevice> _controlDevice;
|
||||||
uint32_t _lastInputSent = 0x00;
|
uint32_t _lastInputSent = 0x00;
|
||||||
bool _gameLoaded = false;
|
bool _gameLoaded = false;
|
||||||
uint8_t _controllerPort = 255;
|
uint8_t _controllerPort = GameConnection::SpectatorPort;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void SendHandshake();
|
void SendHandshake();
|
||||||
|
void SendControllerSelection(uint8_t port);
|
||||||
void ClearInputData();
|
void ClearInputData();
|
||||||
void PushControllerState(uint8_t port, uint8_t state);
|
void PushControllerState(uint8_t port, uint8_t state);
|
||||||
void DisableControllers();
|
void DisableControllers();
|
||||||
|
@ -39,4 +42,8 @@ public:
|
||||||
|
|
||||||
uint8_t GetControllerState(uint8_t port);
|
uint8_t GetControllerState(uint8_t port);
|
||||||
void SendInput();
|
void SendInput();
|
||||||
|
|
||||||
|
void SelectController(uint8_t port);
|
||||||
|
uint8_t GetAvailableControllers();
|
||||||
|
uint8_t GetControllerPort();
|
||||||
};
|
};
|
|
@ -5,6 +5,8 @@
|
||||||
#include "MovieDataMessage.h"
|
#include "MovieDataMessage.h"
|
||||||
#include "GameInformationMessage.h"
|
#include "GameInformationMessage.h"
|
||||||
#include "SaveStateMessage.h"
|
#include "SaveStateMessage.h"
|
||||||
|
#include "PlayerListMessage.h"
|
||||||
|
#include "SelectControllerMessage.h"
|
||||||
#include "ClientConnectionData.h"
|
#include "ClientConnectionData.h"
|
||||||
|
|
||||||
GameConnection::GameConnection(shared_ptr<Socket> socket, shared_ptr<ClientConnectionData> connectionData)
|
GameConnection::GameConnection(shared_ptr<Socket> socket, shared_ptr<ClientConnectionData> connectionData)
|
||||||
|
@ -54,6 +56,8 @@ NetMessage* GameConnection::ReadMessage()
|
||||||
case MessageType::InputData: return new InputDataMessage(_messageBuffer, messageLength);
|
case MessageType::InputData: return new InputDataMessage(_messageBuffer, messageLength);
|
||||||
case MessageType::MovieData: return new MovieDataMessage(_messageBuffer, messageLength);
|
case MessageType::MovieData: return new MovieDataMessage(_messageBuffer, messageLength);
|
||||||
case MessageType::GameInformation: return new GameInformationMessage(_messageBuffer, messageLength);
|
case MessageType::GameInformation: return new GameInformationMessage(_messageBuffer, messageLength);
|
||||||
|
case MessageType::PlayerList: return new PlayerListMessage(_messageBuffer, messageLength);
|
||||||
|
case MessageType::SelectController: return new SelectControllerMessage(_messageBuffer, messageLength);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
|
@ -6,6 +6,13 @@ class Socket;
|
||||||
class NetMessage;
|
class NetMessage;
|
||||||
class ClientConnectionData;
|
class ClientConnectionData;
|
||||||
|
|
||||||
|
struct PlayerInfo
|
||||||
|
{
|
||||||
|
string Name;
|
||||||
|
uint8_t ControllerPort;
|
||||||
|
bool IsHost;
|
||||||
|
};
|
||||||
|
|
||||||
class GameConnection
|
class GameConnection
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
|
@ -24,14 +31,11 @@ private:
|
||||||
|
|
||||||
virtual void ProcessMessage(NetMessage* message) = 0;
|
virtual void ProcessMessage(NetMessage* message) = 0;
|
||||||
|
|
||||||
protected:
|
|
||||||
void SendNetMessage(NetMessage &message);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
static const uint8_t SpectatorPort = 0xFF;
|
||||||
GameConnection(shared_ptr<Socket> socket, shared_ptr<ClientConnectionData> connectionData);
|
GameConnection(shared_ptr<Socket> socket, shared_ptr<ClientConnectionData> connectionData);
|
||||||
|
|
||||||
bool ConnectionError();
|
bool ConnectionError();
|
||||||
|
|
||||||
void ProcessMessages();
|
void ProcessMessages();
|
||||||
|
void SendNetMessage(NetMessage &message);
|
||||||
};
|
};
|
|
@ -6,11 +6,15 @@ using std::thread;
|
||||||
#include "GameServer.h"
|
#include "GameServer.h"
|
||||||
#include "Console.h"
|
#include "Console.h"
|
||||||
#include "../Utilities/Socket.h"
|
#include "../Utilities/Socket.h"
|
||||||
|
#include "PlayerListMessage.h"
|
||||||
|
|
||||||
unique_ptr<GameServer> GameServer::Instance;
|
unique_ptr<GameServer> GameServer::Instance;
|
||||||
|
|
||||||
GameServer::GameServer()
|
GameServer::GameServer(uint16_t listenPort, string hostPlayerName)
|
||||||
{
|
{
|
||||||
|
_port = listenPort;
|
||||||
|
_hostPlayerName = hostPlayerName;
|
||||||
|
_hostControllerPort = 0;
|
||||||
ControlManager::RegisterBroadcaster(this);
|
ControlManager::RegisterBroadcaster(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +33,7 @@ void GameServer::AcceptConnections()
|
||||||
while(true) {
|
while(true) {
|
||||||
shared_ptr<Socket> socket = _listener->Accept();
|
shared_ptr<Socket> socket = _listener->Accept();
|
||||||
if(!socket->ConnectionError()) {
|
if(!socket->ConnectionError()) {
|
||||||
_openConnections.push_back(shared_ptr<GameServerConnection>(new GameServerConnection(socket, 1)));
|
_openConnections.push_back(shared_ptr<GameServerConnection>(new GameServerConnection(socket)));
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -53,6 +57,15 @@ void GameServer::UpdateConnections()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
list<shared_ptr<GameServerConnection>> GameServer::GetConnectionList()
|
||||||
|
{
|
||||||
|
if(GameServer::Started()) {
|
||||||
|
return Instance->_openConnections;
|
||||||
|
} else {
|
||||||
|
return list<shared_ptr<GameServerConnection>>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GameServer::Exec()
|
void GameServer::Exec()
|
||||||
{
|
{
|
||||||
_listener.reset(new Socket());
|
_listener.reset(new Socket());
|
||||||
|
@ -77,10 +90,9 @@ void GameServer::Stop()
|
||||||
MessageManager::DisplayMessage("Net Play", "Server stopped");
|
MessageManager::DisplayMessage("Net Play", "Server stopped");
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameServer::StartServer(uint16_t port)
|
void GameServer::StartServer(uint16_t port, string hostPlayerName)
|
||||||
{
|
{
|
||||||
Instance.reset(new GameServer());
|
Instance.reset(new GameServer(port, hostPlayerName));
|
||||||
Instance->_port = port;
|
|
||||||
Instance->_serverThread.reset(new thread(&GameServer::Exec, Instance.get()));
|
Instance->_serverThread.reset(new thread(&GameServer::Exec, Instance.get()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,4 +120,75 @@ void GameServer::BroadcastInput(uint8_t inputData, uint8_t port)
|
||||||
connection->SendMovieData(inputData, port);
|
connection->SendMovieData(inputData, port);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string GameServer::GetHostPlayerName()
|
||||||
|
{
|
||||||
|
if(GameServer::Started()) {
|
||||||
|
return Instance->_hostPlayerName;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t GameServer::GetHostControllerPort()
|
||||||
|
{
|
||||||
|
if(GameServer::Started()) {
|
||||||
|
return Instance->_hostControllerPort;
|
||||||
|
}
|
||||||
|
return GameConnection::SpectatorPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameServer::SetHostControllerPort(uint8_t port)
|
||||||
|
{
|
||||||
|
if(GameServer::Started()) {
|
||||||
|
Console::Pause();
|
||||||
|
if(port == GameConnection::SpectatorPort || GetAvailableControllers() & (1 << port)) {
|
||||||
|
//Port is available
|
||||||
|
Instance->_hostControllerPort = port;
|
||||||
|
SendPlayerList();
|
||||||
|
}
|
||||||
|
Console::Resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t GameServer::GetAvailableControllers()
|
||||||
|
{
|
||||||
|
uint8_t availablePorts = 0x0F;
|
||||||
|
for(PlayerInfo &playerInfo : GetPlayerList()) {
|
||||||
|
if(playerInfo.ControllerPort < 4) {
|
||||||
|
availablePorts &= ~(1 << playerInfo.ControllerPort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return availablePorts;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<PlayerInfo> GameServer::GetPlayerList()
|
||||||
|
{
|
||||||
|
vector<PlayerInfo> playerList;
|
||||||
|
|
||||||
|
PlayerInfo playerInfo;
|
||||||
|
playerInfo.Name = GetHostPlayerName();
|
||||||
|
playerInfo.ControllerPort = GetHostControllerPort();
|
||||||
|
playerInfo.IsHost = true;
|
||||||
|
playerList.push_back(playerInfo);
|
||||||
|
|
||||||
|
for(shared_ptr<GameServerConnection> &connection : GetConnectionList()) {
|
||||||
|
playerInfo.Name = connection->GetPlayerName();
|
||||||
|
playerInfo.ControllerPort = connection->GetControllerPort();
|
||||||
|
playerInfo.IsHost = false;
|
||||||
|
playerList.push_back(playerInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return playerList;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameServer::SendPlayerList()
|
||||||
|
{
|
||||||
|
vector<PlayerInfo> playerList = GetPlayerList();
|
||||||
|
|
||||||
|
for(shared_ptr<GameServerConnection> &connection : GetConnectionList()) {
|
||||||
|
//Send player list update to all connections
|
||||||
|
PlayerListMessage message(playerList);
|
||||||
|
connection->SendNetMessage(message);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -18,6 +18,9 @@ private:
|
||||||
list<shared_ptr<GameServerConnection>> _openConnections;
|
list<shared_ptr<GameServerConnection>> _openConnections;
|
||||||
bool _initialized = false;
|
bool _initialized = false;
|
||||||
|
|
||||||
|
string _hostPlayerName;
|
||||||
|
uint8_t _hostControllerPort;
|
||||||
|
|
||||||
void AcceptConnections();
|
void AcceptConnections();
|
||||||
void UpdateConnections();
|
void UpdateConnections();
|
||||||
|
|
||||||
|
@ -25,12 +28,21 @@ private:
|
||||||
void Stop();
|
void Stop();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GameServer();
|
GameServer(uint16_t port, string hostPlayerName);
|
||||||
~GameServer();
|
~GameServer();
|
||||||
|
|
||||||
static void StartServer(uint16_t port);
|
static void StartServer(uint16_t port, string hostPlayerName);
|
||||||
static void StopServer();
|
static void StopServer();
|
||||||
static bool Started();
|
static bool Started();
|
||||||
|
|
||||||
|
static string GetHostPlayerName();
|
||||||
|
static uint8_t GetHostControllerPort();
|
||||||
|
static void SetHostControllerPort(uint8_t port);
|
||||||
|
static uint8_t GetAvailableControllers();
|
||||||
|
static vector<PlayerInfo> GetPlayerList();
|
||||||
|
static void SendPlayerList();
|
||||||
|
|
||||||
|
static list<shared_ptr<GameServerConnection>> GetConnectionList();
|
||||||
|
|
||||||
virtual void BroadcastInput(uint8_t inputData, uint8_t port);
|
virtual void BroadcastInput(uint8_t inputData, uint8_t port);
|
||||||
};
|
};
|
|
@ -11,13 +11,16 @@
|
||||||
#include "ClientConnectionData.h"
|
#include "ClientConnectionData.h"
|
||||||
#include "EmulationSettings.h"
|
#include "EmulationSettings.h"
|
||||||
#include "StandardController.h"
|
#include "StandardController.h"
|
||||||
|
#include "SelectControllerMessage.h"
|
||||||
|
#include "PlayerListMessage.h"
|
||||||
|
#include "GameServer.h"
|
||||||
|
|
||||||
GameServerConnection* GameServerConnection::_netPlayDevices[4] = { nullptr,nullptr,nullptr, nullptr };
|
GameServerConnection* GameServerConnection::_netPlayDevices[4] = { nullptr,nullptr,nullptr,nullptr };
|
||||||
|
|
||||||
GameServerConnection::GameServerConnection(shared_ptr<Socket> socket, int controllerPort) : GameConnection(socket, nullptr)
|
GameServerConnection::GameServerConnection(shared_ptr<Socket> socket) : GameConnection(socket, nullptr)
|
||||||
{
|
{
|
||||||
//Server-side connection
|
//Server-side connection
|
||||||
_controllerPort = controllerPort;
|
_controllerPort = GameConnection::SpectatorPort;
|
||||||
MessageManager::RegisterNotificationListener(this);
|
MessageManager::RegisterNotificationListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,34 +75,75 @@ uint32_t GameServerConnection::GetState()
|
||||||
return stateData;
|
return stateData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameServerConnection::ProcessHandshakeResponse(HandShakeMessage* message)
|
||||||
|
{
|
||||||
|
//Send the game's current state to the client and register the controller
|
||||||
|
if(message->IsValid()) {
|
||||||
|
Console::Pause();
|
||||||
|
|
||||||
|
_controllerPort = message->IsSpectator() ? GameConnection::SpectatorPort : GetFirstFreeControllerPort();
|
||||||
|
_connectionData.reset(new ClientConnectionData("", 0, message->GetPlayerName(), message->GetAvatarData(), message->GetAvatarSize(), false));
|
||||||
|
|
||||||
|
string playerPortMessage = _controllerPort == GameConnection::SpectatorPort ? "Spectator" : "Player " + std::to_string(_controllerPort + 1);
|
||||||
|
|
||||||
|
MessageManager::DisplayToast("Net Play", _connectionData->PlayerName + " (" + playerPortMessage + ") connected.", _connectionData->AvatarData, _connectionData->AvatarSize);
|
||||||
|
|
||||||
|
if(Console::GetROMPath().size() > 0) {
|
||||||
|
SendGameInformation();
|
||||||
|
}
|
||||||
|
|
||||||
|
_handshakeCompleted = true;
|
||||||
|
RegisterNetPlayDevice(this, _controllerPort);
|
||||||
|
GameServer::SendPlayerList();
|
||||||
|
Console::Resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GameServerConnection::ProcessMessage(NetMessage* message)
|
void GameServerConnection::ProcessMessage(NetMessage* message)
|
||||||
{
|
{
|
||||||
switch(message->GetType()) {
|
switch(message->GetType()) {
|
||||||
case MessageType::HandShake:
|
case MessageType::HandShake:
|
||||||
//Send the game's current state to the client and register the controller
|
ProcessHandshakeResponse((HandShakeMessage*)message);
|
||||||
if(((HandShakeMessage*)message)->IsValid()) {
|
|
||||||
Console::Pause();
|
|
||||||
_connectionData.reset(new ClientConnectionData("", 0, ((HandShakeMessage*)message)->GetPlayerName(), ((HandShakeMessage*)message)->GetAvatarData(), ((HandShakeMessage*)message)->GetAvatarSize()));
|
|
||||||
|
|
||||||
MessageManager::DisplayToast("Net Play", _connectionData->PlayerName + " (Player " + std::to_string(_controllerPort + 1) + ") connected.", _connectionData->AvatarData, _connectionData->AvatarSize);
|
|
||||||
|
|
||||||
if(Console::GetROMPath().size() > 0) {
|
|
||||||
SendGameInformation();
|
|
||||||
}
|
|
||||||
|
|
||||||
_handshakeCompleted = true;
|
|
||||||
RegisterNetPlayDevice(this, _controllerPort);
|
|
||||||
Console::Resume();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MessageType::InputData:
|
case MessageType::InputData:
|
||||||
PushState(((InputDataMessage*)message)->GetInputState());
|
PushState(((InputDataMessage*)message)->GetInputState());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case MessageType::SelectController:
|
||||||
|
SelectControllerPort(((SelectControllerMessage*)message)->GetPortNumber());
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameServerConnection::SelectControllerPort(uint8_t port)
|
||||||
|
{
|
||||||
|
Console::Pause();
|
||||||
|
if(port == GameConnection::SpectatorPort) {
|
||||||
|
//Client wants to be a spectator, make sure we are not using any controller
|
||||||
|
UnregisterNetPlayDevice(this);
|
||||||
|
_controllerPort = port;
|
||||||
|
} else {
|
||||||
|
GameServerConnection* netPlayDevice = GetNetPlayDevice(port);
|
||||||
|
if(netPlayDevice == this) {
|
||||||
|
//Nothing to do, we're already this player
|
||||||
|
} else if(netPlayDevice == nullptr) {
|
||||||
|
//This port is free, we can switch
|
||||||
|
UnregisterNetPlayDevice(this);
|
||||||
|
RegisterNetPlayDevice(this, port);
|
||||||
|
_controllerPort = port;
|
||||||
|
} else {
|
||||||
|
//Another player is using this port, we can't use it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SendGameInformation();
|
||||||
|
GameServer::SendPlayerList();
|
||||||
|
Console::Resume();
|
||||||
|
}
|
||||||
|
|
||||||
void GameServerConnection::ProcessNotification(ConsoleNotificationType type, void* parameter)
|
void GameServerConnection::ProcessNotification(ConsoleNotificationType type, void* parameter)
|
||||||
{
|
{
|
||||||
switch(type) {
|
switch(type) {
|
||||||
|
@ -138,4 +182,29 @@ void GameServerConnection::UnregisterNetPlayDevice(GameServerConnection* device)
|
||||||
GameServerConnection* GameServerConnection::GetNetPlayDevice(uint8_t port)
|
GameServerConnection* GameServerConnection::GetNetPlayDevice(uint8_t port)
|
||||||
{
|
{
|
||||||
return GameServerConnection::_netPlayDevices[port];
|
return GameServerConnection::_netPlayDevices[port];
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t GameServerConnection::GetFirstFreeControllerPort()
|
||||||
|
{
|
||||||
|
uint8_t hostPost = GameServer::GetHostControllerPort();
|
||||||
|
for(int i = 0; i < 4; i++) {
|
||||||
|
if(hostPost != i && GameServerConnection::_netPlayDevices[i] == nullptr) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return GameConnection::SpectatorPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
string GameServerConnection::GetPlayerName()
|
||||||
|
{
|
||||||
|
if(_connectionData) {
|
||||||
|
return _connectionData->PlayerName;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t GameServerConnection::GetControllerPort()
|
||||||
|
{
|
||||||
|
return _controllerPort;
|
||||||
}
|
}
|
|
@ -6,29 +6,39 @@
|
||||||
#include "IGameBroadcaster.h"
|
#include "IGameBroadcaster.h"
|
||||||
#include "INotificationListener.h"
|
#include "INotificationListener.h"
|
||||||
|
|
||||||
|
class HandShakeMessage;
|
||||||
|
|
||||||
class GameServerConnection : public GameConnection, public INotificationListener
|
class GameServerConnection : public GameConnection, public INotificationListener
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
static GameServerConnection* _netPlayDevices[4];
|
static GameServerConnection* _netPlayDevices[4];
|
||||||
|
|
||||||
list<uint32_t> _inputData;
|
list<uint32_t> _inputData;
|
||||||
int _controllerPort;
|
int _controllerPort;
|
||||||
bool _handshakeCompleted = false;
|
bool _handshakeCompleted = false;
|
||||||
void PushState(uint32_t state);
|
void PushState(uint32_t state);
|
||||||
void SendGameInformation();
|
void SendGameInformation();
|
||||||
|
void SelectControllerPort(uint8_t port);
|
||||||
|
|
||||||
|
void ProcessHandshakeResponse(HandShakeMessage* message);
|
||||||
|
|
||||||
static void RegisterNetPlayDevice(GameServerConnection* connection, uint8_t port);
|
static void RegisterNetPlayDevice(GameServerConnection* connection, uint8_t port);
|
||||||
static void UnregisterNetPlayDevice(GameServerConnection* device);
|
static void UnregisterNetPlayDevice(GameServerConnection* device);
|
||||||
|
static uint8_t GetFirstFreeControllerPort();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void ProcessMessage(NetMessage* message);
|
void ProcessMessage(NetMessage* message);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GameServerConnection(shared_ptr<Socket> socket, int controllerPort);
|
GameServerConnection(shared_ptr<Socket> socket);
|
||||||
~GameServerConnection();
|
~GameServerConnection();
|
||||||
|
|
||||||
uint32_t GetState();
|
uint32_t GetState();
|
||||||
void SendMovieData(uint8_t state, uint8_t port);
|
void SendMovieData(uint8_t state, uint8_t port);
|
||||||
|
|
||||||
|
string GetPlayerName();
|
||||||
|
uint8_t GetControllerPort();
|
||||||
|
|
||||||
virtual void ProcessNotification(ConsoleNotificationType type, void* parameter);
|
virtual void ProcessNotification(ConsoleNotificationType type, void* parameter);
|
||||||
|
|
||||||
static GameServerConnection* GetNetPlayDevice(uint8_t port);
|
static GameServerConnection* GetNetPlayDevice(uint8_t port);
|
||||||
|
|
|
@ -11,6 +11,7 @@ private:
|
||||||
uint32_t _playerNameLength = 0;
|
uint32_t _playerNameLength = 0;
|
||||||
void* _avatarData = nullptr;
|
void* _avatarData = nullptr;
|
||||||
uint32_t _avatarSize = 0;
|
uint32_t _avatarSize = 0;
|
||||||
|
bool _spectator = false;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void ProtectedStreamState()
|
virtual void ProtectedStreamState()
|
||||||
|
@ -18,16 +19,18 @@ protected:
|
||||||
Stream<uint32_t>(_protocolVersion);
|
Stream<uint32_t>(_protocolVersion);
|
||||||
StreamArray((void**)&_playerName, _playerNameLength);
|
StreamArray((void**)&_playerName, _playerNameLength);
|
||||||
StreamArray(&_avatarData, _avatarSize);
|
StreamArray(&_avatarData, _avatarSize);
|
||||||
|
Stream<bool>(_spectator);
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
HandShakeMessage(void* buffer, uint32_t length) : NetMessage(buffer, length) { }
|
HandShakeMessage(void* buffer, uint32_t length) : NetMessage(buffer, length) { }
|
||||||
HandShakeMessage(string playerName, uint8_t* avatarData, uint32_t avatarSize) : NetMessage(MessageType::HandShake)
|
HandShakeMessage(string playerName, uint8_t* avatarData, uint32_t avatarSize, bool spectator) : NetMessage(MessageType::HandShake)
|
||||||
{
|
{
|
||||||
_protocolVersion = 1;
|
_protocolVersion = 1;
|
||||||
CopyString(&_playerName, _playerNameLength, playerName);
|
CopyString(&_playerName, _playerNameLength, playerName);
|
||||||
_avatarSize = avatarSize;
|
_avatarSize = avatarSize;
|
||||||
_avatarData = avatarData;
|
_avatarData = avatarData;
|
||||||
|
_spectator = spectator;
|
||||||
}
|
}
|
||||||
|
|
||||||
string GetPlayerName()
|
string GetPlayerName()
|
||||||
|
@ -49,4 +52,9 @@ public:
|
||||||
{
|
{
|
||||||
return _protocolVersion == CurrentVersion;
|
return _protocolVersion == CurrentVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsSpectator()
|
||||||
|
{
|
||||||
|
return _spectator;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,4 +8,6 @@ enum class MessageType : uint8_t
|
||||||
InputData = 2,
|
InputData = 2,
|
||||||
MovieData = 3,
|
MovieData = 3,
|
||||||
GameInformation = 4,
|
GameInformation = 4,
|
||||||
|
PlayerList = 5,
|
||||||
|
SelectController = 6,
|
||||||
};
|
};
|
|
@ -28,6 +28,13 @@ protected:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void StreamArray(void* value, uint32_t length)
|
||||||
|
{
|
||||||
|
void* pointer = value;
|
||||||
|
uint32_t len = length;
|
||||||
|
StreamArray(&pointer, len);
|
||||||
|
}
|
||||||
|
|
||||||
void StreamArray(void** value, uint32_t &length)
|
void StreamArray(void** value, uint32_t &length)
|
||||||
{
|
{
|
||||||
if(_sending) {
|
if(_sending) {
|
||||||
|
|
63
Core/PlayerListMessage.h
Normal file
63
Core/PlayerListMessage.h
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
#pragma once
|
||||||
|
#include "stdafx.h"
|
||||||
|
#include "NetMessage.h"
|
||||||
|
|
||||||
|
class PlayerListMessage : public NetMessage
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
static const uint32_t PlayerNameMaxLength = 50;
|
||||||
|
vector<PlayerInfo> _playerList;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void ProtectedStreamState()
|
||||||
|
{
|
||||||
|
uint32_t nameLength = PlayerNameMaxLength + 1;
|
||||||
|
char playerName[PlayerNameMaxLength + 1];
|
||||||
|
uint8_t playerPort;
|
||||||
|
bool isHost;
|
||||||
|
|
||||||
|
if(_sending) {
|
||||||
|
uint32_t playerCount = (uint32_t)_playerList.size();
|
||||||
|
Stream<uint32_t>(playerCount);
|
||||||
|
for(uint32_t i = 0; i < playerCount; i++) {
|
||||||
|
memset(playerName, 0, nameLength);
|
||||||
|
memcpy(playerName, _playerList[i].Name.c_str(), std::min((uint32_t)_playerList[i].Name.size(), PlayerNameMaxLength));
|
||||||
|
playerPort = _playerList[i].ControllerPort;
|
||||||
|
|
||||||
|
StreamArray(playerName, nameLength);
|
||||||
|
Stream<uint8_t>(playerPort);
|
||||||
|
Stream<bool>(isHost);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
uint32_t playerCount;
|
||||||
|
Stream<uint32_t>(playerCount);
|
||||||
|
|
||||||
|
for(uint32_t i = 0; i < playerCount; i++) {
|
||||||
|
memset(playerName, 0, nameLength);
|
||||||
|
StreamArray(playerName, nameLength);
|
||||||
|
Stream<uint8_t>(playerPort);
|
||||||
|
Stream<bool>(isHost);
|
||||||
|
|
||||||
|
PlayerInfo playerInfo;
|
||||||
|
playerInfo.Name = playerName;
|
||||||
|
playerInfo.ControllerPort = playerPort;
|
||||||
|
playerInfo.IsHost = isHost;
|
||||||
|
|
||||||
|
_playerList.push_back(playerInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
PlayerListMessage(void* buffer, uint32_t length) : NetMessage(buffer, length) { }
|
||||||
|
|
||||||
|
PlayerListMessage(vector<PlayerInfo> playerList) : NetMessage(MessageType::PlayerList)
|
||||||
|
{
|
||||||
|
_playerList = playerList;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<PlayerInfo> GetPlayerList()
|
||||||
|
{
|
||||||
|
return _playerList;
|
||||||
|
}
|
||||||
|
};
|
28
Core/SelectControllerMessage.h
Normal file
28
Core/SelectControllerMessage.h
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
#pragma once
|
||||||
|
#include "stdafx.h"
|
||||||
|
#include "NetMessage.h"
|
||||||
|
|
||||||
|
class SelectControllerMessage : public NetMessage
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
uint8_t _portNumber;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void ProtectedStreamState()
|
||||||
|
{
|
||||||
|
Stream<uint8_t>(_portNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
SelectControllerMessage(void* buffer, uint32_t length) : NetMessage(buffer, length) { }
|
||||||
|
|
||||||
|
SelectControllerMessage(uint8_t port) : NetMessage(MessageType::SelectController)
|
||||||
|
{
|
||||||
|
_portNumber = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t GetPortNumber()
|
||||||
|
{
|
||||||
|
return _portNumber;
|
||||||
|
}
|
||||||
|
};
|
|
@ -10,5 +10,6 @@ namespace Mesen.GUI.Config
|
||||||
{
|
{
|
||||||
public string Host = "localhost";
|
public string Host = "localhost";
|
||||||
public UInt16 Port = 8888;
|
public UInt16 Port = 8888;
|
||||||
|
public bool Spectator = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,9 +135,10 @@ namespace Mesen.GUI.Config
|
||||||
|
|
||||||
InteropEmu.SetConsoleType(inputInfo.ConsoleType);
|
InteropEmu.SetConsoleType(inputInfo.ConsoleType);
|
||||||
InteropEmu.SetExpansionDevice(inputInfo.ExpansionPortDevice);
|
InteropEmu.SetExpansionDevice(inputInfo.ExpansionPortDevice);
|
||||||
InteropEmu.SetFlag(EmulationFlags.HasFourScore, (inputInfo.ConsoleType == ConsoleType.Nes && inputInfo.UseFourScore) || (inputInfo.ConsoleType == ConsoleType.Famicom && expansionDevice == InteropEmu.ExpansionPortDevice.FourPlayerAdapter));
|
bool hasFourScore = (inputInfo.ConsoleType == ConsoleType.Nes && inputInfo.UseFourScore) || (inputInfo.ConsoleType == ConsoleType.Famicom && expansionDevice == InteropEmu.ExpansionPortDevice.FourPlayerAdapter);
|
||||||
|
InteropEmu.SetFlag(EmulationFlags.HasFourScore, hasFourScore);
|
||||||
for(int i = 0; i < 4; i++) {
|
for(int i = 0; i < 4; i++) {
|
||||||
InteropEmu.SetControllerType(i, inputInfo.Controllers[i].ControllerType);
|
InteropEmu.SetControllerType(i, i < 2 || hasFourScore ? inputInfo.Controllers[i].ControllerType : InteropEmu.ControllerType.None);
|
||||||
InteropEmu.SetControllerKeys(i, inputInfo.Controllers[i].GetKeyMappingSet());
|
InteropEmu.SetControllerKeys(i, inputInfo.Controllers[i].GetKeyMappingSet());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
GUI.NET/Forms/NetPlay/frmClientConfig.Designer.cs
generated
10
GUI.NET/Forms/NetPlay/frmClientConfig.Designer.cs
generated
|
@ -36,6 +36,11 @@
|
||||||
this.tableLayoutPanel1.SuspendLayout();
|
this.tableLayoutPanel1.SuspendLayout();
|
||||||
this.SuspendLayout();
|
this.SuspendLayout();
|
||||||
//
|
//
|
||||||
|
// baseConfigPanel
|
||||||
|
//
|
||||||
|
this.baseConfigPanel.Location = new System.Drawing.Point(0, 111);
|
||||||
|
this.baseConfigPanel.Size = new System.Drawing.Size(290, 29);
|
||||||
|
//
|
||||||
// tableLayoutPanel1
|
// tableLayoutPanel1
|
||||||
//
|
//
|
||||||
this.tableLayoutPanel1.ColumnCount = 2;
|
this.tableLayoutPanel1.ColumnCount = 2;
|
||||||
|
@ -54,7 +59,7 @@
|
||||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
|
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
|
||||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
|
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
|
||||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
|
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
|
||||||
this.tableLayoutPanel1.Size = new System.Drawing.Size(290, 110);
|
this.tableLayoutPanel1.Size = new System.Drawing.Size(290, 111);
|
||||||
this.tableLayoutPanel1.TabIndex = 0;
|
this.tableLayoutPanel1.TabIndex = 0;
|
||||||
//
|
//
|
||||||
// txtPort
|
// txtPort
|
||||||
|
@ -99,7 +104,6 @@
|
||||||
//
|
//
|
||||||
this.chkSpectator.AutoSize = true;
|
this.chkSpectator.AutoSize = true;
|
||||||
this.tableLayoutPanel1.SetColumnSpan(this.chkSpectator, 2);
|
this.tableLayoutPanel1.SetColumnSpan(this.chkSpectator, 2);
|
||||||
this.chkSpectator.Enabled = false;
|
|
||||||
this.chkSpectator.Location = new System.Drawing.Point(3, 55);
|
this.chkSpectator.Location = new System.Drawing.Point(3, 55);
|
||||||
this.chkSpectator.Name = "chkSpectator";
|
this.chkSpectator.Name = "chkSpectator";
|
||||||
this.chkSpectator.Size = new System.Drawing.Size(106, 17);
|
this.chkSpectator.Size = new System.Drawing.Size(106, 17);
|
||||||
|
@ -119,9 +123,9 @@
|
||||||
this.MinimizeBox = false;
|
this.MinimizeBox = false;
|
||||||
this.MinimumSize = new System.Drawing.Size(306, 178);
|
this.MinimumSize = new System.Drawing.Size(306, 178);
|
||||||
this.Name = "frmClientConfig";
|
this.Name = "frmClientConfig";
|
||||||
this.ShowInTaskbar = false;
|
|
||||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||||
this.Text = "Connect...";
|
this.Text = "Connect...";
|
||||||
|
this.Controls.SetChildIndex(this.baseConfigPanel, 0);
|
||||||
this.Controls.SetChildIndex(this.tableLayoutPanel1, 0);
|
this.Controls.SetChildIndex(this.tableLayoutPanel1, 0);
|
||||||
this.tableLayoutPanel1.ResumeLayout(false);
|
this.tableLayoutPanel1.ResumeLayout(false);
|
||||||
this.tableLayoutPanel1.PerformLayout();
|
this.tableLayoutPanel1.PerformLayout();
|
||||||
|
|
|
@ -18,13 +18,16 @@ namespace Mesen.GUI.Forms.NetPlay
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
this.txtHost.Text = ConfigManager.Config.ClientConnectionInfo.Host;
|
Entity = ConfigManager.Config.ClientConnectionInfo;
|
||||||
|
|
||||||
|
AddBinding("Host", this.txtHost);
|
||||||
|
AddBinding("Spectator", chkSpectator);
|
||||||
this.txtPort.Text = ConfigManager.Config.ClientConnectionInfo.Port.ToString();
|
this.txtPort.Text = ConfigManager.Config.ClientConnectionInfo.Port.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateConfig()
|
protected override void UpdateConfig()
|
||||||
{
|
{
|
||||||
ConfigManager.Config.ClientConnectionInfo = new ClientConnectionInfo() { Host = this.txtHost.Text, Port = Convert.ToUInt16(this.txtPort.Text) };
|
((ClientConnectionInfo)Entity).Port = Convert.ToUInt16(this.txtPort.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Field_TextChanged(object sender, EventArgs e)
|
private void Field_TextChanged(object sender, EventArgs e)
|
||||||
|
|
|
@ -117,4 +117,7 @@
|
||||||
<resheader name="writer">
|
<resheader name="writer">
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
|
<metadata name="toolTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||||
|
<value>17, 17</value>
|
||||||
|
</metadata>
|
||||||
</root>
|
</root>
|
68
GUI.NET/Forms/frmMain.Designer.cs
generated
68
GUI.NET/Forms/frmMain.Designer.cs
generated
|
@ -91,6 +91,13 @@
|
||||||
this.mnuNetPlay = new System.Windows.Forms.ToolStripMenuItem();
|
this.mnuNetPlay = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
this.mnuStartServer = new System.Windows.Forms.ToolStripMenuItem();
|
this.mnuStartServer = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
this.mnuConnect = new System.Windows.Forms.ToolStripMenuItem();
|
this.mnuConnect = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
|
this.mnuNetPlaySelectController = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
|
this.mnuNetPlayPlayer1 = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
|
this.mnuNetPlayPlayer2 = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
|
this.mnuNetPlayPlayer3 = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
|
this.mnuNetPlayPlayer4 = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
|
this.toolStripMenuItem3 = new System.Windows.Forms.ToolStripSeparator();
|
||||||
|
this.mnuNetPlaySpectator = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
this.toolStripMenuItem2 = new System.Windows.Forms.ToolStripSeparator();
|
this.toolStripMenuItem2 = new System.Windows.Forms.ToolStripSeparator();
|
||||||
this.mnuFindServer = new System.Windows.Forms.ToolStripMenuItem();
|
this.mnuFindServer = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
this.mnuProfile = new System.Windows.Forms.ToolStripMenuItem();
|
this.mnuProfile = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
|
@ -556,6 +563,7 @@
|
||||||
this.mnuNetPlay.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
this.mnuNetPlay.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||||
this.mnuStartServer,
|
this.mnuStartServer,
|
||||||
this.mnuConnect,
|
this.mnuConnect,
|
||||||
|
this.mnuNetPlaySelectController,
|
||||||
this.toolStripMenuItem2,
|
this.toolStripMenuItem2,
|
||||||
this.mnuFindServer,
|
this.mnuFindServer,
|
||||||
this.mnuProfile});
|
this.mnuProfile});
|
||||||
|
@ -578,6 +586,59 @@
|
||||||
this.mnuConnect.Text = "Connect to Server";
|
this.mnuConnect.Text = "Connect to Server";
|
||||||
this.mnuConnect.Click += new System.EventHandler(this.mnuConnect_Click);
|
this.mnuConnect.Click += new System.EventHandler(this.mnuConnect_Click);
|
||||||
//
|
//
|
||||||
|
// mnuNetPlaySelectController
|
||||||
|
//
|
||||||
|
this.mnuNetPlaySelectController.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||||
|
this.mnuNetPlayPlayer1,
|
||||||
|
this.mnuNetPlayPlayer2,
|
||||||
|
this.mnuNetPlayPlayer3,
|
||||||
|
this.mnuNetPlayPlayer4,
|
||||||
|
this.toolStripMenuItem3,
|
||||||
|
this.mnuNetPlaySpectator});
|
||||||
|
this.mnuNetPlaySelectController.Name = "mnuNetPlaySelectController";
|
||||||
|
this.mnuNetPlaySelectController.Size = new System.Drawing.Size(177, 22);
|
||||||
|
this.mnuNetPlaySelectController.Text = "Select Controller";
|
||||||
|
//
|
||||||
|
// mnuNetPlayPlayer1
|
||||||
|
//
|
||||||
|
this.mnuNetPlayPlayer1.Name = "mnuNetPlayPlayer1";
|
||||||
|
this.mnuNetPlayPlayer1.Size = new System.Drawing.Size(152, 22);
|
||||||
|
this.mnuNetPlayPlayer1.Text = "Player 1";
|
||||||
|
this.mnuNetPlayPlayer1.Click += new System.EventHandler(this.mnuNetPlayPlayer1_Click);
|
||||||
|
//
|
||||||
|
// mnuNetPlayPlayer2
|
||||||
|
//
|
||||||
|
this.mnuNetPlayPlayer2.Name = "mnuNetPlayPlayer2";
|
||||||
|
this.mnuNetPlayPlayer2.Size = new System.Drawing.Size(152, 22);
|
||||||
|
this.mnuNetPlayPlayer2.Text = "Player 2";
|
||||||
|
this.mnuNetPlayPlayer2.Click += new System.EventHandler(this.mnuNetPlayPlayer2_Click);
|
||||||
|
//
|
||||||
|
// mnuNetPlayPlayer3
|
||||||
|
//
|
||||||
|
this.mnuNetPlayPlayer3.Name = "mnuNetPlayPlayer3";
|
||||||
|
this.mnuNetPlayPlayer3.Size = new System.Drawing.Size(152, 22);
|
||||||
|
this.mnuNetPlayPlayer3.Text = "Player 3";
|
||||||
|
this.mnuNetPlayPlayer3.Click += new System.EventHandler(this.mnuNetPlayPlayer3_Click);
|
||||||
|
//
|
||||||
|
// mnuNetPlayPlayer4
|
||||||
|
//
|
||||||
|
this.mnuNetPlayPlayer4.Name = "mnuNetPlayPlayer4";
|
||||||
|
this.mnuNetPlayPlayer4.Size = new System.Drawing.Size(152, 22);
|
||||||
|
this.mnuNetPlayPlayer4.Text = "Player 4";
|
||||||
|
this.mnuNetPlayPlayer4.Click += new System.EventHandler(this.mnuNetPlayPlayer4_Click);
|
||||||
|
//
|
||||||
|
// toolStripMenuItem3
|
||||||
|
//
|
||||||
|
this.toolStripMenuItem3.Name = "toolStripMenuItem3";
|
||||||
|
this.toolStripMenuItem3.Size = new System.Drawing.Size(149, 6);
|
||||||
|
//
|
||||||
|
// mnuNetPlaySpectator
|
||||||
|
//
|
||||||
|
this.mnuNetPlaySpectator.Name = "mnuNetPlaySpectator";
|
||||||
|
this.mnuNetPlaySpectator.Size = new System.Drawing.Size(152, 22);
|
||||||
|
this.mnuNetPlaySpectator.Text = "Spectator";
|
||||||
|
this.mnuNetPlaySpectator.Click += new System.EventHandler(this.mnuNetPlaySpectator_Click);
|
||||||
|
//
|
||||||
// toolStripMenuItem2
|
// toolStripMenuItem2
|
||||||
//
|
//
|
||||||
this.toolStripMenuItem2.Name = "toolStripMenuItem2";
|
this.toolStripMenuItem2.Name = "toolStripMenuItem2";
|
||||||
|
@ -904,6 +965,13 @@
|
||||||
private System.Windows.Forms.ToolStripMenuItem mnuRegionDendy;
|
private System.Windows.Forms.ToolStripMenuItem mnuRegionDendy;
|
||||||
private System.Windows.Forms.ToolStripSeparator toolStripMenuItem12;
|
private System.Windows.Forms.ToolStripSeparator toolStripMenuItem12;
|
||||||
private System.Windows.Forms.ToolStripSeparator toolStripMenuItem1;
|
private System.Windows.Forms.ToolStripSeparator toolStripMenuItem1;
|
||||||
|
private System.Windows.Forms.ToolStripMenuItem mnuNetPlaySelectController;
|
||||||
|
private System.Windows.Forms.ToolStripMenuItem mnuNetPlayPlayer1;
|
||||||
|
private System.Windows.Forms.ToolStripMenuItem mnuNetPlayPlayer2;
|
||||||
|
private System.Windows.Forms.ToolStripMenuItem mnuNetPlayPlayer3;
|
||||||
|
private System.Windows.Forms.ToolStripMenuItem mnuNetPlayPlayer4;
|
||||||
|
private System.Windows.Forms.ToolStripSeparator toolStripMenuItem3;
|
||||||
|
private System.Windows.Forms.ToolStripMenuItem mnuNetPlaySpectator;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -334,6 +334,27 @@ namespace Mesen.GUI.Forms
|
||||||
|
|
||||||
mnuStartServer.Enabled = !isNetPlayClient;
|
mnuStartServer.Enabled = !isNetPlayClient;
|
||||||
mnuConnect.Enabled = !InteropEmu.IsServerRunning();
|
mnuConnect.Enabled = !InteropEmu.IsServerRunning();
|
||||||
|
mnuNetPlaySelectController.Enabled = isNetPlayClient || InteropEmu.IsServerRunning();
|
||||||
|
if(mnuNetPlaySelectController.Enabled) {
|
||||||
|
int availableControllers = InteropEmu.NetPlayGetAvailableControllers();
|
||||||
|
int currentControllerPort = InteropEmu.NetPlayGetControllerPort();
|
||||||
|
mnuNetPlayPlayer1.Enabled = (availableControllers & 0x01) == 0x01;
|
||||||
|
mnuNetPlayPlayer2.Enabled = (availableControllers & 0x02) == 0x02;
|
||||||
|
mnuNetPlayPlayer3.Enabled = (availableControllers & 0x04) == 0x04;
|
||||||
|
mnuNetPlayPlayer4.Enabled = (availableControllers & 0x08) == 0x08;
|
||||||
|
mnuNetPlayPlayer1.Text = "Player 1 (" + InteropEmu.NetPlayGetControllerType(0).ToString() + ")";
|
||||||
|
mnuNetPlayPlayer2.Text = "Player 2 (" + InteropEmu.NetPlayGetControllerType(1).ToString() + ")";
|
||||||
|
mnuNetPlayPlayer3.Text = "Player 3 (" + InteropEmu.NetPlayGetControllerType(2).ToString() + ")";
|
||||||
|
mnuNetPlayPlayer4.Text = "Player 4 (" + InteropEmu.NetPlayGetControllerType(3).ToString() + ")";
|
||||||
|
|
||||||
|
mnuNetPlayPlayer1.Checked = (currentControllerPort == 0);
|
||||||
|
mnuNetPlayPlayer2.Checked = (currentControllerPort == 1);
|
||||||
|
mnuNetPlayPlayer3.Checked = (currentControllerPort == 2);
|
||||||
|
mnuNetPlayPlayer4.Checked = (currentControllerPort == 3);
|
||||||
|
mnuNetPlaySpectator.Checked = (currentControllerPort == 0xFF);
|
||||||
|
|
||||||
|
mnuNetPlaySpectator.Enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
mnuStartServer.Text = InteropEmu.IsServerRunning() ? "Stop Server" : "Start Server";
|
mnuStartServer.Text = InteropEmu.IsServerRunning() ? "Stop Server" : "Start Server";
|
||||||
mnuConnect.Text = isNetPlayClient ? "Disconnect" : "Connect to Server";
|
mnuConnect.Text = isNetPlayClient ? "Disconnect" : "Connect to Server";
|
||||||
|
@ -510,7 +531,7 @@ namespace Mesen.GUI.Forms
|
||||||
} else {
|
} else {
|
||||||
frmServerConfig frm = new frmServerConfig();
|
frmServerConfig frm = new frmServerConfig();
|
||||||
if(frm.ShowDialog(sender) == System.Windows.Forms.DialogResult.OK) {
|
if(frm.ShowDialog(sender) == System.Windows.Forms.DialogResult.OK) {
|
||||||
InteropEmu.StartServer(ConfigManager.Config.ServerInfo.Port);
|
InteropEmu.StartServer(ConfigManager.Config.ServerInfo.Port, ConfigManager.Config.Profile.PlayerName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -522,7 +543,7 @@ namespace Mesen.GUI.Forms
|
||||||
} else {
|
} else {
|
||||||
frmClientConfig frm = new frmClientConfig();
|
frmClientConfig frm = new frmClientConfig();
|
||||||
if(frm.ShowDialog(sender) == System.Windows.Forms.DialogResult.OK) {
|
if(frm.ShowDialog(sender) == System.Windows.Forms.DialogResult.OK) {
|
||||||
InteropEmu.Connect(ConfigManager.Config.ClientConnectionInfo.Host, ConfigManager.Config.ClientConnectionInfo.Port, ConfigManager.Config.Profile.PlayerName, ConfigManager.Config.Profile.PlayerAvatar, (UInt16)ConfigManager.Config.Profile.PlayerAvatar.Length);
|
InteropEmu.Connect(ConfigManager.Config.ClientConnectionInfo.Host, ConfigManager.Config.ClientConnectionInfo.Port, ConfigManager.Config.Profile.PlayerName, ConfigManager.Config.Profile.PlayerAvatar, (UInt16)ConfigManager.Config.Profile.PlayerAvatar.Length, ConfigManager.Config.ClientConnectionInfo.Spectator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -876,5 +897,30 @@ namespace Mesen.GUI.Forms
|
||||||
e.Effect = DragDropEffects.Copy;
|
e.Effect = DragDropEffects.Copy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void mnuNetPlayPlayer1_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
InteropEmu.NetPlaySelectController(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mnuNetPlayPlayer2_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
InteropEmu.NetPlaySelectController(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mnuNetPlayPlayer3_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
InteropEmu.NetPlaySelectController(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mnuNetPlayPlayer4_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
InteropEmu.NetPlaySelectController(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mnuNetPlaySpectator_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
InteropEmu.NetPlaySelectController(0xFF);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,13 +43,18 @@ namespace Mesen.GUI
|
||||||
[DllImport(DLLPath)] public static extern void Stop();
|
[DllImport(DLLPath)] public static extern void Stop();
|
||||||
[DllImport(DLLPath, EntryPoint="GetROMPath")] private static extern IntPtr GetROMPathWrapper();
|
[DllImport(DLLPath, EntryPoint="GetROMPath")] private static extern IntPtr GetROMPathWrapper();
|
||||||
[DllImport(DLLPath)] public static extern void Reset();
|
[DllImport(DLLPath)] public static extern void Reset();
|
||||||
[DllImport(DLLPath)] public static extern void StartServer(UInt16 port);
|
[DllImport(DLLPath)] public static extern void StartServer(UInt16 port, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]string hostPlayerName);
|
||||||
[DllImport(DLLPath)] public static extern void StopServer();
|
[DllImport(DLLPath)] public static extern void StopServer();
|
||||||
[DllImport(DLLPath)] [return: MarshalAs(UnmanagedType.I1)] public static extern bool IsServerRunning();
|
[DllImport(DLLPath)] [return: MarshalAs(UnmanagedType.I1)] public static extern bool IsServerRunning();
|
||||||
[DllImport(DLLPath)] public static extern void Connect([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(UTF8Marshaler))]string host, UInt16 port, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(UTF8Marshaler))]string playerName, byte[] avatarData, UInt32 avatarSize);
|
[DllImport(DLLPath)] public static extern void Connect([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(UTF8Marshaler))]string host, UInt16 port, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(UTF8Marshaler))]string playerName, byte[] avatarData, UInt32 avatarSize, [MarshalAs(UnmanagedType.I1)]bool spectator);
|
||||||
[DllImport(DLLPath)] public static extern void Disconnect();
|
[DllImport(DLLPath)] public static extern void Disconnect();
|
||||||
[DllImport(DLLPath)] [return: MarshalAs(UnmanagedType.I1)] public static extern bool IsConnected();
|
[DllImport(DLLPath)] [return: MarshalAs(UnmanagedType.I1)] public static extern bool IsConnected();
|
||||||
|
|
||||||
|
[DllImport(DLLPath)] public static extern Int32 NetPlayGetAvailableControllers();
|
||||||
|
[DllImport(DLLPath)] public static extern void NetPlaySelectController(Int32 controllerPort);
|
||||||
|
[DllImport(DLLPath)] public static extern ControllerType NetPlayGetControllerType(Int32 controllerPort);
|
||||||
|
[DllImport(DLLPath)] public static extern Int32 NetPlayGetControllerPort();
|
||||||
|
|
||||||
[DllImport(DLLPath)] public static extern void TakeScreenshot();
|
[DllImport(DLLPath)] public static extern void TakeScreenshot();
|
||||||
|
|
||||||
[DllImport(DLLPath)] public static extern IntPtr RegisterNotificationCallback(NotificationListener.NotificationCallback callback);
|
[DllImport(DLLPath)] public static extern IntPtr RegisterNotificationCallback(NotificationListener.NotificationCallback callback);
|
||||||
|
|
|
@ -111,25 +111,54 @@ namespace InteropEmu {
|
||||||
|
|
||||||
DllExport void __stdcall Reset() { Console::Reset(); }
|
DllExport void __stdcall Reset() { Console::Reset(); }
|
||||||
|
|
||||||
DllExport void __stdcall StartServer(uint16_t port) { GameServer::StartServer(port); }
|
DllExport void __stdcall StartServer(uint16_t port, char* hostPlayerName) { GameServer::StartServer(port, hostPlayerName); }
|
||||||
DllExport void __stdcall StopServer() { GameServer::StopServer(); }
|
DllExport void __stdcall StopServer() { GameServer::StopServer(); }
|
||||||
DllExport bool __stdcall IsServerRunning() { return GameServer::Started(); }
|
DllExport bool __stdcall IsServerRunning() { return GameServer::Started(); }
|
||||||
|
|
||||||
DllExport void __stdcall Connect(char* host, uint16_t port, char* playerName, uint8_t* avatarData, uint32_t avatarSize)
|
DllExport void __stdcall Connect(char* host, uint16_t port, char* playerName, uint8_t* avatarData, uint32_t avatarSize, bool spectator)
|
||||||
{
|
{
|
||||||
shared_ptr<ClientConnectionData> connectionData(new ClientConnectionData(
|
shared_ptr<ClientConnectionData> connectionData(new ClientConnectionData(
|
||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
playerName,
|
playerName,
|
||||||
avatarData,
|
avatarData,
|
||||||
avatarSize
|
avatarSize,
|
||||||
));
|
spectator
|
||||||
|
));
|
||||||
|
|
||||||
GameClient::Connect(connectionData);
|
GameClient::Connect(connectionData);
|
||||||
}
|
}
|
||||||
|
|
||||||
DllExport void __stdcall Disconnect() { GameClient::Disconnect(); }
|
DllExport void __stdcall Disconnect() { GameClient::Disconnect(); }
|
||||||
DllExport bool __stdcall IsConnected() { return GameClient::Connected(); }
|
DllExport bool __stdcall IsConnected() { return GameClient::Connected(); }
|
||||||
|
DllExport ControllerType __stdcall NetPlayGetControllerType(int32_t port) { return EmulationSettings::GetControllerType(port); }
|
||||||
|
|
||||||
|
DllExport int32_t __stdcall NetPlayGetAvailableControllers()
|
||||||
|
{
|
||||||
|
if(GameServer::Started()) {
|
||||||
|
return GameServer::GetAvailableControllers();
|
||||||
|
} else {
|
||||||
|
return GameClient::GetAvailableControllers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DllExport void __stdcall NetPlaySelectController(int32_t port)
|
||||||
|
{
|
||||||
|
if(GameServer::Started()) {
|
||||||
|
return GameServer::SetHostControllerPort(port);
|
||||||
|
} else {
|
||||||
|
return GameClient::SelectController(port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DllExport int32_t __stdcall NetPlayGetControllerPort()
|
||||||
|
{
|
||||||
|
if(GameServer::Started()) {
|
||||||
|
return GameServer::GetHostControllerPort();
|
||||||
|
} else {
|
||||||
|
return GameClient::GetControllerPort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DllExport void __stdcall Pause()
|
DllExport void __stdcall Pause()
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Reference in a new issue