Веб-сайт самохостера Lotigara

summaryrefslogtreecommitdiff
path: root/source/game/StarUniverseClient.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/game/StarUniverseClient.cpp')
-rw-r--r--source/game/StarUniverseClient.cpp553
1 files changed, 553 insertions, 0 deletions
diff --git a/source/game/StarUniverseClient.cpp b/source/game/StarUniverseClient.cpp
new file mode 100644
index 0000000..dcfd0c7
--- /dev/null
+++ b/source/game/StarUniverseClient.cpp
@@ -0,0 +1,553 @@
+#include "StarUniverseClient.hpp"
+#include "StarLexicalCast.hpp"
+#include "StarJsonExtra.hpp"
+#include "StarLogging.hpp"
+#include "StarVersion.hpp"
+#include "StarRoot.hpp"
+#include "StarConfiguration.hpp"
+#include "StarPlayerStorage.hpp"
+#include "StarPlayer.hpp"
+#include "StarPlayerLog.hpp"
+#include "StarAssets.hpp"
+#include "StarTime.hpp"
+#include "StarNetPackets.hpp"
+#include "StarTcp.hpp"
+#include "StarWorldClient.hpp"
+#include "StarSystemWorldClient.hpp"
+#include "StarClientContext.hpp"
+#include "StarTeamClient.hpp"
+#include "StarSha256.hpp"
+#include "StarEncode.hpp"
+#include "StarPlayerCodexes.hpp"
+#include "StarQuestManager.hpp"
+#include "StarPlayerUniverseMap.hpp"
+#include "StarWorldTemplate.hpp"
+
+namespace Star {
+
+UniverseClient::UniverseClient(PlayerStoragePtr playerStorage, StatisticsPtr statistics) {
+ m_storageTriggerDeadline = 0;
+ m_playerStorage = move(playerStorage);
+ m_statistics = move(statistics);
+ m_pause = false;
+ reset();
+}
+
+UniverseClient::~UniverseClient() {
+ disconnect();
+}
+
+void UniverseClient::setMainPlayer(PlayerPtr player) {
+ if (isConnected())
+ throw StarException("Cannot call UniverseClient::setMainPlayer while connected");
+
+ if (m_mainPlayer) {
+ m_playerStorage->savePlayer(m_mainPlayer);
+ m_mainPlayer->setClientContext({});
+ m_mainPlayer->setStatistics({});
+ }
+
+ m_mainPlayer = player;
+
+ if (m_mainPlayer) {
+ m_mainPlayer->setClientContext(m_clientContext);
+ m_mainPlayer->setStatistics(m_statistics);
+ m_mainPlayer->setUniverseClient(this);
+ m_playerStorage->backupCycle(m_mainPlayer->uuid());
+ m_playerStorage->savePlayer(m_mainPlayer);
+ m_playerStorage->moveToFront(m_mainPlayer->uuid());
+ }
+}
+
+PlayerPtr UniverseClient::mainPlayer() const {
+ return m_mainPlayer;
+}
+
+Maybe<String> UniverseClient::connect(UniverseConnection connection, bool allowAssetsMismatch, String const& account, String const& password) {
+ auto& root = Root::singleton();
+ auto assets = root.assets();
+
+ reset();
+ m_disconnectReason = {};
+
+ if (!m_mainPlayer)
+ throw StarException("Cannot call UniverseClient::connect with no main player");
+
+ unsigned timeout = assets->json("/client.config:serverConnectTimeout").toUInt();
+
+ connection.pushSingle(make_shared<ProtocolRequestPacket>(StarProtocolVersion));
+ connection.sendAll(timeout);
+ connection.receiveAny(timeout);
+
+ auto protocolResponsePacket = as<ProtocolResponsePacket>(connection.pullSingle());
+ if (!protocolResponsePacket)
+ return String("Join failed! Timeout while establishing connection.");
+ else if (!protocolResponsePacket->allowed)
+ return String(strf("Join failed! Server does not support connections with protocol version %s", StarProtocolVersion));
+
+ connection.pushSingle(make_shared<ClientConnectPacket>(Root::singleton().assets()->digest(), allowAssetsMismatch, m_mainPlayer->uuid(), m_mainPlayer->name(),
+ m_mainPlayer->species(), m_playerStorage->loadShipData(m_mainPlayer->uuid()), ShipUpgrades(m_mainPlayer->shipUpgrades()),
+ m_mainPlayer->log()->introComplete(), account));
+ connection.sendAll(timeout);
+
+ connection.receiveAny(timeout);
+ auto packet = connection.pullSingle();
+ if (auto challenge = as<HandshakeChallengePacket>(packet)) {
+ Logger::info("UniverseClient: Sending Handshake Response");
+ ByteArray passAccountSalt = (password + account).utf8Bytes();
+ passAccountSalt.append(challenge->passwordSalt);
+ ByteArray passHash = Star::sha256(passAccountSalt);
+
+ connection.pushSingle(make_shared<HandshakeResponsePacket>(passHash));
+ connection.sendAll(timeout);
+
+ connection.receiveAny(timeout);
+ packet = connection.pullSingle();
+ }
+
+ if (auto success = as<ConnectSuccessPacket>(packet)) {
+ m_universeClock = make_shared<Clock>();
+ m_clientContext = make_shared<ClientContext>(success->serverUuid);
+ m_teamClient = make_shared<TeamClient>(m_mainPlayer, m_clientContext);
+ m_mainPlayer->setClientContext(m_clientContext);
+ m_mainPlayer->setStatistics(m_statistics);
+ m_worldClient = make_shared<WorldClient>(m_mainPlayer);
+
+ m_connection = move(connection);
+ m_celestialDatabase = make_shared<CelestialSlaveDatabase>(move(success->celestialInformation));
+ m_systemWorldClient = make_shared<SystemWorldClient>(m_universeClock, m_celestialDatabase, m_mainPlayer->universeMap());
+
+ Logger::info("UniverseClient: Joined server as client %s", success->clientId);
+ return {};
+ } else if (auto failure = as<ConnectFailurePacket>(packet)) {
+ Logger::error("UniverseClient: Join failed: %s", failure->reason);
+ return failure->reason;
+ } else {
+ Logger::error("UniverseClient: Join failed! No server response received");
+ return String("Join failed! No server response received");
+ }
+}
+
+bool UniverseClient::isConnected() const {
+ return m_connection && m_connection->isOpen();
+}
+
+void UniverseClient::disconnect() {
+ auto assets = Root::singleton().assets();
+ int timeout = assets->json("/client.config:serverDisconnectTimeout").toInt();
+
+ if (isConnected()) {
+ Logger::info("UniverseClient: Client disconnecting...");
+ m_connection->pushSingle(make_shared<ClientDisconnectRequestPacket>());
+ }
+
+ // Try to handle all the shutdown packets before returning.
+ while (m_connection) {
+ m_connection->sendAll(timeout);
+ if (m_connection->receiveAny(timeout))
+ handlePackets(m_connection->pull());
+ else
+ break;
+ }
+
+ reset();
+ m_mainPlayer = {};
+}
+
+Maybe<String> UniverseClient::disconnectReason() const {
+ return m_disconnectReason;
+}
+
+WorldClientPtr UniverseClient::worldClient() const {
+ return m_worldClient;
+}
+
+SystemWorldClientPtr UniverseClient::systemWorldClient() const {
+ return m_systemWorldClient;
+}
+
+void UniverseClient::update() {
+ auto assets = Root::singleton().assets();
+
+ if (!isConnected())
+ return;
+
+ if (!m_warping && !m_pendingWarp) {
+ if (auto playerWarp = m_mainPlayer->pullPendingWarp())
+ warpPlayer(parseWarpAction(playerWarp->action), (bool)playerWarp->animation, playerWarp->animation.value("default"), playerWarp->deploy);
+ }
+
+ if (m_pendingWarp) {
+ if ((m_warping && !m_mainPlayer->isTeleportingOut()) || (!m_warping && m_warpDelay.tick())) {
+ m_connection->pushSingle(make_shared<PlayerWarpPacket>(take(m_pendingWarp), m_mainPlayer->isDeploying()));
+ m_warpDelay.reset();
+ if (m_warping) {
+ m_warpCinemaCancelTimer = GameTimer(assets->json("/client.config:playerWarpCinemaMinimumTime").toFloat());
+ String cinematic;
+ if (m_mainPlayer->isDeploying())
+ cinematic = assets->json("/client.config:deployCinematic").toString();
+ else
+ cinematic = assets->json("/client.config:warpCinematic").toString();
+ cinematic = cinematic.replaceTags(StringMap<String>{{"species", m_mainPlayer->species()}});
+ m_mainPlayer->setPendingCinematic(Json(move(cinematic)));
+ }
+ }
+ }
+
+ // Don't cancel the warp cinema until at LEAST the
+ // playerWarpCinemaMinimumTime has passed, even if warping is faster than
+ // that.
+ if (m_warpCinemaCancelTimer) {
+ m_warpCinemaCancelTimer->tick();
+ if (m_warpCinemaCancelTimer->ready() && !m_warping) {
+ m_warpCinemaCancelTimer = {};
+ m_mainPlayer->setPendingCinematic(Json());
+ m_mainPlayer->teleportIn();
+ }
+ }
+
+ m_connection->receive();
+ handlePackets(m_connection->pull());
+
+ if (!isConnected())
+ return;
+
+ LogMap::set("universe_time_client", m_universeClock->time());
+
+ m_statistics->update();
+
+ if (!m_pause)
+ m_worldClient->update();
+ m_connection->push(m_worldClient->getOutgoingPackets());
+
+ if (!m_pause)
+ m_systemWorldClient->update();
+ m_connection->push(m_systemWorldClient->pullOutgoingPackets());
+
+ m_teamClient->update();
+
+ auto contextUpdate = m_clientContext->writeUpdate();
+ if (!contextUpdate.empty())
+ m_connection->pushSingle(make_shared<ClientContextUpdatePacket>(move(contextUpdate)));
+
+ auto celestialRequests = m_celestialDatabase->pullRequests();
+ if (!celestialRequests.empty())
+ m_connection->pushSingle(make_shared<CelestialRequestPacket>(move(celestialRequests)));
+
+ m_connection->send();
+
+ if (Time::monotonicMilliseconds() >= m_storageTriggerDeadline) {
+ if (m_mainPlayer) {
+ m_playerStorage->savePlayer(m_mainPlayer);
+ m_playerStorage->moveToFront(m_mainPlayer->uuid());
+ }
+
+ m_storageTriggerDeadline = Time::monotonicMilliseconds() + assets->json("/client.config:storageTriggerInterval").toUInt();
+ }
+
+ if (m_respawning) {
+ if (m_respawnTimer.ready()) {
+ if ((playerOnOwnShip() || m_worldClient->respawnInWorld()) && m_worldClient->inWorld()) {
+ m_worldClient->reviveMainPlayer();
+ m_respawning = false;
+ }
+ } else {
+ if (m_respawnTimer.tick()) {
+ String cinematic = assets->json("/client.config:respawnCinematic").toString();
+ cinematic = cinematic.replaceTags(StringMap<String>{
+ {"species", m_mainPlayer->species()},
+ {"mode", PlayerModeNames.getRight(m_mainPlayer->modeType())}
+ });
+ m_mainPlayer->setPendingCinematic(Json(move(cinematic)));
+ if (!m_worldClient->respawnInWorld())
+ m_pendingWarp = WarpAlias::OwnShip;
+ m_warpDelay.reset();
+ }
+ }
+ } else {
+ if (m_worldClient->mainPlayerDead()) {
+ if (m_mainPlayer->modeConfig().permadeath) {
+ // tooo bad....
+ } else {
+ m_respawning = true;
+ m_respawnTimer.reset();
+ }
+ }
+ }
+
+ m_celestialDatabase->cleanup();
+
+ if (auto netStats = m_connection->incomingStats()) {
+ LogMap::set("client_incoming_bps", netStats->bytesPerSecond);
+ LogMap::set("client_worst_incoming", strf("%s:%s", PacketTypeNames.getRight(netStats->worstPacketType), netStats->worstPacketSize));
+ }
+ if (auto netStats = m_connection->outgoingStats()) {
+ LogMap::set("client_outgoing_bps", netStats->bytesPerSecond);
+ LogMap::set("client_worst_outgoing",
+ strf("%s:%s", PacketTypeNames.getRight(netStats->worstPacketType), netStats->worstPacketSize));
+ }
+}
+
+Maybe<BeamUpRule> UniverseClient::beamUpRule() const {
+ if (auto worldTemplate = currentTemplate())
+ if (auto parameters = worldTemplate->worldParameters())
+ return parameters->beamUpRule;
+
+ return {};
+}
+
+bool UniverseClient::canBeamUp() const {
+ auto playerWorldId = m_clientContext->playerWorldId();
+
+ if (playerWorldId.empty() || playerWorldId.is<ClientShipWorldId>())
+ return false;
+ if (m_mainPlayer->isAdmin())
+ return true;
+ if (m_mainPlayer->isDead() || m_mainPlayer->isTeleporting())
+ return false;
+
+ auto beamUp = beamUpRule();
+ if (beamUp == BeamUpRule::Anywhere || beamUp == BeamUpRule::AnywhereWithWarning)
+ return true;
+ else if (beamUp == BeamUpRule::Surface)
+ return mainPlayer()->modeConfig().allowBeamUpUnderground || mainPlayer()->isOutside();
+
+ return false;
+}
+
+bool UniverseClient::canBeamDown(bool deploy) const {
+ if (!m_clientContext->orbitWarpAction() || flying())
+ return false;
+ if (auto warpAction = m_clientContext->orbitWarpAction()) {
+ if (!deploy && warpAction->second == WarpMode::DeployOnly)
+ return false;
+ else if (deploy && (warpAction->second == WarpMode::BeamOnly || !m_mainPlayer->canDeploy()))
+ return false;
+ }
+ if (m_mainPlayer->isAdmin())
+ return true;
+ if (m_mainPlayer->isDead() || m_mainPlayer->isTeleporting() || !m_clientContext->shipUpgrades().capabilities.contains("teleport"))
+ return false;
+ return true;
+}
+
+bool UniverseClient::canBeamToTeamShip() const {
+ auto playerWorldId = m_clientContext->playerWorldId();
+ if (playerWorldId.empty())
+ return false;
+
+ if (m_mainPlayer->isAdmin())
+ return true;
+
+ if (canBeamUp())
+ return true;
+
+ if (playerWorldId.is<ClientShipWorldId>() && m_clientContext->shipUpgrades().capabilities.contains("teleport"))
+ return true;
+
+ return false;
+}
+
+bool UniverseClient::canTeleport() const {
+ if (m_mainPlayer->isAdmin())
+ return true;
+
+ if (m_clientContext->playerWorldId().is<ClientShipWorldId>())
+ return !flying() && m_clientContext->shipUpgrades().capabilities.contains("teleport");
+
+ return m_mainPlayer->canUseTool();
+}
+
+void UniverseClient::warpPlayer(WarpAction const& warpAction, bool animate, String const& animationType, bool deploy) {
+ // don't interrupt teleportation in progress
+ if (m_warping || m_respawning)
+ return;
+
+ m_mainPlayer->stopLounging();
+ if (animate) {
+ m_mainPlayer->teleportOut(animationType, deploy);
+ m_warping = warpAction;
+ m_warpDelay.reset();
+ }
+
+ m_pendingWarp = warpAction;
+}
+
+void UniverseClient::flyShip(Vec3I const& system, SystemLocation const& destination) {
+ m_connection->pushSingle(make_shared<FlyShipPacket>(system, destination));
+}
+
+CelestialDatabasePtr UniverseClient::celestialDatabase() const {
+ return m_celestialDatabase;
+}
+
+CelestialCoordinate UniverseClient::shipCoordinate() const {
+ return m_clientContext->shipCoordinate();
+}
+
+bool UniverseClient::playerOnOwnShip() const {
+ return playerWorld().is<ClientShipWorldId>() && playerWorld().get<ClientShipWorldId>() == mainPlayer()->uuid();
+}
+
+WorldId UniverseClient::playerWorld() const {
+ return m_clientContext->playerWorldId();
+}
+
+bool UniverseClient::isAdmin() const {
+ return m_mainPlayer->isAdmin();
+}
+
+Uuid UniverseClient::teamUuid() const {
+ if (auto team = m_teamClient->currentTeam())
+ return *team;
+ return m_mainPlayer->uuid();
+}
+
+WorldTemplateConstPtr UniverseClient::currentTemplate() const {
+ return m_worldClient->currentTemplate();
+}
+
+SkyConstPtr UniverseClient::currentSky() const {
+ return m_worldClient->currentSky();
+}
+
+bool UniverseClient::flying() const {
+ if (auto sky = currentSky())
+ return sky->flying();
+ return false;
+}
+
+void UniverseClient::sendChat(String const& text, ChatSendMode sendMode) {
+ if (!text.beginsWith("/"))
+ m_mainPlayer->addChatMessage(text);
+ m_connection->pushSingle(make_shared<ChatSendPacket>(text, sendMode));
+}
+
+List<ChatReceivedMessage> UniverseClient::pullChatMessages() {
+ return take(m_pendingMessages);
+}
+
+uint16_t UniverseClient::players() {
+ return m_serverInfo.apply([](auto const& info) { return info.players; }).value(1);
+}
+
+uint16_t UniverseClient::maxPlayers() {
+ return m_serverInfo.apply([](auto const& info) { return info.maxPlayers; }).value(1);
+}
+
+ClockConstPtr UniverseClient::universeClock() const {
+ return m_universeClock;
+}
+
+JsonRpcInterfacePtr UniverseClient::rpcInterface() const {
+ return m_clientContext->rpcInterface();
+}
+
+ClientContextPtr UniverseClient::clientContext() const {
+ return m_clientContext;
+}
+
+TeamClientPtr UniverseClient::teamClient() const {
+ return m_teamClient;
+}
+
+QuestManagerPtr UniverseClient::questManager() const {
+ return m_mainPlayer->questManager();
+}
+
+PlayerStoragePtr UniverseClient::playerStorage() const {
+ return m_playerStorage;
+}
+
+StatisticsPtr UniverseClient::statistics() const {
+ return m_statistics;
+}
+
+bool UniverseClient::paused() const {
+ return m_pause;
+}
+
+void UniverseClient::setPause(bool pause) {
+ m_pause = pause;
+
+ if (pause)
+ m_universeClock->stop();
+ else
+ m_universeClock->start();
+}
+
+void UniverseClient::handlePackets(List<PacketPtr> const& packets) {
+ for (auto const& packet : packets) {
+ if (auto clientContextUpdate = as<ClientContextUpdatePacket>(packet)) {
+ m_clientContext->readUpdate(clientContextUpdate->updateData);
+ m_playerStorage->applyShipUpdates(m_mainPlayer->uuid(), m_clientContext->newShipUpdates());
+ m_mainPlayer->setShipUpgrades(m_clientContext->shipUpgrades());
+ m_mainPlayer->setAdmin(m_clientContext->isAdmin());
+ m_mainPlayer->setTeam(m_clientContext->team());
+ } else if (auto chatReceivePacket = as<ChatReceivePacket>(packet)) {
+ m_pendingMessages.append(chatReceivePacket->receivedMessage);
+
+ } else if (auto universeTimeUpdatePacket = as<UniverseTimeUpdatePacket>(packet)) {
+ m_universeClock->setTime(universeTimeUpdatePacket->universeTime);
+
+ } else if (auto serverDisconnectPacket = as<ServerDisconnectPacket>(packet)) {
+ reset();
+ m_disconnectReason = serverDisconnectPacket->reason;
+ m_mainPlayer = {};
+
+ } else if (auto celestialResponse = as<CelestialResponsePacket>(packet)) {
+ m_celestialDatabase->pushResponses(move(celestialResponse->responses));
+
+ } else if (auto warpResult = as<PlayerWarpResultPacket>(packet)) {
+ if (m_mainPlayer->isDeploying() && m_warping && m_warping->is<WarpToPlayer>()) {
+ Uuid target = m_warping->get<WarpToPlayer>();
+ for (auto member : m_teamClient->members()) {
+ if (member.uuid == target) {
+ if (member.warpMode != WarpMode::DeployOnly && member.warpMode != WarpMode::BeamOrDeploy)
+ m_mainPlayer->deployAbort();
+ break;
+ }
+ }
+ }
+
+ m_warping.reset();
+ if (!warpResult->success) {
+ m_mainPlayer->teleportAbort();
+ if (warpResult->warpActionInvalid)
+ m_mainPlayer->universeMap()->invalidateWarpAction(warpResult->warpAction);
+ }
+ } else if (auto planetTypeUpdate = as<PlanetTypeUpdatePacket>(packet)) {
+ m_celestialDatabase->invalidateCacheFor(planetTypeUpdate->coordinate);
+ } else if (auto pausePacket = as<PausePacket>(packet)) {
+ setPause(pausePacket->pause);
+ } else if (auto serverInfoPacket = as<ServerInfoPacket>(packet)) {
+ m_serverInfo = ServerInfo{serverInfoPacket->players, serverInfoPacket->maxPlayers};
+ } else if (!m_systemWorldClient->handleIncomingPacket(packet)) {
+ // see if the system world will handle it, otherwise pass it along to the world client
+ m_worldClient->handleIncomingPackets({packet});
+ }
+ }
+}
+
+void UniverseClient::reset() {
+ m_universeClock.reset();
+ m_worldClient.reset();
+ m_celestialDatabase.reset();
+ m_clientContext.reset();
+ m_teamClient.reset();
+ m_warping.reset();
+ m_respawning = false;
+
+ auto assets = Root::singleton().assets();
+ m_warpDelay = GameTimer(assets->json("/client.config:playerWarpDelay").toFloat());
+ m_respawnTimer = GameTimer(assets->json("/client.config:playerReviveTime").toFloat());
+
+ if (m_mainPlayer) {
+ m_mainPlayer->setClientContext({});
+ m_playerStorage->savePlayer(m_mainPlayer);
+ }
+
+ m_connection.reset();
+}
+
+}