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