diff --git a/src/d_player.h b/src/d_player.h index 7848e5b87..3bd99cecb 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -425,7 +425,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..a97d56865 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -334,6 +334,7 @@ actionpointer_t actionpointers[] = {{A_ReaperThinker}, "A_REAPERTHINKER"}, {{A_FlameShieldPaper}, "A_FLAMESHIELDPAPER"}, {{A_InvincSparkleRotate}, "A_INVINCSPARKLEROTATE"}, + {{A_SpawnItemCapsuleParts}, "A_SPAWNITEMCAPSULEPARTS"}, {{NULL}, "NONE"}, @@ -3484,6 +3485,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 +5448,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 9e4c70dc9..7f2e06f56 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 @@ -4065,6 +4067,14 @@ state_t states[NUMSTATES] = {SPR_NULL, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_ITEMICON + {SPR_ICAP, FF_ADD|0, -1, {A_SpawnItemCapsuleParts}, 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 @@ -23055,6 +23065,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_INVISIBLE, // 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_RUNSPAWNFUNC|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 ce77a2a68..ab9456cae 100644 --- a/src/info.h +++ b/src/info.h @@ -287,6 +287,7 @@ enum actionnum A_REAPERTHINKER, A_FLAMESHIELDPAPER, A_INVINCSPARKLEROTATE, + A_SPAWNITEMCAPSULEPARTS, NUMACTIONS }; @@ -557,6 +558,7 @@ void A_ReaperThinker(); void A_MementosTPParticles(); void A_FlameShieldPaper(); void A_InvincSparkleRotate(); +void A_SpawnItemCapsuleParts(); extern boolean actionsoverridden[NUMACTIONS]; @@ -1078,6 +1080,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 @@ -4457,6 +4460,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, @@ -6448,6 +6460,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 7290f3e5e..7074fe42a 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -1916,7 +1916,7 @@ void K_MatchGenericExtraFlags(mobj_t *mo, mobj_t *master) K_FlipFromObject(mo, master); // visibility (usually for hyudoro) - mo->renderflags = (master->renderflags & RF_DONTDRAW); + mo->renderflags = (mo->renderflags & ~RF_DONTDRAW) | (master->renderflags & RF_DONTDRAW); } // same as above, but does not adjust Z height when flipping @@ -1927,7 +1927,7 @@ void K_GenericExtraFlagsNoZAdjust(mobj_t *mo, mobj_t *master) mo->flags2 = (mo->flags2 & ~MF2_OBJECTFLIP)|(master->flags2 & MF2_OBJECTFLIP); // visibility (usually for hyudoro) - mo->renderflags = (master->renderflags & RF_DONTDRAW); + mo->renderflags = (mo->renderflags & ~RF_DONTDRAW) | (master->renderflags & RF_DONTDRAW); } @@ -4470,7 +4470,7 @@ static mobj_t *K_FindLastTrailMobj(player_t *player) return trail; } -static mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t mapthing, INT32 defaultDir, INT32 altthrow) +mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t mapthing, INT32 defaultDir, INT32 altthrow) { mobj_t *mo; INT32 dir; @@ -9002,7 +9002,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--; @@ -9409,4 +9409,46 @@ void K_CheckSpectateStatus(void) } } +UINT8 K_GetInvincibilityItemFrame(void) +{ + return ((leveltime % (7*3)) / 3); +} + +UINT8 K_GetOrbinautItemFrame(UINT8 count) +{ + return min(count - 1, 3); +} + +boolean K_IsSPBInGame(void) +{ + UINT8 i; + thinker_t *think; + + // is there an SPB chasing anyone? + if (spbplace != -1) + return true; + + // do any players have an SPB in their item slot? + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + + if (players[i].itemtype == KITEM_SPB) + return true; + } + + // spbplace is still -1 until a fired SPB finds a target, so look for an in-map SPB just in case + for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next) + { + if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed) + continue; + + if (((mobj_t *)think)->type == MT_SPB) + return true; + } + + return false; +} + //} diff --git a/src/k_kart.h b/src/k_kart.h index 7299ac765..0694488d3 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -74,6 +74,7 @@ void K_SpawnSparkleTrail(mobj_t *mo); void K_SpawnWipeoutTrail(mobj_t *mo, boolean offroad); void K_SpawnDraftDust(mobj_t *mo); void K_DriftDustHandling(mobj_t *spawner); +mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t mapthing, INT32 defaultDir, INT32 altthrow); void K_PuntMine(mobj_t *mine, mobj_t *punter); void K_DoSneaker(player_t *player, INT32 type); void K_DoPogoSpring(mobj_t *mo, fixed_t vertispeed, UINT8 sound); @@ -118,6 +119,9 @@ SINT8 K_Sliptiding(player_t *player); void K_AdjustPlayerFriction(player_t *player); void K_MoveKartPlayer(player_t *player, boolean onground); void K_CheckSpectateStatus(void); +UINT8 K_GetInvincibilityItemFrame(void); +UINT8 K_GetOrbinautItemFrame(UINT8 count); +boolean K_IsSPBInGame(void); // sound stuff for lua void K_PlayAttackTaunt(mobj_t *source); diff --git a/src/p_enemy.c b/src/p_enemy.c index 9dbeb8f5b..9f5fc86d8 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -321,6 +321,7 @@ void A_ReaperThinker(mobj_t *actor); void A_MementosTPParticles(mobj_t *actor); void A_FlameShieldPaper(mobj_t *actor); void A_InvincSparkleRotate(mobj_t *actor); +void A_SpawnItemCapsuleParts(mobj_t *actor); //for p_enemy.c @@ -14651,3 +14652,191 @@ void A_InvincSparkleRotate(mobj_t *actor) actor->angle += ANG1*10*(actor->extravalue2); // Arbitrary value, change this if you want, I suppose. } + +void P_RefreshItemCapsuleParts(mobj_t *mobj) +{ + UINT8 numNumbers = 0; + INT32 count = 0; + INT32 itemType = mobj->threshold; + mobj_t *part; + skincolornum_t color; + UINT32 newRenderFlags = 0; + boolean colorized; + + if (itemType < 1 || itemType >= NUMKARTITEMS) + itemType = KITEM_SAD; + + // update invincibility properties + if (itemType == KITEM_INVINCIBILITY) + { + mobj->renderflags = (mobj->renderflags & ~RF_BRIGHTMASK) | RF_FULLBRIGHT; + mobj->colorized = true; + } + else + { + mobj->renderflags = (mobj->renderflags & ~RF_BRIGHTMASK) | RF_SEMIBRIGHT; + mobj->color = SKINCOLOR_NONE; + mobj->colorized = false; + } + + // update cap colors + if (itemType == KITEM_SUPERRING) + { + color = SKINCOLOR_GOLD; + newRenderFlags |= RF_SEMIBRIGHT; + } + else if (mobj->spawnpoint && (mobj->spawnpoint->options & MTF_EXTRA)) + color = SKINCOLOR_SAPPHIRE; + else if (itemType == KITEM_SPB) + color = SKINCOLOR_JET; + else + color = SKINCOLOR_NONE; + + colorized = (color != SKINCOLOR_NONE); + part = mobj; + while (!P_MobjWasRemoved(part->hnext)) + { + part = part->hnext; + part->color = color; + part->colorized = colorized; + part->renderflags = (part->renderflags & ~RF_BRIGHTMASK) | newRenderFlags; + } + + // update inside item frame + part = mobj->tracer; + if (P_MobjWasRemoved(part)) + return; + + part->threshold = mobj->threshold; + part->movecount = mobj->movecount; + + 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 + case KITEM_SPB: + 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); + } +} + +#define CAPSULESIDES 5 +#define ANG_CAPSULE (UINT32_MAX / CAPSULESIDES) +#define ROTATIONSPEED (2*ANG2) +void A_SpawnItemCapsuleParts(mobj_t *actor) +{ + UINT8 i; + mobj_t *part; + fixed_t buttScale = 0; + statenum_t buttState = S_ITEMCAPSULE_BOTTOM_SIDE_AIR; + angle_t spin = ANGLE_MAX - ROTATIONSPEED; + + if (LUA_CallAction(A_SPAWNITEMCAPSULEPARTS, actor)) + return; + + if (P_IsObjectOnGround(actor)) + { + buttScale = 13*FRACUNIT/10; + buttState = S_ITEMCAPSULE_BOTTOM_SIDE_GROUND; + spin = 0; + } + + // inside item + part = P_SpawnMobjFromMobj(actor, 0, 0, 0, MT_ITEMCAPSULE_PART); + P_SetTarget(&part->target, actor); + P_SetMobjState(part, S_ITEMICON); + part->movedir = ROTATIONSPEED; // rotation speed + part->extravalue1 = 175*FRACUNIT/100; // relative scale + part->flags2 |= MF2_CLASSICPUSH; // classicpush = centered horizontally + P_SetTarget(&actor->tracer, part); // pointer to this item, so we can modify its sprite/frame + + // capsule caps + part = actor; + for (i = 0; i < CAPSULESIDES; i++) + { + // a bottom side + P_SetTarget(&part->hnext, P_SpawnMobjFromMobj(actor, 0, 0, 0, MT_ITEMCAPSULE_PART)); + P_SetTarget(&part->hnext->hprev, part); + part = part->hnext; + P_SetTarget(&part->target, actor); + P_SetMobjState(part, buttState); + part->angle = i * ANG_CAPSULE; + part->movedir = spin; // rotation speed + part->movefactor = 0; // z offset + part->extravalue1 = buttScale; // relative scale + + // a top side + P_SetTarget(&part->hnext, P_SpawnMobjFromMobj(actor, 0, 0, 0, MT_ITEMCAPSULE_PART)); + P_SetTarget(&part->hnext->hprev, part); + part = part->hnext; + P_SetTarget(&part->target, actor); + P_SetMobjState(part, S_ITEMCAPSULE_TOP_SIDE); + part->angle = i * ANG_CAPSULE; + part->movedir = spin; // rotation speed + part->movefactor = actor->info->height - part->info->height; // z offset + } + + P_RefreshItemCapsuleParts(actor); +} +#undef CAPSULESIDES +#undef ANG_CAPSULE +#undef ROTATIONSPEED diff --git a/src/p_inter.c b/src/p_inter.c index a73c688f4..968357096 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -277,6 +277,24 @@ 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 + && special->threshold != KITEM_SPB + && !P_CanPickupItem(player, 1)) + return; + + if ((gametyperules & GTR_BUMPERS) && player->bumpers <= 0) + return; + + if (special->scale < special->extravalue1) // don't break it while it's respawning + return; + + if (special->threshold == KITEM_SPB && K_IsSPBInGame()) // don't spawn a second SPB + 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 +1300,117 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget } break; + case MT_ITEMCAPSULE: + { + UINT8 i; + mobj_t *attacker = inflictor ? inflictor : source; + mobj_t *part = target->hnext; + angle_t angle = FixedAngle(360*P_RandomFixed()); + INT16 spacing = (target->radius >> 1) / target->scale; + + // set respawn fuse + if (modeattacking) // no respawns + ; + else if (target->threshold == KITEM_SUPERRING) + target->fuse = 20*TICRATE; + else + target->fuse = 40*TICRATE; + + // 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; + } + + // dust effects + for (i = 0; i < 10; i++) + { + mobj_t *puff = P_SpawnMobjFromMobj( + target, + P_RandomRange(-spacing, spacing) * FRACUNIT, + P_RandomRange(-spacing, spacing) * FRACUNIT, + P_RandomRange(0, 4*spacing) * FRACUNIT, + MT_SPINDASHDUST + ); + + P_SetScale(puff, (puff->destscale *= 2)); + puff->momz = puff->scale * P_MobjFlip(puff); + + P_Thrust(puff, R_PointToAngle2(target->x, target->y, puff->x, puff->y), 3*puff->scale); + if (attacker) + { + puff->momx += attacker->momx; + puff->momy += attacker->momy; + puff->momz += attacker->momz; + } + } + + // remove inside item + if (target->tracer && !P_MobjWasRemoved(target->tracer)) + P_RemoveMobj(target->tracer); + + // bust capsule caps + while (part && !P_MobjWasRemoved(part)) + { + P_InstaThrust(part, part->angle + ANGLE_90, 6 * part->target->scale); + P_SetObjectMomZ(part, 6 * FRACUNIT, 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; + } + + // special behavior for SPB capsules + if (target->threshold == KITEM_SPB) + { + K_ThrowKartItem(player, true, MT_SPB, 1, 0); + 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_local.h b/src/p_local.h index a143ba9ab..9c282499f 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -365,6 +365,8 @@ void P_InternalFlickyBubble(mobj_t *actor); void P_InternalFlickyFly(mobj_t *actor, fixed_t flyspeed, fixed_t targetdist, fixed_t chasez); void P_InternalFlickyHop(mobj_t *actor, fixed_t momz, fixed_t momh, angle_t angle); +void P_RefreshItemCapsuleParts(mobj_t *mobj); + // // P_MAP // diff --git a/src/p_mobj.c b/src/p_mobj.c index a63200f7f..91f8c9403 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -3748,6 +3748,57 @@ 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(target)) + { + P_RemoveMobj(mobj); + return; + } + + // 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. @@ -5457,11 +5508,11 @@ static void P_MobjSceneryThink(mobj_t *mobj) { case KITEM_ORBINAUT: mobj->tracer->sprite = SPR_ITMO; - mobj->tracer->frame = FF_FULLBRIGHT|(min(mobj->target->player->itemamount-1, 3)); + mobj->tracer->frame = FF_FULLBRIGHT|K_GetOrbinautItemFrame(mobj->target->player->itemamount); break; case KITEM_INVINCIBILITY: mobj->tracer->sprite = SPR_ITMI; - mobj->tracer->frame = FF_FULLBRIGHT|((leveltime % (7*3)) / 3); + mobj->tracer->frame = FF_FULLBRIGHT|K_GetInvincibilityItemFrame(); break; case KITEM_SAD: mobj->tracer->sprite = SPR_ITEM; @@ -5589,6 +5640,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; @@ -6063,11 +6117,11 @@ static boolean P_MobjRegularThink(mobj_t *mobj) { case KITEM_ORBINAUT: mobj->sprite = SPR_ITMO; - mobj->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|(min(mobj->movecount-1, 3)); + mobj->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetOrbinautItemFrame(mobj->movecount); break; case KITEM_INVINCIBILITY: mobj->sprite = SPR_ITMI; - mobj->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|((leveltime % (7*3)) / 3); + mobj->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetInvincibilityItemFrame(); break; case KITEM_SAD: mobj->sprite = SPR_ITEM; @@ -6084,6 +6138,38 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } break; } + case MT_ITEMCAPSULE: + // scale the capsule + if (mobj->scale < mobj->extravalue1) + { + fixed_t oldHeight = mobj->height; + + if ((mobj->extravalue1 - mobj->scale) < mobj->scalespeed) + P_SetScale(mobj, mobj->destscale = mobj->extravalue1); + else + P_SetScale(mobj, mobj->destscale = mobj->scale + mobj->scalespeed); + + if (mobj->eflags & MFE_VERTICALFLIP) + mobj->z -= (mobj->height - oldHeight); + } + + // update & animate capsule + if (!P_MobjWasRemoved(mobj->tracer)) + { + mobj_t *part = mobj->tracer; + + if (mobj->threshold != part->threshold + || mobj->movecount != part->movecount) // change the capsule properties if the item type or amount is updated + P_RefreshItemCapsuleParts(mobj); + + // animate invincibility capsules + if (mobj->threshold == KITEM_INVINCIBILITY) + { + mobj->color = K_RainbowColor(leveltime); + part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetInvincibilityItemFrame(); + } + } + break; case MT_ORBINAUT: { boolean grounded = P_IsObjectOnGround(mobj); @@ -8405,6 +8491,17 @@ static boolean P_FuseThink(mobj_t *mobj) P_RemoveMobj(mobj); // make sure they disappear return false; + case MT_ITEMCAPSULE: + if (mobj->spawnpoint) + P_SpawnMapThing(mobj->spawnpoint); + else + { + mobj_t *newMobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->type); + newMobj->threshold = mobj->threshold; + newMobj->movecount = mobj->movecount; + } + P_RemoveMobj(mobj); + return false; case MT_SMK_ICEBLOCK: { mobj_t *cur = mobj->hnext, *next; @@ -8975,6 +9072,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 +9431,40 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) mobj->fuse = 100; break; // SRB2Kart + case MT_ITEMCAPSULE: + { + fixed_t oldHeight = mobj->height; + + // set default item & count +#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_SPB; + 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 + + // grounded/aerial properties + P_AdjustMobjFloorZ_FFloors(mobj, mobj->subsector->sector, 0); + if (!P_IsObjectOnGround(mobj)) + mobj->flags |= MF_NOGRAVITY; + + // set starting scale + mobj->extravalue1 = mobj->scale; // this acts as the capsule's destscale; we're avoiding P_MobjScaleThink because we want aerial capsules not to scale from their center + mobj->scalespeed >>= 1; + P_SetScale(mobj, mobj->destscale = mapobjectscale >> 4); + if (mobj->eflags & MFE_VERTICALFLIP) + mobj->z += (oldHeight - mobj->height); + + break; + } case MT_KARMAHITBOX: { const fixed_t rad = FixedMul(mobjinfo[MT_PLAYER].radius, mobj->scale); @@ -10072,6 +10204,11 @@ void P_RespawnSpecials(void) pcount++; } +#if 0 // set to 1 to enable quick respawns for testing + if (true) + time = 5*TICRATE; + else +#endif if (gametyperules & GTR_SPHERES) { if (pcount > 2) @@ -10618,6 +10755,21 @@ static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i) if (modifiedgame && !savemoddata) return false; // No cheating!! + break; + case MT_ITEMCAPSULE: + { + boolean isRingCapsule = (mthing->angle < 1 || mthing->angle == KITEM_SUPERRING || mthing->angle >= NUMKARTITEMS); + + // don't spawn ring capsules in GTR_SPHERES gametypes + if (isRingCapsule && (gametyperules & GTR_SPHERES)) + return false; + + // in record attack, only spawn ring capsules + // (behavior can be inverted with the Extra flag, i.e. item capsule spawns and ring capsule does not) + if (modeattacking + && (!(mthing->options & MTF_EXTRA) == !isRingCapsule)) + return false; + } break; default: break; @@ -11567,6 +11719,27 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean } break; } + case MT_ITEMCAPSULE: + { + // Angle = item type + if (mthing->angle > 0 && mthing->angle < NUMKARTITEMS) + mobj->threshold = mthing->angle; + + // Parameter = extra items (x5 for rings) + mobj->movecount += mthing->extrainfo; + + // Special = +16 items (+80 for rings) + if (mthing->options & MTF_OBJECTSPECIAL) + mobj->movecount += 16; + + // Ambush = double size (grounded) / half size (aerial) + if (!(mthing->options & MTF_AMBUSH) == !P_IsObjectOnGround(mobj)) + { + mobj->extravalue1 = min(mobj->extravalue1 << 1, FixedDiv(64*FRACUNIT, mobj->info->radius)); // don't make them larger than the blockmap can handle + mobj->scalespeed <<= 1; + } + break; + } case MT_AAZTREE_HELPER: { fixed_t top = mobj->z; diff --git a/src/p_saveg.c b/src/p_saveg.c index e8e992fcc..27e100ccd 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); @@ -532,7 +532,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