Add Toxomister item

This commit is contained in:
James R 2025-07-02 23:05:31 -07:00
parent 8477dcb1f9
commit dd05900bcb
15 changed files with 617 additions and 2 deletions

View file

@ -687,6 +687,7 @@ consvar_t cv_items[] = {
UnsavedNetVar("gardentop", "On").on_off(), UnsavedNetVar("gardentop", "On").on_off(),
UnsavedNetVar("gachabom", "On").on_off(), UnsavedNetVar("gachabom", "On").on_off(),
UnsavedNetVar("stoneshoe", "On").on_off(), UnsavedNetVar("stoneshoe", "On").on_off(),
UnsavedNetVar("toxomister", "On").on_off(),
UnsavedNetVar("dualsneaker", "On").on_off(), UnsavedNetVar("dualsneaker", "On").on_off(),
UnsavedNetVar("triplesneaker", "On").on_off(), UnsavedNetVar("triplesneaker", "On").on_off(),
UnsavedNetVar("triplebanana", "On").on_off(), UnsavedNetVar("triplebanana", "On").on_off(),

View file

@ -199,7 +199,8 @@ Run this macro, then #undef FOREACH afterward
FOREACH (DROPTARGET, 21),\ FOREACH (DROPTARGET, 21),\
FOREACH (GARDENTOP, 22),\ FOREACH (GARDENTOP, 22),\
FOREACH (GACHABOM, 23),\ FOREACH (GACHABOM, 23),\
FOREACH (STONESHOE, 24) FOREACH (STONESHOE, 24),\
FOREACH (TOXOMISTER, 25)
typedef enum typedef enum
{ {
@ -1066,6 +1067,7 @@ struct player_t
mobj_t *hand; mobj_t *hand;
mobj_t *flickyAttacker; mobj_t *flickyAttacker;
mobj_t *stoneShoe; mobj_t *stoneShoe;
mobj_t *toxomisterCloud;
SINT8 pitblame; // Index of last player that hit you, resets after being in control for a bit. If you deathpit, credit the old attacker! SINT8 pitblame; // Index of last player that hit you, resets after being in control for a bit. If you deathpit, credit the old attacker!

View file

@ -3120,6 +3120,10 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi
"S_FLYBOT767", "S_FLYBOT767",
"S_STON", "S_STON",
"S_TOXAA",
"S_TOXAB",
"S_TOXBA",
}; };
// RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1", // RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1",
@ -4028,6 +4032,10 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t
"MT_STONESHOE", "MT_STONESHOE",
"MT_STONESHOE_CHAIN", "MT_STONESHOE_CHAIN",
"MT_TOXOMISTER_POLE",
"MT_TOXOMISTER_EYE",
"MT_TOXOMISTER_CLOUD",
}; };
const char *const MOBJFLAG_LIST[] = { const char *const MOBJFLAG_LIST[] = {

View file

@ -801,6 +801,8 @@ char sprnames[NUMSPRITES + 1][5] =
"STUN", "STUN",
"STON", "STON",
"TOXA",
"TOXB",
// Pulley // Pulley
"HCCH", "HCCH",
@ -3704,6 +3706,10 @@ state_t states[NUMSTATES] =
{SPR_STUN, FF_FULLBRIGHT|FF_ANIMATE, -1, {NULL}, 4, 4, S_NULL}, // S_FLYBOT767 {SPR_STUN, FF_FULLBRIGHT|FF_ANIMATE, -1, {NULL}, 4, 4, S_NULL}, // S_FLYBOT767
{SPR_STON, 0, -1, {NULL}, 0, 0, S_STON}, // S_STON {SPR_STON, 0, -1, {NULL}, 0, 0, S_STON}, // S_STON
//
{SPR_TOXA, 0, -1, {NULL}, 0, 0, S_TOXAA}, // S_TOXAA
{SPR_TOXA, 1, -1, {NULL}, 0, 0, S_TOXAB}, // S_TOXAB
{SPR_TOXB, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 6, 5, S_TOXBA}, // S_TOXBA
}; };
mobjinfo_t mobjinfo[NUMMOBJTYPES] = mobjinfo_t mobjinfo[NUMMOBJTYPES] =
@ -22712,6 +22718,84 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
MF_SPECIAL|MF_SCENERY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_PICKUPFROMBELOW|MF_DONTENCOREMAP, // flags MF_SPECIAL|MF_SCENERY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_PICKUPFROMBELOW|MF_DONTENCOREMAP, // flags
S_NULL // raisestate S_NULL // raisestate
}, },
{ // MT_TOXOMISTER_POLE
-1, // doomednum
S_TOXAA, // spawnstate
1, // spawnhealth
S_NULL, // seestate
sfx_tossed, // 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
32*FRACUNIT, // radius
64*FRACUNIT, // height
0, // display offset
0, // mass
0, // damage
sfx_None, // activesound
MF_SHOOTABLE|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
{ // MT_TOXOMISTER_EYE
-1, // doomednum
S_TOXAB, // 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
32*FRACUNIT, // radius
64*FRACUNIT, // height
0, // display offset
0, // mass
0, // damage
sfx_None, // activesound
MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
{ // MT_TOXOMISTER_CLOUD
-1, // doomednum
S_TOXBA, // 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
70*FRACUNIT, // radius
70*FRACUNIT, // height
0, // display offset
0, // mass
0, // damage
sfx_None, // activesound
MF_SPECIAL|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
}; };

View file

@ -1338,6 +1338,8 @@ typedef enum sprite
SPR_STUN, SPR_STUN,
SPR_STON, SPR_STON,
SPR_TOXA,
SPR_TOXB,
// Pulley // Pulley
SPR_HCCH, SPR_HCCH,
@ -4188,6 +4190,10 @@ typedef enum state
S_STON, S_STON,
S_TOXAA,
S_TOXAB,
S_TOXBA,
S_FIRSTFREESLOT, S_FIRSTFREESLOT,
S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1, S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1,
NUMSTATES NUMSTATES
@ -5119,6 +5125,10 @@ typedef enum mobj_type
MT_STONESHOE, MT_STONESHOE,
MT_STONESHOE_CHAIN, MT_STONESHOE_CHAIN,
MT_TOXOMISTER_POLE,
MT_TOXOMISTER_EYE,
MT_TOXOMISTER_CLOUD,
MT_FIRSTFREESLOT, MT_FIRSTFREESLOT,
MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1, MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1,
NUMMOBJTYPES NUMMOBJTYPES

View file

@ -172,6 +172,7 @@ static patch_t *kp_droptarget[3];
static patch_t *kp_gardentop[3]; static patch_t *kp_gardentop[3];
static patch_t *kp_gachabom[3]; static patch_t *kp_gachabom[3];
static patch_t *kp_stoneshoe[3]; static patch_t *kp_stoneshoe[3];
static patch_t *kp_toxomister[3];
static patch_t *kp_bar[2]; static patch_t *kp_bar[2];
static patch_t *kp_doublebar[2]; static patch_t *kp_doublebar[2];
static patch_t *kp_triplebar[2]; static patch_t *kp_triplebar[2];
@ -649,6 +650,7 @@ void K_LoadKartHUDGraphics(void)
HU_UpdatePatch(&kp_gardentop[0], "K_ITGTOP"); HU_UpdatePatch(&kp_gardentop[0], "K_ITGTOP");
HU_UpdatePatch(&kp_gachabom[0], "K_ITGBOM"); HU_UpdatePatch(&kp_gachabom[0], "K_ITGBOM");
HU_UpdatePatch(&kp_stoneshoe[0], "K_ITSTON"); HU_UpdatePatch(&kp_stoneshoe[0], "K_ITSTON");
HU_UpdatePatch(&kp_toxomister[0], "K_ITTOX");
HU_UpdatePatch(&kp_bar[0], "K_RBBAR"); HU_UpdatePatch(&kp_bar[0], "K_RBBAR");
HU_UpdatePatch(&kp_doublebar[0], "K_RBBAR2"); HU_UpdatePatch(&kp_doublebar[0], "K_RBBAR2");
HU_UpdatePatch(&kp_triplebar[0], "K_RBBAR3"); HU_UpdatePatch(&kp_triplebar[0], "K_RBBAR3");
@ -710,6 +712,7 @@ void K_LoadKartHUDGraphics(void)
HU_UpdatePatch(&kp_gardentop[1], "K_ISGTOP"); HU_UpdatePatch(&kp_gardentop[1], "K_ISGTOP");
HU_UpdatePatch(&kp_gachabom[1], "K_ISGBOM"); HU_UpdatePatch(&kp_gachabom[1], "K_ISGBOM");
HU_UpdatePatch(&kp_stoneshoe[1], "K_ISSTON"); HU_UpdatePatch(&kp_stoneshoe[1], "K_ISSTON");
HU_UpdatePatch(&kp_toxomister[1], "K_ISTOX");
HU_UpdatePatch(&kp_bar[1], "K_SBBAR"); HU_UpdatePatch(&kp_bar[1], "K_SBBAR");
HU_UpdatePatch(&kp_doublebar[1], "K_SBBAR2"); HU_UpdatePatch(&kp_doublebar[1], "K_SBBAR2");
HU_UpdatePatch(&kp_triplebar[1], "K_SBBAR3"); HU_UpdatePatch(&kp_triplebar[1], "K_SBBAR3");
@ -769,6 +772,7 @@ void K_LoadKartHUDGraphics(void)
HU_UpdatePatch(&kp_gardentop[2], "ISPYGTOP"); HU_UpdatePatch(&kp_gardentop[2], "ISPYGTOP");
HU_UpdatePatch(&kp_gachabom[2], "ISPYGBOM"); HU_UpdatePatch(&kp_gachabom[2], "ISPYGBOM");
HU_UpdatePatch(&kp_stoneshoe[2], "ISPYSTON"); HU_UpdatePatch(&kp_stoneshoe[2], "ISPYSTON");
HU_UpdatePatch(&kp_toxomister[2], "ISPYTOX");
// CHECK indicators // CHECK indicators
sprintf(buffer, "K_CHECKx"); sprintf(buffer, "K_CHECKx");
@ -1190,6 +1194,7 @@ static patch_t *K_GetCachedItemPatch(INT32 item, UINT8 offset)
kp_gardentop, kp_gardentop,
kp_gachabom, kp_gachabom,
kp_stoneshoe, kp_stoneshoe,
kp_toxomister,
}; };
if (item == KITEM_SAD || (item > KITEM_NONE && item < NUMKARTITEMS)) if (item == KITEM_SAD || (item > KITEM_NONE && item < NUMKARTITEMS))

