From a7ae03427805cdf0886ca9eb40add54cf970fc4f Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Mon, 21 Aug 2023 00:59:02 +1000 Subject: [PATCH] Objects can now be placed under tiles that have non-block collision --- source/game/StarObjectDatabase.cpp | 4 +- source/game/StarWorldClient.cpp | 10 +++- source/game/StarWorldClient.hpp | 3 +- source/game/StarWorldImpl.hpp | 55 +++++++++++++-------- source/game/StarWorldServer.cpp | 60 ++++++++++++++++------- source/game/StarWorldServer.hpp | 3 +- source/game/interfaces/StarTileEntity.hpp | 6 ++- source/game/interfaces/StarWorld.hpp | 5 +- 8 files changed, 100 insertions(+), 46 deletions(-) diff --git a/source/game/StarObjectDatabase.cpp b/source/game/StarObjectDatabase.cpp index 4c9a968..d886be8 100644 --- a/source/game/StarObjectDatabase.cpp +++ b/source/game/StarObjectDatabase.cpp @@ -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; diff --git a/source/game/StarWorldClient.cpp b/source/game/StarWorldClient.cpp index 6f4c68d..547c21d 100644 --- a/source/game/StarWorldClient.cpp +++ b/source/game/StarWorldClient.cpp @@ -238,10 +238,16 @@ EntityPtr WorldClient::findEntityAtTile(Vec2I const& pos, EntityFilterOffindEntityAtTile(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 const& iterator) const { diff --git a/source/game/StarWorldClient.hpp b/source/game/StarWorldClient.hpp index 7a2a834..7298863 100644 --- a/source/game/StarWorldClient.hpp +++ b/source/game/StarWorldClient.hpp @@ -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 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 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; diff --git a/source/game/StarWorldImpl.hpp b/source/game/StarWorldImpl.hpp index 91e03fb..b72b095 100644 --- a/source/game/StarWorldImpl.hpp +++ b/source/game/StarWorldImpl.hpp @@ -21,7 +21,11 @@ namespace Star { namespace WorldImpl { template bool tileIsOccupied(shared_ptr const& tileSectorArray, EntityMapPtr const& entityMap, - Vec2I const& pos, TileLayer layer, bool includeEphemeral); + Vec2I const& pos, TileLayer layer, bool includeEphemeral, bool checkCollision = false); + + template + CollisionKind tileCollisionKind(shared_ptr const& tileSectorArray, EntityMapPtr const& entityMap, + Vec2I const& pos); template bool rectTileCollision(shared_ptr const& tileSectorArray, RectI const& region, bool solidCollision); @@ -38,13 +42,13 @@ namespace WorldImpl { template 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 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 bool canPlaceMaterialColorVariant(Vec2I const& pos, TileLayer layer, MaterialColorVariant color, GetTileFunction& getTile); template @@ -80,11 +84,18 @@ namespace WorldImpl { template bool tileIsOccupied(shared_ptr 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 + CollisionKind tileCollisionKind(shared_ptr const& tileSectorArray, EntityMapPtr const& entityMap, + Vec2I const& pos) { + return tileSectorArray->tile(pos).collision; } template @@ -210,7 +221,7 @@ namespace WorldImpl { template 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 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()) { - 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()) { good = WorldImpl::canPlaceMod(pos, placeMod->layer, placeMod->mod, getTile); } else if (auto placeMaterialColor = modification.ptr()) { diff --git a/source/game/StarWorldServer.cpp b/source/game/StarWorldServer.cpp index 6579c91..26ce363 100644 --- a/source/game/StarWorldServer.cpp +++ b/source/game/StarWorldServer.cpp @@ -756,10 +756,15 @@ EntityPtr WorldServer::findEntityAtTile(Vec2I const& pos, EntityFilterOffindEntityAtTile(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 const& iterator) const { const_cast(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()) { - 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 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) { diff --git a/source/game/StarWorldServer.hpp b/source/game/StarWorldServer.hpp index 7e14efa..24436b6 100644 --- a/source/game/StarWorldServer.hpp +++ b/source/game/StarWorldServer.hpp @@ -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 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 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; diff --git a/source/game/interfaces/StarTileEntity.hpp b/source/game/interfaces/StarTileEntity.hpp index 86c01b6..c9037e5 100644 --- a/source/game/interfaces/StarTileEntity.hpp +++ b/source/game/interfaces/StarTileEntity.hpp @@ -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 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; } } diff --git a/source/game/interfaces/StarWorld.hpp b/source/game/interfaces/StarWorld.hpp index f2af197..9c97be5 100644 --- a/source/game/interfaces/StarWorld.hpp +++ b/source/game/interfaces/StarWorld.hpp @@ -76,7 +76,10 @@ public: virtual EntityPtr findEntityAtTile(Vec2I const& pos, EntityFilterOf 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