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