v1.4.4
This commit is contained in:
commit
9c94d113d3
10260 changed files with 1237388 additions and 0 deletions
515
source/game/StarWorldImpl.hpp
Normal file
515
source/game/StarWorldImpl.hpp
Normal file
|
@ -0,0 +1,515 @@
|
|||
#ifndef STAR_WORLD_IMPL_HPP
|
||||
#define STAR_WORLD_IMPL_HPP
|
||||
|
||||
#include "StarIterator.hpp"
|
||||
#include "StarEntityMap.hpp"
|
||||
#include "StarWorldTiles.hpp"
|
||||
#include "StarBlocksAlongLine.hpp"
|
||||
#include "StarBiome.hpp"
|
||||
#include "StarSky.hpp"
|
||||
#include "StarWorldTemplate.hpp"
|
||||
#include "StarLiquidsDatabase.hpp"
|
||||
#include "StarCellularLighting.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarMaterialDatabase.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
#include "StarTileModification.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
namespace WorldImpl {
|
||||
template <typename TileSectorArray>
|
||||
bool tileIsOccupied(shared_ptr<TileSectorArray> const& tileSectorArray, EntityMapPtr const& entityMap,
|
||||
Vec2I const& pos, TileLayer layer, bool includeEphemeral);
|
||||
|
||||
template <typename TileSectorArray>
|
||||
bool rectTileCollision(shared_ptr<TileSectorArray> const& tileSectorArray, RectI const& region, bool solidCollision);
|
||||
|
||||
template <typename TileSectorArray>
|
||||
bool lineTileCollision(WorldGeometry const& worldGeometry, shared_ptr<TileSectorArray> const& tileSectorArray, Vec2F const& begin, Vec2F const& end, CollisionSet const& collisionSet);
|
||||
|
||||
template <typename TileSectorArray>
|
||||
Maybe<pair<Vec2F, Vec2I>> lineTileCollisionPoint(WorldGeometry const& worldGeometry, shared_ptr<TileSectorArray> const& tileSectorArray, Vec2F const& begin, Vec2F const& end, CollisionSet const& collisionSet);
|
||||
|
||||
template <typename TileSectorArray>
|
||||
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);
|
||||
// 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);
|
||||
// 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 TileSectorArray>
|
||||
float windLevel(shared_ptr<TileSectorArray> const& tileSectorArray, Vec2F const& position, float weatherWindLevel);
|
||||
|
||||
template <typename TileSectorArray>
|
||||
float temperature(shared_ptr<TileSectorArray> const& tileSectorArray, WorldTemplateConstPtr const& worldTemplate,
|
||||
SkyConstPtr const& sky, Vec2F const& pos);
|
||||
|
||||
template <typename TileSectorArray>
|
||||
bool breathable(World const* world, shared_ptr<TileSectorArray> const& tileSectorArray, WorldTemplateConstPtr const& worldTemplate, Vec2F const& pos);
|
||||
|
||||
template <typename TileSectorArray>
|
||||
float lightLevel(shared_ptr<TileSectorArray> const& tileSectorArray, EntityMapPtr const& entityMap, WorldGeometry const& worldGeometry,
|
||||
WorldTemplateConstPtr const& worldTemplate, SkyConstPtr const& sky, CellularLightIntensityCalculator& lighting, Vec2F pos);
|
||||
|
||||
InteractiveEntityPtr getInteractiveInRange(WorldGeometry const& geometry, EntityMapPtr const& entityMap,
|
||||
Vec2F const& targetPosition, Vec2F const& sourcePosition, float maxRange);
|
||||
bool isTileEntityInRange(WorldGeometry const& geometry, EntityMapPtr const& entityMap,
|
||||
EntityId targetEntity, Vec2F const& sourcePosition, float maxRange);
|
||||
template <typename TileSectorArray>
|
||||
bool canReachEntity(WorldGeometry const& geometry, shared_ptr<TileSectorArray> const& tileSectorArray,
|
||||
EntityMapPtr const& entityMap, Vec2F const& sourcePosition, float maxRange, EntityId targetEntity, bool preferInteractive = true);
|
||||
|
||||
template <typename TileSectorArray>
|
||||
bool tileIsOccupied(shared_ptr<TileSectorArray> const& tileSectorArray, EntityMapPtr const& entityMap,
|
||||
Vec2I const& pos, TileLayer layer, bool includeEphemeral) {
|
||||
if (layer == TileLayer::Foreground)
|
||||
return tileSectorArray->tile(pos).foreground != EmptyMaterialId || entityMap->tileIsOccupied(pos, includeEphemeral);
|
||||
else
|
||||
return tileSectorArray->tile(pos).background != EmptyMaterialId;
|
||||
}
|
||||
|
||||
template <typename TileSectorArray>
|
||||
bool rectTileCollision(shared_ptr<TileSectorArray> const& tileSectorArray, RectI const& region, CollisionSet const& collisionSet) {
|
||||
return tileSectorArray->tileSatisfies(region, [&collisionSet](Vec2I const&, typename TileSectorArray::Tile const& tile) {
|
||||
return isColliding(tile.collision, collisionSet);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename TileSectorArray>
|
||||
bool lineTileCollision(WorldGeometry const& worldGeometry, shared_ptr<TileSectorArray> const& tileSectorArray,
|
||||
Vec2F const& begin, Vec2F const& end, CollisionSet const& collisionSet) {
|
||||
return !forBlocksAlongLine<float>(begin, worldGeometry.diff(end, begin), [=](int x, int y) -> bool {
|
||||
return !tileSectorArray->tile({x, y}).isColliding(collisionSet);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename TileSectorArray>
|
||||
Maybe<pair<Vec2F, Vec2I>> lineTileCollisionPoint(WorldGeometry const& worldGeometry, shared_ptr<TileSectorArray> const& tileSectorArray,
|
||||
Vec2F const& begin, Vec2F const& end, CollisionSet const& collisionSet) {
|
||||
Maybe<Vec2I> collidingBlock;
|
||||
auto clear = forBlocksAlongLine<float>(begin, worldGeometry.diff(end, begin), [=, &collidingBlock](int x, int y) -> bool {
|
||||
if (tileSectorArray->tile({x, y}).isColliding(collisionSet)) {
|
||||
collidingBlock = Vec2I(x, y);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (clear)
|
||||
return {}; // no colliding blocks along the line
|
||||
|
||||
Vec2F direction = worldGeometry.diff(end, begin).normalized();
|
||||
Vec2F blockPosition = Vec2F(*collidingBlock);
|
||||
|
||||
// find the position of intersecting sides
|
||||
if (direction[0] < 0) blockPosition[0] = blockPosition[0] + 1;
|
||||
if (direction[1] < 0) blockPosition[1] = blockPosition[1] + 1;
|
||||
Vec2F blockDistance = worldGeometry.diff(blockPosition, begin);
|
||||
|
||||
// exclude edges which are in the opposite direction of the line
|
||||
if (direction[0] * blockDistance[0] < 0) blockDistance[0] = 0.0f;
|
||||
if (direction[1] * blockDistance[1] < 0) blockDistance[1] = 0.0f;
|
||||
|
||||
// line length per block in each axis, don't divide by 0
|
||||
float deltaDistX = direction[0] == 0 ? 0.0f : 1.0f / abs(direction[0]);
|
||||
float deltaDistY = direction[1] == 0 ? 0.0f : 1.0f / abs(direction[1]);
|
||||
|
||||
// distance to each edge of the intersect
|
||||
Vec2F intersectDist = Vec2F(abs(blockDistance[0]) * deltaDistX, abs(blockDistance[1]) * deltaDistY);
|
||||
|
||||
Vec2I normal = Vec2I(intersectDist[0] > intersectDist[1] ? 1 : 0, intersectDist[1] > intersectDist[0] ? 1 : 0);
|
||||
normal[0] *= copysign(1.0f, direction[0]);
|
||||
normal[1] *= copysign(1.0f, direction[1]);
|
||||
Vec2F position = begin + (direction * max(intersectDist[0], intersectDist[1]));
|
||||
// HACK: Keeps the end position from being right on a tile border, making line check
|
||||
// *from* this end position from colliding with the first tile
|
||||
if (intersectDist[0] > intersectDist[1])
|
||||
position[0] = blockPosition[0] + (normal[0] * -0.0001f);
|
||||
else
|
||||
position[1] = blockPosition[1] + (normal[1] * -0.0001f);
|
||||
return pair<Vec2F, Vec2I>(position, normal);
|
||||
}
|
||||
|
||||
template <typename TileSectorArray>
|
||||
inline LiquidLevel liquidLevel(shared_ptr<TileSectorArray> const& tileSectorArray, RectF const& region) {
|
||||
if (region.isEmpty())
|
||||
return LiquidLevel();
|
||||
|
||||
RectI sampleRect = RectI::integral(region);
|
||||
// This is not entirely accurate, even though all liquid types in the
|
||||
// region count towards the grand total liquid percentage, only the most
|
||||
// common liquid is reported.
|
||||
float totalSpace = 0;
|
||||
Map<LiquidId, float> totals;
|
||||
tileSectorArray->tileEach(sampleRect, [®ion, &totalSpace, &totals](Vec2I const& pos, typename TileSectorArray::Tile const& tile) {
|
||||
float blockIncidence = RectF(pos[0], pos[1], pos[0] + 1, pos[1] + 1).overlap(region).volume();
|
||||
totalSpace += blockIncidence;
|
||||
auto liquidLevel = tile.liquid;
|
||||
if (liquidLevel.liquid != EmptyLiquidId)
|
||||
totals[liquidLevel.liquid] += min(1.0f, liquidLevel.level) * blockIncidence;
|
||||
});
|
||||
|
||||
float totalLiquidLevel = 0.0f;
|
||||
float maximumLiquidLevel = 0.0f;
|
||||
LiquidId maximumLiquidId = EmptyLiquidId;
|
||||
for (auto const& p : totals) {
|
||||
totalLiquidLevel += p.second;
|
||||
if (p.second > maximumLiquidLevel) {
|
||||
maximumLiquidLevel = p.second;
|
||||
maximumLiquidId = p.first;
|
||||
}
|
||||
}
|
||||
return LiquidLevel(maximumLiquidId, totalLiquidLevel / totalSpace);
|
||||
}
|
||||
|
||||
template <typename TileSectorArray>
|
||||
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) {
|
||||
List<Vec2I> res;
|
||||
forBlocksAlongLine<float>(begin, worldGeometry.diff(end, begin), [&](int x, int y) {
|
||||
if (res.size() >= maxSize)
|
||||
return false;
|
||||
if (tileSectorArray->tile({x, y}).isColliding(collisionSet))
|
||||
res.append({x, y});
|
||||
return true;
|
||||
});
|
||||
|
||||
if (includeEdges)
|
||||
return res;
|
||||
|
||||
SMutableIterator<List<Vec2I>> tileIt{res};
|
||||
while (tileIt.hasNext()) {
|
||||
auto tileRect = RectF::withSize(Vec2F(worldGeometry.xwrap(tileIt.next())), Vec2F::filled(1.0f));
|
||||
Line2F line{worldGeometry.xwrap(begin), worldGeometry.xwrap(end)};
|
||||
auto lineSet = worldGeometry.splitLine(line);
|
||||
if (any(lineSet, [&](Line2F const& line) { return tileRect.edgeIntersection(line).glances; }))
|
||||
tileIt.remove();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename TileSectorArray>
|
||||
bool canPlaceMaterial(shared_ptr<TileSectorArray> const& tileSectorArray, EntityMapPtr const& entityMap,
|
||||
Vec2I const& pos, TileLayer layer, MaterialId material, bool allowEntityOverlap) {
|
||||
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)
|
||||
return true;
|
||||
else if (foreground)
|
||||
return isConnectableMaterial(tile.foreground);
|
||||
else
|
||||
return isConnectableMaterial(tile.background);
|
||||
});
|
||||
};
|
||||
|
||||
if (!materialDatabase->canPlaceInLayer(material, layer))
|
||||
return false;
|
||||
|
||||
if (layer == TileLayer::Background) {
|
||||
if (tileSectorArray->tile(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))
|
||||
return false;
|
||||
} else {
|
||||
if (tileSectorArray->tile(pos).foreground != EmptyMaterialId)
|
||||
return false;
|
||||
|
||||
if (entityMap->tileIsOccupied(pos))
|
||||
return false;
|
||||
|
||||
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))
|
||||
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) {
|
||||
auto materialDatabase = Root::singleton().materialDatabase();
|
||||
|
||||
if (!isRealMaterial(material))
|
||||
return false;
|
||||
|
||||
if (!materialDatabase->canPlaceInLayer(material, layer))
|
||||
return false;
|
||||
|
||||
if (layer == TileLayer::Background) {
|
||||
if (tileSectorArray->tile(pos).background != EmptyMaterialId)
|
||||
return false;
|
||||
} else {
|
||||
if (tileSectorArray->tile(pos).foreground != EmptyMaterialId)
|
||||
return false;
|
||||
|
||||
if (entityMap->tileIsOccupied(pos))
|
||||
return false;
|
||||
|
||||
if (!allowEntityOverlap && entityMap->spaceIsOccupied(RectF::withSize(Vec2F(pos), Vec2F(1, 1))))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename TileSectorArray>
|
||||
bool canPlaceMaterialColorVariant(shared_ptr<TileSectorArray> const& tileSectorArray,
|
||||
Vec2I const& pos, TileLayer layer, MaterialColorVariant color) {
|
||||
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;
|
||||
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) {
|
||||
if (!isRealMod(mod))
|
||||
return false;
|
||||
|
||||
auto materialDatabase = Root::singleton().materialDatabase();
|
||||
auto mat = tileSectorArray->tile(pos).material(layer);
|
||||
auto existingMod = tileSectorArray->tile(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) {
|
||||
TileModificationList success;
|
||||
TileModificationList unknown;
|
||||
TileModificationList failures;
|
||||
for (auto const& pair : modificationList) {
|
||||
Vec2I pos;
|
||||
TileModification modification;
|
||||
std::tie(pos, modification) = pair;
|
||||
|
||||
bool good = false;
|
||||
bool perhaps = false;
|
||||
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);
|
||||
if (perhaps)
|
||||
good = WorldImpl::canPlaceMaterial(tileSectorArray, entityMap, pos, placeMaterial->layer, placeMaterial->material, allowEntityOverlap);
|
||||
} else if (auto placeMod = modification.ptr<PlaceMod>()) {
|
||||
good = WorldImpl::canPlaceMod(tileSectorArray, pos, placeMod->layer, placeMod->mod);
|
||||
} else if (auto placeMaterialColor = modification.ptr<PlaceMaterialColor>()) {
|
||||
good = WorldImpl::canPlaceMaterialColorVariant(tileSectorArray, pos, placeMaterialColor->layer, placeMaterialColor->color);
|
||||
} else if (modification.is<PlaceLiquid>()) {
|
||||
good = tileSectorArray->tile(pos).collision == CollisionKind::None;
|
||||
} else {
|
||||
good = false;
|
||||
}
|
||||
|
||||
if (good)
|
||||
success.append(pair);
|
||||
else if (perhaps)
|
||||
unknown.append(pair);
|
||||
else
|
||||
failures.append(pair);
|
||||
}
|
||||
|
||||
if (!success.empty())
|
||||
success.appendAll(unknown);
|
||||
else
|
||||
failures.appendAll(unknown);
|
||||
|
||||
return {success, failures};
|
||||
}
|
||||
|
||||
template <typename TileSectorArray>
|
||||
float windLevel(shared_ptr<TileSectorArray> const& tileSectorArray, Vec2F const& position, float weatherWindLevel) {
|
||||
auto const& tile = tileSectorArray->tile(Vec2I::floor(position));
|
||||
if (tile.material(TileLayer::Background) != EmptyMaterialId)
|
||||
return 0.0f;
|
||||
if (tile.material(TileLayer::Foreground) != EmptyMaterialId)
|
||||
return 0.0f;
|
||||
return weatherWindLevel;
|
||||
}
|
||||
|
||||
template <typename TileSectorArray>
|
||||
bool breathable(World const* world, shared_ptr<TileSectorArray> const& tileSectorArray, HashMap<DungeonId, bool> const& breathableMap,
|
||||
WorldTemplateConstPtr const& worldTemplate, Vec2F const& pos) {
|
||||
Vec2I ipos = Vec2I::floor(pos);
|
||||
float remainder = pos[1] - ipos[1];
|
||||
auto materialDatabase = Root::singleton().materialDatabase();
|
||||
auto liquidsDatabase = Root::singleton().liquidsDatabase();
|
||||
|
||||
auto tile = tileSectorArray->tile(ipos);
|
||||
bool environmentBreathable = breathableMap.maybe(tile.dungeonId).value(worldTemplate->breathable(ipos[0], ipos[1]));
|
||||
bool liquidBreathable = remainder >= tile.liquid.level;
|
||||
bool foregroundBreathable = tile.collision != CollisionKind::Block || !world->pointCollision(pos);
|
||||
|
||||
return environmentBreathable && foregroundBreathable && liquidBreathable;
|
||||
}
|
||||
|
||||
template <typename TileSectorArray>
|
||||
float lightLevel(shared_ptr<TileSectorArray> const& tileSectorArray, EntityMapPtr const& entityMap, WorldGeometry const& worldGeometry,
|
||||
WorldTemplateConstPtr const& worldTemplate, SkyConstPtr const& sky, CellularLightIntensityCalculator& lighting, Vec2F pos) {
|
||||
if (pos[1] < 0 || pos[1] >= worldGeometry.height())
|
||||
return 0;
|
||||
|
||||
// tileEach can't handle rects that are WAY out of range.
|
||||
pos = worldGeometry.xwrap(pos);
|
||||
|
||||
Vec3F environmentLight = sky->environmentLight().toRgbF();
|
||||
float undergroundLevel = worldTemplate->undergroundLevel();
|
||||
auto materialDatabase = Root::singleton().materialDatabase();
|
||||
auto liquidsDatabase = Root::singleton().liquidsDatabase();
|
||||
|
||||
lighting.begin(pos);
|
||||
|
||||
// Each column in tileEvalColumns is guaranteed to be no larger than the
|
||||
// sector size.
|
||||
CellularLightIntensityCalculator::Cell lightingCellColumn[WorldSectorSize];
|
||||
tileSectorArray->tileEvalColumns(lighting.calculationRegion(), [&](Vec2I const& pos, typename TileSectorArray::Tile const* column, size_t ySize) {
|
||||
for (size_t y = 0; y < ySize; ++y) {
|
||||
auto& tile = column[y];
|
||||
auto& cell = lightingCellColumn[y];
|
||||
|
||||
bool backgroundTransparent = materialDatabase->backgroundLightTransparent(tile.background);
|
||||
bool foregroundTransparent = materialDatabase->foregroundLightTransparent(tile.foreground)
|
||||
&& tile.collision != CollisionKind::Dynamic;
|
||||
|
||||
cell = {materialDatabase->radiantLight(tile.foreground, tile.foregroundMod).sum() / 3.0f, !foregroundTransparent};
|
||||
cell.light += liquidsDatabase->radiantLight(tile.liquid).sum() / 3.0f;
|
||||
if (foregroundTransparent) {
|
||||
cell.light += materialDatabase->radiantLight(tile.background, tile.backgroundMod).sum() / 3.0f;
|
||||
if (backgroundTransparent && pos[1] > undergroundLevel)
|
||||
cell.light += environmentLight.sum() / 3.0f;
|
||||
}
|
||||
}
|
||||
lighting.setCellColumn(pos, lightingCellColumn, ySize);
|
||||
});
|
||||
|
||||
for (auto const& entity : entityMap->entityQuery(RectF(lighting.calculationRegion()))) {
|
||||
for (auto const& light : entity->lightSources()) {
|
||||
Vec2F position = worldGeometry.nearestTo(Vec2F(lighting.calculationRegion().min()), light.position);
|
||||
if (light.pointLight)
|
||||
lighting.addPointLight(position, Color::v3bToFloat(light.color).sum() / 3.0f, light.pointBeam, light.beamAngle, light.beamAmbience);
|
||||
else
|
||||
lighting.addSpreadLight(position, Color::v3bToFloat(light.color).sum() / 3.0f);
|
||||
}
|
||||
}
|
||||
|
||||
return lighting.calculate();
|
||||
}
|
||||
|
||||
inline InteractiveEntityPtr getInteractiveInRange(WorldGeometry const& geometry, EntityMapPtr const& entityMap,
|
||||
Vec2F const& targetPosition, Vec2F const& sourcePosition, float maxRange) {
|
||||
if (auto entity = entityMap->interactiveEntityNear(targetPosition, maxRange)) {
|
||||
if (auto tileEntity = as<TileEntity>(entity)) {
|
||||
if (isTileEntityInRange(geometry, entityMap, tileEntity->entityId(), sourcePosition, maxRange))
|
||||
return entity;
|
||||
} else {
|
||||
if (geometry.diffToNearestCoordInBox(entity->interactiveBoundBox().translated(entity->position()), sourcePosition) .magnitude() <= maxRange)
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
inline bool isTileEntityInRange(WorldGeometry const& geometry, EntityMapPtr const& entityMap, EntityId targetEntity,
|
||||
Vec2F const& sourcePosition, float maxRange) {
|
||||
if (auto entity = entityMap->get<TileEntity>(targetEntity)) {
|
||||
// If any of the entity spaces is within interaction range, then assume the
|
||||
// whole entity is in interaction range
|
||||
for (auto space : entity->spaces()) {
|
||||
if (geometry.diff(entity->position() + centerOfTile(space), sourcePosition).magnitude() <= maxRange)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename TileSectorArray>
|
||||
bool canReachEntity(WorldGeometry const& geometry, shared_ptr<TileSectorArray> const& tileSectorArray,
|
||||
EntityMapPtr const& entityMap, Vec2F const& sourcePosition, float maxRange, EntityId targetEntity, bool preferInteractive) {
|
||||
auto entity = entityMap->entity(targetEntity);
|
||||
if (!entity)
|
||||
return false;
|
||||
|
||||
// exclude the final tile from the collision check since many targets will be collidable tile entities, e.g. doors
|
||||
auto const canReachTile = [&](Vec2F const& end) -> bool {
|
||||
Vec2I const& endTile = Vec2I::floor(end);
|
||||
return forBlocksAlongLine<float>(sourcePosition, geometry.diff(end, sourcePosition), [=](int x, int y) -> bool {
|
||||
Vec2I diff = geometry.diff(endTile, Vec2I(x, y));
|
||||
if (diff[0] == 0 && diff[1] == 0)
|
||||
return true;
|
||||
return !tileSectorArray->tile({x, y}).isColliding(DefaultCollisionSet);
|
||||
});
|
||||
};
|
||||
|
||||
if (auto tileEntity = as<TileEntity>(entity)) {
|
||||
for (auto space : preferInteractive ? tileEntity->interactiveSpaces() : tileEntity->spaces()) {
|
||||
auto spacePosition = entity->position() + centerOfTile(space);
|
||||
if (geometry.diff(spacePosition, sourcePosition).magnitude() <= maxRange) {
|
||||
if (canReachTile(spacePosition))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (preferInteractive && is<InteractiveEntity>(entity)) {
|
||||
auto interactiveEntity = as<InteractiveEntity>(entity);
|
||||
auto entityBounds = interactiveEntity->interactiveBoundBox().translated(entity->position());
|
||||
if (geometry.rectContains(entityBounds, sourcePosition))
|
||||
return true;
|
||||
if (!geometry.rectIntersectsCircle(entityBounds, sourcePosition, maxRange))
|
||||
return false;
|
||||
|
||||
return
|
||||
!lineTileCollision(geometry, tileSectorArray, sourcePosition, entityBounds.nearestCoordTo(sourcePosition), DefaultCollisionSet) ||
|
||||
!lineTileCollision(geometry, tileSectorArray, sourcePosition, Vec2F(entityBounds.xMin(), entityBounds.yMin()), DefaultCollisionSet) ||
|
||||
!lineTileCollision(geometry, tileSectorArray, sourcePosition, Vec2F(entityBounds.xMin(), entityBounds.yMax()), DefaultCollisionSet) ||
|
||||
!lineTileCollision(geometry, tileSectorArray, sourcePosition, Vec2F(entityBounds.xMax(), entityBounds.yMax()), DefaultCollisionSet) ||
|
||||
!lineTileCollision(geometry, tileSectorArray, sourcePosition, Vec2F(entityBounds.xMax(), entityBounds.yMin()), DefaultCollisionSet);
|
||||
} else {
|
||||
if (geometry.diff(entity->position(), sourcePosition).magnitude() <= maxRange)
|
||||
return !lineTileCollision(geometry, tileSectorArray, sourcePosition, entity->position(), DefaultCollisionSet);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Add table
Add a link
Reference in a new issue