mirror of
https://github.com/s1lentq/ReGameDLL_CS.git
synced 2024-12-27 15:15:39 +03:00
920 lines
21 KiB
C++
920 lines
21 KiB
C++
#include "precompiled.h"
|
|
|
|
TYPEDESCRIPTION CFuncTank::m_SaveData[] =
|
|
{
|
|
DEFINE_FIELD(CFuncTank, m_yawCenter, FIELD_FLOAT),
|
|
DEFINE_FIELD(CFuncTank, m_yawRate, FIELD_FLOAT),
|
|
DEFINE_FIELD(CFuncTank, m_yawRange, FIELD_FLOAT),
|
|
DEFINE_FIELD(CFuncTank, m_yawTolerance, FIELD_FLOAT),
|
|
DEFINE_FIELD(CFuncTank, m_pitchCenter, FIELD_FLOAT),
|
|
DEFINE_FIELD(CFuncTank, m_pitchRate, FIELD_FLOAT),
|
|
DEFINE_FIELD(CFuncTank, m_pitchRange, FIELD_FLOAT),
|
|
DEFINE_FIELD(CFuncTank, m_pitchTolerance, FIELD_FLOAT),
|
|
DEFINE_FIELD(CFuncTank, m_fireLast, FIELD_TIME),
|
|
DEFINE_FIELD(CFuncTank, m_fireRate, FIELD_FLOAT),
|
|
DEFINE_FIELD(CFuncTank, m_lastSightTime, FIELD_TIME),
|
|
DEFINE_FIELD(CFuncTank, m_persist, FIELD_FLOAT),
|
|
DEFINE_FIELD(CFuncTank, m_minRange, FIELD_FLOAT),
|
|
DEFINE_FIELD(CFuncTank, m_maxRange, FIELD_FLOAT),
|
|
DEFINE_FIELD(CFuncTank, m_barrelPos, FIELD_VECTOR),
|
|
DEFINE_FIELD(CFuncTank, m_spriteScale, FIELD_FLOAT),
|
|
DEFINE_FIELD(CFuncTank, m_iszSpriteSmoke, FIELD_STRING),
|
|
DEFINE_FIELD(CFuncTank, m_iszSpriteFlash, FIELD_STRING),
|
|
DEFINE_FIELD(CFuncTank, m_bulletType, FIELD_INTEGER),
|
|
DEFINE_FIELD(CFuncTank, m_sightOrigin, FIELD_VECTOR),
|
|
DEFINE_FIELD(CFuncTank, m_spread, FIELD_INTEGER),
|
|
DEFINE_FIELD(CFuncTank, m_pController, FIELD_CLASSPTR),
|
|
DEFINE_FIELD(CFuncTank, m_vecControllerUsePos, FIELD_VECTOR),
|
|
DEFINE_FIELD(CFuncTank, m_flNextAttack, FIELD_TIME),
|
|
DEFINE_FIELD(CFuncTank, m_iBulletDamage, FIELD_INTEGER),
|
|
DEFINE_FIELD(CFuncTank, m_iszMaster, FIELD_STRING),
|
|
};
|
|
|
|
Vector CFuncTank::m_TankSpread[] =
|
|
{
|
|
Vector(0, 0, 0), // perfect
|
|
Vector(0.025, 0.025, 0.025), // small cone
|
|
Vector(0.05, 0.05, 0.05), // medium cone
|
|
Vector(0.1, 0.1, 0.1), // large cone
|
|
Vector(0.25, 0.25, 0.25), // extra-large cone
|
|
};
|
|
|
|
constexpr int MAX_FIRING_SPREADS = ARRAYSIZE(CFuncTank::m_TankSpread);
|
|
|
|
IMPLEMENT_SAVERESTORE(CFuncTank, CBaseEntity)
|
|
|
|
void CFuncTank::Spawn()
|
|
{
|
|
Precache();
|
|
|
|
// so it doesn't get pushed by anything
|
|
pev->movetype = MOVETYPE_PUSH;
|
|
pev->solid = SOLID_BSP;
|
|
SET_MODEL(ENT(pev), STRING(pev->model));
|
|
|
|
m_yawCenter = pev->angles.y;
|
|
m_pitchCenter = pev->angles.x;
|
|
|
|
if (IsActive())
|
|
{
|
|
pev->nextthink = pev->ltime + 1.0f;
|
|
}
|
|
|
|
// Point at the end of the barrel
|
|
m_sightOrigin = BarrelPosition();
|
|
|
|
if (m_fireRate <= 0.0f)
|
|
m_fireRate = 1.0f;
|
|
|
|
if (m_spread > MAX_FIRING_SPREADS)
|
|
m_spread = 0;
|
|
|
|
pev->oldorigin = pev->origin;
|
|
}
|
|
|
|
void CFuncTank::Precache()
|
|
{
|
|
if (m_iszSpriteSmoke)
|
|
{
|
|
PRECACHE_MODEL((char *)STRING(m_iszSpriteSmoke));
|
|
}
|
|
|
|
if (m_iszSpriteFlash)
|
|
{
|
|
PRECACHE_MODEL((char *)STRING(m_iszSpriteFlash));
|
|
}
|
|
|
|
if (pev->noise)
|
|
{
|
|
PRECACHE_SOUND((char *)STRING(pev->noise));
|
|
}
|
|
}
|
|
|
|
void CFuncTank::KeyValue(KeyValueData *pkvd)
|
|
{
|
|
if (FStrEq(pkvd->szKeyName, "yawrate"))
|
|
{
|
|
m_yawRate = Q_atof(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "yawrange"))
|
|
{
|
|
m_yawRange = Q_atof(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "yawtolerance"))
|
|
{
|
|
m_yawTolerance = Q_atof(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "pitchrange"))
|
|
{
|
|
m_pitchRange = Q_atof(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "pitchrate"))
|
|
{
|
|
m_pitchRate = Q_atof(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "pitchtolerance"))
|
|
{
|
|
m_pitchTolerance = Q_atof(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "firerate"))
|
|
{
|
|
m_fireRate = Q_atof(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "barrel"))
|
|
{
|
|
m_barrelPos.x = Q_atof(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "barrely"))
|
|
{
|
|
m_barrelPos.y = Q_atof(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "barrelz"))
|
|
{
|
|
m_barrelPos.z = Q_atof(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "spritescale"))
|
|
{
|
|
m_spriteScale = Q_atof(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "spritesmoke"))
|
|
{
|
|
m_iszSpriteSmoke = ALLOC_STRING(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "spriteflash"))
|
|
{
|
|
m_iszSpriteFlash = ALLOC_STRING(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "rotatesound"))
|
|
{
|
|
pev->noise = ALLOC_STRING(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "persistence"))
|
|
{
|
|
m_persist = Q_atof(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "bullet"))
|
|
{
|
|
m_bulletType = (TANKBULLET)Q_atoi(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "bullet_damage"))
|
|
{
|
|
m_iBulletDamage = Q_atoi(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "firespread"))
|
|
{
|
|
m_spread = Q_atoi(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "minRange"))
|
|
{
|
|
m_minRange = Q_atof(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "maxRange"))
|
|
{
|
|
m_maxRange = Q_atof(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "master"))
|
|
{
|
|
m_iszMaster = ALLOC_STRING(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else
|
|
{
|
|
CBaseEntity::KeyValue(pkvd);
|
|
}
|
|
}
|
|
|
|
BOOL CFuncTank::OnControls(entvars_t *pevTest)
|
|
{
|
|
if (!(pev->spawnflags & SF_TANK_CANCONTROL))
|
|
return FALSE;
|
|
|
|
Vector offset = pevTest->origin - pev->origin;
|
|
if ((m_vecControllerUsePos - pevTest->origin).Length() < 30.0f)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL CFuncTank::StartControl(CBasePlayer *pController)
|
|
{
|
|
if (m_pController)
|
|
return FALSE;
|
|
|
|
// Team only or disabled?
|
|
if (m_iszMaster)
|
|
{
|
|
if (!UTIL_IsMasterTriggered(m_iszMaster, pController))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
ALERT(at_console, "using TANK!\n");
|
|
|
|
m_pController = pController;
|
|
|
|
if (m_pController->m_pActiveItem)
|
|
{
|
|
m_pController->m_pActiveItem->Holster();
|
|
m_pController->pev->weaponmodel = 0;
|
|
|
|
#ifdef BUILD_LATEST_FIXES
|
|
m_pController->pev->viewmodel = 0;
|
|
#endif
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
// if (m_pController->m_iFOV != DEFAULT_FOV)
|
|
{
|
|
m_pController->pev->fov = m_pController->m_iFOV = m_pController->m_iLastZoom = DEFAULT_FOV;
|
|
m_pController->m_bResumeZoom = false;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
m_pController->m_iHideHUD |= HIDEHUD_WEAPONS;
|
|
m_vecControllerUsePos = m_pController->pev->origin;
|
|
|
|
pev->nextthink = pev->ltime + 0.1f;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void CFuncTank::StopControl()
|
|
{
|
|
// TODO: bring back the controllers current weapon
|
|
if (!m_pController)
|
|
return;
|
|
|
|
if (m_pController->m_pActiveItem)
|
|
{
|
|
m_pController->m_pActiveItem->Deploy();
|
|
|
|
if (m_pController->IsPlayer())
|
|
{
|
|
m_pController->ResetMaxSpeed();
|
|
}
|
|
}
|
|
|
|
ALERT(at_console, "stopped using TANK\n");
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
if (m_pController->m_pActiveItem)
|
|
#endif
|
|
{
|
|
m_pController->m_iHideHUD &= ~HIDEHUD_WEAPONS;
|
|
}
|
|
|
|
pev->nextthink = 0;
|
|
m_pController = nullptr;
|
|
|
|
if (IsActive())
|
|
{
|
|
pev->nextthink = pev->ltime + 1.0f;
|
|
}
|
|
}
|
|
|
|
void CFuncTank::ControllerPostFrame()
|
|
{
|
|
if (gpGlobals->time < m_flNextAttack)
|
|
return;
|
|
|
|
Assert(m_pController != nullptr);
|
|
|
|
if (m_pController->pev->button & IN_ATTACK)
|
|
{
|
|
Vector vecForward;
|
|
UTIL_MakeVectorsPrivate(pev->angles, vecForward, nullptr, nullptr);
|
|
|
|
m_fireLast = gpGlobals->time - (1.0f / m_fireRate) - 0.01f;
|
|
Fire(BarrelPosition(), vecForward, m_pController->pev);
|
|
|
|
if (m_pController && m_pController->IsPlayer())
|
|
{
|
|
m_pController->m_iWeaponVolume = LOUD_GUN_VOLUME;
|
|
}
|
|
|
|
m_flNextAttack = gpGlobals->time + (1.0f / m_fireRate);
|
|
}
|
|
}
|
|
|
|
void CFuncTank::Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value)
|
|
{
|
|
// player controlled turret
|
|
if (pev->spawnflags & SF_TANK_CANCONTROL)
|
|
{
|
|
if (pActivator->Classify() != CLASS_PLAYER)
|
|
return;
|
|
|
|
if (value == 2 && useType == USE_SET)
|
|
{
|
|
ControllerPostFrame();
|
|
}
|
|
else if (!m_pController && useType != USE_OFF)
|
|
{
|
|
((CBasePlayer *)pActivator)->m_pTank = this;
|
|
StartControl((CBasePlayer*)pActivator);
|
|
}
|
|
else
|
|
{
|
|
StopControl();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!ShouldToggle(useType, IsActive()))
|
|
return;
|
|
|
|
if (IsActive())
|
|
TankDeactivate();
|
|
else
|
|
TankActivate();
|
|
}
|
|
}
|
|
|
|
edict_t *CFuncTank::FindTarget(edict_t *pPlayer)
|
|
{
|
|
return pPlayer;
|
|
}
|
|
|
|
BOOL CFuncTank::InRange(float range)
|
|
{
|
|
if (range < m_minRange)
|
|
return FALSE;
|
|
|
|
if (m_maxRange > 0 && range > m_maxRange)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void CFuncTank::Think()
|
|
{
|
|
pev->avelocity = g_vecZero;
|
|
TrackTarget();
|
|
|
|
if (Q_fabs(real_t(pev->avelocity.x)) > 1 || Q_fabs(real_t(pev->avelocity.y)) > 1)
|
|
StartRotSound();
|
|
else
|
|
StopRotSound();
|
|
}
|
|
|
|
void CFuncTank::TrackTarget()
|
|
{
|
|
TraceResult tr;
|
|
edict_t *pPlayer = FIND_CLIENT_IN_PVS(edict());
|
|
bool updateTime = false, lineOfSight = false;
|
|
Vector angles, direction, targetPosition, barrelEnd;
|
|
edict_t *pTarget = nullptr;
|
|
|
|
// Get a position to aim for
|
|
if (m_pController)
|
|
{
|
|
// Tanks attempt to mirror the player's angles
|
|
angles = m_pController->pev->v_angle;
|
|
angles.x = 0 - angles.x;
|
|
pev->nextthink = pev->ltime + 0.05f;
|
|
}
|
|
else
|
|
{
|
|
if (IsActive())
|
|
pev->nextthink = pev->ltime + 0.1f;
|
|
else
|
|
return;
|
|
|
|
if (FNullEnt(pPlayer))
|
|
{
|
|
if (IsActive())
|
|
{
|
|
// Wait 2 secs
|
|
pev->nextthink = pev->ltime + 2.0f;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
pTarget = FindTarget(pPlayer);
|
|
if (!pTarget)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Calculate angle needed to aim at target
|
|
barrelEnd = BarrelPosition();
|
|
targetPosition = pTarget->v.origin + pTarget->v.view_ofs;
|
|
float range = (targetPosition - barrelEnd).Length();
|
|
|
|
if (!InRange(range))
|
|
return;
|
|
|
|
UTIL_TraceLine(barrelEnd, targetPosition, dont_ignore_monsters, edict(), &tr);
|
|
|
|
// No line of sight, don't track
|
|
if (tr.flFraction == 1.0f || tr.pHit == pTarget)
|
|
{
|
|
lineOfSight = true;
|
|
|
|
CBaseEntity *pInstance = CBaseEntity::Instance(pTarget);
|
|
if (InRange(range) && pInstance && pInstance->IsAlive())
|
|
{
|
|
updateTime = true;
|
|
m_sightOrigin = UpdateTargetPosition(pInstance);
|
|
}
|
|
}
|
|
|
|
// Track sight origin
|
|
// TODO: I'm not sure what i changed
|
|
direction = m_sightOrigin - pev->origin;
|
|
//direction = m_sightOrigin - barrelEnd;
|
|
angles = UTIL_VecToAngles(direction);
|
|
|
|
// Calculate the additional rotation to point the end of the barrel at the target (not the gun's center)
|
|
AdjustAnglesForBarrel(angles, direction.Length());
|
|
}
|
|
|
|
angles.x = -angles.x;
|
|
|
|
// Force the angles to be relative to the center position
|
|
angles.y = m_yawCenter + UTIL_AngleDistance(angles.y, m_yawCenter);
|
|
angles.x = m_pitchCenter + UTIL_AngleDistance(angles.x, m_pitchCenter);
|
|
|
|
// Limit against range in y
|
|
if (angles.y > m_yawCenter + m_yawRange)
|
|
{
|
|
angles.y = m_yawCenter + m_yawRange;
|
|
|
|
// Don't update if you saw the player, but out of range
|
|
updateTime = false;
|
|
}
|
|
else if (angles.y < (m_yawCenter - m_yawRange))
|
|
{
|
|
angles.y = (m_yawCenter - m_yawRange);
|
|
|
|
// Don't update if you saw the player, but out of range
|
|
updateTime = false;
|
|
}
|
|
|
|
if (updateTime)
|
|
{
|
|
m_lastSightTime = gpGlobals->time;
|
|
}
|
|
|
|
// Move toward target at rate or less
|
|
real_t distY = UTIL_AngleDistance(angles.y, pev->angles.y);
|
|
pev->avelocity.y = distY * 10.0f;
|
|
|
|
if (pev->avelocity.y > m_yawRate)
|
|
{
|
|
pev->avelocity.y = m_yawRate;
|
|
}
|
|
else if (pev->avelocity.y < -m_yawRate)
|
|
{
|
|
pev->avelocity.y = -m_yawRate;
|
|
}
|
|
|
|
// Limit against range in x
|
|
if (angles.x > m_pitchCenter + m_pitchRange)
|
|
{
|
|
angles.x = m_pitchCenter + m_pitchRange;
|
|
}
|
|
else if (angles.x < m_pitchCenter - m_pitchRange)
|
|
{
|
|
angles.x = m_pitchCenter - m_pitchRange;
|
|
}
|
|
|
|
// Move toward target at rate or less
|
|
real_t distX = UTIL_AngleDistance(angles.x, pev->angles.x);
|
|
pev->avelocity.x = distX * 10.0f;
|
|
|
|
if (pev->avelocity.x > m_pitchRate)
|
|
{
|
|
pev->avelocity.x = m_pitchRate;
|
|
}
|
|
else if (pev->avelocity.x < -m_pitchRate)
|
|
{
|
|
pev->avelocity.x = -m_pitchRate;
|
|
}
|
|
|
|
if (m_pController)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (CanFire() && ((Q_fabs(distX) < m_pitchTolerance && Q_fabs(distY) < m_yawTolerance) || (pev->spawnflags & SF_TANK_LINEOFSIGHT)))
|
|
{
|
|
bool fire = false;
|
|
Vector forward;
|
|
UTIL_MakeVectorsPrivate(pev->angles, forward, nullptr, nullptr);
|
|
|
|
if (pev->spawnflags & SF_TANK_LINEOFSIGHT)
|
|
{
|
|
float length = direction.Length();
|
|
UTIL_TraceLine(barrelEnd, barrelEnd + forward * length, dont_ignore_monsters, edict(), &tr);
|
|
|
|
if (tr.pHit == pTarget)
|
|
{
|
|
fire = true;
|
|
}
|
|
}
|
|
else
|
|
fire = true;
|
|
|
|
if (fire)
|
|
{
|
|
Fire(BarrelPosition(), forward, pev);
|
|
}
|
|
else
|
|
m_fireLast = 0;
|
|
}
|
|
else
|
|
m_fireLast = 0;
|
|
}
|
|
|
|
// If barrel is offset, add in additional rotation
|
|
void CFuncTank::AdjustAnglesForBarrel(Vector &angles, float distance)
|
|
{
|
|
real_t r2, d2;
|
|
|
|
if (m_barrelPos.y != 0.0f || m_barrelPos.z != 0.0f)
|
|
{
|
|
distance -= m_barrelPos.z;
|
|
d2 = distance * distance;
|
|
|
|
if (m_barrelPos.y)
|
|
{
|
|
r2 = m_barrelPos.y * m_barrelPos.y;
|
|
angles.y += (180.0f / M_PI) * atan2(m_barrelPos.y, Q_sqrt(d2 - r2));
|
|
}
|
|
|
|
if (m_barrelPos.z)
|
|
{
|
|
r2 = m_barrelPos.z * m_barrelPos.z;
|
|
angles.x += (180.0f / M_PI) * atan2(-m_barrelPos.z, Q_sqrt(d2 - r2));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fire targets and spawn sprites
|
|
void CFuncTank::Fire(const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker)
|
|
{
|
|
if (m_fireLast != 0.0f)
|
|
{
|
|
if (m_iszSpriteSmoke)
|
|
{
|
|
CSprite *pSprite = CSprite::SpriteCreate(STRING(m_iszSpriteSmoke), barrelEnd, TRUE);
|
|
|
|
pSprite->AnimateAndDie(RANDOM_FLOAT(15, 20));
|
|
pSprite->SetTransparency(kRenderTransAlpha, pev->rendercolor.x, pev->rendercolor.y, pev->rendercolor.z, 255, kRenderFxNone);
|
|
pSprite->pev->velocity.z = RANDOM_FLOAT(40, 80);
|
|
pSprite->SetScale(m_spriteScale);
|
|
}
|
|
|
|
if (m_iszSpriteFlash)
|
|
{
|
|
CSprite *pSprite = CSprite::SpriteCreate(STRING(m_iszSpriteFlash), barrelEnd, TRUE);
|
|
|
|
pSprite->AnimateAndDie(60);
|
|
pSprite->SetTransparency(kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation);
|
|
pSprite->SetScale(m_spriteScale);
|
|
|
|
// Hack Hack, make it stick around for at least 100 ms.
|
|
pSprite->pev->nextthink += 0.1f;
|
|
}
|
|
|
|
SUB_UseTargets(this, USE_TOGGLE, 0);
|
|
}
|
|
|
|
m_fireLast = gpGlobals->time;
|
|
}
|
|
|
|
void CFuncTank::TankTrace(const Vector &vecStart, const Vector &vecForward, const Vector &vecSpread, TraceResult &tr)
|
|
{
|
|
// get circular gaussian spread
|
|
float x, z;
|
|
real_t y;
|
|
|
|
do
|
|
{
|
|
x = RANDOM_FLOAT(-0.5, 0.5) + RANDOM_FLOAT(-0.5, 0.5);
|
|
y = RANDOM_FLOAT(-0.5, 0.5) + RANDOM_FLOAT(-0.5, 0.5);
|
|
z = x * x + y * y;
|
|
}
|
|
while (z > 1);
|
|
|
|
Vector vecDir = vecForward + x * vecSpread.x * gpGlobals->v_right + y * vecSpread.y * gpGlobals->v_up;
|
|
Vector vecEnd;
|
|
|
|
vecEnd = vecStart + vecDir * 4096.0f;
|
|
UTIL_TraceLine(vecStart, vecEnd, dont_ignore_monsters, edict(), &tr);
|
|
}
|
|
|
|
void CFuncTank::StartRotSound()
|
|
{
|
|
if (!pev->noise || (pev->spawnflags & SF_TANK_SOUNDON))
|
|
{
|
|
return;
|
|
}
|
|
|
|
pev->spawnflags |= SF_TANK_SOUNDON;
|
|
EMIT_SOUND(edict(), CHAN_STATIC, (char *)STRING(pev->noise), 0.85, ATTN_NORM);
|
|
}
|
|
|
|
void CFuncTank::StopRotSound()
|
|
{
|
|
if (pev->spawnflags & SF_TANK_SOUNDON)
|
|
{
|
|
STOP_SOUND(edict(), CHAN_STATIC, (char *)STRING(pev->noise));
|
|
}
|
|
|
|
pev->spawnflags &= ~SF_TANK_SOUNDON;
|
|
}
|
|
|
|
LINK_ENTITY_TO_CLASS(func_tank, CFuncTankGun, CCSFuncTankGun)
|
|
|
|
void CFuncTankGun::Fire(const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker)
|
|
{
|
|
if (m_fireLast != 0.0f)
|
|
{
|
|
// FireBullets needs gpGlobals->v_up, etc.
|
|
UTIL_MakeAimVectors(pev->angles);
|
|
|
|
Vector vecBarrelEnd(barrelEnd), vecForward(forward);
|
|
|
|
int bulletCount = int((gpGlobals->time - m_fireLast) * m_fireRate);
|
|
if (bulletCount > 0)
|
|
{
|
|
for (int i = 0; i < bulletCount; i++)
|
|
{
|
|
switch (m_bulletType)
|
|
{
|
|
case TANK_BULLET_9MM:
|
|
FireBullets(1, vecBarrelEnd, vecForward, m_TankSpread[m_spread], 4096, BULLET_MONSTER_9MM, 1, m_iBulletDamage, pevAttacker);
|
|
break;
|
|
case TANK_BULLET_MP5:
|
|
FireBullets(1, vecBarrelEnd, vecForward, m_TankSpread[m_spread], 4096, BULLET_MONSTER_MP5, 1, m_iBulletDamage, pevAttacker);
|
|
break;
|
|
case TANK_BULLET_12MM:
|
|
FireBullets(1, vecBarrelEnd, vecForward, m_TankSpread[m_spread], 4096, BULLET_MONSTER_12MM, 1, m_iBulletDamage, pevAttacker);
|
|
break;
|
|
default:
|
|
case TANK_BULLET_NONE:
|
|
break;
|
|
}
|
|
}
|
|
|
|
CFuncTank::Fire(vecBarrelEnd, vecForward, pevAttacker);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CFuncTank::Fire(barrelEnd, forward, pevAttacker);
|
|
}
|
|
}
|
|
|
|
TYPEDESCRIPTION CFuncTankLaser::m_SaveData[] =
|
|
{
|
|
DEFINE_FIELD(CFuncTankLaser, m_pLaser, FIELD_CLASSPTR),
|
|
DEFINE_FIELD(CFuncTankLaser, m_laserTime, FIELD_TIME),
|
|
};
|
|
|
|
LINK_ENTITY_TO_CLASS(func_tanklaser, CFuncTankLaser, CCSFuncTankLaser)
|
|
IMPLEMENT_SAVERESTORE(CFuncTankLaser, CFuncTank)
|
|
|
|
void CFuncTankLaser::Activate()
|
|
{
|
|
if (!GetLaser())
|
|
{
|
|
UTIL_Remove(this);
|
|
ALERT(at_error, "Laser tank with no env_laser!\n");
|
|
}
|
|
else
|
|
{
|
|
m_pLaser->TurnOff();
|
|
}
|
|
}
|
|
|
|
void CFuncTankLaser::KeyValue(KeyValueData *pkvd)
|
|
{
|
|
if (FStrEq(pkvd->szKeyName, "laserentity"))
|
|
{
|
|
pev->message = ALLOC_STRING(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else
|
|
{
|
|
CFuncTank::KeyValue(pkvd);
|
|
}
|
|
}
|
|
|
|
CLaser *CFuncTankLaser::GetLaser()
|
|
{
|
|
if (m_pLaser)
|
|
{
|
|
return m_pLaser;
|
|
}
|
|
|
|
edict_t *pentLaser = FIND_ENTITY_BY_TARGETNAME(nullptr, STRING(pev->message));
|
|
|
|
while (!FNullEnt(pentLaser))
|
|
{
|
|
// Found the landmark
|
|
if (FClassnameIs(pentLaser, "env_laser"))
|
|
{
|
|
m_pLaser = (CLaser *)CBaseEntity::Instance(pentLaser);
|
|
break;
|
|
}
|
|
else
|
|
pentLaser = FIND_ENTITY_BY_TARGETNAME(pentLaser, STRING(pev->message));
|
|
}
|
|
|
|
return m_pLaser;
|
|
}
|
|
|
|
void CFuncTankLaser::Think()
|
|
{
|
|
if (m_pLaser && gpGlobals->time > m_laserTime)
|
|
{
|
|
m_pLaser->TurnOff();
|
|
}
|
|
|
|
CFuncTank::Think();
|
|
}
|
|
|
|
void CFuncTankLaser::Fire(const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker)
|
|
{
|
|
if (m_fireLast != 0.0f && GetLaser())
|
|
{
|
|
// TankTrace needs gpGlobals->v_up, etc.
|
|
UTIL_MakeAimVectors(pev->angles);
|
|
|
|
int bulletCount = int((gpGlobals->time - m_fireLast) * m_fireRate);
|
|
if (bulletCount)
|
|
{
|
|
TraceResult tr;
|
|
for (int i = 0; i < bulletCount; i++)
|
|
{
|
|
m_pLaser->pev->origin = barrelEnd;
|
|
TankTrace(barrelEnd, forward, m_TankSpread[m_spread], tr);
|
|
|
|
m_laserTime = gpGlobals->time;
|
|
m_pLaser->TurnOn();
|
|
m_pLaser->pev->dmgtime = gpGlobals->time - 1.0f;
|
|
m_pLaser->FireAtPoint(tr);
|
|
m_pLaser->pev->nextthink = 0;
|
|
}
|
|
|
|
CFuncTank::Fire(barrelEnd, forward, pev);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CFuncTank::Fire(barrelEnd, forward, pev);
|
|
}
|
|
}
|
|
|
|
LINK_ENTITY_TO_CLASS(func_tankrocket, CFuncTankRocket, CCSFuncTankRocket)
|
|
|
|
void CFuncTankRocket::Precache()
|
|
{
|
|
UTIL_PrecacheOther("rpg_rocket");
|
|
CFuncTank::Precache();
|
|
}
|
|
|
|
void CFuncTankRocket::Fire(const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker)
|
|
{
|
|
if (m_fireLast != 0.0f)
|
|
{
|
|
int bulletCount = int((gpGlobals->time - m_fireLast) * m_fireRate);
|
|
if (bulletCount > 0)
|
|
{
|
|
for (int i = 0; i < bulletCount; i++)
|
|
{
|
|
CBaseEntity *pRocket = CBaseEntity::Create("rpg_rocket", barrelEnd, pev->angles, edict());
|
|
}
|
|
|
|
CFuncTank::Fire(barrelEnd, forward, pev);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CFuncTank::Fire(barrelEnd, forward, pev);
|
|
}
|
|
}
|
|
|
|
LINK_ENTITY_TO_CLASS(func_tankmortar, CFuncTankMortar, CCSFuncTankMortar)
|
|
|
|
void CFuncTankMortar::KeyValue(KeyValueData *pkvd)
|
|
{
|
|
if (FStrEq(pkvd->szKeyName, "iMagnitude"))
|
|
{
|
|
pev->impulse = Q_atoi(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else
|
|
{
|
|
CFuncTank::KeyValue(pkvd);
|
|
}
|
|
}
|
|
|
|
void CFuncTankMortar::Fire(const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker)
|
|
{
|
|
if (m_fireLast != 0.0f)
|
|
{
|
|
int bulletCount = int((gpGlobals->time - m_fireLast) * m_fireRate);
|
|
|
|
// Only create 1 explosion
|
|
if (bulletCount > 0)
|
|
{
|
|
TraceResult tr;
|
|
|
|
// TankTrace needs gpGlobals->v_up, etc.
|
|
UTIL_MakeAimVectors(pev->angles);
|
|
|
|
TankTrace(barrelEnd, forward, m_TankSpread[m_spread], tr);
|
|
ExplosionCreate(tr.vecEndPos, pev->angles, edict(), pev->impulse, TRUE);
|
|
CFuncTank::Fire(barrelEnd, forward, pev);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CFuncTank::Fire(barrelEnd, forward, pev);
|
|
}
|
|
}
|
|
|
|
TYPEDESCRIPTION CFuncTankControls::m_SaveData[] =
|
|
{
|
|
DEFINE_FIELD(CFuncTankControls, m_pTank, FIELD_CLASSPTR),
|
|
};
|
|
|
|
LINK_ENTITY_TO_CLASS(func_tankcontrols, CFuncTankControls, CCSFuncTankControls)
|
|
IMPLEMENT_SAVERESTORE(CFuncTankControls, CBaseEntity)
|
|
|
|
void CFuncTankControls::Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value)
|
|
{
|
|
// pass the Use command onto the controls
|
|
if (m_pTank)
|
|
{
|
|
m_pTank->Use(pActivator, pCaller, useType, value);
|
|
}
|
|
|
|
// if this fails, most likely means save/restore hasn't worked properly
|
|
DbgAssert(m_pTank != nullptr);
|
|
}
|
|
|
|
void CFuncTankControls::Think()
|
|
{
|
|
edict_t *pTarget = nullptr;
|
|
|
|
do
|
|
{
|
|
pTarget = FIND_ENTITY_BY_TARGETNAME(pTarget, STRING(pev->target));
|
|
}
|
|
while (!FNullEnt(pTarget) && Q_strncmp(STRING(pTarget->v.classname), "func_tank", 9) != 0);
|
|
|
|
if (FNullEnt(pTarget))
|
|
{
|
|
ALERT(at_console, "No tank %s\n", STRING(pev->target));
|
|
return;
|
|
}
|
|
|
|
m_pTank = static_cast<CFuncTank *>(Instance(pTarget));
|
|
}
|
|
|
|
void CFuncTankControls::Spawn()
|
|
{
|
|
pev->solid = SOLID_TRIGGER;
|
|
pev->movetype = MOVETYPE_NONE;
|
|
pev->effects |= EF_NODRAW;
|
|
|
|
SET_MODEL(ENT(pev), STRING(pev->model));
|
|
|
|
UTIL_SetSize(pev, pev->mins, pev->maxs);
|
|
UTIL_SetOrigin(pev, pev->origin);
|
|
|
|
// After all the func_tank's have spawned
|
|
pev->nextthink = gpGlobals->time + 0.3f;
|
|
|
|
CBaseEntity::Spawn();
|
|
}
|