1 /* Planet.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 "Planet.h"
14 
15 #include "DataNode.h"
16 #include "text/Format.h"
17 #include "GameData.h"
18 #include "Government.h"
19 #include "PlayerInfo.h"
20 #include "Politics.h"
21 #include "Random.h"
22 #include "Ship.h"
23 #include "ShipEvent.h"
24 #include "SpriteSet.h"
25 #include "System.h"
26 
27 #include <algorithm>
28 
29 using namespace std;
30 
31 namespace {
32 	const string WORMHOLE = "wormhole";
33 	const string PLANET = "planet";
34 
35 	// Planet attributes in the form "requires: <attribute>" restrict the ability of ships to land
36 	// unless the ship has all required attributes.
SetRequiredAttributes(const set<string> & attributes,set<string> & required)37 	void SetRequiredAttributes(const set<string> &attributes, set<string> &required)
38 	{
39 		static const string PREFIX = "requires: ";
40 		static const string PREFIX_END = "requires:!";
41 		required.clear();
42 		for_each(attributes.lower_bound(PREFIX), attributes.lower_bound(PREFIX_END), [&](const string &attribute)
43 		{
44 			required.emplace_hint(required.cend(), attribute.substr(PREFIX.length()));
45 		});
46 	}
47 }
48 
49 
50 
51 // Load a planet's description from a file.
Load(const DataNode & node)52 void Planet::Load(const DataNode &node)
53 {
54 	if(node.Size() < 2)
55 		return;
56 	name = node.Token(1);
57 	// The planet's name is needed to save references to this object, so a
58 	// flag is used to test whether Load() was called at least once for it.
59 	isDefined = true;
60 
61 	// If this planet has been loaded before, these sets of items should be
62 	// reset instead of appending to them:
63 	set<string> shouldOverwrite = {"attributes", "description", "spaceport"};
64 
65 	for(const DataNode &child : node)
66 	{
67 		// Check for the "add" or "remove" keyword.
68 		bool add = (child.Token(0) == "add");
69 		bool remove = (child.Token(0) == "remove");
70 		if((add || remove) && child.Size() < 2)
71 		{
72 			child.PrintTrace("Skipping " + child.Token(0) + " with no key given:");
73 			continue;
74 		}
75 
76 		// Get the key and value (if any).
77 		const string &key = child.Token((add || remove) ? 1 : 0);
78 		int valueIndex = (add || remove) ? 2 : 1;
79 		bool hasValue = (child.Size() > valueIndex);
80 		const string &value = child.Token(hasValue ? valueIndex : 0);
81 
82 		// Check for conditions that require clearing this key's current value.
83 		// "remove <key>" means to clear the key's previous contents.
84 		// "remove <key> <value>" means to remove just that value from the key.
85 		bool removeAll = (remove && !hasValue);
86 		// "<key> clear" is the deprecated way of writing "remove <key>."
87 		removeAll |= (!add && !remove && hasValue && value == "clear");
88 		// If this is the first entry for the given key, and we are not in "add"
89 		// or "remove" mode, its previous value should be cleared.
90 		bool overwriteAll = (!add && !remove && !removeAll && shouldOverwrite.count(key));
91 		// Clear the data of the given type.
92 		if(removeAll || overwriteAll)
93 		{
94 			// Clear the data of the given type.
95 			if(key == "music")
96 				music.clear();
97 			else if(key == "attributes")
98 				attributes.clear();
99 			else if(key == "description")
100 				description.clear();
101 			else if(key == "spaceport")
102 				spaceport.clear();
103 			else if(key == "shipyard")
104 				shipSales.clear();
105 			else if(key == "outfitter")
106 				outfitSales.clear();
107 			else if(key == "government")
108 				government = nullptr;
109 			else if(key == "required reputation")
110 				requiredReputation = 0.;
111 			else if(key == "bribe")
112 				bribe = 0.;
113 			else if(key == "security")
114 				security = 0.;
115 			else if(key == "tribute")
116 				tribute = 0;
117 
118 			// If not in "overwrite" mode, move on to the next node.
119 			if(overwriteAll)
120 				shouldOverwrite.erase(key);
121 			else
122 				continue;
123 		}
124 
125 		// Handle the attributes which can be "removed."
126 		if(!hasValue)
127 		{
128 			child.PrintTrace("Expected key to have a value:");
129 			continue;
130 		}
131 		else if(key == "attributes")
132 		{
133 			if(remove)
134 				for(int i = valueIndex; i < child.Size(); ++i)
135 					attributes.erase(child.Token(i));
136 			else
137 				for(int i = valueIndex; i < child.Size(); ++i)
138 					attributes.insert(child.Token(i));
139 		}
140 		else if(key == "shipyard")
141 		{
142 			if(remove)
143 				shipSales.erase(GameData::Shipyards().Get(value));
144 			else
145 				shipSales.insert(GameData::Shipyards().Get(value));
146 		}
147 		else if(key == "outfitter")
148 		{
149 			if(remove)
150 				outfitSales.erase(GameData::Outfitters().Get(value));
151 			else
152 				outfitSales.insert(GameData::Outfitters().Get(value));
153 		}
154 		// Handle the attributes which cannot be "removed."
155 		else if(remove)
156 		{
157 			child.PrintTrace("Cannot \"remove\" a specific value from the given key:");
158 			continue;
159 		}
160 		else if(key == "landscape")
161 			landscape = SpriteSet::Get(value);
162 		else if(key == "music")
163 			music = value;
164 		else if(key == "description" || key == "spaceport")
165 		{
166 			string &text = (key == "description") ? description : spaceport;
167 			if(!text.empty() && !value.empty() && value[0] > ' ')
168 				text += '\t';
169 			text += value;
170 			text += '\n';
171 		}
172 		else if(key == "government")
173 			government = GameData::Governments().Get(value);
174 		else if(key == "required reputation")
175 			requiredReputation = child.Value(valueIndex);
176 		else if(key == "bribe")
177 			bribe = child.Value(valueIndex);
178 		else if(key == "security")
179 		{
180 			customSecurity = true;
181 			security = child.Value(valueIndex);
182 		}
183 		else if(key == "tribute")
184 		{
185 			tribute = child.Value(valueIndex);
186 			bool resetFleets = !defenseFleets.empty();
187 			for(const DataNode &grand : child)
188 			{
189 				if(grand.Token(0) == "threshold" && grand.Size() >= 2)
190 					defenseThreshold = grand.Value(1);
191 				else if(grand.Token(0) == "fleet")
192 				{
193 					if(grand.Size() >= 2 && !grand.HasChildren())
194 					{
195 						// Allow only one "tribute" node to define the tribute fleets.
196 						if(resetFleets)
197 						{
198 							defenseFleets.clear();
199 							resetFleets = false;
200 						}
201 						defenseFleets.insert(defenseFleets.end(),
202 								grand.Size() >= 3 ? grand.Value(2) : 1,
203 								GameData::Fleets().Get(grand.Token(1))
204 						);
205 					}
206 					else
207 						grand.PrintTrace("Skipping unsupported tribute fleet definition:");
208 				}
209 				else
210 					grand.PrintTrace("Skipping unrecognized tribute attribute:");
211 			}
212 		}
213 		else
214 			child.PrintTrace("Skipping unrecognized attribute:");
215 	}
216 
217 	static const vector<string> AUTO_ATTRIBUTES = {"spaceport", "shipyard", "outfitter"};
218 	bool autoValues[3] = {!spaceport.empty(), !shipSales.empty(), !outfitSales.empty()};
219 	for(unsigned i = 0; i < AUTO_ATTRIBUTES.size(); ++i)
220 	{
221 		if(autoValues[i])
222 			attributes.insert(AUTO_ATTRIBUTES[i]);
223 		else
224 			attributes.erase(AUTO_ATTRIBUTES[i]);
225 	}
226 
227 	// Precalculate commonly used values that can only change due to Load().
228 	inhabited = (HasSpaceport() || requiredReputation || !defenseFleets.empty()) && !attributes.count("uninhabited");
229 	SetRequiredAttributes(Attributes(), requiredAttributes);
230 }
231 
232 
233 
234 // Test if this planet has been loaded (vs. just referred to). It must also be located in
235 // at least one system, and all systems that claim it must themselves be valid.
IsValid() const236 bool Planet::IsValid() const
237 {
238 	return isDefined && !systems.empty() && all_of(systems.begin(), systems.end(),
239 		[](const System *s) noexcept -> bool { return s->IsValid(); });
240 }
241 
242 
243 
244 // Get the name of the planet.
Name() const245 const string &Planet::Name() const
246 {
247 	static const string UNKNOWN = "???";
248 	if(IsWormhole())
249 		return UNKNOWN;
250 
251 	return name;
252 }
253 
254 
255 
SetName(const string & name)256 void Planet::SetName(const string &name)
257 {
258 	this->name = name;
259 }
260 
261 
262 
263 // Get the name used for this planet in the data files.
TrueName() const264 const string &Planet::TrueName() const
265 {
266 	return name;
267 }
268 
269 
270 
271 // Get the planet's descriptive text.
Description() const272 const string &Planet::Description() const
273 {
274 	return description;
275 }
276 
277 
278 
279 // Get the landscape sprite.
Landscape() const280 const Sprite *Planet::Landscape() const
281 {
282 	return landscape;
283 }
284 
285 
286 
287 // Get the name of the ambient audio to play on this planet.
MusicName() const288 const string &Planet::MusicName() const
289 {
290 	return music;
291 }
292 
293 
294 
295 // Get the list of "attributes" of the planet.
Attributes() const296 const set<string> &Planet::Attributes() const
297 {
298 	return attributes;
299 }
300 
301 
302 
303 // Get planet's noun descriptor from attributes
Noun() const304 const string &Planet::Noun() const
305 {
306 	if(IsWormhole())
307 		return WORMHOLE;
308 
309 	for(const string &attribute : attributes)
310 		if(attribute == "moon" || attribute == "station")
311 			return attribute;
312 
313 	return PLANET;
314 }
315 
316 
317 
318 // Check whether there is a spaceport (which implies there is also trading,
319 // jobs, banking, and hiring).
HasSpaceport() const320 bool Planet::HasSpaceport() const
321 {
322 	return !spaceport.empty();
323 }
324 
325 
326 
327 // Get the spaceport's descriptive text.
SpaceportDescription() const328 const string &Planet::SpaceportDescription() const
329 {
330 	return spaceport;
331 }
332 
333 
334 
335 // Check if this planet is inhabited (i.e. it has a spaceport, and does not
336 // have the "uninhabited" attribute).
IsInhabited() const337 bool Planet::IsInhabited() const
338 {
339 	return inhabited;
340 }
341 
342 
343 
344 // Check if this planet has a shipyard.
HasShipyard() const345 bool Planet::HasShipyard() const
346 {
347 	return !Shipyard().empty();
348 }
349 
350 
351 
352 // Get the list of ships in the shipyard.
Shipyard() const353 const Sale<Ship> &Planet::Shipyard() const
354 {
355 	shipyard.clear();
356 	for(const Sale<Ship> *sale : shipSales)
357 		shipyard.Add(*sale);
358 
359 	return shipyard;
360 }
361 
362 
363 
364 // Check if this planet has an outfitter.
HasOutfitter() const365 bool Planet::HasOutfitter() const
366 {
367 	return !Outfitter().empty();
368 }
369 
370 
371 
372 // Get the list of outfits available from the outfitter.
Outfitter() const373 const Sale<Outfit> &Planet::Outfitter() const
374 {
375 	outfitter.clear();
376 	for(const Sale<Outfit> *sale : outfitSales)
377 		outfitter.Add(*sale);
378 
379 	return outfitter;
380 }
381 
382 
383 
384 // Get this planet's government. Most planets follow the government of the system they are in.
GetGovernment() const385 const Government *Planet::GetGovernment() const
386 {
387 	return government ? government : systems.empty() ? nullptr : GetSystem()->GetGovernment();
388 }
389 
390 
391 
392 // You need this good a reputation with this system's government to land here.
RequiredReputation() const393 double Planet::RequiredReputation() const
394 {
395 	return requiredReputation;
396 }
397 
398 
399 
400 // This is what fraction of your fleet's value you must pay as a bribe in
401 // order to land on this planet. (If zero, you cannot bribe it.)
GetBribeFraction() const402 double Planet::GetBribeFraction() const
403 {
404 	return bribe;
405 }
406 
407 
408 
409 // This is how likely the planet's authorities are to notice if you are
410 // doing something illegal.
Security() const411 double Planet::Security() const
412 {
413 	return security;
414 }
415 
416 
417 
HasCustomSecurity() const418 bool Planet::HasCustomSecurity() const
419 {
420 	return customSecurity;
421 }
422 
423 
424 
GetSystem() const425 const System *Planet::GetSystem() const
426 {
427 	return (systems.empty() ? nullptr : systems.front());
428 }
429 
430 
431 
432 // Check if this planet is in the given system. Note that wormholes may be
433 // in more than one system.
IsInSystem(const System * system) const434 bool Planet::IsInSystem(const System *system) const
435 {
436 	return (find(systems.begin(), systems.end(), system) != systems.end());
437 }
438 
439 
440 
SetSystem(const System * system)441 void Planet::SetSystem(const System *system)
442 {
443 	if(find(systems.begin(), systems.end(), system) == systems.end())
444 		systems.push_back(system);
445 }
446 
447 
448 
449 // Remove the given system from the list of systems this planet is in. This
450 // must be done when game events rearrange the planets in a system.
RemoveSystem(const System * system)451 void Planet::RemoveSystem(const System *system)
452 {
453 	auto it = find(systems.begin(), systems.end(), system);
454 	if(it != systems.end())
455 		systems.erase(it);
456 }
457 
458 
459 
460 // Check if this is a wormhole (that is, it appears in multiple systems).
IsWormhole() const461 bool Planet::IsWormhole() const
462 {
463 	return (systems.size() > 1);
464 }
465 
466 
467 
WormholeSource(const System * to) const468 const System *Planet::WormholeSource(const System *to) const
469 {
470 	auto it = find(systems.begin(), systems.end(), to);
471 	if(it == systems.end())
472 		return to;
473 
474 	return (it == systems.begin() ? systems.back() : *--it);
475 }
476 
477 
478 
479 
WormholeDestination(const System * from) const480 const System *Planet::WormholeDestination(const System *from) const
481 {
482 	auto it = find(systems.begin(), systems.end(), from);
483 	if(it == systems.end())
484 		return from;
485 
486 	++it;
487 	return (it == systems.end() ? systems.front() : *it);
488 }
489 
490 
491 
WormholeSystems() const492 const vector<const System *> &Planet::WormholeSystems() const
493 {
494 	return systems;
495 }
496 
497 
498 
499 // Check if the given ship has all the attributes necessary to allow it to
500 // land on this planet.
IsAccessible(const Ship * ship) const501 bool Planet::IsAccessible(const Ship *ship) const
502 {
503 	// If there are no required attributes, then any ship may land here.
504 	if(IsUnrestricted())
505 		return true;
506 	if(!ship)
507 		return false;
508 
509 	const auto &shipAttributes = ship->Attributes();
510 	return all_of(requiredAttributes.cbegin(), requiredAttributes.cend(),
511 			[&](const string &attr) -> bool { return shipAttributes.Get(attr); });
512 }
513 
514 
515 
516 // Check if this planet has any required attributes that restrict landability.
IsUnrestricted() const517 bool Planet::IsUnrestricted() const
518 {
519 	return requiredAttributes.empty();
520 }
521 
522 
523 
524 // Below are convenience functions which access the game state in Politics,
525 // but do so with a less convoluted syntax:
HasFuelFor(const Ship & ship) const526 bool Planet::HasFuelFor(const Ship &ship) const
527 {
528 	return !IsWormhole() && HasSpaceport() && CanLand(ship);
529 }
530 
531 
532 
CanLand(const Ship & ship) const533 bool Planet::CanLand(const Ship &ship) const
534 {
535 	return IsAccessible(&ship) && GameData::GetPolitics().CanLand(ship, this);
536 }
537 
538 
539 
CanLand() const540 bool Planet::CanLand() const
541 {
542 	return GameData::GetPolitics().CanLand(this);
543 }
544 
545 
546 
CanUseServices() const547 bool Planet::CanUseServices() const
548 {
549 	return GameData::GetPolitics().CanUseServices(this);
550 }
551 
552 
553 
Bribe(bool fullAccess) const554 void Planet::Bribe(bool fullAccess) const
555 {
556 	GameData::GetPolitics().BribePlanet(this, fullAccess);
557 }
558 
559 
560 
561 // Demand tribute, and get the planet's response.
DemandTribute(PlayerInfo & player) const562 string Planet::DemandTribute(PlayerInfo &player) const
563 {
564 	if(player.GetCondition("tribute: " + name))
565 		return "We are already paying you as much as we can afford.";
566 	if(!tribute || defenseFleets.empty())
567 		return "Please don't joke about that sort of thing.";
568 	if(player.GetCondition("combat rating") < defenseThreshold)
569 		return "You're not worthy of our time.";
570 
571 	// The player is scary enough for this planet to take notice. Check whether
572 	// this is the first demand for tribute, or not.
573 	if(!isDefending)
574 	{
575 		isDefending = true;
576 		set<const Government *> toProvoke;
577 		for(const auto &fleet : defenseFleets)
578 			toProvoke.insert(fleet->GetGovernment());
579 		for(const auto &gov : toProvoke)
580 			gov->Offend(ShipEvent::PROVOKE);
581 		// Terrorizing a planet is not taken lightly by it or its allies.
582 		GetGovernment()->Offend(ShipEvent::ATROCITY);
583 		return "Our defense fleet will make short work of you.";
584 	}
585 
586 	// The player has already demanded tribute. Have they defeated the entire defense fleet?
587 	bool isDefeated = (defenseDeployed == defenseFleets.size());
588 	for(const shared_ptr<Ship> &ship : defenders)
589 		if(!ship->IsDisabled() && !ship->IsYours())
590 		{
591 			isDefeated = false;
592 			break;
593 		}
594 
595 	if(!isDefeated)
596 		return "We're not ready to surrender yet.";
597 
598 	player.Conditions()["tribute: " + name] = tribute;
599 	GameData::GetPolitics().DominatePlanet(this);
600 	return "We surrender. We will pay you " + Format::Credits(tribute) + " credits per day to leave us alone.";
601 }
602 
603 
604 
605 // While being tributed, attempt to spawn the next specified defense fleet.
DeployDefense(list<shared_ptr<Ship>> & ships) const606 void Planet::DeployDefense(list<shared_ptr<Ship>> &ships) const
607 {
608 	if(!isDefending || Random::Int(60) || defenseDeployed == defenseFleets.size())
609 		return;
610 
611 	auto end = defenders.begin();
612 	defenseFleets[defenseDeployed]->Enter(*GetSystem(), defenders, this);
613 	ships.insert(ships.begin(), defenders.begin(), end);
614 
615 	// All defenders use a special personality.
616 	Personality defenderPersonality = Personality::Defender();
617 	for(auto it = defenders.begin(); it != end; ++it)
618 		(**it).SetPersonality(defenderPersonality);
619 
620 	++defenseDeployed;
621 }
622 
623 
624 
ResetDefense() const625 void Planet::ResetDefense() const
626 {
627 	isDefending = false;
628 	defenseDeployed = 0;
629 	defenders.clear();
630 }
631