Add item capsules

This commit is contained in:
lachablock 2021-06-14 12:20:39 +10:00
parent f031177ca4
commit b78db12b0d
10 changed files with 408 additions and 4 deletions

View file

@ -421,7 +421,7 @@ typedef struct player_s
UINT8 ringdelay; // (0 to 3) - 3 tic delay between every ring usage
UINT16 ringboost; // Ring boost timer
UINT8 sparkleanim; // (0 to 19) - Angle offset for ring sparkle animation
UINT8 superring; // Spawn rings on top of you every tic!
UINT16 superring; // Spawn rings on top of you every tic!
UINT8 curshield; // see kartshields_t
UINT8 bubblecool; // Bubble Shield use cooldown

View file

@ -3484,6 +3484,15 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi
"S_ITEMICON",
// Item capsules
"S_ITEMCAPSULE",
"S_ITEMCAPSULE_TOP_SIDE",
"S_ITEMCAPSULE_BOTTOM_SIDE_AIR",
"S_ITEMCAPSULE_BOTTOM_SIDE_GROUND",
"S_ITEMCAPSULE_TOP",
"S_ITEMCAPSULE_BOTTOM",
"S_ITEMCAPSULE_INSIDE",
// Signpost sparkles
"S_SIGNSPARK1",
"S_SIGNSPARK2",
@ -5438,6 +5447,8 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t
"MT_RANDOMITEM",
"MT_RANDOMITEMPOP",
"MT_FLOATINGITEM",
"MT_ITEMCAPSULE",
"MT_ITEMCAPSULE_PART",
"MT_SIGNSPARKLE",

View file

