Servant Hand

- Points in the direction of the best waypoint to take
- Vwoops in and out like a drop target squash-n-stretch
- Shows WRONG WAY only on debugwaypoints
- Flexible enough to be used for custom purposes and other gametypes, the only caveat being if those gametypes use GTR_CIRCUIT conflicting with the other purpose of PF_WRONGWAY
This commit is contained in:
toaster 2023-06-15 18:46:44 +01:00
parent 7d82d8cd0c
commit 1fee9f65fb
10 changed files with 198 additions and 7 deletions

View file

@ -744,10 +744,14 @@ struct player_t
mobj_t *stumbleIndicator; mobj_t *stumbleIndicator;
mobj_t *sliptideZipIndicator; mobj_t *sliptideZipIndicator;
mobj_t *whip; mobj_t *whip;
mobj_t *hand;
UINT8 instaShieldCooldown; UINT8 instaShieldCooldown;
UINT8 guardCooldown; UINT8 guardCooldown;
UINT8 handtimer;
angle_t besthanddirection;
INT16 incontrol; // -1 to -175 when spinning out or tumbling, 1 to 175 when not. Use to check for combo hits or emergency inputs. INT16 incontrol; // -1 to -175 when spinning out or tumbling, 1 to 175 when not. Use to check for combo hits or emergency inputs.
boolean markedfordeath; boolean markedfordeath;

View file

@ -3291,6 +3291,8 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi
"S_BLOCKRING", "S_BLOCKRING",
"S_BLOCKBODY", "S_BLOCKBODY",
"S_SERVANTHAND",
// Signpost sparkles // Signpost sparkles
"S_SIGNSPARK1", "S_SIGNSPARK1",
"S_SIGNSPARK2", "S_SIGNSPARK2",
@ -5337,6 +5339,8 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t
"MT_BLOCKRING", "MT_BLOCKRING",
"MT_BLOCKBODY", "MT_BLOCKBODY",
"MT_SERVANTHAND",
"MT_SIGNSPARKLE", "MT_SIGNSPARKLE",
"MT_FASTLINE", "MT_FASTLINE",

View file

@ -2689,6 +2689,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
P_SetTarget(&players[player].awayview.mobj, NULL); P_SetTarget(&players[player].awayview.mobj, NULL);
P_SetTarget(&players[player].stumbleIndicator, NULL); P_SetTarget(&players[player].stumbleIndicator, NULL);
P_SetTarget(&players[player].whip, NULL); P_SetTarget(&players[player].whip, NULL);
P_SetTarget(&players[player].hand, NULL);
P_SetTarget(&players[player].ringShooter, NULL); P_SetTarget(&players[player].ringShooter, NULL);
P_SetTarget(&players[player].followmobj, NULL); P_SetTarget(&players[player].followmobj, NULL);

View file

