Merge branch 'jartha/toxomister' into 'master'

Toxomister

See merge request kart-krew-dev/ring-racers-internal!2678
This commit is contained in:
Oni VelocitOni 2025-07-09 02:18:20 +00:00
commit 1dc2dd4575
16 changed files with 650 additions and 5 deletions

View file

@ -687,6 +687,7 @@ consvar_t cv_items[] = {
UnsavedNetVar("gardentop", "On").on_off(),
UnsavedNetVar("gachabom", "On").on_off(),
UnsavedNetVar("stoneshoe", "On").on_off(),
UnsavedNetVar("toxomister", "On").on_off(),
UnsavedNetVar("dualsneaker", "On").on_off(),
UnsavedNetVar("triplesneaker", "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 (GARDENTOP, 22),\
FOREACH (GACHABOM, 23),\
FOREACH (STONESHOE, 24)
FOREACH (STONESHOE, 24),\
FOREACH (TOXOMISTER, 25)
typedef enum
{
@ -1066,6 +1067,7 @@ struct player_t
mobj_t *hand;
mobj_t *flickyAttacker;
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!

View file

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

View file

@ -801,6 +801,8 @@ char sprnames[NUMSPRITES + 1][5] =
"STUN",
"STON",
"TOXA",
"TOXB",
// Pulley
"HCCH",
@ -3704,6 +3706,11 @@ state_t states[NUMSTATES] =
{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_TOXA, 0, -1, {NULL}, 0, 0, S_TOXAA}, // S_TOXAA
{SPR_TOXA, 0, 175, {NULL}, 0, 0, S_NULL}, // S_TOXAA_DEAD
{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] =
@ -22712,6 +22719,84 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
MF_SPECIAL|MF_SCENERY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_PICKUPFROMBELOW|MF_DONTENCOREMAP, // flags
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_TOXAA_DEAD, // 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_STON,
SPR_TOXA,
SPR_TOXB,
// Pulley
SPR_HCCH,
@ -4188,6 +4190,11 @@ typedef enum state
S_STON,
S_TOXAA,
S_TOXAA_DEAD,
S_TOXAB,
S_TOXBA,
S_FIRSTFREESLOT,
S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1,
NUMSTATES
@ -5119,6 +5126,10 @@ typedef enum mobj_type
MT_STONESHOE,
MT_STONESHOE_CHAIN,
MT_TOXOMISTER_POLE,
MT_TOXOMISTER_EYE,
MT_TOXOMISTER_CLOUD,
MT_FIRSTFREESLOT,
MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1,
NUMMOBJTYPES

View file

@ -172,6 +172,7 @@ static patch_t *kp_droptarget[3];
static patch_t *kp_gardentop[3];
static patch_t *kp_gachabom[3];
static patch_t *kp_stoneshoe[3];
static patch_t *kp_toxomister[3];
static patch_t *kp_bar[2];
static patch_t *kp_doublebar[2];
static patch_t *kp_triplebar[2];
@ -652,6 +653,7 @@ void K_LoadKartHUDGraphics(void)
HU_UpdatePatch(&kp_gardentop[0], "K_ITGTOP");
HU_UpdatePatch(&kp_gachabom[0], "K_ITGBOM");
HU_UpdatePatch(&kp_stoneshoe[0], "K_ITSTON");
HU_UpdatePatch(&kp_toxomister[0], "K_ITTOX");
HU_UpdatePatch(&kp_bar[0], "K_RBBAR");
HU_UpdatePatch(&kp_doublebar[0], "K_RBBAR2");
HU_UpdatePatch(&kp_triplebar[0], "K_RBBAR3");
@ -713,6 +715,7 @@ void K_LoadKartHUDGraphics(void)
HU_UpdatePatch(&kp_gardentop[1], "K_ISGTOP");
HU_UpdatePatch(&kp_gachabom[1], "K_ISGBOM");
HU_UpdatePatch(&kp_stoneshoe[1], "K_ISSTON");
HU_UpdatePatch(&kp_toxomister[1], "K_ISTOX");
HU_UpdatePatch(&kp_bar[1], "K_SBBAR");
HU_UpdatePatch(&kp_doublebar[1], "K_SBBAR2");
HU_UpdatePatch(&kp_triplebar[1], "K_SBBAR3");
@ -772,6 +775,7 @@ void K_LoadKartHUDGraphics(void)
HU_UpdatePatch(&kp_gardentop[2], "ISPYGTOP");
HU_UpdatePatch(&kp_gachabom[2], "ISPYGBOM");
HU_UpdatePatch(&kp_stoneshoe[2], "ISPYSTON");
HU_UpdatePatch(&kp_toxomister[2], "ISPYTOX");
// CHECK indicators
sprintf(buffer, "K_CHECKx");
@ -1196,6 +1200,7 @@ static patch_t *K_GetCachedItemPatch(INT32 item, UINT8 offset)
kp_gardentop,
kp_gachabom,
kp_stoneshoe,
kp_toxomister,
};
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;
}
if (!P_MobjWasRemoved(player->toxomisterCloud))
{
p_speed = FixedMul(p_speed, Obj_GetToxomisterCloudDrag(player->toxomisterCloud));
}
if (K_PlayerUsesBotMovement(player) == true && player->botvars.rubberband > 0)
{
// 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);
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;
newx = player->mo->x + player->mo->momx;
@ -15069,6 +15074,21 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
player->botvars.itemconfirm = 0;
}
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:
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO
&& !player->sadtimer)
@ -16363,6 +16383,7 @@ boolean K_IsPickMeUpItem(mobjtype_t type)
case MT_SSMINE:
case MT_SSMINE_SHIELD:
case MT_FLOATINGITEM: // Stone Shoe
case MT_TOXOMISTER_POLE:
return true;
default:
return false;
@ -16425,6 +16446,9 @@ static boolean K_PickUp(player_t *player, mobj_t *picked)
else
type = KITEM_SAD;
break;
case MT_TOXOMISTER_POLE:
type = KITEM_TOXOMISTER;
break;
default:
type = KITEM_SAD;
break;

