mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-10-30 08:01:28 +00:00
605 lines
14 KiB
C
605 lines
14 KiB
C
// 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;
|
|
}
|