breaking-bad-ds/source/multiplayer.c
William cd9753f5f6
Fix line endings, bug, text & sound fixes (#8)
* Fix wrong line endings

* Fixup launch tasks

* Fixup nflib lib dir

* Fixup debugger, improve dialogue save tracking

* Fixup typo

* Improve stability of game end logic

* Dialogue text fixes

* Fix SFX bug on Hank's mineral screen

* Bump to 1.0.6

* Add SFX to cracking minigame
2023-11-22 01:04:19 +00:00

781 lines
19 KiB
C

#include "multiplayer.h"
// ** Wifi variables **:
int wifiChannel = 10;
// ** Wifi buffers **:
// Read part
char WIFI_Buffer[1024] = ""; // Wifi buffer is the incoming data from the wifi (DO NOT USE LIKE THIS, please use the "wirelessData" define)
#define wirelessData (WIFI_Buffer + 32) // Define to use the WIFI_Buffer array to avoid wrong values
char Values[1024] = ""; // Store all the values received from the wifi and wainting for treatment
char TempValues[1024] = ""; // Store values that can't be treated yet
int WIFI_ReceivedDataLength = 0; // Size of data received from the wifi
// Write part
char tempSendBuffer[1024] = ""; // Temp buffer to use when the host need to resend the same data if the wifi is not working correctly
// ** Room variables **:
Client clients[MAX_CLIENT];
Client *localClient = &clients[0];
int hostIndex = EMPTY; // Index of the host in the clients array
bool isHost = false; // Is the Nintendo DS hosting a room?
// For host
int speakTo = 1; // Index of the speak to speak with
int idCount = 0; // Id of the next client to add
int lastCommunication = 0; // Countdown to check the wifi timeout
char tempMacAddress[13]; // Mac address of the client that is trying to join the room
int timeoutCount = 0; // Store how many times the wifi has failed to communicate with the client
// For non-host
int joinRoomTimer = WIFI_TIMEOUT; // Timer to limit the time to join a room
bool tryJoinRoom = false; // Is the client trying to join a room
bool skipData; // If true, the client will skip data of the current wifi packet
// Other variables
const bool MP_MODE = false;
int mpStatus = MP_CLIENT_SEARCHING;
void joinMultiplayer(bool hostRoom)
{
nifiInit();
if (hostRoom)
{
createRoom();
}
else
{
mpStatus = MP_CLIENT_SEARCHING;
joinRoomTimer = WIFI_TIMEOUT;
tryJoinRoom = true;
resetNifiValues();
}
}
void disableMultiplayer()
{
isHost = false;
joinRoomTimer = WIFI_TIMEOUT;
for (int i = 1; i < MAX_CLIENT; i++)
{
removeClient(&clients[i]);
}
resetNifiValues();
Wifi_DisableWifi();
mpStatus = MP_CONNECTION_LOST;
}
void tickMultiplayer()
{
if (tryJoinRoom)
{
joinRoomTimer--;
if (joinRoomTimer == 0) // Resend the request each time the timer is ended
{
scanForRoom();
// Reset the timer
joinRoomTimer = WIFI_TIMEOUT;
}
return;
}
// Check if the local client has lost the connection with the host
if (localClient->id != EMPTY && !isHost)
{
lastCommunication++;
if (lastCommunication == CLIENT_TIMEOUT)
{
for (int i = 0; i < MAX_CLIENT; i++)
{
removeClient(&clients[i]);
}
mpStatus = MP_CONNECTION_LOST;
}
}
shareRequest(localClient, TICK_GAME);
}
/**
* @brief Wifi handler call when data is received
*
* @param packetID
* @param readlength
*/
void WirelessHandler(int packetID, int readlength)
{
Wifi_RxRawReadPacket(packetID, readlength, (unsigned short *)WIFI_Buffer);
// Get the correct data length
WIFI_ReceivedDataLength = readlength - 32;
// Treatment of the data
treatData();
}
/**
* @brief Send data to clients
*
* @param buffer
* @param length
*/
void SendWirelessData(unsigned short *buffer, int length)
{
if (Wifi_RawTxFrame(length, 0x0014, buffer) != 0)
{
printf("Error calling RawTxFrame\n");
}
}
/**
* @brief Reset variable used by the the Wifi to avoid wrong values when reconnecting
*
*/
void resetNifiValues()
{
// Reset clients data
for (int i = 0; i < MAX_CLIENT; i++)
{
resetClientValues(&clients[i]);
}
// Reset buffers
WIFI_Buffer[0] = '\0';
Values[0] = '\0';
TempValues[0] = '\0';
tempSendBuffer[0] = '\0';
// Reset variables
lastCommunication = 0;
hostIndex = EMPTY;
WIFI_ReceivedDataLength = 0;
}
/**
* @brief Prepare the Nifi system
*/
void nifiPrepare()
{
// Changes how packets are handled
Wifi_SetRawPacketMode(PACKET_MODE_NIFI);
// Init Wifi without automatic settings
Wifi_InitDefault(false);
}
/**
* @brief Init the Nifi system
*
*/
void nifiInit()
{
resetNifiValues();
// Enable Wifi
Wifi_EnableWifi();
// Configure custom packet handler for when
Wifi_RawSetPacketHandler(WirelessHandler);
// Force specific channel for communication
Wifi_SetChannel(wifiChannel);
// Get MAC address of the Nintendo DS
u8 macAddressUnsigned[6];
Wifi_GetData(WIFIGETDATA_MACADDRESS, 6, macAddressUnsigned);
// Convert unsigned values to hexa values
sprintf(localClient->macAddress, "%02X%02X%02X%02X%02X%02X", macAddressUnsigned[0], macAddressUnsigned[1], macAddressUnsigned[2], macAddressUnsigned[3], macAddressUnsigned[4], macAddressUnsigned[5]);
}
/*
* Treat data from the wifi module
*/
void treatData()
{
// Get data lenght
int recvd_len = strlen(wirelessData);
// printf("%s\n", wirelessData);
// Check if the packet is valid
if (WIFI_ReceivedDataLength == recvd_len + 1)
{
// Add the wifi buffer to the data buffer
strcat(Values, wirelessData);
int StartPosition, EndPosition;
// Get data of the packet
while ((StartPosition = strstr(Values, "{") - Values + 1) > 0 && (EndPosition = strstr(Values + StartPosition, "}") - Values) > 0)
{
char currentPacket[MAX_REQUEST_LENGTH] = "";
strncpy(currentPacket, Values + StartPosition, EndPosition - StartPosition);
// Start spliting incoming data
char *ptr = strtok(currentPacket, ";");
int SplitCount = 0;
char params[MAX_REQUEST_PARAM_COUNT][MAX_REQUEST_PARAM_LENGTH] = {0};
while (ptr != NULL)
{
strcpy(params[SplitCount], ptr);
SplitCount++;
ptr = strtok(NULL, ";");
if (MP_MODE)
{
NF_WriteText(1, 0, 1, 4 + SplitCount, ptr);
}
}
if (strcmp(params[REQUEST_TYPE_INDEX], "GAME") == 0 && !skipData) // Check if the request is about game management (refuse the request if the request was already treated)
{
if (strcmp(params[REQUEST_NAME_INDEX], "TICK_GAME") == 0) // A client is sending a tick
{
// printf("%s\n", wirelessData);
int clientId, destinatorId;
// Get the client destinator id
sscanf(params[3], "%d", &destinatorId);
// If the local client is in a party and the destinator id is the same as the local client id
if (destinatorId == localClient->id && localClient->id != EMPTY)
{
// Get the if of the client that sent the request
sscanf(params[2], "%d", &clientId);
Client *client = getClientById(clientId);
if (client != NULL)
{
// Get the value
sscanf(params[4], "%d", &client->playerTargetX);
sscanf(params[5], "%d", &client->playerTargetZ);
sscanf(params[6], "%d", &client->playerTileX);
sscanf(params[7], "%d", &client->playerTileZ);
sscanf(params[8], "%d", &client->playerFacing);
sscanf(params[9], "%d", &client->timeLeft);
sscanf(params[10], "%d", &client->currentBatchStep);
sscanf(params[11], "%d", &client->batchesComplete);
shareRequest(client, TICK_GAME);
}
}
}
}
else if (strcmp(params[REQUEST_TYPE_INDEX], "ROOM") == 0) // Check if the request is about room management
{
if (isHost) // These request are for the host client
{
if (strcmp(params[REQUEST_NAME_INDEX], "SCAN") == 0) // A client is searching for a room
{
printf("%s SEARCH FOR ROOM\n", params[2]);
// Get the mac address of the client
sprintf(tempMacAddress, params[2]);
addClient(EMPTY, false);
mpStatus = MP_HOST_READY;
}
else if (strcmp(params[REQUEST_NAME_INDEX], "CONFIRM_LISTEN") == 0) // If the client has received host's data
{
// Get the id of the client that has received the data
int clientId;
sscanf(params[2], "%d", &clientId);
// If the id of the client is the same as the id of current treated client
if (clientId == clients[speakTo].id)
{
communicateWithNextClient();
}
}
}
else // These request are for non-host clients
{
if (strcmp(params[REQUEST_NAME_INDEX], "CONFIRM_JOIN") == 0) // The host said that the local client can join the room
{
// Check if the mac address of the local client is the same as the one of the client that wants to join the room
if (strcmp(params[2], localClient->macAddress) == 0 && localClient->id == EMPTY)
{
// Get the host id
int hostId;
sscanf(params[3], "%d", &hostId);
// Set local player id
sscanf(params[4], "%d", &localClient->id);
addClient(hostId, true);
mpStatus = MP_CLIENT_READY;
printf("JOINED %d'S ROOM, YOUR ID: %d\n", hostId, localClient->id);
tryJoinRoom = false;
}
}
else if (strcmp(params[REQUEST_NAME_INDEX], "WANTSPEAK") == 0) // The host asked for communication
{
int clientId;
sscanf(params[2], "%d", &clientId);
// If the host wants to communicate with the local client
if (localClient->id == clientId)
{
lastCommunication = 0;
int messageId;
sscanf(params[3], "%d", &messageId);
// If the request wasn't read yet
if (localClient->lastMessageId < messageId)
{
// Clear temp buffer
strcpy(tempSendBuffer, "");
skipData = false;
localClient->lastMessageId = messageId;
}
else // If the request was already read
{
// Skip the request data
skipData = true;
}
SendDataTo(&clients[hostIndex]);
}
}
else if (strcmp(params[REQUEST_NAME_INDEX], "ADDCLIENTS") == 0) // Add multiples non local players
{
int destinatorId;
sscanf(params[2], "%d", &destinatorId);
if (destinatorId == localClient->id)
{
for (int i = 3; i < SplitCount; i++)
{
int FoundId = EMPTY;
sscanf(params[i], "%d", &FoundId);
addClient(FoundId, false);
mpStatus = MP_CLIENT_READY;
}
}
}
}
//////// Next conditions are for the host and non-host clients
if (strcmp(params[REQUEST_NAME_INDEX], "QUIT") == 0 && !skipData) // A client quit the party
{
int clientId;
sscanf(params[2], "%d", &clientId);
int destinatorId;
sscanf(params[3], "%d", &destinatorId);
if (localClient->id == destinatorId)
{
Client *client = getClientById(clientId);
if (client != NULL)
{
removeClient(client);
printf("%d HAS LEFT THE ROOM\n", clientId);
mpStatus = MP_CONNECTION_LOST;
}
}
}
}
// Clear "TempValues"
for (int i = 0; i < sizeof(TempValues); i++)
TempValues[i] = '\0';
// Add all characters after current data packet to "TempValues"
strcat(TempValues, Values + EndPosition + 1);
// Copy "TempValues" to "Values"
strcpy(Values, TempValues);
}
}
}
/**
* @brief Share a request to clients
*
* @param clientSender
* @param requestType
*/
void shareRequest(Client *clientSender, enum RequestType requestType)
{
// If the local client is the host
if (isHost)
{
// Send the data to all clients
for (int clientIndex = 1; clientIndex < MAX_CLIENT; clientIndex++)
{
Client *clientToUpdate = &clients[clientIndex];
// Avoid sending the request to the current treated client
if (clientSender->id != clientToUpdate->id && clientToUpdate->id != EMPTY)
{
createRequest(clientSender, clientToUpdate, requestType);
}
}
}
else if (clientSender == localClient) // Share the request to the host only
{
Client *clientToUpdate = &clients[hostIndex];
createRequest(clientSender, clientToUpdate, requestType);
}
}
/**
* @brief Create a request and add it to the client to update's send buffer
*
* @param clientSender Client pointer to get data from
* @param clientToUpdate Client pointer to send data to
* @param requestType Request type (See RequestType enum in main.h)
*/
void createRequest(Client *clientSender, Client *clientToUpdate, enum RequestType requestType)
{
// (Increase the buffer size if needed)
char buffer[255] = "";
switch (requestType)
{
case TICK_GAME:
sprintf(buffer, "{GAME;TICK_GAME;%d;%d;%d;%d;%d;%d;%d;%d;%d;%d}", clientSender->id, clientToUpdate->id,
clientSender->playerTargetX, clientSender->playerTargetZ,
clientSender->playerTileX, clientSender->playerTileZ,
clientSender->playerFacing, clientSender->timeLeft,
clientSender->currentBatchStep, clientSender->batchesComplete);
break;
}
if (MP_MODE)
{
NF_WriteText(1, 0, 1, 2, buffer);
}
AddDataTo(clientToUpdate, buffer);
}
/**
* @brief Get the Client By Id (doens't return the local player)
*
* @param clientId
* @return Client*
*/
Client *getClientById(int clientId)
{
for (int i = 1; i < MAX_CLIENT; i++)
{
if (clients[i].id == clientId)
{
return &clients[i];
}
}
return NULL;
}
/**
* @brief Create a room
*
*/
void createRoom()
{
// Is the client is not already a host
if (!isHost)
{
resetNifiValues();
// Create a room
isHost = true;
idCount = 0;
localClient->id = idCount;
mpStatus = MP_HOST_SEARCHING;
timerStart(0, ClockDivider_1024, TIMER_FREQ_1024(WIFI_RATE), managerServer);
}
}
/**
* @brief Remove a client
*
* @param client Client pointer to remove
*/
void removeClient(Client *client)
{
if (client->id != EMPTY)
{
if (isHost)
{
for (int i = 1; i < MAX_CLIENT; i++)
{
// Send a request to all clients to remove the client from all other clients (the removed client is not called)
if (&clients[i] != client && clients[i].id != EMPTY)
{
char buffer[18];
sprintf(buffer, "{ROOM;QUIT;%d;%d}", client->id, clients[i].id);
AddDataTo(&clients[i], buffer);
}
}
}
}
resetClientValues(client);
}
/**
* @brief Reset client values
*
* @param client Client pointer to reset
*/
void resetClientValues(Client *client)
{
client->id = EMPTY;
if (client != localClient)
{
strcpy(client->macAddress, "");
client->playerTargetX = client->playerTileX = isHost ? 3 : 4;
}
else
{
skipData = false;
client->playerTargetX = client->playerTileX = !isHost ? 3 : 4;
}
strcpy(client->sendBuffer, "");
client->lastMessageId = 0;
client->playerTargetZ = client->playerTileZ = 1;
client->batchesComplete = client->currentBatchStep = 0;
client->playerFacing = 1;
client->timeLeft = EMPTY;
}
/**
* @brief Add a client
*
* @param id Client's id (not used by the room owner, set to -1/EMPTY)
* @param addHost Are we adding the host?
*/
void addClient(int id, bool addHost)
{
bool macAlreadyExists = false;
bool idAlreadyExists = false;
if (isHost) // If the local client is the host
{
// Check if the client to add is already in the room (same mac address), because the client is maybe trying to join the room multiple times if the wifi is not working properly
for (int i = 1; i < MAX_CLIENT; i++)
{
if (strcmp(tempMacAddress, clients[i].macAddress) == 0)
{
macAlreadyExists = true;
break;
}
}
}
else
{
// Check if the client to add is already in the room (same id)
for (int i = 1; i < MAX_CLIENT; i++)
{
if (clients[i].id == id)
{
idAlreadyExists = true;
break;
}
}
}
// If the client to add is not already in the room
if (!macAlreadyExists && !idAlreadyExists)
{
int addedClientIndex = EMPTY;
// Try to find a free client slot
for (int i = 1; i < MAX_CLIENT; i++)
{
if (clients[i].id == EMPTY)
{
if (isHost)
{
// Set client id
idCount++;
clients[i].id = idCount;
// Set client mac address
sprintf(clients[i].macAddress, tempMacAddress);
printf("ADDED AT %d, ID : %d\n", i, idCount);
}
else
{
printf("CLIENT ADDED : %d\n", id);
// Apply id
clients[i].id = id;
}
// Store the host index
if (addHost)
{
hostIndex = i;
}
addedClientIndex = i;
break;
}
}
// If the client is added by the host
if (addedClientIndex != EMPTY && isHost)
{
// Send the client his id
char newClientBuffer[100];
sprintf(newClientBuffer, "{ROOM;CONFIRM_JOIN;%s;%d;%d}", tempMacAddress, localClient->id, clients[addedClientIndex].id);
// Send the client all the other clients ids
sprintf(newClientBuffer + strlen(newClientBuffer), "{ROOM;ADDCLIENTS;%d", clients[addedClientIndex].id);
// Send the client id to all the other clients
for (int i = 1; i < MAX_CLIENT; i++)
{
if (clients[i].id != EMPTY && i != addedClientIndex)
{
// Send the client all the other clients ids
sprintf(newClientBuffer + strlen(newClientBuffer), ";%d", clients[i].id);
// Send the client id to all the other clients
char bufferForOtherClients[24];
sprintf(bufferForOtherClients, "{ROOM;ADDCLIENTS;%d;%d}", clients[i].id, clients[addedClientIndex].id);
AddDataTo(&clients[i], bufferForOtherClients);
}
}
sprintf(newClientBuffer + strlen(newClientBuffer), "}");
AddDataTo(&clients[addedClientIndex], newClientBuffer);
}
}
}
/**
* @brief Scan for a room
*
*/
void scanForRoom()
{
isHost = false;
char buffer[25];
sprintf(buffer, "{ROOM;SCAN;%s}", localClient->macAddress);
SendWirelessData((unsigned short *)buffer, strlen(buffer) + 1);
}
/**
* @brief Select the next client to communicate with
*
*/
void communicateWithNextClient()
{
// Reset values
timeoutCount = 0;
lastCommunication = 0;
// Change client to communicate with
speakTo++;
if (speakTo == MAX_CLIENT) // Go back to the beginning of the list
speakTo = 1;
if (clients[speakTo].id != EMPTY)
{
// AddDataTo(&clients[speakTo], "{}"); // TO REMOVE FIX : Data can't be sent if the buffer is empty
SendDataTo(&clients[speakTo]);
}
else
{
lastCommunication = WIFI_TIMEOUT - 1;
}
}
/**
* @brief Manage client's communication order and timeout
*
*/
void managerServer()
{
// Only the host can manage the server
if (isHost)
{
// Increase the time to check timeout
lastCommunication++;
if (lastCommunication == WIFI_TIMEOUT)
{
// If the clients is in the party (id not empty)
if (clients[speakTo].id != EMPTY)
{
// If the timeout max count isn't reached, retry communication
if (timeoutCount <= MAX_TIMEOUT_RETRY)
{
timeoutCount++;
lastCommunication = 0;
SendDataTo(&clients[speakTo]);
}
else
{
// Remove the client
removeClient(&clients[speakTo]);
communicateWithNextClient();
}
}
else
{
communicateWithNextClient();
}
}
}
}
/**
* @brief Add data in the client's send buffer to send it to the client
*
* @param client client to add data to
* @param data data to add
*/
void AddDataTo(Client *client, const char *data)
{
if (client != NULL && client->id != EMPTY && strlen(data) != 0)
{
sprintf(client->sendBuffer + strlen(client->sendBuffer), "%s", data);
}
}
/**
* @brief Send data to a client with Nifi
*
* @param client client to send data to
*/
void SendDataTo(Client *client)
{
// If the buffer is not empty, copy the buffer to a new one and clear the buffer
if (timeoutCount == 0)
{
if (isHost)
{
client->lastMessageId++;
// Ask to client to communicate
sprintf(tempSendBuffer, "{ROOM;WANTSPEAK;%d;%d}", client->id, client->lastMessageId);
}
else
{
// Tell the host that the local client has received the message
sprintf(tempSendBuffer, "{ROOM;CONFIRM_LISTEN;%d}", localClient->id);
}
sprintf(tempSendBuffer + strlen(tempSendBuffer), "%s", client->sendBuffer);
// Clear the client's send buffer
strcpy(client->sendBuffer, "");
}
// Send data
SendWirelessData((unsigned short *)tempSendBuffer, strlen(tempSendBuffer) + 1);
}
int getMultiplayerStatus()
{
return mpStatus;
}
Client *getOpponent()
{
for (int i = 1; i < MAX_CLIENT; i++)
{
if (clients[i].id != EMPTY)
{
return &clients[i];
}
}
mpStatus = MP_CONNECTION_LOST;
return NULL;
}
Client *getLocalClient()
{
return localClient;
}
bool isHostClient()
{
return isHost;
}