scummvm/engines/saga2/terrain.cpp
2022-10-29 23:45:58 +02:00

903 lines
26 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* aint32 with this program; if not, write to the Free Software
*
*
* Based on the original sources
* Faery Tale II -- The Halls of the Dead
* (c) 1993-1996 The Wyrmkeep Entertainment Co.
*/
#include "saga2/saga2.h"
#include "saga2/idtypes.h"
#include "saga2/tile.h"
#include "saga2/tileline.h"
#include "saga2/actor.h"
namespace Saga2 {
extern WorldMapData *mapList;
static int16 prevMapNum;
static StaticTilePoint prevCoords = {(int16)minint16, (int16)minint16, (int16)minint16};
static MetaTilePtr prevMeta = nullptr;
/* ===================================================================== *
Terrain damage info
* ===================================================================== */
void drown(GameObject *obj) {
if (isActor(obj)) {
Actor *a = (Actor *) obj;
if (!a->hasEffect(kActorWaterBreathe)) {
if (g_vm->_rnd->getRandomNumber(kDrowningDamageOddsYes + kDrowningDamageOddsNo - 1) > kDrowningDamageOddsNo - 1) {
a->acceptDamage(a->thisID(), kDrowningDamagePerFrame);
}
}
}
}
void lavaDamage(GameObject *obj) {
if (isActor(obj)) {
Actor *a = (Actor *) obj;
if (a->resists(kResistHeat))
return;
}
if (g_vm->_rnd->getRandomNumber(kHeatDamageOddsYes + kHeatDamageOddsNo - 1) > kHeatDamageOddsNo - 1) {
obj->acceptDamage(obj->thisID(), kHeatDamagePerFrame, kDamageHeat, kHeatDamageDicePerFrame, 6);
}
}
void coldDamage(GameObject *obj) {
if (isActor(obj)) {
Actor *a = (Actor *) obj;
if (a->resists(kResistCold))
return;
}
if (g_vm->_rnd->getRandomNumber(kColdDamageOddsYes + kColdDamageOddsNo - 1) > kColdDamageOddsNo - 1) {
obj->acceptDamage(obj->thisID(), kColdDamagePerFrame, kDamageCold, kColdDamageDicePerFrame, 6);
}
}
void terrainDamageSlash(GameObject *obj) {
if (g_vm->_rnd->getRandomNumber(kTerrainDamageOddsYes + kTerrainDamageOddsNo - 1) > kTerrainDamageOddsNo - 1) {
obj->acceptDamage(obj->thisID(), kTerrainDamagePerFrame, kDamageSlash, kTerrainDamageDicePerFrame, 6);
}
}
void terrainDamageBash(GameObject *obj) {
if (g_vm->_rnd->getRandomNumber(kTerrainDamageOddsYes + kTerrainDamageOddsNo - 1) > kTerrainDamageOddsNo - 1) {
obj->acceptDamage(obj->thisID(), kTerrainDamagePerFrame, kDamageImpact, kTerrainDamageDicePerFrame, 6);
}
}
void fallingDamage(GameObject *obj, int16 speed) {
if (isActor(obj)) {
Actor *a = (Actor *) obj;
if (!a->hasEffect(kActorSlowFall)) {
a->acceptDamage(a->thisID(), (MAX(0, speed - 16)*kFallingDamageMult) / kFallingDamageDiv);
}
}
}
/* ===================================================================== *
Function to get the terrain bits for a single UV location (with mask)
* ===================================================================== */
uint32 tileTerrain(
int16 mapNum,
const TilePoint &pt,
int16 mask,
int16 minZ,
int16 maxZ) {
WorldMapData *map = &mapList[mapNum];
TilePoint metaCoords = pt >> kPlatShift,
origin = metaCoords << kPlatShift,
coords = pt - origin;
MetaTilePtr metaPtr;
uint32 terrain = 0;
// A simple method for avoiding looking up the metatile again.
/* if ( prevMeta
&& prevMapNum == mapNum
&& prevCoords == metaCoords )
metaPtr = prevMeta;
else
*/
{
// Look up the metatile on the map.
metaPtr = prevMeta = map->lookupMeta(metaCoords);
prevMapNum = mapNum;
prevCoords.set(metaCoords.u, metaCoords.v, metaCoords.z);
}
if (metaPtr == nullptr) return 0L;
for (int i = 0; i < kMaxPlatforms; i++) {
Platform *p;
if ((p = metaPtr->fetchPlatform(mapNum, i)) == nullptr)
continue;
if (p->flags & kPlVisible) {
int16 height;
TileInfo *ti;
int16 trFlags;
ti = p->fetchTile(
mapNum,
coords,
origin,
height,
trFlags);
if (ti) {
int16 tileMinZ = height,
tileMaxZ = height;
int32 combinedMask = ti->combinedTerrainMask();
if (combinedMask & kTerrainRaised)
tileMaxZ += ti->attrs.terrainHeight;
if (combinedMask & kTerrainWater)
tileMinZ -= ti->attrs.terrainHeight;
if (tileMinZ < maxZ
&& tileMaxZ >= minZ) {
uint32 terrainResult = 0,
tileFgdTerrain = (1 << ti->attrs.fgdTerrain),
tileBgdTerrain = (1 << ti->attrs.bgdTerrain);
// If only checking the top of raised terrain treat it
// as if it were normal terrain.
if (minZ >= tileMaxZ) {
if (tileFgdTerrain & kTerrainSupportingRaised)
tileFgdTerrain = kTerrainNormal;
if (tileBgdTerrain & kTerrainSupportingRaised)
tileBgdTerrain = kTerrainNormal;
}
// If this tile is sensitive to being walked on,
// set the "sensitive" flag.
if (trFlags & kTrTileSensitive)
terrainResult |= kTerrainActive;
if (mask & ti->attrs.terrainMask)
terrainResult |= tileFgdTerrain;
if (mask & ~ti->attrs.terrainMask)
terrainResult |= tileBgdTerrain;
// This prevents actors from walking through
// catwalks and other surfaces which have no bottom.
if ((terrainResult & kTerrainSolidSurface)
&& height > minZ + kMaxStepHeight) {
terrainResult |= kTerrainStone;
}
terrain |= terrainResult;
}
}
}
}
return terrain;
}
/* ===================================================================== *
Function to get the terrain infor for a rectilinear volume
* ===================================================================== */
uint16 uMaxMasks[4] = { 0x0000, 0x000F, 0x00FF, 0x0FFF },
uMinMasks[4] = { 0xFFFF, 0xFFF0, 0xFF00, 0xF000 },
vMaxMasks[4] = { 0x0000, 0x1111, 0x3333, 0x7777 },
vMinMasks[4] = { 0xFFFF, 0xEEEE, 0xCCCC, 0x8888 };
uint32 volumeTerrain(int16 mapNum, const TileRegion &vol) {
uint32 terrain = 0; // accumulated terrain
TilePoint tilePt;
TileRegion footprint,
subPos,
volume;
// Convert to subtile coords
volume.min.u = vol.min.u >> kSubTileShift;
volume.min.v = vol.min.v >> kSubTileShift;
volume.max.u = (vol.max.u + kSubTileMask) >> kSubTileShift;
volume.max.v = (vol.max.v + kSubTileMask) >> kSubTileShift;
volume.min.z = vol.min.z;
volume.max.z = vol.max.z;
// Calculate the footprint of the object (in subtile coords)
footprint.min.u = volume.min.u >> kTileSubShift;
footprint.min.v = volume.min.v >> kTileSubShift;
footprint.max.u = volume.max.u >> kTileSubShift;
footprint.max.v = volume.max.v >> kTileSubShift;
// Calculate which subtiles the region falls upon.
subPos.min.u = volume.min.u & kSubTileMask;
subPos.min.v = volume.min.v & kSubTileMask;
subPos.max.u = volume.max.u & kSubTileMask;
subPos.max.v = volume.max.v & kSubTileMask;
tilePt.z = 0;
for (tilePt.v = footprint.min.v;
tilePt.v <= footprint.max.v;
tilePt.v++) {
uint16 vSectionMask = 0xFFFF;
if (tilePt.v == footprint.min.v)
vSectionMask &= vMinMasks[subPos.min.v];
if (tilePt.v == footprint.max.v)
vSectionMask &= vMaxMasks[subPos.max.v];
for (tilePt.u = footprint.min.u;
tilePt.u <= footprint.max.u;
tilePt.u++) {
uint16 uSectionMask = vSectionMask;
if (tilePt.u == footprint.min.u)
uSectionMask &= uMinMasks[subPos.min.u];
if (tilePt.u == footprint.max.u)
uSectionMask &= uMaxMasks[subPos.max.u];
terrain |= tileTerrain(
mapNum,
tilePt,
uSectionMask,
volume.min.z,
volume.max.z);
}
}
return terrain;
}
uint32 volumeTerrain(
int16 mapNum,
const TilePoint &pos,
int16 objSection,
int16 objHeight) {
uint32 terrain = 0; // accumulated terrain
TileRegion volume;
// Calculate the volume the object occupies
volume.min.u = pos.u - objSection;
volume.min.v = pos.v - objSection;
volume.max.u = pos.u + objSection;
volume.max.v = pos.v + objSection;
volume.min.z = pos.z;
volume.max.z = pos.z + objHeight;
terrain = volumeTerrain(mapNum, volume);
return terrain;
}
uint32 volumeTerrain(
int16 mapNum,
const TilePoint &pos,
int16 uCross,
int16 vCross,
int16 objHeight) {
uint32 terrain = 0; // accumulated terrain
TileRegion volume;
// Calculate the volume the object occupies
volume.min.u = pos.u - uCross;
volume.min.v = pos.v - vCross;
volume.max.u = pos.u + uCross;
volume.max.v = pos.v + vCross;
volume.min.z = pos.z;
volume.max.z = pos.z + objHeight;
if (debugChannelSet(-1, kDebugTiles)) {
TilePoint minUminV(volume.min.u, volume.min.v, volume.min.z);
TilePoint maxUminV(volume.max.u, volume.min.v, volume.min.z);
TilePoint maxUmaxV(volume.max.u, volume.max.v, volume.min.z);
TilePoint minUmaxV(volume.min.u, volume.max.v, volume.min.z);
TPLine(minUminV, maxUminV, 7);
TPLine(maxUminV, maxUmaxV, 7);
TPLine(maxUmaxV, minUmaxV, 7);
TPLine(minUmaxV, minUminV, 7);
}
terrain = volumeTerrain(mapNum, volume);
return terrain;
}
/* ===================================================================== *
Function to get the terrain info for linear area
* ===================================================================== */
uint16 uMask[4] = { 0x000F, 0x00F0, 0x0F00, 0xF000 },
vMask[4] = { 0x1111, 0x2222, 0x4444, 0x8888 };
uint32 lineTerrain(
int16 mapNum,
const TilePoint &from,
const TilePoint &to,
uint32 opaqueTerrain) {
uint32 terrain = 0;
TilePoint curSubTile,
destSubTile,
tilePt;
int8 uStep,
vStep;
uint16 uDiff,
vDiff;
uint16 errorTerm = 0;
uint16 subTileMask_ = 0;
int16 tileStartZ,
minZ,
maxZ;
int32 curZ,
zStep;
TilePoint prevPoint = from;
TilePoint tempPoint;
// Calculate starting subtile coordinates
curSubTile.u = from.u >> kSubTileShift;
curSubTile.v = from.v >> kSubTileShift;
curSubTile.z = tileStartZ = from.z;
// Calculate destination subtil coordinates
destSubTile.u = to.u >> kSubTileShift;
destSubTile.v = to.v >> kSubTileShift;
destSubTile.z = to.z;
tilePt.u = curSubTile.u >> kTileSubShift;
tilePt.v = curSubTile.v >> kTileSubShift;
tilePt.z = 0;
if (destSubTile.u > curSubTile.u) {
uStep = 1;
uDiff = destSubTile.u - curSubTile.u;
} else {
uStep = -1;
uDiff = curSubTile.u - destSubTile.u;
}
if (destSubTile.v > curSubTile.v) {
vStep = 1;
vDiff = destSubTile.v - curSubTile.v;
} else {
vStep = -1;
vDiff = curSubTile.v - destSubTile.v;
}
if (uDiff == 0 && vDiff == 0) return 0;
curZ = (int32)curSubTile.z << 16;
zStep = ((int32)(destSubTile.z - curSubTile.z) << 16);
if (zStep > 0) {
minZ = tileStartZ;
maxZ = curSubTile.z;
} else {
minZ = curSubTile.z;
maxZ = tileStartZ;
}
if (uDiff > vDiff) {
// U difference is greater
zStep /= uDiff;
for (;
curSubTile.u != destSubTile.u;
curSubTile.u += uStep) {
curZ += zStep;
if ((curSubTile.u >> kTileSubShift) != tilePt.u) {
curSubTile.z = curZ >> 16;
terrain |= tileTerrain(
mapNum,
tilePt,
subTileMask_,
minZ,
maxZ + 1);
if (terrain & opaqueTerrain) return terrain;
tilePt.u = curSubTile.u >> kTileSubShift;
tileStartZ = curSubTile.z;
subTileMask_ = 0;
}
subTileMask_ |= (uMask[curSubTile.u & kSubTileMask] &
vMask[curSubTile.v & kSubTileMask]);
if (debugChannelSet(-1, kDebugTiles)) {
tempPoint.u = curSubTile.u << kTileSubShift;
tempPoint.v = curSubTile.v << kTileSubShift;
tempPoint.z = curSubTile.z;
TPLine(prevPoint, tempPoint);
prevPoint = tempPoint;
}
errorTerm += vDiff;
if (errorTerm >= uDiff) {
errorTerm -= uDiff;
curSubTile.v += vStep;
if ((curSubTile.v >> kTileSubShift) != tilePt.z) {
curSubTile.z = curZ >> 16;
terrain |= tileTerrain(
mapNum,
tilePt,
subTileMask_,
minZ,
maxZ + 1);
if (terrain & opaqueTerrain) return terrain;
tilePt.v = curSubTile.v >> kTileSubShift;
tileStartZ = curSubTile.z;
subTileMask_ = 0;
}
subTileMask_ |= (uMask[curSubTile.u & kSubTileMask] &
vMask[curSubTile.v & kSubTileMask]);
if (debugChannelSet(-1, kDebugTiles)) {
tempPoint.u = curSubTile.u << kTileSubShift;
tempPoint.v = curSubTile.v << kTileSubShift;
tempPoint.z = curSubTile.z;
TPLine(prevPoint, tempPoint);
prevPoint = tempPoint;
warning("***************************");
}
}
}
} else {
// V difference is greater
zStep /= vDiff;
for (;
curSubTile.v != destSubTile.v;
curSubTile.v += vStep) {
curZ += zStep;
if ((curSubTile.v >> kTileSubShift) != tilePt.v) {
curSubTile.z = curZ >> 16;
terrain |= tileTerrain(
mapNum,
tilePt,
subTileMask_,
minZ,
maxZ + 1);
if (terrain & opaqueTerrain) return terrain;
tilePt.v = curSubTile.v >> kTileSubShift;
tileStartZ = curSubTile.z;
subTileMask_ = 0;
}
subTileMask_ |= (uMask[curSubTile.u & kSubTileMask] &
vMask[curSubTile.v & kSubTileMask]);
if (debugChannelSet(-1, kDebugTiles)) {
tempPoint.u = curSubTile.u << kTileSubShift;
tempPoint.v = curSubTile.v << kTileSubShift;
tempPoint.z = curSubTile.z;
TPLine(prevPoint, tempPoint);
prevPoint = tempPoint;
}
errorTerm += uDiff;
if (errorTerm >= vDiff) {
errorTerm -= vDiff;
curSubTile.u += uStep;
if ((curSubTile.u >> kTileSubShift) != tilePt.u) {
curSubTile.z = curZ >> 16;
terrain |= tileTerrain(
mapNum,
tilePt,
subTileMask_,
minZ,
maxZ + 1);
if (terrain & opaqueTerrain) return terrain;
tilePt.u = curSubTile.u >> kTileSubShift;
tileStartZ = curSubTile.z;
subTileMask_ = 0;
}
subTileMask_ |= (uMask[curSubTile.u & kSubTileMask] &
vMask[curSubTile.v & kSubTileMask]);
if (debugChannelSet(-1, kDebugTiles)) {
tempPoint.u = curSubTile.u << kTileSubShift;
tempPoint.v = curSubTile.v << kTileSubShift;
tempPoint.z = curSubTile.z;
TPLine(prevPoint, tempPoint);
prevPoint = tempPoint;
}
}
}
}
curSubTile.z = curZ >> 16;
terrain |= tileTerrain(
mapNum,
tilePt,
subTileMask_,
minZ,
maxZ);
return terrain;
}
/* ===================================================================== *
Function to return slope information
* ===================================================================== */
// This function determines the height of the tile's actual
// surface, given by the tile slope information. If there are
// several tiles within the same space, then it uses the highest
// one who's base is below the character's feet. Tiles which
// have no supporting surfaces are not considered.
//
// This routine now also returns a bunch of information about the
// tile which forms the surface.
int16 tileSlopeHeight(
const TilePoint &pt,
int16 mapNum,
int objectHeight,
StandingTileInfo *stiResult,
uint8 *platformResult) {
// Calculate coordinates of tile, metatile, and subtile
TilePoint tileCoords = pt >> kTileUVShift,
metaCoords = tileCoords >> kPlatShift,
origin = metaCoords << kPlatShift,
coords = tileCoords - origin,
subTile((pt.u >> kSubTileShift) & kSubTileMask,
(pt.v >> kSubTileShift) & kSubTileMask,
0);
MetaTilePtr metaPtr;
StandingTileInfo highestTile, // Represents highest tile which is below
// object's base
lowestTile; // Represents lowest tile at tile position
int16 supportHeight,
highestSupportHeight,
lowestSupportHeight;
uint8 i,
highestSupportPlatform = 0,
lowestSupportPlatform = 0;
// Look up the metatile on the map.
metaPtr = prevMeta = mapList[mapNum].lookupMeta(metaCoords);
prevMapNum = mapNum;
prevCoords.set(metaCoords.u, metaCoords.v, metaCoords.z);
if (metaPtr != nullptr) {
highestTile.surfaceTile = lowestTile.surfaceTile = nullptr;
highestSupportHeight = -100;
lowestSupportHeight = 0x7FFF;
// Search each platform until we find a tile which is under
// the character.
for (i = 0; i < kMaxPlatforms; i++) {
Platform *p;
if ((p = metaPtr->fetchPlatform(mapNum, i)) == nullptr)
continue;
if (p->flags & kPlVisible) {
TileInfo *ti;
StandingTileInfo sti;
// Get the tile, and its base height
ti = p->fetchTAGInstance(
mapNum,
coords,
origin,
sti);
if (ti) {
int16 tileBase = sti.surfaceHeight;
int32 subTileTerrain =
ti->attrs.testTerrain(calcSubTileMask(subTile.u,
subTile.v));
if (subTileTerrain & kTerrainInsubstantial)
continue;
else if (subTileTerrain & kTerrainSupportingRaised)
// calculate height of raised surface
supportHeight = sti.surfaceHeight +
ti->attrs.terrainHeight;
else if (subTileTerrain & kTerrainWater) {
// calculate depth of water
supportHeight = sti.surfaceHeight -
ti->attrs.terrainHeight;
tileBase = supportHeight;
} else
// calculate height of unraised surface
supportHeight = sti.surfaceHeight +
ptHeight(TilePoint(pt.u & kTileUVMask,
pt.v & kTileUVMask,
0),
ti->attrs.cornerHeight);
// See if the tile is a potential supporting surface
if (tileBase < pt.z + objectHeight
&& supportHeight >= highestSupportHeight
&& (ti->combinedTerrainMask() &
(kTerrainSurface | kTerrainRaised))) {
highestTile = sti;
highestSupportHeight = supportHeight;
highestSupportPlatform = i;
} else if (highestTile.surfaceTile == nullptr &&
supportHeight <= lowestSupportHeight &&
(ti->combinedTerrainMask() &
(kTerrainSurface | kTerrainRaised))) {
lowestTile = sti;
lowestSupportHeight = supportHeight;
lowestSupportPlatform = i;
}
}
}
}
if (highestTile.surfaceTile) {
if (stiResult) *stiResult = highestTile;
if (platformResult) *platformResult = highestSupportPlatform;
return highestSupportHeight;
}
if (lowestTile.surfaceTile) {
if (stiResult) *stiResult = lowestTile;
if (platformResult) *platformResult = lowestSupportPlatform;
return lowestSupportHeight;
}
}
if (stiResult) {
stiResult->surfaceTile = nullptr;
stiResult->surfaceTAG = nullptr;
stiResult->surfaceHeight = 0;
}
if (platformResult) *platformResult = 0;
return 0;
}
// Old-style version of tileSlopeHeight()
int16 tileSlopeHeight(
const TilePoint &pt,
GameObject *obj,
StandingTileInfo *stiResult,
uint8 *platformResult) {
assert(obj);
assert(obj->proto());
return tileSlopeHeight(
pt,
obj->getMapNum(),
obj->proto()->height,
stiResult,
platformResult);
}
// A version of tileSlopeHeight that takes an explicit map number
int16 tileSlopeHeight(
const TilePoint &pt,
int mapNum,
GameObject *obj,
StandingTileInfo *stiResult,
uint8 *platformResult) {
assert(obj);
assert(obj->proto());
return tileSlopeHeight(
pt,
mapNum,
obj->proto()->height,
stiResult,
platformResult);
}
/* ===================================================================== *
Test functions
* ===================================================================== */
// return terrain that object is currently interacting with
uint32 objectTerrain(GameObject *obj, StandingTileInfo &sti) {
int16 mapNum = obj->getMapNum();
ProtoObj *proto = obj->proto();
uint32 terrain;
TilePoint loc = obj->getLocation();
sti.surfaceTAG = nullptr;
terrain = volumeTerrain(mapNum,
loc,
proto->crossSection,
proto->height);
// If one of the tiles we're standing on is active,
// double check to see if we're really standing on it.
if (terrain & kTerrainActive) {
int16 tHeight;
// Determine the height of the landscape we're on
tHeight = tileSlopeHeight(loc, obj, &sti);
// If the character is indeed standing ON the landscape
// REM: This depends on the nature of the tile I think!!!
if (sti.surfaceTile == nullptr
|| sti.surfaceTAG == nullptr
|| !(sti.surfaceRef.flags & kTrTileSensitive)
|| loc.z >= tHeight + 2
/* || loc.z >= standingTile->attrs.terrainHeight */) {
terrain &= ~kTerrainActive;
}
}
return terrain;
}
// return terrain that object is currently interacting with
int16 checkBlocked(
GameObject *obj,
int16 mapNum,
const TilePoint &loc,
GameObject **blockResultObj) {
ProtoObj *proto = obj->proto();
uint8 height = proto->height;
int32 terrain;
GameObject *blockObj;
GameWorld *world;
if (blockResultObj) *blockResultObj = nullptr;
// check to make sure the actor recognizes terrain
if (!isActor(obj) || !((Actor *) obj)->hasEffect(kActorNoncorporeal)) {
TilePoint testLoc = loc;
testLoc.z = MAX<int16>(loc.z, 8);
terrain = volumeTerrain(mapNum,
testLoc,
proto->crossSection,
height);
// Check for intersection with a wall or obstacle
if (terrain & kTerrainRaised) return kBlockageTerrain;
}
// See if object collided with an object
world = (GameWorld *)GameObject::objectAddress(mapList[mapNum].worldID);
blockObj = objectCollision(obj, world, loc);
if (blockObj) {
if (blockResultObj) *blockResultObj = blockObj;
return kBlockageObject;
}
return kBlockageNone;
}
// return terrain that object is currently interacting with
int16 checkBlocked(
GameObject *obj,
const TilePoint &loc,
GameObject **blockResultObj) {
return checkBlocked(obj, obj->getMapNum(), loc, blockResultObj);
}
// same as checkBlocked() above but includes additional "walking off
// cliff" check
int16 checkWalkable(
GameObject *obj,
const TilePoint &loc,
GameObject **blockResultObj) {
int16 result;
int16 supportHeight;
StandingTileInfo sti;
if ((result = checkBlocked(obj, loc, blockResultObj)) != kBlockageNone)
return result;
supportHeight = tileSlopeHeight(loc, obj, &sti);
if (supportHeight < loc.z - kMaxStepHeight * 4)
return kBlockageTerrain;
if (sti.surfaceTile != nullptr) {
int16 subTileU,
subTileV,
mask;
subTileU = (loc.u & kTileUVMask) >> kSubTileShift;
subTileV = (loc.v & kTileUVMask) >> kSubTileShift;
mask = 1 << ((subTileU << kSubTileShift) + subTileV);
// If the suporting subtile is funiture consider this blocked
if (sti.surfaceTile->attrs.testTerrain(mask) & kTerrainFurniture)
return kBlockageTerrain;
}
return kBlockageNone;
}
// return terrain that object is currently interacting with
int16 checkContact(
GameObject *obj,
const TilePoint &loc,
GameObject **blockResultObj) {
int16 mapNum = obj->getMapNum();
ProtoObj *proto = obj->proto();
int32 terrain;
GameObject *blockObj;
GameWorld *world;
if (blockResultObj) *blockResultObj = nullptr;
terrain = volumeTerrain(mapNum,
loc,
proto->crossSection,
proto->height);
// Check for intersection with a wall or obstacle
if (terrain & kTerrainRaised) return kBlockageTerrain;
// Check for intersection with slope of the terrain.
if (((terrain & kTerrainSurface)
&& loc.z <= tileSlopeHeight(loc, obj))
|| (!(terrain & kTerrainWater)
&& loc.z <= 0))
return kBlockageTerrain;
// See if object collided with an object
world = (GameWorld *)GameObject::objectAddress(mapList[mapNum].worldID);
blockObj = objectCollision(obj, world, loc);
if (blockObj) {
if (blockResultObj) *blockResultObj = blockObj;
return kBlockageObject;
}
return kBlockageNone;
}
} // end of namespace Saga2