diff options
author | milo24x7@gmail.com <milo24x7@gmail.com@076cb2c4-205e-83fd-5cf3-1be9aa105544> | 2013-07-07 22:08:49 +0000 |
---|---|---|
committer | milo24x7@gmail.com <milo24x7@gmail.com@076cb2c4-205e-83fd-5cf3-1be9aa105544> | 2013-07-07 22:08:49 +0000 |
commit | d17521c8b9085a91d08fecfd0b51bbbf7b1dccac (patch) | |
tree | 4673104b47dc68a079cac9f94deefd48dfcb66fa /Stars45/TacticalAI.cpp | |
parent | 1de4b2bdbb019be6f1b7262c3eba5568d7682edd (diff) | |
download | starshatter-d17521c8b9085a91d08fecfd0b51bbbf7b1dccac.zip starshatter-d17521c8b9085a91d08fecfd0b51bbbf7b1dccac.tar.gz starshatter-d17521c8b9085a91d08fecfd0b51bbbf7b1dccac.tar.bz2 |
Updated open source license declaration and fixed some formatting issues.
Diffstat (limited to 'Stars45/TacticalAI.cpp')
-rw-r--r-- | Stars45/TacticalAI.cpp | 1942 |
1 files changed, 983 insertions, 959 deletions
diff --git a/Stars45/TacticalAI.cpp b/Stars45/TacticalAI.cpp index f8e7e32..0e61015 100644 --- a/Stars45/TacticalAI.cpp +++ b/Stars45/TacticalAI.cpp @@ -1,959 +1,983 @@ -/* Project Starshatter 4.5
- Destroyer Studios LLC
- Copyright (C) 1997-2004. All Rights Reserved.
-
- SUBSYSTEM: Stars.exe
- FILE: TacticalAI.cpp
- AUTHOR: John DiCamillo
-
-
- OVERVIEW
- ========
- Generic Ship Tactical Level AI class
-*/
-
-#include "MemDebug.h"
-#include "TacticalAI.h"
-#include "ShipAI.h"
-#include "CarrierAI.h"
-#include "Ship.h"
-#include "ShipDesign.h"
-#include "Element.h"
-#include "Instruction.h"
-#include "RadioMessage.h"
-#include "RadioTraffic.h"
-#include "Contact.h"
-#include "WeaponGroup.h"
-#include "Drive.h"
-#include "Hangar.h"
-#include "Sim.h"
-#include "Shot.h"
-#include "Drone.h"
-#include "StarSystem.h"
-
-#include "Game.h"
-#include "Random.h"
-
-// +----------------------------------------------------------------------+
-
-static int exec_time_seed = 0;
-
-// +----------------------------------------------------------------------+
-
-TacticalAI::TacticalAI(ShipAI* ai)
-: ship(0), ship_ai(0), carrier_ai(0), navpt(0), orders(0),
-action(0), threat_level(0), support_level(1),
-directed_tgtid(0)
-{
- if (ai) {
- ship_ai = ai;
- ship = ai->GetShip();
-
- Sim* sim = Sim::GetSim();
-
- if (ship && ship->GetHangar() && ship->GetCommandAILevel() > 0 &&
- ship != sim->GetPlayerShip())
- carrier_ai = new(__FILE__,__LINE__) CarrierAI(ship, ship_ai->GetAILevel());
- }
-
- agression = 0;
- roe = FLEXIBLE;
- element_index = 1;
- exec_time = exec_time_seed;
- exec_time_seed += 17;
-}
-
-TacticalAI::~TacticalAI()
-{
- delete carrier_ai;
-}
-
-// +--------------------------------------------------------------------+
-
-void
-TacticalAI::ExecFrame(double secs)
-{
- const int exec_period = 1000;
-
- if (!ship || !ship_ai)
- return;
-
- navpt = ship->GetNextNavPoint();
- orders = ship->GetRadioOrders();
-
- if ((int) Game::GameTime() - exec_time > exec_period) {
- element_index = ship->GetElementIndex();
-
- CheckOrders();
- SelectTarget();
- FindThreat();
- FindSupport();
-
- if (element_index > 1) {
- int formation = 0;
-
- if (orders && orders->Formation() >= 0)
- formation = orders->Formation();
-
- else if (navpt)
- formation = navpt->Formation();
-
- FindFormationSlot(formation);
- }
-
- ship_ai->SetNavPoint(navpt);
-
- if (carrier_ai)
- carrier_ai->ExecFrame(secs);
-
- exec_time += exec_period;
- }
-}
-
-// +--------------------------------------------------------------------+
-
-void
-TacticalAI::CheckOrders()
-{
- directed_tgtid = 0;
-
- if (CheckShipOrders())
- return;
-
- if (CheckFlightPlan())
- return;
-
- if (CheckObjectives())
- return;
-}
-
-// +--------------------------------------------------------------------+
-
-bool
-TacticalAI::CheckShipOrders()
-{
- return ProcessOrders();
-}
-
-// +--------------------------------------------------------------------+
-
-bool
-TacticalAI::CheckObjectives()
-{
- bool processed = false;
- Ship* ward = 0;
- Element* elem = ship->GetElement();
-
- if (elem) {
- Instruction* obj = elem->GetTargetObjective();
-
- if (obj) {
- ship_ai->ClearPatrol();
-
- if (obj->Action()) {
- switch (obj->Action()) {
- case Instruction::INTERCEPT:
- case Instruction::STRIKE:
- case Instruction::ASSAULT:
- {
- SimObject* tgt = obj->GetTarget();
- if (tgt && tgt->Type() == SimObject::SIM_SHIP) {
- roe = DIRECTED;
- SelectTargetDirected((Ship*) tgt);
- }
- }
- break;
-
- case Instruction::DEFEND:
- case Instruction::ESCORT:
- {
- SimObject* tgt = obj->GetTarget();
- if (tgt && tgt->Type() == SimObject::SIM_SHIP) {
- roe = DEFENSIVE;
- ward = (Ship*) tgt;
- }
- }
- break;
-
- default:
- break;
- }
- }
-
- orders = obj;
- processed = true;
- }
- }
-
- ship_ai->SetWard(ward);
- return processed;
-}
-
-// +--------------------------------------------------------------------+
-
-bool
-TacticalAI::ProcessOrders()
-{
- if (ship_ai)
- ship_ai->ClearPatrol();
-
- if (orders && orders->EMCON() > 0) {
- int desired_emcon = orders->EMCON();
-
- if (ship_ai && (ship_ai->GetThreat() || ship_ai->GetThreatMissile()))
- desired_emcon = 3;
-
- if (ship->GetEMCON() != desired_emcon)
- ship->SetEMCON(desired_emcon);
- }
-
- if (orders && orders->Action()) {
- switch (orders->Action()) {
- case RadioMessage::ATTACK:
- case RadioMessage::BRACKET:
- case RadioMessage::IDENTIFY:
- {
- bool tgt_ok = false;
- SimObject* tgt = orders->GetTarget();
-
- if (tgt && tgt->Type() == SimObject::SIM_SHIP) {
- Ship* tgt_ship = (Ship*) tgt;
-
- if (CanTarget(tgt_ship)) {
- roe = DIRECTED;
- SelectTargetDirected((Ship*) tgt);
-
- ship_ai->SetBracket(orders->Action() == RadioMessage::BRACKET);
- ship_ai->SetIdentify(orders->Action() == RadioMessage::IDENTIFY);
- ship_ai->SetNavPoint(0);
-
- tgt_ok = true;
- }
- }
-
- if (!tgt_ok)
- ClearRadioOrders();
- }
- break;
-
- case RadioMessage::ESCORT:
- case RadioMessage::COVER_ME:
- {
- SimObject* tgt = orders->GetTarget();
- if (tgt && tgt->Type() == SimObject::SIM_SHIP) {
- roe = DEFENSIVE;
- ship_ai->SetWard((Ship*) tgt);
- ship_ai->SetNavPoint(0);
- }
- else {
- ClearRadioOrders();
- }
- }
- break;
-
- case RadioMessage::WEP_FREE:
- roe = AGRESSIVE;
- ship_ai->DropTarget(0.1);
- break;
-
- case RadioMessage::WEP_HOLD:
- case RadioMessage::FORM_UP:
- roe = NONE;
- ship_ai->DropTarget(5);
- break;
-
- case RadioMessage::MOVE_PATROL:
- roe = SELF_DEFENSIVE;
- ship_ai->SetPatrol(orders->Location());
- ship_ai->SetNavPoint(0);
- ship_ai->DropTarget(Random(5, 10));
- break;
-
- case RadioMessage::RTB:
- case RadioMessage::DOCK_WITH:
- roe = NONE;
-
- ship_ai->DropTarget(10);
-
- if (!ship->GetInbound()) {
- RadioMessage* msg = 0;
- Ship* controller = ship->GetController();
-
- if (orders->Action() == RadioMessage::DOCK_WITH && orders->GetTarget()) {
- controller = (Ship*) orders->GetTarget();
- }
-
- if (!controller) {
- Element* elem = ship->GetElement();
- if (elem && elem->GetCommander()) {
- Element* cmdr = elem->GetCommander();
- controller = cmdr->GetShip(1);
- }
- }
-
- if (controller && controller->GetHangar() &&
- controller->GetHangar()->CanStow(ship)) {
- SimRegion* self_rgn = ship->GetRegion();
- SimRegion* rtb_rgn = controller->GetRegion();
-
- if (self_rgn == rtb_rgn) {
- double range = Point(controller->Location() - ship->Location()).length();
-
- if (range < 50e3) {
- msg = new(__FILE__,__LINE__) RadioMessage(controller, ship, RadioMessage::CALL_INBOUND);
- RadioTraffic::Transmit(msg);
- }
- }
- }
- else {
- ship->ClearRadioOrders();
- }
-
- ship_ai->SetNavPoint(0);
- }
- break;
-
- case RadioMessage::QUANTUM_TO:
- case RadioMessage::FARCAST_TO:
- roe = NONE;
- ship_ai->DropTarget(10);
- break;
-
- }
-
- action = orders->Action();
- return true;
- }
-
- // if we had an action before, this must be a "cancel orders"
- else if (action) {
- ClearRadioOrders();
- }
-
- return false;
-}
-
-void
-TacticalAI::ClearRadioOrders()
-{
- action = 0;
- roe = FLEXIBLE;
-
- if (ship_ai)
- ship_ai->DropTarget(0.1);
-
- if (ship)
- ship->ClearRadioOrders();
-
-}
-
-// +--------------------------------------------------------------------+
-
-bool
-TacticalAI::CheckFlightPlan()
-{
- Ship* ward = 0;
-
- // Find next Instruction:
- navpt = ship->GetNextNavPoint();
-
- roe = FLEXIBLE;
-
- if (navpt) {
- switch (navpt->Action()) {
- case Instruction::LAUNCH:
- case Instruction::DOCK:
- case Instruction::RTB: roe = NONE;
- break;
-
- case Instruction::VECTOR: roe = SELF_DEFENSIVE;
- break;
-
- case Instruction::DEFEND:
- case Instruction::ESCORT: roe = DEFENSIVE;
- break;
-
- case Instruction::INTERCEPT:
- roe = DIRECTED;
- break;
-
- case Instruction::RECON:
- case Instruction::STRIKE:
- case Instruction::ASSAULT: roe = DIRECTED;
- break;
-
- case Instruction::PATROL:
- case Instruction::SWEEP: roe = FLEXIBLE;
- break;
-
- default: break;
- }
-
- if (roe == DEFENSIVE) {
- SimObject* tgt = navpt->GetTarget();
-
- if (tgt && tgt->Type() == SimObject::SIM_SHIP)
- ward = (Ship*) tgt;
- }
-
-
- if (navpt->EMCON() > 0) {
- int desired_emcon = navpt->EMCON();
-
- if (ship_ai && (ship_ai->GetThreat() || ship_ai->GetThreatMissile()))
- desired_emcon = 3;
-
- if (ship->GetEMCON() != desired_emcon)
- ship->SetEMCON(desired_emcon);
- }
- }
-
- if (ship_ai)
- ship_ai->SetWard(ward);
-
- return (navpt != 0);
-}
-
-// +--------------------------------------------------------------------+
-
-void
-TacticalAI::SelectTarget()
-{
- if (!ship) {
- roe = NONE;
- return;
- }
-
- // unarmed vessels should never engage an enemy:
- if (ship->Weapons().size() < 1)
- roe = NONE;
-
- SimObject* target = ship_ai->GetTarget();
- SimObject* ward = ship_ai->GetWard();
-
- // if not allowed to engage, drop and return:
- if (roe == NONE) {
- if (target)
- ship_ai->DropTarget();
- return;
- }
-
- // if we have abandoned our ward, drop and return:
- if (ward && roe != AGRESSIVE) {
- double d = (ward->Location() - ship->Location()).length();
- double safe_zone = 50e3;
-
- if (target) {
- if (ship->IsStarship())
- safe_zone = 100e3;
-
- if (d > safe_zone) {
- ship_ai->DropTarget();
- return;
- }
- }
- else {
- if (d > safe_zone) {
- return;
- }
- }
- }
-
- // already have a target, keep it:
- if (target) {
- if (target->Life()) {
- CheckTarget();
-
- // frigates need to be ready to abandon ship-type targets
- // in favor of drone-type targets, others should just go
- // with what they have:
- if (ship->Class() != Ship::CORVETTE && ship->Class() != Ship::FRIGATE)
- return;
-
- // in case the check decided to drop the target:
- target = ship_ai->GetTarget();
- }
-
- // if the old target is dead, forget it:
- else {
- ship_ai->DropTarget();
- target = 0;
- }
- }
-
- // if not allowed to acquire, forget it:
- if (ship_ai->DropTime() > 0)
- return;
-
- if (roe == DIRECTED) {
- if (target && target->Type() == SimObject::SIM_SHIP)
- SelectTargetDirected((Ship*) target);
- else if (navpt && navpt->GetTarget() && navpt->GetTarget()->Type() == SimObject::SIM_SHIP)
- SelectTargetDirected((Ship*) navpt->GetTarget());
- else
- SelectTargetDirected();
- }
-
- else {
- SelectTargetOpportunity();
-
- // don't switch one ship target for another...
- if (ship->Class() == Ship::CORVETTE || ship->Class() == Ship::FRIGATE) {
- SimObject* potential_target = ship_ai->GetTarget();
- if (target && potential_target && target != potential_target) {
- if (target->Type() == SimObject::SIM_SHIP &&
- potential_target->Type() == SimObject::SIM_SHIP) {
-
- ship_ai->SetTarget(target);
- }
- }
- }
- }
-}
-
-// +--------------------------------------------------------------------+
-
-void
-TacticalAI::SelectTargetDirected(Ship* tgt)
-{
- Ship* potential_target = tgt;
-
- // try to target one of the element's objectives
- // (if it shows up in the contact list)
-
- if (!tgt) {
- Element* elem = ship->GetElement();
-
- if (elem) {
- Instruction* objective = elem->GetTargetObjective();
-
- if (objective) {
- SimObject* obj_sim_obj = objective->GetTarget();
- Ship* obj_tgt = 0;
-
- if (obj_sim_obj && obj_sim_obj->Type() == SimObject::SIM_SHIP)
- obj_tgt = (Ship*) obj_sim_obj;
-
- if (obj_tgt) {
- ListIter<Contact> contact = ship->ContactList();
- while (++contact && !potential_target) {
- Ship* test = contact->GetShip();
-
- if (obj_tgt == test) {
- potential_target = test;
- }
- }
- }
- }
- }
- }
-
- if (!CanTarget(potential_target))
- potential_target = 0;
-
- ship_ai->SetTarget(potential_target);
-
- if (tgt && tgt == ship_ai->GetTarget())
- directed_tgtid = tgt->Identity();
- else
- directed_tgtid = 0;
-}
-
-// +--------------------------------------------------------------------+
-
-bool
-TacticalAI::CanTarget(Ship* tgt)
-{
- bool result = false;
-
- if (tgt && !tgt->InTransition()) {
- if (tgt->IsRogue() || tgt->GetIFF() != ship->GetIFF())
- result = true;
- }
-
- return result;
-}
-
-// +--------------------------------------------------------------------+
-
-void
-TacticalAI::SelectTargetOpportunity()
-{
- // NON-COMBATANTS do not pick targets of opportunity:
- if (ship->GetIFF() == 0)
- return;
-
- SimObject* potential_target = 0;
-
- // pick the closest combatant ship with a different IFF code:
- double target_dist = ship->Design()->commit_range;
-
- SimObject* ward = ship_ai->GetWard();
-
- // FRIGATES are primarily anti-air platforms, but may
- // also attack smaller starships:
-
- if (ship->Class() == Ship::CORVETTE || ship->Class() == Ship::FRIGATE) {
- Ship* current_ship_target = 0;
- Shot* current_shot_target = 0;
-
- // if we are escorting a larger warship, it is good to attack
- // the same target as our ward:
-
- if (ward) {
- Ship* s = (Ship*) ward;
-
- if (s->Class() > ship->Class()) {
- SimObject* obj = s->GetTarget();
-
- if (obj && obj->Type() == SimObject::SIM_SHIP) {
- current_ship_target = (Ship*) obj;
- target_dist = (ship->Location() - obj->Location()).length();
- }
- }
- }
-
- ListIter<Contact> contact = ship->ContactList();
- while (++contact) {
- Ship* c_ship = contact->GetShip();
- Shot* c_shot = contact->GetShot();
-
- if (!c_ship && !c_shot)
- continue;
-
- int c_iff = contact->GetIFF(ship);
- bool rogue = c_ship && c_ship->IsRogue();
- bool tgt_ok = c_iff > 0 &&
- c_iff != ship->GetIFF() &&
- c_iff < 1000;
-
- if (rogue || tgt_ok) {
- if (c_ship && c_ship != ship && !c_ship->InTransition()) {
- if (c_ship->Class() < Ship::DESTROYER ||
- (c_ship->Class() >= Ship::MINE && c_ship->Class() <= Ship::DEFSAT)) {
- // found an enemy, check distance:
- double dist = (ship->Location() - c_ship->Location()).length();
-
- if (dist < 0.75 * target_dist &&
- (!current_ship_target || c_ship->Class() <= current_ship_target->Class())) {
- current_ship_target = c_ship;
- target_dist = dist;
- }
- }
- }
-
- else if (c_shot) {
- // found an enemy shot, is there enough time to engage?
- if (c_shot->GetEta() < 3)
- continue;
-
- // found an enemy shot, check distance:
- double dist = (ship->Location() - c_shot->Location()).length();
-
- if (!current_shot_target) {
- current_shot_target = c_shot;
- target_dist = dist;
- }
-
- // is this shot a better target than the one we've found?
- else {
- Ship* ward = ship_ai->GetWard();
-
- if ((c_shot->IsTracking(ward) || c_shot->IsTracking(ship)) &&
- (!current_shot_target->IsTracking(ward) ||
- !current_shot_target->IsTracking(ship))) {
- current_shot_target = c_shot;
- target_dist = dist;
- }
- else if (dist < target_dist) {
- current_shot_target = c_shot;
- target_dist = dist;
- }
- }
- }
- }
- }
-
- if (current_shot_target)
- potential_target = current_shot_target;
- else
- potential_target = current_ship_target;
- }
-
- // ALL OTHER SHIP CLASSES ignore fighters and only engage
- // other starships:
-
- else {
- List<Ship> ward_threats;
-
- ListIter<Contact> contact = ship->ContactList();
- while (++contact) {
- Ship* c_ship = contact->GetShip();
-
- if (!c_ship)
- continue;
-
- int c_iff = contact->GetIFF(ship);
- bool rogue = c_ship->IsRogue();
- bool tgt_ok = c_ship != ship &&
- c_iff > 0 &&
- c_iff != ship->GetIFF() &&
- !c_ship->InTransition();
-
- if (rogue || tgt_ok) {
- if (c_ship->IsStarship() || c_ship->IsStatic()) {
- // found an enemy, check distance:
- double dist = (ship->Location() - c_ship->Location()).length();
-
- if (dist < 0.75 * target_dist) {
- potential_target = c_ship;
- target_dist = dist;
- }
-
- if (ward && c_ship->IsTracking(ward)) {
- ward_threats.append(c_ship);
- }
- }
- }
- }
-
- // if this ship is protecting a ward,
- // prefer targets that are threatening that ward:
- if (potential_target && ward_threats.size() && !ward_threats.contains((Ship*)potential_target)) {
- target_dist *= 2;
-
- ListIter<Ship> iter = ward_threats;
- while (++iter) {
- Ship* threat = iter.value();
-
- double dist = (ward->Location() - threat->Location()).length();
-
- if (dist < target_dist) {
- potential_target = threat;
- target_dist = dist;
- }
- }
- }
- }
-
- if (ship->Class() != Ship::CARRIER && ship->Class() != Ship::SWACS)
- ship_ai->SetTarget(potential_target);
-}
-
-// +--------------------------------------------------------------------+
-
-void
-TacticalAI::CheckTarget()
-{
- SimObject* tgt = ship_ai->GetTarget();
-
- if (!tgt) return;
-
- if (tgt->GetRegion() != ship->GetRegion()) {
- ship_ai->DropTarget();
- return;
- }
-
- if (tgt->Type() == SimObject::SIM_SHIP) {
- Ship* target = (Ship*) tgt;
-
- // has the target joined our side?
- if (target->GetIFF() == ship->GetIFF() && !target->IsRogue()) {
- ship_ai->DropTarget();
- return;
- }
-
- // is the target already jumping/breaking/dying?
- if (target->InTransition()) {
- ship_ai->DropTarget();
- return;
- }
-
- // have we been ordered to pursue the target?
- if (directed_tgtid) {
- if (directed_tgtid != target->Identity()) {
- ship_ai->DropTarget();
- }
-
- return;
- }
-
- // can we catch the target?
- if (target->Design()->vlimit <= ship->Design()->vlimit ||
- ship->Velocity().length() <= ship->Design()->vlimit)
- return;
-
- // is the target now out of range?
- WeaponDesign* wep_dsn = ship->GetPrimaryDesign();
- if (!wep_dsn)
- return;
-
- // compute the "give up" range:
- double drop_range = 3 * wep_dsn->max_range;
- if (drop_range > 0.75 * ship->Design()->commit_range)
- drop_range = 0.75 * ship->Design()->commit_range;
-
- double range = Point(target->Location() - ship->Location()).length();
- if (range < drop_range)
- return;
-
- // is the target closing or separating?
- Point delta = (target->Location() + target->Velocity()) -
- (ship->Location() + ship->Velocity());
-
- if (delta.length() < range)
- return;
-
- ship_ai->DropTarget();
- }
-
- else if (tgt->Type() == SimObject::SIM_DRONE) {
- Drone* drone = (Drone*) tgt;
-
- // is the target still a threat?
- if (drone->GetEta() < 1 || drone->GetTarget() == 0)
- ship_ai->DropTarget();
- }
-}
-
-// +--------------------------------------------------------------------+
-
-void
-TacticalAI::FindThreat()
-{
- // pick the closest contact on Threat Warning System:
- Ship* threat = 0;
- Shot* threat_missile = 0;
- Ship* rumor = 0;
- double threat_dist = 1e9;
- const DWORD THREAT_REACTION_TIME = 1000; // 1 second
-
- ListIter<Contact> iter = ship->ContactList();
-
- while (++iter) {
- Contact* contact = iter.value();
-
- if (contact->Threat(ship) &&
- (Game::GameTime() - contact->AcquisitionTime()) > THREAT_REACTION_TIME) {
-
- if (contact->GetShot()) {
- threat_missile = contact->GetShot();
- rumor = (Ship*) threat_missile->Owner();
- }
- else {
- double rng = contact->Range(ship);
-
- Ship* c_ship = contact->GetShip();
- if (c_ship && !c_ship->InTransition() &&
- c_ship->Class() != Ship::FREIGHTER &&
- c_ship->Class() != Ship::FARCASTER) {
-
- if (c_ship->GetTarget() == ship) {
- if (!threat || c_ship->Class() > threat->Class()) {
- threat = c_ship;
- threat_dist = 0;
- }
- }
- else if (rng < threat_dist) {
- threat = c_ship;
- threat_dist = rng;
- }
- }
- }
- }
- }
-
- if (rumor && !rumor->InTransition()) {
- iter.reset();
-
- while (++iter) {
- if (iter->GetShip() == rumor) {
- rumor = 0;
- ship_ai->ClearRumor();
- break;
- }
- }
- }
- else {
- rumor = 0;
- ship_ai->ClearRumor();
- }
-
- ship_ai->SetRumor(rumor);
- ship_ai->SetThreat(threat);
- ship_ai->SetThreatMissile(threat_missile);
-}
-
-// +--------------------------------------------------------------------+
-
-void
-TacticalAI::FindSupport()
-{
- if (!ship_ai->GetThreat()) {
- ship_ai->SetSupport(0);
- return;
- }
-
- // pick the biggest friendly contact in the sector:
- Ship* support = 0;
- double support_dist = 1e9;
-
- ListIter<Contact> contact = ship->ContactList();
-
- while (++contact) {
- if (contact->GetShip() && contact->GetIFF(ship) == ship->GetIFF()) {
- Ship* c_ship = contact->GetShip();
-
- if (c_ship != ship && c_ship->Class() >= ship->Class() && !c_ship->InTransition()) {
- if (!support || c_ship->Class() > support->Class())
- support = c_ship;
- }
- }
- }
-
- ship_ai->SetSupport(support);
-}
-
-// +--------------------------------------------------------------------+
-
-void
-TacticalAI::FindFormationSlot(int formation)
-{
- // find the formation delta:
- int s = element_index - 1;
- Point delta(10*s, 0, 10*s);
-
- // diamond:
- if (formation == Instruction::DIAMOND) {
- switch (element_index) {
- case 2: delta = Point( 10, 0, -12); break;
- case 3: delta = Point(-10, 0, -12); break;
- case 4: delta = Point( 0, 0, -24); break;
- }
- }
-
- // spread:
- if (formation == Instruction::SPREAD) {
- switch (element_index) {
- case 2: delta = Point( 15, 0, 0); break;
- case 3: delta = Point(-15, 0, 0); break;
- case 4: delta = Point(-30, 0, 0); break;
- }
- }
-
- // box:
- if (formation == Instruction::BOX) {
- switch (element_index) {
- case 2: delta = Point(15, 0, 0); break;
- case 3: delta = Point( 0, -1, -15); break;
- case 4: delta = Point(15, -1, -15); break;
- }
- }
-
- // trail:
- if (formation == Instruction::TRAIL) {
- delta = Point(0, 0, -15*s);
- }
-
- ship_ai->SetFormationDelta(delta * ship->Radius() * 2);
-}
+/* 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: TacticalAI.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Generic Ship Tactical Level AI class +*/ + +#include "MemDebug.h" +#include "TacticalAI.h" +#include "ShipAI.h" +#include "CarrierAI.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Element.h" +#include "Instruction.h" +#include "RadioMessage.h" +#include "RadioTraffic.h" +#include "Contact.h" +#include "WeaponGroup.h" +#include "Drive.h" +#include "Hangar.h" +#include "Sim.h" +#include "Shot.h" +#include "Drone.h" +#include "StarSystem.h" + +#include "Game.h" +#include "Random.h" + +// +----------------------------------------------------------------------+ + +static int exec_time_seed = 0; + +// +----------------------------------------------------------------------+ + +TacticalAI::TacticalAI(ShipAI* ai) +: ship(0), ship_ai(0), carrier_ai(0), navpt(0), orders(0), +action(0), threat_level(0), support_level(1), +directed_tgtid(0) +{ + if (ai) { + ship_ai = ai; + ship = ai->GetShip(); + + Sim* sim = Sim::GetSim(); + + if (ship && ship->GetHangar() && ship->GetCommandAILevel() > 0 && + ship != sim->GetPlayerShip()) + carrier_ai = new(__FILE__,__LINE__) CarrierAI(ship, ship_ai->GetAILevel()); + } + + agression = 0; + roe = FLEXIBLE; + element_index = 1; + exec_time = exec_time_seed; + exec_time_seed += 17; +} + +TacticalAI::~TacticalAI() +{ + delete carrier_ai; +} + +// +--------------------------------------------------------------------+ + +void +TacticalAI::ExecFrame(double secs) +{ + const int exec_period = 1000; + + if (!ship || !ship_ai) + return; + + navpt = ship->GetNextNavPoint(); + orders = ship->GetRadioOrders(); + + if ((int) Game::GameTime() - exec_time > exec_period) { + element_index = ship->GetElementIndex(); + + CheckOrders(); + SelectTarget(); + FindThreat(); + FindSupport(); + + if (element_index > 1) { + int formation = 0; + + if (orders && orders->Formation() >= 0) + formation = orders->Formation(); + + else if (navpt) + formation = navpt->Formation(); + + FindFormationSlot(formation); + } + + ship_ai->SetNavPoint(navpt); + + if (carrier_ai) + carrier_ai->ExecFrame(secs); + + exec_time += exec_period; + } +} + +// +--------------------------------------------------------------------+ + +void +TacticalAI::CheckOrders() +{ + directed_tgtid = 0; + + if (CheckShipOrders()) + return; + + if (CheckFlightPlan()) + return; + + if (CheckObjectives()) + return; +} + +// +--------------------------------------------------------------------+ + +bool +TacticalAI::CheckShipOrders() +{ + return ProcessOrders(); +} + +// +--------------------------------------------------------------------+ + +bool +TacticalAI::CheckObjectives() +{ + bool processed = false; + Ship* ward = 0; + Element* elem = ship->GetElement(); + + if (elem) { + Instruction* obj = elem->GetTargetObjective(); + + if (obj) { + ship_ai->ClearPatrol(); + + if (obj->Action()) { + switch (obj->Action()) { + case Instruction::INTERCEPT: + case Instruction::STRIKE: + case Instruction::ASSAULT: + { + SimObject* tgt = obj->GetTarget(); + if (tgt && tgt->Type() == SimObject::SIM_SHIP) { + roe = DIRECTED; + SelectTargetDirected((Ship*) tgt); + } + } + break; + + case Instruction::DEFEND: + case Instruction::ESCORT: + { + SimObject* tgt = obj->GetTarget(); + if (tgt && tgt->Type() == SimObject::SIM_SHIP) { + roe = DEFENSIVE; + ward = (Ship*) tgt; + } + } + break; + + default: + break; + } + } + + orders = obj; + processed = true; + } + } + + ship_ai->SetWard(ward); + return processed; +} + +// +--------------------------------------------------------------------+ + +bool +TacticalAI::ProcessOrders() +{ + if (ship_ai) + ship_ai->ClearPatrol(); + + if (orders && orders->EMCON() > 0) { + int desired_emcon = orders->EMCON(); + + if (ship_ai && (ship_ai->GetThreat() || ship_ai->GetThreatMissile())) + desired_emcon = 3; + + if (ship->GetEMCON() != desired_emcon) + ship->SetEMCON(desired_emcon); + } + + if (orders && orders->Action()) { + switch (orders->Action()) { + case RadioMessage::ATTACK: + case RadioMessage::BRACKET: + case RadioMessage::IDENTIFY: + { + bool tgt_ok = false; + SimObject* tgt = orders->GetTarget(); + + if (tgt && tgt->Type() == SimObject::SIM_SHIP) { + Ship* tgt_ship = (Ship*) tgt; + + if (CanTarget(tgt_ship)) { + roe = DIRECTED; + SelectTargetDirected((Ship*) tgt); + + ship_ai->SetBracket(orders->Action() == RadioMessage::BRACKET); + ship_ai->SetIdentify(orders->Action() == RadioMessage::IDENTIFY); + ship_ai->SetNavPoint(0); + + tgt_ok = true; + } + } + + if (!tgt_ok) + ClearRadioOrders(); + } + break; + + case RadioMessage::ESCORT: + case RadioMessage::COVER_ME: + { + SimObject* tgt = orders->GetTarget(); + if (tgt && tgt->Type() == SimObject::SIM_SHIP) { + roe = DEFENSIVE; + ship_ai->SetWard((Ship*) tgt); + ship_ai->SetNavPoint(0); + } + else { + ClearRadioOrders(); + } + } + break; + + case RadioMessage::WEP_FREE: + roe = AGRESSIVE; + ship_ai->DropTarget(0.1); + break; + + case RadioMessage::WEP_HOLD: + case RadioMessage::FORM_UP: + roe = NONE; + ship_ai->DropTarget(5); + break; + + case RadioMessage::MOVE_PATROL: + roe = SELF_DEFENSIVE; + ship_ai->SetPatrol(orders->Location()); + ship_ai->SetNavPoint(0); + ship_ai->DropTarget(Random(5, 10)); + break; + + case RadioMessage::RTB: + case RadioMessage::DOCK_WITH: + roe = NONE; + + ship_ai->DropTarget(10); + + if (!ship->GetInbound()) { + RadioMessage* msg = 0; + Ship* controller = ship->GetController(); + + if (orders->Action() == RadioMessage::DOCK_WITH && orders->GetTarget()) { + controller = (Ship*) orders->GetTarget(); + } + + if (!controller) { + Element* elem = ship->GetElement(); + if (elem && elem->GetCommander()) { + Element* cmdr = elem->GetCommander(); + controller = cmdr->GetShip(1); + } + } + + if (controller && controller->GetHangar() && + controller->GetHangar()->CanStow(ship)) { + SimRegion* self_rgn = ship->GetRegion(); + SimRegion* rtb_rgn = controller->GetRegion(); + + if (self_rgn == rtb_rgn) { + double range = Point(controller->Location() - ship->Location()).length(); + + if (range < 50e3) { + msg = new(__FILE__,__LINE__) RadioMessage(controller, ship, RadioMessage::CALL_INBOUND); + RadioTraffic::Transmit(msg); + } + } + } + else { + ship->ClearRadioOrders(); + } + + ship_ai->SetNavPoint(0); + } + break; + + case RadioMessage::QUANTUM_TO: + case RadioMessage::FARCAST_TO: + roe = NONE; + ship_ai->DropTarget(10); + break; + + } + + action = orders->Action(); + return true; + } + + // if we had an action before, this must be a "cancel orders" + else if (action) { + ClearRadioOrders(); + } + + return false; +} + +void +TacticalAI::ClearRadioOrders() +{ + action = 0; + roe = FLEXIBLE; + + if (ship_ai) + ship_ai->DropTarget(0.1); + + if (ship) + ship->ClearRadioOrders(); + +} + +// +--------------------------------------------------------------------+ + +bool +TacticalAI::CheckFlightPlan() +{ + Ship* ward = 0; + + // Find next Instruction: + navpt = ship->GetNextNavPoint(); + + roe = FLEXIBLE; + + if (navpt) { + switch (navpt->Action()) { + case Instruction::LAUNCH: + case Instruction::DOCK: + case Instruction::RTB: roe = NONE; + break; + + case Instruction::VECTOR: roe = SELF_DEFENSIVE; + break; + + case Instruction::DEFEND: + case Instruction::ESCORT: roe = DEFENSIVE; + break; + + case Instruction::INTERCEPT: + roe = DIRECTED; + break; + + case Instruction::RECON: + case Instruction::STRIKE: + case Instruction::ASSAULT: roe = DIRECTED; + break; + + case Instruction::PATROL: + case Instruction::SWEEP: roe = FLEXIBLE; + break; + + default: break; + } + + if (roe == DEFENSIVE) { + SimObject* tgt = navpt->GetTarget(); + + if (tgt && tgt->Type() == SimObject::SIM_SHIP) + ward = (Ship*) tgt; + } + + + if (navpt->EMCON() > 0) { + int desired_emcon = navpt->EMCON(); + + if (ship_ai && (ship_ai->GetThreat() || ship_ai->GetThreatMissile())) + desired_emcon = 3; + + if (ship->GetEMCON() != desired_emcon) + ship->SetEMCON(desired_emcon); + } + } + + if (ship_ai) + ship_ai->SetWard(ward); + + return (navpt != 0); +} + +// +--------------------------------------------------------------------+ + +void +TacticalAI::SelectTarget() +{ + if (!ship) { + roe = NONE; + return; + } + + // unarmed vessels should never engage an enemy: + if (ship->Weapons().size() < 1) + roe = NONE; + + SimObject* target = ship_ai->GetTarget(); + SimObject* ward = ship_ai->GetWard(); + + // if not allowed to engage, drop and return: + if (roe == NONE) { + if (target) + ship_ai->DropTarget(); + return; + } + + // if we have abandoned our ward, drop and return: + if (ward && roe != AGRESSIVE) { + double d = (ward->Location() - ship->Location()).length(); + double safe_zone = 50e3; + + if (target) { + if (ship->IsStarship()) + safe_zone = 100e3; + + if (d > safe_zone) { + ship_ai->DropTarget(); + return; + } + } + else { + if (d > safe_zone) { + return; + } + } + } + + // already have a target, keep it: + if (target) { + if (target->Life()) { + CheckTarget(); + + // frigates need to be ready to abandon ship-type targets + // in favor of drone-type targets, others should just go + // with what they have: + if (ship->Class() != Ship::CORVETTE && ship->Class() != Ship::FRIGATE) + return; + + // in case the check decided to drop the target: + target = ship_ai->GetTarget(); + } + + // if the old target is dead, forget it: + else { + ship_ai->DropTarget(); + target = 0; + } + } + + // if not allowed to acquire, forget it: + if (ship_ai->DropTime() > 0) + return; + + if (roe == DIRECTED) { + if (target && target->Type() == SimObject::SIM_SHIP) + SelectTargetDirected((Ship*) target); + else if (navpt && navpt->GetTarget() && navpt->GetTarget()->Type() == SimObject::SIM_SHIP) + SelectTargetDirected((Ship*) navpt->GetTarget()); + else + SelectTargetDirected(); + } + + else { + SelectTargetOpportunity(); + + // don't switch one ship target for another... + if (ship->Class() == Ship::CORVETTE || ship->Class() == Ship::FRIGATE) { + SimObject* potential_target = ship_ai->GetTarget(); + if (target && potential_target && target != potential_target) { + if (target->Type() == SimObject::SIM_SHIP && + potential_target->Type() == SimObject::SIM_SHIP) { + + ship_ai->SetTarget(target); + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +TacticalAI::SelectTargetDirected(Ship* tgt) +{ + Ship* potential_target = tgt; + + // try to target one of the element's objectives + // (if it shows up in the contact list) + + if (!tgt) { + Element* elem = ship->GetElement(); + + if (elem) { + Instruction* objective = elem->GetTargetObjective(); + + if (objective) { + SimObject* obj_sim_obj = objective->GetTarget(); + Ship* obj_tgt = 0; + + if (obj_sim_obj && obj_sim_obj->Type() == SimObject::SIM_SHIP) + obj_tgt = (Ship*) obj_sim_obj; + + if (obj_tgt) { + ListIter<Contact> contact = ship->ContactList(); + while (++contact && !potential_target) { + Ship* test = contact->GetShip(); + + if (obj_tgt == test) { + potential_target = test; + } + } + } + } + } + } + + if (!CanTarget(potential_target)) + potential_target = 0; + + ship_ai->SetTarget(potential_target); + + if (tgt && tgt == ship_ai->GetTarget()) + directed_tgtid = tgt->Identity(); + else + directed_tgtid = 0; +} + +// +--------------------------------------------------------------------+ + +bool +TacticalAI::CanTarget(Ship* tgt) +{ + bool result = false; + + if (tgt && !tgt->InTransition()) { + if (tgt->IsRogue() || tgt->GetIFF() != ship->GetIFF()) + result = true; + } + + return result; +} + +// +--------------------------------------------------------------------+ + +void +TacticalAI::SelectTargetOpportunity() +{ + // NON-COMBATANTS do not pick targets of opportunity: + if (ship->GetIFF() == 0) + return; + + SimObject* potential_target = 0; + + // pick the closest combatant ship with a different IFF code: + double target_dist = ship->Design()->commit_range; + + SimObject* ward = ship_ai->GetWard(); + + // FRIGATES are primarily anti-air platforms, but may + // also attack smaller starships: + + if (ship->Class() == Ship::CORVETTE || ship->Class() == Ship::FRIGATE) { + Ship* current_ship_target = 0; + Shot* current_shot_target = 0; + + // if we are escorting a larger warship, it is good to attack + // the same target as our ward: + + if (ward) { + Ship* s = (Ship*) ward; + + if (s->Class() > ship->Class()) { + SimObject* obj = s->GetTarget(); + + if (obj && obj->Type() == SimObject::SIM_SHIP) { + current_ship_target = (Ship*) obj; + target_dist = (ship->Location() - obj->Location()).length(); + } + } + } + + ListIter<Contact> contact = ship->ContactList(); + while (++contact) { + Ship* c_ship = contact->GetShip(); + Shot* c_shot = contact->GetShot(); + + if (!c_ship && !c_shot) + continue; + + int c_iff = contact->GetIFF(ship); + bool rogue = c_ship && c_ship->IsRogue(); + bool tgt_ok = c_iff > 0 && + c_iff != ship->GetIFF() && + c_iff < 1000; + + if (rogue || tgt_ok) { + if (c_ship && c_ship != ship && !c_ship->InTransition()) { + if (c_ship->Class() < Ship::DESTROYER || + (c_ship->Class() >= Ship::MINE && c_ship->Class() <= Ship::DEFSAT)) { + // found an enemy, check distance: + double dist = (ship->Location() - c_ship->Location()).length(); + + if (dist < 0.75 * target_dist && + (!current_ship_target || c_ship->Class() <= current_ship_target->Class())) { + current_ship_target = c_ship; + target_dist = dist; + } + } + } + + else if (c_shot) { + // found an enemy shot, is there enough time to engage? + if (c_shot->GetEta() < 3) + continue; + + // found an enemy shot, check distance: + double dist = (ship->Location() - c_shot->Location()).length(); + + if (!current_shot_target) { + current_shot_target = c_shot; + target_dist = dist; + } + + // is this shot a better target than the one we've found? + else { + Ship* ward = ship_ai->GetWard(); + + if ((c_shot->IsTracking(ward) || c_shot->IsTracking(ship)) && + (!current_shot_target->IsTracking(ward) || + !current_shot_target->IsTracking(ship))) { + current_shot_target = c_shot; + target_dist = dist; + } + else if (dist < target_dist) { + current_shot_target = c_shot; + target_dist = dist; + } + } + } + } + } + + if (current_shot_target) + potential_target = current_shot_target; + else + potential_target = current_ship_target; + } + + // ALL OTHER SHIP CLASSES ignore fighters and only engage + // other starships: + + else { + List<Ship> ward_threats; + + ListIter<Contact> contact = ship->ContactList(); + while (++contact) { + Ship* c_ship = contact->GetShip(); + + if (!c_ship) + continue; + + int c_iff = contact->GetIFF(ship); + bool rogue = c_ship->IsRogue(); + bool tgt_ok = c_ship != ship && + c_iff > 0 && + c_iff != ship->GetIFF() && + !c_ship->InTransition(); + + if (rogue || tgt_ok) { + if (c_ship->IsStarship() || c_ship->IsStatic()) { + // found an enemy, check distance: + double dist = (ship->Location() - c_ship->Location()).length(); + + if (dist < 0.75 * target_dist) { + potential_target = c_ship; + target_dist = dist; + } + + if (ward && c_ship->IsTracking(ward)) { + ward_threats.append(c_ship); + } + } + } + } + + // if this ship is protecting a ward, + // prefer targets that are threatening that ward: + if (potential_target && ward_threats.size() && !ward_threats.contains((Ship*)potential_target)) { + target_dist *= 2; + + ListIter<Ship> iter = ward_threats; + while (++iter) { + Ship* threat = iter.value(); + + double dist = (ward->Location() - threat->Location()).length(); + + if (dist < target_dist) { + potential_target = threat; + target_dist = dist; + } + } + } + } + + if (ship->Class() != Ship::CARRIER && ship->Class() != Ship::SWACS) + ship_ai->SetTarget(potential_target); +} + +// +--------------------------------------------------------------------+ + +void +TacticalAI::CheckTarget() +{ + SimObject* tgt = ship_ai->GetTarget(); + + if (!tgt) return; + + if (tgt->GetRegion() != ship->GetRegion()) { + ship_ai->DropTarget(); + return; + } + + if (tgt->Type() == SimObject::SIM_SHIP) { + Ship* target = (Ship*) tgt; + + // has the target joined our side? + if (target->GetIFF() == ship->GetIFF() && !target->IsRogue()) { + ship_ai->DropTarget(); + return; + } + + // is the target already jumping/breaking/dying? + if (target->InTransition()) { + ship_ai->DropTarget(); + return; + } + + // have we been ordered to pursue the target? + if (directed_tgtid) { + if (directed_tgtid != target->Identity()) { + ship_ai->DropTarget(); + } + + return; + } + + // can we catch the target? + if (target->Design()->vlimit <= ship->Design()->vlimit || + ship->Velocity().length() <= ship->Design()->vlimit) + return; + + // is the target now out of range? + WeaponDesign* wep_dsn = ship->GetPrimaryDesign(); + if (!wep_dsn) + return; + + // compute the "give up" range: + double drop_range = 3 * wep_dsn->max_range; + if (drop_range > 0.75 * ship->Design()->commit_range) + drop_range = 0.75 * ship->Design()->commit_range; + + double range = Point(target->Location() - ship->Location()).length(); + if (range < drop_range) + return; + + // is the target closing or separating? + Point delta = (target->Location() + target->Velocity()) - + (ship->Location() + ship->Velocity()); + + if (delta.length() < range) + return; + + ship_ai->DropTarget(); + } + + else if (tgt->Type() == SimObject::SIM_DRONE) { + Drone* drone = (Drone*) tgt; + + // is the target still a threat? + if (drone->GetEta() < 1 || drone->GetTarget() == 0) + ship_ai->DropTarget(); + } +} + +// +--------------------------------------------------------------------+ + +void +TacticalAI::FindThreat() +{ + // pick the closest contact on Threat Warning System: + Ship* threat = 0; + Shot* threat_missile = 0; + Ship* rumor = 0; + double threat_dist = 1e9; + const DWORD THREAT_REACTION_TIME = 1000; // 1 second + + ListIter<Contact> iter = ship->ContactList(); + + while (++iter) { + Contact* contact = iter.value(); + + if (contact->Threat(ship) && + (Game::GameTime() - contact->AcquisitionTime()) > THREAT_REACTION_TIME) { + + if (contact->GetShot()) { + threat_missile = contact->GetShot(); + rumor = (Ship*) threat_missile->Owner(); + } + else { + double rng = contact->Range(ship); + + Ship* c_ship = contact->GetShip(); + if (c_ship && !c_ship->InTransition() && + c_ship->Class() != Ship::FREIGHTER && + c_ship->Class() != Ship::FARCASTER) { + + if (c_ship->GetTarget() == ship) { + if (!threat || c_ship->Class() > threat->Class()) { + threat = c_ship; + threat_dist = 0; + } + } + else if (rng < threat_dist) { + threat = c_ship; + threat_dist = rng; + } + } + } + } + } + + if (rumor && !rumor->InTransition()) { + iter.reset(); + + while (++iter) { + if (iter->GetShip() == rumor) { + rumor = 0; + ship_ai->ClearRumor(); + break; + } + } + } + else { + rumor = 0; + ship_ai->ClearRumor(); + } + + ship_ai->SetRumor(rumor); + ship_ai->SetThreat(threat); + ship_ai->SetThreatMissile(threat_missile); +} + +// +--------------------------------------------------------------------+ + +void +TacticalAI::FindSupport() +{ + if (!ship_ai->GetThreat()) { + ship_ai->SetSupport(0); + return; + } + + // pick the biggest friendly contact in the sector: + Ship* support = 0; + double support_dist = 1e9; + + ListIter<Contact> contact = ship->ContactList(); + + while (++contact) { + if (contact->GetShip() && contact->GetIFF(ship) == ship->GetIFF()) { + Ship* c_ship = contact->GetShip(); + + if (c_ship != ship && c_ship->Class() >= ship->Class() && !c_ship->InTransition()) { + if (!support || c_ship->Class() > support->Class()) + support = c_ship; + } + } + } + + ship_ai->SetSupport(support); +} + +// +--------------------------------------------------------------------+ + +void +TacticalAI::FindFormationSlot(int formation) +{ + // find the formation delta: + int s = element_index - 1; + Point delta(10*s, 0, 10*s); + + // diamond: + if (formation == Instruction::DIAMOND) { + switch (element_index) { + case 2: delta = Point( 10, 0, -12); break; + case 3: delta = Point(-10, 0, -12); break; + case 4: delta = Point( 0, 0, -24); break; + } + } + + // spread: + if (formation == Instruction::SPREAD) { + switch (element_index) { + case 2: delta = Point( 15, 0, 0); break; + case 3: delta = Point(-15, 0, 0); break; + case 4: delta = Point(-30, 0, 0); break; + } + } + + // box: + if (formation == Instruction::BOX) { + switch (element_index) { + case 2: delta = Point(15, 0, 0); break; + case 3: delta = Point( 0, -1, -15); break; + case 4: delta = Point(15, -1, -15); break; + } + } + + // trail: + if (formation == Instruction::TRAIL) { + delta = Point(0, 0, -15*s); + } + + ship_ai->SetFormationDelta(delta * ship->Radius() * 2); +} |