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