View file

@ -475,6 +475,16 @@ boolean Obj_TickStoneShoeChain(mobj_t *chain);
player_t *Obj_StoneShoeOwnerPlayer(mobj_t *shoe);
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
} // extern "C"
#endif

View file

@ -96,14 +96,15 @@ static UINT32 K_DynamicItemOddsRace[NUMKARTRESULTS-1][2] =
{1, 1}, // lightningshield
{25, 4}, // bubbleshield
{66, 9}, // flameshield
{1, 3}, // hyudoro
{1, 2}, // hyudoro
{0, 0}, // pogospring
{30, 8}, // superring (SPECIAL! distance value specifies when this can NO LONGER appear)
{0, 0}, // kitchensink
{1, 3}, // droptarget
{1, 2}, // droptarget
{43, 5}, // gardentop
{0, 0}, // gachabom
{1, 3}, // stoneshoe
{1, 2}, // stoneshoe
{1, 2}, // toxomister
{45, 6}, // dualsneaker
{55, 8}, // triplesneaker
{25, 2}, // triplebanana
@ -140,6 +141,7 @@ static UINT32 K_DynamicItemOddsBattle[NUMKARTRESULTS-1][2] =
{0, 0}, // gardentop
{10, 5}, // gachabom
{0, 0}, // stoneshoe
{0, 0}, // toxomister
{0, 0}, // dualsneaker
{20, 1}, // triplesneaker
{0, 0}, // triplebanana
@ -176,6 +178,7 @@ static UINT32 K_DynamicItemOddsSpecial[NUMKARTRESULTS-1][2] =
{0, 0}, // gardentop
{0, 0}, // gachabom
{0, 0}, // stoneshoe
{0, 0}, // toxomister
{35, 2}, // dualsneaker
{0, 0}, // triplesneaker
{0, 0}, // triplebanana
@ -212,6 +215,7 @@ static UINT8 K_KartLegacyBattleOdds[NUMKARTRESULTS-1][2] =
{ 0, 0 }, // Garden Top
{ 5, 0 }, // Gachabom
{ 0, 1 }, // Stone Shoe
{ 0, 1 }, // Toxomister
{ 0, 0 }, // Sneaker x2
{ 0, 1 }, // Sneaker x3
{ 0, 0 }, // Banana x3
@ -373,6 +377,7 @@ botItemPriority_e K_GetBotItemPriority(kartitems_t result)
case KITEM_EGGMAN:
case KITEM_GACHABOM:
case KITEM_STONESHOE:
case KITEM_TOXOMISTER:
case KITEM_KITCHENSINK:
{
// Used when in 1st place and relatively far from players.
@ -1052,6 +1057,7 @@ static boolean K_IsItemFirstOnly(kartitems_t item)
case KITEM_HYUDORO:
case KITEM_DROPTARGET:
case KITEM_STONESHOE:
case KITEM_TOXOMISTER:
return true;
default:
return false;

View file

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

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

@ -0,0 +1,439 @@
// 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);
}
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()) && owner()->health > 0; }
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;
// Adapted from P_XYMovement, MT_JAWZ
voice(info->deathsound);
P_KillMobj(this, NULL, NULL, DMG_NORMAL);
P_SetObjectMomZ(this, 24*FRACUNIT, false);
instathrust(R_PointToAngle2(toucher->x, toucher->y, x, y), 32 * mapobjectscale);
flags &= ~MF_NOGRAVITY;
hitlag(toucher, toucher, 8, true);
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 (leveltime % (TICRATE/3) == 0 && follow()->player->rings > -20) // toxomister ring drain
{
follow()->player->rings--;
S_StartSound(follow()->player->mo, sfx_antiri);
}
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(3, target()->player, follow()->player), this);
}
follow()->player->stunned = fuse; // stunned as long as cloud is here
return true;
}
bool tick_patrol()
{
if (Mobj::valid(pole()) && pole()->health > 0)
{
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;
renderflags &= ~RF_DONTDRAW;
voice(sfx_s3k8a);
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);
return;
case MT_TOXOMISTER_POLE:
Obj_ToxomisterPoleCollide(special, toucher);
return;
case MT_TOXOMISTER_CLOUD:
Obj_ToxomisterCloudCollide(special, toucher);
return;
default: // SOC or script pickup
P_SetTarget(&special->target, toucher);
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_BATTLECAPSULE
|| g_tm.thing->type == MT_KART_LEFTOVER
|| g_tm.thing->type == MT_TOXOMISTER_POLE
|| (g_tm.thing->type == MT_PLAYER)))
{
// 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_BATTLECAPSULE
|| thing->type == MT_KART_LEFTOVER
|| thing->type == MT_TOXOMISTER_POLE
|| (thing->type == MT_PLAYER)))
{
// see if it went over / under

View file

@ -1248,6 +1248,10 @@ fixed_t P_GetMobjGravity(mobj_t *mo)
case MT_GACHABOM:
gravityadd = (5*gravityadd)/2;
break;
case MT_TOXOMISTER_POLE:
if (mo->health > 0)
gravityadd = (5*gravityadd)/2;
break;
case MT_BANANA:
case MT_BALLHOG:
case MT_BALLHOG_RETICULE_TEST:
@ -2337,6 +2341,7 @@ boolean P_ZMovement(mobj_t *mo)
case MT_BIGTUMBLEWEED:
case MT_LITTLETUMBLEWEED:
case MT_EMERALD:
case MT_TOXOMISTER_POLE:
if (!(mo->flags & MF_NOCLIPHEIGHT) && P_CheckDeathPitCollide(mo))
{
P_RemoveMobj(mo);
@ -5320,6 +5325,7 @@ boolean P_IsKartItem(INT32 type)
case MT_HYUDORO:
case MT_SINK:
case MT_GACHABOM:
case MT_TOXOMISTER_POLE:
return true;
default:
@ -5346,6 +5352,7 @@ boolean P_IsKartFieldItem(INT32 type)
case MT_DROPTARGET:
case MT_DUELBOMB:
case MT_GACHABOM:
case MT_TOXOMISTER_POLE:
return true;
default:
@ -5379,6 +5386,7 @@ boolean P_IsRelinkItem(INT32 type)
case MT_HYUDORO_CENTER:
case MT_SINK:
case MT_GACHABOM:
case MT_TOXOMISTER_POLE:
return true;
default:
@ -6863,6 +6871,12 @@ static boolean P_MobjDeadThink(mobj_t *mobj)
P_SetMobjState(mobj, mobj->info->xdeathstate);
/* FALLTHRU */
case MT_JAWZ_SHIELD:
mobj->renderflags ^= RF_DONTDRAW;
break;
case MT_TOXOMISTER_POLE:
if (mobj->momz == 0 && P_IsObjectOnGround(mobj))
P_SetMobjState(mobj, mobj->info->xdeathstate);
mobj->renderflags ^= RF_DONTDRAW;
break;
case MT_SSMINE:
@ -10295,6 +10309,15 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
case MT_STONESHOE:
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:
// check mobj against possible water content, before movement code
P_MobjCheckWater(mobj);
@ -11159,6 +11182,9 @@ static void P_DefaultMobjShadowScale(mobj_t *thing)
case MT_STONESHOE_CHAIN:
thing->shadowscale = FRACUNIT/5;
break;
case MT_TOXOMISTER_POLE:
thing->shadowscale = FRACUNIT;
break;
default:
if (thing->flags & (MF_ENEMY|MF_BOSS))
thing->shadowscale = FRACUNIT;

View file

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

View file

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