RingRacers/src/objects/ballhog.cpp
2025-02-13 15:56:37 -06:00

410 lines
9.1 KiB
C++

// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour
// 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.
//-----------------------------------------------------------------------------
/// \file ballhog.cpp
/// \brief Ballhog 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_collide.h"
namespace
{
struct hog_angles
{
fixed_t x, y;
hog_angles(fixed_t nx, fixed_t ny) : x(nx), y(ny) {}
};
std::vector<struct hog_angles> g_hogangles;
};
static void CalculateHogAngles(UINT8 n)
{
const UINT8 total_hogs = n;
// This algorithm should probably be replaced to
// maximize more space covered, but the desired effect
// is achieved for 1 to 5, which is the vast majority of uses.
g_hogangles.clear();
if (total_hogs == 0)
{
return;
}
if (total_hogs == 1 || total_hogs > 4)
{
// Add a point to the exact middle.
g_hogangles.emplace_back(0, 0);
n--;
}
if (total_hogs > 1)
{
const fixed_t base_radius = mobjinfo[MT_BALLHOG].radius * 6;
fixed_t radius = base_radius;
UINT8 max_points = 6;
angle_t circle_offset = 0;
if (total_hogs < 5)
{
// Reduce size to get more space covered.
radius /= 2;
max_points = 4;
}
while (n > 0)
{
const UINT8 add_points = std::min<UINT8>(n, max_points);
angle_t angle = circle_offset;
const angle_t angle_offset = ANGLE_MAX / add_points;
for (UINT8 c = 0; c < add_points; c++)
{
g_hogangles.emplace_back(
FixedMul(FINECOSINE( angle >> ANGLETOFINESHIFT ), radius),
FixedMul( FINESINE( angle >> ANGLETOFINESHIFT ), radius)
);
angle += angle_offset;
n--;
}
radius += base_radius;
circle_offset += ANGLE_MAX / max_points;
max_points += (max_points / 2);
}
}
}
UINT8 K_HogChargeToHogCount(INT32 charge, UINT8 cap)
{
return std::clamp<UINT8>((charge / BALLHOGINCREMENT), 0, cap);
}
static boolean HogReticuleEmulate(mobj_t *mobj)
{
fixed_t x, y, z;
//I_Assert(mobj != NULL);
//I_Assert(P_MobjWasRemoved(mobj) == false);
x = mobj->x, y = mobj->y, z = mobj->z;
//if (mobj->momx || mobj->momy)
{
if (P_XYMovement(mobj) == false)
{
return true;
}
if (P_MobjWasRemoved(mobj) == true)
{
return true;
}
}
//if (mobj->momz)
{
if (P_ZMovement(mobj) == false)
{
return true; // mobj was removed
}
if (P_MobjWasRemoved(mobj) == true)
{
return true;
}
}
if (P_MobjWasRemoved(mobj) == true)
{
return true;
}
if (x == mobj->x && y == mobj->y && z == mobj->z)
{
return true;
}
return false;
}
static void HogReticuleTest(player_t *player, vector3_t *ret, fixed_t final_scale)
{
// Emulate the movement of a tossed ballhog
// until it hits something.
fixed_t dir = FRACUNIT;
if (player->throwdir == 1)
{
dir = 2*FRACUNIT;
}
else if (player->throwdir == -1)
{
dir = FRACUNIT/2;
}
// Use pre-determined speed for tossing
fixed_t proj_speed = FixedMul(82 * FRACUNIT, K_GetKartGameSpeedScalar(gamespeed));
// Scale to map scale
// Intentionally NOT player scale, that doesn't work.
proj_speed = FixedMul(proj_speed, mapobjectscale);
// Shoot forward
//P_MoveOrigin(mo, player->mo->x, player->mo->y, player->mo->z + (player->mo->height / 2));
// FINE! YOU WIN! I'll make an object every time I need to test this...
mobj_t *mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + (player->mo->height / 2), MT_BALLHOG_RETICULE_TEST);
mo->fuse = 2; // if something goes wrong, destroy it
mo->angle = player->mo->angle;
// These are really weird so let's make it a very specific case to make SURE it works...
if (player->mo->eflags & MFE_VERTICALFLIP)
{
mo->z -= player->mo->height;
mo->eflags |= MFE_VERTICALFLIP;
mo->flags2 |= (player->mo->flags2 & MF2_OBJECTFLIP);
}
P_SetTarget(&mo->target, player->mo);
mo->threshold = 10;
mo->extravalue2 = dir;
fixed_t proj_speed_z = ((20 * FRACUNIT) + (10 * dir)) + (FixedDiv(player->mo->momz, mapobjectscale) * P_MobjFlip(player->mo)); // Also intentionally not player scale
P_SetObjectMomZ(mo, proj_speed_z, false);
angle_t fa = (player->mo->angle >> ANGLETOFINESHIFT);
mo->momx = player->mo->momx + FixedMul(FINECOSINE(fa), FixedMul(proj_speed, dir));
mo->momy = player->mo->momy + FixedMul( FINESINE(fa), FixedMul(proj_speed, dir));
if (mo->eflags & MFE_UNDERWATER)
{
mo->momz = (117 * mo->momz) / 200;
}
P_SetScale(mo, final_scale);
mo->destscale = final_scale;
// Contra spread shot scale up
mo->destscale = mo->destscale << 1;
mo->scalespeed = abs(mo->destscale - mo->scale) / (2*TICRATE);
ret->x = mo->x;
ret->y = mo->y;
ret->z = mo->z;
constexpr INT32 max_iteration = 256;
for (INT32 i = 0; i < max_iteration; i++)
{
#if 0
mobj_t *test = P_SpawnMobj(mo->x, mo->y, mo->z, MT_THOK);
test->tics = 2;
#endif
if (HogReticuleEmulate(mo) == true)
{
break;
}
ret->x = mo->x;
ret->y = mo->y;
ret->z = mo->z;
}
P_RemoveMobj(mo);
}
void K_UpdateBallhogReticules(player_t *player, UINT8 num_hogs, boolean on_release)
{
if (player == nullptr)
{
return;
}
if (player->mo == nullptr || P_MobjWasRemoved(player->mo) == true)
{
return;
}
const UINT8 start_hogs = num_hogs;
#if 1
if (start_hogs == 0)
{
return;
}
#endif
CalculateHogAngles(num_hogs);
// Calculate center positon
fixed_t final_scale = K_ItemScaleFromConst(K_GetItemScaleConst(player->mo->scale));
vector3_t center = {0, 0, 0};
HogReticuleTest(player, &center, final_scale); // Originally this was called for everything, but it's more optimized to only run 1 prediction.
constexpr tic_t kBallhogReticuleTime = (TICRATE * 3 / 4);
// Update existing reticules.
mobj_t *reticule = player->ballhogreticule;
while (reticule != nullptr && P_MobjWasRemoved(reticule) == false)
{
mobj_t *next = reticule->hnext;
boolean removed = false;
if (num_hogs > 0)
{
const UINT8 old_hogs = reticule->extravalue1;
UINT8 angle_index = num_hogs - 1;
fixed_t x_offset = FixedMul(g_hogangles[angle_index].x, final_scale);
fixed_t y_offset = FixedMul(g_hogangles[angle_index].y, final_scale);
if (on_release == true)
{
reticule->extravalue1 = start_hogs;
P_MoveOrigin(
reticule,
center.x + x_offset,
center.y + y_offset,
center.z
);
}
else
{
if (start_hogs != old_hogs)
{
// Reset to the middle
P_SetOrigin(reticule, center.x, center.y, center.z);
reticule->extravalue1 = start_hogs;
}
// Move to new position
P_MoveOrigin(
reticule,
reticule->x + (((center.x + x_offset) - reticule->x) / (BALLHOGINCREMENT / 2)),
reticule->y + (((center.y + y_offset) - reticule->y) / (BALLHOGINCREMENT / 2)),
reticule->z + ((center.z - reticule->z) / (BALLHOGINCREMENT / 2))
);
}
reticule->tics = kBallhogReticuleTime;
reticule->color = player->skincolor;
reticule->destscale = final_scale * 2;
P_SetScale(reticule, reticule->destscale);
num_hogs--;
}
#if 0
else
{
// Too many reticules exist, so remove the remainder.
P_RemoveMobj(reticule);
removed = true;
}
#endif
if (next == nullptr || P_MobjWasRemoved(next) == true)
{
break;
}
if (removed == true)
{
P_SetTarget(&next->hprev, nullptr);
}
reticule = next;
}
// Not enough reticules exist, so make new ones.
while (num_hogs > 0)
{
mobj_t *new_reticule = P_SpawnMobjFromMobj((reticule != nullptr) ? reticule : player->mo, 0, 0, 0, MT_BALLHOG_RETICULE);
if (new_reticule == nullptr)
{
break;
}
if (reticule != nullptr)
{
P_SetTarget(&reticule->hnext, new_reticule);
P_SetTarget(&new_reticule->hprev, reticule);
}
else
{
P_SetTarget(&player->ballhogreticule, new_reticule);
P_SetTarget(&new_reticule->hprev, player->mo);
}
new_reticule->extravalue1 = start_hogs;
new_reticule->tics = kBallhogReticuleTime;
new_reticule->color = player->skincolor;
new_reticule->destscale = final_scale * 2;
P_SetScale(new_reticule, new_reticule->destscale);
if (on_release == true)
{
UINT8 angle_index = num_hogs - 1;
fixed_t x_offset = FixedMul(g_hogangles[angle_index].x, final_scale);
fixed_t y_offset = FixedMul(g_hogangles[angle_index].y, final_scale);
P_SetOrigin(new_reticule, center.x + x_offset, center.y + y_offset, center.z);
}
else
{
P_SetOrigin(new_reticule, center.x, center.y, center.z);
}
reticule = new_reticule;
num_hogs--;
}
}
void K_DoBallhogAttack(player_t *player, UINT8 num_hogs)
{
// Update reticules instantly, then untie them to us.
K_UpdateBallhogReticules(player, num_hogs, true);
P_SetTarget(&player->ballhogreticule, nullptr);
CalculateHogAngles(num_hogs);
while (num_hogs > 0)
{
UINT8 angle_index = num_hogs - 1;
fixed_t x_offset = g_hogangles[angle_index].x;
fixed_t y_offset = g_hogangles[angle_index].y;
K_ThrowKartItemEx(
player,
false, MT_BALLHOG, 1, 2,
0,
x_offset, y_offset
);
num_hogs--;
}
}