MapPanel::MapPanel(PlayerInfo &player, int commodity, const System *special) : player(player), distance(player), playerSystem(player.GetSystem()), selectedSystem(special ? special : player.GetSystem()), specialSystem(special), commodity(commodity) { SetIsFullScreen(true); SetInterruptible(false); if(selectedSystem) center = Point(0., 0.) - Zoom() * (selectedSystem->Position()); }
// Constructor. BoardingPanel::BoardingPanel(PlayerInfo &player, const shared_ptr<Ship> &victim) : player(player), you(player.FlagshipPtr()), victim(victim), attackOdds(*you, *victim), defenseOdds(*victim, *you), initialCrew(you->Crew()) { // The escape key should close this panel rather than bringing up the main menu. SetInterruptible(false); // Figure out how much the victim's commodities are worth in the current // system and add them to the list of plunder. const System &system = *player.GetSystem(); for(const auto &it : victim->Cargo().Commodities()) plunder.emplace_back(it.first, it.second, system.Trade(it.first)); // You cannot plunder hand to hand weapons, because they are kept in the // crew's quarters, not mounted on the exterior of the ship. Certain other // outfits are also unplunderable, like mass expansions. for(const auto &it : victim->Outfits()) if(!it.first->Get("unplunderable")) plunder.emplace_back(it.first, it.second); // Some "ships" do not represent something the player could actually pilot. if(!victim->IsCapturable()) messages.emplace_back("This is not a ship that you can capture."); // Sort the plunder by price per ton. sort(plunder.begin(), plunder.end()); }
HailPanel::HailPanel(PlayerInfo &player, const StellarObject *object) : player(player), planet(object->GetPlanet()), sprite(object->GetSprite().GetSprite()), unit(object->Position().Unit()) { SetInterruptible(false); const Government *gov = player.GetSystem()->GetGovernment(); if(planet) header = gov->GetName() + " " + planet->Noun() + " \"" + planet->Name() + "\":"; if(planet && player.Flagship()) { for(const Mission &mission : player.Missions()) if(mission.HasClearance(planet) && mission.ClearanceMessage() != "auto" && mission.HasFullClearance()) { planet->Bribe(); message = mission.ClearanceMessage(); return; } if(planet->CanLand()) message = "You are cleared to land, " + player.Flagship()->Name() + "."; else { SetBribe(planet->GetBribeFraction()); if(bribe) message = "If you want to land here, it'll cost you " + Format::Number(bribe) + " credits."; else message = "I'm afraid we can't permit you to land here."; } } }
// Constructor. BoardingPanel::BoardingPanel(PlayerInfo &player, const shared_ptr<Ship> &victim) : player(player), you(player.FlagshipPtr()), victim(victim), attackOdds(*you, *victim), defenseOdds(*victim, *you) { // The escape key should close this panel rather than bringing up the main menu. SetInterruptible(false); // Figure out how much the victim's commodities are worth in the current // system and add them to the list of plunder. const System &system = *player.GetSystem(); for(const auto &it : victim->Cargo().Commodities()) if(it.second) plunder.emplace_back(it.first, it.second, system.Trade(it.first)); // You cannot plunder hand to hand weapons, because they are kept in the // crew's quarters, not mounted on the exterior of the ship. Certain other // outfits are also unplunderable, like mass expansions. auto sit = victim->Outfits().begin(); auto cit = victim->Cargo().Outfits().begin(); while(sit != victim->Outfits().end() || cit != victim->Cargo().Outfits().end()) { const Outfit *outfit = nullptr; int count = 0; // Merge the outfit lists from the ship itself and its cargo bay. If an // outfit exists in both locations, combine the counts. bool shipIsFirst = (cit == victim->Cargo().Outfits().end() || (sit != victim->Outfits().end() && sit->first <= cit->first)); bool cargoIsFirst = (sit == victim->Outfits().end() || (cit != victim->Cargo().Outfits().end() && cit->first <= sit->first)); if(shipIsFirst) { outfit = sit->first; // Don't include outfits that are installed and unplunderable. But, // "unplunderable" outfits can still be stolen from cargo. if(!sit->first->Get("unplunderable")) count += sit->second; ++sit; } if(cargoIsFirst) { outfit = cit->first; count += cit->second; ++cit; } if(outfit && count) plunder.emplace_back(outfit, count); } // Some "ships" do not represent something the player could actually pilot. if(!victim->IsCapturable()) messages.emplace_back("This is not a ship that you can capture."); // Sort the plunder by price per ton. sort(plunder.begin(), plunder.end()); }
Engine::Engine(PlayerInfo &player) : player(player), calcTickTock(false), drawTickTock(false), terminate(false), step(0), flash(0.), doFlash(false), wasLeavingHyperspace(false), load(0.), loadCount(0), loadSum(0.) { // Start the thread for doing calculations. calcThread = thread(&Engine::ThreadEntryPoint, this); if(!player.IsLoaded() || !player.GetSystem()) return; // Preload any landscapes for this system. for(const StellarObject &object : player.GetSystem()->Objects()) if(object.GetPlanet()) GameData::Preload(object.GetPlanet()->Landscape()); // Now we know the player's current position. Draw the planets. Point center; if(player.GetPlanet()) { for(const StellarObject &object : player.GetSystem()->Objects()) if(object.GetPlanet() == player.GetPlanet()) center = object.Position(); } for(const StellarObject &object : player.GetSystem()->Objects()) if(!object.GetSprite().IsEmpty()) { Point position = object.Position(); Point unit = object.Unit(); position -= center; int type = object.IsStar() ? Radar::SPECIAL : !object.GetPlanet() ? Radar::INACTIVE : object.GetPlanet()->IsWormhole() ? Radar::ANOMALOUS : GameData::GetPolitics().HasDominated(object.GetPlanet()) ? Radar::PLAYER : object.GetPlanet()->CanLand() ? Radar::FRIENDLY : Radar::HOSTILE; double r = max(2., object.Radius() * .03 + .5); draw[calcTickTock].Add(object.GetSprite(), position, unit); radar[calcTickTock].Add(type, position, r, r - 1.); } // Add all neighboring systems to the radar. const Ship *flagship = player.Flagship(); const System *targetSystem = flagship ? flagship->GetTargetSystem() : nullptr; const vector<const System *> &links = (flagship && flagship->Attributes().Get("jump drive")) ? player.GetSystem()->Neighbors() : player.GetSystem()->Links(); for(const System *system : links) radar[calcTickTock].AddPointer( (system == targetSystem) ? Radar::SPECIAL : Radar::INACTIVE, system->Position() - player.GetSystem()->Position()); }
bool Mission::CanComplete(const PlayerInfo &player) const { if(player.GetPlanet() != destination || !waypoints.empty()) return false; if(!toComplete.Test(player.Conditions())) return false; auto it = actions.find(COMPLETE); if(it != actions.end() && !it->second.CanBeDone(player)) return false; for(const NPC &npc : npcs) if(!npc.HasSucceeded(player.GetSystem())) return false; // If any of the cargo for this mission is being carried by a ship that is // not in this system, the mission cannot be completed right now. for(const auto &ship : player.Ships()) if(ship->GetSystem() != player.GetSystem() && ship->Cargo().Get(this)) return false; return true; }
BoardingPanel::BoardingPanel(PlayerInfo &player, const shared_ptr<Ship> &victim) : player(player), you(player.FlagshipPtr()), victim(victim), attackOdds(&*you, &*victim), defenseOdds(&*victim, &*you), initialCrew(you->Crew()) { SetInterruptible(false); const System &system = *player.GetSystem(); for(const auto &it : victim->Cargo().Commodities()) plunder.emplace_back(it.first, it.second, system.Trade(it.first)); // You cannot plunder hand to hand weapons, because they are kept in the // crew's quarters, not mounted on the exterior of the ship. for(const auto &it : victim->Outfits()) if(it.first->Category() != "Hand to Hand") plunder.emplace_back(it.first, it.second); sort(plunder.begin(), plunder.end()); }
PlanetPanel::PlanetPanel(PlayerInfo &player, function<void()> callback) : player(player), callback(callback), planet(*player.GetPlanet()), system(*player.GetSystem()), ui(*GameData::Interfaces().Get("planet")) { trading.reset(new TradingPanel(player)); bank.reset(new BankPanel(player)); spaceport.reset(new SpaceportPanel(player)); hiring.reset(new HiringPanel(player)); text.SetFont(FontSet::Get(14)); text.SetAlignment(WrappedText::JUSTIFIED); text.SetWrapWidth(480); text.Wrap(planet.Description()); // Since the loading of landscape images is deferred, make sure that the // landscapes for this system are loaded before showing the planet panel. GameData::Preload(planet.Landscape()); GameData::FinishLoading(); }
Engine::Engine(PlayerInfo &player) : player(player) { // Start the thread for doing calculations. calcThread = thread(&Engine::ThreadEntryPoint, this); if(!player.IsLoaded() || !player.GetSystem()) return; // Preload any landscapes for this system. for(const StellarObject &object : player.GetSystem()->Objects()) if(object.GetPlanet()) GameData::Preload(object.GetPlanet()->Landscape()); // Figure out what planet the player is landed on, if any. const StellarObject *object = player.GetStellarObject(); if(object) center = object->Position(); // Now we know the player's current position. Draw the planets. draw[calcTickTock].SetCenter(center); radar[calcTickTock].SetCenter(center); for(const StellarObject &object : player.GetSystem()->Objects()) if(object.HasSprite()) { draw[calcTickTock].Add(object); double r = max(2., object.Radius() * .03 + .5); radar[calcTickTock].Add(RadarType(object), object.Position(), r, r - 1.); } // Add all neighboring systems to the radar. const Ship *flagship = player.Flagship(); const System *targetSystem = flagship ? flagship->GetTargetSystem() : nullptr; const vector<const System *> &links = (flagship && flagship->Attributes().Get("jump drive")) ? player.GetSystem()->Neighbors() : player.GetSystem()->Links(); for(const System *system : links) radar[calcTickTock].AddPointer( (system == targetSystem) ? Radar::SPECIAL : Radar::INACTIVE, system->Position() - player.GetSystem()->Position()); }
// "Instantiate" a mission by replacing randomly selected values and places // with a single choice, and then replacing any wildcard text as well. Mission Mission::Instantiate(const PlayerInfo &player) const { Mission result; // If anything goes wrong below, this mission should not be offered. result.hasFailed = true; result.isVisible = isVisible; result.hasPriority = hasPriority; result.isMinor = isMinor; result.autosave = autosave; result.location = location; result.repeat = repeat; result.name = name; result.waypoints = waypoints; // If one of the waypoints is the current system, it is already visited. auto it = result.waypoints.find(player.GetSystem()); if(it != result.waypoints.end()) result.waypoints.erase(it); // First, pick values for all the variables. // If a specific destination is not specified in the mission, pick a random // one out of all the destinations that satisfy the mission requirements. result.destination = destination; if(!result.destination && !destinationFilter.IsEmpty()) { // Find a destination that satisfies the filter. vector<const Planet *> options; for(const auto &it : GameData::Planets()) { // Skip entries with incomplete data. if(it.second.Name().empty() || (clearance.empty() && !it.second.CanLand())) continue; if(it.second.IsWormhole() || !it.second.HasSpaceport()) continue; if(destinationFilter.Matches(&it.second, player.GetSystem())) options.push_back(&it.second); } if(!options.empty()) result.destination = options[Random::Int(options.size())]; else return result; } // If no destination is specified, it is the same as the source planet. Also // use the source planet if the given destination is not a valid planet name. if(!result.destination || !result.destination->GetSystem()) { if(player.GetPlanet()) result.destination = player.GetPlanet(); else return result; } // If cargo is being carried, see if we are supposed to replace a generic // cargo name with something more specific. if(!cargo.empty()) { const Trade::Commodity *commodity = nullptr; if(cargo == "random") commodity = PickCommodity(*player.GetSystem(), *result.destination->GetSystem()); else { for(const Trade::Commodity &option : GameData::Commodities()) if(option.name == cargo) { commodity = &option; break; } } if(commodity) result.cargo = commodity->items[Random::Int(commodity->items.size())]; else result.cargo = cargo; } // Pick a random cargo amount, if requested. if(cargoSize || cargoLimit) { if(cargoProb) result.cargoSize = Random::Polya(cargoLimit, cargoProb) + cargoSize; else if(cargoLimit > cargoSize) result.cargoSize = cargoSize + Random::Int(cargoLimit - cargoSize + 1); else result.cargoSize = cargoSize; } // Pick a random passenger count, if requested. if(passengers | passengerLimit) { if(passengerProb) result.passengers = Random::Polya(passengerLimit, passengerProb) + passengers; else if(passengerLimit > passengers) result.passengers = passengers + Random::Int(passengerLimit - passengers + 1); else result.passengers = passengers; } result.illegalCargoFine = illegalCargoFine; // How far is it to the destination? DistanceMap distance(player.GetSystem()); int jumps = distance.Distance(result.destination->GetSystem()); int defaultPayment = (jumps + 1) * (150 * result.cargoSize + 1500 * result.passengers); int defaultDeadline = doDefaultDeadline ? (2 * jumps) : 0; // Set the deadline, if requested. if(daysToDeadline || defaultDeadline) { result.hasDeadline = true; result.deadline = player.GetDate() + (defaultDeadline + daysToDeadline); } // Copy the completion conditions. No need to copy the offer conditions, // because they have already been checked. result.toComplete = toComplete; result.toFail = toFail; // Generate the substitutions map. map<string, string> subs; subs["<commodity>"] = result.cargo; subs["<tons>"] = to_string(result.cargoSize) + (result.cargoSize == 1 ? " ton" : " tons"); subs["<cargo>"] = subs["<tons>"] + " of " + subs["<commodity>"]; subs["<bunks>"] = to_string(result.passengers); subs["<passengers>"] = (result.passengers == 1) ? "passenger" : "passengers"; subs["<fare>"] = (result.passengers == 1) ? "a passenger" : (subs["<bunks>"] + " passengers"); if(player.GetPlanet()) subs["<origin>"] = player.GetPlanet()->Name(); else if(player.BoardingShip()) subs["<origin>"] = player.BoardingShip()->Name(); subs["<planet>"] = result.destination ? result.destination->Name() : ""; subs["<system>"] = result.destination ? result.destination->GetSystem()->Name() : ""; subs["<destination>"] = subs["<planet>"] + " in the " + subs["<system>"] + " system"; subs["<date>"] = result.deadline.ToString(); subs["<day>"] = result.deadline.LongString(); // Instantiate the NPCs. This also fills in the "<npc>" substitution. for(const NPC &npc : npcs) result.npcs.push_back(npc.Instantiate(subs, player.GetSystem())); // Instantiate the actions. The "complete" action is always first so that // the "<payment>" substitution can be filled in. for(const auto &it : actions) result.actions[it.first] = it.second.Instantiate(subs, defaultPayment); for(const auto &it : onEnter) result.onEnter[it.first] = it.second.Instantiate(subs, defaultPayment); // Perform substitution in the name and description. result.displayName = Format::Replace(displayName, subs); result.description = Format::Replace(description, subs); result.clearance = Format::Replace(clearance, subs); result.blocked = Format::Replace(blocked, subs); result.clearanceFilter = clearanceFilter; result.hasFullClearance = hasFullClearance; result.hasFailed = false; return result; }
// "Instantiate" a mission by replacing randomly selected values and places // with a single choice, and then replacing any wildcard text as well. Mission Mission::Instantiate(const PlayerInfo &player) const { Mission result; // If anything goes wrong below, this mission should not be offered. result.hasFailed = true; result.isVisible = isVisible; result.hasPriority = hasPriority; result.isMinor = isMinor; result.autosave = autosave; result.location = location; result.repeat = repeat; result.name = name; result.waypoints = waypoints; // If one of the waypoints is the current system, it is already visited. auto it = result.waypoints.find(player.GetSystem()); if(it != result.waypoints.end()) result.waypoints.erase(it); // Copy the stopover planet list, and populate the list based on the filters // that were given. result.stopovers = stopovers; // Make sure they all exist in a valid system. for(auto it = result.stopovers.begin(); it != result.stopovers.end(); ) { if((*it)->GetSystem()) ++it; else it = result.stopovers.erase(it); } for(const LocationFilter &filter : stopoverFilters) { const Planet *planet = PickPlanet(filter, player); if(!planet) return result; result.stopovers.insert(planet); } // First, pick values for all the variables. // If a specific destination is not specified in the mission, pick a random // one out of all the destinations that satisfy the mission requirements. result.destination = destination; if(!result.destination && !destinationFilter.IsEmpty()) { result.destination = PickPlanet(destinationFilter, player); if(!result.destination) return result; } // If no destination is specified, it is the same as the source planet. Also // use the source planet if the given destination is not a valid planet name. if(!result.destination || !result.destination->GetSystem()) { if(player.GetPlanet()) result.destination = player.GetPlanet(); else return result; } // If cargo is being carried, see if we are supposed to replace a generic // cargo name with something more specific. if(!cargo.empty()) { const Trade::Commodity *commodity = nullptr; if(cargo == "random") commodity = PickCommodity(*player.GetSystem(), *result.destination->GetSystem()); else { for(const Trade::Commodity &option : GameData::Commodities()) if(option.name == cargo) { commodity = &option; break; } for(const Trade::Commodity &option : GameData::SpecialCommodities()) if(option.name == cargo) { commodity = &option; break; } } if(commodity) result.cargo = commodity->items[Random::Int(commodity->items.size())]; else result.cargo = cargo; } // Pick a random cargo amount, if requested. if(cargoSize || cargoLimit) { if(cargoProb) result.cargoSize = Random::Polya(cargoLimit, cargoProb) + cargoSize; else if(cargoLimit > cargoSize) result.cargoSize = cargoSize + Random::Int(cargoLimit - cargoSize + 1); else result.cargoSize = cargoSize; } // Pick a random passenger count, if requested. if(passengers | passengerLimit) { if(passengerProb) result.passengers = Random::Polya(passengerLimit, passengerProb) + passengers; else if(passengerLimit > passengers) result.passengers = passengers + Random::Int(passengerLimit - passengers + 1); else result.passengers = passengers; } result.illegalCargoFine = illegalCargoFine; // How far is it to the destination? DistanceMap distance(player.GetSystem()); int jumps = distance.Distance(result.destination->GetSystem()); int payload = result.cargoSize + 10 * result.passengers; // Set the deadline, if requested. if(deadlineBase || deadlineMultiplier) result.deadline = player.GetDate() + deadlineBase + deadlineMultiplier * jumps; // Copy the conditions. The offer conditions must be copied too, because they // may depend on a condition that other mission offers might change. result.toOffer = toOffer; result.toComplete = toComplete; result.toFail = toFail; // Generate the substitutions map. map<string, string> subs; subs["<commodity>"] = result.cargo; subs["<tons>"] = to_string(result.cargoSize) + (result.cargoSize == 1 ? " ton" : " tons"); subs["<cargo>"] = subs["<tons>"] + " of " + subs["<commodity>"]; subs["<bunks>"] = to_string(result.passengers); subs["<passengers>"] = (result.passengers == 1) ? "passenger" : "passengers"; subs["<fare>"] = (result.passengers == 1) ? "a passenger" : (subs["<bunks>"] + " passengers"); if(player.GetPlanet()) subs["<origin>"] = player.GetPlanet()->Name(); else if(player.BoardingShip()) subs["<origin>"] = player.BoardingShip()->Name(); subs["<planet>"] = result.destination ? result.destination->Name() : ""; subs["<system>"] = result.destination ? result.destination->GetSystem()->Name() : ""; subs["<destination>"] = subs["<planet>"] + " in the " + subs["<system>"] + " system"; subs["<date>"] = result.deadline.ToString(); subs["<day>"] = result.deadline.LongString(); if(!result.stopovers.empty()) { string planets; const Planet * const *last = &*--result.stopovers.end(); int count = 0; // Iterate by reference to the pointers so we can check when we're at // the very last one in the set. for(const Planet * const &planet : result.stopovers) { if(count++) planets += (&planet != last) ? ", " : (count > 2 ? ", and " : " and "); planets += planet->Name() + " in the " + planet->GetSystem()->Name() + " system"; } subs["<stopovers>"] = planets; } // Instantiate the NPCs. This also fills in the "<npc>" substitution. for(const NPC &npc : npcs) result.npcs.push_back(npc.Instantiate(subs, player.GetSystem())); // Instantiate the actions. The "complete" action is always first so that // the "<payment>" substitution can be filled in. for(const auto &it : actions) result.actions[it.first] = it.second.Instantiate(subs, jumps, payload); for(const auto &it : onEnter) result.onEnter[it.first] = it.second.Instantiate(subs, jumps, payload); // Perform substitution in the name and description. result.displayName = Format::Replace(displayName, subs); result.description = Format::Replace(description, subs); result.clearance = Format::Replace(clearance, subs); result.blocked = Format::Replace(blocked, subs); result.clearanceFilter = clearanceFilter; result.hasFullClearance = hasFullClearance; result.hasFailed = false; return result; }
// When the state of this mission changes, it may make changes to the player // information or show new UI panels. PlayerInfo::MissionCallback() will be // used as the callback for any UI panel that returns a value. bool Mission::Do(Trigger trigger, PlayerInfo &player, UI *ui) { if(trigger == STOPOVER) { // If this is not one of this mission's stopover planets, or if it is // not the very last one that must be visited, do nothing. auto it = stopovers.find(player.GetPlanet()); if(it == stopovers.end()) return false; for(const NPC &npc : npcs) if(npc.IsLeftBehind(player.GetSystem())) { ui->Push(new Dialog("This is a stop for one of your missions, but you have left a ship behind.")); return false; } stopovers.erase(it); if(!stopovers.empty()) return false; } if(trigger == ACCEPT) { ++player.Conditions()[name + ": offered"]; ++player.Conditions()[name + ": active"]; } else if(trigger == DECLINE) ++player.Conditions()[name + ": offered"]; else if(trigger == FAIL) --player.Conditions()[name + ": active"]; else if(trigger == COMPLETE) { --player.Conditions()[name + ": active"]; ++player.Conditions()[name + ": done"]; } // "Jobs" should never show dialogs when offered, nor should they call the // player's mission callback. if(trigger == OFFER && location == JOB) ui = nullptr; auto it = actions.find(trigger); if(it == actions.end()) { // If a mission has no "on offer" field, it is automatically accepted. if(trigger == OFFER && location != JOB) player.MissionCallback(Conversation::ACCEPT); return true; } if(!it->second.CanBeDone(player)) return false; // Set the "reputation" conditions so we can check if this action changed // any of them. for(const auto &it : GameData::Governments()) { int rep = it.second.Reputation(); player.Conditions()["reputation: " + it.first] = rep; } it->second.Do(player, ui, destination ? destination->GetSystem() : nullptr); // Check if any reputation conditions were updated. for(const auto &it : GameData::Governments()) { int rep = it.second.Reputation(); int newRep = player.Conditions()["reputation: " + it.first]; if(newRep != rep) it.second.AddReputation(newRep - rep); } return true; }
// Check to see if the player has done anything they should be fined for. string Politics::Fine(PlayerInfo &player, const Government *gov, int scan, const Ship *target, double security) { // Do nothing if you have already been fined today, or if you evade // detection. auto it = fined.find(gov); if(it != fined.end() || Random::Real() > security || !gov->GetFineFraction()) return ""; string reason; int64_t maxFine = 0; for(const shared_ptr<Ship> &ship : player.Ships()) { // Check if the ship evades being scanned due to interference plating. if(Random::Real() > 1. / (1. + ship->Attributes().Get("scan interference"))) continue; if(target && target != &*ship) continue; if(ship->GetSystem() != player.GetSystem()) continue; if(!scan || (scan & ShipEvent::SCAN_CARGO)) { int64_t fine = ship->Cargo().IllegalCargoFine(); if((fine > maxFine && maxFine >= 0) || fine < 0) { maxFine = fine; reason = "carrying illegal cargo."; } } if(!scan || (scan & ShipEvent::SCAN_OUTFITS)) { for(const auto &it : ship->Outfits()) if(it.second) { int64_t fine = it.first->Get("illegal"); if((fine > maxFine && maxFine >= 0) || fine < 0) { maxFine = fine; reason = "having illegal outfits installed on your ship."; } } } } if(maxFine < 0) { gov->Offend(ShipEvent::ATROCITY); if(!scan) reason = "atrocity"; else reason = "After scanning your ship, the " + gov->GetName() + " captain hails you with a grim expression on his face. He says, \"You are guilty of " + reason + " The penalty for your actions is death. Goodbye.\""; } else if(maxFine > 0) { // Scale the fine based on how lenient this government is. maxFine = maxFine * gov->GetFineFraction() + .5; reason = "The " + gov->GetName() + " fines you " + Format::Number(maxFine) + " credits for " + reason; player.Accounts().AddFine(maxFine); fined.insert(gov); } return reason; }
void AI::Step(const list<shared_ptr<Ship>> &ships, const PlayerInfo &player) { const Ship *flagship = player.Flagship(); step = (step + 1) & 31; int targetTurn = 0; for(const auto &it : ships) { // Skip any carried fighters or drones that are somehow in the list. if(!it->GetSystem()) continue; if(it.get() == flagship) { MovePlayer(*it, player, ships); continue; } bool isPresent = (it->GetSystem() == player.GetSystem()); bool isStranded = !it->JumpsRemaining() && it->Attributes().Get("fuel capacity") && !it->GetSystem()->IsInhabited(); if(isStranded || it->IsDisabled()) { if(it->IsDestroyed() || (it->IsDisabled() && it->IsYours()) || it->GetPersonality().IsDerelict()) continue; bool hasEnemy = false; Ship *firstAlly = nullptr; bool selectNext = false; Ship *nextAlly = nullptr; const Government *gov = it->GetGovernment(); for(const auto &ship : ships) { if(ship->IsDisabled() || !ship->IsTargetable() || ship->GetSystem() != it->GetSystem()) continue; const Government *otherGov = ship->GetGovernment(); // If any enemies of this ship are in system, it cannot call for help. if(otherGov->IsEnemy(gov) && isPresent) { hasEnemy = true; break; } if((otherGov->IsPlayer() && !gov->IsPlayer()) || ship.get() == flagship) continue; if(it->IsDisabled() ? (otherGov == gov) : (!otherGov->IsEnemy(gov))) { if(isStranded && !ship->CanRefuel(*it)) continue; if(!firstAlly) firstAlly = &*ship; else if(ship == it) selectNext = true; else if(selectNext && !nextAlly) nextAlly = &*ship; } } isStranded = false; if(!hasEnemy) { if(!nextAlly) nextAlly = firstAlly; if(nextAlly) { nextAlly->SetShipToAssist(it); isStranded = true; } } if(it->IsDisabled()) continue; } Command command; if(it->IsYours()) { if(isLaunching) command |= Command::DEPLOY; if(isCloaking) command |= Command::CLOAK; } const Personality &personality = it->GetPersonality(); shared_ptr<Ship> parent = it->GetParent(); if(isPresent && personality.IsSurveillance()) { DoSurveillance(*it, command, ships); it->SetCommands(command); continue; } // Fire any weapons that will hit the target. Only ships that are in // the current system can fire. shared_ptr<const Ship> target = it->GetTargetShip(); if(isPresent) { command |= AutoFire(*it, ships); // Each ship only switches targets twice a second, so that it can // focus on damaging one particular ship. targetTurn = (targetTurn + 1) & 31; if(targetTurn == step || !target || !target->IsTargetable() || (target->IsDisabled() && personality.Disables())) it->SetTargetShip(FindTarget(*it, ships)); } double targetDistance = numeric_limits<double>::infinity(); target = it->GetTargetShip(); if(target) targetDistance = target->Position().Distance(it->Position()); // Handle fighters: const string &category = it->Attributes().Category(); bool isDrone = (category == "Drone"); bool isFighter = (category == "Fighter"); if(isDrone || isFighter) { bool hasSpace = true; hasSpace &= parent && (!isDrone || parent->DroneBaysFree()); hasSpace &= parent && (!isFighter || parent->FighterBaysFree()); if(!hasSpace || parent->IsDestroyed() || parent->GetSystem() != it->GetSystem()) { // Handle orphaned fighters and drones. for(const auto &other : ships) if(other->GetGovernment() == it->GetGovernment() && !other->IsDisabled() && other->GetSystem() == it->GetSystem()) if((isDrone && other->DroneBaysFree()) || (isFighter && other->FighterBaysFree())) { it->SetParent(other); break; } } else if(parent && !(it->IsYours() ? isLaunching : parent->Commands().Has(Command::DEPLOY))) { it->SetTargetShip(parent); MoveTo(*it, command, parent->Position(), 40., .8); command |= Command::BOARD; it->SetCommands(command); continue; } } bool mustRecall = false; if(it->HasBays() && !(it->IsYours() ? isLaunching : it->Commands().Has(Command::DEPLOY)) && !target) for(const weak_ptr<const Ship> &ptr : it->GetEscorts()) { shared_ptr<const Ship> escort = ptr.lock(); if(escort && escort->CanBeCarried() && escort->GetSystem() == it->GetSystem() && !escort->IsDisabled()) { mustRecall = true; break; } } shared_ptr<Ship> shipToAssist = it->GetShipToAssist(); if(shipToAssist) { it->SetTargetShip(shipToAssist); if(shipToAssist->IsDestroyed() || shipToAssist->GetSystem() != it->GetSystem()) it->SetShipToAssist(shared_ptr<Ship>()); else if(!it->IsBoarding()) { MoveTo(*it, command, shipToAssist->Position(), 40., .8); command |= Command::BOARD; } it->SetCommands(command); continue; } bool isPlayerEscort = it->IsYours(); if((isPlayerEscort && holdPosition) || mustRecall || isStranded) { if(it->Velocity().Length() > .2 || !target) Stop(*it, command); else command.SetTurn(TurnToward(*it, TargetAim(*it))); } // Hostile "escorts" (i.e. NPCs that are trailing you) only revert to // escort behavior when in a different system from you. Otherwise, // the behavior depends on what the parent is doing, whether there // are hostile targets nearby, and whether the escort has any // immediate needs (like refueling). else if(!parent || parent->IsDestroyed() || (parent->IsDisabled() && !isPlayerEscort)) MoveIndependent(*it, command); else if(parent->GetSystem() != it->GetSystem()) { if(personality.IsStaying()) MoveIndependent(*it, command); else MoveEscort(*it, command); } // From here down, we're only dealing with ships that have a "parent" // which is in the same system as them. If you're an enemy of your // "parent," you don't take orders from them. else if(personality.IsStaying() || parent->GetGovernment()->IsEnemy(it->GetGovernment())) MoveIndependent(*it, command); // This is a friendly escort. If the parent is getting ready to // jump, always follow. else if(parent->Commands().Has(Command::JUMP)) MoveEscort(*it, command); // If the player is ordering escorts to gather, don't go off to fight. else if(isPlayerEscort && moveToMe) MoveEscort(*it, command); // On the other hand, if the player ordered you to attack, do so even // if you're usually more timid than that. else if(isPlayerEscort && sharedTarget.lock()) MoveIndependent(*it, command); // Timid ships always stay near their parent. else if(personality.IsTimid() && parent->Position().Distance(it->Position()) > 500.) MoveEscort(*it, command); // Otherwise, attack targets depending on how heroic you are. else if(target && (targetDistance < 2000. || personality.IsHeroic())) MoveIndependent(*it, command); // This ship does not feel like fighting. else MoveEscort(*it, command); // Apply the afterburner if you're in a heated battle and it will not // use up your last jump worth of fuel. if(it->Attributes().Get("afterburner thrust") && target && !target->IsDisabled() && target->IsTargetable() && target->GetSystem() == it->GetSystem()) { double fuel = it->Fuel() * it->Attributes().Get("fuel capacity"); if(fuel - it->Attributes().Get("afterburner fuel") >= it->JumpFuel()) if(command.Has(Command::FORWARD) && targetDistance < 1000.) command |= Command::AFTERBURNER; } // Your own ships cloak on your command; all others do it when the // AI considers it appropriate. if(!it->IsYours()) DoCloak(*it, command, ships); // Force ships that are overlapping each other to "scatter": DoScatter(*it, command, ships); it->SetCommands(command); } }
TradingPanel::TradingPanel(PlayerInfo &player) : player(player), system(*player.GetSystem()), COMMODITY_COUNT(GameData::Commodities().size()) { SetTrapAllEvents(false); }