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