/* Starshatter OpenSource Distribution Copyright (c) 1997-2004, Destroyer Studios LLC. All Rights Reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name "Destroyer Studios" nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SUBSYSTEM: Stars.exe FILE: Weapon.cpp AUTHOR: John DiCamillo OVERVIEW ======== Weapon class */ #include "MemDebug.h" #include "Weapon.h" #include "Shot.h" #include "Drone.h" #include "Contact.h" #include "Ship.h" #include "Sim.h" #include "SimEvent.h" #include "Random.h" #include "NetGame.h" #include "NetUtil.h" #include "Game.h" #include "Solid.h" // +----------------------------------------------------------------------+ Weapon::Weapon(WeaponDesign* d, int nmuz, Vec3* muzzles, double az, double el) : System(WEAPON, d->type, d->name, d->value, d->capacity, d->capacity, d->recharge_rate), design(d), group(d->group), ammo(-1), ripple_count(0), aim_azimuth((float) az), aim_elevation((float) el), old_azimuth(0.0f), old_elevation(0.0f), aim_time(0), enabled(true), refire(0.0f), mass(d->carry_mass), resist(d->carry_resist), guided(d->guided), shot_speed(d->speed), active_barrel(0), locked(false), centered(false), firing(false), blocked(false), index(0), target(0), subtarget(0), beams(0), orders(MANUAL), control(SINGLE_FIRE), sweep(SWEEP_TIGHT), turret(0), turret_base(0) { ZeroMemory(visible_stores, sizeof(visible_stores)); if (design->primary) abrv = Game::GetText("sys.weapon.primary.abrv"); else abrv = Game::GetText("sys.weapon.secondary.abrv"); nbarrels = nmuz; if (nbarrels > MAX_BARRELS) nbarrels = MAX_BARRELS; if (nbarrels == 0 && design->nbarrels > 0) { nbarrels = design->nbarrels; for (int i = 0; i < nbarrels; i++) muzzle_pts[i] = rel_pts[i] = design->muzzle_pts[i] * design->scale; ammo = design->ammo * nbarrels; } else if (nbarrels == 1 && design->nstores > 0) { nbarrels = design->nstores; for (int i = 0; i < nbarrels; i++) muzzle_pts[i] = rel_pts[i] = (muzzles[0] + design->attachments[i]); ammo = nbarrels; } else { for (int i = 0; i < nbarrels; i++) muzzle_pts[i] = rel_pts[i] = muzzles[i]; ammo = design->ammo * nbarrels; } if (design->syncro) active_barrel = -1; emcon_power[0] = 0; emcon_power[1] = 0; emcon_power[2] = 100; aim_az_max = design->aim_az_max; aim_az_min = design->aim_az_min; aim_az_rest = design->aim_az_rest; aim_el_max = design->aim_el_max; aim_el_min = design->aim_el_min; aim_el_rest = design->aim_el_rest; } // +----------------------------------------------------------------------+ Weapon::Weapon(const Weapon& w) : System(w), design(w.design), ammo(-1), ripple_count(0), enabled(true), refire(0.0f), mass(w.mass), resist(w.resist), aim_azimuth(w.aim_azimuth), aim_elevation(w.aim_elevation), old_azimuth(0.0f), old_elevation(0.0f), aim_time(0), guided(w.guided), shot_speed(w.shot_speed), active_barrel(0), locked(false), centered(false), firing(false), blocked(false), target(0), subtarget(0), beams(0), orders(MANUAL), control(SINGLE_FIRE), sweep(SWEEP_TIGHT), group(w.group), aim_az_max(w.aim_az_max), aim_az_min(w.aim_az_min), aim_az_rest(w.aim_az_rest), aim_el_max(w.aim_el_max), aim_el_min(w.aim_el_min), aim_el_rest(w.aim_el_rest), turret(0), turret_base(0) { Mount(w); ZeroMemory(visible_stores, sizeof(visible_stores)); nbarrels = w.nbarrels; for (int i = 0; i < nbarrels; i++) { muzzle_pts[i] = rel_pts[i] = w.muzzle_pts[i]; } ammo = design->ammo * nbarrels; if (design->syncro) active_barrel = -1; if (design->beam) { beams = new(__FILE__,__LINE__) Shot* [nbarrels]; ZeroMemory(beams, sizeof(Shot*) * nbarrels); } if (aim_az_rest >= 2*PI) aim_az_rest = design->aim_az_rest; if (aim_el_rest >= 2*PI) aim_el_rest = design->aim_el_rest; } // +--------------------------------------------------------------------+ Weapon::~Weapon() { if (beams) { for (int i = 0; i < nbarrels; i++) { if (beams[i]) { Ignore(beams[i]); delete beams[i]; beams[i] = 0; } } delete [] beams; } GRAPHIC_DESTROY(turret); GRAPHIC_DESTROY(turret_base); for (int i = 0; i < MAX_BARRELS; i++) GRAPHIC_DESTROY(visible_stores[i]); } // +--------------------------------------------------------------------+ bool Weapon::IsPrimary() const { return design->primary; } bool Weapon::IsDrone() const { return design->drone; } bool Weapon::IsDecoy() const { return design->decoy_type != 0; } bool Weapon::IsProbe() const { return design->probe != 0; } bool Weapon::IsMissile() const { return !design->primary; } bool Weapon::IsBeam() const { return design->beam; } // +--------------------------------------------------------------------+ Shot* Weapon::GetBeam(int i) { if (beams && i >= 0 && i < nbarrels) return beams[i]; return 0; } // +--------------------------------------------------------------------+ void Weapon::SetOwner(Ship* s) { ship = s; if (design->turret_model) { Solid* t = new(__FILE__,__LINE__) Solid; t->UseModel(design->turret_model); turret = t; } if (design->turret_base_model) { Solid* t = new(__FILE__,__LINE__) Solid; t->UseModel(design->turret_base_model); turret_base = t; } if (!design->primary && design->visible_stores && ammo == nbarrels && design->shot_model != 0) { for (int i = 0; i < nbarrels; i++) { Solid* s = new(__FILE__,__LINE__) Solid; s->UseModel(design->shot_model); visible_stores[i] = s; } } } Solid* Weapon::GetTurret() { return turret; } Solid* Weapon::GetTurretBase() { return turret_base; } Solid* Weapon::GetVisibleStore(int i) { if (i >= 0 && i < MAX_BARRELS) return visible_stores[i]; return 0; } void Weapon::SetAmmo(int a) { if (a >= 0) { if (active_barrel >= 0 && design->visible_stores) { while (a < ammo) { if (active_barrel >= nbarrels) active_barrel = 0; if (visible_stores[active_barrel]) { GRAPHIC_DESTROY(visible_stores[active_barrel]); active_barrel++; ammo--; } } } ammo = a; } } // +--------------------------------------------------------------------+ void Weapon::ExecFrame(double seconds) { System::ExecFrame(seconds); if (refire > 0) refire -= (float) seconds; locked = false; centered = false; if (!ship) return; if (orders == POINT_DEFENSE && enabled) SelectTarget(); if (beams && !target) { for (int i = 0; i < nbarrels; i++) { if (beams[i]) { // aim beam straight: Aim(); SetBeamPoints(false); return; } } } if (design->self_aiming) { Track(target, subtarget); } else if (turret) { ZeroAim(); } if (ship->CheckFire()) return; // aim beam at target: bool aim_beams = false; if (beams) { for (int i = 0; i < nbarrels; i++) { if (beams[i]) { aim_beams = true; SetBeamPoints(true); break; } } } if (!aim_beams) { if (ripple_count > 0) { if (Fire()) ripple_count--; } else if (locked && !blocked) { if (!ship->IsHostileTo(target)) return; if (orders == AUTO && centered) { if (energy >= design->charge && (ammo < 0 || target && target->Integrity() >= 1) && objective.length() < design->max_range) Fire(); } else if (orders == POINT_DEFENSE) { if (energy >= design->min_charge && (ammo < 0 || target && target->Integrity() >= 1) && objective.length() < design->max_range) Fire(); } } } } // +----------------------------------------------------------------------+ void Weapon::Distribute(double delivered_energy, double seconds) { if (UsesWatts()) { if (seconds < 0.01) seconds = 0.01; // convert Joules to Watts: energy = (float) (delivered_energy/seconds); } else if (!Game::Paused()) { energy += (float) (delivered_energy * 1.25); if (energy > capacity) energy = capacity; else if (energy < 0) energy = 0.0f; } } // +--------------------------------------------------------------------+ bool Weapon::Update(SimObject* obj) { if (obj == target) { target = 0; } else if (beams) { for (int i = 0; i < nbarrels; i++) if (obj == beams[i]) beams[i] = 0; } return SimObserver::Update(obj); } const char* Weapon::GetObserverName() const { static char name[256]; sprintf_s(name, "Weapon %s", design->name.data()); return name; } // +--------------------------------------------------------------------+ void Weapon::SetFiringOrders(int o) { if (o >= MANUAL && o <= POINT_DEFENSE) orders = o; } void Weapon::SetControlMode(int m) { if (m >= SINGLE_FIRE && m <= SALVO_FIRE) control = m; } void Weapon::SetSweep(int s) { if (s >= SWEEP_NONE && s <= SWEEP_WIDE) sweep = s; } // +--------------------------------------------------------------------+ bool Weapon::CanTarget(DWORD classification) const { return (design->target_type & classification) ? true : false; } // +--------------------------------------------------------------------+ void Weapon::SetTarget(SimObject* targ, System* sub) { // check self targeting: if (targ == (SimObject*) ship) return; // check target class filter: if (targ) { switch (targ->Type()) { case SimObject::SIM_SHIP: { Ship* tgt_ship = (Ship*) targ; if ((tgt_ship->Class() & design->target_type) == 0) return; } break; case SimObject::SIM_SHOT: return; case SimObject::SIM_DRONE: { if ((design->target_type & Ship::DRONE) == 0) return; } break; default: return; } } // if ok, target this object: if (target != targ) { target = targ; if (target) Observe(target); } subtarget = sub; } // +--------------------------------------------------------------------+ void Weapon::SelectTarget() { bool select_locked = false; SimObject* targ = 0; double dist = 1e9; double az = 0; double el = 0; if (ammo && enabled && (availability > crit_level)) { ZeroAim(); ListIter contact = ship->ContactList(); // lock onto any threatening shots first (if we can): if (design->target_type & Ship::DRONE) { while (++contact) { Shot* c_shot = contact->GetShot(); if (c_shot && contact->Threat(ship)) { // distance from self to target: double distance = Point(c_shot->Location() - muzzle_pts[0]).length(); if (distance > design->min_range && distance < design->max_range && distance < dist) { // check aim basket: select_locked = CanLockPoint(c_shot->Location(), az, el); if (select_locked) { targ = c_shot; dist = distance; } } } } } // lock onto a threatening ship only if it is (much) closer: dist *= 0.2; contact.reset(); while (++contact) { Ship* c_ship = contact->GetShip(); if (!c_ship) continue; // can we lock onto this target? if ((c_ship->IsRogue() || c_ship->GetIFF() > 0 && c_ship->GetIFF() != ship->GetIFF()) && (c_ship->Class() & design->target_type) && c_ship->Weapons().size() > 0) { // distance from self to target: double distance = Point(c_ship->Location() - muzzle_pts[0]).length(); if (distance < design->max_range && distance < dist) { // check aim basket: select_locked = CanLockPoint(c_ship->Location(), az, el); if (select_locked) { targ = c_ship; dist = distance; } } } } } if (!ammo || !enabled) { SetTarget(0,0); locked = false; } else { SetTarget(targ, 0); } } // +--------------------------------------------------------------------+ int Weapon::Track(SimObject* targ, System* sub) { if (ammo && enabled && (availability > crit_level)) { firing = 0; Aim(); } else if (turret) { ZeroAim(); } return locked; } // +--------------------------------------------------------------------+ Shot* Weapon::Fire() { if (Game::Paused()) return 0; if (ship && ship->CheckFire()) return 0; if (ship->IsStarship() && target && !centered) return 0; if (beams && active_barrel >= 0 && active_barrel < nbarrels && beams[active_barrel]) return 0; Shot* shot = 0; if (ammo && enabled && (refire <= 0) && (energy > design->min_charge) && (availability > crit_level)) { refire = design->refire_delay; NetGame* net_game = NetGame::GetInstance(); bool net_client = net_game ? net_game->IsClient() : false; // all barrels if (active_barrel < 0) { if (net_client || IsPrimary()) NetUtil::SendWepTrigger(this, nbarrels); if (!net_game || IsPrimary()) { for (int i = 0; i < nbarrels; i++) shot = FireBarrel(i); } else if (net_game && net_game->IsServer() && IsMissile()) { for (int i = 0; i < nbarrels; i++) { shot = FireBarrel(i); NetUtil::SendWepRelease(this, shot); } } } // single barrel else { if (net_client || IsPrimary()) NetUtil::SendWepTrigger(this, nbarrels); if (!net_game || IsPrimary()) { shot = FireBarrel(active_barrel++); } else if (net_game && net_game->IsServer() && IsMissile()) { shot = FireBarrel(active_barrel++); NetUtil::SendWepRelease(this, shot); } if (active_barrel >= nbarrels) { active_barrel = 0; refire += design->salvo_delay; } } if (design->ripple_count > 0 && ripple_count <= 0) ripple_count = design->ripple_count-1; if (status != NOMINAL) refire *= 2; } return shot; } // +--------------------------------------------------------------------+ Shot* Weapon::NetFirePrimary(SimObject* tgt, System* sub, int count) { Shot* shot = 0; if (!IsPrimary() || Game::Paused()) return shot; if (active_barrel < 0 || active_barrel >= nbarrels) active_barrel = 0; target = tgt; subtarget = sub; aim_time = 0; for (int i = 0; i < count; i++) { shot = FireBarrel(active_barrel++); if (active_barrel >= nbarrels) { active_barrel = 0; refire += design->salvo_delay; } } if (target) Observe(target); return shot; } Shot* Weapon::NetFireSecondary(SimObject* tgt, System* sub, DWORD objid) { Shot* shot = 0; if (IsPrimary() || Game::Paused()) return shot; if (active_barrel < 0 || active_barrel >= nbarrels) active_barrel = 0; target = tgt; subtarget = sub; aim_time = 0; shot = FireBarrel(active_barrel++); if (active_barrel >= nbarrels) { active_barrel = 0; refire += design->salvo_delay; } if (shot) shot->SetObjID(objid); if (target) Observe(target); return shot; } // +--------------------------------------------------------------------+ Shot* Weapon::FireBarrel(int n) { const Point& base_vel = ship->Velocity(); Shot* shot = 0; SimRegion* region = ship->GetRegion(); if (!region || n < 0 || n >= nbarrels || Game::Paused()) return 0; firing = 1; Aim(); Camera rail_cam; rail_cam.Clone(aim_cam); Point shotpos = muzzle_pts[n]; if (design->length > 0) shotpos = shotpos + aim_cam.vpn() * design->length; // guns may be slewed towards target: if (design->primary) { shot = CreateShot(shotpos, aim_cam, design, ship); if (shot) { shot->SetVelocity(shot->Velocity() + base_vel); } } // missiles always launch in rail direction: else { // unless they are on a mobile launcher if (turret && design->self_aiming) { shot = CreateShot(shotpos, aim_cam, design, ship); shot->SetVelocity(base_vel); } else { shot = CreateShot(shotpos, rail_cam, design, ship); if (shot /* && !turret */) { Matrix orient = ship->Cam().Orientation(); if (aim_azimuth != 0) orient.Yaw(aim_azimuth); if (aim_elevation != 0) orient.Pitch(aim_elevation); Point eject = design->eject * orient; shot->SetVelocity(base_vel + eject); } } if (shot && visible_stores[n]) { GRAPHIC_DESTROY(visible_stores[n]); } } if (shot) { if (ammo > 0) ammo--; if (guided && target) shot->SeekTarget(target, subtarget); float shot_load; if (energy > design->charge) shot_load = design->charge; else shot_load = energy; energy -= shot_load; shot->SetCharge(shot_load * availability); if (target && design->flak && !design->guided) { double speed = shot->Velocity().length(); double range = (target->Location() - shot->Location()).length(); if (range > design->min_range && range < design->max_range) { shot->SetFuse(range / speed); } } region->InsertObject(shot); if (beams) { beams[n] = shot; Observe(beams[n]); // aim beam at target: SetBeamPoints(true); } if (ship) { ShipStats* stats = ShipStats::Find(ship->Name()); if (design->primary) stats->AddGunShot(); else if (design->decoy_type == 0 && design->damage > 0) stats->AddMissileShot(); } } return shot; } Shot* Weapon::CreateShot(const Point& loc, const Camera& cam, WeaponDesign* dsn, const Ship* own) { if (dsn->drone) return new(__FILE__,__LINE__) Drone(loc, cam, dsn, own); else return new(__FILE__,__LINE__) Shot(loc, cam, dsn, own); } // +--------------------------------------------------------------------+ void Weapon::Orient(const Physical* rep) { System::Orient(rep); const Matrix& orientation = rep->Cam().Orientation(); Point loc = rep->Location(); // align graphics with camera: if (turret) { if (!design->self_aiming) ZeroAim(); const Matrix& aim = aim_cam.Orientation(); turret->MoveTo(mount_loc); turret->SetOrientation(aim); if (turret_base) { Matrix base = orientation; if (design->turret_axis == 1) { base.Pitch(aim_elevation); base.Pitch(old_elevation); } else { base.Yaw(aim_azimuth); base.Yaw(old_azimuth); } turret_base->MoveTo(mount_loc); turret_base->SetOrientation(base); } for (int i = 0; i < nbarrels; i++) { muzzle_pts[i] = (rel_pts[i] * aim) + mount_loc; if (visible_stores[i]) { visible_stores[i]->SetOrientation(aim); visible_stores[i]->MoveTo(muzzle_pts[i]); } } } else { for (int i = 0; i < nbarrels; i++) { muzzle_pts[i] = (rel_pts[i] * orientation) + loc; if (visible_stores[i]) { visible_stores[i]->SetOrientation(orientation); visible_stores[i]->MoveTo(muzzle_pts[i]); } } } } // +--------------------------------------------------------------------+ void Weapon::SetBeamPoints(bool aim) { for (int i = 0; i < nbarrels; i++) { if (beams && beams[i]) { Point from = muzzle_pts[i]; Point to; double len = design->length; if (len < 1 || len > 1e7) len = design->max_range; if (len < 1) len = 3e5; else if (len > 1e7) len = 1e7; // always use the aim cam, to enforce slew_rate to = from + aim_cam.vpn() * len; beams[i]->SetBeamPoints(from, to); } } } // +--------------------------------------------------------------------+ void Weapon::Aim() { locked = false; centered = false; FindObjective(); if (target) { double az = 0; double el = 0; locked = CanLockPoint(obj_w, az, el, &objective); double spread_az = design->spread_az; double spread_el = design->spread_el; // beam sweep target: if (design->beam) { double factor = 0; double az_phase = 0; double el_phase = 0; if (target->Type() == SimObject::SIM_SHIP) { Ship* s = (Ship*) target; if (s->IsStarship()) { switch (sweep) { default: case SWEEP_NONE: factor = 0; break; case SWEEP_TIGHT: factor = 1; break; case SWEEP_WIDE: factor = 2; break; } } } if (factor > 0) { factor *= atan2(target->Radius(), (double) objective.z); for (int i = 0; i < nbarrels; i++) { if (beams && beams[i]) { az_phase = sin(beams[i]->Life() * 0.4 * PI); el_phase = sin(beams[i]->Life() * 1.0 * PI); break; } } az += factor * spread_az * az_phase; el += factor * spread_el * el_phase * 0.25; } } else if (!design->beam) { if (spread_az > 0) az += Random(-spread_az, spread_az); if (spread_el > 0) el += Random(-spread_el, spread_el); } AimTurret(az, -el); // check range for guided weapons: if (locked && guided) { double range = objective.length(); if (range > design->max_track) locked = false; else if (range > design->max_range) { if (firing) { if (RandomChance(1,4)) // 1 in 4 chance of locking anyway locked = false; } else { locked = false; } } } if (locked) { Point tloc = target->Location(); tloc = Transform(tloc); if (tloc.z > 1) { az = atan2(fabs(tloc.x), tloc.z); el = atan2(fabs(tloc.y), tloc.z); double firing_cone = 10*DEGREES; if (orders == MANUAL) firing_cone = 30*DEGREES; if (az < firing_cone && el < firing_cone) centered = true; } } } else { AimTurret(aim_az_rest, -aim_el_rest); } } void Weapon::FindObjective() { ZeroAim(); if (!target || !design->self_aiming) { objective = Point(); return; } obj_w = target->Location(); if (subtarget) { obj_w = subtarget->MountLocation(); } else if (target->Type() == SimObject::SIM_SHIP) { Ship* tgt_ship = (Ship*) target; if (tgt_ship->IsGroundUnit()) obj_w += Point(0,150,0); } if (!design->beam && shot_speed > 0) { // distance from self to target: double distance = Point(obj_w - muzzle_pts[0]).length(); // TRUE shot speed is relative to ship speed: Point eff_shot_vel = ship->Velocity() + aim_cam.vpn() * shot_speed - target->Velocity(); double eff_shot_speed = eff_shot_vel.length(); // time to reach target: double time = distance / eff_shot_speed; // where the target will be when the shot reaches it: obj_w += (target->Velocity() - ship->Velocity()) * time + target->Acceleration() * 0.25 * time * time; } // transform into camera coords: objective = Transform(obj_w); } Point Weapon::Transform(const Point& pt) { Point result; Point obj_t = pt - aim_cam.Pos(); result.x = obj_t * aim_cam.vrt(); result.y = obj_t * aim_cam.vup(); result.z = obj_t * aim_cam.vpn(); return result; } bool Weapon::CanLockPoint(const Point& test, double& az, double& el, Point* obj) { Point pt = Transform(test); bool locked = true; // first compute az: if (fabs(pt.z) < 0.1) pt.z = 0.1; az = atan(pt.x / pt.z); if (pt.z < 0) az -= PI; if (az < -PI) az += 2*PI; // then, rotate target into az-coords to compute el: Camera tmp; tmp.Clone(aim_cam); aim_cam.Yaw(az); pt = Transform(test); aim_cam.Clone(tmp); if (fabs(pt.z) < 0.1) pt.z = 0.1; el = atan(pt.y / pt.z); if (obj) *obj = pt; // is the target in the basket? // clamp if necessary: if (az > aim_az_max) { az = aim_az_max; locked = false; } else if (az < aim_az_min) { az = aim_az_min; locked = false; } if (el > aim_el_max) { el = aim_el_max; locked = false; } else if (el < aim_el_min) { el = aim_el_min; locked = false; } if (IsDrone() && guided) { double firing_cone = 10*DEGREES; if (orders == MANUAL) firing_cone = 20*DEGREES; if (az < firing_cone && el < firing_cone) locked = true; } return locked; } // +--------------------------------------------------------------------+ void Weapon::AimTurret(double az, double el) { double seconds = (Game::GameTime() - aim_time) / 1000.0; // don't let the weapon turn faster than turret slew rate: double max_turn = design->slew_rate * seconds; if (fabs(az-old_azimuth) > max_turn) { if (az > old_azimuth) az = old_azimuth + max_turn; else az = old_azimuth - max_turn; } if (fabs(el-old_elevation) > PI/2 * seconds) { if (el > old_elevation) el = old_elevation + max_turn; else el = old_elevation - max_turn; } aim_cam.Yaw(az); aim_cam.Pitch(el); old_azimuth = (float) az; old_elevation = (float) el; aim_time = Game::GameTime(); } void Weapon::ZeroAim() { aim_cam.Clone(ship->Cam()); if (aim_azimuth != 0) aim_cam.Yaw(aim_azimuth); if (aim_elevation != 0) aim_cam.Pitch(aim_elevation); aim_cam.MoveTo(muzzle_pts[0]); }