Merge branch 'master' of https://git.do.srb2.org/KartKrew/Kart into catholic-vfx

This commit is contained in:
toaster 2023-11-12 16:24:47 +00:00
commit 5cab2401b6
29 changed files with 1248 additions and 81 deletions

View file

@ -64,6 +64,8 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32
p_tick.c
p_user.c
p_slopes.c
p_sweep.cpp
p_test.cpp
tables.c
r_bsp.cpp
r_data.c

View file

@ -806,6 +806,7 @@ consvar_t cv_numlaps = OnlineCheat("numlaps", "Map default").values(numlaps_cons
consvar_t cv_restrictskinchange = OnlineCheat("restrictskinchange", "Yes").yes_no().description("Don't let players change their skin in the middle of gameplay");
consvar_t cv_spbtest = OnlineCheat("spbtest", "Off").on_off().description("SPB can never target a player");
consvar_t cv_showgremlins = OnlineCheat("showgremlins", "No").yes_no().description("Show line collision errors");
consvar_t cv_timescale = OnlineCheat(cvlist_timer)("timescale", "1.0").floating_point().min_max(FRACUNIT/20, 20*FRACUNIT).description("Overclock or slow down the game");
consvar_t cv_ufo_follow = OnlineCheat("ufo_follow", "0").min_max(0, MAXPLAYERS).description("Make UFO Catcher folow this player");
consvar_t cv_ufo_health = OnlineCheat("ufo_health", "-1").min_max(-1, 100).description("Override UFO Catcher health -- applied at spawn or when value is changed");

View file

@ -976,20 +976,9 @@ void D_SRB2Loop(void)
// Fully completed frame made.
finishprecise = I_GetPreciseTime();
if (!singletics)
{
INT64 elapsed = (INT64)(finishprecise - enterprecise);
// in the case of "match refresh rate" + vsync, don't sleep at all
const boolean vsync_with_match_refresh = cv_vidwait.value && cv_fpscap.value == 0;
if (elapsed > 0 && (INT64)capbudget > elapsed && !vsync_with_match_refresh)
{
I_SleepDuration(capbudget - (finishprecise - enterprecise));
}
}
// Capture the time once more to get the real delta time.
finishprecise = I_GetPreciseTime();
// Use the time before sleep for frameskip calculations:
// post-sleep time is literally being intentionally wasted
deltasecs = (double)((INT64)(finishprecise - enterprecise)) / I_GetPrecisePrecision();
deltatics = deltasecs * NEWTICRATE;
@ -1009,6 +998,23 @@ void D_SRB2Loop(void)
{
frameskip = 0;
}
if (!singletics)
{
INT64 elapsed = (INT64)(finishprecise - enterprecise);
// in the case of "match refresh rate" + vsync, don't sleep at all
const boolean vsync_with_match_refresh = cv_vidwait.value && cv_fpscap.value == 0;
if (elapsed > 0 && (INT64)capbudget > elapsed && !vsync_with_match_refresh)
{
I_SleepDuration(capbudget - (finishprecise - enterprecise));
}
}
// Capture the time once more to get the real delta time.
finishprecise = I_GetPreciseTime();
deltasecs = (double)((INT64)(finishprecise - enterprecise)) / I_GetPrecisePrecision();
deltatics = deltasecs * NEWTICRATE;
}
}

View file

