1 /* PlayerInfo.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 "PlayerInfo.h"
14 
15 #include "Audio.h"
16 #include "ConversationPanel.h"
17 #include "DataFile.h"
18 #include "DataWriter.h"
19 #include "Dialog.h"
20 #include "Files.h"
21 #include "text/Format.h"
22 #include "GameData.h"
23 #include "Government.h"
24 #include "Hardpoint.h"
25 #include "Messages.h"
26 #include "Mission.h"
27 #include "Outfit.h"
28 #include "Person.h"
29 #include "Planet.h"
30 #include "Politics.h"
31 #include "Preferences.h"
32 #include "Random.h"
33 #include "SavedGame.h"
34 #include "Ship.h"
35 #include "ShipEvent.h"
36 #include "StartConditions.h"
37 #include "StellarObject.h"
38 #include "System.h"
39 #include "UI.h"
40 
41 #include <algorithm>
42 #include <cmath>
43 #include <ctime>
44 #include <iterator>
45 #include <limits>
46 #include <sstream>
47 #include <stdexcept>
48 
49 using namespace std;
50 
51 
52 
53 // Completely clear all loaded information, to prepare for loading a file or
54 // creating a new pilot.
Clear()55 void PlayerInfo::Clear()
56 {
57 	*this = PlayerInfo();
58 
59 	Random::Seed(time(nullptr));
60 	GameData::Revert();
61 	Messages::Reset();
62 }
63 
64 
65 
66 // Check if a player has been loaded.
IsLoaded() const67 bool PlayerInfo::IsLoaded() const
68 {
69 	return !firstName.empty();
70 }
71 
72 
73 
74 // Make a new player.
New(const StartConditions & start)75 void PlayerInfo::New(const StartConditions &start)
76 {
77 	// Clear any previously loaded data.
78 	Clear();
79 
80 	// Copy the core information from the full starting scenario.
81 	startData = start;
82 	// Copy any ships in the start conditions.
83 	for(const Ship &ship : start.Ships())
84 	{
85 		ships.emplace_back(new Ship(ship));
86 		ships.back()->SetSystem(&start.GetSystem());
87 		ships.back()->SetPlanet(&start.GetPlanet());
88 		ships.back()->SetIsSpecial();
89 		ships.back()->SetIsYours();
90 		ships.back()->SetGovernment(GameData::PlayerGovernment());
91 	}
92 	// Load starting conditions from a "start" item in the data files. If no
93 	// such item exists, StartConditions defines default values.
94 	date = start.GetDate();
95 	GameData::SetDate(date);
96 	// Make sure the fleet depreciation object knows it is tracking the player's
97 	// fleet, not the planet's stock.
98 	depreciation.Init(ships, date.DaysSinceEpoch());
99 
100 	SetSystem(start.GetSystem());
101 	SetPlanet(&start.GetPlanet());
102 	accounts = start.GetAccounts();
103 	start.GetConditions().Apply(conditions);
104 	UpdateAutoConditions();
105 
106 	// Generate missions that will be available on the first day.
107 	CreateMissions();
108 
109 	// Add to the list of events that should happen on certain days.
110 	for(const auto &it : GameData::Events())
111 		if(it.second.GetDate())
112 			AddEvent(it.second, it.second.GetDate());
113 }
114 
115 
116 
117 // Load player information from a saved game file.
Load(const string & path)118 void PlayerInfo::Load(const string &path)
119 {
120 	// Make sure any previously loaded data is cleared.
121 	Clear();
122 
123 	filePath = path;
124 	// Strip anything after the "~" from snapshots, so that the file we save
125 	// will be the auto-save, not the snapshot.
126 	size_t pos = filePath.find('~');
127 	size_t namePos = filePath.length() - Files::Name(filePath).length();
128 	if(pos != string::npos && pos > namePos)
129 		filePath = filePath.substr(0, pos) + ".txt";
130 
131 	// The player may have bribed their current planet in the last session. Ensure
132 	// we provide the same access to services in this session, too.
133 	bool hasFullClearance = false;
134 
135 	DataFile file(path);
136 	for(const DataNode &child : file)
137 	{
138 		// Basic player information and persistent UI settings:
139 		if(child.Token(0) == "pilot" && child.Size() >= 3)
140 		{
141 			firstName = child.Token(1);
142 			lastName = child.Token(2);
143 		}
144 		else if(child.Token(0) == "date" && child.Size() >= 4)
145 			date = Date(child.Value(1), child.Value(2), child.Value(3));
146 		else if(child.Token(0) == "system" && child.Size() >= 2)
147 			system = GameData::Systems().Get(child.Token(1));
148 		else if(child.Token(0) == "planet" && child.Size() >= 2)
149 			planet = GameData::Planets().Get(child.Token(1));
150 		else if(child.Token(0) == "clearance")
151 			hasFullClearance = true;
152 		else if(child.Token(0) == "launching")
153 			shouldLaunch = true;
154 		else if(child.Token(0) == "playtime" && child.Size() >= 2)
155 			playTime = child.Value(1);
156 		else if(child.Token(0) == "travel" && child.Size() >= 2)
157 			travelPlan.push_back(GameData::Systems().Get(child.Token(1)));
158 		else if(child.Token(0) == "travel destination" && child.Size() >= 2)
159 			travelDestination = GameData::Planets().Get(child.Token(1));
160 		else if(child.Token(0) == "map coloring" && child.Size() >= 2)
161 			mapColoring = child.Value(1);
162 		else if(child.Token(0) == "map zoom" && child.Size() >= 2)
163 			mapZoom = child.Value(1);
164 		else if(child.Token(0) == "collapsed" && child.Size() >= 2)
165 		{
166 			for(const DataNode &grand : child)
167 				collapsed[child.Token(1)].insert(grand.Token(0));
168 		}
169 		else if(child.Token(0) == "reputation with")
170 		{
171 			for(const DataNode &grand : child)
172 				if(grand.Size() >= 2)
173 					reputationChanges.emplace_back(
174 						GameData::Governments().Get(grand.Token(0)), grand.Value(1));
175 		}
176 
177 		// Records of things you own:
178 		else if(child.Token(0) == "ship")
179 		{
180 			// Ships owned by the player have various special characteristics:
181 			ships.push_back(make_shared<Ship>(child));
182 			ships.back()->SetIsSpecial();
183 			ships.back()->SetIsYours();
184 			// Defer finalizing this ship until we have processed all changes to game state.
185 		}
186 		else if(child.Token(0) == "groups" && child.Size() >= 2 && !ships.empty())
187 			groups[ships.back().get()] = child.Value(1);
188 		else if(child.Token(0) == "storage")
189 		{
190 			for(const DataNode &grand : child)
191 				if(grand.Size() >= 2 && grand.Token(0) == "planet")
192 					for(const DataNode &grandGrand : grand)
193 						if(grandGrand.Token(0) == "cargo")
194 						{
195 							CargoHold &storage = planetaryStorage[GameData::Planets().Get(grand.Token(1))];
196 							storage.Load(grandGrand);
197 						}
198 		}
199 		else if(child.Token(0) == "account")
200 			accounts.Load(child, true);
201 		else if(child.Token(0) == "cargo")
202 			cargo.Load(child);
203 		else if(child.Token(0) == "basis")
204 		{
205 			for(const DataNode &grand : child)
206 				if(grand.Size() >= 2)
207 					costBasis[grand.Token(0)] += grand.Value(1);
208 		}
209 		else if(child.Token(0) == "stock")
210 		{
211 			for(const DataNode &grand : child)
212 				if(grand.Size() >= 2)
213 					stock[GameData::Outfits().Get(grand.Token(0))] += grand.Value(1);
214 		}
215 		else if(child.Token(0) == "fleet depreciation")
216 			depreciation.Load(child);
217 		else if(child.Token(0) == "stock depreciation")
218 			stockDepreciation.Load(child);
219 
220 		// Records of things you have done or are doing, or have happened to you:
221 		else if(child.Token(0) == "mission")
222 		{
223 			missions.emplace_back(child);
224 			cargo.AddMissionCargo(&missions.back());
225 		}
226 		else if(child.Token(0) == "available job")
227 			availableJobs.emplace_back(child);
228 		else if(child.Token(0) == "available mission")
229 			availableMissions.emplace_back(child);
230 		else if(child.Token(0) == "conditions")
231 		{
232 			for(const DataNode &grand : child)
233 				conditions[grand.Token(0)] = (grand.Size() >= 2) ? grand.Value(1) : 1;
234 		}
235 		else if(child.Token(0) == "event")
236 			gameEvents.emplace_back(child);
237 		else if(child.Token(0) == "changes")
238 		{
239 			for(const DataNode &grand : child)
240 				dataChanges.push_back(grand);
241 		}
242 		else if(child.Token(0) == "economy")
243 			economy = child;
244 		else if(child.Token(0) == "destroyed" && child.Size() >= 2)
245 			destroyedPersons.push_back(child.Token(1));
246 
247 		// Records of things you have discovered:
248 		else if(child.Token(0) == "visited" && child.Size() >= 2)
249 			Visit(*GameData::Systems().Get(child.Token(1)));
250 		else if(child.Token(0) == "visited planet" && child.Size() >= 2)
251 			Visit(*GameData::Planets().Get(child.Token(1)));
252 		else if(child.Token(0) == "harvested")
253 		{
254 			for(const DataNode &grand : child)
255 				if(grand.Size() >= 2)
256 					harvested.emplace(
257 						GameData::Systems().Get(grand.Token(0)),
258 						GameData::Outfits().Get(grand.Token(1)));
259 		}
260 		else if(child.Token(0) == "logbook")
261 		{
262 			for(const DataNode &grand : child)
263 			{
264 				if(grand.Size() >= 3)
265 				{
266 					Date date(grand.Value(0), grand.Value(1), grand.Value(2));
267 					string text;
268 					for(const DataNode &great : grand)
269 					{
270 						if(!text.empty())
271 							text += "\n\t";
272 						text += great.Token(0);
273 					}
274 					logbook.emplace(date, text);
275 				}
276 				else if(grand.Size() >= 2)
277 				{
278 					string &text = specialLogs[grand.Token(0)][grand.Token(1)];
279 					for(const DataNode &great : grand)
280 					{
281 						if(!text.empty())
282 							text += "\n\t";
283 						text += great.Token(0);
284 					}
285 				}
286 			}
287 		}
288 		else if(child.Token(0) == "start")
289 			startData.Load(child);
290 	}
291 	// Modify the game data with any changes that were loaded from this file.
292 	ApplyChanges();
293 	// Ensure the player is in a valid state after loading & applying changes.
294 	ValidateLoad();
295 
296 	// Restore access to services, if it was granted previously.
297 	if(planet && hasFullClearance)
298 		planet->Bribe();
299 
300 	// Based on the ships that were loaded, calculate the player's capacity for
301 	// cargo and passengers.
302 	UpdateCargoCapacities();
303 
304 	// If no depreciation record was loaded, every item in the player's fleet
305 	// will count as non-depreciated.
306 	if(!depreciation.IsLoaded())
307 		depreciation.Init(ships, date.DaysSinceEpoch());
308 }
309 
310 
311 
312 // Load the most recently saved player (if any). Returns false when no save was loaded.
LoadRecent()313 bool PlayerInfo::LoadRecent()
314 {
315 	string recentPath = Files::Read(Files::Config() + "recent.txt");
316 	// Trim trailing whitespace (including newlines) from the path.
317 	while(!recentPath.empty() && recentPath.back() <= ' ')
318 		recentPath.pop_back();
319 
320 	if(recentPath.empty() || !Files::Exists(recentPath))
321 	{
322 		Clear();
323 		return false;
324 	}
325 
326 	Load(recentPath);
327 	return true;
328 }
329 
330 
331 
332 // Save this player. The file name is based on the player's name.
Save() const333 void PlayerInfo::Save() const
334 {
335 	// Don't save dead players or players that are not fully created.
336 	if(!CanBeSaved())
337 		return;
338 
339 	// Remember that this was the most recently saved player.
340 	Files::Write(Files::Config() + "recent.txt", filePath + '\n');
341 
342 	if(filePath.rfind(".txt") == filePath.length() - 4)
343 	{
344 		// Only update the backups if this save will have a newer date.
345 		SavedGame saved(filePath);
346 		if(saved.GetDate() != date.ToString())
347 		{
348 			string root = filePath.substr(0, filePath.length() - 4);
349 			string files[4] = {
350 				root + "~~previous-3.txt",
351 				root + "~~previous-2.txt",
352 				root + "~~previous-1.txt",
353 				filePath
354 			};
355 			for(int i = 0; i < 3; ++i)
356 				if(Files::Exists(files[i + 1]))
357 					Files::Move(files[i + 1], files[i]);
358 		}
359 	}
360 
361 	Save(filePath);
362 }
363 
364 
365 
366 // Get the base file name for the player, without the ".txt" extension. This
367 // will usually be "<first> <last>", but may be different if multiple players
368 // exist with the same name, in which case a number is appended.
Identifier() const369 string PlayerInfo::Identifier() const
370 {
371 	string name = Files::Name(filePath);
372 	return (name.length() < 4) ? "" : name.substr(0, name.length() - 4);
373 }
374 
375 
376 
377 // Apply the given set of changes to the game data.
AddChanges(list<DataNode> & changes)378 void PlayerInfo::AddChanges(list<DataNode> &changes)
379 {
380 	bool changedSystems = false;
381 	for(const DataNode &change : changes)
382 	{
383 		changedSystems |= (change.Token(0) == "system");
384 		changedSystems |= (change.Token(0) == "link");
385 		changedSystems |= (change.Token(0) == "unlink");
386 		GameData::Change(change);
387 	}
388 	if(changedSystems)
389 	{
390 		// Recalculate what systems have been seen.
391 		GameData::UpdateSystems();
392 		seen.clear();
393 		for(const System *system : visitedSystems)
394 		{
395 			seen.insert(system);
396 			for(const System *neighbor : system->VisibleNeighbors())
397 				if(!neighbor->Hidden() || system->Links().count(neighbor))
398 					seen.insert(neighbor);
399 		}
400 	}
401 
402 	// Only move the changes into my list if they are not already there.
403 	if(&changes != &dataChanges)
404 		dataChanges.splice(dataChanges.end(), changes);
405 }
406 
407 
408 
409 // Add an event that will happen at the given date.
AddEvent(const GameEvent & event,const Date & date)410 void PlayerInfo::AddEvent(const GameEvent &event, const Date &date)
411 {
412 	gameEvents.push_back(event);
413 	gameEvents.back().SetDate(date);
414 }
415 
416 
417 
418 // Mark this player as dead, and handle the changes to the player's fleet.
Die(int response,const shared_ptr<Ship> & capturer)419 void PlayerInfo::Die(int response, const shared_ptr<Ship> &capturer)
420 {
421 	isDead = true;
422 	// The player loses access to all their ships if they die on a planet.
423 	if(GetPlanet() || !flagship)
424 		ships.clear();
425 	// If the flagship should explode due to choices made in a mission's
426 	// conversation, it should still appear in the player's ship list (but
427 	// will be red, because it is dead). The player's escorts will scatter
428 	// automatically, as they have a now-dead parent.
429 	else if(response == Conversation::EXPLODE)
430 		flagship->Destroy();
431 	// If it died in open combat, it is already marked destroyed.
432 	else if(!flagship->IsDestroyed())
433 	{
434 		// The player died due to the failed capture of an NPC or a
435 		// "mutiny". The flagship is either captured or changes government.
436 		if(!flagship->IsYours())
437 		{
438 			// The flagship was already captured, via BoardingPanel,
439 			// and its parent-escort relationships were updated in
440 			// Ship::WasCaptured().
441 		}
442 		// The referenced ship may not be boarded by the player, so before
443 		// letting it capture the flagship it must be near the flagship.
444 		else if(capturer && capturer->Position().Distance(flagship->Position()) <= 1.)
445 			flagship->WasCaptured(capturer);
446 		else
447 		{
448 			// A "mutiny" occurred.
449 			flagship->SetIsYours(false);
450 			// TODO: perhaps allow missions to set the new government.
451 			flagship->SetGovernment(GameData::Governments().Get("Independent"));
452 			// Your escorts do not follow it, nor does it wait for them.
453 			for(const shared_ptr<Ship> &ship : ships)
454 				ship->SetParent(nullptr);
455 		}
456 		// Remove the flagship from the player's ship list.
457 		auto it = find(ships.begin(), ships.end(), flagship);
458 		if(it != ships.end())
459 			ships.erase(it);
460 	}
461 }
462 
463 
464 
465 // Query whether this player is dead.
IsDead() const466 bool PlayerInfo::IsDead() const
467 {
468 	return isDead;
469 }
470 
471 
472 
473 // Get the player's first name.
FirstName() const474 const string &PlayerInfo::FirstName() const
475 {
476 	return firstName;
477 }
478 
479 
480 
481 // Get the player's last name.
LastName() const482 const string &PlayerInfo::LastName() const
483 {
484 	return lastName;
485 }
486 
487 
488 
489 // Set the player's name. This will also set the saved game file name.
SetName(const string & first,const string & last)490 void PlayerInfo::SetName(const string &first, const string &last)
491 {
492 	firstName = first;
493 	lastName = last;
494 
495 	string fileName = first + " " + last;
496 
497 	// If there are multiple pilots with the same name, append a number to the
498 	// pilot name to generate a unique file name.
499 	filePath = Files::Saves() + fileName;
500 	int index = 0;
501 	while(true)
502 	{
503 		string path = filePath;
504 		if(index++)
505 			path += " " + to_string(index);
506 		path += ".txt";
507 
508 		if(!Files::Exists(path))
509 		{
510 			filePath.swap(path);
511 			break;
512 		}
513 	}
514 }
515 
516 
517 
518 // Get the current date (game world, not real world).
GetDate() const519 const Date &PlayerInfo::GetDate() const
520 {
521 	return date;
522 }
523 
524 
525 
526 // Set the date to the next day, and perform all daily actions.
IncrementDate()527 void PlayerInfo::IncrementDate()
528 {
529 	++date;
530 	conditions["day"] = date.Day();
531 	conditions["month"] = date.Month();
532 	conditions["year"] = date.Year();
533 
534 	// Check if any special events should happen today.
535 	auto it = gameEvents.begin();
536 	while(it != gameEvents.end())
537 	{
538 		if(date < it->GetDate())
539 			++it;
540 		else
541 		{
542 			it->Apply(*this);
543 			it = gameEvents.erase(it);
544 		}
545 	}
546 
547 	// Check if any missions have failed because of deadlines.
548 	for(Mission &mission : missions)
549 		if(mission.CheckDeadline(date) && mission.IsVisible())
550 			Messages::Add("You failed to meet the deadline for the mission \"" + mission.Name() + "\".");
551 
552 	// Check what salaries and tribute the player receives.
553 	int64_t total[2] = {0, 0};
554 	static const string prefix[2] = {"salary: ", "tribute: "};
555 	for(int i = 0; i < 2; ++i)
556 	{
557 		auto it = conditions.lower_bound(prefix[i]);
558 		for( ; it != conditions.end() && !it->first.compare(0, prefix[i].length(), prefix[i]); ++it)
559 			total[i] += it->second;
560 	}
561 	if(total[0] || total[1])
562 	{
563 		string message = "You receive ";
564 		if(total[0])
565 			message += Format::Credits(total[0]) + " credits salary";
566 		if(total[0] && total[1])
567 			message += " and ";
568 		if(total[1])
569 			message += Format::Credits(total[1]) + " credits in tribute";
570 		message += ".";
571 		Messages::Add(message);
572 		accounts.AddCredits(total[0] + total[1]);
573 	}
574 
575 	// For accounting, keep track of the player's net worth. This is for
576 	// calculation of yearly income to determine maximum mortgage amounts.
577 	int64_t assets = depreciation.Value(ships, date.DaysSinceEpoch());
578 	for(const shared_ptr<Ship> &ship : ships)
579 		assets += ship->Cargo().Value(system);
580 
581 	// Have the player pay salaries, mortgages, etc. and print a message that
582 	// summarizes the payments that were made.
583 	string message = accounts.Step(assets, Salaries(), Maintenance());
584 	if(!message.empty())
585 		Messages::Add(message);
586 
587 	// Reset the reload counters for all your ships.
588 	for(const shared_ptr<Ship> &ship : ships)
589 		ship->GetArmament().ReloadAll();
590 
591 	// Re-calculate all automatic conditions
592 	UpdateAutoConditions();
593 }
594 
595 
596 
StartData() const597 const CoreStartData &PlayerInfo::StartData() const noexcept
598 {
599 	return startData;
600 }
601 
602 
603 
604 // Set the player's current start system, and mark that system as visited.
SetSystem(const System & system)605 void PlayerInfo::SetSystem(const System &system)
606 {
607 	this->system = &system;
608 	Visit(system);
609 }
610 
611 
612 
613 // Get the player's current star system.
GetSystem() const614 const System *PlayerInfo::GetSystem() const
615 {
616 	return system;
617 }
618 
619 
620 
621 // Set the planet the player is landed on.
SetPlanet(const Planet * planet)622 void PlayerInfo::SetPlanet(const Planet *planet)
623 {
624 	this->planet = planet;
625 }
626 
627 
628 
629 // Get the planet the player is landed on.
GetPlanet() const630 const Planet *PlayerInfo::GetPlanet() const
631 {
632 	return planet;
633 }
634 
635 
636 
637 // If the player is landed, return the stellar object they are on. Some planets
638 // (e.g. ringworlds) may include multiple stellar objects in the same system.
GetStellarObject() const639 const StellarObject *PlayerInfo::GetStellarObject() const
640 {
641 	if(!system || !planet)
642 		return nullptr;
643 
644 	double closestDistance = numeric_limits<double>::infinity();
645 	const StellarObject *closestObject = nullptr;
646 	for(const StellarObject &object : system->Objects())
647 		if(object.GetPlanet() == planet)
648 		{
649 			if(!Flagship())
650 				return &object;
651 
652 			double distance = Flagship()->Position().Distance(object.Position());
653 			if(distance < closestDistance)
654 			{
655 				closestDistance = distance;
656 				closestObject = &object;
657 			}
658 		}
659 	return closestObject;
660 }
661 
662 
663 
664 // Check if the player must take off immediately.
ShouldLaunch() const665 bool PlayerInfo::ShouldLaunch() const
666 {
667 	return shouldLaunch;
668 }
669 
670 
671 
672 // Access the player's account information.
Accounts() const673 const Account &PlayerInfo::Accounts() const
674 {
675 	return accounts;
676 }
677 
678 
679 
680 // Access the player's account information (and allow modifying it).
Accounts()681 Account &PlayerInfo::Accounts()
682 {
683 	return accounts;
684 }
685 
686 
687 
688 // Calculate how much the player pays in daily salaries.
Salaries() const689 int64_t PlayerInfo::Salaries() const
690 {
691 	// Don't count extra crew on anything but the flagship.
692 	int64_t crew = 0;
693 	const Ship *flagship = Flagship();
694 	if(flagship)
695 		crew = flagship->Crew() - flagship->RequiredCrew();
696 
697 	// A ship that is "parked" remains on a planet and requires no salaries.
698 	for(const shared_ptr<Ship> &ship : ships)
699 		if(!ship->IsParked() && !ship->IsDestroyed())
700 			crew += ship->RequiredCrew();
701 	if(!crew)
702 		return 0;
703 
704 	// Every crew member except the player receives 100 credits per day.
705 	return 100 * (crew - 1);
706 }
707 
708 
709 
710 // Calculate the daily maintenance cost for all ships and in cargo outfits.
Maintenance() const711 int64_t PlayerInfo::Maintenance() const
712 {
713 	int64_t maintenance = 0;
714 	// If the player is landed, then cargo will be in the player's
715 	// pooled cargo. Check there so that the bank panel can display the
716 	// correct total maintenance costs. When launched all cargo will be
717 	// in the player's ships instead of in the pooled cargo, so no outfit
718 	// will be counted twice.
719 	for(const auto &outfit : Cargo().Outfits())
720 		maintenance += max<int64_t>(0, outfit.first->Get("maintenance costs")) * outfit.second;
721 	for(const shared_ptr<Ship> &ship : ships)
722 		if(!ship->IsDestroyed())
723 		{
724 			maintenance += max<int64_t>(0, ship->Attributes().Get("maintenance costs"));
725 			for(const auto &outfit : ship->Cargo().Outfits())
726 				maintenance += max<int64_t>(0, outfit.first->Get("maintenance costs")) * outfit.second;
727 			if(!ship->IsParked())
728 				maintenance += max<int64_t>(0, ship->Attributes().Get("operating costs"));
729 		}
730 	return maintenance;
731 }
732 
733 
734 
735 // Get a pointer to the ship that the player controls. This is always the first
736 // ship in the list.
Flagship() const737 const Ship *PlayerInfo::Flagship() const
738 {
739 	return const_cast<PlayerInfo *>(this)->FlagshipPtr().get();
740 }
741 
742 
743 
744 // Get a pointer to the ship that the player controls. This is always the first
745 // ship in the list.
Flagship()746 Ship *PlayerInfo::Flagship()
747 {
748 	return FlagshipPtr().get();
749 }
750 
751 
752 
753 // Determine which ship is the flagship and return the shared pointer to it.
FlagshipPtr()754 const shared_ptr<Ship> &PlayerInfo::FlagshipPtr()
755 {
756 	if(!flagship)
757 	{
758 		for(const shared_ptr<Ship> &it : ships)
759 			if(!it->IsParked() && it->GetSystem() == system && it->CanBeFlagship())
760 			{
761 				flagship = it;
762 				break;
763 			}
764 	}
765 
766 	static const shared_ptr<Ship> empty;
767 	return (flagship && flagship->IsYours()) ? flagship : empty;
768 }
769 
770 
771 
772 // Access the full list of ships that the player owns.
Ships() const773 const vector<shared_ptr<Ship>> &PlayerInfo::Ships() const
774 {
775 	return ships;
776 }
777 
778 
779 
780 // Inspect the flightworthiness of the player's active fleet, individually and
781 // as a whole, to determine which ships cannot travel with the group.
782 // Returns a mapping of ships to the reason their flight check failed.
FlightCheck() const783 map<const shared_ptr<Ship>, vector<string>> PlayerInfo::FlightCheck() const
784 {
785 	// Count of all bay types in the active fleet.
786 	auto bayCount = map<string, size_t>{};
787 	// Classification of the present ships by category. Parked ships are ignored.
788 	auto categoryCount = map<string, vector<shared_ptr<Ship>>>{};
789 
790 	auto flightChecks = map<const shared_ptr<Ship>, vector<string>>{};
791 	for(const auto &ship : ships)
792 		if(ship->GetSystem() == system && !ship->IsDisabled() && !ship->IsParked())
793 		{
794 			auto checks = ship->FlightCheck();
795 			if(!checks.empty())
796 				flightChecks.emplace(ship, checks);
797 
798 			categoryCount[ship->Attributes().Category()].emplace_back(ship);
799 			if(ship->CanBeCarried() || !ship->HasBays())
800 				continue;
801 
802 			for(auto &bay : ship->Bays())
803 			{
804 				++bayCount[bay.category];
805 				// The bays should always be empty. But if not, count that ship too.
806 				if(bay.ship)
807 				{
808 					Files::LogError("Expected bay to be empty for " + ship->ModelName() + ": " + ship->Name());
809 					categoryCount[bay.ship->Attributes().Category()].emplace_back(bay.ship);
810 				}
811 			}
812 		}
813 
814 	// Identify transportable ships that cannot jump and have no bay to be carried in.
815 	for(auto &bayType : bayCount)
816 	{
817 		const auto &shipsOfType = categoryCount[bayType.first];
818 		if(shipsOfType.empty())
819 			continue;
820 		for(const auto &carriable : shipsOfType)
821 		{
822 			if(carriable->JumpsRemaining() != 0)
823 			{
824 				// This ship can travel between systems and does not require a bay.
825 			}
826 			// This ship requires a bay to travel between systems.
827 			else if(bayType.second > 0)
828 				--bayType.second;
829 			else
830 			{
831 				// Include the lack of bay availability amongst any other
832 				// warnings for this carriable ship.
833 				auto it = flightChecks.find(carriable);
834 				string warning = "no bays?";
835 				if(it != flightChecks.end())
836 					it->second.emplace_back(warning);
837 				else
838 					flightChecks.emplace(carriable, vector<string>{warning});
839 			}
840 		}
841 	}
842 	return flightChecks;
843 }
844 
845 
846 
847 // Add a captured ship to your fleet.
AddShip(const shared_ptr<Ship> & ship)848 void PlayerInfo::AddShip(const shared_ptr<Ship> &ship)
849 {
850 	ships.push_back(ship);
851 	ship->SetIsSpecial();
852 	ship->SetIsYours();
853 }
854 
855 
856 
857 // Adds a ship of the given model with the given name to the player's fleet.
858 // If this ship is being gifted, it costs nothing and starts fully depreciated.
BuyShip(const Ship * model,const string & name,bool isGift)859 void PlayerInfo::BuyShip(const Ship *model, const string &name, bool isGift)
860 {
861 	if(!model)
862 		return;
863 
864 	int day = date.DaysSinceEpoch();
865 	int64_t cost = isGift ? 0 : stockDepreciation.Value(*model, day);
866 	if(accounts.Credits() >= cost)
867 	{
868 		ships.push_back(make_shared<Ship>(*model));
869 		ships.back()->SetName(name);
870 		ships.back()->SetSystem(system);
871 		ships.back()->SetPlanet(planet);
872 		ships.back()->SetIsSpecial();
873 		ships.back()->SetIsYours();
874 		ships.back()->SetGovernment(GameData::PlayerGovernment());
875 
876 		accounts.AddCredits(-cost);
877 		flagship.reset();
878 
879 		// Record the transfer of this ship in the depreciation and stock info.
880 		if(!isGift)
881 		{
882 			depreciation.Buy(*model, day, &stockDepreciation);
883 			for(const auto &it : model->Outfits())
884 				stock[it.first] -= it.second;
885 		}
886 	}
887 }
888 
889 
890 
891 // Sell the given ship (if it belongs to the player).
SellShip(const Ship * selected)892 void PlayerInfo::SellShip(const Ship *selected)
893 {
894 	for(auto it = ships.begin(); it != ships.end(); ++it)
895 		if(it->get() == selected)
896 		{
897 			int day = date.DaysSinceEpoch();
898 			int64_t cost = depreciation.Value(*selected, day);
899 
900 			// Record the transfer of this ship in the depreciation and stock info.
901 			stockDepreciation.Buy(*selected, day, &depreciation);
902 			for(const auto &it : selected->Outfits())
903 				stock[it.first] += it.second;
904 
905 			accounts.AddCredits(cost);
906 			ships.erase(it);
907 			flagship.reset();
908 			return;
909 		}
910 }
911 
912 
913 
DisownShip(const Ship * selected)914 void PlayerInfo::DisownShip(const Ship *selected)
915 {
916 	for(auto it = ships.begin(); it != ships.end(); ++it)
917 		if(it->get() == selected)
918 		{
919 			ships.erase(it);
920 			flagship.reset();
921 			return;
922 		}
923 }
924 
925 
926 
927 // Park or unpark the given ship. A parked ship remains on a planet instead of
928 // flying with the player, and requires no daily crew payments.
ParkShip(const Ship * selected,bool isParked)929 void PlayerInfo::ParkShip(const Ship *selected, bool isParked)
930 {
931 	for(auto it = ships.begin(); it != ships.end(); ++it)
932 		if(it->get() == selected)
933 		{
934 			isParked &= !(*it)->IsDisabled();
935 			(*it)->SetIsParked(isParked);
936 			UpdateCargoCapacities();
937 			flagship.reset();
938 			return;
939 		}
940 }
941 
942 
943 
944 // Rename the given ship.
RenameShip(const Ship * selected,const string & name)945 void PlayerInfo::RenameShip(const Ship *selected, const string &name)
946 {
947 	for(auto it = ships.begin(); it != ships.end(); ++it)
948 		if(it->get() == selected)
949 		{
950 			(*it)->SetName(name);
951 			return;
952 		}
953 }
954 
955 
956 
957 // Change the order of the given ship in the list.
ReorderShip(int fromIndex,int toIndex)958 void PlayerInfo::ReorderShip(int fromIndex, int toIndex)
959 {
960 	// Make sure the indices are valid.
961 	if(fromIndex == toIndex)
962 		return;
963 	if(static_cast<unsigned>(fromIndex) >= ships.size())
964 		return;
965 	if(static_cast<unsigned>(toIndex) >= ships.size())
966 		return;
967 
968 	// Reorder the list.
969 	shared_ptr<Ship> ship = ships[fromIndex];
970 	ships.erase(ships.begin() + fromIndex);
971 	ships.insert(ships.begin() + toIndex, ship);
972 	flagship.reset();
973 }
974 
975 
976 
ReorderShips(const set<int> & fromIndices,int toIndex)977 int PlayerInfo::ReorderShips(const set<int> &fromIndices, int toIndex)
978 {
979 	if(fromIndices.empty() || static_cast<unsigned>(toIndex) >= ships.size())
980 		return -1;
981 
982 	// When shifting ships up in the list, move to the desired index. If
983 	// moving down, move after the selected index.
984 	int direction = (*fromIndices.begin() < toIndex) ? 1 : 0;
985 
986 	// Remove the ships from last to first, so that each removal leaves all the
987 	// remaining indices in the set still valid.
988 	vector<shared_ptr<Ship>> removed;
989 	for(set<int>::const_iterator it = fromIndices.end(); it != fromIndices.begin(); )
990 	{
991 		// The "it" pointer doesn't point to the beginning of the list, so it is
992 		// safe to decrement it here.
993 		--it;
994 
995 		// Bail out if any invalid indices are encountered.
996 		if(static_cast<unsigned>(*it) >= ships.size())
997 			return -1;
998 
999 		removed.insert(removed.begin(), ships[*it]);
1000 		ships.erase(ships.begin() + *it);
1001 		// If this index is before the insertion point, removing it causes the
1002 		// insertion point to shift back one space.
1003 		if(*it < toIndex)
1004 			--toIndex;
1005 	}
1006 	// Make sure the insertion index is within the list.
1007 	toIndex = min<int>(toIndex + direction, ships.size());
1008 	ships.insert(ships.begin() + toIndex, removed.begin(), removed.end());
1009 	flagship.reset();
1010 
1011 	return toIndex;
1012 }
1013 
1014 
1015 
1016 // Find out how attractive the player's fleet is to pirates. Aside from a
1017 // heavy freighter, no single ship should attract extra pirate attention.
RaidFleetFactors() const1018 pair<double, double> PlayerInfo::RaidFleetFactors() const
1019 {
1020 	double attraction = 0.;
1021 	double deterrence = 0.;
1022 	for(const shared_ptr<Ship> &ship : Ships())
1023 	{
1024 		if(ship->IsParked() || ship->IsDestroyed())
1025 			continue;
1026 
1027 		attraction += max(0., .4 * sqrt(ship->Attributes().Get("cargo space")) - 1.8);
1028 		for(const Hardpoint &hardpoint : ship->Weapons())
1029 			if(hardpoint.GetOutfit())
1030 			{
1031 				const Outfit *weapon = hardpoint.GetOutfit();
1032 				if(weapon->Ammo() && !ship->OutfitCount(weapon->Ammo()))
1033 					continue;
1034 				double damage = weapon->ShieldDamage() + weapon->HullDamage()
1035 					+ (weapon->RelativeShieldDamage() * ship->Attributes().Get("shields"))
1036 					+ (weapon->RelativeHullDamage() * ship->Attributes().Get("hull"));
1037 				deterrence += .12 * damage / weapon->Reload();
1038 			}
1039 	}
1040 
1041 	return make_pair(attraction, deterrence);
1042 }
1043 
1044 
1045 
1046 // Get cargo information.
Cargo()1047 CargoHold &PlayerInfo::Cargo()
1048 {
1049 	return cargo;
1050 }
1051 
1052 
1053 
1054 // Get cargo information.
Cargo() const1055 const CargoHold &PlayerInfo::Cargo() const
1056 {
1057 	return cargo;
1058 }
1059 
1060 
1061 
1062 // Get planetary storage information for current planet. Returns a pointer,
1063 // since we might not be on a planet, or since the storage might be empty.
Storage(bool forceCreate)1064 CargoHold *PlayerInfo::Storage(bool forceCreate)
1065 {
1066 	if(planet && (forceCreate || planetaryStorage.count(planet)))
1067 		return &(planetaryStorage[planet]);
1068 
1069 	// Nullptr can be returned when forceCreate is true if there is no
1070 	// planet; nullptr is the best we can offer in such cases.
1071 	return nullptr;
1072 }
1073 
1074 
1075 
1076 // Get planetary storage information for all planets (for map and overviews).
PlanetaryStorage() const1077 const std::map<const Planet *, CargoHold> &PlayerInfo::PlanetaryStorage() const
1078 {
1079 	return planetaryStorage;
1080 }
1081 
1082 
1083 
1084 // Adjust the cost basis for the given commodity.
AdjustBasis(const string & commodity,int64_t adjustment)1085 void PlayerInfo::AdjustBasis(const string &commodity, int64_t adjustment)
1086 {
1087 	costBasis[commodity] += adjustment;
1088 }
1089 
1090 
1091 
1092 // Get the cost basis for some number of tons of the given commodity. Each ton
1093 // of the commodity that you own is assumed to have the same basis.
GetBasis(const string & commodity,int tons) const1094 int64_t PlayerInfo::GetBasis(const string &commodity, int tons) const
1095 {
1096 	// Handle cost basis even when not landed on a planet.
1097 	int total = cargo.Get(commodity);
1098 	for(const auto &ship : ships)
1099 		total += ship->Cargo().Get(commodity);
1100 	if(!total)
1101 		return 0;
1102 
1103 	auto it = costBasis.find(commodity);
1104 	int64_t basis = (it == costBasis.end()) ? 0 : it->second;
1105 	return (basis * tons) / total;
1106 }
1107 
1108 
1109 
1110 // Switch cargo from being stored in ships to being stored here. Also recharge
1111 // ships, check for mission completion, and apply fines for contraband.
Land(UI * ui)1112 void PlayerInfo::Land(UI *ui)
1113 {
1114 	// This can only be done while landed.
1115 	if(!system || !planet)
1116 		return;
1117 
1118 	Audio::Play(Audio::Get("landing"));
1119 	Audio::PlayMusic(planet->MusicName());
1120 
1121 	// Mark this planet as visited.
1122 	Visit(*planet);
1123 	if(planet == travelDestination)
1124 		travelDestination = nullptr;
1125 
1126 	// Remove any ships that have been destroyed or captured.
1127 	map<string, int> lostCargo;
1128 	vector<shared_ptr<Ship>>::iterator it = ships.begin();
1129 	while(it != ships.end())
1130 	{
1131 		if((*it)->IsDestroyed() || !(*it)->IsYours())
1132 		{
1133 			// If any of your ships are destroyed, your cargo "cost basis" should
1134 			// be adjusted based on what you lost.
1135 			for(const auto &cargo : (*it)->Cargo().Commodities())
1136 				if(cargo.second)
1137 					lostCargo[cargo.first] += cargo.second;
1138 			// Also, the ship and everything in it should be removed from your
1139 			// depreciation records. Transfer it to a throw-away record:
1140 			Depreciation().Buy(**it, date.DaysSinceEpoch(), &depreciation);
1141 
1142 			it = ships.erase(it);
1143 		}
1144 		else
1145 			++it;
1146 	}
1147 
1148 	// "Unload" all fighters, so they will get recharged, etc.
1149 	for(const shared_ptr<Ship> &ship : ships)
1150 		ship->UnloadBays();
1151 
1152 	// Ships that are landed with you on the planet should fully recharge
1153 	// and pool all their cargo together. Those in remote systems restore
1154 	// what they can without landing.
1155 	bool hasSpaceport = planet->HasSpaceport() && planet->CanUseServices();
1156 	UpdateCargoCapacities();
1157 	for(const shared_ptr<Ship> &ship : ships)
1158 		if(!ship->IsParked() && !ship->IsDisabled())
1159 		{
1160 			if(ship->GetSystem() == system)
1161 			{
1162 				ship->Recharge(hasSpaceport);
1163 				ship->Cargo().TransferAll(cargo);
1164 				if(!ship->GetPlanet())
1165 					ship->SetPlanet(planet);
1166 			}
1167 			else
1168 				ship->Recharge(false);
1169 		}
1170 	// Adjust cargo cost basis for any cargo lost due to a ship being destroyed.
1171 	for(const auto &it : lostCargo)
1172 		AdjustBasis(it.first, -(costBasis[it.first] * it.second) / (cargo.Get(it.first) + it.second));
1173 
1174 	// Bring auto conditions up-to-date for missions to check your current status.
1175 	UpdateAutoConditions();
1176 
1177 	// Evaluate changes to NPC spawning criteria.
1178 	if(!freshlyLoaded)
1179 		UpdateMissionNPCs();
1180 
1181 	// Update missions that are completed, or should be failed.
1182 	StepMissions(ui);
1183 	UpdateCargoCapacities();
1184 
1185 	// If the player is actually landing (rather than simply loading the game),
1186 	// new missions are created and new fines may be levied.
1187 	if(!freshlyLoaded)
1188 	{
1189 		// Create whatever missions this planet has to offer.
1190 		CreateMissions();
1191 		// Check if the player is doing anything illegal.
1192 		Fine(ui);
1193 	}
1194 	// Upon loading the game, prompt the player about any paused missions, but if there are many
1195 	// do not name them all (since this would overflow the screen).
1196 	else if(ui && !inactiveMissions.empty())
1197 	{
1198 		string message = "These active missions or jobs were deactivated due to a missing definition - perhaps you recently removed a plugin?\n";
1199 		auto mit = inactiveMissions.rbegin();
1200 		int named = 0;
1201 		while(mit != inactiveMissions.rend() && (++named < 10))
1202 		{
1203 			message += "\t\"" + mit->Name() + "\"\n";
1204 			++mit;
1205 		}
1206 		if(mit != inactiveMissions.rend())
1207 			message += " and " + to_string(distance(mit, inactiveMissions.rend())) + " more.\n";
1208 		message += "They will be reactivated when the necessary plugin is reinstalled.";
1209 		ui->Push(new Dialog(message));
1210 	}
1211 
1212 	// Hire extra crew back if any were lost in-flight (i.e. boarding) or
1213 	// some bunks were freed up upon landing (i.e. completed missions).
1214 	if(Preferences::Has("Rehire extra crew when lost") && hasSpaceport && flagship)
1215 	{
1216 		int added = desiredCrew - flagship->Crew();
1217 		if(added > 0)
1218 		{
1219 			flagship->AddCrew(added);
1220 			Messages::Add("You hire " + to_string(added) + (added == 1
1221 					? " extra crew member to fill your now-empty bunk."
1222 					: " extra crew members to fill your now-empty bunks."));
1223 		}
1224 	}
1225 
1226 	freshlyLoaded = false;
1227 	flagship.reset();
1228 }
1229 
1230 
1231 
1232 // Load the cargo back into your ships. This may require selling excess, in
1233 // which case a message will be returned.
TakeOff(UI * ui)1234 bool PlayerInfo::TakeOff(UI *ui)
1235 {
1236 	// This can only be done while landed.
1237 	if(!system || !planet)
1238 		return false;
1239 
1240 	if(flagship)
1241 		flagship->AllowCarried(true);
1242 	flagship.reset();
1243 	flagship = FlagshipPtr();
1244 	if(!flagship)
1245 		return false;
1246 	flagship->AllowCarried(false);
1247 
1248 	shouldLaunch = false;
1249 	Audio::Play(Audio::Get("takeoff"));
1250 
1251 	// Jobs are only available when you are landed.
1252 	availableJobs.clear();
1253 	availableMissions.clear();
1254 	doneMissions.clear();
1255 	stock.clear();
1256 
1257 	// Special persons who appeared last time you left the planet, can appear again.
1258 	GameData::ResetPersons();
1259 
1260 	// Store the total cargo counts in case we need to adjust cost bases below.
1261 	map<string, int> originalTotals = cargo.Commodities();
1262 
1263 	// Move the flagship to the start of your list of ships. It does not make
1264 	// sense that the flagship would change if you are reunited with a different
1265 	// ship that was higher up the list.
1266 	auto it = find(ships.begin(), ships.end(), flagship);
1267 	if(it != ships.begin() && it != ships.end())
1268 	{
1269 		ships.erase(it);
1270 		ships.insert(ships.begin(), flagship);
1271 	}
1272 	// Make sure your jump-capable ships all know who the flagship is.
1273 	for(const shared_ptr<Ship> &ship : ships)
1274 	{
1275 		bool shouldHaveParent = (ship != flagship && !ship->IsParked() && (!ship->CanBeCarried() || ship->JumpFuel()));
1276 		ship->SetParent(shouldHaveParent ? flagship : shared_ptr<Ship>());
1277 	}
1278 	// Make sure your flagship is not included in the escort selection.
1279 	for(auto it = selectedShips.begin(); it != selectedShips.end(); )
1280 	{
1281 		shared_ptr<Ship> ship = it->lock();
1282 		if(!ship || ship == flagship)
1283 			it = selectedShips.erase(it);
1284 		else
1285 			++it;
1286 	}
1287 
1288 	// Recharge any ships that can be recharged, and load available cargo.
1289 	bool hasSpaceport = planet->HasSpaceport() && planet->CanUseServices();
1290 	for(const shared_ptr<Ship> &ship : ships)
1291 		if(!ship->IsParked() && !ship->IsDisabled())
1292 		{
1293 			if(ship->GetSystem() != system)
1294 			{
1295 				ship->Recharge(false);
1296 				continue;
1297 			}
1298 			else
1299 				ship->Recharge(hasSpaceport);
1300 
1301 			if(ship != flagship)
1302 			{
1303 				ship->Cargo().SetBunks(ship->Attributes().Get("bunks") - ship->RequiredCrew());
1304 				cargo.TransferAll(ship->Cargo());
1305 			}
1306 			else
1307 			{
1308 				// Your flagship takes first priority for passengers but last for cargo.
1309 				desiredCrew = ship->Crew();
1310 				ship->Cargo().SetBunks(ship->Attributes().Get("bunks") - desiredCrew);
1311 				for(const auto &it : cargo.PassengerList())
1312 					cargo.TransferPassengers(it.first, it.second, ship->Cargo());
1313 			}
1314 		}
1315 	// Load up your flagship last, so that it will have space free for any
1316 	// plunder that you happen to acquire.
1317 	cargo.TransferAll(flagship->Cargo());
1318 
1319 	if(cargo.Passengers())
1320 	{
1321 		int extra = min(cargo.Passengers(), flagship->Crew() - flagship->RequiredCrew());
1322 		if(extra)
1323 		{
1324 			flagship->AddCrew(-extra);
1325 			Messages::Add("You fired " + to_string(extra) + " crew members to free up bunks for passengers.");
1326 			flagship->Cargo().SetBunks(flagship->Attributes().Get("bunks") - flagship->Crew());
1327 			cargo.TransferAll(flagship->Cargo());
1328 		}
1329 	}
1330 
1331 	int extra = flagship->Crew() + flagship->Cargo().Passengers() - flagship->Attributes().Get("bunks");
1332 	if(extra > 0)
1333 	{
1334 		flagship->AddCrew(-extra);
1335 		Messages::Add("You fired " + to_string(extra) + " crew members because you have no bunks for them.");
1336 		flagship->Cargo().SetBunks(flagship->Attributes().Get("bunks") - flagship->Crew());
1337 	}
1338 
1339 	// For each active, carriable ship you own, try to find an active ship that has a bay for it.
1340 	auto carriers = vector<Ship *>{};
1341 	auto toLoad = vector<shared_ptr<Ship>>{};
1342 	for(auto &ship : ships)
1343 		if(!ship->IsParked() && !ship->IsDisabled())
1344 		{
1345 			if(ship->CanBeCarried())
1346 				toLoad.emplace_back(ship);
1347 			else if(ship->HasBays())
1348 				carriers.emplace_back(ship.get());
1349 		}
1350 	if(!toLoad.empty())
1351 	{
1352 		size_t uncarried = toLoad.size();
1353 		if(!carriers.empty())
1354 		{
1355 			// Order carried ships such that those requiring bays are loaded first. For
1356 			// jump-capable carried ships, prefer loading those with a shorter range.
1357 			stable_sort(toLoad.begin(), toLoad.end(),
1358 				[](const shared_ptr<Ship> &a, const shared_ptr<Ship> &b)
1359 				{
1360 					return a->JumpsRemaining() < b->JumpsRemaining();
1361 				});
1362 			// We are guaranteed that each carried `ship` is not parked and not disabled, and that
1363 			// all possible parents are also not parked, not disabled, and not `ship`.
1364 			for(auto &ship : toLoad)
1365 				for(auto &parent : carriers)
1366 					if(parent->GetSystem() == ship->GetSystem() && parent->Carry(ship))
1367 					{
1368 						--uncarried;
1369 						break;
1370 					}
1371 		}
1372 
1373 		if(uncarried)
1374 		{
1375 			// The remaining uncarried ships are launched alongside the player.
1376 			string message = (uncarried > 1) ? "Some escorts were" : "One escort was";
1377 			Messages::Add(message + " unable to dock with a carrier.");
1378 		}
1379 	}
1380 
1381 	// By now, all cargo should have been divvied up among your ships. So, any
1382 	// mission cargo or passengers left behind cannot be carried, and those
1383 	// missions have failed.
1384 	vector<const Mission *> missionsToRemove;
1385 	for(const auto &it : cargo.MissionCargo())
1386 		if(it.second)
1387 		{
1388 			if(it.first->IsVisible())
1389 				Messages::Add("Mission \"" + it.first->Name()
1390 					+ "\" failed because you do not have space for the cargo.");
1391 			missionsToRemove.push_back(it.first);
1392 		}
1393 	for(const auto &it : cargo.PassengerList())
1394 		if(it.second)
1395 		{
1396 			if(it.first->IsVisible())
1397 				Messages::Add("Mission \"" + it.first->Name()
1398 					+ "\" failed because you do not have enough passenger bunks free.");
1399 			missionsToRemove.push_back(it.first);
1400 
1401 		}
1402 	for(const Mission *mission : missionsToRemove)
1403 		RemoveMission(Mission::FAIL, *mission, ui);
1404 
1405 	// Any ordinary cargo left behind can be sold.
1406 	int64_t income = 0;
1407 	int day = date.DaysSinceEpoch();
1408 	int64_t sold = cargo.Used();
1409 	int64_t totalBasis = 0;
1410 	if(sold)
1411 	{
1412 		for(const auto &commodity : cargo.Commodities())
1413 		{
1414 			if(!commodity.second)
1415 				continue;
1416 
1417 			// Figure out how much income you get for selling this cargo.
1418 			int64_t value = commodity.second * static_cast<int64_t>(system->Trade(commodity.first));
1419 			income += value;
1420 
1421 			int original = originalTotals[commodity.first];
1422 			auto it = costBasis.find(commodity.first);
1423 			if(!original || it == costBasis.end() || !it->second)
1424 				continue;
1425 
1426 			// Now, figure out how much of that income is profit by calculating
1427 			// the cost basis for this cargo (which is just the total cost basis
1428 			// multiplied by the percent of the cargo you are selling).
1429 			int64_t basis = it->second * commodity.second / original;
1430 			it->second -= basis;
1431 			totalBasis += basis;
1432 		}
1433 		for(const auto &outfit : cargo.Outfits())
1434 		{
1435 			// Compute the total value for each type of excess outfit.
1436 			if(!outfit.second)
1437 				continue;
1438 			int64_t cost = depreciation.Value(outfit.first, day, outfit.second);
1439 			for(int i = 0; i < outfit.second; ++i)
1440 				stockDepreciation.Buy(outfit.first, day, &depreciation);
1441 			income += cost;
1442 		}
1443 	}
1444 	accounts.AddCredits(income);
1445 	cargo.Clear();
1446 	stockDepreciation = Depreciation();
1447 	if(sold)
1448 	{
1449 		// Report how much excess cargo was sold, and what profit you earned.
1450 		ostringstream out;
1451 		out << "You sold " << sold << " tons of excess cargo for " << Format::Credits(income) << " credits";
1452 		if(totalBasis && totalBasis != income)
1453 			out << " (for a profit of " << (income - totalBasis) << " credits).";
1454 		else
1455 			out << ".";
1456 		Messages::Add(out.str());
1457 	}
1458 
1459 	return true;
1460 }
1461 
1462 
1463 
AddPlayTime(chrono::nanoseconds timeVal)1464 void PlayerInfo::AddPlayTime(chrono::nanoseconds timeVal)
1465 {
1466 	playTime += timeVal.count() * .000000001;
1467 }
1468 
1469 
1470 
GetPlayTime() const1471 double PlayerInfo::GetPlayTime() const noexcept
1472 {
1473 	return playTime;
1474 }
1475 
1476 
1477 
1478 // Get the player's logbook.
Logbook() const1479 const multimap<Date, string> &PlayerInfo::Logbook() const
1480 {
1481 	return logbook;
1482 }
1483 
1484 
1485 
AddLogEntry(const string & text)1486 void PlayerInfo::AddLogEntry(const string &text)
1487 {
1488 	logbook.emplace(date, text);
1489 }
1490 
1491 
1492 
SpecialLogs() const1493 const map<string, map<string, string>> &PlayerInfo::SpecialLogs() const
1494 {
1495 	return specialLogs;
1496 }
1497 
1498 
1499 
AddSpecialLog(const string & type,const string & name,const string & text)1500 void PlayerInfo::AddSpecialLog(const string &type, const string &name, const string &text)
1501 {
1502 	string &entry = specialLogs[type][name];
1503 	if(!entry.empty())
1504 		entry += "\n\t";
1505 	entry += text;
1506 }
1507 
1508 
1509 
HasLogs() const1510 bool PlayerInfo::HasLogs() const
1511 {
1512 	return !logbook.empty() || !specialLogs.empty();
1513 }
1514 
1515 
1516 
1517 // Call this after missions update, or if leaving the outfitter, shipyard, or
1518 // hiring panel. Updates the information on how much space is available.
UpdateCargoCapacities()1519 void PlayerInfo::UpdateCargoCapacities()
1520 {
1521 	int size = 0;
1522 	int bunks = 0;
1523 	flagship = FlagshipPtr();
1524 	for(const shared_ptr<Ship> &ship : ships)
1525 		if(ship->GetSystem() == system && !ship->IsParked() && !ship->IsDisabled())
1526 		{
1527 			size += ship->Attributes().Get("cargo space");
1528 			int crew = (ship == flagship ? ship->Crew() : ship->RequiredCrew());
1529 			bunks += ship->Attributes().Get("bunks") - crew;
1530 		}
1531 	cargo.SetSize(size);
1532 	cargo.SetBunks(bunks);
1533 }
1534 
1535 
1536 
1537 // Get the list of active missions.
Missions() const1538 const list<Mission> &PlayerInfo::Missions() const
1539 {
1540 	return missions;
1541 }
1542 
1543 
1544 
1545 // Get the list of ordinary jobs that are available on the job board.
AvailableJobs() const1546 const list<Mission> &PlayerInfo::AvailableJobs() const
1547 {
1548 	return availableJobs;
1549 }
1550 
1551 
1552 
1553 // Return a pointer to the mission that was most recently accepted while in-flight.
ActiveBoardingMission() const1554 const Mission *PlayerInfo::ActiveBoardingMission() const
1555 {
1556 	return activeBoardingMission;
1557 }
1558 
1559 
1560 
1561 // Update mission NPCs with the player's current conditions.
UpdateMissionNPCs()1562 void PlayerInfo::UpdateMissionNPCs()
1563 {
1564 	for(Mission &mission : missions)
1565 		mission.UpdateNPCs(*this);
1566 }
1567 
1568 
1569 
1570 // Accept the given job.
AcceptJob(const Mission & mission,UI * ui)1571 void PlayerInfo::AcceptJob(const Mission &mission, UI *ui)
1572 {
1573 	for(auto it = availableJobs.begin(); it != availableJobs.end(); ++it)
1574 		if(&*it == &mission)
1575 		{
1576 			cargo.AddMissionCargo(&mission);
1577 			it->Do(Mission::OFFER, *this);
1578 			it->Do(Mission::ACCEPT, *this, ui);
1579 			auto spliceIt = it->IsUnique() ? missions.begin() : missions.end();
1580 			missions.splice(spliceIt, availableJobs, it);
1581 			break;
1582 		}
1583 }
1584 
1585 
1586 
1587 // Look at the list of available missions and see if any of them can be offered
1588 // right now, in the given location (landing or spaceport). If there are no
1589 // missions that can be accepted, return a null pointer.
MissionToOffer(Mission::Location location)1590 Mission *PlayerInfo::MissionToOffer(Mission::Location location)
1591 {
1592 	if(ships.empty())
1593 		return nullptr;
1594 
1595 	// If a mission can be offered right now, move it to the start of the list
1596 	// so we know what mission the callback is referring to, and return it.
1597 	for(auto it = availableMissions.begin(); it != availableMissions.end(); ++it)
1598 		if(it->IsAtLocation(location) && it->CanOffer(*this) && it->HasSpace(*this))
1599 		{
1600 			availableMissions.splice(availableMissions.begin(), availableMissions, it);
1601 			return &availableMissions.front();
1602 		}
1603 	return nullptr;
1604 }
1605 
1606 
1607 
1608 // Check if any of the game's missions can be offered from this ship, given its
1609 // relationship with the player. If none offer, return nullptr.
BoardingMission(const shared_ptr<Ship> & ship)1610 Mission *PlayerInfo::BoardingMission(const shared_ptr<Ship> &ship)
1611 {
1612 	// Do not create missions from existing mission NPC's, or the player's ships.
1613 	if(ship->IsSpecial())
1614 		return nullptr;
1615 	// Ensure that boarding this NPC again does not create a mission.
1616 	ship->SetIsSpecial();
1617 
1618 	// Update auto conditions to reflect the player's flagship's free capacity.
1619 	UpdateAutoConditions(true);
1620 	// "boardingMissions" is emptied by MissionCallback, but to be sure:
1621 	boardingMissions.clear();
1622 
1623 	Mission::Location location = (ship->GetGovernment()->IsEnemy()
1624 			? Mission::BOARDING : Mission::ASSISTING);
1625 
1626 	// Check for available boarding or assisting missions.
1627 	for(const auto &it : GameData::Missions())
1628 		if(it.second.IsAtLocation(location) && it.second.CanOffer(*this, ship))
1629 		{
1630 			boardingMissions.push_back(it.second.Instantiate(*this, ship));
1631 			if(boardingMissions.back().HasFailed(*this))
1632 				boardingMissions.pop_back();
1633 			else
1634 				return &boardingMissions.back();
1635 		}
1636 
1637 	return nullptr;
1638 }
1639 
1640 
1641 
1642 // Engine calls this after placing the boarding mission's NPCs.
ClearActiveBoardingMission()1643 void PlayerInfo::ClearActiveBoardingMission()
1644 {
1645 	activeBoardingMission = nullptr;
1646 }
1647 
1648 
1649 
1650 // If one of your missions cannot be offered because you do not have enough
1651 // space for it, and it specifies a message to be shown in that situation,
1652 // show that message.
HandleBlockedMissions(Mission::Location location,UI * ui)1653 void PlayerInfo::HandleBlockedMissions(Mission::Location location, UI *ui)
1654 {
1655 	list<Mission> &missionList = availableMissions.empty() ? boardingMissions : availableMissions;
1656 	if(ships.empty() || missionList.empty())
1657 		return;
1658 
1659 	for(auto it = missionList.begin(); it != missionList.end(); ++it)
1660 		if(it->IsAtLocation(location) && it->CanOffer(*this) && !it->HasSpace(*this))
1661 		{
1662 			string message = it->BlockedMessage(*this);
1663 			if(!message.empty())
1664 			{
1665 				ui->Push(new Dialog(message));
1666 				return;
1667 			}
1668 		}
1669 }
1670 
1671 
1672 
1673 // Callback for accepting or declining whatever mission has been offered.
1674 // Responses which would kill the player are handled before the on offer
1675 // conversation ended.
MissionCallback(int response)1676 void PlayerInfo::MissionCallback(int response)
1677 {
1678 	list<Mission> &missionList = availableMissions.empty() ? boardingMissions : availableMissions;
1679 	if(missionList.empty())
1680 		return;
1681 
1682 	Mission &mission = missionList.front();
1683 
1684 	// If landed, this conversation may require the player to immediately depart.
1685 	shouldLaunch |= (GetPlanet() && Conversation::RequiresLaunch(response));
1686 	if(response == Conversation::ACCEPT || response == Conversation::LAUNCH)
1687 	{
1688 		bool shouldAutosave = mission.RecommendsAutosave();
1689 		if(planet)
1690 		{
1691 			cargo.AddMissionCargo(&mission);
1692 			UpdateCargoCapacities();
1693 		}
1694 		else if(Flagship())
1695 			flagship->Cargo().AddMissionCargo(&mission);
1696 		else
1697 			return;
1698 
1699 		// Move this mission from the offering list into the "accepted"
1700 		// list, viewable on the MissionPanel. Unique missions are moved
1701 		// to the front, so they appear at the top of the list if viewed.
1702 		auto spliceIt = mission.IsUnique() ? missions.begin() : missions.end();
1703 		missions.splice(spliceIt, missionList, missionList.begin());
1704 		mission.Do(Mission::ACCEPT, *this);
1705 		if(shouldAutosave)
1706 			Autosave();
1707 		// If this is a mission offered in-flight, expose a pointer to it
1708 		// so Engine::SpawnFleets can add its ships without requiring the
1709 		// player to land.
1710 		if(mission.IsAtLocation(Mission::BOARDING) || mission.IsAtLocation(Mission::ASSISTING))
1711 			activeBoardingMission = &*--spliceIt;
1712 	}
1713 	else if(response == Conversation::DECLINE || response == Conversation::FLEE)
1714 	{
1715 		mission.Do(Mission::DECLINE, *this);
1716 		missionList.pop_front();
1717 	}
1718 	else if(response == Conversation::DEFER || response == Conversation::DEPART)
1719 	{
1720 		mission.Do(Mission::DEFER, *this);
1721 		missionList.pop_front();
1722 	}
1723 }
1724 
1725 
1726 
1727 // Basic callback, allowing conversations to force the player to depart from a
1728 // planet without requiring a mission to offer.
BasicCallback(int response)1729 void PlayerInfo::BasicCallback(int response)
1730 {
1731 	// If landed, this conversation may require the player to immediately depart.
1732 	shouldLaunch |= (GetPlanet() && Conversation::RequiresLaunch(response));
1733 }
1734 
1735 
1736 
1737 // Mark a mission for removal, either because it was completed, or it failed,
1738 // or because the player aborted it.
RemoveMission(Mission::Trigger trigger,const Mission & mission,UI * ui)1739 void PlayerInfo::RemoveMission(Mission::Trigger trigger, const Mission &mission, UI *ui)
1740 {
1741 	for(auto it = missions.begin(); it != missions.end(); ++it)
1742 		if(&*it == &mission)
1743 		{
1744 			// Don't delete the mission yet, because the conversation or dialog
1745 			// panel may still be showing. Instead, just mark it as done. Doing
1746 			// this first avoids the possibility of an infinite loop, e.g. if a
1747 			// mission's "on fail" fails the mission itself.
1748 			doneMissions.splice(doneMissions.end(), missions, it);
1749 
1750 			it->Do(trigger, *this, ui);
1751 			cargo.RemoveMissionCargo(&mission);
1752 			for(shared_ptr<Ship> &ship : ships)
1753 				ship->Cargo().RemoveMissionCargo(&mission);
1754 			return;
1755 		}
1756 }
1757 
1758 
1759 
1760 // Mark a mission as failed, but do not remove it from the mission list yet.
FailMission(const Mission & mission)1761 void PlayerInfo::FailMission(const Mission &mission)
1762 {
1763 	for(auto it = missions.begin(); it != missions.end(); ++it)
1764 		if(&*it == &mission)
1765 		{
1766 			it->Fail();
1767 			return;
1768 		}
1769 }
1770 
1771 
1772 
1773 // Update mission status based on an event.
HandleEvent(const ShipEvent & event,UI * ui)1774 void PlayerInfo::HandleEvent(const ShipEvent &event, UI *ui)
1775 {
1776 	// Combat rating increases when you disable an enemy ship.
1777 	if(event.ActorGovernment() && event.ActorGovernment()->IsPlayer())
1778 		if((event.Type() & ShipEvent::DISABLE) && event.Target() && !event.Target()->IsYours())
1779 		{
1780 			auto &rating = conditions["combat rating"];
1781 			static const int64_t maxRating = 2000000000;
1782 			rating = min(maxRating, rating + (event.Target()->Cost() + 250000) / 500000);
1783 		}
1784 
1785 	for(Mission &mission : missions)
1786 		mission.Do(event, *this, ui);
1787 
1788 	// If the player's flagship was destroyed, the player is dead.
1789 	if((event.Type() & ShipEvent::DESTROY) && !ships.empty() && event.Target().get() == Flagship())
1790 		Die();
1791 }
1792 
1793 
1794 
1795 // Get the value of the given condition (default 0).
GetCondition(const string & name) const1796 int64_t PlayerInfo::GetCondition(const string &name) const
1797 {
1798 	auto it = conditions.find(name);
1799 	return (it == conditions.end()) ? 0 : it->second;
1800 }
1801 
1802 
1803 
1804 // Get mutable access to the player's list of conditions.
Conditions()1805 map<string, int64_t> &PlayerInfo::Conditions()
1806 {
1807 	return conditions;
1808 }
1809 
1810 
1811 
1812 // Access the player's list of conditions.
Conditions() const1813 const map<string, int64_t> &PlayerInfo::Conditions() const
1814 {
1815 	return conditions;
1816 }
1817 
1818 
1819 
1820 // Set and check the reputation conditions, which missions and events can use to
1821 // modify the player's reputation with other governments.
SetReputationConditions()1822 void PlayerInfo::SetReputationConditions()
1823 {
1824 	for(const auto &it : GameData::Governments())
1825 	{
1826 		int64_t rep = it.second.Reputation();
1827 		conditions["reputation: " + it.first] = rep;
1828 	}
1829 }
1830 
1831 
1832 
CheckReputationConditions()1833 void PlayerInfo::CheckReputationConditions()
1834 {
1835 	for(const auto &it : GameData::Governments())
1836 	{
1837 		int64_t rep = it.second.Reputation();
1838 		int64_t newRep = conditions["reputation: " + it.first];
1839 		if(newRep != rep)
1840 			it.second.AddReputation(newRep - rep);
1841 	}
1842 }
1843 
1844 
1845 
1846 // Check if the player knows the location of the given system (whether or not
1847 // they have actually visited it).
HasSeen(const System & system) const1848 bool PlayerInfo::HasSeen(const System &system) const
1849 {
1850 	if(seen.count(&system))
1851 		return true;
1852 
1853 	auto usesSystem = [&system](const Mission &m) noexcept -> bool
1854 	{
1855 		if(!m.IsVisible())
1856 			return false;
1857 		if(m.Waypoints().count(&system))
1858 			return true;
1859 		for(auto &&p : m.Stopovers())
1860 			if(p->IsInSystem(&system))
1861 				return true;
1862 		return false;
1863 	};
1864 	if(any_of(availableJobs.begin(), availableJobs.end(), usesSystem))
1865 		return true;
1866 	if(any_of(missions.begin(), missions.end(), usesSystem))
1867 		return true;
1868 
1869 	return KnowsName(system);
1870 }
1871 
1872 
1873 
1874 // Check if the player has visited the given system.
HasVisited(const System & system) const1875 bool PlayerInfo::HasVisited(const System &system) const
1876 {
1877 	return visitedSystems.count(&system);
1878 }
1879 
1880 
1881 
1882 // Check if the player has visited the given system.
HasVisited(const Planet & planet) const1883 bool PlayerInfo::HasVisited(const Planet &planet) const
1884 {
1885 	return visitedPlanets.count(&planet);
1886 }
1887 
1888 
1889 
1890 // Check if the player knows the name of a system, either from visiting there or
1891 // because a job or active mission includes the name of that system.
KnowsName(const System & system) const1892 bool PlayerInfo::KnowsName(const System &system) const
1893 {
1894 	if(HasVisited(system))
1895 		return true;
1896 
1897 	for(const Mission &mission : availableJobs)
1898 		if(mission.Destination()->IsInSystem(&system))
1899 			return true;
1900 
1901 	for(const Mission &mission : missions)
1902 		if(mission.IsVisible() && mission.Destination()->IsInSystem(&system))
1903 			return true;
1904 
1905 	return false;
1906 }
1907 
1908 
1909 // Mark the given system as visited, and mark all its neighbors as seen.
Visit(const System & system)1910 void PlayerInfo::Visit(const System &system)
1911 {
1912 	visitedSystems.insert(&system);
1913 	seen.insert(&system);
1914 	for(const System *neighbor : system.VisibleNeighbors())
1915 		if(!neighbor->Hidden() || system.Links().count(neighbor))
1916 			seen.insert(neighbor);
1917 }
1918 
1919 
1920 
1921 // Mark the given planet as visited.
Visit(const Planet & planet)1922 void PlayerInfo::Visit(const Planet &planet)
1923 {
1924 	visitedPlanets.insert(&planet);
1925 }
1926 
1927 
1928 
1929 // Mark a system as unvisited, even if visited previously.
Unvisit(const System & system)1930 void PlayerInfo::Unvisit(const System &system)
1931 {
1932 	visitedSystems.erase(&system);
1933 	for(const StellarObject &object : system.Objects())
1934 		if(object.GetPlanet())
1935 			Unvisit(*object.GetPlanet());
1936 }
1937 
1938 
1939 
Unvisit(const Planet & planet)1940 void PlayerInfo::Unvisit(const Planet &planet)
1941 {
1942 	visitedPlanets.erase(&planet);
1943 }
1944 
1945 
1946 
1947 // Check if the player has a hyperspace route set.
HasTravelPlan() const1948 bool PlayerInfo::HasTravelPlan() const
1949 {
1950 	return !travelPlan.empty();
1951 }
1952 
1953 
1954 
1955 // Access the player's travel plan.
TravelPlan() const1956 const vector<const System *> &PlayerInfo::TravelPlan() const
1957 {
1958 	return travelPlan;
1959 }
1960 
1961 
1962 
TravelPlan()1963 vector<const System *> &PlayerInfo::TravelPlan()
1964 {
1965 	return travelPlan;
1966 }
1967 
1968 
1969 
1970 // This is called when the player enters the system that is their current
1971 // hyperspace target.
PopTravel()1972 void PlayerInfo::PopTravel()
1973 {
1974 	if(!travelPlan.empty())
1975 	{
1976 		Visit(*travelPlan.back());
1977 		travelPlan.pop_back();
1978 	}
1979 }
1980 
1981 
1982 
1983 // Get the planet to land on at the end of the travel path.
TravelDestination() const1984 const Planet *PlayerInfo::TravelDestination() const
1985 {
1986 	return travelDestination;
1987 }
1988 
1989 
1990 
1991 // Set the planet to land on at the end of the travel path.
SetTravelDestination(const Planet * planet)1992 void PlayerInfo::SetTravelDestination(const Planet *planet)
1993 {
1994 	travelDestination = planet;
1995 	if(planet && planet->IsInSystem(system) && Flagship())
1996 		Flagship()->SetTargetStellar(system->FindStellar(planet));
1997 }
1998 
1999 
2000 
2001 // Check which secondary weapon the player has selected.
SelectedWeapon() const2002 const Outfit *PlayerInfo::SelectedWeapon() const
2003 {
2004 	return selectedWeapon;
2005 }
2006 
2007 
2008 
2009 // Cycle through all available secondary weapons.
SelectNext()2010 void PlayerInfo::SelectNext()
2011 {
2012 	if(!flagship || flagship->Outfits().empty())
2013 		return;
2014 
2015 	// Start with the currently selected weapon, if any.
2016 	auto it = flagship->Outfits().find(selectedWeapon);
2017 	if(it == flagship->Outfits().end())
2018 		it = flagship->Outfits().begin();
2019 	else
2020 		++it;
2021 
2022 	// Find the next secondary weapon.
2023 	for( ; it != flagship->Outfits().end(); ++it)
2024 		if(it->first->Icon())
2025 		{
2026 			selectedWeapon = it->first;
2027 			return;
2028 		}
2029 	selectedWeapon = nullptr;
2030 }
2031 
2032 
2033 
2034 // Escorts currently selected for giving orders.
SelectedShips() const2035 const vector<weak_ptr<Ship>> &PlayerInfo::SelectedShips() const
2036 {
2037 	return selectedShips;
2038 }
2039 
2040 
2041 
2042 // Select any player ships in the given box or list. Return true if any were
2043 // selected, so we know not to search further for a match.
SelectShips(const Rectangle & box,bool hasShift)2044 bool PlayerInfo::SelectShips(const Rectangle &box, bool hasShift)
2045 {
2046 	// If shift is not held down, replace the current selection.
2047 	if(!hasShift)
2048 		selectedShips.clear();
2049 	// If shift is not held, the first ship in the box will also become the
2050 	// player's flagship's target.
2051 	bool first = !hasShift;
2052 
2053 	bool matched = false;
2054 	for(const shared_ptr<Ship> &ship : ships)
2055 		if(!ship->IsDestroyed() && !ship->IsParked() && ship->GetSystem() == system && ship.get() != Flagship()
2056 				&& box.Contains(ship->Position()))
2057 		{
2058 			matched = true;
2059 			SelectShip(ship, &first);
2060 		}
2061 	return matched;
2062 }
2063 
2064 
2065 
SelectShips(const vector<const Ship * > & stack,bool hasShift)2066 bool PlayerInfo::SelectShips(const vector<const Ship *> &stack, bool hasShift)
2067 {
2068 	// If shift is not held down, replace the current selection.
2069 	if(!hasShift)
2070 		selectedShips.clear();
2071 	// If shift is not held, the first ship in the stack will also become the
2072 	// player's flagship's target.
2073 	bool first = !hasShift;
2074 
2075 	// Loop through all the player's ships and check which of them are in the
2076 	// given stack.
2077 	bool matched = false;
2078 	for(const shared_ptr<Ship> &ship : ships)
2079 	{
2080 		auto it = find(stack.begin(), stack.end(), ship.get());
2081 		if(it != stack.end())
2082 		{
2083 			matched = true;
2084 			SelectShip(ship, &first);
2085 		}
2086 	}
2087 	return matched;
2088 }
2089 
2090 
2091 
SelectShip(const Ship * ship,bool hasShift)2092 void PlayerInfo::SelectShip(const Ship *ship, bool hasShift)
2093 {
2094 	// If shift is not held down, replace the current selection.
2095 	if(!hasShift)
2096 		selectedShips.clear();
2097 
2098 	bool first = !hasShift;
2099 	for(const shared_ptr<Ship> &it : ships)
2100 		if(it.get() == ship)
2101 			SelectShip(it, &first);
2102 }
2103 
2104 
2105 
SelectGroup(int group,bool hasShift)2106 void PlayerInfo::SelectGroup(int group, bool hasShift)
2107 {
2108 	int bit = (1 << group);
2109 	// If the shift key is held down and all the ships in the given group are
2110 	// already selected, deselect them all. Otherwise, select them all. The easy
2111 	// way to do this is first to remove all the ships that match in one pass,
2112 	// then add them in a subsequent pass if any were not selected.
2113 	const Ship *oldTarget = nullptr;
2114 	if(Flagship() && Flagship()->GetTargetShip())
2115 	{
2116 		oldTarget = Flagship()->GetTargetShip().get();
2117 		Flagship()->SetTargetShip(shared_ptr<Ship>());
2118 	}
2119 	if(hasShift)
2120 	{
2121 		bool allWereSelected = true;
2122 		for(const shared_ptr<Ship> &ship : ships)
2123 			if(groups[ship.get()] & bit)
2124 			{
2125 				auto it = selectedShips.begin();
2126 				for( ; it != selectedShips.end(); ++it)
2127 					if(it->lock() == ship)
2128 						break;
2129 				if(it != selectedShips.end())
2130 					selectedShips.erase(it);
2131 				else
2132 					allWereSelected = false;
2133 			}
2134 		if(allWereSelected)
2135 			return;
2136 	}
2137 	else
2138 		selectedShips.clear();
2139 
2140 	// Now, go through and add any ships in the group to the selection. Even if
2141 	// shift is held they won't be added twice, because we removed them above.
2142 	for(const shared_ptr<Ship> &ship : ships)
2143 		if(groups[ship.get()] & bit)
2144 		{
2145 			selectedShips.push_back(ship);
2146 			if(ship.get() == oldTarget)
2147 				Flagship()->SetTargetShip(ship);
2148 		}
2149 }
2150 
2151 
2152 
SetGroup(int group,const set<Ship * > * newShips)2153 void PlayerInfo::SetGroup(int group, const set<Ship *> *newShips)
2154 {
2155 	int bit = (1 << group);
2156 	int mask = ~bit;
2157 	// First, remove any of your ships that are in the group.
2158 	for(const shared_ptr<Ship> &ship : ships)
2159 		groups[ship.get()] &= mask;
2160 	// Then, add all the currently selected ships to the group.
2161 	if(newShips)
2162 	{
2163 		for(const Ship *ship : *newShips)
2164 			groups[ship] |= bit;
2165 	}
2166 	else
2167 	{
2168 		for(const weak_ptr<Ship> &ptr : selectedShips)
2169 		{
2170 			shared_ptr<Ship> ship = ptr.lock();
2171 			if(ship)
2172 				groups[ship.get()] |= bit;
2173 		}
2174 	}
2175 }
2176 
2177 
2178 
GetGroup(int group)2179 set<Ship *> PlayerInfo::GetGroup(int group)
2180 {
2181 	int bit = (1 << group);
2182 	set<Ship *> result;
2183 
2184 	for(const shared_ptr<Ship> &ship : ships)
2185 	{
2186 		auto it = groups.find(ship.get());
2187 		if(it != groups.end() && (it->second & bit))
2188 			result.insert(ship.get());
2189 	}
2190 	return result;
2191 }
2192 
2193 
2194 
2195 // Keep track of any outfits that you have sold since landing. These will be
2196 // available to buy back until you take off.
Stock(const Outfit * outfit) const2197 int PlayerInfo::Stock(const Outfit *outfit) const
2198 {
2199 	auto it = stock.find(outfit);
2200 	return (it == stock.end() ? 0 : it->second);
2201 }
2202 
2203 
2204 
2205 // Transfer outfits from the player to the planet or vice versa.
AddStock(const Outfit * outfit,int count)2206 void PlayerInfo::AddStock(const Outfit *outfit, int count)
2207 {
2208 	// If you sell an individual outfit that is not sold here and that you
2209 	// acquired by buying a ship here, have it appear as "in stock" in case you
2210 	// change your mind about selling it. (On the other hand, if you sell an
2211 	// entire ship right after buying it, its outfits will not be "in stock.")
2212 	if(count > 0 && stock[outfit] < 0)
2213 		stock[outfit] = 0;
2214 	stock[outfit] += count;
2215 
2216 	int day = date.DaysSinceEpoch();
2217 	if(count > 0)
2218 	{
2219 		// Remember how depreciated these items are.
2220 		for(int i = 0; i < count; ++i)
2221 			stockDepreciation.Buy(outfit, day, &depreciation);
2222 	}
2223 	else
2224 	{
2225 		// If the count is negative, outfits are being transferred from stock
2226 		// into the player's possession.
2227 		for(int i = 0; i < -count; ++i)
2228 			depreciation.Buy(outfit, day, &stockDepreciation);
2229 	}
2230 }
2231 
2232 
2233 
2234 // Get depreciation information.
FleetDepreciation() const2235 const Depreciation &PlayerInfo::FleetDepreciation() const
2236 {
2237 	return depreciation;
2238 }
2239 
2240 
2241 
StockDepreciation() const2242 const Depreciation &PlayerInfo::StockDepreciation() const
2243 {
2244 	return stockDepreciation;
2245 }
2246 
2247 
2248 
Harvest(const Outfit * type)2249 void PlayerInfo::Harvest(const Outfit *type)
2250 {
2251 	if(type && system)
2252 		harvested.insert(make_pair(system, type));
2253 }
2254 
2255 
2256 
Harvested() const2257 const set<pair<const System *, const Outfit *>> &PlayerInfo::Harvested() const
2258 {
2259 	return harvested;
2260 }
2261 
2262 
2263 
GetEscortDestination() const2264 const pair<const System *, Point> &PlayerInfo::GetEscortDestination() const
2265 {
2266 	return interstellarEscortDestination;
2267 }
2268 
2269 
2270 
2271 // Determine if a system and nonzero position were specified.
HasEscortDestination() const2272 bool PlayerInfo::HasEscortDestination() const
2273 {
2274 	return interstellarEscortDestination.first && interstellarEscortDestination.second;
2275 }
2276 
2277 
2278 
2279 // Set (or clear) the stored escort travel destination.
SetEscortDestination(const System * system,Point pos)2280 void PlayerInfo::SetEscortDestination(const System *system, Point pos)
2281 {
2282 	interstellarEscortDestination.first = system;
2283 	interstellarEscortDestination.second = pos;
2284 }
2285 
2286 
2287 
2288 // Get what coloring is currently selected in the map.
MapColoring() const2289 int PlayerInfo::MapColoring() const
2290 {
2291 	return mapColoring;
2292 }
2293 
2294 
2295 
2296 // Set what the map is being colored by.
SetMapColoring(int index)2297 void PlayerInfo::SetMapColoring(int index)
2298 {
2299 	mapColoring = index;
2300 }
2301 
2302 
2303 
2304 // Get the map zoom level.
MapZoom() const2305 int PlayerInfo::MapZoom() const
2306 {
2307 	return mapZoom;
2308 }
2309 
2310 
2311 
2312 // Set the map zoom level.
SetMapZoom(int level)2313 void PlayerInfo::SetMapZoom(int level)
2314 {
2315 	mapZoom = level;
2316 }
2317 
2318 
2319 
2320 // Get the set of collapsed categories for the named panel.
Collapsed(const string & name)2321 set<string> &PlayerInfo::Collapsed(const string &name)
2322 {
2323 	return collapsed[name];
2324 }
2325 
2326 
2327 
2328 // Apply any "changes" saved in this player info to the global game state.
ApplyChanges()2329 void PlayerInfo::ApplyChanges()
2330 {
2331 	for(const auto &it : reputationChanges)
2332 		it.first->SetReputation(it.second);
2333 	reputationChanges.clear();
2334 	AddChanges(dataChanges);
2335 	GameData::ReadEconomy(economy);
2336 	economy = DataNode();
2337 
2338 	// Make sure all stellar objects are correctly positioned. This is needed
2339 	// because EnterSystem() is not called the first time through.
2340 	GameData::SetDate(GetDate());
2341 	// SetDate() clears any bribes from yesterday, so restore any auto-clearance.
2342 	for(const Mission &mission : Missions())
2343 		if(mission.ClearanceMessage() == "auto")
2344 		{
2345 			mission.Destination()->Bribe(mission.HasFullClearance());
2346 			for(const Planet *planet : mission.Stopovers())
2347 				planet->Bribe(mission.HasFullClearance());
2348 		}
2349 
2350 	// Check if any special persons have been destroyed.
2351 	GameData::DestroyPersons(destroyedPersons);
2352 	destroyedPersons.clear();
2353 
2354 	// Check which planets you have dominated.
2355 	static const string prefix = "tribute: ";
2356 	for(auto it = conditions.lower_bound(prefix); it != conditions.end(); ++it)
2357 	{
2358 		if(it->first.compare(0, prefix.length(), prefix))
2359 			break;
2360 
2361 		const Planet *planet = GameData::Planets().Find(it->first.substr(prefix.length()));
2362 		if(planet)
2363 			GameData::GetPolitics().DominatePlanet(planet);
2364 	}
2365 
2366 	// Issue warnings for any data which has been mentioned but not actually defined, and
2367 	// ensure that all "undefined" data is appropriately named.
2368 	GameData::CheckReferences();
2369 
2370 	// Now that all outfits have names, we can finish loading the player's ships.
2371 	for(auto &&ship : ships)
2372 	{
2373 		// Government changes may have changed the player's ship swizzles.
2374 		ship->SetGovernment(GameData::PlayerGovernment());
2375 		ship->FinishLoading(false);
2376 	}
2377 }
2378 
2379 
2380 
2381 // Make change's to the player's planet, system, & ship locations as needed, to ensure the player and
2382 // their ships are in valid locations, even if the player did something drastic, such as remove a mod.
ValidateLoad()2383 void PlayerInfo::ValidateLoad()
2384 {
2385 	// If a system was not specified in the player data, use the flagship's system.
2386 	if(!planet && !ships.empty())
2387 	{
2388 		string warning = "Warning: no planet specified for player";
2389 		auto it = find_if(ships.begin(), ships.end(), [](const shared_ptr<Ship> &ship) noexcept -> bool
2390 			{ return ship->GetPlanet() && ship->GetPlanet()->IsValid() && !ship->IsParked() && ship->CanBeFlagship(); });
2391 		if(it != ships.end())
2392 		{
2393 			planet = (*it)->GetPlanet();
2394 			system = (*it)->GetSystem();
2395 			warning += ". Defaulting to location of flagship \"" + (*it)->Name() + "\", " + planet->TrueName() + ".";
2396 		}
2397 		else
2398 			warning += " (no ships could supply a valid player location).";
2399 
2400 		Files::LogError(warning);
2401 	}
2402 
2403 	// As a result of external game data changes (e.g. unloading a mod) it's possible the player ended up
2404 	// with an undefined system or planet. In that case, move them to the starting system to avoid crashing.
2405 	if(planet && !system)
2406 	{
2407 		system = planet->GetSystem();
2408 		Files::LogError("Warning: player system was not specified. Defaulting to the specified planet's system.");
2409 	}
2410 	if(!planet || !planet->IsValid() || !system || !system->IsValid())
2411 	{
2412 		system = &startData.GetSystem();
2413 		planet = &startData.GetPlanet();
2414 		Files::LogError("Warning: player system and/or planet was not valid. Defaulting to the starting location.");
2415 	}
2416 
2417 	// Every ship ought to have specified a valid location, but if not,
2418 	// move it to the player's location to avoid invalid states.
2419 	for(auto &&ship : ships)
2420 	{
2421 		if(!ship->GetSystem() || !ship->GetSystem()->IsValid())
2422 		{
2423 			ship->SetSystem(system);
2424 			Files::LogError("Warning: player ship \"" + ship->Name()
2425 				+ "\" did not specify a valid system. Defaulting to the player's system.");
2426 		}
2427 		// In-system ships that aren't on a valid planet should get moved to the player's planet
2428 		// (but e.g. disabled ships or those that didn't have a planet should remain in space).
2429 		if(ship->GetSystem() == system && ship->GetPlanet() && !ship->GetPlanet()->IsValid())
2430 		{
2431 			ship->SetPlanet(planet);
2432 			Files::LogError("Warning: in-system player ship \"" + ship->Name()
2433 				+ "\" specified an invalid planet. Defaulting to the player's planet.");
2434 		}
2435 		// Owned ships that are not in the player's system always start in flight.
2436 	}
2437 
2438 	// Validate the travel plan.
2439 	if(travelDestination && !travelDestination->IsValid())
2440 	{
2441 		Files::LogError("Warning: removed invalid travel plan destination \"" + travelDestination->TrueName() + ".\"");
2442 		travelDestination = nullptr;
2443 	}
2444 	if(!travelPlan.empty() && any_of(travelPlan.begin(), travelPlan.end(),
2445 			[](const System *waypoint) noexcept -> bool { return !waypoint->IsValid(); }))
2446 	{
2447 		travelPlan.clear();
2448 		travelDestination = nullptr;
2449 		Files::LogError("Warning: reset the travel plan due to use of invalid system(s).");
2450 	}
2451 
2452 	// For old saves, default to the first start condition (the default "Endless Sky" start).
2453 	if(startData.Identifier().empty())
2454 	{
2455 		// It is possible that there are no start conditions defined (e.g. a bad installation or
2456 		// incomplete total conversion plugin). In that case, it is not possible to continue.
2457 		const auto startCount = GameData::StartOptions().size();
2458 		if(startCount >= 1)
2459 		{
2460 			startData = GameData::StartOptions().front();
2461 			// When necessary, record in the pilot file that the starting data is just an assumption.
2462 			if(startCount >= 2)
2463 				conditions["unverified start scenario"] = true;
2464 		}
2465 		else
2466 			throw runtime_error("Unable to set a starting scenario for an existing pilot. (No valid \"start\" "
2467 				"nodes were found in data files or loaded plugins--make sure you've installed the game properly.)");
2468 	}
2469 
2470 	// Validate the missions that were loaded. Active-but-invalid missions are removed from
2471 	// the standard mission list, effectively pausing them until necessary data is restored.
2472 	missions.sort([](const Mission &lhs, const Mission &rhs) noexcept -> bool { return lhs.IsValid(); });
2473 	auto isInvalidMission = [](const Mission &m) noexcept -> bool { return !m.IsValid(); };
2474 	auto mit = find_if(missions.begin(), missions.end(), isInvalidMission);
2475 	if(mit != missions.end())
2476 		inactiveMissions.splice(inactiveMissions.end(), missions, mit, missions.end());
2477 
2478 	// Invalid available jobs or missions are erased (since there is no guarantee
2479 	// the player will be on the correct planet when a plugin is re-added).
2480 	availableJobs.remove_if(isInvalidMission);
2481 	availableMissions.remove_if(isInvalidMission);
2482 }
2483 
2484 
2485 
2486 // Update the conditions that reflect the current status of the player.
UpdateAutoConditions(bool isBoarding)2487 void PlayerInfo::UpdateAutoConditions(bool isBoarding)
2488 {
2489 	// Bound financial conditions to +/- 4.6 x 10^18 credits, within the range of a 64-bit int.
2490 	static constexpr int64_t limit = static_cast<int64_t>(1) << 62;
2491 	conditions["net worth"] = min(limit, max(-limit, accounts.NetWorth()));
2492 	conditions["credits"] = min(limit, accounts.Credits());
2493 	conditions["unpaid mortgages"] = min(limit, accounts.TotalDebt("Mortgage"));
2494 	conditions["unpaid fines"] = min(limit, accounts.TotalDebt("Fine"));
2495 	conditions["unpaid salaries"] = min(limit, accounts.SalariesOwed());
2496 	conditions["unpaid maintenance"] = min(limit, accounts.MaintenanceDue());
2497 	conditions["credit score"] = accounts.CreditScore();
2498 	// Serialize the current reputation with other governments.
2499 	SetReputationConditions();
2500 	// Helper lambda function to clear a range
2501 	auto clearRange = [](map<string, int64_t> &conditionsMap, string firstStr, string lastStr)
2502 	{
2503 		auto first = conditionsMap.lower_bound(firstStr);
2504 		auto last = conditionsMap.lower_bound(lastStr);
2505 		if(first != last)
2506 			conditionsMap.erase(first, last);
2507 	};
2508 	// Clear any existing ships: conditions. (Note: '!' = ' ' + 1.)
2509 	clearRange(conditions, "ships: ", "ships:!");
2510 	// Store special conditions for cargo and passenger space.
2511 	conditions["cargo space"] = 0;
2512 	conditions["passenger space"] = 0;
2513 	for(const shared_ptr<Ship> &ship : ships)
2514 		if(!ship->IsParked() && !ship->IsDisabled() && ship->GetSystem() == system)
2515 		{
2516 			conditions["cargo space"] += ship->Attributes().Get("cargo space");
2517 			conditions["passenger space"] += ship->Attributes().Get("bunks") - ship->RequiredCrew();
2518 			++conditions["ships: " + ship->Attributes().Category()];
2519 		}
2520 	// If boarding a ship, missions should not consider the space available
2521 	// in the player's entire fleet. The only fleet parameter offered to a
2522 	// boarding mission is the fleet composition (e.g. 4 Heavy Warships).
2523 	if(isBoarding && flagship)
2524 	{
2525 		conditions["cargo space"] = flagship->Cargo().Free();
2526 		conditions["passenger space"] = flagship->Cargo().BunksFree();
2527 	}
2528 
2529 	// Clear any existing flagship system: and planet: conditions. (Note: '!' = ' ' + 1.)
2530 	clearRange(conditions, "flagship system: ", "flagship system:!");
2531 	clearRange(conditions, "flagship planet: ", "flagship planet:!");
2532 
2533 	// Store conditions for flagship current crew, required crew, and bunks.
2534 	if(flagship)
2535 	{
2536 		conditions["flagship crew"] = flagship->Crew();
2537 		conditions["flagship required crew"] = flagship->RequiredCrew();
2538 		conditions["flagship bunks"] = flagship->Attributes().Get("bunks");
2539 		if(flagship->GetSystem())
2540 			conditions["flagship system: " + flagship->GetSystem()->Name()] = 1;
2541 		if(flagship->GetPlanet())
2542 			conditions["flagship planet: " + flagship->GetPlanet()->TrueName()] = 1;
2543 	}
2544 	else
2545 	{
2546 		conditions["flagship crew"] = 0;
2547 		conditions["flagship required crew"] = 0;
2548 		conditions["flagship bunks"] = 0;
2549 	}
2550 
2551 	// Conditions for your fleet's attractiveness to pirates:
2552 	pair<double, double> factors = RaidFleetFactors();
2553 	conditions["cargo attractiveness"] = factors.first;
2554 	conditions["armament deterrence"] = factors.second;
2555 	conditions["pirate attraction"] = factors.first - factors.second;
2556 }
2557 
2558 
2559 
2560 // New missions are generated each time you land on a planet.
CreateMissions()2561 void PlayerInfo::CreateMissions()
2562 {
2563 	boardingMissions.clear();
2564 
2565 	// Check for available missions.
2566 	bool skipJobs = planet && !planet->IsInhabited();
2567 	bool hasPriorityMissions = false;
2568 	for(const auto &it : GameData::Missions())
2569 	{
2570 		if(it.second.IsAtLocation(Mission::BOARDING) || it.second.IsAtLocation(Mission::ASSISTING))
2571 			continue;
2572 		if(skipJobs && it.second.IsAtLocation(Mission::JOB))
2573 			continue;
2574 
2575 		if(it.second.CanOffer(*this))
2576 		{
2577 			list<Mission> &missions =
2578 				it.second.IsAtLocation(Mission::JOB) ? availableJobs : availableMissions;
2579 
2580 			missions.push_back(it.second.Instantiate(*this));
2581 			if(missions.back().HasFailed(*this))
2582 				missions.pop_back();
2583 			else if(!it.second.IsAtLocation(Mission::JOB))
2584 				hasPriorityMissions |= missions.back().HasPriority();
2585 		}
2586 	}
2587 
2588 	// If any of the available missions are "priority" missions, no other
2589 	// special missions will be offered in the spaceport.
2590 	if(hasPriorityMissions)
2591 	{
2592 		auto it = availableMissions.begin();
2593 		while(it != availableMissions.end())
2594 		{
2595 			if(it->IsAtLocation(Mission::SPACEPORT) && !it->HasPriority())
2596 				it = availableMissions.erase(it);
2597 			else
2598 				++it;
2599 		}
2600 	}
2601 	else if(availableMissions.size() > 1)
2602 	{
2603 		// Minor missions only get offered if no other missions (including other
2604 		// minor missions) are competing with them. This is to avoid having two
2605 		// or three missions pop up as soon as you enter the spaceport.
2606 		auto it = availableMissions.begin();
2607 		while(it != availableMissions.end())
2608 		{
2609 			if(it->IsMinor())
2610 			{
2611 				it = availableMissions.erase(it);
2612 				if(availableMissions.size() <= 1)
2613 					break;
2614 			}
2615 			else
2616 				++it;
2617 		}
2618 	}
2619 }
2620 
2621 
2622 
2623 // Updates each mission upon landing, to perform landing actions (Stopover,
2624 // Visit, Complete, Fail), and remove now-complete or now-failed missions.
StepMissions(UI * ui)2625 void PlayerInfo::StepMissions(UI *ui)
2626 {
2627 	// Check for NPCs that have been destroyed without their destruction
2628 	// being registered, e.g. by self-destruct:
2629 	for(Mission &mission : missions)
2630 		for(const NPC &npc : mission.NPCs())
2631 			for(const shared_ptr<Ship> &ship : npc.Ships())
2632 				if(ship->IsDestroyed())
2633 					mission.Do(ShipEvent(nullptr, ship, ShipEvent::DESTROY), *this, ui);
2634 
2635 	// Check missions for status changes from landing.
2636 	string visitText;
2637 	int missionVisits = 0;
2638 	auto substitutions = map<string, string>{
2639 		{"<first>", firstName},
2640 		{"<last>", lastName}
2641 	};
2642 	if(Flagship())
2643 		substitutions["<ship>"] = Flagship()->Name();
2644 
2645 	auto mit = missions.begin();
2646 	while(mit != missions.end())
2647 	{
2648 		Mission &mission = *mit;
2649 		++mit;
2650 
2651 		// If this is a stopover for the mission, perform the stopover action.
2652 		mission.Do(Mission::STOPOVER, *this, ui);
2653 
2654 		if(mission.HasFailed(*this))
2655 			RemoveMission(Mission::FAIL, mission, ui);
2656 		else if(mission.CanComplete(*this))
2657 			RemoveMission(Mission::COMPLETE, mission, ui);
2658 		else if(mission.Destination() == GetPlanet() && !freshlyLoaded)
2659 		{
2660 			mission.Do(Mission::VISIT, *this, ui);
2661 			if(mission.IsUnique() || !mission.IsVisible())
2662 				continue;
2663 
2664 			// On visit dialogs are handled separately as to avoid a player
2665 			// getting spammed by on visit dialogs if they are stacking jobs
2666 			// from the same destination.
2667 			if(visitText.empty())
2668 			{
2669 				const auto &text = mission.GetAction(Mission::VISIT).DialogText();
2670 				if(!text.empty())
2671 					visitText = Format::Replace(text, substitutions);
2672 			}
2673 			++missionVisits;
2674 		}
2675 	}
2676 	if(!visitText.empty())
2677 	{
2678 		if(missionVisits > 1)
2679 			visitText += "\n\t(You have " + Format::Number(missionVisits - 1) + " other unfinished "
2680 				+ ((missionVisits > 2) ? "missions" : "mission") + " at this location.)";
2681 		ui->Push(new Dialog(visitText));
2682 	}
2683 	// One mission's actions may influence another mission, so loop through one
2684 	// more time to see if any mission is now completed or failed due to a change
2685 	// that happened in another mission the first time through.
2686 	mit = missions.begin();
2687 	while(mit != missions.end())
2688 	{
2689 		Mission &mission = *mit;
2690 		++mit;
2691 
2692 		if(mission.HasFailed(*this))
2693 			RemoveMission(Mission::FAIL, mission, ui);
2694 		else if(mission.CanComplete(*this))
2695 			RemoveMission(Mission::COMPLETE, mission, ui);
2696 	}
2697 
2698 	// Search for any missions that have failed but for which we are still
2699 	// holding on to some cargo.
2700 	set<const Mission *> active;
2701 	for(const Mission &it : missions)
2702 		active.insert(&it);
2703 
2704 	vector<const Mission *> missionsToRemove;
2705 	for(const auto &it : cargo.MissionCargo())
2706 		if(!active.count(it.first))
2707 			missionsToRemove.push_back(it.first);
2708 	for(const auto &it : cargo.PassengerList())
2709 		if(!active.count(it.first))
2710 			missionsToRemove.push_back(it.first);
2711 	for(const Mission *mission : missionsToRemove)
2712 		cargo.RemoveMissionCargo(mission);
2713 }
2714 
2715 
2716 
Autosave() const2717 void PlayerInfo::Autosave() const
2718 {
2719 	if(!CanBeSaved() || filePath.length() < 4)
2720 		return;
2721 
2722 	string path = filePath.substr(0, filePath.length() - 4) + "~autosave.txt";
2723 	Save(path);
2724 }
2725 
2726 
2727 
Save(const string & path) const2728 void PlayerInfo::Save(const string &path) const
2729 {
2730 	DataWriter out(path);
2731 
2732 
2733 	// Basic player information and persistent UI settings:
2734 
2735 	// Pilot information:
2736 	out.Write("pilot", firstName, lastName);
2737 	out.Write("date", date.Day(), date.Month(), date.Year());
2738 	if(system)
2739 		out.Write("system", system->Name());
2740 	if(planet)
2741 		out.Write("planet", planet->TrueName());
2742 	if(planet && planet->CanUseServices())
2743 		out.Write("clearance");
2744 	out.Write("playtime", playTime);
2745 	// This flag is set if the player must leave the planet immediately upon
2746 	// entering their ship (i.e. because a mission forced them to take off).
2747 	if(shouldLaunch)
2748 		out.Write("launching");
2749 	for(const System *system : travelPlan)
2750 		out.Write("travel", system->Name());
2751 	if(travelDestination)
2752 		out.Write("travel destination", travelDestination->TrueName());
2753 
2754 	// Save the current setting for the map coloring;
2755 	out.Write("map coloring", mapColoring);
2756 	out.Write("map zoom", mapZoom);
2757 	// Remember what categories are collapsed.
2758 	for(const auto &it : collapsed)
2759 	{
2760 		// Skip panels where nothing was collapsed.
2761 		if(it.second.empty())
2762 			continue;
2763 
2764 		out.Write("collapsed", it.first);
2765 		out.BeginChild();
2766 		{
2767 			for(const auto &cit : it.second)
2768 				out.Write(cit);
2769 		}
2770 		out.EndChild();
2771 	}
2772 
2773 	out.Write("reputation with");
2774 	out.BeginChild();
2775 	{
2776 		for(const auto &it : GameData::Governments())
2777 			if(!it.second.IsPlayer())
2778 				out.Write(it.first, it.second.Reputation());
2779 	}
2780 	out.EndChild();
2781 
2782 
2783 	// Records of things you own:
2784 	out.Write();
2785 	out.WriteComment("What you own:");
2786 
2787 	// Save all the data for all the player's ships.
2788 	for(const shared_ptr<Ship> &ship : ships)
2789 	{
2790 		ship->Save(out);
2791 		auto it = groups.find(ship.get());
2792 		if(it != groups.end() && it->second)
2793 			out.Write("groups", it->second);
2794 	}
2795 	if(!planetaryStorage.empty())
2796 	{
2797 		out.Write("storage");
2798 		out.BeginChild();
2799 		{
2800 			for(const auto &it : planetaryStorage)
2801 				if(!it.second.IsEmpty())
2802 				{
2803 					out.Write("planet", it.first->TrueName());
2804 					out.BeginChild();
2805 					{
2806 						it.second.Save(out);
2807 					}
2808 					out.EndChild();
2809 				}
2810 		}
2811 		out.EndChild();
2812 	}
2813 
2814 	// Save accounting information, cargo, and cargo cost bases.
2815 	accounts.Save(out);
2816 	cargo.Save(out);
2817 	if(!costBasis.empty())
2818 	{
2819 		out.Write("basis");
2820 		out.BeginChild();
2821 		{
2822 			for(const auto &it : costBasis)
2823 				if(it.second)
2824 					out.Write(it.first, it.second);
2825 		}
2826 		out.EndChild();
2827 	}
2828 
2829 	if(!stock.empty())
2830 	{
2831 		out.Write("stock");
2832 		out.BeginChild();
2833 		{
2834 			using StockElement = pair<const Outfit *const, int>;
2835 			WriteSorted(stock,
2836 				[](const StockElement *lhs, const StockElement *rhs)
2837 					{ return lhs->first->Name() < rhs->first->Name(); },
2838 				[&out](const StockElement &it)
2839 				{
2840 					if(it.second)
2841 						out.Write(it.first->Name(), it.second);
2842 				});
2843 		}
2844 		out.EndChild();
2845 	}
2846 	depreciation.Save(out, date.DaysSinceEpoch());
2847 	stockDepreciation.Save(out, date.DaysSinceEpoch());
2848 
2849 
2850 	// Records of things you have done or are doing, or have happened to you:
2851 	out.Write();
2852 	out.WriteComment("What you've done:");
2853 
2854 	// Save all missions (accepted, accepted-but-invalid, and available).
2855 	for(const Mission &mission : missions)
2856 		mission.Save(out);
2857 	for(const Mission &mission : inactiveMissions)
2858 		mission.Save(out);
2859 	for(const Mission &mission : availableJobs)
2860 		mission.Save(out, "available job");
2861 	for(const Mission &mission : availableMissions)
2862 		mission.Save(out, "available mission");
2863 
2864 	// Save any "condition" flags that are set.
2865 	if(!conditions.empty())
2866 	{
2867 		out.Write("conditions");
2868 		out.BeginChild();
2869 		{
2870 			for(const auto &it : conditions)
2871 			{
2872 				// If the condition's value is 1, don't bother writing the 1.
2873 				if(it.second == 1)
2874 					out.Write(it.first);
2875 				else if(it.second)
2876 					out.Write(it.first, it.second);
2877 			}
2878 		}
2879 		out.EndChild();
2880 	}
2881 
2882 	// Save pending events, and changes that have happened due to past events.
2883 	for(const GameEvent &event : gameEvents)
2884 		event.Save(out);
2885 	if(!dataChanges.empty())
2886 	{
2887 		out.Write("changes");
2888 		out.BeginChild();
2889 		{
2890 			for(const DataNode &node : dataChanges)
2891 				out.Write(node);
2892 		}
2893 		out.EndChild();
2894 	}
2895 	GameData::WriteEconomy(out);
2896 
2897 	// Check which persons have been captured or destroyed.
2898 	for(const auto &it : GameData::Persons())
2899 		if(it.second.IsDestroyed())
2900 			out.Write("destroyed", it.first);
2901 
2902 
2903 	// Records of things you have discovered:
2904 	out.Write();
2905 	out.WriteComment("What you know:");
2906 
2907 	// Save a list of systems the player has visited.
2908 	WriteSorted(visitedSystems,
2909 		[](const System *const *lhs, const System *const *rhs)
2910 			{ return (*lhs)->Name() < (*rhs)->Name(); },
2911 		[&out](const System *system)
2912 		{
2913 			out.Write("visited", system->Name());
2914 		});
2915 
2916 	// Save a list of planets the player has visited.
2917 	WriteSorted(visitedPlanets,
2918 		[](const Planet *const *lhs, const Planet *const *rhs)
2919 			{ return (*lhs)->TrueName() < (*rhs)->TrueName(); },
2920 		[&out](const Planet *planet)
2921 		{
2922 			out.Write("visited planet", planet->TrueName());
2923 		});
2924 
2925 	if(!harvested.empty())
2926 	{
2927 		out.Write("harvested");
2928 		out.BeginChild();
2929 		{
2930 			using HarvestLog = pair<const System *, const Outfit *>;
2931 			WriteSorted(harvested,
2932 				[](const HarvestLog *lhs, const HarvestLog *rhs) -> bool
2933 				{
2934 					// Sort by system name and then by outfit name.
2935 					if(lhs->first != rhs->first)
2936 						return lhs->first->Name() < rhs->first->Name();
2937 					else
2938 						return lhs->second->Name() < rhs->second->Name();
2939 				},
2940 				[&out](const HarvestLog &it)
2941 				{
2942 					out.Write(it.first->Name(), it.second->Name());
2943 				});
2944 		}
2945 		out.EndChild();
2946 	}
2947 
2948 	out.Write("logbook");
2949 	out.BeginChild();
2950 	{
2951 		for(auto &&it : logbook)
2952 		{
2953 			out.Write(it.first.Day(), it.first.Month(), it.first.Year());
2954 			out.BeginChild();
2955 			{
2956 				// Break the text up into paragraphs.
2957 				for(const string &line : Format::Split(it.second, "\n\t"))
2958 					out.Write(line);
2959 			}
2960 			out.EndChild();
2961 		}
2962 		for(auto &&it : specialLogs)
2963 			for(auto &&eit : it.second)
2964 			{
2965 				out.Write(it.first, eit.first);
2966 				out.BeginChild();
2967 				{
2968 					// Break the text up into paragraphs.
2969 					for(const string &line : Format::Split(eit.second, "\n\t"))
2970 						out.Write(line);
2971 				}
2972 				out.EndChild();
2973 			}
2974 	}
2975 	out.EndChild();
2976 
2977 	out.Write();
2978 	out.WriteComment("How you began:");
2979 	startData.Save(out);
2980 }
2981 
2982 
2983 
2984 // Check (and perform) any fines incurred by planetary security. If the player
2985 // has dominated the planet, or was given clearance to this planet by a mission,
2986 // planetary security is avoided. Infiltrating implies evasion of security.
Fine(UI * ui)2987 void PlayerInfo::Fine(UI *ui)
2988 {
2989 	const Planet *planet = GetPlanet();
2990 	// Dominated planets should never fine you.
2991 	// By default, uninhabited planets should not fine the player.
2992 	if(GameData::GetPolitics().HasDominated(planet)
2993 		|| !(planet->IsInhabited() || planet->HasCustomSecurity()))
2994 		return;
2995 
2996 	// Planets should not fine you if you have mission clearance or are infiltrating.
2997 	for(const Mission &mission : missions)
2998 		if(mission.HasClearance(planet) || (!mission.HasFullClearance() &&
2999 					(mission.Destination() == planet || mission.Stopovers().count(planet))))
3000 			return;
3001 
3002 	// The planet's government must have the authority to enforce laws.
3003 	const Government *gov = planet->GetGovernment();
3004 	if(!gov->CanEnforce(planet))
3005 		return;
3006 
3007 	string message = gov->Fine(*this, 0, nullptr, planet->Security());
3008 	if(!message.empty())
3009 	{
3010 		if(message == "atrocity")
3011 		{
3012 			const Conversation *conversation = gov->DeathSentence();
3013 			if(conversation)
3014 				ui->Push(new ConversationPanel(*this, *conversation));
3015 			else
3016 			{
3017 				message = "Before you can leave your ship, the " + gov->GetName()
3018 					+ " authorities show up and begin scanning it. They say, \"Captain "
3019 					+ LastName()
3020 					+ ", we detect highly illegal material on your ship.\""
3021 					"\n\tYou are sentenced to lifetime imprisonment on a penal colony."
3022 					" Your days of traveling the stars have come to an end.";
3023 				ui->Push(new Dialog(message));
3024 			}
3025 			// All ships belonging to the player should be removed.
3026 			Die();
3027 		}
3028 		else
3029 			ui->Push(new Dialog(message));
3030 	}
3031 }
3032 
3033 
3034 
3035 // Helper function to update the ship selection.
SelectShip(const shared_ptr<Ship> & ship,bool * first)3036 void PlayerInfo::SelectShip(const shared_ptr<Ship> &ship, bool *first)
3037 {
3038 	// Make sure this ship is not already selected.
3039 	auto it = selectedShips.begin();
3040 	for( ; it != selectedShips.end(); ++it)
3041 		if(it->lock() == ship)
3042 			break;
3043 	if(it == selectedShips.end())
3044 	{
3045 		// This ship is not yet selected.
3046 		selectedShips.push_back(ship);
3047 		Ship *flagship = Flagship();
3048 		if(*first && flagship && ship.get() != flagship)
3049 		{
3050 			flagship->SetTargetShip(ship);
3051 			*first = false;
3052 		}
3053 	}
3054 }
3055 
3056 
3057 
3058 // Check that this player's current state can be saved.
CanBeSaved() const3059 bool PlayerInfo::CanBeSaved() const
3060 {
3061 	return (!isDead && planet && system && !firstName.empty() && !lastName.empty());
3062 }
3063