diff --git a/src/deh_tables.c b/src/deh_tables.c index 97a78e936..0196e2086 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -1208,6 +1208,8 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_EMERALDSPARK6", "S_EMERALDSPARK7", + "S_EMERALDFLARE1", + // Emerald hunt shards "S_SHRD1", "S_SHRD2", @@ -4807,6 +4809,7 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_EMBLEM", "MT_EMERALD", "MT_EMERALDSPARK", + "MT_EMERALDFLARE", "MT_EMERHUNT", // Emerald Hunt "MT_EMERALDSPAWN", // Emerald spawner w/ delay diff --git a/src/info.c b/src/info.c index a3d358d7e..f21641943 100644 --- a/src/info.c +++ b/src/info.c @@ -1878,6 +1878,8 @@ state_t states[NUMSTATES] = {SPR_ESPK, FF_FULLBRIGHT|5, 3, {NULL}, 0, 0, S_EMERALDSPARK7}, // S_EMERALDSPARK6 {SPR_ESPK, FF_FULLBRIGHT|6, 3, {NULL}, 0, 0, S_NULL}, // S_EMERALDSPARK7 + {SPR_LENS, FF_FULLBRIGHT|FF_ADD|FF_TRANS10|FF_ANIMATE|11, 8, {NULL}, 7, 1, S_GAINAX_MID2}, // S_EMERALDFLARE1 + // Emerald hunt shards {SPR_SHRD, 0, -1, {NULL}, 0, 0, S_NULL}, // S_SHRD1 {SPR_SHRD, 1, -1, {NULL}, 0, 0, S_NULL}, // S_SHRD2 @@ -8330,6 +8332,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL // raisestate }, + { // MT_EMERALDFLARE + -1, // doomednum + S_INVISIBLE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // 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 + 8*FRACUNIT, // radius + 8*FRACUNIT, // height + 0, // display offset + 16, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags + S_NULL // raisestate + }, + { // MT_EMERHUNT 320, // doomednum S_SHRD1, // spawnstate diff --git a/src/info.h b/src/info.h index 9dc14922b..81e6554bc 100644 --- a/src/info.h +++ b/src/info.h @@ -2362,6 +2362,8 @@ typedef enum state S_EMERALDSPARK6, S_EMERALDSPARK7, + S_EMERALDFLARE1, + // Emerald hunt shards S_SHRD1, S_SHRD2, @@ -5996,6 +5998,7 @@ typedef enum mobj_type MT_EMBLEM, MT_EMERALD, MT_EMERALDSPARK, + MT_EMERALDFLARE, MT_EMERHUNT, // Emerald Hunt MT_EMERALDSPAWN, // Emerald spawner w/ delay diff --git a/src/k_battle.c b/src/k_battle.c index 837f173b4..4daa5afe4 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -278,8 +278,7 @@ void K_DropEmeraldsFromPlayer(player_t *player, UINT32 emeraldType) if ((player->emeralds & emeraldFlag) && (emeraldFlag & emeraldType)) { - mobj_t *emerald = K_SpawnChaosEmerald(player->mo->x, player->mo->y, player->mo->z, player->mo->angle - ANGLE_90, flip, emeraldFlag); - P_SetTarget(&emerald->target, player->mo); + K_SpawnChaosEmerald(player->mo->x, player->mo->y, player->mo->z, player->mo->angle - ANGLE_90, flip, emeraldFlag); player->emeralds &= ~emeraldFlag; break; // Drop only one emerald. Emerald wins are hard enough! diff --git a/src/k_objects.h b/src/k_objects.h index 8066e7078..79afc08cb 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -207,6 +207,9 @@ void Obj_SneakerPanelCollide(mobj_t *pad, mobj_t *mo); /* Emerald */ void Obj_SpawnEmeraldSparks(mobj_t *source); void Obj_EmeraldThink(mobj_t *emerald); +void Obj_EmeraldFlareThink(mobj_t *flare); +void Obj_BeginEmeraldOrbit(mobj_t *emerald, mobj_t *target, fixed_t radius, INT32 revolution_time, tic_t fuse); +void Obj_GiveEmerald(mobj_t *emerald); #ifdef __cplusplus } // extern "C" diff --git a/src/objects/emerald.c b/src/objects/emerald.c index 40524cd12..fdf7449f2 100644 --- a/src/objects/emerald.c +++ b/src/objects/emerald.c @@ -1,10 +1,25 @@ #include "../k_battle.h" #include "../k_objects.h" +#include "../k_specialstage.h" #include "../info.h" #include "../m_random.h" #include "../p_local.h" +#include "../r_main.h" +#include "../s_sound.h" #include "../tables.h" +#define emerald_type(o) ((o)->extravalue1) +#define emerald_anim_start(o) ((o)->movedir) +#define emerald_revolution_time(o) ((o)->threshold) +#define emerald_start_radius(o) ((o)->movecount) +#define emerald_target_radius(o) ((o)->extravalue2) +#define emerald_z_shift(o) ((o)->reactiontime) +#define emerald_scale_rate(o) ((o)->movefactor) + +// Think of this like EMERALD_SPEED_UP / EMERALD_SPEED_UP_RATE +#define EMERALD_SPEED_UP (1) // speed up by this much... +#define EMERALD_SPEED_UP_RATE (1) // ...every N tics + void Obj_SpawnEmeraldSparks(mobj_t *mobj) { if (leveltime % 3 != 0) @@ -25,31 +40,95 @@ void Obj_SpawnEmeraldSparks(mobj_t *mobj) sparkle->sprzoff = mobj->sprzoff; } +static INT32 get_elapsed(mobj_t *emerald) +{ + return leveltime - min((tic_t)emerald_anim_start(emerald), leveltime); +} + +static INT32 get_revolve_time(mobj_t *emerald) +{ + return max(1, emerald_revolution_time(emerald)); +} + +static fixed_t get_suck_factor(mobj_t *emerald) +{ + const INT32 suck_time = get_revolve_time(emerald) * 2; + + return (min(get_elapsed(emerald), suck_time) * FRACUNIT) / suck_time; +} + +static fixed_t get_current_radius(mobj_t *emerald) +{ + fixed_t s = emerald_start_radius(emerald); + fixed_t t = emerald_target_radius(emerald); + + return s + FixedMul(t - s, get_suck_factor(emerald)); +} + +static fixed_t get_bob(mobj_t *emerald) +{ + // With a fuse, the emerald experiences "speed up" and the + // scale also shrinks. All of these these effects caused + // the bob phase shift to look disproportioned. + angle_t phase = emerald->fuse ? 0 : get_elapsed(emerald) * ((ANGLE_MAX / get_revolve_time(emerald)) / 2); + + return FixedMul(30 * mapobjectscale, FSIN(emerald->angle + phase)); +} + +static fixed_t center_of(mobj_t *mobj) +{ + return mobj->z + (mobj->height / 2); +} + +static fixed_t get_target_z(mobj_t *emerald) +{ + fixed_t shift = FixedMul(emerald_z_shift(emerald), FRACUNIT - get_suck_factor(emerald)); + + return center_of(emerald->target) + get_bob(emerald) + shift; +} + +static void speed_up(mobj_t *emerald) +{ + // Revolution time shouldn't decrease below zero. + if (emerald_revolution_time(emerald) <= EMERALD_SPEED_UP) + { + return; + } + + if (get_elapsed(emerald) % EMERALD_SPEED_UP_RATE) + { + return; + } + + // Decrease the fuse proportionally to the revolution time. + const fixed_t ratio = (emerald->fuse * FRACUNIT) / emerald_revolution_time(emerald); + + emerald_revolution_time(emerald) -= EMERALD_SPEED_UP; + + emerald->fuse = max(1, (emerald_revolution_time(emerald) * ratio) / FRACUNIT); +} + static void Obj_EmeraldOrbitPlayer(mobj_t *emerald) { - const int kOrbitTics = 64; - const int kPhaseTics = 128; - - const fixed_t orbit_radius = 100 * mapobjectscale; - const fixed_t orbit_height = 30 * mapobjectscale; - - mobj_t *targ = emerald->target; - - angle_t a = emerald->angle; - - fixed_t x = FixedMul(orbit_radius, FCOS(a)); - fixed_t y = FixedMul(orbit_radius, FSIN(a)); - - angle_t phase = (ANGLE_MAX / kPhaseTics) * (leveltime % kPhaseTics); + fixed_t r = get_current_radius(emerald); + fixed_t x = FixedMul(r, FCOS(emerald->angle)); + fixed_t y = FixedMul(r, FSIN(emerald->angle)); P_MoveOrigin( emerald, - targ->x + x, - targ->y + y, - targ->z + targ->height + FixedMul(orbit_height, FSIN(a + phase)) + emerald->target->x + x, + emerald->target->y + y, + get_target_z(emerald) ); - emerald->angle += ANGLE_MAX / kOrbitTics; + emerald->angle += ANGLE_MAX / get_revolve_time(emerald); + + if (emerald->fuse > 0) + { + speed_up(emerald); + + P_InstaScale(emerald, emerald->fuse * emerald_scale_rate(emerald)); + } } void Obj_EmeraldThink(mobj_t *emerald) @@ -84,3 +163,156 @@ void Obj_EmeraldThink(mobj_t *emerald) K_BattleOvertimeKiller(emerald); } + +static mobj_t *spawn_glow(mobj_t *flare) +{ + mobj_t *targ = flare->target; + mobj_t *x = P_SpawnGhostMobj(targ); + + x->old_x = targ->old_x; + x->old_y = targ->old_y; + x->old_z = targ->old_z; + + x->fuse = 2; // this actually does last one tic + x->extravalue1 = 1; + x->extravalue2 = 0; + + x->renderflags = RF_ADD | RF_ALWAYSONTOP; + + // FIXME: linkdraw doesn't work consistently, so I drew it on top of everyting (and through walls) +#if 0 + P_SetTarget(&x->tracer, targ); + x->flags2 |= MF2_LINKDRAW; + x->dispoffset = 1000; +#endif + + return x; +} + +static mobj_t *spawn_glow_colorize(mobj_t *flare) +{ + mobj_t *x = spawn_glow(flare); + + x->color = flare->color; + x->colorized = true; + + return x; +} + +void Obj_EmeraldFlareThink(mobj_t *flare) +{ + const INT32 kExtraTics = 3; + const INT32 flare_tics = states[S_EMERALDFLARE1].tics + kExtraTics; + + if (P_MobjWasRemoved(flare->target)) + { + P_RemoveMobj(flare); + return; + } + + // Target is assumed to be the emerald in orbit. When + // emerald fuse runs out, it shall update player's emerald + // flags. Time the flare animation so it ends with the + // emerald fuse. + if (!flare->fuse && flare->target->fuse > flare_tics) + { + return; + } + + if (flare->state == &states[S_INVISIBLE]) + { + // In special stages, just follow the emerald. + if (specialstageinfo.valid == false) + { + // Update target to player. We don't need to track + // the emerald anymore. + P_SetTarget(&flare->target, flare->target->target); + + if (P_MobjWasRemoved(flare->target)) + { + P_RemoveMobj(flare); + return; + } + } + + P_SetMobjState(flare, S_EMERALDFLARE1); + flare->fuse = flare_tics; + } + + // Focus on center of player. + P_SetOrigin(flare, flare->target->x, flare->target->y, center_of(flare->target)); + + if (leveltime & 1) + { + // Stacked for more exposure + spawn_glow_colorize(flare); + spawn_glow(flare); + spawn_glow(flare); + } +} + +static void spawn_lens_flare(mobj_t *emerald) +{ + mobj_t *flare = P_SpawnMobjFromMobj(emerald, 0, 0, 0, MT_EMERALDFLARE); + + P_SetTarget(&flare->target, emerald); + P_InstaScale(flare, emerald->target->scale); + + flare->color = emerald->color; + flare->colorized = true; + + flare->renderflags |= RF_ALWAYSONTOP; + + // FIXME: linkdraw doesn't work consistently, so I drew it on top of everyting (and through walls) +#if 0 + P_SetTarget(&flare->tracer, emerald->target); + flare->flags2 |= MF2_LINKDRAW; + flare->dispoffset = 1000; +#endif +} + +void Obj_BeginEmeraldOrbit(mobj_t *emerald, mobj_t *target, fixed_t radius, INT32 revolution_time, tic_t fuse) +{ + P_SetTarget(&emerald->target, target); + + emerald_anim_start(emerald) = leveltime; + emerald_revolution_time(emerald) = revolution_time; + + emerald_start_radius(emerald) = R_PointToDist2(target->x, target->y, emerald->x, emerald->y); + emerald_target_radius(emerald) = radius; + + emerald->fuse = fuse; + + if (fuse) + { + emerald_scale_rate(emerald) = emerald->scale / fuse; + } + + emerald->angle = R_PointToAngle2(target->x, target->y, emerald->x, emerald->y); + emerald_z_shift(emerald) = emerald->z - get_target_z(emerald); + + emerald->flags |= MF_NOGRAVITY | MF_NOCLIP | MF_NOCLIPTHING | MF_NOCLIPHEIGHT; + emerald->shadowscale = 0; + + spawn_lens_flare(emerald); +} + +void Obj_GiveEmerald(mobj_t *emerald) +{ + if (P_MobjWasRemoved(emerald->target)) + { + return; + } + + player_t *player = emerald->target->player; + + if (!player) + { + return; + } + + player->emeralds |= emerald_type(emerald); + K_CheckEmeralds(player); + + S_StartSound(emerald->target, emerald->info->deathsound); +} diff --git a/src/objects/ufo.c b/src/objects/ufo.c index f8e15c459..6a9f4bbdc 100644 --- a/src/objects/ufo.c +++ b/src/objects/ufo.c @@ -883,7 +883,7 @@ boolean Obj_UFOEmeraldCollect(mobj_t *ufo, mobj_t *toucher) const int kScaleTics = 16; // Emerald will now orbit the player - P_SetTarget(&emerald->target, toucher); + Obj_BeginEmeraldOrbit(emerald, toucher, 100 * mapobjectscale, 64, 0); // Scale down because the emerald is huge // Super Emerald needs to be scaled down further diff --git a/src/p_inter.c b/src/p_inter.c index 918c3faca..eebfa1ddc 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -464,9 +464,14 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (toucher->hitlag > 0) return; - player->emeralds |= special->extravalue1; - K_CheckEmeralds(player); - break; + // Emerald will now orbit the player + + { + const tic_t orbit = 2*TICRATE; + Obj_BeginEmeraldOrbit(special, toucher, toucher->radius, orbit, orbit * 20); + } + + return; case MT_SPECIAL_UFO: if (Obj_UFOEmeraldCollect(special, toucher) == false) { diff --git a/src/p_mobj.c b/src/p_mobj.c index ab0d25e5e..2402a2ed5 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -7551,6 +7551,14 @@ static boolean P_MobjRegularThink(mobj_t *mobj) case MT_EMERALD: Obj_EmeraldThink(mobj); + if (P_MobjWasRemoved(mobj)) + { + return false; + } + break; + case MT_EMERALDFLARE: + Obj_EmeraldFlareThink(mobj); + if (P_MobjWasRemoved(mobj)) { return false; @@ -9825,6 +9833,12 @@ static boolean P_FuseThink(mobj_t *mobj) break; } + case MT_EMERALD: + { + Obj_GiveEmerald(mobj); + P_RemoveMobj(mobj); + return false; + } case MT_PLAYER: break; // don't remove default: