mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-10-30 08:01:28 +00:00
Merge branch 'jartha/toxomister' into 'master'
Toxomister See merge request kart-krew-dev/ring-racers-internal!2678
This commit is contained in:
commit
1dc2dd4575
16 changed files with 650 additions and 5 deletions
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
|
||||
|
|
|
|||
|
|
@ -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[] = {
|
||||
|
|
|
|||
85
src/info.c
85
src/info.c
|
|
@ -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
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
11
src/info.h
11
src/info.h
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
26
src/k_kart.c
26
src/k_kart.c
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
439
src/objects/toxomister.cpp
Normal 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();
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
26
src/p_mobj.c
26
src/p_mobj.c
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue