osb/source/game/StarWorldGeneration.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

1585 lines
64 KiB
C++

#include "StarWorldGeneration.hpp"
#include "StarWorldServer.hpp"
#include "StarMaterialItem.hpp"
#include "StarMaterialDatabase.hpp"
#include "StarNpcDatabase.hpp"
#include "StarMonsterDatabase.hpp"
#include "StarNpc.hpp"
#include "StarBiome.hpp"
#include "StarSky.hpp"
#include "StarWorldTemplate.hpp"
#include "StarBiomePlacement.hpp"
#include "StarWireEntity.hpp"
#include "StarItemDrop.hpp"
#include "StarLogging.hpp"
#include "StarRoot.hpp"
#include "StarItemDatabase.hpp"
#include "StarProjectileDatabase.hpp"
#include "StarProjectile.hpp"
#include "StarObjectDatabase.hpp"
#include "StarObject.hpp"
#include "StarContainerObject.hpp"
#include "StarMonster.hpp"
#include "StarEntityMap.hpp"
#include "StarPlant.hpp"
#include "StarLiquidsDatabase.hpp"
#include "StarStagehand.hpp"
#include "StarVehicleDatabase.hpp"
namespace Star {
static int const PlantAdjustmentLimit = 2;
LiquidWorld::LiquidWorld(WorldServer* world) {
m_worldServer = world;
auto& root = Root::singleton();
m_liquidsDatabase = root.liquidsDatabase();
m_materialDatabase = root.materialDatabase();
}
Vec2I LiquidWorld::uniqueLocation(Vec2I const& location) const {
return m_worldServer->geometry().xwrap(location);
}
float LiquidWorld::drainLevel(Vec2I const& location) const {
if (location[1] > m_worldServer->worldTemplate()->undergroundLevel()) {
auto const& tile = m_worldServer->getServerTile(location);
if (!m_materialDatabase->blocksLiquidFlow(tile.background)) {
auto const& belowTile = m_worldServer->getServerTile(location + Vec2I(0, -1));
if (m_materialDatabase->blocksLiquidFlow(belowTile.background) || m_materialDatabase->blocksLiquidFlow(belowTile.foreground) || belowTile.liquid.source)
return m_liquidsDatabase->backgroundDrain();
}
}
return 0.0f;
}
CellularLiquidCell<LiquidId> LiquidWorld::cell(Vec2I const& location) const {
auto const& tile = m_worldServer->getServerTile(location);
if (m_materialDatabase->blocksLiquidFlow(tile.foreground)) {
return CellularLiquidCollisionCell();
} else {
if (tile.liquid.source)
return CellularLiquidSourceCell<LiquidId>{tile.liquid.liquid, tile.liquid.pressure};
else if (tile.liquid.liquid != EmptyLiquidId)
return CellularLiquidFlowCell<LiquidId>{tile.liquid.liquid, tile.liquid.level, tile.liquid.pressure};
else
return CellularLiquidFlowCell<LiquidId>{{}, 0.0f, 0.0f};
}
}
void LiquidWorld::setFlow(Vec2I const& location, CellularLiquidFlowCell<LiquidId> const& flow) {
if (flow.liquid) {
m_worldServer->setLiquid(location, *flow.liquid, flow.level, flow.pressure);
auto const& tile = m_worldServer->getServerTile(location);
if (auto materialInteraction = m_materialDatabase->liquidMaterialInteraction(*flow.liquid, tile.background)) {
if (!materialInteraction->topOnly && tile.liquid.level >= materialInteraction->consumeLiquid) {
if (auto modifyTile = m_worldServer->modifyServerTile(location)) {
modifyTile->liquid.take(materialInteraction->consumeLiquid);
modifyTile->background = materialInteraction->transformTo;
m_worldServer->activateLiquidLocation(location);
}
}
}
if (auto modInteraction = m_materialDatabase->liquidModInteraction(*flow.liquid, tile.backgroundMod)) {
if (!modInteraction->topOnly && tile.liquid.level >= modInteraction->consumeLiquid) {
if (auto modifyTile = m_worldServer->modifyServerTile(location)) {
modifyTile->liquid.take(modInteraction->consumeLiquid);
modifyTile->backgroundMod = modInteraction->transformTo;
m_worldServer->activateLiquidLocation(location);
}
}
}
} else {
m_worldServer->setLiquid(location, EmptyLiquidId, 0.0f, 0.0f);
}
}
void LiquidWorld::liquidInteraction(Vec2I const& a, LiquidId aLiquid, Vec2I const& b, LiquidId bLiquid) {
auto handleInteraction = [this](Vec2I const& target, Maybe<LiquidInteractionResult> interaction) {
if (interaction) {
if (interaction->isLeft()) {
m_worldServer->modifyTile(target, PlaceMaterial{TileLayer::Foreground, interaction->left(), 0}, false);
} else {
auto liquidLevel = m_worldServer->liquidLevel(target);
m_worldServer->setLiquid(target, interaction->right(), liquidLevel.level, liquidLevel.level);
}
}
};
handleInteraction(a, m_liquidsDatabase->interact(aLiquid, bLiquid));
handleInteraction(b, m_liquidsDatabase->interact(bLiquid, aLiquid));
}
void LiquidWorld::liquidCollision(Vec2I const& liquidPos, LiquidId liquidId, Vec2I const& blockPos) {
auto const& blockTile = m_worldServer->getServerTile(blockPos);
if (auto materialInteraction = m_materialDatabase->liquidMaterialInteraction(liquidId, blockTile.foreground)) {
if ((!materialInteraction->topOnly || liquidPos[1] > blockPos[1]) && m_worldServer->liquidLevel(liquidPos).level >= materialInteraction->consumeLiquid) {
auto modifyLiquidTile = m_worldServer->modifyServerTile(liquidPos);
auto modifyBlockTile = m_worldServer->modifyServerTile(blockPos);
if (modifyLiquidTile && modifyBlockTile) {
modifyLiquidTile->liquid.take(materialInteraction->consumeLiquid);
modifyBlockTile->foreground = materialInteraction->transformTo;
if (!m_materialDatabase->isMultiColor(materialInteraction->transformTo))
modifyBlockTile->foregroundColorVariant = DefaultMaterialColorVariant;
m_worldServer->activateLiquidLocation(liquidPos);
}
}
}
if (auto modInteraction = m_materialDatabase->liquidModInteraction(liquidId, blockTile.foregroundMod)) {
if ((!modInteraction->topOnly || liquidPos[1] > blockPos[1]) && m_worldServer->liquidLevel(liquidPos).level >= modInteraction->consumeLiquid) {
auto modifyLiquidTile = m_worldServer->modifyServerTile(liquidPos);
auto modifyBlockTile = m_worldServer->modifyServerTile(blockPos);
if (modifyLiquidTile && modifyBlockTile) {
modifyLiquidTile->liquid.take(modInteraction->consumeLiquid);
modifyBlockTile->foregroundMod = modInteraction->transformTo;
m_worldServer->activateLiquidLocation(liquidPos);
}
}
}
}
FallingBlocksWorld::FallingBlocksWorld(WorldServer* w)
: m_worldServer(w), m_materialDatabase(Root::singleton().materialDatabase()) {}
FallingBlockType FallingBlocksWorld::blockType(Vec2I const& pos) {
auto const& tile = m_worldServer->getServerTile(pos, true);
if (tile.rootSource) {
return FallingBlockType::Immovable;
} if (tile.foreground == EmptyMaterialId) {
if (m_worldServer->tileIsOccupied(pos, TileLayer::Foreground))
return FallingBlockType::Immovable;
else
return FallingBlockType::Open;
} else if (m_materialDatabase->isCascadingFallingMaterial(tile.foreground)) {
return FallingBlockType::Cascading;
} else if (m_materialDatabase->isFallingMaterial(tile.foreground)) {
return FallingBlockType::Falling;
} else {
return FallingBlockType::Immovable;
}
}
void FallingBlocksWorld::moveBlock(Vec2I const& from, Vec2I const& to) {
auto fromTile = m_worldServer->modifyServerTile(from, true);
auto toTile = m_worldServer->modifyServerTile(to, true);
if (!fromTile || !toTile)
return;
if (m_worldServer->isTileProtected(to)) {
for (auto drop : m_worldServer->destroyBlock(TileLayer::Foreground, from, true, true))
m_worldServer->addEntity(ItemDrop::createRandomizedDrop(drop, Vec2F(to)));
} else {
toTile->foreground = fromTile->foreground;
toTile->foregroundMod = NoModId;
toTile->foregroundHueShift = fromTile->foregroundHueShift;
toTile->foregroundColorVariant = fromTile->foregroundColorVariant;
toTile->updateCollision(m_materialDatabase->materialCollisionKind(toTile->foreground));
fromTile->foreground = EmptyMaterialId;
fromTile->foregroundMod = NoModId;
fromTile->updateCollision(CollisionKind::None);
m_worldServer->requestGlobalBreakCheck();
}
}
DungeonGeneratorWorld::DungeonGeneratorWorld(WorldServer* worldServer, bool markForActivation)
: m_worldServer(worldServer), m_markForActivation(markForActivation) {}
WorldGeometry DungeonGeneratorWorld::getWorldGeometry() const {
return m_worldServer->geometry();
}
void DungeonGeneratorWorld::markRegion(RectI const& region) {
if (!m_markForActivation)
return;
Logger::debug("Marking {} as dungeon region", region);
m_worldServer->signalRegion(region);
m_worldServer->activateLiquidRegion(region);
}
void DungeonGeneratorWorld::markTerrain(PolyF const& region) {
if (!m_markForActivation)
return;
Logger::debug("Marking poly as dungeon terrain region: {}", region);
m_worldServer->worldTemplate()->addCustomTerrainRegion(region);
}
void DungeonGeneratorWorld::markSpace(PolyF const& region) {
if (!m_markForActivation)
return;
Logger::debug("Marking poly as dungeon space region: {}", region);
m_worldServer->worldTemplate()->addCustomSpaceRegion(region);
}
void DungeonGeneratorWorld::setForegroundMaterial(Vec2I const& position, MaterialId material, MaterialHue hueshift, MaterialColorVariant colorVariant) {
if (ServerTile* tile = m_worldServer->modifyServerTile(position)) {
m_worldServer->modifyLiquid(position, EmptyLiquidId, 0);
tile->foreground = material;
tile->foregroundHueShift = hueshift;
tile->foregroundColorVariant = colorVariant;
tile->foregroundMod = NoModId;
tile->foregroundModHueShift = MaterialHue();
tile->collision = Root::singleton().materialDatabase()->materialCollisionKind(tile->foreground);
tile->collisionCacheDirty = true;
}
}
void DungeonGeneratorWorld::setBackgroundMaterial(Vec2I const& position, MaterialId material, MaterialHue hueshift, MaterialColorVariant colorVariant) {
if (ServerTile* tile = m_worldServer->modifyServerTile(position)) {
m_worldServer->modifyLiquid(position, EmptyLiquidId, 0);
tile->background = material;
tile->backgroundHueShift = hueshift;
tile->backgroundColorVariant = colorVariant;
tile->backgroundMod = NoModId;
tile->backgroundModHueShift = MaterialHue();
}
}
void DungeonGeneratorWorld::placeObject(Vec2I const& pos, String const& objectName, Star::Direction direction, Json const& parameters) {
m_worldServer->signalRegion(RectI::withSize(pos, {1, 1}));
auto objectDatabase = Root::singleton().objectDatabase();
if (auto object = objectDatabase->createForPlacement(m_worldServer, objectName, pos, direction, parameters))
m_worldServer->addEntity(object);
else
Logger::warn("Failed to place dungeon object: {} direction: {} position: {}", objectName, (int)direction, pos);
}
void DungeonGeneratorWorld::placeVehicle(Vec2F const& pos, String const& vehicleName, Json const& parameters) {
m_worldServer->signalRegion(RectI::withSize(Vec2I(pos), {1, 1}));
auto vehicleDatabase = Root::singleton().vehicleDatabase();
auto vehicle = vehicleDatabase->create(vehicleName, parameters.opt().value(JsonObject{}).set("persistent", true));
vehicle->setPosition(pos);
m_worldServer->addEntity(vehicle);
}
void DungeonGeneratorWorld::placeSurfaceBiomeItems(Vec2I const& pos) {
List<BiomeItemPlacement> surfaceItems = m_worldServer->worldTemplate()->potentialBiomeItemsAt(pos[0], pos[1]).surfaceBiomeItems;
placeBiomeItems(pos, surfaceItems);
}
void DungeonGeneratorWorld::placeBiomeTree(Vec2I const& pos) {
if (auto biome = m_worldServer->worldTemplate()->blockBiome(pos[0], pos[1])) {
m_worldServer->signalRegion(RectI::withSize(pos, {1, 1}));
auto seed = m_worldServer->worldTemplate()->seedFor(pos[0], pos[1]);
if (auto treeVariant = biome->surfacePlaceables.firstTreeType())
placePlant(Root::singleton().plantDatabase()->createPlant(*treeVariant, seed), pos);
}
}
// yay, copy paste coding, kyren WILL kill me
void DungeonGeneratorWorld::placePlant(PlantPtr const& plant, Vec2I const& position) {
if (!plant)
return;
auto spaces = plant->spaces();
auto roots = plant->roots();
auto const& primaryRoot = plant->primaryRoot();
auto background = m_worldServer->getServerTile(position).background;
bool adjustBackground = background == EmptyMaterialId || background == NullMaterialId;
auto withinAdjustment = [=](Vec2I const& pos) {
return PlantAdjustmentLimit - std::abs(pos[0]) > 0 && PlantAdjustmentLimit - std::abs(pos[1]) > 0;
};
// Bail out if we don't have at least one free space, and root in the primary
// root position, or if we're in a dungeon region.
auto primaryTile = m_worldServer->getServerTile(position);
auto rootTile = m_worldServer->getServerTile(position + primaryRoot);
if (isConnectableMaterial(primaryTile.foreground) || !isConnectableMaterial(rootTile.foreground))
return;
// First bail out if we can't fit anything we're not adjusting
for (auto space : spaces) {
Vec2I pspace = space + position;
if (withinAdjustment(space) && !m_worldServer->atTile<Plant>(pspace).empty())
return;
// Bail out if we hit a different plant's root tile, or if we're not in the
// adjustment space and we hit a non-empty tile.
auto tile = m_worldServer->getServerTile(pspace);
if (tile.rootSource || (!withinAdjustment(space) && !(tile.foreground == EmptyMaterialId || tile.foreground == NullMaterialId)))
return;
}
// Check all the roots outside of the adjustment limit
for (auto root : roots) {
root += position;
if (!withinAdjustment(root) && !isConnectableMaterial(m_worldServer->getServerTile(root).foreground))
return;
}
// Clear all the necessary blocks within the adjustment limit
for (auto space : spaces) {
if (!withinAdjustment(space))
continue;
space += position;
if (auto tile = m_worldServer->modifyServerTile(space)) {
if (isConnectableMaterial(tile->foreground))
*tile = primaryTile;
if (adjustBackground)
tile->background = EmptyMaterialId;
tile->collision = CollisionKind::None;
tile->collision = Root::singleton().materialDatabase()->materialCollisionKind(tile->foreground);
tile->collisionCacheDirty = true;
} else {
return;
}
}
// Make all the root blocks a real material based on the primary root.
for (auto root : roots) {
root += position;
if (auto tile = m_worldServer->modifyServerTile(root)) {
if (!isRealMaterial(tile->foreground)) {
*tile = rootTile;
tile->collision = Root::singleton().materialDatabase()->materialCollisionKind(tile->foreground);
tile->collisionCacheDirty = true;
}
} else {
return;
}
}
plant->setTilePosition(position);
m_worldServer->addEntity(plant);
return;
}
void DungeonGeneratorWorld::placeBiomeItems(Vec2I const& pos, List<BiomeItemPlacement>& potentialItems) {
m_worldServer->signalRegion(RectI::withSize(pos, {1, 1}));
sort(potentialItems);
for (auto const& placement : potentialItems) {
auto seed = m_worldServer->worldTemplate()->seedFor(placement.position[0], placement.position[1]);
if (placement.item.is<GrassVariant>()) {
auto& grass = placement.item.get<GrassVariant>();
placePlant(Root::singleton().plantDatabase()->createPlant(grass, seed), placement.position);
} else if (placement.item.is<BushVariant>()) {
auto& bush = placement.item.get<BushVariant>();
placePlant(Root::singleton().plantDatabase()->createPlant(bush, seed), placement.position);
} else if (placement.item.is<TreePair>()) {
auto& treePair = placement.item.get<TreePair>();
TreeVariant treeVariant;
if (seed % 2 == 0)
treeVariant = treePair.first;
else
treeVariant = treePair.second;
placePlant(Root::singleton().plantDatabase()->createPlant(treeVariant, seed), placement.position);
} else if (placement.item.is<ObjectPool>()) {
auto& objectPool = placement.item.get<ObjectPool>();
auto direction = seed % 2 ? Direction::Left : Direction::Right;
auto objectPair = objectPool.select(seed);
if (auto object = Root::singleton().objectDatabase()->createForPlacement(
m_worldServer, objectPair.first, placement.position, direction, objectPair.second))
m_worldServer->addEntity(object);
} else if (placement.item.is<TreasureBoxSet>()) {
auto& treasureBoxSet = placement.item.get<TreasureBoxSet>();
auto direction = seed % 2 ? Direction::Left : Direction::Right;
if (auto treasureContainer = Root::singleton().treasureDatabase()->createTreasureChest(m_worldServer, treasureBoxSet, placement.position, direction, seed))
m_worldServer->addEntity(treasureContainer);
}
}
}
void DungeonGeneratorWorld::addDrop(Vec2F const& position, ItemDescriptor const& item) {
m_worldServer->addEntity(ItemDrop::createRandomizedDrop(item, position));
}
void DungeonGeneratorWorld::spawnNpc(Vec2F const& position, Json const& parameters) {
auto kind = parameters.getString("kind");
if (kind.equals("npc", String::CaseInsensitive)) {
auto npcDatabase = Root::singleton().npcDatabase();
uint64_t seed = parameters.getUInt("seed", Random::randu64());
String species = parameters.getString("species");
String typeName = parameters.getString("typeName", "default");
JsonObject uniqueParameters = parameters.getObject("parameters", {});
if (!uniqueParameters.contains("persistent"))
uniqueParameters["persistent"] = true;
auto npc = npcDatabase->createNpc(npcDatabase->generateNpcVariant(species, typeName, m_worldServer->threatLevel(), seed, uniqueParameters));
npc->setPosition(position - npc->feetOffset());
m_worldServer->addEntity(npc);
} else if (kind.equals("monster", String::CaseInsensitive)) {
auto monsterDatabase = Root::singleton().monsterDatabase();
uint64_t seed = parameters.getUInt("seed", Random::randu64());
String typeName = parameters.getString("typeName");
JsonObject uniqueParameters = parameters.getObject("parameters", {});
if (!uniqueParameters.contains("persistent"))
uniqueParameters["persistent"] = true;
auto monster = monsterDatabase->createMonster(monsterDatabase->monsterVariant(typeName, seed, uniqueParameters));
monster->setPosition(position);
m_worldServer->addEntity(monster);
} else
throw StarException(strf("Unknown spawnable kind '{}'", kind));
}
void DungeonGeneratorWorld::spawnStagehand(Vec2F const& position, Json const& definition) {
auto stagehand = Root::singleton().stagehandDatabase()->createStagehand(definition.getString("type"), definition.get("parameters", Json()));
stagehand->setPosition(position);
m_worldServer->addEntity(stagehand);
}
void DungeonGeneratorWorld::setLiquid(Vec2I const& pos, LiquidStore const& liquid) {
ServerTile* tile = m_worldServer->modifyServerTile(pos);
starAssert(tile);
if (tile)
tile->liquid = liquid;
}
void DungeonGeneratorWorld::setPlayerStart(Vec2F const& startPosition) {
m_worldServer->setPlayerStart(startPosition);
}
void DungeonGeneratorWorld::connectWireGroup(List<Vec2I> const& wireGroup) {
List<WireConnection> outbounds;
List<WireConnection> inbounds;
for (auto entry : wireGroup) {
bool found = false;
Vec2F posf = centerOfTile(entry);
RectF bounds = {posf - Vec2F(16, 16), posf + Vec2F(16, 16)};
for (auto entity : m_worldServer->query<WireEntity>(bounds)) {
for (size_t i = 0; i < entity->nodeCount(WireDirection::Input); ++i) {
if (entity->tilePosition() + entity->nodePosition({WireDirection::Input, i}) == entry) {
inbounds.append(WireConnection{entity->tilePosition(), i});
found = true;
}
}
for (size_t i = 0; i < entity->nodeCount(WireDirection::Output); ++i) {
if (entity->tilePosition() + entity->nodePosition({WireDirection::Output, i}) == entry) {
outbounds.append(WireConnection{entity->tilePosition(), i});
found = true;
}
}
}
if (!found)
Logger::warn("Dungeon wire endpoint not found. {}", entry);
}
if (!outbounds.size() || !inbounds.size()) {
Logger::warn("Dungeon wires did not make a circuit.");
return;
}
for (auto outbound : outbounds) {
auto out = m_worldServer->atTile<WireEntity>(outbound.entityLocation).first();
for (auto inbound : inbounds) {
auto in = m_worldServer->atTile<WireEntity>(inbound.entityLocation).first();
in->addNodeConnection({WireDirection::Input, inbound.nodeIndex}, outbound);
out->addNodeConnection({WireDirection::Output, outbound.nodeIndex}, inbound);
}
}
}
void DungeonGeneratorWorld::setForegroundMod(Vec2I const& position, ModId mod, MaterialHue hueshift) {
ServerTile* tile = m_worldServer->modifyServerTile(position);
if (tile) {
tile->foregroundMod = mod;
tile->foregroundModHueShift = hueshift;
}
}
void DungeonGeneratorWorld::setBackgroundMod(Vec2I const& position, ModId mod, MaterialHue hueshift) {
ServerTile* tile = m_worldServer->modifyServerTile(position);
if (tile) {
tile->backgroundMod = mod;
tile->foregroundModHueShift = hueshift;
}
}
void DungeonGeneratorWorld::setTileProtection(DungeonId dungeonId, bool isProtected) {
m_worldServer->setTileProtection(dungeonId, isProtected);
}
bool DungeonGeneratorWorld::checkSolid(Vec2I const& position, TileLayer layer) {
auto const& tile = m_worldServer->getServerTile(position);
return tile.material(layer) != EmptyMaterialId && tile.material(layer) != NullMaterialId;
}
bool DungeonGeneratorWorld::checkOpen(Vec2I const& position, TileLayer layer) {
auto const& tile = m_worldServer->getServerTile(position);
return tile.material(layer) == EmptyMaterialId || tile.material(layer) == NullMaterialId;
}
bool DungeonGeneratorWorld::checkOceanLiquid(Vec2I const& position) {
auto const& block = m_worldServer->worldTemplate()->blockInfo(position[0], position[1]);
return block.oceanLiquid != EmptyLiquidId && position[1] < block.oceanLiquidLevel;
}
DungeonId DungeonGeneratorWorld::getDungeonIdAt(Vec2I const& position) {
return m_worldServer->getServerTile(position).dungeonId;
}
void DungeonGeneratorWorld::setDungeonIdAt(Vec2I const& position, DungeonId dungeonId) {
if (auto tile = m_worldServer->modifyServerTile(position))
tile->dungeonId = dungeonId;
}
void DungeonGeneratorWorld::clearTileEntities(RectI const& bounds, Set<Vec2I> const& positions, bool clearAnchoredObjects) {
auto entities = m_worldServer->entityQuery(RectF(bounds).padded(1), entityTypeFilter<TileEntity>());
auto geometry = m_worldServer->geometry();
entities.filter([positions, geometry, clearAnchoredObjects](EntityPtr entity) {
auto tileEntity = as<TileEntity>(entity);
for (auto pos : tileEntity->spaces()) {
if (positions.contains(geometry.xwrap(pos + tileEntity->tilePosition())))
return true;
}
if (clearAnchoredObjects) {
for (auto pos : tileEntity->roots()) {
if (positions.contains(geometry.xwrap(pos + tileEntity->tilePosition())))
return true;
}
if (auto object = as<Object>(entity)) {
for (auto pos : object->anchorPositions()) {
if (positions.contains(geometry.xwrap(pos)))
return true;
}
}
}
return false;
});
for (auto entity : entities)
m_worldServer->removeEntity(entity->entityId(), false);
}
SpawnerWorld::SpawnerWorld(WorldServer* server)
: m_worldServer(server) {}
WorldGeometry SpawnerWorld::geometry() const {
return m_worldServer->geometry();
}
List<RectF> SpawnerWorld::clientWindows() const {
List<RectF> windows;
for (auto clientId : m_worldServer->clientIds())
windows.append(m_worldServer->clientWindow(clientId));
return windows;
}
bool SpawnerWorld::signalRegion(RectF const& region) const {
return m_worldServer->signalRegion(RectI::integral(region));
}
CollisionKind SpawnerWorld::collision(Vec2I const& position) const {
return m_worldServer->getServerTile(position + Vec2I(0, 1)).collision;
}
bool SpawnerWorld::isFreeSpace(RectF const& area) const {
return !m_worldServer->polyCollision(PolyF(area));
}
bool SpawnerWorld::isBackgroundEmpty(Vec2I const& pos) const {
return m_worldServer->getServerTile(pos).background == EmptyMaterialId;
}
LiquidLevel SpawnerWorld::liquidLevel(Vec2I const& position) const {
return m_worldServer->liquidLevel(position);
}
bool SpawnerWorld::spawningProhibited(RectF const& area) const {
RectI region = RectI::integral(area);
// Don't spawn the entity if its region overlaps with a dungeon
for (int x = region.xMin(); x < region.xMax(); ++x) {
for (int y = region.yMin(); y < region.yMax(); ++y) {
auto const& tile = m_worldServer->getServerTile({x, y});
if (tile.collision == CollisionKind::Null || tile.dungeonId != NoDungeonId)
return true;
}
}
return false;
}
uint64_t SpawnerWorld::spawnSeed() const {
return m_worldServer->worldTemplate()->worldSeed();
}
SpawnProfile SpawnerWorld::spawnProfile(Vec2F const& position) const {
Vec2I ipos = Vec2I::floor(position);
// Block biome, *not* environment biome, includes things like detached
// biomes.
if (auto biome = m_worldServer->worldTemplate()->blockBiome(ipos[0], ipos[1])) {
// Dungeons, including ConstructionDungeonId (player constructed areas)
// should be immune from spawning.
auto tile = m_worldServer->getServerTile(ipos);
if (tile.dungeonId == NoDungeonId)
return biome->spawnProfile;
}
return {};
}
float SpawnerWorld::dayLevel() const {
return m_worldServer->sky()->dayLevel();
}
float SpawnerWorld::threatLevel() const {
return m_worldServer->threatLevel();
}
EntityId SpawnerWorld::spawnEntity(EntityPtr entity) const {
m_worldServer->addEntity(entity);
return entity->entityId();
}
void SpawnerWorld::despawnEntity(EntityId entityId) {
m_worldServer->removeEntity(entityId, false);
}
EntityPtr SpawnerWorld::getEntity(EntityId entityId) const {
return m_worldServer->entity(entityId);
}
WorldGenerator::WorldGenerator(WorldServer* server) : m_worldServer(server) {
m_microDungeonFactory = make_shared<MicroDungeonFactory>();
}
void WorldGenerator::generateSectorLevel(WorldStorage* worldStorage, Sector const& sector, SectorGenerationLevel generationLevel) {
if (generationLevel == SectorGenerationLevel::BaseTiles) {
prepareTiles(worldStorage, sector);
} else if (generationLevel == SectorGenerationLevel::MicroDungeons) {
if (!worldStorage->floatingDungeonWorld())
generateMicroDungeons(worldStorage, sector);
} else if (generationLevel == SectorGenerationLevel::CaveLiquid) {
if (!worldStorage->floatingDungeonWorld())
generateCaveLiquid(worldStorage, sector);
} else if (generationLevel == SectorGenerationLevel::Finalize) {
if (!worldStorage->floatingDungeonWorld())
prepareSector(worldStorage, sector);
else
prepareSectorBiomeBlocks(worldStorage, sector);
m_worldServer->activateLiquidRegion(worldStorage->tileArray()->sectorRegion(sector));
}
}
void WorldGenerator::sectorLoadLevelChanged(WorldStorage* worldStorage, Sector const& sector, SectorLoadLevel loadLevel) {
if (loadLevel == SectorLoadLevel::Loaded) {
if (worldStorage->sectorGenerationLevel(sector) == SectorGenerationLevel::Complete)
m_worldServer->activateLiquidRegion(worldStorage->tileArray()->sectorRegion(sector));
}
}
void WorldGenerator::terraformSector(WorldStorage* worldStorage, Sector const& sector) {
// Logger::info("terraforming sector {}...", sector);
reapplyBiome(worldStorage, sector);
}
void WorldGenerator::initEntity(WorldStorage*, EntityId entityId, EntityPtr const& entity) {
entity->init(m_worldServer, entityId, EntityMode::Master);
if (auto tileEntity = as<TileEntity>(entity))
m_worldServer->updateTileEntityTiles(tileEntity, false, false);
}
void WorldGenerator::destructEntity(WorldStorage*, EntityPtr const& entity) {
if (entity->isSlave())
throw StarException("Cannot destruct slave entity in WorldStorage, something has gone wrong!");
if (auto tileEntity = as<TileEntity>(entity))
m_worldServer->updateTileEntityTiles(tileEntity, true, false);
entity->uninit();
}
bool WorldGenerator::entityKeepAlive(WorldStorage*, EntityPtr const& entity) const {
return entity->isSlave() || (entity->isMaster() && entity->keepAlive());
}
bool WorldGenerator::entityPersistent(WorldStorage*, EntityPtr const& entity) const {
return entity->isMaster() && entity->persistent();
}
RpcPromise<Vec2I> WorldGenerator::enqueuePlacement(List<BiomeItemDistribution> distributions, Maybe<DungeonId> id) {
auto promise = RpcPromise<Vec2I>::createPair();
m_queuedPlacements.append(QueuedPlacement {
std::move(distributions),
id,
promise.second,
false,
});
return promise.first;
}
void WorldGenerator::replaceBiomeBlocks(ServerTile* tile) {
if (auto blockBiome = m_worldServer->worldTemplate()->biome(tile->blockBiomeIndex)) {
if (tile->foreground == BiomeMaterialId) {
tile->foreground = blockBiome->mainBlock;
tile->foregroundHueShift = m_worldServer->worldTemplate()->biomeMaterialHueShift(tile->blockBiomeIndex, tile->foreground);
} else if ((tile->foreground >= Biome1MaterialId) && (tile->foreground <= Biome5MaterialId)) {
auto& subblocks = blockBiome->subBlocks;
if (subblocks.size())
tile->foreground = subblocks[(tile->foreground - Biome1MaterialId) % subblocks.size()];
else
tile->foreground = blockBiome->mainBlock;
tile->foregroundHueShift = m_worldServer->worldTemplate()->biomeMaterialHueShift(tile->blockBiomeIndex, tile->foreground);
}
if (tile->background == BiomeMaterialId) {
tile->background = blockBiome->mainBlock;
tile->backgroundHueShift =
m_worldServer->worldTemplate()->biomeMaterialHueShift(tile->blockBiomeIndex, tile->background);
} else if ((tile->background >= Biome1MaterialId) && (tile->background <= Biome5MaterialId)) {
auto& subblocks = blockBiome->subBlocks;
if (subblocks.size())
tile->background = subblocks[(tile->background - Biome1MaterialId) % subblocks.size()];
else
tile->background = blockBiome->mainBlock;
tile->backgroundHueShift = m_worldServer->worldTemplate()->biomeMaterialHueShift(tile->blockBiomeIndex, tile->background);
}
} else {
if (isBiomeMaterial(tile->foreground)) {
tile->foreground = EmptyMaterialId;
tile->foregroundHueShift = 0;
}
if (isBiomeMod(tile->foregroundMod)) {
tile->foregroundMod = NoModId;
tile->foregroundModHueShift = 0;
}
if (isBiomeMaterial(tile->background)) {
tile->background = EmptyMaterialId;
tile->backgroundHueShift = 0;
}
if (isBiomeMod(tile->backgroundMod)) {
tile->backgroundMod = NoModId;
tile->backgroundModHueShift = 0;
}
}
}
void WorldGenerator::prepareTiles(WorldStorage* worldStorage, ServerTileSectorArray::Sector const& sector) {
auto materialDatabase = Root::singleton().materialDatabase();
auto planet = m_worldServer->worldTemplate();
// Generate sector.
auto tileArray = worldStorage->tileArray();
RectI sectorRegion = tileArray->sectorRegion(sector);
for (int x = sectorRegion.xMin(); x < sectorRegion.xMax(); ++x) {
for (int y = sectorRegion.yMin(); y < sectorRegion.yMax(); ++y) {
Vec2I pos(x, y);
ServerTile* tile = tileArray->modifyTile(pos);
starAssert(tile);
if (!tile)
continue;
auto blockInfo = planet->blockInfo(pos[0], pos[1]);
tile->blockBiomeIndex = blockInfo.blockBiomeIndex;
tile->environmentBiomeIndex = blockInfo.environmentBiomeIndex;
tile->biomeTransition = blockInfo.biomeTransition;
if (tile->foreground == NullMaterialId) {
tile->foreground = blockInfo.foreground;
tile->foregroundColorVariant = DefaultMaterialColorVariant;
tile->foregroundHueShift = planet->biomeMaterialHueShift(tile->blockBiomeIndex, tile->foreground);
if (materialDatabase->supportsMod(tile->foreground, blockInfo.foregroundMod)) {
tile->foregroundMod = blockInfo.foregroundMod;
tile->foregroundModHueShift = planet->biomeModHueShift(tile->blockBiomeIndex, tile->foregroundMod);
}
}
if (tile->background == NullMaterialId) {
tile->background = blockInfo.background;
tile->backgroundColorVariant = DefaultMaterialColorVariant;
tile->backgroundHueShift = planet->biomeMaterialHueShift(tile->blockBiomeIndex, tile->background);
if (materialDatabase->supportsMod(tile->background, blockInfo.backgroundMod)) {
tile->backgroundMod = blockInfo.backgroundMod;
tile->backgroundModHueShift = planet->biomeModHueShift(tile->blockBiomeIndex, tile->backgroundMod);
}
}
if (tile->foreground != EmptyMaterialId) {
tile->liquid = LiquidStore();
} else if (blockInfo.oceanLiquid != EmptyLiquidId && pos[1] < blockInfo.oceanLiquidLevel) {
float pressure = blockInfo.oceanLiquidLevel - pos[1];
if (tile->background == EmptyMaterialId)
tile->liquid = LiquidStore::endless(blockInfo.oceanLiquid, pressure);
else if (blockInfo.encloseLiquids)
tile->liquid = LiquidStore::filled(blockInfo.oceanLiquid, 1.0f, pressure);
}
}
}
}
void WorldGenerator::generateMicroDungeons(WorldStorage* worldStorage, ServerTileSectorArray::Sector const& sector) {
auto facade = make_shared<DungeonGeneratorWorld>(m_worldServer, false);
RectI sectorTiles = worldStorage->tileArray()->sectorRegion(sector);
RectI bounds = sectorTiles.padded(WorldSectorSize - 1);
List<pair<BiomeItemPlacement, QueuedPlacement*>> placementQueue;
for (int x = sectorTiles.xMin(); x < sectorTiles.xMax(); ++x) {
for (int y = sectorTiles.yMin(); y < sectorTiles.yMax(); ++y) {
auto potential = m_worldServer->worldTemplate()->potentialBiomeItemsAt(x, y);
for (auto const& placement : m_worldServer->worldTemplate()->validBiomeItems(x, y, potential))
placementQueue.append({placement, {}});
for (auto& p : m_queuedPlacements) {
WorldTemplate::PotentialBiomeItems queuedItems;
m_worldServer->worldTemplate()->addPotentialBiomeItems(x, y, queuedItems, p.distributions, BiomePlacementArea::Surface);
m_worldServer->worldTemplate()->addPotentialBiomeItems(x, y, queuedItems, p.distributions, BiomePlacementArea::Underground);
for (auto placement : m_worldServer->worldTemplate()->validBiomeItems(x, y, queuedItems))
placementQueue.append({std::move(placement), &p});
}
}
}
sort(placementQueue);
for (auto const& p : placementQueue) {
auto& placement = p.first;
auto queued = p.second;
if (queued && queued->fulfilled)
continue;
if (placement.item.is<MicroDungeonNames>()) {
auto seed = m_worldServer->worldTemplate()->seedFor(placement.position[0], placement.position[1]);
auto const& dungeonName = staticRandomFrom(placement.item.get<MicroDungeonNames>(), seed);
Maybe<DungeonId> dungeonId;
starAssert(!dungeonName.empty());
if (auto generateResult = m_microDungeonFactory->generate(bounds, dungeonName, placement.position, seed, m_worldServer->threatLevel(), facade)) {
if (queued) {
dungeonId = queued->dungeonId;
queued->promise.fulfill(placement.position);
queued->fulfilled = true;
}
for (auto position : generateResult->second) {
if (ServerTile* tile = m_worldServer->modifyServerTile(position)) {
replaceBiomeBlocks(tile);
tile->dungeonId = dungeonId.value(tile->dungeonId);
}
}
}
}
}
m_queuedPlacements = m_queuedPlacements.filtered([&](QueuedPlacement& p) {
return !p.fulfilled;
});
}
void WorldGenerator::generateCaveLiquid(WorldStorage* worldStorage, ServerTileSectorArray::Sector const& sector) {
Set<Vec2I> openNodes = caveLiquidSeeds(worldStorage, sector);
if (!openNodes.size())
return;
auto tileArray = worldStorage->tileArray();
Vec2I dimensions(tileArray->size());
auto wrapCoords = [=](Vec2I const& coord) -> Vec2I { return Vec2I{pmod<int>(coord[0], dimensions[0]), coord[1]}; };
RectI sectorTiles = tileArray->sectorRegion(sector);
RectI bounds = sectorTiles.padded(Vec2I(WorldSectorSize - 1, WorldSectorSize - 1));
bounds.min()[1] = clamp<int>(bounds.min()[1], 0, dimensions[1] - 1);
bounds.max()[1] = clamp<int>(bounds.max()[1], 0, dimensions[1] - 1);
auto materialDatabase = Root::singleton().materialDatabase();
auto samplePoint = sectorTiles.center();
auto blockInfo = m_worldServer->worldTemplate()->blockInfo(samplePoint[0], samplePoint[1]);
LiquidId fillLiquid = blockInfo.caveLiquid;
bool fillMicrodungeons = blockInfo.fillMicrodungeons;
bool encloseLiquids = blockInfo.encloseLiquids;
Set<Vec2I> badNodes;
for (int i = bounds.xMin(); i <= bounds.xMax(); i++) {
badNodes.add({i, bounds.yMin()});
badNodes.add({i, bounds.yMax()});
}
for (int i = bounds.yMin(); i <= bounds.yMax(); i++) {
badNodes.add({bounds.xMin(), i});
badNodes.add({bounds.xMax(), i});
}
Set<Vec2I> candidateNodes;
auto propose = [&](Vec2I const& position) {
if (!bounds.contains(position))
return;
if (candidateNodes.contains(position))
return;
if (badNodes.contains(position))
return;
auto tile = tileArray->tile(wrapCoords(position));
starAssert(tile.foreground != NullMaterialId);
if (tile.foreground != EmptyMaterialId) {
// Not sure why this doesn't poison solid materials, but it does (occasionally) encounter that case
if (!BlockCollisionSet.contains(materialDatabase->materialCollisionKind(tile.foreground)))
badNodes.add(position);
return;
}
if ((tile.dungeonId != NoDungeonId && (!fillMicrodungeons || tile.dungeonId != BiomeMicroDungeonId))
|| (!encloseLiquids && tile.background == EmptyMaterialId)
|| (tile.liquid.liquid != fillLiquid && tile.liquid.liquid != EmptyLiquidId)) {
badNodes.add(position);
return;
}
candidateNodes.add(position);
openNodes.add(position);
};
while (openNodes.size()) {
auto node = *openNodes.begin();
openNodes.remove(node);
propose(node + Vec2I(-1, 0));
propose(node + Vec2I(1, 0));
propose(node + Vec2I(0, -1));
}
Set<Vec2I> visitedNodes;
auto poison = [&](Vec2I position) {
if (!bounds.contains(position))
return;
if (visitedNodes.contains(position))
return;
visitedNodes.add(position);
auto tile = tileArray->tile(wrapCoords(position));
starAssert(tile.foreground != NullMaterialId);
if (tile.foreground != EmptyMaterialId)
return;
badNodes.add(position);
};
while (badNodes.size()) {
auto node = *badNodes.begin();
badNodes.remove(node);
candidateNodes.remove(node);
poison(node + Vec2I(-1, 0));
poison(node + Vec2I(1, 0));
poison(node + Vec2I(0, 1)); // upwards, not downwards
}
Set<Vec2I> solidSurroundings(candidateNodes);
auto solids = [&](Vec2I position) {
auto tile = tileArray->tile(wrapCoords(position));
starAssert(tile.foreground != NullMaterialId);
if (tile.foreground != EmptyMaterialId)
solidSurroundings.add(position);
};
for (auto position : candidateNodes) {
solids(position + Vec2I(1, 0));
solids(position + Vec2I(-1, 0));
solids(position + Vec2I(0, 1));
solids(position + Vec2I(0, -1));
}
MaterialId biomeBlock = m_worldServer->worldTemplate()->biome(tileArray->tile(samplePoint).blockBiomeIndex)->mainBlock;
Map<Vec2I, float> drops = determineLiquidLevel(candidateNodes, solidSurroundings);
for (auto iter = drops.begin(); iter != drops.end(); ++iter) {
auto tile = tileArray->modifyTile(wrapCoords(iter->first));
starAssert(tile);
if (!tile)
continue;
if (iter->second)
tile->liquid = LiquidStore::filled(fillLiquid, 1.0f, iter->second);
if (encloseLiquids && tile->background == EmptyMaterialId)
tile->background = biomeBlock;
}
}
void WorldGenerator::prepareSector(WorldStorage* worldStorage, ServerTileSectorArray::Sector const& sector) {
auto materialDatabase = Root::singleton().materialDatabase();
auto planet = m_worldServer->worldTemplate();
auto tileArray = worldStorage->tileArray();
RectI sectorTiles = tileArray->sectorRegion(sector);
for (int x = sectorTiles.xMin(); x < sectorTiles.xMax(); ++x) {
for (int y = sectorTiles.yMin(); y < sectorTiles.yMax(); ++y) {
Vec2I position(x, y);
ServerTile* tile = tileArray->modifyTile(position);
starAssert(tile);
if (!tile)
continue;
starAssert(tile->foreground != NullMaterialId);
if (tile->liquid.source) {
auto blockInfo = planet->blockInfo(position[0], position[1]);
// make sure that ocean liquid never exists on tiles without empty
// background (except in real dungeons)
if (!isRealDungeon(tile->dungeonId) && tile->background != EmptyMaterialId)
tile->liquid.source = false;
// pressurize liquid under the ocean
if (blockInfo.oceanLiquid != EmptyLiquidId && position[1] < blockInfo.oceanLiquidLevel) {
float pressure = blockInfo.oceanLiquidLevel - position[1];
tile->liquid.pressure = pressure;
}
}
if (!isRealMaterial(tile->foreground))
tile->foregroundColorVariant = DefaultMaterialColorVariant;
if (!isRealMaterial(tile->background))
tile->backgroundColorVariant = DefaultMaterialColorVariant;
replaceBiomeBlocks(tile);
placeBiomeGrass(worldStorage, tile, position);
tile->collision = maxCollision(tile->collision, materialDatabase->materialCollisionKind(tile->foreground));
}
}
List<BiomeItemPlacement> placementQueue;
for (int x = sectorTiles.xMin(); x < sectorTiles.xMax(); ++x) {
for (int y = sectorTiles.yMin(); y < sectorTiles.yMax(); ++y) {
auto tile = tileArray->tile(Vec2I(x, y));
if (tile.dungeonId == NoDungeonId) {
auto potential = m_worldServer->worldTemplate()->potentialBiomeItemsAt(x, y);
for (auto const& placement : m_worldServer->worldTemplate()->validBiomeItems(x, y, potential))
placementQueue.append(placement);
}
}
}
sort(placementQueue);
for (auto const& placement : placementQueue) {
auto seed = m_worldServer->worldTemplate()->seedFor(placement.position[0], placement.position[1]);
if (placement.item.is<GrassVariant>()) {
auto& grass = placement.item.get<GrassVariant>();
placePlant(worldStorage, Root::singleton().plantDatabase()->createPlant(grass, seed), placement.position);
} else if (placement.item.is<BushVariant>()) {
auto& bush = placement.item.get<BushVariant>();
placePlant(worldStorage, Root::singleton().plantDatabase()->createPlant(bush, seed), placement.position);
} else if (placement.item.is<TreePair>()) {
auto& treePair = placement.item.get<TreePair>();
TreeVariant treeVariant;
if (seed % 2 == 0)
treeVariant = treePair.first;
else
treeVariant = treePair.second;
placePlant(worldStorage, Root::singleton().plantDatabase()->createPlant(treeVariant, seed), placement.position);
} else if (placement.item.is<ObjectPool>()) {
auto& objectPool = placement.item.get<ObjectPool>();
auto direction = seed % 2 ? Direction::Left : Direction::Right;
auto objectPair = objectPool.select(seed);
if (auto object = Root::singleton().objectDatabase()->createForPlacement(
m_worldServer, objectPair.first, placement.position, direction, objectPair.second))
m_worldServer->addEntity(object);
} else if (placement.item.is<TreasureBoxSet>()) {
auto& treasureBoxSet = placement.item.get<TreasureBoxSet>();
auto direction = seed % 2 ? Direction::Left : Direction::Right;
if (auto treasureContainer = Root::singleton().treasureDatabase()->createTreasureChest(
m_worldServer, treasureBoxSet, placement.position, direction, seed))
m_worldServer->addEntity(treasureContainer);
}
}
for (int x = sectorTiles.xMin(); x < sectorTiles.xMax(); ++x) {
for (int y = sectorTiles.yMin(); y < sectorTiles.yMax(); ++y) {
if (auto* tile = worldStorage->tileArray()->modifyTile({x, y}))
tile->collisionCacheDirty = true;
}
}
}
void WorldGenerator::prepareSectorBiomeBlocks(WorldStorage* worldStorage, ServerTileSectorArray::Sector const& sector) {
auto tileArray = worldStorage->tileArray();
auto materialDatabase = Root::singleton().materialDatabase();
RectI sectorTiles = tileArray->sectorRegion(sector);
for (int x = sectorTiles.xMin(); x < sectorTiles.xMax(); ++x) {
for (int y = sectorTiles.yMin(); y < sectorTiles.yMax(); ++y) {
Vec2I position(x, y);
ServerTile* tile = tileArray->modifyTile(position);
replaceBiomeBlocks(tile);
placeBiomeGrass(worldStorage, tile, position);
tile->collision = maxCollision(tile->collision, materialDatabase->materialCollisionKind(tile->foreground));
}
}
}
void WorldGenerator::placeBiomeGrass(WorldStorage* worldStorage, ServerTile* tile, Vec2I const& position) {
if (auto blockBiome = m_worldServer->worldTemplate()->biome(tile->blockBiomeIndex)) {
// determine layer for grass mod calculation
TileLayer modLayer = tile->foreground != EmptyMaterialId ? TileLayer::Foreground : TileLayer::Background;
// don't place mods in dungeons unless explicitly specified, also don't
// touch non-grass mods
if (tile->mod(modLayer) == BiomeModId || tile->mod(modLayer) == UndergroundBiomeModId
|| (tile->dungeonId == NoDungeonId && tile->mod(modLayer) == NoModId)) {
// check whether we're floor or ceiling
auto tileAbove = worldStorage->tileArray()->tile(position + Vec2I(0, 1));
auto tileBelow = worldStorage->tileArray()->tile(position + Vec2I(0, -1));
bool isFloor = (tile->foreground != EmptyMaterialId && tileAbove.foreground == EmptyMaterialId) || (tile->background != EmptyMaterialId && tileAbove.background == EmptyMaterialId);
bool isCeiling = !isFloor && ((tile->foreground != EmptyMaterialId && tileBelow.foreground == EmptyMaterialId) || (tile->background != EmptyMaterialId && tileBelow.background == EmptyMaterialId));
// get the appropriate placeables for above/below ground
BiomePlaceables const* placeables;
if ((isFloor && tileAbove.background != EmptyMaterialId) || (isCeiling && tileBelow.background != EmptyMaterialId))
placeables = &blockBiome->undergroundPlaceables;
else
placeables = &blockBiome->surfacePlaceables;
// determine the proper grass mod or lack thereof
ModId grassModId = NoModId;
if (isFloor) {
auto grassChance = staticRandomFloat(m_worldServer->worldTemplate()->worldSeed(), position[0], position[1]);
if (isRealMod(placeables->grassMod) && grassChance <= placeables->grassModDensity)
grassModId = placeables->grassMod;
} else if (isCeiling) {
auto grassChance = staticRandomFloat(m_worldServer->worldTemplate()->worldSeed(), position[0], position[1]);
if (isRealMod(placeables->ceilingGrassMod) && grassChance <= placeables->ceilingGrassModDensity)
grassModId = placeables->ceilingGrassMod;
}
// set the selected grass mod
if (modLayer == TileLayer::Foreground) {
tile->foregroundMod = grassModId;
tile->backgroundMod = NoModId;
} else {
tile->foregroundMod = NoModId;
tile->backgroundMod = grassModId;
}
}
// update hue shifts appropriately
tile->foregroundModHueShift = m_worldServer->worldTemplate()->biomeModHueShift(tile->blockBiomeIndex, tile->foregroundMod);
tile->backgroundModHueShift = m_worldServer->worldTemplate()->biomeModHueShift(tile->blockBiomeIndex, tile->backgroundMod);
}
}
void WorldGenerator::reapplyBiome(WorldStorage* worldStorage, ServerTileSectorArray::Sector const& sector) {
auto materialDatabase = Root::singleton().materialDatabase();
auto planet = m_worldServer->worldTemplate();
auto tileArray = worldStorage->tileArray();
RectI sectorTiles = tileArray->sectorRegion(sector);
// Logger::info("Reapplying biome in sector {}...", sectorTiles);
auto entities = m_worldServer->entityQuery(RectF(sectorTiles.padded(1)));
List<TileEntityPtr> biomeTileEntities;
for (auto entity : entities) {
if (auto plant = as<Plant>(entity)) {
biomeTileEntities.append(as<TileEntity>(entity));
} else if (auto object = as<Object>(entity)) {
if (object->biomePlaced())
biomeTileEntities.append(as<TileEntity>(entity));
}
}
List<Vec2I> biomeItemTiles;
for (int x = sectorTiles.xMin(); x < sectorTiles.xMax(); ++x) {
for (int y = sectorTiles.yMin(); y < sectorTiles.yMax(); ++y) {
Vec2I position(x, y);
ServerTile* tile = m_worldServer->modifyServerTile(position);
starAssert(tile);
if (!tile)
continue;
auto blockInfo = planet->blockBiomeInfo(position[0], position[1]);
if (blockInfo.blockBiomeIndex != tile->blockBiomeIndex) {
auto newBiome = planet->biome(blockInfo.blockBiomeIndex);
auto oldBiome = planet->biome(tile->blockBiomeIndex);
biomeTileEntities.filter([&, position](TileEntityPtr tileEntity) {
if (tileEntity->tilePosition() == position) {
m_worldServer->removeEntity(tileEntity->entityId(), false);
return false;
}
return true;
});
// update biome index
tile->blockBiomeIndex = blockInfo.blockBiomeIndex;
tile->environmentBiomeIndex = blockInfo.environmentBiomeIndex;
tile->biomeTransition = true;
// replace biome blocks
if (tile->foreground == oldBiome->mainBlock || oldBiome->subBlocks.contains(tile->foreground)) {
tile->foreground = blockInfo.foreground;
tile->foregroundColorVariant = DefaultMaterialColorVariant;
if (tile->foreground == newBiome->mainBlock)
tile->foregroundHueShift = newBiome->materialHueShift;
}
if (tile->background == oldBiome->mainBlock || oldBiome->subBlocks.contains(tile->background)) {
tile->background = blockInfo.background;
tile->backgroundColorVariant = DefaultMaterialColorVariant;
if (tile->background == newBiome->mainBlock)
tile->backgroundHueShift = newBiome->materialHueShift;
}
if (tile->foreground != EmptyMaterialId || tile->background != EmptyMaterialId) {
// remove old biome mods
if (tile->foregroundMod == oldBiome->surfacePlaceables.grassMod || tile->foregroundMod == oldBiome->surfacePlaceables.ceilingGrassMod ||
tile->foregroundMod == oldBiome->undergroundPlaceables.grassMod || tile->foregroundMod == oldBiome->undergroundPlaceables.ceilingGrassMod) {
tile->foregroundMod = NoModId;
tile->foregroundModHueShift = 0;
}
if (tile->backgroundMod == oldBiome->surfacePlaceables.grassMod || tile->backgroundMod == oldBiome->surfacePlaceables.ceilingGrassMod ||
tile->backgroundMod == oldBiome->undergroundPlaceables.grassMod || tile->backgroundMod == oldBiome->undergroundPlaceables.ceilingGrassMod) {
tile->backgroundMod = NoModId;
tile->backgroundModHueShift = 0;
}
// apply new biome mods
TileLayer modLayer = tile->foreground != EmptyMaterialId ? TileLayer::Foreground : TileLayer::Background;
if (tile->mod(modLayer) == NoModId) {
// check whether we're floor or ceiling
auto tileAbove = worldStorage->tileArray()->tile(position + Vec2I(0, 1));
auto tileBelow = worldStorage->tileArray()->tile(position + Vec2I(0, -1));
bool isFloor = tile->foreground != EmptyMaterialId && tileAbove.foreground == EmptyMaterialId;
bool isCeiling = !isFloor && tile->foreground != EmptyMaterialId && tileBelow.foreground == EmptyMaterialId;
bool isModFloor, isModCeiling;
if (modLayer == TileLayer::Foreground) {
isModFloor = isFloor;
isModCeiling = isCeiling;
} else {
isModFloor = tile->background != EmptyMaterialId && tileAbove.background == EmptyMaterialId;
isModCeiling = !isModFloor && tile->background != EmptyMaterialId && tileBelow.background == EmptyMaterialId;
}
// get the appropriate placeables for above/below ground
BiomePlaceables const* placeables;
if ((isFloor && tileAbove.background != EmptyMaterialId) || (isCeiling && tileBelow.background != EmptyMaterialId))
placeables = &newBiome->undergroundPlaceables;
else
placeables = &newBiome->surfacePlaceables;
// determine the proper grass mod or lack thereof
ModId grassModId = NoModId;
if (isModFloor) {
auto grassChance = staticRandomFloat(m_worldServer->worldTemplate()->worldSeed(), position[0], position[1]);
if (isRealMod(placeables->grassMod) && grassChance <= placeables->grassModDensity)
grassModId = placeables->grassMod;
} else if (isModCeiling) {
auto grassChance = staticRandomFloat(m_worldServer->worldTemplate()->worldSeed(), position[0], position[1]);
if (isRealMod(placeables->ceilingGrassMod) && grassChance <= placeables->ceilingGrassModDensity)
grassModId = placeables->ceilingGrassMod;
}
// set the selected grass mod
if (modLayer == TileLayer::Foreground && materialDatabase->supportsMod(tile->foreground, grassModId)) {
tile->foregroundMod = grassModId;
tile->backgroundMod = NoModId;
} else if (modLayer == TileLayer::Background && materialDatabase->supportsMod(tile->background, grassModId)) {
tile->foregroundMod = NoModId;
tile->backgroundMod = grassModId;
}
}
} else {
tile->foregroundMod = NoModId;
tile->backgroundMod = NoModId;
}
tile->collision = maxCollision(tile->collision, materialDatabase->materialCollisionKind(tile->foreground));
}
if (tile->biomeTransition && !blockInfo.biomeTransition) {
tile->biomeTransition = false;
if (!isSolidColliding(tile->collision))
biomeItemTiles.append(position);
}
}
}
auto simplePlacePlant = [&](PlantPtr const& plant, Vec2I const& position) {
if (!plant)
return false;
auto spaces = plant->spaces();
auto roots = plant->roots();
auto const& primaryRoot = plant->primaryRoot();
auto blockBiome = planet->worldLayout()->getBiome(worldStorage->tileArray()->tile(position).blockBiomeIndex);
auto positionValid = [&](Vec2I const& pos) {
auto primaryTile = worldStorage->tileArray()->tile(pos);
auto primaryRootTile = worldStorage->tileArray()->tile(pos + primaryRoot);
if (isConnectableMaterial(primaryTile.foreground) || !isConnectableMaterial(primaryRootTile.foreground))
return false;
for (auto root : roots) {
auto rootTile = worldStorage->tileArray()->tile(root + pos);
if (!isConnectableMaterial(rootTile.foreground) || rootTile.blockBiomeIndex != primaryTile.blockBiomeIndex ||
(rootTile.foreground != blockBiome->mainBlock && !blockBiome->subBlocks.contains(rootTile.foreground)))
return false;
}
for (auto space : spaces) {
Vec2I pspace = space + pos;
if (!m_worldServer->atTile<TileEntity>(pspace).empty())
return false;
auto tile = worldStorage->tileArray()->tile(pspace);
if (tile.foreground != EmptyMaterialId)
return false;
}
return true;
};
List<Vec2I> tryPositions = {
position,
position + Vec2I{-1, 0},
position + Vec2I{1, 0},
position + Vec2I{-2, 0},
position + Vec2I{2, 0},
position + Vec2I{-1, 1},
position + Vec2I{-1, -1},
position + Vec2I{1, 1},
position + Vec2I{1, -1}
};
for (auto pos : tryPositions) {
if (positionValid(pos)) {
plant->setTilePosition(pos);
m_worldServer->addEntity(plant);
return true;
}
}
return false;
};
auto placeBiomeItem = [&](BiomeItemPlacement biomeItemPlacement, Vec2I position) {
auto seed = m_worldServer->worldTemplate()->seedFor(position[0], position[1]);
if (biomeItemPlacement.item.is<GrassVariant>()) {
auto& grass = biomeItemPlacement.item.get<GrassVariant>();
simplePlacePlant(Root::singleton().plantDatabase()->createPlant(grass, seed), position);
} else if (biomeItemPlacement.item.is<BushVariant>()) {
auto& bush = biomeItemPlacement.item.get<BushVariant>();
simplePlacePlant(Root::singleton().plantDatabase()->createPlant(bush, seed), position);
} else if (biomeItemPlacement.item.is<TreePair>()) {
auto& treePair = biomeItemPlacement.item.get<TreePair>();
TreeVariant treeVariant;
if (seed % 2 == 0)
treeVariant = treePair.first;
else
treeVariant = treePair.second;
simplePlacePlant(Root::singleton().plantDatabase()->createPlant(treeVariant, seed), position);
} else if (biomeItemPlacement.item.is<ObjectPool>()) {
auto& objectPool = biomeItemPlacement.item.get<ObjectPool>();
auto direction = seed % 2 ? Direction::Left : Direction::Right;
auto objectPair = objectPool.select(seed);
if (auto object = Root::singleton().objectDatabase()->createForPlacement(m_worldServer, objectPair.first, position, direction, objectPair.second)) {
if (object->biomePlaced())
m_worldServer->addEntity(object);
}
}
};
for (auto position : biomeItemTiles) {
ServerTile* tile = m_worldServer->modifyServerTile(position);
auto blockBiome = planet->worldLayout()->getBiome(tile->blockBiomeIndex);
auto tileAbove = m_worldServer->getServerTile(position + Vec2I{0, 1});
auto tileBelow = m_worldServer->getServerTile(position + Vec2I{0, -1});
if (tile->background != EmptyMaterialId) {
for (auto const& itemDistribution : blockBiome->undergroundPlaceables.itemDistributions) {
if (itemDistribution.mode() == BiomePlacementMode::Background) {
if (auto itemToPlace = itemDistribution.itemToPlace(position[0], position[1]))
placeBiomeItem(*itemToPlace, position);
}
}
if (isSolidColliding(tileAbove.collision)) {
for (auto const& itemDistribution : blockBiome->undergroundPlaceables.itemDistributions) {
if (itemDistribution.mode() == BiomePlacementMode::Ceiling) {
if (auto itemToPlace = itemDistribution.itemToPlace(position[0], position[1]))
placeBiomeItem(*itemToPlace, position);
}
}
}
if (isSolidColliding(tileBelow.collision)) {
for (auto const& itemDistribution : blockBiome->undergroundPlaceables.itemDistributions) {
if (itemDistribution.mode() == BiomePlacementMode::Floor) {
if (auto itemToPlace = itemDistribution.itemToPlace(position[0], position[1]))
placeBiomeItem(*itemToPlace, position);
}
}
}
} else {
if (isSolidColliding(tileBelow.collision)) {
for (auto const& itemDistribution : blockBiome->surfacePlaceables.itemDistributions) {
if (itemDistribution.mode() == BiomePlacementMode::Floor) {
if (auto itemToPlace = itemDistribution.itemToPlace(position[0], position[1]))
placeBiomeItem(*itemToPlace, position);
}
}
}
}
}
}
Set<Vec2I> WorldGenerator::caveLiquidSeeds(WorldStorage* worldStorage, ServerTileSectorArray::Sector const& sector) {
RectI sectorTiles = worldStorage->tileArray()->sectorRegion(sector);
auto samplePoint = sectorTiles.center();
auto blockInfo = m_worldServer->worldTemplate()->blockInfo(samplePoint[0], samplePoint[1]);
float seedDensity = blockInfo.caveLiquidSeedDensity;
Set<Vec2I> nodes;
if (seedDensity > 0) {
int frequency = int(100 / seedDensity);
for (int y = frequency * floor(sectorTiles.min()[1] / frequency); y < sectorTiles.max()[1]; y += frequency) {
for (int x = frequency * floor(sectorTiles.min()[0] / frequency); x < sectorTiles.max()[0]; x += frequency) {
if (sectorTiles.contains(Vec2I(x, y)))
nodes.add(Vec2I(x, y));
}
}
}
return nodes;
}
Map<Vec2I, float> WorldGenerator::determineLiquidLevel(Set<Vec2I> const& spots, Set<Vec2I> const& filled) {
Set<Vec2I> openSet(spots);
Map<Vec2I, float> results;
auto geometry = m_worldServer->geometry();
while (openSet.size() > 0) {
Set<Vec2I> cluster;
Set<Vec2I> openCluster;
openCluster.add(*(openSet.begin()));
while (openCluster.size() > 0) {
Vec2I node = *(openCluster.begin());
openCluster.remove(node);
if (openSet.contains(node)) {
openSet.remove(node);
cluster.add(node);
openCluster.add(geometry.xwrap(Vec2I(node.x(), node.y() + 1)));
openCluster.add(geometry.xwrap(Vec2I(node.x(), node.y() - 1)));
openCluster.add(geometry.xwrap(Vec2I(node.x() + 1, node.y())));
openCluster.add(geometry.xwrap(Vec2I(node.x() - 1, node.y())));
}
}
levelCluster(cluster, filled, results);
}
return results;
}
void WorldGenerator::levelCluster(Set<Vec2I>& cluster, Set<Vec2I> const& filled, Map<Vec2I, float>& results) {
int maxY = std::numeric_limits<int>::min();
int minY = std::numeric_limits<int>::max();
for (auto iter = cluster.begin(); iter != cluster.end(); iter++) {
auto droplet = (*iter);
if (filled.contains(droplet + Vec2I(1, 0)) && filled.contains(droplet + Vec2I(-1, 0))
&& filled.contains(droplet + Vec2I(0, -1))) {
if (droplet.y() > maxY)
maxY = droplet.y();
if (!filled.contains(droplet + Vec2I(0, 1))) {
if (droplet.y() <= minY)
minY = droplet.y();
}
} else {
if (droplet.y() <= minY)
minY = droplet.y() - 1;
}
}
int liquidLevel = std::min(maxY, minY);
for (auto iter = cluster.begin(); iter != cluster.end(); iter++) {
int pressure = (liquidLevel - (*iter).y());
if (pressure >= 0)
results[*iter] = 1.0f + pressure;
}
}
bool WorldGenerator::placePlant(WorldStorage* worldStorage, PlantPtr const& plant, Vec2I const& position) {
if (!plant)
return false;
auto spaces = plant->spaces();
auto roots = plant->roots();
auto const& primaryRoot = plant->primaryRoot();
auto background = m_worldServer->getServerTile(position).background;
bool adjustBackground = background == EmptyMaterialId || background == NullMaterialId;
auto withinAdjustment = [=](Vec2I const& pos) {
return PlantAdjustmentLimit - std::abs(pos[0]) > 0 && PlantAdjustmentLimit - std::abs(pos[1]) > 0;
};
// Bail out if we don't have at least one free space, and root in the primary
// root position, or if we're in a dungeon region.
auto primaryTile = worldStorage->tileArray()->tile(position);
auto rootTile = worldStorage->tileArray()->tile(position + primaryRoot);
if (primaryTile.dungeonId != NoDungeonId || rootTile.dungeonId != NoDungeonId)
return false;
if (isConnectableMaterial(primaryTile.foreground) || !isConnectableMaterial(rootTile.foreground))
return false;
// First bail out if we can't fit anything we're not adjusting
for (auto space : spaces) {
Vec2I pspace = space + position;
if (withinAdjustment(space) && !m_worldServer->atTile<Plant>(pspace).empty())
return false;
// Bail out if we hit a different plant's root tile, or if we're not in the
// adjustment space and we hit a non-empty tile.
auto tile = worldStorage->tileArray()->tile(pspace);
if (tile.rootSource || (!withinAdjustment(space) && tile.foreground != EmptyMaterialId))
return false;
}
// Check all the roots outside of the adjustment limit
for (auto root : roots) {
root += position;
if (!withinAdjustment(root) && !isConnectableMaterial(worldStorage->tileArray()->tile(root).foreground))
return false;
}
// Clear all the necessary blocks within the adjustment limit
for (auto space : spaces) {
if (!withinAdjustment(space))
continue;
space += position;
if (auto tile = worldStorage->tileArray()->modifyTile(space)) {
if (isConnectableMaterial(tile->foreground))
*tile = primaryTile;
if (adjustBackground)
tile->background = EmptyMaterialId;
tile->collision = CollisionKind::None;
tile->collisionCacheDirty = true;
} else {
return false;
}
}
// Make all the root blocks a real material based on the primary root.
for (auto root : roots) {
root += position;
if (auto tile = worldStorage->tileArray()->modifyTile(root)) {
if (!isRealMaterial(tile->foreground)) {
*tile = rootTile;
tile->collision = Root::singleton().materialDatabase()->materialCollisionKind(tile->foreground);
tile->collisionCacheDirty = true;
}
} else {
return false;
}
}
plant->setTilePosition(position);
m_worldServer->addEntity(plant);
return true;
}
}