From b78db12b0dae44c6374d7755bb7862591b56547c Mon Sep 17 00:00:00 2001 From: lachablock Date: Mon, 14 Jun 2021 12:20:39 +1000 Subject: [PATCH] Add item capsules --- src/d_player.h | 2 +- src/deh_tables.c | 11 +++ src/info.c | 64 +++++++++++++ src/info.h | 12 +++ src/k_kart.c | 2 +- src/p_inter.c | 82 +++++++++++++++++ src/p_mobj.c | 233 +++++++++++++++++++++++++++++++++++++++++++++++ src/p_saveg.c | 4 +- src/sounds.c | 1 + src/sounds.h | 1 + 10 files changed, 408 insertions(+), 4 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index ba83aae60..36ceb85d4 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -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 diff --git a/src/deh_tables.c b/src/deh_tables.c index 891f681bf..128f14726 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -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", diff --git a/src/info.c b/src/info.c index 5788d8945..0f8cd833e 100644 --- a/src/info.c +++ b/src/info.c @@ -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 diff --git a/src/info.h b/src/info.h index d15d0dde0..57f8ed5ad 100644 --- a/src/info.h +++ b/src/info.h @@ -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, diff --git a/src/k_kart.c b/src/k_kart.c index 396dc38cb..de3904277 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -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--; diff --git a/src/p_inter.c b/src/p_inter.c index a73c688f4..861cf2f84 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -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; diff --git a/src/p_mobj.c b/src/p_mobj.c index 602566b1b..c2782d1c3 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -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; diff --git a/src/p_saveg.c b/src/p_saveg.c index 35f68be29..39bced967 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -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); diff --git a/src/sounds.c b/src/sounds.c index 636b53a8c..b46e11ba1 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -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 diff --git a/src/sounds.h b/src/sounds.h index ac7d5b9cf..8d81aae20 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -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