* Guest star: fixed-point item bar * Bubble scaling and shards have been refined * No more passive protection against explosives and thunder * The bubble boost timer now protects you from mine explosion particles, not just flashtics, making it easier to pass through proxmines
871 lines
22 KiB
C
871 lines
22 KiB
C
/// \file k_collide.c
|
|
/// \brief SRB2Kart item collision hooks
|
|
|
|
#include "k_collide.h"
|
|
#include "k_stats.h"
|
|
#include "doomstat.h"
|
|
#include "doomtype.h"
|
|
#include "p_mobj.h"
|
|
#include "k_kart.h"
|
|
#include "k_odds.h"
|
|
#include "p_local.h"
|
|
#include "s_sound.h"
|
|
#include "r_main.h" // R_PointToAngle2, R_PointToDist2
|
|
#include "hu_stuff.h" // Sink snipe print
|
|
#include "doomdef.h" // Sink snipe print
|
|
#include "g_game.h" // Sink snipe print
|
|
|
|
angle_t K_GetCollideAngle(mobj_t *t1, mobj_t *t2)
|
|
{
|
|
fixed_t momux, momuy;
|
|
angle_t test;
|
|
|
|
if (!(t1->flags & MF_PAPERCOLLISION))
|
|
{
|
|
return R_PointToAngle2(t1->x, t1->y, t2->x, t2->y)+ANGLE_90;
|
|
}
|
|
|
|
test = R_PointToAngle2(0, 0, t2->momx, t2->momy) + ANGLE_90 - t1->angle;
|
|
if (test > ANGLE_180)
|
|
test = t1->angle + ANGLE_180;
|
|
else
|
|
test = t1->angle;
|
|
|
|
// intentional way around - sine...
|
|
momuy = P_AproxDistance(t2->momx, t2->momy);
|
|
momux = t2->momx - P_ReturnThrustY(t2, test, 2*momuy);
|
|
momuy = t2->momy - P_ReturnThrustX(t2, test, 2*momuy);
|
|
|
|
return R_PointToAngle2(0, 0, momux, momuy);
|
|
}
|
|
|
|
boolean K_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2)
|
|
{
|
|
boolean damageitem = false;
|
|
boolean sprung = false;
|
|
|
|
if (((t1->target == t2) || (!(t2->flags & (MF_ENEMY|MF_BOSS)) && (t1->target == t2->target))) && ((t1->threshold > 0 && t2->type == MT_PLAYER) || (t2->type != MT_PLAYER && t2->threshold > 0)))
|
|
return true;
|
|
|
|
if (t1->health <= 0 || t2->health <= 0)
|
|
return true;
|
|
|
|
if ((t1->type == MT_ORBINAUT_SHIELD || t1->type == MT_JAWZ_SHIELD) && t1->lastlook
|
|
&& (t2->type == MT_ORBINAUT_SHIELD || t2->type == MT_JAWZ_SHIELD) && t2->lastlook
|
|
&& (t1->target == t2->target)) // Don't hit each other if you have the same target
|
|
return true;
|
|
|
|
if (t2->player)
|
|
{
|
|
if (t2->player->flashing && !(t1->type == MT_ORBINAUT || t1->type == MT_JAWZ))
|
|
return true;
|
|
|
|
if (t2->player->hyudorotimer)
|
|
return true; // no interaction
|
|
|
|
if (t2->player->flamestore && (K_GetShieldFromPlayer(t2->player) == KSHIELD_FLAME))
|
|
{
|
|
// Melt item
|
|
S_StartSound(t2, sfx_s3k43);
|
|
}
|
|
else
|
|
{
|
|
// Player Damage
|
|
P_DamageMobj(t2, t1, t1->target, 1, DMG_WIPEOUT);
|
|
K_KartBouncing(t2, t1, false, false);
|
|
S_StartSound(t2, sfx_s3k7b);
|
|
}
|
|
|
|
damageitem = true;
|
|
}
|
|
else if (t2->type == MT_ORBINAUT || t2->type == MT_JAWZ || t2->type == MT_JAWZ_DUD
|
|
|| t2->type == MT_ORBINAUT_SHIELD || t2->type == MT_JAWZ_SHIELD
|
|
|| t2->type == MT_BANANA || t2->type == MT_BANANA_SHIELD
|
|
|| t2->type == MT_BALLHOG)
|
|
{
|
|
// Other Item Damage
|
|
angle_t bounceangle = K_GetCollideAngle(t1, t2);
|
|
|
|
S_StartSound(t2, t2->info->deathsound);
|
|
P_KillMobj(t2, t1, t1, DMG_NORMAL);
|
|
|
|
P_SetObjectMomZ(t2, 8*FRACUNIT, false);
|
|
P_InstaThrust(t2, bounceangle, 16*FRACUNIT);
|
|
|
|
P_SpawnMobj(t2->x/2 + t1->x/2, t2->y/2 + t1->y/2, t2->z/2 + t1->z/2, MT_ITEMCLASH);
|
|
|
|
damageitem = true;
|
|
}
|
|
else if (t2->type == MT_SSMINE_SHIELD || t2->type == MT_SSMINE || t2->type == MT_LANDMINE)
|
|
{
|
|
damageitem = true;
|
|
// Bomb death
|
|
P_KillMobj(t2, t1, t1, DMG_NORMAL);
|
|
}
|
|
else if (t2->flags & MF_SPRING && (t1->type != MT_ORBINAUT_SHIELD && t1->type != MT_JAWZ_SHIELD))
|
|
{
|
|
// Let thrown items hit springs!
|
|
sprung = P_DoSpring(t2, t1);
|
|
}
|
|
else if (t2->flags & MF_SHOOTABLE)
|
|
{
|
|
// Shootable damage
|
|
P_DamageMobj(t2, t1, t1->target, 1, DMG_NORMAL);
|
|
damageitem = true;
|
|
}
|
|
|
|
if (damageitem)
|
|
{
|
|
// This Item Damage
|
|
angle_t bounceangle = K_GetCollideAngle(t2, t1);
|
|
S_StartSound(t1, t1->info->deathsound);
|
|
P_KillMobj(t1, t2, t2, DMG_NORMAL);
|
|
|
|
P_SetObjectMomZ(t1, 8*FRACUNIT, false);
|
|
P_InstaThrust(t1, bounceangle, 16*FRACUNIT);
|
|
}
|
|
|
|
if (sprung)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
boolean K_BananaBallhogCollide(mobj_t *t1, mobj_t *t2)
|
|
{
|
|
boolean damageitem = false;
|
|
|
|
if (((t1->target == t2) || (!(t2->flags & (MF_ENEMY|MF_BOSS)) && (t1->target == t2->target))) && (t1->threshold > 0 || (t2->type != MT_PLAYER && t2->threshold > 0)))
|
|
return true;
|
|
|
|
if (t1->health <= 0 || t2->health <= 0)
|
|
return true;
|
|
|
|
if (((t1->type == MT_BANANA_SHIELD) && (t2->type == MT_BANANA_SHIELD))
|
|
&& (t1->target == t2->target)) // Don't hit each other if you have the same target
|
|
return true;
|
|
|
|
if (t1->type == MT_BALLHOG && t2->type == MT_BALLHOG)
|
|
return true; // Ballhogs don't collide with eachother
|
|
|
|
if (t2->player)
|
|
{
|
|
|
|
// Banana snipe!
|
|
if (t1->type == MT_BANANA && t1->health > 1)
|
|
S_StartSound(t2, sfx_bsnipe);
|
|
|
|
damageitem = true;
|
|
|
|
if (t2->player->flamestore && (K_GetShieldFromPlayer(t2->player) == KSHIELD_FLAME))
|
|
{
|
|
// Melt item
|
|
S_StartSound(t2, sfx_s3k43);
|
|
}
|
|
else
|
|
{
|
|
P_DamageMobj(t2, t1, t1->target, 1, DMG_NORMAL);
|
|
}
|
|
}
|
|
else if (t2->type == MT_BANANA || t2->type == MT_BANANA_SHIELD
|
|
|| t2->type == MT_ORBINAUT || t2->type == MT_ORBINAUT_SHIELD
|
|
|| t2->type == MT_JAWZ || t2->type == MT_JAWZ_DUD || t2->type == MT_JAWZ_SHIELD
|
|
|| t2->type == MT_BALLHOG)
|
|
{
|
|
// Other Item Damage
|
|
angle_t bounceangle = K_GetCollideAngle(t1, t2);
|
|
|
|
S_StartSound(t2, t2->info->deathsound);
|
|
P_KillMobj(t2, t1, t1, DMG_NORMAL);
|
|
|
|
P_SetObjectMomZ(t2, 8*FRACUNIT, false);
|
|
P_InstaThrust(t2, bounceangle, 16*FRACUNIT);
|
|
|
|
P_SpawnMobj(t2->x/2 + t1->x/2, t2->y/2 + t1->y/2, t2->z/2 + t1->z/2, MT_ITEMCLASH);
|
|
|
|
damageitem = true;
|
|
}
|
|
else if (t2->type == MT_SSMINE_SHIELD || t2->type == MT_SSMINE || t2->type == MT_LANDMINE)
|
|
{
|
|
damageitem = true;
|
|
// Bomb death
|
|
P_KillMobj(t2, t1, t1, DMG_NORMAL);
|
|
}
|
|
else if (t2->flags & MF_SHOOTABLE)
|
|
{
|
|
// Shootable damage
|
|
P_DamageMobj(t2, t1, t1->target, 1, DMG_NORMAL);
|
|
damageitem = true;
|
|
}
|
|
|
|
if (damageitem)
|
|
{
|
|
// This Item Damage
|
|
angle_t bounceangle = K_GetCollideAngle(t2, t1);
|
|
|
|
S_StartSound(t1, t1->info->deathsound);
|
|
P_KillMobj(t1, t2, t2, DMG_NORMAL);
|
|
|
|
P_SetObjectMomZ(t1, 8*FRACUNIT, false);
|
|
P_InstaThrust(t1, bounceangle, 16*FRACUNIT);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
boolean K_EggItemCollide(mobj_t *t1, mobj_t *t2)
|
|
{
|
|
// Push fakes out of other item boxes
|
|
if (t2->type == MT_RANDOMITEM || t2->type == MT_EGGMANITEM)
|
|
{
|
|
P_InstaThrust(t1, R_PointToAngle2(t2->x, t2->y, t1->x, t1->y), t2->radius/4);
|
|
return true;
|
|
}
|
|
|
|
if (t2->player)
|
|
{
|
|
if ((t1->target == t2 || t1->target == t2->target) && (t1->threshold > 0))
|
|
return true;
|
|
|
|
if (t1->health <= 0 || t2->health <= 0)
|
|
return true;
|
|
|
|
if (!P_CanPickupItem(t2->player, 2))
|
|
return true;
|
|
|
|
if ((gametyperules & GTR_BUMPERS) && t2->player->bumper <= 0)
|
|
{
|
|
if (t2->player->karmamode || t2->player->karmadelay)
|
|
return true;
|
|
t2->player->karmamode = 2;
|
|
}
|
|
else
|
|
{
|
|
K_DropItems(t2->player); //K_StripItems(t2->player);
|
|
//K_StripOther(t2->player);
|
|
t2->player->itemroulette = KROULETTE_ACTIVE;
|
|
t2->player->roulettetype = KROULETTETYPE_EGGMAN;
|
|
}
|
|
|
|
if (t2->player->flamestore && (K_GetShieldFromPlayer(t2->player) == KSHIELD_FLAME))
|
|
{
|
|
// Melt item
|
|
S_StartSound(t2, sfx_s3k43);
|
|
P_KillMobj(t1, t2, t2, DMG_NORMAL);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
mobj_t *poof = P_SpawnMobj(t1->x, t1->y, t1->z, MT_EXPLODE);
|
|
S_StartSound(poof, t1->info->deathsound);
|
|
|
|
#if 0
|
|
// Eggbox snipe!
|
|
if (t1->type == MT_EGGMANITEM && t1->health > 1)
|
|
S_StartSound(t2, sfx_bsnipe);
|
|
#endif
|
|
|
|
if (t1->target && t1->target->player)
|
|
{
|
|
if ((gametyperules & GTR_CIRCUIT) || t1->target->player->bumper > 0)
|
|
t2->player->eggmanblame = t1->target->player-players;
|
|
else
|
|
t2->player->eggmanblame = t2->player-players;
|
|
|
|
if (t1->target->hnext == t1)
|
|
{
|
|
P_SetTarget(&t1->target->hnext, NULL);
|
|
t1->target->player->itemflags &= ~IF_EGGMANOUT;
|
|
}
|
|
}
|
|
|
|
P_RemoveMobj(t1);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
boolean K_MineCollide(mobj_t *t1, mobj_t *t2)
|
|
{
|
|
if (((t1->target == t2) || (!(t2->flags & (MF_ENEMY|MF_BOSS)) && (t1->target == t2->target))) && (t1->threshold > 0 || (t2->type != MT_PLAYER && t2->threshold > 0)))
|
|
return true;
|
|
|
|
if (t1->health <= 0 || t2->health <= 0)
|
|
return true;
|
|
|
|
if (t2->player)
|
|
{
|
|
if (t2->player->flashing > 0)
|
|
return true;
|
|
|
|
// Bomb punting
|
|
if ((t1->state >= &states[S_SSMINE1] && t1->state <= &states[S_SSMINE4])
|
|
|| (t1->state >= &states[S_SSMINE_DEPLOY8] && t1->state <= &states[S_SSMINE_DEPLOY13]))
|
|
{
|
|
P_KillMobj(t1, t2, t2, DMG_NORMAL);
|
|
}
|
|
else
|
|
{
|
|
K_PuntMine(t1, t2);
|
|
}
|
|
}
|
|
else if (t2->type == MT_ORBINAUT || t2->type == MT_JAWZ || t2->type == MT_JAWZ_DUD
|
|
|| t2->type == MT_ORBINAUT_SHIELD || t2->type == MT_JAWZ_SHIELD)
|
|
{
|
|
// Bomb death
|
|
angle_t bounceangle = K_GetCollideAngle(t1, t2);
|
|
|
|
P_KillMobj(t1, t2, t2, DMG_NORMAL);
|
|
|
|
// Other Item Damage
|
|
S_StartSound(t2, t2->info->deathsound);
|
|
P_KillMobj(t2, t1, t1, DMG_NORMAL);
|
|
|
|
P_SetObjectMomZ(t2, 8*FRACUNIT, false);
|
|
P_InstaThrust(t2, bounceangle, 16*FRACUNIT);
|
|
}
|
|
else if (t2->flags & MF_SHOOTABLE)
|
|
{
|
|
// Bomb death
|
|
P_KillMobj(t1, t2, t2, DMG_NORMAL);
|
|
// Shootable damage
|
|
P_DamageMobj(t2, t1, t1->target, 1, DMG_NORMAL);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
boolean K_MineExplosionCollide(mobj_t *t1, mobj_t *t2)
|
|
{
|
|
if (t2->player)
|
|
{
|
|
// don't sideswipe anyone boosting with a bubble shield!
|
|
if (t2->player->flashing > 0 || t2->player->bubbleboost > 0)
|
|
return true;
|
|
|
|
if (t1->state == &states[S_MINEEXPLOSION1])
|
|
{
|
|
P_DamageMobj(t2, t1, t1->target, 1, DMG_EXPLODE);
|
|
}
|
|
else
|
|
{
|
|
P_DamageMobj(t2, t1, t1->target, 1, DMG_NORMAL);
|
|
}
|
|
}
|
|
else if (t2->flags & MF_SHOOTABLE)
|
|
{
|
|
// Shootable damage
|
|
P_DamageMobj(t2, t1, t1->target, 1, DMG_NORMAL);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
boolean K_LandMineCollide(mobj_t *t1, mobj_t *t2)
|
|
{
|
|
if (((t1->target == t2) || (!(t2->flags & (MF_ENEMY|MF_BOSS)) && (t1->target == t2->target))) && (t1->threshold > 0 || (t2->type != MT_PLAYER && t2->threshold > 0)))
|
|
return true;
|
|
|
|
if (t1->health <= 0 || t2->health <= 0)
|
|
return true;
|
|
|
|
if (t2->player)
|
|
{
|
|
if (t2->player->flashing)
|
|
return true;
|
|
|
|
// Banana snipe!
|
|
if (t1->health > 1)
|
|
S_StartSound(t2, sfx_bsnipe);
|
|
|
|
if (t2->player->flamestore && (K_GetShieldFromPlayer(t2->player) == KSHIELD_FLAME))
|
|
{
|
|
// Melt item
|
|
S_StartSound(t2, sfx_s3k43);
|
|
}
|
|
else
|
|
{
|
|
// Player Damage
|
|
P_DamageMobj(t2, t1, t1->target, 1, DMG_FLIPOVER);
|
|
}
|
|
|
|
P_SpawnMobj(t1->x, t1->y, t1->z, MT_MINEEXPLOSIONSOUND);
|
|
P_KillMobj(t1, t2, t2, DMG_NORMAL);
|
|
}
|
|
else if (t2->type == MT_BANANA || t2->type == MT_BANANA_SHIELD
|
|
|| t2->type == MT_ORBINAUT || t2->type == MT_ORBINAUT_SHIELD
|
|
|| t2->type == MT_JAWZ || t2->type == MT_JAWZ_DUD || t2->type == MT_JAWZ_SHIELD
|
|
|| t2->type == MT_BALLHOG)
|
|
{
|
|
// Other Item Damage
|
|
angle_t bounceangle = K_GetCollideAngle(t1, t2);
|
|
|
|
if (t2->eflags & MFE_VERTICALFLIP)
|
|
t2->z -= t2->height;
|
|
else
|
|
t2->z += t2->height;
|
|
|
|
S_StartSound(t2, t2->info->deathsound);
|
|
P_KillMobj(t2, t1, t1, DMG_NORMAL);
|
|
|
|
P_SetObjectMomZ(t2, 8*FRACUNIT, false);
|
|
P_InstaThrust(t2, bounceangle, 16*FRACUNIT);
|
|
|
|
P_SpawnMobj(t2->x/2 + t1->x/2, t2->y/2 + t1->y/2, t2->z/2 + t1->z/2, MT_ITEMCLASH);
|
|
|
|
P_SpawnMobj(t1->x, t1->y, t1->z, MT_MINEEXPLOSIONSOUND);
|
|
P_KillMobj(t1, t2, t2, DMG_NORMAL);
|
|
}
|
|
else if (t2->type == MT_SSMINE_SHIELD || t2->type == MT_SSMINE || t2->type == MT_LANDMINE)
|
|
{
|
|
P_SpawnMobj(t1->x, t1->y, t1->z, MT_MINEEXPLOSIONSOUND);
|
|
P_KillMobj(t1, t2, t2, DMG_NORMAL);
|
|
// Bomb death
|
|
P_KillMobj(t2, t1, t1, DMG_NORMAL);
|
|
}
|
|
else if (t2->flags & MF_SHOOTABLE)
|
|
{
|
|
// Shootable damage
|
|
P_DamageMobj(t2, t1, t1->target, 1, DMG_NORMAL);
|
|
P_SpawnMobj(t1->x, t1->y, t1->z, MT_MINEEXPLOSIONSOUND);
|
|
P_KillMobj(t1, t2, t2, DMG_NORMAL);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static mobj_t *lightningSource;
|
|
static fixed_t lightningDist;
|
|
|
|
static inline BlockItReturn_t PIT_ThunderShieldAttack(mobj_t *thing)
|
|
{
|
|
if (lightningSource == NULL || P_MobjWasRemoved(lightningSource))
|
|
{
|
|
// Invalid?
|
|
return BMIT_ABORT;
|
|
}
|
|
|
|
if (thing == NULL || P_MobjWasRemoved(thing))
|
|
{
|
|
// Invalid?
|
|
return BMIT_ABORT;
|
|
}
|
|
|
|
if (thing == lightningSource)
|
|
{
|
|
// Don't explode yourself!!
|
|
return BMIT_CONTINUE;
|
|
}
|
|
|
|
if (thing->health <= 0)
|
|
{
|
|
// Dead
|
|
return BMIT_CONTINUE;
|
|
}
|
|
|
|
if (thing->type != MT_SPB)
|
|
{
|
|
if (!(thing->flags & MF_SHOOTABLE) || (thing->flags & MF_SCENERY))
|
|
{
|
|
// Not shootable
|
|
return BMIT_CONTINUE;
|
|
}
|
|
}
|
|
|
|
if (thing->player && thing->player->spectator)
|
|
{
|
|
// Spectator
|
|
return BMIT_CONTINUE;
|
|
}
|
|
|
|
if (P_AproxDistance(thing->x - lightningSource->x, thing->y - lightningSource->y) > lightningDist + thing->radius)
|
|
{
|
|
// Too far away
|
|
return BMIT_CONTINUE;
|
|
}
|
|
|
|
#if 0
|
|
if (P_CheckSight(lightningSource, thing) == false)
|
|
{
|
|
// Not in sight
|
|
return BMIT_CONTINUE;
|
|
}
|
|
#endif
|
|
|
|
P_DamageMobj(thing, lightningSource, lightningSource, 1, DMG_VOLTAGE|DMG_CANTHURTSELF);
|
|
return BMIT_CONTINUE;
|
|
}
|
|
|
|
void K_ThunderShieldAttack(mobj_t *actor, fixed_t size)
|
|
{
|
|
INT32 bx, by, xl, xh, yl, yh;
|
|
|
|
lightningDist = FixedMul(size, actor->scale);
|
|
lightningSource = actor;
|
|
|
|
// Use blockmap to check for nearby shootables
|
|
yh = (unsigned)(actor->y + lightningDist - bmaporgy)>>MAPBLOCKSHIFT;
|
|
yl = (unsigned)(actor->y - lightningDist - bmaporgy)>>MAPBLOCKSHIFT;
|
|
xh = (unsigned)(actor->x + lightningDist - bmaporgx)>>MAPBLOCKSHIFT;
|
|
xl = (unsigned)(actor->x - lightningDist - bmaporgx)>>MAPBLOCKSHIFT;
|
|
|
|
BMBOUNDFIX (xl, xh, yl, yh);
|
|
|
|
for (by = yl; by <= yh; by++)
|
|
for (bx = xl; bx <= xh; bx++)
|
|
P_BlockThingsIterator(bx, by, PIT_ThunderShieldAttack);
|
|
}
|
|
|
|
static void K_BubbleShieldCollideDrain(player_t *player, mobj_t *bubble, INT16 dmg)
|
|
{
|
|
if (!player)
|
|
{
|
|
// Bad reference passed.
|
|
return;
|
|
}
|
|
|
|
if (!bubble || P_MobjWasRemoved(bubble) )
|
|
{
|
|
// Bad/Stale reference passed.
|
|
return;
|
|
}
|
|
|
|
// Damage the shield after an item hit.
|
|
K_RemoveBubbleHealth(player, dmg);
|
|
|
|
if (K_GetShieldFromPlayer(player) == KSHIELD_BUBBLE)
|
|
{
|
|
if ((dmg > 0) && (bubble) && (!P_MobjWasRemoved(bubble)))
|
|
{
|
|
// Play a distinct "crack!" noise to clue the player in.
|
|
S_StartSound(bubble, sfx_s3k6e);
|
|
}
|
|
}
|
|
|
|
// Apply a cooldown if the Bubble Shield took damage without shattering.
|
|
if ((player->bubblehealth > 0) && (dmg > 0))
|
|
player->bubbleblowup = 0;
|
|
}
|
|
|
|
static boolean K_BubbleReflectingTrapItem(const mobj_t *t)
|
|
{
|
|
return t->type == MT_BANANA || (t->type == MT_ORBINAUT && t->flags2 & MF2_AMBUSH) || t->type == MT_JAWZ_DUD ||
|
|
t->type == MT_EGGMANITEM || t->type == MT_SSMINE || t->type == MT_SSMINE_SHIELD || t->type == MT_LANDMINE;
|
|
}
|
|
|
|
static boolean K_StrongPlayerBump(const player_t *player)
|
|
{
|
|
return (((K_GetKartInvinType() == KARTINVIN_LEGACY) && (player->invincibilitytimer))
|
|
|| (player->growshrinktimer > 0));
|
|
}
|
|
|
|
boolean K_BubbleShieldReflect(mobj_t *t1, mobj_t *t2)
|
|
{
|
|
if (t2->threshold && !t2->player)
|
|
return false;
|
|
|
|
mobj_t *owner = t1->player ? t1 : t1->target;
|
|
|
|
angle_t angle = R_PointToAngle2(t1->x, t1->y, t2->x, t2->y);
|
|
fixed_t momentum = max(FixedHypot(owner->momx, owner->momy), FixedHypot(t2->momx, t2->momy));
|
|
momentum = max(3*momentum/4, 16*mapobjectscale); // do SOMETHING!
|
|
|
|
if (t2->player && !K_StrongPlayerBump(t2->player))
|
|
{
|
|
P_Thrust(t2, angle, momentum/2);
|
|
}
|
|
else
|
|
{
|
|
P_InstaThrust(t2, angle, momentum);
|
|
if (!t2->player)
|
|
t2->angle = angle;
|
|
}
|
|
|
|
if (K_BubbleReflectingTrapItem(t2))
|
|
{
|
|
// Stupid hack: Toss trap/dud items into the air
|
|
t2->momz += (24*t2->scale) * P_MobjFlip(t2);
|
|
t2->z += t2->momz;
|
|
}
|
|
|
|
if (!t2->player)
|
|
{
|
|
if (t2->type == MT_JAWZ)
|
|
P_SetTarget(&t2->tracer, t2->target); // Back to the source!
|
|
P_SetTarget(&t2->target, owner); // Let the source reflect it back again!
|
|
if ((t2->type == MT_ORBINAUT && t2->flags2 & MF2_AMBUSH) || t2->type == MT_JAWZ_DUD)
|
|
t2->flags |= MF_NOCLIPTHING;
|
|
else
|
|
t2->threshold = 10;
|
|
}
|
|
|
|
S_StartSound(t1, sfx_s3k44);
|
|
return true;
|
|
}
|
|
|
|
boolean K_BubbleShieldCanReflect(mobj_t *t)
|
|
{
|
|
return (t->type == MT_ORBINAUT || t->type == MT_JAWZ || t->type == MT_JAWZ_DUD
|
|
|| t->type == MT_BANANA || t->type == MT_EGGMANITEM || t->type == MT_BALLHOG
|
|
|| t->type == MT_SSMINE || t->type == MT_LANDMINE || t->type == MT_SINK
|
|
|| t->type == MT_KART_LEFTOVER
|
|
|| t->type == MT_PLAYER);
|
|
}
|
|
|
|
static INT16 K_GetBubbleDamage(mobj_t* itm)
|
|
{
|
|
if ((!itm) || (P_MobjWasRemoved(itm)))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
INT16 dmg = BUBBLEITMDAMAGE;
|
|
fixed_t momentum_epsilon =
|
|
FixedMul(FixedMul(itm->radius, itm->scale), K_GetKartGameSpeedScalar(gamespeed));
|
|
|
|
switch (itm->type)
|
|
{
|
|
case MT_EGGMANITEM:
|
|
case MT_BALLHOG:
|
|
// Experiment: Let Bubble Shields hard-counter Ballhogs and Eggboxes.
|
|
dmg = 0;
|
|
break;
|
|
case MT_SINK:
|
|
case MT_JAWZ:
|
|
// Sinks and Jawz smash Bubble Shields to bits.
|
|
dmg = MAXBUBBLEHEALTH;
|
|
break;
|
|
case MT_BANANA:
|
|
if (itm->health <= 1)
|
|
{
|
|
// Not sniping; don't run the penetration check.
|
|
break;
|
|
}
|
|
/*FALLTHRU*/
|
|
case MT_ORBINAUT:
|
|
{
|
|
fixed_t momentum = K_Momentum3D(itm);
|
|
|
|
// Reward players on the offensive by letting fast Orbinauts shatter Bubble Shields.
|
|
// Same thing for Banana snipes.
|
|
if (momentum >= momentum_epsilon)
|
|
{
|
|
dmg = MAXBUBBLEHEALTH;
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return dmg;
|
|
}
|
|
|
|
boolean K_BubbleShieldCollide(mobj_t *t1, mobj_t *t2)
|
|
{
|
|
// don't bump yourself
|
|
if (t1->target == t2)
|
|
return true;
|
|
|
|
if (K_BubbleShieldCanReflect(t2))
|
|
{
|
|
boolean reflected = K_BubbleShieldReflect(t1, t2);
|
|
|
|
if (reflected)
|
|
{
|
|
// Drain my stuff please.
|
|
K_BubbleShieldCollideDrain(t1->target->player, t1, K_GetBubbleDamage(t2));
|
|
}
|
|
|
|
return reflected;
|
|
}
|
|
|
|
if (t2->flags & MF_SHOOTABLE)
|
|
{
|
|
boolean shootable = P_DamageMobj(t2, t1, t1->target, 1, DMG_NORMAL);
|
|
|
|
if (shootable)
|
|
{
|
|
// Drain my stuff please.
|
|
K_BubbleShieldCollideDrain(t1->target->player, t1, K_GetBubbleDamage(t2));
|
|
}
|
|
|
|
return shootable;
|
|
}
|
|
|
|
// no interaction
|
|
return true;
|
|
}
|
|
|
|
boolean K_KitchenSinkCollide(mobj_t *t1, mobj_t *t2)
|
|
{
|
|
if (((t1->target == t2) || (!(t2->flags & (MF_ENEMY|MF_BOSS)) && (t1->target == t2->target))) && (t1->threshold > 0 || (t2->type != MT_PLAYER && t2->threshold > 0)))
|
|
return true;
|
|
|
|
if (t2->player)
|
|
{
|
|
if (t2->player->flashing > 0)
|
|
return true;
|
|
|
|
K_StatPlayerSink(t1->player, P_MobjWasRemoved(t2->target) ? NULL : t2->target->player);
|
|
|
|
S_StartSound(NULL, sfx_bsnipe); //let all players hear it.
|
|
S_StartSound(NULL, sfx_cgot); //let all players hear it.
|
|
|
|
HU_SetCEchoFlags(0);
|
|
HU_SetCEchoDuration(5);
|
|
HU_DoCEcho(va("%s\\was hit by a kitchen sink.\\\\\\\\", player_names[t2->player-players]));
|
|
I_OutputMsg("%s was hit by a kitchen sink.\n", player_names[t2->player-players]);
|
|
|
|
P_PlayerRingBurst(t2->player, t2->player->rings);
|
|
P_DamageMobj(t2, t1, t1->target, 1, DMG_INSTAKILL);
|
|
P_KillMobj(t1, t2, t2, DMG_NORMAL);
|
|
}
|
|
else if (t2->flags & MF_SHOOTABLE)
|
|
{
|
|
// Shootable damage
|
|
P_KillMobj(t2, t2, t1->target, DMG_NORMAL);
|
|
// This item damage
|
|
P_KillMobj(t1, t2, t2, DMG_NORMAL);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
boolean K_FallingRockCollide(mobj_t *t1, mobj_t *t2)
|
|
{
|
|
if ((t2->player && (G_CompatLevel(0x0008) || !t2->player->hyudorotimer)) || t2->type == MT_FALLINGROCK)
|
|
K_KartBouncing(t2, t1, false, false);
|
|
|
|
return true;
|
|
}
|
|
|
|
boolean K_SMKIceBlockCollide(mobj_t *t1, mobj_t *t2)
|
|
{
|
|
if (!(t2->flags & MF_SOLID || t2->flags & MF_SHOOTABLE || t2->flags & MF_BOUNCE))
|
|
return true;
|
|
|
|
if (!(t2->health))
|
|
return true;
|
|
|
|
if (t2->type == MT_BANANA || t2->type == MT_BANANA_SHIELD
|
|
|| t2->type == MT_EGGMANITEM || t2->type == MT_EGGMANITEM_SHIELD
|
|
|| t2->type == MT_SSMINE || t2->type == MT_SSMINE_SHIELD
|
|
|| t2->type == MT_ORBINAUT_SHIELD || t2->type == MT_JAWZ_SHIELD)
|
|
return false;
|
|
|
|
if (t1->health)
|
|
P_KillMobj(t1, t2, t2, DMG_NORMAL);
|
|
|
|
/*
|
|
if (t2->player && (t2->player->invincibilitytimer > 0
|
|
|| t2->player->growshrinktimer > 0))
|
|
return true;
|
|
*/
|
|
|
|
K_KartBouncing(t2, t1, false, true);
|
|
return false;
|
|
}
|
|
|
|
boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2)
|
|
{
|
|
const boolean flameT1 = ((t1->player->flamestore > 0) && (t1->player->flametimer > 0));
|
|
const boolean flameT2 = ((t2->player->flamestore > 0) && (t2->player->flametimer > 0));
|
|
const boolean hyudoroT1 = (t1->player->hyudorotimer > 0);
|
|
const boolean hyudoroT2 = (t2->player->hyudorotimer > 0);
|
|
|
|
boolean t1Condition = false;
|
|
boolean t2Condition = false;
|
|
|
|
// Rim suggestion: Flipover damage is negligible at best, just cull it from Invincibility as a whole.
|
|
if (K_GetKartInvinType() == KARTINVIN_LEGACY)
|
|
{
|
|
t1Condition = (t1->player->invincibilitytimer > 0);
|
|
t2Condition = (t2->player->invincibilitytimer > 0);
|
|
}
|
|
|
|
if ((t1Condition == true || flameT1 == true) && (t2Condition == true || flameT2 == true))
|
|
{
|
|
K_DoInstashield(t1->player);
|
|
K_DoInstashield(t2->player);
|
|
return false;
|
|
}
|
|
else if (K_GetKartInvinType() == KARTINVIN_LEGACY)
|
|
{
|
|
if (t1Condition == true && t2Condition == false)
|
|
{
|
|
P_DamageMobj(t2, t1, t1, 1, DMG_WIPEOUT);
|
|
return true;
|
|
}
|
|
else if (t1Condition == false && t2Condition == true)
|
|
{
|
|
P_DamageMobj(t1, t2, t2, 1, DMG_WIPEOUT);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
t1Condition = (t1->scale > t2->scale + (mapobjectscale/8));
|
|
t2Condition = (t2->scale > t1->scale + (mapobjectscale/8));
|
|
|
|
if ((t1Condition == true || flameT1 == true) && (t2Condition == true || flameT2 == true))
|
|
{
|
|
return false;
|
|
}
|
|
else if (t1Condition == true && t2Condition == false)
|
|
{
|
|
if (G_CompatLevel(0x0008) || !hyudoroT1)
|
|
P_DamageMobj(t2, t1, t1, 1, DMG_SQUISH);
|
|
return true;
|
|
}
|
|
else if (t1Condition == false && t2Condition == true)
|
|
{
|
|
if (G_CompatLevel(0x0008) || !hyudoroT2)
|
|
P_DamageMobj(t1, t2, t2, 1, DMG_SQUISH);
|
|
return true;
|
|
}
|
|
|
|
// Flame Shield dash damage
|
|
t1Condition = flameT1;
|
|
t2Condition = flameT2;
|
|
|
|
if (t1Condition == true && t2Condition == false)
|
|
{
|
|
P_DamageMobj(t2, t1, t1, 1, DMG_FLIPOVER);
|
|
return true;
|
|
}
|
|
else if (t1Condition == false && t2Condition == true)
|
|
{
|
|
P_DamageMobj(t1, t2, t2, 1, DMG_FLIPOVER);
|
|
return true;
|
|
}
|
|
|
|
// Battle Mode Sneaker damage
|
|
// (Pogo Spring damage is handled in head-stomping code)
|
|
if (gametyperules & GTR_BUMPERS)
|
|
{
|
|
t1Condition = ((t1->player->sneakertimer > 0)
|
|
&& !P_PlayerInPain(t1->player)
|
|
&& (t1->player->flashing == 0));
|
|
t2Condition = ((t2->player->sneakertimer > 0)
|
|
&& !P_PlayerInPain(t2->player)
|
|
&& (t2->player->flashing == 0));
|
|
|
|
if (t1Condition == true && t2Condition == false)
|
|
{
|
|
P_DamageMobj(t2, t1, t1, 1, DMG_WIPEOUT|DMG_STEAL);
|
|
return true;
|
|
}
|
|
else if (t1Condition == false && t2Condition == true)
|
|
{
|
|
P_DamageMobj(t1, t2, t2, 1, DMG_WIPEOUT|DMG_STEAL);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|