#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(biomes, toClimateConfig(config.climate))), crops(config.worldSeed, config.farming), randomTick(config.worldSeed, config.randomTicksPerChunk), scheduler(config.worldSeed, config.blockRandomTicksPerChunk), redstone(std::make_unique(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(dims.x) * static_cast(dims.y) * static_cast(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 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 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(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(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 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