Merge branch 'metalrecording' into 'master'

Metal battle, recording, and playback improvements

Closes #283 and #215

See merge request STJr/SRB2Internal!431
This commit is contained in:
MascaraSnake 2019-11-12 17:49:53 -05:00
commit c5ea20a699
16 changed files with 684 additions and 313 deletions

View file

@ -2491,7 +2491,7 @@ static void CL_RemovePlayer(INT32 playernum, INT32 reason)
void CL_Reset(void) void CL_Reset(void)
{ {
if (metalrecording) if (metalrecording)
G_StopMetalRecording(); G_StopMetalRecording(false);
if (metalplayback) if (metalplayback)
G_StopMetalDemo(); G_StopMetalDemo();
if (demorecording) if (demorecording)

View file

@ -1211,9 +1211,9 @@ static void SendNameAndColor(void)
players[consoleplayer].mo->color = players[consoleplayer].skincolor; players[consoleplayer].mo->color = players[consoleplayer].skincolor;
if (metalrecording) if (metalrecording)
{ // Metal Sonic is Sonic, obviously. { // Starring Metal Sonic as themselves, obviously.
SetPlayerSkinByNum(consoleplayer, 0); SetPlayerSkinByNum(consoleplayer, 5);
CV_StealthSet(&cv_skin, skins[0].name); CV_StealthSet(&cv_skin, skins[5].name);
} }
else if ((foundskin = R_SkinAvailable(cv_skin.string)) != -1 && R_SkinUsable(consoleplayer, foundskin)) else if ((foundskin = R_SkinAvailable(cv_skin.string)) != -1 && R_SkinUsable(consoleplayer, foundskin))
{ {

View file

@ -5375,25 +5375,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
"S_CYBRAKDEMONVILEEXPLOSION3", "S_CYBRAKDEMONVILEEXPLOSION3",
// Metal Sonic (Race) // Metal Sonic (Race)
// S_PLAY_STND "S_METALSONIC_RACE",
"S_METALSONIC_STAND",
// S_PLAY_TAP1
"S_METALSONIC_WAIT1",
"S_METALSONIC_WAIT2",
// S_PLAY_WALK
"S_METALSONIC_WALK1",
"S_METALSONIC_WALK2",
"S_METALSONIC_WALK3",
"S_METALSONIC_WALK4",
"S_METALSONIC_WALK5",
"S_METALSONIC_WALK6",
"S_METALSONIC_WALK7",
"S_METALSONIC_WALK8",
// S_PLAY_SPD1
"S_METALSONIC_RUN1",
"S_METALSONIC_RUN2",
"S_METALSONIC_RUN3",
"S_METALSONIC_RUN4",
// Metal Sonic (Battle) // Metal Sonic (Battle)
"S_METALSONIC_FLOAT", "S_METALSONIC_FLOAT",
"S_METALSONIC_VECTOR", "S_METALSONIC_VECTOR",

View file

@ -289,7 +289,7 @@ static struct {
// There is no conflict here. // There is no conflict here.
typedef struct demoghost { typedef struct demoghost {
UINT8 checksum[16]; UINT8 checksum[16];
UINT8 *buffer, *p, color; UINT8 *buffer, *p, color, fadein;
UINT16 version; UINT16 version;
mobj_t oldmo, *mo; mobj_t oldmo, *mo;
struct demoghost *next; struct demoghost *next;
@ -2755,6 +2755,8 @@ void G_DoReborn(INT32 playernum)
LUAh_MapChange(gamemap); LUAh_MapChange(gamemap);
#endif #endif
G_DoLoadLevel(true); G_DoLoadLevel(true);
if (metalrecording)
G_BeginMetal();
return; return;
} }
} }
@ -2922,7 +2924,7 @@ boolean G_GametypeUsesLives(void)
{ {
// Coop, Competitive // Coop, Competitive
if ((gametype == GT_COOP || gametype == GT_COMPETITION) if ((gametype == GT_COOP || gametype == GT_COMPETITION)
&& !modeattacking // No lives in Time Attack && !(modeattacking || metalrecording) // No lives in Time Attack
//&& !G_IsSpecialStage(gamemap) //&& !G_IsSpecialStage(gamemap)
&& !(maptol & TOL_NIGHTS)) // No lives in NiGHTS && !(maptol & TOL_NIGHTS)) // No lives in NiGHTS
return true; return true;
@ -3053,7 +3055,7 @@ static void G_DoCompleted(void)
if (metalplayback) if (metalplayback)
G_StopMetalDemo(); G_StopMetalDemo();
if (metalrecording) if (metalrecording)
G_StopMetalRecording(); G_StopMetalRecording(false);
for (i = 0; i < MAXPLAYERS; i++) for (i = 0; i < MAXPLAYERS; i++)
if (playeringame[i]) if (playeringame[i])
@ -4049,7 +4051,7 @@ char *G_BuildMapTitle(INT32 mapnum)
// DEMO RECORDING // DEMO RECORDING
// //
#define DEMOVERSION 0x000a #define DEMOVERSION 0x000c
#define DEMOHEADER "\xF0" "SRB2Replay" "\x0F" #define DEMOHEADER "\xF0" "SRB2Replay" "\x0F"
#define DF_GHOST 0x01 // This demo contains ghost data too! #define DF_GHOST 0x01 // This demo contains ghost data too!
@ -4065,6 +4067,8 @@ char *G_BuildMapTitle(INT32 mapnum)
#define ZT_BUTTONS 0x08 #define ZT_BUTTONS 0x08
#define ZT_AIMING 0x10 #define ZT_AIMING 0x10
#define DEMOMARKER 0x80 // demoend #define DEMOMARKER 0x80 // demoend
#define METALDEATH 0x44
#define METALSNICE 0x69
static ticcmd_t oldcmd; static ticcmd_t oldcmd;
@ -4073,7 +4077,6 @@ static ticcmd_t oldcmd;
#define GZT_MOMXY 0x02 #define GZT_MOMXY 0x02
#define GZT_MOMZ 0x04 #define GZT_MOMZ 0x04
#define GZT_ANGLE 0x08 #define GZT_ANGLE 0x08
// Not used for Metal Sonic
#define GZT_FRAME 0x10 // Animation frame #define GZT_FRAME 0x10 // Animation frame
#define GZT_SPR2 0x20 // Player animations #define GZT_SPR2 0x20 // Player animations
#define GZT_EXTRA 0x40 #define GZT_EXTRA 0x40
@ -4089,7 +4092,15 @@ static ticcmd_t oldcmd;
#define EZT_SCALE 0x10 // Changed size #define EZT_SCALE 0x10 // Changed size
#define EZT_HIT 0x20 // Damaged a mobj #define EZT_HIT 0x20 // Damaged a mobj
#define EZT_SPRITE 0x40 // Changed sprite set completely out of PLAY (NiGHTS, SOCs, whatever) #define EZT_SPRITE 0x40 // Changed sprite set completely out of PLAY (NiGHTS, SOCs, whatever)
// spare EZT slot 0x80 #define EZT_HEIGHT 0x80 // Changed height
// GZT_FOLLOW flags
#define FZT_SPAWNED 0x01 // just been spawned
#define FZT_SKIN 0x02 // has skin
#define FZT_LINKDRAW 0x04 // has linkdraw (combine with spawned only)
#define FZT_COLORIZED 0x08 // colorized (ditto)
#define FZT_SCALE 0x10 // different scale to object
// spare FZT slots 0x20 to 0x80
static mobj_t oldmetal, oldghost; static mobj_t oldmetal, oldghost;
@ -4215,28 +4226,28 @@ void G_WriteDemoTiccmd(ticcmd_t *cmd, INT32 playernum)
void G_GhostAddThok(void) void G_GhostAddThok(void)
{ {
if (!demorecording || !(demoflags & DF_GHOST)) if (!metalrecording && (!demorecording || !(demoflags & DF_GHOST)))
return; return;
ghostext.flags = (ghostext.flags & ~EZT_THOKMASK) | EZT_THOK; ghostext.flags = (ghostext.flags & ~EZT_THOKMASK) | EZT_THOK;
} }
void G_GhostAddSpin(void) void G_GhostAddSpin(void)
{ {
if (!demorecording || !(demoflags & DF_GHOST)) if (!metalrecording && (!demorecording || !(demoflags & DF_GHOST)))
return; return;
ghostext.flags = (ghostext.flags & ~EZT_THOKMASK) | EZT_SPIN; ghostext.flags = (ghostext.flags & ~EZT_THOKMASK) | EZT_SPIN;
} }
void G_GhostAddRev(void) void G_GhostAddRev(void)
{ {
if (!demorecording || !(demoflags & DF_GHOST)) if (!metalrecording && (!demorecording || !(demoflags & DF_GHOST)))
return; return;
ghostext.flags = (ghostext.flags & ~EZT_THOKMASK) | EZT_REV; ghostext.flags = (ghostext.flags & ~EZT_THOKMASK) | EZT_REV;
} }
void G_GhostAddFlip(void) void G_GhostAddFlip(void)
{ {
if (!demorecording || !(demoflags & DF_GHOST)) if (!metalrecording && (!demorecording || !(demoflags & DF_GHOST)))
return; return;
ghostext.flags |= EZT_FLIP; ghostext.flags |= EZT_FLIP;
} }
@ -4256,7 +4267,7 @@ void G_GhostAddColor(ghostcolor_t color)
void G_GhostAddScale(fixed_t scale) void G_GhostAddScale(fixed_t scale)
{ {
if (!demorecording || !(demoflags & DF_GHOST)) if (!metalrecording && (!demorecording || !(demoflags & DF_GHOST)))
return; return;
if (ghostext.lastscale == scale) if (ghostext.lastscale == scale)
{ {
@ -4282,6 +4293,7 @@ void G_WriteGhostTic(mobj_t *ghost)
char ziptic = 0; char ziptic = 0;
UINT8 *ziptic_p; UINT8 *ziptic_p;
UINT32 i; UINT32 i;
fixed_t height;
if (!demo_p) if (!demo_p)
return; return;
@ -4371,6 +4383,12 @@ void G_WriteGhostTic(mobj_t *ghost)
ghostext.flags |= EZT_SPRITE; ghostext.flags |= EZT_SPRITE;
} }
if ((height = FixedDiv(ghost->height, ghost->scale)) != oldghost.height)
{
oldghost.height = height;
ghostext.flags |= EZT_HEIGHT;
}
if (ghostext.flags) if (ghostext.flags)
{ {
ziptic |= GZT_EXTRA; ziptic |= GZT_EXTRA;
@ -4410,26 +4428,60 @@ void G_WriteGhostTic(mobj_t *ghost)
ghostext.hitlist = NULL; ghostext.hitlist = NULL;
} }
if (ghostext.flags & EZT_SPRITE) if (ghostext.flags & EZT_SPRITE)
WRITEUINT8(demo_p,oldghost.sprite); WRITEUINT16(demo_p,oldghost.sprite);
if (ghostext.flags & EZT_HEIGHT)
{
height >>= FRACBITS;
WRITEINT16(demo_p, height);
}
ghostext.flags = 0; ghostext.flags = 0;
} }
if (ghost->player && ghost->player->followmobj) // bloats tails runs but what can ya do if (ghost->player && ghost->player->followmobj && !(ghost->player->followmobj->sprite == SPR_NULL || (ghost->player->followmobj->flags2 & MF2_DONTDRAW))) // bloats tails runs but what can ya do
{ {
INT16 temp; INT16 temp;
UINT8 *followtic_p = demo_p++;
UINT8 followtic = 0;
ziptic |= GZT_FOLLOW; ziptic |= GZT_FOLLOW;
if (ghost->player->followmobj->skin)
followtic |= FZT_SKIN;
if (!(oldghost.flags2 & MF2_AMBUSH))
{
followtic |= FZT_SPAWNED;
WRITEINT16(demo_p,ghost->player->followmobj->info->height>>FRACBITS);
if (ghost->player->followmobj->flags2 & MF2_LINKDRAW)
followtic |= FZT_LINKDRAW;
if (ghost->player->followmobj->colorized)
followtic |= FZT_COLORIZED;
if (followtic & FZT_SKIN)
WRITEUINT8(demo_p,(UINT8)(((skin_t *)(ghost->player->followmobj->skin))-skins));
oldghost.flags2 |= MF2_AMBUSH;
}
temp = (INT16)((ghost->player->followmobj->x-ghost->x)>>8); temp = (INT16)((ghost->player->followmobj->x-ghost->x)>>8);
WRITEINT16(demo_p,temp); WRITEINT16(demo_p,temp);
temp = (INT16)((ghost->player->followmobj->y-ghost->y)>>8); temp = (INT16)((ghost->player->followmobj->y-ghost->y)>>8);
WRITEINT16(demo_p,temp); WRITEINT16(demo_p,temp);
temp = (INT16)((ghost->player->followmobj->z-ghost->z)>>8); temp = (INT16)((ghost->player->followmobj->z-ghost->z)>>8);
WRITEINT16(demo_p,temp); WRITEINT16(demo_p,temp);
WRITEUINT8(demo_p,ghost->player->followmobj->sprite); if (followtic & FZT_SKIN)
WRITEUINT8(demo_p,ghost->player->followmobj->sprite2); WRITEUINT8(demo_p,ghost->player->followmobj->sprite2);
WRITEUINT16(demo_p,ghost->player->followmobj->sprite);
WRITEUINT8(demo_p,(ghost->player->followmobj->frame & FF_FRAMEMASK)); WRITEUINT8(demo_p,(ghost->player->followmobj->frame & FF_FRAMEMASK));
WRITEUINT8(demo_p,ghost->player->followmobj->color);
if (ghost->player->followmobj->scale != ghost->scale)
{
followtic |= FZT_SCALE;
WRITEFIXED(demo_p,ghost->player->followmobj->scale);
}
*followtic_p = followtic;
} }
else
oldghost.flags2 &= ~MF2_AMBUSH;
*ziptic_p = ziptic; *ziptic_p = ziptic;
@ -4533,15 +4585,28 @@ void G_ConsGhostTic(void)
} }
} }
if (xziptic & EZT_SPRITE) if (xziptic & EZT_SPRITE)
demo_p++; demo_p += sizeof(UINT16);
if (xziptic & EZT_HEIGHT)
demo_p += sizeof(INT16);
} }
if (ziptic & GZT_FOLLOW) if (ziptic & GZT_FOLLOW)
{ // Even more... { // Even more...
UINT8 followtic = READUINT8(demo_p);
if (followtic & FZT_SPAWNED)
{
demo_p += sizeof(INT16);
if (followtic & FZT_SKIN)
demo_p++;
}
if (followtic & FZT_SCALE)
demo_p += sizeof(fixed_t);
demo_p += sizeof(INT16); demo_p += sizeof(INT16);
demo_p += sizeof(INT16); demo_p += sizeof(INT16);
demo_p += sizeof(INT16); demo_p += sizeof(INT16);
demo_p++; if (followtic & FZT_SKIN)
demo_p++;
demo_p += sizeof(UINT16);
demo_p++; demo_p++;
demo_p++; demo_p++;
} }
@ -4629,6 +4694,11 @@ void G_GhostTicker(void)
g->mo->z = g->oldmo.z; g->mo->z = g->oldmo.z;
P_SetThingPosition(g->mo); P_SetThingPosition(g->mo);
g->mo->frame = g->oldmo.frame | tr_trans30<<FF_TRANSSHIFT; g->mo->frame = g->oldmo.frame | tr_trans30<<FF_TRANSSHIFT;
if (g->fadein)
{
g->mo->frame += (((--g->fadein)/6)<<FF_TRANSSHIFT); // this calc never exceeds 9 unless g->fadein is bad, and it's only set once, so...
g->mo->flags2 &= ~MF2_DONTDRAW;
}
g->mo->sprite2 = g->oldmo.sprite2; g->mo->sprite2 = g->oldmo.sprite2;
if (ziptic & GZT_EXTRA) if (ziptic & GZT_EXTRA)
@ -4686,33 +4756,38 @@ void G_GhostTicker(void)
break; break;
} }
} }
if (type == MT_GHOST) if (type != MT_NULL)
{ {
mobj = P_SpawnGhostMobj(g->mo); // does a large portion of the work for us if (type == MT_GHOST)
mobj->frame = (mobj->frame & ~FF_FRAMEMASK)|tr_trans60<<FF_TRANSSHIFT; // P_SpawnGhostMobj sets trans50, we want trans60
}
else
{
mobj = P_SpawnMobj(g->mo->x, g->mo->y, g->mo->z - FixedDiv(FixedMul(g->mo->info->height, g->mo->scale) - g->mo->height,3*FRACUNIT), MT_THOK);
mobj->sprite = states[mobjinfo[type].spawnstate].sprite;
mobj->frame = (states[mobjinfo[type].spawnstate].frame & FF_FRAMEMASK) | tr_trans60<<FF_TRANSSHIFT;
mobj->tics = -1; // nope.
mobj->color = g->mo->color;
if (g->mo->eflags & MFE_VERTICALFLIP)
{ {
mobj->flags2 |= MF2_OBJECTFLIP; mobj = P_SpawnGhostMobj(g->mo); // does a large portion of the work for us
mobj->eflags |= MFE_VERTICALFLIP; mobj->frame = (mobj->frame & ~FF_FRAMEMASK)|tr_trans60<<FF_TRANSSHIFT; // P_SpawnGhostMobj sets trans50, we want trans60
} }
P_SetScale(mobj, g->mo->scale); else
mobj->destscale = g->mo->scale; {
mobj = P_SpawnMobjFromMobj(g->mo, 0, 0, -FixedDiv(FixedMul(g->mo->info->height, g->mo->scale) - g->mo->height,3*FRACUNIT), MT_THOK);
mobj->sprite = states[mobjinfo[type].spawnstate].sprite;
mobj->frame = (states[mobjinfo[type].spawnstate].frame & FF_FRAMEMASK) | tr_trans60<<FF_TRANSSHIFT;
mobj->color = g->mo->color;
mobj->skin = g->mo->skin;
P_SetScale(mobj, (mobj->destscale = g->mo->scale));
if (type == MT_THOK) // spintrail-specific modification for MT_THOK
{
mobj->frame = FF_TRANS80;
mobj->fuse = mobj->tics;
}
mobj->tics = -1; // nope.
}
mobj->floorz = mobj->z;
mobj->ceilingz = mobj->z+mobj->height;
P_UnsetThingPosition(mobj);
mobj->flags = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY; // make an ATTEMPT to curb crazy SOCs fucking stuff up...
P_SetThingPosition(mobj);
if (!mobj->fuse)
mobj->fuse = 8;
P_SetTarget(&mobj->target, g->mo);
} }
mobj->floorz = mobj->z;
mobj->ceilingz = mobj->z+mobj->height;
P_UnsetThingPosition(mobj);
mobj->flags = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY; // make an ATTEMPT to curb crazy SOCs fucking stuff up...
P_SetThingPosition(mobj);
mobj->fuse = 8;
P_SetTarget(&mobj->target, g->mo);
} }
if (xziptic & EZT_HIT) if (xziptic & EZT_HIT)
{ // Spawn hit poofs for killing things! { // Spawn hit poofs for killing things!
@ -4742,7 +4817,12 @@ void G_GhostTicker(void)
} }
} }
if (xziptic & EZT_SPRITE) if (xziptic & EZT_SPRITE)
g->mo->sprite = READUINT8(g->p); g->mo->sprite = READUINT16(g->p);
if (xziptic & EZT_HEIGHT)
{
fixed_t temp = READINT16(g->p)<<FRACBITS;
g->mo->height = FixedMul(temp, g->mo->scale);
}
} }
// Tick ghost colors (Super and Mario Invincibility flashing) // Tick ghost colors (Super and Mario Invincibility flashing)
@ -4768,55 +4848,81 @@ void G_GhostTicker(void)
#define follow g->mo->tracer #define follow g->mo->tracer
if (ziptic & GZT_FOLLOW) if (ziptic & GZT_FOLLOW)
{ // Even more... { // Even more...
if (!follow) UINT8 followtic = READUINT8(g->p);
fixed_t temp;
if (followtic & FZT_SPAWNED)
{ {
mobj_t *newmo = P_SpawnMobj(g->mo->x, g->mo->y, g->mo->z, MT_GHOST); if (follow)
P_SetTarget(&g->mo->tracer, newmo); P_RemoveMobj(follow);
P_SetTarget(&newmo->tracer, g->mo); P_SetTarget(&follow, P_SpawnMobjFromMobj(g->mo, 0, 0, 0, MT_GHOST));
newmo->skin = g->mo->skin; P_SetTarget(&follow->tracer, g->mo);
newmo->tics = -1; follow->tics = -1;
newmo->flags2 |= MF2_LINKDRAW; temp = READINT16(g->p)<<FRACBITS;
follow->height = FixedMul(follow->scale, temp);
follow->eflags = (follow->eflags & ~MFE_VERTICALFLIP)|(g->mo->eflags & MFE_VERTICALFLIP); if (followtic & FZT_LINKDRAW)
follow->destscale = g->mo->destscale; follow->flags2 |= MF2_LINKDRAW;
if (followtic & FZT_COLORIZED)
follow->colorized = true;
if (followtic & FZT_SKIN)
follow->skin = &skins[READUINT8(g->p)];
}
if (follow)
{
if (followtic & FZT_SCALE)
follow->destscale = READFIXED(g->p);
else
follow->destscale = g->mo->destscale;
if (follow->destscale != follow->scale) if (follow->destscale != follow->scale)
P_SetScale(follow, follow->destscale); P_SetScale(follow, follow->destscale);
}
else P_UnsetThingPosition(follow);
{ temp = READINT16(g->p)<<8;
if (xziptic & EZT_FLIP) follow->x = g->mo->x + temp;
g->mo->eflags ^= MFE_VERTICALFLIP; temp = READINT16(g->p)<<8;
if (xziptic & EZT_SCALE) follow->y = g->mo->y + temp;
temp = READINT16(g->p)<<8;
follow->z = g->mo->z + temp;
P_SetThingPosition(follow);
if (followtic & FZT_SKIN)
follow->sprite2 = READUINT8(g->p);
else
follow->sprite2 = 0;
follow->sprite = READUINT16(g->p);
follow->frame = (READUINT8(g->p)) | (g->mo->frame & FF_TRANSMASK);
follow->angle = g->mo->angle;
follow->color = READUINT8(g->p);
if (!(followtic & FZT_SPAWNED))
{ {
follow->destscale = g->mo->destscale; if (xziptic & EZT_FLIP)
if (follow->destscale != follow->scale) {
P_SetScale(follow, follow->destscale); follow->flags2 ^= MF2_OBJECTFLIP;
follow->eflags ^= MFE_VERTICALFLIP;
}
} }
} }
P_UnsetThingPosition(follow);
follow->x = g->mo->x + (READINT16(g->p)<<8);
follow->y = g->mo->y + (READINT16(g->p)<<8);
follow->z = g->mo->z + (READINT16(g->p)<<8);
P_SetThingPosition(follow);
follow->sprite = READUINT8(g->p);
follow->sprite2 = READUINT8(g->p);
follow->frame = (READUINT8(g->p)) | tr_trans30<<FF_TRANSSHIFT;
follow->angle = g->mo->angle;
follow->color = g->mo->color;
} }
else if (follow) else if (follow)
{ {
P_RemoveMobj(follow); P_RemoveMobj(follow);
P_SetTarget(&follow, NULL); P_SetTarget(&follow, NULL);
} }
#undef follow
// Demo ends after ghost data. // Demo ends after ghost data.
if (*g->p == DEMOMARKER) if (*g->p == DEMOMARKER)
{ {
g->mo->momx = g->mo->momy = g->mo->momz = 0; g->mo->momx = g->mo->momy = g->mo->momz = 0;
#if 1 // freeze frame (maybe more useful for time attackers)
g->mo->colorized = true;
if (follow)
follow->colorized = true;
#else // dissapearing act
g->mo->fuse = TICRATE;
if (follow)
follow->fuse = TICRATE;
#endif
if (p) if (p)
p->next = g->next; p->next = g->next;
else else
@ -4825,17 +4931,41 @@ void G_GhostTicker(void)
continue; continue;
} }
p = g; p = g;
#undef follow
} }
} }
void G_ReadMetalTic(mobj_t *metal) void G_ReadMetalTic(mobj_t *metal)
{ {
UINT8 ziptic; UINT8 ziptic;
UINT16 speed; UINT8 xziptic = 0;
UINT8 statetype;
if (!metal_p) if (!metal_p)
return; return;
if (!metal->health)
{
G_StopMetalDemo();
return;
}
switch (*metal_p)
{
case METALSNICE:
break;
case METALDEATH:
if (metal->tracer)
P_RemoveMobj(metal->tracer);
P_KillMobj(metal, NULL, NULL, 0);
/* FALLTHRU */
case DEMOMARKER:
default:
// end of demo data stream
G_StopMetalDemo();
return;
}
metal_p++;
ziptic = READUINT8(metal_p); ziptic = READUINT8(metal_p);
// Read changes from the tic // Read changes from the tic
@ -4862,9 +4992,9 @@ void G_ReadMetalTic(mobj_t *metal)
if (ziptic & GZT_ANGLE) if (ziptic & GZT_ANGLE)
metal->angle = READUINT8(metal_p)<<24; metal->angle = READUINT8(metal_p)<<24;
if (ziptic & GZT_FRAME) if (ziptic & GZT_FRAME)
metal_p++; // Currently unused. (Metal Sonic figures out what he's doing his own damn self.) oldmetal.frame = READUINT32(metal_p);
if (ziptic & GZT_SPR2) if (ziptic & GZT_SPR2)
metal_p++; oldmetal.sprite2 = READUINT8(metal_p);
// Set movement, position, and angle // Set movement, position, and angle
// oldmetal contains where you're supposed to be. // oldmetal contains where you're supposed to be.
@ -4876,67 +5006,160 @@ void G_ReadMetalTic(mobj_t *metal)
metal->y = oldmetal.y; metal->y = oldmetal.y;
metal->z = oldmetal.z; metal->z = oldmetal.z;
P_SetThingPosition(metal); P_SetThingPosition(metal);
metal->frame = oldmetal.frame;
metal->sprite2 = oldmetal.sprite2;
if (ziptic & GZT_EXTRA) if (ziptic & GZT_EXTRA)
{ // But wait, there's more! { // But wait, there's more!
ziptic = READUINT8(metal_p); xziptic = READUINT8(metal_p);
if (ziptic & EZT_FLIP) if (xziptic & EZT_FLIP)
metal->eflags ^= MFE_VERTICALFLIP; metal->eflags ^= MFE_VERTICALFLIP;
if (ziptic & EZT_SCALE) if (xziptic & EZT_SCALE)
{ {
metal->destscale = READFIXED(metal_p); metal->destscale = READFIXED(metal_p);
if (metal->destscale != metal->scale) if (metal->destscale != metal->scale)
P_SetScale(metal, metal->destscale); P_SetScale(metal, metal->destscale);
} }
} if (xziptic & EZT_THOKMASK)
{ // Let's only spawn ONE of these per frame, thanks.
mobj_t *mobj;
INT32 type = -1;
if (metal->skin)
{
skin_t *skin = (skin_t *)metal->skin;
switch (xziptic & EZT_THOKMASK)
{
case EZT_THOK:
type = skin->thokitem < 0 ? (UINT32)mobjinfo[MT_PLAYER].painchance : (UINT32)skin->thokitem;
break;
case EZT_SPIN:
type = skin->spinitem < 0 ? (UINT32)mobjinfo[MT_PLAYER].damage : (UINT32)skin->spinitem;
break;
case EZT_REV:
type = skin->revitem < 0 ? (UINT32)mobjinfo[MT_PLAYER].raisestate : (UINT32)skin->revitem;
break;
}
}
if (type != MT_NULL)
{
if (type == MT_GHOST)
{
mobj = P_SpawnGhostMobj(metal); // does a large portion of the work for us
}
else
{
mobj = P_SpawnMobjFromMobj(metal, 0, 0, -FixedDiv(FixedMul(metal->info->height, metal->scale) - metal->height,3*FRACUNIT), MT_THOK);
mobj->sprite = states[mobjinfo[type].spawnstate].sprite;
mobj->frame = states[mobjinfo[type].spawnstate].frame;
mobj->angle = metal->angle;
mobj->color = metal->color;
mobj->skin = metal->skin;
P_SetScale(mobj, (mobj->destscale = metal->scale));
// Calculates player's speed based on distance-of-a-line formula if (type == MT_THOK) // spintrail-specific modification for MT_THOK
speed = FixedDiv(P_AproxDistance(oldmetal.momx, oldmetal.momy), metal->scale)>>FRACBITS; {
mobj->frame = FF_TRANS70;
// Use speed to decide an appropriate state mobj->fuse = mobj->tics;
if (speed > 28) // default skin runspeed }
statetype = 2; mobj->tics = -1; // nope.
else if (speed > 1) // stopspeed }
statetype = 1; mobj->floorz = mobj->z;
else mobj->ceilingz = mobj->z+mobj->height;
statetype = 0; P_UnsetThingPosition(mobj);
mobj->flags = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY; // make an ATTEMPT to curb crazy SOCs fucking stuff up...
// Set state P_SetThingPosition(mobj);
if (statetype != metal->threshold) if (!mobj->fuse)
{ mobj->fuse = 8;
switch (statetype) P_SetTarget(&mobj->target, metal);
{ }
case 2: // run }
P_SetMobjState(metal,metal->info->meleestate); if (xziptic & EZT_SPRITE)
break; metal->sprite = READUINT16(metal_p);
case 1: // walk if (xziptic & EZT_HEIGHT)
P_SetMobjState(metal,metal->info->seestate); {
break; fixed_t temp = READINT16(metal_p)<<FRACBITS;
default: // stand metal->height = FixedMul(temp, metal->scale);
P_SetMobjState(metal,metal->info->spawnstate);
break;
} }
metal->threshold = statetype;
} }
// TODO: Modify state durations based on movement speed, similar to players? #define follow metal->tracer
if (ziptic & GZT_FOLLOW)
{ // Even more...
UINT8 followtic = READUINT8(metal_p);
fixed_t temp;
if (followtic & FZT_SPAWNED)
{
if (follow)
P_RemoveMobj(follow);
P_SetTarget(&follow, P_SpawnMobjFromMobj(metal, 0, 0, 0, MT_GHOST));
P_SetTarget(&follow->tracer, metal);
follow->tics = -1;
temp = READINT16(metal_p)<<FRACBITS;
follow->height = FixedMul(follow->scale, temp);
if (*metal_p == DEMOMARKER) if (followtic & FZT_LINKDRAW)
{ follow->flags2 |= MF2_LINKDRAW;
// end of demo data stream
G_StopMetalDemo(); if (followtic & FZT_COLORIZED)
return; follow->colorized = true;
}
if (followtic & FZT_SKIN)
follow->skin = &skins[READUINT8(metal_p)];
}
if (follow)
{
if (followtic & FZT_SCALE)
follow->destscale = READFIXED(metal_p);
else
follow->destscale = metal->destscale;
if (follow->destscale != follow->scale)
P_SetScale(follow, follow->destscale);
P_UnsetThingPosition(follow);
temp = READINT16(metal_p)<<8;
follow->x = metal->x + temp;
temp = READINT16(metal_p)<<8;
follow->y = metal->y + temp;
temp = READINT16(metal_p)<<8;
follow->z = metal->z + temp;
P_SetThingPosition(follow);
if (followtic & FZT_SKIN)
follow->sprite2 = READUINT8(metal_p);
else
follow->sprite2 = 0;
follow->sprite = READUINT16(metal_p);
follow->frame = READUINT32(metal_p); // NOT & FF_FRAMEMASK here, so 32 bits
follow->angle = metal->angle;
follow->color = READUINT8(metal_p);
if (!(followtic & FZT_SPAWNED))
{
if (xziptic & EZT_FLIP)
{
follow->flags2 ^= MF2_OBJECTFLIP;
follow->eflags ^= MFE_VERTICALFLIP;
}
}
}
}
else if (follow)
{
P_RemoveMobj(follow);
P_SetTarget(&follow, NULL);
}
#undef follow
} }
void G_WriteMetalTic(mobj_t *metal) void G_WriteMetalTic(mobj_t *metal)
{ {
UINT8 ziptic = 0; UINT8 ziptic = 0;
UINT8 *ziptic_p; UINT8 *ziptic_p;
fixed_t height;
if (!demo_p) // demo_p will be NULL until the race start linedef executor is triggered! if (!demo_p) // demo_p will be NULL until the race start linedef executor is activated!
return; return;
WRITEUINT8(demo_p, METALSNICE);
ziptic_p = demo_p++; // the ziptic, written at the end of this function ziptic_p = demo_p++; // the ziptic, written at the end of this function
#define MAXMOM (0xFFFF<<8) #define MAXMOM (0xFFFF<<8)
@ -4949,10 +5172,10 @@ void G_WriteMetalTic(mobj_t *metal)
oldmetal.x = metal->x; oldmetal.x = metal->x;
oldmetal.y = metal->y; oldmetal.y = metal->y;
oldmetal.z = metal->z; oldmetal.z = metal->z;
ziptic |= GZT_XYZ;
WRITEFIXED(demo_p,oldmetal.x); WRITEFIXED(demo_p,oldmetal.x);
WRITEFIXED(demo_p,oldmetal.y); WRITEFIXED(demo_p,oldmetal.y);
WRITEFIXED(demo_p,oldmetal.z); WRITEFIXED(demo_p,oldmetal.z);
ziptic |= GZT_XYZ;
} }
else else
{ {
@ -4965,16 +5188,16 @@ void G_WriteMetalTic(mobj_t *metal)
{ {
oldmetal.momx = momx; oldmetal.momx = momx;
oldmetal.momy = momy; oldmetal.momy = momy;
ziptic |= GZT_MOMXY;
WRITEINT16(demo_p,momx); WRITEINT16(demo_p,momx);
WRITEINT16(demo_p,momy); WRITEINT16(demo_p,momy);
ziptic |= GZT_MOMXY;
} }
momx = (INT16)((metal->z-oldmetal.z)>>8); momx = (INT16)((metal->z-oldmetal.z)>>8);
if (momx != oldmetal.momz) if (momx != oldmetal.momz)
{ {
oldmetal.momz = momx; oldmetal.momz = momx;
WRITEINT16(demo_p,momx);
ziptic |= GZT_MOMZ; ziptic |= GZT_MOMZ;
WRITEINT16(demo_p,momx);
} }
// This SHOULD set oldmetal.x/y/z to match metal->x/y/z // This SHOULD set oldmetal.x/y/z to match metal->x/y/z
@ -4997,41 +5220,112 @@ void G_WriteMetalTic(mobj_t *metal)
WRITEUINT8(demo_p,oldmetal.angle); WRITEUINT8(demo_p,oldmetal.angle);
} }
// Metal Sonic does not need our state changes. // Store the sprite frame.
// ... currently. if ((metal->frame & FF_FRAMEMASK) != oldmetal.frame)
{ {
UINT8 *exttic_p = NULL; oldmetal.frame = metal->frame; // NOT & FF_FRAMEMASK here, so 32 bits
UINT8 exttic = 0; ziptic |= GZT_FRAME;
if ((metal->eflags & MFE_VERTICALFLIP) != (oldmetal.eflags & MFE_VERTICALFLIP)) WRITEUINT32(demo_p,oldmetal.frame);
{
if (!exttic_p)
exttic_p = demo_p++;
exttic |= EZT_FLIP;
oldmetal.eflags ^= MFE_VERTICALFLIP;
}
if (metal->scale != oldmetal.scale)
{
if (!exttic_p)
exttic_p = demo_p++;
exttic |= EZT_SCALE;
WRITEFIXED(demo_p,metal->scale);
oldmetal.scale = metal->scale;
}
if (exttic_p)
{
*exttic_p = exttic;
ziptic |= GZT_EXTRA;
}
} }
if (metal->sprite == SPR_PLAY
&& metal->sprite2 != oldmetal.sprite2)
{
oldmetal.sprite2 = metal->sprite2;
ziptic |= GZT_SPR2;
WRITEUINT8(demo_p,oldmetal.sprite2);
}
// Check for sprite set changes
if (metal->sprite != oldmetal.sprite)
{
oldmetal.sprite = metal->sprite;
ghostext.flags |= EZT_SPRITE;
}
if ((height = FixedDiv(metal->height, metal->scale)) != oldmetal.height)
{
oldmetal.height = height;
ghostext.flags |= EZT_HEIGHT;
}
if (ghostext.flags & ~(EZT_COLOR|EZT_HIT)) // these two aren't handled by metal ever
{
ziptic |= GZT_EXTRA;
if (ghostext.scale == ghostext.lastscale)
ghostext.flags &= ~EZT_SCALE;
WRITEUINT8(demo_p,ghostext.flags);
if (ghostext.flags & EZT_SCALE)
{
WRITEFIXED(demo_p,ghostext.scale);
ghostext.lastscale = ghostext.scale;
}
if (ghostext.flags & EZT_SPRITE)
WRITEUINT16(demo_p,oldmetal.sprite);
if (ghostext.flags & EZT_HEIGHT)
{
height >>= FRACBITS;
WRITEINT16(demo_p, height);
}
ghostext.flags = 0;
}
if (metal->player && metal->player->followmobj && !(metal->player->followmobj->sprite == SPR_NULL || (metal->player->followmobj->flags2 & MF2_DONTDRAW)))
{
INT16 temp;
UINT8 *followtic_p = demo_p++;
UINT8 followtic = 0;
ziptic |= GZT_FOLLOW;
if (metal->player->followmobj->skin)
followtic |= FZT_SKIN;
if (!(oldmetal.flags2 & MF2_AMBUSH))
{
followtic |= FZT_SPAWNED;
WRITEINT16(demo_p,metal->player->followmobj->info->height>>FRACBITS);
if (metal->player->followmobj->flags2 & MF2_LINKDRAW)
followtic |= FZT_LINKDRAW;
if (metal->player->followmobj->colorized)
followtic |= FZT_COLORIZED;
if (followtic & FZT_SKIN)
WRITEUINT8(demo_p,(UINT8)(((skin_t *)(metal->player->followmobj->skin))-skins));
oldmetal.flags2 |= MF2_AMBUSH;
}
if (metal->player->followmobj->scale != metal->scale)
{
followtic |= FZT_SCALE;
WRITEFIXED(demo_p,metal->player->followmobj->scale);
}
temp = (INT16)((metal->player->followmobj->x-metal->x)>>8);
WRITEINT16(demo_p,temp);
temp = (INT16)((metal->player->followmobj->y-metal->y)>>8);
WRITEINT16(demo_p,temp);
temp = (INT16)((metal->player->followmobj->z-metal->z)>>8);
WRITEINT16(demo_p,temp);
if (followtic & FZT_SKIN)
WRITEUINT8(demo_p,metal->player->followmobj->sprite2);
WRITEUINT16(demo_p,metal->player->followmobj->sprite);
WRITEUINT32(demo_p,metal->player->followmobj->frame); // NOT & FF_FRAMEMASK here, so 32 bits
WRITEUINT8(demo_p,metal->player->followmobj->color);
*followtic_p = followtic;
}
else
oldmetal.flags2 &= ~MF2_AMBUSH;
*ziptic_p = ziptic; *ziptic_p = ziptic;
// attention here for the ticcmd size! // attention here for the ticcmd size!
// latest demos with mouse aiming byte in ticcmd // latest demos with mouse aiming byte in ticcmd
if (demo_p >= demoend - 32) if (demo_p >= demoend - 32)
{ {
G_StopMetalRecording(); // no more space G_StopMetalRecording(false); // no more space
return; return;
} }
} }
@ -5165,22 +5459,36 @@ void G_BeginRecording(void)
// And mobjtype_t is best with UINT32 too... // And mobjtype_t is best with UINT32 too...
WRITEUINT32(demo_p, player->followitem); WRITEUINT32(demo_p, player->followitem);
// Save pflag data // Save pflag data - see SendWeaponPref()
{ {
UINT8 buf = 0; UINT8 buf = 0;
if (player->pflags & PF_FLIPCAM) pflags_t pflags = 0;
if (cv_flipcam.value)
{
buf |= 0x01; buf |= 0x01;
if (player->pflags & PF_ANALOGMODE) pflags |= PF_FLIPCAM;
}
if (cv_analog.value)
{
buf |= 0x02; buf |= 0x02;
if (player->pflags & PF_DIRECTIONCHAR) pflags |= PF_ANALOGMODE;
}
if (cv_directionchar.value)
{
buf |= 0x04; buf |= 0x04;
if (player->pflags & PF_AUTOBRAKE) pflags |= PF_DIRECTIONCHAR;
}
if (cv_autobrake.value)
{
buf |= 0x08; buf |= 0x08;
pflags |= PF_AUTOBRAKE;
}
if (cv_usejoystick.value) if (cv_usejoystick.value)
buf |= 0x10; buf |= 0x10;
CV_SetValue(&cv_showinputjoy, !!(cv_usejoystick.value)); CV_SetValue(&cv_showinputjoy, !!(cv_usejoystick.value));
WRITEUINT8(demo_p,buf); WRITEUINT8(demo_p,buf);
player->pflags = pflags;
} }
// Save netvar data // Save netvar data
@ -5209,8 +5517,10 @@ void G_BeginMetal(void)
{ {
mobj_t *mo = players[consoleplayer].mo; mobj_t *mo = players[consoleplayer].mo;
#if 0
if (demo_p) if (demo_p)
return; return;
#endif
demo_p = demobuffer; demo_p = demobuffer;
@ -5225,6 +5535,9 @@ void G_BeginMetal(void)
M_Memcpy(demo_p, "METL", 4); demo_p += 4; M_Memcpy(demo_p, "METL", 4); demo_p += 4;
memset(&ghostext,0,sizeof(ghostext));
ghostext.lastscale = ghostext.scale = FRACUNIT;
// Set up our memory. // Set up our memory.
memset(&oldmetal,0,sizeof(oldmetal)); memset(&oldmetal,0,sizeof(oldmetal));
oldmetal.x = mo->x; oldmetal.x = mo->x;
@ -5873,7 +6186,9 @@ void G_AddGhost(char *defdemoname)
gh->mo->state = states+S_PLAY_STND; gh->mo->state = states+S_PLAY_STND;
gh->mo->sprite = gh->mo->state->sprite; gh->mo->sprite = gh->mo->state->sprite;
gh->mo->sprite2 = (gh->mo->state->frame & FF_FRAMEMASK); gh->mo->sprite2 = (gh->mo->state->frame & FF_FRAMEMASK);
gh->mo->frame = tr_trans20<<FF_TRANSSHIFT; //gh->mo->frame = tr_trans30<<FF_TRANSSHIFT;
gh->mo->flags2 |= MF2_DONTDRAW;
gh->fadein = (9-3)*6; // fade from invisible to trans30 over as close to 35 tics as possible
gh->mo->tics = -1; gh->mo->tics = -1;
CONS_Printf(M_GetText("Added ghost %s from %s\n"), name, pdemoname); CONS_Printf(M_GetText("Added ghost %s from %s\n"), name, pdemoname);
@ -5994,19 +6309,23 @@ void G_StopMetalDemo(void)
} }
// Stops metal sonic recording. // Stops metal sonic recording.
ATTRNORETURN void FUNCNORETURN G_StopMetalRecording(void) ATTRNORETURN void FUNCNORETURN G_StopMetalRecording(boolean kill)
{ {
boolean saved = false; boolean saved = false;
if (demo_p) if (demo_p)
{ {
UINT8 *p = demobuffer+16; // checksum position UINT8 *p = demobuffer+16; // checksum position
if (kill)
WRITEUINT8(demo_p, METALDEATH); // add the metal death marker
else
WRITEUINT8(demo_p, DEMOMARKER); // add the demo end marker
#ifdef NOMD5 #ifdef NOMD5
UINT8 i; {
WRITEUINT8(demo_p, DEMOMARKER); // add the demo end marker UINT8 i;
for (i = 0; i < 16; i++, p++) for (i = 0; i < 16; i++, p++)
*p = P_RandomByte(); // This MD5 was chosen by fair dice roll and most likely < 50% correct. *p = P_RandomByte(); // This MD5 was chosen by fair dice roll and most likely < 50% correct.
}
#else #else
WRITEUINT8(demo_p, DEMOMARKER); // add the demo end marker
md5_buffer((char *)p+16, demo_p - (p+16), (void *)p); // make a checksum of everything after the checksum in the file. md5_buffer((char *)p+16, demo_p - (p+16), (void *)p); // make a checksum of everything after the checksum in the file.
#endif #endif
saved = FIL_WriteFile(va("%sMS.LMP", G_BuildMapName(gamemap)), demobuffer, demo_p - demobuffer); // finally output the file. saved = FIL_WriteFile(va("%sMS.LMP", G_BuildMapName(gamemap)), demobuffer, demo_p - demobuffer); // finally output the file.

View file

@ -175,7 +175,7 @@ void G_AddGhost(char *defdemoname);
void G_DoPlayMetal(void); void G_DoPlayMetal(void);
void G_DoneLevelLoad(void); void G_DoneLevelLoad(void);
void G_StopMetalDemo(void); void G_StopMetalDemo(void);
ATTRNORETURN void FUNCNORETURN G_StopMetalRecording(void); ATTRNORETURN void FUNCNORETURN G_StopMetalRecording(boolean kill);
void G_StopDemo(void); void G_StopDemo(void);
boolean G_CheckDemoStatus(void); boolean G_CheckDemoStatus(void);

View file

@ -1800,38 +1800,24 @@ state_t states[NUMSTATES] =
{SPR_NULL, 0, 1, {A_BossScream}, 0, 0, S_CYBRAKDEMONVILEEXPLOSION1}, //S_CYBRAKDEMONVILEEXPLOSION3, {SPR_NULL, 0, 1, {A_BossScream}, 0, 0, S_CYBRAKDEMONVILEEXPLOSION1}, //S_CYBRAKDEMONVILEEXPLOSION3,
// Metal Sonic // Metal Sonic
{SPR_METL, 0, 35, {NULL}, 0, 0, S_METALSONIC_WAIT1}, // S_METALSONIC_STAND {SPR_PLAY, SPR2_STND, -1, {NULL}, 0, 0, S_METALSONIC_RACE}, // S_METALSONIC_RACE
{SPR_METL, 1, 8, {NULL}, 0, 0, S_METALSONIC_WAIT2}, // S_METALSONIC_WAIT1
{SPR_METL, 2, 8, {NULL}, 0, 0, S_METALSONIC_WAIT1}, // S_METALSONIC_WAIT2
{SPR_METL, 3, 4, {NULL}, 0, 0, S_METALSONIC_WALK2}, // S_METALSONIC_WALK1
{SPR_METL, 4, 4, {NULL}, 0, 0, S_METALSONIC_WALK3}, // S_METALSONIC_WALK2
{SPR_METL, 5, 4, {NULL}, 0, 0, S_METALSONIC_WALK4}, // S_METALSONIC_WALK3
{SPR_METL, 6, 4, {NULL}, 0, 0, S_METALSONIC_WALK5}, // S_METALSONIC_WALK4
{SPR_METL, 7, 4, {NULL}, 0, 0, S_METALSONIC_WALK6}, // S_METALSONIC_WALK5
{SPR_METL, 6, 4, {NULL}, 0, 0, S_METALSONIC_WALK7}, // S_METALSONIC_WALK6
{SPR_METL, 5, 4, {NULL}, 0, 0, S_METALSONIC_WALK8}, // S_METALSONIC_WALK7
{SPR_METL, 4, 4, {NULL}, 0, 0, S_METALSONIC_WALK1}, // S_METALSONIC_WALK8
{SPR_METL, 8, 2, {NULL}, 0, 0, S_METALSONIC_RUN2}, // S_METALSONIC_RUN1
{SPR_METL, 9, 2, {NULL}, 0, 0, S_METALSONIC_RUN3}, // S_METALSONIC_RUN2
{SPR_METL, 10, 2, {NULL}, 0, 0, S_METALSONIC_RUN4}, // S_METALSONIC_RUN3
{SPR_METL, 9, 2, {NULL}, 0, 0, S_METALSONIC_RUN1}, // S_METALSONIC_RUN4
{SPR_METL, 4, -1, {NULL}, 0, 0, S_NULL}, // S_METALSONIC_FLOAT {SPR_METL, 4, -1, {NULL}, 0, 0, S_NULL}, // S_METALSONIC_FLOAT
{SPR_METL, 12|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_METALSONIC_STUN}, // S_METALSONIC_VECTOR {SPR_METL, 16|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_METALSONIC_STUN}, // S_METALSONIC_VECTOR
{SPR_METL, 11, -1, {NULL}, 0, 0, S_METALSONIC_FLOAT}, // S_METALSONIC_STUN {SPR_METL, 15, -1, {NULL}, 0, 0, S_METALSONIC_FLOAT}, // S_METALSONIC_STUN
{SPR_METL, 13, 20, {NULL}, 0, 0, S_METALSONIC_GATHER},// S_METALSONIC_RAISE {SPR_METL, 17, 20, {NULL}, 0, 0, S_METALSONIC_GATHER},// S_METALSONIC_RAISE
{SPR_METL, 14, -1, {NULL}, 0, 0, S_NULL}, // S_METALSONIC_GATHER {SPR_METL, 18, -1, {NULL}, 0, 0, S_NULL}, // S_METALSONIC_GATHER
{SPR_METL, 15, -1, {NULL}, 0, 0, S_METALSONIC_BOUNCE},// S_METALSONIC_DASH {SPR_METL, 6|FF_FULLBRIGHT|FF_ANIMATE|FF_GLOBALANIM, -1, {NULL}, 1, 2, S_METALSONIC_BOUNCE},// S_METALSONIC_DASH
{SPR_METL, 14, -1, {NULL}, 0, 0, S_NULL}, // S_METALSONIC_BOUNCE {SPR_METL, 18|FF_FULLBRIGHT|FF_ANIMATE|FF_GLOBALANIM, -1, {NULL}, 1, 2, S_NULL}, // S_METALSONIC_BOUNCE
{SPR_METL, 16, -1, {NULL}, 0, 0, S_NULL}, // S_METALSONIC_BADBOUNCE {SPR_METL, 14, -1, {NULL}, 0, 0, S_NULL}, // S_METALSONIC_BADBOUNCE
{SPR_METL, 13, -1, {NULL}, 0, 0, S_METALSONIC_GATHER},// S_METALSONIC_SHOOT {SPR_METL, 17, -1, {NULL}, 0, 0, S_METALSONIC_GATHER},// S_METALSONIC_SHOOT
{SPR_METL, 11, 40, {A_Pain}, 0, 0, S_METALSONIC_FLOAT}, // S_METALSONIC_PAIN {SPR_METL, 15, 40, {A_Pain}, 0, 0, S_METALSONIC_FLOAT}, // S_METALSONIC_PAIN
{SPR_METL, 13, 2, {A_Fall}, 0, 0, S_METALSONIC_DEATH2},// S_METALSONIC_DEATH1 {SPR_METL, 17, 2, {A_Fall}, 0, 0, S_METALSONIC_DEATH2},// S_METALSONIC_DEATH1
{SPR_METL, 13, 4, {A_BossScream}, 0, 0, S_METALSONIC_DEATH3},// S_METALSONIC_DEATH2 {SPR_METL, 17, 4, {A_BossScream}, 0, 0, S_METALSONIC_DEATH3},// S_METALSONIC_DEATH2
{SPR_METL, 13, 0, {A_Repeat}, 17, S_METALSONIC_DEATH2, S_METALSONIC_DEATH4}, // S_METALSONIC_DEATH3 {SPR_METL, 17, 0, {A_Repeat}, 17, S_METALSONIC_DEATH2, S_METALSONIC_DEATH4}, // S_METALSONIC_DEATH3
{SPR_METL, 13, -1, {A_BossDeath}, 0, 0, S_NULL}, // S_METALSONIC_DEATH4 {SPR_METL, 17, -1, {A_BossDeath}, 0, 0, S_NULL}, // S_METALSONIC_DEATH4
{SPR_METL, 11, 1, {A_BossScream}, 0, 0, S_METALSONIC_FLEE2}, // S_METALSONIC_FLEE1 {SPR_METL, 15, 1, {A_BossScream}, 0, 0, S_METALSONIC_FLEE2}, // S_METALSONIC_FLEE1
{SPR_METL, 11, 7, {NULL}, 0, 0, S_METALSONIC_FLEE1}, // S_METALSONIC_FLEE2 {SPR_METL, 15, 7, {NULL}, 0, 0, S_METALSONIC_FLEE1}, // S_METALSONIC_FLEE2
{SPR_MSCF, FF_FULLBRIGHT|FF_TRANS30|FF_ANIMATE, -1, {NULL}, 11, 1, S_NULL}, // S_MSSHIELD_F1 {SPR_MSCF, FF_FULLBRIGHT|FF_TRANS30|FF_ANIMATE, -1, {NULL}, 11, 1, S_NULL}, // S_MSSHIELD_F1
{SPR_MSCF, FF_FULLBRIGHT|FF_ANIMATE|12, -1, {NULL}, 8, 2, S_NULL}, // S_MSSHIELD_F2 {SPR_MSCF, FF_FULLBRIGHT|FF_ANIMATE|12, -1, {NULL}, 8, 2, S_NULL}, // S_MSSHIELD_F2
@ -6636,18 +6622,18 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
{ // MT_METALSONIC_RACE { // MT_METALSONIC_RACE
207, // doomednum 207, // doomednum
S_METALSONIC_STAND, // spawnstate S_METALSONIC_RACE, // spawnstate
8, // spawnhealth 8, // spawnhealth
S_METALSONIC_WALK1, // seestate S_NULL, // seestate
sfx_None, // seesound sfx_None, // seesound
0, // reactiontime 0, // reactiontime
sfx_None, // attacksound sfx_None, // attacksound
S_NULL, // painstate S_NULL, // painstate
0, // painchance 0, // painchance
sfx_None, // painsound sfx_None, // painsound
S_METALSONIC_RUN1, // meleestate S_NULL, // meleestate
S_NULL, // missilestate S_NULL, // missilestate
S_NULL, // deathstate S_PLAY_DEAD, // deathstate
S_NULL, // xdeathstate S_NULL, // xdeathstate
sfx_None, // deathsound sfx_None, // deathsound
0, // speed 0, // speed
@ -6657,7 +6643,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
0, // mass 0, // mass
0, // damage 0, // damage
sfx_None, // activesound sfx_None, // activesound
MF_SCENERY|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
S_NULL // raisestate S_NULL // raisestate
}, },
@ -6707,7 +6693,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
0, // speed 0, // speed
32*FRACUNIT, // radius 32*FRACUNIT, // radius
52*FRACUNIT, // height 52*FRACUNIT, // height
0, // display offset 1, // display offset
0, // mass 0, // mass
0, // damage 0, // damage
sfx_None, // activesound sfx_None, // activesound

View file

@ -1934,25 +1934,7 @@ typedef enum state
S_CYBRAKDEMONVILEEXPLOSION3, S_CYBRAKDEMONVILEEXPLOSION3,
// Metal Sonic (Race) // Metal Sonic (Race)
// S_PLAY_STND S_METALSONIC_RACE,
S_METALSONIC_STAND,
// S_PLAY_TAP1
S_METALSONIC_WAIT1,
S_METALSONIC_WAIT2,
// S_PLAY_WALK
S_METALSONIC_WALK1,
S_METALSONIC_WALK2,
S_METALSONIC_WALK3,
S_METALSONIC_WALK4,
S_METALSONIC_WALK5,
S_METALSONIC_WALK6,
S_METALSONIC_WALK7,
S_METALSONIC_WALK8,
// S_PLAY_SPD1
S_METALSONIC_RUN1,
S_METALSONIC_RUN2,
S_METALSONIC_RUN3,
S_METALSONIC_RUN4,
// Metal Sonic (Battle) // Metal Sonic (Battle)
S_METALSONIC_FLOAT, S_METALSONIC_FLOAT,
S_METALSONIC_VECTOR, S_METALSONIC_VECTOR,

View file

@ -9107,10 +9107,11 @@ void A_BossJetFume(mobj_t *actor)
P_SetTarget(&filler->target, actor); P_SetTarget(&filler->target, actor);
filler->fuse = 59; filler->fuse = 59;
P_SetTarget(&actor->tracer, filler); P_SetTarget(&actor->tracer, filler);
filler->destscale = actor->scale/3; P_SetScale(filler, (filler->destscale = actor->scale/3));
P_SetScale(filler, filler->destscale);
if (actor->eflags & MFE_VERTICALFLIP) if (actor->eflags & MFE_VERTICALFLIP)
filler->flags2 |= MF2_OBJECTFLIP; filler->flags2 |= MF2_OBJECTFLIP;
filler->color = SKINCOLOR_ICY;
filler->colorized = true;
} }
else if (locvar1 == 3) // Boss 4 jet flame else if (locvar1 == 3) // Boss 4 jet flame
{ {

View file

@ -2372,7 +2372,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
if (target->player && !target->player->spectator) if (target->player && !target->player->spectator)
{ {
if (metalrecording) // Ack! Metal Sonic shouldn't die! Cut the tape, end recording! if (metalrecording) // Ack! Metal Sonic shouldn't die! Cut the tape, end recording!
G_StopMetalRecording(); G_StopMetalRecording(true);
if (gametype == GT_MATCH // note, no team match suicide penalty if (gametype == GT_MATCH // note, no team match suicide penalty
&& ((target == source) || (source == NULL && inflictor == NULL) || (source && !source->player))) && ((target == source) || (source == NULL && inflictor == NULL) || (source && !source->player)))
{ // Suicide penalty { // Suicide penalty
@ -2763,6 +2763,12 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
} }
} }
break; break;
case MT_METALSONIC_RACE:
target->fuse = TICRATE*3;
target->momx = target->momy = target->momz = 0;
P_SetObjectMomZ(target, 14*FRACUNIT, false);
target->flags = (target->flags & ~MF_NOGRAVITY)|(MF_NOCLIP|MF_NOCLIPTHING);
break;
default: default:
break; break;
} }
@ -3617,11 +3623,12 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
} }
else if (inflictor && inflictor->flags & MF_MISSILE) else if (inflictor && inflictor->flags & MF_MISSILE)
return false; // Metal Sonic walk through flame !! return false; // Metal Sonic walk through flame !!
else else if (!player->powers[pw_flashing])
{ // Oh no! Metal Sonic is hit !! { // Oh no! Metal Sonic is hit !!
P_ShieldDamage(player, inflictor, source, damage, damagetype); P_ShieldDamage(player, inflictor, source, damage, damagetype);
return true; return true;
} }
return false;
} }
else if (player->powers[pw_invulnerability] || player->powers[pw_flashing] || player->powers[pw_super]) // ignore bouncing & such in invulnerability else if (player->powers[pw_invulnerability] || player->powers[pw_flashing] || player->powers[pw_super]) // ignore bouncing & such in invulnerability
{ {

View file

@ -2563,19 +2563,30 @@ static boolean P_ZMovement(mobj_t *mo)
if (!mo->player && P_CheckDeathPitCollide(mo)) if (!mo->player && P_CheckDeathPitCollide(mo))
{ {
if (mo->flags & MF_ENEMY || mo->flags & MF_BOSS || mo->type == MT_MINECART) switch (mo->type)
{ {
// Kill enemies, bosses and minecarts that fall into death pits. case MT_GHOST:
if (mo->health) case MT_METALSONIC_RACE:
{ case MT_EXPLODE:
P_KillMobj(mo, NULL, NULL, 0); case MT_BOSSEXPLODE:
return false; case MT_SONIC3KBOSSEXPLODE:
} break;
} default:
else if (mo->flags & MF_ENEMY || mo->flags & MF_BOSS || mo->type == MT_MINECART)
{ {
P_RemoveMobj(mo); // Kill enemies, bosses and minecarts that fall into death pits.
return false; if (mo->health)
{
P_KillMobj(mo, NULL, NULL, 0);
}
return false;
}
else
{
P_RemoveMobj(mo);
return false;
}
break;
} }
} }
@ -5567,9 +5578,9 @@ static void P_Boss9Thinker(mobj_t *mobj)
P_InstaThrust(mobj, mobj->angle, -4*FRACUNIT); P_InstaThrust(mobj, mobj->angle, -4*FRACUNIT);
P_TryMove(mobj, mobj->x+mobj->momx, mobj->y+mobj->momy, true); P_TryMove(mobj, mobj->x+mobj->momx, mobj->y+mobj->momy, true);
mobj->momz -= gravity; mobj->momz -= gravity;
if (mobj->z < mobj->watertop) if (mobj->z < mobj->watertop || mobj->z < (mobj->floorz + 16*FRACUNIT))
{ {
mobj->watertop = mobj->target->floorz + 32*FRACUNIT; mobj->watertop = mobj->floorz + 32*FRACUNIT;
P_SetMobjState(mobj, mobj->info->spawnstate); P_SetMobjState(mobj, mobj->info->spawnstate);
} }
return; return;
@ -5577,16 +5588,22 @@ static void P_Boss9Thinker(mobj_t *mobj)
if ((!mobj->target || !(mobj->target->flags & MF_SHOOTABLE))) if ((!mobj->target || !(mobj->target->flags & MF_SHOOTABLE)))
{ {
if (mobj->tracer) if (mobj->hprev)
P_RemoveMobj(mobj->tracer); {
P_RemoveMobj(mobj->hprev);
P_SetTarget(&mobj->hprev, NULL);
}
P_BossTargetPlayer(mobj, false); P_BossTargetPlayer(mobj, false);
if (mobj->target && (!P_IsObjectOnGround(mobj->target) || mobj->target->player->pflags & PF_SPINNING)) if (mobj->target && (!P_IsObjectOnGround(mobj->target) || mobj->target->player->pflags & PF_SPINNING))
P_SetTarget(&mobj->target, NULL); // Wait for them to hit the ground first P_SetTarget(&mobj->target, NULL); // Wait for them to hit the ground first
if (!mobj->target) // Still no target, aww. if (!mobj->target) // Still no target, aww.
{ {
// Reset the boss. // Reset the boss.
if (mobj->tracer) if (mobj->hprev)
P_RemoveMobj(mobj->tracer); {
P_RemoveMobj(mobj->hprev);
P_SetTarget(&mobj->hprev, NULL);
}
P_SetMobjState(mobj, mobj->info->spawnstate); P_SetMobjState(mobj, mobj->info->spawnstate);
mobj->fuse = 0; mobj->fuse = 0;
mobj->momx = FixedDiv(mobj->momx, FRACUNIT + (FRACUNIT>>2)); mobj->momx = FixedDiv(mobj->momx, FRACUNIT + (FRACUNIT>>2));
@ -5600,7 +5617,7 @@ static void P_Boss9Thinker(mobj_t *mobj)
return; return;
} }
else if (!mobj->fuse) else if (!mobj->fuse)
mobj->fuse = 10*TICRATE; mobj->fuse = 8*TICRATE;
} }
// AI goes here. // AI goes here.
@ -5627,16 +5644,18 @@ static void P_Boss9Thinker(mobj_t *mobj)
mobj->angle -= InvAngle(angle)/8; mobj->angle -= InvAngle(angle)/8;
// Alter your energy bubble's size/position // Alter your energy bubble's size/position
if (mobj->health > 3) if (mobj->health > mobj->info->damage)
{ {
mobj->tracer->destscale = FRACUNIT + (4*TICRATE - mobj->fuse)*(FRACUNIT/2)/TICRATE + FixedMul(FINECOSINE(angle>>ANGLETOFINESHIFT),FRACUNIT/2); if (mobj->hprev)
P_SetScale(mobj->tracer, mobj->tracer->destscale); {
} mobj->hprev->destscale = FRACUNIT + (2*TICRATE - mobj->fuse)*(FRACUNIT/2)/TICRATE + FixedMul(FINECOSINE(angle>>ANGLETOFINESHIFT),FRACUNIT/2);
P_SetScale(mobj->hprev, mobj->hprev->destscale);
P_TeleportMove(mobj->tracer, mobj->x, mobj->y, mobj->z + mobj->height/2 - mobj->tracer->height/2); P_TeleportMove(mobj->hprev, mobj->x, mobj->y, mobj->z + mobj->height/2 - mobj->hprev->height/2);
mobj->tracer->momx = mobj->momx; mobj->hprev->momx = mobj->momx;
mobj->tracer->momy = mobj->momy; mobj->hprev->momy = mobj->momy;
mobj->tracer->momz = mobj->momz; mobj->hprev->momz = mobj->momz;
}
// Firin' mah lazors - INDICATOR // Firin' mah lazors - INDICATOR
if (mobj->fuse > TICRATE/2) if (mobj->fuse > TICRATE/2)
@ -5724,6 +5743,7 @@ static void P_Boss9Thinker(mobj_t *mobj)
S_StartSound(mobj, sfx_s3kb3); S_StartSound(mobj, sfx_s3kb3);
} }
} }
}
// up... // up...
mobj->z += mobj->height/2; mobj->z += mobj->height/2;
@ -5750,12 +5770,12 @@ static void P_Boss9Thinker(mobj_t *mobj)
if (mobj->health > mobj->info->damage) if (mobj->health > mobj->info->damage)
{ {
P_SetScale(missile, FRACUNIT/3); P_SetScale(missile, FRACUNIT/3);
missile->color = SKINCOLOR_GOLD; // sonic cd electric power missile->color = SKINCOLOR_MAGENTA; // sonic OVA/4 purple power
} }
else else
{ {
P_SetScale(missile, FRACUNIT/5); P_SetScale(missile, FRACUNIT/5);
missile->color = SKINCOLOR_MAGENTA; // sonic OVA/4 purple power missile->color = SKINCOLOR_SUNSET; // sonic cd electric power
} }
missile->destscale = missile->scale*2; missile->destscale = missile->scale*2;
missile->scalespeed = abs(missile->scale - missile->destscale)/missile->fuse; missile->scalespeed = abs(missile->scale - missile->destscale)/missile->fuse;
@ -5774,8 +5794,10 @@ static void P_Boss9Thinker(mobj_t *mobj)
if (mobj->movedir == 0 || mobj->movedir == 2) { // Pausing between bounces in the pinball phase if (mobj->movedir == 0 || mobj->movedir == 2) { // Pausing between bounces in the pinball phase
if (mobj->target->player->powers[pw_tailsfly]) // Trying to escape, eh? if (mobj->target->player->powers[pw_tailsfly]) // Trying to escape, eh?
mobj->watertop = mobj->target->z + mobj->target->momz*6; // Readjust your aim. >:3 mobj->watertop = mobj->target->z + mobj->target->momz*6; // Readjust your aim. >:3
else else if (mobj->target->floorz > mobj->floorz)
mobj->watertop = mobj->target->floorz + 16*FRACUNIT; mobj->watertop = mobj->target->floorz + 16*FRACUNIT;
else
mobj->watertop = mobj->floorz + 16*FRACUNIT;
if (!(mobj->threshold%4)) { if (!(mobj->threshold%4)) {
mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x + mobj->target->momx*4, mobj->target->y + mobj->target->momy*4); mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x + mobj->target->momx*4, mobj->target->y + mobj->target->momy*4);
@ -5867,8 +5889,6 @@ static void P_Boss9Thinker(mobj_t *mobj)
return; return;
} }
P_SpawnGhostMobj(mobj);
// Pinball attack! // Pinball attack!
if (mobj->movecount == 3 && (mobj->movedir == 0 || mobj->movedir == 2)) if (mobj->movecount == 3 && (mobj->movedir == 0 || mobj->movedir == 2))
{ {
@ -5883,20 +5903,20 @@ static void P_Boss9Thinker(mobj_t *mobj)
if (!P_TryMove(mobj, mobj->x+mobj->momx, mobj->y+mobj->momy, true)) if (!P_TryMove(mobj, mobj->x+mobj->momx, mobj->y+mobj->momy, true))
{ // Hit a wall? Find a direction to bounce { // Hit a wall? Find a direction to bounce
mobj->threshold--; mobj->threshold--;
P_SetMobjState(mobj, mobj->state->nextstate);
if (!mobj->threshold) { // failed bounce! if (!mobj->threshold) { // failed bounce!
S_StartSound(mobj, sfx_mspogo); S_StartSound(mobj, sfx_mspogo);
P_BounceMove(mobj); P_BounceMove(mobj);
mobj->angle = R_PointToAngle2(mobj->momx, mobj->momy,0,0); mobj->angle = R_PointToAngle2(mobj->momx, mobj->momy,0,0);
mobj->momz = 4*FRACUNIT; mobj->momz = 4*FRACUNIT;
mobj->flags &= ~MF_PAIN; mobj->flags &= ~MF_PAIN;
mobj->fuse = 10*TICRATE; mobj->fuse = 8*TICRATE;
mobj->movecount = 0; mobj->movecount = 0;
P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_CYBRAKDEMON_VILE_EXPLOSION); P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_CYBRAKDEMON_VILE_EXPLOSION);
P_SetMobjState(mobj, mobj->info->meleestate); P_SetMobjState(mobj, mobj->info->meleestate);
} }
else if (!(mobj->threshold%4)) else if (!(mobj->threshold%4))
{ // We've decided to lock onto the player this bounce. { // We've decided to lock onto the player this bounce.
P_SetMobjState(mobj, mobj->state->nextstate);
S_StartSound(mobj, sfx_s3k5a); S_StartSound(mobj, sfx_s3k5a);
mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x + mobj->target->momx*4, mobj->target->y + mobj->target->momy*4); mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x + mobj->target->momx*4, mobj->target->y + mobj->target->momy*4);
mobj->reactiontime = TICRATE - 5*(mobj->info->damage - mobj->health); // targetting time mobj->reactiontime = TICRATE - 5*(mobj->info->damage - mobj->health); // targetting time
@ -5913,6 +5933,8 @@ static void P_Boss9Thinker(mobj_t *mobj)
return; return;
} }
P_SpawnGhostMobj(mobj)->colorized = false;
// Vector form dodge! // Vector form dodge!
mobj->angle += mobj->movedir; mobj->angle += mobj->movedir;
P_InstaThrust(mobj, mobj->angle, -speed); P_InstaThrust(mobj, mobj->angle, -speed);
@ -6009,7 +6031,7 @@ static void P_Boss9Thinker(mobj_t *mobj)
if (mobj->health > mobj->info->damage) if (mobj->health > mobj->info->damage)
{ // No more bubble if we're broken (pinch phase) { // No more bubble if we're broken (pinch phase)
mobj_t *shield = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_MSSHIELD_FRONT); mobj_t *shield = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_MSSHIELD_FRONT);
P_SetTarget(&mobj->tracer, shield); P_SetTarget(&mobj->hprev, shield);
P_SetTarget(&shield->target, mobj); P_SetTarget(&shield->target, mobj);
// Attack 2: Energy shot! // Attack 2: Energy shot!
@ -6040,14 +6062,15 @@ static void P_Boss9Thinker(mobj_t *mobj)
} }
else else
{ {
mobj_t *shield = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_MSSHIELD_FRONT); /*mobj_t *shield = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_MSSHIELD_FRONT);
P_SetTarget(&mobj->tracer, shield); P_SetTarget(&mobj->tracer, shield);
P_SetTarget(&shield->target, mobj); P_SetTarget(&shield->target, mobj);
shield->height -= 20*FRACUNIT; // different offset... shield->height -= 20*FRACUNIT; // different offset...
P_SetMobjState(shield, S_MSSHIELD_F2); P_SetMobjState(shield, S_MSSHIELD_F2);*/
P_SetMobjState(mobj, S_METALSONIC_BOUNCE);
//P_LinedefExecute(LE_PINCHPHASE, mobj, NULL); -- why does this happen twice? see case 2... //P_LinedefExecute(LE_PINCHPHASE, mobj, NULL); -- why does this happen twice? see case 2...
} }
mobj->fuse = 4*TICRATE; mobj->fuse = 3*TICRATE;
mobj->flags |= MF_PAIN; mobj->flags |= MF_PAIN;
if (mobj->info->attacksound) if (mobj->info->attacksound)
S_StartSound(mobj, mobj->info->attacksound); S_StartSound(mobj, mobj->info->attacksound);
@ -6058,14 +6081,14 @@ static void P_Boss9Thinker(mobj_t *mobj)
case 2: case 2:
{ {
// We're all charged and ready now! Unleash the fury!! // We're all charged and ready now! Unleash the fury!!
mobj_t *removemobj = mobj->tracer;
S_StopSound(mobj); S_StopSound(mobj);
P_SetTarget(&mobj->tracer, mobj->hnext); if (mobj->hprev)
P_RemoveMobj(removemobj); {
P_RemoveMobj(mobj->hprev);
P_SetTarget(&mobj->hprev, NULL);
}
if (mobj->health <= mobj->info->damage) if (mobj->health <= mobj->info->damage)
{ {
mobj_t *whoosh;
// Attack 1: Pinball dash! // Attack 1: Pinball dash!
if (mobj->health == 1) if (mobj->health == 1)
mobj->movedir = 0; mobj->movedir = 0;
@ -6078,9 +6101,13 @@ static void P_Boss9Thinker(mobj_t *mobj)
mobj->threshold = 12; // bounce 12 times mobj->threshold = 12; // bounce 12 times
else else
mobj->threshold = 24; // bounce 24 times mobj->threshold = 24; // bounce 24 times
mobj->watertop = mobj->target->floorz + 16*FRACUNIT; if (mobj->floorz >= mobj->target->floorz)
mobj->watertop = mobj->floorz + 16*FRACUNIT;
else
mobj->watertop = mobj->target->floorz + 16*FRACUNIT;
P_LinedefExecute(LE_PINCHPHASE, mobj, NULL); P_LinedefExecute(LE_PINCHPHASE, mobj, NULL);
#if 0
whoosh = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_GHOST); // done here so the offset is correct whoosh = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_GHOST); // done here so the offset is correct
whoosh->frame = FF_FULLBRIGHT; whoosh->frame = FF_FULLBRIGHT;
whoosh->sprite = SPR_ARMA; whoosh->sprite = SPR_ARMA;
@ -6088,9 +6115,13 @@ static void P_Boss9Thinker(mobj_t *mobj)
whoosh->scalespeed = FixedMul(whoosh->scalespeed, whoosh->scale); whoosh->scalespeed = FixedMul(whoosh->scalespeed, whoosh->scale);
whoosh->height = 38*whoosh->scale; whoosh->height = 38*whoosh->scale;
whoosh->fuse = 10; whoosh->fuse = 10;
whoosh->color = SKINCOLOR_MAGENTA; whoosh->color = SKINCOLOR_SUNSET;
whoosh->colorized = true; whoosh->colorized = true;
whoosh->flags |= MF_NOCLIPHEIGHT; whoosh->flags |= MF_NOCLIPHEIGHT;
#endif
P_SetMobjState(mobj->tracer, S_JETFUMEFLASH);
P_SetScale(mobj->tracer, mobj->scale << 1);
} }
else else
{ {
@ -6102,10 +6133,13 @@ static void P_Boss9Thinker(mobj_t *mobj)
} }
case 3: case 3:
// Return to idle. // Return to idle.
mobj->watertop = mobj->target->floorz + 32*FRACUNIT; if (mobj->floorz >= mobj->target->floorz)
mobj->watertop = mobj->floorz + 32*FRACUNIT;
else
mobj->watertop = mobj->target->floorz + 32*FRACUNIT;
P_SetMobjState(mobj, mobj->info->spawnstate); P_SetMobjState(mobj, mobj->info->spawnstate);
mobj->flags &= ~MF_PAIN; mobj->flags &= ~MF_PAIN;
mobj->fuse = 10*TICRATE; mobj->fuse = 8*TICRATE;
break; break;
} }
mobj->movecount++; mobj->movecount++;
@ -8264,6 +8298,20 @@ void P_MobjThinker(mobj_t *mobj)
P_SetObjectMomZ(mobj, -2 * FRACUNIT / 3, true); P_SetObjectMomZ(mobj, -2 * FRACUNIT / 3, true);
} }
break; break;
case MT_METALSONIC_RACE:
{
if (!(mobj->fuse % 8))
{
fixed_t r = mobj->radius >> FRACBITS;
mobj_t *explosion = P_SpawnMobj(
mobj->x + (P_RandomRange(r, -r) << FRACBITS),
mobj->y + (P_RandomRange(r, -r) << FRACBITS),
mobj->z + (P_RandomKey(mobj->height >> FRACBITS) << FRACBITS),
MT_SONIC3KBOSSEXPLODE);
S_StartSound(explosion, sfx_s3kb4);
}
P_SetObjectMomZ(mobj, -2 * FRACUNIT / 3, true);
}
default: default:
break; break;
} }
@ -8706,11 +8754,17 @@ void P_MobjThinker(mobj_t *mobj)
} }
else if (mobj->fuse == 59) else if (mobj->fuse == 59)
{ {
boolean dashmod = ((mobj->target->flags & MF_PAIN) && (mobj->target->health <= mobj->target->info->damage));
jetx = mobj->target->x + P_ReturnThrustX(mobj->target, mobj->target->angle, -mobj->target->radius); jetx = mobj->target->x + P_ReturnThrustX(mobj->target, mobj->target->angle, -mobj->target->radius);
jety = mobj->target->y + P_ReturnThrustY(mobj->target, mobj->target->angle, -mobj->target->radius); jety = mobj->target->y + P_ReturnThrustY(mobj->target, mobj->target->angle, -mobj->target->radius);
P_UnsetThingPosition(mobj); P_UnsetThingPosition(mobj);
mobj->x = jetx; mobj->x = jetx;
mobj->y = jety; mobj->y = jety;
mobj->destscale = mobj->target->scale;
if (!(dashmod && mobj->target->state == states+S_METALSONIC_BOUNCE))
{
mobj->destscale = (mobj->destscale + FixedDiv(R_PointToDist2(0, 0, mobj->target->momx, mobj->target->momy), 36*mobj->target->scale))/3;
}
if (mobj->target->eflags & MFE_VERTICALFLIP) if (mobj->target->eflags & MFE_VERTICALFLIP)
mobj->z = mobj->target->z + mobj->target->height/2 + mobj->height/2; mobj->z = mobj->target->z + mobj->target->height/2 + mobj->height/2;
else else
@ -8718,6 +8772,14 @@ void P_MobjThinker(mobj_t *mobj)
mobj->floorz = mobj->z; mobj->floorz = mobj->z;
mobj->ceilingz = mobj->z+mobj->height; mobj->ceilingz = mobj->z+mobj->height;
P_SetThingPosition(mobj); P_SetThingPosition(mobj);
if (dashmod)
{
mobj->color = SKINCOLOR_SUNSET;
if (mobj->target->movecount == 3 && !mobj->target->reactiontime && (mobj->target->movedir == 0 || mobj->target->movedir == 2))
P_SpawnGhostMobj(mobj);
}
else
mobj->color = SKINCOLOR_ICY;
} }
mobj->fuse++; mobj->fuse++;
} }
@ -10402,8 +10464,11 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
if (nummaprings >= 0) if (nummaprings >= 0)
nummaprings++; nummaprings++;
break; break;
case MT_METALSONIC_BATTLE:
case MT_METALSONIC_RACE: case MT_METALSONIC_RACE:
mobj->skin = &skins[5];
/* FALLTHRU */
case MT_METALSONIC_BATTLE:
mobj->color = skins[5].prefcolor;
sc = 5; sc = 5;
break; break;
case MT_FANG: case MT_FANG:
@ -10474,6 +10539,12 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
if (!(mobj->flags & MF_NOTHINK)) if (!(mobj->flags & MF_NOTHINK))
P_AddThinker(THINK_MOBJ, &mobj->thinker); P_AddThinker(THINK_MOBJ, &mobj->thinker);
if (mobj->skin) // correct inadequecies above.
{
mobj->sprite2 = P_GetSkinSprite2(mobj->skin, (mobj->frame & FF_FRAMEMASK), NULL);
mobj->frame &= ~FF_FRAMEMASK;
}
// Call action functions when the state is set // Call action functions when the state is set
if (st->action.acp1 && (mobj->flags & MF_RUNSPAWNFUNC)) if (st->action.acp1 && (mobj->flags & MF_RUNSPAWNFUNC))
{ {
@ -11256,6 +11327,8 @@ void P_MovePlayerToSpawn(INT32 playernum, mapthing_t *mthing)
} }
if (mthing->options & MTF_AMBUSH) if (mthing->options & MTF_AMBUSH)
P_SetPlayerMobjState(mobj, S_PLAY_FALL); P_SetPlayerMobjState(mobj, S_PLAY_FALL);
else if (metalrecording)
P_SetPlayerMobjState(mobj, S_PLAY_WAIT);
} }
else else
z = floor; z = floor;

