diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f71f96778..b4a7ff490 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -167,6 +167,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 +189,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..af94a2773 100644 --- a/src/Makefile +++ b/src/Makefile @@ -566,6 +566,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) \ diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 001a95ea8..d9d124840 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; 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 fcb2cf315..3ce214aab 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 @@ -690,8 +691,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); @@ -2770,6 +2769,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; @@ -2917,6 +2922,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 @@ -2947,6 +2953,7 @@ static void Command_Map_f(void) if (COM_CheckParm("-force")) { G_SetGameModified(false, true); + startgp = true; } else { @@ -2993,7 +3000,6 @@ static void Command_Map_f(void) // new encoremode value // use cvar by default - newencoremode = (cv_kartencore.value == 1); if (COM_CheckParm("-encore")) @@ -3040,6 +3046,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); } @@ -5063,14 +5132,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); @@ -5155,11 +5226,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: @@ -5170,8 +5236,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: @@ -5182,8 +5246,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 @@ -5192,17 +5254,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 @@ -5713,14 +5770,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); @@ -6211,24 +6268,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")); } } @@ -6241,47 +6309,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 22ca441be..5e13f56da 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -60,10 +60,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 8360714be..6a06c7493 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -431,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; // ======================================================================== @@ -516,6 +518,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 8564e52db..a0efc5cb2 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -1255,6 +1255,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); @@ -3396,6 +3503,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) 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/g_game.c b/src/g_game.c index ebbf60e3d..0c0564175 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; @@ -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++) @@ -2564,6 +2571,7 @@ void G_PlayerReborn(INT32 player) player_t *p; INT32 score, marescore; INT32 lives; + boolean lostlife; INT32 continues; // SRB2kart UINT8 kartspeed; @@ -2585,7 +2593,10 @@ void G_PlayerReborn(INT32 player) boolean spectator; boolean bot; UINT8 botdifficulty; + UINT8 botdiffincrease; + boolean botrival; SINT8 pity; + SINT8 xtralife; // SRB2kart respawnvars_t respawn; @@ -2603,6 +2614,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; @@ -2632,7 +2644,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) @@ -2683,6 +2698,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; @@ -2709,7 +2725,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; @@ -3201,6 +3220,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; @@ -3276,18 +3336,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 } // @@ -3623,8 +3680,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 @@ -3641,10 +3697,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 } @@ -3664,11 +3731,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)) @@ -3678,8 +3767,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); @@ -3723,44 +3811,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. @@ -3770,14 +3832,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: @@ -3837,7 +3891,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++) @@ -3934,7 +3988,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); @@ -4503,7 +4557,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) @@ -4523,23 +4577,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); @@ -4568,63 +4619,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.... 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..ebc1b4869 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) 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_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 7d341d5c5..76be34126 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -393,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; @@ -471,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) @@ -610,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; @@ -772,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 { @@ -780,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: @@ -908,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) @@ -2052,6 +2062,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); } @@ -2082,9 +2098,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]); @@ -2107,19 +2126,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 { @@ -7380,7 +7405,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 @@ -7417,6 +7443,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 @@ -7707,6 +7740,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]; @@ -7729,6 +7764,8 @@ static patch_t *kp_itemminimap; static patch_t *kp_alagles[10]; static patch_t *kp_blagles[6]; +static patch_t *kp_cpu; + void K_LoadKartHUDGraphics(void) { INT32 i, j; @@ -7999,6 +8036,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++) @@ -8086,6 +8131,8 @@ 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); } // For the item toggle menu @@ -9130,7 +9177,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) { @@ -9243,6 +9290,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]; @@ -9327,7 +9375,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) @@ -9349,7 +9397,7 @@ static void K_drawKartLapsAndRings(void) 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); @@ -9367,7 +9415,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]); @@ -9391,7 +9439,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); @@ -9582,34 +9630,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; @@ -9682,52 +9702,297 @@ 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 (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 = thiscam->x; + c.y = thiscam->y; + c.z = thiscam->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 = 4096*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 ((ntplayer->kartstuff[k_position] >= stplyr->kartstuff[k_position]-1) + && (ntplayer->kartstuff[k_position] <= stplyr->kartstuff[k_position]+1)) + { + ; // TODO: Draw a cool name tag for online + } } } } @@ -10628,13 +10893,19 @@ static void K_drawDistributionDebugger(void) spbrush = true; } + if (stplyr->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; @@ -10735,6 +11006,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_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_playerlib.c b/src/lua_playerlib.c index 89b354c19..947114e82 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -244,6 +244,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")) @@ -486,6 +488,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 a00707bb3..16cef2eb9 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 @@ -226,7 +227,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); @@ -239,6 +241,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; @@ -462,6 +465,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. // ========================================================================== @@ -801,30 +811,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[] = @@ -1805,6 +1815,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", @@ -3369,6 +3381,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)"); @@ -4214,6 +4230,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) { @@ -6659,6 +6704,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 = @@ -7543,6 +7590,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 916f11383..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]) diff --git a/src/p_mobj.c b/src/p_mobj.c index 4754172f6..842d1e2b1 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -11406,9 +11406,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}; @@ -11679,10 +11676,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 d450b2295..7bfeba762 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -138,6 +138,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); @@ -277,6 +278,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); @@ -330,6 +333,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 @@ -460,6 +464,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 463b14814..655b667fd 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; @@ -2464,8 +2463,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; @@ -2490,11 +2504,6 @@ static void P_LevelInitStuff(void) memset(&battleovertime, 0, sizeof(struct battleovertime)); speedscramble = encorescramble = -1; - - if (!modeattacking) - { - K_UpdateMatchRaceBots(); - } } // @@ -2679,12 +2688,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; - } } } @@ -3381,6 +3384,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 @@ -3432,9 +3457,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_user.c b/src/p_user.c index ed31c1d11..a96ddecd7 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 @@ -48,6 +47,8 @@ #include "k_kart.h" #include "console.h" // CON_LogMessage #include "k_respawn.h" +#include "k_bot.h" +#include "k_grandprix.h" #ifdef HW3SOUND #include "hardware/hw3sound.h" @@ -949,7 +950,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! @@ -967,8 +967,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; } @@ -1686,12 +1686,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; @@ -1702,7 +1710,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]; @@ -1710,7 +1718,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); @@ -1720,10 +1728,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; } @@ -1735,24 +1739,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 @@ -4164,23 +4188,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)); } } @@ -7094,14 +7116,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 @@ -8355,13 +8372,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) { @@ -8369,8 +8401,6 @@ void P_DoTimeOver(player_t *player) P_DamageMobj(player->mo, NULL, NULL, 10000); } - player->lives = 0; - P_EndingMusic(player); if (!exitcountdown) @@ -8487,7 +8517,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; } } @@ -8495,24 +8525,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; + } } } @@ -8526,33 +8558,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); } } } @@ -8590,17 +8598,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 289182fbb..c43f430c7 100644 --- a/src/r_things.c +++ b/src/r_things.c @@ -2920,6 +2920,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; @@ -3190,6 +3195,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 470e32fb2..6d0e86425 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/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) {