/* =========================================================================== Copyright (C) 1997-2001 Id Software, Inc. Copyright (C) 2000-2002 Mr. Hyde and Mad Dog This file is part of Lazarus Quake 2 Mod source code. Lazarus Quake 2 Mod source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Lazarus Quake 2 Mod source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Lazarus Quake 2 Mod source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // g_turret.c #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 // Added Musashi's double barreled turret code //CW++ #define FL_TURRET_DOUBLE 0x00000001 #define FL_TURRET_DOUBLE_ALT 0x00000002 #define FL_TURRET_DOUBLE_ALT_FIRING 0x00000004 //CW-- void NoAmmoWeaponChange (edict_t *ent); #define TURRET_GRENADE_SPEED 800 // DWH - Added TurretTarget to scan the player's view for a damageable target. // Used with homing rockets edict_t *TurretTarget(edict_t *self) { float bd, d; int i; edict_t *who, *best; trace_t tr; vec3_t dir, end, forward, right, up, 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; iinuse) 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; } // end DWH 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; } } //CW++ void hrocket_turret_fire (edict_t *self, edict_t *owner, vec3_t start, vec3_t dir, vec3_t start2, vec3_t dir2, int damage, int speed, float damage_radius, int radius_damage, edict_t *home_target) { if (self->moreflags & FL_TURRET_DOUBLE) { if (self->moreflags & FL_TURRET_DOUBLE_ALT) { if (self->moreflags & FL_TURRET_DOUBLE_ALT_FIRING) { fire_rocket(owner, start2, dir2, damage, speed, 150, damage, home_target); self->moreflags &= ~FL_TURRET_DOUBLE_ALT_FIRING; } else { fire_rocket(owner, start, dir, damage, speed, 150, damage, home_target); self->moreflags |= FL_TURRET_DOUBLE_ALT_FIRING; } } else { fire_rocket(owner, start, dir, damage, speed, 150, damage, home_target); fire_rocket(owner, start2, dir2, damage, speed, 150, damage, home_target); } } else fire_rocket(owner, start, dir, damage, speed, 150, damage, home_target); } //CW-- static unsigned int /*long*/ HB_Shots; void turret_breach_fire (edict_t *self) { edict_t *owner; vec3_t forward, right, up; vec3_t start; int damage; int speed; //CW++ vec3_t forward2, right2, up2; vec3_t start2; //CW-- 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); //CW++ if (self->moreflags & FL_TURRET_DOUBLE) { AngleVectors(self->s.angles, forward2, right2, up2); VectorMA(self->s.origin, self->muzzle2[0], forward2, start2); VectorMA(start2, self->muzzle2[1], right2, start2); VectorMA(start2, self->muzzle2[2], up2, start2); } //CW-- speed = 550 + 50 * skill->value; // 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) { switch (self->sounds) { case 1: // railgun { damage = 150; //CWFIXME use self->mass //CW++ if (self->moreflags & FL_TURRET_DOUBLE) { if (self->moreflags & FL_TURRET_DOUBLE_ALT) { if (self->moreflags & FL_TURRET_DOUBLE_ALT_FIRING) { fire_rail(owner, start2, forward2, damage, 0); self->moreflags &= ~FL_TURRET_DOUBLE_ALT_FIRING; } else { fire_rail(owner, start, forward, damage, 0); self->moreflags |= FL_TURRET_DOUBLE_ALT_FIRING; } } else { fire_rail(owner, start, forward, damage, 0); fire_rail(owner, start2, forward2, damage, 0); } } else //CW-- fire_rail (owner, start, forward, damage, 0); gi.positioned_sound (start, self, CHAN_WEAPON, gi.soundindex("weapons/railgf1a.wav"), 1, ATTN_NORM, 0); //ed - muzzleflash ? on a turret ? Yeah baby 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 2: // rocket { damage = 100 + random() * 50; //CW++ if (self->moreflags & FL_TURRET_DOUBLE) { if (self->moreflags & FL_TURRET_DOUBLE_ALT) { if (self->moreflags & FL_TURRET_DOUBLE_ALT_FIRING) { fire_rocket(owner, start2, forward2, damage, speed, 150, damage, NULL); self->moreflags &= ~FL_TURRET_DOUBLE_ALT_FIRING; } else { fire_rocket(owner, start, forward, damage, speed, 150, damage, NULL); self->moreflags |= FL_TURRET_DOUBLE_ALT_FIRING; } } else { fire_rocket(owner, start, forward, damage, speed, 150, damage, NULL); fire_rocket(owner, start2, forward2, damage, speed, 150, damage, NULL); } } else //CW-- fire_rocket (owner, start, forward, damage, speed, 150, damage, NULL); gi.positioned_sound (start, self, CHAN_WEAPON, gi.soundindex("weapons/rocklf1a.wav"), 1, ATTN_NORM, 0); self->delay = level.time + self->wait; break; } case 3: // BFG { damage = 500; //CW++ if (self->moreflags & FL_TURRET_DOUBLE) { if (self->moreflags & FL_TURRET_DOUBLE_ALT) { if (self->moreflags & FL_TURRET_DOUBLE_ALT_FIRING) { fire_bfg(owner, start2, forward2, damage, speed, 1000); self->moreflags &= ~FL_TURRET_DOUBLE_ALT_FIRING; } else { fire_bfg(owner, start, forward, damage, speed, 1000); self->moreflags |= FL_TURRET_DOUBLE_ALT_FIRING; } } else { fire_bfg(owner, start, forward, damage, speed, 1000); fire_bfg(owner, start2, forward2, damage, speed, 1000); } } else //CW-- fire_bfg (owner, start, forward, damage, speed, 1000); gi.positioned_sound (start, self, CHAN_WEAPON, gi.soundindex("weapons/laser2.wav"), 1, ATTN_NORM, 0); self->delay = level.time + self->wait; break; } case 4: // Homing rockets { damage = 100 + random() * 50; if (owner->target_ent == self || owner == self) { // monster-controlled or automated turret //fire_rocket (owner, start, forward, damage, speed, 150, damage, owner->enemy); hrocket_turret_fire(self, owner, start, forward, start2, forward2, damage, speed, 150, damage, owner->enemy); //CW } else if (self->spawnflags & SF_TURRET_PLAYER_CONTROLLABLE || allow_player_use_abandoned_turret->value) { // what is player aiming at? edict_t *target; target = TurretTarget(self); //fire_rocket (owner, start, forward, damage, speed, 150, damage, target); hrocket_turret_fire(self, owner, start, forward, start2, forward2, damage, speed, 150, damage, target); //CW } else { // shouldn't be possible to get here //fire_rocket (owner, start, forward, damage, speed, 150, damage, NULL); hrocket_turret_fire(self, owner, start, forward, start2, forward2, damage, speed, 150, damage, NULL); //CW } gi.positioned_sound (start, self, CHAN_WEAPON, gi.soundindex("weapons/rocklf1a.wav"), 1, ATTN_NORM, 0); self->delay = level.time + self->wait; break; } case 5: // Machinegun { // "wait" = damage for machinegun - default = 2 //CW++ if (self->moreflags & FL_TURRET_DOUBLE) { if (self->moreflags & FL_TURRET_DOUBLE_ALT) { if (self->moreflags & FL_TURRET_DOUBLE_ALT_FIRING) { fire_bullet(owner, start2, forward2, self->wait, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_MACHINEGUN); self->moreflags &= ~FL_TURRET_DOUBLE_ALT_FIRING; } else { fire_bullet(owner, start, forward, self->wait, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_MACHINEGUN); self->moreflags |= FL_TURRET_DOUBLE_ALT_FIRING; } } else { fire_bullet(owner, start, forward, self->wait, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_MACHINEGUN); fire_bullet(owner, start2, forward2, self->wait, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_MACHINEGUN); } } else //CW-- fire_bullet (owner, start, forward, self->wait, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_MACHINEGUN); gi.WriteByte (svc_muzzleflash); gi.WriteShort (self-g_edicts); gi.WriteByte (MZ_MACHINEGUN); gi.multicast (start, MULTICAST_PVS); self->delay = level.time; // No delay on machinegun break; } case 6: // Hyperblaster { HB_Shots++; //CW++ if (self->moreflags & FL_TURRET_DOUBLE) { if (self->moreflags & FL_TURRET_DOUBLE_ALT) { if (self->moreflags & FL_TURRET_DOUBLE_ALT_FIRING) { fire_blaster (owner, start2, forward2, self->wait, 1000, (!(HB_Shots % 4))?EF_HYPERBLASTER:0, true, BLASTER_ORANGE); self->moreflags &= ~FL_TURRET_DOUBLE_ALT_FIRING; } else { fire_blaster (owner, start, forward, self->wait, 1000, (!(HB_Shots % 4))?EF_HYPERBLASTER:0, true, BLASTER_ORANGE); self->moreflags |= FL_TURRET_DOUBLE_ALT_FIRING; } } else { fire_blaster (owner, start, forward, self->wait, 1000, (!(HB_Shots % 4))?EF_HYPERBLASTER:0, true, BLASTER_ORANGE); fire_blaster (owner, start2, forward2, self->wait, 1000, 0, true, BLASTER_ORANGE); } } else //CW-- fire_blaster (owner, start, forward, self->wait, 1000, (!(HB_Shots % 4))?EF_HYPERBLASTER:0, true, BLASTER_ORANGE); gi.positioned_sound(start,self,CHAN_WEAPON,gi.soundindex("weapons/hyprbf1a.wav"),1,ATTN_NORM,0); self->delay = level.time; // No delay break; } case 7: // Grenade launcher { //CW++ if (self->moreflags & FL_TURRET_DOUBLE) { if (self->moreflags & FL_TURRET_DOUBLE_ALT) { if (self->moreflags & FL_TURRET_DOUBLE_ALT_FIRING) { fire_grenade (owner, start2, forward2, 50, self->fog_far, 2.5, 90, false); self->moreflags &= ~FL_TURRET_DOUBLE_ALT_FIRING; } else { fire_grenade (owner, start, forward, 50, self->fog_far, 2.5, 90, false); self->moreflags |= FL_TURRET_DOUBLE_ALT_FIRING; } } else { fire_grenade (owner, start, forward, 50, self->fog_far, 2.5, 90, false); fire_grenade (owner, start2, forward2, 50, self->fog_far, 2.5, 90, false); } } else //CW-- fire_grenade (owner, start, forward, 50, self->fog_far, 2.5, 90, false); gi.WriteByte (svc_muzzleflash2); gi.WriteShort (self - g_edicts); gi.WriteByte (MZ2_GUNNER_GRENADE_1); gi.multicast (start, MULTICAST_PVS); self->delay = level.time + self->wait; break; } default: { damage = 100; //CW++ if (self->moreflags & FL_TURRET_DOUBLE) { if (self->moreflags & FL_TURRET_DOUBLE_ALT) { if (self->moreflags & FL_TURRET_DOUBLE_ALT_FIRING) { fire_rocket(owner, start2, forward2, damage, speed, 150, damage, NULL); self->moreflags &= ~FL_TURRET_DOUBLE_ALT_FIRING; } else { fire_rocket(owner, start, forward, damage, speed, 150, damage, NULL); self->moreflags |= FL_TURRET_DOUBLE_ALT_FIRING; } } else { fire_rocket(owner, start, forward, damage, speed, 150, damage, NULL); fire_rocket(owner, start2, forward2, damage, speed, 150, damage, NULL); } } else //CW-- fire_rocket (owner, start, forward, damage, speed, 150, damage, NULL); 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) { int i; edict_t *ent; vec3_t forward; // level the gun self->move_angles[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, forward); forward[2] = forward[2] + 150; if (forward[2] < 80) forward[2] = 80; for (i=0; i<3; i++) ent->velocity[i] = forward[i]; ent->s.origin[2] = ent->s.origin[2] + 1; ent->movetype = MOVETYPE_WALK; ent->gravity = 1; 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 || 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) delta_angle += 360; if(delta_angle > 180) delta_angle -= 360; 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) delta_angle += 360; if(delta_angle > 180) delta_angle -= 360; 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 || self->pos2[YAW] != 360) 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) yaw_range += 360; yaw_base = self->move_angles[YAW] - self->pos1[YAW]; if(yaw_base < 0) yaw_base += 360; if (yaw_base > yaw_range) { float dmin, dmax; dmin = fabs(self->pos1[YAW] - self->move_angles[YAW]); if (dmin < 0 ) dmin += 360; else if (dmin > 360) dmin -= 360; dmax = fabs(self->pos2[YAW] - self->move_angles[YAW]); if (dmax < 0) dmax += 360; else if (dmax > 360) dmax -= 360; 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) delta[0] += 360; else if (delta[0] > 180) delta[0] -= 360; if (delta[1] < -180) delta[1] += 360; else if (delta[1] > 180) delta[1] -= 360; delta[2] = 0; // gi.dprintf("move=%g, current=%g, delta=%g, pos1=%g, pos2=%g\n", // self->move_angles[PITCH],current_angles[PITCH],delta[PITCH],self->pos1[PITCH],self->pos2[PITCH]); 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, yaw_0; int i; turret_turn(self); yaw_r = self->pos2[YAW] - self->pos1[YAW]; if(yaw_r < 0) yaw_r += 360; if (self->pos1[YAW] != 0 || self->pos2[YAW] != 360) yaw_restrict = true; else yaw_restrict = false; self->nextthink = level.time + FRAMETIME; if(self->deadflag == DEAD_DEAD) return; if ( (self->owner) && (self->owner->target_ent == self) && (self->owner->spawnflags & SF_TURRETDRIVER_REMOTE_DRIVER)) remote_monster = true; else remote_monster = false; if (self->owner || self->viewer) { if( !(self->spawnflags & SF_TURRET_MD2)) { self->s.effects &= ~EF_ANIM23; self->s.effects |= EF_ANIM01; } self->do_not_rotate = true; } else { if( !(self->spawnflags & SF_TURRET_MD2)) { self->s.effects &= ~EF_ANIM01; self->s.effects |= EF_ANIM23; } self->do_not_rotate = false; } 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; ent->do_not_rotate = self->do_not_rotate; } } } // 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]; angle *= (M_PI*2 / 360); 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 angle = self->s.angles[PITCH] * (M_PI*2 / 360); 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 || allow_player_use_abandoned_turret->value))// && !remote_monster) { // 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]; AnglesNormalize(self->move_angles); // FIXME: do a tracebox from up and behind towards the turret, to try and keep them from // getting stuck inside the rotating turret // x & y AngleVectors(self->s.angles, forward, NULL, NULL); VectorScale(forward, 32, forward); VectorSubtract(self->s.origin, forward, target); VectorAdd(target, tv(0,0,8), 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 || allow_player_use_abandoned_turret->value) && !remote_monster) { // check if a player has mounted the turret int i; edict_t *ent; vec3_t target, forward; vec3_t dir; // find a player ent = &g_edicts[0]; ent++; for (i=0 ; ivalue ; 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, forward); VectorSubtract(self->s.origin, forward, target); VectorSubtract (target, ent->s.origin, dir); if (fabs(dir[2]) < 64) dir[2] = 0; if (VectorLength(dir) < 16) { // 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; //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 float reaction_time; vec3_t f, forward, right, up, start, t_start; edict_t *gomer; float best_dist = WORLD_SIZE; // was 8192 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) yaw_0 += 360; } 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; } 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 } // hunt for monster if(!remote_monster) { for(i=maxclients->value+1; ienemy) continue; // no need to re-check this guy if(!gomer->inuse) continue; if(!(gomer->svflags & SVF_MONSTER)) continue; if(gomer->health < gomer->gib_health) continue; if(gomer->svflags & SVF_NOCLIENT) continue; if(!gi.inPVS(self->s.origin,gomer->s.origin)) continue; 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) yaw_0 += 360; } 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; } } } } } // 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 // hunt for closest player - hunt ALL entities since // we want to view fake players using camera for(i=1; iinuse) 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; 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) yaw_0 += 360; } 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; if(skill->value >= 2) { VectorMA(target,FRAMETIME,self->enemy->velocity,target); /* May add some variant of this for skill 3 later. For now, the following is virtually indistinguishable from skill 2 for most normal setups. Trouble is, it is sometimes EASIER than skill 2. if(skill->value > 2) { float t; VectorSubtract(target,self->s.origin,dir); VectorNormalize(dir); vectoangles(dir,dir); VectorSubtract(dir,self->s.angles,dir); AnglesNormalize(dir); dir[2] = max( fabs(dir[0]), fabs(dir[1]) ); if(dir[2] > 0) { t = dir[2]/self->speed; VectorMA(target,t,self->enemy->velocity,target); } } */ } if(self->sounds == 7) { if(!AimGrenade (self, start, target, self->fog_far, dir)) { // Can't get a grenade to target. Correct yaw but // not pitch vec_t pitch = self->move_angles[PITCH]; vectoangles (dir, self->move_angles); self->move_angles[PITCH] = pitch; if(skill->value > 0) turret_turn(self); return; } } else 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)) { // don't fire rockets or homing rockets if remote turret_driver is // too close to target if(remote_monster) { vec3_t range; vec_t r; VectorSubtract(self->enemy->s.origin,self->owner->s.origin,range); r = VectorLength(range); if(r < 128) return; } if (level.time < self->monsterinfo.attack_finished) { if(skill->value > 0) turret_turn(self); return; } if(self->sounds == 5 || self->sounds == 6) reaction_time = 0; else reaction_time = max(0., 0.5*(2-skill->value)); if ((level.time - self->monsterinfo.trail_time) < reaction_time) { if(skill->value > 0) turret_turn(self); return; } self->monsterinfo.attack_finished = level.time + reaction_time; if(self->sounds != 5 && self->sounds != 6) self->monsterinfo.attack_finished += self->wait; turret_breach_fire(self); if(skill->value > 0) turret_turn(self); } } else { if(skill->value > 0) 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); if(skill->value > 0) 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); //CW++ Double-barrelled turrets. self->target_ent = NULL; self->target_ent = G_PickTarget(self->combattarget); if (self->target_ent) { VectorSubtract(self->target_ent->s.origin, self->s.origin, self->muzzle2); self->moreflags |= FL_TURRET_DOUBLE; if (self->style > 0) self->moreflags |= FL_TURRET_DOUBLE_ALT; G_FreeEdict(self->target_ent); } //CW-- } 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; } } 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_driver_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); void SP_monster_infantry (edict_t *self); void monster_start_go(edict_t *self); void turret_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { int i; edict_t *ent; edict_t *player; // ensure turret_base stops rotating if (self->team) { for (ent = self->teammaster; ent; ent = ent->teamchain) { if(ent != self) { ent->avelocity[1] = 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)) { if(self->owner->spawnflags & SF_TURRETDRIVER_REMOTE_DRIVER) { // remote driver - remove and replace with normal infantry edict_t *monster; monster = self->owner->child; if(monster) { monster->health = self->owner->health; monster->enemy = self->owner->enemy; G_FreeEdict(self->owner); monster->solid = SOLID_BBOX; monster->movetype = MOVETYPE_STEP; monster->svflags &= ~SVF_NOCLIENT; monster_start_go (monster); gi.linkentity (monster); if(monster->enemy) FoundTarget(monster); } } else { 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; ivalue; 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*FRAMETIME; temp->destroytarget = self->destroytarget; temp->target_ent = attacker; gi.linkentity(temp); } self->nextthink = 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 /= 4; if(self->spawnflags & SF_TURRET_MD2) self->move_angles[0] = self->pos1[0]; else self->move_angles[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; } 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) { self->class_id = ENTITY_TURRET_BREACH; // 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; } // Knightmare- check for "models/" already in path if ( !strncmp(self->usermodel, "models/", 7) ) Com_sprintf(modelname, sizeof(modelname), "%s", self->usermodel); else Com_sprintf(modelname, sizeof(modelname), "models/%s", self->usermodel); self->s.modelindex = gi.modelindex (modelname); if ( (VectorLength(self->bleft) == 0) && (VectorLength(self->tright) == 0) ) { VectorSet(self->bleft,-16,-16,-16); VectorSet(self->tright,16, 16, 16); } 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; // grenade speed. Yes I know this is a misuse of this. Sue me. if (st.distance) self->fog_far = st.distance; else self->fog_far = TURRET_GRENADE_SPEED; if (!self->speed) self->speed = 50; if (!self->dmg) self->dmg = 10; if (!st.minpitch) st.minpitch = -30; if (!st.maxpitch) st.maxpitch = 30; if (!st.maxyaw) st.maxyaw = 360; 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; } // Added touch routine to help prevent player from getting stuck after // jumping on turret barrel self->touch = turret_breach_touch; self->pos1[PITCH] = -1 * st.minpitch; self->pos1[YAW] = st.minyaw; self->pos2[PITCH] = -1 * st.maxpitch; self->pos2[YAW] = st.maxyaw; if(self->pos1[YAW] < 0) self->pos1[YAW] += 360; if(self->pos2[YAW] < 0) self->pos2[YAW] += 360; 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; // Lazarus: Added so monsters will attack turrets that // fire at them self->monsterinfo.aiflags |= AI_GOOD_GUY; gi.linkentity (self); } void SP_model_turret(edict_t *self) { self->spawnflags |= SF_TURRET_MD2; SP_turret_breach(self); self->class_id = ENTITY_MODEL_TURRET; } /*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) { self->class_id = ENTITY_TURRET_BASE; 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; self->think = turret_base_finish; self->nextthink = level.time + FRAMETIME; gi.linkentity (self); } /*QUAKED turret_driver (1 .5 0) (-16 -16 -24) (16 16 32) Must NOT be on the team with the rest of the turret parts. Instead it must target the turret_breach. */ void infantry_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage); void infantry_stand (edict_t *self); void monster_use (edict_t *self, edict_t *other, edict_t *activator); void turret_driver_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { edict_t *ent; if(self->target_ent->inuse) { // level the gun self->target_ent->move_angles[0] = 0; if(self->spawnflags & SF_TURRETDRIVER_REMOTE_DRIVER) // "remote" driver... turn off TRACK for turret self->target_ent->spawnflags &= ~SF_TURRET_TRACKING; else { // remove the driver from the end of the team chain for (ent = self->target_ent->teammaster; ent->teamchain != self; ent = ent->teamchain) ; ent->teamchain = NULL; self->teammaster = NULL; self->flags &= ~FL_TEAMSLAVE; self->target_ent->teammaster->owner = NULL; } self->target_ent->owner = NULL; } // Knightmare added - deathtarget support if (self->deathtarget) self->target = self->deathtarget; if (self->target) G_UseTargets (self, self->enemy); // end Knightmare infantry_die (self, inflictor, attacker, damage); } void turret_driver_think (edict_t *self) { vec3_t target; vec3_t dir; float reaction_time; self->nextthink = level.time + FRAMETIME; //ed - yaay, turrets will kill monsters and stuff now. if (self->enemy && (!self->enemy->inuse || self->enemy->health <= 0)) self->enemy = NULL; if (!self->enemy) { if (!FindTarget (self)) return; self->monsterinfo.trail_time = level.time; self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; } else { if (visible (self, self->enemy)) { if (self->monsterinfo.aiflags & AI_LOST_SIGHT) { self->monsterinfo.trail_time = level.time; self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; } } else { self->monsterinfo.aiflags |= AI_LOST_SIGHT; return; } } // let the turret know where we want it to aim VectorCopy (self->enemy->s.origin, target); target[2] += self->enemy->viewheight; VectorSubtract (target, self->target_ent->s.origin, dir); if(self->target_ent->sounds == 7) { // grenade launcher if(!AimGrenade (self->target_ent, self->target_ent->s.origin, target, self->target_ent->fog_far, dir)) { vectoangles (dir, self->target_ent->move_angles); return; } } vectoangles (dir, self->target_ent->move_angles); // decide if we should shoot if (level.time < self->monsterinfo.attack_finished) return; if(self->target_ent->sounds==5 || self->target_ent->sounds==6) reaction_time = 0.; else reaction_time = max(0., (2-skill->value)); if ((level.time - self->monsterinfo.trail_time) < reaction_time) return; if(self->target_ent->sounds==5 || self->target_ent->sounds==6) self->monsterinfo.attack_finished = level.time + FRAMETIME; else self->monsterinfo.attack_finished = level.time + reaction_time + 1.0; self->target_ent->spawnflags |= 65536; } void turret_driver_link (edict_t *self) { vec3_t vec; edict_t *ent; self->target_ent = G_PickTarget (self->target); self->target_ent->owner = self; // DWH: We no longer require turret_breach to be teamed with a turret_base UNLESS // there's a turret_driver if (!self->target_ent->team) { gi.dprintf("turret_driver at %s targets a no-team turret_breach\n", vtos(self->s.origin)); G_FreeEdict(self); return; } // DWH: REMOTE (=1) drivers aren't part of the turret team, and don't think // (the turret_breach thinks for them) if(!(self->spawnflags & SF_TURRETDRIVER_REMOTE_DRIVER)) { self->think = turret_driver_think; self->nextthink = level.time + FRAMETIME; self->target_ent->teammaster->owner = self; VectorCopy (self->target_ent->s.angles, self->s.angles); } vec[0] = self->target_ent->s.origin[0] - self->s.origin[0]; vec[1] = self->target_ent->s.origin[1] - self->s.origin[1]; vec[2] = 0; self->move_origin[0] = VectorLength(vec); VectorSubtract (self->s.origin, self->target_ent->s.origin, vec); vectoangles (vec, vec); AnglesNormalize(vec); self->move_origin[1] = vec[1]; self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2]; // add the driver to the end of the team chain // DWH: REMOTE (=1) drivers don't move with turret, and the turret tracks // players if(self->spawnflags & SF_TURRETDRIVER_REMOTE_DRIVER) self->target_ent->spawnflags |= SF_TURRET_TRACKING; else { for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain) ; ent->teamchain = self; self->teammaster = self->target_ent->teammaster; self->flags |= FL_TEAMSLAVE; } } void SP_turret_driver (edict_t *self) { if (deathmatch->value) { G_FreeEdict(self); return; } self->class_id = ENTITY_TURRET_DRIVER; self->movetype = MOVETYPE_PUSH; self->solid = SOLID_BBOX; self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2"); VectorSet (self->mins, -16, -16, -24); VectorSet (self->maxs, 16, 16, 32); self->health = 100; self->gib_health = 0; self->mass = 200; self->viewheight = 24; self->die = turret_driver_die; self->monsterinfo.stand = infantry_stand; self->flags |= FL_NO_KNOCKBACK; level.total_monsters++; self->svflags |= SVF_MONSTER; self->s.renderfx |= RF_FRAMELERP; self->takedamage = DAMAGE_AIM; self->use = monster_use; self->clipmask = MASK_MONSTERSOLID; VectorCopy (self->s.origin, self->s.old_origin); self->monsterinfo.aiflags |= AI_STAND_GROUND|AI_DUCKED; if (st.item) { self->item = FindItemByClassname (st.item); if (!self->item) gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item); } self->think = turret_driver_link; self->nextthink = level.time + FRAMETIME; if(self->spawnflags & SF_TURRETDRIVER_REMOTE_DRIVER) { // remote turret driver - go ahead and create his "real" infantry replacement // NOW so the switch won't be so time-consuming edict_t *infantry; infantry = G_Spawn(); infantry->spawnflags = SF_TURRET_TRIGGER_SPAWN; VectorCopy(self->s.angles,infantry->s.angles); VectorCopy(self->s.origin,infantry->s.origin); infantry->s.origin[2] += 1.0; infantry->health = self->health; SP_monster_infantry(infantry); self->child = infantry; } gi.linkentity (self); }