View file

@ -2050,8 +2050,7 @@ void P_SpawnThokMobj(player_t *player)
mobj->eflags |= (player->mo->eflags & MFE_VERTICALFLIP); mobj->eflags |= (player->mo->eflags & MFE_VERTICALFLIP);
// scale // scale
P_SetScale(mobj, player->mo->scale); P_SetScale(mobj, (mobj->destscale = player->mo->scale));
mobj->destscale = player->mo->scale;
if (type == MT_THOK) // spintrail-specific modification for MT_THOK if (type == MT_THOK) // spintrail-specific modification for MT_THOK
{ {
@ -2061,8 +2060,7 @@ void P_SpawnThokMobj(player_t *player)
} }
P_SetTarget(&mobj->target, player->mo); // the one thing P_SpawnGhostMobj doesn't do P_SetTarget(&mobj->target, player->mo); // the one thing P_SpawnGhostMobj doesn't do
if (demorecording) G_GhostAddThok();
G_GhostAddThok();
} }
// //
@ -4576,8 +4574,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
if (player->revitem && !(leveltime % 5)) // Now spawn the color thok circle. if (player->revitem && !(leveltime % 5)) // Now spawn the color thok circle.
{ {
P_SpawnSpinMobj(player, player->revitem); P_SpawnSpinMobj(player, player->revitem);
if (demorecording) G_GhostAddRev();
G_GhostAddRev();
} }
} }
@ -8346,8 +8343,7 @@ static void P_MovePlayer(player_t *player)
if (player->pflags & PF_SPINNING && P_AproxDistance(player->speed, player->mo->momz) > FixedMul(15<<FRACBITS, player->mo->scale) && !(player->pflags & PF_JUMPED)) if (player->pflags & PF_SPINNING && P_AproxDistance(player->speed, player->mo->momz) > FixedMul(15<<FRACBITS, player->mo->scale) && !(player->pflags & PF_JUMPED))
{ {
P_SpawnSpinMobj(player, player->spinitem); P_SpawnSpinMobj(player, player->spinitem);
if (demorecording) G_GhostAddSpin();
G_GhostAddSpin();
} }
@ -11114,6 +11110,7 @@ static void P_DoMetalJetFume(player_t *player, mobj_t *fume)
} }
fume->movecount = dashmode; // keeps track of previous dashmode value so we know whether Metal is entering or leaving it fume->movecount = dashmode; // keeps track of previous dashmode value so we know whether Metal is entering or leaving it
fume->eflags = (fume->flags2 & ~MF2_OBJECTFLIP) | (mo->flags2 & MF2_OBJECTFLIP); // Make sure to flip in reverse gravity!
fume->eflags = (fume->eflags & ~MFE_VERTICALFLIP) | (mo->eflags & MFE_VERTICALFLIP); // Make sure to flip in reverse gravity! fume->eflags = (fume->eflags & ~MFE_VERTICALFLIP) | (mo->eflags & MFE_VERTICALFLIP); // Make sure to flip in reverse gravity!
// Finally, set its position // Finally, set its position
@ -11122,10 +11119,7 @@ static void P_DoMetalJetFume(player_t *player, mobj_t *fume)
P_UnsetThingPosition(fume); P_UnsetThingPosition(fume);
fume->x = mo->x + P_ReturnThrustX(fume, angle, dist); fume->x = mo->x + P_ReturnThrustX(fume, angle, dist);
fume->y = mo->y + P_ReturnThrustY(fume, angle, dist); fume->y = mo->y + P_ReturnThrustY(fume, angle, dist);
if (fume->eflags & MFE_VERTICALFLIP) fume->z = mo->z + ((mo->height - fume->height) >> 1);
fume->z = mo->z + ((mo->height + fume->height) >> 1);
else
fume->z = mo->z + ((mo->height - fume->height) >> 1);
P_SetThingPosition(fume); P_SetThingPosition(fume);
} }
@ -11517,7 +11511,7 @@ void P_PlayerThink(player_t *player)
// deez New User eXperiences. // deez New User eXperiences.
{ {
angle_t diff = 0; angle_t oldang = player->drawangle, diff = 0;
UINT8 factor; UINT8 factor;
// Directionchar! // Directionchar!
// Camera angle stuff. // Camera angle stuff.
@ -11531,6 +11525,13 @@ void P_PlayerThink(player_t *player)
player->drawangle = player->mo->angle; player->drawangle = player->mo->angle;
else if (P_PlayerInPain(player)) else if (P_PlayerInPain(player))
; ;
else if (player->powers[pw_justsprung]) // restricted, potentially by lua
{
#ifdef SPRINGSPIN
if (player->powers[pw_justsprung] & (1<<15))
player->drawangle += (player->powers[pw_justsprung] & ~(1<<15))*(ANG2+ANG1);
#endif
}
else if (player->powers[pw_carry] && player->mo->tracer) // carry else if (player->powers[pw_carry] && player->mo->tracer) // carry
{ {
switch (player->powers[pw_carry]) switch (player->powers[pw_carry])
@ -11568,13 +11569,6 @@ void P_PlayerThink(player_t *player)
break; break;
} }
} }
else if (player->powers[pw_justsprung])
{
#ifdef SPRINGSPIN
if (player->powers[pw_justsprung] & (1<<15))
player->drawangle += (player->powers[pw_justsprung] & ~(1<<15))*(ANG2+ANG1);
#endif
}
else if ((player->skidtime > (TICRATE/2 - 2) || ((player->pflags & (PF_SPINNING|PF_STARTDASH)) == PF_SPINNING)) && (abs(player->rmomx) > 5*player->mo->scale || abs(player->rmomy) > 5*player->mo->scale)) // spin/skid force else if ((player->skidtime > (TICRATE/2 - 2) || ((player->pflags & (PF_SPINNING|PF_STARTDASH)) == PF_SPINNING)) && (abs(player->rmomx) > 5*player->mo->scale || abs(player->rmomy) > 5*player->mo->scale)) // spin/skid force
player->drawangle = R_PointToAngle2(0, 0, player->rmomx, player->rmomy); player->drawangle = R_PointToAngle2(0, 0, player->rmomx, player->rmomy);
else if (((player->charability2 == CA2_GUNSLINGER || player->charability2 == CA2_MELEE) && player->panim == PA_ABILITY2) || player->pflags & PF_STASIS || player->skidtime) else if (((player->charability2 == CA2_GUNSLINGER || player->charability2 == CA2_MELEE) && player->panim == PA_ABILITY2) || player->pflags & PF_STASIS || player->skidtime)
@ -11635,6 +11629,22 @@ void P_PlayerThink(player_t *player)
player->drawangle += diff; player->drawangle += diff;
} }
// reset from waiting to standing when turning on the spot
if (player->panim == PA_IDLE)
{
diff = player->drawangle - oldang;
if (diff > ANGLE_180)
diff = InvAngle(diff);
if (diff > ANG10/2)
{
statenum_t stat = player->mo->state-states;
if (stat == S_PLAY_WAIT)
P_SetPlayerMobjState(player->mo, S_PLAY_STND);
else if (stat == S_PLAY_STND && player->mo->tics != -1)
player->mo->tics++;
}
}
// Autobrake! check ST_drawInput if you modify this // Autobrake! check ST_drawInput if you modify this
{ {
boolean currentlyonground = P_IsObjectOnGround(player->mo); boolean currentlyonground = P_IsObjectOnGround(player->mo);
@ -11874,7 +11884,7 @@ void P_PlayerThink(player_t *player)
#define dashmode player->dashmode #define dashmode player->dashmode
// Dash mode - thanks be to VelocitOni // Dash mode - thanks be to VelocitOni
if ((player->charflags & SF_DASHMODE) && !player->gotflag && !player->powers[pw_carry] && !player->exiting && !(maptol & TOL_NIGHTS)) // woo, dashmode! no nights tho. if ((player->charflags & SF_DASHMODE) && !player->gotflag && !player->powers[pw_carry] && !player->exiting && !(maptol & TOL_NIGHTS) && !metalrecording) // woo, dashmode! no nights tho.
{ {
boolean totallyradical = player->speed >= FixedMul(player->runspeed, player->mo->scale); boolean totallyradical = player->speed >= FixedMul(player->runspeed, player->mo->scale);
boolean floating = (player->secondjump == 1); boolean floating = (player->secondjump == 1);

View file

@ -567,8 +567,12 @@ static void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 skinnum, U
else if (skinnum == TC_METALSONIC) else if (skinnum == TC_METALSONIC)
{ {
for (i = 0; i < 6; i++) for (i = 0; i < 6; i++)
{
dest_colormap[Color_Index[SKINCOLOR_BLUE-1][12-i]] = Color_Index[SKINCOLOR_BLUE-1][i]; dest_colormap[Color_Index[SKINCOLOR_BLUE-1][12-i]] = Color_Index[SKINCOLOR_BLUE-1][i];
}
dest_colormap[159] = dest_colormap[253] = dest_colormap[254] = 0; dest_colormap[159] = dest_colormap[253] = dest_colormap[254] = 0;
for (i = 0; i < 16; i++)
dest_colormap[96+i] = dest_colormap[Color_Index[SKINCOLOR_COBALT-1][i]];
} }
else if (skinnum == TC_DASHMODE) // This is a long one, because MotorRoach basically hand-picked the indices else if (skinnum == TC_DASHMODE) // This is a long one, because MotorRoach basically hand-picked the indices
{ {

View file

@ -2701,6 +2701,7 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum)
|| (modeattacking) // If you have someone else's run you might as well take a look || (modeattacking) // If you have someone else's run you might as well take a look
|| (Playing() && (R_SkinAvailable(mapheaderinfo[gamemap-1]->forcecharacter) == skinnum)) // Force 1. || (Playing() && (R_SkinAvailable(mapheaderinfo[gamemap-1]->forcecharacter) == skinnum)) // Force 1.
|| (netgame && (cv_forceskin.value == skinnum)) // Force 2. || (netgame && (cv_forceskin.value == skinnum)) // Force 2.
|| (metalrecording && skinnum == 5) // Force 3.
); );
} }

View file

@ -2181,7 +2181,7 @@ void I_Quit(void)
if (demorecording) if (demorecording)
G_CheckDemoStatus(); G_CheckDemoStatus();
if (metalrecording) if (metalrecording)
G_StopMetalRecording(); G_StopMetalRecording(false);
D_QuitNetGame(); D_QuitNetGame();
I_ShutdownMusic(); I_ShutdownMusic();
@ -2299,7 +2299,7 @@ void I_Error(const char *error, ...)
if (demorecording) if (demorecording)
G_CheckDemoStatus(); G_CheckDemoStatus();
if (metalrecording) if (metalrecording)
G_StopMetalRecording(); G_StopMetalRecording(false);
D_QuitNetGame(); D_QuitNetGame();
I_ShutdownMusic(); I_ShutdownMusic();

View file

@ -837,7 +837,13 @@ static void ST_drawLivesArea(void)
} }
// Lives number // Lives number
if (G_GametypeUsesLives() || gametype == GT_RACE) if (metalrecording)
{
if (((2*leveltime)/TICRATE) & 1)
V_DrawRightAlignedString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y+8,
hudinfo[HUD_LIVES].f|V_PERPLAYER|V_REDMAP|V_HUDTRANS, "REC");
}
else if (G_GametypeUsesLives() || gametype == GT_RACE)
{ {
// x // x
V_DrawScaledPatch(hudinfo[HUD_LIVES].x+22, hudinfo[HUD_LIVES].y+10, V_DrawScaledPatch(hudinfo[HUD_LIVES].x+22, hudinfo[HUD_LIVES].y+10,

View file

@ -647,7 +647,7 @@ void I_Error(const char *error, ...)
if (demorecording) if (demorecording)
G_CheckDemoStatus(); G_CheckDemoStatus();
if (metalrecording) if (metalrecording)
G_StopMetalRecording(); G_StopMetalRecording(false);
D_QuitNetGame(); D_QuitNetGame();
@ -733,7 +733,7 @@ void I_Quit(void)
if (demorecording) if (demorecording)
G_CheckDemoStatus(); G_CheckDemoStatus();
if (metalrecording) if (metalrecording)
G_StopMetalRecording(); G_StopMetalRecording(false);
M_SaveConfig(NULL); // save game config, cvars.. M_SaveConfig(NULL); // save game config, cvars..
#ifndef NONET #ifndef NONET