mirror of
https://github.com/blendogames/thirtyflightsofloving.git
synced 2024-11-14 16:40:57 +00:00
9481c7c513
Changed Zaero and 3ZB2 game DLLs to use WORLD_SIZE for various calculations instead of 8192. Cleaned up string handling in 3ZB2 game DLL. Added func_plat2, func_door_secret2, and func_force_wall from Rogue to 3ZB2 game DLL. Added alternate attack contact explode for grenade launcher in 3ZB2 game DLL. Added awakening2 game DLL source.
1281 lines
33 KiB
C
1281 lines
33 KiB
C
// g_turret.c
|
|
|
|
//CW: This turret code is taken from the Lazarus mod source (version 2.1),
|
|
// which David Hyde released on 22-Sep-01 for public use.
|
|
// See 'http://planetquake.com/lazarus' for more information.
|
|
|
|
|
|
#include "g_local.h"
|
|
|
|
#define SF_TURRETDRIVER_REMOTE_DRIVER 1
|
|
#define SF_TURRET_PLAYER_CONTROLLABLE 1
|
|
#define SF_TURRET_TRIGGER_SPAWN 2
|
|
#define SF_TURRET_TRACKING 4
|
|
#define SF_TURRET_GOODGUY 8
|
|
#define SF_TURRET_INACTIVE 16
|
|
#define SF_TURRET_MD2 32
|
|
|
|
//CW++
|
|
#define SF_TURRET_TEAM1 64
|
|
#define SF_TURRET_TEAM2 128
|
|
//CW--
|
|
|
|
void NoAmmoWeaponChange (edict_t *ent);
|
|
|
|
// DH++
|
|
// Added TurretTarget to scan the player's view for a damageable target.
|
|
// Used with homing rockets
|
|
edict_t *TurretTarget(edict_t *self)
|
|
{
|
|
edict_t *who;
|
|
edict_t *best;
|
|
trace_t tr;
|
|
float bd;
|
|
float d;
|
|
int i;
|
|
vec3_t dir;
|
|
vec3_t end;
|
|
vec3_t forward;
|
|
vec3_t right;
|
|
vec3_t up;
|
|
vec3_t start;
|
|
|
|
AngleVectors(self->s.angles, forward, right, up);
|
|
VectorMA(self->s.origin, self->move_origin[0], forward, start);
|
|
VectorMA(start, self->move_origin[1], right, start);
|
|
VectorMA(start, self->move_origin[2], up, start);
|
|
VectorMA(start, WORLD_SIZE, forward, end); // was 8192
|
|
|
|
/* Check for aiming directly at a damageable entity */
|
|
tr = gi.trace(start, NULL, NULL, end, self, MASK_SHOT);
|
|
if ((tr.ent->takedamage != DAMAGE_NO) && (tr.ent->solid != SOLID_NOT))
|
|
return tr.ent;
|
|
|
|
/* Check for damageable entity within a tolerance of view angle */
|
|
bd = 0;
|
|
best = NULL;
|
|
for (i = 1, who = g_edicts+1; i < globals.num_edicts; i++, who++)
|
|
{
|
|
if (!who->inuse)
|
|
continue;
|
|
if (who->takedamage == DAMAGE_NO)
|
|
continue;
|
|
if (who->solid == SOLID_NOT)
|
|
continue;
|
|
|
|
VectorMA(who->absmin,0.5,who->size,end);
|
|
tr = gi.trace (start, vec3_origin, vec3_origin, end, self, MASK_OPAQUE);
|
|
if (tr.fraction < 1.0)
|
|
continue;
|
|
|
|
VectorSubtract(end, self->s.origin, dir);
|
|
VectorNormalize(dir);
|
|
d = DotProduct(forward, dir);
|
|
if (d > bd)
|
|
{
|
|
bd = d;
|
|
best = who;
|
|
}
|
|
}
|
|
if (bd > 0.90)
|
|
return best;
|
|
|
|
return NULL;
|
|
}
|
|
//DH--
|
|
|
|
void turret_blocked(edict_t *self, edict_t *other)
|
|
{
|
|
edict_t *attacker;
|
|
edict_t *ent;
|
|
edict_t *master;
|
|
|
|
if (other == world)
|
|
{
|
|
// world brush - stop
|
|
self->avelocity[YAW] = 0;
|
|
if (self->team)
|
|
{
|
|
for (ent = self->teammaster; ent; ent = ent->teamchain)
|
|
ent->avelocity[YAW] = 0;
|
|
}
|
|
if (self->owner)
|
|
self->owner->avelocity[YAW] = 0;
|
|
|
|
gi.linkentity(self);
|
|
}
|
|
|
|
if (other->takedamage)
|
|
{
|
|
vec3_t dir;
|
|
VectorSubtract(other->s.origin,self->s.origin,dir);
|
|
VectorNormalize(dir);
|
|
|
|
if (self->teammaster)
|
|
master = self->teammaster;
|
|
else
|
|
master = self;
|
|
|
|
if (self->teammaster)
|
|
{
|
|
if (self->teammaster->owner)
|
|
attacker = self->teammaster->owner;
|
|
else
|
|
attacker = self->teammaster;
|
|
}
|
|
else if(self->owner)
|
|
attacker = self->owner;
|
|
else
|
|
attacker = self;
|
|
|
|
// give a big kickback to help prevent getting stuck
|
|
T_Damage(other, self, attacker, dir, other->s.origin, vec3_origin, master->dmg, 50, 0, MOD_CRUSH);
|
|
}
|
|
if (!(other->svflags & SVF_MONSTER) && (!other->client))
|
|
{
|
|
T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
|
|
if (other)
|
|
BecomeExplosion1(other);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
void turret_breach_fire(edict_t *self)
|
|
{
|
|
edict_t *owner;
|
|
vec3_t forward;
|
|
vec3_t right;
|
|
vec3_t up;
|
|
vec3_t start;
|
|
|
|
AngleVectors(self->s.angles, forward, right, up);
|
|
VectorMA(self->s.origin, self->move_origin[0], forward, start);
|
|
VectorMA(start, self->move_origin[1], right, start);
|
|
VectorMA(start, self->move_origin[2], up, start);
|
|
|
|
// DWH: automated turrets have no driver, so use self
|
|
if (self->owner && !(self->owner->spawnflags & SF_TURRETDRIVER_REMOTE_DRIVER))
|
|
owner = self->owner;
|
|
else
|
|
owner = self;
|
|
/*
|
|
ed - self->teammaster->owner causes quake 2 to crash when the player uses BUTTON_SHOOT
|
|
its been changed to self->owner incase anything weird happens.
|
|
*/
|
|
//FIXME : only use the normal damages if self->owner (turret_driver) doesn't have one
|
|
|
|
if (self->delay <= level.time + 0.001) //CW: Was just < , but this screwed up the RoF
|
|
{ //CW: Also, added 0.001 to account for fp error
|
|
switch (self->sounds)
|
|
{
|
|
case 1: // Rocket Launcher
|
|
Fire_Rocket(owner, start, forward, self->mass, sv_rocket_speed->value, 150, self->mass, false);
|
|
gi.positioned_sound(start, self, CHAN_WEAPON, gi.soundindex("weapons/rocklf1a.wav"), 1, ATTN_NORM, 0);
|
|
self->delay = level.time + self->wait;
|
|
break;
|
|
|
|
case 2: // Railgun
|
|
Fire_Rail(owner, start, forward, self->mass, 2);
|
|
gi.positioned_sound(start, self, CHAN_WEAPON, gi.soundindex("weapons/railgf1a.wav"), 1, ATTN_NORM, 0);
|
|
gi.WriteByte(svc_muzzleflash);
|
|
gi.WriteShort(self-g_edicts);
|
|
gi.WriteByte(MZ_RAILGUN);
|
|
gi.multicast(start, MULTICAST_PVS);
|
|
self->delay = level.time + self->wait;
|
|
break;
|
|
|
|
case 3: // Machinegun
|
|
if (++self->style > 4)
|
|
{
|
|
vec3_t t_start;
|
|
trace_t tr;
|
|
|
|
VectorMA(start, 300.0, forward, t_start);
|
|
tr = gi.trace(start, NULL, NULL, t_start, self, MASK_SHOT);
|
|
if (tr.fraction == 1.0)
|
|
Fire_Tracer(owner, t_start, forward, 2000.0, 0.3);
|
|
|
|
self->style = 0;
|
|
}
|
|
Fire_Bullet(owner, start, forward, self->mass, 4, DEFAULT_MGTURRET_HSPREAD, DEFAULT_MGTURRET_VSPREAD, MOD_MACHINEGUN);
|
|
gi.positioned_sound(start, self, CHAN_WEAPON, gi.soundindex("turret/mchngun.wav"), 1, ATTN_NORM, 0);
|
|
self->delay = level.time + FRAMETIME; // no delay on machinegun (=> fires every server frame)
|
|
break;
|
|
//CW++
|
|
case 4: // E.S.G.
|
|
Fire_Spike(owner, start, forward, self->mass, sv_spike_speed->value, 2, sv_spike_bang_radius->value, (int)sv_spike_bang_damage->value);
|
|
gi.positioned_sound(start, self, CHAN_WEAPON, gi.soundindex("weapons/esg/fire.wav"), 1, ATTN_NORM, 0);
|
|
self->delay = level.time + self->wait;
|
|
break;
|
|
|
|
case 5: // Shock Rifle
|
|
Fire_Shock(self, start, forward, 0, sv_shock_speed->value, 2, self->mass, sv_shock_radius->value, false);
|
|
gi.WriteByte(svc_muzzleflash);
|
|
gi.WriteShort(self-g_edicts);
|
|
gi.WriteByte(MZ_BFG);
|
|
gi.multicast(self->s.origin, MULTICAST_PVS);
|
|
self->delay = level.time + self->wait;
|
|
break;
|
|
|
|
case 6: // Plasma Launcher
|
|
Fire_Plasma(owner, start, forward, self->mass, sv_plasma_speed->value);
|
|
gi.positioned_sound(start, self, CHAN_WEAPON, gi.soundindex("floater/fltatck1.wav"), 1, ATTN_NORM, 0);
|
|
self->delay = level.time + self->wait;
|
|
break;
|
|
//CW--
|
|
default: // Rocket Launcher
|
|
Fire_Rocket(owner, start, forward, self->mass, sv_rocket_speed->value, 150, self->mass, false);
|
|
gi.positioned_sound(start, self, CHAN_WEAPON, gi.soundindex("weapons/rocklf1a.wav"), 1, ATTN_NORM, 0);
|
|
self->delay = level.time + self->wait;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void turret_disengage(edict_t *self)
|
|
{
|
|
edict_t *ent;
|
|
vec3_t forward;
|
|
int i;
|
|
|
|
// level the gun
|
|
self->move_angles[0] = 0.0;
|
|
|
|
ent = self->owner;
|
|
|
|
//ed - to keep remove tracking of the entity
|
|
ent->turret = NULL;
|
|
|
|
// throw them back from turret
|
|
AngleVectors(self->s.angles, forward, NULL, NULL);
|
|
VectorScale(forward, -300.0, forward);
|
|
forward[2] = forward[2] + 150.0;
|
|
if (forward[2] < 80.0)
|
|
forward[2] = 80.0;
|
|
|
|
for (i = 0; i < 3; i++)
|
|
ent->velocity[i] = forward[i];
|
|
|
|
ent->s.origin[2] = ent->s.origin[2] + 1.0;
|
|
ent->movetype = MOVETYPE_WALK;
|
|
ent->gravity = 1.0;
|
|
|
|
ent->flags &= ~FL_TURRET_OWNER;
|
|
|
|
// turn ON client side prediction for this player
|
|
ent->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
|
|
|
|
gi.linkentity(ent);
|
|
|
|
self->owner = NULL;
|
|
}
|
|
|
|
void turret_turn(edict_t *self)
|
|
{
|
|
vec3_t current_angles;
|
|
vec3_t delta;
|
|
qboolean yaw_restrict;
|
|
|
|
VectorCopy(self->s.angles, current_angles);
|
|
AnglesNormalize(current_angles);
|
|
|
|
if (self->viewer && self->viewer->client)
|
|
{
|
|
gclient_t *client = self->viewer->client;
|
|
|
|
if ((client->old_owner_angles[0] != client->ucmd.angles[0]) || (client->old_owner_angles[1] != client->ucmd.angles[1]))
|
|
{
|
|
// Give game a bit of time to catch up after player causes ucmd pitch angle to roll over...
|
|
// otherwise we'll hit on the above test even though player hasn't hit +lookup/+lookdown
|
|
float delta;
|
|
|
|
delta = level.time - self->touch_debounce_time;
|
|
if ((delta < 0.0) || (delta > 1.0))
|
|
{
|
|
float delta_angle;
|
|
float fastest = self->speed * FRAMETIME;
|
|
|
|
delta_angle = SHORT2ANGLE(client->ucmd.angles[0]-client->old_owner_angles[0]);
|
|
if (delta_angle < -180.0)
|
|
delta_angle += 360.0;
|
|
if (delta_angle > 180.0)
|
|
delta_angle -= 360.0;
|
|
if (delta_angle > fastest)
|
|
delta_angle = fastest;
|
|
if (delta_angle < -fastest)
|
|
delta_angle = -fastest;
|
|
self->move_angles[0] += delta_angle;
|
|
|
|
delta_angle = SHORT2ANGLE(client->ucmd.angles[1]-client->old_owner_angles[1]);
|
|
if (delta_angle < -180.0)
|
|
delta_angle += 360.0;
|
|
if (delta_angle > 180.0)
|
|
delta_angle -= 360.0;
|
|
if (delta_angle > fastest)
|
|
delta_angle = fastest;
|
|
if (delta_angle < -fastest)
|
|
delta_angle = -fastest;
|
|
self->move_angles[1] += delta_angle;
|
|
|
|
client->old_owner_angles[0] = client->ucmd.angles[0];
|
|
client->old_owner_angles[1] = client->ucmd.angles[1];
|
|
}
|
|
self->touch_debounce_time = level.time + 5.0;
|
|
}
|
|
}
|
|
|
|
//=======
|
|
AnglesNormalize(self->move_angles);
|
|
|
|
// clamp angles to mins & maxs
|
|
if (self->move_angles[PITCH] > self->pos1[PITCH])
|
|
self->move_angles[PITCH] = self->pos1[PITCH];
|
|
else if (self->move_angles[PITCH] < self->pos2[PITCH])
|
|
self->move_angles[PITCH] = self->pos2[PITCH];
|
|
|
|
// Lazarus: Special case - if there are no constraints on YAW, don't adjust angle
|
|
if ((self->pos1[YAW] != 0.0) || (self->pos2[YAW] != 360.0))
|
|
yaw_restrict = true;
|
|
else
|
|
yaw_restrict = false;
|
|
|
|
if (yaw_restrict)
|
|
{
|
|
float yaw_range;
|
|
float yaw_base;
|
|
|
|
yaw_range = self->pos2[YAW] - self->pos1[YAW];
|
|
if (yaw_range < 0.0)
|
|
yaw_range += 360.0;
|
|
|
|
yaw_base = self->move_angles[YAW] - self->pos1[YAW];
|
|
if (yaw_base < 0.0)
|
|
yaw_base += 360.0;
|
|
|
|
if (yaw_base > yaw_range)
|
|
{
|
|
float dmin;
|
|
float dmax;
|
|
|
|
dmin = fabs(self->pos1[YAW] - self->move_angles[YAW]);
|
|
if (dmin < 0.0)
|
|
dmin += 360.0;
|
|
else if (dmin > 360.0)
|
|
dmin -= 360.0;
|
|
|
|
dmax = fabs(self->pos2[YAW] - self->move_angles[YAW]);
|
|
if (dmax < 0.0)
|
|
dmax += 360.0;
|
|
else if (dmax > 360.0)
|
|
dmax -= 360.0;
|
|
|
|
if (fabs(dmin) < fabs(dmax))
|
|
self->move_angles[YAW] = self->pos1[YAW];
|
|
else
|
|
self->move_angles[YAW] = self->pos2[YAW];
|
|
}
|
|
}
|
|
|
|
VectorSubtract(self->move_angles, current_angles, delta);
|
|
if (delta[0] < -180.0)
|
|
delta[0] += 360.0;
|
|
else if (delta[0] > 180.0)
|
|
delta[0] -= 360.0;
|
|
|
|
if (delta[1] < -180.0)
|
|
delta[1] += 360.0;
|
|
else if (delta[1] > 180.0)
|
|
delta[1] -= 360.0;
|
|
|
|
delta[2] = 0.0;
|
|
|
|
VectorScale(delta, 1.0/FRAMETIME, delta);
|
|
if (delta[0] > self->speed)
|
|
delta[0] = self->speed;
|
|
if (delta[0] < -self->speed)
|
|
delta[0] = -self->speed;
|
|
if (delta[1] > self->speed)
|
|
delta[1] = self->speed;
|
|
if (delta[1] < -self->speed)
|
|
delta[1] = -self->speed;
|
|
VectorCopy(delta, self->avelocity);
|
|
|
|
if (self->team)
|
|
{
|
|
edict_t *ent;
|
|
for (ent = self->teammaster; ent; ent = ent->teamchain)
|
|
{
|
|
ent->avelocity[1] = self->avelocity[1];
|
|
if (ent->solid == SOLID_NOT)
|
|
ent->avelocity[0] = self->avelocity[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
void turret_breach_think(edict_t *self)
|
|
{
|
|
edict_t *ent;
|
|
edict_t *victim;
|
|
trace_t tr;
|
|
vec3_t dir, angles;
|
|
vec3_t target;
|
|
qboolean remote_monster;
|
|
qboolean yaw_restrict;
|
|
float yaw_r;
|
|
float yaw_0 = 0; //CW
|
|
int i;
|
|
|
|
turret_turn(self);
|
|
yaw_r = self->pos2[YAW] - self->pos1[YAW];
|
|
if (yaw_r < 0.0)
|
|
yaw_r += 360.0;
|
|
|
|
if ((self->pos1[YAW] != 0.0) || (self->pos2[YAW] != 360.0))
|
|
yaw_restrict = true;
|
|
else
|
|
yaw_restrict = false;
|
|
|
|
self->nextthink = level.time + FRAMETIME;
|
|
|
|
if (self->deadflag == DEAD_DEAD)
|
|
return;
|
|
|
|
remote_monster = false;
|
|
|
|
if (self->owner || self->viewer)
|
|
{
|
|
if (!(self->spawnflags & SF_TURRET_MD2))
|
|
{
|
|
self->s.effects &= ~EF_ANIM23;
|
|
self->s.effects |= EF_ANIM01;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!(self->spawnflags & SF_TURRET_MD2))
|
|
{
|
|
self->s.effects &= ~EF_ANIM01;
|
|
self->s.effects |= EF_ANIM23;
|
|
}
|
|
}
|
|
|
|
if (self->team)
|
|
{
|
|
for (ent = self->teammaster; ent; ent = ent->teamchain)
|
|
{
|
|
if (ent != self->owner)
|
|
{
|
|
if (ent->solid != SOLID_NOT)
|
|
ent->s.effects = self->s.effects;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if we have a driver, adjust his velocities
|
|
if (self->owner && !remote_monster)
|
|
{
|
|
if (self->owner->target_ent == self)
|
|
{
|
|
float angle;
|
|
float diff;
|
|
float target_z;
|
|
vec3_t dir;
|
|
|
|
// angular is easy, just copy ours
|
|
self->owner->avelocity[0] = self->avelocity[0];
|
|
self->owner->avelocity[1] = self->avelocity[1];
|
|
|
|
// x & y
|
|
angle = self->s.angles[1] + self->owner->move_origin[1];
|
|
//CW++
|
|
angle = DEG2RAD(angle);
|
|
//CW--
|
|
target[0] = SnapToEights(self->s.origin[0] + cos(angle) * self->owner->move_origin[0]);
|
|
target[1] = SnapToEights(self->s.origin[1] + sin(angle) * self->owner->move_origin[0]);
|
|
target[2] = self->owner->s.origin[2];
|
|
|
|
VectorSubtract (target, self->owner->s.origin, dir);
|
|
self->owner->velocity[0] = dir[0] * 1.0 / FRAMETIME;
|
|
self->owner->velocity[1] = dir[1] * 1.0 / FRAMETIME;
|
|
|
|
// z
|
|
//CW++
|
|
angle = DEG2RAD(self->s.angles[PITCH]);
|
|
//CW--
|
|
target_z = SnapToEights(self->s.origin[2] + self->owner->move_origin[0] * tan(angle) + self->owner->move_origin[2]);
|
|
|
|
diff = target_z - self->owner->s.origin[2];
|
|
self->owner->velocity[2] = diff * 1.0 / FRAMETIME;
|
|
|
|
if (self->spawnflags & 65536)
|
|
{
|
|
turret_breach_fire(self);
|
|
self->spawnflags &= ~65536;
|
|
}
|
|
return;
|
|
}
|
|
else if (self->spawnflags & SF_TURRET_PLAYER_CONTROLLABLE)
|
|
{ // a player is controlling the turret, move towards view angles
|
|
vec3_t target, forward;
|
|
|
|
for (i = 0; i < 3; i++)
|
|
self->move_angles[i] = self->owner->client->v_angle[i];
|
|
|
|
// FIXME: do a tracebox from up and behind towards the turret, to try and keep them from
|
|
// getting stuck inside the rotating turre
|
|
// x & y
|
|
AngleVectors(self->s.angles, forward, NULL, NULL);
|
|
VectorScale(forward, 32.0, forward);
|
|
VectorSubtract(self->s.origin, forward, target);
|
|
VectorAdd(target, tv(0.0,0.0,8.0), self->owner->s.origin);
|
|
gi.linkentity(self->owner);
|
|
|
|
if (self->owner->client->ps.pmove.velocity[2] > 15)
|
|
turret_disengage(self);
|
|
}
|
|
}
|
|
else if ((self->spawnflags & SF_TURRET_PLAYER_CONTROLLABLE) && !remote_monster)
|
|
{ // check if a player has mounted the turret
|
|
|
|
edict_t *ent;
|
|
vec3_t target;
|
|
vec3_t forward;
|
|
vec3_t dir;
|
|
int i;
|
|
|
|
// find a player
|
|
ent = &g_edicts[0];
|
|
ent++;
|
|
for (i = 0; i < (int)maxclients->value; i++, ent++)
|
|
{
|
|
if (!ent->inuse)
|
|
continue;
|
|
if (ent->solid == SOLID_NOT)
|
|
continue;
|
|
|
|
// determine distance from turret seat location
|
|
|
|
// x & y
|
|
AngleVectors(self->s.angles, forward, NULL, NULL);
|
|
VectorScale(forward, 32.0, forward);
|
|
VectorSubtract(self->s.origin, forward, target);
|
|
|
|
VectorSubtract(target, ent->s.origin, dir);
|
|
if (fabs(dir[2]) < 64.0)
|
|
dir[2] = 0.0;
|
|
|
|
if (VectorLength(dir) < 16.0)
|
|
{ // player has taken control of turret
|
|
self->owner = ent;
|
|
ent->movetype = MOVETYPE_PUSH; // don't let them move, or they'll get stuck
|
|
ent->gravity = 0.0;
|
|
|
|
//ed - to keep track of the entity
|
|
ent->turret = self;
|
|
|
|
// turn off client side prediction for this player
|
|
ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
|
|
|
|
gi.linkentity(ent);
|
|
|
|
//ed - set the flag on the client so that when they shoot the
|
|
// turret shoots instead of "using" it
|
|
ent->flags |= FL_TURRET_OWNER;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((self->spawnflags & SF_TURRET_TRACKING) && (!self->owner || remote_monster))
|
|
{
|
|
// TRACK - automated turret
|
|
edict_t *gomer;
|
|
vec3_t f;
|
|
vec3_t forward;
|
|
vec3_t right;
|
|
vec3_t up;
|
|
vec3_t start;
|
|
vec3_t t_start;
|
|
float best_dist = WORLD_SIZE; // was 8192.0
|
|
float dist;
|
|
|
|
if (self->viewer && (level.time < self->touch_debounce_time))
|
|
return;
|
|
|
|
AngleVectors(self->s.angles, forward, right, up);
|
|
VectorMA(self->s.origin,self->move_origin[0], forward, start);
|
|
VectorMA(start, self->move_origin[1], right, start);
|
|
VectorMA(start, self->move_origin[2], up, start);
|
|
|
|
self->oldenemy = self->enemy;
|
|
|
|
if (self->enemy)
|
|
{
|
|
// Check that current enemy is valid. if so, find
|
|
// distance. Don't switch enemies unless another
|
|
// monster is at least 100 units closer to the camera
|
|
if (self->enemy->inuse)
|
|
{
|
|
if ((self->enemy->health > self->enemy->gib_health) && !(self->enemy->svflags & SVF_NOCLIENT) &&
|
|
!(self->enemy->flags & FL_NOTARGET))
|
|
{
|
|
if (gi.inPVS(self->s.origin,self->enemy->s.origin))
|
|
{
|
|
VectorMA(self->enemy->absmin,0.5,self->enemy->size,target);
|
|
VectorSubtract(target,self->s.origin,dir);
|
|
vectoangles(dir,angles);
|
|
AnglesNormalize(angles);
|
|
if (yaw_restrict)
|
|
{
|
|
yaw_0 = angles[YAW] - self->pos1[YAW];
|
|
if (yaw_0 < 0.0)
|
|
yaw_0 += 360.0;
|
|
}
|
|
|
|
if ((angles[PITCH] > self->pos1[PITCH]) || (angles[PITCH] < self->pos2[PITCH]) || (yaw_restrict && (yaw_0 > yaw_r)))
|
|
self->enemy = NULL;
|
|
else
|
|
{
|
|
VectorCopy(self->s.origin,t_start);
|
|
VectorCopy(dir,f);
|
|
VectorNormalize(f);
|
|
VectorMA(t_start, self->teammaster->base_radius, f, t_start);
|
|
tr = gi.trace(t_start, vec3_origin, vec3_origin, target, self, MASK_SHOT);
|
|
if (tr.ent == self->enemy)
|
|
{
|
|
VectorSubtract(target, self->s.origin, dir);
|
|
best_dist = VectorLength(dir) - 100.0;
|
|
}
|
|
else
|
|
self->enemy = NULL;
|
|
}
|
|
}
|
|
else
|
|
self->enemy = NULL;
|
|
}
|
|
else
|
|
self->enemy = NULL;
|
|
}
|
|
else
|
|
self->enemy = NULL;
|
|
}
|
|
|
|
// for GOODGUY weapon-firing turrets, if current enemy is a player or GOODGUY monster,
|
|
// reset best_dist so that bad monsters will be selected if found, regardless of distance.
|
|
if ((self->enemy) && (self->sounds >= 0) && (self->spawnflags & SF_TURRET_GOODGUY))
|
|
{
|
|
if (self->enemy->client || (self->enemy->monsterinfo.aiflags & AI_GOOD_GUY))
|
|
best_dist = WORLD_SIZE; // was 8192.0
|
|
}
|
|
|
|
// for weapon-firing turrets, if GOODGUY is set and we already have an enemy, we're done.
|
|
if ((self->sounds >= 0) && (self->spawnflags & SF_TURRET_GOODGUY) && self->enemy)
|
|
goto good_enemy;
|
|
|
|
// for non-GOODGUY weapon-firing turrets, reset best_dist so that players will
|
|
// ALWAYS be selected if found
|
|
if ((self->sounds >= 0) && !(self->spawnflags & SF_TURRET_GOODGUY))
|
|
best_dist = WORLD_SIZE; // was 8192.0
|
|
|
|
// hunt for closest player - hunt ALL entities since we want to view fake players using camera
|
|
for (i = 1; i < globals.num_edicts; i++)
|
|
{
|
|
gomer = g_edicts + i;
|
|
if (!gomer->inuse)
|
|
continue;
|
|
if (!gomer->client)
|
|
continue;
|
|
if (gomer->svflags & SVF_NOCLIENT)
|
|
continue;
|
|
if (gomer->health < gomer->gib_health)
|
|
continue;
|
|
if (gomer->flags & FL_NOTARGET)
|
|
continue;
|
|
if (!gi.inPVS(self->s.origin, gomer->s.origin))
|
|
continue;
|
|
|
|
//CW++
|
|
// Don't fire at team-mates.
|
|
|
|
if ((sv_gametype->value == G_CTF) || (sv_gametype->value == G_TDM))
|
|
{
|
|
if ((self->spawnflags & SF_TURRET_TEAM1) && (gomer->client->resp.ctf_team == CTF_TEAM1))
|
|
continue;
|
|
|
|
if ((self->spawnflags & SF_TURRET_TEAM2) && (gomer->client->resp.ctf_team == CTF_TEAM2))
|
|
continue;
|
|
}
|
|
else if (sv_gametype->value == G_ASLT)
|
|
{
|
|
if (self->spawnflags & SF_TURRET_TEAM1)
|
|
{
|
|
if (gomer->client->resp.ctf_team == asltgame.t_attack)
|
|
continue;
|
|
}
|
|
|
|
if (self->spawnflags & SF_TURRET_TEAM2)
|
|
{
|
|
if (gomer->client->resp.ctf_team != asltgame.t_attack)
|
|
continue;
|
|
}
|
|
}
|
|
//CW--
|
|
|
|
VectorMA(gomer->absmin, 0.5, gomer->size, target);
|
|
VectorCopy(self->s.origin, t_start);
|
|
VectorSubtract(target, self->s.origin, dir);
|
|
VectorCopy(dir, f);
|
|
VectorNormalize(f);
|
|
VectorMA(t_start, self->teammaster->base_radius, f, t_start);
|
|
tr = gi.trace(t_start, vec3_origin, vec3_origin, target, self, MASK_SHOT);
|
|
if (tr.ent == gomer)
|
|
{
|
|
vectoangles(dir, angles);
|
|
AnglesNormalize(angles);
|
|
if (yaw_restrict)
|
|
{
|
|
yaw_0 = angles[YAW] - self->pos1[YAW];
|
|
if (yaw_0 < 0.0)
|
|
yaw_0 += 360.0;
|
|
}
|
|
if ((angles[PITCH] <= self->pos1[PITCH]) && (angles[PITCH] >= self->pos2[PITCH]) &&
|
|
(!yaw_restrict || (yaw_0 <= yaw_r)))
|
|
{
|
|
dist = VectorLength(dir);
|
|
if (dist < best_dist)
|
|
{
|
|
self->enemy = gomer;
|
|
best_dist = dist;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
good_enemy:
|
|
if (self->enemy)
|
|
{
|
|
if (self->enemy != self->oldenemy)
|
|
{
|
|
self->monsterinfo.trail_time = level.time;
|
|
self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
|
|
}
|
|
|
|
VectorCopy(self->enemy->s.origin, target);
|
|
if (self->enemy->deadflag)
|
|
target[2] -= 16.0;
|
|
|
|
VectorSubtract(target, self->s.origin, dir);
|
|
VectorNormalize(dir);
|
|
vectoangles(dir, self->move_angles);
|
|
|
|
// decide if we should shoot
|
|
victim = NULL;
|
|
if (self->spawnflags & SF_TURRET_GOODGUY)
|
|
{
|
|
if ((self->enemy->svflags & SVF_MONSTER) && !(self->enemy->monsterinfo.aiflags & AI_GOOD_GUY))
|
|
victim = self->enemy;
|
|
}
|
|
else
|
|
{
|
|
if (self->enemy->client)
|
|
victim = self->enemy;
|
|
}
|
|
|
|
if (victim && (self->sounds >= 0) && (DotProduct(forward,dir) > 0.99))
|
|
{
|
|
// never automatically fire a turret remotely controlled by a player
|
|
if (!self->viewer || (self->viewer && !self->viewer->client))
|
|
{
|
|
//CW++
|
|
if (level.time < self->monsterinfo.trail_time)
|
|
//CW--
|
|
{
|
|
turret_turn(self);
|
|
return;
|
|
}
|
|
|
|
self->monsterinfo.attack_finished = level.time; //CW
|
|
if (self->sounds != 3) //CW
|
|
self->monsterinfo.attack_finished += self->wait;
|
|
//CW++
|
|
else
|
|
self->monsterinfo.attack_finished += FRAMETIME;
|
|
//CW--
|
|
turret_breach_fire(self);
|
|
turret_turn(self);
|
|
}
|
|
}
|
|
else
|
|
turret_turn(self);
|
|
}
|
|
}
|
|
// If turret has no enemy and isn't controlled by a player or monster, check for "followtarget"
|
|
if ((!self->enemy) && ((!self->owner) || remote_monster))
|
|
{
|
|
if (self->followtarget)
|
|
{
|
|
self->enemy = G_Find(NULL, FOFS(targetname), self->followtarget);
|
|
if (self->enemy)
|
|
{
|
|
VectorMA(self->enemy->absmin, 0.5, self->enemy->size, target);
|
|
VectorSubtract(target, self->s.origin, dir);
|
|
vectoangles(dir, self->move_angles);
|
|
turret_turn(self);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void turret_breach_finish_init(edict_t *self)
|
|
{
|
|
// get and save info for muzzle location
|
|
if (!self->target)
|
|
gi.dprintf("%s at %s needs a target\n", self->classname, vtos(self->s.origin));
|
|
else
|
|
{
|
|
self->target_ent = G_PickTarget(self->target);
|
|
if (!self->target_ent)
|
|
{
|
|
gi.dprintf("%s at %s, target %s does not exist\n", self->classname, vtos(self->s.origin), self->target);
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
VectorSubtract(self->target_ent->s.origin, self->s.origin, self->move_origin);
|
|
G_FreeEdict(self->target_ent);
|
|
}
|
|
|
|
if (!self->team)
|
|
self->teammaster = self;
|
|
|
|
self->teammaster->dmg = self->dmg;
|
|
|
|
if (!(self->spawnflags & (SF_TURRET_TRIGGER_SPAWN | SF_TURRET_GOODGUY | SF_TURRET_INACTIVE)))
|
|
{
|
|
self->think = turret_breach_think;
|
|
self->think(self);
|
|
}
|
|
else
|
|
{
|
|
self->think = NULL;
|
|
self->nextthink = 0.0;
|
|
}
|
|
}
|
|
|
|
void turret_die_temp_think(edict_t *self)
|
|
{
|
|
edict_t *target;
|
|
|
|
target = G_Find(NULL, FOFS(targetname), self->destroytarget);
|
|
while (target)
|
|
{
|
|
if (target && target->use)
|
|
target->use(target, self->target_ent, self->target_ent);
|
|
|
|
target = G_Find(target, FOFS(targetname), self->destroytarget);
|
|
}
|
|
|
|
G_FreeEdict(self);
|
|
}
|
|
|
|
void turret_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
|
|
{
|
|
edict_t *ent;
|
|
edict_t *player;
|
|
int i;
|
|
|
|
// ensure turret_base stops rotating
|
|
if (self->team)
|
|
{
|
|
for (ent = self->teammaster; ent; ent = ent->teamchain)
|
|
{
|
|
if (ent != self)
|
|
{
|
|
ent->avelocity[1] = 0.0;
|
|
gi.linkentity(ent);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (self->deadflag != DEAD_DEAD)
|
|
{
|
|
// if turret has a driver, kill him too unless he's a "remote" driver
|
|
if (self->owner && (self->owner->target_ent == self))
|
|
T_Damage(self->owner, inflictor, attacker, vec3_origin, self->owner->s.origin, vec3_origin, 100000, 1, 0, 0);
|
|
|
|
// if turret is being used as a camera by a player, turn camera off for that player
|
|
for (i = 0, player = g_edicts+1; i < (int)maxclients->value; i++, player++)
|
|
{
|
|
if (player->client && (player->client->spycam == self))
|
|
camera_off(player);
|
|
}
|
|
if (self->deathtarget)
|
|
{
|
|
edict_t *target;
|
|
target = G_Find(NULL, FOFS(targetname), self->deathtarget);
|
|
while (target)
|
|
{
|
|
if (target && target->use)
|
|
target->use(target, attacker, attacker);
|
|
target = G_Find(target, FOFS(targetname), self->deathtarget);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (self->health <= self->gib_health)
|
|
{
|
|
if (self->destroytarget)
|
|
{
|
|
if (self->deadflag == DEAD_DEAD)
|
|
{
|
|
// we were already dead, so deathtarget has been fired
|
|
edict_t *target;
|
|
target = G_Find(NULL, FOFS(targetname), self->destroytarget);
|
|
while (target)
|
|
{
|
|
if (target && target->use)
|
|
target->use(target,attacker,attacker);
|
|
|
|
target = G_Find(target, FOFS(targetname), self->destroytarget);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// we were killed and gibbed in the same frame. postpone destroytarget just a bit
|
|
edict_t *temp;
|
|
temp = G_Spawn();
|
|
temp->solid = SOLID_NOT;
|
|
temp->svflags = SVF_NOCLIENT;
|
|
temp->think = turret_die_temp_think;
|
|
temp->nextthink = level.time + (2.0 * FRAMETIME);
|
|
temp->destroytarget = self->destroytarget;
|
|
temp->target_ent = attacker;
|
|
//CW++
|
|
temp->classname = "temp_turret_die";
|
|
//CW--
|
|
gi.linkentity(temp);
|
|
}
|
|
self->nextthink = 0.0;
|
|
gi.linkentity(self);
|
|
}
|
|
if (self->dmg > 0)
|
|
BecomeExplosion1(self);
|
|
else
|
|
G_FreeEdict(self);
|
|
|
|
return;
|
|
}
|
|
|
|
if (self->deadflag == DEAD_DEAD)
|
|
return;
|
|
|
|
self->deadflag = DEAD_DEAD;
|
|
self->takedamage = DAMAGE_YES;
|
|
|
|
// slow turret down and level it... or for MD2 turrets set to minpitch
|
|
self->speed *= 0.25; //CW
|
|
if (self->spawnflags & SF_TURRET_MD2)
|
|
self->move_angles[0] = self->pos1[0];
|
|
else
|
|
self->move_angles[0] = 0.0;
|
|
}
|
|
|
|
void toggle_turret_breach(edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
if (!(self->spawnflags & SF_TURRET_INACTIVE))
|
|
{
|
|
self->spawnflags |= SF_TURRET_INACTIVE;
|
|
VectorCopy(self->s.angles, self->move_angles);
|
|
if (self->team)
|
|
{
|
|
edict_t *ent;
|
|
for (ent = self->teammaster; ent; ent = ent->teamchain)
|
|
{
|
|
VectorClear(ent->avelocity);
|
|
gi.linkentity(ent);
|
|
}
|
|
}
|
|
self->think = NULL;
|
|
self->nextthink = 0.0;
|
|
}
|
|
else
|
|
{
|
|
self->spawnflags &= ~SF_TURRET_INACTIVE;
|
|
self->think = turret_breach_think;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
}
|
|
}
|
|
|
|
void use_turret_breach(edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
if (self->spawnflags & SF_TURRET_TRIGGER_SPAWN)
|
|
{
|
|
self->spawnflags &= ~SF_TURRET_TRIGGER_SPAWN;
|
|
self->svflags &= ~SVF_NOCLIENT;
|
|
|
|
if(self->spawnflags & SF_TURRET_MD2)
|
|
self->solid = SOLID_BBOX;
|
|
else
|
|
self->solid = SOLID_BSP;
|
|
|
|
self->think = turret_breach_think;
|
|
self->think(self);
|
|
}
|
|
}
|
|
void turret_breach_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
// This added for Lazarus to help prevent player from becoming stuck when
|
|
// jumping onto a TRACK turret.
|
|
|
|
// We only care about TRACK turrets. For monster controlled turrets the angles
|
|
// should of course be controlled by the monster.
|
|
if (!(self->spawnflags & SF_TURRET_TRACKING))
|
|
return;
|
|
|
|
// We only care about players... everybody else knows better than to
|
|
// get tangled up with turret :-)
|
|
if (!other->client)
|
|
return;
|
|
|
|
// Do nothing for turrets that already have an enemy
|
|
if (self->enemy)
|
|
return;
|
|
|
|
if ((other->client) && (other->absmin[2] > self->s.origin[2]))
|
|
{
|
|
if (fabs(self->s.angles[PITCH] - self->pos1[PITCH]) < fabs(self->s.angles[PITCH] - self->pos2[PITCH]))
|
|
self->move_angles[PITCH] = self->pos2[PITCH];
|
|
else
|
|
self->move_angles[PITCH] = self->pos1[PITCH];
|
|
|
|
if (fabs(self->s.angles[YAW] - self->pos1[YAW]) < fabs(self->s.angles[YAW] - self->pos2[YAW]))
|
|
self->move_angles[YAW] = self->pos2[YAW];
|
|
else
|
|
self->move_angles[YAW] = self->pos1[YAW];
|
|
}
|
|
}
|
|
|
|
void SP_turret_breach(edict_t *self)
|
|
{
|
|
// Good guy turrets shoot at monsters, not players. Turn TRACK on if it ain't already.
|
|
if (self->spawnflags & SF_TURRET_GOODGUY)
|
|
self->spawnflags |= (SF_TURRET_TRACKING | SF_TURRET_INACTIVE);
|
|
|
|
if (self->spawnflags & SF_TURRET_MD2)
|
|
{
|
|
char modelname[256];
|
|
if (!self->usermodel)
|
|
{
|
|
gi.dprintf("%s w/o a model and MD2 spawnflag set at %s\n", self->classname, vtos(self->s.origin));
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
Com_sprintf(modelname, sizeof(modelname), "models/%s", self->usermodel);
|
|
self->s.modelindex = gi.modelindex (modelname);
|
|
|
|
if ((VectorLength(self->bleft) == 0.0) && (VectorLength(self->tright) == 0.0))
|
|
{
|
|
VectorSet(self->bleft, -16.0, -16.0, -16.0);
|
|
VectorSet(self->tright, 16.0, 16.0, 16.0);
|
|
}
|
|
VectorCopy(self->bleft, self->mins);
|
|
VectorCopy(self->tright, self->maxs);
|
|
|
|
if (self->spawnflags & SF_TURRET_TRIGGER_SPAWN)
|
|
{
|
|
self->svflags |= SVF_NOCLIENT;
|
|
self->solid = SOLID_NOT;
|
|
self->use = use_turret_breach;
|
|
}
|
|
else
|
|
{
|
|
self->solid = SOLID_BBOX;
|
|
if (self->spawnflags & SF_TURRET_TRACKING)
|
|
self->use = toggle_turret_breach;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (self->spawnflags & SF_TURRET_TRIGGER_SPAWN)
|
|
{
|
|
self->svflags |= SVF_NOCLIENT;
|
|
self->solid = SOLID_NOT;
|
|
self->use = use_turret_breach;
|
|
}
|
|
else
|
|
{
|
|
self->solid = SOLID_BSP;
|
|
if (self->spawnflags & SF_TURRET_TRACKING)
|
|
self->use = toggle_turret_breach;
|
|
}
|
|
gi.setmodel(self, self->model);
|
|
}
|
|
self->movetype = MOVETYPE_PUSH;
|
|
|
|
if (!self->speed)
|
|
self->speed = 50.0;
|
|
|
|
if (!self->dmg)
|
|
self->dmg = 10;
|
|
|
|
if (!st.minpitch)
|
|
st.minpitch = -30.0;
|
|
if (!st.maxpitch)
|
|
st.maxpitch = 30.0;
|
|
if (!st.maxyaw)
|
|
st.maxyaw = 360.0;
|
|
|
|
if (!self->wait)
|
|
self->wait = 2.0;
|
|
|
|
if (self->health)
|
|
{
|
|
self->die = turret_die;
|
|
self->takedamage = DAMAGE_YES;
|
|
}
|
|
else
|
|
{
|
|
self->die = NULL;
|
|
self->takedamage = DAMAGE_NO;
|
|
}
|
|
|
|
//CW++
|
|
// Use the 'mass' field to specify damage done by turret's shot; if zero, use the
|
|
// default server setting for that weapon type.
|
|
|
|
if (self->mass < 1)
|
|
{
|
|
switch (self->sounds)
|
|
{
|
|
case 1: // Rocket Launcher
|
|
self->mass = (int)sv_rocket_damage->value;
|
|
break;
|
|
|
|
case 2: // Railgun
|
|
self->mass = (int)sv_railgun_damage->value;
|
|
break;
|
|
|
|
case 3: // Machinegun
|
|
if (self->wait < 1.0) // take old-style turrets into account
|
|
self->mass = (int)sv_mac10_damage->value;
|
|
else
|
|
self->mass = (int)self->wait;
|
|
break;
|
|
|
|
case 4: // E.S.G.
|
|
self->mass = (int)sv_spike_damage->value;
|
|
break;
|
|
|
|
case 5: // Shock Rifle
|
|
self->mass = (int)sv_shock_radius_damage->value;
|
|
break;
|
|
|
|
case 6: // Plasma
|
|
self->mass = (int)sv_plasma_damage->value;
|
|
break;
|
|
|
|
default: // Rocket Launcher
|
|
self->mass = (int)sv_rocket_damage->value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Avert your eyes - it's ugly hack time!
|
|
|
|
if (!Q_stricmp(level.mapname, "awaken2_assault2") && self->targetname)
|
|
{
|
|
if (!Q_stricmp(self->targetname, "turret111"))
|
|
{
|
|
self->viewmessage = "[Turret #1]:\n\nMachinegun";
|
|
self->s.angles[YAW] = 0.0;
|
|
st.minpitch = -35.0;
|
|
}
|
|
else if (!Q_stricmp(self->targetname, "turret1"))
|
|
{
|
|
self->viewmessage = "[Turret #2]:\n\nShockbolt";
|
|
st.minpitch = -35.0;
|
|
self->wait = 1.0;
|
|
}
|
|
}
|
|
//CW--
|
|
|
|
// Added touch routine to help prevent player from getting stuck after jumping on turret barrel
|
|
self->touch = turret_breach_touch;
|
|
|
|
self->pos1[PITCH] = -1.0 * st.minpitch;
|
|
self->pos1[YAW] = st.minyaw;
|
|
self->pos2[PITCH] = -1.0 * st.maxpitch;
|
|
self->pos2[YAW] = st.maxyaw;
|
|
|
|
if (self->pos1[YAW] < 0.0)
|
|
self->pos1[YAW] += 360.0;
|
|
if (self->pos2[YAW] < 0.0)
|
|
self->pos2[YAW] += 360.0;
|
|
|
|
self->ideal_yaw = self->s.angles[YAW];
|
|
self->move_angles[YAW] = self->ideal_yaw;
|
|
|
|
self->blocked = turret_blocked;
|
|
|
|
self->think = turret_breach_finish_init;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
|
|
//CW++
|
|
self->delay = level.time;
|
|
|
|
if (!self->count)
|
|
self->count = 1;
|
|
//CW--
|
|
|
|
gi.linkentity(self);
|
|
}
|
|
|
|
void SP_model_turret(edict_t *self)
|
|
{
|
|
self->spawnflags |= SF_TURRET_MD2;
|
|
SP_turret_breach(self);
|
|
}
|
|
|
|
/*QUAKED turret_base (0 0 0) ?
|
|
This portion of the turret changes yaw only. MUST be teamed with a turret_breach.
|
|
*/
|
|
void turret_base_finish(edict_t *self)
|
|
{
|
|
vec_t radius;
|
|
|
|
if (self->team)
|
|
{
|
|
// should ALWAYS have a team, but we're being pessimistic here
|
|
radius = (self->maxs[0] - self->mins[0])*(self->maxs[0] - self->mins[0]) +
|
|
(self->maxs[1] - self->mins[1])*(self->maxs[1] - self->mins[1]) +
|
|
(self->maxs[2] - self->mins[2])*(self->maxs[2] - self->mins[2]);
|
|
self->teammaster->base_radius = sqrt(radius);
|
|
}
|
|
}
|
|
|
|
void use_turret_base(edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
self->svflags &= ~SVF_NOCLIENT;
|
|
self->solid = SOLID_BSP;
|
|
}
|
|
|
|
void SP_turret_base(edict_t *self)
|
|
{
|
|
if (self->spawnflags & SF_TURRET_TRIGGER_SPAWN)
|
|
{
|
|
self->svflags |= SVF_NOCLIENT;
|
|
self->solid = SOLID_NOT;
|
|
self->use = use_turret_base;
|
|
}
|
|
else
|
|
self->solid = SOLID_BSP;
|
|
|
|
self->movetype = MOVETYPE_PUSH;
|
|
gi.setmodel(self, self->model);
|
|
self->blocked = turret_blocked;
|
|
|
|
// DWH
|
|
self->s.angles[PITCH] = self->s.angles[ROLL] = 0.0;
|
|
self->think = turret_base_finish;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
gi.linkentity(self);
|
|
}
|