From 0a6fedc48a6356eb2da4244b0fdce8aaa693b4db Mon Sep 17 00:00:00 2001 From: "James R." Date: Sun, 17 Sep 2023 21:31:50 -0700 Subject: [PATCH] Hyudoro: carry item capsule back to player, use MT_EMERALD for collect animation - Carry back item capsule, painted with the skincolor of the player who was stolen from - Give player item by orbiting and shrinking into the player like an emerald - Lock player item box while animation plays, draw an empty item box on the HUD - At end of animation, burst item capsule to place item in player's hotbar - Hyudoros can no longer stack items in the hotbar -- consistent with item capsules - (If you have a Super Ring in your hotbar and Hyudoro is holding a Super Ring too, it will wait for you to use yours) --- MT_ITEMCAPSULE changes: - extravalue2 != 0: use this color on caps - MF2_STRONGBOX: award items like the roulette - Super Ring is placed in the hotbar instead of awarding rings directly - SPB is placed in the hotbar instead of throwing it immediately - DMG_INSTAKILL: don't respawn --- src/objects/emerald.c | 38 +++++++-- src/objects/hyudoro.c | 183 ++++++++++++++++++++++++++++++------------ src/p_inter.c | 26 +++--- src/p_mobj.c | 9 ++- 4 files changed, 185 insertions(+), 71 deletions(-) diff --git a/src/objects/emerald.c b/src/objects/emerald.c index cc7105b54..9174c0fca 100644 --- a/src/objects/emerald.c +++ b/src/objects/emerald.c @@ -304,13 +304,8 @@ void Obj_BeginEmeraldOrbit(mobj_t *emerald, mobj_t *target, fixed_t radius, INT3 spawn_lens_flare(emerald); } -void Obj_GiveEmerald(mobj_t *emerald) +static void give_player(mobj_t *emerald) { - if (P_MobjWasRemoved(emerald_orbit(emerald)) || P_MobjWasRemoved(emerald_award(emerald))) - { - return; - } - player_t *player = emerald_award(emerald)->player; if (!player) @@ -324,6 +319,37 @@ void Obj_GiveEmerald(mobj_t *emerald) S_StartSound(emerald_award(emerald), emerald->info->deathsound); } +void Obj_GiveEmerald(mobj_t *emerald) +{ + if (P_MobjWasRemoved(emerald_orbit(emerald)) || P_MobjWasRemoved(emerald_award(emerald))) + { + return; + } + + // FIXME: emerald orbiting behavior should become its own object. For now, + // though, enjoy these special conditions! + switch (emerald_award(emerald)->type) + { + case MT_PLAYER: + give_player(emerald); + break; + + case MT_ITEMCAPSULE: // objects/hyudoro.c + // DMG_INSTAKILL to kill it without respawning later + P_KillMobj(emerald_award(emerald), emerald_orbit(emerald), emerald_orbit(emerald), DMG_INSTAKILL); + + if (emerald_orbit(emerald)->player) + { + // Unlock item for stacked Hyudoros + emerald_orbit(emerald)->player->itemRoulette.reserved = 0; + } + break; + + default: + break; + } +} + void Obj_SetEmeraldAwardee(mobj_t *emerald, mobj_t *awardee) { P_SetTarget(&emerald_award(emerald), awardee); diff --git a/src/objects/hyudoro.c b/src/objects/hyudoro.c index 73b7d9b6b..516051c04 100644 --- a/src/objects/hyudoro.c +++ b/src/objects/hyudoro.c @@ -28,6 +28,7 @@ enum { HYU_PATROL, HYU_RETURN, HYU_HOVER, + HYU_ORBIT, }; // TODO: make these general functions @@ -38,30 +39,19 @@ K_GetSpeed (mobj_t *mobj) return FixedHypot(mobj->momx, mobj->momy); } -static void -K_ChangePlayerItem -( player_t * player, - INT32 itemtype, - INT32 itemamount) -{ - player->itemtype = itemtype; - player->itemamount = itemamount; - K_UnsetItemOut(player); -} - #define hyudoro_mode(o) ((o)->extravalue1) #define hyudoro_itemtype(o) ((o)->movefactor) #define hyudoro_itemcount(o) ((o)->movecount) #define hyudoro_hover_stack(o) ((o)->threshold) #define hyudoro_next(o) ((o)->tracer) #define hyudoro_stackpos(o) ((o)->reactiontime) -#define hyudoro_delivered(o) (hyudoro_itemtype(o) == KITEM_NONE) // cannot be combined #define hyudoro_center(o) ((o)->target) #define hyudoro_target(o) ((o)->target) #define hyudoro_stolefrom(o) ((o)->hnext) +#define hyudoro_capsule(o) ((o)->hprev) #define hyudoro_timer(o) ((o)->movedir) #define hyudoro_center_max_radius(o) ((o)->threshold) @@ -222,6 +212,30 @@ project_hyudoro_hover (mobj_t *hyu) bob_in_place(hyu, 64); } +static boolean +project_hyudoro_orbit (mobj_t *hyu) +{ + mobj_t *orbit = hyudoro_target(hyu); + + if (P_MobjWasRemoved(orbit)) + { + return false; + } + + P_MoveOrigin(hyu, orbit->x, orbit->y, orbit->z); + hyu->destscale = orbit->scale; + + mobj_t *facing = orbit->target; + + if (!P_MobjWasRemoved(facing)) + { + hyu->angle = R_PointToAngle2( + hyu->x, hyu->y, facing->x, facing->y); + } + + return true; +} + static mobj_t * find_duel_target (mobj_t *ignore) { @@ -276,12 +290,6 @@ do_confused (mobj_t *hyu) // Hyudoro is confused. // Spin around, try to find a new target. - if (hyudoro_delivered(hyu)) - { - // Already delivered, not confused - return; - } - // Try to find new target P_SetTarget(&hyudoro_target(hyu), find_duel_target(hyudoro_stolefrom(hyu))); @@ -336,32 +344,31 @@ move_to_player (mobj_t *hyu) static void deliver_item (mobj_t *hyu) { - mobj_t *target = hyudoro_target(hyu); - player_t *player = target->player; + /* set physical position to visual position in stack */ + hyu->z += hyu->momz; + hyu->momz = 0; - P_SetTarget(&hyudoro_target(hyu), NULL); + mobj_t *emerald = P_SpawnMobjFromMobj( + hyu, 0, 0, 0, MT_EMERALD); - if (player) - { - K_ChangePlayerItem(player, - hyudoro_itemtype(hyu), - player->itemamount + hyudoro_itemcount(hyu)); - } + /* only want emerald for its orbiting behavior, so make + it invisible */ + P_SetMobjState(emerald, S_INVISIBLE); - S_StartSound(target, sfx_itpick); + Obj_BeginEmeraldOrbit( + emerald, hyudoro_target(hyu), 0, 64, 128); - // Stop moving here - hyu->momx = 0; - hyu->momy = 0; + /* See Obj_GiveEmerald. I won't risk relying on the + Hyudoro object in case it is removed first. So go + through the capsule instead. */ + Obj_SetEmeraldAwardee(emerald, hyudoro_capsule(hyu)); - hyu->tics = 4; + /* hyudoro will teleport to emerald (orbit the player) */ + hyudoro_mode(hyu) = HYU_ORBIT; + P_SetTarget(&hyudoro_target(hyu), emerald); - hyu->destscale = target->scale / 4; - hyu->scalespeed = - abs(hyu->scale - hyu->destscale) / hyu->tics; - - // sets as already delivered - hyudoro_itemtype(hyu) = KITEM_NONE; + hyu->renderflags &= ~(RF_DONTDRAW | RF_BLENDMASK); + reset_shadow(hyu); } static void @@ -431,9 +438,71 @@ pop_hyudoro (mobj_t **head) while (is_hyudoro(hyu)); } -static void hyudoro_set_held_item_from_player +static mobj_t * +spawn_capsule (mobj_t *hyu) +{ + mobj_t *caps = P_SpawnMobjFromMobj( + hyu, 0, 0, 0, MT_ITEMCAPSULE); + + /* hyudoro only needs its own shadow */ + caps->shadowscale = 0; + + caps->flags |= + MF_NOGRAVITY | + MF_NOCLIP | + MF_NOCLIPTHING | + MF_NOCLIPHEIGHT; + + /* signal that this item capsule always puts items in the HUD */ + caps->flags2 |= MF2_STRONGBOX; + + P_SetTarget(&hyudoro_capsule(hyu), caps); + + /* capsule teleports to hyudoro */ + P_SetTarget(&caps->target, hyu); + + /* so it looks like hyudoro is holding it */ + caps->sprzoff = 20 * hyu->scale; + + return caps; +} + +static void +update_capsule_position (mobj_t *hyu) +{ + mobj_t *caps = hyudoro_capsule(hyu); + + if (P_MobjWasRemoved(caps)) + return; + + caps->extravalue1 = hyu->scale / 3; + + /* hold it in the hyudoro's hands */ + const fixed_t r = hyu->radius; + caps->sprxoff = FixedMul(r, FCOS(hyu->angle)); + caps->spryoff = FixedMul(r, FSIN(hyu->angle)); +} + +static void +set_item ( mobj_t * hyu, - player_t *player) + INT32 item, + INT32 amount) +{ + mobj_t *caps = P_MobjWasRemoved(hyudoro_capsule(hyu)) + ? spawn_capsule(hyu) : hyudoro_capsule(hyu); + + hyudoro_itemtype(hyu) = item; + hyudoro_itemcount(hyu) = amount; + + caps->threshold = hyudoro_itemtype(hyu); + caps->movecount = hyudoro_itemcount(hyu); +} + +static void +hyudoro_set_held_item_from_player +( mobj_t * hyu, + player_t * player) { if (K_ItemEnabled(KITEM_KITCHENSINK)) { @@ -459,15 +528,13 @@ static void hyudoro_set_held_item_from_player itemCooldowns[player->itemtype - 1] = 0; } - hyudoro_itemtype(hyu) = KITEM_KITCHENSINK; - hyudoro_itemcount(hyu) = 1; + set_item(hyu, KITEM_KITCHENSINK, 1); return; } } - hyudoro_itemtype(hyu) = player->itemtype; - hyudoro_itemcount(hyu) = player->itemamount; + set_item(hyu, player->itemtype, player->itemamount); } static boolean @@ -503,6 +570,11 @@ hyudoro_patrol_hit_player hyudoro_set_held_item_from_player(hyu, player); + if (!P_MobjWasRemoved(hyudoro_capsule(hyu))) + { + hyudoro_capsule(hyu)->extravalue2 = player->skincolor; + } + K_StripItems(player); player->hyudorotimer = hyudorotime; @@ -544,16 +616,13 @@ award_immediately (mobj_t *hyu) return false; } - if (player->itemamount && - player->itemtype != hyudoro_itemtype(hyu)) - { + if (!P_CanPickupItem(player, 1)) return false; - } - // Same as picking up paper items; get stacks - // immediately - if (!P_CanPickupItem(player, 3)) - return false; + // Prevent receiving any more items or even stacked + // Hyudoros! Put on a timer so roulette cannot become + // locked permanently. + player->itemRoulette.reserved = 2*TICRATE; } deliver_item(hyu); @@ -770,8 +839,18 @@ Obj_HyudoroThink (mobj_t *hyu) } blend_hover_hyudoro(hyu); break; + + case HYU_ORBIT: + if (!project_hyudoro_orbit(hyu)) + { + P_RemoveMobj(hyu); + return; + } + break; } + update_capsule_position(hyu); + if (hyudoro_timer(hyu) > 0) hyudoro_timer(hyu)--; } diff --git a/src/p_inter.c b/src/p_inter.c index 60c7bbd8d..732a89dc7 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1687,7 +1687,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget INT16 spacing = (target->radius >> 1) / target->scale; // set respawn fuse - if (K_CapsuleTimeAttackRules() == true) // no respawns + if (damagetype == DMG_INSTAKILL || K_CapsuleTimeAttackRules() == true) // no respawns ; else if (target->threshold == KITEM_SUPERRING) target->fuse = 20*TICRATE; @@ -1752,18 +1752,22 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget { player_t *player = source->player; - // special behavior for ring capsules - if (target->threshold == KITEM_SUPERRING) + // MF2_STRONGBOX: always put the item right in the hotbar! + if (!(target->flags2 & MF2_STRONGBOX)) { - K_AwardPlayerRings(player, 5 * target->movecount, true); - break; - } + // special behavior for ring capsules + if (target->threshold == KITEM_SUPERRING) + { + K_AwardPlayerRings(player, 5 * target->movecount, true); + break; + } - // special behavior for SPB capsules - if (target->threshold == KITEM_SPB) - { - K_ThrowKartItem(player, true, MT_SPB, 1, 0, 0); - break; + // special behavior for SPB capsules + if (target->threshold == KITEM_SPB) + { + K_ThrowKartItem(player, true, MT_SPB, 1, 0, 0); + break; + } } if (target->threshold < 1 || target->threshold >= NUMKARTITEMS) // bruh moment prevention diff --git a/src/p_mobj.c b/src/p_mobj.c index 062db5dce..3a6176d7b 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -4443,7 +4443,9 @@ static void P_RefreshItemCapsuleParts(mobj_t *mobj) } // update cap colors - if (itemType == KITEM_SUPERRING) + if (mobj->extravalue2) + color = mobj->extravalue2; + else if (itemType == KITEM_SUPERRING) { color = SKINCOLOR_GOLD; newRenderFlags |= RF_SEMIBRIGHT; @@ -4487,7 +4489,10 @@ static void P_RefreshItemCapsuleParts(mobj_t *mobj) count = mobj->movecount; break; case KITEM_SUPERRING: // always display the number, and multiply it by 5 - count = mobj->movecount * 5; + if (mobj->flags2 & MF2_STRONGBOX) + count = mobj->movecount * 20; // give Super Rings + else + count = mobj->movecount * 5; break; case KITEM_SAD: // never display the number case KITEM_SPB: