mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-12-02 06:04:21 +00:00
495 lines
12 KiB
C
495 lines
12 KiB
C
// DR. ROBOTNIK'S RING RACERS
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright (C) 2022 by Sally "TehRealSalt" Cochenour
|
|
// Copyright (C) 2022 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.
|
|
//-----------------------------------------------------------------------------
|
|
/// \file orbinaut.c
|
|
/// \brief Orbinaut item code.
|
|
|
|
#include "../doomdef.h"
|
|
#include "../doomstat.h"
|
|
#include "../info.h"
|
|
#include "../k_kart.h"
|
|
#include "../k_objects.h"
|
|
#include "../m_random.h"
|
|
#include "../p_local.h"
|
|
#include "../r_main.h"
|
|
#include "../s_sound.h"
|
|
#include "../g_game.h"
|
|
#include "../z_zone.h"
|
|
#include "../k_waypoint.h"
|
|
#include "../k_respawn.h"
|
|
#include "../k_collide.h"
|
|
|
|
#define ORBINAUT_MAXTURN (ANGLE_67h)
|
|
#define ORBINAUT_TURNLERP (16)
|
|
|
|
#define orbinaut_speed(o) ((o)->movefactor)
|
|
#define orbinaut_selfdelay(o) ((o)->threshold)
|
|
#define orbinaut_droptime(o) ((o)->movecount)
|
|
|
|
#define orbinaut_turn(o) ((o)->extravalue1)
|
|
|
|
#define orbinaut_owner(o) ((o)->target)
|
|
|
|
#define orbinaut_shield_dist(o) ((o)->extravalue1)
|
|
|
|
enum {
|
|
ORBI_DROPPED = 0x01, // stationary hazard
|
|
ORBI_TOSSED = 0x02, // Gacha Bom tossed forward
|
|
ORBI_TRAIL = 0x04, // spawn afterimages
|
|
ORBI_SPIN = 0x08, // animate facing angle
|
|
ORBI_WATERSKI = 0x10, // this orbinaut can waterski
|
|
};
|
|
|
|
#define orbinaut_flags(o) ((o)->movedir)
|
|
#define orbinaut_spin(o) ((o)->extravalue2)
|
|
|
|
void Obj_OrbinautThink(mobj_t *th)
|
|
{
|
|
boolean grounded = P_IsObjectOnGround(th);
|
|
|
|
if (th->fuse > 0 && th->fuse <= TICRATE)
|
|
{
|
|
th->renderflags ^= RF_DONTDRAW;
|
|
}
|
|
|
|
if (orbinaut_flags(th) & ORBI_DROPPED)
|
|
{
|
|
if (grounded && (th->flags & MF_NOCLIPTHING))
|
|
{
|
|
th->momx = 1;
|
|
th->momy = 0;
|
|
th->frame = 3;
|
|
S_StartSound(th, th->info->activesound);
|
|
th->flags &= ~MF_NOCLIPTHING;
|
|
}
|
|
else if (orbinaut_droptime(th))
|
|
{
|
|
orbinaut_droptime(th)--;
|
|
}
|
|
else if (th->frame < 3)
|
|
{
|
|
orbinaut_droptime(th) = 2;
|
|
th->frame++;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (orbinaut_flags(th) & ORBI_TRAIL)
|
|
{
|
|
mobj_t *ghost = NULL;
|
|
|
|
ghost = P_SpawnGhostMobj(th);
|
|
ghost->colorized = true; // already has color!
|
|
}
|
|
|
|
th->angle = K_MomentumAngle(th);
|
|
if (orbinaut_turn(th) != 0)
|
|
{
|
|
th->angle += orbinaut_turn(th);
|
|
|
|
if (abs(orbinaut_turn(th)) < ORBINAUT_MAXTURN)
|
|
{
|
|
if (orbinaut_turn(th) < 0)
|
|
{
|
|
orbinaut_turn(th) -= ORBINAUT_MAXTURN / ORBINAUT_TURNLERP;
|
|
}
|
|
else
|
|
{
|
|
orbinaut_turn(th) += ORBINAUT_MAXTURN / ORBINAUT_TURNLERP;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (grounded == true)
|
|
{
|
|
fixed_t finalspeed = orbinaut_speed(th);
|
|
const fixed_t currentspeed = R_PointToDist2(0, 0, th->momx, th->momy);
|
|
fixed_t thrustamount = 0;
|
|
fixed_t frictionsafety = (th->friction == 0) ? 1 : th->friction;
|
|
|
|
if (th->health <= 5)
|
|
{
|
|
INT32 i;
|
|
for (i = 5; i >= th->health; i--)
|
|
{
|
|
finalspeed = FixedMul(finalspeed, FRACUNIT-FRACUNIT/4);
|
|
}
|
|
}
|
|
|
|
if (currentspeed >= finalspeed)
|
|
{
|
|
// Thrust as if you were at top speed, slow down naturally
|
|
thrustamount = FixedDiv(finalspeed, frictionsafety) - finalspeed;
|
|
}
|
|
else
|
|
{
|
|
const fixed_t beatfriction = FixedDiv(currentspeed, frictionsafety) - currentspeed;
|
|
// Thrust to immediately get to top speed
|
|
thrustamount = beatfriction + FixedDiv(finalspeed - currentspeed, frictionsafety);
|
|
}
|
|
|
|
P_Thrust(th, th->angle, thrustamount);
|
|
}
|
|
|
|
if (orbinaut_flags(th) & ORBI_SPIN)
|
|
{
|
|
th->angle = orbinaut_spin(th);
|
|
orbinaut_spin(th) += ANGLE_22h;
|
|
}
|
|
|
|
/* todo: UDMFify
|
|
if (P_MobjTouchingSectorSpecialFlag(th, ?))
|
|
{
|
|
K_DoPogoSpring(th, 0, 1);
|
|
}
|
|
*/
|
|
|
|
if (orbinaut_selfdelay(th) > 0)
|
|
{
|
|
orbinaut_selfdelay(th)--;
|
|
}
|
|
|
|
if (leveltime % 6 == 0)
|
|
{
|
|
S_StartSound(th, th->info->activesound);
|
|
}
|
|
}
|
|
|
|
boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2)
|
|
{
|
|
boolean damageitem = false;
|
|
boolean tumbleitem = false;
|
|
boolean sprung = false;
|
|
|
|
if (t1->health <= 0 || t2->health <= 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if ((orbinaut_owner(t1) == t2)
|
|
|| (!(t2->flags & (MF_ENEMY|MF_BOSS)) && (orbinaut_owner(t1) == t2->target)))
|
|
{
|
|
if ((orbinaut_selfdelay(t1) > 0 && t2->type == MT_PLAYER)
|
|
|| (orbinaut_selfdelay(t2) > 0 && t2->type != MT_PLAYER))
|
|
{
|
|
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
|
|
&& (orbinaut_owner(t1) == t2->target)) // Don't hit each other if you have the same target
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (t1->type == MT_GARDENTOP)
|
|
{
|
|
tumbleitem = true;
|
|
}
|
|
|
|
if (t2->player)
|
|
{
|
|
if ((t2->player->flashing > 0 && t2->hitlag == 0)
|
|
&& !(t1->type == MT_ORBINAUT || t1->type == MT_JAWZ || t1->type == MT_GACHABOM))
|
|
return true;
|
|
|
|
if (t2->player->hyudorotimer)
|
|
return true; // no interaction
|
|
|
|
if (t2->player->flamedash && t2->player->itemtype == KITEM_FLAMESHIELD)
|
|
{
|
|
// Melt item
|
|
S_StartSound(t2, sfx_s3k43);
|
|
}
|
|
else
|
|
{
|
|
// Player Damage
|
|
if (t1->type == MT_ORBINAUT_SHIELD || t1->type == MT_JAWZ_SHIELD)
|
|
{
|
|
// Same hack as Instawhip!
|
|
// If you do this a third time, please make it a part of the damage system.
|
|
// ^ remove all of P_DamageMobj and start over
|
|
P_PlayRinglossSound(t2);
|
|
P_PlayerRingBurst(t2->player, 5);
|
|
P_DamageMobj(t2, t1, t1->target, 1, DMG_WOMBO | DMG_WHUMBLE);
|
|
}
|
|
else
|
|
{
|
|
P_DamageMobj(t2, t1, t1->target, 1, DMG_WOMBO |
|
|
(tumbleitem ? DMG_TUMBLE : DMG_WIPEOUT));
|
|
}
|
|
K_KartBouncing(t2, t1);
|
|
S_StartSound(t2, sfx_s3k7b);
|
|
}
|
|
|
|
damageitem = true;
|
|
}
|
|
else if (t2->type == MT_ORBINAUT || t2->type == MT_JAWZ
|
|
|| t2->type == MT_ORBINAUT_SHIELD || t2->type == MT_JAWZ_SHIELD
|
|
|| t2->type == MT_BANANA || t2->type == MT_BANANA_SHIELD
|
|
|| t2->type == MT_BALLHOG || t2->type == MT_GACHABOM)
|
|
{
|
|
// 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, 24*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;
|
|
}
|
|
else if (t2->flags & MF_PAIN)
|
|
{
|
|
// Hazard blocks
|
|
P_SpawnMobj(t2->x/2 + t1->x/2, t2->y/2 + t1->y/2, t2->z/2 + t1->z/2, MT_ITEMCLASH);
|
|
damageitem = true;
|
|
}
|
|
|
|
if (t1->type == MT_GARDENTOP)
|
|
{
|
|
damageitem = false;
|
|
}
|
|
|
|
if (damageitem && P_MobjWasRemoved(t1) == false)
|
|
{
|
|
angle_t bounceangle;
|
|
if (P_MobjWasRemoved(t2) == false)
|
|
{
|
|
bounceangle = K_GetCollideAngle(t2, t1);
|
|
}
|
|
else
|
|
{
|
|
bounceangle = K_MomentumAngle(t1) + ANGLE_90;
|
|
t2 = NULL; // handles the arguments to P_KillMobj
|
|
}
|
|
|
|
// This Item Damage
|
|
S_StartSound(t1, t1->info->deathsound);
|
|
P_KillMobj(t1, t2, t2, DMG_NORMAL);
|
|
|
|
if (t1->type == MT_GACHABOM && !P_MobjWasRemoved(orbinaut_owner(t1)))
|
|
{
|
|
// Instead of flying out at an angle when
|
|
// destroyed, spawn an explosion and eventually
|
|
// return to sender. The original Gachabom will be
|
|
// removed next tic (see deathstate).
|
|
t1->tics = 2;
|
|
Obj_SpawnGachaBomRebound(t1, orbinaut_owner(t1));
|
|
}
|
|
else
|
|
{
|
|
P_SetObjectMomZ(t1, 24*FRACUNIT, false);
|
|
|
|
P_InstaThrust(t1, bounceangle, 16*FRACUNIT);
|
|
}
|
|
}
|
|
|
|
if (sprung)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Obj_OrbinautThrown(mobj_t *th, fixed_t finalSpeed, SINT8 dir)
|
|
{
|
|
orbinaut_flags(th) = 0;
|
|
|
|
if (orbinaut_owner(th) != NULL && P_MobjWasRemoved(orbinaut_owner(th)) == false
|
|
&& orbinaut_owner(th)->player != NULL)
|
|
{
|
|
th->color = orbinaut_owner(th)->player->skincolor;
|
|
|
|
const mobj_t *owner = orbinaut_owner(th);
|
|
const ffloor_t *rover = P_IsObjectFlipped(owner) ? owner->ceilingrover : owner->floorrover;
|
|
|
|
if (dir != -1 && rover && (rover->fofflags & FOF_SWIMMABLE))
|
|
{
|
|
// The owner can run on water, so we should too!
|
|
orbinaut_flags(th) |= ORBI_WATERSKI;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
th->color = SKINCOLOR_GREY;
|
|
}
|
|
|
|
th->fuse = RR_PROJECTILE_FUSE;
|
|
orbinaut_speed(th) = finalSpeed;
|
|
|
|
orbinaut_flags(th) |= ORBI_TRAIL;
|
|
|
|
if (dir == -1)
|
|
{
|
|
// Thrown backwards, init orbiting in place
|
|
orbinaut_turn(th) = ORBINAUT_MAXTURN / ORBINAUT_TURNLERP;
|
|
|
|
th->angle -= ANGLE_45;
|
|
th->momx = FixedMul(finalSpeed, FINECOSINE(th->angle >> ANGLETOFINESHIFT));
|
|
th->momy = FixedMul(finalSpeed, FINESINE(th->angle >> ANGLETOFINESHIFT));
|
|
}
|
|
}
|
|
|
|
void Obj_GachaBomThrown(mobj_t *th, fixed_t finalSpeed, SINT8 dir)
|
|
{
|
|
Obj_OrbinautThrown(th, finalSpeed, dir);
|
|
|
|
orbinaut_flags(th) &= ~(ORBI_TRAIL);
|
|
|
|
switch (dir)
|
|
{
|
|
case -1:
|
|
orbinaut_flags(th) |= ORBI_SPIN;
|
|
break;
|
|
|
|
case 1:
|
|
orbinaut_flags(th) |= ORBI_TOSSED;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Obj_OrbinautJawzMoveHeld(player_t *player)
|
|
{
|
|
fixed_t finalscale = K_ItemScaleForPlayer(player);
|
|
fixed_t speed = 0;
|
|
mobj_t *cur = NULL, *next = player->mo->hnext;
|
|
|
|
player->bananadrag = 0; // Just to make sure
|
|
|
|
if (next == NULL)
|
|
return;
|
|
|
|
speed = ((8 - min(4, player->itemamount)) * next->info->speed) / 7;
|
|
|
|
while ((cur = next) != NULL && P_MobjWasRemoved(cur) == false)
|
|
{
|
|
const fixed_t radius = FixedHypot(player->mo->radius, player->mo->radius) + FixedHypot(cur->radius, cur->radius); // mobj's distance from its Target, or Radius.
|
|
fixed_t z;
|
|
|
|
next = cur->hnext;
|
|
|
|
if (!cur->health)
|
|
continue;
|
|
|
|
cur->color = player->skincolor;
|
|
|
|
cur->angle -= ANGLE_90;
|
|
cur->angle += FixedAngle(speed) / 3;
|
|
|
|
if (orbinaut_shield_dist(cur) < radius)
|
|
{
|
|
orbinaut_shield_dist(cur) += P_AproxDistance(orbinaut_shield_dist(cur), radius) / 12;
|
|
}
|
|
|
|
if (orbinaut_shield_dist(cur) > radius)
|
|
{
|
|
orbinaut_shield_dist(cur) = radius;
|
|
}
|
|
|
|
// If the player is on the ceiling, then flip your items as well.
|
|
if (player && player->mo->eflags & MFE_VERTICALFLIP)
|
|
{
|
|
cur->eflags |= MFE_VERTICALFLIP;
|
|
}
|
|
else
|
|
{
|
|
cur->eflags &= ~MFE_VERTICALFLIP;
|
|
}
|
|
|
|
// Shrink your items if the player shrunk too.
|
|
cur->destscale = FixedMul(FixedDiv(orbinaut_shield_dist(cur), radius), finalscale);
|
|
P_SetScale(cur, cur->destscale);
|
|
|
|
if (P_MobjFlip(cur) > 0)
|
|
{
|
|
z = player->mo->z;
|
|
}
|
|
else
|
|
{
|
|
z = player->mo->z + player->mo->height - cur->height;
|
|
}
|
|
|
|
cur->flags |= (MF_NOCLIP|MF_NOCLIPTHING); // temporarily make them noclip other objects so they can't hit anyone while in the player
|
|
P_MoveOrigin(cur, player->mo->x, player->mo->y, z);
|
|
cur->momx = FixedMul(FINECOSINE(cur->angle >> ANGLETOFINESHIFT), orbinaut_shield_dist(cur));
|
|
cur->momy = FixedMul(FINESINE(cur->angle >> ANGLETOFINESHIFT), orbinaut_shield_dist(cur));
|
|
cur->flags &= ~(MF_NOCLIP|MF_NOCLIPTHING);
|
|
|
|
if (!P_TryMove(cur, player->mo->x + cur->momx, player->mo->y + cur->momy, true, NULL))
|
|
{
|
|
P_SlideMove(cur, NULL);
|
|
if (P_MobjWasRemoved(cur))
|
|
continue;
|
|
}
|
|
|
|
if (P_IsObjectOnGround(player->mo))
|
|
{
|
|
if (P_MobjFlip(cur) > 0)
|
|
{
|
|
if (cur->floorz > player->mo->z - cur->height)
|
|
{
|
|
z = cur->floorz;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (cur->ceilingz < player->mo->z + player->mo->height + cur->height)
|
|
{
|
|
z = cur->ceilingz - cur->height;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Center it during the scale up animation
|
|
z += (FixedMul(cur->info->height, finalscale - cur->scale) >> 1) * P_MobjFlip(cur);
|
|
|
|
cur->z = z;
|
|
cur->momx = cur->momy = 0;
|
|
cur->angle += ANGLE_90;
|
|
}
|
|
}
|
|
|
|
boolean Obj_GachaBomWasTossed(mobj_t *th)
|
|
{
|
|
return (orbinaut_flags(th) & ORBI_TOSSED) == ORBI_TOSSED;
|
|
}
|
|
|
|
void Obj_OrbinautDrop(mobj_t *th)
|
|
{
|
|
orbinaut_flags(th) |= ORBI_DROPPED;
|
|
}
|
|
|
|
boolean Obj_OrbinautCanRunOnWater(mobj_t *th)
|
|
{
|
|
return (orbinaut_flags(th) & ORBI_WATERSKI) == ORBI_WATERSKI;
|
|
}
|