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  * along 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 
23 #include "twine/scene/collision.h"
24 #include "common/debug.h"
25 #include "common/memstream.h"
26 #include "common/util.h"
27 #include "twine/debugger/debug_scene.h"
28 #include "twine/renderer/renderer.h"
29 #include "twine/resources/resources.h"
30 #include "twine/scene/actor.h"
31 #include "twine/scene/animations.h"
32 #include "twine/scene/extra.h"
33 #include "twine/scene/grid.h"
34 #include "twine/scene/movements.h"
35 #include "twine/scene/scene.h"
36 #include "twine/shared.h"
37 #include "twine/twine.h"
38 
39 namespace TwinE {
40 
Collision(TwinEEngine * engine)41 Collision::Collision(TwinEEngine *engine) : _engine(engine) {
42 }
43 
standingOnActor(int32 actorIdx1,int32 actorIdx2) const44 bool Collision::standingOnActor(int32 actorIdx1, int32 actorIdx2) const {
45 	const ActorStruct *actor1 = _engine->_scene->getActor(actorIdx1);
46 	const ActorStruct *actor2 = _engine->_scene->getActor(actorIdx2);
47 
48 	const IVec3 &processActor = _engine->_movements->_processActor;
49 	const IVec3 &mins1 = processActor + actor1->_boudingBox.mins;
50 	const IVec3 &maxs1 = processActor + actor1->_boudingBox.maxs;
51 
52 	const IVec3 &mins2 = actor2->pos() + actor2->_boudingBox.mins;
53 	const IVec3 &maxs2 = actor2->pos() + actor2->_boudingBox.maxs;
54 
55 	if (mins1.x >= maxs2.x) {
56 		return false;
57 	}
58 
59 	if (maxs1.x <= mins2.x) {
60 		return false;
61 	}
62 
63 	if (mins1.y > (maxs2.y + 1)) {
64 		return false;
65 	}
66 
67 	if (mins1.y <= (maxs2.y - BRICK_HEIGHT)) {
68 		return false;
69 	}
70 
71 	if (maxs1.y <= mins2.y) {
72 		return false;
73 	}
74 
75 	if (mins1.z >= maxs2.z) {
76 		return false;
77 	}
78 
79 	if (maxs1.z <= mins2.z) {
80 		return false;
81 	}
82 
83 	return true;
84 }
85 
getAverageValue(int32 start,int32 end,int32 maxDelay,int32 delay) const86 int32 Collision::getAverageValue(int32 start, int32 end, int32 maxDelay, int32 delay) const {
87 	if (delay <= 0) {
88 		return start;
89 	}
90 
91 	if (delay >= maxDelay) {
92 		return end;
93 	}
94 
95 	return (((end - start) * delay) / maxDelay) + start;
96 }
97 
reajustActorPosition(ShapeType brickShape)98 void Collision::reajustActorPosition(ShapeType brickShape) {
99 	if (brickShape == ShapeType::kNone) {
100 		return;
101 	}
102 
103 	const int32 brkX = (_collision.x * BRICK_SIZE) - BRICK_HEIGHT;
104 	const int32 brkY = _collision.y * BRICK_HEIGHT;
105 	const int32 brkZ = (_collision.z * BRICK_SIZE) - BRICK_HEIGHT;
106 
107 	IVec3 &processActor = _engine->_movements->_processActor;
108 
109 	// double-side stairs
110 	if (brickShape >= ShapeType::kDoubleSideStairsTop1 && brickShape <= ShapeType::kDoubleSideStairsRight2) {
111 		switch (brickShape) {
112 		case ShapeType::kDoubleSideStairsTop1:
113 			if (processActor.z - _collision.z <= processActor.x - _collision.x) {
114 				brickShape = ShapeType::kStairsTopLeft;
115 			} else {
116 				brickShape = ShapeType::kStairsTopRight;
117 			}
118 			break;
119 		case ShapeType::kDoubleSideStairsBottom1:
120 			if (processActor.z - _collision.z <= processActor.x - _collision.x) {
121 				brickShape = ShapeType::kStairsBottomLeft;
122 			} else {
123 				brickShape = ShapeType::kStairsBottomRight;
124 			}
125 			break;
126 		case ShapeType::kDoubleSideStairsLeft1:
127 			if (BRICK_SIZE - processActor.x - _collision.x <= processActor.z - _collision.z) {
128 				brickShape = ShapeType::kStairsTopLeft;
129 			} else {
130 				brickShape = ShapeType::kStairsBottomLeft;
131 			}
132 			break;
133 		case ShapeType::kDoubleSideStairsRight1:
134 			if (BRICK_SIZE - processActor.x - _collision.x <= processActor.z - _collision.z) {
135 				brickShape = ShapeType::kStairsTopRight;
136 			} else {
137 				brickShape = ShapeType::kStairsBottomRight;
138 			}
139 			break;
140 		case ShapeType::kDoubleSideStairsTop2:
141 			if (processActor.x - _collision.x >= processActor.z - _collision.z) {
142 				brickShape = ShapeType::kStairsTopRight;
143 			} else {
144 				brickShape = ShapeType::kStairsTopLeft;
145 			}
146 			break;
147 		case ShapeType::kDoubleSideStairsBottom2:
148 			if (processActor.z - _collision.z <= processActor.x - _collision.x) {
149 				brickShape = ShapeType::kStairsBottomRight;
150 			} else {
151 				brickShape = ShapeType::kStairsBottomLeft;
152 			}
153 			break;
154 		case ShapeType::kDoubleSideStairsLeft2:
155 			if (BRICK_SIZE - processActor.x - _collision.x <= processActor.z - _collision.z) {
156 				brickShape = ShapeType::kStairsBottomLeft;
157 			} else {
158 				brickShape = ShapeType::kStairsTopLeft;
159 			}
160 			break;
161 		case ShapeType::kDoubleSideStairsRight2:
162 			if (BRICK_SIZE - processActor.x - _collision.x <= processActor.z - _collision.z) {
163 				brickShape = ShapeType::kStairsBottomRight;
164 			} else {
165 				brickShape = ShapeType::kStairsTopRight;
166 			}
167 			break;
168 		default:
169 			if (_engine->_cfgfile.Debug) {
170 				debug("Brick Shape %d unsupported", (int)brickShape);
171 			}
172 			break;
173 		}
174 	}
175 
176 	if (brickShape >= ShapeType::kStairsTopLeft && brickShape <= ShapeType::kStairsBottomRight) {
177 		switch (brickShape) {
178 		case ShapeType::kStairsTopLeft:
179 			processActor.y = brkY + getAverageValue(0, BRICK_HEIGHT, BRICK_SIZE, processActor.x - brkX);
180 			break;
181 		case ShapeType::kStairsTopRight:
182 			processActor.y = brkY + getAverageValue(0, BRICK_HEIGHT, BRICK_SIZE, processActor.z - brkZ);
183 			break;
184 		case ShapeType::kStairsBottomLeft:
185 			processActor.y = brkY + getAverageValue(BRICK_HEIGHT, 0, BRICK_SIZE, processActor.z - brkZ);
186 			break;
187 		case ShapeType::kStairsBottomRight:
188 			processActor.y = brkY + getAverageValue(BRICK_HEIGHT, 0, BRICK_SIZE, processActor.x - brkX);
189 			break;
190 		default:
191 			break;
192 		}
193 	}
194 }
195 
handlePushing(const IVec3 & minsTest,const IVec3 & maxsTest,const ActorStruct * actor,ActorStruct * actorTest)196 void Collision::handlePushing(const IVec3 &minsTest, const IVec3 &maxsTest, const ActorStruct *actor, ActorStruct *actorTest) {
197 	IVec3 &processActor = _engine->_movements->_processActor;
198 	const IVec3 &previousActor = _engine->_movements->_previousActor;
199 
200 	const int32 newAngle = _engine->_movements->getAngleAndSetTargetActorDistance(processActor, actorTest->pos());
201 
202 	if (actorTest->_staticFlags.bCanBePushed && !actor->_staticFlags.bCanBePushed) {
203 		actorTest->_lastPos.y = 0;
204 
205 		if (actorTest->_staticFlags.bUseMiniZv) {
206 			if (newAngle >= ANGLE_45 && newAngle < ANGLE_135 && actor->_angle > ANGLE_45 && actor->_angle < ANGLE_135) {
207 				actorTest->_lastPos.x = 192;
208 			}
209 			if (newAngle >= ANGLE_135 && newAngle < ANGLE_225 && actor->_angle > ANGLE_135 && actor->_angle < ANGLE_225) {
210 				actorTest->_lastPos.z = -64;
211 			}
212 			if (newAngle >= ANGLE_225 && newAngle < ANGLE_315 && actor->_angle > ANGLE_225 && actor->_angle < ANGLE_315) {
213 				actorTest->_lastPos.x = -64;
214 			}
215 			if ((newAngle >= ANGLE_315 || newAngle < ANGLE_45) && (actor->_angle > ANGLE_315 || actor->_angle < ANGLE_45)) {
216 				actorTest->_lastPos.z = 192;
217 			}
218 		} else {
219 			actorTest->_lastPos.x = processActor.x - actor->_collisionPos.x;
220 			actorTest->_lastPos.z = processActor.z - actor->_collisionPos.z;
221 		}
222 	}
223 
224 	if ((actorTest->_boudingBox.maxs.x - actorTest->_boudingBox.mins.x == actorTest->_boudingBox.maxs.z - actorTest->_boudingBox.mins.z) &&
225 		(actor->_boudingBox.maxs.x - actor->_boudingBox.mins.x == actor->_boudingBox.maxs.z - actor->_boudingBox.mins.z)) {
226 		if (newAngle < ANGLE_135) {
227 			processActor.x = minsTest.x - actor->_boudingBox.maxs.x;
228 		}
229 		if (newAngle >= ANGLE_135 && newAngle < ANGLE_225) {
230 			processActor.z = maxsTest.z - actor->_boudingBox.mins.z;
231 		}
232 		if (newAngle >= ANGLE_225 && newAngle < ANGLE_315) {
233 			processActor.x = maxsTest.x - actor->_boudingBox.mins.x;
234 		}
235 		if (newAngle >= ANGLE_315 || (newAngle < ANGLE_315 && newAngle < ANGLE_45)) {
236 			processActor.z = minsTest.z - actor->_boudingBox.maxs.z;
237 		}
238 	} else if (!actor->_dynamicFlags.bIsFalling) {
239 		processActor = previousActor;
240 	}
241 }
242 
checkCollisionWithActors(int32 actorIdx)243 int32 Collision::checkCollisionWithActors(int32 actorIdx) {
244 	ActorStruct *actor = _engine->_scene->getActor(actorIdx);
245 
246 	IVec3 &processActor = _engine->_movements->_processActor;
247 	IVec3 mins = processActor + actor->_boudingBox.mins;
248 	IVec3 maxs = processActor + actor->_boudingBox.maxs;
249 
250 	actor->_collision = -1;
251 
252 	for (int32 a = 0; a < _engine->_scene->_sceneNumActors; a++) {
253 		ActorStruct *actorTest = _engine->_scene->getActor(a);
254 
255 		// avoid current processed actor
256 		if (a != actorIdx && actorTest->_entity != -1 && !actor->_staticFlags.bComputeLowCollision && actorTest->_standOn != actorIdx) {
257 			const IVec3 &minsTest = actorTest->pos() + actorTest->_boudingBox.mins;
258 			const IVec3 &maxsTest = actorTest->pos() + actorTest->_boudingBox.maxs;
259 
260 			if (mins.x < maxsTest.x && maxs.x > minsTest.x && mins.y < maxsTest.y && maxs.y > minsTest.y && mins.z < maxsTest.z && maxs.z > minsTest.z) {
261 				actor->_collision = a; // mark as collision with actor a
262 
263 				if (actorTest->_staticFlags.bIsCarrierActor) {
264 					if (actor->_dynamicFlags.bIsFalling || standingOnActor(actorIdx, a)) {
265 						processActor.y = maxsTest.y - actor->_boudingBox.mins.y + 1;
266 						actor->_standOn = a;
267 					} else {
268 						handlePushing(minsTest, maxsTest, actor, actorTest);
269 					}
270 				} else {
271 					if (standingOnActor(actorIdx, a)) {
272 						_engine->_actor->hitActor(actorIdx, a, 1, -1);
273 					}
274 					handlePushing(minsTest, maxsTest, actor, actorTest);
275 				}
276 			}
277 		}
278 	}
279 
280 	if (actor->_dynamicFlags.bIsHitting) {
281 		const IVec3 &destPos = _engine->_movements->rotateActor(0, 200, actor->_angle);
282 		mins = processActor + actor->_boudingBox.mins;
283 		mins.x += destPos.x;
284 		mins.z += destPos.z;
285 
286 		maxs = processActor + actor->_boudingBox.maxs;
287 		maxs.x += destPos.x;
288 		maxs.z += destPos.z;
289 
290 		for (int32 a = 0; a < _engine->_scene->_sceneNumActors; a++) {
291 			const ActorStruct *actorTest = _engine->_scene->getActor(a);
292 
293 			// avoid current processed actor
294 			if (a != actorIdx && actorTest->_entity != -1 && !actorTest->_staticFlags.bIsHidden && actorTest->_standOn != actorIdx) {
295 				const IVec3 minsTest = actorTest->pos() + actorTest->_boudingBox.mins;
296 				const IVec3 maxsTest = actorTest->pos() + actorTest->_boudingBox.maxs;
297 				if (mins.x < maxsTest.x && maxs.x > minsTest.x && mins.y < maxsTest.y && maxs.y > minsTest.y && mins.z < maxsTest.z && maxs.z > minsTest.z) {
298 					_engine->_actor->hitActor(actorIdx, a, actor->_strengthOfHit, actor->_angle + ANGLE_180);
299 					actor->_dynamicFlags.bIsHitting = 0;
300 				}
301 			}
302 		}
303 	}
304 
305 	return actor->_collision;
306 }
307 
checkHeroCollisionWithBricks(int32 x,int32 y,int32 z,int32 damageMask)308 void Collision::checkHeroCollisionWithBricks(int32 x, int32 y, int32 z, int32 damageMask) {
309 	IVec3 &processActor = _engine->_movements->_processActor;
310 	IVec3 &previousActor = _engine->_movements->_previousActor;
311 	ShapeType brickShape = _engine->_grid->getBrickShape(processActor);
312 
313 	processActor.x += x;
314 	processActor.y += y;
315 	processActor.z += z;
316 
317 	if (processActor.x >= 0 && processActor.z >= 0 && processActor.x <= 0x7E00 && processActor.z <= 0x7E00) { // SCENE_SIZE_MAX
318 		const BoundingBox &bbox = _engine->_actor->_processActorPtr->_boudingBox;
319 		reajustActorPosition(brickShape);
320 		brickShape = _engine->_grid->getBrickShapeFull(processActor, bbox.maxs.y);
321 
322 		if (brickShape == ShapeType::kSolid) {
323 			_causeActorDamage |= damageMask;
324 			brickShape = _engine->_grid->getBrickShapeFull(processActor.x, processActor.y, previousActor.z + z, bbox.maxs.y);
325 
326 			if (brickShape == ShapeType::kSolid) {
327 				brickShape = _engine->_grid->getBrickShapeFull(x + previousActor.x, processActor.y, processActor.z, bbox.maxs.y);
328 
329 				if (brickShape != ShapeType::kSolid) {
330 					_processCollision.x = previousActor.x;
331 				}
332 			} else {
333 				_processCollision.z = previousActor.z;
334 			}
335 		}
336 	}
337 
338 	processActor = _processCollision;
339 }
340 
checkActorCollisionWithBricks(int32 x,int32 y,int32 z,int32 damageMask)341 void Collision::checkActorCollisionWithBricks(int32 x, int32 y, int32 z, int32 damageMask) {
342 	IVec3 &processActor = _engine->_movements->_processActor;
343 	IVec3 &previousActor = _engine->_movements->_previousActor;
344 	ShapeType brickShape = _engine->_grid->getBrickShape(processActor);
345 
346 	processActor.x += x;
347 	processActor.y += y;
348 	processActor.z += z;
349 
350 	if (processActor.x >= 0 && processActor.z >= 0 && processActor.x <= 0x7E00 && processActor.z <= 0x7E00) { // SCENE_SIZE_MAX
351 		reajustActorPosition(brickShape);
352 		brickShape = _engine->_grid->getBrickShape(processActor);
353 
354 		if (brickShape == ShapeType::kSolid) {
355 			_causeActorDamage |= damageMask;
356 			brickShape = _engine->_grid->getBrickShape(processActor.x, processActor.y, previousActor.z + z);
357 
358 			if (brickShape == ShapeType::kSolid) {
359 				brickShape = _engine->_grid->getBrickShape(x + previousActor.x, processActor.y, processActor.z);
360 
361 				if (brickShape != ShapeType::kSolid) {
362 					_processCollision.x = previousActor.x;
363 				}
364 			} else {
365 				_processCollision.z = previousActor.z;
366 			}
367 		}
368 	}
369 
370 	processActor = _processCollision;
371 }
372 
stopFalling()373 void Collision::stopFalling() { // ReceptionObj()
374 	if (IS_HERO(_engine->_animations->_currentlyProcessedActorIdx)) {
375 		const IVec3 &processActor = _engine->_movements->_processActor;
376 		const int32 fall = _engine->_scene->_heroYBeforeFall - processActor.y;
377 
378 		if (fall >= BRICK_HEIGHT * 8) {
379 			const IVec3 &actorPos = _engine->_actor->_processActorPtr->pos();
380 			_engine->_extra->addExtraSpecial(actorPos.x, actorPos.y + 1000, actorPos.z, ExtraSpecialType::kHitStars);
381 			if (fall >= BRICK_HEIGHT * 16) {
382 				_engine->_actor->_processActorPtr->setLife(0);
383 			} else {
384 				_engine->_actor->_processActorPtr->addLife(-1);
385 			}
386 			_engine->_animations->initAnim(AnimationTypes::kLandingHit, AnimType::kAnimationAllThen, AnimationTypes::kStanding, _engine->_animations->_currentlyProcessedActorIdx);
387 		} else if (fall > 10) {
388 			_engine->_animations->initAnim(AnimationTypes::kLanding, AnimType::kAnimationAllThen, AnimationTypes::kStanding, _engine->_animations->_currentlyProcessedActorIdx);
389 		} else {
390 			_engine->_animations->initAnim(AnimationTypes::kStanding, AnimType::kAnimationTypeLoop, AnimationTypes::kStanding, _engine->_animations->_currentlyProcessedActorIdx);
391 		}
392 
393 		_engine->_scene->_heroYBeforeFall = 0;
394 	} else {
395 		_engine->_animations->initAnim(AnimationTypes::kLanding, AnimType::kAnimationAllThen, _engine->_actor->_processActorPtr->_animExtra, _engine->_animations->_currentlyProcessedActorIdx);
396 	}
397 
398 	_engine->_actor->_processActorPtr->_dynamicFlags.bIsFalling = 0;
399 }
400 
checkExtraCollisionWithActors(ExtraListStruct * extra,int32 actorIdx)401 int32 Collision::checkExtraCollisionWithActors(ExtraListStruct *extra, int32 actorIdx) {
402 	const BoundingBox *bbox = _engine->_resources->_spriteBoundingBox.bbox(extra->info0);
403 	const IVec3 mins = bbox->mins + extra->pos;
404 	const IVec3 maxs = bbox->maxs + extra->pos;
405 
406 	for (int32 a = 0; a < _engine->_scene->_sceneNumActors; a++) {
407 		const ActorStruct *actorTest = _engine->_scene->getActor(a);
408 
409 		if (a != actorIdx && actorTest->_entity != -1) {
410 			const IVec3 minsTest = actorTest->pos() + actorTest->_boudingBox.mins;
411 			const IVec3 maxsTest = actorTest->pos() + actorTest->_boudingBox.maxs;
412 
413 			if (mins.x < maxsTest.x && maxs.x > minsTest.x && mins.y < maxsTest.y && maxs.y > minsTest.y && mins.z < maxsTest.z && maxs.z > minsTest.z) {
414 				if (extra->strengthOfHit != 0) {
415 					_engine->_actor->hitActor(actorIdx, a, extra->strengthOfHit, -1);
416 				}
417 
418 				return a;
419 			}
420 		}
421 	}
422 
423 	return -1;
424 }
425 
checkExtraCollisionWithBricks(int32 x,int32 y,int32 z,const IVec3 & oldPos)426 bool Collision::checkExtraCollisionWithBricks(int32 x, int32 y, int32 z, const IVec3 &oldPos) {
427 	if (_engine->_grid->getBrickShape(oldPos) != ShapeType::kNone) {
428 		return true;
429 	}
430 
431 	const int32 averageX = ABS(x + oldPos.x) / 2;
432 	const int32 averageY = ABS(y + oldPos.y) / 2;
433 	const int32 averageZ = ABS(z + oldPos.z) / 2;
434 
435 	if (_engine->_grid->getBrickShape(averageX, averageY, averageZ) != ShapeType::kNone) {
436 		return true;
437 	}
438 
439 	if (_engine->_grid->getBrickShape(ABS(oldPos.x + averageX) / 2, ABS(oldPos.y + averageY) / 2, ABS(oldPos.z + averageZ) / 2) != ShapeType::kNone) {
440 		return true;
441 	}
442 
443 	if (_engine->_grid->getBrickShape(ABS(x + averageX) / 2, ABS(y + averageY) / 2, ABS(z + averageZ) / 2) != ShapeType::kNone) {
444 		return true;
445 	}
446 
447 	return false;
448 }
449 
checkExtraCollisionWithExtra(ExtraListStruct * extra,int32 extraIdx) const450 int32 Collision::checkExtraCollisionWithExtra(ExtraListStruct *extra, int32 extraIdx) const {
451 	int32 index = extra->info0;
452 	const BoundingBox *bbox = _engine->_resources->_spriteBoundingBox.bbox(index);
453 	const IVec3 mins = bbox->mins + extra->pos;
454 	const IVec3 maxs = bbox->maxs + extra->pos;
455 
456 	for (int32 i = 0; i < EXTRA_MAX_ENTRIES; i++) {
457 		const ExtraListStruct *extraTest = &_engine->_extra->_extraList[i];
458 		if (i != extraIdx && extraTest->info0 != -1) {
459 			const BoundingBox *testbbox = _engine->_resources->_spriteBoundingBox.bbox(++index);
460 			const IVec3 minsTest = testbbox->mins + extraTest->pos;
461 			const IVec3 maxsTest = testbbox->maxs + extraTest->pos;
462 
463 			if (mins.x >= minsTest.x) {
464 				continue;
465 			}
466 			if (mins.x < maxsTest.x && maxs.x > minsTest.x && mins.y < maxsTest.y && maxs.y > minsTest.y && mins.z < maxsTest.z && maxs.z > minsTest.z) {
467 				return i;
468 			}
469 		}
470 	}
471 
472 	return -1;
473 }
474 
475 } // namespace TwinE
476