Merge branch 'grand-pricks' into 'master'

Grand pricks

See merge request KartKrew/Kart!258
This commit is contained in:
Sal 2020-06-07 01:28:35 -04:00
commit 58dbdc2378
32 changed files with 2288 additions and 540 deletions

View file

@ -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"))

View file

@ -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) \

View file

@ -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;

View file

@ -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;

View file

@ -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)

View file

@ -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();
}
}

View file

@ -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;

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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....

View file

@ -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]);
}
}

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

605
src/k_grandprix.c Normal file
View file

@ -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;
}

144
src/k_grandprix.h Normal file
View file

@ -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

View file

@ -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, FRACUNIT + ((PLAYERSCALING << FRACBITS) / 25)) >> FRACBITS; \
odds *= 2; \
if (rival) \
odds *= 2; \
odds = FixedMul(odds * FRACUNIT, FRACUNIT + ((PLAYERSCALING * FRACUNIT) / 25)) / FRACUNIT; \
if (mashed > 0) \
odds = FixedDiv(odds<<FRACBITS, FRACUNIT + mashed) >> 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) + (160<<FRACBITS))>>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())
{

View file

@ -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;

View file

@ -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);

View file

@ -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.

View file

@ -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"))

View file

@ -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
// ===========

View file

@ -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])

View file

@ -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;

View file

@ -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);

View file

@ -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;
}

View file

@ -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)
{

View file

@ -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

View file

@ -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;

View file

@ -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)
{