1 /* Fleet.cpp
2 Copyright (c) 2014 by Michael Zahniser
3 
4 Endless Sky is free software: you can redistribute it and/or modify it under the
5 terms of the GNU General Public License as published by the Free Software
6 Foundation, either version 3 of the License, or (at your option) any later version.
7 
8 Endless Sky is distributed in the hope that it will be useful, but WITHOUT ANY
9 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
10 PARTICULAR PURPOSE.  See the GNU General Public License for more details.
11 */
12 
13 #include "Fleet.h"
14 
15 #include "DataNode.h"
16 #include "Files.h"
17 #include "GameData.h"
18 #include "Government.h"
19 #include "Phrase.h"
20 #include "pi.h"
21 #include "Planet.h"
22 #include "Random.h"
23 #include "Ship.h"
24 #include "StellarObject.h"
25 #include "System.h"
26 
27 #include <algorithm>
28 #include <cmath>
29 #include <iterator>
30 
31 using namespace std;
32 
33 namespace {
34 	// Generate an offset magnitude that will sample from an annulus (planets)
35 	// or a circle (systems without inhabited planets).
OffsetFrom(pair<Point,double> & center)36 	double OffsetFrom(pair<Point, double> &center)
37 	{
38 		// If the center has a radius, then position ships further away.
39 		double minimumOffset = center.second ? 1. : 0.;
40 		// Since it is sensible that ships would be nearer to the object of
41 		// interest on average, do not apply the sqrt(rand) correction.
42 		return (Random::Real() + minimumOffset) * 400. + 2. * center.second;
43 	}
44 
45 	// Construct a list of all outfits for sale in this system and its linked neighbors.
GetOutfitsForSale(const System * here)46 	Sale<Outfit> GetOutfitsForSale(const System *here)
47 	{
48 		auto outfits = Sale<Outfit>();
49 		if(here)
50 		{
51 			for(const StellarObject &object : here->Objects())
52 			{
53 				const Planet *planet = object.GetPlanet();
54 				if(planet && planet->IsValid() && planet->HasOutfitter())
55 					outfits.Add(planet->Outfitter());
56 			}
57 		}
58 		return outfits;
59 	}
60 
61 	// Construct a list of varying numbers of outfits that were either specified for
62 	// this fleet directly, or are sold in this system or its linked neighbors.
OutfitChoices(const set<const Sale<Outfit> * > & outfitters,const System * hub,int maxSize)63 	vector<const Outfit *> OutfitChoices(const set<const Sale<Outfit> *> &outfitters, const System *hub, int maxSize)
64 	{
65 		auto outfits = vector<const Outfit *>();
66 		if(maxSize > 0)
67 		{
68 			auto choices = Sale<Outfit>();
69 			// If no outfits were directly specified, choose from those sold nearby.
70 			if(outfitters.empty() && hub)
71 			{
72 				choices = GetOutfitsForSale(hub);
73 				for(const System *other : hub->Links())
74 					choices.Add(GetOutfitsForSale(other));
75 			}
76 			else
77 				for(const auto outfitter : outfitters)
78 					choices.Add(*outfitter);
79 
80 			if(!choices.empty())
81 			{
82 				for(const auto outfit : choices)
83 				{
84 					double mass = outfit->Mass();
85 					// Avoid free outfits, massless outfits, and those too large to fit.
86 					if(mass > 0. && mass < maxSize && outfit->Cost() > 0)
87 					{
88 						// Also avoid outfits that add space (such as Outfits / Cargo Expansions)
89 						// or modify bunks.
90 						// TODO: Specify rejection criteria in datafiles as ConditionSets or similar.
91 						const auto &attributes = outfit->Attributes();
92 						if(attributes.Get("outfit space") > 0.
93 								|| attributes.Get("cargo space") > 0.
94 								|| attributes.Get("bunks"))
95 							continue;
96 
97 						outfits.push_back(outfit);
98 					}
99 				}
100 			}
101 		}
102 		// Sort this list of choices ascending by mass, so it can be easily trimmed to just
103 		// the outfits that fit as the ship's free space decreases.
104 		sort(outfits.begin(), outfits.end(), [](const Outfit *a, const Outfit *b)
105 			{ return a->Mass() < b->Mass(); });
106 		return outfits;
107 	}
108 
109 	// Add a random commodity from the list to the ship's cargo.
AddRandomCommodity(Ship & ship,int freeSpace,const vector<string> & commodities)110 	void AddRandomCommodity(Ship &ship, int freeSpace, const vector<string> &commodities)
111 	{
112 		int index = Random::Int(GameData::Commodities().size());
113 		if(!commodities.empty())
114 		{
115 			// If a list of possible commodities was given, pick one of them at
116 			// random and then double-check that it's a valid commodity name.
117 			const string &name = commodities[Random::Int(commodities.size())];
118 			for(const auto &it : GameData::Commodities())
119 				if(it.name == name)
120 				{
121 					index = &it - &GameData::Commodities().front();
122 					break;
123 				}
124 		}
125 
126 		const Trade::Commodity &commodity = GameData::Commodities()[index];
127 		int amount = Random::Int(freeSpace) + 1;
128 		ship.Cargo().Add(commodity.name, amount);
129 	}
130 
131 	// Add a random outfit from the list to the ship's cargo.
AddRandomOutfit(Ship & ship,int freeSpace,const vector<const Outfit * > & outfits)132 	void AddRandomOutfit(Ship &ship, int freeSpace, const vector<const Outfit *> &outfits)
133 	{
134 		if(outfits.empty())
135 			return;
136 		int index = Random::Int(outfits.size());
137 		const Outfit *picked = outfits[index];
138 		int maxQuantity = floor(static_cast<double>(freeSpace) / picked->Mass());
139 		int amount = Random::Int(maxQuantity) + 1;
140 		ship.Cargo().Add(picked, amount);
141 	}
142 }
143 
144 
145 
146 // Construct and Load() at the same time.
Fleet(const DataNode & node)147 Fleet::Fleet(const DataNode &node)
148 {
149 	Load(node);
150 }
151 
152 
153 
Load(const DataNode & node)154 void Fleet::Load(const DataNode &node)
155 {
156 	if(node.Size() >= 2)
157 		fleetName = node.Token(1);
158 
159 	// If Load() has already been called once on this fleet, any subsequent
160 	// calls will replace the variants instead of adding to them.
161 	bool resetVariants = !variants.empty();
162 
163 	for(const DataNode &child : node)
164 	{
165 		// The "add" and "remove" keywords should never be alone on a line, and
166 		// are only valid with "variant" or "personality" definitions.
167 		bool add = (child.Token(0) == "add");
168 		bool remove = (child.Token(0) == "remove");
169 		bool hasValue = (child.Size() >= 2);
170 		if((add || remove) && (!hasValue || (child.Token(1) != "variant" && child.Token(1) != "personality")))
171 		{
172 			child.PrintTrace("Skipping invalid \"" + child.Token(0) + "\" tag:");
173 			continue;
174 		}
175 
176 		// If this line is an add or remove, the key is the token at index 1.
177 		const string &key = child.Token(add || remove);
178 
179 		if(key == "government" && hasValue)
180 			government = GameData::Governments().Get(child.Token(1));
181 		else if(key == "names" && hasValue)
182 			names = GameData::Phrases().Get(child.Token(1));
183 		else if(key == "fighters" && hasValue)
184 			fighterNames = GameData::Phrases().Get(child.Token(1));
185 		else if(key == "cargo" && hasValue)
186 			cargo = static_cast<int>(child.Value(1));
187 		else if(key == "commodities" && hasValue)
188 		{
189 			commodities.clear();
190 			for(int i = 1; i < child.Size(); ++i)
191 				commodities.push_back(child.Token(i));
192 		}
193 		else if(key == "outfitters" && hasValue)
194 		{
195 			outfitters.clear();
196 			for(int i = 1; i < child.Size(); ++i)
197 				outfitters.insert(GameData::Outfitters().Get(child.Token(i)));
198 		}
199 		else if(key == "personality")
200 			personality.Load(child);
201 		else if(key == "variant" && !remove)
202 		{
203 			if(resetVariants && !add)
204 			{
205 				resetVariants = false;
206 				variants.clear();
207 				total = 0;
208 			}
209 			variants.emplace_back(child);
210 			total += variants.back().weight;
211 		}
212 		else if(key == "variant")
213 		{
214 			// If given a full ship definition of one of this fleet's variant members, remove the variant.
215 			bool didRemove = false;
216 			Variant toRemove(child);
217 			for(auto it = variants.begin(); it != variants.end(); ++it)
218 				if(toRemove.ships.size() == it->ships.size() &&
219 					is_permutation(it->ships.begin(), it->ships.end(), toRemove.ships.begin()))
220 				{
221 					total -= it->weight;
222 					variants.erase(it);
223 					didRemove = true;
224 					break;
225 				}
226 
227 			if(!didRemove)
228 				child.PrintTrace("Did not find matching variant for specified operation:");
229 		}
230 		else
231 			child.PrintTrace("Skipping unrecognized attribute:");
232 	}
233 
234 	if(variants.empty())
235 		node.PrintTrace("Warning: " + (fleetName.empty() ? "unnamed fleet" : "Fleet \"" + fleetName + "\"") + " contains no variants:");
236 }
237 
238 
239 
IsValid(bool requireGovernment) const240 bool Fleet::IsValid(bool requireGovernment) const
241 {
242 	// Generally, a government is required for a fleet to be valid.
243 	if(requireGovernment && !government)
244 		return false;
245 
246 	if(names && names->IsEmpty())
247 		return false;
248 
249 	if(fighterNames && fighterNames->IsEmpty())
250 		return false;
251 
252 	// A fleet's variants should reference at least one valid ship.
253 	for(auto &&v : variants)
254 		if(none_of(v.ships.begin(), v.ships.end(),
255 				[](const Ship *const s) noexcept -> bool { return s->IsValid(); }))
256 			return false;
257 
258 	return true;
259 }
260 
261 
262 
RemoveInvalidVariants()263 void Fleet::RemoveInvalidVariants()
264 {
265 	auto IsInvalidVariant = [](const Variant &v) noexcept -> bool
266 	{
267 		return v.ships.empty() || none_of(v.ships.begin(), v.ships.end(),
268 			[](const Ship *const s) noexcept -> bool { return s->IsValid(); });
269 	};
270 	auto firstInvalid = find_if(variants.begin(), variants.end(), IsInvalidVariant);
271 	if(firstInvalid == variants.end())
272 		return;
273 
274 	// Ensure the class invariant can be maintained.
275 	// (This must be done first as we cannot do anything but `erase` elements filtered by `remove_if`.)
276 	int removedWeight = 0;
277 	for(auto it = firstInvalid; it != variants.end(); ++it)
278 		if(IsInvalidVariant(*it))
279 			removedWeight += it->weight;
280 
281 	auto removeIt = remove_if(firstInvalid, variants.end(), IsInvalidVariant);
282 	int count = distance(removeIt, variants.end());
283 	Files::LogError("Warning: " + (fleetName.empty() ? "unnamed fleet" : "fleet \"" + fleetName + "\"")
284 		+ ": Removing " + to_string(count) + " invalid " + (count > 1 ? "variants" : "variant")
285 		+ " (" + to_string(removedWeight) + " of " + to_string(total) + " weight)");
286 
287 	total -= removedWeight;
288 	variants.erase(removeIt, variants.end());
289 }
290 
291 
292 
293 // Get the government of this fleet.
GetGovernment() const294 const Government *Fleet::GetGovernment() const
295 {
296 	return government;
297 }
298 
299 
300 // Choose a fleet to be created during flight, and have it enter the system via jump or planetary departure.
Enter(const System & system,list<shared_ptr<Ship>> & ships,const Planet * planet) const301 void Fleet::Enter(const System &system, list<shared_ptr<Ship>> &ships, const Planet *planet) const
302 {
303 	if(!total || variants.empty())
304 		return;
305 
306 	// Pick a fleet variant to instantiate.
307 	const Variant &variant = ChooseVariant();
308 	if(variant.ships.empty())
309 		return;
310 
311 	// Figure out what system the fleet is starting in, where it is going, and
312 	// what position it should start from in the system.
313 	const System *source = &system;
314 	const System *target = &system;
315 	Point position;
316 	double radius = 1000.;
317 
318 	// Only pick a random entry point for this fleet if a source planet was not specified.
319 	if(!planet)
320 	{
321 		// Where this fleet can come from depends on whether it is friendly to any
322 		// planets in this system and whether it has jump drives.
323 		vector<const System *> linkVector;
324 		// Find out what the "best" jump method the fleet has is. Assume that if the
325 		// others don't have that jump method, they are being carried as fighters.
326 		// That is, content creators should avoid creating fleets with a mix of jump
327 		// drives and hyperdrives.
328 		bool hasJump = false;
329 		bool hasHyper = false;
330 		double jumpDistance = System::DEFAULT_NEIGHBOR_DISTANCE;
331 		for(const Ship *ship : variant.ships)
332 		{
333 			if(ship->Attributes().Get("jump drive"))
334 			{
335 				hasJump = true;
336 				jumpDistance = ship->JumpRange();
337 				break;
338 			}
339 			if(ship->Attributes().Get("hyperdrive"))
340 				hasHyper = true;
341 		}
342 		// Don't try to make a fleet "enter" from another system if none of the
343 		// ships have jump drives.
344 		if(hasJump || hasHyper)
345 		{
346 			bool isWelcomeHere = !system.GetGovernment()->IsEnemy(government);
347 			for(const System *neighbor : (hasJump ? system.JumpNeighbors(jumpDistance) : system.Links()))
348 			{
349 				// If this ship is not "welcome" in the current system, prefer to have
350 				// it enter from a system that is friendly to it. (This is for realism,
351 				// so attack fleets don't come from what ought to be a safe direction.)
352 				if(isWelcomeHere || neighbor->GetGovernment()->IsEnemy(government))
353 					linkVector.push_back(neighbor);
354 				else
355 					linkVector.insert(linkVector.end(), 8, neighbor);
356 			}
357 		}
358 
359 		// Find all the inhabited planets this fleet could take off from.
360 		vector<const Planet *> planetVector;
361 		if(!personality.IsSurveillance())
362 			for(const StellarObject &object : system.Objects())
363 				if(object.HasValidPlanet() && object.GetPlanet()->HasSpaceport()
364 						&& !object.GetPlanet()->GetGovernment()->IsEnemy(government))
365 					planetVector.push_back(object.GetPlanet());
366 
367 		// If there is nowhere for this fleet to come from, don't create it.
368 		size_t options = linkVector.size() + planetVector.size();
369 		if(!options)
370 		{
371 			// Prefer to launch from inhabited planets, but launch from
372 			// uninhabited ones if there is no other option.
373 			for(const StellarObject &object : system.Objects())
374 				if(object.HasValidPlanet() && !object.GetPlanet()->GetGovernment()->IsEnemy(government))
375 					planetVector.push_back(object.GetPlanet());
376 			options = planetVector.size();
377 			if(!options)
378 				return;
379 		}
380 
381 		// Choose a random planet or star system to come from.
382 		size_t choice = Random::Int(options);
383 
384 		// If a planet is chosen, also pick a system to travel to after taking off.
385 		if(choice >= linkVector.size())
386 		{
387 			planet = planetVector[choice - linkVector.size()];
388 			if(!linkVector.empty())
389 				target = linkVector[Random::Int(linkVector.size())];
390 		}
391 		// We are entering this system via hyperspace, not taking off from a planet.
392 		else
393 			source = linkVector[choice];
394 	}
395 
396 	auto placed = Instantiate(variant);
397 	// Carry all ships that can be carried, as they don't need to be positioned
398 	// or checked to see if they can access a particular planet.
399 	for(auto &ship : placed)
400 		PlaceFighter(ship, placed);
401 
402 	// Find the stellar object for this planet, and place the ships there.
403 	if(planet)
404 	{
405 		const StellarObject *object = system.FindStellar(planet);
406 		if(!object)
407 		{
408 			// Log this error.
409 			Files::LogError("Fleet::Enter: Unable to find valid stellar object for planet \""
410 				+ planet->TrueName() + "\" in system \"" + system.Name() + "\"");
411 			return;
412 		}
413 		// To take off from the planet, all non-carried ships must be able to access it.
414 		else if(planet->IsUnrestricted() || all_of(placed.cbegin(), placed.cend(), [&](const shared_ptr<Ship> &ship)
415 				{ return ship->GetParent() || planet->IsAccessible(ship.get()); }))
416 		{
417 			position = object->Position();
418 			radius = object->Radius();
419 		}
420 		// The chosen planet could not be departed from by all ships in the variant.
421 		else
422 		{
423 			// If there are no departure paths, then there are no arrival paths either.
424 			if(source == target)
425 				return;
426 			// Otherwise, have the fleet arrive here from the target system.
427 			std::swap(source, target);
428 			planet = nullptr;
429 		}
430 	}
431 
432 	// Place all the ships in the chosen fleet variant.
433 	shared_ptr<Ship> flagship;
434 	for(shared_ptr<Ship> &ship : placed)
435 	{
436 		// If this is a carried fighter, no need to position it.
437 		if(ship->GetParent())
438 			continue;
439 
440 		Angle angle = Angle::Random(360.);
441 		Point pos = position + angle.Unit() * (Random::Real() * radius);
442 
443 		ships.push_front(ship);
444 		ship->SetSystem(source);
445 		ship->SetPlanet(planet);
446 		if(source == &system)
447 			ship->Place(pos, angle.Unit(), angle);
448 		else
449 		{
450 			// Place the ship stationary and pointed in the right direction.
451 			angle = Angle(system.Position() - source->Position());
452 			ship->Place(pos, Point(), angle);
453 		}
454 		if(target != source)
455 			ship->SetTargetSystem(target);
456 
457 		if(flagship)
458 			ship->SetParent(flagship);
459 		else
460 			flagship = ship;
461 
462 		SetCargo(&*ship);
463 	}
464 }
465 
466 
467 
468 // Place one of the variants in the given system, already "in action." If the carried flag is set,
469 // only uncarried ships will be added to the list (as any carriables will be stored in bays).
Place(const System & system,list<shared_ptr<Ship>> & ships,bool carried) const470 void Fleet::Place(const System &system, list<shared_ptr<Ship>> &ships, bool carried) const
471 {
472 	if(!total || variants.empty())
473 		return;
474 
475 	// Pick a fleet variant to instantiate.
476 	const Variant &variant = ChooseVariant();
477 	if(variant.ships.empty())
478 		return;
479 
480 	// Determine where the fleet is going to or coming from.
481 	auto center = ChooseCenter(system);
482 
483 	// Place all the ships in the chosen fleet variant.
484 	shared_ptr<Ship> flagship;
485 	vector<shared_ptr<Ship>> placed = Instantiate(variant);
486 	for(shared_ptr<Ship> &ship : placed)
487 	{
488 		// If this is a fighter and someone can carry it, no need to position it.
489 		if(carried && PlaceFighter(ship, placed))
490 			continue;
491 
492 		Angle angle = Angle::Random();
493 		Point pos = center.first + Angle::Random().Unit() * OffsetFrom(center);
494 		double velocity = Random::Real() * ship->MaxVelocity();
495 
496 		ships.push_front(ship);
497 		ship->SetSystem(&system);
498 		ship->Place(pos, velocity * angle.Unit(), angle);
499 
500 		if(flagship)
501 			ship->SetParent(flagship);
502 		else
503 			flagship = ship;
504 
505 		SetCargo(&*ship);
506 	}
507 }
508 
509 
510 
511 // Do the randomization to make a ship enter or be in the given system.
Enter(const System & system,Ship & ship,const System * source)512 const System *Fleet::Enter(const System &system, Ship &ship, const System *source)
513 {
514 	if(system.Links().empty() || (source && !system.Links().count(source)))
515 	{
516 		Place(system, ship);
517 		return &system;
518 	}
519 
520 	// Choose which system this ship is coming from.
521 	if(!source)
522 	{
523 		auto it = system.Links().cbegin();
524 		advance(it, Random::Int(system.Links().size()));
525 		source = *it;
526 	}
527 
528 	Angle angle = Angle::Random();
529 	Point pos = angle.Unit() * Random::Real() * 1000.;
530 
531 	ship.Place(pos, angle.Unit(), angle);
532 	ship.SetSystem(source);
533 	ship.SetTargetSystem(&system);
534 
535 	return source;
536 }
537 
538 
539 
Place(const System & system,Ship & ship)540 void Fleet::Place(const System &system, Ship &ship)
541 {
542 	// Choose a random inhabited object in the system to spawn around.
543 	auto center = ChooseCenter(system);
544 	Point pos = center.first + Angle::Random().Unit() * OffsetFrom(center);
545 
546 	double velocity = ship.IsDisabled() ? 0. : Random::Real() * ship.MaxVelocity();
547 
548 	ship.SetSystem(&system);
549 	Angle angle = Angle::Random();
550 	ship.Place(pos, velocity * angle.Unit(), angle);
551 }
552 
553 
554 
Strength() const555 int64_t Fleet::Strength() const
556 {
557 	if(!total || variants.empty())
558 		return 0;
559 
560 	int64_t sum = 0;
561 	for(const Variant &variant : variants)
562 	{
563 		int64_t thisSum = 0;
564 		for(const Ship *ship : variant.ships)
565 			thisSum += ship->Cost();
566 		sum += thisSum * variant.weight;
567 	}
568 	return sum / total;
569 }
570 
571 
572 
Variant(const DataNode & node)573 Fleet::Variant::Variant(const DataNode &node)
574 {
575 	weight = 1;
576 	if(node.Token(0) == "variant" && node.Size() >= 2)
577 		weight = node.Value(1);
578 	else if(node.Token(0) == "add" && node.Size() >= 3)
579 		weight = node.Value(2);
580 
581 	for(const DataNode &child : node)
582 	{
583 		int n = 1;
584 		if(child.Size() >= 2 && child.Value(1) >= 1.)
585 			n = child.Value(1);
586 		ships.insert(ships.end(), n, GameData::Ships().Get(child.Token(0)));
587 	}
588 }
589 
590 
591 
ChooseVariant() const592 const Fleet::Variant &Fleet::ChooseVariant() const
593 {
594 	// Pick a random variant based on the weights.
595 	unsigned index = 0;
596 	for(int choice = Random::Int(total); choice >= variants[index].weight; ++index)
597 		choice -= variants[index].weight;
598 
599 	return variants[index];
600 }
601 
602 
603 
604 // Obtain a positional reference and the radius of the object at that position (e.g. a planet).
605 // Spaceport status can be modified during normal gameplay, so this information is not cached.
ChooseCenter(const System & system)606 pair<Point, double> Fleet::ChooseCenter(const System &system)
607 {
608 	auto centers = vector<pair<Point, double>>();
609 	for(const StellarObject &object : system.Objects())
610 		if(object.HasValidPlanet() && object.GetPlanet()->HasSpaceport())
611 			centers.emplace_back(object.Position(), object.Radius());
612 
613 	if(centers.empty())
614 		return {Point(), 0.};
615 	return centers[Random::Int(centers.size())];
616 }
617 
618 
619 
Instantiate(const Variant & variant) const620 vector<shared_ptr<Ship>> Fleet::Instantiate(const Variant &variant) const
621 {
622 	vector<shared_ptr<Ship>> placed;
623 	for(const Ship *model : variant.ships)
624 	{
625 		// At least one of this variant's ships is valid, but we should avoid spawning any that are not defined.
626 		if(!model->IsValid())
627 		{
628 			Files::LogError("Skipping invalid ship model \"" + model->ModelName() + "\" in fleet \"" + fleetName + "\".");
629 			continue;
630 		}
631 
632 		auto ship = make_shared<Ship>(*model);
633 
634 		const Phrase *phrase = ((ship->CanBeCarried() && fighterNames) ? fighterNames : names);
635 		if(phrase)
636 			ship->SetName(phrase->Get());
637 		ship->SetGovernment(government);
638 		ship->SetPersonality(personality);
639 
640 		placed.push_back(ship);
641 	}
642 	return placed;
643 }
644 
645 
646 
PlaceFighter(shared_ptr<Ship> fighter,vector<shared_ptr<Ship>> & placed) const647 bool Fleet::PlaceFighter(shared_ptr<Ship> fighter, vector<shared_ptr<Ship>> &placed) const
648 {
649 	if(!fighter->CanBeCarried())
650 		return false;
651 
652 	for(const shared_ptr<Ship> &parent : placed)
653 		if(parent->Carry(fighter))
654 			return true;
655 
656 	return false;
657 }
658 
659 
660 
661 // Choose the cargo associated with this ship in the fleet.
662 // If outfits were specified, but not commodities, do not pick commodities.
663 // If commodities were specified, but not outfits, do not pick outfits.
664 // If neither or both were specified, choose commodities more often..
SetCargo(Ship * ship) const665 void Fleet::SetCargo(Ship *ship) const
666 {
667 	const bool canChooseOutfits = commodities.empty() || !outfitters.empty();
668 	const bool canChooseCommodities = outfitters.empty() || !commodities.empty();
669 	// Populate the possible outfits that may be chosen.
670 	int free = ship->Cargo().Free();
671 	auto outfits = OutfitChoices(outfitters, ship->GetSystem(), free);
672 
673 	// Choose random outfits or commodities to transport.
674 	for(int i = 0; i < cargo; ++i)
675 	{
676 		if(free <= 0)
677 			break;
678 		// Remove any outfits that do not fit into remaining cargo.
679 		if(canChooseOutfits && !outfits.empty())
680 			outfits.erase(remove_if(outfits.begin(), outfits.end(),
681 					[&free](const Outfit *a) { return a->Mass() > free; }),
682 				outfits.end());
683 
684 		if(canChooseCommodities && canChooseOutfits)
685 		{
686 			if(Random::Real() < .8)
687 				AddRandomCommodity(*ship, free, commodities);
688 			else
689 				AddRandomOutfit(*ship, free, outfits);
690 		}
691 		else if(canChooseCommodities)
692 			AddRandomCommodity(*ship, free, commodities);
693 		else
694 			AddRandomOutfit(*ship, free, outfits);
695 
696 		free = ship->Cargo().Free();
697 	}
698 	int extraCrew = ship->Attributes().Get("bunks") - ship->RequiredCrew();
699 	if(extraCrew > 0)
700 		ship->AddCrew(Random::Int(extraCrew + 1));
701 }
702