SuperMarioOdysseyOnline/source/server/Client.cpp
Robin C. Ladiges 36f9343f83 don't send empty GameInf and CostumeInf packets and resend them on reconnect
Resend because: on server restarts the server will lose the stage and costume information.

If only one client is connected to the server, the packets currently aren't resent, so the server doesn't know in which stage the client is and what
costume it wears
(which I'd like to display on the website).

With more then one client connected it already works, because when another client joins the server, the client will send both packets.
2022-12-16 13:19:54 -06:00

1429 lines
37 KiB
C++

#include "server/Client.hpp"
#include "al/layout/SimpleLayoutAppearWaitEnd.h"
#include "al/util/LiveActorUtil.h"
#include "game/SaveData/SaveDataAccessFunction.h"
#include "heap/seadHeapMgr.h"
#include "logger.hpp"
#include "packets/Packet.h"
#include "server/hns/HideAndSeekMode.hpp"
SEAD_SINGLETON_DISPOSER_IMPL(Client)
typedef void (Client::*ClientThreadFunc)(void);
/**
* @brief Construct a new Client:: Client object
*
* @param bufferSize defines the maximum amount of puppets the client can handle
*/
Client::Client() {
mHeap = sead::ExpHeap::create(0x50000, "ClientHeap", sead::HeapMgr::instance()->getCurrentHeap(), 8, sead::Heap::cHeapDirection_Forward, false);
sead::ScopedCurrentHeapSetter heapSetter(
mHeap); // every new call after this will use ClientHeap instead of SequenceHeap
mReadThread = new al::AsyncFunctorThread("ClientReadThread", al::FunctorV0M<Client*, ClientThreadFunc>(this, &Client::readFunc), 0, 0x1000, {0});
mKeyboard = new Keyboard(nn::swkbd::GetRequiredStringBufferSize());
mSocket = new SocketClient("SocketClient", mHeap, this);
mPuppetHolder = new PuppetHolder(maxPuppets);
for (size_t i = 0; i < MAXPUPINDEX; i++)
{
mPuppetInfoArr[i] = new PuppetInfo();
sprintf(mPuppetInfoArr[i]->puppetName, "Puppet%zu", i);
}
strcpy(mDebugPuppetInfo.puppetName, "PuppetDebug");
mConnectCount = 0;
curCollectedShines.fill(-1);
collectedShineCount = 0;
mShineArray.allocBuffer(100, nullptr); // max of 100 shine actors in buffer
nn::account::GetLastOpenedUser(&mUserID);
nn::account::Nickname playerName;
nn::account::GetNickname(&playerName, mUserID);
Logger::setLogName(playerName.name); // set Debug logger name to player name
mUsername = playerName.name;
mUserID.print();
Logger::log("Player Name: %s\n", playerName.name);
Logger::log("%s Build Number: %s\n", playerName.name, TOSTRING(BUILDVERSTR));
}
/**
* @brief initializes client class using initInfo obtained from StageScene::init
*
* @param initInfo init info used to create layouts used by client
*/
void Client::init(al::LayoutInitInfo const &initInfo, GameDataHolderAccessor holder) {
mConnectionWait = new (mHeap) al::WindowConfirmWait("ServerWaitConnect", "WindowConfirmWait", initInfo);
mConnectStatus = new (mHeap) al::SimpleLayoutAppearWaitEnd("", "SaveMessage", initInfo, 0, false);
mConnectionWait->setTxtMessage(u"Connecting to Server.");
mConnectionWait->setTxtMessageConfirm(u"Failed to Connect!");
al::setPaneString(mConnectStatus, "TxtSave", u"Connecting to Server.", 0);
al::setPaneString(mConnectStatus, "TxtSaveSh", u"Connecting to Server.", 0);
mHolder = holder;
startThread();
Logger::log("Heap Free Size: %f/%f\n", mHeap->getFreeSize() * 0.001f, mHeap->getSize() * 0.001f);
}
/**
* @brief starts client read thread
*
* @return true if read thread was sucessfully started
* @return false if read thread was unable to start, or thread was already started.
*/
bool Client::startThread() {
if(mReadThread->isDone() ) {
mReadThread->start();
Logger::log("Read Thread Sucessfully Started.\n");
return true;
}else {
Logger::log("Read Thread has already started! Or other unknown reason.\n");
return false;
}
}
/**
* @brief restarts currently active connection to server
*
*/
void Client::restartConnection() {
if (!sInstance) {
Logger::log("Static Instance is null!\n");
return;
}
sead::ScopedCurrentHeapSetter setter(sInstance->mHeap);
Logger::log("Sending Disconnect.\n");
PlayerDC *playerDC = new PlayerDC();
playerDC->mUserID = sInstance->mUserID;
sInstance->mSocket->queuePacket(playerDC);
if (sInstance->mSocket->closeSocket()) {
Logger::log("Sucessfully Closed Socket.\n");
}
sInstance->mConnectCount = 0;
sInstance->mIsConnectionActive = sInstance->mSocket->init(sInstance->mServerIP.cstr(), sInstance->mServerPort).isSuccess();
if(sInstance->mSocket->getLogState() == SOCKET_LOG_CONNECTED) {
Logger::log("Reconnect Sucessful!\n");
} else {
Logger::log("Reconnect Unsuccessful.\n");
}
}
/**
* @brief starts a connection using client's TCP socket class, pulling up the software keyboard for user inputted IP if save file does not have one saved.
*
* @return true if successful connection to server
* @return false if connection was unable to establish
*/
bool Client::startConnection() {
bool isNeedSave = false;
bool isOverride = al::isPadHoldZL(-1);
if (mServerIP.isEmpty() || isOverride) {
mKeyboard->setHeaderText(u"Save File does not contain an IP!");
mKeyboard->setSubText(u"Please set a Server IP Below.");
mServerIP = "127.0.0.1";
Client::openKeyboardIP();
isNeedSave = true;
}
if (!mServerPort || isOverride) {
mKeyboard->setHeaderText(u"Save File does not contain a port!");
mKeyboard->setSubText(u"Please set a Server Port Below.");
mServerPort = 1027;
Client::openKeyboardPort();
isNeedSave = true;
}
if (isNeedSave) {
SaveDataAccessFunction::startSaveDataWrite(mHolder.mData);
}
mIsConnectionActive = mSocket->init(mServerIP.cstr(), mServerPort).isSuccess();
if (mIsConnectionActive) {
Logger::log("Sucessful Connection. Waiting to recieve init packet.\n");
bool waitingForInitPacket = true;
// wait for client init packet
while (waitingForInitPacket) {
Packet *curPacket = mSocket->tryGetPacket();
if (curPacket) {
if (curPacket->mType == PacketType::CLIENTINIT) {
InitPacket* initPacket = (InitPacket*)curPacket;
Logger::log("Server Max Player Size: %d\n", initPacket->maxPlayers);
maxPuppets = initPacket->maxPlayers - 1;
waitingForInitPacket = false;
}
mHeap->free(curPacket);
} else {
Logger::log("Recieve failed! Stopping Connection.\n");
mIsConnectionActive = false;
waitingForInitPacket = false;
}
}
}
return mIsConnectionActive;
}
/**
* @brief Opens up OS's software keyboard in order to change the currently used server IP.
* @returns whether or not a new IP has been defined and needs to be saved.
*/
bool Client::openKeyboardIP() {
if (!sInstance) {
Logger::log("Static Instance is null!\n");
return false;
}
// opens swkbd with the initial text set to the last saved IP
sInstance->mKeyboard->openKeyboard(
sInstance->mServerIP.cstr(), [](nn::swkbd::KeyboardConfig& config) {
config.keyboardMode = nn::swkbd::KeyboardMode::ModeASCII;
config.textMaxLength = MAX_HOSTNAME_LENGTH;
config.textMinLength = 1;
config.isUseUtf8 = true;
config.inputFormMode = nn::swkbd::InputFormMode::OneLine;
});
hostname prevIp = sInstance->mServerIP;
while (true) {
if (sInstance->mKeyboard->isThreadDone()) {
if(!sInstance->mKeyboard->isKeyboardCancelled())
sInstance->mServerIP = sInstance->mKeyboard->getResult();
break;
}
nn::os::YieldThread(); // allow other threads to run
}
bool isFirstConnect = prevIp != sInstance->mServerIP;
sInstance->mSocket->setIsFirstConn(isFirstConnect);
return isFirstConnect;
}
/**
* @brief Opens up OS's software keyboard in order to change the currently used server port.
* @returns whether or not a new port has been defined and needs to be saved.
*/
bool Client::openKeyboardPort() {
if (!sInstance) {
Logger::log("Static Instance is null!\n");
return false;
}
// opens swkbd with the initial text set to the last saved port
char buf[6];
nn::util::SNPrintf(buf, 6, "%u", sInstance->mServerPort);
sInstance->mKeyboard->openKeyboard(buf, [](nn::swkbd::KeyboardConfig& config) {
config.keyboardMode = nn::swkbd::KeyboardMode::ModeNumeric;
config.textMaxLength = 5;
config.textMinLength = 2;
config.isUseUtf8 = true;
config.inputFormMode = nn::swkbd::InputFormMode::OneLine;
});
int prevPort = sInstance->mServerPort;
while (true) {
if (sInstance->mKeyboard->isThreadDone()) {
if(!sInstance->mKeyboard->isKeyboardCancelled())
sInstance->mServerPort = ::atoi(sInstance->mKeyboard->getResult());
break;
}
nn::os::YieldThread(); // allow other threads to run
}
bool isFirstConnect = prevPort != sInstance->mServerPort;
sInstance->mSocket->setIsFirstConn(isFirstConnect);
return isFirstConnect;
}
/**
* @brief main thread function for read thread, responsible for processing packets from server
*
*/
void Client::readFunc() {
if (waitForGameInit) {
nn::os::YieldThread(); // sleep the thread for the first thing we do so that game init can finish
nn::os::SleepThread(nn::TimeSpan::FromSeconds(2));
waitForGameInit = false;
}
mConnectStatus->appear();
al::startAction(mConnectStatus, "Loop", "Loop");
if (!startConnection()) {
Logger::log("Failed to Connect to Server.\n");
nn::os::SleepThread(nn::TimeSpan::FromNanoSeconds(250000000)); // sleep active thread for 0.25 seconds
mConnectStatus->end();
return;
}
nn::os::SleepThread(nn::TimeSpan::FromNanoSeconds(500000000)); // sleep for 0.5 seconds to let connection layout fully show (probably should find a better way to do this)
mConnectStatus->end();
while(mIsConnectionActive) {
Packet *curPacket = mSocket->tryGetPacket(); // will block until a packet has been recieved, or socket disconnected
if (curPacket) {
switch (curPacket->mType)
{
case PacketType::PLAYERINF:
updatePlayerInfo((PlayerInf*)curPacket);
break;
case PacketType::GAMEINF:
updateGameInfo((GameInf*)curPacket);
break;
case PacketType::HACKCAPINF:
updateHackCapInfo((HackCapInf *)curPacket);
break;
case PacketType::CAPTUREINF:
updateCaptureInfo((CaptureInf*)curPacket);
break;
case PacketType::PLAYERCON:
updatePlayerConnect((PlayerConnect*)curPacket);
// Send relevant info packets when another client is connected
if (lastGameInfPacket != emptyGameInfPacket) {
// Assume game packets are empty from first connection
if (lastGameInfPacket.mUserID != mUserID)
lastGameInfPacket.mUserID = mUserID;
mSocket->send(&lastGameInfPacket);
}
// No need to send player/costume packets if they're empty
if (lastPlayerInfPacket.mUserID == mUserID)
mSocket->send(&lastPlayerInfPacket);
if (lastCostumeInfPacket.mUserID == mUserID)
mSocket->send(&lastCostumeInfPacket);
break;
case PacketType::COSTUMEINF:
updateCostumeInfo((CostumeInf*)curPacket);
break;
case PacketType::SHINECOLL:
updateShineInfo((ShineCollect*)curPacket);
break;
case PacketType::PLAYERDC:
Logger::log("Received Player Disconnect!\n");
curPacket->mUserID.print();
disconnectPlayer((PlayerDC*)curPacket);
break;
case PacketType::TAGINF:
updateTagInfo((TagInf*)curPacket);
break;
case PacketType::CHANGESTAGE:
sendToStage((ChangeStagePacket*)curPacket);
break;
case PacketType::CLIENTINIT: {
InitPacket* initPacket = (InitPacket*)curPacket;
Logger::log("Server Max Player Size: %d\n", initPacket->maxPlayers);
maxPuppets = initPacket->maxPlayers - 1;
break;
}
default:
Logger::log("Discarding Unknown Packet Type.\n");
break;
}
mHeap->free(curPacket);
}else { // if false, socket has errored or disconnected, so close the socket and end this thread.
Logger::log("Client Socket Encountered an Error! Errno: 0x%x\n", mSocket->socket_errno);
}
}
Logger::log("Client Read Thread ending.\n");
}
/**
* @brief sends player info packet to current server
*
* @param player pointer to current player class, used to get translation, animation, and capture data
*/
void Client::sendPlayerInfPacket(const PlayerActorBase *playerBase, bool isYukimaru) {
if (!sInstance) {
Logger::log("Static Instance is Null!\n");
return;
}
if(!playerBase) {
Logger::log("Error: Null Player Reference\n");
return;
}
sead::ScopedCurrentHeapSetter setter(sInstance->mHeap);
PlayerInf *packet = new PlayerInf();
packet->mUserID = sInstance->mUserID;
packet->playerPos = al::getTrans(playerBase);
al::calcQuat(&packet->playerRot,
playerBase); // calculate rotation based off pose instead of using quat rotation
if (!isYukimaru) {
PlayerActorHakoniwa* player = (PlayerActorHakoniwa*)playerBase;
for (size_t i = 0; i < 6; i++)
{
packet->animBlendWeights[i] = player->mPlayerAnimator->getBlendWeight(i);
}
const char *hackName = player->mHackKeeper->getCurrentHackName();
if (hackName != nullptr) {
sInstance->isClientCaptured = true;
const char* actName = al::getActionName(player->mHackKeeper->currentHackActor);
if (actName) {
packet->actName = PlayerAnims::FindType(actName);
packet->subActName = PlayerAnims::Type::Unknown;
//strcpy(packet.actName, actName);
} else {
packet->actName = PlayerAnims::Type::Unknown;
packet->subActName = PlayerAnims::Type::Unknown;
}
} else {
packet->actName = PlayerAnims::FindType(player->mPlayerAnimator->mAnimFrameCtrl->getActionName());
packet->subActName = PlayerAnims::FindType(player->mPlayerAnimator->curSubAnim.cstr());
sInstance->isClientCaptured = false;
}
} else {
// TODO: implement YukimaruRacePlayer syncing
for (size_t i = 0; i < 6; i++)
{
packet->animBlendWeights[i] = 0;
}
sInstance->isClientCaptured = false;
packet->actName = PlayerAnims::Type::Unknown;
packet->subActName = PlayerAnims::Type::Unknown;
}
if(sInstance->lastPlayerInfPacket != *packet) {
sInstance->lastPlayerInfPacket = *packet; // deref packet and store in client memory
sInstance->mSocket->queuePacket(packet);
} else {
sInstance->mHeap->free(packet); // free packet if we're not using it
}
}
/**
* @brief sends info related to player's cap actor to server
*
* @param hackCap pointer to cap actor, used to get translation, animation, and state info
*/
void Client::sendHackCapInfPacket(const HackCap* hackCap) {
if (!sInstance) {
Logger::log("Static Instance is Null!\n");
return;
}
sead::ScopedCurrentHeapSetter setter(sInstance->mHeap);
bool isFlying = hackCap->isFlying();
// if cap is in flying state, send packet as often as this function is called
if (isFlying) {
HackCapInf *packet = new HackCapInf();
packet->mUserID = sInstance->mUserID;
packet->capPos = al::getTrans(hackCap);
packet->isCapVisible = isFlying;
packet->capQuat.x = hackCap->mJointKeeper->mJointRot.x;
packet->capQuat.y = hackCap->mJointKeeper->mJointRot.y;
packet->capQuat.z = hackCap->mJointKeeper->mJointRot.z;
packet->capQuat.w = hackCap->mJointKeeper->mSkew;
strcpy(packet->capAnim, al::getActionName(hackCap));
sInstance->mSocket->queuePacket(packet);
sInstance->isSentHackInf = true;
} else if (sInstance->isSentHackInf) { // if cap is not flying, check to see if previous function call sent a packet, and if so, send one final packet resetting cap data.
HackCapInf *packet = new HackCapInf();
packet->mUserID = sInstance->mUserID;
packet->isCapVisible = false;
packet->capPos = sead::Vector3f::zero;
packet->capQuat = sead::Quatf::unit;
sInstance->mSocket->queuePacket(packet);
sInstance->isSentHackInf = false;
}
}
/**
* @brief
* Sends both stage info and player 2D info to the server.
* @param player
* @param holder
*/
void Client::sendGameInfPacket(const PlayerActorHakoniwa* player, GameDataHolderAccessor holder) {
if (!sInstance) {
Logger::log("Static Instance is Null!\n");
return;
}
sead::ScopedCurrentHeapSetter setter(sInstance->mHeap);
GameInf *packet = new GameInf();
packet->mUserID = sInstance->mUserID;
if (player) {
packet->is2D = player->mDimKeeper->is2DModel;
} else {
packet->is2D = false;
}
packet->scenarioNo = holder.mData->mGameDataFile->getScenarioNo();
strcpy(packet->stageName, GameDataFunction::getCurrentStageName(holder));
if (*packet != sInstance->lastGameInfPacket && *packet != sInstance->emptyGameInfPacket) {
sInstance->lastGameInfPacket = *packet;
sInstance->mSocket->queuePacket(packet);
} else {
sInstance->mHeap->free(packet); // free packet if we're not using it
}
}
/**
* @brief
* Sends only stage info to the server.
* @param holder
*/
void Client::sendGameInfPacket(GameDataHolderAccessor holder) {
if (!sInstance) {
Logger::log("Static Instance is Null!\n");
return;
}
sead::ScopedCurrentHeapSetter setter(sInstance->mHeap);
GameInf *packet = new GameInf();
packet->mUserID = sInstance->mUserID;
packet->is2D = false;
packet->scenarioNo = holder.mData->mGameDataFile->getScenarioNo();
strcpy(packet->stageName, GameDataFunction::getCurrentStageName(holder));
if (*packet != sInstance->emptyGameInfPacket) {
sInstance->lastGameInfPacket = *packet;
sInstance->mSocket->queuePacket(packet);
}
}
/**
* @brief
*
*/
void Client::sendTagInfPacket() {
if (!sInstance) {
Logger::log("Static Instance is Null!\n");
return;
}
sead::ScopedCurrentHeapSetter setter(sInstance->mHeap);
HideAndSeekMode* hsMode = GameModeManager::instance()->getMode<HideAndSeekMode>();
if (!GameModeManager::instance()->isMode(GameMode::HIDEANDSEEK)) {
Logger::log("State is not Hide and Seek!\n");
return;
}
HideAndSeekInfo* curInfo = GameModeManager::instance()->getInfo<HideAndSeekInfo>();
TagInf *packet = new TagInf();
packet->mUserID = sInstance->mUserID;
packet->isIt = hsMode->isPlayerIt();
packet->minutes = curInfo->mHidingTime.mMinutes;
packet->seconds = curInfo->mHidingTime.mSeconds;
packet->updateType = static_cast<TagUpdateType>(TagUpdateType::STATE | TagUpdateType::TIME);
sInstance->mSocket->queuePacket(packet);
}
/**
* @brief
*
* @param body
* @param cap
*/
void Client::sendCostumeInfPacket(const char* body, const char* cap) {
if (!sInstance) {
Logger::log("Static Instance is Null!\n");
return;
}
if (!strcmp(body, "") && !strcmp(cap, "")) { return; }
sead::ScopedCurrentHeapSetter setter(sInstance->mHeap);
CostumeInf *packet = new CostumeInf(body, cap);
packet->mUserID = sInstance->mUserID;
sInstance->lastCostumeInfPacket = *packet;
sInstance->mSocket->queuePacket(packet);
}
/**
* @brief
*
* @param player
*/
void Client::sendCaptureInfPacket(const PlayerActorHakoniwa* player) {
if (!sInstance) {
Logger::log("Static Instance is Null!\n");
return;
}
sead::ScopedCurrentHeapSetter setter(sInstance->mHeap);
if (sInstance->isClientCaptured && !sInstance->isSentCaptureInf) {
CaptureInf *packet = new CaptureInf();
packet->mUserID = sInstance->mUserID;
strcpy(packet->hackName, tryConvertName(player->mHackKeeper->getCurrentHackName()));
sInstance->mSocket->queuePacket(packet);
sInstance->isSentCaptureInf = true;
} else if (!sInstance->isClientCaptured && sInstance->isSentCaptureInf) {
CaptureInf *packet = new CaptureInf();
packet->mUserID = sInstance->mUserID;
strcpy(packet->hackName, "");
sInstance->mSocket->queuePacket(packet);
sInstance->isSentCaptureInf = false;
}
}
/**
* @brief
*/
void Client::resendInitPackets() {
// CostumeInfPacket
if (lastCostumeInfPacket.mUserID == mUserID) {
mSocket->queuePacket(&lastCostumeInfPacket);
}
// GameInfPacket
if (lastGameInfPacket != emptyGameInfPacket) {
mSocket->queuePacket(&lastGameInfPacket);
}
}
/**
* @brief
*
* @param shineID
*/
void Client::sendShineCollectPacket(int shineID) {
if (!sInstance) {
Logger::log("Static Instance is Null!\n");
return;
}
sead::ScopedCurrentHeapSetter setter(sInstance->mHeap);
if(sInstance->lastCollectedShine != shineID) {
ShineCollect *packet = new ShineCollect();
packet->mUserID = sInstance->mUserID;
packet->shineId = shineID;
sInstance->lastCollectedShine = shineID;
sInstance->mSocket->queuePacket(packet);
}
}
/**
* @brief
*
* @param packet
*/
void Client::updatePlayerInfo(PlayerInf *packet) {
PuppetInfo* curInfo = findPuppetInfo(packet->mUserID, false);
if (!curInfo) {
return;
}
if(!curInfo->isConnected) {
curInfo->isConnected = true;
}
curInfo->playerPos = packet->playerPos;
// check if rotation is larger than zero and less than or equal to 1
if(abs(packet->playerRot.x) > 0.f || abs(packet->playerRot.y) > 0.f || abs(packet->playerRot.z) > 0.f || abs(packet->playerRot.w) > 0.f) {
if(abs(packet->playerRot.x) <= 1.f || abs(packet->playerRot.y) <= 1.f || abs(packet->playerRot.z) <= 1.f || abs(packet->playerRot.w) <= 1.f) {
curInfo->playerRot = packet->playerRot;
}
}
if (packet->actName != PlayerAnims::Type::Unknown) {
strcpy(curInfo->curAnimStr, PlayerAnims::FindStr(packet->actName));
if (curInfo->curAnimStr[0] == '\0')
Logger::log("[ERROR] %s: actName was out of bounds: %d\n", __func__, packet->actName);
} else {
strcpy(curInfo->curAnimStr, "Wait");
}
if(packet->subActName != PlayerAnims::Type::Unknown) {
strcpy(curInfo->curSubAnimStr, PlayerAnims::FindStr(packet->subActName));
if (curInfo->curSubAnimStr[0] == '\0')
Logger::log("[ERROR] %s: subActName was out of bounds: %d\n", __func__, packet->subActName);
} else {
strcpy(curInfo->curSubAnimStr, "");
}
curInfo->curAnim = packet->actName;
curInfo->curSubAnim = packet->subActName;
for (size_t i = 0; i < 6; i++)
{
// weights can only be between 0 and 1
if(packet->animBlendWeights[i] >= 0.f && packet->animBlendWeights[i] <= 1.f) {
curInfo->blendWeights[i] = packet->animBlendWeights[i];
}
}
//TEMP
if(!curInfo->isCapThrow) {
curInfo->capPos = packet->playerPos;
}
}
/**
* @brief
*
* @param packet
*/
void Client::updateHackCapInfo(HackCapInf *packet) {
PuppetInfo* curInfo = findPuppetInfo(packet->mUserID, false);
if (curInfo) {
curInfo->capPos = packet->capPos;
curInfo->capRot = packet->capQuat;
curInfo->isCapThrow = packet->isCapVisible;
strcpy(curInfo->capAnim, packet->capAnim);
}
}
/**
* @brief
*
* @param packet
*/
void Client::updateCaptureInfo(CaptureInf* packet) {
PuppetInfo* curInfo = findPuppetInfo(packet->mUserID, false);
if (!curInfo) {
return;
}
curInfo->isCaptured = strlen(packet->hackName) > 0;
if (curInfo->isCaptured) {
strcpy(curInfo->curHack, packet->hackName);
}
}
/**
* @brief
*
* @param packet
*/
void Client::updateCostumeInfo(CostumeInf *packet) {
PuppetInfo* curInfo = findPuppetInfo(packet->mUserID, false);
if (!curInfo) {
return;
}
strcpy(curInfo->costumeBody, packet->bodyModel);
strcpy(curInfo->costumeHead, packet->capModel);
}
/**
* @brief
*
* @param packet
*/
void Client::updateShineInfo(ShineCollect* packet) {
if(collectedShineCount < curCollectedShines.size() - 1) {
curCollectedShines[collectedShineCount] = packet->shineId;
collectedShineCount++;
}
}
/**
* @brief
*
* @param packet
*/
void Client::updatePlayerConnect(PlayerConnect* packet) {
PuppetInfo* curInfo = findPuppetInfo(packet->mUserID, true);
if (!curInfo) {
return;
}
if (curInfo->isConnected) {
Logger::log("Info is already being used by another connected player!\n");
packet->mUserID.print("Connection ID");
curInfo->playerID.print("Target Info");
} else {
packet->mUserID.print("Player Connected! ID");
curInfo->playerID = packet->mUserID;
curInfo->isConnected = true;
strcpy(curInfo->puppetName, packet->clientName);
mConnectCount++;
}
}
/**
* @brief
*
* @param packet
*/
void Client::updateGameInfo(GameInf *packet) {
PuppetInfo* curInfo = findPuppetInfo(packet->mUserID, false);
if (!curInfo) {
return;
}
if(curInfo->isConnected) {
curInfo->scenarioNo = packet->scenarioNo;
if(strcmp(packet->stageName, "") != 0 && strlen(packet->stageName) > 3) {
strcpy(curInfo->stageName, packet->stageName);
}
curInfo->is2D = packet->is2D;
}
}
/**
* @brief
*
* @param packet
*/
void Client::updateTagInfo(TagInf *packet) {
// if the packet is for our player, edit info for our player
if (packet->mUserID == mUserID && GameModeManager::instance()->isMode(GameMode::HIDEANDSEEK)) {
HideAndSeekMode* mMode = GameModeManager::instance()->getMode<HideAndSeekMode>();
HideAndSeekInfo* curInfo = GameModeManager::instance()->getInfo<HideAndSeekInfo>();
if (packet->updateType & TagUpdateType::STATE) {
mMode->setPlayerTagState(packet->isIt);
}
if (packet->updateType & TagUpdateType::TIME) {
curInfo->mHidingTime.mSeconds = packet->seconds;
curInfo->mHidingTime.mMinutes = packet->minutes;
}
return;
}
PuppetInfo* curInfo = findPuppetInfo(packet->mUserID, false);
if (!curInfo) {
return;
}
curInfo->isIt = packet->isIt;
curInfo->seconds = packet->seconds;
curInfo->minutes = packet->minutes;
}
/**
* @brief
*
* @param packet
*/
void Client::sendToStage(ChangeStagePacket* packet) {
if (mSceneInfo && mSceneInfo->mSceneObjHolder) {
GameDataHolderAccessor accessor(mSceneInfo->mSceneObjHolder);
Logger::log("Sending Player to %s at Entrance %s in Scenario %d\n", packet->changeStage,
packet->changeID, packet->scenarioNo);
ChangeStageInfo info(accessor.mData, packet->changeID, packet->changeStage, false, packet->scenarioNo, static_cast<ChangeStageInfo::SubScenarioType>(packet->subScenarioType));
GameDataFunction::tryChangeNextStage(accessor, &info);
}
}
/**
* @brief
*
* @param packet
*/
void Client::disconnectPlayer(PlayerDC *packet) {
PuppetInfo* curInfo = findPuppetInfo(packet->mUserID, false);
if (!curInfo || !curInfo->isConnected) {
return;
}
curInfo->isConnected = false;
curInfo->scenarioNo = -1;
strcpy(curInfo->stageName, "");
curInfo->isInSameStage = false;
mConnectCount--;
}
/**
* @brief
*
* @param shineId
* @return true
* @return false
*/
bool Client::isShineCollected(int shineId) {
for (size_t i = 0; i < curCollectedShines.size(); i++)
{
if(curCollectedShines[i] >= 0) {
if(curCollectedShines[i] == shineId) {
return true;
}
}
}
return false;
}
/**
* @brief
*
* @param id
* @return int
*/
PuppetInfo* Client::findPuppetInfo(const nn::account::Uid& id, bool isFindAvailable) {
PuppetInfo *firstAvailable = nullptr;
for (size_t i = 0; i < getMaxPlayerCount() - 1; i++) {
PuppetInfo* curInfo = mPuppetInfoArr[i];
if (curInfo->playerID == id) {
return curInfo;
} else if (isFindAvailable && !firstAvailable && !curInfo->isConnected) {
firstAvailable = curInfo;
}
}
if (!firstAvailable) {
Logger::log("Unable to find Assigned Puppet for Player!\n");
id.print("User ID");
}
return firstAvailable;
}
/**
* @brief
*
* @param holder
*/
void Client::setStageInfo(GameDataHolderAccessor holder) {
if (sInstance) {
sInstance->mStageName = GameDataFunction::getCurrentStageName(holder);
sInstance->mScenario = holder.mData->mGameDataFile->getScenarioNo(); //holder.mData->mGameDataFile->getMainScenarioNoCurrent();
sInstance->mPuppetHolder->setStageInfo(sInstance->mStageName.cstr(), sInstance->mScenario);
}
}
/**
* @brief
*
* @param puppet
* @return true
* @return false
*/
bool Client::tryAddPuppet(PuppetActor *puppet) {
if(sInstance) {
return sInstance->mPuppetHolder->tryRegisterPuppet(puppet);
}else {
return false;
}
}
/**
* @brief
*
* @param puppet
* @return true
* @return false
*/
bool Client::tryAddDebugPuppet(PuppetActor *puppet) {
if(sInstance) {
return sInstance->mPuppetHolder->tryRegisterDebugPuppet(puppet);
}else {
return false;
}
}
/**
* @brief
*
* @param idx
* @return PuppetActor*
*/
PuppetActor *Client::getPuppet(int idx) {
if(sInstance) {
return sInstance->mPuppetHolder->getPuppetActor(idx);
}else {
return nullptr;
}
}
/**
* @brief
*
* @return PuppetInfo*
*/
PuppetInfo *Client::getLatestInfo() {
if(sInstance) {
return Client::getPuppetInfo(sInstance->mPuppetHolder->getSize() - 1);
}else {
return nullptr;
}
}
/**
* @brief
*
* @param idx
* @return PuppetInfo*
*/
PuppetInfo *Client::getPuppetInfo(int idx) {
if(sInstance) {
// unsafe get
PuppetInfo *curInfo = sInstance->mPuppetInfoArr[idx];
if (!curInfo) {
Logger::log("Attempting to Access Puppet Out of Bounds! Value: %d\n", idx);
return nullptr;
}
return curInfo;
}else {
return nullptr;
}
}
/**
* @brief
*
*/
void Client::resetCollectedShines() {
collectedShineCount = 0;
curCollectedShines.fill(-1);
}
/**
* @brief
*
* @param shineId
*/
void Client::removeShine(int shineId) {
for (size_t i = 0; i < curCollectedShines.size(); i++)
{
if(curCollectedShines[i] == shineId) {
curCollectedShines[i] = -1;
collectedShineCount--;
}
}
}
/**
* @brief
*
* @return true
* @return false
*/
bool Client::isNeedUpdateShines() {
return sInstance ? sInstance->collectedShineCount > 0 : false;
}
/**
* @brief
*
*/
void Client::updateShines() {
if (!sInstance) {
Logger::log("Client Null!\n");
return;
}
// skip shine sync if player is in cap kingdom scenario zero (very start of the game)
if (sInstance->mStageName == "CapWorldHomeStage" && (sInstance->mScenario == 0 || sInstance->mScenario == 1)) {
return;
}
GameDataHolderAccessor accessor(sInstance->mCurStageScene);
for (size_t i = 0; i < sInstance->getCollectedShinesCount(); i++)
{
int shineID = sInstance->getShineID(i);
if(shineID < 0) continue;
Logger::log("Shine UID: %d\n", shineID);
GameDataFile::HintInfo* shineInfo = CustomGameDataFunction::getHintInfoByUniqueID(accessor, shineID);
if (shineInfo) {
if (!GameDataFunction::isGotShine(accessor, shineInfo->mStageName.cstr(), shineInfo->mObjId.cstr())) {
Shine* stageShine = findStageShine(shineID);
if (stageShine) {
if (al::isDead(stageShine)) {
stageShine->makeActorAlive();
}
stageShine->getDirect();
stageShine->onSwitchGet();
}
accessor.mData->mGameDataFile->setGotShine(shineInfo);
}
}
}
sInstance->resetCollectedShines();
sInstance->mCurStageScene->mSceneLayout->startShineCountAnim(false);
sInstance->mCurStageScene->mSceneLayout->updateCounterParts(); // updates shine chip layout to (maybe) prevent softlocks
}
/**
* @brief
*
*/
void Client::update() {
if (sInstance) {
sInstance->mPuppetHolder->update();
if (isNeedUpdateShines()) {
updateShines();
}
GameModeManager::instance()->update();
}
}
/**
* @brief
*
*/
void Client::clearArrays() {
if(sInstance) {
sInstance->mPuppetHolder->clearPuppets();
sInstance->mShineArray.clear();
}
}
/**
* @brief
*
* @return PuppetInfo*
*/
PuppetInfo *Client::getDebugPuppetInfo() {
if(sInstance) {
return &sInstance->mDebugPuppetInfo;
}else {
return nullptr;
}
}
/**
* @brief
*
* @return PuppetActor*
*/
PuppetActor *Client::getDebugPuppet() {
if(sInstance) {
return sInstance->mPuppetHolder->getDebugPuppet();
}else {
return nullptr;
}
}
/**
* @brief
*
* @return Keyboard*
*/
Keyboard* Client::getKeyboard() {
if (sInstance) {
return sInstance->mKeyboard;
}
return nullptr;
}
/**
* @brief
*
* @return const char*
*/
const char* Client::getCurrentIP() {
if (sInstance) {
return sInstance->mServerIP.cstr();
}
return nullptr;
}
/**
* @brief
*
* @return const int
*/
const int Client::getCurrentPort() {
if (sInstance) {
return sInstance->mServerPort;
}
return -1;
}
/**
* @brief sets server IP to supplied string, used specifically for loading IP from the save file.
*
* @param ip
*/
void Client::setLastUsedIP(const char* ip) {
if (sInstance) {
sInstance->mServerIP = ip;
}
}
/**
* @brief sets server port to supplied string, used specifically for loading port from the save file.
*
* @param port
*/
void Client::setLastUsedPort(const int port) {
if (sInstance) {
sInstance->mServerPort = port;
}
}
/**
* @brief creates new scene info and copies supplied info to the new info, as well as stores a const ptr to the current stage scene.
*
* @param initInfo
* @param stageScene
*/
void Client::setSceneInfo(const al::ActorInitInfo& initInfo, const StageScene *stageScene) {
if (!sInstance) {
Logger::log("Client Null!\n");
return;
}
sInstance->mSceneInfo = new al::ActorSceneInfo();
memcpy(sInstance->mSceneInfo, &initInfo.mActorSceneInfo, sizeof(al::ActorSceneInfo));
sInstance->mCurStageScene = stageScene;
}
/**
* @brief stores shine pointer supplied into a ptr array if space is available, and shine is not collected.
*
* @param shine
* @return true if shine was able to be successfully stored
* @return false if shine is already collected, or ptr array is full
*/
bool Client::tryRegisterShine(Shine* shine) {
if (sInstance) {
if (!sInstance->mShineArray.isFull()) {
if (!shine->isGot()) {
sInstance->mShineArray.pushBack(shine);
return true;
}
}
}
return false;
}
/**
* @brief finds the actor pointer stored in the shine ptr array based off shine ID
*
* @param shineID Unique ID used for shine actor
* @return Shine* if shine ptr array contains actor with supplied shine ID.
*/
Shine* Client::findStageShine(int shineID) {
if (sInstance) {
for (int i = 0; i < sInstance->mShineArray.size(); i++) {
Shine* curShine = sInstance->mShineArray[i];
if (curShine) {
auto hintInfo =
CustomGameDataFunction::getHintInfoByIndex(curShine, curShine->mShineIdx);
if (hintInfo->mUniqueID == shineID) {
return curShine;
}
}
}
}
return nullptr;
}
void Client::showConnectError(const char16_t* msg) {
if (!sInstance)
return;
sInstance->mConnectionWait->setTxtMessageConfirm(msg);
al::hidePane(sInstance->mConnectionWait, "Page01"); // hide A button prompt
if (!sInstance->mConnectionWait->mIsAlive) {
sInstance->mConnectionWait->appear();
sInstance->mConnectionWait->playLoop();
}
al::startAction(sInstance->mConnectionWait, "Confirm", "State");
}
void Client::showConnect() {
if (!sInstance)
return;
sInstance->mConnectionWait->appear();
sInstance->mConnectionWait->playLoop();
}
void Client::hideConnect() {
if (!sInstance)
return;
sInstance->mConnectionWait->tryEnd();
}