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