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

summaryrefslogtreecommitdiff
path: root/source/game/StarWeather.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/game/StarWeather.cpp')
-rw-r--r--source/game/StarWeather.cpp365
1 files changed, 365 insertions, 0 deletions
diff --git a/source/game/StarWeather.cpp b/source/game/StarWeather.cpp
new file mode 100644
index 0000000..14120ad
--- /dev/null
+++ b/source/game/StarWeather.cpp
@@ -0,0 +1,365 @@
+#include "StarWeather.hpp"
+#include "StarIterator.hpp"
+#include "StarDataStreamExtra.hpp"
+#include "StarRoot.hpp"
+#include "StarTime.hpp"
+#include "StarAssets.hpp"
+#include "StarProjectileDatabase.hpp"
+#include "StarProjectile.hpp"
+#include "StarBiomeDatabase.hpp"
+
+namespace Star {
+
+ServerWeather::ServerWeather() {
+ m_undergroundLevel = 0.0f;
+ m_currentWeatherIndex = NPos;
+ m_currentWeatherIntensity = 0.0f;
+ m_currentWind = 0.0f;
+
+ m_currentTime = 0.0;
+ m_lastWeatherChangeTime = 0.0;
+ m_nextWeatherChangeTime = 0.0;
+
+ m_netGroup.addNetElement(&m_weatherPoolNetState);
+ m_netGroup.addNetElement(&m_undergroundLevelNetState);
+ m_netGroup.addNetElement(&m_currentWeatherIndexNetState);
+ m_netGroup.addNetElement(&m_currentWeatherIntensityNetState);
+ m_netGroup.addNetElement(&m_currentWindNetState);
+}
+
+void ServerWeather::setup(WeatherPool weatherPool, float undergroundLevel, WorldGeometry worldGeometry,
+ WeatherEffectsActiveQuery weatherEffectsActiveQuery) {
+ m_weatherPool = weatherPool;
+ m_undergroundLevel = undergroundLevel;
+
+ m_worldGeometry = worldGeometry;
+ m_weatherEffectsActiveQuery = weatherEffectsActiveQuery;
+
+ m_currentWeatherIndex = NPos;
+ m_currentWeatherType = {};
+
+ m_currentTime = 0.0;
+ m_lastWeatherChangeTime = 0.0;
+ m_nextWeatherChangeTime = 0.0;
+}
+
+void ServerWeather::setReferenceClock(ClockConstPtr referenceClock) {
+ m_referenceClock = move(referenceClock);
+ if (m_referenceClock)
+ m_clockTrackingTime = m_referenceClock->time();
+ else
+ m_clockTrackingTime = {};
+}
+
+void ServerWeather::setClientVisibleRegions(List<RectI> regions) {
+ m_clientVisibleRegions = move(regions);
+}
+
+pair<ByteArray, uint64_t> ServerWeather::writeUpdate(uint64_t fromVersion) {
+ setNetStates();
+ return m_netGroup.writeNetState(fromVersion);
+}
+
+void ServerWeather::update() {
+ spawnWeatherProjectiles(WorldTimestep);
+
+ double dt = WorldTimestep;
+ if (m_referenceClock) {
+ double clockTime = m_referenceClock->time();
+ if (!m_clockTrackingTime) {
+ m_clockTrackingTime = clockTime;
+ } else {
+ // If our reference clock is set, and we have a valid tracking time, then
+ // the dt should be driven by the reference clock.
+ dt = clockTime - *m_clockTrackingTime;
+ m_clockTrackingTime = clockTime;
+ }
+ }
+
+ m_currentTime += dt;
+
+ if (!m_weatherPool.empty()) {
+ auto assets = Root::singleton().assets();
+ double weatherCooldownTime = assets->json("/weather.config:weatherCooldownTime").toDouble();
+ double weatherWarmupTime = assets->json("/weather.config:weatherWarmupTime").toDouble();
+
+ if (m_currentTime >= m_nextWeatherChangeTime) {
+ m_currentWeatherIndex = m_weatherPool.selectIndex();
+ if (m_currentWeatherIndex == NPos)
+ m_currentWeatherType = {};
+ else
+ m_currentWeatherType = Root::singleton().biomeDatabase()->weatherType(m_weatherPool.item(m_currentWeatherIndex));
+
+ m_lastWeatherChangeTime = m_nextWeatherChangeTime;
+ m_nextWeatherChangeTime = m_currentTime + Random::randd(m_currentWeatherType->duration[0], m_currentWeatherType->duration[1]);
+
+ // TODO: For now just set the wind at maximum either left or right, nothing exciting.
+ m_currentWind = m_currentWeatherType->maximumWind * (Random::randb() ? 1 : -1);
+ }
+
+ m_currentWeatherIntensity = min(clamp((m_currentTime - m_lastWeatherChangeTime) / weatherWarmupTime, 0.0, 1.0),
+ clamp((m_nextWeatherChangeTime - m_currentTime) / weatherCooldownTime, 0.0, 1.0));
+
+ } else {
+ m_currentWeatherIndex = NPos;
+ m_currentWeatherType = {};
+ }
+}
+
+float ServerWeather::wind() const {
+ return m_currentWind * m_currentWeatherIntensity;
+}
+
+float ServerWeather::weatherIntensity() const {
+ return m_currentWeatherIntensity;
+}
+
+StringList ServerWeather::statusEffects() const {
+ if (m_currentWeatherType && m_currentWeatherIntensity == 1.0)
+ return m_currentWeatherType->statusEffects;
+ return {};
+}
+
+List<ProjectilePtr> ServerWeather::pullNewProjectiles() {
+ return take(m_newProjectiles);
+}
+
+void ServerWeather::setNetStates() {
+ m_weatherPoolNetState.set(DataStreamBuffer::serializeContainer(m_weatherPool.items()));
+ m_undergroundLevelNetState.set(m_undergroundLevel);
+ m_currentWeatherIndexNetState.set(m_currentWeatherIndex);
+ m_currentWeatherIntensityNetState.set(m_currentWeatherIntensity);
+ m_currentWindNetState.set(m_currentWind);
+}
+
+void ServerWeather::spawnWeatherProjectiles(float dt) {
+ if (!m_currentWeatherType || m_clientVisibleRegions.empty())
+ return;
+
+ auto projectileDatabase = Root::singleton().projectileDatabase();
+
+ // TODO: The complexity of this method is TERRIBLE, if this becomes a problem
+ // for any reason there are large numbers of ways to make this much better,
+ // but this was the lazy, simple-ish, and clear (hah) way.
+
+ for (auto const& projectileConfig : m_currentWeatherType->projectiles) {
+ // Gather all the tops of the client regions together with the proper
+ // padding, splitting at the world wrap boundary.
+ List<pair<Vec2I, int>> baseSpawnRegions;
+ for (auto const& clientRegion : m_clientVisibleRegions) {
+ Vec2I baseRegion = {clientRegion.xMin() - projectileConfig.spawnHorizontalPad, clientRegion.xMax() + projectileConfig.spawnHorizontalPad};
+ int height = clientRegion.yMax();
+ for (auto const& region : m_worldGeometry.splitXRegion(baseRegion))
+ baseSpawnRegions.append({region, height});
+ }
+
+ // We are going to have to eliminate vertically redundant sections of
+ // spawning regions, so gather up every left and right edge of a spawn
+ // region is a "split point"
+ List<int> splitPoints;
+ for (auto const& baseSpawnRegion : baseSpawnRegions) {
+ splitPoints.append(baseSpawnRegion.first[0]);
+ splitPoints.append(baseSpawnRegion.first[1]);
+ }
+
+ // Split every spawn region on every split point.
+ List<pair<Vec2I, int>> splitSpawnRegions;
+ for (auto const& baseSpawnRegion : baseSpawnRegions) {
+ List<Vec2I> regions = {baseSpawnRegion.first};
+ for (auto splitPoint : splitPoints) {
+ auto prevRegions = take(regions);
+ for (auto const& region : prevRegions) {
+ if (splitPoint > region[0] && splitPoint < region[1]) {
+ regions.append({region[0], splitPoint});
+ regions.append({splitPoint, region[1]});
+ } else {
+ regions.append(region);
+ }
+ }
+ }
+ for (auto const& region : regions)
+ splitSpawnRegions.append({region, baseSpawnRegion.second});
+ }
+
+ // Sort the split spawn regions by leftmost point then height, preparing to
+ // remove the lower overlapping sections.
+ sort(splitSpawnRegions,
+ [](pair<Vec2I, int> const& lhs, pair<Vec2I, int> rhs) {
+ return tie(lhs.first[0], lhs.second) < tie(rhs.first[0], rhs.second);
+ });
+
+ // For each region, at this point, if the region to the right shares the
+ // same starting X, because we've split up each region on each possible
+ // overlapping point, then they totally overlap. The lower region (which
+ // should come before in the list) is totally redundant and should be
+ // removed.
+ auto sit = makeSMutableIterator(splitSpawnRegions);
+ while (sit.hasNext()) {
+ auto const& leftRegion = sit.next();
+ if (sit.hasNext()) {
+ auto const& rightRegion = sit.peekNext();
+ if (leftRegion.first[0] == rightRegion.first[0])
+ sit.remove();
+ }
+ }
+
+ for (auto const& spawnRegion : splitSpawnRegions) {
+ RectF spawnRect = RectF(spawnRegion.first[0],
+ spawnRegion.second,
+ spawnRegion.first[1],
+ spawnRegion.second + projectileConfig.spawnAboveRegion);
+
+ // Figure out a good target value based on the rate per x tile, making
+ // sure to handle very low count values appropriately on average.
+ float count = projectileConfig.ratePerX * spawnRect.width() * dt * m_currentWeatherIntensity;
+ if (Random::randf() > fpart(count))
+ count = floor(count);
+ else
+ count = ceil(count);
+
+ for (int i = 0; i < count; ++i) {
+ Vec2F position = {Random::randf() * spawnRect.width() + spawnRect.xMin(), Random::randf() * spawnRect.height() + spawnRect.yMin()};
+
+ if (position[1] > m_undergroundLevel && (!m_weatherEffectsActiveQuery || m_weatherEffectsActiveQuery(Vec2I::floor(position)))) {
+ // Make sure not to spawn projectiles if they intersect any client
+ // visible region.
+ bool intersectsVisibleRegion = false;
+ for (auto const& visibleRegion : m_clientVisibleRegions) {
+ if (RectF(visibleRegion).contains(position)) {
+ intersectsVisibleRegion = true;
+ break;
+ }
+ }
+
+ if (!intersectsVisibleRegion) {
+ auto newProjectile = projectileDatabase->createProjectile(projectileConfig.projectile, projectileConfig.parameters);
+ newProjectile->setInitialPosition(position);
+ newProjectile->setInitialVelocity(projectileConfig.velocity + Vec2F(projectileConfig.windAffectAmount * wind(), 0));
+ newProjectile->setTeam(EntityDamageTeam(TeamType::Environment));
+ m_newProjectiles.append(newProjectile);
+ }
+ }
+ }
+ }
+ }
+}
+
+ClientWeather::ClientWeather() {
+ m_undergroundLevel = 0.0f;
+ m_currentWeatherIndex = NPos;
+ m_currentWeatherIntensity = 0.0f;
+ m_currentWind = 0.0f;
+ m_currentTime = 0.0;
+
+ m_netGroup.addNetElement(&m_weatherPoolNetState);
+ m_netGroup.addNetElement(&m_undergroundLevelNetState);
+ m_netGroup.addNetElement(&m_currentWeatherIndexNetState);
+ m_netGroup.addNetElement(&m_currentWeatherIntensityNetState);
+ m_netGroup.addNetElement(&m_currentWindNetState);
+}
+
+void ClientWeather::setup(WorldGeometry worldGeometry, WeatherEffectsActiveQuery weatherEffectsActiveQuery) {
+ m_worldGeometry = worldGeometry;
+ m_weatherEffectsActiveQuery = weatherEffectsActiveQuery;
+ m_currentTime = 0.0;
+}
+
+void ClientWeather::readUpdate(ByteArray data) {
+ if (!data.empty()) {
+ m_netGroup.readNetState(move(data));
+ getNetStates();
+ }
+}
+
+void ClientWeather::setVisibleRegion(RectI visibleRegion) {
+ m_visibleRegion = visibleRegion;
+}
+
+void ClientWeather::update() {
+ m_currentTime += WorldTimestep;
+
+ if (m_currentWeatherIndex == NPos) {
+ m_currentWeatherType = {};
+ } else {
+ if (m_visibleRegion.yMax() > m_undergroundLevel)
+ m_currentWeatherType = Root::singleton().biomeDatabase()->weatherType(m_weatherPool.item(m_currentWeatherIndex));
+ else
+ m_currentWeatherType = {};
+ }
+
+ if (m_currentWeatherType)
+ spawnWeatherParticles(RectF(m_visibleRegion), WorldTimestep);
+}
+
+float ClientWeather::wind() const {
+ return m_currentWind * m_currentWeatherIntensity;
+}
+
+float ClientWeather::weatherIntensity() const {
+ return m_currentWeatherIntensity;
+}
+
+StringList ClientWeather::statusEffects() const {
+ if (m_currentWeatherIntensity == 1.0 && m_currentWeatherType)
+ return m_currentWeatherType->statusEffects;
+ return {};
+}
+
+List<Particle> ClientWeather::pullNewParticles() {
+ return take(m_particles);
+}
+
+StringList ClientWeather::weatherTrackOptions() const {
+ if (m_currentWeatherType)
+ return m_currentWeatherType->weatherNoises;
+ return {};
+}
+
+void ClientWeather::getNetStates() {
+ if (m_weatherPoolNetState.pullUpdated())
+ m_weatherPool = WeatherPool(DataStreamBuffer::deserializeContainer<WeatherPool::ItemsList>(m_weatherPoolNetState.get()));
+ m_undergroundLevel = m_undergroundLevelNetState.get();
+ m_currentWeatherIndex = m_currentWeatherIndexNetState.get();
+ m_currentWeatherIntensity = m_currentWeatherIntensityNetState.get();
+ m_currentWind = m_currentWindNetState.get();
+}
+
+void ClientWeather::spawnWeatherParticles(RectF newClientRegion, float dt) {
+ if (!m_currentWeatherType)
+ return;
+
+ for (auto const& particleConfig : m_currentWeatherType->particles) {
+ // Move client region to same wrap region as newClientRegion
+ RectF visibleRegion(m_worldGeometry.nearestTo(newClientRegion.min(), m_lastParticleVisibleRegion.min()),
+ m_worldGeometry.nearestTo(newClientRegion.min(), m_lastParticleVisibleRegion.max()));
+
+ Vec2F targetVelocity = particleConfig.particle.velocity + Vec2F(wind(), 0);
+ float angleChange = Vec2F::angleBetween2(Vec2F(0, 1), targetVelocity);
+ visibleRegion.translate(targetVelocity * dt);
+
+ for (auto const& renderZone : newClientRegion.subtract(visibleRegion)) {
+ float count = particleConfig.density * renderZone.width() * renderZone.height() * m_currentWeatherIntensity;
+ if (Random::randf() > fpart(count))
+ count = std::floor(count);
+ else
+ count = std::ceil(count);
+
+ for (int i = 0; i < count; ++i) {
+ auto newParticle = particleConfig.particle;
+ float x = Random::randf() * renderZone.width() + renderZone.xMin();
+ float y = Random::randf() * renderZone.height() + renderZone.yMin();
+ newParticle.position += m_worldGeometry.xwrap(Vec2F(x, y));
+ newParticle.velocity = targetVelocity;
+ if (y > m_undergroundLevel && (!m_weatherEffectsActiveQuery || m_weatherEffectsActiveQuery(Vec2I::floor(newParticle.position)))) {
+ if (particleConfig.autoRotate)
+ newParticle.rotation += angleChange;
+ m_particles.append(move(newParticle));
+ }
+ }
+ }
+ }
+
+ m_lastParticleVisibleRegion = newClientRegion;
+}
+
+}