diff --git a/src/Makefile b/src/Makefile index fee849d6c..16cb8912d 100644 --- a/src/Makefile +++ b/src/Makefile @@ -495,6 +495,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_main.c b/src/d_main.c index dee752107..283deecb4 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -525,7 +525,7 @@ static void D_Display(void) } if (demo.rewinding) - V_DrawFadeScreen(TC_RAINBOW, (leveltime & 0x20) ? SKINCOLOR_PASTEL : SKINCOLOR_MOONSLAM); + V_DrawFadeScreen(TC_RAINBOW, (leveltime & 0x20) ? SKINCOLOR_PASTEL : SKINCOLOR_MOONSET); // vid size change is now finished if it was on... vid.recalc = 0; diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 6e0825cce..549741443 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -389,6 +389,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}; @@ -460,6 +462,8 @@ consvar_t cv_pingtimeout = {"pingtimeout", "10", CV_SAVE, pingtimeout_cons_t, NU static CV_PossibleValue_t showping_cons_t[] = {{0, "Off"}, {1, "Always"}, {2, "Warning"}, {0, NULL}}; consvar_t cv_showping = {"showping", "Always", CV_SAVE, showping_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_showviewpointtext = {"showviewpointtext", "On", CV_SAVE, CV_OnOff, 0, 0, NULL, NULL, 0, 0, NULL}; + // Intermission time Tails 04-19-2002 static CV_PossibleValue_t inttime_cons_t[] = {{0, "MIN"}, {3600, "MAX"}, {0, NULL}}; consvar_t cv_inttime = {"inttime", "20", CV_NETVAR, inttime_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; @@ -720,6 +724,7 @@ void D_RegisterServerCommands(void) CV_RegisterVar(&cv_lagless); CV_RegisterVar(&cv_pingtimeout); CV_RegisterVar(&cv_showping); + CV_RegisterVar(&cv_showviewpointtext); #ifdef SEENAMES CV_RegisterVar(&cv_allowseenames); @@ -1886,8 +1891,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); } @@ -1898,8 +1901,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); } @@ -1910,8 +1911,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); } @@ -1922,8 +1921,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); } @@ -1931,11 +1928,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) @@ -2857,13 +2852,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 511076248..ca0b31b99 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -128,6 +128,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; @@ -148,6 +149,7 @@ extern consvar_t cv_maxping; extern consvar_t cv_lagless; extern consvar_t cv_pingtimeout; extern consvar_t cv_showping; +extern consvar_t cv_showviewpointtext; extern consvar_t cv_skipmapcheck; diff --git a/src/d_player.h b/src/d_player.h index c72c5df12..2465b90ef 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; @@ -251,10 +254,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 @@ -346,6 +345,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; @@ -454,6 +454,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 3933a4207..839f1d133 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -6347,6 +6347,8 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit "S_DRIFTSPARK_B1", "S_DRIFTSPARK_C1", "S_DRIFTSPARK_C2", + "S_DRIFTSPARK_D1", + "S_DRIFTSPARK_D2", // Brake drift sparks "S_BRAKEDRIFT", @@ -6357,6 +6359,12 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit "S_DRIFTDUST3", "S_DRIFTDUST4", + // Drift Sparkles + "S_DRIFTWARNSPARK1", + "S_DRIFTWARNSPARK2", + "S_DRIFTWARNSPARK3", + "S_DRIFTWARNSPARK4", + // Fast lines "S_FASTLINE1", "S_FASTLINE2", @@ -6373,7 +6381,11 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit "S_FASTDUST6", "S_FASTDUST7", - // Thunder Shield Burst + // Drift boost effect + "S_DRIFTEXPLODE1", + "S_DRIFTEXPLODE2", + "S_DRIFTEXPLODE3", + "S_DRIFTEXPLODE4", // Sneaker boost effect "S_BOOSTFLAME", @@ -6704,6 +6716,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", @@ -7790,6 +7807,7 @@ static const char *const MOBJTYPE_LIST[] = { // array length left dynamic for s "MT_FASTLINE", "MT_FASTDUST", + "MT_DRIFTEXPLODE", "MT_BOOSTFLAME", "MT_BOOSTSMOKE", "MT_SNEAKERTRAIL", @@ -8226,7 +8244,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. }; @@ -8285,7 +8303,7 @@ static const char *COLOR_ENUMS[] = { // Rejigged for Kart. "SCARLET", // SKINCOLOR_SCARLET "KETCHUP", // SKINCOLOR_KETCHUP "DAWN", // SKINCOLOR_DAWN - "SUNSET", // SKINCOLOR_SUNSET + "SUNSLAM", // SKINCOLOR_SUNSLAM "CREAMSICLE", // SKINCOLOR_CREAMSICLE "ORANGE", // SKINCOLOR_ORANGE "ROSEWOOD", // SKINCOLOR_ROSEWOOD @@ -8345,9 +8363,9 @@ static const char *COLOR_ENUMS[] = { // Rejigged for Kart. "THISTLE", // SKINCOLOR_THISTLE "PURPLE", // SKINCOLOR_PURPLE "PASTEL", // SKINCOLOR_PASTEL - "MOONSLAM", // SKINCOLOR_MOONSLAM + "MOONSET", // SKINCOLOR_MOONSET "DUSK", // SKINCOLOR_DUSK - "BUBBLEGUM", // SKINCOLOR_BUBBLEGUM + "VIOLET", // SKINCOLOR_VIOLET "MAGENTA", // SKINCOLOR_MAGENTA "FUCHSIA", // SKINCOLOR_FUCHSIA "TOXIC", // SKINCOLOR_TOXIC @@ -8463,10 +8481,6 @@ static const char *const KARTSTUFF_LIST[] = { "POSITION", "OLDPOSITION", "POSITIONDELAY", - "PREVCHECK", - "NEXTCHECK", - "WAYPOINT", - "STARPOSTWP", "STARPOSTFLIP", "RESPAWN", "DROPDASH", @@ -8553,7 +8567,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/doomdef.h b/src/doomdef.h index e1e9ab13d..bb21e3d0f 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -280,7 +280,7 @@ typedef enum SKINCOLOR_SCARLET, SKINCOLOR_KETCHUP, SKINCOLOR_DAWN, - SKINCOLOR_SUNSET, + SKINCOLOR_SUNSLAM, SKINCOLOR_CREAMSICLE, SKINCOLOR_ORANGE, SKINCOLOR_ROSEWOOD, @@ -340,9 +340,9 @@ typedef enum SKINCOLOR_THISTLE, SKINCOLOR_PURPLE, SKINCOLOR_PASTEL, - SKINCOLOR_MOONSLAM, + SKINCOLOR_MOONSET, SKINCOLOR_DUSK, - SKINCOLOR_BUBBLEGUM, + SKINCOLOR_VIOLET, SKINCOLOR_MAGENTA, SKINCOLOR_FUCHSIA, SKINCOLOR_TOXIC, diff --git a/src/g_game.c b/src/g_game.c index c6e318233..1bfe9e536 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2577,7 +2577,6 @@ void G_PlayerReborn(INT32 player) SINT8 pity; // SRB2kart - INT32 starpostwp; INT32 itemtype; INT32 itemamount; INT32 itemroulette; @@ -2599,7 +2598,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)) @@ -2641,12 +2640,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]; @@ -2713,7 +2709,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; @@ -3249,7 +3244,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 @@ -4940,7 +4936,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) { @@ -6962,7 +6961,7 @@ void G_LoadDemoInfo(menudemo_t *pdemo) (void)extrainfo_p; sprintf(pdemo->winnername, "transrights420"); pdemo->winnerskin = 1; - pdemo->winnercolor = SKINCOLOR_MOONSLAM; + pdemo->winnercolor = SKINCOLOR_MOONSET; pdemo->winnertime = 6666;*/ // Read standings! diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 6ccaf1bc5..73e65cceb 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -5357,7 +5357,7 @@ static void HWR_AddSprites(sector_t *sec) // Handle all things in sector. // If a limit exists, handle things a tiny bit different. - if ((limit_dist = (fixed_t)(/*(maptol & TOL_NIGHTS) ? cv_drawdist_nights.value : */cv_drawdist.value) << FRACBITS)) + if ((limit_dist = (fixed_t)(cv_drawdist.value) * mapobjectscale)) { for (thing = sec->thinglist; thing; thing = thing->snext) { @@ -5424,7 +5424,7 @@ static void HWR_AddSprites(sector_t *sec) #ifdef HWPRECIP // No to infinite precipitation draw distance. - if ((limit_dist = (fixed_t)cv_drawdist_precip.value << FRACBITS)) + if ((limit_dist = (fixed_t)(cv_drawdist_precip.value) * mapobjectscale)) { for (precipthing = sec->preciplist; precipthing; precipthing = precipthing->snext) { diff --git a/src/hu_stuff.c b/src/hu_stuff.c index 1afa133b3..76b85dd01 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -812,7 +812,6 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum) case SKINCOLOR_PINK: case SKINCOLOR_ROSE: case SKINCOLOR_LEMONADE: - case SKINCOLOR_BUBBLEGUM: case SKINCOLOR_LILAC: case SKINCOLOR_TAFFY: cstart = "\x8d"; // V_PINKMAP @@ -828,7 +827,7 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum) cstart = "\x85"; // V_REDMAP break; case SKINCOLOR_DAWN: - case SKINCOLOR_SUNSET: + case SKINCOLOR_SUNSLAM: case SKINCOLOR_CREAMSICLE: case SKINCOLOR_ORANGE: case SKINCOLOR_ROSEWOOD: @@ -906,7 +905,8 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum) break; case SKINCOLOR_MAGENTA: case SKINCOLOR_FUCHSIA: - case SKINCOLOR_MOONSLAM: + case SKINCOLOR_MOONSET: + case SKINCOLOR_VIOLET: cstart = "\x8c"; // V_MAGENTAMAP break; case SKINCOLOR_DUSK: diff --git a/src/info.c b/src/info.c index ed16eb7e5..3e7475164 100644 --- a/src/info.c +++ b/src/info.c @@ -57,7 +57,7 @@ char sprnames[NUMSPRITES + 1][5] = "SRBL","SRBM","SRBN","SRBO", //SRB2kart Sprites "RNDM","RPOP","SGNS","FAST","DSHR","BOST","BOSM","KFRE","KINV","KINF", - "WIPD","DRIF","BDRF","DUST","RSHE","FITM","BANA","ORBN","JAWZ","SSMN", + "WIPD","DRIF","BDRF","DUST","DRWS","RSHE","FITM","BANA","ORBN","JAWZ","SSMN", "KRBM","BHOG","BHBM","SPBM","THNS","BUBS","BWVE","FLMS","SINK","SITR", "KBLN","DEZL","POKE","AUDI","DECO","DOOD","SNES","GBAS","SPRS","BUZB", "CHOM","SACO","CRAB","BRNG","BUMP","FLEN","CLAS","PSHW","ISTA", @@ -70,7 +70,7 @@ char sprnames[NUMSPRITES + 1][5] = "ICEB","CNDL","DOCH","DUCK","GTRE","CHES","CHIM","DRGN","LZMN","PGSS", "ZTCH","MKMA","MKMP","RTCH","BOWL","BOWH","BRRL","BRRR","HRSE","TOAH", "BFRT","OFRT","RFRT","PFRT","ASPK","HBST","HBSO","HBSF","WBLZ","WBLN", - "FWRK","MXCL","RGSP","DRAF","GRES","OTFG","XMS4","XMS5","VIEW" + "FWRK","MXCL","RGSP","DRAF","GRES","OTFG","DBOS","XMS4","XMS5","VIEW" }; // Doesn't work with g++, needs actionf_p1 (don't modify this comment) @@ -1876,9 +1876,9 @@ state_t states[NUMSTATES] = {SPR_SPLH, FF_TRANS50|8, 2, {NULL}, 0, 0, S_NULL}, // S_SPLISH9 // Water Splash - {SPR_SPLA, FF_TRANS50 , 3, {NULL}, 0, 0, S_SPLASH2}, // S_SPLASH1 - {SPR_SPLA, FF_TRANS70|1, 3, {NULL}, 0, 0, S_SPLASH3}, // S_SPLASH2 - {SPR_SPLA, FF_TRANS90|2, 3, {NULL}, 0, 0, S_NULL}, // S_SPLASH3 + {SPR_SPLA, 0, 3, {NULL}, 0, 0, S_SPLASH2}, // S_SPLASH1 + {SPR_SPLA, 1, 3, {NULL}, 0, 0, S_SPLASH3}, // S_SPLASH2 + {SPR_SPLA, 2, 3, {NULL}, 0, 0, S_NULL}, // S_SPLASH3 // Smoke {SPR_SMOK, FF_TRANS50 , 4, {NULL}, 0, 0, S_SMOKE2}, // S_SMOKE1 @@ -2587,6 +2587,9 @@ state_t states[NUMSTATES] = {SPR_DRIF, FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_DRIFTSPARK_C2}, // S_DRIFTSPARK_C1 {SPR_DRIF, FF_FULLBRIGHT|FF_TRANS20, 1, {NULL}, 0, 0, S_DRIFTSPARK_A3}, // S_DRIFTSPARK_C2 (Loop back to A3) + {SPR_DRIF, FF_FULLBRIGHT|3, 2, {NULL}, 0, 0, S_DRIFTSPARK_D2}, // S_DRIFTSPARK_D1 + {SPR_DRIF, FF_FULLBRIGHT|2, 1, {NULL}, 0, 0, S_DRIFTSPARK_A2}, // S_DRIFTSPARK_D2 (Loop back to A2) + {SPR_BDRF, FF_FULLBRIGHT|FF_PAPERSPRITE|FF_ANIMATE, -1, {NULL}, 5, 2, S_BRAKEDRIFT}, // S_BRAKEDRIFT {SPR_DUST, 0, 3, {NULL}, 0, 0, S_DRIFTDUST2}, // S_DRIFTDUST1 @@ -2594,6 +2597,11 @@ state_t states[NUMSTATES] = {SPR_DUST, FF_TRANS20|2, 3, {NULL}, 0, 0, S_DRIFTDUST4}, // S_DRIFTDUST3 {SPR_DUST, FF_TRANS20|3, 3, {NULL}, 0, 0, S_NULL}, // S_DRIFTDUST4 + {SPR_DRWS, FF_FULLBRIGHT|0, 3, {NULL}, 0, 0, S_DRIFTWARNSPARK2}, // S_DRIFTWARNSPARK1 + {SPR_DRWS, FF_FULLBRIGHT|1, 3, {NULL}, 0, 0, S_DRIFTWARNSPARK3}, // S_DRIFTWARNSPARK2 + {SPR_DRWS, FF_FULLBRIGHT|FF_TRANS20|2, 3, {NULL}, 0, 0, S_DRIFTWARNSPARK4}, // S_DRIFTWARNSPARK3 + {SPR_DRWS, FF_FULLBRIGHT|FF_TRANS20|3, 3, {NULL}, 0, 0, S_NULL}, // S_DRIFTWARNSPARK4 + {SPR_FAST, FF_PAPERSPRITE|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_FASTLINE2}, // S_FASTLINE1 {SPR_FAST, FF_PAPERSPRITE|FF_FULLBRIGHT|1, 1, {NULL}, 0, 0, S_FASTLINE3}, // S_FASTLINE2 {SPR_FAST, FF_PAPERSPRITE|FF_FULLBRIGHT|2, 1, {NULL}, 0, 0, S_FASTLINE4}, // S_FASTLINE3 @@ -2608,6 +2616,11 @@ state_t states[NUMSTATES] = {SPR_DSHR, FF_PAPERSPRITE|5, 1, {NULL}, 0, 0, S_FASTDUST7}, // S_FASTDUST6 {SPR_DSHR, FF_PAPERSPRITE|6, 1, {NULL}, 0, 0, S_NULL}, // S_FASTDUST7 + {SPR_DBOS, FF_FULLBRIGHT, 2, {NULL}, 6, 1, S_DRIFTEXPLODE2}, // S_DRIFTEXPLODE1 + {SPR_DBOS, FF_FULLBRIGHT|1, 2, {NULL}, 6, 1, S_DRIFTEXPLODE3}, // S_DRIFTEXPLODE2 + {SPR_DBOS, FF_FULLBRIGHT|2, 2, {NULL}, 6, 1, S_DRIFTEXPLODE4}, // S_DRIFTEXPLODE3 + {SPR_DBOS, FF_FULLBRIGHT|3, 2, {NULL}, 6, 1, S_DRIFTEXPLODE1}, // S_DRIFTEXPLODE4 + {SPR_BOST, FF_FULLBRIGHT|FF_ANIMATE, TICRATE, {NULL}, 6, 1, S_BOOSTSMOKESPAWNER}, // S_BOOSTFLAME {SPR_NULL, 0, TICRATE/2, {NULL}, 0, 0, S_NULL}, // S_BOOSTSMOKESPAWNER @@ -2917,7 +2930,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 @@ -15264,6 +15282,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL // raisestate }, + { // MT_DRIFTEXPLODE + -1, // doomednum + S_DRIFTEXPLODE1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 8, // speed + 32*FRACUNIT, // radius + 64*FRACUNIT, // height + 1, // display offset + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags + S_NULL // raisestate + }, + { // MT_BOOSTFLAME -1, // doomednum S_BOOSTFLAME, // spawnstate @@ -16036,7 +16081,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_BALLHOG_DEAD, // deathstate S_NULL, // xdeathstate sfx_hogbom, // deathsound - 64*FRACUNIT, // speed + 80*FRACUNIT, // speed 16*FRACUNIT, // radius 32*FRACUNIT, // height 0, // display offset @@ -16090,7 +16135,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 @@ -16373,7 +16418,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { // MT_WAYPOINT 2001, // doomednum - S_NULL, // spawnstate + S_INVISIBLE, // spawnstate 1000, // spawnhealth S_NULL, // seestate sfx_None, // seesound @@ -16394,7 +16439,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 ddecea091..9d87a82e4 100644 --- a/src/info.h +++ b/src/info.h @@ -608,6 +608,7 @@ typedef enum sprite SPR_DRIF, // Drift Sparks SPR_BDRF, // Brake drift sparks SPR_DUST, // Drift Dust + SPR_DRWS, // Drift dust sparks // Kart Items SPR_RSHE, // Rocket sneaker @@ -788,6 +789,8 @@ typedef enum sprite SPR_OTFG, + SPR_DBOS, // Drift boost flame + // Xmas-specific sprites that don't fit aboxe SPR_XMS4, SPR_XMS5, @@ -3239,6 +3242,8 @@ typedef enum state S_DRIFTSPARK_B1, S_DRIFTSPARK_C1, S_DRIFTSPARK_C2, + S_DRIFTSPARK_D1, + S_DRIFTSPARK_D2, // Brake drift sparks S_BRAKEDRIFT, @@ -3249,6 +3254,12 @@ typedef enum state S_DRIFTDUST3, S_DRIFTDUST4, + // Drift Sparkles + S_DRIFTWARNSPARK1, + S_DRIFTWARNSPARK2, + S_DRIFTWARNSPARK3, + S_DRIFTWARNSPARK4, + // Fast lines S_FASTLINE1, S_FASTLINE2, @@ -3265,7 +3276,11 @@ typedef enum state S_FASTDUST6, S_FASTDUST7, - // Magnet Burst + // Drift boost effect + S_DRIFTEXPLODE1, + S_DRIFTEXPLODE2, + S_DRIFTEXPLODE3, + S_DRIFTEXPLODE4, // Sneaker boost effect S_BOOSTFLAME, @@ -3596,6 +3611,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, @@ -4714,6 +4734,7 @@ typedef enum mobj_type MT_FASTLINE, MT_FASTDUST, + MT_DRIFTEXPLODE, MT_BOOSTFLAME, MT_BOOSTSMOKE, MT_SNEAKERTRAIL, 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 d412b89cb..93bfe32e6 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 @@ -71,7 +74,7 @@ const char *KartColor_Names[MAXSKINCOLORS] = "Scarlet", // SKINCOLOR_SCARLET "Ketchup", // SKINCOLOR_KETCHUP "Dawn", // SKINCOLOR_DAWN - "Sunset", // SKINCOLOR_SUNSET + "Sunslam", // SKINCOLOR_SUNSLAM "Creamsicle", // SKINCOLOR_CREAMSICLE "Orange", // SKINCOLOR_ORANGE "Rosewood", // SKINCOLOR_ROSEWOOD @@ -131,9 +134,9 @@ const char *KartColor_Names[MAXSKINCOLORS] = "Thistle", // SKINCOLOR_THISTLE "Purple", // SKINCOLOR_PURPLE "Pastel", // SKINCOLOR_PASTEL - "Moonslam", // SKINCOLOR_MOONSLAM + "Moonset", // SKINCOLOR_MOONSET "Dusk", // SKINCOLOR_DUSK - "Bubblegum", // SKINCOLOR_BUBBLEGUM + "Violet", // SKINCOLOR_VIOLET "Magenta", // SKINCOLOR_MAGENTA "Fuchsia", // SKINCOLOR_FUCHSIA "Toxic", // SKINCOLOR_TOXIC @@ -177,7 +180,7 @@ const UINT8 KartColor_Opposite[MAXSKINCOLORS*2] = SKINCOLOR_ALGAE,10, // SKINCOLOR_SCARLET SKINCOLOR_MUSTARD,10, // SKINCOLOR_KETCHUP SKINCOLOR_DUSK,8, // SKINCOLOR_DAWN - SKINCOLOR_MOONSLAM,8, // SKINCOLOR_SUNSET + SKINCOLOR_MOONSET,8, // SKINCOLOR_SUNSLAM SKINCOLOR_PERIWINKLE,8, // SKINCOLOR_CREAMSICLE SKINCOLOR_BLUE,8, // SKINCOLOR_ORANGE SKINCOLOR_BLUEBERRY,6, // SKINCOLOR_ROSEWOOD @@ -192,7 +195,7 @@ const UINT8 KartColor_Opposite[MAXSKINCOLORS*2] = SKINCOLOR_KETCHUP,8, // SKINCOLOR_MUSTARD SKINCOLOR_EMERALD,8, // SKINCOLOR_BANANA SKINCOLOR_TEAL,8, // SKINCOLOR_OLIVE - SKINCOLOR_BUBBLEGUM,8, // SKINCOLOR_CROCODILE + SKINCOLOR_VIOLET,8, // SKINCOLOR_CROCODILE SKINCOLOR_NAVY,6, // SKINCOLOR_PERIDOT SKINCOLOR_ROBOHOOD,8, // SKINCOLOR_VOMIT SKINCOLOR_LAVENDER,6, // SKINCOLOR_GARDEN @@ -237,9 +240,9 @@ const UINT8 KartColor_Opposite[MAXSKINCOLORS*2] = SKINCOLOR_AZURE,8, // SKINCOLOR_THISTLE SKINCOLOR_CARIBBEAN,10, // SKINCOLOR_PURPLE SKINCOLOR_FUCHSIA,11, // SKINCOLOR_PASTEL - SKINCOLOR_SUNSET,10, // SKINCOLOR_MOONSLAM + SKINCOLOR_SUNSLAM,10, // SKINCOLOR_MOONSET SKINCOLOR_DAWN,6, // SKINCOLOR_DUSK - SKINCOLOR_CROCODILE,8, // SKINCOLOR_BUBBLEGUM + SKINCOLOR_CROCODILE,8, // SKINCOLOR_VIOLET SKINCOLOR_TURTLE,8, // SKINCOLOR_MAGENTA SKINCOLOR_PASTEL,11, // SKINCOLOR_FUCHSIA SKINCOLOR_MAROON,8, // SKINCOLOR_TOXIC @@ -258,7 +261,7 @@ UINT8 colortranslations[MAXTRANSLATIONS][16] = { { 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31}, // SKINCOLOR_GREY { 3, 5, 8, 11, 15, 17, 19, 21, 23, 24, 25, 26, 27, 29, 30, 31}, // SKINCOLOR_NICKEL { 4, 7, 11, 15, 20, 22, 24, 27, 28, 28, 28, 29, 29, 30, 30, 31}, // SKINCOLOR_BLACK - { 0, 1, 208, 208, 209, 210, 10, 14, 16, 18, 20, 22, 24, 26, 28, 31}, // SKINCOLOR_FAIRY + { 0, 0, 252, 252, 200, 201, 211, 14, 16, 18, 20, 22, 24, 26, 28, 31}, // SKINCOLOR_FAIRY { 0, 80, 80, 81, 82, 218, 240, 11, 13, 16, 18, 21, 23, 26, 28, 31}, // SKINCOLOR_POPCORN { 80, 88, 89, 98, 99, 91, 12, 14, 16, 18, 20, 22, 24, 26, 28, 31}, // SKINCOLOR_ARTICHOKE { 0, 128, 129, 130, 146, 170, 14, 15, 17, 19, 21, 23, 25, 27, 29, 31}, // SKINCOLOR_PIGEON @@ -281,7 +284,7 @@ UINT8 colortranslations[MAXTRANSLATIONS][16] = { { 48, 49, 50, 51, 53, 34, 36, 38, 184, 185, 168, 168, 169, 169, 254, 31}, // SKINCOLOR_SCARLET { 72, 73, 64, 51, 52, 54, 34, 36, 38, 40, 42, 43, 44, 71, 46, 47}, // SKINCOLOR_KETCHUP { 0, 208, 216, 209, 210, 211, 212, 57, 58, 59, 60, 61, 63, 71, 47, 31}, // SKINCOLOR_DAWN - { 82, 72, 73, 64, 51, 53, 55, 213, 214, 195, 195, 173, 174, 175, 253, 254}, // SKINCOLOR_SUNSET + { 82, 72, 73, 64, 51, 53, 55, 213, 214, 195, 195, 173, 174, 175, 253, 254}, // SKINCOLOR_SUNSLAM { 0, 0, 208, 208, 48, 49, 50, 52, 53, 54, 56, 57, 58, 60, 61, 63}, // SKINCOLOR_CREAMSICLE {208, 48, 49, 50, 51, 52, 53, 54, 55, 57, 59, 60, 62, 44, 71, 47}, // SKINCOLOR_ORANGE { 50, 52, 55, 56, 58, 59, 60, 61, 62, 63, 44, 45, 71, 46, 47, 30}, // SKINCOLOR_ROSEWOOD @@ -341,9 +344,9 @@ UINT8 colortranslations[MAXTRANSLATIONS][16] = { { 0, 0, 0, 252, 252, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 254}, // SKINCOLOR_THISTLE { 0, 252, 160, 161, 162, 163, 164, 165, 166, 167, 168, 168, 169, 169, 253, 254}, // SKINCOLOR_PURPLE { 0, 128, 128, 129, 129, 146, 170, 162, 163, 164, 165, 166, 167, 168, 169, 254}, // SKINCOLOR_PASTEL - { 0, 144, 145, 146, 170, 162, 163, 184, 184, 207, 207, 44, 45, 46, 47, 31}, // SKINCOLOR_MOONSLAM + { 0, 144, 145, 146, 170, 162, 163, 184, 184, 207, 207, 44, 45, 46, 47, 31}, // SKINCOLOR_MOONSET {252, 200, 201, 192, 193, 194, 172, 172, 173, 173, 174, 174, 175, 169, 253, 254}, // SKINCOLOR_DUSK - { 0, 252, 252, 200, 201, 181, 182, 183, 184, 166, 167, 168, 169, 253, 254, 31}, // SKINCOLOR_BUBBLEGUM + {176, 177, 178, 179, 180, 181, 182, 183, 184, 164, 165, 166, 167, 168, 169, 254}, // SKINCOLOR_VIOLET {176, 177, 178, 179, 180, 181, 182, 183, 184, 184, 185, 185, 186, 187, 30, 31}, // SKINCOLOR_MAGENTA {208, 209, 209, 32, 33, 182, 183, 184, 185, 185, 186, 186, 187, 253, 254, 31}, // SKINCOLOR_FUCHSIA { 0, 0, 88, 88, 89, 6, 8, 10, 193, 194, 195, 184, 185, 186, 187, 31}, // SKINCOLOR_TOXIC @@ -589,6 +592,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); @@ -741,7 +745,7 @@ static INT32 K_KartItemOddsBattle[NUMKARTRESULTS-1][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 INT32 K_GetShieldFromItem(INT32 item) { @@ -875,9 +879,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; @@ -928,7 +930,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); @@ -958,13 +961,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; @@ -972,7 +975,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) @@ -1033,9 +1036,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 { @@ -1060,11 +1063,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; @@ -1087,6 +1090,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)) { @@ -1118,15 +1125,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; @@ -1979,6 +1989,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 @@ -1994,39 +2119,136 @@ 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]) + destz -= (128 * mapobjectscale) + (player->mo->height); + else + destz += (128 * mapobjectscale); + + if (player->mo->x != destx || player->mo->y != desty || player->mo->z != destz) { - INT32 i; - if (!mapreset) - S_StartSound(player->mo, sfx_s3kcas); + fixed_t step = 64*mapobjectscale; + fixed_t dist = P_AproxDistance(P_AproxDistance(player->mo->x - destx, player->mo->y - desty), player->mo->z - destz); - for (i = 0; i < 8; i++) + 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<mo->y + P_ReturnThrustY(player->mo, newangle, 31<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<mo); - P_SetScale(mo, (mo->destscale = FRACUNIT)); + 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)); + } } } } @@ -2070,11 +2292,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(); + } } } } @@ -3218,8 +3473,62 @@ static mobj_t *K_SpawnKartMissile(mobj_t *source, mobjtype_t type, angle_t an, I return NULL; } +UINT8 K_DriftSparkColor(player_t *player, INT32 charge) +{ + INT32 ds = K_GetKartDriftSparkValue(player); + UINT8 color = SKINCOLOR_NONE; + + if (charge < 0) + { + // Stage 0: Yellow + color = SKINCOLOR_GOLD; + } + else if (charge >= ds*4) + { + // Stage 3: Rainbow + if (charge <= (ds*4)+(32*3)) + { + // transition + color = SKINCOLOR_SILVER; + } + else + { + color = (UINT8)(1 + (leveltime % (MAXSKINCOLORS-1))); + } + } + else if (charge >= ds*2) + { + // Stage 2: Blue + if (charge <= (ds*2)+(32*3)) + { + // transition + color = SKINCOLOR_PURPLE; + } + else + { + color = SKINCOLOR_SAPPHIRE; + } + } + else if (charge >= ds) + { + // Stage 1: Red + if (charge <= (ds)+(32*3)) + { + // transition + color = SKINCOLOR_TANGERINE; + } + else + { + color = SKINCOLOR_KETCHUP; + } + } + + return color; +} + static void K_SpawnDriftSparks(player_t *player) { + INT32 ds = K_GetKartDriftSparkValue(player); fixed_t newx; fixed_t newy; mobj_t *spark; @@ -3233,16 +3542,17 @@ static void K_SpawnDriftSparks(player_t *player) if (leveltime % 2 == 1) return; - if (!P_IsObjectOnGround(player->mo)) - return; - - if (!player->kartstuff[k_drift] || player->kartstuff[k_driftcharge] < K_GetKartDriftSparkValue(player)) + if (!player->kartstuff[k_drift] + || (player->kartstuff[k_driftcharge] < ds && !(player->kartstuff[k_driftcharge] < 0))) return; travelangle = player->mo->angle-(ANGLE_45/5)*player->kartstuff[k_drift]; for (i = 0; i < 2; i++) { + SINT8 size = 1; + UINT8 trail = 0; + newx = player->mo->x + P_ReturnThrustX(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_135, FixedMul(32*FRACUNIT, player->mo->scale)); newy = player->mo->y + P_ReturnThrustY(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_135, FixedMul(32*FRACUNIT, player->mo->scale)); spark = P_SpawnMobj(newx, newy, player->mo->z, MT_DRIFTSPARK); @@ -3256,20 +3566,51 @@ static void K_SpawnDriftSparks(player_t *player) spark->momy = player->mo->momy/2; //spark->momz = player->mo->momz/2; - if (player->kartstuff[k_driftcharge] >= K_GetKartDriftSparkValue(player)*4) + spark->color = K_DriftSparkColor(player, player->kartstuff[k_driftcharge]); + + if (player->kartstuff[k_driftcharge] < 0) { - spark->color = (UINT8)(1 + (leveltime % (MAXSKINCOLORS-1))); + // Stage 0: Yellow + size = 0; } - else if (player->kartstuff[k_driftcharge] >= K_GetKartDriftSparkValue(player)*2) + else if (player->kartstuff[k_driftcharge] >= ds*4) { - if (player->kartstuff[k_driftcharge] <= (K_GetKartDriftSparkValue(player)*2)+(24*3)) - spark->color = SKINCOLOR_RASPBERRY; // transition + // Stage 3: Rainbow + size = 2; + trail = 2; + + if (player->kartstuff[k_driftcharge] <= (ds*4)+(32*3)) + { + // transition + P_SetScale(spark, (spark->destscale = spark->scale*3/2)); + } else - spark->color = SKINCOLOR_KETCHUP; + { + spark->colorized = true; + } + } + else if (player->kartstuff[k_driftcharge] >= ds*2) + { + // Stage 2: Blue + size = 2; + trail = 1; + + if (player->kartstuff[k_driftcharge] <= (ds*2)+(32*3)) + { + // transition + P_SetScale(spark, (spark->destscale = spark->scale*3/2)); + } } else { - spark->color = SKINCOLOR_SAPPHIRE; + // Stage 1: Red + size = 1; + + if (player->kartstuff[k_driftcharge] <= (ds)+(32*3)) + { + // transition + P_SetScale(spark, (spark->destscale = spark->scale*2)); + } } if ((player->kartstuff[k_drift] > 0 && player->cmd.driftturn > 0) // Inward drifts @@ -3277,22 +3618,40 @@ static void K_SpawnDriftSparks(player_t *player) { if ((player->kartstuff[k_drift] < 0 && (i & 1)) || (player->kartstuff[k_drift] > 0 && !(i & 1))) - P_SetMobjState(spark, S_DRIFTSPARK_A1); + { + size++; + } else if ((player->kartstuff[k_drift] < 0 && !(i & 1)) || (player->kartstuff[k_drift] > 0 && (i & 1))) - P_SetMobjState(spark, S_DRIFTSPARK_C1); + { + size--; + } } else if ((player->kartstuff[k_drift] > 0 && player->cmd.driftturn < 0) // Outward drifts || (player->kartstuff[k_drift] < 0 && player->cmd.driftturn > 0)) { if ((player->kartstuff[k_drift] < 0 && (i & 1)) || (player->kartstuff[k_drift] > 0 && !(i & 1))) - P_SetMobjState(spark, S_DRIFTSPARK_C1); + { + size--; + } else if ((player->kartstuff[k_drift] < 0 && !(i & 1)) || (player->kartstuff[k_drift] > 0 && (i & 1))) - P_SetMobjState(spark, S_DRIFTSPARK_A1); + { + size++; + } } + if (size == 2) + P_SetMobjState(spark, S_DRIFTSPARK_A1); + else if (size < 1) + P_SetMobjState(spark, S_DRIFTSPARK_C1); + else if (size > 2) + P_SetMobjState(spark, S_DRIFTSPARK_D1); + + if (trail > 0) + spark->tics += trail; + K_MatchGenericExtraFlags(spark, player->mo); } } @@ -3554,7 +3913,7 @@ void K_SpawnDraftDust(mobj_t *mo) void K_DriftDustHandling(mobj_t *spawner) { angle_t anglediff; - const INT16 spawnrange = spawner->radius>>FRACBITS; + const INT16 spawnrange = spawner->radius >> FRACBITS; if (!P_IsObjectOnGround(spawner) || leveltime % 2 != 0) return; @@ -3571,7 +3930,7 @@ void K_DriftDustHandling(mobj_t *spawner) { angle_t playerangle = spawner->angle; - if (spawner->player->speed < 5<player->speed < 5*spawner->scale) return; if (spawner->player->cmd.forwardmove < 0) @@ -3582,7 +3941,7 @@ void K_DriftDustHandling(mobj_t *spawner) } else { - if (P_AproxDistance(spawner->momx, spawner->momy) < 5<momx, spawner->momy) < 5*spawner->scale) return; anglediff = abs((signed)(spawner->angle - R_PointToAngle2(0, 0, spawner->momx, spawner->momy))); @@ -3593,12 +3952,12 @@ void K_DriftDustHandling(mobj_t *spawner) if (anglediff > ANG10*4) // Trying to turn further than 40 degrees { - fixed_t spawnx = P_RandomRange(-spawnrange, spawnrange)<x + spawnx, spawner->y + spawny, spawner->z, MT_DRIFTDUST); - dust->momx = FixedMul(spawner->momx + (P_RandomRange(-speedrange, speedrange)<scale)/4); - dust->momy = FixedMul(spawner->momy + (P_RandomRange(-speedrange, speedrange)<scale)/4); + dust->momx = FixedMul(spawner->momx + (P_RandomRange(-speedrange, speedrange) * spawner->scale), 3*FRACUNIT/4); + dust->momy = FixedMul(spawner->momy + (P_RandomRange(-speedrange, speedrange) * spawner->scale), 3*FRACUNIT/4); dust->momz = P_MobjFlip(spawner) * (P_RandomRange(1, 4) * (spawner->scale)); P_SetScale(dust, spawner->scale/2); dust->destscale = spawner->scale * 3; @@ -3608,6 +3967,35 @@ void K_DriftDustHandling(mobj_t *spawner) S_StartSound(spawner, sfx_screec); K_MatchGenericExtraFlags(dust, spawner); + + // Sparkle-y warning for when you're about to change drift sparks! + if (spawner->player && spawner->player->kartstuff[k_drift]) + { + INT32 driftval = K_GetKartDriftSparkValue(spawner->player); + INT32 warntime = driftval/3; + INT32 dc = spawner->player->kartstuff[k_driftcharge]; + UINT8 c = SKINCOLOR_NONE; + boolean rainbow = false; + + if (dc >= 0) + { + dc += warntime; + } + + c = K_DriftSparkColor(spawner->player, dc); + + if (dc > (4*driftval)+(32*3)) + { + rainbow = true; + } + + if (c != SKINCOLOR_NONE) + { + P_SetMobjState(dust, S_DRIFTWARNSPARK1); + dust->color = c; + dust->colorized = rainbow; + } + } } } @@ -3639,10 +4027,18 @@ static mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t map return NULL; // Figure out projectile speed by game speed - if (missile && mapthing != MT_BALLHOG) // Trying to keep compatability... - PROJSPEED = FixedMul(mobjinfo[mapthing].speed, FRACUNIT + ((gamespeed-1) * (FRACUNIT/4))); + if (missile) + { + // Use info->speed for missiles + PROJSPEED = FixedMul(mobjinfo[mapthing].speed, K_GetKartGameSpeedScalar(gamespeed)); + } else - PROJSPEED = (82 + ((gamespeed-1) * 14)) * FRACUNIT; // Avg Speed is 41 in Normal + { + // Use pre-determined speed for tossing + PROJSPEED = FixedMul(82 << FRACBITS, K_GetKartGameSpeedScalar(gamespeed)); + } + + // Scale to map size PROJSPEED = FixedMul(PROJSPEED, mapobjectscale); if (altthrow) @@ -3688,11 +4084,11 @@ static mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t map if (dir == -1) { // Shoot backward - mo = K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180 - 0x06000000, 0, PROJSPEED/4); - K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180 - 0x03000000, 0, PROJSPEED/4); - K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180, 0, PROJSPEED/4); - K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180 + 0x03000000, 0, PROJSPEED/4); - K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180 + 0x06000000, 0, PROJSPEED/4); + mo = K_SpawnKartMissile(player->mo, mapthing, (player->mo->angle + ANGLE_180) - 0x06000000, 0, PROJSPEED/8); + K_SpawnKartMissile(player->mo, mapthing, (player->mo->angle + ANGLE_180) - 0x03000000, 0, PROJSPEED/8); + K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180, 0, PROJSPEED/8); + K_SpawnKartMissile(player->mo, mapthing, (player->mo->angle + ANGLE_180) + 0x03000000, 0, PROJSPEED/8); + K_SpawnKartMissile(player->mo, mapthing, (player->mo->angle + ANGLE_180) + 0x06000000, 0, PROJSPEED/8); } else { @@ -3709,7 +4105,7 @@ static mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t map if (dir == -1 && mapthing != MT_SPB) { // Shoot backward - mo = K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180, 0, PROJSPEED/2); + mo = K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180, 0, PROJSPEED/8); } else { @@ -3743,7 +4139,7 @@ static mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t map if (mo) { angle_t fa = player->mo->angle>>ANGLETOFINESHIFT; - fixed_t HEIGHT = (20 + (dir*10))*mapobjectscale + (player->mo->momz*P_MobjFlip(player->mo)); + fixed_t HEIGHT = (20 + (dir*10))*FRACUNIT + (player->mo->momz*P_MobjFlip(player->mo)); P_SetObjectMomZ(mo, HEIGHT, false); mo->momx = player->mo->momx + FixedMul(FINECOSINE(fa), PROJSPEED*dir); @@ -4101,6 +4497,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; @@ -4118,10 +4515,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]); @@ -4138,6 +4532,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; + } } @@ -4323,6 +4744,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; @@ -5685,8 +6107,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]--; @@ -5810,6 +6239,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! @@ -5871,6 +6304,346 @@ 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_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 @@ -5970,42 +6743,57 @@ static void K_KartDrift(player_t *player, boolean onground) // 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)) - && player->kartstuff[k_driftcharge] < dsone && onground) { - player->kartstuff[k_driftcharge] = 0; - } - else 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)) - && (player->kartstuff[k_driftcharge] >= dsone && player->kartstuff[k_driftcharge] < dstwo) - && onground) - { - if (player->kartstuff[k_driftboost] < 20) - player->kartstuff[k_driftboost] = 20; - S_StartSound(player->mo, sfx_s23c); - //K_SpawnDashDustRelease(player); - player->kartstuff[k_driftcharge] = 0; - } - else 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)) - && player->kartstuff[k_driftcharge] < dsthree - && onground) - { - if (player->kartstuff[k_driftboost] < 50) - player->kartstuff[k_driftboost] = 50; - S_StartSound(player->mo, sfx_s23c); - //K_SpawnDashDustRelease(player); - player->kartstuff[k_driftcharge] = 0; - } - else 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)) - && player->kartstuff[k_driftcharge] >= dsthree - && onground) - { - if (player->kartstuff[k_driftboost] < 125) - player->kartstuff[k_driftboost] = 125; - S_StartSound(player->mo, sfx_s23c); - //K_SpawnDashDustRelease(player); + if (player->kartstuff[k_driftcharge] < 0 || player->kartstuff[k_driftcharge] >= dsone) + { + //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)); + //K_FlipFromObject(overlay, player->mo); + + S_StartSound(player->mo, sfx_s23c); + //K_SpawnDashDustRelease(player); + + if (player->kartstuff[k_driftcharge] < 0) + { + // Stage 0: Yellow sparks + if (player->kartstuff[k_driftboost] < 15) + player->kartstuff[k_driftboost] = 15; + + //overlay->color = SKINCOLOR_GOLD; + //overlay->fuse = 8; + } + else if (player->kartstuff[k_driftcharge] >= dsone && player->kartstuff[k_driftcharge] < dstwo) + { + // Stage 1: Red sparks + if (player->kartstuff[k_driftboost] < 20) + player->kartstuff[k_driftboost] = 20; + + //overlay->color = SKINCOLOR_KETCHUP; + //overlay->fuse = 16; + } + else if (player->kartstuff[k_driftcharge] < dsthree) + { + // Stage 2: Blue sparks + if (player->kartstuff[k_driftboost] < 50) + player->kartstuff[k_driftboost] = 50; + + //overlay->color = SKINCOLOR_SAPPHIRE; + //overlay->fuse = 32; + } + else if (player->kartstuff[k_driftcharge] >= dsthree) + { + // Stage 3: Rainbow sparks + if (player->kartstuff[k_driftboost] < 125) + player->kartstuff[k_driftboost] = 125; + + //overlay->color = SKINCOLOR_SILVER; + //overlay->fuse = 120; + } + } + + // Remove charge player->kartstuff[k_driftcharge] = 0; } @@ -6041,71 +6829,107 @@ static void K_KartDrift(player_t *player, boolean onground) player->kartstuff[k_driftend] = 0; } - - // Incease/decrease the drift value to continue drifting in that direction - if (player->kartstuff[k_spinouttimer] == 0 && player->kartstuff[k_jmp] == 1 && onground && player->kartstuff[k_drift] != 0) + if (player->kartstuff[k_spinouttimer] > 0 || player->speed == 0) { + // Stop drifting + player->kartstuff[k_drift] = player->kartstuff[k_driftcharge] = 0; + player->kartstuff[k_aizdriftstrat] = player->kartstuff[k_brakedrift] = 0; + player->kartstuff[k_getsparks] = 0; + } + else if (player->kartstuff[k_jmp] == 1 && player->kartstuff[k_drift] != 0) + { + // Incease/decrease the drift value to continue drifting in that direction fixed_t driftadditive = 24; + boolean playsound = false; - if (player->kartstuff[k_drift] >= 1) // Drifting to the left + if (onground) { - player->kartstuff[k_drift]++; - if (player->kartstuff[k_drift] > 5) - player->kartstuff[k_drift] = 5; + if (player->kartstuff[k_drift] >= 1) // Drifting to the left + { + player->kartstuff[k_drift]++; + if (player->kartstuff[k_drift] > 5) + player->kartstuff[k_drift] = 5; - if (player->cmd.driftturn > 0) // Inward - driftadditive += abs(player->cmd.driftturn)/100; - if (player->cmd.driftturn < 0) // Outward - driftadditive -= abs(player->cmd.driftturn)/75; + if (player->cmd.driftturn > 0) // Inward + driftadditive += abs(player->cmd.driftturn)/100; + if (player->cmd.driftturn < 0) // Outward + driftadditive -= abs(player->cmd.driftturn)/75; + } + else if (player->kartstuff[k_drift] <= -1) // Drifting to the right + { + player->kartstuff[k_drift]--; + if (player->kartstuff[k_drift] < -5) + player->kartstuff[k_drift] = -5; + + if (player->cmd.driftturn < 0) // Inward + driftadditive += abs(player->cmd.driftturn)/100; + if (player->cmd.driftturn > 0) // Outward + driftadditive -= abs(player->cmd.driftturn)/75; + } + + // Disable drift-sparks until you're going fast enough + if (player->kartstuff[k_getsparks] == 0 + || (player->kartstuff[k_offroad] + && !player->kartstuff[k_invincibilitytimer] + && !player->kartstuff[k_hyudorotimer] + && !EITHERSNEAKER(player))) + driftadditive = 0; + + // Inbetween minspeed and minspeed*2, it'll keep your previous drift-spark state. + if (player->speed > minspeed*2) + { + player->kartstuff[k_getsparks] = 1; + + if (player->kartstuff[k_driftcharge] <= -1) + { + player->kartstuff[k_driftcharge] = dsone; // Back to red + playsound = true; + } + } + else if (player->speed <= minspeed) + { + player->kartstuff[k_getsparks] = 0; + driftadditive = 0; + + if (player->kartstuff[k_driftcharge] >= dsone) + { + player->kartstuff[k_driftcharge] = -1; // Set yellow sparks + playsound = true; + } + } } - else if (player->kartstuff[k_drift] <= -1) // Drifting to the right + else { - player->kartstuff[k_drift]--; - if (player->kartstuff[k_drift] < -5) - player->kartstuff[k_drift] = -5; - - if (player->cmd.driftturn < 0) // Inward - driftadditive += abs(player->cmd.driftturn)/100; - if (player->cmd.driftturn > 0) // Outward - driftadditive -= abs(player->cmd.driftturn)/75; - } - - // Disable drift-sparks until you're going fast enough - if (player->kartstuff[k_getsparks] == 0 - || (player->kartstuff[k_offroad] - && !player->kartstuff[k_invincibilitytimer] - && !player->kartstuff[k_hyudorotimer] - && !EITHERSNEAKER(player))) driftadditive = 0; - if (player->speed > minspeed*2) - player->kartstuff[k_getsparks] = 1; + } // This spawns the drift sparks - if (player->kartstuff[k_driftcharge] + driftadditive >= dsone) + if ((player->kartstuff[k_driftcharge] + driftadditive >= dsone) + || (player->kartstuff[k_driftcharge] < 0)) + { K_SpawnDriftSparks(player); + } + + if ((player->kartstuff[k_driftcharge] < dsone && player->kartstuff[k_driftcharge]+driftadditive >= dsone) + || (player->kartstuff[k_driftcharge] < dstwo && player->kartstuff[k_driftcharge]+driftadditive >= dstwo) + || (player->kartstuff[k_driftcharge] < dsthree && player->kartstuff[k_driftcharge]+driftadditive >= dsthree)) + { + playsound = true; + } // Sound whenever you get a different tier of sparks - if (P_IsDisplayPlayer(player) // UGHGHGH... - && ((player->kartstuff[k_driftcharge] < dsone && player->kartstuff[k_driftcharge]+driftadditive >= dsone) - || (player->kartstuff[k_driftcharge] < dstwo && player->kartstuff[k_driftcharge]+driftadditive >= dstwo) - || (player->kartstuff[k_driftcharge] < dsthree && player->kartstuff[k_driftcharge]+driftadditive >= dsthree))) + if (playsound && P_IsDisplayPlayer(player)) { - //S_StartSound(player->mo, sfx_s3ka2); - S_StartSoundAtVolume(player->mo, sfx_s3ka2, 192); // Ugh... + if (player->kartstuff[k_driftcharge] == -1) + S_StartSoundAtVolume(player->mo, sfx_sploss, 192); // Yellow spark sound + else + S_StartSoundAtVolume(player->mo, sfx_s3ka2, 192); } player->kartstuff[k_driftcharge] += driftadditive; player->kartstuff[k_driftend] = 0; } - // Stop drifting - if (player->kartstuff[k_spinouttimer] > 0 || player->speed < minspeed) - { - player->kartstuff[k_drift] = player->kartstuff[k_driftcharge] = 0; - player->kartstuff[k_aizdriftstrat] = player->kartstuff[k_brakedrift] = 0; - player->kartstuff[k_getsparks] = 0; - } - if ((!EITHERSNEAKER(player)) || (!player->cmd.driftturn) || (!player->kartstuff[k_aizdriftstrat]) @@ -6138,12 +6962,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++) { @@ -6152,69 +6979,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++; } } } @@ -6222,14 +7000,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++; } } @@ -6318,7 +7098,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) { @@ -7047,13 +7828,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; } @@ -7355,7 +8132,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; } @@ -7442,6 +8219,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]; @@ -7602,6 +8381,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); @@ -8695,7 +9476,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) @@ -9079,8 +9860,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]]); @@ -9093,7 +9874,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]); } @@ -9135,7 +9916,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) @@ -9358,17 +10139,17 @@ static void K_drawKartWanted(void) return; // set X/Y coords depending on splitscreen. - if (splitscreen < 3) // 1P and 2P use the same code. + if (splitscreen < 3) // 1P and 2P use the same code. { basex = WANT_X; basey = WANT_Y; if (splitscreen == 2) { - basey += 16; // slight adjust for 3P + basey += 16; // slight adjust for 3P basex -= 6; } } - else if (splitscreen == 3) // 4P splitscreen... + else if (splitscreen == 3) // 4P splitscreen... { basex = BASEVIDWIDTH/2 - (SHORT(kp_wantedsplit->width)/2); // center on screen basey = BASEVIDHEIGHT - 55; @@ -9545,6 +10326,7 @@ static void K_drawKartMinimap(void) UINT8 *colormap = NULL; SINT8 localplayers[4]; SINT8 numlocalplayers = 0; + mobj_t *mobj, *next; // for SPB drawing (or any other item(s) we may wanna draw, I dunno!) // Draw the HUD only when playing in a level. // hu_stuff needs this, unlike st_stuff. @@ -9699,6 +10481,26 @@ static void K_drawKartMinimap(void) } } + // 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; @@ -9996,57 +10798,44 @@ static void K_drawKartFirstPerson(void) if (stplyr->mo) { - INT32 dsone = K_GetKartDriftSparkValue(stplyr); - INT32 dstwo = dsone*2; - INT32 dsthree = dstwo*2; + UINT8 driftcolor = K_DriftSparkColor(stplyr, stplyr->kartstuff[k_driftcharge]); + const angle_t ang = R_PointToAngle2(0, 0, stplyr->rmomx, stplyr->rmomy) - stplyr->frameangle; + // yes, the following is correct. no, you do not need to swap the x and y. + fixed_t xoffs = -P_ReturnThrustY(stplyr->mo, ang, (BASEVIDWIDTH<<(FRACBITS-2))/2); + fixed_t yoffs = -(P_ReturnThrustX(stplyr->mo, ang, 4*FRACUNIT) - 4*FRACUNIT); -#ifndef DONTLIKETOASTERSFPTWEAKS + if (splitscreen) + xoffs = FixedMul(xoffs, scale); + + xoffs -= (tn)*scale; + xoffs -= (dr)*scale; + + if (stplyr->frameangle == stplyr->mo->angle) { - const angle_t ang = R_PointToAngle2(0, 0, stplyr->rmomx, stplyr->rmomy) - stplyr->frameangle; - // yes, the following is correct. no, you do not need to swap the x and y. - fixed_t xoffs = -P_ReturnThrustY(stplyr->mo, ang, (BASEVIDWIDTH<<(FRACBITS-2))/2); - fixed_t yoffs = -(P_ReturnThrustX(stplyr->mo, ang, 4*FRACUNIT) - 4*FRACUNIT); + const fixed_t mag = FixedDiv(stplyr->speed, 10*stplyr->mo->scale); - if (splitscreen) - xoffs = FixedMul(xoffs, scale); - - xoffs -= (tn)*scale; - xoffs -= (dr)*scale; - - if (stplyr->frameangle == stplyr->mo->angle) + if (mag < FRACUNIT) { - const fixed_t mag = FixedDiv(stplyr->speed, 10*stplyr->mo->scale); - - if (mag < FRACUNIT) - { - xoffs = FixedMul(xoffs, mag); - if (!splitscreen) - yoffs = FixedMul(yoffs, mag); - } + xoffs = FixedMul(xoffs, mag); + if (!splitscreen) + yoffs = FixedMul(yoffs, mag); } - - if (stplyr->mo->momz > 0) // TO-DO: Draw more of the kart so we can remove this if! - yoffs += stplyr->mo->momz/3; - - if (encoremode) - x -= xoffs; - else - x += xoffs; - if (!splitscreen) - y += yoffs; } - // drift sparks! - if ((leveltime & 1) && (stplyr->kartstuff[k_driftcharge] >= dsthree)) - colmap = R_GetTranslationColormap(TC_RAINBOW, (UINT8)(1 + (leveltime % (MAXSKINCOLORS-1))), GTC_CACHE); - else if ((leveltime & 1) && (stplyr->kartstuff[k_driftcharge] >= dstwo)) - colmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_KETCHUP, GTC_CACHE); - else if ((leveltime & 1) && (stplyr->kartstuff[k_driftcharge] >= dsone)) - colmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_SAPPHIRE, GTC_CACHE); + if (stplyr->mo->momz > 0) // TO-DO: Draw more of the kart so we can remove this if! + yoffs += stplyr->mo->momz/3; + + if (encoremode) + x -= xoffs; else -#endif - // invincibility/grow/shrink! - if (stplyr->mo->colorized && stplyr->mo->color) + x += xoffs; + if (!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! colmap = R_GetTranslationColormap(TC_RAINBOW, stplyr->mo->color, GTC_CACHE); } @@ -10191,7 +10980,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 @@ -10218,14 +11007,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); } } } @@ -10274,9 +11063,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; @@ -10297,13 +11086,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 @@ -10366,11 +11155,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) @@ -10573,6 +11370,11 @@ void K_drawKartHUD(void) K_drawKartFreePlay(leveltime); } + if (splitscreen == 0 && stplyr->kartstuff[k_wrongway] && ((leveltime / 8) & 1)) + { + V_DrawCenteredString(BASEVIDWIDTH>>1, 176, V_REDMAP|V_SNAPTOBOTTOM, "WRONG WAY"); + } + if (cv_kartdebugdistribution.value) K_drawDistributionDebugger(); @@ -10604,6 +11406,8 @@ void K_drawKartHUD(void) } } } + + K_DrawWaypointDebugger(); } //} diff --git a/src/k_kart.h b/src/k_kart.h index 731078479..ec4a78afe 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -32,6 +32,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); @@ -45,6 +46,7 @@ void K_ExplodePlayer(player_t *player, mobj_t *source, mobj_t *inflictor); void K_StealBumper(player_t *player, player_t *victim, boolean force); void K_SpawnKartExplosion(fixed_t x, fixed_t y, fixed_t z, fixed_t radius, INT32 number, mobjtype_t type, angle_t rotangle, boolean spawncenter, boolean ghostit, mobj_t *source); void K_SpawnMineExplosion(mobj_t *source, UINT8 color); +UINT8 K_DriftSparkColor(player_t *player, INT32 charge); void K_SpawnBoostTrail(player_t *player); void K_SpawnSparkleTrail(mobj_t *mo); void K_SpawnWipeoutTrail(mobj_t *mo, boolean translucent); 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/lua_hudlib.c b/src/lua_hudlib.c index ec76552d1..2fe8c33a2 100644 --- a/src/lua_hudlib.c +++ b/src/lua_hudlib.c @@ -139,6 +139,8 @@ enum cameraf { camera_momx, camera_momy, camera_momz, + camera_pan, + camera_pitch, camera_pnum }; @@ -158,6 +160,8 @@ static const char *const camera_opt[] = { "momx", "momy", "momz", + "pan", + "pitch", "pnum", NULL}; @@ -314,6 +318,12 @@ static int camera_get(lua_State *L) case camera_momz: lua_pushinteger(L, cam->momz); break; + case camera_pan: + lua_pushinteger(L, cam->pan); + break; + case camera_pitch: + lua_pushinteger(L, cam->pitch); + break; case camera_pnum: lua_pushinteger(L, camnum); break; 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 8c3a190bd..9da6a074d 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 37b7ff36f..85bd34a94 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1483,15 +1483,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) @@ -1512,13 +1506,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 1358bf195..a4e661bfc 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -107,6 +107,8 @@ typedef struct camera_s // SRB2Kart: camera pans while drifting fixed_t pan; + // SRB2Kart: camera pitches on slopes + angle_t pitch; } camera_t; extern camera_t camera[MAXSPLITSCREENPLAYERS]; @@ -180,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 f82271d8d..2316dc07e 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; @@ -195,7 +305,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object) if (horizspeed) { angle_t finalAngle = spring->angle; - fixed_t finalSpeed = horizspeed; + fixed_t finalSpeed = FixedMul(horizspeed, FixedSqrt(FixedMul(hscale, spring->scale))); fixed_t objectSpeed; if (object->player) @@ -273,7 +383,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object) // Horizontal speed is used as a minimum thrust, not a direct replacement finalSpeed = max(objectSpeed, finalSpeed); - P_InstaThrustEvenIn2D(object, finalAngle, FixedMul(finalSpeed, FixedSqrt(FixedMul(hscale, spring->scale)))); + P_InstaThrustEvenIn2D(object, finalAngle, finalSpeed); } // Re-solidify @@ -932,7 +1042,7 @@ static boolean PIT_CheckThing(mobj_t *thing) S_StartSound(tmthing, tmthing->info->deathsound); P_KillMobj(tmthing, thing, thing); - P_SetObjectMomZ(tmthing, 8*FRACUNIT, false); + P_SetObjectMomZ(tmthing, 12*FRACUNIT, false); P_InstaThrust(tmthing, R_PointToAngle2(thing->x, thing->y, tmthing->x, tmthing->y)+ANGLE_90, 16*FRACUNIT); } else if (thing->type == MT_ORBINAUT || thing->type == MT_JAWZ || thing->type == MT_JAWZ_DUD @@ -949,7 +1059,7 @@ static boolean PIT_CheckThing(mobj_t *thing) S_StartSound(thing, thing->info->deathsound); P_KillMobj(thing, tmthing, tmthing); - P_SetObjectMomZ(thing, 8*FRACUNIT, false); + P_SetObjectMomZ(thing, 12*FRACUNIT, false); P_InstaThrust(thing, R_PointToAngle2(tmthing->x, tmthing->y, thing->x, thing->y)+ANGLE_90, 16*FRACUNIT); P_SpawnMobj(thing->x/2 + tmthing->x/2, thing->y/2 + tmthing->y/2, thing->z/2 + tmthing->z/2, MT_ITEMCLASH); @@ -963,7 +1073,7 @@ static boolean PIT_CheckThing(mobj_t *thing) S_StartSound(tmthing, tmthing->info->deathsound); P_KillMobj(tmthing, thing, thing); - P_SetObjectMomZ(tmthing, 8*FRACUNIT, false); + P_SetObjectMomZ(tmthing, 12*FRACUNIT, false); P_InstaThrust(tmthing, R_PointToAngle2(thing->x, thing->y, tmthing->x, tmthing->y)+ANGLE_90, 16*FRACUNIT); } else if (thing->type == MT_SSMINE_SHIELD || thing->type == MT_SSMINE) @@ -977,7 +1087,7 @@ static boolean PIT_CheckThing(mobj_t *thing) S_StartSound(tmthing, tmthing->info->deathsound); P_KillMobj(tmthing, thing, thing); - P_SetObjectMomZ(tmthing, 8*FRACUNIT, false); + P_SetObjectMomZ(tmthing, 12*FRACUNIT, false); P_InstaThrust(tmthing, R_PointToAngle2(thing->x, thing->y, tmthing->x, tmthing->y)+ANGLE_90, 16*FRACUNIT); // Bomb death @@ -1019,12 +1129,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); } @@ -1097,7 +1215,7 @@ static boolean PIT_CheckThing(mobj_t *thing) S_StartSound(tmthing, tmthing->info->deathsound); P_KillMobj(tmthing, thing, thing); - P_SetObjectMomZ(tmthing, 8*FRACUNIT, false); + P_SetObjectMomZ(tmthing, 12*FRACUNIT, false); P_InstaThrust(tmthing, R_PointToAngle2(thing->x, thing->y, tmthing->x, tmthing->y)+ANGLE_90, 16*FRACUNIT); } else if (thing->type == MT_BANANA || thing->type == MT_BANANA_SHIELD @@ -1114,7 +1232,7 @@ static boolean PIT_CheckThing(mobj_t *thing) S_StartSound(thing, thing->info->deathsound); P_KillMobj(thing, tmthing, tmthing); - P_SetObjectMomZ(thing, 8*FRACUNIT, false); + P_SetObjectMomZ(thing, 12*FRACUNIT, false); P_InstaThrust(thing, R_PointToAngle2(tmthing->x, tmthing->y, thing->x, thing->y)+ANGLE_90, 16*FRACUNIT); P_SpawnMobj(thing->x/2 + tmthing->x/2, thing->y/2 + tmthing->y/2, thing->z/2 + tmthing->z/2, MT_ITEMCLASH); @@ -1128,7 +1246,7 @@ static boolean PIT_CheckThing(mobj_t *thing) S_StartSound(tmthing, tmthing->info->deathsound); P_KillMobj(tmthing, thing, thing); - P_SetObjectMomZ(tmthing, 8*FRACUNIT, false); + P_SetObjectMomZ(tmthing, 12*FRACUNIT, false); P_InstaThrust(tmthing, R_PointToAngle2(thing->x, thing->y, tmthing->x, tmthing->y)+ANGLE_90, 16*FRACUNIT); } @@ -1174,7 +1292,7 @@ static boolean PIT_CheckThing(mobj_t *thing) S_StartSound(thing, thing->info->deathsound); P_KillMobj(thing, tmthing, tmthing); - P_SetObjectMomZ(thing, 8*FRACUNIT, false); + P_SetObjectMomZ(thing, 12*FRACUNIT, false); P_InstaThrust(thing, R_PointToAngle2(tmthing->x, tmthing->y, thing->x, thing->y)+ANGLE_90, 16*FRACUNIT); } @@ -1225,7 +1343,7 @@ static boolean PIT_CheckThing(mobj_t *thing) S_StartSound(thing, thing->info->deathsound); P_KillMobj(thing, tmthing, tmthing); - P_SetObjectMomZ(thing, 8*FRACUNIT, false); + P_SetObjectMomZ(thing, 12*FRACUNIT, false); P_InstaThrust(thing, R_PointToAngle2(tmthing->x, tmthing->y, thing->x, thing->y)+ANGLE_90, 16*FRACUNIT); } else if (thing->type == MT_BANANA_SHIELD || thing->type == MT_BANANA @@ -1253,7 +1371,7 @@ static boolean PIT_CheckThing(mobj_t *thing) S_StartSound(thing, thing->info->deathsound); P_KillMobj(thing, tmthing, tmthing); - P_SetObjectMomZ(thing, 8*FRACUNIT, false); + P_SetObjectMomZ(thing, 12*FRACUNIT, false); P_InstaThrust(thing, R_PointToAngle2(tmthing->x, tmthing->y, thing->x, thing->y)+ANGLE_90, 16*FRACUNIT); } else if (thing->type == MT_SSMINE_SHIELD || thing->type == MT_SSMINE) @@ -1283,15 +1401,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); } @@ -1431,7 +1557,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; @@ -1680,7 +1806,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 @@ -2098,7 +2224,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)); @@ -2165,6 +2291,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; } @@ -2439,6 +2571,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; @@ -2878,6 +3013,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 @@ -2885,8 +3022,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) { @@ -2910,6 +3052,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. @@ -3082,6 +3227,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; } @@ -3793,8 +3962,6 @@ void P_BouncePlayerMove(mobj_t *mo) mmomx = mo->player->rmomx; mmomy = mo->player->rmomy; - mo->player->kartstuff[k_drift] = 0; - mo->player->kartstuff[k_driftcharge] = 0; mo->player->kartstuff[k_pogospring] = 0; // trace along the three leading corners diff --git a/src/p_mobj.c b/src/p_mobj.c index d9df21eaf..1b957e2bf 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 @@ -6110,6 +6104,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 @@ -6543,16 +6602,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! @@ -8283,6 +8351,40 @@ void P_MobjThinker(mobj_t *mobj) S_StartSound(mobj, sfx_s3k4e); mobj->health--; break; + case MT_DRIFTEXPLODE: + if (!mobj->target || !mobj->target->health) + { + P_RemoveMobj(mobj); + return; + } + + mobj->angle = mobj->target->angle; + P_TeleportMove(mobj, mobj->target->x + P_ReturnThrustX(mobj, mobj->angle+ANGLE_180, mobj->target->radius), + mobj->target->y + P_ReturnThrustY(mobj, mobj->angle+ANGLE_180, mobj->target->radius), mobj->target->z); + P_SetScale(mobj, mobj->target->scale); + mobj->flags2 ^= MF2_DONTDRAW; +#ifdef HWRENDER + mobj->modeltilt = mobj->target->modeltilt; +#endif + + { + player_t *p = NULL; + if (mobj->target->target && mobj->target->target->player) + p = mobj->target->target->player; + else if (mobj->target->player) + p = mobj->target->player; + + if (p) + { + if (p->kartstuff[k_driftboost] > mobj->movecount) + { + ; // reset animation + } + + mobj->movecount = p->kartstuff[k_driftboost]; + } + } + break; case MT_BOOSTFLAME: if (!mobj->target || !mobj->target->health) { @@ -8357,6 +8459,7 @@ void P_MobjThinker(mobj_t *mobj) } else { + UINT8 driftcolor = K_DriftSparkColor(mobj->target->player, mobj->target->player->kartstuff[k_driftcharge]); fixed_t newx, newy; angle_t travelangle; @@ -8369,12 +8472,8 @@ void P_MobjThinker(mobj_t *mobj) mobj->angle = travelangle - ((ANGLE_90/5)*mobj->target->player->kartstuff[k_drift]); P_SetScale(mobj, (mobj->destscale = mobj->target->scale)); - if (mobj->target->player->kartstuff[k_driftcharge] >= K_GetKartDriftSparkValue(mobj->target->player)*4) - mobj->color = (UINT8)(1 + (leveltime % (MAXSKINCOLORS-1))); - else if (mobj->target->player->kartstuff[k_driftcharge] >= K_GetKartDriftSparkValue(mobj->target->player)*2) - mobj->color = SKINCOLOR_KETCHUP; - else if (mobj->target->player->kartstuff[k_driftcharge] >= K_GetKartDriftSparkValue(mobj->target->player)) - mobj->color = SKINCOLOR_SAPPHIRE; + if (driftcolor != SKINCOLOR_NONE) + mobj->color = driftcolor; else mobj->color = SKINCOLOR_SILVER; @@ -10034,6 +10133,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); @@ -10719,6 +10824,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; } @@ -10874,6 +10983,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 @@ -11459,6 +11571,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); @@ -11675,11 +11790,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) @@ -11696,8 +11810,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)) @@ -12006,6 +12118,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; @@ -12252,6 +12384,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))); @@ -12387,13 +12582,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 b51dabdd0..e0b3550dc 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 bfbdf6228..c4070ef5c 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 aac04fe82..1c87218b9 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 e42c440dc..cd5c5211e 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); @@ -3716,11 +3716,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 // @@ -3763,14 +3758,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; @@ -3994,7 +3981,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; @@ -4006,8 +3992,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? @@ -4020,19 +4004,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. @@ -6191,69 +6169,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 // @@ -7234,8 +7149,8 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall { static UINT8 lookbackdelay[4] = {0,0,0,0}; UINT8 num; - angle_t angle = 0, focusangle = 0, focusaiming = 0; - fixed_t x, y, z, dist, height, viewpointx, viewpointy, camspeed, camdist, camheight, pviewheight; + angle_t angle = 0, focusangle = 0, focusaiming = 0, pitch = 0; + fixed_t x, y, z, dist, distxy, distz, height, viewpointx, viewpointy, camspeed, camdist, camheight, pviewheight; fixed_t pan, xpan, ypan; INT32 camrotate; boolean camstill, lookback; @@ -7487,8 +7402,36 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall height -= FixedMul(height, player->karthud[khud_boostcam]); } - x = mo->x - FixedMul(FINECOSINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist); - y = mo->y - FixedMul(FINESINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist); + if (mo->standingslope) + { + pitch = (angle_t)FixedMul(P_ReturnThrustX(mo, player->frameangle - mo->standingslope->xydirection, FRACUNIT), (fixed_t)mo->standingslope->zangle); + if (mo->eflags & MFE_VERTICALFLIP) + { + if (pitch >= ANGLE_180) + pitch = 0; + } + else + { + if (pitch < ANGLE_180) + pitch = 0; + } + } + pitch = thiscam->pitch + (angle_t)FixedMul(pitch - thiscam->pitch, camspeed/4); + + if (rendermode == render_opengl +#ifdef GL_SHADERS/* just so we can't possibly forget about it */ + && !cv_grshearing.value +#endif + ) + distxy = FixedMul(dist, FINECOSINE((pitch>>ANGLETOFINESHIFT) & FINEMASK)); + else + distxy = dist; + distz = -FixedMul(dist, FINESINE((pitch>>ANGLETOFINESHIFT) & FINEMASK)); + if (splitscreen == 1) // 2 player is weird, this helps keep players on screen + distz = 3*distz/5; + + x = mo->x - FixedMul(FINECOSINE((angle>>ANGLETOFINESHIFT) & FINEMASK), distxy); + y = mo->y - FixedMul(FINESINE((angle>>ANGLETOFINESHIFT) & FINEMASK), distxy); // SRB2Kart: set camera panning if (camstill || resetcalled || player->playerstate == PST_DEAD) @@ -7498,7 +7441,14 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall if (player->kartstuff[k_drift] != 0) { fixed_t panmax = (dist/5); - pan = FixedDiv(FixedMul(min((fixed_t)player->kartstuff[k_driftcharge], K_GetKartDriftSparkValue(player)), panmax), K_GetKartDriftSparkValue(player)); + INT32 driftval = K_GetKartDriftSparkValue(player); + INT32 dc = player->kartstuff[k_driftcharge]; + + if (dc > driftval || dc < 0) + dc = driftval; + + pan = FixedDiv(FixedMul((fixed_t)dc, panmax), driftval); + if (pan > panmax) pan = panmax; if (player->kartstuff[k_drift] < 0) @@ -7519,9 +7469,9 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall pviewheight = FixedMul(32<scale); if (mo->eflags & MFE_VERTICALFLIP) - z = mo->z + mo->height - pviewheight - camheight; + z = mo->z + mo->height - pviewheight - camheight + distz; else - z = mo->z + pviewheight + camheight; + z = mo->z + pviewheight + camheight + distz; #ifndef NOCLIPCAM // Disable all z-clipping for noclip cam // move camera down to move under lower ceilings @@ -7756,6 +7706,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall } thiscam->pan = pan; + thiscam->pitch = pitch; // compute aming to look the viewed point f1 = viewpointx-thiscam->x; @@ -7763,9 +7714,17 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall dist = FixedHypot(f1, f2); if (mo->eflags & MFE_VERTICALFLIP) + { angle = R_PointToAngle2(0, thiscam->z + thiscam->height, dist, mo->z + mo->height - P_GetPlayerHeight(player)); + if (thiscam->pitch < ANGLE_180 && thiscam->pitch > angle) + angle = thiscam->pitch; + } else + { angle = R_PointToAngle2(0, thiscam->z, dist, mo->z + P_GetPlayerHeight(player)); + if (thiscam->pitch >= ANGLE_180 && thiscam->pitch < angle) + angle = thiscam->pitch; + } if (player->playerstate != PST_DEAD && !((player->pflags & PF_NIGHTSMODE) && player->exiting)) angle += (focusaiming < ANGLE_180 ? focusaiming/2 : InvAngle(InvAngle(focusaiming)/2)); // overcomplicated version of '((signed)focusaiming)/2;' @@ -8132,12 +8091,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 @@ -8370,26 +8323,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/r_main.c b/src/r_main.c index a4fa9d463..06d53a2f5 100644 --- a/src/r_main.c +++ b/src/r_main.c @@ -176,7 +176,7 @@ consvar_t cv_showhud = {"showhud", "Yes", CV_CALL, CV_YesNo, R_SetViewSize, 0, consvar_t cv_translucenthud = {"translucenthud", "10", CV_SAVE, translucenthud_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_translucency = {"translucency", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; -consvar_t cv_drawdist = {"drawdist", "Infinite", CV_SAVE, drawdist_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_drawdist = {"drawdist", "8192", CV_SAVE, drawdist_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; //consvar_t cv_drawdist_nights = {"drawdist_nights", "2048", CV_SAVE, drawdist_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_drawdist_precip = {"drawdist_precip", "1024", CV_SAVE, drawdist_precip_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; //consvar_t cv_precipdensity = {"precipdensity", "Moderate", CV_SAVE, precipdensity_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; @@ -833,7 +833,7 @@ static void R_SetupFreelook(void) // clip it in the case we are looking a hardware 90 degrees full aiming // (lmps, network and use F12...) G_SoftwareClipAimingPitch((INT32 *)&aimingangle); - dy = AIMINGTODY(aimingangle) * viewwidth/BASEVIDWIDTH; + dy = AIMINGTODY(aimingangle) * viewheight/BASEVIDHEIGHT; yslope = &yslopetab[viewheight*8 - (viewheight/2 + dy)]; } centery = (viewheight/2) + dy; diff --git a/src/r_things.c b/src/r_things.c index 9ea8bb77b..4b7339c4e 100644 --- a/src/r_things.c +++ b/src/r_things.c @@ -2102,7 +2102,7 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel) // Handle all things in sector. // If a limit exists, handle things a tiny bit different. - if ((limit_dist = (fixed_t)(/*(maptol & TOL_NIGHTS) ? cv_drawdist_nights.value : */cv_drawdist.value) << FRACBITS)) + if ((limit_dist = (fixed_t)(cv_drawdist.value) * mapobjectscale)) { for (thing = sec->thinglist; thing; thing = thing->snext) { @@ -2168,7 +2168,7 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel) } // no, no infinite draw distance for precipitation. this option at zero is supposed to turn it off - if ((limit_dist = (fixed_t)cv_drawdist_precip.value << FRACBITS)) + if ((limit_dist = (fixed_t)(cv_drawdist_precip.value) * mapobjectscale)) { for (precipthing = sec->preciplist; precipthing; precipthing = precipthing->snext) { diff --git a/src/sounds.c b/src/sounds.c index 97a7655f4..f5fd3cb6d 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -817,6 +817,7 @@ sfxinfo_t S_sfx[NUMSFX] = {"toada", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR}, // Arid Sands Toad scream {"bhurry", false, 255, 0, -1, NULL, 0, -1, -1, LUMPERROR}, // v1.0.2 Battle overtime {"bsnipe", false, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR}, // Banana sniping + {"sploss", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR}, // Down to yellow sparks {"itfree", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR}, // :shitsfree: {"dbgsal", false, 255, 8, -1, NULL, 0, -1, -1, LUMPERROR}, // Debug notification diff --git a/src/sounds.h b/src/sounds.h index 8b9ca609e..e3a8560ca 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -892,6 +892,7 @@ typedef enum sfx_toada, sfx_bhurry, sfx_bsnipe, + sfx_sploss, sfx_itfree, sfx_dbgsal, diff --git a/src/st_stuff.c b/src/st_stuff.c index caed81f3e..b15bb568a 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)); } } */ @@ -1894,38 +1894,40 @@ static void ST_overlayDrawer(void) V_DrawScaledPatch(hudinfo[HUD_GRAVBOOTSICO].x, STRINGY(hudinfo[HUD_GRAVBOOTSICO].y), V_SNAPTORIGHT, gravboots); */ - if (!(multiplayer && demo.playback)) + if (cv_showviewpointtext.value) { - if(!P_IsLocalPlayer(stplyr)) + if (!(multiplayer && demo.playback)) { - /*char name[MAXPLAYERNAME+1]; - // shorten the name if its more than twelve characters. - strlcpy(name, player_names[stplyr-players], 13);*/ + if(!P_IsLocalPlayer(stplyr)) + { + /*char name[MAXPLAYERNAME+1]; + // shorten the name if its more than twelve characters. + strlcpy(name, player_names[stplyr-players], 13);*/ - // Show name of player being displayed - V_DrawCenteredString((BASEVIDWIDTH/2), BASEVIDHEIGHT-40, 0, M_GetText("Viewpoint:")); - V_DrawCenteredString((BASEVIDWIDTH/2), BASEVIDHEIGHT-32, V_ALLOWLOWERCASE, player_names[stplyr-players]); + // Show name of player being displayed + V_DrawCenteredString((BASEVIDWIDTH/2), BASEVIDHEIGHT-40, 0, M_GetText("VIEWPOINT:")); + V_DrawCenteredString((BASEVIDWIDTH/2), BASEVIDHEIGHT-32, V_ALLOWLOWERCASE, player_names[stplyr-players]); + } } - } - else if (!demo.title) - { + else if (!demo.title) + { + if (!splitscreen) + { + V_DrawCenteredString((BASEVIDWIDTH/2), BASEVIDHEIGHT-40, V_HUDTRANSHALF, M_GetText("VIEWPOINT:")); + V_DrawCenteredString((BASEVIDWIDTH/2), BASEVIDHEIGHT-32, V_HUDTRANSHALF|V_ALLOWLOWERCASE, player_names[stplyr-players]); + } + else if (splitscreen == 1) + { + char name[MAXPLAYERNAME+12]; - if (!splitscreen) - { - V_DrawCenteredString((BASEVIDWIDTH/2), BASEVIDHEIGHT-40, V_HUDTRANSHALF, M_GetText("Viewpoint:")); - V_DrawCenteredString((BASEVIDWIDTH/2), BASEVIDHEIGHT-32, V_HUDTRANSHALF|V_ALLOWLOWERCASE, player_names[stplyr-players]); - } - else if (splitscreen == 1) - { - char name[MAXPLAYERNAME+12]; - - INT32 y = (stplyr == &players[displayplayers[0]]) ? 4 : BASEVIDHEIGHT/2-12; - sprintf(name, "VIEWPOINT: %s", player_names[stplyr-players]); - V_DrawRightAlignedThinString(BASEVIDWIDTH-40, y, V_HUDTRANSHALF|V_ALLOWLOWERCASE|K_calcSplitFlags(V_SNAPTOTOP|V_SNAPTOBOTTOM|V_SNAPTORIGHT), name); - } - else if (splitscreen) - { - V_DrawCenteredThinString((vid.width/vid.dupx)/4, BASEVIDHEIGHT/2 - 12, V_HUDTRANSHALF|V_ALLOWLOWERCASE|K_calcSplitFlags(V_SNAPTOBOTTOM|V_SNAPTOLEFT), player_names[stplyr-players]); + INT32 y = (stplyr == &players[displayplayers[0]]) ? 4 : BASEVIDHEIGHT/2-12; + sprintf(name, "VIEWPOINT: %s", player_names[stplyr-players]); + V_DrawRightAlignedThinString(BASEVIDWIDTH-40, y, V_HUDTRANSHALF|V_ALLOWLOWERCASE|K_calcSplitFlags(V_SNAPTOTOP|V_SNAPTOBOTTOM|V_SNAPTORIGHT), name); + } + else if (splitscreen) + { + V_DrawCenteredThinString((vid.width/vid.dupx)/4, BASEVIDHEIGHT/2 - 12, V_HUDTRANSHALF|V_ALLOWLOWERCASE|K_calcSplitFlags(V_SNAPTOBOTTOM|V_SNAPTOLEFT), player_names[stplyr-players]); + } } }