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