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

summaryrefslogtreecommitdiff
path: root/source/game/StarSystemWorldServer.cpp
diff options
context:
space:
mode:
authorKae <80987908+Novaenia@users.noreply.github.com>2023-06-20 14:33:09 +1000
committerKae <80987908+Novaenia@users.noreply.github.com>2023-06-20 14:33:09 +1000
commit6352e8e3196f78388b6c771073f9e03eaa612673 (patch)
treee23772f79a7fbc41bc9108951e9e136857484bf4 /source/game/StarSystemWorldServer.cpp
parent6741a057e5639280d85d0f88ba26f000baa58f61 (diff)
everything everywhere
all at once
Diffstat (limited to 'source/game/StarSystemWorldServer.cpp')
-rw-r--r--source/game/StarSystemWorldServer.cpp462
1 files changed, 462 insertions, 0 deletions
diff --git a/source/game/StarSystemWorldServer.cpp b/source/game/StarSystemWorldServer.cpp
new file mode 100644
index 0000000..3cffbc9
--- /dev/null
+++ b/source/game/StarSystemWorldServer.cpp
@@ -0,0 +1,462 @@
+#include "StarSystemWorldServer.hpp"
+#include "StarRoot.hpp"
+#include "StarCelestialDatabase.hpp"
+#include "StarCelestialGraphics.hpp"
+#include "StarClientContext.hpp"
+#include "StarNetPackets.hpp"
+#include "StarMathCommon.hpp"
+#include "StarJsonExtra.hpp"
+
+namespace Star {
+
+SystemWorldServer::SystemWorldServer(Vec3I location, ClockConstPtr universeClock, CelestialDatabasePtr celestialDatabase)
+ : SystemWorld(move(universeClock), move(celestialDatabase)) {
+ m_location = move(location);
+
+ placeInitialObjects();
+
+ m_lastSpawn = time() - systemConfig().objectSpawnCycle;
+ m_objectSpawnTime = Random::randf(systemConfig().objectSpawnInterval[0], systemConfig().objectSpawnInterval[1]);
+ spawnObjects();
+}
+
+SystemWorldServer::SystemWorldServer(Json const& diskStore, ClockConstPtr universeClock, CelestialDatabasePtr celestialDatabase)
+ : SystemWorld(move(universeClock), move(celestialDatabase)) {
+ m_location = jsonToVec3I(diskStore.get("location"));
+
+ for (auto objectStore : diskStore.getArray("objects")) {
+ auto object = make_shared<SystemObject>(this, objectStore);
+ m_objects.set(object->uuid(), object);
+ }
+
+ m_lastSpawn = diskStore.getDouble("lastSpawn");
+ m_objectSpawnTime = diskStore.getDouble("objectSpawnTime");
+ spawnObjects();
+}
+
+void SystemWorldServer::setClientDestination(ConnectionId const& clientId, SystemLocation const& destination) {
+ auto uuid = m_clientShips.get(clientId);
+ m_ships[uuid]->setDestination(destination);
+}
+
+SystemClientShipPtr SystemWorldServer::clientShip(ConnectionId clientId) const {
+ if (m_clientShips.contains(clientId) && m_ships.contains(m_clientShips.get(clientId)))
+ return m_ships.get(m_clientShips.get(clientId));
+ else
+ return {};
+}
+
+SystemLocation SystemWorldServer::clientShipLocation(ConnectionId clientId) const {
+ return m_ships.get(m_clientShips.get(clientId))->systemLocation();
+}
+
+Maybe<pair<WarpAction, WarpMode>> SystemWorldServer::clientWarpAction(ConnectionId clientId) const {
+ auto ship = m_ships.get(m_clientShips.get(clientId));
+ if (auto objectUuid = ship->systemLocation().maybe<Uuid>()) {
+ if (auto action = objectWarpAction(*objectUuid)) {
+ return pair<WarpAction, WarpMode>(*action, WarpMode::DeployOnly);
+ }
+ } else if (auto coordinate = ship->systemLocation().maybe<CelestialCoordinate>()) {
+ WarpAction warpAction = WarpToWorld(CelestialWorldId(*coordinate));
+ return pair<WarpAction, WarpMode>(warpAction, WarpMode::BeamOrDeploy);
+ } else if (auto position = ship->systemLocation().maybe<Vec2F>()) {
+ // player can beam to asteroid fields simply by being in proximity to them
+ for (auto planet : planets()) {
+ if (abs(planetPosition(planet).magnitude() - position->magnitude()) > systemConfig().asteroidBeamDistance)
+ continue;
+
+ if (auto parameters = m_celestialDatabase->parameters(planet)) {
+ if (auto awp = as<AsteroidsWorldParameters>(parameters->visitableParameters())) {
+ float targetX = (position->angle() / (2 * Constants::pi)) * awp->worldSize[0];
+ return pair<WarpAction, WarpMode>(WarpAction(WarpToWorld(CelestialWorldId(planet), SpawnTargetX(targetX))),
+ WarpMode::DeployOnly);
+ }
+ }
+ }
+ }
+
+ return {};
+}
+
+SkyParameters SystemWorldServer::clientSkyParameters(ConnectionId clientId) const {
+ auto uuid = m_clientShips.get(clientId);
+ return locationSkyParameters(m_ships.get(uuid)->systemLocation());
+}
+
+List<ConnectionId> SystemWorldServer::clients() const {
+ return m_clientShips.keys();
+}
+
+void SystemWorldServer::addClientShip(ConnectionId clientId, Uuid const& uuid, float shipSpeed, SystemLocation location) {
+ if (auto objectUuid = location.maybe<Uuid>()) {
+ if (getObject(*objectUuid) == nullptr)
+ location.reset();
+ }
+ if (!location)
+ location = randomArrivalPosition();
+
+ SystemClientShipPtr ship = make_shared<SystemClientShip>(this, uuid, shipSpeed, location);
+ m_clientShips.set(clientId, ship->uuid());
+ m_ships.set(ship->uuid(), ship);
+ m_clientNetVersions.set(clientId, {{}, {} });
+ m_outgoingPackets.set(clientId, {});
+
+ List<ByteArray> objectStores = m_objects.values().transformed([](SystemObjectPtr const& o) { return o->netStore(); });
+ List<ByteArray> shipStores = m_ships.values().filtered([uuid](SystemClientShipPtr const& s) {
+ return s->uuid() != uuid;
+ }).transformed([](SystemClientShipPtr const& s) {
+ return s->netStore();
+ });
+ pair<Uuid, SystemLocation> clientShip = {ship->uuid(), ship->systemLocation()};
+ m_outgoingPackets[clientId].append(make_shared<SystemWorldStartPacket>(m_location, objectStores, shipStores, clientShip));
+
+ for (ConnectionId otherClient : m_clientShips.keys()) {
+ if (otherClient != clientId)
+ m_outgoingPackets[otherClient].append(make_shared<SystemShipCreatePacket>(ship->netStore()));
+ }
+}
+
+void SystemWorldServer::removeClientShip(ConnectionId clientId) {
+ m_shipDestroyQueue.append(m_clientShips.get(clientId));
+ m_clientShips.remove(clientId);
+ m_clientNetVersions.remove(clientId);
+ m_outgoingPackets.remove(clientId);
+}
+
+List<SystemClientShipPtr> SystemWorldServer::shipsAtLocation(SystemLocation const& location) const {
+ return m_ships.values().filtered([location](auto const& ship) { return ship->systemLocation() == location; });
+}
+
+List<InstanceWorldId> SystemWorldServer::activeInstanceWorlds() const {
+ // Find the warp actions for all ships located at objects
+ List<Maybe<WarpAction>> warpActions = m_clientShips.keys().transformed([this](ConnectionId const& clientId) -> Maybe<WarpAction> {
+ return clientWarpAction(clientId).apply([](auto const& p) { return p.first; });
+ });
+ // Return a list of the ones which lead to instance worlds
+ return warpActions.filtered([](Maybe<WarpAction> const& action) {
+ if (action.isNothing())
+ return false;
+
+ if (auto warpToWorld = action->maybe<WarpToWorld>()) {
+ if (auto instanceWorldId = warpToWorld->world.maybe<InstanceWorldId>())
+ return true;
+ }
+ return false;
+ }).transformed([](Maybe<WarpAction> const& action) { return action->get<WarpToWorld>().world.get<InstanceWorldId>(); });
+
+}
+
+void SystemWorldServer::removeObject(Uuid objectUuid) {
+ if (!m_objects.contains(objectUuid))
+ throw StarException(strf("Cannot remove object with uuid '%s', object doesn't exist.", objectUuid.hex()));
+
+ if (m_objects[objectUuid]->permanent())
+ throw StarException(strf("Cannot remove object with uuid '%s', object is marked permanent", objectUuid.hex()));
+
+ // already removing it
+ if (m_objectDestroyQueue.contains(objectUuid))
+ return;
+
+ // fly away any active ships that are located at the object
+ for (auto p : m_clientShips) {
+ auto ship = m_ships.get(p.second);
+ auto location = ship->systemLocation();
+ if (location == objectUuid || ship->destination() == objectUuid) {
+ ship->setDestination(*systemLocationPosition(objectUuid));
+ if (!ship->flying())
+ m_shipFlights.append(p.first);
+ }
+ }
+
+ m_objectDestroyQueue.append(objectUuid);
+}
+
+bool SystemWorldServer::addObject(SystemObjectPtr object, bool doRangeCheck) {
+ if (doRangeCheck) {
+ CelestialCoordinate system = CelestialCoordinate(m_location);
+ CelestialCoordinate outer = system.child(m_celestialDatabase->childOrbits(system).sorted().last());
+ List<pair<float, float>> orbitDistances;
+ for (auto planet : planets()) {
+ orbitDistances.append({planetOrbitDistance(planet), clusterSize(planet) / 2.0});
+ }
+ for (auto o : m_objects.values()) {
+ if (o->permanent())
+ orbitDistances.append({o->position().magnitude(), 0.0});
+ }
+
+ float maxRange = planetOrbitDistance(outer) + (clusterSize(outer) / 2.0) + systemConfig().clientObjectSpawnPadding;
+ // Allow objectSpawnPadding of room outside the farthest orbit to have an object placed in it
+ maxRange += systemConfig().clientObjectSpawnPadding;
+ float minRange = (planetSize(system) / 2.0) + systemConfig().clientObjectSpawnPadding;
+ float radius = object->position().magnitude();
+ if (radius > maxRange || radius < minRange)
+ return false;
+ for (pair<float, float> p : orbitDistances) {
+ if (abs(radius - p.first) < p.second + systemConfig().clientObjectSpawnPadding)
+ return false;
+ }
+ }
+
+ m_objects.set(object->uuid(), object);
+
+ auto objectStore = object->netStore();
+ for (auto clientId : m_clientShips.keys()) {
+ m_outgoingPackets[clientId].append(make_shared<SystemObjectCreatePacket>(objectStore));
+ }
+
+ m_triggerStorage = true;
+ return true;
+}
+
+void SystemWorldServer::update() {
+ for (auto p : m_ships)
+ p.second->serverUpdate(this, SystemWorldTimestep);
+
+ for (auto p : m_objects) {
+ p.second->serverUpdate(this, SystemWorldTimestep);
+
+ // don't destroy objects that still have players at them
+ if (p.second->shouldDestroy() && shipsAtLocation(p.first).size() == 0)
+ removeObject(p.first);
+ }
+
+ spawnObjects();
+
+ queueUpdatePackets();
+
+ // remove objects and ships after queueing update packets to ensure they're not updated after being removed
+ for (auto objectUuid : take(m_objectDestroyQueue)) {
+ for (auto p : m_clientNetVersions) {
+ p.second.objects.remove(objectUuid);
+ m_outgoingPackets[p.first].append(make_shared<SystemObjectDestroyPacket>(objectUuid));
+ }
+ m_objects.remove(objectUuid);
+ m_triggerStorage = true;
+ }
+ for (auto shipUuid : take(m_shipDestroyQueue)) {
+ for (auto p : m_clientNetVersions) {
+ p.second.ships.remove(shipUuid);
+ m_outgoingPackets[p.first].append(make_shared<SystemShipDestroyPacket>(shipUuid));
+ }
+ m_ships.remove(shipUuid);
+ m_triggerStorage = true;
+ }
+}
+
+List<SystemObjectPtr> SystemWorldServer::objects() const {
+ return m_objects.values();
+}
+
+SystemObjectPtr SystemWorldServer::getObject(Uuid const& uuid) const {
+ return m_objects.maybe(uuid).value({});
+}
+
+List<ConnectionId> SystemWorldServer::pullShipFlights() {
+ return take(m_shipFlights);
+}
+
+void SystemWorldServer::queueUpdatePackets() {
+ for (auto clientId : m_clientNetVersions.keys()) {
+ auto versions = m_clientNetVersions.ptr(clientId);
+
+ HashMap<Uuid, ByteArray> shipUpdates;
+ for (auto ship : m_ships.values()) {
+ uint64_t version = versions->ships.maybe(ship->uuid()).value(0);
+ auto shipUpdate = ship->writeNetState(version);
+ versions->ships.set(ship->uuid(), shipUpdate.second);
+ if (!shipUpdate.first.empty())
+ shipUpdates.set(ship->uuid(), shipUpdate.first);
+ }
+
+ HashMap<Uuid, ByteArray> objectUpdates;
+ for (auto object : m_objects.values()) {
+ uint64_t version = versions->objects.maybe(object->uuid()).value(0);
+ auto objectUpdate = object->writeNetState(version);
+ versions->objects.set(object->uuid(), objectUpdate.second);
+ if (!objectUpdate.first.empty())
+ objectUpdates.set(object->uuid(), objectUpdate.first);
+ }
+ m_outgoingPackets[clientId].append(make_shared<SystemWorldUpdatePacket>(objectUpdates, shipUpdates));
+ }
+}
+
+void SystemWorldServer::handleIncomingPacket(ConnectionId, PacketPtr packet) {
+ if (auto objectSpawn = as<SystemObjectSpawnPacket>(packet)) {
+ RandomSource rand = RandomSource();
+ Vec2F position = objectSpawn->position.value(randomObjectSpawnPosition(rand));
+ auto object = make_shared<SystemObject>(systemObjectConfig(objectSpawn->typeName, objectSpawn->uuid), objectSpawn->uuid, position, time(), objectSpawn->parameters);
+ addObject(object, objectSpawn->position.isValid());
+ }
+}
+
+List<PacketPtr> SystemWorldServer::pullOutgoingPackets(ConnectionId clientId) {
+ return take(m_outgoingPackets[clientId]);
+}
+
+bool SystemWorldServer::triggeredStorage() {
+ bool store = m_triggerStorage;
+ m_triggerStorage = false;
+ return store;
+}
+
+Json SystemWorldServer::diskStore() {
+ JsonArray storedObjects;
+ for (auto o : m_objects)
+ storedObjects.append(o.second->diskStore());
+
+ JsonObject store;
+ store.set("location", jsonFromVec3I(m_location));
+ store.set("objects", storedObjects);
+ store.set("lastSpawn", m_lastSpawn);
+ store.set("objectSpawnTime", m_objectSpawnTime);
+ return store;
+}
+
+void SystemWorldServer::placeInitialObjects() {
+ auto config = Root::singleton().assets()->json("/systemworld.config");
+ RandomSource rand(staticRandomU64("SystemWorldGeneration", strf("%s", m_location)));
+
+ WeightedPool<JsonArray> spawnPools = jsonToWeightedPool<JsonArray>(config.getArray("initialObjectPools"));
+ JsonArray spawn = spawnPools.select(rand);
+ int count = spawn.get(0).toInt();
+ if (count > 0) {
+ WeightedPool<String> objectPool = jsonToWeightedPool<String>(spawn.get(1).toArray());
+ for (int i = 0; i < count; i++) {
+ Uuid uuid = Uuid();
+ auto objectConfig = systemObjectConfig(objectPool.select(rand), uuid);
+ Vec2F position = randomObjectSpawnPosition(rand);
+
+ auto object = make_shared<SystemObject>(objectConfig, uuid, position, time());
+ object->enterOrbit(CelestialCoordinate(m_location), { 0.0, 0.0 }, time()); // orbit center of system
+ m_objects.set(uuid, object);
+ }
+ }
+}
+
+void SystemWorldServer::spawnObjects() {
+ double diff = min(systemConfig().objectSpawnCycle, time() - m_lastSpawn);
+ m_lastSpawn = time() - diff;
+ while (diff > m_objectSpawnTime) {
+ m_lastSpawn += m_objectSpawnTime;
+ m_objectSpawnTime = Random::randf(systemConfig().objectSpawnInterval[0], systemConfig().objectSpawnInterval[1]);
+ diff = time() - m_lastSpawn;
+
+ WeightedPool<String> spawnPool = jsonToWeightedPool<String>(Root::singleton().assets()->json("/systemworld.config:objectSpawnPool").toArray());
+ String name = spawnPool.select();
+ Uuid uuid = Uuid();
+ auto objectConfig = systemObjectConfig(name, uuid);
+
+ SystemObjectPtr object;
+ RandomSource rand = RandomSource(Random::randu64());
+ Vec2F position = randomObjectSpawnPosition(rand);
+ if (time() > m_lastSpawn + m_objectSpawnTime && objectConfig.moving) {
+ // if this is not the last object we're spawning, and it's moving, immediately put it in orbit around a planet
+ auto targets = planets().filtered([this](CelestialCoordinate const& p) {
+ auto objectsAtPlanet = objects().filtered([p](SystemObjectPtr const& o) { return o->orbitTarget() == p; });
+ return objectsAtPlanet.size() == 0;
+ });
+ if (targets.size() > 0) {
+ auto target = Random::randFrom(targets);
+
+ Vec2F targetPosition = planetPosition(target);
+ Vec2F relativeOrbit = (position - targetPosition).normalized() * (clusterSize(target) / 2.0 + objectConfig.orbitDistance);
+ object = make_shared<SystemObject>(objectConfig, uuid, targetPosition + relativeOrbit, m_lastSpawn);
+
+ object->enterOrbit(target, planetPosition(target), m_lastSpawn);
+ } else {
+ object = make_shared<SystemObject>(objectConfig, uuid, position, m_lastSpawn);
+ }
+ } else {
+ object = make_shared<SystemObject>(objectConfig, uuid, position, m_lastSpawn);
+ }
+ addObject(object);
+ }
+}
+
+Vec2F SystemWorldServer::randomObjectSpawnPosition(RandomSource& rand) const {
+ List<Vec2F> spawnRanges;
+ CelestialCoordinate system = CelestialCoordinate(m_location);
+ auto config = systemConfig();
+ auto orbits = m_celestialDatabase->childOrbits(CelestialCoordinate(m_location)).sorted();
+
+ auto addSpawn = [this,&config,&spawnRanges](CelestialCoordinate const& inner, CelestialCoordinate const& outer) {
+ float min = planetOrbitDistance(inner) + (clusterSize(inner) / 2.0) + config.objectSpawnPadding;
+ float max = planetOrbitDistance(outer) - (clusterSize(outer) / 2.0) - config.objectSpawnPadding;
+ spawnRanges.append(Vec2F(min, max));
+ };
+
+ addSpawn(system, system.child(orbits[0]));
+ for (size_t i = 1; i < orbits.size(); i++)
+ addSpawn(system.child(orbits[i - 1]), system.child(orbits[i]));
+
+ CelestialCoordinate outer = system.child(orbits.last());
+ float rim = planetOrbitDistance(outer) + (clusterSize(outer) / 2.0) + config.objectSpawnPadding;
+ spawnRanges.append(Vec2F(rim, rim + config.objectSpawnPadding));
+
+ auto range = rand.randFrom(spawnRanges);
+ return Vec2F::withAngle(rand.randf() * Constants::pi * 2.0, range[0] + (rand.randf() * (range[1] - range[0])));
+}
+
+SkyParameters SystemWorldServer::locationSkyParameters(SystemLocation const& location) const {
+ SkyParameters skyParameters = systemConfig().emptySkyParameters;
+
+ if (auto coordinate = location.maybe<CelestialCoordinate>()) {
+ return SkyParameters(*coordinate, m_celestialDatabase);
+ } else if (auto position = location.maybe<Vec2F>()) {
+ for (auto planet : planets()) {
+ if (abs(position->magnitude() - planetPosition(planet).magnitude()) > systemConfig().asteroidBeamDistance)
+ continue;
+
+ if (auto parameters = m_celestialDatabase->parameters(planet)) {
+ if (auto asteroidsParameters = as<AsteroidsWorldParameters>(parameters->visitableParameters())) {
+ return SkyParameters(planet, m_celestialDatabase);
+ }
+ }
+ }
+ } else if (!location.empty()) {
+ CelestialCoordinate orbitTarget;
+ if (auto objectUuid = location.maybe<Uuid>()) {
+ auto object = getObject(*objectUuid);
+ skyParameters = object->skyParameters();
+ if (auto target = object->orbitTarget())
+ orbitTarget = *target;
+ } else if (auto orbit = location.maybe<CelestialOrbit>()) {
+ orbitTarget = orbit->target;
+ }
+
+ if (orbitTarget.isPlanetaryBody()) {
+ auto parameters = m_celestialDatabase->parameters(orbitTarget);
+
+ if (auto visitableParameters = parameters->visitableParameters()) {
+ if (is<TerrestrialWorldParameters>(visitableParameters)) {
+ uint64_t seed = staticRandomU64(strf("%s", m_location));
+ List<CelestialParameters> worlds;
+ if (auto planet = m_celestialDatabase->parameters(orbitTarget))
+ worlds.append(*planet);
+ for (auto coordinate : m_celestialDatabase->children(orbitTarget)) {
+ if (auto satellite = m_celestialDatabase->parameters(coordinate))
+ worlds.append(*satellite);
+ }
+
+ for (uint64_t i = 0; i < worlds.size(); i++) {
+ auto world = worlds.get(i);
+ Vec2F pos = {
+ staticRandomFloat(seed, world.seed(), "x"),
+ staticRandomFloat(seed, world.seed(), "y")
+ };
+ CelestialParameters parent = i > 0 ? worlds[0] : CelestialParameters();
+ skyParameters.nearbyMoons.append({CelestialGraphics::drawWorld(world, parent), pos});
+ }
+ } else {
+ // put orbited horizon behind existing horizon images
+ skyParameters.horizonImages.insertAllAt(0, CelestialGraphics::worldHorizonImages(*parameters));
+ }
+ }
+ }
+ return skyParameters;
+ }
+
+ return skyParameters;
+}
+
+}