@ -537,6 +537,8 @@ char sprnames[NUMSPRITES + 1][5] =
"KINB", // Darker invincibility sparkle trail
"KINF", // Invincibility flash
"INVI", // Invincibility speedlines
"ICAP", // Item capsules
"WIPD", // Wipeout dust trail
"DRIF", // Drift Sparks
"BDRF", // Brake drift sparks
@ -4063,6 +4065,14 @@ state_t states[NUMSTATES] =
{SPR_NULL, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_ITEMICON
{SPR_ICAP, FF_ADD|0, -1, {NULL}, 0, 0, S_NULL}, // S_ITEMCAPSULE
{SPR_ICAP, FF_PAPERSPRITE|1, -1, {NULL}, 0, 0, S_NULL}, // S_ITEMCAPSULE_TOP_SIDE
{SPR_ICAP, FF_VERTICALFLIP|FF_PAPERSPRITE|1, -1, {NULL}, 0, 0, S_NULL}, // S_ITEMCAPSULE_BOTTOM_SIDE_AIR
{SPR_ICAP, FF_PAPERSPRITE|2, -1, {NULL}, 0, 0, S_NULL}, // S_ITEMCAPSULE_BOTTOM_SIDE_GROUND
{SPR_ICAP, FF_FLOORSPRITE|3, -1, {NULL}, 0, 0, S_NULL}, // S_ITEMCAPSULE_TOP
{SPR_ICAP, FF_FLOORSPRITE|4, -1, {NULL}, 0, 0, S_NULL}, // S_ITEMCAPSULE_BOTTOM
{SPR_ICAP, FF_FLOORSPRITE|5, -1, {NULL}, 0, 0, S_NULL}, // S_ITEMCAPSULE_INSIDE
{SPR_SGNS, FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_SIGNSPARK2}, // S_SIGNSPARK1
{SPR_SGNS, FF_FULLBRIGHT|1, 1, {NULL}, 0, 0, S_SIGNSPARK3}, // S_SIGNSPARK2
{SPR_SGNS, FF_FULLBRIGHT|2, 1, {NULL}, 0, 0, S_SIGNSPARK4}, // S_SIGNSPARK3
@ -23053,6 +23063,60 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
S_NULL // raisestate
},
{ // MT_ITEMCAPSULE
2010, // doomednum
S_ITEMCAPSULE, // spawnstate
1, // 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_itcaps, // deathsound
0, // speed
56*FRACUNIT, // radius
112*FRACUNIT, // height
1, // display offset
100, // mass
0, // damage
sfx_None, // activesound
MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
{ // MT_ITEMCAPSULE_PART
-1, // doomednum
S_INVISIBLE, // 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
26*FRACUNIT, // radius
14*FRACUNIT, // height
0, // display offset
100, // mass
0, // damage
sfx_None, // activesound
MF_SCENERY|MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT, // flags
S_NULL // raisestate
},
{ // MT_SIGNSPARKLE
-1, // doomednum
S_SIGNSPARK1, // spawnstate

View file

@ -1078,6 +1078,7 @@ typedef enum sprite
SPR_KINB, // Darker invincibility sparkle trail
SPR_KINF, // Invincibility flash
SPR_INVI, // Invincibility speedlines
SPR_ICAP, // Item capsules
SPR_WIPD, // Wipeout dust trail
SPR_DRIF, // Drift Sparks
@ -4455,6 +4456,15 @@ typedef enum state
S_ITEMICON,
// Item capsules
S_ITEMCAPSULE,
S_ITEMCAPSULE_TOP_SIDE,
S_ITEMCAPSULE_BOTTOM_SIDE_AIR,
S_ITEMCAPSULE_BOTTOM_SIDE_GROUND,
S_ITEMCAPSULE_TOP,
S_ITEMCAPSULE_BOTTOM,
S_ITEMCAPSULE_INSIDE,
// Signpost sparkles
S_SIGNSPARK1,
S_SIGNSPARK2,
@ -6446,6 +6456,8 @@ typedef enum mobj_type
MT_RANDOMITEM,
MT_RANDOMITEMPOP,
MT_FLOATINGITEM,
MT_ITEMCAPSULE,
MT_ITEMCAPSULE_PART,
MT_SIGNSPARKLE,

View file

@ -8801,7 +8801,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
}
break;
case KITEM_SUPERRING:
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO && player->superring < (UINT8_MAX - (10*3)))
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO && player->superring < (UINT16_MAX - (10*3)))
{
player->superring += (10*3);
player->itemamount--;

View file

@ -277,6 +277,16 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
P_SetTarget(&special->target, toucher);
P_KillMobj(special, toucher, toucher, DMG_NORMAL);
break;
case MT_ITEMCAPSULE:
if (special->threshold != KITEM_SUPERRING && !P_CanPickupItem(player, 1))
return;
if ((gametyperules & GTR_BUMPERS) && player->bumpers <= 0)
return;
S_StartSound(toucher, special->info->deathsound);
P_KillMobj(special, toucher, toucher, DMG_NORMAL);
return;
case MT_KARMAHITBOX:
if (!special->target->player)
return;
@ -1282,6 +1292,78 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
}
break;
case MT_ITEMCAPSULE:
{
UINT8 i;
mobj_t *part = target->hnext;
angle_t angle = FixedAngle(360*P_RandomFixed());
// burst effects
for (i = 0; i < 2; i++)
{
mobj_t *blast = P_SpawnMobjFromMobj(target, 0, 0, target->info->height >> 1, MT_BATTLEBUMPER_BLAST);
blast->angle = angle + i*ANGLE_90;
P_SetScale(blast, 2*blast->scale/3);
blast->destscale = 2*blast->scale;
}
// remove inside item
if (target->tracer && !P_MobjWasRemoved(target->tracer))
P_RemoveMobj(target->tracer);
// bust capsule caps
while (part && !P_MobjWasRemoved(part))
{
mobj_t *attacker = inflictor ? inflictor : source;
P_InstaThrust(part, part->angle + ANGLE_90, 6 * part->target->scale);
P_SetObjectMomZ(part, 4 * part->scale, false);
part->fuse = TICRATE/2;
part->flags &= ~MF_NOGRAVITY;
if (attacker)
{
part->momx += attacker->momx;
part->momy += attacker->momy;
part->momz += attacker->momz;
}
part = part->hnext;
}
// give the player an item!
if (source && source->player)
{
player_t *player = source->player;
// special behavior for ring capsules
if (target->threshold == KITEM_SUPERRING)
{
player->superring = min(player->superring + 5*target->movecount*3, UINT16_MAX);
break;
}
if (target->threshold < 1 || target->threshold >= NUMKARTITEMS) // bruh moment prevention
{
player->itemtype = KITEM_SAD;
player->itemamount = 1;
}
else
{
player->itemtype = target->threshold;
if (K_GetShieldFromItem(player->itemtype) != KSHIELD_NONE) // never give more than 1 shield
player->itemamount = 1;
else
player->itemamount = max(1, target->movecount);
}
player->karthud[khud_itemblink] = TICRATE;
player->karthud[khud_itemblinkmode] = 0;
player->itemroulette = 0;
player->roulettetype = 0;
if (P_IsDisplayPlayer(player))
S_StartSound(NULL, sfx_itrolf);
}
break;
}
case MT_BATTLECAPSULE:
{
mobj_t *cur;

View file

@ -3748,6 +3748,65 @@ static void P_RingThinker(mobj_t *mobj)
P_CycleMobjState(mobj);
}
static void P_ItemCapsulePartThinker(mobj_t *mobj)
{
if (mobj->fuse > 0) // dead
{
mobj->fuse--;
if (mobj->fuse == 0)
{
P_RemoveMobj(mobj);
return;
}
mobj->renderflags ^= RF_DONTDRAW;
}
else // alive
{
mobj_t *target = mobj->target;
fixed_t targetScale, z;
if (P_MobjWasRemoved(mobj->target)) // if the capsule was removed, remove the parts too
{
P_RemoveMobj(mobj);
return;
}
// ring capsules have a different color
if (!(mobj->flags2 & MF2_INFLOAT)
&&mobj->color != target->color)
{
mobj->color = target->color;
mobj->colorized = (mobj->color != SKINCOLOR_NONE);
}
// match the capsule's scale
if (mobj->extravalue1)
targetScale = FixedMul(mobj->extravalue1, target->scale);
else
targetScale = target->scale;
if (mobj->scale != targetScale)
P_SetScale(mobj, mobj->destscale = targetScale);
// find z position
K_GenericExtraFlagsNoZAdjust(mobj, target);
if (mobj->flags & MFE_VERTICALFLIP)
z = target->z + target->height - mobj->height - FixedMul(mobj->scale, mobj->movefactor);
else
z = target->z + FixedMul(mobj->scale, mobj->movefactor);
// rotate & move to capsule
mobj->angle += mobj->movedir;
if (mobj->flags2 & MF2_CLASSICPUSH) // centered
P_TeleportMove(mobj, target->x, target->y, z);
else
P_TeleportMove(mobj,
target->x + P_ReturnThrustX(mobj, mobj->angle + ANGLE_90, mobj->radius),
target->y + P_ReturnThrustY(mobj, mobj->angle + ANGLE_90, mobj->radius),
z);
}
}
//
// P_BossTargetPlayer
// If closest is true, find the closest player.
@ -5589,6 +5648,9 @@ static void P_MobjSceneryThink(mobj_t *mobj)
P_SetMobjStateNF(smok, smok->info->painstate); // same function, diff sprite
}
break;
case MT_ITEMCAPSULE_PART:
P_ItemCapsulePartThinker(mobj);
break;
case MT_BATTLECAPSULE_PIECE:
if (mobj->extravalue2)
mobj->frame |= FF_VERTICALFLIP;
@ -6084,6 +6146,89 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
}
break;
}
case MT_ITEMCAPSULE:
if (!P_MobjWasRemoved(mobj->tracer))
{
UINT8 numNumbers = 0;
INT32 count = 0;
INT32 itemType = mobj->threshold;
mobj_t *part = mobj->tracer;
if (itemType < 1 || itemType >= NUMKARTITEMS)
itemType = KITEM_SAD;
// update color
mobj->color = (itemType == KITEM_SUPERRING ? SKINCOLOR_GOLD : SKINCOLOR_NONE);
// update inside item frame
switch (itemType)
{
case KITEM_ORBINAUT:
part->sprite = SPR_ITMO;
part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetOrbinautItemFrame(mobj->movecount);
break;
case KITEM_INVINCIBILITY:
part->sprite = SPR_ITMI;
part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetInvincibilityItemFrame();
break;
case KITEM_SAD:
part->sprite = SPR_ITEM;
part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE;
break;
default:
part->sprite = SPR_ITEM;
part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|(itemType);
break;
}
// update number frame
if (K_GetShieldFromItem(itemType) != KSHIELD_NONE) // shields don't stack, so don't show a number
;
else
{
switch (itemType)
{
case KITEM_ORBINAUT: // only display the number when the sprite no longer changes
if (mobj->movecount - 1 > K_GetOrbinautItemFrame(mobj->movecount))
count = mobj->movecount;
break;
case KITEM_SUPERRING: // always display the number, and multiply it by 5
count = mobj->movecount * 5;
break;
case KITEM_SAD: // never display the number
break;
default:
if (mobj->movecount > 1)
count = mobj->movecount;
break;
}
}
while (count > 0)
{
if (P_MobjWasRemoved(part->tracer))
{
P_SetTarget(&part->tracer, P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_OVERLAY));
P_SetTarget(&part->tracer->target, part);
P_SetMobjState(part->tracer, S_INVISIBLE);
part->tracer->spriteyoffset = 10*FRACUNIT;
part->tracer->spritexoffset = 13*numNumbers*FRACUNIT;
}
part = part->tracer;
part->sprite = SPR_ITMN;
part->frame = FF_FULLBRIGHT|(count % 10);
count /= 10;
numNumbers++;
}
// delete any extra overlays (I guess in case the number changes?)
if (part->tracer)
{
P_RemoveMobj(part->tracer);
P_SetTarget(&part->tracer, NULL);
}
}
break;
case MT_ORBINAUT:
{
boolean grounded = P_IsObjectOnGround(mobj);
@ -8975,6 +9120,7 @@ static void P_DefaultMobjShadowScale(mobj_t *thing)
case MT_FLOATINGITEM:
case MT_BLUESPHERE:
case MT_EMERALD:
case MT_ITEMCAPSULE:
thing->shadowscale = FRACUNIT/2;
break;
case MT_DRIFTCLIP:
@ -9333,6 +9479,81 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
mobj->fuse = 100;
break;
// SRB2Kart
case MT_ITEMCAPSULE:
{
#define CAPSULESIDES 5
#define ANG_CAPSULE (UINT32_MAX / CAPSULESIDES)
#define ROTATIONSPEED (2*ANG2)
UINT8 i;
mobj_t *part;
fixed_t buttScale = 0;
statenum_t buttState = S_ITEMCAPSULE_BOTTOM_SIDE_AIR;
angle_t spin = -ROTATIONSPEED;
#if 0 // set to 1 to test capsules with random items, e.g. with objectplace
if (P_RandomChance(FRACUNIT/3))
mobj->threshold = KITEM_SUPERRING;
else if (P_RandomChance(FRACUNIT/3))
mobj->threshold = KITEM_ORBINAUT;
else
mobj->threshold = P_RandomRange(1, NUMKARTITEMS - 1);
mobj->movecount = P_RandomChance(FRACUNIT/3) ? 1 : P_RandomKey(32) + 1;
#else
mobj->threshold = KITEM_SUPERRING; // default item is super ring
mobj->movecount = 1;
#endif
P_CheckPosition(mobj, mobj->x, mobj->y); // look for FOFs
if (P_IsObjectOnGround(mobj))
{
mobj->flags &= ~MF_NOGRAVITY;
buttScale = 13*FRACUNIT/10;
buttState = S_ITEMCAPSULE_BOTTOM_SIDE_GROUND;
spin = 0;
}
// inside item
part = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_ITEMCAPSULE_PART);
P_SetTarget(&part->target, mobj);
P_SetMobjState(part, S_ITEMICON);
part->movedir = ROTATIONSPEED; // rotation speed
part->extravalue1 = 175*FRACUNIT/100; // relative scale
part->flags2 |= MF2_CLASSICPUSH|MF2_INFLOAT; // classicpush = centered horizontally, infloat = don't recolor
P_SetTarget(&mobj->tracer, part); // pointer to this item, so we can modify its sprite/frame
P_ItemCapsulePartThinker(part);
// capsule caps
part = mobj;
for (i = 0; i < CAPSULESIDES; i++)
{
// a bottom side
P_SetTarget(&part->hnext, P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_ITEMCAPSULE_PART));
P_SetTarget(&part->hnext->hprev, part);
part = part->hnext;
P_SetTarget(&part->target, mobj);
P_SetMobjState(part, buttState);
part->angle = i * ANG_CAPSULE;
part->movedir = spin; // rotation speed
part->movefactor = 0; // z offset
part->extravalue1 = buttScale; // relative scale
P_ItemCapsulePartThinker(part);
// a top side
P_SetTarget(&part->hnext, P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_ITEMCAPSULE_PART));
P_SetTarget(&part->hnext->hprev, part);
part = part->hnext;
P_SetTarget(&part->target, mobj);
P_SetMobjState(part, S_ITEMCAPSULE_TOP_SIDE);
part->angle = i * ANG_CAPSULE;
part->movedir = spin; // rotation speed
part->movefactor = mobj->info->height - part->info->height; // z offset
P_ItemCapsulePartThinker(part);
}
break;
#undef CAPSULESIDES
#undef ANG_CAPSULE
#undef ROTATIONSPEED
}
case MT_KARMAHITBOX:
{
const fixed_t rad = FixedMul(mobjinfo[MT_PLAYER].radius, mobj->scale);
@ -11567,6 +11788,18 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
}
break;
}
case MT_ITEMCAPSULE:
{
// Angle = item type
// Parameter = extra items (x5 for rings)
// Special = +16 items (+80 for rings)
if (mthing->angle > 0 && mthing->angle < NUMKARTITEMS)
mobj->threshold = mthing->angle;
if (mthing->options & MTF_OBJECTSPECIAL)
mobj->movecount += 16;
mobj->movecount += mthing->extrainfo;
break;
}
case MT_AAZTREE_HELPER:
{
fixed_t top = mobj->z;

View file

@ -276,7 +276,7 @@ static void P_NetArchivePlayers(void)
WRITEUINT8(save_p, players[i].ringdelay);
WRITEUINT16(save_p, players[i].ringboost);
WRITEUINT8(save_p, players[i].sparkleanim);
WRITEUINT8(save_p, players[i].superring);
WRITEUINT16(save_p, players[i].superring);
WRITEUINT8(save_p, players[i].curshield);
WRITEUINT8(save_p, players[i].bubblecool);
@ -528,7 +528,7 @@ static void P_NetUnArchivePlayers(void)
players[i].ringdelay = READUINT8(save_p);
players[i].ringboost = READUINT16(save_p);
players[i].sparkleanim = READUINT8(save_p);
players[i].superring = READUINT8(save_p);
players[i].superring = READUINT16(save_p);
players[i].curshield = READUINT8(save_p);
players[i].bubblecool = READUINT8(save_p);

View file

@ -966,6 +966,7 @@ sfxinfo_t S_sfx[NUMSFX] =
{"itfree", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // :shitsfree:
{"dbgsal", false, 255, 8, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Debug notification
{"cock", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Hammer cocks, bang bang
{"itcaps", false, 64, 16, -1, NULL, 0, -1, -1, LUMPERROR, "Item capsule"},
// SRB2Kart - Engine sounds
// Engine class A

View file

@ -1030,6 +1030,7 @@ typedef enum
sfx_itfree,
sfx_dbgsal,
sfx_cock,
sfx_itcaps,
// Next up: UNIQUE ENGINE SOUNDS! Hoooooo boy...
// Engine class A - Low Speed, Low Weight