Objects can now be placed under tiles that have non-block collision

This commit is contained in:
Kae 2023-08-21 00:59:02 +10:00
parent edbee201ee
commit a7ae034278
8 changed files with 100 additions and 46 deletions

View File

@ -32,9 +32,9 @@ bool ObjectOrientation::placementValid(World const* world, Vec2I const& position
if (!world) if (!world)
return false; return false;
for (auto space : spaces) { for (Vec2I space : spaces) {
space += position; space += position;
if (world->tileIsOccupied(space, TileLayer::Foreground) || world->isTileProtected(space)) if (world->tileIsOccupied(space, TileLayer::Foreground, false, true) || world->isTileProtected(space))
return false; return false;
} }
return true; return true;

View File

@ -238,10 +238,16 @@ EntityPtr WorldClient::findEntityAtTile(Vec2I const& pos, EntityFilterOf<TileEnt
return m_entityMap->findEntityAtTile(pos, entityFilter); return m_entityMap->findEntityAtTile(pos, entityFilter);
} }
bool WorldClient::tileIsOccupied(Vec2I const& pos, TileLayer layer, bool includeEphemeral) const { bool WorldClient::tileIsOccupied(Vec2I const& pos, TileLayer layer, bool includeEphemeral, bool checkCollision) const {
if (!inWorld()) if (!inWorld())
return false; return false;
return WorldImpl::tileIsOccupied(m_tileArray, m_entityMap, pos, layer, includeEphemeral); return WorldImpl::tileIsOccupied(m_tileArray, m_entityMap, pos, layer, includeEphemeral, checkCollision);
}
CollisionKind WorldClient::tileCollisionKind(Vec2I const& pos) const {
if (!inWorld())
return CollisionKind::Null;
return WorldImpl::tileCollisionKind(m_tileArray, m_entityMap, pos);
} }
void WorldClient::forEachCollisionBlock(RectI const& region, function<void(CollisionBlock const&)> const& iterator) const { void WorldClient::forEachCollisionBlock(RectI const& region, function<void(CollisionBlock const&)> const& iterator) const {

View File

@ -63,7 +63,8 @@ public:
EntityPtr findEntity(RectF const& boundBox, EntityFilter entityFilter) const override; EntityPtr findEntity(RectF const& boundBox, EntityFilter entityFilter) const override;
EntityPtr findEntityLine(Vec2F const& begin, Vec2F const& end, EntityFilter entityFilter) const override; EntityPtr findEntityLine(Vec2F const& begin, Vec2F const& end, EntityFilter entityFilter) const override;
EntityPtr findEntityAtTile(Vec2I const& pos, EntityFilterOf<TileEntity> entityFilter) const override; EntityPtr findEntityAtTile(Vec2I const& pos, EntityFilterOf<TileEntity> entityFilter) const override;
bool tileIsOccupied(Vec2I const& pos, TileLayer layer, bool includeEphemeral = false) const override; bool tileIsOccupied(Vec2I const& pos, TileLayer layer, bool includeEphemeral = false, bool checkCollision = false) const override;
CollisionKind tileCollisionKind(Vec2I const& pos) const override;
void forEachCollisionBlock(RectI const& region, function<void(CollisionBlock const&)> const& iterator) const override; void forEachCollisionBlock(RectI const& region, function<void(CollisionBlock const&)> const& iterator) const override;
bool isTileConnectable(Vec2I const& pos, TileLayer layer, bool tilesOnly = false) const override; bool isTileConnectable(Vec2I const& pos, TileLayer layer, bool tilesOnly = false) const override;
bool pointTileCollision(Vec2F const& point, CollisionSet const& collisionSet = DefaultCollisionSet) const override; bool pointTileCollision(Vec2F const& point, CollisionSet const& collisionSet = DefaultCollisionSet) const override;

View File

@ -21,7 +21,11 @@ namespace Star {
namespace WorldImpl { namespace WorldImpl {
template <typename TileSectorArray> template <typename TileSectorArray>
bool tileIsOccupied(shared_ptr<TileSectorArray> const& tileSectorArray, EntityMapPtr const& entityMap, bool tileIsOccupied(shared_ptr<TileSectorArray> const& tileSectorArray, EntityMapPtr const& entityMap,
Vec2I const& pos, TileLayer layer, bool includeEphemeral); Vec2I const& pos, TileLayer layer, bool includeEphemeral, bool checkCollision = false);
template <typename TileSectorArray>
CollisionKind tileCollisionKind(shared_ptr<TileSectorArray> const& tileSectorArray, EntityMapPtr const& entityMap,
Vec2I const& pos);
template <typename TileSectorArray> template <typename TileSectorArray>
bool rectTileCollision(shared_ptr<TileSectorArray> const& tileSectorArray, RectI const& region, bool solidCollision); bool rectTileCollision(shared_ptr<TileSectorArray> const& tileSectorArray, RectI const& region, bool solidCollision);
@ -38,13 +42,13 @@ namespace WorldImpl {
template <typename GetTileFunction> template <typename GetTileFunction>
bool canPlaceMaterial(EntityMapPtr const& entityMap, bool canPlaceMaterial(EntityMapPtr const& entityMap,
Vec2I const& pos, TileLayer layer, MaterialId material, bool allowEntityOverlap, GetTileFunction& getTile); Vec2I const& pos, TileLayer layer, MaterialId material, bool allowEntityOverlap, bool allowTileOverlap, GetTileFunction& getTile);
// returns true if this material could be placed if in the same batch other // returns true if this material could be placed if in the same batch other
// tiles can be placed // tiles can be placed
// that connect to it // that connect to it
template <typename GetTileFunction> template <typename GetTileFunction>
bool perhapsCanPlaceMaterial(EntityMapPtr const& entityMap, bool perhapsCanPlaceMaterial(EntityMapPtr const& entityMap,
Vec2I const& pos, TileLayer layer, MaterialId material, bool allowEntityOverlap, GetTileFunction& getTile); Vec2I const& pos, TileLayer layer, MaterialId material, bool allowEntityOverlap, bool allowTileOverlap, GetTileFunction& getTile);
template <typename GetTileFunction> template <typename GetTileFunction>
bool canPlaceMaterialColorVariant(Vec2I const& pos, TileLayer layer, MaterialColorVariant color, GetTileFunction& getTile); bool canPlaceMaterialColorVariant(Vec2I const& pos, TileLayer layer, MaterialColorVariant color, GetTileFunction& getTile);
template <typename GetTileFunction> template <typename GetTileFunction>
@ -80,11 +84,18 @@ namespace WorldImpl {
template <typename TileSectorArray> template <typename TileSectorArray>
bool tileIsOccupied(shared_ptr<TileSectorArray> const& tileSectorArray, EntityMapPtr const& entityMap, bool tileIsOccupied(shared_ptr<TileSectorArray> const& tileSectorArray, EntityMapPtr const& entityMap,
Vec2I const& pos, TileLayer layer, bool includeEphemeral) { Vec2I const& pos, TileLayer layer, bool includeEphemeral, bool checkCollision) {
auto& tile = tileSectorArray->tile(pos);
if (layer == TileLayer::Foreground) if (layer == TileLayer::Foreground)
return tileSectorArray->tile(pos).foreground != EmptyMaterialId || entityMap->tileIsOccupied(pos, includeEphemeral); return (checkCollision ? tile.collision >= CollisionKind::Dynamic : tile.foreground != EmptyMaterialId) || entityMap->tileIsOccupied(pos, includeEphemeral);
else else
return tileSectorArray->tile(pos).background != EmptyMaterialId; return tile.background != EmptyMaterialId;
}
template <typename TileSectorArray>
CollisionKind tileCollisionKind(shared_ptr<TileSectorArray> const& tileSectorArray, EntityMapPtr const& entityMap,
Vec2I const& pos) {
return tileSectorArray->tile(pos).collision;
} }
template <typename TileSectorArray> template <typename TileSectorArray>
@ -210,7 +221,7 @@ namespace WorldImpl {
template <typename GetTileFunction> template <typename GetTileFunction>
bool canPlaceMaterial(EntityMapPtr const& entityMap, bool canPlaceMaterial(EntityMapPtr const& entityMap,
Vec2I const& pos, TileLayer layer, MaterialId material, bool allowEntityOverlap, GetTileFunction& getTile) { Vec2I const& pos, TileLayer layer, MaterialId material, bool allowEntityOverlap, bool allowTileOverlap, GetTileFunction& getTile) {
auto materialDatabase = Root::singleton().materialDatabase(); auto materialDatabase = Root::singleton().materialDatabase();
if (!isRealMaterial(material)) if (!isRealMaterial(material))
@ -239,26 +250,26 @@ namespace WorldImpl {
if (!materialDatabase->canPlaceInLayer(material, layer)) if (!materialDatabase->canPlaceInLayer(material, layer))
return false; return false;
auto& tile = getTile(pos);
if (layer == TileLayer::Background) { if (layer == TileLayer::Background) {
if (getTile(pos).background != EmptyMaterialId) if (tile.background != EmptyMaterialId && tile.background != ObjectPlatformMaterialId)
return false; return false;
// Can attach background blocks to other background blocks, *or* the // Can attach background blocks to other background blocks, *or* the
// foreground block in front of it. // foreground block in front of it.
if (!isAdjacentToConnectable(pos, 1, false) if (!isAdjacentToConnectable(pos, 1, false) && !isConnectableMaterial(tile.foreground))
&& !isConnectableMaterial(getTile({pos[0], pos[1]}).foreground))
return false; return false;
} else { } else {
if (getTile(pos).foreground != EmptyMaterialId) if (tile.foreground != EmptyMaterialId && tile.foreground != ObjectPlatformMaterialId)
return false; return false;
if (entityMap->tileIsOccupied(pos)) if (!allowTileOverlap && entityMap->tileIsOccupied(pos))
return false; return false;
if (!allowEntityOverlap && entityMap->spaceIsOccupied(RectF::withSize(Vec2F(pos), Vec2F(1, 1)))) if (!allowEntityOverlap && entityMap->spaceIsOccupied(RectF::withSize(Vec2F(pos), Vec2F(0.999f, 0.999f))))
return false; return false;
if (!isAdjacentToConnectable(pos, 1, true) && !isConnectableMaterial(getTile({pos[0], pos[1]}).background)) if (!isAdjacentToConnectable(pos, 1, true) && !isConnectableMaterial(tile.background))
return false; return false;
} }
@ -267,7 +278,7 @@ namespace WorldImpl {
template <typename GetTileFunction> template <typename GetTileFunction>
bool perhapsCanPlaceMaterial(EntityMapPtr const& entityMap, bool perhapsCanPlaceMaterial(EntityMapPtr const& entityMap,
Vec2I const& pos, TileLayer layer, MaterialId material, bool allowEntityOverlap, GetTileFunction& getTile) { Vec2I const& pos, TileLayer layer, MaterialId material, bool allowEntityOverlap, bool allowTileOverlap, GetTileFunction& getTile) {
auto materialDatabase = Root::singleton().materialDatabase(); auto materialDatabase = Root::singleton().materialDatabase();
if (!isRealMaterial(material)) if (!isRealMaterial(material))
@ -276,17 +287,18 @@ namespace WorldImpl {
if (!materialDatabase->canPlaceInLayer(material, layer)) if (!materialDatabase->canPlaceInLayer(material, layer))
return false; return false;
auto& tile = getTile(pos);
if (layer == TileLayer::Background) { if (layer == TileLayer::Background) {
if (getTile(pos).background != EmptyMaterialId) if (tile.background != EmptyMaterialId && tile.background != ObjectPlatformMaterialId)
return false; return false;
} else { } else {
if (getTile(pos).foreground != EmptyMaterialId) if (tile.foreground != EmptyMaterialId && tile.foreground != ObjectPlatformMaterialId)
return false; return false;
if (entityMap->tileIsOccupied(pos)) if (!allowTileOverlap && entityMap->tileIsOccupied(pos))
return false; return false;
if (!allowEntityOverlap && entityMap->spaceIsOccupied(RectF::withSize(Vec2F(pos), Vec2F(1, 1)))) if (!allowEntityOverlap && entityMap->spaceIsOccupied(RectF::withSize(Vec2F(pos), Vec2F(0.999f, 0.999f))))
return false; return false;
} }
@ -321,9 +333,10 @@ namespace WorldImpl {
bool perhaps = false; bool perhaps = false;
if (auto placeMaterial = modification.ptr<PlaceMaterial>()) { if (auto placeMaterial = modification.ptr<PlaceMaterial>()) {
perhaps = WorldImpl::perhapsCanPlaceMaterial(entityMap, pos, placeMaterial->layer, placeMaterial->material, allowEntityOverlap, getTile); bool allowTileOverlap = placeMaterial->collisionOverride != TileCollisionOverride::None && collisionKindFromOverride(placeMaterial->collisionOverride) < CollisionKind::Dynamic;
perhaps = WorldImpl::perhapsCanPlaceMaterial(entityMap, pos, placeMaterial->layer, placeMaterial->material, allowEntityOverlap, allowTileOverlap, getTile);
if (perhaps) if (perhaps)
good = WorldImpl::canPlaceMaterial(entityMap, pos, placeMaterial->layer, placeMaterial->material, allowEntityOverlap, getTile); good = WorldImpl::canPlaceMaterial(entityMap, pos, placeMaterial->layer, placeMaterial->material, allowEntityOverlap, allowTileOverlap, getTile);
} else if (auto placeMod = modification.ptr<PlaceMod>()) { } else if (auto placeMod = modification.ptr<PlaceMod>()) {
good = WorldImpl::canPlaceMod(pos, placeMod->layer, placeMod->mod, getTile); good = WorldImpl::canPlaceMod(pos, placeMod->layer, placeMod->mod, getTile);
} else if (auto placeMaterialColor = modification.ptr<PlaceMaterialColor>()) { } else if (auto placeMaterialColor = modification.ptr<PlaceMaterialColor>()) {

View File

@ -756,10 +756,15 @@ EntityPtr WorldServer::findEntityAtTile(Vec2I const& pos, EntityFilterOf<TileEnt
return m_entityMap->findEntityAtTile(pos, entityFilter); return m_entityMap->findEntityAtTile(pos, entityFilter);
} }
bool WorldServer::tileIsOccupied(Vec2I const& pos, TileLayer layer, bool includeEphemeral) const { bool WorldServer::tileIsOccupied(Vec2I const& pos, TileLayer layer, bool includeEphemeral, bool checkCollision) const {
return WorldImpl::tileIsOccupied(m_tileArray, m_entityMap, pos, layer, includeEphemeral); return WorldImpl::tileIsOccupied(m_tileArray, m_entityMap, pos, layer, includeEphemeral, checkCollision);
} }
CollisionKind WorldServer::tileCollisionKind(Vec2I const& pos) const {
return WorldImpl::tileCollisionKind(m_tileArray, m_entityMap, pos);
}
void WorldServer::forEachCollisionBlock(RectI const& region, function<void(CollisionBlock const&)> const& iterator) const { void WorldServer::forEachCollisionBlock(RectI const& region, function<void(CollisionBlock const&)> const& iterator) const {
const_cast<WorldServer*>(this)->freshenCollision(region); const_cast<WorldServer*>(this)->freshenCollision(region);
m_tileArray->tileEach(region, [iterator](Vec2I const& pos, ServerTile const& tile) { m_tileArray->tileEach(region, [iterator](Vec2I const& pos, ServerTile const& tile) {
@ -1348,7 +1353,8 @@ TileModificationList WorldServer::doApplyTileModifications(TileModificationList
continue; continue;
if (auto placeMaterial = modification.ptr<PlaceMaterial>()) { if (auto placeMaterial = modification.ptr<PlaceMaterial>()) {
if (!WorldImpl::canPlaceMaterial(m_entityMap, pos, placeMaterial->layer, placeMaterial->material, allowEntityOverlap, m_tileGetterFunction)) bool allowTileOverlap = placeMaterial->collisionOverride != TileCollisionOverride::None && collisionKindFromOverride(placeMaterial->collisionOverride) < CollisionKind::Dynamic;
if (!WorldImpl::canPlaceMaterial(m_entityMap, pos, placeMaterial->layer, placeMaterial->material, allowEntityOverlap, allowTileOverlap, m_tileGetterFunction))
continue; continue;
ServerTile* tile = m_tileArray->modifyTile(pos); ServerTile* tile = m_tileArray->modifyTile(pos);
@ -1498,15 +1504,22 @@ void WorldServer::updateTileEntityTiles(TileEntityPtr const& entity, bool removi
ServerTile* tile = m_tileArray->modifyTile(pos); ServerTile* tile = m_tileArray->modifyTile(pos);
if (tile) { if (tile) {
tile->foreground = EmptyMaterialId; bool updated = false;
tile->foregroundMod = NoModId; if (tile->foreground == materialSpace.material) {
tile->rootSource = {}; tile->foreground = EmptyMaterialId;
if (tile->updateCollision(materialDatabase->materialCollisionKind(tile->foreground))) { tile->foregroundMod = NoModId;
tile->rootSource = {};
updated = true;
}
if (tile->collision == materialDatabase->materialCollisionKind(materialSpace.material)
&& tile->updateCollision(materialSpace.prevCollision.value(CollisionKind::None))) {
m_liquidEngine->visitLocation(pos); m_liquidEngine->visitLocation(pos);
m_fallingBlocksAgent->visitLocation(pos); m_fallingBlocksAgent->visitLocation(pos);
dirtyCollision(RectI::withSize(pos, {1, 1})); dirtyCollision(RectI::withSize(pos, { 1, 1 }));
updated = true;
} }
queueTileUpdates(pos); if (updated)
queueTileUpdates(pos);
} }
} }
@ -1515,24 +1528,37 @@ void WorldServer::updateTileEntityTiles(TileEntityPtr const& entity, bool removi
} else { } else {
// add new material spaces and update the known material spaces entry // add new material spaces and update the known material spaces entry
List<MaterialSpace> passedSpaces;
for (auto const& materialSpace : newMaterialSpaces) { for (auto const& materialSpace : newMaterialSpaces) {
Vec2I pos = materialSpace.space + entity->tilePosition(); Vec2I pos = materialSpace.space + entity->tilePosition();
bool updated = false;
bool updatedCollision = false;
ServerTile* tile = m_tileArray->modifyTile(pos); ServerTile* tile = m_tileArray->modifyTile(pos);
if (tile) { if (tile && (tile->foreground == EmptyMaterialId || tile->foreground == materialSpace.material)) {
tile->foreground = materialSpace.material; tile->foreground = materialSpace.material;
tile->foregroundMod = NoModId; tile->foregroundMod = NoModId;
if (isRealMaterial(materialSpace.material)) if (isRealMaterial(materialSpace.material))
tile->rootSource = entity->tilePosition(); tile->rootSource = entity->tilePosition();
if (tile->updateCollision(materialDatabase->materialCollisionKind(tile->foreground))) { passedSpaces.emplaceAppend(materialSpace).prevCollision.emplace(tile->collision);
m_liquidEngine->visitLocation(pos); updatedCollision = tile->updateCollision(materialDatabase->materialCollisionKind(tile->foreground));
m_fallingBlocksAgent->visitLocation(pos); updated = true;
dirtyCollision(RectI::withSize(pos, {1, 1})); passedSpaces.emplaceAppend(materialSpace);
}
queueTileUpdates(pos);
} }
else if (tile && tile->collision < CollisionKind::Dynamic) {
passedSpaces.emplaceAppend(materialSpace).prevCollision.emplace(tile->collision);
updatedCollision = tile->updateCollision(materialDatabase->materialCollisionKind(materialSpace.material));
updated = true;
}
if (updatedCollision) {
m_liquidEngine->visitLocation(pos);
m_fallingBlocksAgent->visitLocation(pos);
dirtyCollision(RectI::withSize(pos, { 1, 1 }));
}
if (updated)
queueTileUpdates(pos);
} }
spaces.materials = move(newMaterialSpaces); spaces.materials = move(passedSpaces);
// add new roots and update known roots entry // add new roots and update known roots entry
for (auto const& rootPos : newRoots) { for (auto const& rootPos : newRoots) {

View File

@ -144,7 +144,8 @@ public:
EntityPtr findEntity(RectF const& boundBox, EntityFilter entityFilter) const override; EntityPtr findEntity(RectF const& boundBox, EntityFilter entityFilter) const override;
EntityPtr findEntityLine(Vec2F const& begin, Vec2F const& end, EntityFilter entityFilter) const override; EntityPtr findEntityLine(Vec2F const& begin, Vec2F const& end, EntityFilter entityFilter) const override;
EntityPtr findEntityAtTile(Vec2I const& pos, EntityFilterOf<TileEntity> entityFilter) const override; EntityPtr findEntityAtTile(Vec2I const& pos, EntityFilterOf<TileEntity> entityFilter) const override;
bool tileIsOccupied(Vec2I const& pos, TileLayer layer, bool includeEphemeral = false) const override; bool tileIsOccupied(Vec2I const& pos, TileLayer layer, bool includeEphemeral = false, bool checkCollision = false) const override;
CollisionKind tileCollisionKind(Vec2I const& pos) const override;
void forEachCollisionBlock(RectI const& region, function<void(CollisionBlock const&)> const& iterator) const override; void forEachCollisionBlock(RectI const& region, function<void(CollisionBlock const&)> const& iterator) const override;
bool isTileConnectable(Vec2I const& pos, TileLayer layer, bool tilesOnly = false) const override; bool isTileConnectable(Vec2I const& pos, TileLayer layer, bool tilesOnly = false) const override;
bool pointTileCollision(Vec2F const& point, CollisionSet const& collisionSet = DefaultCollisionSet) const override; bool pointTileCollision(Vec2F const& point, CollisionSet const& collisionSet = DefaultCollisionSet) const override;

View File

@ -4,6 +4,7 @@
#include "StarEntity.hpp" #include "StarEntity.hpp"
#include "StarTileDamage.hpp" #include "StarTileDamage.hpp"
#include "StarInteractiveEntity.hpp" #include "StarInteractiveEntity.hpp"
#include "StarCollisionBlock.hpp"
namespace Star { namespace Star {
@ -17,6 +18,7 @@ struct MaterialSpace {
Vec2I space; Vec2I space;
MaterialId material; MaterialId material;
Maybe<CollisionKind> prevCollision;
}; };
DataStream& operator<<(DataStream& ds, MaterialSpace const& materialSpace); DataStream& operator<<(DataStream& ds, MaterialSpace const& materialSpace);
@ -89,7 +91,9 @@ inline MaterialSpace::MaterialSpace(Vec2I space, MaterialId material)
: space(space), material(material) {} : space(space), material(material) {}
inline bool MaterialSpace::operator==(MaterialSpace const& rhs) const { inline bool MaterialSpace::operator==(MaterialSpace const& rhs) const {
return tie(space, material) == tie(rhs.space, rhs.material); return space == rhs.space
&& material == rhs.material
&& prevCollision == rhs.prevCollision;
} }
} }

View File

@ -76,7 +76,10 @@ public:
virtual EntityPtr findEntityAtTile(Vec2I const& pos, EntityFilterOf<TileEntity> entityFilter) const = 0; virtual EntityPtr findEntityAtTile(Vec2I const& pos, EntityFilterOf<TileEntity> entityFilter) const = 0;
// Is the given tile layer and position occupied by an entity or block? // Is the given tile layer and position occupied by an entity or block?
virtual bool tileIsOccupied(Vec2I const& pos, TileLayer layer, bool includeEphemeral = false) const = 0; virtual bool tileIsOccupied(Vec2I const& pos, TileLayer layer, bool includeEphemeral = false, bool checkCollision = false) const = 0;
// Returns the collision kind of a tile.
virtual CollisionKind tileCollisionKind(Vec2I const& pos) const = 0;
// Iterate over the collision block for each tile in the region. Collision // Iterate over the collision block for each tile in the region. Collision
// polys for tiles can extend to a maximum of 1 tile outside of the natural // polys for tiles can extend to a maximum of 1 tile outside of the natural