diff --git a/src/Makefile b/src/Makefile index d0e66e269..00db0c55b 100644 --- a/src/Makefile +++ b/src/Makefile @@ -496,6 +496,9 @@ OBJS:=$(i_main_o) \ $(OBJDIR)/st_stuff.o \ $(OBJDIR)/k_kart.o \ $(OBJDIR)/k_pwrlv.o \ + $(OBJDIR)/k_waypoint.o\ + $(OBJDIR)/k_pathfind.o\ + $(OBJDIR)/k_bheap.o \ $(OBJDIR)/m_aatree.o \ $(OBJDIR)/m_anigif.o \ $(OBJDIR)/m_argv.o \ diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 6814bacce..545010112 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -397,6 +397,8 @@ consvar_t cv_kartdebugamount = {"kartdebugamount", "1", CV_NETVAR|CV_CHEAT|CV_NO consvar_t cv_kartdebugshrink = {"kartdebugshrink", "Off", CV_NETVAR|CV_CHEAT|CV_NOSHOWHELP, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_kartdebugdistribution = {"kartdebugdistribution", "Off", CV_NETVAR|CV_CHEAT|CV_NOSHOWHELP, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_kartdebughuddrop = {"kartdebughuddrop", "Off", CV_NETVAR|CV_CHEAT|CV_NOSHOWHELP, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; +static CV_PossibleValue_t kartdebugwaypoint_cons_t[] = {{0, "Off"}, {1, "Forwards"}, {2, "Backwards"}, {0, NULL}}; +consvar_t cv_kartdebugwaypoints = {"kartdebugwaypoints", "Off", CV_NETVAR|CV_CHEAT|CV_NOSHOWHELP, kartdebugwaypoint_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_kartdebugcheckpoint = {"kartdebugcheckpoint", "Off", CV_NOSHOWHELP, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_kartdebugnodes = {"kartdebugnodes", "Off", CV_NOSHOWHELP, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; @@ -1941,8 +1943,6 @@ void SendWeaponPref(void) buf[0] = 0; if (cv_flipcam.value) buf[0] |= 1; - if (cv_analog.value) - buf[0] |= 2; SendNetXCmd(XD_WEAPONPREF, buf, 1); } @@ -1953,8 +1953,6 @@ void SendWeaponPref2(void) buf[0] = 0; if (cv_flipcam2.value) buf[0] |= 1; - if (cv_analog2.value) - buf[0] |= 2; SendNetXCmd2(XD_WEAPONPREF, buf, 1); } @@ -1965,8 +1963,6 @@ void SendWeaponPref3(void) buf[0] = 0; if (cv_flipcam3.value) buf[0] |= 1; - if (cv_analog3.value) - buf[0] |= 2; SendNetXCmd3(XD_WEAPONPREF, buf, 1); } @@ -1977,8 +1973,6 @@ void SendWeaponPref4(void) buf[0] = 0; if (cv_flipcam4.value) buf[0] |= 1; - if (cv_analog4.value) - buf[0] |= 2; SendNetXCmd4(XD_WEAPONPREF, buf, 1); } @@ -1986,11 +1980,9 @@ static void Got_WeaponPref(UINT8 **cp,INT32 playernum) { UINT8 prefs = READUINT8(*cp); - players[playernum].pflags &= ~(PF_FLIPCAM|PF_ANALOGMODE); + players[playernum].pflags &= ~(PF_FLIPCAM); if (prefs & 1) players[playernum].pflags |= PF_FLIPCAM; - if (prefs & 2) - players[playernum].pflags |= PF_ANALOGMODE; } static void Got_PowerLevel(UINT8 **cp,INT32 playernum) @@ -3244,13 +3236,15 @@ static void Got_Respawn(UINT8 **cp, INT32 playernum) return; } - // incase the above checks were modified to allow sending a respawn on these occasions: - if (players[respawnplayer].mo && !P_IsObjectOnGround(players[respawnplayer].mo)) - return; - if (players[respawnplayer].mo) - P_DamageMobj(players[respawnplayer].mo, NULL, NULL, 10000); - demo_extradata[playernum] |= DXD_RESPAWN; + { + // incase the above checks were modified to allow sending a respawn on these occasions: + if (!P_IsObjectOnGround(players[respawnplayer].mo)) + return; + + K_DoIngameRespawn(&players[respawnplayer]); + demo_extradata[playernum] |= DXD_RESPAWN; + } } /** Deals with an ::XD_RANDOMSEED message in a netgame. diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 29cd68818..8238dea2b 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -127,6 +127,7 @@ extern consvar_t cv_votetime; extern consvar_t cv_kartdebugitem, cv_kartdebugamount, cv_kartdebugshrink, cv_kartdebugdistribution, cv_kartdebughuddrop; extern consvar_t cv_kartdebugcheckpoint, cv_kartdebugnodes, cv_kartdebugcolorize; +extern consvar_t cv_kartdebugwaypoints; extern consvar_t cv_itemfinder; diff --git a/src/d_player.h b/src/d_player.h index 697c0356c..07e0f7a97 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -29,6 +29,9 @@ // as commands per game tick. #include "d_ticcmd.h" +// the player struct stores a waypoint for racing +#include "k_waypoint.h" + // Extra abilities/settings for skins (combinable stuff) typedef enum { @@ -118,7 +121,7 @@ typedef enum /*** misc ***/ PF_FORCESTRAFE = 1<<29, // Turning inputs are translated into strafing inputs - PF_ANALOGMODE = 1<<30, // Analog mode? + PF_HITFINISHLINE = 1<<30, // Already hit the finish line this tic // free: 1<<30 and 1<<31 } pflags_t; @@ -240,10 +243,6 @@ typedef enum k_position, // Used for Kart positions, mostly for deterministic stuff k_oldposition, // Used for taunting when you pass someone k_positiondelay, // Used for position number, so it can grow when passing/being passed - k_prevcheck, // Previous checkpoint distance; for p_user.c (was "pw_pcd") - k_nextcheck, // Next checkpoint distance; for p_user.c (was "pw_ncd") - k_waypoint, // Waypoints. - k_starpostwp, // Temporarily stores player waypoint for... some reason. Used when respawning and finishing. k_starpostflip, // the last starpost we hit requires flipping? k_respawn, // Timer for the DEZ laser respawn effect k_dropdash, // Charge up for respawn Drop Dash @@ -331,6 +330,7 @@ typedef enum k_springstars, // Spawn stars around a player when they hit a spring k_springcolor, // Color of spring stars k_killfield, // How long have you been in the kill field, stay in too long and lose a bumper + k_wrongway, // Display WRONG WAY on screen NUMKARTSTUFF } kartstufftype_t; @@ -436,6 +436,8 @@ typedef struct player_s angle_t frameangle; // for the player add the ability to have the sprite only face other angles INT16 lturn_max[MAXPREDICTTICS]; // What's the expected turn value for full-left for a number of frames back (to account for netgame latency)? INT16 rturn_max[MAXPREDICTTICS]; // Ditto but for full-right + UINT32 distancetofinish; + waypoint_t *nextwaypoint; // Bit flags. // See pflags_t, above. diff --git a/src/dehacked.c b/src/dehacked.c index 5789396b1..a9d46b2a4 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -6660,6 +6660,11 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit // DEZ respawn laser "S_DEZLASER", + "S_DEZLASER_TRAIL1", + "S_DEZLASER_TRAIL2", + "S_DEZLASER_TRAIL3", + "S_DEZLASER_TRAIL4", + "S_DEZLASER_TRAIL5", // Audience Members "S_RANDOMAUDIENCE", @@ -8180,7 +8185,7 @@ static const char *const PLAYERFLAG_LIST[] = { /*** misc ***/ "FORCESTRAFE", // Translate turn inputs into strafe inputs - "ANALOGMODE", // Analog mode? + "HITFINISHLINE", // Already hit the finish line this tic NULL // stop loop here. }; @@ -8417,10 +8422,6 @@ static const char *const KARTSTUFF_LIST[] = { "POSITION", "OLDPOSITION", "POSITIONDELAY", - "PREVCHECK", - "NEXTCHECK", - "WAYPOINT", - "STARPOSTWP", "STARPOSTFLIP", "RESPAWN", "DROPDASH", @@ -8502,7 +8503,8 @@ static const char *const KARTSTUFF_LIST[] = { "TIREGREASE", "SPRINGSTARS", "SPRINGCOLOR", - "KILLFIELD" + "KILLFIELD", + "WRONGWAY" }; #endif diff --git a/src/doomdata.h b/src/doomdata.h index 6319238b7..aa4ea1a54 100644 --- a/src/doomdata.h +++ b/src/doomdata.h @@ -46,6 +46,9 @@ enum ML_BLOCKMAP, // LUT, motion clipping, walls/grid element }; +// Extra flag for objects +#define MTF_EXTRA 1 + // Reverse gravity flag for objects. #define MTF_OBJECTFLIP 2 diff --git a/src/g_game.c b/src/g_game.c index b486ddecf..de4162f81 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2580,7 +2580,6 @@ void G_PlayerReborn(INT32 player) SINT8 pity; // SRB2kart - INT32 starpostwp; INT32 itemtype; INT32 itemamount; INT32 itemroulette; @@ -2602,7 +2601,7 @@ void G_PlayerReborn(INT32 player) jointime = players[player].jointime; splitscreenindex = players[player].splitscreenindex; spectator = players[player].spectator; - pflags = (players[player].pflags & (PF_TIMEOVER|PF_FLIPCAM|PF_TAGIT|PF_TAGGED|PF_ANALOGMODE|PF_WANTSTOJOIN)); + pflags = (players[player].pflags & (PF_TIMEOVER|PF_FLIPCAM|PF_TAGIT|PF_TAGGED|PF_WANTSTOJOIN)); // As long as we're not in multiplayer, carry over cheatcodes from map to map if (!(netgame || multiplayer)) @@ -2644,12 +2643,9 @@ void G_PlayerReborn(INT32 player) rings = (G_BattleGametype() ? 0 : 5); comebackpoints = 0; wanted = 0; - starpostwp = 0; } else { - starpostwp = players[player].kartstuff[k_starpostwp]; - itemroulette = (players[player].kartstuff[k_itemroulette] > 0 ? 1 : 0); roulettetype = players[player].kartstuff[k_roulettetype]; @@ -2716,7 +2712,6 @@ void G_PlayerReborn(INT32 player) p->pity = pity; // SRB2kart - p->kartstuff[k_starpostwp] = starpostwp; // TODO: get these out of kartstuff, it causes desync (Does it...?) p->kartstuff[k_itemroulette] = itemroulette; p->kartstuff[k_roulettetype] = roulettetype; p->kartstuff[k_itemtype] = itemtype; @@ -3252,7 +3247,8 @@ void G_DoReborn(INT32 playernum) // respawn at the start mobj_t *oldmo = NULL; - if (player->starpostnum || ((mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE) && player->laps)) // SRB2kart + // Now only respawn at the start if you haven't crossed it at all + if (player->laps) // SRB2kart starpost = true; // first dissasociate the corpse @@ -4943,7 +4939,10 @@ void G_ReadDemoExtraData(void) if (extradata & DXD_RESPAWN) { if (players[p].mo) - P_DamageMobj(players[p].mo, NULL, NULL, 10000); // Is this how this should work..? + { + // Is this how this should work..? + K_DoIngameRespawn(&players[p]); + } } if (extradata & DXD_SKIN) { diff --git a/src/info.c b/src/info.c index 135af682e..cdccdb420 100644 --- a/src/info.c +++ b/src/info.c @@ -2876,7 +2876,12 @@ state_t states[NUMSTATES] = {SPR_KBLN, FF_FULLBRIGHT|1, -1, {NULL}, 0, 0, S_BATTLEBUMPER2}, // S_BATTLEBUMPER2 {SPR_KBLN, FF_FULLBRIGHT|2, -1, {NULL}, 0, 0, S_BATTLEBUMPER3}, // S_BATTLEBUMPER3 - {SPR_DEZL, FF_FULLBRIGHT|FF_PAPERSPRITE, 8, {NULL}, 0, 0, S_NULL}, // S_DEZLASER + {SPR_DEZL, FF_FULLBRIGHT|FF_PAPERSPRITE, 8, {NULL}, 0, 0, S_NULL}, // S_DEZLASER + {SPR_DEZL, FF_FULLBRIGHT|1, 2, {NULL}, 0, 0, S_DEZLASER_TRAIL2}, // S_DEZLASER_TRAIL1 + {SPR_DEZL, FF_FULLBRIGHT|2, 2, {NULL}, 0, 0, S_DEZLASER_TRAIL3}, // S_DEZLASER_TRAIL2 + {SPR_DEZL, FF_FULLBRIGHT|FF_PAPERSPRITE|3, 4, {NULL}, 0, 0, S_DEZLASER_TRAIL4}, // S_DEZLASER_TRAIL3 + {SPR_DEZL, FF_FULLBRIGHT|2, 2, {NULL}, 0, 0, S_DEZLASER_TRAIL5}, // S_DEZLASER_TRAIL4 + {SPR_DEZL, FF_FULLBRIGHT|1, 2, {NULL}, 0, 0, S_NULL}, // S_DEZLASER_TRAIL5 {SPR_NULL, 0, 1, {A_RandomStateRange}, S_AUDIENCE_CHAO_CHEER1, S_AUDIENCE_CHAO_CHEER2, S_RANDOMAUDIENCE}, // S_RANDOMAUDIENCE @@ -16076,7 +16081,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_SPB_DEAD, // deathstate S_NULL, // xdeathstate sfx_s3k5d, // deathsound - 64*FRACUNIT, // speed + 80*FRACUNIT, // speed 24*FRACUNIT, // radius 48*FRACUNIT, // height 0, // display offset @@ -16278,7 +16283,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { // MT_WAYPOINT 2001, // doomednum - S_NULL, // spawnstate + S_INVISIBLE, // spawnstate 1000, // spawnhealth S_NULL, // seestate sfx_None, // seesound @@ -16299,7 +16304,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 0, // damage sfx_None, // activesound - MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY, // flags + MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags S_NULL // raisestate }, diff --git a/src/info.h b/src/info.h index a20875c68..95ae5c225 100644 --- a/src/info.h +++ b/src/info.h @@ -3552,6 +3552,11 @@ typedef enum state // DEZ Laser respawn S_DEZLASER, + S_DEZLASER_TRAIL1, + S_DEZLASER_TRAIL2, + S_DEZLASER_TRAIL3, + S_DEZLASER_TRAIL4, + S_DEZLASER_TRAIL5, // Audience Members S_RANDOMAUDIENCE, diff --git a/src/k_bheap.c b/src/k_bheap.c new file mode 100644 index 000000000..40c652b5e --- /dev/null +++ b/src/k_bheap.c @@ -0,0 +1,595 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 by Sean "Sryder" Ryder +// Copyright (C) 2018-2020 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file k_bheap.c +/// \brief Binary Heap implementation for SRB2 code base. + +#include "k_bheap.h" + +#include "z_zone.h" + +/*-------------------------------------------------- + static boolean K_BHeapItemValidate(bheap_t *heap, bheapitem_t *item) + + Validates an item on a heap to ensure it is correct and on that heap. + + Input Arguments:- + heap - The heap to validate the item with + item - The item to validate + + Return:- + True if the item is valid, false if it isn't. +--------------------------------------------------*/ +static boolean K_BHeapItemValidate(bheap_t *heap, bheapitem_t *item) +{ + boolean heapitemvalid = false; + + I_Assert(heap != NULL); + I_Assert(item != NULL); + + if ((item->data != NULL) && (item->heapindex < SIZE_MAX / 2) && (item->owner == heap)) + { + heapitemvalid = true; + } + + return heapitemvalid; +} + +/*-------------------------------------------------- + static bheapitem_t *K_BHeapItemsCompare(bheap_t *heap, bheapitem_t *item1, bheapitem_t *item2) + + Compares 2 items in the heap to find the better (lower) value one. + + Input Arguments:- + heap - The heap to compare items + item1 - The first item to compare + item2 - The second item to compare + + Return:- + The item out of the 2 sent in that has the better value, returns item2 if they are identical +--------------------------------------------------*/ +static bheapitem_t *K_BHeapItemsCompare(bheap_t *heap, bheapitem_t *item1, bheapitem_t *item2) +{ + bheapitem_t *lowervalueitem = NULL; + + I_Assert(heap != NULL); + I_Assert(K_BHeapValid(heap)); + I_Assert(item1 != NULL); + I_Assert(item2 != NULL); + I_Assert(K_BHeapItemValidate(heap, item1)); + I_Assert(K_BHeapItemValidate(heap, item2)); + + (void)heap; + + if (item1->value < item2->value) + { + lowervalueitem = item1; + } + else + { + lowervalueitem = item2; + } + + return lowervalueitem; +} + +/*-------------------------------------------------- + static void K_BHeapSwapItems(bheap_t *heap, bheapitem_t *item1, bheapitem_t *item2) + + Swaps 2 items in the heap + + Input Arguments:- + heap - The heap to swap items in + item1 - The first item to swap in the heap + item2 - The second item to swap in the heap + + Return:- + None +--------------------------------------------------*/ +static void K_BHeapSwapItems(bheap_t *heap, bheapitem_t *item1, bheapitem_t *item2) +{ + I_Assert(heap != NULL); + I_Assert(K_BHeapValid(heap)); + I_Assert(item1 != NULL); + I_Assert(item2 != NULL); + I_Assert(K_BHeapItemValidate(heap, item1)); + I_Assert(K_BHeapItemValidate(heap, item2)); + + (void)heap; + + { + size_t tempitemindex = item1->heapindex; + bheapitem_t tempitemstore = *item1; + + // Swap the items fully with each other + *item1 = *item2; + *item2 = tempitemstore; + + // Swap the heap index on each item to be correct + item2->heapindex = item1->heapindex; + item1->heapindex = tempitemindex; + + if (item1->indexchanged != NULL) + { + item1->indexchanged(item1->data, item1->heapindex); + } + if (item2->indexchanged != NULL) + { + item2->indexchanged(item2->data, item2->heapindex); + } + } +} + +/*-------------------------------------------------- + static size_t K_BHeapItemGetParentIndex(bheapitem_t *item) + + Gets the parent index of a heap item + + Input Arguments:- + item - The item to get the parent index of + + Return:- + The parent index of the item +--------------------------------------------------*/ +static size_t K_BHeapItemGetParentIndex(bheapitem_t *item) +{ + size_t parentindex = SIZE_MAX; + + I_Assert(item != NULL); + I_Assert(item->heapindex < (SIZE_MAX / 2)); + + parentindex = (item->heapindex - 1U) / 2U; + + return parentindex; +} + +/*-------------------------------------------------- + static size_t K_BHeapItemGetLeftChildIndex(bheapitem_t *item) + + Gets the left child index of a heap item + + Input Arguments:- + item - The item to get the left child index of + + Return:- + The left child index of the item +--------------------------------------------------*/ +static size_t K_BHeapItemGetLeftChildIndex(bheapitem_t *item) +{ + size_t leftchildindex = SIZE_MAX; + + I_Assert(item != NULL); + I_Assert(item->heapindex < (SIZE_MAX / 2)); + + leftchildindex = (item->heapindex * 2U) + 1U; + + return leftchildindex; +} + +/*-------------------------------------------------- + static size_t K_BHeapItemGetRightChildIndex(bheapitem_t *item) + + Gets the right child index of a heap item + + Input Arguments:- + item - The item to get the right child index of + + Return:- + The right child index of the item +--------------------------------------------------*/ +static size_t K_BHeapItemGetRightChildIndex(bheapitem_t *item) +{ + size_t rightchildindex = SIZE_MAX; + + I_Assert(item != NULL); + I_Assert(item->heapindex < (SIZE_MAX / 2)); + + rightchildindex = (item->heapindex * 2U) + 2U; + + return rightchildindex; +} + +/*-------------------------------------------------- + static void K_BHeapSortUp(bheap_t *heap, bheapitem_t *item) + + Sorts a heapitem up the list to its correct index, lower value items are higher up + + Input Arguments:- + heap - The heap to sort the item up. + item - The item to sort up the heap + + Return:- + None +--------------------------------------------------*/ +static void K_BHeapSortUp(bheap_t *heap, bheapitem_t *item) +{ + I_Assert(heap != NULL); + I_Assert(K_BHeapValid(heap)); + I_Assert(item != NULL); + + if (item->heapindex > 0U) + { + size_t parentindex = SIZE_MAX; + do + { + parentindex = K_BHeapItemGetParentIndex(item); + + // Swap the nodes if the parent has a higher value + if (K_BHeapItemsCompare(heap, item, &heap->array[parentindex]) == item) + { + K_BHeapSwapItems(heap, item, &heap->array[parentindex]); + } + else + { + break; + } + } while (parentindex > 0U); + } +} + +/*-------------------------------------------------- + static void K_BHeapSortDown(bheap_t *heap, bheapitem_t *item) + + Sorts a heapitem down the list to its correct index, higher value items are further down + + Input Arguments:- + heap - The heap to sort the item down. + item - The item to sort down the heap + + Return:- + None +--------------------------------------------------*/ +static void K_BHeapSortDown(bheap_t *heap, bheapitem_t *item) +{ + I_Assert(heap != NULL); + I_Assert(K_BHeapValid(heap)); + I_Assert(item != NULL); + + if (heap->count > 0U) + { + size_t leftchildindex = SIZE_MAX; + size_t rightchildindex = SIZE_MAX; + bheapitem_t *leftchild = NULL; + bheapitem_t *rightchild = NULL; + bheapitem_t *swapchild = NULL; + boolean noswapneeded = false; + + do + { + leftchildindex = K_BHeapItemGetLeftChildIndex(item); + rightchildindex = K_BHeapItemGetRightChildIndex(item); + + if (leftchildindex < heap->count) + { + leftchild = &heap->array[leftchildindex]; + swapchild = leftchild; + if (rightchildindex < heap->count) + { + rightchild = &heap->array[rightchildindex]; + // Choose the lower child node to swap with + if (K_BHeapItemsCompare(heap, leftchild, rightchild) == rightchild) + { + swapchild = rightchild; + } + } + + // Swap with the lower child, if it's lower than item + if (K_BHeapItemsCompare(heap, swapchild, item) == swapchild) + { + K_BHeapSwapItems(heap, item, swapchild); + } + else + { + noswapneeded = true; + } + } + else + { + noswapneeded = true; + } + + if (noswapneeded) + { + break; + } + + } while (item->heapindex < (heap->count - 1U)); + } +} + +/*-------------------------------------------------- + boolean K_BHeapInit(bheap_t *const heap, size_t initialcapacity) + + See header file for description. +--------------------------------------------------*/ +boolean K_BHeapInit(bheap_t *const heap, size_t initialcapacity) +{ + boolean initsuccess = false; + + if (heap == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL heap in K_BHeapInit.\n"); + } + else if (initialcapacity == 0U) + { + CONS_Debug(DBG_GAMELOGIC, "initialcapacity is 0 in K_BHeapInit.\n"); + } + else + { + heap->array = Z_Calloc(initialcapacity * sizeof(bheapitem_t), PU_STATIC, NULL); + + if (heap->array == NULL) + { + I_Error("K_BHeapInit: Out of Memory."); + } + + heap->capacity = initialcapacity; + heap->count = 0U; + + initsuccess = true; + } + + return initsuccess; +} + +/*-------------------------------------------------- + boolean K_BHeapValid(bheap_t *const heap) + + See header file for description. +--------------------------------------------------*/ +boolean K_BHeapValid(bheap_t *const heap) +{ + boolean heapvalid = false; + + if (heap == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL heap in K_BHeapValid.\n"); + } + else + { + if ((heap->capacity > 0U) && (heap->array != NULL)) + { + heapvalid = true; + } + } + + return heapvalid; +} + +/*-------------------------------------------------- + boolean K_BHeapPush(bheap_t *const heap, void *const item, UINT32 value, updateindexfunc changeindexcallback) + + See header file for description. +--------------------------------------------------*/ +boolean K_BHeapPush(bheap_t *const heap, void *const item, UINT32 value, updateindexfunc changeindexcallback) +{ + boolean pushsuccess = false; + if (heap == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL heap in K_BHeapPush.\n"); + } + else if (!K_BHeapValid(heap)) + { + CONS_Debug(DBG_GAMELOGIC, "Uninitialised heap in K_BHeapPush.\n"); + } + else if (item == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL item in K_BHeapPush.\n"); + } + else if (heap->count >= (SIZE_MAX / 2)) + { + CONS_Debug(DBG_GAMELOGIC, "Tried to push too many items on binary heap in K_BHeapPush.\n"); + } + else + { + bheapitem_t *newitem = NULL; + // If the capacity of the heap has been reached, a realloc is needed + // I'm just doing a basic double of capacity for simplicity + if (heap->count >= heap->capacity) + { + size_t newarraycapacity = heap->capacity * 2; + heap->array = Z_Realloc(heap->array, newarraycapacity, PU_STATIC, NULL); + + if (heap->array == NULL) + { + I_Error("K_BHeapPush: Out of Memory."); + } + + heap->capacity = newarraycapacity; + } + + newitem = &heap->array[heap->count]; + + newitem->heapindex = heap->count; + newitem->owner = heap; + newitem->data = item; + newitem->value = value; + newitem->indexchanged = changeindexcallback; + + if (newitem->indexchanged != NULL) + { + newitem->indexchanged(newitem->data, newitem->heapindex); + } + + heap->count++; + + K_BHeapSortUp(heap, &heap->array[heap->count - 1U]); + + pushsuccess = true; + } + + return pushsuccess; +} + +/*-------------------------------------------------- + boolean K_BHeapPop(bheap_t *const heap, bheapitem_t *const returnitemstorage) + + See header file for description. +--------------------------------------------------*/ +boolean K_BHeapPop(bheap_t *const heap, bheapitem_t *const returnitemstorage) +{ + boolean popsuccess = false; + if (heap == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL heap in K_BHeapPop.\n"); + } + else if (!K_BHeapValid(heap)) + { + CONS_Debug(DBG_GAMELOGIC, "Uninitialised heap in K_BHeapPop.\n"); + } + else if (heap->count == 0U) + { + CONS_Debug(DBG_GAMELOGIC, "Tried to Pop from empty heap in K_BHeapPop.\n"); + } + else if (returnitemstorage == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL returnitemstorage in K_BHeapPop.\n"); + } + else + { + *returnitemstorage = heap->array[0]; + + // Invalidate the heap related data from the return item + returnitemstorage->owner = NULL; + returnitemstorage->heapindex = SIZE_MAX; + + if (returnitemstorage->indexchanged != NULL) + { + returnitemstorage->indexchanged(returnitemstorage->data, returnitemstorage->heapindex); + } + + heap->count--; + + heap->array[0] = heap->array[heap->count]; + heap->array[0].heapindex = 0U; + memset(&heap->array[heap->count], 0x00, sizeof(bheapitem_t)); + + K_BHeapSortDown(heap, &heap->array[0]); + popsuccess = true; + } + + return popsuccess; +} + +/*-------------------------------------------------- + boolean K_UpdateBHeapItemValue(bheapitem_t *const item, const UINT32 newvalue) + + See header file for description. +--------------------------------------------------*/ +boolean K_UpdateBHeapItemValue(bheapitem_t *const item, const UINT32 newvalue) +{ + boolean updatevaluesuccess = false; + if (item == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL item in K_UpdateHeapItemValue.\n"); + } + else if (item->owner == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "item has NULL owner in K_UpdateHeapItemValue.\n"); + } + else if (K_BHeapItemValidate(item->owner, item) == false) + { + CONS_Debug(DBG_GAMELOGIC, "Invalid item in K_UpdateHeapItemValue.\n"); + } + else if (K_BHeapValid(item->owner) == false) + { + CONS_Debug(DBG_GAMELOGIC, "Invalid item owner in K_UpdateHeapItemValue.\n"); + } + else + { + size_t oldvalue = item->value; + item->value = newvalue; + if (newvalue < oldvalue) + { + K_BHeapSortUp(item->owner, item); + } + else if (newvalue > oldvalue) + { + K_BHeapSortDown(item->owner, item); + } + else + { + // No change is needed as the value is the same + } + + } + + return updatevaluesuccess; +} + +/*-------------------------------------------------- + size_t K_BHeapContains(bheap_t *const heap, void *const data, size_t index) + + See header file for description. +--------------------------------------------------*/ +size_t K_BHeapContains(bheap_t *const heap, void *const data, size_t index) +{ + size_t heapindexwithdata = SIZE_MAX; + + if (heap == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL heap in K_BHeapContains.\n"); + } + else if (!K_BHeapValid(heap)) + { + CONS_Debug(DBG_GAMELOGIC, "Uninitialised heap in K_BHeapContains.\n"); + } + else if (data == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL data in K_BHeapContains.\n"); + } + else + { + if ((heap->count != 0U) && (index < heap->count)) + { + if (heap->array[index].data == data) + { + heapindexwithdata = index; + } + } + else if (index == SIZE_MAX) + { + size_t i; + for (i = 0; i < heap->count; i++) + { + if (heap->array[i].data == data) + { + heapindexwithdata = i; + break; + } + } + } + } + + return heapindexwithdata; +} + +boolean K_BHeapFree(bheap_t *const heap) +{ + boolean freesuccess = false; + + if (heap == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL heap in K_BHeapFree.\n"); + } + else if (!K_BHeapValid(heap)) + { + CONS_Debug(DBG_GAMELOGIC, "Uninitialised heap in K_BHeapFree.\n"); + } + else + { + Z_Free(heap->array); + heap->array = NULL; + heap->capacity = 0U; + heap->count = 0U; + freesuccess = true; + } + + return freesuccess; +} diff --git a/src/k_bheap.h b/src/k_bheap.h new file mode 100644 index 000000000..04e37492c --- /dev/null +++ b/src/k_bheap.h @@ -0,0 +1,153 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 by Sean "Sryder" Ryder +// Copyright (C) 2018-2020 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file k_bheap.h +/// \brief Binary Heap implementation for SRB2 code base. + +#ifndef __K_BHEAP__ +#define __K_BHEAP__ + +#include "doomdef.h" + +typedef void(*updateindexfunc)(void *const, const size_t); + +typedef struct bheapitem_s +{ + size_t heapindex; // The index in the heap this item is + updateindexfunc indexchanged; // A callback function that is called when this item changes index to alert data + struct bheap_s *owner; // The heap that owns this item + void *data; // data for this heap item + UINT32 value; // The value of this item, the lowest value item is first in the array +} bheapitem_t; + +typedef struct bheap_s +{ + size_t capacity; // capacity of the heap + size_t count; // number of items in the heap + bheapitem_t *array; // pointer to the heap items array +} bheap_t; + + +/*-------------------------------------------------- + boolean K_BHeapInit(bheap_t *const heap, size_t initialcapacity) + + Initialises a binary heap. + + Input Arguments:- + heap - The heap to initialise + initialcapacity - The initial capacity the heap should hold + + Return:- + True if the initialisation was successful, false if it wasn't. +--------------------------------------------------*/ + +boolean K_BHeapInit(bheap_t *const heap, size_t initialcapacity); + + +/*-------------------------------------------------- + boolean K_BHeapValid(bheap_t *const heap) + + Checks a binary heap for validity + + Input Arguments:- + heap - The heap to validate + + Return:- + True if the binary heap is valid, false if it isn't +--------------------------------------------------*/ + +boolean K_BHeapValid(bheap_t *const heap); + + +/*-------------------------------------------------- + boolean K_BHeapPush(bheap_t *const heap, void *const item, const UINT32 value, updateindexfunc changeindexcallback) + + Adds a new item to a binary heap. + + Input Arguments:- + heap - The heap to add to. + item - The item to add to the heap. + value - The value of this item for the heap, lowest is first in the heap + changeindexcallback - A callback function that is called when the item's index changes, can be NULL + + Return:- + True if the push to the heap was successful, false if it wasn't due to invalid parameters +--------------------------------------------------*/ + +boolean K_BHeapPush(bheap_t *const heap, void *const item, UINT32 value, updateindexfunc changeindexcallback); + + +/*-------------------------------------------------- + boolean K_BHeapPop(bheap_t *const heap, bheapitem_t *const returnitemstorage) + + Pops the first item off of the heap, then orders it back to be correct. + + Input Arguments:- + heap - The heap to pop from. + returnitemstorage - The first item on the Heap is placed in here + + Return:- + true if the pop from the heap was successful, false if it wasn't. +--------------------------------------------------*/ + +boolean K_BHeapPop(bheap_t *const heap, bheapitem_t *const returnitemstorage); + + +/*-------------------------------------------------- + boolean K_UpdateBHeapItemValue(bheapitem_t *const item, const UINT32 newvalue) + + Updates the heap item's value, and reorders it in the array appropriately. Only works if the item is in a heap + validly. If it's a heapitem that is not currently in a heap (ie it's been popped off) just change the value + manually. + + Input Arguments:- + item - The item to update the value of. + newvalue - The new value the item will hold + + Return:- + true if the update was successful, false if it wasn't +--------------------------------------------------*/ + +boolean K_UpdateBHeapItemValue(bheapitem_t *const item, const UINT32 newvalue); + + +/*-------------------------------------------------- + size_t K_BHeapContains(bheap_t *const heap, void *const data, size_t index) + + Checks to see if data is contained in the heap. If index is not SIZE_MAX, then only the index sent in is + checked. Otherwise every index is checked linearly. + + Input Arguments:- + heap - The heap to check the contents of + data - The data that is being checked for + index - The index of the heap to check, if SIZE_MAX, check every index + + Return:- + The heap index that contains data, SIZE_MAX if it is not in the heap +--------------------------------------------------*/ + +size_t K_BHeapContains(bheap_t *const heap, void *const data, size_t index); + + +/*-------------------------------------------------- + boolean K_BHeapFree(bheap_t *const heap) + + Free the binary heap. + This does NOT free the data held within the binary heap items. Make sure those can still be freed manually. + + Input Arguments:- + heap - The heap to free + + Return:- + True if the heap was freed successfully, false if the heap wasn't valid to free +--------------------------------------------------*/ + +boolean K_BHeapFree(bheap_t *const heap); + +#endif diff --git a/src/k_kart.c b/src/k_kart.c index 45260521f..fd5ca4859 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -12,6 +12,7 @@ #include "m_random.h" #include "p_local.h" #include "p_slopes.h" +#include "p_setup.h" #include "r_draw.h" #include "r_local.h" #include "s_sound.h" @@ -24,6 +25,8 @@ #include "lua_hud.h" // For Lua hud checks #include "lua_hook.h" // For MobjDamage and ShouldDamage +#include "k_waypoint.h" + // SOME IMPORTANT VARIABLES DEFINED IN DOOMDEF.H: // gamespeed is cc (0 for easy, 1 for normal, 2 for hard) // franticitems is Frantic Mode items, bool @@ -587,6 +590,7 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_kartdebugshrink); CV_RegisterVar(&cv_kartdebugdistribution); CV_RegisterVar(&cv_kartdebughuddrop); + CV_RegisterVar(&cv_kartdebugwaypoints); CV_RegisterVar(&cv_kartdebugcheckpoint); CV_RegisterVar(&cv_kartdebugnodes); @@ -706,7 +710,7 @@ static INT32 K_KartItemOddsBattle[NUMKARTRESULTS][6] = /*Jawz x2*/ { 0, 0, 1, 2, 4, 2 } // Jawz x2 }; -#define DISTVAR (64*14) +#define DISTVAR (2048) // Magic number distance for use with item roulette tiers /** \brief Item Roulette for Kart @@ -845,9 +849,7 @@ static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean sp if (first != -1 && second != -1) // calculate 2nd's distance from 1st, for SPB { - secondist = P_AproxDistance(P_AproxDistance(players[first].mo->x - players[second].mo->x, - players[first].mo->y - players[second].mo->y), - players[first].mo->z - players[second].mo->z) / mapobjectscale; + secondist = players[second].distancetofinish - players[first].distancetofinish; if (franticitems) secondist = (15 * secondist) / 14; secondist = ((28 + (8-pingame)) * secondist) / 28; @@ -896,7 +898,8 @@ static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean sp POWERITEMODDS(newodds); break; case KITEM_SPB: - if ((indirectitemcooldown > 0) || (pexiting > 0) || (secondist/DISTVAR < 3)) + if ((indirectitemcooldown > 0) || (secondist/DISTVAR < 3) + || (first != -1 && players[first].distancetofinish > 8*DISTVAR)) // No SPB near the end of the race newodds = 0; else newodds *= min((secondist/DISTVAR)-4, 3); // POWERITEMODDS(newodds); @@ -925,13 +928,13 @@ static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean sp return newodds; } -//{ SRB2kart Roulette Code - Distance Based, no waypoints +//{ SRB2kart Roulette Code - Distance Based, yes waypoints -static INT32 K_FindUseodds(player_t *player, fixed_t mashed, INT32 pdis, INT32 bestbumper, boolean spbrush) +static UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper, boolean spbrush) { - INT32 i; - INT32 n = 0; - INT32 useodds = 0; + UINT8 i; + UINT8 n = 0; + UINT8 useodds = 0; UINT8 disttable[14]; UINT8 totallen = 0; UINT8 distlen = 0; @@ -939,7 +942,7 @@ static INT32 K_FindUseodds(player_t *player, fixed_t mashed, INT32 pdis, INT32 b for (i = 0; i < 8; i++) { - INT32 j; + UINT8 j; boolean available = false; if (G_BattleGametype() && i > 5) @@ -1000,9 +1003,9 @@ static INT32 K_FindUseodds(player_t *player, fixed_t mashed, INT32 pdis, INT32 b SETUPDISTTABLE(6,3); SETUPDISTTABLE(7,1); - if (pdis <= 0) // (64*14) * 0 = 0 + if (pdis == 0) useodds = disttable[0]; - else if (pdis > DISTVAR * ((12 * distlen) / 14)) // (64*14) * 12 = 10752 + else if (pdis > DISTVAR * ((12 * distlen) / 14)) useodds = disttable[distlen-1]; else { @@ -1027,11 +1030,11 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) INT32 i; UINT8 pingame = 0; UINT8 roulettestop; - INT32 pdis = 0; - INT32 useodds = 0; + UINT32 pdis = 0; + UINT8 useodds = 0; INT32 spawnchance[NUMKARTRESULTS]; INT32 totalspawnchance = 0; - INT32 bestbumper = 0; + UINT8 bestbumper = 0; fixed_t mashed = 0; boolean dontforcespb = false; boolean spbrush = false; @@ -1054,6 +1057,10 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) bestbumper = players[i].kartstuff[k_bumper]; } + // No forced SPB in 1v1s, it has to be randomly rolled + if (pingame <= 2) + dontforcespb = true; + // This makes the roulette produce the random noises. if ((player->kartstuff[k_itemroulette] % 3) == 1 && P_IsDisplayPlayer(player)) { @@ -1085,15 +1092,18 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) for (i = 0; i < MAXPLAYERS; i++) { - if (playeringame[i] && !players[i].spectator && players[i].mo - && players[i].kartstuff[k_position] < player->kartstuff[k_position]) - pdis += P_AproxDistance(P_AproxDistance(players[i].mo->x - player->mo->x, - players[i].mo->y - player->mo->y), - players[i].mo->z - player->mo->z) / mapobjectscale - * (pingame - players[i].kartstuff[k_position]) - / max(1, ((pingame - 1) * (pingame + 1) / 3)); + if (playeringame[i] && !players[i].spectator + && players[i].kartstuff[k_position] == 1) + { + // This player is first! Yay! + pdis = player->distancetofinish - players[i].distancetofinish; + break; + } } + if (mapobjectscale != FRACUNIT) + pdis = FixedDiv(pdis, mapobjectscale); + if (franticitems) // Frantic items make the distances between everyone artifically higher, for crazier items pdis = (15 * pdis) / 14; @@ -1905,6 +1915,121 @@ static void K_SpawnBrakeDriftSparks(player_t *player) // Be sure to update the m sparks->flags2 |= MF2_DONTDRAW; } +/** \brief Preps a player to respawn + + \param player player to respawn + + \return void +*/ +void K_DoIngameRespawn(player_t *player) +{ + if (!player->mo || P_MobjWasRemoved(player->mo)) + return; + + if (player->kartstuff[k_respawn]) + return; + + if (leveltime <= starttime) + return; + + if (player->nextwaypoint == NULL) // Starpost xyz not initalized(?) + { + UINT32 bestdist = UINT32_MAX; + mapthing_t *beststart = NULL; + UINT8 numstarts = 0; + + if (G_RaceGametype()) + { + numstarts = numcoopstarts; + } + else if (G_BattleGametype()) + { + numstarts = numdmstarts; + } + + if (numstarts > 0) + { + UINT8 i = 0; + + for (i = 0; i < numstarts; i++) + { + UINT32 dist = UINT32_MAX; + mapthing_t *checkstart = NULL; + + if (G_RaceGametype()) + { + checkstart = playerstarts[i]; + } + else if (G_BattleGametype()) + { + checkstart = deathmatchstarts[i]; + } + else + { + break; + } + + dist = (UINT32)P_AproxDistance((player->mo->x >> FRACBITS) - checkstart->x, + (player->mo->y >> FRACBITS) - checkstart->y); + + if (dist < bestdist) + { + beststart = checkstart; + bestdist = dist; + } + } + } + + if (beststart == NULL) + { + CONS_Alert(CONS_WARNING, "No respawn points!\n"); + } + else + { + sector_t *s; + fixed_t z = (beststart->options >> ZSHIFT); + + player->starpostx = beststart->x; + player->starposty = beststart->y; + s = R_PointInSubsector(beststart->x << FRACBITS, beststart->y << FRACBITS)->sector; + + if (beststart->options & MTF_OBJECTFLIP) + { + player->starpostz = ( +#ifdef ESLOPE + s->c_slope ? P_GetZAt(s->c_slope, beststart->x << FRACBITS, beststart->y << FRACBITS) : +#endif + s->ceilingheight) >> FRACBITS; + + if (z) + player->starpostz -= z; + + player->starpostz -= mobjinfo[MT_PLAYER].height; + player->kartstuff[k_starpostflip] = 1; + } + else + { + player->starpostz = ( +#ifdef ESLOPE + s->f_slope ? P_GetZAt(s->f_slope, beststart->x << FRACBITS, beststart->y << FRACBITS) : +#endif + s->floorheight) >> FRACBITS; + + if (z) + player->starpostz += z; + + player->kartstuff[k_starpostflip] = 0; + } + } + } + + player->mo->flags &= ~(MF_SOLID|MF_SHOOTABLE); + player->mo->flags |= MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY; + player->mo->flags2 &= ~MF2_DONTDRAW; + + player->kartstuff[k_respawn] = 48; +} + /** \brief Calculates the respawn timer and drop-boosting \param player player object passed from K_KartPlayerThink @@ -1920,39 +2045,147 @@ void K_RespawnChecker(player_t *player) if (player->kartstuff[k_respawn] > 1) { - player->kartstuff[k_respawn]--; - player->mo->momz = 0; + fixed_t destx = 0, desty = 0, destz = 0; + + player->mo->momx = player->mo->momy = player->mo->momz = 0; player->powers[pw_flashing] = 2; player->powers[pw_nocontrol] = 2; - if (leveltime % 8 == 0) + + if (leveltime % 8 == 0 && !mapreset) + S_StartSound(player->mo, sfx_s3kcas); + + destx = (player->starpostx << FRACBITS); + desty = (player->starposty << FRACBITS); + destz = (player->starpostz << FRACBITS); + + if (player->kartstuff[k_starpostflip]) { - INT32 i; - if (!mapreset) - S_StartSound(player->mo, sfx_s3kcas); + // This variable is set from the settings of the best waypoint, thus this waypoint is FLIPPED as well. + // So we should flip the player in advance for it as well. + player->mo->flags2 |= MF2_OBJECTFLIP; + player->mo->eflags |= MFE_VERTICALFLIP; + destz -= (128 * mapobjectscale) + (player->mo->height); + } + else + { + // Ditto, but this waypoint isn't flipped, so make sure the player also isn't flipped! + player->mo->flags2 &= ~MF2_OBJECTFLIP; + player->mo->eflags &= ~MFE_VERTICALFLIP; + destz += (128 * mapobjectscale); + } - for (i = 0; i < 8; i++) + if (player->mo->x != destx || player->mo->y != desty || player->mo->z != destz) + { + fixed_t step = 64*mapobjectscale; + fixed_t dist = P_AproxDistance(P_AproxDistance(player->mo->x - destx, player->mo->y - desty), player->mo->z - destz); + + if (dist <= step) // You're ready to respawn { - mobj_t *mo; - angle_t newangle; - fixed_t newx, newy, newz; + P_TryMove(player->mo, destx, desty, true); + player->mo->z = destz; + } + else + { + fixed_t stepx = 0, stepy = 0, stepz = 0; + angle_t stepha = R_PointToAngle2(player->mo->x, player->mo->y, destx, desty); + angle_t stepva = R_PointToAngle2(0, player->mo->z, P_AproxDistance(player->mo->x - destx, player->mo->y - desty), destz); + fixed_t laserx = 0, lasery = 0, laserz = 0; + UINT8 lasersteps = 4; - newangle = FixedAngle(((360/8)*i)*FRACUNIT); - newx = player->mo->x + P_ReturnThrustX(player->mo, newangle, 31 * player->mo->scale); - newy = player->mo->y + P_ReturnThrustY(player->mo, newangle, 31 * player->mo->scale); - if (player->mo->eflags & MFE_VERTICALFLIP) - newz = player->mo->z + player->mo->height; - else - newz = player->mo->z; + // Move toward the respawn point + stepx = FixedMul(FixedMul(FINECOSINE(stepha >> ANGLETOFINESHIFT), step), FINECOSINE(stepva >> ANGLETOFINESHIFT)); + stepy = FixedMul(FixedMul(FINESINE(stepha >> ANGLETOFINESHIFT), step), FINECOSINE(stepva >> ANGLETOFINESHIFT)); + stepz = FixedMul(FINESINE(stepva >> ANGLETOFINESHIFT), 2*step); - mo = P_SpawnMobj(newx, newy, newz, MT_DEZLASER); - if (mo) + P_TryMove(player->mo, player->mo->x + stepx, player->mo->y + stepy, true); + player->mo->z += stepz; + + // Spawn lasers along the path + laserx = player->mo->x + (stepx / 2); + lasery = player->mo->y + (stepy / 2); + laserz = player->mo->z + (stepz / 2); + dist = P_AproxDistance(P_AproxDistance(laserx - destx, lasery - desty), laserz - destz); + + if (dist > step/2) { + while (lasersteps) + { + + stepha = R_PointToAngle2(laserx, lasery, destx, desty); + stepva = R_PointToAngle2(0, laserz, P_AproxDistance(laserx - destx, lasery - desty), destz); + + stepx = FixedMul(FixedMul(FINECOSINE(stepha >> ANGLETOFINESHIFT), step), FINECOSINE(stepva >> ANGLETOFINESHIFT)); + stepy = FixedMul(FixedMul(FINESINE(stepha >> ANGLETOFINESHIFT), step), FINECOSINE(stepva >> ANGLETOFINESHIFT)); + stepz = FixedMul(FINESINE(stepva >> ANGLETOFINESHIFT), 2*step); + + laserx += stepx; + lasery += stepy; + laserz += stepz; + dist = P_AproxDistance(P_AproxDistance(laserx - destx, lasery - desty), laserz - destz); + + if (dist <= step/2) + break; + + lasersteps--; + } + } + + if (lasersteps == 0) // Don't spawn them beyond the respawn point. + { + mobj_t *laser; + + laser = P_SpawnMobj(laserx, lasery, laserz + (player->mo->height / 2), MT_DEZLASER); + + if (laser && !P_MobjWasRemoved(laser)) + { + P_SetMobjState(laser, S_DEZLASER_TRAIL1); + if (player->mo->eflags & MFE_VERTICALFLIP) + laser->eflags |= MFE_VERTICALFLIP; + P_SetTarget(&laser->target, player->mo); + laser->angle = stepha + ANGLE_90; + P_SetScale(laser, (laser->destscale = FRACUNIT)); + } + } + } + } + else + { + player->kartstuff[k_respawn]--; + + player->mo->flags |= MF_SOLID|MF_SHOOTABLE; + player->mo->flags &= ~(MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY); + if (!(player->pflags & PF_NOCLIP)) + player->mo->flags &= ~MF_NOCLIP; + + if (leveltime % 8 == 0) + { + INT32 i; + + for (i = 0; i < 8; i++) + { + mobj_t *laser; + angle_t newangle; + fixed_t newx, newy, newz; + + newangle = FixedAngle(((360/8)*i)*FRACUNIT); + newx = player->mo->x + P_ReturnThrustX(player->mo, newangle, 31 * player->mo->scale); + newy = player->mo->y + P_ReturnThrustY(player->mo, newangle, 31 * player->mo->scale); if (player->mo->eflags & MFE_VERTICALFLIP) - mo->eflags |= MFE_VERTICALFLIP; - P_SetTarget(&mo->target, player->mo); - mo->angle = newangle+ANGLE_90; - mo->momz = (8 * player->mo->scale) * P_MobjFlip(player->mo); - P_SetScale(mo, (mo->destscale = player->mo->scale)); + newz = player->mo->z + player->mo->height; + else + newz = player->mo->z; + + laser = P_SpawnMobj(newx, newy, newz, MT_DEZLASER); + + if (laser && !P_MobjWasRemoved(laser)) + { + if (player->mo->eflags & MFE_VERTICALFLIP) + laser->eflags |= MFE_VERTICALFLIP; + P_SetTarget(&laser->target, player->mo); + laser->angle = newangle+ANGLE_90; + laser->momz = (8 * player->mo->scale) * P_MobjFlip(player->mo); + P_SetScale(laser, (laser->destscale = player->mo->scale)); + } } } } @@ -1974,7 +2207,7 @@ void K_RespawnChecker(player_t *player) // Sal: The old behavior was stupid and prone to accidental usage. // Let's rip off Mania instead, and turn this into a Drop Dash! - if (cmd->buttons & BT_ACCELERATE) + if (cmd->buttons & BT_ACCELERATE && !player->kartstuff[k_spinouttimer]) // Lat: Since we're letting players spin out on respawn, don't let them charge a dropdash in this state. (It wouldn't work anyway) player->kartstuff[k_dropdash]++; else player->kartstuff[k_dropdash] = 0; @@ -1996,11 +2229,44 @@ void K_RespawnChecker(player_t *player) player->kartstuff[k_startboost] = 50; K_SpawnDashDustRelease(player); } + player->mo->colorized = false; player->kartstuff[k_dropdash] = 0; player->kartstuff[k_respawn] = 0; + //P_PlayRinglossSound(player->mo); P_PlayerRingBurst(player, 3); + + if (G_BattleGametype()) + { + if (player->kartstuff[k_bumper] > 0) + { + if (player->kartstuff[k_bumper] == 1) + { + mobj_t *karmahitbox = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_KARMAHITBOX); // Player hitbox is too small!! + P_SetTarget(&karmahitbox->target, player->mo); + karmahitbox->destscale = player->mo->scale; + P_SetScale(karmahitbox, player->mo->scale); + CONS_Printf(M_GetText("%s lost all of their bumpers!\n"), player_names[player-players]); + } + player->kartstuff[k_bumper]--; + if (K_IsPlayerWanted(player)) + K_CalculateBattleWanted(); + } + + if (!player->kartstuff[k_bumper]) + { + player->kartstuff[k_comebacktimer] = comebacktime; + if (player->kartstuff[k_comebackmode] == 2) + { + mobj_t *poof = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EXPLODE); + S_StartSound(poof, mobjinfo[MT_KARMAHITBOX].seesound); + player->kartstuff[k_comebackmode] = 0; + } + } + + K_CheckBumpers(); + } } } } @@ -4116,6 +4382,7 @@ void K_DoSneaker(player_t *player, INT32 type) static void K_DoShrink(player_t *user) { INT32 i; + mobj_t *mobj, *next; S_StartSound(user->mo, sfx_kc46); // Sound the BANG! user->pflags |= PF_ATTACKDOWN; @@ -4133,10 +4400,7 @@ static void K_DoShrink(player_t *user) // Grow should get taken away. if (players[i].kartstuff[k_growshrinktimer] > 0) K_RemoveGrowShrink(&players[i]); - // Don't hit while invulnerable! - else if (!players[i].kartstuff[k_invincibilitytimer] - && players[i].kartstuff[k_growshrinktimer] <= 0 - && !players[i].kartstuff[k_hyudorotimer]) + else { // Start shrinking! K_DropItems(&players[i]); @@ -4153,6 +4417,33 @@ static void K_DoShrink(player_t *user) } } } + + // kill everything in the kitem list while we're at it: + for (mobj = kitemcap; mobj; mobj = next) + { + next = mobj->itnext; + + // check if the item is being held by a player behind us before removing it. + // check if the item is a "shield" first, bc i'm p sure thrown items keep the player that threw em as target anyway + + if (mobj->type == MT_BANANA_SHIELD || mobj->type == MT_JAWZ_SHIELD || + mobj->type == MT_SSMINE_SHIELD || mobj->type == MT_EGGMANITEM_SHIELD || + mobj->type == MT_SINK_SHIELD || mobj->type == MT_ORBINAUT_SHIELD) + { + if (mobj->target && mobj->target->player) + { + if (mobj->target->player->kartstuff[k_position] > user->kartstuff[k_position]) + continue; // this guy's behind us, don't take his stuff away! + } + } + + mobj->destscale = 0; + mobj->flags &= ~(MF_SOLID|MF_SHOOTABLE|MF_SPECIAL); + mobj->flags |= MF_NOCLIPTHING; // Just for safety + + if (mobj->type == MT_SPB) + spbplace = -1; + } } @@ -4325,6 +4616,7 @@ void K_DropHnextList(player_t *player) dropwork = P_SpawnMobj(work->x, work->y, work->z, type); P_SetTarget(&dropwork->target, player->mo); + P_AddKartItem(dropwork); // needs to be called here so shrink can bust items off players in front of the user. dropwork->angle = work->angle; dropwork->flags2 = work->flags2; dropwork->flags |= MF_NOCLIPTHING; @@ -5426,25 +5718,25 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->mo->colorized = false; player->mo->color = player->skincolor; } - } - else if (player->kartstuff[k_killfield]) // You're gonna REALLY diiiiie - { - const INT32 flashtime = 4<<(4-(player->kartstuff[k_killfield]/TICRATE)); - if (player->kartstuff[k_killfield] == 1 || (player->kartstuff[k_killfield] % (flashtime/2) != 0)) - { - player->mo->colorized = false; - player->mo->color = player->skincolor; - } - else if (player->kartstuff[k_killfield] % flashtime == 0) - { - player->mo->colorized = true; - player->mo->color = SKINCOLOR_BYZANTIUM; - } - else - { - player->mo->colorized = true; - player->mo->color = SKINCOLOR_RUBY; - } + } + else if (player->kartstuff[k_killfield]) // You're gonna REALLY diiiiie + { + const INT32 flashtime = 4<<(4-(player->kartstuff[k_killfield]/TICRATE)); + if (player->kartstuff[k_killfield] == 1 || (player->kartstuff[k_killfield] % (flashtime/2) != 0)) + { + player->mo->colorized = false; + player->mo->color = player->skincolor; + } + else if (player->kartstuff[k_killfield] % flashtime == 0) + { + player->mo->colorized = true; + player->mo->color = SKINCOLOR_BYZANTIUM; + } + else + { + player->mo->colorized = true; + player->mo->color = SKINCOLOR_RUBY; + } } else if (player->kartstuff[k_ringboost] && (leveltime & 1)) // ring boosting { @@ -5600,8 +5892,15 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->kartstuff[k_stolentimer]--; if (player->kartstuff[k_squishedtimer]) + { player->kartstuff[k_squishedtimer]--; + if ((player->kartstuff[k_squishedtimer] == 0) && !(player->pflags & PF_NOCLIP)) + { + player->mo->flags &= ~MF_NOCLIP; + } + } + if (player->kartstuff[k_justbumped]) player->kartstuff[k_justbumped]--; @@ -5617,27 +5916,27 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) K_KartPlayerHUDUpdate(player); - if (G_BattleGametype() && player->kartstuff[k_bumper] > 0 - && !player->kartstuff[k_spinouttimer] && !player->kartstuff[k_squishedtimer] - && !player->kartstuff[k_respawn] && !player->powers[pw_flashing]) - { - player->kartstuff[k_wanted]++; - if (battleovertime.enabled >= 10*TICRATE) - { - if (P_AproxDistance(player->mo->x - battleovertime.x, player->mo->y - battleovertime.y) > battleovertime.radius) - { - player->kartstuff[k_killfield]++; - if (player->kartstuff[k_killfield] > 4*TICRATE) - { - K_SpinPlayer(player, NULL, 0, NULL, false); - //player->kartstuff[k_killfield] = 1; - } - } - else if (player->kartstuff[k_killfield] > 0) - player->kartstuff[k_killfield]--; - } - } - else if (player->kartstuff[k_killfield] > 0) + if (G_BattleGametype() && player->kartstuff[k_bumper] > 0 + && !player->kartstuff[k_spinouttimer] && !player->kartstuff[k_squishedtimer] + && !player->kartstuff[k_respawn] && !player->powers[pw_flashing]) + { + player->kartstuff[k_wanted]++; + if (battleovertime.enabled >= 10*TICRATE) + { + if (P_AproxDistance(player->mo->x - battleovertime.x, player->mo->y - battleovertime.y) > battleovertime.radius) + { + player->kartstuff[k_killfield]++; + if (player->kartstuff[k_killfield] > 4*TICRATE) + { + K_SpinPlayer(player, NULL, 0, NULL, false); + //player->kartstuff[k_killfield] = 1; + } + } + else if (player->kartstuff[k_killfield] > 0) + player->kartstuff[k_killfield]--; + } + } + else if (player->kartstuff[k_killfield] > 0) player->kartstuff[k_killfield]--; if (P_IsObjectOnGround(player->mo)) @@ -5702,6 +6001,10 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) void K_KartPlayerAfterThink(player_t *player) { + // Moved to afterthink, as at this point the players have had their distances to the finish line updated + // and this will correctly account for all players + K_KartUpdatePosition(player); + if (player->kartstuff[k_curshield] || player->kartstuff[k_invincibilitytimer] || (player->kartstuff[k_growshrinktimer] != 0 && player->kartstuff[k_growshrinktimer] % 5 == 4)) // 4 instead of 0 because this is afterthink! @@ -5763,6 +6066,347 @@ void K_KartPlayerAfterThink(player_t *player) } } +/*-------------------------------------------------- + static waypoint_t *K_GetPlayerNextWaypoint(player_t *player) + + Gets the next waypoint of a player, by finding their closest waypoint, then checking which of itself and next or + previous waypoints are infront of the player. + + Input Arguments:- + player - The player the next waypoint is being found for + + Return:- + The waypoint that is the player's next waypoint +--------------------------------------------------*/ +static waypoint_t *K_GetPlayerNextWaypoint(player_t *player) +{ + waypoint_t *bestwaypoint = NULL; + + if ((player != NULL) && (player->mo != NULL) && (P_MobjWasRemoved(player->mo) == false)) + { + waypoint_t *waypoint = K_GetBestWaypointForMobj(player->mo); + boolean updaterespawn = false; + + bestwaypoint = waypoint; + + // check the waypoint's location in relation to the player + // If it's generally in front, it's fine, otherwise, use the best next/previous waypoint. + // EXCEPTION: If our best waypoint is the finishline AND we're facing towards it, don't do this. + // Otherwise it breaks the distance calculations. + if (waypoint != NULL) + { + boolean finishlinehack = false; + angle_t playerangle = player->mo->angle; + angle_t momangle = player->mo->angle; + angle_t angletowaypoint = + R_PointToAngle2(player->mo->x, player->mo->y, waypoint->mobj->x, waypoint->mobj->y); + angle_t angledelta = ANGLE_MAX; + angle_t momdelta = ANGLE_MAX; + + if (player->mo->momx != 0 || player->mo->momy != 0) + { + // Defaults to facing angle if you're not moving. + momangle = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy); + } + + angledelta = playerangle - angletowaypoint; + if (angledelta > ANGLE_180) + { + angledelta = InvAngle(angledelta); + } + + momdelta = momangle - angletowaypoint; + if (momdelta > ANGLE_180) + { + momdelta = InvAngle(momdelta); + } + + if (bestwaypoint == K_GetFinishLineWaypoint()) + { + // facing towards the finishline + if (angledelta <= ANGLE_90) + { + finishlinehack = true; + } + } + + // We're using a lot of angle calculations here, because only using facing angle or only using momentum angle both have downsides. + // nextwaypoints will be picked if you're facing OR moving forward. + // prevwaypoints will be picked if you're facing AND moving backward. + if ((angledelta > ANGLE_45 || momdelta > ANGLE_45) + && (finishlinehack == false)) + { + angle_t nextbestdelta = angledelta; + angle_t nextbestmomdelta = momdelta; + size_t i = 0U; + + if ((waypoint->nextwaypoints != NULL) && (waypoint->numnextwaypoints > 0U)) + { + for (i = 0U; i < waypoint->numnextwaypoints; i++) + { + angletowaypoint = R_PointToAngle2( + player->mo->x, player->mo->y, + waypoint->nextwaypoints[i]->mobj->x, waypoint->nextwaypoints[i]->mobj->y); + + angledelta = playerangle - angletowaypoint; + if (angledelta > ANGLE_180) + { + angledelta = InvAngle(angledelta); + } + + momdelta = momangle - angletowaypoint; + if (momdelta > ANGLE_180) + { + momdelta = InvAngle(momdelta); + } + + if (angledelta < nextbestdelta || momdelta < nextbestmomdelta) + { + bestwaypoint = waypoint->nextwaypoints[i]; + + if (angledelta < nextbestdelta) + { + nextbestdelta = angledelta; + } + if (momdelta < nextbestmomdelta) + { + nextbestmomdelta = momdelta; + } + + // Remove wrong way flag if we're using nextwaypoints + player->kartstuff[k_wrongway] = 0; + updaterespawn = true; + } + } + } + + if ((waypoint->prevwaypoints != NULL) && (waypoint->numprevwaypoints > 0U)) + { + for (i = 0U; i < waypoint->numprevwaypoints; i++) + { + angletowaypoint = R_PointToAngle2( + player->mo->x, player->mo->y, + waypoint->prevwaypoints[i]->mobj->x, waypoint->prevwaypoints[i]->mobj->y); + + angledelta = playerangle - angletowaypoint; + if (angledelta > ANGLE_180) + { + angledelta = InvAngle(angledelta); + } + + momdelta = momangle - angletowaypoint; + if (momdelta > ANGLE_180) + { + momdelta = InvAngle(momdelta); + } + + if (angledelta < nextbestdelta && momdelta < nextbestmomdelta) + { + bestwaypoint = waypoint->prevwaypoints[i]; + + nextbestdelta = angledelta; + nextbestmomdelta = momdelta; + + // Set wrong way flag if we're using prevwaypoints + player->kartstuff[k_wrongway] = 1; + updaterespawn = false; + } + } + } + } + } + + if (!P_IsObjectOnGround(player->mo)) + { + updaterespawn = false; + } + + // Respawn point should only be updated when we're going to a nextwaypoint + if ((updaterespawn) && + (bestwaypoint != NULL) && + (bestwaypoint != player->nextwaypoint) && + (player->kartstuff[k_respawn] == 0) && + (K_GetWaypointIsSpawnpoint(bestwaypoint)) && // Don't try to respawn on waypoints that are marked with no respawn + (K_GetWaypointIsShortcut(bestwaypoint) == false) && (K_GetWaypointIsEnabled(bestwaypoint) == true)) + { + size_t i = 0U; + waypoint_t *aimwaypoint = NULL; + + player->starpostx = bestwaypoint->mobj->x >> FRACBITS; + player->starposty = bestwaypoint->mobj->y >> FRACBITS; + player->starpostz = bestwaypoint->mobj->z >> FRACBITS; + player->kartstuff[k_starpostflip] = (bestwaypoint->mobj->flags2 & MF2_OBJECTFLIP); + + // starpostangle is to the first valid nextwaypoint for simplicity + // if we reach the last waypoint and it's still not valid, just use it anyway. Someone needs to fix + // their map! + for (i = 0U; i < bestwaypoint->numnextwaypoints; i++) + { + aimwaypoint = bestwaypoint->nextwaypoints[i]; + + if ((i == bestwaypoint->numnextwaypoints - 1U) + || ((K_GetWaypointIsEnabled(aimwaypoint) == true) + && (K_GetWaypointIsSpawnpoint(aimwaypoint) == true))) + { + player->starpostangle = R_PointToAngle2( + bestwaypoint->mobj->x, bestwaypoint->mobj->y, aimwaypoint->mobj->x, aimwaypoint->mobj->y); + break; + } + } + } + } + + return bestwaypoint; +} + +static boolean K_PlayerCloserToNextWaypoints(waypoint_t *const waypoint, player_t *const player) +{ + boolean nextiscloser = true; + + if ((waypoint != NULL) && (player != NULL) && (player->mo != NULL)) + { + size_t i = 0U; + waypoint_t *currentwpcheck = NULL; + angle_t angletoplayer = ANGLE_MAX; + angle_t currentanglecheck = ANGLE_MAX; + angle_t bestangle = ANGLE_MAX; + + angletoplayer = R_PointToAngle2(waypoint->mobj->x, waypoint->mobj->y, + player->mo->x, player->mo->y); + + for (i = 0U; i < waypoint->numnextwaypoints; i++) + { + currentwpcheck = waypoint->nextwaypoints[i]; + currentanglecheck = R_PointToAngle2( + waypoint->mobj->x, waypoint->mobj->y, currentwpcheck->mobj->x, currentwpcheck->mobj->y); + + // Get delta angle + currentanglecheck = currentanglecheck - angletoplayer; + + if (currentanglecheck > ANGLE_180) + { + currentanglecheck = InvAngle(currentanglecheck); + } + + if (currentanglecheck < bestangle) + { + bestangle = currentanglecheck; + } + } + + for (i = 0U; i < waypoint->numprevwaypoints; i++) + { + currentwpcheck = waypoint->prevwaypoints[i]; + currentanglecheck = R_PointToAngle2( + waypoint->mobj->x, waypoint->mobj->y, currentwpcheck->mobj->x, currentwpcheck->mobj->y); + + // Get delta angle + currentanglecheck = currentanglecheck - angletoplayer; + + if (currentanglecheck > ANGLE_180) + { + currentanglecheck = InvAngle(currentanglecheck); + } + + if (currentanglecheck < bestangle) + { + bestangle = currentanglecheck; + nextiscloser = false; + break; + } + } + } + + return nextiscloser; +} + +/*-------------------------------------------------- + static void K_UpdateDistanceFromFinishLine(player_t *const player) + + Updates the distance a player has to the finish line. + + Input Arguments:- + player - The player the distance is being updated for + + Return:- + None +--------------------------------------------------*/ +static void K_UpdateDistanceFromFinishLine(player_t *const player) +{ + if ((player != NULL) && (player->mo != NULL)) + { + if (player->exiting) + { + player->nextwaypoint = K_GetFinishLineWaypoint(); + player->distancetofinish = 0U; + } + else + { + waypoint_t *finishline = K_GetFinishLineWaypoint(); + waypoint_t *nextwaypoint = K_GetPlayerNextWaypoint(player); + + if (nextwaypoint != NULL) + { + // If nextwaypoint is NULL, it means we don't want to update the waypoint until we touch another one. + // player->nextwaypoint will keep its previous value in this case. + player->nextwaypoint = nextwaypoint; + } + + // nextwaypoint is now the waypoint that is in front of us + if ((player->nextwaypoint != NULL) && (finishline != NULL)) + { + const boolean useshortcuts = false; + const boolean huntbackwards = false; + boolean pathfindsuccess = false; + path_t pathtofinish = {}; + + pathfindsuccess = + K_PathfindToWaypoint(player->nextwaypoint, finishline, &pathtofinish, useshortcuts, huntbackwards); + + // Update the player's distance to the finish line if a path was found. + // Using shortcuts won't find a path, so distance won't be updated until the player gets back on track + if (pathfindsuccess == true) + { + // Add euclidean distance to the next waypoint to the distancetofinish + UINT32 adddist; + fixed_t disttowaypoint = + P_AproxDistance( + (player->mo->x >> FRACBITS) - (player->nextwaypoint->mobj->x >> FRACBITS), + (player->mo->y >> FRACBITS) - (player->nextwaypoint->mobj->y >> FRACBITS)); + disttowaypoint = P_AproxDistance(disttowaypoint, (player->mo->z >> FRACBITS) - (player->nextwaypoint->mobj->z >> FRACBITS)); + + adddist = (UINT32)disttowaypoint; + + player->distancetofinish = pathtofinish.totaldist + adddist; + Z_Free(pathtofinish.array); + + // distancetofinish is currently a flat distance to the finish line, but in order to be fully + // correct we need to add to it the length of the entire circuit multiplied by the number of laps + // left after this one. This will give us the total distance to the finish line, and allow item + // distance calculation to work easily + if ((mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE) == 0U) + { + const UINT8 numfulllapsleft = ((UINT8)cv_numlaps.value - player->laps); + + player->distancetofinish += numfulllapsleft * K_GetCircuitLength(); + + // An additional HACK, to fix looking backwards towards the finish line + // If the player's next waypoint is the finishline and the angle distance from player to + // connectin waypoints implies they're closer to a next waypoint, add a full track distance + if (player->nextwaypoint == finishline) + { + if (K_PlayerCloserToNextWaypoints(player->nextwaypoint, player) == true) + { + player->distancetofinish += K_GetCircuitLength(); + } + } + } + } + } + } + } +} + // Returns false if this player being placed here causes them to collide with any other player // Used in g_game.c for match etc. respawning // This does not check along the z because the z is not correctly set for the spawnee at this point @@ -5860,12 +6504,11 @@ static void K_KartDrift(player_t *player, boolean onground) // Holding the Jump button will enable drifting. // Drift Release (Moved here so you can't "chain" drifts) - if ((player->kartstuff[k_drift] != -5 && player->kartstuff[k_drift] != 5) - // || (player->kartstuff[k_drift] >= 1 && player->kartstuff[k_turndir] != 1) || (player->kartstuff[k_drift] <= -1 && player->kartstuff[k_turndir] != -1)) - && onground) + if (player->kartstuff[k_drift] != -5 && player->kartstuff[k_drift] != 5) { if (player->kartstuff[k_driftcharge] < 0 || player->kartstuff[k_driftcharge] >= dsone) { + angle_t pushdir = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy); //mobj_t *overlay = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_DRIFTEXPLODE); //P_SetTarget(&overlay->target, player->mo); //P_SetScale(overlay, (overlay->destscale = player->mo->scale)); @@ -5877,6 +6520,9 @@ static void K_KartDrift(player_t *player, boolean onground) if (player->kartstuff[k_driftcharge] < 0) { // Stage 0: Yellow sparks + if (!onground) + P_Thrust(player->mo, pushdir, player->speed / 8); + if (player->kartstuff[k_driftboost] < 15) player->kartstuff[k_driftboost] = 15; @@ -5886,6 +6532,9 @@ static void K_KartDrift(player_t *player, boolean onground) else if (player->kartstuff[k_driftcharge] >= dsone && player->kartstuff[k_driftcharge] < dstwo) { // Stage 1: Red sparks + if (!onground) + P_Thrust(player->mo, pushdir, player->speed / 4); + if (player->kartstuff[k_driftboost] < 20) player->kartstuff[k_driftboost] = 20; @@ -5895,6 +6544,9 @@ static void K_KartDrift(player_t *player, boolean onground) else if (player->kartstuff[k_driftcharge] < dsthree) { // Stage 2: Blue sparks + if (!onground) + P_Thrust(player->mo, pushdir, player->speed / 3); + if (player->kartstuff[k_driftboost] < 50) player->kartstuff[k_driftboost] = 50; @@ -5904,6 +6556,9 @@ static void K_KartDrift(player_t *player, boolean onground) else if (player->kartstuff[k_driftcharge] >= dsthree) { // Stage 3: Rainbow sparks + if (!onground) + P_Thrust(player->mo, pushdir, player->speed / 2); + if (player->kartstuff[k_driftboost] < 125) player->kartstuff[k_driftboost] = 125; @@ -6081,12 +6736,15 @@ void K_KartUpdatePosition(player_t *player) { fixed_t position = 1; fixed_t oldposition = player->kartstuff[k_position]; - fixed_t i, ppcd, pncd, ipcd, incd; - fixed_t pmo, imo; - mobj_t *mo; + fixed_t i; if (player->spectator || !player->mo) + { + // Ensure these are reset for spectators + player->kartstuff[k_position] = 0; + player->kartstuff[k_positiondelay] = 0; return; + } for (i = 0; i < MAXPLAYERS; i++) { @@ -6095,69 +6753,20 @@ void K_KartUpdatePosition(player_t *player) if (G_RaceGametype()) { - if ((((players[i].starpostnum) + (numstarposts + 1) * players[i].laps) > - ((player->starpostnum) + (numstarposts + 1) * player->laps))) - position++; - else if (((players[i].starpostnum) + (numstarposts+1)*players[i].laps) == - ((player->starpostnum) + (numstarposts+1)*player->laps)) + if (player->exiting) // End of match standings { - ppcd = pncd = ipcd = incd = 0; - - player->kartstuff[k_prevcheck] = players[i].kartstuff[k_prevcheck] = 0; - player->kartstuff[k_nextcheck] = players[i].kartstuff[k_nextcheck] = 0; - - // This checks every thing on the map, and looks for MT_BOSS3WAYPOINT (the thing we're using for checkpoint wp's, for now) - for (mo = waypointcap; mo != NULL; mo = mo->tracer) + // Only time matters + if (players[i].realtime < player->realtime) + position++; + } + else + { + // I'm a lap behind this player OR + // My distance to the finish line is higher, so I'm behind + if ((players[i].laps > player->laps) + || (players[i].distancetofinish < player->distancetofinish)) { - pmo = P_AproxDistance(P_AproxDistance( mo->x - player->mo->x, - mo->y - player->mo->y), - mo->z - player->mo->z) / FRACUNIT; - imo = P_AproxDistance(P_AproxDistance( mo->x - players[i].mo->x, - mo->y - players[i].mo->y), - mo->z - players[i].mo->z) / FRACUNIT; - - if (mo->health == player->starpostnum && (!mo->movecount || mo->movecount == player->laps+1)) - { - player->kartstuff[k_prevcheck] += pmo; - ppcd++; - } - if (mo->health == (player->starpostnum + 1) && (!mo->movecount || mo->movecount == player->laps+1)) - { - player->kartstuff[k_nextcheck] += pmo; - pncd++; - } - if (mo->health == players[i].starpostnum && (!mo->movecount || mo->movecount == players[i].laps+1)) - { - players[i].kartstuff[k_prevcheck] += imo; - ipcd++; - } - if (mo->health == (players[i].starpostnum + 1) && (!mo->movecount || mo->movecount == players[i].laps+1)) - { - players[i].kartstuff[k_nextcheck] += imo; - incd++; - } - } - - if (ppcd > 1) player->kartstuff[k_prevcheck] /= ppcd; - if (pncd > 1) player->kartstuff[k_nextcheck] /= pncd; - if (ipcd > 1) players[i].kartstuff[k_prevcheck] /= ipcd; - if (incd > 1) players[i].kartstuff[k_nextcheck] /= incd; - - if ((players[i].kartstuff[k_nextcheck] > 0 || player->kartstuff[k_nextcheck] > 0) && !player->exiting) - { - if ((players[i].kartstuff[k_nextcheck] - players[i].kartstuff[k_prevcheck]) < - (player->kartstuff[k_nextcheck] - player->kartstuff[k_prevcheck])) - position++; - } - else if (!player->exiting) - { - if (players[i].kartstuff[k_prevcheck] > player->kartstuff[k_prevcheck]) - position++; - } - else - { - if (players[i].starposttime < player->starposttime) - position++; + position++; } } } @@ -6165,14 +6774,16 @@ void K_KartUpdatePosition(player_t *player) { if (player->exiting) // End of match standings { - if (players[i].marescore > player->marescore) // Only score matters + // Only score matters + if (players[i].marescore > player->marescore) position++; } else { - if (players[i].kartstuff[k_bumper] == player->kartstuff[k_bumper] && players[i].marescore > player->marescore) - position++; - else if (players[i].kartstuff[k_bumper] > player->kartstuff[k_bumper]) + // I have less points than but the same bumpers as this player OR + // I have less bumpers than this player + if ((players[i].kartstuff[k_bumper] == player->kartstuff[k_bumper] && players[i].marescore > player->marescore) + || (players[i].kartstuff[k_bumper] > player->kartstuff[k_bumper])) position++; } } @@ -6305,7 +6916,8 @@ void K_MoveKartPlayer(player_t *player, boolean onground) boolean HOLDING_ITEM = (player->kartstuff[k_itemheld] || player->kartstuff[k_eggmanheld]); boolean NO_HYUDORO = (player->kartstuff[k_stolentimer] == 0 && player->kartstuff[k_stealingtimer] == 0); - K_KartUpdatePosition(player); + K_UpdateDistanceFromFinishLine(player); + player->pflags &= ~PF_HITFINISHLINE; if (!player->exiting) { @@ -6963,13 +7575,9 @@ void K_MoveKartPlayer(player_t *player, boolean onground) // Squishing // If a Grow player or a sector crushes you, get flattened instead of being killed. - if (player->kartstuff[k_squishedtimer] <= 0) + if (player->kartstuff[k_squishedtimer] > 0) { - player->mo->flags &= ~MF_NOCLIP; - } - else - { - player->mo->flags |= MF_NOCLIP; + //player->mo->flags |= MF_NOCLIP; player->mo->momx = 0; player->mo->momy = 0; } @@ -7271,7 +7879,7 @@ void K_CheckSpectateStatus(void) continue; if (leveltime > (starttime + 20*TICRATE)) // DON'T allow if the match is 20 seconds in return; - if (G_RaceGametype() && players[i].laps) // DON'T allow if the race is at 2 laps + if (G_RaceGametype() && players[i].laps >= 2) // DON'T allow if the race is at 2 laps return; continue; } @@ -7358,6 +7966,8 @@ static patch_t *kp_winnernum[NUMPOSFRAMES]; static patch_t *kp_facenum[MAXPLAYERS+1]; static patch_t *kp_facehighlight[8]; +static patch_t *kp_spbminimap; + static patch_t *kp_ringsticker[2]; static patch_t *kp_ringstickersplit[4]; static patch_t *kp_ring[6]; @@ -7422,7 +8032,7 @@ static patch_t *kp_lapanim_number[10][3]; static patch_t *kp_lapanim_emblem[2]; static patch_t *kp_lapanim_hand[3]; -static patch_t *kp_yougotem; +static patch_t *kp_yougotem; static patch_t *kp_itemminimap; static patch_t *kp_alagles[10]; @@ -7513,6 +8123,8 @@ void K_LoadKartHUDGraphics(void) kp_facehighlight[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); } + kp_spbminimap = W_CachePatchName("SPBMMAP", PU_HUDGFX); + // Rings & Lives kp_ringsticker[0] = W_CachePatchName("RNGBACKA", PU_HUDGFX); kp_ringsticker[1] = W_CachePatchName("RNGBACKB", PU_HUDGFX); @@ -7726,7 +8338,7 @@ void K_LoadKartHUDGraphics(void) kp_lapanim_hand[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); } - kp_yougotem = (patch_t *) W_CachePatchName("YOUGOTEM", PU_HUDGFX); + kp_yougotem = (patch_t *) W_CachePatchName("YOUGOTEM", PU_HUDGFX); kp_itemminimap = (patch_t *) W_CachePatchName("MMAPITEM", PU_HUDGFX); sprintf(buffer, "ALAGLESx"); @@ -8528,7 +9140,7 @@ static void K_DrawKartPositionNum(INT32 num) { if (win) // 1st place winner? You get rainbows!! localpatch = kp_winnernum[(leveltime % (NUMWINFRAMES*3)) / 3]; - else if (stplyr->laps+1 >= cv_numlaps.value || stplyr->exiting) // Check for the final lap, or won + else if (stplyr->laps >= cv_numlaps.value || stplyr->exiting) // Check for the final lap, or won { // Alternate frame every three frames switch (leveltime % 9) @@ -8912,8 +9524,8 @@ static void K_drawKartLapsAndRings(void) if (cv_numlaps.value >= 10) { UINT8 ln[2]; - ln[0] = ((abs(stplyr->laps+1) / 10) % 10); - ln[1] = (abs(stplyr->laps+1) % 10); + ln[0] = ((abs(stplyr->laps) / 10) % 10); + ln[1] = (abs(stplyr->laps) % 10); V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|splitflags, pingnum[ln[0]]); V_DrawScaledPatch(fx+17, fy, V_HUDTRANS|splitflags, pingnum[ln[1]]); @@ -8926,7 +9538,7 @@ static void K_drawKartLapsAndRings(void) } else { - V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|splitflags, kp_facenum[(stplyr->laps+1) % 10]); + V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|splitflags, kp_facenum[(stplyr->laps) % 10]); V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|splitflags, kp_facenum[(cv_numlaps.value) % 10]); } @@ -8968,7 +9580,7 @@ static void K_drawKartLapsAndRings(void) if (stplyr->exiting) V_DrawKartString(LAPS_X+33, LAPS_Y+3, V_HUDTRANS|splitflags, "FIN"); else - V_DrawKartString(LAPS_X+33, LAPS_Y+3, V_HUDTRANS|splitflags, va("%d/%d", stplyr->laps+1, cv_numlaps.value)); + V_DrawKartString(LAPS_X+33, LAPS_Y+3, V_HUDTRANS|splitflags, va("%d/%d", stplyr->laps, cv_numlaps.value)); // Rings if (netgame) @@ -9197,7 +9809,7 @@ static void K_drawKartWanted(void) basey = WANT_Y; if (r_splitscreen == 2) { - basey += 16; // slight adjust for 3P + basey += 16; // slight adjust for 3P basex -= 6; } } @@ -9291,279 +9903,300 @@ static void K_drawKartPlayerCheck(void) V_DrawMappedPatch(x, CHEK_Y, V_HUDTRANS|splitflags, kp_check[pnum], colormap); } } -} +} -static void K_drawKartMinimapIcon(fixed_t objx, fixed_t objy, INT32 hudx, INT32 hudy, INT32 flags, patch_t *icon, UINT8 *colormap, patch_t *AutomapPic) -{ - // amnum xpos & ypos are the icon's speed around the HUD. - // The number being divided by is for how fast it moves. - // The higher the number, the slower it moves. - - // am xpos & ypos are the icon's starting position. Withouht - // it, they wouldn't 'spawn' on the top-right side of the HUD. - - fixed_t amnumxpos, amnumypos; - INT32 amxpos, amypos; - - node_t *bsp = &nodes[numnodes-1]; - fixed_t maxx, minx, maxy, miny; - - fixed_t mapwidth, mapheight; - fixed_t xoffset, yoffset; - fixed_t xscale, yscale, zoom; - - maxx = maxy = INT32_MAX; - minx = miny = INT32_MIN; - minx = bsp->bbox[0][BOXLEFT]; - maxx = bsp->bbox[0][BOXRIGHT]; - miny = bsp->bbox[0][BOXBOTTOM]; - maxy = bsp->bbox[0][BOXTOP]; - - if (bsp->bbox[1][BOXLEFT] < minx) - minx = bsp->bbox[1][BOXLEFT]; - if (bsp->bbox[1][BOXRIGHT] > maxx) - maxx = bsp->bbox[1][BOXRIGHT]; - if (bsp->bbox[1][BOXBOTTOM] < miny) - miny = bsp->bbox[1][BOXBOTTOM]; - if (bsp->bbox[1][BOXTOP] > maxy) - maxy = bsp->bbox[1][BOXTOP]; - - // You might be wondering why these are being bitshift here - // it's because mapwidth and height would otherwise overflow for maps larger than half the size possible... - // map boundaries and sizes will ALWAYS be whole numbers thankfully - // later calculations take into consideration that these are actually not in terms of FRACUNIT though - minx >>= FRACBITS; - maxx >>= FRACBITS; - miny >>= FRACBITS; - maxy >>= FRACBITS; - - mapwidth = maxx - minx; - mapheight = maxy - miny; - - // These should always be small enough to be bitshift back right now - xoffset = (minx + mapwidth/2)<width, mapwidth); - yscale = FixedDiv(AutomapPic->height, mapheight); - zoom = FixedMul(min(xscale, yscale), FRACUNIT-FRACUNIT/20); - - amnumxpos = (FixedMul(objx, zoom) - FixedMul(xoffset, zoom)); - amnumypos = -(FixedMul(objy, zoom) - FixedMul(yoffset, zoom)); - - if (encoremode) - amnumxpos = -amnumxpos; - - amxpos = amnumxpos + ((hudx + AutomapPic->width/2 - (icon->width/2))<height/2 - (icon->height/2))<width/2 + (icon->width/2))<bbox[0][BOXLEFT]; + maxx = bsp->bbox[0][BOXRIGHT]; + miny = bsp->bbox[0][BOXBOTTOM]; + maxy = bsp->bbox[0][BOXTOP]; + + if (bsp->bbox[1][BOXLEFT] < minx) + minx = bsp->bbox[1][BOXLEFT]; + if (bsp->bbox[1][BOXRIGHT] > maxx) + maxx = bsp->bbox[1][BOXRIGHT]; + if (bsp->bbox[1][BOXBOTTOM] < miny) + miny = bsp->bbox[1][BOXBOTTOM]; + if (bsp->bbox[1][BOXTOP] > maxy) + maxy = bsp->bbox[1][BOXTOP]; + + // You might be wondering why these are being bitshift here + // it's because mapwidth and height would otherwise overflow for maps larger than half the size possible... + // map boundaries and sizes will ALWAYS be whole numbers thankfully + // later calculations take into consideration that these are actually not in terms of FRACUNIT though + minx >>= FRACBITS; + maxx >>= FRACBITS; + miny >>= FRACBITS; + maxy >>= FRACBITS; + + mapwidth = maxx - minx; + mapheight = maxy - miny; + + // These should always be small enough to be bitshift back right now + xoffset = (minx + mapwidth/2)<width, mapwidth); + yscale = FixedDiv(AutomapPic->height, mapheight); + zoom = FixedMul(min(xscale, yscale), FRACUNIT-FRACUNIT/20); + + amnumxpos = (FixedMul(objx, zoom) - FixedMul(xoffset, zoom)); + amnumypos = -(FixedMul(objy, zoom) - FixedMul(yoffset, zoom)); + + if (encoremode) + amnumxpos = -amnumxpos; + + amxpos = amnumxpos + ((hudx + AutomapPic->width/2 - (icon->width/2))<height/2 - (icon->height/2))<width/2 + (icon->width/2))<width/2); - y = MINI_Y - (AutomapPic->height/2); - - if (timeinmap > 105) - { - minimaptrans = cv_kartminimap.value; - if (timeinmap <= 113) - minimaptrans = ((((INT32)timeinmap) - 105)*minimaptrans)/(113-105); - if (!minimaptrans) - return; - } - else - return; - - minimaptrans = ((10-minimaptrans)<width), y, splitflags|V_FLIP, AutomapPic); - else - V_DrawScaledPatch(x, y, splitflags, AutomapPic); - - if (!(r_splitscreen == 2)) - { - splitflags &= ~minimaptrans; - splitflags |= V_HUDTRANSHALF; - } - - // let offsets transfer to the heads, too! - if (encoremode) - x += SHORT(AutomapPic->leftoffset); - else - x -= SHORT(AutomapPic->leftoffset); - y -= SHORT(AutomapPic->topoffset); - - // Draw the super item in Battle - if (G_BattleGametype() && battleovertime.enabled) - { - if (battleovertime.enabled >= 10*TICRATE || (battleovertime.enabled & 1)) - { - const INT32 prevsplitflags = splitflags; - splitflags &= ~V_HUDTRANSHALF; - splitflags |= V_HUDTRANS; - colormap = R_GetTranslationColormap(TC_RAINBOW, (UINT8)(1 + (leveltime % (MAXSKINCOLORS-1))), GTC_CACHE); - K_drawKartMinimapIcon(battleovertime.x, battleovertime.y, x, y, splitflags, kp_itemminimap, colormap, AutomapPic); - splitflags = prevsplitflags; - } - } - - // initialize - for (i = 0; i < 4; i++) - localplayers[i] = -1; - - // Player's tiny icons on the Automap. (drawn opposite direction so player 1 is drawn last in splitscreen) - if (ghosts) - { - demoghost *g = ghosts; - while (g) - { - if (g->mo->skin) - skin = ((skin_t*)g->mo->skin)-skins; - else - skin = 0; - if (g->mo->color) - { - if (g->mo->colorized) - colormap = R_GetTranslationColormap(TC_RAINBOW, g->mo->color, GTC_CACHE); - else - colormap = R_GetTranslationColormap(skin, g->mo->color, GTC_CACHE); - } - else - colormap = NULL; - K_drawKartMinimapIcon(g->mo->x, g->mo->y, x, y, splitflags, facemmapprefix[skin], colormap, AutomapPic); - g = g->next; - } - - if (!stplyr->mo || stplyr->spectator) // do we need the latter..? - return; - - localplayers[numlocalplayers] = stplyr-players; - numlocalplayers++; - } - else - { - for (i = MAXPLAYERS-1; i >= 0; i--) - { - if (!playeringame[i]) - continue; - if (!players[i].mo || players[i].spectator) - continue; - - if (i != displayplayers[0] || r_splitscreen) - { - if (G_BattleGametype() && players[i].kartstuff[k_bumper] <= 0) - continue; - - if (players[i].kartstuff[k_hyudorotimer] > 0) - { - if (!((players[i].kartstuff[k_hyudorotimer] < 1*TICRATE/2 - || players[i].kartstuff[k_hyudorotimer] > hyudorotime-(1*TICRATE/2)) - && !(leveltime & 1))) - continue; - } - } - - if (i == displayplayers[0] || i == displayplayers[1] || i == displayplayers[2] || i == displayplayers[3]) - { - // Draw display players on top of everything else - localplayers[numlocalplayers] = i; - numlocalplayers++; - continue; - } - - if (players[i].mo->skin) - skin = ((skin_t*)players[i].mo->skin)-skins; - else - skin = 0; - - if (players[i].mo->color) - { - if (players[i].mo->colorized) - colormap = R_GetTranslationColormap(TC_RAINBOW, players[i].mo->color, GTC_CACHE); - else - colormap = R_GetTranslationColormap(skin, players[i].mo->color, GTC_CACHE); - } - else - colormap = NULL; - - K_drawKartMinimapIcon(players[i].mo->x, players[i].mo->y, x, y, splitflags, facemmapprefix[skin], colormap, AutomapPic); + // Maybe move this somewhere else where this won't be a concern? + if (stplyr != &players[displayplayers[0]]) + return; + + lumpnum = W_CheckNumForName(va("%sR", G_BuildMapName(gamemap))); + + if (lumpnum != -1) + AutomapPic = W_CachePatchName(va("%sR", G_BuildMapName(gamemap)), PU_HUDGFX); + else + return; // no pic, just get outta here + + x = MINI_X - (AutomapPic->width/2); + y = MINI_Y - (AutomapPic->height/2); + + if (timeinmap > 105) + { + minimaptrans = cv_kartminimap.value; + if (timeinmap <= 113) + minimaptrans = ((((INT32)timeinmap) - 105)*minimaptrans)/(113-105); + if (!minimaptrans) + return; + } + else + return; + + minimaptrans = ((10-minimaptrans)<width), y, splitflags|V_FLIP, AutomapPic); + else + V_DrawScaledPatch(x, y, splitflags, AutomapPic); + + if (!(r_splitscreen == 2)) + { + splitflags &= ~minimaptrans; + splitflags |= V_HUDTRANSHALF; + } + + // let offsets transfer to the heads, too! + if (encoremode) + x += SHORT(AutomapPic->leftoffset); + else + x -= SHORT(AutomapPic->leftoffset); + y -= SHORT(AutomapPic->topoffset); + + // Draw the super item in Battle + if (G_BattleGametype() && battleovertime.enabled) + { + if (battleovertime.enabled >= 10*TICRATE || (battleovertime.enabled & 1)) + { + const INT32 prevsplitflags = splitflags; + splitflags &= ~V_HUDTRANSHALF; + splitflags |= V_HUDTRANS; + colormap = R_GetTranslationColormap(TC_RAINBOW, (UINT8)(1 + (leveltime % (MAXSKINCOLORS-1))), GTC_CACHE); + K_drawKartMinimapIcon(battleovertime.x, battleovertime.y, x, y, splitflags, kp_itemminimap, colormap, AutomapPic); + splitflags = prevsplitflags; + } + } + + // initialize + for (i = 0; i < 4; i++) + localplayers[i] = -1; + + // Player's tiny icons on the Automap. (drawn opposite direction so player 1 is drawn last in splitscreen) + if (ghosts) + { + demoghost *g = ghosts; + while (g) + { + if (g->mo->skin) + skin = ((skin_t*)g->mo->skin)-skins; + else + skin = 0; + if (g->mo->color) + { + if (g->mo->colorized) + colormap = R_GetTranslationColormap(TC_RAINBOW, g->mo->color, GTC_CACHE); + else + colormap = R_GetTranslationColormap(skin, g->mo->color, GTC_CACHE); + } + else + colormap = NULL; + K_drawKartMinimapIcon(g->mo->x, g->mo->y, x, y, splitflags, facemmapprefix[skin], colormap, AutomapPic); + g = g->next; + } + + if (!stplyr->mo || stplyr->spectator) // do we need the latter..? + return; + + localplayers[numlocalplayers] = stplyr-players; + numlocalplayers++; + } + else + { + for (i = MAXPLAYERS-1; i >= 0; i--) + { + if (!playeringame[i]) + continue; + if (!players[i].mo || players[i].spectator) + continue; + + if (i != displayplayers[0] || r_splitscreen) + { + if (G_BattleGametype() && players[i].kartstuff[k_bumper] <= 0) + continue; + + if (players[i].kartstuff[k_hyudorotimer] > 0) + { + if (!((players[i].kartstuff[k_hyudorotimer] < 1*TICRATE/2 + || players[i].kartstuff[k_hyudorotimer] > hyudorotime-(1*TICRATE/2)) + && !(leveltime & 1))) + continue; + } + } + + if (i == displayplayers[0] || i == displayplayers[1] || i == displayplayers[2] || i == displayplayers[3]) + { + // Draw display players on top of everything else + localplayers[numlocalplayers] = i; + numlocalplayers++; + continue; + } + + if (players[i].mo->skin) + skin = ((skin_t*)players[i].mo->skin)-skins; + else + skin = 0; + + if (players[i].mo->color) + { + if (players[i].mo->colorized) + colormap = R_GetTranslationColormap(TC_RAINBOW, players[i].mo->color, GTC_CACHE); + else + colormap = R_GetTranslationColormap(skin, players[i].mo->color, GTC_CACHE); + } + else + colormap = NULL; + + K_drawKartMinimapIcon(players[i].mo->x, players[i].mo->y, x, y, splitflags, facemmapprefix[skin], colormap, AutomapPic); // Target reticule if ((G_RaceGametype() && players[i].kartstuff[k_position] == spbplace) || (G_BattleGametype() && K_IsPlayerWanted(&players[i]))) - K_drawKartMinimapIcon(players[i].mo->x, players[i].mo->y, x, y, splitflags, kp_wantedreticle, NULL, AutomapPic); - } - } - - // draw our local players here, opaque. - splitflags &= ~V_HUDTRANSHALF; - splitflags |= V_HUDTRANS; - - for (i = 0; i < numlocalplayers; i++) - { - if (i == -1) - continue; // this doesn't interest us - - if (players[localplayers[i]].mo->skin) - skin = ((skin_t*)players[localplayers[i]].mo->skin)-skins; - else - skin = 0; - - if (players[localplayers[i]].mo->color) - { - if (players[localplayers[i]].mo->colorized) - colormap = R_GetTranslationColormap(TC_RAINBOW, players[localplayers[i]].mo->color, GTC_CACHE); - else - colormap = R_GetTranslationColormap(skin, players[localplayers[i]].mo->color, GTC_CACHE); - } - else - colormap = NULL; - + K_drawKartMinimapIcon(players[i].mo->x, players[i].mo->y, x, y, splitflags, kp_wantedreticle, NULL, AutomapPic); + } + } + + // draw SPB(s?) + for (mobj = kitemcap; mobj; mobj = next) + { + next = mobj->itnext; + if (mobj->type == MT_SPB) + { + colormap = NULL; + + if (mobj->target && !P_MobjWasRemoved(mobj->target)) + { + if (mobj->player && mobj->player->skincolor) + colormap = R_GetTranslationColormap(TC_RAINBOW, mobj->player->skincolor, GTC_CACHE); + else if (mobj->color) + colormap = R_GetTranslationColormap(TC_RAINBOW, mobj->color, GTC_CACHE); + } + + K_drawKartMinimapIcon(mobj->x, mobj->y, x, y, splitflags, kp_spbminimap, colormap, AutomapPic); + } + } + + // draw our local players here, opaque. + splitflags &= ~V_HUDTRANSHALF; + splitflags |= V_HUDTRANS; + + for (i = 0; i < numlocalplayers; i++) + { + if (i == -1) + continue; // this doesn't interest us + + if (players[localplayers[i]].mo->skin) + skin = ((skin_t*)players[localplayers[i]].mo->skin)-skins; + else + skin = 0; + + if (players[localplayers[i]].mo->color) + { + if (players[localplayers[i]].mo->colorized) + colormap = R_GetTranslationColormap(TC_RAINBOW, players[localplayers[i]].mo->color, GTC_CACHE); + else + colormap = R_GetTranslationColormap(skin, players[localplayers[i]].mo->color, GTC_CACHE); + } + else + colormap = NULL; + K_drawKartMinimapIcon(players[localplayers[i]].mo->x, players[localplayers[i]].mo->y, x, y, splitflags, facemmapprefix[skin], colormap, AutomapPic); - // Target reticule + // Target reticule if ((G_RaceGametype() && players[localplayers[i]].kartstuff[k_position] == spbplace) - || (G_BattleGametype() && K_IsPlayerWanted(&players[localplayers[i]]))) - K_drawKartMinimapIcon(players[localplayers[i]].mo->x, players[localplayers[i]].mo->y, x, y, splitflags, kp_wantedreticle, NULL, AutomapPic); - } -} + || (G_BattleGametype() && K_IsPlayerWanted(&players[localplayers[i]]))) + K_drawKartMinimapIcon(players[localplayers[i]].mo->x, players[localplayers[i]].mo->y, x, y, splitflags, kp_wantedreticle, NULL, AutomapPic); + } +} static void K_drawKartStartCountdown(void) { @@ -9863,7 +10496,7 @@ static void K_drawKartFirstPerson(void) if (!r_splitscreen) y += yoffs; - + if ((leveltime & 1) && (driftcolor != SKINCOLOR_NONE)) // drift sparks! colmap = R_GetTranslationColormap(TC_RAINBOW, driftcolor, GTC_CACHE); else if (stplyr->mo->colorized && stplyr->mo->color) // invincibility/grow/shrink! @@ -10011,7 +10644,7 @@ static void K_drawLapStartAnim(void) kp_lapanim_hand[stplyr->karthud[khud_laphand]-1], NULL); } - if (stplyr->laps == (UINT8)(cv_numlaps.value - 1)) + if (stplyr->laps == (UINT8)(cv_numlaps.value)) { V_DrawFixedPatch((62 - (32*max(0, progress-76)))*FRACUNIT, // 27 30*FRACUNIT, // 24 @@ -10038,14 +10671,14 @@ static void K_drawLapStartAnim(void) V_DrawFixedPatch((188 + (32*max(0, progress-76)))*FRACUNIT, // 194 30*FRACUNIT, // 24 FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, - kp_lapanim_number[(((UINT32)stplyr->laps+1) / 10)][min(progress/2-8, 2)], NULL); + kp_lapanim_number[(((UINT32)stplyr->laps) / 10)][min(progress/2-8, 2)], NULL); if (progress/2-10 >= 0) { V_DrawFixedPatch((208 + (32*max(0, progress-76)))*FRACUNIT, // 221 30*FRACUNIT, // 24 FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, - kp_lapanim_number[(((UINT32)stplyr->laps+1) % 10)][min(progress/2-10, 2)], NULL); + kp_lapanim_number[(((UINT32)stplyr->laps) % 10)][min(progress/2-10, 2)], NULL); } } } @@ -10121,9 +10754,9 @@ static void K_drawDistributionDebugger(void) kp_orbinaut[4], kp_jawz[1] }; - INT32 useodds = 0; - INT32 pingame = 0, bestbumper = 0; - INT32 pdis = 0; + UINT8 useodds = 0; + UINT8 pingame = 0, bestbumper = 0; + UINT32 pdis = 0; INT32 i; INT32 x = -9, y = -9; boolean spbrush = false; @@ -10144,13 +10777,13 @@ static void K_drawDistributionDebugger(void) // lovely double loop...... for (i = 0; i < MAXPLAYERS; i++) { - if (playeringame[i] && !players[i].spectator && players[i].mo - && players[i].kartstuff[k_position] < stplyr->kartstuff[k_position]) - pdis += P_AproxDistance(P_AproxDistance(players[i].mo->x - stplyr->mo->x, - players[i].mo->y - stplyr->mo->y), - players[i].mo->z - stplyr->mo->z) / mapobjectscale - * (pingame - players[i].kartstuff[k_position]) - / max(1, ((pingame - 1) * (pingame + 1) / 3)); + if (playeringame[i] && !players[i].spectator + && players[i].kartstuff[k_position] == 1) + { + // This player is first! Yay! + pdis = stplyr->distancetofinish - players[i].distancetofinish; + break; + } } if (franticitems) // Frantic items make the distances between everyone artifically higher, for crazier items @@ -10213,11 +10846,19 @@ static void K_drawCheckpointDebugger(void) if (stplyr != &players[displayplayers[0]]) // only for p1 return; - if (stplyr->starpostnum >= (numstarposts - (numstarposts/2))) + if (stplyr->starpostnum == numstarposts) V_DrawString(8, 184, 0, va("Checkpoint: %d / %d (Can finish)", stplyr->starpostnum, numstarposts)); else - V_DrawString(8, 184, 0, va("Checkpoint: %d / %d (Skip: %d)", stplyr->starpostnum, numstarposts, ((numstarposts/2) + stplyr->starpostnum))); - V_DrawString(8, 192, 0, va("Waypoint dist: Prev %d, Next %d", stplyr->kartstuff[k_prevcheck], stplyr->kartstuff[k_nextcheck])); + V_DrawString(8, 184, 0, va("Checkpoint: %d / %d", stplyr->starpostnum, numstarposts)); +} + +static void K_DrawWaypointDebugger(void) +{ + if ((cv_kartdebugwaypoints.value != 0) && (stplyr == &players[displayplayers[0]])) + { + V_DrawString(8, 166, 0, va("'Best' Waypoint ID: %d", K_GetWaypointID(stplyr->nextwaypoint))); + V_DrawString(8, 176, 0, va("Finishline Distance: %d", stplyr->distancetofinish)); + } } void K_drawKartHUD(void) @@ -10420,6 +11061,11 @@ void K_drawKartHUD(void) K_drawKartFreePlay(leveltime); } + if (r_splitscreen == 0 && stplyr->kartstuff[k_wrongway] && ((leveltime / 8) & 1)) + { + V_DrawCenteredString(BASEVIDWIDTH>>1, 176, V_REDMAP|V_SNAPTOBOTTOM, "WRONG WAY"); + } + if (netgame && r_splitscreen) { K_drawMiniPing(); @@ -10456,6 +11102,8 @@ void K_drawKartHUD(void) } } } + + K_DrawWaypointDebugger(); } //} diff --git a/src/k_kart.h b/src/k_kart.h index 119b3f9a7..891834f26 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -27,6 +27,7 @@ void K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean solid) void K_KartPainEnergyFling(player_t *player); void K_FlipFromObject(mobj_t *mo, mobj_t *master); void K_MatchGenericExtraFlags(mobj_t *mo, mobj_t *master); +void K_DoIngameRespawn(player_t *player); void K_RespawnChecker(player_t *player); void K_KartMoveAnimation(player_t *player); void K_KartPlayerHUDUpdate(player_t *player); diff --git a/src/k_pathfind.c b/src/k_pathfind.c new file mode 100644 index 000000000..8cccd1e81 --- /dev/null +++ b/src/k_pathfind.c @@ -0,0 +1,506 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 by Sean "Sryder" Ryder +// Copyright (C) 2018-2020 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file k_pathfind.c +/// \brief A* Pathfinding algorithm implementation for SRB2 code base. + +#include "k_pathfind.h" + +#include "doomdef.h" +#include "z_zone.h" +#include "k_bheap.h" + +static const size_t DEFAULT_NODEARRAY_CAPACITY = 8U; +static const size_t DEFAULT_OPENSET_CAPACITY = 8U; +static const size_t DEFAULT_CLOSEDSET_CAPACITY = 8U; + + +/*-------------------------------------------------- + static UINT32 K_NodeGetFScore(const pathfindnode_t *const node) + + Gets the FScore of a node. The FScore is the GScore plus the HScore. + + Input Arguments:- + node - The node to get the FScore of + + Return:- + The FScore of the node. +--------------------------------------------------*/ +static UINT32 K_NodeGetFScore(const pathfindnode_t *const node) +{ + UINT32 fscore = UINT32_MAX; + + I_Assert(node != NULL); + + fscore = node->gscore + node->hscore; + + return fscore; +} + +/*-------------------------------------------------- + static void K_NodeUpdateHeapIndex(void *const node, const size_t newheapindex) + + A callback for the Openset Binary Heap to be able to update the heapindex of the pathfindnodes when they are + moved. + + Input Arguments:- + node - The node that has been updated, should be a pointer to a pathfindnode_t + newheapindex - The new heapindex of the node. + + Return:- + None +--------------------------------------------------*/ +static void K_NodeUpdateHeapIndex(void *const node, const size_t newheapindex) +{ + if (node == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL node in K_NodeUpdateHeapIndex.\n"); + } + else + { + pathfindnode_t *truenode = (pathfindnode_t*)node; + truenode->heapindex = newheapindex; + } +} + +/*-------------------------------------------------- + static pathfindnode_t *K_NodesArrayContainsNodeData( + pathfindnode_t *nodesarray, + void* nodedata, + size_t nodesarraycount) + + Checks whether the Nodes Array contains a node with a waypoint. Searches from the end to the start for speed + reasons. + + Input Arguments:- + nodesarray - The nodes array within the A* algorithm + waypoint - The waypoint to check is within the nodes array + nodesarraycount - The current size of the nodes array + + Return:- + The pathfind node that has the waypoint if there is one. NULL if the waypoint is not in the nodes array. +--------------------------------------------------*/ +static pathfindnode_t *K_NodesArrayContainsNodeData( + pathfindnode_t *nodesarray, + void* nodedata, + size_t nodesarraycount) +{ + pathfindnode_t *foundnode = NULL; + size_t i = 0U; + + I_Assert(nodesarray != NULL); + I_Assert(nodedata != NULL); + + // It is more likely that we'll find the node we are looking for from the end of the array + // Yes, the for loop looks weird, remember that size_t is unsigned and we want to check 0, after it hits 0 it + // will loop back up to SIZE_MAX + for (i = nodesarraycount - 1U; i < nodesarraycount; i--) + { + if (nodesarray[i].nodedata == nodedata) + { + foundnode = &nodesarray[i]; + break; + } + } + return foundnode; +} + +/*-------------------------------------------------- + static boolean K_ClosedsetContainsNode(pathfindnode_t **closedset, pathfindnode_t *node, size_t closedsetcount) + + Checks whether the Closedset contains a node. Searches from the end to the start for speed reasons. + + Input Arguments:- + closedset - The closed set within the A* algorithm + node - The node to check is within the closed set + closedsetcount - The current size of the closedset + + Return:- + True if the node is in the closed set, false if it isn't +--------------------------------------------------*/ +static boolean K_ClosedsetContainsNode(pathfindnode_t **closedset, pathfindnode_t *node, size_t closedsetcount) +{ + boolean nodeisinclosedset = false; + size_t i = 0U; + + I_Assert(closedset != NULL); + I_Assert(node != NULL); + // It is more likely that we'll find the node we are looking for from the end of the array + // Yes, the for loop looks weird, remember that size_t is unsigned and we want to check 0, after it hits 0 it + // will loop back up to SIZE_MAX + for (i = closedsetcount - 1U; i < closedsetcount; i--) + { + if (closedset[i] == node) + { + nodeisinclosedset = true; + break; + } + } + return nodeisinclosedset; +} + +/*-------------------------------------------------- + static boolean K_PathfindSetupValid(const pathfindsetup_t *const pathfindsetup) + + Checks that the setup given for pathfinding is valid and can be used. + + Input Arguments:- + pathfindsetup - The setup for the pathfinding given + + Return:- + True if pathfinding setup is valid, false if it isn't. +--------------------------------------------------*/ +static boolean K_PathfindSetupValid(const pathfindsetup_t *const pathfindsetup) +{ + boolean pathfindsetupvalid = false; + size_t sourcenodenumconnectednodes = 0U; + size_t endnodenumconnectednodes = 0U; + + if (pathfindsetup == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL pathfindsetup in K_PathfindSetupValid.\n"); + } + else if (pathfindsetup->startnodedata == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "Pathfindsetup has NULL startnodedata.\n"); + } + else if (pathfindsetup->endnodedata == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "Pathfindsetup has NULL endnodedata.\n"); + } + else if (pathfindsetup->getconnectednodes == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "Pathfindsetup has NULL getconnectednodes function.\n"); + } + else if (pathfindsetup->getconnectioncosts == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "Pathfindsetup has NULL getconnectioncosts function.\n"); + } + else if (pathfindsetup->getheuristic == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "Pathfindsetup has NULL getheuristic function.\n"); + } + else if (pathfindsetup->gettraversable == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "Pathfindsetup has NULL gettraversable function.\n"); + } + else if (pathfindsetup->getconnectednodes(pathfindsetup->startnodedata, &sourcenodenumconnectednodes) == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "K_PathfindSetupValid: Source node returned NULL connecting nodes.\n"); + } + else if (sourcenodenumconnectednodes == 0U) + { + CONS_Debug(DBG_GAMELOGIC, "K_PathfindSetupValid: Source node has 0 connecting nodes.\n"); + } + else if (pathfindsetup->getconnectednodes(pathfindsetup->endnodedata, &endnodenumconnectednodes) == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "K_PathfindSetupValid: End node returned NULL connecting nodes.\n"); + } + else if (endnodenumconnectednodes == 0U) + { + CONS_Debug(DBG_GAMELOGIC, "K_PathfindSetupValid: End node has 0 connecting nodes.\n"); + } + else + { + pathfindsetupvalid = true; + } + + return pathfindsetupvalid; +} + +static boolean K_ReconstructPath(path_t *const path, pathfindnode_t *const destinationnode) +{ + boolean reconstructsuccess = false; + + I_Assert(path != NULL); + I_Assert(destinationnode != NULL); + + { + size_t numnodes = 0U; + pathfindnode_t *thisnode = destinationnode; + + // If the path we're placing our new path into already has data, free it + if (path->array != NULL) + { + Z_Free(path->array); + path->numnodes = 0U; + path->totaldist = 0U; + } + + // Do a fast check of how many nodes there are so we know how much space to allocate + for (thisnode = destinationnode; thisnode; thisnode = thisnode->camefrom) + { + numnodes++; + } + + if (numnodes > 0U) + { + // Allocate memory for the path + path->numnodes = numnodes; + path->array = Z_Calloc(numnodes * sizeof(pathfindnode_t), PU_STATIC, NULL); + path->totaldist = destinationnode->gscore; + if (path->array == NULL) + { + I_Error("K_ReconstructPath: Out of memory."); + } + + // Put the nodes into the return array + for (thisnode = destinationnode; thisnode; thisnode = thisnode->camefrom) + { + path->array[numnodes - 1U] = *thisnode; + // Correct the camefrom element to point to the previous element in the array instead + if ((path->array[numnodes - 1U].camefrom != NULL) && (numnodes > 1U)) + { + path->array[numnodes - 1U].camefrom = &path->array[numnodes - 2U]; + } + else + { + path->array[numnodes - 1U].camefrom = NULL; + } + + numnodes--; + } + + reconstructsuccess = true; + } + } + + return reconstructsuccess; +} + +/*-------------------------------------------------- + boolean K_PathfindAStar(path_t *const path, pathfindsetup_t *const pathfindsetup) + + See header file for description. +--------------------------------------------------*/ +boolean K_PathfindAStar(path_t *const path, pathfindsetup_t *const pathfindsetup) +{ + boolean pathfindsuccess = false; + + if (path == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL path in K_PathfindAStar.\n"); + } + else if (pathfindsetup == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL pathfindsetup in K_PathfindAStar.\n"); + } + else if (!K_PathfindSetupValid(pathfindsetup)) + { + CONS_Debug(DBG_GAMELOGIC, "K_PathfindAStar: Pathfinding setup is not valid.\n"); + } + else if (pathfindsetup->startnodedata == pathfindsetup->endnodedata) + { + // At the destination, return a simple 1 node path + pathfindnode_t singlenode = {}; + singlenode.camefrom = NULL; + singlenode.nodedata = pathfindsetup->endnodedata; + singlenode.heapindex = SIZE_MAX; + singlenode.hscore = 0U; + singlenode.gscore = 0U; + + K_ReconstructPath(path, &singlenode); + + pathfindsuccess = true; + } + else + { + bheap_t openset = {}; + bheapitem_t poppedbheapitem = {}; + pathfindnode_t *nodesarray = NULL; + pathfindnode_t **closedset = NULL; + pathfindnode_t *newnode = NULL; + pathfindnode_t *currentnode = NULL; + pathfindnode_t *connectingnode = NULL; + void **connectingnodesdata = NULL; + void *checknodedata = NULL; + UINT32 *connectingnodecosts = NULL; + size_t numconnectingnodes = 0U; + size_t connectingnodeheapindex = 0U; + size_t nodesarraycount = 0U; + size_t closedsetcount = 0U; + size_t i = 0U; + UINT32 tentativegscore = 0U; + + // Set the dynamic structure capacites to defaults if they are 0 + if (pathfindsetup->nodesarraycapacity == 0U) + { + pathfindsetup->nodesarraycapacity = DEFAULT_NODEARRAY_CAPACITY; + } + if (pathfindsetup->opensetcapacity == 0U) + { + pathfindsetup->opensetcapacity = DEFAULT_OPENSET_CAPACITY; + } + if (pathfindsetup->closedsetcapacity == 0U) + { + pathfindsetup->closedsetcapacity = DEFAULT_CLOSEDSET_CAPACITY; + } + + // Allocate the necessary memory + nodesarray = Z_Calloc(pathfindsetup->nodesarraycapacity * sizeof(pathfindnode_t), PU_STATIC, NULL); + if (nodesarray == NULL) + { + I_Error("K_PathfindAStar: Out of memory allocating nodes array."); + } + closedset = Z_Calloc(pathfindsetup->closedsetcapacity * sizeof(pathfindnode_t*), PU_STATIC, NULL); + if (closedset == NULL) + { + I_Error("K_PathfindAStar: Out of memory allocating closed set."); + } + K_BHeapInit(&openset, pathfindsetup->opensetcapacity); + + // Create the first node and add it to the open set + newnode = &nodesarray[nodesarraycount]; + newnode->heapindex = SIZE_MAX; + newnode->nodedata = pathfindsetup->startnodedata; + newnode->camefrom = NULL; + newnode->gscore = 0U; + newnode->hscore = pathfindsetup->getheuristic(newnode->nodedata, pathfindsetup->endnodedata); + nodesarraycount++; + K_BHeapPush(&openset, newnode, K_NodeGetFScore(newnode), K_NodeUpdateHeapIndex); + + // update openset capacity if it changed + if (openset.capacity != pathfindsetup->opensetcapacity) + { + pathfindsetup->opensetcapacity = openset.capacity; + } + + // Go through each node in the openset, adding new ones from each node to it + // this continues until a path is found or there are no more nodes to check + while (openset.count > 0U) + { + // pop the best node off of the openset + K_BHeapPop(&openset, &poppedbheapitem); + currentnode = (pathfindnode_t*)poppedbheapitem.data; + + if (currentnode->nodedata == pathfindsetup->endnodedata) + { + pathfindsuccess = K_ReconstructPath(path, currentnode); + break; + } + + // Place the node we just popped into the closed set, as we are now evaluating it + if (closedsetcount >= pathfindsetup->closedsetcapacity) + { + // Need to reallocate closedset to fit another node + pathfindsetup->closedsetcapacity = pathfindsetup->closedsetcapacity * 2; + closedset = + Z_Realloc(closedset, pathfindsetup->closedsetcapacity * sizeof(pathfindnode_t*), PU_STATIC, NULL); + if (closedset == NULL) + { + I_Error("K_PathfindAStar: Out of memory reallocating closed set."); + } + } + closedset[closedsetcount] = currentnode; + closedsetcount++; + + // Get the needed data for the next nodes from the current node + connectingnodesdata = pathfindsetup->getconnectednodes(currentnode->nodedata, &numconnectingnodes); + connectingnodecosts = pathfindsetup->getconnectioncosts(currentnode->nodedata); + + if (connectingnodesdata == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "K_PathfindAStar: A Node returned NULL connecting node data.\n"); + } + else if (connectingnodecosts == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "K_PathfindAStar: A Node returned NULL connecting node costs.\n"); + } + else + { + // For each connecting node add it to the openset if it's unevaluated and not there, + // skip it if it's in the closedset or not traversable + for (i = 0; i < numconnectingnodes; i++) + { + checknodedata = connectingnodesdata[i]; + + if (checknodedata == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "K_PathfindAStar: A Node has a NULL connecting node.\n"); + } + else + { + // skip this node if it isn't traversable + if (pathfindsetup->gettraversable(checknodedata) == false) + { + continue; + } + + // Figure out what the gscore of this route for the connecting node is + tentativegscore = currentnode->gscore + connectingnodecosts[i]; + + // find this data in the nodes array if it's been generated before + connectingnode = K_NodesArrayContainsNodeData(nodesarray, checknodedata, nodesarraycount); + + if (connectingnode != NULL) + { + // The connecting node has been seen before, so it must be in either the closedset (skip it) + // or the openset (re-evaluate it's gscore) + if (K_ClosedsetContainsNode(closedset, connectingnode, closedsetcount) == true) + { + continue; + } + else if (tentativegscore < connectingnode->gscore) + { + // The node is not in the closedset, update it's gscore if this path to it is faster + connectingnode->gscore = tentativegscore; + connectingnode->camefrom = currentnode; + + connectingnodeheapindex = + K_BHeapContains(&openset, connectingnode, connectingnode->heapindex); + if (connectingnodeheapindex != SIZE_MAX) + { + K_UpdateBHeapItemValue( + &openset.array[connectingnodeheapindex], K_NodeGetFScore(connectingnode)); + } + else + { + // SOMEHOW the node is not in either the closed set OR the open set + CONS_Debug(DBG_GAMELOGIC, "K_PathfindAStar: A Node is not in either set.\n"); + } + } + } + else + { + // Node is not created yet, so it hasn't been seen so far + // Reallocate nodesarray if it's full + if (nodesarraycount >= pathfindsetup->nodesarraycapacity) + { + pathfindsetup->nodesarraycapacity = pathfindsetup->nodesarraycapacity * 2; + nodesarray = Z_Realloc(nodesarray, pathfindsetup->nodesarraycapacity * sizeof(pathfindnode_t), PU_STATIC, NULL); + + if (nodesarray == NULL) + { + I_Error("K_PathfindAStar: Out of memory reallocating nodes array."); + } + } + + // Create the new node and add it to the nodes array and open set + newnode = &nodesarray[nodesarraycount]; + newnode->heapindex = SIZE_MAX; + newnode->nodedata = checknodedata; + newnode->camefrom = currentnode; + newnode->gscore = tentativegscore; + newnode->hscore = pathfindsetup->getheuristic(newnode->nodedata, pathfindsetup->endnodedata); + nodesarraycount++; + K_BHeapPush(&openset, newnode, K_NodeGetFScore(newnode), K_NodeUpdateHeapIndex); + } + } + } + } + } + + // Clean up the memory + K_BHeapFree(&openset); + Z_Free(closedset); + Z_Free(nodesarray); + } + + return pathfindsuccess; +} diff --git a/src/k_pathfind.h b/src/k_pathfind.h new file mode 100644 index 000000000..ba0e38f47 --- /dev/null +++ b/src/k_pathfind.h @@ -0,0 +1,82 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 by Sean "Sryder" Ryder +// Copyright (C) 2018-2020 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file k_pathfind.h +/// \brief A* Pathfinding algorithm implementation for SRB2 code base. + +#ifndef __K_PATHFIND__ +#define __K_PATHFIND__ + +#include "doomtype.h" + +// function pointer for returning a node's connected node data +// should return a pointer to an array of pointers to the data, as arguments takes a node's data and a pointer that the +// number of connected nodes should be placed into +typedef void**(*getconnectednodesfunc)(void*, size_t*); + +// function pointer for getting the list of connected node costs/distances +typedef UINT32*(*getnodeconnectioncostsfunc)(void*); + +// function pointer for getting a heuristic between 2 nodes from their base data +typedef UINT32(*getnodeheuristicfunc)(void*, void*); + +// function pointer for getting if a node is traversable from its base data +typedef boolean(*getnodetraversablefunc)(void*); + + +// A pathfindnode contains information about a node from the pathfinding +// heapindex is only used within the pathfinding algorithm itself, and is always 0 after it is completed +typedef struct pathfindnode_s { + size_t heapindex; // The index in the openset binary heap. Only valid while the node is in the openset. + void *nodedata; + struct pathfindnode_s *camefrom; // should eventually be the most efficient predecessor node + UINT32 gscore; // The accumulated distance from the start to this node + UINT32 hscore; // The heuristic from this node to the goal +} pathfindnode_t; + +// Contains the final created path after pathfinding is completed +typedef struct path_s { + size_t numnodes; + struct pathfindnode_s *array; + UINT32 totaldist; +} path_t; + +// Contains info about the pathfinding used to setup the algorithm +// (e.g. the base capacities of the dynamically allocated arrays) +// should be setup by the caller before starting pathfinding +// base capacities will be 8 if they aren't setup, missing callback functions will cause an error. +// Can be accessed after the pathfinding is complete to get the final capacities of them +typedef struct pathfindsetup_s { + size_t opensetcapacity; + size_t closedsetcapacity; + size_t nodesarraycapacity; + void *startnodedata; + void *endnodedata; + getconnectednodesfunc getconnectednodes; + getnodeconnectioncostsfunc getconnectioncosts; + getnodeheuristicfunc getheuristic; + getnodetraversablefunc gettraversable; +} pathfindsetup_t; + + +/*-------------------------------------------------- + boolean K_PathfindAStar(path_t *const path, pathfindsetup_t *const pathfindsetup); + + From a source waypoint and destination waypoint, find the best path between them using the A* algorithm. + + Input Arguments:- + path - The return location of the found path + pathfindsetup - The information regarding pathfinding setup, see pathfindsetup_t + + Return:- + True if a path was found between source and destination, false otherwise. +--------------------------------------------------*/ +boolean K_PathfindAStar(path_t *const path, pathfindsetup_t *const pathfindsetup); + +#endif diff --git a/src/k_waypoint.c b/src/k_waypoint.c new file mode 100644 index 000000000..f77e6d62f --- /dev/null +++ b/src/k_waypoint.c @@ -0,0 +1,1763 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 by Sean "Sryder" Ryder +// Copyright (C) 2018-2020 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file k_waypoint.c +/// \brief Waypoint handling from the relevant mobjs +/// Setup and interfacing with waypoints for the main game + +#include "k_waypoint.h" + +#include "d_netcmd.h" +#include "p_local.h" +#include "p_tick.h" +#include "z_zone.h" +#include "g_game.h" + +// The number of sparkles per waypoint connection in the waypoint visualisation +static const UINT32 SPARKLES_PER_CONNECTION = 16U; + +// Some defaults for the size of the dynamically allocated sets for pathfinding. These are kept for the purpose of +// allocating a size that is less likely to need reallocating again during the pathfinding. +#define OPENSET_BASE_SIZE (16U) +#define CLOSEDSET_BASE_SIZE (256U) +#define NODESARRAY_BASE_SIZE (256U) + +static waypoint_t *waypointheap = NULL; +static waypoint_t *firstwaypoint = NULL; +static waypoint_t *finishline = NULL; + +static UINT32 circuitlength = 0U; + +static size_t numwaypoints = 0U; +static size_t numwaypointmobjs = 0U; +static size_t baseopensetsize = OPENSET_BASE_SIZE; +static size_t baseclosedsetsize = CLOSEDSET_BASE_SIZE; +static size_t basenodesarraysize = NODESARRAY_BASE_SIZE; + + +/*-------------------------------------------------- + waypoint_t *K_GetFinishLineWaypoint(void) + + See header file for description. +--------------------------------------------------*/ +waypoint_t *K_GetFinishLineWaypoint(void) +{ + return finishline; +} + +/*-------------------------------------------------- + boolean K_GetWaypointIsFinishline(waypoint_t *waypoint) + + See header file for description. +--------------------------------------------------*/ +boolean K_GetWaypointIsFinishline(waypoint_t *waypoint) +{ + boolean waypointisfinishline = false; + + if (waypoint == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL waypoint in K_GetWaypointIsFinishline.\n"); + } + else if ((waypoint->mobj == NULL) || (P_MobjWasRemoved(waypoint->mobj) == true)) + { + CONS_Debug(DBG_GAMELOGIC, "NULL waypoint mobj in K_GetWaypointIsFinishline.\n"); + } + else + { + waypointisfinishline = (waypoint->mobj->extravalue2 == 1); + } + + return waypointisfinishline; +} + +/*-------------------------------------------------- + boolean K_GetWaypointIsShortcut(waypoint_t *waypoint) + + See header file for description. +--------------------------------------------------*/ +boolean K_GetWaypointIsShortcut(waypoint_t *waypoint) +{ + boolean waypointisshortcut = false; + + if (waypoint == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL waypoint in K_GetWaypointIsShortcut.\n"); + } + else if ((waypoint->mobj == NULL) || (P_MobjWasRemoved(waypoint->mobj) == true)) + { + CONS_Debug(DBG_GAMELOGIC, "NULL waypoint mobj in K_GetWaypointIsShortcut.\n"); + } + else + { + waypointisshortcut = (waypoint->mobj->lastlook == 1); + } + + return waypointisshortcut; +} + +/*-------------------------------------------------- + boolean K_GetWaypointIsEnabled(waypoint_t *waypoint) + + See header file for description. +--------------------------------------------------*/ +boolean K_GetWaypointIsEnabled(waypoint_t *waypoint) +{ + boolean waypointisenabled = true; + + if (waypoint == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL waypoint in K_GetWaypointIsEnabled.\n"); + } + else if ((waypoint->mobj == NULL) || (P_MobjWasRemoved(waypoint->mobj) == true)) + { + CONS_Debug(DBG_GAMELOGIC, "NULL waypoint mobj in K_GetWaypointIsEnabled.\n"); + } + else + { + waypointisenabled = (waypoint->mobj->extravalue1 == 1); + } + + return waypointisenabled; +} + +/*-------------------------------------------------- + boolean K_GetWaypointIsSpawnpoint(waypoint_t *waypoint) + + See header file for description. +--------------------------------------------------*/ +boolean K_GetWaypointIsSpawnpoint(waypoint_t *waypoint) +{ + boolean waypointisspawnpoint = true; + + if (waypoint == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL waypoint in K_GetWaypointIsEnabled.\n"); + } + else if ((waypoint->mobj == NULL) || (P_MobjWasRemoved(waypoint->mobj) == true)) + { + CONS_Debug(DBG_GAMELOGIC, "NULL waypoint mobj in K_GetWaypointIsEnabled.\n"); + } + else + { + waypointisspawnpoint = (waypoint->mobj->reactiontime == 1); + } + + return waypointisspawnpoint; +} + +/*-------------------------------------------------- + INT32 K_GetWaypointNextID(waypoint_t *waypoint) + + See header file for description. +--------------------------------------------------*/ +INT32 K_GetWaypointNextID(waypoint_t *waypoint) +{ + INT32 nextwaypointid = -1; + + if (waypoint == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL waypoint in K_GetWaypointNextID.\n"); + } + else if ((waypoint->mobj == NULL) || (P_MobjWasRemoved(waypoint->mobj) == true)) + { + CONS_Debug(DBG_GAMELOGIC, "NULL waypoint mobj in K_GetWaypointNextID.\n"); + } + else + { + nextwaypointid = waypoint->mobj->threshold; + } + + return nextwaypointid; +} + +/*-------------------------------------------------- + INT32 K_GetWaypointID(waypoint_t *waypoint) + + See header file for description. +--------------------------------------------------*/ +INT32 K_GetWaypointID(waypoint_t *waypoint) +{ + INT32 waypointid = -1; + + if (waypoint == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL waypoint in K_GetWaypointID.\n"); + } + else if ((waypoint->mobj == NULL) || (P_MobjWasRemoved(waypoint->mobj) == true)) + { + CONS_Debug(DBG_GAMELOGIC, "NULL waypoint mobj in K_GetWaypointID.\n"); + } + else + { + waypointid = waypoint->mobj->movecount; + } + + return waypointid; +} + +/*-------------------------------------------------- + UINT32 K_GetCircuitLength(void) + + See header file for description. +--------------------------------------------------*/ +UINT32 K_GetCircuitLength(void) +{ + return circuitlength; +} + +/*-------------------------------------------------- + waypoint_t *K_GetClosestWaypointToMobj(mobj_t *const mobj) + + See header file for description. +--------------------------------------------------*/ +waypoint_t *K_GetClosestWaypointToMobj(mobj_t *const mobj) +{ + waypoint_t *closestwaypoint = NULL; + + if ((mobj == NULL) || P_MobjWasRemoved(mobj)) + { + CONS_Debug(DBG_GAMELOGIC, "NULL mobj in K_GetClosestWaypointToMobj.\n"); + } + else + { + size_t i = 0U; + waypoint_t *checkwaypoint = NULL; + fixed_t closestdist = INT32_MAX; + fixed_t checkdist = INT32_MAX; + + for (i = 0; i < numwaypoints; i++) + { + checkwaypoint = &waypointheap[i]; + + checkdist = P_AproxDistance( + (mobj->x / FRACUNIT) - (checkwaypoint->mobj->x / FRACUNIT), + (mobj->y / FRACUNIT) - (checkwaypoint->mobj->y / FRACUNIT)); + checkdist = P_AproxDistance(checkdist, (mobj->z / FRACUNIT) - (checkwaypoint->mobj->z / FRACUNIT)); + + if (checkdist < closestdist) + { + closestwaypoint = checkwaypoint; + closestdist = checkdist; + } + } + } + + return closestwaypoint; +} + +/*-------------------------------------------------- + waypoint_t *K_GetBestWaypointForMobj(mobj_t *const mobj) + + See header file for description. +--------------------------------------------------*/ +waypoint_t *K_GetBestWaypointForMobj(mobj_t *const mobj) +{ + waypoint_t *bestwaypoint = NULL; + + if ((mobj == NULL) || P_MobjWasRemoved(mobj)) + { + CONS_Debug(DBG_GAMELOGIC, "NULL mobj in K_GetBestWaypointForMobj.\n"); + } + else + { + size_t i = 0U; + waypoint_t *checkwaypoint = NULL; + fixed_t closestdist = INT32_MAX; + fixed_t checkdist = INT32_MAX; + + for (i = 0; i < numwaypoints; i++) + { + checkwaypoint = &waypointheap[i]; + + checkdist = P_AproxDistance( + (mobj->x / FRACUNIT) - (checkwaypoint->mobj->x / FRACUNIT), + (mobj->y / FRACUNIT) - (checkwaypoint->mobj->y / FRACUNIT)); + checkdist = P_AproxDistance(checkdist, ((mobj->z / FRACUNIT) - (checkwaypoint->mobj->z / FRACUNIT)) * 4); + + if (checkdist < closestdist) + { + if (!P_CheckSight(mobj, checkwaypoint->mobj)) + { + // Save sight checks for the end, so we only do it if we have to + continue; + } + + bestwaypoint = checkwaypoint; + closestdist = checkdist; + } + } + } + + return bestwaypoint; +} + +/*-------------------------------------------------- + size_t K_GetWaypointHeapIndex(waypoint_t *waypoint) + + See header file for description. +--------------------------------------------------*/ +size_t K_GetWaypointHeapIndex(waypoint_t *waypoint) +{ + size_t waypointindex = SIZE_MAX; + + if (waypoint == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL waypoint in K_GetWaypointID.\n"); + } + else + { + waypointindex = waypoint - waypointheap; + } + + return waypointindex; +} + +/*-------------------------------------------------- + waypoint_t *K_GetWaypointFromIndex(size_t waypointindex) + + See header file for description. +--------------------------------------------------*/ +waypoint_t *K_GetWaypointFromIndex(size_t waypointindex) +{ + waypoint_t *waypoint = NULL; + + if (waypointindex >= numwaypoints) + { + CONS_Debug(DBG_GAMELOGIC, "waypointindex higher than number of waypoints in K_GetWaypointFromIndex"); + } + else + { + waypoint = &waypointheap[waypointindex]; + } + + return waypoint; +} + +/*-------------------------------------------------- + static UINT32 K_DistanceBetweenWaypoints(waypoint_t *const waypoint1, waypoint_t *const waypoint2) + + Gets the Euclidean distance between 2 waypoints by using their mobjs. Used for the heuristic. + + Input Arguments:- + waypoint1 - The first waypoint + waypoint2 - The second waypoint + + Return:- + Euclidean distance between the 2 waypoints +--------------------------------------------------*/ +static UINT32 K_DistanceBetweenWaypoints(waypoint_t *const waypoint1, waypoint_t *const waypoint2) +{ + UINT32 finaldist = UINT32_MAX; + I_Assert(waypoint1 != NULL); + I_Assert(waypoint2 != NULL); + + { + const fixed_t xydist = + P_AproxDistance(waypoint1->mobj->x - waypoint2->mobj->x, waypoint1->mobj->y - waypoint2->mobj->y); + const fixed_t xyzdist = P_AproxDistance(xydist, waypoint1->mobj->z - waypoint2->mobj->z); + finaldist = ((UINT32)xyzdist >> FRACBITS); + } + + return finaldist; +} + +/*-------------------------------------------------- + void K_DebugWaypointsSpawnLine(waypoint_t *const waypoint1, waypoint_t *const waypoint2) + + Draw a debugging line between 2 waypoints + + Input Arguments:- + waypoint1 - A waypoint to draw the line between + waypoint2 - The other waypoint to draw the line between +--------------------------------------------------*/ +static void K_DebugWaypointsSpawnLine(waypoint_t *const waypoint1, waypoint_t *const waypoint2) +{ + mobj_t *waypointmobj1, *waypointmobj2; + mobj_t *spawnedmobj; + fixed_t stepx, stepy, stepz; + fixed_t x, y, z; + UINT32 waypointdist; + INT32 n; + skincolors_t linkcolour = SKINCOLOR_GREEN; + + // This array is used to choose which colour should be on this connection + const skincolors_t linkcolours[] = { + SKINCOLOR_RED, + SKINCOLOR_BLUE, + SKINCOLOR_ORANGE, + SKINCOLOR_PINK, + SKINCOLOR_DREAM, + SKINCOLOR_CYAN, + SKINCOLOR_WHITE, + }; + const size_t linkcolourssize = sizeof(linkcolours) / sizeof(skincolors_t); + + // Error conditions + I_Assert(waypoint1 != NULL); + I_Assert(waypoint1->mobj != NULL); + I_Assert(waypoint2 != NULL); + I_Assert(waypoint2->mobj != NULL); + I_Assert(cv_kartdebugwaypoints.value != 0); + + linkcolour = K_GetWaypointID(waypoint1)%linkcolourssize; + + waypointmobj1 = waypoint1->mobj; + waypointmobj2 = waypoint2->mobj; + + n = SPARKLES_PER_CONNECTION; + + // For every 2048 fracunits, double the number of sparkles + waypointdist = K_DistanceBetweenWaypoints(waypoint1, waypoint2); + n *= (waypointdist / 2048) + 1; + + // Draw the line + stepx = (waypointmobj2->x - waypointmobj1->x) / n; + stepy = (waypointmobj2->y - waypointmobj1->y) / n; + stepz = (waypointmobj2->z - waypointmobj1->z) / n; + x = waypointmobj1->x; + y = waypointmobj1->y; + z = waypointmobj1->z; + do + { + if ((leveltime + n) % 16 <= 4) + { + spawnedmobj = P_SpawnMobj(x, y, z, MT_SPARK); + P_SetMobjState(spawnedmobj, S_THOK); + spawnedmobj->state->nextstate = S_NULL; + spawnedmobj->state->tics = 1; + spawnedmobj->frame = spawnedmobj->frame & ~FF_TRANSMASK; + spawnedmobj->color = linkcolours[linkcolour]; + spawnedmobj->scale = FixedMul(FRACUNIT/4, FixedDiv((15 - ((leveltime + n) % 16))*FRACUNIT, 15*FRACUNIT)); + } + + x += stepx; + y += stepy; + z += stepz; + } while (n--); +} + +/*-------------------------------------------------- + void K_DebugWaypointsVisualise(void) + + See header file for description. +--------------------------------------------------*/ +void K_DebugWaypointsVisualise(void) +{ + mobj_t *waypointmobj; + mobj_t *debugmobj; + waypoint_t *waypoint; + waypoint_t *otherwaypoint; + UINT32 i; + + if (waypointcap == NULL) + { + // No point putting a debug message here when it could easily happen when turning on the cvar in battle + return; + } + if (cv_kartdebugwaypoints.value == 0) + { + // Going to nip this in the bud and say no drawing all this without the cvar, it's not particularly optimised + return; + } + + // Hunt through the waypointcap so we can show all waypoint mobjs and not just ones that were able to be graphed + for (waypointmobj = waypointcap; waypointmobj != NULL; waypointmobj = waypointmobj->tracer) + { + waypoint = K_SearchWaypointHeapForMobj(waypointmobj); + + debugmobj = P_SpawnMobj(waypointmobj->x, waypointmobj->y, waypointmobj->z, MT_SPARK); + P_SetMobjState(debugmobj, S_THOK); + + debugmobj->frame &= ~FF_TRANSMASK; + debugmobj->frame |= FF_TRANS20; + + // There's a waypoint setup for this mobj! So draw that it's a valid waypoint and draw lines to its connections + if (waypoint != NULL) + { + if (waypoint->numnextwaypoints == 0 && waypoint->numprevwaypoints == 0) + { + debugmobj->color = SKINCOLOR_RED; + } + else if (waypoint->numnextwaypoints == 0 || waypoint->numprevwaypoints == 0) + { + debugmobj->color = SKINCOLOR_YELLOW; + } + else if (waypoint == players[displayplayers[0]].nextwaypoint) + { + debugmobj->color = SKINCOLOR_GREEN; + } + else + { + debugmobj->color = SKINCOLOR_BLUE; + } + + // Valid waypoint, so draw lines of SPARKLES to its next or previous waypoints + if (cv_kartdebugwaypoints.value == 1) + { + for (i = 0; i < waypoint->numnextwaypoints; i++) + { + if (waypoint->nextwaypoints[i] != NULL) + { + otherwaypoint = waypoint->nextwaypoints[i]; + K_DebugWaypointsSpawnLine(waypoint, otherwaypoint); + } + } + } + else if (cv_kartdebugwaypoints.value == 2) + { + for (i = 0; i < waypoint->numprevwaypoints; i++) + { + if (waypoint->prevwaypoints[i] != NULL) + { + otherwaypoint = waypoint->prevwaypoints[i]; + K_DebugWaypointsSpawnLine(waypoint, otherwaypoint); + } + } + } + } + else + { + debugmobj->color = SKINCOLOR_RED; + } + debugmobj->state->tics = 1; + debugmobj->state->nextstate = S_NULL; + } +} + +/*-------------------------------------------------- + static size_t K_GetOpensetBaseSize(void) + + Gets the base size the Openset hinary heap should have + + Input Arguments:- + None + + Return:- + The base size the Openset binary heap should have +--------------------------------------------------*/ +static size_t K_GetOpensetBaseSize(void) +{ + size_t returnsize = 0; + + returnsize = baseopensetsize; + + return returnsize; +} + +/*-------------------------------------------------- + static size_t K_GetClosedsetBaseSize(void) + + Gets the base size the Closedset heap should have + + Input Arguments:- + None + + Return:- + The base size the Closedset heap should have +--------------------------------------------------*/ +static size_t K_GetClosedsetBaseSize(void) +{ + size_t returnsize = 0; + + returnsize = baseclosedsetsize; + + return returnsize; +} + +/*-------------------------------------------------- + static size_t K_GetNodesArrayBaseSize(void) + + Gets the base size the Nodes array should have + + Input Arguments:- + None + + Return:- + The base size the Nodes array should have +--------------------------------------------------*/ +static size_t K_GetNodesArrayBaseSize(void) +{ + size_t returnsize = 0; + + returnsize = basenodesarraysize; + + return returnsize; +} + +/*-------------------------------------------------- + static void K_UpdateOpensetBaseSize(size_t newbaseopensetsize) + + Sets the new base size of the openset binary heap, if it is bigger than before. + + Input Arguments:- + newbaseopensetsize - The size to try and set the base Openset size to + + Return:- + None +--------------------------------------------------*/ +static void K_UpdateOpensetBaseSize(size_t newbaseopensetsize) +{ + if (newbaseopensetsize > baseopensetsize) + { + baseopensetsize = newbaseopensetsize; + } +} + +/*-------------------------------------------------- + static void K_UpdateClosedsetBaseSize(size_t newbaseclosedsetsize) + + Sets the new base size of the closedset heap, if it is bigger than before. + + Input Arguments:- + newbaseclosedsetsize - The size to try and set the base Closedset size to + + Return:- + None +--------------------------------------------------*/ +static void K_UpdateClosedsetBaseSize(size_t newbaseclosedsetsize) +{ + if (newbaseclosedsetsize > baseopensetsize) + { + baseclosedsetsize = newbaseclosedsetsize; + } +} + +/*-------------------------------------------------- + static void K_UpdateNodesArrayBaseSize(size_t newnodesarraysize) + + Sets the new base size of the nodes array, if it is bigger than before. + + Input Arguments:- + newnodesarraysize - The size to try and set the base nodes array size to + + Return:- + None +--------------------------------------------------*/ +static void K_UpdateNodesArrayBaseSize(size_t newnodesarraysize) +{ + if (newnodesarraysize > basenodesarraysize) + { + basenodesarraysize = newnodesarraysize; + } +} + +/*-------------------------------------------------- + static void **K_WaypointPathfindGetNext(void *data, size_t *numconnections) + + Gets the list of next waypoints as the connecting waypoints. For pathfinding only. + + Input Arguments:- + data - Should point to a waypoint_t to get nextwaypoints from + numconnections - Should point to a size_t to return the number of next waypoints + + Return:- + None +--------------------------------------------------*/ +static void **K_WaypointPathfindGetNext(void *data, size_t *numconnections) +{ + waypoint_t **connectingwaypoints = NULL; + + if (data == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "K_WaypointPathfindGetNext received NULL data.\n"); + } + else if (numconnections == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "K_WaypointPathfindGetNext received NULL numconnections.\n"); + } + else + { + waypoint_t *waypoint = (waypoint_t *)data; + connectingwaypoints = waypoint->nextwaypoints; + *numconnections = waypoint->numnextwaypoints; + } + + return (void**)connectingwaypoints; +} + +/*-------------------------------------------------- + static void **K_WaypointPathfindGetPrev(void *data, size_t *numconnections) + + Gets the list of previous waypoints as the connecting waypoints. For pathfinding only. + + Input Arguments:- + data - Should point to a waypoint_t to get prevwaypoints from + numconnections - Should point to a size_t to return the number of previous waypoints + + Return:- + None +--------------------------------------------------*/ +static void **K_WaypointPathfindGetPrev(void *data, size_t *numconnections) +{ + waypoint_t **connectingwaypoints = NULL; + + if (data == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "K_WaypointPathfindGetPrev received NULL data.\n"); + } + else if (numconnections == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "K_WaypointPathfindGetPrev received NULL numconnections.\n"); + } + else + { + waypoint_t *waypoint = (waypoint_t *)data; + connectingwaypoints = waypoint->prevwaypoints; + *numconnections = waypoint->numprevwaypoints; + } + + return (void**)connectingwaypoints; +} + +/*-------------------------------------------------- + static UINT32 *K_WaypointPathfindGetNextCosts(void* data) + + Gets the list of costs the next waypoints have. For pathfinding only. + + Input Arguments:- + data - Should point to a waypoint_t to get nextwaypointdistances from + + Return:- + A pointer to an array of UINT32's describing the cost of going from a waypoint to a next waypoint +--------------------------------------------------*/ +static UINT32 *K_WaypointPathfindGetNextCosts(void* data) +{ + UINT32 *connectingnodecosts = NULL; + + if (data == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "K_WaypointPathfindGetNextCosts received NULL data.\n"); + } + else + { + waypoint_t *waypoint = (waypoint_t *)data; + connectingnodecosts = waypoint->nextwaypointdistances; + } + + return connectingnodecosts; +} + +/*-------------------------------------------------- + static UINT32 *K_WaypointPathfindGetPrevCosts(void* data) + + Gets the list of costs the previous waypoints have. For pathfinding only. + + Input Arguments:- + data - Should point to a waypoint_t to get prevwaypointdistances from + + Return:- + A pointer to an array of UINT32's describing the cost of going from a waypoint to a previous waypoint +--------------------------------------------------*/ +static UINT32 *K_WaypointPathfindGetPrevCosts(void* data) +{ + UINT32 *connectingnodecosts = NULL; + + if (data == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "K_WaypointPathfindGetPrevCosts received NULL data.\n"); + } + else + { + waypoint_t *waypoint = (waypoint_t *)data; + connectingnodecosts = waypoint->prevwaypointdistances; + } + + return connectingnodecosts; +} + +/*-------------------------------------------------- + static UINT32 K_WaypointPathfindGetHeuristic(void *data1, void *data2) + + Gets the heuristic (euclidean distance) between 2 waypoints. For pathfinding only. + + Input Arguments:- + data1 - Should point to a waypoint_t for the first waypoint + data2 - Should point to a waypoint_t for the second waypoint + + Return:- + A UINT32 for the heuristic of the 2 waypoints. +--------------------------------------------------*/ +static UINT32 K_WaypointPathfindGetHeuristic(void *data1, void *data2) +{ + UINT32 nodeheuristic = UINT32_MAX; + + if (data1 == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "K_WaypointPathfindGetHeuristic received NULL data1.\n"); + } + else if (data2 == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "K_WaypointPathfindGetHeuristic received NULL data2.\n"); + } + else + { + waypoint_t *waypoint1 = (waypoint_t *)data1; + waypoint_t *waypoint2 = (waypoint_t *)data2; + + nodeheuristic = K_DistanceBetweenWaypoints(waypoint1, waypoint2); + } + + return nodeheuristic; +} + +/*-------------------------------------------------- + static boolean K_WaypointPathfindTraversableAllEnabled(void *data) + + Checks if a waypoint used as a pathfindnode is traversable. For pathfinding only. + Variant that accepts shortcut waypoints as traversable. + + Input Arguments:- + data - Should point to a waypoint_t to check traversability of + + Return:- + True if the waypoint is traversable, false otherwise. +--------------------------------------------------*/ +static boolean K_WaypointPathfindTraversableAllEnabled(void *data) +{ + boolean traversable = false; + + if (data == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "K_WaypointPathfindTraversableAllEnabled received NULL data.\n"); + } + else + { + waypoint_t *waypoint = (waypoint_t *)data; + traversable = (K_GetWaypointIsEnabled(waypoint) == true); + } + + return traversable; +} + +/*-------------------------------------------------- + static boolean K_WaypointPathfindTraversableNoShortcuts(void *data) + + Checks if a waypoint used as a pathfindnode is traversable. For pathfinding only. + Variant that does not accept shortcut waypoints as traversable. + + Input Arguments:- + data - Should point to a waypoint_t to check traversability of + + Return:- + True if the waypoint is traversable, false otherwise. +--------------------------------------------------*/ +static boolean K_WaypointPathfindTraversableNoShortcuts(void *data) +{ + boolean traversable = false; + + if (data == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "K_WaypointPathfindTraversableNoShortcuts received NULL data.\n"); + } + else + { + waypoint_t *waypoint = (waypoint_t *)data; + traversable = ((K_GetWaypointIsShortcut(waypoint) == false) && (K_GetWaypointIsEnabled(waypoint) == true)); + } + + return traversable; +} + +/*-------------------------------------------------- + boolean K_PathfindToWaypoint( + waypoint_t *const sourcewaypoint, + waypoint_t *const destinationwaypoint, + path_t *const returnpath, + const boolean useshortcuts, + const boolean huntbackwards) + + See header file for description. +--------------------------------------------------*/ +boolean K_PathfindToWaypoint( + waypoint_t *const sourcewaypoint, + waypoint_t *const destinationwaypoint, + path_t *const returnpath, + const boolean useshortcuts, + const boolean huntbackwards) +{ + boolean pathfound = false; + + if (sourcewaypoint == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL sourcewaypoint in K_PathfindToWaypoint.\n"); + } + else if (destinationwaypoint == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL destinationwaypoint in K_PathfindToWaypoint.\n"); + } + else if (((huntbackwards == false) && (sourcewaypoint->numnextwaypoints == 0)) + || ((huntbackwards == true) && (sourcewaypoint->numprevwaypoints == 0))) + { + CONS_Debug(DBG_GAMELOGIC, + "K_PathfindToWaypoint: sourcewaypoint with ID %d has no next waypoint\n", + K_GetWaypointID(sourcewaypoint)); + } + else if (((huntbackwards == false) && (destinationwaypoint->numprevwaypoints == 0)) + || ((huntbackwards == true) && (destinationwaypoint->numnextwaypoints == 0))) + { + CONS_Debug(DBG_GAMELOGIC, + "K_PathfindToWaypoint: destinationwaypoint with ID %d has no previous waypoint\n", + K_GetWaypointID(destinationwaypoint)); + } + else + { + pathfindsetup_t pathfindsetup = {}; + getconnectednodesfunc nextnodesfunc = K_WaypointPathfindGetNext; + getnodeconnectioncostsfunc nodecostsfunc = K_WaypointPathfindGetNextCosts; + getnodeheuristicfunc heuristicfunc = K_WaypointPathfindGetHeuristic; + getnodetraversablefunc traversablefunc = K_WaypointPathfindTraversableNoShortcuts; + + if (huntbackwards) + { + nextnodesfunc = K_WaypointPathfindGetPrev; + nodecostsfunc = K_WaypointPathfindGetPrevCosts; + } + if (useshortcuts) + { + traversablefunc = K_WaypointPathfindTraversableAllEnabled; + } + + + pathfindsetup.opensetcapacity = K_GetOpensetBaseSize(); + pathfindsetup.closedsetcapacity = K_GetClosedsetBaseSize(); + pathfindsetup.nodesarraycapacity = K_GetNodesArrayBaseSize(); + pathfindsetup.startnodedata = sourcewaypoint; + pathfindsetup.endnodedata = destinationwaypoint; + pathfindsetup.getconnectednodes = nextnodesfunc; + pathfindsetup.getconnectioncosts = nodecostsfunc; + pathfindsetup.getheuristic = heuristicfunc; + pathfindsetup.gettraversable = traversablefunc; + + pathfound = K_PathfindAStar(returnpath, &pathfindsetup); + + K_UpdateOpensetBaseSize(pathfindsetup.opensetcapacity); + K_UpdateClosedsetBaseSize(pathfindsetup.closedsetcapacity); + K_UpdateNodesArrayBaseSize(pathfindsetup.nodesarraycapacity); + } + + return pathfound; +} + +/*-------------------------------------------------- + waypoint_t *K_GetNextWaypointToDestination( + waypoint_t *const sourcewaypoint, + waypoint_t *const destinationwaypoint, + const boolean useshortcuts, + const boolean huntbackwards) + + See header file for description. +--------------------------------------------------*/ +waypoint_t *K_GetNextWaypointToDestination( + waypoint_t *const sourcewaypoint, + waypoint_t *const destinationwaypoint, + const boolean useshortcuts, + const boolean huntbackwards) +{ + waypoint_t *nextwaypoint = NULL; + + if (sourcewaypoint == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL sourcewaypoint in K_GetNextWaypointToDestination.\n"); + } + else if (destinationwaypoint == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL destinationwaypoint in K_GetNextWaypointToDestination.\n"); + } + else if (sourcewaypoint == destinationwaypoint) + { + // Source and destination waypoint are the same, we're already there + nextwaypoint = destinationwaypoint; + } + else if (((huntbackwards == false) && (sourcewaypoint->numnextwaypoints == 0)) + || ((huntbackwards == true) && (sourcewaypoint->numprevwaypoints == 0))) + { + CONS_Debug(DBG_GAMELOGIC, + "K_GetNextWaypointToDestination: sourcewaypoint with ID %d has no next waypoint\n", + K_GetWaypointID(sourcewaypoint)); + } + else if (((huntbackwards == false) && (destinationwaypoint->numprevwaypoints == 0)) + || ((huntbackwards == true) && (destinationwaypoint->numnextwaypoints == 0))) + { + CONS_Debug(DBG_GAMELOGIC, + "K_GetNextWaypointToDestination: destinationwaypoint with ID %d has no previous waypoint\n", + K_GetWaypointID(destinationwaypoint)); + } + else + { + // If there is only 1 next waypoint it doesn't matter if it's a shortcut + if ((huntbackwards == false) && sourcewaypoint->numnextwaypoints == 1) + { + nextwaypoint = sourcewaypoint->nextwaypoints[0]; + } + else if ((huntbackwards == true) && sourcewaypoint->numprevwaypoints == 1) + { + nextwaypoint = sourcewaypoint->prevwaypoints[0]; + } + else + { + path_t pathtowaypoint = {}; + pathfindsetup_t pathfindsetup = {}; + boolean pathfindsuccess = false; + getconnectednodesfunc nextnodesfunc = K_WaypointPathfindGetNext; + getnodeconnectioncostsfunc nodecostsfunc = K_WaypointPathfindGetNextCosts; + getnodeheuristicfunc heuristicfunc = K_WaypointPathfindGetHeuristic; + getnodetraversablefunc traversablefunc = K_WaypointPathfindTraversableNoShortcuts; + + if (huntbackwards) + { + nextnodesfunc = K_WaypointPathfindGetPrev; + nodecostsfunc = K_WaypointPathfindGetPrevCosts; + } + if (useshortcuts) + { + traversablefunc = K_WaypointPathfindTraversableAllEnabled; + } + + + pathfindsetup.opensetcapacity = K_GetOpensetBaseSize(); + pathfindsetup.closedsetcapacity = K_GetClosedsetBaseSize(); + pathfindsetup.nodesarraycapacity = K_GetNodesArrayBaseSize(); + pathfindsetup.startnodedata = sourcewaypoint; + pathfindsetup.endnodedata = destinationwaypoint; + pathfindsetup.getconnectednodes = nextnodesfunc; + pathfindsetup.getconnectioncosts = nodecostsfunc; + pathfindsetup.getheuristic = heuristicfunc; + pathfindsetup.gettraversable = traversablefunc; + + pathfindsuccess = K_PathfindAStar(&pathtowaypoint, &pathfindsetup); + + K_UpdateOpensetBaseSize(pathfindsetup.opensetcapacity); + K_UpdateClosedsetBaseSize(pathfindsetup.closedsetcapacity); + K_UpdateNodesArrayBaseSize(pathfindsetup.nodesarraycapacity); + + if (pathfindsuccess) + { + // A direct path to the destination has been found. + if (pathtowaypoint.numnodes > 1) + { + nextwaypoint = (waypoint_t*)pathtowaypoint.array[1].nodedata; + } + else + { + // Shouldn't happen, as this is the source waypoint. + CONS_Debug(DBG_GAMELOGIC, "Only one waypoint pathfound in K_GetNextWaypointToDestination.\n"); + nextwaypoint = (waypoint_t*)pathtowaypoint.array[0].nodedata; + } + + Z_Free(pathtowaypoint.array); + } + else + { + size_t i = 0U; + waypoint_t **nextwaypointlist = NULL; + size_t numnextwaypoints = 0U; + boolean waypointisenabled = true; + boolean waypointisshortcut = false; + + if (huntbackwards) + { + nextwaypointlist = sourcewaypoint->prevwaypoints; + numnextwaypoints = sourcewaypoint->numprevwaypoints; + } + else + { + nextwaypointlist = sourcewaypoint->nextwaypoints; + numnextwaypoints = sourcewaypoint->numnextwaypoints; + } + + // No direct path to the destination has been found, choose a next waypoint from what is available + // 1. If shortcuts are allowed, pick the first shortcut path that is enabled + // 2. If shortcuts aren't allowed, or there are no shortcuts, pick the first enabled waypoint + // 3. If there's no waypoints enabled, then nothing can be done and there is no next waypoint + if (useshortcuts) + { + for (i = 0U; i < numnextwaypoints; i++) + { + waypointisenabled = K_GetWaypointIsEnabled(nextwaypointlist[i]); + waypointisshortcut = K_GetWaypointIsShortcut(nextwaypointlist[i]); + + if (waypointisenabled && waypointisshortcut) + { + nextwaypoint = nextwaypointlist[i]; + break; + } + } + } + + if (nextwaypoint == NULL) + { + for (i = 0U; i < numnextwaypoints; i++) + { + waypointisenabled = K_GetWaypointIsEnabled(nextwaypointlist[i]); + + if (waypointisenabled) + { + nextwaypoint = nextwaypointlist[i]; + break; + } + } + } + } + } + } + + return nextwaypoint; +} + +/*-------------------------------------------------- + boolean K_CheckWaypointForMobj(waypoint_t *const waypoint, void *const mobjpointer) + + Compares a waypoint's mobj and a void pointer that *should* point to an mobj. Intended for use with the + K_SearchWaypoint functions ONLY. No, it is not my responsibility to make sure the pointer you sent in is + actually an mobj. + + Input Arguments:- + waypoint - The waypoint that is currently being compared against + mobjpointer - A pointer that should be to an mobj to check with the waypoint for matching + + Return:- + The waypoint that uses that mobj, NULL if it wasn't found, NULL if it isn't an MT_WAYPOINT +--------------------------------------------------*/ +static boolean K_CheckWaypointForMobj(waypoint_t *const waypoint, void *const mobjpointer) +{ + boolean mobjsmatch = false; + + // Error Conditions + I_Assert(waypoint != NULL); + I_Assert(waypoint->mobj != NULL); + I_Assert(mobjpointer != NULL); + + { + mobj_t *const mobj = (mobj_t *)mobjpointer; + + if (P_MobjWasRemoved(mobj)) + { + CONS_Debug(DBG_GAMELOGIC, "Mobj Was Removed in K_CheckWaypointForMobj"); + } + else if (mobj->type != MT_WAYPOINT) + { + CONS_Debug(DBG_GAMELOGIC, "Non MT_WAYPOINT mobj in K_CheckWaypointForMobj. Type=%d.\n", mobj->type); + } + else + { + // All that error checking for 3 lines :^) + if (waypoint->mobj == mobj) + { + mobjsmatch = true; + } + } + } + + return mobjsmatch; +} + +/*-------------------------------------------------- + waypoint_t *K_TraverseWaypoints( + waypoint_t *waypoint, + boolean (*conditionalfunc)(waypoint_t *const, void *const), + void *const condition, + boolean *const visitedarray) + + Searches through the waypoint list for a waypoint that matches a condition, just does a simple flood search + of the graph with no pathfinding + + Input Arguments:- + waypoint - The waypoint that is currently being checked, goes through nextwaypoints after this one + conditionalfunc - The function that will be used to check a waypoint against condition + condition - the condition being checked by conditionalfunc + visitedarray - An array of booleans that let us know if a waypoint has already been checked, marked to true + when one is, so we don't repeat going down a path. Cannot be changed to a different pointer + + Return:- + The waypoint that uses that mobj, NULL if it wasn't found, NULL if it isn't an MT_WAYPOINT +--------------------------------------------------*/ +static waypoint_t *K_TraverseWaypoints( + waypoint_t *waypoint, + boolean (*conditionalfunc)(waypoint_t *const, void *const), + void *const condition, + boolean *const visitedarray) +{ + waypoint_t *foundwaypoint = NULL; + + // Error conditions + I_Assert(condition != NULL); + I_Assert(conditionalfunc != NULL); + I_Assert(visitedarray != NULL); + +searchwaypointstart: + I_Assert(waypoint != NULL); + + { + size_t waypointindex = K_GetWaypointHeapIndex(waypoint); + // If we've already visited this waypoint, we've already checked the next waypoints, no point continuing + if ((waypointindex != SIZE_MAX) && (visitedarray[waypointindex] != true)) + { + // Mark this waypoint as being visited + visitedarray[waypointindex] = true; + + if (conditionalfunc(waypoint, condition) == true) + { + foundwaypoint = waypoint; + } + else + { + // If this waypoint only has one next waypoint, set the waypoint to be the next one and jump back + // to the start, this is to avoid going too deep into the stack where we can + // Yes this is a horrible horrible goto, but the alternative is a do while loop with an extra + // variable, which is slightly more confusing. This is probably the fastest and least confusing + // option that keeps this functionality + if (waypoint->numnextwaypoints == 1 && waypoint->nextwaypoints[0] != NULL) + { + waypoint = waypoint->nextwaypoints[0]; + goto searchwaypointstart; + } + else if (waypoint->numnextwaypoints != 0) + { + // The nesting here is a bit nasty, but it's better than potentially a lot of function calls on + // the stack, and another function would be very small in this case + UINT32 i; + // For each next waypoint, Search through it's path continuation until we hopefully find the one + // we're looking for + for (i = 0; i < waypoint->numnextwaypoints; i++) + { + if (waypoint->nextwaypoints[i] != NULL) + { + foundwaypoint = K_TraverseWaypoints(waypoint->nextwaypoints[i], conditionalfunc, + condition, visitedarray); + + if (foundwaypoint != NULL) + { + break; + } + } + } + } + else + { + // No next waypoints, this function will be returned from + } + } + } + } + + return foundwaypoint; +} + +/*-------------------------------------------------- + waypoint_t *K_SearchWaypointGraph( + boolean (*conditionalfunc)(waypoint_t *const, void *const), + void *const condition) + + Searches through the waypoint graph for a waypoint that matches the conditional + + Input Arguments:- + conditionalfunc - The function that will be used to check a waypoint against condition + condition - the condition being checked by conditionalfunc + + Return:- + The waypoint that uses that mobj, NULL if it wasn't found, NULL if it isn't an MT_WAYPOINT +--------------------------------------------------*/ +static waypoint_t *K_SearchWaypointGraph( + boolean (*conditionalfunc)(waypoint_t *const, void *const), + void *const condition) +{ + boolean *visitedarray = NULL; + waypoint_t *foundwaypoint = NULL; + + // Error conditions + I_Assert(condition != NULL); + I_Assert(conditionalfunc != NULL); + I_Assert(firstwaypoint != NULL); + + visitedarray = Z_Calloc(numwaypoints * sizeof(boolean), PU_STATIC, NULL); + foundwaypoint = K_TraverseWaypoints(firstwaypoint, conditionalfunc, condition, visitedarray); + Z_Free(visitedarray); + + return foundwaypoint; +} + +/*-------------------------------------------------- + waypoint_t *K_SearchWaypointGraphForMobj(mobj_t * const mobj) + + See header file for description. +--------------------------------------------------*/ +waypoint_t *K_SearchWaypointGraphForMobj(mobj_t *const mobj) +{ + waypoint_t *foundwaypoint = NULL; + + if (mobj == NULL || P_MobjWasRemoved(mobj)) + { + CONS_Debug(DBG_GAMELOGIC, "NULL mobj in K_SearchWaypointGraphForMobj.\n"); + } + else if (mobj->type != MT_WAYPOINT) + { + CONS_Debug(DBG_GAMELOGIC, "Non MT_WAYPOINT mobj in K_SearchWaypointGraphForMobj. Type=%d.\n", mobj->type); + } + else + { + foundwaypoint = K_SearchWaypointGraph(K_CheckWaypointForMobj, (void *)mobj); + } + + return foundwaypoint; +} + +/*-------------------------------------------------- + waypoint_t *K_SearchWaypointHeap( + boolean (*conditionalfunc)(waypoint_t *const, void *const), + void *const condition) + + Searches through the waypoint heap for a waypoint that matches the conditional + + Input Arguments:- + conditionalfunc - The function that will be used to check a waypoint against condition + condition - the condition being checked by conditionalfunc + + Return:- + The waypoint that uses that mobj, NULL if it wasn't found, NULL if it isn't an MT_WAYPOINT +--------------------------------------------------*/ +static waypoint_t *K_SearchWaypointHeap( + boolean (*conditionalfunc)(waypoint_t *const, void *const), + void *const condition) +{ + UINT32 i = 0; + waypoint_t *foundwaypoint = NULL; + + // Error conditions + I_Assert(condition != NULL); + I_Assert(conditionalfunc != NULL); + I_Assert(waypointheap != NULL); + + // Simply search through the waypointheap for the waypoint which matches the condition. Much simpler when no + // pathfinding is needed. Search up to numwaypoints and NOT numwaypointmobjs as numwaypoints is the real number of + // waypoints setup in the heap while numwaypointmobjs ends up being the capacity + for (i = 0; i < numwaypoints; i++) + { + if (conditionalfunc(&waypointheap[i], condition) == true) + { + foundwaypoint = &waypointheap[i]; + break; + } + } + + return foundwaypoint; +} + +/*-------------------------------------------------- + waypoint_t *K_SearchWaypointHeapForMobj(mobj_t *const mobj) + + See header file for description. +--------------------------------------------------*/ +waypoint_t *K_SearchWaypointHeapForMobj(mobj_t *const mobj) +{ + waypoint_t *foundwaypoint = NULL; + + if (mobj == NULL || P_MobjWasRemoved(mobj)) + { + CONS_Debug(DBG_GAMELOGIC, "NULL mobj in K_SearchWaypointHeapForMobj.\n"); + } + else if (mobj->type != MT_WAYPOINT) + { + CONS_Debug(DBG_GAMELOGIC, "Non MT_WAYPOINT mobj in K_SearchWaypointHeapForMobj. Type=%d.\n", mobj->type); + } + else + { + foundwaypoint = K_SearchWaypointHeap(K_CheckWaypointForMobj, (void *)mobj); + } + + return foundwaypoint; +} + +/*-------------------------------------------------- + static UINT32 K_SetupCircuitLength(void) + + Sets up the Circuit Length by getting the best path from the finishwaypoint back to itself. + On sprint maps, circuitlength is 0. + + Input Arguments:- + None + + Return:- + Length of the circuit +--------------------------------------------------*/ +static UINT32 K_SetupCircuitLength(void) +{ + I_Assert(firstwaypoint != NULL); + I_Assert(numwaypoints > 0U); + I_Assert(finishline != NULL); + + // The circuit length only makes sense in circuit maps, sprint maps do not need to use it + // The main usage of the circuit length is to add onto a player's distance to finish line so crossing the finish + // line places people correctly relative to each other + if ((mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE) == LF_SECTIONRACE) + { + circuitlength = 0U; + } + else + { + // Create a fake finishline waypoint, then try and pathfind to the finishline from it + waypoint_t fakefinishline = *finishline; + path_t bestcircuitpath = {}; + const boolean useshortcuts = false; + const boolean huntbackwards = false; + + K_PathfindToWaypoint(&fakefinishline, finishline, &bestcircuitpath, useshortcuts, huntbackwards); + + circuitlength = bestcircuitpath.totaldist; + + Z_Free(bestcircuitpath.array); + } + + return circuitlength; +} + +/*-------------------------------------------------- + static void K_AddPrevToWaypoint(waypoint_t *const waypoint, waypoint_t *const prevwaypoint) + + Adds another waypoint to a waypoint's previous waypoint list, this needs to be done like this because there is no + way to identify previous waypoints from just IDs, so we need to reallocate the memory for every previous waypoint + + Input Arguments:- + waypoint - The waypoint which is having its previous waypoint list added to + prevwaypoint - The waypoint which is being added to the previous waypoint list + + Return:- + Pointer to waypoint_t for the rest of the waypoint data to be placed into +--------------------------------------------------*/ +static void K_AddPrevToWaypoint(waypoint_t *const waypoint, waypoint_t *const prevwaypoint) +{ + // Error conditions + I_Assert(waypoint != NULL); + I_Assert(prevwaypoint != NULL); + + waypoint->numprevwaypoints++; + waypoint->prevwaypoints = + Z_Realloc(waypoint->prevwaypoints, waypoint->numprevwaypoints * sizeof(waypoint_t *), PU_LEVEL, NULL); + + if (!waypoint->prevwaypoints) + { + I_Error("K_AddPrevToWaypoint: Failed to reallocate memory for previous waypoints."); + } + + waypoint->prevwaypointdistances = + Z_Realloc(waypoint->prevwaypointdistances, waypoint->numprevwaypoints * sizeof(fixed_t), PU_LEVEL, NULL); + + if (!waypoint->prevwaypointdistances) + { + I_Error("K_AddPrevToWaypoint: Failed to reallocate memory for previous waypoint distances."); + } + + waypoint->prevwaypoints[waypoint->numprevwaypoints - 1] = prevwaypoint; +} + +/*-------------------------------------------------- + static waypoint_t *K_MakeWaypoint(mobj_t *const mobj) + + Make a new waypoint from a map object. Setups up most of the data for it, and allocates most memory + Remaining creation is handled in K_SetupWaypoint + + Input Arguments:- + mobj - The map object that this waypoint is represented by + + Return:- + Pointer to the setup waypoint, NULL if one was not setup +--------------------------------------------------*/ +static waypoint_t *K_MakeWaypoint(mobj_t *const mobj) +{ + waypoint_t *madewaypoint = NULL; + mobj_t *otherwaypointmobj = NULL; + + // Error conditions + I_Assert(mobj != NULL); + I_Assert(!P_MobjWasRemoved(mobj)); + I_Assert(waypointcap != NULL); // No waypoint mobjs in map load + I_Assert(numwaypoints < numwaypointmobjs); // waypoint array reached max capacity + + // numwaypoints is incremented later in K_SetupWaypoint + madewaypoint = &waypointheap[numwaypoints]; + numwaypoints++; + + P_SetTarget(&madewaypoint->mobj, mobj); + + // Don't allow a waypoint that has its next ID set to itself to work + if (mobj->threshold != mobj->movecount) { + // Go through the other waypoint mobjs in the map to find out how many waypoints are after this one + for (otherwaypointmobj = waypointcap; otherwaypointmobj != NULL; otherwaypointmobj = otherwaypointmobj->tracer) + { + // threshold = next waypoint id, movecount = my id + if (mobj->threshold == otherwaypointmobj->movecount) + { + madewaypoint->numnextwaypoints++; + } + } + } + + // No next waypoints + if (madewaypoint->numnextwaypoints != 0) + { + // Allocate memory to hold enough pointers to all of the next waypoints + madewaypoint->nextwaypoints = + Z_Calloc(madewaypoint->numnextwaypoints * sizeof(waypoint_t *), PU_LEVEL, NULL); + if (madewaypoint->nextwaypoints == NULL) + { + I_Error("K_MakeWaypoint: Out of Memory allocating next waypoints."); + } + madewaypoint->nextwaypointdistances = + Z_Calloc(madewaypoint->numnextwaypoints * sizeof(fixed_t), PU_LEVEL, NULL); + if (madewaypoint->nextwaypointdistances == NULL) + { + I_Error("K_MakeWaypoint: Out of Memory allocating next waypoint distances."); + } + } + + return madewaypoint; +} + +/*-------------------------------------------------- + static waypoint_t *K_SetupWaypoint(mobj_t *const mobj) + + Either gets an already made waypoint, or sets up a new waypoint for an mobj, + including next and previous waypoints + + Input Arguments:- + mobj - The map object that this waypoint is represented by + + Return:- + Pointer to the setup waypoint, NULL if one was not setup +--------------------------------------------------*/ +static waypoint_t *K_SetupWaypoint(mobj_t *const mobj) +{ + waypoint_t *thiswaypoint = NULL; + + // Error conditions + I_Assert(mobj != NULL); + I_Assert(!P_MobjWasRemoved(mobj)); + I_Assert(mobj->type == MT_WAYPOINT); + I_Assert(waypointcap != NULL); // no waypoint mobjs in map load + + // If we have waypoints already created, search through them first to see if this mobj is already added. + if (firstwaypoint != NULL) + { + thiswaypoint = K_SearchWaypointHeapForMobj(mobj); + } + + // The waypoint hasn't already been made, so make it + if (thiswaypoint == NULL) + { + mobj_t *otherwaypointmobj = NULL; + UINT32 nextwaypointindex = 0; + + thiswaypoint = K_MakeWaypoint(mobj); + + if (thiswaypoint != NULL) + { + // Set the first waypoint if it isn't already + if (firstwaypoint == NULL) + { + firstwaypoint = thiswaypoint; + } + + if (K_GetWaypointIsFinishline(thiswaypoint)) + { + if (finishline != NULL) + { + const INT32 oldfinishlineid = K_GetWaypointID(finishline); + const INT32 thiswaypointid = K_GetWaypointID(thiswaypoint); + CONS_Alert( + CONS_WARNING, "Multiple finish line waypoints with IDs %d and %d! Using %d.", + oldfinishlineid, thiswaypointid, thiswaypointid); + } + finishline = thiswaypoint; + } + + if (thiswaypoint->numnextwaypoints > 0) + { + waypoint_t *nextwaypoint = NULL; + fixed_t nextwaypointdistance = 0; + // Go through the waypoint mobjs to setup the next waypoints and make this waypoint know they're its + // next. I kept this out of K_MakeWaypoint so the stack isn't gone down as deep + for (otherwaypointmobj = waypointcap; + otherwaypointmobj != NULL; + otherwaypointmobj = otherwaypointmobj->tracer) + { + // threshold = next waypoint id, movecount = my id + if (mobj->threshold == otherwaypointmobj->movecount) + { + nextwaypoint = K_SetupWaypoint(otherwaypointmobj); + nextwaypointdistance = K_DistanceBetweenWaypoints(thiswaypoint, nextwaypoint); + thiswaypoint->nextwaypoints[nextwaypointindex] = nextwaypoint; + thiswaypoint->nextwaypointdistances[nextwaypointindex] = nextwaypointdistance; + K_AddPrevToWaypoint(nextwaypoint, thiswaypoint); + nextwaypoint->prevwaypointdistances[nextwaypoint->numprevwaypoints - 1] = nextwaypointdistance; + nextwaypointindex++; + } + if (nextwaypointindex >= thiswaypoint->numnextwaypoints) + { + break; + } + } + } + else + { + CONS_Alert( + CONS_WARNING, "Waypoint with ID %d has no next waypoint.\n", K_GetWaypointID(thiswaypoint)); + } + } + else + { + CONS_Debug(DBG_SETUP, "K_SetupWaypoint failed to make new waypoint with ID %d.\n", mobj->movecount); + } + } + + return thiswaypoint; +} + +/*-------------------------------------------------- + static boolean K_AllocateWaypointHeap(void) + + Allocates the waypoint heap enough space for the number of waypoint mobjs on the map + + Return:- + True if the allocation was successful, false if it wasn't. Will I_Error if out of memory still. +--------------------------------------------------*/ +static boolean K_AllocateWaypointHeap(void) +{ + mobj_t *waypointmobj = NULL; + boolean allocationsuccessful = false; + + // Error conditions + I_Assert(waypointheap == NULL); // waypointheap is already allocated + I_Assert(waypointcap != NULL); // no waypoint mobjs at map load + + // This should be an allocation for the first time. Reset the number of mobjs back to 0 if it's not already + numwaypointmobjs = 0; + + // Find how many waypoint mobjs there are in the map, this is the maximum number of waypoints there CAN be + for (waypointmobj = waypointcap; waypointmobj != NULL; waypointmobj = waypointmobj->tracer) + { + if (waypointmobj->type != MT_WAYPOINT) + { + CONS_Debug(DBG_SETUP, + "Non MT_WAYPOINT mobj in waypointcap in K_AllocateWaypointHeap. Type=%d\n.", waypointmobj->type); + continue; + } + + numwaypointmobjs++; + } + + if (numwaypointmobjs > 0) + { + // Allocate space in the heap for every mobj, it's possible some mobjs aren't linked up and not all of the + // heap allocated will be used, but it's a fairly reasonable assumption that this isn't going to be awful + waypointheap = Z_Calloc(numwaypointmobjs * sizeof(waypoint_t), PU_LEVEL, NULL); + + if (waypointheap == NULL) + { + // We could theoretically CONS_Debug here and continue without using waypoints, but I feel that will + // require error checks that will end up spamming the console when we think waypoints SHOULD be working. + // Safer to just exit if out of memory + I_Error("K_AllocateWaypointHeap: Out of memory."); + } + allocationsuccessful = true; + } + else + { + CONS_Debug(DBG_SETUP, "No waypoint mobjs in waypointcap.\n"); + } + + return allocationsuccessful; +} + +/*-------------------------------------------------- + void K_FreeWaypoints(void) + + For safety, this will free the waypointheap and all the waypoints allocated if they aren't already before they + are setup. If the PU_LEVEL tag is cleared, make sure to call K_ClearWaypoints or this will try to free already + freed memory! +--------------------------------------------------*/ +static void K_FreeWaypoints(void) +{ + if (waypointheap != NULL) + { + // Free the waypointheap + Z_Free(waypointheap); + } + + K_ClearWaypoints(); +} + +/*-------------------------------------------------- + boolean K_SetupWaypointList(void) + + See header file for description. +--------------------------------------------------*/ +boolean K_SetupWaypointList(void) +{ + boolean setupsuccessful = false; + + K_FreeWaypoints(); + + if (!waypointcap) + { + CONS_Alert(CONS_ERROR, "No waypoints in map.\n"); + } + else + { + if (K_AllocateWaypointHeap() == true) + { + mobj_t *waypointmobj = NULL; + + // Loop through the waypointcap here so that all waypoints are added to the heap, and allow easier debugging + for (waypointmobj = waypointcap; waypointmobj; waypointmobj = waypointmobj->tracer) + { + K_SetupWaypoint(waypointmobj); + } + + if (firstwaypoint == NULL) + { + CONS_Alert(CONS_ERROR, "No waypoints in map.\n"); + } + else + { + CONS_Debug(DBG_SETUP, "Successfully setup %s waypoints.\n", sizeu1(numwaypoints)); + if (finishline == NULL) + { + CONS_Alert( + CONS_WARNING, "No finish line waypoint in the map! Using first setup waypoint with ID %d.\n", + K_GetWaypointID(firstwaypoint)); + finishline = firstwaypoint; + } + + if (K_SetupCircuitLength() == 0 + && ((mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE) != LF_SECTIONRACE)) + { + CONS_Alert(CONS_ERROR, "Circuit track waypoints do not form a circuit.\n"); + } + + setupsuccessful = true; + } + } + } + + return setupsuccessful; +} + +/*-------------------------------------------------- + void K_ClearWaypoints(void) + + See header file for description. +--------------------------------------------------*/ +void K_ClearWaypoints(void) +{ + waypointheap = NULL; + firstwaypoint = NULL; + finishline = NULL; + numwaypoints = 0U; + numwaypointmobjs = 0U; + circuitlength = 0U; +} diff --git a/src/k_waypoint.h b/src/k_waypoint.h new file mode 100644 index 000000000..fb8d37f20 --- /dev/null +++ b/src/k_waypoint.h @@ -0,0 +1,338 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 by Sean "Sryder" Ryder +// Copyright (C) 2018-2020 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file k_waypoint.h +/// \brief Waypoint handling from the relevant mobjs +/// Setup and interfacing with waypoints for the main game + +#ifndef __K_WAYPOINT__ +#define __K_WAYPOINT__ + +#include "doomtype.h" +#include "p_mobj.h" +#include "k_pathfind.h" + +typedef struct waypoint_s +{ + mobj_t *mobj; + struct waypoint_s **nextwaypoints; + struct waypoint_s **prevwaypoints; + UINT32 *nextwaypointdistances; + UINT32 *prevwaypointdistances; + size_t numnextwaypoints; + size_t numprevwaypoints; +} waypoint_t; + + +// AVAILABLE FOR LUA + + +/*-------------------------------------------------- + waypoint_t *K_GetFinishLineWaypoint(void); + + Returns the waypoint actually being used as the finish line. + + Input Arguments:- + None + + Return:- + The waypoint that is being used as the finishline. +--------------------------------------------------*/ + +waypoint_t *K_GetFinishLineWaypoint(void); + + +/*-------------------------------------------------- + boolean K_GetWaypointIsFinishline(waypoint_t *waypoint) + + Returns whether the waypoint is marked as the finishline. This may not actually be the finishline. + + Input Arguments:- + waypoint - The waypoint to return finishline status of. + + Return:- + true if the waypoint is marked as being the finishline, false if it isn't. +--------------------------------------------------*/ + +boolean K_GetWaypointIsFinishline(waypoint_t *waypoint); + + +/*-------------------------------------------------- + boolean K_GetWaypointIsShortcut(waypoint_t *waypoint) + + Returns whether the waypoint is part of a shortcut. + + Input Arguments:- + waypoint - The waypoint to return shortcut status of. + + Return:- + true if the waypoint is a shortcut, false if it isn't. +--------------------------------------------------*/ + +boolean K_GetWaypointIsShortcut(waypoint_t *waypoint); + + +/*-------------------------------------------------- + boolean K_GetWaypointIsEnabled(waypoint_t *waypoint) + + Returns whether the waypoint is enabled. + + Input Arguments:- + waypoint - The waypoint to return enabled status of. + + Return:- + true if the waypoint is enabled, false if it isn't. +--------------------------------------------------*/ + +boolean K_GetWaypointIsEnabled(waypoint_t *waypoint); + + +/*-------------------------------------------------- + boolean K_GetWaypointIsSpawnpoint(waypoint_t *waypoint) + + Returns whether the waypoint is a spawnpoint. + + Input Arguments:- + waypoint - The waypoint to return spawnpoint status of. + + Return:- + true if the waypoint is a spawnpoint, false if it isn't. +--------------------------------------------------*/ + +boolean K_GetWaypointIsSpawnpoint(waypoint_t *waypoint); + + +/*-------------------------------------------------- + INT32 K_GetWaypointNextID(waypoint_t *waypoint) + + Returns the waypoint's next waypoint ID. + + Input Arguments:- + waypoint - The waypoint to return the next waypoint ID of. + + Return:- + The next waypoint ID, -1 if there is no waypoint or mobj. +--------------------------------------------------*/ + +INT32 K_GetWaypointNextID(waypoint_t *waypoint); + + +/*-------------------------------------------------- + INT32 K_GetWaypointID(waypoint_t *waypoint) + + Returns the waypoint's ID. + + Input Arguments:- + waypoint - The waypoint to return the ID of. + + Return:- + The waypoint ID, -1 if there is no waypoint or mobj. +--------------------------------------------------*/ +INT32 K_GetWaypointID(waypoint_t *waypoint); + + +/*-------------------------------------------------- + UINT32 K_GetCircuitLength(void) + + Returns the circuit length, 0 on sprint maps. + + Input Arguments:- + + Return:- + The circuit length. +--------------------------------------------------*/ +UINT32 K_GetCircuitLength(void); + + +/*-------------------------------------------------- + waypoint_t *K_GetClosestWaypointToMobj(mobj_t *const mobj) + + Returns the closest waypoint to an mobj + + Input Arguments:- + mobj - mobj to get the closest waypoint of. + + Return:- + The closest waypoint to the mobj +--------------------------------------------------*/ +waypoint_t *K_GetClosestWaypointToMobj(mobj_t *const mobj); + + +/*-------------------------------------------------- + waypoint_t *K_GetBestWaypointForMobj(mobj_t *const mobj) + + Similar to K_GetClosestWaypointToMobj, but prioritizes horizontal distance over vertical distance, and + sight checks to ensure that the waypoint and mobj are the in same area. Can potentially return NULL if + there are no visible waypoints. + + Input Arguments:- + mobj - mobj to get the waypoint for. + + Return:- + The best waypoint for the mobj, or NULL if there were no matches +--------------------------------------------------*/ +waypoint_t *K_GetBestWaypointForMobj(mobj_t *const mobj); + + +/*-------------------------------------------------- + boolean K_PathfindToWaypoint( + waypoint_t *const sourcewaypoint, + waypoint_t *const destinationwaypoint, + path_t *const returnpath, + const boolean useshortcuts, + const boolean huntbackwards) + + Use pathfinding to try and find the best route to the destination. Data is allocated into the returnpath, + and should be freed when done with. A call to this with a path already in the returnpath will free the data + already in there if one is found. + + Input Arguments:- + sourcewaypoint - The waypoint to start searching from + destinationwaypoint - The waypoint to try and get to. + returnpath - The path_t that will contain the final found path + useshortcuts - Whether to use waypoints that are marked as being shortcuts in the search + huntbackwards - Goes through the waypoints backwards if true + + Return:- + True if a path was found to the waypoint, false if there wasn't. +--------------------------------------------------*/ + +boolean K_PathfindToWaypoint( + waypoint_t *const sourcewaypoint, + waypoint_t *const destinationwaypoint, + path_t *const returnpath, + const boolean useshortcuts, + const boolean huntbackwards); + + +/*-------------------------------------------------- + waypoint_t *K_GetNextWaypointToDestination( + waypoint_t *const sourcewaypoint, + waypoint_t *const destinationwaypoint, + const boolean useshortcuts, + const boolean huntbackwards) + + Uses pathfinding to find the next waypoint to go to in order to get to the destination waypoint, from the source + waypoint. If the source waypoint only has one next waypoint it will always pick that one and not do any + pathfinding. + + Input Arguments:- + sourcewaypoint - The waypoint to start searching from + destinationwaypoint - The waypoint to try and get to. + useshortcuts - Whether to use waypoints that are marked as being shortcuts in the search + huntbackwards - Goes through the waypoints backwards if true + + Return:- + The next waypoint on the way to the destination waypoint. Returns the source waypoint if the source and + destination are the same. +--------------------------------------------------*/ + +waypoint_t *K_GetNextWaypointToDestination( + waypoint_t *const sourcewaypoint, + waypoint_t *const destinationwaypoint, + const boolean useshortcuts, + const boolean huntbackwards); + + +/*-------------------------------------------------- + waypoint_t *K_SearchWaypointGraphForMobj(mobj_t *const mobj) + + Searches through the waypoint graph for a waypoint that has an mobj, if a waypoint can be found through here it + does mean that the waypoint graph can be traversed to find it + + Input Arguments:- + mobj - The mobj that we are searching for, cannot be changed to a different pointer + + Return:- + The waypoint that uses that mobj, NULL if it wasn't found, NULL if it isn't an MT_WAYPOINT +--------------------------------------------------*/ + +waypoint_t *K_SearchWaypointGraphForMobj(mobj_t * const mobj); + +/*-------------------------------------------------- + waypoint_t *K_SearchWaypointHeapForMobj(mobj_t *const mobj) + + Searches through the waypoint heap for a waypoint that has an mobj, this does not necessarily mean the waypoint + can be reached from another waypoint + + Input Arguments:- + mobj - The mobj that we are searching for, cannot be changed to a different pointer + + Return:- + The waypoint that uses that mobj, NULL if it wasn't found, NULL if it isn't an MT_WAYPOINT +--------------------------------------------------*/ + +waypoint_t *K_SearchWaypointHeapForMobj(mobj_t * const mobj); + + +// NOT AVAILABLE FOR LUA + + +/*-------------------------------------------------- + size_t K_GetWaypointHeapIndex(waypoint_t *waypoint) + + Returns the waypoint's index in the waypoint heap. + + Input Arguments:- + waypoint - The waypoint to return the index of. + + Return:- + The waypoint heap index, SIZE_MAX if there's an issue with the waypoint. +--------------------------------------------------*/ +size_t K_GetWaypointHeapIndex(waypoint_t *waypoint); + + +/*-------------------------------------------------- + waypoint_t *K_GetWaypointFromIndex(size_t waypointindex) + + Returns the waypoint from an index to the heap. + + Input Arguments:- + waypointindex - The index of the waypoint to get + + Return:- + The waypoint from the heap index, NULL if the index if too high +--------------------------------------------------*/ +waypoint_t *K_GetWaypointFromIndex(size_t waypointindex); + + +/*-------------------------------------------------- + void K_DebugWaypointsVisualise() + + Creates mobjs in order to visualise waypoints for debugging. +--------------------------------------------------*/ + +void K_DebugWaypointsVisualise(void); + + +/*-------------------------------------------------- + boolean K_SetupWaypointList(void) + + Sets up the waypoint list for Kart race maps, prints out warnings if something is wrong. + + Return:- + true if waypoint setup was seemingly successful, false if no waypoints were setup + A true return value does not necessarily mean that the waypoints on the map are completely correct +--------------------------------------------------*/ + +boolean K_SetupWaypointList(void); + + +/*-------------------------------------------------- + void K_ClearWaypoints(void) + + Clears waypointheap, firstwaypoint, numwaypoints, and numwaypointmobjs + WARNING: This does *not* Free waypointheap or any waypoints! They are stored in PU_LEVEL so they are freed once + the level is completed! This is called just before K_SetupWaypointList in P_SetupLevel as they are freed then. + A separate method is called in K_SetupWaypointList that will free everything specifically if they aren't already +--------------------------------------------------*/ + +void K_ClearWaypoints(void); + +#endif \ No newline at end of file diff --git a/src/m_cheat.c b/src/m_cheat.c index c24a8014b..d61d22cd0 100644 --- a/src/m_cheat.c +++ b/src/m_cheat.c @@ -292,9 +292,18 @@ void Command_CheatNoClip_f(void) REQUIRE_NOULTIMATE; plyr = &players[consoleplayer]; + + if (!plyr->mo || P_MobjWasRemoved(plyr->mo)) + return; + plyr->pflags ^= PF_NOCLIP; CONS_Printf(M_GetText("No Clipping %s\n"), plyr->pflags & PF_NOCLIP ? M_GetText("On") : M_GetText("Off")); + if (plyr->pflags & PF_NOCLIP) + plyr->mo->flags |= MF_NOCLIP; + else + plyr->mo->flags &= ~MF_NOCLIP; + G_SetGameModified(multiplayer, true); } diff --git a/src/p_enemy.c b/src/p_enemy.c index 6d6c26133..4097db059 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -24,6 +24,7 @@ #include "i_video.h" #include "lua_hook.h" #include "k_kart.h" // SRB2kart +#include "k_waypoint.h" #ifdef HW3SOUND #include "hardware/hw3sound.h" @@ -8444,14 +8445,31 @@ void A_JawzExplode(mobj_t *actor) return; } +static void SpawnSPBTrailRings(mobj_t *actor) +{ + I_Assert(actor != NULL); + + if (leveltime % 6 == 0) + { + mobj_t *ring = P_SpawnMobj(actor->x - actor->momx, actor->y - actor->momy, + actor->z - actor->momz + (24*mapobjectscale), MT_RING); + ring->threshold = 10; + ring->fuse = 120*TICRATE; + } +} + void A_SPBChase(mobj_t *actor) { player_t *player = NULL; + player_t *scplayer = NULL; // secondary target for seeking UINT8 i; UINT8 bestrank = UINT8_MAX; fixed_t dist; angle_t hang, vang; fixed_t wspeed, xyspeed, zspeed; + fixed_t pdist = 1536<threshold) // Just fired, go straight. { actor->lastlook = -1; + actor->cusval = -1; spbplace = -1; P_InstaThrust(actor, actor->angle, wspeed); - actor->flags &= ~MF_NOCLIPTHING; // just in case. return; } @@ -8491,18 +8509,22 @@ void A_SPBChase(mobj_t *actor) } } + // lastlook = last player num targetted + // cvmem = stored speed + // cusval = next waypoint heap index + // extravalue1 = SPB movement mode + // extravalue2 = mode misc option + if (actor->extravalue1 == 1) // MODE: TARGETING { + actor->cusval = -1; // Reset waypoint + if (actor->tracer && actor->tracer->health) { - fixed_t defspeed = wspeed; fixed_t range = (160*actor->tracer->scale); fixed_t cx = 0, cy =0; - // we're tailing a player, now's a good time to regain our damage properties - actor->flags &= ~MF_NOCLIPTHING; - // Play the intimidating gurgle if (!S_SoundPlaying(actor, actor->info->activesound)) S_StartSound(actor, actor->info->activesound); @@ -8600,13 +8622,7 @@ void A_SPBChase(mobj_t *actor) actor->momz = FixedMul(zspeed, FINESINE(actor->movedir>>ANGLETOFINESHIFT)); // Spawn a trail of rings behind the SPB! - if (leveltime % 6 == 0) - { - mobj_t *ring = P_SpawnMobj(actor->x - actor->momx, actor->y - actor->momx, - actor->z - actor->momz + (24*mapobjectscale), MT_RING); - ring->threshold = 10; - ring->fuse = 120*TICRATE; - } + SpawnSPBTrailRings(actor); // Red speed lines for when it's gaining on its target. A tell for when you're starting to lose too much speed! if (R_PointToDist2(0, 0, actor->momx, actor->momy) > (actor->tracer->player ? (16*actor->tracer->player->speed)/15 @@ -8639,9 +8655,7 @@ void A_SPBChase(mobj_t *actor) else if (actor->extravalue1 == 2) // MODE: WAIT... { actor->momx = actor->momy = actor->momz = 0; // Stoooop - - // don't hurt players that have nothing to do with this: - actor->flags |= MF_NOCLIPTHING; + actor->cusval = -1; // Reset waypoint if (actor->lastlook != -1 && playeringame[actor->lastlook] @@ -8667,6 +8681,11 @@ void A_SPBChase(mobj_t *actor) } else // MODE: SEEKING { + waypoint_t *lastwaypoint = NULL; + waypoint_t *bestwaypoint = NULL; + waypoint_t *nextwaypoint = NULL; + waypoint_t *tempwaypoint = NULL; + actor->lastlook = -1; // Just make sure this is reset if (!player || !player->mo || player->mo->health <= 0 || player->kartstuff[k_respawn]) @@ -8679,17 +8698,72 @@ void A_SPBChase(mobj_t *actor) } // Found someone, now get close enough to initiate the slaughter... - - // don't hurt players that have nothing to do with this: - actor->flags |= MF_NOCLIPTHING; - P_SetTarget(&actor->tracer, player->mo); spbplace = bestrank; - dist = P_AproxDistance(P_AproxDistance(actor->x-actor->tracer->x, actor->y-actor->tracer->y), actor->z-actor->tracer->z); - hang = R_PointToAngle2(actor->x, actor->y, actor->tracer->x, actor->tracer->y); - vang = R_PointToAngle2(0, actor->z, dist, actor->tracer->z); + // Move along the waypoints until you get close enough + if (actor->cusval > -1 && actor->extravalue2 > 0) + { + // Previously set nextwaypoint + lastwaypoint = K_GetWaypointFromIndex((size_t)actor->cusval); + tempwaypoint = K_GetBestWaypointForMobj(actor); + // check if the tempwaypoint corresponds to lastwaypoint's next ID at least; + // This is to avoid situations where the SPB decides to suicide jump down a bridge because it found a COMPLETELY unrelated waypoint down there. + + if (K_GetWaypointID(tempwaypoint) == K_GetWaypointNextID(lastwaypoint) || K_GetWaypointID(tempwaypoint) == K_GetWaypointID(lastwaypoint)) + // either our previous or curr waypoint ID, sure, take it + bestwaypoint = tempwaypoint; + else + bestwaypoint = K_GetWaypointFromIndex((size_t)actor->extravalue2); // keep going from the PREVIOUS wp. + } + else + bestwaypoint = K_GetBestWaypointForMobj(actor); + + if (bestwaypoint == NULL && lastwaypoint == NULL) + { + // We have invalid waypoints all around, so use closest to try and make it non-NULL. + bestwaypoint = K_GetClosestWaypointToMobj(actor); + } + + if (bestwaypoint != NULL) + { + const boolean huntbackwards = false; + boolean useshortcuts = false; + + // If the player is on a shortcut, use shortcuts. No escape. + if (K_GetWaypointIsShortcut(player->nextwaypoint)) + { + useshortcuts = true; + } + + nextwaypoint = K_GetNextWaypointToDestination( + bestwaypoint, player->nextwaypoint, useshortcuts, huntbackwards); + } + + if (nextwaypoint == NULL && lastwaypoint != NULL) + { + // Restore to the last nextwaypoint + nextwaypoint = lastwaypoint; + } + + if (nextwaypoint != NULL) + { + const fixed_t xywaypointdist = P_AproxDistance( + actor->x - nextwaypoint->mobj->x, actor->y - nextwaypoint->mobj->y); + + hang = R_PointToAngle2(actor->x, actor->y, nextwaypoint->mobj->x, nextwaypoint->mobj->y); + vang = R_PointToAngle2(0, actor->z, xywaypointdist, nextwaypoint->mobj->z); + + actor->cusval = (INT32)K_GetWaypointHeapIndex(nextwaypoint); + actor->extravalue2 = (INT32)K_GetWaypointHeapIndex(bestwaypoint); // save our last best, used above. + } + else + { + // continue straight ahead... Shouldn't happen. + hang = actor->angle; + vang = 0U; + } { // Smoothly rotate horz angle @@ -8698,13 +8772,13 @@ void A_SPBChase(mobj_t *actor) if (invert) input = InvAngle(input); + input = FixedAngle(AngleFixed(input)/8); + // Slow down when turning; it looks better and makes U-turns not unfair xyspeed = FixedMul(wspeed, max(0, (((180<angle += input; // Smoothly rotate vert angle @@ -8713,13 +8787,13 @@ void A_SPBChase(mobj_t *actor) if (invert) input = InvAngle(input); + input = FixedAngle(AngleFixed(input)/8); + // Slow down when turning; might as well do it for momz, since we do it above too zspeed = FixedMul(wspeed, max(0, (((180<movedir += input; } @@ -8727,15 +8801,49 @@ void A_SPBChase(mobj_t *actor) actor->momy = FixedMul(FixedMul(xyspeed, FINESINE(actor->angle>>ANGLETOFINESHIFT)), FINECOSINE(actor->movedir>>ANGLETOFINESHIFT)); actor->momz = FixedMul(zspeed, FINESINE(actor->movedir>>ANGLETOFINESHIFT)); - if (dist <= (3072*actor->tracer->scale)) // Close enough to target? + // see if a player is near us, if they are, try to hit them by slightly thrusting towards them, otherwise, bleh! + for (i=0; i < MAXPLAYERS; i++) { - S_StartSound(actor, actor->info->attacksound); // Siren sound; might not need this anymore, but I'm keeping it for now just for debugging. + if (!playeringame[i] || players[i].spectator || players[i].exiting) + continue; // not in-game + + if (R_PointToDist2(actor->x, actor->y, players[i].mo->x, players[i].mo->y) < pdist) + { + pdist = R_PointToDist2(actor->x, actor->y, players[i].mo->x, players[i].mo->y); + scplayer = &players[i]; // it doesn't matter if we override this guy now. + } + } + + // different player from our main target, try and ram into em~! + if (scplayer && scplayer != player) + { + pangle = actor->angle - R_PointToAngle2(actor->x, actor->y,scplayer->mo->x, scplayer->mo->y); + // check if the angle wouldn't make us LOSE speed... + if ((INT32)pangle/ANG1 >= -80 && (INT32)pangle/ANG1 <= 80) // allow for around 80 degrees + { + // Thrust us towards the guy, try to screw em up! + P_Thrust(actor, R_PointToAngle2(actor->x, actor->y, scplayer->mo->x, scplayer->mo->y), actor->movefactor/4); // not too fast though. + } + } + + // Spawn a trail of rings behind the SPB! + SpawnSPBTrailRings(actor); + + if (dist <= (1024*actor->tracer->scale)) // Close enough to target? + { + S_StartSound(actor, actor->info->attacksound); actor->extravalue1 = 1; // TARGET ACQUIRED actor->extravalue2 = 7*TICRATE; actor->cvmem = wspeed; } } + // Finally, no matter what, the spb should not be able to be under the ground, or above the ceiling; + if (actor->z < actor->floorz) + actor->z = actor->floorz; + else if (actor->z > actor->ceilingz - actor->height) + actor->z = actor->ceilingz - actor->height; + return; } @@ -10577,8 +10685,8 @@ void A_RemoteDamage(mobj_t *actor) if (locvar2 == 1) // Kill mobj! { - if (target->player) // players die using P_DamageMobj instead for some reason - P_DamageMobj(target, source, source, 10000); + if (target->player) + K_DoIngameRespawn(target->player); else P_KillMobj(target, source, source); } diff --git a/src/p_inter.c b/src/p_inter.c index ce9499746..e98740655 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1462,15 +1462,9 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) case MT_STARPOST: if (player->bot) return; - // SRB2kart - 150117 - if (player->exiting) //STOP MESSING UP MY STATS FASDFASDF - { - player->kartstuff[k_starpostwp] = player->kartstuff[k_waypoint]; - return; - } // // SRB2kart: make sure the player will have enough checkpoints to touch - if (circuitmap && special->health >= ((numstarposts/2) + player->starpostnum)) + if (circuitmap && special->health - player->starpostnum > 1) { // blatant reuse of a variable that's normally unused in circuit if (!player->tossdelay) @@ -1491,13 +1485,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) // Save the player's time and position. player->starposttime = player->realtime; //this makes race mode's timers work correctly whilst not affecting sp -x - //player->starposttime = leveltime; - player->starpostx = toucher->x>>FRACBITS; - player->starposty = toucher->y>>FRACBITS; - player->starpostz = special->z>>FRACBITS; - player->starpostangle = special->angle; player->starpostnum = special->health; - player->kartstuff[k_starpostflip] = special->spawnpoint->options & MTF_OBJECTFLIP; // store flipping //S_StartSound(toucher, special->info->painsound); return; diff --git a/src/p_local.h b/src/p_local.h index 85fd08d71..a4e661bfc 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -182,7 +182,6 @@ boolean P_LookForEnemies(player_t *player); void P_NukeEnemies(mobj_t *inflictor, mobj_t *source, fixed_t radius); void P_HomingAttack(mobj_t *source, mobj_t *enemy); /// \todo doesn't belong in p_user //boolean P_SuperReady(player_t *player); -boolean P_AnalogMove(player_t *player); /*boolean P_TransferToNextMare(player_t *player); UINT8 P_FindLowestMare(void);*/ UINT8 P_FindLowestLap(void); diff --git a/src/p_map.c b/src/p_map.c index 5606f3fa6..9c602f70b 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -68,6 +68,20 @@ line_t *ceilingline; // that is, for any line which is 'solid' line_t *blockingline; +// Mostly re-ported from DOOM Legacy +// Keep track of special lines as they are hit, process them when the move is valid +static size_t *spechit = NULL; +static size_t spechit_max = 0U; +static size_t numspechit = 0U; + +// Need a intermediate buffer for P_TryMove because it performs multiple moves +// the lines put into spechit will be moved into here after each checkposition, +// then and duplicates will be removed before processing +static size_t *spechitint = NULL; +static size_t spechitint_max = 0U; +static size_t numspechitint = 0U; + + msecnode_t *sector_list = NULL; mprecipsecnode_t *precipsector_list = NULL; camera_t *mapcampointer; @@ -81,6 +95,8 @@ camera_t *mapcampointer; // boolean P_TeleportMove(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z) { + numspechit = 0U; + // the move is ok, // so link the thing into its new position P_UnsetThingPosition(thing); @@ -113,6 +129,100 @@ boolean P_TeleportMove(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z) // MOVEMENT ITERATOR FUNCTIONS // ========================================================================= +// For our intermediate buffer, remove any duplicate entries by adding each one to +// a temprary buffer if it's not already in there, copy the temporary buffer back over the intermediate afterwards +static void spechitint_removedups(void) +{ + // Only needs to be run if there's more than 1 line crossed + if (numspechitint > 1U) + { + boolean valueintemp = false; + size_t i = 0U, j = 0U; + size_t numspechittemp = 0U; + size_t *spechittemp = Z_Calloc(numspechitint * sizeof(size_t), PU_STATIC, NULL); + + // Fill the hashtable + for (i = 0U; i < numspechitint; i++) + { + valueintemp = false; + for (j = 0; j < numspechittemp; j++) + { + if (spechitint[i] == spechittemp[j]) + { + valueintemp = true; + break; + } + } + + if (!valueintemp) + { + spechittemp[numspechittemp] = spechitint[i]; + numspechittemp++; + } + } + + // The hash table now IS the result we want to send back + // easiest way to handle this is a memcpy + if (numspechittemp != numspechitint) + { + memcpy(spechitint, spechittemp, numspechittemp * sizeof(size_t)); + numspechitint = numspechittemp; + } + + Z_Free(spechittemp); + } +} + +// copy the contents of spechit into the end of spechitint +static void spechitint_copyinto(void) +{ + if (numspechit > 0U) + { + if (numspechitint + numspechit >= spechitint_max) + { + spechitint_max = spechitint_max + numspechit; + spechitint = Z_Realloc(spechitint, spechitint_max * sizeof(size_t), PU_STATIC, NULL); + } + + memcpy(&spechitint[numspechitint], spechit, numspechit * sizeof(size_t)); + numspechitint += numspechit; + } +} + +static void add_spechit(line_t *ld) +{ + if (numspechit >= spechit_max) + { + spechit_max = spechit_max ? spechit_max * 2U : 16U; + spechit = Z_Realloc(spechit, spechit_max * sizeof(size_t), PU_STATIC, NULL); + } + + spechit[numspechit] = ld - lines; + numspechit++; +} + +static boolean P_SpecialIsLinedefCrossType(UINT16 ldspecial) +{ + boolean linedefcrossspecial = false; + + switch (ldspecial) + { + case 2001: // Finish line + { + linedefcrossspecial = true; + } + break; + + default: + { + linedefcrossspecial = false; + } + break; + } + + return linedefcrossspecial; +} + //#define TELEPORTJANK boolean P_DoSpring(mobj_t *spring, mobj_t *object) @@ -146,7 +256,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object) object->eflags |= MFE_SPRUNG; // apply this flag asap! spring->flags &= ~(MF_SOLID|MF_SPECIAL); // De-solidify -#ifdef TELEPORTJANK +#if 0 if (horizspeed && vertispeed) // Mimic SA { object->momx = object->momy = 0; @@ -897,12 +1007,20 @@ static boolean PIT_CheckThing(mobj_t *thing) if (thing->type == MT_PLAYER) { - S_StartSound(NULL, sfx_bsnipe); //let all players hear it. + mobj_t *explosion; + + S_StartSound(NULL, sfx_bsnipe); // let all players hear it. + HU_SetCEchoFlags(0); HU_SetCEchoDuration(5); HU_DoCEcho(va("%s\\was hit by a kitchen sink.\\\\\\\\", player_names[thing->player-players])); I_OutputMsg("%s was hit by a kitchen sink.\n", player_names[thing->player-players]); - P_DamageMobj(thing, tmthing, tmthing->target, 10000); + + explosion = P_SpawnMobj(thing->x, thing->y, thing->z, MT_SPBEXPLOSION); + explosion->extravalue1 = 1; // Tell K_ExplodePlayer to use extra knockback + if (tmthing->target && !P_MobjWasRemoved(tmthing->target)) + P_SetTarget(&explosion->target, tmthing->target); + P_KillMobj(tmthing, thing, thing); } @@ -1161,15 +1279,23 @@ static boolean PIT_CheckThing(mobj_t *thing) } else if (thing->type == MT_SINK) { + mobj_t *explosion; + if ((thing->target == tmthing) && (thing->threshold > 0)) return true; - S_StartSound(NULL, sfx_cgot); //let all players hear it. + S_StartSound(NULL, sfx_bsnipe); // let all players hear it. + HU_SetCEchoFlags(0); HU_SetCEchoDuration(5); HU_DoCEcho(va("%s\\was hit by a kitchen sink.\\\\\\\\", player_names[tmthing->player-players])); I_OutputMsg("%s was hit by a kitchen sink.\n", player_names[tmthing->player-players]); - P_DamageMobj(tmthing, thing, thing->target, 10000); + + explosion = P_SpawnMobj(tmthing->x, tmthing->y, tmthing->z, MT_SPBEXPLOSION); + explosion->extravalue1 = 1; // Tell K_ExplodePlayer to use extra knockback + if (thing->target && !P_MobjWasRemoved(thing->target)) + P_SetTarget(&explosion->target, thing->target); + P_KillMobj(thing, tmthing, tmthing); } @@ -1309,7 +1435,7 @@ static boolean PIT_CheckThing(mobj_t *thing) thing->angle = tmthing->angle; - if (!demo.playback || P_AnalogMove(thing->player)) + if (!demo.playback) { if (thing->player == &players[consoleplayer]) localangle[0] = thing->angle; @@ -1554,7 +1680,7 @@ static boolean PIT_CheckThing(mobj_t *thing) { // Objects kill you if it falls from above. if (thing != tmthing->target) - P_DamageMobj(thing, tmthing, tmthing->target, 10000); + K_DoIngameRespawn(thing->player); tmthing->momz = -tmthing->momz/2; // Bounce, just for fun! // The tmthing->target allows the pusher of the object @@ -1972,7 +2098,7 @@ static boolean PIT_CheckLine(line_t *ld) if (P_BoxOnLineSide(tmbbox, ld) != -1) return true; -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. + 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. { fixed_t cosradius, sinradius; cosradius = FixedMul(tmthing->radius, FINECOSINE(tmthing->angle>>ANGLETOFINESHIFT)); @@ -2039,6 +2165,12 @@ if (tmthing->flags & MF_PAPERCOLLISION) // Caution! Turning whilst up against a if (lowfloor < tmdropoffz) tmdropoffz = lowfloor; + // we've crossed the line + if (P_SpecialIsLinedefCrossType(ld->special)) + { + add_spechit(ld); + } + return true; } @@ -2313,6 +2445,9 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y) validcount++; + // reset special lines + numspechit = 0U; + if (tmflags & MF_NOCLIP) return true; @@ -2752,6 +2887,8 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff) { fixed_t tryx = thing->x; fixed_t tryy = thing->y; + fixed_t oldx = tryx; + fixed_t oldy = tryy; fixed_t radius = thing->radius; fixed_t thingtop = thing->z + thing->height; #ifdef ESLOPE @@ -2759,8 +2896,13 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff) #endif floatok = false; - if (radius < MAXRADIUS/2) - radius = MAXRADIUS/2; + // reset this to 0 at the start of each trymove call as it's only used here + numspechitint = 0U; + + // This makes sure that there are no freezes from computing extremely small movements. + // Originally was MAXRADIUS/2, but that causes some inconsistencies for small players. + if (radius < mapobjectscale) + radius = mapobjectscale; do { if (thing->flags & MF_NOCLIP) { @@ -2784,6 +2926,9 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff) if (!P_CheckPosition(thing, tryx, tryy)) return false; // solid wall or thing + // copy into the spechitint buffer from spechit + spechitint_copyinto(); + if (!(thing->flags & MF_NOCLIP)) { //All things are affected by their scale. @@ -2956,6 +3101,30 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff) thing->eflags |= MFE_ONGROUND; P_SetThingPosition(thing); + + // remove any duplicates that may be in spechitint + spechitint_removedups(); + + // handle any of the special lines that were crossed + if (!(thing->flags & (MF_NOCLIP))) + { + line_t *ld = NULL; + INT32 side = 0, oldside = 0; + while (numspechitint--) + { + ld = &lines[spechitint[numspechitint]]; + side = P_PointOnLineSide(thing->x, thing->y, ld); + oldside = P_PointOnLineSide(oldx, oldy, ld); + if (side != oldside) + { + if (ld->special) + { + P_CrossSpecialLine(ld, oldside, thing); + } + } + } + } + return true; } diff --git a/src/p_mobj.c b/src/p_mobj.c index 0eeb97ac1..20119b6f5 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -47,6 +47,7 @@ consvar_t cv_splats = {"splats", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0 actioncache_t actioncachehead; static mobj_t *overlaycap = NULL; +mobj_t *kitemcap = NULL; // Used for Kart offensive items (the ones that can get removed by sizedown) mobj_t *waypointcap = NULL; void P_InitCachedActions(void) @@ -1655,18 +1656,11 @@ void P_XYMovement(mobj_t *mo) { mo->health--; if (mo->health == 0) - mo->destscale = 1; - } - else - { - if (mo->scale < mapobjectscale/16) - { - P_RemoveMobj(mo); - return; - } + mo->destscale = 0; } } //} + if (!P_TryMove(mo, mo->x + xmove, mo->y + ymove, true) && !(mo->eflags & MFE_SPRUNG)) { // blocked move @@ -6105,6 +6099,71 @@ static boolean P_AddShield(mobj_t *thing) return true; }*/ + +// Kartitem stuff. +boolean P_IsKartItem(INT32 type) +{ + if (type == MT_EGGMANITEM || type == MT_EGGMANITEM_SHIELD || + type == MT_BANANA || type == MT_BANANA_SHIELD || + type == MT_ORBINAUT || type == MT_ORBINAUT_SHIELD || + type == MT_JAWZ || type == MT_JAWZ_DUD || type == MT_JAWZ_SHIELD || + type == MT_SSMINE || type == MT_SSMINE_SHIELD || + type == MT_SINK || type == MT_SINK_SHIELD || + type == MT_SPB) + return true; + else + return false; +} + +// Called when a kart item "thinks" +void P_AddKartItem(mobj_t *thing) +{ + I_Assert(thing != NULL); + + if (kitemcap == NULL) + P_SetTarget(&kitemcap, thing); + else { + mobj_t *mo; + for (mo = kitemcap; mo && mo->itnext; mo = mo->itnext) + ; + + I_Assert(mo != NULL); + I_Assert(mo->itnext == NULL); + + P_SetTarget(&mo->itnext, thing); + } + P_SetTarget(&thing->itnext, NULL); +} + +// Called only when a kart item is removed +// Keeps the hnext list from corrupting. +static void P_RemoveKartItem(mobj_t *thing) +{ + mobj_t *mo; + for (mo = kitemcap; mo; mo = mo->itnext) + if (mo->itnext == thing) + { + P_SetTarget(&mo->itnext, thing->itnext); + P_SetTarget(&thing->itnext, NULL); + return; + } +} + +// Doesn't actually do anything since items have their own thinkers, +// but this is necessary for the sole purpose of updating kitemcap +void P_RunKartItems(void) +{ + mobj_t *mobj, *next; + + for (mobj = kitemcap; mobj; mobj = next) + { + next = mobj->itnext; + P_SetTarget(&mobj->itnext, NULL); + } + P_SetTarget(&kitemcap, NULL); +} + + void P_RunOverlays(void) { // run overlays @@ -6538,16 +6597,25 @@ void P_MobjThinker(mobj_t *mobj) mobj->z -= mobj->height - oldheight; if (mobj->scale == mobj->destscale) + { /// \todo Lua hook for "reached destscale"? - switch(mobj->type) + + if (mobj->scale == 0) { - case MT_EGGMOBILE_FIRE: - mobj->destscale = FRACUNIT; - mobj->scalespeed = FRACUNIT>>4; - break; - default: - break; + P_RemoveMobj(mobj); + return; } + + switch (mobj->type) + { + case MT_EGGMOBILE_FIRE: + mobj->destscale = FRACUNIT; + mobj->scalespeed = FRACUNIT>>4; + break; + default: + break; + } + } } if (mobj->type == MT_GHOST && mobj->fuse > 0 // Not guaranteed to be MF_SCENERY or not MF_SCENERY! @@ -8315,7 +8383,10 @@ void P_MobjThinker(mobj_t *mobj) if (p) { if (p->kartstuff[k_driftboost] > mobj->movecount) + { ; // reset animation + } + mobj->movecount = p->kartstuff[k_driftboost]; } } @@ -9756,6 +9827,12 @@ for (i = ((mobj->flags2 & MF2_STRONGBOX) ? strongboxamt : weakboxamt); i; --i) s } } + if (P_MobjWasRemoved(mobj)) + return; // obligatory paranoia check + + if (P_IsKartItem(mobj->type)) // mobj is a kart item we want on the list: + P_AddKartItem(mobj); // add to kitem list + // Can end up here if a player dies. if (mobj->player) P_CyclePlayerMobjState(mobj); @@ -10436,6 +10513,10 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) } } break; + case MT_BOSS3WAYPOINT: + // Remove before release + CONS_Alert(CONS_WARNING, "Boss waypoints are deprecated. Did you forget to remove the old checkpoints, too?\n"); + break; default: break; } @@ -10591,6 +10672,9 @@ void P_RemoveMobj(mobj_t *mobj) if (mobj->type == MT_SPB) spbplace = -1; + if (P_IsKartItem(mobj->type)) + P_RemoveKartItem(mobj); + mobj->health = 0; // Just because // unlink from sector and block lists @@ -11176,6 +11260,9 @@ void P_SpawnPlayer(INT32 playernum) // Spawn with a pity shield if necessary. //P_DoPityCheck(p); + if (p->kartstuff[k_respawn] != 0) + p->mo->flags |= MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY; + if (G_BattleGametype()) // SRB2kart { mobj_t *overheadarrow = P_SpawnMobj(mobj->x, mobj->y, mobj->z + P_GetPlayerHeight(p)+16*FRACUNIT, MT_PLAYERARROW); @@ -11392,11 +11479,10 @@ void P_MovePlayerToStarpost(INT32 playernum) sector->ceilingheight; if (mobj->player->kartstuff[k_starpostflip]) - z = (p->starpostz<height; + z = (p->starpostz<height; else - z = (p->starpostz<starpostz<starpostz + 128) << FRACBITS; // reverse gravity exists, pls mobj->player->kartstuff[k_starpostflip] = 0; if (z < floor) @@ -11413,8 +11499,6 @@ void P_MovePlayerToStarpost(INT32 playernum) mobj->angle = p->starpostangle; - p->kartstuff[k_waypoint] = p->kartstuff[k_starpostwp]; // SRB2kart - P_AfterPlayerSpawn(playernum); //if (!(netgame || multiplayer)) @@ -11723,6 +11807,26 @@ void P_SpawnMapThing(mapthing_t *mthing) else mthing->z = (INT16)(z>>FRACBITS); } + else if (i == MT_WAYPOINT) + { + // just gets set on either the floor or ceiling + boolean flip = (!!(mobjinfo[i].flags & MF_SPAWNCEILING) ^ !!(mthing->options & MTF_OBJECTFLIP)); + + // applying offsets! (if any) + if (flip) + { + z = ONCEILINGZ; + } + else + { + z = ONFLOORZ; + } + + if (z == ONFLOORZ) + mthing->z = 0; + else + mthing->z = (INT16)(z>>FRACBITS); + } else { fixed_t offset = 0; @@ -11969,6 +12073,69 @@ ML_NOCLIMB : Direction not controllable if (mthing->angle >= 360) mobj->tics += 7*(mthing->angle / 360) + 1; // starting delay break; + case MT_WAYPOINT: + { + // Just like MT_SPINMACEPOINT, this now works here too! + INT32 line = P_FindSpecialLineFromTag(2000, mthing->angle, -1); + mobj->radius = 384*FRACUNIT; + // Set the radius, mobj z, and mthing z to match what the parameters want + if (line != -1) + { + fixed_t lineradius = sides[lines[line].sidenum[0]].textureoffset; + fixed_t linez = sides[lines[line].sidenum[0]].rowoffset; + + if (lineradius > 0) + mobj->radius = lineradius; + mobj->z += linez; + mthing->z += linez >> FRACBITS; + } + // Use threshold to store the next waypoint ID + // movecount is being used for the current waypoint ID + // reactiontime lets us know if we can respawn at it + // lastlook is used for indicating the waypoint is a shortcut + // extravalue1 is used for indicating the waypoint is disabled + // extravalue2 is used for indicating the waypoint is the finishline + mobj->threshold = ((mthing->options >> ZSHIFT)); + mobj->movecount = mthing->angle; + if (mthing->options & MTF_EXTRA) + { + mobj->extravalue1 = 0; // The waypoint is disabled if extra is on + } + else + { + mobj->extravalue1 = 1; + } + if (mthing->options & MTF_OBJECTSPECIAL) + { + mobj->lastlook = 1; // the waypoint is a shortcut if objectspecial is on + } + else + { + mobj->lastlook = 0; + } + if (mthing->options & MTF_AMBUSH) + { + mobj->reactiontime = 0; // Can't respawn at if Ambush is on + } + else + { + mobj->reactiontime = 1; + } + if (mthing->extrainfo == 1) + { + mobj->extravalue2 = 1; // extrainfo of 1 means the waypoint is at the finish line + } + else + { + mobj->extravalue2 = 0; + } + + + // Sryder 2018-12-7: Grabbed this from the old MT_BOSS3WAYPOINT section so they'll be in the waypointcap instead + P_SetTarget(&mobj->tracer, waypointcap); + P_SetTarget(&waypointcap, mobj); + break; + } // SRB2Kart case MT_BALLOON: mobj->color = (1 + (mthing->angle % (MAXSKINCOLORS-1))); @@ -12104,13 +12271,6 @@ ML_NOCLIMB : Direction not controllable if (!foundanother) numstarposts++; } - else if (i == MT_BOSS3WAYPOINT) // SRB2kart 120217 - Used to store checkpoint num - { - mobj->health = mthing->angle; - mobj->movecount = mthing->extrainfo; - P_SetTarget(&mobj->tracer, waypointcap); - P_SetTarget(&waypointcap, mobj); - } else if (i == MT_SPIKE) { // Pop up spikes! diff --git a/src/p_mobj.h b/src/p_mobj.h index 03a19870c..5cc93a56d 100644 --- a/src/p_mobj.h +++ b/src/p_mobj.h @@ -319,6 +319,9 @@ typedef struct mobj_s struct mobj_s *hnext; struct mobj_s *hprev; + // One last pointer for kart item lists + struct mobj_s *itnext; + INT32 health; // for player this is rings + 1 // Movement direction, movement generation (zig-zagging). @@ -437,12 +440,18 @@ typedef struct actioncache_s extern actioncache_t actioncachehead; +extern mobj_t *kitemcap; extern mobj_t *waypointcap; void P_InitCachedActions(void); void P_RunCachedActions(void); void P_AddCachedAction(mobj_t *mobj, INT32 statenum); +// kartitem stuff: Returns true if the specified 'type' is one of the kart item constants we want in the kitemcap list +boolean P_IsKartItem(INT32 type); +void P_AddKartItem(mobj_t *thing); // needs to be called in k_kart.c +void P_RunKartItems(void); + // check mobj against water content, before movement code void P_MobjCheckWater(mobj_t *mobj); diff --git a/src/p_saveg.c b/src/p_saveg.c index b81cc0f50..4767b09d1 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -279,6 +279,9 @@ static void P_NetArchivePlayers(void) WRITEINT16(save_p, players[i].lturn_max[j]); WRITEINT16(save_p, players[i].rturn_max[j]); } + + WRITEUINT32(save_p, players[i].distancetofinish); + WRITEUINT32(save_p, K_GetWaypointHeapIndex(players[i].nextwaypoint)); } } @@ -447,6 +450,9 @@ static void P_NetUnArchivePlayers(void) players[i].lturn_max[j] = READINT16(save_p); players[i].rturn_max[j] = READINT16(save_p); } + + players[i].distancetofinish = READUINT32(save_p); + players[i].nextwaypoint = (waypoint_t *)(size_t)READUINT32(save_p); } } @@ -951,9 +957,11 @@ typedef enum MD2_HNEXT = 1<<7, MD2_HPREV = 1<<8, MD2_COLORIZED = 1<<9, - MD2_WAYPOINTCAP = 1<<10 + MD2_WAYPOINTCAP = 1<<10, + MD2_KITEMCAP = 1<<11, + MD2_ITNEXT = 1<<12 #ifdef ESLOPE - , MD2_SLOPE = 1<<11 + , MD2_SLOPE = 1<<13 #endif } mobj_diff2_t; @@ -1145,6 +1153,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type) diff2 |= MD2_HNEXT; if (mobj->hprev) diff2 |= MD2_HPREV; + if (mobj->itnext) + diff2 |= MD2_ITNEXT; #ifdef ESLOPE if (mobj->standingslope) diff2 |= MD2_SLOPE; @@ -1153,6 +1163,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type) diff2 |= MD2_COLORIZED; if (mobj == waypointcap) diff2 |= MD2_WAYPOINTCAP; + if (mobj == kitemcap) + diff2 |= MD2_KITEMCAP; if (diff2 != 0) diff |= MD_MORE; @@ -1268,6 +1280,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type) WRITEUINT32(save_p, mobj->hnext->mobjnum); if (diff2 & MD2_HPREV) WRITEUINT32(save_p, mobj->hprev->mobjnum); + if (diff2 & MD2_ITNEXT) + WRITEUINT32(save_p, mobj->itnext->mobjnum); #ifdef ESLOPE if (diff2 & MD2_SLOPE) WRITEUINT16(save_p, mobj->standingslope->id); @@ -2145,6 +2159,8 @@ static void LoadMobjThinker(actionf_p1 thinker) mobj->hnext = (mobj_t *)(size_t)READUINT32(save_p); if (diff2 & MD2_HPREV) mobj->hprev = (mobj_t *)(size_t)READUINT32(save_p); + if (diff2 & MD2_ITNEXT) + mobj->itnext = (mobj_t *)(size_t)READUINT32(save_p); #ifdef ESLOPE if (diff2 & MD2_SLOPE) { @@ -2186,6 +2202,9 @@ static void LoadMobjThinker(actionf_p1 thinker) if (diff2 & MD2_WAYPOINTCAP) P_SetTarget(&waypointcap, mobj); + if (diff2 & MD2_KITEMCAP) + P_SetTarget(&kitemcap, mobj); + mobj->info = (mobjinfo_t *)next; // temporarily, set when leave this function } @@ -3038,6 +3057,13 @@ static void P_RelinkPointers(void) if (!(mobj->hprev = P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "hprev not found on %d\n", mobj->type); } + if (mobj->itnext) + { + temp = (UINT32)(size_t)mobj->itnext; + mobj->itnext = NULL; + if (!(mobj->itnext = P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "itnext not found on %d\n", mobj->type); + } if (mobj->player && mobj->player->capsule) { temp = (UINT32)(size_t)mobj->player->capsule; @@ -3066,6 +3092,15 @@ static void P_RelinkPointers(void) if (!P_SetTarget(&mobj->player->awayviewmobj, P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "awayviewmobj not found on %d\n", mobj->type); } + if (mobj->player && mobj->player->nextwaypoint) + { + temp = (UINT32)(size_t)mobj->player->nextwaypoint; + mobj->player->nextwaypoint = K_GetWaypointFromIndex(temp); + if (mobj->player->nextwaypoint == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "nextwaypoint not found on %d\n", mobj->type); + } + } } } } @@ -3309,7 +3344,7 @@ static void P_NetArchiveMisc(void) WRITEUINT32(save_p, hyubgone); WRITEUINT32(save_p, mapreset); - for (i = 0; i < MAXPLAYERS; i++) + for (i = 0; i < MAXPLAYERS; i++) WRITEINT16(save_p, nospectategrief[i]); WRITEUINT8(save_p, thwompsactive); @@ -3432,7 +3467,7 @@ static inline boolean P_NetUnArchiveMisc(void) hyubgone = READUINT32(save_p); mapreset = READUINT32(save_p); - for (i = 0; i < MAXPLAYERS; i++) + for (i = 0; i < MAXPLAYERS; i++) nospectategrief[i] = READINT16(save_p); thwompsactive = (boolean)READUINT8(save_p); diff --git a/src/p_setup.c b/src/p_setup.c index d501914c8..3647aa6ea 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -85,6 +85,7 @@ // SRB2Kart #include "k_kart.h" #include "k_pwrlv.h" +#include "k_waypoint.h" // // Map MD5, calculated on level load. @@ -3115,6 +3116,17 @@ boolean P_SetupLevel(boolean skipprecip) if (loadprecip) // ugly hack for P_NetUnArchiveMisc (and P_LoadNetGame) P_SpawnPrecipitation(); + + // The waypoint data that's in PU_LEVEL needs to be reset back to 0/NULL now since PU_LEVEL was cleared + K_ClearWaypoints(); + // Load the waypoints please! + if (G_RaceGametype()) + { + if (K_SetupWaypointList() == false) + { + CONS_Alert(CONS_ERROR, "Waypoints were not able to be setup! Player positions will not work correctly.\n"); + } + } #ifdef HWRENDER // not win32 only 19990829 by Kin if (rendermode != render_soft && rendermode != render_none) { diff --git a/src/p_sight.c b/src/p_sight.c index 626f8bbef..f230f40f6 100644 --- a/src/p_sight.c +++ b/src/p_sight.c @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2018 by Sonic Team Junior. +// Copyright (C) 1999-2020 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -14,6 +14,7 @@ #include "doomdef.h" #include "doomstat.h" #include "p_local.h" +#include "p_slopes.h" #include "r_main.h" #include "r_state.h" @@ -103,12 +104,20 @@ static fixed_t P_InterceptVector2(divline_t *v2, divline_t *v1) static boolean P_CrossSubsecPolyObj(polyobj_t *po, register los_t *los) { size_t i; + sector_t *polysec; + + if (!(po->flags & POF_RENDERALL)) + return true; // the polyobject isn't visible, so we can ignore it + + polysec = po->lines[0]->backsector; for (i = 0; i < po->numLines; ++i) { line_t *line = po->lines[i]; divline_t divl; const vertex_t *v1,*v2; + fixed_t frac; + fixed_t topslope, bottomslope; // already checked other side? if (line->validcount == validcount) @@ -140,7 +149,22 @@ static boolean P_CrossSubsecPolyObj(polyobj_t *po, register los_t *los) continue; // stop because it is not two sided - return false; + //if (!(po->flags & POF_TESTHEIGHT)) + //return false; + + frac = P_InterceptVector2(&los->strace, &divl); + + // get slopes of top and bottom of this polyobject line + topslope = FixedDiv(polysec->ceilingheight - los->sightzstart , frac); + bottomslope = FixedDiv(polysec->floorheight - los->sightzstart , frac); + + if (topslope >= los->topslope && bottomslope <= los->bottomslope) + return false; // view completely blocked + + // TODO: figure out if it's worth considering partially blocked cases or not? + // maybe to adjust los's top/bottom slopes if needed + //if (los->topslope <= los->bottomslope) + //return false; } return true; @@ -193,6 +217,15 @@ static boolean P_CrossSubsector(size_t num, register los_t *los) const sector_t *front, *back; const vertex_t *v1,*v2; fixed_t frac; + fixed_t frontf, backf, frontc, backc; +#ifdef ESLOPE + fixed_t fracx, fracy; +#endif + + /* SRB2Kart doesn't have this? + if (seg->glseg) + continue; + */ // already checked other side? if (line->validcount == validcount) @@ -227,36 +260,51 @@ static boolean P_CrossSubsector(size_t num, register los_t *los) if (!(line->flags & ML_TWOSIDED)) return false; + // calculate fractional intercept (how far along we are divided by how far we are from t2) + frac = P_InterceptVector2(&los->strace, &divl); + + front = seg->frontsector; + back = seg->backsector; +#ifdef ESLOPE + // calculate position at intercept + fracx = los->strace.x + FixedMul(los->strace.dx, frac); + fracy = los->strace.y + FixedMul(los->strace.dy, frac); + // calculate sector heights + frontf = (front->f_slope) ? P_GetZAt(front->f_slope, fracx, fracy) : front->floorheight; + frontc = (front->c_slope) ? P_GetZAt(front->c_slope, fracx, fracy) : front->ceilingheight; + backf = (back->f_slope) ? P_GetZAt(back->f_slope, fracx, fracy) : back->floorheight; + backc = (back->c_slope) ? P_GetZAt(back->c_slope, fracx, fracy) : back->ceilingheight; +#else + frontf = front->floorheight; + frontc = front->ceilingheight; + backf = back->floorheight; + backc = back->ceilingheight; +#endif // crosses a two sided line // no wall to block sight with? - if ((front = seg->frontsector)->floorheight == - (back = seg->backsector)->floorheight && - front->ceilingheight == back->ceilingheight) + if (frontf == backf && frontc == backc + && !front->ffloors & !back->ffloors) // (and no FOFs) continue; // possible occluder // because of ceiling height differences - popentop = front->ceilingheight < back->ceilingheight ? - front->ceilingheight : back->ceilingheight ; + popentop = min(frontc, backc); // because of floor height differences - popenbottom = front->floorheight > back->floorheight ? - front->floorheight : back->floorheight ; + popenbottom = max(frontf, backf); // quick test for totally closed doors if (popenbottom >= popentop) return false; - frac = P_InterceptVector2(&los->strace, &divl); - - if (front->floorheight != back->floorheight) + if (frontf != backf) { fixed_t slope = FixedDiv(popenbottom - los->sightzstart , frac); if (slope > los->bottomslope) los->bottomslope = slope; } - if (front->ceilingheight != back->ceilingheight) + if (frontc != backc) { fixed_t slope = FixedDiv(popentop - los->sightzstart , frac); if (slope < los->topslope) @@ -265,6 +313,58 @@ static boolean P_CrossSubsector(size_t num, register los_t *los) if (los->topslope <= los->bottomslope) return false; + + // Monster Iestyn: check FOFs! + if (front->ffloors || back->ffloors) + { + ffloor_t *rover; + fixed_t topslope, bottomslope; + fixed_t topz, bottomz; + // check front sector's FOFs first + for (rover = front->ffloors; rover; rover = rover->next) + { + if (!(rover->flags & FF_EXISTS) + || !(rover->flags & FF_RENDERSIDES) || rover->flags & FF_TRANSLUCENT) + { + continue; + } + +#ifdef ESLOPE + topz = (*rover->t_slope) ? P_GetZAt(*rover->t_slope, fracx, fracy) : *rover->topheight; + bottomz = (*rover->b_slope) ? P_GetZAt(*rover->b_slope, fracx, fracy) : *rover->bottomheight; +#else + topz = *rover->topheight; + bottomz = *rover->bottomheight; +#endif + topslope = FixedDiv(topz - los->sightzstart , frac); + bottomslope = FixedDiv(bottomz - los->sightzstart , frac); + if (topslope >= los->topslope && bottomslope <= los->bottomslope) + return false; // view completely blocked + } + // check back sector's FOFs as well + for (rover = back->ffloors; rover; rover = rover->next) + { + if (!(rover->flags & FF_EXISTS) + || !(rover->flags & FF_RENDERSIDES) || rover->flags & FF_TRANSLUCENT) + { + continue; + } + +#ifdef ESLOPE + topz = (*rover->t_slope) ? P_GetZAt(*rover->t_slope, fracx, fracy) : *rover->topheight; + bottomz = (*rover->b_slope) ? P_GetZAt(*rover->b_slope, fracx, fracy) : *rover->bottomheight; +#else + topz = *rover->topheight; + bottomz = *rover->bottomheight; +#endif + topslope = FixedDiv(topz - los->sightzstart , frac); + bottomslope = FixedDiv(bottomz - los->sightzstart , frac); + if (topslope >= los->topslope && bottomslope <= los->bottomslope) + return false; // view completely blocked + } + // TODO: figure out if it's worth considering partially blocked cases or not? + // maybe to adjust los's top/bottom slopes if needed + } } // passed the subsector ok @@ -375,6 +475,8 @@ boolean P_CheckSight(mobj_t *t1, mobj_t *t2) if (s1 == s2) // Both sectors are the same. { ffloor_t *rover; + fixed_t topz1, bottomz1; // top, bottom heights at t1's position + fixed_t topz2, bottomz2; // likewise but for t2 for (rover = s1->ffloors; rover; rover = rover->next) { @@ -382,14 +484,35 @@ boolean P_CheckSight(mobj_t *t1, mobj_t *t2) /// \todo Improve by checking fog density/translucency /// and setting a sight limit. if (!(rover->flags & FF_EXISTS) - || !(rover->flags & FF_RENDERPLANES) || rover->flags & FF_TRANSLUCENT) + || !(rover->flags & FF_RENDERPLANES) /*|| (rover->flags & FF_TRANSLUCENT)*/) { continue; } +#ifdef ESLOPE + if (*rover->t_slope) + { + topz1 = P_GetZAt(*rover->t_slope, t1->x, t1->y); + topz2 = P_GetZAt(*rover->t_slope, t2->x, t2->y); + } + else + topz1 = topz2 = *rover->topheight; + + if (*rover->b_slope) + { + bottomz1 = P_GetZAt(*rover->b_slope, t1->x, t1->y); + bottomz2 = P_GetZAt(*rover->b_slope, t2->x, t2->y); + } + else + bottomz1 = bottomz2 = *rover->bottomheight; +#else + topz1 = topz2 = *rover->topheight; + bottomz1 = bottomz2 = *rover->bottomheight; +#endif + // Check for blocking floors here. - if ((los.sightzstart < *rover->bottomheight && t2->z >= *rover->topheight) - || (los.sightzstart >= *rover->topheight && t2->z + t2->height < *rover->bottomheight)) + if ((los.sightzstart < bottomz1 && t2->z >= topz2) + || (los.sightzstart >= topz1 && t2->z + t2->height < bottomz2)) { // no way to see through that return false; @@ -400,19 +523,19 @@ boolean P_CheckSight(mobj_t *t1, mobj_t *t2) if (!(rover->flags & FF_INVERTPLANES)) { - if (los.sightzstart >= *rover->topheight && t2->z + t2->height < *rover->topheight) + if (los.sightzstart >= topz1 && t2->z + t2->height < topz2) return false; // blocked by upper outside plane - if (los.sightzstart < *rover->bottomheight && t2->z >= *rover->bottomheight) + if (los.sightzstart < bottomz1 && t2->z >= bottomz2) return false; // blocked by lower outside plane } if (rover->flags & FF_INVERTPLANES || rover->flags & FF_BOTHPLANES) { - if (los.sightzstart < *rover->topheight && t2->z >= *rover->topheight) + if (los.sightzstart < topz1 && t2->z >= topz2) return false; // blocked by upper inside plane - if (los.sightzstart >= *rover->bottomheight && t2->z + t2->height < *rover->bottomheight) + if (los.sightzstart >= bottomz1 && t2->z + t2->height < bottomz2) return false; // blocked by lower inside plane } } diff --git a/src/p_spec.c b/src/p_spec.c index be35e4f66..2b1fe535b 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -1705,41 +1705,6 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller if (!(ALL7EMERALDS(emeralds))) return false; } - else if (GETSECSPECIAL(caller->special, 2) == 7) // SRB2Kart: reusing for Race Lap executor - { - UINT8 lap; - - if (actor && actor->player && triggerline->flags & ML_EFFECT4) - { - /*if (maptol & TOL_NIGHTS) - lap = actor->player->mare; - else*/ - lap = actor->player->laps; - } - else - { - /*if (maptol & TOL_NIGHTS) - lap = P_FindLowestMare(); - else*/ - lap = P_FindLowestLap(); - } - - if (triggerline->flags & ML_NOCLIMB) // Need higher than or equal to - { - if (lap < (sides[triggerline->sidenum[0]].textureoffset >> FRACBITS)) - return false; - } - else if (triggerline->flags & ML_BLOCKMONSTERS) // Need lower than or equal to - { - if (lap > (sides[triggerline->sidenum[0]].textureoffset >> FRACBITS)) - return false; - } - else // Need equal to - { - if (lap != (sides[triggerline->sidenum[0]].textureoffset >> FRACBITS)) - return false; - } - } // If we were not triggered by a sector type especially for the purpose, // a Linedef Executor linedef trigger is not handling sector triggers properly, return. @@ -1937,15 +1902,17 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller else // These special types work only once if (specialtype == 302 // Once - || specialtype == 304 // Ring count - Once - || specialtype == 307 // Character ability - Once - || specialtype == 308 // Race only - Once - || specialtype == 315 // No of pushables - Once - || specialtype == 318 // Unlockable trigger - Once - || specialtype == 320 // Unlockable - Once - || specialtype == 321 || specialtype == 322 // Trigger on X calls - Continuous + Each Time - || specialtype == 328 // Encore Load - || specialtype == 399) // Level Load + || specialtype == 304 // Ring count - Once + || specialtype == 307 // Character ability - Once + || specialtype == 308 // Race only - Once + || specialtype == 315 // No of pushables - Once + || specialtype == 318 // Unlockable trigger - Once + || specialtype == 320 // Unlockable - Once + || specialtype == 321 || specialtype == 322 // Trigger on X calls - Continuous + Each Time + || specialtype == 328 // Encore Load + || specialtype == 399 // Level Load + || specialtype == 2002 // SRB2Kart Race Lap + ) triggerline->special = 0; // Clear it out return true; @@ -1981,6 +1948,7 @@ void P_LinedefExecute(INT16 tag, mobj_t *actor, sector_t *caller) if (lines[masterline].special == 313 || lines[masterline].special == 399 || lines[masterline].special == 328 + || lines[masterline].special == 2002 // SRB2Kart race lap trigger // Each-time executors handle themselves, too || lines[masterline].special == 301 // Each time || lines[masterline].special == 306 // Character ability - Each time @@ -2089,6 +2057,188 @@ void P_SwitchWeather(UINT8 newWeather) P_SpawnPrecipitation(); } +// Passed over the finish line forwards +static void K_HandleLapIncrement(player_t *player) +{ + if (player) + { + if ((player->starpostnum == numstarposts) || (player->laps == 0)) + { + size_t i = 0; + UINT8 nump = 0; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + nump++; + } + + player->laps++; + + // Set up lap animation vars + if (player->laps > 1) + { + if (nump > 1) + { + if (K_IsPlayerLosing(player)) + player->karthud[khud_laphand] = 3; + else + { + if (nump > 2 && player->kartstuff[k_position] == 1) // 1st place in 1v1 uses thumbs up + player->karthud[khud_laphand] = 1; + else + player->karthud[khud_laphand] = 2; + } + } + else + player->karthud[khud_laphand] = 0; // No hands in FREE PLAY + + player->karthud[khud_lapanimation] = 80; + } + + if (netgame && player->laps >= (UINT8)cv_numlaps.value) + CON_LogMessage(va(M_GetText("%s has finished the race.\n"), player_names[player-players])); + + // SRB2Kart: save best lap for record attack + if (player == &players[consoleplayer]) + { + if (curlap < bestlap || bestlap == 0) + bestlap = curlap; + curlap = 0; + } + + player->starposttime = player->realtime; + player->starpostnum = 0; + + if (P_IsDisplayPlayer(player)) + { + if (player->laps == (UINT8)(cv_numlaps.value)) // final lap + S_StartSound(NULL, sfx_s3k68); + else if ((player->laps > 1) && (player->laps < (UINT8)(cv_numlaps.value))) // non-final lap + S_StartSound(NULL, sfx_s221); + else if (player->laps > (UINT8)(cv_numlaps.value)) + { + // finished + S_StartSound(NULL, sfx_s3k6a); + } + + } + else + { + if ((player->laps > (UINT8)(cv_numlaps.value)) && (player->kartstuff[k_position] == 1)) + { + // opponent finished + S_StartSound(NULL, sfx_s253); + } + } + + // finished race exit setup + if (player->laps > (unsigned)cv_numlaps.value) + { + P_DoPlayerExit(player); + P_SetupSignExit(player); + } + + thwompsactive = true; // Lap 2 effects + + for (i = 0; i < numlines; i++) + { + if (lines[i].special == 2002) // Race lap trigger + { + UINT8 lap; + + if (lines[i].flags & ML_EFFECT4) + { + lap = player->laps; + } + else + { + lap = P_FindLowestLap(); + } + + if (lines[i].flags & ML_NOCLIMB) // Need higher than or equal to + { + if (lap < (sides[lines[i].sidenum[0]].textureoffset >> FRACBITS)) + continue; + } + else if (lines[i].flags & ML_BLOCKMONSTERS) // Need lower than or equal to + { + if (lap > (sides[lines[i].sidenum[0]].textureoffset >> FRACBITS)) + continue; + } + else // Need equal to + { + if (lap != (sides[lines[i].sidenum[0]].textureoffset >> FRACBITS)) + continue; + } + + P_RunTriggerLinedef(&lines[i], player->mo, NULL); + } + } + } + else if (player->starpostnum) + { + S_StartSound(player->mo, sfx_s26d); + } + } +} + +// player went backwards over the line +static void K_HandleLapDecrement(player_t *player) +{ + if (player) + { + if ((player->starpostnum == 0) && (player->laps > 0)) + { + player->starpostnum = numstarposts; + player->laps--; + } + } +} + +// +// P_CrossSpecialLine - TRIGGER +// Called every time a thing origin is about +// to cross a line with specific specials +// Kart - Only used for the finish line currently +// +void P_CrossSpecialLine(line_t *line, INT32 side, mobj_t *thing) +{ + // only used for the players currently + if (thing && thing->player) + { + player_t *player = thing->player; + switch (line->special) + { + case 2001: // Finish Line + { + if (G_RaceGametype() && !(player->exiting) && !(player->pflags & PF_HITFINISHLINE)) + { + if (((line->flags & (ML_NOCLIMB)) && (side == 0)) + || (!(line->flags & (ML_NOCLIMB)) && (side == 1))) // crossed from behind to infront + { + K_HandleLapIncrement(player); + } + else + { + K_HandleLapDecrement(player); + } + + player->pflags |= PF_HITFINISHLINE; + } + } + break; + + default: + { + // Do nothing + } + break; + } + } +} + /** Gets an object. * * \param type Object type to look for. @@ -3621,10 +3771,10 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers case 6: // Death Pit (Camera Mod) case 7: // Death Pit (No Camera Mod) if (roversector || P_MobjReadyToTrigger(player->mo, sector)) - P_DamageMobj(player->mo, NULL, NULL, 10000); + K_DoIngameRespawn(player); break; case 8: // Instant Kill - P_DamageMobj(player->mo, NULL, NULL, 10000); + K_DoIngameRespawn(player); break; case 9: // Ring Drainer (Floor Touch) case 10: // Ring Drainer (No Floor Touch) @@ -3862,7 +4012,7 @@ DoneSection2: if (player->mo->scale > mapobjectscale) linespeed = FixedMul(linespeed, mapobjectscale + (player->mo->scale - mapobjectscale)); - if (!demo.playback || P_AnalogMove(player)) + if (!demo.playback) { if (player == &players[consoleplayer]) localangle[0] = player->mo->angle; @@ -4213,113 +4363,8 @@ DoneSection2: } break; - case 10: // Finish Line - // SRB2kart - 150117 - if (G_RaceGametype() && (player->starpostnum >= (numstarposts - (numstarposts/2)) || player->exiting)) - player->kartstuff[k_starpostwp] = player->kartstuff[k_waypoint] = 0; - // - if (G_RaceGametype() && !player->exiting) - { - if (player->starpostnum >= (numstarposts - (numstarposts/2))) // srb2kart: must have touched *enough* starposts (was originally "(player->starpostnum == numstarposts)") - { - UINT8 nump = 0; - - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator) - continue; - nump++; - } - - player->laps++; - - // Set up lap animation vars - if (nump > 1) - { - if (K_IsPlayerLosing(player)) - player->karthud[khud_laphand] = 3; - else - { - if (nump > 2 && player->kartstuff[k_position] == 1) // 1st place in 1v1 uses thumbs up - player->karthud[khud_laphand] = 1; - else - player->karthud[khud_laphand] = 2; - } - } - else - player->karthud[khud_laphand] = 0; // No hands in FREE PLAY - - player->karthud[khud_lapanimation] = 80; - - if (player->pflags & PF_NIGHTSMODE) - player->drillmeter += 48*20; - - if (netgame && player->laps >= (UINT8)cv_numlaps.value) - CON_LogMessage(va(M_GetText("%s has finished the race.\n"), player_names[player-players])); - - // SRB2Kart: save best lap for record attack - if (player == &players[consoleplayer]) - { - if (curlap < bestlap || bestlap == 0) - bestlap = curlap; - curlap = 0; - } - - player->starposttime = player->realtime; - player->starpostnum = 0; - - if (mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE) - { - // SRB2Kart 281118 - // Save the player's time and position. - player->starpostx = player->mo->x>>FRACBITS; - player->starposty = player->mo->y>>FRACBITS; - player->starpostz = player->mo->floorz>>FRACBITS; - player->kartstuff[k_starpostflip] = player->mo->flags2 & MF2_OBJECTFLIP; // store flipping - 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... - } - else - { - // SRB2kart 200117 - // Reset starposts (checkpoints) info - player->starpostangle = player->starpostx = player->starposty = player->starpostz = player->kartstuff[k_starpostflip] = 0; - } - - if (P_IsDisplayPlayer(player)) - { - if (player->laps == (UINT8)(cv_numlaps.value - 1)) - S_StartSound(NULL, sfx_s3k68); - else if (player->laps < (UINT8)(cv_numlaps.value - 1)) - S_StartSound(NULL, sfx_s221); - } - - //player->starpostangle = player->starposttime = player->starpostnum = 0; - //player->starpostx = player->starposty = player->starpostz = 0; - - // Play the starpost sound for 'consistency' - // S_StartSound(player->mo, sfx_strpst); - - thwompsactive = true; // Lap 2 effects - } - else if (player->starpostnum) - { - // blatant reuse of a variable that's normally unused in circuit - if (!player->tossdelay) - S_StartSound(player->mo, sfx_s26d); - player->tossdelay = 3; - } - - if (player->laps >= (unsigned)cv_numlaps.value) - { - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_s3k6a); - else if (player->kartstuff[k_position] == 1) - S_StartSound(NULL, sfx_s253); - - P_DoPlayerExit(player); - P_SetupSignExit(player); - } - } + case 10: // Finish Line (Unused) + // SRB2Kart 20190616 - Is now a linedef type that activates by crossing over it break; case 11: // Rope hang @@ -4872,7 +4917,7 @@ static void P_RunSpecialSectorCheck(player_t *player, sector_t *sector) case 6: // Super Sonic Transform case 8: // Zoom Tube Start case 9: // Zoom Tube End - case 10: // Finish line + case 10: // Finish line (Unused) nofloorneeded = true; break; } @@ -5731,9 +5776,9 @@ void P_SpawnSpecials(INT32 fromnetsave) // Process Section 4 switch(GETSECSPECIAL(sector->special, 4)) { - case 10: // Circuit finish line - if (G_RaceGametype()) - circuitmap = true; + case 10: // Circuit finish line (Unused) + // Remove before release + CONS_Alert(CONS_WARNING, "Finish line sector type is deprecated.\n"); break; } } @@ -6664,6 +6709,15 @@ void P_SpawnSpecials(INT32 fromnetsave) sectors[s].midmap = lines[i].frontsector->midmap; break; + // SRB2Kart + case 2000: // Waypoint Parameters + break; + case 2001: // Finish Line + if (G_RaceGametype()) + circuitmap = true; + break; + case 2002: // Linedef Trigger: Race Lap + break; default: break; } @@ -6884,8 +6938,8 @@ void T_Scroll(scroll_t *s) height = P_GetSpecialBottomZ(thing, sec, psec); - if (!(thing->flags & MF_NOCLIP)) // Thing must be clipped - if (!(thing->flags & MF_NOGRAVITY || thing->z+thing->height != height)) // Thing must a) be non-floating and have z+height == height + if (!(thing->flags & MF_NOCLIP) && // Thing must be clipped + (!(thing->flags & MF_NOGRAVITY || thing->z+thing->height != height))) // Thing must a) be non-floating and have z+height == height { // Move objects only if on floor // non-floating, and clipped. @@ -6960,8 +7014,8 @@ void T_Scroll(scroll_t *s) height = P_GetSpecialTopZ(thing, sec, psec); - if (!(thing->flags & MF_NOCLIP)) // Thing must be clipped - if (!(thing->flags & MF_NOGRAVITY || thing->z != height))// Thing must a) be non-floating and have z == height + if (!(thing->flags & MF_NOCLIP) && // Thing must be clipped + (!(thing->flags & MF_NOGRAVITY || thing->z != height))) // Thing must a) be non-floating and have z == height { // Move objects only if on floor or underwater, // non-floating, and clipped. @@ -7856,7 +7910,7 @@ void T_Pusher(pusher_t *p) thing->player->pflags |= PF_SLIDING; thing->angle = R_PointToAngle2 (0, 0, xspeed<<(FRACBITS-PUSH_FACTOR), yspeed<<(FRACBITS-PUSH_FACTOR)); - if (!demo.playback || P_AnalogMove(thing->player)) + if (!demo.playback) { if (thing->player == &players[consoleplayer]) { diff --git a/src/p_spec.h b/src/p_spec.h index b65745998..a49946c2a 100644 --- a/src/p_spec.h +++ b/src/p_spec.h @@ -56,6 +56,8 @@ INT32 P_FindSpecialLineFromTag(INT16 special, INT16 tag, INT32 start); INT32 P_FindMinSurroundingLight(sector_t *sector, INT32 max); +void P_CrossSpecialLine(line_t *ld, INT32 side, mobj_t *thing); + void P_SetupSignExit(player_t *player); boolean P_IsFlagAtBase(mobjtype_t flag); diff --git a/src/p_tick.c b/src/p_tick.c index ab2623705..c514905a9 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -23,6 +23,7 @@ #include "lua_script.h" #include "lua_hook.h" #include "k_kart.h" +#include "k_waypoint.h" // Object place #include "m_cheat.h" @@ -172,6 +173,7 @@ void P_InitThinkers(void) { thinkercap.prev = thinkercap.next = &thinkercap; waypointcap = NULL; + kitemcap = NULL; } // @@ -628,6 +630,9 @@ void P_Ticker(boolean run) if (runemeraldmanager) P_EmeraldManager(); // Power stone mode*/ + // formality so kitemcap gets updated properly each frame. + P_RunKartItems(); + if (run) { P_RunThinkers(); @@ -736,6 +741,11 @@ void P_Ticker(boolean run) && --mapreset <= 1 && server) // Remember: server uses it for mapchange, but EVERYONE ticks down for the animation D_MapChange(gamemap, gametype, encoremode, true, 0, false, false); + + if (cv_kartdebugwaypoints.value != 0) + { + K_DebugWaypointsVisualise(); + } } // Always move the camera. diff --git a/src/p_user.c b/src/p_user.c index e025b6e74..34e68de3a 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -1253,7 +1253,7 @@ void P_RestoreMusic(player_t *player) #if 0 // Event - Final Lap // Still works for GME, but disabled for consistency - if (G_RaceGametype() && player->laps >= (UINT8)(cv_numlaps.value - 1)) + if (G_RaceGametype() && player->laps >= (UINT8)(cv_numlaps.value)) S_SpeedMusic(1.2f); #endif S_ChangeMusicEx(mapmusname, mapmusflags, true, mapmusposition, 0, 0); @@ -3724,11 +3724,6 @@ void P_Telekinesis(player_t *player, fixed_t thrust, fixed_t range) player->pflags |= PF_THOKKED; } -boolean P_AnalogMove(player_t *player) -{ - return player->pflags & PF_ANALOGMODE; -} - // // P_GetPlayerControlDirection // @@ -3771,14 +3766,6 @@ boolean P_AnalogMove(player_t *player) origtempangle = tempangle = 0; // relative to the axis rather than the player! controlplayerdirection = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy); } - else if (P_AnalogMove(player) && thiscam->chase) - { - if (player->awayviewtics) - origtempangle = tempangle = player->awayviewmobj->angle; - else - origtempangle = tempangle = thiscam->angle; - controlplayerdirection = player->mo->angle; - } else { origtempangle = tempangle = player->mo->angle; @@ -4002,7 +3989,6 @@ static void P_3dMovement(player_t *player) angle_t dangle; // replaces old quadrants bits //boolean dangleflip = false; // SRB2kart - toaster //fixed_t normalspd = FixedMul(player->normalspeed, player->mo->scale); - boolean analogmove = false; fixed_t oldMagnitude, newMagnitude; #ifdef ESLOPE vector3_t totalthrust; @@ -4014,8 +4000,6 @@ static void P_3dMovement(player_t *player) // Get the old momentum; this will be needed at the end of the function! -SH oldMagnitude = R_PointToDist2(player->mo->momx - player->cmomx, player->mo->momy - player->cmomy, 0, 0); - analogmove = P_AnalogMove(player); - cmd = &player->cmd; if ((player->exiting || mapreset) || player->pflags & PF_STASIS || player->kartstuff[k_spinouttimer]) // pw_introcam? @@ -4028,19 +4012,13 @@ static void P_3dMovement(player_t *player) if (!(player->pflags & PF_FORCESTRAFE) && !player->kartstuff[k_pogospring]) cmd->sidemove = 0; - if (analogmove) - { - movepushangle = (cmd->angleturn<<16 /* not FRACBITS */); - } + if (player->kartstuff[k_drift] != 0) + movepushangle = player->mo->angle-(ANGLE_45/5)*player->kartstuff[k_drift]; + else if (player->kartstuff[k_spinouttimer] || player->kartstuff[k_wipeoutslow]) // if spun out, use the boost angle + movepushangle = (angle_t)player->kartstuff[k_boostangle]; else - { - if (player->kartstuff[k_drift] != 0) - movepushangle = player->mo->angle-(ANGLE_45/5)*player->kartstuff[k_drift]; - else if (player->kartstuff[k_spinouttimer] || player->kartstuff[k_wipeoutslow]) // if spun out, use the boost angle - movepushangle = (angle_t)player->kartstuff[k_boostangle]; - else - movepushangle = player->mo->angle; - } + movepushangle = player->mo->angle; + movepushsideangle = movepushangle-ANGLE_90; // cmomx/cmomy stands for the conveyor belt speed. @@ -6199,69 +6177,6 @@ static void P_MovePlayer(player_t *player) player->pflags &= ~PF_STARTDASH; */ - ////////////////// - //ANALOG CONTROL// - ////////////////// - -#if 0 - // This really looks like it should be moved to P_3dMovement. -Red - if (P_AnalogMove(player) - && (cmd->forwardmove != 0 || cmd->sidemove != 0) && !player->climbing && !twodlevel && !(player->mo->flags2 & MF2_TWOD)) - { - // If travelling slow enough, face the way the controls - // point and not your direction of movement. - if (player->speed < FixedMul(5*FRACUNIT, player->mo->scale) || player->pflags & PF_GLIDING || !onground) - { - angle_t tempangle; - - tempangle = (cmd->angleturn << 16); - -#ifdef REDSANALOG // Ease to it. Chillax. ~Red - tempangle += R_PointToAngle2(0, 0, cmd->forwardmove*FRACUNIT, -cmd->sidemove*FRACUNIT); - { - fixed_t tweenvalue = max(abs(cmd->forwardmove), abs(cmd->sidemove)); - - if (tweenvalue < 10 && (cmd->buttons & (BT_FORWARD|BT_BACKWARD)) == (BT_FORWARD|BT_BACKWARD)) { - tempangle = (cmd->angleturn << 16); - tweenvalue = 16; - } - - tweenvalue *= tweenvalue*tweenvalue*1536; - - //if (player->pflags & PF_GLIDING) - //tweenvalue >>= 1; - - tempangle -= player->mo->angle; - - if (tempangle < ANGLE_180 && tempangle > tweenvalue) - player->mo->angle += tweenvalue; - else if (tempangle >= ANGLE_180 && InvAngle(tempangle) > tweenvalue) - player->mo->angle -= tweenvalue; - else - player->mo->angle += tempangle; - } -#else - // Less math this way ~Red - player->mo->angle = R_PointToAngle2(0, 0, cmd->forwardmove*FRACUNIT, -cmd->sidemove*FRACUNIT)+tempangle; -#endif - } - // Otherwise, face the direction you're travelling. - else if (player->panim == PA_WALK || player->panim == PA_RUN || player->panim == PA_ROLL - /*|| ((player->mo->state >= &states[S_PLAY_ABL1] && player->mo->state <= &states[S_PLAY_SPC4]) && player->charability == CA_FLY)*/) // SRB2kart - idk - player->mo->angle = R_PointToAngle2(0, 0, player->rmomx, player->rmomy); - - // Update the local angle control. - if (player == &players[consoleplayer]) - localangle[0] = player->mo->angle; - else if (player == &players[displayplayers[1]]) - localangle[1] = player->mo->angle; - else if (player == &players[displayplayers[2]]) - localangle[2] = player->mo->angle; - else if (player == &players[displayplayers[3]]) - localangle[3] = player->mo->angle; - } -#endif - /////////////////////////// //BOMB SHIELD ACTIVATION,// //HOMING, AND OTHER COOL // @@ -8181,12 +8096,6 @@ void P_PlayerThink(player_t *player) // The timer might've reached zero, but we'll run the remote view camera anyway by setting it to -1. } - /// \note do this in the cheat code - if (player->pflags & PF_NOCLIP) - player->mo->flags |= MF_NOCLIP; - else - player->mo->flags &= ~MF_NOCLIP; - cmd = &player->cmd; // SRB2kart @@ -8419,26 +8328,7 @@ void P_PlayerThink(player_t *player) player->mo->reactiontime--; else if (player->mo->tracer && player->mo->tracer->type == MT_TUBEWAYPOINT) { - // SRB2kart - don't need no rope hangin' - //if (player->pflags & PF_ROPEHANG) - //{ - // if (!P_AnalogMove(player)) - // player->mo->angle = (cmd->angleturn<<16 /* not FRACBITS */); - - // ticruned++; - // if ((cmd->angleturn & TICCMD_RECEIVED) == 0) - // ticmiss++; - - // P_DoRopeHang(player); - // P_SetPlayerMobjState(player->mo, S_PLAY_CARRY); - // P_DoJumpStuff(player, &player->cmd); - //} - //else - { - P_DoZoomTube(player); - //if (!(player->panim == PA_ROLL) && player->charability2 == CA2_SPINDASH) // SRB2kart - // P_SetPlayerMobjState(player->mo, S_PLAY_ATK1); - } + P_DoZoomTube(player); player->rmomx = player->rmomy = 0; // no actual momentum from your controls P_ResetScore(player); } diff --git a/src/st_stuff.c b/src/st_stuff.c index 4f46e1b69..8033dca70 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -1514,7 +1514,7 @@ static inline void ST_drawRaceHUD(void) // SRB2kart - unused. if (stplyr->exiting) V_DrawString(hudinfo[HUD_LAP].x, STRINGY(hudinfo[HUD_LAP].y), V_YELLOWMAP, "FINISHED!"); else - V_DrawString(hudinfo[HUD_LAP].x, STRINGY(hudinfo[HUD_LAP].y), 0, va("Lap: %u/%d", stplyr->laps+1, cv_numlaps.value)); + V_DrawString(hudinfo[HUD_LAP].x, STRINGY(hudinfo[HUD_LAP].y), 0, va("Lap: %u/%d", stplyr->laps, cv_numlaps.value)); } } */