View file

@ -4143,6 +4143,11 @@ fixed_t K_GetNewSpeed(const player_t *player)
p_speed = 15 * p_speed / 10; p_speed = 15 * p_speed / 10;
} }
if (!P_MobjWasRemoved(player->toxomisterCloud))
{
p_speed = FixedMul(p_speed, Obj_GetToxomisterCloudDrag(player->toxomisterCloud));
}
if (K_PlayerUsesBotMovement(player) == true && player->botvars.rubberband > 0) if (K_PlayerUsesBotMovement(player) == true && player->botvars.rubberband > 0)
{ {
// Acceleration is tied to top speed... // Acceleration is tied to top speed...
@ -7076,7 +7081,7 @@ mobj_t *K_ThrowKartItemEx(player_t *player, boolean missile, mobjtype_t mapthing
{ {
mobj_t *lasttrail = K_FindLastTrailMobj(player); mobj_t *lasttrail = K_FindLastTrailMobj(player);
if (mapthing == MT_BUBBLESHIELDTRAP) // Drop directly on top of you. if (mapthing == MT_BUBBLESHIELDTRAP || mapthing == MT_TOXOMISTER_POLE) // Drop directly on top of you.
{ {
newangle = player->mo->angle; newangle = player->mo->angle;
newx = player->mo->x + player->mo->momx; newx = player->mo->x + player->mo->momx;
@ -15069,6 +15074,21 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
player->botvars.itemconfirm = 0; player->botvars.itemconfirm = 0;
} }
break; break;
case KITEM_TOXOMISTER:
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
{
K_SetItemOut(player); // need this to set itemscale
mobj_t *pole = K_ThrowKartItem(player, false, MT_TOXOMISTER_POLE, -1, 0, 0);
Obj_InitToxomisterPole(pole);
K_UnsetItemOut(player);
player->itemamount--;
K_PlayAttackTaunt(player->mo);
player->botvars.itemconfirm = 0;
}
break;
case KITEM_SAD: case KITEM_SAD:
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO
&& !player->sadtimer) && !player->sadtimer)
@ -16363,6 +16383,7 @@ boolean K_IsPickMeUpItem(mobjtype_t type)
case MT_SSMINE: case MT_SSMINE:
case MT_SSMINE_SHIELD: case MT_SSMINE_SHIELD:
case MT_FLOATINGITEM: // Stone Shoe case MT_FLOATINGITEM: // Stone Shoe
case MT_TOXOMISTER_POLE:
return true; return true;
default: default:
return false; return false;
@ -16425,6 +16446,9 @@ static boolean K_PickUp(player_t *player, mobj_t *picked)
else else
type = KITEM_SAD; type = KITEM_SAD;
break; break;
case MT_TOXOMISTER_POLE:
type = KITEM_TOXOMISTER;
break;
default: default:
type = KITEM_SAD; type = KITEM_SAD;
break; break;

View file

@ -475,6 +475,16 @@ boolean Obj_TickStoneShoeChain(mobj_t *chain);
player_t *Obj_StoneShoeOwnerPlayer(mobj_t *shoe); player_t *Obj_StoneShoeOwnerPlayer(mobj_t *shoe);
void Obj_CollideStoneShoe(mobj_t *mover, mobj_t *mobj); void Obj_CollideStoneShoe(mobj_t *mover, mobj_t *mobj);
/* Toxomister */
void Obj_InitToxomisterPole(mobj_t *pole);
boolean Obj_TickToxomisterPole(mobj_t *pole);
boolean Obj_TickToxomisterEye(mobj_t *eye);
boolean Obj_TickToxomisterCloud(mobj_t *cloud);
boolean Obj_ToxomisterPoleCollide(mobj_t *pole, mobj_t *toucher);
boolean Obj_ToxomisterCloudCollide(mobj_t *cloud, mobj_t *toucher);
fixed_t Obj_GetToxomisterCloudDrag(mobj_t *cloud);
#ifdef __cplusplus #ifdef __cplusplus
} // extern "C" } // extern "C"
#endif #endif

View file

@ -67,6 +67,7 @@ target_sources(SRB2SDL2 PRIVATE
stone-shoe.cpp stone-shoe.cpp
exp.c exp.c
bail.c bail.c
toxomister.cpp
) )
add_subdirectory(versus) add_subdirectory(versus)

427
src/objects/toxomister.cpp Normal file
View file

@ -0,0 +1,427 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2025 by James Robert Roman
// Copyright (C) 2025 by Kart Krew
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include <algorithm>
#include <array>
#include "objects.hpp"
#include "../core/static_vec.hpp"
#include "../d_player.h"
#include "../doomdef.h"
#include "../doomtype.h"
#include "../g_game.h"
#include "../k_hud.h" // transflag
#include "../m_easing.h"
#include "../m_fixed.h"
#include "../m_random.h"
#include "../r_main.h"
#include "../tables.h"
using namespace srb2::objects;
namespace
{
Fixed distance3d(const Mobj* a, const Mobj* b)
{
return FixedHypot(FixedHypot(a->x - b->x, a->y - b->y), a->z - b->z);
}
Vec2<Fixed> angle_vector(angle_t x)
{
return Vec2<Fixed> {FCOS(x), FSIN(x)};
}
// copied from objects/hyudoro.c
static void
sine_bob
( mobj_t * hyu,
INT32 height,
angle_t a,
fixed_t sineofs)
{
hyu->sprzoff = FixedMul(height * hyu->scale,
sineofs + FINESINE(a >> ANGLETOFINESHIFT)) * P_MobjFlip(hyu);
if (P_IsObjectFlipped(hyu))
hyu->sprzoff -= hyu->height;
}
static void
bob_in_place
( mobj_t * hyu,
INT32 height,
INT32 bob_speed)
{
sine_bob(hyu,
height,
(leveltime & (bob_speed - 1)) *
(ANGLE_MAX / bob_speed), -(3*FRACUNIT/4));
}
struct Eye;
struct Pole;
struct Cloud;
struct Eye : Mobj
{
static constexpr INT32 kOrbitRadius = 24;
bool valid() const { return Mobj::valid(owner()); }
bool tick()
{
if (!valid())
{
remove();
return false;
}
return true;
}
};
struct Pole : Mobj
{
static constexpr sfxenum_t kSound = sfx_s3kdal;
void extravalue1() = delete;
tic_t last_touch0() const { return mobj_t::extravalue1; }
void last_touch0(tic_t n) { mobj_t::extravalue1 = n; }
void extravalue2() = delete;
bool clouds_spawned() const { return mobj_t::extravalue2; }
void clouds_spawned(bool n) { mobj_t::extravalue2 = n; }
void reactiontime() = delete;
tic_t sound_started() const { return mobj_t::reactiontime; }
void sound_started(tic_t n) { mobj_t::reactiontime = n; }
void tracer() = delete;
Eye* eye() const { return Mobj::tracer<Eye>(); }
void eye(Eye* n) { Mobj::tracer(n); }
bool valid() const
{
if (!Mobj::valid(eye()))
return false;
return true;
}
void init()
{
Eye* p_eye = spawn_from<Eye>(MT_TOXOMISTER_EYE);
p_eye->owner(this);
p_eye->spriteyoffset(96*FRACUNIT);
last_touch0(leveltime);
clouds_spawned(false);
eye(p_eye);
flags |= MF_SPECIAL;
}
void spawn_clouds_in_orbit();
bool tick()
{
if (!valid())
{
remove();
return false;
}
if (P_IsObjectOnGround(this))
{
if (!clouds_spawned())
{
spawn_clouds_in_orbit();
clouds_spawned(true);
voice(sfx_s3k9e);
}
if (!voice_playing(kSound))
{
voice(kSound);
sound_started(leveltime);
}
if ((leveltime - sound_started()) % 256 == 0)
voice(kSound);
}
else
{
P_SpawnGhostMobj(this);
}
tick_eye();
return true;
}
void tick_eye()
{
Mobj::PosArg p = {pos2d(), z};
p.x += momx;
p.y += momy;
p.z += momz;
Mobj* targ = find_nearest_eyeball_target();
if (targ)
{
INT32 angle_to_targ = angle_to2d(targ);
Vec2<Fixed> v = angle_vector(angle_to_targ) * Fixed {Eye::kOrbitRadius * mapobjectscale};
p.x += v.x;
p.y += v.y;
eye()->angle = angle_to_targ;
}
eye()->move_origin(p);
}
angle_t angle_to2d(Mobj* mobj) const
{
return R_PointToAngle2(x, y, mobj->x, mobj->y);
}
Mobj* find_nearest_eyeball_target() const
{
srb2::StaticVec<Mobj*, MAXPLAYERS> targets;
for (INT32 i = 0; i < MAXPLAYERS; ++i)
{
if (!playeringame[i])
continue;
if (!players[i].mo)
continue;
targets.push_back(static_cast<Mobj*>(players[i].mo));
}
if (targets.empty())
return nullptr;
return *std::min_element(
targets.begin(),
targets.end(),
[this](Mobj* a, Mobj* b) { return distance3d(this, a) < distance3d(this, b); }
);
}
bool touch(Mobj* toucher)
{
if (touch_cooldown(toucher, 0))
return false;
if (K_TryPickMeUp(this, toucher, false))
return false;
// TODO: spawn a puff of smoke?
remove();
return false;
}
bool touch_cooldown
( Mobj* toucher,
UINT8 k)
{
tic_t cooldown = leveltime - last_touch0();
if (toucher == target() && cooldown < 10)
{
last_touch0(leveltime);
return true;
}
return false;
}
};
struct Cloud : Mobj
{
static constexpr INT32 kMaxFuse = 5*TICRATE;
void hnext() = delete;
Mobj* follow() const { return Mobj::hnext<Mobj>(); }
void follow(Mobj* n) { Mobj::hnext(n); }
void tracer() = delete;
Pole* pole() const { return Mobj::tracer<Pole>(); }
void pole(Pole* n) { Mobj::tracer(n); }
Fixed fuse_frac() const { return FRACUNIT - fuse * FRACUNIT / kMaxFuse; }
Fixed drag_var() const { return Easing_Linear(fuse_frac(), FRACUNIT/3, FRACUNIT); }
bool tick()
{
if (Mobj::valid(follow()))
return tick_follow();
return tick_patrol();
}
bool tick_follow()
{
if (!Mobj::valid(follow()))
{
remove();
return false;
}
move_origin(follow()->pos());
momx = 0;
momy = 0;
momz = 0;
bob_in_place(this, 8, 64);
voice_loop(sfx_s3kcfl);
if (fuse < 3*TICRATE && leveltime % (1 + fuse / TICRATE) == 0)
{
renderflags ^= RF_DONTDRAW;
}
if (fuse < kMaxFuse && (kMaxFuse - fuse) % 20 == 0 && Mobj::valid(target()) && target()->player && follow()->player)
{
K_SpawnAmps(target()->player, K_PvPAmpReward(5, target()->player, follow()->player), this);
}
return true;
}
bool tick_patrol()
{
if (Mobj::valid(pole()))
{
move_origin(pole()->pos());
instathrust(angle, 64 * mapobjectscale);
}
else
{
if (!fuse)
{
fuse = 3*TICRATE;
instathrust(angle, 2 * mapobjectscale);
}
if (leveltime & 1)
{
renderflags ^= RF_DONTDRAW;
}
}
return true;
}
bool touch(Mobj* toucher)
{
if (toucher == target())
return false;
if (toucher->player)
{
if (this == toucher->player->toxomisterCloud) // already attached
return true;
if (!P_MobjWasRemoved(toucher->player->toxomisterCloud))
{
toucher->player->pflags |= PF_CASTSHADOW;
return true;
}
P_SetTarget(&toucher->player->toxomisterCloud, this);
}
toucher->hitlag(8);
scale_to(destscale);
follow(toucher);
fuse = kMaxFuse;
voice(sfx_s3ka0);
return true;
}
};
void Pole::spawn_clouds_in_orbit()
{
constexpr INT32 kNumClouds = 6;
std::array<UINT32, kNumClouds> weights;
std::array<INT32, kNumClouds> order;
angle_t a = 0;
angle_t a_incr = ANGLE_MAX / kNumClouds;
for (INT32 i = 0; i < kNumClouds; ++i)
{
weights[i] = P_Random(PR_TRACKHAZARD);
order[i] = i;
}
std::stable_sort(order.begin(), order.end(), [&](INT32 a, INT32 b) { return weights[a] < weights[b]; });
for (INT32 i : order)
{
Cloud* cloud = spawn_from<Cloud>({}, MT_TOXOMISTER_CLOUD);
cloud->pole(this);
cloud->angle = a;
cloud->target(target());
cloud->spriteyoffset(24*FRACUNIT);
cloud->hitlag(2 + i * 4);
cloud->scale_between(1, cloud->scale(), cloud->scale() / 5);
a += a_incr;
}
}
}; // namespace
void Obj_InitToxomisterPole(mobj_t *pole)
{
static_cast<Pole*>(pole)->init();
}
boolean Obj_TickToxomisterPole(mobj_t *pole)
{
return static_cast<Pole*>(pole)->tick();
}
boolean Obj_TickToxomisterEye(mobj_t *eye)
{
return static_cast<Eye*>(eye)->tick();
}
boolean Obj_TickToxomisterCloud(mobj_t *cloud)
{
return static_cast<Cloud*>(cloud)->tick();
}
boolean Obj_ToxomisterPoleCollide(mobj_t *pole, mobj_t *toucher)
{
return static_cast<Pole*>(pole)->touch(static_cast<Mobj*>(toucher));
}
boolean Obj_ToxomisterCloudCollide(mobj_t *cloud, mobj_t *toucher)
{
return static_cast<Cloud*>(cloud)->touch(static_cast<Mobj*>(toucher));
}
fixed_t Obj_GetToxomisterCloudDrag(mobj_t *cloud)
{
return static_cast<Cloud*>(cloud)->drag_var();
}

View file

@ -1129,6 +1129,14 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
Obj_CollideStoneShoe(toucher, special); Obj_CollideStoneShoe(toucher, special);
return; return;
case MT_TOXOMISTER_POLE:
Obj_ToxomisterPoleCollide(special, toucher);
return;
case MT_TOXOMISTER_CLOUD:
Obj_ToxomisterCloudCollide(special, toucher);
return;
default: // SOC or script pickup default: // SOC or script pickup
P_SetTarget(&special->target, toucher); P_SetTarget(&special->target, toucher);
break; break;

View file

@ -1025,6 +1025,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
|| g_tm.thing->type == MT_MONITOR || g_tm.thing->type == MT_MONITOR
|| g_tm.thing->type == MT_BATTLECAPSULE || g_tm.thing->type == MT_BATTLECAPSULE
|| g_tm.thing->type == MT_KART_LEFTOVER || g_tm.thing->type == MT_KART_LEFTOVER
|| g_tm.thing->type == MT_TOXOMISTER_POLE
|| (g_tm.thing->type == MT_PLAYER))) || (g_tm.thing->type == MT_PLAYER)))
{ {
// see if it went over / under // see if it went over / under
@ -1043,6 +1044,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
|| thing->type == MT_MONITOR || thing->type == MT_MONITOR
|| thing->type == MT_BATTLECAPSULE || thing->type == MT_BATTLECAPSULE
|| thing->type == MT_KART_LEFTOVER || thing->type == MT_KART_LEFTOVER
|| thing->type == MT_TOXOMISTER_POLE
|| (thing->type == MT_PLAYER))) || (thing->type == MT_PLAYER)))
{ {
// see if it went over / under // see if it went over / under

View file

@ -1246,6 +1246,7 @@ fixed_t P_GetMobjGravity(mobj_t *mo)
gravityadd /= 2; gravityadd /= 2;
break; break;
case MT_GACHABOM: case MT_GACHABOM:
case MT_TOXOMISTER_POLE:
gravityadd = (5*gravityadd)/2; gravityadd = (5*gravityadd)/2;
break; break;
case MT_BANANA: case MT_BANANA:
@ -2337,6 +2338,7 @@ boolean P_ZMovement(mobj_t *mo)
case MT_BIGTUMBLEWEED: case MT_BIGTUMBLEWEED:
case MT_LITTLETUMBLEWEED: case MT_LITTLETUMBLEWEED:
case MT_EMERALD: case MT_EMERALD:
case MT_TOXOMISTER_POLE:
if (!(mo->flags & MF_NOCLIPHEIGHT) && P_CheckDeathPitCollide(mo)) if (!(mo->flags & MF_NOCLIPHEIGHT) && P_CheckDeathPitCollide(mo))
{ {
P_RemoveMobj(mo); P_RemoveMobj(mo);
@ -5320,6 +5322,7 @@ boolean P_IsKartItem(INT32 type)
case MT_HYUDORO: case MT_HYUDORO:
case MT_SINK: case MT_SINK:
case MT_GACHABOM: case MT_GACHABOM:
case MT_TOXOMISTER_POLE:
return true; return true;
default: default:
@ -5346,6 +5349,7 @@ boolean P_IsKartFieldItem(INT32 type)
case MT_DROPTARGET: case MT_DROPTARGET:
case MT_DUELBOMB: case MT_DUELBOMB:
case MT_GACHABOM: case MT_GACHABOM:
case MT_TOXOMISTER_POLE:
return true; return true;
default: default:
@ -5379,6 +5383,7 @@ boolean P_IsRelinkItem(INT32 type)
case MT_HYUDORO_CENTER: case MT_HYUDORO_CENTER:
case MT_SINK: case MT_SINK:
case MT_GACHABOM: case MT_GACHABOM:
case MT_TOXOMISTER_POLE:
return true; return true;
default: default:
@ -10295,6 +10300,15 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
case MT_STONESHOE: case MT_STONESHOE:
return Obj_TickStoneShoe(mobj); return Obj_TickStoneShoe(mobj);
case MT_TOXOMISTER_POLE:
return Obj_TickToxomisterPole(mobj);
case MT_TOXOMISTER_EYE:
return Obj_TickToxomisterEye(mobj);
case MT_TOXOMISTER_CLOUD:
return Obj_TickToxomisterCloud(mobj);
default: default:
// check mobj against possible water content, before movement code // check mobj against possible water content, before movement code
P_MobjCheckWater(mobj); P_MobjCheckWater(mobj);
@ -11159,6 +11173,9 @@ static void P_DefaultMobjShadowScale(mobj_t *thing)
case MT_STONESHOE_CHAIN: case MT_STONESHOE_CHAIN:
thing->shadowscale = FRACUNIT/5; thing->shadowscale = FRACUNIT/5;
break; break;
case MT_TOXOMISTER_POLE:
thing->shadowscale = FRACUNIT;
break;
default: default:
if (thing->flags & (MF_ENEMY|MF_BOSS)) if (thing->flags & (MF_ENEMY|MF_BOSS))
thing->shadowscale = FRACUNIT; thing->shadowscale = FRACUNIT;

View file

@ -93,6 +93,7 @@ typedef enum
BALLHOGRETICULE = 0x8000, BALLHOGRETICULE = 0x8000,
STONESHOE = 0x10000, STONESHOE = 0x10000,
FLYBOT = 0x20000, FLYBOT = 0x20000,
TOXOMISTERCLOUD = 0x40000,
} player_saveflags; } player_saveflags;
static inline void P_ArchivePlayer(savebuffer_t *save) static inline void P_ArchivePlayer(savebuffer_t *save)
@ -368,6 +369,9 @@ static void P_NetArchivePlayers(savebuffer_t *save)
if (players[i].stoneShoe) if (players[i].stoneShoe)
flags |= STONESHOE; flags |= STONESHOE;
if (players[i].toxomisterCloud)
flags |= TOXOMISTERCLOUD;
if (players[i].flybot) if (players[i].flybot)
flags |= FLYBOT; flags |= FLYBOT;
@ -421,6 +425,9 @@ static void P_NetArchivePlayers(savebuffer_t *save)
if (flags & STONESHOE) if (flags & STONESHOE)
WRITEUINT32(save->p, players[i].stoneShoe->mobjnum); WRITEUINT32(save->p, players[i].stoneShoe->mobjnum);
if (flags & TOXOMISTERCLOUD)
WRITEUINT32(save->p, players[i].toxomisterCloud->mobjnum);
if (flags & FLYBOT) if (flags & FLYBOT)
WRITEUINT32(save->p, players[i].flybot->mobjnum); WRITEUINT32(save->p, players[i].flybot->mobjnum);
@ -1082,6 +1089,9 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
if (flags & STONESHOE) if (flags & STONESHOE)
players[i].stoneShoe = (mobj_t *)(size_t)READUINT32(save->p); players[i].stoneShoe = (mobj_t *)(size_t)READUINT32(save->p);
if (flags & TOXOMISTERCLOUD)
players[i].toxomisterCloud = (mobj_t *)(size_t)READUINT32(save->p);
if (flags & FLYBOT) if (flags & FLYBOT)
players[i].flybot = (mobj_t *)(size_t)READUINT32(save->p); players[i].flybot = (mobj_t *)(size_t)READUINT32(save->p);
@ -6247,6 +6257,11 @@ static void P_RelinkPointers(void)
if (!RelinkMobj(&players[i].stoneShoe)) if (!RelinkMobj(&players[i].stoneShoe))
CONS_Debug(DBG_GAMELOGIC, "stoneShoe not found on player %d\n", i); CONS_Debug(DBG_GAMELOGIC, "stoneShoe not found on player %d\n", i);
} }
if (players[i].toxomisterCloud)
{
if (!RelinkMobj(&players[i].toxomisterCloud))
CONS_Debug(DBG_GAMELOGIC, "toxomisterCloud not found on player %d\n", i);
}
if (players[i].flybot) if (players[i].flybot)
{ {
if (!RelinkMobj(&players[i].flybot)) if (!RelinkMobj(&players[i].flybot))

View file

@ -4259,6 +4259,7 @@ void P_PlayerThink(player_t *player)
PlayerPointerErase(player->ballhogreticule); PlayerPointerErase(player->ballhogreticule);
PlayerPointerErase(player->flickyAttacker); PlayerPointerErase(player->flickyAttacker);
PlayerPointerErase(player->stoneShoe); PlayerPointerErase(player->stoneShoe);
PlayerPointerErase(player->toxomisterCloud);
PlayerPointerErase(player->powerup.flickyController); PlayerPointerErase(player->powerup.flickyController);
PlayerPointerErase(player->powerup.barrier); PlayerPointerErase(player->powerup.barrier);