/* 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: StarshipAI.cpp AUTHOR: John DiCamillo OVERVIEW ======== Starship (low-level) Artificial Intelligence class */ #include "MemDebug.h" #include "StarshipAI.h" #include "StarshipTacticalAI.h" #include "Ship.h" #include "ShipDesign.h" #include "Element.h" #include "Mission.h" #include "Instruction.h" #include "RadioMessage.h" #include "Contact.h" #include "WeaponGroup.h" #include "Drive.h" #include "Sim.h" #include "StarSystem.h" #include "FlightComp.h" #include "Farcaster.h" #include "QuantumDrive.h" #include "Game.h" #include "Random.h" // +----------------------------------------------------------------------+ StarshipAI::StarshipAI(SimObject* s) : ShipAI(s), sub_select_time(0), subtarget(0), tgt_point_defense(false) { ai_type = STARSHIP; // signifies this ship is a dead hulk: if (ship && ship->Design()->auto_roll < 0) { Point torque(rand()-16000, rand()-16000, rand()-16000); torque.Normalize(); torque *= ship->Mass() / 10; ship->SetFLCSMode(0); if (ship->GetFLCS()) ship->GetFLCS()->PowerOff(); ship->ApplyTorque(torque); ship->SetVelocity(RandomDirection() * Random(20, 50)); for (int i = 0; i < 64; i++) { Weapon* w = ship->GetWeaponByIndex(i+1); if (w) w->DrainPower(0); else break; } } else { tactical = new(__FILE__,__LINE__) StarshipTacticalAI(this); } sub_select_time = Game::GameTime() + (DWORD) Random(0, 2000); point_defense_time = sub_select_time; } // +--------------------------------------------------------------------+ StarshipAI::~StarshipAI() { } // +--------------------------------------------------------------------+ void StarshipAI::FindObjective() { distance = 0; int order = ship->GetRadioOrders()->Action(); if (order == RadioMessage::QUANTUM_TO || order == RadioMessage::FARCAST_TO) { FindObjectiveQuantum(); objective = Transform(obj_w); return; } bool hold = order == RadioMessage::WEP_HOLD || order == RadioMessage::FORM_UP; bool form = hold || (!order && !target) || (farcaster); // if not the element leader, stay in formation: if (form && element_index > 1) { ship->SetDirectorInfo(Game::GetText("ai.formation")); if (navpt && navpt->Action() == Instruction::LAUNCH) { FindObjectiveNavPoint(); } else { navpt = 0; FindObjectiveFormation(); } // transform into camera coords: objective = Transform(obj_w); return; } // under orders? bool directed = false; double threat_level = 0; double support_level = 1; Ship* ward = ship->GetWard(); if (tactical) { directed = (tactical->RulesOfEngagement() == TacticalAI::DIRECTED); threat_level = tactical->ThreatLevel(); support_level = tactical->SupportLevel(); } // threat processing: if (hold || !directed && threat_level >= 2*support_level) { // seek support: if (support) { double d_support = Point(support->Location() - ship->Location()).length(); if (d_support > 35e3) { ship->SetDirectorInfo(Game::GetText("ai.regroup")); FindObjectiveTarget(support); objective = Transform(obj_w); return; } } // run away: else if (threat && threat != target) { ship->SetDirectorInfo(Game::GetText("ai.retreat")); obj_w = ship->Location() + Point(ship->Location() - threat->Location()) * 100; objective = Transform(obj_w); return; } } // weapons hold: if (hold) { if (navpt) { ship->SetDirectorInfo(Game::GetText("ai.seek-navpt")); FindObjectiveNavPoint(); } else if (patrol) { ship->SetDirectorInfo(Game::GetText("ai.patrol")); FindObjectivePatrol(); } else { ship->SetDirectorInfo(Game::GetText("ai.holding")); objective = Point(); } } // normal processing: else if (target) { ship->SetDirectorInfo(Game::GetText("ai.seek-target")); FindObjectiveTarget(target); } else if (patrol) { ship->SetDirectorInfo(Game::GetText("ai.patrol")); FindObjectivePatrol(); } else if (ward) { ship->SetDirectorInfo(Game::GetText("ai.seek-ward")); FindObjectiveFormation(); } else if (navpt) { ship->SetDirectorInfo(Game::GetText("ai.seek-navpt")); FindObjectiveNavPoint(); } else if (rumor) { ship->SetDirectorInfo(Game::GetText("ai.search")); FindObjectiveTarget(rumor); } else { objective = Point(); } // transform into camera coords: objective = Transform(obj_w); } // +--------------------------------------------------------------------+ void StarshipAI::Navigator() { // signifies this ship is a dead hulk: if (ship && ship->Design()->auto_roll < 0) { ship->SetDirectorInfo(Game::GetText("ai.dead")); return; } accumulator.Clear(); magnitude = 0; hold = false; if ((ship->GetElement() && ship->GetElement()->GetHoldTime() > 0) || (navpt && navpt->Status() == Instruction::COMPLETE && navpt->HoldTime() > 0)) hold = true; ship->SetFLCSMode(Ship::FLCS_HELM); if (!ship->GetDirectorInfo()) { if (target) ship->SetDirectorInfo(Game::GetText("ai.seek-target")); else if (ship->GetWard()) ship->SetDirectorInfo(Game::GetText("ai.seek-ward")); else ship->SetDirectorInfo(Game::GetText("ai.patrol")); } if (farcaster && distance < 25e3) { accumulator = SeekTarget(); } else { accumulator = AvoidCollision(); if (!other && !hold) accumulator = SeekTarget(); } HelmControl(); ThrottleControl(); FireControl(); AdjustDefenses(); } // +--------------------------------------------------------------------+ void StarshipAI::HelmControl() { // signifies this ship is a dead hulk: if (ship && ship->Design()->auto_roll < 0) { return; } double trans_x = 0; double trans_y = 0; double trans_z = 0; bool station_keeping = distance < 0; if (station_keeping) { accumulator.brake = 1; accumulator.stop = 1; ship->SetHelmPitch(0); } else { Element* elem = ship->GetElement(); Ship* ward = ship->GetWard(); Ship* s_threat = 0; if (threat && threat->Class() >= ship->Class()) s_threat = threat; if (other || target || ward || s_threat || navpt || patrol || farcaster || element_index > 1) { ship->SetHelmHeading(accumulator.yaw); if (elem->Type() == Mission::FLIGHT_OPS) { ship->SetHelmPitch(0); if (ship->NumInbound() > 0) { ship->SetHelmHeading(ship->CompassHeading()); } } else if (accumulator.pitch > 60*DEGREES) { ship->SetHelmPitch(60*DEGREES); } else if (accumulator.pitch < -60*DEGREES) { ship->SetHelmPitch(-60*DEGREES); } else { ship->SetHelmPitch(accumulator.pitch); } } else { ship->SetHelmPitch(0); } } ship->SetTransX(trans_x); ship->SetTransY(trans_y); ship->SetTransZ(trans_z); ship->ExecFLCSFrame(); } void StarshipAI::ThrottleControl() { // signifies this ship is a dead hulk: if (ship && ship->Design()->auto_roll < 0) { return; } // station keeping: if (distance < 0) { old_throttle = 0; throttle = 0; ship->SetThrottle(0); if (ship->GetFLCS()) ship->GetFLCS()->FullStop(); return; } // normal throttle processing: double ship_speed = ship->Velocity() * ship->Heading(); double brakes = 0; Ship* ward = ship->GetWard(); Ship* s_threat = 0; if (threat && threat->Class() >= ship->Class()) s_threat = threat; if (target || s_threat) { // target pursuit, or retreat throttle = 100; if (target && distance < 50e3) { double closing_speed = ship_speed; if (target) { Point delta = target->Location() - ship->Location(); delta.Normalize(); closing_speed = ship->Velocity() * delta; } if (closing_speed > 300) { throttle = 30; brakes = 0.25; } } throttle *= (1 - accumulator.brake); if (throttle < 1 && ship->GetFLCS() != 0) ship->GetFLCS()->FullStop(); } else if (ward) { // escort, match speed of ward double speed = ward->Velocity().length(); throttle = old_throttle; if (speed == 0) { double d = (ship->Location() - ward->Location()).length(); if (d > 30e3) speed = (d - 30e3) / 100; } if (speed > 0) { if (ship_speed > speed) { throttle = old_throttle - 1; brakes = 0.2; } else if (ship_speed < speed - 10) { throttle = old_throttle + 1; } } else { throttle = 0; brakes = 0.5; } } else if (patrol || farcaster) { // seek patrol point throttle = 100; if (distance < 10 * ship_speed) { if (ship->Velocity().length() > 200) throttle = 5; else throttle = 50; } } else if (navpt) { // lead only, get speed from navpt double speed = navpt->Speed(); throttle = old_throttle; if (hold) { throttle = 0; brakes = 1; } else { if (speed <= 0) speed = 300; if (ship_speed > speed) { if (throttle > 0 && old_throttle > 1) throttle = old_throttle - 1; brakes = 0.25; } else if (ship_speed < speed - 10) { throttle = old_throttle + 1; } } } else if (element_index > 1) { // wingman Ship* lead = ship->GetElement()->GetShip(1); double lv = lead->Velocity().length(); double sv = ship_speed; double dv = lv-sv; double dt = 0; if (dv > 0) dt = dv * 1e-2 * seconds; else if (dv < 0) dt = dv * 1e-2 * seconds; throttle = old_throttle + dt; } else { throttle = 0; } old_throttle = throttle; ship->SetThrottle(throttle); if (ship_speed > 1 && brakes > 0) ship->SetTransY(-brakes * ship->Design()->trans_y); else if (throttle > 10 && (ship->GetEMCON() < 2 || ship->GetFuelLevel() < 10)) ship->SetTransY(ship->Design()->trans_y); } // +--------------------------------------------------------------------+ Steer StarshipAI::SeekTarget() { if (navpt) { SimRegion* self_rgn = ship->GetRegion(); SimRegion* nav_rgn = navpt->Region(); QuantumDrive* qdrive = ship->GetQuantumDrive(); if (self_rgn && !nav_rgn) { nav_rgn = self_rgn; navpt->SetRegion(nav_rgn); } bool use_farcaster = self_rgn != nav_rgn && (navpt->Farcast() || !qdrive || !qdrive->IsPowerOn() || qdrive->Status() < System::DEGRADED ); if (use_farcaster) { if (!farcaster) { ListIter s = self_rgn->Ships(); while (++s && !farcaster) { if (s->GetFarcaster()) { const Ship* dest = s->GetFarcaster()->GetDest(); if (dest && dest->GetRegion() == nav_rgn) { farcaster = s->GetFarcaster(); } } } } if (farcaster) { if (farcaster->GetShip()->GetRegion() != self_rgn) farcaster = farcaster->GetDest()->GetFarcaster(); obj_w = farcaster->EndPoint(); distance = Point(obj_w - ship->Location()).length(); if (distance < 1000) farcaster = 0; } } else if (self_rgn != nav_rgn) { QuantumDrive* q = ship->GetQuantumDrive(); if (q) { if (q->ActiveState() == QuantumDrive::ACTIVE_READY) { q->SetDestination(navpt->Region(), navpt->Location()); q->Engage(); } } } } return ShipAI::SeekTarget(); } // +--------------------------------------------------------------------+ Steer StarshipAI::AvoidCollision() { if (!ship || ship->Velocity().length() < 25) return Steer(); return ShipAI::AvoidCollision(); } // +--------------------------------------------------------------------+ void StarshipAI::FireControl() { // identify unknown contacts: if (identify) { if (fabs(ship->GetHelmHeading() - ship->CompassHeading()) < 10*DEGREES) { Contact* contact = ship->FindContact(target); if (contact && !contact->ActLock()) { if (!ship->GetProbe()) { ship->LaunchProbe(); } } } return; } // investigate last known location of enemy ship: if (rumor && !target && ship->GetProbeLauncher() && !ship->GetProbe()) { // is rumor in basket? Point rmr = Transform(rumor->Location()); rmr.Normalize(); double dx = fabs(rmr.x); double dy = fabs(rmr.y); if (dx < 10*DEGREES && dy < 10*DEGREES && rmr.z > 0) { ship->LaunchProbe(); } } // Corvettes and Frigates are anti-air platforms. They need to // target missile threats even when the threat is aimed at another // friendly ship. Forward facing weapons must be on auto fire, // while lateral and aft facing weapons are set to point defense. if (ship->Class() == Ship::CORVETTE || ship->Class() == Ship::FRIGATE) { ListIter iter = ship->Weapons(); while (++iter) { WeaponGroup* group = iter.value(); ListIter w_iter = group->GetWeapons(); while (++w_iter) { Weapon* weapon = w_iter.value(); double az = weapon->GetAzimuth(); if (fabs(az) < 45*DEGREES) { weapon->SetFiringOrders(Weapon::AUTO); weapon->SetTarget(target, 0); } else { weapon->SetFiringOrders(Weapon::POINT_DEFENSE); } } } } // All other starships are free to engage ship targets. Weapon // fire control is managed by the type of weapon. else { System* subtgt = SelectSubtarget(); ListIter iter = ship->Weapons(); while (++iter) { WeaponGroup* weapon = iter.value(); if (weapon->GetDesign()->target_type & Ship::DROPSHIPS) { // anti-air weapon? weapon->SetFiringOrders(Weapon::POINT_DEFENSE); } else if (weapon->IsDrone()) { // torpedoes weapon->SetFiringOrders(Weapon::MANUAL); weapon->SetTarget(target, 0); if (target && target->GetRegion() == ship->GetRegion()) { Point delta = target->Location() - ship->Location(); double range = delta.length(); if (range < weapon->GetDesign()->max_range * 0.9 && !AssessTargetPointDefense()) weapon->SetFiringOrders(Weapon::AUTO); else if (range < weapon->GetDesign()->max_range * 0.5) weapon->SetFiringOrders(Weapon::AUTO); } } else { // anti-ship weapon weapon->SetFiringOrders(Weapon::AUTO); weapon->SetTarget(target, subtgt); weapon->SetSweep(subtgt ? Weapon::SWEEP_NONE : Weapon::SWEEP_TIGHT); } } } } // +--------------------------------------------------------------------+ System* StarshipAI::SelectSubtarget() { if (Game::GameTime() - sub_select_time < 2345) return subtarget; subtarget = 0; if (!target || target->Type() != SimObject::SIM_SHIP || GetAILevel() < 1) return subtarget; Ship* tgt_ship = (Ship*) target; if (!tgt_ship->IsStarship()) return subtarget; Weapon* subtgt = 0; double dist = 50e3; Point svec = ship->Location() - tgt_ship->Location(); sub_select_time = Game::GameTime(); // first pass: turrets ListIter g_iter = tgt_ship->Weapons(); while (++g_iter) { WeaponGroup* g = g_iter.value(); if (g->GetDesign() && g->GetDesign()->turret_model) { ListIter w_iter = g->GetWeapons(); while (++w_iter) { Weapon* w = w_iter.value(); if (w->Availability() < 35) continue; if (w->GetAimVector() * svec < 0) continue; if (w->GetTurret()) { Point tloc = w->GetTurret()->Location(); Point delta = tloc - ship->Location(); double dlen = delta.length(); if (dlen < dist) { subtgt = w; dist = dlen; } } } } } // second pass: major weapons if (!subtgt) { g_iter.reset(); while (++g_iter) { WeaponGroup* g = g_iter.value(); if (g->GetDesign() && !g->GetDesign()->turret_model) { ListIter w_iter = g->GetWeapons(); while (++w_iter) { Weapon* w = w_iter.value(); if (w->Availability() < 35) continue; if (w->GetAimVector() * svec < 0) continue; Point tloc = w->MountLocation(); Point delta = tloc - ship->Location(); double dlen = delta.length(); if (dlen < dist) { subtgt = w; dist = dlen; } } } } } subtarget = subtgt; return subtarget; } // +--------------------------------------------------------------------+ bool StarshipAI::AssessTargetPointDefense() { if (Game::GameTime() - point_defense_time < 3500) return tgt_point_defense; tgt_point_defense = false; if (!target || target->Type() != SimObject::SIM_SHIP || GetAILevel() < 2) return tgt_point_defense; Ship* tgt_ship = (Ship*) target; if (!tgt_ship->IsStarship()) return tgt_point_defense; Weapon* subtgt = 0; Point svec = ship->Location() - tgt_ship->Location(); point_defense_time = Game::GameTime(); // first pass: turrets ListIter g_iter = tgt_ship->Weapons(); while (++g_iter && !tgt_point_defense) { WeaponGroup* g = g_iter.value(); if (g->CanTarget(1)) { ListIter w_iter = g->GetWeapons(); while (++w_iter && !tgt_point_defense) { Weapon* w = w_iter.value(); if (w->Availability() > 35 && w->GetAimVector() * svec > 0) tgt_point_defense = true; } } } return tgt_point_defense; } // +--------------------------------------------------------------------+ Point StarshipAI::Transform(const Point& point) { return point - self->Location(); } Steer StarshipAI::Seek(const Point& point) { // the point is in relative world coordinates // x: distance east(-) / west(+) // y: altitude down(-) / up(+) // z: distance north(-) / south(+) Steer result; result.yaw = atan2(point.x, point.z) + PI; double adjacent = sqrt(point.x*point.x + point.z*point.z); if (fabs(point.y) > ship->Radius() && adjacent > ship->Radius()) result.pitch = atan(point.y / adjacent); if (!_finite(result.yaw)) result.yaw = 0; if (!_finite(result.pitch)) result.pitch = 0; return result; } Steer StarshipAI::Flee(const Point& point) { Steer result = Seek(point); result.yaw += PI; return result; } Steer StarshipAI::Avoid(const Point& point, float radius) { Steer result = Seek(point); if (point * ship->BeamLine() > 0) result.yaw -= PI/2; else result.yaw += PI/2; return result; }