diff options
author | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
---|---|---|
committer | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
commit | 6352e8e3196f78388b6c771073f9e03eaa612673 (patch) | |
tree | e23772f79a7fbc41bc9108951e9e136857484bf4 /source/client/StarClientApplication.cpp | |
parent | 6741a057e5639280d85d0f88ba26f000baa58f61 (diff) |
everything everywhere
all at once
Diffstat (limited to 'source/client/StarClientApplication.cpp')
-rw-r--r-- | source/client/StarClientApplication.cpp | 903 |
1 files changed, 903 insertions, 0 deletions
diff --git a/source/client/StarClientApplication.cpp b/source/client/StarClientApplication.cpp new file mode 100644 index 0000000..ae194ea --- /dev/null +++ b/source/client/StarClientApplication.cpp @@ -0,0 +1,903 @@ +#include "StarClientApplication.hpp" +#include "StarConfiguration.hpp" +#include "StarJsonExtra.hpp" +#include "StarFile.hpp" +#include "StarEncode.hpp" +#include "StarLogging.hpp" +#include "StarJsonExtra.hpp" +#include "StarRoot.hpp" +#include "StarVersion.hpp" +#include "StarPlayer.hpp" +#include "StarPlayerStorage.hpp" +#include "StarPlayerLog.hpp" +#include "StarAssets.hpp" +#include "StarWorldTemplate.hpp" +#include "StarWorldClient.hpp" +#include "StarRootLoader.hpp" + +namespace Star { + +Json const AdditionalAssetsSettings = Json::parseJson(R"JSON( + { + "missingImage" : "/assetmissing.png", + "missingAudio" : "/assetmissing.wav" + } + )JSON"); + +Json const AdditionalDefaultConfiguration = Json::parseJson(R"JSON( + { + "configurationVersion" : { + "client" : 8 + }, + + "allowAssetsMismatch" : false, + "vsync" : true, + "limitTextureAtlasSize" : false, + "useMultiTexturing" : true, + "audioChannelSeparation" : [-25, 25], + + "sfxVol" : 100, + "musicVol" : 70, + "windowedResolution" : [1000, 600], + "fullscreenResolution" : [1920, 1080], + "fullscreen" : false, + "borderless" : false, + "maximized" : true, + "zoomLevel" : 3.0, + "speechBubbles" : true, + + "title" : { + "multiPlayerAddress" : "", + "multiPlayerPort" : "", + "multiPlayerAccount" : "" + }, + + "bindings" : { + "PlayerUp" : [ { "type" : "key", "value" : "W", "mods" : [] } ], + "PlayerDown" : [ { "type" : "key", "value" : "S", "mods" : [] } ], + "PlayerLeft" : [ { "type" : "key", "value" : "A", "mods" : [] } ], + "PlayerRight" : [ { "type" : "key", "value" : "D", "mods" : [] } ], + "PlayerJump" : [ { "type" : "key", "value" : "Space", "mods" : [] } ], + "PlayerDropItem" : [ { "type" : "key", "value" : "Q", "mods" : [] } ], + "PlayerInteract" : [ { "type" : "key", "value" : "E", "mods" : [] } ], + "PlayerShifting" : [ { "type" : "key", "value" : "RShift", "mods" : [] }, { "type" : "key", "value" : "LShift", "mods" : [] } ], + "PlayerTechAction1" : [ { "type" : "key", "value" : "F", "mods" : [] } ], + "PlayerTechAction2" : [], + "PlayerTechAction3" : [], + "EmoteBlabbering" : [ { "type" : "key", "value" : "Right", "mods" : ["LCtrl", "LShift"] } ], + "EmoteShouting" : [ { "type" : "key", "value" : "Up", "mods" : ["LCtrl", "LAlt"] } ], + "EmoteHappy" : [ { "type" : "key", "value" : "Up", "mods" : [] } ], + "EmoteSad" : [ { "type" : "key", "value" : "Down", "mods" : [] } ], + "EmoteNeutral" : [ { "type" : "key", "value" : "Left", "mods" : [] } ], + "EmoteLaugh" : [ { "type" : "key", "value" : "Left", "mods" : [ "LCtrl" ] } ], + "EmoteAnnoyed" : [ { "type" : "key", "value" : "Right", "mods" : [] } ], + "EmoteOh" : [ { "type" : "key", "value" : "Right", "mods" : [ "LCtrl" ] } ], + "EmoteOooh" : [ { "type" : "key", "value" : "Down", "mods" : [ "LCtrl" ] } ], + "EmoteBlink" : [ { "type" : "key", "value" : "Up", "mods" : [ "LCtrl" ] } ], + "EmoteWink" : [ { "type" : "key", "value" : "Up", "mods" : ["LCtrl", "LShift"] } ], + "EmoteEat" : [ { "type" : "key", "value" : "Down", "mods" : ["LCtrl", "LShift"] } ], + "EmoteSleep" : [ { "type" : "key", "value" : "Left", "mods" : ["LCtrl", "LShift"] } ], + "ShowLabels" : [ { "type" : "key", "value" : "RAlt", "mods" : [] }, { "type" : "key", "value" : "LAlt", "mods" : [] } ], + "CameraShift" : [ { "type" : "key", "value" : "RCtrl", "mods" : [] }, { "type" : "key", "value" : "LCtrl", "mods" : [] } ], + "TitleBack" : [ { "type" : "key", "value" : "Esc", "mods" : [] } ], + "CinematicSkip" : [ { "type" : "key", "value" : "Esc", "mods" : [] } ], + "CinematicNext" : [ { "type" : "key", "value" : "Right", "mods" : [] }, { "type" : "key", "value" : "Return", "mods" : [] } ], + "GuiClose" : [ { "type" : "key", "value" : "Esc", "mods" : [] } ], + "GuiShifting" : [ { "type" : "key", "value" : "RShift", "mods" : [] }, { "type" : "key", "value" : "LShift", "mods" : [] } ], + "KeybindingCancel" : [ { "type" : "key", "value" : "Esc", "mods" : [] } ], + "KeybindingClear" : [ { "type" : "key", "value" : "Del", "mods" : [] }, { "type" : "key", "value" : "Backspace", "mods" : [] } ], + "ChatPageUp" : [ { "type" : "key", "value" : "PageUp", "mods" : [] } ], + "ChatPageDown" : [ { "type" : "key", "value" : "PageDown", "mods" : [] } ], + "ChatPreviousLine" : [ { "type" : "key", "value" : "Up", "mods" : [] } ], + "ChatNextLine" : [ { "type" : "key", "value" : "Down", "mods" : [] } ], + "ChatSendLine" : [ { "type" : "key", "value" : "Return", "mods" : [] } ], + "ChatBegin" : [ { "type" : "key", "value" : "Return", "mods" : [] } ], + "ChatBeginCommand" : [ { "type" : "key", "value" : "/", "mods" : [] } ], + "ChatStop" : [ { "type" : "key", "value" : "Esc", "mods" : [] } ], + "InterfaceHideHud" : [ { "type" : "key", "value" : "Z", "mods" : [ "LAlt" ] } ], + "InterfaceChangeBarGroup" : [ { "type" : "key", "value" : "X", "mods" : [] } ], + "InterfaceDeselectHands" : [ { "type" : "key", "value" : "Z", "mods" : [] } ], + "InterfaceBar1" : [ { "type" : "key", "value" : "1", "mods" : [] } ], + "InterfaceBar2" : [ { "type" : "key", "value" : "2", "mods" : [] } ], + "InterfaceBar3" : [ { "type" : "key", "value" : "3", "mods" : [] } ], + "InterfaceBar4" : [ { "type" : "key", "value" : "4", "mods" : [] } ], + "InterfaceBar5" : [ { "type" : "key", "value" : "5", "mods" : [] } ], + "InterfaceBar6" : [ { "type" : "key", "value" : "6", "mods" : [] } ], + "InterfaceBar7" : [], + "InterfaceBar8" : [], + "InterfaceBar9" : [], + "InterfaceBar10" : [], + "EssentialBar1" : [ { "type" : "key", "value" : "R", "mods" : [] } ], + "EssentialBar2" : [ { "type" : "key", "value" : "T", "mods" : [] } ], + "EssentialBar3" : [ { "type" : "key", "value" : "Y", "mods" : [] } ], + "EssentialBar4" : [ { "type" : "key", "value" : "N", "mods" : [] } ], + "InterfaceRepeatCommand" : [ { "type" : "key", "value" : "P", "mods" : [] } ], + "InterfaceToggleFullscreen" : [ { "type" : "key", "value" : "F11", "mods" : [] } ], + "InterfaceReload" : [ ], + "InterfaceEscapeMenu" : [ { "type" : "key", "value" : "Esc", "mods" : [] } ], + "InterfaceInventory" : [ { "type" : "key", "value" : "I", "mods" : [] } ], + "InterfaceCodex" : [ { "type" : "key", "value" : "L", "mods" : [] } ], + "InterfaceQuest" : [ { "type" : "key", "value" : "J", "mods" : [] } ], + "InterfaceCrafting" : [ { "type" : "key", "value" : "C", "mods" : [] } ] + } + } + )JSON"); + +void ClientApplication::startup(StringList const& cmdLineArgs) { + RootLoader rootLoader({AdditionalAssetsSettings, AdditionalDefaultConfiguration, String("starbound.log"), LogLevel::Info, false, String("starbound.config")}); + m_root = rootLoader.initOrDie(cmdLineArgs).first; + + Logger::info("Client Version %s (%s) Source ID: %s Protocol: %s", StarVersionString, StarArchitectureString, StarSourceIdentifierString, StarProtocolVersion); + + auto assets = m_root->assets(); + m_minInterfaceScale = assets->json("/interface.config:minInterfaceScale").toInt(); + m_maxInterfaceScale = assets->json("/interface.config:maxInterfaceScale").toInt(); + m_crossoverRes = jsonToVec2F(assets->json("/interface.config:interfaceCrossoverRes")); +} + +void ClientApplication::shutdown() { + m_mainInterface.reset(); + + if (m_universeClient) + m_universeClient->disconnect(); + + if (m_universeServer) { + m_universeServer->stop(); + m_universeServer->join(); + m_universeServer.reset(); + } + + if (m_statistics) { + m_statistics->writeStatistics(); + m_statistics.reset(); + } + + m_universeClient.reset(); + m_statistics.reset(); +} + +void ClientApplication::applicationInit(ApplicationControllerPtr appController) { + Application::applicationInit(appController); + + appController->setCursorVisible(false); + + AudioFormat audioFormat = appController->enableAudio(); + m_mainMixer = make_shared<MainMixer>(audioFormat.sampleRate, audioFormat.channels); + + m_mainMixer->setVolume(0.5); + + m_guiContext = make_shared<GuiContext>(m_mainMixer->mixer(), appController); + + appController->setTargetUpdateRate(1.0f / WorldTimestep); + + auto configuration = m_root->configuration(); + bool vsync = configuration->get("vsync").toBool(); + Vec2U windowedSize = jsonToVec2U(configuration->get("windowedResolution")); + Vec2U fullscreenSize = jsonToVec2U(configuration->get("fullscreenResolution")); + bool fullscreen = configuration->get("fullscreen").toBool(); + bool borderless = configuration->get("borderless").toBool(); + bool maximized = configuration->get("maximized").toBool(); + + appController->setApplicationTitle(m_root->assets()->json("/client.config:windowTitle").toString()); + appController->setVSyncEnabled(vsync); + + if (fullscreen) + appController->setFullscreenWindow(fullscreenSize); + else if (borderless) + appController->setBorderlessWindow(); + else if (maximized) + appController->setMaximizedWindow(); + else + appController->setNormalWindow(windowedSize); + + appController->setMaxFrameSkip(m_root->assets()->json("/client.config:maxFrameSkip").toUInt()); + appController->setUpdateTrackWindow(m_root->assets()->json("/client.config:updateTrackWindow").toFloat()); +} + +void ClientApplication::renderInit(RendererPtr renderer) { + Application::renderInit(renderer); + + String rendererConfig = strf("/rendering/%s.config", renderer->rendererId()); + if (m_root->assets()->assetExists(rendererConfig)) + renderer->setEffectConfig(m_root->assets()->json(rendererConfig)); + else + Logger::warn("No rendering config found for renderer with id '%s'", renderer->rendererId()); + + if (m_root->configuration()->get("limitTextureAtlasSize").optBool().value(false)) + renderer->setSizeLimitEnabled(true); + + renderer->setMultiTexturingEnabled(m_root->configuration()->get("useMultiTexturing").optBool().value(true)); + + m_guiContext->renderInit(renderer); + + m_cinematicOverlay = make_shared<Cinematic>(); + m_errorScreen = make_shared<ErrorScreen>(); + + if (m_titleScreen) + m_titleScreen->renderInit(renderer); + if (m_worldPainter) + m_worldPainter->renderInit(renderer); + + changeState(MainAppState::Mods); +} + +void ClientApplication::windowChanged(WindowMode windowMode, Vec2U screenSize) { + auto config = m_root->configuration(); + if (windowMode == WindowMode::Fullscreen) { + config->set("fullscreenResolution", jsonFromVec2U(screenSize)); + config->set("fullscreen", true); + config->set("borderless", false); + } else if (windowMode == WindowMode::Borderless) { + config->set("borderless", true); + config->set("fullscreen", false); + } else if (windowMode == WindowMode::Maximized) { + config->set("maximized", true); + config->set("fullscreen", false); + config->set("borderless", false); + config->set("windowedResolution", jsonFromVec2U(screenSize)); + } else { + config->set("maximized", false); + config->set("fullscreen", false); + config->set("borderless", false); + config->set("windowedResolution", jsonFromVec2U(screenSize)); + } +} + +void ClientApplication::processInput(InputEvent const& event) { + if (auto keyDown = event.ptr<KeyDownEvent>()) { + m_heldKeyEvents.append(*keyDown); + m_edgeKeyEvents.append(*keyDown); + } else if (auto keyUp = event.ptr<KeyUpEvent>()) { + eraseWhere(m_heldKeyEvents, [&](auto& keyEvent) { + return keyEvent.key == keyUp->key; + }); + + Maybe<KeyMod> modKey = KeyModNames.maybeLeft(KeyNames.getRight(keyUp->key)); + if (modKey) + m_heldKeyEvents.transform([&](auto& keyEvent) { + return KeyDownEvent{keyEvent.key, keyEvent.mods & ~*modKey}; + }); + } + + if (m_state == MainAppState::Splash) { + m_cinematicOverlay->handleInputEvent(event); + + } else if (m_state == MainAppState::ModsWarning || m_state == MainAppState::Error) { + m_errorScreen->handleInputEvent(event); + + } else if (m_state == MainAppState::Title) { + if (!m_cinematicOverlay->handleInputEvent(event)) + m_titleScreen->handleInputEvent(event); + + } else if (m_state == MainAppState::SinglePlayer || m_state == MainAppState::MultiPlayer) { + if (!m_cinematicOverlay->handleInputEvent(event)) + m_mainInterface->handleInputEvent(event); + } +} + +void ClientApplication::update() { + if (m_state >= MainAppState::Title) { + if (auto p2pNetworkingService = appController()->p2pNetworkingService()) { + if (auto join = p2pNetworkingService->pullPendingJoin()) { + m_pendingMultiPlayerConnection = PendingMultiPlayerConnection{join.takeValue(), {}, {}}; + changeState(MainAppState::Title); + } + + if (auto req = p2pNetworkingService->pullJoinRequest()) + m_mainInterface->queueJoinRequest(*req); + + p2pNetworkingService->update(); + } + } + + if (m_state == MainAppState::Mods) + updateMods(); + else if (m_state == MainAppState::ModsWarning) + updateModsWarning(); + if (m_state == MainAppState::Splash) + updateSplash(); + else if (m_state == MainAppState::Error) + updateError(); + else if (m_state == MainAppState::Title) + updateTitle(); + else if (m_state > MainAppState::Title) + updateRunning(); + + m_guiContext->cleanup(); + m_edgeKeyEvents.clear(); +} + +void ClientApplication::render() { + auto config = m_root->configuration(); + auto assets = m_root->assets(); + + if (m_guiContext->windowWidth() >= m_crossoverRes[0] && m_guiContext->windowHeight() >= m_crossoverRes[1]) + m_guiContext->setInterfaceScale(m_maxInterfaceScale); + else + m_guiContext->setInterfaceScale(m_minInterfaceScale); + + if (m_state == MainAppState::Mods || m_state == MainAppState::Splash) { + m_cinematicOverlay->render(); + + } else if (m_state == MainAppState::Title) { + m_titleScreen->render(); + m_cinematicOverlay->render(); + + } else if (m_state > MainAppState::Title) { + if (auto worldClient = m_universeClient->worldClient()) { + if (auto renderer = Application::renderer()) + renderer->setEffectParameter("lightMapEnabled", true); + worldClient->render(m_renderData, TilePainter::BorderTileSize); + m_worldPainter->render(m_renderData); + m_mainInterface->renderInWorldElements(); + if (auto renderer = Application::renderer()) + renderer->setEffectParameter("lightMapEnabled", false); + } + m_mainInterface->render(); + m_cinematicOverlay->render(); + + } else if (m_state == MainAppState::ModsWarning || m_state == MainAppState::Error) { + m_errorScreen->render(); + } +} + +void ClientApplication::getAudioData(int16_t* sampleData, size_t frameCount) { + m_mainMixer->read(sampleData, frameCount); +} + +void ClientApplication::changeState(MainAppState newState) { + MainAppState oldState = m_state; + m_state = newState; + + if (m_state == MainAppState::Quit) + appController()->quit(); + + if (newState == MainAppState::Mods) + m_cinematicOverlay->load(m_root->assets()->json("/cinematics/mods/modloading.cinematic")); + + if (newState == MainAppState::Splash) { + m_cinematicOverlay->load(m_root->assets()->json("/cinematics/splash.cinematic")); + m_rootLoader = Thread::invoke("Async root loader", [this]() { + m_root->fullyLoad(); + }); + } + + if (oldState > MainAppState::Title && m_state <= MainAppState::Title) { + m_mainInterface.reset(); + if (m_universeClient) + m_universeClient->disconnect(); + + if (m_universeServer) { + m_universeServer->stop(); + m_universeServer->join(); + m_universeServer.reset(); + } + m_cinematicOverlay->stop(); + + if (auto p2pNetworkingService = appController()->p2pNetworkingService()) { + p2pNetworkingService->setJoinUnavailable(); + p2pNetworkingService->setAcceptingP2PConnections(false); + } + } + + if (oldState > MainAppState::Title && m_state == MainAppState::Title) + m_titleScreen->resetState(); + + if (oldState >= MainAppState::Title && m_state < MainAppState::Title) { + m_playerStorage.reset(); + + if (m_statistics) { + m_statistics->writeStatistics(); + m_statistics.reset(); + } + + m_universeClient.reset(); + m_mainMixer->setUniverseClient({}); + m_titleScreen.reset(); + } + + if (oldState < MainAppState::Title && m_state >= MainAppState::Title) { + if (m_rootLoader) + m_rootLoader.finish(); + + m_cinematicOverlay->stop(); + + m_playerStorage = make_shared<PlayerStorage>(m_root->toStoragePath("player")); + m_statistics = make_shared<Statistics>(m_root->toStoragePath("player"), appController()->statisticsService()); + m_universeClient = make_shared<UniverseClient>(m_playerStorage, m_statistics); + m_mainMixer->setUniverseClient(m_universeClient); + m_titleScreen = make_shared<TitleScreen>(m_playerStorage, m_mainMixer->mixer()); + if (auto renderer = Application::renderer()) + m_titleScreen->renderInit(renderer); + } + + if (m_state == MainAppState::Title) { + auto configuration = m_root->configuration(); + + if (m_pendingMultiPlayerConnection) { + if (auto address = m_pendingMultiPlayerConnection->server.ptr<HostAddressWithPort>()) { + m_titleScreen->setMultiPlayerAddress(toString(address->address())); + m_titleScreen->setMultiPlayerPort(toString(address->port())); + m_titleScreen->setMultiPlayerAccount(configuration->getPath("title.multiPlayerAccount").toString()); + m_titleScreen->goToMultiPlayerSelectCharacter(false); + } else { + m_titleScreen->goToMultiPlayerSelectCharacter(true); + } + } else { + m_titleScreen->setMultiPlayerAddress(configuration->getPath("title.multiPlayerAddress").toString()); + m_titleScreen->setMultiPlayerPort(configuration->getPath("title.multiPlayerPort").toString()); + m_titleScreen->setMultiPlayerAccount(configuration->getPath("title.multiPlayerAccount").toString()); + } + } + + if (m_state > MainAppState::Title) { + if (m_titleScreen->currentlySelectedPlayer()) { + m_player = m_titleScreen->currentlySelectedPlayer(); + } else { + if (auto uuid = m_playerStorage->playerUuidAt(0)) + m_player = m_playerStorage->loadPlayer(*uuid); + + if (!m_player) { + setError("Error loading player!"); + return; + } + } + + m_universeClient->setMainPlayer(m_player); + m_cinematicOverlay->setPlayer(m_player); + + auto assets = m_root->assets(); + String loadingCinematic = assets->json("/client.config:loadingCinematic").toString(); + m_cinematicOverlay->load(assets->json(loadingCinematic)); + if (!m_player->log()->introComplete()) { + String introCinematic = assets->json("/client.config:introCinematic").toString(); + introCinematic = introCinematic.replaceTags(StringMap<String>{{"species", m_player->species()}}); + m_player->setPendingCinematic(Json(introCinematic)); + } else { + m_player->setPendingCinematic(Json()); + } + + if (m_state == MainAppState::MultiPlayer) { + PacketSocketUPtr packetSocket; + + auto multiPlayerConnection = m_pendingMultiPlayerConnection.take(); + + if (auto address = multiPlayerConnection.server.ptr<HostAddressWithPort>()) { + try { + packetSocket = TcpPacketSocket::open(TcpSocket::connectTo(*address)); + } catch (StarException const& e) { + setError(strf("Join failed! Error connecting to '%s'", *address), e); + return; + } + + } else { + auto p2pPeerId = multiPlayerConnection.server.ptr<P2PNetworkingPeerId>(); + + if (auto p2pNetworkingService = appController()->p2pNetworkingService()) { + auto result = p2pNetworkingService->connectToPeer(*p2pPeerId); + if (result.isLeft()) { + setError(strf("Cannot join peer: %s", result.left())); + return; + } else { + packetSocket = P2PPacketSocket::open(move(result.right())); + } + } else { + setError("Internal error, no p2p networking service when joining p2p networking peer"); + return; + } + } + + bool allowAssetsMismatch = m_root->configuration()->get("allowAssetsMismatch").toBool(); + if (auto errorMessage = m_universeClient->connect(UniverseConnection(move(packetSocket)), allowAssetsMismatch, + multiPlayerConnection.account, multiPlayerConnection.password)) { + setError(*errorMessage); + return; + } + + if (auto address = multiPlayerConnection.server.ptr<HostAddressWithPort>()) + m_currentRemoteJoin = *address; + else + m_currentRemoteJoin.reset(); + + } else { + if (!m_universeServer) { + try { + m_universeServer = make_shared<UniverseServer>(m_root->toStoragePath("universe")); + m_universeServer->start(); + } catch (StarException const& e) { + setError("Unable to start local server", e); + return; + } + } + + if (auto errorMessage = m_universeClient->connect(m_universeServer->addLocalClient(), "", "")) { + setError(strf("Error connecting locally: %s", *errorMessage)); + return; + } + } + + m_titleScreen->stopMusic(); + + m_worldPainter = make_shared<WorldPainter>(); + m_mainInterface = make_shared<MainInterface>(m_universeClient, m_worldPainter, m_cinematicOverlay); + + if (auto renderer = Application::renderer()) { + m_worldPainter->renderInit(renderer); + } + } +} + +void ClientApplication::setError(String const& error) { + Logger::error(error.utf8Ptr()); + m_errorScreen->setMessage(error); + changeState(MainAppState::Error); +} + +void ClientApplication::setError(String const& error, std::exception const& e) { + Logger::error("%s\n%s", error, outputException(e, true)); + m_errorScreen->setMessage(strf("%s\n%s", error, outputException(e, false))); + changeState(MainAppState::Error); +} + +void ClientApplication::updateMods() { + m_cinematicOverlay->update(); + auto ugcService = appController()->userGeneratedContentService(); + if (ugcService) { + if (ugcService->triggerContentDownload()) { + StringList modDirectories; + for (auto contentId : ugcService->subscribedContentIds()) { + if (auto contentDirectory = ugcService->contentDownloadDirectory(contentId)) { + Logger::info("Loading mods from user generated content with id '%s' from directory '%s'", contentId, *contentDirectory); + modDirectories.append(*contentDirectory); + } else { + Logger::warn("User generated content with id '%s' is not available", contentId); + } + } + + if (modDirectories.empty()) { + Logger::info("No subscribed user generated content"); + changeState(MainAppState::Splash); + } else { + Logger::info("Reloading to include all user generated content"); + Root::singleton().reloadWithMods(modDirectories); + + auto configuration = m_root->configuration(); + auto assets = m_root->assets(); + + if (configuration->get("modsWarningShown").optBool().value()) { + changeState(MainAppState::Splash); + } else { + configuration->set("modsWarningShown", true); + m_errorScreen->setMessage(assets->json("/interface.config:modsWarningMessage").toString()); + changeState(MainAppState::ModsWarning); + } + } + } + } else { + changeState(MainAppState::Splash); + } +} + +void ClientApplication::updateModsWarning() { + m_errorScreen->update(); + + if (m_errorScreen->accepted()) + changeState(MainAppState::Splash); +} + +void ClientApplication::updateSplash() { + m_cinematicOverlay->update(); + if (!m_rootLoader.isRunning() && (m_cinematicOverlay->completable() || m_cinematicOverlay->completed())) + changeState(MainAppState::Title); +} + +void ClientApplication::updateError() { + m_errorScreen->update(); + + if (m_errorScreen->accepted()) + changeState(MainAppState::Title); +} + +void ClientApplication::updateTitle() { + m_cinematicOverlay->update(); + + m_titleScreen->update(); + m_mainMixer->update(); + + appController()->setAcceptingTextInput(m_titleScreen->textInputActive()); + + auto p2pNetworkingService = appController()->p2pNetworkingService(); + if (p2pNetworkingService) + p2pNetworkingService->setActivityData("In Main Menu", {}); + + if (m_titleScreen->currentState() == TitleState::StartSinglePlayer) { + changeState(MainAppState::SinglePlayer); + + } else if (m_titleScreen->currentState() == TitleState::StartMultiPlayer) { + if (!m_pendingMultiPlayerConnection || m_pendingMultiPlayerConnection->server.is<HostAddressWithPort>()) { + auto addressString = m_titleScreen->multiPlayerAddress().trim(); + auto portString = m_titleScreen->multiPlayerPort().trim(); + portString = portString.empty() ? toString(m_root->configuration()->get("gameServerPort").toUInt()) : portString; + if (auto port = maybeLexicalCast<uint16_t>(portString)) { + auto address = HostAddressWithPort::lookup(addressString, *port); + if (address.isLeft()) { + setError(address.left()); + } else { + m_pendingMultiPlayerConnection = PendingMultiPlayerConnection{ + address.right(), + m_titleScreen->multiPlayerAccount(), + m_titleScreen->multiPlayerPassword() + }; + + auto configuration = m_root->configuration(); + configuration->setPath("title.multiPlayerAddress", m_titleScreen->multiPlayerAddress()); + configuration->setPath("title.multiPlayerPort", m_titleScreen->multiPlayerPort()); + configuration->setPath("title.multiPlayerAccount", m_titleScreen->multiPlayerAccount()); + + changeState(MainAppState::MultiPlayer); + } + } else { + setError(strf("invalid port: %s", portString)); + } + } else { + changeState(MainAppState::MultiPlayer); + } + + } else if (m_titleScreen->currentState() == TitleState::Quit) { + changeState(MainAppState::Quit); + } +} + +void ClientApplication::updateRunning() { + try { + auto p2pNetworkingService = appController()->p2pNetworkingService(); + bool clientIPJoinable = m_root->configuration()->get("clientIPJoinable").toBool(); + bool clientP2PJoinable = m_root->configuration()->get("clientP2PJoinable").toBool(); + Maybe<pair<uint16_t, uint16_t>> party = make_pair(m_universeClient->players(), m_universeClient->maxPlayers()); + + if (m_state == MainAppState::MultiPlayer) { + if (p2pNetworkingService) { + p2pNetworkingService->setAcceptingP2PConnections(false); + if (clientP2PJoinable && m_currentRemoteJoin) + p2pNetworkingService->setJoinRemote(*m_currentRemoteJoin); + else + p2pNetworkingService->setJoinUnavailable(); + } + } else { + m_universeServer->setListeningTcp(clientIPJoinable); + if (p2pNetworkingService) { + p2pNetworkingService->setAcceptingP2PConnections(clientP2PJoinable); + if (clientP2PJoinable) { + p2pNetworkingService->setJoinLocal(m_universeServer->maxClients()); + } else { + p2pNetworkingService->setJoinUnavailable(); + party = {}; + } + } + } + + if (p2pNetworkingService) + p2pNetworkingService->setActivityData("In Game", party); + + if (!m_mainInterface->inputFocus() && !m_cinematicOverlay->suppressInput()) { + m_player->setShifting(isActionTaken(InterfaceAction::PlayerShifting)); + + if (isActionTaken(InterfaceAction::PlayerRight)) + m_player->moveRight(); + if (isActionTaken(InterfaceAction::PlayerLeft)) + m_player->moveLeft(); + if (isActionTaken(InterfaceAction::PlayerUp)) + m_player->moveUp(); + if (isActionTaken(InterfaceAction::PlayerDown)) + m_player->moveDown(); + if (isActionTaken(InterfaceAction::PlayerJump)) + m_player->jump(); + + if (isActionTaken(InterfaceAction::PlayerTechAction1)) + m_player->special(1); + if (isActionTaken(InterfaceAction::PlayerTechAction2)) + m_player->special(2); + if (isActionTaken(InterfaceAction::PlayerTechAction3)) + m_player->special(3); + + if (isActionTakenEdge(InterfaceAction::PlayerInteract)) + m_player->beginTrigger(); + else if (!isActionTaken(InterfaceAction::PlayerInteract)) + m_player->endTrigger(); + + if (isActionTakenEdge(InterfaceAction::PlayerDropItem)) + m_player->dropItem(); + + if (isActionTakenEdge(InterfaceAction::EmoteBlabbering)) + m_player->addEmote(HumanoidEmote::Blabbering); + if (isActionTakenEdge(InterfaceAction::EmoteShouting)) + m_player->addEmote(HumanoidEmote::Shouting); + if (isActionTakenEdge(InterfaceAction::EmoteHappy)) + m_player->addEmote(HumanoidEmote::Happy); + if (isActionTakenEdge(InterfaceAction::EmoteSad)) + m_player->addEmote(HumanoidEmote::Sad); + if (isActionTakenEdge(InterfaceAction::EmoteNeutral)) + m_player->addEmote(HumanoidEmote::NEUTRAL); + if (isActionTakenEdge(InterfaceAction::EmoteLaugh)) + m_player->addEmote(HumanoidEmote::Laugh); + if (isActionTakenEdge(InterfaceAction::EmoteAnnoyed)) + m_player->addEmote(HumanoidEmote::Annoyed); + if (isActionTakenEdge(InterfaceAction::EmoteOh)) + m_player->addEmote(HumanoidEmote::Oh); + if (isActionTakenEdge(InterfaceAction::EmoteOooh)) + m_player->addEmote(HumanoidEmote::OOOH); + if (isActionTakenEdge(InterfaceAction::EmoteBlink)) + m_player->addEmote(HumanoidEmote::Blink); + if (isActionTakenEdge(InterfaceAction::EmoteWink)) + m_player->addEmote(HumanoidEmote::Wink); + if (isActionTakenEdge(InterfaceAction::EmoteEat)) + m_player->addEmote(HumanoidEmote::Eat); + if (isActionTakenEdge(InterfaceAction::EmoteSleep)) + m_player->addEmote(HumanoidEmote::Sleep); + } + + auto checkDisconnection = [this]() { + if (!m_universeClient->isConnected()) { + m_cinematicOverlay->stop(); + String errMessage; + if (auto disconnectReason = m_universeClient->disconnectReason()) + errMessage = strf("You were disconnected from the server for the following reason:\n%s", *disconnectReason); + else + errMessage = "Client-server connection no longer valid!"; + Logger::error(errMessage.utf8Ptr()); + m_errorScreen->setMessage(errMessage); + changeState(MainAppState::Error); + return true; + } + + return false; + }; + + if (checkDisconnection()) + return; + + m_universeClient->update(); + + if (checkDisconnection()) + return; + + if (auto worldClient = m_universeClient->worldClient()) + worldClient->setInteractiveHighlightMode(isActionTaken(InterfaceAction::ShowLabels)); + + updateCamera(); + + m_cinematicOverlay->update(); + m_mainInterface->update(); + m_mainMixer->update(m_cinematicOverlay->muteSfx(), m_cinematicOverlay->muteMusic()); + + appController()->setAcceptingTextInput(m_mainInterface->textInputActive()); + + for (auto const& interactAction : m_player->pullInteractActions()) + m_mainInterface->handleInteractAction(interactAction); + + if (m_universeServer) { + if (auto p2pNetworkingService = appController()->p2pNetworkingService()) { + for (auto& p2pClient : p2pNetworkingService->acceptP2PConnections()) + m_universeServer->addClient(UniverseConnection(P2PPacketSocket::open(move(p2pClient)))); + } + + m_universeServer->setPause(m_mainInterface->escapeDialogOpen()); + } + + Vec2F aimPosition = m_player->aimPosition(); + LogMap::set("render_fps", appController()->renderFps()); + LogMap::set("update_rate", appController()->updateRate()); + LogMap::set("player_pos", strf("%4.2f %4.2f", m_player->position()[0], m_player->position()[1])); + LogMap::set("player_vel", strf("%4.2f %4.2f", m_player->velocity()[0], m_player->velocity()[1])); + LogMap::set("player_aim", strf("%4.2f %4.2f", aimPosition[0], aimPosition[1])); + if (m_universeClient->worldClient()) { + LogMap::set("liquid_level", strf("%d", m_universeClient->worldClient()->liquidLevel(Vec2I::floor(aimPosition)).level)); + LogMap::set("dungeonId", strf("%d", m_universeClient->worldClient()->dungeonId(Vec2I::floor(aimPosition)))); + } + + if (m_mainInterface->currentState() == MainInterface::ReturnToTitle) + changeState(MainAppState::Title); + + } catch (std::exception& e) { + setError("Exception caught in client main-loop", e); + } +} + +bool ClientApplication::isActionTaken(InterfaceAction action) const { + for (auto keyEvent : m_heldKeyEvents) { + if (m_guiContext->actions(keyEvent).contains(action)) + return true; + } + + return false; +} + +bool ClientApplication::isActionTakenEdge(InterfaceAction action) const { + for (auto keyEvent : m_edgeKeyEvents) { + if (m_guiContext->actions(keyEvent).contains(action)) + return true; + } + + return false; +} + +void ClientApplication::updateCamera() { + if (!m_universeClient->worldClient()) + return; + + if (m_mainInterface->fixedCamera()) + return; + + auto assets = m_root->assets(); + auto camera = m_worldPainter->camera(); + + const float triggerRadius = 100.0f; + const float deadzone = 0.1f; + const float smoothFactor = 30.0f; + const float panFactor = 1.5f; + + auto playerCameraPosition = m_player->cameraPosition(); + + if (isActionTaken(InterfaceAction::CameraShift)) { + m_snapBackCameraOffset = false; + m_cameraOffsetDownTicks++; + Vec2F aim = m_universeClient->worldClient()->geometry().diff(m_mainInterface->cursorWorldPosition(), playerCameraPosition); + + float magnitude = aim.magnitude() / (triggerRadius / camera.pixelRatio()); + if (magnitude > deadzone) { + float cameraXOffset = aim.x() / magnitude; + float cameraYOffset = aim.y() / magnitude; + magnitude = (magnitude - deadzone) / (1.0 - deadzone); + if (magnitude > 1) + magnitude = 1; + cameraXOffset *= magnitude * 0.5f * camera.pixelRatio() * panFactor; + cameraYOffset *= magnitude * 0.5f * camera.pixelRatio() * panFactor; + m_cameraXOffset = (m_cameraXOffset * (smoothFactor - 1.0) + cameraXOffset) / smoothFactor; + m_cameraYOffset = (m_cameraYOffset * (smoothFactor - 1.0) + cameraYOffset) / smoothFactor; + } + } else { + if ((m_cameraOffsetDownTicks > 0) && (m_cameraOffsetDownTicks < 20)) + m_snapBackCameraOffset = true; + if (m_snapBackCameraOffset) { + m_cameraXOffset = (m_cameraXOffset * (smoothFactor - 1.0)) / smoothFactor; + m_cameraYOffset = (m_cameraYOffset * (smoothFactor - 1.0)) / smoothFactor; + } + m_cameraOffsetDownTicks = 0; + } + Vec2F newCameraPosition; + + newCameraPosition.setX(playerCameraPosition.x()); + newCameraPosition.setY(playerCameraPosition.y()); + + auto baseCamera = newCameraPosition; + + const float cameraSmoothRadius = assets->json("/interface.config:cameraSmoothRadius").toFloat(); + const float cameraSmoothFactor = assets->json("/interface.config:cameraSmoothFactor").toFloat(); + + auto cameraSmoothDistance = m_universeClient->worldClient()->geometry().diff(m_cameraPositionSmoother, newCameraPosition).magnitude(); + if (cameraSmoothDistance > cameraSmoothRadius) { + auto cameraDelta = m_universeClient->worldClient()->geometry().diff(m_cameraPositionSmoother, newCameraPosition); + m_cameraPositionSmoother = newCameraPosition + cameraDelta.normalized() * cameraSmoothRadius; + m_cameraSmoothDelta = {}; + } + + auto cameraDelta = m_universeClient->worldClient()->geometry().diff(m_cameraPositionSmoother, newCameraPosition); + if (cameraDelta.magnitude() > assets->json("/interface.config:cameraSmoothDeadzone").toFloat()) + newCameraPosition = newCameraPosition + cameraDelta * (cameraSmoothFactor - 1.0) / cameraSmoothFactor; + m_cameraPositionSmoother = newCameraPosition; + + newCameraPosition.setX(newCameraPosition.x() + m_cameraXOffset / camera.pixelRatio()); + newCameraPosition.setY(newCameraPosition.y() + m_cameraYOffset / camera.pixelRatio()); + + auto smoothDelta = newCameraPosition - baseCamera; + + m_worldPainter->setCameraPosition(m_universeClient->worldClient()->geometry(), baseCamera + (smoothDelta + m_cameraSmoothDelta) * 0.5f); + m_cameraSmoothDelta = smoothDelta; + camera = m_worldPainter->camera(); + + m_universeClient->worldClient()->setClientWindow(camera.worldTileRect()); +} + +} + +STAR_MAIN_APPLICATION(Star::ClientApplication); |