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)
return false;
for (auto space : spaces) {
for (Vec2I space : spaces) {
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 true;

View File

@ -238,10 +238,16 @@ EntityPtr WorldClient::findEntityAtTile(Vec2I const& pos, EntityFilterOf<TileEnt
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())
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 {

View File

@ -63,7 +63,8 @@ public:
EntityPtr findEntity(RectF const& boundBox, 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;
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;
bool isTileConnectable(Vec2I const& pos, TileLayer layer, bool tilesOnly = false) const override;
bool pointTileCollision(Vec2F const& point, CollisionSet const& collisionSet = DefaultCollisionSet) const override;

View File

@ -21,7 +21,11 @@ namespace Star {
namespace WorldImpl {
template <typename TileSectorArray>
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>
bool rectTileCollision(shared_ptr<TileSectorArray> const& tileSectorArray, RectI const& region, bool solidCollision);
@ -38,13 +42,13 @@ namespace WorldImpl {
template <typename GetTileFunction>
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
// tiles can be placed
// that connect to it
template <typename GetTileFunction>
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>
bool canPlaceMaterialColorVariant(Vec2I const& pos, TileLayer layer, MaterialColorVariant color, GetTileFunction& getTile);
template <typename GetTileFunction>
@ -80,11 +84,18 @@ namespace WorldImpl {
template <typename TileSectorArray>
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)
return tileSectorArray->tile(pos).foreground != EmptyMaterialId || entityMap->tileIsOccupied(pos, includeEphemeral);
return (checkCollision ? tile.collision >= CollisionKind::Dynamic : tile.foreground != EmptyMaterialId) || entityMap->tileIsOccupied(pos, includeEphemeral);
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>
@ -210,7 +221,7 @@ namespace WorldImpl {
template <typename GetTileFunction>
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();
if (!isRealMaterial(material))
@ -239,26 +250,26 @@ namespace WorldImpl {
if (!materialDatabase->canPlaceInLayer(material, layer))
return false;
auto& tile = getTile(pos);
if (layer == TileLayer::Background) {
if (getTile(pos).background != EmptyMaterialId)
if (tile.background != EmptyMaterialId && tile.background != ObjectPlatformMaterialId)
return false;
// Can attach background blocks to other background blocks, *or* the
// foreground block in front of it.
if (!isAdjacentToConnectable(pos, 1, false)
&& !isConnectableMaterial(getTile({pos[0], pos[1]}).foreground))
if (!isAdjacentToConnectable(pos, 1, false) && !isConnectableMaterial(tile.foreground))
return false;
} else {
if (getTile(pos).foreground != EmptyMaterialId)
if (tile.foreground != EmptyMaterialId && tile.foreground != ObjectPlatformMaterialId)
return false;
if (entityMap->tileIsOccupied(pos))
if (!allowTileOverlap && entityMap->tileIsOccupied(pos))
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;
if (!isAdjacentToConnectable(pos, 1, true) && !isConnectableMaterial(getTile({pos[0], pos[1]}).background))
if (!isAdjacentToConnectable(pos, 1, true) && !isConnectableMaterial(tile.background))
return false;
}
@ -267,7 +278,7 @@ namespace WorldImpl {
template <typename GetTileFunction>
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();
if (!isRealMaterial(material))
@ -276,17 +287,18 @@ namespace WorldImpl {
if (!materialDatabase->canPlaceInLayer(material, layer))
return false;
auto& tile = getTile(pos);
if (layer == TileLayer::Background) {
if (getTile(pos).background != EmptyMaterialId)
if (tile.background != EmptyMaterialId && tile.background != ObjectPlatformMaterialId)
return false;
} else {
if (getTile(pos).foreground != EmptyMaterialId)
if (tile.foreground != EmptyMaterialId && tile.foreground != ObjectPlatformMaterialId)
return false;
if (entityMap->tileIsOccupied(pos))
if (!allowTileOverlap && entityMap->tileIsOccupied(pos))
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;
}
@ -321,9 +333,10 @@ namespace WorldImpl {
bool perhaps = false;
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)
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>()) {
good = WorldImpl::canPlaceMod(pos, placeMod->layer, placeMod->mod, getTile);
} 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);
}
bool WorldServer::tileIsOccupied(Vec2I const& pos, TileLayer layer, bool includeEphemeral) const {
return WorldImpl::tileIsOccupied(m_tileArray, m_entityMap, pos, layer, includeEphemeral);
bool WorldServer::tileIsOccupied(Vec2I const& pos, TileLayer layer, bool includeEphemeral, bool checkCollision) const {
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 {
const_cast<WorldServer*>(this)->freshenCollision(region);
m_tileArray->tileEach(region, [iterator](Vec2I const& pos, ServerTile const& tile) {
@ -1348,7 +1353,8 @@ TileModificationList WorldServer::doApplyTileModifications(TileModificationList
continue;
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;
ServerTile* tile = m_tileArray->modifyTile(pos);
@ -1498,15 +1504,22 @@ void WorldServer::updateTileEntityTiles(TileEntityPtr const& entity, bool removi
ServerTile* tile = m_tileArray->modifyTile(pos);
if (tile) {
tile->foreground = EmptyMaterialId;
tile->foregroundMod = NoModId;
tile->rootSource = {};
if (tile->updateCollision(materialDatabase->materialCollisionKind(tile->foreground))) {
bool updated = false;
if (tile->foreground == materialSpace.material) {
tile->foreground = EmptyMaterialId;
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_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 {
// add new material spaces and update the known material spaces entry
List<MaterialSpace> passedSpaces;
for (auto const& materialSpace : newMaterialSpaces) {
Vec2I pos = materialSpace.space + entity->tilePosition();
bool updated = false;
bool updatedCollision = false;
ServerTile* tile = m_tileArray->modifyTile(pos);
if (tile) {
if (tile && (tile->foreground == EmptyMaterialId || tile->foreground == materialSpace.material)) {
tile->foreground = materialSpace.material;
tile->foregroundMod = NoModId;
if (isRealMaterial(materialSpace.material))
tile->rootSource = entity->tilePosition();
if (tile->updateCollision(materialDatabase->materialCollisionKind(tile->foreground))) {
m_liquidEngine->visitLocation(pos);
m_fallingBlocksAgent->visitLocation(pos);
dirtyCollision(RectI::withSize(pos, {1, 1}));
}
queueTileUpdates(pos);
passedSpaces.emplaceAppend(materialSpace).prevCollision.emplace(tile->collision);
updatedCollision = tile->updateCollision(materialDatabase->materialCollisionKind(tile->foreground));
updated = true;
passedSpaces.emplaceAppend(materialSpace);
}
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
for (auto const& rootPos : newRoots) {

View File

@ -144,7 +144,8 @@ public:
EntityPtr findEntity(RectF const& boundBox, 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;
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;
bool isTileConnectable(Vec2I const& pos, TileLayer layer, bool tilesOnly = false) const override;
bool pointTileCollision(Vec2F const& point, CollisionSet const& collisionSet = DefaultCollisionSet) const override;

View File

@ -4,6 +4,7 @@
#include "StarEntity.hpp"
#include "StarTileDamage.hpp"
#include "StarInteractiveEntity.hpp"
#include "StarCollisionBlock.hpp"
namespace Star {
@ -17,6 +18,7 @@ struct MaterialSpace {
Vec2I space;
MaterialId material;
Maybe<CollisionKind> prevCollision;
};
DataStream& operator<<(DataStream& ds, MaterialSpace const& materialSpace);
@ -89,7 +91,9 @@ inline MaterialSpace::MaterialSpace(Vec2I space, MaterialId material)
: space(space), material(material) {}
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;
// 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
// polys for tiles can extend to a maximum of 1 tile outside of the natural