mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2026-04-27 04:21:47 +00:00
Merge branch 'master' of https://git.do.srb2.org/KartKrew/Kart into round-queue
# Conflicts: # src/d_netcmd.h
This commit is contained in:
commit
2d46112c58
58 changed files with 2402 additions and 664 deletions
|
|
@ -141,6 +141,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32
|
|||
k_podium.c
|
||||
k_rank.c
|
||||
k_vote.c
|
||||
k_serverstats.c
|
||||
)
|
||||
|
||||
if(SRB2_CONFIG_ENABLE_WEBM_MOVIES)
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@
|
|||
#include "m_cond.h" // netUnlocked
|
||||
#include "g_party.h"
|
||||
#include "k_vote.h"
|
||||
#include "k_serverstats.h"
|
||||
|
||||
// cl loading screen
|
||||
#include "v_video.h"
|
||||
|
|
@ -2775,6 +2776,7 @@ void CL_ClearPlayer(INT32 playernum)
|
|||
P_SetTarget(&players[playernum].hoverhyudoro, NULL);
|
||||
P_SetTarget(&players[playernum].stumbleIndicator, NULL);
|
||||
P_SetTarget(&players[playernum].sliptideZipIndicator, NULL);
|
||||
P_SetTarget(&players[playernum].ringShooter, NULL);
|
||||
}
|
||||
|
||||
// Handle parties.
|
||||
|
|
@ -2972,12 +2974,18 @@ static void Command_Nodes(void)
|
|||
|
||||
if (playernode[i] != UINT8_MAX)
|
||||
{
|
||||
CONS_Printf(" - node %.2d", playernode[i]);
|
||||
CONS_Printf(" [node %.2d]", playernode[i]);
|
||||
if (I_GetNodeAddress && (address = I_GetNodeAddress(playernode[i])) != NULL)
|
||||
CONS_Printf(" - %s", address);
|
||||
}
|
||||
|
||||
CONS_Printf(" [RRID-%s] ", GetPrettyRRID(players[i].public_key, true));
|
||||
if (K_UsingPowerLevels() != PWRLV_DISABLED) // No power type?!
|
||||
{
|
||||
CONS_Printf(" [%.4d PWR]", clientpowerlevels[i][K_UsingPowerLevels()]);
|
||||
}
|
||||
|
||||
|
||||
CONS_Printf(" [RRID-%s]", GetPrettyRRID(players[i].public_key, true));
|
||||
|
||||
if (IsPlayerAdmin(i))
|
||||
CONS_Printf(M_GetText(" (verified admin)"));
|
||||
|
|
@ -3846,6 +3854,7 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
|
|||
|
||||
READSTRINGN(*p, player_names[newplayernum], MAXPLAYERNAME);
|
||||
READMEM(*p, players[newplayernum].public_key, PUBKEYLENGTH);
|
||||
READMEM(*p, clientpowerlevels[newplayernum], sizeof(((serverplayer_t *)0)->powerlevels));
|
||||
|
||||
console = READUINT8(*p);
|
||||
splitscreenplayer = READUINT8(*p);
|
||||
|
|
@ -4002,8 +4011,10 @@ static void Got_AddBot(UINT8 **p, INT32 playernum)
|
|||
}
|
||||
|
||||
static boolean SV_AddWaitingPlayers(SINT8 node, UINT8 *availabilities,
|
||||
const char *name, uint8_t *key, const char *name2, uint8_t *key2,
|
||||
const char *name3, uint8_t *key3, const char *name4, uint8_t *key4)
|
||||
const char *name, uint8_t *key, UINT16 *pwr,
|
||||
const char *name2, uint8_t *key2, UINT16 *pwr2,
|
||||
const char *name3, uint8_t *key3, UINT16 *pwr3,
|
||||
const char *name4, uint8_t *key4, UINT16 *pwr4)
|
||||
{
|
||||
INT32 n, newplayernum, i;
|
||||
UINT8 buf[4 + MAXPLAYERNAME + PUBKEYLENGTH + MAXAVAILABILITY];
|
||||
|
|
@ -4070,24 +4081,28 @@ const char *name3, uint8_t *key3, const char *name4, uint8_t *key4)
|
|||
nodetoplayer[node] = newplayernum;
|
||||
WRITESTRINGN(buf_p, name, MAXPLAYERNAME);
|
||||
WRITEMEM(buf_p, key, PUBKEYLENGTH);
|
||||
WRITEMEM(buf_p, pwr, sizeof(((serverplayer_t *)0)->powerlevels));
|
||||
}
|
||||
else if (playerpernode[node] < 2)
|
||||
{
|
||||
nodetoplayer2[node] = newplayernum;
|
||||
WRITESTRINGN(buf_p, name2, MAXPLAYERNAME);
|
||||
WRITEMEM(buf_p, key2, PUBKEYLENGTH);
|
||||
WRITEMEM(buf_p, pwr2, sizeof(((serverplayer_t *)0)->powerlevels));
|
||||
}
|
||||
else if (playerpernode[node] < 3)
|
||||
{
|
||||
nodetoplayer3[node] = newplayernum;
|
||||
WRITESTRINGN(buf_p, name3, MAXPLAYERNAME);
|
||||
WRITEMEM(buf_p, key3, PUBKEYLENGTH);
|
||||
WRITEMEM(buf_p, pwr3, sizeof(((serverplayer_t *)0)->powerlevels));
|
||||
}
|
||||
else if (playerpernode[node] < 4)
|
||||
{
|
||||
nodetoplayer4[node] = newplayernum;
|
||||
WRITESTRINGN(buf_p, name4, MAXPLAYERNAME);
|
||||
WRITEMEM(buf_p, key4, PUBKEYLENGTH);
|
||||
WRITEMEM(buf_p, pwr4, sizeof(((serverplayer_t *)0)->powerlevels));
|
||||
}
|
||||
|
||||
WRITEUINT8(buf_p, nodetoplayer[node]); // consoleplayer
|
||||
|
|
@ -4177,8 +4192,11 @@ boolean SV_SpawnServer(void)
|
|||
UINT8 *availabilitiesbuffer = R_GetSkinAvailabilities(false, false);
|
||||
SINT8 node = 0;
|
||||
for (; node < MAXNETNODES; node++)
|
||||
result |= SV_AddWaitingPlayers(node, availabilitiesbuffer, cv_playername[0].zstring, PR_GetLocalPlayerProfile(0)->public_key, cv_playername[1].zstring, PR_GetLocalPlayerProfile(1)->public_key,
|
||||
cv_playername[2].zstring, PR_GetLocalPlayerProfile(2)->public_key, cv_playername[3].zstring, PR_GetLocalPlayerProfile(3)->public_key);
|
||||
result |= SV_AddWaitingPlayers(node, availabilitiesbuffer,
|
||||
cv_playername[0].zstring, PR_GetLocalPlayerProfile(0)->public_key, SV_RetrieveStats(PR_GetLocalPlayerProfile(0)->public_key)->powerlevels,
|
||||
cv_playername[1].zstring, PR_GetLocalPlayerProfile(1)->public_key, SV_RetrieveStats(PR_GetLocalPlayerProfile(1)->public_key)->powerlevels,
|
||||
cv_playername[2].zstring, PR_GetLocalPlayerProfile(2)->public_key, SV_RetrieveStats(PR_GetLocalPlayerProfile(2)->public_key)->powerlevels,
|
||||
cv_playername[3].zstring, PR_GetLocalPlayerProfile(3)->public_key, SV_RetrieveStats(PR_GetLocalPlayerProfile(3)->public_key)->powerlevels);
|
||||
}
|
||||
return result;
|
||||
#endif
|
||||
|
|
@ -4259,13 +4277,13 @@ static size_t TotalTextCmdPerTic(tic_t tic)
|
|||
memset(allZero, 0, PUBKEYLENGTH);
|
||||
|
||||
if (split == 0)
|
||||
return (memcmp(players[nodetoplayer[node]].public_key, allZero, PUBKEYLENGTH) == 0);
|
||||
return PR_IsKeyGuest(players[nodetoplayer[node]].public_key);
|
||||
else if (split == 1)
|
||||
return (memcmp(players[nodetoplayer2[node]].public_key, allZero, PUBKEYLENGTH) == 0);
|
||||
return PR_IsKeyGuest(players[nodetoplayer2[node]].public_key);
|
||||
else if (split == 2)
|
||||
return (memcmp(players[nodetoplayer3[node]].public_key, allZero, PUBKEYLENGTH) == 0);
|
||||
return PR_IsKeyGuest(players[nodetoplayer3[node]].public_key);
|
||||
else if (split == 3)
|
||||
return (memcmp(players[nodetoplayer4[node]].public_key, allZero, PUBKEYLENGTH) == 0);
|
||||
return PR_IsKeyGuest(players[nodetoplayer4[node]].public_key);
|
||||
else
|
||||
I_Error("IsSplitPlayerOnNodeGuest: Out of bounds");
|
||||
return false; // unreachable
|
||||
|
|
@ -4274,10 +4292,7 @@ static size_t TotalTextCmdPerTic(tic_t tic)
|
|||
|
||||
static boolean IsPlayerGuest(int player)
|
||||
{
|
||||
char allZero[PUBKEYLENGTH];
|
||||
memset(allZero, 0, PUBKEYLENGTH);
|
||||
|
||||
return (memcmp(players[player].public_key, allZero, PUBKEYLENGTH) == 0);
|
||||
return PR_IsKeyGuest(players[player].public_key);
|
||||
}
|
||||
|
||||
/** Called when a PT_CLIENTJOIN packet is received
|
||||
|
|
@ -4288,7 +4303,7 @@ static boolean IsPlayerGuest(int player)
|
|||
static void HandleConnect(SINT8 node)
|
||||
{
|
||||
char names[MAXSPLITSCREENPLAYERS][MAXPLAYERNAME + 1];
|
||||
INT32 i;
|
||||
INT32 i, j;
|
||||
UINT8 availabilitiesbuffer[MAXAVAILABILITY];
|
||||
|
||||
// Sal: Dedicated mode is INCREDIBLY hacked together.
|
||||
|
|
@ -4402,11 +4417,8 @@ static void HandleConnect(SINT8 node)
|
|||
memcpy(lastReceivedKey[node][i], PR_GetLocalPlayerProfile(i)->public_key, sizeof(lastReceivedKey[node][i]));
|
||||
}
|
||||
else // Remote player, gotta check their signature.
|
||||
{
|
||||
char allZero[PUBKEYLENGTH];
|
||||
memset(allZero, 0, sizeof(allZero));
|
||||
|
||||
if (memcmp(lastReceivedKey[node][i], allZero, PUBKEYLENGTH) == 0) // IsSplitPlayerOnNodeGuest isn't appropriate here, they're not in-game yet!
|
||||
{
|
||||
if (PR_IsKeyGuest(lastReceivedKey[node][i])) // IsSplitPlayerOnNodeGuest isn't appropriate here, they're not in-game yet!
|
||||
{
|
||||
if (!cv_allowguests.value)
|
||||
{
|
||||
|
|
@ -4427,6 +4439,42 @@ static void HandleConnect(SINT8 node)
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check non-GUESTS for duplicate pubkeys, they'll create nonsense stats
|
||||
if (!PR_IsKeyGuest(lastReceivedKey[node][i]))
|
||||
{
|
||||
// Players already here
|
||||
for (j = 0; j < MAXPLAYERS; j++)
|
||||
{
|
||||
if (memcmp(lastReceivedKey[node][i], players[j].public_key, PUBKEYLENGTH) == 0)
|
||||
{
|
||||
#ifdef DEVELOP
|
||||
CONS_Alert(CONS_WARNING, "Joining player's pubkey matches existing player, stat updates will be nonsense!\n");
|
||||
#else
|
||||
SV_SendRefuse(node, M_GetText("Duplicate pubkey already on server.\n(Did you share your profile?)"));
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Players we're trying to add
|
||||
for (j = 0; j < netbuffer->u.clientcfg.localplayers - playerpernode[node]; j++)
|
||||
{
|
||||
if (PR_IsKeyGuest(lastReceivedKey[node][j]))
|
||||
continue;
|
||||
if (i == j)
|
||||
continue;
|
||||
if (memcmp(lastReceivedKey[node][i], lastReceivedKey[node][j], PUBKEYLENGTH) == 0)
|
||||
{
|
||||
#ifdef DEVELOP
|
||||
CONS_Alert(CONS_WARNING, "Players with same pubkey in the joning party, stat updates will be nonsense!\n");
|
||||
#else
|
||||
SV_SendRefuse(node, M_GetText("Duplicate pubkey in local party.\n(How did you even do this?)"));
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(availabilitiesbuffer, netbuffer->u.clientcfg.availabilities, sizeof(availabilitiesbuffer));
|
||||
|
|
@ -4466,8 +4514,11 @@ static void HandleConnect(SINT8 node)
|
|||
DEBFILE("send savegame\n");
|
||||
}
|
||||
|
||||
SV_AddWaitingPlayers(node, availabilitiesbuffer, names[0], lastReceivedKey[node][0], names[1], lastReceivedKey[node][1],
|
||||
names[2], lastReceivedKey[node][2], names[3], lastReceivedKey[node][3]);
|
||||
SV_AddWaitingPlayers(node, availabilitiesbuffer,
|
||||
names[0], lastReceivedKey[node][0], SV_RetrieveStats(lastReceivedKey[node][0])->powerlevels,
|
||||
names[1], lastReceivedKey[node][1], SV_RetrieveStats(lastReceivedKey[node][1])->powerlevels,
|
||||
names[2], lastReceivedKey[node][2], SV_RetrieveStats(lastReceivedKey[node][2])->powerlevels,
|
||||
names[3], lastReceivedKey[node][3], SV_RetrieveStats(lastReceivedKey[node][3])->powerlevels);
|
||||
joindelay += cv_joindelay.value * TICRATE;
|
||||
player_joining = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ struct event_t
|
|||
{
|
||||
evtype_t type;
|
||||
INT32 data1; // keys / mouse/joystick buttons
|
||||
INT32 data2; // mouse/joystick x move
|
||||
INT32 data2; // mouse/joystick x move; key repeat
|
||||
INT32 data3; // mouse/joystick y move
|
||||
INT32 device; // which device ID it belongs to (controller ID)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@
|
|||
#include "acs/interface.h"
|
||||
#include "k_podium.h"
|
||||
#include "k_vote.h"
|
||||
#include "k_serverstats.h"
|
||||
|
||||
#ifdef HWRENDER
|
||||
#include "hardware/hw_main.h" // 3D View Rendering
|
||||
|
|
@ -1620,6 +1621,8 @@ void D_SRB2Main(void)
|
|||
// Load Profiles now that default controls have been defined
|
||||
PR_LoadProfiles(); // load control profiles
|
||||
|
||||
SV_LoadStats();
|
||||
|
||||
#if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL)
|
||||
VID_PrepareModeList(); // Regenerate Modelist according to cv_fullscreen
|
||||
#endif
|
||||
|
|
@ -1888,6 +1891,8 @@ void D_SRB2Main(void)
|
|||
}
|
||||
}
|
||||
|
||||
SV_SaveStats();
|
||||
|
||||
if (autostart || netgame)
|
||||
{
|
||||
gameaction = ga_nothing;
|
||||
|
|
|
|||
|
|
@ -80,7 +80,6 @@
|
|||
|
||||
static void Got_NameAndColor(UINT8 **cp, INT32 playernum);
|
||||
static void Got_WeaponPref(UINT8 **cp, INT32 playernum);
|
||||
static void Got_PowerLevel(UINT8 **cp, INT32 playernum);
|
||||
static void Got_PartyInvite(UINT8 **cp, INT32 playernum);
|
||||
static void Got_AcceptPartyInvite(UINT8 **cp, INT32 playernum);
|
||||
static void Got_CancelPartyInvite(UINT8 **cp, INT32 playernum);
|
||||
|
|
@ -93,7 +92,6 @@ static void Got_PickVotecmd(UINT8 **cp, INT32 playernum);
|
|||
static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum);
|
||||
static void Got_Addfilecmd(UINT8 **cp, INT32 playernum);
|
||||
static void Got_Pause(UINT8 **cp, INT32 playernum);
|
||||
static void Got_Respawn(UINT8 **cp, INT32 playernum);
|
||||
static void Got_RandomSeed(UINT8 **cp, INT32 playernum);
|
||||
static void Got_RunSOCcmd(UINT8 **cp, INT32 playernum);
|
||||
static void Got_Teamchange(UINT8 **cp, INT32 playernum);
|
||||
|
|
@ -181,7 +179,6 @@ static void Command_ListWADS_f(void);
|
|||
static void Command_ListDoomednums_f(void);
|
||||
static void Command_RunSOC(void);
|
||||
static void Command_Pause(void);
|
||||
static void Command_Respawn(void);
|
||||
|
||||
static void Command_Version_f(void);
|
||||
#ifdef UPDATE_ALERT
|
||||
|
|
@ -612,7 +609,6 @@ const char *netxcmdnames[MAXNETXCMD - 1] =
|
|||
"RUNSOC", // XD_RUNSOC
|
||||
"REQADDFILE", // XD_REQADDFILE
|
||||
"SETMOTD", // XD_SETMOTD
|
||||
"RESPAWN", // XD_RESPAWN
|
||||
"DEMOTED", // XD_DEMOTED
|
||||
"LUACMD", // XD_LUACMD
|
||||
"LUAVAR", // XD_LUAVAR
|
||||
|
|
@ -623,7 +619,6 @@ const char *netxcmdnames[MAXNETXCMD - 1] =
|
|||
"MODIFYVOTE", // XD_MODIFYVOTE
|
||||
"PICKVOTE", // XD_PICKVOTE
|
||||
"REMOVEPLAYER", // XD_REMOVEPLAYER
|
||||
"POWERLEVEL", // XD_POWERLEVEL
|
||||
"PARTYINVITE", // XD_PARTYINVITE
|
||||
"ACCEPTPARTYINVITE", // XD_ACCEPTPARTYINVITE
|
||||
"LEAVEPARTY", // XD_LEAVEPARTY
|
||||
|
|
@ -664,7 +659,6 @@ void D_RegisterServerCommands(void)
|
|||
|
||||
RegisterNetXCmd(XD_NAMEANDCOLOR, Got_NameAndColor);
|
||||
RegisterNetXCmd(XD_WEAPONPREF, Got_WeaponPref);
|
||||
RegisterNetXCmd(XD_POWERLEVEL, Got_PowerLevel);
|
||||
RegisterNetXCmd(XD_PARTYINVITE, Got_PartyInvite);
|
||||
RegisterNetXCmd(XD_ACCEPTPARTYINVITE, Got_AcceptPartyInvite);
|
||||
RegisterNetXCmd(XD_CANCELPARTYINVITE, Got_CancelPartyInvite);
|
||||
|
|
@ -674,7 +668,6 @@ void D_RegisterServerCommands(void)
|
|||
RegisterNetXCmd(XD_ADDFILE, Got_Addfilecmd);
|
||||
RegisterNetXCmd(XD_REQADDFILE, Got_RequestAddfilecmd);
|
||||
RegisterNetXCmd(XD_PAUSE, Got_Pause);
|
||||
RegisterNetXCmd(XD_RESPAWN, Got_Respawn);
|
||||
RegisterNetXCmd(XD_RUNSOC, Got_RunSOCcmd);
|
||||
RegisterNetXCmd(XD_LUACMD, Got_Luacmd);
|
||||
RegisterNetXCmd(XD_LUAFILE, Got_LuaFile);
|
||||
|
|
@ -724,7 +717,6 @@ void D_RegisterServerCommands(void)
|
|||
|
||||
COM_AddCommand("runsoc", Command_RunSOC);
|
||||
COM_AddCommand("pause", Command_Pause);
|
||||
COM_AddCommand("respawn", Command_Respawn);
|
||||
|
||||
COM_AddCommand("gametype", Command_ShowGametype_f);
|
||||
COM_AddCommand("version", Command_Version_f);
|
||||
|
|
@ -1817,17 +1809,6 @@ static void Got_WeaponPref(UINT8 **cp,INT32 playernum)
|
|||
demo_extradata[playernum] |= DXD_WEAPONPREF;
|
||||
}
|
||||
|
||||
static void Got_PowerLevel(UINT8 **cp,INT32 playernum)
|
||||
{
|
||||
UINT16 race = (UINT16)READUINT16(*cp);
|
||||
UINT16 battle = (UINT16)READUINT16(*cp);
|
||||
|
||||
clientpowerlevels[playernum][PWRLV_RACE] = min(PWRLVRECORD_MAX, race);
|
||||
clientpowerlevels[playernum][PWRLV_BATTLE] = min(PWRLVRECORD_MAX, battle);
|
||||
|
||||
CONS_Debug(DBG_GAMELOGIC, "set player %d to power %d\n", playernum, race);
|
||||
}
|
||||
|
||||
static void Got_PartyInvite(UINT8 **cp,INT32 playernum)
|
||||
{
|
||||
UINT8 invitee;
|
||||
|
|
@ -1979,28 +1960,8 @@ static void Got_LeaveParty(UINT8 **cp,INT32 playernum)
|
|||
|
||||
void D_SendPlayerConfig(UINT8 n)
|
||||
{
|
||||
const profile_t *pr = PR_GetProfile(cv_lastprofile[n].value);
|
||||
|
||||
UINT8 buf[4];
|
||||
UINT8 *p = buf;
|
||||
|
||||
SendNameAndColor(n);
|
||||
WeaponPref_Send(n);
|
||||
|
||||
if (pr != NULL)
|
||||
{
|
||||
// Send it over
|
||||
WRITEUINT16(p, pr->powerlevels[PWRLV_RACE]);
|
||||
WRITEUINT16(p, pr->powerlevels[PWRLV_BATTLE]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Guest players have no power level
|
||||
WRITEUINT16(p, 0);
|
||||
WRITEUINT16(p, 0);
|
||||
}
|
||||
|
||||
SendNetXCmdForPlayer(n, XD_POWERLEVEL, buf, p-buf);
|
||||
}
|
||||
|
||||
void D_Cheat(INT32 playernum, INT32 cheat, ...)
|
||||
|
|
@ -3596,61 +3557,6 @@ static void Got_Pause(UINT8 **cp, INT32 playernum)
|
|||
G_ResetAllDeviceRumbles();
|
||||
}
|
||||
|
||||
// Command for stuck characters in netgames, griefing, etc.
|
||||
static void Command_Respawn(void)
|
||||
{
|
||||
UINT8 buf[4];
|
||||
UINT8 *cp = buf;
|
||||
|
||||
|
||||
|
||||
if (!(gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_VOTING))
|
||||
{
|
||||
CONS_Printf(M_GetText("You must be in a level to use this.\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (players[consoleplayer].mo && !P_IsObjectOnGround(players[consoleplayer].mo)) // KART: Nice try, but no, you won't be cheesing spb anymore.
|
||||
{
|
||||
CONS_Printf(M_GetText("You must be on the floor to use this.\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
// todo: this probably isnt necessary anymore with v2
|
||||
if (players[consoleplayer].mo && (P_PlayerInPain(&players[consoleplayer]) || spbplace == players[consoleplayer].position)) // KART: Nice try, but no, you won't be cheesing spb anymore (x2)
|
||||
{
|
||||
CONS_Printf(M_GetText("Nice try.\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
WRITEINT32(cp, consoleplayer);
|
||||
SendNetXCmd(XD_RESPAWN, &buf, 4);
|
||||
}
|
||||
|
||||
static void Got_Respawn(UINT8 **cp, INT32 playernum)
|
||||
{
|
||||
INT32 respawnplayer = READINT32(*cp);
|
||||
|
||||
// You can't respawn someone else. Nice try, there.
|
||||
if (respawnplayer != playernum || P_PlayerInPain(&players[respawnplayer]) || spbplace == players[respawnplayer].position) // srb2kart: "|| (!(gametyperules & GTR_CIRCUIT))"
|
||||
{
|
||||
CONS_Alert(CONS_WARNING, M_GetText("Illegal respawn command received from %s\n"), player_names[playernum]);
|
||||
if (server)
|
||||
SendKick(playernum, KICK_MSG_CON_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (players[respawnplayer].mo)
|
||||
{
|
||||
// incase the above checks were modified to allow sending a respawn on these occasions:
|
||||
if (!P_IsObjectOnGround(players[respawnplayer].mo))
|
||||
return;
|
||||
|
||||
P_DamageMobj(players[respawnplayer].mo, NULL, NULL, 1, DMG_DEATHPIT);
|
||||
demo_extradata[playernum] |= DXD_RESPAWN;
|
||||
}
|
||||
}
|
||||
|
||||
/** Deals with an ::XD_RANDOMSEED message in a netgame.
|
||||
* These messages set the position of the random number LUT and are crucial to
|
||||
* correct synchronization.
|
||||
|
|
@ -4165,10 +4071,7 @@ static void Command_Login_f(void)
|
|||
|
||||
boolean IsPlayerAdmin(INT32 playernum)
|
||||
{
|
||||
#if defined (TESTERS) || defined (HOSTTESTERS)
|
||||
(void)playernum;
|
||||
return false;
|
||||
#elif defined (DEVELOP)
|
||||
#if defined(DEVELOP) && !(defined(HOSTTESTERS) || defined(TESTERS))
|
||||
return playernum != serverplayer;
|
||||
#else
|
||||
INT32 i;
|
||||
|
|
|
|||
|
|
@ -158,31 +158,29 @@ typedef enum
|
|||
XD_RUNSOC, // 15
|
||||
XD_REQADDFILE, // 16
|
||||
XD_SETMOTD, // 17
|
||||
XD_RESPAWN, // 18
|
||||
XD_DEMOTED, // 19
|
||||
XD_LUACMD, // 20
|
||||
XD_LUAVAR, // 21
|
||||
XD_LUAFILE, // 22
|
||||
XD_DEMOTED, // 18
|
||||
XD_LUACMD, // 19
|
||||
XD_LUAVAR, // 20
|
||||
XD_LUAFILE, // 21
|
||||
|
||||
// SRB2Kart
|
||||
XD_SETUPVOTE, // 23
|
||||
XD_MODIFYVOTE, // 24
|
||||
XD_PICKVOTE, // 25
|
||||
XD_REMOVEPLAYER,// 26
|
||||
XD_POWERLEVEL, // 27
|
||||
XD_PARTYINVITE, // 28
|
||||
XD_ACCEPTPARTYINVITE, // 29
|
||||
XD_LEAVEPARTY, // 30
|
||||
XD_CANCELPARTYINVITE, // 31
|
||||
XD_CHEAT, // 32
|
||||
XD_ADDBOT, // 33
|
||||
XD_DISCORD, // 34
|
||||
XD_PLAYSOUND, // 35
|
||||
XD_SCHEDULETASK, // 36
|
||||
XD_SCHEDULECLEAR, // 37
|
||||
XD_AUTOMATE, // 38
|
||||
XD_REQMAPQUEUE, // 39
|
||||
XD_MAPQUEUE, // 40
|
||||
XD_SETUPVOTE, // 22
|
||||
XD_MODIFYVOTE, // 23
|
||||
XD_PICKVOTE, // 24
|
||||
XD_REMOVEPLAYER,// 25
|
||||
XD_PARTYINVITE, // 26
|
||||
XD_ACCEPTPARTYINVITE, // 27
|
||||
XD_LEAVEPARTY, // 28
|
||||
XD_CANCELPARTYINVITE, // 29
|
||||
XD_CHEAT, // 30
|
||||
XD_ADDBOT, // 31
|
||||
XD_DISCORD, // 32
|
||||
XD_PLAYSOUND, // 33
|
||||
XD_SCHEDULETASK, // 34
|
||||
XD_SCHEDULECLEAR, // 35
|
||||
XD_AUTOMATE, // 36
|
||||
XD_REQMAPQUEUE, // 37
|
||||
XD_MAPQUEUE, // 38
|
||||
|
||||
MAXNETXCMD
|
||||
} netxcmd_t;
|
||||
|
|
|
|||
|
|
@ -310,6 +310,7 @@ struct respawnvars_t
|
|||
tic_t dropdash; // Drop Dash charge timer
|
||||
boolean truedeath; // Your soul has left your body
|
||||
boolean manual; // Respawn coords were manually set, please respawn exactly there
|
||||
boolean fromRingShooter; // Respawn was from Ring Shooter, don't allow E-Brake drop
|
||||
boolean init;
|
||||
};
|
||||
|
||||
|
|
@ -505,8 +506,9 @@ struct player_t
|
|||
UINT32 distancetofinish;
|
||||
waypoint_t *currentwaypoint;
|
||||
waypoint_t *nextwaypoint;
|
||||
respawnvars_t respawn; // Respawn info
|
||||
tic_t airtime; // Keep track of how long you've been in the air
|
||||
respawnvars_t respawn; // Respawn info
|
||||
mobj_t *ringShooter; // DEZ respawner object
|
||||
tic_t airtime; // Used to track just air time, but has evolved over time into a general "karted" timer. Rename this variable?
|
||||
UINT8 startboost; // (0 to 125) - Boost you get from start of race or respawn drop dash
|
||||
|
||||
UINT16 flashing;
|
||||
|
|
|
|||
|
|
@ -35,13 +35,15 @@ typedef enum
|
|||
BT_BRAKE = 1<<3, // Brake
|
||||
BT_ATTACK = 1<<4, // Use Item
|
||||
BT_LOOKBACK = 1<<5, // Look Backward
|
||||
BT_RESPAWN = 1<<6, // Respawn
|
||||
BT_VOTE = 1<<7, // Vote
|
||||
|
||||
BT_EBRAKEMASK = (BT_ACCELERATE|BT_BRAKE),
|
||||
BT_SPINDASHMASK = (BT_ACCELERATE|BT_BRAKE|BT_DRIFT),
|
||||
|
||||
// free: 1<<6 to 1<<12
|
||||
// free: 1<<8 to 1<<12
|
||||
|
||||
// Lua garbage
|
||||
// Lua garbage, replace with freeslottable buttons some day
|
||||
BT_LUAA = 1<<13,
|
||||
BT_LUAB = 1<<14,
|
||||
BT_LUAC = 1<<15,
|
||||
|
|
|
|||
|
|
@ -2458,25 +2458,6 @@ static void readcondition(UINT8 set, UINT32 id, char *word2)
|
|||
return;
|
||||
}
|
||||
}
|
||||
else if (fastcmp(params[0], "POWERLEVEL"))
|
||||
{
|
||||
PARAMCHECK(2);
|
||||
ty = UC_POWERLEVEL;
|
||||
re = atoi(params[1]);
|
||||
x1 = atoi(params[2]);
|
||||
|
||||
if (re < PWRLVRECORD_MIN || re > PWRLVRECORD_MAX)
|
||||
{
|
||||
deh_warning("Power level requirement %d out of range (%d - %d) for condition ID %d", re, PWRLVRECORD_MIN, PWRLVRECORD_MAX, id+1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (x1 < 0 || x1 >= PWRLV_NUMTYPES)
|
||||
{
|
||||
deh_warning("Power level type %d out of range (0 - %d) for condition ID %d", x1, PWRLV_NUMTYPES-1, id+1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (fastcmp(params[0], "GAMECLEAR"))
|
||||
{
|
||||
ty = UC_GAMECLEAR;
|
||||
|
|
|
|||
|
|
@ -326,6 +326,7 @@ actionpointer_t actionpointers[] =
|
|||
{{A_FlameShieldPaper}, "A_FLAMESHIELDPAPER"},
|
||||
{{A_InvincSparkleRotate}, "A_INVINCSPARKLEROTATE"},
|
||||
{{A_SpawnItemDebrisCloud}, "A_SPAWNITEMDEBRISCLOUD"},
|
||||
{{A_RingShooterFace}, "A_RINGSHOOTERFACE"},
|
||||
|
||||
{{NULL}, "NONE"},
|
||||
|
||||
|
|
@ -3867,6 +3868,15 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi
|
|||
|
||||
"S_SMOOTHLANDING",
|
||||
|
||||
// DEZ Ring Shooter
|
||||
"S_TIREGRABBER",
|
||||
"S_RINGSHOOTER_SIDE",
|
||||
"S_RINGSHOOTER_NIPPLES",
|
||||
"S_RINGSHOOTER_SCREEN",
|
||||
"S_RINGSHOOTER_NUMBERBACK",
|
||||
"S_RINGSHOOTER_NUMBERFRONT",
|
||||
"S_RINGSHOOTER_FACE",
|
||||
|
||||
// DEZ respawn laser
|
||||
"S_DEZLASER",
|
||||
"S_DEZLASER_TRAIL1",
|
||||
|
|
@ -5433,6 +5443,11 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t
|
|||
|
||||
"MT_SMOOTHLANDING",
|
||||
|
||||
"MT_TIREGRABBER",
|
||||
"MT_RINGSHOOTER",
|
||||
"MT_RINGSHOOTER_PART",
|
||||
"MT_RINGSHOOTER_SCREEN",
|
||||
|
||||
"MT_DEZLASER",
|
||||
|
||||
"MT_WAYPOINT",
|
||||
|
|
|
|||
|
|
@ -353,14 +353,6 @@ void G_ReadDemoExtraData(void)
|
|||
}
|
||||
}
|
||||
}
|
||||
if (extradata & DXD_RESPAWN)
|
||||
{
|
||||
if (players[p].mo)
|
||||
{
|
||||
// Is this how this should work..?
|
||||
P_DamageMobj(players[p].mo, NULL, NULL, 1, DMG_DEATHPIT);
|
||||
}
|
||||
}
|
||||
if (extradata & DXD_WEAPONPREF)
|
||||
{
|
||||
WeaponPref_Parse(&demobuf.p, p);
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ extern UINT8 demo_writerng;
|
|||
#define DXD_NAME 0x08 // name changed
|
||||
#define DXD_COLOR 0x10 // color changed
|
||||
#define DXD_FOLLOWER 0x20 // follower was changed
|
||||
#define DXD_RESPAWN 0x40 // "respawn" command in console
|
||||
|
||||
#define DXD_WEAPONPREF 0x80 // netsynced playsim settings were changed
|
||||
|
||||
#define DXD_PST_PLAYING 0x01
|
||||
|
|
|
|||
17
src/g_game.c
17
src/g_game.c
|
|
@ -67,6 +67,7 @@
|
|||
#include "acs/interface.h"
|
||||
#include "g_party.h"
|
||||
#include "k_vote.h"
|
||||
#include "k_serverstats.h"
|
||||
|
||||
#ifdef HAVE_DISCORDRPC
|
||||
#include "discord.h"
|
||||
|
|
@ -1323,7 +1324,6 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
|
|||
// C
|
||||
if (G_PlayerInputDown(forplayer, gc_spindash, 0))
|
||||
{
|
||||
forward = 0;
|
||||
cmd->buttons |= BT_SPINDASHMASK;
|
||||
}
|
||||
|
||||
|
|
@ -1339,6 +1339,18 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
|
|||
cmd->buttons |= BT_LOOKBACK;
|
||||
}
|
||||
|
||||
// respawn
|
||||
if (G_PlayerInputDown(forplayer, gc_respawn, 0))
|
||||
{
|
||||
cmd->buttons |= (BT_RESPAWN | BT_EBRAKEMASK);
|
||||
}
|
||||
|
||||
// mp general function button
|
||||
if (G_PlayerInputDown(forplayer, gc_vote, 0))
|
||||
{
|
||||
cmd->buttons |= BT_VOTE;
|
||||
}
|
||||
|
||||
// lua buttons a thru c
|
||||
if (G_PlayerInputDown(forplayer, gc_luaa, 0)) { cmd->buttons |= BT_LUAA; }
|
||||
if (G_PlayerInputDown(forplayer, gc_luab, 0)) { cmd->buttons |= BT_LUAB; }
|
||||
|
|
@ -1580,6 +1592,8 @@ void G_DoLoadLevelEx(boolean resetplayer, gamestate_t newstate)
|
|||
// clear hud messages remains (usually from game startup)
|
||||
CON_ClearHUD();
|
||||
|
||||
SV_UpdateStats();
|
||||
|
||||
server_lagless = cv_lagless.value;
|
||||
|
||||
if (doAutomate == true)
|
||||
|
|
@ -2630,6 +2644,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
|
|||
P_SetTarget(&players[player].follower, NULL);
|
||||
P_SetTarget(&players[player].awayview.mobj, NULL);
|
||||
P_SetTarget(&players[player].stumbleIndicator, NULL);
|
||||
P_SetTarget(&players[player].ringShooter, NULL);
|
||||
P_SetTarget(&players[player].followmobj, NULL);
|
||||
|
||||
hoverhyudoro = players[player].hoverhyudoro;
|
||||
|
|
|
|||
|
|
@ -456,6 +456,11 @@ void G_MapEventsToControls(event_t *ev)
|
|||
case ev_keydown:
|
||||
if (ev->data1 < NUMINPUTS)
|
||||
{
|
||||
if (ev->data2) // OS repeat? We handle that ourselves
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
DeviceGameKeyDownArray[ev->data1] = JOYAXISRANGE;
|
||||
|
||||
if (AutomaticControllerReassignmentIsAllowed(ev->device))
|
||||
|
|
|
|||
|
|
@ -91,13 +91,13 @@ typedef enum
|
|||
|
||||
// alias gameplay controls
|
||||
gc_accel = gc_a,
|
||||
gc_brake = gc_x,
|
||||
gc_drift = gc_r,
|
||||
|
||||
gc_item = gc_l,
|
||||
gc_spindash = gc_c,
|
||||
|
||||
gc_lookback = gc_b,
|
||||
gc_spindash = gc_c,
|
||||
gc_brake = gc_x,
|
||||
gc_respawn = gc_y,
|
||||
gc_vote = gc_z,
|
||||
gc_item = gc_l,
|
||||
gc_drift = gc_r,
|
||||
} gamecontrols_e;
|
||||
|
||||
// mouse values are used once
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ target_sources(SRB2SDL2 PRIVATE
|
|||
pass_twodee.hpp
|
||||
pass.cpp
|
||||
pass.hpp
|
||||
patch_atlas.cpp
|
||||
patch_atlas.hpp
|
||||
twodee.cpp
|
||||
twodee.hpp
|
||||
)
|
||||
|
|
|
|||
|
|
@ -22,47 +22,10 @@ using namespace srb2;
|
|||
using namespace srb2::hwr2;
|
||||
using namespace srb2::rhi;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct AtlasEntry
|
||||
{
|
||||
uint32_t x;
|
||||
uint32_t y;
|
||||
uint32_t w;
|
||||
uint32_t h;
|
||||
|
||||
uint32_t trim_x;
|
||||
uint32_t trim_y;
|
||||
uint32_t orig_w;
|
||||
uint32_t orig_h;
|
||||
};
|
||||
|
||||
struct Atlas
|
||||
{
|
||||
Atlas() = default;
|
||||
Atlas(Atlas&&) = default;
|
||||
|
||||
Handle<Texture> tex;
|
||||
uint32_t tex_width;
|
||||
uint32_t tex_height;
|
||||
std::unordered_map<const patch_t*, AtlasEntry> entries;
|
||||
|
||||
std::unique_ptr<stbrp_context> rp_ctx {nullptr};
|
||||
std::unique_ptr<stbrp_node[]> rp_nodes {nullptr};
|
||||
|
||||
Atlas& operator=(Atlas&&) = default;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
struct srb2::hwr2::TwodeePassData
|
||||
{
|
||||
Handle<Texture> default_tex;
|
||||
Handle<Texture> default_colormap_tex;
|
||||
std::vector<Atlas> patch_atlases;
|
||||
std::unordered_map<const patch_t*, size_t> patch_lookup;
|
||||
std::vector<const patch_t*> patches_to_upload;
|
||||
std::unordered_map<const uint8_t*, Handle<Texture>> colormaps;
|
||||
std::vector<const uint8_t*> colormaps_to_upload;
|
||||
std::unordered_map<TwodeePipelineKey, Handle<Pipeline>> pipelines;
|
||||
|
|
@ -83,202 +46,6 @@ TwodeePass::~TwodeePass() = default;
|
|||
static constexpr const uint32_t kVboInitSize = 32768;
|
||||
static constexpr const uint32_t kIboInitSize = 4096;
|
||||
|
||||
static Rect trimmed_patch_dim(const patch_t* patch);
|
||||
|
||||
static void create_atlas(Rhi& rhi, TwodeePassData& pass_data)
|
||||
{
|
||||
Atlas new_atlas;
|
||||
new_atlas.tex = rhi.create_texture({
|
||||
TextureFormat::kLuminanceAlpha,
|
||||
2048,
|
||||
2048,
|
||||
TextureWrapMode::kClamp,
|
||||
TextureWrapMode::kClamp
|
||||
});
|
||||
new_atlas.tex_width = 2048;
|
||||
new_atlas.tex_height = 2048;
|
||||
new_atlas.rp_ctx = std::make_unique<stbrp_context>();
|
||||
new_atlas.rp_nodes = std::make_unique<stbrp_node[]>(4096);
|
||||
for (size_t i = 0; i < 4096; i++)
|
||||
{
|
||||
new_atlas.rp_nodes[i] = {};
|
||||
}
|
||||
stbrp_init_target(new_atlas.rp_ctx.get(), 2048, 2048, new_atlas.rp_nodes.get(), 4096);
|
||||
// it is CRITICALLY important that the atlas is MOVED, not COPIED, otherwise the node ptrs will be broken
|
||||
pass_data.patch_atlases.push_back(std::move(new_atlas));
|
||||
}
|
||||
|
||||
static void pack_patches(Rhi& rhi, TwodeePassData& pass_data, tcb::span<const patch_t*> patches)
|
||||
{
|
||||
// Prepare stbrp rects for patches to be loaded.
|
||||
std::vector<stbrp_rect> rects;
|
||||
for (size_t i = 0; i < patches.size(); i++)
|
||||
{
|
||||
const patch_t* patch = patches[i];
|
||||
Rect trimmed_rect = trimmed_patch_dim(patch);
|
||||
stbrp_rect rect {};
|
||||
rect.id = i;
|
||||
rect.w = trimmed_rect.w;
|
||||
rect.h = trimmed_rect.h;
|
||||
rects.push_back(std::move(rect));
|
||||
}
|
||||
|
||||
while (rects.size() > 0)
|
||||
{
|
||||
if (pass_data.patch_atlases.size() == 0)
|
||||
{
|
||||
create_atlas(rhi, pass_data);
|
||||
}
|
||||
|
||||
for (size_t atlas_index = 0; atlas_index < pass_data.patch_atlases.size(); atlas_index++)
|
||||
{
|
||||
auto& atlas = pass_data.patch_atlases[atlas_index];
|
||||
|
||||
stbrp_pack_rects(atlas.rp_ctx.get(), rects.data(), rects.size());
|
||||
for (auto itr = rects.begin(); itr != rects.end();)
|
||||
{
|
||||
auto& rect = *itr;
|
||||
if (rect.was_packed)
|
||||
{
|
||||
AtlasEntry entry;
|
||||
const patch_t* patch = patches[rect.id];
|
||||
// TODO prevent unnecessary recalculation of trim?
|
||||
Rect trimmed_rect = trimmed_patch_dim(patch);
|
||||
entry.x = static_cast<uint32_t>(rect.x);
|
||||
entry.y = static_cast<uint32_t>(rect.y);
|
||||
entry.w = static_cast<uint32_t>(rect.w);
|
||||
entry.h = static_cast<uint32_t>(rect.h);
|
||||
entry.trim_x = static_cast<uint32_t>(trimmed_rect.x);
|
||||
entry.trim_y = static_cast<uint32_t>(trimmed_rect.y);
|
||||
entry.orig_w = static_cast<uint32_t>(patch->width);
|
||||
entry.orig_h = static_cast<uint32_t>(patch->height);
|
||||
atlas.entries.insert_or_assign(patch, std::move(entry));
|
||||
pass_data.patch_lookup.insert_or_assign(patch, atlas_index);
|
||||
pass_data.patches_to_upload.push_back(patch);
|
||||
rects.erase(itr);
|
||||
continue;
|
||||
}
|
||||
++itr;
|
||||
}
|
||||
|
||||
// If we still have rects to pack, and we're at the last atlas, create another atlas.
|
||||
// TODO This could end up in an infinite loop if the patches are bigger than an atlas. Such patches need to
|
||||
// be loaded as individual RHI textures instead.
|
||||
if (atlas_index == pass_data.patch_atlases.size() - 1 && rects.size() > 0)
|
||||
{
|
||||
create_atlas(rhi, pass_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Derive the subrect of the given patch with empty columns and rows excluded.
|
||||
static Rect trimmed_patch_dim(const patch_t* patch)
|
||||
{
|
||||
bool minx_found = false;
|
||||
int32_t minx = 0;
|
||||
int32_t maxx = 0;
|
||||
int32_t miny = patch->height;
|
||||
int32_t maxy = 0;
|
||||
for (int32_t x = 0; x < patch->width; x++)
|
||||
{
|
||||
const int32_t columnofs = patch->columnofs[x];
|
||||
const column_t* column = reinterpret_cast<const column_t*>(patch->columns + columnofs);
|
||||
|
||||
// If the first pole is empty (topdelta = 255), there are no pixels in this column
|
||||
if (!minx_found && column->topdelta == 0xFF)
|
||||
{
|
||||
// Thus, the minx is at least one higher than the current column.
|
||||
minx = x + 1;
|
||||
continue;
|
||||
}
|
||||
minx_found = true;
|
||||
|
||||
if (minx_found && column->topdelta != 0xFF)
|
||||
{
|
||||
maxx = x;
|
||||
}
|
||||
|
||||
miny = std::min(static_cast<int32_t>(column->topdelta), miny);
|
||||
|
||||
int32_t prevdelta = 0;
|
||||
int32_t topdelta = 0;
|
||||
while (column->topdelta != 0xFF)
|
||||
{
|
||||
topdelta = column->topdelta;
|
||||
|
||||
// Tall patches hack
|
||||
if (topdelta <= prevdelta)
|
||||
{
|
||||
topdelta += prevdelta;
|
||||
}
|
||||
prevdelta = topdelta;
|
||||
|
||||
maxy = std::max(topdelta + column->length, maxy);
|
||||
|
||||
column = reinterpret_cast<const column_t*>(reinterpret_cast<const uint8_t*>(column) + column->length + 4);
|
||||
}
|
||||
}
|
||||
|
||||
maxx += 1;
|
||||
maxx = std::max(minx, maxx);
|
||||
maxy = std::max(miny, maxy);
|
||||
|
||||
return {minx, miny, static_cast<uint32_t>(maxx - minx), static_cast<uint32_t>(maxy - miny)};
|
||||
}
|
||||
|
||||
static void convert_patch_to_trimmed_rg8_pixels(const patch_t* patch, std::vector<uint8_t>& out)
|
||||
{
|
||||
Rect trimmed_rect = trimmed_patch_dim(patch);
|
||||
if (trimmed_rect.w % 2 > 0)
|
||||
{
|
||||
// In order to force 4-byte row alignment, an extra column is added to the image data.
|
||||
// Look up GL_UNPACK_ALIGNMENT (which defaults to 4 bytes)
|
||||
trimmed_rect.w += 1;
|
||||
}
|
||||
out.clear();
|
||||
// 2 bytes per pixel; 1 for the color index, 1 for the alpha. (RG8)
|
||||
out.resize(trimmed_rect.w * trimmed_rect.h * 2, 0);
|
||||
for (int32_t x = 0; x < static_cast<int32_t>(trimmed_rect.w) && x < (patch->width - trimmed_rect.x); x++)
|
||||
{
|
||||
const int32_t columnofs = patch->columnofs[x + trimmed_rect.x];
|
||||
const column_t* column = reinterpret_cast<const column_t*>(patch->columns + columnofs);
|
||||
|
||||
int32_t prevdelta = 0;
|
||||
int32_t topdelta = 0;
|
||||
while (column->topdelta != 0xFF)
|
||||
{
|
||||
topdelta = column->topdelta;
|
||||
// prevdelta is used to implement tall patches hack
|
||||
if (topdelta <= prevdelta)
|
||||
{
|
||||
topdelta += prevdelta;
|
||||
}
|
||||
|
||||
prevdelta = topdelta;
|
||||
const uint8_t* source = reinterpret_cast<const uint8_t*>(column) + 3;
|
||||
int32_t count = column->length; // is this byte order safe...?
|
||||
|
||||
for (int32_t i = 0; i < count; i++)
|
||||
{
|
||||
int32_t output_y = topdelta + i - trimmed_rect.y;
|
||||
if (output_y < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (output_y >= static_cast<int32_t>(trimmed_rect.h))
|
||||
{
|
||||
break;
|
||||
}
|
||||
size_t pixel_index = (output_y * trimmed_rect.w + x) * 2;
|
||||
out[pixel_index + 0] = source[i]; // index in luminance/red channel
|
||||
out[pixel_index + 1] = 0xFF; // alpha/green value of 1
|
||||
}
|
||||
column = reinterpret_cast<const column_t*>(reinterpret_cast<const uint8_t*>(column) + column->length + 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static TwodeePipelineKey pipeline_key_for_cmd(const Draw2dCmd& cmd)
|
||||
{
|
||||
return {hwr2::get_blend_mode(cmd), hwr2::is_draw_lines(cmd)};
|
||||
|
|
@ -358,24 +125,26 @@ static PipelineDesc make_pipeline_desc(TwodeePipelineKey key)
|
|||
{0.f, 0.f, 0.f, 1.f}};
|
||||
}
|
||||
|
||||
static void rewrite_patch_quad_vertices(Draw2dList& list, const Draw2dPatchQuad& cmd, TwodeePassData* data)
|
||||
void TwodeePass::rewrite_patch_quad_vertices(Draw2dList& list, const Draw2dPatchQuad& cmd) const
|
||||
{
|
||||
// Patch quads are clipped according to the patch's atlas entry
|
||||
if (cmd.patch == nullptr)
|
||||
const patch_t* patch = cmd.patch;
|
||||
if (patch == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::size_t atlas_index = data->patch_lookup[cmd.patch];
|
||||
auto& atlas = data->patch_atlases[atlas_index];
|
||||
auto& entry = atlas.entries[cmd.patch];
|
||||
srb2::NotNull<const PatchAtlas*> atlas = patch_atlas_cache_->find_patch(patch);
|
||||
std::optional<PatchAtlas::Entry> entry_optional = atlas->find_patch(patch);
|
||||
SRB2_ASSERT(entry_optional.has_value());
|
||||
PatchAtlas::Entry entry = *entry_optional;
|
||||
|
||||
// Rewrite the vertex data completely.
|
||||
// The UVs of the trimmed patch in atlas UV space.
|
||||
const float atlas_umin = static_cast<float>(entry.x) / atlas.tex_width;
|
||||
const float atlas_umax = static_cast<float>(entry.x + entry.w) / atlas.tex_width;
|
||||
const float atlas_vmin = static_cast<float>(entry.y) / atlas.tex_height;
|
||||
const float atlas_vmax = static_cast<float>(entry.y + entry.h) / atlas.tex_height;
|
||||
const float atlas_umin = static_cast<float>(entry.x) / atlas->texture_size();
|
||||
const float atlas_umax = static_cast<float>(entry.x + entry.w) / atlas->texture_size();
|
||||
const float atlas_vmin = static_cast<float>(entry.y) / atlas->texture_size();
|
||||
const float atlas_vmax = static_cast<float>(entry.y + entry.h) / atlas->texture_size();
|
||||
|
||||
// The UVs of the trimmed patch in untrimmed UV space.
|
||||
// The command's UVs are in untrimmed UV space.
|
||||
|
|
@ -542,27 +311,6 @@ void TwodeePass::prepass(Rhi& rhi)
|
|||
);
|
||||
}
|
||||
|
||||
// Check for patches that are being freed after this frame. Those patches must be present in the atlases for this
|
||||
// frame, but all atlases need to be cleared and rebuilt on next call to prepass.
|
||||
// This is based on the assumption that patches are very rarely freed during runtime; occasionally repacking the
|
||||
// atlases to free up space from patches that will never be referenced again is acceptable.
|
||||
if (rebuild_atlases_)
|
||||
{
|
||||
for (auto& atlas : data_->patch_atlases)
|
||||
{
|
||||
rhi.destroy_texture(atlas.tex);
|
||||
}
|
||||
data_->patch_atlases.clear();
|
||||
data_->patch_lookup.clear();
|
||||
rebuild_atlases_ = false;
|
||||
}
|
||||
|
||||
if (data_->patch_atlases.size() > 2)
|
||||
{
|
||||
// Rebuild the atlases next frame because we have too many patches in the atlas cache.
|
||||
rebuild_atlases_ = true;
|
||||
}
|
||||
|
||||
// Stage 1 - command list patch detection
|
||||
std::unordered_set<const patch_t*> found_patches;
|
||||
std::unordered_set<const uint8_t*> found_colormaps;
|
||||
|
|
@ -587,19 +335,11 @@ void TwodeePass::prepass(Rhi& rhi)
|
|||
}
|
||||
}
|
||||
|
||||
std::unordered_set<const patch_t*> patch_cache_hits;
|
||||
std::unordered_set<const patch_t*> patch_cache_misses;
|
||||
for (auto patch : found_patches)
|
||||
{
|
||||
if (data_->patch_lookup.find(patch) != data_->patch_lookup.end())
|
||||
{
|
||||
patch_cache_hits.insert(patch);
|
||||
}
|
||||
else
|
||||
{
|
||||
patch_cache_misses.insert(patch);
|
||||
}
|
||||
patch_atlas_cache_->queue_patch(patch);
|
||||
}
|
||||
patch_atlas_cache_->pack(rhi);
|
||||
|
||||
for (auto colormap : found_colormaps)
|
||||
{
|
||||
|
|
@ -612,11 +352,6 @@ void TwodeePass::prepass(Rhi& rhi)
|
|||
data_->colormaps_to_upload.push_back(colormap);
|
||||
}
|
||||
|
||||
// Stage 2 - pack rects into atlases
|
||||
std::vector<const patch_t*> patches_to_pack(patch_cache_misses.begin(), patch_cache_misses.end());
|
||||
pack_patches(rhi, *data_, patches_to_pack);
|
||||
// We now know what patches need to be uploaded.
|
||||
|
||||
size_t list_index = 0;
|
||||
for (auto& list : *ctx_)
|
||||
{
|
||||
|
|
@ -695,7 +430,6 @@ void TwodeePass::prepass(Rhi& rhi)
|
|||
// We need to split the merged commands based on the kind of texture
|
||||
// Patches are converted to atlas texture indexes, which we've just packed the patch rects for
|
||||
// Flats are uploaded as individual textures.
|
||||
// TODO actually implement flat drawing
|
||||
auto tex_visitor = srb2::Overload {
|
||||
[&](const Draw2dPatchQuad& cmd)
|
||||
{
|
||||
|
|
@ -705,8 +439,8 @@ void TwodeePass::prepass(Rhi& rhi)
|
|||
}
|
||||
else
|
||||
{
|
||||
size_t atlas_index = data_->patch_lookup[cmd.patch];
|
||||
typeof(merged_cmd.texture) atlas_index_texture = atlas_index;
|
||||
srb2::NotNull<const PatchAtlas*> atlas = patch_atlas_cache_->find_patch(cmd.patch);
|
||||
typeof(merged_cmd.texture) atlas_index_texture = atlas->texture();
|
||||
new_cmd_needed = new_cmd_needed || (merged_cmd.texture != atlas_index_texture);
|
||||
}
|
||||
|
||||
|
|
@ -739,7 +473,8 @@ void TwodeePass::prepass(Rhi& rhi)
|
|||
{
|
||||
if (cmd.patch != nullptr)
|
||||
{
|
||||
the_new_one.texture = data_->patch_lookup[cmd.patch];
|
||||
srb2::NotNull<const PatchAtlas*> atlas = patch_atlas_cache_->find_patch(cmd.patch);
|
||||
the_new_one.texture = atlas->texture();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -776,7 +511,7 @@ void TwodeePass::prepass(Rhi& rhi)
|
|||
// Perform coordinate transformations
|
||||
{
|
||||
auto vtx_transform_visitor = srb2::Overload {
|
||||
[&](const Draw2dPatchQuad& cmd) { rewrite_patch_quad_vertices(list, cmd, data_.get()); },
|
||||
[&](const Draw2dPatchQuad& cmd) { rewrite_patch_quad_vertices(list, cmd); },
|
||||
[&](const Draw2dVertices& cmd) {}};
|
||||
std::visit(vtx_transform_visitor, cmd);
|
||||
}
|
||||
|
|
@ -828,25 +563,6 @@ void TwodeePass::transfer(Rhi& rhi, Handle<TransferContext> ctx)
|
|||
}
|
||||
data_->colormaps_to_upload.clear();
|
||||
|
||||
// Convert patches to RG8 textures and upload to atlas pages
|
||||
std::vector<uint8_t> patch_data;
|
||||
for (const patch_t* patch_to_upload : data_->patches_to_upload)
|
||||
{
|
||||
Atlas& atlas = data_->patch_atlases[data_->patch_lookup[patch_to_upload]];
|
||||
AtlasEntry& entry = atlas.entries[patch_to_upload];
|
||||
|
||||
convert_patch_to_trimmed_rg8_pixels(patch_to_upload, patch_data);
|
||||
|
||||
rhi.update_texture(
|
||||
ctx,
|
||||
atlas.tex,
|
||||
{static_cast<int32_t>(entry.x), static_cast<int32_t>(entry.y), entry.w, entry.h},
|
||||
PixelFormat::kRG8,
|
||||
tcb::as_bytes(tcb::span(patch_data))
|
||||
);
|
||||
}
|
||||
data_->patches_to_upload.clear();
|
||||
|
||||
Handle<Texture> palette_tex = palette_manager_->palette();
|
||||
|
||||
// Update the buffers for each list
|
||||
|
|
@ -867,10 +583,9 @@ void TwodeePass::transfer(Rhi& rhi, Handle<TransferContext> ctx)
|
|||
{
|
||||
TextureBinding tx[3];
|
||||
auto tex_visitor = srb2::Overload {
|
||||
[&](size_t atlas_index)
|
||||
[&](Handle<Texture> texture)
|
||||
{
|
||||
Atlas& atlas = data_->patch_atlases[atlas_index];
|
||||
tx[0] = {SamplerName::kSampler0, atlas.tex};
|
||||
tx[0] = {SamplerName::kSampler0, texture};
|
||||
tx[1] = {SamplerName::kSampler1, palette_tex};
|
||||
},
|
||||
[&](const MergedTwodeeCommandFlatTexture& tex)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
#include <vector>
|
||||
|
||||
#include "../cxxutil.hpp"
|
||||
#include "patch_atlas.hpp"
|
||||
#include "pass.hpp"
|
||||
#include "pass_resource_managers.hpp"
|
||||
#include "twodee.hpp"
|
||||
|
|
@ -52,7 +53,7 @@ struct MergedTwodeeCommand
|
|||
{
|
||||
TwodeePipelineKey pipeline_key = {};
|
||||
rhi::Handle<rhi::BindingSet> binding_set = {};
|
||||
std::optional<std::variant<size_t, MergedTwodeeCommandFlatTexture>> texture;
|
||||
std::optional<std::variant<rhi::Handle<rhi::Texture>, MergedTwodeeCommandFlatTexture>> texture;
|
||||
const uint8_t* colormap;
|
||||
uint32_t index_offset = 0;
|
||||
uint32_t elements = 0;
|
||||
|
|
@ -78,17 +79,19 @@ struct TwodeePass final : public Pass
|
|||
std::shared_ptr<TwodeePassData> data_;
|
||||
std::shared_ptr<MainPaletteManager> palette_manager_;
|
||||
std::shared_ptr<FlatTextureManager> flat_manager_;
|
||||
std::shared_ptr<PatchAtlasCache> patch_atlas_cache_;
|
||||
rhi::Handle<rhi::UniformSet> us_1;
|
||||
rhi::Handle<rhi::UniformSet> us_2;
|
||||
std::vector<MergedTwodeeCommandList> cmd_lists_;
|
||||
std::vector<std::tuple<rhi::Handle<rhi::Buffer>, std::size_t>> vbos_;
|
||||
std::vector<std::tuple<rhi::Handle<rhi::Buffer>, std::size_t>> ibos_;
|
||||
bool rebuild_atlases_ = false;
|
||||
rhi::Handle<rhi::RenderPass> render_pass_;
|
||||
rhi::Handle<rhi::Texture> output_;
|
||||
uint32_t output_width_ = 0;
|
||||
uint32_t output_height_ = 0;
|
||||
|
||||
void rewrite_patch_quad_vertices(Draw2dList& list, const Draw2dPatchQuad& cmd) const;
|
||||
|
||||
TwodeePass();
|
||||
virtual ~TwodeePass();
|
||||
|
||||
|
|
|
|||
385
src/hwr2/patch_atlas.cpp
Normal file
385
src/hwr2/patch_atlas.cpp
Normal file
|
|
@ -0,0 +1,385 @@
|
|||
// SONIC ROBO BLAST 2
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright (C) 2023 by Ronald "Eidolon" Kinard
|
||||
//
|
||||
// This program is free software distributed under the
|
||||
// terms of the GNU General Public License, version 2.
|
||||
// See the 'LICENSE' file for more details.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "patch_atlas.hpp"
|
||||
|
||||
#include <stb_rect_pack.h>
|
||||
|
||||
#include "../r_patch.h"
|
||||
|
||||
using namespace srb2;
|
||||
using namespace srb2::hwr2;
|
||||
using namespace srb2::rhi;
|
||||
|
||||
rhi::Rect srb2::hwr2::trimmed_patch_dimensions(const patch_t* patch)
|
||||
{
|
||||
bool minx_found = false;
|
||||
int32_t minx = 0;
|
||||
int32_t maxx = 0;
|
||||
int32_t miny = patch->height;
|
||||
int32_t maxy = 0;
|
||||
for (int32_t x = 0; x < patch->width; x++)
|
||||
{
|
||||
const int32_t columnofs = patch->columnofs[x];
|
||||
const column_t* column = reinterpret_cast<const column_t*>(patch->columns + columnofs);
|
||||
|
||||
// If the first pole is empty (topdelta = 255), there are no pixels in this column
|
||||
if (!minx_found && column->topdelta == 0xFF)
|
||||
{
|
||||
// Thus, the minx is at least one higher than the current column.
|
||||
minx = x + 1;
|
||||
continue;
|
||||
}
|
||||
minx_found = true;
|
||||
|
||||
if (minx_found && column->topdelta != 0xFF)
|
||||
{
|
||||
maxx = x;
|
||||
}
|
||||
|
||||
miny = std::min(static_cast<int32_t>(column->topdelta), miny);
|
||||
|
||||
int32_t prevdelta = 0;
|
||||
int32_t topdelta = 0;
|
||||
while (column->topdelta != 0xFF)
|
||||
{
|
||||
topdelta = column->topdelta;
|
||||
|
||||
// Tall patches hack
|
||||
if (topdelta <= prevdelta)
|
||||
{
|
||||
topdelta += prevdelta;
|
||||
}
|
||||
prevdelta = topdelta;
|
||||
|
||||
maxy = std::max(topdelta + column->length, maxy);
|
||||
|
||||
column = reinterpret_cast<const column_t*>(reinterpret_cast<const uint8_t*>(column) + column->length + 4);
|
||||
}
|
||||
}
|
||||
|
||||
maxx += 1;
|
||||
maxx = std::max(minx, maxx);
|
||||
maxy = std::max(miny, maxy);
|
||||
|
||||
return {minx, miny, static_cast<uint32_t>(maxx - minx), static_cast<uint32_t>(maxy - miny)};
|
||||
}
|
||||
|
||||
void srb2::hwr2::convert_patch_to_trimmed_rg8_pixels(const patch_t* patch, std::vector<uint8_t>& out)
|
||||
{
|
||||
Rect trimmed_rect = srb2::hwr2::trimmed_patch_dimensions(patch);
|
||||
if (trimmed_rect.w % 2 > 0)
|
||||
{
|
||||
// In order to force 4-byte row alignment, an extra column is added to the image data.
|
||||
// Look up GL_UNPACK_ALIGNMENT (which defaults to 4 bytes)
|
||||
trimmed_rect.w += 1;
|
||||
}
|
||||
out.clear();
|
||||
// 2 bytes per pixel; 1 for the color index, 1 for the alpha. (RG8)
|
||||
out.resize(trimmed_rect.w * trimmed_rect.h * 2, 0);
|
||||
for (int32_t x = 0; x < static_cast<int32_t>(trimmed_rect.w) && x < (patch->width - trimmed_rect.x); x++)
|
||||
{
|
||||
const int32_t columnofs = patch->columnofs[x + trimmed_rect.x];
|
||||
const column_t* column = reinterpret_cast<const column_t*>(patch->columns + columnofs);
|
||||
|
||||
int32_t prevdelta = 0;
|
||||
int32_t topdelta = 0;
|
||||
while (column->topdelta != 0xFF)
|
||||
{
|
||||
topdelta = column->topdelta;
|
||||
// prevdelta is used to implement tall patches hack
|
||||
if (topdelta <= prevdelta)
|
||||
{
|
||||
topdelta += prevdelta;
|
||||
}
|
||||
|
||||
prevdelta = topdelta;
|
||||
const uint8_t* source = reinterpret_cast<const uint8_t*>(column) + 3;
|
||||
int32_t count = column->length; // is this byte order safe...?
|
||||
|
||||
for (int32_t i = 0; i < count; i++)
|
||||
{
|
||||
int32_t output_y = topdelta + i - trimmed_rect.y;
|
||||
if (output_y < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (output_y >= static_cast<int32_t>(trimmed_rect.h))
|
||||
{
|
||||
break;
|
||||
}
|
||||
size_t pixel_index = (output_y * trimmed_rect.w + x) * 2;
|
||||
out[pixel_index + 0] = source[i]; // index in luminance/red channel
|
||||
out[pixel_index + 1] = 0xFF; // alpha/green value of 1
|
||||
}
|
||||
column = reinterpret_cast<const column_t*>(reinterpret_cast<const uint8_t*>(column) + column->length + 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PatchAtlas::PatchAtlas(Handle<Texture> texture, uint32_t size) : tex_(texture), size_(size)
|
||||
{
|
||||
rp_ctx = std::make_unique<stbrp_context>();
|
||||
rp_nodes = std::make_unique<stbrp_node[]>(size * 2);
|
||||
const size_t double_size = size * 2;
|
||||
for (size_t i = 0; i < double_size; i++)
|
||||
{
|
||||
rp_nodes[i] = {};
|
||||
}
|
||||
stbrp_init_target(rp_ctx.get(), size, size, rp_nodes.get(), double_size);
|
||||
}
|
||||
|
||||
PatchAtlas::PatchAtlas(PatchAtlas&&) = default;
|
||||
PatchAtlas& PatchAtlas::operator=(PatchAtlas&&) = default;
|
||||
|
||||
void PatchAtlas::pack_rects(tcb::span<stbrp_rect> rects)
|
||||
{
|
||||
stbrp_pack_rects(rp_ctx.get(), rects.data(), rects.size());
|
||||
}
|
||||
|
||||
std::optional<PatchAtlas::Entry> PatchAtlas::find_patch(srb2::NotNull<const patch_t*> patch) const
|
||||
{
|
||||
auto itr = entries_.find(patch);
|
||||
if (itr == entries_.end())
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return itr->second;
|
||||
}
|
||||
|
||||
PatchAtlasCache::PatchAtlasCache(uint32_t tex_size, size_t max_textures)
|
||||
: tex_size_(tex_size)
|
||||
, max_textures_(max_textures)
|
||||
{
|
||||
}
|
||||
|
||||
PatchAtlasCache::PatchAtlasCache(PatchAtlasCache&&) = default;
|
||||
PatchAtlasCache& PatchAtlasCache::operator=(PatchAtlasCache&&) = default;
|
||||
PatchAtlasCache::~PatchAtlasCache() = default;
|
||||
|
||||
bool PatchAtlasCache::need_to_reset() const
|
||||
{
|
||||
if (atlases_.size() > max_textures_)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (Patch_WasFreedThisFrame())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PatchAtlasCache::reset(Rhi& rhi)
|
||||
{
|
||||
for (auto& atlas : atlases_)
|
||||
{
|
||||
rhi.destroy_texture(atlas.texture());
|
||||
}
|
||||
|
||||
atlases_.clear();
|
||||
patch_lookup_.clear();
|
||||
}
|
||||
|
||||
bool PatchAtlasCache::ready_for_lookup() const
|
||||
{
|
||||
if (!patches_to_pack_.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static PatchAtlas create_atlas(Rhi& rhi, uint32_t size)
|
||||
{
|
||||
Handle<Texture> texture = rhi.create_texture(
|
||||
{
|
||||
TextureFormat::kLuminanceAlpha,
|
||||
size,
|
||||
size,
|
||||
TextureWrapMode::kClamp,
|
||||
TextureWrapMode::kClamp
|
||||
}
|
||||
);
|
||||
|
||||
PatchAtlas new_atlas(texture, size);
|
||||
|
||||
return new_atlas;
|
||||
}
|
||||
|
||||
void PatchAtlasCache::pack(Rhi& rhi)
|
||||
{
|
||||
// Prepare stbrp rects for patches to be loaded.
|
||||
std::vector<stbrp_rect> rects;
|
||||
|
||||
std::vector<const patch_t*> large_patches;
|
||||
|
||||
std::vector<const patch_t*> patches;
|
||||
for (auto patch : patches_to_pack_)
|
||||
{
|
||||
patches.push_back(patch);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < patches.size(); i++)
|
||||
{
|
||||
const patch_t* patch = patches[i];
|
||||
Rect trimmed_rect = trimmed_patch_dimensions(patch);
|
||||
|
||||
if (rect_is_large(trimmed_rect.w, trimmed_rect.h))
|
||||
{
|
||||
large_patches.push_back(patch);
|
||||
continue;
|
||||
}
|
||||
|
||||
stbrp_rect rect {};
|
||||
|
||||
rect.id = i;
|
||||
rect.w = trimmed_rect.w;
|
||||
rect.h = trimmed_rect.h;
|
||||
rects.push_back(std::move(rect));
|
||||
}
|
||||
|
||||
while (rects.size() > 0)
|
||||
{
|
||||
if (atlases_.size() == 0)
|
||||
{
|
||||
atlases_.push_back(create_atlas(rhi, tex_size_));
|
||||
}
|
||||
|
||||
for (size_t atlas_index = 0; atlas_index < atlases_.size(); atlas_index++)
|
||||
{
|
||||
auto& atlas = atlases_[atlas_index];
|
||||
atlas.pack_rects(rects);
|
||||
for (auto itr = rects.begin(); itr != rects.end();)
|
||||
{
|
||||
auto& rect = *itr;
|
||||
if (rect.was_packed)
|
||||
{
|
||||
PatchAtlas::Entry entry;
|
||||
const patch_t* patch = patches[rect.id];
|
||||
Rect trimmed_rect = trimmed_patch_dimensions(patch);
|
||||
entry.x = static_cast<uint32_t>(rect.x);
|
||||
entry.y = static_cast<uint32_t>(rect.y);
|
||||
entry.w = static_cast<uint32_t>(rect.w);
|
||||
entry.h = static_cast<uint32_t>(rect.h);
|
||||
entry.trim_x = static_cast<uint32_t>(trimmed_rect.x);
|
||||
entry.trim_y = static_cast<uint32_t>(trimmed_rect.y);
|
||||
entry.orig_w = static_cast<uint32_t>(patch->width);
|
||||
entry.orig_h = static_cast<uint32_t>(patch->height);
|
||||
atlas.entries_.insert_or_assign(patch, std::move(entry));
|
||||
patch_lookup_.insert_or_assign(patch, atlas_index);
|
||||
patches_to_upload_.insert(patch);
|
||||
rects.erase(itr);
|
||||
continue;
|
||||
}
|
||||
++itr;
|
||||
}
|
||||
|
||||
// If we still have rects to pack, and we're at the last atlas, create another atlas.
|
||||
if (atlas_index == atlases_.size() - 1 && rects.size() > 0)
|
||||
{
|
||||
atlases_.push_back(create_atlas(rhi, tex_size_));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Create large patch "atlases"
|
||||
|
||||
patches_to_pack_.clear();
|
||||
}
|
||||
|
||||
PatchAtlas* PatchAtlasCache::find_patch(srb2::NotNull<const patch_t*> patch)
|
||||
{
|
||||
SRB2_ASSERT(ready_for_lookup());
|
||||
|
||||
auto itr = patch_lookup_.find(patch);
|
||||
if (itr == patch_lookup_.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t atlas_index = itr->second;
|
||||
|
||||
SRB2_ASSERT(atlas_index < atlases_.size());
|
||||
|
||||
return &atlases_[atlas_index];
|
||||
}
|
||||
|
||||
const PatchAtlas* PatchAtlasCache::find_patch(srb2::NotNull<const patch_t*> patch) const
|
||||
{
|
||||
SRB2_ASSERT(ready_for_lookup());
|
||||
|
||||
auto itr = patch_lookup_.find(patch);
|
||||
if (itr == patch_lookup_.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t atlas_index = itr->second;
|
||||
|
||||
SRB2_ASSERT(atlas_index < atlases_.size());
|
||||
|
||||
return &atlases_[atlas_index];
|
||||
}
|
||||
|
||||
void PatchAtlasCache::queue_patch(srb2::NotNull<const patch_t*> patch)
|
||||
{
|
||||
if (patch_lookup_.find(patch) != patch_lookup_.end())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
patches_to_pack_.insert(patch);
|
||||
}
|
||||
|
||||
void PatchAtlasCache::prepass(Rhi& rhi)
|
||||
{
|
||||
if (need_to_reset())
|
||||
{
|
||||
reset(rhi);
|
||||
}
|
||||
}
|
||||
|
||||
void PatchAtlasCache::transfer(Rhi& rhi, Handle<TransferContext> ctx)
|
||||
{
|
||||
SRB2_ASSERT(ready_for_lookup());
|
||||
|
||||
// Upload atlased patches
|
||||
std::vector<uint8_t> patch_data;
|
||||
for (const patch_t* patch_to_upload : patches_to_upload_)
|
||||
{
|
||||
srb2::NotNull<PatchAtlas*> atlas = find_patch(patch_to_upload);
|
||||
|
||||
std::optional<PatchAtlas::Entry> entry = atlas->find_patch(patch_to_upload);
|
||||
SRB2_ASSERT(entry.has_value());
|
||||
|
||||
convert_patch_to_trimmed_rg8_pixels(patch_to_upload, patch_data);
|
||||
|
||||
rhi.update_texture(
|
||||
ctx,
|
||||
atlas->tex_,
|
||||
{static_cast<int32_t>(entry->x), static_cast<int32_t>(entry->y), entry->w, entry->h},
|
||||
PixelFormat::kRG8,
|
||||
tcb::as_bytes(tcb::span(patch_data))
|
||||
);
|
||||
|
||||
patch_data.clear();
|
||||
}
|
||||
patches_to_upload_.clear();
|
||||
}
|
||||
|
||||
void PatchAtlasCache::graphics(Rhi& rhi, Handle<GraphicsContext> ctx)
|
||||
{
|
||||
}
|
||||
|
||||
void PatchAtlasCache::postpass(Rhi& rhi)
|
||||
{
|
||||
}
|
||||
145
src/hwr2/patch_atlas.hpp
Normal file
145
src/hwr2/patch_atlas.hpp
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
// SONIC ROBO BLAST 2
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright (C) 2023 by Ronald "Eidolon" Kinard
|
||||
//
|
||||
// This program is free software distributed under the
|
||||
// terms of the GNU General Public License, version 2.
|
||||
// See the 'LICENSE' file for more details.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef __SRB2_HWR2_PATCH_ATLAS_HPP__
|
||||
#define __SRB2_HWR2_PATCH_ATLAS_HPP__
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include <tcb/span.hpp>
|
||||
|
||||
#include "pass.hpp"
|
||||
#include "../r_defs.h"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
// Forward declare the stb_rect_pack types since they are only pointed to
|
||||
|
||||
struct stbrp_context;
|
||||
struct stbrp_node;
|
||||
struct stbrp_rect;
|
||||
};
|
||||
|
||||
namespace srb2::hwr2
|
||||
{
|
||||
|
||||
class PatchAtlas
|
||||
{
|
||||
public:
|
||||
struct Entry
|
||||
{
|
||||
uint32_t x;
|
||||
uint32_t y;
|
||||
uint32_t w;
|
||||
uint32_t h;
|
||||
|
||||
uint32_t trim_x;
|
||||
uint32_t trim_y;
|
||||
uint32_t orig_w;
|
||||
uint32_t orig_h;
|
||||
};
|
||||
|
||||
private:
|
||||
rhi::Handle<rhi::Texture> tex_;
|
||||
uint32_t size_;
|
||||
|
||||
std::unordered_map<const patch_t*, Entry> entries_;
|
||||
|
||||
std::unique_ptr<stbrp_context> rp_ctx {nullptr};
|
||||
std::unique_ptr<stbrp_node[]> rp_nodes {nullptr};
|
||||
|
||||
friend class PatchAtlasCache;
|
||||
|
||||
public:
|
||||
PatchAtlas(rhi::Handle<rhi::Texture> tex, uint32_t size);
|
||||
PatchAtlas(const PatchAtlas&) = delete;
|
||||
PatchAtlas& operator=(const PatchAtlas&) = delete;
|
||||
PatchAtlas(PatchAtlas&&);
|
||||
PatchAtlas& operator=(PatchAtlas&&);
|
||||
|
||||
/// @brief Get the Luminance-Alpha RHI texture handle for this atlas texture
|
||||
rhi::Handle<rhi::Texture> texture() const noexcept { return tex_; }
|
||||
|
||||
uint32_t texture_size() const noexcept { return size_; }
|
||||
|
||||
std::optional<Entry> find_patch(srb2::NotNull<const patch_t*> patch) const;
|
||||
|
||||
void pack_rects(tcb::span<stbrp_rect> rects);
|
||||
};
|
||||
|
||||
/// @brief A resource-managing pass which creates and manages a set of Atlas Textures with
|
||||
/// optimally packed Patches, allowing drawing passes to reuse the same texture binds for
|
||||
/// drawing things like sprites and 2D elements.
|
||||
class PatchAtlasCache : public Pass
|
||||
{
|
||||
std::vector<PatchAtlas> atlases_;
|
||||
std::unordered_map<const patch_t*, size_t> patch_lookup_;
|
||||
|
||||
std::unordered_set<const patch_t*> patches_to_pack_;
|
||||
std::unordered_set<const patch_t*> patches_to_upload_;
|
||||
|
||||
uint32_t tex_size_ = 2048;
|
||||
size_t max_textures_ = 2;
|
||||
|
||||
bool need_to_reset() const;
|
||||
|
||||
/// @brief Clear the atlases and reset for lookup.
|
||||
void reset(rhi::Rhi& rhi);
|
||||
bool ready_for_lookup() const;
|
||||
|
||||
/// @brief Decide if a rect's dimensions are Large, that is, the rect should not be packed and instead its patch
|
||||
/// should be uploaded in isolation.
|
||||
bool rect_is_large(uint32_t w, uint32_t h) const noexcept { return false; }
|
||||
|
||||
public:
|
||||
PatchAtlasCache(uint32_t tex_size, size_t max_textures);
|
||||
|
||||
PatchAtlasCache(const PatchAtlasCache&) = delete;
|
||||
PatchAtlasCache(PatchAtlasCache&&);
|
||||
PatchAtlasCache& operator=(const PatchAtlasCache&) = delete;
|
||||
PatchAtlasCache& operator=(PatchAtlasCache&&);
|
||||
virtual ~PatchAtlasCache();
|
||||
|
||||
/// @brief Queue a patch to be packed. All patches will be packed after the prepass phase,
|
||||
/// or the owner can explicitly request a pack.
|
||||
void queue_patch(srb2::NotNull<const patch_t*> patch);
|
||||
|
||||
/// @brief Pack queued patches, allowing them to be looked up with find_patch.
|
||||
void pack(rhi::Rhi& rhi);
|
||||
|
||||
/// @brief Find the atlas a patch belongs to, or nullopt if it is not cached.
|
||||
/// This may not be called if there are still patches that need to be packed.
|
||||
/// The return value of this function may change between invocations of prepass for any given input.
|
||||
const PatchAtlas* find_patch(srb2::NotNull<const patch_t*> patch) const;
|
||||
PatchAtlas* find_patch(srb2::NotNull<const patch_t*> patch);
|
||||
|
||||
virtual void prepass(rhi::Rhi& rhi) override;
|
||||
virtual void transfer(rhi::Rhi& rhi, rhi::Handle<rhi::TransferContext> ctx) override;
|
||||
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
|
||||
virtual void postpass(rhi::Rhi& rhi) override;
|
||||
};
|
||||
|
||||
/// @brief Calculate the subregion of the patch which excludes empty space on the borders.
|
||||
rhi::Rect trimmed_patch_dimensions(const patch_t* patch);
|
||||
|
||||
/// @brief Convert a patch to RG8 pixel data. If the patch's trimmed width is not a multiple of 2,
|
||||
/// an additional blank column will be emitted to the output; this pixel data is ignored by RHI
|
||||
/// during upload, but required for the RHI device's Unpack Alignment of 4 bytes.
|
||||
/// @param patch the patch to convert
|
||||
/// @param out the output vector, cleared before writing.
|
||||
void convert_patch_to_trimmed_rg8_pixels(const patch_t* patch, std::vector<uint8_t>& out);
|
||||
|
||||
} // namespace srb2::hwr2
|
||||
|
||||
#endif // __SRB2_HWR2_PATCH_ATLAS_HPP__
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#include "cxxutil.hpp"
|
||||
#include "f_finale.h"
|
||||
#include "hwr2/patch_atlas.hpp"
|
||||
#include "hwr2/pass_blit_postimg_screens.hpp"
|
||||
#include "hwr2/pass_blit_rect.hpp"
|
||||
#include "hwr2/pass_imgui.hpp"
|
||||
|
|
@ -192,12 +193,14 @@ static InternalPassData build_pass_manager()
|
|||
auto palette_manager = std::make_shared<MainPaletteManager>();
|
||||
auto common_resources_manager = std::make_shared<CommonResourcesManager>();
|
||||
auto flat_texture_manager = std::make_shared<FlatTextureManager>();
|
||||
auto patch_atlas_cache = std::make_shared<PatchAtlasCache>(2048, 2);
|
||||
auto resource_manager = std::make_shared<PassManager>();
|
||||
|
||||
resource_manager->insert("framebuffer_manager", framebuffer_manager);
|
||||
resource_manager->insert("palette_manager", palette_manager);
|
||||
resource_manager->insert("common_resources_manager", common_resources_manager);
|
||||
resource_manager->insert("flat_texture_manager", flat_texture_manager);
|
||||
resource_manager->insert("patch_atlas_cache", patch_atlas_cache);
|
||||
|
||||
// Basic Rendering is responsible for drawing 3d, 2d, and postprocessing the image.
|
||||
// This is drawn to an alternating internal color buffer.
|
||||
|
|
@ -209,6 +212,7 @@ static InternalPassData build_pass_manager()
|
|||
auto blit_postimg_screens = std::make_shared<BlitPostimgScreens>(palette_manager);
|
||||
auto twodee = std::make_shared<TwodeePass>();
|
||||
twodee->flat_manager_ = flat_texture_manager;
|
||||
twodee->patch_atlas_cache_ = patch_atlas_cache;
|
||||
twodee->data_ = make_twodee_pass_data();
|
||||
twodee->ctx_ = &g_2d;
|
||||
auto pp_simple_blit_pass = std::make_shared<BlitRectPass>(false);
|
||||
|
|
@ -451,6 +455,7 @@ static InternalPassData build_pass_manager()
|
|||
void I_NewTwodeeFrame(void)
|
||||
{
|
||||
g_2d = Twodee();
|
||||
Patch_ResetFreedThisFrame();
|
||||
}
|
||||
|
||||
void I_NewImguiFrame(void)
|
||||
|
|
|
|||
120
src/info.c
120
src/info.c
|
|
@ -606,6 +606,10 @@ char sprnames[NUMSPRITES + 1][5] =
|
|||
"TWBS", // Tripwire Boost
|
||||
"TWBT", // Tripwire BLASTER
|
||||
"SMLD", // Smooth landing
|
||||
|
||||
"TIRG", // Tire grabbers
|
||||
"RSHT", // DEZ Ring Shooter
|
||||
|
||||
"DEZL", // DEZ Laser respawn
|
||||
|
||||
// Additional Kart Objects
|
||||
|
|
@ -4464,6 +4468,14 @@ state_t states[NUMSTATES] =
|
|||
|
||||
{SPR_SMLD, FF_FULLBRIGHT|FF_ADD|FF_ANIMATE, -1, {NULL}, 7, 2, S_NULL}, // S_SMOOTHLANDING
|
||||
|
||||
{SPR_TIRG, FF_ANIMATE, -1, {NULL}, 1, 1, S_NULL}, // S_TIREGRABBER
|
||||
{SPR_RSHT, FF_PAPERSPRITE|0, -1, {NULL}, 0, 0, S_NULL}, // S_RINGSHOOTER_SIDE
|
||||
{SPR_RSHT, FF_SEMIBRIGHT|FF_PAPERSPRITE|2, -1, {NULL}, 0, 0, S_NULL}, // S_RINGSHOOTER_NIPPLES
|
||||
{SPR_RSHT, FF_PAPERSPRITE|4, -1, {NULL}, 0, 0, S_NULL}, // S_RINGSHOOTER_SCREEN
|
||||
{SPR_RSHT, FF_FULLBRIGHT|FF_PAPERSPRITE|5, -1, {NULL}, 0, 0, S_NULL}, // S_RINGSHOOTER_NUMBERBACK
|
||||
{SPR_RSHT, FF_FULLBRIGHT|FF_PAPERSPRITE|9, -1, {NULL}, 0, 0, S_NULL}, // S_RINGSHOOTER_NUMBERFRONT
|
||||
{SPR_PLAY, FF_FULLBRIGHT|FF_PAPERSPRITE|SPR2_XTRA, -1, {A_RingShooterFace}, 0, 0, S_NULL}, // S_RINGSHOOTER_FACE
|
||||
|
||||
{SPR_DEZL, FF_FULLBRIGHT|FF_PAPERSPRITE, 8, {NULL}, 0, 0, S_NULL}, // S_DEZLASER
|
||||
{SPR_DEZL, FF_FULLBRIGHT|1, 2, {NULL}, 0, 0, S_DEZLASER_TRAIL2}, // S_DEZLASER_TRAIL1
|
||||
{SPR_DEZL, FF_FULLBRIGHT|2, 2, {NULL}, 0, 0, S_DEZLASER_TRAIL3}, // S_DEZLASER_TRAIL2
|
||||
|
|
@ -24623,6 +24635,114 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
|
|||
S_NULL // raisestate
|
||||
},
|
||||
|
||||
{ // MT_TIREGRABBER
|
||||
-1, // doomednum
|
||||
S_TIREGRABBER, // spawnstate
|
||||
1000, // spawnhealth
|
||||
S_NULL, // seestate
|
||||
sfx_None, // seesound
|
||||
0, // 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
|
||||
20*FRACUNIT, // radius
|
||||
36*FRACUNIT, // height
|
||||
0, // display offset
|
||||
0, // mass
|
||||
0, // damage
|
||||
sfx_None, // activesound
|
||||
MF_NOBLOCKMAP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY|MF_SCENERY|MF_DONTENCOREMAP, // flags
|
||||
S_NULL // raisestate
|
||||
},
|
||||
|
||||
{ // MT_RINGSHOOTER
|
||||
-1, // doomednum
|
||||
S_INVISIBLE, // spawnstate
|
||||
1000, // spawnhealth
|
||||
S_NULL, // seestate
|
||||
sfx_None, // seesound
|
||||
0, // reactiontime
|
||||
sfx_None, // attacksound
|
||||
S_NULL, // painstate
|
||||
0, // painchance
|
||||
sfx_s3ka7, // painsound
|
||||
S_NULL, // meleestate
|
||||
S_NULL, // missilestate
|
||||
S_NULL, // deathstate
|
||||
S_NULL, // xdeathstate
|
||||
sfx_s3kad, // deathsound
|
||||
0, // speed
|
||||
24*FRACUNIT, // radius
|
||||
8*FRACUNIT, // height
|
||||
0, // display offset
|
||||
0, // mass
|
||||
0, // damage
|
||||
sfx_None, // activesound
|
||||
MF_NOGRAVITY|MF_SCENERY|MF_DONTENCOREMAP, // flags
|
||||
S_NULL // raisestate
|
||||
},
|
||||
|
||||
{ // MT_RINGSHOOTER_PART
|
||||
-1, // doomednum
|
||||
S_RINGSHOOTER_SIDE, // spawnstate
|
||||
1000, // spawnhealth
|
||||
S_NULL, // seestate
|
||||
sfx_None, // seesound
|
||||
0, // 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
|
||||
6*FRACUNIT, // radius
|
||||
70*FRACUNIT, // height
|
||||
0, // display offset
|
||||
0, // mass
|
||||
0, // damage
|
||||
sfx_None, // activesound
|
||||
MF_NOBLOCKMAP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY|MF_SCENERY|MF_DONTENCOREMAP, // flags
|
||||
S_NULL // raisestate
|
||||
},
|
||||
|
||||
{ // MT_RINGSHOOTER_SCREEN
|
||||
-1, // doomednum
|
||||
S_RINGSHOOTER_SCREEN, // spawnstate
|
||||
1000, // spawnhealth
|
||||
S_NULL, // seestate
|
||||
sfx_None, // seesound
|
||||
0, // 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
|
||||
23*FRACUNIT, // radius
|
||||
39*FRACUNIT, // height
|
||||
0, // display offset
|
||||
0, // mass
|
||||
0, // damage
|
||||
sfx_None, // activesound
|
||||
MF_NOBLOCKMAP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY|MF_SCENERY|MF_DONTENCOREMAP, // flags
|
||||
S_NULL // raisestate
|
||||
},
|
||||
|
||||
{ // MT_DEZLASER
|
||||
-1, // doomednum
|
||||
S_DEZLASER, // spawnstate
|
||||
|
|
|
|||
20
src/info.h
20
src/info.h
|
|
@ -294,6 +294,7 @@ enum actionnum
|
|||
A_FLAMESHIELDPAPER,
|
||||
A_INVINCSPARKLEROTATE,
|
||||
A_SPAWNITEMDEBRISCLOUD,
|
||||
A_RINGSHOOTERFACE,
|
||||
NUMACTIONS
|
||||
};
|
||||
|
||||
|
|
@ -568,6 +569,7 @@ void A_MementosTPParticles();
|
|||
void A_FlameShieldPaper();
|
||||
void A_InvincSparkleRotate();
|
||||
void A_SpawnItemDebrisCloud();
|
||||
void A_RingShooterFace();
|
||||
|
||||
extern boolean actionsoverridden[NUMACTIONS];
|
||||
|
||||
|
|
@ -1157,6 +1159,10 @@ typedef enum sprite
|
|||
SPR_TWBS, // Tripwire Boost
|
||||
SPR_TWBT, // Tripwire BLASTER
|
||||
SPR_SMLD, // Smooth landing
|
||||
|
||||
SPR_TIRG, // Tire grabbers
|
||||
SPR_RSHT, // DEZ Ring Shooter
|
||||
|
||||
SPR_DEZL, // DEZ Laser respawn
|
||||
|
||||
// Additional Kart Objects
|
||||
|
|
@ -4906,6 +4912,15 @@ typedef enum state
|
|||
|
||||
S_SMOOTHLANDING,
|
||||
|
||||
// DEZ Ring Shooter
|
||||
S_TIREGRABBER,
|
||||
S_RINGSHOOTER_SIDE,
|
||||
S_RINGSHOOTER_NIPPLES,
|
||||
S_RINGSHOOTER_SCREEN,
|
||||
S_RINGSHOOTER_NUMBERBACK,
|
||||
S_RINGSHOOTER_NUMBERFRONT,
|
||||
S_RINGSHOOTER_FACE,
|
||||
|
||||
// DEZ Laser respawn
|
||||
S_DEZLASER,
|
||||
S_DEZLASER_TRAIL1,
|
||||
|
|
@ -6508,6 +6523,11 @@ typedef enum mobj_type
|
|||
|
||||
MT_SMOOTHLANDING,
|
||||
|
||||
MT_TIREGRABBER,
|
||||
MT_RINGSHOOTER,
|
||||
MT_RINGSHOOTER_PART,
|
||||
MT_RINGSHOOTER_SCREEN,
|
||||
|
||||
MT_DEZLASER,
|
||||
|
||||
MT_WAYPOINT,
|
||||
|
|
|
|||
|
|
@ -444,15 +444,22 @@ boolean K_LandMineCollide(mobj_t *t1, mobj_t *t2)
|
|||
else
|
||||
t2->z += t2->height;
|
||||
|
||||
P_SpawnMobj(t2->x/2 + t1->x/2, t2->y/2 + t1->y/2, t2->z/2 + t1->z/2, MT_ITEMCLASH);
|
||||
|
||||
S_StartSound(t2, t2->info->deathsound);
|
||||
P_KillMobj(t2, t1, t1, DMG_NORMAL);
|
||||
|
||||
P_SetObjectMomZ(t2, 24*FRACUNIT, false);
|
||||
P_InstaThrust(t2, bounceangle, 16*FRACUNIT);
|
||||
if (P_MobjWasRemoved(t2))
|
||||
{
|
||||
t2 = NULL; // handles the arguments to P_KillMobj
|
||||
}
|
||||
else
|
||||
{
|
||||
P_SetObjectMomZ(t2, 24*FRACUNIT, false);
|
||||
P_InstaThrust(t2, bounceangle, 16*FRACUNIT);
|
||||
|
||||
P_SpawnMobj(t2->x/2 + t1->x/2, t2->y/2 + t1->y/2, t2->z/2 + t1->z/2, MT_ITEMCLASH);
|
||||
|
||||
t1->reactiontime = t2->hitlag;
|
||||
t1->reactiontime = t2->hitlag;
|
||||
}
|
||||
P_KillMobj(t1, t2, t2, DMG_NORMAL);
|
||||
}
|
||||
else if (t2->type == MT_SSMINE_SHIELD || t2->type == MT_SSMINE || t2->type == MT_LANDMINE)
|
||||
|
|
@ -466,7 +473,15 @@ boolean K_LandMineCollide(mobj_t *t1, mobj_t *t2)
|
|||
// Shootable damage
|
||||
P_DamageMobj(t2, t1, t1->target, 1, DMG_NORMAL);
|
||||
|
||||
t1->reactiontime = t2->hitlag;
|
||||
if (P_MobjWasRemoved(t2))
|
||||
{
|
||||
t2 = NULL; // handles the arguments to P_KillMobj
|
||||
}
|
||||
else
|
||||
{
|
||||
t1->reactiontime = t2->hitlag;
|
||||
}
|
||||
|
||||
P_KillMobj(t1, t2, t2, DMG_NORMAL);
|
||||
}
|
||||
|
||||
|
|
@ -796,6 +811,10 @@ boolean K_KitchenSinkCollide(mobj_t *t1, mobj_t *t2)
|
|||
{
|
||||
// Shootable damage
|
||||
P_KillMobj(t2, t2, t1->target, DMG_NORMAL);
|
||||
if (P_MobjWasRemoved(t2))
|
||||
{
|
||||
t2 = NULL; // handles the arguments to P_KillMobj
|
||||
}
|
||||
// This item damage
|
||||
P_KillMobj(t1, t2, t2, DMG_NORMAL);
|
||||
}
|
||||
|
|
|
|||
53
src/k_kart.c
53
src/k_kart.c
|
|
@ -3407,7 +3407,9 @@ SINT8 K_GetForwardMove(player_t *player)
|
|||
{
|
||||
SINT8 forwardmove = player->cmd.forwardmove;
|
||||
|
||||
if ((player->pflags & PF_STASIS) || (player->carry == CR_SLIDING))
|
||||
if ((player->pflags & PF_STASIS)
|
||||
|| (player->carry == CR_SLIDING)
|
||||
|| Obj_PlayerRingShooterFreeze(player) == true)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -3418,7 +3420,9 @@ SINT8 K_GetForwardMove(player_t *player)
|
|||
return MAXPLMOVE;
|
||||
}
|
||||
|
||||
if (player->spinouttimer || K_PlayerEBrake(player))
|
||||
if (player->spinouttimer != 0
|
||||
|| K_PressingEBrake(player) == true
|
||||
|| K_PlayerEBrake(player) == true)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -6164,7 +6168,8 @@ void K_PopPlayerShield(player_t *player)
|
|||
return; // everything is handled by Obj_GardenTopDestroy
|
||||
|
||||
case KSHIELD_LIGHTNING:
|
||||
K_DoLightningShield(player);
|
||||
S_StartSound(player->mo, sfx_s3k7c);
|
||||
// K_DoLightningShield(player);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -7546,6 +7551,11 @@ static void K_UpdateTripwire(player_t *player)
|
|||
}
|
||||
}
|
||||
|
||||
boolean K_PressingEBrake(player_t *player)
|
||||
{
|
||||
return ((K_GetKartButtons(player) & BT_EBRAKEMASK) == BT_EBRAKEMASK);
|
||||
}
|
||||
|
||||
/** \brief Decreases various kart timers and powers per frame. Called in P_PlayerThink in p_user.c
|
||||
|
||||
\param player player object passed from P_PlayerThink
|
||||
|
|
@ -7725,7 +7735,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
|
|||
}
|
||||
|
||||
// Make ABSOLUTELY SURE that your flashing tics don't get set WHILE you're still in hit animations.
|
||||
if (player->spinouttimer != 0 || player->wipeoutslow != 0)
|
||||
if (player->spinouttimer != 0)
|
||||
{
|
||||
if (( player->spinouttype & KSPIN_IFRAMES ) == 0)
|
||||
player->flashing = 0;
|
||||
|
|
@ -8073,7 +8083,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
|
|||
K_SpawnGardenTopSpeedLines(player);
|
||||
}
|
||||
// Only allow drifting while NOT trying to do an spindash input.
|
||||
else if ((K_GetKartButtons(player) & BT_EBRAKEMASK) != BT_EBRAKEMASK)
|
||||
else if (K_PressingEBrake(player) == false)
|
||||
{
|
||||
player->pflags |= PF_DRIFTINPUT;
|
||||
}
|
||||
|
|
@ -8855,10 +8865,21 @@ INT16 K_UpdateSteeringValue(INT16 inputSteering, INT16 destSteering)
|
|||
// player->steering is the turning value, but with easing applied.
|
||||
// Keeps micro-turning from old easing, but isn't controller dependent.
|
||||
|
||||
const INT16 amount = KART_FULLTURN/3;
|
||||
INT16 amount = KART_FULLTURN/3;
|
||||
INT16 diff = destSteering - inputSteering;
|
||||
INT16 outputSteering = inputSteering;
|
||||
|
||||
|
||||
// We switched steering directions, lighten up on easing for a more responsive countersteer.
|
||||
// (Don't do this for steering 0, let digital inputs tap-adjust!)
|
||||
if ((inputSteering > 0 && destSteering < 0) || (inputSteering < 0 && destSteering > 0))
|
||||
{
|
||||
// Don't let small turns in direction X allow instant turns in direction Y.
|
||||
INT16 countersteer = min(KART_FULLTURN, abs(inputSteering)); // The farthest we should go is to 0 -- neutral.
|
||||
amount = max(countersteer, amount); // But don't reduce turning strength from baseline either.
|
||||
}
|
||||
|
||||
|
||||
if (abs(diff) <= amount)
|
||||
{
|
||||
// Reached the intended value, set instantly.
|
||||
|
|
@ -8929,10 +8950,16 @@ INT16 K_GetKartTurnValue(player_t *player, INT16 turnvalue)
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (Obj_PlayerRingShooterFreeze(player) == true)
|
||||
{
|
||||
// No turning while using Ring Shooter
|
||||
return 0;
|
||||
}
|
||||
|
||||
currentSpeed = FixedHypot(player->mo->momx, player->mo->momy);
|
||||
|
||||
if ((currentSpeed <= 0) // Not moving
|
||||
&& ((K_GetKartButtons(player) & BT_EBRAKEMASK) != BT_EBRAKEMASK) // Not e-braking
|
||||
&& (K_PressingEBrake(player) == false) // Not e-braking
|
||||
&& (player->respawn.state == RESPAWNST_NONE) // Not respawning
|
||||
&& (player->curshield != KSHIELD_TOP) // Not riding a Top
|
||||
&& (P_IsObjectOnGround(player->mo) == true)) // On the ground
|
||||
|
|
@ -9718,7 +9745,12 @@ static INT32 K_FlameShieldMax(player_t *player)
|
|||
boolean K_PlayerEBrake(player_t *player)
|
||||
{
|
||||
if (player->respawn.state != RESPAWNST_NONE
|
||||
&& player->respawn.init == true)
|
||||
&& (player->respawn.init == true || player->respawn.fromRingShooter == true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Obj_PlayerRingShooterFreeze(player) == true)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -9728,7 +9760,7 @@ boolean K_PlayerEBrake(player_t *player)
|
|||
return true;
|
||||
}
|
||||
|
||||
if ((K_GetKartButtons(player) & BT_EBRAKEMASK) == BT_EBRAKEMASK
|
||||
if (K_PressingEBrake(player) == true
|
||||
&& player->drift == 0
|
||||
&& P_PlayerInPain(player) == false
|
||||
&& player->justbumped == 0
|
||||
|
|
@ -11375,6 +11407,8 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
|
|||
{
|
||||
player->pflags &= ~PF_AIRFAILSAFE;
|
||||
}
|
||||
|
||||
Obj_RingShooterInput(player);
|
||||
}
|
||||
|
||||
void K_CheckSpectateStatus(void)
|
||||
|
|
@ -11644,6 +11678,7 @@ void K_EggmanTransfer(player_t *source, player_t *victim)
|
|||
return;
|
||||
|
||||
K_AddHitLag(victim->mo, 2, true);
|
||||
K_DropItems(victim);
|
||||
victim->eggmanexplode = 6*TICRATE;
|
||||
victim->itemRoulette.eggman = false;
|
||||
victim->itemRoulette.active = false;
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ void K_SpawnBumpEffect(mobj_t *mo);
|
|||
void K_KartMoveAnimation(player_t *player);
|
||||
void K_KartPlayerHUDUpdate(player_t *player);
|
||||
void K_KartResetPlayerColor(player_t *player);
|
||||
boolean K_PressingEBrake(player_t *player);
|
||||
void K_KartPlayerThink(player_t *player, ticcmd_t *cmd);
|
||||
void K_KartPlayerAfterThink(player_t *player);
|
||||
fixed_t K_MomentumThreshold(const mobj_t *mo);
|
||||
|
|
|
|||
|
|
@ -1673,7 +1673,6 @@ static void M_DrawProfileCard(INT32 x, INT32 y, boolean greyedout, profile_t *p)
|
|||
UINT16 truecol = SKINCOLOR_BLACK;
|
||||
UINT8 *colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_BLACK, GTC_CACHE);
|
||||
INT32 skinnum = -1;
|
||||
INT32 powerlevel = -1;
|
||||
|
||||
char pname[PROFILENAMELEN+1] = "NEW";
|
||||
|
||||
|
|
@ -1683,7 +1682,6 @@ static void M_DrawProfileCard(INT32 x, INT32 y, boolean greyedout, profile_t *p)
|
|||
colormap = R_GetTranslationColormap(TC_DEFAULT, truecol, GTC_CACHE);
|
||||
strcpy(pname, p->profilename);
|
||||
skinnum = R_SkinAvailable(p->skinname);
|
||||
powerlevel = p->powerlevels[0]; // Only display race power level.
|
||||
}
|
||||
|
||||
// check setup_player for colormap for the card.
|
||||
|
|
@ -1700,13 +1698,13 @@ static void M_DrawProfileCard(INT32 x, INT32 y, boolean greyedout, profile_t *p)
|
|||
if (greyedout)
|
||||
return; // only used for profiles we can't select.
|
||||
|
||||
// Draw pwlv if we can
|
||||
if (powerlevel > -1)
|
||||
if (p != NULL)
|
||||
{
|
||||
V_DrawFixedPatch((x+30)*FRACUNIT, (y+84)*FRACUNIT, FRACUNIT, 0, pwrlv, colormap);
|
||||
V_DrawCenteredKartString(x+30, y+87, 0, va("%d\n", powerlevel));
|
||||
V_DrawCenteredKartString(x+30, y+87, 0, va("%d", p->wins));
|
||||
}
|
||||
|
||||
|
||||
// check what setup_player is doing in priority.
|
||||
if (sp->mdepth >= CSSTEP_CHARS)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -106,6 +106,14 @@ void Obj_LoopEndpointCollide(mobj_t *special, mobj_t *toucher);
|
|||
void Obj_BeginDropTargetMorph(mobj_t *target, skincolornum_t color);
|
||||
boolean Obj_DropTargetMorphThink(mobj_t *morph);
|
||||
|
||||
/* Ring Shooter */
|
||||
boolean Obj_RingShooterThinker(mobj_t *mo);
|
||||
boolean Obj_PlayerRingShooterFreeze(player_t *const player);
|
||||
void Obj_RingShooterInput(player_t *player);
|
||||
void Obj_PlayerUsedRingShooter(mobj_t *base, player_t *player);
|
||||
void Obj_RingShooterDelete(mobj_t *mo);
|
||||
void Obj_UpdateRingShooterFace(mobj_t *part);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@ profile_t* PR_MakeProfile(
|
|||
boolean guest)
|
||||
{
|
||||
profile_t *new = Z_Malloc(sizeof(profile_t), PU_STATIC, NULL);
|
||||
UINT8 i;
|
||||
|
||||
new->version = PROFILEVER;
|
||||
|
||||
|
|
@ -72,11 +71,7 @@ profile_t* PR_MakeProfile(
|
|||
// Copy from gamecontrol directly as we'll be setting controls up directly in the profile.
|
||||
memcpy(new->controls, controlarray, sizeof(new->controls));
|
||||
|
||||
// Init both power levels
|
||||
for (i = 0; i < PWRLV_NUMTYPES; i++)
|
||||
{
|
||||
new->powerlevels[i] = (guest ? 0 : PWRLVRECORD_START);
|
||||
}
|
||||
new->wins = 0;
|
||||
|
||||
return new;
|
||||
}
|
||||
|
|
@ -270,11 +265,7 @@ void PR_SaveProfiles(void)
|
|||
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]);
|
||||
}
|
||||
WRITEUINT32(save.p, profilesList[i]->wins);
|
||||
|
||||
// Consvars.
|
||||
WRITEUINT8(save.p, profilesList[i]->kickstartaccel);
|
||||
|
|
@ -397,16 +388,15 @@ void PR_LoadProfiles(void)
|
|||
profilesList[i]->followercolor = PROFILEDEFAULTFOLLOWERCOLOR;
|
||||
}
|
||||
|
||||
// PWR.
|
||||
for (j = 0; j < PWRLV_NUMTYPES; j++)
|
||||
// Profile update 5-->6: PWR isn't in profile data anymore.
|
||||
if (version < 6)
|
||||
{
|
||||
profilesList[i]->powerlevels[j] = READUINT16(save.p);
|
||||
if (profilesList[i]->powerlevels[j] < PWRLVRECORD_MIN
|
||||
|| profilesList[i]->powerlevels[j] > PWRLVRECORD_MAX)
|
||||
{
|
||||
// invalid, reset
|
||||
profilesList[i]->powerlevels[j] = PWRLVRECORD_START;
|
||||
}
|
||||
save.p += PWRLV_NUMTYPES*2;
|
||||
profilesList[i]->wins = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
profilesList[i]->wins = READUINT32(save.p);
|
||||
}
|
||||
|
||||
// Consvars.
|
||||
|
|
@ -425,7 +415,7 @@ void PR_LoadProfiles(void)
|
|||
{
|
||||
#ifdef DEVELOP
|
||||
// Profile update 1-->2: Add gc_rankings.
|
||||
// Profile update 3-->5: Add gc_startlossless.
|
||||
// Profile update 4-->5: Add gc_startlossless.
|
||||
if ((j == gc_rankings && version < 2) ||
|
||||
(j == gc_startlossless && version < 5))
|
||||
{
|
||||
|
|
@ -630,3 +620,12 @@ char *GetPrettyRRID(const unsigned char *bin, boolean brief)
|
|||
|
||||
return rrid_buf;
|
||||
}
|
||||
|
||||
|
||||
static char allZero[PUBKEYLENGTH];
|
||||
|
||||
boolean PR_IsKeyGuest(uint8_t *key)
|
||||
{
|
||||
//memset(allZero, 0, PUBKEYLENGTH); -- not required, allZero is 0's
|
||||
return (memcmp(key, allZero, PUBKEYLENGTH) == 0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ extern "C" {
|
|||
#define SKINNAMESIZE 16
|
||||
|
||||
#define PROFILENAMELEN 6
|
||||
#define PROFILEVER 5
|
||||
#define PROFILEVER 6
|
||||
#define MAXPROFILES 16
|
||||
#define PROFILESFILE "ringprofiles.prf"
|
||||
#define PROFILE_GUEST 0
|
||||
|
|
@ -69,7 +69,7 @@ struct profile_t
|
|||
char follower[SKINNAMESIZE+1]; // Follower
|
||||
UINT16 followercolor; // Follower color
|
||||
|
||||
UINT16 powerlevels[PWRLV_NUMTYPES]; // PWRLV for each gametype.
|
||||
UINT32 wins; // I win I win I win
|
||||
|
||||
// Player-specific consvars.
|
||||
// @TODO: List all of those
|
||||
|
|
@ -166,6 +166,8 @@ boolean PR_IsLocalPlayerGuest(INT32 player);
|
|||
|
||||
char *GetPrettyRRID(const unsigned char *bin, boolean brief);
|
||||
|
||||
boolean PR_IsKeyGuest(uint8_t *key);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#include "p_tick.h" // leveltime
|
||||
#include "k_grandprix.h"
|
||||
#include "k_profiles.h"
|
||||
#include "k_serverstats.h"
|
||||
|
||||
// Client-sided calculations done for Power Levels.
|
||||
// This is done so that clients will never be able to hack someone else's score over the server.
|
||||
|
|
@ -414,7 +415,6 @@ void K_CashInPowerLevels(void)
|
|||
{
|
||||
SINT8 powerType = K_UsingPowerLevels();
|
||||
UINT8 i;
|
||||
boolean gamedataupdate;
|
||||
|
||||
//CONS_Printf("\n========\n");
|
||||
//CONS_Printf("Cashing in power level changes...\n");
|
||||
|
|
@ -424,29 +424,17 @@ void K_CashInPowerLevels(void)
|
|||
{
|
||||
if (playeringame[i] == true && powerType != PWRLV_DISABLED)
|
||||
{
|
||||
profile_t *pr = PR_GetPlayerProfile(&players[i]);
|
||||
INT16 inc = K_FinalPowerIncrement(&players[i], clientpowerlevels[i][powerType], clientPowerAdd[i]);
|
||||
|
||||
clientpowerlevels[i][powerType] += inc;
|
||||
|
||||
//CONS_Printf("%s: %d -> %d (%d)\n", player_names[i], clientpowerlevels[i][powerType] - inc, clientpowerlevels[i][powerType], inc);
|
||||
|
||||
if (pr != NULL && inc != 0)
|
||||
{
|
||||
pr->powerlevels[powerType] = clientpowerlevels[i][powerType];
|
||||
|
||||
gamedataupdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
clientPowerAdd[i] = 0;
|
||||
}
|
||||
|
||||
if (gamedataupdate)
|
||||
{
|
||||
M_UpdateUnlockablesAndExtraEmblems(true, true);
|
||||
G_SaveGameData();
|
||||
}
|
||||
SV_UpdateStats();
|
||||
|
||||
//CONS_Printf("========\n");
|
||||
}
|
||||
|
|
@ -580,7 +568,6 @@ void K_SetPowerLevelScrambles(SINT8 powertype)
|
|||
|
||||
void K_PlayerForfeit(UINT8 playerNum, boolean pointLoss)
|
||||
{
|
||||
profile_t *pr;
|
||||
UINT8 p = 0;
|
||||
|
||||
SINT8 powerType = PWRLV_DISABLED;
|
||||
|
|
@ -651,18 +638,10 @@ void K_PlayerForfeit(UINT8 playerNum, boolean pointLoss)
|
|||
return;
|
||||
}
|
||||
|
||||
if (inc < 0 && pointLoss == false)
|
||||
if (pointLoss)
|
||||
{
|
||||
// Don't record point losses for sync-out / crashes.
|
||||
return;
|
||||
}
|
||||
|
||||
pr = PR_GetPlayerProfile(&players[playerNum]);
|
||||
if (pr != NULL)
|
||||
{
|
||||
pr->powerlevels[powerType] = yourPower + inc;
|
||||
|
||||
M_UpdateUnlockablesAndExtraEmblems(true, true);
|
||||
G_SaveGameData();
|
||||
clientpowerlevels[playerNum][powerType] += clientPowerAdd[playerNum];
|
||||
clientPowerAdd[playerNum] = 0;
|
||||
SV_UpdateStats();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,9 +175,27 @@ void K_DoIngameRespawn(player_t *player)
|
|||
}
|
||||
else if (player->respawn.wp != NULL)
|
||||
{
|
||||
const UINT32 dist = RESPAWN_DIST + (player->airtime * 48);
|
||||
player->respawn.distanceleft = (dist * mapobjectscale) / FRACUNIT;
|
||||
K_RespawnAtWaypoint(player, player->respawn.wp);
|
||||
if (player->respawn.fromRingShooter == true)
|
||||
{
|
||||
waypoint_t *prevWP = player->respawn.wp;
|
||||
while (prevWP->numprevwaypoints > 0)
|
||||
{
|
||||
prevWP = prevWP->prevwaypoints[0];
|
||||
if (K_GetWaypointIsSpawnpoint(prevWP) == true)
|
||||
break;
|
||||
}
|
||||
|
||||
const UINT32 dist = (player->airtime * 48);
|
||||
player->respawn.distanceleft = (dist * mapobjectscale) / FRACUNIT;
|
||||
|
||||
K_RespawnAtWaypoint(player, prevWP);
|
||||
}
|
||||
else
|
||||
{
|
||||
const UINT32 dist = RESPAWN_DIST + (player->airtime * 48);
|
||||
player->respawn.distanceleft = (dist * mapobjectscale) / FRACUNIT;
|
||||
K_RespawnAtWaypoint(player, player->respawn.wp);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -465,7 +483,9 @@ static void K_MovePlayerToRespawnPoint(player_t *player)
|
|||
player->mo->momz = step.z;
|
||||
}
|
||||
|
||||
if (player->respawn.init == false && K_PlayerEBrake(player) == true)
|
||||
if (player->respawn.init == false
|
||||
&& player->respawn.fromRingShooter == false
|
||||
&& K_PlayerEBrake(player) == true)
|
||||
{
|
||||
// Manual drop!
|
||||
player->respawn.state = RESPAWNST_DROP;
|
||||
|
|
@ -822,6 +842,7 @@ void K_RespawnChecker(player_t *player)
|
|||
K_MovePlayerToRespawnPoint(player);
|
||||
return;
|
||||
case RESPAWNST_DROP:
|
||||
player->respawn.fromRingShooter = false;
|
||||
player->mo->momx = player->mo->momy = 0;
|
||||
player->flashing = 3;
|
||||
if (player->respawn.timer > 0)
|
||||
|
|
|
|||
236
src/k_serverstats.c
Normal file
236
src/k_serverstats.c
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
// SONIC ROBO BLAST 2
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright (C) 1998-2000 by DooM Legacy Team.
|
||||
// Copyright (C) 1999-2020 by Sonic Team Junior.
|
||||
// Copyright (C) 2023 by AJ "Tyron" Martinez
|
||||
//
|
||||
// This program is free software distributed under the
|
||||
// terms of the GNU General Public License, version 2.
|
||||
// See the 'LICENSE' file for more details.
|
||||
//-----------------------------------------------------------------------------
|
||||
/// \file k_serverstats.c
|
||||
/// \brief implements methods for serverside stat tracking.
|
||||
|
||||
#include "doomtype.h"
|
||||
#include "d_main.h" // pandf
|
||||
#include "byteptr.h" // READ/WRITE macros
|
||||
#include "p_saveg.h" // savebuffer_t
|
||||
#include "m_misc.h" //FIL_WriteFile()
|
||||
#include "k_serverstats.h"
|
||||
#include "z_zone.h"
|
||||
#include "time.h"
|
||||
|
||||
static serverplayer_t *trackedList;
|
||||
static size_t numtracked = 0;
|
||||
static size_t numallocated = 0;
|
||||
static boolean initialized = false;
|
||||
|
||||
UINT16 guestpwr[PWRLV_NUMTYPES]; // All-zero power level to reference for guests
|
||||
|
||||
static void SV_InitializeStats(void)
|
||||
{
|
||||
if (!initialized)
|
||||
{
|
||||
numallocated = 8;
|
||||
trackedList = Z_Calloc(
|
||||
sizeof(serverplayer_t) * numallocated,
|
||||
PU_STATIC,
|
||||
&trackedList
|
||||
);
|
||||
|
||||
if (trackedList == NULL)
|
||||
{
|
||||
I_Error("Not enough memory for server stats\n");
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void SV_ExpandStats(size_t needed)
|
||||
{
|
||||
I_Assert(trackedList != NULL);
|
||||
|
||||
while (numallocated < needed)
|
||||
{
|
||||
numallocated *= 2;
|
||||
trackedList = Z_Realloc(
|
||||
trackedList,
|
||||
sizeof(serverplayer_t) * numallocated,
|
||||
PU_STATIC,
|
||||
&trackedList
|
||||
);
|
||||
|
||||
if (trackedList == NULL)
|
||||
{
|
||||
I_Error("Not enough memory for server stats\n");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Read stats file to trackedList for ingame use
|
||||
void SV_LoadStats(void)
|
||||
{
|
||||
const size_t headerlen = strlen(SERVERSTATSHEADER);
|
||||
savebuffer_t save = {0};
|
||||
unsigned int i, j;
|
||||
|
||||
if (!server)
|
||||
return;
|
||||
|
||||
if (P_SaveBufferFromFile(&save, va(pandf, srb2home, SERVERSTATSFILE)) == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SV_InitializeStats();
|
||||
|
||||
if (strncmp(SERVERSTATSHEADER, (const char *)save.buffer, headerlen))
|
||||
{
|
||||
const char *gdfolder = "the Ring Racers folder";
|
||||
if (strcmp(srb2home,"."))
|
||||
gdfolder = srb2home;
|
||||
|
||||
P_SaveBufferFree(&save);
|
||||
I_Error("Not a valid server stats file.\nDelete %s (maybe in %s) and try again.", SERVERSTATSFILE, gdfolder);
|
||||
}
|
||||
|
||||
save.p += headerlen;
|
||||
UINT8 version = READUINT8(save.p);
|
||||
(void)version; // for now
|
||||
|
||||
numtracked = READUINT32(save.p);
|
||||
|
||||
SV_ExpandStats(numtracked);
|
||||
|
||||
for(i = 0; i < numtracked; i++)
|
||||
{
|
||||
READMEM(save.p, trackedList[i].public_key, PUBKEYLENGTH);
|
||||
READMEM(save.p, &trackedList[i].lastseen, sizeof(trackedList[i].lastseen));
|
||||
for(j = 0; j < PWRLV_NUMTYPES; j++)
|
||||
{
|
||||
trackedList[i].powerlevels[j] = READUINT16(save.p);
|
||||
}
|
||||
trackedList[i].hash = quickncasehash((char*)trackedList[i].public_key, PUBKEYLENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
// Save trackedList to disc
|
||||
void SV_SaveStats(void)
|
||||
{
|
||||
size_t length = 0;
|
||||
const size_t headerlen = strlen(SERVERSTATSHEADER);
|
||||
savebuffer_t save = {0};
|
||||
unsigned int i, j;
|
||||
|
||||
if (!server)
|
||||
return;
|
||||
|
||||
// header + version + numtracked + payload
|
||||
if (P_SaveBufferAlloc(&save, headerlen + sizeof(UINT32) + sizeof(UINT8) + (numtracked * sizeof(serverplayer_t))) == false)
|
||||
{
|
||||
I_Error("No more free memory for saving server stats\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Add header.
|
||||
WRITESTRINGN(save.p, SERVERSTATSHEADER, headerlen);
|
||||
|
||||
WRITEUINT8(save.p, SERVERSTATSVER);
|
||||
|
||||
WRITEUINT32(save.p, numtracked);
|
||||
|
||||
for(i = 0; i < numtracked; i++)
|
||||
{
|
||||
WRITEMEM(save.p, trackedList[i].public_key, PUBKEYLENGTH);
|
||||
WRITEMEM(save.p, &trackedList[i].lastseen, sizeof(trackedList[i].lastseen));
|
||||
for(j = 0; j < PWRLV_NUMTYPES; j++)
|
||||
{
|
||||
WRITEUINT16(save.p, trackedList[i].powerlevels[j]);
|
||||
}
|
||||
}
|
||||
|
||||
length = save.p - save.buffer;
|
||||
|
||||
if (!FIL_WriteFile(va(pandf, srb2home, SERVERSTATSFILE), save.buffer, length))
|
||||
{
|
||||
P_SaveBufferFree(&save);
|
||||
I_Error("Couldn't save server stats. Are you out of Disk space / playing in a protected folder?");
|
||||
}
|
||||
P_SaveBufferFree(&save);
|
||||
}
|
||||
|
||||
// New player, grab their stats from trackedList or initialize new ones if they're new
|
||||
serverplayer_t *SV_RetrieveStats(uint8_t *key)
|
||||
{
|
||||
UINT32 j, hash;
|
||||
|
||||
SV_InitializeStats();
|
||||
|
||||
hash = quickncasehash((char*)key, PUBKEYLENGTH);
|
||||
|
||||
// Existing record?
|
||||
for(j = 0; j < numtracked; j++)
|
||||
{
|
||||
if (hash != trackedList[j].hash) // Not crypto magic, just an early out with a faster comparison
|
||||
continue;
|
||||
if (memcmp(trackedList[j].public_key, key, PUBKEYLENGTH) == 0)
|
||||
return &trackedList[j];
|
||||
}
|
||||
|
||||
// Untracked below this point, make a new record
|
||||
SV_ExpandStats(numtracked+1);
|
||||
|
||||
// Default stats
|
||||
trackedList[numtracked].lastseen = time(NULL);
|
||||
memcpy(&trackedList[numtracked].public_key, key, PUBKEYLENGTH);
|
||||
for(j = 0; j < PWRLV_NUMTYPES; j++)
|
||||
{
|
||||
trackedList[numtracked].powerlevels[j] = PR_IsKeyGuest(key) ? 0 : PWRLVRECORD_START;
|
||||
}
|
||||
trackedList[numtracked].hash = quickncasehash((char*)key, PUBKEYLENGTH);
|
||||
|
||||
numtracked++;
|
||||
|
||||
return &trackedList[numtracked - 1];
|
||||
}
|
||||
|
||||
// Write player stats to trackedList, then save to disk
|
||||
void SV_UpdateStats(void)
|
||||
{
|
||||
UINT32 i, j, hash;
|
||||
|
||||
if (!server)
|
||||
return;
|
||||
|
||||
SV_InitializeStats();
|
||||
|
||||
for(i = 0; i < MAXPLAYERS; i++)
|
||||
{
|
||||
if (!playeringame[i])
|
||||
continue;
|
||||
|
||||
if (PR_IsKeyGuest(players[i].public_key))
|
||||
continue;
|
||||
|
||||
hash = quickncasehash((char*)players[i].public_key, PUBKEYLENGTH);
|
||||
|
||||
for(j = 0; j < numtracked; j++)
|
||||
{
|
||||
if (hash != trackedList[j].hash) // Not crypto magic, just an early out with a faster comparison
|
||||
continue;
|
||||
if (memcmp(&trackedList[j].public_key, players[i].public_key, PUBKEYLENGTH) == 0)
|
||||
{
|
||||
trackedList[j].lastseen = time(NULL);
|
||||
memcpy(&trackedList[j].powerlevels, clientpowerlevels[i], sizeof(trackedList[j].powerlevels));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// SV_RetrievePWR should always be called for a key before SV_UpdateStats runs,
|
||||
// so this shouldn't be reachable.
|
||||
}
|
||||
|
||||
SV_SaveStats();
|
||||
}
|
||||
53
src/k_serverstats.h
Normal file
53
src/k_serverstats.h
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
// SONIC ROBO BLAST 2
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright (C) 1993-1996 by id Software, Inc.
|
||||
// Copyright (C) 1998-2000 by DooM Legacy Team.
|
||||
// Copyright (C) 2011-2016 by Matthew "Inuyasha" Walsh.
|
||||
// Copyright (C) 1999-2018 by Sonic Team Junior.
|
||||
// Copyright (C) 2023 by AJ "Tyron" Martinez
|
||||
//
|
||||
// This program is free software distributed under the
|
||||
// terms of the GNU General Public License, version 2.
|
||||
// See the 'LICENSE' file for more details.
|
||||
//-----------------------------------------------------------------------------
|
||||
/// \file k_serverstats.h
|
||||
/// \brief serverside stat tracking definitions
|
||||
|
||||
#ifndef __SERVERSTATS_H__
|
||||
#define __SERVERSTATS_H__
|
||||
|
||||
#include "doomdef.h" // MAXPLAYERNAME
|
||||
#include "g_input.h" // Input related stuff
|
||||
#include "string.h" // strcpy etc
|
||||
#include "g_game.h" // game CVs
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define SERVERSTATSFILE "srvstats.dat"
|
||||
#define SERVERSTATSHEADER "Doctor Robotnik's Ring Racers Server Stats"
|
||||
#define SERVERSTATSVER 1
|
||||
|
||||
struct serverplayer_t
|
||||
{
|
||||
uint8_t public_key[PUBKEYLENGTH];
|
||||
UINT32 lastseen;
|
||||
UINT16 powerlevels[PWRLV_NUMTYPES];
|
||||
|
||||
UINT32 hash; // Not persisted! Used for early outs during key comparisons
|
||||
};
|
||||
|
||||
void SV_SaveStats(void);
|
||||
|
||||
void SV_LoadStats(void);
|
||||
|
||||
serverplayer_t *SV_RetrieveStats(uint8_t *key);
|
||||
|
||||
void SV_UpdateStats(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
@ -584,7 +584,7 @@ void K_ProcessTerrainEffect(mobj_t *mo)
|
|||
{
|
||||
if (player->mo->floorrover != NULL)
|
||||
{
|
||||
slope = *player->mo->ceilingrover->t_slope;
|
||||
slope = *player->mo->floorrover->t_slope;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
25
src/m_cond.c
25
src/m_cond.c
|
|
@ -683,25 +683,6 @@ boolean M_CheckCondition(condition_t *cn, player_t *player)
|
|||
}
|
||||
case UC_TOTALRINGS: // Requires grabbing >= x rings
|
||||
return (gamedata->totalrings >= (unsigned)cn->requirement);
|
||||
case UC_POWERLEVEL: // Requires power level >= x on a certain gametype
|
||||
{
|
||||
UINT8 i;
|
||||
|
||||
if (gamestate == GS_LEVEL)
|
||||
return false; // this one could be laggy with many profiles available
|
||||
|
||||
for (i = PROFILE_GUEST; i < PR_GetNumProfiles(); i++)
|
||||
{
|
||||
profile_t *p = PR_GetProfile(i);
|
||||
|
||||
if (p->powerlevels[cn->extrainfo1] >= (unsigned)cn->requirement)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
case UC_GAMECLEAR: // Requires game beaten >= x times
|
||||
return (gamedata->timesBeaten >= (unsigned)cn->requirement);
|
||||
case UC_OVERALLTIME: // Requires overall time <= x
|
||||
|
|
@ -1039,12 +1020,6 @@ static const char *M_GetConditionString(condition_t *cn)
|
|||
return va("collect %u,%03u Rings", (cn->requirement/1000), (cn->requirement%1000));
|
||||
return va("collect %u Rings", cn->requirement);
|
||||
|
||||
case UC_POWERLEVEL: // Requires power level >= x on a certain gametype
|
||||
return va("get a PWR of %d in %s", cn->requirement,
|
||||
(cn->extrainfo1 == PWRLV_RACE)
|
||||
? "Race"
|
||||
: "Battle");
|
||||
|
||||
case UC_GAMECLEAR: // Requires game beaten >= x times
|
||||
if (cn->requirement > 1)
|
||||
return va("beat game %d times", cn->requirement);
|
||||
|
|
|
|||
|
|
@ -32,8 +32,6 @@ typedef enum
|
|||
UC_ROUNDSPLAYED, // ROUNDSPLAYED [x played]
|
||||
UC_TOTALRINGS, // TOTALRINGS [x collected]
|
||||
|
||||
UC_POWERLEVEL, // SRB2Kart: POWERLEVEL [power level to reach] [gametype, "0" for race, "1" for battle]
|
||||
|
||||
UC_GAMECLEAR, // GAMECLEAR <x times>
|
||||
UC_OVERALLTIME, // OVERALLTIME [time to beat, tics]
|
||||
|
||||
|
|
|
|||
|
|
@ -490,7 +490,7 @@ static void M_DrawTickStats(void)
|
|||
|
||||
void M_DrawPerfStats(void)
|
||||
{
|
||||
char s[100];
|
||||
char s[363];
|
||||
|
||||
PS_SetFrameTime();
|
||||
|
||||
|
|
|
|||
|
|
@ -22,11 +22,10 @@ menuitem_t OPTIONS_ProfileControls[] = {
|
|||
{IT_CONTROL, "X", "Brake / Back",
|
||||
"PR_BTX", {.routine = M_ProfileSetControl}, gc_x, 0},
|
||||
|
||||
// @TODO What does this do???
|
||||
{IT_CONTROL, "Y", "N/A ?",
|
||||
{IT_CONTROL, "Y", "Respawn",
|
||||
"PR_BTY", {.routine = M_ProfileSetControl}, gc_y, 0},
|
||||
|
||||
{IT_CONTROL, "Z", "N/A ?",
|
||||
{IT_CONTROL, "Z", "Multiplayer quick-chat / quick-vote",
|
||||
"PR_BTZ", {.routine = M_ProfileSetControl}, gc_z, 0},
|
||||
|
||||
{IT_CONTROL, "L", "Use item",
|
||||
|
|
@ -62,13 +61,13 @@ menuitem_t OPTIONS_ProfileControls[] = {
|
|||
{IT_CONTROL, "RECORD LOSSLESS", "Record a pixel perfect GIF.",
|
||||
NULL, {.routine = M_ProfileSetControl}, gc_startlossless, 0},
|
||||
|
||||
{IT_CONTROL, "OPEN CHAT", "Opens chatbox in online games.",
|
||||
{IT_CONTROL, "OPEN CHAT", "Opens full keyboard chatting for online games.",
|
||||
NULL, {.routine = M_ProfileSetControl}, gc_talk, 0},
|
||||
|
||||
{IT_CONTROL, "OPEN TEAM CHAT", "Do we even have team gamemodes?",
|
||||
{IT_CONTROL, "OPEN TEAM CHAT", "Opens team-only full chat for online games.",
|
||||
NULL, {.routine = M_ProfileSetControl}, gc_teamtalk, 0},
|
||||
|
||||
{IT_CONTROL, "SHOW RANKINGS", "Show mid-game rankings.",
|
||||
{IT_CONTROL, "SHOW RANKINGS", "Display the current rankings mid-game.",
|
||||
NULL, {.routine = M_ProfileSetControl}, gc_rankings, 0},
|
||||
|
||||
{IT_CONTROL, "OPEN CONSOLE", "Opens the developer options console.",
|
||||
|
|
|
|||
|
|
@ -14,4 +14,5 @@ target_sources(SRB2SDL2 PRIVATE
|
|||
item-spot.c
|
||||
loops.c
|
||||
drop-target.c
|
||||
ring-shooter.c
|
||||
)
|
||||
|
|
|
|||
|
|
@ -260,14 +260,25 @@ boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2)
|
|||
damageitem = false;
|
||||
}
|
||||
|
||||
if (damageitem)
|
||||
if (damageitem && P_MobjWasRemoved(t1) == false)
|
||||
{
|
||||
angle_t bounceangle;
|
||||
if (P_MobjWasRemoved(t2) == false)
|
||||
{
|
||||
bounceangle = K_GetCollideAngle(t2, t1);
|
||||
}
|
||||
else
|
||||
{
|
||||
bounceangle = K_MomentumAngle(t1) + ANGLE_90;
|
||||
t2 = NULL; // handles the arguments to P_KillMobj
|
||||
}
|
||||
|
||||
// This Item Damage
|
||||
angle_t bounceangle = K_GetCollideAngle(t2, t1);
|
||||
S_StartSound(t1, t1->info->deathsound);
|
||||
P_KillMobj(t1, t2, t2, DMG_NORMAL);
|
||||
|
||||
P_SetObjectMomZ(t1, 24*FRACUNIT, false);
|
||||
|
||||
P_InstaThrust(t1, bounceangle, 16*FRACUNIT);
|
||||
}
|
||||
|
||||
|
|
|
|||
801
src/objects/ring-shooter.c
Normal file
801
src/objects/ring-shooter.c
Normal file
|
|
@ -0,0 +1,801 @@
|
|||
// DR. ROBOTNIK'S RING RACERS
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright (C) by Sally "TehRealSalt" Cochenour
|
||||
// Copyright (C) by "Lach"
|
||||
// Copyright (C) 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 ring-shooter.c
|
||||
/// \brief DEZ "Ring Shooter" respawner object
|
||||
|
||||
#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 "../r_skins.h"
|
||||
#include "../k_respawn.h"
|
||||
#include "../lua_hook.h"
|
||||
|
||||
#define RS_FUSE_TIME (4*TICRATE)
|
||||
#define RS_FUSE_BLINK (TICRATE >> 1)
|
||||
|
||||
#define RS_GRABBER_START (16 << FRACBITS)
|
||||
#define RS_GRABBER_SLIDE (RS_GRABBER_START >> 4)
|
||||
#define RS_GRABBER_EXTRA (18 << FRACBITS)
|
||||
|
||||
#define RS_KARTED_INC (3)
|
||||
|
||||
#define rs_base_scalespeed(o) ((o)->scalespeed)
|
||||
#define rs_base_initstate(o) ((o)->threshold)
|
||||
#define rs_base_xscale(o) ((o)->extravalue1)
|
||||
#define rs_base_yscale(o) ((o)->extravalue2)
|
||||
|
||||
#define rs_base_playerid(o) ((o)->lastlook)
|
||||
#define rs_base_playerface(o) ((o)->cusval)
|
||||
#define rs_base_playerlast(o) ((o)->watertop)
|
||||
|
||||
#define rs_base_karted(o) ((o)->movecount)
|
||||
#define rs_base_grabberdist(o) ((o)->movefactor)
|
||||
#define rs_base_canceled(o) ((o)->cvmem)
|
||||
|
||||
#define rs_part_xoffset(o) ((o)->extravalue1)
|
||||
#define rs_part_yoffset(o) ((o)->extravalue2)
|
||||
|
||||
static void RemoveRingShooterPointer(mobj_t *base)
|
||||
{
|
||||
player_t *player = NULL;
|
||||
|
||||
if (rs_base_playerid(base) < 0 || rs_base_playerid(base) >= MAXPLAYERS)
|
||||
{
|
||||
// No pointer set
|
||||
return;
|
||||
}
|
||||
|
||||
// NULL the player's pointer.
|
||||
player = &players[ rs_base_playerid(base) ];
|
||||
P_SetTarget(&player->ringShooter, NULL);
|
||||
|
||||
// Remove our player ID
|
||||
rs_base_playerid(base) = -1;
|
||||
}
|
||||
|
||||
|
||||
static void ChangeRingShooterPointer(mobj_t *base, player_t *player)
|
||||
{
|
||||
// Remove existing pointer first.
|
||||
RemoveRingShooterPointer(base);
|
||||
|
||||
if (player == NULL)
|
||||
{
|
||||
// Just remove it.
|
||||
return;
|
||||
}
|
||||
|
||||
// Set new player pointer.
|
||||
P_SetTarget(&player->ringShooter, base);
|
||||
|
||||
// Set new player ID.
|
||||
rs_base_playerid(base) = (player - players);
|
||||
}
|
||||
|
||||
static void ScalePart(mobj_t *part, mobj_t *base)
|
||||
{
|
||||
part->spritexscale = rs_base_xscale(base);
|
||||
part->spriteyscale = rs_base_yscale(base);
|
||||
|
||||
if (part->type == MT_TIREGRABBER)
|
||||
{
|
||||
part->spritexscale /= 2;
|
||||
part->spriteyscale /= 2;
|
||||
}
|
||||
}
|
||||
|
||||
static void MovePart(mobj_t *part, mobj_t *base, mobj_t *refNipple)
|
||||
{
|
||||
P_MoveOrigin(
|
||||
part,
|
||||
refNipple->x + FixedMul(rs_part_xoffset(part), rs_base_xscale(base)),
|
||||
refNipple->y + FixedMul(rs_part_yoffset(part), rs_base_xscale(base)),
|
||||
part->z
|
||||
);
|
||||
}
|
||||
|
||||
static void ShowHidePart(mobj_t *part, mobj_t *base)
|
||||
{
|
||||
part->renderflags = (part->renderflags & ~RF_DONTDRAW) | (base->renderflags & RF_DONTDRAW);
|
||||
}
|
||||
|
||||
static fixed_t GetTireDist(mobj_t *base)
|
||||
{
|
||||
return -(RS_GRABBER_EXTRA + rs_base_grabberdist(base));
|
||||
}
|
||||
|
||||
static void MoveTire(mobj_t *part, mobj_t *base)
|
||||
{
|
||||
const fixed_t dis = FixedMul(GetTireDist(base), base->scale);
|
||||
const fixed_t c = FINECOSINE(part->angle >> ANGLETOFINESHIFT);
|
||||
const fixed_t s = FINESINE(part->angle >> ANGLETOFINESHIFT);
|
||||
P_MoveOrigin(
|
||||
part,
|
||||
base->x + FixedMul(dis, c),
|
||||
base->y + FixedMul(dis, s),
|
||||
part->z
|
||||
);
|
||||
}
|
||||
|
||||
// I've tried to reduce redundancy as much as I can,
|
||||
// but check K_SpawnRingShooter if you edit this
|
||||
static void UpdateRingShooterParts(mobj_t *mo)
|
||||
{
|
||||
mobj_t *part, *refNipple;
|
||||
|
||||
part = mo;
|
||||
while (!P_MobjWasRemoved(part->target))
|
||||
{
|
||||
part = part->target;
|
||||
ScalePart(part, mo);
|
||||
MoveTire(part, mo);
|
||||
}
|
||||
|
||||
part = mo;
|
||||
while (!P_MobjWasRemoved(part->hprev))
|
||||
{
|
||||
part = part->hprev;
|
||||
ScalePart(part, mo);
|
||||
}
|
||||
refNipple = part;
|
||||
|
||||
part = mo;
|
||||
while (!P_MobjWasRemoved(part->hnext))
|
||||
{
|
||||
part = part->hnext;
|
||||
MovePart(part, mo, refNipple);
|
||||
ScalePart(part, mo);
|
||||
}
|
||||
|
||||
part = mo->tracer;
|
||||
part->z = mo->z + FixedMul(refNipple->height, rs_base_yscale(mo));
|
||||
MovePart(part, mo, refNipple);
|
||||
ScalePart(part, mo);
|
||||
}
|
||||
|
||||
static void UpdateRingShooterPartsVisibility(mobj_t *mo)
|
||||
{
|
||||
mobj_t *part;
|
||||
|
||||
part = mo;
|
||||
while (!P_MobjWasRemoved(part->target))
|
||||
{
|
||||
part = part->target;
|
||||
ShowHidePart(part, mo);
|
||||
}
|
||||
|
||||
part = mo;
|
||||
while (!P_MobjWasRemoved(part->hprev))
|
||||
{
|
||||
part = part->hprev;
|
||||
ShowHidePart(part, mo);
|
||||
}
|
||||
|
||||
part = mo;
|
||||
while (!P_MobjWasRemoved(part->hnext))
|
||||
{
|
||||
part = part->hnext;
|
||||
ShowHidePart(part, mo);
|
||||
}
|
||||
|
||||
part = mo->tracer;
|
||||
ShowHidePart(part, mo);
|
||||
}
|
||||
|
||||
static void RingShooterCountdown(mobj_t *mo)
|
||||
{
|
||||
mobj_t *part = mo->tracer;
|
||||
|
||||
if (mo->reactiontime < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (--mo->reactiontime > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (!P_MobjWasRemoved(part->tracer))
|
||||
{
|
||||
part = part->tracer;
|
||||
part->frame--;
|
||||
}
|
||||
|
||||
switch ((part->frame & FF_FRAMEMASK) - (part->state->frame & FF_FRAMEMASK))
|
||||
{
|
||||
case -1:
|
||||
{
|
||||
mo->reactiontime = -1;
|
||||
|
||||
if (rs_base_playerface(mo) >= 0 && rs_base_playerface(mo) < MAXPLAYERS)
|
||||
{
|
||||
if (playeringame[rs_base_playerface(mo)] == true)
|
||||
{
|
||||
player_t *player = &players[ rs_base_playerid(mo) ];
|
||||
part->skin = &skins[player->skin];
|
||||
}
|
||||
}
|
||||
|
||||
P_SetMobjState(part, S_RINGSHOOTER_FACE);
|
||||
break;
|
||||
}
|
||||
case 0:
|
||||
{
|
||||
mo->reactiontime = TICRATE;
|
||||
S_StartSound(mo, mo->info->deathsound);
|
||||
|
||||
if (rs_base_playerid(mo) >= 0 && rs_base_playerid(mo) < MAXPLAYERS)
|
||||
{
|
||||
if (playeringame[rs_base_playerid(mo)] == true)
|
||||
{
|
||||
player_t *player = &players[ rs_base_playerid(mo) ];
|
||||
Obj_PlayerUsedRingShooter(mo, player);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
mo->reactiontime = TICRATE;
|
||||
S_StartSound(mo, mo->info->painsound);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void RingShooterFlicker(mobj_t *mo)
|
||||
{
|
||||
UINT32 trans;
|
||||
mobj_t *part = mo->tracer;
|
||||
|
||||
while (!P_MobjWasRemoved(part->tracer))
|
||||
{
|
||||
part = part->tracer;
|
||||
}
|
||||
|
||||
part->renderflags ^= RF_DONTDRAW;
|
||||
if (part->renderflags & RF_DONTDRAW)
|
||||
{
|
||||
trans = FF_TRANS50;
|
||||
}
|
||||
else
|
||||
{
|
||||
trans = 0;
|
||||
}
|
||||
part->target->frame = (part->target->frame & ~FF_TRANSMASK) | trans;
|
||||
}
|
||||
|
||||
static void ActivateRingShooter(mobj_t *mo)
|
||||
{
|
||||
mobj_t *part = mo->tracer;
|
||||
|
||||
while (!P_MobjWasRemoved(part->tracer))
|
||||
{
|
||||
part = part->tracer;
|
||||
part->renderflags &= ~RF_DONTDRAW;
|
||||
part->frame += 4;
|
||||
}
|
||||
|
||||
RingShooterCountdown(mo);
|
||||
}
|
||||
|
||||
static boolean RingShooterInit(mobj_t *mo)
|
||||
{
|
||||
if (rs_base_initstate(mo) == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (rs_base_initstate(mo))
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
rs_base_yscale(mo) += rs_base_scalespeed(mo);
|
||||
if (rs_base_yscale(mo) >= FRACUNIT)
|
||||
{
|
||||
//rs_base_xscale(mo) -= rs_base_scalespeed(mo);
|
||||
rs_base_initstate(mo)++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
rs_base_scalespeed(mo) -= FRACUNIT/5;
|
||||
rs_base_yscale(mo) += rs_base_scalespeed(mo);
|
||||
rs_base_xscale(mo) -= rs_base_scalespeed(mo);
|
||||
if (rs_base_yscale(mo) < 3*FRACUNIT/4)
|
||||
{
|
||||
rs_base_initstate(mo)++;
|
||||
rs_base_scalespeed(mo) = FRACUNIT >> 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
rs_base_yscale(mo) += rs_base_scalespeed(mo);
|
||||
rs_base_xscale(mo) -= rs_base_scalespeed(mo);
|
||||
if (rs_base_yscale(mo) >= FRACUNIT)
|
||||
{
|
||||
rs_base_initstate(mo)++;
|
||||
rs_base_xscale(mo) = rs_base_yscale(mo) = FRACUNIT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
if (rs_base_canceled(mo) != 0)
|
||||
{
|
||||
rs_base_initstate(mo) = -1;
|
||||
ActivateRingShooter(mo);
|
||||
}
|
||||
else
|
||||
{
|
||||
rs_base_grabberdist(mo) -= RS_GRABBER_SLIDE;
|
||||
if (rs_base_grabberdist(mo) <= 0)
|
||||
{
|
||||
rs_base_initstate(mo) = -1;
|
||||
rs_base_grabberdist(mo) = 0;
|
||||
ActivateRingShooter(mo);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
rs_base_initstate(mo) = 0; // fix invalid states
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateRingShooterParts(mo);
|
||||
return (rs_base_initstate(mo) != -1);
|
||||
}
|
||||
|
||||
boolean Obj_RingShooterThinker(mobj_t *mo)
|
||||
{
|
||||
if (RingShooterInit(mo) == true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mo->fuse > 0)
|
||||
{
|
||||
mo->fuse--;
|
||||
|
||||
if (mo->fuse == 0)
|
||||
{
|
||||
P_RemoveMobj(mo);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (rs_base_canceled(mo) == 0)
|
||||
{
|
||||
rs_base_karted(mo) += RS_KARTED_INC;
|
||||
|
||||
if (P_MobjWasRemoved(mo->tracer) == false)
|
||||
{
|
||||
RingShooterCountdown(mo);
|
||||
}
|
||||
}
|
||||
|
||||
if (P_MobjWasRemoved(mo->tracer) == false)
|
||||
{
|
||||
RingShooterFlicker(mo);
|
||||
}
|
||||
|
||||
if (mo->fuse < RS_FUSE_BLINK)
|
||||
{
|
||||
if (leveltime & 1)
|
||||
{
|
||||
mo->renderflags |= RF_DONTDRAW;
|
||||
}
|
||||
else
|
||||
{
|
||||
mo->renderflags &= ~RF_DONTDRAW;
|
||||
}
|
||||
|
||||
UpdateRingShooterPartsVisibility(mo);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Obj_PlayerUsedRingShooter(mobj_t *base, player_t *player)
|
||||
{
|
||||
const UINT8 playerID = player - players;
|
||||
if (playerID == rs_base_playerlast(base))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// The original player should no longer have control over it,
|
||||
// if they are using it via releasing.
|
||||
RemoveRingShooterPointer(base);
|
||||
|
||||
// Respawn using the respawner's karted value.
|
||||
if (rs_base_karted(base) > 0)
|
||||
{
|
||||
player->airtime += rs_base_karted(base);
|
||||
}
|
||||
|
||||
player->respawn.fromRingShooter = true;
|
||||
K_DoIngameRespawn(player);
|
||||
|
||||
// Now other players can run into it!
|
||||
base->flags |= MF_SPECIAL;
|
||||
|
||||
// Reset the fuse so everyone can conga line :B
|
||||
if (base->fuse < RS_FUSE_TIME)
|
||||
{
|
||||
if (base->fuse < RS_FUSE_BLINK)
|
||||
{
|
||||
base->renderflags &= ~RF_DONTDRAW;
|
||||
UpdateRingShooterPartsVisibility(base);
|
||||
}
|
||||
|
||||
base->fuse = RS_FUSE_TIME;
|
||||
}
|
||||
|
||||
// Record the last person to use the ring shooter.
|
||||
rs_base_playerlast(base) = playerID;
|
||||
}
|
||||
|
||||
void Obj_RingShooterDelete(mobj_t *mo)
|
||||
{
|
||||
mobj_t *part;
|
||||
|
||||
RemoveRingShooterPointer(mo);
|
||||
|
||||
part = mo->target;
|
||||
while (P_MobjWasRemoved(part) == false)
|
||||
{
|
||||
mobj_t *delete = part;
|
||||
part = part->target;
|
||||
P_RemoveMobj(delete);
|
||||
}
|
||||
|
||||
part = mo->hprev;
|
||||
while (P_MobjWasRemoved(part) == false)
|
||||
{
|
||||
mobj_t *delete = part;
|
||||
part = part->hprev;
|
||||
P_RemoveMobj(delete);
|
||||
}
|
||||
|
||||
part = mo->hnext;
|
||||
while (P_MobjWasRemoved(part) == false)
|
||||
{
|
||||
mobj_t *delete = part;
|
||||
part = part->hnext;
|
||||
P_RemoveMobj(delete);
|
||||
}
|
||||
|
||||
part = mo->tracer;
|
||||
if (P_MobjWasRemoved(part) == false)
|
||||
{
|
||||
P_RemoveMobj(part);
|
||||
}
|
||||
}
|
||||
|
||||
// I've tried to reduce redundancy as much as I can,
|
||||
// but check P_UpdateRingShooterParts if you edit this
|
||||
static void SpawnRingShooter(player_t *player)
|
||||
{
|
||||
const fixed_t scale = 2*FRACUNIT;
|
||||
mobjinfo_t *info = &mobjinfo[MT_RINGSHOOTER_PART];
|
||||
mobj_t *mo = player->mo;
|
||||
mobj_t *base = P_SpawnMobj(mo->x, mo->y, mo->z, MT_RINGSHOOTER);
|
||||
mobj_t *part, *refNipple;
|
||||
UINT32 frameNum;
|
||||
angle_t angle;
|
||||
vector2_t offset;
|
||||
SINT8 i;
|
||||
|
||||
rs_base_playerid(base) = rs_base_playerlast(base) = -1;
|
||||
rs_base_karted(base) = -(RS_KARTED_INC * TICRATE); // wait for "3"
|
||||
rs_base_grabberdist(base) = RS_GRABBER_START;
|
||||
|
||||
K_FlipFromObject(base, mo);
|
||||
P_SetScale(base, base->destscale = FixedMul(base->destscale, scale));
|
||||
base->angle = mo->angle;
|
||||
base->scalespeed = FRACUNIT/2;
|
||||
base->extravalue1 = FRACUNIT; // horizontal scale
|
||||
base->extravalue2 = 0; // vertical scale
|
||||
base->fuse = RS_FUSE_TIME;
|
||||
|
||||
// the ring shooter object itself is invisible and acts as the thinker
|
||||
// each ring shooter uses four linked lists to keep track of its parts
|
||||
// the hprev chain stores the two NIPPLE BARS
|
||||
// the hnext chain stores the four sides of the box
|
||||
// the tracer chain stores the screen and the screen layers
|
||||
// the target chain stores the tire grabbers
|
||||
|
||||
// spawn the RING NIPPLES
|
||||
part = base;
|
||||
frameNum = 0;
|
||||
FV2_Load(&offset, -96*FRACUNIT, 160*FRACUNIT);
|
||||
FV2_Divide(&offset, scale);
|
||||
for (i = -1; i < 2; i += 2)
|
||||
{
|
||||
P_SetTarget(&part->hprev, P_SpawnMobjFromMobj(base,
|
||||
P_ReturnThrustX(NULL, base->angle - ANGLE_90, i*offset.x) + P_ReturnThrustX(NULL, base->angle, offset.y),
|
||||
P_ReturnThrustY(NULL, base->angle - ANGLE_90, i*offset.x) + P_ReturnThrustY(NULL, base->angle, offset.y),
|
||||
0, MT_RINGSHOOTER_PART));
|
||||
P_SetTarget(&part->hprev->hnext, part);
|
||||
part = part->hprev;
|
||||
P_SetTarget(&part->target, base);
|
||||
|
||||
part->angle = base->angle - i * ANGLE_45;
|
||||
P_SetMobjState(part, S_RINGSHOOTER_NIPPLES);
|
||||
part->frame += frameNum;
|
||||
part->flags |= MF_NOTHINK;
|
||||
part->old_spriteyscale = part->spriteyscale = 0;
|
||||
frameNum++;
|
||||
}
|
||||
refNipple = part; // keep the second ring nipple; its position will be referenced by the box
|
||||
|
||||
// spawn the box
|
||||
part = base;
|
||||
frameNum = 0;
|
||||
angle = base->angle + ANGLE_90;
|
||||
FV2_Load(&offset, offset.x - info->radius, offset.y - info->radius); // set the new origin to the centerpoint of the box
|
||||
FV2_Load(&offset,
|
||||
P_ReturnThrustX(NULL, base->angle - ANGLE_90, offset.x) + P_ReturnThrustX(NULL, base->angle, offset.y),
|
||||
P_ReturnThrustY(NULL, base->angle - ANGLE_90, offset.x) + P_ReturnThrustY(NULL, base->angle, offset.y)); // transform it relative to the base
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
P_SetTarget(&part->hnext, P_SpawnMobjFromMobj(base,
|
||||
offset.x + P_ReturnThrustX(NULL, angle, info->radius),
|
||||
offset.y + P_ReturnThrustY(NULL, angle, info->radius),
|
||||
0, MT_RINGSHOOTER_PART));
|
||||
P_SetTarget(&part->hnext->hprev, part);
|
||||
part = part->hnext;
|
||||
P_SetTarget(&part->target, base);
|
||||
|
||||
if (i == 2)
|
||||
frameNum++;
|
||||
frameNum ^= FF_HORIZONTALFLIP;
|
||||
angle -= ANGLE_90;
|
||||
part->angle = angle;
|
||||
part->frame += frameNum;
|
||||
part->extravalue1 = part->x - refNipple->x;
|
||||
part->extravalue2 = part->y - refNipple->y;
|
||||
part->flags |= MF_NOTHINK;
|
||||
part->old_spriteyscale = part->spriteyscale = 0;
|
||||
}
|
||||
|
||||
// spawn the screen
|
||||
part = P_SpawnMobjFromMobj(base, offset.x, offset.y, 0, MT_RINGSHOOTER_SCREEN);
|
||||
P_SetTarget(&base->tracer, part);
|
||||
P_SetTarget(&part->target, base);
|
||||
part->angle = base->angle - ANGLE_45;
|
||||
part->extravalue1 = part->x - refNipple->x;
|
||||
part->extravalue2 = part->y - refNipple->y;
|
||||
part->flags |= MF_NOTHINK;
|
||||
part->old_spriteyscale = part->spriteyscale = 0;
|
||||
|
||||
// spawn the screen numbers
|
||||
for (i = 0; i < 2; i++)
|
||||
{
|
||||
P_SetTarget(&part->tracer, P_SpawnMobjFromMobj(part, 0, 0, 0, MT_OVERLAY));
|
||||
P_SetTarget(&part->tracer->target, part);
|
||||
part = part->tracer;
|
||||
part->angle = part->target->angle;
|
||||
P_SetMobjState(part, S_RINGSHOOTER_NUMBERBACK + i);
|
||||
part->renderflags |= RF_DONTDRAW;
|
||||
}
|
||||
|
||||
P_SetTarget(&part->hprev, base);
|
||||
|
||||
// spawn the grabbers
|
||||
part = base;
|
||||
angle = base->angle + ANGLE_45;
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
const fixed_t dis = GetTireDist(base);
|
||||
P_SetTarget(
|
||||
&part->target,
|
||||
P_SpawnMobjFromMobj(
|
||||
base,
|
||||
P_ReturnThrustX(NULL, angle, dis),
|
||||
P_ReturnThrustY(NULL, angle, dis),
|
||||
0,
|
||||
MT_TIREGRABBER
|
||||
)
|
||||
);
|
||||
part = part->target;
|
||||
P_SetTarget(&part->tracer, base);
|
||||
|
||||
angle -= ANGLE_90;
|
||||
part->angle = angle;
|
||||
part->extravalue1 = part->extravalue2 = 0;
|
||||
part->old_spriteyscale = part->spriteyscale = 0;
|
||||
}
|
||||
|
||||
ChangeRingShooterPointer(base, player);
|
||||
rs_base_playerface(base) = (player - players);
|
||||
}
|
||||
|
||||
static boolean AllowRingShooter(player_t *player)
|
||||
{
|
||||
const fixed_t minSpeed = 6 * player->mo->scale;
|
||||
|
||||
if (/*(gametyperules & GTR_CIRCUIT) &&*/ leveltime < starttime)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (player->respawn.state != RESPAWNST_NONE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (player->drift == 0
|
||||
&& player->justbumped == 0
|
||||
&& player->spindashboost == 0
|
||||
&& player->nocontrol == 0
|
||||
&& player->fastfall == 0
|
||||
&& player->speed < minSpeed
|
||||
&& P_PlayerInPain(player) == false
|
||||
&& P_IsObjectOnGround(player->mo) == true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean Obj_PlayerRingShooterFreeze(player_t *const player)
|
||||
{
|
||||
mobj_t *const base = player->ringShooter;
|
||||
|
||||
if (AllowRingShooter(player) == true
|
||||
&& (player->cmd.buttons & BT_RESPAWN) == BT_RESPAWN
|
||||
&& P_MobjWasRemoved(base) == false)
|
||||
{
|
||||
return (rs_base_canceled(base) == 0);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Obj_RingShooterInput(player_t *player)
|
||||
{
|
||||
mobj_t *const base = player->ringShooter;
|
||||
|
||||
if (AllowRingShooter(player) == true
|
||||
&& (player->cmd.buttons & BT_RESPAWN) == BT_RESPAWN)
|
||||
{
|
||||
if (P_MobjWasRemoved(base) == true)
|
||||
{
|
||||
SpawnRingShooter(player);
|
||||
return;
|
||||
}
|
||||
|
||||
if (rs_base_canceled(base) == 0)
|
||||
{
|
||||
player->mo->momx = player->mo->momy = 0;
|
||||
P_SetPlayerAngle(player, base->angle);
|
||||
fixed_t setz;
|
||||
|
||||
if (base->eflags & MFE_VERTICALFLIP)
|
||||
{
|
||||
setz = base->z + base->height - player->mo->height;
|
||||
setz = max(setz, player->mo->z);
|
||||
}
|
||||
else
|
||||
{
|
||||
setz = min(player->mo->z, base->z);
|
||||
}
|
||||
|
||||
P_MoveOrigin(
|
||||
player->mo,
|
||||
base->x, base->y,
|
||||
setz
|
||||
);
|
||||
player->fastfall = 0;
|
||||
|
||||
if (base->fuse < RS_FUSE_TIME)
|
||||
{
|
||||
if (base->fuse < RS_FUSE_BLINK)
|
||||
{
|
||||
base->renderflags &= ~RF_DONTDRAW;
|
||||
UpdateRingShooterPartsVisibility(base);
|
||||
}
|
||||
|
||||
base->fuse = RS_FUSE_TIME;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (P_MobjWasRemoved(base) == false)
|
||||
{
|
||||
if (rs_base_initstate(base) != -1)
|
||||
{
|
||||
// We released during the intro animation.
|
||||
// Cancel it entirely, prevent another one being created for a bit.
|
||||
rs_base_canceled(base) = 1;
|
||||
|
||||
if (base->fuse > RS_FUSE_BLINK)
|
||||
{
|
||||
base->fuse = RS_FUSE_BLINK;
|
||||
}
|
||||
}
|
||||
else if (rs_base_canceled(base) == 0)
|
||||
{
|
||||
// We released during the countdown.
|
||||
// We activate with the current karted timer on the ring shooter.
|
||||
Obj_PlayerUsedRingShooter(base, player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Obj_UpdateRingShooterFace(mobj_t *part)
|
||||
{
|
||||
mobj_t *const base = part->hprev;
|
||||
player_t *player = NULL;
|
||||
|
||||
if (P_MobjWasRemoved(base) == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (rs_base_playerface(base) < 0 || rs_base_playerface(base) >= MAXPLAYERS)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (playeringame[ rs_base_playerface(base) ] == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
player = &players[ rs_base_playerface(base) ];
|
||||
|
||||
// it's a good idea to set the actor's skin *before* it uses this action,
|
||||
// but just in case, if it doesn't have the player's skin, set its skin then call the state again to get the correct sprite
|
||||
if (part->skin != &skins[player->skin])
|
||||
{
|
||||
part->skin = &skins[player->skin];
|
||||
P_SetMobjState(part, (statenum_t)(part->state - states));
|
||||
return;
|
||||
}
|
||||
|
||||
// okay, now steal the player's color nyehehehe
|
||||
part->color = player->skincolor;
|
||||
|
||||
// set the frame to the WANTED pic
|
||||
part->frame = (part->frame & ~FF_FRAMEMASK) | FACE_WANTED;
|
||||
|
||||
// set the threshold overlay flags
|
||||
part->threshold = (OV_DONTXYSCALE|OV_DONTSCREENOFFSET);
|
||||
|
||||
// we're going to assume the character's WANTED icon is 32 x 32
|
||||
// let's squish the sprite a bit so that it matches the dimensions of the screen's sprite, which is 26 x 22
|
||||
// (TODO: maybe get the dimensions/offsets from the patches themselves?)
|
||||
part->spritexscale = FixedDiv(26*FRACUNIT, 32*FRACUNIT);
|
||||
part->spriteyscale = FixedDiv(22*FRACUNIT, 32*FRACUNIT);
|
||||
|
||||
// a normal WANTED icon should have (0, 0) offsets
|
||||
// so let's offset it such that it will match the position of the screen's sprite
|
||||
part->spritexoffset = 16*FRACUNIT; // 32 / 2
|
||||
part->spriteyoffset = 28*FRACUNIT + FixedDiv(11*FRACUNIT, part->spriteyscale); // 32 - 4 (generic monster bottom) + 11 (vertical offset of screen sprite from the bottom)
|
||||
}
|
||||
|
|
@ -330,6 +330,7 @@ void A_MementosTPParticles(mobj_t *actor);
|
|||
void A_FlameShieldPaper(mobj_t *actor);
|
||||
void A_InvincSparkleRotate(mobj_t *actor);
|
||||
void A_SpawnItemDebrisCloud(mobj_t *actor);
|
||||
void A_RingShooterFace(mobj_t *actor);
|
||||
|
||||
//for p_enemy.c
|
||||
|
||||
|
|
@ -13810,3 +13811,15 @@ A_SpawnItemDebrisCloud (mobj_t *actor)
|
|||
puff->momz += FixedMul(target->momz, fade);
|
||||
}
|
||||
}
|
||||
|
||||
// sets the actor's
|
||||
// vars do nothing
|
||||
void A_RingShooterFace(mobj_t *actor)
|
||||
{
|
||||
if (LUA_CallAction(A_RINGSHOOTERFACE, actor))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Obj_UpdateRingShooterFace(actor);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -580,6 +580,10 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
|
|||
Obj_LoopEndpointCollide(special, toucher);
|
||||
return;
|
||||
|
||||
case MT_RINGSHOOTER:
|
||||
Obj_PlayerUsedRingShooter(special, player);
|
||||
return;
|
||||
|
||||
default: // SOC or script pickup
|
||||
P_SetTarget(&special->target, toucher);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -2232,7 +2232,8 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y, TryMoveResult_t *re
|
|||
// with MF_NOCLIP enabled, but they won't be blocked
|
||||
// regardless of the result. This allows for SPBs and
|
||||
// the UFO to collide.
|
||||
return true;
|
||||
// ...but be careful about removed obj! ~toast 140423
|
||||
return !P_MobjWasRemoved(thing);
|
||||
}
|
||||
|
||||
validcount++;
|
||||
|
|
|
|||
91
src/p_mobj.c
91
src/p_mobj.c
|
|
@ -1878,7 +1878,7 @@ void P_XYMovement(mobj_t *mo)
|
|||
FIXED_TO_FLOAT(AngleFixed(oldangle-newangle))
|
||||
);
|
||||
*/
|
||||
|
||||
|
||||
}
|
||||
else if (predictedz - mo->z > abs(slopemom.z / 2))
|
||||
{
|
||||
|
|
@ -6641,6 +6641,12 @@ static void P_MobjSceneryThink(mobj_t *mobj)
|
|||
mobj->momz = newz - mobj->z;
|
||||
}
|
||||
break;
|
||||
case MT_RINGSHOOTER:
|
||||
if (Obj_RingShooterThinker(mobj) == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case MT_SPINDASHWIND:
|
||||
case MT_DRIFTELECTRICSPARK:
|
||||
mobj->renderflags ^= RF_DONTDRAW;
|
||||
|
|
@ -10399,7 +10405,7 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
|
|||
|
||||
if (type == MT_NULL)
|
||||
{
|
||||
#if 0
|
||||
#if 0
|
||||
#ifdef PARANOIA
|
||||
I_Error("Tried to spawn MT_NULL\n");
|
||||
#endif
|
||||
|
|
@ -11140,26 +11146,10 @@ void P_RemoveMobj(mobj_t *mobj)
|
|||
iquetail = (iquetail+1)&(ITEMQUESIZE-1);
|
||||
}
|
||||
|
||||
if (mobj->type == MT_KARMAHITBOX) // Remove linked list objects for certain types
|
||||
{
|
||||
mobj_t *cur = mobj->hnext;
|
||||
|
||||
while (cur && !P_MobjWasRemoved(cur))
|
||||
{
|
||||
mobj_t *prev = cur; // Kind of a dumb var, but we need to set cur before we remove the mobj
|
||||
cur = cur->hnext;
|
||||
P_RemoveMobj(prev);
|
||||
}
|
||||
}
|
||||
|
||||
if (mobj->type == MT_OVERLAY)
|
||||
P_RemoveOverlay(mobj);
|
||||
|
||||
if (mobj->type == MT_SPB)
|
||||
spbplace = -1;
|
||||
|
||||
if (P_IsTrackerType(mobj->type))
|
||||
{
|
||||
P_RemoveTracker(mobj);
|
||||
}
|
||||
|
||||
if (mobj->player && mobj->player->followmobj)
|
||||
{
|
||||
|
|
@ -11167,19 +11157,56 @@ void P_RemoveMobj(mobj_t *mobj)
|
|||
P_SetTarget(&mobj->player->followmobj, NULL);
|
||||
}
|
||||
|
||||
if (mobj->type == MT_SHRINK_POHBEE)
|
||||
// Remove linked list objects for certain types
|
||||
switch (mobj->type)
|
||||
{
|
||||
Obj_PohbeeRemoved(mobj);
|
||||
}
|
||||
case MT_KARMAHITBOX:
|
||||
{
|
||||
mobj_t *cur = mobj->hnext;
|
||||
|
||||
if (mobj->type == MT_SHRINK_GUN)
|
||||
{
|
||||
Obj_ShrinkGunRemoved(mobj);
|
||||
}
|
||||
while (cur && !P_MobjWasRemoved(cur))
|
||||
{
|
||||
mobj_t *prev = cur; // Kind of a dumb var, but we need to set cur before we remove the mobj
|
||||
cur = cur->hnext;
|
||||
P_RemoveMobj(prev);
|
||||
}
|
||||
|
||||
if (mobj->type == MT_SPECIAL_UFO_PIECE)
|
||||
{
|
||||
Obj_UFOPieceRemoved(mobj);
|
||||
break;
|
||||
}
|
||||
case MT_OVERLAY:
|
||||
{
|
||||
P_RemoveOverlay(mobj);
|
||||
break;
|
||||
}
|
||||
case MT_SPB:
|
||||
{
|
||||
spbplace = -1;
|
||||
break;
|
||||
}
|
||||
case MT_SHRINK_POHBEE:
|
||||
{
|
||||
Obj_PohbeeRemoved(mobj);
|
||||
break;
|
||||
}
|
||||
case MT_SHRINK_GUN:
|
||||
{
|
||||
Obj_ShrinkGunRemoved(mobj);
|
||||
break;
|
||||
}
|
||||
case MT_SPECIAL_UFO_PIECE:
|
||||
{
|
||||
Obj_UFOPieceRemoved(mobj);
|
||||
break;
|
||||
}
|
||||
case MT_RINGSHOOTER:
|
||||
{
|
||||
Obj_RingShooterDelete(mobj);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mobj->health = 0; // Just because
|
||||
|
|
@ -13143,8 +13170,8 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
|
|||
}
|
||||
case MT_RANDOMITEM:
|
||||
{
|
||||
boolean delayed = !(gametyperules & GTR_CIRCUIT);
|
||||
if (leveltime < (delayed ? starttime : 3))
|
||||
const boolean delayed = !(gametyperules & GTR_CIRCUIT);
|
||||
if (leveltime == 0)
|
||||
{
|
||||
mobj->flags2 |= MF2_BOSSNOTRAP; // mark as here on map start
|
||||
if (delayed)
|
||||
|
|
|
|||
|
|
@ -64,14 +64,15 @@ savedata_t savedata;
|
|||
// than an UINT16
|
||||
typedef enum
|
||||
{
|
||||
AWAYVIEW = 0x01,
|
||||
FOLLOWITEM = 0x02,
|
||||
FOLLOWER = 0x04,
|
||||
SKYBOXVIEW = 0x08,
|
||||
SKYBOXCENTER = 0x10,
|
||||
HOVERHYUDORO = 0x20,
|
||||
STUMBLE = 0x40,
|
||||
SLIPTIDEZIP = 0x80
|
||||
AWAYVIEW = 0x0001,
|
||||
FOLLOWITEM = 0x0002,
|
||||
FOLLOWER = 0x0004,
|
||||
SKYBOXVIEW = 0x0008,
|
||||
SKYBOXCENTER = 0x0010,
|
||||
HOVERHYUDORO = 0x0020,
|
||||
STUMBLE = 0x0040,
|
||||
SLIPTIDEZIP = 0x0080,
|
||||
RINGSHOOTER = 0x0100
|
||||
} player_saveflags;
|
||||
|
||||
static inline void P_ArchivePlayer(savebuffer_t *save)
|
||||
|
|
@ -218,6 +219,9 @@ static void P_NetArchivePlayers(savebuffer_t *save)
|
|||
if (players[i].sliptideZipIndicator)
|
||||
flags |= SLIPTIDEZIP;
|
||||
|
||||
if (players[i].ringShooter)
|
||||
flags |= RINGSHOOTER;
|
||||
|
||||
WRITEUINT16(save->p, flags);
|
||||
|
||||
if (flags & SKYBOXVIEW)
|
||||
|
|
@ -241,6 +245,9 @@ static void P_NetArchivePlayers(savebuffer_t *save)
|
|||
if (flags & SLIPTIDEZIP)
|
||||
WRITEUINT32(save->p, players[i].sliptideZipIndicator->mobjnum);
|
||||
|
||||
if (flags & RINGSHOOTER)
|
||||
WRITEUINT32(save->p, players[i].ringShooter->mobjnum);
|
||||
|
||||
WRITEUINT32(save->p, (UINT32)players[i].followitem);
|
||||
|
||||
WRITEUINT32(save->p, players[i].charflags);
|
||||
|
|
@ -614,6 +621,9 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
|
|||
if (flags & SLIPTIDEZIP)
|
||||
players[i].sliptideZipIndicator = (mobj_t *)(size_t)READUINT32(save->p);
|
||||
|
||||
if (flags & RINGSHOOTER)
|
||||
players[i].ringShooter = (mobj_t *)(size_t)READUINT32(save->p);
|
||||
|
||||
players[i].followitem = (mobjtype_t)READUINT32(save->p);
|
||||
|
||||
//SetPlayerSkinByNum(i, players[i].skin);
|
||||
|
|
@ -4380,6 +4390,8 @@ static void P_NetUnArchiveThinkers(savebuffer_t *save)
|
|||
{
|
||||
next = currentthinker->next;
|
||||
|
||||
currentthinker->references = 0; // Heinous but this is the only place the assertion in P_UnlinkThinkers is wrong
|
||||
|
||||
if (currentthinker->function.acp1 == (actionf_p1)P_MobjThinker || currentthinker->function.acp1 == (actionf_p1)P_NullPrecipThinker)
|
||||
P_RemoveSavegameMobj((mobj_t *)currentthinker); // item isn't saved, don't remove it
|
||||
else
|
||||
|
|
@ -4864,6 +4876,13 @@ static void P_RelinkPointers(void)
|
|||
if (!P_SetTarget(&players[i].sliptideZipIndicator, P_FindNewPosition(temp)))
|
||||
CONS_Debug(DBG_GAMELOGIC, "sliptideZipIndicator not found on player %d\n", i);
|
||||
}
|
||||
if (players[i].ringShooter)
|
||||
{
|
||||
temp = (UINT32)(size_t)players[i].ringShooter;
|
||||
players[i].ringShooter = NULL;
|
||||
if (!P_SetTarget(&players[i].ringShooter, P_FindNewPosition(temp)))
|
||||
CONS_Debug(DBG_GAMELOGIC, "ringShooter not found on player %d\n", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -782,7 +782,8 @@ void P_Ticker(boolean run)
|
|||
low = 65536 / (3+player->numsneakers);
|
||||
high = 65536 / (3+player->numsneakers);
|
||||
}
|
||||
else if (player->boostpower < FRACUNIT && P_IsObjectOnGround(player->mo))
|
||||
else if (((player->boostpower < FRACUNIT) || (player->stairjank > 8))
|
||||
&& P_IsObjectOnGround(player->mo))
|
||||
{
|
||||
low = 65536 / 32;
|
||||
high = 65536 / 32;
|
||||
|
|
|
|||
30
src/p_user.c
30
src/p_user.c
|
|
@ -62,6 +62,7 @@
|
|||
#include "k_rank.h"
|
||||
#include "k_director.h"
|
||||
#include "g_party.h"
|
||||
#include "k_profiles.h"
|
||||
|
||||
#ifdef HW3SOUND
|
||||
#include "hardware/hw3sound.h"
|
||||
|
|
@ -1378,6 +1379,13 @@ void P_DoPlayerExit(player_t *player)
|
|||
if (modeattacking)
|
||||
G_UpdateRecords();
|
||||
|
||||
profile_t *pr = PR_GetPlayerProfile(player);
|
||||
if (pr != NULL && !losing)
|
||||
{
|
||||
pr->wins++;
|
||||
PR_SaveProfiles();
|
||||
}
|
||||
|
||||
player->karthud[khud_cardanimation] = 0; // srb2kart: reset battle animation
|
||||
|
||||
if (player == &players[consoleplayer])
|
||||
|
|
@ -2235,16 +2243,30 @@ static void P_UpdatePlayerAngle(player_t *player)
|
|||
angle_t leniency = (4*ANG1/3) * min(player->cmd.latency, 6);
|
||||
// Don't force another turning tic, just give them the desired angle!
|
||||
|
||||
if (targetDelta == angleChange || K_Sliptiding(player) || (maxTurnRight == 0 && maxTurnLeft == 0))
|
||||
if (targetDelta == angleChange || (maxTurnRight == 0 && maxTurnLeft == 0))
|
||||
{
|
||||
// Either we're dead on, we can't steer, or we're in a special handling state.
|
||||
// Stuff like sliptiding requires some blind-faith steering:
|
||||
// if a camera correction stops our turn input, the sliptide randomly fails!
|
||||
// Either we're dead on or we can't steer at all.
|
||||
player->steering = targetsteering;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We're off. Try to legally steer the player towards their camera.
|
||||
|
||||
if (K_Sliptiding(player) && P_IsObjectOnGround(player->mo) && (player->cmd.turning != 0) && ((player->cmd.turning > 0) == (player->aizdriftstrat > 0)))
|
||||
{
|
||||
// Don't change handling direction if someone's inputs are sliptiding, you'll break the sliptide!
|
||||
if (player->cmd.turning > 0)
|
||||
{
|
||||
steeringLeft = max(steeringLeft, 1);
|
||||
steeringRight = max(steeringRight, steeringLeft);
|
||||
}
|
||||
else
|
||||
{
|
||||
steeringRight = min(steeringRight, -1);
|
||||
steeringLeft = min(steeringLeft, steeringRight);
|
||||
}
|
||||
}
|
||||
|
||||
player->steering = P_FindClosestTurningForAngle(player, targetDelta, steeringLeft, steeringRight);
|
||||
angleChange = K_GetKartTurnValue(player, player->steering) << TICCMD_REDUCE;
|
||||
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@ patch_t *Patch_Create(softwarepatch_t *source, size_t srcsize, void *dest)
|
|||
return patch;
|
||||
}
|
||||
|
||||
static boolean g_patch_was_freed_this_frame = false;
|
||||
|
||||
//
|
||||
// Frees a patch from memory.
|
||||
//
|
||||
|
|
@ -97,6 +99,8 @@ static void Patch_FreeData(patch_t *patch)
|
|||
|
||||
Z_Free(patch->columnofs);
|
||||
Z_Free(patch->columns);
|
||||
|
||||
g_patch_was_freed_this_frame = true;
|
||||
}
|
||||
|
||||
void Patch_Free(patch_t *patch)
|
||||
|
|
@ -108,6 +112,16 @@ void Patch_Free(patch_t *patch)
|
|||
Z_Free(patch);
|
||||
}
|
||||
|
||||
boolean Patch_WasFreedThisFrame(void)
|
||||
{
|
||||
return g_patch_was_freed_this_frame;
|
||||
}
|
||||
|
||||
void Patch_ResetFreedThisFrame(void)
|
||||
{
|
||||
g_patch_was_freed_this_frame = false;
|
||||
}
|
||||
|
||||
//
|
||||
// Frees patches with a tag range.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ extern "C" {
|
|||
// Patch functions
|
||||
patch_t *Patch_Create(softwarepatch_t *source, size_t srcsize, void *dest);
|
||||
void Patch_Free(patch_t *patch);
|
||||
boolean Patch_WasFreedThisFrame(void);
|
||||
void Patch_ResetFreedThisFrame(void);
|
||||
|
||||
#define Patch_FreeTag(tagnum) Patch_FreeTags(tagnum, tagnum)
|
||||
void Patch_FreeTags(INT32 lowtag, INT32 hightag);
|
||||
|
|
|
|||
|
|
@ -1759,6 +1759,14 @@ Rect GlCoreRhi::get_renderbuffer_size(Handle<Renderbuffer> renderbuffer)
|
|||
return ret;
|
||||
}
|
||||
|
||||
uint32_t GlCoreRhi::get_buffer_size(Handle<Buffer> buffer)
|
||||
{
|
||||
SRB2_ASSERT(buffer_slab_.is_valid(buffer));
|
||||
auto& buf = buffer_slab_[buffer];
|
||||
|
||||
return buf.desc.size;
|
||||
}
|
||||
|
||||
void GlCoreRhi::finish()
|
||||
{
|
||||
SRB2_ASSERT(graphics_context_active_ == false);
|
||||
|
|
|
|||
|
|
@ -184,6 +184,7 @@ public:
|
|||
|
||||
virtual TextureDetails get_texture_details(Handle<Texture> texture) override;
|
||||
virtual Rect get_renderbuffer_size(Handle<Renderbuffer> renderbuffer) override;
|
||||
virtual uint32_t get_buffer_size(Handle<Buffer> buffer) override;
|
||||
|
||||
virtual Handle<TransferContext> begin_transfer() override;
|
||||
virtual void end_transfer(Handle<TransferContext> handle) override;
|
||||
|
|
|
|||
|
|
@ -83,3 +83,28 @@ const ProgramRequirements& rhi::program_requirements_for_program(PipelineProgram
|
|||
std::terminate();
|
||||
}
|
||||
}
|
||||
|
||||
bool rhi::recreate_buffer_to_size(Rhi& rhi, Handle<Buffer>& buffer, const BufferDesc& desc)
|
||||
{
|
||||
bool recreate = false;
|
||||
if (buffer == kNullHandle)
|
||||
{
|
||||
recreate = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::size_t existing_size = rhi.get_buffer_size(buffer);
|
||||
if (existing_size < desc.size)
|
||||
{
|
||||
rhi.destroy_buffer(buffer);
|
||||
recreate = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (recreate)
|
||||
{
|
||||
buffer = rhi.create_buffer(desc);
|
||||
}
|
||||
|
||||
return recreate;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -608,6 +608,7 @@ struct Rhi
|
|||
|
||||
virtual TextureDetails get_texture_details(Handle<Texture> texture) = 0;
|
||||
virtual Rect get_renderbuffer_size(Handle<Renderbuffer> renderbuffer) = 0;
|
||||
virtual uint32_t get_buffer_size(Handle<Buffer> buffer) = 0;
|
||||
|
||||
virtual Handle<TransferContext> begin_transfer() = 0;
|
||||
virtual void end_transfer(Handle<TransferContext> handle) = 0;
|
||||
|
|
@ -653,6 +654,17 @@ struct Rhi
|
|||
virtual void finish() = 0;
|
||||
};
|
||||
|
||||
// Utility functions
|
||||
|
||||
/// @brief If the buffer for the given handle is too small or does not exist, creates a new buffer with the given
|
||||
/// parameters.
|
||||
/// @param buffer the existing valid buffer handle or kNullHandle, replaced if recreated
|
||||
/// @param type
|
||||
/// @param usage
|
||||
/// @param size the target size of the new buffer
|
||||
/// @return true if the buffer was recreated, false otherwise
|
||||
bool recreate_buffer_to_size(Rhi& rhi, Handle<Buffer>& buffer, const BufferDesc& desc);
|
||||
|
||||
} // namespace srb2::rhi
|
||||
|
||||
#endif // __SRB2_RHI_RHI_HPP__
|
||||
|
|
|
|||
|
|
@ -268,6 +268,96 @@ static void write_backtrace(INT32 signal)
|
|||
#undef CRASHLOG_STDERR_WRITE
|
||||
#endif // UNIXBACKTRACE
|
||||
|
||||
static void I_ShowErrorMessageBox(const char *messagefordevelopers, boolean dumpmade)
|
||||
{
|
||||
static char finalmessage[2048];
|
||||
size_t firstimpressionsline = 3; // "Dr Robotnik's Ring Racers" has encountered...
|
||||
|
||||
if (M_CheckParm("-dedicated"))
|
||||
return;
|
||||
|
||||
snprintf(
|
||||
finalmessage,
|
||||
sizeof(finalmessage),
|
||||
"Hee Ho!\n"
|
||||
"\n"
|
||||
"\"Dr. Robotnik's Ring Racers\" has encountered an unrecoverable error and needs to close.\n"
|
||||
"This is (usually) not your fault, but we encourage you to report it in the community. This should be done alongside your "
|
||||
"%s"
|
||||
"log file (%s).\n"
|
||||
"\n"
|
||||
"The following information is for a programmer (please be nice to them!) but\n"
|
||||
"may also be useful for server hosts and add-on creators.\n"
|
||||
"\n"
|
||||
"%s",
|
||||
dumpmade ?
|
||||
#if defined (UNIXBACKTRACE)
|
||||
"crash-log.txt"
|
||||
#elif defined (_WIN32)
|
||||
".rpt crash dump"
|
||||
#endif
|
||||
" (very important!) and " : "",
|
||||
#ifdef LOGMESSAGES
|
||||
logfilename[0] ? logfilename :
|
||||
#endif
|
||||
"uh oh, one wasn't made!?",
|
||||
messagefordevelopers);
|
||||
|
||||
// Rudementary word wrapping.
|
||||
// Simple and effective. Does not handle nonuniform letter sizes, etc. but who cares.
|
||||
{
|
||||
size_t max = 0, maxatstart = 0, start = 0, width = 0, i;
|
||||
|
||||
for (i = 0; finalmessage[i]; i++)
|
||||
{
|
||||
if (finalmessage[i] == ' ')
|
||||
{
|
||||
start = i;
|
||||
max += 4;
|
||||
maxatstart = max;
|
||||
}
|
||||
else if (finalmessage[i] == '\n')
|
||||
{
|
||||
if (firstimpressionsline > 0)
|
||||
{
|
||||
firstimpressionsline--;
|
||||
if (firstimpressionsline == 0)
|
||||
{
|
||||
width = max;
|
||||
}
|
||||
}
|
||||
start = 0;
|
||||
max = 0;
|
||||
maxatstart = 0;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
max += 8;
|
||||
|
||||
// Start trying to wrap if presumed length exceeds the space we want.
|
||||
if (width > 0 && max >= width && start > 0)
|
||||
{
|
||||
finalmessage[start] = '\n';
|
||||
max -= maxatstart;
|
||||
start = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Implement message box with SDL_ShowSimpleMessageBox,
|
||||
// which should fail gracefully if it can't put a message box up
|
||||
// on the target system
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
|
||||
"Dr. Robotnik's Ring Racers "VERSIONSTRING" Error",
|
||||
finalmessage, NULL);
|
||||
|
||||
// Note that SDL_ShowSimpleMessageBox does *not* require SDL to be
|
||||
// initialized at the time, so calling it after SDL_Quit() is
|
||||
// perfectly okay! In addition, we do this on purpose so the
|
||||
// fullscreen window is closed before displaying the error message
|
||||
// in case the fullscreen window blocks it for some absurd reason.
|
||||
}
|
||||
|
||||
static void I_ReportSignal(int num, int coredumped)
|
||||
{
|
||||
//static char msg[] = "oh no! back to reality!\r\n";
|
||||
|
|
@ -317,10 +407,15 @@ static void I_ReportSignal(int num, int coredumped)
|
|||
|
||||
I_OutputMsg("\nProcess killed by signal: %s\n\n", sigmsg);
|
||||
|
||||
if (!M_CheckParm("-dedicated"))
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
|
||||
"Process killed by signal",
|
||||
sigmsg, NULL);
|
||||
I_ShowErrorMessageBox(sigmsg,
|
||||
#if defined (UNIXBACKTRACE)
|
||||
true
|
||||
#elif defined (_WIN32)
|
||||
!M_CheckParm("-noexchndl")
|
||||
#else
|
||||
false
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
#ifndef NEWSIGNALHANDLER
|
||||
|
|
@ -1712,13 +1807,7 @@ void I_Error(const char *error, ...)
|
|||
I_ShutdownGraphics();
|
||||
I_ShutdownInput();
|
||||
|
||||
// Implement message box with SDL_ShowSimpleMessageBox,
|
||||
// which should fail gracefully if it can't put a message box up
|
||||
// on the target system
|
||||
if (!M_CheckParm("-dedicated"))
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
|
||||
"Dr. Robotnik's Ring Racers "VERSIONSTRING" Error",
|
||||
buffer, NULL);
|
||||
I_ShowErrorMessageBox(buffer, false);
|
||||
|
||||
// We wait until now to do this so the funny sound can be heard
|
||||
I_ShutdownSound();
|
||||
|
|
@ -1726,12 +1815,6 @@ void I_Error(const char *error, ...)
|
|||
I_ShutdownSystem();
|
||||
SDL_Quit();
|
||||
|
||||
// Note that SDL_ShowSimpleMessageBox does *not* require SDL to be
|
||||
// initialized at the time, so calling it after SDL_Quit() is
|
||||
// perfectly okay! In addition, we do this on purpose so the
|
||||
// fullscreen window is closed before displaying the error message
|
||||
// in case the fullscreen window blocks it for some absurd reason.
|
||||
|
||||
W_Shutdown();
|
||||
|
||||
#if defined (PARANOIA) || defined (DEVELOP)
|
||||
|
|
|
|||
|
|
@ -571,6 +571,7 @@ static void Impl_HandleKeyboardEvent(SDL_KeyboardEvent evt, Uint32 type)
|
|||
return;
|
||||
}
|
||||
event.data1 = Impl_SDL_Scancode_To_Keycode(evt.keysym.scancode);
|
||||
event.data2 = evt.repeat;
|
||||
if (event.data1) D_PostEvent(&event);
|
||||
}
|
||||
|
||||
|
|
@ -746,6 +747,7 @@ static void Impl_HandleControllerButtonEvent(SDL_ControllerButtonEvent evt, Uint
|
|||
}
|
||||
|
||||
event.data1 = KEY_JOY1;
|
||||
event.data2 = 0;
|
||||
|
||||
if (type == SDL_CONTROLLERBUTTONUP)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -196,6 +196,9 @@ TYPEDEF (pathfindsetup_t);
|
|||
// k_profiles.h
|
||||
TYPEDEF (profile_t);
|
||||
|
||||
// h_serverstats.h
|
||||
TYPEDEF (serverplayer_t);
|
||||
|
||||
// k_terrain.h
|
||||
TYPEDEF (t_splash_t);
|
||||
TYPEDEF (t_footstep_t);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue