Merge branch 'save_p-unglobal' into acs

This commit is contained in:
Sally Coolatta 2022-12-26 18:05:15 -05:00
commit 7a5b276ece
33 changed files with 4038 additions and 2650 deletions

View file

@ -1128,8 +1128,6 @@ static boolean SV_SendServerConfig(INT32 node)
return waspacketsent;
}
#define SAVEGAMESIZE (768*1024)
static boolean SV_ResendingSavegameToAnyone(void)
{
INT32 i;
@ -1143,28 +1141,29 @@ static boolean SV_ResendingSavegameToAnyone(void)
static void SV_SendSaveGame(INT32 node, boolean resending)
{
size_t length, compressedlen;
UINT8 *savebuffer;
savebuffer_t save;
UINT8 *compressedsave;
UINT8 *buffertosend;
// first save it in a malloced buffer
savebuffer = (UINT8 *)malloc(SAVEGAMESIZE);
if (!savebuffer)
save.size = NETSAVEGAMESIZE;
save.buffer = (UINT8 *)malloc(save.size);
if (!save.buffer)
{
CONS_Alert(CONS_ERROR, M_GetText("No more free memory for savegame\n"));
return;
}
// Leave room for the uncompressed length.
save_p = savebuffer + sizeof(UINT32);
save.p = save.buffer + sizeof(UINT32);
save.end = save.buffer + save.size;
P_SaveNetGame(resending);
P_SaveNetGame(&save, resending);
length = save_p - savebuffer;
if (length > SAVEGAMESIZE)
length = save.p - save.buffer;
if (length > NETSAVEGAMESIZE)
{
free(savebuffer);
save_p = NULL;
free(save.buffer);
I_Error("Savegame buffer overrun");
}
@ -1178,11 +1177,10 @@ static void SV_SendSaveGame(INT32 node, boolean resending)
}
// Attempt to compress it.
if((compressedlen = lzf_compress(savebuffer + sizeof(UINT32), length - sizeof(UINT32), compressedsave + sizeof(UINT32), length - sizeof(UINT32) - 1)))
if ((compressedlen = lzf_compress(save.buffer + sizeof(UINT32), length - sizeof(UINT32), compressedsave + sizeof(UINT32), length - sizeof(UINT32) - 1)))
{
// Compressing succeeded; send compressed data
free(savebuffer);
free(save.buffer);
// State that we're compressed.
buffertosend = compressedsave;
@ -1192,16 +1190,14 @@ static void SV_SendSaveGame(INT32 node, boolean resending)
else
{
// Compression failed to make it smaller; send original
free(compressedsave);
// State that we're not compressed
buffertosend = savebuffer;
WRITEUINT32(savebuffer, 0);
buffertosend = save.buffer;
WRITEUINT32(save.buffer, 0);
}
AddRamToSendQueue(node, buffertosend, length, SF_RAM, 0);
save_p = NULL;
// Remember when we started sending the savegame so we can handle timeouts
sendingsavegame[node] = true;
@ -1215,7 +1211,7 @@ static consvar_t cv_dumpconsistency = CVAR_INIT ("dumpconsistency", "Off", CV_SA
static void SV_SavedGame(void)
{
size_t length;
UINT8 *savebuffer;
savebuffer_t save;
char tmpsave[256];
if (!cv_dumpconsistency.value)
@ -1224,29 +1220,30 @@ static void SV_SavedGame(void)
sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home);
// first save it in a malloced buffer
save_p = savebuffer = (UINT8 *)malloc(SAVEGAMESIZE);
if (!save_p)
save.size = NETSAVEGAMESIZE;
save.p = save.buffer = (UINT8 *)malloc(save.size);
if (!save.p)
{
CONS_Alert(CONS_ERROR, M_GetText("No more free memory for savegame\n"));
return;
}
P_SaveNetGame(false);
save.end = save.buffer + save.size;
length = save_p - savebuffer;
if (length > SAVEGAMESIZE)
P_SaveNetGame(&save, false);
length = save.p - save.buffer;
if (length > NETSAVEGAMESIZE)
{
free(savebuffer);
save_p = NULL;
free(save.buffer);
I_Error("Savegame buffer overrun");
}
// then save it!
if (!FIL_WriteFile(tmpsave, savebuffer, length))
if (!FIL_WriteFile(tmpsave, save.buffer, length))
CONS_Printf(M_GetText("Didn't save %s for netgame"), tmpsave);
free(savebuffer);
save_p = NULL;
free(save.buffer);
}
#undef TMPSAVENAME
@ -1256,13 +1253,13 @@ static void SV_SavedGame(void)
static void CL_LoadReceivedSavegame(boolean reloading)
{
UINT8 *savebuffer = NULL;
savebuffer_t save;
size_t length, decompressedlen;
char tmpsave[256];
sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home);
length = FIL_ReadFile(tmpsave, &savebuffer);
length = FIL_ReadFile(tmpsave, &save.buffer);
CONS_Printf(M_GetText("Loading savegame length %s\n"), sizeu1(length));
if (!length)
@ -1271,16 +1268,22 @@ static void CL_LoadReceivedSavegame(boolean reloading)
return;
}
save_p = savebuffer;
save.p = save.buffer;
save.size = length;
save.end = save.buffer + save.size;
// Decompress saved game if necessary.
decompressedlen = READUINT32(save_p);
decompressedlen = READUINT32(save.p);
if(decompressedlen > 0)
{
UINT8 *decompressedbuffer = Z_Malloc(decompressedlen, PU_STATIC, NULL);
lzf_decompress(save_p, length - sizeof(UINT32), decompressedbuffer, decompressedlen);
Z_Free(savebuffer);
save_p = savebuffer = decompressedbuffer;
lzf_decompress(save.p, length - sizeof(UINT32), decompressedbuffer, decompressedlen);
Z_Free(save.buffer);
save.p = save.buffer = decompressedbuffer;
save.size = decompressedlen;
save.end = save.buffer + decompressedlen;
}
paused = false;
@ -1290,7 +1293,7 @@ static void CL_LoadReceivedSavegame(boolean reloading)
automapactive = false;
// load a base level
if (P_LoadNetGame(reloading))
if (P_LoadNetGame(&save, reloading))
{
if (!reloading)
{
@ -1312,8 +1315,8 @@ static void CL_LoadReceivedSavegame(boolean reloading)
}
// done
Z_Free(savebuffer);
save_p = NULL;
Z_Free(save.buffer);
save.p = NULL;
if (unlink(tmpsave) == -1)
CONS_Alert(CONS_ERROR, M_GetText("Can't delete %s\n"), tmpsave);
consistancy[gametic%BACKUPTICS] = Consistancy();
@ -6064,6 +6067,7 @@ void CL_ClearRewinds(void)
rewind_t *CL_SaveRewindPoint(size_t demopos)
{
savebuffer_t save;
rewind_t *rewind;
if (rewindhead && rewindhead->leveltime + REWIND_POINT_INTERVAL > leveltime)
@ -6073,8 +6077,12 @@ rewind_t *CL_SaveRewindPoint(size_t demopos)
if (!rewind)
return NULL;
save_p = rewind->savebuffer;
P_SaveNetGame(false);
save.buffer = save.p = rewind->savebuffer;
save.size = NETSAVEGAMESIZE;
save.end = save.buffer + save.size;
P_SaveNetGame(&save, false);
rewind->leveltime = leveltime;
rewind->next = rewindhead;
rewind->demopos = demopos;
@ -6085,6 +6093,7 @@ rewind_t *CL_SaveRewindPoint(size_t demopos)
rewind_t *CL_RewindToTime(tic_t time)
{
savebuffer_t save;
rewind_t *rewind;
while (rewindhead && rewindhead->leveltime > time)
@ -6097,8 +6106,12 @@ rewind_t *CL_RewindToTime(tic_t time)
if (!rewindhead)
return NULL;
save_p = rewindhead->savebuffer;
P_LoadNetGame(false);
save.buffer = save.p = rewindhead->savebuffer;
save.size = NETSAVEGAMESIZE;
save.end = save.buffer + save.size;
P_LoadNetGame(&save, false);
wipegamestate = gamestate; // No fading back in!
timeinmap = leveltime;

View file

@ -22,6 +22,7 @@
#include "mserv.h"
#include "k_pwrlv.h" // PWRLV_NUMTYPES
#include "p_saveg.h" // NETSAVEGAMESIZE
/*
The 'packet version' is used to distinguish packet formats.
@ -530,7 +531,7 @@ extern boolean hu_stopped;
//
struct rewind_t {
UINT8 savebuffer[(768*1024)];
UINT8 savebuffer[NETSAVEGAMESIZE];
tic_t leveltime;
size_t demopos;

View file

@ -1872,6 +1872,18 @@ void D_SRB2Main(void)
G_SetUsedCheats();
}
if (grandprixinfo.gp == true && mapheaderinfo[pstartmap-1])
{
if (mapheaderinfo[pstartmap-1]->typeoflevel & TOL_SPECIAL)
{
specialStage.active = true;
specialStage.encore = grandprixinfo.encore;
grandprixinfo.eventmode = GPEVENT_SPECIAL;
}
G_SetUsedCheats();
}
D_MapChange(pstartmap, gametype, (cv_kartencore.value == 1), true, 0, false, false);
}
}

View file

@ -2853,7 +2853,7 @@ static void Command_Map_f(void)
if (mapheaderinfo[newmapnum-1])
{
// Let's just guess so we don't have to specify the gametype EVERY time...
newgametype = (mapheaderinfo[newmapnum-1]->typeoflevel & TOL_RACE) ? GT_RACE : GT_BATTLE;
newgametype = (mapheaderinfo[newmapnum-1]->typeoflevel & (TOL_BATTLE|TOL_BOSS)) ? GT_BATTLE : GT_RACE;
}
}
@ -2902,11 +2902,59 @@ static void Command_Map_f(void)
if (!(netgame || multiplayer))
{
grandprixinfo.gamespeed = (cv_kartspeed.value == KARTSPEED_AUTO ? KARTSPEED_NORMAL : cv_kartspeed.value);
grandprixinfo.masterbots = false;
if (option_skill)
{
const char *skillname = COM_Argv(option_skill + 1);
INT32 newskill = -1;
INT32 j;
for (j = 0; gpdifficulty_cons_t[j].strvalue; j++)
{
if (!strcasecmp(gpdifficulty_cons_t[j].strvalue, skillname))
{
newskill = (INT16)gpdifficulty_cons_t[j].value;
break;
}
}
if (!gpdifficulty_cons_t[j].strvalue) // reached end of the list with no match
{
INT32 num = atoi(COM_Argv(option_skill + 1)); // assume they gave us a skill number, which is okay too
if (num >= KARTSPEED_EASY && num <= KARTGP_MASTER)
newskill = (INT16)num;
}
if (newskill != -1)
{
if (newskill == KARTGP_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;
grandprixinfo.eventmode = GPEVENT_NONE;
if (newgametype == GT_BATTLE)
{
grandprixinfo.gp = false;
specialStage.active = false;
K_ResetBossInfo();
grandprixinfo.eventmode = GPEVENT_BONUS;
if (mapheaderinfo[newmapnum-1] &&
mapheaderinfo[newmapnum-1]->typeoflevel & TOL_BOSS)
@ -2914,71 +2962,24 @@ static void Command_Map_f(void)
bossinfo.boss = true;
bossinfo.encore = newencoremode;
}
else
{
bossinfo.boss = false;
K_ResetBossInfo();
}
}
else
{
if (mapheaderinfo[newmapnum-1] &&
mapheaderinfo[newmapnum-1]->typeoflevel & TOL_SPECIAL) // Special Stage
{
grandprixinfo.gp = false;
bossinfo.boss = false;
specialStage.active = true;
specialStage.encore = newencoremode;
grandprixinfo.eventmode = GPEVENT_SPECIAL;
}
else // default GP
else
{
grandprixinfo.gamespeed = (cv_kartspeed.value == KARTSPEED_AUTO ? KARTSPEED_NORMAL : cv_kartspeed.value);
grandprixinfo.masterbots = false;
if (option_skill)
{
const char *skillname = COM_Argv(option_skill + 1);
INT32 newskill = -1;
INT32 j;
for (j = 0; gpdifficulty_cons_t[j].strvalue; j++)
{
if (!strcasecmp(gpdifficulty_cons_t[j].strvalue, skillname))
{
newskill = (INT16)gpdifficulty_cons_t[j].value;
break;
}
}
if (!gpdifficulty_cons_t[j].strvalue) // reached end of the list with no match
{
INT32 num = atoi(COM_Argv(option_skill + 1)); // assume they gave us a skill number, which is okay too
if (num >= KARTSPEED_EASY && num <= KARTGP_MASTER)
newskill = (INT16)num;
}
if (newskill != -1)
{
if (newskill == KARTGP_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;
bossinfo.boss = false;
specialStage.active = false;
grandprixinfo.initalize = true;
}
}
}
@ -5775,10 +5776,9 @@ static void Command_Togglemodified_f(void)
modifiedgame = !modifiedgame;
}
extern UINT8 *save_p;
static void Command_Archivetest_f(void)
{
UINT8 *buf;
savebuffer_t save;
UINT32 i, wrote;
thinker_t *th;
if (gamestate != GS_LEVEL)
@ -5794,28 +5794,30 @@ static void Command_Archivetest_f(void)
((mobj_t *)th)->mobjnum = i++;
// allocate buffer
buf = save_p = ZZ_Alloc(1024);
save.size = 1024;
save.buffer = save.p = ZZ_Alloc(save.size);
save.end = save.buffer + save.size;
// test archive
CONS_Printf("LUA_Archive...\n");
LUA_Archive(&save_p);
WRITEUINT8(save_p, 0x7F);
wrote = (UINT32)(save_p-buf);
LUA_Archive(&save, true);
WRITEUINT8(save.p, 0x7F);
wrote = (UINT32)(save.p - save.buffer);
// clear Lua state, so we can really see what happens!
CONS_Printf("Clearing state!\n");
LUA_ClearExtVars();
// test unarchive
save_p = buf;
save.p = save.buffer;
CONS_Printf("LUA_UnArchive...\n");
LUA_UnArchive(&save_p);
i = READUINT8(save_p);
if (i != 0x7F || wrote != (UINT32)(save_p-buf))
CONS_Printf("Savegame corrupted. (write %u, read %u)\n", wrote, (UINT32)(save_p-buf));
LUA_UnArchive(&save, true);
i = READUINT8(save.p);
if (i != 0x7F || wrote != (UINT32)(save.p - save.buffer))
CONS_Printf("Savegame corrupted. (write %u, read %u)\n", wrote, (UINT32)(save.p - save.buffer));
// free buffer
Z_Free(buf);
Z_Free(save.buffer);
CONS_Printf("Done. No crash.\n");
}
#endif

View file

@ -4534,6 +4534,11 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi
// Broly Ki Orb
"S_BROLY1",
"S_BROLY2",
"S_SPECIAL_UFO_POD",
"S_SPECIAL_UFO_OVERLAY",
"S_SPECIAL_UFO_ARM",
"S_SPECIAL_UFO_STEM",
};
// RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1",
@ -5631,6 +5636,9 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t
"MT_BEAMPOINT",
"MT_BROLY",
"MT_SPECIAL_UFO",
"MT_SPECIAL_UFO_PIECE",
};
const char *const MOBJFLAG_LIST[] = {

View file

@ -549,7 +549,7 @@ void DRPC_UpdatePresence(void)
if (gamestate == GS_LEVEL && Playing())
{
const time_t currentTime = time(NULL);
const time_t mapTimeStart = currentTime - ((leveltime + (modeattacking ? starttime : 0)) / TICRATE);
const time_t mapTimeStart = currentTime - ((leveltime + starttime) / TICRATE);
discordPresence.startTimestamp = mapTimeStart;

File diff suppressed because it is too large Load diff

View file

@ -18,8 +18,6 @@
#include "doomstat.h"
#include "d_event.h"
extern UINT8 *demo_p;
// ======================================
// DEMO playback/recording related stuff.
// ======================================

View file

@ -73,9 +73,6 @@ UINT8 ultimatemode = false;
JoyType_t Joystick[MAXSPLITSCREENPLAYERS];
// 1024 bytes is plenty for a savegame
#define SAVEGAMESIZE (1024)
// SRB2kart
char gamedatafilename[64] =
#ifdef DEVELOP
@ -344,8 +341,6 @@ boolean precache = true; // if true, load all graphics at start
INT16 prevmap, nextmap;
static UINT8 *savebuffer;
static void weaponPrefChange(void);
static void weaponPrefChange2(void);
static void weaponPrefChange3(void);
@ -4314,6 +4309,7 @@ void G_LoadGameData(void)
UINT32 versionID;
UINT8 versionMinor;
UINT8 rtemp;
savebuffer_t save;
//For records
UINT32 numgamedatamapheaders;
@ -4342,7 +4338,7 @@ void G_LoadGameData(void)
return;
}
length = FIL_ReadFile(va(pandf, srb2home, gamedatafilename), &savebuffer);
length = FIL_ReadFile(va(pandf, srb2home, gamedatafilename), &save.buffer);
if (!length)
{
// No gamedata. We can save a new one.
@ -4350,35 +4346,35 @@ void G_LoadGameData(void)
return;
}
save_p = savebuffer;
save.p = save.buffer;
// Version check
versionID = READUINT32(save_p);
versionID = READUINT32(save.p);
if (versionID != GD_VERSIONCHECK)
{
const char *gdfolder = "the Ring Racers folder";
if (strcmp(srb2home,"."))
gdfolder = srb2home;
Z_Free(savebuffer);
save_p = NULL;
Z_Free(save.buffer);
save.p = NULL;
I_Error("Game data is not for Ring Racers v2.0.\nDelete %s(maybe in %s) and try again.", gamedatafilename, gdfolder);
}
versionMinor = READUINT8(save_p);
versionMinor = READUINT8(save.p);
if (versionMinor > GD_VERSIONMINOR)
{
Z_Free(savebuffer);
save_p = NULL;
Z_Free(save.buffer);
save.p = NULL;
I_Error("Game data is from the future! (expected %d, got %d)", GD_VERSIONMINOR, versionMinor);
}
gamedata->totalplaytime = READUINT32(save_p);
gamedata->matchesplayed = READUINT32(save_p);
gamedata->totalplaytime = READUINT32(save.p);
gamedata->matchesplayed = READUINT32(save.p);
{
// Quick & dirty hash for what mod this save file is for.
UINT32 modID = READUINT32(save_p);
UINT32 modID = READUINT32(save.p);
UINT32 expectedID = quickncasehash(timeattackfolder, 64);
if (modID != expectedID)
@ -4391,34 +4387,34 @@ void G_LoadGameData(void)
// To save space, use one bit per collected/achieved/unlocked flag
for (i = 0; i < MAXEMBLEMS;)
{
rtemp = READUINT8(save_p);
rtemp = READUINT8(save.p);
for (j = 0; j < 8 && j+i < MAXEMBLEMS; ++j)
gamedata->collected[j+i] = ((rtemp >> j) & 1);
i += j;
}
for (i = 0; i < MAXUNLOCKABLES;)
{
rtemp = READUINT8(save_p);
rtemp = READUINT8(save.p);
for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j)
gamedata->unlocked[j+i] = ((rtemp >> j) & 1);
i += j;
}
for (i = 0; i < MAXUNLOCKABLES;)
{
rtemp = READUINT8(save_p);
rtemp = READUINT8(save.p);
for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j)
gamedata->unlockpending[j+i] = ((rtemp >> j) & 1);
i += j;
}
for (i = 0; i < MAXCONDITIONSETS;)
{
rtemp = READUINT8(save_p);
rtemp = READUINT8(save.p);
for (j = 0; j < 8 && j+i < MAXCONDITIONSETS; ++j)
gamedata->achieved[j+i] = ((rtemp >> j) & 1);
i += j;
}
gamedata->challengegridwidth = READUINT16(save_p);
gamedata->challengegridwidth = READUINT16(save.p);
Z_Free(gamedata->challengegrid);
if (gamedata->challengegridwidth)
{
@ -4427,7 +4423,7 @@ void G_LoadGameData(void)
PU_STATIC, NULL);
for (i = 0; i < (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT); i++)
{
gamedata->challengegrid[i] = READUINT8(save_p);
gamedata->challengegrid[i] = READUINT8(save.p);
}
}
else
@ -4435,10 +4431,10 @@ void G_LoadGameData(void)
gamedata->challengegrid = NULL;
}
gamedata->timesBeaten = READUINT32(save_p);
gamedata->timesBeaten = READUINT32(save.p);
// Main records
numgamedatamapheaders = READUINT32(save_p);
numgamedatamapheaders = READUINT32(save.p);
if (numgamedatamapheaders >= NEXTMAP_SPECIAL)
goto datacorrupt;
@ -4449,12 +4445,12 @@ void G_LoadGameData(void)
tic_t rectime;
tic_t reclap;
READSTRINGN(save_p, mapname, sizeof(mapname));
READSTRINGN(save.p, mapname, sizeof(mapname));
mapnum = G_MapNumber(mapname);
rtemp = READUINT8(save_p);
rectime = (tic_t)READUINT32(save_p);
reclap = (tic_t)READUINT32(save_p);
rtemp = READUINT8(save.p);
rectime = (tic_t)READUINT32(save.p);
reclap = (tic_t)READUINT32(save.p);
if (mapnum < nummapheaders && mapheaderinfo[mapnum])
{
@ -4480,8 +4476,8 @@ void G_LoadGameData(void)
}
// done
Z_Free(savebuffer);
save_p = NULL;
Z_Free(save.buffer);
save.p = NULL;
// Don't consider loaded until it's a success!
// It used to do this much earlier, but this would cause the gamedata to
@ -4501,8 +4497,8 @@ void G_LoadGameData(void)
if (strcmp(srb2home,"."))
gdfolder = srb2home;
Z_Free(savebuffer);
save_p = NULL;
Z_Free(save.buffer);
save.p = NULL;
I_Error("Corrupt game data file.\nDelete %s(maybe in %s) and try again.", gamedatafilename, gdfolder);
}
@ -4515,6 +4511,7 @@ void G_SaveGameData(void)
size_t length;
INT32 i, j;
UINT8 btemp;
savebuffer_t save;
if (!gamedata->loaded)
return; // If never loaded (-nodata), don't save
@ -4534,20 +4531,22 @@ void G_SaveGameData(void)
}
length += nummapheaders * (MAXMAPLUMPNAME+1+4+4);
save_p = savebuffer = (UINT8 *)malloc(length);
if (!save_p)
save.size = length;
save.p = save.buffer = (UINT8 *)malloc(save.size);
if (!save.p)
{
CONS_Alert(CONS_ERROR, M_GetText("No more free memory for saving game data\n"));
return;
}
save.end = save.buffer + save.size;
// Version test
WRITEUINT32(save_p, GD_VERSIONCHECK); // 4
WRITEUINT8(save_p, GD_VERSIONMINOR); // 1
WRITEUINT32(save_p, gamedata->totalplaytime); // 4
WRITEUINT32(save_p, gamedata->matchesplayed); // 4
WRITEUINT32(save_p, quickncasehash(timeattackfolder, 64));
WRITEUINT32(save.p, GD_VERSIONCHECK); // 4
WRITEUINT8(save.p, GD_VERSIONMINOR); // 1
WRITEUINT32(save.p, gamedata->totalplaytime); // 4
WRITEUINT32(save.p, gamedata->matchesplayed); // 4
WRITEUINT32(save.p, quickncasehash(timeattackfolder, 64));
// To save space, use one bit per collected/achieved/unlocked flag
for (i = 0; i < MAXEMBLEMS;) // MAXEMBLEMS * 1;
@ -4555,7 +4554,7 @@ void G_SaveGameData(void)
btemp = 0;
for (j = 0; j < 8 && j+i < MAXEMBLEMS; ++j)
btemp |= (gamedata->collected[j+i] << j);
WRITEUINT8(save_p, btemp);
WRITEUINT8(save.p, btemp);
i += j;
}
@ -4565,7 +4564,7 @@ void G_SaveGameData(void)
btemp = 0;
for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j)
btemp |= (gamedata->unlocked[j+i] << j);
WRITEUINT8(save_p, btemp);
WRITEUINT8(save.p, btemp);
i += j;
}
for (i = 0; i < MAXUNLOCKABLES;)
@ -4573,7 +4572,7 @@ void G_SaveGameData(void)
btemp = 0;
for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j)
btemp |= (gamedata->unlockpending[j+i] << j);
WRITEUINT8(save_p, btemp);
WRITEUINT8(save.p, btemp);
i += j;
}
@ -4582,52 +4581,51 @@ void G_SaveGameData(void)
btemp = 0;
for (j = 0; j < 8 && j+i < MAXCONDITIONSETS; ++j)
btemp |= (gamedata->achieved[j+i] << j);
WRITEUINT8(save_p, btemp);
WRITEUINT8(save.p, btemp);
i += j;
}
if (gamedata->challengegrid) // 2 + gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT
{
WRITEUINT16(save_p, gamedata->challengegridwidth);
WRITEUINT16(save.p, gamedata->challengegridwidth);
for (i = 0; i < (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT); i++)
{
WRITEUINT8(save_p, gamedata->challengegrid[i]);
WRITEUINT8(save.p, gamedata->challengegrid[i]);
}
}
else // 2
{
WRITEUINT16(save_p, 0);
WRITEUINT16(save.p, 0);
}
WRITEUINT32(save_p, gamedata->timesBeaten); // 4
WRITEUINT32(save.p, gamedata->timesBeaten); // 4
// Main records
WRITEUINT32(save_p, nummapheaders); // 4
WRITEUINT32(save.p, nummapheaders); // 4
for (i = 0; i < nummapheaders; i++) // nummapheaders * (255+1+4+4)
{
// For figuring out which header to assing it to on load
WRITESTRINGN(save_p, mapheaderinfo[i]->lumpname, MAXMAPLUMPNAME);
WRITESTRINGN(save.p, mapheaderinfo[i]->lumpname, MAXMAPLUMPNAME);
WRITEUINT8(save_p, (mapheaderinfo[i]->mapvisited & MV_MAX));
WRITEUINT8(save.p, (mapheaderinfo[i]->mapvisited & MV_MAX));
if (mapheaderinfo[i]->mainrecord)
{
WRITEUINT32(save_p, mapheaderinfo[i]->mainrecord->time);
WRITEUINT32(save_p, mapheaderinfo[i]->mainrecord->lap);
WRITEUINT32(save.p, mapheaderinfo[i]->mainrecord->time);
WRITEUINT32(save.p, mapheaderinfo[i]->mainrecord->lap);
}
else
{
WRITEUINT32(save_p, 0);
WRITEUINT32(save_p, 0);
WRITEUINT32(save.p, 0);
WRITEUINT32(save.p, 0);
}
}
length = save_p - savebuffer;
length = save.p - save.buffer;
FIL_WriteFile(va(pandf, srb2home, gamedatafilename), savebuffer, length);
free(savebuffer);
save_p = savebuffer = NULL;
FIL_WriteFile(va(pandf, srb2home, gamedatafilename), save.buffer, length);
free(save.buffer);
// Also save profiles here.
PR_SaveProfiles();
@ -4644,6 +4642,7 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride)
size_t length;
char vcheck[VERSIONSIZE];
char savename[255];
savebuffer_t save;
// memset savedata to all 0, fixes calling perfectly valid saves corrupt because of bots
memset(&savedata, 0, sizeof(savedata));
@ -4658,18 +4657,20 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride)
else
sprintf(savename, savegamename, slot);
length = FIL_ReadFile(savename, &savebuffer);
length = FIL_ReadFile(savename, &save.buffer);
if (!length)
{
CONS_Printf(M_GetText("Couldn't read file %s\n"), savename);
return;
}
save_p = savebuffer;
save.p = save.buffer;
save.size = length;
save.end = save.buffer + save.size;
memset(vcheck, 0, sizeof (vcheck));
sprintf(vcheck, (marathonmode ? "back-up %d" : "version %d"), VERSION);
if (strcmp((const char *)save_p, (const char *)vcheck))
if (strcmp((const char *)save.p, (const char *)vcheck))
{
#ifdef SAVEGAME_OTHERVERSIONS
M_StartMessage(M_GetText("Save game from different version.\nYou can load this savegame, but\nsaving afterwards will be disabled.\n\nDo you want to continue anyway?\n\n(Press 'Y' to confirm)\n"),
@ -4679,15 +4680,14 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride)
M_ClearMenus(true); // so ESC backs out to title
M_StartMessage(M_GetText("Save game from different version\n\nPress ESC\n"), NULL, MM_NOTHING);
Command_ExitGame_f();
Z_Free(savebuffer);
save_p = savebuffer = NULL;
Z_Free(save.buffer);
// no cheating!
memset(&savedata, 0, sizeof(savedata));
#endif
return; // bad version
}
save_p += VERSIONSIZE;
save.p += VERSIONSIZE;
if (demo.playback) // reset game engine
G_StopDemo();
@ -4696,13 +4696,13 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride)
// automapactive = false;
// dearchive all the modifications
if (!P_LoadGame(mapoverride))
if (!P_LoadGame(&save, mapoverride))
{
M_ClearMenus(true); // so ESC backs out to title
M_StartMessage(M_GetText("Savegame file corrupted\n\nPress ESC\n"), NULL, MM_NOTHING);
Command_ExitGame_f();
Z_Free(savebuffer);
save_p = savebuffer = NULL;
Z_Free(save.buffer);
save.p = save.buffer = NULL;
// no cheating!
memset(&savedata, 0, sizeof(savedata));
@ -4710,13 +4710,12 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride)
}
if (marathonmode)
{
marathontime = READUINT32(save_p);
marathonmode |= READUINT8(save_p);
marathontime = READUINT32(save.p);
marathonmode |= READUINT8(save.p);
}
// done
Z_Free(savebuffer);
save_p = savebuffer = NULL;
Z_Free(save.buffer);
// gameaction = ga_nothing;
// G_SetGamestate(GS_LEVEL);
@ -4742,6 +4741,7 @@ void G_SaveGame(UINT32 slot, INT16 mapnum)
boolean saved;
char savename[256] = "";
const char *backup;
savebuffer_t save;
if (marathonmode)
strcpy(savename, liveeventbackup);
@ -4754,31 +4754,32 @@ void G_SaveGame(UINT32 slot, INT16 mapnum)
char name[VERSIONSIZE];
size_t length;
save_p = savebuffer = (UINT8 *)malloc(SAVEGAMESIZE);
if (!save_p)
save.size = SAVEGAMESIZE;
save.p = save.buffer = (UINT8 *)malloc(save.size);
if (!save.p)
{
CONS_Alert(CONS_ERROR, M_GetText("No more free memory for saving game data\n"));
return;
}
save.end = save.buffer + save.size;
memset(name, 0, sizeof (name));
sprintf(name, (marathonmode ? "back-up %d" : "version %d"), VERSION);
WRITEMEM(save_p, name, VERSIONSIZE);
WRITEMEM(save.p, name, VERSIONSIZE);
P_SaveGame(mapnum);
P_SaveGame(&save, mapnum);
if (marathonmode)
{
UINT32 writetime = marathontime;
if (!(marathonmode & MA_INGAME))
writetime += TICRATE*5; // live event backup penalty because we don't know how long it takes to get to the next map
WRITEUINT32(save_p, writetime);
WRITEUINT8(save_p, (marathonmode & ~MA_INIT));
WRITEUINT32(save.p, writetime);
WRITEUINT8(save.p, (marathonmode & ~MA_INIT));
}
length = save_p - savebuffer;
saved = FIL_WriteFile(backup, savebuffer, length);
free(savebuffer);
save_p = savebuffer = NULL;
length = save.p - save.buffer;
saved = FIL_WriteFile(backup, save.buffer, length);
free(save.buffer);
}
gameaction = ga_nothing;
@ -4790,7 +4791,7 @@ void G_SaveGame(UINT32 slot, INT16 mapnum)
}
#define BADSAVE goto cleanup;
#define CHECKPOS if (save_p >= end_p) BADSAVE
#define CHECKPOS if (save.p >= save.end) BADSAVE
void G_SaveGameOver(UINT32 slot, boolean modifylives)
{
boolean saved = false;
@ -4798,6 +4799,7 @@ void G_SaveGameOver(UINT32 slot, boolean modifylives)
char vcheck[VERSIONSIZE];
char savename[255];
const char *backup;
savebuffer_t save;
if (marathonmode)
strcpy(savename, liveeventbackup);
@ -4805,7 +4807,7 @@ void G_SaveGameOver(UINT32 slot, boolean modifylives)
sprintf(savename, savegamename, slot);
backup = va("%s",savename);
length = FIL_ReadFile(savename, &savebuffer);
length = FIL_ReadFile(savename, &save.buffer);
if (!length)
{
CONS_Printf(M_GetText("Couldn't read file %s\n"), savename);
@ -4814,35 +4816,37 @@ void G_SaveGameOver(UINT32 slot, boolean modifylives)
{
char temp[sizeof(timeattackfolder)];
UINT8 *end_p = savebuffer + length;
UINT8 *lives_p;
SINT8 pllives;
save_p = savebuffer;
save.p = save.buffer;
save.size = length;
save.end = save.buffer + save.size;
// Version check
memset(vcheck, 0, sizeof (vcheck));
sprintf(vcheck, (marathonmode ? "back-up %d" : "version %d"), VERSION);
if (strcmp((const char *)save_p, (const char *)vcheck)) BADSAVE
save_p += VERSIONSIZE;
if (strcmp((const char *)save.p, (const char *)vcheck)) BADSAVE
save.p += VERSIONSIZE;
// P_UnArchiveMisc()
(void)READINT16(save_p);
(void)READINT16(save.p);
CHECKPOS
(void)READUINT16(save_p); // emeralds
(void)READUINT16(save.p); // emeralds
CHECKPOS
READSTRINGN(save_p, temp, sizeof(temp)); // mod it belongs to
READSTRINGN(save.p, temp, sizeof(temp)); // mod it belongs to
if (strcmp(temp, timeattackfolder)) BADSAVE
// P_UnArchivePlayer()
CHECKPOS
(void)READUINT16(save_p);
(void)READUINT16(save.p);
CHECKPOS
WRITEUINT8(save_p, numgameovers);
WRITEUINT8(save.p, numgameovers);
CHECKPOS
lives_p = save_p;
pllives = READSINT8(save_p); // lives
lives_p = save.p;
pllives = READSINT8(save.p); // lives
CHECKPOS
if (modifylives && pllives < startinglivesbalance[numgameovers])
{
@ -4850,28 +4854,28 @@ void G_SaveGameOver(UINT32 slot, boolean modifylives)
WRITESINT8(lives_p, pllives);
}
(void)READINT32(save_p); // Score
(void)READINT32(save.p); // Score
CHECKPOS
(void)READINT32(save_p); // continues
(void)READINT32(save.p); // continues
// File end marker check
CHECKPOS
switch (READUINT8(save_p))
switch (READUINT8(save.p))
{
case 0xb7:
{
UINT8 i, banksinuse;
CHECKPOS
banksinuse = READUINT8(save_p);
banksinuse = READUINT8(save.p);
CHECKPOS
if (banksinuse > NUM_LUABANKS)
BADSAVE
for (i = 0; i < banksinuse; i++)
{
(void)READINT32(save_p);
(void)READINT32(save.p);
CHECKPOS
}
if (READUINT8(save_p) != 0x1d)
if (READUINT8(save.p) != 0x1d)
BADSAVE
}
case 0x1d:
@ -4881,7 +4885,7 @@ void G_SaveGameOver(UINT32 slot, boolean modifylives)
}
// done
saved = FIL_WriteFile(backup, savebuffer, length);
saved = FIL_WriteFile(backup, save.buffer, length);
}
cleanup:
@ -4889,8 +4893,8 @@ cleanup:
CONS_Printf(M_GetText("Game saved.\n"));
else if (!saved)
CONS_Alert(CONS_ERROR, M_GetText("Error while writing to %s for save slot %u, base: %s\n"), backup, slot, (marathonmode ? liveeventbackup : savegamename));
Z_Free(savebuffer);
save_p = savebuffer = NULL;
Z_Free(save.buffer);
save.p = save.buffer = NULL;
}
#undef CHECKPOS

View file

@ -785,6 +785,10 @@ char sprnames[NUMSPRITES + 1][5] =
"FLBM",
"UFOB",
"UFOA",
"UFOS",
// First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later
"VIEW",
};
@ -5146,6 +5150,11 @@ state_t states[NUMSTATES] =
// Broly Ki Orb
{SPR_LSSJ, FF_REVERSESUBTRACT|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_BROLY2}, // S_BROLY1
{SPR_NULL, 0, 5*TICRATE, {A_SSMineFlash}, 0, 0, S_NULL}, // S_BROLY2
{SPR_UFOB, 0, -1, {NULL}, 0, 0, S_NULL}, // S_SPECIAL_UFO_POD
{SPR_UFOB, 1|FF_FULLBRIGHT|FF_ANIMATE, -1, {NULL}, 1, 1, S_NULL}, // S_SPECIAL_UFO_OVERLAY
{SPR_UFOA, FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_SPECIAL_UFO_ARM
{SPR_UFOS, 0, -1, {NULL}, 0, 0, S_NULL}, // S_SPECIAL_UFO_STEM
};
mobjinfo_t mobjinfo[NUMMOBJTYPES] =
@ -23622,7 +23631,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
100, // mass
1, // damage
sfx_kc64, // activesound
MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags
MF_SOLID|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
@ -29084,6 +29093,60 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
{ // MT_SPECIAL_UFO
-1, // doomednum
S_CHAOSEMERALD1, // spawnstate
101, // spawnhealth
S_NULL, // seestate
sfx_None, // seesound
8, // reactiontime
sfx_None, // attacksound
S_NULL, // painstate
0, // painchance
sfx_None, // painsound
S_NULL, // meleestate
S_NULL, // missilestate
S_NULL, // deathstate
S_NULL, // xdeathstate
sfx_None, // deathsound
0, // speed
108*FRACUNIT, // radius
72*FRACUNIT, // height
0, // display offset
16, // mass
0, // damage
sfx_None, // activesound
MF_SHOOTABLE|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
{ // MT_SPECIAL_UFO_PIECE
-1, // doomednum
S_INVISIBLE, // spawnstate
1000, // spawnhealth
S_NULL, // seestate
sfx_None, // seesound
8, // reactiontime
sfx_None, // attacksound
S_NULL, // painstate
0, // painchance
sfx_None, // painsound
S_NULL, // meleestate
S_NULL, // missilestate
S_NULL, // deathstate
S_NULL, // xdeathstate
sfx_None, // deathsound
0, // speed
8*FRACUNIT, // radius
16*FRACUNIT, // height
1, // display offset
100, // mass
1, // damage
sfx_None, // activesound
MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY|MF_DONTENCOREMAP|MF_NOSQUISH, // flags
S_NULL // raisestate
},
};
skincolor_t skincolors[MAXSKINCOLORS] = {

View file

@ -1332,6 +1332,10 @@ typedef enum sprite
SPR_FLBM, // Finish line beam
SPR_UFOB,
SPR_UFOA,
SPR_UFOS,
// First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later
SPR_VIEW,
@ -5569,6 +5573,11 @@ typedef enum state
S_BROLY1,
S_BROLY2,
S_SPECIAL_UFO_POD,
S_SPECIAL_UFO_OVERLAY,
S_SPECIAL_UFO_ARM,
S_SPECIAL_UFO_STEM,
S_FIRSTFREESLOT,
S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1,
NUMSTATES
@ -6686,6 +6695,9 @@ typedef enum mobj_type
MT_BROLY,
MT_SPECIAL_UFO,
MT_SPECIAL_UFO_PIECE,
MT_FIRSTFREESLOT,
MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1,
NUMMOBJTYPES

View file

@ -184,6 +184,9 @@ UINT32 K_GetPlayerDontDrawFlag(player_t *player)
{
UINT32 flag = 0;
if (player == NULL)
return flag;
if (player == &players[displayplayers[0]])
flag = RF_DONTDRAWP1;
else if (r_splitscreen >= 1 && player == &players[displayplayers[1]])
@ -507,6 +510,51 @@ void K_RunItemCooldowns(void)
}
}
boolean K_TimeAttackRules(void)
{
UINT8 playing = 0;
UINT8 i;
if (specialStage.active == true)
{
// Kind of a hack -- Special Stages
// are expected to be 1-player, so
// we won't use the Time Attack changes
return false;
}
if (modeattacking != ATTACKING_NONE)
{
// Time Attack obviously uses Time Attack rules :p
return true;
}
if (battlecapsules == true)
{
// Break the Capsules always uses Time Attack
// rules, since you can bring 2-4 players in
// via Grand Prix.
return true;
}
for (i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i] == false || players[i].spectator == true)
{
continue;
}
playing++;
if (playing > 1)
{
break;
}
}
// Use Time Attack gameplay rules with only 1P.
return (playing <= 1);
}
//}
//{ SRB2kart p_user.c Stuff
@ -530,6 +578,10 @@ static fixed_t K_PlayerWeight(mobj_t *mobj, mobj_t *against)
{
weight = 0; // This player does not cause any bump action
}
else if (against && against->type == MT_SPECIAL_UFO)
{
weight = 0;
}
else
{
// Applies rubberbanding, to prevent rubberbanding bots
@ -1029,7 +1081,7 @@ static void K_UpdateOffroad(player_t *player)
player->offroad = 0;
}
static void K_DrawDraftCombiring(player_t *player, player_t *victim, fixed_t curdist, fixed_t maxdist, boolean transparent)
static void K_DrawDraftCombiring(player_t *player, mobj_t *victim, fixed_t curdist, fixed_t maxdist, boolean transparent)
{
#define CHAOTIXBANDLEN 15
#define CHAOTIXBANDCOLORS 9
@ -1060,9 +1112,9 @@ static void K_DrawDraftCombiring(player_t *player, player_t *victim, fixed_t cur
c = FixedMul(CHAOTIXBANDCOLORS<<FRACBITS, FixedDiv(curdist-minimumdist, maxdist-minimumdist)) >> FRACBITS;
}
stepx = (victim->mo->x - player->mo->x) / CHAOTIXBANDLEN;
stepy = (victim->mo->y - player->mo->y) / CHAOTIXBANDLEN;
stepz = ((victim->mo->z + (victim->mo->height / 2)) - (player->mo->z + (player->mo->height / 2))) / CHAOTIXBANDLEN;
stepx = (victim->x - player->mo->x) / CHAOTIXBANDLEN;
stepy = (victim->y - player->mo->y) / CHAOTIXBANDLEN;
stepz = ((victim->z + (victim->height / 2)) - (player->mo->z + (player->mo->height / 2))) / CHAOTIXBANDLEN;
curx = player->mo->x + stepx;
cury = player->mo->y + stepy;
@ -1096,7 +1148,7 @@ static void K_DrawDraftCombiring(player_t *player, player_t *victim, fixed_t cur
if (transparent)
band->renderflags |= RF_GHOSTLY;
band->renderflags |= RF_DONTDRAW & ~(K_GetPlayerDontDrawFlag(player) | K_GetPlayerDontDrawFlag(victim));
band->renderflags |= RF_DONTDRAW & ~(K_GetPlayerDontDrawFlag(player) | K_GetPlayerDontDrawFlag(victim->player));
}
curx += stepx;
@ -1121,6 +1173,129 @@ static boolean K_HasInfiniteTether(player_t *player)
return false;
}
static boolean K_TryDraft(player_t *player, mobj_t *dest, fixed_t minDist, fixed_t draftdistance, UINT8 leniency)
{
//#define EASYDRAFTTEST
fixed_t dist, olddraft;
fixed_t theirSpeed = 0;
#ifndef EASYDRAFTTEST
angle_t yourangle, theirangle, diff;
#endif
#ifndef EASYDRAFTTEST
// Don't draft on yourself :V
if (dest->player && dest->player == player)
{
return false;
}
#endif
if (dest->player != NULL)
{
// No tethering off of the guy who got the starting bonus :P
if (dest->player->startboost > 0)
{
return false;
}
theirSpeed = dest->player->speed;
}
else
{
theirSpeed = R_PointToDist2(0, 0, dest->momx, dest->momy);
}
// They're not enough speed to draft off of them.
if (theirSpeed < 20 * dest->scale)
{
return false;
}
#ifndef EASYDRAFTTEST
yourangle = K_MomentumAngle(player->mo);
theirangle = K_MomentumAngle(dest);
// Not in front of this player.
diff = AngleDelta(R_PointToAngle2(player->mo->x, player->mo->y, dest->x, dest->y), yourangle);
if (diff > ANG10)
{
return false;
}
// Not moving in the same direction.
diff = AngleDelta(yourangle, theirangle);
if (diff > ANGLE_90)
{
return false;
}
#endif
dist = P_AproxDistance(P_AproxDistance(dest->x - player->mo->x, dest->y - player->mo->y), dest->z - player->mo->z);
#ifndef EASYDRAFTTEST
// TOO close to draft.
if (dist < minDist)
{
return false;
}
// Not close enough to draft.
if (dist > draftdistance && draftdistance > 0)
{
return false;
}
#endif
olddraft = player->draftpower;
player->draftleeway = leniency;
if (dest->player != NULL)
{
player->lastdraft = dest->player - players;
}
else
{
player->lastdraft = MAXPLAYERS;
}
// Draft power is used later in K_GetKartBoostPower, ranging from 0 for normal speed and FRACUNIT for max draft speed.
// How much this increments every tic biases toward acceleration! (min speed gets 1.5% per tic, max speed gets 0.5% per tic)
if (player->draftpower < FRACUNIT)
{
fixed_t add = (FRACUNIT/200) + ((9 - player->kartspeed) * ((3*FRACUNIT)/1600));;
player->draftpower += add;
if (player->bot && player->botvars.rival)
{
// Double speed for the rival!
player->draftpower += add;
}
if (gametype == GT_BATTLE)
{
// TODO: gametyperules
// Double speed in Battle
player->draftpower += add;
}
}
if (player->draftpower > FRACUNIT)
{
player->draftpower = FRACUNIT;
}
// Play draft finish noise
if (olddraft < FRACUNIT && player->draftpower >= FRACUNIT)
{
S_StartSound(player->mo, sfx_cdfm62);
}
// Spawn in the visual!
K_DrawDraftCombiring(player, dest, dist, draftdistance, false);
return true;
}
/** \brief Updates the player's drafting values once per frame
\param player player object passed from K_KartPlayerThink
@ -1129,6 +1304,9 @@ static boolean K_HasInfiniteTether(player_t *player)
*/
static void K_UpdateDraft(player_t *player)
{
const boolean addUfo = ((specialStage.active == true)
&& (specialStage.ufo != NULL && P_MobjWasRemoved(specialStage.ufo) == false));
fixed_t topspd = K_GetKartSpeed(player, false, false);
fixed_t draftdistance;
fixed_t minDist;
@ -1166,104 +1344,43 @@ static void K_UpdateDraft(player_t *player)
}
// Not enough speed to draft.
if (player->speed >= 20*player->mo->scale)
if (player->speed >= 20 * player->mo->scale)
{
//#define EASYDRAFTTEST
if (addUfo == true)
{
// Tether off of the UFO!
if (K_TryDraft(player, specialStage.ufo, minDist, draftdistance, leniency) == true)
{
return; // Finished doing our draft.
}
}
// Let's hunt for players to draft off of!
for (i = 0; i < MAXPLAYERS; i++)
{
fixed_t dist, olddraft;
#ifndef EASYDRAFTTEST
angle_t yourangle, theirangle, diff;
#endif
player_t *otherPlayer = NULL;
if (!playeringame[i] || players[i].spectator || !players[i].mo)
continue;
#ifndef EASYDRAFTTEST
// Don't draft on yourself :V
if (&players[i] == player)
continue;
#endif
// Not enough speed to draft off of.
if (players[i].speed < 20*players[i].mo->scale)
continue;
// No tethering off of the guy who got the starting bonus :P
if (players[i].startboost > 0)
continue;
#ifndef EASYDRAFTTEST
yourangle = K_MomentumAngle(player->mo);
theirangle = K_MomentumAngle(players[i].mo);
diff = R_PointToAngle2(player->mo->x, player->mo->y, players[i].mo->x, players[i].mo->y) - yourangle;
if (diff > ANGLE_180)
diff = InvAngle(diff);
// Not in front of this player.
if (diff > ANG10)
continue;
diff = yourangle - theirangle;
if (diff > ANGLE_180)
diff = InvAngle(diff);
// Not moving in the same direction.
if (diff > ANGLE_90)
continue;
#endif
dist = P_AproxDistance(P_AproxDistance(players[i].mo->x - player->mo->x, players[i].mo->y - player->mo->y), players[i].mo->z - player->mo->z);
#ifndef EASYDRAFTTEST
// TOO close to draft.
if (dist < minDist)
continue;
// Not close enough to draft.
if (dist > draftdistance && draftdistance > 0)
continue;
#endif
olddraft = player->draftpower;
player->draftleeway = leniency;
player->lastdraft = i;
// Draft power is used later in K_GetKartBoostPower, ranging from 0 for normal speed and FRACUNIT for max draft speed.
// How much this increments every tic biases toward acceleration! (min speed gets 1.5% per tic, max speed gets 0.5% per tic)
if (player->draftpower < FRACUNIT)
if (playeringame[i] == false)
{
fixed_t add = (FRACUNIT/200) + ((9 - player->kartspeed) * ((3*FRACUNIT)/1600));;
player->draftpower += add;
if (player->bot && player->botvars.rival)
{
// Double speed for the rival!
player->draftpower += add;
}
if (gametype == GT_BATTLE)
{
// TODO: gametyperules
// Double speed in Battle
player->draftpower += add;
}
continue;
}
if (player->draftpower > FRACUNIT)
player->draftpower = FRACUNIT;
otherPlayer = &players[i];
// Play draft finish noise
if (olddraft < FRACUNIT && player->draftpower >= FRACUNIT)
S_StartSound(player->mo, sfx_cdfm62);
if (otherPlayer->spectator == true)
{
continue;
}
// Spawn in the visual!
K_DrawDraftCombiring(player, &players[i], dist, draftdistance, false);
if (otherPlayer->mo == NULL || P_MobjWasRemoved(otherPlayer->mo) == true)
{
continue;
}
return; // Finished doing our draft.
if (K_TryDraft(player, otherPlayer->mo, minDist, draftdistance, leniency) == true)
{
return; // Finished doing our draft.
}
}
}
@ -1284,7 +1401,13 @@ static void K_UpdateDraft(player_t *player)
{
player_t *victim = &players[player->lastdraft];
fixed_t dist = P_AproxDistance(P_AproxDistance(victim->mo->x - player->mo->x, victim->mo->y - player->mo->y), victim->mo->z - player->mo->z);
K_DrawDraftCombiring(player, victim, dist, draftdistance, true);
K_DrawDraftCombiring(player, victim->mo, dist, draftdistance, true);
}
else if (addUfo == true)
{
// kind of a hack to not have to mess with how lastdraft works
fixed_t dist = P_AproxDistance(P_AproxDistance(specialStage.ufo->x - player->mo->x, specialStage.ufo->y - player->mo->y), specialStage.ufo->z - player->mo->z);
K_DrawDraftCombiring(player, specialStage.ufo, dist, draftdistance, true);
}
}
else // Remove draft speed boost.
@ -3135,7 +3258,7 @@ boolean K_PlayerShrinkCheat(player_t *player)
return (
(player->pflags & PF_SHRINKACTIVE)
&& (player->bot == false)
&& (modeattacking == false) // Anyone want to make another record attack category?
&& (modeattacking == ATTACKING_NONE) // Anyone want to make another record attack category?
);
}
@ -6664,12 +6787,18 @@ static void K_MoveHeldObjects(player_t *player)
}
}
player_t *K_FindJawzTarget(mobj_t *actor, player_t *source, angle_t range)
mobj_t *K_FindJawzTarget(mobj_t *actor, player_t *source, angle_t range)
{
fixed_t best = INT32_MAX;
player_t *wtarg = NULL;
mobj_t *wtarg = NULL;
INT32 i;
if (specialStage.active == true)
{
// Always target the UFO.
return specialStage.ufo;
}
for (i = 0; i < MAXPLAYERS; i++)
{
angle_t thisang = ANGLE_MAX;
@ -6685,7 +6814,7 @@ player_t *K_FindJawzTarget(mobj_t *actor, player_t *source, angle_t range)
player = &players[i];
// Don't target yourself, stupid.
if (player == source)
if (source != NULL && player == source)
{
continue;
}
@ -6724,7 +6853,7 @@ player_t *K_FindJawzTarget(mobj_t *actor, player_t *source, angle_t range)
if (gametyperules & GTR_CIRCUIT)
{
if (player->position >= source->position)
if (source != NULL && player->position >= source->position)
{
// Don't pay attention to people who aren't above your position
continue;
@ -6766,7 +6895,7 @@ player_t *K_FindJawzTarget(mobj_t *actor, player_t *source, angle_t range)
if (thisScore < best)
{
wtarg = player;
wtarg = player->mo;
best = thisScore;
}
}
@ -7895,24 +8024,32 @@ void K_KartPlayerAfterThink(player_t *player)
// Jawz reticule (seeking)
if (player->itemtype == KITEM_JAWZ && (player->pflags & PF_ITEMOUT))
{
INT32 lastTargID = player->lastjawztarget;
player_t *lastTarg = NULL;
player_t *targ = NULL;
const INT32 lastTargID = player->lastjawztarget;
mobj_t *lastTarg = NULL;
INT32 targID = MAXPLAYERS;
mobj_t *targ = NULL;
mobj_t *ret = NULL;
if ((lastTargID >= 0 && lastTargID <= MAXPLAYERS)
if (specialStage.active == true && lastTargID == MAXPLAYERS)
{
// Aiming at the UFO.
lastTarg = specialStage.ufo;
}
else if ((lastTargID >= 0 && lastTargID <= MAXPLAYERS)
&& playeringame[lastTargID] == true)
{
if (players[lastTargID].spectator == false)
{
lastTarg = &players[lastTargID];
lastTarg = players[lastTargID].mo;
}
}
if (player->throwdir == -1)
{
// Backwards Jawz targets yourself.
targ = player;
targ = player->mo;
player->jawztargetdelay = 0;
}
else
@ -7921,9 +8058,14 @@ void K_KartPlayerAfterThink(player_t *player)
targ = K_FindJawzTarget(player->mo, player, ANGLE_45);
}
if (targ != NULL && targ->mo != NULL && P_MobjWasRemoved(targ->mo) == false)
if (targ != NULL && P_MobjWasRemoved(targ) == false)
{
if (targ - players == lastTargID)
if (targ->player != NULL)
{
targID = targ->player - players;
}
if (targID == lastTargID)
{
// Increment delay.
if (player->jawztargetdelay < 10)
@ -7942,33 +8084,33 @@ void K_KartPlayerAfterThink(player_t *player)
else
{
// Allow a swap.
if (P_IsDisplayPlayer(player) || P_IsDisplayPlayer(targ))
if (P_IsDisplayPlayer(player) || P_IsDisplayPlayer(targ->player))
{
S_StartSound(NULL, sfx_s3k89);
}
else
{
S_StartSound(targ->mo, sfx_s3k89);
S_StartSound(targ, sfx_s3k89);
}
player->lastjawztarget = targ - players;
player->lastjawztarget = targID;
player->jawztargetdelay = 5;
}
}
}
if (targ == NULL || targ->mo == NULL || P_MobjWasRemoved(targ->mo) == true)
if (targ == NULL || P_MobjWasRemoved(targ) == true)
{
player->lastjawztarget = -1;
player->jawztargetdelay = 0;
return;
}
ret = P_SpawnMobj(targ->mo->x, targ->mo->y, targ->mo->z, MT_PLAYERRETICULE);
ret->old_x = targ->mo->old_x;
ret->old_y = targ->mo->old_y;
ret->old_z = targ->mo->old_z;
P_SetTarget(&ret->target, targ->mo);
ret = P_SpawnMobj(targ->x, targ->y, targ->z, MT_PLAYERRETICULE);
ret->old_x = targ->old_x;
ret->old_y = targ->old_y;
ret->old_z = targ->old_z;
P_SetTarget(&ret->target, targ);
ret->frame |= ((leveltime % 10) / 2);
ret->tics = 1;
ret->color = player->skincolor;

View file

@ -56,6 +56,9 @@ UINT8 K_ItemResultToAmount(SINT8 getitem);
tic_t K_GetItemCooldown(SINT8 itemResult);
void K_SetItemCooldown(SINT8 itemResult, tic_t time);
void K_RunItemCooldowns(void);
boolean K_TimeAttackRules(void);
fixed_t K_GetMobjWeight(mobj_t *mobj, mobj_t *against);
boolean K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2);
boolean K_KartSolidBounce(mobj_t *bounceMobj, mobj_t *solidMobj);
@ -118,7 +121,7 @@ void K_UpdateHnextList(player_t *player, boolean clean);
void K_DropHnextList(player_t *player, boolean keepshields);
void K_RepairOrbitChain(mobj_t *orbit);
void K_CalculateBananaSlope(mobj_t *mobj, fixed_t x, fixed_t y, fixed_t z, fixed_t radius, fixed_t height, boolean flip, boolean player);
player_t *K_FindJawzTarget(mobj_t *actor, player_t *source, angle_t range);
mobj_t *K_FindJawzTarget(mobj_t *actor, player_t *source, angle_t range);
INT32 K_GetKartRingPower(player_t *player, boolean boosted);
void K_UpdateDistanceFromFinishLine(player_t *const player);
boolean K_CheckPlayersRespawnColliding(INT32 playernum, fixed_t x, fixed_t y);

View file

@ -58,4 +58,15 @@ void Obj_DuelBombInit(mobj_t *bomb);
mobj_t *Obj_SpawnBrolyKi(mobj_t *source, tic_t duration);
void Obj_BrolyKiThink(mobj_t *ki);
/* Special Stage UFO */
waypoint_t *K_GetSpecialUFOWaypoint(mobj_t *ufo);
void Obj_SpecialUFOThinker(mobj_t *ufo);
boolean Obj_SpecialUFODamage(mobj_t *ufo, mobj_t *inflictor, mobj_t *source, UINT8 damageType);
void Obj_PlayerUFOCollide(mobj_t *ufo, mobj_t *other);
void Obj_UFOPieceThink(mobj_t *piece);
void Obj_UFOPieceDead(mobj_t *piece);
void Obj_UFOPieceRemoved(mobj_t *piece);
mobj_t *Obj_CreateSpecialUFO(void);
UINT32 K_GetSpecialUFODistance(void);
#endif/*k_objects_H*/

View file

@ -10,9 +10,10 @@
/// \file k_profiles.c
/// \brief implements methods for profiles etc.
#include "doomtype.h"
#include "d_main.h" // pandf
#include "byteptr.h" // READ/WRITE macros
#include "p_saveg.h" // save_p
#include "p_saveg.h" // savebuffer_t
#include "m_misc.h" //FIL_WriteFile()
#include "k_profiles.h"
#include "z_zone.h"
@ -211,68 +212,68 @@ void PR_InitNewProfile(void)
PR_AddProfile(dprofile);
}
static UINT8 *savebuffer;
void PR_SaveProfiles(void)
{
size_t length = 0;
const size_t headerlen = strlen(PROFILEHEADER);
UINT8 i, j, k;
savebuffer_t save;
save_p = savebuffer = (UINT8 *)malloc(sizeof(UINT32) + (numprofiles * sizeof(profile_t)));
if (!save_p)
save.size = sizeof(UINT32) + (numprofiles * sizeof(profile_t));
save.p = save.buffer = (UINT8 *)malloc(save.size);
if (!save.p)
{
I_Error("No more free memory for saving profiles\n");
return;
}
save.end = save.buffer + save.size;
// Add header.
WRITESTRINGN(save_p, PROFILEHEADER, headerlen);
WRITEUINT8(save_p, PROFILEVER);
WRITEUINT8(save_p, numprofiles);
WRITESTRINGN(save.p, PROFILEHEADER, headerlen);
WRITEUINT8(save.p, PROFILEVER);
WRITEUINT8(save.p, numprofiles);
for (i = 1; i < numprofiles; i++)
{
// Names.
WRITESTRINGN(save_p, profilesList[i]->profilename, PROFILENAMELEN);
WRITESTRINGN(save_p, profilesList[i]->playername, MAXPLAYERNAME);
WRITESTRINGN(save.p, profilesList[i]->profilename, PROFILENAMELEN);
WRITESTRINGN(save.p, profilesList[i]->playername, MAXPLAYERNAME);
// Character and colour.
WRITESTRINGN(save_p, profilesList[i]->skinname, SKINNAMESIZE);
WRITEUINT16(save_p, profilesList[i]->color);
WRITESTRINGN(save.p, profilesList[i]->skinname, SKINNAMESIZE);
WRITEUINT16(save.p, profilesList[i]->color);
// Follower and colour.
WRITESTRINGN(save_p, profilesList[i]->follower, SKINNAMESIZE);
WRITEUINT16(save_p, profilesList[i]->followercolor);
WRITESTRINGN(save.p, profilesList[i]->follower, SKINNAMESIZE);
WRITEUINT16(save.p, profilesList[i]->followercolor);
// PWR.
for (j = 0; j < PWRLV_NUMTYPES; j++)
{
WRITEUINT16(save_p, profilesList[i]->powerlevels[j]);
WRITEUINT16(save.p, profilesList[i]->powerlevels[j]);
}
// Consvars.
WRITEUINT8(save_p, profilesList[i]->kickstartaccel);
WRITEUINT8(save.p, profilesList[i]->kickstartaccel);
// Controls.
for (j = 0; j < num_gamecontrols; j++)
{
for (k = 0; k < MAXINPUTMAPPING; k++)
{
WRITEINT32(save_p, profilesList[i]->controls[j][k]);
WRITEINT32(save.p, profilesList[i]->controls[j][k]);
}
}
}
length = save_p - savebuffer;
length = save.p - save.buffer;
if (!FIL_WriteFile(va(pandf, srb2home, PROFILESFILE), savebuffer, length))
if (!FIL_WriteFile(va(pandf, srb2home, PROFILESFILE), save.buffer, length))
{
free(savebuffer);
free(save.buffer);
I_Error("Couldn't save profiles. Are you out of Disk space / playing in a protected folder?");
}
free(savebuffer);
save_p = savebuffer = NULL;
free(save.buffer);
}
void PR_LoadProfiles(void)
@ -288,8 +289,9 @@ void PR_LoadProfiles(void)
gamecontroldefault,
true
);
savebuffer_t save;
length = FIL_ReadFile(va(pandf, srb2home, PROFILESFILE), &savebuffer);
length = FIL_ReadFile(va(pandf, srb2home, PROFILESFILE), &save.buffer);
if (!length)
{
// No profiles. Add the default one.
@ -297,29 +299,29 @@ void PR_LoadProfiles(void)
return;
}
save_p = savebuffer;
save.p = save.buffer;
if (strncmp(PROFILEHEADER, (const char *)savebuffer, headerlen))
if (strncmp(PROFILEHEADER, (const char *)save.buffer, headerlen))
{
const char *gdfolder = "the Ring Racers folder";
if (strcmp(srb2home,"."))
gdfolder = srb2home;
Z_Free(savebuffer);
save_p = NULL;
Z_Free(save.buffer);
save.p = NULL;
I_Error("Not a valid Profile file.\nDelete %s (maybe in %s) and try again.", PROFILESFILE, gdfolder);
}
save_p += headerlen;
save.p += headerlen;
version = READUINT8(save_p);
version = READUINT8(save.p);
if (version > PROFILEVER)
{
Z_Free(savebuffer);
save_p = NULL;
Z_Free(save.buffer);
save.p = NULL;
I_Error("Existing %s is from the future! (expected %d, got %d)", PROFILESFILE, PROFILEVER, version);
}
numprofiles = READUINT8(save_p);
numprofiles = READUINT8(save.p);
if (numprofiles > MAXPROFILES)
numprofiles = MAXPROFILES;
@ -331,12 +333,12 @@ void PR_LoadProfiles(void)
profilesList[i]->version = PROFILEVER;
// Names.
READSTRINGN(save_p, profilesList[i]->profilename, PROFILENAMELEN);
READSTRINGN(save_p, profilesList[i]->playername, MAXPLAYERNAME);
READSTRINGN(save.p, profilesList[i]->profilename, PROFILENAMELEN);
READSTRINGN(save.p, profilesList[i]->playername, MAXPLAYERNAME);
// Character and colour.
READSTRINGN(save_p, profilesList[i]->skinname, SKINNAMESIZE);
profilesList[i]->color = READUINT16(save_p);
READSTRINGN(save.p, profilesList[i]->skinname, SKINNAMESIZE);
profilesList[i]->color = READUINT16(save.p);
if (profilesList[i]->color == SKINCOLOR_NONE)
{
@ -349,8 +351,8 @@ void PR_LoadProfiles(void)
}
// Follower and colour.
READSTRINGN(save_p, profilesList[i]->follower, SKINNAMESIZE);
profilesList[i]->followercolor = READUINT16(save_p);
READSTRINGN(save.p, profilesList[i]->follower, SKINNAMESIZE);
profilesList[i]->followercolor = READUINT16(save.p);
if (profilesList[i]->followercolor == FOLLOWERCOLOR_MATCH
|| profilesList[i]->followercolor == FOLLOWERCOLOR_OPPOSITE)
@ -367,7 +369,7 @@ void PR_LoadProfiles(void)
// PWR.
for (j = 0; j < PWRLV_NUMTYPES; j++)
{
profilesList[i]->powerlevels[j] = READUINT16(save_p);
profilesList[i]->powerlevels[j] = READUINT16(save.p);
if (profilesList[i]->powerlevels[j] < PWRLVRECORD_MIN
|| profilesList[i]->powerlevels[j] > PWRLVRECORD_MAX)
{
@ -377,7 +379,7 @@ void PR_LoadProfiles(void)
}
// Consvars.
profilesList[i]->kickstartaccel = (boolean)READUINT8(save_p);
profilesList[i]->kickstartaccel = (boolean)READUINT8(save.p);
// Controls.
for (j = 0; j < num_gamecontrols; j++)
@ -396,7 +398,7 @@ void PR_LoadProfiles(void)
for (k = 0; k < MAXINPUTMAPPING; k++)
{
profilesList[i]->controls[j][k] = READINT32(save_p);
profilesList[i]->controls[j][k] = READINT32(save.p);
}
}
}

