thirtyflightsofloving/missionpack/m_turret.c
Knightmare66 0d4e872ce9 Added LMCTF / LM Escape plasma rifle to missionpack DLL.
Added plasma guards (monster_soldier_plasma_re and monster_soldier_plasma_sp) from LM Escape to missionpack DLL.
Added Zaero items/weapons to missionpack DLL.
Added support for Zaero doors  to missionpack DLL.
Fixed crash caused by killtargeting sentien (laser edict not freed) in missionpack DLL.
Fixed bug with broken Rogue turrets in missionpack DLL.
Fixed crash in g_combat.c->M_ReactToDamage() caused by attacker with NULL classname in missionpack DLL.
2020-08-09 02:45:19 -04:00

1188 lines
29 KiB
C

/*
==============================================================================
TURRET
==============================================================================
*/
#include "g_local.h"
#include "m_turret.h"
#define SPAWN_BLASTER 8
#define SPAWN_RAILGUN 16
#define SPAWN_ROCKET 32
#define SPAWN_HEATBEAM 64
#define SPAWN_WEAPONCHOICE 120
#define SPAWN_INSTANT_WEAPON 80
#define SPAWN_WALL_UNIT 128
#define STATE_TOP 0
#define STATE_BOTTOM 1
#define STATE_UP 2
#define STATE_DOWN 3
#define STATE_LOWEST 4
extern qboolean FindTarget (edict_t *self);
void turret_run (edict_t *self);
void TurretAim (edict_t *self);
void turret_sight (edict_t *self, edict_t *other);
void turret_search (edict_t *self);
void turret_stand (edict_t *self);
void turret_wake (edict_t *self);
void turret_ready_gun (edict_t *self);
void turret_run (edict_t *self);
void turret_attack (edict_t *self);
mmove_t turret_move_fire;
mmove_t turret_move_fire_blind;
void TurretAim (edict_t *self)
{
vec3_t end, dir;
vec3_t ang;
float move, idealPitch, idealYaw, current, speed, turn_yaw=0.0f;
int orientation;
qboolean is_spinning = false;
qboolean is_turning = false;
// gi.dprintf("turret_aim: %d %d\n", self->s.frame, self->monsterinfo.nextframe);
if (self->movewith_set && self->movewith_ent && self->movewith_ent->inuse
&& (!strcmp(self->movewith_ent->classname, "func_rotating")
|| !strcmp(self->movewith_ent->classname, "func_rotating_dh")
|| !strcmp(self->movewith_ent->classname, "func_trackchange") )
&& (self->movewith_ent->avelocity[YAW])) {
is_spinning = true;
turn_yaw = self->movewith_ent->avelocity[YAW] * FRAMETIME;
}
if (self->movewith_set && self->movewith_ent && self->movewith_ent->inuse
&& (!strcmp(self->movewith_ent->classname, "func_train")
|| !strcmp(self->movewith_ent->classname, "func_tracktrain")
|| !strcmp(self->movewith_ent->classname, "model_train")
|| !strcmp(self->movewith_ent->classname, "func_door_rotating")
|| !strcmp(self->movewith_ent->classname, "func_door_swinging") )
&& (self->movewith_ent->avelocity[YAW])) {
is_turning = true;
turn_yaw = self->movewith_ent->avelocity[YAW] * FRAMETIME;
}
if (!self->enemy || self->enemy == world)
{
if (!FindTarget (self))
return;
}
if (!self->enemy->classname)
return;
if (!strcmp(self->enemy->classname, "gib") || !strcmp(self->enemy->classname, "debris"))
return;
// if turret is still in inactive mode, ready the gun, but don't aim
if (self->s.frame < FRAME_active01)
{
turret_ready_gun (self);
return;
}
// if turret is still readying, don't aim.
if (self->s.frame < FRAME_run01)
return;
// PMM - blindfire aiming here
if (self->monsterinfo.currentmove == &turret_move_fire_blind) // && !is_spinning)
{
VectorCopy (self->monsterinfo.blind_fire_target, end);
if (self->enemy->s.origin[2] < self->monsterinfo.blind_fire_target[2])
end[2] += self->enemy->viewheight + 10;
else
end[2] += self->enemy->mins[2] - 10;
}
else
{
VectorCopy (self->enemy->s.origin, end);
if (self->enemy->client)
end[2] += self->enemy->viewheight;
}
VectorSubtract (end, self->s.origin, dir);
vectoangles2 (dir, ang);
//
// Clamp first
//
idealPitch = ang[PITCH];
idealYaw = ang[YAW];
orientation = self->offset[1];
switch (orientation)
{
case -1: // up pitch: 0 to 90
if (idealPitch < -90)
idealPitch += 360;
if (idealPitch > -5)
idealPitch = -5;
break;
case -2: // down pitch: -180 to -360
if (idealPitch > -90)
idealPitch -= 360;
if (idealPitch < -355)
idealPitch = -355;
else if (idealPitch > -185)
idealPitch = -185;
break;
// Knightmare- support odd yaw angles
default:
if (idealPitch < -180)
idealPitch += 360;
if (idealPitch > 85)
idealPitch = 85;
else if (idealPitch < -85)
idealPitch = -85;
if ( idealYaw > (orientation+180) )
idealYaw -= 360;
if ( idealYaw < (orientation-180) )
idealYaw += 360;
if (is_turning) // Knightmare- factor in avelocity of parent entity
{
// gi.dprintf("turret_aim: clamping yaw for turning parent entity\n");
if ( idealYaw > (orientation + turn_yaw + 85) )
idealYaw = orientation + turn_yaw + 85;
else if ( idealYaw < (orientation + turn_yaw - 85) )
idealYaw = orientation + turn_yaw - 85;
}
else if (is_spinning) // Knightmare- if out of range, point to front
{
// gi.dprintf("turret_aim: clamping yaw for spinning parent entity\n");
if ( idealYaw > (orientation + 85) )
idealYaw = orientation;
else if ( idealYaw < (orientation - 85) )
idealYaw = orientation;
}
else
{
if ( idealYaw > (orientation + 85) )
idealYaw = orientation + 85;
else if ( idealYaw < (orientation - 85) )
idealYaw = orientation - 85;
}
break;
// end Knightmare
}
//
// adjust pitch
//
current = self->s.angles[PITCH];
speed = self->yaw_speed;
if (idealPitch != current)
{
move = idealPitch - current;
while (move >= 360)
move -= 360;
if (move >= 90)
{
move = move - 360;
}
while (move <= -360)
move += 360;
if (move <= -90)
{
move = move + 360;
}
if (move > 0)
{
if (move > speed)
move = speed;
}
else
{
if (move < -speed)
move = -speed;
}
self->s.angles[PITCH] = anglemod (current + move);
}
//
// adjust yaw
//
current = self->s.angles[YAW];
speed = self->yaw_speed;
if (idealYaw != current)
{
move = idealYaw - current;
if (move >= 180)
move -= 360;
if (move <= -180)
move += 360;
if (move > 0)
{
if (move > speed)
move = speed;
}
else
{
if (move < -speed)
move = -speed;
}
self->s.angles[YAW] = anglemod (current + move);
}
}
void turret_sight (edict_t *self, edict_t *other)
{
}
void turret_search (edict_t *self)
{
}
mframe_t turret_frames_stand [] =
{
ai_stand, 0, NULL,
ai_stand, 0, NULL
};
mmove_t turret_move_stand = {FRAME_stand01, FRAME_stand02, turret_frames_stand, NULL};
void turret_stand (edict_t *self)
{
// gi.dprintf("turret_stand\n");
self->monsterinfo.currentmove = &turret_move_stand;
}
mframe_t turret_frames_ready_gun [] =
{
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL
};
mmove_t turret_move_ready_gun = { FRAME_active01, FRAME_run01, turret_frames_ready_gun, turret_run };
void turret_ready_gun (edict_t *self)
{
self->monsterinfo.currentmove = &turret_move_ready_gun;
}
mframe_t turret_frames_seek [] =
{
ai_walk, 0, TurretAim,
ai_walk, 0, TurretAim
};
mmove_t turret_move_seek = {FRAME_run01, FRAME_run02, turret_frames_seek, NULL};
void turret_walk (edict_t *self)
{
if (self->s.frame < FRAME_run01)
turret_ready_gun(self);
else
self->monsterinfo.currentmove = &turret_move_seek;
}
mframe_t turret_frames_run [] =
{
ai_run, 0, TurretAim,
ai_run, 0, TurretAim
};
mmove_t turret_move_run = {FRAME_run01, FRAME_run02, turret_frames_run, turret_run};
void turret_run (edict_t *self)
{
if (self->s.frame < FRAME_run01)
turret_ready_gun(self);
else
self->monsterinfo.currentmove = &turret_move_run;
}
// **********************
// ATTACK
// **********************
#define TURRET_BULLET_DAMAGE 4
#define TURRET_HEAT_DAMAGE 4
void Turret_Railgun_Fire (edict_t *self)
{
vec3_t start, dir;
TurretAim (self);
VectorCopy (self->s.origin, start);
// trail the target....
// VectorMA(end, -0.2, self->enemy->velocity, end);
VectorSubtract (self->aim_point, start, dir);
VectorNormalize(dir);
monster_fire_railgun (self, start, dir, 50, 0, MZ2_GLADIATOR_RAILGUN_1);
self->think = monster_think;
self->nextthink = level.time + FRAMETIME;
}
void Turret_Railgun_Aim (edict_t *self)
{
vec3_t end;
TurretAim (self);
if (!self->enemy || !self->enemy->inuse)
return;
VectorCopy (self->enemy->s.origin, end);
// aim for the head.
if ((self->enemy) && (self->enemy->client))
end[2]+=self->enemy->viewheight;
else
end[2]+=22;
VectorCopy (end, self->aim_point); //save for aiming the shot
self->think = Turret_Railgun_Fire;
self->nextthink = level.time + 2 * FRAMETIME;
}
void TurretFire (edict_t *self)
{
vec3_t forward;
vec3_t start, end, dir;
float time, dist, chance;
trace_t trace;
int rocketSpeed;
TurretAim (self);
if (!self->enemy || !self->enemy->inuse)
return;
VectorSubtract (self->enemy->s.origin, self->s.origin, dir);
VectorNormalize(dir);
AngleVectors(self->s.angles, forward, NULL, NULL);
chance = DotProduct(dir, forward);
if (chance < 0.98)
{
// gi.dprintf("off-angle\n");
return;
}
chance = random();
// rockets fire less often than the others do.
if (self->spawnflags & SPAWN_ROCKET)
{
chance = chance * 3;
rocketSpeed = 550;
if (skill->value == 2)
{
rocketSpeed += 200 * random();
}
else if (skill->value == 3)
{
rocketSpeed += 100 + (200 * random());
}
}
else if (self->spawnflags & SPAWN_BLASTER)
{
if (skill->value == 0)
rocketSpeed = 600;
else if (skill->value == 1)
rocketSpeed = 800;
else
rocketSpeed = 1000;
chance = chance * 2;
}
// up the fire chance 20% per skill level.
chance = chance - (0.2 * skill->value);
if (/*chance < 0.5 && */visible(self, self->enemy))
{
VectorCopy (self->s.origin, start);
VectorCopy (self->enemy->s.origin, end);
// aim for the head.
if ((self->enemy) && (self->enemy->client))
end[2]+=self->enemy->viewheight;
else
end[2]+=22;
// Lazarus fog reduction of accuracy
if (self->monsterinfo.visibility < FOG_CANSEEGOOD)
{
end[0] += crandom() * 640 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
end[1] += crandom() * 640 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
end[2] += crandom() * 320 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
}
VectorSubtract (end, start, dir);
dist = VectorLength(dir);
// check for predictive fire if distance less than 512
if (!(self->spawnflags & SPAWN_INSTANT_WEAPON) && (dist<512))
{
chance = random();
// ramp chance. easy - 50%, avg - 60%, hard - 70%, nightmare - 80%
chance += (3 - skill->value) * 0.1;
if (chance < 0.8)
{
// lead the target....
time = dist / 1000;
VectorMA(end, time, self->enemy->velocity, end);
// Lazarus fog reduction of accuracy
if (self->monsterinfo.visibility < FOG_CANSEEGOOD)
{
end[0] += crandom() * 640 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
end[1] += crandom() * 640 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
end[2] += crandom() * 320 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
}
VectorSubtract (end, start, dir);
}
}
VectorNormalize(dir);
trace = gi.trace(start, vec3_origin, vec3_origin, end, self, MASK_SHOT);
if (trace.ent == self->enemy || trace.ent == world || (self->monsterinfo.visibility < FOG_CANSEEGOOD))
{
if (self->spawnflags & SPAWN_BLASTER)
monster_fire_blaster(self, start, dir, 20, rocketSpeed, MZ2_TURRET_BLASTER, EF_BLASTER, BLASTER_ORANGE);
else if (self->spawnflags & SPAWN_RAILGUN && self->last_fire_time <= level.time) //was SPAWN_MACHINEGUN
{
//monster_fire_bullet (self, start, dir, TURRET_BULLET_DAMAGE, 0, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_TURRET_MACHINEGUN);
gi.sound (self, CHAN_WEAPON, gi.soundindex ("gladiator/railgun.wav"), 1, ATTN_NORM, 0);
self->think = Turret_Railgun_Aim;
self->nextthink = level.time + 2 * FRAMETIME;
self->last_fire_time = level.time + 1.5;
}
else if (self->spawnflags & SPAWN_ROCKET)
{
if (dist * trace.fraction > 72)
monster_fire_rocket (self, start, dir, 50, rocketSpeed, MZ2_TURRET_ROCKET, NULL);
}
}
}
}
// PMM
void TurretFireBlind (edict_t *self)
{
vec3_t forward;
vec3_t start, end, dir;
float dist, chance;
int rocketSpeed;
TurretAim (self);
if (!self->enemy || !self->enemy->inuse)
return;
VectorSubtract (self->monsterinfo.blind_fire_target, self->s.origin, dir);
VectorNormalize(dir);
AngleVectors(self->s.angles, forward, NULL, NULL);
chance = DotProduct(dir, forward);
if (chance < 0.98)
{
// gi.dprintf("off-angle\n");
return;
}
if (self->spawnflags & SPAWN_ROCKET)
{
rocketSpeed = 550;
if (skill->value == 2)
{
rocketSpeed += 200 * random();
}
else if (skill->value == 3)
{
rocketSpeed += 100 + (200 * random());
}
}
VectorCopy (self->s.origin, start);
VectorCopy (self->monsterinfo.blind_fire_target, end);
if (self->enemy->s.origin[2] < self->monsterinfo.blind_fire_target[2])
end[2] += self->enemy->viewheight + 10;
else
end[2] += self->enemy->mins[2] - 10;
VectorSubtract (end, start, dir);
dist = VectorLength(dir);
VectorNormalize(dir);
if (self->spawnflags & SPAWN_BLASTER)
monster_fire_blaster(self, start, dir, 20, 1000, MZ2_TURRET_BLASTER, EF_BLASTER, BLASTER_ORANGE);
else if (self->spawnflags & SPAWN_ROCKET)
monster_fire_rocket (self, start, dir, 50, rocketSpeed, MZ2_TURRET_ROCKET, NULL);
}
//pmm
mframe_t turret_frames_fire [] =
{
ai_run, 0, TurretFire,
ai_run, 0, TurretAim,
ai_run, 0, TurretAim,
ai_run, 0, TurretAim
};
mmove_t turret_move_fire = {FRAME_pow01, FRAME_pow04, turret_frames_fire, turret_run};
//PMM
// the blind frames need to aim first
mframe_t turret_frames_fire_blind [] =
{
ai_run, 0, TurretAim,
ai_run, 0, TurretAim,
ai_run, 0, TurretAim,
ai_run, 0, TurretFireBlind
};
mmove_t turret_move_fire_blind = {FRAME_pow01, FRAME_pow04, turret_frames_fire_blind, turret_run};
//pmm
void turret_attack(edict_t *self)
{
float r, chance;
if (self->s.frame < FRAME_run01)
turret_ready_gun(self);
// PMM
else if (self->monsterinfo.attack_state != AS_BLIND)
{
self->monsterinfo.nextframe = FRAME_pow01;
self->monsterinfo.currentmove = &turret_move_fire;
}
else
{
// setup shot probabilities
if (self->monsterinfo.blind_fire_delay < 1.0)
chance = 1.0;
else if (self->monsterinfo.blind_fire_delay < 7.5)
chance = 0.4;
else
chance = 0.1;
r = random();
// minimum of 3 seconds, plus 0-4, after the shots are done - total time should be max less than 7.5
self->monsterinfo.blind_fire_delay += 0.4 + 3.0 + random()*4.0;
// don't shoot at the origin
if (VectorCompare (self->monsterinfo.blind_fire_target, vec3_origin))
return;
// don't shoot if the dice say not to
if (r > chance)
return;
self->monsterinfo.nextframe = FRAME_pow01;
self->monsterinfo.currentmove = &turret_move_fire_blind;
}
// pmm
}
// **********************
// PAIN
// **********************
void turret_pain (edict_t *self, edict_t *other, float kick, int damage)
{
return;
}
// **********************
// DEATH
// **********************
void turret_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
{
vec3_t forward;
vec3_t start;
edict_t *base;
gi.WriteByte (svc_temp_entity);
//gi.WriteByte (TE_PLAIN_EXPLOSION);
gi.WriteByte (TE_EXPLOSION1);
gi.WritePosition (self->s.origin);
gi.multicast (self->s.origin, MULTICAST_PHS);
AngleVectors(self->s.angles, forward, NULL, NULL);
VectorMA(self->s.origin, 1, forward, start);
ThrowDebris (self, "models/objects/debris1/tris.md2", 1, start, 0, 0);
ThrowDebris (self, "models/objects/debris1/tris.md2", 2, start, 0, 0);
ThrowDebris (self, "models/objects/debris1/tris.md2", 1, start, 0, 0);
ThrowDebris (self, "models/objects/debris1/tris.md2", 2, start, 0, 0);
if (self->teamchain)
{
base = self->teamchain;
base->solid = SOLID_BBOX;
base->takedamage = DAMAGE_NO;
base->movetype = MOVETYPE_NONE;
gi.linkentity (base);
}
if (self->target)
{
if (self->enemy && self->enemy->inuse)
G_UseTargets (self, self->enemy);
else
G_UseTargets (self, self);
}
G_FreeEdict(self);
}
// **********************
// WALL SPAWN
// **********************
void turret_wall_spawn (edict_t *turret)
{
edict_t *ent;
int angle;
vec3_t forward;
ent = G_Spawn();
ent->classname = "turret_wall";
VectorCopy (turret->s.origin, ent->s.origin);
VectorCopy (turret->s.angles, turret->deploy_angles);
VectorCopy (turret->s.angles, ent->s.angles);
VectorCopy (ent->s.angles, ent->deploy_angles);
angle = ent->s.angles[1];
if (ent->s.angles[0] == 90)
angle = -1;
else if (ent->s.angles[0] == 270)
angle = -2;
switch (angle)
{
case -1:
VectorSet(ent->mins, -16, -16, -8);
VectorSet(ent->maxs, 16, 16, 0);
break;
case -2:
VectorSet(ent->mins, -16, -16, 0);
VectorSet(ent->maxs, 16, 16, 8);
break;
case 0:
VectorSet(ent->mins, -8, -16, -16);
VectorSet(ent->maxs, 0, 16, 16);
break;
case 90:
VectorSet(ent->mins, -16, -8, -16);
VectorSet(ent->maxs, 16, 0, 16);
break;
case 180:
VectorSet(ent->mins, 0, -16, -16);
VectorSet(ent->maxs, 8, 16, 16);
break;
case 270:
VectorSet(ent->mins, -16, 0, -16);
VectorSet(ent->maxs, 16, 8, 16);
break;
default:
if (angle > 315 || angle <= 45)
{
VectorSet(ent->mins, -8, -16, -16);
VectorSet(ent->maxs, 0, 16, 16);
}
else if (angle > 45 && angle <= 135)
{
VectorSet(ent->mins, -16, -8, -16);
VectorSet(ent->maxs, 16, 0, 16);
}
else if (angle > 135 && angle <= 225)
{
VectorSet(ent->mins, 0, -16, -16);
VectorSet(ent->maxs, 8, 16, 16);
}
else if (angle > 225 && angle <= 315)
{
VectorSet(ent->mins, -16, 0, -16);
VectorSet(ent->maxs, 16, 8, 16);
}
break;
}
/* if (turret->s.angles[0] == 270)
VectorSet (forward, 0, 0, 1);
else if (turret->s.angles[0] == 90)
VectorSet (forward, 0, 0, -1);
else if (turret->s.angles[1] == 0)
VectorSet (forward, 1, 0, 0);
else if (turret->s.angles[1] == 90)
VectorSet (forward, 0, 1, 0);
else if (turret->s.angles[1] == 180)
VectorSet (forward, -1, 0, 0);
else if (turret->s.angles[1] == 270)
VectorSet (forward, 0, -1, 0);
*/
// Knightmare- set up the turret's movement positions here
AngleVectors (turret->s.angles, forward, NULL, NULL);
// Move turret wall backward a bit so it will be flush with surrounding wall
VectorMA (ent->s.origin, -2, forward, ent->s.origin);
VectorCopy (turret->s.origin, turret->pos1);
VectorMA (turret->pos1, 32, forward, turret->pos2);
VectorCopy (ent->s.origin, ent->pos1);
VectorMA (ent->pos1, 32, forward, ent->pos2);
ent->movetype = MOVETYPE_PUSH;
ent->solid = SOLID_NOT;
ent->moveinfo.state = STATE_BOTTOM;
ent->teammaster = turret;
turret->teammaster = turret;
turret->teamchain = ent;
turret->moveinfo.state = STATE_BOTTOM;
ent->teamchain = NULL;
ent->flags |= FL_TEAMSLAVE;
ent->owner = turret;
// Knightmare- set movewith if applicable and backup original angles
if (turret->movewith)
ent->movewith = turret->movewith;
VectorCopy (ent->s.angles, ent->org_angles);
ent->s.modelindex = gi.modelindex("models/monsters/turretbase/tris.md2");
ent->s.renderfx |= RF_NOSHADOW; // Knightmare added
gi.linkentity (ent);
}
void turret_wake (edict_t *self)
{
// The wall section will call this when it stops moving.
// just return without doing anything. Easiest way to have a null function.
self->moveinfo.state = STATE_TOP;
if (self->flags & FL_TEAMSLAVE)
return;
self->monsterinfo.stand = turret_stand;
self->monsterinfo.walk = turret_walk;
self->monsterinfo.run = turret_run;
self->monsterinfo.dodge = NULL;
self->monsterinfo.attack = turret_attack;
self->monsterinfo.melee = NULL;
self->monsterinfo.sight = turret_sight;
self->monsterinfo.search = turret_search;
self->monsterinfo.currentmove = &turret_move_stand;
self->takedamage = DAMAGE_AIM;
self->movetype = MOVETYPE_NONE;
// prevent counting twice
//self->monsterinfo.aiflags |= AI_DO_NOT_COUNT;
self->monsterinfo.monsterflags |= MFL_DO_NOT_COUNT;
gi.linkentity (self);
stationarymonster_start (self);
if (self->spawnflags & SPAWN_RAILGUN) //was SPAWN_MACHINEGUN
self->s.skinnum = 1;
else if (self->spawnflags & SPAWN_ROCKET)
self->s.skinnum = 2;
// but we do want the death to count
//self->monsterinfo.aiflags &= ~AI_DO_NOT_COUNT;
self->monsterinfo.monsterflags &= ~MFL_DO_NOT_COUNT;
}
extern void Move_Calc (edict_t *ent, vec3_t dest, void(*func)(edict_t*));
void turret_activate (edict_t *self, edict_t *other, edict_t *activator)
{
// vec3_t endpos;
edict_t *base;
self->movetype = MOVETYPE_PUSH;
self->use = NULL; // Knightmare added
if (!self->speed)
self->speed = 15;
self->moveinfo.speed = self->speed;
self->moveinfo.accel = self->speed;
self->moveinfo.decel = self->speed;
// start up the turret
self->moveinfo.state = STATE_UP;
Move_Calc(self, self->pos2, turret_wake);
base = self->teamchain;
if (base)
{
base->movetype = MOVETYPE_PUSH;
base->speed = self->speed;
base->moveinfo.speed = base->speed;
base->moveinfo.accel = base->speed;
base->moveinfo.decel = base->speed;
// start up the wall section
self->teamchain->moveinfo.state = STATE_UP;
Move_Calc(self->teamchain, self->teamchain->pos2, turret_wake);
}
gi.sound (self, CHAN_VOICE, gi.soundindex ("world/dr_short.wav"), 1, ATTN_NORM, 0);
}
// PMM
// checkattack .. ignore range, just attack if available
qboolean turret_checkattack (edict_t *self)
{
vec3_t spot1, spot2;
float chance, nexttime;
trace_t tr;
int enemy_range;
if (self->enemy->health > 0)
{
// see if any entities are in the way of the shot
VectorCopy (self->s.origin, spot1);
spot1[2] += self->viewheight;
VectorCopy (self->enemy->s.origin, spot2);
spot2[2] += self->enemy->viewheight;
tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WINDOW);
// do we have a clear shot?
if (tr.ent != self->enemy)
{
// PGM - we want them to go ahead and shoot at info_notnulls if they can.
if (self->enemy->solid != SOLID_NOT || tr.fraction < 1.0) //PGM
{
// PMM - if we can't see our target, and we're not blocked by a monster, go into blind fire if available
if ((!(tr.ent->svflags & SVF_MONSTER)) && (!visible(self, self->enemy)))
{
if ((self->monsterinfo.blindfire) && (self->monsterinfo.blind_fire_delay <= 10.0))
{
if (level.time < self->monsterinfo.attack_finished)
{
return false;
}
if (level.time < (self->monsterinfo.trail_time + self->monsterinfo.blind_fire_delay))
{
// wait for our time
return false;
}
else
{
// make sure we're not going to shoot something we don't want to shoot
tr = gi.trace (spot1, NULL, NULL, self->monsterinfo.blind_fire_target, self, CONTENTS_MONSTER);
if (tr.allsolid || tr.startsolid || ((tr.fraction < 1.0) && (tr.ent != self->enemy)))
{
return false;
}
self->monsterinfo.attack_state = AS_BLIND;
self->monsterinfo.attack_finished = level.time + 0.5 + 2*random();
return true;
}
}
}
// pmm
return false;
}
}
}
if (level.time < self->monsterinfo.attack_finished)
return false;
enemy_range = range(self, self->enemy);
if (enemy_range == RANGE_MELEE)
{
// don't always melee in easy mode
if (skill->value == 0 && (rand()&3) )
return false;
self->monsterinfo.attack_state = AS_MISSILE;
return true;
}
if (self->spawnflags & SPAWN_ROCKET)
{
chance = 0.10;
nexttime = (1.8 - (0.2 * skill->value));
}
else if (self->spawnflags & SPAWN_BLASTER)
{
chance = 0.35;
nexttime = (1.2 - (0.2 * skill->value));
}
else
{
chance = 0.50;
nexttime = (0.8 - (0.1 * skill->value));
}
if (skill->value == 0)
chance *= 0.5;
else if (skill->value > 1)
chance *= 2;
// PGM - go ahead and shoot every time if it's a info_notnull
// PMM - added visibility check
if ( ((random () < chance) && (visible(self, self->enemy))) || (self->enemy->solid == SOLID_NOT))
{
self->monsterinfo.attack_state = AS_MISSILE;
// self->monsterinfo.attack_finished = level.time + 0.3 + 2*random();
self->monsterinfo.attack_finished = level.time + nexttime;
return true;
}
self->monsterinfo.attack_state = AS_STRAIGHT;
return false;
}
// **********************
// SPAWN
// **********************
/*QUAKED monster_turret (1 .5 0) (-16 -16 -16) (16 16 16) Ambush Trigger_Spawn Sight Blaster RailGun Rocket Heatbeam WallUnit
The automated defense turret that mounts on walls.
Check the weapon you want it to use: blaster, railgun, rocket, heatbeam.
Default weapon is blaster.
When activated, wall units move 32 units in the direction they're facing.
*/
void SP_monster_turret (edict_t *self)
{
int angle;
if (deathmatch->value)
{
G_FreeEdict (self);
return;
}
// VERSIONING
// if (g_showlogic && g_showlogic->value)
// gi.dprintf ("%s\n", ROGUE_VERSION_STRING);
// self->plat2flags = ROGUE_VERSION_ID;
// versions
// pre-caches
gi.soundindex ("world/dr_short.wav");
gi.modelindex ("models/objects/debris1/tris.md2");
if (self->style)
{
PatchMonsterModel("models/monsters/turret/tris.md2");
self->s.skinnum = self->style * 3;
}
self->s.modelindex = gi.modelindex("models/monsters/turret/tris.md2");
self->s.renderfx |= RF_NOSHADOW; // Knightmare added
VectorSet (self->mins, -12, -12, -12);
VectorSet (self->maxs, 12, 12, 12);
self->movetype = MOVETYPE_NONE;
self->solid = SOLID_BBOX;
if (!self->health)
self->health = 240;
if (!self->gib_health)
self->gib_health = -100;
if (!self->mass)
self->mass = 250;
if (!self->yaw_speed)
self->yaw_speed = 45;
self->flags |= FL_MECHANICAL;
self->pain = turret_pain;
self->die = turret_die;
// map designer didn't specify weapon type. set it now.
if (!(self->spawnflags & SPAWN_WEAPONCHOICE))
{
self->spawnflags |= SPAWN_BLASTER;
// self->spawnflags |= SPAWN_MACHINEGUN;
// self->spawnflags |= SPAWN_ROCKET;
// self->spawnflags |= SPAWN_HEATBEAM;
}
if (self->spawnflags & SPAWN_HEATBEAM)
{
self->spawnflags &= ~SPAWN_HEATBEAM;
self->spawnflags |= SPAWN_BLASTER;
}
if (!(self->spawnflags & SPAWN_WALL_UNIT))
{
self->monsterinfo.stand = turret_stand;
self->monsterinfo.walk = turret_walk;
self->monsterinfo.run = turret_run;
self->monsterinfo.dodge = NULL;
self->monsterinfo.attack = turret_attack;
self->monsterinfo.melee = NULL;
self->monsterinfo.sight = turret_sight;
self->monsterinfo.search = turret_search;
self->monsterinfo.currentmove = &turret_move_stand;
}
// PMM
self->monsterinfo.checkattack = turret_checkattack;
self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
self->monsterinfo.scale = MODEL_SCALE;
self->gravity = 0;
self->last_fire_time = 0;
VectorCopy (self->s.angles, self->offset);
angle = (int)self->s.angles[1];
switch (angle)
{
case -1: // up
self->s.angles[0] = 270;
self->s.angles[1] = 0;
self->s.origin[2] += 2;
break;
case -2: // down
self->s.angles[0] = 90;
self->s.angles[1] = 0;
self->s.origin[2] -= 2;
break;
case 0:
self->s.origin[0] += 2;
break;
case 90:
self->s.origin[1] += 2;
break;
case 180:
self->s.origin[0] -= 2;
break;
case 270:
self->s.origin[1] -= 2;
break;
default:
if (angle > 338 || angle <= 22) // near 0
self->s.origin[0] += 2;
else if (angle > 22 && angle <= 68) { // near 45
self->s.origin[0] += 1;
self->s.origin[1] += 1; }
else if (angle > 68 && angle <= 112) // near 90
self->s.origin[1] += 2;
else if (angle > 112 && angle <= 158) { // near 135
self->s.origin[1] += 1;
self->s.origin[0] -= 1; }
else if (angle > 158 && angle <= 202) // near 180
self->s.origin[0] -= 2;
else if (angle > 202 && angle <= 248) { // near 225
self->s.origin[0] -= 1;
self->s.origin[1] -= 1; }
else if (angle > 248 && angle <= 292) // near 270
self->s.origin[1] -= 2;
else if (angle > 292 && angle <= 338) { // near 315
self->s.origin[1] -= 1;
self->s.origin[0] += 1; }
break;
}
gi.linkentity (self);
if (self->spawnflags & SPAWN_WALL_UNIT)
{
// vec3_t forward;
if (!self->targetname)
{
// gi.dprintf("Wall Unit Turret without targetname! %s\n", vtos(self->s.origin));
G_FreeEdict(self);
return;
}
self->takedamage = DAMAGE_NO;
self->use = turret_activate;
turret_wall_spawn(self);
//if ((!(self->monsterinfo.aiflags & AI_GOOD_GUY)) && (!(self->monsterinfo.aiflags & AI_DO_NOT_COUNT)))
if ((!(self->monsterinfo.aiflags & AI_GOOD_GUY)) && (!(self->monsterinfo.monsterflags & MFL_DO_NOT_COUNT)))
level.total_monsters++;
}
else
{
stationarymonster_start (self);
}
if (self->spawnflags & SPAWN_BLASTER)
{
gi.modelindex ("models/objects/laser/tris.md2");
gi.soundindex ("misc/lasfly.wav");
gi.soundindex ("soldier/solatck2.wav");
self->spawnflags &= ~SPAWN_RAILGUN;
self->spawnflags &= ~SPAWN_ROCKET;
self->s.skinnum = 0;
}
else if (self->spawnflags & SPAWN_RAILGUN) //was SPAWN_MACHINEGUN
{
//gi.soundindex ("infantry/infatck1.wav");
gi.soundindex ("gladiator/railgun.wav");
self->spawnflags &= ~SPAWN_ROCKET;
self->s.skinnum = 1;
}
else if (self->spawnflags & SPAWN_ROCKET)
{
gi.soundindex ("weapons/rockfly.wav");
gi.modelindex ("models/objects/rocket/tris.md2");
gi.soundindex ("chick/chkatck2.wav");
self->s.skinnum = 2;
}
else
{
self->spawnflags |= SPAWN_BLASTER;
gi.modelindex ("models/objects/laser/tris.md2");
gi.soundindex ("misc/lasfly.wav");
gi.soundindex ("soldier/solatck2.wav");
self->s.skinnum = 0;
}
// Lazarus
if (self->powerarmor)
{
if (self->powerarmortype == 1)
self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN;
else
self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD;
self->monsterinfo.power_armor_power = self->powerarmor;
}
self->common_name = "Sentry Turret";
// PMM - turrets don't get mad at monsters, and visa versa
self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
// PMM - blindfire
if (self->spawnflags & (SPAWN_ROCKET|SPAWN_BLASTER))
self->monsterinfo.blindfire = true;
}