Files
voxelforge/core/src/WorldSim.cpp
T

207 lines
7.2 KiB
C++

#include "vf/WorldSim.h"
#include "vf/Climate.h"
namespace vf {
namespace {
long long atLeastOne(long long value) { return value < 1 ? 1 : value; }
ClimateConfig toClimateConfig(const WorldClimateSettings& settings) {
ClimateConfig config;
config.seaLevel = settings.seaLevel;
config.heightLapse = settings.heightLapse;
config.latitudeLapse = settings.latitudeLapse;
config.freezeTemperature = settings.freezeTemperature;
config.meltTemperature = settings.meltTemperature;
config.frostSpreadTemperature = settings.frostSpreadTemperature;
config.maxSnowDepth = settings.maxSnowDepth;
config.packThreshold = settings.packThreshold;
return config;
}
uint64_t fnv1a(uint64_t hash, uint8_t byte) {
hash ^= byte;
hash *= 1099511628211ull;
return hash;
}
} // namespace
WorldSim::WorldSim(World& world, const Vec3i& regionOrigin, const Vec3i& regionDims,
const WorldSimConfig& config)
: world(world),
origin(regionOrigin),
dims(regionDims),
tickCounter(0),
weatherInterval(atLeastOne(config.weatherIntervalTicks)),
climateInterval(atLeastOne(config.climateIntervalTicks)),
logisticsInterval(atLeastOne(config.logisticsIntervalTicks)),
mobDt(config.mobDt),
clock(config.startTick, config.time),
sky(config.worldSeed, config.weather),
biomes(config.biome),
weatherClimate(std::make_unique<Climate>(biomes, toClimateConfig(config.climate))),
crops(config.worldSeed, config.farming),
randomTick(config.worldSeed, config.randomTicksPerChunk),
scheduler(config.worldSeed, config.blockRandomTicksPerChunk),
redstone(std::make_unique<RedstoneCircuit>(world, regionOrigin, regionDims)),
cartConfig(config.cart),
creatureConfig(config.creature),
poweredRail([](const Vec3i&) { return false; }) {
scheduler.setRandomTickHook([](World& w, const Vec3i& pos, Block block) {
if (block == Block::Dirt) tryGrowGrass(w, pos);
});
std::size_t volume = static_cast<std::size_t>(dims.x) * static_cast<std::size_t>(dims.y) *
static_cast<std::size_t>(dims.z);
baseline.resize(volume);
clearDirty();
}
WorldSim::~WorldSim() = default;
int WorldSim::localIndexOf(int gx, int gy, int gz) const {
int lx = gx - origin.x;
int ly = gy - origin.y;
int lz = gz - origin.z;
return (ly * dims.z + lz) * dims.x + lx;
}
void WorldSim::step(long long ticks) {
for (long long i = 0; i < ticks; ++i) {
++tickCounter;
clock.advance(1);
if (tickCounter % weatherInterval == 0) sky.advance(weatherInterval);
runFarmingRandomTicks();
scheduler.tick(world);
if (tickCounter % climateInterval == 0) runClimate();
redstone->tick();
redstone->applyLamps();
if (tickCounter % logisticsInterval == 0) runLogistics();
runMinecarts();
runCreatures();
}
recomputeDirty();
}
void WorldSim::runFarmingRandomTicks() {
int skyLight = clock.skyLightLevel();
Vec3i chunkMin{floorDiv(origin.x, chunkSize), floorDiv(origin.y, chunkSize),
floorDiv(origin.z, chunkSize)};
Vec3i chunkMax{floorDiv(origin.x + dims.x - 1, chunkSize),
floorDiv(origin.y + dims.y - 1, chunkSize),
floorDiv(origin.z + dims.z - 1, chunkSize)};
std::vector<Vec3i> cells;
for (int cx = chunkMin.x; cx <= chunkMax.x; ++cx)
for (int cy = chunkMin.y; cy <= chunkMax.y; ++cy)
for (int cz = chunkMin.z; cz <= chunkMax.z; ++cz) {
randomTick.sampleGlobal(Vec3i{cx, cy, cz}, tickCounter, cells);
for (const Vec3i& cell : cells) crops.randomTick(world, cell, skyLight);
}
}
void WorldSim::runClimate() {
Precipitation precip = currentPrecipitation();
std::vector<ClimateChange> changes = weatherClimate->tick(world, origin, dims, precip);
Climate::apply(world, changes);
}
void WorldSim::runLogistics() {
for (int gx = origin.x; gx < origin.x + dims.x; ++gx)
for (int gy = origin.y; gy < origin.y + dims.y; ++gy)
for (int gz = origin.z; gz < origin.z + dims.z; ++gz) {
Vec3i pos{gx, gy, gz};
switch (world.blockAt(gx, gy, gz)) {
case Block::Hopper:
tickHopper(world, pos, hopperStates[pos], store);
break;
case Block::Dispenser:
tickDispenser(world, pos, dispenserStates[pos], store, *redstone);
break;
case Block::Dropper:
tickDropper(world, pos, dispenserStates[pos], store, *redstone);
break;
default:
break;
}
}
}
void WorldSim::runMinecarts() {
if (carts.empty()) return;
stepMinecarts(world, carts, cartConfig, poweredRail, store);
}
void WorldSim::runCreatures() {
for (Creature& creature : mobs)
updateCreature(world, creature, player, hasPlayer, creatureConfig, mobDt);
}
void WorldSim::recomputeDirty() {
dirtyList.clear();
for (int gx = origin.x; gx < origin.x + dims.x; ++gx)
for (int gy = origin.y; gy < origin.y + dims.y; ++gy)
for (int gz = origin.z; gz < origin.z + dims.z; ++gz) {
int idx = localIndexOf(gx, gy, gz);
if (world.blockAt(gx, gy, gz) != baselineBlock(idx))
dirtyList.push_back(Vec3i{gx, gy, gz});
}
}
void WorldSim::clearDirty() {
for (int gx = origin.x; gx < origin.x + dims.x; ++gx)
for (int gy = origin.y; gy < origin.y + dims.y; ++gy)
for (int gz = origin.z; gz < origin.z + dims.z; ++gz)
baseline[static_cast<std::size_t>(localIndexOf(gx, gy, gz))] =
world.blockAt(gx, gy, gz);
dirtyList.clear();
}
uint64_t WorldSim::regionHash() const {
uint64_t hash = 1469598103934665603ull;
for (int gx = origin.x; gx < origin.x + dims.x; ++gx)
for (int gy = origin.y; gy < origin.y + dims.y; ++gy)
for (int gz = origin.z; gz < origin.z + dims.z; ++gz)
hash = fnv1a(hash, static_cast<uint8_t>(world.blockAt(gx, gy, gz)));
return hash;
}
int WorldSim::snowDepthAt(const Vec3i& pos) const { return weatherClimate->snowDepthAt(pos); }
int WorldSim::iceAgeAt(const Vec3i& pos) const { return weatherClimate->iceAgeAt(pos); }
float WorldSim::temperatureAt(int gx, int gy, int gz) const {
return weatherClimate->temperatureAt(gx, gy, gz);
}
Precipitation WorldSim::currentPrecipitation() const {
Vec3i center{origin.x + dims.x / 2, origin.y + dims.y / 2, origin.z + dims.z / 2};
float temperature = weatherClimate->temperatureAt(center.x, center.y, center.z);
return sky.precipitationAt(temperature);
}
void WorldSim::addMinecart(const Minecart& cart) { carts.push_back(cart); }
void WorldSim::setPoweredRailPredicate(std::function<bool(const Vec3i&)> predicate) {
poweredRail = std::move(predicate);
}
void WorldSim::addCreature(const Creature& creature) { mobs.push_back(creature); }
void WorldSim::setPlayer(const Vec3& pos) {
player = pos;
hasPlayer = true;
}
void WorldSim::clearPlayer() { hasPlayer = false; }
} // namespace vf