This commit is contained in:
Aria 2025-03-21 22:23:30 +11:00
commit 9c94d113d3
Signed by untrusted user who does not match committer: aria
GPG key ID: 19AB7AA462B8AB3B
10260 changed files with 1237388 additions and 0 deletions

View file

@ -0,0 +1,31 @@
INCLUDE_DIRECTORIES (
${STAR_EXTERN_INCLUDES}
${STAR_CORE_INCLUDES}
${STAR_BASE_INCLUDES}
${STAR_GAME_INCLUDES}
${STAR_PLATFORM_INCLUDES}
${STAR_APPLICATION_INCLUDES}
${STAR_RENDERING_INCLUDES}
${STAR_WINDOWING_INCLUDES}
${STAR_FRONTEND_INCLUDES}
)
SET (star_client_HEADERS
StarClientApplication.hpp
)
SET (star_client_SOURCES
StarClientApplication.cpp
)
IF (STAR_SYSTEM_WINDOWS)
SET (star_client_RESOURCES
starbound.rc
)
ENDIF ()
ADD_EXECUTABLE (starbound WIN32
$<TARGET_OBJECTS:star_extern> $<TARGET_OBJECTS:star_core> $<TARGET_OBJECTS:star_base> $<TARGET_OBJECTS:star_game>
$<TARGET_OBJECTS:star_application> $<TARGET_OBJECTS:star_rendering> $<TARGET_OBJECTS:star_windowing> $<TARGET_OBJECTS:star_frontend>
${star_client_HEADERS} ${star_client_SOURCES} ${star_client_RESOURCES})
TARGET_LINK_LIBRARIES (starbound ${STAR_EXT_LIBS} ${STAR_EXT_GUI_LIBS})

View file

@ -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);

View file

@ -0,0 +1,117 @@
#ifndef STAR_CLIENT_APPLICATION_HPP
#define STAR_CLIENT_APPLICATION_HPP
#include "StarUniverseServer.hpp"
#include "StarUniverseClient.hpp"
#include "StarWorldPainter.hpp"
#include "StarGameTypes.hpp"
#include "StarMainInterface.hpp"
#include "StarMainMixer.hpp"
#include "StarTitleScreen.hpp"
#include "StarErrorScreen.hpp"
#include "StarCinematic.hpp"
#include "StarKeyBindings.hpp"
#include "StarMainApplication.hpp"
namespace Star {
class ClientApplication : public Application {
protected:
virtual void startup(StringList const& cmdLineArgs) override;
virtual void shutdown() override;
virtual void applicationInit(ApplicationControllerPtr appController) override;
virtual void renderInit(RendererPtr renderer) override;
virtual void windowChanged(WindowMode windowMode, Vec2U screenSize) override;
virtual void processInput(InputEvent const& event) override;
virtual void update() override;
virtual void render() override;
virtual void getAudioData(int16_t* stream, size_t len) override;
private:
enum class MainAppState {
Quit,
Startup,
Mods,
ModsWarning,
Splash,
Error,
Title,
SinglePlayer,
MultiPlayer
};
struct PendingMultiPlayerConnection {
Variant<P2PNetworkingPeerId, HostAddressWithPort> server;
String account;
String password;
};
void changeState(MainAppState newState);
void setError(String const& error);
void setError(String const& error, std::exception const& e);
void updateMods();
void updateModsWarning();
void updateSplash();
void updateError();
void updateTitle();
void updateRunning();
bool isActionTaken(InterfaceAction action) const;
bool isActionTakenEdge(InterfaceAction action) const;
void updateCamera();
RootUPtr m_root;
ThreadFunction<void> m_rootLoader;
MainAppState m_state = MainAppState::Startup;
// Valid after applicationInit is called
MainMixerPtr m_mainMixer;
GuiContextPtr m_guiContext;
// Valid after renderInit is called the first time
CinematicPtr m_cinematicOverlay;
ErrorScreenPtr m_errorScreen;
// Valid if main app state >= Title
PlayerStoragePtr m_playerStorage;
StatisticsPtr m_statistics;
UniverseClientPtr m_universeClient;
TitleScreenPtr m_titleScreen;
// Valid if main app state > Title
PlayerPtr m_player;
WorldPainterPtr m_worldPainter;
WorldRenderData m_renderData;
MainInterfacePtr m_mainInterface;
// Valid if main app state == SinglePlayer
UniverseServerPtr m_universeServer;
float m_cameraXOffset = 0.0f;
float m_cameraYOffset = 0.0f;
bool m_snapBackCameraOffset = false;
int m_cameraOffsetDownTicks = 0;
Vec2F m_cameraPositionSmoother;
Vec2F m_cameraSmoothDelta;
int m_minInterfaceScale = 2;
int m_maxInterfaceScale = 3;
Vec2F m_crossoverRes;
List<KeyDownEvent> m_heldKeyEvents;
List<KeyDownEvent> m_edgeKeyEvents;
Maybe<PendingMultiPlayerConnection> m_pendingMultiPlayerConnection;
Maybe<HostAddressWithPort> m_currentRemoteJoin;
};
}
#endif

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!--Windows 7-->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
<!--Windows Vista-->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
</application>
</compatibility>
</assembly>

BIN
source/client/starbound.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

View file

@ -0,0 +1,25 @@
1 VERSIONINFO
FILEVERSION 0,9,0,0
PRODUCTVERSION 0,9,0,0
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904E4"
BEGIN
VALUE "CompanyName", "Chucklefish LTD"
VALUE "FileDescription", "Starbound"
VALUE "FileVersion", "0.9beta"
VALUE "InternalName", "starbound"
VALUE "LegalCopyright", "Chucklefish LTD"
VALUE "OriginalFilename", "starbound.exe"
VALUE "ProductName", "Starbound"
VALUE "ProductVersion", "0.9beta"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1252
END
END
icon ICON "starbound-largelogo.ico"