SuperMarioOdysseyOnline/source/server/Client.cpp
2022-10-29 17:17:38 -07:00

1469 lines
38 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);
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() {
Logger::log("Restarting connection.\n");
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->setQueueOpen(false);
sInstance->mSocket->clearMessageQueues();
sInstance->mSocket->send(playerDC);
sInstance->mHeap->free(playerDC);
if (sInstance->mSocket->closeSocket()) {
Logger::log("Sucessfully Closed Socket.\n");
}
Logger::log("Waiting for send/recv threads to finish.\n");
sInstance->mSocket->waitForThreads();
sInstance->mConnectCount = 0;
Logger::log("Reinitializing connection\n");
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
// 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;
}
case PacketType::UDPINIT: {
UdpInit* initPacket = (UdpInit*)curPacket;
Logger::log("Received udp init packet from server\n");
sInstance->mSocket->setPeerUdpPort(initPacket->port);
sendUdpHolePunch();
sendUdpInit();
break;
}
case PacketType::HOLEPUNCH:
sendUdpHolePunch();
break;
default:
Logger::log("Discarding Unknown Packet Type.\n");
break;
}
mHeap->free(curPacket);
}else { // if false, socket has errored or disconnected, so restart the connection
Logger::log("Client Socket Encountered an Error, restarting connection! Errno: 0x%x\n", mSocket->socket_errno);
this->restartConnection();
}
}
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) {
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));
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;
}
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
*
* @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
* Send a udp holepunch packet to the server
*/
void Client::sendUdpHolePunch() {
if (!sInstance) {
Logger::log("Static Instance is Null!\n");
return;
}
sead::ScopedCurrentHeapSetter setter(sInstance->mHeap);
HolePunch *packet = new HolePunch();
packet->mUserID = sInstance->mUserID;
sInstance->mSocket->queuePacket(packet);
}
/**
* @brief
* Send a udp init packet to server
*/
void Client::sendUdpInit() {
if (!sInstance) {
Logger::log("Static Instance is Null!\n");
return;
}
sead::ScopedCurrentHeapSetter setter(sInstance->mHeap);
UdpInit *packet = new UdpInit();
packet->mUserID = sInstance->mUserID;
packet->port = sInstance->mSocket->getLocalUdpPort();
sInstance->mSocket->queuePacket(packet);
}
/**
* @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();
}