1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3#include <base/math.h>
4#include <base/system.h>
5#include <base/vmath.h>
6
7#include <antibot/antibot_data.h>
8
9#include <cmath>
10#include <engine/map.h>
11
12#include <game/collision.h>
13#include <game/layers.h>
14#include <game/mapitems.h>
15
16#include <engine/shared/config.h>
17
18vec2 ClampVel(int MoveRestriction, vec2 Vel)
19{
20 if(Vel.x > 0 && (MoveRestriction & CANTMOVE_RIGHT))
21 {
22 Vel.x = 0;
23 }
24 if(Vel.x < 0 && (MoveRestriction & CANTMOVE_LEFT))
25 {
26 Vel.x = 0;
27 }
28 if(Vel.y > 0 && (MoveRestriction & CANTMOVE_DOWN))
29 {
30 Vel.y = 0;
31 }
32 if(Vel.y < 0 && (MoveRestriction & CANTMOVE_UP))
33 {
34 Vel.y = 0;
35 }
36 return Vel;
37}
38
39CCollision::CCollision()
40{
41 m_pTiles = 0;
42 m_Width = 0;
43 m_Height = 0;
44 m_pLayers = 0;
45
46 m_pTele = 0;
47 m_pSpeedup = 0;
48 m_pFront = 0;
49 m_pSwitch = 0;
50 m_pDoor = 0;
51 m_pTune = 0;
52}
53
54CCollision::~CCollision()
55{
56 Dest();
57}
58
59void CCollision::Init(class CLayers *pLayers)
60{
61 Dest();
62 m_HighestSwitchNumber = 0;
63 m_pLayers = pLayers;
64 m_Width = m_pLayers->GameLayer()->m_Width;
65 m_Height = m_pLayers->GameLayer()->m_Height;
66 m_pTiles = static_cast<CTile *>(m_pLayers->Map()->GetData(Index: m_pLayers->GameLayer()->m_Data));
67
68 if(m_pLayers->TeleLayer())
69 {
70 unsigned int Size = m_pLayers->Map()->GetDataSize(Index: m_pLayers->TeleLayer()->m_Tele);
71 if(Size >= (size_t)m_Width * m_Height * sizeof(CTeleTile))
72 m_pTele = static_cast<CTeleTile *>(m_pLayers->Map()->GetData(Index: m_pLayers->TeleLayer()->m_Tele));
73 }
74
75 if(m_pLayers->SpeedupLayer())
76 {
77 unsigned int Size = m_pLayers->Map()->GetDataSize(Index: m_pLayers->SpeedupLayer()->m_Speedup);
78 if(Size >= (size_t)m_Width * m_Height * sizeof(CSpeedupTile))
79 m_pSpeedup = static_cast<CSpeedupTile *>(m_pLayers->Map()->GetData(Index: m_pLayers->SpeedupLayer()->m_Speedup));
80 }
81
82 if(m_pLayers->SwitchLayer())
83 {
84 unsigned int Size = m_pLayers->Map()->GetDataSize(Index: m_pLayers->SwitchLayer()->m_Switch);
85 if(Size >= (size_t)m_Width * m_Height * sizeof(CSwitchTile))
86 m_pSwitch = static_cast<CSwitchTile *>(m_pLayers->Map()->GetData(Index: m_pLayers->SwitchLayer()->m_Switch));
87
88 m_pDoor = new CDoorTile[m_Width * m_Height];
89 mem_zero(block: m_pDoor, size: (size_t)m_Width * m_Height * sizeof(CDoorTile));
90 }
91 else
92 {
93 m_pDoor = 0;
94 }
95
96 if(m_pLayers->TuneLayer())
97 {
98 unsigned int Size = m_pLayers->Map()->GetDataSize(Index: m_pLayers->TuneLayer()->m_Tune);
99 if(Size >= (size_t)m_Width * m_Height * sizeof(CTuneTile))
100 m_pTune = static_cast<CTuneTile *>(m_pLayers->Map()->GetData(Index: m_pLayers->TuneLayer()->m_Tune));
101 }
102
103 if(m_pLayers->FrontLayer())
104 {
105 unsigned int Size = m_pLayers->Map()->GetDataSize(Index: m_pLayers->FrontLayer()->m_Front);
106 if(Size >= (size_t)m_Width * m_Height * sizeof(CTile))
107 m_pFront = static_cast<CTile *>(m_pLayers->Map()->GetData(Index: m_pLayers->FrontLayer()->m_Front));
108 }
109
110 for(int i = 0; i < m_Width * m_Height; i++)
111 {
112 int Index;
113 if(m_pSwitch)
114 {
115 if(m_pSwitch[i].m_Number > m_HighestSwitchNumber)
116 m_HighestSwitchNumber = m_pSwitch[i].m_Number;
117
118 if(m_pSwitch[i].m_Number)
119 m_pDoor[i].m_Number = m_pSwitch[i].m_Number;
120 else
121 m_pDoor[i].m_Number = 0;
122
123 Index = m_pSwitch[i].m_Type;
124
125 if(Index <= TILE_NPH_ENABLE)
126 {
127 if((Index >= TILE_JUMP && Index <= TILE_SUBTRACT_TIME) || Index == TILE_ALLOW_TELE_GUN || Index == TILE_ALLOW_BLUE_TELE_GUN)
128 m_pSwitch[i].m_Type = Index;
129 else
130 m_pSwitch[i].m_Type = 0;
131 }
132 }
133 }
134
135 m_TeleIns.clear();
136 m_TeleOuts.clear();
137 m_TeleCheckOuts.clear();
138 if(m_pTele)
139 {
140 for(int i = 0; i < m_Width * m_Height; i++)
141 {
142 int Number = TeleLayer()[i].m_Number;
143 int Type = TeleLayer()[i].m_Type;
144 if(Number > 0)
145 {
146 if(Type == TILE_TELEIN)
147 {
148 m_TeleIns[Number - 1].emplace_back(args: i % m_Width * 32.0f + 16.0f, args: i / m_Width * 32.0f + 16.0f);
149 }
150 else if(Type == TILE_TELEOUT)
151 {
152 m_TeleOuts[Number - 1].emplace_back(args: i % m_Width * 32.0f + 16.0f, args: i / m_Width * 32.0f + 16.0f);
153 }
154 else if(Type == TILE_TELECHECKOUT)
155 {
156 m_TeleCheckOuts[Number - 1].emplace_back(args: i % m_Width * 32.0f + 16.0f, args: i / m_Width * 32.0f + 16.0f);
157 }
158 }
159 }
160 }
161}
162
163void CCollision::FillAntibot(CAntibotMapData *pMapData)
164{
165 pMapData->m_Width = m_Width;
166 pMapData->m_Height = m_Height;
167 pMapData->m_pTiles = (unsigned char *)malloc(size: (size_t)m_Width * m_Height);
168 for(int i = 0; i < m_Width * m_Height; i++)
169 {
170 pMapData->m_pTiles[i] = 0;
171 if(m_pTiles[i].m_Index >= TILE_SOLID && m_pTiles[i].m_Index <= TILE_NOLASER)
172 {
173 pMapData->m_pTiles[i] = m_pTiles[i].m_Index;
174 }
175 }
176}
177
178enum
179{
180 MR_DIR_HERE = 0,
181 MR_DIR_RIGHT,
182 MR_DIR_DOWN,
183 MR_DIR_LEFT,
184 MR_DIR_UP,
185 NUM_MR_DIRS
186};
187
188static int GetMoveRestrictionsRaw(int Direction, int Tile, int Flags)
189{
190 Flags = Flags & (TILEFLAG_XFLIP | TILEFLAG_YFLIP | TILEFLAG_ROTATE);
191 switch(Tile)
192 {
193 case TILE_STOP:
194 switch(Flags)
195 {
196 case ROTATION_0: return CANTMOVE_DOWN;
197 case ROTATION_90: return CANTMOVE_LEFT;
198 case ROTATION_180: return CANTMOVE_UP;
199 case ROTATION_270: return CANTMOVE_RIGHT;
200
201 case TILEFLAG_YFLIP ^ ROTATION_0: return CANTMOVE_UP;
202 case TILEFLAG_YFLIP ^ ROTATION_90: return CANTMOVE_RIGHT;
203 case TILEFLAG_YFLIP ^ ROTATION_180: return CANTMOVE_DOWN;
204 case TILEFLAG_YFLIP ^ ROTATION_270: return CANTMOVE_LEFT;
205 }
206 break;
207 case TILE_STOPS:
208 switch(Flags)
209 {
210 case ROTATION_0:
211 case ROTATION_180:
212 case TILEFLAG_YFLIP ^ ROTATION_0:
213 case TILEFLAG_YFLIP ^ ROTATION_180:
214 return CANTMOVE_DOWN | CANTMOVE_UP;
215 case ROTATION_90:
216 case ROTATION_270:
217 case TILEFLAG_YFLIP ^ ROTATION_90:
218 case TILEFLAG_YFLIP ^ ROTATION_270:
219 return CANTMOVE_LEFT | CANTMOVE_RIGHT;
220 }
221 break;
222 case TILE_STOPA:
223 return CANTMOVE_LEFT | CANTMOVE_RIGHT | CANTMOVE_UP | CANTMOVE_DOWN;
224 }
225 return 0;
226}
227
228static int GetMoveRestrictionsMask(int Direction)
229{
230 switch(Direction)
231 {
232 case MR_DIR_HERE: return 0;
233 case MR_DIR_RIGHT: return CANTMOVE_RIGHT;
234 case MR_DIR_DOWN: return CANTMOVE_DOWN;
235 case MR_DIR_LEFT: return CANTMOVE_LEFT;
236 case MR_DIR_UP: return CANTMOVE_UP;
237 default: dbg_assert(false, "invalid dir");
238 }
239 return 0;
240}
241
242static int GetMoveRestrictions(int Direction, int Tile, int Flags)
243{
244 int Result = GetMoveRestrictionsRaw(Direction, Tile, Flags);
245 // Generally, stoppers only have an effect if they block us from moving
246 // *onto* them. The one exception is one-way blockers, they can also
247 // block us from moving if we're on top of them.
248 if(Direction == MR_DIR_HERE && Tile == TILE_STOP)
249 {
250 return Result;
251 }
252 return Result & GetMoveRestrictionsMask(Direction);
253}
254
255int CCollision::GetMoveRestrictions(CALLBACK_SWITCHACTIVE pfnSwitchActive, void *pUser, vec2 Pos, float Distance, int OverrideCenterTileIndex) const
256{
257 static const vec2 DIRECTIONS[NUM_MR_DIRS] =
258 {
259 vec2(0, 0),
260 vec2(1, 0),
261 vec2(0, 1),
262 vec2(-1, 0),
263 vec2(0, -1)};
264 dbg_assert(0.0f <= Distance && Distance <= 32.0f, "invalid distance");
265 int Restrictions = 0;
266 for(int d = 0; d < NUM_MR_DIRS; d++)
267 {
268 vec2 ModPos = Pos + DIRECTIONS[d] * Distance;
269 int ModMapIndex = GetPureMapIndex(Pos: ModPos);
270 if(d == MR_DIR_HERE && OverrideCenterTileIndex >= 0)
271 {
272 ModMapIndex = OverrideCenterTileIndex;
273 }
274 for(int Front = 0; Front < 2; Front++)
275 {
276 int Tile;
277 int Flags;
278 if(!Front)
279 {
280 Tile = GetTileIndex(Index: ModMapIndex);
281 Flags = GetTileFlags(Index: ModMapIndex);
282 }
283 else
284 {
285 Tile = GetFTileIndex(Index: ModMapIndex);
286 Flags = GetFTileFlags(Index: ModMapIndex);
287 }
288 Restrictions |= ::GetMoveRestrictions(Direction: d, Tile, Flags);
289 }
290 if(pfnSwitchActive)
291 {
292 int TeleNumber = GetDTileNumber(Index: ModMapIndex);
293 if(pfnSwitchActive(TeleNumber, pUser))
294 {
295 int Tile = GetDTileIndex(Index: ModMapIndex);
296 int Flags = GetDTileFlags(Index: ModMapIndex);
297 Restrictions |= ::GetMoveRestrictions(Direction: d, Tile, Flags);
298 }
299 }
300 }
301 return Restrictions;
302}
303
304int CCollision::GetTile(int x, int y) const
305{
306 if(!m_pTiles)
307 return 0;
308
309 int Nx = clamp(val: x / 32, lo: 0, hi: m_Width - 1);
310 int Ny = clamp(val: y / 32, lo: 0, hi: m_Height - 1);
311 int pos = Ny * m_Width + Nx;
312
313 if(m_pTiles[pos].m_Index >= TILE_SOLID && m_pTiles[pos].m_Index <= TILE_NOLASER)
314 return m_pTiles[pos].m_Index;
315 return 0;
316}
317
318// TODO: rewrite this smarter!
319int CCollision::IntersectLine(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision) const
320{
321 float Distance = distance(a: Pos0, b: Pos1);
322 int End(Distance + 1);
323 vec2 Last = Pos0;
324 for(int i = 0; i <= End; i++)
325 {
326 float a = i / (float)End;
327 vec2 Pos = mix(a: Pos0, b: Pos1, amount: a);
328 // Temporary position for checking collision
329 int ix = round_to_int(f: Pos.x);
330 int iy = round_to_int(f: Pos.y);
331
332 if(CheckPoint(x: ix, y: iy))
333 {
334 if(pOutCollision)
335 *pOutCollision = Pos;
336 if(pOutBeforeCollision)
337 *pOutBeforeCollision = Last;
338 return GetCollisionAt(x: ix, y: iy);
339 }
340
341 Last = Pos;
342 }
343 if(pOutCollision)
344 *pOutCollision = Pos1;
345 if(pOutBeforeCollision)
346 *pOutBeforeCollision = Pos1;
347 return 0;
348}
349
350int CCollision::IntersectLineTeleHook(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision, int *pTeleNr) const
351{
352 float Distance = distance(a: Pos0, b: Pos1);
353 int End(Distance + 1);
354 vec2 Last = Pos0;
355 int dx = 0, dy = 0; // Offset for checking the "through" tile
356 ThroughOffset(Pos0, Pos1, pOffsetX: &dx, pOffsetY: &dy);
357 for(int i = 0; i <= End; i++)
358 {
359 float a = i / (float)End;
360 vec2 Pos = mix(a: Pos0, b: Pos1, amount: a);
361 // Temporary position for checking collision
362 int ix = round_to_int(f: Pos.x);
363 int iy = round_to_int(f: Pos.y);
364
365 int Index = GetPureMapIndex(Pos);
366 if(pTeleNr)
367 {
368 if(g_Config.m_SvOldTeleportHook)
369 *pTeleNr = IsTeleport(Index);
370 else
371 *pTeleNr = IsTeleportHook(Index);
372 }
373 if(pTeleNr && *pTeleNr)
374 {
375 if(pOutCollision)
376 *pOutCollision = Pos;
377 if(pOutBeforeCollision)
378 *pOutBeforeCollision = Last;
379 return TILE_TELEINHOOK;
380 }
381
382 int hit = 0;
383 if(CheckPoint(x: ix, y: iy))
384 {
385 if(!IsThrough(x: ix, y: iy, xoff: dx, yoff: dy, pos0: Pos0, pos1: Pos1))
386 hit = GetCollisionAt(x: ix, y: iy);
387 }
388 else if(IsHookBlocker(x: ix, y: iy, pos0: Pos0, pos1: Pos1))
389 {
390 hit = TILE_NOHOOK;
391 }
392 if(hit)
393 {
394 if(pOutCollision)
395 *pOutCollision = Pos;
396 if(pOutBeforeCollision)
397 *pOutBeforeCollision = Last;
398 return hit;
399 }
400
401 Last = Pos;
402 }
403 if(pOutCollision)
404 *pOutCollision = Pos1;
405 if(pOutBeforeCollision)
406 *pOutBeforeCollision = Pos1;
407 return 0;
408}
409
410int CCollision::IntersectLineTeleWeapon(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision, int *pTeleNr) const
411{
412 float Distance = distance(a: Pos0, b: Pos1);
413 int End(Distance + 1);
414 vec2 Last = Pos0;
415 for(int i = 0; i <= End; i++)
416 {
417 float a = i / (float)End;
418 vec2 Pos = mix(a: Pos0, b: Pos1, amount: a);
419 // Temporary position for checking collision
420 int ix = round_to_int(f: Pos.x);
421 int iy = round_to_int(f: Pos.y);
422
423 int Index = GetPureMapIndex(Pos);
424 if(pTeleNr)
425 {
426 if(g_Config.m_SvOldTeleportWeapons)
427 *pTeleNr = IsTeleport(Index);
428 else
429 *pTeleNr = IsTeleportWeapon(Index);
430 }
431 if(pTeleNr && *pTeleNr)
432 {
433 if(pOutCollision)
434 *pOutCollision = Pos;
435 if(pOutBeforeCollision)
436 *pOutBeforeCollision = Last;
437 return TILE_TELEINWEAPON;
438 }
439
440 if(CheckPoint(x: ix, y: iy))
441 {
442 if(pOutCollision)
443 *pOutCollision = Pos;
444 if(pOutBeforeCollision)
445 *pOutBeforeCollision = Last;
446 return GetCollisionAt(x: ix, y: iy);
447 }
448
449 Last = Pos;
450 }
451 if(pOutCollision)
452 *pOutCollision = Pos1;
453 if(pOutBeforeCollision)
454 *pOutBeforeCollision = Pos1;
455 return 0;
456}
457
458// TODO: OPT: rewrite this smarter!
459void CCollision::MovePoint(vec2 *pInoutPos, vec2 *pInoutVel, float Elasticity, int *pBounces) const
460{
461 if(pBounces)
462 *pBounces = 0;
463
464 vec2 Pos = *pInoutPos;
465 vec2 Vel = *pInoutVel;
466 if(CheckPoint(Pos: Pos + Vel))
467 {
468 int Affected = 0;
469 if(CheckPoint(x: Pos.x + Vel.x, y: Pos.y))
470 {
471 pInoutVel->x *= -Elasticity;
472 if(pBounces)
473 (*pBounces)++;
474 Affected++;
475 }
476
477 if(CheckPoint(x: Pos.x, y: Pos.y + Vel.y))
478 {
479 pInoutVel->y *= -Elasticity;
480 if(pBounces)
481 (*pBounces)++;
482 Affected++;
483 }
484
485 if(Affected == 0)
486 {
487 pInoutVel->x *= -Elasticity;
488 pInoutVel->y *= -Elasticity;
489 }
490 }
491 else
492 {
493 *pInoutPos = Pos + Vel;
494 }
495}
496
497bool CCollision::TestBox(vec2 Pos, vec2 Size) const
498{
499 Size *= 0.5f;
500 if(CheckPoint(x: Pos.x - Size.x, y: Pos.y - Size.y))
501 return true;
502 if(CheckPoint(x: Pos.x + Size.x, y: Pos.y - Size.y))
503 return true;
504 if(CheckPoint(x: Pos.x - Size.x, y: Pos.y + Size.y))
505 return true;
506 if(CheckPoint(x: Pos.x + Size.x, y: Pos.y + Size.y))
507 return true;
508 return false;
509}
510
511void CCollision::MoveBox(vec2 *pInoutPos, vec2 *pInoutVel, vec2 Size, vec2 Elasticity, bool *pGrounded) const
512{
513 // do the move
514 vec2 Pos = *pInoutPos;
515 vec2 Vel = *pInoutVel;
516
517 float Distance = length(a: Vel);
518 int Max = (int)Distance;
519
520 if(Distance > 0.00001f)
521 {
522 float Fraction = 1.0f / (float)(Max + 1);
523 float ElasticityX = clamp(val: Elasticity.x, lo: -1.0f, hi: 1.0f);
524 float ElasticityY = clamp(val: Elasticity.y, lo: -1.0f, hi: 1.0f);
525
526 for(int i = 0; i <= Max; i++)
527 {
528 // Early break as optimization to stop checking for collisions for
529 // large distances after the obstacles we have already hit reduced
530 // our speed to exactly 0.
531 if(Vel == vec2(0, 0))
532 {
533 break;
534 }
535
536 vec2 NewPos = Pos + Vel * Fraction; // TODO: this row is not nice
537
538 // Fraction can be very small and thus the calculation has no effect, no
539 // reason to continue calculating.
540 if(NewPos == Pos)
541 {
542 break;
543 }
544
545 if(TestBox(Pos: vec2(NewPos.x, NewPos.y), Size))
546 {
547 int Hits = 0;
548
549 if(TestBox(Pos: vec2(Pos.x, NewPos.y), Size))
550 {
551 if(pGrounded && ElasticityY > 0 && Vel.y > 0)
552 *pGrounded = true;
553 NewPos.y = Pos.y;
554 Vel.y *= -ElasticityY;
555 Hits++;
556 }
557
558 if(TestBox(Pos: vec2(NewPos.x, Pos.y), Size))
559 {
560 NewPos.x = Pos.x;
561 Vel.x *= -ElasticityX;
562 Hits++;
563 }
564
565 // neither of the tests got a collision.
566 // this is a real _corner case_!
567 if(Hits == 0)
568 {
569 if(pGrounded && ElasticityY > 0 && Vel.y > 0)
570 *pGrounded = true;
571 NewPos.y = Pos.y;
572 Vel.y *= -ElasticityY;
573 NewPos.x = Pos.x;
574 Vel.x *= -ElasticityX;
575 }
576 }
577
578 Pos = NewPos;
579 }
580 }
581
582 *pInoutPos = Pos;
583 *pInoutVel = Vel;
584}
585
586// DDRace
587
588void CCollision::Dest()
589{
590 delete[] m_pDoor;
591 m_pTiles = 0;
592 m_Width = 0;
593 m_Height = 0;
594 m_pLayers = 0;
595 m_pTele = 0;
596 m_pSpeedup = 0;
597 m_pFront = 0;
598 m_pSwitch = 0;
599 m_pTune = 0;
600 m_pDoor = 0;
601}
602
603int CCollision::IsSolid(int x, int y) const
604{
605 int index = GetTile(x, y);
606 return index == TILE_SOLID || index == TILE_NOHOOK;
607}
608
609bool CCollision::IsThrough(int x, int y, int xoff, int yoff, vec2 pos0, vec2 pos1) const
610{
611 int pos = GetPureMapIndex(x, y);
612 if(m_pFront && (m_pFront[pos].m_Index == TILE_THROUGH_ALL || m_pFront[pos].m_Index == TILE_THROUGH_CUT))
613 return true;
614 if(m_pFront && m_pFront[pos].m_Index == TILE_THROUGH_DIR && ((m_pFront[pos].m_Flags == ROTATION_0 && pos0.y > pos1.y) || (m_pFront[pos].m_Flags == ROTATION_90 && pos0.x < pos1.x) || (m_pFront[pos].m_Flags == ROTATION_180 && pos0.y < pos1.y) || (m_pFront[pos].m_Flags == ROTATION_270 && pos0.x > pos1.x)))
615 return true;
616 int offpos = GetPureMapIndex(x: x + xoff, y: y + yoff);
617 return m_pTiles[offpos].m_Index == TILE_THROUGH || (m_pFront && m_pFront[offpos].m_Index == TILE_THROUGH);
618}
619
620bool CCollision::IsHookBlocker(int x, int y, vec2 pos0, vec2 pos1) const
621{
622 int pos = GetPureMapIndex(x, y);
623 if(m_pTiles[pos].m_Index == TILE_THROUGH_ALL || (m_pFront && m_pFront[pos].m_Index == TILE_THROUGH_ALL))
624 return true;
625 if(m_pTiles[pos].m_Index == TILE_THROUGH_DIR && ((m_pTiles[pos].m_Flags == ROTATION_0 && pos0.y < pos1.y) ||
626 (m_pTiles[pos].m_Flags == ROTATION_90 && pos0.x > pos1.x) ||
627 (m_pTiles[pos].m_Flags == ROTATION_180 && pos0.y > pos1.y) ||
628 (m_pTiles[pos].m_Flags == ROTATION_270 && pos0.x < pos1.x)))
629 return true;
630 if(m_pFront && m_pFront[pos].m_Index == TILE_THROUGH_DIR && ((m_pFront[pos].m_Flags == ROTATION_0 && pos0.y < pos1.y) || (m_pFront[pos].m_Flags == ROTATION_90 && pos0.x > pos1.x) || (m_pFront[pos].m_Flags == ROTATION_180 && pos0.y > pos1.y) || (m_pFront[pos].m_Flags == ROTATION_270 && pos0.x < pos1.x)))
631 return true;
632 return false;
633}
634
635int CCollision::IsWallJump(int Index) const
636{
637 if(Index < 0)
638 return 0;
639
640 return m_pTiles[Index].m_Index == TILE_WALLJUMP;
641}
642
643int CCollision::IsNoLaser(int x, int y) const
644{
645 return (CCollision::GetTile(x, y) == TILE_NOLASER);
646}
647
648int CCollision::IsFNoLaser(int x, int y) const
649{
650 return (CCollision::GetFTile(x, y) == TILE_NOLASER);
651}
652
653int CCollision::IsTeleport(int Index) const
654{
655 if(Index < 0 || !m_pTele)
656 return 0;
657
658 if(m_pTele[Index].m_Type == TILE_TELEIN)
659 return m_pTele[Index].m_Number;
660
661 return 0;
662}
663
664int CCollision::IsEvilTeleport(int Index) const
665{
666 if(Index < 0)
667 return 0;
668 if(!m_pTele)
669 return 0;
670
671 if(m_pTele[Index].m_Type == TILE_TELEINEVIL)
672 return m_pTele[Index].m_Number;
673
674 return 0;
675}
676
677bool CCollision::IsCheckTeleport(int Index) const
678{
679 if(Index < 0 || !m_pTele)
680 return false;
681 return m_pTele[Index].m_Type == TILE_TELECHECKIN;
682}
683
684bool CCollision::IsCheckEvilTeleport(int Index) const
685{
686 if(Index < 0 || !m_pTele)
687 return false;
688 return m_pTele[Index].m_Type == TILE_TELECHECKINEVIL;
689}
690
691int CCollision::IsTeleCheckpoint(int Index) const
692{
693 if(Index < 0)
694 return 0;
695
696 if(!m_pTele)
697 return 0;
698
699 if(m_pTele[Index].m_Type == TILE_TELECHECK)
700 return m_pTele[Index].m_Number;
701
702 return 0;
703}
704
705int CCollision::IsTeleportWeapon(int Index) const
706{
707 if(Index < 0 || !m_pTele)
708 return 0;
709
710 if(m_pTele[Index].m_Type == TILE_TELEINWEAPON)
711 return m_pTele[Index].m_Number;
712
713 return 0;
714}
715
716int CCollision::IsTeleportHook(int Index) const
717{
718 if(Index < 0 || !m_pTele)
719 return 0;
720
721 if(m_pTele[Index].m_Type == TILE_TELEINHOOK)
722 return m_pTele[Index].m_Number;
723
724 return 0;
725}
726
727int CCollision::IsSpeedup(int Index) const
728{
729 if(Index < 0 || !m_pSpeedup)
730 return 0;
731
732 if(m_pSpeedup[Index].m_Force > 0)
733 return Index;
734
735 return 0;
736}
737
738int CCollision::IsTune(int Index) const
739{
740 if(Index < 0 || !m_pTune)
741 return 0;
742
743 if(m_pTune[Index].m_Type)
744 return m_pTune[Index].m_Number;
745
746 return 0;
747}
748
749void CCollision::GetSpeedup(int Index, vec2 *pDir, int *pForce, int *pMaxSpeed) const
750{
751 if(Index < 0 || !m_pSpeedup)
752 return;
753 float Angle = m_pSpeedup[Index].m_Angle * (pi / 180.0f);
754 *pForce = m_pSpeedup[Index].m_Force;
755 *pDir = direction(angle: Angle);
756 if(pMaxSpeed)
757 *pMaxSpeed = m_pSpeedup[Index].m_MaxSpeed;
758}
759
760int CCollision::GetSwitchType(int Index) const
761{
762 if(Index < 0 || !m_pSwitch)
763 return 0;
764
765 if(m_pSwitch[Index].m_Type > 0)
766 return m_pSwitch[Index].m_Type;
767
768 return 0;
769}
770
771int CCollision::GetSwitchNumber(int Index) const
772{
773 if(Index < 0 || !m_pSwitch)
774 return 0;
775
776 if(m_pSwitch[Index].m_Type > 0 && m_pSwitch[Index].m_Number > 0)
777 return m_pSwitch[Index].m_Number;
778
779 return 0;
780}
781
782int CCollision::GetSwitchDelay(int Index) const
783{
784 if(Index < 0 || !m_pSwitch)
785 return 0;
786
787 if(m_pSwitch[Index].m_Type > 0)
788 return m_pSwitch[Index].m_Delay;
789
790 return 0;
791}
792
793int CCollision::IsMover(int x, int y, int *pFlags) const
794{
795 int Nx = clamp(val: x / 32, lo: 0, hi: m_Width - 1);
796 int Ny = clamp(val: y / 32, lo: 0, hi: m_Height - 1);
797 int Index = m_pTiles[Ny * m_Width + Nx].m_Index;
798 *pFlags = m_pTiles[Ny * m_Width + Nx].m_Flags;
799 if(Index < 0)
800 return 0;
801 if(Index == TILE_CP || Index == TILE_CP_F)
802 return Index;
803 else
804 return 0;
805}
806
807vec2 CCollision::CpSpeed(int Index, int Flags) const
808{
809 if(Index < 0)
810 return vec2(0, 0);
811 vec2 target;
812 if(Index == TILE_CP || Index == TILE_CP_F)
813 switch(Flags)
814 {
815 case ROTATION_0:
816 target.x = 0;
817 target.y = -4;
818 break;
819 case ROTATION_90:
820 target.x = 4;
821 target.y = 0;
822 break;
823 case ROTATION_180:
824 target.x = 0;
825 target.y = 4;
826 break;
827 case ROTATION_270:
828 target.x = -4;
829 target.y = 0;
830 break;
831 default:
832 target = vec2(0, 0);
833 break;
834 }
835 if(Index == TILE_CP_F)
836 target *= 4;
837 return target;
838}
839
840int CCollision::GetPureMapIndex(float x, float y) const
841{
842 int Nx = clamp(val: round_to_int(f: x) / 32, lo: 0, hi: m_Width - 1);
843 int Ny = clamp(val: round_to_int(f: y) / 32, lo: 0, hi: m_Height - 1);
844 return Ny * m_Width + Nx;
845}
846
847bool CCollision::TileExists(int Index) const
848{
849 if(Index < 0)
850 return false;
851
852 if((m_pTiles[Index].m_Index >= TILE_FREEZE && m_pTiles[Index].m_Index <= TILE_TELE_LASER_DISABLE) || (m_pTiles[Index].m_Index >= TILE_LFREEZE && m_pTiles[Index].m_Index <= TILE_LUNFREEZE))
853 return true;
854 if(m_pFront && ((m_pFront[Index].m_Index >= TILE_FREEZE && m_pFront[Index].m_Index <= TILE_TELE_LASER_DISABLE) || (m_pFront[Index].m_Index >= TILE_LFREEZE && m_pFront[Index].m_Index <= TILE_LUNFREEZE)))
855 return true;
856 if(m_pTele && (m_pTele[Index].m_Type == TILE_TELEIN || m_pTele[Index].m_Type == TILE_TELEINEVIL || m_pTele[Index].m_Type == TILE_TELECHECKINEVIL || m_pTele[Index].m_Type == TILE_TELECHECK || m_pTele[Index].m_Type == TILE_TELECHECKIN))
857 return true;
858 if(m_pSpeedup && m_pSpeedup[Index].m_Force > 0)
859 return true;
860 if(m_pDoor && m_pDoor[Index].m_Index)
861 return true;
862 if(m_pSwitch && m_pSwitch[Index].m_Type)
863 return true;
864 if(m_pTune && m_pTune[Index].m_Type)
865 return true;
866 return TileExistsNext(Index);
867}
868
869bool CCollision::TileExistsNext(int Index) const
870{
871 if(Index < 0)
872 return false;
873 int TileOnTheLeft = (Index - 1 > 0) ? Index - 1 : Index;
874 int TileOnTheRight = (Index + 1 < m_Width * m_Height) ? Index + 1 : Index;
875 int TileBelow = (Index + m_Width < m_Width * m_Height) ? Index + m_Width : Index;
876 int TileAbove = (Index - m_Width > 0) ? Index - m_Width : Index;
877
878 if((m_pTiles[TileOnTheRight].m_Index == TILE_STOP && m_pTiles[TileOnTheRight].m_Flags == ROTATION_270) || (m_pTiles[TileOnTheLeft].m_Index == TILE_STOP && m_pTiles[TileOnTheLeft].m_Flags == ROTATION_90))
879 return true;
880 if((m_pTiles[TileBelow].m_Index == TILE_STOP && m_pTiles[TileBelow].m_Flags == ROTATION_0) || (m_pTiles[TileAbove].m_Index == TILE_STOP && m_pTiles[TileAbove].m_Flags == ROTATION_180))
881 return true;
882 if(m_pTiles[TileOnTheRight].m_Index == TILE_STOPA || m_pTiles[TileOnTheLeft].m_Index == TILE_STOPA || ((m_pTiles[TileOnTheRight].m_Index == TILE_STOPS || m_pTiles[TileOnTheLeft].m_Index == TILE_STOPS)))
883 return true;
884 if(m_pTiles[TileBelow].m_Index == TILE_STOPA || m_pTiles[TileAbove].m_Index == TILE_STOPA || ((m_pTiles[TileBelow].m_Index == TILE_STOPS || m_pTiles[TileAbove].m_Index == TILE_STOPS) && m_pTiles[TileBelow].m_Flags | ROTATION_180 | ROTATION_0))
885 return true;
886 if(m_pFront)
887 {
888 if(m_pFront[TileOnTheRight].m_Index == TILE_STOPA || m_pFront[TileOnTheLeft].m_Index == TILE_STOPA || ((m_pFront[TileOnTheRight].m_Index == TILE_STOPS || m_pFront[TileOnTheLeft].m_Index == TILE_STOPS)))
889 return true;
890 if(m_pFront[TileBelow].m_Index == TILE_STOPA || m_pFront[TileAbove].m_Index == TILE_STOPA || ((m_pFront[TileBelow].m_Index == TILE_STOPS || m_pFront[TileAbove].m_Index == TILE_STOPS) && m_pFront[TileBelow].m_Flags | ROTATION_180 | ROTATION_0))
891 return true;
892 if((m_pFront[TileOnTheRight].m_Index == TILE_STOP && m_pFront[TileOnTheRight].m_Flags == ROTATION_270) || (m_pFront[TileOnTheLeft].m_Index == TILE_STOP && m_pFront[TileOnTheLeft].m_Flags == ROTATION_90))
893 return true;
894 if((m_pFront[TileBelow].m_Index == TILE_STOP && m_pFront[TileBelow].m_Flags == ROTATION_0) || (m_pFront[TileAbove].m_Index == TILE_STOP && m_pFront[TileAbove].m_Flags == ROTATION_180))
895 return true;
896 }
897 if(m_pDoor)
898 {
899 if(m_pDoor[TileOnTheRight].m_Index == TILE_STOPA || m_pDoor[TileOnTheLeft].m_Index == TILE_STOPA || ((m_pDoor[TileOnTheRight].m_Index == TILE_STOPS || m_pDoor[TileOnTheLeft].m_Index == TILE_STOPS)))
900 return true;
901 if(m_pDoor[TileBelow].m_Index == TILE_STOPA || m_pDoor[TileAbove].m_Index == TILE_STOPA || ((m_pDoor[TileBelow].m_Index == TILE_STOPS || m_pDoor[TileAbove].m_Index == TILE_STOPS) && m_pDoor[TileBelow].m_Flags | ROTATION_180 | ROTATION_0))
902 return true;
903 if((m_pDoor[TileOnTheRight].m_Index == TILE_STOP && m_pDoor[TileOnTheRight].m_Flags == ROTATION_270) || (m_pDoor[TileOnTheLeft].m_Index == TILE_STOP && m_pDoor[TileOnTheLeft].m_Flags == ROTATION_90))
904 return true;
905 if((m_pDoor[TileBelow].m_Index == TILE_STOP && m_pDoor[TileBelow].m_Flags == ROTATION_0) || (m_pDoor[TileAbove].m_Index == TILE_STOP && m_pDoor[TileAbove].m_Flags == ROTATION_180))
906 return true;
907 }
908 return false;
909}
910
911int CCollision::GetMapIndex(vec2 Pos) const
912{
913 int Nx = clamp(val: (int)Pos.x / 32, lo: 0, hi: m_Width - 1);
914 int Ny = clamp(val: (int)Pos.y / 32, lo: 0, hi: m_Height - 1);
915 int Index = Ny * m_Width + Nx;
916
917 if(TileExists(Index))
918 return Index;
919 else
920 return -1;
921}
922
923std::vector<int> CCollision::GetMapIndices(vec2 PrevPos, vec2 Pos, unsigned MaxIndices) const
924{
925 std::vector<int> vIndices;
926 float d = distance(a: PrevPos, b: Pos);
927 int End(d + 1);
928 if(!d)
929 {
930 int Nx = clamp(val: (int)Pos.x / 32, lo: 0, hi: m_Width - 1);
931 int Ny = clamp(val: (int)Pos.y / 32, lo: 0, hi: m_Height - 1);
932 int Index = Ny * m_Width + Nx;
933
934 if(TileExists(Index))
935 {
936 vIndices.push_back(x: Index);
937 return vIndices;
938 }
939 else
940 return vIndices;
941 }
942 else
943 {
944 int LastIndex = 0;
945 for(int i = 0; i < End; i++)
946 {
947 float a = i / d;
948 vec2 Tmp = mix(a: PrevPos, b: Pos, amount: a);
949 int Nx = clamp(val: (int)Tmp.x / 32, lo: 0, hi: m_Width - 1);
950 int Ny = clamp(val: (int)Tmp.y / 32, lo: 0, hi: m_Height - 1);
951 int Index = Ny * m_Width + Nx;
952 if(TileExists(Index) && LastIndex != Index)
953 {
954 if(MaxIndices && vIndices.size() > MaxIndices)
955 return vIndices;
956 vIndices.push_back(x: Index);
957 LastIndex = Index;
958 }
959 }
960
961 return vIndices;
962 }
963}
964
965vec2 CCollision::GetPos(int Index) const
966{
967 if(Index < 0)
968 return vec2(0, 0);
969
970 int x = Index % m_Width;
971 int y = Index / m_Width;
972 return vec2(x * 32 + 16, y * 32 + 16);
973}
974
975int CCollision::GetTileIndex(int Index) const
976{
977 if(Index < 0)
978 return 0;
979 return m_pTiles[Index].m_Index;
980}
981
982int CCollision::GetFTileIndex(int Index) const
983{
984 if(Index < 0 || !m_pFront)
985 return 0;
986 return m_pFront[Index].m_Index;
987}
988
989int CCollision::GetTileFlags(int Index) const
990{
991 if(Index < 0)
992 return 0;
993 return m_pTiles[Index].m_Flags;
994}
995
996int CCollision::GetFTileFlags(int Index) const
997{
998 if(Index < 0 || !m_pFront)
999 return 0;
1000 return m_pFront[Index].m_Flags;
1001}
1002
1003int CCollision::GetIndex(int Nx, int Ny) const
1004{
1005 return m_pTiles[Ny * m_Width + Nx].m_Index;
1006}
1007
1008int CCollision::GetIndex(vec2 PrevPos, vec2 Pos) const
1009{
1010 float Distance = distance(a: PrevPos, b: Pos);
1011
1012 if(!Distance)
1013 {
1014 int Nx = clamp(val: (int)Pos.x / 32, lo: 0, hi: m_Width - 1);
1015 int Ny = clamp(val: (int)Pos.y / 32, lo: 0, hi: m_Height - 1);
1016
1017 if((m_pTele) ||
1018 (m_pSpeedup && m_pSpeedup[Ny * m_Width + Nx].m_Force > 0))
1019 {
1020 return Ny * m_Width + Nx;
1021 }
1022 }
1023
1024 for(int i = 0, id = std::ceil(x: Distance); i < id; i++)
1025 {
1026 float a = (float)i / Distance;
1027 vec2 Tmp = mix(a: PrevPos, b: Pos, amount: a);
1028 int Nx = clamp(val: (int)Tmp.x / 32, lo: 0, hi: m_Width - 1);
1029 int Ny = clamp(val: (int)Tmp.y / 32, lo: 0, hi: m_Height - 1);
1030 if((m_pTele) ||
1031 (m_pSpeedup && m_pSpeedup[Ny * m_Width + Nx].m_Force > 0))
1032 {
1033 return Ny * m_Width + Nx;
1034 }
1035 }
1036
1037 return -1;
1038}
1039
1040int CCollision::GetFIndex(int Nx, int Ny) const
1041{
1042 if(!m_pFront)
1043 return 0;
1044 return m_pFront[Ny * m_Width + Nx].m_Index;
1045}
1046
1047int CCollision::GetFTile(int x, int y) const
1048{
1049 if(!m_pFront)
1050 return 0;
1051 int Nx = clamp(val: x / 32, lo: 0, hi: m_Width - 1);
1052 int Ny = clamp(val: y / 32, lo: 0, hi: m_Height - 1);
1053 if(m_pFront[Ny * m_Width + Nx].m_Index == TILE_DEATH || m_pFront[Ny * m_Width + Nx].m_Index == TILE_NOLASER)
1054 return m_pFront[Ny * m_Width + Nx].m_Index;
1055 else
1056 return 0;
1057}
1058
1059int CCollision::Entity(int x, int y, int Layer) const
1060{
1061 if(x < 0 || x >= m_Width || y < 0 || y >= m_Height)
1062 return 0;
1063
1064 const int Index = y * m_Width + x;
1065 switch(Layer)
1066 {
1067 case LAYER_GAME:
1068 return m_pTiles[Index].m_Index - ENTITY_OFFSET;
1069 case LAYER_FRONT:
1070 return m_pFront[Index].m_Index - ENTITY_OFFSET;
1071 case LAYER_SWITCH:
1072 return m_pSwitch[Index].m_Type - ENTITY_OFFSET;
1073 case LAYER_TELE:
1074 return m_pTele[Index].m_Type - ENTITY_OFFSET;
1075 case LAYER_SPEEDUP:
1076 return m_pSpeedup[Index].m_Type - ENTITY_OFFSET;
1077 case LAYER_TUNE:
1078 return m_pTune[Index].m_Type - ENTITY_OFFSET;
1079 default:
1080 dbg_assert(false, "Layer invalid");
1081 dbg_break();
1082 }
1083}
1084
1085void CCollision::SetCollisionAt(float x, float y, int id)
1086{
1087 int Nx = clamp(val: round_to_int(f: x) / 32, lo: 0, hi: m_Width - 1);
1088 int Ny = clamp(val: round_to_int(f: y) / 32, lo: 0, hi: m_Height - 1);
1089
1090 m_pTiles[Ny * m_Width + Nx].m_Index = id;
1091}
1092
1093void CCollision::SetDCollisionAt(float x, float y, int Type, int Flags, int Number)
1094{
1095 if(!m_pDoor)
1096 return;
1097 int Nx = clamp(val: round_to_int(f: x) / 32, lo: 0, hi: m_Width - 1);
1098 int Ny = clamp(val: round_to_int(f: y) / 32, lo: 0, hi: m_Height - 1);
1099
1100 m_pDoor[Ny * m_Width + Nx].m_Index = Type;
1101 m_pDoor[Ny * m_Width + Nx].m_Flags = Flags;
1102 m_pDoor[Ny * m_Width + Nx].m_Number = Number;
1103}
1104
1105int CCollision::GetDTileIndex(int Index) const
1106{
1107 if(!m_pDoor || Index < 0 || !m_pDoor[Index].m_Index)
1108 return 0;
1109 return m_pDoor[Index].m_Index;
1110}
1111
1112int CCollision::GetDTileNumber(int Index) const
1113{
1114 if(!m_pDoor || Index < 0 || !m_pDoor[Index].m_Index)
1115 return 0;
1116 if(m_pDoor[Index].m_Number)
1117 return m_pDoor[Index].m_Number;
1118 return 0;
1119}
1120
1121int CCollision::GetDTileFlags(int Index) const
1122{
1123 if(!m_pDoor || Index < 0 || !m_pDoor[Index].m_Index)
1124 return 0;
1125 return m_pDoor[Index].m_Flags;
1126}
1127
1128void ThroughOffset(vec2 Pos0, vec2 Pos1, int *pOffsetX, int *pOffsetY)
1129{
1130 float x = Pos0.x - Pos1.x;
1131 float y = Pos0.y - Pos1.y;
1132 if(absolute(a: x) > absolute(a: y))
1133 {
1134 if(x < 0)
1135 {
1136 *pOffsetX = -32;
1137 *pOffsetY = 0;
1138 }
1139 else
1140 {
1141 *pOffsetX = 32;
1142 *pOffsetY = 0;
1143 }
1144 }
1145 else
1146 {
1147 if(y < 0)
1148 {
1149 *pOffsetX = 0;
1150 *pOffsetY = -32;
1151 }
1152 else
1153 {
1154 *pOffsetX = 0;
1155 *pOffsetY = 32;
1156 }
1157 }
1158}
1159
1160int CCollision::IntersectNoLaser(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision) const
1161{
1162 float d = distance(a: Pos0, b: Pos1);
1163 vec2 Last = Pos0;
1164
1165 for(int i = 0, id = std::ceil(x: d); i < id; i++)
1166 {
1167 float a = (int)i / d;
1168 vec2 Pos = mix(a: Pos0, b: Pos1, amount: a);
1169 int Nx = clamp(val: round_to_int(f: Pos.x) / 32, lo: 0, hi: m_Width - 1);
1170 int Ny = clamp(val: round_to_int(f: Pos.y) / 32, lo: 0, hi: m_Height - 1);
1171 if(GetIndex(Nx, Ny) == TILE_SOLID || GetIndex(Nx, Ny) == TILE_NOHOOK || GetIndex(Nx, Ny) == TILE_NOLASER || GetFIndex(Nx, Ny) == TILE_NOLASER)
1172 {
1173 if(pOutCollision)
1174 *pOutCollision = Pos;
1175 if(pOutBeforeCollision)
1176 *pOutBeforeCollision = Last;
1177 if(GetFIndex(Nx, Ny) == TILE_NOLASER)
1178 return GetFCollisionAt(x: Pos.x, y: Pos.y);
1179 else
1180 return GetCollisionAt(x: Pos.x, y: Pos.y);
1181 }
1182 Last = Pos;
1183 }
1184 if(pOutCollision)
1185 *pOutCollision = Pos1;
1186 if(pOutBeforeCollision)
1187 *pOutBeforeCollision = Pos1;
1188 return 0;
1189}
1190
1191int CCollision::IntersectNoLaserNW(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision) const
1192{
1193 float d = distance(a: Pos0, b: Pos1);
1194 vec2 Last = Pos0;
1195
1196 for(int i = 0, id = std::ceil(x: d); i < id; i++)
1197 {
1198 float a = (float)i / d;
1199 vec2 Pos = mix(a: Pos0, b: Pos1, amount: a);
1200 if(IsNoLaser(x: round_to_int(f: Pos.x), y: round_to_int(f: Pos.y)) || IsFNoLaser(x: round_to_int(f: Pos.x), y: round_to_int(f: Pos.y)))
1201 {
1202 if(pOutCollision)
1203 *pOutCollision = Pos;
1204 if(pOutBeforeCollision)
1205 *pOutBeforeCollision = Last;
1206 if(IsNoLaser(x: round_to_int(f: Pos.x), y: round_to_int(f: Pos.y)))
1207 return GetCollisionAt(x: Pos.x, y: Pos.y);
1208 else
1209 return GetFCollisionAt(x: Pos.x, y: Pos.y);
1210 }
1211 Last = Pos;
1212 }
1213 if(pOutCollision)
1214 *pOutCollision = Pos1;
1215 if(pOutBeforeCollision)
1216 *pOutBeforeCollision = Pos1;
1217 return 0;
1218}
1219
1220int CCollision::IntersectAir(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision) const
1221{
1222 float d = distance(a: Pos0, b: Pos1);
1223 vec2 Last = Pos0;
1224
1225 for(int i = 0, id = std::ceil(x: d); i < id; i++)
1226 {
1227 float a = (float)i / d;
1228 vec2 Pos = mix(a: Pos0, b: Pos1, amount: a);
1229 if(IsSolid(x: round_to_int(f: Pos.x), y: round_to_int(f: Pos.y)) || (!GetTile(x: round_to_int(f: Pos.x), y: round_to_int(f: Pos.y)) && !GetFTile(x: round_to_int(f: Pos.x), y: round_to_int(f: Pos.y))))
1230 {
1231 if(pOutCollision)
1232 *pOutCollision = Pos;
1233 if(pOutBeforeCollision)
1234 *pOutBeforeCollision = Last;
1235 if(!GetTile(x: round_to_int(f: Pos.x), y: round_to_int(f: Pos.y)) && !GetFTile(x: round_to_int(f: Pos.x), y: round_to_int(f: Pos.y)))
1236 return -1;
1237 else if(!GetTile(x: round_to_int(f: Pos.x), y: round_to_int(f: Pos.y)))
1238 return GetTile(x: round_to_int(f: Pos.x), y: round_to_int(f: Pos.y));
1239 else
1240 return GetFTile(x: round_to_int(f: Pos.x), y: round_to_int(f: Pos.y));
1241 }
1242 Last = Pos;
1243 }
1244 if(pOutCollision)
1245 *pOutCollision = Pos1;
1246 if(pOutBeforeCollision)
1247 *pOutBeforeCollision = Pos1;
1248 return 0;
1249}
1250
1251int CCollision::IsTimeCheckpoint(int Index) const
1252{
1253 if(Index < 0)
1254 return -1;
1255
1256 int z = m_pTiles[Index].m_Index;
1257 if(z >= TILE_TIME_CHECKPOINT_FIRST && z <= TILE_TIME_CHECKPOINT_LAST)
1258 return z - TILE_TIME_CHECKPOINT_FIRST;
1259 return -1;
1260}
1261
1262int CCollision::IsFTimeCheckpoint(int Index) const
1263{
1264 if(Index < 0 || !m_pFront)
1265 return -1;
1266
1267 int z = m_pFront[Index].m_Index;
1268 if(z >= TILE_TIME_CHECKPOINT_FIRST && z <= TILE_TIME_CHECKPOINT_LAST)
1269 return z - TILE_TIME_CHECKPOINT_FIRST;
1270 return -1;
1271}
1272