@ -512,6 +512,15 @@ typedef enum
BOT_ITEM_PR__MAX
} botItemPriority_e;
typedef struct {
tic_t enter_tic, exit_tic;
tic_t zoom_in_speed, zoom_out_speed;
fixed_t dist;
angle_t pan;
fixed_t pan_speed; // in degrees
tic_t pan_accel, pan_back;
} sonicloopcamvars_t;
// player_t struct for loop state
typedef struct {
fixed_t radius;
@ -521,6 +530,7 @@ typedef struct {
vector2_t origin_shift;
vector2_t shift;
boolean flip;
sonicloopcamvars_t camera;
} sonicloopvars_t;
// player_t struct for power-ups
@ -726,6 +736,7 @@ struct player_t
UINT8 flamelength; // Flame Shield dash meter, number of segments
UINT16 ballhogcharge; // Ballhog charge up -- the higher this value, the more projectiles
boolean ballhogtap; // Ballhog released during charge: used to allow semirapid tapfire
UINT16 hyudorotimer; // Duration of the Hyudoro offroad effect itself
SINT8 stealingtimer; // if >0 you are stealing, if <0 you are being stolen from
@ -917,6 +928,8 @@ struct player_t
UINT8 instaWhipChargeLockout;
UINT8 guardCooldown;
UINT8 preventfailsafe; // Set when taking damage to prevent cheesing eggboxes
UINT8 handtimer;
angle_t besthanddirection;

View file

@ -55,6 +55,7 @@ struct thinker_t
// killough 11/98: count of how many other objects reference
// this one using pointers. Used for garbage collection.
INT32 references;
boolean cachable;
#ifdef PARANOIA
INT32 debug_mobjtype;

View file

@ -494,6 +494,11 @@ void F_RunWipe(UINT8 wipemode, UINT8 wipetype, boolean drawMenu, const char *col
WipeInAction = true;
wipe_scr = screens[0];
// FIXME: Wipes SUCK and drop input events for some reason, causing stuck gamepad inputs.
// It's better to ignore an intentional hold than to turn a tap into a phantom hold.
// (If you're removing this, remove the one after the inner loop too!)
memset(gamekeydown, 0, sizeof(gamekeydown));
// lastwipetic should either be 0 or the tic we last wiped
// on for fade-to-black
for (;;)
@ -581,6 +586,11 @@ void F_RunWipe(UINT8 wipemode, UINT8 wipetype, boolean drawMenu, const char *col
WipeInAction = false;
// FIXME: Wipes SUCK and drop input events for some reason, causing stuck gamepad inputs.
// It's better to ignore an intentional hold than to turn a tap into a phantom hold.
// (If you're removing this, remove the one before the inner loop too!)
memset(gamekeydown, 0, sizeof(gamekeydown));
if (fcolor)
{
Z_Free(fcolor);

View file

@ -8434,6 +8434,9 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
if (player->invincibilitytimer && onground == true)
player->invincibilitytimer--;
if (player->preventfailsafe)
player->preventfailsafe--;
if ((player->respawn.state == RESPAWNST_NONE) && player->growshrinktimer != 0)
{
if (player->growshrinktimer > 0 && onground == true)
@ -11017,7 +11020,8 @@ static void K_AirFailsafe(player_t *player)
const fixed_t thrustSpeed = 6*player->mo->scale; // 10*player->mo->scale
if (player->speed > maxSpeed // Above the max speed that you're allowed to use this technique.
|| player->respawn.state != RESPAWNST_NONE) // Respawning, you don't need this AND drop dash :V
|| player->respawn.state != RESPAWNST_NONE // Respawning, you don't need this AND drop dash :V
|| player->preventfailsafe) // You just got hit or interacted with something committal, no mashing for distance
{
player->pflags &= ~PF_AIRFAILSAFE;
return;
@ -11027,9 +11031,12 @@ static void K_AirFailsafe(player_t *player)
if (leveltime < introtime)
return;
if ((K_GetKartButtons(player) & BT_ACCELERATE) || K_GetForwardMove(player) != 0)
UINT8 buttons = K_GetKartButtons(player);
// Accel inputs queue air-failsafe for when they're released,
// as long as they're not part of a fastfall attempt.
if ((buttons & (BT_ACCELERATE|BT_BRAKE)) == BT_ACCELERATE || K_GetForwardMove(player) != 0)
{
// Queue up later
player->pflags |= PF_AIRFAILSAFE;
return;
}
@ -11734,8 +11741,19 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
{
INT32 ballhogmax = (player->itemamount) * BALLHOGINCREMENT;
if ((cmd->buttons & BT_ATTACK) && (player->pflags & PF_HOLDREADY)
&& (player->ballhogcharge < ballhogmax))
// This construct looks a little goofy, but we're basically just
// trying to prevent rapid taps from restarting a charge, while
// still allowing quick tapfire.
// (The player still has to pace their shots like this, it's not
// semi-auto, but that's probably kind of okay.)
if (player->ballhogcharge && !(cmd->buttons & BT_ATTACK))
player->ballhogtap = true;
if (player->ballhogcharge == 0)
player->ballhogtap = false;
boolean realcharge = (cmd->buttons & BT_ATTACK) && (player->pflags & PF_HOLDREADY) && (player->ballhogcharge < ballhogmax);
if ((realcharge && !player->ballhogtap) || (player->ballhogtap && player->ballhogcharge < BALLHOGINCREMENT))
{
player->ballhogcharge++;
if (player->ballhogcharge % BALLHOGINCREMENT == 0)

View file

@ -343,6 +343,8 @@ static int player_get(lua_State *L)
lua_pushinteger(L, plr->instaWhipCooldown);
else if (fastcmp(field,"guardCooldown"))
lua_pushinteger(L, plr->guardCooldown);
else if (fastcmp(field,"preventfailsafe"))
lua_pushinteger(L, plr->preventfailsafe);
/*
else if (fastcmp(field,"itemroulette"))
lua_pushinteger(L, plr->itemroulette);
@ -387,6 +389,8 @@ static int player_get(lua_State *L)
lua_pushinteger(L, plr->flamelength);
else if (fastcmp(field,"ballhogcharge"))
lua_pushinteger(L, plr->ballhogcharge);
else if (fastcmp(field,"ballhogtap"))
lua_pushinteger(L, plr->ballhogtap);
else if (fastcmp(field,"hyudorotimer"))
lua_pushinteger(L, plr->hyudorotimer);
else if (fastcmp(field,"stealingtimer"))
@ -831,6 +835,8 @@ static int player_set(lua_State *L)
plr->instaWhipCharge = luaL_checkinteger(L, 3);
else if (fastcmp(field,"guardCooldown"))
plr->guardCooldown = luaL_checkinteger(L, 3);
else if (fastcmp(field,"preventfailsafe"))
plr->preventfailsafe = luaL_checkinteger(L, 3);
/*
else if (fastcmp(field,"itemroulette"))
plr->itemroulette = luaL_checkinteger(L, 3);
@ -875,6 +881,8 @@ static int player_set(lua_State *L)
plr->flamelength = luaL_checkinteger(L, 3);
else if (fastcmp(field,"ballhogcharge"))
plr->ballhogcharge = luaL_checkinteger(L, 3);
else if (fastcmp(field,"ballhogtap"))
plr->ballhogtap = luaL_checkinteger(L, 3);
else if (fastcmp(field,"hyudorotimer"))
plr->hyudorotimer = luaL_checkinteger(L, 3);
else if (fastcmp(field,"stealingtimer"))

107
src/math/fixed.hpp Normal file
View file

@ -0,0 +1,107 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2023 by James Robert Roman
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef math_fixed_hpp
#define math_fixed_hpp
#include <type_traits>
#include "traits.hpp"
#include "../m_fixed.h"
namespace srb2::math
{
struct Fixed
{
static Fixed copysign(fixed_t x, fixed_t y) { return (x < 0) != (y < 0) ? -x : x; }
static Fixed hypot(fixed_t x, fixed_t y) { return FixedHypot(x, y); }
constexpr Fixed() : val_(0) {}
constexpr Fixed(fixed_t val) : val_(val) {}
template <typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
Fixed(T val) : val_(FloatToFixed(val)) {}
Fixed(const Fixed& b) = default;
Fixed& operator=(const Fixed& b) = default;
fixed_t value() const { return val_; }
int sign() const { return val_ < 0 ? -1 : 1; }
operator fixed_t() const { return val_; }
explicit operator float() const { return FixedToFloat(val_); }
Fixed& operator+=(const Fixed& b)
{
val_ += b.val_;
return *this;
}
Fixed& operator-=(const Fixed& b)
{
val_ -= b.val_;
return *this;
}
Fixed& operator*=(const Fixed& b)
{
val_ = FixedMul(val_, b.val_);
return *this;
}
Fixed& operator/=(const Fixed& b)
{
val_ = FixedDiv(val_, b.val_);
return *this;
}
Fixed operator-() const { return -val_; }
#define X(op) \
template <typename T> \
Fixed operator op(const T& b) const { return val_ op b; } \
Fixed operator op(const Fixed& b) const \
{ \
Fixed f{val_};\
f op##= b;\
return f;\
} \
template <typename T> \
Fixed& operator op##=(const T& b) \
{ \
val_ op##= b; \
return *this; \
}
X(+)
X(-)
X(*)
X(/)
#undef X
private:
fixed_t val_;
};
template <>
struct Traits<Fixed>
{
static constexpr Fixed kZero = 0;
static constexpr Fixed kUnit = FRACUNIT;
static constexpr auto copysign = Fixed::copysign;
static constexpr auto hypot = Fixed::hypot;
};
}; // namespace srb2::math
#endif/*math_fixed_hpp*/

102
src/math/line_equation.hpp Normal file
View file

@ -0,0 +1,102 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2023 by James Robert Roman
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef math_line_equation_hpp
#define math_line_equation_hpp
#include "fixed.hpp"
#include "line_segment.hpp"
#include "vec.hpp"
namespace srb2::math
{
template <typename T>
struct LineEquation
{
using vec2 = Vec2<T>;
using line_segment = LineSegment<T>;
// Fixed-point: shift value by this amount during
// multiplications and divisions to avoid overflows.
static constexpr std::enable_if_t<std::is_same_v<T, Fixed>, fixed_t> kF = 1024; // fixed_t, not Fixed
LineEquation() {}
LineEquation(const vec2& p, const vec2& d) : d_(d), m_(d.y / d.x), b_(p.y - (p.x * m())) {}
LineEquation(const line_segment& l) : LineEquation(l.a, l.b - l.a) {}
const vec2& d() const { return d_; }
T m() const { return m_; }
T b() const { return b_; }
T y(T x) const { return (m() * x) + b(); }
vec2 intersect(const LineEquation& q) const
{
T x = (b() - q.b()) / (q.m() - m());
return {x, y(x)};
}
protected:
vec2 d_{};
T m_{}, b_{};
};
template <>
inline LineEquation<Fixed>::LineEquation(const vec2& p, const vec2& d) :
d_(d), m_((d.y / d.x) / kF), b_((p.y / kF) - (p.x * m_))
{
}
template <>
inline Fixed LineEquation<Fixed>::m() const
{
return m_ * kF;
}
template <>
inline Fixed LineEquation<Fixed>::b() const
{
return b_ * kF;
}
template <>
inline Fixed LineEquation<Fixed>::y(Fixed x) const
{
return ((m_ * x) + b_) * kF;
}
template <>
inline LineEquation<Fixed>::vec2 LineEquation<Fixed>::intersect(const LineEquation& q) const
{
Fixed x = ((b_ - q.b_) / ((q.m_ - m_) * kF)) * kF;
return {x, y(x)};
}
template <typename T>
struct LineEquationX : LineEquation<T>
{
T x(T y) const { return (y - LineEquation<T>::b()) / LineEquation<T>::m(); }
};
template <>
struct LineEquationX<Fixed> : LineEquation<Fixed>
{
LineEquationX() {}
LineEquationX(const vec2& p, const vec2& d) : LineEquation(p, d), w_((d.x / d.y) / kF), a_((p.x / kF) - (p.y * w_)) {}
LineEquationX(const line_segment& l) : LineEquationX(l.a, l.b - l.a) {}
Fixed x(Fixed y) const { return ((w_ * y) + a_) * kF; }
protected:
Fixed w_{}, a_{};
};
}; // namespace srb2::math
#endif/*math_line_equation_hpp*/

43
src/math/line_segment.hpp Normal file
View file

@ -0,0 +1,43 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2023 by James Robert Roman
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef math_line_segment_hpp
#define math_line_segment_hpp
#include <algorithm>
#include <utility>
#include "vec.hpp"
namespace srb2::math
{
template <typename T>
struct LineSegment
{
using vec2 = Vec2<T>;
using view = std::pair<const vec2&, const vec2&>;
vec2 a, b;
LineSegment(vec2 a_, vec2 b_) : a(a_), b(b_) {}
template <typename U>
LineSegment(const LineSegment<U>& b) : LineSegment(b.a, b.b) {}
bool horizontal() const { return a.y == b.y; }
bool vertical() const { return a.x == b.x; }
view by_x() const { return std::minmax(a, b, [](auto& a, auto& b) { return a.x < b.x; }); }
view by_y() const { return std::minmax(a, b, [](auto& a, auto& b) { return a.y < b.y; }); }
};
}; // namespace srb2
#endif/*math_line_segment_hpp*/

34
src/math/traits.hpp Normal file
View file

@ -0,0 +1,34 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2023 by James Robert Roman
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef math_traits_hpp
#define math_traits_hpp
#include <cmath>
#include <type_traits>
namespace srb2::math
{
template <typename T, typename = void>
struct Traits;
template <typename T>
struct Traits<T, std::enable_if_t<std::is_floating_point_v<T>>>
{
static constexpr T kZero = 0.0;
static constexpr T kUnit = 1.0;
static T copysign(T x, T y) { return std::copysign(x, y); }
static T hypot(T x, T y) { return std::hypot(x, y); }
};
}; // namespace srb2::math
#endif/*math_traits_hpp*/

84
src/math/vec.hpp Normal file
View file

@ -0,0 +1,84 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2023 by James Robert Roman
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef math_vec_hpp
#define math_vec_hpp
#include <type_traits>
#include "traits.hpp"
namespace srb2::math
{
template <typename T>
struct Vec2
{
T x, y;
Vec2() : x{}, y{} {}
Vec2(T x_, T y_) : x(x_), y(y_) {}
Vec2(T z) : x(z), y(z) {}
template <typename U>
Vec2(const Vec2<U>& b) : Vec2(b.x, b.y) {}
T magnitude() const { return Traits<T>::hypot(x, y); }
Vec2 normal() const { return {-y, x}; }
#define X(op) \
Vec2& operator op##=(const Vec2& b) \
{ \
x op##= b.x; \
y op##= b.y; \
return *this; \
} \
Vec2 operator op(const Vec2& b) const { return Vec2(x op b.x, y op b.y); } \
X(+)
X(-)
X(*)
X(/)
#undef X
Vec2 operator-() const { return Vec2(-x, -y); }
};
template <typename>
struct is_vec2 : std::false_type {};
template <typename T>
struct is_vec2<Vec2<T>> : std::true_type {};
template <typename T>
inline constexpr bool is_vec2_v = is_vec2<T>::value;
#define X(op) \
template <typename T, typename U, std::enable_if_t<!is_vec2_v<T>, bool> = true> \
Vec2<T> operator op(const T& a, const Vec2<U>& b) \
{ \
return Vec2 {a} op Vec2<T> {b}; \
} \
template <typename T, typename U, std::enable_if_t<!is_vec2_v<U>, bool> = true> \
Vec2<U> operator op(const Vec2<T>& a, const U& b) \
{ \
return Vec2<U> {a} op Vec2 {b}; \
} \
X(+)
X(-)
X(*)
X(/)
#undef X
}; // namespace srb2::math
#endif/*math_vec_hpp*/

View file

@ -295,6 +295,7 @@ Obj_LoopEndpointCollide
{
player_t *player = toucher->player;
sonicloopvars_t *s = &player->loop;
sonicloopcamvars_t *cam = &s->camera;
mobj_t *anchor = end_anchor(end);
mobj_t *center = anchor ? anchor_center(anchor) : NULL;
@ -352,6 +353,30 @@ Obj_LoopEndpointCollide
s->flip = center_has_flip(center);
cam->enter_tic = leveltime;
cam->exit_tic = INFTICS;
if (center->thing_args[4]) // is camera distance set?
{
cam->zoom_out_speed = center->thing_args[2];
cam->zoom_in_speed = center->thing_args[3];
cam->dist = center->thing_args[4] * FRACUNIT;
cam->pan = FixedAngle(center->thing_args[5] * FRACUNIT);
cam->pan_speed = center->thing_args[6] * FRACUNIT;
cam->pan_accel = center->thing_args[7];
cam->pan_back = center->thing_args[8];
}
else
{
cam->zoom_out_speed = 20;
cam->zoom_in_speed = 60;
cam->dist = radius;
cam->pan = ANGLE_22h;
cam->pan_speed = 6*FRACUNIT;
cam->pan_accel = 10;
cam->pan_back = 40;
}
player->speed =
3 * (player->speed + toucher->momz) / 2;

View file

@ -3054,6 +3054,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
player->fastfall = 0;
player->ringboost = 0;
player->glanceDir = 0;
player->preventfailsafe = TICRATE*3;
player->pflags &= ~PF_GAINAX;
if (player->spectator == false && !(player->charflags & SF_IRONMAN))

View file

@ -77,6 +77,7 @@ typedef enum
NUM_THINKERLISTS
} thinklistnum_t; /**< Thinker lists. */
extern thinker_t thlist[];
extern mobj_t *mobjcache;
void P_InitThinkers(void);
void P_AddThinker(const thinklistnum_t n, thinker_t *thinker);
@ -386,9 +387,16 @@ struct tm_t
// so missiles don't explode against sky hack walls
line_t *ceilingline;
// set by PIT_CheckLine() for any line that stopped the PIT_CheckLine()
// that is, for any line which is 'solid'
line_t *blockingline;
// P_CheckPosition: this position blocks movement
boolean blocking;
// P_CheckPosition: set this before each call to
// P_CheckPosition to enable a line sweep on collided
// lines
boolean sweep;
// sweep: max step up at tm.x, tm.y
fixed_t maxstep;
};
extern tm_t tm;
@ -414,6 +422,7 @@ struct TryMoveResult_t
boolean success;
line_t *line;
mobj_t *mo;
vector2_t normal;
};
boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y, TryMoveResult_t *result);
@ -421,6 +430,10 @@ boolean P_CheckMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff, T
boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff, TryMoveResult_t *result);
boolean P_SceneryTryMove(mobj_t *thing, fixed_t x, fixed_t y, TryMoveResult_t *result);
void P_TestLine(line_t *ld);
void P_ClearTestLines(void);
line_t *P_SweepTestLines(fixed_t ax, fixed_t ay, fixed_t bx, fixed_t by, fixed_t r, vector2_t *return_normal);
boolean P_IsLineBlocking(const line_t *ld, const mobj_t *thing);
boolean P_IsLineTripWire(const line_t *ld);
boolean P_CheckCameraPosition(fixed_t x, fixed_t y, camera_t *thiscam);

View file

@ -37,6 +37,7 @@ void P_HaltPlayerOrbit(player_t *player)
player->mo->flags &= ~(MF_NOCLIPHEIGHT);
player->loop.radius = 0;
player->loop.camera.exit_tic = leveltime;
}
void P_ExitPlayerOrbit(player_t *player)

View file

@ -1760,7 +1760,6 @@ static BlockItReturn_t PIT_CheckCameraLine(line_t *ld)
// could be crossed in either order.
// this line is out of the if so upper and lower textures can be hit by a splat
tm.blockingline = ld;
if (!ld->backsector) // one sided line
{
if (P_PointOnLineSide(mapcampointer->x, mapcampointer->y, ld))
@ -1831,6 +1830,22 @@ boolean P_IsLineTripWire(const line_t *ld)
return ld->tripwire;
}
static boolean P_UsingStepUp(mobj_t *thing)
{
if (thing->flags & MF_NOCLIP)
{
return false;
}
// orbits have no collision
if (thing->player && thing->player->loop.radius)
{
return false;
}
return true;
}
//
// PIT_CheckLine
// Adjusts tm.floorz and tm.ceilingz as lines are contacted
@ -1888,14 +1903,20 @@ static BlockItReturn_t PIT_CheckLine(line_t *ld)
// could be crossed in either order.
// this line is out of the if so upper and lower textures can be hit by a splat
tm.blockingline = ld;
{
UINT8 shouldCollide = LUA_HookMobjLineCollide(tm.thing, tm.blockingline); // checks hook for thing's type
UINT8 shouldCollide = LUA_HookMobjLineCollide(tm.thing, ld); // checks hook for thing's type
if (P_MobjWasRemoved(tm.thing))
return BMIT_CONTINUE; // one of them was removed???
if (shouldCollide == 1)
return BMIT_ABORT; // force collide
{
if (tm.sweep)
{
P_TestLine(ld);
}
tm.blocking = true; // force collide
return BMIT_CONTINUE;
}
else if (shouldCollide == 2)
return BMIT_CONTINUE; // force no collide
}
@ -1904,15 +1925,55 @@ static BlockItReturn_t PIT_CheckLine(line_t *ld)
{
if (P_PointOnLineSide(tm.thing->x, tm.thing->y, ld))
return BMIT_CONTINUE; // don't hit the back side
return BMIT_ABORT;
if (tm.sweep)
{
P_TestLine(ld);
}
tm.blocking = true;
return BMIT_CONTINUE;
}
if (P_IsLineBlocking(ld, tm.thing))
return BMIT_ABORT;
{
if (tm.sweep)
{
P_TestLine(ld);
}
tm.blocking = true;
return BMIT_CONTINUE;
}
// set openrange, opentop, openbottom
P_LineOpening(ld, tm.thing, &open);
if (tm.sweep && P_UsingStepUp(tm.thing))
{
// copied from P_TryMove
// TODO: refactor this into one place
if (open.range < tm.thing->height)
{
P_TestLine(ld);
}
else if (tm.maxstep > 0)
{
if (tm.thing->z < open.floor)
{
if (open.floorstep > tm.maxstep)
{
P_TestLine(ld);
}
}
else if (open.ceiling < tm.thing->z + tm.thing->height)
{
if (open.ceilingstep > tm.maxstep)
{
P_TestLine(ld);
}
}
}
}
// adjust floor / ceiling heights
if (open.ceiling < tm.ceilingz)
{
@ -2032,7 +2093,8 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y, TryMoveResult_t *re
tm.bbox[BOXLEFT] = x - tm.thing->radius;
newsubsec = R_PointInSubsector(x, y);
tm.ceilingline = tm.blockingline = NULL;
tm.ceilingline = NULL;
tm.blocking = false;
// The base floor / ceiling is from the subsector
// that contains the point.
@ -2304,23 +2366,33 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y, TryMoveResult_t *re
validcount++;
P_ClearTestLines();
// check lines
for (bx = xl; bx <= xh; bx++)
{
for (by = yl; by <= yh; by++)
{
if (!P_BlockLinesIterator(bx, by, PIT_CheckLine))
{
blockval = false;
}
P_BlockLinesIterator(bx, by, PIT_CheckLine);
}
}
if (tm.blocking)
{
blockval = false;
}
if (result != NULL)
{
result->line = tm.blockingline;
result->line = NULL;
result->mo = tm.hitthing;
}
else
{
P_ClearTestLines();
}
tm.sweep = false;
return blockval;
}
@ -2369,7 +2441,7 @@ boolean P_CheckCameraPosition(fixed_t x, fixed_t y, camera_t *thiscam)
tm.bbox[BOXLEFT] = x - thiscam->radius;
newsubsec = R_PointInSubsector(x, y);
tm.ceilingline = tm.blockingline = NULL;
tm.ceilingline = NULL;
mapcampointer = thiscam;
@ -2743,22 +2815,6 @@ fixed_t P_GetThingStepUp(mobj_t *thing, fixed_t destX, fixed_t destY)
return maxstep;
}
static boolean P_UsingStepUp(mobj_t *thing)
{
if (thing->flags & MF_NOCLIP)
{
return false;
}
// orbits have no collision
if (thing->player && thing->player->loop.radius)
{
return false;
}
return true;
}
static boolean
increment_move
( mobj_t * thing,
@ -2811,7 +2867,29 @@ increment_move
tryy = y;
}
if (!P_CheckPosition(thing, tryx, tryy, result))
if (P_UsingStepUp(thing))
{
tm.maxstep = P_GetThingStepUp(thing, tryx, tryy);
}
if (result)
{
tm.sweep = true;
}
boolean move_ok = P_CheckPosition(thing, tryx, tryy, result);
if (P_MobjWasRemoved(thing))
{
return false;
}
if (result)
{
result->line = P_SweepTestLines(thing->x, thing->y, x, y, thing->radius, &result->normal);
}
if (!move_ok)
{
return false; // solid wall or thing
}
@ -3456,30 +3534,27 @@ static void P_HitSlideLine(line_t *ld)
//
// HitBounceLine, for players
//
static void P_PlayerHitBounceLine(line_t *ld)
static void P_PlayerHitBounceLine(line_t *ld, vector2_t* normal)
{
INT32 side;
angle_t lineangle;
fixed_t movelen;
fixed_t x, y;
side = P_PointOnLineSide(slidemo->x, slidemo->y, ld);
lineangle = ld->angle - ANGLE_90;
if (side == 1)
lineangle += ANGLE_180;
lineangle >>= ANGLETOFINESHIFT;
movelen = P_AproxDistance(tmxmove, tmymove);
if (slidemo->player && movelen < (15*mapobjectscale))
movelen = (15*mapobjectscale);
x = FixedMul(movelen, FINECOSINE(lineangle));
y = FixedMul(movelen, FINESINE(lineangle));
if (!ld)
{
angle_t th = R_PointToAngle2(0, 0, tmxmove, tmymove);
normal->x = -FCOS(th);
normal->y = -FSIN(th);
}
if (P_IsLineTripWire(ld))
x = FixedMul(movelen, normal->x);
y = FixedMul(movelen, normal->y);
if (ld && P_IsLineTripWire(ld))
{
tmxmove = x * 4;
tmymove = y * 4;
@ -3948,6 +4023,8 @@ papercollision:
static void P_BouncePlayerMove(mobj_t *mo, TryMoveResult_t *result)
{
extern consvar_t cv_showgremlins;
fixed_t mmomx = 0, mmomy = 0;
fixed_t oldmomx = mo->momx, oldmomy = mo->momy;
@ -3972,8 +4049,23 @@ static void P_BouncePlayerMove(mobj_t *mo, TryMoveResult_t *result)
slidemo = mo;
bestslideline = result->line;
if (bestslideline == NULL)
return;
if (bestslideline == NULL && cv_showgremlins.value)
{
// debug
mobj_t*x = P_SpawnMobj(mo->x, mo->y, mo->z, MT_THOK);
x->frame = FF_FULLBRIGHT | FF_ADD;
x->renderflags = RF_ALWAYSONTOP;
x->color = SKINCOLOR_RED;
CONS_Printf(
"GREMLIN: leveltime=%u x=%f y=%f z=%f angle=%f\n",
leveltime,
FixedToFloat(mo->x),
FixedToFloat(mo->y),
FixedToFloat(mo->z),
AngleToFloat(R_PointToAngle2(0, 0, oldmomx, oldmomy))
);
}
if (mo->eflags & MFE_JUSTBOUNCEDWALL) // Stronger push-out
{
@ -3986,7 +4078,7 @@ static void P_BouncePlayerMove(mobj_t *mo, TryMoveResult_t *result)
tmymove = FixedMul(mmomy, (FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
}
if (P_IsLineTripWire(bestslideline))
if (bestslideline && P_IsLineTripWire(bestslideline))
{
// TRIPWIRE CANNOT BE MADE NONBOUNCY
K_ApplyTripWire(mo->player, TRIPSTATE_BLOCKED);
@ -4004,7 +4096,7 @@ static void P_BouncePlayerMove(mobj_t *mo, TryMoveResult_t *result)
K_SpawnBumpEffect(mo);
}
P_PlayerHitBounceLine(bestslideline);
P_PlayerHitBounceLine(bestslideline, &result->normal);
mo->eflags |= MFE_JUSTBOUNCEDWALL;
mo->momx = tmxmove;
@ -4012,7 +4104,7 @@ static void P_BouncePlayerMove(mobj_t *mo, TryMoveResult_t *result)
mo->player->cmomx = tmxmove;
mo->player->cmomy = tmymove;
if (!P_IsLineTripWire(bestslideline))
if (!bestslideline || !P_IsLineTripWire(bestslideline))
{
if (!P_TryMove(mo, mo->x + tmxmove, mo->y + tmymove, true, NULL))
{

View file

@ -63,6 +63,8 @@ mobj_t *waypointcap = NULL;
// general purpose.
mobj_t *trackercap = NULL;
mobj_t *mobjcache = NULL;
void P_InitCachedActions(void)
{
actioncachehead.prev = actioncachehead.next = &actioncachehead;
@ -1669,7 +1671,7 @@ void P_XYMovement(mobj_t *mo)
// blocked move
moved = false;
if (LUA_HookMobjMoveBlocked(mo, tm.hitthing, tm.blockingline))
if (LUA_HookMobjMoveBlocked(mo, tm.hitthing, result.line))
{
if (P_MobjWasRemoved(mo))
return;
@ -1677,7 +1679,7 @@ void P_XYMovement(mobj_t *mo)
else if (P_MobjWasRemoved(mo))
return;
P_PushSpecialLine(tm.blockingline, mo);
P_PushSpecialLine(result.line, mo);
if (mo->flags & MF_MISSILE)
{
@ -11051,7 +11053,16 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
type = MT_RAY;
}
mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL);
if (mobjcache != NULL)
{
mobj = mobjcache;
mobjcache = mobjcache->hnext;
memset(mobj, 0, sizeof(*mobj));
}
else
{
mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL);
}
// this is officially a mobj, declared as soon as possible.
mobj->thinker.function.acp1 = (actionf_p1)P_MobjThinker;
@ -11959,7 +11970,9 @@ void P_RemoveMobj(mobj_t *mobj)
INT32 prevreferences;
if (!mobj->thinker.references)
{
Z_Free(mobj); // No refrrences? Can be removed immediately! :D
// no references, dump it directly in the mobj cache
mobj->hnext = mobjcache;
mobjcache = mobj;
return;
}

View file

@ -485,6 +485,7 @@ static void P_NetArchivePlayers(savebuffer_t *save)
WRITEUINT8(save->p, players[i].flamelength);
WRITEUINT16(save->p, players[i].ballhogcharge);
WRITEUINT8(save->p, players[i].ballhogtap);
WRITEUINT16(save->p, players[i].hyudorotimer);
WRITESINT8(save->p, players[i].stealingtimer);
@ -557,6 +558,8 @@ static void P_NetArchivePlayers(savebuffer_t *save)
WRITEUINT8(save->p, players[i].instaWhipCooldown);
WRITEUINT8(save->p, players[i].guardCooldown);
WRITEUINT8(save->p, players[i].preventfailsafe);
WRITEUINT8(save->p, players[i].handtimer);
WRITEANGLE(save->p, players[i].besthanddirection);
@ -692,6 +695,17 @@ static void P_NetArchivePlayers(savebuffer_t *save)
WRITEFIXED(save->p, players[i].loop.shift.y);
WRITEUINT8(save->p, players[i].loop.flip);
// sonicloopcamvars_t
WRITEUINT32(save->p, players[i].loop.camera.enter_tic);
WRITEUINT32(save->p, players[i].loop.camera.exit_tic);
WRITEUINT32(save->p, players[i].loop.camera.zoom_in_speed);
WRITEUINT32(save->p, players[i].loop.camera.zoom_out_speed);
WRITEFIXED(save->p, players[i].loop.camera.dist);
WRITEANGLE(save->p, players[i].loop.camera.pan);
WRITEFIXED(save->p, players[i].loop.camera.pan_speed);
WRITEUINT32(save->p, players[i].loop.camera.pan_accel);
WRITEUINT32(save->p, players[i].loop.camera.pan_back);
// ACS has read access to this, so it has to be net-communicated.
// It is the ONLY roundcondition that is sent over the wire and I'd like it to stay that way.
WRITEUINT32(save->p, players[i].roundconditions.unlocktriggers);
@ -1006,6 +1020,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
players[i].flamelength = READUINT8(save->p);
players[i].ballhogcharge = READUINT16(save->p);
players[i].ballhogtap = READUINT8(save->p);
players[i].hyudorotimer = READUINT16(save->p);
players[i].stealingtimer = READSINT8(save->p);
@ -1078,6 +1093,8 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
players[i].instaWhipCooldown = READUINT8(save->p);
players[i].guardCooldown = READUINT8(save->p);
players[i].preventfailsafe = READUINT8(save->p);
players[i].handtimer = READUINT8(save->p);
players[i].besthanddirection = READANGLE(save->p);
@ -1224,6 +1241,17 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
players[i].loop.shift.y = READFIXED(save->p);
players[i].loop.flip = READUINT8(save->p);
// sonicloopcamvars_t
players[i].loop.camera.enter_tic = READUINT32(save->p);
players[i].loop.camera.exit_tic = READUINT32(save->p);
players[i].loop.camera.zoom_in_speed = READUINT32(save->p);
players[i].loop.camera.zoom_out_speed = READUINT32(save->p);
players[i].loop.camera.dist = READFIXED(save->p);
players[i].loop.camera.pan = READANGLE(save->p);
players[i].loop.camera.pan_speed = READFIXED(save->p);
players[i].loop.camera.pan_accel = READUINT32(save->p);
players[i].loop.camera.pan_back = READUINT32(save->p);
// ACS has read access to this, so it has to be net-communicated.
// It is the ONLY roundcondition that is sent over the wire and I'd like it to stay that way.
players[i].roundconditions.unlocktriggers = READUINT32(save->p);

View file

@ -8363,6 +8363,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
Patch_FreeTag(PU_PATCH_LOWPRIORITY);
Patch_FreeTag(PU_PATCH_ROTATED);
Z_FreeTags(PU_LEVEL, PU_PURGELEVEL - 1);
mobjcache = NULL;
R_InitializeLevelInterpolators();

271
src/p_sweep.cpp Normal file
View file

@ -0,0 +1,271 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2023 by James Robert Roman
//
// 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 <optional>
#include "p_sweep.hpp"
using namespace srb2::math;
using namespace srb2::sweep;
Result SlopeAABBvsLine::vs_slope(const line_segment& l) const
{
auto [a, b] = l.by_x(); // left, right
LineEquation<unit> ql{l};
unit ls = copysign(kUnit, ql.m());
auto hit = [&](const vec2& k, unit xr, unit x, const vec2& n) -> Contact
{
std::optional<vec2> k2;
if (l.horizontal())
{
// Horizontal line: create second contact point on opposite corner.
// TODO: avoid duplicate point
k2 = vec2(std::clamp(x + xr, a.x, b.x), k.y);
}
return {time(x), n, k, k2};
};
auto slide = [&](const vec2& k, const vec2& s) -> std::optional<Contact>
{
vec2 kf = k * s;
vec2 r = r_ * s;
vec2 p = k - r;
// Slide vertically along AABB left/right edge.
unit f = q_.y(p.x) * s.y;
if (f - r_ > kf.y)
{
// Out of bounds detection.
// This should never slide in front.
// If it does, there was never a hit.
return {};
}
if (f + r_ < kf.y)
{
// Slid behind contact point.
// Try sliding horizontally along AABB top/bottom
// edge.
if (q_.m() == kZero)
{
// Sweep is horizontal.
// It is impossible to slide against a line's
// end by the X axis because the line segment
// lies on that axis.
return {};
}
p.x = q_.x(p.y);
f = p.x * s.x;
if (f - r_ > kf.x)
{
// Slid beyond contact point.
return {};
}
if (f + r_ < kf.x)
{
// Out of bounds detection.
// This should never slide behind.
// If it does, there was never a hit.
return {};
}
return hit(k, r.x, p.x, {kZero, -s.y});
}
return hit(k, r.x, p.x, {-s.x, kZero});
};
// xrs.x = x radius
// xrs.y = x sign
auto bind = [&](const vec2& k, const vec2& xrs, unit ns) -> std::optional<Contact>
{
if (k.x < a.x)
{
return slide(a, {xrs.y, ls});
}
if (k.x > b.x)
{
return slide(b, {xrs.y, -ls});
}
return hit(k, xrs.x, k.x + xrs.x, normal(l) * ns);
};
if (ql.m() == q_.m())
{
// Parallel lines can only cross at the ends.
vec2 s{kUnit, ls};
return order(slide(a, s), slide(b, -s), ds_.x);
}
vec2 i = ql.intersect(q_);
// Compare slopes to determine if ray is moving upward or
// downward into line.
// For a positive line, AABB top left corner hits the
// line first if the ray is moving upward.
// Swap diagonal corners to bottom right if moving
// downward.
unit ys = q_.m() * ds_.x < ql.m() * ds_.x ? -kUnit : kUnit;
unit yr = r_ * ys;
// Swap left/right corners if line is negative.
unit xr = yr * ls;
// Intersection as if ray were offset -r, +r.
vec2 v = [&]
{
unit y = (q_.m() * xr) + yr;
unit x = y / (ql.m() - q_.m());
return vec2 {x, (x * q_.m()) + y};
}();
// Find the intersection along diagonally oppposing AABB
// corners.
vec2 xrs{xr, ds_.x};
return {bind(i + v, xrs, -ys), bind(i - v, -xrs, -ys)};
}
// TODO: Comments. Bitch.
Result SlopeAABBvsLine::vs_vertical(const line_segment& l) const
{
auto [a, b] = l.by_y(); // bottom, top
auto hit = [&](const vec2& p, std::optional<vec2> q, unit x, const vec2& n) -> Contact { return {time(x), n, p, q}; };
auto bind = [&](const vec2& k, const vec2& a, const vec2& b, const vec2& s, auto limit) -> std::optional<Contact>
{
vec2 r = r_ * s;
vec2 af = a * s;
unit kyf = k.y * s.y;
if (kyf + r_ < af.y)
{
if (q_.m() == kZero)
{
return {};
}
unit x = q_.x(a.y - r.y);
if ((x * s.x) - r_ > af.x)
{
return {};
}
return hit(a, {}, x, {kZero, -s.y});
}
// TODO: avoid duplicate point
vec2 k2{k.x, limit(k.y - r.y, a.y)};
unit byf = b.y * s.y;
vec2 n{-s.x, kZero};
if (kyf + r_ > byf)
{
if (kyf - r_ > byf)
{
return {};
}
return hit(b, k2, k.x - r.x, n);
}
return hit(vec2(k.x, k.y + r.y), k2, k.x - r.x, n);
};
vec2 i{a.x, q_.y(a.x)};
vec2 v{kZero, q_.m() * r_ * ds_.x * ds_.y};
vec2 s = ds_ * ds_.y;
// Damn you, template overloads!
auto min = [](unit x, unit y) { return std::min(x, y); };
auto max = [](unit x, unit y) { return std::max(x, y); };
return order(bind(i - v, a, b, s, max), bind(i + v, b, a, -s, min), ds_.y);
}
Result VerticalAABBvsLine::vs_slope(const line_segment& l) const
{
auto [a, b] = l.by_x(); // left, right
LineEquation<unit> ql{l};
auto hit = [&](const vec2& k, unit xr, unit y, const vec2& n) -> Contact
{
std::optional<vec2> k2;
if (l.horizontal())
{
// Horizontal line: create second contact point on opposite corner.
// TODO: avoid duplicate point
k2 = vec2(std::clamp(x_ + xr, a.x, b.x), k.y);
}
return {time(y), n, k, k2};
};
auto bind = [&](const vec2& a, const vec2& b, const vec2& s) -> std::optional<Contact>
{
vec2 r = r_ * s;
unit xf = x_ * s.x;
if (xf - r_ > b.x * s.x)
{
return {};
}
unit axf = a.x * s.x;
if (xf - r_ < axf)
{
if (xf + r_ < axf)
{
return {};
}
return hit(a, r.x, a.y - r.y, {kZero, -s.y});
}
vec2 i{x_, ql.y(x_)};
vec2 v{r.x, ql.m() * r.x};
vec2 k = i - v;
return hit(k, r.x, k.y - r.y, normal(l) * -s.y);
};
unit mys = copysign(kUnit, ql.m() * ds_.y);
vec2 s{kUnit, ds_.y * mys};
return order(bind(a, b, s), bind(b, a, -s), mys);
}
Result VerticalAABBvsLine::vs_vertical(const line_segment& l) const
{
// Box does not overlap Y plane.
if (x_ + r_ < l.a.x || x_ - r_ > l.a.x)
{
return {};
}
auto [a, b] = l.by_y(); // bottom, top
auto hit = [&](const vec2& k, unit yr) -> Contact { return {time(k.y + yr), {kZero, -ds_.y}, k}; };
// Offset away from line ends.
// Contacts are opposite when swept downward.
return order(hit(a, -r_), hit(b, r_), ds_.y);
}

131
src/p_sweep.hpp Normal file
View file

@ -0,0 +1,131 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2023 by James Robert Roman
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef p_sweep_hpp
#define p_sweep_hpp
#include <optional>
#include <variant>
#include "math/fixed.hpp"
#include "math/line_equation.hpp"
#include "math/line_segment.hpp"
#include "math/vec.hpp"
namespace srb2::sweep
{
using unit = math::Fixed;
using vec2 = math::Vec2<unit>;
using line_segment = math::LineSegment<unit>;
struct Contact
{
unit z; // time
vec2 n; // normal TODO REMOVE duplicate for each contact
vec2 p; // contact point 1
std::optional<vec2> q; // AABBvsLine: contact point 2
};
struct Result
{
std::optional<Contact> hit, exit; // TODO result itself should be optional, not each contact
};
namespace detail
{
template <typename T>
struct BaseAABBvsLine : protected srb2::math::Traits<unit>
{
public:
Result operator()(const line_segment& l) const
{
auto derived = static_cast<const T*>(this);
return l.vertical() ? derived->vs_vertical(l) : derived->vs_slope(l);
}
protected:
unit r_; // AABB radius
vec2 ds_; // sweep direction signs
BaseAABBvsLine(unit r, const vec2& d, unit pz, unit dz) :
r_(r), ds_(copysign(kUnit, d.x), copysign(kUnit, d.y)), t_(pz, dz) {}
unit time(unit x) const { return (x - t_.x) / t_.y; }
static Result order(std::optional<Contact>&& t1, std::optional<Contact>&& t2, unit s)
{
return s > kZero ? Result {t1, t2} : Result {t2, t1};
}
static vec2 normal(const vec2& v)
{
// Normalize vector so that x is positive -- normal always points up.
return v.normal() * (copysign(kUnit, v.x) / v.magnitude());
}
static vec2 normal(const line_segment& l) { return normal(l.b - l.a); }
private:
vec2 t_; // origin and length for calculating time
};
}; // namespace detail
// Sweep can be represented as y = mx + b
struct SlopeAABBvsLine : detail::BaseAABBvsLine<SlopeAABBvsLine>
{
SlopeAABBvsLine(unit r, const line_segment& l) : SlopeAABBvsLine(r, l.a, l.b - l.a) {}
Result vs_slope(const line_segment& l) const;
Result vs_vertical(const line_segment& l) const;
private:
math::LineEquationX<unit> q_;
SlopeAABBvsLine(unit r, const vec2& p, const vec2& d) : BaseAABBvsLine(r, d, p.x, d.x), q_(p, d) {}
};
// Sweep is vertical
struct VerticalAABBvsLine : detail::BaseAABBvsLine<VerticalAABBvsLine>
{
VerticalAABBvsLine(unit r, const line_segment& l) : VerticalAABBvsLine(r, l.a, l.b - l.a) {}
Result vs_slope(const line_segment& l) const;
Result vs_vertical(const line_segment& l) const;
private:
unit x_;
VerticalAABBvsLine(unit r, const vec2& p, const vec2& d) : BaseAABBvsLine(r, d, p.y, d.y), x_(p.x) {}
};
struct AABBvsLine
{
AABBvsLine(unit r, const line_segment& l) :
var_(l.vertical() ? var_t {VerticalAABBvsLine(r, l)} : var_t {SlopeAABBvsLine(r, l)})
{
}
Result operator()(const line_segment& l) const
{
Result rs;
std::visit([&](auto& sweeper) { rs = sweeper(l); }, var_);
return rs;
}
private:
using var_t = std::variant<SlopeAABBvsLine, VerticalAABBvsLine>;
var_t var_;
};
}; // namespace srb2::sweep
#endif/*p_sweep_hpp*/

78
src/p_test.cpp Normal file
View file

@ -0,0 +1,78 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2023 by James Robert Roman
//
// 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 <optional>
#include <vector>
#include "math/fixed.hpp"
#include "p_sweep.hpp"
#include "p_local.h"
namespace
{
std::vector<line_t*> g_lines;
};
void P_TestLine(line_t* ld)
{
g_lines.emplace_back(ld);
}
line_t* P_SweepTestLines(fixed_t ax, fixed_t ay, fixed_t bx, fixed_t by, fixed_t r, vector2_t* return_normal)
{
using namespace srb2::math;
using namespace srb2::sweep;
struct Collision
{
unit z;
vec2 normal;
line_t* ld;
bool operator<(const Collision& b) const { return z < b.z; }
};
std::optional<Collision> collision;
LineSegment<Fixed> l{{ax, ay}, {bx, by}};
AABBvsLine sweep{r, l};
for (line_t* ld : g_lines)
{
LineSegment<Fixed> ls{{ld->v1->x, ld->v1->y}, {ld->v2->x, ld->v2->y}};
Result rs = sweep(ls);
if (rs.hit)
{
if (!collision || rs.hit->z < collision->z)
{
collision = {rs.hit->z, rs.hit->n, ld};
}
}
}
g_lines.clear();
if (!collision)
{
return nullptr;
}
return_normal->x = Fixed {collision->normal.x};
return_normal->y = Fixed {collision->normal.y};
return collision->ld;
}
void P_ClearTestLines(void)
{
g_lines.clear();
}

View file

@ -310,6 +310,7 @@ void P_AddThinker(const thinklistnum_t n, thinker_t *thinker)
thlist[n].prev = thinker;
thinker->references = 0; // killough 11/98: init reference counter to 0
thinker->cachable = n == THINK_MOBJ;
#ifdef PARANOIA
thinker->debug_mobjtype = MT_NULL;
@ -427,7 +428,16 @@ void P_UnlinkThinker(thinker_t *thinker)
I_Assert(thinker->references == 0);
(next->prev = thinker->prev)->next = next;
Z_Free(thinker);
if (thinker->cachable)
{
// put cachable thinkers in the mobj cache, so we can avoid allocations
((mobj_t *)thinker)->hnext = mobjcache;
mobjcache = (mobj_t *)thinker;
}
else
{
Z_Free(thinker);
}
}
//
@ -708,6 +718,9 @@ static inline void P_DeviceRumbleTick(void)
if (player->mo == NULL)
continue;
if (player->exiting)
continue;
if ((player->mo->eflags & MFE_DAMAGEHITLAG) && player->mo->hitlag)
{
low = high = 65536 / 2;

View file

@ -3088,6 +3088,10 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
fixed_t scaleDiff;
fixed_t cameraScale = mapobjectscale;
sonicloopcamvars_t *loop = &player->loop.camera;
tic_t loop_out = leveltime - loop->enter_tic;
tic_t loop_in = max(leveltime, loop->exit_tic) - loop->exit_tic;
thiscam->old_x = thiscam->x;
thiscam->old_y = thiscam->y;
thiscam->old_z = thiscam->z;
@ -3212,6 +3216,58 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
camdist = FixedMul(cv_cam_dist[num].value, cameraScale);
camheight = FixedMul(cv_cam_height[num].value, cameraScale);
if (loop_in < loop->zoom_in_speed)
{
fixed_t f = loop_out < loop->zoom_out_speed
? (loop_out * FRACUNIT) / loop->zoom_out_speed
: FRACUNIT - ((loop_in * FRACUNIT) / loop->zoom_in_speed);
camspeed -= FixedMul(f, camspeed - (FRACUNIT/10));
camdist += FixedMul(f, loop->dist);
}
if (loop_in < max(loop->pan_back, 1))
{
fixed_t f = (loop_in * FRACUNIT) / max(loop->pan_back, 1);
fixed_t dx = mo->x - thiscam->x;
fixed_t dy = mo->y - thiscam->y;
angle_t th = R_PointToAngle2(0, 0, dx, dy);
fixed_t d = AngleFixed(focusangle - th);
if (d > 180*FRACUNIT)
{
d -= (360*FRACUNIT);
}
focusangle = th + FixedAngle(FixedMul(f, d));
if (loop_in == 0)
{
focusaiming = R_PointToAngle2(0, thiscam->z, FixedHypot(dx, dy), mo->z);
}
}
if (loop_in == 0)
{
tic_t accel = max(loop->pan_accel, 1);
fixed_t f = (min(loop_out, accel) * FRACUNIT) / accel;
INT32 turn = AngleDeltaSigned(focusangle, player->loop.yaw - loop->pan);
INT32 turnspeed = FixedAngle(FixedMul(f, loop->pan_speed));
if (turn > turnspeed)
{
if (turn < ANGLE_90)
{
turnspeed = -(turnspeed);
}
focusangle += turnspeed;
}
}
if (timeover)
{
const INT32 timeovercam = max(0, min(180, (player->karthud[khud_timeovercam] - 2*TICRATE)*15));
@ -3894,6 +3950,11 @@ DoABarrelRoll (player_t *player)
fixed_t smoothing;
if (player->loop.radius)
{
return;
}
if (player->respawn.state != RESPAWNST_NONE)
{
player->tilt = 0;

View file

@ -140,10 +140,6 @@ void R_InterpolateView(fixed_t frac)
prevview = newview;
}
viewx = R_LerpFixed(prevview->x, newview->x, frac);
viewy = R_LerpFixed(prevview->y, newview->y, frac);
viewz = R_LerpFixed(prevview->z, newview->z, frac);
viewangle = R_LerpAngle(prevview->angle, newview->angle, frac);
aimingangle = R_LerpAngle(prevview->aim, newview->aim, frac);
viewroll = R_LerpAngle(prevview->roll, newview->roll, frac);
@ -151,6 +147,12 @@ void R_InterpolateView(fixed_t frac)
viewsin = FINESINE(viewangle>>ANGLETOFINESHIFT);
viewcos = FINECOSINE(viewangle>>ANGLETOFINESHIFT);
fixed_t zoom = R_LerpFixed(prevview->zoom, newview->zoom, frac);
viewx = R_LerpFixed(prevview->x, newview->x, frac) - FixedMul(viewcos, zoom);
viewy = R_LerpFixed(prevview->y, newview->y, frac) - FixedMul(viewsin, zoom);
viewz = R_LerpFixed(prevview->z, newview->z, frac);
viewplayer = newview->player;
viewsector = R_PointInSubsector(viewx, viewy)->sector;

View file

@ -48,6 +48,7 @@ struct viewvars_t {
fixed_t x;
fixed_t y;
fixed_t z;
fixed_t zoom;
boolean sky;
sector_t *sector;
player_t *player;

View file

@ -1244,6 +1244,7 @@ void R_SetupFrame(int s)
newview->x = r_viewmobj->x;
newview->y = r_viewmobj->y;
newview->z = r_viewmobj->z;
newview->zoom = 0;
R_SetupCommonFrame(player, r_viewmobj->subsector);
}
@ -1252,9 +1253,13 @@ void R_SetupFrame(int s)
{
r_viewmobj = NULL;
newview->x = thiscam->x;
newview->y = thiscam->y;
fixed_t x = player->mo ? player->mo->x : thiscam->x;
fixed_t y = player->mo ? player->mo->y : thiscam->y;
newview->x = x;
newview->y = y;
newview->z = thiscam->z + (thiscam->height>>1);
newview->zoom = FixedHypot(thiscam->x - x, thiscam->y - y);
R_SetupCommonFrame(player, thiscam->subsector);
}
@ -1267,6 +1272,7 @@ void R_SetupFrame(int s)
newview->x = r_viewmobj->x;
newview->y = r_viewmobj->y;
newview->z = player->viewz;
newview->zoom = 0;
R_SetupCommonFrame(player, r_viewmobj->subsector);
}
@ -1297,6 +1303,7 @@ void R_SkyboxFrame(int s)
newview->x = r_viewmobj->x;
newview->y = r_viewmobj->y;
newview->z = r_viewmobj->z; // 26/04/17: use actual Z position instead of spawnpoint angle!
newview->zoom = 0;
if (mapheaderinfo[gamemap-1])
{