· 5 years ago · Mar 22, 2020, 12:14 AM
1diff --git a/src/Makefile b/src/Makefile
2index fee849d6c..16cb8912d 100644
3--- a/src/Makefile
4+++ b/src/Makefile
5@@ -495,6 +495,9 @@ OBJS:=$(i_main_o) \
6 $(OBJDIR)/st_stuff.o \
7 $(OBJDIR)/k_kart.o \
8 $(OBJDIR)/k_pwrlv.o \
9+ $(OBJDIR)/k_waypoint.o\
10+ $(OBJDIR)/k_pathfind.o\
11+ $(OBJDIR)/k_bheap.o \
12 $(OBJDIR)/m_aatree.o \
13 $(OBJDIR)/m_anigif.o \
14 $(OBJDIR)/m_argv.o \
15diff --git a/src/d_netcmd.c b/src/d_netcmd.c
16index 5e57c293b..404d7f181 100644
17--- a/src/d_netcmd.c
18+++ b/src/d_netcmd.c
19@@ -387,6 +387,8 @@ consvar_t cv_kartdebugamount = {"kartdebugamount", "1", CV_NETVAR|CV_CHEAT|CV_NO
20 consvar_t cv_kartdebugshrink = {"kartdebugshrink", "Off", CV_NETVAR|CV_CHEAT|CV_NOSHOWHELP, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
21 consvar_t cv_kartdebugdistribution = {"kartdebugdistribution", "Off", CV_NETVAR|CV_CHEAT|CV_NOSHOWHELP, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
22 consvar_t cv_kartdebughuddrop = {"kartdebughuddrop", "Off", CV_NETVAR|CV_CHEAT|CV_NOSHOWHELP, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
23+static CV_PossibleValue_t kartdebugwaypoint_cons_t[] = {{0, "Off"}, {1, "Forwards"}, {2, "Backwards"}, {0, NULL}};
24+consvar_t cv_kartdebugwaypoints = {"kartdebugwaypoints", "Off", CV_NETVAR|CV_CHEAT|CV_NOSHOWHELP, kartdebugwaypoint_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
25
26 consvar_t cv_kartdebugcheckpoint = {"kartdebugcheckpoint", "Off", CV_NOSHOWHELP, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
27 consvar_t cv_kartdebugnodes = {"kartdebugnodes", "Off", CV_NOSHOWHELP, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
28@@ -1887,8 +1889,6 @@ void SendWeaponPref(void)
29 buf[0] = 0;
30 if (cv_flipcam.value)
31 buf[0] |= 1;
32- if (cv_analog.value)
33- buf[0] |= 2;
34 SendNetXCmd(XD_WEAPONPREF, buf, 1);
35 }
36
37@@ -1899,8 +1899,6 @@ void SendWeaponPref2(void)
38 buf[0] = 0;
39 if (cv_flipcam2.value)
40 buf[0] |= 1;
41- if (cv_analog2.value)
42- buf[0] |= 2;
43 SendNetXCmd2(XD_WEAPONPREF, buf, 1);
44 }
45
46@@ -1911,8 +1909,6 @@ void SendWeaponPref3(void)
47 buf[0] = 0;
48 if (cv_flipcam3.value)
49 buf[0] |= 1;
50- if (cv_analog3.value)
51- buf[0] |= 2;
52 SendNetXCmd3(XD_WEAPONPREF, buf, 1);
53 }
54
55@@ -1923,8 +1919,6 @@ void SendWeaponPref4(void)
56 buf[0] = 0;
57 if (cv_flipcam4.value)
58 buf[0] |= 1;
59- if (cv_analog4.value)
60- buf[0] |= 2;
61 SendNetXCmd4(XD_WEAPONPREF, buf, 1);
62 }
63
64@@ -1932,11 +1926,9 @@ static void Got_WeaponPref(UINT8 **cp,INT32 playernum)
65 {
66 UINT8 prefs = READUINT8(*cp);
67
68- players[playernum].pflags &= ~(PF_FLIPCAM|PF_ANALOGMODE);
69+ players[playernum].pflags &= ~(PF_FLIPCAM);
70 if (prefs & 1)
71 players[playernum].pflags |= PF_FLIPCAM;
72- if (prefs & 2)
73- players[playernum].pflags |= PF_ANALOGMODE;
74 }
75
76 static void Got_PowerLevel(UINT8 **cp,INT32 playernum)
77@@ -2858,13 +2850,15 @@ static void Got_Respawn(UINT8 **cp, INT32 playernum)
78 return;
79 }
80
81- // incase the above checks were modified to allow sending a respawn on these occasions:
82- if (players[respawnplayer].mo && !P_IsObjectOnGround(players[respawnplayer].mo))
83- return;
84-
85 if (players[respawnplayer].mo)
86- P_DamageMobj(players[respawnplayer].mo, NULL, NULL, 10000);
87- demo_extradata[playernum] |= DXD_RESPAWN;
88+ {
89+ // incase the above checks were modified to allow sending a respawn on these occasions:
90+ if (!P_IsObjectOnGround(players[respawnplayer].mo))
91+ return;
92+
93+ K_DoIngameRespawn(&players[respawnplayer]);
94+ demo_extradata[playernum] |= DXD_RESPAWN;
95+ }
96 }
97
98 /** Deals with an ::XD_RANDOMSEED message in a netgame.
99diff --git a/src/d_netcmd.h b/src/d_netcmd.h
100index 60541aa8c..c20ebb69e 100644
101--- a/src/d_netcmd.h
102+++ b/src/d_netcmd.h
103@@ -127,6 +127,7 @@ extern consvar_t cv_votetime;
104
105 extern consvar_t cv_kartdebugitem, cv_kartdebugamount, cv_kartdebugshrink, cv_kartdebugdistribution, cv_kartdebughuddrop;
106 extern consvar_t cv_kartdebugcheckpoint, cv_kartdebugnodes, cv_kartdebugcolorize;
107+extern consvar_t cv_kartdebugwaypoints;
108
109 extern consvar_t cv_itemfinder;
110
111diff --git a/src/d_player.h b/src/d_player.h
112index 697c0356c..07e0f7a97 100644
113--- a/src/d_player.h
114+++ b/src/d_player.h
115@@ -29,6 +29,9 @@
116 // as commands per game tick.
117 #include "d_ticcmd.h"
118
119+// the player struct stores a waypoint for racing
120+#include "k_waypoint.h"
121+
122 // Extra abilities/settings for skins (combinable stuff)
123 typedef enum
124 {
125@@ -118,7 +121,7 @@ typedef enum
126
127 /*** misc ***/
128 PF_FORCESTRAFE = 1<<29, // Turning inputs are translated into strafing inputs
129- PF_ANALOGMODE = 1<<30, // Analog mode?
130+ PF_HITFINISHLINE = 1<<30, // Already hit the finish line this tic
131
132 // free: 1<<30 and 1<<31
133 } pflags_t;
134@@ -240,10 +243,6 @@ typedef enum
135 k_position, // Used for Kart positions, mostly for deterministic stuff
136 k_oldposition, // Used for taunting when you pass someone
137 k_positiondelay, // Used for position number, so it can grow when passing/being passed
138- k_prevcheck, // Previous checkpoint distance; for p_user.c (was "pw_pcd")
139- k_nextcheck, // Next checkpoint distance; for p_user.c (was "pw_ncd")
140- k_waypoint, // Waypoints.
141- k_starpostwp, // Temporarily stores player waypoint for... some reason. Used when respawning and finishing.
142 k_starpostflip, // the last starpost we hit requires flipping?
143 k_respawn, // Timer for the DEZ laser respawn effect
144 k_dropdash, // Charge up for respawn Drop Dash
145@@ -331,6 +330,7 @@ typedef enum
146 k_springstars, // Spawn stars around a player when they hit a spring
147 k_springcolor, // Color of spring stars
148 k_killfield, // How long have you been in the kill field, stay in too long and lose a bumper
149+ k_wrongway, // Display WRONG WAY on screen
150
151 NUMKARTSTUFF
152 } kartstufftype_t;
153@@ -436,6 +436,8 @@ typedef struct player_s
154 angle_t frameangle; // for the player add the ability to have the sprite only face other angles
155 INT16 lturn_max[MAXPREDICTTICS]; // What's the expected turn value for full-left for a number of frames back (to account for netgame latency)?
156 INT16 rturn_max[MAXPREDICTTICS]; // Ditto but for full-right
157+ UINT32 distancetofinish;
158+ waypoint_t *nextwaypoint;
159
160 // Bit flags.
161 // See pflags_t, above.
162diff --git a/src/dehacked.c b/src/dehacked.c
163index 5789396b1..a9d46b2a4 100644
164--- a/src/dehacked.c
165+++ b/src/dehacked.c
166@@ -6660,6 +6660,11 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
167
168 // DEZ respawn laser
169 "S_DEZLASER",
170+ "S_DEZLASER_TRAIL1",
171+ "S_DEZLASER_TRAIL2",
172+ "S_DEZLASER_TRAIL3",
173+ "S_DEZLASER_TRAIL4",
174+ "S_DEZLASER_TRAIL5",
175
176 // Audience Members
177 "S_RANDOMAUDIENCE",
178@@ -8180,7 +8185,7 @@ static const char *const PLAYERFLAG_LIST[] = {
179
180 /*** misc ***/
181 "FORCESTRAFE", // Translate turn inputs into strafe inputs
182- "ANALOGMODE", // Analog mode?
183+ "HITFINISHLINE", // Already hit the finish line this tic
184
185 NULL // stop loop here.
186 };
187@@ -8417,10 +8422,6 @@ static const char *const KARTSTUFF_LIST[] = {
188 "POSITION",
189 "OLDPOSITION",
190 "POSITIONDELAY",
191- "PREVCHECK",
192- "NEXTCHECK",
193- "WAYPOINT",
194- "STARPOSTWP",
195 "STARPOSTFLIP",
196 "RESPAWN",
197 "DROPDASH",
198@@ -8502,7 +8503,8 @@ static const char *const KARTSTUFF_LIST[] = {
199 "TIREGREASE",
200 "SPRINGSTARS",
201 "SPRINGCOLOR",
202- "KILLFIELD"
203+ "KILLFIELD",
204+ "WRONGWAY"
205 };
206 #endif
207
208diff --git a/src/doomdata.h b/src/doomdata.h
209index 6319238b7..aa4ea1a54 100644
210--- a/src/doomdata.h
211+++ b/src/doomdata.h
212@@ -46,6 +46,9 @@ enum
213 ML_BLOCKMAP, // LUT, motion clipping, walls/grid element
214 };
215
216+// Extra flag for objects
217+#define MTF_EXTRA 1
218+
219 // Reverse gravity flag for objects.
220 #define MTF_OBJECTFLIP 2
221
222diff --git a/src/g_game.c b/src/g_game.c
223index 6fbe4bec8..7bd183664 100644
224--- a/src/g_game.c
225+++ b/src/g_game.c
226@@ -2576,7 +2576,6 @@ void G_PlayerReborn(INT32 player)
227 SINT8 pity;
228
229 // SRB2kart
230- INT32 starpostwp;
231 INT32 itemtype;
232 INT32 itemamount;
233 INT32 itemroulette;
234@@ -2598,7 +2597,7 @@ void G_PlayerReborn(INT32 player)
235 jointime = players[player].jointime;
236 splitscreenindex = players[player].splitscreenindex;
237 spectator = players[player].spectator;
238- pflags = (players[player].pflags & (PF_TIMEOVER|PF_FLIPCAM|PF_TAGIT|PF_TAGGED|PF_ANALOGMODE|PF_WANTSTOJOIN));
239+ pflags = (players[player].pflags & (PF_TIMEOVER|PF_FLIPCAM|PF_TAGIT|PF_TAGGED|PF_WANTSTOJOIN));
240
241 // As long as we're not in multiplayer, carry over cheatcodes from map to map
242 if (!(netgame || multiplayer))
243@@ -2640,12 +2639,9 @@ void G_PlayerReborn(INT32 player)
244 rings = (G_BattleGametype() ? 0 : 5);
245 comebackpoints = 0;
246 wanted = 0;
247- starpostwp = 0;
248 }
249 else
250 {
251- starpostwp = players[player].kartstuff[k_starpostwp];
252-
253 itemroulette = (players[player].kartstuff[k_itemroulette] > 0 ? 1 : 0);
254 roulettetype = players[player].kartstuff[k_roulettetype];
255
256@@ -2712,7 +2708,6 @@ void G_PlayerReborn(INT32 player)
257 p->pity = pity;
258
259 // SRB2kart
260- p->kartstuff[k_starpostwp] = starpostwp; // TODO: get these out of kartstuff, it causes desync (Does it...?)
261 p->kartstuff[k_itemroulette] = itemroulette;
262 p->kartstuff[k_roulettetype] = roulettetype;
263 p->kartstuff[k_itemtype] = itemtype;
264@@ -3248,7 +3243,8 @@ void G_DoReborn(INT32 playernum)
265 // respawn at the start
266 mobj_t *oldmo = NULL;
267
268- if (player->starpostnum || ((mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE) && player->laps)) // SRB2kart
269+ // Now only respawn at the start if you haven't crossed it at all
270+ if (player->laps) // SRB2kart
271 starpost = true;
272
273 // first dissasociate the corpse
274@@ -4939,7 +4935,10 @@ void G_ReadDemoExtraData(void)
275 if (extradata & DXD_RESPAWN)
276 {
277 if (players[p].mo)
278- P_DamageMobj(players[p].mo, NULL, NULL, 10000); // Is this how this should work..?
279+ {
280+ // Is this how this should work..?
281+ K_DoIngameRespawn(&players[p]);
282+ }
283 }
284 if (extradata & DXD_SKIN)
285 {
286diff --git a/src/info.c b/src/info.c
287index 135af682e..cdccdb420 100644
288--- a/src/info.c
289+++ b/src/info.c
290@@ -2876,7 +2876,12 @@ state_t states[NUMSTATES] =
291 {SPR_KBLN, FF_FULLBRIGHT|1, -1, {NULL}, 0, 0, S_BATTLEBUMPER2}, // S_BATTLEBUMPER2
292 {SPR_KBLN, FF_FULLBRIGHT|2, -1, {NULL}, 0, 0, S_BATTLEBUMPER3}, // S_BATTLEBUMPER3
293
294- {SPR_DEZL, FF_FULLBRIGHT|FF_PAPERSPRITE, 8, {NULL}, 0, 0, S_NULL}, // S_DEZLASER
295+ {SPR_DEZL, FF_FULLBRIGHT|FF_PAPERSPRITE, 8, {NULL}, 0, 0, S_NULL}, // S_DEZLASER
296+ {SPR_DEZL, FF_FULLBRIGHT|1, 2, {NULL}, 0, 0, S_DEZLASER_TRAIL2}, // S_DEZLASER_TRAIL1
297+ {SPR_DEZL, FF_FULLBRIGHT|2, 2, {NULL}, 0, 0, S_DEZLASER_TRAIL3}, // S_DEZLASER_TRAIL2
298+ {SPR_DEZL, FF_FULLBRIGHT|FF_PAPERSPRITE|3, 4, {NULL}, 0, 0, S_DEZLASER_TRAIL4}, // S_DEZLASER_TRAIL3
299+ {SPR_DEZL, FF_FULLBRIGHT|2, 2, {NULL}, 0, 0, S_DEZLASER_TRAIL5}, // S_DEZLASER_TRAIL4
300+ {SPR_DEZL, FF_FULLBRIGHT|1, 2, {NULL}, 0, 0, S_NULL}, // S_DEZLASER_TRAIL5
301
302 {SPR_NULL, 0, 1, {A_RandomStateRange}, S_AUDIENCE_CHAO_CHEER1, S_AUDIENCE_CHAO_CHEER2, S_RANDOMAUDIENCE}, // S_RANDOMAUDIENCE
303
304@@ -16076,7 +16081,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
305 S_SPB_DEAD, // deathstate
306 S_NULL, // xdeathstate
307 sfx_s3k5d, // deathsound
308- 64*FRACUNIT, // speed
309+ 80*FRACUNIT, // speed
310 24*FRACUNIT, // radius
311 48*FRACUNIT, // height
312 0, // display offset
313@@ -16278,7 +16283,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
314
315 { // MT_WAYPOINT
316 2001, // doomednum
317- S_NULL, // spawnstate
318+ S_INVISIBLE, // spawnstate
319 1000, // spawnhealth
320 S_NULL, // seestate
321 sfx_None, // seesound
322@@ -16299,7 +16304,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
323 100, // mass
324 0, // damage
325 sfx_None, // activesound
326- MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY, // flags
327+ MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags
328 S_NULL // raisestate
329 },
330
331diff --git a/src/info.h b/src/info.h
332index a20875c68..95ae5c225 100644
333--- a/src/info.h
334+++ b/src/info.h
335@@ -3552,6 +3552,11 @@ typedef enum state
336
337 // DEZ Laser respawn
338 S_DEZLASER,
339+ S_DEZLASER_TRAIL1,
340+ S_DEZLASER_TRAIL2,
341+ S_DEZLASER_TRAIL3,
342+ S_DEZLASER_TRAIL4,
343+ S_DEZLASER_TRAIL5,
344
345 // Audience Members
346 S_RANDOMAUDIENCE,
347diff --git a/src/k_bheap.c b/src/k_bheap.c
348new file mode 100644
349index 000000000..cf6848f68
350--- /dev/null
351+++ b/src/k_bheap.c
352@@ -0,0 +1,579 @@
353+#include "k_bheap.h"
354+
355+#include "z_zone.h"
356+
357+/*--------------------------------------------------
358+ static boolean K_BHeapItemValidate(bheap_t *heap, bheapitem_t *item)
359+
360+ Validates an item on a heap to ensure it is correct and on that heap.
361+
362+ Input Arguments:-
363+ heap - The heap to validate the item with
364+ item - The item to validate
365+
366+ Return:-
367+ True if the item is valid, false if it isn't.
368+--------------------------------------------------*/
369+static boolean K_BHeapItemValidate(bheap_t *heap, bheapitem_t *item)
370+{
371+ boolean heapitemvalid = false;
372+
373+ I_Assert(heap != NULL);
374+ I_Assert(item != NULL);
375+
376+ if ((item->data != NULL) && (item->heapindex < SIZE_MAX / 2) && (item->owner == heap))
377+ {
378+ heapitemvalid = true;
379+ }
380+
381+ return heapitemvalid;
382+}
383+
384+/*--------------------------------------------------
385+ static bheapitem_t *K_BHeapItemsCompare(bheap_t *heap, bheapitem_t *item1, bheapitem_t *item2)
386+
387+ Compares 2 items in the heap to find the better (lower) value one.
388+
389+ Input Arguments:-
390+ heap - The heap to compare items
391+ item1 - The first item to compare
392+ item2 - The second item to compare
393+
394+ Return:-
395+ The item out of the 2 sent in that has the better value, returns item2 if they are identical
396+--------------------------------------------------*/
397+static bheapitem_t *K_BHeapItemsCompare(bheap_t *heap, bheapitem_t *item1, bheapitem_t *item2)
398+{
399+ bheapitem_t *lowervalueitem = NULL;
400+
401+ I_Assert(heap != NULL);
402+ I_Assert(K_BHeapValid(heap));
403+ I_Assert(item1 != NULL);
404+ I_Assert(item2 != NULL);
405+ I_Assert(K_BHeapItemValidate(heap, item1));
406+ I_Assert(K_BHeapItemValidate(heap, item2));
407+
408+ if (item1->value < item2->value)
409+ {
410+ lowervalueitem = item1;
411+ }
412+ else
413+ {
414+ lowervalueitem = item2;
415+ }
416+
417+ return lowervalueitem;
418+}
419+
420+/*--------------------------------------------------
421+ static void K_BHeapSwapItems(bheap_t *heap, bheapitem_t *item1, bheapitem_t *item2)
422+
423+ Swaps 2 items in the heap
424+
425+ Input Arguments:-
426+ heap - The heap to swap items in
427+ item1 - The first item to swap in the heap
428+ item2 - The second item to swap in the heap
429+
430+ Return:-
431+ None
432+--------------------------------------------------*/
433+static void K_BHeapSwapItems(bheap_t *heap, bheapitem_t *item1, bheapitem_t *item2)
434+{
435+ I_Assert(heap != NULL);
436+ I_Assert(K_BHeapValid(heap));
437+ I_Assert(item1 != NULL);
438+ I_Assert(item2 != NULL);
439+ I_Assert(K_BHeapItemValidate(heap, item1));
440+ I_Assert(K_BHeapItemValidate(heap, item2));
441+
442+ {
443+ size_t tempitemindex = item1->heapindex;
444+ bheapitem_t tempitemstore = *item1;
445+
446+ // Swap the items fully with each other
447+ *item1 = *item2;
448+ *item2 = tempitemstore;
449+
450+ // Swap the heap index on each item to be correct
451+ item2->heapindex = item1->heapindex;
452+ item1->heapindex = tempitemindex;
453+
454+ if (item1->indexchanged != NULL)
455+ {
456+ item1->indexchanged(item1->data, item1->heapindex);
457+ }
458+ if (item2->indexchanged != NULL)
459+ {
460+ item2->indexchanged(item2->data, item2->heapindex);
461+ }
462+ }
463+}
464+
465+/*--------------------------------------------------
466+ static size_t K_BHeapItemGetParentIndex(bheapitem_t *item)
467+
468+ Gets the parent index of a heap item
469+
470+ Input Arguments:-
471+ item - The item to get the parent index of
472+
473+ Return:-
474+ The parent index of the item
475+--------------------------------------------------*/
476+static size_t K_BHeapItemGetParentIndex(bheapitem_t *item)
477+{
478+ size_t parentindex = SIZE_MAX;
479+
480+ I_Assert(item != NULL);
481+ I_Assert(item->heapindex < (SIZE_MAX / 2));
482+
483+ parentindex = (item->heapindex - 1U) / 2U;
484+
485+ return parentindex;
486+}
487+
488+/*--------------------------------------------------
489+ static size_t K_BHeapItemGetLeftChildIndex(bheapitem_t *item)
490+
491+ Gets the left child index of a heap item
492+
493+ Input Arguments:-
494+ item - The item to get the left child index of
495+
496+ Return:-
497+ The left child index of the item
498+--------------------------------------------------*/
499+static size_t K_BHeapItemGetLeftChildIndex(bheapitem_t *item)
500+{
501+ size_t leftchildindex = SIZE_MAX;
502+
503+ I_Assert(item != NULL);
504+ I_Assert(item->heapindex < (SIZE_MAX / 2));
505+
506+ leftchildindex = (item->heapindex * 2U) + 1U;
507+
508+ return leftchildindex;
509+}
510+
511+/*--------------------------------------------------
512+ static size_t K_BHeapItemGetRightChildIndex(bheapitem_t *item)
513+
514+ Gets the right child index of a heap item
515+
516+ Input Arguments:-
517+ item - The item to get the right child index of
518+
519+ Return:-
520+ The right child index of the item
521+--------------------------------------------------*/
522+static size_t K_BHeapItemGetRightChildIndex(bheapitem_t *item)
523+{
524+ size_t rightchildindex = SIZE_MAX;
525+
526+ I_Assert(item != NULL);
527+ I_Assert(item->heapindex < (SIZE_MAX / 2));
528+
529+ rightchildindex = (item->heapindex * 2U) + 2U;
530+
531+ return rightchildindex;
532+}
533+
534+/*--------------------------------------------------
535+ static void K_BHeapSortUp(bheap_t *heap, bheapitem_t *item)
536+
537+ Sorts a heapitem up the list to its correct index, lower value items are higher up
538+
539+ Input Arguments:-
540+ heap - The heap to sort the item up.
541+ item - The item to sort up the heap
542+
543+ Return:-
544+ None
545+--------------------------------------------------*/
546+static void K_BHeapSortUp(bheap_t *heap, bheapitem_t *item)
547+{
548+ I_Assert(heap != NULL);
549+ I_Assert(K_BHeapValid(heap));
550+ I_Assert(item != NULL);
551+
552+ if (item->heapindex > 0U)
553+ {
554+ size_t parentindex = SIZE_MAX;
555+ do
556+ {
557+ parentindex = K_BHeapItemGetParentIndex(item);
558+
559+ // Swap the nodes if the parent has a higher value
560+ if (K_BHeapItemsCompare(heap, item, &heap->array[parentindex]) == item)
561+ {
562+ K_BHeapSwapItems(heap, item, &heap->array[parentindex]);
563+ }
564+ else
565+ {
566+ break;
567+ }
568+ } while (parentindex > 0U);
569+ }
570+}
571+
572+/*--------------------------------------------------
573+ static void K_BHeapSortDown(bheap_t *heap, bheapitem_t *item)
574+
575+ Sorts a heapitem down the list to its correct index, higher value items are further down
576+
577+ Input Arguments:-
578+ heap - The heap to sort the item down.
579+ item - The item to sort down the heap
580+
581+ Return:-
582+ None
583+--------------------------------------------------*/
584+static void K_BHeapSortDown(bheap_t *heap, bheapitem_t *item)
585+{
586+ I_Assert(heap != NULL);
587+ I_Assert(K_BHeapValid(heap));
588+ I_Assert(item != NULL);
589+
590+ if (heap->count > 0U)
591+ {
592+ size_t leftchildindex = SIZE_MAX;
593+ size_t rightchildindex = SIZE_MAX;
594+ bheapitem_t *leftchild = NULL;
595+ bheapitem_t *rightchild = NULL;
596+ bheapitem_t *swapchild = NULL;
597+ boolean noswapneeded = false;
598+
599+ do
600+ {
601+ leftchildindex = K_BHeapItemGetLeftChildIndex(item);
602+ rightchildindex = K_BHeapItemGetRightChildIndex(item);
603+
604+ if (leftchildindex < heap->count)
605+ {
606+ leftchild = &heap->array[leftchildindex];
607+ swapchild = leftchild;
608+ if (rightchildindex < heap->count)
609+ {
610+ rightchild = &heap->array[rightchildindex];
611+ // Choose the lower child node to swap with
612+ if (K_BHeapItemsCompare(heap, leftchild, rightchild) == rightchild)
613+ {
614+ swapchild = rightchild;
615+ }
616+ }
617+
618+ // Swap with the lower child, if it's lower than item
619+ if (K_BHeapItemsCompare(heap, swapchild, item) == swapchild)
620+ {
621+ K_BHeapSwapItems(heap, item, swapchild);
622+ }
623+ else
624+ {
625+ noswapneeded = true;
626+ }
627+ }
628+ else
629+ {
630+ noswapneeded = true;
631+ }
632+
633+ if (noswapneeded)
634+ {
635+ break;
636+ }
637+
638+ } while (item->heapindex < (heap->count - 1U));
639+ }
640+}
641+
642+/*--------------------------------------------------
643+ boolean K_BHeapInit(bheap_t *const heap, size_t initialcapacity)
644+
645+ See header file for description.
646+--------------------------------------------------*/
647+boolean K_BHeapInit(bheap_t *const heap, size_t initialcapacity)
648+{
649+ boolean initsuccess = false;
650+
651+ if (heap == NULL)
652+ {
653+ CONS_Debug(DBG_GAMELOGIC, "NULL heap in K_BHeapInit.\n");
654+ }
655+ else if (initialcapacity == 0U)
656+ {
657+ CONS_Debug(DBG_GAMELOGIC, "initialcapacity is 0 in K_BHeapInit.\n");
658+ }
659+ else
660+ {
661+ heap->array = Z_Calloc(initialcapacity * sizeof(bheapitem_t), PU_STATIC, NULL);
662+
663+ if (heap->array == NULL)
664+ {
665+ I_Error("K_BHeapInit: Out of Memory.");
666+ }
667+
668+ heap->capacity = initialcapacity;
669+ heap->count = 0U;
670+
671+ initsuccess = true;
672+ }
673+
674+ return initsuccess;
675+}
676+
677+/*--------------------------------------------------
678+ boolean K_BHeapValid(bheap_t *const heap)
679+
680+ See header file for description.
681+--------------------------------------------------*/
682+boolean K_BHeapValid(bheap_t *const heap)
683+{
684+ boolean heapvalid = false;
685+
686+ if (heap == NULL)
687+ {
688+ CONS_Debug(DBG_GAMELOGIC, "NULL heap in K_BHeapValid.\n");
689+ }
690+ else
691+ {
692+ if ((heap->capacity > 0U) && (heap->array != NULL))
693+ {
694+ heapvalid = true;
695+ }
696+ }
697+
698+ return heapvalid;
699+}
700+
701+/*--------------------------------------------------
702+ boolean K_BHeapPush(bheap_t *const heap, void *const item, UINT32 value, updateindexfunc changeindexcallback)
703+
704+ See header file for description.
705+--------------------------------------------------*/
706+boolean K_BHeapPush(bheap_t *const heap, void *const item, UINT32 value, updateindexfunc changeindexcallback)
707+{
708+ boolean pushsuccess = false;
709+ if (heap == NULL)
710+ {
711+ CONS_Debug(DBG_GAMELOGIC, "NULL heap in K_BHeapPush.\n");
712+ }
713+ else if (!K_BHeapValid(heap))
714+ {
715+ CONS_Debug(DBG_GAMELOGIC, "Uninitialised heap in K_BHeapPush.\n");
716+ }
717+ else if (item == NULL)
718+ {
719+ CONS_Debug(DBG_GAMELOGIC, "NULL item in K_BHeapPush.\n");
720+ }
721+ else if (heap->count >= (SIZE_MAX / 2))
722+ {
723+ CONS_Debug(DBG_GAMELOGIC, "Tried to push too many items on binary heap in K_BHeapPush.\n");
724+ }
725+ else
726+ {
727+ bheapitem_t *newitem = NULL;
728+ // If the capacity of the heap has been reached, a realloc is needed
729+ // I'm just doing a basic double of capacity for simplicity
730+ if (heap->count >= heap->capacity)
731+ {
732+ size_t newarraycapacity = heap->capacity * 2;
733+ heap->array = Z_Realloc(heap->array, newarraycapacity, PU_STATIC, NULL);
734+
735+ if (heap->array == NULL)
736+ {
737+ I_Error("K_BHeapPush: Out of Memory.");
738+ }
739+
740+ heap->capacity = newarraycapacity;
741+ }
742+
743+ newitem = &heap->array[heap->count];
744+
745+ newitem->heapindex = heap->count;
746+ newitem->owner = heap;
747+ newitem->data = item;
748+ newitem->value = value;
749+ newitem->indexchanged = changeindexcallback;
750+
751+ if (newitem->indexchanged != NULL)
752+ {
753+ newitem->indexchanged(newitem->data, newitem->heapindex);
754+ }
755+
756+ heap->count++;
757+
758+ K_BHeapSortUp(heap, &heap->array[heap->count - 1U]);
759+
760+ pushsuccess = true;
761+ }
762+
763+ return pushsuccess;
764+}
765+
766+/*--------------------------------------------------
767+ boolean K_BHeapPop(bheap_t *const heap, bheapitem_t *const returnitemstorage)
768+
769+ See header file for description.
770+--------------------------------------------------*/
771+boolean K_BHeapPop(bheap_t *const heap, bheapitem_t *const returnitemstorage)
772+{
773+ boolean popsuccess = false;
774+ if (heap == NULL)
775+ {
776+ CONS_Debug(DBG_GAMELOGIC, "NULL heap in K_BHeapPop.\n");
777+ }
778+ else if (!K_BHeapValid(heap))
779+ {
780+ CONS_Debug(DBG_GAMELOGIC, "Uninitialised heap in K_BHeapPop.\n");
781+ }
782+ else if (heap->count == 0U)
783+ {
784+ CONS_Debug(DBG_GAMELOGIC, "Tried to Pop from empty heap in K_BHeapPop.\n");
785+ }
786+ else if (returnitemstorage == NULL)
787+ {
788+ CONS_Debug(DBG_GAMELOGIC, "NULL returnitemstorage in K_BHeapPop.\n");
789+ }
790+ else
791+ {
792+ *returnitemstorage = heap->array[0];
793+
794+ // Invalidate the heap related data from the return item
795+ returnitemstorage->owner = NULL;
796+ returnitemstorage->heapindex = SIZE_MAX;
797+
798+ if (returnitemstorage->indexchanged != NULL)
799+ {
800+ returnitemstorage->indexchanged(returnitemstorage->data, returnitemstorage->heapindex);
801+ }
802+
803+ heap->count--;
804+
805+ heap->array[0] = heap->array[heap->count];
806+ heap->array[0].heapindex = 0U;
807+ memset(&heap->array[heap->count], 0x00, sizeof(bheapitem_t));
808+
809+ K_BHeapSortDown(heap, &heap->array[0]);
810+ popsuccess = true;
811+ }
812+
813+ return popsuccess;
814+}
815+
816+/*--------------------------------------------------
817+ boolean K_UpdateBHeapItemValue(bheapitem_t *const item, const UINT32 newvalue)
818+
819+ See header file for description.
820+--------------------------------------------------*/
821+boolean K_UpdateBHeapItemValue(bheapitem_t *const item, const UINT32 newvalue)
822+{
823+ boolean updatevaluesuccess = false;
824+ if (item == NULL)
825+ {
826+ CONS_Debug(DBG_GAMELOGIC, "NULL item in K_UpdateHeapItemValue.\n");
827+ }
828+ else if (item->owner == NULL)
829+ {
830+ CONS_Debug(DBG_GAMELOGIC, "item has NULL owner in K_UpdateHeapItemValue.\n");
831+ }
832+ else if (K_BHeapItemValidate(item->owner, item) == false)
833+ {
834+ CONS_Debug(DBG_GAMELOGIC, "Invalid item in K_UpdateHeapItemValue.\n");
835+ }
836+ else if (K_BHeapValid(item->owner) == false)
837+ {
838+ CONS_Debug(DBG_GAMELOGIC, "Invalid item owner in K_UpdateHeapItemValue.\n");
839+ }
840+ else
841+ {
842+ size_t oldvalue = item->value;
843+ item->value = newvalue;
844+ if (newvalue < oldvalue)
845+ {
846+ K_BHeapSortUp(item->owner, item);
847+ }
848+ else if (newvalue > oldvalue)
849+ {
850+ K_BHeapSortDown(item->owner, item);
851+ }
852+ else
853+ {
854+ // No change is needed as the value is the same
855+ }
856+
857+ }
858+
859+ return updatevaluesuccess;
860+}
861+
862+/*--------------------------------------------------
863+ size_t K_BHeapContains(bheap_t *const heap, void *const data, size_t index)
864+
865+ See header file for description.
866+--------------------------------------------------*/
867+size_t K_BHeapContains(bheap_t *const heap, void *const data, size_t index)
868+{
869+ size_t heapindexwithdata = SIZE_MAX;
870+
871+ if (heap == NULL)
872+ {
873+ CONS_Debug(DBG_GAMELOGIC, "NULL heap in K_BHeapContains.\n");
874+ }
875+ else if (!K_BHeapValid(heap))
876+ {
877+ CONS_Debug(DBG_GAMELOGIC, "Uninitialised heap in K_BHeapContains.\n");
878+ }
879+ else if (data == NULL)
880+ {
881+ CONS_Debug(DBG_GAMELOGIC, "NULL data in K_BHeapContains.\n");
882+ }
883+ else
884+ {
885+ if ((heap->count != 0U) && (index < heap->count))
886+ {
887+ if (heap->array[index].data == data)
888+ {
889+ heapindexwithdata = index;
890+ }
891+ }
892+ else if (index == SIZE_MAX)
893+ {
894+ size_t i;
895+ for (i = 0; i < heap->count; i++)
896+ {
897+ if (heap->array[i].data == data)
898+ {
899+ heapindexwithdata = i;
900+ break;
901+ }
902+ }
903+ }
904+ }
905+
906+ return heapindexwithdata;
907+}
908+
909+boolean K_BHeapFree(bheap_t *const heap)
910+{
911+ boolean freesuccess = false;
912+
913+ if (heap == NULL)
914+ {
915+ CONS_Debug(DBG_GAMELOGIC, "NULL heap in K_BHeapFree.\n");
916+ }
917+ else if (!K_BHeapValid(heap))
918+ {
919+ CONS_Debug(DBG_GAMELOGIC, "Uninitialised heap in K_BHeapFree.\n");
920+ }
921+ else
922+ {
923+ Z_Free(heap->array);
924+ heap->array = NULL;
925+ heap->capacity = 0U;
926+ heap->count = 0U;
927+ freesuccess = true;
928+ }
929+
930+ return freesuccess;
931+}
932diff --git a/src/k_bheap.h b/src/k_bheap.h
933new file mode 100644
934index 000000000..f1c6d2d5c
935--- /dev/null
936+++ b/src/k_bheap.h
937@@ -0,0 +1,141 @@
938+#ifndef __K_BHEAP__
939+#define __K_BHEAP__
940+
941+#include "doomdef.h"
942+
943+typedef void(*updateindexfunc)(void *const, const size_t);
944+
945+typedef struct bheapitem_s
946+{
947+ size_t heapindex; // The index in the heap this item is
948+ updateindexfunc indexchanged; // A callback function that is called when this item changes index to alert data
949+ struct bheap_s *owner; // The heap that owns this item
950+ void *data; // data for this heap item
951+ UINT32 value; // The value of this item, the lowest value item is first in the array
952+} bheapitem_t;
953+
954+typedef struct bheap_s
955+{
956+ size_t capacity; // capacity of the heap
957+ size_t count; // number of items in the heap
958+ bheapitem_t *array; // pointer to the heap items array
959+} bheap_t;
960+
961+
962+/*--------------------------------------------------
963+ boolean K_BHeapInit(bheap_t *const heap, size_t initialcapacity)
964+
965+ Initialises a binary heap.
966+
967+ Input Arguments:-
968+ heap - The heap to initialise
969+ initialcapacity - The initial capacity the heap should hold
970+
971+ Return:-
972+ True if the initialisation was successful, false if it wasn't.
973+--------------------------------------------------*/
974+
975+boolean K_BHeapInit(bheap_t *const heap, size_t initialcapacity);
976+
977+
978+/*--------------------------------------------------
979+ boolean K_BHeapValid(bheap_t *const heap)
980+
981+ Checks a binary heap for validity
982+
983+ Input Arguments:-
984+ heap - The heap to validate
985+
986+ Return:-
987+ True if the binary heap is valid, false if it isn't
988+--------------------------------------------------*/
989+
990+boolean K_BHeapValid(bheap_t *const heap);
991+
992+
993+/*--------------------------------------------------
994+ boolean K_BHeapPush(bheap_t *const heap, void *const item, const UINT32 value, updateindexfunc changeindexcallback)
995+
996+ Adds a new item to a binary heap.
997+
998+ Input Arguments:-
999+ heap - The heap to add to.
1000+ item - The item to add to the heap.
1001+ value - The value of this item for the heap, lowest is first in the heap
1002+ changeindexcallback - A callback function that is called when the item's index changes, can be NULL
1003+
1004+ Return:-
1005+ True if the push to the heap was successful, false if it wasn't due to invalid parameters
1006+--------------------------------------------------*/
1007+
1008+boolean K_BHeapPush(bheap_t *const heap, void *const item, UINT32 value, updateindexfunc changeindexcallback);
1009+
1010+
1011+/*--------------------------------------------------
1012+ boolean K_BHeapPop(bheap_t *const heap, bheapitem_t *const returnitemstorage)
1013+
1014+ Pops the first item off of the heap, then orders it back to be correct.
1015+
1016+ Input Arguments:-
1017+ heap - The heap to pop from.
1018+ returnitemstorage - The first item on the Heap is placed in here
1019+
1020+ Return:-
1021+ true if the pop from the heap was successful, false if it wasn't.
1022+--------------------------------------------------*/
1023+
1024+boolean K_BHeapPop(bheap_t *const heap, bheapitem_t *const returnitemstorage);
1025+
1026+
1027+/*--------------------------------------------------
1028+ boolean K_UpdateBHeapItemValue(bheapitem_t *const item, const UINT32 newvalue)
1029+
1030+ Updates the heap item's value, and reorders it in the array appropriately. Only works if the item is in a heap
1031+ validly. If it's a heapitem that is not currently in a heap (ie it's been popped off) just change the value
1032+ manually.
1033+
1034+ Input Arguments:-
1035+ item - The item to update the value of.
1036+ newvalue - The new value the item will hold
1037+
1038+ Return:-
1039+ true if the update was successful, false if it wasn't
1040+--------------------------------------------------*/
1041+
1042+boolean K_UpdateBHeapItemValue(bheapitem_t *const item, const UINT32 newvalue);
1043+
1044+
1045+/*--------------------------------------------------
1046+ size_t K_BHeapContains(bheap_t *const heap, void *const data, size_t index)
1047+
1048+ Checks to see if data is contained in the heap. If index is not SIZE_MAX, then only the index sent in is
1049+ checked. Otherwise every index is checked linearly.
1050+
1051+ Input Arguments:-
1052+ heap - The heap to check the contents of
1053+ data - The data that is being checked for
1054+ index - The index of the heap to check, if SIZE_MAX, check every index
1055+
1056+ Return:-
1057+ The heap index that contains data, SIZE_MAX if it is not in the heap
1058+--------------------------------------------------*/
1059+
1060+size_t K_BHeapContains(bheap_t *const heap, void *const data, size_t index);
1061+
1062+
1063+/*--------------------------------------------------
1064+ boolean K_BHeapFree(bheap_t *const heap)
1065+
1066+ Free the binary heap.
1067+ This does NOT free the data held within the binary heap items. Make sure those can still be freed manually.
1068+
1069+ Input Arguments:-
1070+ heap - The heap to free
1071+
1072+ Return:-
1073+ True if the heap was freed successfully, false if the heap wasn't valid to free
1074+--------------------------------------------------*/
1075+
1076+boolean K_BHeapFree(bheap_t *const heap);
1077+
1078+#endif
1079diff --git a/src/k_kart.c b/src/k_kart.c
1080index 83b202f02..3fe2b9b3c 100644
1081--- a/src/k_kart.c
1082+++ b/src/k_kart.c
1083@@ -12,6 +12,7 @@
1084 #include "m_random.h"
1085 #include "p_local.h"
1086 #include "p_slopes.h"
1087+#include "p_setup.h"
1088 #include "r_draw.h"
1089 #include "r_local.h"
1090 #include "s_sound.h"
1091@@ -24,6 +25,8 @@
1092 #include "lua_hud.h" // For Lua hud checks
1093 #include "lua_hook.h" // For MobjDamage and ShouldDamage
1094
1095+#include "k_waypoint.h"
1096+
1097 // SOME IMPORTANT VARIABLES DEFINED IN DOOMDEF.H:
1098 // gamespeed is cc (0 for easy, 1 for normal, 2 for hard)
1099 // franticitems is Frantic Mode items, bool
1100@@ -587,6 +590,7 @@ void K_RegisterKartStuff(void)
1101 CV_RegisterVar(&cv_kartdebugshrink);
1102 CV_RegisterVar(&cv_kartdebugdistribution);
1103 CV_RegisterVar(&cv_kartdebughuddrop);
1104+ CV_RegisterVar(&cv_kartdebugwaypoints);
1105
1106 CV_RegisterVar(&cv_kartdebugcheckpoint);
1107 CV_RegisterVar(&cv_kartdebugnodes);
1108@@ -706,7 +710,7 @@ static INT32 K_KartItemOddsBattle[NUMKARTRESULTS][6] =
1109 /*Jawz x2*/ { 0, 0, 1, 2, 4, 2 } // Jawz x2
1110 };
1111
1112-#define DISTVAR (64*14)
1113+#define DISTVAR (2048) // Magic number distance for use with item roulette tiers
1114
1115 /** \brief Item Roulette for Kart
1116
1117@@ -845,9 +849,7 @@ static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean sp
1118
1119 if (first != -1 && second != -1) // calculate 2nd's distance from 1st, for SPB
1120 {
1121- secondist = P_AproxDistance(P_AproxDistance(players[first].mo->x - players[second].mo->x,
1122- players[first].mo->y - players[second].mo->y),
1123- players[first].mo->z - players[second].mo->z) / mapobjectscale;
1124+ secondist = players[second].distancetofinish - players[first].distancetofinish;
1125 if (franticitems)
1126 secondist = (15 * secondist) / 14;
1127 secondist = ((28 + (8-pingame)) * secondist) / 28;
1128@@ -896,7 +898,8 @@ static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean sp
1129 POWERITEMODDS(newodds);
1130 break;
1131 case KITEM_SPB:
1132- if ((indirectitemcooldown > 0) || (pexiting > 0) || (secondist/DISTVAR < 3))
1133+ if ((indirectitemcooldown > 0) || (secondist/DISTVAR < 3)
1134+ || (first != -1 && players[first].distancetofinish > 8*DISTVAR)) // No SPB near the end of the race
1135 newodds = 0;
1136 else
1137 newodds *= min((secondist/DISTVAR)-4, 3); // POWERITEMODDS(newodds);
1138@@ -925,13 +928,13 @@ static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean sp
1139 return newodds;
1140 }
1141
1142-//{ SRB2kart Roulette Code - Distance Based, no waypoints
1143+//{ SRB2kart Roulette Code - Distance Based, yes waypoints
1144
1145-static INT32 K_FindUseodds(player_t *player, fixed_t mashed, INT32 pdis, INT32 bestbumper, boolean spbrush)
1146+static UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper, boolean spbrush)
1147 {
1148- INT32 i;
1149- INT32 n = 0;
1150- INT32 useodds = 0;
1151+ UINT8 i;
1152+ UINT8 n = 0;
1153+ UINT8 useodds = 0;
1154 UINT8 disttable[14];
1155 UINT8 totallen = 0;
1156 UINT8 distlen = 0;
1157@@ -939,7 +942,7 @@ static INT32 K_FindUseodds(player_t *player, fixed_t mashed, INT32 pdis, INT32 b
1158
1159 for (i = 0; i < 8; i++)
1160 {
1161- INT32 j;
1162+ UINT8 j;
1163 boolean available = false;
1164
1165 if (G_BattleGametype() && i > 5)
1166@@ -1000,9 +1003,9 @@ static INT32 K_FindUseodds(player_t *player, fixed_t mashed, INT32 pdis, INT32 b
1167 SETUPDISTTABLE(6,3);
1168 SETUPDISTTABLE(7,1);
1169
1170- if (pdis <= 0) // (64*14) * 0 = 0
1171+ if (pdis == 0)
1172 useodds = disttable[0];
1173- else if (pdis > DISTVAR * ((12 * distlen) / 14)) // (64*14) * 12 = 10752
1174+ else if (pdis > DISTVAR * ((12 * distlen) / 14))
1175 useodds = disttable[distlen-1];
1176 else
1177 {
1178@@ -1027,11 +1030,11 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd)
1179 INT32 i;
1180 UINT8 pingame = 0;
1181 UINT8 roulettestop;
1182- INT32 pdis = 0;
1183- INT32 useodds = 0;
1184+ UINT32 pdis = 0;
1185+ UINT8 useodds = 0;
1186 INT32 spawnchance[NUMKARTRESULTS];
1187 INT32 totalspawnchance = 0;
1188- INT32 bestbumper = 0;
1189+ UINT8 bestbumper = 0;
1190 fixed_t mashed = 0;
1191 boolean dontforcespb = false;
1192 boolean spbrush = false;
1193@@ -1054,6 +1057,10 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd)
1194 bestbumper = players[i].kartstuff[k_bumper];
1195 }
1196
1197+ // No forced SPB in 1v1s, it has to be randomly rolled
1198+ if (pingame <= 2)
1199+ dontforcespb = true;
1200+
1201 // This makes the roulette produce the random noises.
1202 if ((player->kartstuff[k_itemroulette] % 3) == 1 && P_IsDisplayPlayer(player))
1203 {
1204@@ -1085,15 +1092,18 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd)
1205
1206 for (i = 0; i < MAXPLAYERS; i++)
1207 {
1208- if (playeringame[i] && !players[i].spectator && players[i].mo
1209- && players[i].kartstuff[k_position] < player->kartstuff[k_position])
1210- pdis += P_AproxDistance(P_AproxDistance(players[i].mo->x - player->mo->x,
1211- players[i].mo->y - player->mo->y),
1212- players[i].mo->z - player->mo->z) / mapobjectscale
1213- * (pingame - players[i].kartstuff[k_position])
1214- / max(1, ((pingame - 1) * (pingame + 1) / 3));
1215+ if (playeringame[i] && !players[i].spectator
1216+ && players[i].kartstuff[k_position] == 1)
1217+ {
1218+ // This player is first! Yay!
1219+ pdis = player->distancetofinish - players[i].distancetofinish;
1220+ break;
1221+ }
1222 }
1223
1224+ if (mapobjectscale != FRACUNIT)
1225+ pdis = FixedDiv(pdis, mapobjectscale);
1226+
1227 if (franticitems) // Frantic items make the distances between everyone artifically higher, for crazier items
1228 pdis = (15 * pdis) / 14;
1229
1230@@ -1905,6 +1915,121 @@ static void K_SpawnBrakeDriftSparks(player_t *player) // Be sure to update the m
1231 sparks->flags2 |= MF2_DONTDRAW;
1232 }
1233
1234+/** \brief Preps a player to respawn
1235+
1236+ \param player player to respawn
1237+
1238+ \return void
1239+*/
1240+void K_DoIngameRespawn(player_t *player)
1241+{
1242+ if (!player->mo || P_MobjWasRemoved(player->mo))
1243+ return;
1244+
1245+ if (player->kartstuff[k_respawn])
1246+ return;
1247+
1248+ if (leveltime <= starttime)
1249+ return;
1250+
1251+ if (player->nextwaypoint == NULL) // Starpost xyz not initalized(?)
1252+ {
1253+ UINT32 bestdist = UINT32_MAX;
1254+ mapthing_t *beststart = NULL;
1255+ UINT8 numstarts = 0;
1256+
1257+ if (G_RaceGametype())
1258+ {
1259+ numstarts = numcoopstarts;
1260+ }
1261+ else if (G_BattleGametype())
1262+ {
1263+ numstarts = numdmstarts;
1264+ }
1265+
1266+ if (numstarts > 0)
1267+ {
1268+ UINT8 i = 0;
1269+
1270+ for (i = 0; i < numstarts; i++)
1271+ {
1272+ UINT32 dist = UINT32_MAX;
1273+ mapthing_t *checkstart = NULL;
1274+
1275+ if (G_RaceGametype())
1276+ {
1277+ checkstart = playerstarts[i];
1278+ }
1279+ else if (G_BattleGametype())
1280+ {
1281+ checkstart = deathmatchstarts[i];
1282+ }
1283+ else
1284+ {
1285+ break;
1286+ }
1287+
1288+ dist = (UINT32)P_AproxDistance((player->mo->x >> FRACBITS) - checkstart->x,
1289+ (player->mo->y >> FRACBITS) - checkstart->y);
1290+
1291+ if (dist < bestdist)
1292+ {
1293+ beststart = checkstart;
1294+ bestdist = dist;
1295+ }
1296+ }
1297+ }
1298+
1299+ if (beststart == NULL)
1300+ {
1301+ CONS_Alert(CONS_WARNING, "No respawn points!\n");
1302+ }
1303+ else
1304+ {
1305+ sector_t *s;
1306+ fixed_t z = (beststart->options >> ZSHIFT);
1307+
1308+ player->starpostx = beststart->x;
1309+ player->starposty = beststart->y;
1310+ s = R_PointInSubsector(beststart->x << FRACBITS, beststart->y << FRACBITS)->sector;
1311+
1312+ if (beststart->options & MTF_OBJECTFLIP)
1313+ {
1314+ player->starpostz = (
1315+#ifdef ESLOPE
1316+ s->c_slope ? P_GetZAt(s->c_slope, beststart->x << FRACBITS, beststart->y << FRACBITS) :
1317+#endif
1318+ s->ceilingheight) >> FRACBITS;
1319+
1320+ if (z)
1321+ player->starpostz -= z;
1322+
1323+ player->starpostz -= mobjinfo[MT_PLAYER].height;
1324+ player->kartstuff[k_starpostflip] = 1;
1325+ }
1326+ else
1327+ {
1328+ player->starpostz = (
1329+#ifdef ESLOPE
1330+ s->f_slope ? P_GetZAt(s->f_slope, beststart->x << FRACBITS, beststart->y << FRACBITS) :
1331+#endif
1332+ s->floorheight) >> FRACBITS;
1333+
1334+ if (z)
1335+ player->starpostz += z;
1336+
1337+ player->kartstuff[k_starpostflip] = 0;
1338+ }
1339+ }
1340+ }
1341+
1342+ player->mo->flags &= ~(MF_SOLID|MF_SHOOTABLE);
1343+ player->mo->flags |= MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY;
1344+ player->mo->flags2 &= ~MF2_DONTDRAW;
1345+
1346+ player->kartstuff[k_respawn] = 48;
1347+}
1348+
1349 /** \brief Calculates the respawn timer and drop-boosting
1350
1351 \param player player object passed from K_KartPlayerThink
1352@@ -1920,39 +2045,136 @@ void K_RespawnChecker(player_t *player)
1353
1354 if (player->kartstuff[k_respawn] > 1)
1355 {
1356- player->kartstuff[k_respawn]--;
1357- player->mo->momz = 0;
1358+ fixed_t destx = 0, desty = 0, destz = 0;
1359+
1360+ player->mo->momx = player->mo->momy = player->mo->momz = 0;
1361 player->powers[pw_flashing] = 2;
1362 player->powers[pw_nocontrol] = 2;
1363- if (leveltime % 8 == 0)
1364+
1365+ if (leveltime % 8 == 0 && !mapreset)
1366+ S_StartSound(player->mo, sfx_s3kcas);
1367+
1368+ destx = (player->starpostx << FRACBITS);
1369+ desty = (player->starposty << FRACBITS);
1370+ destz = (player->starpostz << FRACBITS);
1371+
1372+ if (player->kartstuff[k_starpostflip])
1373+ destz -= (128 * mapobjectscale) + (player->mo->height);
1374+ else
1375+ destz += (128 * mapobjectscale);
1376+
1377+ if (player->mo->x != destx || player->mo->y != desty || player->mo->z != destz)
1378 {
1379- INT32 i;
1380- if (!mapreset)
1381- S_StartSound(player->mo, sfx_s3kcas);
1382+ fixed_t step = 64*mapobjectscale;
1383+ fixed_t dist = P_AproxDistance(P_AproxDistance(player->mo->x - destx, player->mo->y - desty), player->mo->z - destz);
1384
1385- for (i = 0; i < 8; i++)
1386+ if (dist <= step) // You're ready to respawn
1387+ {
1388+ P_TryMove(player->mo, destx, desty, true);
1389+ player->mo->z = destz;
1390+ }
1391+ else
1392 {
1393- mobj_t *mo;
1394- angle_t newangle;
1395- fixed_t newx, newy, newz;
1396+ fixed_t stepx = 0, stepy = 0, stepz = 0;
1397+ angle_t stepha = R_PointToAngle2(player->mo->x, player->mo->y, destx, desty);
1398+ angle_t stepva = R_PointToAngle2(0, player->mo->z, P_AproxDistance(player->mo->x - destx, player->mo->y - desty), destz);
1399+ fixed_t laserx = 0, lasery = 0, laserz = 0;
1400+ UINT8 lasersteps = 4;
1401+
1402+ // Move toward the respawn point
1403+ stepx = FixedMul(FixedMul(FINECOSINE(stepha >> ANGLETOFINESHIFT), step), FINECOSINE(stepva >> ANGLETOFINESHIFT));
1404+ stepy = FixedMul(FixedMul(FINESINE(stepha >> ANGLETOFINESHIFT), step), FINECOSINE(stepva >> ANGLETOFINESHIFT));
1405+ stepz = FixedMul(FINESINE(stepva >> ANGLETOFINESHIFT), 2*step);
1406+
1407+ P_TryMove(player->mo, player->mo->x + stepx, player->mo->y + stepy, true);
1408+ player->mo->z += stepz;
1409+
1410+ // Spawn lasers along the path
1411+ laserx = player->mo->x + (stepx / 2);
1412+ lasery = player->mo->y + (stepy / 2);
1413+ laserz = player->mo->z + (stepz / 2);
1414+ dist = P_AproxDistance(P_AproxDistance(laserx - destx, lasery - desty), laserz - destz);
1415+
1416+ if (dist > step/2)
1417+ {
1418+ while (lasersteps)
1419+ {
1420
1421- newangle = FixedAngle(((360/8)*i)*FRACUNIT);
1422- newx = player->mo->x + P_ReturnThrustX(player->mo, newangle, 31 * player->mo->scale);
1423- newy = player->mo->y + P_ReturnThrustY(player->mo, newangle, 31 * player->mo->scale);
1424- if (player->mo->eflags & MFE_VERTICALFLIP)
1425- newz = player->mo->z + player->mo->height;
1426- else
1427- newz = player->mo->z;
1428+ stepha = R_PointToAngle2(laserx, lasery, destx, desty);
1429+ stepva = R_PointToAngle2(0, laserz, P_AproxDistance(laserx - destx, lasery - desty), destz);
1430+
1431+ stepx = FixedMul(FixedMul(FINECOSINE(stepha >> ANGLETOFINESHIFT), step), FINECOSINE(stepva >> ANGLETOFINESHIFT));
1432+ stepy = FixedMul(FixedMul(FINESINE(stepha >> ANGLETOFINESHIFT), step), FINECOSINE(stepva >> ANGLETOFINESHIFT));
1433+ stepz = FixedMul(FINESINE(stepva >> ANGLETOFINESHIFT), 2*step);
1434
1435- mo = P_SpawnMobj(newx, newy, newz, MT_DEZLASER);
1436- if (mo)
1437+ laserx += stepx;
1438+ lasery += stepy;
1439+ laserz += stepz;
1440+ dist = P_AproxDistance(P_AproxDistance(laserx - destx, lasery - desty), laserz - destz);
1441+
1442+ if (dist <= step/2)
1443+ break;
1444+
1445+ lasersteps--;
1446+ }
1447+ }
1448+
1449+ if (lasersteps == 0) // Don't spawn them beyond the respawn point.
1450+ {
1451+ mobj_t *laser;
1452+
1453+ laser = P_SpawnMobj(laserx, lasery, laserz + (player->mo->height / 2), MT_DEZLASER);
1454+
1455+ if (laser && !P_MobjWasRemoved(laser))
1456+ {
1457+ P_SetMobjState(laser, S_DEZLASER_TRAIL1);
1458+ if (player->mo->eflags & MFE_VERTICALFLIP)
1459+ laser->eflags |= MFE_VERTICALFLIP;
1460+ P_SetTarget(&laser->target, player->mo);
1461+ laser->angle = stepha + ANGLE_90;
1462+ P_SetScale(laser, (laser->destscale = FRACUNIT));
1463+ }
1464+ }
1465+ }
1466+ }
1467+ else
1468+ {
1469+ player->kartstuff[k_respawn]--;
1470+
1471+ player->mo->flags |= MF_SOLID|MF_SHOOTABLE;
1472+ player->mo->flags &= ~(MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY);
1473+ if (!(player->pflags & PF_NOCLIP))
1474+ player->mo->flags &= ~MF_NOCLIP;
1475+
1476+ if (leveltime % 8 == 0)
1477+ {
1478+ INT32 i;
1479+
1480+ for (i = 0; i < 8; i++)
1481 {
1482+ mobj_t *laser;
1483+ angle_t newangle;
1484+ fixed_t newx, newy, newz;
1485+
1486+ newangle = FixedAngle(((360/8)*i)*FRACUNIT);
1487+ newx = player->mo->x + P_ReturnThrustX(player->mo, newangle, 31 * player->mo->scale);
1488+ newy = player->mo->y + P_ReturnThrustY(player->mo, newangle, 31 * player->mo->scale);
1489 if (player->mo->eflags & MFE_VERTICALFLIP)
1490- mo->eflags |= MFE_VERTICALFLIP;
1491- P_SetTarget(&mo->target, player->mo);
1492- mo->angle = newangle+ANGLE_90;
1493+ newz = player->mo->z + player->mo->height;
1494+ else
1495+ newz = player->mo->z;
1496+
1497+ laser = P_SpawnMobj(newx, newy, newz, MT_DEZLASER);
1498+
1499+ if (laser && !P_MobjWasRemoved(laser))
1500+ {
1501+ if (player->mo->eflags & MFE_VERTICALFLIP)
1502+ laser->eflags |= MFE_VERTICALFLIP;
1503+ P_SetTarget(&laser->target, player->mo);
1504+ laser->angle = newangle+ANGLE_90;
1505 mo->momz = (8 * player->mo->scale) * P_MobjFlip(player->mo);
1506 P_SetScale(mo, (mo->destscale = player->mo->scale));
1507+ }
1508 }
1509 }
1510 }
1511@@ -1996,11 +2218,44 @@ void K_RespawnChecker(player_t *player)
1512 player->kartstuff[k_startboost] = 50;
1513 K_SpawnDashDustRelease(player);
1514 }
1515+
1516 player->mo->colorized = false;
1517 player->kartstuff[k_dropdash] = 0;
1518 player->kartstuff[k_respawn] = 0;
1519+
1520 //P_PlayRinglossSound(player->mo);
1521 P_PlayerRingBurst(player, 3);
1522+
1523+ if (G_BattleGametype())
1524+ {
1525+ if (player->kartstuff[k_bumper] > 0)
1526+ {
1527+ if (player->kartstuff[k_bumper] == 1)
1528+ {
1529+ mobj_t *karmahitbox = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_KARMAHITBOX); // Player hitbox is too small!!
1530+ P_SetTarget(&karmahitbox->target, player->mo);
1531+ karmahitbox->destscale = player->mo->scale;
1532+ P_SetScale(karmahitbox, player->mo->scale);
1533+ CONS_Printf(M_GetText("%s lost all of their bumpers!\n"), player_names[player-players]);
1534+ }
1535+ player->kartstuff[k_bumper]--;
1536+ if (K_IsPlayerWanted(player))
1537+ K_CalculateBattleWanted();
1538+ }
1539+
1540+ if (!player->kartstuff[k_bumper])
1541+ {
1542+ player->kartstuff[k_comebacktimer] = comebacktime;
1543+ if (player->kartstuff[k_comebackmode] == 2)
1544+ {
1545+ mobj_t *poof = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EXPLODE);
1546+ S_StartSound(poof, mobjinfo[MT_KARMAHITBOX].seesound);
1547+ player->kartstuff[k_comebackmode] = 0;
1548+ }
1549+ }
1550+
1551+ K_CheckBumpers();
1552+ }
1553 }
1554 }
1555 }
1556@@ -4116,6 +4371,7 @@ void K_DoSneaker(player_t *player, INT32 type)
1557 static void K_DoShrink(player_t *user)
1558 {
1559 INT32 i;
1560+ mobj_t *mobj, *next;
1561
1562 S_StartSound(user->mo, sfx_kc46); // Sound the BANG!
1563 user->pflags |= PF_ATTACKDOWN;
1564@@ -4133,10 +4389,7 @@ static void K_DoShrink(player_t *user)
1565 // Grow should get taken away.
1566 if (players[i].kartstuff[k_growshrinktimer] > 0)
1567 K_RemoveGrowShrink(&players[i]);
1568- // Don't hit while invulnerable!
1569- else if (!players[i].kartstuff[k_invincibilitytimer]
1570- && players[i].kartstuff[k_growshrinktimer] <= 0
1571- && !players[i].kartstuff[k_hyudorotimer])
1572+ else
1573 {
1574 // Start shrinking!
1575 K_DropItems(&players[i]);
1576@@ -4153,6 +4406,33 @@ static void K_DoShrink(player_t *user)
1577 }
1578 }
1579 }
1580+
1581+ // kill everything in the kitem list while we're at it:
1582+ for (mobj = kitemcap; mobj; mobj = next)
1583+ {
1584+ next = mobj->itnext;
1585+
1586+ // check if the item is being held by a player behind us before removing it.
1587+ // check if the item is a "shield" first, bc i'm p sure thrown items keep the player that threw em as target anyway
1588+
1589+ if (mobj->type == MT_BANANA_SHIELD || mobj->type == MT_JAWZ_SHIELD ||
1590+ mobj->type == MT_SSMINE_SHIELD || mobj->type == MT_EGGMANITEM_SHIELD ||
1591+ mobj->type == MT_SINK_SHIELD || mobj->type == MT_ORBINAUT_SHIELD)
1592+ {
1593+ if (mobj->target && mobj->target->player)
1594+ {
1595+ if (mobj->target->player->kartstuff[k_position] > user->kartstuff[k_position])
1596+ continue; // this guy's behind us, don't take his stuff away!
1597+ }
1598+ }
1599+
1600+ mobj->destscale = 0;
1601+ mobj->flags &= ~(MF_SOLID|MF_SHOOTABLE|MF_SPECIAL);
1602+ mobj->flags |= MF_NOCLIPTHING; // Just for safety
1603+
1604+ if (mobj->type == MT_SPB)
1605+ spbplace = -1;
1606+ }
1607 }
1608
1609
1610@@ -4325,6 +4605,7 @@ void K_DropHnextList(player_t *player)
1611
1612 dropwork = P_SpawnMobj(work->x, work->y, work->z, type);
1613 P_SetTarget(&dropwork->target, player->mo);
1614+ P_AddKartItem(dropwork); // needs to be called here so shrink can bust items off players in front of the user.
1615 dropwork->angle = work->angle;
1616 dropwork->flags2 = work->flags2;
1617 dropwork->flags |= MF_NOCLIPTHING;
1618@@ -5600,8 +5881,15 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
1619 player->kartstuff[k_stolentimer]--;
1620
1621 if (player->kartstuff[k_squishedtimer])
1622+ {
1623 player->kartstuff[k_squishedtimer]--;
1624
1625+ if ((player->kartstuff[k_squishedtimer] == 0) && !(player->pflags & PF_NOCLIP))
1626+ {
1627+ player->mo->flags &= ~MF_NOCLIP;
1628+ }
1629+ }
1630+
1631 if (player->kartstuff[k_justbumped])
1632 player->kartstuff[k_justbumped]--;
1633
1634@@ -5702,6 +5990,10 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
1635
1636 void K_KartPlayerAfterThink(player_t *player)
1637 {
1638+ // Moved to afterthink, as at this point the players have had their distances to the finish line updated
1639+ // and this will correctly account for all players
1640+ K_KartUpdatePosition(player);
1641+
1642 if (player->kartstuff[k_curshield]
1643 || player->kartstuff[k_invincibilitytimer]
1644 || (player->kartstuff[k_growshrinktimer] != 0 && player->kartstuff[k_growshrinktimer] % 5 == 4)) // 4 instead of 0 because this is afterthink!
1645@@ -5763,6 +6055,346 @@ void K_KartPlayerAfterThink(player_t *player)
1646 }
1647 }
1648
1649+/*--------------------------------------------------
1650+ static waypoint_t *K_GetPlayerNextWaypoint(player_t *player)
1651+
1652+ Gets the next waypoint of a player, by finding their closest waypoint, then checking which of itself and next or
1653+ previous waypoints are infront of the player.
1654+
1655+ Input Arguments:-
1656+ player - The player the next waypoint is being found for
1657+
1658+ Return:-
1659+ The waypoint that is the player's next waypoint
1660+--------------------------------------------------*/
1661+static waypoint_t *K_GetPlayerNextWaypoint(player_t *player)
1662+{
1663+ waypoint_t *bestwaypoint = NULL;
1664+
1665+ if ((player != NULL) && (player->mo != NULL) && (P_MobjWasRemoved(player->mo) == false))
1666+ {
1667+ waypoint_t *waypoint = K_GetBestWaypointForMobj(player->mo);
1668+ boolean updaterespawn = false;
1669+
1670+ bestwaypoint = waypoint;
1671+
1672+ // check the waypoint's location in relation to the player
1673+ // If it's generally in front, it's fine, otherwise, use the best next/previous waypoint.
1674+ // EXCEPTION: If our best waypoint is the finishline AND we're facing towards it, don't do this.
1675+ // Otherwise it breaks the distance calculations.
1676+ if (waypoint != NULL)
1677+ {
1678+ boolean finishlinehack = false;
1679+ angle_t playerangle = player->mo->angle;
1680+ angle_t momangle = player->mo->angle;
1681+ angle_t angletowaypoint =
1682+ R_PointToAngle2(player->mo->x, player->mo->y, waypoint->mobj->x, waypoint->mobj->y);
1683+ angle_t angledelta = ANGLE_MAX;
1684+ angle_t momdelta = ANGLE_MAX;
1685+
1686+ if (player->mo->momx != 0 || player->mo->momy != 0)
1687+ {
1688+ // Defaults to facing angle if you're not moving.
1689+ momangle = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy);
1690+ }
1691+
1692+ angledelta = playerangle - angletowaypoint;
1693+ if (angledelta > ANGLE_180)
1694+ {
1695+ angledelta = InvAngle(angledelta);
1696+ }
1697+
1698+ momdelta = momangle - angletowaypoint;
1699+ if (momdelta > ANGLE_180)
1700+ {
1701+ momdelta = InvAngle(momdelta);
1702+ }
1703+
1704+ if (bestwaypoint == K_GetFinishLineWaypoint())
1705+ {
1706+ // facing towards the finishline
1707+ if (angledelta <= ANGLE_90)
1708+ {
1709+ finishlinehack = true;
1710+ }
1711+ }
1712+
1713+ // We're using a lot of angle calculations here, because only using facing angle or only using momentum angle both have downsides.
1714+ // nextwaypoints will be picked if you're facing OR moving forward.
1715+ // prevwaypoints will be picked if you're facing AND moving backward.
1716+ if ((angledelta > ANGLE_45 || momdelta > ANGLE_45)
1717+ && (finishlinehack == false))
1718+ {
1719+ angle_t nextbestdelta = angledelta;
1720+ angle_t nextbestmomdelta = momdelta;
1721+ size_t i = 0U;
1722+
1723+ if ((waypoint->nextwaypoints != NULL) && (waypoint->numnextwaypoints > 0U))
1724+ {
1725+ for (i = 0U; i < waypoint->numnextwaypoints; i++)
1726+ {
1727+ angletowaypoint = R_PointToAngle2(
1728+ player->mo->x, player->mo->y,
1729+ waypoint->nextwaypoints[i]->mobj->x, waypoint->nextwaypoints[i]->mobj->y);
1730+
1731+ angledelta = playerangle - angletowaypoint;
1732+ if (angledelta > ANGLE_180)
1733+ {
1734+ angledelta = InvAngle(angledelta);
1735+ }
1736+
1737+ momdelta = momangle - angletowaypoint;
1738+ if (momdelta > ANGLE_180)
1739+ {
1740+ momdelta = InvAngle(momdelta);
1741+ }
1742+
1743+ if (angledelta < nextbestdelta || momdelta < nextbestmomdelta)
1744+ {
1745+ bestwaypoint = waypoint->nextwaypoints[i];
1746+
1747+ if (angledelta < nextbestdelta)
1748+ {
1749+ nextbestdelta = angledelta;
1750+ }
1751+ if (momdelta < nextbestmomdelta)
1752+ {
1753+ nextbestmomdelta = momdelta;
1754+ }
1755+
1756+ // Remove wrong way flag if we're using nextwaypoints
1757+ player->kartstuff[k_wrongway] = 0;
1758+ updaterespawn = true;
1759+ }
1760+ }
1761+ }
1762+
1763+ if ((waypoint->prevwaypoints != NULL) && (waypoint->numprevwaypoints > 0U))
1764+ {
1765+ for (i = 0U; i < waypoint->numprevwaypoints; i++)
1766+ {
1767+ angletowaypoint = R_PointToAngle2(
1768+ player->mo->x, player->mo->y,
1769+ waypoint->prevwaypoints[i]->mobj->x, waypoint->prevwaypoints[i]->mobj->y);
1770+
1771+ angledelta = playerangle - angletowaypoint;
1772+ if (angledelta > ANGLE_180)
1773+ {
1774+ angledelta = InvAngle(angledelta);
1775+ }
1776+
1777+ momdelta = momangle - angletowaypoint;
1778+ if (momdelta > ANGLE_180)
1779+ {
1780+ momdelta = InvAngle(momdelta);
1781+ }
1782+
1783+ if (angledelta < nextbestdelta && momdelta < nextbestmomdelta)
1784+ {
1785+ bestwaypoint = waypoint->prevwaypoints[i];
1786+
1787+ nextbestdelta = angledelta;
1788+ nextbestmomdelta = momdelta;
1789+
1790+ // Set wrong way flag if we're using prevwaypoints
1791+ player->kartstuff[k_wrongway] = 1;
1792+ updaterespawn = false;
1793+ }
1794+ }
1795+ }
1796+ }
1797+ }
1798+
1799+ if (!P_IsObjectOnGround(player->mo))
1800+ {
1801+ updaterespawn = false;
1802+ }
1803+
1804+ // Respawn point should only be updated when we're going to a nextwaypoint
1805+ if ((updaterespawn) &&
1806+ (bestwaypoint != NULL) &&
1807+ (bestwaypoint != player->nextwaypoint) &&
1808+ (player->kartstuff[k_respawn] == 0) &&
1809+ (K_GetWaypointIsShortcut(bestwaypoint) == false) && (K_GetWaypointIsEnabled(bestwaypoint) == true))
1810+ {
1811+ size_t i = 0U;
1812+ waypoint_t *aimwaypoint = NULL;
1813+
1814+ player->starpostx = bestwaypoint->mobj->x >> FRACBITS;
1815+ player->starposty = bestwaypoint->mobj->y >> FRACBITS;
1816+ player->starpostz = bestwaypoint->mobj->z >> FRACBITS;
1817+ player->kartstuff[k_starpostflip] = (bestwaypoint->mobj->flags2 & MF2_OBJECTFLIP);
1818+
1819+ // starpostangle is to the first valid nextwaypoint for simplicity
1820+ // if we reach the last waypoint and it's still not valid, just use it anyway. Someone needs to fix
1821+ // their map!
1822+ for (i = 0U; i < bestwaypoint->numnextwaypoints; i++)
1823+ {
1824+ aimwaypoint = bestwaypoint->nextwaypoints[i];
1825+
1826+ if ((i == bestwaypoint->numnextwaypoints - 1U)
1827+ || ((K_GetWaypointIsEnabled(aimwaypoint) == true)
1828+ && (K_GetWaypointIsSpawnpoint(aimwaypoint) == true)))
1829+ {
1830+ player->starpostangle = R_PointToAngle2(
1831+ bestwaypoint->mobj->x, bestwaypoint->mobj->y, aimwaypoint->mobj->x, aimwaypoint->mobj->y);
1832+ break;
1833+ }
1834+ }
1835+ }
1836+ }
1837+
1838+ return bestwaypoint;
1839+}
1840+
1841+static boolean K_PlayerCloserToNextWaypoints(waypoint_t *const waypoint, player_t *const player)
1842+{
1843+ boolean nextiscloser = true;
1844+
1845+ if ((waypoint != NULL) && (player != NULL) && (player->mo != NULL))
1846+ {
1847+ size_t i = 0U;
1848+ waypoint_t *currentwpcheck = NULL;
1849+ angle_t angletoplayer = ANGLE_MAX;
1850+ angle_t currentanglecheck = ANGLE_MAX;
1851+ angle_t bestangle = ANGLE_MAX;
1852+
1853+ angletoplayer = R_PointToAngle2(waypoint->mobj->x, waypoint->mobj->y,
1854+ player->mo->x, player->mo->y);
1855+
1856+ for (i = 0U; i < waypoint->numnextwaypoints; i++)
1857+ {
1858+ currentwpcheck = waypoint->nextwaypoints[i];
1859+ currentanglecheck = R_PointToAngle2(
1860+ waypoint->mobj->x, waypoint->mobj->y, currentwpcheck->mobj->x, currentwpcheck->mobj->y);
1861+
1862+ // Get delta angle
1863+ currentanglecheck = currentanglecheck - angletoplayer;
1864+
1865+ if (currentanglecheck > ANGLE_180)
1866+ {
1867+ currentanglecheck = InvAngle(currentanglecheck);
1868+ }
1869+
1870+ if (currentanglecheck < bestangle)
1871+ {
1872+ bestangle = currentanglecheck;
1873+ }
1874+ }
1875+
1876+ for (i = 0U; i < waypoint->numprevwaypoints; i++)
1877+ {
1878+ currentwpcheck = waypoint->prevwaypoints[i];
1879+ currentanglecheck = R_PointToAngle2(
1880+ waypoint->mobj->x, waypoint->mobj->y, currentwpcheck->mobj->x, currentwpcheck->mobj->y);
1881+
1882+ // Get delta angle
1883+ currentanglecheck = currentanglecheck - angletoplayer;
1884+
1885+ if (currentanglecheck > ANGLE_180)
1886+ {
1887+ currentanglecheck = InvAngle(currentanglecheck);
1888+ }
1889+
1890+ if (currentanglecheck < bestangle)
1891+ {
1892+ bestangle = currentanglecheck;
1893+ nextiscloser = false;
1894+ break;
1895+ }
1896+ }
1897+ }
1898+
1899+ return nextiscloser;
1900+}
1901+
1902+/*--------------------------------------------------
1903+ static void K_UpdateDistanceFromFinishLine(player_t *const player)
1904+
1905+ Updates the distance a player has to the finish line.
1906+
1907+ Input Arguments:-
1908+ player - The player the distance is being updated for
1909+
1910+ Return:-
1911+ None
1912+--------------------------------------------------*/
1913+static void K_UpdateDistanceFromFinishLine(player_t *const player)
1914+{
1915+ if ((player != NULL) && (player->mo != NULL))
1916+ {
1917+ if (player->exiting)
1918+ {
1919+ player->nextwaypoint = K_GetFinishLineWaypoint();
1920+ player->distancetofinish = 0U;
1921+ }
1922+ else
1923+ {
1924+ waypoint_t *finishline = K_GetFinishLineWaypoint();
1925+ waypoint_t *nextwaypoint = K_GetPlayerNextWaypoint(player);
1926+
1927+ if (nextwaypoint != NULL)
1928+ {
1929+ // If nextwaypoint is NULL, it means we don't want to update the waypoint until we touch another one.
1930+ // player->nextwaypoint will keep its previous value in this case.
1931+ player->nextwaypoint = nextwaypoint;
1932+ }
1933+
1934+ // nextwaypoint is now the waypoint that is in front of us
1935+ if ((player->nextwaypoint != NULL) && (finishline != NULL))
1936+ {
1937+ const boolean useshortcuts = false;
1938+ const boolean huntbackwards = false;
1939+ boolean pathfindsuccess = false;
1940+ path_t pathtofinish = {};
1941+
1942+ pathfindsuccess =
1943+ K_PathfindToWaypoint(player->nextwaypoint, finishline, &pathtofinish, useshortcuts, huntbackwards);
1944+
1945+ // Update the player's distance to the finish line if a path was found.
1946+ // Using shortcuts won't find a path, so distance won't be updated until the player gets back on track
1947+ if (pathfindsuccess == true)
1948+ {
1949+ // Add euclidean distance to the next waypoint to the distancetofinish
1950+ UINT32 adddist;
1951+ fixed_t disttowaypoint =
1952+ P_AproxDistance(
1953+ (player->mo->x >> FRACBITS) - (player->nextwaypoint->mobj->x >> FRACBITS),
1954+ (player->mo->y >> FRACBITS) - (player->nextwaypoint->mobj->y >> FRACBITS));
1955+ disttowaypoint = P_AproxDistance(disttowaypoint, (player->mo->z >> FRACBITS) - (player->nextwaypoint->mobj->z >> FRACBITS));
1956+
1957+ adddist = (UINT32)disttowaypoint;
1958+
1959+ player->distancetofinish = pathtofinish.totaldist + adddist;
1960+ Z_Free(pathtofinish.array);
1961+
1962+ // distancetofinish is currently a flat distance to the finish line, but in order to be fully
1963+ // correct we need to add to it the length of the entire circuit multiplied by the number of laps
1964+ // left after this one. This will give us the total distance to the finish line, and allow item
1965+ // distance calculation to work easily
1966+ if ((mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE) == 0U)
1967+ {
1968+ const UINT8 numfulllapsleft = ((UINT8)cv_numlaps.value - player->laps);
1969+
1970+ player->distancetofinish += numfulllapsleft * K_GetCircuitLength();
1971+
1972+ // An additional HACK, to fix looking backwards towards the finish line
1973+ // If the player's next waypoint is the finishline and the angle distance from player to
1974+ // connectin waypoints implies they're closer to a next waypoint, add a full track distance
1975+ if (player->nextwaypoint == finishline)
1976+ {
1977+ if (K_PlayerCloserToNextWaypoints(player->nextwaypoint, player) == true)
1978+ {
1979+ player->distancetofinish += K_GetCircuitLength();
1980+ }
1981+ }
1982+ }
1983+ }
1984+ }
1985+ }
1986+ }
1987+}
1988+
1989 // Returns false if this player being placed here causes them to collide with any other player
1990 // Used in g_game.c for match etc. respawning
1991 // This does not check along the z because the z is not correctly set for the spawnee at this point
1992@@ -6081,12 +6713,15 @@ void K_KartUpdatePosition(player_t *player)
1993 {
1994 fixed_t position = 1;
1995 fixed_t oldposition = player->kartstuff[k_position];
1996- fixed_t i, ppcd, pncd, ipcd, incd;
1997- fixed_t pmo, imo;
1998- mobj_t *mo;
1999+ fixed_t i;
2000
2001 if (player->spectator || !player->mo)
2002+ {
2003+ // Ensure these are reset for spectators
2004+ player->kartstuff[k_position] = 0;
2005+ player->kartstuff[k_positiondelay] = 0;
2006 return;
2007+ }
2008
2009 for (i = 0; i < MAXPLAYERS; i++)
2010 {
2011@@ -6095,69 +6730,20 @@ void K_KartUpdatePosition(player_t *player)
2012
2013 if (G_RaceGametype())
2014 {
2015- if ((((players[i].starpostnum) + (numstarposts + 1) * players[i].laps) >
2016- ((player->starpostnum) + (numstarposts + 1) * player->laps)))
2017- position++;
2018- else if (((players[i].starpostnum) + (numstarposts+1)*players[i].laps) ==
2019- ((player->starpostnum) + (numstarposts+1)*player->laps))
2020+ if (player->exiting) // End of match standings
2021 {
2022- ppcd = pncd = ipcd = incd = 0;
2023-
2024- player->kartstuff[k_prevcheck] = players[i].kartstuff[k_prevcheck] = 0;
2025- player->kartstuff[k_nextcheck] = players[i].kartstuff[k_nextcheck] = 0;
2026-
2027- // This checks every thing on the map, and looks for MT_BOSS3WAYPOINT (the thing we're using for checkpoint wp's, for now)
2028- for (mo = waypointcap; mo != NULL; mo = mo->tracer)
2029- {
2030- pmo = P_AproxDistance(P_AproxDistance( mo->x - player->mo->x,
2031- mo->y - player->mo->y),
2032- mo->z - player->mo->z) / FRACUNIT;
2033- imo = P_AproxDistance(P_AproxDistance( mo->x - players[i].mo->x,
2034- mo->y - players[i].mo->y),
2035- mo->z - players[i].mo->z) / FRACUNIT;
2036-
2037- if (mo->health == player->starpostnum && (!mo->movecount || mo->movecount == player->laps+1))
2038- {
2039- player->kartstuff[k_prevcheck] += pmo;
2040- ppcd++;
2041- }
2042- if (mo->health == (player->starpostnum + 1) && (!mo->movecount || mo->movecount == player->laps+1))
2043- {
2044- player->kartstuff[k_nextcheck] += pmo;
2045- pncd++;
2046- }
2047- if (mo->health == players[i].starpostnum && (!mo->movecount || mo->movecount == players[i].laps+1))
2048- {
2049- players[i].kartstuff[k_prevcheck] += imo;
2050- ipcd++;
2051- }
2052- if (mo->health == (players[i].starpostnum + 1) && (!mo->movecount || mo->movecount == players[i].laps+1))
2053- {
2054- players[i].kartstuff[k_nextcheck] += imo;
2055- incd++;
2056- }
2057- }
2058-
2059- if (ppcd > 1) player->kartstuff[k_prevcheck] /= ppcd;
2060- if (pncd > 1) player->kartstuff[k_nextcheck] /= pncd;
2061- if (ipcd > 1) players[i].kartstuff[k_prevcheck] /= ipcd;
2062- if (incd > 1) players[i].kartstuff[k_nextcheck] /= incd;
2063-
2064- if ((players[i].kartstuff[k_nextcheck] > 0 || player->kartstuff[k_nextcheck] > 0) && !player->exiting)
2065- {
2066- if ((players[i].kartstuff[k_nextcheck] - players[i].kartstuff[k_prevcheck]) <
2067- (player->kartstuff[k_nextcheck] - player->kartstuff[k_prevcheck]))
2068- position++;
2069- }
2070- else if (!player->exiting)
2071- {
2072- if (players[i].kartstuff[k_prevcheck] > player->kartstuff[k_prevcheck])
2073- position++;
2074- }
2075- else
2076+ // Only time matters
2077+ if (players[i].realtime < player->realtime)
2078+ position++;
2079+ }
2080+ else
2081+ {
2082+ // I'm a lap behind this player OR
2083+ // My distance to the finish line is higher, so I'm behind
2084+ if ((players[i].laps > player->laps)
2085+ || (players[i].distancetofinish < player->distancetofinish))
2086 {
2087- if (players[i].starposttime < player->starposttime)
2088- position++;
2089+ position++;
2090 }
2091 }
2092 }
2093@@ -6165,14 +6751,16 @@ void K_KartUpdatePosition(player_t *player)
2094 {
2095 if (player->exiting) // End of match standings
2096 {
2097- if (players[i].marescore > player->marescore) // Only score matters
2098+ // Only score matters
2099+ if (players[i].marescore > player->marescore)
2100 position++;
2101 }
2102 else
2103 {
2104- if (players[i].kartstuff[k_bumper] == player->kartstuff[k_bumper] && players[i].marescore > player->marescore)
2105- position++;
2106- else if (players[i].kartstuff[k_bumper] > player->kartstuff[k_bumper])
2107+ // I have less points than but the same bumpers as this player OR
2108+ // I have less bumpers than this player
2109+ if ((players[i].kartstuff[k_bumper] == player->kartstuff[k_bumper] && players[i].marescore > player->marescore)
2110+ || (players[i].kartstuff[k_bumper] > player->kartstuff[k_bumper]))
2111 position++;
2112 }
2113 }
2114@@ -6305,7 +6893,8 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
2115 boolean HOLDING_ITEM = (player->kartstuff[k_itemheld] || player->kartstuff[k_eggmanheld]);
2116 boolean NO_HYUDORO = (player->kartstuff[k_stolentimer] == 0 && player->kartstuff[k_stealingtimer] == 0);
2117
2118- K_KartUpdatePosition(player);
2119+ K_UpdateDistanceFromFinishLine(player);
2120+ player->pflags &= ~PF_HITFINISHLINE;
2121
2122 if (!player->exiting)
2123 {
2124@@ -6963,13 +7552,9 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
2125 // Squishing
2126 // If a Grow player or a sector crushes you, get flattened instead of being killed.
2127
2128- if (player->kartstuff[k_squishedtimer] <= 0)
2129+ if (player->kartstuff[k_squishedtimer] > 0)
2130 {
2131- player->mo->flags &= ~MF_NOCLIP;
2132- }
2133- else
2134- {
2135- player->mo->flags |= MF_NOCLIP;
2136+ //player->mo->flags |= MF_NOCLIP;
2137 player->mo->momx = 0;
2138 player->mo->momy = 0;
2139 }
2140@@ -7271,7 +7856,7 @@ void K_CheckSpectateStatus(void)
2141 continue;
2142 if (leveltime > (starttime + 20*TICRATE)) // DON'T allow if the match is 20 seconds in
2143 return;
2144- if (G_RaceGametype() && players[i].laps) // DON'T allow if the race is at 2 laps
2145+ if (G_RaceGametype() && players[i].laps >= 2) // DON'T allow if the race is at 2 laps
2146 return;
2147 continue;
2148 }
2149@@ -7358,6 +7943,8 @@ static patch_t *kp_winnernum[NUMPOSFRAMES];
2150 static patch_t *kp_facenum[MAXPLAYERS+1];
2151 static patch_t *kp_facehighlight[8];
2152
2153+static patch_t *kp_spbminimap;
2154+
2155 static patch_t *kp_ringsticker[2];
2156 static patch_t *kp_ringstickersplit[4];
2157 static patch_t *kp_ring[6];
2158@@ -7513,6 +8100,8 @@ void K_LoadKartHUDGraphics(void)
2159 kp_facehighlight[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
2160 }
2161
2162+ kp_spbminimap = W_CachePatchName("SPBMMAP", PU_HUDGFX);
2163+
2164 // Rings & Lives
2165 kp_ringsticker[0] = W_CachePatchName("RNGBACKA", PU_HUDGFX);
2166 kp_ringsticker[1] = W_CachePatchName("RNGBACKB", PU_HUDGFX);
2167@@ -8528,7 +9117,7 @@ static void K_DrawKartPositionNum(INT32 num)
2168 {
2169 if (win) // 1st place winner? You get rainbows!!
2170 localpatch = kp_winnernum[(leveltime % (NUMWINFRAMES*3)) / 3];
2171- else if (stplyr->laps+1 >= cv_numlaps.value || stplyr->exiting) // Check for the final lap, or won
2172+ else if (stplyr->laps >= cv_numlaps.value || stplyr->exiting) // Check for the final lap, or won
2173 {
2174 // Alternate frame every three frames
2175 switch (leveltime % 9)
2176@@ -8912,8 +9501,8 @@ static void K_drawKartLapsAndRings(void)
2177 if (cv_numlaps.value >= 10)
2178 {
2179 UINT8 ln[2];
2180- ln[0] = ((abs(stplyr->laps+1) / 10) % 10);
2181- ln[1] = (abs(stplyr->laps+1) % 10);
2182+ ln[0] = ((abs(stplyr->laps) / 10) % 10);
2183+ ln[1] = (abs(stplyr->laps) % 10);
2184
2185 V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|splitflags, pingnum[ln[0]]);
2186 V_DrawScaledPatch(fx+17, fy, V_HUDTRANS|splitflags, pingnum[ln[1]]);
2187@@ -8926,7 +9515,7 @@ static void K_drawKartLapsAndRings(void)
2188 }
2189 else
2190 {
2191- V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|splitflags, kp_facenum[(stplyr->laps+1) % 10]);
2192+ V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|splitflags, kp_facenum[(stplyr->laps) % 10]);
2193 V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|splitflags, kp_facenum[(cv_numlaps.value) % 10]);
2194 }
2195
2196@@ -8968,7 +9557,7 @@ static void K_drawKartLapsAndRings(void)
2197 if (stplyr->exiting)
2198 V_DrawKartString(LAPS_X+33, LAPS_Y+3, V_HUDTRANS|splitflags, "FIN");
2199 else
2200- V_DrawKartString(LAPS_X+33, LAPS_Y+3, V_HUDTRANS|splitflags, va("%d/%d", stplyr->laps+1, cv_numlaps.value));
2201+ V_DrawKartString(LAPS_X+33, LAPS_Y+3, V_HUDTRANS|splitflags, va("%d/%d", stplyr->laps, cv_numlaps.value));
2202
2203 // Rings
2204 if (netgame)
2205@@ -9191,17 +9780,17 @@ static void K_drawKartWanted(void)
2206 return;
2207
2208 // set X/Y coords depending on splitscreen.
2209- if (splitscreen < 3) // 1P and 2P use the same code.
2210+ if (splitscreen < 3) // 1P and 2P use the same code.
2211 {
2212 basex = WANT_X;
2213 basey = WANT_Y;
2214 if (splitscreen == 2)
2215 {
2216- basey += 16; // slight adjust for 3P
2217+ basey += 16; // slight adjust for 3P
2218 basex -= 6;
2219 }
2220 }
2221- else if (splitscreen == 3) // 4P splitscreen...
2222+ else if (splitscreen == 3) // 4P splitscreen...
2223 {
2224 basex = BASEVIDWIDTH/2 - (SHORT(kp_wantedsplit->width)/2); // center on screen
2225 basey = BASEVIDHEIGHT - 55;
2226@@ -9378,6 +9967,7 @@ static void K_drawKartMinimap(void)
2227 UINT8 *colormap = NULL;
2228 SINT8 localplayers[4];
2229 SINT8 numlocalplayers = 0;
2230+ mobj_t *mobj, *next; // for SPB drawing (or any other item(s) we may wanna draw, I dunno!)
2231
2232 // Draw the HUD only when playing in a level.
2233 // hu_stuff needs this, unlike st_stuff.
2234@@ -9532,6 +10122,26 @@ static void K_drawKartMinimap(void)
2235 }
2236 }
2237
2238+ // draw SPB(s?)
2239+ for (mobj = kitemcap; mobj; mobj = next)
2240+ {
2241+ next = mobj->itnext;
2242+ if (mobj->type == MT_SPB)
2243+ {
2244+ colormap = NULL;
2245+
2246+ if (mobj->target && !P_MobjWasRemoved(mobj->target))
2247+ {
2248+ if (mobj->player && mobj->player->skincolor)
2249+ colormap = R_GetTranslationColormap(TC_RAINBOW, mobj->player->skincolor, GTC_CACHE);
2250+ else if (mobj->color)
2251+ colormap = R_GetTranslationColormap(TC_RAINBOW, mobj->color, GTC_CACHE);
2252+ }
2253+
2254+ K_drawKartMinimapIcon(mobj->x, mobj->y, x, y, splitflags, kp_spbminimap, colormap, AutomapPic);
2255+ }
2256+ }
2257+
2258 // draw our local players here, opaque.
2259 splitflags &= ~V_HUDTRANSHALF;
2260 splitflags |= V_HUDTRANS;
2261@@ -10011,7 +10621,7 @@ static void K_drawLapStartAnim(void)
2262 kp_lapanim_hand[stplyr->karthud[khud_laphand]-1], NULL);
2263 }
2264
2265- if (stplyr->laps == (UINT8)(cv_numlaps.value - 1))
2266+ if (stplyr->laps == (UINT8)(cv_numlaps.value))
2267 {
2268 V_DrawFixedPatch((62 - (32*max(0, progress-76)))*FRACUNIT, // 27
2269 30*FRACUNIT, // 24
2270@@ -10038,14 +10648,14 @@ static void K_drawLapStartAnim(void)
2271 V_DrawFixedPatch((188 + (32*max(0, progress-76)))*FRACUNIT, // 194
2272 30*FRACUNIT, // 24
2273 FRACUNIT, V_SNAPTOTOP|V_HUDTRANS,
2274- kp_lapanim_number[(((UINT32)stplyr->laps+1) / 10)][min(progress/2-8, 2)], NULL);
2275+ kp_lapanim_number[(((UINT32)stplyr->laps) / 10)][min(progress/2-8, 2)], NULL);
2276
2277 if (progress/2-10 >= 0)
2278 {
2279 V_DrawFixedPatch((208 + (32*max(0, progress-76)))*FRACUNIT, // 221
2280 30*FRACUNIT, // 24
2281 FRACUNIT, V_SNAPTOTOP|V_HUDTRANS,
2282- kp_lapanim_number[(((UINT32)stplyr->laps+1) % 10)][min(progress/2-10, 2)], NULL);
2283+ kp_lapanim_number[(((UINT32)stplyr->laps) % 10)][min(progress/2-10, 2)], NULL);
2284 }
2285 }
2286 }
2287@@ -10092,9 +10702,9 @@ static void K_drawDistributionDebugger(void)
2288 kp_orbinaut[4],
2289 kp_jawz[1]
2290 };
2291- INT32 useodds = 0;
2292- INT32 pingame = 0, bestbumper = 0;
2293- INT32 pdis = 0;
2294+ UINT8 useodds = 0;
2295+ UINT8 pingame = 0, bestbumper = 0;
2296+ UINT32 pdis = 0;
2297 INT32 i;
2298 INT32 x = -9, y = -9;
2299 boolean spbrush = false;
2300@@ -10115,13 +10725,13 @@ static void K_drawDistributionDebugger(void)
2301 // lovely double loop......
2302 for (i = 0; i < MAXPLAYERS; i++)
2303 {
2304- if (playeringame[i] && !players[i].spectator && players[i].mo
2305- && players[i].kartstuff[k_position] < stplyr->kartstuff[k_position])
2306- pdis += P_AproxDistance(P_AproxDistance(players[i].mo->x - stplyr->mo->x,
2307- players[i].mo->y - stplyr->mo->y),
2308- players[i].mo->z - stplyr->mo->z) / mapobjectscale
2309- * (pingame - players[i].kartstuff[k_position])
2310- / max(1, ((pingame - 1) * (pingame + 1) / 3));
2311+ if (playeringame[i] && !players[i].spectator
2312+ && players[i].kartstuff[k_position] == 1)
2313+ {
2314+ // This player is first! Yay!
2315+ pdis = stplyr->distancetofinish - players[i].distancetofinish;
2316+ break;
2317+ }
2318 }
2319
2320 if (franticitems) // Frantic items make the distances between everyone artifically higher, for crazier items
2321@@ -10184,11 +10794,19 @@ static void K_drawCheckpointDebugger(void)
2322 if (stplyr != &players[displayplayers[0]]) // only for p1
2323 return;
2324
2325- if (stplyr->starpostnum >= (numstarposts - (numstarposts/2)))
2326+ if (stplyr->starpostnum == numstarposts)
2327 V_DrawString(8, 184, 0, va("Checkpoint: %d / %d (Can finish)", stplyr->starpostnum, numstarposts));
2328 else
2329- V_DrawString(8, 184, 0, va("Checkpoint: %d / %d (Skip: %d)", stplyr->starpostnum, numstarposts, ((numstarposts/2) + stplyr->starpostnum)));
2330- V_DrawString(8, 192, 0, va("Waypoint dist: Prev %d, Next %d", stplyr->kartstuff[k_prevcheck], stplyr->kartstuff[k_nextcheck]));
2331+ V_DrawString(8, 184, 0, va("Checkpoint: %d / %d", stplyr->starpostnum, numstarposts));
2332+}
2333+
2334+static void K_DrawWaypointDebugger(void)
2335+{
2336+ if ((cv_kartdebugwaypoints.value != 0) && (stplyr == &players[displayplayers[0]]))
2337+ {
2338+ V_DrawString(8, 166, 0, va("'Best' Waypoint ID: %d", K_GetWaypointID(stplyr->nextwaypoint)));
2339+ V_DrawString(8, 176, 0, va("Finishline Distance: %d", stplyr->distancetofinish));
2340+ }
2341 }
2342
2343 void K_drawKartHUD(void)
2344@@ -10391,6 +11009,11 @@ void K_drawKartHUD(void)
2345 K_drawKartFreePlay(leveltime);
2346 }
2347
2348+ if (splitscreen == 0 && stplyr->kartstuff[k_wrongway] && ((leveltime / 8) & 1))
2349+ {
2350+ V_DrawCenteredString(BASEVIDWIDTH>>1, 176, V_REDMAP|V_SNAPTOBOTTOM, "WRONG WAY");
2351+ }
2352+
2353 if (cv_kartdebugdistribution.value)
2354 K_drawDistributionDebugger();
2355
2356@@ -10422,6 +11045,8 @@ void K_drawKartHUD(void)
2357 }
2358 }
2359 }
2360+
2361+ K_DrawWaypointDebugger();
2362 }
2363
2364 //}
2365diff --git a/src/k_kart.h b/src/k_kart.h
2366index 119b3f9a7..891834f26 100644
2367--- a/src/k_kart.h
2368+++ b/src/k_kart.h
2369@@ -27,6 +27,7 @@ void K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean solid)
2370 void K_KartPainEnergyFling(player_t *player);
2371 void K_FlipFromObject(mobj_t *mo, mobj_t *master);
2372 void K_MatchGenericExtraFlags(mobj_t *mo, mobj_t *master);
2373+void K_DoIngameRespawn(player_t *player);
2374 void K_RespawnChecker(player_t *player);
2375 void K_KartMoveAnimation(player_t *player);
2376 void K_KartPlayerHUDUpdate(player_t *player);
2377diff --git a/src/k_pathfind.c b/src/k_pathfind.c
2378new file mode 100644
2379index 000000000..563456de1
2380--- /dev/null
2381+++ b/src/k_pathfind.c
2382@@ -0,0 +1,494 @@
2383+#include "k_pathfind.h"
2384+
2385+#include "doomdef.h"
2386+#include "z_zone.h"
2387+#include "k_bheap.h"
2388+
2389+static const size_t DEFAULT_NODEARRAY_CAPACITY = 8U;
2390+static const size_t DEFAULT_OPENSET_CAPACITY = 8U;
2391+static const size_t DEFAULT_CLOSEDSET_CAPACITY = 8U;
2392+
2393+
2394+/*--------------------------------------------------
2395+ static UINT32 K_NodeGetFScore(const pathfindnode_t *const node)
2396+
2397+ Gets the FScore of a node. The FScore is the GScore plus the HScore.
2398+
2399+ Input Arguments:-
2400+ node - The node to get the FScore of
2401+
2402+ Return:-
2403+ The FScore of the node.
2404+--------------------------------------------------*/
2405+static UINT32 K_NodeGetFScore(const pathfindnode_t *const node)
2406+{
2407+ UINT32 fscore = UINT32_MAX;
2408+
2409+ I_Assert(node != NULL);
2410+
2411+ fscore = node->gscore + node->hscore;
2412+
2413+ return fscore;
2414+}
2415+
2416+/*--------------------------------------------------
2417+ static void K_NodeUpdateHeapIndex(void *const node, const size_t newheapindex)
2418+
2419+ A callback for the Openset Binary Heap to be able to update the heapindex of the pathfindnodes when they are
2420+ moved.
2421+
2422+ Input Arguments:-
2423+ node - The node that has been updated, should be a pointer to a pathfindnode_t
2424+ newheapindex - The new heapindex of the node.
2425+
2426+ Return:-
2427+ None
2428+--------------------------------------------------*/
2429+static void K_NodeUpdateHeapIndex(void *const node, const size_t newheapindex)
2430+{
2431+ if (node == NULL)
2432+ {
2433+ CONS_Debug(DBG_GAMELOGIC, "NULL node in K_NodeUpdateHeapIndex.\n");
2434+ }
2435+ else
2436+ {
2437+ pathfindnode_t *truenode = (pathfindnode_t*)node;
2438+ truenode->heapindex = newheapindex;
2439+ }
2440+}
2441+
2442+/*--------------------------------------------------
2443+ static pathfindnode_t *K_NodesArrayContainsNodeData(
2444+ pathfindnode_t *nodesarray,
2445+ void* nodedata,
2446+ size_t nodesarraycount)
2447+
2448+ Checks whether the Nodes Array contains a node with a waypoint. Searches from the end to the start for speed
2449+ reasons.
2450+
2451+ Input Arguments:-
2452+ nodesarray - The nodes array within the A* algorithm
2453+ waypoint - The waypoint to check is within the nodes array
2454+ nodesarraycount - The current size of the nodes array
2455+
2456+ Return:-
2457+ The pathfind node that has the waypoint if there is one. NULL if the waypoint is not in the nodes array.
2458+--------------------------------------------------*/
2459+static pathfindnode_t *K_NodesArrayContainsNodeData(
2460+ pathfindnode_t *nodesarray,
2461+ void* nodedata,
2462+ size_t nodesarraycount)
2463+{
2464+ pathfindnode_t *foundnode = NULL;
2465+ size_t i = 0U;
2466+
2467+ I_Assert(nodesarray != NULL);
2468+ I_Assert(nodedata != NULL);
2469+
2470+ // It is more likely that we'll find the node we are looking for from the end of the array
2471+ // Yes, the for loop looks weird, remember that size_t is unsigned and we want to check 0, after it hits 0 it
2472+ // will loop back up to SIZE_MAX
2473+ for (i = nodesarraycount - 1U; i < nodesarraycount; i--)
2474+ {
2475+ if (nodesarray[i].nodedata == nodedata)
2476+ {
2477+ foundnode = &nodesarray[i];
2478+ break;
2479+ }
2480+ }
2481+ return foundnode;
2482+}
2483+
2484+/*--------------------------------------------------
2485+ static boolean K_ClosedsetContainsNode(pathfindnode_t **closedset, pathfindnode_t *node, size_t closedsetcount)
2486+
2487+ Checks whether the Closedset contains a node. Searches from the end to the start for speed reasons.
2488+
2489+ Input Arguments:-
2490+ closedset - The closed set within the A* algorithm
2491+ node - The node to check is within the closed set
2492+ closedsetcount - The current size of the closedset
2493+
2494+ Return:-
2495+ True if the node is in the closed set, false if it isn't
2496+--------------------------------------------------*/
2497+static boolean K_ClosedsetContainsNode(pathfindnode_t **closedset, pathfindnode_t *node, size_t closedsetcount)
2498+{
2499+ boolean nodeisinclosedset = false;
2500+ size_t i = 0U;
2501+
2502+ I_Assert(closedset != NULL);
2503+ I_Assert(node != NULL);
2504+ // It is more likely that we'll find the node we are looking for from the end of the array
2505+ // Yes, the for loop looks weird, remember that size_t is unsigned and we want to check 0, after it hits 0 it
2506+ // will loop back up to SIZE_MAX
2507+ for (i = closedsetcount - 1U; i < closedsetcount; i--)
2508+ {
2509+ if (closedset[i] == node)
2510+ {
2511+ nodeisinclosedset = true;
2512+ break;
2513+ }
2514+ }
2515+ return nodeisinclosedset;
2516+}
2517+
2518+/*--------------------------------------------------
2519+ static boolean K_PathfindSetupValid(const pathfindsetup_t *const pathfindsetup)
2520+
2521+ Checks that the setup given for pathfinding is valid and can be used.
2522+
2523+ Input Arguments:-
2524+ pathfindsetup - The setup for the pathfinding given
2525+
2526+ Return:-
2527+ True if pathfinding setup is valid, false if it isn't.
2528+--------------------------------------------------*/
2529+static boolean K_PathfindSetupValid(const pathfindsetup_t *const pathfindsetup)
2530+{
2531+ boolean pathfindsetupvalid = false;
2532+ size_t sourcenodenumconnectednodes = 0U;
2533+ size_t endnodenumconnectednodes = 0U;
2534+
2535+ if (pathfindsetup == NULL)
2536+ {
2537+ CONS_Debug(DBG_GAMELOGIC, "NULL pathfindsetup in K_PathfindSetupValid.\n");
2538+ }
2539+ else if (pathfindsetup->startnodedata == NULL)
2540+ {
2541+ CONS_Debug(DBG_GAMELOGIC, "Pathfindsetup has NULL startnodedata.\n");
2542+ }
2543+ else if (pathfindsetup->endnodedata == NULL)
2544+ {
2545+ CONS_Debug(DBG_GAMELOGIC, "Pathfindsetup has NULL endnodedata.\n");
2546+ }
2547+ else if (pathfindsetup->getconnectednodes == NULL)
2548+ {
2549+ CONS_Debug(DBG_GAMELOGIC, "Pathfindsetup has NULL getconnectednodes function.\n");
2550+ }
2551+ else if (pathfindsetup->getconnectioncosts == NULL)
2552+ {
2553+ CONS_Debug(DBG_GAMELOGIC, "Pathfindsetup has NULL getconnectioncosts function.\n");
2554+ }
2555+ else if (pathfindsetup->getheuristic == NULL)
2556+ {
2557+ CONS_Debug(DBG_GAMELOGIC, "Pathfindsetup has NULL getheuristic function.\n");
2558+ }
2559+ else if (pathfindsetup->gettraversable == NULL)
2560+ {
2561+ CONS_Debug(DBG_GAMELOGIC, "Pathfindsetup has NULL gettraversable function.\n");
2562+ }
2563+ else if (pathfindsetup->getconnectednodes(pathfindsetup->startnodedata, &sourcenodenumconnectednodes) == NULL)
2564+ {
2565+ CONS_Debug(DBG_GAMELOGIC, "K_PathfindSetupValid: Source node returned NULL connecting nodes.\n");
2566+ }
2567+ else if (sourcenodenumconnectednodes == 0U)
2568+ {
2569+ CONS_Debug(DBG_GAMELOGIC, "K_PathfindSetupValid: Source node has 0 connecting nodes.\n");
2570+ }
2571+ else if (pathfindsetup->getconnectednodes(pathfindsetup->endnodedata, &endnodenumconnectednodes) == NULL)
2572+ {
2573+ CONS_Debug(DBG_GAMELOGIC, "K_PathfindSetupValid: End node returned NULL connecting nodes.\n");
2574+ }
2575+ else if (endnodenumconnectednodes == 0U)
2576+ {
2577+ CONS_Debug(DBG_GAMELOGIC, "K_PathfindSetupValid: End node has 0 connecting nodes.\n");
2578+ }
2579+ else
2580+ {
2581+ pathfindsetupvalid = true;
2582+ }
2583+
2584+ return pathfindsetupvalid;
2585+}
2586+
2587+static boolean K_ReconstructPath(path_t *const path, pathfindnode_t *const destinationnode)
2588+{
2589+ boolean reconstructsuccess = false;
2590+
2591+ I_Assert(path != NULL);
2592+ I_Assert(destinationnode != NULL);
2593+
2594+ {
2595+ size_t numnodes = 0U;
2596+ pathfindnode_t *thisnode = destinationnode;
2597+
2598+ // If the path we're placing our new path into already has data, free it
2599+ if (path->array != NULL)
2600+ {
2601+ Z_Free(path->array);
2602+ path->numnodes = 0U;
2603+ path->totaldist = 0U;
2604+ }
2605+
2606+ // Do a fast check of how many nodes there are so we know how much space to allocate
2607+ for (thisnode = destinationnode; thisnode; thisnode = thisnode->camefrom)
2608+ {
2609+ numnodes++;
2610+ }
2611+
2612+ if (numnodes > 0U)
2613+ {
2614+ // Allocate memory for the path
2615+ path->numnodes = numnodes;
2616+ path->array = Z_Calloc(numnodes * sizeof(pathfindnode_t), PU_STATIC, NULL);
2617+ path->totaldist = destinationnode->gscore;
2618+ if (path->array == NULL)
2619+ {
2620+ I_Error("K_ReconstructPath: Out of memory.");
2621+ }
2622+
2623+ // Put the nodes into the return array
2624+ for (thisnode = destinationnode; thisnode; thisnode = thisnode->camefrom)
2625+ {
2626+ path->array[numnodes - 1U] = *thisnode;
2627+ // Correct the camefrom element to point to the previous element in the array instead
2628+ if ((path->array[numnodes - 1U].camefrom != NULL) && (numnodes > 1U))
2629+ {
2630+ path->array[numnodes - 1U].camefrom = &path->array[numnodes - 2U];
2631+ }
2632+ else
2633+ {
2634+ path->array[numnodes - 1U].camefrom = NULL;
2635+ }
2636+
2637+ numnodes--;
2638+ }
2639+
2640+ reconstructsuccess = true;
2641+ }
2642+ }
2643+
2644+ return reconstructsuccess;
2645+}
2646+
2647+/*--------------------------------------------------
2648+ boolean K_PathfindAStar(path_t *const path, pathfindsetup_t *const pathfindsetup)
2649+
2650+ See header file for description.
2651+--------------------------------------------------*/
2652+boolean K_PathfindAStar(path_t *const path, pathfindsetup_t *const pathfindsetup)
2653+{
2654+ boolean pathfindsuccess = false;
2655+
2656+ if (path == NULL)
2657+ {
2658+ CONS_Debug(DBG_GAMELOGIC, "NULL path in K_PathfindAStar.\n");
2659+ }
2660+ else if (pathfindsetup == NULL)
2661+ {
2662+ CONS_Debug(DBG_GAMELOGIC, "NULL pathfindsetup in K_PathfindAStar.\n");
2663+ }
2664+ else if (!K_PathfindSetupValid(pathfindsetup))
2665+ {
2666+ CONS_Debug(DBG_GAMELOGIC, "K_PathfindAStar: Pathfinding setup is not valid.\n");
2667+ }
2668+ else if (pathfindsetup->startnodedata == pathfindsetup->endnodedata)
2669+ {
2670+ // At the destination, return a simple 1 node path
2671+ pathfindnode_t singlenode = {};
2672+ singlenode.camefrom = NULL;
2673+ singlenode.nodedata = pathfindsetup->endnodedata;
2674+ singlenode.heapindex = SIZE_MAX;
2675+ singlenode.hscore = 0U;
2676+ singlenode.gscore = 0U;
2677+
2678+ K_ReconstructPath(path, &singlenode);
2679+
2680+ pathfindsuccess = true;
2681+ }
2682+ else
2683+ {
2684+ bheap_t openset = {};
2685+ bheapitem_t poppedbheapitem = {};
2686+ pathfindnode_t *nodesarray = NULL;
2687+ pathfindnode_t **closedset = NULL;
2688+ pathfindnode_t *newnode = NULL;
2689+ pathfindnode_t *currentnode = NULL;
2690+ pathfindnode_t *connectingnode = NULL;
2691+ void **connectingnodesdata = NULL;
2692+ void *checknodedata = NULL;
2693+ UINT32 *connectingnodecosts = NULL;
2694+ size_t numconnectingnodes = 0U;
2695+ size_t connectingnodeheapindex = 0U;
2696+ size_t nodesarraycount = 0U;
2697+ size_t closedsetcount = 0U;
2698+ size_t i = 0U;
2699+ UINT32 tentativegscore = 0U;
2700+
2701+ // Set the dynamic structure capacites to defaults if they are 0
2702+ if (pathfindsetup->nodesarraycapacity == 0U)
2703+ {
2704+ pathfindsetup->nodesarraycapacity = DEFAULT_NODEARRAY_CAPACITY;
2705+ }
2706+ if (pathfindsetup->opensetcapacity == 0U)
2707+ {
2708+ pathfindsetup->opensetcapacity = DEFAULT_OPENSET_CAPACITY;
2709+ }
2710+ if (pathfindsetup->closedsetcapacity == 0U)
2711+ {
2712+ pathfindsetup->closedsetcapacity = DEFAULT_CLOSEDSET_CAPACITY;
2713+ }
2714+
2715+ // Allocate the necessary memory
2716+ nodesarray = Z_Calloc(pathfindsetup->nodesarraycapacity * sizeof(pathfindnode_t), PU_STATIC, NULL);
2717+ if (nodesarray == NULL)
2718+ {
2719+ I_Error("K_PathfindAStar: Out of memory allocating nodes array.");
2720+ }
2721+ closedset = Z_Calloc(pathfindsetup->closedsetcapacity * sizeof(pathfindnode_t*), PU_STATIC, NULL);
2722+ if (closedset == NULL)
2723+ {
2724+ I_Error("K_PathfindAStar: Out of memory allocating closed set.");
2725+ }
2726+ K_BHeapInit(&openset, pathfindsetup->opensetcapacity);
2727+
2728+ // Create the first node and add it to the open set
2729+ newnode = &nodesarray[nodesarraycount];
2730+ newnode->heapindex = SIZE_MAX;
2731+ newnode->nodedata = pathfindsetup->startnodedata;
2732+ newnode->camefrom = NULL;
2733+ newnode->gscore = 0U;
2734+ newnode->hscore = pathfindsetup->getheuristic(newnode->nodedata, pathfindsetup->endnodedata);
2735+ nodesarraycount++;
2736+ K_BHeapPush(&openset, newnode, K_NodeGetFScore(newnode), K_NodeUpdateHeapIndex);
2737+
2738+ // update openset capacity if it changed
2739+ if (openset.capacity != pathfindsetup->opensetcapacity)
2740+ {
2741+ pathfindsetup->opensetcapacity = openset.capacity;
2742+ }
2743+
2744+ // Go through each node in the openset, adding new ones from each node to it
2745+ // this continues until a path is found or there are no more nodes to check
2746+ while (openset.count > 0U)
2747+ {
2748+ // pop the best node off of the openset
2749+ K_BHeapPop(&openset, &poppedbheapitem);
2750+ currentnode = (pathfindnode_t*)poppedbheapitem.data;
2751+
2752+ if (currentnode->nodedata == pathfindsetup->endnodedata)
2753+ {
2754+ pathfindsuccess = K_ReconstructPath(path, currentnode);
2755+ break;
2756+ }
2757+
2758+ // Place the node we just popped into the closed set, as we are now evaluating it
2759+ if (closedsetcount >= pathfindsetup->closedsetcapacity)
2760+ {
2761+ // Need to reallocate closedset to fit another node
2762+ pathfindsetup->closedsetcapacity = pathfindsetup->closedsetcapacity * 2;
2763+ closedset =
2764+ Z_Realloc(closedset, pathfindsetup->closedsetcapacity * sizeof(pathfindnode_t*), PU_STATIC, NULL);
2765+ if (closedset == NULL)
2766+ {
2767+ I_Error("K_PathfindAStar: Out of memory reallocating closed set.");
2768+ }
2769+ }
2770+ closedset[closedsetcount] = currentnode;
2771+ closedsetcount++;
2772+
2773+ // Get the needed data for the next nodes from the current node
2774+ connectingnodesdata = pathfindsetup->getconnectednodes(currentnode->nodedata, &numconnectingnodes);
2775+ connectingnodecosts = pathfindsetup->getconnectioncosts(currentnode->nodedata);
2776+
2777+ if (connectingnodesdata == NULL)
2778+ {
2779+ CONS_Debug(DBG_GAMELOGIC, "K_PathfindAStar: A Node returned NULL connecting node data.\n");
2780+ }
2781+ else if (connectingnodecosts == NULL)
2782+ {
2783+ CONS_Debug(DBG_GAMELOGIC, "K_PathfindAStar: A Node returned NULL connecting node costs.\n");
2784+ }
2785+ else
2786+ {
2787+ // For each connecting node add it to the openset if it's unevaluated and not there,
2788+ // skip it if it's in the closedset or not traversable
2789+ for (i = 0; i < numconnectingnodes; i++)
2790+ {
2791+ checknodedata = connectingnodesdata[i];
2792+
2793+ if (checknodedata == NULL)
2794+ {
2795+ CONS_Debug(DBG_GAMELOGIC, "K_PathfindAStar: A Node has a NULL connecting node.\n");
2796+ }
2797+ else
2798+ {
2799+ // skip this node if it isn't traversable
2800+ if (pathfindsetup->gettraversable(checknodedata) == false)
2801+ {
2802+ continue;
2803+ }
2804+
2805+ // Figure out what the gscore of this route for the connecting node is
2806+ tentativegscore = currentnode->gscore + connectingnodecosts[i];
2807+
2808+ // find this data in the nodes array if it's been generated before
2809+ connectingnode = K_NodesArrayContainsNodeData(nodesarray, checknodedata, nodesarraycount);
2810+
2811+ if (connectingnode != NULL)
2812+ {
2813+ // The connecting node has been seen before, so it must be in either the closedset (skip it)
2814+ // or the openset (re-evaluate it's gscore)
2815+ if (K_ClosedsetContainsNode(closedset, connectingnode, closedsetcount) == true)
2816+ {
2817+ continue;
2818+ }
2819+ else if (tentativegscore < connectingnode->gscore)
2820+ {
2821+ // The node is not in the closedset, update it's gscore if this path to it is faster
2822+ connectingnode->gscore = tentativegscore;
2823+ connectingnode->camefrom = currentnode;
2824+
2825+ connectingnodeheapindex =
2826+ K_BHeapContains(&openset, connectingnode, connectingnode->heapindex);
2827+ if (connectingnodeheapindex != SIZE_MAX)
2828+ {
2829+ K_UpdateBHeapItemValue(
2830+ &openset.array[connectingnodeheapindex], K_NodeGetFScore(connectingnode));
2831+ }
2832+ else
2833+ {
2834+ // SOMEHOW the node is not in either the closed set OR the open set
2835+ CONS_Debug(DBG_GAMELOGIC, "K_PathfindAStar: A Node is not in either set.\n");
2836+ }
2837+ }
2838+ }
2839+ else
2840+ {
2841+ // Node is not created yet, so it hasn't been seen so far
2842+ // Reallocate nodesarray if it's full
2843+ if (nodesarraycount >= pathfindsetup->nodesarraycapacity)
2844+ {
2845+ pathfindsetup->nodesarraycapacity = pathfindsetup->nodesarraycapacity * 2;
2846+ nodesarray = Z_Realloc(nodesarray, pathfindsetup->nodesarraycapacity * sizeof(pathfindnode_t), PU_STATIC, NULL);
2847+
2848+ if (nodesarray == NULL)
2849+ {
2850+ I_Error("K_PathfindAStar: Out of memory reallocating nodes array.");
2851+ }
2852+ }
2853+
2854+ // Create the new node and add it to the nodes array and open set
2855+ newnode = &nodesarray[nodesarraycount];
2856+ newnode->heapindex = SIZE_MAX;
2857+ newnode->nodedata = checknodedata;
2858+ newnode->camefrom = currentnode;
2859+ newnode->gscore = tentativegscore;
2860+ newnode->hscore = pathfindsetup->getheuristic(newnode->nodedata, pathfindsetup->endnodedata);
2861+ nodesarraycount++;
2862+ K_BHeapPush(&openset, newnode, K_NodeGetFScore(newnode), K_NodeUpdateHeapIndex);
2863+ }
2864+ }
2865+ }
2866+ }
2867+ }
2868+
2869+ // Clean up the memory
2870+ K_BHeapFree(&openset);
2871+ Z_Free(closedset);
2872+ Z_Free(nodesarray);
2873+ }
2874+
2875+ return pathfindsuccess;
2876+}
2877diff --git a/src/k_pathfind.h b/src/k_pathfind.h
2878new file mode 100644
2879index 000000000..dac23373c
2880--- /dev/null
2881+++ b/src/k_pathfind.h
2882@@ -0,0 +1,70 @@
2883+#ifndef __K_PATHFIND__
2884+#define __K_PATHFIND__
2885+
2886+#include "doomtype.h"
2887+
2888+// function pointer for returning a node's connected node data
2889+// should return a pointer to an array of pointers to the data, as arguments takes a node's data and a pointer that the
2890+// number of connected nodes should be placed into
2891+typedef void**(*getconnectednodesfunc)(void*, size_t*);
2892+
2893+// function pointer for getting the list of connected node costs/distances
2894+typedef UINT32*(*getnodeconnectioncostsfunc)(void*);
2895+
2896+// function pointer for getting a heuristic between 2 nodes from their base data
2897+typedef UINT32(*getnodeheuristicfunc)(void*, void*);
2898+
2899+// function pointer for getting if a node is traversable from its base data
2900+typedef boolean(*getnodetraversablefunc)(void*);
2901+
2902+
2903+// A pathfindnode contains information about a node from the pathfinding
2904+// heapindex is only used within the pathfinding algorithm itself, and is always 0 after it is completed
2905+typedef struct pathfindnode_s {
2906+ size_t heapindex; // The index in the openset binary heap. Only valid while the node is in the openset.
2907+ void *nodedata;
2908+ struct pathfindnode_s *camefrom; // should eventually be the most efficient predecessor node
2909+ UINT32 gscore; // The accumulated distance from the start to this node
2910+ UINT32 hscore; // The heuristic from this node to the goal
2911+} pathfindnode_t;
2912+
2913+// Contains the final created path after pathfinding is completed
2914+typedef struct path_s {
2915+ size_t numnodes;
2916+ struct pathfindnode_s *array;
2917+ UINT32 totaldist;
2918+} path_t;
2919+
2920+// Contains info about the pathfinding used to setup the algorithm
2921+// (e.g. the base capacities of the dynamically allocated arrays)
2922+// should be setup by the caller before starting pathfinding
2923+// base capacities will be 8 if they aren't setup, missing callback functions will cause an error.
2924+// Can be accessed after the pathfinding is complete to get the final capacities of them
2925+typedef struct pathfindsetup_s {
2926+ size_t opensetcapacity;
2927+ size_t closedsetcapacity;
2928+ size_t nodesarraycapacity;
2929+ void *startnodedata;
2930+ void *endnodedata;
2931+ getconnectednodesfunc getconnectednodes;
2932+ getnodeconnectioncostsfunc getconnectioncosts;
2933+ getnodeheuristicfunc getheuristic;
2934+ getnodetraversablefunc gettraversable;
2935+} pathfindsetup_t;
2936+
2937+
2938+/*--------------------------------------------------
2939+ boolean K_PathfindAStar(path_t *const path, pathfindsetup_t *const pathfindsetup);
2940+
2941+ From a source waypoint and destination waypoint, find the best path between them using the A* algorithm.
2942+
2943+ Input Arguments:-
2944+ path - The return location of the found path
2945+ pathfindsetup - The information regarding pathfinding setup, see pathfindsetup_t
2946+
2947+ Return:-
2948+ True if a path was found between source and destination, false otherwise.
2949+--------------------------------------------------*/
2950+boolean K_PathfindAStar(path_t *const path, pathfindsetup_t *const pathfindsetup);
2951+
2952+#endif
2953diff --git a/src/k_waypoint.c b/src/k_waypoint.c
2954new file mode 100644
2955index 000000000..fc5aa4590
2956--- /dev/null
2957+++ b/src/k_waypoint.c
2958@@ -0,0 +1,1750 @@
2959+#include "k_waypoint.h"
2960+
2961+#include "d_netcmd.h"
2962+#include "p_local.h"
2963+#include "p_tick.h"
2964+#include "z_zone.h"
2965+#include "g_game.h"
2966+
2967+// The number of sparkles per waypoint connection in the waypoint visualisation
2968+static const UINT32 SPARKLES_PER_CONNECTION = 16U;
2969+
2970+// Some defaults for the size of the dynamically allocated sets for pathfinding. These are kept for the purpose of
2971+// allocating a size that is less likely to need reallocating again during the pathfinding.
2972+#define OPENSET_BASE_SIZE (16U)
2973+#define CLOSEDSET_BASE_SIZE (256U)
2974+#define NODESARRAY_BASE_SIZE (256U)
2975+
2976+static waypoint_t *waypointheap = NULL;
2977+static waypoint_t *firstwaypoint = NULL;
2978+static waypoint_t *finishline = NULL;
2979+
2980+static UINT32 circuitlength = 0U;
2981+
2982+static size_t numwaypoints = 0U;
2983+static size_t numwaypointmobjs = 0U;
2984+static size_t baseopensetsize = OPENSET_BASE_SIZE;
2985+static size_t baseclosedsetsize = CLOSEDSET_BASE_SIZE;
2986+static size_t basenodesarraysize = NODESARRAY_BASE_SIZE;
2987+
2988+
2989+/*--------------------------------------------------
2990+ waypoint_t *K_GetFinishLineWaypoint(void)
2991+
2992+ See header file for description.
2993+--------------------------------------------------*/
2994+waypoint_t *K_GetFinishLineWaypoint(void)
2995+{
2996+ return finishline;
2997+}
2998+
2999+/*--------------------------------------------------
3000+ boolean K_GetWaypointIsFinishline(waypoint_t *waypoint)
3001+
3002+ See header file for description.
3003+--------------------------------------------------*/
3004+boolean K_GetWaypointIsFinishline(waypoint_t *waypoint)
3005+{
3006+ boolean waypointisfinishline = false;
3007+
3008+ if (waypoint == NULL)
3009+ {
3010+ CONS_Debug(DBG_GAMELOGIC, "NULL waypoint in K_GetWaypointIsFinishline.\n");
3011+ }
3012+ else if ((waypoint->mobj == NULL) || (P_MobjWasRemoved(waypoint->mobj) == true))
3013+ {
3014+ CONS_Debug(DBG_GAMELOGIC, "NULL waypoint mobj in K_GetWaypointIsFinishline.\n");
3015+ }
3016+ else
3017+ {
3018+ waypointisfinishline = (waypoint->mobj->extravalue2 == 1);
3019+ }
3020+
3021+ return waypointisfinishline;
3022+}
3023+
3024+/*--------------------------------------------------
3025+ boolean K_GetWaypointIsShortcut(waypoint_t *waypoint)
3026+
3027+ See header file for description.
3028+--------------------------------------------------*/
3029+boolean K_GetWaypointIsShortcut(waypoint_t *waypoint)
3030+{
3031+ boolean waypointisshortcut = false;
3032+
3033+ if (waypoint == NULL)
3034+ {
3035+ CONS_Debug(DBG_GAMELOGIC, "NULL waypoint in K_GetWaypointIsShortcut.\n");
3036+ }
3037+ else if ((waypoint->mobj == NULL) || (P_MobjWasRemoved(waypoint->mobj) == true))
3038+ {
3039+ CONS_Debug(DBG_GAMELOGIC, "NULL waypoint mobj in K_GetWaypointIsShortcut.\n");
3040+ }
3041+ else
3042+ {
3043+ waypointisshortcut = (waypoint->mobj->lastlook == 1);
3044+ }
3045+
3046+ return waypointisshortcut;
3047+}
3048+
3049+/*--------------------------------------------------
3050+ boolean K_GetWaypointIsEnabled(waypoint_t *waypoint)
3051+
3052+ See header file for description.
3053+--------------------------------------------------*/
3054+boolean K_GetWaypointIsEnabled(waypoint_t *waypoint)
3055+{
3056+ boolean waypointisenabled = true;
3057+
3058+ if (waypoint == NULL)
3059+ {
3060+ CONS_Debug(DBG_GAMELOGIC, "NULL waypoint in K_GetWaypointIsEnabled.\n");
3061+ }
3062+ else if ((waypoint->mobj == NULL) || (P_MobjWasRemoved(waypoint->mobj) == true))
3063+ {
3064+ CONS_Debug(DBG_GAMELOGIC, "NULL waypoint mobj in K_GetWaypointIsEnabled.\n");
3065+ }
3066+ else
3067+ {
3068+ waypointisenabled = (waypoint->mobj->extravalue1 == 1);
3069+ }
3070+
3071+ return waypointisenabled;
3072+}
3073+
3074+/*--------------------------------------------------
3075+ boolean K_GetWaypointIsSpawnpoint(waypoint_t *waypoint)
3076+
3077+ See header file for description.
3078+--------------------------------------------------*/
3079+boolean K_GetWaypointIsSpawnpoint(waypoint_t *waypoint)
3080+{
3081+ boolean waypointisspawnpoint = true;
3082+
3083+ if (waypoint == NULL)
3084+ {
3085+ CONS_Debug(DBG_GAMELOGIC, "NULL waypoint in K_GetWaypointIsEnabled.\n");
3086+ }
3087+ else if ((waypoint->mobj == NULL) || (P_MobjWasRemoved(waypoint->mobj) == true))
3088+ {
3089+ CONS_Debug(DBG_GAMELOGIC, "NULL waypoint mobj in K_GetWaypointIsEnabled.\n");
3090+ }
3091+ else
3092+ {
3093+ waypointisspawnpoint = (waypoint->mobj->reactiontime == 1);
3094+ }
3095+
3096+ return waypointisspawnpoint;
3097+}
3098+
3099+/*--------------------------------------------------
3100+ INT32 K_GetWaypointNextID(waypoint_t *waypoint)
3101+
3102+ See header file for description.
3103+--------------------------------------------------*/
3104+INT32 K_GetWaypointNextID(waypoint_t *waypoint)
3105+{
3106+ INT32 nextwaypointid = -1;
3107+
3108+ if (waypoint == NULL)
3109+ {
3110+ CONS_Debug(DBG_GAMELOGIC, "NULL waypoint in K_GetWaypointNextID.\n");
3111+ }
3112+ else if ((waypoint->mobj == NULL) || (P_MobjWasRemoved(waypoint->mobj) == true))
3113+ {
3114+ CONS_Debug(DBG_GAMELOGIC, "NULL waypoint mobj in K_GetWaypointNextID.\n");
3115+ }
3116+ else
3117+ {
3118+ nextwaypointid = waypoint->mobj->threshold;
3119+ }
3120+
3121+ return nextwaypointid;
3122+}
3123+
3124+/*--------------------------------------------------
3125+ INT32 K_GetWaypointID(waypoint_t *waypoint)
3126+
3127+ See header file for description.
3128+--------------------------------------------------*/
3129+INT32 K_GetWaypointID(waypoint_t *waypoint)
3130+{
3131+ INT32 waypointid = -1;
3132+
3133+ if (waypoint == NULL)
3134+ {
3135+ CONS_Debug(DBG_GAMELOGIC, "NULL waypoint in K_GetWaypointID.\n");
3136+ }
3137+ else if ((waypoint->mobj == NULL) || (P_MobjWasRemoved(waypoint->mobj) == true))
3138+ {
3139+ CONS_Debug(DBG_GAMELOGIC, "NULL waypoint mobj in K_GetWaypointID.\n");
3140+ }
3141+ else
3142+ {
3143+ waypointid = waypoint->mobj->movecount;
3144+ }
3145+
3146+ return waypointid;
3147+}
3148+
3149+/*--------------------------------------------------
3150+ UINT32 K_GetCircuitLength(void)
3151+
3152+ See header file for description.
3153+--------------------------------------------------*/
3154+UINT32 K_GetCircuitLength(void)
3155+{
3156+ return circuitlength;
3157+}
3158+
3159+/*--------------------------------------------------
3160+ waypoint_t *K_GetClosestWaypointToMobj(mobj_t *const mobj)
3161+
3162+ See header file for description.
3163+--------------------------------------------------*/
3164+waypoint_t *K_GetClosestWaypointToMobj(mobj_t *const mobj)
3165+{
3166+ waypoint_t *closestwaypoint = NULL;
3167+
3168+ if ((mobj == NULL) || P_MobjWasRemoved(mobj))
3169+ {
3170+ CONS_Debug(DBG_GAMELOGIC, "NULL mobj in K_GetClosestWaypointToMobj.\n");
3171+ }
3172+ else
3173+ {
3174+ size_t i = 0U;
3175+ waypoint_t *checkwaypoint = NULL;
3176+ fixed_t closestdist = INT32_MAX;
3177+ fixed_t checkdist = INT32_MAX;
3178+
3179+ for (i = 0; i < numwaypoints; i++)
3180+ {
3181+ checkwaypoint = &waypointheap[i];
3182+
3183+ checkdist = P_AproxDistance(
3184+ (mobj->x / FRACUNIT) - (checkwaypoint->mobj->x / FRACUNIT),
3185+ (mobj->y / FRACUNIT) - (checkwaypoint->mobj->y / FRACUNIT));
3186+ checkdist = P_AproxDistance(checkdist, (mobj->z / FRACUNIT) - (checkwaypoint->mobj->z / FRACUNIT));
3187+
3188+ if (checkdist < closestdist)
3189+ {
3190+ closestwaypoint = checkwaypoint;
3191+ closestdist = checkdist;
3192+ }
3193+ }
3194+ }
3195+
3196+ return closestwaypoint;
3197+}
3198+
3199+/*--------------------------------------------------
3200+ waypoint_t *K_GetBestWaypointForMobj(mobj_t *const mobj)
3201+
3202+ See header file for description.
3203+--------------------------------------------------*/
3204+waypoint_t *K_GetBestWaypointForMobj(mobj_t *const mobj)
3205+{
3206+ waypoint_t *bestwaypoint = NULL;
3207+
3208+ if ((mobj == NULL) || P_MobjWasRemoved(mobj))
3209+ {
3210+ CONS_Debug(DBG_GAMELOGIC, "NULL mobj in K_GetBestWaypointForMobj.\n");
3211+ }
3212+ else
3213+ {
3214+ size_t i = 0U;
3215+ waypoint_t *checkwaypoint = NULL;
3216+ fixed_t closestdist = INT32_MAX;
3217+ fixed_t checkdist = INT32_MAX;
3218+
3219+ for (i = 0; i < numwaypoints; i++)
3220+ {
3221+ checkwaypoint = &waypointheap[i];
3222+
3223+ checkdist = P_AproxDistance(
3224+ (mobj->x / FRACUNIT) - (checkwaypoint->mobj->x / FRACUNIT),
3225+ (mobj->y / FRACUNIT) - (checkwaypoint->mobj->y / FRACUNIT));
3226+ checkdist = P_AproxDistance(checkdist, ((mobj->z / FRACUNIT) - (checkwaypoint->mobj->z / FRACUNIT)) * 4);
3227+
3228+ if (checkdist < closestdist)
3229+ {
3230+ if (!P_CheckSight(mobj, checkwaypoint->mobj))
3231+ {
3232+ // Save sight checks for the end, so we only do it if we have to
3233+ continue;
3234+ }
3235+
3236+ bestwaypoint = checkwaypoint;
3237+ closestdist = checkdist;
3238+ }
3239+ }
3240+ }
3241+
3242+ return bestwaypoint;
3243+}
3244+
3245+/*--------------------------------------------------
3246+ size_t K_GetWaypointHeapIndex(waypoint_t *waypoint)
3247+
3248+ See header file for description.
3249+--------------------------------------------------*/
3250+size_t K_GetWaypointHeapIndex(waypoint_t *waypoint)
3251+{
3252+ size_t waypointindex = SIZE_MAX;
3253+
3254+ if (waypoint == NULL)
3255+ {
3256+ CONS_Debug(DBG_GAMELOGIC, "NULL waypoint in K_GetWaypointID.\n");
3257+ }
3258+ else
3259+ {
3260+ waypointindex = waypoint - waypointheap;
3261+ }
3262+
3263+ return waypointindex;
3264+}
3265+
3266+/*--------------------------------------------------
3267+ waypoint_t *K_GetWaypointFromIndex(size_t waypointindex)
3268+
3269+ See header file for description.
3270+--------------------------------------------------*/
3271+waypoint_t *K_GetWaypointFromIndex(size_t waypointindex)
3272+{
3273+ waypoint_t *waypoint = NULL;
3274+
3275+ if (waypointindex >= numwaypoints)
3276+ {
3277+ CONS_Debug(DBG_GAMELOGIC, "waypointindex higher than number of waypoints in K_GetWaypointFromIndex");
3278+ }
3279+ else
3280+ {
3281+ waypoint = &waypointheap[waypointindex];
3282+ }
3283+
3284+ return waypoint;
3285+}
3286+
3287+/*--------------------------------------------------
3288+ static UINT32 K_DistanceBetweenWaypoints(waypoint_t *const waypoint1, waypoint_t *const waypoint2)
3289+
3290+ Gets the Euclidean distance between 2 waypoints by using their mobjs. Used for the heuristic.
3291+
3292+ Input Arguments:-
3293+ waypoint1 - The first waypoint
3294+ waypoint2 - The second waypoint
3295+
3296+ Return:-
3297+ Euclidean distance between the 2 waypoints
3298+--------------------------------------------------*/
3299+static UINT32 K_DistanceBetweenWaypoints(waypoint_t *const waypoint1, waypoint_t *const waypoint2)
3300+{
3301+ UINT32 finaldist = UINT32_MAX;
3302+ I_Assert(waypoint1 != NULL);
3303+ I_Assert(waypoint2 != NULL);
3304+
3305+ {
3306+ const fixed_t xydist =
3307+ P_AproxDistance(waypoint1->mobj->x - waypoint2->mobj->x, waypoint1->mobj->y - waypoint2->mobj->y);
3308+ const fixed_t xyzdist = P_AproxDistance(xydist, waypoint1->mobj->z - waypoint2->mobj->z);
3309+ finaldist = ((UINT32)xyzdist >> FRACBITS);
3310+ }
3311+
3312+ return finaldist;
3313+}
3314+
3315+/*--------------------------------------------------
3316+ void K_DebugWaypointsSpawnLine(waypoint_t *const waypoint1, waypoint_t *const waypoint2)
3317+
3318+ Draw a debugging line between 2 waypoints
3319+
3320+ Input Arguments:-
3321+ waypoint1 - A waypoint to draw the line between
3322+ waypoint2 - The other waypoint to draw the line between
3323+--------------------------------------------------*/
3324+static void K_DebugWaypointsSpawnLine(waypoint_t *const waypoint1, waypoint_t *const waypoint2)
3325+{
3326+ mobj_t *waypointmobj1, *waypointmobj2;
3327+ mobj_t *spawnedmobj;
3328+ fixed_t stepx, stepy, stepz;
3329+ fixed_t x, y, z;
3330+ UINT32 waypointdist;
3331+ INT32 n;
3332+ skincolors_t linkcolour = SKINCOLOR_GREEN;
3333+
3334+ // This array is used to choose which colour should be on this connection
3335+ const skincolors_t linkcolours[] = {
3336+ SKINCOLOR_RED,
3337+ SKINCOLOR_BLUE,
3338+ SKINCOLOR_ORANGE,
3339+ SKINCOLOR_PINK,
3340+ SKINCOLOR_DREAM,
3341+ SKINCOLOR_CYAN,
3342+ SKINCOLOR_WHITE,
3343+ };
3344+ const size_t linkcolourssize = sizeof(linkcolours) / sizeof(skincolors_t);
3345+
3346+ // Error conditions
3347+ I_Assert(waypoint1 != NULL);
3348+ I_Assert(waypoint1->mobj != NULL);
3349+ I_Assert(waypoint2 != NULL);
3350+ I_Assert(waypoint2->mobj != NULL);
3351+ I_Assert(cv_kartdebugwaypoints.value != 0);
3352+
3353+ linkcolour = K_GetWaypointID(waypoint1)%linkcolourssize;
3354+
3355+ waypointmobj1 = waypoint1->mobj;
3356+ waypointmobj2 = waypoint2->mobj;
3357+
3358+ n = SPARKLES_PER_CONNECTION;
3359+
3360+ // For every 2048 fracunits, double the number of sparkles
3361+ waypointdist = K_DistanceBetweenWaypoints(waypoint1, waypoint2);
3362+ n *= (waypointdist / 2048) + 1;
3363+
3364+ // Draw the line
3365+ stepx = (waypointmobj2->x - waypointmobj1->x) / n;
3366+ stepy = (waypointmobj2->y - waypointmobj1->y) / n;
3367+ stepz = (waypointmobj2->z - waypointmobj1->z) / n;
3368+ x = waypointmobj1->x;
3369+ y = waypointmobj1->y;
3370+ z = waypointmobj1->z;
3371+ do
3372+ {
3373+ if ((leveltime + n) % 16 <= 4)
3374+ {
3375+ spawnedmobj = P_SpawnMobj(x, y, z, MT_SPARK);
3376+ P_SetMobjState(spawnedmobj, S_THOK);
3377+ spawnedmobj->state->nextstate = S_NULL;
3378+ spawnedmobj->state->tics = 1;
3379+ spawnedmobj->frame = spawnedmobj->frame & ~FF_TRANSMASK;
3380+ spawnedmobj->color = linkcolours[linkcolour];
3381+ spawnedmobj->scale = FixedMul(FRACUNIT/4, FixedDiv((15 - ((leveltime + n) % 16))*FRACUNIT, 15*FRACUNIT));
3382+ }
3383+
3384+ x += stepx;
3385+ y += stepy;
3386+ z += stepz;
3387+ } while (n--);
3388+}
3389+
3390+/*--------------------------------------------------
3391+ void K_DebugWaypointsVisualise(void)
3392+
3393+ See header file for description.
3394+--------------------------------------------------*/
3395+void K_DebugWaypointsVisualise(void)
3396+{
3397+ mobj_t *waypointmobj;
3398+ mobj_t *debugmobj;
3399+ waypoint_t *waypoint;
3400+ waypoint_t *otherwaypoint;
3401+ UINT32 i;
3402+
3403+ if (waypointcap == NULL)
3404+ {
3405+ // No point putting a debug message here when it could easily happen when turning on the cvar in battle
3406+ return;
3407+ }
3408+ if (cv_kartdebugwaypoints.value == 0)
3409+ {
3410+ // Going to nip this in the bud and say no drawing all this without the cvar, it's not particularly optimised
3411+ return;
3412+ }
3413+
3414+ // Hunt through the waypointcap so we can show all waypoint mobjs and not just ones that were able to be graphed
3415+ for (waypointmobj = waypointcap; waypointmobj != NULL; waypointmobj = waypointmobj->tracer)
3416+ {
3417+ waypoint = K_SearchWaypointHeapForMobj(waypointmobj);
3418+
3419+ debugmobj = P_SpawnMobj(waypointmobj->x, waypointmobj->y, waypointmobj->z, MT_SPARK);
3420+ P_SetMobjState(debugmobj, S_THOK);
3421+
3422+ debugmobj->frame &= ~FF_TRANSMASK;
3423+ debugmobj->frame |= FF_TRANS20;
3424+
3425+ // There's a waypoint setup for this mobj! So draw that it's a valid waypoint and draw lines to its connections
3426+ if (waypoint != NULL)
3427+ {
3428+ if (waypoint->numnextwaypoints == 0 && waypoint->numprevwaypoints == 0)
3429+ {
3430+ debugmobj->color = SKINCOLOR_RED;
3431+ }
3432+ else if (waypoint->numnextwaypoints == 0 || waypoint->numprevwaypoints == 0)
3433+ {
3434+ debugmobj->color = SKINCOLOR_YELLOW;
3435+ }
3436+ else if (waypoint == players[displayplayers[0]].nextwaypoint)
3437+ {
3438+ debugmobj->color = SKINCOLOR_GREEN;
3439+ }
3440+ else
3441+ {
3442+ debugmobj->color = SKINCOLOR_BLUE;
3443+ }
3444+
3445+ // Valid waypoint, so draw lines of SPARKLES to its next or previous waypoints
3446+ if (cv_kartdebugwaypoints.value == 1)
3447+ {
3448+ for (i = 0; i < waypoint->numnextwaypoints; i++)
3449+ {
3450+ if (waypoint->nextwaypoints[i] != NULL)
3451+ {
3452+ otherwaypoint = waypoint->nextwaypoints[i];
3453+ K_DebugWaypointsSpawnLine(waypoint, otherwaypoint);
3454+ }
3455+ }
3456+ }
3457+ else if (cv_kartdebugwaypoints.value == 2)
3458+ {
3459+ for (i = 0; i < waypoint->numprevwaypoints; i++)
3460+ {
3461+ if (waypoint->prevwaypoints[i] != NULL)
3462+ {
3463+ otherwaypoint = waypoint->prevwaypoints[i];
3464+ K_DebugWaypointsSpawnLine(waypoint, otherwaypoint);
3465+ }
3466+ }
3467+ }
3468+ }
3469+ else
3470+ {
3471+ debugmobj->color = SKINCOLOR_RED;
3472+ }
3473+ debugmobj->state->tics = 1;
3474+ debugmobj->state->nextstate = S_NULL;
3475+ }
3476+}
3477+
3478+/*--------------------------------------------------
3479+ static size_t K_GetOpensetBaseSize(void)
3480+
3481+ Gets the base size the Openset hinary heap should have
3482+
3483+ Input Arguments:-
3484+ None
3485+
3486+ Return:-
3487+ The base size the Openset binary heap should have
3488+--------------------------------------------------*/
3489+static size_t K_GetOpensetBaseSize(void)
3490+{
3491+ size_t returnsize = 0;
3492+
3493+ returnsize = baseopensetsize;
3494+
3495+ return returnsize;
3496+}
3497+
3498+/*--------------------------------------------------
3499+ static size_t K_GetClosedsetBaseSize(void)
3500+
3501+ Gets the base size the Closedset heap should have
3502+
3503+ Input Arguments:-
3504+ None
3505+
3506+ Return:-
3507+ The base size the Closedset heap should have
3508+--------------------------------------------------*/
3509+static size_t K_GetClosedsetBaseSize(void)
3510+{
3511+ size_t returnsize = 0;
3512+
3513+ returnsize = baseclosedsetsize;
3514+
3515+ return returnsize;
3516+}
3517+
3518+/*--------------------------------------------------
3519+ static size_t K_GetNodesArrayBaseSize(void)
3520+
3521+ Gets the base size the Nodes array should have
3522+
3523+ Input Arguments:-
3524+ None
3525+
3526+ Return:-
3527+ The base size the Nodes array should have
3528+--------------------------------------------------*/
3529+static size_t K_GetNodesArrayBaseSize(void)
3530+{
3531+ size_t returnsize = 0;
3532+
3533+ returnsize = basenodesarraysize;
3534+
3535+ return returnsize;
3536+}
3537+
3538+/*--------------------------------------------------
3539+ static void K_UpdateOpensetBaseSize(size_t newbaseopensetsize)
3540+
3541+ Sets the new base size of the openset binary heap, if it is bigger than before.
3542+
3543+ Input Arguments:-
3544+ newbaseopensetsize - The size to try and set the base Openset size to
3545+
3546+ Return:-
3547+ None
3548+--------------------------------------------------*/
3549+static void K_UpdateOpensetBaseSize(size_t newbaseopensetsize)
3550+{
3551+ if (newbaseopensetsize > baseopensetsize)
3552+ {
3553+ baseopensetsize = newbaseopensetsize;
3554+ }
3555+}
3556+
3557+/*--------------------------------------------------
3558+ static void K_UpdateClosedsetBaseSize(size_t newbaseclosedsetsize)
3559+
3560+ Sets the new base size of the closedset heap, if it is bigger than before.
3561+
3562+ Input Arguments:-
3563+ newbaseclosedsetsize - The size to try and set the base Closedset size to
3564+
3565+ Return:-
3566+ None
3567+--------------------------------------------------*/
3568+static void K_UpdateClosedsetBaseSize(size_t newbaseclosedsetsize)
3569+{
3570+ if (newbaseclosedsetsize > baseopensetsize)
3571+ {
3572+ baseclosedsetsize = newbaseclosedsetsize;
3573+ }
3574+}
3575+
3576+/*--------------------------------------------------
3577+ static void K_UpdateNodesArrayBaseSize(size_t newnodesarraysize)
3578+
3579+ Sets the new base size of the nodes array, if it is bigger than before.
3580+
3581+ Input Arguments:-
3582+ newnodesarraysize - The size to try and set the base nodes array size to
3583+
3584+ Return:-
3585+ None
3586+--------------------------------------------------*/
3587+static void K_UpdateNodesArrayBaseSize(size_t newnodesarraysize)
3588+{
3589+ if (newnodesarraysize > basenodesarraysize)
3590+ {
3591+ basenodesarraysize = newnodesarraysize;
3592+ }
3593+}
3594+
3595+/*--------------------------------------------------
3596+ static void **K_WaypointPathfindGetNext(void *data, size_t *numconnections)
3597+
3598+ Gets the list of next waypoints as the connecting waypoints. For pathfinding only.
3599+
3600+ Input Arguments:-
3601+ data - Should point to a waypoint_t to get nextwaypoints from
3602+ numconnections - Should point to a size_t to return the number of next waypoints
3603+
3604+ Return:-
3605+ None
3606+--------------------------------------------------*/
3607+static void **K_WaypointPathfindGetNext(void *data, size_t *numconnections)
3608+{
3609+ waypoint_t **connectingwaypoints = NULL;
3610+
3611+ if (data == NULL)
3612+ {
3613+ CONS_Debug(DBG_GAMELOGIC, "K_WaypointPathfindGetNext received NULL data.\n");
3614+ }
3615+ else if (numconnections == NULL)
3616+ {
3617+ CONS_Debug(DBG_GAMELOGIC, "K_WaypointPathfindGetNext received NULL numconnections.\n");
3618+ }
3619+ else
3620+ {
3621+ waypoint_t *waypoint = (waypoint_t *)data;
3622+ connectingwaypoints = waypoint->nextwaypoints;
3623+ *numconnections = waypoint->numnextwaypoints;
3624+ }
3625+
3626+ return (void**)connectingwaypoints;
3627+}
3628+
3629+/*--------------------------------------------------
3630+ static void **K_WaypointPathfindGetPrev(void *data, size_t *numconnections)
3631+
3632+ Gets the list of previous waypoints as the connecting waypoints. For pathfinding only.
3633+
3634+ Input Arguments:-
3635+ data - Should point to a waypoint_t to get prevwaypoints from
3636+ numconnections - Should point to a size_t to return the number of previous waypoints
3637+
3638+ Return:-
3639+ None
3640+--------------------------------------------------*/
3641+static void **K_WaypointPathfindGetPrev(void *data, size_t *numconnections)
3642+{
3643+ waypoint_t **connectingwaypoints = NULL;
3644+
3645+ if (data == NULL)
3646+ {
3647+ CONS_Debug(DBG_GAMELOGIC, "K_WaypointPathfindGetPrev received NULL data.\n");
3648+ }
3649+ else if (numconnections == NULL)
3650+ {
3651+ CONS_Debug(DBG_GAMELOGIC, "K_WaypointPathfindGetPrev received NULL numconnections.\n");
3652+ }
3653+ else
3654+ {
3655+ waypoint_t *waypoint = (waypoint_t *)data;
3656+ connectingwaypoints = waypoint->prevwaypoints;
3657+ *numconnections = waypoint->numprevwaypoints;
3658+ }
3659+
3660+ return (void**)connectingwaypoints;
3661+}
3662+
3663+/*--------------------------------------------------
3664+ static UINT32 *K_WaypointPathfindGetNextCosts(void* data)
3665+
3666+ Gets the list of costs the next waypoints have. For pathfinding only.
3667+
3668+ Input Arguments:-
3669+ data - Should point to a waypoint_t to get nextwaypointdistances from
3670+
3671+ Return:-
3672+ A pointer to an array of UINT32's describing the cost of going from a waypoint to a next waypoint
3673+--------------------------------------------------*/
3674+static UINT32 *K_WaypointPathfindGetNextCosts(void* data)
3675+{
3676+ UINT32 *connectingnodecosts = NULL;
3677+
3678+ if (data == NULL)
3679+ {
3680+ CONS_Debug(DBG_GAMELOGIC, "K_WaypointPathfindGetNextCosts received NULL data.\n");
3681+ }
3682+ else
3683+ {
3684+ waypoint_t *waypoint = (waypoint_t *)data;
3685+ connectingnodecosts = waypoint->nextwaypointdistances;
3686+ }
3687+
3688+ return connectingnodecosts;
3689+}
3690+
3691+/*--------------------------------------------------
3692+ static UINT32 *K_WaypointPathfindGetPrevCosts(void* data)
3693+
3694+ Gets the list of costs the previous waypoints have. For pathfinding only.
3695+
3696+ Input Arguments:-
3697+ data - Should point to a waypoint_t to get prevwaypointdistances from
3698+
3699+ Return:-
3700+ A pointer to an array of UINT32's describing the cost of going from a waypoint to a previous waypoint
3701+--------------------------------------------------*/
3702+static UINT32 *K_WaypointPathfindGetPrevCosts(void* data)
3703+{
3704+ UINT32 *connectingnodecosts = NULL;
3705+
3706+ if (data == NULL)
3707+ {
3708+ CONS_Debug(DBG_GAMELOGIC, "K_WaypointPathfindGetPrevCosts received NULL data.\n");
3709+ }
3710+ else
3711+ {
3712+ waypoint_t *waypoint = (waypoint_t *)data;
3713+ connectingnodecosts = waypoint->prevwaypointdistances;
3714+ }
3715+
3716+ return connectingnodecosts;
3717+}
3718+
3719+/*--------------------------------------------------
3720+ static UINT32 K_WaypointPathfindGetHeuristic(void *data1, void *data2)
3721+
3722+ Gets the heuristic (euclidean distance) between 2 waypoints. For pathfinding only.
3723+
3724+ Input Arguments:-
3725+ data1 - Should point to a waypoint_t for the first waypoint
3726+ data2 - Should point to a waypoint_t for the second waypoint
3727+
3728+ Return:-
3729+ A UINT32 for the heuristic of the 2 waypoints.
3730+--------------------------------------------------*/
3731+static UINT32 K_WaypointPathfindGetHeuristic(void *data1, void *data2)
3732+{
3733+ UINT32 nodeheuristic = UINT32_MAX;
3734+
3735+ if (data1 == NULL)
3736+ {
3737+ CONS_Debug(DBG_GAMELOGIC, "K_WaypointPathfindGetHeuristic received NULL data1.\n");
3738+ }
3739+ else if (data2 == NULL)
3740+ {
3741+ CONS_Debug(DBG_GAMELOGIC, "K_WaypointPathfindGetHeuristic received NULL data2.\n");
3742+ }
3743+ else
3744+ {
3745+ waypoint_t *waypoint1 = (waypoint_t *)data1;
3746+ waypoint_t *waypoint2 = (waypoint_t *)data2;
3747+
3748+ nodeheuristic = K_DistanceBetweenWaypoints(waypoint1, waypoint2);
3749+ }
3750+
3751+ return nodeheuristic;
3752+}
3753+
3754+/*--------------------------------------------------
3755+ static boolean K_WaypointPathfindTraversableAllEnabled(void *data)
3756+
3757+ Checks if a waypoint used as a pathfindnode is traversable. For pathfinding only.
3758+ Variant that accepts shortcut waypoints as traversable.
3759+
3760+ Input Arguments:-
3761+ data - Should point to a waypoint_t to check traversability of
3762+
3763+ Return:-
3764+ True if the waypoint is traversable, false otherwise.
3765+--------------------------------------------------*/
3766+static boolean K_WaypointPathfindTraversableAllEnabled(void *data)
3767+{
3768+ boolean traversable = false;
3769+
3770+ if (data == NULL)
3771+ {
3772+ CONS_Debug(DBG_GAMELOGIC, "K_WaypointPathfindTraversableAllEnabled received NULL data.\n");
3773+ }
3774+ else
3775+ {
3776+ waypoint_t *waypoint = (waypoint_t *)data;
3777+ traversable = (K_GetWaypointIsEnabled(waypoint) == true);
3778+ }
3779+
3780+ return traversable;
3781+}
3782+
3783+/*--------------------------------------------------
3784+ static boolean K_WaypointPathfindTraversableNoShortcuts(void *data)
3785+
3786+ Checks if a waypoint used as a pathfindnode is traversable. For pathfinding only.
3787+ Variant that does not accept shortcut waypoints as traversable.
3788+
3789+ Input Arguments:-
3790+ data - Should point to a waypoint_t to check traversability of
3791+
3792+ Return:-
3793+ True if the waypoint is traversable, false otherwise.
3794+--------------------------------------------------*/
3795+static boolean K_WaypointPathfindTraversableNoShortcuts(void *data)
3796+{
3797+ boolean traversable = false;
3798+
3799+ if (data == NULL)
3800+ {
3801+ CONS_Debug(DBG_GAMELOGIC, "K_WaypointPathfindTraversableNoShortcuts received NULL data.\n");
3802+ }
3803+ else
3804+ {
3805+ waypoint_t *waypoint = (waypoint_t *)data;
3806+ traversable = ((K_GetWaypointIsShortcut(waypoint) == false) && (K_GetWaypointIsEnabled(waypoint) == true));
3807+ }
3808+
3809+ return traversable;
3810+}
3811+
3812+/*--------------------------------------------------
3813+ boolean K_PathfindToWaypoint(
3814+ waypoint_t *const sourcewaypoint,
3815+ waypoint_t *const destinationwaypoint,
3816+ path_t *const returnpath,
3817+ const boolean useshortcuts,
3818+ const boolean huntbackwards)
3819+
3820+ See header file for description.
3821+--------------------------------------------------*/
3822+boolean K_PathfindToWaypoint(
3823+ waypoint_t *const sourcewaypoint,
3824+ waypoint_t *const destinationwaypoint,
3825+ path_t *const returnpath,
3826+ const boolean useshortcuts,
3827+ const boolean huntbackwards)
3828+{
3829+ boolean pathfound = false;
3830+
3831+ if (sourcewaypoint == NULL)
3832+ {
3833+ CONS_Debug(DBG_GAMELOGIC, "NULL sourcewaypoint in K_PathfindToWaypoint.\n");
3834+ }
3835+ else if (destinationwaypoint == NULL)
3836+ {
3837+ CONS_Debug(DBG_GAMELOGIC, "NULL destinationwaypoint in K_PathfindToWaypoint.\n");
3838+ }
3839+ else if (((huntbackwards == false) && (sourcewaypoint->numnextwaypoints == 0))
3840+ || ((huntbackwards == true) && (sourcewaypoint->numprevwaypoints == 0)))
3841+ {
3842+ CONS_Debug(DBG_GAMELOGIC,
3843+ "K_PathfindToWaypoint: sourcewaypoint with ID %d has no next waypoint\n",
3844+ K_GetWaypointID(sourcewaypoint));
3845+ }
3846+ else if (((huntbackwards == false) && (destinationwaypoint->numprevwaypoints == 0))
3847+ || ((huntbackwards == true) && (destinationwaypoint->numnextwaypoints == 0)))
3848+ {
3849+ CONS_Debug(DBG_GAMELOGIC,
3850+ "K_PathfindToWaypoint: destinationwaypoint with ID %d has no previous waypoint\n",
3851+ K_GetWaypointID(destinationwaypoint));
3852+ }
3853+ else
3854+ {
3855+ pathfindsetup_t pathfindsetup = {};
3856+ getconnectednodesfunc nextnodesfunc = K_WaypointPathfindGetNext;
3857+ getnodeconnectioncostsfunc nodecostsfunc = K_WaypointPathfindGetNextCosts;
3858+ getnodeheuristicfunc heuristicfunc = K_WaypointPathfindGetHeuristic;
3859+ getnodetraversablefunc traversablefunc = K_WaypointPathfindTraversableNoShortcuts;
3860+
3861+ if (huntbackwards)
3862+ {
3863+ nextnodesfunc = K_WaypointPathfindGetPrev;
3864+ nodecostsfunc = K_WaypointPathfindGetPrevCosts;
3865+ }
3866+ if (useshortcuts)
3867+ {
3868+ traversablefunc = K_WaypointPathfindTraversableAllEnabled;
3869+ }
3870+
3871+
3872+ pathfindsetup.opensetcapacity = K_GetOpensetBaseSize();
3873+ pathfindsetup.closedsetcapacity = K_GetClosedsetBaseSize();
3874+ pathfindsetup.nodesarraycapacity = K_GetNodesArrayBaseSize();
3875+ pathfindsetup.startnodedata = sourcewaypoint;
3876+ pathfindsetup.endnodedata = destinationwaypoint;
3877+ pathfindsetup.getconnectednodes = nextnodesfunc;
3878+ pathfindsetup.getconnectioncosts = nodecostsfunc;
3879+ pathfindsetup.getheuristic = heuristicfunc;
3880+ pathfindsetup.gettraversable = traversablefunc;
3881+
3882+ pathfound = K_PathfindAStar(returnpath, &pathfindsetup);
3883+
3884+ K_UpdateOpensetBaseSize(pathfindsetup.opensetcapacity);
3885+ K_UpdateClosedsetBaseSize(pathfindsetup.closedsetcapacity);
3886+ K_UpdateNodesArrayBaseSize(pathfindsetup.nodesarraycapacity);
3887+ }
3888+
3889+ return pathfound;
3890+}
3891+
3892+/*--------------------------------------------------
3893+ waypoint_t *K_GetNextWaypointToDestination(
3894+ waypoint_t *const sourcewaypoint,
3895+ waypoint_t *const destinationwaypoint,
3896+ const boolean useshortcuts,
3897+ const boolean huntbackwards)
3898+
3899+ See header file for description.
3900+--------------------------------------------------*/
3901+waypoint_t *K_GetNextWaypointToDestination(
3902+ waypoint_t *const sourcewaypoint,
3903+ waypoint_t *const destinationwaypoint,
3904+ const boolean useshortcuts,
3905+ const boolean huntbackwards)
3906+{
3907+ waypoint_t *nextwaypoint = NULL;
3908+
3909+ if (sourcewaypoint == NULL)
3910+ {
3911+ CONS_Debug(DBG_GAMELOGIC, "NULL sourcewaypoint in K_GetNextWaypointToDestination.\n");
3912+ }
3913+ else if (destinationwaypoint == NULL)
3914+ {
3915+ CONS_Debug(DBG_GAMELOGIC, "NULL destinationwaypoint in K_GetNextWaypointToDestination.\n");
3916+ }
3917+ else if (sourcewaypoint == destinationwaypoint)
3918+ {
3919+ // Source and destination waypoint are the same, we're already there
3920+ nextwaypoint = destinationwaypoint;
3921+ }
3922+ else if (((huntbackwards == false) && (sourcewaypoint->numnextwaypoints == 0))
3923+ || ((huntbackwards == true) && (sourcewaypoint->numprevwaypoints == 0)))
3924+ {
3925+ CONS_Debug(DBG_GAMELOGIC,
3926+ "K_GetNextWaypointToDestination: sourcewaypoint with ID %d has no next waypoint\n",
3927+ K_GetWaypointID(sourcewaypoint));
3928+ }
3929+ else if (((huntbackwards == false) && (destinationwaypoint->numprevwaypoints == 0))
3930+ || ((huntbackwards == true) && (destinationwaypoint->numnextwaypoints == 0)))
3931+ {
3932+ CONS_Debug(DBG_GAMELOGIC,
3933+ "K_GetNextWaypointToDestination: destinationwaypoint with ID %d has no previous waypoint\n",
3934+ K_GetWaypointID(destinationwaypoint));
3935+ }
3936+ else
3937+ {
3938+ // If there is only 1 next waypoint it doesn't matter if it's a shortcut
3939+ if ((huntbackwards == false) && sourcewaypoint->numnextwaypoints == 1)
3940+ {
3941+ nextwaypoint = sourcewaypoint->nextwaypoints[0];
3942+ }
3943+ else if ((huntbackwards == true) && sourcewaypoint->numprevwaypoints == 1)
3944+ {
3945+ nextwaypoint = sourcewaypoint->prevwaypoints[0];
3946+ }
3947+ else
3948+ {
3949+ path_t pathtowaypoint = {};
3950+ pathfindsetup_t pathfindsetup = {};
3951+ boolean pathfindsuccess = false;
3952+ getconnectednodesfunc nextnodesfunc = K_WaypointPathfindGetNext;
3953+ getnodeconnectioncostsfunc nodecostsfunc = K_WaypointPathfindGetNextCosts;
3954+ getnodeheuristicfunc heuristicfunc = K_WaypointPathfindGetHeuristic;
3955+ getnodetraversablefunc traversablefunc = K_WaypointPathfindTraversableNoShortcuts;
3956+
3957+ if (huntbackwards)
3958+ {
3959+ nextnodesfunc = K_WaypointPathfindGetPrev;
3960+ nodecostsfunc = K_WaypointPathfindGetPrevCosts;
3961+ }
3962+ if (useshortcuts)
3963+ {
3964+ traversablefunc = K_WaypointPathfindTraversableAllEnabled;
3965+ }
3966+
3967+
3968+ pathfindsetup.opensetcapacity = K_GetOpensetBaseSize();
3969+ pathfindsetup.closedsetcapacity = K_GetClosedsetBaseSize();
3970+ pathfindsetup.nodesarraycapacity = K_GetNodesArrayBaseSize();
3971+ pathfindsetup.startnodedata = sourcewaypoint;
3972+ pathfindsetup.endnodedata = destinationwaypoint;
3973+ pathfindsetup.getconnectednodes = nextnodesfunc;
3974+ pathfindsetup.getconnectioncosts = nodecostsfunc;
3975+ pathfindsetup.getheuristic = heuristicfunc;
3976+ pathfindsetup.gettraversable = traversablefunc;
3977+
3978+ pathfindsuccess = K_PathfindAStar(&pathtowaypoint, &pathfindsetup);
3979+
3980+ K_UpdateOpensetBaseSize(pathfindsetup.opensetcapacity);
3981+ K_UpdateClosedsetBaseSize(pathfindsetup.closedsetcapacity);
3982+ K_UpdateNodesArrayBaseSize(pathfindsetup.nodesarraycapacity);
3983+
3984+ if (pathfindsuccess)
3985+ {
3986+ // A direct path to the destination has been found.
3987+ if (pathtowaypoint.numnodes > 1)
3988+ {
3989+ nextwaypoint = (waypoint_t*)pathtowaypoint.array[1].nodedata;
3990+ }
3991+ else
3992+ {
3993+ // Shouldn't happen, as this is the source waypoint.
3994+ CONS_Debug(DBG_GAMELOGIC, "Only one waypoint pathfound in K_GetNextWaypointToDestination.\n");
3995+ nextwaypoint = (waypoint_t*)pathtowaypoint.array[0].nodedata;
3996+ }
3997+
3998+ Z_Free(pathtowaypoint.array);
3999+ }
4000+ else
4001+ {
4002+ size_t i = 0U;
4003+ waypoint_t **nextwaypointlist = NULL;
4004+ size_t numnextwaypoints = 0U;
4005+ boolean waypointisenabled = true;
4006+ boolean waypointisshortcut = false;
4007+
4008+ if (huntbackwards)
4009+ {
4010+ nextwaypointlist = sourcewaypoint->prevwaypoints;
4011+ numnextwaypoints = sourcewaypoint->numprevwaypoints;
4012+ }
4013+ else
4014+ {
4015+ nextwaypointlist = sourcewaypoint->nextwaypoints;
4016+ numnextwaypoints = sourcewaypoint->numnextwaypoints;
4017+ }
4018+
4019+ // No direct path to the destination has been found, choose a next waypoint from what is available
4020+ // 1. If shortcuts are allowed, pick the first shortcut path that is enabled
4021+ // 2. If shortcuts aren't allowed, or there are no shortcuts, pick the first enabled waypoint
4022+ // 3. If there's no waypoints enabled, then nothing can be done and there is no next waypoint
4023+ if (useshortcuts)
4024+ {
4025+ for (i = 0U; i < numnextwaypoints; i++)
4026+ {
4027+ waypointisenabled = K_GetWaypointIsEnabled(nextwaypointlist[i]);
4028+ waypointisshortcut = K_GetWaypointIsShortcut(nextwaypointlist[i]);
4029+
4030+ if (waypointisenabled && waypointisshortcut)
4031+ {
4032+ nextwaypoint = nextwaypointlist[i];
4033+ break;
4034+ }
4035+ }
4036+ }
4037+
4038+ if (nextwaypoint == NULL)
4039+ {
4040+ for (i = 0U; i < numnextwaypoints; i++)
4041+ {
4042+ waypointisenabled = K_GetWaypointIsEnabled(nextwaypointlist[i]);
4043+
4044+ if (waypointisenabled)
4045+ {
4046+ nextwaypoint = nextwaypointlist[i];
4047+ break;
4048+ }
4049+ }
4050+ }
4051+ }
4052+ }
4053+ }
4054+
4055+ return nextwaypoint;
4056+}
4057+
4058+/*--------------------------------------------------
4059+ boolean K_CheckWaypointForMobj(waypoint_t *const waypoint, void *const mobjpointer)
4060+
4061+ Compares a waypoint's mobj and a void pointer that *should* point to an mobj. Intended for use with the
4062+ K_SearchWaypoint functions ONLY. No, it is not my responsibility to make sure the pointer you sent in is
4063+ actually an mobj.
4064+
4065+ Input Arguments:-
4066+ waypoint - The waypoint that is currently being compared against
4067+ mobjpointer - A pointer that should be to an mobj to check with the waypoint for matching
4068+
4069+ Return:-
4070+ The waypoint that uses that mobj, NULL if it wasn't found, NULL if it isn't an MT_WAYPOINT
4071+--------------------------------------------------*/
4072+static boolean K_CheckWaypointForMobj(waypoint_t *const waypoint, void *const mobjpointer)
4073+{
4074+ boolean mobjsmatch = false;
4075+
4076+ // Error Conditions
4077+ I_Assert(waypoint != NULL);
4078+ I_Assert(waypoint->mobj != NULL);
4079+ I_Assert(mobjpointer != NULL);
4080+
4081+ {
4082+ mobj_t *const mobj = (mobj_t *)mobjpointer;
4083+
4084+ if (P_MobjWasRemoved(mobj))
4085+ {
4086+ CONS_Debug(DBG_GAMELOGIC, "Mobj Was Removed in K_CheckWaypointForMobj");
4087+ }
4088+ else if (mobj->type != MT_WAYPOINT)
4089+ {
4090+ CONS_Debug(DBG_GAMELOGIC, "Non MT_WAYPOINT mobj in K_CheckWaypointForMobj. Type=%d.\n", mobj->type);
4091+ }
4092+ else
4093+ {
4094+ // All that error checking for 3 lines :^)
4095+ if (waypoint->mobj == mobj)
4096+ {
4097+ mobjsmatch = true;
4098+ }
4099+ }
4100+ }
4101+
4102+ return mobjsmatch;
4103+}
4104+
4105+/*--------------------------------------------------
4106+ waypoint_t *K_TraverseWaypoints(
4107+ waypoint_t *waypoint,
4108+ boolean (*conditionalfunc)(waypoint_t *const, void *const),
4109+ void *const condition,
4110+ boolean *const visitedarray)
4111+
4112+ Searches through the waypoint list for a waypoint that matches a condition, just does a simple flood search
4113+ of the graph with no pathfinding
4114+
4115+ Input Arguments:-
4116+ waypoint - The waypoint that is currently being checked, goes through nextwaypoints after this one
4117+ conditionalfunc - The function that will be used to check a waypoint against condition
4118+ condition - the condition being checked by conditionalfunc
4119+ visitedarray - An array of booleans that let us know if a waypoint has already been checked, marked to true
4120+ when one is, so we don't repeat going down a path. Cannot be changed to a different pointer
4121+
4122+ Return:-
4123+ The waypoint that uses that mobj, NULL if it wasn't found, NULL if it isn't an MT_WAYPOINT
4124+--------------------------------------------------*/
4125+static waypoint_t *K_TraverseWaypoints(
4126+ waypoint_t *waypoint,
4127+ boolean (*conditionalfunc)(waypoint_t *const, void *const),
4128+ void *const condition,
4129+ boolean *const visitedarray)
4130+{
4131+ waypoint_t *foundwaypoint = NULL;
4132+
4133+ // Error conditions
4134+ I_Assert(condition != NULL);
4135+ I_Assert(conditionalfunc != NULL);
4136+ I_Assert(visitedarray != NULL);
4137+
4138+searchwaypointstart:
4139+ I_Assert(waypoint != NULL);
4140+
4141+ {
4142+ size_t waypointindex = K_GetWaypointHeapIndex(waypoint);
4143+ // If we've already visited this waypoint, we've already checked the next waypoints, no point continuing
4144+ if ((waypointindex != SIZE_MAX) && (visitedarray[waypointindex] != true))
4145+ {
4146+ // Mark this waypoint as being visited
4147+ visitedarray[waypointindex] = true;
4148+
4149+ if (conditionalfunc(waypoint, condition) == true)
4150+ {
4151+ foundwaypoint = waypoint;
4152+ }
4153+ else
4154+ {
4155+ // If this waypoint only has one next waypoint, set the waypoint to be the next one and jump back
4156+ // to the start, this is to avoid going too deep into the stack where we can
4157+ // Yes this is a horrible horrible goto, but the alternative is a do while loop with an extra
4158+ // variable, which is slightly more confusing. This is probably the fastest and least confusing
4159+ // option that keeps this functionality
4160+ if (waypoint->numnextwaypoints == 1 && waypoint->nextwaypoints[0] != NULL)
4161+ {
4162+ waypoint = waypoint->nextwaypoints[0];
4163+ goto searchwaypointstart;
4164+ }
4165+ else if (waypoint->numnextwaypoints != 0)
4166+ {
4167+ // The nesting here is a bit nasty, but it's better than potentially a lot of function calls on
4168+ // the stack, and another function would be very small in this case
4169+ UINT32 i;
4170+ // For each next waypoint, Search through it's path continuation until we hopefully find the one
4171+ // we're looking for
4172+ for (i = 0; i < waypoint->numnextwaypoints; i++)
4173+ {
4174+ if (waypoint->nextwaypoints[i] != NULL)
4175+ {
4176+ foundwaypoint = K_TraverseWaypoints(waypoint->nextwaypoints[i], conditionalfunc,
4177+ condition, visitedarray);
4178+
4179+ if (foundwaypoint != NULL)
4180+ {
4181+ break;
4182+ }
4183+ }
4184+ }
4185+ }
4186+ else
4187+ {
4188+ // No next waypoints, this function will be returned from
4189+ }
4190+ }
4191+ }
4192+ }
4193+
4194+ return foundwaypoint;
4195+}
4196+
4197+/*--------------------------------------------------
4198+ waypoint_t *K_SearchWaypointGraph(
4199+ boolean (*conditionalfunc)(waypoint_t *const, void *const),
4200+ void *const condition)
4201+
4202+ Searches through the waypoint graph for a waypoint that matches the conditional
4203+
4204+ Input Arguments:-
4205+ conditionalfunc - The function that will be used to check a waypoint against condition
4206+ condition - the condition being checked by conditionalfunc
4207+
4208+ Return:-
4209+ The waypoint that uses that mobj, NULL if it wasn't found, NULL if it isn't an MT_WAYPOINT
4210+--------------------------------------------------*/
4211+static waypoint_t *K_SearchWaypointGraph(
4212+ boolean (*conditionalfunc)(waypoint_t *const, void *const),
4213+ void *const condition)
4214+{
4215+ boolean *visitedarray = NULL;
4216+ waypoint_t *foundwaypoint = NULL;
4217+
4218+ // Error conditions
4219+ I_Assert(condition != NULL);
4220+ I_Assert(conditionalfunc != NULL);
4221+ I_Assert(firstwaypoint != NULL);
4222+
4223+ visitedarray = Z_Calloc(numwaypoints * sizeof(boolean), PU_STATIC, NULL);
4224+ foundwaypoint = K_TraverseWaypoints(firstwaypoint, conditionalfunc, condition, visitedarray);
4225+ Z_Free(visitedarray);
4226+
4227+ return foundwaypoint;
4228+}
4229+
4230+/*--------------------------------------------------
4231+ waypoint_t *K_SearchWaypointGraphForMobj(mobj_t * const mobj)
4232+
4233+ See header file for description.
4234+--------------------------------------------------*/
4235+waypoint_t *K_SearchWaypointGraphForMobj(mobj_t *const mobj)
4236+{
4237+ waypoint_t *foundwaypoint = NULL;
4238+
4239+ if (mobj == NULL || P_MobjWasRemoved(mobj))
4240+ {
4241+ CONS_Debug(DBG_GAMELOGIC, "NULL mobj in K_SearchWaypointGraphForMobj.\n");
4242+ }
4243+ else if (mobj->type != MT_WAYPOINT)
4244+ {
4245+ CONS_Debug(DBG_GAMELOGIC, "Non MT_WAYPOINT mobj in K_SearchWaypointGraphForMobj. Type=%d.\n", mobj->type);
4246+ }
4247+ else
4248+ {
4249+ foundwaypoint = K_SearchWaypointGraph(K_CheckWaypointForMobj, (void *)mobj);
4250+ }
4251+
4252+ return foundwaypoint;
4253+}
4254+
4255+/*--------------------------------------------------
4256+ waypoint_t *K_SearchWaypointHeap(
4257+ boolean (*conditionalfunc)(waypoint_t *const, void *const),
4258+ void *const condition)
4259+
4260+ Searches through the waypoint heap for a waypoint that matches the conditional
4261+
4262+ Input Arguments:-
4263+ conditionalfunc - The function that will be used to check a waypoint against condition
4264+ condition - the condition being checked by conditionalfunc
4265+
4266+ Return:-
4267+ The waypoint that uses that mobj, NULL if it wasn't found, NULL if it isn't an MT_WAYPOINT
4268+--------------------------------------------------*/
4269+static waypoint_t *K_SearchWaypointHeap(
4270+ boolean (*conditionalfunc)(waypoint_t *const, void *const),
4271+ void *const condition)
4272+{
4273+ UINT32 i = 0;
4274+ waypoint_t *foundwaypoint = NULL;
4275+
4276+ // Error conditions
4277+ I_Assert(condition != NULL);
4278+ I_Assert(conditionalfunc != NULL);
4279+ I_Assert(waypointheap != NULL);
4280+
4281+ // Simply search through the waypointheap for the waypoint which matches the condition. Much simpler when no
4282+ // pathfinding is needed. Search up to numwaypoints and NOT numwaypointmobjs as numwaypoints is the real number of
4283+ // waypoints setup in the heap while numwaypointmobjs ends up being the capacity
4284+ for (i = 0; i < numwaypoints; i++)
4285+ {
4286+ if (conditionalfunc(&waypointheap[i], condition) == true)
4287+ {
4288+ foundwaypoint = &waypointheap[i];
4289+ break;
4290+ }
4291+ }
4292+
4293+ return foundwaypoint;
4294+}
4295+
4296+/*--------------------------------------------------
4297+ waypoint_t *K_SearchWaypointHeapForMobj(mobj_t *const mobj)
4298+
4299+ See header file for description.
4300+--------------------------------------------------*/
4301+waypoint_t *K_SearchWaypointHeapForMobj(mobj_t *const mobj)
4302+{
4303+ waypoint_t *foundwaypoint = NULL;
4304+
4305+ if (mobj == NULL || P_MobjWasRemoved(mobj))
4306+ {
4307+ CONS_Debug(DBG_GAMELOGIC, "NULL mobj in K_SearchWaypointHeapForMobj.\n");
4308+ }
4309+ else if (mobj->type != MT_WAYPOINT)
4310+ {
4311+ CONS_Debug(DBG_GAMELOGIC, "Non MT_WAYPOINT mobj in K_SearchWaypointHeapForMobj. Type=%d.\n", mobj->type);
4312+ }
4313+ else
4314+ {
4315+ foundwaypoint = K_SearchWaypointHeap(K_CheckWaypointForMobj, (void *)mobj);
4316+ }
4317+
4318+ return foundwaypoint;
4319+}
4320+
4321+/*--------------------------------------------------
4322+ static UINT32 K_SetupCircuitLength(void)
4323+
4324+ Sets up the Circuit Length by getting the best path from the finishwaypoint back to itself.
4325+ On sprint maps, circuitlength is 0.
4326+
4327+ Input Arguments:-
4328+ None
4329+
4330+ Return:-
4331+ Length of the circuit
4332+--------------------------------------------------*/
4333+static UINT32 K_SetupCircuitLength(void)
4334+{
4335+ I_Assert(firstwaypoint != NULL);
4336+ I_Assert(numwaypoints > 0U);
4337+ I_Assert(finishline != NULL);
4338+
4339+ // The circuit length only makes sense in circuit maps, sprint maps do not need to use it
4340+ // The main usage of the circuit length is to add onto a player's distance to finish line so crossing the finish
4341+ // line places people correctly relative to each other
4342+ if ((mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE) == LF_SECTIONRACE)
4343+ {
4344+ circuitlength = 0U;
4345+ }
4346+ else
4347+ {
4348+ // Create a fake finishline waypoint, then try and pathfind to the finishline from it
4349+ waypoint_t fakefinishline = *finishline;
4350+ path_t bestcircuitpath = {};
4351+ const boolean useshortcuts = false;
4352+ const boolean huntbackwards = false;
4353+
4354+ K_PathfindToWaypoint(&fakefinishline, finishline, &bestcircuitpath, useshortcuts, huntbackwards);
4355+
4356+ circuitlength = bestcircuitpath.totaldist;
4357+
4358+ Z_Free(bestcircuitpath.array);
4359+ }
4360+
4361+ return circuitlength;
4362+}
4363+
4364+/*--------------------------------------------------
4365+ static void K_AddPrevToWaypoint(waypoint_t *const waypoint, waypoint_t *const prevwaypoint)
4366+
4367+ Adds another waypoint to a waypoint's previous waypoint list, this needs to be done like this because there is no
4368+ way to identify previous waypoints from just IDs, so we need to reallocate the memory for every previous waypoint
4369+
4370+ Input Arguments:-
4371+ waypoint - The waypoint which is having its previous waypoint list added to
4372+ prevwaypoint - The waypoint which is being added to the previous waypoint list
4373+
4374+ Return:-
4375+ Pointer to waypoint_t for the rest of the waypoint data to be placed into
4376+--------------------------------------------------*/
4377+static void K_AddPrevToWaypoint(waypoint_t *const waypoint, waypoint_t *const prevwaypoint)
4378+{
4379+ // Error conditions
4380+ I_Assert(waypoint != NULL);
4381+ I_Assert(prevwaypoint != NULL);
4382+
4383+ waypoint->numprevwaypoints++;
4384+ waypoint->prevwaypoints =
4385+ Z_Realloc(waypoint->prevwaypoints, waypoint->numprevwaypoints * sizeof(waypoint_t *), PU_LEVEL, NULL);
4386+
4387+ if (!waypoint->prevwaypoints)
4388+ {
4389+ I_Error("K_AddPrevToWaypoint: Failed to reallocate memory for previous waypoints.");
4390+ }
4391+
4392+ waypoint->prevwaypointdistances =
4393+ Z_Realloc(waypoint->prevwaypointdistances, waypoint->numprevwaypoints * sizeof(fixed_t), PU_LEVEL, NULL);
4394+
4395+ if (!waypoint->prevwaypointdistances)
4396+ {
4397+ I_Error("K_AddPrevToWaypoint: Failed to reallocate memory for previous waypoint distances.");
4398+ }
4399+
4400+ waypoint->prevwaypoints[waypoint->numprevwaypoints - 1] = prevwaypoint;
4401+}
4402+
4403+/*--------------------------------------------------
4404+ static waypoint_t *K_MakeWaypoint(mobj_t *const mobj)
4405+
4406+ Make a new waypoint from a map object. Setups up most of the data for it, and allocates most memory
4407+ Remaining creation is handled in K_SetupWaypoint
4408+
4409+ Input Arguments:-
4410+ mobj - The map object that this waypoint is represented by
4411+
4412+ Return:-
4413+ Pointer to the setup waypoint, NULL if one was not setup
4414+--------------------------------------------------*/
4415+static waypoint_t *K_MakeWaypoint(mobj_t *const mobj)
4416+{
4417+ waypoint_t *madewaypoint = NULL;
4418+ mobj_t *otherwaypointmobj = NULL;
4419+
4420+ // Error conditions
4421+ I_Assert(mobj != NULL);
4422+ I_Assert(!P_MobjWasRemoved(mobj));
4423+ I_Assert(waypointcap != NULL); // No waypoint mobjs in map load
4424+ I_Assert(numwaypoints < numwaypointmobjs); // waypoint array reached max capacity
4425+
4426+ // numwaypoints is incremented later in K_SetupWaypoint
4427+ madewaypoint = &waypointheap[numwaypoints];
4428+ numwaypoints++;
4429+
4430+ P_SetTarget(&madewaypoint->mobj, mobj);
4431+
4432+ // Don't allow a waypoint that has its next ID set to itself to work
4433+ if (mobj->threshold != mobj->movecount) {
4434+ // Go through the other waypoint mobjs in the map to find out how many waypoints are after this one
4435+ for (otherwaypointmobj = waypointcap; otherwaypointmobj != NULL; otherwaypointmobj = otherwaypointmobj->tracer)
4436+ {
4437+ // threshold = next waypoint id, movecount = my id
4438+ if (mobj->threshold == otherwaypointmobj->movecount)
4439+ {
4440+ madewaypoint->numnextwaypoints++;
4441+ }
4442+ }
4443+ }
4444+
4445+ // No next waypoints
4446+ if (madewaypoint->numnextwaypoints != 0)
4447+ {
4448+ // Allocate memory to hold enough pointers to all of the next waypoints
4449+ madewaypoint->nextwaypoints =
4450+ Z_Calloc(madewaypoint->numnextwaypoints * sizeof(waypoint_t *), PU_LEVEL, NULL);
4451+ if (madewaypoint->nextwaypoints == NULL)
4452+ {
4453+ I_Error("K_MakeWaypoint: Out of Memory allocating next waypoints.");
4454+ }
4455+ madewaypoint->nextwaypointdistances =
4456+ Z_Calloc(madewaypoint->numnextwaypoints * sizeof(fixed_t), PU_LEVEL, NULL);
4457+ if (madewaypoint->nextwaypointdistances == NULL)
4458+ {
4459+ I_Error("K_MakeWaypoint: Out of Memory allocating next waypoint distances.");
4460+ }
4461+ }
4462+
4463+ return madewaypoint;
4464+}
4465+
4466+/*--------------------------------------------------
4467+ static waypoint_t *K_SetupWaypoint(mobj_t *const mobj)
4468+
4469+ Either gets an already made waypoint, or sets up a new waypoint for an mobj,
4470+ including next and previous waypoints
4471+
4472+ Input Arguments:-
4473+ mobj - The map object that this waypoint is represented by
4474+
4475+ Return:-
4476+ Pointer to the setup waypoint, NULL if one was not setup
4477+--------------------------------------------------*/
4478+static waypoint_t *K_SetupWaypoint(mobj_t *const mobj)
4479+{
4480+ waypoint_t *thiswaypoint = NULL;
4481+
4482+ // Error conditions
4483+ I_Assert(mobj != NULL);
4484+ I_Assert(!P_MobjWasRemoved(mobj));
4485+ I_Assert(mobj->type == MT_WAYPOINT);
4486+ I_Assert(waypointcap != NULL); // no waypoint mobjs in map load
4487+
4488+ // If we have waypoints already created, search through them first to see if this mobj is already added.
4489+ if (firstwaypoint != NULL)
4490+ {
4491+ thiswaypoint = K_SearchWaypointHeapForMobj(mobj);
4492+ }
4493+
4494+ // The waypoint hasn't already been made, so make it
4495+ if (thiswaypoint == NULL)
4496+ {
4497+ mobj_t *otherwaypointmobj = NULL;
4498+ UINT32 nextwaypointindex = 0;
4499+
4500+ thiswaypoint = K_MakeWaypoint(mobj);
4501+
4502+ if (thiswaypoint != NULL)
4503+ {
4504+ // Set the first waypoint if it isn't already
4505+ if (firstwaypoint == NULL)
4506+ {
4507+ firstwaypoint = thiswaypoint;
4508+ }
4509+
4510+ if (K_GetWaypointIsFinishline(thiswaypoint))
4511+ {
4512+ if (finishline != NULL)
4513+ {
4514+ const INT32 oldfinishlineid = K_GetWaypointID(finishline);
4515+ const INT32 thiswaypointid = K_GetWaypointID(thiswaypoint);
4516+ CONS_Alert(
4517+ CONS_WARNING, "Multiple finish line waypoints with IDs %d and %d! Using %d.",
4518+ oldfinishlineid, thiswaypointid, thiswaypointid);
4519+ }
4520+ finishline = thiswaypoint;
4521+ }
4522+
4523+ if (thiswaypoint->numnextwaypoints > 0)
4524+ {
4525+ waypoint_t *nextwaypoint = NULL;
4526+ fixed_t nextwaypointdistance = 0;
4527+ // Go through the waypoint mobjs to setup the next waypoints and make this waypoint know they're its
4528+ // next. I kept this out of K_MakeWaypoint so the stack isn't gone down as deep
4529+ for (otherwaypointmobj = waypointcap;
4530+ otherwaypointmobj != NULL;
4531+ otherwaypointmobj = otherwaypointmobj->tracer)
4532+ {
4533+ // threshold = next waypoint id, movecount = my id
4534+ if (mobj->threshold == otherwaypointmobj->movecount)
4535+ {
4536+ nextwaypoint = K_SetupWaypoint(otherwaypointmobj);
4537+ nextwaypointdistance = K_DistanceBetweenWaypoints(thiswaypoint, nextwaypoint);
4538+ thiswaypoint->nextwaypoints[nextwaypointindex] = nextwaypoint;
4539+ thiswaypoint->nextwaypointdistances[nextwaypointindex] = nextwaypointdistance;
4540+ K_AddPrevToWaypoint(nextwaypoint, thiswaypoint);
4541+ nextwaypoint->prevwaypointdistances[nextwaypoint->numprevwaypoints - 1] = nextwaypointdistance;
4542+ nextwaypointindex++;
4543+ }
4544+ if (nextwaypointindex >= thiswaypoint->numnextwaypoints)
4545+ {
4546+ break;
4547+ }
4548+ }
4549+ }
4550+ else
4551+ {
4552+ CONS_Alert(
4553+ CONS_WARNING, "Waypoint with ID %d has no next waypoint.\n", K_GetWaypointID(thiswaypoint));
4554+ }
4555+ }
4556+ else
4557+ {
4558+ CONS_Debug(DBG_SETUP, "K_SetupWaypoint failed to make new waypoint with ID %d.\n", mobj->movecount);
4559+ }
4560+ }
4561+
4562+ return thiswaypoint;
4563+}
4564+
4565+/*--------------------------------------------------
4566+ static boolean K_AllocateWaypointHeap(void)
4567+
4568+ Allocates the waypoint heap enough space for the number of waypoint mobjs on the map
4569+
4570+ Return:-
4571+ True if the allocation was successful, false if it wasn't. Will I_Error if out of memory still.
4572+--------------------------------------------------*/
4573+static boolean K_AllocateWaypointHeap(void)
4574+{
4575+ mobj_t *waypointmobj = NULL;
4576+ boolean allocationsuccessful = false;
4577+
4578+ // Error conditions
4579+ I_Assert(waypointheap == NULL); // waypointheap is already allocated
4580+ I_Assert(waypointcap != NULL); // no waypoint mobjs at map load
4581+
4582+ // This should be an allocation for the first time. Reset the number of mobjs back to 0 if it's not already
4583+ numwaypointmobjs = 0;
4584+
4585+ // Find how many waypoint mobjs there are in the map, this is the maximum number of waypoints there CAN be
4586+ for (waypointmobj = waypointcap; waypointmobj != NULL; waypointmobj = waypointmobj->tracer)
4587+ {
4588+ if (waypointmobj->type != MT_WAYPOINT)
4589+ {
4590+ CONS_Debug(DBG_SETUP,
4591+ "Non MT_WAYPOINT mobj in waypointcap in K_AllocateWaypointHeap. Type=%d\n.", waypointmobj->type);
4592+ continue;
4593+ }
4594+
4595+ numwaypointmobjs++;
4596+ }
4597+
4598+ if (numwaypointmobjs > 0)
4599+ {
4600+ // Allocate space in the heap for every mobj, it's possible some mobjs aren't linked up and not all of the
4601+ // heap allocated will be used, but it's a fairly reasonable assumption that this isn't going to be awful
4602+ waypointheap = Z_Calloc(numwaypointmobjs * sizeof(waypoint_t), PU_LEVEL, NULL);
4603+
4604+ if (waypointheap == NULL)
4605+ {
4606+ // We could theoretically CONS_Debug here and continue without using waypoints, but I feel that will
4607+ // require error checks that will end up spamming the console when we think waypoints SHOULD be working.
4608+ // Safer to just exit if out of memory
4609+ I_Error("K_AllocateWaypointHeap: Out of memory.");
4610+ }
4611+ allocationsuccessful = true;
4612+ }
4613+ else
4614+ {
4615+ CONS_Debug(DBG_SETUP, "No waypoint mobjs in waypointcap.\n");
4616+ }
4617+
4618+ return allocationsuccessful;
4619+}
4620+
4621+/*--------------------------------------------------
4622+ void K_FreeWaypoints(void)
4623+
4624+ For safety, this will free the waypointheap and all the waypoints allocated if they aren't already before they
4625+ are setup. If the PU_LEVEL tag is cleared, make sure to call K_ClearWaypoints or this will try to free already
4626+ freed memory!
4627+--------------------------------------------------*/
4628+static void K_FreeWaypoints(void)
4629+{
4630+ if (waypointheap != NULL)
4631+ {
4632+ // Free the waypointheap
4633+ Z_Free(waypointheap);
4634+ }
4635+
4636+ K_ClearWaypoints();
4637+}
4638+
4639+/*--------------------------------------------------
4640+ boolean K_SetupWaypointList(void)
4641+
4642+ See header file for description.
4643+--------------------------------------------------*/
4644+boolean K_SetupWaypointList(void)
4645+{
4646+ boolean setupsuccessful = false;
4647+
4648+ K_FreeWaypoints();
4649+
4650+ if (!waypointcap)
4651+ {
4652+ CONS_Alert(CONS_ERROR, "No waypoints in map.\n");
4653+ }
4654+ else
4655+ {
4656+ if (K_AllocateWaypointHeap() == true)
4657+ {
4658+ mobj_t *waypointmobj = NULL;
4659+
4660+ // Loop through the waypointcap here so that all waypoints are added to the heap, and allow easier debugging
4661+ for (waypointmobj = waypointcap; waypointmobj; waypointmobj = waypointmobj->tracer)
4662+ {
4663+ K_SetupWaypoint(waypointmobj);
4664+ }
4665+
4666+ if (firstwaypoint == NULL)
4667+ {
4668+ CONS_Alert(CONS_ERROR, "No waypoints in map.\n");
4669+ }
4670+ else
4671+ {
4672+ CONS_Debug(DBG_SETUP, "Successfully setup %s waypoints.\n", sizeu1(numwaypoints));
4673+ if (finishline == NULL)
4674+ {
4675+ CONS_Alert(
4676+ CONS_WARNING, "No finish line waypoint in the map! Using first setup waypoint with ID %d.\n",
4677+ K_GetWaypointID(firstwaypoint));
4678+ finishline = firstwaypoint;
4679+ }
4680+
4681+ if (K_SetupCircuitLength() == 0
4682+ && ((mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE) != LF_SECTIONRACE))
4683+ {
4684+ CONS_Alert(CONS_ERROR, "Circuit track waypoints do not form a circuit.\n");
4685+ }
4686+
4687+ setupsuccessful = true;
4688+ }
4689+ }
4690+ }
4691+
4692+ return setupsuccessful;
4693+}
4694+
4695+/*--------------------------------------------------
4696+ void K_ClearWaypoints(void)
4697+
4698+ See header file for description.
4699+--------------------------------------------------*/
4700+void K_ClearWaypoints(void)
4701+{
4702+ waypointheap = NULL;
4703+ firstwaypoint = NULL;
4704+ finishline = NULL;
4705+ numwaypoints = 0U;
4706+ numwaypointmobjs = 0U;
4707+ circuitlength = 0U;
4708+}
4709diff --git a/src/k_waypoint.h b/src/k_waypoint.h
4710new file mode 100644
4711index 000000000..058ff6882
4712--- /dev/null
4713+++ b/src/k_waypoint.h
4714@@ -0,0 +1,325 @@
4715+#ifndef __K_WAYPOINT__
4716+#define __K_WAYPOINT__
4717+
4718+#include "doomtype.h"
4719+#include "p_mobj.h"
4720+#include "k_pathfind.h"
4721+
4722+typedef struct waypoint_s
4723+{
4724+ mobj_t *mobj;
4725+ struct waypoint_s **nextwaypoints;
4726+ struct waypoint_s **prevwaypoints;
4727+ UINT32 *nextwaypointdistances;
4728+ UINT32 *prevwaypointdistances;
4729+ size_t numnextwaypoints;
4730+ size_t numprevwaypoints;
4731+} waypoint_t;
4732+
4733+
4734+// AVAILABLE FOR LUA
4735+
4736+
4737+/*--------------------------------------------------
4738+ waypoint_t *K_GetFinishLineWaypoint(void);
4739+
4740+ Returns the waypoint actually being used as the finish line.
4741+
4742+ Input Arguments:-
4743+ None
4744+
4745+ Return:-
4746+ The waypoint that is being used as the finishline.
4747+--------------------------------------------------*/
4748+
4749+waypoint_t *K_GetFinishLineWaypoint(void);
4750+
4751+
4752+/*--------------------------------------------------
4753+ boolean K_GetWaypointIsFinishline(waypoint_t *waypoint)
4754+
4755+ Returns whether the waypoint is marked as the finishline. This may not actually be the finishline.
4756+
4757+ Input Arguments:-
4758+ waypoint - The waypoint to return finishline status of.
4759+
4760+ Return:-
4761+ true if the waypoint is marked as being the finishline, false if it isn't.
4762+--------------------------------------------------*/
4763+
4764+boolean K_GetWaypointIsFinishline(waypoint_t *waypoint);
4765+
4766+
4767+/*--------------------------------------------------
4768+ boolean K_GetWaypointIsShortcut(waypoint_t *waypoint)
4769+
4770+ Returns whether the waypoint is part of a shortcut.
4771+
4772+ Input Arguments:-
4773+ waypoint - The waypoint to return shortcut status of.
4774+
4775+ Return:-
4776+ true if the waypoint is a shortcut, false if it isn't.
4777+--------------------------------------------------*/
4778+
4779+boolean K_GetWaypointIsShortcut(waypoint_t *waypoint);
4780+
4781+
4782+/*--------------------------------------------------
4783+ boolean K_GetWaypointIsEnabled(waypoint_t *waypoint)
4784+
4785+ Returns whether the waypoint is enabled.
4786+
4787+ Input Arguments:-
4788+ waypoint - The waypoint to return enabled status of.
4789+
4790+ Return:-
4791+ true if the waypoint is enabled, false if it isn't.
4792+--------------------------------------------------*/
4793+
4794+boolean K_GetWaypointIsEnabled(waypoint_t *waypoint);
4795+
4796+
4797+/*--------------------------------------------------
4798+ boolean K_GetWaypointIsSpawnpoint(waypoint_t *waypoint)
4799+
4800+ Returns whether the waypoint is a spawnpoint.
4801+
4802+ Input Arguments:-
4803+ waypoint - The waypoint to return spawnpoint status of.
4804+
4805+ Return:-
4806+ true if the waypoint is a spawnpoint, false if it isn't.
4807+--------------------------------------------------*/
4808+
4809+boolean K_GetWaypointIsSpawnpoint(waypoint_t *waypoint);
4810+
4811+
4812+/*--------------------------------------------------
4813+ INT32 K_GetWaypointNextID(waypoint_t *waypoint)
4814+
4815+ Returns the waypoint's next waypoint ID.
4816+
4817+ Input Arguments:-
4818+ waypoint - The waypoint to return the next waypoint ID of.
4819+
4820+ Return:-
4821+ The next waypoint ID, -1 if there is no waypoint or mobj.
4822+--------------------------------------------------*/
4823+
4824+INT32 K_GetWaypointNextID(waypoint_t *waypoint);
4825+
4826+
4827+/*--------------------------------------------------
4828+ INT32 K_GetWaypointID(waypoint_t *waypoint)
4829+
4830+ Returns the waypoint's ID.
4831+
4832+ Input Arguments:-
4833+ waypoint - The waypoint to return the ID of.
4834+
4835+ Return:-
4836+ The waypoint ID, -1 if there is no waypoint or mobj.
4837+--------------------------------------------------*/
4838+INT32 K_GetWaypointID(waypoint_t *waypoint);
4839+
4840+
4841+/*--------------------------------------------------
4842+ UINT32 K_GetCircuitLength(void)
4843+
4844+ Returns the circuit length, 0 on sprint maps.
4845+
4846+ Input Arguments:-
4847+
4848+ Return:-
4849+ The circuit length.
4850+--------------------------------------------------*/
4851+UINT32 K_GetCircuitLength(void);
4852+
4853+
4854+/*--------------------------------------------------
4855+ waypoint_t *K_GetClosestWaypointToMobj(mobj_t *const mobj)
4856+
4857+ Returns the closest waypoint to an mobj
4858+
4859+ Input Arguments:-
4860+ mobj - mobj to get the closest waypoint of.
4861+
4862+ Return:-
4863+ The closest waypoint to the mobj
4864+--------------------------------------------------*/
4865+waypoint_t *K_GetClosestWaypointToMobj(mobj_t *const mobj);
4866+
4867+
4868+/*--------------------------------------------------
4869+ waypoint_t *K_GetBestWaypointForMobj(mobj_t *const mobj)
4870+
4871+ Similar to K_GetClosestWaypointToMobj, but prioritizes horizontal distance over vertical distance, and
4872+ sight checks to ensure that the waypoint and mobj are the in same area. Can potentially return NULL if
4873+ there are no visible waypoints.
4874+
4875+ Input Arguments:-
4876+ mobj - mobj to get the waypoint for.
4877+
4878+ Return:-
4879+ The best waypoint for the mobj, or NULL if there were no matches
4880+--------------------------------------------------*/
4881+waypoint_t *K_GetBestWaypointForMobj(mobj_t *const mobj);
4882+
4883+
4884+/*--------------------------------------------------
4885+ boolean K_PathfindToWaypoint(
4886+ waypoint_t *const sourcewaypoint,
4887+ waypoint_t *const destinationwaypoint,
4888+ path_t *const returnpath,
4889+ const boolean useshortcuts,
4890+ const boolean huntbackwards)
4891+
4892+ Use pathfinding to try and find the best route to the destination. Data is allocated into the returnpath,
4893+ and should be freed when done with. A call to this with a path already in the returnpath will free the data
4894+ already in there if one is found.
4895+
4896+ Input Arguments:-
4897+ sourcewaypoint - The waypoint to start searching from
4898+ destinationwaypoint - The waypoint to try and get to.
4899+ returnpath - The path_t that will contain the final found path
4900+ useshortcuts - Whether to use waypoints that are marked as being shortcuts in the search
4901+ huntbackwards - Goes through the waypoints backwards if true
4902+
4903+ Return:-
4904+ True if a path was found to the waypoint, false if there wasn't.
4905+--------------------------------------------------*/
4906+
4907+boolean K_PathfindToWaypoint(
4908+ waypoint_t *const sourcewaypoint,
4909+ waypoint_t *const destinationwaypoint,
4910+ path_t *const returnpath,
4911+ const boolean useshortcuts,
4912+ const boolean huntbackwards);
4913+
4914+
4915+/*--------------------------------------------------
4916+ waypoint_t *K_GetNextWaypointToDestination(
4917+ waypoint_t *const sourcewaypoint,
4918+ waypoint_t *const destinationwaypoint,
4919+ const boolean useshortcuts,
4920+ const boolean huntbackwards)
4921+
4922+ Uses pathfinding to find the next waypoint to go to in order to get to the destination waypoint, from the source
4923+ waypoint. If the source waypoint only has one next waypoint it will always pick that one and not do any
4924+ pathfinding.
4925+
4926+ Input Arguments:-
4927+ sourcewaypoint - The waypoint to start searching from
4928+ destinationwaypoint - The waypoint to try and get to.
4929+ useshortcuts - Whether to use waypoints that are marked as being shortcuts in the search
4930+ huntbackwards - Goes through the waypoints backwards if true
4931+
4932+ Return:-
4933+ The next waypoint on the way to the destination waypoint. Returns the source waypoint if the source and
4934+ destination are the same.
4935+--------------------------------------------------*/
4936+
4937+waypoint_t *K_GetNextWaypointToDestination(
4938+ waypoint_t *const sourcewaypoint,
4939+ waypoint_t *const destinationwaypoint,
4940+ const boolean useshortcuts,
4941+ const boolean huntbackwards);
4942+
4943+
4944+/*--------------------------------------------------
4945+ waypoint_t *K_SearchWaypointGraphForMobj(mobj_t *const mobj)
4946+
4947+ Searches through the waypoint graph for a waypoint that has an mobj, if a waypoint can be found through here it
4948+ does mean that the waypoint graph can be traversed to find it
4949+
4950+ Input Arguments:-
4951+ mobj - The mobj that we are searching for, cannot be changed to a different pointer
4952+
4953+ Return:-
4954+ The waypoint that uses that mobj, NULL if it wasn't found, NULL if it isn't an MT_WAYPOINT
4955+--------------------------------------------------*/
4956+
4957+waypoint_t *K_SearchWaypointGraphForMobj(mobj_t * const mobj);
4958+
4959+/*--------------------------------------------------
4960+ waypoint_t *K_SearchWaypointHeapForMobj(mobj_t *const mobj)
4961+
4962+ Searches through the waypoint heap for a waypoint that has an mobj, this does not necessarily mean the waypoint
4963+ can be reached from another waypoint
4964+
4965+ Input Arguments:-
4966+ mobj - The mobj that we are searching for, cannot be changed to a different pointer
4967+
4968+ Return:-
4969+ The waypoint that uses that mobj, NULL if it wasn't found, NULL if it isn't an MT_WAYPOINT
4970+--------------------------------------------------*/
4971+
4972+waypoint_t *K_SearchWaypointHeapForMobj(mobj_t * const mobj);
4973+
4974+
4975+// NOT AVAILABLE FOR LUA
4976+
4977+
4978+/*--------------------------------------------------
4979+ size_t K_GetWaypointHeapIndex(waypoint_t *waypoint)
4980+
4981+ Returns the waypoint's index in the waypoint heap.
4982+
4983+ Input Arguments:-
4984+ waypoint - The waypoint to return the index of.
4985+
4986+ Return:-
4987+ The waypoint heap index, SIZE_MAX if there's an issue with the waypoint.
4988+--------------------------------------------------*/
4989+size_t K_GetWaypointHeapIndex(waypoint_t *waypoint);
4990+
4991+
4992+/*--------------------------------------------------
4993+ waypoint_t *K_GetWaypointFromIndex(size_t waypointindex)
4994+
4995+ Returns the waypoint from an index to the heap.
4996+
4997+ Input Arguments:-
4998+ waypointindex - The index of the waypoint to get
4999+
5000+ Return:-
5001+ The waypoint from the heap index, NULL if the index if too high
5002+--------------------------------------------------*/
5003+waypoint_t *K_GetWaypointFromIndex(size_t waypointindex);
5004+
5005+
5006+/*--------------------------------------------------
5007+ void K_DebugWaypointsVisualise()
5008+
5009+ Creates mobjs in order to visualise waypoints for debugging.
5010+--------------------------------------------------*/
5011+
5012+void K_DebugWaypointsVisualise(void);
5013+
5014+
5015+/*--------------------------------------------------
5016+ boolean K_SetupWaypointList(void)
5017+
5018+ Sets up the waypoint list for Kart race maps, prints out warnings if something is wrong.
5019+
5020+ Return:-
5021+ true if waypoint setup was seemingly successful, false if no waypoints were setup
5022+ A true return value does not necessarily mean that the waypoints on the map are completely correct
5023+--------------------------------------------------*/
5024+
5025+boolean K_SetupWaypointList(void);
5026+
5027+
5028+/*--------------------------------------------------
5029+ void K_ClearWaypoints(void)
5030+
5031+ Clears waypointheap, firstwaypoint, numwaypoints, and numwaypointmobjs
5032+ WARNING: This does *not* Free waypointheap or any waypoints! They are stored in PU_LEVEL so they are freed once
5033+ the level is completed! This is called just before K_SetupWaypointList in P_SetupLevel as they are freed then.
5034+ A separate method is called in K_SetupWaypointList that will free everything specifically if they aren't already
5035+--------------------------------------------------*/
5036+
5037+void K_ClearWaypoints(void);
5038+
5039+#endif
5040\ No newline at end of file
5041diff --git a/src/m_cheat.c b/src/m_cheat.c
5042index c24a8014b..d61d22cd0 100644
5043--- a/src/m_cheat.c
5044+++ b/src/m_cheat.c
5045@@ -292,9 +292,18 @@ void Command_CheatNoClip_f(void)
5046 REQUIRE_NOULTIMATE;
5047
5048 plyr = &players[consoleplayer];
5049+
5050+ if (!plyr->mo || P_MobjWasRemoved(plyr->mo))
5051+ return;
5052+
5053 plyr->pflags ^= PF_NOCLIP;
5054 CONS_Printf(M_GetText("No Clipping %s\n"), plyr->pflags & PF_NOCLIP ? M_GetText("On") : M_GetText("Off"));
5055
5056+ if (plyr->pflags & PF_NOCLIP)
5057+ plyr->mo->flags |= MF_NOCLIP;
5058+ else
5059+ plyr->mo->flags &= ~MF_NOCLIP;
5060+
5061 G_SetGameModified(multiplayer, true);
5062 }
5063
5064diff --git a/src/p_enemy.c b/src/p_enemy.c
5065index 64a3ee12e..521d44b1b 100644
5066--- a/src/p_enemy.c
5067+++ b/src/p_enemy.c
5068@@ -24,6 +24,7 @@
5069 #include "i_video.h"
5070 #include "lua_hook.h"
5071 #include "k_kart.h" // SRB2kart
5072+#include "k_waypoint.h"
5073
5074 #ifdef HW3SOUND
5075 #include "hardware/hw3sound.h"
5076@@ -8444,14 +8445,32 @@ void A_JawzExplode(mobj_t *actor)
5077 return;
5078 }
5079
5080+static void SpawnSPBTrailRings(mobj_t *actor)
5081+{
5082+ if (actor != NULL)
5083+ {
5084+ if (leveltime % 6 == 0)
5085+ {
5086+ mobj_t *ring = P_SpawnMobj(actor->x - actor->momx, actor->y - actor->momy,
5087+ actor->z - actor->momz + (24*mapobjectscale), MT_RING);
5088+ ring->threshold = 10;
5089+ ring->fuse = 120*TICRATE;
5090+ }
5091+ }
5092+}
5093+
5094 void A_SPBChase(mobj_t *actor)
5095 {
5096 player_t *player = NULL;
5097+ player_t *scplayer = NULL; // secondary target for seeking
5098 UINT8 i;
5099 UINT8 bestrank = UINT8_MAX;
5100 fixed_t dist;
5101 angle_t hang, vang;
5102 fixed_t wspeed, xyspeed, zspeed;
5103+ fixed_t pdist = 1536<<FRACBITS; // best player distance when seeking
5104+ angle_t pangle; // angle between us and the player
5105+
5106 #ifdef HAVE_BLUA
5107 if (LUA_CallAction("A_SPBChase", actor))
5108 return;
5109@@ -8463,9 +8482,9 @@ void A_SPBChase(mobj_t *actor)
5110 if (actor->threshold) // Just fired, go straight.
5111 {
5112 actor->lastlook = -1;
5113+ actor->cusval = -1;
5114 spbplace = -1;
5115 P_InstaThrust(actor, actor->angle, wspeed);
5116- actor->flags &= ~MF_NOCLIPTHING; // just in case.
5117 return;
5118 }
5119
5120@@ -8491,18 +8510,22 @@ void A_SPBChase(mobj_t *actor)
5121 }
5122 }
5123
5124+ // lastlook = last player num targetted
5125+ // cvmem = stored speed
5126+ // cusval = next waypoint heap index
5127+ // extravalue1 = SPB movement mode
5128+ // extravalue2 = mode misc option
5129+
5130 if (actor->extravalue1 == 1) // MODE: TARGETING
5131 {
5132+ actor->cusval = -1; // Reset waypoint
5133+
5134 if (actor->tracer && actor->tracer->health)
5135 {
5136-
5137 fixed_t defspeed = wspeed;
5138 fixed_t range = (160*actor->tracer->scale);
5139 fixed_t cx = 0, cy =0;
5140
5141- // we're tailing a player, now's a good time to regain our damage properties
5142- actor->flags &= ~MF_NOCLIPTHING;
5143-
5144 // Play the intimidating gurgle
5145 if (!S_SoundPlaying(actor, actor->info->activesound))
5146 S_StartSound(actor, actor->info->activesound);
5147@@ -8600,13 +8623,7 @@ void A_SPBChase(mobj_t *actor)
5148 actor->momz = FixedMul(zspeed, FINESINE(actor->movedir>>ANGLETOFINESHIFT));
5149
5150 // Spawn a trail of rings behind the SPB!
5151- if (leveltime % 6 == 0)
5152- {
5153- mobj_t *ring = P_SpawnMobj(actor->x - actor->momx, actor->y - actor->momx,
5154- actor->z - actor->momz + (24*mapobjectscale), MT_RING);
5155- ring->threshold = 10;
5156- ring->fuse = 120*TICRATE;
5157- }
5158+ SpawnSPBTrailRings(actor);
5159
5160 // Red speed lines for when it's gaining on its target. A tell for when you're starting to lose too much speed!
5161 if (R_PointToDist2(0, 0, actor->momx, actor->momy) > (actor->tracer->player ? (16*actor->tracer->player->speed)/15
5162@@ -8639,9 +8656,7 @@ void A_SPBChase(mobj_t *actor)
5163 else if (actor->extravalue1 == 2) // MODE: WAIT...
5164 {
5165 actor->momx = actor->momy = actor->momz = 0; // Stoooop
5166-
5167- // don't hurt players that have nothing to do with this:
5168- actor->flags |= MF_NOCLIPTHING;
5169+ actor->cusval = -1; // Reset waypoint
5170
5171 if (actor->lastlook != -1
5172 && playeringame[actor->lastlook]
5173@@ -8667,6 +8682,11 @@ void A_SPBChase(mobj_t *actor)
5174 }
5175 else // MODE: SEEKING
5176 {
5177+ waypoint_t *lastwaypoint = NULL;
5178+ waypoint_t *bestwaypoint = NULL;
5179+ waypoint_t *nextwaypoint = NULL;
5180+ waypoint_t *tempwaypoint = NULL;
5181+
5182 actor->lastlook = -1; // Just make sure this is reset
5183
5184 if (!player || !player->mo || player->mo->health <= 0 || player->kartstuff[k_respawn])
5185@@ -8679,17 +8699,72 @@ void A_SPBChase(mobj_t *actor)
5186 }
5187
5188 // Found someone, now get close enough to initiate the slaughter...
5189-
5190- // don't hurt players that have nothing to do with this:
5191- actor->flags |= MF_NOCLIPTHING;
5192-
5193 P_SetTarget(&actor->tracer, player->mo);
5194 spbplace = bestrank;
5195-
5196 dist = P_AproxDistance(P_AproxDistance(actor->x-actor->tracer->x, actor->y-actor->tracer->y), actor->z-actor->tracer->z);
5197
5198- hang = R_PointToAngle2(actor->x, actor->y, actor->tracer->x, actor->tracer->y);
5199- vang = R_PointToAngle2(0, actor->z, dist, actor->tracer->z);
5200+ // Move along the waypoints until you get close enough
5201+ if (actor->cusval > -1 && actor->extravalue2 > 0)
5202+ {
5203+ // Previously set nextwaypoint
5204+ lastwaypoint = K_GetWaypointFromIndex((size_t)actor->cusval);
5205+ tempwaypoint = K_GetBestWaypointForMobj(actor);
5206+ // check if the tempwaypoint corresponds to lastwaypoint's next ID at least;
5207+ // This is to avoid situations where the SPB decides to suicide jump down a bridge because it found a COMPLETELY unrelated waypoint down there.
5208+
5209+ if (K_GetWaypointID(tempwaypoint) == K_GetWaypointNextID(lastwaypoint) || K_GetWaypointID(tempwaypoint) == K_GetWaypointID(lastwaypoint))
5210+ // either our previous or curr waypoint ID, sure, take it
5211+ bestwaypoint = tempwaypoint;
5212+ else
5213+ bestwaypoint = K_GetWaypointFromIndex((size_t)actor->extravalue2); // keep going from the PREVIOUS wp.
5214+ }
5215+ else
5216+ bestwaypoint = K_GetBestWaypointForMobj(actor);
5217+
5218+ if (bestwaypoint == NULL && lastwaypoint == NULL)
5219+ {
5220+ // We have invalid waypoints all around, so use closest to try and make it non-NULL.
5221+ bestwaypoint = K_GetClosestWaypointToMobj(actor);
5222+ }
5223+
5224+ if (bestwaypoint != NULL)
5225+ {
5226+ const boolean huntbackwards = false;
5227+ boolean useshortcuts = false;
5228+
5229+ // If the player is on a shortcut, use shortcuts. No escape.
5230+ if (K_GetWaypointIsShortcut(player->nextwaypoint))
5231+ {
5232+ useshortcuts = true;
5233+ }
5234+
5235+ nextwaypoint = K_GetNextWaypointToDestination(
5236+ bestwaypoint, player->nextwaypoint, useshortcuts, huntbackwards);
5237+ }
5238+
5239+ if (nextwaypoint == NULL && lastwaypoint != NULL)
5240+ {
5241+ // Restore to the last nextwaypoint
5242+ nextwaypoint = lastwaypoint;
5243+ }
5244+
5245+ if (nextwaypoint != NULL)
5246+ {
5247+ const fixed_t xywaypointdist = P_AproxDistance(
5248+ actor->x - nextwaypoint->mobj->x, actor->y - nextwaypoint->mobj->y);
5249+
5250+ hang = R_PointToAngle2(actor->x, actor->y, nextwaypoint->mobj->x, nextwaypoint->mobj->y);
5251+ vang = R_PointToAngle2(0, actor->z, xywaypointdist, nextwaypoint->mobj->z);
5252+
5253+ actor->cusval = (INT32)K_GetWaypointHeapIndex(nextwaypoint);
5254+ actor->extravalue2 = (INT32)K_GetWaypointHeapIndex(bestwaypoint); // save our last best, used above.
5255+ }
5256+ else
5257+ {
5258+ // continue straight ahead... Shouldn't happen.
5259+ hang = actor->angle;
5260+ vang = 0U;
5261+ }
5262
5263 {
5264 // Smoothly rotate horz angle
5265@@ -8698,13 +8773,13 @@ void A_SPBChase(mobj_t *actor)
5266 if (invert)
5267 input = InvAngle(input);
5268
5269+ input = FixedAngle(AngleFixed(input)/8);
5270+
5271 // Slow down when turning; it looks better and makes U-turns not unfair
5272 xyspeed = FixedMul(wspeed, max(0, (((180<<FRACBITS) - AngleFixed(input)) / 90) - FRACUNIT));
5273
5274- input = FixedAngle(AngleFixed(input)/4);
5275 if (invert)
5276 input = InvAngle(input);
5277-
5278 actor->angle += input;
5279
5280 // Smoothly rotate vert angle
5281@@ -8713,13 +8788,13 @@ void A_SPBChase(mobj_t *actor)
5282 if (invert)
5283 input = InvAngle(input);
5284
5285+ input = FixedAngle(AngleFixed(input)/8);
5286+
5287 // Slow down when turning; might as well do it for momz, since we do it above too
5288 zspeed = FixedMul(wspeed, max(0, (((180<<FRACBITS) - AngleFixed(input)) / 90) - FRACUNIT));
5289
5290- input = FixedAngle(AngleFixed(input)/4);
5291 if (invert)
5292 input = InvAngle(input);
5293-
5294 actor->movedir += input;
5295 }
5296
5297@@ -8727,15 +8802,49 @@ void A_SPBChase(mobj_t *actor)
5298 actor->momy = FixedMul(FixedMul(xyspeed, FINESINE(actor->angle>>ANGLETOFINESHIFT)), FINECOSINE(actor->movedir>>ANGLETOFINESHIFT));
5299 actor->momz = FixedMul(zspeed, FINESINE(actor->movedir>>ANGLETOFINESHIFT));
5300
5301- if (dist <= (3072*actor->tracer->scale)) // Close enough to target?
5302+ // see if a player is near us, if they are, try to hit them by slightly thrusting towards them, otherwise, bleh!
5303+ for (i=0; i < MAXPLAYERS; i++)
5304 {
5305- S_StartSound(actor, actor->info->attacksound); // Siren sound; might not need this anymore, but I'm keeping it for now just for debugging.
5306+ if (!playeringame[i] || players[i].spectator || players[i].exiting)
5307+ continue; // not in-game
5308+
5309+ if (R_PointToDist2(actor->x, actor->y, players[i].mo->x, players[i].mo->y) < pdist)
5310+ {
5311+ pdist = R_PointToDist2(actor->x, actor->y, players[i].mo->x, players[i].mo->y);
5312+ scplayer = &players[i]; // it doesn't matter if we override this guy now.
5313+ }
5314+ }
5315+
5316+ // different player from our main target, try and ram into em~!
5317+ if (scplayer && scplayer != player)
5318+ {
5319+ pangle = actor->angle - R_PointToAngle2(actor->x, actor->y,scplayer->mo->x, scplayer->mo->y);
5320+ // check if the angle wouldn't make us LOSE speed...
5321+ if ((INT32)pangle/ANG1 >= -80 && (INT32)pangle/ANG1 <= 80) // allow for around 80 degrees
5322+ {
5323+ // Thrust us towards the guy, try to screw em up!
5324+ P_Thrust(actor, R_PointToAngle2(actor->x, actor->y, scplayer->mo->x, scplayer->mo->y), actor->movefactor/4); // not too fast though.
5325+ }
5326+ }
5327+
5328+ // Spawn a trail of rings behind the SPB!
5329+ SpawnSPBTrailRings(actor);
5330+
5331+ if (dist <= (1024*actor->tracer->scale)) // Close enough to target?
5332+ {
5333+ S_StartSound(actor, actor->info->attacksound);
5334 actor->extravalue1 = 1; // TARGET ACQUIRED
5335 actor->extravalue2 = 7*TICRATE;
5336 actor->cvmem = wspeed;
5337 }
5338 }
5339
5340+ // Finally, no matter what, the spb should not be able to be under the ground, or above the ceiling;
5341+ if (actor->z < actor->floorz)
5342+ actor->z = actor->floorz;
5343+ else if (actor->z > actor->ceilingz - actor->height)
5344+ actor->z = actor->ceilingz - actor->height;
5345+
5346 return;
5347 }
5348
5349@@ -10577,8 +10686,8 @@ void A_RemoteDamage(mobj_t *actor)
5350
5351 if (locvar2 == 1) // Kill mobj!
5352 {
5353- if (target->player) // players die using P_DamageMobj instead for some reason
5354- P_DamageMobj(target, source, source, 10000);
5355+ if (target->player)
5356+ K_DoIngameRespawn(target->player);
5357 else
5358 P_KillMobj(target, source, source);
5359 }
5360diff --git a/src/p_inter.c b/src/p_inter.c
5361index 656a09b81..905cf1c1f 100644
5362--- a/src/p_inter.c
5363+++ b/src/p_inter.c
5364@@ -1462,15 +1462,9 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
5365 case MT_STARPOST:
5366 if (player->bot)
5367 return;
5368- // SRB2kart - 150117
5369- if (player->exiting) //STOP MESSING UP MY STATS FASDFASDF
5370- {
5371- player->kartstuff[k_starpostwp] = player->kartstuff[k_waypoint];
5372- return;
5373- }
5374 //
5375 // SRB2kart: make sure the player will have enough checkpoints to touch
5376- if (circuitmap && special->health >= ((numstarposts/2) + player->starpostnum))
5377+ if (circuitmap && special->health - player->starpostnum > 1)
5378 {
5379 // blatant reuse of a variable that's normally unused in circuit
5380 if (!player->tossdelay)
5381@@ -1491,13 +1485,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
5382
5383 // Save the player's time and position.
5384 player->starposttime = player->realtime; //this makes race mode's timers work correctly whilst not affecting sp -x
5385- //player->starposttime = leveltime;
5386- player->starpostx = toucher->x>>FRACBITS;
5387- player->starposty = toucher->y>>FRACBITS;
5388- player->starpostz = special->z>>FRACBITS;
5389- player->starpostangle = special->angle;
5390 player->starpostnum = special->health;
5391- player->kartstuff[k_starpostflip] = special->spawnpoint->options & MTF_OBJECTFLIP; // store flipping
5392
5393 //S_StartSound(toucher, special->info->painsound);
5394 return;
5395diff --git a/src/p_local.h b/src/p_local.h
5396index 85fd08d71..a4e661bfc 100644
5397--- a/src/p_local.h
5398+++ b/src/p_local.h
5399@@ -182,7 +182,6 @@ boolean P_LookForEnemies(player_t *player);
5400 void P_NukeEnemies(mobj_t *inflictor, mobj_t *source, fixed_t radius);
5401 void P_HomingAttack(mobj_t *source, mobj_t *enemy); /// \todo doesn't belong in p_user
5402 //boolean P_SuperReady(player_t *player);
5403-boolean P_AnalogMove(player_t *player);
5404 /*boolean P_TransferToNextMare(player_t *player);
5405 UINT8 P_FindLowestMare(void);*/
5406 UINT8 P_FindLowestLap(void);
5407diff --git a/src/p_map.c b/src/p_map.c
5408index 3bdc42e87..adedd581f 100644
5409--- a/src/p_map.c
5410+++ b/src/p_map.c
5411@@ -68,6 +68,20 @@ line_t *ceilingline;
5412 // that is, for any line which is 'solid'
5413 line_t *blockingline;
5414
5415+// Mostly re-ported from DOOM Legacy
5416+// Keep track of special lines as they are hit, process them when the move is valid
5417+static size_t *spechit = NULL;
5418+static size_t spechit_max = 0U;
5419+static size_t numspechit = 0U;
5420+
5421+// Need a intermediate buffer for P_TryMove because it performs multiple moves
5422+// the lines put into spechit will be moved into here after each checkposition,
5423+// then and duplicates will be removed before processing
5424+static size_t *spechitint = NULL;
5425+static size_t spechitint_max = 0U;
5426+static size_t numspechitint = 0U;
5427+
5428+
5429 msecnode_t *sector_list = NULL;
5430 mprecipsecnode_t *precipsector_list = NULL;
5431 camera_t *mapcampointer;
5432@@ -81,6 +95,8 @@ camera_t *mapcampointer;
5433 //
5434 boolean P_TeleportMove(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z)
5435 {
5436+ numspechit = 0U;
5437+
5438 // the move is ok,
5439 // so link the thing into its new position
5440 P_UnsetThingPosition(thing);
5441@@ -113,6 +129,100 @@ boolean P_TeleportMove(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z)
5442 // MOVEMENT ITERATOR FUNCTIONS
5443 // =========================================================================
5444
5445+// For our intermediate buffer, remove any duplicate entries by adding each one to
5446+// a temprary buffer if it's not already in there, copy the temporary buffer back over the intermediate afterwards
5447+static void spechitint_removedups(void)
5448+{
5449+ // Only needs to be run if there's more than 1 line crossed
5450+ if (numspechitint > 1U)
5451+ {
5452+ boolean valueintemp = false;
5453+ size_t i = 0U, j = 0U;
5454+ size_t numspechittemp = 0U;
5455+ size_t *spechittemp = Z_Calloc(numspechitint * sizeof(size_t), PU_STATIC, NULL);
5456+
5457+ // Fill the hashtable
5458+ for (i = 0U; i < numspechitint; i++)
5459+ {
5460+ valueintemp = false;
5461+ for (j = 0; j < numspechittemp; j++)
5462+ {
5463+ if (spechitint[i] == spechittemp[j])
5464+ {
5465+ valueintemp = true;
5466+ break;
5467+ }
5468+ }
5469+
5470+ if (!valueintemp)
5471+ {
5472+ spechittemp[numspechittemp] = spechitint[i];
5473+ numspechittemp++;
5474+ }
5475+ }
5476+
5477+ // The hash table now IS the result we want to send back
5478+ // easiest way to handle this is a memcpy
5479+ if (numspechittemp != numspechitint)
5480+ {
5481+ memcpy(spechitint, spechittemp, numspechittemp * sizeof(size_t));
5482+ numspechitint = numspechittemp;
5483+ }
5484+
5485+ Z_Free(spechittemp);
5486+ }
5487+}
5488+
5489+// copy the contents of spechit into the end of spechitint
5490+static void spechitint_copyinto(void)
5491+{
5492+ if (numspechit > 0U)
5493+ {
5494+ if (numspechitint + numspechit >= spechitint_max)
5495+ {
5496+ spechitint_max = spechitint_max + numspechit;
5497+ spechitint = Z_Realloc(spechitint, spechitint_max * sizeof(size_t), PU_STATIC, NULL);
5498+ }
5499+
5500+ memcpy(&spechitint[numspechitint], spechit, numspechit * sizeof(size_t));
5501+ numspechitint += numspechit;
5502+ }
5503+}
5504+
5505+static void add_spechit(line_t *ld)
5506+{
5507+ if (numspechit >= spechit_max)
5508+ {
5509+ spechit_max = spechit_max ? spechit_max * 2U : 16U;
5510+ spechit = Z_Realloc(spechit, spechit_max * sizeof(size_t), PU_STATIC, NULL);
5511+ }
5512+
5513+ spechit[numspechit] = ld - lines;
5514+ numspechit++;
5515+}
5516+
5517+static boolean P_SpecialIsLinedefCrossType(UINT16 ldspecial)
5518+{
5519+ boolean linedefcrossspecial = false;
5520+
5521+ switch (ldspecial)
5522+ {
5523+ case 2001: // Finish line
5524+ {
5525+ linedefcrossspecial = true;
5526+ }
5527+ break;
5528+
5529+ default:
5530+ {
5531+ linedefcrossspecial = false;
5532+ }
5533+ break;
5534+ }
5535+
5536+ return linedefcrossspecial;
5537+}
5538+
5539 //#define TELEPORTJANK
5540
5541 boolean P_DoSpring(mobj_t *spring, mobj_t *object)
5542@@ -146,7 +256,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
5543 object->eflags |= MFE_SPRUNG; // apply this flag asap!
5544 spring->flags &= ~(MF_SOLID|MF_SPECIAL); // De-solidify
5545
5546-#ifdef TELEPORTJANK
5547+#if 0
5548 if (horizspeed && vertispeed) // Mimic SA
5549 {
5550 object->momx = object->momy = 0;
5551@@ -897,12 +1007,20 @@ static boolean PIT_CheckThing(mobj_t *thing)
5552
5553 if (thing->type == MT_PLAYER)
5554 {
5555- S_StartSound(NULL, sfx_bsnipe); //let all players hear it.
5556+ mobj_t *explosion;
5557+
5558+ S_StartSound(NULL, sfx_bsnipe); // let all players hear it.
5559+
5560 HU_SetCEchoFlags(0);
5561 HU_SetCEchoDuration(5);
5562 HU_DoCEcho(va("%s\\was hit by a kitchen sink.\\\\\\\\", player_names[thing->player-players]));
5563 I_OutputMsg("%s was hit by a kitchen sink.\n", player_names[thing->player-players]);
5564- P_DamageMobj(thing, tmthing, tmthing->target, 10000);
5565+
5566+ explosion = P_SpawnMobj(thing->x, thing->y, thing->z, MT_SPBEXPLOSION);
5567+ explosion->extravalue1 = 1; // Tell K_ExplodePlayer to use extra knockback
5568+ if (tmthing->target && !P_MobjWasRemoved(tmthing->target))
5569+ P_SetTarget(&explosion->target, tmthing->target);
5570+
5571 P_KillMobj(tmthing, thing, thing);
5572 }
5573
5574@@ -1161,15 +1279,23 @@ static boolean PIT_CheckThing(mobj_t *thing)
5575 }
5576 else if (thing->type == MT_SINK)
5577 {
5578+ mobj_t *explosion;
5579+
5580 if ((thing->target == tmthing) && (thing->threshold > 0))
5581 return true;
5582
5583- S_StartSound(NULL, sfx_cgot); //let all players hear it.
5584+ S_StartSound(NULL, sfx_bsnipe); // let all players hear it.
5585+
5586 HU_SetCEchoFlags(0);
5587 HU_SetCEchoDuration(5);
5588 HU_DoCEcho(va("%s\\was hit by a kitchen sink.\\\\\\\\", player_names[tmthing->player-players]));
5589 I_OutputMsg("%s was hit by a kitchen sink.\n", player_names[tmthing->player-players]);
5590- P_DamageMobj(tmthing, thing, thing->target, 10000);
5591+
5592+ explosion = P_SpawnMobj(tmthing->x, tmthing->y, tmthing->z, MT_SPBEXPLOSION);
5593+ explosion->extravalue1 = 1; // Tell K_ExplodePlayer to use extra knockback
5594+ if (thing->target && !P_MobjWasRemoved(thing->target))
5595+ P_SetTarget(&explosion->target, thing->target);
5596+
5597 P_KillMobj(thing, tmthing, tmthing);
5598 }
5599
5600@@ -1309,7 +1435,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
5601
5602 thing->angle = tmthing->angle;
5603
5604- if (!demo.playback || P_AnalogMove(thing->player))
5605+ if (!demo.playback)
5606 {
5607 if (thing->player == &players[consoleplayer])
5608 localangle[0] = thing->angle;
5609@@ -1554,7 +1680,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
5610 {
5611 // Objects kill you if it falls from above.
5612 if (thing != tmthing->target)
5613- P_DamageMobj(thing, tmthing, tmthing->target, 10000);
5614+ K_DoIngameRespawn(thing->player);
5615
5616 tmthing->momz = -tmthing->momz/2; // Bounce, just for fun!
5617 // The tmthing->target allows the pusher of the object
5618@@ -1972,7 +2098,7 @@ static boolean PIT_CheckLine(line_t *ld)
5619 if (P_BoxOnLineSide(tmbbox, ld) != -1)
5620 return true;
5621
5622-if (tmthing->flags & MF_PAPERCOLLISION) // Caution! Turning whilst up against a wall will get you stuck. You probably shouldn't give the player this flag.
5623+ if (tmthing->flags & MF_PAPERCOLLISION) // Caution! Turning whilst up against a wall will get you stuck. You probably shouldn't give the player this flag.
5624 {
5625 fixed_t cosradius, sinradius;
5626 cosradius = FixedMul(tmthing->radius, FINECOSINE(tmthing->angle>>ANGLETOFINESHIFT));
5627@@ -2039,6 +2165,12 @@ if (tmthing->flags & MF_PAPERCOLLISION) // Caution! Turning whilst up against a
5628 if (lowfloor < tmdropoffz)
5629 tmdropoffz = lowfloor;
5630
5631+ // we've crossed the line
5632+ if (P_SpecialIsLinedefCrossType(ld->special))
5633+ {
5634+ add_spechit(ld);
5635+ }
5636+
5637 return true;
5638 }
5639
5640@@ -2313,6 +2445,9 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
5641
5642 validcount++;
5643
5644+ // reset special lines
5645+ numspechit = 0U;
5646+
5647 if (tmflags & MF_NOCLIP)
5648 return true;
5649
5650@@ -2752,6 +2887,8 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
5651 {
5652 fixed_t tryx = thing->x;
5653 fixed_t tryy = thing->y;
5654+ fixed_t oldx = tryx;
5655+ fixed_t oldy = tryy;
5656 fixed_t radius = thing->radius;
5657 fixed_t thingtop = thing->z + thing->height;
5658 #ifdef ESLOPE
5659@@ -2759,8 +2896,13 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
5660 #endif
5661 floatok = false;
5662
5663- if (radius < MAXRADIUS/2)
5664- radius = MAXRADIUS/2;
5665+ // reset this to 0 at the start of each trymove call as it's only used here
5666+ numspechitint = 0U;
5667+
5668+ // This makes sure that there are no freezes from computing extremely small movements.
5669+ // Originally was MAXRADIUS/2, but that causes some inconsistencies for small players.
5670+ if (radius < mapobjectscale)
5671+ radius = mapobjectscale;
5672
5673 do {
5674 if (thing->flags & MF_NOCLIP) {
5675@@ -2784,6 +2926,9 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
5676 if (!P_CheckPosition(thing, tryx, tryy))
5677 return false; // solid wall or thing
5678
5679+ // copy into the spechitint buffer from spechit
5680+ spechitint_copyinto();
5681+
5682 if (!(thing->flags & MF_NOCLIP))
5683 {
5684 //All things are affected by their scale.
5685@@ -2956,6 +3101,30 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
5686 thing->eflags |= MFE_ONGROUND;
5687
5688 P_SetThingPosition(thing);
5689+
5690+ // remove any duplicates that may be in spechitint
5691+ spechitint_removedups();
5692+
5693+ // handle any of the special lines that were crossed
5694+ if (!(thing->flags & (MF_NOCLIP)))
5695+ {
5696+ line_t *ld = NULL;
5697+ INT32 side = 0, oldside = 0;
5698+ while (numspechitint--)
5699+ {
5700+ ld = &lines[spechitint[numspechitint]];
5701+ side = P_PointOnLineSide(thing->x, thing->y, ld);
5702+ oldside = P_PointOnLineSide(oldx, oldy, ld);
5703+ if (side != oldside)
5704+ {
5705+ if (ld->special)
5706+ {
5707+ P_CrossSpecialLine(ld, oldside, thing);
5708+ }
5709+ }
5710+ }
5711+ }
5712+
5713 return true;
5714 }
5715
5716diff --git a/src/p_mobj.c b/src/p_mobj.c
5717index 50bb4009a..cdfe8fcb1 100644
5718--- a/src/p_mobj.c
5719+++ b/src/p_mobj.c
5720@@ -47,6 +47,7 @@ consvar_t cv_splats = {"splats", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0
5721 actioncache_t actioncachehead;
5722
5723 static mobj_t *overlaycap = NULL;
5724+mobj_t *kitemcap = NULL; // Used for Kart offensive items (the ones that can get removed by sizedown)
5725 mobj_t *waypointcap = NULL;
5726
5727 void P_InitCachedActions(void)
5728@@ -1655,18 +1656,11 @@ void P_XYMovement(mobj_t *mo)
5729 {
5730 mo->health--;
5731 if (mo->health == 0)
5732- mo->destscale = 1;
5733- }
5734- else
5735- {
5736- if (mo->scale < mapobjectscale/16)
5737- {
5738- P_RemoveMobj(mo);
5739- return;
5740- }
5741+ mo->destscale = 0;
5742 }
5743 }
5744 //}
5745+
5746 if (!P_TryMove(mo, mo->x + xmove, mo->y + ymove, true) && !(mo->eflags & MFE_SPRUNG))
5747 {
5748 // blocked move
5749@@ -6105,6 +6099,71 @@ static boolean P_AddShield(mobj_t *thing)
5750 return true;
5751 }*/
5752
5753+
5754+// Kartitem stuff.
5755+boolean P_IsKartItem(INT32 type)
5756+{
5757+ if (type == MT_EGGMANITEM || type == MT_EGGMANITEM_SHIELD ||
5758+ type == MT_BANANA || type == MT_BANANA_SHIELD ||
5759+ type == MT_ORBINAUT || type == MT_ORBINAUT_SHIELD ||
5760+ type == MT_JAWZ || type == MT_JAWZ_DUD || type == MT_JAWZ_SHIELD ||
5761+ type == MT_SSMINE || type == MT_SSMINE_SHIELD ||
5762+ type == MT_SINK || type == MT_SINK_SHIELD ||
5763+ type == MT_SPB)
5764+ return true;
5765+ else
5766+ return false;
5767+}
5768+
5769+// Called when a kart item "thinks"
5770+void P_AddKartItem(mobj_t *thing)
5771+{
5772+ I_Assert(thing != NULL);
5773+
5774+ if (kitemcap == NULL)
5775+ P_SetTarget(&kitemcap, thing);
5776+ else {
5777+ mobj_t *mo;
5778+ for (mo = kitemcap; mo && mo->itnext; mo = mo->itnext)
5779+ ;
5780+
5781+ I_Assert(mo != NULL);
5782+ I_Assert(mo->itnext == NULL);
5783+
5784+ P_SetTarget(&mo->itnext, thing);
5785+ }
5786+ P_SetTarget(&thing->itnext, NULL);
5787+}
5788+
5789+// Called only when a kart item is removed
5790+// Keeps the hnext list from corrupting.
5791+static void P_RemoveKartItem(mobj_t *thing)
5792+{
5793+ mobj_t *mo;
5794+ for (mo = kitemcap; mo; mo = mo->itnext)
5795+ if (mo->itnext == thing)
5796+ {
5797+ P_SetTarget(&mo->itnext, thing->itnext);
5798+ P_SetTarget(&thing->itnext, NULL);
5799+ return;
5800+ }
5801+}
5802+
5803+// Doesn't actually do anything since items have their own thinkers,
5804+// but this is necessary for the sole purpose of updating kitemcap
5805+void P_RunKartItems(void)
5806+{
5807+ mobj_t *mobj, *next;
5808+
5809+ for (mobj = kitemcap; mobj; mobj = next)
5810+ {
5811+ next = mobj->itnext;
5812+ P_SetTarget(&mobj->itnext, NULL);
5813+ }
5814+ P_SetTarget(&kitemcap, NULL);
5815+}
5816+
5817+
5818 void P_RunOverlays(void)
5819 {
5820 // run overlays
5821@@ -6538,16 +6597,25 @@ void P_MobjThinker(mobj_t *mobj)
5822 mobj->z -= mobj->height - oldheight;
5823
5824 if (mobj->scale == mobj->destscale)
5825+ {
5826 /// \todo Lua hook for "reached destscale"?
5827- switch(mobj->type)
5828+
5829+ if (mobj->scale == 0)
5830 {
5831- case MT_EGGMOBILE_FIRE:
5832- mobj->destscale = FRACUNIT;
5833- mobj->scalespeed = FRACUNIT>>4;
5834- break;
5835- default:
5836- break;
5837+ P_RemoveMobj(mobj);
5838+ return;
5839 }
5840+
5841+ switch (mobj->type)
5842+ {
5843+ case MT_EGGMOBILE_FIRE:
5844+ mobj->destscale = FRACUNIT;
5845+ mobj->scalespeed = FRACUNIT>>4;
5846+ break;
5847+ default:
5848+ break;
5849+ }
5850+ }
5851 }
5852
5853 if (mobj->type == MT_GHOST && mobj->fuse > 0 // Not guaranteed to be MF_SCENERY or not MF_SCENERY!
5854@@ -9756,6 +9824,12 @@ for (i = ((mobj->flags2 & MF2_STRONGBOX) ? strongboxamt : weakboxamt); i; --i) s
5855 }
5856 }
5857
5858+ if (P_MobjWasRemoved(mobj))
5859+ return; // obligatory paranoia check
5860+
5861+ if (P_IsKartItem(mobj->type)) // mobj is a kart item we want on the list:
5862+ P_AddKartItem(mobj); // add to kitem list
5863+
5864 // Can end up here if a player dies.
5865 if (mobj->player)
5866 P_CyclePlayerMobjState(mobj);
5867@@ -10436,6 +10510,10 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
5868 }
5869 }
5870 break;
5871+ case MT_BOSS3WAYPOINT:
5872+ // Remove before release
5873+ CONS_Alert(CONS_WARNING, "Boss waypoints are deprecated. Did you forget to remove the old checkpoints, too?\n");
5874+ break;
5875 default:
5876 break;
5877 }
5878@@ -10591,6 +10669,9 @@ void P_RemoveMobj(mobj_t *mobj)
5879 if (mobj->type == MT_SPB)
5880 spbplace = -1;
5881
5882+ if (P_IsKartItem(mobj->type))
5883+ P_RemoveKartItem(mobj);
5884+
5885 mobj->health = 0; // Just because
5886
5887 // unlink from sector and block lists
5888@@ -11176,6 +11257,9 @@ void P_SpawnPlayer(INT32 playernum)
5889 // Spawn with a pity shield if necessary.
5890 //P_DoPityCheck(p);
5891
5892+ if (p->kartstuff[k_respawn] != 0)
5893+ p->mo->flags |= MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY;
5894+
5895 if (G_BattleGametype()) // SRB2kart
5896 {
5897 mobj_t *overheadarrow = P_SpawnMobj(mobj->x, mobj->y, mobj->z + P_GetPlayerHeight(p)+16*FRACUNIT, MT_PLAYERARROW);
5898@@ -11392,11 +11476,10 @@ void P_MovePlayerToStarpost(INT32 playernum)
5899 sector->ceilingheight;
5900
5901 if (mobj->player->kartstuff[k_starpostflip])
5902- z = (p->starpostz<<FRACBITS) - FixedMul(128<<FRACBITS, mapobjectscale) - mobj->height;
5903+ z = (p->starpostz<<FRACBITS) - (128 * mapobjectscale) - mobj->height;
5904 else
5905- z = (p->starpostz<<FRACBITS) + FixedMul(128<<FRACBITS, mapobjectscale);
5906+ z = (p->starpostz<<FRACBITS) + (128 * mapobjectscale);
5907
5908- //z = (p->starpostz + 128) << FRACBITS; // reverse gravity exists, pls
5909 mobj->player->kartstuff[k_starpostflip] = 0;
5910
5911 if (z < floor)
5912@@ -11413,8 +11496,6 @@ void P_MovePlayerToStarpost(INT32 playernum)
5913
5914 mobj->angle = p->starpostangle;
5915
5916- p->kartstuff[k_waypoint] = p->kartstuff[k_starpostwp]; // SRB2kart
5917-
5918 P_AfterPlayerSpawn(playernum);
5919
5920 //if (!(netgame || multiplayer))
5921@@ -11723,6 +11804,26 @@ void P_SpawnMapThing(mapthing_t *mthing)
5922 else
5923 mthing->z = (INT16)(z>>FRACBITS);
5924 }
5925+ else if (i == MT_WAYPOINT)
5926+ {
5927+ // just gets set on either the floor or ceiling
5928+ boolean flip = (!!(mobjinfo[i].flags & MF_SPAWNCEILING) ^ !!(mthing->options & MTF_OBJECTFLIP));
5929+
5930+ // applying offsets! (if any)
5931+ if (flip)
5932+ {
5933+ z = ONCEILINGZ;
5934+ }
5935+ else
5936+ {
5937+ z = ONFLOORZ;
5938+ }
5939+
5940+ if (z == ONFLOORZ)
5941+ mthing->z = 0;
5942+ else
5943+ mthing->z = (INT16)(z>>FRACBITS);
5944+ }
5945 else
5946 {
5947 fixed_t offset = 0;
5948@@ -11969,6 +12070,74 @@ ML_NOCLIMB : Direction not controllable
5949 if (mthing->angle >= 360)
5950 mobj->tics += 7*(mthing->angle / 360) + 1; // starting delay
5951 break;
5952+ case MT_WAYPOINT:
5953+ {
5954+ size_t line;
5955+ mobj->radius = 384*FRACUNIT;
5956+ // Same reason as for MT_SPINMACEPOINT we can't use the function to find the linedef
5957+ for (line = 0; line < numlines; line++)
5958+ {
5959+ if (lines[line].special == 2000 && lines[line].tag == mthing->angle)
5960+ break;
5961+ }
5962+ // Set the radius, mobj z, and mthing z to match what the parameters want
5963+ if (line < numlines)
5964+ {
5965+ fixed_t lineradius = sides[lines[line].sidenum[0]].textureoffset;
5966+ fixed_t linez = sides[lines[line].sidenum[0]].rowoffset;
5967+
5968+ if (lineradius > 0)
5969+ mobj->radius = lineradius;
5970+ mobj->z += linez;
5971+ mthing->z += linez >> FRACBITS;
5972+ }
5973+ // Use threshold to store the next waypoint ID
5974+ // movecount is being used for the current waypoint ID
5975+ // reactiontime lets us know if we can respawn at it
5976+ // lastlook is used for indicating the waypoint is a shortcut
5977+ // extravalue1 is used for indicating the waypoint is disabled
5978+ // extravalue2 is used for indicating the waypoint is the finishline
5979+ mobj->threshold = ((mthing->options >> ZSHIFT));
5980+ mobj->movecount = mthing->angle;
5981+ if (mthing->options & MTF_EXTRA)
5982+ {
5983+ mobj->extravalue1 = 0; // The waypoint is disabled if extra is on
5984+ }
5985+ else
5986+ {
5987+ mobj->extravalue1 = 1;
5988+ }
5989+ if (mthing->options & MTF_OBJECTSPECIAL)
5990+ {
5991+ mobj->lastlook = 1; // the waypoint is a shortcut if objectspecial is on
5992+ }
5993+ else
5994+ {
5995+ mobj->lastlook = 0;
5996+ }
5997+ if (mthing->options & MTF_AMBUSH)
5998+ {
5999+ mobj->reactiontime = 0; // Can't respawn at if Ambush is on
6000+ }
6001+ else
6002+ {
6003+ mobj->reactiontime = 1;
6004+ }
6005+ if (mthing->extrainfo == 1)
6006+ {
6007+ mobj->extravalue2 = 1; // extrainfo of 1 means the waypoint is at the finish line
6008+ }
6009+ else
6010+ {
6011+ mobj->extravalue2 = 0;
6012+ }
6013+
6014+
6015+ // Sryder 2018-12-7: Grabbed this from the old MT_BOSS3WAYPOINT section so they'll be in the waypointcap instead
6016+ P_SetTarget(&mobj->tracer, waypointcap);
6017+ P_SetTarget(&waypointcap, mobj);
6018+ break;
6019+ }
6020 // SRB2Kart
6021 case MT_BALLOON:
6022 mobj->color = (1 + (mthing->angle % (MAXSKINCOLORS-1)));
6023@@ -12104,13 +12273,6 @@ ML_NOCLIMB : Direction not controllable
6024 if (!foundanother)
6025 numstarposts++;
6026 }
6027- else if (i == MT_BOSS3WAYPOINT) // SRB2kart 120217 - Used to store checkpoint num
6028- {
6029- mobj->health = mthing->angle;
6030- mobj->movecount = mthing->extrainfo;
6031- P_SetTarget(&mobj->tracer, waypointcap);
6032- P_SetTarget(&waypointcap, mobj);
6033- }
6034 else if (i == MT_SPIKE)
6035 {
6036 // Pop up spikes!
6037diff --git a/src/p_mobj.h b/src/p_mobj.h
6038index 03a19870c..5cc93a56d 100644
6039--- a/src/p_mobj.h
6040+++ b/src/p_mobj.h
6041@@ -319,6 +319,9 @@ typedef struct mobj_s
6042 struct mobj_s *hnext;
6043 struct mobj_s *hprev;
6044
6045+ // One last pointer for kart item lists
6046+ struct mobj_s *itnext;
6047+
6048 INT32 health; // for player this is rings + 1
6049
6050 // Movement direction, movement generation (zig-zagging).
6051@@ -437,12 +440,18 @@ typedef struct actioncache_s
6052
6053 extern actioncache_t actioncachehead;
6054
6055+extern mobj_t *kitemcap;
6056 extern mobj_t *waypointcap;
6057
6058 void P_InitCachedActions(void);
6059 void P_RunCachedActions(void);
6060 void P_AddCachedAction(mobj_t *mobj, INT32 statenum);
6061
6062+// kartitem stuff: Returns true if the specified 'type' is one of the kart item constants we want in the kitemcap list
6063+boolean P_IsKartItem(INT32 type);
6064+void P_AddKartItem(mobj_t *thing); // needs to be called in k_kart.c
6065+void P_RunKartItems(void);
6066+
6067 // check mobj against water content, before movement code
6068 void P_MobjCheckWater(mobj_t *mobj);
6069
6070diff --git a/src/p_saveg.c b/src/p_saveg.c
6071index b81cc0f50..4767b09d1 100644
6072--- a/src/p_saveg.c
6073+++ b/src/p_saveg.c
6074@@ -279,6 +279,9 @@ static void P_NetArchivePlayers(void)
6075 WRITEINT16(save_p, players[i].lturn_max[j]);
6076 WRITEINT16(save_p, players[i].rturn_max[j]);
6077 }
6078+
6079+ WRITEUINT32(save_p, players[i].distancetofinish);
6080+ WRITEUINT32(save_p, K_GetWaypointHeapIndex(players[i].nextwaypoint));
6081 }
6082 }
6083
6084@@ -447,6 +450,9 @@ static void P_NetUnArchivePlayers(void)
6085 players[i].lturn_max[j] = READINT16(save_p);
6086 players[i].rturn_max[j] = READINT16(save_p);
6087 }
6088+
6089+ players[i].distancetofinish = READUINT32(save_p);
6090+ players[i].nextwaypoint = (waypoint_t *)(size_t)READUINT32(save_p);
6091 }
6092 }
6093
6094@@ -951,9 +957,11 @@ typedef enum
6095 MD2_HNEXT = 1<<7,
6096 MD2_HPREV = 1<<8,
6097 MD2_COLORIZED = 1<<9,
6098- MD2_WAYPOINTCAP = 1<<10
6099+ MD2_WAYPOINTCAP = 1<<10,
6100+ MD2_KITEMCAP = 1<<11,
6101+ MD2_ITNEXT = 1<<12
6102 #ifdef ESLOPE
6103- , MD2_SLOPE = 1<<11
6104+ , MD2_SLOPE = 1<<13
6105 #endif
6106 } mobj_diff2_t;
6107
6108@@ -1145,6 +1153,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
6109 diff2 |= MD2_HNEXT;
6110 if (mobj->hprev)
6111 diff2 |= MD2_HPREV;
6112+ if (mobj->itnext)
6113+ diff2 |= MD2_ITNEXT;
6114 #ifdef ESLOPE
6115 if (mobj->standingslope)
6116 diff2 |= MD2_SLOPE;
6117@@ -1153,6 +1163,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
6118 diff2 |= MD2_COLORIZED;
6119 if (mobj == waypointcap)
6120 diff2 |= MD2_WAYPOINTCAP;
6121+ if (mobj == kitemcap)
6122+ diff2 |= MD2_KITEMCAP;
6123 if (diff2 != 0)
6124 diff |= MD_MORE;
6125
6126@@ -1268,6 +1280,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
6127 WRITEUINT32(save_p, mobj->hnext->mobjnum);
6128 if (diff2 & MD2_HPREV)
6129 WRITEUINT32(save_p, mobj->hprev->mobjnum);
6130+ if (diff2 & MD2_ITNEXT)
6131+ WRITEUINT32(save_p, mobj->itnext->mobjnum);
6132 #ifdef ESLOPE
6133 if (diff2 & MD2_SLOPE)
6134 WRITEUINT16(save_p, mobj->standingslope->id);
6135@@ -2145,6 +2159,8 @@ static void LoadMobjThinker(actionf_p1 thinker)
6136 mobj->hnext = (mobj_t *)(size_t)READUINT32(save_p);
6137 if (diff2 & MD2_HPREV)
6138 mobj->hprev = (mobj_t *)(size_t)READUINT32(save_p);
6139+ if (diff2 & MD2_ITNEXT)
6140+ mobj->itnext = (mobj_t *)(size_t)READUINT32(save_p);
6141 #ifdef ESLOPE
6142 if (diff2 & MD2_SLOPE)
6143 {
6144@@ -2186,6 +2202,9 @@ static void LoadMobjThinker(actionf_p1 thinker)
6145 if (diff2 & MD2_WAYPOINTCAP)
6146 P_SetTarget(&waypointcap, mobj);
6147
6148+ if (diff2 & MD2_KITEMCAP)
6149+ P_SetTarget(&kitemcap, mobj);
6150+
6151 mobj->info = (mobjinfo_t *)next; // temporarily, set when leave this function
6152 }
6153
6154@@ -3038,6 +3057,13 @@ static void P_RelinkPointers(void)
6155 if (!(mobj->hprev = P_FindNewPosition(temp)))
6156 CONS_Debug(DBG_GAMELOGIC, "hprev not found on %d\n", mobj->type);
6157 }
6158+ if (mobj->itnext)
6159+ {
6160+ temp = (UINT32)(size_t)mobj->itnext;
6161+ mobj->itnext = NULL;
6162+ if (!(mobj->itnext = P_FindNewPosition(temp)))
6163+ CONS_Debug(DBG_GAMELOGIC, "itnext not found on %d\n", mobj->type);
6164+ }
6165 if (mobj->player && mobj->player->capsule)
6166 {
6167 temp = (UINT32)(size_t)mobj->player->capsule;
6168@@ -3066,6 +3092,15 @@ static void P_RelinkPointers(void)
6169 if (!P_SetTarget(&mobj->player->awayviewmobj, P_FindNewPosition(temp)))
6170 CONS_Debug(DBG_GAMELOGIC, "awayviewmobj not found on %d\n", mobj->type);
6171 }
6172+ if (mobj->player && mobj->player->nextwaypoint)
6173+ {
6174+ temp = (UINT32)(size_t)mobj->player->nextwaypoint;
6175+ mobj->player->nextwaypoint = K_GetWaypointFromIndex(temp);
6176+ if (mobj->player->nextwaypoint == NULL)
6177+ {
6178+ CONS_Debug(DBG_GAMELOGIC, "nextwaypoint not found on %d\n", mobj->type);
6179+ }
6180+ }
6181 }
6182 }
6183 }
6184diff --git a/src/p_setup.c b/src/p_setup.c
6185index b51dabdd0..e0b3550dc 100644
6186--- a/src/p_setup.c
6187+++ b/src/p_setup.c
6188@@ -85,6 +85,7 @@
6189 // SRB2Kart
6190 #include "k_kart.h"
6191 #include "k_pwrlv.h"
6192+#include "k_waypoint.h"
6193
6194 //
6195 // Map MD5, calculated on level load.
6196@@ -3115,6 +3116,17 @@ boolean P_SetupLevel(boolean skipprecip)
6197 if (loadprecip) // ugly hack for P_NetUnArchiveMisc (and P_LoadNetGame)
6198 P_SpawnPrecipitation();
6199
6200+
6201+ // The waypoint data that's in PU_LEVEL needs to be reset back to 0/NULL now since PU_LEVEL was cleared
6202+ K_ClearWaypoints();
6203+ // Load the waypoints please!
6204+ if (G_RaceGametype())
6205+ {
6206+ if (K_SetupWaypointList() == false)
6207+ {
6208+ CONS_Alert(CONS_ERROR, "Waypoints were not able to be setup! Player positions will not work correctly.\n");
6209+ }
6210+ }
6211 #ifdef HWRENDER // not win32 only 19990829 by Kin
6212 if (rendermode != render_soft && rendermode != render_none)
6213 {
6214diff --git a/src/p_sight.c b/src/p_sight.c
6215index 626f8bbef..f230f40f6 100644
6216--- a/src/p_sight.c
6217+++ b/src/p_sight.c
6218@@ -2,7 +2,7 @@
6219 //-----------------------------------------------------------------------------
6220 // Copyright (C) 1993-1996 by id Software, Inc.
6221 // Copyright (C) 1998-2000 by DooM Legacy Team.
6222-// Copyright (C) 1999-2018 by Sonic Team Junior.
6223+// Copyright (C) 1999-2020 by Sonic Team Junior.
6224 //
6225 // This program is free software distributed under the
6226 // terms of the GNU General Public License, version 2.
6227@@ -14,6 +14,7 @@
6228 #include "doomdef.h"
6229 #include "doomstat.h"
6230 #include "p_local.h"
6231+#include "p_slopes.h"
6232 #include "r_main.h"
6233 #include "r_state.h"
6234
6235@@ -103,12 +104,20 @@ static fixed_t P_InterceptVector2(divline_t *v2, divline_t *v1)
6236 static boolean P_CrossSubsecPolyObj(polyobj_t *po, register los_t *los)
6237 {
6238 size_t i;
6239+ sector_t *polysec;
6240+
6241+ if (!(po->flags & POF_RENDERALL))
6242+ return true; // the polyobject isn't visible, so we can ignore it
6243+
6244+ polysec = po->lines[0]->backsector;
6245
6246 for (i = 0; i < po->numLines; ++i)
6247 {
6248 line_t *line = po->lines[i];
6249 divline_t divl;
6250 const vertex_t *v1,*v2;
6251+ fixed_t frac;
6252+ fixed_t topslope, bottomslope;
6253
6254 // already checked other side?
6255 if (line->validcount == validcount)
6256@@ -140,7 +149,22 @@ static boolean P_CrossSubsecPolyObj(polyobj_t *po, register los_t *los)
6257 continue;
6258
6259 // stop because it is not two sided
6260- return false;
6261+ //if (!(po->flags & POF_TESTHEIGHT))
6262+ //return false;
6263+
6264+ frac = P_InterceptVector2(&los->strace, &divl);
6265+
6266+ // get slopes of top and bottom of this polyobject line
6267+ topslope = FixedDiv(polysec->ceilingheight - los->sightzstart , frac);
6268+ bottomslope = FixedDiv(polysec->floorheight - los->sightzstart , frac);
6269+
6270+ if (topslope >= los->topslope && bottomslope <= los->bottomslope)
6271+ return false; // view completely blocked
6272+
6273+ // TODO: figure out if it's worth considering partially blocked cases or not?
6274+ // maybe to adjust los's top/bottom slopes if needed
6275+ //if (los->topslope <= los->bottomslope)
6276+ //return false;
6277 }
6278
6279 return true;
6280@@ -193,6 +217,15 @@ static boolean P_CrossSubsector(size_t num, register los_t *los)
6281 const sector_t *front, *back;
6282 const vertex_t *v1,*v2;
6283 fixed_t frac;
6284+ fixed_t frontf, backf, frontc, backc;
6285+#ifdef ESLOPE
6286+ fixed_t fracx, fracy;
6287+#endif
6288+
6289+ /* SRB2Kart doesn't have this?
6290+ if (seg->glseg)
6291+ continue;
6292+ */
6293
6294 // already checked other side?
6295 if (line->validcount == validcount)
6296@@ -227,36 +260,51 @@ static boolean P_CrossSubsector(size_t num, register los_t *los)
6297 if (!(line->flags & ML_TWOSIDED))
6298 return false;
6299
6300+ // calculate fractional intercept (how far along we are divided by how far we are from t2)
6301+ frac = P_InterceptVector2(&los->strace, &divl);
6302+
6303+ front = seg->frontsector;
6304+ back = seg->backsector;
6305+#ifdef ESLOPE
6306+ // calculate position at intercept
6307+ fracx = los->strace.x + FixedMul(los->strace.dx, frac);
6308+ fracy = los->strace.y + FixedMul(los->strace.dy, frac);
6309+ // calculate sector heights
6310+ frontf = (front->f_slope) ? P_GetZAt(front->f_slope, fracx, fracy) : front->floorheight;
6311+ frontc = (front->c_slope) ? P_GetZAt(front->c_slope, fracx, fracy) : front->ceilingheight;
6312+ backf = (back->f_slope) ? P_GetZAt(back->f_slope, fracx, fracy) : back->floorheight;
6313+ backc = (back->c_slope) ? P_GetZAt(back->c_slope, fracx, fracy) : back->ceilingheight;
6314+#else
6315+ frontf = front->floorheight;
6316+ frontc = front->ceilingheight;
6317+ backf = back->floorheight;
6318+ backc = back->ceilingheight;
6319+#endif
6320 // crosses a two sided line
6321 // no wall to block sight with?
6322- if ((front = seg->frontsector)->floorheight ==
6323- (back = seg->backsector)->floorheight &&
6324- front->ceilingheight == back->ceilingheight)
6325+ if (frontf == backf && frontc == backc
6326+ && !front->ffloors & !back->ffloors) // (and no FOFs)
6327 continue;
6328
6329 // possible occluder
6330 // because of ceiling height differences
6331- popentop = front->ceilingheight < back->ceilingheight ?
6332- front->ceilingheight : back->ceilingheight ;
6333+ popentop = min(frontc, backc);
6334
6335 // because of floor height differences
6336- popenbottom = front->floorheight > back->floorheight ?
6337- front->floorheight : back->floorheight ;
6338+ popenbottom = max(frontf, backf);
6339
6340 // quick test for totally closed doors
6341 if (popenbottom >= popentop)
6342 return false;
6343
6344- frac = P_InterceptVector2(&los->strace, &divl);
6345-
6346- if (front->floorheight != back->floorheight)
6347+ if (frontf != backf)
6348 {
6349 fixed_t slope = FixedDiv(popenbottom - los->sightzstart , frac);
6350 if (slope > los->bottomslope)
6351 los->bottomslope = slope;
6352 }
6353
6354- if (front->ceilingheight != back->ceilingheight)
6355+ if (frontc != backc)
6356 {
6357 fixed_t slope = FixedDiv(popentop - los->sightzstart , frac);
6358 if (slope < los->topslope)
6359@@ -265,6 +313,58 @@ static boolean P_CrossSubsector(size_t num, register los_t *los)
6360
6361 if (los->topslope <= los->bottomslope)
6362 return false;
6363+
6364+ // Monster Iestyn: check FOFs!
6365+ if (front->ffloors || back->ffloors)
6366+ {
6367+ ffloor_t *rover;
6368+ fixed_t topslope, bottomslope;
6369+ fixed_t topz, bottomz;
6370+ // check front sector's FOFs first
6371+ for (rover = front->ffloors; rover; rover = rover->next)
6372+ {
6373+ if (!(rover->flags & FF_EXISTS)
6374+ || !(rover->flags & FF_RENDERSIDES) || rover->flags & FF_TRANSLUCENT)
6375+ {
6376+ continue;
6377+ }
6378+
6379+#ifdef ESLOPE
6380+ topz = (*rover->t_slope) ? P_GetZAt(*rover->t_slope, fracx, fracy) : *rover->topheight;
6381+ bottomz = (*rover->b_slope) ? P_GetZAt(*rover->b_slope, fracx, fracy) : *rover->bottomheight;
6382+#else
6383+ topz = *rover->topheight;
6384+ bottomz = *rover->bottomheight;
6385+#endif
6386+ topslope = FixedDiv(topz - los->sightzstart , frac);
6387+ bottomslope = FixedDiv(bottomz - los->sightzstart , frac);
6388+ if (topslope >= los->topslope && bottomslope <= los->bottomslope)
6389+ return false; // view completely blocked
6390+ }
6391+ // check back sector's FOFs as well
6392+ for (rover = back->ffloors; rover; rover = rover->next)
6393+ {
6394+ if (!(rover->flags & FF_EXISTS)
6395+ || !(rover->flags & FF_RENDERSIDES) || rover->flags & FF_TRANSLUCENT)
6396+ {
6397+ continue;
6398+ }
6399+
6400+#ifdef ESLOPE
6401+ topz = (*rover->t_slope) ? P_GetZAt(*rover->t_slope, fracx, fracy) : *rover->topheight;
6402+ bottomz = (*rover->b_slope) ? P_GetZAt(*rover->b_slope, fracx, fracy) : *rover->bottomheight;
6403+#else
6404+ topz = *rover->topheight;
6405+ bottomz = *rover->bottomheight;
6406+#endif
6407+ topslope = FixedDiv(topz - los->sightzstart , frac);
6408+ bottomslope = FixedDiv(bottomz - los->sightzstart , frac);
6409+ if (topslope >= los->topslope && bottomslope <= los->bottomslope)
6410+ return false; // view completely blocked
6411+ }
6412+ // TODO: figure out if it's worth considering partially blocked cases or not?
6413+ // maybe to adjust los's top/bottom slopes if needed
6414+ }
6415 }
6416
6417 // passed the subsector ok
6418@@ -375,6 +475,8 @@ boolean P_CheckSight(mobj_t *t1, mobj_t *t2)
6419 if (s1 == s2) // Both sectors are the same.
6420 {
6421 ffloor_t *rover;
6422+ fixed_t topz1, bottomz1; // top, bottom heights at t1's position
6423+ fixed_t topz2, bottomz2; // likewise but for t2
6424
6425 for (rover = s1->ffloors; rover; rover = rover->next)
6426 {
6427@@ -382,14 +484,35 @@ boolean P_CheckSight(mobj_t *t1, mobj_t *t2)
6428 /// \todo Improve by checking fog density/translucency
6429 /// and setting a sight limit.
6430 if (!(rover->flags & FF_EXISTS)
6431- || !(rover->flags & FF_RENDERPLANES) || rover->flags & FF_TRANSLUCENT)
6432+ || !(rover->flags & FF_RENDERPLANES) /*|| (rover->flags & FF_TRANSLUCENT)*/)
6433 {
6434 continue;
6435 }
6436
6437+#ifdef ESLOPE
6438+ if (*rover->t_slope)
6439+ {
6440+ topz1 = P_GetZAt(*rover->t_slope, t1->x, t1->y);
6441+ topz2 = P_GetZAt(*rover->t_slope, t2->x, t2->y);
6442+ }
6443+ else
6444+ topz1 = topz2 = *rover->topheight;
6445+
6446+ if (*rover->b_slope)
6447+ {
6448+ bottomz1 = P_GetZAt(*rover->b_slope, t1->x, t1->y);
6449+ bottomz2 = P_GetZAt(*rover->b_slope, t2->x, t2->y);
6450+ }
6451+ else
6452+ bottomz1 = bottomz2 = *rover->bottomheight;
6453+#else
6454+ topz1 = topz2 = *rover->topheight;
6455+ bottomz1 = bottomz2 = *rover->bottomheight;
6456+#endif
6457+
6458 // Check for blocking floors here.
6459- if ((los.sightzstart < *rover->bottomheight && t2->z >= *rover->topheight)
6460- || (los.sightzstart >= *rover->topheight && t2->z + t2->height < *rover->bottomheight))
6461+ if ((los.sightzstart < bottomz1 && t2->z >= topz2)
6462+ || (los.sightzstart >= topz1 && t2->z + t2->height < bottomz2))
6463 {
6464 // no way to see through that
6465 return false;
6466@@ -400,19 +523,19 @@ boolean P_CheckSight(mobj_t *t1, mobj_t *t2)
6467
6468 if (!(rover->flags & FF_INVERTPLANES))
6469 {
6470- if (los.sightzstart >= *rover->topheight && t2->z + t2->height < *rover->topheight)
6471+ if (los.sightzstart >= topz1 && t2->z + t2->height < topz2)
6472 return false; // blocked by upper outside plane
6473
6474- if (los.sightzstart < *rover->bottomheight && t2->z >= *rover->bottomheight)
6475+ if (los.sightzstart < bottomz1 && t2->z >= bottomz2)
6476 return false; // blocked by lower outside plane
6477 }
6478
6479 if (rover->flags & FF_INVERTPLANES || rover->flags & FF_BOTHPLANES)
6480 {
6481- if (los.sightzstart < *rover->topheight && t2->z >= *rover->topheight)
6482+ if (los.sightzstart < topz1 && t2->z >= topz2)
6483 return false; // blocked by upper inside plane
6484
6485- if (los.sightzstart >= *rover->bottomheight && t2->z + t2->height < *rover->bottomheight)
6486+ if (los.sightzstart >= bottomz1 && t2->z + t2->height < bottomz2)
6487 return false; // blocked by lower inside plane
6488 }
6489 }
6490diff --git a/src/p_spec.c b/src/p_spec.c
6491index bfbdf6228..c4070ef5c 100644
6492--- a/src/p_spec.c
6493+++ b/src/p_spec.c
6494@@ -1705,41 +1705,6 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
6495 if (!(ALL7EMERALDS(emeralds)))
6496 return false;
6497 }
6498- else if (GETSECSPECIAL(caller->special, 2) == 7) // SRB2Kart: reusing for Race Lap executor
6499- {
6500- UINT8 lap;
6501-
6502- if (actor && actor->player && triggerline->flags & ML_EFFECT4)
6503- {
6504- /*if (maptol & TOL_NIGHTS)
6505- lap = actor->player->mare;
6506- else*/
6507- lap = actor->player->laps;
6508- }
6509- else
6510- {
6511- /*if (maptol & TOL_NIGHTS)
6512- lap = P_FindLowestMare();
6513- else*/
6514- lap = P_FindLowestLap();
6515- }
6516-
6517- if (triggerline->flags & ML_NOCLIMB) // Need higher than or equal to
6518- {
6519- if (lap < (sides[triggerline->sidenum[0]].textureoffset >> FRACBITS))
6520- return false;
6521- }
6522- else if (triggerline->flags & ML_BLOCKMONSTERS) // Need lower than or equal to
6523- {
6524- if (lap > (sides[triggerline->sidenum[0]].textureoffset >> FRACBITS))
6525- return false;
6526- }
6527- else // Need equal to
6528- {
6529- if (lap != (sides[triggerline->sidenum[0]].textureoffset >> FRACBITS))
6530- return false;
6531- }
6532- }
6533 // If we were not triggered by a sector type especially for the purpose,
6534 // a Linedef Executor linedef trigger is not handling sector triggers properly, return.
6535
6536@@ -1937,15 +1902,17 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
6537 else
6538 // These special types work only once
6539 if (specialtype == 302 // Once
6540- || specialtype == 304 // Ring count - Once
6541- || specialtype == 307 // Character ability - Once
6542- || specialtype == 308 // Race only - Once
6543- || specialtype == 315 // No of pushables - Once
6544- || specialtype == 318 // Unlockable trigger - Once
6545- || specialtype == 320 // Unlockable - Once
6546- || specialtype == 321 || specialtype == 322 // Trigger on X calls - Continuous + Each Time
6547- || specialtype == 328 // Encore Load
6548- || specialtype == 399) // Level Load
6549+ || specialtype == 304 // Ring count - Once
6550+ || specialtype == 307 // Character ability - Once
6551+ || specialtype == 308 // Race only - Once
6552+ || specialtype == 315 // No of pushables - Once
6553+ || specialtype == 318 // Unlockable trigger - Once
6554+ || specialtype == 320 // Unlockable - Once
6555+ || specialtype == 321 || specialtype == 322 // Trigger on X calls - Continuous + Each Time
6556+ || specialtype == 328 // Encore Load
6557+ || specialtype == 399 // Level Load
6558+ || specialtype == 2002 // SRB2Kart Race Lap
6559+ )
6560 triggerline->special = 0; // Clear it out
6561
6562 return true;
6563@@ -1981,6 +1948,7 @@ void P_LinedefExecute(INT16 tag, mobj_t *actor, sector_t *caller)
6564 if (lines[masterline].special == 313
6565 || lines[masterline].special == 399
6566 || lines[masterline].special == 328
6567+ || lines[masterline].special == 2002 // SRB2Kart race lap trigger
6568 // Each-time executors handle themselves, too
6569 || lines[masterline].special == 301 // Each time
6570 || lines[masterline].special == 306 // Character ability - Each time
6571@@ -2089,6 +2057,188 @@ void P_SwitchWeather(UINT8 newWeather)
6572 P_SpawnPrecipitation();
6573 }
6574
6575+// Passed over the finish line forwards
6576+static void K_HandleLapIncrement(player_t *player)
6577+{
6578+ if (player)
6579+ {
6580+ if ((player->starpostnum == numstarposts) || (player->laps == 0))
6581+ {
6582+ size_t i = 0;
6583+ UINT8 nump = 0;
6584+
6585+ for (i = 0; i < MAXPLAYERS; i++)
6586+ {
6587+ if (!playeringame[i] || players[i].spectator)
6588+ continue;
6589+ nump++;
6590+ }
6591+
6592+ player->laps++;
6593+
6594+ // Set up lap animation vars
6595+ if (player->laps > 1)
6596+ {
6597+ if (nump > 1)
6598+ {
6599+ if (K_IsPlayerLosing(player))
6600+ player->karthud[khud_laphand] = 3;
6601+ else
6602+ {
6603+ if (nump > 2 && player->kartstuff[k_position] == 1) // 1st place in 1v1 uses thumbs up
6604+ player->karthud[khud_laphand] = 1;
6605+ else
6606+ player->karthud[khud_laphand] = 2;
6607+ }
6608+ }
6609+ else
6610+ player->karthud[khud_laphand] = 0; // No hands in FREE PLAY
6611+
6612+ player->karthud[khud_lapanimation] = 80;
6613+ }
6614+
6615+ if (netgame && player->laps >= (UINT8)cv_numlaps.value)
6616+ CON_LogMessage(va(M_GetText("%s has finished the race.\n"), player_names[player-players]));
6617+
6618+ // SRB2Kart: save best lap for record attack
6619+ if (player == &players[consoleplayer])
6620+ {
6621+ if (curlap < bestlap || bestlap == 0)
6622+ bestlap = curlap;
6623+ curlap = 0;
6624+ }
6625+
6626+ player->starposttime = player->realtime;
6627+ player->starpostnum = 0;
6628+
6629+ if (P_IsDisplayPlayer(player))
6630+ {
6631+ if (player->laps == (UINT8)(cv_numlaps.value)) // final lap
6632+ S_StartSound(NULL, sfx_s3k68);
6633+ else if ((player->laps > 1) && (player->laps < (UINT8)(cv_numlaps.value))) // non-final lap
6634+ S_StartSound(NULL, sfx_s221);
6635+ else if (player->laps > (UINT8)(cv_numlaps.value))
6636+ {
6637+ // finished
6638+ S_StartSound(NULL, sfx_s3k6a);
6639+ }
6640+
6641+ }
6642+ else
6643+ {
6644+ if ((player->laps > (UINT8)(cv_numlaps.value)) && (player->kartstuff[k_position] == 1))
6645+ {
6646+ // opponent finished
6647+ S_StartSound(NULL, sfx_s253);
6648+ }
6649+ }
6650+
6651+ // finished race exit setup
6652+ if (player->laps > (unsigned)cv_numlaps.value)
6653+ {
6654+ P_DoPlayerExit(player);
6655+ P_SetupSignExit(player);
6656+ }
6657+
6658+ thwompsactive = true; // Lap 2 effects
6659+
6660+ for (i = 0; i < numlines; i++)
6661+ {
6662+ if (lines[i].special == 2002) // Race lap trigger
6663+ {
6664+ UINT8 lap;
6665+
6666+ if (lines[i].flags & ML_EFFECT4)
6667+ {
6668+ lap = player->laps;
6669+ }
6670+ else
6671+ {
6672+ lap = P_FindLowestLap();
6673+ }
6674+
6675+ if (lines[i].flags & ML_NOCLIMB) // Need higher than or equal to
6676+ {
6677+ if (lap < (sides[lines[i].sidenum[0]].textureoffset >> FRACBITS))
6678+ continue;
6679+ }
6680+ else if (lines[i].flags & ML_BLOCKMONSTERS) // Need lower than or equal to
6681+ {
6682+ if (lap > (sides[lines[i].sidenum[0]].textureoffset >> FRACBITS))
6683+ continue;
6684+ }
6685+ else // Need equal to
6686+ {
6687+ if (lap != (sides[lines[i].sidenum[0]].textureoffset >> FRACBITS))
6688+ continue;
6689+ }
6690+
6691+ P_RunTriggerLinedef(&lines[i], player->mo, NULL);
6692+ }
6693+ }
6694+ }
6695+ else if (player->starpostnum)
6696+ {
6697+ S_StartSound(player->mo, sfx_s26d);
6698+ }
6699+ }
6700+}
6701+
6702+// player went backwards over the line
6703+static void K_HandleLapDecrement(player_t *player)
6704+{
6705+ if (player)
6706+ {
6707+ if ((player->starpostnum == 0) && (player->laps > 0))
6708+ {
6709+ player->starpostnum = numstarposts;
6710+ player->laps--;
6711+ }
6712+ }
6713+}
6714+
6715+//
6716+// P_CrossSpecialLine - TRIGGER
6717+// Called every time a thing origin is about
6718+// to cross a line with specific specials
6719+// Kart - Only used for the finish line currently
6720+//
6721+void P_CrossSpecialLine(line_t *line, INT32 side, mobj_t *thing)
6722+{
6723+ // only used for the players currently
6724+ if (thing && thing->player)
6725+ {
6726+ player_t *player = thing->player;
6727+ switch (line->special)
6728+ {
6729+ case 2001: // Finish Line
6730+ {
6731+ if (G_RaceGametype() && !(player->exiting) && !(player->pflags & PF_HITFINISHLINE))
6732+ {
6733+ if (((line->flags & (ML_NOCLIMB)) && (side == 0))
6734+ || (!(line->flags & (ML_NOCLIMB)) && (side == 1))) // crossed from behind to infront
6735+ {
6736+ K_HandleLapIncrement(player);
6737+ }
6738+ else
6739+ {
6740+ K_HandleLapDecrement(player);
6741+ }
6742+
6743+ player->pflags |= PF_HITFINISHLINE;
6744+ }
6745+ }
6746+ break;
6747+
6748+ default:
6749+ {
6750+ // Do nothing
6751+ }
6752+ break;
6753+ }
6754+ }
6755+}
6756+
6757 /** Gets an object.
6758 *
6759 * \param type Object type to look for.
6760@@ -3621,10 +3771,10 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers
6761 case 6: // Death Pit (Camera Mod)
6762 case 7: // Death Pit (No Camera Mod)
6763 if (roversector || P_MobjReadyToTrigger(player->mo, sector))
6764- P_DamageMobj(player->mo, NULL, NULL, 10000);
6765+ K_DoIngameRespawn(player);
6766 break;
6767 case 8: // Instant Kill
6768- P_DamageMobj(player->mo, NULL, NULL, 10000);
6769+ K_DoIngameRespawn(player);
6770 break;
6771 case 9: // Ring Drainer (Floor Touch)
6772 case 10: // Ring Drainer (No Floor Touch)
6773@@ -3862,7 +4012,7 @@ DoneSection2:
6774 if (player->mo->scale > mapobjectscale)
6775 linespeed = FixedMul(linespeed, mapobjectscale + (player->mo->scale - mapobjectscale));
6776
6777- if (!demo.playback || P_AnalogMove(player))
6778+ if (!demo.playback)
6779 {
6780 if (player == &players[consoleplayer])
6781 localangle[0] = player->mo->angle;
6782@@ -4213,113 +4363,8 @@ DoneSection2:
6783 }
6784 break;
6785
6786- case 10: // Finish Line
6787- // SRB2kart - 150117
6788- if (G_RaceGametype() && (player->starpostnum >= (numstarposts - (numstarposts/2)) || player->exiting))
6789- player->kartstuff[k_starpostwp] = player->kartstuff[k_waypoint] = 0;
6790- //
6791- if (G_RaceGametype() && !player->exiting)
6792- {
6793- if (player->starpostnum >= (numstarposts - (numstarposts/2))) // srb2kart: must have touched *enough* starposts (was originally "(player->starpostnum == numstarposts)")
6794- {
6795- UINT8 nump = 0;
6796-
6797- for (i = 0; i < MAXPLAYERS; i++)
6798- {
6799- if (!playeringame[i] || players[i].spectator)
6800- continue;
6801- nump++;
6802- }
6803-
6804- player->laps++;
6805-
6806- // Set up lap animation vars
6807- if (nump > 1)
6808- {
6809- if (K_IsPlayerLosing(player))
6810- player->karthud[khud_laphand] = 3;
6811- else
6812- {
6813- if (nump > 2 && player->kartstuff[k_position] == 1) // 1st place in 1v1 uses thumbs up
6814- player->karthud[khud_laphand] = 1;
6815- else
6816- player->karthud[khud_laphand] = 2;
6817- }
6818- }
6819- else
6820- player->karthud[khud_laphand] = 0; // No hands in FREE PLAY
6821-
6822- player->karthud[khud_lapanimation] = 80;
6823-
6824- if (player->pflags & PF_NIGHTSMODE)
6825- player->drillmeter += 48*20;
6826-
6827- if (netgame && player->laps >= (UINT8)cv_numlaps.value)
6828- CON_LogMessage(va(M_GetText("%s has finished the race.\n"), player_names[player-players]));
6829-
6830- // SRB2Kart: save best lap for record attack
6831- if (player == &players[consoleplayer])
6832- {
6833- if (curlap < bestlap || bestlap == 0)
6834- bestlap = curlap;
6835- curlap = 0;
6836- }
6837-
6838- player->starposttime = player->realtime;
6839- player->starpostnum = 0;
6840-
6841- if (mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE)
6842- {
6843- // SRB2Kart 281118
6844- // Save the player's time and position.
6845- player->starpostx = player->mo->x>>FRACBITS;
6846- player->starposty = player->mo->y>>FRACBITS;
6847- player->starpostz = player->mo->floorz>>FRACBITS;
6848- player->kartstuff[k_starpostflip] = player->mo->flags2 & MF2_OBJECTFLIP; // store flipping
6849- player->starpostangle = player->mo->angle; //R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy); torn; a momentum-based guess is less likely to be wrong in general, but when it IS wrong, it fucks you over entirely...
6850- }
6851- else
6852- {
6853- // SRB2kart 200117
6854- // Reset starposts (checkpoints) info
6855- player->starpostangle = player->starpostx = player->starposty = player->starpostz = player->kartstuff[k_starpostflip] = 0;
6856- }
6857-
6858- if (P_IsDisplayPlayer(player))
6859- {
6860- if (player->laps == (UINT8)(cv_numlaps.value - 1))
6861- S_StartSound(NULL, sfx_s3k68);
6862- else if (player->laps < (UINT8)(cv_numlaps.value - 1))
6863- S_StartSound(NULL, sfx_s221);
6864- }
6865-
6866- //player->starpostangle = player->starposttime = player->starpostnum = 0;
6867- //player->starpostx = player->starposty = player->starpostz = 0;
6868-
6869- // Play the starpost sound for 'consistency'
6870- // S_StartSound(player->mo, sfx_strpst);
6871-
6872- thwompsactive = true; // Lap 2 effects
6873- }
6874- else if (player->starpostnum)
6875- {
6876- // blatant reuse of a variable that's normally unused in circuit
6877- if (!player->tossdelay)
6878- S_StartSound(player->mo, sfx_s26d);
6879- player->tossdelay = 3;
6880- }
6881-
6882- if (player->laps >= (unsigned)cv_numlaps.value)
6883- {
6884- if (P_IsDisplayPlayer(player))
6885- S_StartSound(NULL, sfx_s3k6a);
6886- else if (player->kartstuff[k_position] == 1)
6887- S_StartSound(NULL, sfx_s253);
6888-
6889- P_DoPlayerExit(player);
6890- P_SetupSignExit(player);
6891- }
6892- }
6893+ case 10: // Finish Line (Unused)
6894+ // SRB2Kart 20190616 - Is now a linedef type that activates by crossing over it
6895 break;
6896
6897 case 11: // Rope hang
6898@@ -4872,7 +4917,7 @@ static void P_RunSpecialSectorCheck(player_t *player, sector_t *sector)
6899 case 6: // Super Sonic Transform
6900 case 8: // Zoom Tube Start
6901 case 9: // Zoom Tube End
6902- case 10: // Finish line
6903+ case 10: // Finish line (Unused)
6904 nofloorneeded = true;
6905 break;
6906 }
6907@@ -5731,9 +5776,9 @@ void P_SpawnSpecials(INT32 fromnetsave)
6908 // Process Section 4
6909 switch(GETSECSPECIAL(sector->special, 4))
6910 {
6911- case 10: // Circuit finish line
6912- if (G_RaceGametype())
6913- circuitmap = true;
6914+ case 10: // Circuit finish line (Unused)
6915+ // Remove before release
6916+ CONS_Alert(CONS_WARNING, "Finish line sector type is deprecated.\n");
6917 break;
6918 }
6919 }
6920@@ -6664,6 +6709,15 @@ void P_SpawnSpecials(INT32 fromnetsave)
6921 sectors[s].midmap = lines[i].frontsector->midmap;
6922 break;
6923
6924+ // SRB2Kart
6925+ case 2000: // Waypoint Parameters
6926+ break;
6927+ case 2001: // Finish Line
6928+ if (G_RaceGametype())
6929+ circuitmap = true;
6930+ break;
6931+ case 2002: // Linedef Trigger: Race Lap
6932+ break;
6933 default:
6934 break;
6935 }
6936@@ -6884,8 +6938,8 @@ void T_Scroll(scroll_t *s)
6937
6938 height = P_GetSpecialBottomZ(thing, sec, psec);
6939
6940- if (!(thing->flags & MF_NOCLIP)) // Thing must be clipped
6941- if (!(thing->flags & MF_NOGRAVITY || thing->z+thing->height != height)) // Thing must a) be non-floating and have z+height == height
6942+ if (!(thing->flags & MF_NOCLIP) && // Thing must be clipped
6943+ (!(thing->flags & MF_NOGRAVITY || thing->z+thing->height != height))) // Thing must a) be non-floating and have z+height == height
6944 {
6945 // Move objects only if on floor
6946 // non-floating, and clipped.
6947@@ -6960,8 +7014,8 @@ void T_Scroll(scroll_t *s)
6948
6949 height = P_GetSpecialTopZ(thing, sec, psec);
6950
6951- if (!(thing->flags & MF_NOCLIP)) // Thing must be clipped
6952- if (!(thing->flags & MF_NOGRAVITY || thing->z != height))// Thing must a) be non-floating and have z == height
6953+ if (!(thing->flags & MF_NOCLIP) && // Thing must be clipped
6954+ (!(thing->flags & MF_NOGRAVITY || thing->z != height))) // Thing must a) be non-floating and have z == height
6955 {
6956 // Move objects only if on floor or underwater,
6957 // non-floating, and clipped.
6958@@ -7856,7 +7910,7 @@ void T_Pusher(pusher_t *p)
6959 thing->player->pflags |= PF_SLIDING;
6960 thing->angle = R_PointToAngle2 (0, 0, xspeed<<(FRACBITS-PUSH_FACTOR), yspeed<<(FRACBITS-PUSH_FACTOR));
6961
6962- if (!demo.playback || P_AnalogMove(thing->player))
6963+ if (!demo.playback)
6964 {
6965 if (thing->player == &players[consoleplayer])
6966 {
6967diff --git a/src/p_spec.h b/src/p_spec.h
6968index b65745998..a49946c2a 100644
6969--- a/src/p_spec.h
6970+++ b/src/p_spec.h
6971@@ -56,6 +56,8 @@ INT32 P_FindSpecialLineFromTag(INT16 special, INT16 tag, INT32 start);
6972
6973 INT32 P_FindMinSurroundingLight(sector_t *sector, INT32 max);
6974
6975+void P_CrossSpecialLine(line_t *ld, INT32 side, mobj_t *thing);
6976+
6977 void P_SetupSignExit(player_t *player);
6978 boolean P_IsFlagAtBase(mobjtype_t flag);
6979
6980diff --git a/src/p_tick.c b/src/p_tick.c
6981index aac04fe82..1c87218b9 100644
6982--- a/src/p_tick.c
6983+++ b/src/p_tick.c
6984@@ -23,6 +23,7 @@
6985 #include "lua_script.h"
6986 #include "lua_hook.h"
6987 #include "k_kart.h"
6988+#include "k_waypoint.h"
6989
6990 // Object place
6991 #include "m_cheat.h"
6992@@ -172,6 +173,7 @@ void P_InitThinkers(void)
6993 {
6994 thinkercap.prev = thinkercap.next = &thinkercap;
6995 waypointcap = NULL;
6996+ kitemcap = NULL;
6997 }
6998
6999 //
7000@@ -628,6 +630,9 @@ void P_Ticker(boolean run)
7001 if (runemeraldmanager)
7002 P_EmeraldManager(); // Power stone mode*/
7003
7004+ // formality so kitemcap gets updated properly each frame.
7005+ P_RunKartItems();
7006+
7007 if (run)
7008 {
7009 P_RunThinkers();
7010@@ -736,6 +741,11 @@ void P_Ticker(boolean run)
7011 && --mapreset <= 1
7012 && server) // Remember: server uses it for mapchange, but EVERYONE ticks down for the animation
7013 D_MapChange(gamemap, gametype, encoremode, true, 0, false, false);
7014+
7015+ if (cv_kartdebugwaypoints.value != 0)
7016+ {
7017+ K_DebugWaypointsVisualise();
7018+ }
7019 }
7020
7021 // Always move the camera.
7022diff --git a/src/p_user.c b/src/p_user.c
7023index 5ac789e54..666855f53 100644
7024--- a/src/p_user.c
7025+++ b/src/p_user.c
7026@@ -1253,7 +1253,7 @@ void P_RestoreMusic(player_t *player)
7027 #if 0
7028 // Event - Final Lap
7029 // Still works for GME, but disabled for consistency
7030- if (G_RaceGametype() && player->laps >= (UINT8)(cv_numlaps.value - 1))
7031+ if (G_RaceGametype() && player->laps >= (UINT8)(cv_numlaps.value))
7032 S_SpeedMusic(1.2f);
7033 #endif
7034 S_ChangeMusicEx(mapmusname, mapmusflags, true, mapmusposition, 0, 0);
7035@@ -3716,11 +3716,6 @@ void P_Telekinesis(player_t *player, fixed_t thrust, fixed_t range)
7036 player->pflags |= PF_THOKKED;
7037 }
7038
7039-boolean P_AnalogMove(player_t *player)
7040-{
7041- return player->pflags & PF_ANALOGMODE;
7042-}
7043-
7044 //
7045 // P_GetPlayerControlDirection
7046 //
7047@@ -3763,14 +3758,6 @@ boolean P_AnalogMove(player_t *player)
7048 origtempangle = tempangle = 0; // relative to the axis rather than the player!
7049 controlplayerdirection = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy);
7050 }
7051- else if (P_AnalogMove(player) && thiscam->chase)
7052- {
7053- if (player->awayviewtics)
7054- origtempangle = tempangle = player->awayviewmobj->angle;
7055- else
7056- origtempangle = tempangle = thiscam->angle;
7057- controlplayerdirection = player->mo->angle;
7058- }
7059 else
7060 {
7061 origtempangle = tempangle = player->mo->angle;
7062@@ -3994,7 +3981,6 @@ static void P_3dMovement(player_t *player)
7063 angle_t dangle; // replaces old quadrants bits
7064 //boolean dangleflip = false; // SRB2kart - toaster
7065 //fixed_t normalspd = FixedMul(player->normalspeed, player->mo->scale);
7066- boolean analogmove = false;
7067 fixed_t oldMagnitude, newMagnitude;
7068 #ifdef ESLOPE
7069 vector3_t totalthrust;
7070@@ -4006,8 +3992,6 @@ static void P_3dMovement(player_t *player)
7071 // Get the old momentum; this will be needed at the end of the function! -SH
7072 oldMagnitude = R_PointToDist2(player->mo->momx - player->cmomx, player->mo->momy - player->cmomy, 0, 0);
7073
7074- analogmove = P_AnalogMove(player);
7075-
7076 cmd = &player->cmd;
7077
7078 if ((player->exiting || mapreset) || player->pflags & PF_STASIS || player->kartstuff[k_spinouttimer]) // pw_introcam?
7079@@ -4020,19 +4004,13 @@ static void P_3dMovement(player_t *player)
7080 if (!(player->pflags & PF_FORCESTRAFE) && !player->kartstuff[k_pogospring])
7081 cmd->sidemove = 0;
7082
7083- if (analogmove)
7084- {
7085- movepushangle = (cmd->angleturn<<16 /* not FRACBITS */);
7086- }
7087+ if (player->kartstuff[k_drift] != 0)
7088+ movepushangle = player->mo->angle-(ANGLE_45/5)*player->kartstuff[k_drift];
7089+ else if (player->kartstuff[k_spinouttimer] || player->kartstuff[k_wipeoutslow]) // if spun out, use the boost angle
7090+ movepushangle = (angle_t)player->kartstuff[k_boostangle];
7091 else
7092- {
7093- if (player->kartstuff[k_drift] != 0)
7094- movepushangle = player->mo->angle-(ANGLE_45/5)*player->kartstuff[k_drift];
7095- else if (player->kartstuff[k_spinouttimer] || player->kartstuff[k_wipeoutslow]) // if spun out, use the boost angle
7096- movepushangle = (angle_t)player->kartstuff[k_boostangle];
7097- else
7098- movepushangle = player->mo->angle;
7099- }
7100+ movepushangle = player->mo->angle;
7101+
7102 movepushsideangle = movepushangle-ANGLE_90;
7103
7104 // cmomx/cmomy stands for the conveyor belt speed.
7105@@ -6191,69 +6169,6 @@ static void P_MovePlayer(player_t *player)
7106 player->pflags &= ~PF_STARTDASH;
7107 */
7108
7109- //////////////////
7110- //ANALOG CONTROL//
7111- //////////////////
7112-
7113-#if 0
7114- // This really looks like it should be moved to P_3dMovement. -Red
7115- if (P_AnalogMove(player)
7116- && (cmd->forwardmove != 0 || cmd->sidemove != 0) && !player->climbing && !twodlevel && !(player->mo->flags2 & MF2_TWOD))
7117- {
7118- // If travelling slow enough, face the way the controls
7119- // point and not your direction of movement.
7120- if (player->speed < FixedMul(5*FRACUNIT, player->mo->scale) || player->pflags & PF_GLIDING || !onground)
7121- {
7122- angle_t tempangle;
7123-
7124- tempangle = (cmd->angleturn << 16);
7125-
7126-#ifdef REDSANALOG // Ease to it. Chillax. ~Red
7127- tempangle += R_PointToAngle2(0, 0, cmd->forwardmove*FRACUNIT, -cmd->sidemove*FRACUNIT);
7128- {
7129- fixed_t tweenvalue = max(abs(cmd->forwardmove), abs(cmd->sidemove));
7130-
7131- if (tweenvalue < 10 && (cmd->buttons & (BT_FORWARD|BT_BACKWARD)) == (BT_FORWARD|BT_BACKWARD)) {
7132- tempangle = (cmd->angleturn << 16);
7133- tweenvalue = 16;
7134- }
7135-
7136- tweenvalue *= tweenvalue*tweenvalue*1536;
7137-
7138- //if (player->pflags & PF_GLIDING)
7139- //tweenvalue >>= 1;
7140-
7141- tempangle -= player->mo->angle;
7142-
7143- if (tempangle < ANGLE_180 && tempangle > tweenvalue)
7144- player->mo->angle += tweenvalue;
7145- else if (tempangle >= ANGLE_180 && InvAngle(tempangle) > tweenvalue)
7146- player->mo->angle -= tweenvalue;
7147- else
7148- player->mo->angle += tempangle;
7149- }
7150-#else
7151- // Less math this way ~Red
7152- player->mo->angle = R_PointToAngle2(0, 0, cmd->forwardmove*FRACUNIT, -cmd->sidemove*FRACUNIT)+tempangle;
7153-#endif
7154- }
7155- // Otherwise, face the direction you're travelling.
7156- else if (player->panim == PA_WALK || player->panim == PA_RUN || player->panim == PA_ROLL
7157- /*|| ((player->mo->state >= &states[S_PLAY_ABL1] && player->mo->state <= &states[S_PLAY_SPC4]) && player->charability == CA_FLY)*/) // SRB2kart - idk
7158- player->mo->angle = R_PointToAngle2(0, 0, player->rmomx, player->rmomy);
7159-
7160- // Update the local angle control.
7161- if (player == &players[consoleplayer])
7162- localangle[0] = player->mo->angle;
7163- else if (player == &players[displayplayers[1]])
7164- localangle[1] = player->mo->angle;
7165- else if (player == &players[displayplayers[2]])
7166- localangle[2] = player->mo->angle;
7167- else if (player == &players[displayplayers[3]])
7168- localangle[3] = player->mo->angle;
7169- }
7170-#endif
7171-
7172 ///////////////////////////
7173 //BOMB SHIELD ACTIVATION,//
7174 //HOMING, AND OTHER COOL //
7175@@ -8176,12 +8091,6 @@ void P_PlayerThink(player_t *player)
7176 // The timer might've reached zero, but we'll run the remote view camera anyway by setting it to -1.
7177 }
7178
7179- /// \note do this in the cheat code
7180- if (player->pflags & PF_NOCLIP)
7181- player->mo->flags |= MF_NOCLIP;
7182- else
7183- player->mo->flags &= ~MF_NOCLIP;
7184-
7185 cmd = &player->cmd;
7186
7187 // SRB2kart
7188@@ -8414,26 +8323,7 @@ void P_PlayerThink(player_t *player)
7189 player->mo->reactiontime--;
7190 else if (player->mo->tracer && player->mo->tracer->type == MT_TUBEWAYPOINT)
7191 {
7192- // SRB2kart - don't need no rope hangin'
7193- //if (player->pflags & PF_ROPEHANG)
7194- //{
7195- // if (!P_AnalogMove(player))
7196- // player->mo->angle = (cmd->angleturn<<16 /* not FRACBITS */);
7197-
7198- // ticruned++;
7199- // if ((cmd->angleturn & TICCMD_RECEIVED) == 0)
7200- // ticmiss++;
7201-
7202- // P_DoRopeHang(player);
7203- // P_SetPlayerMobjState(player->mo, S_PLAY_CARRY);
7204- // P_DoJumpStuff(player, &player->cmd);
7205- //}
7206- //else
7207- {
7208- P_DoZoomTube(player);
7209- //if (!(player->panim == PA_ROLL) && player->charability2 == CA2_SPINDASH) // SRB2kart
7210- // P_SetPlayerMobjState(player->mo, S_PLAY_ATK1);
7211- }
7212+ P_DoZoomTube(player);
7213 player->rmomx = player->rmomy = 0; // no actual momentum from your controls
7214 P_ResetScore(player);
7215 }
7216diff --git a/src/st_stuff.c b/src/st_stuff.c
7217index db179545f..b15bb568a 100644
7218--- a/src/st_stuff.c
7219+++ b/src/st_stuff.c
7220@@ -1514,7 +1514,7 @@ static inline void ST_drawRaceHUD(void) // SRB2kart - unused.
7221 if (stplyr->exiting)
7222 V_DrawString(hudinfo[HUD_LAP].x, STRINGY(hudinfo[HUD_LAP].y), V_YELLOWMAP, "FINISHED!");
7223 else
7224- V_DrawString(hudinfo[HUD_LAP].x, STRINGY(hudinfo[HUD_LAP].y), 0, va("Lap: %u/%d", stplyr->laps+1, cv_numlaps.value));
7225+ V_DrawString(hudinfo[HUD_LAP].x, STRINGY(hudinfo[HUD_LAP].y), 0, va("Lap: %u/%d", stplyr->laps, cv_numlaps.value));
7226 }
7227 }
7228 */