diff --git a/src/Makefile.cfg b/src/Makefile.cfg index a3baeedda..43e659d75 100644 --- a/src/Makefile.cfg +++ b/src/Makefile.cfg @@ -134,6 +134,9 @@ endif ifndef GCC295 WFLAGS+=-Wendif-labels endif +ifdef GCC40 + WFLAGS+=-std=gnu89 +endif ifdef GCC41 WFLAGS+=-Wshadow endif diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 3ce214aab..2f8bb0c5c 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -116,6 +116,16 @@ static void Skin_OnChange(void); static void Skin2_OnChange(void); static void Skin3_OnChange(void); static void Skin4_OnChange(void); + +static void Follower_OnChange(void); +static void Follower2_OnChange(void); +static void Follower3_OnChange(void); +static void Follower4_OnChange(void); +static void Followercolor_OnChange(void); +static void Followercolor2_OnChange(void); +static void Followercolor3_OnChange(void); +static void Followercolor4_OnChange(void); + static void Color_OnChange(void); static void Color2_OnChange(void); static void Color3_OnChange(void); @@ -292,6 +302,23 @@ consvar_t cv_skin2 = {"skin2", DEFAULTSKIN2, CV_SAVE|CV_CALL|CV_NOINIT, NULL, Sk consvar_t cv_skin3 = {"skin3", DEFAULTSKIN3, CV_SAVE|CV_CALL|CV_NOINIT, NULL, Skin3_OnChange, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_skin4 = {"skin4", DEFAULTSKIN4, CV_SAVE|CV_CALL|CV_NOINIT, NULL, Skin4_OnChange, 0, NULL, NULL, 0, 0, NULL}; +// player's followers. Also saved. +consvar_t cv_follower = {"follower", "-1", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Follower_OnChange, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_follower2 = {"follower2", "-1", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Follower2_OnChange, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_follower3 = {"follower3", "-1", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Follower3_OnChange, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_follower4 = {"follower4", "-1", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Follower4_OnChange, 0, NULL, NULL, 0, 0, NULL}; + +// player's follower colors... Also saved... +consvar_t cv_followercolor = {"followercolor", "Match", CV_SAVE|CV_CALL|CV_NOINIT, Followercolor_cons_t, Followercolor_OnChange, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_followercolor2 = {"followercolor2", "Match", CV_SAVE|CV_CALL|CV_NOINIT, Followercolor_cons_t, Followercolor2_OnChange, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_followercolor3 = {"followercolor3", "Match", CV_SAVE|CV_CALL|CV_NOINIT, Followercolor_cons_t, Followercolor3_OnChange, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_followercolor4 = {"followercolor4", "Match", CV_SAVE|CV_CALL|CV_NOINIT, Followercolor_cons_t, Followercolor4_OnChange, 0, NULL, NULL, 0, 0, NULL}; + + +// Follower toggle +static CV_PossibleValue_t followers_cons_t[] = {{0, "Yours only"}, {1, "Everyone's"}, {0, NULL}}; +consvar_t cv_showfollowers = {"showfollowers", "Everyone's", CV_SAVE, followers_cons_t, 0, 0, NULL, NULL, 0, 0, NULL}; + consvar_t cv_skipmapcheck = {"skipmapcheck", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; INT32 cv_debug; @@ -804,11 +831,17 @@ void D_RegisterClientCommands(void) for (i = 0; i < MAXSKINCOLORS; i++) { - Color_cons_t[i].value = i; - Color_cons_t[i].strvalue = KartColor_Names[i]; // SRB2kart + Color_cons_t[i].value = Followercolor_cons_t[i].value = i; + Color_cons_t[i].strvalue = Followercolor_cons_t[i].strvalue = KartColor_Names[i]; // SRB2kart } - Color_cons_t[MAXSKINCOLORS].value = 0; - Color_cons_t[MAXSKINCOLORS].strvalue = NULL; + Color_cons_t[MAXSKINCOLORS].value = Followercolor_cons_t[MAXSKINCOLORS+2].value = 0; + Color_cons_t[MAXSKINCOLORS].strvalue = Followercolor_cons_t[MAXSKINCOLORS+2].strvalue = NULL; + + Followercolor_cons_t[MAXSKINCOLORS].value = MAXSKINCOLORS; + Followercolor_cons_t[MAXSKINCOLORS].strvalue = "Match"; // Add "Match" option, which will make the follower color match the player's + + Followercolor_cons_t[MAXSKINCOLORS+1].value = MAXSKINCOLORS+1; + Followercolor_cons_t[MAXSKINCOLORS+1].strvalue = "Opposite"; // Add "Opposite" option, ...which is like "Match", but for coloropposite. if (dedicated) return; @@ -880,18 +913,27 @@ void D_RegisterClientCommands(void) CV_RegisterVar(&cv_playername); CV_RegisterVar(&cv_playercolor); CV_RegisterVar(&cv_skin); // r_things.c (skin NAME) + CV_RegisterVar(&cv_follower); + CV_RegisterVar(&cv_followercolor); + CV_RegisterVar(&cv_showfollowers); // secondary player (splitscreen) CV_RegisterVar(&cv_playername2); CV_RegisterVar(&cv_playercolor2); CV_RegisterVar(&cv_skin2); + CV_RegisterVar(&cv_follower2); + CV_RegisterVar(&cv_followercolor2); // third player CV_RegisterVar(&cv_playername3); CV_RegisterVar(&cv_playercolor3); CV_RegisterVar(&cv_skin3); + CV_RegisterVar(&cv_follower3); + CV_RegisterVar(&cv_followercolor3); // fourth player CV_RegisterVar(&cv_playername4); CV_RegisterVar(&cv_playercolor4); CV_RegisterVar(&cv_skin4); + CV_RegisterVar(&cv_follower4); + CV_RegisterVar(&cv_followercolor4); // preferred number of players CV_RegisterVar(&cv_splitplayers); @@ -1449,7 +1491,7 @@ static INT32 snacpending = 0, snac2pending = 0, snac3pending = 0, snac4pending = // static void SendNameAndColor(void) { - XBOXSTATIC char buf[MAXPLAYERNAME+2]; + XBOXSTATIC char buf[MAXPLAYERNAME+3]; char *p; p = buf; @@ -1474,10 +1516,13 @@ static void SendNameAndColor(void) CV_StealthSet(&cv_playercolor, cv_playercolor.defaultvalue); } - if (!strcmp(cv_playername.string, player_names[consoleplayer]) - && cv_playercolor.value == players[consoleplayer].skincolor - && !strcmp(cv_skin.string, skins[players[consoleplayer].skin].name)) - return; + // ditto for follower colour: + if (!cv_followercolor.value) + CV_StealthSet(&cv_followercolor, "Match"); // set it to "Match". I don't care about your stupidity! + + // so like, this is sent before we even use anything like cvars or w/e so it's possible that follower is set to a pretty yikes value, so let's fix that before we send garbage that could crash the game: + if (cv_follower.value > numfollowers-1 || cv_follower.value < -1) + CV_StealthSet(&cv_follower, "-1"); // We'll handle it later if we're not playing. if (!Playing()) @@ -1496,6 +1541,10 @@ static void SendNameAndColor(void) if (players[consoleplayer].mo) players[consoleplayer].mo->color = players[consoleplayer].skincolor; + // Update follower for local games: + if (cv_follower.value >= -1 && cv_follower.value != players[consoleplayer].followerskin) + SetFollower(consoleplayer, cv_follower.value); + if (metalrecording) { // Metal Sonic is Sonic, obviously. SetPlayerSkinByNum(consoleplayer, 0); @@ -1559,6 +1608,8 @@ static void SendNameAndColor(void) WRITESTRINGN(p, cv_playername.zstring, MAXPLAYERNAME); WRITEUINT8(p, (UINT8)cv_playercolor.value); WRITEUINT8(p, (UINT8)cv_skin.value); + WRITESINT8(p, (UINT8)cv_follower.value); + WRITESINT8(p, (UINT8)cv_followercolor.value); SendNetXCmd(XD_NAMEANDCOLOR, buf, p - buf); } @@ -1566,7 +1617,7 @@ static void SendNameAndColor(void) static void SendNameAndColor2(void) { INT32 secondplaya = -1; - XBOXSTATIC char buf[MAXPLAYERNAME+2]; + XBOXSTATIC char buf[MAXPLAYERNAME+3]; char *p; if (splitscreen < 1) @@ -1602,6 +1653,14 @@ static void SendNameAndColor2(void) CV_StealthSet(&cv_playercolor2, cv_playercolor2.defaultvalue); } + // ditto for follower colour: + if (!cv_followercolor2.value) + CV_StealthSet(&cv_followercolor2, "Match"); // set it to "Match". I don't care about your stupidity! + + // so like, this is sent before we even use anything like cvars or w/e so it's possible that follower is set to a pretty yikes value, so let's fix that before we send garbage that could crash the game: + if (cv_follower2.value > numfollowers-1 || cv_follower2.value < -1) + CV_StealthSet(&cv_follower2, "-1"); + // We'll handle it later if we're not playing. if (!Playing()) return; @@ -1619,6 +1678,10 @@ static void SendNameAndColor2(void) if (players[secondplaya].mo) players[secondplaya].mo->color = players[secondplaya].skincolor; + // Update follower for local games: + if (cv_follower2.value >= -1 && cv_follower2.value != players[secondplaya].followerskin) + SetFollower(secondplaya, cv_follower2.value); + if ((foundskin = R_SkinAvailable(cv_skin2.string)) != -1) { //boolean notsame; @@ -1675,13 +1738,15 @@ static void SendNameAndColor2(void) WRITESTRINGN(p, cv_playername2.zstring, MAXPLAYERNAME); WRITEUINT8(p, (UINT8)cv_playercolor2.value); WRITEUINT8(p, (UINT8)cv_skin2.value); + WRITESINT8(p, (UINT8)cv_follower2.value); + WRITESINT8(p, (UINT8)cv_followercolor2.value); SendNetXCmd2(XD_NAMEANDCOLOR, buf, p - buf); } static void SendNameAndColor3(void) { INT32 thirdplaya = -1; - XBOXSTATIC char buf[MAXPLAYERNAME+2]; + XBOXSTATIC char buf[MAXPLAYERNAME+3]; char *p; if (splitscreen < 2) @@ -1706,6 +1771,10 @@ static void SendNameAndColor3(void) CV_StealthSetValue(&cv_playercolor3, skincolor_blueteam); } + // ditto for follower colour: + if (!cv_followercolor3.value) + CV_StealthSet(&cv_followercolor3, "Match"); // set it to "Match". I don't care about your stupidity! + // never allow the color "none" if (!cv_playercolor3.value) { @@ -1717,6 +1786,10 @@ static void SendNameAndColor3(void) CV_StealthSet(&cv_playercolor3, cv_playercolor3.defaultvalue); } + // so like, this is sent before we even use anything like cvars or w/e so it's possible that follower is set to a pretty yikes value, so let's fix that before we send garbage that could crash the game: + if (cv_follower3.value > numfollowers-1 || cv_follower3.value < -1) + CV_StealthSet(&cv_follower3, "-1"); + // We'll handle it later if we're not playing. if (!Playing()) return; @@ -1734,6 +1807,10 @@ static void SendNameAndColor3(void) if (players[thirdplaya].mo) players[thirdplaya].mo->color = players[thirdplaya].skincolor; + // Update follower for local games: + if (cv_follower3.value >= -1 && cv_follower3.value != players[thirdplaya].followerskin) + SetFollower(thirdplaya, cv_follower3.value); + if ((foundskin = R_SkinAvailable(cv_skin3.string)) != -1) { //boolean notsame; @@ -1790,13 +1867,15 @@ static void SendNameAndColor3(void) WRITESTRINGN(p, cv_playername3.zstring, MAXPLAYERNAME); WRITEUINT8(p, (UINT8)cv_playercolor3.value); WRITEUINT8(p, (UINT8)cv_skin3.value); + WRITESINT8(p, (UINT8)cv_follower3.value); + WRITESINT8(p, (UINT8)cv_followercolor3.value); SendNetXCmd3(XD_NAMEANDCOLOR, buf, p - buf); } static void SendNameAndColor4(void) { INT32 fourthplaya = -1; - XBOXSTATIC char buf[MAXPLAYERNAME+2]; + XBOXSTATIC char buf[MAXPLAYERNAME+3]; char *p; if (splitscreen < 3) @@ -1821,6 +1900,10 @@ static void SendNameAndColor4(void) CV_StealthSetValue(&cv_playercolor4, skincolor_blueteam); } + // ditto for follower colour: + if (!cv_followercolor4.value) + CV_StealthSet(&cv_followercolor4, "Match"); // set it to "Match". I don't care about your stupidity! + // never allow the color "none" if (!cv_playercolor4.value) { @@ -1832,6 +1915,10 @@ static void SendNameAndColor4(void) CV_StealthSet(&cv_playercolor4, cv_playercolor4.defaultvalue); } + // so like, this is sent before we even use anything like cvars or w/e so it's possible that follower is set to a pretty yikes value, so let's fix that before we send garbage that could crash the game: + if (cv_follower4.value > numfollowers-1 || cv_follower4.value < -1) + CV_StealthSet(&cv_follower4, "-1"); + // We'll handle it later if we're not playing. if (!Playing()) return; @@ -1849,6 +1936,10 @@ static void SendNameAndColor4(void) if (players[fourthplaya].mo) players[fourthplaya].mo->color = players[fourthplaya].skincolor; + // Update follower for local games: + if (cv_follower4.value >= -1 && cv_follower4.value != players[fourthplaya].followerskin) + SetFollower(fourthplaya, cv_follower4.value); + if ((foundskin = R_SkinAvailable(cv_skin4.string)) != -1) { //boolean notsame; @@ -1905,6 +1996,8 @@ static void SendNameAndColor4(void) WRITESTRINGN(p, cv_playername4.zstring, MAXPLAYERNAME); WRITEUINT8(p, (UINT8)cv_playercolor4.value); WRITEUINT8(p, (UINT8)cv_skin4.value); + WRITESINT8(p, (UINT8)cv_follower4.value); + WRITESINT8(p, (UINT8)cv_followercolor4.value); SendNetXCmd4(XD_NAMEANDCOLOR, buf, p - buf); } @@ -1912,7 +2005,8 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) { player_t *p = &players[playernum]; char name[MAXPLAYERNAME+1]; - UINT8 color, skin; + UINT8 color, skin, followercolor; + SINT8 follower; #ifdef PARANOIA if (playernum < 0 || playernum > MAXPLAYERS) @@ -1936,6 +2030,8 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) READSTRINGN(*cp, name, MAXPLAYERNAME); color = READUINT8(*cp); skin = READUINT8(*cp); + follower = READSINT8(*cp); + followercolor = READSINT8(*cp); // set name if (strcasecmp(player_names[playernum], name) != 0) @@ -1995,6 +2091,13 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) } else SetPlayerSkinByNum(playernum, skin); + + // set follower colour: + // Don't bother doing garbage and kicking if we receive None, this is both silly and a waste of time, this will be handled properly in P_HandleFollower. + p->followercolor = followercolor; + + // set follower + SetFollower(playernum, follower); } void SendWeaponPref(void) @@ -2815,6 +2918,7 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pencoremode, boolean r } chmappending++; + if (netgame) WRITEUINT32(buf_p, M_RandomizedSeed()); // random seed SendNetXCmd(XD_MAP, buf, buf_p - buf); @@ -6034,6 +6138,192 @@ static void Name4_OnChange(void) SendNameAndColor4(); } +// sends the follower change for players +static void Follower_OnChange(void) +{ + char str[SKINNAMESIZE+1], cpy[SKINNAMESIZE+1]; + INT32 num; + char set[10]; // This isn't Lua and mixed declarations in the middle of code make caveman compilers scream. + + // there is a slight chance that we will actually use a string instead so... + // let's investigate the string... + strcpy(str, cv_follower.string); + strcpy(cpy, cv_follower.string); + strlwr(str); + if (stricmp(cpy,"0") !=0 && !atoi(cpy)) // yep, that's a string alright... + { + if (stricmp(cpy, "None") == 0) + { + CV_StealthSet(&cv_follower, "-1"); + + if (!Playing()) + return; // don't send anything there. + + SendNameAndColor(); + return; + } + + num = R_FollowerAvailable(str); + + if (num == -1) // that's an error. + CONS_Alert(CONS_WARNING, M_GetText("Follower '%s' not found\n"), str); + + sprintf(set, "%d", num); + CV_StealthSet(&cv_follower, set); // set it to a number. It's easier for us to send later :) + } + + if (!Playing()) + return; // don't send anything there. + + SendNameAndColor(); +} + +// About the same as Color_OnChange but for followers. +static void Followercolor_OnChange(void) +{ + + if (!Playing()) + return; // do whatever you want if you aren't in the game or don't have a follower. + + if (!P_PlayerMoving(consoleplayer)) + { + // Color change menu scrolling fix is no longer necessary + SendNameAndColor(); + } +} + +// repeat for the 3 other players + +static void Follower2_OnChange(void) +{ + char str[SKINNAMESIZE+1], cpy[SKINNAMESIZE+1]; + if (!Playing() || !splitscreen) + return; // do whatever you want + + strcpy(str, cv_follower2.string); + strcpy(cpy, cv_follower2.string); + strlwr(str); + if (stricmp(cpy,"0") !=0 && !atoi(cpy)) // yep, that's a string alright... + { + + if (stricmp(cpy, "None") == 0) + { + CV_StealthSet(&cv_follower2, "-1"); + SendNameAndColor2(); + return; + } + + + INT32 num = R_FollowerAvailable(str); + char set[10]; + if (num == -1) // that's an error. + CONS_Alert(CONS_WARNING, M_GetText("Follower '%s' not found\n"), str); + + sprintf(set, "%d", num); + CV_StealthSet(&cv_follower2, set); // set it to a number. It's easier for us to send later :) + } + SendNameAndColor2(); +} + +static void Followercolor2_OnChange(void) +{ + + if (!Playing()) + return; // do whatever you want if you aren't in the game or don't have a follower. + + if (!P_PlayerMoving(g_localplayers[1])) + { + // Color change menu scrolling fix is no longer necessary + SendNameAndColor2(); + } +} + +static void Follower3_OnChange(void) +{ + char str[SKINNAMESIZE+1], cpy[SKINNAMESIZE+1]; + if (!Playing() || !splitscreen) + return; // do whatever you want + + strcpy(str, cv_follower3.string); + strcpy(cpy, cv_follower3.string); + strlwr(str); + if (stricmp(cpy,"0") !=0 && !atoi(cpy)) // yep, that's a string alright... + { + + if (stricmp(cpy, "None") == 0) + { + CV_StealthSet(&cv_follower3, "-1"); + SendNameAndColor3(); + return; + } + + INT32 num = R_FollowerAvailable(str); + char set[10]; + if (num == -1) // that's an error. + CONS_Alert(CONS_WARNING, M_GetText("Follower '%s' not found\n"), str); + + sprintf(set, "%d", num); + CV_StealthSet(&cv_follower3, set); // set it to a number. It's easier for us to send later :) + } + SendNameAndColor3(); +} + +static void Followercolor3_OnChange(void) +{ + + if (!Playing()) + return; // do whatever you want if you aren't in the game or don't have a follower. + + if (!P_PlayerMoving(g_localplayers[2])) + { + // Color change menu scrolling fix is no longer necessary + SendNameAndColor3(); + } +} + +static void Follower4_OnChange(void) +{ + char str[SKINNAMESIZE+1], cpy[SKINNAMESIZE+1]; + if (!Playing() || !splitscreen) + return; // do whatever you want + + strcpy(str, cv_follower4.string); + strcpy(cpy, cv_follower4.string); + strlwr(str); + if (stricmp(cpy,"0") !=0 && !atoi(cpy)) // yep, that's a string alright... + { + + if (stricmp(cpy, "None") == 0) + { + CV_StealthSet(&cv_follower4, "-1"); + SendNameAndColor4(); + return; + } + + INT32 num = R_FollowerAvailable(str); + char set[10]; + if (num == -1) // that's an error. + CONS_Alert(CONS_WARNING, M_GetText("Follower '%s' not found\n"), str); + + sprintf(set, "%d", num); + CV_StealthSet(&cv_follower4, set); // set it to a number. It's easier for us to send later :) + } + SendNameAndColor4(); +} + +static void Followercolor4_OnChange(void) +{ + + if (!Playing()) + return; // do whatever you want if you aren't in the game or don't have a follower. + + if (!P_PlayerMoving(g_localplayers[3])) + { + // Color change menu scrolling fix is no longer necessary + SendNameAndColor4(); + } +} + /** Sends a skin change for the console player, unless that player is moving. * \sa cv_skin, Skin2_OnChange, Color_OnChange * \author Graue diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 5e13f56da..56690c130 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -21,18 +21,23 @@ extern consvar_t cv_playername; extern consvar_t cv_playercolor; extern consvar_t cv_skin; +extern consvar_t cv_follower; +extern consvar_t cv_showfollowers; // secondary splitscreen player extern consvar_t cv_playername2; extern consvar_t cv_playercolor2; extern consvar_t cv_skin2; +extern consvar_t cv_follower2; // third splitscreen player extern consvar_t cv_playername3; extern consvar_t cv_playercolor3; extern consvar_t cv_skin3; +extern consvar_t cv_follower3; // fourth splitscreen player extern consvar_t cv_playername4; extern consvar_t cv_playercolor4; extern consvar_t cv_skin4; +extern consvar_t cv_follower4; // preferred number of players extern consvar_t cv_splitplayers; diff --git a/src/d_player.h b/src/d_player.h index 6a06c7493..4ae420052 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -513,6 +513,12 @@ typedef struct player_s // SRB2kart UINT8 kartspeed; // Kart speed stat between 1 and 9 UINT8 kartweight; // Kart weight stat between 1 and 9 + + INT32 followerskin; // Kart: This player's follower "skin" + boolean followerready; // Kart: Used to know when we can have a follower or not. (This is set on the first NameAndColor follower update) + UINT8 followercolor; // Kart: Used to store the follower colour the player wishes to use + mobj_t *follower; // Kart: This is the follower object we have. (If any) + // UINT32 charflags; // Extra abilities/settings for skins (combinable stuff) diff --git a/src/dehacked.c b/src/dehacked.c index a0efc5cb2..d8a3f6d01 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -34,6 +34,7 @@ #include "lua_script.h" #include "lua_hook.h" #include "d_clisrv.h" +#include "r_things.h" // for followers #include "m_cond.h" @@ -674,6 +675,271 @@ static void readfreeslots(MYFILE *f) Z_Free(s); } +// This here is our current only way to make followers. +INT32 numfollowers = 0; + +static void readfollower(MYFILE *f) +{ + char *s; + char *word, *word2, dname[SKINNAMESIZE+1]; + char *tmp; + char testname[SKINNAMESIZE]; + + boolean nameset; + INT32 fallbackstate = 0; + INT32 res; + INT32 i; + + if (numfollowers > MAXSKINS) + { + deh_warning("Error: Too many followers, cannot add anymore.\n"); + return; + } + + s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); + + // Ready the default variables for followers. We will overwrite them as we go! We won't set the name or states RIGHT HERE as this is handled down instead. + followers[numfollowers].scale = FRACUNIT; + followers[numfollowers].bubblescale = 0; // No bubble by default + followers[numfollowers].atangle = 230; + followers[numfollowers].dist = 32; // changed from 16 to 32 to better account for ogl models + followers[numfollowers].height = 16; + followers[numfollowers].zoffs = 32; + followers[numfollowers].horzlag = 2; + followers[numfollowers].vertlag = 6; + followers[numfollowers].bobspeed = TICRATE*2; + followers[numfollowers].bobamp = 4; + followers[numfollowers].hitconfirmtime = TICRATE; + followers[numfollowers].defaultcolor = 1; + + do + { + if (myfgets(s, MAXLINELEN, f)) + { + if (s[0] == '\n') + break; + + tmp = strchr(s, '#'); + if (tmp) + *tmp = '\0'; + if (s == tmp) + continue; // Skip comment lines, but don't break. + + word = strtok(s, " "); + if (word) + strupr(word); + else + break; + + word2 = strtok(NULL, " = "); + + if (!word2) + break; + + if (word2[strlen(word2)-1] == '\n') + word2[strlen(word2)-1] = '\0'; + + if (fastcmp(word, "NAME")) + { + DEH_WriteUndoline(word, va("%s", followers[numfollowers].name), UNDO_NONE); + strcpy(followers[numfollowers].name, word2); + nameset = true; + } + else if (fastcmp(word, "DEFAULTCOLOR")) + { + DEH_WriteUndoline(word, va("%d", followers[numfollowers].defaultcolor), UNDO_NONE); + followers[numfollowers].defaultcolor = (UINT8)get_number(word2); + } + + else if (fastcmp(word, "SCALE")) + { + DEH_WriteUndoline(word, va("%d", followers[numfollowers].scale), UNDO_NONE); + followers[numfollowers].scale = get_number(word2); + } + else if (fastcmp(word, "BUBBLESCALE")) + { + DEH_WriteUndoline(word, va("%d", followers[numfollowers].bubblescale), UNDO_NONE); + followers[numfollowers].bubblescale = get_number(word2); + } + else if (fastcmp(word, "ATANGLE")) + { + DEH_WriteUndoline(word, va("%d", followers[numfollowers].atangle), UNDO_NONE); + followers[numfollowers].atangle = (INT32)atoi(word2); + } + else if (fastcmp(word, "HORZLAG")) + { + DEH_WriteUndoline(word, va("%d", followers[numfollowers].horzlag), UNDO_NONE); + followers[numfollowers].horzlag = (INT32)atoi(word2); + } + else if (fastcmp(word, "VERTLAG")) + { + DEH_WriteUndoline(word, va("%d", followers[numfollowers].vertlag), UNDO_NONE); + followers[numfollowers].vertlag = (INT32)atoi(word2); + } + else if (fastcmp(word, "BOBSPEED")) + { + DEH_WriteUndoline(word, va("%d", followers[numfollowers].bobspeed), UNDO_NONE); + followers[numfollowers].bobspeed = (INT32)atoi(word2); + } + else if (fastcmp(word, "BOBAMP")) + { + DEH_WriteUndoline(word, va("%d", followers[numfollowers].bobamp), UNDO_NONE); + followers[numfollowers].bobamp = (INT32)atoi(word2); + } + else if (fastcmp(word, "ZOFFSET") || (fastcmp(word, "ZOFFS"))) + { + DEH_WriteUndoline(word, va("%d", followers[numfollowers].zoffs), UNDO_NONE); + followers[numfollowers].zoffs = (INT32)atoi(word2); + } + else if (fastcmp(word, "DISTANCE") || (fastcmp(word, "DIST"))) + { + DEH_WriteUndoline(word, va("%d", followers[numfollowers].dist), UNDO_NONE); + followers[numfollowers].dist = (INT32)atoi(word2); + } + else if (fastcmp(word, "HEIGHT")) + { + DEH_WriteUndoline(word, va("%d", followers[numfollowers].height), UNDO_NONE); + followers[numfollowers].height = (INT32)atoi(word2); + } + else if (fastcmp(word, "IDLESTATE")) + { + if (word2) + strupr(word2); + DEH_WriteUndoline(word, va("%d", followers[numfollowers].idlestate), UNDO_NONE); + followers[numfollowers].idlestate = get_number(word2); + fallbackstate = followers[numfollowers].idlestate; + } + else if (fastcmp(word, "FOLLOWSTATE")) + { + if (word2) + strupr(word2); + DEH_WriteUndoline(word, va("%d", followers[numfollowers].followstate), UNDO_NONE); + followers[numfollowers].followstate = get_number(word2); + } + else if (fastcmp(word, "HURTSTATE")) + { + if (word2) + strupr(word2); + DEH_WriteUndoline(word, va("%d", followers[numfollowers].hurtstate), UNDO_NONE); + followers[numfollowers].hurtstate = get_number(word2); + } + else if (fastcmp(word, "LOSESTATE")) + { + if (word2) + strupr(word2); + DEH_WriteUndoline(word, va("%d", followers[numfollowers].losestate), UNDO_NONE); + followers[numfollowers].losestate = get_number(word2); + } + else if (fastcmp(word, "WINSTATE")) + { + if (word2) + strupr(word2); + DEH_WriteUndoline(word, va("%d", followers[numfollowers].winstate), UNDO_NONE); + followers[numfollowers].winstate = get_number(word2); + } + else if (fastcmp(word, "HITSTATE") || (fastcmp(word, "HITCONFIRMSTATE"))) + { + if (word2) + strupr(word2); + DEH_WriteUndoline(word, va("%d", followers[numfollowers].hitconfirmstate), UNDO_NONE); + followers[numfollowers].hitconfirmstate = get_number(word2); + } + else if (fastcmp(word, "HITTIME") || (fastcmp(word, "HITCONFIRMTIME"))) + { + DEH_WriteUndoline(word, va("%d", followers[numfollowers].hitconfirmtime), UNDO_NONE); + followers[numfollowers].hitconfirmtime = (INT32)atoi(word2); + } + else + deh_warning("Follower %d: unknown word '%s'", numfollowers, word); + } + } while (!myfeof(f)); // finish when the line is empty + + if (!nameset) // well this is problematic. + { + strcpy(followers[numfollowers].name, va("Follower%d", numfollowers)); // this is lazy, so what + } + + // set skin name (this is just the follower's name in lowercases): + // but before we do, let's... actually check if another follower isn't doing the same shit... + + strcpy(testname, followers[numfollowers].name); + + // lower testname for skin checks... + strlwr(testname); + res = R_FollowerAvailable(testname); + if (res > -1) // yikes, someone else has stolen our name already + { + INT32 startlen = strlen(testname); + char cpy[2]; + //deh_warning("There was already a follower with the same name. (%s)", testname); This warning probably isn't necessary anymore? + sprintf(cpy, "%d", numfollowers); + memcpy(&testname[startlen], cpy, 2); + // in that case, we'll be very lazy and copy numfollowers to the end of our skin name. + } + + strcpy(followers[numfollowers].skinname, testname); + strcpy(dname, followers[numfollowers].skinname); // display name, just used for printing succesful stuff or errors later down the line. + + // now that the skin name is ready, post process the actual name to turn the underscores into spaces! + for (i = 0; followers[numfollowers].name[i]; i++) + { + if (followers[numfollowers].name[i] == '_') + followers[numfollowers].name[i] = ' '; + } + + // fallbacks for variables + // Print a warning if the variable is on a weird value and set it back to the minimum available if that's the case. +#define FALLBACK(field, field2, threshold, set) \ +if (followers[numfollowers].field < threshold) \ +{ \ + followers[numfollowers].field = set; \ + deh_warning("Follower '%s': Value for '%s' is too low! Minimum should be %d. Value was overwritten to %d.", dname, field2, set, set); \ +} \ + + FALLBACK(dist, "DIST", 0, 0); + FALLBACK(height, "HEIGHT", 1, 1); + FALLBACK(zoffs, "ZOFFS", 0, 0); + FALLBACK(horzlag, "HORZLAG", 1, 1); + FALLBACK(vertlag, "VERTLAG", 1, 1); + FALLBACK(bobamp, "BOBAMP", 0, 0); + FALLBACK(bobspeed, "BOBSPEED", 0, 0); + FALLBACK(hitconfirmtime, "HITCONFIRMTIME", 1, 1); + FALLBACK(scale, "SCALE", 1, 1); // No null/negative scale + FALLBACK(bubblescale, "BUBBLESCALE", 0, 0); // No negative scale + + // Special case for color I suppose + if (followers[numfollowers].defaultcolor < 0 || followers[numfollowers].defaultcolor > MAXSKINCOLORS-1) + { + followers[numfollowers].defaultcolor = 1; + deh_warning("Follower \'%s\': Value for 'color' should be between 1 and %d.\n", dname, MAXSKINCOLORS-1); + } + +#undef FALLBACK + + // also check if we forgot states. If we did, we will set any missing state to the follower's idlestate. + // Print a warning in case we don't have a fallback and set the state to S_INVISIBLE (rather than S_NULL) if unavailable. + +#define NOSTATE(field, field2) \ +if (!followers[numfollowers].field) \ +{ \ + followers[numfollowers].field = fallbackstate ? fallbackstate : S_INVISIBLE; \ + if (!fallbackstate) \ + deh_warning("Follower '%s' is missing state definition for '%s', no idlestate fallback was found", dname, field2); \ +} \ + + NOSTATE(idlestate, "IDLESTATE"); + NOSTATE(followstate, "FOLLOWSTATE"); + NOSTATE(hurtstate, "HURTSTATE"); + NOSTATE(losestate, "LOSESTATE"); + NOSTATE(winstate, "WINSTATE"); + NOSTATE(hitconfirmstate, "HITCONFIRMSTATE"); +#undef NOSTATE + + CONS_Printf("Added follower '%s'\n", dname); + numfollowers++; // add 1 follower + Z_Free(s); +} + static void readthing(MYFILE *f, INT32 num) { char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); @@ -3418,6 +3684,13 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad) // This is not a major mod. continue; } + else if (fastcmp(word, "FOLLOWER")) + { + readfollower(f); // at the same time this will be our only way to ADD followers for now. Yikes. + DEH_WriteUndoline(word, "", UNDO_HEADER); + // This is not a major mod either. + continue; // continue so that we don't error. + } word2 = strtok(NULL, " "); if (fastcmp(word, "CHARACTER")) { @@ -7207,6 +7480,31 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit "S_OPAQUESMOKE4", "S_OPAQUESMOKE5", + "S_FOLLOWERBUBBLE_FRONT", + "S_FOLLOWERBUBBLE_BACK", + + "S_GCHAOIDLE", + "S_GCHAOFLY", + "S_GCHAOSAD1", + "S_GCHAOSAD2", + "S_GCHAOSAD3", + "S_GCHAOSAD4", + "S_GCHAOHAPPY1", + "S_GCHAOHAPPY2", + "S_GCHAOHAPPY3", + "S_GCHAOHAPPY4", + + "S_CHEESEIDLE", + "S_CHEESEFLY", + "S_CHEESESAD1", + "S_CHEESESAD2", + "S_CHEESESAD3", + "S_CHEESESAD4", + "S_CHEESEHAPPY1", + "S_CHEESEHAPPY2", + "S_CHEESEHAPPY3", + "S_CHEESEHAPPY4", + "S_RINGDEBT", "S_RINGSPARKS1", "S_RINGSPARKS2", @@ -8074,6 +8372,10 @@ static const char *const MOBJTYPE_LIST[] = { // array length left dynamic for s "MT_BATTLECAPSULE", "MT_BATTLECAPSULE_PIECE", + "MT_FOLLOWER", + "MT_FOLLOWERBUBBLE_FRONT", + "MT_FOLLOWERBUBBLE_BACK", + "MT_WATERTRAIL", "MT_WATERTRAILUNDERLAY", diff --git a/src/g_game.c b/src/g_game.c index 47b2c6808..1b994ede1 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2570,6 +2570,10 @@ void G_PlayerReborn(INT32 player) // SRB2kart UINT8 kartspeed; UINT8 kartweight; + boolean followerready; + INT32 followerskin; + UINT8 followercolor; + mobj_t *follower; // old follower, will probably be removed by the time we're dead but you never know. // INT32 charflags; INT32 pflags; @@ -2630,6 +2634,10 @@ void G_PlayerReborn(INT32 player) // SRB2kart kartspeed = players[player].kartspeed; kartweight = players[player].kartweight; + follower = players[player].follower; + followerready = players[player].followerready; + followercolor = players[player].followercolor; + followerskin = players[player].followerskin; // charflags = players[player].charflags; @@ -2684,6 +2692,9 @@ void G_PlayerReborn(INT32 player) wanted = players[player].kartstuff[k_wanted]; } + // Obliterate follower from existence + P_SetTarget(&players[player].follower, NULL); + memcpy(&respawn, &players[player].respawn, sizeof (respawn)); p = &players[player]; @@ -2740,6 +2751,16 @@ void G_PlayerReborn(INT32 player) memcpy(&p->respawn, &respawn, sizeof (p->respawn)); + if (follower) + P_RemoveMobj(follower); + + p->followerready = followerready; + p->followerskin = followerskin; + p->followercolor = followercolor; + //p->follower = NULL; // respawn a new one with you, it looks better. + // ^ Not necessary anyway since it will be respawned regardless considering it doesn't exist anymore. + + // Don't do anything immediately p->pflags |= PF_USEDOWN; p->pflags |= PF_ATTACKDOWN; @@ -3207,7 +3228,7 @@ void G_AddPlayer(INT32 playernum) p->jointime = 0; p->playerstate = PST_REBORN; - demo_extradata[playernum] |= DXD_PLAYSTATE|DXD_COLOR|DXD_NAME|DXD_SKIN; // Set everything + demo_extradata[playernum] |= DXD_PLAYSTATE|DXD_COLOR|DXD_NAME|DXD_SKIN|DXD_FOLLOWER; // Set everything } void G_ExitLevel(void) @@ -4936,6 +4957,25 @@ void G_ReadDemoExtraData(void) // Name M_Memcpy(player_names[p],demo_p,16); demo_p += 16; + } + if (extradata & DXD_FOLLOWER) + { + // Set our follower + M_Memcpy(name, demo_p, 16); + demo_p += 16; + SetPlayerFollower(p, name); + + // Follower's color + M_Memcpy(name, demo_p, 16); + demo_p += 16; + for (i = 0; i < MAXSKINCOLORS; i++) + if (!stricmp(KartColor_Names[i], name)) // SRB2kart + { + players[p].followercolor = i; + break; + } + + } if (extradata & DXD_PLAYSTATE) { @@ -5039,6 +5079,7 @@ void G_WriteDemoExtraData(void) WRITEUINT8(demo_p, skins[players[i].skin].kartspeed); WRITEUINT8(demo_p, skins[players[i].skin].kartweight); + } if (demo_extradata[i] & DXD_COLOR) { @@ -5056,6 +5097,21 @@ void G_WriteDemoExtraData(void) M_Memcpy(demo_p,name,16); demo_p += 16; } + if (demo_extradata[i] & DXD_FOLLOWER) + { + // write follower + memset(name, 0, 16); + strncpy(name, followers[players[i].followerskin].skinname, 16); + M_Memcpy(demo_p, name, 16); + demo_p += 16; + + // write follower color + memset(name, 0, 16); + strncpy(name, Followercolor_cons_t[players[i].followercolor].strvalue, 16); // Not KartColor_Names because followercolor has extra values such as "Match" + M_Memcpy(demo_p,name,16); + demo_p += 16; + + } if (demo_extradata[i] & DXD_PLAYSTATE) { demo_writerng = 1; @@ -5656,6 +5712,8 @@ void G_GhostTicker(void) g->p += 16; // Same tbh if (ziptic & DXD_NAME) g->p += 16; // yea + if (ziptic & DXD_FOLLOWER) + g->p += 32; // ok (32 because there's both the skin and the colour) if (ziptic & DXD_PLAYSTATE && READUINT8(g->p) != DXD_PST_PLAYING) I_Error("Ghost is not a record attack ghost"); //@TODO lmao don't blow up like this } @@ -6394,6 +6452,12 @@ void G_BeginRecording(void) CV_SaveNetVars(&demo_p, true); // Now store some info for each in-game player + + // Lat' 12/05/19: Do note that for the first game you load, everything that gets saved here is total garbage; + // The name will always be Player , the skin sonic, the color None and the follower 0. This is only correct on subsequent games. + // In the case of said first game, the skin and the likes are updated with Got_NameAndColor, which are then saved in extradata for the demo with DXD_SKIN in r_things.c for instance. + + for (p = 0; p < MAXPLAYERS; p++) { if (playeringame[p]) { player = &players[p]; @@ -6418,6 +6482,25 @@ void G_BeginRecording(void) M_Memcpy(demo_p,name,16); demo_p += 16; + // Save follower's skin name + // PS: We must check for 'follower' to determine if the followerskin is valid. It's going to be 0 if we don't have a follower, but 0 is also absolutely a valid follower! + // Doesn't really matter if the follower mobj is valid so long as it exists in a way or another. + + memset(name, 0, 16); + if (player->follower) + strncpy(name, followers[player->followerskin].skinname, 16); + else + strncpy(name, "None", 16); // Say we don't have one, then. + + M_Memcpy(demo_p,name,16); + demo_p += 16; + + // Save follower's colour + memset(name, 0, 16); + strncpy(name, Followercolor_cons_t[player->followercolor].strvalue, 16); // Not KartColor_Names because followercolor has extra values such as "Match" + M_Memcpy(demo_p, name, 16); + demo_p += 16; + // Score, since Kart uses this to determine where you start on the map WRITEUINT32(demo_p, player->score); @@ -7017,7 +7100,7 @@ void G_DoPlayDemo(char *defdemoname) { UINT8 i, p; lumpnum_t l; - char skin[17],color[17],*n,*pdemoname; + char skin[17],color[17],follower[17],*n,*pdemoname; UINT8 version,subversion; UINT32 randseed; char msg[1024]; @@ -7031,6 +7114,7 @@ void G_DoPlayDemo(char *defdemoname) skin[16] = '\0'; color[16] = '\0'; + follower[16] = '\0'; // No demo name means we're restarting the current demo if (defdemoname == NULL) @@ -7345,6 +7429,23 @@ void G_DoPlayDemo(char *defdemoname) break; } + // Follower + M_Memcpy(follower, demo_p, 16); + demo_p += 16; + SetPlayerFollower(p, follower); + + // Follower colour + M_Memcpy(color, demo_p, 16); + demo_p += 16; + for (i = 0; i < MAXSKINCOLORS +2; i++) // +2 because of Match and Opposite + { + if (!stricmp(Followercolor_cons_t[i].strvalue, color)) + { + players[p].followercolor = i; + break; + } + } + // Score, since Kart uses this to determine where you start on the map players[p].score = READUINT32(demo_p); @@ -7574,6 +7675,9 @@ void G_AddGhost(char *defdemoname) M_Memcpy(color, p, 16); p += 16; + // Follower data was here, skip it, we don't care about it for ghosts. + p += 32; // followerskin (16) + followercolor (16) + p += 4; // score p += 2; // powerlevel diff --git a/src/g_game.h b/src/g_game.h index 21d32b302..e0f25aa92 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -226,6 +226,7 @@ extern UINT8 demo_writerng; #define DXD_NAME 0x04 // name changed #define DXD_COLOR 0x08 // color changed #define DXD_PLAYSTATE 0x10 // state changed between playing, spectating, or not in-game +#define DXD_FOLLOWER 0x20 // follower was changed #define DXD_PST_PLAYING 0x01 #define DXD_PST_SPECTATING 0x02 diff --git a/src/info.c b/src/info.c index 795885f92..ca367fa75 100644 --- a/src/info.c +++ b/src/info.c @@ -53,6 +53,7 @@ char sprnames[NUMSPRITES + 1][5] = "SRBB","SRBC","SRBD","SRBE","SRBF","SRBG","SRBH","SRBI","SRBJ","SRBK", "SRBL","SRBM","SRBN","SRBO", //SRB2kart Sprites + "RNDM","RPOP","SGNS","FAST","DSHR","BOST","BOSM","KFRE","KINV","KINF", "WIPD","DRIF","BDRF","DUST","DRWS","RSHE","FITM","BANA","ORBN","JAWZ","SSMN", "KRBM","BHOG","BHBM","SPBM","THNS","BUBS","BWVE", @@ -69,8 +70,9 @@ char sprnames[NUMSPRITES + 1][5] = "ICEB","CNDL","DOCH","DUCK","GTRE","CHES","CHIM","DRGN","LZMN","PGSS", "ZTCH","MKMA","MKMP","RTCH","BOWL","BOWH","BRRL","BRRR","HRSE","TOAH", "BFRT","OFRT","RFRT","PFRT","ASPK","HBST","HBSO","HBSF","WBLZ","WBLN", + "FWRK","MXCL","RGSP","DRAF","GRES","OTFG","DBOS","EGOO","WTRL","XMS4", - "XMS5","VIEW" + "XMS5","FBUB","GCHA","CHEZ","VIEW" }; // Doesn't work with g++, needs actionf_p1 (don't modify this comment) @@ -3461,6 +3463,37 @@ state_t states[NUMSTATES] = {SPR_SMOK, 3, 7, {NULL}, 0, 0, S_OPAQUESMOKE5}, // S_OPAQUESMOKE4 {SPR_SMOK, 4, 8, {NULL}, 0, 0, S_NULL}, // S_OPAQUESMOKE5 + + // followers: + + // bubble + {SPR_FBUB, 11|FF_ANIMATE|FF_TRANS70|FF_FULLBRIGHT, -1, {NULL}, 10, 3, S_FOLLOWERBUBBLE_FRONT}, // S_FOLLOWERBUBBLE_FRONT + {SPR_FBUB, FF_ANIMATE|0|FF_FULLBRIGHT, -1, {NULL}, 10, 3, S_FOLLOWERBUBBLE_BACK}, // S_FOLLOWERBUBBLE_BACK + + // generic chao: + {SPR_GCHA, FF_ANIMATE, -1, {NULL}, 1, 4, S_GCHAOIDLE}, //S_GCHAOIDLE + {SPR_GCHA, 2|FF_ANIMATE, -1, {NULL}, 1, 2, S_GCHAOFLY}, //S_GCHAOFLY + {SPR_GCHA, 7, 5, {NULL}, 0, 0, S_GCHAOSAD2}, //S_GCHAOSAD1 + {SPR_GCHA, 8, 3, {NULL}, 0, 0, S_GCHAOSAD3}, //S_GCHAOSAD2 + {SPR_GCHA, 9, 6, {NULL}, 0, 0, S_GCHAOSAD4}, //S_GCHAOSAD3 + {SPR_GCHA, 8, 3, {NULL}, 0, 0, S_GCHAOSAD1}, //S_GCHAOSAD4 + {SPR_GCHA, 4, 8, {NULL}, 0, 0, S_GCHAOHAPPY2}, //S_GCHAOHAPPY1 + {SPR_GCHA, 5, 4, {NULL}, 0, 0, S_GCHAOHAPPY3}, //S_GCHAOHAPPY2 + {SPR_GCHA, 6, 8, {NULL}, 0, 0, S_GCHAOHAPPY4}, //S_GCHAOHAPPY3 + {SPR_GCHA, 5, 4, {NULL}, 0, 0, S_GCHAOHAPPY1}, //S_GCHAOHAPPY4 + + // cheese: + {SPR_CHEZ, FF_ANIMATE, -1, {NULL}, 1, 4, S_CHEESEIDLE}, //S_CHEESEIDLE + {SPR_CHEZ, 2|FF_ANIMATE, -1, {NULL}, 1, 2, S_CHEESEFLY}, //S_CHEESEFLY + {SPR_CHEZ, 7, 5, {NULL}, 0, 0, S_CHEESESAD2}, //S_CHEESESAD1 + {SPR_CHEZ, 8, 3, {NULL}, 0, 0, S_CHEESESAD3}, //S_CHEESESAD2 + {SPR_CHEZ, 9, 6, {NULL}, 0, 0, S_CHEESESAD4}, //S_CHEESESAD3 + {SPR_CHEZ, 8, 3, {NULL}, 0, 0, S_CHEESESAD1}, //S_CHEESESAD4 + {SPR_CHEZ, 4, 8, {NULL}, 0, 0, S_CHEESEHAPPY2}, //S_CHEESEHAPPY1 + {SPR_CHEZ, 5, 4, {NULL}, 0, 0, S_CHEESEHAPPY3}, //S_CHEESEHAPPY2 + {SPR_CHEZ, 6, 8, {NULL}, 0, 0, S_CHEESEHAPPY4}, //S_CHEESEHAPPY3 + {SPR_CHEZ, 5, 4, {NULL}, 0, 0, S_CHEESEHAPPY1}, //S_CHEESEHAPPY4 + {SPR_MXCL, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_RINGDEBT {SPR_RGSP, FF_PAPERSPRITE|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_RINGSPARKS2}, // S_RINGSPARKS1 @@ -20553,6 +20586,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL // raisestate }, + { // MT_RINGSPARKS -1, // doomednum S_RINGSPARKS1, // spawnstate @@ -20796,6 +20830,90 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL // raisestate }, + { // MT_FOLLOWER + -1, // doomednum + S_INVISIBLE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 8<eflags = (mo->eflags & ~MFE_DRAWONLYFORP4)|(master->eflags & MFE_DRAWONLYFORP4); } +// same as above, but does not adjust Z height when flipping +void K_GenericExtraFlagsNoZAdjust(mobj_t *mo, mobj_t *master) +{ + // flipping + mo->eflags = (mo->eflags & ~MFE_VERTICALFLIP)|(master->eflags & MFE_VERTICALFLIP); + mo->flags2 = (mo->flags2 & ~MF2_OBJECTFLIP)|(master->flags2 & MF2_OBJECTFLIP); + + // visibility (usually for hyudoro) + mo->flags2 = (mo->flags2 & ~MF2_DONTDRAW)|(master->flags2 & MF2_DONTDRAW); + mo->eflags = (mo->eflags & ~MFE_DRAWONLYFORP1)|(master->eflags & MFE_DRAWONLYFORP1); + mo->eflags = (mo->eflags & ~MFE_DRAWONLYFORP2)|(master->eflags & MFE_DRAWONLYFORP2); + mo->eflags = (mo->eflags & ~MFE_DRAWONLYFORP3)|(master->eflags & MFE_DRAWONLYFORP3); + mo->eflags = (mo->eflags & ~MFE_DRAWONLYFORP4)|(master->eflags & MFE_DRAWONLYFORP4); +} + + void K_SpawnDashDustRelease(player_t *player) { fixed_t newx; @@ -1899,6 +1915,13 @@ void K_PlayPainSound(mobj_t *source) void K_PlayHitEmSound(mobj_t *source) { + + if (source->player->follower) + { + follower_t fl = followers[source->player->followerskin]; + source->player->follower->movecount = fl.hitconfirmtime; // movecount is used to play the hitconfirm animation for followers. + } + if (cv_kartvoices.value) S_StartSound(source, sfx_khitem); else diff --git a/src/k_kart.h b/src/k_kart.h index af247029d..2508b6f63 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -28,6 +28,7 @@ void K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean solid) void K_KartPainEnergyFling(player_t *player); void K_FlipFromObject(mobj_t *mo, mobj_t *master); void K_MatchGenericExtraFlags(mobj_t *mo, mobj_t *master); +void K_GenericExtraFlagsNoZAdjust(mobj_t *mo, mobj_t *master); void K_SpawnDashDustRelease(player_t *player); void K_KartMoveAnimation(player_t *player); void K_KartPlayerHUDUpdate(player_t *player); diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index 947114e82..1e93e5687 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -239,6 +239,14 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->kartspeed); else if (fastcmp(field,"kartweight")) lua_pushinteger(L, plr->kartweight); + else if (fastcmp(field,"followerskin")) + lua_pushinteger(L, plr->followerskin); + else if (fastcmp(field,"followerready")) + lua_pushboolean(L, plr->followerready); + else if (fastcmp(field,"followercolor")) + lua_pushinteger(L, plr->followercolor); + else if (fastcmp(field,"follower")) + LUA_PushUserdata(L, plr->follower, META_MOBJ); // else if (fastcmp(field,"charflags")) lua_pushinteger(L, plr->charflags); @@ -368,6 +376,7 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->awayviewtics); else if (fastcmp(field,"awayviewaiming")) lua_pushangle(L, plr->awayviewaiming); + else if (fastcmp(field,"spectator")) lua_pushboolean(L, plr->spectator); else if (fastcmp(field,"bot")) @@ -483,6 +492,14 @@ static int player_set(lua_State *L) plr->kartspeed = (UINT8)luaL_checkinteger(L, 3); else if (fastcmp(field,"kartweight")) plr->kartweight = (UINT8)luaL_checkinteger(L, 3); + else if (fastcmp(field,"followerskin")) + plr->followerskin = luaL_checkinteger(L, 3); + else if (fastcmp(field,"followercolor")) + plr->followercolor = luaL_checkinteger(L, 3); + else if (fastcmp(field,"followerready")) + plr->followerready = luaL_checkboolean(L, 3); + else if (fastcmp(field,"follower")) // it's probably best we don't allow the follower mobj to change. + return NOSET; // else if (fastcmp(field,"charflags")) plr->charflags = (UINT32)luaL_checkinteger(L, 3); diff --git a/src/m_menu.c b/src/m_menu.c index 5624f3214..46b7a319d 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -158,6 +158,7 @@ INT16 startmap; // Mario, NiGHTS, or just a plain old normal game? static INT16 itemOn = 1; // menu item skull is on, Hack by Tails 09-18-2002 static INT16 skullAnimCounter = 10; // skull animation counter +static tic_t followertimer = 0; // Used for smooth follower floating static UINT8 setupcontrolplayer; static INT32 (*setupcontrols)[2]; // pointer to the gamecontrols of the player being edited @@ -1032,6 +1033,7 @@ static menuitem_t MP_PlayerSetupMenu[] = { {IT_KEYHANDLER | IT_STRING, NULL, "Name", M_HandleSetupMultiPlayer, 0}, {IT_KEYHANDLER | IT_STRING, NULL, "Character", M_HandleSetupMultiPlayer, 16}, // Tails 01-18-2001 + {IT_KEYHANDLER | IT_STRING, NULL, "Follower", M_HandleSetupMultiPlayer, 26}, {IT_KEYHANDLER | IT_STRING, NULL, "Color", M_HandleSetupMultiPlayer, 152}, }; @@ -1430,24 +1432,27 @@ enum static menuitem_t OP_HUDOptionsMenu[] = { - {IT_STRING | IT_CVAR, NULL, "Show HUD (F3)", &cv_showhud, 10}, - {IT_STRING | IT_CVAR | IT_CV_SLIDER, - NULL, "HUD Visibility", &cv_translucenthud, 20}, - {IT_STRING | IT_SUBMENU, NULL, "Online HUD options...",&OP_ChatOptionsDef, 35}, - {IT_STRING | IT_CVAR, NULL, "Background Glass", &cons_backcolor, 45}, + {IT_STRING | IT_CVAR, NULL, "Show Followers", &cv_showfollowers, 10}, + + {IT_STRING | IT_CVAR, NULL, "Show HUD (F3)", &cv_showhud, 20}, + {IT_STRING | IT_CVAR | IT_CV_SLIDER, + NULL, "HUD Visibility", &cv_translucenthud, 30}, + + {IT_STRING | IT_SUBMENU, NULL, "Online HUD options...",&OP_ChatOptionsDef, 45}, + {IT_STRING | IT_CVAR, NULL, "Background Glass", &cons_backcolor, 55}, {IT_STRING | IT_CVAR | IT_CV_SLIDER, - NULL, "Minimap Visibility", &cv_kartminimap, 60}, - {IT_STRING | IT_CVAR, NULL, "Speedometer Display", &cv_kartspeedometer, 70}, - {IT_STRING | IT_CVAR, NULL, "Show \"CHECK\"", &cv_kartcheck, 80}, + NULL, "Minimap Visibility", &cv_kartminimap, 70}, + {IT_STRING | IT_CVAR, NULL, "Speedometer Display", &cv_kartspeedometer, 80}, + {IT_STRING | IT_CVAR, NULL, "Show \"CHECK\"", &cv_kartcheck, 90}, - {IT_STRING | IT_CVAR, NULL, "Menu Highlights", &cons_menuhighlight, 95}, + {IT_STRING | IT_CVAR, NULL, "Menu Highlights", &cons_menuhighlight, 105}, // highlight info - (GOOD HIGHLIGHT, WARNING HIGHLIGHT) - 105 (see M_DrawHUDOptions) - {IT_STRING | IT_CVAR, NULL, "Console Text Size", &cv_constextsize, 120}, + {IT_STRING | IT_CVAR, NULL, "Console Text Size", &cv_constextsize, 130}, - {IT_STRING | IT_CVAR, NULL, "Show \"FOCUS LOST\"", &cv_showfocuslost, 135}, + {IT_STRING | IT_CVAR, NULL, "Show \"FOCUS LOST\"", &cv_showfocuslost, 145}, }; // Ok it's still called chatoptions but we'll put ping display in here to be clean @@ -3336,6 +3341,8 @@ void M_Ticker(void) if (--skullAnimCounter <= 0) skullAnimCounter = 8; + followertimer++; + if (currentMenu == &PlaybackMenuDef) { if (playback_enterheld > 0) @@ -9312,9 +9319,15 @@ static void M_HandleConnectIP(INT32 choice) // ======================== // Tails 03-02-2002 +// used for skin display on player setup menu static INT32 multi_tics; static state_t *multi_state; +// used for follower display on player setup menu +static INT32 follower_tics; +static UINT32 follower_frame; // used for FF_ANIMATE garbo +static state_t *follower_state; + // this is set before entering the MultiPlayer setup menu, // for either player 1 or 2 static char setupm_name[MAXPLAYERNAME+1]; @@ -9322,8 +9335,10 @@ static player_t *setupm_player; static consvar_t *setupm_cvskin; static consvar_t *setupm_cvcolor; static consvar_t *setupm_cvname; +static consvar_t *setupm_cvfollower; static INT32 setupm_fakeskin; static INT32 setupm_fakecolor; +static INT32 setupm_fakefollower; // -1 is for none, our followers start at 0 static void M_DrawSetupMultiPlayerMenu(void) { @@ -9341,6 +9356,7 @@ static void M_DrawSetupMultiPlayerMenu(void) UINT8 i; const UINT8 *flashcol = V_GetStringColormap(highlightflags); INT32 statx, staty; + char *fname; mx = MP_PlayerSetupDef.x; my = MP_PlayerSetupDef.y; @@ -9372,11 +9388,31 @@ static void M_DrawSetupMultiPlayerMenu(void) '\x1D' | highlightflags, false); // right arrow } + // draw follower string + fname = malloc(SKINNAMESIZE+1); + + if (setupm_fakefollower == -1) + strcpy(fname, "None"); + else + strcpy(fname, followers[setupm_fakefollower].name); + + st = V_StringWidth(fname, 0); + V_DrawString(BASEVIDWIDTH - mx - st, my + 26, + ((MP_PlayerSetupMenu[2].status & IT_TYPE) == IT_SPACE ? V_TRANSLUCENT : 0)|highlightflags|V_ALLOWLOWERCASE, + fname); + if (itemOn == 2) + { + V_DrawCharacter(BASEVIDWIDTH - mx - 10 - st - (skullAnimCounter/5), my + 26, + '\x1C' | highlightflags, false); // left arrow + V_DrawCharacter(BASEVIDWIDTH - mx + 2 + (skullAnimCounter/5), my + 26, + '\x1D' | highlightflags, false); // right arrow + } + // draw the name of the color you have chosen // Just so people don't go thinking that "Default" is Green. st = V_StringWidth(KartColor_Names[setupm_fakecolor], 0); V_DrawString(BASEVIDWIDTH - mx - st, my + 152, highlightflags|V_ALLOWLOWERCASE, KartColor_Names[setupm_fakecolor]); // SRB2kart - if (itemOn == 2) + if (itemOn == 3) { V_DrawCharacter(BASEVIDWIDTH - mx - 10 - st - (skullAnimCounter/5), my + 152, '\x1C' | highlightflags, false); // left arrow @@ -9524,7 +9560,7 @@ static void M_DrawSetupMultiPlayerMenu(void) sprframe = &sprdef->spriteframes[frame]; patch = W_CachePatchNum(sprframe->lumppat[1], PU_CACHE); - if (sprframe->flip & 1) // Only for first sprite + if (sprframe->flip & 2) // Only for first sprite flags |= V_FLIP; // This sprite is left/right flipped! // draw box around guy @@ -9545,9 +9581,88 @@ static void M_DrawSetupMultiPlayerMenu(void) else V_DrawMappedPatch(mx+43, my+131, flags, patch, colormap); } + + // draw their follower if there is one + if (setupm_fakefollower > -1 && setupm_fakefollower < numfollowers) + { + // animate the follower + + if (--follower_tics <= 0) + { + + // FF_ANIMATE; cycle through FRAMES and get back afterwards. This will be prominent amongst followers hence why it's being supported here. + if (follower_state->frame & FF_ANIMATE) + { + follower_frame++; + follower_tics = follower_state->var2; + if (follower_frame > (follower_state->frame & FF_FRAMEMASK) + follower_state->var1) // that's how it works, right? + follower_frame = follower_state->frame & FF_FRAMEMASK; + } + else + { + st = follower_state->nextstate; + if (st != S_NULL) + follower_state = &states[st]; + follower_tics = follower_state->tics; + if (follower_tics == -1) + follower_tics = 15; // er, what? + // get spritedef: + follower_frame = follower_state->frame & FF_FRAMEMASK; + } + } + sprdef = &sprites[follower_state->sprite]; + + // draw the follower + + if (follower_frame >= sprdef->numframes) + follower_frame = 0; // frame doesn't exist, we went beyond it... what? + sprframe = &sprdef->spriteframes[follower_frame]; + patch = W_CachePatchNum(sprframe->lumppat[1], PU_CACHE); + if (sprframe->flip & 2) // Only for first sprite + flags |= V_FLIP; // This sprite is left/right flipped! + + // @TODO: Reminder that followers on the menu right now do NOT support the 'followercolor' command, considering this whole menu is getting remade anyway, I see no point in incorporating it in right now. + + // draw follower sprite + if (setupm_fakecolor) // inverse should never happen + { + + // Fake the follower's in game appearance by now also applying some of its variables! coolio, eh? + follower_t fl = followers[setupm_fakefollower]; // shortcut for our sanity + // smooth floating, totally not stolen from rocket sneakers. + const fixed_t pi = (22<>ANGLETOFINESHIFT) & FINEMASK); + + UINT8 *colormap = R_GetTranslationColormap(-1, setupm_fakecolor, 0); + V_DrawFixedPatch((mx+65)*FRACUNIT, (my+131-fl.zoffs)*FRACUNIT+sine, fl.scale, flags, patch, colormap); + Z_Free(colormap); + } + } + #undef charw } +// follower state update. This is its own function so that it's at least somewhat clean +static void M_GetFollowerState(void) +{ + + if (setupm_fakefollower <= -1 || setupm_fakefollower > numfollowers-1) // yikes, there's none! + return; + // ^ we don't actually need to set anything since it won't be displayed anyway. + + //followertimer = 0; // reset timer. not like it'll overflow anytime soon but whatever. + + // set follower state + follower_state = &states[followers[setupm_fakefollower].followstate]; + + if (follower_state->frame & FF_ANIMATE) + follower_tics = follower_state->var2; // support for FF_ANIMATE + else + follower_tics = follower_state->tics; + + follower_frame = follower_state->frame & FF_FRAMEMASK; +} + // Handle 1P/2P MP Setup static void M_HandleSetupMultiPlayer(INT32 choice) { @@ -9575,7 +9690,13 @@ static void M_HandleSetupMultiPlayer(INT32 choice) S_StartSound(NULL,sfx_menu1); // Tails setupm_fakeskin--; } - else if (itemOn == 2) // player color + else if (itemOn == 2) // follower + { + S_StartSound(NULL,sfx_menu1); + setupm_fakefollower--; + M_GetFollowerState(); // update follower state + } + else if (itemOn == 3) // player color { S_StartSound(NULL,sfx_menu1); // Tails setupm_fakecolor--; @@ -9587,8 +9708,15 @@ static void M_HandleSetupMultiPlayer(INT32 choice) { S_StartSound(NULL,sfx_menu1); // Tails setupm_fakeskin++; + M_GetFollowerState(); // update follower state } - else if (itemOn == 2) // player color + else if (itemOn == 2) // follower + { + S_StartSound(NULL,sfx_menu1); + setupm_fakefollower++; + M_GetFollowerState(); + } + else if (itemOn == 3) // player color { S_StartSound(NULL,sfx_menu1); // Tails setupm_fakecolor++; @@ -9608,7 +9736,12 @@ static void M_HandleSetupMultiPlayer(INT32 choice) setupm_name[l-1] =0; } } - else if (itemOn == 2) + else if (itemOn == 2) // follower + { + S_StartSound(NULL,sfx_menu1); + setupm_fakefollower = -1; + } + else if (itemOn == 3) { UINT8 col = skins[setupm_fakeskin].prefcolor; if (setupm_fakecolor != col) @@ -9646,6 +9779,18 @@ static void M_HandleSetupMultiPlayer(INT32 choice) if (setupm_fakeskin > numskins-1) setupm_fakeskin = 0; + // check followers: + if (setupm_fakefollower < -1) + { + setupm_fakefollower = numfollowers-1; + M_GetFollowerState(); // update follower state + } + if (setupm_fakefollower > numfollowers-1) + { + setupm_fakefollower = -1; + M_GetFollowerState(); // update follower state + } + // check color if (setupm_fakecolor < 1) setupm_fakecolor = MAXSKINCOLORS-1; @@ -9668,6 +9813,7 @@ static void M_SetupMultiPlayer(INT32 choice) multi_state = &states[mobjinfo[MT_PLAYER].seestate]; multi_tics = multi_state->tics; + strcpy(setupm_name, cv_playername.string); // set for player 1 @@ -9675,6 +9821,15 @@ static void M_SetupMultiPlayer(INT32 choice) setupm_cvskin = &cv_skin; setupm_cvcolor = &cv_playercolor; setupm_cvname = &cv_playername; + setupm_cvfollower = &cv_follower; + + setupm_fakefollower = atoi(setupm_cvfollower->string); // update fake follower value + + // yikes, we don't want none of that... + if (setupm_fakefollower > numfollowers-1) + setupm_fakefollower = -1; + + M_GetFollowerState(); // update follower state // For whatever reason this doesn't work right if you just use ->value setupm_fakeskin = R_SkinAvailable(setupm_cvskin->string); @@ -9706,6 +9861,15 @@ static void M_SetupMultiPlayer2(INT32 choice) setupm_cvskin = &cv_skin2; setupm_cvcolor = &cv_playercolor2; setupm_cvname = &cv_playername2; + setupm_cvfollower = &cv_follower2; + + setupm_fakefollower = atoi(setupm_cvfollower->string); // update fake follower value + + // yikes, we don't want none of that... + if (setupm_fakefollower > numfollowers-1) + setupm_fakefollower = -1; + + M_GetFollowerState(); // update follower state // For whatever reason this doesn't work right if you just use ->value setupm_fakeskin = R_SkinAvailable(setupm_cvskin->string); @@ -9737,6 +9901,15 @@ static void M_SetupMultiPlayer3(INT32 choice) setupm_cvskin = &cv_skin3; setupm_cvcolor = &cv_playercolor3; setupm_cvname = &cv_playername3; + setupm_cvfollower = &cv_follower3; + + setupm_fakefollower = atoi(setupm_cvfollower->string); // update fake follower value + + // yikes, we don't want none of that... + if (setupm_fakefollower > numfollowers-1) + setupm_fakefollower = -1; + + M_GetFollowerState(); // update follower state // For whatever reason this doesn't work right if you just use ->value setupm_fakeskin = R_SkinAvailable(setupm_cvskin->string); @@ -9768,6 +9941,15 @@ static void M_SetupMultiPlayer4(INT32 choice) setupm_cvskin = &cv_skin4; setupm_cvcolor = &cv_playercolor4; setupm_cvname = &cv_playername4; + setupm_cvfollower = &cv_follower4; + + setupm_fakefollower = atoi(setupm_cvfollower->string); // update fake follower value + + // yikes, we don't want none of that... + if (setupm_fakefollower > numfollowers-1) + setupm_fakefollower = -1; + + M_GetFollowerState(); // update follower state // For whatever reason this doesn't work right if you just use ->value setupm_fakeskin = R_SkinAvailable(setupm_cvskin->string); @@ -9800,6 +9982,7 @@ static boolean M_QuitMultiPlayerMenu(void) // you know what? always putting these in the buffer won't hurt anything. COM_BufAddText (va("%s \"%s\"\n",setupm_cvskin->name,skins[setupm_fakeskin].name)); COM_BufAddText (va("%s %d\n",setupm_cvcolor->name,setupm_fakecolor)); + COM_BufAddText (va("%s %d\n",setupm_cvfollower->name,setupm_fakefollower)); return true; } @@ -10683,7 +10866,7 @@ static void M_DrawHUDOptions(void) const char *str1 = " Warning highlight"; const char *str2 = ","; const char *str3 = "Good highlight"; - INT32 x = BASEVIDWIDTH - currentMenu->x + 2, y = currentMenu->y + 105; + INT32 x = BASEVIDWIDTH - currentMenu->x + 2, y = currentMenu->y + 115; INT32 w0 = V_StringWidth(str0, 0), w1 = V_StringWidth(str1, 0), w2 = V_StringWidth(str2, 0), w3 = V_StringWidth(str3, 0); M_DrawGenericMenu(); diff --git a/src/p_mobj.c b/src/p_mobj.c index 842d1e2b1..2547f3212 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -6495,6 +6495,27 @@ void P_MobjThinker(mobj_t *mobj) #endif switch (mobj->type) { + case MT_FOLLOWER: + // small thinker for follower: + // We cleanse ourselves from existence if our target player doesn't exist for whatever reason. (generally players leaving) + if (!mobj->target || P_MobjWasRemoved(mobj->target) || !mobj->target->player || mobj->target->player->spectator || mobj->target->player->followerskin < 0) + { + // Remove possible hnext list (bubble) + mobj_t *bub = mobj->hnext; + mobj_t *tmp; + + while (bub && !P_MobjWasRemoved(bub)) + { + tmp = bub->hnext; + P_RemoveMobj(bub); + bub = tmp; + } + + P_RemoveMobj(mobj); + } + + return; + case MT_HOOP: if (mobj->fuse > 1) P_MoveHoop(mobj); @@ -11870,6 +11891,8 @@ void P_SpawnPlayer(INT32 playernum) p->awayviewmobj = NULL; p->awayviewtics = 0; + P_SetTarget(&p->follower, NULL); // cleanse follower from existence + // set the scale to the mobj's destscale so settings get correctly set. if we don't, they sometimes don't. if (cv_kartdebugshrink.value && !modeattacking && !p->bot) mobj->destscale = 6*mobj->destscale/8; diff --git a/src/p_saveg.c b/src/p_saveg.c index 7bfeba762..16863ae33 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -57,10 +57,11 @@ typedef enum { // RFLAGPOINT = 0x01, // BFLAGPOINT = 0x02, - CAPSULE = 0x04, - AWAYVIEW = 0x08, - FIRSTAXIS = 0x10, - SECONDAXIS = 0x20, + CAPSULE = 4, + AWAYVIEW = 8, + FIRSTAXIS = 16, + SECONDAXIS = 32, + FOLLOWER = 64, } player_saveflags; // @@ -218,6 +219,9 @@ static void P_NetArchivePlayers(void) if (players[i].axis2) flags |= SECONDAXIS; + if (players[i].follower) + flags |= FOLLOWER; + WRITEINT16(save_p, players[i].lastsidehit); WRITEINT16(save_p, players[i].lastlinehit); @@ -249,6 +253,14 @@ static void P_NetArchivePlayers(void) // SRB2kart WRITEUINT8(save_p, players[i].kartspeed); WRITEUINT8(save_p, players[i].kartweight); + + WRITEUINT8(save_p, players[i].followerskin); + WRITEUINT8(save_p, players[i].followerready); // booleans are really just numbers eh?? + WRITEUINT8(save_p, players[i].followercolor); + if (flags & FOLLOWER) + WRITEUINT32(save_p, players[i].follower->mobjnum); + + // for (j = 0; j < NUMKARTSTUFF; j++) @@ -435,6 +447,13 @@ static void P_NetUnArchivePlayers(void) // SRB2kart players[i].kartspeed = READUINT8(save_p); players[i].kartweight = READUINT8(save_p); + + players[i].followerskin = READUINT8(save_p); + players[i].followerready = READUINT8(save_p); + players[i].followercolor = READUINT8(save_p); + if (flags & FOLLOWER) + players[i].follower = (mobj_t *)(size_t)READUINT32(save_p); + // for (j = 0; j < NUMKARTSTUFF; j++) @@ -3130,6 +3149,7 @@ static void P_RelinkPointers(void) if (!(mobj->itnext = P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "itnext not found on %d\n", mobj->type); } + if (mobj->player) { if (mobj->player->capsule) @@ -3160,6 +3180,13 @@ static void P_RelinkPointers(void) if (!P_SetTarget(&mobj->player->awayviewmobj, P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "awayviewmobj not found on %d\n", mobj->type); } + if (mobj->player->follower) + { + temp = (UINT32)(size_t)mobj->player->follower; + mobj->player->follower = NULL; + if (!P_SetTarget(&mobj->player->follower, P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "follower not found on %d\n", mobj->type); + } if (mobj->player->nextwaypoint) { temp = (UINT32)(size_t)mobj->player->nextwaypoint; diff --git a/src/p_setup.c b/src/p_setup.c index 655b667fd..7d2f8ab0f 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -2460,6 +2460,9 @@ static void P_LevelInitStuff(void) // and this stupid flag as a result players[i].pflags &= ~PF_TRANSFERTOCLOSEST; + + // Wipe follower from existence to avoid crashes + players[i].follower = NULL; } // SRB2Kart: map load variables diff --git a/src/p_user.c b/src/p_user.c index aefd9b685..fd5691601 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -45,6 +45,7 @@ // SRB2kart #include "m_cond.h" // M_UpdateUnlockablesAndExtraEmblems #include "k_kart.h" +#include "k_color.h" // KartColor_Opposite #include "console.h" // CON_LogMessage #include "k_respawn.h" #include "k_bot.h" @@ -8407,6 +8408,285 @@ void P_DoTimeOver(player_t *player) exitcountdown = 5*TICRATE; } +/* set follower state with our weird hacks + the reason we do this is to avoid followers ever using actions (majormods, yikes!) + without having to touch p_mobj.c. + so we give it 1more tic and change the state when tic == 1 instead of 0 + cool beans? + cool beans. +*/ +static void P_SetFollowerState(mobj_t *f, INT32 state) +{ + + if (!f || P_MobjWasRemoved(f)) + return; // safety net + + // No, do NOT set the follower to S_NULL. Set it to S_INVISIBLE. + if (state == S_NULL) + { + state = S_INVISIBLE; + f->threshold = 1; // Threshold = 1 means stop doing anything related to setting states, so that we don't get out of S_INVISIBLE + } + + // extravalue2 stores the last "first state" we used. + // because states default to idlestates, if we use an animation that uses an "ongoing" state line, don't reset it! + // this prevents it from looking very dumb + if (state == f->extravalue2) + return; + + // we will save the state into extravalue2. + f->extravalue2 = state; + + P_SetMobjStateNF(f, state); + if (f->state->tics > 0) + f->tics++; +} + +// +//P_HandleFollower +// +//Handle the follower's spawning and moving along with the player. Do note that some of the stuff like the removal if a player doesn't exist anymore is handled in MT_FOLLOWER's thinker. +static void P_HandleFollower(player_t *player) +{ + follower_t fl; + angle_t an; + fixed_t zoffs; + fixed_t sx, sy, sz; + UINT8 color; + + fixed_t bubble; // bubble scale (0 if no bubble) + mobj_t *bmobj; // temp bubble mobj + + + if (!player->followerready) + return; // we aren't ready to perform anything follower related yet. + + // How about making sure our follower exists and is added before trying to spawn it n' all? + if (player->followerskin > numfollowers-1 || player->followerskin < -1) + { + //CONS_Printf("Follower skin invlaid. Setting to -1.\n"); + player->followerskin = -1; + return; + } + + // don't do anything if we can't have a follower to begin with. (It gets removed under those conditions) + if (player->spectator) + return; + if (player->followerskin < 0) + return; + // Before we do anything, let's be sure of where we're supposed to be + fl = followers[player->followerskin]; + + an = player->mo->angle + (fl.atangle)*ANG1; // it's aproximative but it really doesn't matter in the grand scheme of things... + zoffs = (fl.zoffs)*FRACUNIT; + bubble = fl.bubblescale; // 0 if no bubble to spawn. + + // do you like angle maths? I certainly don't... + sx = player->mo->x + FixedMul((player->mo->scale*fl.dist), FINECOSINE((an)>>ANGLETOFINESHIFT)); + sy = player->mo->y + FixedMul((player->mo->scale*fl.dist), FINESINE((an)>>ANGLETOFINESHIFT)); + + // for the z coordinate, don't be a doof like Steel and forget that MFE_VERTICALFLIP exists :P + sz = player->mo->z + FixedMul(player->mo->scale, zoffs)*P_MobjFlip(player->mo); + if (player->mo->eflags & MFE_VERTICALFLIP) + sz += fl.height*player->mo->scale; + + // finally, add a cool floating effect to the z height. + // not stolen from k_kart I swear!! + const fixed_t pi = (22<>ANGLETOFINESHIFT) & FINEMASK); + sz += FixedMul(player->mo->scale, sine)*P_MobjFlip(player->mo); + + // Set follower colour + + switch (player->followercolor) + { + case MAXSKINCOLORS: // "Match" + color = player->skincolor; + break; + case MAXSKINCOLORS+1: // "Opposite" + color = KartColor_Opposite[player->skincolor*2]; + break; + default: + + color = player->followercolor; + if (!color || color > MAXSKINCOLORS+2) // Make sure this isn't garbage + color = player->skincolor; // "Match" as fallback. + + break; + } + + + + if (!player->follower) // follower doesn't exist / isn't valid + { + //CONS_Printf("Spawning follower...\n"); + // so let's spawn one! + P_SetTarget(&player->follower, P_SpawnMobj(sx, sy, sz, MT_FOLLOWER)); + P_SetFollowerState(player->follower, fl.idlestate); + P_SetTarget(&player->follower->target, player->mo); // we need that to know when we need to disappear + player->follower->angle = player->mo->angle; + + // This is safe to only spawn it here, the follower is removed then respawned when switched. + if (bubble) + { + bmobj = P_SpawnMobj(player->follower->x, player->follower->y, player->follower->z, MT_FOLLOWERBUBBLE_FRONT); + P_SetTarget(&player->follower->hnext, bmobj); + P_SetTarget(&bmobj->target, player->follower); // Used to know if we have to despawn at some point. + + bmobj = P_SpawnMobj(player->follower->x, player->follower->y, player->follower->z, MT_FOLLOWERBUBBLE_BACK); + P_SetTarget(&player->follower->hnext->hnext, bmobj); // this seems absolutely stupid, I know, but this will make updating the momentums/flags of these a bit easier. + P_SetTarget(&bmobj->target, player->follower); // Ditto + } + + player->follower->extravalue1 = 0; // extravalue1 is used to know what "state set" to use. + /* + 0 = idle + 1 = forwards + 2 = hurt + 3 = win + 4 = lose + 5 = hitconfirm (< this one uses ->movecount as timer to know when to end, and goes back to normal states afterwards, unless hurt) + */ + } + else // follower exists, woo! + { + + // Safety net (2) + + if (P_MobjWasRemoved(player->follower)) + { + P_SetTarget(&player->follower, NULL); // Remove this and respawn one, don't crash the game if Lua decides to P_RemoveMobj this thing. + return; + } + + // first of all, handle states following the same model as above: + if (player->follower->tics == 1) + P_SetFollowerState(player->follower, player->follower->state->nextstate); + + // move the follower next to us (yes, this is really basic maths but it looks pretty damn clean in practice)! + player->follower->momx = (sx - player->follower->x)/fl.horzlag; + player->follower->momy = (sy - player->follower->y)/fl.horzlag; + player->follower->momz = (sz - player->follower->z)/fl.vertlag; + player->follower->angle = player->mo->angle; + + if (player->mo->colorized) + player->follower->color = player->mo->color; + else + player->follower->color = color; + + player->follower->colorized = player->mo->colorized; + + P_SetScale(player->follower, FixedMul(fl.scale, player->mo->scale)); + K_GenericExtraFlagsNoZAdjust(player->follower, player->mo); // Not K_MatchGenericExtraFlag because the Z adjust it has only works properly if master & mo have the same Z height. + + // For comeback in battle. + player->follower->flags2 = (player->follower->flags2 & ~MF2_SHADOW)|(player->mo->flags2 & MF2_SHADOW); + + // Make the follower invisible if we no contest'd rather than removing it. No one will notice the diff seriously. + // Also make the follower invisible if we choose not to have it displayed because it isn't ours. (also quick hacky check for f12) + if (player->pflags & PF_TIMEOVER || (!cv_showfollowers.value && (!P_IsDisplayPlayer(player) || displayplayers[0] != consoleplayer) )) + player->follower->flags2 |= MF2_DONTDRAW; + + if (player->speed && (player->follower->momx || player->follower->momy)) + player->follower->angle = R_PointToAngle2(0, 0, player->follower->momx, player->follower->momy); + // if we're moving let's make the angle the direction we're moving towards. This is to avoid drifting / reverse looking awkward. + // Make sure the follower itself is also moving however, otherwise we'll be facing angle 0 + + // Finally, if the follower has bubbles, move them, set their scale, etc.... + // This is what I meant earlier by it being easier, now we can just use this weird lil loop to get the job done! + + bmobj = player->follower->hnext; // will be NULL if there's no bubble + + while (bmobj && !P_MobjWasRemoved(bmobj)) + { + // match follower's momentums and (e)flags(2). + bmobj->momx = player->follower->momx; + bmobj->momy = player->follower->momy; + bmobj->momz = player->follower->momz; + + P_SetScale(bmobj, FixedMul(bubble, player->mo->scale)); + K_GenericExtraFlagsNoZAdjust(bmobj, player->follower); + bmobj->flags2 = (player->follower->flags2 & ~MF2_SHADOW)|(player->mo->flags2 & MF2_SHADOW); + + if (player->follower->threshold) // threshold means the follower was "despawned" with S_NULL (is actually just set to S_INVISIBLE) + P_SetMobjState(bmobj, S_INVISIBLE); // sooooo... let's do the same! + + bmobj = bmobj->hnext; // switch to other bubble layer or exit + } + + + if (player->follower->threshold) + return; // Threshold means the follower was "despanwed" with S_NULL. + + // However with how the code is factored, this is just a special case of S_INVISBLE to avoid having to add other player variables. + + + // handle follower animations. Could probably be better... + // hurt or dead + if (player->kartstuff[k_spinouttimer] || player->mo->state == &states[S_KART_SPIN] || player->mo->health <= 0) + { + player->follower->movecount = 0; // cancel hit confirm. + player->follower->angle = player->frameangle; // spin out + if (player->follower->extravalue1 != 2) + { + player->follower->extravalue1 = 2; + P_SetFollowerState(player->follower, fl.hurtstate); + } + if (player->mo->health <= 0) // if dead, follow the player's z momentum exactly so they both look like they die at the same speed. + player->follower->momz = player->mo->momz; + } + else if (player->follower->movecount) + { + if (player->follower->extravalue1 != 5) + { + player->follower->extravalue1 = 5; + P_SetFollowerState(player->follower, fl.hitconfirmstate); + } + player->follower->movecount--; + } + else if (player->speed > 10*player->mo->scale) // animation for moving fast enough + { + + if (player->follower->extravalue1 != 1) + { + player->follower->extravalue1 = 1; + P_SetFollowerState(player->follower, fl.followstate); + } + } + else // animations when nearly still. This includes winning and losing. + { + if (player->follower->extravalue1 != 0) + { + + if (player->exiting) // win/ loss animations + { + if (K_IsPlayerLosing(player)) // L + { + if (player->follower->extravalue1 != 4) + { + player->follower->extravalue1 = 4; + P_SetFollowerState(player->follower, fl.losestate); + } + } + else // W + { + if (player->follower->extravalue1 != 3) + { + player->follower->extravalue1 = 3; + P_SetFollowerState(player->follower, fl.winstate); + } + } + } + else // normal standstill + { + player->follower->extravalue1 = 0; + P_SetFollowerState(player->follower, fl.idlestate); + } + } + } + } +} + // // P_PlayerThink // @@ -8461,6 +8741,9 @@ void P_PlayerThink(player_t *player) player->awayviewtics = 0; // reset to zero } + // Run followes here. We need them to run even when we're dead to follow through what we're doing. + P_HandleFollower(player); + if (player->flashcount) player->flashcount--; diff --git a/src/r_data.h b/src/r_data.h index 610a9c399..f9812d4e4 100644 --- a/src/r_data.h +++ b/src/r_data.h @@ -60,6 +60,7 @@ extern INT16 color8to16[256]; // remap color index to highcolor extern INT16 *hicolormaps; // remap high colors to high colors.. extern CV_PossibleValue_t Color_cons_t[]; +extern CV_PossibleValue_t Followercolor_cons_t[]; // follower colours table, not a duplicate because of the "Match" option. // Load TEXTURE1/TEXTURE2/PNAMES definitions, create lookup tables void R_LoadTextures(void); diff --git a/src/r_draw.c b/src/r_draw.c index 7746693bf..c993810d5 100644 --- a/src/r_draw.c +++ b/src/r_draw.c @@ -147,6 +147,7 @@ static UINT8** translationtablecache[TT_CACHE_SIZE] = {NULL}; // SKINCOLOR DEFINITIONS HAVE BEEN MOVED TO K_KART.C CV_PossibleValue_t Color_cons_t[MAXSKINCOLORS+1]; +CV_PossibleValue_t Followercolor_cons_t[MAXSKINCOLORS+3]; // +3 to account for "Match", "Opposite" & NULL /** \brief The R_InitTranslationTables diff --git a/src/r_things.c b/src/r_things.c index c43f430c7..5a428afc8 100644 --- a/src/r_things.c +++ b/src/r_things.c @@ -2891,7 +2891,12 @@ void R_DrawMasked(void) // // ========================================================================== +// We can assume those are tied to skins somewhat, hence why they're defined here. INT32 numskins = 0; +follower_t followers[MAXSKINS]; +// default followers are defined in SOC_FLWR in followers.kart / gfx.kart (depending on what exe this is, at this point) + + skin_t skins[MAXSKINS]; // FIXTHIS: don't work because it must be inistilised before the config load //#define SKINVALUES @@ -2954,6 +2959,19 @@ INT32 R_SkinAvailable(const char *name) return -1; } +// same thing but for followers: +INT32 R_FollowerAvailable(const char *name) +{ + INT32 i; + + for (i = 0; i < numfollowers; i++) + { + if (stricmp(followers[i].skinname,name)==0) + return i; + } + return -1; +} + // network code calls this when a 'skin change' is received boolean SetPlayerSkin(INT32 playernum, const char *skinname) { @@ -2979,13 +2997,37 @@ boolean SetPlayerSkin(INT32 playernum, const char *skinname) return false; } +// Again, same thing but for followers; +boolean SetPlayerFollower(INT32 playernum, const char *skinname) +{ + INT32 i; + player_t *player = &players[playernum]; + + for (i = 0; i < numfollowers; i++) + { + // search in the skin list + if (stricmp(followers[i].skinname, skinname) == 0) + { + SetFollower(playernum, i); + return true; + } + } + + if (P_IsLocalPlayer(player)) + CONS_Alert(CONS_WARNING, M_GetText("Follower '%s' not found.\n"), skinname); + else if(server || IsPlayerAdmin(consoleplayer)) + CONS_Alert(CONS_WARNING, M_GetText("Player %d (%s) follower '%s' not found\n"), playernum, player_names[playernum], skinname); + + SetFollower(playernum, -1); // reminder that -1 is nothing + return false; +} + // Same as SetPlayerSkin, but uses the skin #. // network code calls this when a 'skin change' is received void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum) { player_t *player = &players[playernum]; skin_t *skin = &skins[skinnum]; - if (skinnum >= 0 && skinnum < numskins) // Make sure it exists! { player->skin = skinnum; @@ -3016,6 +3058,7 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum) if (player->mo) P_SetScale(player->mo, player->mo->scale); + // for replays: We have changed our skin mid-game; let the game know so it can do the same in the replay! demo_extradata[playernum] |= DXD_SKIN; return; @@ -3028,6 +3071,53 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum) SetPlayerSkinByNum(playernum, 0); // not found put the sonic skin } +// you get the drill, now we do the same for followers: +void SetFollower(INT32 playernum, INT32 skinnum) +{ + player_t *player = &players[playernum]; + mobj_t *bub; + mobj_t *tmp; + + player->followerready = true; // we are ready to perform follower related actions in the player thinker, now. + if (skinnum >= -1 && skinnum <= numfollowers) // Make sure it exists! + { + /* + We don't spawn the follower here since it'll be easier to handle all of it in the Player thinker itself. + However, we will despawn it right here if there's any to make it easy for the player thinker to replace it or delete it. + */ + if (player->follower && skinnum != player->followerskin) // this is also called when we change colour so don't respawn the follower unless we changed skins + { + + // Remove follower's possible hnext list (bubble) + bub = player->follower->hnext; + + while (bub && !P_MobjWasRemoved(bub)) + { + tmp = bub->hnext; + P_RemoveMobj(bub); + bub = tmp; + } + + P_RemoveMobj(player->follower); + P_SetTarget(&player->follower, NULL); + } + + player->followerskin = skinnum; + //CONS_Printf("Updated player follower num\n"); + + // for replays: We have changed our follower mid-game; let the game know so it can do the same in the replay! + demo_extradata[playernum] |= DXD_FOLLOWER; + + return; + } + + if (P_IsLocalPlayer(player)) + CONS_Alert(CONS_WARNING, M_GetText("Follower %d not found\n"), skinnum); + else if(server || IsPlayerAdmin(consoleplayer)) + CONS_Alert(CONS_WARNING, "Player %d (%s) follower %d not found\n", playernum, player_names[playernum], skinnum); + SetFollower(playernum, -1); // Not found, then set -1 (nothing) as our follower. +} + // // Add skins from a pwad, each skin preceded by 'S_SKIN' marker // diff --git a/src/r_things.h b/src/r_things.h index 6d0e86425..a42e1e891 100644 --- a/src/r_things.h +++ b/src/r_things.h @@ -103,6 +103,45 @@ typedef struct } skin_t; extern CV_PossibleValue_t Forceskin_cons_t[]; +// +// for followers. +// +// We'll define these here because they're really just a mobj that'll follow some rules behind a player +// +typedef struct follower_s +{ + char skinname[SKINNAMESIZE+1]; // Skin Name. This is what to refer to when asking the commands anything. + char name[SKINNAMESIZE+1]; // Name. This is used for the menus. We'll just follow the same rules as skins for this. + + UINT8 defaultcolor; // default color for menus. + + fixed_t scale; // Scale relative to the player's. + fixed_t bubblescale; // Bubble scale relative to the player scale. If not set, no bubble will spawn (default) + + // some position shenanigans: + INT32 atangle; // angle the object will be at around the player. The object itself will always face the same direction as the player. + INT32 dist; // distance relative to the player. (In a circle) + INT32 height; // height of the follower, this is mostly important for Z flipping. + INT32 zoffs; // Z offset relative to the player's height. Cannot be negative. + + // movement options + + INT32 horzlag; // Lag for X/Y displacement. Default is 2. Must be > 0 because we divide by this number. + INT32 vertlag; // not Vert from Neptunia lagging, this is for Z displacement lag Default is 6. Must be > 0 because we divide by this number. + INT32 bobamp; // Bob amplitude. Default is 4. + INT32 bobspeed; // Arbitrary modifier for bobbing speed, default is TICRATE*2 (70). + + // from there on out, everything is STATES to allow customization + // these are only set once when the action is performed and are then free to animate however they want. + + INT32 idlestate; // state when the player is at a standstill + INT32 followstate; // state when the player is moving + INT32 hurtstate; // state when the player is being hurt + INT32 winstate; // state when the player has won + INT32 losestate; // state when the player has lost + INT32 hitconfirmstate; // state for hit confirm + INT32 hitconfirmtime; // time to keep the above playing for +} follower_t; // ----------- // NOT SKINS STUFF ! @@ -199,12 +238,18 @@ typedef struct drawnode_s extern INT32 numskins; extern skin_t skins[MAXSKINS]; +extern INT32 numfollowers; +extern follower_t followers[MAXSKINS]; // again, use the same rules as skins, no reason not to. boolean SetPlayerSkin(INT32 playernum,const char *skinname); void SetPlayerSkinByNum(INT32 playernum,INT32 skinnum); // Tails 03-16-2002 INT32 R_SkinAvailable(const char *name); void R_AddSkins(UINT16 wadnum); +INT32 R_FollowerAvailable(const char *name); +boolean SetPlayerFollower(INT32 playernum,const char *skinname); +void SetFollower(INT32 playernum,INT32 skinnum); + #ifdef DELFILE void R_DelSkins(UINT16 wadnum); #endif