diff --git a/src/deh_tables.c b/src/deh_tables.c index 36239071c..c39d4fb67 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -3005,6 +3005,10 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi // MT_STARSTREAM "S_STARSTREAM", + + // MT_SCRIPT_THING + "S_TALKPOINT", + "S_TALKPOINT_ORB", }; // RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1", @@ -3726,6 +3730,7 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_CHECKPOINT_END", "MT_SCRIPT_THING", + "MT_SCRIPT_THING_ORB", "MT_RIDEROID", "MT_RIDEROIDNODE", diff --git a/src/info.c b/src/info.c index bc5068769..beeb54ccc 100644 --- a/src/info.c +++ b/src/info.c @@ -734,6 +734,9 @@ char sprnames[NUMSPRITES + 1][5] = "SENC", "SEAS", + // Tutorial + "TLKP", // Talk Point + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later "VIEW", }; @@ -3515,6 +3518,10 @@ state_t states[NUMSTATES] = // MT_STARSTREAM {SPR_SEAS, FF_ANIMATE|0, 30, {NULL}, 29, 1, S_NULL}, // S_STARSTREAM + + // MT_SCRIPT_THING + {SPR_TLKP, 0|FF_SEMIBRIGHT|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_TALKPOINT + {SPR_TLKP, 1|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_TALKPOINT_ORB }; mobjinfo_t mobjinfo[NUMMOBJTYPES] = @@ -18517,7 +18524,34 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { // MT_SCRIPT_THING 4096, // doomednum - S_INVISIBLE, // spawnstate + S_TALKPOINT, // 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 + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 0, // display offset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY|MF_SCENERY, // flags + S_NULL // raisestate + }, + + { // MT_SCRIPT_THING_ORB + -1, // doomednum + S_TALKPOINT_ORB, // spawnstate 1000, // spawnhealth S_NULL, // seestate sfx_None, // seesound diff --git a/src/info.h b/src/info.h index da42752e4..d6992b557 100644 --- a/src/info.h +++ b/src/info.h @@ -1269,6 +1269,9 @@ typedef enum sprite SPR_SENC, // Cabotron SPR_SEAS, // Starstream + // Tutorial + SPR_TLKP, // Talk Point + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later SPR_VIEW, @@ -4002,6 +4005,10 @@ typedef enum state // MT_STARSTREAM S_STARSTREAM, + // MT_SCRIPT_THING + S_TALKPOINT, + S_TALKPOINT_ORB, + S_FIRSTFREESLOT, S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1, NUMSTATES @@ -4742,6 +4749,7 @@ typedef enum mobj_type MT_CHECKPOINT_END, MT_SCRIPT_THING, + MT_SCRIPT_THING_ORB, MT_RIDEROID, MT_RIDEROIDNODE, diff --git a/src/k_objects.h b/src/k_objects.h index cc2162d96..fa708ae5c 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -393,7 +393,9 @@ void Obj_SSCabotronMobjThink(mobj_t* mo); void Obj_SSCabotronStarMobjThink(mobj_t* mo); /* Talk Point */ +void Obj_TalkPointInit(mobj_t* mo); void Obj_TalkPointThink(mobj_t* mo); +void Obj_TalkPointOrbThink(mobj_t* mo); #ifdef __cplusplus } // extern "C" diff --git a/src/objects/talk-point.cpp b/src/objects/talk-point.cpp index e423fe071..1748eab57 100644 --- a/src/objects/talk-point.cpp +++ b/src/objects/talk-point.cpp @@ -7,24 +7,45 @@ // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- +#include +#include + #include "objects.hpp" #include "../doomdef.h" #include "../g_game.h" #include "../p_spec.h" +#include "../tables.h" using namespace srb2::objects; namespace { +Vec2 angle_vector(angle_t x) +{ + return Vec2 {FCOS(x), FSIN(x)}; +} + struct TalkPoint : Mobj { + static constexpr angle_t kSpinSpeed = ANGLE_11hh; + static constexpr tic_t kCollectDuration = 12; + void thing_args() = delete; Fixed radius() const { return mobj_t::thing_args[0] * FRACUNIT; } bool oneshot() const { return !mobj_t::thing_args[1]; } bool disabled() const { return mobj_t::thing_args[2]; } + void extravalue1() = delete; + tic_t collect() const { return mobj_t::extravalue1; } + void collect(tic_t n) { mobj_t::extravalue1 = n; } + + // This value scales up as the radius gets larger + Fixed scaling() const { return std::min(FRACUNIT, Fixed {320 * mapobjectscale} / radius()); } + + void init(); + void think() { if (disabled()) @@ -33,6 +54,26 @@ struct TalkPoint : Mobj return; } + if (collect() > 0) + { + collect(collect() - 1); + + if (collect() == 0) + { + remove(); + return; + } + + // Collect animation: stretch upward, spin faster, fade out + spriteyscale(kCollectDuration * FRACUNIT / collect()); + angle += kCollectDuration * kSpinSpeed / collect(); + renderflags = (renderflags & ~RF_TRANSMASK) | + ((9 - (collect() * 9 / kCollectDuration)) << RF_TRANSSHIFT); + return; + } + + angle += kSpinSpeed; + for (UINT8 i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] == false || players[i].spectator == true) @@ -53,8 +94,9 @@ struct TalkPoint : Mobj if (oneshot()) { - remove(); - return; + // Collect animation: appear brighter during fade-out + renderflags |= RF_FULLBRIGHT; + collect(kCollectDuration); } break; @@ -63,9 +105,115 @@ struct TalkPoint : Mobj } }; +struct Orb : Mobj +{ + static constexpr std::array kColors = { + SKINCOLOR_NONE, + SKINCOLOR_JAWZ, + SKINCOLOR_AQUAMARINE, + SKINCOLOR_LIME, + SKINCOLOR_BANANA, + SKINCOLOR_CREAMSICLE, + SKINCOLOR_DAWN, + SKINCOLOR_TAFFY, + SKINCOLOR_VIOLET, + SKINCOLOR_RUST, + }; + + void target() = delete; + TalkPoint* origin() const { return Mobj::target(); } + void origin(TalkPoint* n) { Mobj::target(n); } + + void extravalue1() = delete; + angle_t speed() const { return mobj_t::extravalue1; } + void speed(angle_t n) { mobj_t::extravalue1 = n; } + + bool valid() const { return Mobj::valid(origin()) && origin()->valid(); } + + static Orb* spawn(TalkPoint* origin, UINT8 index, angle_t angle) + { + Orb* orb = Mobj::spawn({origin->pos2d() + radius_vector(origin, angle), origin->z}, MT_SCRIPT_THING_ORB); + orb->origin(origin); + + orb->color = kColors[index % kColors.size()]; // cycle colors + orb->colorized = true; + + orb->angle = angle; + orb->sprzoff(20 * mapobjectscale); // roughly eye level with the player + orb->speed(FixedAngle(Fixed {45*FRACUNIT/8} * origin->scaling())); // slower with larger radius + + return orb; + } + + void think() + { + if (!valid()) + { + remove(); + return; + } + + // rotate + angle += speed(); + move_origin({origin()->pos2d() + radius_vector(), origin()->z}); + + // bob + spriteyoffset(8 * FSIN(angle + (leveltime * speed()))); + } + +private: + static vec2 radius_vector(TalkPoint* origin, angle_t angle) + { + // Collect animation: orbs fly outward + return angle_vector(angle) * origin->radius() * origin->spriteyscale(); + } + + vec2 radius_vector() const { return radius_vector(origin(), angle); } +}; + +void TalkPoint::init() +{ + if (scaling() == 0) + { + return; + } + + // Orbs get spaced further apart as the radius increases + fixed_t orb_rad = Fixed {88 * mapobjectscale} / scaling(); + if (orb_rad == 0) + { + return; + } + + // Spawn more orbs within a larger radius + INT32 count = (Fixed {M_TAU_FIXED} * radius()) / orb_rad; + if (count == 0) + { + return; + } + + angle_t step = ANGLE_MAX / count; + angle_t angle = 0; + for (INT32 i = 0; i < count; ++i) + { + Orb::spawn(this, i, angle); + angle += step; + } +} + }; // namespace +void Obj_TalkPointInit(mobj_t* mo) +{ + static_cast(mo)->init(); +} + void Obj_TalkPointThink(mobj_t* mo) { static_cast(mo)->think(); } + +void Obj_TalkPointOrbThink(mobj_t* mo) +{ + static_cast(mo)->think(); +} diff --git a/src/p_mobj.c b/src/p_mobj.c index 31549adc6..34fbb9b75 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -6460,6 +6460,9 @@ static void P_MobjSceneryThink(mobj_t *mobj) case MT_SCRIPT_THING: Obj_TalkPointThink(mobj); return; + case MT_SCRIPT_THING_ORB: + Obj_TalkPointOrbThink(mobj); + return; case MT_SPIKEDTARGET: { if (P_MobjWasRemoved(mobj->target) || (mobj->target->health <= 0) || (mobj->target->z == mobj->target->floorz)) @@ -10565,6 +10568,10 @@ fixed_t P_GetMobjDefaultScale(mobj_t *mobj) return 4*FRACUNIT; case MT_WALLSPIKE: return 2*FRACUNIT; + case MT_SCRIPT_THING: + return 4*FRACUNIT; + case MT_SCRIPT_THING_ORB: + return 2*FRACUNIT; default: break; } @@ -13841,6 +13848,11 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj) Obj_SSWindowMapThingSpawn(mobj, mthing); break; } + case MT_SCRIPT_THING: + { + Obj_TalkPointInit(mobj); + break; + } default: break; }