Files
2026-05-05 15:01:01 -07:00

296 lines
8.0 KiB
C++

#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
//-----------------------------------------------------------------------
//
// hhGun
//
//-----------------------------------------------------------------------
const idEventDef EV_SetEnemy("setenemy", "e");
const idEventDef EV_FireGunAt("fireat", "e");
CLASS_DECLARATION(idEntity, hhGun)
EVENT( EV_Activate, hhGun::Event_Activate )
EVENT( EV_SetEnemy, hhGun::Event_SetEnemy )
EVENT( EV_FireGunAt, hhGun::Event_FireAt )
END_CLASS
float AngleBetweenVectors(const idVec3 &v1, const idVec3 &v2) {
float dot = (v1 * v2) / ( v1.Length() * v2.Length() );
return RAD2DEG(idMath::ACos(dot));
}
void hhGun::Spawn(void) {
coneAngle = spawnArgs.GetFloat("coneAngle");
burstCount = spawnArgs.GetInt("bursts");
burstRate = ( int )( spawnArgs.GetFloat( "burstRate" ) * 1000.0f );
fireRate = ( int )( spawnArgs.GetFloat( "fireRate" ) * 1000.0f );
fireDeviation = ( int )( spawnArgs.GetFloat( "fireDeviation" ) * 1000.0f );
targetOffset = spawnArgs.GetVector("targetOffset");
targetRadius = spawnArgs.GetFloat("targetRadius");
nextFireTime = 0;
nextBurstTime = 0;
nextEnemyTime = 0;
firing = false;
enemyRate = 200;
GetPhysics()->SetContents(CONTENTS_SOLID);
fl.takedamage = true;
SetEnemy(NULL);
if (spawnArgs.GetBool("enabled")) {
BecomeActive(TH_THINK);
}
}
void hhGun::Save(idSaveGame *savefile) const {
enemy.Save(savefile);
savefile->WriteInt( enemyRate );
savefile->WriteInt( nextEnemyTime );
savefile->WriteVec3( targetOffset );
savefile->WriteFloat( targetRadius );
savefile->WriteInt( fireRate );
savefile->WriteInt( fireDeviation );
savefile->WriteInt( nextFireTime );
savefile->WriteInt( burstRate );
savefile->WriteInt( burstCount );
savefile->WriteInt( nextBurstTime );
savefile->WriteInt( curBurst );
savefile->WriteBool( firing );
savefile->WriteFloat( coneAngle );
}
void hhGun::Restore( idRestoreGame *savefile ) {
enemy.Restore(savefile);
savefile->ReadInt( enemyRate );
savefile->ReadInt( nextEnemyTime );
savefile->ReadVec3( targetOffset );
savefile->ReadFloat( targetRadius );
savefile->ReadInt( fireRate );
savefile->ReadInt( fireDeviation );
savefile->ReadInt( nextFireTime );
savefile->ReadInt( burstRate );
savefile->ReadInt( burstCount );
savefile->ReadInt( nextBurstTime );
savefile->ReadInt( curBurst );
savefile->ReadBool( firing );
savefile->ReadFloat( coneAngle );
}
void hhGun::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location) {
SetEnemy(NULL);
fl.takedamage = false;
BecomeInactive(TH_THINK);
const char *killedModel = spawnArgs.GetString("model_killed", NULL);
if (killedModel) {
SetModel(killedModel);
UpdateVisuals();
}
else {
GetPhysics()->SetContents(0);
}
// Spawn gibs
if (spawnArgs.FindKey("def_debrisspawner")) {
hhUtils::SpawnDebrisMass(spawnArgs.GetString("def_debrisspawner"), this );
}
StartSound( "snd_explode", SND_CHANNEL_ANY );
ActivateTargets( attacker );
}
void hhGun::SetEnemy(idEntity *ent) {
if (ent && ent->IsType(hhPlayer::Type)) {
hhPlayer *player = static_cast<hhPlayer *>(ent);
enemy = player->InVehicle() ? player->GetVehicleInterface()->GetVehicle() : ent;
}
else {
enemy = ent;
}
}
void hhGun::FindEnemy() {
if (enemy.IsValid() && enemy->GetHealth() > 0) {
return;
}
if ( !gameLocal.InPlayerPVS( this ) ) {
return;
}
idEntity *bestEnt = NULL;
float bestDistSqr = idMath::INFINITY;
float radiusSqr = targetRadius*targetRadius;
idVec3 origin = GetPhysics()->GetOrigin();
idMat3 axis = GetPhysics()->GetAxis();
for ( int i = 0; i < MAX_CLIENTS ; i++ ) {
idEntity *ent = gameLocal.entities[ i ];
if (ent) {
idVec3 toEnt = ent->GetPhysics()->GetOrigin() + targetOffset - origin;
float distSqr = toEnt.LengthSqr();
if (distSqr < bestDistSqr && distSqr < radiusSqr &&
AngleBetweenVectors(toEnt, axis[0]) < coneAngle) {
bestDistSqr = distSqr;
bestEnt = ent;
}
}
}
SetEnemy(bestEnt);
}
idMat3 hhGun::GetAimAxis() {
#if 0
// Fast approximation
const idDict *projectileDef = declManager->FindEntityDef( spawnArgs.GetString("def_projectile") );
float projSpeed = idProjectile::GetVelocity( projectileDef ).Length();
idVec3 firePos = GetPhysics()->GetOrigin();
idVec3 enemyPos = enemy->GetPhysics()->GetOrigin() + targetOffset;
idVec3 enemyVel = enemy->GetPhysics()->GetLinearVelocity();
idVec3 toEnemy = enemyPos - firePos;
float enemyDist = toEnemy.Length();
float projTime = enemyDist / projSpeed;
idVec3 predictedEnemyPos = enemyPos + enemyVel * projTime;
idVec3 aim = predictedEnemyPos - firePos;
aim.Normalize();
return aim.hhToMat3();
#else
/* Projectile prediction:
Let:
Pe(t) = position of enemy at time t Ve = velocity of enemy (known)
Pm(t) = position of missile at time t Vm = velocity of missile (magnitude known)
Pf = position firing from
(1) Pe(t) = Pe(0) + Ve * t Enemy position function
(2) Aim(t) = Pe(t) - Pf Aim function is vector from firing point to Pe(t)
|Aim(t)| distance / distance/sec -> sec
(3) -------- = t
|Vm|
(4) d = Pe(0) - Pf delta vector from firing point to initial enemy position
(5) s = |Vm|
Substituting and simplifying yields a quadratic function in terms of t, Ve, d, & s:
At² + Bt + C = 0
*/
const idDict *projectileDef = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_projectile") );
if ( !projectileDef ) {
gameLocal.Error( "Unknown def_projectile: %s\n", spawnArgs.GetString("def_projectile") );
}
float projSpeed = idProjectile::GetVelocity( projectileDef ).Length();
idVec3 firePos = GetPhysics()->GetOrigin();
idVec3 enemyPos = enemy->GetOrigin() + idVec3(0,0,32);
idVec3 enemyVel = enemy->GetPhysics()->GetLinearVelocity();
idVec3 d = enemyPos - firePos;
idVec3 v = enemyVel;
float s = projSpeed;
float a = v.x*v.x + v.y*v.y + v.z*v.x - s*s; // t2 term
float b = 2 * (d.x*v.x + d.y*v.y + d.z*v.z); // t term
float c = d.x*d.x + d.y*d.y + d.z*d.z;
// Use quadratic formula to solve for t
float t1 = (-b + sqrt(b*b - 4*a*c) ) / (2*a);
float t2 = (-b - sqrt(b*b - 4*a*c) ) / (2*a);
float projTime = t1 > 0 ? t1 : t2;
idVec3 predictedEnemyPos = enemyPos + enemyVel * projTime;
idVec3 aim = predictedEnemyPos - firePos;
aim.Normalize();
return aim.hhToMat3();
#endif
}
bool hhGun::ValidEnemy() {
return (enemy.IsValid()) ? enemy->GetHealth() > 0 : false;
}
void hhGun::Fire(idMat3 &axis) {
if (health > 0) {
hhUtils::LaunchProjectile(this, spawnArgs.GetString("def_projectile"), axis, GetOrigin());
StartSound( "snd_fire", SND_CHANNEL_ANY );
}
}
void hhGun::Think(void) {
if (thinkFlags & TH_THINK) {
// Temp: for placement
if (spawnArgs.GetBool("showCone")) {
float radius = targetRadius * tan(DEG2RAD(coneAngle));
gameRenderWorld->DebugCone(colorGreen,
GetPhysics()->GetOrigin(),
GetPhysics()->GetAxis()[0] * targetRadius,
0, radius);
}
if (!ValidEnemy() && gameLocal.time >= nextEnemyTime ) {
FindEnemy();
nextEnemyTime = gameLocal.time + enemyRate;
}
if (ValidEnemy()) {
if (!firing && gameLocal.time >= nextFireTime ) {
firing = true;
curBurst = burstCount;
nextFireTime = gameLocal.time + fireRate + gameLocal.random.CRandomFloat()*fireDeviation;
nextBurstTime = gameLocal.time;
}
if (firing && gameLocal.time >= nextBurstTime ) {
// Aim
idMat3 aimAxis = GetAimAxis();
// Burst if enemy is in cone
if ( AngleBetweenVectors(aimAxis[0], GetPhysics()->GetAxis()[0]) < coneAngle ) {
Fire(aimAxis);
if (--curBurst <= 0) {
firing = false;
}
}
else {
firing = false;
SetEnemy(NULL);
}
nextBurstTime = gameLocal.time + burstRate;
}
}
}
idEntity::Think();
}
void hhGun::Event_Activate(idEntity *activator) {
if (targets.Num() && targets[0].IsValid()) {
Event_FireAt(targets[0].GetEntity());
return;
}
if (thinkFlags & TH_THINK) {
BecomeInactive(TH_THINK);
}
else {
BecomeActive(TH_THINK);
}
}
void hhGun::Event_SetEnemy(idEntity *newEnemy) {
SetEnemy(newEnemy);
}
void hhGun::Event_FireAt(idEntity *victim) {
idVec3 dir = victim->GetOrigin() - GetOrigin();
dir.Normalize();
Fire(dir.ToMat3());
}