@ -559,6 +559,8 @@ char sprnames[NUMSPRITES + 1][5] =
"GRNG", // Guard ring "GRNG", // Guard ring
"GBDY", // Guard body "GBDY", // Guard body
"DHND", // Servant Hand
"WIPD", // Wipeout dust trail "WIPD", // Wipeout dust trail
"DRIF", // Drift Sparks "DRIF", // Drift Sparks
"BDRF", // Brake drift sparks "BDRF", // Brake drift sparks
@ -3956,6 +3958,8 @@ state_t states[NUMSTATES] =
{SPR_GRNG, FF_FULLBRIGHT|FF_PAPERSPRITE|0, -1, {NULL}, 0, 0, S_NULL}, // S_BLOCKRING {SPR_GRNG, FF_FULLBRIGHT|FF_PAPERSPRITE|0, -1, {NULL}, 0, 0, S_NULL}, // S_BLOCKRING
{SPR_GBDY, FF_FULLBRIGHT|FF_ANIMATE|0, -1, {NULL}, 4, 2, S_NULL}, // S_BLOCKBODY {SPR_GBDY, FF_FULLBRIGHT|FF_ANIMATE|0, -1, {NULL}, 4, 2, S_NULL}, // S_BLOCKBODY
{SPR_DHND, 0, -1, {NULL}, 0, 0, S_NULL}, // S_SERVANTHAND
{SPR_SGNS, FF_ADD|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_SIGNSPARK2}, // S_SIGNSPARK1 {SPR_SGNS, FF_ADD|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_SIGNSPARK2}, // S_SIGNSPARK1
{SPR_SGNS, FF_ADD|FF_FULLBRIGHT|1, 1, {NULL}, 0, 0, S_SIGNSPARK3}, // S_SIGNSPARK2 {SPR_SGNS, FF_ADD|FF_FULLBRIGHT|1, 1, {NULL}, 0, 0, S_SIGNSPARK3}, // S_SIGNSPARK2
{SPR_SGNS, FF_ADD|FF_FULLBRIGHT|2, 1, {NULL}, 0, 0, S_SIGNSPARK4}, // S_SIGNSPARK3 {SPR_SGNS, FF_ADD|FF_FULLBRIGHT|2, 1, {NULL}, 0, 0, S_SIGNSPARK4}, // S_SIGNSPARK3
@ -22760,6 +22764,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
S_NULL // raisestate S_NULL // raisestate
}, },
{ // MT_SERVANTHAND
-1, // doomednum
S_SERVANTHAND, // spawnstate
1000, // spawnhealth
S_NULL, // seestate
sfx_None, // seesound
0, // reactiontime
sfx_None, // attacksound
S_NULL, // painstate
0, // painchance
sfx_None, // painsound
S_NULL, // meleestate
S_NULL, // missilestate
S_NULL, // deathstate
S_NULL, // xdeathstate
sfx_None, // deathsound
0, // speed
40*FRACUNIT, // radius
40*FRACUNIT, // height
0, // display offset
100, // mass
0, // damage
sfx_None, // activesound
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
{ // MT_SIGNSPARKLE { // MT_SIGNSPARKLE
-1, // doomednum -1, // doomednum

View file

@ -1112,6 +1112,8 @@ typedef enum sprite
SPR_GRNG, // Guard ring SPR_GRNG, // Guard ring
SPR_GBDY, // Guard body SPR_GBDY, // Guard body
SPR_DHND, // Servant Hand
SPR_WIPD, // Wipeout dust trail SPR_WIPD, // Wipeout dust trail
SPR_DRIF, // Drift Sparks SPR_DRIF, // Drift Sparks
SPR_BDRF, // Brake drift sparks SPR_BDRF, // Brake drift sparks
@ -4367,6 +4369,8 @@ typedef enum state
S_BLOCKRING, S_BLOCKRING,
S_BLOCKBODY, S_BLOCKBODY,
S_SERVANTHAND,
// Signpost sparkles // Signpost sparkles
S_SIGNSPARK1, S_SIGNSPARK1,
S_SIGNSPARK2, S_SIGNSPARK2,
@ -6448,6 +6452,8 @@ typedef enum mobj_type
MT_BLOCKRING, MT_BLOCKRING,
MT_BLOCKBODY, MT_BLOCKBODY,
MT_SERVANTHAND,
MT_SIGNSPARKLE, MT_SIGNSPARKLE,
MT_FASTLINE, MT_FASTLINE,

View file

@ -5005,7 +5005,7 @@ static void K_DrawWaypointDebugger(void)
} }
V_DrawString(8, 156, 0, va("Current Waypoint ID: %d", K_GetWaypointID(stplyr->currentwaypoint))); V_DrawString(8, 156, 0, va("Current Waypoint ID: %d", K_GetWaypointID(stplyr->currentwaypoint)));
V_DrawString(8, 166, 0, va("Next Waypoint ID: %d", K_GetWaypointID(stplyr->nextwaypoint))); V_DrawString(8, 166, 0, va("Next Waypoint ID: %d%s", K_GetWaypointID(stplyr->nextwaypoint), ((stplyr->pflags & PF_WRONGWAY) ? " (WRONG WAY)" : "")));
V_DrawString(8, 176, 0, va("Finishline Distance: %d", stplyr->distancetofinish)); V_DrawString(8, 176, 0, va("Finishline Distance: %d", stplyr->distancetofinish));
if (numstarposts > 0) if (numstarposts > 0)
@ -5365,11 +5365,6 @@ void K_drawKartHUD(void)
// Draw FREE PLAY. // Draw FREE PLAY.
K_drawKartFreePlay(); K_drawKartFreePlay();
if (r_splitscreen == 0 && (stplyr->pflags & PF_WRONGWAY) && ((leveltime / 8) & 1))
{
V_DrawCenteredString(BASEVIDWIDTH>>1, 176, V_REDMAP|V_SNAPTOBOTTOM, "WRONG WAY");
}
if ((netgame || cv_mindelay.value) && r_splitscreen && Playing()) if ((netgame || cv_mindelay.value) && r_splitscreen && Playing())
{ {
K_drawMiniPing(); K_drawMiniPing();

View file

@ -8275,9 +8275,14 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
player->tripwireState = TRIPSTATE_NONE; player->tripwireState = TRIPSTATE_NONE;
} }
if (player->hand && P_MobjWasRemoved(player->hand))
P_SetTarget(&player->hand, NULL);
if (player->spectator == false) if (player->spectator == false)
{ {
K_KartEbrakeVisuals(player); K_KartEbrakeVisuals(player);
K_KartServantHandVisuals(player);
} }
if (K_GetKartButtons(player) & BT_BRAKE && if (K_GetKartButtons(player) & BT_BRAKE &&
@ -8596,6 +8601,7 @@ static waypoint_t *K_GetPlayerNextWaypoint(player_t *player)
{ {
angle_t nextbestdelta = ANGLE_90; angle_t nextbestdelta = ANGLE_90;
angle_t nextbestmomdelta = ANGLE_90; angle_t nextbestmomdelta = ANGLE_90;
angle_t nextbestanydelta = ANGLE_MAX;
size_t i = 0U; size_t i = 0U;
if ((waypoint->nextwaypoints != NULL) && (waypoint->numnextwaypoints > 0U)) if ((waypoint->nextwaypoints != NULL) && (waypoint->numnextwaypoints > 0U))
@ -8637,8 +8643,14 @@ static waypoint_t *K_GetPlayerNextWaypoint(player_t *player)
momdelta = InvAngle(momdelta); momdelta = InvAngle(momdelta);
} }
if (angledelta < nextbestdelta || momdelta < nextbestmomdelta) if (angledelta < nextbestanydelta || momdelta < nextbestanydelta)
{ {
nextbestanydelta = min(angledelta, momdelta);
player->besthanddirection = angletowaypoint;
if (nextbestanydelta >= ANGLE_90)
continue;
// Wanted to use a next waypoint, so remove WRONG WAY flag. // Wanted to use a next waypoint, so remove WRONG WAY flag.
// Done here instead of when set, because of finish line // Done here instead of when set, because of finish line
// hacks meaning we might not actually use this one, but // hacks meaning we might not actually use this one, but
@ -10158,6 +10170,102 @@ void K_KartEbrakeVisuals(player_t *p)
} }
} }
void K_KartServantHandVisuals(player_t *player)
{
if (player->pflags & PF_WRONGWAY)
{
if (player->handtimer < TICRATE)
{
player->handtimer++;
if (player->hand == NULL && player->handtimer == TICRATE)
{
mobj_t *hand = P_SpawnMobj(
player->mo->x,
player->mo->y,
player->mo->z + player->mo->height + 30*mapobjectscale,
MT_SERVANTHAND
);
if (hand)
{
K_FlipFromObject(hand, player->mo);
hand->old_z = hand->z;
P_SetTarget(&hand->target, player->mo);
P_SetTarget(&player->hand, hand);
hand->fuse = 8;
}
}
}
if (player->hand)
{
player->hand->destscale = mapobjectscale;
}
}
else if (player->handtimer != 0)
{
player->handtimer--;
}
if (player->hand)
{
const fixed_t handpokespeed = 4;
const fixed_t looping = handpokespeed - abs((player->hand->threshold % (handpokespeed*2)) - handpokespeed);
fixed_t xoffs = 0, yoffs = 0;
player->hand->color = player->skincolor;
player->hand->angle = player->besthanddirection;
if (player->hand->fuse != 0)
{
;
}
else if (looping != 0)
{
xoffs = FixedMul(2 * looping * mapobjectscale, FINECOSINE(player->hand->angle >> ANGLETOFINESHIFT)),
yoffs = FixedMul(2 * looping * mapobjectscale, FINESINE(player->hand->angle >> ANGLETOFINESHIFT)),
player->hand->threshold++;
}
else if (player->handtimer == 0)
{
player->hand->fuse = 8;
}
else
{
player->hand->threshold++;
}
if (player->hand->fuse != 0)
{
if ((player->hand->fuse > 4) ^ (player->handtimer < TICRATE/2))
{
player->hand->spritexscale = FRACUNIT/3;
player->hand->spriteyscale = 3*FRACUNIT;
}
else
{
player->hand->spritexscale = 2*FRACUNIT;
player->hand->spriteyscale = FRACUNIT/2;
}
}
P_MoveOrigin(player->hand,
player->mo->x + xoffs,
player->mo->y + yoffs,
player->mo->z + player->mo->height + 30*mapobjectscale
);
K_FlipFromObject(player->hand, player->mo);
player->hand->sprzoff = player->mo->sprzoff;
player->hand->renderflags &= ~RF_DONTDRAW;
player->hand->renderflags |= (RF_DONTDRAW & ~K_GetPlayerDontDrawFlag(player));
}
}
static void K_KartSpindashDust(mobj_t *parent) static void K_KartSpindashDust(mobj_t *parent)
{ {
fixed_t rad = FixedDiv(FixedHypot(parent->radius, parent->radius), parent->scale); fixed_t rad = FixedDiv(FixedHypot(parent->radius, parent->radius), parent->scale);

View file

@ -196,6 +196,7 @@ UINT8 K_GetInvincibilityItemFrame(void);
UINT8 K_GetOrbinautItemFrame(UINT8 count); UINT8 K_GetOrbinautItemFrame(UINT8 count);
boolean K_IsSPBInGame(void); boolean K_IsSPBInGame(void);
void K_KartEbrakeVisuals(player_t *p); void K_KartEbrakeVisuals(player_t *p);
void K_KartServantHandVisuals(player_t *player);
void K_HandleDirectionalInfluence(player_t *player); void K_HandleDirectionalInfluence(player_t *player);
fixed_t K_DefaultPlayerRadius(player_t *player); fixed_t K_DefaultPlayerRadius(player_t *player);

View file

@ -9776,6 +9776,22 @@ static boolean P_FuseThink(mobj_t *mobj)
Obj_SPBExplode(mobj); Obj_SPBExplode(mobj);
break; break;
} }
case MT_SERVANTHAND:
{
if (!mobj->target
|| P_MobjWasRemoved(mobj->target)
|| !mobj->target->player
|| mobj->target->player->handtimer == 0)
{
P_RemoveMobj(mobj);
return false;
}
mobj->spritexscale = FRACUNIT;
mobj->spriteyscale = FRACUNIT;
break;
}
case MT_PLAYER: case MT_PLAYER:
break; // don't remove break; // don't remove
default: default:

View file

@ -76,6 +76,7 @@ typedef enum
SLIPTIDEZIP = 0x0080, SLIPTIDEZIP = 0x0080,
RINGSHOOTER = 0x0100, RINGSHOOTER = 0x0100,
WHIP = 0x0200, WHIP = 0x0200,
HAND = 0x0400,
} player_saveflags; } player_saveflags;
static inline void P_ArchivePlayer(savebuffer_t *save) static inline void P_ArchivePlayer(savebuffer_t *save)
@ -229,6 +230,9 @@ static void P_NetArchivePlayers(savebuffer_t *save)
if (players[i].whip) if (players[i].whip)
flags |= WHIP; flags |= WHIP;
if (players[i].hand)
flags |= HAND;
if (players[i].ringShooter) if (players[i].ringShooter)
flags |= RINGSHOOTER; flags |= RINGSHOOTER;
@ -258,6 +262,9 @@ static void P_NetArchivePlayers(savebuffer_t *save)
if (flags & WHIP) if (flags & WHIP)
WRITEUINT32(save->p, players[i].whip->mobjnum); WRITEUINT32(save->p, players[i].whip->mobjnum);
if (flags & HAND)
WRITEUINT32(save->p, players[i].hand->mobjnum);
if (flags & RINGSHOOTER) if (flags & RINGSHOOTER)
WRITEUINT32(save->p, players[i].ringShooter->mobjnum); WRITEUINT32(save->p, players[i].ringShooter->mobjnum);
@ -429,6 +436,10 @@ static void P_NetArchivePlayers(savebuffer_t *save)
WRITEUINT8(save->p, players[i].instaShieldCooldown); WRITEUINT8(save->p, players[i].instaShieldCooldown);
WRITEUINT8(save->p, players[i].guardCooldown); WRITEUINT8(save->p, players[i].guardCooldown);
WRITEUINT8(save->p, players[i].handtimer);
WRITEANGLE(save->p, players[i].besthanddirection);
WRITEINT16(save->p, players[i].incontrol); WRITEINT16(save->p, players[i].incontrol);
WRITEUINT8(save->p, players[i].markedfordeath); WRITEUINT8(save->p, players[i].markedfordeath);
@ -652,6 +663,9 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
if (flags & WHIP) if (flags & WHIP)
players[i].whip = (mobj_t *)(size_t)READUINT32(save->p); players[i].whip = (mobj_t *)(size_t)READUINT32(save->p);
if (flags & HAND)
players[i].hand = (mobj_t *)(size_t)READUINT32(save->p);
if (flags & RINGSHOOTER) if (flags & RINGSHOOTER)
players[i].ringShooter = (mobj_t *)(size_t)READUINT32(save->p); players[i].ringShooter = (mobj_t *)(size_t)READUINT32(save->p);
@ -824,6 +838,10 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
players[i].instaShieldCooldown = READUINT8(save->p); players[i].instaShieldCooldown = READUINT8(save->p);
players[i].guardCooldown = READUINT8(save->p); players[i].guardCooldown = READUINT8(save->p);
players[i].handtimer = READUINT8(save->p);
players[i].besthanddirection = READANGLE(save->p);
players[i].incontrol = READINT16(save->p); players[i].incontrol = READINT16(save->p);
players[i].markedfordeath = READUINT8(save->p); players[i].markedfordeath = READUINT8(save->p);
@ -5136,6 +5154,13 @@ static void P_RelinkPointers(void)
if (!P_SetTarget(&players[i].whip, P_FindNewPosition(temp))) if (!P_SetTarget(&players[i].whip, P_FindNewPosition(temp)))
CONS_Debug(DBG_GAMELOGIC, "whip not found on player %d\n", i); CONS_Debug(DBG_GAMELOGIC, "whip not found on player %d\n", i);
} }
if (players[i].hand)
{
temp = (UINT32)(size_t)players[i].hand;
players[i].hand = NULL;
if (!P_SetTarget(&players[i].hand, P_FindNewPosition(temp)))
CONS_Debug(DBG_GAMELOGIC, "hand not found on player %d\n", i);
}
if (players[i].ringShooter) if (players[i].ringShooter)
{ {
temp = (UINT32)(size_t)players[i].ringShooter; temp = (UINT32)(size_t)players[i].ringShooter;