View file

@ -110,7 +110,6 @@ static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][8] =
static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS-1][2] =
{
//K L
{ 2, 1 }, // Sneaker
{ 0, 0 }, // Rocket Sneaker
{ 4, 1 }, // Invincibility
@ -143,6 +142,40 @@ static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS-1][2] =
{ 0, 0 } // Gachabom x3
};
static UINT8 K_KartItemOddsSpecial[NUMKARTRESULTS-1][4] =
{
{ 1, 1, 0, 0 }, // Sneaker
{ 0, 0, 0, 0 }, // Rocket Sneaker
{ 0, 0, 0, 0 }, // Invincibility
{ 0, 0, 0, 0 }, // Banana
{ 0, 0, 0, 0 }, // Eggman Monitor
{ 1, 1, 0, 0 }, // Orbinaut
{ 1, 1, 0, 0 }, // Jawz
{ 0, 0, 0, 0 }, // Mine
{ 0, 0, 0, 0 }, // Land Mine
{ 0, 0, 0, 0 }, // Ballhog
{ 0, 0, 0, 1 }, // Self-Propelled Bomb
{ 0, 0, 0, 0 }, // Grow
{ 0, 0, 0, 0 }, // Shrink
{ 0, 0, 0, 0 }, // Lightning Shield
{ 0, 0, 0, 0 }, // Bubble Shield
{ 0, 0, 0, 0 }, // Flame Shield
{ 0, 0, 0, 0 }, // Hyudoro
{ 0, 0, 0, 0 }, // Pogo Spring
{ 0, 0, 0, 0 }, // Super Ring
{ 0, 0, 0, 0 }, // Kitchen Sink
{ 0, 0, 0, 0 }, // Drop Target
{ 0, 0, 0, 0 }, // Garden Top
{ 0, 0, 0, 0 }, // Gachabom
{ 0, 1, 1, 0 }, // Sneaker x2
{ 0, 0, 1, 1 }, // Sneaker x3
{ 0, 0, 0, 0 }, // Banana x3
{ 0, 1, 1, 0 }, // Orbinaut x3
{ 0, 0, 1, 1 }, // Orbinaut x4
{ 0, 0, 1, 1 }, // Jawz x2
{ 0, 0, 0, 0 } // Gachabom x3
};
static kartitems_t K_KartItemReelTimeAttack[] =
{
KITEM_SNEAKER,
@ -326,7 +359,6 @@ static UINT32 K_GetItemRouletteDistance(const player_t *player, UINT8 numPlayers
return 0;
}
#if 0
if (specialStage.active == true)
{
UINT32 ufoDis = K_GetSpecialUFODistance();
@ -343,7 +375,6 @@ static UINT32 K_GetItemRouletteDistance(const player_t *player, UINT8 numPlayers
}
}
else
#endif
{
UINT8 i;
for (i = 0; i < MAXPLAYERS; i++)
@ -475,6 +506,11 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette,
I_Assert(pos < 2); // DO NOT allow positions past the bounds of the table
newOdds = K_KartItemOddsBattle[item-1][pos];
}
else if (specialStage.active == true)
{
I_Assert(pos < 4); // Ditto
newOdds = K_KartItemOddsSpecial[item-1][pos];
}
else
{
I_Assert(pos < 8); // Ditto
@ -537,29 +573,32 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette,
return 0;
}
if (roulette->firstDist < ENDDIST*2 // No SPB when 1st is almost done
|| position == 1) // No SPB for 1st ever
if (specialStage.active == false)
{
return 0;
}
else
{
const UINT32 dist = max(0, ((signed)roulette->secondToFirst) - SPBSTARTDIST);
const UINT32 distRange = SPBFORCEDIST - SPBSTARTDIST;
const fixed_t maxOdds = 20 << FRACBITS;
fixed_t multiplier = FixedDiv(dist, distRange);
if (multiplier < 0)
if (roulette->firstDist < ENDDIST*2 // No SPB when 1st is almost done
|| position == 1) // No SPB for 1st ever
{
multiplier = 0;
return 0;
}
if (multiplier > FRACUNIT)
else
{
multiplier = FRACUNIT;
}
const UINT32 dist = max(0, ((signed)roulette->secondToFirst) - SPBSTARTDIST);
const UINT32 distRange = SPBFORCEDIST - SPBSTARTDIST;
const fixed_t maxOdds = 20 << FRACBITS;
fixed_t multiplier = FixedDiv(dist, distRange);
newOdds = FixedMul(maxOdds, multiplier);
if (multiplier < 0)
{
multiplier = 0;
}
if (multiplier > FRACUNIT)
{
multiplier = FRACUNIT;
}
newOdds = FixedMul(maxOdds, multiplier);
}
}
break;
}
@ -666,6 +705,11 @@ static UINT8 K_FindUseodds(const player_t *player, itemroulette_t *const roulett
oddsValid[i] = false;
continue;
}
else if (specialStage.active == true && i > 3)
{
oddsValid[i] = false;
continue;
}
for (j = 1; j < NUMKARTRESULTS; j++)
{
@ -690,14 +734,24 @@ static UINT8 K_FindUseodds(const player_t *player, itemroulette_t *const roulett
}
else
{
SETUPDISTTABLE(0,1);
SETUPDISTTABLE(1,1);
SETUPDISTTABLE(2,1);
SETUPDISTTABLE(3,2);
SETUPDISTTABLE(4,2);
SETUPDISTTABLE(5,3);
SETUPDISTTABLE(6,3);
SETUPDISTTABLE(7,1);
if (specialStage.active == true) // Special Stages
{
SETUPDISTTABLE(0,2);
SETUPDISTTABLE(1,2);
SETUPDISTTABLE(2,3);
SETUPDISTTABLE(3,1);
}
else
{
SETUPDISTTABLE(0,1);
SETUPDISTTABLE(1,1);
SETUPDISTTABLE(2,1);
SETUPDISTTABLE(3,2);
SETUPDISTTABLE(4,2);
SETUPDISTTABLE(5,3);
SETUPDISTTABLE(6,3);
SETUPDISTTABLE(7,1);
}
for (i = 0; i < totalSize; i++)
{
@ -754,6 +808,11 @@ static boolean K_ForcedSPB(const player_t *player, itemroulette_t *const roulett
return false;
}
if (specialStage.active == true)
{
return false;
}
if (player == NULL)
{
return false;
@ -845,19 +904,36 @@ static void K_InitRoulette(itemroulette_t *const roulette)
roulette->exiting++;
}
if (players[i].position == 1)
if (specialStage.active == true)
{
roulette->firstDist = K_UndoMapScaling(players[i].distancetofinish);
UINT32 dis = K_UndoMapScaling(players[i].distancetofinish);
if (dis < roulette->secondDist)
{
roulette->secondDist = dis;
}
}
if (players[i].position == 2)
else
{
roulette->secondDist = K_UndoMapScaling(players[i].distancetofinish);
if (players[i].position == 1)
{
roulette->firstDist = K_UndoMapScaling(players[i].distancetofinish);
}
if (players[i].position == 2)
{
roulette->secondDist = K_UndoMapScaling(players[i].distancetofinish);
}
}
}
if (specialStage.active == true)
{
roulette->firstDist = K_UndoMapScaling(K_GetSpecialUFODistance());
}
// Calculate 2nd's distance from 1st, for SPB
if (roulette->firstDist != UINT32_MAX && roulette->secondDist != UINT32_MAX)
if (roulette->firstDist != UINT32_MAX && roulette->secondDist != UINT32_MAX
&& roulette->secondDist > roulette->firstDist)
{
roulette->secondToFirst = roulette->secondDist - roulette->firstDist;
roulette->secondToFirst = K_ScaleItemDistance(roulette->secondToFirst, 16 - roulette->playing); // Reversed scaling
@ -964,7 +1040,7 @@ static void K_CalculateRouletteSpeed(itemroulette_t *const roulette)
fixed_t progress = 0;
fixed_t total = 0;
if (modeattacking || roulette->playing <= 1)
if (K_TimeAttackRules() == true)
{
// Time Attack rules; use a consistent speed.
roulette->tics = roulette->speed = ROULETTE_SPEED_TIMEATTACK;
@ -1047,7 +1123,7 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet
return;
}
else if (modeattacking || roulette->playing <= 1)
else if (K_TimeAttackRules() == true)
{
switch (gametype)
{

View file

@ -20,6 +20,7 @@
#include "st_stuff.h"
#include "z_zone.h"
#include "k_waypoint.h"
#include "k_objects.h"
struct specialStage specialStage;
@ -43,6 +44,7 @@ void K_InitSpecialStage(void)
INT32 i;
specialStage.beamDist = UINT32_MAX; // TODO: make proper value
P_SetTarget(&specialStage.ufo, Obj_CreateSpecialUFO());
for (i = 0; i < MAXPLAYERS; i++)
{

View file

@ -22,7 +22,7 @@ extern struct specialStage
boolean encore; ///< Copy of encore, just to make sure you can't cheat it with cvars
UINT32 beamDist; ///< Where the exit beam is.
mobj_t *capsule; ///< The Chaos Emerald capsule.
mobj_t *ufo; ///< The Chaos Emerald capsule.
} specialStage;
/*--------------------------------------------------

View file

@ -1070,6 +1070,45 @@ static boolean K_WaypointPathfindReachedEnd(void *data, void *setupData)
return isEnd;
}
/*--------------------------------------------------
static boolean K_WaypointPathfindNextValid(void *data, void *setupData)
Returns if the current waypoint data has a next waypoint.
Input Arguments:-
data - Should point to a pathfindnode_t to compare
setupData - Should point to the pathfindsetup_t to compare
Return:-
True if the waypoint has a next waypoint, false otherwise.
--------------------------------------------------*/
static boolean K_WaypointPathfindNextValid(void *data, void *setupData)
{
boolean nextValid = false;
if (data == NULL || setupData == NULL)
{
CONS_Debug(DBG_GAMELOGIC, "K_WaypointPathfindNextValid received NULL data.\n");
}
else
{
pathfindnode_t *node = (pathfindnode_t *)data;
pathfindsetup_t *setup = (pathfindsetup_t *)setupData;
waypoint_t *wp = (waypoint_t *)node->nodedata;
if (setup->getconnectednodes == K_WaypointPathfindGetPrev)
{
nextValid = (wp->numprevwaypoints > 0U);
}
else
{
nextValid = (wp->numnextwaypoints > 0U);
}
}
return nextValid;
}
/*--------------------------------------------------
static boolean K_WaypointPathfindReachedGScore(void *data, void *setupData)
@ -1094,8 +1133,9 @@ static boolean K_WaypointPathfindReachedGScore(void *data, void *setupData)
{
pathfindnode_t *node = (pathfindnode_t *)data;
pathfindsetup_t *setup = (pathfindsetup_t *)setupData;
boolean nextValid = K_WaypointPathfindNextValid(data, setupData);
scoreReached = (node->gscore >= setup->endgscore);
scoreReached = (node->gscore >= setup->endgscore) || (nextValid == false);
}
return scoreReached;
@ -1127,8 +1167,9 @@ static boolean K_WaypointPathfindReachedGScoreSpawnable(void *data, void *setupD
pathfindnode_t *node = (pathfindnode_t *)data;
pathfindsetup_t *setup = (pathfindsetup_t *)setupData;
waypoint_t *wp = (waypoint_t *)node->nodedata;
boolean nextValid = K_WaypointPathfindNextValid(data, setupData);
scoreReached = (node->gscore >= setup->endgscore);
scoreReached = (node->gscore >= setup->endgscore) || (nextValid == false);
spawnable = K_GetWaypointIsSpawnpoint(wp);
}
@ -1251,13 +1292,6 @@ boolean K_PathfindThruCircuit(
"K_PathfindThruCircuit: sourcewaypoint with ID %d has no next waypoint\n",
K_GetWaypointID(sourcewaypoint));
}
else if (((huntbackwards == false) && (finishline->numprevwaypoints == 0))
|| ((huntbackwards == true) && (finishline->numnextwaypoints == 0)))
{
CONS_Debug(DBG_GAMELOGIC,
"K_PathfindThruCircuit: finishline with ID %d has no previous waypoint\n",
K_GetWaypointID(finishline));
}
else
{
pathfindsetup_t pathfindsetup = {0};
@ -1334,13 +1368,6 @@ boolean K_PathfindThruCircuitSpawnable(
"K_PathfindThruCircuitSpawnable: sourcewaypoint with ID %d has no next waypoint\n",
K_GetWaypointID(sourcewaypoint));
}
else if (((huntbackwards == false) && (finishline->numprevwaypoints == 0))
|| ((huntbackwards == true) && (finishline->numnextwaypoints == 0)))
{
CONS_Debug(DBG_GAMELOGIC,
"K_PathfindThruCircuitSpawnable: finishline with ID %d has no previous waypoint\n",
K_GetWaypointID(finishline));
}
else
{
pathfindsetup_t pathfindsetup = {0};

View file

@ -834,7 +834,7 @@ int LUA_HookHurtMsg(player_t *player, mobj_t *inflictor, mobj_t *source, UINT8 d
return hook.status;
}
void LUA_HookNetArchive(lua_CFunction archFunc)
void LUA_HookNetArchive(lua_CFunction archFunc, savebuffer_t *save)
{
const hook_t * map = &hookIds[HOOK(NetVars)];
Hook_State hook;
@ -852,8 +852,9 @@ void LUA_HookNetArchive(lua_CFunction archFunc)
// tables becomes an upvalue of archFunc
lua_pushvalue(gL, -1);
lua_pushcclosure(gL, archFunc, 1);
// stack: tables, archFunc
lua_pushlightuserdata(gL, save);
lua_pushcclosure(gL, archFunc, 2);
// stack: tables, savebuffer_t, archFunc
init_hook_call(&hook, 0, res_none);
call_mapped(&hook, map);

View file

@ -1248,10 +1248,10 @@ static UINT8 ArchiveValue(UINT8 **p, int TABLESINDEX, int myindex)
{
polyobj_t *polyobj = *((polyobj_t **)lua_touserdata(gL, myindex));
if (!polyobj)
WRITEUINT8(save_p, ARCH_NULL);
WRITEUINT8(*p, ARCH_NULL);
else {
WRITEUINT8(save_p, ARCH_POLYOBJ);
WRITEUINT16(save_p, polyobj-PolyObjects);
WRITEUINT8(*p, ARCH_POLYOBJ);
WRITEUINT16(*p, polyobj-PolyObjects);
}
break;
}
@ -1353,9 +1353,10 @@ static void ArchiveExtVars(UINT8 **p, void *pointer, const char *ptype)
static int NetArchive(lua_State *L)
{
int TABLESINDEX = lua_upvalueindex(1);
savebuffer_t *save = lua_touserdata(L, lua_upvalueindex(2));
int i, n = lua_gettop(L);
for (i = 1; i <= n; i++)
ArchiveValue(&save_p, TABLESINDEX, i);
ArchiveValue(&save->p, TABLESINDEX, i);
return n;
}
@ -1517,7 +1518,7 @@ static UINT8 UnArchiveValue(UINT8 **p, int TABLESINDEX)
break;
}
case ARCH_POLYOBJ:
LUA_PushUserdata(gL, &PolyObjects[READUINT16(save_p)], META_POLYOBJ);
LUA_PushUserdata(gL, &PolyObjects[READUINT16(*p)], META_POLYOBJ);
break;
case ARCH_SLOPE:
LUA_PushUserdata(gL, P_SlopeById(READUINT16(*p)), META_SLOPE);
@ -1566,9 +1567,10 @@ static void UnArchiveExtVars(UINT8 **p, void *pointer)
static int NetUnArchive(lua_State *L)
{
int TABLESINDEX = lua_upvalueindex(1);
savebuffer_t *save = lua_touserdata(L, lua_upvalueindex(2));
int i, n = lua_gettop(L);
for (i = 1; i <= n; i++)
UnArchiveValue(&save_p, TABLESINDEX);
UnArchiveValue(&save->p, TABLESINDEX);
return n;
}
@ -1602,7 +1604,7 @@ static void UnArchiveTables(UINT8 **p)
lua_rawset(gL, -3);
}
metatableid = READUINT16(save_p);
metatableid = READUINT16(*p);
if (metatableid)
{
// setmetatable(table, registry.metatables[metatableid])
@ -1626,7 +1628,7 @@ void LUA_Step(void)
lua_gc(gL, LUA_GCSTEP, 1);
}
void LUA_Archive(UINT8 **p)
void LUA_Archive(savebuffer_t *save, boolean network)
{
INT32 i;
thinker_t *th;
@ -1639,10 +1641,10 @@ void LUA_Archive(UINT8 **p)
if (!playeringame[i] && i > 0) // NEVER skip player 0, this is for dedi servs.
continue;
// all players in game will be archived, even if they just add a 0.
ArchiveExtVars(p, &players[i], "player");
ArchiveExtVars(&save->p, &players[i], "player");
}
if (p == &save_p)
if (network == true)
{
if (gamestate == GS_LEVEL)
{
@ -1653,22 +1655,22 @@ void LUA_Archive(UINT8 **p)
// archive function will determine when to skip mobjs,
// and write mobjnum in otherwise.
ArchiveExtVars(p, th, "mobj");
ArchiveExtVars(&save->p, th, "mobj");
}
}
WRITEUINT32(*p, UINT32_MAX); // end of mobjs marker, replaces mobjnum.
WRITEUINT32(save->p, UINT32_MAX); // end of mobjs marker, replaces mobjnum.
LUA_HookNetArchive(NetArchive); // call the NetArchive hook in archive mode
LUA_HookNetArchive(NetArchive, save); // call the NetArchive hook in archive mode
}
ArchiveTables(p);
ArchiveTables(&save->p);
if (gL)
lua_pop(gL, 1); // pop tables
}
void LUA_UnArchive(UINT8 **p)
void LUA_UnArchive(savebuffer_t *save, boolean network)
{
UINT32 mobjnum;
INT32 i;
@ -1681,27 +1683,27 @@ void LUA_UnArchive(UINT8 **p)
{
if (!playeringame[i] && i > 0) // same here, this is to synch dediservs properly.
continue;
UnArchiveExtVars(p, &players[i]);
UnArchiveExtVars(&save->p, &players[i]);
}
if (p == &save_p)
if (network == true)
{
do {
mobjnum = READUINT32(*p); // read a mobjnum
mobjnum = READUINT32(save->p); // read a mobjnum
for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
{
if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
continue;
if (((mobj_t *)th)->mobjnum != mobjnum) // find matching mobj
continue;
UnArchiveExtVars(p, th); // apply variables
UnArchiveExtVars(&save->p, th); // apply variables
}
} while(mobjnum != UINT32_MAX); // repeat until end of mobjs marker.
LUA_HookNetArchive(NetUnArchive); // call the NetArchive hook in unarchive mode
LUA_HookNetArchive(NetUnArchive, save); // call the NetArchive hook in unarchive mode
}
UnArchiveTables(p);
UnArchiveTables(&save->p);
if (gL)
lua_pop(gL, 1); // pop tables

View file

@ -53,8 +53,8 @@ void LUA_DumpFile(const char *filename);
#endif
fixed_t LUA_EvalMath(const char *word);
void LUA_Step(void);
void LUA_Archive(UINT8 **p);
void LUA_UnArchive(UINT8 **p);
void LUA_Archive(savebuffer_t *save, boolean network);
void LUA_UnArchive(savebuffer_t *save, boolean network);
int LUA_PushGlobals(lua_State *L, const char *word);
int LUA_WriteGlobals(lua_State *L, const char *word);
@ -63,7 +63,7 @@ void Got_Luacmd(UINT8 **cp, INT32 playernum); // lua_consolelib.c
void LUA_CVarChanged(void *cvar); // lua_consolelib.c
int Lua_optoption(lua_State *L, int narg,
const char *def, const char *const lst[]);
void LUA_HookNetArchive(lua_CFunction archFunc);
void LUA_HookNetArchive(lua_CFunction archFunc, savebuffer_t *save);
void LUA_PushTaggableObjectArray
( lua_State *L,

View file

@ -9,4 +9,5 @@ target_sources(SRB2SDL2 PRIVATE
jawz.c
duel-bomb.c
broly.c
ufo.c
)

View file

@ -24,6 +24,7 @@
#include "../k_waypoint.h"
#include "../k_respawn.h"
#include "../k_collide.h"
#include "../k_specialstage.h"
#define MAX_JAWZ_TURN (ANGLE_90 / 15) // We can turn a maximum of 6 degrees per frame at regular max speed
@ -140,17 +141,21 @@ static void JawzChase(mobj_t *th, boolean grounded)
if (jawz_chase(th) == NULL || P_MobjWasRemoved(jawz_chase(th)) == true)
{
mobj_t *newChase = NULL;
player_t *owner = NULL;
th->angle = K_MomentumAngle(th);
if (jawz_owner(th) != NULL && P_MobjWasRemoved(jawz_owner(th)) == false
&& jawz_owner(th)->player != NULL)
if ((jawz_owner(th) != NULL && P_MobjWasRemoved(jawz_owner(th)) == false)
&& (jawz_owner(th)->player != NULL))
{
player_t *newPlayer = K_FindJawzTarget(th, jawz_owner(th)->player, ANGLE_90);
owner = jawz_owner(th)->player;
}
if (newPlayer != NULL)
{
P_SetTarget(&jawz_chase(th), newPlayer->mo);
}
newChase = K_FindJawzTarget(th, owner, ANGLE_90);
if (newChase != NULL)
{
P_SetTarget(&jawz_chase(th), newChase);
}
}
@ -181,6 +186,11 @@ static void JawzChase(mobj_t *th, boolean grounded)
}
}
static boolean JawzSteersBetter(void)
{
return !!!(gametyperules & GTR_CIRCUIT);
}
void Obj_JawzThink(mobj_t *th)
{
mobj_t *ghost = P_SpawnGhostMobj(th);
@ -211,7 +221,7 @@ void Obj_JawzThink(mobj_t *th)
ghost->colorized = true;
}
if (!(gametyperules & GTR_CIRCUIT))
if (JawzSteersBetter() == true)
{
th->friction = max(0, 3 * th->friction / 4);
}

View file

@ -23,6 +23,7 @@
#include "../z_zone.h"
#include "../k_waypoint.h"
#include "../k_respawn.h"
#include "../k_specialstage.h"
#define SPB_SLIPTIDEDELTA (ANG1 * 3)
#define SPB_STEERDELTA (ANGLE_90 - ANG10)
@ -292,7 +293,7 @@ static boolean SPBSeekSoundPlaying(mobj_t *spb)
|| S_SoundPlaying(spb, sfx_spbskc));
}
static void SPBSeek(mobj_t *spb, player_t *bestPlayer)
static void SPBSeek(mobj_t *spb, mobj_t *bestMobj)
{
const fixed_t desiredSpeed = SPB_DEFAULTSPEED;
@ -321,16 +322,15 @@ static void SPBSeek(mobj_t *spb, player_t *bestPlayer)
spb_lastplayer(spb) = -1; // Just make sure this is reset
if (bestPlayer == NULL
|| bestPlayer->mo == NULL
|| P_MobjWasRemoved(bestPlayer->mo) == true
|| bestPlayer->mo->health <= 0
|| (bestPlayer->respawn.state != RESPAWNST_NONE))
if (bestMobj == NULL
|| P_MobjWasRemoved(bestMobj) == true
|| bestMobj->health <= 0
|| (bestMobj->player != NULL && bestMobj->player->respawn.state != RESPAWNST_NONE))
{
// No one there? Completely STOP.
spb->momx = spb->momy = spb->momz = 0;
if (bestPlayer == NULL)
if (bestMobj == NULL)
{
spbplace = -1;
}
@ -339,8 +339,16 @@ static void SPBSeek(mobj_t *spb, player_t *bestPlayer)
}
// Found someone, now get close enough to initiate the slaughter...
P_SetTarget(&spb_chase(spb), bestPlayer->mo);
spbplace = bestPlayer->position;
P_SetTarget(&spb_chase(spb), bestMobj);
if (bestMobj->player != NULL)
{
spbplace = bestMobj->player->position;
}
else
{
spbplace = 1;
}
dist = SPBDist(spb, spb_chase(spb));
activeDist = FixedMul(SPB_ACTIVEDIST, spb_chase(spb)->scale);
@ -400,7 +408,18 @@ static void SPBSeek(mobj_t *spb, player_t *bestPlayer)
curWaypoint = K_GetWaypointFromIndex( (size_t)spb_curwaypoint(spb) );
}
destWaypoint = bestPlayer->nextwaypoint;
if (bestMobj->player != NULL)
{
destWaypoint = bestMobj->player->nextwaypoint;
}
else if (bestMobj->type == MT_SPECIAL_UFO)
{
destWaypoint = K_GetSpecialUFOWaypoint(bestMobj);
}
else
{
destWaypoint = K_GetBestWaypointForMobj(bestMobj);
}
if (curWaypoint != NULL)
{
@ -433,7 +452,8 @@ static void SPBSeek(mobj_t *spb, player_t *bestPlayer)
if (pathfindsuccess == true)
{
if (cv_spbtest.value) {
if (cv_spbtest.value)
{
if (pathtoplayer.numnodes > 1)
{
// Go to the next waypoint.
@ -529,45 +549,48 @@ static void SPBSeek(mobj_t *spb, player_t *bestPlayer)
SetSPBSpeed(spb, xySpeed, zSpeed);
// see if a player is near us, if they are, try to hit them by slightly thrusting towards them, otherwise, bleh!
steerDist = 1536 * mapobjectscale;
for (i = 0; i < MAXPLAYERS; i++)
if (specialStage.active == false)
{
fixed_t ourDist = INT32_MAX;
INT32 ourDelta = INT32_MAX;
// see if a player is near us, if they are, try to hit them by slightly thrusting towards them, otherwise, bleh!
steerDist = 1536 * mapobjectscale;
if (playeringame[i] == false || players[i].spectator == true)
for (i = 0; i < MAXPLAYERS; i++)
{
// Not in-game
continue;
fixed_t ourDist = INT32_MAX;
INT32 ourDelta = INT32_MAX;
if (playeringame[i] == false || players[i].spectator == true)
{
// Not in-game
continue;
}
if (players[i].mo == NULL || P_MobjWasRemoved(players[i].mo) == true)
{
// Invalid mobj
continue;
}
ourDelta = AngleDelta(spb->angle, R_PointToAngle2(spb->x, spb->y, players[i].mo->x, players[i].mo->y));
if (ourDelta > SPB_STEERDELTA)
{
// Check if the angle wouldn't make us LOSE speed.
continue;
}
ourDist = R_PointToDist2(spb->x, spb->y, players[i].mo->x, players[i].mo->y);
if (ourDist < steerDist)
{
steerDist = ourDist;
steerMobj = players[i].mo; // it doesn't matter if we override this guy now.
}
}
if (players[i].mo == NULL || P_MobjWasRemoved(players[i].mo) == true)
// different player from our main target, try and ram into em~!
if (steerMobj != NULL && steerMobj != spb_chase(spb))
{
// Invalid mobj
continue;
P_Thrust(spb, R_PointToAngle2(spb->x, spb->y, steerMobj->x, steerMobj->y), spb_speed(spb) / 4);
}
ourDelta = AngleDelta(spb->angle, R_PointToAngle2(spb->x, spb->y, players[i].mo->x, players[i].mo->y));
if (ourDelta > SPB_STEERDELTA)
{
// Check if the angle wouldn't make us LOSE speed.
continue;
}
ourDist = R_PointToDist2(spb->x, spb->y, players[i].mo->x, players[i].mo->y);
if (ourDist < steerDist)
{
steerDist = ourDist;
steerMobj = players[i].mo; // it doesn't matter if we override this guy now.
}
}
// different player from our main target, try and ram into em~!
if (steerMobj != NULL && steerMobj != spb_chase(spb))
{
P_Thrust(spb, R_PointToAngle2(spb->x, spb->y, steerMobj->x, steerMobj->y), spb_speed(spb) / 4);
}
if (sliptide != 0)
@ -593,7 +616,7 @@ static void SPBSeek(mobj_t *spb, player_t *bestPlayer)
}
}
static void SPBChase(mobj_t *spb, player_t *bestPlayer)
static void SPBChase(mobj_t *spb, mobj_t *bestMobj)
{
fixed_t baseSpeed = 0;
fixed_t maxSpeed = 0;
@ -642,8 +665,8 @@ static void SPBChase(mobj_t *spb, player_t *bestPlayer)
S_StartSound(spb, spb->info->activesound);
}
// Maybe we want SPB to target an object later? IDK lol
chasePlayer = chase->player;
if (chasePlayer != NULL)
{
UINT8 fracmax = 32;
@ -679,7 +702,8 @@ static void SPBChase(mobj_t *spb, player_t *bestPlayer)
cy = chasePlayer->cmomy;
// Switch targets if you're no longer 1st for long enough
if (bestPlayer != NULL && chasePlayer->position <= bestPlayer->position)
if (bestMobj != NULL
&& (bestMobj->player == NULL || chasePlayer->position <= bestMobj->player->position))
{
spb_modetimer(spb) = SPB_HOTPOTATO;
}
@ -697,6 +721,12 @@ static void SPBChase(mobj_t *spb, player_t *bestPlayer)
}
}
}
else
{
spb_lastplayer(spb) = -1;
spbplace = 1;
spb_modetimer(spb) = SPB_HOTPOTATO;
}
dist = P_AproxDistance(P_AproxDistance(spb->x - chase->x, spb->y - chase->y), spb->z - chase->z);
@ -807,7 +837,7 @@ static void SPBWait(mobj_t *spb)
void Obj_SPBThink(mobj_t *spb)
{
mobj_t *ghost = NULL;
player_t *bestPlayer = NULL;
mobj_t *bestMobj = NULL;
UINT8 bestRank = UINT8_MAX;
size_t i;
@ -844,6 +874,15 @@ void Obj_SPBThink(mobj_t *spb)
}
else
{
if (specialStage.active == true)
{
if (specialStage.ufo != NULL && P_MobjWasRemoved(specialStage.ufo) == false)
{
bestRank = 1;
bestMobj = specialStage.ufo;
}
}
// Find the player with the best rank
for (i = 0; i < MAXPLAYERS; i++)
{
@ -886,7 +925,7 @@ void Obj_SPBThink(mobj_t *spb)
if (player->position < bestRank)
{
bestRank = player->position;
bestPlayer = player;
bestMobj = player->mo;
}
}
@ -894,11 +933,11 @@ void Obj_SPBThink(mobj_t *spb)
{
case SPB_MODE_SEEK:
default:
SPBSeek(spb, bestPlayer);
SPBSeek(spb, bestMobj);
break;
case SPB_MODE_CHASE:
SPBChase(spb, bestPlayer);
SPBChase(spb, bestMobj);
break;
case SPB_MODE_WAIT:
@ -970,14 +1009,18 @@ void Obj_SPBExplode(mobj_t *spb)
void Obj_SPBTouch(mobj_t *spb, mobj_t *toucher)
{
player_t *player = toucher->player;
player_t *const player = toucher->player;
mobj_t *owner = NULL;
mobj_t *chase = NULL;
if (spb_intangible(spb) > 0)
{
return;
}
if ((spb_owner(spb) == toucher || spb_owner(spb) == toucher->target)
owner = spb_owner(spb);
if ((owner == toucher || owner == toucher->target)
&& (spb_nothink(spb) > 0))
{
return;
@ -988,30 +1031,34 @@ void Obj_SPBTouch(mobj_t *spb, mobj_t *toucher)
return;
}
if (player->spectator == true)
if (player != NULL)
{
return;
if (player->spectator == true)
{
return;
}
if (player->bubbleblowup > 0)
{
// Stun the SPB, and remove the shield.
K_DropHnextList(player, false);
spb_mode(spb) = SPB_MODE_WAIT;
spb_modetimer(spb) = 55; // Slightly over the respawn timer length
return;
}
}
if (player->bubbleblowup > 0)
{
// Stun the SPB, and remove the shield.
K_DropHnextList(player, false);
spb_mode(spb) = SPB_MODE_WAIT;
spb_modetimer(spb) = 55; // Slightly over the respawn timer length
return;
}
if (spb_chase(spb) != NULL && P_MobjWasRemoved(spb_chase(spb)) == false
&& toucher == spb_chase(spb))
chase = spb_chase(spb);
if (chase != NULL && P_MobjWasRemoved(chase) == false
&& toucher == chase)
{
// Cause the explosion.
Obj_SPBExplode(spb);
return;
}
else
else if (toucher->flags & MF_SHOOTABLE)
{
// Regular spinout, please.
P_DamageMobj(toucher, spb, spb_owner(spb), 1, DMG_NORMAL);
P_DamageMobj(toucher, spb, owner, 1, DMG_NORMAL);
}
}

832
src/objects/ufo.c Normal file
View file

@ -0,0 +1,832 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2022 by Sally "TehRealSalt" Cochenour
// Copyright (C) 2022 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 ufo.c
/// \brief Special Stage UFO + Emerald handler
#include "../doomdef.h"
#include "../doomstat.h"
#include "../info.h"
#include "../k_kart.h"
#include "../k_objects.h"
#include "../m_random.h"
#include "../p_local.h"
#include "../r_main.h"
#include "../s_sound.h"
#include "../g_game.h"
#include "../z_zone.h"
#include "../k_waypoint.h"
#include "../k_specialstage.h"
#define UFO_BASE_SPEED (42 * FRACUNIT) // UFO's slowest speed.
#define UFO_SPEEDUP (FRACUNIT >> 1) // Acceleration
#define UFO_SLOWDOWN (FRACUNIT >> 1) // Deceleration
#define UFO_SPACING (768 * FRACUNIT) // How far the UFO wants to stay in front
#define UFO_DEADZONE (2048 * FRACUNIT) // Deadzone where it won't update it's speed as much.
#define UFO_SPEEDFACTOR (FRACUNIT * 3 / 4) // Factor of player's best speed, to make it more fair.
#define UFO_DAMAGED_SPEED (UFO_BASE_SPEED >> 1) // Speed to add when UFO takes damage.
#define UFO_START_SPEED (UFO_BASE_SPEED << 1) // Speed when the map starts.
#define UFO_NUMARMS (3)
#define UFO_ARMDELTA (ANGLE_MAX / UFO_NUMARMS)
#define ufo_waypoint(o) ((o)->extravalue1)
#define ufo_distancetofinish(o) ((o)->extravalue2)
#define ufo_speed(o) ((o)->watertop)
#define ufo_collectdelay(o) ((o)->threshold)
#define ufo_pieces(o) ((o)->hnext)
#define ufo_piece_type(o) ((o)->extravalue1)
#define ufo_piece_owner(o) ((o)->target)
#define ufo_piece_next(o) ((o)->hnext)
#define ufo_piece_prev(o) ((o)->hprev)
enum
{
UFO_PIECE_TYPE_POD,
UFO_PIECE_TYPE_ARM,
UFO_PIECE_TYPE_STEM,
};
static void UFOMoveTo(mobj_t *ufo, fixed_t destx, fixed_t desty, fixed_t destz)
{
ufo->momx = destx - ufo->x;
ufo->momy = desty - ufo->y;
ufo->momz = destz - ufo->z;
}
static fixed_t GenericDistance(
fixed_t curx, fixed_t cury, fixed_t curz,
fixed_t destx, fixed_t desty, fixed_t destz)
{
return P_AproxDistance(P_AproxDistance(destx - curx, desty - cury), destz - curz);
}
static boolean UFOEmeraldChase(mobj_t *ufo)
{
return (ufo->health <= 1);
}
static boolean UFOPieceValid(mobj_t *piece)
{
return (piece != NULL && P_MobjWasRemoved(piece) == false && piece->health > 0);
}
static void UFOUpdateDistanceToFinish(mobj_t *ufo)
{
waypoint_t *finishLine = K_GetFinishLineWaypoint();
waypoint_t *nextWaypoint = K_GetWaypointFromIndex((size_t)ufo_waypoint(ufo));
if (nextWaypoint != NULL && finishLine != NULL)
{
const boolean useshortcuts = false;
const boolean huntbackwards = false;
boolean pathfindsuccess = false;
path_t pathtofinish = {0};
pathfindsuccess =
K_PathfindToWaypoint(nextWaypoint, finishLine, &pathtofinish, useshortcuts, huntbackwards);
// Update the UFO's distance to the finish line if a path was found.
if (pathfindsuccess == true)
{
// Add euclidean distance to the next waypoint to the distancetofinish
UINT32 adddist;
fixed_t disttowaypoint =
P_AproxDistance(
(ufo->x >> FRACBITS) - (nextWaypoint->mobj->x >> FRACBITS),
(ufo->y >> FRACBITS) - (nextWaypoint->mobj->y >> FRACBITS));
disttowaypoint = P_AproxDistance(disttowaypoint, (ufo->z >> FRACBITS) - (nextWaypoint->mobj->z >> FRACBITS));
adddist = (UINT32)disttowaypoint;
ufo_distancetofinish(ufo) = pathtofinish.totaldist + adddist;
Z_Free(pathtofinish.array);
}
}
}
static void UFOUpdateSpeed(mobj_t *ufo)
{
const fixed_t baseSpeed = FixedMul(UFO_BASE_SPEED, K_GetKartGameSpeedScalar(gamespeed));
const UINT32 spacing = FixedMul(FixedMul(UFO_SPACING, mapobjectscale), K_GetKartGameSpeedScalar(gamespeed)) >> FRACBITS;
const UINT32 deadzone = FixedMul(FixedMul(UFO_DEADZONE, mapobjectscale), K_GetKartGameSpeedScalar(gamespeed)) >> FRACBITS;
// Best values of all of the players.
UINT32 bestDist = UINT32_MAX;
fixed_t bestSpeed = 0;
// Desired values for the UFO itself.
UINT32 wantedDist = UINT32_MAX;
fixed_t wantedSpeed = ufo_speed(ufo);
fixed_t speedDelta = 0;
UINT8 i;
for (i = 0; i < MAXPLAYERS; i++)
{
player_t *player = NULL;
if (playeringame[i] == false)
{
continue;
}
player = &players[i];
if (player->spectator == true)
{
continue;
}
if (player->mo == NULL || P_MobjWasRemoved(player->mo) == true)
{
continue;
}
if (player->distancetofinish < bestDist)
{
bestDist = player->distancetofinish;
// Doesn't matter if a splitscreen player behind is moving faster behind the one most caught up.
bestSpeed = R_PointToDist2(0, 0, player->rmomx, player->rmomy);
bestSpeed = min(bestSpeed, K_GetKartSpeed(player, false, false)); // Don't become unfair with Sneakers.
bestSpeed = FixedDiv(bestSpeed, mapobjectscale); // Unscale from mapobjectscale to FRACUNIT
bestSpeed = FixedMul(bestSpeed, UFO_SPEEDFACTOR); // Make it a bit more lenient
}
}
if (bestDist == UINT32_MAX)
{
// Invalid, lets go back to base speed.
wantedSpeed = baseSpeed;
}
else
{
INT32 distDelta = 0;
if (bestDist > spacing)
{
wantedDist = bestDist - spacing;
}
else
{
wantedDist = 0;
}
distDelta = ufo_distancetofinish(ufo) - wantedDist;
if (distDelta > 0)
{
// Too far behind! Start speeding up!
wantedSpeed = max(bestSpeed, baseSpeed << 2);
}
else
{
if (abs(distDelta) <= deadzone)
{
// We're in a good spot, try to match the player.
wantedSpeed = max(bestSpeed >> 1, baseSpeed);
}
else
{
// Too far ahead! Start slowing down!
wantedSpeed = baseSpeed;
}
}
}
// Slowly accelerate or decelerate to
// get to our desired speed.
speedDelta = wantedSpeed - ufo_speed(ufo);
if (speedDelta > 0)
{
if (abs(speedDelta) <= UFO_SPEEDUP)
{
ufo_speed(ufo) = wantedSpeed;
}
else
{
ufo_speed(ufo) += UFO_SPEEDUP;
}
}
else if (speedDelta < 0)
{
if (abs(speedDelta) <= UFO_SLOWDOWN)
{
ufo_speed(ufo) = wantedSpeed;
}
else
{
ufo_speed(ufo) -= UFO_SLOWDOWN;
}
}
}
static void UFOUpdateAngle(mobj_t *ufo)
{
angle_t dest = K_MomentumAngle(ufo);
INT32 delta = AngleDeltaSigned(ufo->angle, dest);
ufo->angle += delta >> 2;
}
waypoint_t *K_GetSpecialUFOWaypoint(mobj_t *ufo)
{
if ((ufo == NULL) && (specialStage.active == true))
{
ufo = specialStage.ufo;
}
if (ufo != NULL && P_MobjWasRemoved(ufo) == false
&& ufo->type == MT_SPECIAL_UFO)
{
if (ufo_waypoint(ufo) >= 0)
{
return K_GetWaypointFromIndex((size_t)ufo_waypoint(ufo));
}
}
return NULL;
}
static void UFOMove(mobj_t *ufo)
{
waypoint_t *curWaypoint = NULL;
waypoint_t *destWaypoint = NULL;
fixed_t distLeft = INT32_MAX;
fixed_t newX = ufo->x;
fixed_t newY = ufo->y;
fixed_t newZ = ufo->z;
const fixed_t floatHeight = 24 * ufo->scale;
const boolean useshortcuts = false;
const boolean huntbackwards = false;
boolean pathfindsuccess = false;
path_t pathtofinish = {0};
size_t pathIndex = 0;
boolean reachedEnd = false;
curWaypoint = K_GetSpecialUFOWaypoint(ufo);
destWaypoint = K_GetFinishLineWaypoint();
if (curWaypoint == NULL || destWaypoint == NULL)
{
// Waypoints aren't valid.
// Just stand still.
ufo->momx = 0;
ufo->momy = 0;
ufo->momz = 0;
return;
}
distLeft = FixedMul(ufo_speed(ufo), mapobjectscale);
while (distLeft > 0)
{
fixed_t wpX = curWaypoint->mobj->x;
fixed_t wpY = curWaypoint->mobj->y;
fixed_t wpZ = curWaypoint->mobj->z + floatHeight;
fixed_t distToNext = GenericDistance(
newX, newY, newZ,
wpX, wpY, wpZ
);
if (distToNext > distLeft)
{
// Only made it partially there.
newX += FixedMul(FixedDiv(wpX - newX, distToNext), distLeft);
newY += FixedMul(FixedDiv(wpY - newY, distToNext), distLeft);
newZ += FixedMul(FixedDiv(wpZ - newZ, distToNext), distLeft);
distLeft = 0;
}
else
{
// Close enough to the next waypoint,
// move there and remove the distance.
newX = wpX;
newY = wpY;
newZ = wpZ;
distLeft -= distToNext;
if (curWaypoint == destWaypoint)
{
// Reached the end.
reachedEnd = true;
break;
}
// Create waypoint path to our destination.
// Crazy over-engineered, just to catch when
// waypoints are insanely close to each other :P
if (pathfindsuccess == false)
{
pathfindsuccess = K_PathfindToWaypoint(
curWaypoint, destWaypoint,
&pathtofinish,
useshortcuts, huntbackwards
);
if (pathfindsuccess == false)
{
// Path isn't valid.
// Just keep going.
break;
}
}
pathIndex++;
if (pathIndex >= pathtofinish.numnodes)
{
// Successfully reached the end of the path.
reachedEnd = true;
break;
}
// Now moving to the next waypoint.
curWaypoint = (waypoint_t *)pathtofinish.array[pathIndex].nodedata;
ufo_waypoint(ufo) = (INT32)K_GetWaypointHeapIndex(curWaypoint);
}
}
UFOMoveTo(ufo, newX, newY, newZ);
if (reachedEnd == true)
{
CONS_Printf("You lost...\n");
ufo_waypoint(ufo) = -1; // Invalidate
}
if (pathfindsuccess == true)
{
Z_Free(pathtofinish.array);
}
}
static void UFOEmeraldVFX(mobj_t *ufo)
{
const INT32 bobS = 32;
const angle_t bobA = (leveltime & (bobS - 1)) * (ANGLE_MAX / bobS);
const fixed_t bobH = 16 * ufo->scale;
ufo->sprzoff = FixedMul(bobH, FINESINE(bobA >> ANGLETOFINESHIFT));
if (leveltime % 3 == 0)
{
mobj_t *sparkle = P_SpawnMobjFromMobj(
ufo,
P_RandomRange(PR_SPARKLE, -48, 48) * FRACUNIT,
P_RandomRange(PR_SPARKLE, -48, 48) * FRACUNIT,
(P_RandomRange(PR_SPARKLE, 0, 64) * FRACUNIT) + FixedDiv(ufo->sprzoff, ufo->scale),
MT_EMERALDSPARK
);
sparkle->color = ufo->color;
sparkle->momz += 8 * ufo->scale * P_MobjFlip(ufo);
}
}
void Obj_SpecialUFOThinker(mobj_t *ufo)
{
UFOMove(ufo);
UFOUpdateAngle(ufo);
UFOUpdateDistanceToFinish(ufo);
UFOUpdateSpeed(ufo);
if (UFOEmeraldChase(ufo) == true)
{
// Spawn emerald sparkles
UFOEmeraldVFX(ufo);
ufo_collectdelay(ufo)--;
}
else
{
ufo_collectdelay(ufo) = TICRATE;
}
}
static void UFOCopyHitlagToPieces(mobj_t *ufo)
{
mobj_t *piece = NULL;
piece = ufo_pieces(ufo);
while (UFOPieceValid(piece) == true)
{
piece->hitlag = ufo->hitlag;
piece->eflags = (piece->eflags & ~MFE_DAMAGEHITLAG) | (ufo->eflags & MFE_DAMAGEHITLAG);
piece = ufo_piece_next(piece);
}
}
static void UFOKillPiece(mobj_t *piece)
{
angle_t dir = ANGLE_MAX;
fixed_t thrust = 0;
if (UFOPieceValid(piece) == false)
{
return;
}
piece->health = 0;
piece->tics = TICRATE;
piece->flags &= ~MF_NOGRAVITY;
switch (ufo_piece_type(piece))
{
case UFO_PIECE_TYPE_STEM:
{
piece->tics = 1;
return;
}
case UFO_PIECE_TYPE_ARM:
{
dir = piece->angle;
thrust = 12 * piece->scale;
break;
}
default:
{
dir = FixedAngle(P_RandomRange(PR_DECORATION, 0, 359) << FRACBITS);
thrust = 4 * piece->scale;
break;
}
}
P_Thrust(piece, dir, -thrust);
P_SetObjectMomZ(piece, 12*FRACUNIT, true);
}
static void UFOKillPieces(mobj_t *ufo)
{
mobj_t *piece = NULL;
piece = ufo_pieces(ufo);
while (UFOPieceValid(piece) == true)
{
UFOKillPiece(piece);
piece = ufo_piece_next(piece);
}
}
static UINT8 GetUFODamage(mobj_t *inflictor, UINT8 damageType)
{
if (inflictor != NULL && P_MobjWasRemoved(inflictor) == false)
{
switch (inflictor->type)
{
case MT_JAWZ_SHIELD:
case MT_ORBINAUT_SHIELD:
{
// Shields deal chip damage.
return 10;
}
case MT_JAWZ:
{
// Thrown Jawz deal a bit extra.
return 15;
}
case MT_ORBINAUT:
{
// Thrown orbinauts deal double damage.
return 20;
}
case MT_SPB:
{
// SPB deals triple damage.
return 30;
}
case MT_BANANA:
{
// Banana snipes deal triple damage,
// laid down bananas deal regular damage.
if (inflictor->health > 1)
{
return 30;
}
return 10;
}
case MT_PLAYER:
{
// Players deal damage relative to how many sneakers they used.
return 15 * max(1, inflictor->player->numsneakers);
}
default:
{
break;
}
}
}
// Guess from damage type.
switch (damageType & DMG_TYPEMASK)
{
case DMG_NORMAL:
case DMG_STING:
default:
{
return 10;
}
case DMG_WIPEOUT:
{
return 20;
}
case DMG_EXPLODE:
case DMG_TUMBLE:
{
return 30;
}
case DMG_VOLTAGE:
{
return 15;
}
}
}
boolean Obj_SpecialUFODamage(mobj_t *ufo, mobj_t *inflictor, mobj_t *source, UINT8 damageType)
{
const fixed_t addSpeed = FixedMul(UFO_DAMAGED_SPEED, K_GetKartGameSpeedScalar(gamespeed));
UINT8 damage = 1;
(void)source;
if (UFOEmeraldChase(ufo) == true)
{
// Damaged fully already, no need for any more.
return false;
}
damage = GetUFODamage(inflictor, damageType);
if (damage <= 0)
{
return false;
}
// Speed up on damage!
ufo_speed(ufo) += addSpeed;
K_SetHitLagForObjects(ufo, inflictor, (damage / 3) + 2, true);
UFOCopyHitlagToPieces(ufo);
if (damage >= ufo->health - 1)
{
// Destroy the UFO parts, and make the emerald collectible!
UFOKillPieces(ufo);
ufo->health = 1;
ufo->flags = (ufo->flags & ~MF_SHOOTABLE) | (MF_SPECIAL|MF_PICKUPFROMBELOW);
ufo->shadowscale = FRACUNIT/3;
ufo_speed(ufo) += addSpeed; // Even more speed!
return true;
}
ufo->health -= damage;
return true;
}
void Obj_PlayerUFOCollide(mobj_t *ufo, mobj_t *other)
{
if (other->player == NULL)
{
return;
}
if ((other->player->sneakertimer > 0)
&& !P_PlayerInPain(other->player)
&& (other->player->flashing == 0))
{
// Bump and deal damage.
Obj_SpecialUFODamage(ufo, other, other, DMG_STEAL);
K_KartBouncing(other, ufo);
other->player->sneakertimer = 0;
}
}
void Obj_UFOPieceThink(mobj_t *piece)
{
mobj_t *ufo = ufo_piece_owner(piece);
if (ufo == NULL || P_MobjWasRemoved(ufo) == true)
{
P_KillMobj(piece, NULL, NULL, DMG_NORMAL);
return;
}
piece->destscale = 3 * ufo->destscale / 2;
piece->scalespeed = ufo->scalespeed;
switch (ufo_piece_type(piece))
{
case UFO_PIECE_TYPE_POD:
{
UFOMoveTo(piece, ufo->x, ufo->y, ufo->z + (132 * piece->scale));
break;
}
case UFO_PIECE_TYPE_ARM:
{
fixed_t dis = (88 * piece->scale);
fixed_t x = ufo->x - FixedMul(dis, FINECOSINE(piece->angle >> ANGLETOFINESHIFT));
fixed_t y = ufo->y - FixedMul(dis, FINESINE(piece->angle >> ANGLETOFINESHIFT));
UFOMoveTo(piece, x, y, ufo->z + (24 * piece->scale));
piece->angle -= FixedMul(ANG2, FixedDiv(ufo_speed(ufo), UFO_BASE_SPEED));
break;
}
case UFO_PIECE_TYPE_STEM:
{
fixed_t stemZ = ufo->z + (294 * piece->scale);
fixed_t sc = FixedDiv(FixedDiv(ufo->ceilingz - stemZ, piece->scale), 15 * FRACUNIT);
UFOMoveTo(piece, ufo->x, ufo->y, stemZ);
piece->spriteyscale = sc;
break;
}
default:
{
P_RemoveMobj(piece);
return;
}
}
}
void Obj_UFOPieceDead(mobj_t *piece)
{
piece->renderflags ^= RF_DONTDRAW;
}
void Obj_UFOPieceRemoved(mobj_t *piece)
{
// Repair piece list.
mobj_t *ufo = ufo_piece_owner(piece);
mobj_t *next = ufo_piece_next(piece);
mobj_t *prev = ufo_piece_prev(piece);
if (prev != NULL && P_MobjWasRemoved(prev) == false)
{
P_SetTarget(
&ufo_piece_next(prev),
(next != NULL && P_MobjWasRemoved(next) == false) ? next : NULL
);
}
if (next != NULL && P_MobjWasRemoved(next) == false)
{
P_SetTarget(
&ufo_piece_prev(next),
(prev != NULL && P_MobjWasRemoved(prev) == false) ? prev : NULL
);
}
if (ufo != NULL && P_MobjWasRemoved(ufo) == false)
{
if (piece == ufo_pieces(ufo))
{
P_SetTarget(
&ufo_pieces(ufo),
(next != NULL && P_MobjWasRemoved(next) == false) ? next : NULL
);
}
}
P_SetTarget(&ufo_piece_next(piece), NULL);
P_SetTarget(&ufo_piece_prev(piece), NULL);
}
static mobj_t *InitSpecialUFO(waypoint_t *start)
{
mobj_t *ufo = NULL;
mobj_t *overlay = NULL;
mobj_t *piece = NULL;
mobj_t *prevPiece = NULL;
size_t i;
if (start == NULL)
{
// Simply create at the origin with default values.
ufo = P_SpawnMobj(0, 0, 0, MT_SPECIAL_UFO);
ufo_waypoint(ufo) = -1; // Invalidate
ufo_distancetofinish(ufo) = INT32_MAX;
}
else
{
// Create with a proper waypoint track!
ufo = P_SpawnMobj(start->mobj->x, start->mobj->y, start->mobj->z, MT_SPECIAL_UFO);
ufo_waypoint(ufo) = (INT32)K_GetWaypointHeapIndex(start);
UFOUpdateDistanceToFinish(ufo);
}
ufo_speed(ufo) = FixedMul(UFO_START_SPEED, K_GetKartGameSpeedScalar(gamespeed));
// TODO: Adjustable Special Stage emerald color
ufo->color = SKINCOLOR_CHAOSEMERALD1;
overlay = P_SpawnMobjFromMobj(ufo, 0, 0, 0, MT_OVERLAY);
P_SetTarget(&overlay->target, ufo);
overlay->color = ufo->color;
// TODO: Super Emeralds / Chaos Rings
P_SetMobjState(overlay, S_CHAOSEMERALD_UNDER);
// Create UFO pieces.
// First: UFO center.
piece = P_SpawnMobjFromMobj(ufo, 0, 0, 0, MT_SPECIAL_UFO_PIECE);
P_SetTarget(&ufo_piece_owner(piece), ufo);
P_SetMobjState(piece, S_SPECIAL_UFO_POD);
ufo_piece_type(piece) = UFO_PIECE_TYPE_POD;
overlay = P_SpawnMobjFromMobj(piece, 0, 0, 0, MT_OVERLAY);
P_SetTarget(&overlay->target, piece);
P_SetMobjState(overlay, S_SPECIAL_UFO_OVERLAY);
P_SetTarget(&ufo_pieces(ufo), piece);
prevPiece = piece;
// Add the catcher arms.
for (i = 0; i < UFO_NUMARMS; i++)
{
piece = P_SpawnMobjFromMobj(ufo, 0, 0, 0, MT_SPECIAL_UFO_PIECE);
P_SetTarget(&ufo_piece_owner(piece), ufo);
P_SetMobjState(piece, S_SPECIAL_UFO_ARM);
ufo_piece_type(piece) = UFO_PIECE_TYPE_ARM;
piece->angle = UFO_ARMDELTA * i;
P_SetTarget(&ufo_piece_next(prevPiece), piece);
P_SetTarget(&ufo_piece_prev(piece), prevPiece);
prevPiece = piece;
}
// Add the stem.
piece = P_SpawnMobjFromMobj(ufo, 0, 0, 0, MT_SPECIAL_UFO_PIECE);
P_SetTarget(&ufo_piece_owner(piece), ufo);
P_SetMobjState(piece, S_SPECIAL_UFO_STEM);
ufo_piece_type(piece) = UFO_PIECE_TYPE_STEM;
P_SetTarget(&ufo_piece_next(prevPiece), piece);
P_SetTarget(&ufo_piece_prev(piece), prevPiece);
prevPiece = piece;
return ufo;
}
mobj_t *Obj_CreateSpecialUFO(void)
{
waypoint_t *finishWaypoint = K_GetFinishLineWaypoint();
waypoint_t *startWaypoint = NULL;
if (finishWaypoint != NULL)
{
const boolean huntbackwards = true;
const boolean useshortcuts = false;
const UINT32 traveldist = INT32_MAX; // Go as far back as possible. Not UINT32_MAX to avoid possible overflow.
boolean pathfindsuccess = false;
path_t pathtofinish = {0};
pathfindsuccess = K_PathfindThruCircuit(
finishWaypoint, traveldist,
&pathtofinish,
useshortcuts, huntbackwards
);
if (pathfindsuccess == true)
{
startWaypoint = (waypoint_t *)pathtofinish.array[ pathtofinish.numnodes - 1 ].nodedata;
Z_Free(pathtofinish.array);
}
}
return InitSpecialUFO(startWaypoint);
}
UINT32 K_GetSpecialUFODistance(void)
{
if (specialStage.active == true)
{
if (specialStage.ufo != NULL && P_MobjWasRemoved(specialStage.ufo) == false)
{
return (UINT32)ufo_distancetofinish(specialStage.ufo);
}
}
return UINT32_MAX;
}

View file

@ -386,11 +386,6 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
special->target->player->karmadelay = comebacktime;
}
return;
case MT_SPB:
{
Obj_SPBTouch(special, toucher);
return;
}
case MT_DUELBOMB:
{
Obj_DuelBombTouch(special, toucher);
@ -409,6 +404,18 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
player->emeralds |= special->extravalue1;
K_CheckEmeralds(player);
break;
case MT_SPECIAL_UFO:
if (!P_CanPickupItem(player, 0))
return;
if (special->threshold > 0)
return;
if (toucher->hitlag > 0)
return;
CONS_Printf("You win!\n");
break;
/*
case MT_EERIEFOG:
special->frame &= ~FF_TRANS80;
@ -1402,7 +1409,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
INT16 spacing = (target->radius >> 1) / target->scale;
// set respawn fuse
if (modeattacking) // no respawns
if (K_TimeAttackRules() == true) // no respawns
;
else if (target->threshold == KITEM_SUPERRING)
target->fuse = 20*TICRATE;
@ -2289,7 +2296,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
K_PlayPainSound(target, source);
if ((hardhit == true) || (cv_kartdebughuddrop.value && !modeattacking))
if ((hardhit == true) || cv_kartdebughuddrop.value)
{
K_DropItems(player);
}
@ -2311,6 +2318,11 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
}
else
{
if (target->type == MT_SPECIAL_UFO)
{
return Obj_SpecialUFODamage(target, inflictor, source, damagetype);
}
if (damagetype & DMG_STEAL)
{
// Not a player, steal damage is intended to not do anything

View file

@ -757,6 +757,53 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
// SRB2kart 011617 - Colission[sic] code for kart items //{
if (thing->type == MT_SPB)
{
if (tm.thing->type != MT_PLAYER
&& thing->tracer != tm.thing)
{
// Needs to be a player or the
// thing that we're chasing.
return BMIT_CONTINUE;
}
if (tm.thing->z > thing->z + thing->height)
{
return BMIT_CONTINUE; // overhead
}
if (tm.thing->z + tm.thing->height < thing->z)
{
return BMIT_CONTINUE; // underneath
}
Obj_SPBTouch(thing, tm.thing);
return BMIT_CONTINUE;
}
else if (tm.thing->type == MT_SPB)
{
if (thing->type != MT_PLAYER
&& tm.thing->tracer != thing)
{
// Needs to be a player or the
// thing that we're chasing.
return BMIT_CONTINUE;
}
if (tm.thing->z > thing->z + thing->height)
{
return BMIT_CONTINUE; // overhead
}
if (tm.thing->z + tm.thing->height < thing->z)
{
return BMIT_CONTINUE; // underneath
}
Obj_SPBTouch(tm.thing, thing);
return BMIT_CONTINUE;
}
if (thing->type == MT_SHRINK_GUN || thing->type == MT_SHRINK_PARTICLE)
{
if (tm.thing->type != MT_PLAYER)
@ -1379,6 +1426,14 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
return BMIT_CONTINUE;
}
else if (thing->type == MT_SPECIAL_UFO)
{
if (!(thing->flags & MF_SPECIAL))
{
Obj_PlayerUFOCollide(thing, tm.thing);
return BMIT_CONTINUE;
}
}
else if (thing->type == MT_BLUEROBRA_HEAD || thing->type == MT_BLUEROBRA_JOINT)
{
// see if it went over / under

View file

@ -46,6 +46,7 @@
#include "k_terrain.h"
#include "k_collide.h"
#include "k_objects.h"
#include "k_grandprix.h"
static CV_PossibleValue_t CV_BobSpeed[] = {{0, "MIN"}, {4*FRACUNIT, "MAX"}, {0, NULL}};
consvar_t cv_movebob = CVAR_INIT ("movebob", "1.0", CV_FLOAT|CV_SAVE, CV_BobSpeed, NULL);
@ -5306,10 +5307,24 @@ void P_RunOverlays(void)
mo->pitch = mo->target->pitch;
mo->roll = mo->target->roll;
#if 0
mo->spritexoffset = mo->target->spritexoffset;
mo->spriteyoffset = mo->target->spriteyoffset;
mo->spritexscale = mo->target->spritexscale;
mo->spriteyscale = mo->target->spriteyscale;
mo->sprxoff = mo->target->sprxoff;
mo->spryoff = mo->target->spryoff;
mo->sprzoff = mo->target->sprzoff;
#endif
mo->hitlag = mo->target->hitlag;
mo->eflags = (mo->eflags & ~MFE_DAMAGEHITLAG) | (mo->target->eflags & MFE_DAMAGEHITLAG);
if ((mo->flags & MF_DONTENCOREMAP) != (mo->target->flags & MF_DONTENCOREMAP))
mo->flags ^= MF_DONTENCOREMAP;
mo->dispoffset = mo->target->dispoffset + mo->info->dispoffset;
mo->dispoffset = mo->target->dispoffset;
if (!(mo->state->frame & FF_ANIMATE))
{
@ -5329,6 +5344,7 @@ void P_RunOverlays(void)
// if you're using FF_ANIMATE on an overlay,
// then you're on your own.
zoffs = 0;
mo->dispoffset++;
}
P_UnsetThingPosition(mo);
@ -6755,6 +6771,11 @@ static boolean P_MobjDeadThink(mobj_t *mobj)
S_StartSound(dust, sfx_s3k3d);
}
break;
case MT_SPECIAL_UFO_PIECE:
{
Obj_UFOPieceDead(mobj);
break;
}
default:
break;
}
@ -7366,6 +7387,16 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
Obj_DuelBombThink(mobj);
break;
}
case MT_SPECIAL_UFO:
{
Obj_SpecialUFOThinker(mobj);
break;
}
case MT_SPECIAL_UFO_PIECE:
{
Obj_UFOPieceThink(mobj);
break;
}
case MT_EMERALD:
{
if (battleovertime.enabled >= 10*TICRATE)
@ -10121,6 +10152,7 @@ static void P_DefaultMobjShadowScale(mobj_t *thing)
case MT_PLAYER:
case MT_KART_LEFTOVER:
case MT_BATTLECAPSULE:
case MT_SPECIAL_UFO:
thing->shadowscale = FRACUNIT;
break;
case MT_SMALLMACE:
@ -10978,6 +11010,11 @@ void P_RemoveMobj(mobj_t *mobj)
Obj_ShrinkGunRemoved(mobj);
}
if (mobj->type == MT_SPECIAL_UFO_PIECE)
{
Obj_UFOPieceRemoved(mobj);
}
mobj->health = 0; // Just because
// unlink from sector and block lists
@ -11541,13 +11578,12 @@ void P_SpawnPlayer(INT32 playernum)
}
else if (p->bot)
{
/*
if (bonusgame || specialstage || boss)
if (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE)
{
// Bots should avoid
// Bots aren't supposed to be here.
p->spectator = true;
}
*/
else
{
// No point in a spectating bot!
p->spectator = false;
@ -12018,7 +12054,7 @@ static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i)
// in record attack, only spawn ring capsules
// (behavior can be inverted with the Extra flag, i.e. item capsule spawns and ring capsule does not)
if (modeattacking
if (K_TimeAttackRules() == true
&& (!(mthing->args[2] & TMICF_INVERTTIMEATTACK) == !isRingCapsule))
return false;
}

File diff suppressed because it is too large Load diff

View file

@ -18,13 +18,19 @@
#pragma interface
#endif
// 1024 bytes is plenty for a savegame
#define SAVEGAMESIZE (1024)
// For netgames
#define NETSAVEGAMESIZE (768*1024)
// Persistent storage/archiving.
// These are the load / save game routines.
void P_SaveGame(INT16 mapnum);
void P_SaveNetGame(boolean resending);
boolean P_LoadGame(INT16 mapoverride);
boolean P_LoadNetGame(boolean reloading);
void P_SaveGame(savebuffer_t *save, INT16 mapnum);
void P_SaveNetGame(savebuffer_t *save, boolean resending);
boolean P_LoadGame(savebuffer_t *save, INT16 mapoverride);
boolean P_LoadNetGame(savebuffer_t *save, boolean reloading);
mobj_t *P_FindNewPosition(UINT32 oldposition);
@ -38,6 +44,13 @@ struct savedata_t
};
extern savedata_t savedata;
extern UINT8 *save_p;
struct savebuffer_t
{
UINT8 *buffer;
UINT8 *p;
UINT8 *end;
size_t size;
};
#endif

View file

@ -723,37 +723,40 @@ void P_WriteThings(void)
const char * filename;
size_t i, length;
mapthing_t *mt;
UINT8 *savebuffer, *savebuf_p;
savebuffer_t save;
INT16 temp;
savebuf_p = savebuffer = (UINT8 *)malloc(nummapthings * sizeof (mapthing_t));
save.size = nummapthings * sizeof (mapthing_t);
save.p = save.buffer = (UINT8 *)malloc(nummapthings * sizeof (mapthing_t));
if (!savebuf_p)
if (!save.p)
{
CONS_Alert(CONS_ERROR, M_GetText("No more free memory for thing writing!\n"));
return;
}
save.end = save.buffer + save.size;
mt = mapthings;
for (i = 0; i < nummapthings; i++, mt++)
{
WRITEINT16(savebuf_p, mt->x);
WRITEINT16(savebuf_p, mt->y);
WRITEINT16(save.p, mt->x);
WRITEINT16(save.p, mt->y);
WRITEINT16(savebuf_p, mt->angle);
WRITEINT16(save.p, mt->angle);
temp = (INT16)(mt->type + ((INT16)mt->extrainfo << 12));
WRITEINT16(savebuf_p, temp);
WRITEUINT16(savebuf_p, mt->options);
WRITEINT16(save.p, temp);
WRITEUINT16(save.p, mt->options);
}
length = savebuf_p - savebuffer;
length = save.p - save.buffer;
filename = va("newthings-%s.lmp", G_BuildMapName(gamemap));
FIL_WriteFile(filename, savebuffer, length);
free(savebuffer);
savebuf_p = NULL;
FIL_WriteFile(filename, save.buffer, length);
free(save.buffer);
save.p = NULL;
CONS_Printf(M_GetText("%s saved.\n"), filename);
}

View file

@ -258,6 +258,7 @@ TYPEDEF (polyfadedata_t);
// p_saveg.h
TYPEDEF (savedata_t);
TYPEDEF (savebuffer_t);
// p_setup.h
TYPEDEF (levelflat_t);