303 lines
10 KiB
C++
303 lines
10 KiB
C++
#include "StarFlowingLiquidAgent.hpp"
|
|
#include "StarRoot.hpp"
|
|
#include "StarLiquidsDatabase.hpp"
|
|
#include "StarLivingWorldAgent.hpp"
|
|
#include "StarLogging.hpp"
|
|
|
|
namespace Star {
|
|
|
|
FlowingLiquidAgent::FlowingLiquidAgent() {
|
|
m_livingWorld = {};
|
|
}
|
|
|
|
void FlowingLiquidAgent::bind(LivingWorldFacadePtr world, LivingWorldAgent* livingWorld) {
|
|
this->m_world = world;
|
|
m_livingWorld = livingWorld;
|
|
}
|
|
|
|
void FlowingLiquidAgent::processLocation(Vec2I const& c) {
|
|
auto level = getLiquidLevel(c.x(), c.y());
|
|
int fountainPressure = 0;
|
|
|
|
if (level.liquid != Liquid::SolidTileLiquidPseudoId) {
|
|
if (!hasBackground(c.x(), c.y()) && isOcean(c.x(), c.y())) {
|
|
auto liquid = oceanLiquid(c.x(), c.y());
|
|
auto pressure = oceanLiquidPressure(c.x(), c.y());
|
|
if ((level.liquid != liquid)||(level.level != pressure)) {
|
|
setLiquidLevel(c.x(), c.y(), LiquidLevel{liquid, pressure});
|
|
level = getLiquidLevel(c.x(), c.y());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (level.level >= Liquid::OverflowFullLiquidLevel)
|
|
if (moveLiquidUpwards(c, level)) {
|
|
auto l = getLiquidLevel(c.x(), c.y());
|
|
fountainPressure = (int) level.level - (int) l.level;
|
|
level = l;
|
|
}
|
|
if (level.level > 0)
|
|
if (moveLiquidDown(c, level, true)) {
|
|
level = getLiquidLevel(c.x(), c.y());
|
|
fountainPressure = 0;
|
|
}
|
|
if (level.level >= Liquid::TrivialLevelThreshold) {
|
|
moveLiquidSideWays(c, fountainPressure);
|
|
}
|
|
}
|
|
|
|
bool FlowingLiquidAgent::moveLiquidDown(Vec2I c, LiquidLevel above, bool attemptMoveOut) {
|
|
auto liquidsDatabase = Root::singleton().liquidsDatabase();
|
|
|
|
if (above.liquid == Liquid::SolidTileLiquidPseudoId)
|
|
return false;
|
|
auto below = getLiquidLevel(c.x(), c.y() - 1);
|
|
if (below.liquid == Liquid::SolidTileLiquidPseudoId) {
|
|
if (attemptMoveOut)
|
|
moveLiquidOut(c);
|
|
return false;
|
|
}
|
|
if ((c.y() <= 0) && liquidsDatabase->liquidSettings(above.liquid)->endless)
|
|
return false;
|
|
|
|
int move = std::max(0,
|
|
std::min(std::min((int) above.level, (Liquid::MaxLiquidLevel - 1) - (int) below.level),
|
|
std::max((Liquid::OverflowFullLiquidLevel - 1) - (int) below.level,
|
|
std::min((Liquid::PressurePerLevel - ((int) below.level - (int) above.level)) / 2,
|
|
(int) above.level - (Liquid::TrivialLevelThreshold - 1)))));
|
|
|
|
move *= std::min(liquidsDatabase->liquidSettings(below.liquid)->downwardsSpeedModifier, liquidsDatabase->liquidSettings(above.liquid)->downwardsSpeedModifier);
|
|
|
|
above.level -= move;
|
|
below.level += move;
|
|
below.liquid = above.liquid;
|
|
|
|
if (move > 0) {
|
|
moveLiquid(c.x(), c.y(), 0, -1, above, below);
|
|
return true;
|
|
} else {
|
|
if (attemptMoveOut)
|
|
moveLiquidOut(c);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool FlowingLiquidAgent::moveLiquidSideWays(Vec2I c, int fountain) {
|
|
fountain /= Liquid::PressurePerLevel;
|
|
auto below = getLiquidLevel(c.x(), c.y() - 1);
|
|
if ((below.liquid != Liquid::SolidTileLiquidPseudoId) && (below.level < Liquid::FullLiquidLevel))
|
|
return false;
|
|
bool result = false;
|
|
int move = 0;
|
|
while (true) {
|
|
move++;
|
|
int bdx = 0;
|
|
int bl = 0;
|
|
int l;
|
|
auto level = getLiquidLevel(c.x(), c.y());
|
|
if (level.liquid == Liquid::SolidTileLiquidPseudoId)
|
|
break;
|
|
int idx = 0;
|
|
int preferredDirection = m_random.randInt(1);
|
|
|
|
for (int dx = -1; dx <= 1; dx += 2) {
|
|
l = checkMoveLiquidSideways(c.x() + dx, c.y(), level);
|
|
if ((l > bl) || ((l == bl) && (idx == preferredDirection))) {
|
|
bl = l;
|
|
bdx = dx;
|
|
}
|
|
idx++;
|
|
}
|
|
if (bl == 0)
|
|
break;
|
|
|
|
auto source = getLiquidLevel(c.x(), c.y());
|
|
auto target = getLiquidLevel(c.x() + bdx, c.y());
|
|
|
|
if ((source.level > bl) && ((int) source.level - bl > (int) target.level + bl))
|
|
bl++;
|
|
|
|
if ((fountain > 0) && (m_random.randInt(5 * fountain) != 0))
|
|
bl /= fountain * 10;
|
|
|
|
if ((bl >= Liquid::TrivialSidewaysMoveThreshold)
|
|
&& (((int) source.level - bl) >= Liquid::TrivialLevelThreshold)
|
|
&& (((int) target.level + bl) >= Liquid::TrivialLevelThreshold)) {
|
|
|
|
source.level -= bl;
|
|
target.level += bl;
|
|
target.liquid = source.liquid;
|
|
moveLiquid(c.x(), c.y(), bdx, 0, source, target);
|
|
moveLiquidDown(Vec2I(c.x() + bdx, c.y()), getLiquidLevel(c.x() + bdx, c.y()), false);
|
|
result = true;
|
|
}
|
|
else {
|
|
if (target.level <= Liquid::IdleLevelEvaporateThreshold) {
|
|
source.level = std::max(0, source.level - Liquid::IdleLevelEvaporateThreshold);
|
|
setLiquidLevel(c.x(), c.y(), source);
|
|
result = true;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int FlowingLiquidAgent::checkMoveLiquidSideways(int x, int y, LiquidLevel level) {
|
|
if (level.liquid == Liquid::SolidTileLiquidPseudoId)
|
|
return 0;
|
|
auto beside = getLiquidLevel(x, y);
|
|
if (beside.liquid == Liquid::SolidTileLiquidPseudoId)
|
|
return 0;
|
|
int result = (((int) level.level - (int) beside.level) * 1.9) / 2;
|
|
auto liquidsDatabase = Root::singleton().liquidsDatabase();
|
|
result *= std::min(liquidsDatabase->liquidSettings(level.liquid)->sidewaysSpeedModifier, liquidsDatabase->liquidSettings(beside.liquid)->sidewaysSpeedModifier);
|
|
if (result > level.level)
|
|
result = level.level;
|
|
if (result <= 0)
|
|
return 0;
|
|
return result;
|
|
}
|
|
|
|
bool FlowingLiquidAgent::moveLiquidUpwards(Vec2I c, LiquidLevel level) {
|
|
if (level.liquid == Liquid::SolidTileLiquidPseudoId)
|
|
return false;
|
|
if (level.level < Liquid::OverflowFullLiquidLevel) {
|
|
return false;
|
|
}
|
|
auto above = getLiquidLevel(c.x(), c.y() + 1);
|
|
if (above.liquid == Liquid::SolidTileLiquidPseudoId) {
|
|
return false;
|
|
}
|
|
|
|
int move = std::max(0,
|
|
std::min((int) level.level - (Liquid::OverflowFullLiquidLevel -1),
|
|
(((int) level.level - (int) above.level) - (Liquid::PressurePerLevel)) / 2));
|
|
|
|
if (above.liquid == NullLiquidId)
|
|
above.liquid = level.liquid;
|
|
|
|
auto liquidsDatabase = Root::singleton().liquidsDatabase();
|
|
move *= std::min(liquidsDatabase->liquidSettings(level.liquid)->upwardsSpeedModifier, liquidsDatabase->liquidSettings(above.liquid)->upwardsSpeedModifier);
|
|
move = std::min(move, std::min((int) level.level, (Liquid::MaxLiquidLevel - 1) - (int) above.level));
|
|
|
|
above.level += move;
|
|
level.level -= move;
|
|
above.liquid = level.liquid;
|
|
|
|
if ((move >= Liquid::TrivialUpwardsMoveThreshold)
|
|
&& (above.level >= Liquid::TrivialLevelThreshold)) {
|
|
moveLiquid(c.x(), c.y(), 0, 1, level, above);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FlowingLiquidAgent::moveLiquidOut(Vec2I c) {
|
|
if (!hasBackground(c.x(), c.y()) && !isOcean(c.x(), c.y())) {
|
|
auto current = getLiquidLevel(c.x(), c.y());
|
|
auto liquidsDatabase = Root::singleton().liquidsDatabase();
|
|
auto setting = liquidsDatabase->liquidSettings(current.liquid);
|
|
if (setting->endless)
|
|
return false;
|
|
// Move Liquid into the background (disappear) at the BackgroundFlowPercentage
|
|
current.level *= 1.0f - setting->backgroundFlowPercentage;
|
|
setLiquidLevel(c.x(), c.y(), current);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool FlowingLiquidAgent::sanityCheckLiquid(LiquidLevel const& level) const {
|
|
if (((level.liquid != Liquid::SolidTileLiquidPseudoId)&&(level.liquid != NullLiquidId)) == (level.level == 0)) {
|
|
Logger::warn("Inconsistent liquid data. Liquid:%s Level:%s", level.liquid, level.level);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
LiquidLevel FlowingLiquidAgent::getLiquidLevel(int x, int y) {
|
|
LiquidLevel level = m_world->readTileLiquid(x, y);
|
|
#ifndef NDEBUG
|
|
sanityCheckLiquid(level);
|
|
#endif
|
|
return level;
|
|
}
|
|
|
|
bool FlowingLiquidAgent::hasBackground(int x, int y) {
|
|
return m_world->hasBackground(x, y);
|
|
}
|
|
|
|
bool FlowingLiquidAgent::isOcean(int x, int y) {
|
|
return m_world->isOcean(x, y);
|
|
}
|
|
|
|
LiquidId FlowingLiquidAgent::oceanLiquid(int x, int y) {
|
|
return m_world->oceanLiquid(x, y);
|
|
}
|
|
|
|
uint16_t FlowingLiquidAgent::oceanLiquidPressure(int x, int y) {
|
|
return m_world->oceanLiquidPressure(x, y);
|
|
}
|
|
|
|
void FlowingLiquidAgent::moveLiquid(int x, int y, int dx, int dy,
|
|
LiquidLevel proposedSourceLevel, LiquidLevel proposedTargetLevel) {
|
|
setLiquidLevel(x, y, proposedSourceLevel);
|
|
setLiquidLevel(x + dx, y + dy, proposedTargetLevel);
|
|
}
|
|
|
|
void FlowingLiquidAgent::setLiquidLevel(int x, int y, LiquidLevel level) {
|
|
if (level.level <= Liquid::TrivialLevelThreshold)
|
|
level.level = 0;
|
|
if (level.level == 0)
|
|
level.liquid = NullLiquidId;
|
|
auto prevLevel = getLiquidLevel(x, y);
|
|
auto liquidsDatabase = Root::singleton().liquidsDatabase();
|
|
auto prevLevelSetting = liquidsDatabase->liquidSettings(prevLevel.liquid);
|
|
if (prevLevelSetting->endless)
|
|
return; // can never update a endlesswater tile after generation
|
|
if ((prevLevel.liquid == level.liquid) && (prevLevel.level == level.level))
|
|
return;
|
|
starAssert(prevLevel.liquid != Liquid::SolidTileLiquidPseudoId);
|
|
|
|
MaterialId blockId = NullMaterialId;
|
|
MaterialHue blockHueShift = MaterialHue();
|
|
|
|
float flow = fabs((float) level.level - (float) prevLevel.level);
|
|
auto effect = liquidsDatabase->liquidSettings(level.liquid);
|
|
level.liquid = effect->flowsAs;
|
|
float chance = effect->blockGenerationChance * fmin((float)flow / (float)Liquid::FullLiquidLevel, 1.0f);
|
|
if (Random::randf() < chance) {
|
|
if (!effect->connectedOnly
|
|
||(getLiquidLevel(x-1, y).liquid == Liquid::SolidTileLiquidPseudoId)
|
|
||(getLiquidLevel(x+1, y).liquid == Liquid::SolidTileLiquidPseudoId)
|
|
||(getLiquidLevel(x, y-1).liquid == Liquid::SolidTileLiquidPseudoId)
|
|
||(getLiquidLevel(x, y+1).liquid == Liquid::SolidTileLiquidPseudoId)) {
|
|
blockId = Random::randFrom(effect->blockOptions);
|
|
}
|
|
}
|
|
|
|
if ((level.liquid != NullLiquidId) && (level.liquid != Liquid::SolidTileLiquidPseudoId) && (prevLevel.liquid != NullLiquidId) && (prevLevel.liquid != Liquid::SolidTileLiquidPseudoId)
|
|
&& (level.liquid != prevLevel.liquid)) {
|
|
auto& interaction = effect->liquidInteraction[prevLevel.liquid];
|
|
if (Random::randf() < interaction.blockGenerationChance) {
|
|
blockId = Random::randFrom(interaction.blockOptions);
|
|
} else {
|
|
float chance = flow / (flow + prevLevel.level);
|
|
if (Random::randf() >= chance)
|
|
level.liquid = prevLevel.liquid;
|
|
}
|
|
}
|
|
|
|
if ((blockId != NullMaterialId) && (blockId != EmptyMaterialId)) {
|
|
m_world->writeTileMaterial(x, y, blockId, blockHueShift, true);
|
|
} else {
|
|
if (!sanityCheckLiquid(level))
|
|
return;
|
|
m_world->writeTileLiquid(x, y, level);
|
|
}
|
|
m_livingWorld->visitRegion(x, y, true);
|
|
}
|
|
|
|
}
|