Allow custom characters to define their own states for standard player animations

This commit is contained in:
LJ Sonic 2024-03-19 21:32:36 +01:00
parent 37a2507bfd
commit 351759ef14
11 changed files with 1334 additions and 14 deletions

View file

@ -6378,7 +6378,7 @@ void A_UnidusBall(mobj_t *actor)
else if (locvar1 == 2) else if (locvar1 == 2)
{ {
boolean skull = (actor->target->flags2 & MF2_SKULLFLY) == MF2_SKULLFLY; boolean skull = (actor->target->flags2 & MF2_SKULLFLY) == MF2_SKULLFLY;
if (actor->target->state == &states[actor->target->info->painstate]) if (P_IsMobjInPainState(actor->target))
{ {
P_KillMobj(actor, NULL, NULL, 0); P_KillMobj(actor, NULL, NULL, 0);
return; return;

View file

@ -412,7 +412,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
{ {
if (special->type == MT_STEAM) if (special->type == MT_STEAM)
{ {
if (player && player->mo->state == &states[player->mo->info->painstate]) // can't use gas jets when player is in pain! if (player && P_IsPlayerInState(player, S_PLAY_PAIN)) // can't use gas jets when player is in pain!
return; return;
fixed_t speed = special->info->mass; // gas jets use this for the vertical thrust fixed_t speed = special->info->mass; // gas jets use this for the vertical thrust

View file

@ -135,6 +135,7 @@ boolean P_TryCameraMove(fixed_t x, fixed_t y, camera_t *thiscam);
void P_SlideCameraMove(camera_t *thiscam); void P_SlideCameraMove(camera_t *thiscam);
boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcalled); boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcalled);
pflags_t P_GetJumpFlags(player_t *player); pflags_t P_GetJumpFlags(player_t *player);
statenum_t P_GetCanonicalPlayerState(player_t *player, statenum_t state);
boolean P_IsPlayerInState(player_t *player, statenum_t state); boolean P_IsPlayerInState(player_t *player, statenum_t state);
boolean P_IsPlayerInSuperTransformationState(player_t *player); boolean P_IsPlayerInSuperTransformationState(player_t *player);
boolean P_IsPlayerInNightsTransformationState(player_t *player); boolean P_IsPlayerInNightsTransformationState(player_t *player);
@ -556,5 +557,6 @@ void P_DoSuperDetransformation(player_t *player);
void P_ExplodeMissile(mobj_t *mo); void P_ExplodeMissile(mobj_t *mo);
void P_CheckGravity(mobj_t *mo, boolean affect); void P_CheckGravity(mobj_t *mo, boolean affect);
void P_SetPitchRollFromSlope(mobj_t *mo, pslope_t *slope); void P_SetPitchRollFromSlope(mobj_t *mo, pslope_t *slope);
boolean P_IsMobjInPainState(mobj_t *mobj);
#endif // __P_LOCAL__ #endif // __P_LOCAL__

View file

@ -509,7 +509,7 @@ static void P_DoFan(mobj_t *fan, mobj_t *object)
fixed_t speed = fan->info->mass; // fans use this for the vertical thrust fixed_t speed = fan->info->mass; // fans use this for the vertical thrust
SINT8 flipval = P_MobjFlip(fan); // virtually everything here centers around the thruster's gravity, not the object's! SINT8 flipval = P_MobjFlip(fan); // virtually everything here centers around the thruster's gravity, not the object's!
if (p && object->state == &states[object->info->painstate]) // can't use fans when player is in pain! if (p && P_IsPlayerInState(p, S_PLAY_PAIN)) // can't use fans when player is in pain!
return; return;
// is object's top below thruster's position? if not, calculate distance between their bottoms // is object's top below thruster's position? if not, calculate distance between their bottoms

View file

@ -36,6 +36,7 @@
#include "p_slopes.h" #include "p_slopes.h"
#include "f_finale.h" #include "f_finale.h"
#include "m_cond.h" #include "m_cond.h"
#include "simple_hashmap.h"
#include "netcode/net_command.h" #include "netcode/net_command.h"
static CV_PossibleValue_t CV_BobSpeed[] = {{0, "MIN"}, {4*FRACUNIT, "MAX"}, {0, NULL}}; static CV_PossibleValue_t CV_BobSpeed[] = {{0, "MIN"}, {4*FRACUNIT, "MAX"}, {0, NULL}};
@ -195,9 +196,9 @@ static void P_CyclePlayerMobjState(mobj_t *mobj)
} }
} }
static panim_t GetPlayerAnimationFromState(statenum_t state) static panim_t GetPlayerAnimationFromState(player_t *player, statenum_t state)
{ {
switch(state) switch(P_GetCanonicalPlayerState(player, state))
{ {
case S_PLAY_STND: case S_PLAY_STND:
case S_PLAY_WAIT: case S_PLAY_WAIT:
@ -273,6 +274,12 @@ static boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
I_Error("P_SetPlayerMobjState used for non-player mobj. Use P_SetMobjState instead!\n(Mobj type: %d, State: %d)", mobj->type, state); I_Error("P_SetPlayerMobjState used for non-player mobj. Use P_SetMobjState instead!\n(Mobj type: %d, State: %d)", mobj->type, state);
#endif #endif
// If the state has been overriden for this skin, use the replacement instead
statenum_t customskinstate;
SIMPLEHASH_FIND_INT(skins[player->skin]->defaulttocustomstate, hashentry_int32_int32_t, state, S_NULL, customskinstate)
if (customskinstate)
state = customskinstate;
// Catch falling for nojumpspin // Catch falling for nojumpspin
if ((state == S_PLAY_JUMP) && (player->charflags & SF_NOJUMPSPIN) && (P_MobjFlip(mobj)*mobj->momz < 0)) if ((state == S_PLAY_JUMP) && (player->charflags & SF_NOJUMPSPIN) && (P_MobjFlip(mobj)*mobj->momz < 0))
return P_SetPlayerMobjState(mobj, S_PLAY_FALL); return P_SetPlayerMobjState(mobj, S_PLAY_FALL);
@ -302,14 +309,16 @@ static boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
return true; return true;
} }
// You were in pain state after taking a hit, and you're moving out of pain state now? // You were in pain state after taking a hit, and you're moving out of pain state now?
else if (mobj->state == &states[mobj->info->painstate] && player->powers[pw_flashing] == flashingtics && state != mobj->info->painstate) else if (P_IsPlayerInState(player, S_PLAY_PAIN)
&& player->powers[pw_flashing] == flashingtics
&& P_GetCanonicalPlayerState(player, state) != S_PLAY_PAIN)
{ {
// Start flashing, since you've landed. // Start flashing, since you've landed.
player->powers[pw_flashing] = flashingtics-1; player->powers[pw_flashing] = flashingtics-1;
P_DoPityCheck(player); P_DoPityCheck(player);
} }
player->panim = GetPlayerAnimationFromState(state); player->panim = GetPlayerAnimationFromState(player, state);
if (recursion++) // if recursion detected, if (recursion++) // if recursion detected,
memset(seenstate = tempstate, 0, sizeof tempstate); // clear state table memset(seenstate = tempstate, 0, sizeof tempstate); // clear state table
@ -14339,3 +14348,11 @@ mobj_t *P_SpawnMobjFromMobj(mobj_t *mobj, fixed_t xofs, fixed_t yofs, fixed_t zo
return newmobj; return newmobj;
} }
boolean P_IsMobjInPainState(mobj_t *mobj)
{
if (mobj->player)
return P_IsPlayerInState(mobj->player, S_PLAY_PAIN);
else
return (mobj->state == &states[mobj->info->painstate]);
}

View file

@ -4758,7 +4758,7 @@ static void P_ProcessRopeHang(player_t *player, mtag_t sectag)
if (player->cmd.buttons & BT_SPIN) if (player->cmd.buttons & BT_SPIN)
return; return;
if (!(player->pflags & PF_SLIDING) && player->mo->state == &states[player->mo->info->painstate]) if (!(player->pflags & PF_SLIDING) && P_IsPlayerInState(player, S_PLAY_PAIN))
return; return;
if (player->exiting) if (player->exiting)
@ -8805,7 +8805,7 @@ void T_Pusher(pusher_t *p)
if (thing->player && thing->player->powers[pw_carry] == CR_ROPEHANG) if (thing->player && thing->player->powers[pw_carry] == CR_ROPEHANG)
continue; continue;
if (thing->player && (thing->state == &states[thing->info->painstate]) && (thing->player->powers[pw_flashing] > (flashingtics/4)*3 && thing->player->powers[pw_flashing] <= flashingtics)) if (thing->player && P_IsPlayerInState(thing->player, S_PLAY_PAIN) && (thing->player->powers[pw_flashing] > (flashingtics/4)*3 && thing->player->powers[pw_flashing] <= flashingtics))
continue; continue;
inFOF = touching = moved = false; inFOF = touching = moved = false;

View file

@ -47,6 +47,7 @@
#include "m_cheat.h" #include "m_cheat.h"
// Thok camera snap (ctrl-f "chalupa") // Thok camera snap (ctrl-f "chalupa")
#include "g_input.h" #include "g_input.h"
#include "simple_hashmap.h"
#ifdef HW3SOUND #ifdef HW3SOUND
#include "hardware/hw3sound.h" #include "hardware/hw3sound.h"
@ -967,9 +968,20 @@ pflags_t P_GetJumpFlags(player_t *player)
return (PF_JUMPED|PF_NOJUMPDAMAGE); return (PF_JUMPED|PF_NOJUMPDAMAGE);
return PF_JUMPED; return PF_JUMPED;
} }
// If the state is a custom state for the player's skin, retrieve its "canonical" state
// e.g. S_SKIN_BIGTHECAT_WALK => S_PLAY_WALK
statenum_t P_GetCanonicalPlayerState(player_t *player, statenum_t state)
{
skin_t *skin = skins[player->skin];
statenum_t mappedstate;
SIMPLEHASH_FIND_INT(skin->customtodefaultstate, hashentry_int32_int32_t, state, state, mappedstate)
return mappedstate;
}
boolean P_IsPlayerInState(player_t *player, statenum_t state) boolean P_IsPlayerInState(player_t *player, statenum_t state)
{ {
return (player->mo->state == &states[state]); return (P_GetCanonicalPlayerState(player, player->mo->state - states) == state);
} }
boolean P_IsPlayerInSuperTransformationState(player_t *player) boolean P_IsPlayerInSuperTransformationState(player_t *player)
@ -1020,7 +1032,7 @@ void P_DoPlayerPain(player_t *player, mobj_t *source, mobj_t *inflictor)
fixed_t fallbackspeed; fixed_t fallbackspeed;
P_ResetPlayer(player); P_ResetPlayer(player);
P_SetMobjState(player->mo, player->mo->info->painstate); P_SetMobjState(player->mo, S_PLAY_PAIN);
if (player->mo->eflags & MFE_VERTICALFLIP) if (player->mo->eflags & MFE_VERTICALFLIP)
player->mo->z--; player->mo->z--;
@ -12796,7 +12808,7 @@ void P_PlayerAfterThink(player_t *player)
S_StartSound(NULL, sfx_wepchg); S_StartSound(NULL, sfx_wepchg);
if ((player->pflags & PF_SLIDING) && ((player->pflags & (PF_JUMPED|PF_NOJUMPDAMAGE)) != PF_JUMPED)) if ((player->pflags & PF_SLIDING) && ((player->pflags & (PF_JUMPED|PF_NOJUMPDAMAGE)) != PF_JUMPED))
P_SetMobjState(player->mo, player->mo->info->painstate); P_SetMobjState(player->mo, S_PLAY_PAIN);
/* if (player->powers[pw_carry] == CR_NONE && player->mo->tracer && !player->homing) /* if (player->powers[pw_carry] == CR_NONE && player->mo->tracer && !player->homing)
P_SetTarget(&player->mo->tracer, NULL); P_SetTarget(&player->mo->tracer, NULL);
@ -13235,7 +13247,7 @@ boolean P_PlayerCanEnterSpinGaps(player_t *player)
boolean P_PlayerShouldUseSpinHeight(player_t *player) boolean P_PlayerShouldUseSpinHeight(player_t *player)
{ {
return ((player->pflags & (PF_SPINNING|PF_SLIDING|PF_GLIDING)) return ((player->pflags & (PF_SPINNING|PF_SLIDING|PF_GLIDING))
|| (player->mo->state == &states[player->mo->info->painstate]) || P_IsPlayerInState(player, S_PLAY_PAIN)
|| (player->panim == PA_ROLL) || (player->panim == PA_ROLL)
|| ((player->powers[pw_tailsfly] || (player->charability == CA_FLY && P_IsPlayerInState(player, S_PLAY_FLY_TIRED))) || ((player->powers[pw_tailsfly] || (player->charability == CA_FLY && P_IsPlayerInState(player, S_PLAY_FLY_TIRED)))
&& !(player->charflags & SF_NOJUMPSPIN)) && !(player->charflags & SF_NOJUMPSPIN))

View file

@ -28,6 +28,7 @@
#include "p_local.h" #include "p_local.h"
#include "dehacked.h" // get_number (for thok) #include "dehacked.h" // get_number (for thok)
#include "m_cond.h" #include "m_cond.h"
#include "deh_tables.h"
#ifdef HWRENDER #ifdef HWRENDER
#include "hardware/hw_md2.h" #include "hardware/hw_md2.h"
#endif #endif
@ -773,6 +774,90 @@ static boolean R_ProcessPatchableFields(skin_t *skin, char *stoken, char *value)
return true; return true;
} }
// e.g. "RUN" => S_PLAY_RUN, "RUN1" => S_PLAY_RUN, "RUN8" => S_PLAY_RUN
static statenum_t GetCanonicalPlayerStateNumByName(const char *name)
{
if (startswith(name, "STAND")) return S_PLAY_STND;
else if (startswith(name, "STND")) return S_PLAY_STND;
else if (startswith(name, "WAIT")) return S_PLAY_WAIT;
else if (startswith(name, "WALK")) return S_PLAY_WALK;
else if (startswith(name, "SKID")) return S_PLAY_SKID;
else if (startswith(name, "RUN")) return S_PLAY_RUN;
else if (startswith(name, "DASH")) return S_PLAY_DASH;
else if (startswith(name, "PAIN")) return S_PLAY_PAIN;
else if (startswith(name, "STUN")) return S_PLAY_STUN;
else if (startswith(name, "DEAD")) return S_PLAY_DEAD;
else if (startswith(name, "DRWN")) return S_PLAY_DRWN;
else if (startswith(name, "ROLL")) return S_PLAY_ROLL;
else if (startswith(name, "GASP")) return S_PLAY_GASP;
else if (startswith(name, "JUMP")) return S_PLAY_JUMP;
else if (startswith(name, "SPRING")) return S_PLAY_SPRING;
else if (startswith(name, "FALL")) return S_PLAY_FALL;
else if (startswith(name, "EDGE")) return S_PLAY_EDGE;
else if (startswith(name, "RIDE")) return S_PLAY_RIDE;
else if (startswith(name, "SPINDASH")) return S_PLAY_SPINDASH;
else if (startswith(name, "FLY")) return S_PLAY_FLY;
else if (startswith(name, "SWIM")) return S_PLAY_SWIM;
else if (startswith(name, "FLY_TIRED")) return S_PLAY_FLY_TIRED;
else if (startswith(name, "GLIDE")) return S_PLAY_GLIDE;
else if (startswith(name, "GLIDE_LANDING")) return S_PLAY_GLIDE_LANDING;
else if (startswith(name, "CLING")) return S_PLAY_CLING;
else if (startswith(name, "CLIMB")) return S_PLAY_CLIMB;
else if (startswith(name, "FLOAT")) return S_PLAY_FLOAT;
else if (startswith(name, "FLOAT_RUN")) return S_PLAY_FLOAT_RUN;
else if (startswith(name, "BOUNCE")) return S_PLAY_BOUNCE;
else if (startswith(name, "BOUNCE_LANDING")) return S_PLAY_BOUNCE_LANDING;
else if (startswith(name, "FIRE")) return S_PLAY_FIRE;
else if (startswith(name, "FIRE_FINISH")) return S_PLAY_FIRE_FINISH;
else if (startswith(name, "TWINSPIN")) return S_PLAY_TWINSPIN;
else if (startswith(name, "MELEE")) return S_PLAY_MELEE;
else if (startswith(name, "MELEE_FINISH")) return S_PLAY_MELEE_FINISH;
else if (startswith(name, "MELEE_LANDING")) return S_PLAY_MELEE_LANDING;
else if (startswith(name, "NIGHTS_STAND")) return S_PLAY_NIGHTS_STAND;
else if (startswith(name, "NIGHTS_FLOAT")) return S_PLAY_NIGHTS_FLOAT;
else if (startswith(name, "NIGHTS_FLY")) return S_PLAY_NIGHTS_FLY;
else if (startswith(name, "NIGHTS_DRILL")) return S_PLAY_NIGHTS_DRILL;
else if (startswith(name, "NIGHTS_STUN")) return S_PLAY_NIGHTS_STUN;
else if (startswith(name, "NIGHTS_PULL")) return S_PLAY_NIGHTS_PULL;
else if (startswith(name, "NIGHTS_ATTACK")) return S_PLAY_NIGHTS_ATTACK;
else return S_NULL;
}
static void CacheCustomSkinStates(skin_t *skin)
{
SIMPLEHASH_CLEAR(skin->defaulttocustomstate, hashentry_int32_int32_t)
SIMPLEHASH_CLEAR(skin->customtodefaultstate, hashentry_int32_int32_t)
char *skinstateprefix = va("SKIN_%s_", skin->name);
strupr(skinstateprefix);
size_t skinstateprefixlen = strlen(skinstateprefix);
for (INT32 state = S_FIRSTFREESLOT; state <= S_LASTFREESLOT; state++)
{
const char *statename = FREE_STATES[state - S_FIRSTFREESLOT];
if (!statename)
continue;
if (strncmp(statename, skinstateprefix, skinstateprefixlen))
continue;
statenum_t defaultstate = GetCanonicalPlayerStateNumByName(&statename[skinstateprefixlen]);
if (defaultstate)
{
CONS_Printf("custom skin state: %s => %s\n", &statename[skinstateprefixlen], statename);
// If a default state is overriden by multiple custom states, use the first one as reference
// e.g. WALK+WALK2+WALK3+WALK4 instead of just WALK
statenum_t alreadyoverriden;
SIMPLEHASH_FIND_INT(skin->defaulttocustomstate, hashentry_int32_int32_t, defaultstate, S_NULL, alreadyoverriden)
if (!alreadyoverriden)
SIMPLEHASH_REPLACE_INT(skin->defaulttocustomstate, hashentry_int32_int32_t, defaultstate, state)
SIMPLEHASH_REPLACE_INT(skin->customtodefaultstate, hashentry_int32_int32_t, state, defaultstate)
}
}
}
// //
// Find skin sprites, sounds & optional status bar face, & add them // Find skin sprites, sounds & optional status bar face, & add them
// //
@ -927,6 +1012,8 @@ next_token:
R_FlushTranslationColormapCache(); R_FlushTranslationColormapCache();
CacheCustomSkinStates(skin);
if (mainfile == false) if (mainfile == false)
CONS_Printf(M_GetText("Added skin '%s'\n"), skin->name); CONS_Printf(M_GetText("Added skin '%s'\n"), skin->name);

View file

@ -20,6 +20,7 @@
#include "r_patch.h" #include "r_patch.h"
#include "r_picformats.h" // spriteinfo_t #include "r_picformats.h" // spriteinfo_t
#include "r_defs.h" // spritedef_t #include "r_defs.h" // spritedef_t
#include "simple_hashmap.h"
/// Defaults /// Defaults
#define SKINNAMESIZE 16 #define SKINNAMESIZE 16
@ -88,6 +89,9 @@ typedef struct
spritedef_t sprites[NUMPLAYERSPRITES]; spritedef_t sprites[NUMPLAYERSPRITES];
spriteinfo_t sprinfo[NUMPLAYERSPRITES]; spriteinfo_t sprinfo[NUMPLAYERSPRITES];
} super; } super;
hashentry_int32_int32_t *defaulttocustomstate; // e.g. S_PLAY_WALK => S_SKIN_BIGTHECAT_WALK
hashentry_int32_int32_t *customtodefaultstate; // e.g. S_SKIN_BIGTHECAT_WALK => S_PLAY_WALK
} skin_t; } skin_t;
/// Externs /// Externs

58
src/simple_hashmap.h Normal file
View file

@ -0,0 +1,58 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2024 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file simplehash.h
/// \brief Macros for handling basic hashmap types
#ifndef __SIMPLEHASH__
#define __SIMPLEHASH__
#include "uthash.h"
typedef struct
{
INT32 k;
INT32 v;
UT_hash_handle hh;
} hashentry_int32_int32_t;
// hashmap<type>[key] = value;
#define SIMPLEHASH_REPLACE_INT(hashmap, type, key, value) \
{ \
type *entry = malloc(sizeof(type)); \
if (!entry) \
I_Error("%s:%d: Out of memory\n", __func__, __LINE__); \
entry->k = (key); \
entry->v = (value); \
\
type *oldentry; \
HASH_REPLACE_INT((hashmap), k, entry, oldentry); \
if (oldentry) \
free(oldentry); \
}
#define SIMPLEHASH_CLEAR(hashmap, type) \
{ \
type *entry, *tmpentry; \
HASH_ITER(hh, (hashmap), entry, tmpentry) \
{ \
HASH_DEL((hashmap), entry); \
free(entry); \
} \
}
// value = hashmap<type>[key] or fallback;
#define SIMPLEHASH_FIND_INT(hashmap, type, key, fallback, value) \
{ \
int tmpkey = (key); \
type *entry; \
HASH_FIND_INT((hashmap), &tmpkey, entry); \
(value) = entry ? entry->v : (int)(fallback); \
}
#endif //__SIMPLEHASH__

1140
src/uthash.h Normal file

File diff suppressed because it is too large Load diff