Tile Prediction

This commit is contained in:
Kae 2023-07-31 02:40:06 +10:00
parent 31f5816e8a
commit 610dc72c6d
8 changed files with 233 additions and 80 deletions

View File

@ -343,10 +343,10 @@ void TileSectorArray<Tile, SectorSize>::tileEachTo(MultiArray& results, RectI co
size_t arrayColumnIndex = (x + split.xOffset + xArrayOffset) * results.size(1) + y + yArrayOffset;
if (column) {
for (size_t i = 0; i < columnSize; ++i)
function(results.atIndex(arrayColumnIndex + i), Vec2I((int)x + split.xOffset, y), column[i]);
function(results.atIndex(arrayColumnIndex + i), Vec2I((int)x + split.xOffset, y + i), column[i]);
} else {
for (size_t i = 0; i < columnSize; ++i)
function(results.atIndex(arrayColumnIndex + i), Vec2I((int)x + split.xOffset, y), m_default);
function(results.atIndex(arrayColumnIndex + i), Vec2I((int)x + split.xOffset, y + i), m_default);
}
return true;
}, true);

View File

@ -313,8 +313,8 @@ TileModificationList WorldClient::validTileModifications(TileModificationList co
if (!inWorld())
return {};
return WorldImpl::splitTileModifications(m_tileArray, m_entityMap, modificationList, allowEntityOverlap, [this](Vec2I pos, TileModification) {
return !m_predictedTiles.contains(pos) && !isTileProtected(pos);
return WorldImpl::splitTileModifications(m_entityMap, modificationList, allowEntityOverlap, m_tileGetterFunction, [this](Vec2I pos, TileModification) {
return !isTileProtected(pos);
}).first;
}
@ -322,14 +322,13 @@ TileModificationList WorldClient::applyTileModifications(TileModificationList co
if (!inWorld())
return {};
auto result = WorldImpl::splitTileModifications(m_tileArray, m_entityMap, modificationList, allowEntityOverlap, [this](Vec2I pos, TileModification) {
return !m_predictedTiles.contains(pos) && !isTileProtected(pos);
auto result = WorldImpl::splitTileModifications(m_entityMap, modificationList, allowEntityOverlap, m_tileGetterFunction, [this](Vec2I pos, TileModification) {
return !isTileProtected(pos);
});
if (!result.first.empty()) {
for (auto entry : result.first)
m_predictedTiles[entry.first] = 0;
m_outgoingPackets.append(make_shared<ModifyTileListPacket>(result.first, allowEntityOverlap));
informTilePredictions(result.first);
m_outgoingPackets.append(make_shared<ModifyTileListPacket>(result.first, true));
}
return result.second;
@ -518,7 +517,7 @@ void WorldClient::render(WorldRenderData& renderData, unsigned bufferTiles) {
return a->entityId() < b->entityId();
});
m_tileArray->tileEachTo(renderData.tiles, tileRange, [](RenderTile& renderTile, Vec2I const&, ClientTile const& clientTile) {
m_tileArray->tileEachTo(renderData.tiles, tileRange, [&](RenderTile& renderTile, Vec2I const& position, ClientTile const& clientTile) {
renderTile.foreground = clientTile.foreground;
renderTile.foregroundMod = clientTile.foregroundMod;
@ -539,6 +538,22 @@ void WorldClient::render(WorldRenderData& renderData, unsigned bufferTiles) {
renderTile.liquidId = clientTile.liquid.liquid;
renderTile.liquidLevel = floatToByte(clientTile.liquid.level);
if (!m_predictedTiles.empty()) {
if (auto p = m_predictedTiles.ptr(position)) {
if (p->liquid) {
auto& liquid = *p->liquid;
if (liquid.liquid == renderTile.liquidId)
renderTile.liquidLevel = floatToByte(clientTile.liquid.level + liquid.level, true);
else {
renderTile.liquidId = liquid.liquid;
renderTile.liquidLevel = floatToByte(liquid.level, true);
}
}
p->apply(renderTile);
}
}
});
for (auto const& previewTile : previewTiles) {
@ -991,13 +1006,8 @@ void WorldClient::update(float dt) {
m_lightingCalculator.setMonochrome(Root::singleton().configuration()->get("monochromeLighting").toBool());
auto predictedTilesIt = makeSMutableMapIterator(m_predictedTiles);
while (predictedTilesIt.hasNext()) {
auto& entry = predictedTilesIt.next();
if (entry.second++ > m_modifiedTilePredictionTimeout) {
predictedTilesIt.remove();
}
}
auto expiry = Time::monotonicMilliseconds() + min<int64_t>(m_latency + 100, 2000);
eraseWhere(m_predictedTiles, [expiry](auto& pair){ return pair.second.time > expiry; });
// Secret broadcasts are transmitted through DamageNotifications for vanilla server compatibility.
// Because DamageNotification packets are spoofable, we have to sign the data so other clients can validate that it is legitimate.
@ -1146,6 +1156,11 @@ void WorldClient::update(float dt) {
if (m_collisionDebug)
renderCollisionDebug();
for (auto const& prediction : m_predictedTiles) {
auto poly = PolyF(RectF::withCenter(Vec2F(prediction.first) + Vec2F::filled(0.5f), Vec2F::filled(0.875f)));
SpatialLogger::logPoly("world", poly, Color::Cyan.toRgba());
}
LogMap::set("client_entities", m_entityMap->size());
LogMap::set("client_sectors", toString(loadedSectors.size()));
LogMap::set("client_lua_mem", m_luaRoot->luaMemoryUsage());
@ -1490,6 +1505,7 @@ void WorldClient::lightingTileGather() {
auto materialDatabase = Root::singleton().materialDatabase();
// Each column in tileEvalColumns is guaranteed to be no larger than the sector size.
m_tileArray->tileEvalColumns(m_lightingCalculator.calculationRegion(), [&](Vec2I const& pos, ClientTile const* column, size_t ySize) {
size_t baseIndex = m_lightingCalculator.baseIndexFor(pos);
for (size_t y = 0; y < ySize; ++y) {
@ -1552,6 +1568,23 @@ void WorldClient::initWorld(WorldStartPacket const& startPacket) {
m_worldTemplate = make_shared<WorldTemplate>(startPacket.templateData);
m_entityMap = make_shared<EntityMap>(m_worldTemplate->size(), entitySpace.first, entitySpace.second);
m_tileArray = make_shared<ClientTileSectorArray>(m_worldTemplate->size());
m_tileGetterFunction = [&, tile = ClientTile()](Vec2I pos) mutable -> ClientTile const& {
if (!m_predictedTiles.empty()) {
if (auto p = m_predictedTiles.ptr(pos)) {
p->apply(tile = m_tileArray->tile(pos));
if (p->liquid) {
if (p->liquid->liquid == tile.liquid.liquid)
tile.liquid.level += p->liquid->level;
else {
tile.liquid.liquid = p->liquid->liquid;
tile.liquid.level = p->liquid->level;
}
}
return tile;
}
}
return m_tileArray->tile(pos);
};
m_damageManager = make_shared<DamageManager>(this, startPacket.clientId);
m_luaRoot->restart();
m_luaRoot->tuneAutoGarbageCollection(m_clientConfig.getFloat("luaGcPause"), m_clientConfig.getFloat("luaGcStepMultiplier"));
@ -1735,7 +1768,33 @@ bool WorldClient::readNetTile(Vec2I const& pos, NetTile const& netTile) {
if (!tile)
return false;
m_predictedTiles.remove(pos);
if (!m_predictedTiles.empty()) {
auto findPrediction = m_predictedTiles.find(pos);
if (findPrediction != m_predictedTiles.end()) {
auto& p = findPrediction->second;
if (p.foreground && *p.foreground == netTile.foreground)
p.foreground.reset();
if (p.foregroundMod && *p.foregroundMod == netTile.foregroundMod)
p.foregroundMod.reset();
if (p.foregroundHueShift && *p.foregroundHueShift == netTile.foregroundHueShift)
p.foregroundHueShift.reset();
if (p.foregroundModHueShift && *p.foregroundModHueShift == netTile.foregroundModHueShift)
p.foregroundModHueShift.reset();
if (p.background && *p.background == netTile.background)
p.background.reset();
if (p.backgroundMod && *p.backgroundMod == netTile.backgroundMod)
p.backgroundMod.reset();
if (p.backgroundHueShift && *p.backgroundHueShift == netTile.backgroundHueShift)
p.backgroundHueShift.reset();
if (p.backgroundModHueShift && *p.backgroundModHueShift == netTile.backgroundModHueShift)
p.backgroundModHueShift.reset();
if (!p)
m_predictedTiles.erase(findPrediction);
}
}
tile->background = netTile.background;
tile->backgroundHueShift = netTile.backgroundHueShift;
@ -2079,6 +2138,41 @@ void WorldClient::renderCollisionDebug() {
}
}
void WorldClient::informTilePredictions(TileModificationList const& modifications) {
auto now = Time::monotonicMilliseconds();
for (auto& pair : modifications) {
auto& p = m_predictedTiles[pair.first];
p.time = now;
if (auto placeMaterial = pair.second.ptr<PlaceMaterial>()) {
if (placeMaterial->layer == TileLayer::Foreground) {
p.foreground = placeMaterial->material;
p.foregroundHueShift = placeMaterial->materialHueShift;
} else {
p.background = placeMaterial->material;
p.backgroundHueShift = placeMaterial->materialHueShift;
}
}
else if (auto placeMod = pair.second.ptr<PlaceMod>()) {
if (placeMod->layer == TileLayer::Foreground)
p.foregroundMod = placeMod->mod;
else
p.backgroundMod = placeMod->mod;
}
else if (auto placeColor = pair.second.ptr<PlaceMaterialColor>()) {
if (placeColor->layer == TileLayer::Foreground)
p.foregroundColorVariant = placeColor->color;
else
p.backgroundColorVariant = placeColor->color;
}
else if (auto placeLiquid = pair.second.ptr<PlaceLiquid>()) {
if (!p.liquid || p.liquid->liquid != placeLiquid->liquid)
p.liquid = LiquidLevel(placeLiquid->liquid, placeLiquid->liquidLevel);
else
p.liquid->level += placeLiquid->liquidLevel;
}
}
}
void WorldClient::setupForceRegions() {
m_forceRegions.clear();

View File

@ -203,6 +203,8 @@ private:
bool operator<(DamageNumberKey const& other) const;
};
typedef function<ClientTile const& (Vec2I)> ClientTileGetter;
void lightingTileGather();
void lightingMain();
@ -234,6 +236,8 @@ private:
void freshenCollision(RectI const& region);
void renderCollisionDebug();
void informTilePredictions(TileModificationList const& modifications);
void setTileProtection(DungeonId dungeonId, bool isProtected);
void setupForceRegions();
@ -247,7 +251,7 @@ private:
EntityMapPtr m_entityMap;
ClientTileSectorArrayPtr m_tileArray;
ClientTileGetter m_tileGetterFunction;
DamageManagerPtr m_damageManager;
LuaRootPtr m_luaRoot;
@ -335,7 +339,7 @@ private:
bool m_altMusicActive;
int m_modifiedTilePredictionTimeout;
HashMap<Vec2I, int> m_predictedTiles;
HashMap<Vec2I, PredictedTile> m_predictedTiles;
HashSet<EntityId> m_startupHiddenEntities;
HashMap<DungeonId, float> m_dungeonIdGravity;

View File

@ -36,24 +36,23 @@ namespace WorldImpl {
List<Vec2I> collidingTilesAlongLine(WorldGeometry const& worldGeometry, shared_ptr<TileSectorArray> const& tileSectorArray,
Vec2F const& begin, Vec2F const& end, CollisionSet const& collisionSet, size_t maxSize, bool includeEdges);
template <typename TileSectorArray>
bool canPlaceMaterial(shared_ptr<TileSectorArray> const& tileSectorArray, EntityMapPtr const& entityMap,
Vec2I const& pos, TileLayer layer, MaterialId material, bool allowEntityOverlap);
template <typename GetTileFunction>
bool canPlaceMaterial(EntityMapPtr const& entityMap,
Vec2I const& pos, TileLayer layer, MaterialId material, bool allowEntityOverlap, GetTileFunction& getTile);
// returns true if this material could be placed if in the same batch other
// tiles can be placed
// that connect to it
template <typename TileSectorArray>
bool perhapsCanPlaceMaterial(shared_ptr<TileSectorArray> const& tileSectorArray, EntityMapPtr const& entityMap,
Vec2I const& pos, TileLayer layer, MaterialId material, bool allowEntityOverlap);
template <typename TileSectorArray>
bool canPlaceMaterialColorVariant(shared_ptr<TileSectorArray> const& tileSectorArray, Vec2I const& pos,
TileLayer layer, MaterialColorVariant color);
template <typename TileSectorArray>
bool canPlaceMod(shared_ptr<TileSectorArray> const& tileSectorArray, Vec2I const& pos, TileLayer layer, ModId mod);
template <typename GetTileFunction>
bool perhapsCanPlaceMaterial(EntityMapPtr const& entityMap,
Vec2I const& pos, TileLayer layer, MaterialId material, bool allowEntityOverlap, GetTileFunction& getTile);
template <typename GetTileFunction>
bool canPlaceMaterialColorVariant(Vec2I const& pos, TileLayer layer, MaterialColorVariant color, GetTileFunction& getTile);
template <typename GetTileFunction>
bool canPlaceMod(Vec2I const& pos, TileLayer layer, ModId mod, GetTileFunction& getTile);
// Split modification list into good and bad
template <typename TileSectorArray>
pair<TileModificationList, TileModificationList> splitTileModifications(shared_ptr<TileSectorArray> const& tileSectorArray, EntityMapPtr const& entityMap,
TileModificationList const& modificationList, function<bool(Vec2I pos, TileModification modification)> extraCheck = {});
template <typename GetTileFunction>
pair<TileModificationList, TileModificationList> splitTileModifications(EntityMapPtr const& entityMap, TileModificationList const& modificationList,
bool allowEntityOverlap, function<bool(Vec2I pos, GetTileFunction& getTile, TileModification modification)> extraCheck = {});
template <typename TileSectorArray>
float windLevel(shared_ptr<TileSectorArray> const& tileSectorArray, Vec2F const& position, float weatherWindLevel);
@ -207,43 +206,48 @@ namespace WorldImpl {
return res;
}
template <typename TileSectorArray>
bool canPlaceMaterial(shared_ptr<TileSectorArray> const& tileSectorArray, EntityMapPtr const& entityMap,
Vec2I const& pos, TileLayer layer, MaterialId material, bool allowEntityOverlap) {
template <typename GetTileFunction>
bool canPlaceMaterial(EntityMapPtr const& entityMap,
Vec2I const& pos, TileLayer layer, MaterialId material, bool allowEntityOverlap, GetTileFunction& getTile) {
auto materialDatabase = Root::singleton().materialDatabase();
if (!isRealMaterial(material))
return false;
auto isAdjacentToConnectable = [&](Vec2I const& pos, unsigned distance, bool foreground) {
return tileSectorArray->tileSatisfies(pos, distance, [&](Vec2I const& tpos, typename TileSectorArray::Tile const& tile) {
// Skip if we're looking at the block at pos, allow placement if
// placing on world bottom
if (tpos == pos)
return false;
else if (tpos[1] < 0)
if (pos.y() - distance < 0)
return true;
else if (foreground)
return isConnectableMaterial(tile.foreground);
else
return isConnectableMaterial(tile.background);
});
int maxY = pos.y() + distance + 1;
int maxX = pos.x() + distance + 1;
for (int y = pos.y() - distance; y != maxY; ++y) {
Vec2I tPos = { 0, y };
for (int x = pos.x() - distance; x != maxX; ++x) {
tPos[0] = x;
if (tPos != pos) {
auto& tile = getTile(tPos);
if (isConnectableMaterial(foreground ? tile.foreground : tile.background))
return true;
}
}
}
return false;
};
if (!materialDatabase->canPlaceInLayer(material, layer))
return false;
if (layer == TileLayer::Background) {
if (tileSectorArray->tile(pos).background != EmptyMaterialId)
if (getTile(pos).background != EmptyMaterialId)
return false;
// Can attach background blocks to other background blocks, *or* the
// foreground block in front of it.
if (!isAdjacentToConnectable(pos, 1, false)
&& !isConnectableMaterial(tileSectorArray->tile({pos[0], pos[1]}).foreground))
&& !isConnectableMaterial(getTile({pos[0], pos[1]}).foreground))
return false;
} else {
if (tileSectorArray->tile(pos).foreground != EmptyMaterialId)
if (getTile(pos).foreground != EmptyMaterialId)
return false;
if (entityMap->tileIsOccupied(pos))
@ -252,16 +256,16 @@ namespace WorldImpl {
if (!allowEntityOverlap && entityMap->spaceIsOccupied(RectF::withSize(Vec2F(pos), Vec2F(1, 1))))
return false;
if (!isAdjacentToConnectable(pos, 1, true) && !isConnectableMaterial(tileSectorArray->tile({pos[0], pos[1]}).background))
if (!isAdjacentToConnectable(pos, 1, true) && !isConnectableMaterial(getTile({pos[0], pos[1]}).background))
return false;
}
return true;
}
template <typename TileSectorArray>
bool perhapsCanPlaceMaterial(shared_ptr<TileSectorArray> const& tileSectorArray, EntityMapPtr const& entityMap,
Vec2I const& pos, TileLayer layer, MaterialId material, bool allowEntityOverlap) {
template <typename GetTileFunction>
bool perhapsCanPlaceMaterial(EntityMapPtr const& entityMap,
Vec2I const& pos, TileLayer layer, MaterialId material, bool allowEntityOverlap, GetTileFunction& getTile) {
auto materialDatabase = Root::singleton().materialDatabase();
if (!isRealMaterial(material))
@ -271,10 +275,10 @@ namespace WorldImpl {
return false;
if (layer == TileLayer::Background) {
if (tileSectorArray->tile(pos).background != EmptyMaterialId)
if (getTile(pos).background != EmptyMaterialId)
return false;
} else {
if (tileSectorArray->tile(pos).foreground != EmptyMaterialId)
if (getTile(pos).foreground != EmptyMaterialId)
return false;
if (entityMap->tileIsOccupied(pos))
@ -287,31 +291,31 @@ namespace WorldImpl {
return true;
}
template <typename TileSectorArray>
bool canPlaceMaterialColorVariant(shared_ptr<TileSectorArray> const& tileSectorArray,
Vec2I const& pos, TileLayer layer, MaterialColorVariant color) {
template <typename GetTileFunction>
bool canPlaceMaterialColorVariant(Vec2I const& pos, TileLayer layer, MaterialColorVariant color, GetTileFunction& getTile) {
auto materialDatabase = Root::singleton().materialDatabase();
auto mat = tileSectorArray->tile(pos).material(layer);
auto existingColor = tileSectorArray->tile(pos).materialColor(layer);
auto existingHue = layer == TileLayer::Foreground ? tileSectorArray->tile(pos).foregroundHueShift : tileSectorArray->tile(pos).backgroundHueShift;
auto& tile = getTile(pos);
auto mat = tile.material(layer);
auto existingColor = tile.materialColor(layer);
auto existingHue = layer == TileLayer::Foreground ? tile.foregroundHueShift : tile.backgroundHueShift;
return existingHue != 0 || (existingColor != color && materialDatabase->isMultiColor(mat));
}
template <typename TileSectorArray>
bool canPlaceMod(shared_ptr<TileSectorArray> const& tileSectorArray, Vec2I const& pos, TileLayer layer, ModId mod) {
template <typename GetTileFunction>
bool canPlaceMod(Vec2I const& pos, TileLayer layer, ModId mod, GetTileFunction& getTile) {
if (!isRealMod(mod))
return false;
auto materialDatabase = Root::singleton().materialDatabase();
auto mat = tileSectorArray->tile(pos).material(layer);
auto existingMod = tileSectorArray->tile(pos).mod(layer);
auto mat = getTile(pos).material(layer);
auto existingMod = getTile(pos).mod(layer);
return existingMod != mod && materialDatabase->supportsMod(mat, mod);
}
template <typename TileSectorArray>
pair<TileModificationList, TileModificationList> splitTileModifications(shared_ptr<TileSectorArray> const& tileSectorArray, EntityMapPtr const& entityMap,
TileModificationList const& modificationList, bool allowEntityOverlap, function<bool(Vec2I pos, TileModification modification)> extraCheck) {
template <typename GetTileFunction>
pair<TileModificationList, TileModificationList> splitTileModifications(EntityMapPtr const& entityMap,
TileModificationList const& modificationList, bool allowEntityOverlap, GetTileFunction& getTile, function<bool(Vec2I pos, TileModification modification)> extraCheck) {
TileModificationList success;
TileModificationList unknown;
TileModificationList failures;
@ -325,15 +329,15 @@ namespace WorldImpl {
if (extraCheck && !extraCheck(pos, modification)) {
good = false;
} else if (auto placeMaterial = modification.ptr<PlaceMaterial>()) {
perhaps = WorldImpl::perhapsCanPlaceMaterial(tileSectorArray, entityMap, pos, placeMaterial->layer, placeMaterial->material, allowEntityOverlap);
perhaps = WorldImpl::perhapsCanPlaceMaterial(entityMap, pos, placeMaterial->layer, placeMaterial->material, allowEntityOverlap, getTile);
if (perhaps)
good = WorldImpl::canPlaceMaterial(tileSectorArray, entityMap, pos, placeMaterial->layer, placeMaterial->material, allowEntityOverlap);
good = WorldImpl::canPlaceMaterial(entityMap, pos, placeMaterial->layer, placeMaterial->material, allowEntityOverlap, getTile);
} else if (auto placeMod = modification.ptr<PlaceMod>()) {
good = WorldImpl::canPlaceMod(tileSectorArray, pos, placeMod->layer, placeMod->mod);
good = WorldImpl::canPlaceMod(pos, placeMod->layer, placeMod->mod, getTile);
} else if (auto placeMaterialColor = modification.ptr<PlaceMaterialColor>()) {
good = WorldImpl::canPlaceMaterialColorVariant(tileSectorArray, pos, placeMaterialColor->layer, placeMaterialColor->color);
good = WorldImpl::canPlaceMaterialColorVariant(pos, placeMaterialColor->layer, placeMaterialColor->color, getTile);
} else if (modification.is<PlaceLiquid>()) {
good = tileSectorArray->tile(pos).collision == CollisionKind::None;
good = getTile(pos).collision == CollisionKind::None;
} else {
good = false;
}

View File

@ -822,7 +822,7 @@ void WorldServer::setSpawningEnabled(bool spawningEnabled) {
}
TileModificationList WorldServer::validTileModifications(TileModificationList const& modificationList, bool allowEntityOverlap) const {
return WorldImpl::splitTileModifications(m_tileArray, m_entityMap, modificationList, allowEntityOverlap, [this](Vec2I pos, TileModification) {
return WorldImpl::splitTileModifications(m_entityMap, modificationList, allowEntityOverlap, m_tileGetterFunction, [this](Vec2I pos, TileModification) {
return !isTileProtected(pos);
}).first;
}
@ -1235,6 +1235,7 @@ void WorldServer::init(bool firstTime) {
m_geometry = WorldGeometry(m_worldTemplate->size());
m_entityMap = m_worldStorage->entityMap();
m_tileArray = m_worldStorage->tileArray();
m_tileGetterFunction = [&](Vec2I pos) -> ServerTile const& { return m_tileArray->tile(pos); };
m_damageManager = make_shared<DamageManager>(this, ServerConnectionId);
m_wireProcessor = make_shared<WireProcessor>(m_worldStorage);
m_luaRoot = make_shared<LuaRoot>();
@ -1347,7 +1348,7 @@ TileModificationList WorldServer::doApplyTileModifications(TileModificationList
continue;
if (auto placeMaterial = modification.ptr<PlaceMaterial>()) {
if (!WorldImpl::canPlaceMaterial(m_tileArray, m_entityMap, pos, placeMaterial->layer, placeMaterial->material, allowEntityOverlap))
if (!WorldImpl::canPlaceMaterial(m_entityMap, pos, placeMaterial->layer, placeMaterial->material, allowEntityOverlap, m_tileGetterFunction))
continue;
ServerTile* tile = m_tileArray->modifyTile(pos);
@ -1396,7 +1397,7 @@ TileModificationList WorldServer::doApplyTileModifications(TileModificationList
queueTileUpdates(pos);
} else if (auto placeMod = modification.ptr<PlaceMod>()) {
if (!WorldImpl::canPlaceMod(m_tileArray, pos, placeMod->layer, placeMod->mod))
if (!WorldImpl::canPlaceMod(pos, placeMod->layer, placeMod->mod, m_tileGetterFunction))
continue;
ServerTile* tile = m_tileArray->modifyTile(pos);
@ -1421,7 +1422,7 @@ TileModificationList WorldServer::doApplyTileModifications(TileModificationList
queueTileUpdates(pos);
} else if (auto placeMaterialColor = modification.ptr<PlaceMaterialColor>()) {
if (!WorldImpl::canPlaceMaterialColorVariant(m_tileArray, pos, placeMaterialColor->layer, placeMaterialColor->color))
if (!WorldImpl::canPlaceMaterialColorVariant(pos, placeMaterialColor->layer, placeMaterialColor->color, m_tileGetterFunction))
continue;
WorldTile* tile = m_tileArray->modifyTile(pos);

View File

@ -296,6 +296,8 @@ private:
List<Vec2I> roots;
};
typedef function<ServerTile const& (Vec2I)> ServerTileGetter;
void init(bool firstTime);
// Returns nothing if the processing defined by the given configuration entry
@ -346,6 +348,7 @@ private:
EntityMapPtr m_entityMap;
ServerTileSectorArrayPtr m_tileArray;
ServerTileGetter m_tileGetterFunction;
WorldStoragePtr m_worldStorage;
WorldServerFidelity m_fidelity;
Json m_fidelityConfig;

View File

@ -109,6 +109,22 @@ bool ServerTile::updateCollision(CollisionKind kind) {
return false;
}
PredictedTile::operator bool() const {
return
background
|| backgroundHueShift
|| backgroundColorVariant
|| backgroundMod
|| backgroundModHueShift
|| foreground
|| foregroundHueShift
|| foregroundColorVariant
|| foregroundMod
|| foregroundModHueShift
|| liquid
|| collision;
}
DataStream& operator>>(DataStream& ds, NetTile& tile) {
ds.read(tile.background);
if (tile.background == 0) {

View File

@ -119,6 +119,37 @@ struct NetTile {
DataStream& operator>>(DataStream& ds, NetTile& tile);
DataStream& operator<<(DataStream& ds, NetTile const& tile);
// For storing predicted tile state.
struct PredictedTile {
int64_t time;
Maybe<MaterialId> background;
Maybe<MaterialHue> backgroundHueShift;
Maybe<MaterialColorVariant> backgroundColorVariant;
Maybe<ModId> backgroundMod;
Maybe<MaterialHue> backgroundModHueShift;
Maybe<MaterialId> foreground;
Maybe<MaterialHue> foregroundHueShift;
Maybe<MaterialColorVariant> foregroundColorVariant;
Maybe<ModId> foregroundMod;
Maybe<MaterialHue> foregroundModHueShift;
Maybe<LiquidLevel> liquid;
Maybe<CollisionKind> collision;
operator bool() const;
template <typename Tile>
void apply(Tile& tile) {
if (foreground) tile.foreground = *foreground;
if (foregroundMod) tile.foregroundMod = *foregroundMod;
if (foregroundHueShift) tile.foregroundHueShift = *foregroundHueShift;
if (foregroundModHueShift) tile.foregroundModHueShift = *foregroundModHueShift;
if (background) tile.background = *background;
if (backgroundMod) tile.backgroundMod = *backgroundMod;
if (backgroundHueShift) tile.backgroundHueShift = *backgroundHueShift;
if (backgroundModHueShift) tile.backgroundModHueShift = *backgroundModHueShift;
}
};
// Just the parts of a tile that are used to render. The members here are laid
// out specifically to avoid padding bytes so that a fast path can be taken
// when hashing for chunk render caching.