diff --git a/src/Makefile b/src/Makefile index 2367553ed..3c621001d 100644 --- a/src/Makefile +++ b/src/Makefile @@ -195,6 +195,14 @@ passthru_opts:= # separate suffix with an underscore exesuffix:=$(call _,$(EXESUFFIX)) +# If there are uncommitted changes +# -uno: disregard untracked files +# Warning: this can only be accurate for comptime.c since +# that file is always recompiled! +ifneq ($(shell git status --porcelain -uno),) +opts+=-DCOMPVERSION_UNCOMMITTED +endif + include Makefile.d/platform.mk include Makefile.d/features.mk include Makefile.d/versions.mk diff --git a/src/am_map.c b/src/am_map.c index 04806de94..1bfb145c4 100644 --- a/src/am_map.c +++ b/src/am_map.c @@ -454,7 +454,7 @@ boolean AM_Responder(event_t *ev) { INT32 rc = false; - if (devparm || cv_debug) // only automap in Debug Tails 01-19-2001 + if (devparm || cht_debug) // only automap in Debug Tails 01-19-2001 { if (!automapactive) { @@ -625,7 +625,7 @@ static inline void AM_doFollowPlayer(void) */ void AM_Ticker(void) { - if (!cv_debug) + if (!cht_debug) AM_Stop(); if (dedicated || !automapactive) diff --git a/src/byteptr.h b/src/byteptr.h index fd178ba3b..5d1b50630 100644 --- a/src/byteptr.h +++ b/src/byteptr.h @@ -139,12 +139,78 @@ FUNCINLINE static ATTRINLINE UINT32 readulong(void *ptr) #undef DEALIGNED -#define WRITESTRINGN(p,s,n) do { size_t tmp_i = 0; for (; tmp_i < n && s[tmp_i] != '\0'; tmp_i++) WRITECHAR(p, s[tmp_i]); if (tmp_i < n) WRITECHAR(p, '\0');} while (0) -#define WRITESTRING(p,s) do { size_t tmp_i = 0; for (; s[tmp_i] != '\0'; tmp_i++) WRITECHAR(p, s[tmp_i]); WRITECHAR(p, '\0');} while (0) -#define WRITEMEM(p,s,n) do { memcpy(p, s, n); p += n; } while (0) +#define WRITESTRINGN(p, s, n) do { \ + size_t tmp_i; \ + \ + for (tmp_i = 0; tmp_i < n && s[tmp_i] != '\0'; tmp_i++) \ + WRITECHAR(p, s[tmp_i]); \ + \ + if (tmp_i < n) \ + WRITECHAR(p, '\0'); \ +} while (0) -#define SKIPSTRING(p) while (READCHAR(p) != '\0') +#define WRITESTRINGL(p, s, n) do { \ + size_t tmp_i; \ + \ + for (tmp_i = 0; tmp_i < n - 1 && s[tmp_i] != '\0'; tmp_i++) \ + WRITECHAR(p, s[tmp_i]); \ + \ + WRITECHAR(p, '\0'); \ +} while (0) -#define READSTRINGN(p,s,n) do { size_t tmp_i = 0; for (; tmp_i < n && (s[tmp_i] = READCHAR(p)) != '\0'; tmp_i++); s[tmp_i] = '\0';} while (0) -#define READSTRING(p,s) do { size_t tmp_i = 0; for (; (s[tmp_i] = READCHAR(p)) != '\0'; tmp_i++); s[tmp_i] = '\0';} while (0) -#define READMEM(p,s,n) do { memcpy(s, p, n); p += n; } while (0) +#define WRITESTRING(p, s) do { \ + size_t tmp_i; \ + \ + for (tmp_i = 0; s[tmp_i] != '\0'; tmp_i++) \ + WRITECHAR(p, s[tmp_i]); \ + \ + WRITECHAR(p, '\0'); \ +} while (0) + +#define WRITEMEM(p, s, n) do { \ + memcpy(p, s, n); \ + p += n; \ +} while (0) + +#define SKIPSTRING(p) while (READCHAR(p) != '\0') + +#define SKIPSTRINGN(p, n) do { \ + size_t tmp_i = 0; \ + \ + while (tmp_i < n && READCHAR(p) != '\0') \ + tmp_i++; \ +} while (0) + +#define SKIPSTRINGL(p, n) SKIPSTRINGN(p, n) + +#define READSTRINGN(p, s, n) do { \ + size_t tmp_i = 0; \ + \ + while (tmp_i < n && (s[tmp_i] = READCHAR(p)) != '\0') \ + tmp_i++; \ + \ + s[tmp_i] = '\0'; \ +} while (0) + +#define READSTRINGL(p, s, n) do { \ + size_t tmp_i = 0; \ + \ + while (tmp_i < n - 1 && (s[tmp_i] = READCHAR(p)) != '\0') \ + tmp_i++; \ + \ + s[tmp_i] = '\0'; \ +} while (0) + +#define READSTRING(p, s) do { \ + size_t tmp_i = 0; \ + \ + while ((s[tmp_i] = READCHAR(p)) != '\0') \ + tmp_i++; \ + \ + s[tmp_i] = '\0'; \ +} while (0) + +#define READMEM(p, s, n) do { \ + memcpy(s, p, n); \ + p += n; \ +} while (0) diff --git a/src/command.c b/src/command.c index 0015bcf1c..a66522186 100644 --- a/src/command.c +++ b/src/command.c @@ -37,6 +37,7 @@ #include "r_data.h" // Color_cons_t #include "r_skins.h" #include "m_random.h" +#include "p_local.h" // P_ResetPlayerCheats //======== // protos. @@ -1891,7 +1892,6 @@ void CV_CheatsChanged(void) else { consvar_t *cvar; - UINT8 i; // Set everything back to default. for (cvar = consvar_vars; cvar; cvar = cvar->next) @@ -1899,15 +1899,9 @@ void CV_CheatsChanged(void) CV_SetCVar(cvar, cvar->defaultvalue, false); // Reset any other cheat command effects here, as well. - cv_debug = 0; + cht_debug = 0; - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i]) - continue; - - players[i].cheats = 0; - } + P_ResetPlayerCheats(); } } diff --git a/src/comptime.c b/src/comptime.c index 398eda074..81b5ec7d6 100644 --- a/src/comptime.c +++ b/src/comptime.c @@ -15,6 +15,13 @@ const char *comprevision = SRB2_COMP_REVISION; #elif (defined(COMPVERSION)) #include "comptime.h" +const int compuncommitted = +#if (defined(COMPVERSION_UNCOMMITTED)) +1; +#else +0; +#endif + #else const char *compbranch = "Unknown"; const char *comprevision = "illegal"; diff --git a/src/console.c b/src/console.c index 08d04e318..ede2f633a 100644 --- a/src/console.c +++ b/src/console.c @@ -1521,12 +1521,12 @@ void CONS_Alert(alerttype_t level, const char *fmt, ...) CONS_Printf("%s", txt); } -void CONS_Debug(INT32 debugflags, const char *fmt, ...) +void CONS_Debug(UINT32 debugflags, const char *fmt, ...) { va_list argptr; static char *txt = NULL; - if ((cv_debug & debugflags) != debugflags) + if ((cht_debug & debugflags) != debugflags) return; if (txt == NULL) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 0613ba13d..5cad5e080 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -266,7 +266,7 @@ void SendNetXCmdForPlayer(UINT8 playerid, netxcmd_t id, const void *param, size_ { if (localtextcmd[playerid][0]+2+nparam > MAXTEXTCMD) { - // for future reference: if (cv_debug) != debug disabled. + // for future reference: if (cht_debug) != debug disabled. CONS_Alert(CONS_ERROR, M_GetText("NetXCmd buffer full, cannot add netcmd %d! (size: %d, needed: %s)\n"), id, localtextcmd[playerid][0], sizeu1(nparam)); return; } @@ -895,8 +895,15 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime) netbuffer->packettype = PT_SERVERINFO; netbuffer->u.serverinfo._255 = 255; netbuffer->u.serverinfo.packetversion = PACKETVERSION; + netbuffer->u.serverinfo.version = VERSION; netbuffer->u.serverinfo.subversion = SUBVERSION; + +#ifdef DEVELOP + memcpy(netbuffer->u.serverinfo.commit, + comprevision_abbrev_bin, GIT_SHA_ABBREV); +#endif + strncpy(netbuffer->u.serverinfo.application, SRB2APPLICATION, sizeof netbuffer->u.serverinfo.application); // return back the time value so client can compute their ping @@ -1681,6 +1688,35 @@ static boolean CL_ServerConnectionSearchTicker(tic_t *asksent) if (client) { +#ifdef DEVELOP + // Commits do not match? Do not connect! + if (memcmp(serverlist[i].info.commit, + comprevision_abbrev_bin, + GIT_SHA_ABBREV)) + { + char theirs[GIT_SHA_ABBREV * 2 + 1]; + UINT8 n; + + for (n = 0; n < GIT_SHA_ABBREV; ++n) + { + sprintf(&theirs[n * 2], "%02hhx", + serverlist[i].info.commit[n]); + } + + D_QuitNetGame(); + CL_Reset(); + D_StartTitle(); + + M_StartMessage(va( + "Your EXE differs from the server.\n" + " Yours: %.*s\n" + "Theirs: %s\n\n" + "Press ESC\n", + GIT_SHA_ABBREV * 2, comprevision, theirs), NULL, MM_NOTHING); + return false; + } +#endif + #ifdef HAVE_CURL if (serverlist[i].info.httpsource[0]) strncpy(http_source, serverlist[i].info.httpsource, MAX_MIRROR_LENGTH); diff --git a/src/d_clisrv.h b/src/d_clisrv.h index fabbd01f5..354e62486 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -268,6 +268,9 @@ typedef struct char application[MAXAPPLICATION]; UINT8 version; UINT8 subversion; +#ifdef DEVELOP + UINT8 commit[GIT_SHA_ABBREV]; +#endif UINT8 numberofplayer; UINT8 maxplayer; UINT8 refusereason; // 0: joinable, 1: joins disabled, 2: full diff --git a/src/d_main.c b/src/d_main.c index 82cf8fd5a..5d83c783b 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -96,6 +96,10 @@ int VERSION; int SUBVERSION; +#ifdef DEVELOP +UINT8 comprevision_abbrev_bin[GIT_SHA_ABBREV]; +#endif + #ifdef HAVE_DISCORDRPC #include "discord.h" #endif @@ -962,7 +966,7 @@ void D_StartTitle(void) splitscreen = 0; SplitScreen_OnChange(); - cv_debug = 0; + cht_debug = 0; emeralds = 0; memset(&luabanks, 0, sizeof(luabanks)); lastmaploaded = 0; @@ -1170,6 +1174,20 @@ static void IdentifyVersion(void) #endif } +#ifdef DEVELOP +static void +D_AbbrevCommit (void) +{ + UINT8 i; + + for (i = 0; i < GIT_SHA_ABBREV; ++i) + { + sscanf(&comprevision[i * 2], "%2hhx", + &comprevision_abbrev_bin[i]); + } +} +#endif + static void D_ConvertVersionNumbers (void) { @@ -1194,6 +1212,10 @@ void D_SRB2Main(void) /* break the version string into version numbers, for netplay */ D_ConvertVersionNumbers(); +#ifdef DEVELOP + D_AbbrevCommit(); +#endif + // Print GPL notice for our console users (Linux) CONS_Printf( "\n\nDr. Robotnik's Ring Racers\n" diff --git a/src/d_netcmd.c b/src/d_netcmd.c index dcc351fb9..435cc5b6f 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -96,6 +96,7 @@ static void Got_DiscordInfo(UINT8 **cp, INT32 playernum); static void Got_ScheduleTaskcmd(UINT8 **cp, INT32 playernum); static void Got_ScheduleClearcmd(UINT8 **cp, INT32 playernum); static void Got_Automatecmd(UINT8 **cp, INT32 playernum); +static void Got_Cheat(UINT8 **cp, INT32 playernum); static void PointLimit_OnChange(void); static void TimeLimit_OnChange(void); @@ -325,8 +326,6 @@ consvar_t cv_splitdevice = CVAR_INIT ("splitdevice", "Off", CV_SAVE, CV_OnOff, N consvar_t cv_skipmapcheck = CVAR_INIT ("skipmapcheck", "Off", CV_SAVE, CV_OnOff, NULL); -INT32 cv_debug; - consvar_t cv_usemouse = CVAR_INIT ("use_mouse", "Off", CV_SAVE|CV_CALL,usemouse_cons_t, I_StartupMouse); consvar_t cv_usejoystick[MAXSPLITSCREENPLAYERS] = { @@ -362,7 +361,6 @@ consvar_t cv_joyscale[MAXSPLITSCREENPLAYERS] = { //Alam: Dummy for save #endif // SRB2kart -consvar_t cv_superring = CVAR_INIT ("superring", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_sneaker = CVAR_INIT ("sneaker", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_rocketsneaker = CVAR_INIT ("rocketsneaker", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_invincibility = CVAR_INIT ("invincibility", "On", CV_NETVAR, CV_OnOff, NULL); @@ -372,7 +370,6 @@ consvar_t cv_orbinaut = CVAR_INIT ("orbinaut", "On", CV_NETVAR, CV_OnOff, consvar_t cv_jawz = CVAR_INIT ("jawz", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_mine = CVAR_INIT ("mine", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_landmine = CVAR_INIT ("landmine", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_droptarget = CVAR_INIT ("droptarget", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_ballhog = CVAR_INIT ("ballhog", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_selfpropelledbomb = CVAR_INIT ("selfpropelledbomb", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_grow = CVAR_INIT ("grow", "On", CV_NETVAR, CV_OnOff, NULL); @@ -382,7 +379,10 @@ consvar_t cv_bubbleshield = CVAR_INIT ("bubbleshield", "On", CV_NETVAR, CV_O consvar_t cv_flameshield = CVAR_INIT ("flameshield", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_hyudoro = CVAR_INIT ("hyudoro", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_pogospring = CVAR_INIT ("pogospring", "On", CV_NETVAR, CV_OnOff, NULL); +consvar_t cv_superring = CVAR_INIT ("superring", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_kitchensink = CVAR_INIT ("kitchensink", "On", CV_NETVAR, CV_OnOff, NULL); +consvar_t cv_droptarget = CVAR_INIT ("droptarget", "On", CV_NETVAR, CV_OnOff, NULL); +consvar_t cv_gardentop = CVAR_INIT ("gardentop", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_dualsneaker = CVAR_INIT ("dualsneaker", "On", CV_NETVAR, CV_OnOff, NULL); consvar_t cv_triplesneaker = CVAR_INIT ("triplesneaker", "On", CV_NETVAR, CV_OnOff, NULL); @@ -625,7 +625,8 @@ const char *netxcmdnames[MAXNETXCMD - 1] = "PLAYSOUND", // XD_PLAYSOUND "SCHEDULETASK", // XD_SCHEDULETASK "SCHEDULECLEAR", // XD_SCHEDULECLEAR - "AUTOMATE" // XD_AUTOMATE + "AUTOMATE", // XD_AUTOMATE + "CHEAT", // XD_CHEAT }; // ========================================================================= @@ -678,6 +679,8 @@ void D_RegisterServerCommands(void) RegisterNetXCmd(XD_SCHEDULECLEAR, Got_ScheduleClearcmd); RegisterNetXCmd(XD_AUTOMATE, Got_Automatecmd); + RegisterNetXCmd(XD_CHEAT, Got_Cheat); + // Remote Administration COM_AddCommand("password", Command_Changepassword_f); COM_AddCommand("login", Command_Login_f); // useful in dedicated to kick off remote admin @@ -1094,7 +1097,6 @@ void D_RegisterClientCommands(void) COM_AddCommand("rteleport", Command_RTeleport_f); COM_AddCommand("skynum", Command_Skynum_f); COM_AddCommand("weather", Command_Weather_f); - COM_AddCommand("toggletwod", Command_Toggletwod_f); #ifdef _DEBUG COM_AddCommand("causecfail", Command_CauseCfail_f); #endif @@ -1383,7 +1385,7 @@ UINT8 CanChangeSkin(INT32 playernum) return true; // Not in game, so you can change - if (players[playernum].spectator || players[playernum].playerstate == PST_DEAD || players[playernum].playerstate == PST_REBORN) + if (players[playernum].spectator) return true; // Check for freeeplay @@ -1406,6 +1408,26 @@ UINT8 CanChangeSkin(INT32 playernum) return true; } +boolean CanChangeSkinWhilePlaying(INT32 playernum) +{ + INT32 i; + + // Force skin in effect. + if ((cv_forceskin.value != -1)) + return false; + + for (i = 0; i < MAXPLAYERS; ++i) + { + if (D_IsPlayerHumanAndGaming(i) && + !P_IsLocalPlayer(&players[i])) + { + return CanChangeSkin(playernum); + } + } + + return true; +} + static void ForceAllSkins(INT32 forcedskin) { INT32 i, j; @@ -1731,40 +1753,65 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) #endif } -void SendWeaponPref(UINT8 n) +enum { + WP_KICKSTARTACCEL = 1<<0, + WP_SHRINKME = 1<<1, +}; + +void WeaponPref_Send(UINT8 ssplayer) { - UINT8 buf[1]; + UINT8 prefs = 0; - buf[0] = 0; + if (cv_kickstartaccel[ssplayer].value) + prefs |= WP_KICKSTARTACCEL; - if (cv_kickstartaccel[n].value) - buf[0] |= 1; + if (cv_shrinkme[ssplayer].value) + prefs |= WP_SHRINKME; - if (cv_shrinkme[n].value) - buf[0] |= 2; - - SendNetXCmdForPlayer(n, XD_WEAPONPREF, buf, 1); + SendNetXCmdForPlayer(ssplayer, XD_WEAPONPREF, &prefs, 1); } -static void Got_WeaponPref(UINT8 **cp,INT32 playernum) +void WeaponPref_Save(UINT8 **cp, INT32 playernum) { + player_t *player = &players[playernum]; + + UINT8 prefs = 0; + + if (player->pflags & PF_KICKSTARTACCEL) + prefs |= WP_KICKSTARTACCEL; + + if (player->pflags & PF_SHRINKME) + prefs |= WP_SHRINKME; + + WRITEUINT8(*cp, prefs); +} + +void WeaponPref_Parse(UINT8 **cp, INT32 playernum) +{ + player_t *player = &players[playernum]; + UINT8 prefs = READUINT8(*cp); - players[playernum].pflags &= ~(PF_KICKSTARTACCEL|PF_SHRINKME); + player->pflags &= ~(PF_KICKSTARTACCEL|PF_SHRINKME); - if (prefs & 1) - players[playernum].pflags |= PF_KICKSTARTACCEL; + if (prefs & WP_KICKSTARTACCEL) + player->pflags |= PF_KICKSTARTACCEL; - if (prefs & 2) - players[playernum].pflags |= PF_SHRINKME; + if (prefs & WP_SHRINKME) + player->pflags |= PF_SHRINKME; if (leveltime < 2) { // BAD HACK: No other place I tried to slot this in // made it work for the host when they initally host, // so this will have to do. - K_UpdateShrinkCheat(&players[playernum]); + K_UpdateShrinkCheat(player); } +} + +static void Got_WeaponPref(UINT8 **cp,INT32 playernum) +{ + WeaponPref_Parse(cp, playernum); // SEE ALSO g_demo.c demo_extradata[playernum] |= DXD_WEAPONPREF; @@ -1952,7 +1999,7 @@ void D_SendPlayerConfig(UINT8 n) UINT8 *p = buf; SendNameAndColor(n); - SendWeaponPref(n); + WeaponPref_Send(n); if (pr != NULL) { @@ -1970,6 +2017,65 @@ void D_SendPlayerConfig(UINT8 n) SendNetXCmdForPlayer(n, XD_POWERLEVEL, buf, p-buf); } +void D_Cheat(INT32 playernum, INT32 cheat, ...) +{ + va_list ap; + + UINT8 buf[64]; + UINT8 *p = buf; + + if (!CV_CheatsEnabled()) + { + CONS_Printf("This cannot be used without cheats enabled.\n"); + return; + } + + WRITEUINT8(p, playernum); + WRITEUINT8(p, cheat); + + va_start(ap, cheat); +#define COPY(writemacro, type) writemacro (p, va_arg(ap, type)) + + switch (cheat) + { + case CHEAT_SAVECHECKPOINT: + COPY(WRITEFIXED, fixed_t); // x + COPY(WRITEFIXED, fixed_t); // y + COPY(WRITEFIXED, fixed_t); // z + break; + + case CHEAT_RINGS: + case CHEAT_LIVES: + // If you're confused why 'int' instead of + // 'SINT8', search online: 'default argument promotions' + COPY(WRITESINT8, int); + break; + + case CHEAT_SCALE: + COPY(WRITEFIXED, fixed_t); + break; + + case CHEAT_HURT: + COPY(WRITEINT32, INT32); + break; + + case CHEAT_RELATIVE_TELEPORT: + COPY(WRITEFIXED, fixed_t); + COPY(WRITEFIXED, fixed_t); + COPY(WRITEFIXED, fixed_t); + break; + + case CHEAT_DEVMODE: + COPY(WRITEUINT32, UINT32); + break; + } + +#undef COPY + va_end(ap); + + SendNetXCmd(XD_CHEAT, buf, p - buf); +} + // Only works for displayplayer, sorry! static void Command_ResetCamera_f(void) { @@ -2805,7 +2911,7 @@ static void Command_Map_f(void) newresetplayers = false; // if not forcing and gametypes is the same // don't use a gametype the map doesn't support - if (cv_debug || option_force || cv_skipmapcheck.value) + if (cht_debug || option_force || cv_skipmapcheck.value) { fromlevelselect = false; // The player wants us to trek on anyway. Do so. } @@ -4711,6 +4817,9 @@ static void Command_Version_f(void) CONS_Printf("\x87" "DEVELOP " "\x80"); #endif + if (compuncommitted) + CONS_Printf("\x85" "! UNCOMMITTED CHANGES ! " "\x80"); + CONS_Printf("\n"); } @@ -5409,6 +5518,175 @@ static void Got_Automatecmd(UINT8 **cp, INT32 playernum) } } +static void Got_Cheat(UINT8 **cp, INT32 playernum) +{ + UINT8 targetPlayer = READUINT8(*cp); + cheat_t cheat = READUINT8(*cp); + + player_t *player; + + if (cheat >= NUMBER_OF_CHEATS || !CV_CheatsEnabled() || targetPlayer >= MAXPLAYERS || + playernode[targetPlayer] != playernode[playernum]) + { + CONS_Alert(CONS_WARNING, + M_GetText ("Illegal cheat command received from %s\n"), + player_names[playernum]); + return; + } + + player = &players[targetPlayer]; + + switch (cheat) + { + case CHEAT_NOCLIP: { + const char *status = "on"; + + if (!P_MobjWasRemoved(player->mo)) + { + player->mo->flags ^= MF_NOCLIP; + + if (!(player->mo->flags & MF_NOCLIP)) + { + status = "off"; + } + } + + CV_CheaterWarning(targetPlayer, va("noclip %s", status)); + break; + } + + case CHEAT_GOD: { + const char *status = (player->pflags & PF_GODMODE) ? "off" : "on"; + + player->pflags ^= PF_GODMODE; + + CV_CheaterWarning(targetPlayer, va("GOD MODE %s", status)); + break; + } + + case CHEAT_SAVECHECKPOINT: { + fixed_t x = READFIXED(*cp); + fixed_t y = READFIXED(*cp); + fixed_t z = READFIXED(*cp); + + player->respawn.pointx = x; + player->respawn.pointy = y; + player->respawn.pointz = z; + player->respawn.manual = true; + + CV_CheaterWarning(targetPlayer, va("temporary checkpoint created at %d, %d, %d", + x / FRACUNIT, y / FRACUNIT, z / FRACUNIT)); + break; + } + + case CHEAT_RINGS: { + SINT8 rings = READSINT8(*cp); + + // P_GivePlayerRings does value clamping + player->rings = 0; + P_GivePlayerRings(player, rings); + + CV_CheaterWarning(targetPlayer, va("rings = %d", rings)); + break; + } + + case CHEAT_LIVES: { + SINT8 lives = READSINT8(*cp); + + // P_GivePlayerLives does value clamping + player->lives = 0; + P_GivePlayerLives(player, lives); + + CV_CheaterWarning(targetPlayer, va("lives = %d", lives)); + break; + } + + case CHEAT_SCALE: { + const fixed_t smin = FRACUNIT/100; + const fixed_t smax = 100*FRACUNIT; + + fixed_t s = READFIXED(*cp); + float f; + + s = min(max(smin, s), smax); + f = FIXED_TO_FLOAT(s); + + if (!P_MobjWasRemoved(player->mo)) + { + player->mo->destscale = s; + } + + CV_CheaterWarning(targetPlayer, va("scale = %d%s", (int)f, M_Ftrim(FIXED_TO_FLOAT(s)))); + break; + } + + case CHEAT_FLIP: { + if (!P_MobjWasRemoved(player->mo)) + { + player->mo->flags2 ^= MF2_OBJECTFLIP; + } + + CV_CheaterWarning(targetPlayer, "invert gravity"); + break; + } + + case CHEAT_HURT: { + INT32 damage = READINT32(*cp); + + if (!P_MobjWasRemoved(player->mo)) + { + P_DamageMobj(player->mo, NULL, NULL, damage, DMG_NORMAL); + } + + CV_CheaterWarning(targetPlayer, va("%d damage to me", damage)); + break; + } + + case CHEAT_RELATIVE_TELEPORT: { + fixed_t x = READFIXED(*cp); + fixed_t y = READFIXED(*cp); + fixed_t z = READFIXED(*cp); + + float f[3] = { + FIXED_TO_FLOAT(x), + FIXED_TO_FLOAT(y), + FIXED_TO_FLOAT(z), + }; + char t[3][9]; + + if (!P_MobjWasRemoved(player->mo)) + { + P_MapStart(); + P_SetOrigin(player->mo, + player->mo->x + x, + player->mo->y + y, + player->mo->z + z); + P_MapEnd(); + + S_StartSound(player->mo, sfx_mixup); + } + + strlcpy(t[0], M_Ftrim(f[0]), sizeof t[0]); + strlcpy(t[1], M_Ftrim(f[1]), sizeof t[1]); + strlcpy(t[2], M_Ftrim(f[2]), sizeof t[2]); + + CV_CheaterWarning(targetPlayer, va("relative teleport by %d%s, %d%s, %d%s", + (int)f[0], t[0], (int)f[1], t[1], (int)f[2], t[2])); + break; + } + + case CHEAT_DEVMODE: { + UINT32 flags = READUINT32(*cp); + cht_debug = flags; + CV_CheaterWarning(targetPlayer, va("devmode %x", flags)); + break; + } + + case NUMBER_OF_CHEATS: + break; + } +} + /** Prints the number of displayplayers[0]. * * \todo Possibly remove this; it was useful for debugging at one point. @@ -5464,7 +5742,7 @@ void Command_ExitGame_f(void) splitscreen = 0; SplitScreen_OnChange(); - cv_debug = 0; + cht_debug = 0; emeralds = 0; memset(&luabanks, 0, sizeof(luabanks)); @@ -6046,7 +6324,7 @@ static void Skin_OnChange(void) return; } - if (CanChangeSkin(consoleplayer)) + if (CanChangeSkinWhilePlaying(consoleplayer)) SendNameAndColor(0); else { @@ -6065,7 +6343,7 @@ static void Skin2_OnChange(void) if (!Playing() || !splitscreen) return; // do whatever you want - if (CanChangeSkin(g_localplayers[1])) + if (CanChangeSkinWhilePlaying(g_localplayers[1])) SendNameAndColor(1); else { @@ -6079,7 +6357,7 @@ static void Skin3_OnChange(void) if (!Playing() || splitscreen < 2) return; // do whatever you want - if (CanChangeSkin(g_localplayers[2])) + if (CanChangeSkinWhilePlaying(g_localplayers[2])) SendNameAndColor(2); else { @@ -6093,7 +6371,7 @@ static void Skin4_OnChange(void) if (!Playing() || splitscreen < 3) return; // do whatever you want - if (CanChangeSkin(g_localplayers[3])) + if (CanChangeSkinWhilePlaying(g_localplayers[3])) SendNameAndColor(3); else { diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 0114c726f..2f2b2a25b 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -72,14 +72,38 @@ extern consvar_t cv_pause; extern consvar_t cv_restrictskinchange, cv_allowteamchange, cv_maxplayers, cv_respawntime; // SRB2kart items -extern consvar_t cv_superring, cv_sneaker, cv_rocketsneaker, cv_invincibility, cv_banana; -extern consvar_t cv_eggmanmonitor, cv_orbinaut, cv_jawz, cv_mine, cv_landmine, cv_droptarget; -extern consvar_t cv_ballhog, cv_selfpropelledbomb, cv_grow, cv_shrink; -extern consvar_t cv_lightningshield, cv_bubbleshield, cv_flameshield; -extern consvar_t cv_hyudoro, cv_pogospring, cv_kitchensink; +extern consvar_t + cv_sneaker, + cv_rocketsneaker, + cv_invincibility, + cv_banana, + cv_eggmanmonitor, + cv_orbinaut, + cv_jawz, + cv_mine, + cv_landmine, + cv_ballhog, + cv_selfpropelledbomb, + cv_grow, + cv_shrink, + cv_lightningshield, + cv_bubbleshield, + cv_flameshield, + cv_hyudoro, + cv_pogospring, + cv_superring, + cv_kitchensink, + cv_droptarget, + cv_gardentop; -extern consvar_t cv_dualsneaker, cv_triplesneaker, cv_triplebanana, cv_decabanana; -extern consvar_t cv_tripleorbinaut, cv_quadorbinaut, cv_dualjawz; +extern consvar_t + cv_dualsneaker, + cv_triplesneaker, + cv_triplebanana, + cv_decabanana, + cv_tripleorbinaut, + cv_quadorbinaut, + cv_dualjawz; extern consvar_t cv_kartminimap; extern consvar_t cv_kartcheck; @@ -179,6 +203,7 @@ typedef enum XD_SCHEDULETASK, // 36 XD_SCHEDULECLEAR, // 37 XD_AUTOMATE, // 38 + XD_CHEAT, // 39 MAXNETXCMD } netxcmd_t; @@ -230,7 +255,9 @@ void D_RegisterServerCommands(void); void D_RegisterClientCommands(void); void CleanupPlayerName(INT32 playernum, const char *newname); boolean EnsurePlayerNameIsGood(char *name, INT32 playernum); -void SendWeaponPref(UINT8 n); +void WeaponPref_Send(UINT8 ssplayer); +void WeaponPref_Save(UINT8 **cp, INT32 playernum); +void WeaponPref_Parse(UINT8 **cp, INT32 playernum); void D_SendPlayerConfig(UINT8 n); void Command_ExitGame_f(void); void Command_Retry_f(void); @@ -278,7 +305,10 @@ void Automate_Clear(void); extern UINT32 livestudioaudience_timer; void LiveStudioAudience(void); +void D_Cheat(INT32 playernum, INT32 cheat, ...); + // used for the player setup menu UINT8 CanChangeSkin(INT32 playernum); +boolean CanChangeSkinWhilePlaying(INT32 playernum); #endif diff --git a/src/d_player.h b/src/d_player.h index f2943dd18..ef3fd5193 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -58,14 +58,15 @@ typedef enum // typedef enum { - // free: 1<<0 to 1<<2 + PF_GODMODE = 1<<0, // Immortal. No lightsnake from pits either + + // free: 1<<1 and 1<<2 // Look back VFX has been spawned // TODO: Is there a better way to track this? PF_GAINAX = 1<<3, - // Accessibility and cheats - PF_KICKSTARTACCEL = 1<<4, // Is accelerate in kickstart mode? + PF_KICKSTARTACCEL = 1<<4, // Accessibility feature: Is accelerate in kickstart mode? // 1<<5 free // 1<<6 free @@ -105,13 +106,6 @@ typedef enum // up to 1<<31 is free } pflags_t; -typedef enum -{ - PC_GODMODE = 1, - PC_NOCLIP = 1<<1, - // up to 1<<31 is free -} pcheats_t; - typedef enum { // Are animation frames playing? @@ -159,7 +153,8 @@ Run this macro, then #undef FOREACH afterward FOREACH (POGOSPRING, 18),\ FOREACH (SUPERRING, 19),\ FOREACH (KITCHENSINK, 20),\ - FOREACH (DROPTARGET, 21) + FOREACH (DROPTARGET, 21),\ + FOREACH (GARDENTOP, 22) typedef enum { @@ -187,6 +182,7 @@ typedef enum KSHIELD_LIGHTNING = 1, KSHIELD_BUBBLE = 2, KSHIELD_FLAME = 3, + KSHIELD_TOP = 4, NUMKARTSHIELDS } kartshields_t; @@ -288,6 +284,8 @@ typedef enum #define ITEMSCALE_GROW 1 #define ITEMSCALE_SHRINK 2 +#define GARDENTOP_MAXGRINDTIME (45) + // player_t struct for all respawn variables typedef struct respawnvars_s { @@ -302,6 +300,7 @@ typedef struct respawnvars_s UINT32 distanceleft; // How far along the course to respawn you 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 } respawnvars_t; // player_t struct for all bot variables @@ -372,7 +371,6 @@ typedef struct player_s // Bit flags. // See pflags_t, above. pflags_t pflags; - pcheats_t cheats; // playing animation. panim_t panim; @@ -446,7 +444,6 @@ typedef struct player_s INT32 underwatertilt; fixed_t offroad; // In Super Mario Kart, going offroad has lee-way of about 1 second before you start losing speed - UINT8 waterskip; // Water skipping counter UINT16 tiregrease; // Reduced friction timer after hitting a spring UINT16 springstars; // Spawn stars around a player when they hit a spring @@ -599,6 +596,8 @@ typedef struct player_s UINT8 kickstartaccel; UINT8 stairjank; + UINT8 topdriftheld; + UINT8 topinfirst; UINT8 shrinkLaserDelay; @@ -609,7 +608,4 @@ typedef struct player_s #endif } player_t; -// Value for infinite lives -#define INFLIVES 0x7F - #endif diff --git a/src/deh_tables.c b/src/deh_tables.c index a0d4e55bd..2025d4f03 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -3740,6 +3740,14 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_FLAMESHIELDLINE3", "S_FLAMESHIELDFLASH", + // Marble Garden Zone Spinning Top + "S_GARDENTOP_FLOATING", + "S_GARDENTOP_SINKING1", + "S_GARDENTOP_SINKING2", + "S_GARDENTOP_SINKING3", + "S_GARDENTOP_DEAD", + "S_GARDENTOPSPARK", + // Caked-Up Booty-Sheet Ghost "S_HYUDORO", @@ -5346,6 +5354,8 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_FLAMESHIELDUNDERLAY", "MT_FLAMESHIELDPAPER", "MT_BUBBLESHIELDTRAP", + "MT_GARDENTOP", + "MT_GARDENTOPSPARK", "MT_HYUDORO", "MT_HYUDORO_CENTER", @@ -5651,7 +5661,7 @@ const char *const MOBJFLAG_LIST[] = { // \tMF2_(\S+).*// (.+) --> \t"\1", // \2 const char *const MOBJFLAG2_LIST[] = { "AXIS", // It's a NiGHTS axis! (For faster checking) - "TWOD", // Moves like it's in a 2D level + "\x01", // free: 1<<1 (name un-matchable) "DONTRESPAWN", // Don't respawn this object! "\x01", // free: 1<<3 (name un-matchable) "AUTOMATIC", // Thrown ring has automatic properties @@ -5710,8 +5720,9 @@ const char *const MAPTHINGFLAG_LIST[4] = { }; const char *const PLAYERFLAG_LIST[] = { - // free: 1<<0 to 1<<2 (name un-matchable) - "\x01", + "GODMODE", + + // free: 1<<1 and 1<<2 (name un-matchable) "\x01", "\x01", @@ -6348,9 +6359,6 @@ struct int_const_s const INT_CONST[] = { {"PA_DRIFT",PA_DRIFT}, {"PA_HURT",PA_HURT}, - // Value for infinite lives - {"INFLIVES",INFLIVES}, - // Got Flags, for player->gotflag! // Used to be MF_ for some stupid reason, now they're GF_ to stop them looking like mobjflags {"GF_REDFLAG",GF_REDFLAG}, @@ -6670,6 +6678,7 @@ struct int_const_s const INT_CONST[] = { {"KSHIELD_LIGHTNING",KSHIELD_LIGHTNING}, {"KSHIELD_BUBBLE",KSHIELD_BUBBLE}, {"KSHIELD_FLAME",KSHIELD_FLAME}, + {"KSHIELD_TOP",KSHIELD_TOP}, {"NUMKARTSHIELDS",NUMKARTSHIELDS}, // kartspinoutflags_t diff --git a/src/doomdef.h b/src/doomdef.h index 6997d7951..3492179b4 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -480,7 +480,7 @@ typedef enum void CONS_Printf(const char *fmt, ...) FUNCPRINTF; void CONS_Alert(alerttype_t level, const char *fmt, ...) FUNCDEBUG; -void CONS_Debug(INT32 debugflags, const char *fmt, ...) FUNCDEBUG; +void CONS_Debug(UINT32 debugflags, const char *fmt, ...) FUNCDEBUG; // For help debugging functions. #define POTENTIALLYUNUSED CONS_Alert(CONS_WARNING, "(%s:%d) Unused code appears to be used.\n", __FILE__, __LINE__) @@ -516,24 +516,47 @@ char *sizeu5(size_t num); // d_main.c extern int VERSION; extern int SUBVERSION; -extern boolean devparm; // development mode (-debug) -// d_netcmd.c -extern INT32 cv_debug; -#define DBG_BASIC 0x0001 -#define DBG_DETAILED 0x0002 -#define DBG_PLAYER 0x0004 -#define DBG_RENDER 0x0008 -#define DBG_NIGHTSBASIC 0x0010 -#define DBG_NIGHTS 0x0020 -#define DBG_POLYOBJ 0x0040 -#define DBG_GAMELOGIC 0x0080 -#define DBG_NETPLAY 0x0100 -#define DBG_MEMORY 0x0200 -#define DBG_SETUP 0x0400 -#define DBG_LUA 0x0800 -#define DBG_RANDOMIZER 0x1000 -#define DBG_VIEWMORPH 0x2000 +#ifdef DEVELOP +// 4 bytes handles 8 characters of a git object SHA. At +// around 20k commits, we only need 6 characters for a unique +// abbreviation. Maybe in another 20k commits, more than 8 +// characters will be required! =P +// P.S. 8 is also what comptime generates +#define GIT_SHA_ABBREV (4) +extern UINT8 comprevision_abbrev_bin[GIT_SHA_ABBREV]; +#endif + +extern boolean devparm; // development mode (-debug) + +// m_cheat.c +extern UINT32 cht_debug; + +typedef enum +{ + DBG_NONE = 0x00000000, + DBG_BASIC = 0x00000001, + DBG_DETAILED = 0x00000002, + DBG_PLAYER = 0x00000004, + DBG_RENDER = 0x00000008, + //DBG_NIGHTSBASIC = 0x00000010, // free + //DBG_NIGHTS = 0x00000020, // free + DBG_POLYOBJ = 0x00000040, + DBG_GAMELOGIC = 0x00000080, + DBG_NETPLAY = 0x00000100, + DBG_MEMORY = 0x00000200, + DBG_SETUP = 0x00000400, + DBG_LUA = 0x00000800, + DBG_RNG = 0x00001000, +} debugFlags_t; + +struct debugFlagNames_s +{ + const char *str; + debugFlags_t flag; +}; + +extern struct debugFlagNames_s const debug_flag_names[]; // ======================= // Misc stuff for later... @@ -613,6 +636,7 @@ UINT32 quickncasehash (const char *p, size_t n) // Compile date and time and revision. extern const char *compdate, *comptime, *comprevision, *compbranch; +extern int compuncommitted; // Disabled code and code under testing // None of these that are disabled in the normal build are guaranteed to work perfectly @@ -691,10 +715,6 @@ extern const char *compdate, *comptime, *comprevision, *compbranch; #undef UPDATE_ALERT #endif -/// - SRB2Kart options - -/// Camera always has noclip. -#define NOCLIPCAM - /// Other karma comeback modes //#define OTHERKARMAMODES diff --git a/src/f_finale.c b/src/f_finale.c index 005a4c6a1..1903eeb1a 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -866,7 +866,7 @@ boolean F_CreditResponder(event_t *event) return false; } - /*if (!(timesBeaten) && !(netgame || multiplayer) && !cv_debug) + /*if (!(timesBeaten) && !(netgame || multiplayer) && !cht_debug) return false;*/ if (key != KEY_ESCAPE && key != KEY_ENTER && key != KEY_BACKSPACE) @@ -2006,6 +2006,8 @@ void F_TitleScreenDrawer(void) #else // Regular build addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, va("%s", VERSIONSTRING)); #endif + if (compuncommitted) + addtext(V_REDMAP|V_STRINGDANCE, "! UNCOMMITTED CHANGES !"); } #undef addtext } diff --git a/src/g_demo.c b/src/g_demo.c index bc1f86cc5..c025ff95d 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -364,20 +364,7 @@ void G_ReadDemoExtraData(void) } if (extradata & DXD_WEAPONPREF) { - i = READUINT8(demo_p); - players[p].pflags &= ~(PF_KICKSTARTACCEL|PF_SHRINKME); - if (i & 1) - players[p].pflags |= PF_KICKSTARTACCEL; - if (i & 2) - players[p].pflags |= PF_SHRINKME; - - if (leveltime < 2) - { - // BAD HACK: No other place I tried to slot this in - // made it work for the host when they initally host, - // so this will have to do. - K_UpdateShrinkCheat(&players[p]); - } + WeaponPref_Parse(&demo_p, p); //CONS_Printf("weaponpref is %d for player %d\n", i, p); } @@ -492,12 +479,7 @@ void G_WriteDemoExtraData(void) } if (demo_extradata[i] & DXD_WEAPONPREF) { - UINT8 prefs = 0; - if (players[i].pflags & PF_KICKSTARTACCEL) - prefs |= 1; - if (players[i].pflags & PF_SHRINKME) - prefs |= 2; - WRITEUINT8(demo_p, prefs); + WeaponPref_Save(&demo_p, i); } } diff --git a/src/g_game.c b/src/g_game.c index 3e6d3108d..d5f134a2e 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1304,22 +1304,22 @@ ticcmd_t *G_MoveTiccmd(ticcmd_t* dest, const ticcmd_t* src, const size_t n) static void weaponPrefChange(void) { - SendWeaponPref(0); + WeaponPref_Send(0); } static void weaponPrefChange2(void) { - SendWeaponPref(1); + WeaponPref_Send(1); } static void weaponPrefChange3(void) { - SendWeaponPref(2); + WeaponPref_Send(2); } static void weaponPrefChange4(void) { - SendWeaponPref(3); + WeaponPref_Send(3); } // @@ -2219,7 +2219,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) UINT32 followitem; INT32 pflags; - INT32 cheats; UINT8 ctfteam; @@ -2299,7 +2298,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) botrival = players[player].botvars.rival; pflags = (players[player].pflags & (PF_WANTSTOJOIN|PF_KICKSTARTACCEL|PF_SHRINKME|PF_SHRINKACTIVE)); - cheats = 0; // SRB2kart if (betweenmaps || leveltime < introtime) @@ -2374,10 +2372,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) pflags |= (players[player].pflags & (PF_STASIS|PF_ELIMINATED|PF_NOCONTEST|PF_FAULT|PF_LOSTLIFE)); } - // As long as we're not in multiplayer, carry over cheatcodes from map to map - if (!(netgame || multiplayer)) - cheats = players[player].cheats; - if (!betweenmaps) { // Obliterate follower from existence (if valid memory) @@ -2393,7 +2387,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->roundscore = roundscore; p->lives = lives; p->pflags = pflags; - p->cheats = cheats; p->ctfteam = ctfteam; p->jointime = jointime; p->splitscreenindex = splitscreenindex; @@ -4597,7 +4590,7 @@ void G_SaveGame(UINT32 slot, INT16 mapnum) gameaction = ga_nothing; - if (cv_debug && saved) + if (cht_debug && saved) CONS_Printf(M_GetText("Game saved.\n")); else if (!saved) CONS_Alert(CONS_ERROR, M_GetText("Error while writing to %s for save slot %u, base: %s\n"), backup, slot, (marathonmode ? liveeventbackup : savegamename)); @@ -4699,7 +4692,7 @@ void G_SaveGameOver(UINT32 slot, boolean modifylives) } cleanup: - if (cv_debug && saved) + if (cht_debug && saved) CONS_Printf(M_GetText("Game saved.\n")); else if (!saved) CONS_Alert(CONS_ERROR, M_GetText("Error while writing to %s for save slot %u, base: %s\n"), backup, slot, (marathonmode ? liveeventbackup : savegamename)); @@ -4784,9 +4777,6 @@ void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, boolean skippr players[i].playerstate = PST_REBORN; memset(&players[i].respawn, 0, sizeof (players[i].respawn)); - // Clear cheatcodes too, just in case. - players[i].cheats = 0; - players[i].roundscore = 0; if (resetplayer && !(multiplayer && demo.playback)) // SRB2Kart diff --git a/src/hu_stuff.c b/src/hu_stuff.c index bece93863..e02e3ed67 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -85,9 +85,9 @@ patch_t *framecounter; patch_t *frameslash; // framerate stuff. Used in screen.c static player_t *plr; -boolean chat_on; // entering a chat message? boolean hu_keystrokes; // :) -static char w_chat[HU_MAXMSGLEN]; +boolean chat_on; // entering a chat message? +static char w_chat[HU_MAXMSGLEN + 1]; static size_t c_input = 0; // let's try to make the chat input less shitty. static boolean headsupactive = false; boolean hu_showscores; // draw rankings @@ -487,7 +487,7 @@ void HU_AddChatText(const char *text, boolean playsound) static void DoSayCommand(SINT8 target, size_t usedargs, UINT8 flags) { - char buf[254]; + char buf[2 + HU_MAXMSGLEN + 1]; size_t numwords, ix; char *msg = &buf[2]; const size_t msgspace = sizeof buf - 2; @@ -567,7 +567,7 @@ static void DoSayCommand(SINT8 target, size_t usedargs, UINT8 flags) } buf[0] = target; newmsg = msg+5+spc; - strlcpy(msg, newmsg, 252); + strlcpy(msg, newmsg, HU_MAXMSGLEN + 1); } SendNetXCmd(XD_SAY, buf, strlen(msg) + 1 + msg-buf); @@ -697,7 +697,7 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum) target = READSINT8(*p); flags = READUINT8(*p); msg = (char *)*p; - SKIPSTRING(*p); + SKIPSTRINGL(*p, HU_MAXMSGLEN + 1); if ((cv_mute.value || flags & (HU_CSAY|HU_SHOUT)) && playernum != serverplayer && !(IsPlayerAdmin(playernum))) { @@ -916,71 +916,6 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum) #endif } -// Handles key input and string input -// -static inline boolean HU_keyInChatString(char *s, char ch) -{ - size_t l; - - if ((ch >= HU_FONTSTART && ch <= HU_FONTEND && fontv[HU_FONT].font[ch-HU_FONTSTART]) - || ch == ' ') // Allow spaces, of course - { - l = strlen(s); - if (l < HU_MAXMSGLEN - 1) - { - if (c_input >= strlen(s)) // don't do anything complicated - { - s[l++] = ch; - s[l]=0; - } - else - { - // move everything past c_input for new characters: - size_t m = HU_MAXMSGLEN-1; - while (m>=c_input) - { - if (s[m]) - s[m+1] = (s[m]); - if (m == 0) // prevent overflow - break; - m--; - } - s[c_input] = ch; // and replace this. - } - c_input++; - return true; - } - return false; - } - else if (ch == KEY_BACKSPACE) - { - size_t i = c_input; - - if (c_input <= 0) - return false; - - if (!s[i-1]) - return false; - - if (i >= strlen(s)-1) - { - s[strlen(s)-1] = 0; - c_input--; - return false; - } - - for (; (i < HU_MAXMSGLEN); i++) - { - s[i-1] = s[i]; - } - c_input--; - } - else if (ch != KEY_ENTER) - return false; // did not eat key - - return true; // ate the key -} - // // static void HU_TickSongCredits(void) @@ -1096,154 +1031,125 @@ void HU_Ticker(void) } static boolean teamtalk = false; +static boolean justscrolleddown; +static boolean justscrolledup; +static INT16 typelines = 1; // number of drawfill lines we need when drawing the chat. it's some weird hack and might be one frame off but I'm lazy to make another loop. +// It's up here since it has to be reset when we open the chat. -// Clear spaces so we don't end up with messages only made out of emptiness -static boolean HU_clearChatSpaces(void) +static boolean HU_chatboxContainsOnlySpaces(void) { - size_t i = 0; // Used to just check our message - char c; // current character we're iterating. - boolean nothingbutspaces = true; + size_t i; - for (; i < strlen(w_chat); i++) // iterate through message and eradicate all spaces that don't belong. - { - c = w_chat[i]; - if (!c) - break; // if there's nothing, it's safe to assume our message has ended, so let's not waste any more time here. + for (i = 0; w_chat[i]; i++) + if (w_chat[i] != ' ') + return false; - if (c != ' ') // Isn't a space - { - nothingbutspaces = false; - } - } - return nothingbutspaces; + return true; } -// -// -static void HU_queueChatChar(INT32 c) +static void HU_sendChatMessage(void) { - // send automaticly the message (no more chat char) - if (c == KEY_ENTER) + char buf[2 + HU_MAXMSGLEN + 1]; + char *msg = &buf[2]; + size_t ci; + INT32 target = 0; + + // if our message was nothing but spaces, don't send it. + if (HU_chatboxContainsOnlySpaces()) + return; + + // copy printable characters and terminating '\0' only. + for (ci = 2; w_chat[ci-2]; ci++) { - char buf[2+256]; - char *msg = &buf[2]; - size_t i; - size_t ci = 2; - INT32 target = 0; + char c = w_chat[ci-2]; + if (c >= ' ' && !(c & 0x80)) + buf[ci] = c; + }; + buf[ci] = '\0'; - if (HU_clearChatSpaces()) // Avoids being able to send empty messages, or something. - return; // If this returns true, that means our message was NOTHING but spaces, so don't send it period. + memset(w_chat, '\0', sizeof(w_chat)); + c_input = 0; - do { - c = w_chat[-2+ci++]; - if (!c || (c >= ' ' && !(c & 0x80))) // copy printable characters and terminating '\0' only. - buf[ci-1]=c; - } while (c); - i = 0; - for (;(iERROR: The chat is muted. You can't say anything.", "\x85"), false); + return; + } - c_input = 0; + if (strlen(msg) > 4 && strnicmp(msg, "/pm", 3) == 0) // used /pm + { + INT32 spc = 1; // used if playernum[1] is a space. + char playernum[3]; + const char *newmsg; - for (;(iERROR: The chat is muted. You can't say anything.", "\x85"), false); + HU_AddChatText(va("%sCannot send sayto in Say-Team.", "\x85"), false); return; } - if (strlen(msg) > 4 && strnicmp(msg, "/pm", 3) == 0) // used /pm + strncpy(playernum, msg+3, 3); + // check for undesirable characters in our "number" + if (!(isdigit(playernum[0]) && isdigit(playernum[1]))) { - INT32 spc = 1; // used if playernum[1] is a space. - char playernum[3]; - const char *newmsg; - - // what we're gonna do now is check if the player exists - // with that logic, characters 4 and 5 are our numbers: - - // teamtalk can't send PMs, just don't send it, else everyone would be able to see it, and no one wants to see your sex RP sicko. - if (teamtalk) - { - HU_AddChatText(va("%sCannot send sayto in Say-Team.", "\x85"), false); - return; - } - - strncpy(playernum, msg+3, 3); - - // check for undesirable characters in our "number" - if (((playernum[0] < '0') || (playernum[0] > '9')) || ((playernum[1] < '0') || (playernum[1] > '9'))) - { - // check if playernum[1] is a space - if (playernum[1] == ' ') - spc = 0; - // let it slide - else - { - HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm \'.", false); - return; - } - } - // I'm very bad at C, I swear I am, additional checks eww! - if (spc != 0) - { - if (msg[5] != ' ') - { - HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm \'.", false); - return; - } - } - - target = atoi(playernum); // turn that into a number - //CONS_Printf("%d\n", target); - - // check for target player, if it doesn't exist then we can't send the message! - if (target < MAXPLAYERS && playeringame[target]) // player exists - target++; // even though playernums are from 0 to 31, target is 1 to 32, so up that by 1 to have it work! + // check if playernum[1] is a space + if (playernum[1] == ' ') + spc = 0; + // let it slide else { - HU_AddChatText(va("\x82NOTICE: \x80Player %d does not exist.", target), false); // same + HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm \'.", false); return; } - - // we need to get rid of the /pm - newmsg = msg+5+spc; - strlcpy(msg, newmsg, 255); } - if (ci > 3) // don't send target+flags+empty message. + // I'm very bad at C, I swear I am, additional checks eww! + if (spc != 0 && msg[5] != ' ') { - if (teamtalk) - buf[0] = -1; // target - else - buf[0] = target; - - buf[1] = ((server || IsPlayerAdmin(consoleplayer)) && cv_autoshout.value) ? HU_SHOUT : 0; // flags - SendNetXCmd(XD_SAY, buf, 2 + strlen(&buf[2]) + 1); + HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm \'.", false); + return; } - return; + + target = atoi(playernum); // turn that into a number + + // check for target player, if it doesn't exist then we can't send the message! + if (target < MAXPLAYERS && playeringame[target]) // player exists + target++; // even though playernums are from 0 to 31, target is 1 to 32, so up that by 1 to have it work! + else + { + HU_AddChatText(va("\x82NOTICE: \x80Player %d does not exist.", target), false); // same + return; + } + + // we need to get rid of the /pm + newmsg = msg+5+spc; + strlcpy(msg, newmsg, HU_MAXMSGLEN + 1); + } + if (ci > 2) // don't send target+flags+empty message. + { + if (teamtalk) + buf[0] = -1; // target + else + buf[0] = target; + + buf[1] = ((server || IsPlayerAdmin(consoleplayer)) && cv_autoshout.value) ? HU_SHOUT : 0; // flags + SendNetXCmd(XD_SAY, buf, 2 + strlen(&buf[2]) + 1); } } void HU_clearChatChars(void) { - size_t i = 0; - for (;i HU_MAXMSGLEN) return true; // we can't paste this!! - if (c_input >= strlen(w_chat)) // add it at the end of the string. - { - memcpy(&w_chat[chatlen], paste, pastelen); // copy all of that. - c_input += pastelen; - /*size_t i = 0; - for (;i= c_input) - { - if (w_chat[i]) - w_chat[i+pastelen] = w_chat[i]; - if (i == 0) // prevent overflow - break; - i--; - } - memcpy(&w_chat[c_input], paste, pastelen); // copy all of that. - c_input += pastelen; - return true; - } + memmove(&w_chat[c_input + pastelen], &w_chat[c_input], pastelen); + memcpy(&w_chat[c_input], paste, pastelen); // copy all of that. + c_input += pastelen; + return true; } + else if (c == KEY_ENTER) + { + if (!CHAT_MUTE) + HU_sendChatMessage(); - if (!CHAT_MUTE && HU_keyInChatString(w_chat,c)) - { - HU_queueChatChar(c); - } - if (c == KEY_ENTER) - { chat_on = false; c_input = 0; // reset input cursor chat_scrollmedown = true; // you hit enter, so you might wanna autoscroll to see what you just sent. :) @@ -1417,6 +1301,32 @@ boolean HU_Responder(event_t *ev) else c_input++; } + else if ((c >= HU_FONTSTART && c <= HU_FONTEND && fontv[HU_FONT].font[c-HU_FONTSTART]) + || c == ' ') // Allow spaces, of course + { + if (CHAT_MUTE || strlen(w_chat) >= HU_MAXMSGLEN) + return true; + + memmove(&w_chat[c_input + 1], &w_chat[c_input], strlen(w_chat) - c_input + 1); + w_chat[c_input] = c; + c_input++; + } + else if (c == KEY_BACKSPACE) + { + if (CHAT_MUTE || c_input <= 0) + return true; + + memmove(&w_chat[c_input - 1], &w_chat[c_input], strlen(w_chat) - c_input + 1); + c_input--; + } + else if (c == KEY_DEL) + { + if (CHAT_MUTE || c_input >= strlen(w_chat)) + return true; + + memmove(&w_chat[c_input], &w_chat[c_input + 1], strlen(w_chat) - c_input); + } + return true; } @@ -1962,8 +1872,8 @@ static void HU_DrawChat_Old(void) size_t i = 0; const char *ntalk = "Say: ", *ttalk = "Say-Team: "; const char *talk = ntalk; - INT32 charwidth = 8 * con_scalefactor; //(hu_font['A'-HU_FONTSTART]->width) * con_scalefactor; - INT32 charheight = 8 * con_scalefactor; //(hu_font['A'-HU_FONTSTART]->height) * con_scalefactor; + INT32 charwidth = 8 * con_scalefactor; //(fontv[HU_FONT].font['A'-HU_FONTSTART]->width) * con_scalefactor; + INT32 charheight = 8 * con_scalefactor; //(fontv[HU_FONT].font['A'-HU_FONTSTART]->height) * con_scalefactor; if (teamtalk) { talk = ttalk; @@ -1984,7 +1894,7 @@ static void HU_DrawChat_Old(void) } else { - //charwidth = (hu_font[talk[i]-HU_FONTSTART]->width) * con_scalefactor; + //charwidth = (fontv[HU_FONT].font[talk[i]-HU_FONTSTART]->width) * con_scalefactor; V_DrawCharacter(HU_INPUTX + c, y, talk[i++] | cv_constextsize.value | V_NOSCALESTART, true); } c += charwidth; @@ -2012,7 +1922,7 @@ static void HU_DrawChat_Old(void) } else { - //charwidth = (hu_font[w_chat[i]-HU_FONTSTART]->width) * con_scalefactor; + //charwidth = (fontv[HU_FONT].font[w_chat[i]-HU_FONTSTART]->width) * con_scalefactor; V_DrawCharacter(HU_INPUTX + c, y, w_chat[i++] | cv_constextsize.value | V_NOSCALESTART | t, true); } diff --git a/src/hu_stuff.h b/src/hu_stuff.h index 9bcf45e09..be0d97df9 100644 --- a/src/hu_stuff.h +++ b/src/hu_stuff.h @@ -91,8 +91,8 @@ typedef struct //------------------------------------ // chat stuff //------------------------------------ -#define HU_MAXMSGLEN 224 -#define CHAT_BUFSIZE 64 // that's enough messages, right? We'll delete the older ones when that gets out of hand. +#define HU_MAXMSGLEN 223 +#define CHAT_BUFSIZE 64 // that's enough messages, right? We'll delete the older ones when that gets out of hand. #define NETSPLITSCREEN // why the hell WOULDN'T we want this? #ifdef NETSPLITSCREEN #define OLDCHAT (cv_consolechat.value == 1 || dedicated || vid.width < 640) diff --git a/src/info.c b/src/info.c index 7bdffe11d..ee71d9972 100644 --- a/src/info.c +++ b/src/info.c @@ -4320,6 +4320,13 @@ state_t states[NUMSTATES] = {SPR_FLML, FF_FULLBRIGHT|FF_PAPERSPRITE|FF_ANIMATE|14, 7, {NULL}, 6, 1, S_NULL}, // S_FLAMESHIELDLINE3 {SPR_FLMF, FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_FLAMESHIELDFLASH + {SPR_GTOP, FF_ANIMATE, -1, {NULL}, 5, 1, S_NULL}, // S_GARDENTOP_FLOATING + {SPR_GTOP, 0, 1, {NULL}, 5, 1, S_GARDENTOP_SINKING2}, // S_GARDENTOP_SINKING1 + {SPR_GTOP, 2, 1, {NULL}, 5, 1, S_GARDENTOP_SINKING3}, // S_GARDENTOP_SINKING2 + {SPR_GTOP, 4, 1, {NULL}, 5, 1, S_GARDENTOP_SINKING1}, // S_GARDENTOP_SINKING3 + {SPR_GTOP, FF_ANIMATE, 100, {A_Scream}, 5, 1, S_NULL}, // S_GARDENTOP_DEAD + {SPR_BDRF, FF_FULLBRIGHT|FF_PAPERSPRITE|FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 5, 2, S_NULL}, // S_GARDENTOPSPARK + {SPR_HYUU, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_HYUDORO {SPR_GRWP, FF_FULLBRIGHT|FF_ANIMATE, 13, {NULL}, 7, 1, S_NULL}, // S_GROW_PARTICLE @@ -24065,6 +24072,60 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL // raisestate }, + { // MT_GARDENTOP + -1, // doomednum + S_GARDENTOP_FLOATING, // spawnstate + 8, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 4, // reactiontime + sfx_s3k8b, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_GARDENTOP_DEAD, // deathstate + S_NULL, // xdeathstate + sfx_s3k7a, // deathsound + 40*FRACUNIT, // speed + 30*FRACUNIT, // radius + 68*FRACUNIT, // height + -1, // display offset + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOCLIPTHING|MF_DONTENCOREMAP, // flags + S_NULL // raisestate + }, + + { // MT_GARDENTOPSPARK + -1, // doomednum + S_GARDENTOPSPARK, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 8, // speed + 8*FRACUNIT, // radius + 8*FRACUNIT, // height + 1, // display offset + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_DONTENCOREMAP|MF_NOSQUISH, // flags + S_NULL // raisestate + }, + { // MT_HYUDORO -1, // doomednum S_HYUDORO, // spawnstate diff --git a/src/info.h b/src/info.h index e40ea51ee..975122946 100644 --- a/src/info.h +++ b/src/info.h @@ -4750,6 +4750,14 @@ typedef enum state S_FLAMESHIELDLINE3, S_FLAMESHIELDFLASH, + // Marble Garden Zone Spinning Top + S_GARDENTOP_FLOATING, + S_GARDENTOP_SINKING1, + S_GARDENTOP_SINKING2, + S_GARDENTOP_SINKING3, + S_GARDENTOP_DEAD, + S_GARDENTOPSPARK, + // Caked-Up Booty-Sheet Ghost S_HYUDORO, @@ -6392,6 +6400,8 @@ typedef enum mobj_type MT_FLAMESHIELDUNDERLAY, MT_FLAMESHIELDPAPER, MT_BUBBLESHIELDTRAP, + MT_GARDENTOP, + MT_GARDENTOPSPARK, MT_HYUDORO, MT_HYUDORO_CENTER, diff --git a/src/k_collide.c b/src/k_collide.c index e4a536403..978993339 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -66,17 +66,22 @@ boolean K_BananaBallhogCollide(mobj_t *t1, mobj_t *t2) if (t1->type == MT_BANANA && t1->health > 1) S_StartSound(t2, sfx_bsnipe); + damageitem = true; + if (t2->player->flamedash && t2->player->itemtype == KITEM_FLAMESHIELD) { // Melt item S_StartSound(t2, sfx_s3k43); } + else if (K_IsRidingFloatingTop(t2->player)) + { + // Float over silly banana + damageitem = false; + } else { P_DamageMobj(t2, t1, t1->target, 1, DMG_NORMAL|DMG_WOMBO); } - - damageitem = true; } else if (t2->type == MT_BANANA || t2->type == MT_BANANA_SHIELD || t2->type == MT_ORBINAUT || t2->type == MT_ORBINAUT_SHIELD @@ -774,11 +779,13 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2) // Clash instead of damage if both parties have any of these conditions t1Condition = (K_IsBigger(t1, t2) == true) || (t1->player->invincibilitytimer > 0) - || (t1->player->flamedash > 0 && t1->player->itemtype == KITEM_FLAMESHIELD); + || (t1->player->flamedash > 0 && t1->player->itemtype == KITEM_FLAMESHIELD) + || (t1->player->curshield == KSHIELD_TOP && !K_IsHoldingDownTop(t1->player)); t2Condition = (K_IsBigger(t2, t1) == true) || (t2->player->invincibilitytimer > 0) - || (t2->player->flamedash > 0 && t2->player->itemtype == KITEM_FLAMESHIELD); + || (t2->player->flamedash > 0 && t2->player->itemtype == KITEM_FLAMESHIELD) + || (t2->player->curshield == KSHIELD_TOP && !K_IsHoldingDownTop(t2->player)); if (t1Condition == true && t2Condition == true) { diff --git a/src/k_hud.c b/src/k_hud.c index 14e526dd2..6b4703d2b 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -111,7 +111,7 @@ static patch_t *kp_itemtimer[2]; static patch_t *kp_itemmulsticker[2]; static patch_t *kp_itemx; -static patch_t *kp_superring[2]; +static patch_t *kp_sadface[2]; static patch_t *kp_sneaker[2]; static patch_t *kp_rocketsneaker[2]; static patch_t *kp_invincibility[13]; @@ -121,7 +121,6 @@ static patch_t *kp_orbinaut[5]; static patch_t *kp_jawz[2]; static patch_t *kp_mine[2]; static patch_t *kp_landmine[2]; -static patch_t *kp_droptarget[2]; static patch_t *kp_ballhog[2]; static patch_t *kp_selfpropelledbomb[2]; static patch_t *kp_grow[2]; @@ -131,8 +130,10 @@ static patch_t *kp_bubbleshield[2]; static patch_t *kp_flameshield[2]; static patch_t *kp_hyudoro[2]; static patch_t *kp_pogospring[2]; +static patch_t *kp_superring[2]; static patch_t *kp_kitchensink[2]; -static patch_t *kp_sadface[2]; +static patch_t *kp_droptarget[2]; +static patch_t *kp_gardentop[2]; static patch_t *kp_check[6]; @@ -390,7 +391,7 @@ void K_LoadKartHUDGraphics(void) HU_UpdatePatch(&kp_itemmulsticker[0], "K_ITMUL"); HU_UpdatePatch(&kp_itemx, "K_ITX"); - HU_UpdatePatch(&kp_superring[0], "K_ITRING"); + HU_UpdatePatch(&kp_sadface[0], "K_ITSAD"); HU_UpdatePatch(&kp_sneaker[0], "K_ITSHOE"); HU_UpdatePatch(&kp_rocketsneaker[0], "K_ITRSHE"); @@ -411,7 +412,6 @@ void K_LoadKartHUDGraphics(void) HU_UpdatePatch(&kp_jawz[0], "K_ITJAWZ"); HU_UpdatePatch(&kp_mine[0], "K_ITMINE"); HU_UpdatePatch(&kp_landmine[0], "K_ITLNDM"); - HU_UpdatePatch(&kp_droptarget[0], "K_ITDTRG"); HU_UpdatePatch(&kp_ballhog[0], "K_ITBHOG"); HU_UpdatePatch(&kp_selfpropelledbomb[0], "K_ITSPB"); HU_UpdatePatch(&kp_grow[0], "K_ITGROW"); @@ -421,8 +421,10 @@ void K_LoadKartHUDGraphics(void) HU_UpdatePatch(&kp_flameshield[0], "K_ITFLMS"); HU_UpdatePatch(&kp_hyudoro[0], "K_ITHYUD"); HU_UpdatePatch(&kp_pogospring[0], "K_ITPOGO"); + HU_UpdatePatch(&kp_superring[0], "K_ITRING"); HU_UpdatePatch(&kp_kitchensink[0], "K_ITSINK"); - HU_UpdatePatch(&kp_sadface[0], "K_ITSAD"); + HU_UpdatePatch(&kp_droptarget[0], "K_ITDTRG"); + HU_UpdatePatch(&kp_gardentop[0], "K_ITGTOP"); sprintf(buffer, "FSMFGxxx"); for (i = 0; i < 104; i++) @@ -447,7 +449,7 @@ void K_LoadKartHUDGraphics(void) HU_UpdatePatch(&kp_itemtimer[1], "K_ISIMER"); HU_UpdatePatch(&kp_itemmulsticker[1], "K_ISMUL"); - HU_UpdatePatch(&kp_superring[1], "K_ISRING"); + HU_UpdatePatch(&kp_sadface[1], "K_ISSAD"); HU_UpdatePatch(&kp_sneaker[1], "K_ISSHOE"); HU_UpdatePatch(&kp_rocketsneaker[1], "K_ISRSHE"); sprintf(buffer, "K_ISINVx"); @@ -462,7 +464,6 @@ void K_LoadKartHUDGraphics(void) HU_UpdatePatch(&kp_jawz[1], "K_ISJAWZ"); HU_UpdatePatch(&kp_mine[1], "K_ISMINE"); HU_UpdatePatch(&kp_landmine[1], "K_ISLNDM"); - HU_UpdatePatch(&kp_droptarget[1], "K_ISDTRG"); HU_UpdatePatch(&kp_ballhog[1], "K_ISBHOG"); HU_UpdatePatch(&kp_selfpropelledbomb[1], "K_ISSPB"); HU_UpdatePatch(&kp_grow[1], "K_ISGROW"); @@ -472,8 +473,10 @@ void K_LoadKartHUDGraphics(void) HU_UpdatePatch(&kp_flameshield[1], "K_ISFLMS"); HU_UpdatePatch(&kp_hyudoro[1], "K_ISHYUD"); HU_UpdatePatch(&kp_pogospring[1], "K_ISPOGO"); + HU_UpdatePatch(&kp_superring[1], "K_ISRING"); HU_UpdatePatch(&kp_kitchensink[1], "K_ISSINK"); - HU_UpdatePatch(&kp_sadface[1], "K_ISSAD"); + HU_UpdatePatch(&kp_droptarget[1], "K_ISDTRG"); + HU_UpdatePatch(&kp_gardentop[1], "K_ISGTOP"); sprintf(buffer, "FSMFSxxx"); for (i = 0; i < 104; i++) @@ -662,8 +665,6 @@ const char *K_GetItemPatch(UINT8 item, boolean tiny) return (tiny ? "K_ISMINE" : "K_ITMINE"); case KITEM_LANDMINE: return (tiny ? "K_ISLNDM" : "K_ITLNDM"); - case KITEM_DROPTARGET: - return (tiny ? "K_ISDTRG" : "K_ITDTRG"); case KITEM_BALLHOG: return (tiny ? "K_ISBHOG" : "K_ITBHOG"); case KITEM_SPB: @@ -686,6 +687,10 @@ const char *K_GetItemPatch(UINT8 item, boolean tiny) return (tiny ? "K_ISRING" : "K_ITRING"); case KITEM_KITCHENSINK: return (tiny ? "K_ISSINK" : "K_ITSINK"); + case KITEM_DROPTARGET: + return (tiny ? "K_ISDTRG" : "K_ITDTRG"); + case KITEM_GARDENTOP: + return (tiny ? "K_ISGTOP" : "K_ITGTOP"); case KRITEM_TRIPLEORBINAUT: return (tiny ? "K_ISORBN" : "K_ITORB3"); case KRITEM_QUADORBINAUT: @@ -721,6 +726,7 @@ static patch_t *K_GetCachedItemPatch(INT32 item, UINT8 offset) kp_superring, kp_kitchensink, kp_droptarget, + kp_gardentop, }; if (item == KITEM_SAD || (item > KITEM_NONE && item < NUMKARTITEMS)) @@ -4456,6 +4462,7 @@ static void K_drawDistributionDebugger(void) kp_superring[1], kp_kitchensink[1], kp_droptarget[1], + kp_gardentop[1], kp_sneaker[1], kp_sneaker[1], diff --git a/src/k_kart.c b/src/k_kart.c index 44f7501fb..66432ad54 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -221,7 +221,6 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_jawz); CV_RegisterVar(&cv_mine); CV_RegisterVar(&cv_landmine); - CV_RegisterVar(&cv_droptarget); CV_RegisterVar(&cv_ballhog); CV_RegisterVar(&cv_selfpropelledbomb); CV_RegisterVar(&cv_grow); @@ -233,6 +232,8 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_pogospring); CV_RegisterVar(&cv_superring); CV_RegisterVar(&cv_kitchensink); + CV_RegisterVar(&cv_droptarget); + CV_RegisterVar(&cv_gardentop); CV_RegisterVar(&cv_dualsneaker); CV_RegisterVar(&cv_triplesneaker); @@ -339,6 +340,7 @@ consvar_t *KartItemCVars[NUMKARTRESULTS-1] = &cv_superring, &cv_kitchensink, &cv_droptarget, + &cv_gardentop, &cv_dualsneaker, &cv_triplesneaker, &cv_triplebanana, @@ -353,68 +355,70 @@ consvar_t *KartItemCVars[NUMKARTRESULTS-1] = // Less ugly 2D arrays static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][8] = { - //P-Odds 0 1 2 3 4 5 6 7 - /*Sneaker*/ { 0, 0, 2, 4, 6, 0, 0, 0 }, // Sneaker - /*Rocket Sneaker*/ { 0, 0, 0, 0, 0, 2, 4, 6 }, // Rocket Sneaker - /*Invincibility*/ { 0, 0, 0, 0, 3, 4, 5, 7 }, // Invincibility - /*Banana*/ { 2, 3, 1, 0, 0, 0, 0, 0 }, // Banana - /*Eggman Monitor*/ { 1, 2, 0, 0, 0, 0, 0, 0 }, // Eggman Monitor - /*Orbinaut*/ { 5, 5, 2, 2, 0, 0, 0, 0 }, // Orbinaut - /*Jawz*/ { 0, 4, 2, 1, 0, 0, 0, 0 }, // Jawz - /*Mine*/ { 0, 3, 3, 1, 0, 0, 0, 0 }, // Mine - /*Land Mine*/ { 3, 0, 0, 0, 0, 0, 0, 0 }, // Land Mine - /*Ballhog*/ { 0, 0, 2, 2, 0, 0, 0, 0 }, // Ballhog - /*Self-Propelled Bomb*/ { 0, 0, 0, 0, 0, 0, 0, 0 }, // Self-Propelled Bomb - /*Grow*/ { 0, 0, 0, 1, 2, 3, 0, 0 }, // Grow - /*Shrink*/ { 0, 0, 0, 0, 0, 1, 3, 2 }, // Shrink - /*Lightning Shield*/ { 1, 0, 0, 0, 0, 0, 0, 0 }, // Lightning Shield - /*Bubble Shield*/ { 0, 1, 2, 1, 0, 0, 0, 0 }, // Bubble Shield - /*Flame Shield*/ { 0, 0, 0, 0, 0, 1, 3, 5 }, // Flame Shield - /*Hyudoro*/ { 3, 0, 0, 0, 0, 0, 0, 0 }, // Hyudoro - /*Pogo Spring*/ { 0, 0, 0, 0, 0, 0, 0, 0 }, // Pogo Spring - /*Super Ring*/ { 2, 1, 1, 0, 0, 0, 0, 0 }, // Super Ring - /*Kitchen Sink*/ { 0, 0, 0, 0, 0, 0, 0, 0 }, // Kitchen Sink - /*Drop Target*/ { 3, 0, 0, 0, 0, 0, 0, 0 }, // Drop Target - /*Sneaker x2*/ { 0, 0, 2, 2, 2, 0, 0, 0 }, // Sneaker x2 - /*Sneaker x3*/ { 0, 0, 0, 1, 6, 9, 5, 0 }, // Sneaker x3 - /*Banana x3*/ { 0, 1, 1, 0, 0, 0, 0, 0 }, // Banana x3 - /*Banana x10*/ { 0, 0, 0, 1, 0, 0, 0, 0 }, // Banana x10 - /*Orbinaut x3*/ { 0, 0, 1, 0, 0, 0, 0, 0 }, // Orbinaut x3 - /*Orbinaut x4*/ { 0, 0, 0, 2, 0, 0, 0, 0 }, // Orbinaut x4 - /*Jawz x2*/ { 0, 0, 1, 2, 1, 0, 0, 0 } // Jawz x2 + //B C D E F G H I + { 0, 0, 2, 3, 4, 0, 0, 0 }, // Sneaker + { 0, 0, 0, 0, 0, 3, 4, 5 }, // Rocket Sneaker + { 0, 0, 0, 0, 2, 5, 5, 7 }, // Invincibility + { 2, 3, 1, 0, 0, 0, 0, 0 }, // Banana + { 1, 2, 0, 0, 0, 0, 0, 0 }, // Eggman Monitor + { 5, 5, 2, 2, 0, 0, 0, 0 }, // Orbinaut + { 0, 4, 2, 1, 0, 0, 0, 0 }, // Jawz + { 0, 3, 3, 1, 0, 0, 0, 0 }, // Mine + { 3, 0, 0, 0, 0, 0, 0, 0 }, // Land Mine + { 0, 0, 2, 2, 0, 0, 0, 0 }, // Ballhog + { 0, 0, 0, 0, 0, 2, 4, 0 }, // Self-Propelled Bomb + { 0, 0, 0, 0, 2, 5, 0, 0 }, // Grow + { 0, 0, 0, 0, 0, 2, 4, 2 }, // Shrink + { 1, 0, 0, 0, 0, 0, 0, 0 }, // Lightning Shield + { 0, 1, 2, 1, 0, 0, 0, 0 }, // Bubble Shield + { 0, 0, 0, 0, 0, 1, 3, 5 }, // Flame Shield + { 3, 0, 0, 0, 0, 0, 0, 0 }, // Hyudoro + { 0, 0, 0, 0, 0, 0, 0, 0 }, // Pogo Spring + { 2, 1, 1, 0, 0, 0, 0, 0 }, // Super Ring + { 0, 0, 0, 0, 0, 0, 0, 0 }, // Kitchen Sink + { 3, 0, 0, 0, 0, 0, 0, 0 }, // Drop Target + { 0, 0, 0, 3, 5, 0, 0, 0 }, // Garden Top + { 0, 0, 2, 2, 2, 0, 0, 0 }, // Sneaker x2 + { 0, 0, 0, 0, 4, 4, 4, 0 }, // Sneaker x3 + { 0, 1, 1, 0, 0, 0, 0, 0 }, // Banana x3 + { 0, 0, 0, 1, 0, 0, 0, 0 }, // Banana x10 + { 0, 0, 1, 0, 0, 0, 0, 0 }, // Orbinaut x3 + { 0, 0, 0, 2, 0, 0, 0, 0 }, // Orbinaut x4 + { 0, 0, 1, 2, 1, 0, 0, 0 } // Jawz x2 }; static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS][2] = { - //P-Odds 0 1 - /*Sneaker*/ { 2, 1 }, // Sneaker - /*Rocket Sneaker*/ { 0, 0 }, // Rocket Sneaker - /*Invincibility*/ { 4, 1 }, // Invincibility - /*Banana*/ { 0, 0 }, // Banana - /*Eggman Monitor*/ { 1, 0 }, // Eggman Monitor - /*Orbinaut*/ { 8, 0 }, // Orbinaut - /*Jawz*/ { 8, 1 }, // Jawz - /*Mine*/ { 6, 1 }, // Mine - /*Land Mine*/ { 2, 0 }, // Land Mine - /*Ballhog*/ { 2, 1 }, // Ballhog - /*Self-Propelled Bomb*/ { 0, 0 }, // Self-Propelled Bomb - /*Grow*/ { 2, 1 }, // Grow - /*Shrink*/ { 0, 0 }, // Shrink - /*Lightning Shield*/ { 4, 0 }, // Lightning Shield - /*Bubble Shield*/ { 1, 0 }, // Bubble Shield - /*Flame Shield*/ { 1, 0 }, // Flame Shield - /*Hyudoro*/ { 2, 0 }, // Hyudoro - /*Pogo Spring*/ { 3, 0 }, // Pogo Spring - /*Super Ring*/ { 0, 0 }, // Super Ring - /*Kitchen Sink*/ { 0, 0 }, // Kitchen Sink - /*Drop Target*/ { 2, 0 }, // Drop Target - /*Sneaker x2*/ { 0, 0 }, // Sneaker x2 - /*Sneaker x3*/ { 0, 1 }, // Sneaker x3 - /*Banana x3*/ { 0, 0 }, // Banana x3 - /*Banana x10*/ { 1, 1 }, // Banana x10 - /*Orbinaut x3*/ { 2, 0 }, // Orbinaut x3 - /*Orbinaut x4*/ { 1, 1 }, // Orbinaut x4 - /*Jawz x2*/ { 5, 1 } // Jawz x2 + //K L + { 2, 1 }, // Sneaker + { 0, 0 }, // Rocket Sneaker + { 4, 1 }, // Invincibility + { 0, 0 }, // Banana + { 1, 0 }, // Eggman Monitor + { 8, 0 }, // Orbinaut + { 8, 1 }, // Jawz + { 6, 1 }, // Mine + { 2, 0 }, // Land Mine + { 2, 1 }, // Ballhog + { 0, 0 }, // Self-Propelled Bomb + { 2, 1 }, // Grow + { 0, 0 }, // Shrink + { 4, 0 }, // Lightning Shield + { 1, 0 }, // Bubble Shield + { 1, 0 }, // Flame Shield + { 2, 0 }, // Hyudoro + { 3, 0 }, // Pogo Spring + { 0, 0 }, // Super Ring + { 0, 0 }, // Kitchen Sink + { 2, 0 }, // Drop Target + { 4, 0 }, // Garden Top + { 0, 0 }, // Sneaker x2 + { 0, 1 }, // Sneaker x3 + { 0, 0 }, // Banana x3 + { 1, 1 }, // Banana x10 + { 2, 0 }, // Orbinaut x3 + { 1, 1 }, // Orbinaut x4 + { 5, 1 } // Jawz x2 }; #define DISTVAR (2048) // Magic number distance for use with item roulette tiers @@ -445,6 +449,7 @@ INT32 K_GetShieldFromItem(INT32 item) case KITEM_LIGHTNINGSHIELD: return KSHIELD_LIGHTNING; case KITEM_BUBBLESHIELD: return KSHIELD_BUBBLE; case KITEM_FLAMESHIELD: return KSHIELD_FLAME; + case KITEM_GARDENTOP: return KSHIELD_TOP; default: return KSHIELD_NONE; } } @@ -718,10 +723,20 @@ INT32 K_KartGetItemOdds( if (players[i].exiting) pexiting++; - if (shieldtype != KSHIELD_NONE && shieldtype == K_GetShieldFromItem(players[i].itemtype)) + switch (shieldtype) { - // Don't allow more than one of each shield type at a time - return 0; + case KSHIELD_NONE: + /* Marble Garden Top is not REALLY + a Sonic 3 shield */ + case KSHIELD_TOP: + break; + + default: + if (shieldtype == K_GetShieldFromItem(players[i].itemtype)) + { + // Don't allow more than one of each shield type at a time + return 0; + } } if (players[i].position == 1) @@ -1362,7 +1377,13 @@ static fixed_t K_PlayerWeight(mobj_t *mobj, mobj_t *against) if (!mobj->player) return weight; - if (against && !P_MobjWasRemoved(against) && against->player + if (against && (against->type == MT_GARDENTOP || (against->player && against->player->curshield == KSHIELD_TOP))) + { + /* Players bumping into a Top get zero weight -- the + Top rider is immovable. */ + weight = 0; + } + else if (against && !P_MobjWasRemoved(against) && against->player && ((!P_PlayerInPain(against->player) && P_PlayerInPain(mobj->player)) // You're hurt || (against->player->itemtype == KITEM_BUBBLESHIELD && mobj->player->itemtype != KITEM_BUBBLESHIELD))) // They have a Bubble Shield { @@ -1951,6 +1972,18 @@ static void K_DrawDraftCombiring(player_t *player, player_t *victim, fixed_t cur #undef CHAOTIXBANDLEN } +static boolean K_HasInfiniteTether(player_t *player) +{ + switch (player->curshield) + { + case KSHIELD_LIGHTNING: + case KSHIELD_TOP: + return true; + } + + return false; +} + /** \brief Updates the player's drafting values once per frame \param player player object passed from K_KartPlayerThink @@ -1965,7 +1998,7 @@ static void K_UpdateDraft(player_t *player) UINT8 leniency; UINT8 i; - if (player->itemtype == KITEM_LIGHTNINGSHIELD) + if (K_HasInfiniteTether(player)) { // Lightning Shield gets infinite draft distance as its (other) passive effect. draftdistance = 0; @@ -2403,7 +2436,7 @@ void K_SpawnDriftBoostClipSpark(mobj_t *clip) spark->momy = clip->momx/2; } -void K_SpawnNormalSpeedLines(player_t *player) +static void K_SpawnGenericSpeedLines(player_t *player, boolean top) { mobj_t *fast = P_SpawnMobj(player->mo->x + (P_RandomRange(PR_DECORATION,-36,36) * player->mo->scale), player->mo->y + (P_RandomRange(PR_DECORATION,-36,36) * player->mo->scale), @@ -2411,20 +2444,40 @@ void K_SpawnNormalSpeedLines(player_t *player) MT_FASTLINE); P_SetTarget(&fast->target, player->mo); - P_InitAngle(fast, K_MomentumAngle(player->mo)); fast->momx = 3*player->mo->momx/4; fast->momy = 3*player->mo->momy/4; fast->momz = 3*P_GetMobjZMovement(player->mo)/4; - K_MatchGenericExtraFlags(fast, player->mo); + fast->z += player->mo->sprzoff; - if (player->tripwireLeniency) + if (top) { - fast->destscale = fast->destscale * 2; - P_SetScale(fast, 3*fast->scale/2); + P_InitAngle(fast, player->mo->angle); + P_SetScale(fast, (fast->destscale = + 3 * fast->destscale / 2)); + + fast->spritexscale = 3*FRACUNIT; + } + else + { + P_InitAngle(fast, K_MomentumAngle(player->mo)); + + if (player->tripwireLeniency) + { + fast->destscale = fast->destscale * 2; + P_SetScale(fast, 3*fast->scale/2); + } } - if (player->eggmanexplode) + K_MatchGenericExtraFlags(fast, player->mo); + + if (top) + { + fast->color = SKINCOLOR_SUNSLAM; + fast->colorized = true; + fast->renderflags |= RF_ADD; + } + else if (player->eggmanexplode) { // Make it red when you have the eggman speed boost fast->color = SKINCOLOR_RED; @@ -2452,6 +2505,16 @@ void K_SpawnNormalSpeedLines(player_t *player) } } +void K_SpawnNormalSpeedLines(player_t *player) +{ + K_SpawnGenericSpeedLines(player, false); +} + +void K_SpawnGardenTopSpeedLines(player_t *player) +{ + K_SpawnGenericSpeedLines(player, true); +} + void K_SpawnInvincibilitySpeedLines(mobj_t *mo) { mobj_t *fast = P_SpawnMobjFromMobj(mo, @@ -2550,14 +2613,21 @@ static void K_SpawnGrowShrinkParticles(mobj_t *mo, INT32 timer) void K_SpawnBumpEffect(mobj_t *mo) { + mobj_t *top = mo->player ? K_GetGardenTop(mo->player) : NULL; + mobj_t *fx = P_SpawnMobj(mo->x, mo->y, mo->z, MT_BUMP); + if (mo->eflags & MFE_VERTICALFLIP) fx->eflags |= MFE_VERTICALFLIP; else fx->eflags &= ~MFE_VERTICALFLIP; + fx->scale = mo->scale; - S_StartSound(mo, sfx_s3k49); + if (top) + S_StartSound(mo, top->info->attacksound); + else + S_StartSound(mo, sfx_s3k49); } static SINT8 K_GlanceAtPlayers(player_t *glancePlayer) @@ -2712,6 +2782,10 @@ void K_KartMoveAnimation(player_t *player) drift = intsign(player->aizdriftturn); turndir = 0; } + else if (player->curshield == KSHIELD_TOP) + { + drift = -turndir; + } else if (turndir == 0 && drift == 0) { // Only try glancing if you're driving straight. @@ -3215,6 +3289,8 @@ boolean K_ApplyOffroad(player_t *player) { if (player->invincibilitytimer || player->hyudorotimer || player->sneakertimer) return false; + if (K_IsRidingFloatingTop(player)) + return false; return true; } @@ -3222,6 +3298,8 @@ boolean K_SlopeResistance(player_t *player) { if (player->invincibilitytimer || player->sneakertimer || player->tiregrease || player->flamedash) return true; + if (player->curshield == KSHIELD_TOP) + return true; return false; } @@ -3235,7 +3313,7 @@ tripwirepass_t K_TripwirePassConditions(player_t *player) if ( player->flamedash || - player->speed > 2 * K_GetKartSpeed(player, false, true) + player->speed > 2 * K_GetKartSpeed(player, false, false) ) return TRIPWIRE_BOOST; @@ -3253,17 +3331,296 @@ boolean K_TripwirePass(player_t *player) return (player->tripwirePass != TRIPWIRE_NONE); } -boolean K_WaterRun(player_t *player) +boolean K_MovingHorizontally(mobj_t *mobj) { - if ( - player->invincibilitytimer || - player->sneakertimer || - player->tiregrease || - player->flamedash || - player->speed > 2 * K_GetKartSpeed(player, false, true) - ) - return true; - return false; + return (P_AproxDistance(mobj->momx, mobj->momy) / 5 > abs(mobj->momz)); +} + +boolean K_WaterRun(mobj_t *mobj) +{ + switch (mobj->type) + { + case MT_JAWZ: + { + if (mobj->tracer != NULL && P_MobjWasRemoved(mobj->tracer) == false) + { + fixed_t jawzFeet = P_GetMobjFeet(mobj); + fixed_t chaseFeet = P_GetMobjFeet(mobj->tracer); + fixed_t footDiff = (chaseFeet - jawzFeet) * P_MobjFlip(mobj); + + // Water run if the player we're chasing is above/equal to us. + // Start water skipping if they're underneath the water. + return (footDiff > -mobj->tracer->height); + } + + return false; + } + + case MT_PLAYER: + { + if (mobj->player == NULL) + { + return false; + } + + if (mobj->player->curshield == KSHIELD_TOP) + { + return K_IsHoldingDownTop(mobj->player) == false; + } + + if (mobj->player->invincibilitytimer + || mobj->player->sneakertimer + || mobj->player->tiregrease + || mobj->player->flamedash + || mobj->player->speed > 2 * K_GetKartSpeed(mobj->player, false, false)) + { + return true; + } + + return false; + } + + default: + { + return false; + } + } +} + +boolean K_WaterSkip(mobj_t *mobj) +{ + if (mobj->waterskip >= 2) + { + // Already finished waterskipping. + return false; + } + + switch (mobj->type) + { + case MT_PLAYER: + { + if (mobj->player != NULL && mobj->player->curshield == KSHIELD_TOP) + { + // Don't allow + return false; + } + // Allow + break; + } + + case MT_ORBINAUT: + case MT_JAWZ: + case MT_BALLHOG: + { + // Allow + break; + } + + default: + { + // Don't allow + return false; + } + } + + if (mobj->waterskip > 0) + { + // Already waterskipping. + // Simply make sure you haven't slowed down drastically. + return (P_AproxDistance(mobj->momx, mobj->momy) > 20 * mapobjectscale); + } + else + { + // Need to be moving horizontally and not vertically + // to be able to start a water skip. + return K_MovingHorizontally(mobj); + } +} + +void K_SpawnWaterRunParticles(mobj_t *mobj) +{ + fixed_t runSpeed = 14 * mobj->scale; + fixed_t curSpeed = INT32_MAX; + fixed_t topSpeed = INT32_MAX; + fixed_t trailScale = FRACUNIT; + + if (mobj->momz != 0) + { + // Only while touching ground. + return; + } + + if (mobj->watertop == INT32_MAX || mobj->waterbottom == INT32_MIN) + { + // Invalid water plane. + return; + } + + if (mobj->player != NULL) + { + if (mobj->player->spectator) + { + // Not as spectator. + return; + } + + if (mobj->player->carry == CR_SLIDING) + { + // Not in water slides. + return; + } + + topSpeed = K_GetKartSpeed(mobj->player, false, false); + runSpeed = FixedMul(runSpeed, mobj->movefactor); + } + else + { + topSpeed = FixedMul(mobj->scale, K_GetKartSpeedFromStat(5)); + } + + curSpeed = P_AproxDistance(mobj->momx, mobj->momy); + + if (curSpeed <= runSpeed) + { + // Not fast enough. + return; + } + + // Near the water plane. + if ((!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z + mobj->height >= mobj->watertop && mobj->z <= mobj->watertop) + || (mobj->eflags & MFE_VERTICALFLIP && mobj->z + mobj->height >= mobj->waterbottom && mobj->z <= mobj->waterbottom)) + { + if (topSpeed > runSpeed) + { + trailScale = FixedMul(FixedDiv(curSpeed - runSpeed, topSpeed - runSpeed), mapobjectscale); + } + else + { + trailScale = mapobjectscale; // Scaling is based off difference between runspeed and top speed + } + + if (trailScale > 0) + { + const angle_t forwardangle = K_MomentumAngle(mobj); + const fixed_t playerVisualRadius = mobj->radius + (8 * mobj->scale); + const size_t numFrames = S_WATERTRAIL8 - S_WATERTRAIL1; + const statenum_t curOverlayFrame = S_WATERTRAIL1 + (leveltime % numFrames); + const statenum_t curUnderlayFrame = S_WATERTRAILUNDERLAY1 + (leveltime % numFrames); + fixed_t x1, x2, y1, y2; + mobj_t *water; + + x1 = mobj->x + mobj->momx + P_ReturnThrustX(mobj, forwardangle + ANGLE_90, playerVisualRadius); + y1 = mobj->y + mobj->momy + P_ReturnThrustY(mobj, forwardangle + ANGLE_90, playerVisualRadius); + x1 = x1 + P_ReturnThrustX(mobj, forwardangle, playerVisualRadius); + y1 = y1 + P_ReturnThrustY(mobj, forwardangle, playerVisualRadius); + + x2 = mobj->x + mobj->momx + P_ReturnThrustX(mobj, forwardangle - ANGLE_90, playerVisualRadius); + y2 = mobj->y + mobj->momy + P_ReturnThrustY(mobj, forwardangle - ANGLE_90, playerVisualRadius); + x2 = x2 + P_ReturnThrustX(mobj, forwardangle, playerVisualRadius); + y2 = y2 + P_ReturnThrustY(mobj, forwardangle, playerVisualRadius); + + // Left + // underlay + water = P_SpawnMobj(x1, y1, + ((mobj->eflags & MFE_VERTICALFLIP) ? mobj->waterbottom - FixedMul(mobjinfo[MT_WATERTRAILUNDERLAY].height, mobj->scale) : mobj->watertop), MT_WATERTRAILUNDERLAY); + P_InitAngle(water, forwardangle - ANGLE_180 - ANGLE_22h); + water->destscale = trailScale; + water->momx = mobj->momx; + water->momy = mobj->momy; + water->momz = mobj->momz; + P_SetScale(water, trailScale); + P_SetMobjState(water, curUnderlayFrame); + + // overlay + water = P_SpawnMobj(x1, y1, + ((mobj->eflags & MFE_VERTICALFLIP) ? mobj->waterbottom - FixedMul(mobjinfo[MT_WATERTRAIL].height, mobj->scale) : mobj->watertop), MT_WATERTRAIL); + P_InitAngle(water, forwardangle - ANGLE_180 - ANGLE_22h); + water->destscale = trailScale; + water->momx = mobj->momx; + water->momy = mobj->momy; + water->momz = mobj->momz; + P_SetScale(water, trailScale); + P_SetMobjState(water, curOverlayFrame); + + // Right + // Underlay + water = P_SpawnMobj(x2, y2, + ((mobj->eflags & MFE_VERTICALFLIP) ? mobj->waterbottom - FixedMul(mobjinfo[MT_WATERTRAILUNDERLAY].height, mobj->scale) : mobj->watertop), MT_WATERTRAILUNDERLAY); + P_InitAngle(water, forwardangle - ANGLE_180 + ANGLE_22h); + water->destscale = trailScale; + water->momx = mobj->momx; + water->momy = mobj->momy; + water->momz = mobj->momz; + P_SetScale(water, trailScale); + P_SetMobjState(water, curUnderlayFrame); + + // Overlay + water = P_SpawnMobj(x2, y2, + ((mobj->eflags & MFE_VERTICALFLIP) ? mobj->waterbottom - FixedMul(mobjinfo[MT_WATERTRAIL].height, mobj->scale) : mobj->watertop), MT_WATERTRAIL); + P_InitAngle(water, forwardangle - ANGLE_180 + ANGLE_22h); + water->destscale = trailScale; + water->momx = mobj->momx; + water->momy = mobj->momy; + water->momz = mobj->momz; + P_SetScale(water, trailScale); + P_SetMobjState(water, curOverlayFrame); + + if (!S_SoundPlaying(mobj, sfx_s3kdbs)) + { + const INT32 volume = (min(trailScale, FRACUNIT) * 255) / FRACUNIT; + S_StartSoundAtVolume(mobj, sfx_s3kdbs, volume); + } + } + + // Little water sound while touching water - just a nicety. + if ((mobj->eflags & MFE_TOUCHWATER) && !(mobj->eflags & MFE_UNDERWATER)) + { + if (P_RandomChance(PR_BUBBLE, FRACUNIT/2) && leveltime % TICRATE == 0) + { + S_StartSound(mobj, sfx_floush); + } + } + } +} + +boolean K_IsRidingFloatingTop(player_t *player) +{ + if (player->curshield != KSHIELD_TOP) + { + return false; + } + + return !Obj_GardenTopPlayerIsGrinding(player); +} + +boolean K_IsHoldingDownTop(player_t *player) +{ + if (player->curshield != KSHIELD_TOP) + { + return false; + } + + if ((K_GetKartButtons(player) & BT_DRIFT) != BT_DRIFT) + { + return false; + } + + return true; +} + +mobj_t *K_GetGardenTop(player_t *player) +{ + if (player->curshield != KSHIELD_TOP) + { + return NULL; + } + + if (player->mo == NULL) + { + return NULL; + } + + return player->mo->hnext; } static fixed_t K_FlameShieldDashVar(INT32 val) @@ -3428,7 +3785,7 @@ static void K_GetKartBoostPower(player_t *player) draftspeed *= 2; } - if (player->itemtype == KITEM_LIGHTNINGSHIELD) + if (K_HasInfiniteTether(player)) { // infinite tether draftspeed *= 2; @@ -3550,6 +3907,10 @@ fixed_t K_GetKartAccel(player_t *player) if (gametype == GT_BATTLE && player->bumpers <= 0) k_accel *= 2; + // Marble Garden Top gets 800% accel + if (player->curshield == KSHIELD_TOP) + k_accel *= 8; + return FixedMul(k_accel, (FRACUNIT + player->accelboost) / 4); } @@ -3639,17 +4000,35 @@ SINT8 K_GetForwardMove(player_t *player) forwardmove = MAXPLMOVE; } + if (player->curshield == KSHIELD_TOP) + { + if (forwardmove < 0 || + (K_GetKartButtons(player) & BT_DRIFT)) + { + forwardmove = 0; + } + else + { + forwardmove = MAXPLMOVE; + } + } + return forwardmove; } fixed_t K_GetNewSpeed(player_t *player) { const fixed_t accelmax = 4000; - const fixed_t p_speed = K_GetKartSpeed(player, true, true); + fixed_t p_speed = K_GetKartSpeed(player, true, true); fixed_t p_accel = K_GetKartAccel(player); fixed_t newspeed, oldspeed, finalspeed; + if (player->curshield == KSHIELD_TOP) + { + p_speed = 11 * p_speed / 10; + } + if (K_PlayerUsesBotMovement(player) == true && player->botvars.rubberband > 0) { // Acceleration is tied to top speed... @@ -4676,6 +5055,19 @@ fixed_t K_ItemScaleForPlayer(player_t *player) } } +fixed_t K_DefaultPlayerRadius(player_t *player) +{ + mobj_t *top = K_GetGardenTop(player); + + if (top) + { + return top->radius; + } + + return FixedMul(player->mo->scale, + player->mo->info->radius); +} + static mobj_t *K_SpawnKartMissile(mobj_t *source, mobjtype_t type, angle_t an, INT32 flags2, fixed_t speed, SINT8 dir) { mobj_t *th; @@ -4790,6 +5182,9 @@ static mobj_t *K_SpawnKartMissile(mobj_t *source, mobjtype_t type, angle_t an, I th->destscale = th->destscale << 1; th->scalespeed = abs(th->destscale - th->scale) / (2*TICRATE); break; + case MT_GARDENTOP: + th->movefactor = finalspeed; + break; default: break; } @@ -5450,8 +5845,11 @@ void K_DriftDustHandling(mobj_t *spawner) dust->destscale = spawner->scale * 3; dust->scalespeed = spawner->scale/12; - if (leveltime % 6 == 0) - S_StartSound(spawner, sfx_screec); + if (!spawner->player || !K_GetGardenTop(spawner->player)) + { + if (leveltime % 6 == 0) + S_StartSound(spawner, sfx_screec); + } K_MatchGenericExtraFlags(dust, spawner); @@ -5611,7 +6009,7 @@ mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t mapthing, if (missile) // Shootables { - if (dir < 0 && mapthing != MT_SPB) + if (dir < 0 && mapthing != MT_SPB && mapthing != MT_GARDENTOP) { // Shoot backward mo = K_SpawnKartMissile(player->mo, mapthing, (player->mo->angle + ANGLE_180) + angleOffset, 0, PROJSPEED, dir); @@ -6222,7 +6620,7 @@ void K_DropHnextList(player_t *player, boolean keepshields) flip = P_MobjFlip(player->mo); ponground = P_IsObjectOnGround(player->mo); - if (shield != KSHIELD_NONE && !keepshields) + if (shield != KSHIELD_NONE && shield != KSHIELD_TOP && !keepshields) { if (shield == KSHIELD_LIGHTNING) { @@ -6274,6 +6672,9 @@ void K_DropHnextList(player_t *player, boolean keepshields) orbit = false; type = MT_EGGMANITEM; break; + case MT_GARDENTOP: + Obj_GardenTopDestroy(player); + return; // intentionally do nothing case MT_ROCKETSNEAKER: case MT_SINK_SHIELD: @@ -7580,6 +7981,33 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) { const boolean onground = P_IsObjectOnGround(player->mo); + /* reset sprite offsets :) */ + player->mo->sprxoff = 0; + player->mo->spryoff = 0; + player->mo->sprzoff = 0; + player->mo->spritexoffset = 0; + player->mo->spriteyoffset = 0; + + if (player->curshield == KSHIELD_TOP) + { + mobj_t *top = K_GetGardenTop(player); + + if (top) + { + /* FIXME: I cannot figure out how offset the + player correctly in real time to pivot around + the BOTTOM of the Top. This hack plus the one + in R_PlayerSpriteRotation. */ + player->mo->spritexoffset += FixedMul( + FixedDiv(top->height, top->scale), + FINESINE(top->rollangle >> ANGLETOFINESHIFT)); + + player->mo->sprzoff += top->sprzoff + ( + P_GetMobjHead(top) - + P_GetMobjFeet(player->mo)); + } + } + K_UpdateOffroad(player); K_UpdateDraft(player); K_UpdateEngineSounds(player); // Thanks, VAda! @@ -7960,9 +8388,6 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) } } - if (P_IsObjectOnGround(player->mo)) - player->waterskip = 0; - if (player->instashield) player->instashield--; @@ -8027,8 +8452,20 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (cmd->buttons & BT_DRIFT) { + if (player->curshield == KSHIELD_TOP) + { + if (player->topdriftheld <= GARDENTOP_MAXGRINDTIME) + player->topdriftheld++; + + // Squish :) + player->mo->spritexscale = 6*FRACUNIT/4; + player->mo->spriteyscale = 2*FRACUNIT/4; + + if (leveltime & 1) + K_SpawnGardenTopSpeedLines(player); + } // Only allow drifting while NOT trying to do an spindash input. - if ((K_GetKartButtons(player) & BT_EBRAKEMASK) != BT_EBRAKEMASK) + else if ((K_GetKartButtons(player) & BT_EBRAKEMASK) != BT_EBRAKEMASK) { player->pflags |= PF_DRIFTINPUT; } @@ -8165,7 +8602,7 @@ void K_KartResetPlayerColor(player_t *player) finalise: - if (player->curshield) + if (player->curshield && player->curshield != KSHIELD_TOP) { fullbright = true; } @@ -8756,6 +9193,16 @@ INT16 K_UpdateSteeringValue(INT16 inputSteering, INT16 destSteering) return outputSteering; } +static fixed_t K_GetUnderwaterStrafeMul(player_t *player) +{ + const fixed_t minSpeed = 11 * player->mo->scale; + fixed_t baseline = INT32_MAX; + + baseline = 2 * K_GetKartSpeed(player, false, true) / 3; + + return max(0, FixedDiv(player->speed - minSpeed, baseline - minSpeed)); +} + INT16 K_GetKartTurnValue(player_t *player, INT16 turnvalue) { fixed_t turnfixed = turnvalue * FRACUNIT; @@ -8800,13 +9247,25 @@ INT16 K_GetKartTurnValue(player_t *player, INT16 turnvalue) if ((currentSpeed <= 0) // Not moving && ((K_GetKartButtons(player) & BT_EBRAKEMASK) != BT_EBRAKEMASK) // 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 { return 0; } p_maxspeed = K_GetKartSpeed(player, false, true); - p_speed = min(currentSpeed, (p_maxspeed * 2)); + + if (player->curshield == KSHIELD_TOP) + { + // Do not downscale turning speed with faster + // movement speed; behaves as if turning in place. + p_speed = 0; + } + else + { + p_speed = min(currentSpeed, (p_maxspeed * 2)); + } + weightadjust = FixedDiv((p_maxspeed * 3) - p_speed, (p_maxspeed * 3) + (player->kartweight * FRACUNIT)); if (K_PlayerUsesBotMovement(player)) @@ -8833,10 +9292,12 @@ INT16 K_GetKartTurnValue(player_t *player, INT16 turnvalue) turnfixed = FixedMul(turnfixed, FRACUNIT + player->handleboost); } - if ((player->mo->eflags & MFE_UNDERWATER) && - player->speed > 11 * player->mo->scale) + if (player->curshield == KSHIELD_TOP) + ; + else if (player->mo->eflags & MFE_UNDERWATER) { - turnfixed /= 2; + fixed_t div = min(FRACUNIT + K_GetUnderwaterStrafeMul(player), 2*FRACUNIT); + turnfixed = FixedDiv(turnfixed, div); } // Weight has a small effect on turning @@ -8847,8 +9308,7 @@ INT16 K_GetKartTurnValue(player_t *player, INT16 turnvalue) INT32 K_GetUnderwaterTurnAdjust(player_t *player) { - if ((player->mo->eflags & MFE_UNDERWATER) && - player->speed > 11 * player->mo->scale) + if (player->mo->eflags & MFE_UNDERWATER) { INT32 steer = (K_GetKartTurnValue(player, player->steering) << TICCMD_REDUCE); @@ -8856,8 +9316,7 @@ INT32 K_GetUnderwaterTurnAdjust(player_t *player) if (!player->drift) steer = 9 * steer / 5; - return FixedMul(steer, 8 * FixedDiv(player->speed, - 2 * K_GetKartSpeed(player, false, true) / 3)); + return FixedMul(steer, 8 * K_GetUnderwaterStrafeMul(player)); } else return 0; @@ -9223,6 +9682,7 @@ void K_KartUpdatePosition(player_t *player) fixed_t position = 1; fixed_t oldposition = player->position; fixed_t i; + INT32 realplayers = 0; if (player->spectator || !player->mo) { @@ -9237,6 +9697,8 @@ void K_KartUpdatePosition(player_t *player) if (!playeringame[i] || players[i].spectator || !players[i].mo) continue; + realplayers++; + if (gametyperules & GTR_CIRCUIT) { if (player->exiting) // End of match standings @@ -9300,6 +9762,33 @@ void K_KartUpdatePosition(player_t *player) if (oldposition != position) // Changed places? player->positiondelay = 10; // Position number growth + /* except in FREE PLAY */ + if (player->curshield == KSHIELD_TOP && + (gametyperules & GTR_CIRCUIT) && + realplayers > 1) + { + /* grace period so you don't fall off INSTANTLY */ + if (position == 1 && player->topinfirst < 2*TICRATE) + { + player->topinfirst++; + } + else + { + if (position == 1) + { + Obj_GardenTopThrow(player); + } + else + { + player->topinfirst = 0; + } + } + } + else + { + player->topinfirst = 0; + } + player->position = position; } @@ -9451,6 +9940,7 @@ void K_KartEbrakeVisuals(player_t *p) p->mo->hprev->angle = p->mo->angle; p->mo->hprev->fuse = TICRATE/2; // When we leave spindash for any reason, make sure this bubble goes away soon after. K_FlipFromObject(p->mo->hprev, p->mo); + p->mo->hprev->sprzoff = p->mo->sprzoff; } if (!p->spindash) @@ -9818,6 +10308,11 @@ void K_AdjustPlayerFriction(player_t *player) player->mo->friction += ((FRACUNIT - prevfriction) / greasetics) * player->tiregrease; } + if (player->curshield == KSHIELD_TOP) + { + player->mo->friction += 1024; + } + /* if (K_PlayerEBrake(player) == true) { @@ -10468,6 +10963,37 @@ void K_MoveKartPlayer(player_t *player, boolean onground) } } break; + case KITEM_GARDENTOP: + if (ATTACK_IS_DOWN && NO_HYUDORO) + { + if (player->curshield != KSHIELD_TOP) + { + player->topinfirst = 0; + Obj_GardenTopDeploy(player->mo); + } + else + { + if (player->throwdir == -1) + { + mobj_t *top = Obj_GardenTopDestroy(player); + + // Fly off the Top at high speed + P_Thrust(player->mo, K_MomentumAngle(player->mo), 80 * mapobjectscale); + P_SetObjectMomZ(player->mo, player->mo->info->height / 8, true); + + top->momx = player->mo->momx; + top->momy = player->mo->momy; + top->momz = player->mo->momz; + } + else + { + Obj_GardenTopThrow(player); + S_StartSound(player->mo, sfx_tossed); // play only when actually thrown :^,J + K_PlayAttackTaunt(player->mo); + } + } + } + break; case KITEM_BUBBLESHIELD: if (player->curshield != KSHIELD_BUBBLE) { diff --git a/src/k_kart.h b/src/k_kart.h index 1c75288e5..7973a236c 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -69,6 +69,7 @@ void K_SpawnDashDustRelease(player_t *player); void K_SpawnDriftBoostClip(player_t *player); void K_SpawnDriftBoostClipSpark(mobj_t *clip); void K_SpawnNormalSpeedLines(player_t *player); +void K_SpawnGardenTopSpeedLines(player_t *player); void K_SpawnInvincibilitySpeedLines(mobj_t *mo); void K_SpawnBumpEffect(mobj_t *mo); void K_KartMoveAnimation(player_t *player); @@ -142,7 +143,13 @@ boolean K_ApplyOffroad(player_t *player); boolean K_SlopeResistance(player_t *player); tripwirepass_t K_TripwirePassConditions(player_t *player); boolean K_TripwirePass(player_t *player); -boolean K_WaterRun(player_t *player); +boolean K_MovingHorizontally(mobj_t *mobj); +boolean K_WaterRun(mobj_t *mobj); +boolean K_WaterSkip(mobj_t *mobj); +void K_SpawnWaterRunParticles(mobj_t *mobj); +boolean K_IsRidingFloatingTop(player_t *player); +boolean K_IsHoldingDownTop(player_t *player); +mobj_t *K_GetGardenTop(player_t *player); void K_ApplyTripWire(player_t *player, tripwirestate_t state); INT16 K_GetSpindashChargeTime(player_t *player); fixed_t K_GetSpindashChargeSpeed(player_t *player); @@ -169,6 +176,7 @@ UINT8 K_GetOrbinautItemFrame(UINT8 count); boolean K_IsSPBInGame(void); void K_KartEbrakeVisuals(player_t *p); void K_HandleDirectionalInfluence(player_t *player); +fixed_t K_DefaultPlayerRadius(player_t *player); // sound stuff for lua void K_PlayAttackTaunt(mobj_t *source); diff --git a/src/k_menufunc.c b/src/k_menufunc.c index e143d5670..1f015a907 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -2016,7 +2016,7 @@ void M_QuitResponse(INT32 ch) if (ch == MA_YES) { - if (!(netgame || cv_debug)) + if (!(netgame || cht_debug)) { mrand = M_RandomKey(sizeof(quitsounds) / sizeof(INT32)); if (quitsounds[mrand]) @@ -3577,7 +3577,7 @@ void M_LevelSelectHandler(INT32 choice) strncpy(connectedservername, cv_servername.string, MAXSERVERNAME); // Still need to reset devmode - cv_debug = 0; + cht_debug = 0; if (demo.playback) G_StopDemo(); @@ -3686,7 +3686,7 @@ void M_StartTimeAttack(INT32 choice) } // Still need to reset devmode - cv_debug = 0; + cht_debug = 0; emeralds = 0; if (demo.playback) diff --git a/src/k_objects.h b/src/k_objects.h index be287c0f4..96e0fa2b5 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -9,6 +9,14 @@ void Obj_HyudoroThink(mobj_t *actor); void Obj_HyudoroCenterThink(mobj_t *actor); void Obj_HyudoroCollide(mobj_t *special, mobj_t *toucher); +/* Garden Top */ +void Obj_GardenTopDeploy(mobj_t *rider); +mobj_t *Obj_GardenTopThrow(player_t *player); +mobj_t *Obj_GardenTopDestroy(player_t *player); +void Obj_GardenTopThink(mobj_t *top); +void Obj_GardenTopSparkThink(mobj_t *spark); +boolean Obj_GardenTopPlayerIsGrinding(player_t *player); + /* Shrink */ void Obj_PohbeeThinker(mobj_t *pohbee); void Obj_PohbeeRemoved(mobj_t *pohbee); diff --git a/src/k_respawn.c b/src/k_respawn.c index 94bb04e18..227cf221f 100644 --- a/src/k_respawn.c +++ b/src/k_respawn.c @@ -157,7 +157,13 @@ void K_DoIngameRespawn(player_t *player) P_ResetPlayer(player); // Set up respawn position if invalid - if (player->respawn.wp != NULL && leveltime >= starttime) + if (player->respawn.manual == true) + { + player->respawn.distanceleft = 0; + player->respawn.pointz += K_RespawnOffset(player, player->respawn.flip); + player->respawn.manual = false; // one respawn only! + } + else if (player->respawn.wp != NULL && leveltime >= starttime) { const UINT32 dist = RESPAWN_DIST + (player->airtime * 48); player->respawn.distanceleft = (dist * mapobjectscale) / FRACUNIT; diff --git a/src/k_terrain.c b/src/k_terrain.c index 01b029b92..cd420e37a 100644 --- a/src/k_terrain.c +++ b/src/k_terrain.c @@ -1082,7 +1082,7 @@ void K_UpdateTerrainOverlay(mobj_t *mo) fixed_t speedDiv = FRACUNIT + FixedMul(FixedDiv(speed, maxSpeed), o->speed); tic_t animSpeed = max(FixedDiv(mo->state->tics, speedDiv), 1); - mo->tics = min(mo->tics, animSpeed); + mo->tics = min((tic_t)mo->tics, animSpeed); } } diff --git a/src/lua_baselib.c b/src/lua_baselib.c index bd05ae8b5..7a72b31dc 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -954,12 +954,15 @@ static int lib_pCheckDeathPitCollide(lua_State *L) static int lib_pCheckSolidLava(lua_State *L) { + mobj_t *mo = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ)); ffloor_t *rover = *((ffloor_t **)luaL_checkudata(L, 2, META_FFLOOR)); //HUDSAFE INLEVEL + if (!mo) + return LUA_ErrInvalid(L, "mobj_t"); if (!rover) return LUA_ErrInvalid(L, "ffloor_t"); - lua_pushboolean(L, P_CheckSolidLava(rover)); + lua_pushboolean(L, P_CheckSolidLava(mo, rover)); return 1; } diff --git a/src/lua_blockmaplib.c b/src/lua_blockmaplib.c index 1949d56bb..efb14a9c3 100644 --- a/src/lua_blockmaplib.c +++ b/src/lua_blockmaplib.c @@ -51,7 +51,7 @@ static UINT8 lib_searchBlockmap_Objects(lua_State *L, INT32 x, INT32 y, mobj_t * LUA_PushUserdata(L, thing, META_MOBJ); LUA_PushUserdata(L, mobj, META_MOBJ); if (lua_pcall(gL, 2, 1, 0)) { - if (!blockfuncerror || cv_debug & DBG_LUA) + if (!blockfuncerror || cht_debug & DBG_LUA) CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); lua_pop(gL, 1); blockfuncerror = true; @@ -112,7 +112,7 @@ static UINT8 lib_searchBlockmap_Lines(lua_State *L, INT32 x, INT32 y, mobj_t *th LUA_PushUserdata(L, thing, META_MOBJ); LUA_PushUserdata(L, po->lines[i], META_LINE); if (lua_pcall(gL, 2, 1, 0)) { - if (!blockfuncerror || cv_debug & DBG_LUA) + if (!blockfuncerror || cht_debug & DBG_LUA) CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); lua_pop(gL, 1); blockfuncerror = true; @@ -149,7 +149,7 @@ static UINT8 lib_searchBlockmap_Lines(lua_State *L, INT32 x, INT32 y, mobj_t *th LUA_PushUserdata(L, thing, META_MOBJ); LUA_PushUserdata(L, ld, META_LINE); if (lua_pcall(gL, 2, 1, 0)) { - if (!blockfuncerror || cv_debug & DBG_LUA) + if (!blockfuncerror || cht_debug & DBG_LUA) CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); lua_pop(gL, 1); blockfuncerror = true; @@ -195,7 +195,7 @@ static UINT8 lib_searchBlockmap_PolyObjs(lua_State *L, INT32 x, INT32 y, mobj_t LUA_PushUserdata(L, thing, META_MOBJ); LUA_PushUserdata(L, po, META_POLYOBJ); if (lua_pcall(gL, 2, 1, 0)) { - if (!blockfuncerror || cv_debug & DBG_LUA) + if (!blockfuncerror || cht_debug & DBG_LUA) CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1)); lua_pop(gL, 1); blockfuncerror = true; diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c index 8d47b7389..b797f669c 100644 --- a/src/lua_hooklib.c +++ b/src/lua_hooklib.c @@ -411,7 +411,7 @@ static int call_single_hook_no_copy(Hook_State *hook) else { /* print the error message once */ - if (cv_debug & DBG_LUA || !in_bit_array(hooksErrored, hook->id)) + if (cht_debug & DBG_LUA || !in_bit_array(hooksErrored, hook->id)) { CONS_Alert(CONS_WARNING, "%s\n", lua_tostring(gL, -1)); set_bit_array(hooksErrored, hook->id); diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c index 418a43bf9..ecd87f49f 100644 --- a/src/lua_mobjlib.c +++ b/src/lua_mobjlib.c @@ -100,6 +100,7 @@ enum mobj_e { mobj_spryoff, mobj_sprzoff, mobj_hitlag, + mobj_waterskip, mobj_dispoffset }; @@ -181,6 +182,7 @@ static const char *const mobj_opt[] = { "spryoff", "sprzoff", "hitlag", + "waterskip", "dispoffset", NULL}; @@ -460,6 +462,9 @@ static int mobj_get(lua_State *L) case mobj_hitlag: lua_pushinteger(L, mo->hitlag); break; + case mobj_waterskip: + lua_pushinteger(L, mo->waterskip); + break; case mobj_dispoffset: lua_pushinteger(L, mo->dispoffset); break; @@ -835,6 +840,9 @@ static int mobj_set(lua_State *L) case mobj_hitlag: mo->hitlag = luaL_checkinteger(L, 3); break; + case mobj_waterskip: + mo->waterskip = (UINT8)luaL_checkinteger(L, 3); + break; case mobj_dispoffset: mo->dispoffset = luaL_checkinteger(L, 3); break; diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index 85e73a12f..c5f5cc4d0 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -264,8 +264,6 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->aizdriftturn); else if (fastcmp(field,"offroad")) lua_pushinteger(L, plr->offroad); - else if (fastcmp(field,"waterskip")) - lua_pushinteger(L, plr->waterskip); else if (fastcmp(field,"tiregrease")) lua_pushinteger(L, plr->tiregrease); else if (fastcmp(field,"springstars")) @@ -634,8 +632,6 @@ static int player_set(lua_State *L) plr->aizdriftturn = luaL_checkinteger(L, 3); else if (fastcmp(field,"offroad")) plr->offroad = luaL_checkinteger(L, 3); - else if (fastcmp(field,"waterskip")) - plr->waterskip = luaL_checkinteger(L, 3); else if (fastcmp(field,"tiregrease")) plr->tiregrease = luaL_checkinteger(L, 3); else if (fastcmp(field,"springstars")) diff --git a/src/m_cheat.c b/src/m_cheat.c index 98043cd2d..aafdf5b87 100644 --- a/src/m_cheat.c +++ b/src/m_cheat.c @@ -38,6 +38,8 @@ #include "lua_script.h" #include "lua_hook.h" +#include "fastcmp.h" + // // CHEAT SEQUENCE PACKAGE // @@ -111,7 +113,7 @@ static UINT8 cheatf_devmode(void) for (i = 0; i < MAXUNLOCKABLES; i++) unlockables[i].unlocked = true; devparm = true; - cv_debug |= 0x8000; + cht_debug |= 0x8000; // Refresh secrets menu existing. M_ClearMenus(true); @@ -251,37 +253,18 @@ boolean cht_Responder(event_t *ev) // command that can be typed at the console! void Command_CheatNoClip_f(void) { - player_t *plyr; - REQUIRE_CHEATS; REQUIRE_INLEVEL; - REQUIRE_SINGLEPLAYER; // TODO: make netplay compatible - plyr = &players[consoleplayer]; - - if (!plyr->mo || P_MobjWasRemoved(plyr->mo)) - return; - - plyr->cheats ^= PC_NOCLIP; - CONS_Printf(M_GetText("No Clipping %s\n"), plyr->cheats & PC_NOCLIP ? M_GetText("On") : M_GetText("Off")); - - if (plyr->cheats & PC_NOCLIP) - plyr->mo->flags |= MF_NOCLIP; - else - plyr->mo->flags &= ~MF_NOCLIP; + D_Cheat(consoleplayer, CHEAT_NOCLIP); } void Command_CheatGod_f(void) { - player_t *plyr; - REQUIRE_CHEATS; REQUIRE_INLEVEL; - REQUIRE_SINGLEPLAYER; // TODO: make multiplayer compatible - plyr = &players[consoleplayer]; - plyr->cheats ^= PC_GODMODE; - CONS_Printf(M_GetText("Cheese Mode %s\n"), plyr->cheats & PC_GODMODE ? M_GetText("On") : M_GetText("Off")); + D_Cheat(consoleplayer, CHEAT_GOD); } void Command_Scale_f(void) @@ -291,7 +274,6 @@ void Command_Scale_f(void) REQUIRE_CHEATS; REQUIRE_INLEVEL; - REQUIRE_SINGLEPLAYER; // TODO: make multiplayer compatible if (scale < FRACUNIT/100 || scale > 100*FRACUNIT) //COM_Argv(1) will return a null string if they did not give a paramater, so... { @@ -299,29 +281,21 @@ void Command_Scale_f(void) return; } - if (!players[consoleplayer].mo) - return; - - players[consoleplayer].mo->destscale = scale; - - CONS_Printf(M_GetText("Scale set to %s\n"), COM_Argv(1)); + D_Cheat(consoleplayer, CHEAT_SCALE, scale); } void Command_Gravflip_f(void) { REQUIRE_CHEATS; REQUIRE_INLEVEL; - REQUIRE_SINGLEPLAYER; // TODO: make multiplayer compatible - if (players[consoleplayer].mo) - players[consoleplayer].mo->flags2 ^= MF2_OBJECTFLIP; + D_Cheat(consoleplayer, CHEAT_FLIP); } void Command_Hurtme_f(void) { REQUIRE_CHEATS; REQUIRE_INLEVEL; - REQUIRE_SINGLEPLAYER; // TODO: make multiplayer compatible if (COM_Argc() < 2) { @@ -329,69 +303,26 @@ void Command_Hurtme_f(void) return; } - P_DamageMobj(players[consoleplayer].mo, NULL, NULL, atoi(COM_Argv(1)), DMG_NORMAL); + D_Cheat(consoleplayer, CHEAT_HURT, atoi(COM_Argv(1))); } void Command_RTeleport_f(void) { - fixed_t intx, inty, intz; - size_t i; - player_t *p = &players[consoleplayer]; - subsector_t *ss; + float x = atof(COM_Argv(1)); + float y = atof(COM_Argv(2)); + float z = atof(COM_Argv(3)); REQUIRE_CHEATS; REQUIRE_INLEVEL; - REQUIRE_SINGLEPLAYER; // TODO: make multiplayer compatible - if (COM_Argc() < 3 || COM_Argc() > 7) + if (COM_Argc() != 4) { - CONS_Printf(M_GetText("rteleport -x -y -z : relative teleport to a location\n")); + CONS_Printf(M_GetText("rteleport : relative teleport to a location\n")); return; } - if (!p->mo) - return; - - i = COM_CheckParm("-x"); - if (i) - intx = atoi(COM_Argv(i + 1)); - else - intx = 0; - - i = COM_CheckParm("-y"); - if (i) - inty = atoi(COM_Argv(i + 1)); - else - inty = 0; - - ss = R_PointInSubsectorOrNull(p->mo->x + intx*FRACUNIT, p->mo->y + inty*FRACUNIT); - if (!ss || ss->sector->ceilingheight - ss->sector->floorheight < p->mo->height) - { - CONS_Alert(CONS_NOTICE, M_GetText("Not a valid location.\n")); - return; - } - i = COM_CheckParm("-z"); - if (i) - { - intz = atoi(COM_Argv(i + 1)); - intz <<= FRACBITS; - intz += p->mo->z; - if (intz < ss->sector->floorheight) - intz = ss->sector->floorheight; - if (intz > ss->sector->ceilingheight - p->mo->height) - intz = ss->sector->ceilingheight - p->mo->height; - } - else - intz = p->mo->z; - - CONS_Printf(M_GetText("Teleporting by %d, %d, %d...\n"), intx, inty, FixedInt((intz-p->mo->z))); - - P_MapStart(); - if (!P_SetOrigin(p->mo, p->mo->x+intx*FRACUNIT, p->mo->y+inty*FRACUNIT, intz)) - CONS_Alert(CONS_WARNING, M_GetText("Unable to teleport to that spot!\n")); - else - S_StartSound(p->mo, sfx_mixup); - P_MapEnd(); + D_Cheat(consoleplayer, CHEAT_RELATIVE_TELEPORT, + FLOAT_TO_FIXED(x), FLOAT_TO_FIXED(y), FLOAT_TO_FIXED(z)); } void Command_Teleport_f(void) @@ -619,7 +550,6 @@ void Command_Skynum_f(void) { REQUIRE_CHEATS; REQUIRE_INLEVEL; - REQUIRE_SINGLEPLAYER; // TODO: make multiplayer compatible if (COM_Argc() != 2) { @@ -651,18 +581,6 @@ void Command_Weather_f(void) P_SwitchWeather(atoi(COM_Argv(1))); } -void Command_Toggletwod_f(void) -{ - player_t *p = &players[consoleplayer]; - - REQUIRE_CHEATS; - REQUIRE_INLEVEL; - REQUIRE_SINGLEPLAYER; // TODO: make multiplayer compatible - - if (p->mo) - p->mo->flags2 ^= MF2_TWOD; -} - #ifdef _DEBUG // You never thought you needed this, did you? >=D // Yes, this has the specific purpose of completely screwing you up @@ -715,15 +633,15 @@ void Command_Dumplua_f(void) void Command_Savecheckpoint_f(void) { + mobj_t *thing = players[consoleplayer].mo; + REQUIRE_CHEATS; REQUIRE_INLEVEL; - REQUIRE_SINGLEPLAYER; // TODO: make multiplayer compatible - players[consoleplayer].respawn.pointx = players[consoleplayer].mo->x; - players[consoleplayer].respawn.pointy = players[consoleplayer].mo->y; - players[consoleplayer].respawn.pointz = players[consoleplayer].mo->floorz; - - CONS_Printf(M_GetText("Temporary checkpoint created at %d, %d, %d\n"), players[consoleplayer].respawn.pointx, players[consoleplayer].respawn.pointy, players[consoleplayer].respawn.pointz); + if (!P_MobjWasRemoved(thing)) + { + D_Cheat(consoleplayer, CHEAT_SAVECHECKPOINT, thing->x, thing->y, thing->z); + } } // Like M_GetAllEmeralds() but for console devmode junkies. @@ -749,24 +667,81 @@ void Command_Resetemeralds_f(void) } */ +// +// Devmode +// + +UINT32 cht_debug; + +struct debugFlagNames_s const debug_flag_names[] = +{ + {"None", DBG_NONE}, + {"Basic", DBG_BASIC}, + {"Detailed", DBG_DETAILED}, + {"Player", DBG_PLAYER}, + {"Render", DBG_RENDER}, + {"Renderer", DBG_RENDER}, // alt name + {"Polyobj", DBG_POLYOBJ}, + {"GameLogic", DBG_GAMELOGIC}, + {"Game", DBG_GAMELOGIC}, // alt name + {"Netplay", DBG_NETPLAY}, + {"Memory", DBG_MEMORY}, + {"Setup", DBG_SETUP}, + {"Lua", DBG_LUA}, + {"RNG", DBG_RNG}, + {"Randomizer", DBG_RNG}, // alt name + {NULL, 0} +}; + void Command_Devmode_f(void) { + size_t argc = 0; + REQUIRE_CHEATS; - REQUIRE_SINGLEPLAYER; // TODO: make multiplayer compatible - if (COM_Argc() > 1) + argc = COM_Argc(); + if (argc > 1) { - const char *arg = COM_Argv(1); + UINT32 flags = 0; + size_t i; - if (arg[0] && arg[0] == '0' && - arg[1] && arg[1] == 'x') // Use hexadecimal! - cv_debug = axtoi(arg+2); - else - cv_debug = atoi(arg); + for (i = 1; i < argc; i++) + { + const char *arg = COM_Argv(i); + size_t j; + + // Try it as a string + for (j = 0; debug_flag_names[j].str; j++) + { + if (stricmp(arg, debug_flag_names[j].str) == 0) + { + break; + } + } + + if (debug_flag_names[j].str) + { + flags |= debug_flag_names[j].flag; + continue; + } + + // Try it as a number + if (arg[0] && arg[0] == '0' && + arg[1] && arg[1] == 'x') // Use hexadecimal! + { + flags |= axtoi(arg+2); + } + else + { + flags |= atoi(arg); + } + } + + D_Cheat(consoleplayer, CHEAT_DEVMODE, flags); } else { - CONS_Printf(M_GetText("devmode : enable debugging tools and info, prepend with 0x to use hexadecimal\n")); + CONS_Printf(M_GetText("devmode : Enable debugging info. Prepend with 0x to use hexadecimal\n")); return; } } @@ -776,13 +751,7 @@ void Command_Setrings_f(void) REQUIRE_CHEATS; REQUIRE_INLEVEL; - if (COM_Argc() > 1) - { - // P_GivePlayerRings does value clamping - players[consoleplayer].rings = 0; - P_GivePlayerRings(&players[consoleplayer], atoi(COM_Argv(1))); - players[consoleplayer].totalring -= atoi(COM_Argv(1)); //undo totalring addition done in P_GivePlayerRings - } + D_Cheat(consoleplayer, CHEAT_RINGS, atoi(COM_Argv(1))); } void Command_Setlives_f(void) @@ -790,20 +759,7 @@ void Command_Setlives_f(void) REQUIRE_CHEATS; REQUIRE_INLEVEL; - if (COM_Argc() > 1) - { - SINT8 lives = atoi(COM_Argv(1)); - if (lives == -1) - { - players[consoleplayer].lives = INFLIVES; // infinity! - } - else - { - // P_GivePlayerLives does value clamping - players[consoleplayer].lives = 0; - P_GivePlayerLives(&players[consoleplayer], atoi(COM_Argv(1))); - } - } + D_Cheat(consoleplayer, CHEAT_LIVES, atoi(COM_Argv(1))); } // diff --git a/src/m_cheat.h b/src/m_cheat.h index ec1a129cf..b5e018afc 100644 --- a/src/m_cheat.h +++ b/src/m_cheat.h @@ -19,6 +19,24 @@ #include "p_mobj.h" #include "command.h" +typedef enum { + CHEAT_NOCLIP, + CHEAT_GOD, + CHEAT_SAVECHECKPOINT, + CHEAT_RINGS, + CHEAT_LIVES, + CHEAT_SCALE, + CHEAT_FLIP, + CHEAT_HURT, + CHEAT_RELATIVE_TELEPORT, + CHEAT_DEVMODE, + + NUMBER_OF_CHEATS +} cheat_t; + +// +// Cheat sequences +// boolean cht_Responder(event_t *ev); void cht_Init(void); @@ -56,7 +74,6 @@ void Command_Teleport_f(void); void Command_RTeleport_f(void); void Command_Skynum_f(void); void Command_Weather_f(void); -void Command_Toggletwod_f(void); #ifdef _DEBUG void Command_CauseCfail_f(void); #endif diff --git a/src/objects/Sourcefile b/src/objects/Sourcefile index d768232c5..b8cb63b1f 100644 --- a/src/objects/Sourcefile +++ b/src/objects/Sourcefile @@ -1,4 +1,5 @@ hyudoro.c +gardentop.c shrink.c item-debris.c spb.c diff --git a/src/objects/gardentop.c b/src/objects/gardentop.c new file mode 100644 index 000000000..3d55e03d1 --- /dev/null +++ b/src/objects/gardentop.c @@ -0,0 +1,614 @@ +#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_local.h" +#include "../s_sound.h" + +// TODO: separate from this file +static fixed_t K_FlipZOffset(mobj_t *us, mobj_t *them) +{ + fixed_t z = 0; + + if (them->eflags & MFE_VERTICALFLIP) + z += them->height; + + if (us->eflags & MFE_VERTICALFLIP) + z -= us->height; + + return z; +} + +#define SPARKCOLOR SKINCOLOR_ROBIN + +enum { + TOP_ANCHORED, + TOP_LOOSE, +}; + +#define topsfx_floating sfx_s3k7d +#define topsfx_grinding sfx_s3k79 +#define topsfx_lift sfx_s3ka0 + +#define rider_top(o) ((o)->hnext) + +#define top_mode(o) ((o)->extravalue1) +#define top_float(o) ((o)->lastlook) +#define top_sound(o) ((o)->extravalue2) +#define top_soundtic(o) ((o)->movecount) + +/* TOP_ANCHORED */ +#define top_rider(o) ((o)->tracer) + +/* TOP_LOOSE */ +#define top_waveangle(o) ((o)->movedir) +/* wavepause will take mobjinfo reactiontime automatically */ +#define top_wavepause(o) ((o)->reactiontime) + +#define spark_top(o) ((o)->target) +#define spark_angle(o) ((o)->movedir) + +static inline player_t * +get_rider_player (mobj_t *rider) +{ + return rider ? rider->player : NULL; +} + +static inline player_t * +get_top_rider_player (mobj_t *top) +{ + return get_rider_player(top_rider(top)); +} + +static inline boolean +is_top_grind_input (mobj_t *top) +{ + player_t *player = get_top_rider_player(top); + + return player && K_IsHoldingDownTop(player); +} + +static inline boolean +is_top_grinding (mobj_t *top) +{ + if (top_float(top) > 0) + return false; + + if (!P_IsObjectOnGround(top)) + return false; + + return true; +} + +static inline fixed_t +grind_spark_base_scale (player_t *player) +{ + return FRACUNIT/2 + + player->topdriftheld * FRACUNIT + / GARDENTOP_MAXGRINDTIME; +} + +static inline INT32 +get_player_steer_tilt +( player_t * player, + INT32 stages) +{ + return player->steering + * stages + + // 1 degree for a full turn + / KART_FULLTURN + * ANG1 + + // stages is for fractions of a full turn, divide to + // get a fraction of a degree + / stages + + // angle is inverted in reverse gravity + * P_MobjFlip(player->mo); +} + +static inline fixed_t +goofy_shake (fixed_t n) +{ + return P_RandomRange(PR_DECORATION, -1, 1) * n; +} + +static inline void +init_top +( mobj_t * top, + INT32 mode) +{ + top_mode(top) = mode; + top_float(top) = 0; + top_sound(top) = sfx_None; + top_waveangle(top) = 0; +} + +static void +spawn_spark +( mobj_t * top, + angle_t angle) +{ + mobj_t *spark = P_SpawnMobjFromMobj( + top, 0, 0, 0, MT_GARDENTOPSPARK); + + P_SetTarget(&spark_top(spark), top); + + spark_angle(spark) = angle; + + spark->color = SPARKCOLOR; + spark->spriteyscale = 3*FRACUNIT/4; +} + +static void +spawn_spark_circle +( mobj_t * top, + UINT8 n) +{ + const angle_t a = ANGLE_MAX / n; + + UINT8 i; + + for (i = 0; i < n; ++i) + { + spawn_spark(top, i * a); + } +} + +static void +spawn_grind_spark (mobj_t *top) +{ + mobj_t *rider = top_rider(top); + mobj_t *spark; + + player_t *player = NULL; + + fixed_t x = 0; + fixed_t y = 0; + + angle_t angle = top->angle; + + if (rider) + { + const fixed_t speed = -20 * top->scale; + + angle = K_MomentumAngle(rider); + + x = P_ReturnThrustX(rider, angle, speed); + y = P_ReturnThrustY(rider, angle, speed); + + player = get_rider_player(rider); + } + + spark = P_SpawnMobjFromMobj( + top, x, y, 0, MT_DRIFTSPARK); + + spark->momx = x; + spark->momy = y; + + P_SetMobjState(spark, S_DRIFTSPARK_A1); + + spark->angle = angle; + spark->color = SPARKCOLOR; + + if (player) + { + spark->destscale = FixedMul(spark->destscale, + grind_spark_base_scale(player)); + + P_SetScale(spark, spark->destscale); + } +} + +static void +loop_sfx +( mobj_t * top, + sfxenum_t sfx) +{ + switch (sfx) + { + case topsfx_floating: + if (S_SoundPlaying(top, sfx)) + { + return; + } + break; + + case topsfx_grinding: + if ((sfxenum_t)top_sound(top) != sfx) + { + top_soundtic(top) = leveltime; + } + + /* FIXME: could this sound just be looped + normally? :face_holding_back_tears: */ + if ((leveltime - top_soundtic(top)) % 28 > 0) + { + return; + } + break; + + default: + break; + } + + S_StartSound(top, sfx); +} + +static void +modulate (mobj_t *top) +{ + const fixed_t max_hover = top->height / 4; + const fixed_t hover_step = max_hover / 4; + + sfxenum_t ambience = sfx_None; + + if (is_top_grind_input(top)) + { + if (top_float(top) == max_hover) + { + P_SetMobjState(top, S_GARDENTOP_SINKING1); + } + + if (top_float(top) > 0) + { + top_float(top) = max(0, + top_float(top) - hover_step); + } + else if (P_IsObjectOnGround(top)) + { + spawn_grind_spark(top); + ambience = topsfx_grinding; + } + } + else + { + if (top_float(top) == 0) + { + P_SetMobjState(top, S_GARDENTOP_FLOATING); + + S_StopSoundByID(top, topsfx_grinding); + S_StartSound(top, topsfx_lift); + } + + if (top_float(top) < max_hover) + { + top_float(top) = min(max_hover, + top_float(top) + hover_step); + } + else + { + ambience = topsfx_floating; + } + } + + top->sprzoff = top_float(top) * P_MobjFlip(top); + + if (ambience) + { + loop_sfx(top, ambience); + } + + top_sound(top) = ambience; +} + +static void +tilt (mobj_t *top) +{ + player_t *player = get_top_rider_player(top); + + INT32 tilt = top->rollangle; + + if (is_top_grind_input(top)) + { + const angle_t tiltmax = ANGLE_22h; + + tilt += get_player_steer_tilt(player, 4); + + if (abs(tilt) > tiltmax) + { + tilt = intsign(tilt) * tiltmax; + } + } + else + { + const angle_t decay = ANG1 * 2; + + if (abs(tilt) > decay) + { + tilt -= intsign(tilt) * decay; + } + else + { + tilt = 0; + } + } + + top->rollangle = tilt; + + /* Vibrate left and right if you're about to lose it. */ + if (player && player->topinfirst) + { + top->spritexoffset = P_LerpFlip(32*FRACUNIT, 1); + } + else + { + top->spritexoffset = 0; + } + + /* Go ABSOLUTELY NUTS if the player is tumbling... */ + if (player && player->tumbleBounces > 0) + { + const fixed_t yofs = 48 * FRACUNIT; + const fixed_t ofs3d = 24 * top->scale; + + /* spriteyoffset scales, e.g. with K_Squish */ + top->spriteyoffset = FixedDiv( + goofy_shake(yofs), top->spriteyscale); + + top->sprxoff = goofy_shake(ofs3d); + top->spryoff = goofy_shake(ofs3d); + } + else + { + top->spriteyoffset = 0; + top->sprxoff = 0; + top->spryoff = 0; + } +} + +static void +anchor_top (mobj_t *top) +{ + mobj_t *rider = top_rider(top); + player_t *player = get_rider_player(rider); + + if (player && player->curshield != KSHIELD_TOP) + { + P_RemoveMobj(top); + return; + } + + tilt(top); + + P_MoveOrigin(top, rider->x, rider->y, + rider->z + K_FlipZOffset(top, rider)); + + K_GenericExtraFlagsNoZAdjust(top, rider); + + /* Copying the Z momentum lets the Top squash and stretch + as it falls with the player. Don't copy the X/Y + momentum because then it would always get slightly + ahead of the player. */ + top->momx = 0; + top->momy = 0; + top->momz = rider->momz; + + /* The Z momentum can put the Top slightly ahead of the + player in that axis too. It looks cool if the Top + falls below you but not if it bounces up. */ + if (top->momz * P_MobjFlip(top) > 0) + { + top->momz = 0; + } + + /* match rider's slope tilt */ + top->pitch = rider->pitch; + top->roll = rider->roll; +} + +static void +loose_think (mobj_t *top) +{ + const fixed_t thrustamount = top->movefactor; + const angle_t momangle = K_MomentumAngle(top); + + angle_t ang = top->angle; + + mobj_t *ghost = P_SpawnGhostMobj(top); + ghost->colorized = true; // already has color! + + if (AngleDelta(ang, momangle) > ANGLE_90) + { + top->angle = momangle; + } + + if (top_wavepause(top)) + { + top_wavepause(top)--; + } + else + { + /* oscillate between +90 and -90 degrees */ + ang += AbsAngle(top_waveangle(top)) - ANGLE_90; + } + + P_InstaThrust(top, top->angle, thrustamount); + P_Thrust(top, ang, thrustamount); + + //top_waveangle(top) = (angle_t)top_waveangle(top) + ANG10; + top_waveangle(top) += ANG10; + + /* intangibility grace period */ + if (top->threshold > 0) + { + top->threshold--; + } +} + +static void +anchor_spark (mobj_t *spark) +{ + mobj_t *top = spark_top(spark); + mobj_t *rider = top_rider(top); + player_t *player = get_rider_player(rider); + + const angle_t angle = top->angle + spark_angle(spark); + const fixed_t x = P_ReturnThrustX(top, angle, spark->scale); + const fixed_t y = P_ReturnThrustY(top, angle, spark->scale); + + /* FIXME: THIS FUNCTION FUCKING SUCKS */ + K_FlipFromObject(spark, top); + + P_MoveOrigin(spark, top->x + x, top->y + y, + top->z + K_FlipZOffset(spark, top)); + + spark->angle = angle; + + if (player) + { + const fixed_t topspeed = + K_GetKartSpeed(player, false, false); + + const fixed_t speed = FixedHypot( + rider->momx, rider->momy); + + P_SetScale(spark, FixedMul(top->scale, FRACUNIT/2 + + FixedDiv(speed / 2, topspeed))); + } +} + +void +Obj_GardenTopDeploy (mobj_t *rider) +{ + player_t *player = rider->player; + + mobj_t *top = P_SpawnMobjFromMobj( + rider, 0, 0, 0, MT_GARDENTOP); + + init_top(top, TOP_ANCHORED); + + top->flags |= MF_NOCLIPHEIGHT; + + /* only the player's shadow needs to be rendered */ + top->shadowscale = 0; + + P_SetTarget(&top_rider(top), rider); + P_SetTarget(&rider_top(rider), top); + + if (player) + { + player->curshield = KSHIELD_TOP; + rider->radius = K_DefaultPlayerRadius(player); + } + + spawn_spark_circle(top, 6); +} + +mobj_t * +Obj_GardenTopThrow (player_t *player) +{ + mobj_t *top = K_GetGardenTop(player); + + if (top) + { + const fixed_t oldfloat = top_float(top); + const fixed_t height = top->height; + + K_UpdateHnextList(player, true); + + /* Sucks that another one needs to be spawned but + this way, the throwing function can be used. */ + top = K_ThrowKartItem( + player, true, MT_GARDENTOP, 1, 0, 0); + + init_top(top, TOP_LOOSE); + + top_float(top) = oldfloat; + top_waveangle(top) = 0; + + /* prevents it from hitting us on its way out */ + top->threshold = 20; + + /* ensure it's tangible */ + top->flags &= ~(MF_NOCLIPTHING); + + /* Put player PHYSICALLY on top. While riding the + Top, player collision was used and the player + technically remained on the ground. Now they + should fall off. */ + P_SetOrigin(player->mo, player->mo->x, player->mo->y, + player->mo->z + height * P_MobjFlip(player->mo)); + + if (player->itemamount > 0) + player->itemamount--; + + if (player->itemamount <= 0) + player->itemtype = KITEM_NONE; + + player->curshield = KSHIELD_NONE; + + player->mo->radius = K_DefaultPlayerRadius(player); + } + + return top; +} + +mobj_t * +Obj_GardenTopDestroy (player_t *player) +{ + mobj_t *top = Obj_GardenTopThrow(player); + + if (top) + { + /* kill kill kill die die die */ + P_KillMobj(top, NULL, NULL, DMG_NORMAL); + } + + return top; +} + +void +Obj_GardenTopThink (mobj_t *top) +{ + modulate(top); + + switch (top_mode(top)) + { + case TOP_ANCHORED: + if (top_rider(top)) + { + anchor_top(top); + } + break; + + case TOP_LOOSE: + loose_think(top); + break; + } +} + +void +Obj_GardenTopSparkThink (mobj_t *spark) +{ + mobj_t *top = spark_top(spark); + + if (!top) + { + P_RemoveMobj(spark); + return; + } + + anchor_spark(spark); + + if (is_top_grinding(top)) + { + spark->renderflags ^= RF_DONTDRAW; + } + else + { + spark->renderflags |= RF_DONTDRAW; + } +} + +boolean +Obj_GardenTopPlayerIsGrinding (player_t *player) +{ + mobj_t *top = K_GetGardenTop(player); + + return top ? is_top_grinding(top) : false; +} diff --git a/src/objects/orbinaut.c b/src/objects/orbinaut.c index 0c04c4b67..ac209ec16 100644 --- a/src/objects/orbinaut.c +++ b/src/objects/orbinaut.c @@ -143,6 +143,7 @@ void Obj_OrbinautThink(mobj_t *th) boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2) { boolean damageitem = false; + boolean tumbleitem = false; boolean sprung = false; if ((orbinaut_selfdelay(t1) > 0 && t2->hitlag > 0) @@ -173,6 +174,11 @@ boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2) return true; } + if (t1->type == MT_GARDENTOP) + { + tumbleitem = true; + } + if (t2->player) { if ((t2->player->flashing > 0 && t2->hitlag == 0) @@ -190,7 +196,8 @@ boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2) else { // Player Damage - P_DamageMobj(t2, t1, t1->target, 1, DMG_WIPEOUT|DMG_WOMBO); + P_DamageMobj(t2, t1, t1->target, 1, DMG_WOMBO | + (tumbleitem ? DMG_TUMBLE : DMG_WIPEOUT)); K_KartBouncing(t2, t1); S_StartSound(t2, sfx_s3k7b); } @@ -233,6 +240,11 @@ boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2) damageitem = true; } + if (t1->type == MT_GARDENTOP) + { + damageitem = false; + } + if (damageitem) { // This Item Damage diff --git a/src/p_enemy.c b/src/p_enemy.c index 60632cca7..55ea6de81 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -8216,7 +8216,7 @@ void A_OrbitNights(mobj_t* actor) if (!actor->target) { - if (cv_debug && !(actor->target && actor->target->player)) + if (cht_debug && !(actor->target && actor->target->player)) CONS_Printf("ERROR: Powerup has no target!\n"); return; } @@ -8286,7 +8286,7 @@ void A_SetObjectState(mobj_t *actor) if ((!locvar2 && !actor->target) || (locvar2 && !actor->tracer)) { - if (cv_debug) + if (cht_debug) CONS_Printf("A_SetObjectState: No target to change state!\n"); return; } @@ -8377,7 +8377,7 @@ void A_KnockBack(mobj_t *actor) if (!target) { - if(cv_debug) + if(cht_debug) CONS_Printf("A_KnockBack: No target!\n"); return; } @@ -8441,7 +8441,7 @@ void A_RingDrain(mobj_t *actor) if (!actor->target || !actor->target->player) { - if(cv_debug) + if(cht_debug) CONS_Printf("A_RingDrain: No player targeted!\n"); return; } @@ -8645,7 +8645,7 @@ void A_Custom3DRotate(mobj_t *actor) if (hspeed==0 && vspeed==0) { - if (cv_debug) + if (cht_debug) CONS_Printf("Error: A_Custom3DRotate: Object has no speed.\n"); return; } @@ -9080,7 +9080,7 @@ void A_SetCustomValue(mobj_t *actor) if (LUA_CallAction(A_SETCUSTOMVALUE, actor)) return; - if (cv_debug) + if (cht_debug) CONS_Printf("Init custom value is %d\n", actor->cusval); if (locvar1 == 0 && locvar2 == 4) @@ -9100,7 +9100,7 @@ void A_SetCustomValue(mobj_t *actor) else // replace actor->cusval = locvar1; - if(cv_debug) + if(cht_debug) CONS_Printf("New custom value is %d\n", actor->cusval); } @@ -9467,7 +9467,7 @@ void A_SetScale(mobj_t *actor) if (locvar1 <= 0) { - if(cv_debug) + if(cht_debug) CONS_Printf("A_SetScale: Valid scale not specified!\n"); return; } @@ -9481,7 +9481,7 @@ void A_SetScale(mobj_t *actor) if (!target) { - if(cv_debug) + if(cht_debug) CONS_Printf("A_SetScale: No target!\n"); return; } @@ -9522,7 +9522,7 @@ void A_RemoteDamage(mobj_t *actor) if (!target) { - if(cv_debug) + if(cht_debug) CONS_Printf("A_RemoteDamage: No target!\n"); return; } @@ -13149,7 +13149,7 @@ void A_ItemPop(mobj_t *actor) if (!(actor->target && actor->target->player)) { - if (cv_debug && !(actor->target && actor->target->player)) + if (cht_debug && !(actor->target && actor->target->player)) CONS_Printf("ERROR: Powerup has no target!\n"); return; } diff --git a/src/p_inter.c b/src/p_inter.c index d9950aa89..1a6c3ad30 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1879,7 +1879,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da if (player) // Player is the target { - if (player->cheats & PC_GODMODE) + if (player->pflags & PF_GODMODE) return false; if (!force) @@ -2019,6 +2019,12 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da player->emeralds = 0; K_CheckEmeralds(source->player); } + + /* Drop "shield" immediately on contact. */ + if (source->player->curshield == KSHIELD_TOP) + { + Obj_GardenTopDestroy(source->player); + } } else { diff --git a/src/p_local.h b/src/p_local.h index 5fc73f1a2..c6671b9ae 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -195,6 +195,8 @@ void P_PlayerAfterThink(player_t *player); void P_DoPlayerExit(player_t *player); void P_DoTimeOver(player_t *player); +void P_ResetPlayerCheats(void); + void P_InstaThrust(mobj_t *mo, angle_t angle, fixed_t move); fixed_t P_ReturnThrustX(mobj_t *mo, angle_t angle, fixed_t move); fixed_t P_ReturnThrustY(mobj_t *mo, angle_t angle, fixed_t move); @@ -315,7 +317,7 @@ fixed_t P_CameraCeilingZ(camera_t *mobj, sector_t *sector, sector_t *boundsec, f boolean P_InsideANonSolidFFloor(mobj_t *mobj, ffloor_t *rover); boolean P_CheckDeathPitCollide(mobj_t *mo); -boolean P_CheckSolidLava(ffloor_t *rover); +boolean P_CheckSolidLava(mobj_t *mobj, ffloor_t *rover); void P_AdjustMobjFloorZ_FFloors(mobj_t *mo, sector_t *sector, UINT8 motype); mobj_t *P_SpawnMobjFromMobj(mobj_t *mobj, fixed_t xofs, fixed_t yofs, fixed_t zofs, mobjtype_t type); @@ -337,8 +339,8 @@ boolean P_CameraThinker(player_t *player, camera_t *thiscam, boolean resetcalled void P_Attract(mobj_t *source, mobj_t *enemy, boolean nightsgrab); mobj_t *P_GetClosestAxis(mobj_t *source); -boolean P_CanRunOnWater(player_t *player, ffloor_t *rover); -boolean P_CheckSolidFFloorSurface(player_t *player, ffloor_t *rover); +boolean P_CanRunOnWater(mobj_t *mobj, ffloor_t *rover); +boolean P_CheckSolidFFloorSurface(mobj_t *mobj, ffloor_t *rover); void P_MaceRotate(mobj_t *center, INT32 baserot, INT32 baseprevrot); diff --git a/src/p_map.c b/src/p_map.c index 62baa23e6..8c804d7dd 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -866,6 +866,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) && (tmthing->type == MT_ORBINAUT || tmthing->type == MT_JAWZ || tmthing->type == MT_BANANA || tmthing->type == MT_EGGMANITEM || tmthing->type == MT_BALLHOG || tmthing->type == MT_SSMINE || tmthing->type == MT_LANDMINE || tmthing->type == MT_SINK + || tmthing->type == MT_GARDENTOP || (tmthing->type == MT_PLAYER && thing->target != tmthing))) { // see if it went over / under @@ -881,6 +882,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) && (thing->type == MT_ORBINAUT || thing->type == MT_JAWZ || thing->type == MT_BANANA || thing->type == MT_EGGMANITEM || thing->type == MT_BALLHOG || thing->type == MT_SSMINE || tmthing->type == MT_LANDMINE || thing->type == MT_SINK + || thing->type == MT_GARDENTOP || (thing->type == MT_PLAYER && tmthing->target != thing))) { // see if it went over / under @@ -901,6 +903,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) && (tmthing->type == MT_ORBINAUT || tmthing->type == MT_JAWZ || tmthing->type == MT_BANANA || tmthing->type == MT_EGGMANITEM || tmthing->type == MT_BALLHOG || tmthing->type == MT_SSMINE || tmthing->type == MT_LANDMINE || tmthing->type == MT_SINK + || tmthing->type == MT_GARDENTOP || (tmthing->type == MT_PLAYER))) { // see if it went over / under @@ -915,6 +918,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) && (thing->type == MT_ORBINAUT || thing->type == MT_JAWZ || thing->type == MT_BANANA || thing->type == MT_EGGMANITEM || thing->type == MT_BALLHOG || thing->type == MT_SSMINE || tmthing->type == MT_LANDMINE || thing->type == MT_SINK + || thing->type == MT_GARDENTOP || (thing->type == MT_PLAYER))) { // see if it went over / under @@ -932,7 +936,8 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) return BMIT_CONTINUE; if (tmthing->type == MT_ORBINAUT || tmthing->type == MT_JAWZ - || tmthing->type == MT_ORBINAUT_SHIELD || tmthing->type == MT_JAWZ_SHIELD) + || tmthing->type == MT_ORBINAUT_SHIELD || tmthing->type == MT_JAWZ_SHIELD + || tmthing->type == MT_GARDENTOP) { // see if it went over / under if (tmthing->z > thing->z + thing->height) @@ -943,7 +948,8 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) return Obj_OrbinautJawzCollide(tmthing, thing) ? BMIT_CONTINUE : BMIT_ABORT; } else if (thing->type == MT_ORBINAUT || thing->type == MT_JAWZ - || thing->type == MT_ORBINAUT_SHIELD || thing->type == MT_JAWZ_SHIELD) + || thing->type == MT_ORBINAUT_SHIELD || thing->type == MT_JAWZ_SHIELD + || thing->type == MT_GARDENTOP) { // see if it went over / under if (tmthing->z > thing->z + thing->height) @@ -1940,7 +1946,7 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y) continue; } - if (thing->player && P_CheckSolidFFloorSurface(thing->player, rover)) + if (P_CheckSolidFFloorSurface(thing, rover)) ; else if (thing->type == MT_SKIM && (rover->flags & FF_SWIMMABLE)) ; @@ -2354,11 +2360,7 @@ boolean P_TryCameraMove(fixed_t x, fixed_t y, camera_t *thiscam) fixed_t tryx = thiscam->x; fixed_t tryy = thiscam->y; -#ifndef NOCLIPCAM - if ((players[displayplayers[i]].cheats & PC_NOCLIP) || (leveltime < introtime)) // Noclipping player camera noclips too!! -#else if (!(players[displayplayers[i]].pflags & PF_NOCONTEST)) // Time Over should not clip through walls -#endif { floatok = true; thiscam->floorz = thiscam->z; @@ -2524,9 +2526,7 @@ static boolean P_WaterRunning(mobj_t *thing) static boolean P_WaterStepUp(mobj_t *thing) { - player_t *player = thing->player; - return (player && player->waterskip) || - P_WaterRunning(thing); + return (thing->waterskip > 0 || P_WaterRunning(thing)); } fixed_t P_BaseStepUp(void) @@ -2844,6 +2844,11 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff) thing->terrain = NULL; } + if (thing->player && K_IsRidingFloatingTop(thing->player)) + { + stairjank = false; + } + /* FIXME: slope step down (even up) has some false positives, so just ignore them entirely. */ if (stairjank && !oldslope && !thing->standingslope && diff --git a/src/p_maputl.c b/src/p_maputl.c index 09e5cdd5d..74fe2331d 100644 --- a/src/p_maputl.c +++ b/src/p_maputl.c @@ -777,7 +777,7 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj) if (!(rover->flags & FF_EXISTS)) continue; - if (mobj->player && P_CheckSolidFFloorSurface(mobj->player, rover)) + if (P_CheckSolidFFloorSurface(mobj, rover)) ; else if (!((rover->flags & FF_BLOCKPLAYER && mobj->player) || (rover->flags & FF_BLOCKOTHERS && !mobj->player))) @@ -821,7 +821,7 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj) if (!(rover->flags & FF_EXISTS)) continue; - if (mobj->player && P_CheckSolidFFloorSurface(mobj->player, rover)) + if (P_CheckSolidFFloorSurface(mobj, rover)) ; else if (!((rover->flags & FF_BLOCKPLAYER && mobj->player) || (rover->flags & FF_BLOCKOTHERS && !mobj->player))) diff --git a/src/p_mobj.c b/src/p_mobj.c index c7dcac8ff..c3e8795b6 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -1105,6 +1105,11 @@ fixed_t P_GetMobjGravity(mobj_t *mo) } } + if (mo->waterskip > 0) + { + gravityadd = (4*gravityadd)/3; + } + if (mo->player) { if (mo->flags2 & MF2_OBJECTFLIP) @@ -1118,11 +1123,6 @@ fixed_t P_GetMobjGravity(mobj_t *mo) P_PlayerFlip(mo); } - if (mo->player->waterskip) - { - gravityadd = (4*gravityadd)/3; - } - if (mo->player->trickpanel >= 2) { gravityadd = (5*gravityadd)/2; @@ -1133,7 +1133,11 @@ fixed_t P_GetMobjGravity(mobj_t *mo) gravityadd = FixedMul(TUMBLEGRAVITY, gravityadd); } - if (mo->player->fastfall != 0) + if (K_IsHoldingDownTop(mo->player)) + { + gravityadd = (5*gravityadd)/2; + } + else if (mo->player->fastfall != 0) { // Fast falling gravityadd *= 4; @@ -1726,8 +1730,7 @@ void P_XYMovement(mobj_t *mo) //{ SRB2kart - Orbinaut, Ballhog // Bump sparks - if (mo->type == MT_ORBINAUT || mo->type == MT_BALLHOG - || mo->type == MT_DUELBOMB) + if (mo->type == MT_ORBINAUT || mo->type == MT_BALLHOG) { mobj_t *fx; fx = P_SpawnMobj(mo->x, mo->y, mo->z, MT_BUMP); @@ -1741,6 +1744,7 @@ void P_XYMovement(mobj_t *mo) switch (mo->type) { case MT_ORBINAUT: // Orbinaut speed decreasing + case MT_GARDENTOP: if (mo->health > 1) { S_StartSound(mo, mo->info->attacksound); @@ -1945,7 +1949,7 @@ void P_AdjustMobjFloorZ_FFloors(mobj_t *mo, sector_t *sector, UINT8 motype) topheight = P_GetFOFTopZ(mo, sector, rover, mo->x, mo->y, NULL); bottomheight = P_GetFOFBottomZ(mo, sector, rover, mo->x, mo->y, NULL); - if (mo->player && P_CheckSolidFFloorSurface(mo->player, rover)) // only the player should stand on lava or run on water + if (P_CheckSolidFFloorSurface(mo, rover)) // only the player should stand on lava or run on water ; else if (motype != 0 && rover->flags & FF_SWIMMABLE) // "scenery" only continue; @@ -2089,7 +2093,7 @@ boolean P_CheckDeathPitCollide(mobj_t *mo) I_Assert(mo != NULL); I_Assert(!P_MobjWasRemoved(mo)); - if (mo->player && mo->player->cheats & PC_GODMODE) + if (mo->player && mo->player->pflags & PF_GODMODE) return false; if (((mo->z <= mo->subsector->sector->floorheight @@ -2104,11 +2108,18 @@ boolean P_CheckDeathPitCollide(mobj_t *mo) return false; } -boolean P_CheckSolidLava(ffloor_t *rover) +boolean P_CheckSolidLava(mobj_t *mobj, ffloor_t *rover) { + if (mobj->player == NULL) + { + return false; + } + if (rover->flags & FF_SWIMMABLE && GETSECSPECIAL(rover->master->frontsector->special, 1) == 3 && !(rover->master->flags & ML_BLOCKPLAYERS)) - return true; + { + return true; + } return false; } @@ -2186,18 +2197,6 @@ boolean P_ZMovement(mobj_t *mo) case MT_BIGTUMBLEWEED: case MT_LITTLETUMBLEWEED: case MT_SHELL: - // SRB2kart stuff that should die in pits - // Shouldn't stop moving along the Z if there's no speed though! - case MT_EGGMANITEM: - case MT_BANANA: - case MT_ORBINAUT: - case MT_JAWZ: - case MT_BALLHOG: - case MT_SSMINE: - case MT_LANDMINE: - case MT_DROPTARGET: - case MT_BUBBLESHIELDTRAP: - case MT_DUELBOMB: // Remove stuff from death pits. if (P_CheckDeathPitCollide(mo)) { @@ -2279,6 +2278,17 @@ boolean P_ZMovement(mobj_t *mo) } break; default: + // SRB2kart stuff that should die in pits + // Shouldn't stop moving along the Z if there's no speed though! + if (P_CanDeleteKartItem(mo->type)) + { + // Remove stuff from death pits. + if (P_CheckDeathPitCollide(mo)) + { + P_RemoveMobj(mo); + return false; + } + } break; } @@ -3087,31 +3097,115 @@ boolean P_SceneryZMovement(mobj_t *mo) return true; } +// // P_CanRunOnWater // -// Returns true if player can waterrun on the 3D floor +// Returns true if player can water run on a 3D floor // -boolean P_CanRunOnWater(player_t *player, ffloor_t *rover) +boolean P_CanRunOnWater(mobj_t *mobj, ffloor_t *rover) { - boolean flip = player->mo->eflags & MFE_VERTICALFLIP; - fixed_t surfaceheight = flip ? player->mo->waterbottom : player->mo->watertop; - fixed_t playerbottom = flip ? (player->mo->z + player->mo->height) : player->mo->z; - fixed_t clip = flip ? (surfaceheight - playerbottom) : (playerbottom - surfaceheight); - fixed_t span = player->mo->watertop - player->mo->waterbottom; + const boolean flip = (mobj->eflags & MFE_VERTICALFLIP); + player_t *player = mobj->player; - return - clip > -(player->mo->height / 2) && - span > player->mo->height && - player->speed / 5 > abs(player->mo->momz) && - player->speed > K_GetKartSpeed(player, false, false) && - K_WaterRun(player) && - (rover->flags & FF_SWIMMABLE); + fixed_t surfaceheight = INT32_MAX; + fixed_t surfDiff = INT32_MAX; + + fixed_t floorheight = INT32_MAX; + fixed_t floorDiff = INT32_MAX; + + fixed_t mobjbottom = INT32_MAX; + fixed_t maxStep = INT32_MAX; + boolean doifit = false; + + pslope_t *waterSlope = NULL; + angle_t ourZAng = 0; + angle_t waterZAng = 0; + + if (rover == NULL) + { + // No rover. + return false; + } + + if (!(rover->flags & FF_SWIMMABLE)) + { + // It's not even a water FOF. + return false; + } + + if (player != NULL + && player->carry != CR_NONE) // Special carry state. + { + // No good player state. + return false; + } + + if (P_IsObjectOnGround(mobj) == false) + { + // Don't allow jumping onto water to start a water run. + // (Already water running still counts as being on the ground.) + return false; + } + + if (K_WaterRun(mobj) == false) + { + // Basic conditions for enabling water run. + return false; + } + + if (mobj->standingslope != NULL) + { + ourZAng = mobj->standingslope->zangle; + } + + waterSlope = (flip ? *rover->b_slope : *rover->t_slope); + if (waterSlope != NULL) + { + waterZAng = waterSlope->zangle; + } + + if (ourZAng != waterZAng) + { + // The surface slopes are different. + return false; + } + + surfaceheight = flip ? P_GetFFloorBottomZAt(rover, mobj->x, mobj->y) : P_GetFFloorTopZAt(rover, mobj->x, mobj->y); + mobjbottom = flip ? (mobj->z + mobj->height) : mobj->z; + + doifit = flip ? (surfaceheight - mobj->floorz >= mobj->height) : (mobj->ceilingz - surfaceheight >= mobj->height); + + if (!doifit) + { + // Object can't fit in this space. + return false; + } + + maxStep = P_GetThingStepUp(mobj); + + surfDiff = flip ? (surfaceheight - mobjbottom) : (mobjbottom - surfaceheight); + if (surfDiff <= maxStep && surfDiff >= 0) + { + // We start water run IF we can step-down! + floorheight = flip ? P_GetSectorCeilingZAt(mobj->subsector->sector, mobj->x, mobj->y) : P_GetSectorFloorZAt(mobj->subsector->sector, mobj->x, mobj->y); + floorDiff = flip ? (floorheight - mobjbottom) : (mobjbottom - floorheight); + if (floorDiff <= maxStep && floorDiff >= 0) + { + // ... but NOT if real floor is in range. + // FIXME: Count solid FOFs in this check + return false; + } + + return true; + } + + return false; } -boolean P_CheckSolidFFloorSurface(player_t *player, ffloor_t *rover) +boolean P_CheckSolidFFloorSurface(mobj_t *mobj, ffloor_t *rover) { - return P_CheckSolidLava(rover) || - P_CanRunOnWater(player, rover); + return P_CheckSolidLava(mobj, rover) || + P_CanRunOnWater(mobj, rover); } // @@ -3133,6 +3227,8 @@ void P_MobjCheckWater(mobj_t *mobj) boolean wasgroundpounding = false; fixed_t top2 = P_GetSectorCeilingZAt(sector, mobj->x, mobj->y); fixed_t bot2 = P_GetSectorFloorZAt(sector, mobj->x, mobj->y); + pslope_t *topslope = NULL; + pslope_t *bottomslope = NULL; // Default if no water exists. mobj->watertop = mobj->waterbottom = mobj->z - 1000*FRACUNIT; @@ -3175,6 +3271,9 @@ void P_MobjCheckWater(mobj_t *mobj) mobj->watertop = topheight; mobj->waterbottom = bottomheight; + topslope = *rover->t_slope; + bottomslope = *rover->b_slope; + // Just touching the water? if (((mobj->eflags & MFE_VERTICALFLIP) && thingtop - height < bottomheight) || (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z + height > topheight)) @@ -3212,13 +3311,28 @@ void P_MobjCheckWater(mobj_t *mobj) mobj->watertop = mobj->z; mobj->waterbottom = mobj->z - height; } + + topslope = bottomslope = NULL; } } - // Spectators and dead players don't get to do any of the things after this. - if (p && (p->spectator || p->playerstate != PST_LIVE)) + if (P_IsObjectOnGround(mobj) == true) { - return; + mobj->waterskip = 0; + } + + if (p != NULL) + { + // Spectators and dead players don't get to do any of the things after this. + if (p->spectator || p->playerstate != PST_LIVE) + { + return; + } + } + + if (mobj->flags & MF_APPLYTERRAIN) + { + K_SpawnWaterRunParticles(mobj); } // The rest of this code only executes on a water state change. @@ -3227,20 +3341,21 @@ void P_MobjCheckWater(mobj_t *mobj) return; } - if (p && !p->waterskip && - p->curshield != KSHIELD_BUBBLE && wasinwater) + if (p != NULL + && p->curshield != KSHIELD_BUBBLE + && mobj->waterskip == 0 + && wasinwater) { + // Play the gasp sound S_StartSound(mobj, sfx_s3k38); } - if ((p) // Players - || (mobj->flags & MF_PUSHABLE) // Pushables - || ((mobj->info->flags & MF_PUSHABLE) && mobj->fuse) // Previously pushable, might be moving still - ) + if (mobj->flags & MF_APPLYTERRAIN) { fixed_t waterZ = INT32_MAX; fixed_t solidZ = INT32_MAX; fixed_t diff = INT32_MAX; + INT32 waterDelta = 0; fixed_t thingZ = INT32_MAX; boolean splashValid = false; @@ -3249,11 +3364,19 @@ void P_MobjCheckWater(mobj_t *mobj) { waterZ = mobj->waterbottom; solidZ = mobj->ceilingz; + if (bottomslope) + { + waterDelta = bottomslope->zdelta; + } } else { waterZ = mobj->watertop; solidZ = mobj->floorz; + if (topslope) + { + waterDelta = topslope->zdelta; + } } diff = waterZ - solidZ; @@ -3322,25 +3445,22 @@ void P_MobjCheckWater(mobj_t *mobj) splish->destscale = mobj->scale; P_SetScale(splish, mobj->scale); + + // skipping stone! + if (K_WaterSkip(mobj) == true + && abs(waterDelta) < FRACUNIT/21) // Only on flat water + { + const fixed_t hop = 5 * mapobjectscale; + + mobj->momx = (4*mobj->momx)/5; + mobj->momy = (4*mobj->momy)/5; + mobj->momz = hop * P_MobjFlip(mobj); + + mobj->waterskip++; + } } - - // skipping stone! - if (p && p->waterskip < 2 - && ((p->speed/3 > abs(mobj->momz)) // Going more forward than horizontal, so you can skip across the water. - || (p->speed > 20*mapobjectscale && p->waterskip)) // Already skipped once, so you can skip once more! - && (splashValid == true)) - { - const fixed_t hop = 5 * mobj->scale; - - mobj->momx = (4*mobj->momx)/5; - mobj->momy = (4*mobj->momy)/5; - mobj->momz = hop * P_MobjFlip(mobj); - - p->waterskip++; - } - } - else if (P_MobjFlip(mobj) * mobj->momz > 0) + else { if (splashValid == true && !(mobj->eflags & MFE_UNDERWATER)) // underwater check to prevent splashes on opposite side { @@ -3628,7 +3748,7 @@ boolean P_CameraThinker(player_t *player, camera_t *thiscam, boolean resetcalled player->karthud[khud_timeovercam] = (2*TICRATE)+1; } - if (!resetcalled && !(player->cheats & PC_NOCLIP || leveltime < introtime) && !P_CheckSight(&dummy, player->mo)) // TODO: "P_CheckCameraSight" instead. + if (!resetcalled && !(player->mo->flags & MF_NOCLIP || leveltime < introtime) && !P_CheckSight(&dummy, player->mo)) // TODO: "P_CheckCameraSight" instead. { P_ResetCamera(player, thiscam); } @@ -4472,7 +4592,7 @@ mobj_t *P_GetClosestAxis(mobj_t *source) } if (closestaxis == NULL) - CONS_Debug(DBG_NIGHTS, "ERROR: No axis points found!\n"); + CONS_Alert(CONS_ERROR, "No axis points found!\n"); return closestaxis; } @@ -4544,7 +4664,7 @@ void P_SpawnHoopOfSomething(fixed_t x, fixed_t y, fixed_t z, fixed_t radius, INT if (!axis) { - CONS_Debug(DBG_NIGHTS, "You forgot to put axis points in the map!\n"); + CONS_Alert(CONS_WARNING, "You forgot to put axis points in the map!\n"); return; } @@ -5000,20 +5120,59 @@ cont: } // Kartitem stuff. + +// This item is never attached to a player -- it can DIE +// unconditionally in death sectors. +boolean P_IsKartFieldItem(INT32 type) +{ + switch (type) + { + case MT_BANANA: + case MT_EGGMANITEM: + case MT_ORBINAUT: + case MT_JAWZ: + case MT_SSMINE: + case MT_LANDMINE: + case MT_BALLHOG: + case MT_BUBBLESHIELDTRAP: + case MT_POGOSPRING: + case MT_SINK: + case MT_DROPTARGET: + case MT_DUELBOMB: + return true; + + default: + return false; + } +} + boolean P_IsKartItem(INT32 type) { - if (type == MT_EGGMANITEM || type == MT_EGGMANITEM_SHIELD || - type == MT_BANANA || type == MT_BANANA_SHIELD || - type == MT_DROPTARGET || type == MT_DROPTARGET_SHIELD || - type == MT_ORBINAUT || type == MT_ORBINAUT_SHIELD || - type == MT_JAWZ || type == MT_JAWZ_SHIELD || - type == MT_SSMINE || type == MT_SSMINE_SHIELD || - type == MT_SINK || type == MT_SINK_SHIELD || - type == MT_SPB || type == MT_BALLHOG || type == MT_BUBBLESHIELDTRAP || - type == MT_LANDMINE) - return true; - else - return false; + switch (type) + { + case MT_EGGMANITEM_SHIELD: + case MT_BANANA_SHIELD: + case MT_DROPTARGET_SHIELD: + case MT_ORBINAUT_SHIELD: + case MT_JAWZ_SHIELD: + case MT_SSMINE_SHIELD: + case MT_SINK_SHIELD: + case MT_SPB: + case MT_HYUDORO: + return true; + + default: + return P_IsKartFieldItem(type); + } +} + +// This item can die in death sectors. There may be some +// special conditions for items that don't switch types... +// TODO: just make a general function for things that should +// die like this? +boolean P_CanDeleteKartItem(INT32 type) +{ + return P_IsKartFieldItem(type); } // Called when a kart item "thinks" @@ -6335,6 +6494,7 @@ static boolean P_MobjDeadThink(mobj_t *mobj) return false; } /* FALLTHRU */ + case MT_GARDENTOP: case MT_ORBINAUT_SHIELD: case MT_BANANA_SHIELD: case MT_EGGMANITEM_SHIELD: @@ -6725,11 +6885,13 @@ static boolean P_MobjRegularThink(mobj_t *mobj) case MT_ORBINAUT: { Obj_OrbinautThink(mobj); + P_MobjCheckWater(mobj); break; } case MT_JAWZ: { Obj_JawzThink(mobj); + P_MobjCheckWater(mobj); break; } case MT_EGGMANITEM: @@ -6793,6 +6955,8 @@ static boolean P_MobjRegularThink(mobj_t *mobj) if (mobj->threshold > 0) mobj->threshold--; + + P_MobjCheckWater(mobj); } break; case MT_SINK: @@ -7049,7 +7213,11 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } } break; - case MT_TRIPWIREBOOST: + case MT_TRIPWIREBOOST: { + mobj_t *top; + fixed_t newHeight; + fixed_t newScale; + if (!mobj->target || !mobj->target->health || !mobj->target->player || !mobj->target->player->tripwireLeniency) { @@ -7057,10 +7225,21 @@ static boolean P_MobjRegularThink(mobj_t *mobj) return false; } + newHeight = mobj->target->height; + newScale = mobj->target->scale; + + top = K_GetGardenTop(mobj->target->player); + + if (top) + { + newHeight += 5 * top->height / 4; + newScale = FixedMul(newScale, FixedDiv(newHeight / 2, mobj->target->height)); + } + mobj->angle = K_MomentumAngle(mobj->target); - P_MoveOrigin(mobj, mobj->target->x, mobj->target->y, mobj->target->z + (mobj->target->height >> 1)); - mobj->destscale = mobj->target->scale; - P_SetScale(mobj, mobj->target->scale); + P_MoveOrigin(mobj, mobj->target->x, mobj->target->y, mobj->target->z + (newHeight / 2)); + mobj->destscale = newScale; + P_SetScale(mobj, newScale); if (mobj->extravalue1) { @@ -7132,6 +7311,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } } break; + } case MT_BOOSTFLAME: if (!mobj->target || !mobj->target->health) { @@ -7703,6 +7883,16 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } break; } + case MT_GARDENTOP: + { + Obj_GardenTopThink(mobj); + break; + } + case MT_GARDENTOPSPARK: + { + Obj_GardenTopSparkThink(mobj); + break; + } case MT_HYUDORO: { Obj_HyudoroThink(mobj); @@ -9318,11 +9508,7 @@ void P_MobjThinker(mobj_t *mobj) return; // Destroy items sector special - if (mobj->type == MT_BANANA || mobj->type == MT_EGGMANITEM - || mobj->type == MT_ORBINAUT || mobj->type == MT_BALLHOG - || mobj->type == MT_JAWZ - || mobj->type == MT_SSMINE || mobj->type == MT_BUBBLESHIELDTRAP - || mobj->type == MT_LANDMINE) + if (P_CanDeleteKartItem(mobj->type)) { if (mobj->health > 0 && P_MobjTouchingSectorSpecial(mobj, 4, 7, true)) { @@ -9749,6 +9935,7 @@ static void P_DefaultMobjShadowScale(mobj_t *thing) case MT_BUBBLESHIELD: case MT_BUBBLESHIELDTRAP: case MT_FLAMESHIELD: + case MT_GARDENTOP: thing->shadowscale = FRACUNIT; break; case MT_RING: @@ -9891,6 +10078,7 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) mobj->colorized = false; mobj->hitlag = 0; + mobj->waterskip = 0; // Set shadowscale here, before spawn hook so that Lua can change it P_DefaultMobjShadowScale(mobj); diff --git a/src/p_mobj.h b/src/p_mobj.h index 4cdacea6d..19cd1c77a 100644 --- a/src/p_mobj.h +++ b/src/p_mobj.h @@ -169,7 +169,7 @@ typedef enum typedef enum { MF2_AXIS = 1, // It's a NiGHTS axis! (For faster checking) - MF2_TWOD = 1<<1, // Moves like it's in a 2D level + // free: 1<<1 MF2_DONTRESPAWN = 1<<2, // Don't respawn this object! // free: 1<<3 MF2_AUTOMATIC = 1<<4, // Thrown ring has automatic properties @@ -409,6 +409,7 @@ typedef struct mobj_s struct mobj_s *terrainOverlay; // Overlay sprite object for terrain INT32 hitlag; // Sal-style hit lag, straight from Captain Fetch's jowls + UINT8 waterskip; // Water skipping counter INT32 dispoffset; @@ -499,7 +500,9 @@ void P_RunCachedActions(void); void P_AddCachedAction(mobj_t *mobj, INT32 statenum); // kartitem stuff: Returns true if the specified 'type' is one of the kart item constants we want in the kitemcap list +boolean P_IsKartFieldItem(INT32 type); boolean P_IsKartItem(INT32 type); +boolean P_CanDeleteKartItem(INT32 type); void P_AddKartItem(mobj_t *thing); // needs to be called in k_kart.c void P_RunKartItems(void); diff --git a/src/p_saveg.c b/src/p_saveg.c index d050da4b1..62f11e7ef 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -280,7 +280,6 @@ static void P_NetArchivePlayers(void) WRITEINT32(save_p, players[i].underwatertilt); WRITEFIXED(save_p, players[i].offroad); - WRITEUINT8(save_p, players[i].waterskip); WRITEUINT16(save_p, players[i].tiregrease); WRITEUINT16(save_p, players[i].springstars); @@ -379,6 +378,8 @@ static void P_NetArchivePlayers(void) WRITEUINT8(save_p, players[i].kickstartaccel); WRITEUINT8(save_p, players[i].stairjank); + WRITEUINT8(save_p, players[i].topdriftheld); + WRITEUINT8(save_p, players[i].topinfirst); WRITEUINT8(save_p, players[i].shrinkLaserDelay); @@ -394,6 +395,7 @@ static void P_NetArchivePlayers(void) WRITEUINT32(save_p, players[i].respawn.distanceleft); WRITEUINT32(save_p, players[i].respawn.dropdash); WRITEUINT8(save_p, players[i].respawn.truedeath); + WRITEUINT8(save_p, players[i].respawn.manual); // botvars_t WRITEUINT8(save_p, players[i].botvars.difficulty); @@ -577,7 +579,6 @@ static void P_NetUnArchivePlayers(void) players[i].underwatertilt = READINT32(save_p); players[i].offroad = READFIXED(save_p); - players[i].waterskip = READUINT8(save_p); players[i].tiregrease = READUINT16(save_p); players[i].springstars = READUINT16(save_p); @@ -676,6 +677,8 @@ static void P_NetUnArchivePlayers(void) players[i].kickstartaccel = READUINT8(save_p); players[i].stairjank = READUINT8(save_p); + players[i].topdriftheld = READUINT8(save_p); + players[i].topinfirst = READUINT8(save_p); players[i].shrinkLaserDelay = READUINT8(save_p); @@ -691,6 +694,7 @@ static void P_NetUnArchivePlayers(void) players[i].respawn.distanceleft = READUINT32(save_p); players[i].respawn.dropdash = READUINT32(save_p); players[i].respawn.truedeath = READUINT8(save_p); + players[i].respawn.manual = READUINT8(save_p); // botvars_t players[i].botvars.difficulty = READUINT8(save_p); @@ -1638,6 +1642,7 @@ typedef enum MD2_ITNEXT = 1<<27, MD2_LASTMOMZ = 1<<28, MD2_TERRAIN = 1<<29, + MD2_WATERSKIP = 1<<30, } mobj_diff2_t; typedef enum @@ -1872,6 +1877,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type) } if (mobj->hitlag) diff2 |= MD2_HITLAG; + if (mobj->waterskip) + diff2 |= MD2_WATERSKIP; if (mobj->dispoffset) diff2 |= MD2_DISPOFFSET; if (mobj == waypointcap) @@ -2082,6 +2089,10 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type) { WRITEINT32(save_p, mobj->hitlag); } + if (diff2 & MD2_WATERSKIP) + { + WRITEUINT8(save_p, mobj->waterskip); + } if (diff2 & MD2_DISPOFFSET) { WRITEINT32(save_p, mobj->dispoffset); @@ -3191,6 +3202,10 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker) { mobj->hitlag = READINT32(save_p); } + if (diff2 & MD2_WATERSKIP) + { + mobj->waterskip = READUINT8(save_p); + } if (diff2 & MD2_DISPOFFSET) { mobj->dispoffset = READINT32(save_p); @@ -4600,6 +4615,8 @@ static void P_NetArchiveMisc(boolean resending) WRITEINT16(save_p, task->timer); WRITESTRING(save_p, task->command); } + + WRITEUINT32(save_p, cht_debug); } static inline boolean P_NetUnArchiveMisc(boolean reloading) @@ -4764,6 +4781,8 @@ static inline boolean P_NetUnArchiveMisc(boolean reloading) Schedule_Add(basetime, timer, command); } + cht_debug = READUINT32(save_p); + return true; } diff --git a/src/p_spec.c b/src/p_spec.c index b8c103f70..3934e56c9 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -2836,16 +2836,6 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) EV_DoCrush(line, crushBothOnce); break; - case 432: // Enable 2D Mode (Disable if noclimb) - if (mo && mo->player) - { - if (line->flags & ML_NOCLIMB) - mo->flags2 &= ~MF2_TWOD; - else - mo->flags2 |= MF2_TWOD; - } - break; - case 433: // Flip gravity (Flop gravity if noclimb) Works on pushables, too! if (line->flags & ML_NOCLIMB) mo->flags2 &= ~MF2_OBJECTFLIP; @@ -4426,7 +4416,7 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers switch (special) { case 1: // Damage (Generic) - if (roversector || P_MobjReadyToTrigger(player->mo, sector)) + if (!K_IsRidingFloatingTop(player) && (roversector || P_MobjReadyToTrigger(player->mo, sector))) P_DamageMobj(player->mo, NULL, NULL, 1, DMG_NORMAL); break; case 2: // Damage (Water) // SRB2kart - These three damage types are now offroad sectors @@ -4434,7 +4424,7 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers case 4: // Damage (Electrical) break; case 5: // Spikes - if (roversector || P_MobjReadyToTrigger(player->mo, sector)) + if (!K_IsRidingFloatingTop(player) && (roversector || P_MobjReadyToTrigger(player->mo, sector))) P_DamageMobj(player->mo, NULL, NULL, 1, DMG_NORMAL); break; case 6: // Death Pit (Camera Mod) diff --git a/src/p_telept.c b/src/p_telept.c index 59774dd76..530830ad3 100644 --- a/src/p_telept.c +++ b/src/p_telept.c @@ -35,7 +35,7 @@ void P_MixUp(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle, INT32 starpostnum, tic_t starposttime, angle_t starpostangle, fixed_t starpostscale, angle_t drawangle, INT32 flags2) { - const INT32 takeflags2 = MF2_TWOD|MF2_OBJECTFLIP; + const INT32 takeflags2 = MF2_OBJECTFLIP; UINT8 i; (void)starposttime; diff --git a/src/p_tick.c b/src/p_tick.c index 708ed6ca8..ad83abca6 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -44,6 +44,14 @@ INT32 P_AltFlip(INT32 n, tic_t tics) return leveltime % (2 * tics) < tics ? n : -(n); } +// Please read p_tick.h +INT32 P_LerpFlip(INT32 n, tic_t tics) +{ + const tic_t w = 2 * tics; + + return P_AltFlip(((leveltime % w) - tics) * n, w); +} + // // THINKERS // All thinkers should be allocated by Z_Calloc diff --git a/src/p_tick.h b/src/p_tick.h index 7b10a5f28..f892942ea 100644 --- a/src/p_tick.h +++ b/src/p_tick.h @@ -35,4 +35,12 @@ mobj_t *P_SetTarget(mobj_t **mo, mobj_t *target); // killough 11/98 INT32 P_AltFlip(INT32 value, tic_t tics); #define P_RandomFlip(value) P_AltFlip(value, 1) +// Multiply value back and forth between -(tics) and +(tics). +// Example output P_ModulateFlip(2, 2): +// Tic: 0 1 2 3 4 5 6 7 8 +// Val: -4 -2 0 2 4 2 0 -2 -4 +// A half cycle (one direction) takes 2 * tics. +// A full cycle takes 4 * tics. +INT32 P_LerpFlip(INT32 value, tic_t tics); + #endif diff --git a/src/p_user.c b/src/p_user.c index 5261b84cb..2217b399d 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -1801,7 +1801,7 @@ static void P_3dMovement(player_t *player) // Get the old momentum; this will be needed at the end of the function! -SH oldMagnitude = R_PointToDist2(player->mo->momx - player->cmomx, player->mo->momy - player->cmomy, 0, 0); - if (player->stairjank > 8 && leveltime & 3) + if ((player->stairjank > 8 && leveltime & 3) || K_IsRidingFloatingTop(player)) { movepushangle = K_MomentumAngle(player->mo); } @@ -1884,6 +1884,7 @@ static void P_3dMovement(player_t *player) if (player->mo->movefactor != FRACUNIT) // Friction-scaled acceleration... movepushforward = FixedMul(movepushforward, player->mo->movefactor); + if (player->curshield != KSHIELD_TOP) { INT32 a = K_GetUnderwaterTurnAdjust(player); INT32 adj = 0; @@ -1967,6 +1968,24 @@ static void P_3dMovement(player_t *player) player->mo->momx += totalthrust.x; player->mo->momy += totalthrust.y; + // Releasing a drift while on the Top translates all your + // momentum (and even then some) into whichever direction + // you're facing + if (onground && player->curshield == KSHIELD_TOP && (K_GetKartButtons(player) & BT_DRIFT) != BT_DRIFT && (player->oldcmd.buttons & BT_DRIFT)) + { + const fixed_t gmin = FRACUNIT/4; + const fixed_t gmax = 5*FRACUNIT/2; + + const fixed_t grindfactor = (gmax - gmin) / GARDENTOP_MAXGRINDTIME; + const fixed_t grindscale = gmin + (player->topdriftheld * grindfactor); + + const fixed_t speed = R_PointToDist2(0, 0, player->mo->momx, player->mo->momy); + + P_InstaThrust(player->mo, player->mo->angle, FixedMul(speed, grindscale)); + + player->topdriftheld = 0;/* reset after release */ + } + if (!onground) { const fixed_t airspeedcap = (50*mapobjectscale); @@ -2292,100 +2311,6 @@ void P_MovePlayer(player_t *player) //GAMEPLAY STUFF// ////////////////// - if (((!(player->mo->eflags & MFE_VERTICALFLIP) && player->mo->z + player->mo->height >= player->mo->watertop && player->mo->z <= player->mo->watertop) - || (player->mo->eflags & MFE_VERTICALFLIP && player->mo->z + player->mo->height >= player->mo->waterbottom && player->mo->z <= player->mo->waterbottom)) - && (player->speed > runspd) - && player->mo->momz == 0 && player->carry != CR_SLIDING && !player->spectator) - { - fixed_t playerTopSpeed = K_GetKartSpeed(player, false, false); - fixed_t trailScale = FixedMul(FixedDiv(player->speed - runspd, playerTopSpeed - runspd), mapobjectscale); - - if (playerTopSpeed > runspd) - trailScale = FixedMul(FixedDiv(player->speed - runspd, playerTopSpeed - runspd), mapobjectscale); - else - trailScale = mapobjectscale; // Scaling is based off difference between runspeed and top speed - - if (trailScale > 0) - { - const angle_t forwardangle = K_MomentumAngle(player->mo); - const fixed_t playerVisualRadius = player->mo->radius + (8 * player->mo->scale); - const size_t numFrames = S_WATERTRAIL8 - S_WATERTRAIL1; - const statenum_t curOverlayFrame = S_WATERTRAIL1 + (leveltime % numFrames); - const statenum_t curUnderlayFrame = S_WATERTRAILUNDERLAY1 + (leveltime % numFrames); - fixed_t x1, x2, y1, y2; - mobj_t *water; - - x1 = player->mo->x + player->mo->momx + P_ReturnThrustX(player->mo, forwardangle + ANGLE_90, playerVisualRadius); - y1 = player->mo->y + player->mo->momy + P_ReturnThrustY(player->mo, forwardangle + ANGLE_90, playerVisualRadius); - x1 = x1 + P_ReturnThrustX(player->mo, forwardangle, playerVisualRadius); - y1 = y1 + P_ReturnThrustY(player->mo, forwardangle, playerVisualRadius); - - x2 = player->mo->x + player->mo->momx + P_ReturnThrustX(player->mo, forwardangle - ANGLE_90, playerVisualRadius); - y2 = player->mo->y + player->mo->momy + P_ReturnThrustY(player->mo, forwardangle - ANGLE_90, playerVisualRadius); - x2 = x2 + P_ReturnThrustX(player->mo, forwardangle, playerVisualRadius); - y2 = y2 + P_ReturnThrustY(player->mo, forwardangle, playerVisualRadius); - - // Left - // underlay - water = P_SpawnMobj(x1, y1, - ((player->mo->eflags & MFE_VERTICALFLIP) ? player->mo->waterbottom - FixedMul(mobjinfo[MT_WATERTRAILUNDERLAY].height, player->mo->scale) : player->mo->watertop), MT_WATERTRAILUNDERLAY); - P_InitAngle(water, forwardangle - ANGLE_180 - ANGLE_22h); - water->destscale = trailScale; - water->momx = player->mo->momx; - water->momy = player->mo->momy; - water->momz = player->mo->momz; - P_SetScale(water, trailScale); - P_SetMobjState(water, curUnderlayFrame); - - // overlay - water = P_SpawnMobj(x1, y1, - ((player->mo->eflags & MFE_VERTICALFLIP) ? player->mo->waterbottom - FixedMul(mobjinfo[MT_WATERTRAIL].height, player->mo->scale) : player->mo->watertop), MT_WATERTRAIL); - P_InitAngle(water, forwardangle - ANGLE_180 - ANGLE_22h); - water->destscale = trailScale; - water->momx = player->mo->momx; - water->momy = player->mo->momy; - water->momz = player->mo->momz; - P_SetScale(water, trailScale); - P_SetMobjState(water, curOverlayFrame); - - // Right - // Underlay - water = P_SpawnMobj(x2, y2, - ((player->mo->eflags & MFE_VERTICALFLIP) ? player->mo->waterbottom - FixedMul(mobjinfo[MT_WATERTRAILUNDERLAY].height, player->mo->scale) : player->mo->watertop), MT_WATERTRAILUNDERLAY); - P_InitAngle(water, forwardangle - ANGLE_180 + ANGLE_22h); - water->destscale = trailScale; - water->momx = player->mo->momx; - water->momy = player->mo->momy; - water->momz = player->mo->momz; - P_SetScale(water, trailScale); - P_SetMobjState(water, curUnderlayFrame); - - // Overlay - water = P_SpawnMobj(x2, y2, - ((player->mo->eflags & MFE_VERTICALFLIP) ? player->mo->waterbottom - FixedMul(mobjinfo[MT_WATERTRAIL].height, player->mo->scale) : player->mo->watertop), MT_WATERTRAIL); - P_InitAngle(water, forwardangle - ANGLE_180 + ANGLE_22h); - water->destscale = trailScale; - water->momx = player->mo->momx; - water->momy = player->mo->momy; - water->momz = player->mo->momz; - P_SetScale(water, trailScale); - P_SetMobjState(water, curOverlayFrame); - - if (!S_SoundPlaying(player->mo, sfx_s3kdbs)) - { - const INT32 volume = (min(trailScale, FRACUNIT) * 255) / FRACUNIT; - S_StartSoundAtVolume(player->mo, sfx_s3kdbs, volume); - } - } - } - - // Little water sound while touching water - just a nicety. - if ((player->mo->eflags & MFE_TOUCHWATER) && !(player->mo->eflags & MFE_UNDERWATER) && !player->spectator) - { - if (P_RandomChance(PR_BUBBLE, FRACUNIT/2) && leveltime % TICRATE == 0) - S_StartSound(player->mo, sfx_floush); - } - //////////////////////////// //SPINNING AND SPINDASHING// //////////////////////////// @@ -3068,10 +2993,6 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall mobj_t *mo; fixed_t f1, f2; fixed_t speed; -#ifndef NOCLIPCAM - boolean cameranoclip; - subsector_t *newsubsec; -#endif fixed_t playerScale = FixedDiv(player->mo->scale, mapobjectscale); fixed_t scaleDiff = playerScale - FRACUNIT; @@ -3129,12 +3050,6 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall return true; } -#ifndef NOCLIPCAM - cameranoclip = ((player->cheats & PC_NOCLIP) - || (mo->flags & (MF_NOCLIP|MF_NOCLIPHEIGHT)) // Noclipping player camera noclips too!! - || (leveltime < introtime)); // Kart intro cam -#endif - if ((player->pflags & PF_NOCONTEST) && (gametyperules & GTR_CIRCUIT)) // 1 for momentum keep, 2 for turnaround timeover = (player->karthud[khud_timeovercam] > 2*TICRATE ? 2 : 1); else @@ -3263,6 +3178,13 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall } } + /* The Top is Big Large so zoom out */ + if (player->curshield == KSHIELD_TOP) + { + camdist += 40 * mapobjectscale; + camheight += 40 * mapobjectscale; + } + if (!resetcalled && (leveltime >= introtime && timeover != 2) && (t_cam_rotate[num] != -42)) { @@ -3377,204 +3299,6 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall z = mo->z + pviewheight + distz; } -#ifndef NOCLIPCAM // Disable all z-clipping for noclip cam - // move camera down to move under lower ceilings - newsubsec = R_PointInSubsectorOrNull(((mo->x>>FRACBITS) + (thiscam->x>>FRACBITS))<<(FRACBITS-1), ((mo->y>>FRACBITS) + (thiscam->y>>FRACBITS))<<(FRACBITS-1)); - - if (!newsubsec) - newsubsec = thiscam->subsector; - - if (newsubsec) - { - fixed_t myfloorz, myceilingz; - fixed_t midz = thiscam->z + (thiscam->z - mo->z)/2; - fixed_t midx = ((mo->x>>FRACBITS) + (thiscam->x>>FRACBITS))<<(FRACBITS-1); - fixed_t midy = ((mo->y>>FRACBITS) + (thiscam->y>>FRACBITS))<<(FRACBITS-1); - - // Cameras use the heightsec's heights rather then the actual sector heights. - // If you can see through it, why not move the camera through it too? - if (newsubsec->sector->camsec >= 0) - { - myfloorz = sectors[newsubsec->sector->camsec].floorheight; - myceilingz = sectors[newsubsec->sector->camsec].ceilingheight; - } - else if (newsubsec->sector->heightsec >= 0) - { - myfloorz = sectors[newsubsec->sector->heightsec].floorheight; - myceilingz = sectors[newsubsec->sector->heightsec].ceilingheight; - } - else - { - myfloorz = P_CameraGetFloorZ(thiscam, newsubsec->sector, midx, midy, NULL); - myceilingz = P_CameraGetCeilingZ(thiscam, newsubsec->sector, midx, midy, NULL); - } - - // Check list of fake floors and see if floorz/ceilingz need to be altered. - if (newsubsec->sector->ffloors) - { - ffloor_t *rover; - fixed_t delta1, delta2; - INT32 thingtop = midz + thiscam->height; - - for (rover = newsubsec->sector->ffloors; rover; rover = rover->next) - { - fixed_t topheight, bottomheight; - if (!(rover->flags & FF_BLOCKOTHERS) || !(rover->flags & FF_EXISTS) || !(rover->flags & FF_RENDERALL) || GETSECSPECIAL(rover->master->frontsector->special, 4) == 12) - continue; - - topheight = P_CameraGetFOFTopZ(thiscam, newsubsec->sector, rover, midx, midy, NULL); - bottomheight = P_CameraGetFOFBottomZ(thiscam, newsubsec->sector, rover, midx, midy, NULL); - - delta1 = midz - (bottomheight - + ((topheight - bottomheight)/2)); - delta2 = thingtop - (bottomheight - + ((topheight - bottomheight)/2)); - if (topheight > myfloorz && abs(delta1) < abs(delta2)) - myfloorz = topheight; - if (bottomheight < myceilingz && abs(delta1) >= abs(delta2)) - myceilingz = bottomheight; - } - } - - // Check polyobjects and see if floorz/ceilingz need to be altered - { - INT32 xl, xh, yl, yh, bx, by; - validcount++; - - xl = (unsigned)(tmbbox[BOXLEFT] - bmaporgx)>>MAPBLOCKSHIFT; - xh = (unsigned)(tmbbox[BOXRIGHT] - bmaporgx)>>MAPBLOCKSHIFT; - yl = (unsigned)(tmbbox[BOXBOTTOM] - bmaporgy)>>MAPBLOCKSHIFT; - yh = (unsigned)(tmbbox[BOXTOP] - bmaporgy)>>MAPBLOCKSHIFT; - - BMBOUNDFIX(xl, xh, yl, yh); - - for (by = yl; by <= yh; by++) - for (bx = xl; bx <= xh; bx++) - { - INT32 offset; - polymaplink_t *plink; // haleyjd 02/22/06 - - if (bx < 0 || by < 0 || bx >= bmapwidth || by >= bmapheight) - continue; - - offset = by*bmapwidth + bx; - - // haleyjd 02/22/06: consider polyobject lines - plink = polyblocklinks[offset]; - - while (plink) - { - polyobj_t *po = plink->po; - - if (po->validcount != validcount) // if polyobj hasn't been checked - { - sector_t *polysec; - fixed_t delta1, delta2, thingtop; - fixed_t polytop, polybottom; - - po->validcount = validcount; - - if (!P_PointInsidePolyobj(po, x, y) || !(po->flags & POF_SOLID)) - { - plink = (polymaplink_t *)(plink->link.next); - continue; - } - - // We're inside it! Yess... - polysec = po->lines[0]->backsector; - - if (GETSECSPECIAL(polysec->special, 4) == 12) - { // Camera noclip polyobj. - plink = (polymaplink_t *)(plink->link.next); - continue; - } - - if (po->flags & POF_CLIPPLANES) - { - polytop = polysec->ceilingheight; - polybottom = polysec->floorheight; - } - else - { - polytop = INT32_MAX; - polybottom = INT32_MIN; - } - - thingtop = midz + thiscam->height; - delta1 = midz - (polybottom + ((polytop - polybottom)/2)); - delta2 = thingtop - (polybottom + ((polytop - polybottom)/2)); - - if (polytop > myfloorz && abs(delta1) < abs(delta2)) - myfloorz = polytop; - - if (polybottom < myceilingz && abs(delta1) >= abs(delta2)) - myceilingz = polybottom; - } - plink = (polymaplink_t *)(plink->link.next); - } - } - } - - // crushed camera - if (myceilingz <= myfloorz + thiscam->height && !resetcalled && !cameranoclip) - { - P_ResetCamera(player, thiscam); - return true; - } - - // camera fit? - if (myceilingz != myfloorz - && myceilingz - thiscam->height < z) - { -/* // no fit - if (!resetcalled && !cameranoclip) - { - P_ResetCamera(player, thiscam); - return true; - } -*/ - z = myceilingz - thiscam->height-FixedMul(11*FRACUNIT, mo->scale); - // is the camera fit is there own sector - } - - // Make the camera a tad smarter with 3d floors - if (newsubsec->sector->ffloors && !cameranoclip) - { - ffloor_t *rover; - - for (rover = newsubsec->sector->ffloors; rover; rover = rover->next) - { - fixed_t topheight, bottomheight; - if ((rover->flags & FF_BLOCKOTHERS) && (rover->flags & FF_RENDERALL) && (rover->flags & FF_EXISTS) && GETSECSPECIAL(rover->master->frontsector->special, 4) == 12) - { - topheight = P_CameraGetFOFTopZ(thiscam, newsubsec->sector, rover, midx, midy, NULL); - bottomheight = P_CameraGetFOFBottomZ(thiscam, newsubsec->sector, rover, midx, midy, NULL); - - if (bottomheight - thiscam->height < z - && midz < bottomheight) - z = bottomheight - thiscam->height-FixedMul(11*FRACUNIT, mo->scale); - - else if (topheight + thiscam->height > z - && midz > topheight) - z = topheight; - - if ((mo->z >= topheight && midz < bottomheight) - || ((mo->z < bottomheight && mo->z+mo->height < topheight) && midz >= topheight)) - { - // Can't see - if (!resetcalled) - P_ResetCamera(player, thiscam); - return true; - } - } - } - } - } - - if (thiscam->z < thiscam->floorz && !cameranoclip) - thiscam->z = thiscam->floorz; -#endif // NOCLIPCAM - // point viewed by the camera // this point is just 64 unit forward the player dist = 64*cameraScale; @@ -4660,3 +4384,28 @@ boolean P_PlayerFullbright(player_t *player) { return (player->invincibilitytimer > 0); } + +void P_ResetPlayerCheats(void) +{ + INT32 i; + + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *player = &players[i]; + mobj_t *thing = player->mo; + + if (!playeringame[i]) + continue; + + player->pflags &= ~(PF_GODMODE); + player->respawn.manual = false; + + if (P_MobjWasRemoved(thing)) + continue; + + thing->flags &= ~(MF_NOCLIP); + + thing->destscale = mapobjectscale; + P_SetScale(thing, thing->destscale); + } +} diff --git a/src/r_main.c b/src/r_main.c index 58c65427d..3e79f9871 100644 --- a/src/r_main.c +++ b/src/r_main.c @@ -915,39 +915,9 @@ void R_ApplyViewMorph(int s) end = width * height; -#if 0 - if (cv_debug & DBG_VIEWMORPH) + for (p = 0; p < end; p++) { - UINT8 border = 32; - UINT8 grid = 160; - INT32 ws = vid.width / 4; - INT32 hs = vid.width * (vid.height / 4); - - memcpy(tmpscr, srcscr, vid.width*vid.height); - for (p = 0; p < vid.width; p++) - { - tmpscr[viewmorph.scrmap[p]] = border; - tmpscr[viewmorph.scrmap[p + hs]] = grid; - tmpscr[viewmorph.scrmap[p + hs*2]] = grid; - tmpscr[viewmorph.scrmap[p + hs*3]] = grid; - tmpscr[viewmorph.scrmap[end - 1 - p]] = border; - } - for (p = vid.width; p < end; p += vid.width) - { - tmpscr[viewmorph.scrmap[p]] = border; - tmpscr[viewmorph.scrmap[p + ws]] = grid; - tmpscr[viewmorph.scrmap[p + ws*2]] = grid; - tmpscr[viewmorph.scrmap[p + ws*3]] = grid; - tmpscr[viewmorph.scrmap[end - 1 - p]] = border; - } - } - else -#endif - { - for (p = 0; p < end; p++) - { - tmpscr[p] = srcscr[viewmorph[s].scrmap[p]]; - } + tmpscr[p] = srcscr[viewmorph[s].scrmap[p]]; } VID_BlitLinearScreen(tmpscr, srcscr, diff --git a/src/r_patchrotation.c b/src/r_patchrotation.c index e2506baa5..d3a844396 100644 --- a/src/r_patchrotation.c +++ b/src/r_patchrotation.c @@ -42,6 +42,8 @@ static angle_t R_PlayerSpriteRotation(player_t *player, player_t *viewPlayer) angle_t rollAngle = 0; + mobj_t *top = K_GetGardenTop(player); + if (player->mo->eflags & MFE_UNDERWATER) { rollAngle -= player->underwatertilt; @@ -61,6 +63,14 @@ static angle_t R_PlayerSpriteRotation(player_t *player, player_t *viewPlayer) (17 / player->stairjank)); } + if (top) + { + /* FIXME: why does it not look right at more acute + angles without this? There's a related hack to + spritexoffset in K_KartPlayerThink. */ + rollAngle += 3 * (INT32)top->rollangle / 2; + } + return rollAngle; } diff --git a/src/st_stuff.c b/src/st_stuff.c index 138c0e64b..d46da54b5 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -364,7 +364,7 @@ static void ST_drawDebugInfo(void) if (!stplyr->mo) return; - if (cv_debug & DBG_BASIC) + if (cht_debug & DBG_BASIC) { const fixed_t d = AngleFixed(stplyr->mo->angle); V_DrawRightAlignedString(320, 168, V_MONOSPACE, va("X: %6d", stplyr->mo->x>>FRACBITS)); @@ -375,7 +375,7 @@ static void ST_drawDebugInfo(void) height = 152; } - if (cv_debug & DBG_DETAILED) + if (cht_debug & DBG_DETAILED) { //V_DrawRightAlignedString(320, height - 104, V_MONOSPACE, va("SHIELD: %5x", stplyr->powers[pw_shield])); V_DrawRightAlignedString(320, height - 96, V_MONOSPACE, va("SCALE: %5d%%", (stplyr->mo->scale*100)/FRACUNIT)); @@ -404,7 +404,7 @@ static void ST_drawDebugInfo(void) height -= 120; } - if (cv_debug & DBG_RANDOMIZER) // randomizer testing + if (cht_debug & DBG_RNG) // randomizer testing { // TODO: this only accounts for the undefined class, // which should be phased out as much as possible anyway. @@ -421,7 +421,7 @@ static void ST_drawDebugInfo(void) height -= 32; } - if (cv_debug & DBG_MEMORY) + if (cht_debug & DBG_MEMORY) V_DrawRightAlignedString(320, height, V_MONOSPACE, va("Heap used: %7sKB", sizeu1(Z_TagsUsage(0, INT32_MAX)>>10))); } diff --git a/src/v_video.c b/src/v_video.c index 8ed5ed53f..0e85bf2fa 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -36,6 +36,7 @@ // SRB2Kart #include "k_hud.h" +#include "i_time.h" // Each screen is [vid.width*vid.height]; UINT8 *screens[5]; @@ -1636,6 +1637,14 @@ UINT8 *V_GetStringColormap(INT32 colorflags) #endif } +INT32 V_DanceYOffset(INT32 counter) +{ + const INT32 duration = 16; + const INT32 step = (I_GetTime() + counter) % duration; + + return abs(step - (duration / 2)) - (duration / 4); +} + // Writes a single character (draw WHITE if bit 7 set) // void V_DrawCharacter(INT32 x, INT32 y, INT32 c, boolean lowercaseallowed) @@ -2044,9 +2053,13 @@ void V_DrawStringScaled( boolean uppercase; boolean notcolored; + boolean dance; + boolean nodanceoverride; + INT32 dancecounter; + fixed_t cx, cy; - fixed_t cxoff; + fixed_t cxoff, cyoff; fixed_t cw; INT32 spacing; @@ -2057,6 +2070,14 @@ void V_DrawStringScaled( uppercase = !( flags & V_ALLOWLOWERCASE ); flags &= ~(V_FLIP);/* These two (V_ALLOWLOWERCASE) share a bit. */ + dance = (flags & V_STRINGDANCE) != 0; + nodanceoverride = !dance; + dancecounter = 0; + + /* Some of these flags get overloaded in this function so + don't pass them on. */ + flags &= ~(V_PARAMMASK); + if (colormap == NULL) { colormap = V_GetStringColormap(( flags & V_CHARCOLORMASK )); @@ -2247,8 +2268,9 @@ void V_DrawStringScaled( cx = x; cy = y; + cyoff = 0; - for (; ( c = *s ); ++s) + for (; ( c = *s ); ++s, ++dancecounter) { switch (c) { @@ -2267,18 +2289,29 @@ void V_DrawStringScaled( ( ( c & 0x7f )<< V_CHARCOLORSHIFT )& V_CHARCOLORMASK); } + if (nodanceoverride) + { + dance = false; + } + } + else if (c == V_STRINGDANCE) + { + dance = true; } else if (cx < right) { if (uppercase) c = toupper(c); + if (dance) + cyoff = V_DanceYOffset(dancecounter) * FRACUNIT; + c -= font->start; if (c >= 0 && c < font->size && font->font[c]) { cw = SHORT (font->font[c]->width) * dupx; cxoff = (*dim_fn)(scale, chw, hchw, dupx, &cw); - V_DrawFixedPatch(cx + cxoff, cy, scale, + V_DrawFixedPatch(cx + cxoff, cy + cyoff, scale, flags, font->font[c], colormap); cx += cw; } diff --git a/src/v_video.h b/src/v_video.h index 9564bca15..35be68b2f 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -84,6 +84,9 @@ void V_CubeApply(RGBA_t *input); // Bottom 8 bits are used for parameter (screen or character) #define V_PARAMMASK 0x000000FF +// strings/characters only +#define V_STRINGDANCE 0x00000002 + // flags hacked in scrn (not supported by all functions (see src)) // patch scaling uses bits 9 and 10 #define V_SCALEPATCHSHIFT 8 @@ -220,6 +223,8 @@ void V_DrawPromptBack(INT32 boxheight, INT32 color); #define V__IntegerStringWidth( scale,option,font,string ) \ (V_StringScaledWidth(scale,FRACUNIT,FRACUNIT,option,font,string) / FRACUNIT) +INT32 V_DanceYOffset(INT32 counter); + // draw a single character void V_DrawCharacter(INT32 x, INT32 y, INT32 c, boolean lowercaseallowed); // draw a single character, but for the chat