osb/source/game/StarUniverseClient.cpp
Kai Blaschke 431a9c00a5
Fixed a huge amount of Clang warnings
On Linux and macOS, using Clang to compile OpenStarbound produces about 400 MB worth of warnings during the build, making the compiler output unreadable and slowing the build down considerably.

99% of the warnings were unqualified uses of std::move and std::forward, which are now all properly qualified.

Fixed a few other minor warnings about non-virtual destructors and some uses of std::move preventing copy elision on temporary objects.

Most remaining warnings are now unused parameters.
2024-02-19 16:55:19 +01:00

709 lines
23 KiB
C++

#include "StarUniverseClient.hpp"
#include "StarLexicalCast.hpp"
#include "StarJsonExtra.hpp"
#include "StarLogging.hpp"
#include "StarVersion.hpp"
#include "StarRoot.hpp"
#include "StarConfiguration.hpp"
#include "StarProjectileDatabase.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"
#include "StarCelestialLuaBindings.hpp"
namespace Star {
UniverseClient::UniverseClient(PlayerStoragePtr playerStorage, StatisticsPtr statistics) {
m_storageTriggerDeadline = 0;
m_playerStorage = std::move(playerStorage);
m_statistics = std::move(statistics);
m_pause = false;
m_luaRoot = make_shared<LuaRoot>();
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();
{
auto protocolRequest = make_shared<ProtocolRequestPacket>(StarProtocolVersion);
protocolRequest->setCompressionMode(PacketCompressionMode::Enabled);
// Signal that we're OpenStarbound. Vanilla Starbound only compresses packets above 64 bytes - by forcing it we can communicate this.
// If you know a less cursed way, please let me know.
connection.pushSingle(protocolRequest);
}
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 {}", StarProtocolVersion));
m_legacyServer = protocolResponsePacket->compressionMode() != PacketCompressionMode::Enabled; // True if server is vanilla
connection.setLegacy(m_legacyServer);
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()), 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_mainPlayer->uuid());
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_worldClient->setAsyncLighting(true);
for (auto& pair : m_luaCallbacks)
m_worldClient->setLuaCallbacks(pair.first, pair.second);
m_connection = std::move(connection);
m_celestialDatabase = make_shared<CelestialSlaveDatabase>(std::move(success->celestialInformation));
m_systemWorldClient = make_shared<SystemWorldClient>(m_universeClock, m_celestialDatabase, m_mainPlayer->universeMap());
Logger::info("UniverseClient: Joined {} server as client {}", m_legacyServer ? "Starbound" : "OpenStarbound", success->clientId);
return {};
} else if (auto failure = as<ConnectFailurePacket>(packet)) {
Logger::error("UniverseClient: Join failed: {}", 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(float dt) {
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(dt))) {
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());
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);
}
}
}
// 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(dt);
for (auto& p : m_scriptContexts)
p.second->update();
}
m_connection->push(m_worldClient->getOutgoingPackets());
if (!m_pause)
m_systemWorldClient->update(dt);
m_connection->push(m_systemWorldClient->pullOutgoingPackets());
m_teamClient->update();
auto contextUpdate = m_clientContext->writeUpdate();
if (!contextUpdate.empty())
m_connection->pushSingle(make_shared<ClientContextUpdatePacket>(std::move(contextUpdate)));
auto celestialRequests = m_celestialDatabase->pullRequests();
if (!celestialRequests.empty())
m_connection->pushSingle(make_shared<CelestialRequestPacket>(std::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(dt)) {
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(std::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("net_incoming_bps", netStats->bytesPerSecond);
LogMap::set("net_worst_incoming", strf("{}:{}", PacketTypeNames.getRight(netStats->worstPacketType), netStats->worstPacketSize));
}
if (auto netStats = m_connection->outgoingStats()) {
LogMap::set("net_outgoing_bps", netStats->bytesPerSecond);
LogMap::set("net_worst_outgoing",
strf("{}:{}", 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>() == m_clientContext->playerUuid();
}
bool UniverseClient::playerIsOriginal() const {
return m_clientContext->playerUuid() == 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_clientContext->playerUuid();
}
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);
}
void UniverseClient::setLuaCallbacks(String const& groupName, LuaCallbacks const& callbacks) {
m_luaCallbacks[groupName] = callbacks;
if (m_worldClient)
m_worldClient->setLuaCallbacks(groupName, callbacks);
}
void UniverseClient::startLua() {
setLuaCallbacks("celestial", LuaBindings::makeCelestialCallbacks(this));
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();
}
bool UniverseClient::reloadPlayer(Json const& data, Uuid const& uuid, bool resetInterfaces, bool showIndicator) {
auto player = mainPlayer();
bool playerInWorld = player->inWorld();
auto world = as<WorldClient>(player->world());
EntityId entityId = (playerInWorld || !world->inWorld())
? player->entityId()
: connectionEntitySpace(world->connection()).first;
if (m_playerReloadPreCallback)
m_playerReloadPreCallback(resetInterfaces);
ProjectilePtr indicator;
if (playerInWorld) {
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);
}
world->removeEntity(player->entityId(), false);
} else {
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();
}
world->addEntity(player, entityId);
if (indicator && indicator->inWorld())
world->removeEntity(indicator->entityId(), false);
CelestialCoordinate coordinate = m_systemWorldClient->location();
player->universeMap()->addMappedCoordinate(coordinate);
player->universeMap()->filterMappedObjects(coordinate, m_systemWorldClient->objectKeys());
if (m_playerReloadCallback)
m_playerReloadCallback(resetInterfaces);
if (exception)
std::rethrow_exception(exception);
return true;
}
bool UniverseClient::switchPlayer(Uuid const& uuid) {
if (uuid == mainPlayer()->uuid())
return false;
else if (auto data = m_playerStorage->maybeGetPlayerData(uuid)) {
if (reloadPlayer(*data, uuid, true, true)) {
auto dance = Root::singleton().assets()->json("/player.config:swapDance");
if (dance.isType(Json::Type::String))
m_mainPlayer->humanoid()->setDance(dance.toString());
return true;
}
}
return false;
}
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) {
if (auto uuid = m_playerStorage->playerUuidByName(name, mainPlayer()->uuid()))
return switchPlayer(*uuid);
else
return switchPlayer(Uuid(name));
}
UniverseClient::ReloadPlayerCallback& UniverseClient::playerReloadPreCallback() {
return m_playerReloadPreCallback;
}
UniverseClient::ReloadPlayerCallback& UniverseClient::playerReloadCallback() {
return m_playerReloadCallback;
}
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_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;
}
}
}
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() {
stopLua();
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_playerStorage->savePlayer(m_mainPlayer);
m_connection.reset();
}
}