From e33e19d0587146859d48a134ec9fd94e7b7ba5cd Mon Sep 17 00:00:00 2001 From: "FWoltermann@gmail.com" Date: Thu, 8 Dec 2011 14:53:40 +0000 Subject: Initial upload --- Stars45/ShipAI.cpp | 1358 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1358 insertions(+) create mode 100644 Stars45/ShipAI.cpp (limited to 'Stars45/ShipAI.cpp') diff --git a/Stars45/ShipAI.cpp b/Stars45/ShipAI.cpp new file mode 100644 index 0000000..1118a8a --- /dev/null +++ b/Stars45/ShipAI.cpp @@ -0,0 +1,1358 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: ShipAI.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Starship (low-level) Artificial Intelligence class +*/ + +#include "MemDebug.h" +#include "ShipAI.h" +#include "TacticalAI.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Shot.h" +#include "Element.h" +#include "NavLight.h" +#include "Instruction.h" +#include "RadioMessage.h" +#include "RadioTraffic.h" +#include "Contact.h" +#include "WeaponGroup.h" +#include "Drive.h" +#include "Shield.h" +#include "Sim.h" +#include "Player.h" +#include "StarSystem.h" +#include "FlightComp.h" +#include "Farcaster.h" +#include "QuantumDrive.h" +#include "Debris.h" +#include "Asteroid.h" + +#include "Game.h" +#include "Random.h" + +// +----------------------------------------------------------------------+ + +ShipAI::ShipAI(SimObject* s) + : SteerAI(s), + support(0), rumor(0), threat(0), threat_missile(0), drop_time(0), + too_close(0), navpt(0), patrol(0), engaged_ship_id(0), + bracket(false), identify(false), hold(false), takeoff(false), + throttle(0), old_throttle(0), element_index(1), splash_count(0), + tactical(0), farcaster(0), ai_level(2), last_avoid_time(0), + last_call_time(0) +{ + ship = (Ship*) self; + + Sim* sim = Sim::GetSim(); + Ship* pship = sim->GetPlayerShip(); + int player_team = 1; + + if (pship) + player_team = pship->GetIFF(); + + Player* player = Player::GetCurrentPlayer(); + if (player) { + if (ship && ship->GetIFF() && ship->GetIFF() != player_team) { + ai_level = player->AILevel(); + } + else if (player->AILevel() == 0) { + ai_level = 1; + } + } + + // evil alien ships are *always* smart: + if (ship && ship->GetIFF() > 1 && ship->Design()->auto_roll > 1) { + ai_level = 2; + } +} + + +// +--------------------------------------------------------------------+ + +ShipAI::~ShipAI() +{ + delete tactical; +} + +void +ShipAI::ClearTactical() +{ + delete tactical; + tactical = 0; +} + +// +--------------------------------------------------------------------+ + +Ship* +ShipAI::GetWard() const +{ + return ship->GetWard(); +} + +void +ShipAI::SetWard(Ship* s) +{ + if (s == ship->GetWard()) + return; + + ship->SetWard(s); + + Point form = RandomDirection(); + form.SwapYZ(); + + if (fabs(form.x) < 0.5) { + if (form.x < 0) + form.x = -0.5; + else + form.x = 0.5; + } + + if (ship && ship->IsStarship()) { + form *= 30e3; + } + else { + form *= 15e3; + form.y = 500; + } + + SetFormationDelta(form); +} + +void +ShipAI::SetSupport(Ship* s) +{ + if (support == s) + return; + + support = s; + + if (support) + Observe(support); +} + +void +ShipAI::SetRumor(Ship* s) +{ + if (!s || rumor == s) + return; + + rumor = s; + + if (rumor) + Observe(rumor); +} + +void +ShipAI::ClearRumor() +{ + rumor = 0; +} + +void +ShipAI::SetThreat(Ship* s) +{ + if (threat == s) + return; + + threat = s; + + if (threat) + Observe(threat); +} + +void +ShipAI::SetThreatMissile(Shot* s) +{ + if (threat_missile == s) + return; + + threat_missile = s; + + if (threat_missile) + Observe(threat_missile); +} + +bool +ShipAI::Update(SimObject* obj) +{ + if (obj == support) + support = 0; + + if (obj == threat) + threat = 0; + + if (obj == threat_missile) + threat_missile = 0; + + if (obj == rumor) + rumor = 0; + + return SteerAI::Update(obj); +} + +const char* +ShipAI::GetObserverName() const +{ + static char name[64]; + sprintf(name, "ShipAI(%s)", self->Name()); + return name; +} + +// +--------------------------------------------------------------------+ + +Point +ShipAI::GetPatrol() const +{ + return patrol_loc; +} + +void +ShipAI::SetPatrol(const Point& p) +{ + patrol = 1; + patrol_loc = p; +} + +void +ShipAI::ClearPatrol() +{ + patrol = 0; +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::ExecFrame(double secs) +{ + seconds = secs; + + if (drop_time > 0) drop_time -= seconds; + if (!ship) return; + + ship->SetDirectorInfo(" "); + + // check to make sure current navpt is still valid: + if (navpt) + navpt = ship->GetNextNavPoint(); + + if (ship->GetFlightPhase() == Ship::TAKEOFF || ship->GetFlightPhase() == Ship::LAUNCH) + takeoff = true; + + if (takeoff) { + FindObjective(); + Navigator(); + + if (ship->MissionClock() > 10000) + takeoff = false; + + return; + } + + // initial assessment: + if (ship->MissionClock() < 5000) + return; + + element_index = ship->GetElementIndex(); + + NavlightControl(); + CheckTarget(); + + if (tactical) + tactical->ExecFrame(seconds); + + if (target && target != ship->GetTarget()) { + ship->LockTarget(target); + + // if able to lock target, and target is a ship (not a shot)... + if (target == ship->GetTarget() && target->Type() == SimObject::SIM_SHIP) { + + // if this isn't the same ship we last called out: + if (target->Identity() != engaged_ship_id && Game::GameTime() - last_call_time > 10000) { + // call engaging: + RadioMessage* msg = new(__FILE__,__LINE__) RadioMessage(ship->GetElement(), ship, RadioMessage::CALL_ENGAGING); + msg->AddTarget(target); + RadioTraffic::Transmit(msg); + last_call_time = Game::GameTime(); + + engaged_ship_id = target->Identity(); + } + } + } + + else if (!target) { + target = ship->GetTarget(); + + if (engaged_ship_id && !target) { + engaged_ship_id = 0; + + /*** + *** XXX + *** Not the right place to make this decision. + *** + *** There is a brief wait between killing a target and + *** selecting a new one, so this message is sent after + *** every kill. + *** + *** Need to track when the entire element has been + *** put down. + + if (element_index == 1) { + RadioMessage* msg = new(__FILE__,__LINE__) RadioMessage(ship->GetElement(), ship, RadioMessage::RESUME_MISSION); + RadioTraffic::Transmit(msg); + } + + *** + ***/ + } + } + + FindObjective(); + Navigator(); +} + +// +--------------------------------------------------------------------+ + +Point +ShipAI::ClosingVelocity() +{ + if (ship && target) { + if (ship->GetPrimaryDesign()) { + WeaponDesign* guns = ship->GetPrimaryDesign(); + Point delta = target->Location() - ship->Location(); + + // fighters need to aim the ship so that the guns will hit the target + if (guns->firing_cone < 10*DEGREES && guns->max_range <= delta.length()) { + Point aim_vec = ship->Heading(); + aim_vec.Normalize(); + + Point shot_vel = ship->Velocity() + aim_vec * guns->speed; + return shot_vel - target->Velocity(); + } + + // ships with turreted weapons just need to worry about actual closing speed + else { + return ship->Velocity() - target->Velocity(); + } + } + + else { + return ship->Velocity(); + } + } + + return Point(1,0,0); +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::FindObjective() +{ + distance = 0; + + int order = ship->GetRadioOrders()->Action(); + + if (order == RadioMessage::QUANTUM_TO || + order == RadioMessage::FARCAST_TO) { + + FindObjectiveQuantum(); + objective = Transform(obj_w); + return; + } + + bool form = (order == RadioMessage::WEP_HOLD) || + (order == RadioMessage::FORM_UP) || + (order == RadioMessage::MOVE_PATROL) || + (order == RadioMessage::RTB) || + (order == RadioMessage::DOCK_WITH) || + (!order && !target) || + (farcaster); + + Ship* ward = ship->GetWard(); + + // 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; + if (tactical) + directed = (tactical->RulesOfEngagement() == TacticalAI::DIRECTED); + + // threat processing: + if (threat && !directed) { + double d_threat = Point(threat->Location() - ship->Location()).length(); + + // 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 != target) { + ship->SetDirectorInfo(Game::GetText("ai.retreat")); + obj_w = ship->Location() + Point(ship->Location() - threat->Location()) * 100; + objective = Transform(obj_w); + return; + } + } + + // normal processing: + if (target) { + ship->SetDirectorInfo(Game::GetText("ai.seek-target")); + FindObjectiveTarget(target); + objective = AimTransform(obj_w); + } + + else if (patrol) { + ship->SetDirectorInfo(Game::GetText("ai.patrol")); + FindObjectivePatrol(); + objective = Transform(obj_w); + } + + else if (ward) { + ship->SetDirectorInfo(Game::GetText("ai.seek-ward")); + FindObjectiveFormation(); + objective = Transform(obj_w); + } + + else if (navpt && form) { + ship->SetDirectorInfo(Game::GetText("ai.seek-navpt")); + FindObjectiveNavPoint(); + objective = Transform(obj_w); + } + + else if (rumor) { + ship->SetDirectorInfo(Game::GetText("ai.search")); + FindObjectiveTarget(rumor); + objective = Transform(obj_w); + } + + else { + obj_w = Point(); + objective = Point(); + } +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::FindObjectiveTarget(SimObject* tgt) +{ + if (!tgt) { + obj_w = Point(); + return; + } + + navpt = 0; // this tells fire control that we are chasing a target, + // instead of a navpoint! + + Point cv = ClosingVelocity(); + double cvl = cv.length(); + double time = 0; + + if (cvl > 50) { + // distance from self to target: + distance = Point(tgt->Location() - self->Location()).length(); + + // time to reach target: + time = distance / cvl; + + // where the target will be when we reach it: + if (time < 15) { + Point run_vec = tgt->Velocity(); + obj_w = tgt->Location() + (run_vec * time); + + + if (time < 10) + obj_w += (tgt->Acceleration() * 0.33 * time * time); + } + else { + obj_w = tgt->Location(); + } + } + + else { + obj_w = tgt->Location(); + } + + distance = Point(obj_w - self->Location()).length(); + + if (cvl > 50) { + time = distance / cvl; + + // where we will be when the target gets there: + if (time < 15) { + Point self_dest = self->Location() + cv * time; + Point err = obj_w - self_dest; + + obj_w += err; + } + } + + Point approach = obj_w - self->Location(); + distance = approach.length(); + + if (bracket && distance > 25e3) { + Point offset = approach.cross(Point(0,1,0)); + offset.Normalize(); + offset *= 15e3; + + Ship* s = (Ship*) self; + if (s->GetElementIndex() & 1) + obj_w -= offset; + else + obj_w += offset; + } +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::FindObjectivePatrol() +{ + navpt = 0; + + Point npt = patrol_loc; + obj_w = npt; + + // distance from self to navpt: + distance = Point(obj_w - self->Location()).length(); + + if (distance < 1000) { + ship->ClearRadioOrders(); + ClearPatrol(); + } +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::FindObjectiveNavPoint() +{ + SimRegion* self_rgn = ship->GetRegion(); + SimRegion* nav_rgn = navpt->Region(); + QuantumDrive* qdrive = ship->GetQuantumDrive(); + + if (!self_rgn) + return; + + if (!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) { + FindObjectiveFarcaster(self_rgn, nav_rgn); + } + + else { + if (farcaster) { + if (farcaster->GetShip()->GetRegion() != self_rgn) + farcaster = farcaster->GetDest()->GetFarcaster(); + + obj_w = farcaster->EndPoint(); + } + + else { + // transform from starsystem to world coordinates: + Point npt = navpt->Region()->Location() + navpt->Location(); + + SimRegion* active_region = ship->GetRegion(); + + if (active_region) + npt -= active_region->Location(); + + npt = npt.OtherHand(); + + obj_w = npt; + } + + // distance from self to navpt: + distance = Point(obj_w - ship->Location()).length(); + + if (farcaster && distance < 1000) + farcaster = 0; + + if (distance < 1000 || (navpt->Action() == Instruction::LAUNCH && distance > 25000)) + ship->SetNavptStatus(navpt, Instruction::COMPLETE); + } +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::FindObjectiveQuantum() +{ + Instruction* orders = ship->GetRadioOrders(); + SimRegion* self_rgn = ship->GetRegion(); + SimRegion* nav_rgn = orders->Region(); + QuantumDrive* qdrive = ship->GetQuantumDrive(); + + if (!self_rgn || !nav_rgn) + return; + + bool use_farcaster = self_rgn != nav_rgn && + (orders->Farcast() || + !qdrive || + !qdrive->IsPowerOn() || + qdrive->Status() < System::DEGRADED + ); + + if (use_farcaster) { + FindObjectiveFarcaster(self_rgn, nav_rgn); + } + + else { + if (farcaster) { + if (farcaster->GetShip()->GetRegion() != self_rgn) + farcaster = farcaster->GetDest()->GetFarcaster(); + + obj_w = farcaster->EndPoint(); + } + + else { + // transform from starsystem to world coordinates: + Point npt = orders->Region()->Location() + orders->Location(); + + SimRegion* active_region = ship->GetRegion(); + + if (active_region) + npt -= active_region->Location(); + + npt = npt.OtherHand(); + + obj_w = npt; + + if (qdrive && qdrive->ActiveState() == QuantumDrive::ACTIVE_READY) { + qdrive->SetDestination(nav_rgn, orders->Location()); + qdrive->Engage(); + return; + } + } + + // distance from self to navpt: + distance = Point(obj_w - ship->Location()).length(); + + if (farcaster) { + if (distance < 1000) { + farcaster = 0; + ship->ClearRadioOrders(); + } + } + else if (self_rgn == nav_rgn) { + ship->ClearRadioOrders(); + } + } +} + +void +ShipAI::FindObjectiveFarcaster(SimRegion* src_rgn, SimRegion* dst_rgn) +{ + if (!farcaster) { + ListIter s = src_rgn->Ships(); + while (++s && !farcaster) { + if (s->GetFarcaster()) { + const Ship* dest = s->GetFarcaster()->GetDest(); + if (dest && dest->GetRegion() == dst_rgn) { + farcaster = s->GetFarcaster(); + } + } + } + } + + if (farcaster) { + Point apt = farcaster->ApproachPoint(0); + Point npt = farcaster->StartPoint(); + double r1 = (ship->Location() - npt).length(); + + if (r1 > 50e3) { + obj_w = apt; + distance = r1; + } + + else { + double r2 = (ship->Location() - apt).length(); + double r3 = (npt - apt).length(); + + if (r1+r2 < 1.2*r3) { + obj_w = npt; + distance = r1; + } + else { + obj_w = apt; + distance = r2; + } + } + + objective = Transform(obj_w); + } +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::SetFormationDelta(const Point& point) +{ + formation_delta = point; +} + +void +ShipAI::FindObjectiveFormation() +{ + const double prediction = 5; + + // find the base position: + Element* elem = ship->GetElement(); + Ship* lead = elem->GetShip(1); + Ship* ward = ship->GetWard(); + + if (!lead || lead == ship) { + lead = ward; + + distance = (lead->Location() - self->Location()).length(); + if (distance < 30e3 && lead->Velocity().length() < 50) { + obj_w = self->Location() + lead->Heading() * 1e6; + distance = -1; + return; + } + } + + obj_w = lead->Location() + lead->Velocity() * prediction; + Matrix m; m.Rotate(0, 0, lead->CompassHeading() - PI); + Point fd = formation_delta * m; + obj_w += fd; + + // try to avoid smacking into the ground... + if (ship->IsAirborne()) { + if (ship->AltitudeAGL() < 3000 || lead->AltitudeAGL() < 3000) { + obj_w.y += 500; + } + } + + Point dst_w = self->Location() + self->Velocity() * prediction; + Point dlt_w = obj_w - dst_w; + + distance = dlt_w.length(); + + // get slot z distance: + dlt_w += ship->Location(); + slot_dist = Transform(dlt_w).z; + + Director* lead_dir = lead->GetDirector(); + if (lead_dir && (lead_dir->Type() == FIGHTER || lead_dir->Type() == STARSHIP)) { + ShipAI* lead_ai = (ShipAI*) lead_dir; + farcaster = lead_ai->GetFarcaster(); + } + else { + Instruction* navpt = elem->GetNextNavPoint(); + if (!navpt) { + farcaster = 0; + return; + } + + 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) { + 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(); + } + } + } + } + else 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; + } + } +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::Splash(const Ship* targ) +{ + if (splash_count > 6) + splash_count = 4; + + // call splash: + RadioTraffic::SendQuickMessage(ship, RadioMessage::SPLASH_1 + splash_count); + splash_count++; +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::SetTarget(SimObject* targ, System* sub) +{ + if (targ != target) { + bracket = false; + } + + SteerAI::SetTarget(targ, sub); +} + +void +ShipAI::DropTarget(double dtime) +{ + SetTarget(0); + drop_time = dtime; // seconds until we can re-acquire + + ship->DropTarget(); +} + +void +ShipAI::SetBracket(bool b) +{ + bracket = b; + identify = false; +} + +void +ShipAI::SetIdentify(bool i) +{ + identify = i; + bracket = false; +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::Navigator() +{ + 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 (target) + ship->SetDirectorInfo(Game::GetText("ai.seek-target")); + else if (rumor) + ship->SetDirectorInfo(Game::GetText("ai.seek-rumor")); + else + ship->SetDirectorInfo(Game::GetText("ai.none")); + + Accumulate(AvoidCollision()); + Accumulate(AvoidTerrain()); + + if (!hold) + Accumulate(SeekTarget()); + + HelmControl(); + ThrottleControl(); + FireControl(); + AdjustDefenses(); +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::HelmControl() +{ + double trans_x = 0; + double trans_y = 0; + double trans_z = 0; + + ship->SetHelmHeading(accumulator.yaw); + + if (fabs(accumulator.pitch) < 5*DEGREES || fabs(accumulator.pitch) > 45*DEGREES) { + trans_z = objective.y; + ship->SetHelmPitch(0); + } + + else { + ship->SetHelmPitch(accumulator.pitch); + } + + ship->SetTransX(trans_x); + ship->SetTransY(trans_y); + ship->SetTransZ(trans_z); + + ship->ExecFLCSFrame(); +} + +/***************************************** + ** + ** NOTE: + ** No one is really using this method. + ** It is overridden by both StarshipAI + ** and FighterAI. + ** + *****************************************/ + +void +ShipAI::ThrottleControl() +{ + if (navpt && !threat && !target) { // lead only, get speed from navpt + double speed = navpt->Speed(); + + if (speed > 0) + throttle = speed / ship->VelocityLimit() * 100; + else + throttle = 50; + } + + else if (patrol && !threat && !target) { // lead only, get speed from navpt + double speed = 200; + + if (distance > 5000) + speed = 500; + + if (ship->Velocity().length() > speed) + throttle = 0; + else + throttle = 50; + } + + else { + if (threat || target || element_index < 2) { // element lead + throttle = 100; + + if (!threat && !target) + throttle = 50; + + if (accumulator.brake > 0) { + throttle *= (1 - accumulator.brake); + } + } + + else { // wingman + Ship* lead = ship->GetElement()->GetShip(1); + double lv = lead->Velocity().length(); + double sv = ship->Velocity().length(); + 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; + } + } + + old_throttle = throttle; + ship->SetThrottle((int) throttle); +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::NavlightControl() +{ + Ship* leader = ship->GetLeader(); + + if (leader && leader != ship) { + bool navlight_enabled = false; + + if (leader->NavLights().size() > 0) + navlight_enabled = leader->NavLights().at(0)->IsEnabled(); + + for (int i = 0; i < ship->NavLights().size(); i++) { + if (navlight_enabled) + ship->NavLights().at(i)->Enable(); + else + ship->NavLights().at(i)->Disable(); + } + } +} + +// +--------------------------------------------------------------------+ + +Steer +ShipAI::AvoidTerrain() +{ + Steer avoid; + return avoid; +} + +// +--------------------------------------------------------------------+ + +Steer +ShipAI::AvoidCollision() +{ + Steer avoid; + + if (!ship || !ship->GetRegion() || !ship->GetRegion()->IsActive()) + return avoid; + + if (other && (other->Life() == 0 || other->Integrity() < 1)) { + other = 0; + last_avoid_time = 0; // check for a new obstacle immediately + } + + if (!other && Game::GameTime() - last_avoid_time < 500) + return avoid; + + brake = 0; + + // don't get closer than this: + double avoid_dist = 5 * self->Radius(); + + if (avoid_dist < 1e3) avoid_dist = 1e3; + else if (avoid_dist > 12e3) avoid_dist = 12e3; + + // find the soonest potential collision, + // ignore any that occur after this: + double avoid_time = 15; + + if (ship->Design()->avoid_time > 0) + avoid_time = ship->Design()->avoid_time; + else if (ship->IsStarship()) + avoid_time *= 1.5; + + Point bearing = self->Velocity(); + bearing.Normalize(); + + bool found = false; + int num_contacts = ship->NumContacts(); + ListIter contact = ship->ContactList(); + + // check current obstacle first: + if (other) { + found = AvoidTestSingleObject(other, bearing, avoid_dist, avoid_time, avoid); + } + + if (!found) { + // avoid ships: + while (++contact && !found) { + Ship* c_ship = contact->GetShip(); + + if (c_ship && c_ship != ship && c_ship->IsStarship()) { + found = AvoidTestSingleObject(c_ship, bearing, avoid_dist, avoid_time, avoid); + } + } + + // also avoid large pieces of debris: + if (!found) { + ListIter iter = ship->GetRegion()->Rocks(); + while (++iter && !found) { + Debris* debris = iter.value(); + + if (debris->Mass() > ship->Mass()) + found = AvoidTestSingleObject(debris, bearing, avoid_dist, avoid_time, avoid); + } + } + + // and asteroids: + if (!found) { + // give asteroids a wider berth - + avoid_dist *= 8; + + ListIter iter = ship->GetRegion()->Roids(); + while (++iter && !found) { + Asteroid* roid = iter.value(); + found = AvoidTestSingleObject(roid, bearing, avoid_dist, avoid_time, avoid); + } + + if (!found) + avoid_dist /= 8; + } + + // if found, steer to avoid: + if (other) { + avoid = Avoid(obstacle, (float) (ship->Radius() + other->Radius() + avoid_dist * 0.9)); + avoid.brake = brake; + + ship->SetDirectorInfo(Game::GetText("ai.avoid-collision")); + } + } + + last_avoid_time = Game::GameTime(); + return avoid; +} + +bool +ShipAI::AvoidTestSingleObject(SimObject* obj, + const Point& bearing, + double avoid_dist, + double& avoid_time, + Steer& avoid) +{ + if (too_close == obj->Identity()) { + double dist = (ship->Location() - obj->Location()).length(); + double closure = (ship->Velocity() - obj->Velocity()) * bearing; + + if (closure > 1 && dist < avoid_dist) { + avoid = AvoidCloseObject(obj); + return true; + } + else { + too_close = 0; + } + } + + // will we get close? + double time = ClosestApproachTime(ship->Location(), ship->Velocity(), + obj->Location(), obj->Velocity()); + + // already past the obstacle: + if (time <= 0) { + if (other == obj) other = 0; + return false; + } + + // how quickly could we collide? + Point current_relation = ship->Location() - obj->Location(); + double current_distance = current_relation.length() - ship->Radius() - obj->Radius(); + + // are we really far away? + if (current_distance > 25e3) { + if (other == obj) other = 0; + return false; + } + + // is the obstacle a farcaster? + if (obj->Type() == SimObject::SIM_SHIP) { + Ship* c_ship = (Ship*) obj; + + if (c_ship->GetFarcaster()) { + // are we on a safe vector? + Point dir = ship->Velocity(); + dir.Normalize(); + + double angle_off = fabs(acos(dir * obj->Cam().vpn())); + + if (angle_off > 90*DEGREES) + angle_off = 180*DEGREES - angle_off; + + if (angle_off < 35*DEGREES) { + // will we pass through the center? + Point d = ship->Location() + dir * (current_distance + ship->Radius() + obj->Radius()); + double err = (obj->Location() - d).length(); + + if (err < 0.667 * obj->Radius()) { + return false; + } + } + } + } + + // rate of closure: + double closing_velocity = (ship->Velocity() - obj->Velocity()) * bearing; + + // are we too close already? + if (current_distance < (avoid_dist * 0.35)) { + if (closing_velocity > 1 || current_distance < ship->Radius()) { + avoid = AvoidCloseObject(obj); + return true; + } + } + + // too far away to worry about: + double separation = (avoid_dist + obj->Radius()); + if ((current_distance-separation) / closing_velocity > avoid_time) { + if (other == obj) other = 0; + return false; + } + + // where will we be? + Point selfpt = ship->Location() + ship->Velocity() * time; + Point testpt = obj->Location() + obj->Velocity() * time; + + // how close will we get? + double dist = (selfpt - testpt).length() + - ship->Radius() + - obj->Radius(); + + // that's too close: + if (dist < avoid_dist) { + if (dist < avoid_dist * 0.25 && time < avoid_time * 0.5) { + avoid = AvoidCloseObject(obj); + return true; + } + + obstacle = Transform(testpt); + + if (obstacle.z > 0) { + other = obj; + avoid_time = time; + brake = 0.5; + + Observe(other); + } + } + + // hysteresis: + else if (other == obj && dist > avoid_dist * 1.25) { + other = 0; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +Steer +ShipAI::AvoidCloseObject(SimObject* obj) +{ + too_close = obj->Identity(); + obstacle = Transform(obj->Location()); + other = obj; + + Observe(other); + + Steer avoid = Flee(obstacle); + avoid.brake = 0.3; + + ship->SetDirectorInfo(Game::GetText("ai.avoid-collision")); + return avoid; +} + +// +--------------------------------------------------------------------+ + +Steer +ShipAI::SeekTarget() +{ + Ship* ward = ship->GetWard(); + + if (!target && !ward && !navpt && !patrol) { + if (element_index > 1) { + // wingmen keep in formation: + return Seek(objective); + } + + if (farcaster) { + return Seek(objective); + } + + if (rumor) { + return Seek(objective); + } + + return Steer(); + } + + if (patrol) { + Steer result = Seek(objective); + + if (distance < 2000) { + result.brake = 1; + } + + return result; + } + + if (target && too_close == target->Identity()) { + drop_time = 4; + return Avoid(objective, 0.0f); + } + else if (drop_time > 0) { + return Steer(); + } + + return Seek(objective); +} + +// +--------------------------------------------------------------------+ + +Steer +ShipAI::EvadeThreat() +{ + return Steer(); +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::FireControl() +{ +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::AdjustDefenses() +{ + Shield* shield = ship->GetShield(); + + if (shield) { + double desire = 50; + + if (threat_missile || threat) + desire = 100; + + shield->SetPowerLevel(desire); + } +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::CheckTarget() +{ + if (target) { + if (target->Life() == 0) + target = 0; + + else if (target->Type() == SimObject::SIM_SHIP) { + Ship* tgt_ship = (Ship*) target; + + if (tgt_ship->GetIFF() == ship->GetIFF() && !tgt_ship->IsRogue()) + target = 0; + } + } +} -- cgit v1.1