1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * aint32 with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  *
22  * Based on the original sources
23  *   Faery Tale II -- The Halls of the Dead
24  *   (c) 1993-1996 The Wyrmkeep Entertainment Co.
25  */
26 
27 #include "saga2/saga2.h"
28 #include "saga2/idtypes.h"
29 #include "saga2/tile.h"
30 #include "saga2/tileline.h"
31 #include "saga2/actor.h"
32 
33 namespace Saga2 {
34 
35 
36 extern WorldMapData     *mapList;
37 
38 static int16        prevMapNum;
39 static StaticTilePoint prevCoords = {(int16)minint16, (int16)minint16, (int16)minint16};
40 static MetaTilePtr  prevMeta = NULL;
41 
42 /* ===================================================================== *
43    Terrain damage info
44  * ===================================================================== */
45 
drown(GameObject * obj)46 void drown(GameObject *obj) {
47 	if (isActor(obj)) {
48 		Actor *a = (Actor *) obj;
49 		if (!a->hasEffect(actorWaterBreathe)) {
50 			if (g_vm->_rnd->getRandomNumber(drowningDamageOddsYes + drowningDamageOddsNo - 1) > drowningDamageOddsNo - 1) {
51 				a->acceptDamage(a->thisID(), drowningDamagePerFrame);
52 			}
53 		}
54 	}
55 
56 }
57 
lavaDamage(GameObject * obj)58 void lavaDamage(GameObject *obj) {
59 	if (isActor(obj)) {
60 		Actor *a = (Actor *) obj;
61 		if (a->resists(resistHeat))
62 			return;
63 	}
64 	if (g_vm->_rnd->getRandomNumber(heatDamageOddsYes + heatDamageOddsNo - 1) > heatDamageOddsNo - 1) {
65 		obj->acceptDamage(obj->thisID(), heatDamagePerFrame, kDamageHeat, heatDamageDicePerFrame, 6);
66 	}
67 }
68 
coldDamage(GameObject * obj)69 void coldDamage(GameObject *obj) {
70 	if (isActor(obj)) {
71 		Actor *a = (Actor *) obj;
72 		if (a->resists(resistCold))
73 			return;
74 	}
75 	if (g_vm->_rnd->getRandomNumber(coldDamageOddsYes + coldDamageOddsNo - 1) > coldDamageOddsNo - 1) {
76 		obj->acceptDamage(obj->thisID(), coldDamagePerFrame, kDamageCold, coldDamageDicePerFrame, 6);
77 	}
78 }
79 
terrainDamageSlash(GameObject * obj)80 void terrainDamageSlash(GameObject *obj) {
81 	if (g_vm->_rnd->getRandomNumber(terrainDamageOddsYes + terrainDamageOddsNo - 1) > terrainDamageOddsNo - 1) {
82 		obj->acceptDamage(obj->thisID(), terrainDamagePerFrame, kDamageSlash, terrainDamageDicePerFrame, 6);
83 	}
84 }
85 
terrainDamageBash(GameObject * obj)86 void terrainDamageBash(GameObject *obj) {
87 	if (g_vm->_rnd->getRandomNumber(terrainDamageOddsYes + terrainDamageOddsNo - 1) > terrainDamageOddsNo - 1) {
88 		obj->acceptDamage(obj->thisID(), terrainDamagePerFrame, kDamageImpact, terrainDamageDicePerFrame, 6);
89 	}
90 }
91 
fallingDamage(GameObject * obj,int16 speed)92 void fallingDamage(GameObject *obj, int16 speed) {
93 	if (isActor(obj)) {
94 		Actor *a = (Actor *) obj;
95 		if (!a->hasEffect(actorSlowFall)) {
96 			a->acceptDamage(a->thisID(), (MAX(0, speed - 16)*fallingDamageMult) / fallingDamageDiv);
97 		}
98 	}
99 
100 }
101 
102 
103 
104 /* ===================================================================== *
105    Function to get the terrain bits for a single UV location (with mask)
106  * ===================================================================== */
107 
tileTerrain(int16 mapNum,const TilePoint & pt,int16 mask,int16 minZ,int16 maxZ)108 uint32 tileTerrain(
109     int16 mapNum,
110     const TilePoint &pt,
111     int16 mask,
112     int16 minZ,
113     int16 maxZ) {
114 	WorldMapData    *map = &mapList[mapNum];
115 
116 	TilePoint       metaCoords = pt >> kPlatShift,
117 	                origin = metaCoords << kPlatShift,
118 	                coords = pt - origin;
119 	MetaTilePtr     metaPtr;
120 	uint32          terrain = 0;
121 
122 	//  A simple method for avoiding looking up the metatile again.
123 	/*  if (    prevMeta
124 	        &&  prevMapNum == mapNum
125 	        &&  prevCoords == metaCoords )
126 	        metaPtr = prevMeta;
127 	    else
128 	*/
129 	{
130 		//  Look up the metatile on the map.
131 		metaPtr = prevMeta = map->lookupMeta(metaCoords);
132 		prevMapNum = mapNum;
133 		prevCoords.set(metaCoords.u, metaCoords.v, metaCoords.z);
134 	}
135 
136 	if (metaPtr == NULL) return 0L;
137 
138 	for (int i = 0; i < maxPlatforms; i++) {
139 		Platform    *p;
140 
141 		if ((p = metaPtr->fetchPlatform(mapNum, i)) == NULL)
142 			continue;
143 
144 		if (p->flags & plVisible) {
145 			int16           height;
146 			TileInfo        *ti;
147 			int16           trFlags;
148 
149 			ti =    p->fetchTile(
150 			            mapNum,
151 			            coords,
152 			            origin,
153 			            height,
154 			            trFlags);
155 
156 			if (ti) {
157 				int16   tileMinZ = height,
158 				        tileMaxZ = height;
159 				int32   combinedMask = ti->combinedTerrainMask();
160 
161 				if (combinedMask & terrainRaised)
162 					tileMaxZ += ti->attrs.terrainHeight;
163 				if (combinedMask & terrainWater)
164 					tileMinZ -= ti->attrs.terrainHeight;
165 
166 				if (tileMinZ <  maxZ
167 				        &&  tileMaxZ >= minZ) {
168 					uint32   terrainResult = 0,
169 					         tileFgdTerrain = (1 << ti->attrs.fgdTerrain),
170 					         tileBgdTerrain = (1 << ti->attrs.bgdTerrain);
171 
172 					//  If only checking the top of raised terrain treat it
173 					//  as if it were normal terrain.
174 					if (minZ >= tileMaxZ) {
175 						if (tileFgdTerrain & terrainSupportingRaised)
176 							tileFgdTerrain = terrainNormal;
177 						if (tileBgdTerrain & terrainSupportingRaised)
178 							tileBgdTerrain = terrainNormal;
179 					}
180 
181 					//  If this tile is sensitive to being walked on,
182 					//  set the "sensitive" flag.
183 					if (trFlags & trTileSensitive)
184 						terrainResult |= terrainActive;
185 
186 					if (mask & ti->attrs.terrainMask)
187 						terrainResult |= tileFgdTerrain;
188 
189 					if (mask & ~ti->attrs.terrainMask)
190 						terrainResult |= tileBgdTerrain;
191 
192 					//  This prevents actors from walking through
193 					//  catwalks and other surfaces which have no bottom.
194 
195 					if ((terrainResult & terrainSolidSurface)
196 					        &&  height > minZ + kMaxStepHeight) {
197 						terrainResult |= terrainStone;
198 					}
199 
200 					terrain |= terrainResult;
201 				}
202 			}
203 		}
204 	}
205 	return terrain;
206 }
207 
208 /* ===================================================================== *
209    Function to get the terrain infor for a rectilinear volume
210  * ===================================================================== */
211 
212 uint16          uMaxMasks[4] = { 0x0000, 0x000F, 0x00FF, 0x0FFF },
213                                  uMinMasks[4] = { 0xFFFF, 0xFFF0, 0xFF00, 0xF000 },
214                                          vMaxMasks[4] = { 0x0000, 0x1111, 0x3333, 0x7777 },
215                                                  vMinMasks[4] = { 0xFFFF, 0xEEEE, 0xCCCC, 0x8888 };
216 
volumeTerrain(int16 mapNum,const TileRegion & vol)217 uint32 volumeTerrain(int16 mapNum, const TileRegion &vol) {
218 	uint32      terrain = 0;            // accumulated terrain
219 	TilePoint   tilePt;
220 	TileRegion  footprint,
221 	            subPos,
222 	            volume;
223 
224 	//  Convert to subtile coords
225 	volume.min.u = vol.min.u >> kSubTileShift;
226 	volume.min.v = vol.min.v >> kSubTileShift;
227 	volume.max.u = (vol.max.u + kSubTileMask) >> kSubTileShift;
228 	volume.max.v = (vol.max.v + kSubTileMask) >> kSubTileShift;
229 	volume.min.z = vol.min.z;
230 	volume.max.z = vol.max.z;
231 
232 	//  Calculate the footprint of the object (in subtile coords)
233 	footprint.min.u = volume.min.u >> kTileSubShift;
234 	footprint.min.v = volume.min.v >> kTileSubShift;
235 	footprint.max.u = volume.max.u >> kTileSubShift;
236 	footprint.max.v = volume.max.v >> kTileSubShift;
237 
238 	//  Calculate which subtiles the region falls upon.
239 	subPos.min.u = volume.min.u & kSubTileMask;
240 	subPos.min.v = volume.min.v & kSubTileMask;
241 	subPos.max.u = volume.max.u & kSubTileMask;
242 	subPos.max.v = volume.max.v & kSubTileMask;
243 
244 	tilePt.z = 0;
245 
246 	for (tilePt.v = footprint.min.v;
247 	        tilePt.v <= footprint.max.v;
248 	        tilePt.v++) {
249 		uint16      vSectionMask = 0xFFFF;
250 
251 		if (tilePt.v == footprint.min.v)
252 			vSectionMask &= vMinMasks[subPos.min.v];
253 
254 		if (tilePt.v == footprint.max.v)
255 			vSectionMask &= vMaxMasks[subPos.max.v];
256 
257 		for (tilePt.u = footprint.min.u;
258 		        tilePt.u <= footprint.max.u;
259 		        tilePt.u++) {
260 			uint16  uSectionMask = vSectionMask;
261 
262 			if (tilePt.u == footprint.min.u)
263 				uSectionMask &= uMinMasks[subPos.min.u];
264 
265 			if (tilePt.u == footprint.max.u)
266 				uSectionMask &= uMaxMasks[subPos.max.u];
267 
268 			terrain |= tileTerrain(
269 			               mapNum,
270 			               tilePt,
271 			               uSectionMask,
272 			               volume.min.z,
273 			               volume.max.z);
274 		}
275 	}
276 	return terrain;
277 }
278 
volumeTerrain(int16 mapNum,const TilePoint & pos,int16 objSection,int16 objHeight)279 uint32 volumeTerrain(
280     int16 mapNum,
281     const TilePoint &pos,
282     int16 objSection,
283     int16 objHeight) {
284 	uint32      terrain = 0;            // accumulated terrain
285 	TileRegion  volume;
286 
287 	//  Calculate the volume the object occupies
288 	volume.min.u = pos.u - objSection;
289 	volume.min.v = pos.v - objSection;
290 	volume.max.u = pos.u + objSection;
291 	volume.max.v = pos.v + objSection;
292 	volume.min.z = pos.z;
293 	volume.max.z = pos.z + objHeight;
294 
295 	terrain = volumeTerrain(mapNum, volume);
296 
297 	return terrain;
298 }
299 
volumeTerrain(int16 mapNum,const TilePoint & pos,int16 uCross,int16 vCross,int16 objHeight)300 uint32 volumeTerrain(
301     int16 mapNum,
302     const TilePoint &pos,
303     int16 uCross,
304     int16 vCross,
305     int16 objHeight) {
306 	uint32      terrain = 0;            // accumulated terrain
307 	TileRegion  volume;
308 
309 	//  Calculate the volume the object occupies
310 	volume.min.u = pos.u - uCross;
311 	volume.min.v = pos.v - vCross;
312 	volume.max.u = pos.u + uCross;
313 	volume.max.v = pos.v + vCross;
314 	volume.min.z = pos.z;
315 	volume.max.z = pos.z + objHeight;
316 
317 	if (debugChannelSet(-1, kDebugTiles)) {
318 		TilePoint minUminV(volume.min.u, volume.min.v, volume.min.z);
319 		TilePoint maxUminV(volume.max.u, volume.min.v, volume.min.z);
320 		TilePoint maxUmaxV(volume.max.u, volume.max.v, volume.min.z);
321 		TilePoint minUmaxV(volume.min.u, volume.max.v, volume.min.z);
322 
323 		TPLine(minUminV, maxUminV, 7);
324 		TPLine(maxUminV, maxUmaxV, 7);
325 		TPLine(maxUmaxV, minUmaxV, 7);
326 		TPLine(minUmaxV, minUminV, 7);
327 	}
328 
329 	terrain = volumeTerrain(mapNum, volume);
330 
331 	return terrain;
332 }
333 
334 /* ===================================================================== *
335    Function to get the terrain info for linear area
336  * ===================================================================== */
337 
338 uint16  uMask[4] = { 0x000F, 0x00F0, 0x0F00, 0xF000 },
339                      vMask[4] = { 0x1111, 0x2222, 0x4444, 0x8888 };
340 
lineTerrain(int16 mapNum,const TilePoint & from,const TilePoint & to,uint32 opaqueTerrain)341 uint32 lineTerrain(
342     int16           mapNum,
343     const TilePoint &from,
344     const TilePoint &to,
345     uint32          opaqueTerrain) {
346 	uint32      terrain = 0;
347 
348 	TilePoint   curSubTile,
349 	            destSubTile,
350 	            tilePt;
351 
352 	int8        uStep,
353 	            vStep;
354 
355 	uint16      uDiff,
356 	            vDiff;
357 
358 	uint16      errorTerm = 0;
359 
360 	uint16      subTileMask_ = 0;
361 
362 	int16       tileStartZ,
363 	            minZ,
364 	            maxZ;
365 
366 	int32       curZ,
367 	            zStep;
368 
369 	TilePoint   prevPoint = from;
370 	TilePoint   tempPoint;
371 
372 	//  Calculate starting subtile coordinates
373 	curSubTile.u = from.u >> kSubTileShift;
374 	curSubTile.v = from.v >> kSubTileShift;
375 	curSubTile.z = tileStartZ = from.z;
376 
377 	//  Calculate destination subtil coordinates
378 	destSubTile.u = to.u >> kSubTileShift;
379 	destSubTile.v = to.v >> kSubTileShift;
380 	destSubTile.z = to.z;
381 
382 	tilePt.u = curSubTile.u >> kTileSubShift;
383 	tilePt.v = curSubTile.v >> kTileSubShift;
384 	tilePt.z = 0;
385 
386 	if (destSubTile.u > curSubTile.u) {
387 		uStep = 1;
388 		uDiff = destSubTile.u - curSubTile.u;
389 	} else {
390 		uStep = -1;
391 		uDiff = curSubTile.u - destSubTile.u;
392 	}
393 
394 	if (destSubTile.v > curSubTile.v) {
395 		vStep = 1;
396 		vDiff = destSubTile.v - curSubTile.v;
397 	} else {
398 		vStep = -1;
399 		vDiff = curSubTile.v - destSubTile.v;
400 	}
401 
402 	if (uDiff == 0 && vDiff == 0) return 0;
403 
404 	curZ = (int32)curSubTile.z << 16;
405 	zStep = ((int32)(destSubTile.z - curSubTile.z) << 16);
406 	if (zStep > 0) {
407 		minZ = tileStartZ;
408 		maxZ = curSubTile.z;
409 	} else {
410 		minZ = curSubTile.z;
411 		maxZ = tileStartZ;
412 	}
413 
414 	if (uDiff > vDiff) {
415 		//  U difference is greater
416 
417 		zStep /= uDiff;
418 
419 		for (;
420 		        curSubTile.u != destSubTile.u;
421 		        curSubTile.u += uStep) {
422 			curZ += zStep;
423 
424 			if ((curSubTile.u >> kTileSubShift) != tilePt.u) {
425 				curSubTile.z = curZ >> 16;
426 
427 				terrain |=  tileTerrain(
428 				                mapNum,
429 				                tilePt,
430 				                subTileMask_,
431 				                minZ,
432 				                maxZ + 1);
433 				if (terrain & opaqueTerrain) return terrain;
434 
435 				tilePt.u = curSubTile.u >> kTileSubShift;
436 				tileStartZ = curSubTile.z;
437 				subTileMask_ = 0;
438 			}
439 
440 			subTileMask_ |= (uMask[curSubTile.u & kSubTileMask] &
441 			                vMask[curSubTile.v & kSubTileMask]);
442 
443 			if (debugChannelSet(-1, kDebugTiles)) {
444 				tempPoint.u = curSubTile.u << kTileSubShift;
445 				tempPoint.v = curSubTile.v << kTileSubShift;
446 				tempPoint.z = curSubTile.z;
447 				TPLine(prevPoint, tempPoint);
448 				prevPoint = tempPoint;
449 			}
450 
451 			errorTerm += vDiff;
452 			if (errorTerm >= uDiff) {
453 				errorTerm -= uDiff;
454 				curSubTile.v += vStep;
455 
456 				if ((curSubTile.v >> kTileSubShift) != tilePt.z) {
457 					curSubTile.z = curZ >> 16;
458 
459 					terrain |=  tileTerrain(
460 					                mapNum,
461 					                tilePt,
462 					                subTileMask_,
463 					                minZ,
464 					                maxZ + 1);
465 					if (terrain & opaqueTerrain) return terrain;
466 
467 					tilePt.v = curSubTile.v >> kTileSubShift;
468 					tileStartZ = curSubTile.z;
469 					subTileMask_ = 0;
470 				}
471 
472 				subTileMask_ |= (uMask[curSubTile.u & kSubTileMask] &
473 				                vMask[curSubTile.v & kSubTileMask]);
474 
475 				if (debugChannelSet(-1, kDebugTiles)) {
476 					tempPoint.u = curSubTile.u << kTileSubShift;
477 					tempPoint.v = curSubTile.v << kTileSubShift;
478 					tempPoint.z = curSubTile.z;
479 					TPLine(prevPoint, tempPoint);
480 					prevPoint = tempPoint;
481 				warning("***************************");
482 				}
483 			}
484 		}
485 	} else {
486 		//  V difference is greater
487 
488 		zStep /= vDiff;
489 
490 		for (;
491 		        curSubTile.v != destSubTile.v;
492 		        curSubTile.v += vStep) {
493 			curZ += zStep;
494 
495 			if ((curSubTile.v >> kTileSubShift) != tilePt.v) {
496 				curSubTile.z = curZ >> 16;
497 
498 				terrain |=  tileTerrain(
499 				                mapNum,
500 				                tilePt,
501 				                subTileMask_,
502 				                minZ,
503 				                maxZ + 1);
504 				if (terrain & opaqueTerrain) return terrain;
505 
506 				tilePt.v = curSubTile.v >> kTileSubShift;
507 				tileStartZ = curSubTile.z;
508 				subTileMask_ = 0;
509 			}
510 
511 			subTileMask_ |= (uMask[curSubTile.u & kSubTileMask] &
512 			                vMask[curSubTile.v & kSubTileMask]);
513 
514 			if (debugChannelSet(-1, kDebugTiles)) {
515 				tempPoint.u = curSubTile.u << kTileSubShift;
516 				tempPoint.v = curSubTile.v << kTileSubShift;
517 				tempPoint.z = curSubTile.z;
518 				TPLine(prevPoint, tempPoint);
519 				prevPoint = tempPoint;
520 			}
521 
522 			errorTerm += uDiff;
523 			if (errorTerm >= vDiff) {
524 				errorTerm -= vDiff;
525 				curSubTile.u += uStep;
526 
527 				if ((curSubTile.u >> kTileSubShift) != tilePt.u) {
528 					curSubTile.z = curZ >> 16;
529 
530 					terrain |=  tileTerrain(
531 					                mapNum,
532 					                tilePt,
533 					                subTileMask_,
534 					                minZ,
535 					                maxZ + 1);
536 					if (terrain & opaqueTerrain) return terrain;
537 
538 					tilePt.u = curSubTile.u >> kTileSubShift;
539 					tileStartZ = curSubTile.z;
540 					subTileMask_ = 0;
541 				}
542 
543 				subTileMask_ |= (uMask[curSubTile.u & kSubTileMask] &
544 				                vMask[curSubTile.v & kSubTileMask]);
545 
546 				if (debugChannelSet(-1, kDebugTiles)) {
547 					tempPoint.u = curSubTile.u << kTileSubShift;
548 					tempPoint.v = curSubTile.v << kTileSubShift;
549 					tempPoint.z = curSubTile.z;
550 					TPLine(prevPoint, tempPoint);
551 					prevPoint = tempPoint;
552 				}
553 			}
554 		}
555 	}
556 
557 	curSubTile.z = curZ >> 16;
558 
559 	terrain |=  tileTerrain(
560 	                mapNum,
561 	                tilePt,
562 	                subTileMask_,
563 	                minZ,
564 	                maxZ);
565 
566 	return terrain;
567 }
568 
569 
570 /* ===================================================================== *
571    Function to return slope information
572  * ===================================================================== */
573 
574 //  This function determines the height of the tile's actual
575 //  surface, given by the tile slope information. If there are
576 //  several tiles within the same space, then it uses the highest
577 //  one who's base is below the character's feet. Tiles which
578 //  have no supporting surfaces are not considered.
579 //
580 //  This routine now also returns a bunch of information about the
581 //  tile which forms the surface.
582 
tileSlopeHeight(const TilePoint & pt,int16 mapNum,int objectHeight,StandingTileInfo * stiResult,uint8 * platformResult)583 int16 tileSlopeHeight(
584     const TilePoint     &pt,
585     int16               mapNum,
586     int                 objectHeight,
587     StandingTileInfo    *stiResult,
588     uint8               *platformResult) {
589 	//  Calculate coordinates of tile, metatile, and subtile
590 	TilePoint       tileCoords = pt >> kTileUVShift,
591 	                metaCoords = tileCoords >> kPlatShift,
592 	                origin = metaCoords << kPlatShift,
593 	                coords = tileCoords - origin,
594 	                subTile((pt.u >> kSubTileShift) & kSubTileMask,
595 	                        (pt.v >> kSubTileShift) & kSubTileMask,
596 	                        0);
597 
598 	MetaTilePtr     metaPtr;
599 	StandingTileInfo highestTile,   //  Represents highest tile which is below
600 	                 //  object's base
601 	                 lowestTile;     //  Represents lowest tile at tile position
602 	int16           supportHeight,
603 	                highestSupportHeight,
604 	                lowestSupportHeight;
605 	uint8           i,
606 	                highestSupportPlatform = 0,
607 	                lowestSupportPlatform = 0;
608 
609 	//  Look up the metatile on the map.
610 	metaPtr = prevMeta = mapList[mapNum].lookupMeta(metaCoords);
611 	prevMapNum = mapNum;
612 	prevCoords.set(metaCoords.u, metaCoords.v, metaCoords.z);
613 
614 	if (metaPtr != NULL) {
615 		highestTile.surfaceTile = lowestTile.surfaceTile = NULL;
616 		highestSupportHeight = -100;
617 		lowestSupportHeight = 0x7FFF;
618 
619 		//  Search each platform until we find a tile which is under
620 		//  the character.
621 
622 		for (i = 0; i < maxPlatforms; i++) {
623 			Platform    *p;
624 
625 			if ((p = metaPtr->fetchPlatform(mapNum, i)) == NULL)
626 				continue;
627 
628 			if (p->flags & plVisible) {
629 				TileInfo        *ti;
630 				StandingTileInfo sti;
631 
632 				//  Get the tile, and its base height
633 				ti =    p->fetchTAGInstance(
634 				            mapNum,
635 				            coords,
636 				            origin,
637 				            sti);
638 
639 				if (ti) {
640 					int16   tileBase = sti.surfaceHeight;
641 					int32 subTileTerrain =
642 					    ti->attrs.testTerrain(calcSubTileMask(subTile.u,
643 					                          subTile.v));
644 					if (subTileTerrain & terrainInsubstantial)
645 						continue;
646 					else if (subTileTerrain & terrainSupportingRaised)
647 						// calculate height of raised surface
648 						supportHeight = sti.surfaceHeight +
649 						                ti->attrs.terrainHeight;
650 					else if (subTileTerrain & terrainWater) {
651 						// calculate depth of water
652 						supportHeight = sti.surfaceHeight -
653 						                ti->attrs.terrainHeight;
654 						tileBase =  supportHeight;
655 					} else
656 						// calculate height of unraised surface
657 						supportHeight = sti.surfaceHeight +
658 						                ptHeight(TilePoint(pt.u & kTileUVMask,
659 						                                   pt.v & kTileUVMask,
660 						                                   0),
661 						                         ti->attrs.cornerHeight);
662 
663 					//  See if the tile is a potential supporting surface
664 					if (tileBase < pt.z + objectHeight
665 					        &&  supportHeight >= highestSupportHeight
666 					        && (ti->combinedTerrainMask() &
667 					            (terrainSurface | terrainRaised))) {
668 						highestTile = sti;
669 						highestSupportHeight = supportHeight;
670 						highestSupportPlatform = i;
671 					} else if (highestTile.surfaceTile == NULL &&
672 					           supportHeight <= lowestSupportHeight &&
673 					           (ti->combinedTerrainMask() &
674 					            (terrainSurface | terrainRaised))) {
675 						lowestTile = sti;
676 						lowestSupportHeight = supportHeight;
677 						lowestSupportPlatform = i;
678 					}
679 				}
680 			}
681 		}
682 
683 		if (highestTile.surfaceTile) {
684 			if (stiResult) *stiResult = highestTile;
685 			if (platformResult) *platformResult = highestSupportPlatform;
686 			return highestSupportHeight;
687 		}
688 		if (lowestTile.surfaceTile) {
689 			if (stiResult) *stiResult = lowestTile;
690 			if (platformResult) *platformResult = lowestSupportPlatform;
691 			return lowestSupportHeight;
692 		}
693 	}
694 
695 	if (stiResult) {
696 		stiResult->surfaceTile = NULL;
697 		stiResult->surfaceTAG = NULL;
698 		stiResult->surfaceHeight = 0;
699 	}
700 	if (platformResult) *platformResult = 0;
701 	return 0;
702 }
703 
704 //  Old-style version of tileSlopeHeight()
tileSlopeHeight(const TilePoint & pt,GameObject * obj,StandingTileInfo * stiResult,uint8 * platformResult)705 int16 tileSlopeHeight(
706     const TilePoint     &pt,
707     GameObject          *obj,
708     StandingTileInfo    *stiResult,
709     uint8               *platformResult) {
710 	assert(obj);
711 	assert(obj->proto());
712 	return  tileSlopeHeight(
713 	            pt,
714 	            obj->getMapNum(),
715 	            obj->proto()->height,
716 	            stiResult,
717 	            platformResult);
718 }
719 
720 //  A version of tileSlopeHeight that takes an explicit map number
tileSlopeHeight(const TilePoint & pt,int mapNum,GameObject * obj,StandingTileInfo * stiResult,uint8 * platformResult)721 int16 tileSlopeHeight(
722     const TilePoint     &pt,
723     int                 mapNum,
724     GameObject          *obj,
725     StandingTileInfo    *stiResult,
726     uint8               *platformResult) {
727 	assert(obj);
728 	assert(obj->proto());
729 	return  tileSlopeHeight(
730 	            pt,
731 	            mapNum,
732 	            obj->proto()->height,
733 	            stiResult,
734 	            platformResult);
735 }
736 
737 /* ===================================================================== *
738    Test functions
739  * ===================================================================== */
740 
741 //  return terrain that object is currently interacting with
objectTerrain(GameObject * obj,StandingTileInfo & sti)742 uint32 objectTerrain(GameObject *obj, StandingTileInfo &sti) {
743 	int16           mapNum = obj->getMapNum();
744 	ProtoObj        *proto = obj->proto();
745 	uint32          terrain;
746 	TilePoint       loc = obj->getLocation();
747 
748 	sti.surfaceTAG = NULL;
749 
750 	terrain = volumeTerrain(mapNum,
751 	                        loc,
752 	                        proto->crossSection,
753 	                        proto->height);
754 
755 	//  If one of the tiles we're standing on is active,
756 	//  double check to see if we're really standing on it.
757 
758 	if (terrain & terrainActive) {
759 		int16       tHeight;
760 
761 		//  Determine the height of the landscape we're on
762 
763 		tHeight = tileSlopeHeight(loc, obj, &sti);
764 
765 		//  If the character is indeed standing ON the landscape
766 		//  REM: This depends on the nature of the tile I think!!!
767 
768 		if (sti.surfaceTile == NULL
769 		        ||  sti.surfaceTAG == NULL
770 		        ||  !(sti.surfaceRef.flags & trTileSensitive)
771 		        ||  loc.z >= tHeight + 2
772 		        /* ||   loc.z >= standingTile->attrs.terrainHeight */) {
773 			terrain &= ~terrainActive;
774 		}
775 	}
776 
777 	return terrain;
778 }
779 
780 //  return terrain that object is currently interacting with
checkBlocked(GameObject * obj,int16 mapNum,const TilePoint & loc,GameObject ** blockResultObj)781 int16 checkBlocked(
782     GameObject *obj,
783     int16 mapNum,
784     const TilePoint &loc,
785     GameObject **blockResultObj) {
786 	ProtoObj        *proto = obj->proto();
787 	uint8           height = proto->height;
788 	int32           terrain;
789 	GameObject      *blockObj;
790 	GameWorld       *world;
791 
792 	if (blockResultObj) *blockResultObj = NULL;
793 
794 
795 	//  check to make sure the actor recognizes terrain
796 	if (!isActor(obj) || !((Actor *) obj)->hasEffect(actorNoncorporeal)) {
797 		TilePoint       testLoc = loc;
798 
799 		testLoc.z = MAX<int16>(loc.z, 8);
800 
801 		terrain = volumeTerrain(mapNum,
802 		                        testLoc,
803 		                        proto->crossSection,
804 		                        height);
805 
806 		//  Check for intersection with a wall or obstacle
807 		if (terrain & terrainRaised) return blockageTerrain;
808 	}
809 
810 	//  See if object collided with an object
811 	world = (GameWorld *)GameObject::objectAddress(mapList[mapNum].worldID);
812 	blockObj = objectCollision(obj, world, loc);
813 	if (blockObj) {
814 		if (blockResultObj) *blockResultObj = blockObj;
815 		return blockageObject;
816 	}
817 
818 	return blockageNone;
819 }
820 
821 //  return terrain that object is currently interacting with
checkBlocked(GameObject * obj,const TilePoint & loc,GameObject ** blockResultObj)822 int16 checkBlocked(
823     GameObject *obj,
824     const TilePoint &loc,
825     GameObject **blockResultObj) {
826 	return checkBlocked(obj, obj->getMapNum(), loc, blockResultObj);
827 }
828 
829 //  same as checkBlocked() above but includes additional "walking off
830 //  cliff" check
checkWalkable(GameObject * obj,const TilePoint & loc,GameObject ** blockResultObj)831 int16 checkWalkable(
832     GameObject *obj,
833     const TilePoint &loc,
834     GameObject **blockResultObj) {
835 	int16               result;
836 	int16               supportHeight;
837 	StandingTileInfo    sti;
838 
839 	if ((result = checkBlocked(obj, loc, blockResultObj)) != blockageNone)
840 		return result;
841 
842 	supportHeight = tileSlopeHeight(loc, obj, &sti);
843 
844 	if (supportHeight < loc.z - kMaxStepHeight * 4)
845 		return blockageTerrain;
846 
847 	if (sti.surfaceTile != NULL) {
848 		int16               subTileU,
849 		                    subTileV,
850 		                    mask;
851 
852 		subTileU = (loc.u & kTileUVMask) >> kSubTileShift;
853 		subTileV = (loc.v & kTileUVMask) >> kSubTileShift;
854 		mask = 1 << ((subTileU << kSubTileShift) + subTileV);
855 
856 		//  If the suporting subtile is funiture consider this blocked
857 		if (sti.surfaceTile->attrs.testTerrain(mask) & terrainFurniture)
858 			return blockageTerrain;
859 	}
860 
861 	return blockageNone;
862 }
863 
864 //  return terrain that object is currently interacting with
checkContact(GameObject * obj,const TilePoint & loc,GameObject ** blockResultObj)865 int16 checkContact(
866     GameObject *obj,
867     const TilePoint &loc,
868     GameObject **blockResultObj) {
869 	int16           mapNum = obj->getMapNum();
870 	ProtoObj        *proto = obj->proto();
871 	int32           terrain;
872 	GameObject      *blockObj;
873 	GameWorld       *world;
874 
875 	if (blockResultObj) *blockResultObj = NULL;
876 
877 	terrain = volumeTerrain(mapNum,
878 	                        loc,
879 	                        proto->crossSection,
880 	                        proto->height);
881 
882 	//  Check for intersection with a wall or obstacle
883 	if (terrain & terrainRaised) return blockageTerrain;
884 
885 	//  Check for intersection with slope of the terrain.
886 	if (((terrain & terrainSurface)
887 	        &&  loc.z <= tileSlopeHeight(loc, obj))
888 	        || (!(terrain & terrainWater)
889 	            &&  loc.z <= 0))
890 		return blockageTerrain;
891 
892 	//  See if object collided with an object
893 	world = (GameWorld *)GameObject::objectAddress(mapList[mapNum].worldID);
894 	blockObj = objectCollision(obj, world, loc);
895 	if (blockObj) {
896 		if (blockResultObj) *blockResultObj = blockObj;
897 		return blockageObject;
898 	}
899 
900 	return blockageNone;
901 }
902 
903 } // end of namespace Saga2
904