2023-06-20 04:33:09 +00:00
|
|
|
#include "StarUniverseClient.hpp"
|
|
|
|
#include "StarLexicalCast.hpp"
|
|
|
|
#include "StarJsonExtra.hpp"
|
|
|
|
#include "StarLogging.hpp"
|
|
|
|
#include "StarVersion.hpp"
|
|
|
|
#include "StarRoot.hpp"
|
|
|
|
#include "StarConfiguration.hpp"
|
2023-08-02 11:28:37 +00:00
|
|
|
#include "StarProjectileDatabase.hpp"
|
2023-06-20 04:33:09 +00:00
|
|
|
#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"
|
2023-07-20 07:53:57 +00:00
|
|
|
#include "StarCelestialLuaBindings.hpp"
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
namespace Star {
|
|
|
|
|
|
|
|
UniverseClient::UniverseClient(PlayerStoragePtr playerStorage, StatisticsPtr statistics) {
|
|
|
|
m_storageTriggerDeadline = 0;
|
2024-02-19 15:55:19 +00:00
|
|
|
m_playerStorage = std::move(playerStorage);
|
|
|
|
m_statistics = std::move(statistics);
|
2023-06-20 04:33:09 +00:00
|
|
|
m_pause = false;
|
2023-07-17 12:20:39 +00:00
|
|
|
m_luaRoot = make_shared<LuaRoot>();
|
2023-06-20 04:33:09 +00:00
|
|
|
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();
|
|
|
|
|
2023-07-31 10:22:09 +00:00
|
|
|
{
|
|
|
|
auto protocolRequest = make_shared<ProtocolRequestPacket>(StarProtocolVersion);
|
|
|
|
protocolRequest->setCompressionMode(PacketCompressionMode::Enabled);
|
2024-07-27 04:09:12 +00:00
|
|
|
// Signal that we're OpenStarbound. Vanilla Starbound only compresses
|
|
|
|
// packets above 64 bytes - by forcing it, we can communicate this.
|
2023-07-31 10:22:09 +00:00
|
|
|
connection.pushSingle(protocolRequest);
|
|
|
|
}
|
2023-06-20 04:33:09 +00:00
|
|
|
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)
|
2023-06-27 10:23:44 +00:00
|
|
|
return String(strf("Join failed! Server does not support connections with protocol version {}", StarProtocolVersion));
|
2023-06-20 04:33:09 +00:00
|
|
|
|
2024-07-27 04:09:12 +00:00
|
|
|
if (!(m_legacyServer = protocolResponsePacket->compressionMode() != PacketCompressionMode::Enabled)) {
|
|
|
|
if (auto compressedSocket = as<CompressedPacketSocket>(&connection.packetSocket())) {
|
|
|
|
if (protocolResponsePacket->info) {
|
|
|
|
auto compressionName = protocolResponsePacket->info.getString("compression", "None");
|
|
|
|
auto compressionMode = NetCompressionModeNames.maybeLeft(compressionName);
|
|
|
|
if (!compressionMode)
|
|
|
|
return String(strf("Join failed! Unknown net stream connection type '{}'", compressionName));
|
|
|
|
|
|
|
|
Logger::info("UniverseClient: Using '{}' network stream compression", NetCompressionModeNames.getRight(*compressionMode));
|
|
|
|
compressedSocket->setCompressionStreamEnabled(compressionMode == NetCompressionMode::Zstd);
|
|
|
|
} else if (!m_legacyServer) {
|
|
|
|
Logger::info("UniverseClient: Defaulting to Zstd network stream compression (older server version)");
|
|
|
|
compressedSocket->setCompressionStreamEnabled(true);// old OpenSB server version always expects it!
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
connection.packetSocket().setLegacy(m_legacyServer);
|
2024-08-13 06:23:01 +00:00
|
|
|
auto clientConnect = make_shared<ClientConnectPacket>(Root::singleton().assets()->digest(), allowAssetsMismatch, m_mainPlayer->uuid(), m_mainPlayer->name(),
|
2023-07-17 12:20:39 +00:00
|
|
|
m_mainPlayer->species(), m_playerStorage->loadShipData(m_mainPlayer->uuid()), m_mainPlayer->shipUpgrades(),
|
2024-08-13 06:23:01 +00:00
|
|
|
m_mainPlayer->log()->introComplete(), account);
|
|
|
|
clientConnect->info = JsonObject{ {"brand", "OpenStarbound"} };
|
|
|
|
connection.pushSingle(std::move(clientConnect));
|
2023-06-20 04:33:09 +00:00
|
|
|
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>();
|
2023-07-22 12:31:04 +00:00
|
|
|
m_clientContext = make_shared<ClientContext>(success->serverUuid, m_mainPlayer->uuid());
|
2023-06-20 04:33:09 +00:00
|
|
|
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);
|
2024-04-04 03:35:36 +00:00
|
|
|
m_worldClient->clientState().setLegacy(m_legacyServer);
|
2023-11-24 09:35:45 +00:00
|
|
|
m_worldClient->setAsyncLighting(true);
|
2023-07-04 09:27:16 +00:00
|
|
|
for (auto& pair : m_luaCallbacks)
|
|
|
|
m_worldClient->setLuaCallbacks(pair.first, pair.second);
|
2023-06-20 04:33:09 +00:00
|
|
|
|
2024-02-19 15:55:19 +00:00
|
|
|
m_connection = std::move(connection);
|
|
|
|
m_celestialDatabase = make_shared<CelestialSlaveDatabase>(std::move(success->celestialInformation));
|
2023-06-20 04:33:09 +00:00
|
|
|
m_systemWorldClient = make_shared<SystemWorldClient>(m_universeClock, m_celestialDatabase, m_mainPlayer->universeMap());
|
|
|
|
|
2023-07-31 10:22:09 +00:00
|
|
|
Logger::info("UniverseClient: Joined {} server as client {}", m_legacyServer ? "Starbound" : "OpenStarbound", success->clientId);
|
2023-06-20 04:33:09 +00:00
|
|
|
return {};
|
|
|
|
} else if (auto failure = as<ConnectFailurePacket>(packet)) {
|
2023-06-27 10:23:44 +00:00
|
|
|
Logger::error("UniverseClient: Join failed: {}", failure->reason);
|
2023-06-20 04:33:09 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-03-19 02:35:55 +00:00
|
|
|
GlobalTimescale = 1.0f;
|
2023-06-20 04:33:09 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-07-20 14:58:49 +00:00
|
|
|
void UniverseClient::update(float dt) {
|
2023-06-20 04:33:09 +00:00
|
|
|
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) {
|
2023-07-20 14:58:49 +00:00
|
|
|
if ((m_warping && !m_mainPlayer->isTeleportingOut()) || (!m_warping && m_warpDelay.tick(dt))) {
|
2023-06-20 04:33:09 +00:00
|
|
|
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());
|
2023-06-28 14:13:43 +00:00
|
|
|
|
|
|
|
bool isDeploying = m_mainPlayer->isDeploying();
|
|
|
|
String cinematicJsonPath = isDeploying ? "/client.config:deployCinematic" : "/client.config:warpCinematic";
|
|
|
|
String cinematicAssetPath = assets->json(cinematicJsonPath).toString()
|
|
|
|
.replaceTags(StringMap<String>{{"species", m_mainPlayer->species()}});
|
|
|
|
|
|
|
|
Json cinematic = jsonMerge(assets->json(cinematicJsonPath + "Base"), assets->json(cinematicAssetPath));
|
|
|
|
m_mainPlayer->setPendingCinematic(cinematic);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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();
|
|
|
|
|
2023-07-17 12:20:39 +00:00
|
|
|
if (!m_pause) {
|
2023-07-20 14:58:49 +00:00
|
|
|
m_worldClient->update(dt);
|
2023-07-17 12:20:39 +00:00
|
|
|
for (auto& p : m_scriptContexts)
|
|
|
|
p.second->update();
|
|
|
|
}
|
2023-06-20 04:33:09 +00:00
|
|
|
m_connection->push(m_worldClient->getOutgoingPackets());
|
|
|
|
|
|
|
|
if (!m_pause)
|
2023-07-20 14:58:49 +00:00
|
|
|
m_systemWorldClient->update(dt);
|
2023-06-20 04:33:09 +00:00
|
|
|
m_connection->push(m_systemWorldClient->pullOutgoingPackets());
|
|
|
|
|
|
|
|
m_teamClient->update();
|
|
|
|
|
|
|
|
auto contextUpdate = m_clientContext->writeUpdate();
|
|
|
|
if (!contextUpdate.empty())
|
2024-02-19 15:55:19 +00:00
|
|
|
m_connection->pushSingle(make_shared<ClientContextUpdatePacket>(std::move(contextUpdate)));
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
auto celestialRequests = m_celestialDatabase->pullRequests();
|
|
|
|
if (!celestialRequests.empty())
|
2024-02-19 15:55:19 +00:00
|
|
|
m_connection->pushSingle(make_shared<CelestialRequestPacket>(std::move(celestialRequests)));
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
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 {
|
2023-07-20 14:58:49 +00:00
|
|
|
if (m_respawnTimer.tick(dt)) {
|
2023-06-20 04:33:09 +00:00
|
|
|
String cinematic = assets->json("/client.config:respawnCinematic").toString();
|
|
|
|
cinematic = cinematic.replaceTags(StringMap<String>{
|
|
|
|
{"species", m_mainPlayer->species()},
|
|
|
|
{"mode", PlayerModeNames.getRight(m_mainPlayer->modeType())}
|
|
|
|
});
|
2024-02-19 15:55:19 +00:00
|
|
|
m_mainPlayer->setPendingCinematic(Json(std::move(cinematic)));
|
2023-06-20 04:33:09 +00:00
|
|
|
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()) {
|
2024-03-15 05:00:56 +00:00
|
|
|
LogMap::set("net_total_incoming", strf("{:4.3f} kB/s", netStats->bytesPerSecond / 1000.f));
|
|
|
|
LogMap::set("net_worst_incoming", strf("^cyan;{}^reset; ({:4.3f} kB/s)", PacketTypeNames.getRight(netStats->worstPacketType), (float)netStats->worstPacketSize / 1000.f));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
if (auto netStats = m_connection->outgoingStats()) {
|
2024-03-15 05:00:56 +00:00
|
|
|
LogMap::set("net_total_outgoing", strf("{:4.3f} kB/s", netStats->bytesPerSecond / 1000.f));
|
|
|
|
LogMap::set("net_worst_outgoing", strf("^cyan;{}^reset; ({:4.3f} kB/s)", PacketTypeNames.getRight(netStats->worstPacketType), (float)netStats->worstPacketSize / 1000.f));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2023-07-26 09:02:33 +00:00
|
|
|
return playerWorld().is<ClientShipWorldId>() && playerWorld().get<ClientShipWorldId>() == m_clientContext->playerUuid();
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
2023-07-22 12:31:04 +00:00
|
|
|
bool UniverseClient::playerIsOriginal() const {
|
|
|
|
return m_clientContext->playerUuid() == mainPlayer()->uuid();
|
|
|
|
}
|
|
|
|
|
2023-06-20 04:33:09 +00:00
|
|
|
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;
|
2023-07-26 09:02:33 +00:00
|
|
|
return m_clientContext->playerUuid();
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-04-21 20:07:59 +00:00
|
|
|
void UniverseClient::sendChat(String const& text, ChatSendMode sendMode, Maybe<bool> speak) {
|
|
|
|
if (speak.value(!text.beginsWith("/")))
|
2023-06-20 04:33:09 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2023-07-04 09:27:16 +00:00
|
|
|
void UniverseClient::setLuaCallbacks(String const& groupName, LuaCallbacks const& callbacks) {
|
|
|
|
m_luaCallbacks[groupName] = callbacks;
|
|
|
|
if (m_worldClient)
|
|
|
|
m_worldClient->setLuaCallbacks(groupName, callbacks);
|
|
|
|
}
|
|
|
|
|
2023-07-17 12:20:39 +00:00
|
|
|
void UniverseClient::startLua() {
|
2023-07-20 07:53:57 +00:00
|
|
|
setLuaCallbacks("celestial", LuaBindings::makeCelestialCallbacks(this));
|
|
|
|
|
2023-07-17 12:20:39 +00:00
|
|
|
auto assets = Root::singleton().assets();
|
|
|
|
for (auto& p : assets->json("/client.config:universeScriptContexts").toObject()) {
|
|
|
|
auto scriptComponent = make_shared<ScriptComponent>();
|
|
|
|
scriptComponent->setLuaRoot(m_luaRoot);
|
|
|
|
scriptComponent->setScripts(jsonToStringList(p.second.toArray()));
|
|
|
|
|
|
|
|
for (auto& pair : m_luaCallbacks)
|
|
|
|
scriptComponent->addCallbacks(pair.first, pair.second);
|
|
|
|
|
|
|
|
m_scriptContexts.set(p.first, scriptComponent);
|
|
|
|
scriptComponent->init();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void UniverseClient::stopLua() {
|
|
|
|
for (auto& p : m_scriptContexts)
|
|
|
|
p.second->uninit();
|
|
|
|
|
|
|
|
m_scriptContexts.clear();
|
|
|
|
}
|
|
|
|
|
2024-02-28 17:11:55 +00:00
|
|
|
bool UniverseClient::reloadPlayer(Json const& data, Uuid const&, bool resetInterfaces, bool showIndicator) {
|
2023-07-22 12:31:04 +00:00
|
|
|
auto player = mainPlayer();
|
2023-07-28 14:52:56 +00:00
|
|
|
bool playerInWorld = player->inWorld();
|
|
|
|
auto world = as<WorldClient>(player->world());
|
|
|
|
|
|
|
|
EntityId entityId = (playerInWorld || !world->inWorld())
|
|
|
|
? player->entityId()
|
|
|
|
: connectionEntitySpace(world->connection()).first;
|
2023-07-22 12:31:04 +00:00
|
|
|
|
|
|
|
if (m_playerReloadPreCallback)
|
2023-07-28 16:12:03 +00:00
|
|
|
m_playerReloadPreCallback(resetInterfaces);
|
2023-07-22 12:31:04 +00:00
|
|
|
|
2023-08-02 11:28:37 +00:00
|
|
|
ProjectilePtr indicator;
|
|
|
|
|
2023-07-28 14:52:56 +00:00
|
|
|
if (playerInWorld) {
|
2023-08-02 11:28:37 +00:00
|
|
|
if (showIndicator) {
|
|
|
|
// EntityCreatePacket for player entities can be pretty big.
|
|
|
|
// We can show a loading projectile to other players while the create packet uploads.
|
|
|
|
auto projectileDb = Root::singleton().projectileDatabase();
|
|
|
|
auto config = projectileDb->projectileConfig("opensb:playerloading");
|
|
|
|
indicator = projectileDb->createProjectile("stationpartsound", config);
|
|
|
|
indicator->setInitialPosition(player->position());
|
|
|
|
indicator->setInitialDirection({ 1.0f, 0.0f });
|
|
|
|
world->addEntity(indicator);
|
|
|
|
}
|
|
|
|
|
2023-07-28 14:52:56 +00:00
|
|
|
world->removeEntity(player->entityId(), false);
|
|
|
|
} else {
|
2023-07-22 12:31:04 +00:00
|
|
|
m_respawning = false;
|
|
|
|
m_respawnTimer.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
Json originalData = m_playerStorage->savePlayer(player);
|
|
|
|
std::exception_ptr exception;
|
|
|
|
|
|
|
|
try {
|
|
|
|
auto newData = data.set("movementController", originalData.get("movementController"));
|
|
|
|
player->diskLoad(newData);
|
|
|
|
}
|
|
|
|
catch (std::exception const& e) {
|
|
|
|
player->diskLoad(originalData);
|
|
|
|
exception = std::current_exception();
|
|
|
|
}
|
|
|
|
|
2023-07-28 14:52:56 +00:00
|
|
|
world->addEntity(player, entityId);
|
2023-07-22 12:31:04 +00:00
|
|
|
|
2023-08-02 11:28:37 +00:00
|
|
|
if (indicator && indicator->inWorld())
|
|
|
|
world->removeEntity(indicator->entityId(), false);
|
|
|
|
|
2023-07-22 12:31:04 +00:00
|
|
|
CelestialCoordinate coordinate = m_systemWorldClient->location();
|
|
|
|
player->universeMap()->addMappedCoordinate(coordinate);
|
|
|
|
player->universeMap()->filterMappedObjects(coordinate, m_systemWorldClient->objectKeys());
|
|
|
|
|
|
|
|
if (m_playerReloadCallback)
|
2023-07-28 16:12:03 +00:00
|
|
|
m_playerReloadCallback(resetInterfaces);
|
2023-07-22 12:31:04 +00:00
|
|
|
|
|
|
|
if (exception)
|
|
|
|
std::rethrow_exception(exception);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool UniverseClient::switchPlayer(Uuid const& uuid) {
|
|
|
|
if (uuid == mainPlayer()->uuid())
|
|
|
|
return false;
|
2023-08-18 10:03:06 +00:00
|
|
|
else if (auto data = m_playerStorage->maybeGetPlayerData(uuid)) {
|
|
|
|
if (reloadPlayer(*data, uuid, true, true)) {
|
2024-03-18 06:12:42 +00:00
|
|
|
if (auto dance = Root::singleton().assets()->json("/player.config").optString("swapDance"))
|
|
|
|
m_mainPlayer->humanoid()->setDance(*dance);
|
2023-08-18 10:03:06 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2023-07-22 12:31:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool UniverseClient::switchPlayer(size_t index) {
|
|
|
|
if (auto uuid = m_playerStorage->playerUuidAt(index))
|
|
|
|
return switchPlayer(*uuid);
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool UniverseClient::switchPlayer(String const& name) {
|
2023-07-24 07:54:31 +00:00
|
|
|
if (auto uuid = m_playerStorage->playerUuidByName(name, mainPlayer()->uuid()))
|
2023-07-22 12:31:04 +00:00
|
|
|
return switchPlayer(*uuid);
|
2024-03-09 00:09:04 +00:00
|
|
|
else if (name.utf8Size() == UuidSize * 2)
|
2023-10-25 04:30:31 +00:00
|
|
|
return switchPlayer(Uuid(name));
|
2024-03-09 00:09:04 +00:00
|
|
|
else
|
|
|
|
return false;
|
2023-07-22 12:31:04 +00:00
|
|
|
}
|
|
|
|
|
2023-07-28 16:12:03 +00:00
|
|
|
UniverseClient::ReloadPlayerCallback& UniverseClient::playerReloadPreCallback() {
|
2023-07-22 12:31:04 +00:00
|
|
|
return m_playerReloadPreCallback;
|
|
|
|
}
|
|
|
|
|
2023-07-28 16:12:03 +00:00
|
|
|
UniverseClient::ReloadPlayerCallback& UniverseClient::playerReloadCallback() {
|
2023-07-22 12:31:04 +00:00
|
|
|
return m_playerReloadCallback;
|
|
|
|
}
|
|
|
|
|
2023-06-20 04:33:09 +00:00
|
|
|
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) {
|
2024-09-02 12:18:03 +00:00
|
|
|
try {
|
|
|
|
if (auto clientContextUpdate = as<ClientContextUpdatePacket>(packet)) {
|
|
|
|
m_clientContext->readUpdate(clientContextUpdate->updateData);
|
|
|
|
m_playerStorage->applyShipUpdates(m_clientContext->playerUuid(), m_clientContext->newShipUpdates());
|
|
|
|
|
|
|
|
if (playerIsOriginal())
|
|
|
|
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;
|
|
|
|
break; // Stop handling other packets
|
|
|
|
|
|
|
|
} else if (auto celestialResponse = as<CelestialResponsePacket>(packet)) {
|
|
|
|
m_celestialDatabase->pushResponses(std::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;
|
|
|
|
}
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-02 12:18:03 +00:00
|
|
|
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);
|
|
|
|
GlobalTimescale = clamp(pausePacket->timescale, 0.0f, 1024.f);
|
|
|
|
} 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});
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
2024-09-02 12:18:03 +00:00
|
|
|
}
|
|
|
|
catch (StarException const& e) {
|
|
|
|
Logger::error("Exception thrown while handling {} packet", PacketTypeNames.getRight(packet->type()));
|
|
|
|
throw;
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void UniverseClient::reset() {
|
2023-07-17 12:20:39 +00:00
|
|
|
stopLua();
|
|
|
|
|
2023-06-20 04:33:09 +00:00
|
|
|
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());
|
|
|
|
|
2023-06-26 10:40:32 +00:00
|
|
|
if (m_mainPlayer)
|
2023-06-20 04:33:09 +00:00
|
|
|
m_playerStorage->savePlayer(m_mainPlayer);
|
|
|
|
|
|
|
|
m_connection.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|