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
521bool CCollision::IsOnGround(vec2 Pos, float Size) const
522{
523 if(CheckPoint(x: Pos.x + Size / 2, y: Pos.y + Size / 2 + 5))
524 return true;
525 if(CheckPoint(x: Pos.x - Size / 2, y: Pos.y + Size / 2 + 5))
526 return true;
527
528 return false;
529}
530
531void CCollision::MoveBox(vec2 *pInoutPos, vec2 *pInoutVel, vec2 Size, vec2 Elasticity, bool *pGrounded) const
532{
533 // do the move
534 vec2 Pos = *pInoutPos;
535 vec2 Vel = *pInoutVel;
536
537 float Distance = length(a: Vel);
538 int Max = (int)Distance;
539
540 if(Distance > 0.00001f)
541 {
542 float Fraction = 1.0f / (float)(Max + 1);
543 float ElasticityX = std::clamp(val: Elasticity.x, lo: -1.0f, hi: 1.0f);
544 float ElasticityY = std::clamp(val: Elasticity.y, lo: -1.0f, hi: 1.0f);
545
546 for(int i = 0; i <= Max; i++)
547 {
548 // Early break as optimization to stop checking for collisions for
549 // large distances after the obstacles we have already hit reduced
550 // our speed to exactly 0.
551 if(Vel == vec2(0, 0))
552 {
553 break;
554 }
555
556 vec2 NewPos = Pos + Vel * Fraction; // TODO: this row is not nice
557
558 // Fraction can be very small and thus the calculation has no effect, no
559 // reason to continue calculating.
560 if(NewPos == Pos)
561 {
562 break;
563 }
564
565 if(TestBox(Pos: vec2(NewPos.x, NewPos.y), Size))
566 {
567 int Hits = 0;
568
569 if(TestBox(Pos: vec2(Pos.x, NewPos.y), Size))
570 {
571 if(pGrounded && ElasticityY > 0 && Vel.y > 0)
572 *pGrounded = true;
573 NewPos.y = Pos.y;
574 Vel.y *= -ElasticityY;
575 Hits++;
576 }
577
578 if(TestBox(Pos: vec2(NewPos.x, Pos.y), Size))
579 {
580 NewPos.x = Pos.x;
581 Vel.x *= -ElasticityX;
582 Hits++;
583 }
584
585 // neither of the tests got a collision.
586 // this is a real _corner case_!
587 if(Hits == 0)
588 {
589 if(pGrounded && ElasticityY > 0 && Vel.y > 0)
590 *pGrounded = true;
591 NewPos.y = Pos.y;
592 Vel.y *= -ElasticityY;
593 NewPos.x = Pos.x;
594 Vel.x *= -ElasticityX;
595 }
596 }
597
598 Pos = NewPos;
599 }
600 }
601
602 *pInoutPos = Pos;
603 *pInoutVel = Vel;
604}
605
606// DDRace
607
608int CCollision::IsSolid(int x, int y) const
609{
610 const int Index = GetTile(x, y);
611 return Index == TILE_SOLID || Index == TILE_NOHOOK;
612}
613
614bool CCollision::IsThrough(int x, int y, int OffsetX, int OffsetY, vec2 Pos0, vec2 Pos1) const
615{
616 const int Index = GetPureMapIndex(x, y);
617 if(m_pFront && (m_pFront[Index].m_Index == TILE_THROUGH_ALL || m_pFront[Index].m_Index == TILE_THROUGH_CUT))
618 return true;
619 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)))
620 return true;
621 const int OffsetIndex = GetPureMapIndex(x: x + OffsetX, y: y + OffsetY);
622 return m_pTiles[OffsetIndex].m_Index == TILE_THROUGH || (m_pFront && m_pFront[OffsetIndex].m_Index == TILE_THROUGH);
623}
624
625bool CCollision::IsHookBlocker(int x, int y, vec2 Pos0, vec2 Pos1) const
626{
627 const int Index = GetPureMapIndex(x, y);
628 if(m_pTiles[Index].m_Index == TILE_THROUGH_ALL || (m_pFront && m_pFront[Index].m_Index == TILE_THROUGH_ALL))
629 return true;
630 if(m_pTiles[Index].m_Index == TILE_THROUGH_DIR && ((m_pTiles[Index].m_Flags == ROTATION_0 && Pos0.y < Pos1.y) ||
631 (m_pTiles[Index].m_Flags == ROTATION_90 && Pos0.x > Pos1.x) ||
632 (m_pTiles[Index].m_Flags == ROTATION_180 && Pos0.y > Pos1.y) ||
633 (m_pTiles[Index].m_Flags == ROTATION_270 && Pos0.x < Pos1.x)))
634 return true;
635 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)))
636 return true;
637 return false;
638}
639
640int CCollision::IsWallJump(int Index) const
641{
642 if(Index < 0)
643 return 0;
644
645 return m_pTiles[Index].m_Index == TILE_WALLJUMP;
646}
647
648int CCollision::IsNoLaser(int x, int y) const
649{
650 return (CCollision::GetTile(x, y) == TILE_NOLASER);
651}
652
653int CCollision::IsFrontNoLaser(int x, int y) const
654{
655 return (CCollision::GetFrontTile(x, y) == TILE_NOLASER);
656}
657
658int CCollision::IsTeleport(int Index) const
659{
660 if(Index < 0 || !m_pTele)
661 return 0;
662
663 if(m_pTele[Index].m_Type == TILE_TELEIN)
664 return m_pTele[Index].m_Number;
665
666 return 0;
667}
668
669int CCollision::IsEvilTeleport(int Index) const
670{
671 if(Index < 0)
672 return 0;
673 if(!m_pTele)
674 return 0;
675
676 if(m_pTele[Index].m_Type == TILE_TELEINEVIL)
677 return m_pTele[Index].m_Number;
678
679 return 0;
680}
681
682bool CCollision::IsCheckTeleport(int Index) const
683{
684 if(Index < 0 || !m_pTele)
685 return false;
686 return m_pTele[Index].m_Type == TILE_TELECHECKIN;
687}
688
689bool CCollision::IsCheckEvilTeleport(int Index) const
690{
691 if(Index < 0 || !m_pTele)
692 return false;
693 return m_pTele[Index].m_Type == TILE_TELECHECKINEVIL;
694}
695
696int CCollision::IsTeleCheckpoint(int Index) const
697{
698 if(Index < 0)
699 return 0;
700
701 if(!m_pTele)
702 return 0;
703
704 if(m_pTele[Index].m_Type == TILE_TELECHECK)
705 return m_pTele[Index].m_Number;
706
707 return 0;
708}
709
710int CCollision::IsTeleportWeapon(int Index) const
711{
712 if(Index < 0 || !m_pTele)
713 return 0;
714
715 if(m_pTele[Index].m_Type == TILE_TELEINWEAPON)
716 return m_pTele[Index].m_Number;
717
718 return 0;
719}
720
721int CCollision::IsTeleportHook(int Index) const
722{
723 if(Index < 0 || !m_pTele)
724 return 0;
725
726 if(m_pTele[Index].m_Type == TILE_TELEINHOOK)
727 return m_pTele[Index].m_Number;
728
729 return 0;
730}
731
732int CCollision::IsSpeedup(int Index) const
733{
734 if(Index < 0 || !m_pSpeedup)
735 return 0;
736
737 if(m_pSpeedup[Index].m_Force > 0)
738 return Index;
739
740 return 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, int Type, int Flags, int 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