Skip to content

Commit

Permalink
RPG improvements (fixes #42)
Browse files Browse the repository at this point in the history
Added mp_rpg_laser_mode to control rocket laser guidance:
0 = HL behavior. Rockets follow a "random" laser within its FOV.
1 = Realistic. Rockets follow the most centered laser. Its target may suddenly change if another laser is significantly closer and nearly the same direction.
2 = Sven behavior. Rockets follow the laser of the player/npc that fired the rocket.

Also:
- fixed rocket trail stopping short of the impact point
- rockets are less likely to follow a laser that's behind an obstacle
- laser is interpolated. This smooths movement but adds ex_interp amount of latency.
  • Loading branch information
wootguy committed Dec 3, 2024
1 parent d6d24b9 commit 540a36c
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 57 deletions.
3 changes: 2 additions & 1 deletion dlls/CBaseEntity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1257,7 +1257,8 @@ void CBaseEntity::ParametricInterpolation(float flInterval) {
#if 0
// Interpolate past position to current.
// This should in theory create smoother movement due to clients not predicting
// the wrong path if the velocity changes, but I can't tell any difference.
// the wrong path if the velocity changes, but I can't tell any difference unless
// the entity has the TE_BEAMFOLLOW effect
pev->startpos = m_oldOrigin;
pev->endpos = pev->origin;
pev->starttime = gpGlobals->time;
Expand Down
2 changes: 2 additions & 0 deletions dlls/game/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ cvar_t pluginupdatepath ={"plugin_update_path","valve_pending/", FCVAR_SERVER, 0
cvar_t pluginautoupdate ={"plugin_auto_update", "0", FCVAR_SERVER, 0, 0 };
cvar_t mp_skill_allow ={"mp_skill_allow", "1", FCVAR_SERVER, 0, 0 };
cvar_t mp_default_medkit ={"mp_default_medkit", "0", FCVAR_SERVER, 0, 0 };
cvar_t mp_rpg_laser_mode ={"mp_rpg_laser_mode", "1", FCVAR_SERVER, 0, 0 };

cvar_t soundvariety={"mp_soundvariety","0", FCVAR_SERVER, 0, 0 };

Expand Down Expand Up @@ -378,6 +379,7 @@ void GameDLLInit( void )
CVAR_REGISTER (&pluginautoupdate);
CVAR_REGISTER (&mp_skill_allow);
CVAR_REGISTER (&mp_default_medkit);
CVAR_REGISTER (&mp_rpg_laser_mode);

CVAR_REGISTER (&mp_chattime);

Expand Down
1 change: 1 addition & 0 deletions dlls/game/game.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ EXPORT extern cvar_t pluginupdatepath; // root path for plugin file updates to b
EXPORT extern cvar_t pluginautoupdate; // attempt to update plugins after every map change
EXPORT extern cvar_t mp_skill_allow; // 0 = no, 1 = yes
EXPORT extern cvar_t mp_default_medkit; // provide a medkit by default unless nomedkit is in the cfg
EXPORT extern cvar_t mp_rpg_laser_mode; // 0 = HL, 1 = realistic, 2 = rockets follow owner's laser

// Enables classic func_pushable physics (which is horribly broken, but fun)
// The higher your FPS, the faster you can boost pushables. You also get boosted.
Expand Down
2 changes: 1 addition & 1 deletion dlls/monster/CBaseGrunt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -798,7 +798,7 @@ void CBaseGrunt::ShootRPG(Vector& vecShootOrigin, Vector& vecShootDir) {
PointAtEnemy();

if (!m_hRpgSpot) {
m_hRpgSpot = CLaserSpot::CreateSpot();
m_hRpgSpot = CLaserSpot::CreateSpot(edict());
}
CLaserSpot* spot = (CLaserSpot*)m_hRpgSpot.GetEntity();

Expand Down
120 changes: 69 additions & 51 deletions dlls/weapon/CRpg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ enum rpg_e {

LINK_ENTITY_TO_CLASS( weapon_rpg, CRpg )

int laserBeamIdx;

#ifndef CLIENT_DLL

LINK_ENTITY_TO_CLASS( laser_spot, CLaserSpot )
Expand All @@ -58,12 +60,13 @@ IMPLEMENT_SAVERESTORE(CRpgRocket, CGrenade)

//=========================================================
//=========================================================
CLaserSpot *CLaserSpot::CreateSpot( void )
CLaserSpot *CLaserSpot::CreateSpot(edict_t* owner)
{
CLaserSpot *pSpot = GetClassPtr( (CLaserSpot *)NULL );
pSpot->Spawn();

pSpot->pev->classname = MAKE_STRING("laser_spot");
pSpot->pev->owner = owner;

return pSpot;
}
Expand All @@ -73,7 +76,7 @@ CLaserSpot *CLaserSpot::CreateSpot( void )
void CLaserSpot::Spawn( void )
{
Precache( );
pev->movetype = MOVETYPE_NONE;
pev->movetype = MOVETYPE_NOCLIP;
pev->solid = SOLID_NOT;

pev->rendermode = kRenderGlow;
Expand Down Expand Up @@ -195,6 +198,13 @@ void CRpgRocket::Explode(TraceResult* pTrace, int bitsDamageType)
}

CGrenade::Explode(pTrace, bitsDamageType);

// stay visible for another think so the interpolated beam effect has time to catch up
pev->velocity = g_vecZero;
pev->movetype = MOVETYPE_NONE;
pev->effects = 0;
pev->rendermode = kRenderTransTexture;
pev->renderamt = 1;
}

//=========================================================
Expand Down Expand Up @@ -243,6 +253,10 @@ void CRpgRocket :: FollowThink( void )

vecTarget = gpGlobals->v_forward;
flMax = 4096;

float bestDot = -1.0f;
float bestDist = FLT_MAX;
Vector bestDir = vecTarget;

// Examine all entities within a reasonable radius
while ((pOther = UTIL_FindEntityByClassname( pOther, "laser_spot" )) != NULL)
Expand All @@ -256,14 +270,30 @@ void CRpgRocket :: FollowThink( void )

UTIL_TraceLine(pev->origin, vSpotLocation, dont_ignore_monsters, ENT(pev), &tr);
// ALERT( at_console, "%f\n", tr.flFraction );
if (tr.flFraction >= 0.90)
if ((tr.vecEndPos - vSpotLocation).Length() < 16)
{
vecDir = pOther->pev->origin - pev->origin;
flDist = vecDir.Length( );
vecDir = vecDir.Normalize( );
flDot = DotProduct( gpGlobals->v_forward, vecDir );
if ((flDot > 0) && (flDist * (1 - flDot) < flMax))

bool isBetter = true;

if (mp_rpg_laser_mode.value == 1) {
// the best target is the brightest and most centered
bool isMoreCentered = flDot > bestDot;
bool isBrighter = (flDist * 2 < bestDist && fabs(flDot - bestDot) < 0.05f);
bool isDimmer = (flDist > bestDist * 2 && fabs(flDot - bestDot) < 0.05f);
isBetter = (isMoreCentered || isBrighter) && !isDimmer;
}
else if (mp_rpg_laser_mode.value == 2) {
isBetter = pev->owner == pOther->pev->owner;
}

if ((flDot > 0) && (flDist * (1 - flDot) < flMax) && isBetter)
{
bestDot = flDot;
bestDist = flDist;
flMax = flDist * (1 - flDot);
vecTarget = vecDir;
}
Expand Down Expand Up @@ -378,8 +408,6 @@ void CRpg::Reload( void )
CLaserSpot* m_pSpot = (CLaserSpot*)m_hSpot.GetEntity();
m_pSpot->Suspend( 2.1 );
m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 2.1;

m_hBeam->pev->effects |= EF_NODRAW;
}
#endif

Expand Down Expand Up @@ -429,7 +457,7 @@ void CRpg::Precache( void )
m_defaultModelW = "models/w_rpg.mdl";
CBasePlayerWeapon::Precache();

PRECACHE_MODEL("sprites/laserbeam.spr");
laserBeamIdx = PRECACHE_MODEL("sprites/laserbeam.spr");

PRECACHE_SOUND("items/9mmclip1.wav");

Expand Down Expand Up @@ -506,15 +534,11 @@ void CRpg::Holster( int skiplocal /* = 0 */ )
m_pSpot->Killed( NULL, GIB_NEVER );
m_hSpot = NULL;
}
if (m_hBeam) {
UTIL_Remove(m_hBeam);
}
#endif

}



void CRpg::PrimaryAttack()
{
CBasePlayer* m_pPlayer = GetPlayer();
Expand Down Expand Up @@ -576,8 +600,6 @@ void CRpg::SecondaryAttack()
{
m_pSpot->Killed( NULL, GIB_NORMAL );
m_hSpot = NULL;

UTIL_Remove(m_hBeam);
}
#endif

Expand Down Expand Up @@ -640,57 +662,53 @@ void CRpg::UpdateSpot( void )

if (m_fSpotActive)
{
if (!m_hSpot)
{
m_hSpot = CLaserSpot::CreateSpot();
if (!m_hSpot) {
m_hSpot = CLaserSpot::CreateSpot(m_pPlayer->edict());
}
CLaserSpot* m_pSpot = (CLaserSpot*)m_hSpot.GetEntity();

UTIL_MakeVectors( m_pPlayer->pev->v_angle );
Vector vecSrc = m_pPlayer->GetGunPosition( );;
Vector vecSrc = m_pPlayer->GetGunPosition( );
Vector vecAiming = gpGlobals->v_forward;

TraceResult tr;
UTIL_TraceLine ( vecSrc, vecSrc + vecAiming * 8192, dont_ignore_monsters, ENT(m_pPlayer->pev), &tr );

UTIL_SetOrigin( m_pSpot->pev, tr.vecEndPos );

if (!m_hBeam) {
CBeam* beam = CBeam::BeamCreate("sprites/laserbeam.spr", 8);
beam->PointEntInit(tr.vecEndPos, m_pPlayer->entindex());
beam->SetEndAttachment(1);
beam->SetColor(255, 32, 32);
beam->SetNoise(0);
beam->SetBrightness(48);
beam->SetScrollRate(64);
m_hBeam = beam;
}

CBeam* beam = (CBeam*)m_hBeam.GetEntity();
if (beam) {
beam->pev->effects = m_pSpot->pev->effects;
if (UTIL_PointContents(tr.vecEndPos) == CONTENTS_SKY) {
// back up until out of the sky, or else the client won't render the laser beam
Vector delta = tr.vecEndPos - vecSrc;
Vector bestPos = tr.vecEndPos;
for (float f = 0.01f; f <= 1.0f; f += 0.02f) {
bestPos = tr.vecEndPos - (delta * f);
if (UTIL_PointContents(bestPos) != CONTENTS_SKY) {
break;
}
}

const bool fix_crash = true;
m_pSpot->pev->renderamt = 1; // almost invisible, but still rendered so laser beam works
UTIL_SetOrigin(m_pSpot->pev, bestPos);
}
else {
m_pSpot->pev->renderamt = 255;
UTIL_SetOrigin(m_pSpot->pev, tr.vecEndPos);
}

if (fix_crash || UTIL_PointContents(tr.vecEndPos) == CONTENTS_SKY) {
// dot exits the PVS in this case
beam->PointEntInit(tr.vecEndPos, m_pPlayer->entindex());
beam->SetEndAttachment(1);
}
else {
// DON'T DO THIS. Somehow, it is causing client crashes.
// In a replay, I see a laser spot switches from index 922 to 934
// and then every client loses connection at the same time.
// Can't reproduce and I've only seen it happen twice in months.
// The player index never changes so that should be safe.
// Removing the dot randomly doesn't crash. Messing with attachments
// here doesn't crash. idk what happened.

beam->EntsInit(m_pPlayer->entindex(), m_pSpot->entindex());
beam->SetStartAttachment(1);
}
if (gpGlobals->time - m_lastBeamUpdate >= 0.95f && !(m_pSpot->pev->effects & EF_NODRAW)) {
// WARNING: Creating a beam entity that uses attachments has caused client crashes before,
// but I haven't seen that happen yet with TE_BEAMENTS. If this causes crashes again,
// then revert to using BeamEntPoint (attached to the player, not spot).
m_lastBeamUpdate = gpGlobals->time;
UTIL_BeamEnts(m_pSpot->entindex(), 0, m_pPlayer->entindex(), 1, false, laserBeamIdx,
0, 0, 10, 8, 0, RGBA(255, 32, 32, 48), 64, MSG_PVS, m_pPlayer->pev->origin);
}
}
else if (m_lastBeamUpdate) {
MESSAGE_BEGIN(MSG_PVS, SVC_TEMPENTITY, m_pPlayer->pev->origin);
WRITE_BYTE(TE_KILLBEAM);
WRITE_SHORT(m_pPlayer->entindex());
MESSAGE_END();
m_lastBeamUpdate = 0;
}
#endif

}
Expand Down
7 changes: 3 additions & 4 deletions dlls/weapon/CRpg.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class CLaserSpot : public CBaseEntity
void Suspend( float flSuspendTime );
void EXPORT Revive( void );

static CLaserSpot *CreateSpot( void );
static CLaserSpot *CreateSpot( edict_t* owner );
};

class CRpg : public CBasePlayerWeapon
Expand Down Expand Up @@ -48,9 +48,9 @@ class CRpg : public CBasePlayerWeapon
BOOL ShouldWeaponIdle( void ) { return TRUE; };

EHANDLE m_hSpot;
EHANDLE m_hBeam;
int m_fSpotActive;
int m_cActiveRockets;// how many missiles in flight from this launcher right now?
float m_lastBeamUpdate;

virtual int MergedModelBody() { return MERGE_MDL_W_RPG; }

Expand Down Expand Up @@ -79,9 +79,8 @@ class CRpgRocket : public CGrenade
void EXPORT FollowThink( void );
void EXPORT IgniteThink( void );
void EXPORT RocketTouch( CBaseEntity *pOther );
virtual const char* GetDeathNoticeWeapon() { return "rpg_rocket"; };

virtual void Explode(TraceResult* pTrace, int bitsDamageType);
virtual const char* GetDeathNoticeWeapon() { return "rpg_rocket"; };

static CRpgRocket *CreateRpgRocket( Vector vecOrigin, Vector vecAngles, CBaseEntity *pOwner, CRpg *pLauncher );

Expand Down

0 comments on commit 540a36c

Please sign in to comment.