diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f71f96778..4bb0368a9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -18,6 +18,7 @@ set(SRB2_CORE_SOURCES g_game.c g_input.c g_splitscreen.c + font.c hu_stuff.c i_tcp.c info.c @@ -73,6 +74,7 @@ set(SRB2_CORE_HEADERS g_game.h g_input.h g_state.h + font.h hu_stuff.h i_joy.h i_net.h @@ -167,6 +169,7 @@ set(SRB2_CORE_GAME_SOURCES k_botitem.c k_botsearch.c k_respawn.c + k_grandprix.c p_local.h p_maputl.h @@ -188,6 +191,7 @@ set(SRB2_CORE_GAME_SOURCES k_color.h k_bot.h k_respawn.h + k_grandprix.h ) if(NOT (CMAKE_CXX_COMPILER_ID MATCHES "Clang")) diff --git a/src/Makefile b/src/Makefile index 59b8dd382..bdbe71f70 100644 --- a/src/Makefile +++ b/src/Makefile @@ -502,6 +502,7 @@ OBJS:=$(i_main_o) \ $(OBJDIR)/am_map.o \ $(OBJDIR)/command.o \ $(OBJDIR)/console.o \ + $(OBJDIR)/font.o \ $(OBJDIR)/hu_stuff.o \ $(OBJDIR)/y_inter.o \ $(OBJDIR)/st_stuff.o \ @@ -566,6 +567,7 @@ OBJS:=$(i_main_o) \ $(OBJDIR)/k_bot.o \ $(OBJDIR)/k_botitem.o \ $(OBJDIR)/k_botsearch.o \ + $(OBJDIR)/k_grandprix.o\ $(i_cdmus_o) \ $(i_net_o) \ $(i_system_o) \ @@ -813,7 +815,7 @@ $(OBJDIR)/v_video.o: v_video.c doomdef.h doomtype.h g_state.h m_swap.h r_local.h tables.h m_fixed.h screen.h command.h m_bbox.h r_main.h d_player.h \ p_pspr.h info.h d_think.h sounds.h p_mobj.h doomdata.h d_ticcmd.h \ r_data.h r_defs.h r_state.h r_bsp.h r_segs.h r_plane.h r_sky.h \ - r_things.h r_draw.h v_video.h hu_stuff.h d_event.h w_wad.h console.h \ + r_things.h r_draw.h v_video.h font.h hu_stuff.h d_event.h w_wad.h console.h \ i_video.h z_zone.h doomstat.h d_clisrv.h d_netcmd.h $(CC) $(CFLAGS) -fno-omit-frame-pointer $(WFLAGS) -c $< -o $@ endif diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 001a95ea8..4253532b4 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -49,6 +49,7 @@ #include "k_battle.h" #include "k_pwrlv.h" #include "k_bot.h" +#include "k_grandprix.h" #ifdef CLIENT_LOADINGSCREEN // cl loading screen @@ -575,6 +576,7 @@ static inline void resynch_write_player(resynch_pak *rsp, const size_t i) // Score is resynched in the rspfirm resync packet rsp->health = 0; // resynched with mo health rsp->lives = players[i].lives; + rsp->lostlife = players[i].lostlife; rsp->continues = players[i].continues; rsp->scoreadd = players[i].scoreadd; rsp->xtralife = players[i].xtralife; @@ -657,6 +659,8 @@ static inline void resynch_write_player(resynch_pak *rsp, const size_t i) // botvars_t rsp->bot = players[i].bot; rsp->bot_difficulty = players[i].botvars.difficulty; + rsp->bot_diffincrease = players[i].botvars.diffincrease; + rsp->bot_rival = players[i].botvars.rival; rsp->bot_itemdelay = players[i].botvars.itemdelay; rsp->bot_itemconfirm = players[i].botvars.itemconfirm; rsp->bot_turnconfirm = players[i].botvars.turnconfirm; @@ -714,6 +718,7 @@ static void resynch_read_player(resynch_pak *rsp) // Score is resynched in the rspfirm resync packet players[i].health = rsp->health; players[i].lives = rsp->lives; + players[i].lostlife = rsp->lostlife; players[i].continues = rsp->continues; players[i].scoreadd = rsp->scoreadd; players[i].xtralife = rsp->xtralife; @@ -795,6 +800,8 @@ static void resynch_read_player(resynch_pak *rsp) // botvars_t players[i].bot = rsp->bot; players[i].botvars.difficulty = rsp->bot_difficulty; + players[i].botvars.diffincrease = rsp->bot_diffincrease; + players[i].botvars.rival = rsp->bot_rival; players[i].botvars.itemdelay = rsp->bot_itemdelay; players[i].botvars.itemconfirm = rsp->bot_itemconfirm; players[i].botvars.turnconfirm = rsp->bot_turnconfirm; @@ -5267,20 +5274,29 @@ static void Local_Maketic(INT32 realtics) // game responder calls HU_Responder, AM_Responder, F_Responder, // and G_MapEventsToControls if (!dedicated) rendergametic = gametic; + // translate inputs (keyboard/mouse/joystick) into game controls + G_BuildTiccmd(&localcmds, realtics, 1); + localcmds.angleturn |= TICCMD_RECEIVED; + if (splitscreen) { G_BuildTiccmd(&localcmds2, realtics, 2); + localcmds2.angleturn |= TICCMD_RECEIVED; + if (splitscreen > 1) { G_BuildTiccmd(&localcmds3, realtics, 3); + localcmds3.angleturn |= TICCMD_RECEIVED; + if (splitscreen > 2) + { G_BuildTiccmd(&localcmds4, realtics, 4); + localcmds4.angleturn |= TICCMD_RECEIVED; + } } } - - localcmds.angleturn |= TICCMD_RECEIVED; } void SV_SpawnPlayer(INT32 playernum, INT32 x, INT32 y, angle_t angle) @@ -5317,11 +5333,30 @@ void SV_SpawnPlayer(INT32 playernum, INT32 x, INT32 y, angle_t angle) static void SV_Maketic(void) { INT32 j; + boolean b[MAXPLAYERS]; + + memset(b, false, sizeof (b)); + + for (j = 0; j < MAXPLAYERS; j++) + { + if (K_PlayerUsesBotMovement(&players[j])) + { + b[j] = true; + K_BuildBotTiccmd(&players[j], &netcmds[maketic%TICQUEUE][j]); + } + } for (j = 0; j < MAXNETNODES; j++) + { if (playerpernode[j]) { INT32 player = nodetoplayer[j]; + + if (b[player]) + { + continue; + } + if ((netcmds[maketic%TICQUEUE][player].angleturn & TICCMD_RECEIVED) == 0) { // we didn't receive this tic INT32 i; @@ -5342,6 +5377,7 @@ static void SV_Maketic(void) } } } + } // all tic are now proceed make the next maketic++; diff --git a/src/d_clisrv.h b/src/d_clisrv.h index b9bcd61e7..dd10c7bb6 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -216,6 +216,7 @@ typedef struct // Score is resynched in the confirm resync packet INT32 health; SINT8 lives; + boolean lostlife; SINT8 continues; UINT8 scoreadd; SINT8 xtralife; @@ -295,6 +296,8 @@ typedef struct // botvars_t boolean bot; UINT8 bot_difficulty; + UINT8 bot_diffincrease; + boolean bot_rival; tic_t bot_itemdelay; tic_t bot_itemconfirm; SINT8 bot_turnconfirm; diff --git a/src/d_main.c b/src/d_main.c index 536735c16..fe7e312ee 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -75,6 +75,7 @@ int snprintf(char *str, size_t n, const char *fmt, ...); #include "fastcmp.h" #include "keys.h" #include "filesrch.h" // refreshdirmenu +#include "k_grandprix.h" #ifdef CMAKECONFIG #include "config.h" @@ -753,6 +754,9 @@ void D_StartTitle(void) // reset modeattacking modeattacking = ATTACKING_NONE; + // Reset GP + memset(&grandprixinfo, 0, sizeof(struct grandprixinfo)); + // empty maptol so mario/etc sounds don't play in sound test when they shouldn't maptol = 0; @@ -1132,7 +1136,24 @@ void D_SRB2Main(void) else { if (!M_CheckParm("-server")) + { G_SetGameModified(true, true); + + // Start up a "minor" grand prix session + memset(&grandprixinfo, 0, sizeof(struct grandprixinfo)); + + grandprixinfo.gamespeed = KARTSPEED_NORMAL; + grandprixinfo.encore = false; + grandprixinfo.masterbots = false; + + grandprixinfo.gp = true; + grandprixinfo.roundnum = 0; + grandprixinfo.cup = NULL; + grandprixinfo.wonround = false; + + grandprixinfo.initalize = true; + } + autostart = true; } } @@ -1515,18 +1536,46 @@ void D_SRB2Main(void) INT16 newskill = -1; const char *sskill = M_GetNextParm(); - for (j = 0; kartspeed_cons_t[j].strvalue; j++) - if (!strcasecmp(kartspeed_cons_t[j].strvalue, sskill)) + const UINT8 master = KARTSPEED_HARD+1; + const char *masterstr = "Master"; + + if (!strcasecmp(masterstr, sskill)) + { + newskill = master; + } + else + { + for (j = 0; kartspeed_cons_t[j].strvalue; j++) { - newskill = (INT16)kartspeed_cons_t[j].value; - break; + if (!strcasecmp(kartspeed_cons_t[j].strvalue, sskill)) + { + newskill = (INT16)kartspeed_cons_t[j].value; + break; + } } - if (!kartspeed_cons_t[j].strvalue) // reached end of the list with no match + if (!kartspeed_cons_t[j].strvalue) // reached end of the list with no match + { + j = atoi(sskill); // assume they gave us a skill number, which is okay too + if (j >= KARTSPEED_EASY && j <= master) + newskill = (INT16)j; + } + } + + if (grandprixinfo.gp == true) { - j = atoi(sskill); // assume they gave us a skill number, which is okay too - if (j >= KARTSPEED_EASY && j <= KARTSPEED_HARD) - newskill = (INT16)j; + if (newskill == master) + { + grandprixinfo.masterbots = true; + newskill = KARTSPEED_HARD; + } + + grandprixinfo.gamespeed = newskill; + } + else if (newskill == master) + { + grandprixinfo.masterbots = true; + newskill = KARTSPEED_HARD; } if (newskill != -1) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index f1ffe77ae..2f8bb0c5c 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -52,6 +52,7 @@ #include "y_inter.h" #include "k_color.h" #include "k_respawn.h" +#include "k_grandprix.h" #ifdef NETGAME_DEVMODE #define CV_RESTRICT CV_NETVAR @@ -717,8 +718,6 @@ void D_RegisterServerCommands(void) AddMServCommands(); // p_mobj.c - CV_RegisterVar(&cv_itemrespawntime); - CV_RegisterVar(&cv_itemrespawn); CV_RegisterVar(&cv_flagtime); CV_RegisterVar(&cv_suddendeath); @@ -2873,6 +2872,12 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pencoremode, boolean r if (netgame || multiplayer) FLS = false; + if (grandprixinfo.gp == true) + { + // Too lazy to change the input value for every instance of this function....... + pencoremode = grandprixinfo.encore; + } + if (delay != 2) { UINT8 flags = 0; @@ -3021,6 +3026,7 @@ static void Command_Map_f(void) INT32 newmapnum; boolean newresetplayers, newencoremode; INT32 newgametype = gametype; + boolean startgp = false; // max length of command: map map03 -gametype race -noresetplayers -force -encore // 1 2 3 4 5 6 7 @@ -3051,6 +3057,7 @@ static void Command_Map_f(void) if (COM_CheckParm("-force")) { G_SetGameModified(false, true); + startgp = true; } else { @@ -3097,7 +3104,6 @@ static void Command_Map_f(void) // new encoremode value // use cvar by default - newencoremode = (cv_kartencore.value == 1); if (COM_CheckParm("-encore")) @@ -3144,6 +3150,69 @@ static void Command_Map_f(void) return; } + if (startgp) + { + i = COM_CheckParm("-skill"); + + grandprixinfo.gamespeed = (cv_kartspeed.value == KARTSPEED_AUTO ? KARTSPEED_NORMAL : cv_kartspeed.value); + grandprixinfo.masterbots = false; + + if (i) + { + const UINT8 master = KARTSPEED_HARD+1; + const char *masterstr = "Master"; + const char *skillname = COM_Argv(i+1); + INT32 newskill = -1; + INT32 j; + + if (!strcasecmp(masterstr, skillname)) + { + newskill = master; + } + else + { + for (j = 0; kartspeed_cons_t[j].strvalue; j++) + { + if (!strcasecmp(kartspeed_cons_t[j].strvalue, skillname)) + { + newskill = (INT16)kartspeed_cons_t[j].value; + break; + } + } + + if (!kartspeed_cons_t[j].strvalue) // reached end of the list with no match + { + j = atoi(COM_Argv(i+1)); // assume they gave us a skill number, which is okay too + if (j >= KARTSPEED_EASY && j <= master) + newskill = (INT16)j; + } + } + + if (newskill != -1) + { + if (newskill == master) + { + grandprixinfo.gamespeed = KARTSPEED_HARD; + grandprixinfo.masterbots = true; + } + else + { + grandprixinfo.gamespeed = newskill; + grandprixinfo.masterbots = false; + } + } + } + + grandprixinfo.encore = newencoremode; + + grandprixinfo.gp = true; + grandprixinfo.roundnum = 0; + grandprixinfo.cup = NULL; + grandprixinfo.wonround = false; + + grandprixinfo.initalize = true; + } + fromlevelselect = false; D_MapChange(newmapnum, newgametype, newencoremode, newresetplayers, 0, false, false); } @@ -5167,14 +5236,16 @@ static void PointLimit_OnChange(void) static void NumLaps_OnChange(void) { - if (!G_RaceGametype() || (modeattacking || demo.playback)) + if (K_CanChangeRules() == false) + { return; + } - if (server && Playing() - && (netgame || multiplayer) - && (mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE) - && (cv_numlaps.value > mapheaderinfo[gamemap - 1]->numlaps)) + if ((mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE) + && (cv_numlaps.value > mapheaderinfo[gamemap - 1]->numlaps)) + { CV_StealthSetValue(&cv_numlaps, mapheaderinfo[gamemap - 1]->numlaps); + } // Just don't be verbose CONS_Printf(M_GetText("Number of laps set to %d\n"), cv_numlaps.value); @@ -5259,11 +5330,6 @@ void D_GameTypeChanged(INT32 lastgametype) // There will always be a server, and this only needs to be done once. if (server && (multiplayer || netgame)) { - if (gametype == GT_COMPETITION || gametype == GT_COOP) - CV_SetValue(&cv_itemrespawn, 0); - else if (!cv_itemrespawn.changed) - CV_SetValue(&cv_itemrespawn, 1); - switch (gametype) { case GT_MATCH: @@ -5274,8 +5340,6 @@ void D_GameTypeChanged(INT32 lastgametype) CV_SetValue(&cv_pointlimit, 0); CV_SetValue(&cv_timelimit, 120); } - if (!cv_itemrespawntime.changed) - CV_Set(&cv_itemrespawntime, cv_itemrespawntime.defaultvalue); // respawn normally break; case GT_TAG: case GT_HIDEANDSEEK: @@ -5286,8 +5350,6 @@ void D_GameTypeChanged(INT32 lastgametype) CV_SetValue(&cv_timelimit, 5); CV_SetValue(&cv_pointlimit, 0); } - if (!cv_itemrespawntime.changed) - CV_Set(&cv_itemrespawntime, cv_itemrespawntime.defaultvalue); // respawn normally break; case GT_CTF: if (!cv_timelimit.changed && !cv_pointlimit.changed) // user hasn't changed limits @@ -5296,17 +5358,12 @@ void D_GameTypeChanged(INT32 lastgametype) CV_SetValue(&cv_timelimit, 0); CV_SetValue(&cv_pointlimit, 5); } - if (!cv_itemrespawntime.changed) - CV_Set(&cv_itemrespawntime, cv_itemrespawntime.defaultvalue); // respawn normally break; } } else if (!multiplayer && !netgame) { - gametype = GT_RACE; // SRB2kart - // These shouldn't matter anymore - //CV_Set(&cv_itemrespawntime, cv_itemrespawntime.defaultvalue); - //CV_SetValue(&cv_itemrespawn, 0); + gametype = GT_RACE; } // reset timelimit and pointlimit in race/coop, prevent stupid cheats @@ -5817,14 +5874,14 @@ void Command_ExitGame_f(void) void Command_Retry_f(void) { - if (!(gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_VOTING)) + if (!(gamestate == GS_LEVEL || gamestate == GS_INTERMISSION)) + { CONS_Printf(M_GetText("You must be in a level to use this.\n")); - else if (netgame || multiplayer) - CONS_Printf(M_GetText("This only works in single player.\n")); - /*else if (!&players[consoleplayer] || players[consoleplayer].lives <= 1) - CONS_Printf(M_GetText("You can't retry without any lives remaining!\n")); - else if (G_IsSpecialStage(gamemap)) - CONS_Printf(M_GetText("You can't retry special stages!\n"));*/ + } + else if (grandprixinfo.gp == false) + { + CONS_Printf(M_GetText("This only works in Grand Prix.\n")); + } else { M_ClearMenus(true); @@ -6501,24 +6558,35 @@ static void Command_ShowTime_f(void) // SRB2Kart: On change messages static void BaseNumLaps_OnChange(void) { - if (gamestate == GS_LEVEL) + if (K_CanChangeRules() == true) { - if (cv_basenumlaps.value) - CONS_Printf(M_GetText("Number of laps will be changed to %d next round.\n"), cv_basenumlaps.value); - else - CONS_Printf(M_GetText("Number of laps will be changed to map defaults next round.\n")); + const char *str = va("%d", cv_basenumlaps.value); + + if (cv_basenumlaps.value == 0) + { + str = "map defaults"; + } + + CONS_Printf(M_GetText("Number of laps will be changed to %s next round.\n"), str); } } static void KartFrantic_OnChange(void) { - if ((boolean)cv_kartfrantic.value != franticitems && gamestate == GS_LEVEL && leveltime > starttime) - CONS_Printf(M_GetText("Frantic items will be turned %s next round.\n"), cv_kartfrantic.value ? M_GetText("on") : M_GetText("off")); + if (K_CanChangeRules() == false) + { + return; + } + + if (leveltime < starttime) + { + CONS_Printf(M_GetText("Frantic items has been set to %s.\n"), cv_kartfrantic.value ? M_GetText("on") : M_GetText("off")); + franticitems = (boolean)cv_kartfrantic.value; + } else { - CONS_Printf(M_GetText("Frantic items has been turned %s.\n"), cv_kartfrantic.value ? M_GetText("on") : M_GetText("off")); - franticitems = (boolean)cv_kartfrantic.value; + CONS_Printf(M_GetText("Frantic items will be turned %s next round.\n"), cv_kartfrantic.value ? M_GetText("on") : M_GetText("off")); } } @@ -6531,47 +6599,59 @@ static void KartSpeed_OnChange(void) return; } - if (G_RaceGametype()) + if (K_CanChangeRules() == false) { - if ((gamestate == GS_LEVEL && leveltime < starttime) && (cv_kartspeed.value != KARTSPEED_AUTO)) - { - CONS_Printf(M_GetText("Game speed has been changed to \"%s\".\n"), cv_kartspeed.string); - gamespeed = (UINT8)cv_kartspeed.value; - } - else if (cv_kartspeed.value != (signed)gamespeed) - { - CONS_Printf(M_GetText("Game speed will be changed to \"%s\" next round.\n"), cv_kartspeed.string); - } + return; + } + + if (leveltime < starttime && cv_kartspeed.value != KARTSPEED_AUTO) + { + CONS_Printf(M_GetText("Game speed has been changed to \"%s\".\n"), cv_kartspeed.string); + gamespeed = (UINT8)cv_kartspeed.value; + } + else + { + CONS_Printf(M_GetText("Game speed will be changed to \"%s\" next round.\n"), cv_kartspeed.string); } } static void KartEncore_OnChange(void) { - if (G_RaceGametype()) + if (K_CanChangeRules() == false) { - if ((cv_kartencore.value == 1) != encoremode && gamestate == GS_LEVEL /*&& leveltime > starttime*/) - CONS_Printf(M_GetText("Encore Mode will be set to %s next round.\n"), cv_kartencore.string); - else - CONS_Printf(M_GetText("Encore Mode has been set to %s.\n"), cv_kartencore.string); + return; } + + CONS_Printf(M_GetText("Encore Mode will be set to %s next round.\n"), cv_kartencore.string); } static void KartComeback_OnChange(void) { - if (G_BattleGametype()) + if (K_CanChangeRules() == false) { - if ((boolean)cv_kartcomeback.value != comeback && gamestate == GS_LEVEL && leveltime > starttime) - CONS_Printf(M_GetText("Karma Comeback will be turned %s next round.\n"), cv_kartcomeback.value ? M_GetText("on") : M_GetText("off")); - else - { - CONS_Printf(M_GetText("Karma Comeback has been turned %s.\n"), cv_kartcomeback.value ? M_GetText("on") : M_GetText("off")); - comeback = (boolean)cv_kartcomeback.value; - } + return; + } + + if (leveltime < starttime) + { + CONS_Printf(M_GetText("Karma Comeback has been turned %s.\n"), cv_kartcomeback.value ? M_GetText("on") : M_GetText("off")); + comeback = (boolean)cv_kartcomeback.value; + } + else + { + CONS_Printf(M_GetText("Karma Comeback will be turned %s next round.\n"), cv_kartcomeback.value ? M_GetText("on") : M_GetText("off")); } } static void KartEliminateLast_OnChange(void) { - if (G_RaceGametype() && cv_karteliminatelast.value) + if (K_CanChangeRules() == false) + { + CV_StealthSet(&cv_karteliminatelast, cv_karteliminatelast.defaultvalue); + } + + if (G_RaceGametype()) + { P_CheckRacers(); + } } diff --git a/src/d_netcmd.h b/src/d_netcmd.h index e533555d6..56690c130 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -65,10 +65,6 @@ extern consvar_t cv_usemouse2; extern consvar_t cv_mouse2opt; #endif -// normally in p_mobj but the .h is not read -extern consvar_t cv_itemrespawntime; -extern consvar_t cv_itemrespawn; - extern consvar_t cv_flagtime; extern consvar_t cv_suddendeath; diff --git a/src/d_player.h b/src/d_player.h index 32a0cbc6c..4ae420052 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -316,8 +316,8 @@ typedef enum k_stealingtimer, // You are stealing an item, this is your timer k_stolentimer, // You are being stolen from, this is your timer k_superring, // Spawn rings on top of you every tic! - k_sneakertimer, // Duration of the Sneaker Boost itself - k_levelbooster, // Duration of a level booster's boost (same as sneaker, but separated for boost stacking) + k_sneakertimer, // Duration of a Sneaker Boost (from Sneakers or level boosters) + k_numsneakers, // Number of stacked sneaker effects k_growshrinktimer, // > 0 = Big, < 0 = small k_squishedtimer, // Squished frame timer k_rocketsneakertimer, // Rocket Sneaker duration timer @@ -391,9 +391,6 @@ typedef enum NUMKARTHUD } karthudtype_t; -// QUICKLY GET EITHER SNEAKER OR LEVEL BOOSTER SINCE THEY ARE FUNCTIONALLY IDENTICAL -#define EITHERSNEAKER(p) (p->kartstuff[k_sneakertimer] || p->kartstuff[k_levelbooster]) - // QUICKLY GET RING TOTAL, INCLUDING RINGS CURRENTLY IN THE PICKUP ANIMATION #define RINGTOTAL(p) (p->kartstuff[k_rings] + p->kartstuff[k_pickuprings]) @@ -434,12 +431,14 @@ typedef struct respawnvars_s // player_t struct for all bot variables typedef struct botvars_s { - UINT8 difficulty; + UINT8 difficulty; // Bot's difficulty setting + UINT8 diffincrease; // In GP: bot difficulty will increase this much next round + boolean rival; // If true, they're the GP rival - tic_t itemdelay; - tic_t itemconfirm; + tic_t itemdelay; // Delay before using item at all + tic_t itemconfirm; // When high enough, they will use their item - SINT8 turnconfirm; + SINT8 turnconfirm; // Confirm turn direction } botvars_t; // ======================================================================== @@ -525,6 +524,7 @@ typedef struct player_s UINT32 charflags; // Extra abilities/settings for skins (combinable stuff) // See SF_ flags SINT8 lives; + boolean lostlife; SINT8 continues; // continues that player has acquired SINT8 xtralife; // Ring Extra Life counter diff --git a/src/dehacked.c b/src/dehacked.c index 8a7c7cd9b..d8a3f6d01 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -1521,6 +1521,113 @@ static void readlevelheader(MYFILE *f, INT32 num) Z_Free(s); } +static void readcupheader(MYFILE *f, cupheader_t *cup) +{ + char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); + char *word; + char *word2; + char *tmp; + INT32 i; + + do + { + if (myfgets(s, MAXLINELEN, f)) + { + if (s[0] == '\n') + break; + + // First remove trailing newline, if there is one + tmp = strchr(s, '\n'); + if (tmp) + *tmp = '\0'; + + tmp = strchr(s, '#'); + if (tmp) + *tmp = '\0'; + if (s == tmp) + continue; // Skip comment lines, but don't break. + + // Set / reset word, because some things (Lua.) move it + word = s; + + // Get the part before the " = " + tmp = strchr(s, '='); + if (tmp) + *(tmp-1) = '\0'; + else + break; + strupr(word); + + // Now get the part after + word2 = tmp += 2; + i = atoi(word2); // used for numerical settings + strupr(word2); + + if (fastcmp(word, "ICON")) + { + deh_strlcpy(cup->icon, word2, + sizeof(cup->icon), va("%s Cup: icon", cup->name)); + } + else if (fastcmp(word, "LEVELLIST")) + { + cup->numlevels = 0; + + tmp = strtok(word2,","); + do { + INT32 map = atoi(tmp); + + if (tmp[0] >= 'A' && tmp[0] <= 'Z' && tmp[2] == '\0') + map = M_MapNumber(tmp[0], tmp[1]); + + if (!map) + break; + + if (cup->numlevels >= MAXLEVELLIST) + { + deh_warning("%s Cup: reached max levellist (%d)\n", cup->name, MAXLEVELLIST); + break; + } + + cup->levellist[cup->numlevels] = map - 1; + cup->numlevels++; + } while((tmp = strtok(NULL,",")) != NULL); + } + else if (fastcmp(word, "BONUSGAME")) + { + // Convert to map number + if (word2[0] >= 'A' && word2[0] <= 'Z' && word2[2] == '\0') + i = M_MapNumber(word2[0], word2[1]); + cup->bonusgame = (INT16)i - 1; + } + else if (fastcmp(word, "SPECIALSTAGE")) + { + // Convert to map number + if (word2[0] >= 'A' && word2[0] <= 'Z' && word2[2] == '\0') + i = M_MapNumber(word2[0], word2[1]); + cup->specialstage = (INT16)i - 1; + } + else if (fastcmp(word, "EMERALDNUM")) + { + if (i >= 0 && i <= 14) + cup->emeraldnum = (UINT8)i; + else + deh_warning("%s Cup: invalid emerald number %d", cup->name, i); + } + else if (fastcmp(word, "UNLOCKABLE")) + { + if (i >= 0 && i <= MAXUNLOCKABLES) // 0 for no unlock required, anything else requires something + cup->unlockrequired = (SINT8)i - 1; + else + deh_warning("%s Cup: invalid unlockable number %d", cup->name, i); + } + else + deh_warning("%s Cup: unknown word '%s'", cup->name, word); + } + } while (!myfeof(f)); // finish when the line is empty + + Z_Free(s); +} + static void readcutscenescene(MYFILE *f, INT32 num, INT32 scenenum) { char *s = Z_Calloc(MAXLINELEN, PU_STATIC, NULL); @@ -3669,6 +3776,42 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad) } DEH_WriteUndoline(word, word2, UNDO_HEADER); } + else if (fastcmp(word, "CUP")) + { + cupheader_t *cup = kartcupheaders; + cupheader_t *prev = NULL; + + while (cup) + { + if (fastcmp(cup->name, word2)) + { + // mark as a major mod if it replaces an already-existing cup + G_SetGameModified(multiplayer, true); + break; + } + + prev = cup; + cup = cup->next; + } + + // Nothing found, add to the end. + if (!cup) + { + cup = Z_Calloc(sizeof (cupheader_t), PU_STATIC, NULL); + cup->id = numkartcupheaders; + deh_strlcpy(cup->name, word2, + sizeof(cup->name), va("Cup header %s: name", word2)); + if (prev != NULL) + prev->next = cup; + if (kartcupheaders == NULL) + kartcupheaders = cup; + numkartcupheaders++; + CONS_Printf("Added cup %d ('%s')\n", cup->id, cup->name); + } + + readcupheader(f, cup); + DEH_WriteUndoline(word, word2, UNDO_HEADER); + } else if (fastcmp(word, "CUTSCENE")) { if (i > 0 && i < 129) @@ -8691,7 +8834,7 @@ static const char *const KARTSTUFF_LIST[] = { "STOLENTIMER", "SUPERRING", "SNEAKERTIMER", - "LEVELBOOSTER", + "NUMSNEAKERS", "GROWSHRINKTIMER", "SQUISHEDTIMER", "ROCKETSNEAKERTIMER", diff --git a/src/doomstat.h b/src/doomstat.h index e963001e7..ffac81726 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -325,6 +325,27 @@ typedef struct extern mapheader_t* mapheaderinfo[NUMMAPS]; +// This could support more, but is that a good idea? +// Keep in mind that it may encourage people making overly long cups just because they "can", and would be a waste of memory. +#define MAXLEVELLIST 5 + +typedef struct cupheader_s +{ + UINT16 id; ///< Cup ID + char name[15]; ///< Cup title (14 chars) + char icon[9]; ///< Name of the icon patch + INT16 levellist[MAXLEVELLIST]; ///< List of levels that belong to this cup + UINT8 numlevels; ///< Number of levels defined in levellist + INT16 bonusgame; ///< Map number to use for bonus game + INT16 specialstage; ///< Map number to use for special stage + UINT8 emeraldnum; ///< ID of Emerald to use for special stage (1-7 for Chaos Emeralds, 8-14 for Super Emeralds, 0 for no emerald) + SINT8 unlockrequired; ///< An unlockable is required to select this cup. -1 for no unlocking required. + struct cupheader_s *next; ///< Next cup in linked list +} cupheader_t; + +extern cupheader_t *kartcupheaders; // Start of cup linked list +extern UINT16 numkartcupheaders; + enum TypeOfLevel { TOL_SP = 0x01, ///< Single Player diff --git a/src/font.c b/src/font.c new file mode 100644 index 000000000..aeaabd018 --- /dev/null +++ b/src/font.c @@ -0,0 +1,77 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 1999-2018 by Sonic Team Junior. +// Copyright (C) 2019 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 font.c +/// \brief Font setup + +#include "doomdef.h" +#include "hu_stuff.h" +#include "font.h" +#include "z_zone.h" + +font_t fontv[MAX_FONTS]; +int fontc; + +static void +FontCache (font_t *fnt) +{ + int i; + int c; + + c = fnt->start; + for (i = 0; i < fnt->size; ++i, ++c) + { + fnt->font[i] = HU_CachePatch( + "%s%.*d", + fnt->prefix, + fnt->digits, + c); + } +} + +void +Font_Load (void) +{ + int i; + for (i = 0; i < fontc; ++i) + { + FontCache(&fontv[i]); + } +} + +int +Font_DumbRegister (const font_t *sfnt) +{ + font_t *fnt; + + if (fontc == MAX_FONTS) + return -1; + + fnt = &fontv[fontc]; + + memcpy(fnt, sfnt, sizeof (font_t)); + + if (!( fnt->font = ZZ_Alloc(sfnt->size * sizeof (patch_t *)) )) + return -1; + + return fontc++; +} + +int +Font_Register (const font_t *sfnt) +{ + int d; + + d = Font_DumbRegister(sfnt); + + if (d >= 0) + FontCache(&fontv[d]); + + return d; +} diff --git a/src/font.h b/src/font.h new file mode 100644 index 000000000..88bedb44f --- /dev/null +++ b/src/font.h @@ -0,0 +1,49 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 1999-2018 by Sonic Team Junior. +// Copyright (C) 2019 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 font.h +/// \brief Font setup + +#ifndef __FONT_H__ +#define __FONT_H__ + +#define MAX_FONTS 32 + +typedef struct font font_t; + +struct font +{ + patch_t **font; + + UINT8 start; + UINT8 size; + + char prefix[8];/* 7 used at most */ + unsigned digits : 2; +}; + +extern font_t fontv[MAX_FONTS]; +extern int fontc; + +/* +Reloads already registered fonts. +*/ +void Font_Load (void); + +/* +Registers and loads a new font. +*/ +int Font_Register (const font_t *); + +/* +Register a new font, but do not load it yet. +*/ +int Font_DumbRegister (const font_t *); + +#endif diff --git a/src/g_game.c b/src/g_game.c index 45008fd4e..712b8410d 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -54,6 +54,7 @@ #include "k_pwrlv.h" #include "k_color.h" #include "k_respawn.h" +#include "k_grandprix.h" gameaction_t gameaction; gamestate_t gamestate = GS_NULL; @@ -170,6 +171,10 @@ struct quake quake; // Map Header Information mapheader_t* mapheaderinfo[NUMMAPS] = {NULL}; +// Kart cup definitions +cupheader_t *kartcupheaders = NULL; +UINT16 numkartcupheaders = 0; + static boolean exitgame = false; static boolean retrying = false; @@ -1430,7 +1435,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) { // forward with key or button // SRB2kart - we use an accel/brake instead of forward/backward. axis = JoyAxis(AXISMOVE, ssplayer); - if (InputDown(gc_accelerate, ssplayer) || (gamepadjoystickmove && axis > 0) || EITHERSNEAKER(player)) + if (InputDown(gc_accelerate, ssplayer) || (gamepadjoystickmove && axis > 0) || player->kartstuff[k_sneakertimer]) { cmd->buttons |= BT_ACCELERATE; forward = forwardmove[1]; // 50 @@ -2352,17 +2357,19 @@ void G_Ticker(boolean run) if (gamestate == GS_LEVEL) { // Or, alternatively, retry. - if (!(netgame || multiplayer) && G_GetRetryFlag()) + if (G_GetRetryFlag()) { G_ClearRetryFlag(); - // Costs a life to retry ... unless the player in question is dead already. - /*if (G_GametypeUsesLives() && players[consoleplayer].playerstate == PST_LIVE) - players[consoleplayer].lives -= 1; + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i]) + { + K_PlayerLoseLife(&players[i]); + } + } - G_DoReborn(consoleplayer);*/ - - D_MapChange(gamemap, gametype, (cv_kartencore.value == 1), true, 1, false, false); + D_MapChange(gamemap, gametype, (cv_kartencore.value == 1), false, 1, false, false); } for (i = 0; i < MAXPLAYERS; i++) @@ -2394,16 +2401,18 @@ void G_Ticker(boolean run) if (playeringame[i]) { - if (K_PlayerUsesBotMovement(&players[i])) + G_CopyTiccmd(cmd, &netcmds[buf][i], 1); + + // Use the leveltime sent in the player's ticcmd to determine control lag + if (modeattacking || K_PlayerUsesBotMovement(&players[i])) { - K_BuildBotTiccmd(&players[i], cmd); + // Never has lag cmd->latency = 0; } else { - G_CopyTiccmd(cmd, &netcmds[buf][i], 1); - // Use the leveltime sent in the player's ticcmd to determine control lag - cmd->latency = modeattacking ? 0 : min(((leveltime & 0xFF) - cmd->latency) & 0xFF, MAXPREDICTTICS-1); //@TODO add a cvar to allow setting this max + //@TODO add a cvar to allow setting this max + cmd->latency = min(((leveltime & 0xFF) - cmd->latency) & 0xFF, MAXPREDICTTICS-1); } } } @@ -2564,6 +2573,7 @@ void G_PlayerReborn(INT32 player) player_t *p; INT32 score, marescore; INT32 lives; + boolean lostlife; INT32 continues; // SRB2kart UINT8 kartspeed; @@ -2589,7 +2599,10 @@ void G_PlayerReborn(INT32 player) boolean spectator; boolean bot; UINT8 botdifficulty; + UINT8 botdiffincrease; + boolean botrival; SINT8 pity; + SINT8 xtralife; // SRB2kart respawnvars_t respawn; @@ -2607,6 +2620,7 @@ void G_PlayerReborn(INT32 player) score = players[player].score; marescore = players[player].marescore; lives = players[player].lives; + lostlife = players[player].lostlife; continues = players[player].continues; ctfteam = players[player].ctfteam; exiting = players[player].exiting; @@ -2640,7 +2654,10 @@ void G_PlayerReborn(INT32 player) mare = players[player].mare; bot = players[player].bot; botdifficulty = players[player].botvars.difficulty; + botdiffincrease = players[player].botvars.diffincrease; + botrival = players[player].botvars.rival; pity = players[player].pity; + xtralife = players[player].xtralife; // SRB2kart if (leveltime <= starttime) @@ -2691,6 +2708,7 @@ void G_PlayerReborn(INT32 player) p->score = score; p->marescore = marescore; p->lives = lives; + p->lostlife = lostlife; p->continues = continues; p->pflags = pflags; p->ctfteam = ctfteam; @@ -2717,7 +2735,10 @@ void G_PlayerReborn(INT32 player) p->mare = mare; p->bot = bot; p->botvars.difficulty = botdifficulty; + p->botvars.diffincrease = botdiffincrease; + p->botvars.rival = botrival; p->pity = pity; + p->xtralife = xtralife; // SRB2kart p->kartstuff[k_itemroulette] = itemroulette; @@ -3218,6 +3239,47 @@ void G_ExitLevel(void) { if (gamestate == GS_LEVEL) { + if (grandprixinfo.gp == true && grandprixinfo.wonround != true) + { + UINT8 i; + + // You didn't win... + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].spectator && !players[i].bot) + { + if (players[i].lives > 0) + { + break; + } + } + } + + if (i == MAXPLAYERS) + { + // GAME OVER, try again from the start! + + if (netgame) + { + ; // restart cup here if we do online GP + } + else + { + D_QuitNetGame(); + CL_Reset(); + D_StartTitle(); + } + } + else + { + // Go redo this course. + G_SetRetryFlag(); + } + + return; + } + gameaction = ga_completed; lastdraw = true; @@ -3293,18 +3355,15 @@ boolean G_IsSpecialStage(INT32 mapnum) // boolean G_GametypeUsesLives(void) { - // SRB2kart NEEDS no lives -#if 0 - // Coop, Competitive - if ((gametype == GT_COOP || gametype == GT_COMPETITION) - && !modeattacking // No lives in Time Attack - //&& !G_IsSpecialStage(gamemap) - && !(maptol & TOL_NIGHTS)) // No lives in NiGHTS + if ((grandprixinfo.gp == true) // In Grand Prix + && (gametype == GT_RACE) // NOT in bonus round + && !(modeattacking) // NOT in Record Attack + && !G_IsSpecialStage(gamemap)) // NOT in special stage + { return true; + } + return false; -#else - return false; -#endif } // @@ -3640,8 +3699,7 @@ void G_AddMapToBuffer(INT16 map) static void G_DoCompleted(void) { INT32 i, j = 0; - boolean gottoken = false; - SINT8 powertype = PWRLV_DISABLED; + SINT8 powertype = K_UsingPowerLevels(); tokenlist = 0; // Reset the list @@ -3658,10 +3716,21 @@ static void G_DoCompleted(void) // SRB2Kart: exitlevel shouldn't get you the points if (!players[i].exiting && !(players[i].pflags & PF_TIMEOVER)) { - players[i].pflags |= PF_TIMEOVER; - if (P_IsLocalPlayer(&players[i])) - j++; + if (players[i].bot) + { + K_FakeBotResults(&players[i]); + } + else + { + players[i].pflags |= PF_TIMEOVER; + + if (P_IsLocalPlayer(&players[i])) + { + j++; + } + } } + G_PlayerFinishLevel(i); // take away cards and stuff } @@ -3681,11 +3750,33 @@ static void G_DoCompleted(void) // go to next level // nextmap is 0-based, unlike gamemap if (nextmapoverride != 0) + { nextmap = (INT16)(nextmapoverride-1); - else if (mapheaderinfo[gamemap-1]->nextlevel == 1101) // SRB2Kart: !!! WHENEVER WE GET GRAND PRIX, GO TO AWARDS MAP INSTEAD !!! - nextmap = (INT16)(mapheaderinfo[gamemap] ? gamemap : (spstage_start-1)); // (gamemap-1)+1 == gamemap :V + } + else if (grandprixinfo.gp == true) + { + if (grandprixinfo.roundnum == 0 || grandprixinfo.cup == NULL) // Single session + { + nextmap = prevmap; // Same map + } + else + { + if (grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) // On final map + { + nextmap = 1101; // ceremonymap + } + else + { + // Proceed to next map + nextmap = grandprixinfo.cup->levellist[grandprixinfo.roundnum]; + grandprixinfo.roundnum++; + } + } + } else + { nextmap = (INT16)(mapheaderinfo[gamemap-1]->nextlevel-1); + } // Remember last map for when you come out of the special stage. if (!G_IsSpecialStage(gamemap)) @@ -3695,8 +3786,7 @@ static void G_DoCompleted(void) // a map of the proper gametype -- skip levels that don't support // the current gametype. (Helps avoid playing boss levels in Race, // for instance). - if (!token && !G_IsSpecialStage(gamemap) && !modeattacking - && (nextmap >= 0 && nextmap < NUMMAPS)) + if (!modeattacking && grandprixinfo.gp == false && (nextmap >= 0 && nextmap < NUMMAPS)) { register INT16 cm = nextmap; INT16 tolflag = G_TOLFlag(gametype); @@ -3740,44 +3830,18 @@ static void G_DoCompleted(void) if (nextmap < 0 || (nextmap >= NUMMAPS && nextmap < 1100-1) || nextmap > 1102-1) I_Error("Followed map %d to invalid map %d\n", prevmap + 1, nextmap + 1); - // wrap around in race - if (nextmap >= 1100-1 && nextmap <= 1102-1 && G_RaceGametype()) - nextmap = (INT16)(spstage_start-1); - - if (gametype == GT_COOP && token) - { - token--; - gottoken = true; - - if (!(emeralds & EMERALD1)) - nextmap = (INT16)(sstage_start - 1); // Special Stage 1 - else if (!(emeralds & EMERALD2)) - nextmap = (INT16)(sstage_start); // Special Stage 2 - else if (!(emeralds & EMERALD3)) - nextmap = (INT16)(sstage_start + 1); // Special Stage 3 - else if (!(emeralds & EMERALD4)) - nextmap = (INT16)(sstage_start + 2); // Special Stage 4 - else if (!(emeralds & EMERALD5)) - nextmap = (INT16)(sstage_start + 3); // Special Stage 5 - else if (!(emeralds & EMERALD6)) - nextmap = (INT16)(sstage_start + 4); // Special Stage 6 - else if (!(emeralds & EMERALD7)) - nextmap = (INT16)(sstage_start + 5); // Special Stage 7 - else - gottoken = false; - } - - if (G_IsSpecialStage(gamemap) && !gottoken) - nextmap = lastmap; // Exiting from a special stage? Go back to the game. Tails 08-11-2001 - automapactive = false; if (gametype != GT_COOP) { if (cv_advancemap.value == 0) // Stay on same map. + { nextmap = prevmap; + } else if (cv_advancemap.value == 2) // Go to random map. + { nextmap = G_RandMap(G_TOLFlag(gametype), prevmap, false, 0, false, NULL); + } } // We are committed to this map now. @@ -3787,14 +3851,6 @@ static void G_DoCompleted(void) P_AllocMapHeader(nextmap); // Set up power level gametype scrambles - if (netgame && cv_kartusepwrlv.value) - { - if (G_RaceGametype()) - powertype = PWRLV_RACE; - else if (G_BattleGametype()) - powertype = PWRLV_BATTLE; - } - K_SetPowerLevelScrambles(powertype); demointermission: @@ -3854,7 +3910,7 @@ void G_NextLevel(void) { if (gamestate != GS_VOTING) { - if ((cv_advancemap.value == 3) && !modeattacking && !skipstats && (multiplayer || netgame)) + if ((cv_advancemap.value == 3) && grandprixinfo.gp == false && !modeattacking && !skipstats && (multiplayer || netgame)) { UINT8 i; for (i = 0; i < MAXPLAYERS; i++) @@ -3951,7 +4007,7 @@ static void G_DoContinued(void) token = 0; // Reset # of lives - pl->lives = (ultimatemode) ? 1 : 3; + pl->lives = 3; D_MapChange(gamemap, gametype, false, false, 0, false, false); @@ -4520,7 +4576,7 @@ void G_SaveGame(UINT32 savegameslot) void G_DeferedInitNew(boolean pencoremode, const char *mapname, INT32 pickedchar, UINT8 ssplayers, boolean FLS) { INT32 i; - UINT8 color = 0; + //UINT8 color = 0; paused = false; if (demo.playback) @@ -4540,23 +4596,20 @@ void G_DeferedInitNew(boolean pencoremode, const char *mapname, INT32 pickedchar // this leave the actual game if needed SV_StartSinglePlayerServer(); - if (savedata.lives > 0) - { - color = savedata.skincolor; - } - else if (splitscreen != ssplayers) + if (splitscreen != ssplayers) { splitscreen = ssplayers; SplitScreen_OnChange(); } - if (!color && !modeattacking) - color = skins[pickedchar].prefcolor; + //if (!color) + //color = skins[pickedchar].prefcolor; + SetPlayerSkinByNum(consoleplayer, pickedchar); CV_StealthSet(&cv_skin, skins[pickedchar].name); - if (color) - CV_StealthSetValue(&cv_playercolor, color); + //if (color) + //CV_StealthSetValue(&cv_playercolor, color); if (mapname) D_MapChange(M_MapNumber(mapname[3], mapname[4]), gametype, pencoremode, true, 1, false, FLS); @@ -4585,63 +4638,40 @@ void G_InitNew(UINT8 pencoremode, const char *mapname, boolean resetplayer, bool if (!demo.playback && !netgame) // Netgame sets random seed elsewhere, demo playback sets seed just before us! P_SetRandSeed(M_RandomizedSeed()); // Use a more "Random" random seed - //SRB2Kart - Score is literally the only thing you SHOULDN'T reset at all times - //if (resetplayer) + // Clear a bunch of variables + tokenlist = token = sstimer = redscore = bluescore = lastmap = 0; + racecountdown = exitcountdown = mapreset = 0; + + for (i = 0; i < MAXPLAYERS; i++) { - // Clear a bunch of variables - tokenlist = token = sstimer = redscore = bluescore = lastmap = 0; - racecountdown = exitcountdown = mapreset = 0; + players[i].playerstate = PST_REBORN; + players[i].starpostnum = 0; + memset(&players[i].respawn, 0, sizeof (players[i].respawn)); - for (i = 0; i < MAXPLAYERS; i++) + // The latter two should clear by themselves, but just in case + players[i].pflags &= ~(PF_TAGIT|PF_TAGGED|PF_FULLSTASIS); + + // Clear cheatcodes too, just in case. + players[i].pflags &= ~(PF_GODMODE|PF_NOCLIP|PF_INVIS); + + players[i].marescore = 0; + + if (resetplayer && !(multiplayer && demo.playback)) // SRB2Kart { - players[i].playerstate = PST_REBORN; - players[i].starpostnum = 0; - memset(&players[i].respawn, 0, sizeof (players[i].respawn)); - -#if 0 - if (netgame || multiplayer) - { - players[i].lives = cv_startinglives.value; - players[i].continues = 0; - } - else if (pultmode) - { - players[i].lives = 1; - players[i].continues = 0; - } - else - { - players[i].lives = 3; - players[i].continues = 1; - } - + players[i].lives = 3; players[i].xtralife = 0; -#else - players[i].lives = 1; // SRB2Kart -#endif - - // The latter two should clear by themselves, but just in case - players[i].pflags &= ~(PF_TAGIT|PF_TAGGED|PF_FULLSTASIS); - - // Clear cheatcodes too, just in case. - players[i].pflags &= ~(PF_GODMODE|PF_NOCLIP|PF_INVIS); - - players[i].marescore = 0; - - if (resetplayer && !(multiplayer && demo.playback)) // SRB2Kart - { - players[i].score = 0; - } + players[i].totalring = 0; + players[i].score = 0; } - - // Reset unlockable triggers - unlocktriggers = 0; - - // clear itemfinder, just in case - if (!dedicated) // except in dedicated servers, where it is not registered and can actually I_Error debug builds - CV_StealthSetValue(&cv_itemfinder, 0); } + // Reset unlockable triggers + unlocktriggers = 0; + + // clear itemfinder, just in case + if (!dedicated) // except in dedicated servers, where it is not registered and can actually I_Error debug builds + CV_StealthSetValue(&cv_itemfinder, 0); + // internal game map // well this check is useless because it is done before (d_netcmd.c::command_map_f) // but in case of for demos.... @@ -5156,7 +5186,7 @@ void G_ReadDemoTiccmd(ticcmd_t *cmd, INT32 playernum) || (players[displayplayers[0]].respawn.state != RESPAWNST_NONE) // Respawning || (players[displayplayers[0]].spectator || objectplacing)) // Not a physical player && !(players[displayplayers[0]].kartstuff[k_spinouttimer] - && (players[displayplayers[0]].kartstuff[k_sneakertimer] || players[displayplayers[0]].kartstuff[k_levelbooster]))) // Spinning and boosting cancels out spinout + && players[displayplayers[0]].kartstuff[k_sneakertimer])) // Spinning and boosting cancels out spinout localangle[0] += (cmd->angleturn<<16); if (!(demoflags & DF_GHOST) && *demo_p == DEMOMARKER) @@ -8152,7 +8182,7 @@ boolean G_DemoTitleResponder(event_t *ev) return true; } - if ((ch >= HU_FONTSTART && ch <= HU_FONTEND && hu_font[ch-HU_FONTSTART]) + if ((ch >= HU_FONTSTART && ch <= HU_FONTEND && fontv[HU_FONT].font[ch-HU_FONTSTART]) || ch == ' ') // Allow spaces, of course { len = strlen(demo.titlename); diff --git a/src/hu_stuff.c b/src/hu_stuff.c index 7338a232b..1044dee19 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -14,6 +14,7 @@ #include "doomdef.h" #include "byteptr.h" #include "hu_stuff.h" +#include "font.h" #include "m_menu.h" // gametype_cons_t #include "m_cond.h" // emblems @@ -53,6 +54,7 @@ #include "s_sound.h" // song credits #include "k_kart.h" +#include "k_color.h" // coords are scaled #define HU_INPUTX 0 @@ -64,19 +66,9 @@ //------------------------------------------- // heads up font //------------------------------------------- -patch_t *hu_font[HU_FONTSIZE]; -patch_t *kart_font[KART_FONTSIZE]; // SRB2kart -patch_t *tny_font[HU_FONTSIZE]; -patch_t *tallnum[10]; // 0-9 -patch_t *nightsnum[10]; // 0-9 - -// Level title and credits fonts -patch_t *lt_font[LT_FONTSIZE]; -patch_t *cred_font[CRED_FONTSIZE]; // ping font // Note: I'd like to adress that at this point we might *REALLY* want to work towards a common drawString function that can take any font we want because this is really turning into a MESS. :V -Lat' -patch_t *pingnum[10]; patch_t *pinggfx[5]; // small ping graphic patch_t *mping[5]; // smaller ping graphic @@ -188,137 +180,53 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum); void HU_LoadGraphics(void) { - char buffer[9]; - INT32 i, j; + INT32 i; if (dedicated) return; - j = HU_FONTSTART; - for (i = 0; i < HU_FONTSIZE; i++, j++) - { - // cache the heads-up font for entire game execution - sprintf(buffer, "STCFN%.3d", j); - if (W_CheckNumForName(buffer) == LUMPERROR) - hu_font[i] = NULL; - else - hu_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX); - - // tiny version of the heads-up font - sprintf(buffer, "TNYFN%.3d", j); - if (W_CheckNumForName(buffer) == LUMPERROR) - tny_font[i] = NULL; - else - tny_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX); - } - - // cache the level title font for entire game execution - lt_font[0] = (patch_t *)W_CachePatchName("LTFNT039", PU_HUDGFX); /// \note fake start hack - - // Number support - lt_font[9] = (patch_t *)W_CachePatchName("LTFNT048", PU_HUDGFX); - lt_font[10] = (patch_t *)W_CachePatchName("LTFNT049", PU_HUDGFX); - lt_font[11] = (patch_t *)W_CachePatchName("LTFNT050", PU_HUDGFX); - lt_font[12] = (patch_t *)W_CachePatchName("LTFNT051", PU_HUDGFX); - lt_font[13] = (patch_t *)W_CachePatchName("LTFNT052", PU_HUDGFX); - lt_font[14] = (patch_t *)W_CachePatchName("LTFNT053", PU_HUDGFX); - lt_font[15] = (patch_t *)W_CachePatchName("LTFNT054", PU_HUDGFX); - lt_font[16] = (patch_t *)W_CachePatchName("LTFNT055", PU_HUDGFX); - lt_font[17] = (patch_t *)W_CachePatchName("LTFNT056", PU_HUDGFX); - lt_font[18] = (patch_t *)W_CachePatchName("LTFNT057", PU_HUDGFX); - - // SRB2kart - j = KART_FONTSTART; - for (i = 0; i < KART_FONTSIZE; i++, j++) - { - // cache the heads-up font for entire game execution - sprintf(buffer, "MKFNT%.3d", j); - if (W_CheckNumForName(buffer) == LUMPERROR) - kart_font[i] = NULL; - else - kart_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX); - } - // - - j = LT_FONTSTART; - for (i = 0; i < LT_FONTSIZE; i++) - { - sprintf(buffer, "LTFNT%.3d", j); - j++; - - if (W_CheckNumForName(buffer) == LUMPERROR) - lt_font[i] = NULL; - else - lt_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX); - } - - // cache the credits font for entire game execution (why not?) - j = CRED_FONTSTART; - for (i = 0; i < CRED_FONTSIZE; i++) - { - sprintf(buffer, "CRFNT%.3d", j); - j++; - - if (W_CheckNumForName(buffer) == LUMPERROR) - cred_font[i] = NULL; - else - cred_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX); - } - - //cache numbers too! - for (i = 0; i < 10; i++) - { - sprintf(buffer, "STTNUM%d", i); - tallnum[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX); - sprintf(buffer, "NGTNUM%d", i); - nightsnum[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - sprintf(buffer, "PINGN%d", i); - pingnum[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - } + Font_Load(); // minus for negative tallnums - tallminus = (patch_t *)W_CachePatchName("STTMINUS", PU_HUDGFX); + tallminus = HU_CachePatch("STTMINUS"); // cache the crosshairs, don't bother to know which one is being used, // just cache all 3, they're so small anyway. for (i = 0; i < HU_CROSSHAIRS; i++) { - sprintf(buffer, "CROSHAI%c", '1'+i); - crosshair[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX); + crosshair[i] = HU_CachePatch("CROSHAI%c", '1'+i); } - emblemicon = W_CachePatchName("EMBLICON", PU_HUDGFX); - tokenicon = W_CachePatchName("TOKNICON", PU_HUDGFX); + emblemicon = HU_CachePatch("EMBLICON"); + tokenicon = HU_CachePatch("TOKNICON"); - emeraldpics[0] = W_CachePatchName("CHAOS1", PU_HUDGFX); - emeraldpics[1] = W_CachePatchName("CHAOS2", PU_HUDGFX); - emeraldpics[2] = W_CachePatchName("CHAOS3", PU_HUDGFX); - emeraldpics[3] = W_CachePatchName("CHAOS4", PU_HUDGFX); - emeraldpics[4] = W_CachePatchName("CHAOS5", PU_HUDGFX); - emeraldpics[5] = W_CachePatchName("CHAOS6", PU_HUDGFX); - emeraldpics[6] = W_CachePatchName("CHAOS7", PU_HUDGFX); - tinyemeraldpics[0] = W_CachePatchName("TEMER1", PU_HUDGFX); - tinyemeraldpics[1] = W_CachePatchName("TEMER2", PU_HUDGFX); - tinyemeraldpics[2] = W_CachePatchName("TEMER3", PU_HUDGFX); - tinyemeraldpics[3] = W_CachePatchName("TEMER4", PU_HUDGFX); - tinyemeraldpics[4] = W_CachePatchName("TEMER5", PU_HUDGFX); - tinyemeraldpics[5] = W_CachePatchName("TEMER6", PU_HUDGFX); - tinyemeraldpics[6] = W_CachePatchName("TEMER7", PU_HUDGFX); + emeraldpics[0] = HU_CachePatch("CHAOS1"); + emeraldpics[1] = HU_CachePatch("CHAOS2"); + emeraldpics[2] = HU_CachePatch("CHAOS3"); + emeraldpics[3] = HU_CachePatch("CHAOS4"); + emeraldpics[4] = HU_CachePatch("CHAOS5"); + emeraldpics[5] = HU_CachePatch("CHAOS6"); + emeraldpics[6] = HU_CachePatch("CHAOS7"); + tinyemeraldpics[0] = HU_CachePatch("TEMER1"); + tinyemeraldpics[1] = HU_CachePatch("TEMER2"); + tinyemeraldpics[2] = HU_CachePatch("TEMER3"); + tinyemeraldpics[3] = HU_CachePatch("TEMER4"); + tinyemeraldpics[4] = HU_CachePatch("TEMER5"); + tinyemeraldpics[5] = HU_CachePatch("TEMER6"); + tinyemeraldpics[6] = HU_CachePatch("TEMER7"); - songcreditbg = W_CachePatchName("K_SONGCR", PU_HUDGFX); + songcreditbg = HU_CachePatch("K_SONGCR"); // cache ping gfx: for (i = 0; i < 5; i++) { - sprintf(buffer, "PINGGFX%d", i+1); - pinggfx[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX); - sprintf(buffer, "MPING%d", i+1); - mping[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX); + pinggfx[i] = HU_CachePatch("PINGGFX%d", i+1); + mping[i] = HU_CachePatch("MPING%d", i+1); } // fps stuff - framecounter = W_CachePatchName("FRAMER", PU_HUDGFX); - frameslash = W_CachePatchName("FRAMESL", PU_HUDGFX);; + framecounter = HU_CachePatch("FRAMER"); + frameslash = HU_CachePatch("FRAMESL");; } // Initialise Heads up @@ -326,6 +234,8 @@ void HU_LoadGraphics(void) // void HU_Init(void) { + font_t font; + #ifndef NONET COM_AddCommand("say", Command_Say_f); COM_AddCommand("sayto", Command_Sayto_f); @@ -337,9 +247,78 @@ void HU_Init(void) // set shift translation table shiftxform = english_shiftxform; + /* + Setup fonts + */ + + if (!dedicated) + { +#define DIM( s, n ) ( font.start = s, font.size = n ) +#define ADIM( name ) DIM (name ## _FONTSTART, name ## _FONTSIZE) +#define PR( s ) strcpy(font.prefix, s) +#define DIG( n ) ( font.digits = n ) +#define REG Font_DumbRegister(&font) + + DIG (3); + + ADIM (HU); + + PR ("STCFN"); + REG; + + PR ("TNYFN"); + REG; + + ADIM (KART); + PR ("MKFNT"); + REG; + + ADIM (LT); + PR ("LTFNT"); + REG; + + ADIM (CRED); + PR ("CRFNT"); + REG; + + DIG (1); + + DIM (0, 10); + + PR ("STTNUM"); + REG; + + PR ("NGTNUM"); + REG; + + PR ("PINGN"); + REG; + +#undef REG +#undef DIG +#undef PR +#undef ADMIN +#undef DIM + } + HU_LoadGraphics(); } +patch_t *HU_CachePatch(const char *format, ...) +{ + va_list ap; + char buffer[9]; + + va_start (ap, format); + vsprintf(buffer, format, ap); + va_end (ap); + + if (W_CheckNumForName(buffer) == LUMPERROR) + return NULL; + else + return (patch_t *)W_CachePatchName(buffer, PU_HUDGFX); +} + static inline void HU_Stop(void) { headsupactive = false; @@ -761,168 +740,27 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum) const char *prefix = "", *cstart = "", *cend = "", *adminchar = "\x82~\x83", *remotechar = "\x82@\x83", *fmt2, *textcolor = "\x80"; char *tempchar = NULL; - // player is a spectator? - if (players[playernum].spectator) + if (players[playernum].spectator) { - cstart = "\x86"; // grey name - textcolor = "\x86"; + // grey text + cstart = textcolor = "\x86"; } else if (target == -1) // say team { - if (players[playernum].ctfteam == 1) // red + if (players[playernum].ctfteam == 1) { - cstart = "\x85"; - textcolor = "\x85"; + // red text + cstart = textcolor = "\x85"; } - else // blue + else { - cstart = "\x84"; - textcolor = "\x84"; + // blue text + cstart = textcolor = "\x84"; } } else { - const UINT8 color = players[playernum].skincolor; - - cstart = "\x83"; - - switch (color) - { - case SKINCOLOR_WHITE: - case SKINCOLOR_SILVER: - case SKINCOLOR_SLATE: - cstart = "\x80"; // White - break; - case SKINCOLOR_GREY: - case SKINCOLOR_NICKEL: - case SKINCOLOR_BLACK: - case SKINCOLOR_SKUNK: - case SKINCOLOR_PLATINUM: - case SKINCOLOR_JET: - cstart = "\x86"; // V_GRAYMAP - break; - case SKINCOLOR_SEPIA: - case SKINCOLOR_BEIGE: - case SKINCOLOR_CARAMEL: - case SKINCOLOR_PEACH: - case SKINCOLOR_BROWN: - case SKINCOLOR_LEATHER: - case SKINCOLOR_RUST: - case SKINCOLOR_WRISTWATCH: - cstart = "\x8e"; // V_BROWNMAP - break; - case SKINCOLOR_FAIRY: - case SKINCOLOR_PINK: - case SKINCOLOR_ROSE: - case SKINCOLOR_LEMONADE: - case SKINCOLOR_LILAC: - case SKINCOLOR_BLOSSOM: - case SKINCOLOR_TAFFY: - cstart = "\x8d"; // V_PINKMAP - break; - case SKINCOLOR_CINNAMON: - case SKINCOLOR_RUBY: - case SKINCOLOR_RASPBERRY: - case SKINCOLOR_RED: - case SKINCOLOR_CRIMSON: - case SKINCOLOR_MAROON: - case SKINCOLOR_SCARLET: - case SKINCOLOR_KETCHUP: - cstart = "\x85"; // V_REDMAP - break; - case SKINCOLOR_DAWN: - case SKINCOLOR_SUNSLAM: - case SKINCOLOR_CREAMSICLE: - case SKINCOLOR_ORANGE: - case SKINCOLOR_ROSEWOOD: - case SKINCOLOR_TANGERINE: - cstart = "\x87"; // V_ORANGEMAP - break; - case SKINCOLOR_TAN: - case SKINCOLOR_CREAM: - cstart = "\x8f"; // V_TANMAP - break; - case SKINCOLOR_GOLD: - case SKINCOLOR_ROYAL: - case SKINCOLOR_BRONZE: - case SKINCOLOR_COPPER: - case SKINCOLOR_THUNDER: - cstart = "\x8A"; // V_GOLDMAP - break; - case SKINCOLOR_POPCORN: - case SKINCOLOR_YELLOW: - case SKINCOLOR_MUSTARD: - case SKINCOLOR_BANANA: - case SKINCOLOR_OLIVE: - case SKINCOLOR_CROCODILE: - cstart = "\x82"; // V_YELLOWMAP - break; - case SKINCOLOR_ARTICHOKE: - case SKINCOLOR_PERIDOT: - case SKINCOLOR_VOMIT: - case SKINCOLOR_GARDEN: - case SKINCOLOR_LIME: - case SKINCOLOR_HANDHELD: - case SKINCOLOR_TEA: - case SKINCOLOR_PISTACHIO: - case SKINCOLOR_MOSS: - case SKINCOLOR_CAMOUFLAGE: - case SKINCOLOR_MINT: - case SKINCOLOR_GREEN: - case SKINCOLOR_PINETREE: - case SKINCOLOR_TURTLE: - case SKINCOLOR_SWAMP: - case SKINCOLOR_DREAM: - case SKINCOLOR_PLAGUE: - case SKINCOLOR_EMERALD: - case SKINCOLOR_ALGAE: - cstart = "\x83"; // V_GREENMAP - break; - case SKINCOLOR_AQUAMARINE: - case SKINCOLOR_TURQUOISE: - case SKINCOLOR_TEAL: - cstart = "\x8b"; // V_AQUAMAP - break; - case SKINCOLOR_PIGEON: - case SKINCOLOR_ROBIN: - case SKINCOLOR_CYAN: - case SKINCOLOR_JAWZ: - case SKINCOLOR_CERULEAN: - case SKINCOLOR_NAVY: - case SKINCOLOR_SAPPHIRE: - cstart = "\x88"; // V_SKYMAP - break; - case SKINCOLOR_STEEL: - case SKINCOLOR_ULTRAMARINE: - case SKINCOLOR_PERIWINKLE: - case SKINCOLOR_BLUE: - case SKINCOLOR_MIDNIGHT: - case SKINCOLOR_BLUEBERRY: - case SKINCOLOR_NOVA: - cstart = "\x84"; // V_BLUEMAP - break; - case SKINCOLOR_THISTLE: - case SKINCOLOR_PURPLE: - case SKINCOLOR_PASTEL: - cstart = "\x81"; // V_PURPLEMAP - break; - case SKINCOLOR_MAGENTA: - case SKINCOLOR_FUCHSIA: - case SKINCOLOR_MOONSET: - case SKINCOLOR_VIOLET: - cstart = "\x8c"; // V_MAGENTAMAP - break; - case SKINCOLOR_DUSK: - case SKINCOLOR_TOXIC: - case SKINCOLOR_MAUVE: - case SKINCOLOR_LAVENDER: - case SKINCOLOR_BYZANTIUM: - case SKINCOLOR_POMEGRANATE: - cstart = "\x89"; // V_LAVENDERMAP - break; - default: - break; - } + cstart = "\x80" + (K_SkincolorToTextColor(players[playernum].skincolor) >> V_CHARCOLORSHIFT); } prefix = cstart; @@ -932,6 +770,7 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum) tempchar = (char *)Z_Calloc(strlen(cstart) + strlen(adminchar) + 1, PU_STATIC, NULL); else if (IsPlayerAdmin(playernum)) tempchar = (char *)Z_Calloc(strlen(cstart) + strlen(remotechar) + 1, PU_STATIC, NULL); + if (tempchar) { if (playernum == serverplayer) @@ -999,7 +838,7 @@ static inline boolean HU_keyInChatString(char *s, char ch) { size_t l; - if ((ch >= HU_FONTSTART && ch <= HU_FONTEND && hu_font[ch-HU_FONTSTART]) + if ((ch >= HU_FONTSTART && ch <= HU_FONTEND && fontv[HU_FONT].font[ch-HU_FONTSTART]) || ch == ' ') // Allow spaces, of course { l = strlen(s); @@ -1433,7 +1272,7 @@ static char *CHAT_WordWrap(INT32 x, INT32 w, INT32 option, const char *string) c = toupper(c); c -= HU_FONTSTART; - if (c < 0 || c >= HU_FONTSIZE || !hu_font[c]) + if (c < 0 || c >= HU_FONTSIZE || !fontv[HU_FONT].font[c]) { chw = spacewidth; lastusablespace = i; diff --git a/src/hu_stuff.h b/src/hu_stuff.h index c3da1d2a2..4ad473357 100644 --- a/src/hu_stuff.h +++ b/src/hu_stuff.h @@ -17,6 +17,7 @@ #include "d_event.h" #include "w_wad.h" #include "r_defs.h" +#include "font.h" //------------------------------------ // heads up font @@ -42,6 +43,23 @@ #define CRED_FONTEND 'Z' // the last font character #define CRED_FONTSIZE (CRED_FONTEND - CRED_FONTSTART + 1) +#define X( name ) name ## _FONT +/* fonts */ +enum +{ + X (HU), + X (TINY), + X (KART), + + X (LT), + X (CRED), + + X (TALLNUM), + X (NIGHTSNUM), + X (PINGNUM), +}; +#undef X + #define HU_CROSSHAIRS 3 // maximum of 9 - see HU_Init(); extern char *shiftxform; // english translation shift table @@ -78,15 +96,9 @@ void HU_AddChatText(const char *text, boolean playsound); // set true when entering a chat message extern boolean chat_on; -extern patch_t *hu_font[HU_FONTSIZE], *kart_font[KART_FONTSIZE], *tny_font[HU_FONTSIZE]; // SRB2kart -extern patch_t *tallnum[10]; -extern patch_t *pingnum[10]; extern patch_t *pinggfx[5]; -extern patch_t *nightsnum[10]; extern patch_t *framecounter; extern patch_t *frameslash; -extern patch_t *lt_font[LT_FONTSIZE]; -extern patch_t *cred_font[CRED_FONTSIZE]; extern patch_t *emeraldpics[7]; extern patch_t *tinyemeraldpics[7]; extern patch_t *rflagico; @@ -104,6 +116,9 @@ void HU_Init(void); void HU_LoadGraphics(void); +// Load a HUDGFX patch or NULL. +patch_t *HU_CachePatch(const char *format, ...); + // reset heads up when consoleplayer respawns. void HU_Start(void); diff --git a/src/k_battle.c b/src/k_battle.c index fe727cee3..fc1141581 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -254,7 +254,6 @@ void K_CheckBumpers(void) for (i = 0; i < MAXPLAYERS; i++) { players[i].pflags |= PF_TIMEOVER; - //players[i].lives = 0; P_DoPlayerExit(&players[i]); } } diff --git a/src/k_bot.c b/src/k_bot.c index 6beb6fe9e..933f6d37e 100644 --- a/src/k_bot.c +++ b/src/k_bot.c @@ -1,5 +1,6 @@ // SONIC ROBO BLAST 2 KART //----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 by Sally "TehRealSalt" Cochenour // Copyright (C) 2018-2020 by Kart Krew // // This program is free software distributed under the @@ -295,11 +296,17 @@ boolean K_BotCanTakeCut(player_t *player) --------------------------------------------------*/ static UINT32 K_BotRubberbandDistance(player_t *player) { - const UINT32 spacing = 2048; + const UINT32 spacing = FixedDiv(512 * FRACUNIT, K_GetKartGameSpeedScalar(gamespeed)) / FRACUNIT; const UINT8 portpriority = player - players; UINT8 pos = 0; UINT8 i; + if (player->botvars.rival) + { + // The rival should always try to be the front runner for the race. + return 0; + } + for (i = 0; i < MAXPLAYERS; i++) { if (i == portpriority) @@ -312,15 +319,15 @@ static UINT32 K_BotRubberbandDistance(player_t *player) // First check difficulty levels, then score, then settle it with port priority! if (player->botvars.difficulty < players[i].botvars.difficulty) { - pos++; + pos += 3; } else if (player->score < players[i].score) { - pos++; + pos += 2; } else if (i < portpriority) { - pos++; + pos += 1; } } } @@ -336,11 +343,13 @@ static UINT32 K_BotRubberbandDistance(player_t *player) fixed_t K_BotRubberband(player_t *player) { fixed_t rubberband = FRACUNIT; + fixed_t max, min; player_t *firstplace = NULL; UINT8 i; if (player->exiting) { + // You're done, we don't need to rubberband anymore. return FRACUNIT; } @@ -372,8 +381,8 @@ fixed_t K_BotRubberband(player_t *player) if (wanteddist > player->distancetofinish) { - // Whoa, you're too far ahead! - rubberband += (MAXBOTDIFFICULTY - player->botvars.difficulty) * distdiff; + // Whoa, you're too far ahead! Slow back down a little. + rubberband += (MAXBOTDIFFICULTY - player->botvars.difficulty) * (distdiff / 3); } else { @@ -382,13 +391,23 @@ fixed_t K_BotRubberband(player_t *player) } } - if (rubberband > 2*FRACUNIT) + // Lv. 1: x1.0 max + // Lv. 5: x1.5 max + // Lv. 9: x2.0 max + max = FRACUNIT + ((FRACUNIT * (player->botvars.difficulty - 1)) / (MAXBOTDIFFICULTY - 1)); + + // Lv. 1: x0.75 min + // Lv. 5: x0.875 min + // Lv. 9: x1.0 min + min = FRACUNIT - (((FRACUNIT/4) * (MAXBOTDIFFICULTY - player->botvars.difficulty)) / (MAXBOTDIFFICULTY - 1)); + + if (rubberband > max) { - rubberband = 2*FRACUNIT; + rubberband = max; } - else if (rubberband < 7*FRACUNIT/8) + else if (rubberband < min) { - rubberband = 7*FRACUNIT/8; + rubberband = min; } return rubberband; @@ -447,6 +466,25 @@ fixed_t K_BotTopSpeedRubberband(player_t *player) return rubberband; } +/*-------------------------------------------------- + fixed_t K_BotFrictionRubberband(player_t *player, fixed_t frict) + + See header file for description. +--------------------------------------------------*/ +fixed_t K_BotFrictionRubberband(player_t *player, fixed_t frict) +{ + fixed_t rubberband = K_BotRubberband(player) - FRACUNIT; + + if (rubberband <= 0) + { + // Never get stronger than normal friction + return frict; + } + + // 128 is a magic number that felt good in-game + return FixedDiv(frict, FRACUNIT + (rubberband / 2)); +} + /*-------------------------------------------------- fixed_t K_DistanceOfLineFromPoint(fixed_t v1x, fixed_t v1y, fixed_t v2x, fixed_t v2y, fixed_t cx, fixed_t cy) @@ -637,7 +675,7 @@ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) // Remove any existing controls memset(cmd, 0, sizeof(ticcmd_t)); - cmd->angleturn = (player->mo->angle >> 16) | TICCMD_RECEIVED; + cmd->angleturn = (player->mo->angle >> 16); if (gamestate != GS_LEVEL) { diff --git a/src/k_bot.h b/src/k_bot.h index 48472b5fd..2cb1ae460 100644 --- a/src/k_bot.h +++ b/src/k_bot.h @@ -1,5 +1,6 @@ // SONIC ROBO BLAST 2 KART //----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 by Sally "TehRealSalt" Cochenour // Copyright (C) 2018-2020 by Kart Krew // // This program is free software distributed under the @@ -95,6 +96,23 @@ fixed_t K_BotRubberband(player_t *player); fixed_t K_BotTopSpeedRubberband(player_t *player); +/*-------------------------------------------------- + fixed_t K_BotFrictionRubberband(player_t *player, fixed_t frict); + + Gives a multiplier for a bot's rubberbanding. + Adjusted from K_BotRubberband to be used for friction. + + Input Arguments:- + player - Player to check. + frict - Friction value to adjust. + + Return:- + The new friction value. +--------------------------------------------------*/ + +fixed_t K_BotFrictionRubberband(player_t *player, fixed_t frict); + + /*-------------------------------------------------- fixed_t K_DistanceOfLineFromPoint(fixed_t v1x, fixed_t v1y, fixed_t v2x, fixed_t v2y, fixed_t cx, fixed_t cy); @@ -228,5 +246,4 @@ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd); void K_BotItemUsage(player_t *player, ticcmd_t *cmd, INT16 turnamt); - #endif diff --git a/src/k_botitem.c b/src/k_botitem.c index 55f8e15e4..94f0b8e10 100644 --- a/src/k_botitem.c +++ b/src/k_botitem.c @@ -1,5 +1,6 @@ // SONIC ROBO BLAST 2 KART //----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 by Sally "TehRealSalt" Cochenour // Copyright (C) 2018-2020 by Kart Krew // // This program is free software distributed under the diff --git a/src/k_botsearch.c b/src/k_botsearch.c index 28e08f3cf..4dd55cc42 100644 --- a/src/k_botsearch.c +++ b/src/k_botsearch.c @@ -1,5 +1,6 @@ // SONIC ROBO BLAST 2 KART //----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 by Sally "TehRealSalt" Cochenour // Copyright (C) 2018-2020 by Kart Krew // // This program is free software distributed under the diff --git a/src/k_color.c b/src/k_color.c index 89112db29..ee3fbc626 100644 --- a/src/k_color.c +++ b/src/k_color.c @@ -380,12 +380,160 @@ UINT8 colortranslations[MAXTRANSLATIONS][16] = { { 0, 0, 96, 96, 97, 98, 98, 99, 81, 81, 69, 71, 73, 75, 77, 79}, // SKINCOLOR_CSUPER5 }; +/*-------------------------------------------------- + INT32 K_SkincolorToTextColor(UINT8 skincolor) + + See header file for description. +--------------------------------------------------*/ +INT32 K_SkincolorToTextColor(UINT8 skincolor) +{ + INT32 textcolor = 0; + + switch (skincolor) + { + default: // default to white? + case SKINCOLOR_WHITE: + case SKINCOLOR_SILVER: + case SKINCOLOR_SLATE: + textcolor = 0; // No color is white + break; + case SKINCOLOR_GREY: + case SKINCOLOR_NICKEL: + case SKINCOLOR_BLACK: + case SKINCOLOR_SKUNK: + case SKINCOLOR_PLATINUM: + case SKINCOLOR_JET: + textcolor = V_GRAYMAP; + break; + case SKINCOLOR_SEPIA: + case SKINCOLOR_BEIGE: + case SKINCOLOR_CARAMEL: + case SKINCOLOR_PEACH: + case SKINCOLOR_BROWN: + case SKINCOLOR_LEATHER: + case SKINCOLOR_RUST: + case SKINCOLOR_WRISTWATCH: + textcolor = V_BROWNMAP; + break; + case SKINCOLOR_FAIRY: + case SKINCOLOR_PINK: + case SKINCOLOR_ROSE: + case SKINCOLOR_LEMONADE: + case SKINCOLOR_LILAC: + case SKINCOLOR_BLOSSOM: + case SKINCOLOR_TAFFY: + textcolor = V_PINKMAP; + break; + case SKINCOLOR_CINNAMON: + case SKINCOLOR_RUBY: + case SKINCOLOR_RASPBERRY: + case SKINCOLOR_RED: + case SKINCOLOR_CRIMSON: + case SKINCOLOR_MAROON: + case SKINCOLOR_SCARLET: + case SKINCOLOR_KETCHUP: + textcolor = V_REDMAP; + break; + case SKINCOLOR_DAWN: + case SKINCOLOR_SUNSLAM: + case SKINCOLOR_CREAMSICLE: + case SKINCOLOR_ORANGE: + case SKINCOLOR_ROSEWOOD: + case SKINCOLOR_TANGERINE: + textcolor = V_ORANGEMAP; + break; + case SKINCOLOR_TAN: + case SKINCOLOR_CREAM: + textcolor = V_TANMAP; + break; + case SKINCOLOR_GOLD: + case SKINCOLOR_ROYAL: + case SKINCOLOR_BRONZE: + case SKINCOLOR_COPPER: + case SKINCOLOR_THUNDER: + textcolor = V_GOLDMAP; + break; + case SKINCOLOR_POPCORN: + case SKINCOLOR_YELLOW: + case SKINCOLOR_MUSTARD: + case SKINCOLOR_BANANA: + case SKINCOLOR_OLIVE: + case SKINCOLOR_CROCODILE: + textcolor = V_YELLOWMAP; + break; + case SKINCOLOR_ARTICHOKE: + case SKINCOLOR_PERIDOT: + case SKINCOLOR_VOMIT: + case SKINCOLOR_GARDEN: + case SKINCOLOR_LIME: + case SKINCOLOR_HANDHELD: + case SKINCOLOR_TEA: + case SKINCOLOR_PISTACHIO: + case SKINCOLOR_MOSS: + case SKINCOLOR_CAMOUFLAGE: + case SKINCOLOR_MINT: + case SKINCOLOR_GREEN: + case SKINCOLOR_PINETREE: + case SKINCOLOR_TURTLE: + case SKINCOLOR_SWAMP: + case SKINCOLOR_DREAM: + case SKINCOLOR_PLAGUE: + case SKINCOLOR_EMERALD: + case SKINCOLOR_ALGAE: + textcolor = V_GREENMAP; + break; + case SKINCOLOR_AQUAMARINE: + case SKINCOLOR_TURQUOISE: + case SKINCOLOR_TEAL: + textcolor = V_AQUAMAP; + break; + case SKINCOLOR_PIGEON: + case SKINCOLOR_ROBIN: + case SKINCOLOR_CYAN: + case SKINCOLOR_JAWZ: + case SKINCOLOR_CERULEAN: + case SKINCOLOR_NAVY: + case SKINCOLOR_SAPPHIRE: + textcolor = V_SKYMAP; + break; + case SKINCOLOR_STEEL: + case SKINCOLOR_ULTRAMARINE: + case SKINCOLOR_PERIWINKLE: + case SKINCOLOR_BLUE: + case SKINCOLOR_MIDNIGHT: + case SKINCOLOR_BLUEBERRY: + case SKINCOLOR_NOVA: + textcolor = V_BLUEMAP; + break; + case SKINCOLOR_THISTLE: + case SKINCOLOR_PURPLE: + case SKINCOLOR_PASTEL: + textcolor = V_PURPLEMAP; + break; + case SKINCOLOR_MAGENTA: + case SKINCOLOR_FUCHSIA: + case SKINCOLOR_MOONSET: + case SKINCOLOR_VIOLET: + textcolor = V_MAGENTAMAP; + break; + case SKINCOLOR_DUSK: + case SKINCOLOR_TOXIC: + case SKINCOLOR_MAUVE: + case SKINCOLOR_LAVENDER: + case SKINCOLOR_BYZANTIUM: + case SKINCOLOR_POMEGRANATE: + textcolor = V_LAVENDERMAP; + break; + } + + return textcolor; +} + /*-------------------------------------------------- UINT8 K_ColorRelativeLuminance(UINT8 r, UINT8 g, UINT8 b) See header file for description. --------------------------------------------------*/ - UINT8 K_ColorRelativeLuminance(UINT8 r, UINT8 g, UINT8 b) { UINT32 redweight = 1063 * r; @@ -400,7 +548,6 @@ UINT8 K_ColorRelativeLuminance(UINT8 r, UINT8 g, UINT8 b) See header file for description. --------------------------------------------------*/ - void K_RainbowColormap(UINT8 *dest_colormap, UINT8 skincolor) { INT32 i; @@ -444,14 +591,11 @@ void K_RainbowColormap(UINT8 *dest_colormap, UINT8 skincolor) } } -/** \brief Generates a translation colormap for Kart, to replace R_GenerateTranslationColormap in r_draw.c +/*-------------------------------------------------- + void K_GenerateKartColormap(UINT8 *dest_colormap, INT32 skinnum, UINT8 color) - \param dest_colormap colormap to populate - \param skinnum number of skin, TC_DEFAULT or TC_BOSS - \param color translation color - - \return void -*/ + See header file for description. +--------------------------------------------------*/ void K_GenerateKartColormap(UINT8 *dest_colormap, INT32 skinnum, UINT8 color) { INT32 i; @@ -505,12 +649,11 @@ void K_GenerateKartColormap(UINT8 *dest_colormap, INT32 skinnum, UINT8 color) } } -/** \brief Pulls kart color by name, to replace R_GetColorByName in r_draw.c +/*-------------------------------------------------- + UINT8 K_GetKartColorByName(const char *name) - \param name color name - - \return 0 -*/ + See header file for description. +--------------------------------------------------*/ UINT8 K_GetKartColorByName(const char *name) { UINT8 color = (UINT8)atoi(name); diff --git a/src/k_color.h b/src/k_color.h index 2a21473e2..85164623d 100644 --- a/src/k_color.h +++ b/src/k_color.h @@ -23,6 +23,21 @@ extern UINT8 colortranslations[MAXTRANSLATIONS][16]; extern const char *KartColor_Names[MAXSKINCOLORS]; extern const UINT8 KartColor_Opposite[MAXSKINCOLORS*2]; +/*-------------------------------------------------- + INT32 K_SkincolorToTextColor(UINT8 skincolor); + + Gives you the "text" color (V_ constants) from a skincolor (SKINCOLOR_ constants). + Used primarily by the chat system. + + Input Arguments:- + skincolor - SKINCOLOR_ constant + + Return:- + V_ constant for font coloring +--------------------------------------------------*/ +INT32 K_SkincolorToTextColor(UINT8 skincolor); + + /*-------------------------------------------------- UINT8 K_ColorRelativeLuminance(UINT8 r, UINT8 g, UINT8 b); diff --git a/src/k_grandprix.c b/src/k_grandprix.c new file mode 100644 index 000000000..a4882147a --- /dev/null +++ b/src/k_grandprix.c @@ -0,0 +1,605 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 by Sally "TehRealSalt" Cochenour +// 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_grandprix.c +/// \brief Grand Prix mode game logic & bot behaviors + +#include "k_grandprix.h" +#include "doomdef.h" +#include "d_player.h" +#include "g_game.h" +#include "k_bot.h" +#include "k_kart.h" +#include "m_random.h" +#include "r_things.h" + +struct grandprixinfo grandprixinfo; + +/*-------------------------------------------------- + UINT8 K_BotStartingDifficulty(SINT8 value) + + See header file for description. +--------------------------------------------------*/ +UINT8 K_BotStartingDifficulty(SINT8 value) +{ + // startingdifficulty: Easy = 3, Normal = 6, Hard = 9 + SINT8 difficulty = (value + 1) * 3; + + if (difficulty > MAXBOTDIFFICULTY) + { + difficulty = MAXBOTDIFFICULTY; + } + else if (difficulty < 1) + { + difficulty = 1; + } + + return difficulty; +} + +/*-------------------------------------------------- + INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers) + + See header file for description. +--------------------------------------------------*/ +INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers) +{ + INT16 points; + + if (position >= numplayers || position == 0) + { + // Invalid position, no points + return 0; + } + + points = numplayers - position; + + // Give bonus to high-ranking players, depending on player count + // This rounds out the point gain when you get 1st every race, + // and gives bots able to catch up in points if a player gets an early lead. + // The maximum points you can get in a cup is: ((number of players - 1) + (max extra points)) * (number of races) + // 8P: (7 + 5) * 5 = 60 maximum points + // 12P: (11 + 5) * 5 = 80 maximum points + // 16P: (15 + 5) * 5 = 100 maximum points + switch (numplayers) + { + case 0: case 1: case 2: // 1v1 + break; // No bonus needed. + case 3: case 4: // 3-4P + if (position == 1) { points += 1; } // 1st gets +1 extra point + break; + case 5: case 6: // 5-6P + if (position == 1) { points += 3; } // 1st gets +3 extra points + else if (position == 2) { points += 1; } // 2nd gets +1 extra point + break; + default: // Normal matches + if (position == 1) { points += 5; } // 1st gets +5 extra points + else if (position == 2) { points += 3; } // 2nd gets +3 extra points + else if (position == 3) { points += 1; } // 3rd gets +1 extra point + break; + } + + // somehow underflowed? + if (points < 0) + { + points = 0; + } + + return points; +} + +/*-------------------------------------------------- + void K_InitGrandPrixBots(void) + + See header file for description. +--------------------------------------------------*/ +void K_InitGrandPrixBots(void) +{ + const char *defaultbotskinname = "eggrobo"; + SINT8 defaultbotskin = R_SkinAvailable(defaultbotskinname); + + const UINT8 startingdifficulty = K_BotStartingDifficulty(grandprixinfo.gamespeed); + UINT8 difficultylevels[MAXPLAYERS]; + + UINT8 playercount = 8; + UINT8 wantedbots = 0; + + UINT8 numplayers = 0; + UINT8 competitors[MAXSPLITSCREENPLAYERS]; + + boolean skinusable[MAXSKINS]; + UINT8 botskinlist[MAXPLAYERS]; + UINT8 botskinlistpos = 0; + + UINT8 newplayernum = 0; + UINT8 i, j; + + if (defaultbotskin == -1) + { + // This shouldn't happen, but just in case + defaultbotskin = 0; + } + + memset(competitors, MAXPLAYERS, sizeof (competitors)); + memset(botskinlist, defaultbotskin, sizeof (botskinlist)); + + // init usable bot skins list + for (i = 0; i < MAXSKINS; i++) + { + if (i < numskins) + { + skinusable[i] = true; + } + else + { + skinusable[i] = false; + } + } + +#if MAXPLAYERS != 16 + I_Error("GP bot difficulty levels need rebalacned for the new player count!\n"); +#endif + + if (grandprixinfo.masterbots) + { + // Everyone is max difficulty!! + memset(difficultylevels, MAXBOTDIFFICULTY, sizeof (difficultylevels)); + } + else + { + // init difficulty levels list + difficultylevels[0] = max(1, startingdifficulty); + difficultylevels[1] = max(1, startingdifficulty-1); + difficultylevels[2] = max(1, startingdifficulty-2); + difficultylevels[3] = max(1, startingdifficulty-3); + difficultylevels[4] = max(1, startingdifficulty-3); + difficultylevels[5] = max(1, startingdifficulty-4); + difficultylevels[6] = max(1, startingdifficulty-4); + difficultylevels[7] = max(1, startingdifficulty-4); + difficultylevels[8] = max(1, startingdifficulty-5); + difficultylevels[9] = max(1, startingdifficulty-5); + difficultylevels[10] = max(1, startingdifficulty-5); + difficultylevels[11] = max(1, startingdifficulty-6); + difficultylevels[12] = max(1, startingdifficulty-6); + difficultylevels[13] = max(1, startingdifficulty-6); + difficultylevels[14] = max(1, startingdifficulty-7); + difficultylevels[15] = max(1, startingdifficulty-7); + } + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i]) + { + if (numplayers < MAXSPLITSCREENPLAYERS && !players[i].spectator) + { + competitors[numplayers] = i; + skinusable[players[i].skin] = false; + numplayers++; + } + else + { + players[i].spectator = true; // force spectate for all other players, if they happen to exist? + } + } + } + + if (numplayers > 2) + { + // Add 3 bots per player beyond 2P + playercount += (numplayers-2) * 3; + } + + wantedbots = playercount - numplayers; + + // Create rival list + if (numplayers > 0) + { + for (i = 0; i < SKINRIVALS; i++) + { + for (j = 0; j < numplayers; j++) + { + player_t *p = &players[competitors[j]]; + char *rivalname = skins[p->skin].rivals[i]; + SINT8 rivalnum = R_SkinAvailable(rivalname); + + if (rivalnum != -1 && skinusable[rivalnum]) + { + botskinlist[botskinlistpos] = rivalnum; + skinusable[rivalnum] = false; + botskinlistpos++; + } + } + } + } + + // Pad the remaining list with random skins if we need to + if (botskinlistpos < wantedbots) + { + for (i = botskinlistpos; i < wantedbots; i++) + { + UINT8 val = M_RandomKey(numskins); + UINT8 loops = 0; + + while (!skinusable[val]) + { + if (loops >= numskins) + { + // no more skins + break; + } + + val++; + + if (val >= numskins) + { + val = 0; + } + + loops++; + } + + if (loops >= numskins) + { + // leave the rest of the table as the default skin + break; + } + + botskinlist[i] = val; + skinusable[val] = false; + } + } + + for (i = 0; i < wantedbots; i++) + { + if (!K_AddBot(botskinlist[i], difficultylevels[i], &newplayernum)) + { + break; + } + } +} + +/*-------------------------------------------------- + static INT16 K_RivalScore(player_t *bot) + + Creates a "rival score" for a bot, used to determine which bot is the + most deserving of the rival status. + + Input Arguments:- + bot - Player to check. + + Return:- + "Rival score" value. +--------------------------------------------------*/ +static INT16 K_RivalScore(player_t *bot) +{ + const UINT16 difficulty = bot->botvars.difficulty; + const UINT16 score = bot->score; + SINT8 roundnum = 1, roundsleft = 1; + UINT16 lowestscore = UINT16_MAX; + UINT8 lowestdifficulty = MAXBOTDIFFICULTY; + UINT8 i; + + if (grandprixinfo.cup != NULL) + { + roundnum = grandprixinfo.roundnum; + roundsleft = grandprixinfo.cup->numlevels - grandprixinfo.roundnum; + } + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + { + continue; + } + + if (players[i].score < lowestscore) + { + lowestscore = players[i].score; + } + + if (players[i].bot == true && players[i].botvars.difficulty < lowestdifficulty) + { + lowestdifficulty = players[i].botvars.difficulty; + } + } + + // In the early game, difficulty is more important. + // This will try to influence the higher difficulty bots to get rival more often & get even more points. + // However, when we're running low on matches left, we need to focus more on raw score! + + return ((difficulty - lowestdifficulty) * roundsleft) + ((score - lowestscore) * roundnum); +} + +/*-------------------------------------------------- + void K_UpdateGrandPrixBots(void) + + See header file for description. +--------------------------------------------------*/ +void K_UpdateGrandPrixBots(void) +{ + player_t *oldrival = NULL; + player_t *newrival = NULL; + UINT16 newrivalscore = 0; + UINT8 i; + + // Find the rival. + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator || !players[i].bot) + { + continue; + } + + if (players[i].botvars.diffincrease) + { + players[i].botvars.difficulty += players[i].botvars.diffincrease; + + if (players[i].botvars.difficulty > MAXBOTDIFFICULTY) + { + players[i].botvars.difficulty = MAXBOTDIFFICULTY; + } + + players[i].botvars.diffincrease = 0; + } + + if (players[i].botvars.rival) + { + if (oldrival == NULL) + { + // Record the old rival to compare with our calculated new rival + oldrival = &players[i]; + } + else + { + // Somehow 2 rivals were set?! + // Let's quietly fix our mess... + players[i].botvars.rival = false; + } + } + } + + // Find the bot with the best average of score & difficulty. + for (i = 0; i < MAXPLAYERS; i++) + { + UINT16 ns = 0; + + if (!playeringame[i] || players[i].spectator || !players[i].bot) + { + continue; + } + + ns = K_RivalScore(&players[i]); + + if (ns > newrivalscore) + { + newrival = &players[i]; + newrivalscore = ns; + } + } + + // Even if there's a new rival, we want to make sure that they're a better fit than the current one. + if (oldrival != newrival) + { + if (oldrival != NULL) + { + UINT16 os = K_RivalScore(oldrival); + + if (newrivalscore < os + 5) + { + // This rival's only *slightly* better, no need to fire the old one. + // Our current rival's just fine, thank you very much. + return; + } + + // Hand over your badge. + oldrival->botvars.rival = false; + } + + // Set our new rival! + newrival->botvars.rival = true; + } +} + +/*-------------------------------------------------- + static UINT8 K_BotExpectedStanding(player_t *bot) + + Predicts what placement a bot was expected to be in. + Used for determining if a bot's difficulty should raise. + + Input Arguments:- + bot - Player to check. + + Return:- + Position number the bot was expected to be in. +--------------------------------------------------*/ +static UINT8 K_BotExpectedStanding(player_t *bot) +{ + UINT8 pos = 1; + UINT8 i; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (i == (bot - players)) + { + continue; + } + + if (playeringame[i] && !players[i].spectator) + { + if (players[i].bot) + { + if (players[i].botvars.difficulty > bot->botvars.difficulty) + { + pos++; + } + else if (players[i].score > bot->score) + { + pos++; + } + } + else + { + // Human player, always increment + pos++; + } + } + } + + return pos; +} + +/*-------------------------------------------------- + void K_IncreaseBotDifficulty(player_t *bot) + + See header file for description. +--------------------------------------------------*/ +void K_IncreaseBotDifficulty(player_t *bot) +{ + UINT8 expectedstanding; + INT16 standingdiff; + + if (bot->botvars.difficulty >= MAXBOTDIFFICULTY) + { + // Already at max difficulty, don't need to increase + return; + } + + // Increment bot difficulty based on what position you were meant to come in! + expectedstanding = K_BotExpectedStanding(bot); + standingdiff = expectedstanding - bot->kartstuff[k_position]; + + if (standingdiff >= -2) + { + UINT8 increase; + + if (standingdiff > 5) + { + increase = 3; + } + else if (standingdiff > 2) + { + increase = 2; + } + else + { + increase = 1; + } + + bot->botvars.diffincrease = increase; + } + else + { + bot->botvars.diffincrease = 0; + } +} + +/*-------------------------------------------------- + void K_FakeBotResults(player_t *bot) + + See header file for description. +--------------------------------------------------*/ +void K_FakeBotResults(player_t *bot) +{ + const UINT32 distfactor = FixedMul(32 * bot->mo->scale, K_GetKartGameSpeedScalar(gamespeed)) / FRACUNIT; + UINT32 worstdist = 0; + tic_t besttime = UINT32_MAX; + UINT8 numplayers = 0; + UINT8 i; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].spectator) + { + numplayers++; + + if (players[i].exiting && players[i].realtime < besttime) + { + besttime = players[i].realtime; + } + + if (players[i].distancetofinish > worstdist) + { + worstdist = players[i].distancetofinish; + } + } + } + + if (besttime == UINT32_MAX // No one finished, so you don't finish either. + || bot->distancetofinish >= worstdist) // Last place, you aren't going to finish. + { + bot->pflags |= PF_TIMEOVER; + return; + } + + // hey, you "won" + bot->exiting = 2; + bot->realtime += (bot->distancetofinish / distfactor); + bot->distancetofinish = 0; + K_IncreaseBotDifficulty(bot); +} + +/*-------------------------------------------------- + void K_PlayerLoseLife(player_t *player) + + See header file for description. +--------------------------------------------------*/ +void K_PlayerLoseLife(player_t *player) +{ + if (!G_GametypeUsesLives()) + { + return; + } + + if (player->spectator || player->exiting || player->bot || player->lostlife) + { + return; + } + + player->lives--; + player->lostlife = true; + +#if 0 + if (player->lives <= 0) + { + if (P_IsLocalPlayer(player)) + { + S_StopMusic(); + S_ChangeMusicInternal("gmover", false); + } + } +#endif +} + +/*-------------------------------------------------- + boolean K_CanChangeRules(void) + + See header file for description. +--------------------------------------------------*/ +boolean K_CanChangeRules(void) +{ + if (demo.playback) + { + // We've already got our important settings! + return false; + } + + if (grandprixinfo.gp == true && grandprixinfo.roundnum > 0) + { + // Don't cheat the rules of the GP! + return false; + } + + if (modeattacking == true) + { + // Don't cheat the rules of Time Trials! + return false; + } + + return true; +} diff --git a/src/k_grandprix.h b/src/k_grandprix.h new file mode 100644 index 000000000..9f27b485b --- /dev/null +++ b/src/k_grandprix.h @@ -0,0 +1,144 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 by Sally "TehRealSalt" Cochenour +// 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_grandprix.h +/// \brief Grand Prix mode game logic & bot behaviors + +#ifndef __K_GRANDPRIX__ +#define __K_GRANDPRIX__ + +#include "doomdef.h" +#include "doomstat.h" + +extern struct grandprixinfo +{ + boolean gp; ///< If true, then we are in a Grand Prix. + UINT8 roundnum; ///< Round number. If 0, this is a single session from the warp command. + cupheader_t *cup; ///< Which cup are we playing? + UINT8 gamespeed; ///< Copy of gamespeed, just to make sure you can't cheat it with cvars + boolean encore; ///< Ditto, but for encore mode + boolean masterbots; ///< If true, all bots should be max difficulty (Master Mode) + boolean initalize; ///< If true, we need to initialize a new session. + boolean wonround; ///< If false, then we retry the map instead of going to the next. +} grandprixinfo; + + +/*-------------------------------------------------- + UINT8 K_BotStartingDifficulty(SINT8 value); + + Determines the starting difficulty of the bots + for a specific game speed. + + Input Arguments:- + value - Game speed value to use. + + Return:- + Bot difficulty level. +--------------------------------------------------*/ + +UINT8 K_BotStartingDifficulty(SINT8 value); + + +/*-------------------------------------------------- + INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers); + + Calculates the number of points that a player would + recieve if they won the round. + + Input Arguments:- + position - Finishing position. + numplayers - Number of players in the game. + + Return:- + Number of points to give. +--------------------------------------------------*/ + +INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers); + + +/*-------------------------------------------------- + void K_InitGrandPrixBots(void); + + Spawns bots specifically tailored for Grand Prix mode. +--------------------------------------------------*/ + +void K_InitGrandPrixBots(void); + + +/*-------------------------------------------------- + void K_UpdateGrandPrixBots(void); + + Updates bot settings based on the the results of the race. +--------------------------------------------------*/ + +void K_UpdateGrandPrixBots(void); + + +/*-------------------------------------------------- + void K_IncreaseBotDifficulty(player_t *bot); + + Increases the difficulty of this bot when they finish the race. + + Input Arguments:- + bot - Player to do this for. + + Return:- + None +--------------------------------------------------*/ + +void K_IncreaseBotDifficulty(player_t *bot); + + +/*-------------------------------------------------- + void K_FakeBotResults(player_t *bot); + + Fakes the results of the race, when all human players have + already finished and only bots were remaining. + + Input Arguments:- + bot - Player to do this for. + + Return:- + None +--------------------------------------------------*/ + +void K_FakeBotResults(player_t *bot); + + +/*-------------------------------------------------- + void K_PlayerLoseLife(player_t *player); + + Removes a life from a human player. + + Input Arguments:- + player - Player to do this for. + + Return:- + None +--------------------------------------------------*/ + +void K_PlayerLoseLife(player_t *player); + + +/*-------------------------------------------------- + boolean K_CanChangeRules(void); + + Returns whenver or not the server is allowed + to change the game rules. + + Input Arguments:- + None + + Return:- + None +--------------------------------------------------*/ + +boolean K_CanChangeRules(void); + +#endif diff --git a/src/k_kart.c b/src/k_kart.c index 01a92179f..58e8aa646 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -78,6 +78,48 @@ player_t *K_GetItemBoxPlayer(mobj_t *mobj) return player; } +// Angle reflection used by springs & speed pads +angle_t K_ReflectAngle(angle_t yourangle, angle_t theirangle, fixed_t yourspeed, fixed_t theirspeed) +{ + INT32 angoffset; + boolean subtract = false; + + angoffset = yourangle - theirangle; + + if ((angle_t)angoffset > ANGLE_180) + { + // Flip on wrong side + angoffset = InvAngle((angle_t)angoffset); + subtract = !subtract; + } + + // Fix going directly against the spring's angle sending you the wrong way + if ((angle_t)angoffset > ANGLE_90) + { + angoffset = ANGLE_180 - angoffset; + } + + // Offset is reduced to cap it (90 / 2 = max of 45 degrees) + angoffset /= 2; + + // Reduce further based on how slow your speed is compared to the spring's speed + // (set both to 0 to ignore this) + if (theirspeed != 0 && yourspeed != 0) + { + if (theirspeed > yourspeed) + { + angoffset = FixedDiv(angoffset, FixedDiv(theirspeed, yourspeed)); + } + } + + if (subtract) + angoffset = (signed)(theirangle) - angoffset; + else + angoffset = (signed)(theirangle) + angoffset; + + return (angle_t)angoffset; +} + //{ SRB2kart Net Variables void K_RegisterKartStuff(void) @@ -351,7 +393,7 @@ static void K_KartGetItemResult(player_t *player, SINT8 getitem) \return void */ -static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean spbrush, boolean bot) +static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean spbrush, boolean bot, boolean rival) { INT32 newodds; INT32 i; @@ -429,10 +471,12 @@ static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean sp #define POWERITEMODDS(odds) {\ if (franticitems) \ - odds <<= 1; \ - odds = FixedMul(odds<> FRACBITS; \ + odds *= 2; \ + if (rival) \ + odds *= 2; \ + odds = FixedMul(odds * FRACUNIT, FRACUNIT + ((PLAYERSCALING * FRACUNIT) / 25)) / FRACUNIT; \ if (mashed > 0) \ - odds = FixedDiv(odds<> FRACBITS; \ + odds = FixedDiv(odds * FRACUNIT, FRACUNIT + mashed) / FRACUNIT; \ } #define COOLDOWNONSTART (leveltime < (30*TICRATE)+starttime) @@ -568,7 +612,7 @@ static UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 for (j = 1; j < NUMKARTRESULTS; j++) { - if (K_KartGetItemOdds(i, j, mashed, spbrush, player->bot) > 0) + if (K_KartGetItemOdds(i, j, mashed, spbrush, player->bot, (player->bot && player->botvars.rival)) > 0) { available = true; break; @@ -730,7 +774,9 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) pdis = FixedDiv(pdis * FRACUNIT, mapobjectscale) / FRACUNIT; if (franticitems) // Frantic items make the distances between everyone artifically higher, for crazier items + { pdis = (15 * pdis) / 14; + } if (spbplace != -1 && player->kartstuff[k_position] == spbplace+1) // SPB Rush Mode: It's 2nd place's job to catch-up items and make 1st place's job hell { @@ -738,6 +784,12 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) spbrush = true; } + if (player->bot && player->botvars.rival) + { + // Rival has better odds :) + pdis = (15 * pdis) / 14; + } + pdis = ((28 + (8-pingame)) * pdis) / 28; // scale with player count // SPECIAL CASE No. 1: @@ -866,7 +918,7 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) useodds = K_FindUseodds(player, mashed, pdis, bestbumper, spbrush); for (i = 1; i < NUMKARTRESULTS; i++) - spawnchance[i] = (totalspawnchance += K_KartGetItemOdds(useodds, i, mashed, spbrush, player->bot)); + spawnchance[i] = (totalspawnchance += K_KartGetItemOdds(useodds, i, mashed, spbrush, player->bot, (player->bot && player->botvars.rival))); // Award the player whatever power is rolled if (totalspawnchance > 0) @@ -1906,7 +1958,7 @@ void K_MomentumToFacing(player_t *player) boolean K_ApplyOffroad(player_t *player) { - if (player->kartstuff[k_invincibilitytimer] || player->kartstuff[k_hyudorotimer] || EITHERSNEAKER(player)) + if (player->kartstuff[k_invincibilitytimer] || player->kartstuff[k_hyudorotimer] || player->kartstuff[k_sneakertimer]) return false; return true; } @@ -1943,29 +1995,44 @@ static void K_GetKartBoostPower(player_t *player) accelboost += (a) / numboosts; \ } - if (player->kartstuff[k_levelbooster]) // Level boosters - ADDBOOST(FRACUNIT/2, 8*FRACUNIT); // + 50% top speed, + 800% acceleration - if (player->kartstuff[k_sneakertimer]) // Sneaker - ADDBOOST(FRACUNIT/2, 8*FRACUNIT); // + 50% top speed, + 800% acceleration + { + UINT8 i; + for (i = 0; i < player->kartstuff[k_numsneakers]; i++) + { + ADDBOOST(FRACUNIT/2, 8*FRACUNIT); // + 50% top speed, + 800% acceleration + } + } if (player->kartstuff[k_invincibilitytimer]) // Invincibility - ADDBOOST((3*FRACUNIT)/8, 3*FRACUNIT); // + 37.5% top speed, + 300% acceleration + { + ADDBOOST(3*FRACUNIT/8, 3*FRACUNIT); // + 37.5% top speed, + 300% acceleration + } if (player->kartstuff[k_flamedash]) // Flame Shield dash + { ADDBOOST(K_FlameShieldDashVar(player->kartstuff[k_flamedash]), 3*FRACUNIT); // + infinite top speed, + 300% acceleration + } if (player->kartstuff[k_startboost]) // Startup Boost + { ADDBOOST(FRACUNIT/4, 6*FRACUNIT); // + 25% top speed, + 600% acceleration + } if (player->kartstuff[k_driftboost]) // Drift Boost + { ADDBOOST(FRACUNIT/4, 4*FRACUNIT); // + 25% top speed, + 400% acceleration + } if (player->kartstuff[k_ringboost]) // Ring Boost + { ADDBOOST(FRACUNIT/5, 4*FRACUNIT); // + 20% top speed, + 400% acceleration + } if (player->kartstuff[k_eggmanexplode]) // Ready-to-explode - ADDBOOST(FRACUNIT/5, FRACUNIT); // + 20% top speed, + 100% acceleration + { + ADDBOOST(3*FRACUNIT/20, FRACUNIT); // + 15% top speed, + 100% acceleration + } if (player->kartstuff[k_draftpower] > 0) // Drafting { @@ -1978,9 +2045,13 @@ static void K_GetKartBoostPower(player_t *player) // value smoothing if (speedboost > player->kartstuff[k_speedboost]) + { player->kartstuff[k_speedboost] = speedboost; + } else + { player->kartstuff[k_speedboost] += (speedboost - player->kartstuff[k_speedboost]) / (TICRATE/2); + } player->kartstuff[k_accelboost] = accelboost; player->kartstuff[k_numboosts] = numboosts; @@ -2014,6 +2085,12 @@ fixed_t K_GetKartSpeed(player_t *player, boolean doboostpower) { // Give top speed a buff for bots, since it's a fairly weak stat without drifting fixed_t speedmul = ((kartspeed-1) * FRACUNIT / 8) / 10; // +10% for speed 9 + + if (player->botvars.rival == true) + { + speedmul += FRACUNIT/10; // +10% for rival + } + finalspeed = FixedMul(finalspeed, FRACUNIT + speedmul); } @@ -2044,9 +2121,12 @@ fixed_t K_GetKartAccel(player_t *player) //k_accel += 3 * (9 - kartspeed); // 36 - 60 k_accel += 4 * (9 - kartspeed); // 32 - 64 + if (K_PlayerUsesBotMovement(player)) { - k_accel = FixedMul(k_accel, K_BotRubberband(player)); + // Rubberbanding acceleration is waekened since it makes hits feel more meaningful + fixed_t rubberband = K_BotRubberband(player) - FRACUNIT; + k_accel = FixedMul(k_accel, FRACUNIT + (rubberband/2)); } return FixedMul(k_accel, FRACUNIT+player->kartstuff[k_accelboost]); @@ -2069,19 +2149,25 @@ UINT16 K_GetKartFlashing(player_t *player) fixed_t K_3dKartMovement(player_t *player, boolean onground, fixed_t forwardmove) { - fixed_t accelmax = 4000; + const fixed_t accelmax = 4000; + const fixed_t p_speed = K_GetKartSpeed(player, true); + const fixed_t p_accel = K_GetKartAccel(player); fixed_t newspeed, oldspeed, finalspeed; - fixed_t p_speed = K_GetKartSpeed(player, true); - fixed_t p_accel = K_GetKartAccel(player); + fixed_t orig = ORIG_FRICTION; if (!onground) return 0; // If the player isn't on the ground, there is no change in speed + if (K_PlayerUsesBotMovement(player)) + { + orig = K_BotFrictionRubberband(player, ORIG_FRICTION); + } + // ACCELCODE!!!1!11! oldspeed = R_PointToDist2(0, 0, player->rmomx, player->rmomy); // FixedMul(P_AproxDistance(player->rmomx, player->rmomy), player->mo->scale); // Don't calculate the acceleration as ever being above top speed if (oldspeed > p_speed) oldspeed = p_speed; - newspeed = FixedDiv(FixedDiv(FixedMul(oldspeed, accelmax - p_accel) + FixedMul(p_speed, p_accel), accelmax), ORIG_FRICTION); + newspeed = FixedDiv(FixedDiv(FixedMul(oldspeed, accelmax - p_accel) + FixedMul(p_speed, p_accel), accelmax), orig); if (player->kartstuff[k_pogospring]) // Pogo Spring minimum/maximum thrust { @@ -2103,7 +2189,7 @@ fixed_t K_3dKartMovement(player_t *player, boolean onground, fixed_t forwardmove // 0 with no gas, and // -25 when only braking. - if (EITHERSNEAKER(player)) + if (player->kartstuff[k_sneakertimer]) forwardmove = 50; finalspeed *= forwardmove/25; @@ -2187,7 +2273,7 @@ void K_SpinPlayer(player_t *player, mobj_t *source, INT32 type, mobj_t *inflicto K_PlayHitEmSound(source); player->kartstuff[k_sneakertimer] = 0; - //player->kartstuff[k_levelbooster] = 0; + player->kartstuff[k_numsneakers] = 0; player->kartstuff[k_driftboost] = 0; player->kartstuff[k_ringboost] = 0; @@ -2334,7 +2420,7 @@ void K_SquishPlayer(player_t *player, mobj_t *source, mobj_t *inflictor) #endif player->kartstuff[k_sneakertimer] = 0; - player->kartstuff[k_levelbooster] = 0; + player->kartstuff[k_numsneakers] = 0; player->kartstuff[k_driftboost] = 0; player->kartstuff[k_ringboost] = 0; @@ -2461,7 +2547,7 @@ void K_ExplodePlayer(player_t *player, mobj_t *source, mobj_t *inflictor) // A b player->mo->momx = player->mo->momy = 0; player->kartstuff[k_sneakertimer] = 0; - player->kartstuff[k_levelbooster] = 0; + player->kartstuff[k_numsneakers] = 0; player->kartstuff[k_driftboost] = 0; player->kartstuff[k_ringboost] = 0; @@ -3886,13 +3972,28 @@ void K_DoSneaker(player_t *player, INT32 type) if (!player->kartstuff[k_floorboost] || player->kartstuff[k_floorboost] == 3) { - S_StartSound(player->mo, sfx_cdfm01); + const sfxenum_t normalsfx = sfx_cdfm01; + const sfxenum_t smallsfx = sfx_cdfm40; + sfxenum_t sfx = normalsfx; + + if (player->kartstuff[k_numsneakers]) + { + // Use a less annoying sound when stacking sneakers. + sfx = smallsfx; + } + + S_StopSoundByID(player->mo, normalsfx); + S_StopSoundByID(player->mo, smallsfx); + S_StartSound(player->mo, sfx); + K_SpawnDashDustRelease(player); if (intendedboost > player->kartstuff[k_speedboost]) player->karthud[khud_destboostcam] = FixedMul(FRACUNIT, FixedDiv((intendedboost - player->kartstuff[k_speedboost]), intendedboost)); + + player->kartstuff[k_numsneakers]++; } - if (!EITHERSNEAKER(player)) + if (!player->kartstuff[k_sneakertimer]) { if (type == 2) { @@ -3926,10 +4027,10 @@ void K_DoSneaker(player_t *player, INT32 type) { player->pflags |= PF_ATTACKDOWN; K_PlayBoostTaunt(player->mo); - player->kartstuff[k_sneakertimer] = sneakertime; + } - else - player->kartstuff[k_levelbooster] = sneakertime; + + player->kartstuff[k_sneakertimer] = sneakertime; // set angle for spun out players: player->kartstuff[k_boostangle] = (INT32)player->mo->angle; @@ -4035,7 +4136,7 @@ void K_DoPogoSpring(mobj_t *mo, fixed_t vertispeed, UINT8 sound) thrust = 72<player->kartstuff[k_pogospring] != 2) { - if (EITHERSNEAKER(mo->player)) + if (mo->player->kartstuff[k_sneakertimer]) thrust = FixedMul(thrust, (5*FRACUNIT)/4); else if (mo->player->kartstuff[k_invincibilitytimer]) thrust = FixedMul(thrust, (9*FRACUNIT)/8); @@ -5238,7 +5339,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->speed > 0) { // Speed lines - if (EITHERSNEAKER(player) || player->kartstuff[k_ringboost] + if (player->kartstuff[k_sneakertimer] || player->kartstuff[k_ringboost] || player->kartstuff[k_driftboost] || player->kartstuff[k_startboost] || player->kartstuff[k_eggmanexplode]) { @@ -5454,7 +5555,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) { if ((P_IsObjectOnGround(player->mo) || (player->kartstuff[k_spinouttype] != 0)) - && (!EITHERSNEAKER(player))) + && (!player->kartstuff[k_sneakertimer])) { player->kartstuff[k_spinouttimer]--; if (player->kartstuff[k_wipeoutslow] > 1) @@ -5493,15 +5594,19 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->kartstuff[k_ringboost]--; if (player->kartstuff[k_sneakertimer]) + { player->kartstuff[k_sneakertimer]--; - if (player->kartstuff[k_levelbooster]) - player->kartstuff[k_levelbooster]--; + if (player->kartstuff[k_sneakertimer] <= 0) + { + player->kartstuff[k_numsneakers] = 0; + } + } if (player->kartstuff[k_flamedash]) player->kartstuff[k_flamedash]--; - if (EITHERSNEAKER(player) && player->kartstuff[k_wipeoutslow] > 0 && player->kartstuff[k_wipeoutslow] < wipeoutslowtime+1) + if (player->kartstuff[k_sneakertimer] && player->kartstuff[k_wipeoutslow] > 0 && player->kartstuff[k_wipeoutslow] < wipeoutslowtime+1) player->kartstuff[k_wipeoutslow] = wipeoutslowtime+1; if (player->kartstuff[k_floorboost]) @@ -6194,7 +6299,7 @@ INT16 K_GetKartTurnValue(player_t *player, INT16 turnvalue) return turnvalue; } - if (EITHERSNEAKER(player) || player->kartstuff[k_invincibilitytimer] || player->kartstuff[k_growshrinktimer] > 0) + if (player->kartstuff[k_sneakertimer] || player->kartstuff[k_invincibilitytimer] || player->kartstuff[k_growshrinktimer] > 0) { turnvalue = 5*turnvalue/4; } @@ -6433,7 +6538,7 @@ static void K_KartDrift(player_t *player, boolean onground) player->kartstuff[k_driftend] = 0; } - if ((!EITHERSNEAKER(player)) + if ((!player->kartstuff[k_sneakertimer]) || (!player->cmd.driftturn) || (!player->kartstuff[k_aizdriftstrat]) || (player->cmd.driftturn > 0) != (player->kartstuff[k_aizdriftstrat] > 0)) @@ -6729,7 +6834,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) { K_DoSneaker(player, 2); K_PlayBoostTaunt(player->mo); - player->kartstuff[k_rocketsneakertimer] -= 2*TICRATE; + player->kartstuff[k_rocketsneakertimer] -= 3*TICRATE; if (player->kartstuff[k_rocketsneakertimer] < 1) player->kartstuff[k_rocketsneakertimer] = 1; } @@ -6795,9 +6900,9 @@ void K_MoveKartPlayer(player_t *player, boolean onground) P_SetScale(overlay, player->mo->scale); } player->kartstuff[k_invincibilitytimer] = itemtime+(2*TICRATE); // 10 seconds - if (P_IsDisplayPlayer(player)) + if (P_IsLocalPlayer(player)) S_ChangeMusicSpecial("kinvnc"); - else + if (! P_IsDisplayPlayer(player)) S_StartSound(player->mo, (cv_kartinvinsfx.value ? sfx_alarmg : sfx_kinvnc)); P_RestoreMusic(player); K_PlayPowerGloatSound(player->mo); @@ -6999,9 +7104,9 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if (cv_kartdebugshrink.value && !modeattacking && !player->bot) player->mo->destscale = (6*player->mo->destscale)/8; player->kartstuff[k_growshrinktimer] = itemtime+(4*TICRATE); // 12 seconds - if (P_IsDisplayPlayer(player)) + if (P_IsLocalPlayer(player)) S_ChangeMusicSpecial("kgrow"); - else + if (! P_IsDisplayPlayer(player)) S_StartSound(player->mo, (cv_kartinvinsfx.value ? sfx_alarmg : sfx_kgrow)); P_RestoreMusic(player); S_StartSound(player->mo, sfx_kc5a); @@ -7323,7 +7428,8 @@ void K_MoveKartPlayer(player_t *player, boolean onground) player->mo->friction += 4608; } - if (player->speed > 0 && cmd->forwardmove < 0) // change friction while braking no matter what, otherwise it's not any more effective than just letting go off accel + // change friction while braking no matter what, otherwise it's not any more effective than just letting go off accel + if (player->speed > 0 && cmd->forwardmove < 0) player->mo->friction -= 2048; // Karma ice physics @@ -7360,6 +7466,13 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if (player->mo->movefactor < 32) player->mo->movefactor = 32; } + + // Don't go too far above your top speed when rubberbanding + // Down here, because we do NOT want to modify movefactor + if (K_PlayerUsesBotMovement(player)) + { + player->mo->friction = K_BotFrictionRubberband(player, player->mo->friction); + } } K_KartDrift(player, P_IsObjectOnGround(player->mo)); // Not using onground, since we don't want this affected by spring pads @@ -7650,6 +7763,8 @@ static patch_t *kp_sadface[2]; static patch_t *kp_check[6]; +static patch_t *kp_rival[2]; + static patch_t *kp_eggnum[4]; static patch_t *kp_flameshieldmeter[104][2]; @@ -7672,6 +7787,10 @@ static patch_t *kp_itemminimap; static patch_t *kp_alagles[10]; static patch_t *kp_blagles[6]; +static patch_t *kp_cpu; + +static patch_t *kp_nametagstem; + void K_LoadKartHUDGraphics(void) { INT32 i, j; @@ -7942,6 +8061,14 @@ void K_LoadKartHUDGraphics(void) kp_check[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); } + // Rival indicators + sprintf(buffer, "K_RIVALx"); + for (i = 0; i < 2; i++) + { + buffer[7] = '1'+i; + kp_rival[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + // Eggman warning numbers sprintf(buffer, "K_EGGNx"); for (i = 0; i < 4; i++) @@ -8029,6 +8156,10 @@ void K_LoadKartHUDGraphics(void) buffer[7] = '0'+i; kp_blagles[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); } + + kp_cpu = (patch_t *) W_CachePatchName("K_CPU", PU_HUDGFX); + + kp_nametagstem = (patch_t *) W_CachePatchName("K_NAMEST", PU_HUDGFX); } // For the item toggle menu @@ -9073,7 +9204,7 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I { if (players[tab[i].num].bot) { - ; // TODO: Put a graphic here to indicate this player is a bot! + V_DrawScaledPatch(x + ((i < 8) ? -25 : rightoffset + 3), y-2, 0, kp_cpu); } else if (tab[i].num != serverplayer || !server_lagless) { @@ -9186,6 +9317,7 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I static void K_drawKartLapsAndRings(void) { + const boolean uselives = G_GametypeUsesLives(); SINT8 ringanim_realframe = stplyr->karthud[khud_ringframe]; INT32 splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM|V_SNAPTOLEFT); UINT8 rn[2]; @@ -9254,14 +9386,14 @@ static void K_drawKartLapsAndRings(void) ln[0] = ((stplyr->laps / 10) % 10); ln[1] = (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]]); + V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[ln[0]]); + V_DrawScaledPatch(fx+17, fy, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[ln[1]]); ln[0] = ((abs(cv_numlaps.value) / 10) % 10); ln[1] = (abs(cv_numlaps.value) % 10); - V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|splitflags, pingnum[ln[0]]); - V_DrawScaledPatch(fx+31, fy, V_HUDTRANS|splitflags, pingnum[ln[1]]); + V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[ln[0]]); + V_DrawScaledPatch(fx+31, fy, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[ln[1]]); } else { @@ -9270,7 +9402,7 @@ static void K_drawKartLapsAndRings(void) } // Rings - if (netgame) + if (!uselives) { V_DrawScaledPatch(fx-2 + (flipflag ? (SHORT(kp_ringstickersplit[1]->width) - 3) : 0), fy-10, V_HUDTRANS|splitflags|flipflag, kp_ringstickersplit[1]); if (flipflag) @@ -9284,19 +9416,19 @@ static void K_drawKartLapsAndRings(void) if (stplyr->kartstuff[k_rings] < 0) // Draw the minus for ring debt V_DrawMappedPatch(fr+7, fy-10, V_HUDTRANS|splitflags, kp_ringdebtminussmall, ringmap); - V_DrawMappedPatch(fr+11, fy-10, V_HUDTRANS|splitflags, pingnum[rn[0]], ringmap); - V_DrawMappedPatch(fr+15, fy-10, V_HUDTRANS|splitflags, pingnum[rn[1]], ringmap); + V_DrawMappedPatch(fr+11, fy-10, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[rn[0]], ringmap); + V_DrawMappedPatch(fr+15, fy-10, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[rn[1]], ringmap); // SPB ring lock if (stplyr->kartstuff[k_ringlock]) V_DrawScaledPatch(fr-12, fy-23, V_HUDTRANS|splitflags, kp_ringspblocksmall[stplyr->karthud[khud_ringspblock]]); // Lives - if (!netgame) + if (uselives) { UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, stplyr->skincolor, GTC_CACHE); V_DrawMappedPatch(fr+21, fy-13, V_HUDTRANS|splitflags, facemmapprefix[stplyr->skin], colormap); - V_DrawScaledPatch(fr+34, fy-10, V_HUDTRANS|splitflags, pingnum[(stplyr->lives % 10)]); // make sure this doesn't overflow + V_DrawScaledPatch(fr+34, fy-10, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[(stplyr->lives % 10)]); // make sure this doesn't overflow } } else @@ -9310,7 +9442,7 @@ static void K_drawKartLapsAndRings(void) V_DrawKartString(LAPS_X+33, LAPS_Y+3, V_HUDTRANS|splitflags, va("%d/%d", stplyr->laps, cv_numlaps.value)); // Rings - if (netgame) + if (!uselives) V_DrawScaledPatch(LAPS_X, LAPS_Y-11, V_HUDTRANS|splitflags, kp_ringsticker[1]); else V_DrawScaledPatch(LAPS_X, LAPS_Y-11, V_HUDTRANS|splitflags, kp_ringsticker[0]); @@ -9334,7 +9466,7 @@ static void K_drawKartLapsAndRings(void) V_DrawScaledPatch(LAPS_X-5, LAPS_Y-28, V_HUDTRANS|splitflags, kp_ringspblock[stplyr->karthud[khud_ringspblock]]); // Lives - if (!netgame) + if (uselives) { UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, stplyr->skincolor, GTC_CACHE); V_DrawMappedPatch(LAPS_X+46, LAPS_Y-16, V_HUDTRANS|splitflags, facerankprefix[stplyr->skin], colormap); @@ -9442,14 +9574,14 @@ static void K_drawKartBumpersOrKarma(void) ln[0] = ((numtargets / 10) % 10); ln[1] = (numtargets % 10); - V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|splitflags, pingnum[ln[0]]); - V_DrawScaledPatch(fx+17, fy, V_HUDTRANS|splitflags, pingnum[ln[1]]); + V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[ln[0]]); + V_DrawScaledPatch(fx+17, fy, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[ln[1]]); ln[0] = ((maptargets / 10) % 10); ln[1] = (maptargets % 10); - V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|splitflags, pingnum[ln[0]]); - V_DrawScaledPatch(fx+31, fy, V_HUDTRANS|splitflags, pingnum[ln[1]]); + V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[ln[0]]); + V_DrawScaledPatch(fx+31, fy, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[ln[1]]); } else { @@ -9476,14 +9608,14 @@ static void K_drawKartBumpersOrKarma(void) ln[0] = ((abs(stplyr->kartstuff[k_bumper]) / 10) % 10); ln[1] = (abs(stplyr->kartstuff[k_bumper]) % 10); - V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|splitflags, pingnum[ln[0]]); - V_DrawScaledPatch(fx+17, fy, V_HUDTRANS|splitflags, pingnum[ln[1]]); + V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[ln[0]]); + V_DrawScaledPatch(fx+17, fy, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[ln[1]]); ln[0] = ((abs(maxbumper) / 10) % 10); ln[1] = (abs(maxbumper) % 10); - V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|splitflags, pingnum[ln[0]]); - V_DrawScaledPatch(fx+31, fy, V_HUDTRANS|splitflags, pingnum[ln[1]]); + V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[ln[0]]); + V_DrawScaledPatch(fx+31, fy, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[ln[1]]); } else { @@ -9525,34 +9657,6 @@ static void K_drawKartBumpersOrKarma(void) } } -static fixed_t K_FindCheckX(fixed_t px, fixed_t py, angle_t ang, fixed_t mx, fixed_t my) -{ - fixed_t dist, x; - fixed_t range = RING_DIST/3; - angle_t diff; - - range *= gamespeed+1; - - dist = abs(R_PointToDist2(px, py, mx, my)); - if (dist > range) - return -320; - - diff = R_PointToAngle2(px, py, mx, my) - ang; - - if (diff < ANGLE_90 || diff > ANGLE_270) - return -320; - else - x = (FixedMul(FINETANGENT(((diff+ANGLE_90)>>ANGLETOFINESHIFT) & 4095), 160<>FRACBITS; - - if (encoremode) - x = 320-x; - - if (r_splitscreen > 1) - x /= 2; - - return x; -} - static void K_drawKartWanted(void) { UINT8 i, numwanted = 0; @@ -9625,52 +9729,336 @@ static void K_drawKartWanted(void) } } +static void K_ObjectTracking(fixed_t *hud_x, fixed_t *hud_y, vertex_t *campos, angle_t camang, angle_t camaim, UINT8 camnum, vertex_t *point) +{ + const INT32 swhalf = (BASEVIDWIDTH / 2); + const fixed_t swhalffixed = swhalf * FRACUNIT; + + const INT32 shhalf = (BASEVIDHEIGHT / 2); + const fixed_t shhalffixed = shhalf * FRACUNIT; + + INT32 anglediff = (signed)(camang - R_PointToAngle2(campos->x, campos->y, point->x, point->y)); + fixed_t distance = R_PointToDist2(campos->x, campos->y, point->x, point->y); + fixed_t factor = INT32_MAX; + + if (abs(anglediff) > ANGLE_90) + { + if (hud_x != NULL) + { + *hud_x = -BASEVIDWIDTH * FRACUNIT; + } + + if (hud_y != NULL) + { + *hud_y = -BASEVIDWIDTH * FRACUNIT; + } + + //*hud_scale = FRACUNIT; + return; + } + + factor = max(1, FINECOSINE(anglediff >> ANGLETOFINESHIFT)); + +#define NEWTAN(n) FINETANGENT(((n + ANGLE_90) >> ANGLETOFINESHIFT) & 4095) + + if (hud_x != NULL) + { + *hud_x = FixedMul(NEWTAN(anglediff), swhalffixed) + swhalffixed; + + if (encoremode) + { + *hud_x = (BASEVIDWIDTH * FRACUNIT) - *hud_x; + } + + if (r_splitscreen >= 2) + { + *hud_x /= 2; + + if (camnum & 1) + { + *hud_x += swhalffixed; + } + } + } + + if (hud_y != NULL) + { + *hud_y = campos->z - point->z; + *hud_y = FixedDiv(*hud_y, FixedMul(factor, distance)); + *hud_y = (*hud_y * swhalf) + shhalffixed; + *hud_y = *hud_y + NEWTAN(camaim) * swhalf; + + if (r_splitscreen >= 1) + { + *hud_y /= 2; + + if (camnum > 1) + { + *hud_y += shhalffixed; + } + } + } + + //*hud_scale = FixedDiv(swhalffixed, FixedMul(factor, distance)); + +#undef NEWTAN +} + static void K_drawKartPlayerCheck(void) { - INT32 i; - UINT8 *colormap; - INT32 x; - + const fixed_t maxdistance = FixedMul(1280 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed)); + camera_t *thiscam; + vertex_t c; + UINT8 cnum = 0; + UINT8 i; INT32 splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM); - if (!stplyr->mo || stplyr->spectator) + if (stplyr == NULL || stplyr->mo == NULL || P_MobjWasRemoved(stplyr->mo)) + { return; + } - if (stplyr->awayviewtics) + if (stplyr->spectator || stplyr->awayviewtics) + { return; + } - if (( stplyr->cmd.buttons & BT_LOOKBACK )) + if (stplyr->cmd.buttons & BT_LOOKBACK) + { return; + } + + if (r_splitscreen) + { + for (i = 1; i <= r_splitscreen; i++) + { + if (stplyr == &players[displayplayers[i]]) + { + cnum = i; + break; + } + } + } + + thiscam = &camera[cnum]; + + c.x = stplyr->mo->x; + c.y = stplyr->mo->y; + c.z = stplyr->mo->z; for (i = 0; i < MAXPLAYERS; i++) { + player_t *checkplayer = &players[i]; + fixed_t distance = maxdistance+1; + UINT8 *colormap = NULL; UINT8 pnum = 0; + fixed_t x = 0; + vertex_t v; - if (&players[i] == stplyr) - continue; - if (!playeringame[i] || players[i].spectator) - continue; - if (!players[i].mo) - continue; - - if ((players[i].kartstuff[k_invincibilitytimer] <= 0) && (leveltime & 2)) - pnum++; // white frames - - if (players[i].kartstuff[k_itemtype] == KITEM_GROW || players[i].kartstuff[k_growshrinktimer] > 0) - pnum += 4; - else if (players[i].kartstuff[k_itemtype] == KITEM_INVINCIBILITY || players[i].kartstuff[k_invincibilitytimer]) - pnum += 2; - - x = K_FindCheckX(stplyr->mo->x, stplyr->mo->y, stplyr->mo->angle, players[i].mo->x, players[i].mo->y); - if (x <= 320 && x >= 0) + if (!playeringame[i] || checkplayer->spectator) { - if (x < 14) - x = 14; - else if (x > 306) - x = 306; + // Not in-game + continue; + } - colormap = R_GetTranslationColormap(TC_DEFAULT, players[i].mo->color, GTC_CACHE); - V_DrawMappedPatch(x, CHEK_Y, V_HUDTRANS|splitflags, kp_check[pnum], colormap); + if (checkplayer->mo == NULL || P_MobjWasRemoved(checkplayer->mo)) + { + // No object + continue; + } + + if (checkplayer == stplyr) + { + // This is you! + continue; + } + + v.x = checkplayer->mo->x; + v.y = checkplayer->mo->y; + v.z = checkplayer->mo->z; + + distance = R_PointToDist2(c.x, c.y, v.x, v.y); + + if (distance > maxdistance) + { + // Too far away + continue; + } + + if ((checkplayer->kartstuff[k_invincibilitytimer] <= 0) && (leveltime & 2)) + { + pnum++; // white frames + } + + if (checkplayer->kartstuff[k_itemtype] == KITEM_GROW || checkplayer->kartstuff[k_growshrinktimer] > 0) + { + pnum += 4; + } + else if (checkplayer->kartstuff[k_itemtype] == KITEM_INVINCIBILITY || checkplayer->kartstuff[k_invincibilitytimer]) + { + pnum += 2; + } + + K_ObjectTracking(&x, NULL, &c, thiscam->angle + ANGLE_180, 0, cnum, &v); + + colormap = R_GetTranslationColormap(TC_DEFAULT, checkplayer->mo->color, GTC_CACHE); + V_DrawFixedPatch(x, CHEK_Y * FRACUNIT, FRACUNIT, V_HUDTRANS|splitflags, kp_check[pnum], colormap); + } +} + +static void K_drawKartNameTags(void) +{ + const fixed_t maxdistance = 8192*mapobjectscale; + camera_t *thiscam; + vertex_t c; + UINT8 cnum = 0; + UINT8 i; + + if (stplyr == NULL || stplyr->mo == NULL || P_MobjWasRemoved(stplyr->mo)) + { + return; + } + + if (stplyr->awayviewtics) + { + return; + } + + if (r_splitscreen) + { + for (i = 1; i <= r_splitscreen; i++) + { + if (stplyr == &players[displayplayers[i]]) + { + cnum = i; + break; + } + } + } + + thiscam = &camera[cnum]; + + c.x = thiscam->x; + c.y = thiscam->y; + c.z = thiscam->z; + + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *ntplayer = &players[i]; + fixed_t distance = maxdistance+1; + + fixed_t x = -BASEVIDWIDTH * FRACUNIT; + fixed_t y = -BASEVIDWIDTH * FRACUNIT; + + vertex_t v; + UINT8 j; + + if (!playeringame[i] || ntplayer->spectator) + { + // Not in-game + continue; + } + + if (ntplayer->mo == NULL || P_MobjWasRemoved(ntplayer->mo)) + { + // No object + continue; + } + + if (!P_CheckSight(stplyr->mo, ntplayer->mo)) + { + // Can't see + continue; + } + + for (j = 0; j <= r_splitscreen; j++) + { + if (ntplayer == &players[displayplayers[j]]) + { + break; + } + } + + if (j <= r_splitscreen) + { + // Is a player that's being shown on this computer + continue; + } + + v.x = ntplayer->mo->x; + v.y = ntplayer->mo->y; + v.z = ntplayer->mo->z; + + if (!(ntplayer->mo->eflags & MFE_VERTICALFLIP)) + { + v.z += ntplayer->mo->height; + } + + distance = R_PointToDist2(c.x, c.y, v.x, v.y); + + if (distance > maxdistance) + { + // Too far away + continue; + } + + K_ObjectTracking(&x, &y, &c, thiscam->angle, thiscam->aiming, cnum, &v); + + if (x == -BASEVIDWIDTH * FRACUNIT) + { + // Off-screen + continue; + } + + if (ntplayer->bot) + { + if (ntplayer->botvars.rival == true) + { + UINT8 blink = ((leveltime / 7) & 1); + V_DrawFixedPatch(x, y, FRACUNIT, V_HUDTRANS, kp_rival[blink], NULL); + } + } + else if (netgame) + { + if ((ntplayer->kartstuff[k_position] >= stplyr->kartstuff[k_position]-2) + && (ntplayer->kartstuff[k_position] <= stplyr->kartstuff[k_position]+2)) + { + INT32 namelen = V_ThinStringWidth(player_names[i], V_6WIDTHSPACE|V_ALLOWLOWERCASE); + INT32 clr = K_SkincolorToTextColor(ntplayer->skincolor); + UINT8 *colormap = V_GetStringColormap(clr); + INT32 barx = 0, bary = 0, barw = 0; + + // Since there's no "V_DrawFixedFill", and I don't feel like making it, + // fuck it, we're gonna just V_NOSCALESTART hack it + barw = (namelen * vid.dupx); + + barx = (x * vid.dupx) / FRACUNIT; + bary = (y * vid.dupy) / FRACUNIT; + + barx += (6 * vid.dupx); + bary -= (16 * vid.dupx); + + // Center it if necessary + if (vid.width != BASEVIDWIDTH * vid.dupx) + { + barx += (vid.width - (BASEVIDWIDTH * vid.dupx)) / 2; + } + + if (vid.height != BASEVIDHEIGHT * vid.dupy) + { + bary += (vid.height - (BASEVIDHEIGHT * vid.dupy)) / 2; + } + + V_DrawFill(barx, bary, barw, (3 * vid.dupy), colormap[31]|V_NOSCALESTART); + V_DrawFill(barx, bary + vid.dupy, barw, vid.dupy, colormap[0]|V_NOSCALESTART); + // END DRAWFILL DUMBNESS + + // Draw the stem + V_DrawFixedPatch(x, y, FRACUNIT, 0, kp_nametagstem, colormap); + + // Draw the name itself + V_DrawThinStringAtFixed(x + (5*FRACUNIT), y - (26*FRACUNIT), V_6WIDTHSPACE|V_ALLOWLOWERCASE|clr, player_names[i]); + } } } } @@ -10327,7 +10715,7 @@ static void K_drawInput(void) V_DrawFill(x+(xoffs), y+BUTTH, BUTTW-1, 2, splitflags|31);\ }\ V_DrawFill(x+(xoffs), y+offs, BUTTW-1, BUTTH, col);\ - V_DrawFixedPatch((x+1+(xoffs))<bot && stplyr->botvars.rival) + { + // Rival has better odds :) + pdis = (15 * pdis) / 14; + } + pdis = ((28 + (8-pingame)) * pdis) / 28; // scale with player count useodds = K_FindUseodds(stplyr, 0, pdis, bestbumper, spbrush); for (i = 1; i < NUMKARTRESULTS; i++) { - const INT32 itemodds = K_KartGetItemOdds(useodds, i, 0, spbrush, stplyr->bot); + const INT32 itemodds = K_KartGetItemOdds(useodds, i, 0, spbrush, stplyr->bot, (stplyr->bot && stplyr->botvars.rival)); if (itemodds <= 0) continue; @@ -10678,6 +11072,12 @@ void K_drawKartHUD(void) if (cv_kartcheck.value && !splitscreen && !players[displayplayers[0]].exiting && !freecam) K_drawKartPlayerCheck(); + // nametags +#ifdef HAVE_BLUA + if (LUA_HudEnabled(hud_names)) +#endif + K_drawKartNameTags(); + // Draw WANTED status if (G_BattleGametype()) { diff --git a/src/k_kart.h b/src/k_kart.h index 316e40788..2508b6f63 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -13,6 +13,7 @@ #define KART_FULLTURN 800 player_t *K_GetItemBoxPlayer(mobj_t *mobj); +angle_t K_ReflectAngle(angle_t angle, angle_t against, fixed_t maxspeed, fixed_t yourspeed); void K_RegisterKartStuff(void); diff --git a/src/k_pwrlv.c b/src/k_pwrlv.c index 8101a013e..eac664408 100644 --- a/src/k_pwrlv.c +++ b/src/k_pwrlv.c @@ -8,6 +8,7 @@ #include "m_random.h" #include "m_cond.h" // M_UpdateUnlockablesAndExtraEmblems #include "p_tick.h" // leveltime +#include "k_grandprix.h" // Online rankings for the main gametypes. // This array is saved to the gamedata. @@ -25,6 +26,27 @@ INT16 nospectategrief[MAXPLAYERS]; SINT8 speedscramble = -1; SINT8 encorescramble = -1; +SINT8 K_UsingPowerLevels(void) +{ + SINT8 pt = PWRLV_DISABLED; + + if (!cv_kartusepwrlv.value || !netgame || grandprixinfo.gp == true) + { + return PWRLV_DISABLED; + } + + if (G_RaceGametype()) + { + pt = PWRLV_RACE; + } + else if (G_BattleGametype()) + { + pt = PWRLV_BATTLE; + } + + return pt; +} + void K_ClearClientPowerLevels(void) { UINT8 i, j; diff --git a/src/k_pwrlv.h b/src/k_pwrlv.h index dfa300114..579e298af 100644 --- a/src/k_pwrlv.h +++ b/src/k_pwrlv.h @@ -21,6 +21,7 @@ extern UINT16 vspowerlevel[PWRLV_NUMTYPES]; extern UINT16 clientpowerlevels[MAXPLAYERS][PWRLV_NUMTYPES]; extern INT16 nospectategrief[MAXPLAYERS]; +SINT8 K_UsingPowerLevels(void); void K_ClearClientPowerLevels(void); INT16 K_CalculatePowerLevelInc(INT16 diff); INT16 K_CalculatePowerLevelAvg(void); diff --git a/src/lua_hud.h b/src/lua_hud.h index 960195cda..286700544 100644 --- a/src/lua_hud.h +++ b/src/lua_hud.h @@ -19,6 +19,7 @@ enum hud { hud_minimap, hud_item, hud_position, + hud_names, // online nametags hud_check, // "CHECK" f-zero indicator hud_minirankings, // Rankings to the left hud_battlebumpers, // mini rankings battle bumpers. diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c index 1ba21532f..8410cd55f 100644 --- a/src/lua_hudlib.c +++ b/src/lua_hudlib.c @@ -22,6 +22,7 @@ #include "v_video.h" #include "w_wad.h" #include "z_zone.h" +#include "hu_stuff.h" #include "lua_script.h" #include "lua_libs.h" diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index 985ed179f..1e93e5687 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -252,6 +252,8 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->charflags); else if (fastcmp(field,"lives")) lua_pushinteger(L, plr->lives); + else if (fastcmp(field,"lostlife")) + lua_pushboolean(L, plr->lostlife); else if (fastcmp(field,"continues")) lua_pushinteger(L, plr->continues); else if (fastcmp(field,"xtralife")) @@ -503,6 +505,8 @@ static int player_set(lua_State *L) plr->charflags = (UINT32)luaL_checkinteger(L, 3); else if (fastcmp(field,"lives")) plr->lives = (SINT8)luaL_checkinteger(L, 3); + else if (fastcmp(field,"lostlife")) + plr->lostlife = luaL_checkboolean(L, 3); else if (fastcmp(field,"continues")) plr->continues = (SINT8)luaL_checkinteger(L, 3); else if (fastcmp(field,"xtralife")) diff --git a/src/m_menu.c b/src/m_menu.c index ab8d0cc4a..739433ec8 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -60,6 +60,7 @@ #include "k_pwrlv.h" #include "d_player.h" // KITEM_ constants #include "k_color.h" +#include "k_grandprix.h" #include "i_joy.h" // for joystick menu controls @@ -227,7 +228,8 @@ menu_t SP_MainDef, MP_MainDef, OP_MainDef; menu_t MISC_ScrambleTeamDef, MISC_ChangeTeamDef, MISC_ChangeSpectateDef; // Single Player -//static void M_LoadGame(INT32 choice); +static void M_GrandPrixTemp(INT32 choice); +static void M_StartGrandPrix(INT32 choice); static void M_TimeAttack(INT32 choice); static boolean M_QuitTimeAttackMenu(void); static void M_BreakTheCapsules(INT32 choice); @@ -240,6 +242,7 @@ static void M_ModeAttackEndGame(INT32 choice); static void M_SetGuestReplay(INT32 choice); //static void M_ChoosePlayer(INT32 choice); menu_t SP_LevelStatsDef; +static menu_t SP_GrandPrixTempDef; static menu_t SP_TimeAttackDef, SP_ReplayDef, SP_GuestReplayDef, SP_GhostDef; //static menu_t SP_NightsAttackDef, SP_NightsReplayDef, SP_NightsGuestReplayDef, SP_NightsGhostDef; @@ -463,6 +466,13 @@ static consvar_t cv_dummycontinues = {"dummycontinues", "0", CV_HIDEN, liveslimi //static consvar_t cv_dummymares = {"dummymares", "Overall", CV_HIDEN|CV_CALL, dummymares_cons_t, Dummymares_OnChange, 0, NULL, NULL, 0, 0, NULL}; static consvar_t cv_dummystaff = {"dummystaff", "0", CV_HIDEN|CV_CALL, dummystaff_cons_t, Dummystaff_OnChange, 0, NULL, NULL, 0, 0, NULL}; +static CV_PossibleValue_t dummygpdifficulty_cons_t[] = {{0, "Easy"}, {1, "Normal"}, {2, "Hard"}, {3, "Master"}, {0, NULL}}; +static CV_PossibleValue_t dummygpcup_cons_t[50] = {{1, "TEMP"}}; // A REALLY BIG NUMBER, SINCE THIS IS TEMP UNTIL NEW MENUS + +static consvar_t cv_dummygpdifficulty = {"dummygpdifficulty", "Normal", CV_HIDEN, dummygpdifficulty_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; +static consvar_t cv_dummygpencore = {"dummygpencore", "Off", CV_HIDEN, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; +static consvar_t cv_dummygpcup = {"dummygpcup", "TEMP", CV_HIDEN, dummygpcup_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; + // ========================================================================== // ORGANIZATION START. // ========================================================================== @@ -802,30 +812,30 @@ static menuitem_t SR_EmblemHintMenu[] = // Single Player Main static menuitem_t SP_MainMenu[] = { - //{IT_CALL | IT_STRING, NULL, "Grand Prix", M_LoadGame, 92}, - {IT_SECRET, NULL, "Time Attack", M_TimeAttack, 100}, - {IT_SECRET, NULL, "Break the Capsules", M_BreakTheCapsules, 108}, + {IT_STRING|IT_CALL, NULL, "Grand Prix", M_GrandPrixTemp, 92}, + {IT_SECRET, NULL, "Time Attack", M_TimeAttack, 100}, + {IT_SECRET, NULL, "Break the Capsules", M_BreakTheCapsules, 108}, }; enum { - //spgrandprix, + spgrandprix, sptimeattack, spbreakthecapsules }; // Single Player Load Game -/*static menuitem_t SP_LoadGameMenu[] = +static menuitem_t SP_GrandPrixPlaceholderMenu[] = { - {IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLoadSave, '\0'}, // dummy menuitem for the control func -}; + {IT_STRING|IT_CVAR, NULL, "Character", &cv_chooseskin, 10}, + {IT_STRING|IT_CVAR, NULL, "Color", &cv_playercolor, 20}, -// Single Player Level Select -static menuitem_t SP_LevelSelectMenu[] = -{ - {IT_STRING|IT_CVAR, NULL, "Level", &cv_nextmap, 78}, - {IT_WHITESTRING|IT_CALL, NULL, "Start", M_LevelSelectWarp, 130}, -};*/ + {IT_STRING|IT_CVAR, NULL, "Difficulty", &cv_dummygpdifficulty, 40}, + {IT_STRING|IT_CVAR, NULL, "Encore Mode", &cv_dummygpencore, 50}, + + {IT_STRING|IT_CVAR, NULL, "Cup", &cv_dummygpcup, 70}, + {IT_STRING|IT_CALL, NULL, "Start", M_StartGrandPrix, 80}, +}; // Single Player Time Attack static menuitem_t SP_TimeAttackMenu[] = @@ -1810,6 +1820,8 @@ menu_t SP_LevelStatsDef = NULL }; +static menu_t SP_GrandPrixTempDef = DEFAULTMENUSTYLE(NULL, SP_GrandPrixPlaceholderMenu, &MainDef, 60, 30); + static menu_t SP_TimeAttackDef = { "M_ATTACK", @@ -3376,6 +3388,10 @@ void M_Init(void) //CV_RegisterVar(&cv_dummymares); CV_RegisterVar(&cv_dummystaff); + CV_RegisterVar(&cv_dummygpdifficulty); + CV_RegisterVar(&cv_dummygpencore); + CV_RegisterVar(&cv_dummygpcup); + quitmsg[QUITMSG] = M_GetText("Eggman's tied explosives\nto your girlfriend, and\nwill activate them if\nyou press the 'Y' key!\nPress 'N' to save her!\n\n(Press 'Y' to quit)"); quitmsg[QUITMSG1] = M_GetText("What would Tails say if\nhe saw you quitting the game?\n\n(Press 'Y' to quit)"); quitmsg[QUITMSG2] = M_GetText("Hey!\nWhere do ya think you're goin'?\n\n(Press 'Y' to quit)"); @@ -4221,6 +4237,35 @@ static void M_PatchSkinNameTable(void) return; } +// +// M_PrepareCupList +// +static void M_PrepareCupList(void) +{ + cupheader_t *cup = kartcupheaders; + INT32 i = 0; + + memset(dummygpcup_cons_t, 0, sizeof (dummygpcup_cons_t)); + + while (cup != NULL) + { + dummygpcup_cons_t[i].strvalue = cup->name; + dummygpcup_cons_t[i].value = i+1; + // this will probably crash or do something stupid at over 50 cups, + // but this is all behavior that gets completely overwritten in new-menus, so I'm not worried + i++; + cup = cup->next; + } + + for (; i < 50; i++) + { + dummygpcup_cons_t[i].strvalue = NULL; + dummygpcup_cons_t[i].value = 0; + } + + CV_SetValue(&cv_dummygpcup, 1); // This causes crash sometimes?! +} + // Call before showing any level-select menus static void M_PrepareLevelSelect(void) { @@ -6666,6 +6711,8 @@ static void M_Credits(INT32 choice) static void M_SinglePlayerMenu(INT32 choice) { (void)choice; + + SP_MainMenu[spgrandprix].status = IT_CALL|IT_STRING; SP_MainMenu[sptimeattack].status = (M_SecretUnlocked(SECRET_TIMEATTACK)) ? IT_CALL|IT_STRING : IT_SECRET; SP_MainMenu[spbreakthecapsules].status = @@ -7550,6 +7597,80 @@ static void M_HandleLevelStats(INT32 choice) } } +static void M_GrandPrixTemp(INT32 choice) +{ + (void)choice; + M_PatchSkinNameTable(); + M_PrepareCupList(); + M_SetupNextMenu(&SP_GrandPrixTempDef); +} + +// Start Grand Prix! +static void M_StartGrandPrix(INT32 choice) +{ + cupheader_t *gpcup = kartcupheaders; + + (void)choice; + + if (gpcup == NULL) + { + // welp + I_Error("No cup definitions for GP\n"); + return; + } + + M_ClearMenus(true); + + memset(&grandprixinfo, 0, sizeof(struct grandprixinfo)); + + switch (cv_dummygpdifficulty.value) + { + case 0: + grandprixinfo.gamespeed = KARTSPEED_EASY; + break; + case 1: + default: + grandprixinfo.gamespeed = KARTSPEED_NORMAL; + break; + case 2: + grandprixinfo.gamespeed = KARTSPEED_HARD; + break; + case 3: + grandprixinfo.gamespeed = KARTSPEED_HARD; + grandprixinfo.masterbots = true; + break; + + } + + grandprixinfo.encore = (boolean)(cv_dummygpencore.value); + + while (gpcup != NULL && gpcup->id != cv_dummygpcup.value-1) + { + gpcup = gpcup->next; + } + + if (gpcup == NULL) + { + gpcup = kartcupheaders; + } + + grandprixinfo.cup = gpcup; + + grandprixinfo.gp = true; + grandprixinfo.roundnum = 1; + grandprixinfo.wonround = false; + + grandprixinfo.initalize = true; + + G_DeferedInitNew( + false, + G_BuildMapName(grandprixinfo.cup->levellist[0] + 1), + (UINT8)(cv_chooseskin.value - 1), + (UINT8)(cv_splitplayers.value - 1), + false + ); +} + // =========== // MODE ATTACK // =========== diff --git a/src/p_inter.c b/src/p_inter.c index 49d3546ce..48cca6952 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -29,6 +29,7 @@ #include "k_kart.h" // SRB2kart #include "k_battle.h" #include "k_pwrlv.h" +#include "k_grandprix.h" // CTF player names #define CTFTEAMCODE(pl) pl->ctfteam ? (pl->ctfteam == 1 ? "\x85" : "\x84") : "" @@ -1968,71 +1969,146 @@ void P_CheckPointLimit(void) // Checks whether or not to end a race netgame. boolean P_CheckRacers(void) { - INT32 i, j, numplayersingame = 0, numexiting = 0; + UINT8 i; + UINT8 numplayersingame = 0; + UINT8 numexiting = 0; + boolean eliminatelast = cv_karteliminatelast.value; + boolean everyonedone = true; + boolean eliminatebots = false; boolean griefed = false; // Check if all the players in the race have finished. If so, end the level. for (i = 0; i < MAXPLAYERS; i++) { - if (!playeringame[i] || players[i].spectator || players[i].exiting || players[i].bot || !players[i].lives) - continue; + if (nospectategrief[i] != -1) // prevent spectate griefing + { + griefed = true; + } - break; + if (!playeringame[i] || players[i].spectator || players[i].lives <= 0) // Not playing + { + // Y'all aren't even playing + continue; + } + + numplayersingame++; + + if (players[i].exiting || (players[i].pflags & PF_TIMEOVER)) + { + numexiting++; + } + else + { + if (players[i].bot) + { + // Isn't a human, thus doesn't matter. (Sorry, robots.) + // Set this so that we can check for bots that need to get eliminated, though! + eliminatebots = true; + continue; + } + + everyonedone = false; + } } - if (i == MAXPLAYERS) // finished + // If we returned here with bots left, then the last place bot may have a chance to finish the map and NOT get time over. + // Not that it affects anything, they didn't make the map take longer or even get any points from it. But... it's a bit inconsistent! + // So if there's any bots, we'll let the game skip this, continue onto calculating eliminatelast, THEN we return true anyway. + if (everyonedone && !eliminatebots) { + // Everyone's finished, we're done here! racecountdown = exitcountdown = 0; return true; } - for (j = 0; j < MAXPLAYERS; j++) + if (numplayersingame <= 1) { - if (nospectategrief[j] != -1) // prevent spectate griefing - griefed = true; - if (!playeringame[j] || players[j].spectator) - continue; - numplayersingame++; - if (players[j].exiting) - numexiting++; + // Never do this without enough players. + eliminatelast = false; + } + else + { + if (grandprixinfo.gp == true) + { + // Always do this in GP + eliminatelast = true; + } + else if (griefed) + { + // Don't do this if someone spectated + eliminatelast = false; + } } - if (cv_karteliminatelast.value && numplayersingame > 1 && !griefed) + if (eliminatelast == true && (numplayersingame <= numexiting-1)) { - // check if we just got unlucky and there was only one guy who was a problem - for (j = i+1; j < MAXPLAYERS; j++) + // Everyone's done playing but one guy apparently. + // Just kill everyone who is still playing. + + for (i = 0; i < MAXPLAYERS; i++) { - if (!playeringame[j] || players[j].spectator || players[j].exiting || !players[j].lives) + if (!playeringame[i] || players[i].spectator || players[i].lives <= 0) // Not playing + { + // Y'all aren't even playing continue; - break; + } + + if (players[i].exiting || (players[i].pflags & PF_TIMEOVER)) + { + // You're done, you're free to go. + continue; + } + + P_DoTimeOver(&players[i]); } - if (j == MAXPLAYERS) // finish anyways, force a time over - { - P_DoTimeOver(&players[i]); - racecountdown = exitcountdown = 0; - return true; - } + // Everyone should be done playing at this point now. + racecountdown = exitcountdown = 0; + return true; } - if (!racecountdown) // Check to see if the winners have finished, to set countdown. + if (everyonedone) { + // See above: there might be bots that are still going, but all players are done, so we can exit now. + racecountdown = exitcountdown = 0; + return true; + } + + // SO, we're not done playing. + // Let's see if it's time to start the death counter! + + if (!racecountdown) + { + // If the winners are all done, then start the death timer. UINT8 winningpos = 1; winningpos = max(1, numplayersingame/2); if (numplayersingame % 2) // any remainder? + { winningpos++; + } if (numexiting >= winningpos) - racecountdown = (((netgame || multiplayer) ? cv_countdowntime.value : 30)*TICRATE) + 1; // 30 seconds to finish, get going! + { + tic_t countdown = 30*TICRATE; // 30 seconds left to finish, get going! + + if (netgame) + { + // Custom timer + countdown = cv_countdowntime.value * TICRATE; + } + + racecountdown = countdown + 1; + } } - if (numplayersingame < 2) // reset nospectategrief in free play + // We're still playing, but no one else is, so we need to reset spectator griefing. + if (numplayersingame <= 1) { - for (j = 0; j < MAXPLAYERS; j++) - nospectategrief[j] = -1; + memset(nospectategrief, -1, sizeof (nospectategrief)); } + // Turns out we're still having a good time & playing the game, we didn't have to do anything :) return false; } @@ -2150,11 +2226,28 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source) { if (target->flags & MF_MONITOR || target->type == MT_RANDOMITEM) { + UINT8 i; + P_SetTarget(&target->target, source); source->player->numboxes++; - if (cv_itemrespawn.value && (netgame || multiplayer)) + + for (i = 0; i < MAXPLAYERS; i++) { - target->fuse = cv_itemrespawntime.value*TICRATE + 2; // Random box generation + if (&players[i] == source->player) + { + continue; + } + + if (playeringame[i] && !players[i].spectator && players[i].lives != 0) + { + break; + } + } + + if (i < MAXPLAYERS) + { + // Respawn items in multiplayer, don't respawn them when alone + target->fuse = 2*TICRATE + 2; } } @@ -2246,20 +2339,6 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source) target->flags |= MF_NOBLOCKMAP|MF_NOCLIPHEIGHT; P_SetThingPosition(target); - if (!target->player->bot && !G_IsSpecialStage(gamemap) && G_GametypeUsesLives()) - { - target->player->lives -= 1; // Lose a life Tails 03-11-2000 - - if (target->player->lives <= 0) // Tails 03-14-2000 - { - if (P_IsLocalPlayer(target->player)/* && target->player == &players[consoleplayer] */) - { - S_StopMusic(); // Stop the Music! Tails 03-14-2000 - S_ChangeMusicInternal("gmover", false); // Yousa dead now, Okieday? Tails 03-14-2000 - } - } - } - target->player->playerstate = PST_DEAD; if (target->player == &players[consoleplayer]) @@ -3153,6 +3232,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da || inflictor->type == MT_SMK_THWOMP || inflictor->player)) { player->kartstuff[k_sneakertimer] = 0; + player->kartstuff[k_numsneakers] = 0; K_SpinPlayer(player, source, 1, inflictor, false); K_KartPainEnergyFling(player); diff --git a/src/p_map.c b/src/p_map.c index 00d93efe7..4ef258ebb 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -324,39 +324,10 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object) // This makes it a bit more interesting & unique than just being a speed boost in a pre-defined direction if (savemomx || savemomy) { - angle_t momang; - INT32 angoffset; - boolean subtract = false; - - momang = R_PointToAngle2(0, 0, savemomx, savemomy); - - angoffset = momang; - angoffset -= spring->angle; // Subtract - - // Flip on wrong side - if ((angle_t)angoffset > ANGLE_180) - { - angoffset = InvAngle((angle_t)angoffset); - subtract = !subtract; - } - - // Fix going directly against the spring's angle sending you the wrong way - if ((spring->angle - momang) > ANGLE_90) - angoffset = ANGLE_180 - angoffset; - - // Offset is reduced to cap it (90 / 2 = max of 45 degrees) - angoffset /= 2; - - // Reduce further based on how slow your speed is compared to the spring's speed - if (finalSpeed > objectSpeed) - angoffset = FixedDiv(angoffset, FixedDiv(finalSpeed, objectSpeed)); - - if (subtract) - angoffset = (signed)(spring->angle) - angoffset; - else - angoffset = (signed)(spring->angle) + angoffset; - - finalAngle = angoffset; + finalAngle = K_ReflectAngle( + R_PointToAngle2(0, 0, savemomx, savemomy), finalAngle, + objectSpeed, finalSpeed + ); } } diff --git a/src/p_mobj.c b/src/p_mobj.c index d6d7c7661..37df5bb09 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -8369,10 +8369,9 @@ void P_MobjThinker(mobj_t *mobj) if (p) { - if (p->kartstuff[k_sneakertimer] > mobj->movecount - || p->kartstuff[k_levelbooster] > mobj->movecount) + if (p->kartstuff[k_sneakertimer] > mobj->movecount) P_SetMobjState(mobj, S_BOOSTFLAME); - mobj->movecount = max(p->kartstuff[k_sneakertimer], p->kartstuff[k_levelbooster]); + mobj->movecount = p->kartstuff[k_sneakertimer]; } } @@ -11428,9 +11427,6 @@ void P_RemoveSavegameMobj(mobj_t *mobj) P_RemoveThinker((thinker_t *)mobj); } -static CV_PossibleValue_t respawnitemtime_cons_t[] = {{1, "MIN"}, {300, "MAX"}, {0, NULL}}; -consvar_t cv_itemrespawntime = {"respawnitemtime", "2", CV_NETVAR|CV_CHEAT, respawnitemtime_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; -consvar_t cv_itemrespawn = {"respawnitem", "On", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; static CV_PossibleValue_t flagtime_cons_t[] = {{0, "MIN"}, {300, "MAX"}, {0, NULL}}; consvar_t cv_flagtime = {"flagtime", "30", CV_NETVAR|CV_CHEAT|CV_NOSHOWHELP, flagtime_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_suddendeath = {"suddendeath", "Off", CV_NETVAR|CV_CHEAT|CV_NOSHOWHELP, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; @@ -11701,10 +11697,6 @@ void P_RespawnSpecials(void) time = (time * 3) / max(1, mapheaderinfo[gamemap-1]->numlaps); } - // only respawn items when cv_itemrespawn is on - //if (!cv_itemrespawn.value) // TODO: remove this cvar - //return; - // nothing left to respawn? if (iquehead == iquetail) return; diff --git a/src/p_saveg.c b/src/p_saveg.c index 98e8bbb97..8b3f468e7 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -141,6 +141,7 @@ static void P_NetArchivePlayers(void) WRITEFIXED(save_p, players[i].dashspeed); WRITEINT32(save_p, players[i].dashtime); WRITESINT8(save_p, players[i].lives); + WRITEUINT8(save_p, players[i].lostlife); WRITESINT8(save_p, players[i].continues); WRITESINT8(save_p, players[i].xtralife); WRITEUINT8(save_p, players[i].gotcontinue); @@ -291,6 +292,8 @@ static void P_NetArchivePlayers(void) // botvars_t WRITEUINT8(save_p, players[i].botvars.difficulty); + WRITEUINT8(save_p, players[i].botvars.diffincrease); + WRITEUINT8(save_p, players[i].botvars.rival); WRITEUINT32(save_p, players[i].botvars.itemdelay); WRITEUINT32(save_p, players[i].botvars.itemconfirm); WRITESINT8(save_p, players[i].botvars.turnconfirm); @@ -346,6 +349,7 @@ static void P_NetUnArchivePlayers(void) players[i].dashspeed = READFIXED(save_p); // dashing speed players[i].dashtime = READINT32(save_p); // dashing speed players[i].lives = READSINT8(save_p); + players[i].lostlife = (boolean)READUINT8(save_p); players[i].continues = READSINT8(save_p); // continues that player has acquired players[i].xtralife = READSINT8(save_p); // Ring Extra Life counter players[i].gotcontinue = READUINT8(save_p); // got continue from stage @@ -483,6 +487,8 @@ static void P_NetUnArchivePlayers(void) // botvars_t players[i].botvars.difficulty = READUINT8(save_p); + players[i].botvars.diffincrease = READUINT8(save_p); + players[i].botvars.rival = (boolean)READUINT8(save_p); players[i].botvars.itemdelay = READUINT32(save_p); players[i].botvars.itemconfirm = READUINT32(save_p); players[i].botvars.turnconfirm = READSINT8(save_p); diff --git a/src/p_setup.c b/src/p_setup.c index fa86bb33c..7d2f8ab0f 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -88,6 +88,7 @@ #include "k_pwrlv.h" #include "k_waypoint.h" #include "k_bot.h" +#include "k_grandprix.h" // // Map MD5, calculated on level load. @@ -2415,22 +2416,20 @@ static void P_LevelInitStuff(void) for (i = 0; i < MAXPLAYERS; i++) { -#if 0 - if ((netgame || multiplayer) && (gametype == GT_COMPETITION || players[i].lives <= 0)) + if (grandprixinfo.gp == false) { - // In Co-Op, replenish a user's lives if they are depleted. - players[i].lives = cv_startinglives.value; + players[i].lives = 3; + players[i].xtralife = 0; + players[i].totalring = 0; } -#else - players[i].lives = 1; // SRB2Kart -#endif players[i].realtime = racecountdown = exitcountdown = 0; curlap = bestlap = 0; // SRB2Kart + players[i].lostlife = false; players[i].gotcontinue = false; - players[i].xtralife = players[i].deadtimer = players[i].numboxes = players[i].totalring = players[i].laps = 0; + players[i].deadtimer = players[i].numboxes = players[i].laps = 0; players[i].health = 1; players[i].aiming = 0; players[i].pflags &= ~PF_TIMEOVER; @@ -2467,8 +2466,23 @@ static void P_LevelInitStuff(void) } // SRB2Kart: map load variables - if (modeattacking) // Just play it safe and set everything + if (grandprixinfo.gp == true) { + if (G_BattleGametype()) + { + gamespeed = KARTSPEED_EASY; + } + else + { + gamespeed = grandprixinfo.gamespeed; + } + + franticitems = false; + comeback = true; + } + else if (modeattacking) + { + // Just play it safe and set everything gamespeed = KARTSPEED_HARD; franticitems = false; comeback = true; @@ -2493,11 +2507,6 @@ static void P_LevelInitStuff(void) memset(&battleovertime, 0, sizeof(struct battleovertime)); speedscramble = encorescramble = -1; - - if (!modeattacking) - { - K_UpdateMatchRaceBots(); - } } // @@ -2682,12 +2691,6 @@ static void P_ForceCharacter(const char *forcecharskin) } SetPlayerSkin(consoleplayer, forcecharskin); - // normal player colors in single player - if ((unsigned)cv_playercolor.value != skins[players[consoleplayer].skin].prefcolor && !modeattacking) - { - CV_StealthSetValue(&cv_playercolor, skins[players[consoleplayer].skin].prefcolor); - players[consoleplayer].skincolor = skins[players[consoleplayer].skin].prefcolor; - } } } @@ -3384,6 +3387,28 @@ boolean P_SetupLevel(boolean skipprecip) } #endif + // NOW you can try to spawn in the Battle capsules, if there's not enough players for a match + K_SpawnBattleCapsules(); + + if (grandprixinfo.gp == true) + { + if (grandprixinfo.initalize == true) + { + K_InitGrandPrixBots(); + grandprixinfo.initalize = false; + } + else if (grandprixinfo.wonround == true) + { + K_UpdateGrandPrixBots(); + grandprixinfo.wonround = false; + } + } + else if (!modeattacking) + { + // We're in a Match Race, use simplistic randomized bots. + K_UpdateMatchRaceBots(); + } + P_MapEnd(); // Remove the loading shit from the screen @@ -3435,9 +3460,6 @@ boolean P_SetupLevel(boolean skipprecip) #endif } - // NOW you can try to spawn in the Battle capsules, if there's not enough players for a match - K_SpawnBattleCapsules(); - return true; } diff --git a/src/p_spec.c b/src/p_spec.c index 03c08fdee..aa5d52927 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -4052,52 +4052,38 @@ DoneSection2: case 5: // Speed pad w/o spin case 6: // Speed pad w/ spin - if (player->kartstuff[k_dashpadcooldown] != 0) + if (player->kartstuff[k_floorboost] != 0) + { + player->kartstuff[k_floorboost] = 2; break; + } i = P_FindSpecialLineFromTag(4, sector->tag, -1); if (i != -1) { - angle_t lineangle; - fixed_t linespeed; - - lineangle = R_PointToAngle2(lines[i].v1->x, lines[i].v1->y, lines[i].v2->x, lines[i].v2->y); - linespeed = P_AproxDistance(lines[i].v2->x-lines[i].v1->x, lines[i].v2->y-lines[i].v1->y); + angle_t lineangle = R_PointToAngle2(lines[i].v1->x, lines[i].v1->y, lines[i].v2->x, lines[i].v2->y); + fixed_t linespeed = P_AproxDistance(lines[i].v2->x-lines[i].v1->x, lines[i].v2->y-lines[i].v1->y); + fixed_t playerspeed = P_AproxDistance(player->mo->momx, player->mo->momy); // SRB2Kart: Scale the speed you get from them! // This is scaled differently from other horizontal speed boosts from stuff like springs, because of how this is used for some ramp jumps. if (player->mo->scale > mapobjectscale) - linespeed = FixedMul(linespeed, mapobjectscale + (player->mo->scale - mapobjectscale)); - - if (!(lines[i].flags & ML_EFFECT4)) { - P_UnsetThingPosition(player->mo); - if (roversector) // make FOF speed pads work - { - player->mo->x = roversector->soundorg.x; - player->mo->y = roversector->soundorg.y; - } - else - { - player->mo->x = sector->soundorg.x; - player->mo->y = sector->soundorg.y; - } - P_SetThingPosition(player->mo); + linespeed = FixedMul(linespeed, mapobjectscale + (player->mo->scale - mapobjectscale)); } - P_InstaThrust(player->mo, lineangle, linespeed); + lineangle = K_ReflectAngle( + R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy), lineangle, + playerspeed, linespeed + ); + + P_InstaThrust(player->mo, lineangle, max(linespeed, 2*playerspeed)); player->kartstuff[k_dashpadcooldown] = TICRATE/3; player->kartstuff[k_pogospring] = 0; - S_StartSound(player->mo, sfx_spdpad); - - { - sfxenum_t pick = P_RandomKey(2); // Gotta roll the RNG every time this is called for sync reasons - if (cv_kartvoices.value) - S_StartSound(player->mo, sfx_kbost1+pick); - //K_TauntVoiceTimers(player); - } + player->kartstuff[k_floorboost] = 2; + S_StartSound(player->mo, sfx_cdfm62); } break; diff --git a/src/p_user.c b/src/p_user.c index 484b7b8fc..1818f21b7 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -40,7 +40,6 @@ #include "st_stuff.h" #include "lua_script.h" #include "lua_hook.h" -#include "k_bot.h" // Objectplace #include "m_cheat.h" // SRB2kart @@ -49,6 +48,8 @@ #include "k_color.h" // KartColor_Opposite #include "console.h" // CON_LogMessage #include "k_respawn.h" +#include "k_bot.h" +#include "k_grandprix.h" #ifdef HW3SOUND #include "hardware/hw3sound.h" @@ -950,7 +951,6 @@ void P_GivePlayerRings(player_t *player, INT32 num_rings) return; player->kartstuff[k_rings] += num_rings; - //player->totalring += num_rings; // Used for GP lives later if (player->kartstuff[k_rings] > 20) player->kartstuff[k_rings] = 20; // Caps at 20 rings, sorry! @@ -968,8 +968,8 @@ void P_GivePlayerLives(player_t *player, INT32 numlives) { player->lives += numlives; - if (player->lives > 99) - player->lives = 99; + if (player->lives > 9) + player->lives = 9; else if (player->lives < 1) player->lives = 1; } @@ -1687,12 +1687,20 @@ mobj_t *P_SpawnGhostMobj(mobj_t *mobj) // Player exits the map via sector trigger void P_DoPlayerExit(player_t *player) { + const boolean losing = K_IsPlayerLosing(player); + if (player->exiting || mapreset) return; if (P_IsLocalPlayer(player) && (!player->spectator && !demo.playback)) legitimateexit = true; + if (G_GametypeUsesLives() && losing) + { + // Remove a life from the losing player + K_PlayerLoseLife(player); + } + if (G_RaceGametype()) // If in Race Mode, allow { player->exiting = raceexittime+2; @@ -1703,7 +1711,7 @@ void P_DoPlayerExit(player_t *player) if (P_IsDisplayPlayer(player)) { sfxenum_t sfx_id; - if (K_IsPlayerLosing(player)) + if (losing) sfx_id = ((skin_t *)player->mo->skin)->soundsid[S_sfx[sfx_klose].skinsound]; else sfx_id = ((skin_t *)player->mo->skin)->soundsid[S_sfx[sfx_kwin].skinsound]; @@ -1711,7 +1719,7 @@ void P_DoPlayerExit(player_t *player) } else { - if (K_IsPlayerLosing(player)) + if (losing) S_StartSound(player->mo, sfx_klose); else S_StartSound(player->mo, sfx_kwin); @@ -1721,10 +1729,6 @@ void P_DoPlayerExit(player_t *player) if (cv_inttime.value > 0) P_EndingMusic(player); - // SRB2kart 120217 - //if (!exitcountdown) - //exitcountdown = racecountdown + 8*TICRATE; - if (P_CheckRacers()) player->exiting = raceexittime+1; } @@ -1736,24 +1740,44 @@ void P_DoPlayerExit(player_t *player) else player->exiting = raceexittime+2; // Accidental death safeguard??? - //player->pflags &= ~PF_GLIDING; - /* // SRB2kart - don't need - if (player->climbing) + if (grandprixinfo.gp == true) { - player->climbing = 0; - player->pflags |= PF_JUMPED; - P_SetPlayerMobjState(player->mo, S_PLAY_ATK1); + if (player->bot) + { + // Bots are going to get harder... :) + K_IncreaseBotDifficulty(player); + } + else if (!losing) + { + const UINT8 lifethreshold = 20; + UINT8 extra = 0; + + // YOU WIN + grandprixinfo.wonround = true; + + // Increase your total rings + if (RINGTOTAL(player) > 0) + { + player->totalring += RINGTOTAL(player); + + extra = player->totalring / lifethreshold; + + if (extra > player->xtralife) + { + P_GivePlayerLives(player, extra - player->xtralife); + S_StartSound(NULL, sfx_cdfm73); + player->xtralife = extra; + } + } + } } - */ + player->powers[pw_underwater] = 0; player->powers[pw_spacetime] = 0; player->karthud[khud_cardanimation] = 0; // srb2kart: reset battle animation if (player == &players[consoleplayer]) demo.savebutton = leveltime; - - /*if (playeringame[player-players] && netgame && !circuitmap) - CONS_Printf(M_GetText("%s has completed the level.\n"), player_names[player-players]);*/ } #define SPACESPECIAL 12 @@ -4165,23 +4189,21 @@ static void P_3dMovement(player_t *player) const fixed_t airspeedcap = (50*mapobjectscale); const fixed_t speed = R_PointToDist2(0, 0, player->mo->momx, player->mo->momy); + // If you're going too fast in the air, ease back down to a certain speed. + // Helps lots of jumps from breaking when using speed items, since you can't move in the air. if (speed > airspeedcap) { fixed_t div = 32*FRACUNIT; fixed_t newspeed; + // Make rubberbanding bots slow down faster if (K_PlayerUsesBotMovement(player)) { - fixed_t baserubberband = K_BotRubberband(player); - fixed_t rubberband = FixedMul(baserubberband, - FixedMul(baserubberband, - FixedMul(baserubberband, - baserubberband - ))); // This looks extremely goofy, but we need this really high, but at the same time, proportional. + fixed_t rubberband = K_BotRubberband(player) - FRACUNIT; - if (rubberband > FRACUNIT) + if (rubberband > 0) { - div = FixedMul(div, rubberband); + div = FixedDiv(div, FRACUNIT + (rubberband * 2)); } } @@ -6252,7 +6274,7 @@ static void P_MovePlayer(player_t *player) //////////////////////////// // SRB2kart - Drifting smoke and fire - if ((EITHERSNEAKER(player) || player->kartstuff[k_flamedash]) + if ((player->kartstuff[k_sneakertimer] || player->kartstuff[k_flamedash]) && onground && (leveltime & 1)) K_SpawnBoostTrail(player); @@ -7095,14 +7117,9 @@ static void P_DeathThink(player_t *player) K_KartPlayerHUDUpdate(player); - // Force respawn if idle for more than 30 seconds in shooter modes. - if (player->lives > 0 /*&& leveltime >= starttime*/) // *could* you respawn? + if (player->lives > 0 && !(player->pflags & PF_TIMEOVER) && player->deadtimer > TICRATE) { - // SRB2kart - spawn automatically after 1 second - if (player->deadtimer > ((netgame || multiplayer) - ? cv_respawntime.value*TICRATE - : TICRATE)) // don't let them change it in record attack - player->playerstate = PST_REBORN; + player->playerstate = PST_REBORN; } // Keep time rolling @@ -8356,13 +8373,28 @@ static void P_CalcPostImg(player_t *player) void P_DoTimeOver(player_t *player) { - if (netgame && player->health > 0) + if (player->pflags & PF_TIMEOVER) + { + // NO! Don't do this! + return; + } + + if (P_IsLocalPlayer(player) && !demo.playback) + { + legitimateexit = true; // SRB2kart: losing a race is still seeing it through to the end :p + } + + if (netgame && !player->bot) + { CON_LogMessage(va(M_GetText("%s ran out of time.\n"), player_names[player-players])); + } player->pflags |= PF_TIMEOVER; - if (P_IsLocalPlayer(player) && !demo.playback) - legitimateexit = true; // SRB2kart: losing a race is still seeing it through to the end :p + if (G_GametypeUsesLives()) + { + K_PlayerLoseLife(player); + } if (player->mo) { @@ -8370,8 +8402,6 @@ void P_DoTimeOver(player_t *player) P_DamageMobj(player->mo, NULL, NULL, 10000); } - player->lives = 0; - P_EndingMusic(player); if (!exitcountdown) @@ -8770,7 +8800,7 @@ void P_PlayerThink(player_t *player) { if (playeringame[i] && !players[i].spectator) { - if (!players[i].exiting && players[i].lives > 0) + if (!players[i].exiting && !(players[i].pflags & PF_TIMEOVER) && players[i].lives > 0) break; } } @@ -8778,24 +8808,26 @@ void P_PlayerThink(player_t *player) if (i == MAXPLAYERS && player->exiting == raceexittime+2) // finished player->exiting = raceexittime+1; +#if 0 // If 10 seconds are left on the timer, // begin the drown music for countdown! - - // SRB2Kart: despite how perfect this is, it's disabled FOR A REASON - /*if (racecountdown == 11*TICRATE - 1) + if (racecountdown == 11*TICRATE - 1) { if (P_IsLocalPlayer(player)) S_ChangeMusicInternal("drown", false); - }*/ + } +#endif // If you've hit the countdown and you haven't made // it to the exit, you're a goner! - else if (racecountdown == 1 && !player->exiting && !player->spectator && player->lives > 0) + if (racecountdown == 1 && !player->spectator && !player->exiting && !(player->pflags & PF_TIMEOVER) && player->lives > 0) { P_DoTimeOver(player); if (player->playerstate == PST_DEAD) + { return; + } } } @@ -8809,33 +8841,9 @@ void P_PlayerThink(player_t *player) if (player->exiting == 2 || exitcountdown == 2) { - if (cv_playersforexit.value) // Count to be sure everyone's exited + if (server) { - INT32 i; - - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator || players[i].bot) - continue; - if (players[i].lives <= 0) - continue; - - if (!players[i].exiting || players[i].exiting > 3) - break; - } - - if (i == MAXPLAYERS) - { - if (server) - SendNetXCmd(XD_EXITLEVEL, NULL, 0); - } - else - player->exiting = 3; - } - else - { - if (server) - SendNetXCmd(XD_EXITLEVEL, NULL, 0); + SendNetXCmd(XD_EXITLEVEL, NULL, 0); } } } @@ -8873,17 +8881,6 @@ void P_PlayerThink(player_t *player) player->health = 1; } -#if 0 - if ((netgame || multiplayer) && player->lives <= 0) - { - // In Co-Op, replenish a user's lives if they are depleted. - // of course, this is just a cheap hack, meh... - player->lives = cv_startinglives.value; - } -#else - player->lives = 1; // SRB2Kart -#endif - // SRB2kart 010217 if (leveltime < starttime) { diff --git a/src/r_things.c b/src/r_things.c index 9fcdf81e5..369bdfafe 100644 --- a/src/r_things.c +++ b/src/r_things.c @@ -2925,6 +2925,11 @@ static void Sk_SetDefaultValue(skin_t *skin) strncpy(skin->facewant, "PLAYWANT", 9); strncpy(skin->facemmap, "PLAYMMAP", 9); + for (i = 0; i < SKINRIVALS; i++) + { + strcpy(skin->rivals[i], ""); + } + skin->starttranscolor = 96; skin->prefcolor = SKINCOLOR_GREEN; @@ -3280,6 +3285,45 @@ void R_AddSkins(UINT16 wadnum) strupr(value); strncpy(skin->facemmap, value, sizeof skin->facemmap); } + else if (!stricmp(stoken, "rivals")) + { + size_t len = strlen(value); + size_t i; + char rivalname[SKINNAMESIZE] = ""; + UINT8 pos = 0; + UINT8 numrivals = 0; + + // Can't use strtok, because this function's already using it. + // Using it causes it to upset the saved pointer, + // corrupting the reading for the rest of the file. + + // So instead we get to crawl through the value, character by character, + // and write it down as we go, until we hit a comma or the end of the string. + // Yaaay. + + for (i = 0; i <= len; i++) + { + if (numrivals >= SKINRIVALS) + { + break; + } + + if (value[i] == ',' || i == len) + { + STRBUFCPY(skin->rivals[numrivals], rivalname); + strlwr(skin->rivals[numrivals]); + numrivals++; + + memset(rivalname, 0, sizeof (rivalname)); + pos = 0; + + continue; + } + + rivalname[pos] = value[i]; + pos++; + } + } #define FULLPROCESS(field) else if (!stricmp(stoken, #field)) skin->field = get_number(value); // character type identification diff --git a/src/r_things.h b/src/r_things.h index fc2cae479..a42e1e891 100644 --- a/src/r_things.h +++ b/src/r_things.h @@ -67,6 +67,7 @@ void R_DrawMasked(void); // SKINS STUFF // ----------- #define SKINNAMESIZE 16 +#define SKINRIVALS 3 // should be all lowercase!! S_SKIN processing does a strlwr #define DEFAULTSKIN "sonic" #define DEFAULTSKIN2 "tails" // secondary player @@ -95,6 +96,8 @@ typedef struct UINT8 prefcolor; fixed_t highresscale; // scale of highres, default is 0.5 + char rivals[SKINRIVALS][SKINNAMESIZE+1]; // Your top 3 rivals for GP mode. Uses names so that you can reference skins that aren't added + // specific sounds per skin sfxenum_t soundsid[NUMSKINSOUNDS]; // sound # in S_sfx table } skin_t; diff --git a/src/sdl/Srb2SDL-vc10.vcxproj b/src/sdl/Srb2SDL-vc10.vcxproj index 69b3465ac..895a21df4 100644 --- a/src/sdl/Srb2SDL-vc10.vcxproj +++ b/src/sdl/Srb2SDL-vc10.vcxproj @@ -231,6 +231,7 @@ + @@ -376,6 +377,7 @@ + diff --git a/src/sdl/Srb2SDL-vc9.vcproj b/src/sdl/Srb2SDL-vc9.vcproj index b21eedb87..85c2533ea 100644 --- a/src/sdl/Srb2SDL-vc9.vcproj +++ b/src/sdl/Srb2SDL-vc9.vcproj @@ -2090,6 +2090,50 @@ RelativePath="..\console.h" > + + + + + + + + + + + + + + + + diff --git a/src/sdl12/Srb2SDL-vc10.vcxproj b/src/sdl12/Srb2SDL-vc10.vcxproj index d7f13751e..1edb3d2db 100644 --- a/src/sdl12/Srb2SDL-vc10.vcxproj +++ b/src/sdl12/Srb2SDL-vc10.vcxproj @@ -655,6 +655,16 @@ %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) + + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) @@ -1366,6 +1376,7 @@ + diff --git a/src/sdl12/Srb2SDL-vc9.vcproj b/src/sdl12/Srb2SDL-vc9.vcproj index 9d807be21..16c00454d 100644 --- a/src/sdl12/Srb2SDL-vc9.vcproj +++ b/src/sdl12/Srb2SDL-vc9.vcproj @@ -2090,6 +2090,50 @@ RelativePath="..\console.h" > + + + + + + + + + + + + + + + + diff --git a/src/st_stuff.h b/src/st_stuff.h index 5ed5dd1c6..3c8c2902f 100644 --- a/src/st_stuff.h +++ b/src/st_stuff.h @@ -65,7 +65,6 @@ extern INT32 st_palette; // 0 is default, any others are special palettes. extern lumpnum_t st_borderpatchnum; // patches, also used in intermission -extern patch_t *tallnum[10]; extern patch_t *sboscore; extern patch_t *sbotime; extern patch_t *sbocolon; diff --git a/src/v_video.c b/src/v_video.c index e5fed2a16..20931291f 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -1582,17 +1582,17 @@ void V_DrawCharacter(INT32 x, INT32 y, INT32 c, boolean lowercaseallowed) c -= HU_FONTSTART; else c = toupper(c) - HU_FONTSTART; - if (c < 0 || c >= HU_FONTSIZE || !hu_font[c]) + if (c < 0 || c >= HU_FONTSIZE || !fontv[HU_FONT].font[c]) return; - w = SHORT(hu_font[c]->width); + w = SHORT(fontv[HU_FONT].font[c]->width); if (x + w > vid.width) return; if (colormap != NULL) - V_DrawMappedPatch(x, y, flags, hu_font[c], colormap); + V_DrawMappedPatch(x, y, flags, fontv[HU_FONT].font[c], colormap); else - V_DrawScaledPatch(x, y, flags, hu_font[c]); + V_DrawScaledPatch(x, y, flags, fontv[HU_FONT].font[c]); } // Writes a single character for the chat (half scaled). (draw WHITE if bit 7 set) @@ -1609,14 +1609,14 @@ void V_DrawChatCharacter(INT32 x, INT32 y, INT32 c, boolean lowercaseallowed, UI c -= HU_FONTSTART; else c = toupper(c) - HU_FONTSTART; - if (c < 0 || c >= HU_FONTSIZE || !hu_font[c]) + if (c < 0 || c >= HU_FONTSIZE || !fontv[HU_FONT].font[c]) return; - w = SHORT(hu_font[c]->width)/2; + w = SHORT(fontv[HU_FONT].font[c]->width)/2; if (x + w > vid.width) return; - V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT/2, flags, hu_font[c], colormap); + V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT/2, flags, fontv[HU_FONT].font[c], colormap); } // Precompile a wordwrapped string to any given width. @@ -1667,13 +1667,13 @@ char *V_WordWrap(INT32 x, INT32 w, INT32 option, const char *string) c = toupper(c); c -= HU_FONTSTART; - if (c < 0 || c >= HU_FONTSIZE || !hu_font[c]) + if (c < 0 || c >= HU_FONTSIZE || !fontv[HU_FONT].font[c]) { chw = spacewidth; lastusablespace = i; } else - chw = (charwidth ? charwidth : hu_font[c]->width); + chw = (charwidth ? charwidth : fontv[HU_FONT].font[c]->width); x += chw; @@ -1688,214 +1688,297 @@ char *V_WordWrap(INT32 x, INT32 w, INT32 option, const char *string) return newstring; } -// -// Write a string using the hu_font -// NOTE: the text is centered for screens larger than the base width -// -void V_DrawString(INT32 x, INT32 y, INT32 option, const char *string) +static inline fixed_t FixedCharacterDim( + fixed_t scale, + fixed_t chw, + INT32 hchw, + INT32 dupx, + fixed_t * cwp) { - INT32 w, c, cx = x, cy = y, dupx, dupy, scrwidth, center = 0, left = 0; - const char *ch = string; - INT32 charflags = 0; - const UINT8 *colormap = NULL; - INT32 spacewidth = 4, charwidth = 0; - - INT32 lowercase = (option & V_ALLOWLOWERCASE); - option &= ~V_FLIP; // which is also shared with V_ALLOWLOWERCASE... - - if (option & V_NOSCALESTART) - { - dupx = vid.dupx; - dupy = vid.dupy; - scrwidth = vid.width; - } - else - { - dupx = dupy = 1; - scrwidth = vid.width/vid.dupx; - if (!(option & V_SNAPTOLEFT)) - { - left = (scrwidth - BASEVIDWIDTH)/2; - scrwidth -= left; - } - } - - charflags = (option & V_CHARCOLORMASK); - colormap = V_GetStringColormap(charflags); - - switch (option & V_SPACINGMASK) - { - case V_MONOSPACE: - spacewidth = 8; - /* FALLTHRU */ - case V_OLDSPACING: - charwidth = 8; - break; - case V_6WIDTHSPACE: - spacewidth = 6; - default: - break; - } - - for (;;ch++) - { - if (!*ch) - break; - if (*ch & 0x80) //color parsing -x 2.16.09 - { - // manually set flags override color codes - if (!(option & V_CHARCOLORMASK)) - { - charflags = ((*ch & 0x7f) << V_CHARCOLORSHIFT) & V_CHARCOLORMASK; - colormap = V_GetStringColormap(charflags); - } - continue; - } - if (*ch == '\n') - { - cx = x; - - if (option & V_RETURN8) - cy += 8*dupy; - else - cy += 12*dupy; - - continue; - } - - c = *ch; - if (!lowercase) - c = toupper(c); - c -= HU_FONTSTART; - - // character does not exist or is a space - if (c < 0 || c >= HU_FONTSIZE || !hu_font[c]) - { - cx += spacewidth * dupx; - continue; - } - - if (charwidth) - { - w = charwidth * dupx; - center = w/2 - SHORT(hu_font[c]->width)*dupx/2; - } - else - w = SHORT(hu_font[c]->width) * dupx; - - if (cx > scrwidth) - break; - if (cx+left + w < 0) //left boundary check - { - cx += w; - continue; - } - - V_DrawFixedPatch((cx + center)<> 1 ); + (*cwp) = chw; + return FixedMul (( cxoff * dupx )<< FRACBITS, scale); +} - if (option & V_NOSCALESTART) +static inline fixed_t BunchedCharacterDim( + fixed_t scale, + fixed_t chw, + INT32 hchw, + INT32 dupx, + fixed_t * cwp) +{ + (void)chw; + (void)hchw; + (void)dupx; + (*cwp) = FixedMul (max (1, (*cwp) - 1) << FRACBITS, scale); + return 0; +} + +void V_DrawStringScaled( + fixed_t x, + fixed_t y, + fixed_t scale, + fixed_t spacescale, + fixed_t lfscale, + INT32 flags, + int fontno, + const char *s) +{ + fixed_t chw; + INT32 hchw;/* half-width for centering */ + fixed_t spacew; + fixed_t lfh; + + INT32 dupx; + + fixed_t right; + fixed_t bot; + + fixed_t (*dim_fn)(fixed_t,fixed_t,INT32,INT32,fixed_t *); + + font_t *font; + + boolean uppercase; + boolean notcolored; + + const UINT8 *colormap; + + fixed_t cx, cy; + + fixed_t cxoff; + fixed_t cw; + + INT32 spacing; + fixed_t left; + + int c; + + uppercase = !( flags & V_ALLOWLOWERCASE ); + flags &= ~(V_FLIP);/* These two (V_ALLOWLOWERCASE) share a bit. */ + + colormap = V_GetStringColormap(( flags & V_CHARCOLORMASK )); + notcolored = !colormap; + + font = &fontv[fontno]; + + chw = 0; + + spacing = ( flags & V_SPACINGMASK ); + + /* + Hardcoded until a better system can be implemented + for determining how fonts space. + */ + switch (fontno) { - dupx = vid.dupx; - dupy = vid.dupy; - scrwidth = vid.width; + default: + case HU_FONT: + spacew = 4; + switch (spacing) + { + case V_MONOSPACE: + spacew = 8; + /* FALLTHRU */ + case V_OLDSPACING: + chw = 8; + break; + case V_6WIDTHSPACE: + spacew = 6; + } + break; + case TINY_FONT: + spacew = 2; + switch (spacing) + { + case V_MONOSPACE: + spacew = 5; + /* FALLTHRU */ + case V_OLDSPACING: + chw = 5; + break; + // Out of video flags, so we're reusing this for alternate charwidth instead + /*case V_6WIDTHSPACE: + spacewidth = 3;*/ + } + break; + case KART_FONT: + spacew = 12; + switch (spacing) + { + case V_MONOSPACE: + spacew = 12; + /* FALLTHRU */ + case V_OLDSPACING: + chw = 12; + break; + case V_6WIDTHSPACE: + spacew = 6; + } + break; + case LT_FONT: + spacew = 12; + break; + case CRED_FONT: + spacew = 16; + break; + } + switch (fontno) + { + default: + case HU_FONT: + case TINY_FONT: + case KART_FONT: + if (( flags & V_RETURN8 )) + lfh = 8; + else + lfh = 12; + break; + case LT_FONT: + case CRED_FONT: + lfh = 12; + break; + } + + hchw = chw >> 1; + + chw <<= FRACBITS; + spacew <<= FRACBITS; + +#define Mul( id, scale ) ( id = FixedMul (scale, id) ) + Mul (chw, scale); + Mul (spacew, scale); + Mul (lfh, scale); + + Mul (spacew, spacescale); + Mul (lfh, lfscale); +#undef Mul + + if (( flags & V_NOSCALESTART )) + { + dupx = vid.dupx; + + hchw *= dupx; + + chw *= dupx; + spacew *= dupx; + lfh *= vid.dupy; + + right = vid.width; } else { - dupx = dupy = 1; - scrwidth = vid.width/vid.dupx; - left = (scrwidth - BASEVIDWIDTH)/2; + dupx = 1; + + right = ( vid.width / vid.dupx ); + if (!( flags & V_SNAPTOLEFT )) + { + left = ( right - BASEVIDWIDTH )/ 2;/* left edge of drawable area */ + x = ( left << FRACBITS )+ x; + right -= left; + } } - charflags = (option & V_CHARCOLORMASK); - colormap = V_GetStringColormap(charflags); + right <<= FRACBITS; + bot = vid.height << FRACBITS; - switch (option & V_SPACINGMASK) + if (fontno == TINY_FONT) { - case V_MONOSPACE: - spacewidth = 12; - /* FALLTHRU */ - case V_OLDSPACING: - charwidth = 12; - break; - case V_6WIDTHSPACE: - spacewidth = 6; - default: - break; - } - - for (;;ch++) - { - if (!*ch) - break; - if (*ch & 0x80) //color parsing -x 2.16.09 - { - // manually set flags override color codes - if (!(option & V_CHARCOLORMASK)) - { - charflags = ((*ch & 0x7f) << V_CHARCOLORSHIFT) & V_CHARCOLORMASK; - colormap = V_GetStringColormap(charflags); - } - continue; - } - if (*ch == '\n') - { - cx = x; - - if (option & V_RETURN8) - cy += 8*dupy; - else - cy += 12*dupy; - - continue; - } - - c = *ch; - if (!lowercase) - c = toupper(c); - c -= KART_FONTSTART; - - // character does not exist or is a space - if (c < 0 || c >= KART_FONTSIZE || !kart_font[c]) - { - cx += spacewidth * dupx; - continue; - } - - if (charwidth) - { - w = charwidth * dupx; - center = w/2 - SHORT(kart_font[c]->width)*dupx/2; - } + if (chw) + dim_fn = FixedCharacterDim; else - w = SHORT(kart_font[c]->width) * dupx; - - if (cx > scrwidth) - break; - if (cx+left + w < 0) //left boundary check { - cx += w; - continue; + /* Reuse this flag for the alternate bunched-up spacing. */ + if (( flags & V_6WIDTHSPACE )) + dim_fn = BunchedCharacterDim; + else + dim_fn = VariableCharacterDim; } + } + else + { + if (chw) + dim_fn = CenteredCharacterDim; + else + dim_fn = VariableCharacterDim; + } - V_DrawFixedPatch((cx + center)<= bot) + return; + cx = x; + break; + default: + if (( c & 0x80 )) + { + if (notcolored) + { + colormap = V_GetStringColormap( + ( ( c & 0x7f )<< V_CHARCOLORSHIFT )& + V_CHARCOLORMASK); + } + } + else if (cx < right) + { + if (uppercase) + c = toupper(c); + + c -= font->start; + if (c >= 0 && c < font->size && font->font[c]) + { + cw = SHORT (font->font[c]->width) * dupx; + cxoff = (*dim_fn)(scale, chw, hchw, dupx, &cw); + V_DrawFixedPatch(cx + cxoff, cy, scale, + flags, font->font[c], colormap); + cx += cw; + } + else + cx += spacew; + } + } } } // @@ -1912,220 +1995,12 @@ void V_DrawRightAlignedString(INT32 x, INT32 y, INT32 option, const char *string V_DrawString(x, y, option, string); } -// -// Write a string using the hu_font, 0.5x scale -// NOTE: the text is centered for screens larger than the base width -// -void V_DrawSmallString(INT32 x, INT32 y, INT32 option, const char *string) -{ - INT32 w, c, cx = x, cy = y, dupx, dupy, scrwidth, center = 0, left = 0; - const char *ch = string; - INT32 charflags = 0; - const UINT8 *colormap = NULL; - INT32 spacewidth = 2, charwidth = 0; - - INT32 lowercase = (option & V_ALLOWLOWERCASE); - option &= ~V_FLIP; // which is also shared with V_ALLOWLOWERCASE... - - if (option & V_NOSCALESTART) - { - dupx = vid.dupx; - dupy = vid.dupy; - scrwidth = vid.width; - } - else - { - dupx = dupy = 1; - scrwidth = vid.width/vid.dupx; - left = (scrwidth - BASEVIDWIDTH)/2; - scrwidth -= left; - } - - charflags = (option & V_CHARCOLORMASK); - colormap = V_GetStringColormap(charflags); - - switch (option & V_SPACINGMASK) - { - case V_MONOSPACE: - spacewidth = 4; - /* FALLTHRU */ - case V_OLDSPACING: - charwidth = 4; - break; - case V_6WIDTHSPACE: - spacewidth = 3; - default: - break; - } - - for (;;ch++) - { - if (!*ch) - break; - if (*ch & 0x80) //color parsing -x 2.16.09 - { - // manually set flags override color codes - if (!(option & V_CHARCOLORMASK)) - { - charflags = ((*ch & 0x7f) << V_CHARCOLORSHIFT) & V_CHARCOLORMASK; - colormap = V_GetStringColormap(charflags); - } - continue; - } - if (*ch == '\n') - { - cx = x; - - if (option & V_RETURN8) - cy += 4*dupy; - else - cy += 6*dupy; - - continue; - } - - c = *ch; - if (!lowercase) - c = toupper(c); - c -= HU_FONTSTART; - - if (c < 0 || c >= HU_FONTSIZE || !hu_font[c]) - { - cx += spacewidth * dupx; - continue; - } - - if (charwidth) - { - w = charwidth * dupx; - center = w/2 - SHORT(hu_font[c]->width)*dupx/4; - } - else - w = SHORT(hu_font[c]->width) * dupx / 2; - if (cx > scrwidth) - break; - if (cx+left + w < 0) //left boundary check - { - cx += w; - continue; - } - - V_DrawFixedPatch((cx + center)<= HU_FONTSIZE || !tny_font[c]) - { - cx += spacewidth * dupx; - continue; - } - - if (charwidth) - w = charwidth * dupx; - else - w = ((option & V_6WIDTHSPACE ? max(1, SHORT(tny_font[c]->width)-1) // Reuse this flag for the alternate bunched-up spacing - : SHORT(tny_font[c]->width)) * dupx); - - if (cx > scrwidth) - break; - if (cx+left + w < 0) //left boundary check - { - cx += w; - continue; - } - - V_DrawFixedPatch(cx<= HU_FONTSIZE || !hu_font[c]) - { - cx += (spacewidth * dupx)<width)*(dupx/2); - } - else - w = SHORT(hu_font[c]->width) * dupx; - - if ((cx>>FRACBITS) > scrwidth) - break; - if ((cx>>FRACBITS)+left + w < 0) //left boundary check - { - cx += w<width); + INT32 w = SHORT(fontv[TALLNUM_FONT].font[0]->width); boolean neg; if (flags & V_NOSCALESTART) @@ -2245,7 +2029,7 @@ void V_DrawTallNum(INT32 x, INT32 y, INT32 flags, INT32 num) do { x -= w; - V_DrawScaledPatch(x, y, flags, tallnum[num % 10]); + V_DrawScaledPatch(x, y, flags, fontv[TALLNUM_FONT].font[num % 10]); num /= 10; } while (num); @@ -2258,7 +2042,7 @@ void V_DrawTallNum(INT32 x, INT32 y, INT32 flags, INT32 num) // Does not handle negative numbers in a special way, don't try to feed it any. void V_DrawPaddedTallNum(INT32 x, INT32 y, INT32 flags, INT32 num, INT32 digits) { - INT32 w = SHORT(tallnum[0]->width); + INT32 w = SHORT(fontv[TALLNUM_FONT].font[0]->width); if (flags & V_NOSCALESTART) w *= vid.dupx; @@ -2270,7 +2054,7 @@ void V_DrawPaddedTallNum(INT32 x, INT32 y, INT32 flags, INT32 num, INT32 digits) do { x -= w; - V_DrawScaledPatch(x, y, flags, tallnum[num % 10]); + V_DrawScaledPatch(x, y, flags, fontv[TALLNUM_FONT].font[num % 10]); num /= 10; } while (--digits); } @@ -2280,7 +2064,7 @@ void V_DrawPaddedTallNum(INT32 x, INT32 y, INT32 flags, INT32 num, INT32 digits) void V_DrawPingNum(INT32 x, INT32 y, INT32 flags, INT32 num, const UINT8 *colormap) { - INT32 w = SHORT(pingnum[0]->width); // this SHOULD always be 5 but I guess custom graphics exist. + INT32 w = SHORT(fontv[PINGNUM_FONT].font[0]->width); // this SHOULD always be 5 but I guess custom graphics exist. if (flags & V_NOSCALESTART) w *= vid.dupx; @@ -2292,61 +2076,11 @@ void V_DrawPingNum(INT32 x, INT32 y, INT32 flags, INT32 num, const UINT8 *colorm do { x -= (w-1); // Oni wanted their outline to intersect. - V_DrawFixedPatch(x<= CRED_FONTSIZE) - { - cx += (16*dupx)<width) * dupx; - if ((cx>>FRACBITS) > scrwidth) - break; - - V_DrawSciencePatch(cx, cy, option, cred_font[c], FRACUNIT); - cx += w<= CRED_FONTSIZE) w += 16; else - w += SHORT(cred_font[c]->width); + w += SHORT(fontv[CRED_FONT].font[c]->width); } return w; } -// Write a string using the level title font -// NOTE: the text is centered for screens larger than the base width -// -void V_DrawLevelTitle(INT32 x, INT32 y, INT32 option, const char *string) -{ - INT32 w, c, cx = x, cy = y, dupx, dupy, scrwidth, left = 0; - const char *ch = string; - - if (option & V_NOSCALESTART) - { - dupx = vid.dupx; - dupy = vid.dupy; - scrwidth = vid.width; - } - else - { - dupx = dupy = 1; - scrwidth = vid.width/vid.dupx; - left = (scrwidth - BASEVIDWIDTH)/2; - scrwidth -= left; - } - - for (;;) - { - c = *ch++; - if (!c) - break; - if (c == '\n') - { - cx = x; - cy += 12*dupy; - continue; - } - - c = toupper(c) - LT_FONTSTART; - if (c < 0 || c >= LT_FONTSIZE || !lt_font[c]) - { - cx += 12*dupx; - continue; - } - - w = SHORT(lt_font[c]->width) * dupx; - if (cx > scrwidth) - break; - if (cx+left + w < 0) //left boundary check - { - cx += w; - continue; - } - - V_DrawScaledPatch(cx, cy, option, lt_font[c]); - cx += w; - } -} - // Find string width from lt_font chars // INT32 V_LevelNameWidth(const char *string) @@ -2435,10 +2114,10 @@ INT32 V_LevelNameWidth(const char *string) for (i = 0; i < strlen(string); i++) { c = toupper(string[i]) - LT_FONTSTART; - if (c < 0 || c >= LT_FONTSIZE || !lt_font[c]) + if (c < 0 || c >= LT_FONTSIZE || !fontv[LT_FONT].font[c]) w += 12; else - w += SHORT(lt_font[c]->width); + w += SHORT(fontv[LT_FONT].font[c]->width); } return w; @@ -2454,11 +2133,11 @@ INT32 V_LevelNameHeight(const char *string) for (i = 0; i < strlen(string); i++) { c = toupper(string[i]) - LT_FONTSTART; - if (c < 0 || c >= LT_FONTSIZE || !lt_font[c]) + if (c < 0 || c >= LT_FONTSIZE || !fontv[LT_FONT].font[c]) continue; - if (SHORT(lt_font[c]->height) > w) - w = SHORT(lt_font[c]->height); + if (SHORT(fontv[LT_FONT].font[c]->height) > w) + w = SHORT(fontv[LT_FONT].font[c]->height); } return w; @@ -2494,10 +2173,10 @@ INT32 V_StringWidth(const char *string, INT32 option) continue; c = toupper(c) - HU_FONTSTART; - if (c < 0 || c >= HU_FONTSIZE || !hu_font[c]) + if (c < 0 || c >= HU_FONTSIZE || !fontv[HU_FONT].font[c]) w += spacewidth; else - w += (charwidth ? charwidth : SHORT(hu_font[c]->width)); + w += (charwidth ? charwidth : SHORT(fontv[HU_FONT].font[c]->width)); } return w; @@ -2533,10 +2212,10 @@ INT32 V_SmallStringWidth(const char *string, INT32 option) continue; c = toupper(c) - HU_FONTSTART; - if (c < 0 || c >= HU_FONTSIZE || !hu_font[c]) + if (c < 0 || c >= HU_FONTSIZE || !fontv[HU_FONT].font[c]) w += spacewidth; else - w += (charwidth ? charwidth : SHORT(hu_font[c]->width)/2); + w += (charwidth ? charwidth : SHORT(fontv[HU_FONT].font[c]->width)/2); } return w; @@ -2573,17 +2252,17 @@ INT32 V_ThinStringWidth(const char *string, INT32 option) if ((UINT8)c >= 0x80 && (UINT8)c <= 0x8F) //color parsing! -Inuyasha 2.16.09 continue; - if (!lowercase || !tny_font[c-HU_FONTSTART]) + if (!lowercase || !fontv[TINY_FONT].font[c-HU_FONTSTART]) c = toupper(c); c -= HU_FONTSTART; - if (c < 0 || c >= HU_FONTSIZE || !tny_font[c]) + if (c < 0 || c >= HU_FONTSIZE || !fontv[TINY_FONT].font[c]) w += spacewidth; else { w += (charwidth ? charwidth - : ((option & V_6WIDTHSPACE && i < strlen(string)-1) ? max(1, SHORT(tny_font[c]->width)-1) // Reuse this flag for the alternate bunched-up spacing - : SHORT(tny_font[c]->width))); + : ((option & V_6WIDTHSPACE && i < strlen(string)-1) ? max(1, SHORT(fontv[TINY_FONT].font[c]->width)-1) // Reuse this flag for the alternate bunched-up spacing + : SHORT(fontv[TINY_FONT].font[c]->width))); } } diff --git a/src/v_video.h b/src/v_video.h index b3de62c92..2066d1399 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -173,6 +173,12 @@ void V_DrawCustomFadeScreen(const char *lump, UINT8 strength); void V_DrawFadeConsBack(INT32 plines); void V_EncoreInvertScreen(void); +/* Convenience macros for leagacy string function macros. */ +#define V__DrawOneScaleString( x,y,scale,option,font,string ) \ + V_DrawStringScaled(x,y,scale,FRACUNIT,FRACUNIT,option,font,string) +#define V__DrawDupxString( x,y,scale,option,font,string )\ + V__DrawOneScaleString ((x)<>1,option,HU_FONT,string) void V_DrawRightAlignedSmallString(INT32 x, INT32 y, INT32 option, const char *string); // draw a string using the tny_font -void V_DrawThinString(INT32 x, INT32 y, INT32 option, const char *string); +#define V_DrawThinString( x,y,option,string ) \ + V__DrawDupxString (x,y,FRACUNIT,option,TINY_FONT,string) void V_DrawCenteredThinString(INT32 x, INT32 y, INT32 option, const char *string); void V_DrawRightAlignedThinString(INT32 x, INT32 y, INT32 option, const char *string); -void V_DrawStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string); +#define V_DrawStringAtFixed( x,y,option,string ) \ + V__DrawOneScaleString (x,y,FRACUNIT,option,HU_FONT,string) + +#define V_DrawThinStringAtFixed( x,y,option,string ) \ + V__DrawOneScaleString (x,y,FRACUNIT,option,TINY_FONT,string) // Draw tall nums, used for menu, HUD, intermission void V_DrawTallNum(INT32 x, INT32 y, INT32 flags, INT32 num); @@ -214,7 +240,8 @@ void V_DrawPingNum(INT32 x, INT32 y, INT32 flags, INT32 num, const UINT8 *colorm INT32 V_LevelNameWidth(const char *string); INT32 V_LevelNameHeight(const char *string); -void V_DrawCreditString(fixed_t x, fixed_t y, INT32 option, const char *string); +#define V_DrawCreditString( x,y,option,string ) \ + V__DrawOneScaleString (x,y,FRACUNIT,option,CRED_FONT,string) INT32 V_CreditStringWidth(const char *string); // Find string width from hu_font chars diff --git a/src/win32/Srb2win-vc10.vcxproj b/src/win32/Srb2win-vc10.vcxproj index 65500d9bc..698efa09b 100644 --- a/src/win32/Srb2win-vc10.vcxproj +++ b/src/win32/Srb2win-vc10.vcxproj @@ -223,6 +223,20 @@ + + + + + + + + + + + + + + @@ -376,6 +390,21 @@ + + + + + + + + + + + + + + + diff --git a/src/win32/Srb2win-vc9.vcproj b/src/win32/Srb2win-vc9.vcproj index cac081ad4..2bf2d6631 100644 --- a/src/win32/Srb2win-vc9.vcproj +++ b/src/win32/Srb2win-vc9.vcproj @@ -1831,6 +1831,50 @@ RelativePath="..\console.h" > + + + + + + + + + + + + + + + + diff --git a/src/y_inter.c b/src/y_inter.c index 94e24aed1..c216497af 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -43,6 +43,7 @@ #include "k_pwrlv.h" #include "console.h" // cons_menuhighlight #include "lua_hook.h" // IntermissionThinker hook +#include "k_grandprix.h" #ifdef HWRENDER #include "hardware/hw_main.h" @@ -198,7 +199,12 @@ static void Y_CompareScore(INT32 i) static void Y_CompareRank(INT32 i) { INT16 increase = ((data.match.increase[i] == INT16_MIN) ? 0 : data.match.increase[i]); - UINT32 score = (powertype != -1 ? clientpowerlevels[i][powertype] : players[i].score); + UINT32 score = players[i].score; + + if (powertype != PWRLV_DISABLED) + { + score = clientpowerlevels[i][powertype]; + } if (!(data.match.val[data.match.numplayers] == UINT32_MAX || (score - increase) > data.match.val[data.match.numplayers])) return; @@ -302,18 +308,26 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) data.match.name[data.match.numplayers] = player_names[i]; if (data.match.numplayers && (data.match.val[data.match.numplayers] == data.match.val[data.match.numplayers-1])) - data.match.pos[data.match.numplayers] = data.match.pos[data.match.numplayers-1]; - else - data.match.pos[data.match.numplayers] = data.match.numplayers+1; - - if ((!rankingsmode && powertype == -1) // Single player rankings (grand prix). Online rank is handled below. - && !(players[i].pflags & PF_TIMEOVER) && (data.match.pos[data.match.numplayers] < (numplayersingame + numgriefers))) { - data.match.increase[i] = (numplayersingame + numgriefers) - data.match.pos[data.match.numplayers]; + data.match.pos[data.match.numplayers] = data.match.pos[data.match.numplayers-1]; + } + else + { + data.match.pos[data.match.numplayers] = data.match.numplayers+1; + } + + if ((powertype == PWRLV_DISABLED) + && (!rankingsmode) + && !(players[i].pflags & PF_TIMEOVER) + && (data.match.pos[data.match.numplayers] < (numplayersingame + numgriefers))) + { + // Online rank is handled further below in this file. + data.match.increase[i] = K_CalculateGPRankPoints(data.match.pos[data.match.numplayers], numplayersingame + numgriefers); players[i].score += data.match.increase[i]; } if (demo.recording && !rankingsmode) + { G_WriteStanding( data.match.pos[data.match.numplayers], data.match.name[data.match.numplayers], @@ -321,6 +335,7 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) *data.match.color[data.match.numplayers], data.match.val[data.match.numplayers] ); + } data.match.numplayers++; } @@ -440,9 +455,36 @@ void Y_IntermissionDrawer(void) int y2; if (data.match.rankingsmode) - timeheader = (powertype != -1 ? "PWR.LV" : "RANK"); + { + if (powertype == PWRLV_DISABLED) + { + timeheader = "RANK"; + } + else + { + timeheader = "PWR.LV"; + } + } else - timeheader = ((intertype == int_race || (intertype == int_match && battlecapsules)) ? "TIME" : "SCORE"); + { + switch (intertype) + { + default: + case int_race: + timeheader = "TIME"; + break; + case int_match: + if (battlecapsules) + { + timeheader = "TIME"; + } + else + { + timeheader = "SCORE"; + } + break; + } + } // draw the level name V_DrawCenteredString(-4 + x + BASEVIDWIDTH/2, 12, 0, data.match.levelstring); @@ -533,8 +575,11 @@ void Y_IntermissionDrawer(void) if (data.match.rankingsmode) { - if (powertype != -1 && !clientpowerlevels[data.match.num[i]][powertype]) // No power level (splitscreen guests) + if (powertype != PWRLV_DISABLED && !clientpowerlevels[data.match.num[i]][powertype]) + { + // No power level (splitscreen guests) STRBUFCPY(strtime, "----"); + } else { if (data.match.increase[data.match.num[i]] != INT16_MIN) @@ -600,7 +645,7 @@ void Y_IntermissionDrawer(void) } dotimer: - if (timer) + if (timer && grandprixinfo.gp == false) { char *string; INT32 tickdown = (timer+1)/TICRATE; @@ -705,74 +750,73 @@ void Y_Ticker(void) if (intertype == int_race || intertype == int_match) { - if (netgame || multiplayer) + if (!(multiplayer && demo.playback)) // Don't advance to rankings in replays { - if (sorttic == -1) - sorttic = intertic + max((cv_inttime.value/2)-2, 2)*TICRATE; // 8 second pause after match results - else if (!(multiplayer && demo.playback)) // Don't advance to rankings in replays + if (!data.match.rankingsmode && (intertic >= sorttic + 8)) { - if (!data.match.rankingsmode && (intertic >= sorttic + 8)) - Y_CalculateMatchData(1, Y_CompareRank); + Y_CalculateMatchData(1, Y_CompareRank); + } - if (data.match.rankingsmode && intertic > sorttic+16+(2*TICRATE)) + if (data.match.rankingsmode && intertic > sorttic+16+(2*TICRATE)) + { + INT32 q=0,r=0; + boolean kaching = true; + + for (q = 0; q < data.match.numplayers; q++) { - INT32 q=0,r=0; - boolean kaching = true; - - for (q = 0; q < data.match.numplayers; q++) - { - if (data.match.num[q] == MAXPLAYERS + if (data.match.num[q] == MAXPLAYERS || !data.match.increase[data.match.num[q]] || data.match.increase[data.match.num[q]] == INT16_MIN) - continue; + { + continue; + } - r++; - data.match.jitter[data.match.num[q]] = 1; + r++; + data.match.jitter[data.match.num[q]] = 1; - if (powertype != -1) + if (powertype != PWRLV_DISABLED) + { + // Power Levels + if (abs(data.match.increase[data.match.num[q]]) < 10) { - // Power Levels - if (abs(data.match.increase[data.match.num[q]]) < 10) - { - // Not a lot of point increase left, just set to 0 instantly - data.match.increase[data.match.num[q]] = 0; - } - else - { - SINT8 remove = 0; // default (should not happen) - - if (data.match.increase[data.match.num[q]] < 0) - remove = -10; - else if (data.match.increase[data.match.num[q]] > 0) - remove = 10; - - // Remove 10 points at a time - data.match.increase[data.match.num[q]] -= remove; - - // Still not zero, no kaching yet - if (data.match.increase[data.match.num[q]] != 0) - kaching = false; - } + // Not a lot of point increase left, just set to 0 instantly + data.match.increase[data.match.num[q]] = 0; } else { - // Basic bitch points - if (data.match.increase[data.match.num[q]]) - { - if (--data.match.increase[data.match.num[q]]) - kaching = false; - } + SINT8 remove = 0; // default (should not happen) + + if (data.match.increase[data.match.num[q]] < 0) + remove = -10; + else if (data.match.increase[data.match.num[q]] > 0) + remove = 10; + + // Remove 10 points at a time + data.match.increase[data.match.num[q]] -= remove; + + // Still not zero, no kaching yet + if (data.match.increase[data.match.num[q]] != 0) + kaching = false; } } - - if (r) - { - S_StartSound(NULL, (kaching ? sfx_chchng : sfx_ptally)); - Y_CalculateMatchData(2, Y_CompareRank); - } else - endtic = intertic + 3*TICRATE; // 3 second pause after end of tally + { + // Basic bitch points + if (data.match.increase[data.match.num[q]]) + { + if (--data.match.increase[data.match.num[q]]) + kaching = false; + } + } } + + if (r) + { + S_StartSound(NULL, (kaching ? sfx_chchng : sfx_ptally)); + Y_CalculateMatchData(2, Y_CompareRank); + } + else + endtic = intertic + 3*TICRATE; // 3 second pause after end of tally } } else @@ -1047,19 +1091,11 @@ void Y_StartIntermission(void) #endif // set player Power Level type - powertype = PWRLV_DISABLED; - - if (netgame && cv_kartusepwrlv.value) - { - if (G_RaceGametype()) - powertype = PWRLV_RACE; - else if (G_BattleGametype()) - powertype = PWRLV_BATTLE; - } + powertype = K_UsingPowerLevels(); if (!multiplayer) { - timer = 0; + timer = 20*TICRATE; if (!majormods && !multiplayer && !demo.playback) // move this once we have a proper time attack screen { @@ -1078,7 +1114,7 @@ void Y_StartIntermission(void) } else { - if (cv_inttime.value == 0 && gametype == GT_COOP) + if (cv_inttime.value == 0) timer = 0; else if (demo.playback) // Override inttime (which is pulled from the replay anyway timer = 10*TICRATE; @@ -1091,6 +1127,8 @@ void Y_StartIntermission(void) } } + sorttic = max((timer/2) - 2*TICRATE, 2*TICRATE); // 8 second pause after match results + if (gametype == GT_MATCH) intertype = int_match; else //if (gametype == GT_RACE) @@ -1136,7 +1174,9 @@ void Y_StartIntermission(void) } if (powertype != PWRLV_DISABLED) + { K_UpdatePowerLevels(); + } //if (intertype == int_race || intertype == int_match) {