From 44b642ffa38bdc851a3d805e8751deb9f7e2eb55 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Mon, 10 Apr 2023 22:39:17 -0700 Subject: [PATCH 01/65] WIP: improved countersteering --- src/k_kart.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index c06b15bd7..5fcaa2ca1 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -8854,10 +8854,17 @@ 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; + + if ((inputSteering > 0 && destSteering < inputSteering) || (inputSteering < 0 && destSteering > inputSteering)) + { + amount = KART_FULLTURN; + } + + if (abs(diff) <= amount) { // Reached the intended value, set instantly. From 94cf9fc47bc0c15bcdadf1e0f99a83d1e6e5070e Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Mon, 10 Apr 2023 23:15:23 -0700 Subject: [PATCH 02/65] WIP: don't oversteer when crossing 0 boundary --- src/k_kart.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 5fcaa2ca1..cdce66665 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -8859,9 +8859,9 @@ INT16 K_UpdateSteeringValue(INT16 inputSteering, INT16 destSteering) INT16 outputSteering = inputSteering; - if ((inputSteering > 0 && destSteering < inputSteering) || (inputSteering < 0 && destSteering > inputSteering)) + if ((inputSteering > 0 && destSteering < 0) || (inputSteering < 0 && destSteering > 0)) { - amount = KART_FULLTURN; + amount = max(min(KART_FULLTURN, abs(inputSteering)), amount); } From b4ffc33fcb56034b339cb65603ad30c43203fda2 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Tue, 11 Apr 2023 00:15:44 -0700 Subject: [PATCH 03/65] Sliptide-aware camera steering, fix countersteer easing --- src/k_kart.c | 6 +++++- src/p_user.c | 22 ++++++++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index cdce66665..43ebdea3a 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -8859,9 +8859,13 @@ INT16 K_UpdateSteeringValue(INT16 inputSteering, INT16 destSteering) 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)) { - amount = max(min(KART_FULLTURN, abs(inputSteering)), amount); + // 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. } diff --git a/src/p_user.c b/src/p_user.c index e05d1a0f7..b48dc206d 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2235,16 +2235,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) + { + steeringRight = max(steeringRight, max(steeringLeft, 1)); + steeringLeft = max(steeringLeft, 1); + } + else + { + steeringLeft = min(steeringLeft, max(steeringRight, -1)); + steeringRight = min(steeringLeft, -1); + } + } + player->steering = P_FindClosestTurningForAngle(player, targetDelta, steeringLeft, steeringRight); angleChange = K_GetKartTurnValue(player, player->steering) << TICCMD_REDUCE; From b9133530e2ed52ae5100f1e07091e5f4b2880049 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 11 Apr 2023 17:39:53 +0100 Subject: [PATCH 04/65] Obj_OrbinautJawzCollide: Guard for removed objects --- src/objects/orbinaut.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/objects/orbinaut.c b/src/objects/orbinaut.c index caed22068..f7c2ac89a 100644 --- a/src/objects/orbinaut.c +++ b/src/objects/orbinaut.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); } From c7e510a3ece25ec0558c2da05b58a3be321406c4 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 11 Apr 2023 17:41:41 +0100 Subject: [PATCH 05/65] k_collide.cpp: Guard against dereferencing possibly invalid t2 --- src/k_collide.cpp | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/k_collide.cpp b/src/k_collide.cpp index 671f3d19a..9bb2283be 100644 --- a/src/k_collide.cpp +++ b/src/k_collide.cpp @@ -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); } From 134a5ef9c0bb7624111bfb3dd2594de23a17fbd0 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Sat, 1 Apr 2023 22:08:21 -0700 Subject: [PATCH 06/65] WIP --- src/CMakeLists.txt | 1 + src/d_clisrv.c | 13 +++- src/d_main.c | 5 ++ src/d_netcmd.c | 6 ++ src/g_game.c | 3 + src/k_pwrlv.c | 16 +--- src/k_serverstats.c | 179 ++++++++++++++++++++++++++++++++++++++++++++ src/k_serverstats.h | 49 ++++++++++++ src/typedef.h | 3 + 9 files changed, 261 insertions(+), 14 deletions(-) create mode 100644 src/k_serverstats.c create mode 100644 src/k_serverstats.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 21ef13ce9..f92be390e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 37bb6323b..f1ff9cf96 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -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" @@ -2972,12 +2973,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)")); @@ -3910,6 +3917,8 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum) HU_AddChatText(joinmsg, false); } + SV_RetrieveStats(newplayernum); + if (server && multiplayer && motd[0] != '\0') COM_BufAddText(va("sayto %d %s\n", newplayernum, motd)); diff --git a/src/d_main.c b/src/d_main.c index c96ff0dc9..2821d7686 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -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 @@ -1619,6 +1620,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; diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 27cdc9042..c1fa1b7d0 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -1809,6 +1809,9 @@ static void Got_WeaponPref(UINT8 **cp,INT32 playernum) static void Got_PowerLevel(UINT8 **cp,INT32 playernum) { + // Server keeps track of this now, no-sell XD_POWERLEVEL + + /* UINT16 race = (UINT16)READUINT16(*cp); UINT16 battle = (UINT16)READUINT16(*cp); @@ -1816,6 +1819,7 @@ static void Got_PowerLevel(UINT8 **cp,INT32 playernum) 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) @@ -1977,6 +1981,7 @@ void D_SendPlayerConfig(UINT8 n) SendNameAndColor(n); WeaponPref_Send(n); + /* if (pr != NULL) { // Send it over @@ -1991,6 +1996,7 @@ void D_SendPlayerConfig(UINT8 n) } SendNetXCmdForPlayer(n, XD_POWERLEVEL, buf, p-buf); + */ } void D_Cheat(INT32 playernum, INT32 cheat, ...) diff --git a/src/g_game.c b/src/g_game.c index f5173e642..7a767bdf7 100644 --- a/src/g_game.c +++ b/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" @@ -1580,6 +1581,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) diff --git a/src/k_pwrlv.c b/src/k_pwrlv.c index 0f234a504..5b1611d7b 100644 --- a/src/k_pwrlv.c +++ b/src/k_pwrlv.c @@ -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. @@ -636,18 +637,9 @@ 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(); + CONS_Printf("Stats update by %d\n", inc); + SV_UpdateStats(); } } diff --git a/src/k_serverstats.c b/src/k_serverstats.c new file mode 100644 index 000000000..49541e315 --- /dev/null +++ b/src/k_serverstats.c @@ -0,0 +1,179 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 1998-2000 by DooM Legacy Team. +// Copyright (C) 1999-2020 by Sonic Team Junior. +// +// 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" + +static serverplayer_t trackedList[MAXTRACKEDSERVERPLAYERS]; +static UINT32 numtracked = 0; + +// Read stats file to trackedList for ingame use +void SV_LoadStats(void) +{ + const size_t headerlen = strlen(SERVERSTATSHEADER); + savebuffer_t save = {0}; + + if (!server) + return; + + if (P_SaveBufferFromFile(&save, va(pandf, srb2home, SERVERSTATSFILE)) == false) + { + return; + } + + 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; + + numtracked = READUINT32(save.p); + if (numtracked > MAXTRACKEDSERVERPLAYERS) + numtracked = MAXTRACKEDSERVERPLAYERS; + + READMEM(save.p, trackedList, (numtracked * sizeof(serverplayer_t))); +} + +// Save trackedList to disc +void SV_SaveStats(void) +{ + size_t length = 0; + const size_t headerlen = strlen(SERVERSTATSHEADER); + UINT8 i; + savebuffer_t save = {0}; + + if (!server) + return; + + /* + if (profilesList[PROFILE_GUEST] == NULL) + { + // Profiles have not been loaded yet, don't overwrite with garbage. + return; + } + */ + + if (P_SaveBufferAlloc(&save, headerlen + sizeof(UINT32) + (numtracked * sizeof(serverplayer_t))) == false) + { + I_Error("No more free memory for saving server stats\n"); + return; + } + + // Add header. + WRITESTRINGN(save.p, SERVERSTATSHEADER, headerlen); + + WRITEUINT32(save.p, numtracked); + + WRITEMEM(save.p, trackedList, (numtracked * sizeof(serverplayer_t))); + + for (i = 0; i < numtracked; i++) + { + + } + + 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 +void SV_RetrieveStats(int player) +{ + if (!server) + return; + + UINT32 j; + + for(j = 0; j < numtracked; j++) + { + if (memcmp(trackedList[j].public_key, players[player].public_key, PUBKEYLENGTH) == 0) + { + memcpy(clientpowerlevels[player], trackedList[j].powerlevels, sizeof(trackedList[j].powerlevels)); + return; + } + } + + uint8_t allZero[PUBKEYLENGTH]; + memset(allZero, 0, PUBKEYLENGTH); + + for(j = 0; j < PWRLV_NUMTYPES; j++) + { + if (memcmp(players[player].public_key, allZero, PUBKEYLENGTH) == 0) + clientpowerlevels[player][j] = 0; + else + clientpowerlevels[player][j] = PWRLVRECORD_START; + } + +} + +// Write player stats to trackedList, then save to disk +void SV_UpdateStats(void) +{ + UINT32 i, j; + uint8_t allZero[PUBKEYLENGTH]; + memset(allZero, 0, PUBKEYLENGTH); + + if (!server) + return; + + CONS_Printf("SV_UpdateStats\n"); + + for(i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + continue; + + if (memcmp(players[i].public_key, allZero, PUBKEYLENGTH) == 0) + { + continue; + } + + CONS_Printf("updating %d\n", i); + + boolean match = false; + for(j = 0; j < numtracked; j++) + { + if (memcmp(trackedList[j].public_key, players[i].public_key, PUBKEYLENGTH) == 0) + { + memcpy(trackedList[j].powerlevels, clientpowerlevels[i], sizeof(trackedList[j].powerlevels)); + match = true; + break; + } + } + + if (match) + continue; + + memcpy(trackedList[numtracked].public_key, players[i].public_key, PUBKEYLENGTH); + memcpy(trackedList[numtracked].powerlevels, clientpowerlevels[i], sizeof(trackedList[numtracked].powerlevels)); + + numtracked++; + } + + SV_SaveStats(); +} \ No newline at end of file diff --git a/src/k_serverstats.h b/src/k_serverstats.h new file mode 100644 index 000000000..161d11cd7 --- /dev/null +++ b/src/k_serverstats.h @@ -0,0 +1,49 @@ +// 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. +// +// 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 MAXTRACKEDSERVERPLAYERS 9999 +#define SERVERSTATSHEADER "Doctor Robotnik's Ring Racers Server Stats" + +struct serverplayer_t +{ + uint8_t public_key[PUBKEYLENGTH]; + UINT16 powerlevels[PWRLV_NUMTYPES]; +}; + +void SV_SaveStats(void); + +void SV_LoadStats(void); + +void SV_RetrieveStats(int player); + +void SV_UpdateStats(void); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif diff --git a/src/typedef.h b/src/typedef.h index 2683bff70..a8d2d669f 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -195,6 +195,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); From d89d06be314f6eec475201eded1ce2012a5b6dd6 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Sat, 1 Apr 2023 23:15:57 -0700 Subject: [PATCH 07/65] Instantly update stats when players leave --- src/k_pwrlv.c | 3 ++- src/k_serverstats.c | 4 ---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/k_pwrlv.c b/src/k_pwrlv.c index 5b1611d7b..5aec0e5e9 100644 --- a/src/k_pwrlv.c +++ b/src/k_pwrlv.c @@ -639,7 +639,8 @@ void K_PlayerForfeit(UINT8 playerNum, boolean pointLoss) if (pointLoss) { - CONS_Printf("Stats update by %d\n", inc); + clientpowerlevels[playerNum][powerType] += clientPowerAdd[playerNum]; + clientPowerAdd[playerNum] = 0; SV_UpdateStats(); } } diff --git a/src/k_serverstats.c b/src/k_serverstats.c index 49541e315..46aa06d3f 100644 --- a/src/k_serverstats.c +++ b/src/k_serverstats.c @@ -141,8 +141,6 @@ void SV_UpdateStats(void) if (!server) return; - CONS_Printf("SV_UpdateStats\n"); - for(i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i]) @@ -153,8 +151,6 @@ void SV_UpdateStats(void) continue; } - CONS_Printf("updating %d\n", i); - boolean match = false; for(j = 0; j < numtracked; j++) { From 7eb5755963e26479d5d66337418a65c5116b8537 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Sun, 2 Apr 2023 00:01:34 -0700 Subject: [PATCH 08/65] Disallow duplicate non-GUEST pubkeys --- src/d_clisrv.c | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index f1ff9cf96..d2f1b1ac1 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -4297,7 +4297,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. @@ -4396,6 +4396,8 @@ static void HandleConnect(SINT8 node) { int sigcheck; boolean newnode = false; + char allZero[PUBKEYLENGTH]; + memset(allZero, 0, sizeof(allZero)); for (i = 0; i < netbuffer->u.clientcfg.localplayers - playerpernode[node]; i++) { @@ -4411,10 +4413,7 @@ 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 (!cv_allowguests.value) @@ -4436,6 +4435,34 @@ static void HandleConnect(SINT8 node) return; } } + + // Check non-GUESTS for duplicate pubkeys, they'll create nonsense stats + if (memcmp(lastReceivedKey[node][i], allZero, PUBKEYLENGTH) != 0) + { + // Players already here + for (j = 0; j < MAXPLAYERS; j++) + { + if (memcmp(lastReceivedKey[node][i], players[j].public_key, PUBKEYLENGTH) == 0) + { + SV_SendRefuse(node, M_GetText("Duplicate pubkey already on server.\n(Did you share your profile?)")); + return; + } + } + + // Players we're trying to add + for (j = 0; j < netbuffer->u.clientcfg.localplayers - playerpernode[node]; j++) + { + if (memcmp(lastReceivedKey[node][i], allZero, PUBKEYLENGTH) == 0) + continue; + if (i == j) + continue; + if (memcmp(lastReceivedKey[node][i], lastReceivedKey[node][j], PUBKEYLENGTH) == 0) + { + SV_SendRefuse(node, M_GetText("Duplicate pubkey in local party.\n(How did you even do this?)")); + return; + } + } + } } memcpy(availabilitiesbuffer, netbuffer->u.clientcfg.availabilities, sizeof(availabilitiesbuffer)); From 10fb49668908f14368ccd35843f81743c25de49b Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Sun, 2 Apr 2023 00:29:58 -0700 Subject: [PATCH 09/65] Version field for srvstats.dat --- src/k_serverstats.c | 8 ++++++-- src/k_serverstats.h | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/k_serverstats.c b/src/k_serverstats.c index 46aa06d3f..3e30f9290 100644 --- a/src/k_serverstats.c +++ b/src/k_serverstats.c @@ -46,7 +46,8 @@ void SV_LoadStats(void) } save.p += headerlen; - + UINT8 version = READUINT8(save.p); + numtracked = READUINT32(save.p); if (numtracked > MAXTRACKEDSERVERPLAYERS) numtracked = MAXTRACKEDSERVERPLAYERS; @@ -73,7 +74,8 @@ void SV_SaveStats(void) } */ - if (P_SaveBufferAlloc(&save, headerlen + sizeof(UINT32) + (numtracked * sizeof(serverplayer_t))) == false) + // 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; @@ -82,6 +84,8 @@ void SV_SaveStats(void) // Add header. WRITESTRINGN(save.p, SERVERSTATSHEADER, headerlen); + WRITEUINT8(save.p, SERVERSTATSVER); + WRITEUINT32(save.p, numtracked); WRITEMEM(save.p, trackedList, (numtracked * sizeof(serverplayer_t))); diff --git a/src/k_serverstats.h b/src/k_serverstats.h index 161d11cd7..ffaaf571b 100644 --- a/src/k_serverstats.h +++ b/src/k_serverstats.h @@ -27,6 +27,7 @@ extern "C" { #define SERVERSTATSFILE "srvstats.dat" #define MAXTRACKEDSERVERPLAYERS 9999 #define SERVERSTATSHEADER "Doctor Robotnik's Ring Racers Server Stats" +#define SERVERSTATSVER 1 struct serverplayer_t { From 3012839138d09d134f0cabd5239107ed4c3923f8 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Wed, 5 Apr 2023 21:01:26 -0700 Subject: [PATCH 10/65] Serverside PWR: Completely rip out profile PWR, add PWR to XD_ADDPLAYER --- src/d_clisrv.c | 48 +++++++++++++++++++++++++-------------------- src/d_netcmd.c | 37 ++++++++++------------------------ src/d_netcmd.h | 2 +- src/deh_soc.c | 19 ------------------ src/k_menudraw.c | 10 ++-------- src/k_profiles.c | 9 +++++++++ src/k_profiles.h | 2 ++ src/k_pwrlv.c | 15 +------------- src/k_serverstats.c | 41 ++++++++++++++------------------------ src/k_serverstats.h | 2 +- src/m_cond.c | 25 ----------------------- src/m_cond.h | 2 -- 12 files changed, 68 insertions(+), 144 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index d2f1b1ac1..bd4c5064a 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -3853,6 +3853,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); @@ -3917,8 +3918,6 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum) HU_AddChatText(joinmsg, false); } - SV_RetrieveStats(newplayernum); - if (server && multiplayer && motd[0] != '\0') COM_BufAddText(va("sayto %d %s\n", newplayernum, motd)); @@ -4011,8 +4010,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]; @@ -4079,24 +4080,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 @@ -4186,8 +4191,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_RetrievePWR(PR_GetLocalPlayerProfile(0)->public_key), + cv_playername[1].zstring, PR_GetLocalPlayerProfile(1)->public_key, SV_RetrievePWR(PR_GetLocalPlayerProfile(1)->public_key), + cv_playername[2].zstring, PR_GetLocalPlayerProfile(2)->public_key, SV_RetrievePWR(PR_GetLocalPlayerProfile(2)->public_key), + cv_playername[3].zstring, PR_GetLocalPlayerProfile(3)->public_key, SV_RetrievePWR(PR_GetLocalPlayerProfile(3)->public_key)); } return result; #endif @@ -4268,13 +4276,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 @@ -4283,10 +4291,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 @@ -4396,8 +4401,6 @@ static void HandleConnect(SINT8 node) { int sigcheck; boolean newnode = false; - char allZero[PUBKEYLENGTH]; - memset(allZero, 0, sizeof(allZero)); for (i = 0; i < netbuffer->u.clientcfg.localplayers - playerpernode[node]; i++) { @@ -4414,7 +4417,7 @@ static void HandleConnect(SINT8 node) } else // Remote player, gotta check their signature. { - 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) { @@ -4437,7 +4440,7 @@ static void HandleConnect(SINT8 node) } // Check non-GUESTS for duplicate pubkeys, they'll create nonsense stats - if (memcmp(lastReceivedKey[node][i], allZero, PUBKEYLENGTH) != 0) + if (!PR_IsKeyGuest(lastReceivedKey[node][i])) { // Players already here for (j = 0; j < MAXPLAYERS; j++) @@ -4452,7 +4455,7 @@ static void HandleConnect(SINT8 node) // Players we're trying to add for (j = 0; j < netbuffer->u.clientcfg.localplayers - playerpernode[node]; j++) { - if (memcmp(lastReceivedKey[node][i], allZero, PUBKEYLENGTH) == 0) + if (PR_IsKeyGuest(lastReceivedKey[node][j])) continue; if (i == j) continue; @@ -4502,8 +4505,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_RetrievePWR(lastReceivedKey[node][0]), + names[1], lastReceivedKey[node][1], SV_RetrievePWR(lastReceivedKey[node][1]), + names[2], lastReceivedKey[node][2], SV_RetrievePWR(lastReceivedKey[node][2]), + names[3], lastReceivedKey[node][3], SV_RetrievePWR(lastReceivedKey[node][3])); joindelay += cv_joindelay.value * TICRATE; player_joining = true; } diff --git a/src/d_netcmd.c b/src/d_netcmd.c index c1fa1b7d0..2dbf96b62 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -618,7 +618,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 @@ -657,7 +656,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); @@ -1807,19 +1805,21 @@ static void Got_WeaponPref(UINT8 **cp,INT32 playernum) demo_extradata[playernum] |= DXD_WEAPONPREF; } +// Currently unused, PWR is sent in XD_ADDPLAYER static void Got_PowerLevel(UINT8 **cp,INT32 playernum) { - // Server keeps track of this now, no-sell XD_POWERLEVEL + int i; - /* - UINT16 race = (UINT16)READUINT16(*cp); - UINT16 battle = (UINT16)READUINT16(*cp); + for(i = 0; i < MAXPLAYERS; i++) + { + 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); + clientpowerlevels[i][PWRLV_RACE] = min(PWRLVRECORD_MAX, race); + clientpowerlevels[i][PWRLV_BATTLE] = min(PWRLVRECORD_MAX, battle); - CONS_Debug(DBG_GAMELOGIC, "set player %d to power %d\n", playernum, race); - */ + CONS_Debug(DBG_GAMELOGIC, "set player %d to power %d\n", i, race); + } } static void Got_PartyInvite(UINT8 **cp,INT32 playernum) @@ -1980,23 +1980,6 @@ void D_SendPlayerConfig(UINT8 n) 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, ...) diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 8cab1e10f..d4d5fee85 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -169,7 +169,7 @@ typedef enum XD_MODIFYVOTE, // 24 XD_PICKVOTE, // 25 XD_REMOVEPLAYER,// 26 - XD_POWERLEVEL, // 27 + XD_POWERLEVEL, // 27 [No longer used, might be repurposed - PWR is in XD_ADDPLAYER now] XD_PARTYINVITE, // 28 XD_ACCEPTPARTYINVITE, // 29 XD_LEAVEPARTY, // 30 diff --git a/src/deh_soc.c b/src/deh_soc.c index a7962e3af..fea7df30e 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -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; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 33cbf3aec..b7e633c47 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -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,12 +1698,8 @@ 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) - { - V_DrawFixedPatch((x+30)*FRACUNIT, (y+84)*FRACUNIT, FRACUNIT, 0, pwrlv, colormap); - V_DrawCenteredKartString(x+30, y+87, 0, va("%d\n", powerlevel)); - } + V_DrawFixedPatch((x+30)*FRACUNIT, (y+84)*FRACUNIT, FRACUNIT, 0, pwrlv, colormap); + V_DrawCenteredKartString(x+30, y+87, 0, "TEMP"); // check what setup_player is doing in priority. if (sp->mdepth >= CSSTEP_CHARS) diff --git a/src/k_profiles.c b/src/k_profiles.c index 853306556..d8b421d7c 100644 --- a/src/k_profiles.c +++ b/src/k_profiles.c @@ -630,3 +630,12 @@ char *GetPrettyRRID(const unsigned char *bin, boolean brief) return rrid_buf; } + + +char allZero[PUBKEYLENGTH]; + +boolean PR_IsKeyGuest(uint8_t *key) +{ + memset(allZero, 0, PUBKEYLENGTH); + return (memcmp(key, allZero, PUBKEYLENGTH) == 0); +} \ No newline at end of file diff --git a/src/k_profiles.h b/src/k_profiles.h index 2a866868f..65e1d740f 100644 --- a/src/k_profiles.h +++ b/src/k_profiles.h @@ -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 diff --git a/src/k_pwrlv.c b/src/k_pwrlv.c index 5aec0e5e9..9e3b3f640 100644 --- a/src/k_pwrlv.c +++ b/src/k_pwrlv.c @@ -400,7 +400,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"); @@ -410,29 +409,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"); } diff --git a/src/k_serverstats.c b/src/k_serverstats.c index 3e30f9290..f04a3c182 100644 --- a/src/k_serverstats.c +++ b/src/k_serverstats.c @@ -21,6 +21,8 @@ static serverplayer_t trackedList[MAXTRACKEDSERVERPLAYERS]; static UINT32 numtracked = 0; +UINT16 guestpwr[PWRLV_NUMTYPES]; // All-zero power level to reference for guests + // Read stats file to trackedList for ingame use void SV_LoadStats(void) { @@ -106,41 +108,35 @@ void SV_SaveStats(void) } // New player, grab their stats from trackedList or initialize new ones if they're new -void SV_RetrieveStats(int player) +UINT16 *SV_RetrievePWR(uint8_t *key) { - if (!server) - return; - UINT32 j; + // Existing record? for(j = 0; j < numtracked; j++) { - if (memcmp(trackedList[j].public_key, players[player].public_key, PUBKEYLENGTH) == 0) + if (memcmp(trackedList[j].public_key, key, PUBKEYLENGTH) == 0) { - memcpy(clientpowerlevels[player], trackedList[j].powerlevels, sizeof(trackedList[j].powerlevels)); - return; + return trackedList[j].powerlevels; } } - uint8_t allZero[PUBKEYLENGTH]; - memset(allZero, 0, PUBKEYLENGTH); - + // Untracked, make a new record + memcpy(trackedList[numtracked].public_key, key, PUBKEYLENGTH); for(j = 0; j < PWRLV_NUMTYPES; j++) { - if (memcmp(players[player].public_key, allZero, PUBKEYLENGTH) == 0) - clientpowerlevels[player][j] = 0; - else - clientpowerlevels[player][j] = PWRLVRECORD_START; + trackedList[numtracked].powerlevels[j] = PR_IsKeyGuest(key) ? 0 : PWRLVRECORD_START; } + + numtracked++; + return trackedList[numtracked - 1].powerlevels; } // Write player stats to trackedList, then save to disk void SV_UpdateStats(void) { UINT32 i, j; - uint8_t allZero[PUBKEYLENGTH]; - memset(allZero, 0, PUBKEYLENGTH); if (!server) return; @@ -150,29 +146,22 @@ void SV_UpdateStats(void) if (!playeringame[i]) continue; - if (memcmp(players[i].public_key, allZero, PUBKEYLENGTH) == 0) + if (PR_IsKeyGuest(players[i].public_key)) { continue; } - boolean match = false; for(j = 0; j < numtracked; j++) { if (memcmp(trackedList[j].public_key, players[i].public_key, PUBKEYLENGTH) == 0) { memcpy(trackedList[j].powerlevels, clientpowerlevels[i], sizeof(trackedList[j].powerlevels)); - match = true; break; } } - if (match) - continue; - - memcpy(trackedList[numtracked].public_key, players[i].public_key, PUBKEYLENGTH); - memcpy(trackedList[numtracked].powerlevels, clientpowerlevels[i], sizeof(trackedList[numtracked].powerlevels)); - - numtracked++; + // SV_RetrievePWR should always be called for a key before SV_UpdateStats runs, + // so this shouldn't be reachable. } SV_SaveStats(); diff --git a/src/k_serverstats.h b/src/k_serverstats.h index ffaaf571b..73210095b 100644 --- a/src/k_serverstats.h +++ b/src/k_serverstats.h @@ -39,7 +39,7 @@ void SV_SaveStats(void); void SV_LoadStats(void); -void SV_RetrieveStats(int player); +UINT16 *SV_RetrievePWR(uint8_t *key); void SV_UpdateStats(void); diff --git a/src/m_cond.c b/src/m_cond.c index 45db53caf..5b551f646 100644 --- a/src/m_cond.c +++ b/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); diff --git a/src/m_cond.h b/src/m_cond.h index c7712d5f7..a53e66ad7 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -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 UC_OVERALLTIME, // OVERALLTIME [time to beat, tics] From c7c9f145cad920c7c94558f9791a7c67827be599 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Wed, 5 Apr 2023 21:06:05 -0700 Subject: [PATCH 11/65] Really get rid of XD_POWERLEVEL --- src/d_netcmd.c | 18 ------------------ src/d_netcmd.h | 23 +++++++++++------------ 2 files changed, 11 insertions(+), 30 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 2dbf96b62..d366888b6 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -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); @@ -1805,23 +1804,6 @@ static void Got_WeaponPref(UINT8 **cp,INT32 playernum) demo_extradata[playernum] |= DXD_WEAPONPREF; } -// Currently unused, PWR is sent in XD_ADDPLAYER -static void Got_PowerLevel(UINT8 **cp,INT32 playernum) -{ - int i; - - for(i = 0; i < MAXPLAYERS; i++) - { - UINT16 race = (UINT16)READUINT16(*cp); - UINT16 battle = (UINT16)READUINT16(*cp); - - clientpowerlevels[i][PWRLV_RACE] = min(PWRLVRECORD_MAX, race); - clientpowerlevels[i][PWRLV_BATTLE] = min(PWRLVRECORD_MAX, battle); - - CONS_Debug(DBG_GAMELOGIC, "set player %d to power %d\n", i, race); - } -} - static void Got_PartyInvite(UINT8 **cp,INT32 playernum) { UINT8 invitee; diff --git a/src/d_netcmd.h b/src/d_netcmd.h index d4d5fee85..31f430ed2 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -169,18 +169,17 @@ typedef enum XD_MODIFYVOTE, // 24 XD_PICKVOTE, // 25 XD_REMOVEPLAYER,// 26 - XD_POWERLEVEL, // 27 [No longer used, might be repurposed - PWR is in XD_ADDPLAYER now] - 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_PARTYINVITE, // 27 + XD_ACCEPTPARTYINVITE, // 28 + XD_LEAVEPARTY, // 29 + XD_CANCELPARTYINVITE, // 30 + XD_CHEAT, // 31 + XD_ADDBOT, // 32 + XD_DISCORD, // 33 + XD_PLAYSOUND, // 34 + XD_SCHEDULETASK, // 35 + XD_SCHEDULECLEAR, // 36 + XD_AUTOMATE, // 37 MAXNETXCMD } netxcmd_t; From 4f569641fc39e36eeb9438c3a8ac0ab08b23bc74 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Thu, 6 Apr 2023 22:01:10 -0700 Subject: [PATCH 12/65] Store total wins in profiles --- src/k_menudraw.c | 8 ++++++-- src/k_profiles.c | 28 +++++++++++----------------- src/k_profiles.h | 2 +- src/k_serverstats.c | 13 ------------- src/p_user.c | 8 ++++++++ 5 files changed, 26 insertions(+), 33 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index b7e633c47..2111acfbb 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -1698,8 +1698,12 @@ static void M_DrawProfileCard(INT32 x, INT32 y, boolean greyedout, profile_t *p) if (greyedout) return; // only used for profiles we can't select. - V_DrawFixedPatch((x+30)*FRACUNIT, (y+84)*FRACUNIT, FRACUNIT, 0, pwrlv, colormap); - V_DrawCenteredKartString(x+30, y+87, 0, "TEMP"); + if (p != NULL) + { + V_DrawFixedPatch((x+30)*FRACUNIT, (y+84)*FRACUNIT, FRACUNIT, 0, pwrlv, colormap); + V_DrawCenteredKartString(x+30, y+87, 0, va("%d", p->wins)); + } + // check what setup_player is doing in priority. if (sp->mdepth >= CSSTEP_CHARS) diff --git a/src/k_profiles.c b/src/k_profiles.c index d8b421d7c..2a7c95350 100644 --- a/src/k_profiles.c +++ b/src/k_profiles.c @@ -72,11 +72,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 +266,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 +389,18 @@ void PR_LoadProfiles(void) profilesList[i]->followercolor = PROFILEDEFAULTFOLLOWERCOLOR; } - // PWR. - for (j = 0; j < PWRLV_NUMTYPES; j++) + // Profile update 4-->5: PWR isn't in profile data anymore. + if (version < 5) { - profilesList[i]->powerlevels[j] = READUINT16(save.p); - if (profilesList[i]->powerlevels[j] < PWRLVRECORD_MIN - || profilesList[i]->powerlevels[j] > PWRLVRECORD_MAX) + for (j = 0; j < PWRLV_NUMTYPES; j++) { - // invalid, reset - profilesList[i]->powerlevels[j] = PWRLVRECORD_START; + READUINT16(save.p); } + profilesList[i]->wins = 0; + } + else + { + profilesList[i]->wins = READUINT32(save.p); } // Consvars. diff --git a/src/k_profiles.h b/src/k_profiles.h index 65e1d740f..4890a4207 100644 --- a/src/k_profiles.h +++ b/src/k_profiles.h @@ -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; // PWRLV for each gametype. // Player-specific consvars. // @TODO: List all of those diff --git a/src/k_serverstats.c b/src/k_serverstats.c index f04a3c182..aba804c31 100644 --- a/src/k_serverstats.c +++ b/src/k_serverstats.c @@ -68,14 +68,6 @@ void SV_SaveStats(void) if (!server) return; - /* - if (profilesList[PROFILE_GUEST] == NULL) - { - // Profiles have not been loaded yet, don't overwrite with garbage. - return; - } - */ - // header + version + numtracked + payload if (P_SaveBufferAlloc(&save, headerlen + sizeof(UINT32) + sizeof(UINT8) + (numtracked * sizeof(serverplayer_t))) == false) { @@ -92,11 +84,6 @@ void SV_SaveStats(void) WRITEMEM(save.p, trackedList, (numtracked * sizeof(serverplayer_t))); - for (i = 0; i < numtracked; i++) - { - - } - length = save.p - save.buffer; if (!FIL_WriteFile(va(pandf, srb2home, SERVERSTATSFILE), save.buffer, length)) diff --git a/src/p_user.c b/src/p_user.c index e05d1a0f7..96201366d 100644 --- a/src/p_user.c +++ b/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]) From 0aabb6ddcdb72d3db653f85cc7c17b803a8fc130 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Mon, 10 Apr 2023 18:58:51 -0700 Subject: [PATCH 13/65] Return full stats struct, dynamically allocate trackedList --- src/d_clisrv.c | 16 ++++----- src/k_serverstats.c | 84 ++++++++++++++++++++++++++++++++++++--------- src/k_serverstats.h | 4 +-- 3 files changed, 78 insertions(+), 26 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index bd4c5064a..6fd96e8ad 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -4192,10 +4192,10 @@ boolean SV_SpawnServer(void) SINT8 node = 0; for (; node < MAXNETNODES; node++) result |= SV_AddWaitingPlayers(node, availabilitiesbuffer, - cv_playername[0].zstring, PR_GetLocalPlayerProfile(0)->public_key, SV_RetrievePWR(PR_GetLocalPlayerProfile(0)->public_key), - cv_playername[1].zstring, PR_GetLocalPlayerProfile(1)->public_key, SV_RetrievePWR(PR_GetLocalPlayerProfile(1)->public_key), - cv_playername[2].zstring, PR_GetLocalPlayerProfile(2)->public_key, SV_RetrievePWR(PR_GetLocalPlayerProfile(2)->public_key), - cv_playername[3].zstring, PR_GetLocalPlayerProfile(3)->public_key, SV_RetrievePWR(PR_GetLocalPlayerProfile(3)->public_key)); + 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 @@ -4506,10 +4506,10 @@ static void HandleConnect(SINT8 node) } SV_AddWaitingPlayers(node, availabilitiesbuffer, - names[0], lastReceivedKey[node][0], SV_RetrievePWR(lastReceivedKey[node][0]), - names[1], lastReceivedKey[node][1], SV_RetrievePWR(lastReceivedKey[node][1]), - names[2], lastReceivedKey[node][2], SV_RetrievePWR(lastReceivedKey[node][2]), - names[3], lastReceivedKey[node][3], SV_RetrievePWR(lastReceivedKey[node][3])); + 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; } diff --git a/src/k_serverstats.c b/src/k_serverstats.c index aba804c31..1114ecbb0 100644 --- a/src/k_serverstats.c +++ b/src/k_serverstats.c @@ -17,12 +17,57 @@ #include "m_misc.h" //FIL_WriteFile() #include "k_serverstats.h" #include "z_zone.h" +#include "time.h" -static serverplayer_t trackedList[MAXTRACKEDSERVERPLAYERS]; -static UINT32 numtracked = 0; +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 *= numtracked; + 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) { @@ -37,6 +82,8 @@ void SV_LoadStats(void) return; } + SV_InitializeStats(); + if (strncmp(SERVERSTATSHEADER, (const char *)save.buffer, headerlen)) { const char *gdfolder = "the Ring Racers folder"; @@ -51,8 +98,8 @@ void SV_LoadStats(void) UINT8 version = READUINT8(save.p); numtracked = READUINT32(save.p); - if (numtracked > MAXTRACKEDSERVERPLAYERS) - numtracked = MAXTRACKEDSERVERPLAYERS; + + SV_ExpandStats(numtracked); READMEM(save.p, trackedList, (numtracked * sizeof(serverplayer_t))); } @@ -95,29 +142,33 @@ void SV_SaveStats(void) } // New player, grab their stats from trackedList or initialize new ones if they're new -UINT16 *SV_RetrievePWR(uint8_t *key) +serverplayer_t *SV_RetrieveStats(uint8_t *key) { UINT32 j; + SV_InitializeStats(); + // Existing record? for(j = 0; j < numtracked; j++) { if (memcmp(trackedList[j].public_key, key, PUBKEYLENGTH) == 0) - { - return trackedList[j].powerlevels; - } + return &trackedList[j]; } - // Untracked, make a new record - memcpy(trackedList[numtracked].public_key, key, PUBKEYLENGTH); + // 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; } - + numtracked++; - return trackedList[numtracked - 1].powerlevels; + return &trackedList[numtracked - 1]; } // Write player stats to trackedList, then save to disk @@ -128,21 +179,22 @@ void SV_UpdateStats(void) if (!server) return; + SV_InitializeStats(); + for(i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i]) continue; if (PR_IsKeyGuest(players[i].public_key)) - { continue; - } for(j = 0; j < numtracked; j++) { - if (memcmp(trackedList[j].public_key, players[i].public_key, PUBKEYLENGTH) == 0) + if (memcmp(&trackedList[j].public_key, players[i].public_key, PUBKEYLENGTH) == 0) { - memcpy(trackedList[j].powerlevels, clientpowerlevels[i], sizeof(trackedList[j].powerlevels)); + trackedList[j].lastseen = time(NULL); + memcpy(&trackedList[j].powerlevels, clientpowerlevels[i], sizeof(trackedList[j].powerlevels)); break; } } diff --git a/src/k_serverstats.h b/src/k_serverstats.h index 73210095b..143f39892 100644 --- a/src/k_serverstats.h +++ b/src/k_serverstats.h @@ -25,13 +25,13 @@ extern "C" { #endif #define SERVERSTATSFILE "srvstats.dat" -#define MAXTRACKEDSERVERPLAYERS 9999 #define SERVERSTATSHEADER "Doctor Robotnik's Ring Racers Server Stats" #define SERVERSTATSVER 1 struct serverplayer_t { uint8_t public_key[PUBKEYLENGTH]; + time_t lastseen; UINT16 powerlevels[PWRLV_NUMTYPES]; }; @@ -39,7 +39,7 @@ void SV_SaveStats(void); void SV_LoadStats(void); -UINT16 *SV_RetrievePWR(uint8_t *key); +serverplayer_t *SV_RetrieveStats(uint8_t *key); void SV_UpdateStats(void); From a97373cb7a8541012b92fae46d0756bae4a42a47 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 11 Apr 2023 19:49:08 +0100 Subject: [PATCH 14/65] Increment PROFILEVER due to merge conflict resolution --- src/k_profiles.c | 6 +++--- src/k_profiles.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/k_profiles.c b/src/k_profiles.c index 2a7c95350..67a0b822e 100644 --- a/src/k_profiles.c +++ b/src/k_profiles.c @@ -389,8 +389,8 @@ void PR_LoadProfiles(void) profilesList[i]->followercolor = PROFILEDEFAULTFOLLOWERCOLOR; } - // Profile update 4-->5: PWR isn't in profile data anymore. - if (version < 5) + // Profile update 5-->6: PWR isn't in profile data anymore. + if (version < 6) { for (j = 0; j < PWRLV_NUMTYPES; j++) { @@ -419,7 +419,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)) { diff --git a/src/k_profiles.h b/src/k_profiles.h index 4890a4207..117c91900 100644 --- a/src/k_profiles.h +++ b/src/k_profiles.h @@ -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 From d61d822231eedaf93fa45f3aba75499e12c8f17e Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 11 Apr 2023 20:02:39 +0100 Subject: [PATCH 15/65] Compilation fixes: Unused variable warnings --- src/d_netcmd.c | 5 ----- src/k_profiles.c | 6 +----- src/k_pwrlv.c | 1 - src/k_serverstats.c | 4 ++-- 4 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index d366888b6..8d79971bf 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -1955,11 +1955,6 @@ 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); } diff --git a/src/k_profiles.c b/src/k_profiles.c index 67a0b822e..630c83af9 100644 --- a/src/k_profiles.c +++ b/src/k_profiles.c @@ -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; @@ -392,10 +391,7 @@ void PR_LoadProfiles(void) // Profile update 5-->6: PWR isn't in profile data anymore. if (version < 6) { - for (j = 0; j < PWRLV_NUMTYPES; j++) - { - READUINT16(save.p); - } + save.p += PWRLV_NUMTYPES*2; profilesList[i]->wins = 0; } else diff --git a/src/k_pwrlv.c b/src/k_pwrlv.c index 9e3b3f640..528c7afd0 100644 --- a/src/k_pwrlv.c +++ b/src/k_pwrlv.c @@ -553,7 +553,6 @@ void K_SetPowerLevelScrambles(SINT8 powertype) void K_PlayerForfeit(UINT8 playerNum, boolean pointLoss) { - profile_t *pr; UINT8 p = 0; SINT8 powerType = PWRLV_DISABLED; diff --git a/src/k_serverstats.c b/src/k_serverstats.c index 1114ecbb0..351c69b4d 100644 --- a/src/k_serverstats.c +++ b/src/k_serverstats.c @@ -96,7 +96,8 @@ void SV_LoadStats(void) save.p += headerlen; UINT8 version = READUINT8(save.p); - + (void)version; // for now + numtracked = READUINT32(save.p); SV_ExpandStats(numtracked); @@ -109,7 +110,6 @@ void SV_SaveStats(void) { size_t length = 0; const size_t headerlen = strlen(SERVERSTATSHEADER); - UINT8 i; savebuffer_t save = {0}; if (!server) From d85779c12b731cdf027e2151358b04da6facec92 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 11 Apr 2023 20:05:43 +0100 Subject: [PATCH 16/65] Newlines at end of k_profiles and k_serverstats --- src/k_profiles.c | 1 - src/k_serverstats.c | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/k_profiles.c b/src/k_profiles.c index 630c83af9..04fbf0b46 100644 --- a/src/k_profiles.c +++ b/src/k_profiles.c @@ -628,4 +628,3 @@ boolean PR_IsKeyGuest(uint8_t *key) { memset(allZero, 0, PUBKEYLENGTH); return (memcmp(key, allZero, PUBKEYLENGTH) == 0); -} \ No newline at end of file diff --git a/src/k_serverstats.c b/src/k_serverstats.c index 351c69b4d..e046dcc4c 100644 --- a/src/k_serverstats.c +++ b/src/k_serverstats.c @@ -204,4 +204,4 @@ void SV_UpdateStats(void) } SV_SaveStats(); -} \ No newline at end of file +} From 86fe3e266b0ff9801a92a6ffbb1131fc31736236 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 11 Apr 2023 20:05:53 +0100 Subject: [PATCH 17/65] allZero doesn't need to be memset --- src/k_profiles.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/k_profiles.c b/src/k_profiles.c index 04fbf0b46..e536f95e3 100644 --- a/src/k_profiles.c +++ b/src/k_profiles.c @@ -622,9 +622,10 @@ char *GetPrettyRRID(const unsigned char *bin, boolean brief) } -char allZero[PUBKEYLENGTH]; +static char allZero[PUBKEYLENGTH]; boolean PR_IsKeyGuest(uint8_t *key) { - memset(allZero, 0, PUBKEYLENGTH); + //memset(allZero, 0, PUBKEYLENGTH); -- not required, allZero is 0's return (memcmp(key, allZero, PUBKEYLENGTH) == 0); +} From df38d6fcde6d258277fa4035f0a6a955116bc778 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 11 Apr 2023 20:07:45 +0100 Subject: [PATCH 18/65] Comment fix --- src/k_profiles.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_profiles.h b/src/k_profiles.h index 117c91900..f0ac07d56 100644 --- a/src/k_profiles.h +++ b/src/k_profiles.h @@ -69,7 +69,7 @@ struct profile_t char follower[SKINNAMESIZE+1]; // Follower UINT16 followercolor; // Follower color - UINT32 wins; // PWRLV for each gametype. + UINT32 wins; // I win I win I win // Player-specific consvars. // @TODO: List all of those From 1a2121b3ca895257ab798ba737a5b3f09e4162d2 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 11 Apr 2023 20:32:55 -0700 Subject: [PATCH 19/65] K_ProcessTerrainEffect: fix spring crash on top of FOF --- src/k_terrain.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_terrain.c b/src/k_terrain.c index 0bf90fc17..d0f3d52bf 100644 --- a/src/k_terrain.c +++ b/src/k_terrain.c @@ -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 { From f0658910afcd212f946830ef19f6ac528ace5674 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Tue, 11 Apr 2023 22:15:55 -0700 Subject: [PATCH 20/65] Credit in k_serverstats.c --- src/k_serverstats.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/k_serverstats.c b/src/k_serverstats.c index e046dcc4c..0bcb500ed 100644 --- a/src/k_serverstats.c +++ b/src/k_serverstats.c @@ -2,6 +2,7 @@ //----------------------------------------------------------------------------- // 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. From 2961629332a48077cbdbdeec7c7004027080d5c4 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Tue, 11 Apr 2023 22:24:12 -0700 Subject: [PATCH 21/65] Allow and warn about duplicate pubkeys in DEVELOP --- src/d_clisrv.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 6fd96e8ad..37f0db6f2 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -4447,8 +4447,12 @@ static void HandleConnect(SINT8 node) { if (memcmp(lastReceivedKey[node][i], players[j].public_key, PUBKEYLENGTH) == 0) { - SV_SendRefuse(node, M_GetText("Duplicate pubkey already on server.\n(Did you share your profile?)")); - return; + #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 } } @@ -4461,8 +4465,12 @@ static void HandleConnect(SINT8 node) continue; if (memcmp(lastReceivedKey[node][i], lastReceivedKey[node][j], PUBKEYLENGTH) == 0) { - SV_SendRefuse(node, M_GetText("Duplicate pubkey in local party.\n(How did you even do this?)")); - return; + #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 } } } From 44848717df1f4ebfb6f9739e65151cb780b6d85a Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Tue, 11 Apr 2023 22:27:13 -0700 Subject: [PATCH 22/65] Credit in k_serverstats.h --- src/k_serverstats.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/k_serverstats.h b/src/k_serverstats.h index 143f39892..636e48dc9 100644 --- a/src/k_serverstats.h +++ b/src/k_serverstats.h @@ -4,6 +4,7 @@ // 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. From 515e0baa01c9c4328555da2b26ea067f35e979c1 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Tue, 11 Apr 2023 22:55:13 -0700 Subject: [PATCH 23/65] Do serverstats file I/O entry by entry --- src/k_serverstats.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/k_serverstats.c b/src/k_serverstats.c index 0bcb500ed..b3b259361 100644 --- a/src/k_serverstats.c +++ b/src/k_serverstats.c @@ -74,6 +74,7 @@ void SV_LoadStats(void) { const size_t headerlen = strlen(SERVERSTATSHEADER); savebuffer_t save = {0}; + unsigned int i, j; if (!server) return; @@ -103,7 +104,15 @@ void SV_LoadStats(void) SV_ExpandStats(numtracked); - READMEM(save.p, trackedList, (numtracked * sizeof(serverplayer_t))); + 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); + } + } } // Save trackedList to disc @@ -112,6 +121,7 @@ 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; @@ -130,7 +140,15 @@ void SV_SaveStats(void) WRITEUINT32(save.p, numtracked); - WRITEMEM(save.p, trackedList, (numtracked * sizeof(serverplayer_t))); + 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; From 2426170e0bd6270292c27523ca982a7e67331ee9 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Tue, 11 Apr 2023 23:09:40 -0700 Subject: [PATCH 24/65] Hash pubkeys for early out during stat retrieval --- src/k_serverstats.c | 16 +++++++++++++--- src/k_serverstats.h | 2 ++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/k_serverstats.c b/src/k_serverstats.c index b3b259361..a71bcca4f 100644 --- a/src/k_serverstats.c +++ b/src/k_serverstats.c @@ -112,6 +112,7 @@ void SV_LoadStats(void) { trackedList[i].powerlevels[j] = READUINT16(save.p); } + trackedList[i].hash = quickncasehash((char*)trackedList[i].public_key, PUBKEYLENGTH); } } @@ -163,13 +164,17 @@ void SV_SaveStats(void) // New player, grab their stats from trackedList or initialize new ones if they're new serverplayer_t *SV_RetrieveStats(uint8_t *key) { - UINT32 j; + 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]; } @@ -184,6 +189,7 @@ serverplayer_t *SV_RetrieveStats(uint8_t *key) { trackedList[numtracked].powerlevels[j] = PR_IsKeyGuest(key) ? 0 : PWRLVRECORD_START; } + trackedList[numtracked].hash = quickncasehash((char*)key, PUBKEYLENGTH); numtracked++; @@ -193,7 +199,7 @@ serverplayer_t *SV_RetrieveStats(uint8_t *key) // Write player stats to trackedList, then save to disk void SV_UpdateStats(void) { - UINT32 i, j; + UINT32 i, j, hash; if (!server) return; @@ -208,8 +214,12 @@ void SV_UpdateStats(void) 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); diff --git a/src/k_serverstats.h b/src/k_serverstats.h index 636e48dc9..313582e85 100644 --- a/src/k_serverstats.h +++ b/src/k_serverstats.h @@ -34,6 +34,8 @@ struct serverplayer_t uint8_t public_key[PUBKEYLENGTH]; time_t lastseen; UINT16 powerlevels[PWRLV_NUMTYPES]; + + UINT32 hash; // Not persisted! Used for early outs during key comparisons }; void SV_SaveStats(void); From 2b8f71bec5b341fd512ca4fa189684ac72dedf66 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Tue, 11 Apr 2023 23:11:23 -0700 Subject: [PATCH 25/65] It's probably bad to allocate exponential memory --- src/k_serverstats.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_serverstats.c b/src/k_serverstats.c index a71bcca4f..7959d3dc5 100644 --- a/src/k_serverstats.c +++ b/src/k_serverstats.c @@ -53,7 +53,7 @@ static void SV_ExpandStats(size_t needed) while (numallocated < needed) { - numallocated *= numtracked; + numallocated *= 2; trackedList = Z_Realloc( trackedList, sizeof(serverplayer_t) * numallocated, From d870bf0416602e650ed824922358bcbbab4fa2ab Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Wed, 12 Apr 2023 00:47:00 -0700 Subject: [PATCH 26/65] Fix min/max allowing for theoretical handling breakage in camera-sliptide logic --- src/p_user.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_user.c b/src/p_user.c index b48dc206d..d58ac8c26 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2254,7 +2254,7 @@ static void P_UpdatePlayerAngle(player_t *player) } else { - steeringLeft = min(steeringLeft, max(steeringRight, -1)); + steeringLeft = min(steeringLeft, min(steeringRight, -1)); steeringRight = min(steeringLeft, -1); } } From e3ea9df6833f3c7be0f66007a26d29c6aa351a48 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Wed, 12 Apr 2023 01:26:33 -0700 Subject: [PATCH 27/65] Fix sliptide death-spirals on input boundary cross --- src/p_user.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/p_user.c b/src/p_user.c index d58ac8c26..631df9583 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2249,13 +2249,13 @@ static void P_UpdatePlayerAngle(player_t *player) // Don't change handling direction if someone's inputs are sliptiding, you'll break the sliptide! if (player->cmd.turning > 0) { - steeringRight = max(steeringRight, max(steeringLeft, 1)); steeringLeft = max(steeringLeft, 1); + steeringRight = max(steeringRight, steeringLeft); } else { - steeringLeft = min(steeringLeft, min(steeringRight, -1)); - steeringRight = min(steeringLeft, -1); + steeringRight = min(steeringRight, -1); + steeringLeft = min(steeringLeft, steeringRight); } } From 4f2dccbf7243279265d2d23b07d987cb898b69f1 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Wed, 12 Apr 2023 02:51:48 -0700 Subject: [PATCH 28/65] Use UINT32 time_t --- src/k_serverstats.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_serverstats.h b/src/k_serverstats.h index 313582e85..d739a5185 100644 --- a/src/k_serverstats.h +++ b/src/k_serverstats.h @@ -32,7 +32,7 @@ extern "C" { struct serverplayer_t { uint8_t public_key[PUBKEYLENGTH]; - time_t lastseen; + UINT32 lastseen; UINT16 powerlevels[PWRLV_NUMTYPES]; UINT32 hash; // Not persisted! Used for early outs during key comparisons From adc2adb5c8b21c7f9466c685b94d9a9073befa0a Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 12 Apr 2023 14:54:42 +0100 Subject: [PATCH 29/65] I_ShowErrorMessageBox for SDL interface Handles showing the error message box on quit. - Polite communication that the game fell over and that you (probably) didn't do anything wrong - Accomodates pointing you to UNIXBACKTRACE and _WIN32 exchndl crash logs if relevant - Points you to the specific dated and timed log file that contains the error - Tells you that the vague and often confusing error message (like I_Error) is for a programmer, server host, or add-on creator - Hee Ho! --- src/sdl/i_system.c | 114 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 97 insertions(+), 17 deletions(-) diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index 76cafa09a..f6b2853ab 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -268,6 +268,93 @@ static void write_backtrace(INT32 signal) #undef CRASHLOG_STDERR_WRITE #endif // UNIXBACKTRACE +static void I_ShowErrorMessageBox(const char *messagefordevelopers, boolean dumpmade) +{ + static char finalmessage[1024]; + 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 " : "", + logfilename, + 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 +404,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 +1804,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 +1812,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) From 74247d8100fb55007b091e096a79737362d843f6 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 12 Apr 2023 15:08:17 +0100 Subject: [PATCH 30/65] Permit compiling with LOGMESSAGES disabled (and accomodate a crash early enough in the startup before the log is made, too) --- src/sdl/i_system.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index f6b2853ab..f93a3b3b4 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -297,7 +297,10 @@ static void I_ShowErrorMessageBox(const char *messagefordevelopers, boolean dump ".rpt crash dump" #endif " (very important!) and " : "", - logfilename, +#ifdef LOGMESSAGES + logfilename[0] ? logfilename : +#endif + "uh oh, one wasn't made!?", messagefordevelopers); // Rudementary word wrapping. From 3210b7b80c0a506f764ac0a50500221d2fe6f6f3 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 3 Apr 2023 00:21:16 +0100 Subject: [PATCH 31/65] Revert "Revert "Ignore OS key repeats for game controls"" This reverts commit b6b5175bbee451579dc7d364cb40787e29b25a83. --- src/d_event.h | 2 +- src/g_input.c | 5 +++++ src/sdl/i_video.cpp | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/d_event.h b/src/d_event.h index 8b85eed6e..67bbc34fd 100644 --- a/src/d_event.h +++ b/src/d_event.h @@ -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) }; diff --git a/src/g_input.c b/src/g_input.c index ecda7b3db..0ea851dec 100644 --- a/src/g_input.c +++ b/src/g_input.c @@ -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)) diff --git a/src/sdl/i_video.cpp b/src/sdl/i_video.cpp index 73c51315d..7fa82e058 100644 --- a/src/sdl/i_video.cpp +++ b/src/sdl/i_video.cpp @@ -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); } From fa48fa421bd926e007a243385870af9300d362b0 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 12 Apr 2023 18:48:53 +0100 Subject: [PATCH 32/65] Guarantee initialisation of event.data2 to 0 for controller button event --- src/sdl/i_video.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sdl/i_video.cpp b/src/sdl/i_video.cpp index 7fa82e058..88eb183e5 100644 --- a/src/sdl/i_video.cpp +++ b/src/sdl/i_video.cpp @@ -747,6 +747,7 @@ static void Impl_HandleControllerButtonEvent(SDL_ControllerButtonEvent evt, Uint } event.data1 = KEY_JOY1; + event.data2 = 0; if (type == SDL_CONTROLLERBUTTONUP) { From 8311ba8acbd0b854fa6454f8f44f21dd5695ffd1 Mon Sep 17 00:00:00 2001 From: SteelT Date: Wed, 12 Apr 2023 14:11:23 -0400 Subject: [PATCH 33/65] m_perfstats.c: Fix directive output may be truncated warning/error Lets the game build on my end with ERRORMODE enabled --- src/m_perfstats.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/m_perfstats.c b/src/m_perfstats.c index f3e92fe30..75d43fc50 100644 --- a/src/m_perfstats.c +++ b/src/m_perfstats.c @@ -490,7 +490,7 @@ static void M_DrawTickStats(void) void M_DrawPerfStats(void) { - char s[100]; + char s[363]; PS_SetFrameTime(); From bdfccb4478b3d0fa9d64342a67be4517274fcd68 Mon Sep 17 00:00:00 2001 From: SteelT Date: Wed, 12 Apr 2023 14:31:50 -0400 Subject: [PATCH 34/65] Change static buffer size to 2048, fixes the "directive output may be truncated writing up to" error --- src/sdl/i_system.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index f93a3b3b4..47a2c8412 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -270,7 +270,7 @@ static void write_backtrace(INT32 signal) static void I_ShowErrorMessageBox(const char *messagefordevelopers, boolean dumpmade) { - static char finalmessage[1024]; + static char finalmessage[2048]; size_t firstimpressionsline = 3; // "Dr Robotnik's Ring Racers" has encountered... if (M_CheckParm("-dedicated")) From 43b1686abf8cb2c4f359415abf3474a32df9f029 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 12 Apr 2023 23:08:50 +0100 Subject: [PATCH 35/65] Controller rumble if Stairjank is affecting your momentum directioning Uses the same strength as offroad because of how it's functionally equivalent in Sealed Star stages --- src/p_tick.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/p_tick.c b/src/p_tick.c index fd24f37e4..b7e907e6b 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -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; From 32d59372d32f15bcc549d8c7472b57e16a0d2fd7 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 13 Apr 2023 20:39:40 +0100 Subject: [PATCH 36/65] P_NetUnArchiveThinkers: set thinker references to 0 before removing Fixes failed assertion on server join (resolves #514) --- src/p_saveg.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/p_saveg.c b/src/p_saveg.c index d66ea3f20..c31161f40 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -4337,6 +4337,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 From f3d062e4e1d3f68b532f1c5630d45e46880dc3a7 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Thu, 13 Apr 2023 16:19:35 -0700 Subject: [PATCH 37/65] Use non-DEVELOP admin promotion in (HOST)TESTERS --- src/d_netcmd.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 27cdc9042..141a5341a 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -3919,10 +3919,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; From 733ed325da75dd7a06eb8400956a3a20594aa72b Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Thu, 13 Apr 2023 23:28:35 -0700 Subject: [PATCH 38/65] Instead of detonating lightning when getting hit, make a sad sound --- src/k_kart.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index a339b64a3..c9a66d8d6 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -6164,7 +6164,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; } From 2f6366938511991843c715d05f2f9b9ecd711711 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 14 Apr 2023 14:38:32 +0100 Subject: [PATCH 39/65] P_CheckPosition: If thing was removed in its remit and iteration has MF_NOCLIP, do not return true This fixes increment_move in the case of Lightning shield pop --- src/p_map.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/p_map.c b/src/p_map.c index ed83fd441..62739d935 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -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++; From 4edae065cb3394a44fb4fe50b3790f1cfa165d22 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 14 Apr 2023 20:58:17 +0100 Subject: [PATCH 40/65] MT_RANDOMITEM: Fix Prison Break/Versus delayed spawn `leveltime`'s behaviour was changed, and this code was fragile and dependent on the old behaviour --- src/p_mobj.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/p_mobj.c b/src/p_mobj.c index 7b4c3e7b3..46f2443ae 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -13143,8 +13143,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) From ae1d0e680a2ec215b2517de33d7855a0d0ecb115 Mon Sep 17 00:00:00 2001 From: Eidolon Date: Sat, 15 Apr 2023 16:28:21 -0500 Subject: [PATCH 41/65] rhi: Add Rhi::get_buffer_size --- src/rhi/gl3_core/gl3_core_rhi.cpp | 8 ++++++++ src/rhi/gl3_core/gl3_core_rhi.hpp | 1 + src/rhi/rhi.hpp | 1 + 3 files changed, 10 insertions(+) diff --git a/src/rhi/gl3_core/gl3_core_rhi.cpp b/src/rhi/gl3_core/gl3_core_rhi.cpp index ce3df3b05..18985ac43 100644 --- a/src/rhi/gl3_core/gl3_core_rhi.cpp +++ b/src/rhi/gl3_core/gl3_core_rhi.cpp @@ -1759,6 +1759,14 @@ Rect GlCoreRhi::get_renderbuffer_size(Handle renderbuffer) return ret; } +uint32_t GlCoreRhi::get_buffer_size(Handle 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); diff --git a/src/rhi/gl3_core/gl3_core_rhi.hpp b/src/rhi/gl3_core/gl3_core_rhi.hpp index 633e43754..8814ac36f 100644 --- a/src/rhi/gl3_core/gl3_core_rhi.hpp +++ b/src/rhi/gl3_core/gl3_core_rhi.hpp @@ -184,6 +184,7 @@ public: virtual TextureDetails get_texture_details(Handle texture) override; virtual Rect get_renderbuffer_size(Handle renderbuffer) override; + virtual uint32_t get_buffer_size(Handle buffer) override; virtual Handle begin_transfer() override; virtual void end_transfer(Handle handle) override; diff --git a/src/rhi/rhi.hpp b/src/rhi/rhi.hpp index f947fa201..aeb62e69c 100644 --- a/src/rhi/rhi.hpp +++ b/src/rhi/rhi.hpp @@ -608,6 +608,7 @@ struct Rhi virtual TextureDetails get_texture_details(Handle texture) = 0; virtual Rect get_renderbuffer_size(Handle renderbuffer) = 0; + virtual uint32_t get_buffer_size(Handle buffer) = 0; virtual Handle begin_transfer() = 0; virtual void end_transfer(Handle handle) = 0; From 5f78620bd9fc437ad872149f3b584cc9edc9e9ef Mon Sep 17 00:00:00 2001 From: Eidolon Date: Sat, 15 Apr 2023 16:29:18 -0500 Subject: [PATCH 42/65] rhi: Add rhi::recreate_buffer_to_size Simple utility to retain or recreate a buffer to a desired size. --- src/rhi/rhi.cpp | 25 +++++++++++++++++++++++++ src/rhi/rhi.hpp | 11 +++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/rhi/rhi.cpp b/src/rhi/rhi.cpp index d11f4440b..76668af83 100644 --- a/src/rhi/rhi.cpp +++ b/src/rhi/rhi.cpp @@ -83,3 +83,28 @@ const ProgramRequirements& rhi::program_requirements_for_program(PipelineProgram std::terminate(); } } + +bool rhi::recreate_buffer_to_size(Rhi& rhi, Handle& 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; +} diff --git a/src/rhi/rhi.hpp b/src/rhi/rhi.hpp index aeb62e69c..0187cf9fc 100644 --- a/src/rhi/rhi.hpp +++ b/src/rhi/rhi.hpp @@ -654,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, const BufferDesc& desc); + } // namespace srb2::rhi #endif // __SRB2_RHI_RHI_HPP__ From 1506e958c1c13674a5ae54f937e1bfb107e91c45 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Sat, 15 Apr 2023 16:26:01 -0700 Subject: [PATCH 43/65] Don't check wipeoutslow in flashtic blocker, it might be unset shortly after --- src/k_kart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index c9a66d8d6..f04d27789 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -7726,7 +7726,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; From 3ad3dd5cd9935e2397dbeadf86304543686c804d Mon Sep 17 00:00:00 2001 From: Eidolon Date: Sat, 15 Apr 2023 21:04:07 -0500 Subject: [PATCH 44/65] rhi: Extract patch atlas cache to own class --- src/hwr2/CMakeLists.txt | 2 + src/hwr2/pass_twodee.cpp | 325 +++------------------------------ src/hwr2/pass_twodee.hpp | 7 +- src/hwr2/patch_atlas.cpp | 379 +++++++++++++++++++++++++++++++++++++++ src/hwr2/patch_atlas.hpp | 145 +++++++++++++++ src/i_video_common.cpp | 4 + 6 files changed, 555 insertions(+), 307 deletions(-) create mode 100644 src/hwr2/patch_atlas.cpp create mode 100644 src/hwr2/patch_atlas.hpp diff --git a/src/hwr2/CMakeLists.txt b/src/hwr2/CMakeLists.txt index cef776505..8e525cad9 100644 --- a/src/hwr2/CMakeLists.txt +++ b/src/hwr2/CMakeLists.txt @@ -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 ) diff --git a/src/hwr2/pass_twodee.cpp b/src/hwr2/pass_twodee.cpp index 8b0f22b71..326caab0c 100644 --- a/src/hwr2/pass_twodee.cpp +++ b/src/hwr2/pass_twodee.cpp @@ -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 tex; - uint32_t tex_width; - uint32_t tex_height; - std::unordered_map entries; - - std::unique_ptr rp_ctx {nullptr}; - std::unique_ptr rp_nodes {nullptr}; - - Atlas& operator=(Atlas&&) = default; -}; - -} // namespace - struct srb2::hwr2::TwodeePassData { Handle default_tex; Handle default_colormap_tex; - std::vector patch_atlases; - std::unordered_map patch_lookup; - std::vector patches_to_upload; std::unordered_map> colormaps; std::vector colormaps_to_upload; std::unordered_map> 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(); - new_atlas.rp_nodes = std::make_unique(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 patches) -{ - // Prepare stbrp rects for patches to be loaded. - std::vector 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(rect.x); - entry.y = static_cast(rect.y); - entry.w = static_cast(rect.w); - entry.h = static_cast(rect.h); - entry.trim_x = static_cast(trimmed_rect.x); - entry.trim_y = static_cast(trimmed_rect.y); - entry.orig_w = static_cast(patch->width); - entry.orig_h = static_cast(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(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(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(reinterpret_cast(column) + column->length + 4); - } - } - - maxx += 1; - maxx = std::max(minx, maxx); - maxy = std::max(miny, maxy); - - return {minx, miny, static_cast(maxx - minx), static_cast(maxy - miny)}; -} - -static void convert_patch_to_trimmed_rg8_pixels(const patch_t* patch, std::vector& 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(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(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(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(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(reinterpret_cast(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 atlas = patch_atlas_cache_->find_patch(patch); + std::optional 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(entry.x) / atlas.tex_width; - const float atlas_umax = static_cast(entry.x + entry.w) / atlas.tex_width; - const float atlas_vmin = static_cast(entry.y) / atlas.tex_height; - const float atlas_vmax = static_cast(entry.y + entry.h) / atlas.tex_height; + const float atlas_umin = static_cast(entry.x) / atlas->texture_size(); + const float atlas_umax = static_cast(entry.x + entry.w) / atlas->texture_size(); + const float atlas_vmin = static_cast(entry.y) / atlas->texture_size(); + const float atlas_vmax = static_cast(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 found_patches; std::unordered_set found_colormaps; @@ -587,19 +335,11 @@ void TwodeePass::prepass(Rhi& rhi) } } - std::unordered_set patch_cache_hits; - std::unordered_set 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 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 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 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 ctx) } data_->colormaps_to_upload.clear(); - // Convert patches to RG8 textures and upload to atlas pages - std::vector 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(entry.x), static_cast(entry.y), entry.w, entry.h}, - PixelFormat::kRG8, - tcb::as_bytes(tcb::span(patch_data)) - ); - } - data_->patches_to_upload.clear(); - Handle palette_tex = palette_manager_->palette(); // Update the buffers for each list @@ -867,10 +583,9 @@ void TwodeePass::transfer(Rhi& rhi, Handle ctx) { TextureBinding tx[3]; auto tex_visitor = srb2::Overload { - [&](size_t atlas_index) + [&](Handle 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) diff --git a/src/hwr2/pass_twodee.hpp b/src/hwr2/pass_twodee.hpp index 4edb495c2..15a0924a4 100644 --- a/src/hwr2/pass_twodee.hpp +++ b/src/hwr2/pass_twodee.hpp @@ -18,6 +18,7 @@ #include #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 binding_set = {}; - std::optional> texture; + std::optional, 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 data_; std::shared_ptr palette_manager_; std::shared_ptr flat_manager_; + std::shared_ptr patch_atlas_cache_; rhi::Handle us_1; rhi::Handle us_2; std::vector cmd_lists_; std::vector, std::size_t>> vbos_; std::vector, std::size_t>> ibos_; - bool rebuild_atlases_ = false; rhi::Handle render_pass_; rhi::Handle 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(); diff --git a/src/hwr2/patch_atlas.cpp b/src/hwr2/patch_atlas.cpp new file mode 100644 index 000000000..90db30483 --- /dev/null +++ b/src/hwr2/patch_atlas.cpp @@ -0,0 +1,379 @@ +// 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 + +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(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(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(reinterpret_cast(column) + column->length + 4); + } + } + + maxx += 1; + maxx = std::max(minx, maxx); + maxy = std::max(miny, maxy); + + return {minx, miny, static_cast(maxx - minx), static_cast(maxy - miny)}; +} + +void srb2::hwr2::convert_patch_to_trimmed_rg8_pixels(const patch_t* patch, std::vector& 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(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(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(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(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(reinterpret_cast(column) + column->length + 4); + } + } +} + +PatchAtlas::PatchAtlas(Handle texture, uint32_t size) : tex_(texture), size_(size) +{ + rp_ctx = std::make_unique(); + rp_nodes = std::make_unique(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 rects) +{ + stbrp_pack_rects(rp_ctx.get(), rects.data(), rects.size()); +} + +std::optional PatchAtlas::find_patch(srb2::NotNull 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; + } + 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 = 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 rects; + + std::vector large_patches; + + std::vector 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(rect.x); + entry.y = static_cast(rect.y); + entry.w = static_cast(rect.w); + entry.h = static_cast(rect.h); + entry.trim_x = static_cast(trimmed_rect.x); + entry.trim_y = static_cast(trimmed_rect.y); + entry.orig_w = static_cast(patch->width); + entry.orig_h = static_cast(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 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 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 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 ctx) +{ + SRB2_ASSERT(ready_for_lookup()); + + // Upload atlased patches + std::vector patch_data; + for (const patch_t* patch_to_upload : patches_to_upload_) + { + srb2::NotNull atlas = find_patch(patch_to_upload); + + std::optional 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(entry->x), static_cast(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 ctx) +{ +} + +void PatchAtlasCache::postpass(Rhi& rhi) +{ +} diff --git a/src/hwr2/patch_atlas.hpp b/src/hwr2/patch_atlas.hpp new file mode 100644 index 000000000..9707074b2 --- /dev/null +++ b/src/hwr2/patch_atlas.hpp @@ -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 +#include +#include +#include +#include +#include + +#include + +#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 tex_; + uint32_t size_; + + std::unordered_map entries_; + + std::unique_ptr rp_ctx {nullptr}; + std::unique_ptr rp_nodes {nullptr}; + + friend class PatchAtlasCache; + +public: + PatchAtlas(rhi::Handle 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 texture() const noexcept { return tex_; } + + uint32_t texture_size() const noexcept { return size_; } + + std::optional find_patch(srb2::NotNull patch) const; + + void pack_rects(tcb::span 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 atlases_; + std::unordered_map patch_lookup_; + + std::unordered_set patches_to_pack_; + std::unordered_set 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 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 patch) const; + PatchAtlas* find_patch(srb2::NotNull patch); + + virtual void prepass(rhi::Rhi& rhi) override; + virtual void transfer(rhi::Rhi& rhi, rhi::Handle ctx) override; + virtual void graphics(rhi::Rhi& rhi, rhi::Handle 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& out); + +} // namespace srb2::hwr2 + +#endif // __SRB2_HWR2_PATCH_ATLAS_HPP__ diff --git a/src/i_video_common.cpp b/src/i_video_common.cpp index 80379ad41..e41725a16 100644 --- a/src/i_video_common.cpp +++ b/src/i_video_common.cpp @@ -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(); auto common_resources_manager = std::make_shared(); auto flat_texture_manager = std::make_shared(); + auto patch_atlas_cache = std::make_shared(2048, 2); auto resource_manager = std::make_shared(); 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(palette_manager); auto twodee = std::make_shared(); 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(false); From 2b01d8a5899df6aacfa53d58417d81fa2aaa146a Mon Sep 17 00:00:00 2001 From: Eidolon Date: Sat, 15 Apr 2023 22:19:01 -0500 Subject: [PATCH 45/65] rhi: Reset patch atlas if patch is freed --- src/hwr2/patch_atlas.cpp | 6 ++++++ src/i_video_common.cpp | 1 + src/r_patch.cpp | 14 ++++++++++++++ src/r_patch.h | 2 ++ 4 files changed, 23 insertions(+) diff --git a/src/hwr2/patch_atlas.cpp b/src/hwr2/patch_atlas.cpp index 90db30483..fe5f6229c 100644 --- a/src/hwr2/patch_atlas.cpp +++ b/src/hwr2/patch_atlas.cpp @@ -11,6 +11,8 @@ #include +#include "../r_patch.h" + using namespace srb2; using namespace srb2::hwr2; using namespace srb2::rhi; @@ -168,6 +170,10 @@ bool PatchAtlasCache::need_to_reset() const { return true; } + if (Patch_WasFreedThisFrame()) + { + return true; + } return false; } diff --git a/src/i_video_common.cpp b/src/i_video_common.cpp index e41725a16..20294e9b6 100644 --- a/src/i_video_common.cpp +++ b/src/i_video_common.cpp @@ -455,6 +455,7 @@ static InternalPassData build_pass_manager() void I_NewTwodeeFrame(void) { g_2d = Twodee(); + Patch_ResetFreedThisFrame(); } void I_NewImguiFrame(void) diff --git a/src/r_patch.cpp b/src/r_patch.cpp index 443e940a3..3e544ef82 100644 --- a/src/r_patch.cpp +++ b/src/r_patch.cpp @@ -99,6 +99,8 @@ static void Patch_FreeData(patch_t *patch) Z_Free(patch->columns); } +static boolean g_patch_was_freed_this_frame = false; + void Patch_Free(patch_t *patch) { if (!patch || patch == missingpat) @@ -106,6 +108,18 @@ void Patch_Free(patch_t *patch) Patch_FreeData(patch); Z_Free(patch); + + g_patch_was_freed_this_frame = true; +} + +boolean Patch_WasFreedThisFrame(void) +{ + return g_patch_was_freed_this_frame; +} + +void Patch_ResetFreedThisFrame(void) +{ + g_patch_was_freed_this_frame = false; } // diff --git a/src/r_patch.h b/src/r_patch.h index dd3a6a29c..c0f0448c1 100644 --- a/src/r_patch.h +++ b/src/r_patch.h @@ -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); From aca8b2f51d2ef7367e3e8b88d809a488927bae09 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Sat, 15 Apr 2023 20:29:49 -0700 Subject: [PATCH 46/65] Drop items when an eggbox is transferred to you --- src/k_kart.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/k_kart.c b/src/k_kart.c index c9a66d8d6..370396c94 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -11656,6 +11656,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; From 716ba0156e5d7a98751fed198cc2ed7edefd6e2a Mon Sep 17 00:00:00 2001 From: Eidolon Date: Sun, 16 Apr 2023 22:47:53 -0500 Subject: [PATCH 47/65] Move WasFreedThisFrame to Patch_FreeData --- src/r_patch.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/r_patch.cpp b/src/r_patch.cpp index 3e544ef82..b09416969 100644 --- a/src/r_patch.cpp +++ b/src/r_patch.cpp @@ -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,9 +99,9 @@ static void Patch_FreeData(patch_t *patch) Z_Free(patch->columnofs); Z_Free(patch->columns); -} -static boolean g_patch_was_freed_this_frame = false; + g_patch_was_freed_this_frame = true; +} void Patch_Free(patch_t *patch) { @@ -108,8 +110,6 @@ void Patch_Free(patch_t *patch) Patch_FreeData(patch); Z_Free(patch); - - g_patch_was_freed_this_frame = true; } boolean Patch_WasFreedThisFrame(void) From c98ff9616b8e271ea7b6ba465f9aa8914efd83cd Mon Sep 17 00:00:00 2001 From: lachablock Date: Sat, 8 Jan 2022 22:14:29 +1100 Subject: [PATCH 48/65] Start Ring Shooter respawn: while e-braking, enter the respawn command to spawn a Ring Shooter. Currently purely visual and does not despawn. Steals the player's face for funsies. --- src/d_netcmd.c | 5 +- src/deh_tables.c | 15 ++++++ src/info.c | 120 +++++++++++++++++++++++++++++++++++++++++++++++ src/info.h | 20 ++++++++ src/k_kart.c | 85 +++++++++++++++++++++++++++++++++ src/k_kart.h | 1 + src/p_enemy.c | 47 +++++++++++++++++++ src/p_mobj.c | 22 +++++++++ 8 files changed, 314 insertions(+), 1 deletion(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 5b3a9124f..4a83f9b27 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -3366,7 +3366,10 @@ static void Got_Respawn(UINT8 **cp, INT32 playernum) if (!P_IsObjectOnGround(players[respawnplayer].mo)) return; - P_DamageMobj(players[respawnplayer].mo, NULL, NULL, 1, DMG_DEATHPIT); + if (K_PlayerEBrake(&players[respawnplayer])) + K_SpawnRingShooter(&players[respawnplayer]); + else + P_DamageMobj(players[respawnplayer].mo, NULL, NULL, 1, DMG_DEATHPIT); demo_extradata[playernum] |= DXD_RESPAWN; } } diff --git a/src/deh_tables.c b/src/deh_tables.c index bdb74b55b..b5746272d 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -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", diff --git a/src/info.c b/src/info.c index 83cf21e49..7d35232fd 100644 --- a/src/info.c +++ b/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|8, -1, {NULL}, 0, 0, S_NULL}, // S_RINGSHOOTER_NUMBERBACK + {SPR_RSHT, FF_FULLBRIGHT|FF_PAPERSPRITE|12, -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_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*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_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 diff --git a/src/info.h b/src/info.h index 6c9d7c773..d80c9798a 100644 --- a/src/info.h +++ b/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, diff --git a/src/k_kart.c b/src/k_kart.c index 9af81605f..4f8658ecf 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -11774,3 +11774,88 @@ boolean K_Cooperative(void) } //} + +void K_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; + UINT32 frameNum; + angle_t angle; + vector2_t offset; + SINT8 i; + + K_FlipFromObject(base, mo); + P_SetTarget(&base->target, mo); + P_SetScale(base, base->destscale = FixedMul(base->destscale, scale)); + P_InitAngle(base, mo->angle); + + // 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); + + P_InitAngle(part, base->angle - i * ANGLE_45); + P_SetMobjState(part, S_RINGSHOOTER_NIPPLES); + part->frame += frameNum; + frameNum++; + } + + // 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->frame += frameNum; + P_InitAngle(part, angle); + } + + // spawn the screen + part = P_SpawnMobjFromMobj(base, offset.x, offset.y, info->height, MT_RINGSHOOTER_SCREEN); + P_SetTarget(&base->tracer, part); + P_SetTarget(&part->target, base); + P_InitAngle(part, base->angle - ANGLE_45); + + // 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; + P_InitAngle(part, part->target->angle); + P_SetMobjState(part, S_RINGSHOOTER_NUMBERBACK + i); + } + + // test face feature (to be moved into thinker later) + part->skin = mo->skin; + P_SetMobjState(part, S_RINGSHOOTER_FACE); +} diff --git a/src/k_kart.h b/src/k_kart.h index 782740ce8..0c91fe601 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -189,6 +189,7 @@ boolean K_IsSPBInGame(void); void K_KartEbrakeVisuals(player_t *p); void K_HandleDirectionalInfluence(player_t *player); fixed_t K_DefaultPlayerRadius(player_t *player); +void K_SpawnRingShooter(player_t *player); // sound stuff for lua void K_PlayAttackTaunt(mobj_t *source); diff --git a/src/p_enemy.c b/src/p_enemy.c index 9c457bdb5..3278e8249 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -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,49 @@ A_SpawnItemDebrisCloud (mobj_t *actor) puff->momz += FixedMul(target->momz, fade); } } + +// sets the actor's +// vars do nothing +void A_RingShooterFace(mobj_t *actor) +{ + player_t *player; + mobj_t *mo = actor; + + if (LUA_CallAction(A_RINGSHOOTERFACE, actor)) + return; + + // get the player, if possible + while ((mo->player == NULL) && !P_MobjWasRemoved(mo->target)) + mo = mo->target; + + player = mo->player; + + if (!player) // something changed my target, abort + return; + + // 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 (actor->skin != &skins[player->skin]) + { + actor->skin = &skins[player->skin]; + P_SetMobjState(actor, (statenum_t)(actor->state-states)); + return; + } + + // okay, now steal the player's color nyehehehe + actor->color = player->skincolor; + + // set the frame to the WANTED pic + actor->frame = (actor->frame & ~FF_FRAMEMASK) | FACE_WANTED; + + // 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?) + actor->spritexscale = FixedDiv(26*FRACUNIT, 32*FRACUNIT); + actor->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 + actor->spritexoffset = 16*FRACUNIT; // 32 / 2 + actor->spriteyoffset = 28*FRACUNIT + FixedDiv(11*FRACUNIT, actor->spriteyscale); // 32 - 4 (generic monster bottom) + 11 (vertical offset of screen sprite from the bottom) +} diff --git a/src/p_mobj.c b/src/p_mobj.c index 46f2443ae..376afcb06 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -4550,6 +4550,25 @@ static void P_SpawnItemCapsuleParts(mobj_t *mobj) #undef ANG_CAPSULE #undef ROTATIONSPEED +static void P_RingShooterThinker(mobj_t *mo) +{ + UINT32 trans; + mobj_t *part = mo; + + while (!P_MobjWasRemoved(part->tracer)) + part = part->tracer; + + if (part == mo) // ??? where did you go + return; + + part->renderflags ^= RF_DONTDRAW; + if (part->renderflags & RF_DONTDRAW) + trans = FF_TRANS50; + else + trans = 0; + part->target->frame = (part->target->frame & ~FF_TRANSMASK) | trans; +} + // // P_BossTargetPlayer // If closest is true, find the closest player. @@ -6641,6 +6660,9 @@ static void P_MobjSceneryThink(mobj_t *mobj) mobj->momz = newz - mobj->z; } break; + case MT_RINGSHOOTER: + P_RingShooterThinker(mobj); + break; case MT_SPINDASHWIND: case MT_DRIFTELECTRICSPARK: mobj->renderflags ^= RF_DONTDRAW; From a4584906399eaee58314e94fd25b6bf9057863a7 Mon Sep 17 00:00:00 2001 From: lachablock Date: Sun, 22 May 2022 23:48:03 +1000 Subject: [PATCH 49/65] Ring Shooter: countdown animation & experimental stretchy spawn --- src/info.c | 8 +-- src/k_kart.c | 33 ++++++++--- src/p_mobj.c | 152 +++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 177 insertions(+), 16 deletions(-) diff --git a/src/info.c b/src/info.c index 7d35232fd..a082abbc5 100644 --- a/src/info.c +++ b/src/info.c @@ -4472,8 +4472,8 @@ state_t states[NUMSTATES] = {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|8, -1, {NULL}, 0, 0, S_NULL}, // S_RINGSHOOTER_NUMBERBACK - {SPR_RSHT, FF_FULLBRIGHT|FF_PAPERSPRITE|12, -1, {NULL}, 0, 0, S_NULL}, // S_RINGSHOOTER_NUMBERFRONT + {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 @@ -24672,12 +24672,12 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = sfx_None, // attacksound S_NULL, // painstate 0, // painchance - sfx_None, // painsound + sfx_s3ka7, // painsound S_NULL, // meleestate S_NULL, // missilestate S_NULL, // deathstate S_NULL, // xdeathstate - sfx_None, // deathsound + sfx_s3kad, // deathsound 0, // speed 16*FRACUNIT, // radius 16*FRACUNIT, // height diff --git a/src/k_kart.c b/src/k_kart.c index 4f8658ecf..d4eb041a4 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -11775,13 +11775,15 @@ boolean K_Cooperative(void) //} +// I've tried to reduce redundancy as much as I can, +// but check P_UpdateRingShooterParts if you edit this void K_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; + mobj_t *part, *refNipple; UINT32 frameNum; angle_t angle; vector2_t offset; @@ -11791,6 +11793,15 @@ void K_SpawnRingShooter(player_t *player) P_SetTarget(&base->target, mo); P_SetScale(base, base->destscale = FixedMul(base->destscale, scale)); P_InitAngle(base, mo->angle); + base->scalespeed = FRACUNIT/2; + base->extravalue1 = FRACUNIT; // horizontal scale + base->extravalue2 = 0; // vertical scale + + // the ring shooter object itself is invisible and acts as the thinker + // each ring shooter uses three 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 // spawn the RING NIPPLES part = base; @@ -11810,8 +11821,11 @@ void K_SpawnRingShooter(player_t *player) P_InitAngle(part, 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; @@ -11835,15 +11849,23 @@ void K_SpawnRingShooter(player_t *player) frameNum++; frameNum ^= FF_HORIZONTALFLIP; angle -= ANGLE_90; - part->frame += frameNum; P_InitAngle(part, 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, info->height, MT_RINGSHOOTER_SCREEN); + part = P_SpawnMobjFromMobj(base, offset.x, offset.y, 0, MT_RINGSHOOTER_SCREEN); P_SetTarget(&base->tracer, part); P_SetTarget(&part->target, base); P_InitAngle(part, 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++) @@ -11853,9 +11875,6 @@ void K_SpawnRingShooter(player_t *player) part = part->tracer; P_InitAngle(part, part->target->angle); P_SetMobjState(part, S_RINGSHOOTER_NUMBERBACK + i); + part->renderflags |= RF_DONTDRAW; } - - // test face feature (to be moved into thinker later) - part->skin = mo->skin; - P_SetMobjState(part, S_RINGSHOOTER_FACE); } diff --git a/src/p_mobj.c b/src/p_mobj.c index 376afcb06..07b5840be 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -4550,17 +4550,150 @@ static void P_SpawnItemCapsuleParts(mobj_t *mobj) #undef ANG_CAPSULE #undef ROTATIONSPEED -static void P_RingShooterThinker(mobj_t *mo) +// ------------ +// RING SHOOTER +// ------------ + +static void P_ActivateRingShooter(mobj_t *mo) +{ + mobj_t *part = mo->tracer; + + while (!P_MobjWasRemoved(part->tracer)) + { + part = part->tracer; + part->renderflags &= ~RF_DONTDRAW; + part->frame += 4; + } +} + +#define scaleSpeed mo->scalespeed +#define scaleState mo->threshold +#define xScale mo->extravalue1 +#define yScale mo->extravalue2 +#define xOffset part->extravalue1 +#define yOffset part->extravalue2 +#define SCALEPART part->spritexscale = xScale; part->spriteyscale = yScale; +#define MOVEPART P_MoveOrigin(part, refNipple->x + FixedMul(xOffset, xScale), refNipple->y + FixedMul(yOffset, xScale), part->z); + +// I've tried to reduce redundancy as much as I can, +// but check K_SpawnRingShooter if you edit this +static void P_UpdateRingShooterParts(mobj_t *mo) +{ + mobj_t *part, *refNipple; + + part = mo; + while (!P_MobjWasRemoved(part->hprev)) + { + part = part->hprev; + SCALEPART + } + refNipple = part; + + part = mo; + while (!P_MobjWasRemoved(part->hnext)) + { + part = part->hnext; + MOVEPART + SCALEPART + } + + part = mo->tracer; + part->z = mo->z + FixedMul(refNipple->height, yScale); + MOVEPART + SCALEPART +} + +static boolean P_RingShooterInit(mobj_t *mo) +{ + if (scaleState == -1) + return false; + + switch (scaleState) { + case 0: + yScale += scaleSpeed; + if (yScale >= FRACUNIT) + { + //xScale -= scaleSpeed; + scaleState++; + } + break; + case 1: + scaleSpeed -= FRACUNIT/5; + yScale += scaleSpeed; + xScale -= scaleSpeed; + if (yScale < 3*FRACUNIT/4) + { + scaleState ++; + scaleSpeed = FRACUNIT >> 2; + } + break; + case 2: + yScale += scaleSpeed; + xScale -= scaleSpeed; + if (yScale >= FRACUNIT) + { + scaleState = -1; + xScale = yScale = FRACUNIT; + P_ActivateRingShooter(mo); + } + } + + P_UpdateRingShooterParts(mo); + return scaleState != -1; +} +#undef scaleSpeed +#undef scaleState +#undef xScale +#undef yScale +#undef xOffset +#undef yOffset +#undef MOVEPART +#undef SCALEPART + +static void P_RingShooterCountdown(mobj_t *mo) +{ + mobj_t *part = mo->tracer; + + if (mo->reactiontime == -1) + return; + + if (mo->reactiontime > 0) + { + mo->reactiontime--; + 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; + part->skin = mo->skin; + P_SetMobjState(part, S_RINGSHOOTER_FACE); + break; + case 0: + mo->reactiontime = TICRATE; + S_StartSound(mo, mo->info->deathsound); + break; + default: + mo->reactiontime = TICRATE; + S_StartSound(mo, mo->info->painsound); + break; + } +} + +static void P_RingShooterFlicker(mobj_t *mo) { UINT32 trans; - mobj_t *part = mo; + mobj_t *part = mo->tracer; while (!P_MobjWasRemoved(part->tracer)) part = part->tracer; - if (part == mo) // ??? where did you go - return; - part->renderflags ^= RF_DONTDRAW; if (part->renderflags & RF_DONTDRAW) trans = FF_TRANS50; @@ -4569,6 +4702,15 @@ static void P_RingShooterThinker(mobj_t *mo) part->target->frame = (part->target->frame & ~FF_TRANSMASK) | trans; } +static void P_RingShooterThinker(mobj_t *mo) +{ + if (P_MobjWasRemoved(mo->tracer) || P_RingShooterInit(mo)) + return; + + P_RingShooterCountdown(mo); + P_RingShooterFlicker(mo); +} + // // P_BossTargetPlayer // If closest is true, find the closest player. From f9317b265e01e866f3c1933595d829f1a607ce1f Mon Sep 17 00:00:00 2001 From: Lach <54614944+lachablock@users.noreply.github.com> Date: Tue, 20 Sep 2022 22:28:20 +1000 Subject: [PATCH 50/65] Correct Ring Shooter countdown --- src/p_mobj.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/p_mobj.c b/src/p_mobj.c index 07b5840be..fbdf33252 100644 --- a/src/p_mobj.c +++ b/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)) { @@ -4657,11 +4657,8 @@ static void P_RingShooterCountdown(mobj_t *mo) if (mo->reactiontime == -1) return; - if (mo->reactiontime > 0) - { - mo->reactiontime--; + if (--mo->reactiontime > 0) return; - } while (!P_MobjWasRemoved(part->tracer)) { @@ -10563,7 +10560,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 From 668d832ca11965c45d7f54150d2864d39f4a2b22 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 10 Apr 2023 21:45:45 -0400 Subject: [PATCH 51/65] Delete respawn command, put Ring shooter on Y --- src/d_netcmd.c | 63 ---------------------- src/d_netcmd.h | 39 +++++++------- src/d_ticcmd.h | 6 ++- src/g_demo.c | 8 --- src/g_demo.h | 2 +- src/g_game.c | 12 +++++ src/g_input.h | 12 ++--- src/k_kart.c | 44 +++++++++++++-- src/k_kart.h | 1 + src/menus/options-profiles-edit-controls.c | 11 ++-- 10 files changed, 87 insertions(+), 111 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 4a83f9b27..7c4296a43 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -92,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); @@ -177,7 +176,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 @@ -606,7 +604,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 @@ -664,7 +661,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); @@ -711,7 +707,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); @@ -3316,64 +3311,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; - - if (K_PlayerEBrake(&players[respawnplayer])) - K_SpawnRingShooter(&players[respawnplayer]); - else - 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. diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 31f430ed2..0eee560d5 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -158,28 +158,27 @@ 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_PARTYINVITE, // 27 - XD_ACCEPTPARTYINVITE, // 28 - XD_LEAVEPARTY, // 29 - XD_CANCELPARTYINVITE, // 30 - XD_CHEAT, // 31 - XD_ADDBOT, // 32 - XD_DISCORD, // 33 - XD_PLAYSOUND, // 34 - XD_SCHEDULETASK, // 35 - XD_SCHEDULECLEAR, // 36 - XD_AUTOMATE, // 37 + 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 MAXNETXCMD } netxcmd_t; diff --git a/src/d_ticcmd.h b/src/d_ticcmd.h index a3cdf4d9b..4f36a0425 100644 --- a/src/d_ticcmd.h +++ b/src/d_ticcmd.h @@ -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, diff --git a/src/g_demo.c b/src/g_demo.c index da617188a..0d9ffa409 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -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); diff --git a/src/g_demo.h b/src/g_demo.h index 4b4195e2d..661dacada 100644 --- a/src/g_demo.h +++ b/src/g_demo.h @@ -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 diff --git a/src/g_game.c b/src/g_game.c index 7a767bdf7..9f198c33b 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1340,6 +1340,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; + } + + // 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; } diff --git a/src/g_input.h b/src/g_input.h index 9441a7ef1..d90092971 100644 --- a/src/g_input.h +++ b/src/g_input.h @@ -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 diff --git a/src/k_kart.c b/src/k_kart.c index d4eb041a4..6d3769d8b 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -11387,6 +11387,15 @@ void K_MoveKartPlayer(player_t *player, boolean onground) { player->pflags &= ~PF_AIRFAILSAFE; } + + if (K_AllowRingShooter(player) == true) + { + if ((cmd->buttons & BT_RESPAWN) + && !(player->oldcmd.buttons & BT_RESPAWN)) + { + K_SpawnRingShooter(player); + } + } } void K_CheckSpectateStatus(void) @@ -11775,6 +11784,31 @@ boolean K_Cooperative(void) //} +boolean K_AllowRingShooter(player_t *player) +{ + const fixed_t minSpeed = 6 * player->mo->scale; + + if (player->respawn.state != RESPAWNST_NONE + && player->respawn.init == true) + { + 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; +} + // I've tried to reduce redundancy as much as I can, // but check P_UpdateRingShooterParts if you edit this void K_SpawnRingShooter(player_t *player) @@ -11792,7 +11826,7 @@ void K_SpawnRingShooter(player_t *player) K_FlipFromObject(base, mo); P_SetTarget(&base->target, mo); P_SetScale(base, base->destscale = FixedMul(base->destscale, scale)); - P_InitAngle(base, mo->angle); + base->angle = mo->angle; base->scalespeed = FRACUNIT/2; base->extravalue1 = FRACUNIT; // horizontal scale base->extravalue2 = 0; // vertical scale @@ -11818,7 +11852,7 @@ void K_SpawnRingShooter(player_t *player) part = part->hprev; P_SetTarget(&part->target, base); - P_InitAngle(part, base->angle - i * ANGLE_45); + part->angle = base->angle - i * ANGLE_45; P_SetMobjState(part, S_RINGSHOOTER_NIPPLES); part->frame += frameNum; part->flags |= MF_NOTHINK; @@ -11849,7 +11883,7 @@ void K_SpawnRingShooter(player_t *player) frameNum++; frameNum ^= FF_HORIZONTALFLIP; angle -= ANGLE_90; - P_InitAngle(part, angle); + part->angle = angle; part->frame += frameNum; part->extravalue1 = part->x - refNipple->x; part->extravalue2 = part->y - refNipple->y; @@ -11861,7 +11895,7 @@ void K_SpawnRingShooter(player_t *player) part = P_SpawnMobjFromMobj(base, offset.x, offset.y, 0, MT_RINGSHOOTER_SCREEN); P_SetTarget(&base->tracer, part); P_SetTarget(&part->target, base); - P_InitAngle(part, base->angle - ANGLE_45); + part->angle = base->angle - ANGLE_45; part->extravalue1 = part->x - refNipple->x; part->extravalue2 = part->y - refNipple->y; part->flags |= MF_NOTHINK; @@ -11873,7 +11907,7 @@ void K_SpawnRingShooter(player_t *player) P_SetTarget(&part->tracer, P_SpawnMobjFromMobj(part, 0, 0, 0, MT_OVERLAY)); P_SetTarget(&part->tracer->target, part); part = part->tracer; - P_InitAngle(part, part->target->angle); + part->angle = part->target->angle; P_SetMobjState(part, S_RINGSHOOTER_NUMBERBACK + i); part->renderflags |= RF_DONTDRAW; } diff --git a/src/k_kart.h b/src/k_kart.h index 0c91fe601..bde76ffe6 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -189,6 +189,7 @@ boolean K_IsSPBInGame(void); void K_KartEbrakeVisuals(player_t *p); void K_HandleDirectionalInfluence(player_t *player); fixed_t K_DefaultPlayerRadius(player_t *player); +boolean K_AllowRingShooter(player_t *player); void K_SpawnRingShooter(player_t *player); // sound stuff for lua diff --git a/src/menus/options-profiles-edit-controls.c b/src/menus/options-profiles-edit-controls.c index 9fded87e0..b97b7f5b7 100644 --- a/src/menus/options-profiles-edit-controls.c +++ b/src/menus/options-profiles-edit-controls.c @@ -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.", From 319cee4afa7eaa2e33c6c7d3627ad66f42451d32 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 10 Apr 2023 22:33:46 -0400 Subject: [PATCH 52/65] Move ring shooter to its own file --- src/d_player.h | 2 +- src/k_kart.c | 138 +-------------- src/k_kart.h | 2 - src/k_objects.h | 4 + src/objects/CMakeLists.txt | 1 + src/objects/ring-shooter.c | 349 +++++++++++++++++++++++++++++++++++++ src/p_mobj.c | 160 +---------------- 7 files changed, 357 insertions(+), 299 deletions(-) create mode 100644 src/objects/ring-shooter.c diff --git a/src/d_player.h b/src/d_player.h index f1638024d..6e165c75f 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -506,7 +506,7 @@ struct player_t 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 + 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; diff --git a/src/k_kart.c b/src/k_kart.c index 6d3769d8b..3d7311e98 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -11388,14 +11388,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) player->pflags &= ~PF_AIRFAILSAFE; } - if (K_AllowRingShooter(player) == true) - { - if ((cmd->buttons & BT_RESPAWN) - && !(player->oldcmd.buttons & BT_RESPAWN)) - { - K_SpawnRingShooter(player); - } - } + Obj_RingShooterInput(player); } void K_CheckSpectateStatus(void) @@ -11783,132 +11776,3 @@ boolean K_Cooperative(void) } //} - -boolean K_AllowRingShooter(player_t *player) -{ - const fixed_t minSpeed = 6 * player->mo->scale; - - if (player->respawn.state != RESPAWNST_NONE - && player->respawn.init == true) - { - 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; -} - -// I've tried to reduce redundancy as much as I can, -// but check P_UpdateRingShooterParts if you edit this -void K_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; - - K_FlipFromObject(base, mo); - P_SetTarget(&base->target, 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 - - // the ring shooter object itself is invisible and acts as the thinker - // each ring shooter uses three 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 - - // 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; - } -} diff --git a/src/k_kart.h b/src/k_kart.h index bde76ffe6..782740ce8 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -189,8 +189,6 @@ boolean K_IsSPBInGame(void); void K_KartEbrakeVisuals(player_t *p); void K_HandleDirectionalInfluence(player_t *player); fixed_t K_DefaultPlayerRadius(player_t *player); -boolean K_AllowRingShooter(player_t *player); -void K_SpawnRingShooter(player_t *player); // sound stuff for lua void K_PlayAttackTaunt(mobj_t *source); diff --git a/src/k_objects.h b/src/k_objects.h index a46c3513f..7e7c3871b 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -106,6 +106,10 @@ 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 */ +void Obj_RingShooterThinker(mobj_t *mo); +void Obj_RingShooterInput(player_t *player); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index ef74b299f..609f18de4 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -14,4 +14,5 @@ target_sources(SRB2SDL2 PRIVATE item-spot.c loops.c drop-target.c + ring-shooter.c ) diff --git a/src/objects/ring-shooter.c b/src/objects/ring-shooter.c new file mode 100644 index 000000000..ac5eb9d52 --- /dev/null +++ b/src/objects/ring-shooter.c @@ -0,0 +1,349 @@ +// 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" + +#define RS_FUSE_TIME (4*TICRATE) + +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; + } +} + +#define scaleSpeed mo->scalespeed +#define scaleState mo->threshold +#define xScale mo->extravalue1 +#define yScale mo->extravalue2 +#define xOffset part->extravalue1 +#define yOffset part->extravalue2 +#define SCALEPART part->spritexscale = xScale; part->spriteyscale = yScale; +#define MOVEPART P_MoveOrigin(part, refNipple->x + FixedMul(xOffset, xScale), refNipple->y + FixedMul(yOffset, xScale), 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->hprev)) + { + part = part->hprev; + SCALEPART + } + refNipple = part; + + part = mo; + while (!P_MobjWasRemoved(part->hnext)) + { + part = part->hnext; + MOVEPART + SCALEPART + } + + part = mo->tracer; + part->z = mo->z + FixedMul(refNipple->height, yScale); + MOVEPART + SCALEPART +} + +static boolean RingShooterInit(mobj_t *mo) +{ + if (scaleState == -1) + { + return false; + } + + switch (scaleState) + { + case 0: + { + yScale += scaleSpeed; + if (yScale >= FRACUNIT) + { + //xScale -= scaleSpeed; + scaleState++; + } + break; + } + case 1: + { + scaleSpeed -= FRACUNIT/5; + yScale += scaleSpeed; + xScale -= scaleSpeed; + if (yScale < 3*FRACUNIT/4) + { + scaleState ++; + scaleSpeed = FRACUNIT >> 2; + } + break; + } + case 2: + { + yScale += scaleSpeed; + xScale -= scaleSpeed; + if (yScale >= FRACUNIT) + { + scaleState = -1; + xScale = yScale = FRACUNIT; + ActivateRingShooter(mo); + } + } + } + + UpdateRingShooterParts(mo); + return scaleState != -1; +} +#undef scaleSpeed +#undef scaleState +#undef xScale +#undef yScale +#undef xOffset +#undef yOffset +#undef MOVEPART +#undef SCALEPART + +static void RingShooterCountdown(mobj_t *mo) +{ + mobj_t *part = mo->tracer; + + if (mo->reactiontime == -1) + { + 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; + part->skin = mo->skin; + P_SetMobjState(part, S_RINGSHOOTER_FACE); + break; + } + case 0: + { + mo->reactiontime = TICRATE; + S_StartSound(mo, mo->info->deathsound); + 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; +} + +void Obj_RingShooterThinker(mobj_t *mo) +{ + if (P_MobjWasRemoved(mo->tracer) || RingShooterInit(mo)) + return; + + RingShooterCountdown(mo); + RingShooterFlicker(mo); +} + +static boolean AllowRingShooter(player_t *player) +{ + const fixed_t minSpeed = 6 * player->mo->scale; + + if (player->respawn.state != RESPAWNST_NONE + && player->respawn.init == true) + { + 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; +} + +// 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; + + K_FlipFromObject(base, mo); + P_SetTarget(&base->target, 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 three 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 + + // 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; + } +} + +void Obj_RingShooterInput(player_t *player) +{ + if (AllowRingShooter(player) == true + && (player->cmd.buttons & BT_RESPAWN) == BT_RESPAWN + && (player->oldcmd.buttons & BT_RESPAWN) == 0) + { + SpawnRingShooter(player); + } +} diff --git a/src/p_mobj.c b/src/p_mobj.c index fbdf33252..f26eeaea6 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -4550,164 +4550,6 @@ static void P_SpawnItemCapsuleParts(mobj_t *mobj) #undef ANG_CAPSULE #undef ROTATIONSPEED -// ------------ -// RING SHOOTER -// ------------ - -static void P_ActivateRingShooter(mobj_t *mo) -{ - mobj_t *part = mo->tracer; - - while (!P_MobjWasRemoved(part->tracer)) - { - part = part->tracer; - part->renderflags &= ~RF_DONTDRAW; - part->frame += 4; - } -} - -#define scaleSpeed mo->scalespeed -#define scaleState mo->threshold -#define xScale mo->extravalue1 -#define yScale mo->extravalue2 -#define xOffset part->extravalue1 -#define yOffset part->extravalue2 -#define SCALEPART part->spritexscale = xScale; part->spriteyscale = yScale; -#define MOVEPART P_MoveOrigin(part, refNipple->x + FixedMul(xOffset, xScale), refNipple->y + FixedMul(yOffset, xScale), part->z); - -// I've tried to reduce redundancy as much as I can, -// but check K_SpawnRingShooter if you edit this -static void P_UpdateRingShooterParts(mobj_t *mo) -{ - mobj_t *part, *refNipple; - - part = mo; - while (!P_MobjWasRemoved(part->hprev)) - { - part = part->hprev; - SCALEPART - } - refNipple = part; - - part = mo; - while (!P_MobjWasRemoved(part->hnext)) - { - part = part->hnext; - MOVEPART - SCALEPART - } - - part = mo->tracer; - part->z = mo->z + FixedMul(refNipple->height, yScale); - MOVEPART - SCALEPART -} - -static boolean P_RingShooterInit(mobj_t *mo) -{ - if (scaleState == -1) - return false; - - switch (scaleState) { - case 0: - yScale += scaleSpeed; - if (yScale >= FRACUNIT) - { - //xScale -= scaleSpeed; - scaleState++; - } - break; - case 1: - scaleSpeed -= FRACUNIT/5; - yScale += scaleSpeed; - xScale -= scaleSpeed; - if (yScale < 3*FRACUNIT/4) - { - scaleState ++; - scaleSpeed = FRACUNIT >> 2; - } - break; - case 2: - yScale += scaleSpeed; - xScale -= scaleSpeed; - if (yScale >= FRACUNIT) - { - scaleState = -1; - xScale = yScale = FRACUNIT; - P_ActivateRingShooter(mo); - } - } - - P_UpdateRingShooterParts(mo); - return scaleState != -1; -} -#undef scaleSpeed -#undef scaleState -#undef xScale -#undef yScale -#undef xOffset -#undef yOffset -#undef MOVEPART -#undef SCALEPART - -static void P_RingShooterCountdown(mobj_t *mo) -{ - mobj_t *part = mo->tracer; - - if (mo->reactiontime == -1) - 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; - part->skin = mo->skin; - P_SetMobjState(part, S_RINGSHOOTER_FACE); - break; - case 0: - mo->reactiontime = TICRATE; - S_StartSound(mo, mo->info->deathsound); - break; - default: - mo->reactiontime = TICRATE; - S_StartSound(mo, mo->info->painsound); - break; - } -} - -static void P_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 P_RingShooterThinker(mobj_t *mo) -{ - if (P_MobjWasRemoved(mo->tracer) || P_RingShooterInit(mo)) - return; - - P_RingShooterCountdown(mo); - P_RingShooterFlicker(mo); -} - // // P_BossTargetPlayer // If closest is true, find the closest player. @@ -6800,7 +6642,7 @@ static void P_MobjSceneryThink(mobj_t *mobj) } break; case MT_RINGSHOOTER: - P_RingShooterThinker(mobj); + Obj_RingShooterThinker(mobj); break; case MT_SPINDASHWIND: case MT_DRIFTELECTRICSPARK: From d28601fd52cb0d47ffb2362348cf51c08636c26a Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Wed, 12 Apr 2023 01:48:43 -0400 Subject: [PATCH 53/65] Use less confusing defines for ring shooter --- src/objects/ring-shooter.c | 87 ++++++++++++++++++++------------------ 1 file changed, 47 insertions(+), 40 deletions(-) diff --git a/src/objects/ring-shooter.c b/src/objects/ring-shooter.c index ac5eb9d52..53e1d077b 100644 --- a/src/objects/ring-shooter.c +++ b/src/objects/ring-shooter.c @@ -40,14 +40,29 @@ static void ActivateRingShooter(mobj_t *mo) } } -#define scaleSpeed mo->scalespeed -#define scaleState mo->threshold -#define xScale mo->extravalue1 -#define yScale mo->extravalue2 -#define xOffset part->extravalue1 -#define yOffset part->extravalue2 -#define SCALEPART part->spritexscale = xScale; part->spriteyscale = yScale; -#define MOVEPART P_MoveOrigin(part, refNipple->x + FixedMul(xOffset, xScale), refNipple->y + FixedMul(yOffset, xScale), part->z); +#define rs_base_scalespeed(o) ((o)->scalespeed) +#define rs_base_scalestate(o) ((o)->threshold) +#define rs_base_xscale(o) ((o)->extravalue1) +#define rs_base_yscale(o) ((o)->extravalue2) + +#define rs_part_xoffset(o) ((o)->extravalue1) +#define rs_part_yoffset(o) ((o)->extravalue2) + +static void ScalePart(mobj_t *part, mobj_t *base) +{ + part->spritexscale = rs_base_xscale(base); + part->spriteyscale = rs_base_yscale(base); +} + +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 + ); +} // I've tried to reduce redundancy as much as I can, // but check K_SpawnRingShooter if you edit this @@ -59,7 +74,7 @@ static void UpdateRingShooterParts(mobj_t *mo) while (!P_MobjWasRemoved(part->hprev)) { part = part->hprev; - SCALEPART + ScalePart(part, mo); } refNipple = part; @@ -67,71 +82,63 @@ static void UpdateRingShooterParts(mobj_t *mo) while (!P_MobjWasRemoved(part->hnext)) { part = part->hnext; - MOVEPART - SCALEPART + MovePart(part, mo, refNipple); + ScalePart(part, mo); } part = mo->tracer; - part->z = mo->z + FixedMul(refNipple->height, yScale); - MOVEPART - SCALEPART + part->z = mo->z + FixedMul(refNipple->height, rs_base_yscale(mo)); + MovePart(part, mo, refNipple); + ScalePart(part, mo); } static boolean RingShooterInit(mobj_t *mo) { - if (scaleState == -1) + if (rs_base_scalestate(mo) == -1) { return false; } - switch (scaleState) + switch (rs_base_scalestate(mo)) { case 0: { - yScale += scaleSpeed; - if (yScale >= FRACUNIT) + rs_base_yscale(mo) += rs_base_scalespeed(mo); + if (rs_base_yscale(mo) >= FRACUNIT) { - //xScale -= scaleSpeed; - scaleState++; + //rs_base_xscale(mo) -= rs_base_scalespeed(mo); + rs_base_scalestate(mo)++; } break; } case 1: { - scaleSpeed -= FRACUNIT/5; - yScale += scaleSpeed; - xScale -= scaleSpeed; - if (yScale < 3*FRACUNIT/4) + 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) { - scaleState ++; - scaleSpeed = FRACUNIT >> 2; + rs_base_scalestate(mo)++; + rs_base_scalespeed(mo) = FRACUNIT >> 2; } break; } case 2: { - yScale += scaleSpeed; - xScale -= scaleSpeed; - if (yScale >= FRACUNIT) + rs_base_yscale(mo) += rs_base_scalespeed(mo); + rs_base_xscale(mo) -= rs_base_scalespeed(mo); + if (rs_base_yscale(mo) >= FRACUNIT) { - scaleState = -1; - xScale = yScale = FRACUNIT; + rs_base_scalestate(mo) = -1; + rs_base_xscale(mo) = rs_base_yscale(mo) = FRACUNIT; ActivateRingShooter(mo); } } } UpdateRingShooterParts(mo); - return scaleState != -1; + return (rs_base_scalestate(mo) != -1); } -#undef scaleSpeed -#undef scaleState -#undef xScale -#undef yScale -#undef xOffset -#undef yOffset -#undef MOVEPART -#undef SCALEPART static void RingShooterCountdown(mobj_t *mo) { From 4e7b6f0cc3e2f9ccf3ba7160c9157f3bc78bcdef Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Wed, 12 Apr 2023 21:08:45 -0400 Subject: [PATCH 54/65] Add ring shooter tire grabbers + more player logic --- src/d_clisrv.c | 1 + src/d_player.h | 3 +- src/g_game.c | 1 + src/k_objects.h | 3 +- src/objects/ring-shooter.c | 303 ++++++++++++++++++++++++++++++++++--- src/p_mobj.c | 82 ++++++---- src/p_saveg.c | 33 +++- 7 files changed, 366 insertions(+), 60 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 37f0db6f2..24e8a5166 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -2776,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. diff --git a/src/d_player.h b/src/d_player.h index 6e165c75f..4ecfef161 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -505,7 +505,8 @@ struct player_t UINT32 distancetofinish; waypoint_t *currentwaypoint; waypoint_t *nextwaypoint; - respawnvars_t respawn; // Respawn info + 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 diff --git a/src/g_game.c b/src/g_game.c index 9f198c33b..97cc64c64 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2645,6 +2645,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; diff --git a/src/k_objects.h b/src/k_objects.h index 7e7c3871b..6b45dbb55 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -107,8 +107,9 @@ void Obj_BeginDropTargetMorph(mobj_t *target, skincolornum_t color); boolean Obj_DropTargetMorphThink(mobj_t *morph); /* Ring Shooter */ -void Obj_RingShooterThinker(mobj_t *mo); +boolean Obj_RingShooterThinker(mobj_t *mo); void Obj_RingShooterInput(player_t *player); +void Obj_RingShooterDelete(mobj_t *mo); #ifdef __cplusplus } // extern "C" diff --git a/src/objects/ring-shooter.c b/src/objects/ring-shooter.c index 53e1d077b..740fa3cd1 100644 --- a/src/objects/ring-shooter.c +++ b/src/objects/ring-shooter.c @@ -25,33 +25,76 @@ #include "../k_waypoint.h" #include "../r_skins.h" #include "../k_respawn.h" +#include "../lua_hook.h" #define RS_FUSE_TIME (4*TICRATE) -static void ActivateRingShooter(mobj_t *mo) -{ - mobj_t *part = mo->tracer; +#define RS_GRABBER_START (16 << FRACBITS) +#define RS_GRABBER_SLIDE (RS_GRABBER_START >> 4) +#define RS_GRABBER_EXTRA (18 << FRACBITS) - while (!P_MobjWasRemoved(part->tracer)) - { - part = part->tracer; - part->renderflags &= ~RF_DONTDRAW; - part->frame += 4; - } -} +#define RS_KARTED_INC (3) #define rs_base_scalespeed(o) ((o)->scalespeed) #define rs_base_scalestate(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_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) @@ -64,12 +107,43 @@ static void MovePart(mobj_t *part, mobj_t *base, mobj_t *refNipple) ); } +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)) { @@ -92,6 +166,47 @@ static void UpdateRingShooterParts(mobj_t *mo) 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 ActivateRingShooter(mobj_t *mo) +{ + mobj_t *part = mo->tracer; + + while (!P_MobjWasRemoved(part->tracer)) + { + part = part->tracer; + part->renderflags &= ~RF_DONTDRAW; + part->frame += 4; + } +} + static boolean RingShooterInit(mobj_t *mo) { if (rs_base_scalestate(mo) == -1) @@ -129,10 +244,26 @@ static boolean RingShooterInit(mobj_t *mo) rs_base_xscale(mo) -= rs_base_scalespeed(mo); if (rs_base_yscale(mo) >= FRACUNIT) { - rs_base_scalestate(mo) = -1; + rs_base_scalestate(mo)++; rs_base_xscale(mo) = rs_base_yscale(mo) = FRACUNIT; + } + break; + } + case 3: + { + rs_base_grabberdist(mo) -= RS_GRABBER_SLIDE; + if (rs_base_grabberdist(mo) <= 0) + { + rs_base_scalestate(mo) = -1; + rs_base_grabberdist(mo) = 0; ActivateRingShooter(mo); } + break; + } + default: + { + rs_base_scalestate(mo) = 0; // fix invalid states + break; } } @@ -206,13 +337,87 @@ static void RingShooterFlicker(mobj_t *mo) part->target->frame = (part->target->frame & ~FF_TRANSMASK) | trans; } -void Obj_RingShooterThinker(mobj_t *mo) +boolean Obj_RingShooterThinker(mobj_t *mo) { - if (P_MobjWasRemoved(mo->tracer) || RingShooterInit(mo)) - return; + if (RingShooterInit(mo) == true) + { + return true; + } - RingShooterCountdown(mo); - RingShooterFlicker(mo); + 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); + RingShooterFlicker(mo); + } + } + + if (mo->fuse < TICRATE) + { + if (leveltime & 1) + { + mo->renderflags |= RF_DONTDRAW; + } + else + { + mo->renderflags &= ~RF_DONTDRAW; + } + + UpdateRingShooterPartsVisibility(mo); + } + + return true; +} + +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); + } } static boolean AllowRingShooter(player_t *player) @@ -254,8 +459,11 @@ static void SpawnRingShooter(player_t *player) vector2_t offset; SINT8 i; + rs_base_playerid(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_SetTarget(&base->target, mo); P_SetScale(base, base->destscale = FixedMul(base->destscale, scale)); base->angle = mo->angle; base->scalespeed = FRACUNIT/2; @@ -264,10 +472,11 @@ static void SpawnRingShooter(player_t *player) base->fuse = RS_FUSE_TIME; // the ring shooter object itself is invisible and acts as the thinker - // each ring shooter uses three linked lists to keep track of its parts + // 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; @@ -343,14 +552,66 @@ static void SpawnRingShooter(player_t *player) P_SetMobjState(part, S_RINGSHOOTER_NUMBERBACK + i); part->renderflags |= RF_DONTDRAW; } + + // 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); } void Obj_RingShooterInput(player_t *player) { + mobj_t *const base = player->ringShooter; + if (AllowRingShooter(player) == true - && (player->cmd.buttons & BT_RESPAWN) == BT_RESPAWN - && (player->oldcmd.buttons & BT_RESPAWN) == 0) + && (player->cmd.buttons & BT_RESPAWN) == BT_RESPAWN) { - SpawnRingShooter(player); + if (P_MobjWasRemoved(base) == true) + { + SpawnRingShooter(player); + return; + } + + if (rs_base_canceled(base) != 0) + { + if (base->fuse < TICRATE) + { + base->renderflags &= ~RF_DONTDRAW; + UpdateRingShooterPartsVisibility(base); + } + + base->fuse = RS_FUSE_TIME; + } + } + else if (P_MobjWasRemoved(base) == false) + { + if (rs_base_scalestate(base) != -1) + { + // We released during the intro animation. + // Cancel it entirely. + rs_base_canceled(base) = 1; + } } } diff --git a/src/p_mobj.c b/src/p_mobj.c index f26eeaea6..af2394298 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -6642,7 +6642,10 @@ static void P_MobjSceneryThink(mobj_t *mobj) } break; case MT_RINGSHOOTER: - Obj_RingShooterThinker(mobj); + if (Obj_RingShooterThinker(mobj) == false) + { + return; + } break; case MT_SPINDASHWIND: case MT_DRIFTELECTRICSPARK: @@ -11143,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) { @@ -11170,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 diff --git a/src/p_saveg.c b/src/p_saveg.c index c31161f40..0e335c210 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -63,14 +63,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) @@ -217,6 +218,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) @@ -240,6 +244,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); @@ -613,6 +620,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); @@ -4823,6 +4833,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); + } } } From 92b8e38f11fdb3d360547f2daec3133ee492e40c Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Thu, 13 Apr 2023 00:46:24 -0400 Subject: [PATCH 55/65] Actual Ring Shooter functionality --- src/info.c | 6 +- src/k_objects.h | 2 + src/objects/ring-shooter.c | 349 +++++++++++++++++++++++++------------ src/p_enemy.c | 36 +--- src/p_inter.c | 4 + 5 files changed, 248 insertions(+), 149 deletions(-) diff --git a/src/info.c b/src/info.c index a082abbc5..cfa23907a 100644 --- a/src/info.c +++ b/src/info.c @@ -24679,13 +24679,13 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL, // xdeathstate sfx_s3kad, // deathsound 0, // speed - 16*FRACUNIT, // radius - 16*FRACUNIT, // height + 24*FRACUNIT, // radius + 8*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 + MF_NOGRAVITY|MF_SCENERY|MF_DONTENCOREMAP, // flags S_NULL // raisestate }, diff --git a/src/k_objects.h b/src/k_objects.h index 6b45dbb55..9bb8d70d2 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -109,7 +109,9 @@ boolean Obj_DropTargetMorphThink(mobj_t *morph); /* Ring Shooter */ boolean Obj_RingShooterThinker(mobj_t *mo); 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" diff --git a/src/objects/ring-shooter.c b/src/objects/ring-shooter.c index 740fa3cd1..c1c60c49d 100644 --- a/src/objects/ring-shooter.c +++ b/src/objects/ring-shooter.c @@ -28,6 +28,7 @@ #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) @@ -36,7 +37,7 @@ #define RS_KARTED_INC (3) #define rs_base_scalespeed(o) ((o)->scalespeed) -#define rs_base_scalestate(o) ((o)->threshold) +#define rs_base_initstate(o) ((o)->threshold) #define rs_base_xscale(o) ((o)->extravalue1) #define rs_base_yscale(o) ((o)->extravalue2) @@ -44,6 +45,7 @@ #define rs_base_karted(o) ((o)->movecount) #define rs_base_grabberdist(o) ((o)->movefactor) #define rs_base_canceled(o) ((o)->cvmem) +#define rs_base_playerface(o) ((o)->cusval) #define rs_part_xoffset(o) ((o)->extravalue1) #define rs_part_yoffset(o) ((o)->extravalue2) @@ -195,87 +197,11 @@ static void UpdateRingShooterPartsVisibility(mobj_t *mo) ShowHidePart(part, mo); } -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; - } -} - -static boolean RingShooterInit(mobj_t *mo) -{ - if (rs_base_scalestate(mo) == -1) - { - return false; - } - - switch (rs_base_scalestate(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_scalestate(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_scalestate(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_scalestate(mo)++; - rs_base_xscale(mo) = rs_base_yscale(mo) = FRACUNIT; - } - break; - } - case 3: - { - rs_base_grabberdist(mo) -= RS_GRABBER_SLIDE; - if (rs_base_grabberdist(mo) <= 0) - { - rs_base_scalestate(mo) = -1; - rs_base_grabberdist(mo) = 0; - ActivateRingShooter(mo); - } - break; - } - default: - { - rs_base_scalestate(mo) = 0; // fix invalid states - break; - } - } - - UpdateRingShooterParts(mo); - return (rs_base_scalestate(mo) != -1); -} - static void RingShooterCountdown(mobj_t *mo) { mobj_t *part = mo->tracer; - if (mo->reactiontime == -1) + if (mo->reactiontime < 0) { return; } @@ -296,7 +222,16 @@ static void RingShooterCountdown(mobj_t *mo) case -1: { mo->reactiontime = -1; - part->skin = mo->skin; + + 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; } @@ -304,6 +239,15 @@ static void RingShooterCountdown(mobj_t *mo) { 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: @@ -337,6 +281,92 @@ static void RingShooterFlicker(mobj_t *mo) 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) @@ -355,18 +385,22 @@ boolean Obj_RingShooterThinker(mobj_t *mo) } } - if (rs_base_canceled(mo) != 0) + if (rs_base_canceled(mo) == 0) { rs_base_karted(mo) += RS_KARTED_INC; if (P_MobjWasRemoved(mo->tracer) == false) { RingShooterCountdown(mo); - RingShooterFlicker(mo); } } - if (mo->fuse < TICRATE) + if (P_MobjWasRemoved(mo->tracer) == false) + { + RingShooterFlicker(mo); + } + + if (mo->fuse < RS_FUSE_BLINK) { if (leveltime & 1) { @@ -383,6 +417,29 @@ boolean Obj_RingShooterThinker(mobj_t *mo) return true; } +void Obj_PlayerUsedRingShooter(mobj_t *base, player_t *player) +{ + // 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); + } + K_DoIngameRespawn(player); + + // Now other players can run into it! + base->flags |= MF_SPECIAL; + + if (base->fuse < RS_FUSE_TIME) + { + // Reset the fuse so everyone can conga line :B + base->fuse = RS_FUSE_TIME; + } +} + void Obj_RingShooterDelete(mobj_t *mo) { mobj_t *part; @@ -420,31 +477,6 @@ void Obj_RingShooterDelete(mobj_t *mo) } } -static boolean AllowRingShooter(player_t *player) -{ - const fixed_t minSpeed = 6 * player->mo->scale; - - if (player->respawn.state != RESPAWNST_NONE - && player->respawn.init == true) - { - 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; -} - // 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) @@ -553,6 +585,8 @@ static void SpawnRingShooter(player_t *player) part->renderflags |= RF_DONTDRAW; } + P_SetTarget(&part->hprev, base); + // spawn the grabbers part = base; angle = base->angle + ANGLE_45; @@ -579,6 +613,36 @@ static void SpawnRingShooter(player_t *player) } 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; } void Obj_RingShooterInput(player_t *player) @@ -596,22 +660,85 @@ void Obj_RingShooterInput(player_t *player) if (rs_base_canceled(base) != 0) { - if (base->fuse < TICRATE) + if (base->fuse < RS_FUSE_BLINK) { base->renderflags &= ~RF_DONTDRAW; UpdateRingShooterPartsVisibility(base); } - base->fuse = RS_FUSE_TIME; + if (base->fuse < RS_FUSE_TIME) + { + base->fuse = RS_FUSE_TIME; + } } } else if (P_MobjWasRemoved(base) == false) { - if (rs_base_scalestate(base) != -1) + if (rs_base_initstate(base) != -1) { // We released during the intro animation. - // Cancel it entirely. + // 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; + + // 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) +} diff --git a/src/p_enemy.c b/src/p_enemy.c index 3278e8249..59979ac9a 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -13816,44 +13816,10 @@ A_SpawnItemDebrisCloud (mobj_t *actor) // vars do nothing void A_RingShooterFace(mobj_t *actor) { - player_t *player; - mobj_t *mo = actor; - if (LUA_CallAction(A_RINGSHOOTERFACE, actor)) - return; - - // get the player, if possible - while ((mo->player == NULL) && !P_MobjWasRemoved(mo->target)) - mo = mo->target; - - player = mo->player; - - if (!player) // something changed my target, abort - return; - - // 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 (actor->skin != &skins[player->skin]) { - actor->skin = &skins[player->skin]; - P_SetMobjState(actor, (statenum_t)(actor->state-states)); return; } - // okay, now steal the player's color nyehehehe - actor->color = player->skincolor; - - // set the frame to the WANTED pic - actor->frame = (actor->frame & ~FF_FRAMEMASK) | FACE_WANTED; - - // 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?) - actor->spritexscale = FixedDiv(26*FRACUNIT, 32*FRACUNIT); - actor->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 - actor->spritexoffset = 16*FRACUNIT; // 32 / 2 - actor->spriteyoffset = 28*FRACUNIT + FixedDiv(11*FRACUNIT, actor->spriteyscale); // 32 - 4 (generic monster bottom) + 11 (vertical offset of screen sprite from the bottom) + Obj_UpdateRingShooterFace(actor); } diff --git a/src/p_inter.c b/src/p_inter.c index dbb18b571..ad8542695 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -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; From 14d371f00f358e6d529b493d04567a854fa8a4fb Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Thu, 13 Apr 2023 03:47:24 -0400 Subject: [PATCH 56/65] Basic Ring Shooter freeze implementation It's probably too easy to break, but this should be enough for testing at least. --- src/k_kart.c | 4 +++- src/k_objects.h | 1 + src/objects/ring-shooter.c | 16 +++++++++++++++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 3d7311e98..3facc2b54 100644 --- a/src/k_kart.c +++ b/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; } diff --git a/src/k_objects.h b/src/k_objects.h index 9bb8d70d2..875f177a8 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -108,6 +108,7 @@ 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); diff --git a/src/objects/ring-shooter.c b/src/objects/ring-shooter.c index c1c60c49d..d9846dd9a 100644 --- a/src/objects/ring-shooter.c +++ b/src/objects/ring-shooter.c @@ -645,6 +645,20 @@ static boolean AllowRingShooter(player_t *player) 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; @@ -658,7 +672,7 @@ void Obj_RingShooterInput(player_t *player) return; } - if (rs_base_canceled(base) != 0) + if (rs_base_canceled(base) == 0) { if (base->fuse < RS_FUSE_BLINK) { From 3ea9b2d82ded1670fe1c06d42dd64a9549c07eb7 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Thu, 13 Apr 2023 03:56:49 -0400 Subject: [PATCH 57/65] Ring Shooter: Force player mom to 0 --- src/objects/ring-shooter.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/objects/ring-shooter.c b/src/objects/ring-shooter.c index d9846dd9a..39a6cf946 100644 --- a/src/objects/ring-shooter.c +++ b/src/objects/ring-shooter.c @@ -674,6 +674,8 @@ void Obj_RingShooterInput(player_t *player) if (rs_base_canceled(base) == 0) { + player->mo->momx = player->mo->momy = 0; + if (base->fuse < RS_FUSE_BLINK) { base->renderflags &= ~RF_DONTDRAW; From b123fd98a2674f9981b851aa90ce14bc9fa56793 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Thu, 13 Apr 2023 21:17:43 -0400 Subject: [PATCH 58/65] Ring Shooter: Disallow turning while in it --- src/k_kart.c | 6 ++++++ src/objects/ring-shooter.c | 1 + 2 files changed, 7 insertions(+) diff --git a/src/k_kart.c b/src/k_kart.c index 3facc2b54..a6485a378 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -8943,6 +8943,12 @@ 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 diff --git a/src/objects/ring-shooter.c b/src/objects/ring-shooter.c index 39a6cf946..c71613716 100644 --- a/src/objects/ring-shooter.c +++ b/src/objects/ring-shooter.c @@ -675,6 +675,7 @@ void Obj_RingShooterInput(player_t *player) if (rs_base_canceled(base) == 0) { player->mo->momx = player->mo->momy = 0; + P_SetPlayerAngle(player, base->angle); if (base->fuse < RS_FUSE_BLINK) { From 3b7ac38d9fa6976488d3cfba6b91d9c585855cd4 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Fri, 14 Apr 2023 05:34:44 -0400 Subject: [PATCH 59/65] Ring Shooter: E-Brake + adjust distance - Y is now additionally another macro for HOLD! - Disable HOLD! drop from respawning when done with Ring Shooter. - Immediate release Ring Shooter now goes back a waypoint, and does not have a minimum distance to go forward anymore. --- src/d_player.h | 1 + src/g_game.c | 3 +-- src/k_kart.c | 22 +++++++++++++++++----- src/k_kart.h | 1 + src/k_respawn.c | 27 +++++++++++++++++++++++---- src/objects/ring-shooter.c | 7 +++++++ 6 files changed, 50 insertions(+), 11 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index 4ecfef161..d0313aaed 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -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; }; diff --git a/src/g_game.c b/src/g_game.c index 97cc64c64..527ce39ad 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1324,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; } @@ -1343,7 +1342,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) // respawn if (G_PlayerInputDown(forplayer, gc_respawn, 0)) { - cmd->buttons |= BT_RESPAWN; + cmd->buttons |= (BT_RESPAWN | BT_EBRAKEMASK); } // mp general function button diff --git a/src/k_kart.c b/src/k_kart.c index a6485a378..ed24fef49 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -3420,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; } @@ -7549,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 @@ -8076,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; } @@ -8952,7 +8959,7 @@ INT16 K_GetKartTurnValue(player_t *player, INT16 turnvalue) 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 @@ -9738,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; } @@ -9748,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 diff --git a/src/k_kart.h b/src/k_kart.h index 782740ce8..bf67be518 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -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); diff --git a/src/k_respawn.c b/src/k_respawn.c index 252550f06..5573feea1 100644 --- a/src/k_respawn.c +++ b/src/k_respawn.c @@ -175,9 +175,25 @@ 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; + if (prevWP->numprevwaypoints > 0) + { + prevWP = prevWP->prevwaypoints[0]; + } + + 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 +481,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 +840,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) diff --git a/src/objects/ring-shooter.c b/src/objects/ring-shooter.c index c71613716..95d7f92b4 100644 --- a/src/objects/ring-shooter.c +++ b/src/objects/ring-shooter.c @@ -428,6 +428,8 @@ void Obj_PlayerUsedRingShooter(mobj_t *base, player_t *player) { player->airtime += rs_base_karted(base); } + + player->respawn.fromRingShooter = true; K_DoIngameRespawn(player); // Now other players can run into it! @@ -676,6 +678,11 @@ void Obj_RingShooterInput(player_t *player) { player->mo->momx = player->mo->momy = 0; P_SetPlayerAngle(player, base->angle); + P_MoveOrigin( + player->mo, + base->x, base->y, + base->z // TODO: reverse gravity + ); if (base->fuse < RS_FUSE_BLINK) { From c4a087790a296485deaa9869462068b56cc6e4ce Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 13 Apr 2023 16:22:28 +0100 Subject: [PATCH 60/65] Obj_UpdateRingShooterFace: Set threshold overlay flags (fixes scaling/alignment of face) --- src/objects/ring-shooter.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/objects/ring-shooter.c b/src/objects/ring-shooter.c index 95d7f92b4..9e93c8b00 100644 --- a/src/objects/ring-shooter.c +++ b/src/objects/ring-shooter.c @@ -755,6 +755,9 @@ void Obj_UpdateRingShooterFace(mobj_t *part) // 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?) From 1c4137648b450e8bbc405a4a7a6e94c392175121 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Fri, 14 Apr 2023 05:41:44 -0400 Subject: [PATCH 61/65] Ring Shooter: Fix sometimes invisible after use --- src/objects/ring-shooter.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/objects/ring-shooter.c b/src/objects/ring-shooter.c index 9e93c8b00..6fd4e5162 100644 --- a/src/objects/ring-shooter.c +++ b/src/objects/ring-shooter.c @@ -435,9 +435,15 @@ void Obj_PlayerUsedRingShooter(mobj_t *base, player_t *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) { - // Reset the fuse so everyone can conga line :B + if (base->fuse < RS_FUSE_BLINK) + { + base->renderflags &= ~RF_DONTDRAW; + UpdateRingShooterPartsVisibility(base); + } + base->fuse = RS_FUSE_TIME; } } @@ -684,14 +690,14 @@ void Obj_RingShooterInput(player_t *player) base->z // TODO: reverse gravity ); - if (base->fuse < RS_FUSE_BLINK) - { - base->renderflags &= ~RF_DONTDRAW; - UpdateRingShooterPartsVisibility(base); - } - if (base->fuse < RS_FUSE_TIME) { + if (base->fuse < RS_FUSE_BLINK) + { + base->renderflags &= ~RF_DONTDRAW; + UpdateRingShooterPartsVisibility(base); + } + base->fuse = RS_FUSE_TIME; } } From 5917861d172568c07ed2818e1d8da36fdf2ff2fe Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Fri, 14 Apr 2023 06:02:32 -0400 Subject: [PATCH 62/65] Ring Shooter: Ignore multiple uses from 1 player --- src/objects/ring-shooter.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/objects/ring-shooter.c b/src/objects/ring-shooter.c index 6fd4e5162..79d6f25a0 100644 --- a/src/objects/ring-shooter.c +++ b/src/objects/ring-shooter.c @@ -42,10 +42,12 @@ #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_base_playerface(o) ((o)->cusval) #define rs_part_xoffset(o) ((o)->extravalue1) #define rs_part_yoffset(o) ((o)->extravalue2) @@ -419,6 +421,12 @@ boolean Obj_RingShooterThinker(mobj_t *mo) 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); @@ -446,6 +454,9 @@ void Obj_PlayerUsedRingShooter(mobj_t *base, player_t *player) 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) @@ -499,7 +510,7 @@ static void SpawnRingShooter(player_t *player) vector2_t offset; SINT8 i; - rs_base_playerid(base) = -1; + 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; From 5b348ee1957af58e3e8ac6aa7c54fee43a825b9f Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 14 Apr 2023 17:53:21 +0100 Subject: [PATCH 63/65] K_DoIngameRespawn: Ring Shooter-induced lightsnake always starts from the previous SPAWNPOINT waypoint, not the previous waypoint in general --- src/k_respawn.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/k_respawn.c b/src/k_respawn.c index 5573feea1..8a7d9cc7f 100644 --- a/src/k_respawn.c +++ b/src/k_respawn.c @@ -178,9 +178,11 @@ void K_DoIngameRespawn(player_t *player) if (player->respawn.fromRingShooter == true) { waypoint_t *prevWP = player->respawn.wp; - if (prevWP->numprevwaypoints > 0) + while (prevWP->numprevwaypoints > 0) { prevWP = prevWP->prevwaypoints[0]; + if (K_GetWaypointIsSpawnpoint(prevWP) == true) + break; } const UINT32 dist = (player->airtime * 48); From e7896a7118809d8b2de090830d934dedb91d1f85 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 14 Apr 2023 20:19:00 +0100 Subject: [PATCH 64/65] Obj_RingShooterInput: Fix several issues with the to-spot teleport - Fix undesired fastfall bounce - Fix reverse gravity --- src/objects/ring-shooter.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/objects/ring-shooter.c b/src/objects/ring-shooter.c index 79d6f25a0..76f782f4e 100644 --- a/src/objects/ring-shooter.c +++ b/src/objects/ring-shooter.c @@ -695,11 +695,24 @@ void Obj_RingShooterInput(player_t *player) { 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, - base->z // TODO: reverse gravity + setz ); + player->fastfall = 0; if (base->fuse < RS_FUSE_TIME) { From 03ef0b4e75e9b404a22d0b2b9c79ef9c6b38d1d5 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 14 Apr 2023 20:20:00 +0100 Subject: [PATCH 65/65] No Ring Shooters before the start of the level in general, not just in Race/GTR_CIRCUIT --- src/objects/ring-shooter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/objects/ring-shooter.c b/src/objects/ring-shooter.c index 76f782f4e..3150cf16f 100644 --- a/src/objects/ring-shooter.c +++ b/src/objects/ring-shooter.c @@ -639,7 +639,7 @@ static boolean AllowRingShooter(player_t *player) { const fixed_t minSpeed = 6 * player->mo->scale; - if ((gametyperules & GTR_CIRCUIT) && leveltime < starttime) + if (/*(gametyperules & GTR_CIRCUIT) &&*/ leveltime < starttime) { return false; }