// When the state of this mission changes, it may make changes to the player // information or show new UI panels. PlayerInfo::MissionCallback() will be // used as the callback for any UI panel that returns a value. bool Mission::Do(Trigger trigger, PlayerInfo &player, UI *ui) const { if(trigger == ACCEPT) { ++player.Conditions()[name + ": offered"]; ++player.Conditions()[name + ": active"]; } else if(trigger == DECLINE) ++player.Conditions()[name + ": offered"]; else if(trigger == FAIL) --player.Conditions()[name + ": active"]; else if(trigger == COMPLETE) { --player.Conditions()[name + ": active"]; ++player.Conditions()[name + ": done"]; } // "Jobs" should never show dialogs when offered, nor should they call the // player's mission callback. if(trigger == OFFER && location == JOB) ui = nullptr; auto it = actions.find(trigger); if(it == actions.end()) { // If a mission has no "on offer" field, it is automatically accepted. if(trigger == OFFER && location != JOB) player.MissionCallback(Conversation::ACCEPT); return true; } if(!it->second.CanBeDone(player)) return false; // Set a condition for the player's net worth. Limit it to the range of a 32-bit int. static const int64_t limit = 2000000000; player.Conditions()["net worth"] = min(limit, max(-limit, player.Accounts().NetWorth())); // Set the "reputation" conditions so we can check if this action changed // any of them. for(const auto &it : GameData::Governments()) { int rep = it.second.Reputation(); player.Conditions()["reputation: " + it.first] = rep; } it->second.Do(player, ui, destination ? destination->GetSystem() : nullptr); // Check if any reputation conditions were updated. for(const auto &it : GameData::Governments()) { int rep = it.second.Reputation(); int newRep = player.Conditions()["reputation: " + it.first]; if(newRep != rep) it.second.AddReputation(newRep - rep); } return true; }
void GameEvent::Apply(PlayerInfo &player) { // Serialize the current reputation with other governments. player.SetReputationConditions(); // Apply this event's ConditionSet to the player's conditions. conditionsToApply.Apply(player.Conditions()); // Apply (and store a record of applying) this event's other general // changes (e.g. updating an outfitter's inventory). player.AddChanges(changes); // Update the current reputation with other governments (e.g. this // event's ConditionSet may have altered some reputations). player.CheckReputationConditions(); for(const System *system : systemsToUnvisit) player.Unvisit(system); for(const Planet *planet : planetsToUnvisit) player.Unvisit(planet); // Perform visits after unvisits, as "unvisit <system>" // will unvisit any planets in that system. for(const System *system : systemsToVisit) player.Visit(system); for(const Planet *planet : planetsToVisit) player.Visit(planet); }
// Demand tribute, and get the planet's response. string Planet::DemandTribute(PlayerInfo &player) const { if(player.GetCondition("tribute: " + name)) return "We are already paying you as much as we can afford."; if(!tribute || !defenseFleet || !defenseCount || player.GetCondition("combat rating") < defenseThreshold) return "Please don't joke about that sort of thing."; // The player is scary enough for this planet to take notice. Check whether // this is the first demand for tribute, or not. if(!isDefending) { isDefending = true; GameData::GetPolitics().Offend(defenseFleet->GetGovernment(), ShipEvent::PROVOKE); GameData::GetPolitics().Offend(GetGovernment(), ShipEvent::PROVOKE); return "Our defense fleet will make short work of you."; } // The player has already demanded tribute. Have they killed off the entire // defense fleet? bool isDefeated = (defenseDeployed == defenseCount); for(const shared_ptr<Ship> &ship : defenders) if(!ship->IsDisabled() && !ship->IsYours()) { isDefeated = false; break; } if(!isDefeated) return "We're not ready to surrender yet."; player.Conditions()["tribute: " + name] = tribute; GameData::GetPolitics().DominatePlanet(this); return "We surrender. We will pay you " + Format::Number(tribute) + " credits per day to leave us alone."; }
// Check if it's possible to offer or complete this mission right now. bool Mission::CanOffer(const PlayerInfo &player) const { if(location == BOARDING || location == ASSISTING) { if(!player.BoardingShip()) return false; if(!sourceFilter.Matches(*player.BoardingShip())) return false; } else { if(source && source != player.GetPlanet()) return false; if(!sourceFilter.Matches(player.GetPlanet())) return false; } if(!toOffer.Test(player.Conditions())) return false; if(!toFail.IsEmpty() && toFail.Test(player.Conditions())) return false; if(repeat) { auto cit = player.Conditions().find(name + ": offered"); if(cit != player.Conditions().end() && cit->second >= repeat) return false; } auto it = actions.find(OFFER); if(it != actions.end() && !it->second.CanBeDone(player)) return false; it = actions.find(ACCEPT); if(it != actions.end() && !it->second.CanBeDone(player)) return false; it = actions.find(DECLINE); if(it != actions.end() && !it->second.CanBeDone(player)) return false; return true; }
bool Mission::HasFailed(const PlayerInfo &player) const { if(!toFail.IsEmpty() && toFail.Test(player.Conditions())) return true; for(const NPC &npc : npcs) if(npc.HasFailed()) return true; return hasFailed; }
void GameEvent::Apply(PlayerInfo &player) { for(const auto &it : GameData::Governments()) { int rep = it.second.Reputation(); player.Conditions()["reputation: " + it.first] = rep; } conditionsToApply.Apply(player.Conditions()); player.AddChanges(changes); for(const auto &it : GameData::Governments()) { int rep = it.second.Reputation(); int newRep = player.Conditions()["reputation: " + it.first]; if(rep != newRep) it.second.AddReputation(newRep - rep); } for(const System *system : systemsToUnvisit) player.Unvisit(system); }
// Demand tribute, and get the planet's response. string Planet::DemandTribute(PlayerInfo &player) const { if(player.GetCondition("tribute: " + name)) return "We are already paying you as much as we can afford."; if(!tribute || defenseFleets.empty()) return "Please don't joke about that sort of thing."; if(player.GetCondition("combat rating") < defenseThreshold) return "You're not worthy of our time."; // The player is scary enough for this planet to take notice. Check whether // this is the first demand for tribute, or not. if(!isDefending) { isDefending = true; set<const Government *> toProvoke; for(const auto &fleet : defenseFleets) toProvoke.insert(fleet->GetGovernment()); for(const auto &gov : toProvoke) gov->Offend(ShipEvent::PROVOKE); // Terrorizing a planet is not taken lightly by it or its allies. GetGovernment()->Offend(ShipEvent::ATROCITY); return "Our defense fleet will make short work of you."; } // The player has already demanded tribute. Have they defeated the entire defense fleet? bool isDefeated = (defenseDeployed == defenseFleets.size()); for(const shared_ptr<Ship> &ship : defenders) if(!ship->IsDisabled() && !ship->IsYours()) { isDefeated = false; break; } if(!isDefeated) return "We're not ready to surrender yet."; player.Conditions()["tribute: " + name] = tribute; GameData::GetPolitics().DominatePlanet(this); return "We surrender. We will pay you " + Format::Credits(tribute) + " credits per day to leave us alone."; }
bool Mission::CanComplete(const PlayerInfo &player) const { if(player.GetPlanet() != destination || !waypoints.empty()) return false; if(!toComplete.Test(player.Conditions())) return false; auto it = actions.find(COMPLETE); if(it != actions.end() && !it->second.CanBeDone(player)) return false; for(const NPC &npc : npcs) if(!npc.HasSucceeded(player.GetSystem())) return false; // If any of the cargo for this mission is being carried by a ship that is // not in this system, the mission cannot be completed right now. for(const auto &ship : player.Ships()) if(ship->GetSystem() != player.GetSystem() && ship->Cargo().Get(this)) return false; return true; }
// When the state of this mission changes, it may make changes to the player // information or show new UI panels. PlayerInfo::MissionCallback() will be // used as the callback for any UI panel that returns a value. bool Mission::Do(Trigger trigger, PlayerInfo &player, UI *ui) { if(trigger == STOPOVER) { // If this is not one of this mission's stopover planets, or if it is // not the very last one that must be visited, do nothing. auto it = stopovers.find(player.GetPlanet()); if(it == stopovers.end()) return false; for(const NPC &npc : npcs) if(npc.IsLeftBehind(player.GetSystem())) { ui->Push(new Dialog("This is a stop for one of your missions, but you have left a ship behind.")); return false; } stopovers.erase(it); if(!stopovers.empty()) return false; } if(trigger == ACCEPT) { ++player.Conditions()[name + ": offered"]; ++player.Conditions()[name + ": active"]; } else if(trigger == DECLINE) ++player.Conditions()[name + ": offered"]; else if(trigger == FAIL) --player.Conditions()[name + ": active"]; else if(trigger == COMPLETE) { --player.Conditions()[name + ": active"]; ++player.Conditions()[name + ": done"]; } // "Jobs" should never show dialogs when offered, nor should they call the // player's mission callback. if(trigger == OFFER && location == JOB) ui = nullptr; auto it = actions.find(trigger); if(it == actions.end()) { // If a mission has no "on offer" field, it is automatically accepted. if(trigger == OFFER && location != JOB) player.MissionCallback(Conversation::ACCEPT); return true; } if(!it->second.CanBeDone(player)) return false; // Set the "reputation" conditions so we can check if this action changed // any of them. for(const auto &it : GameData::Governments()) { int rep = it.second.Reputation(); player.Conditions()["reputation: " + it.first] = rep; } it->second.Do(player, ui, destination ? destination->GetSystem() : nullptr); // Check if any reputation conditions were updated. for(const auto &it : GameData::Governments()) { int rep = it.second.Reputation(); int newRep = player.Conditions()["reputation: " + it.first]; if(newRep != rep) it.second.AddReputation(newRep - rep); } return true; }
void MissionAction::Do(PlayerInfo &player, UI *ui, const System *destination) const { bool isOffer = (trigger == "offer"); if(!conversation.IsEmpty()) { ConversationPanel *panel = new ConversationPanel(player, conversation, destination); if(isOffer) panel->SetCallback(&player, &PlayerInfo::MissionCallback); ui->Push(panel); } else if(!dialogText.empty()) { map<string, string> subs; subs["<first>"] = player.FirstName(); subs["<last>"] = player.LastName(); if(player.Flagship()) subs["<ship>"] = player.Flagship()->Name(); string text = Format::Replace(dialogText, subs); if(isOffer) ui->Push(new Dialog(text, player, destination)); else ui->Push(new Dialog(text)); } else if(isOffer && ui) player.MissionCallback(Conversation::ACCEPT); Ship *flagship = player.Flagship(); for(const auto &it : gifts) { int count = it.second; string name = it.first->Name(); if(!count || name.empty()) continue; string message; if(abs(count) == 1) { char c = tolower(name.front()); bool isVowel = (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u'); message = (isVowel ? "An " : "A ") + name + " was "; } else message = to_string(abs(count)) + " " + name + "s were "; if(count > 0) message += "added to your "; else message += "removed from your "; bool didCargo = false; bool didShip = false; int cargoCount = player.Cargo().Get(it.first); if(count < 0 && cargoCount) { int moved = min(cargoCount, -count); count += moved; player.Cargo().Transfer(it.first, moved); didCargo = true; } while(flagship && count) { int moved = (count > 0) ? 1 : -1; if(flagship->Attributes().CanAdd(*it.first, moved)) { flagship->AddOutfit(it.first, moved); didShip = true; } else break; count -= moved; } if(count > 0) { player.Cargo().Transfer(it.first, -count); didCargo = true; if(count > 0) { string special = "The " + name + (count == 1 ? " was" : "s were"); special += " put in your cargo hold because there is not enough space to install "; special += (count == 1) ? "it" : "them"; special += " in your ship."; ui->Push(new Dialog(special)); } } if(didCargo && didShip) message += "cargo hold and your flagship."; else if(didCargo) message += "cargo hold."; else message += "flagship."; Messages::Add(message); } if(payment) player.Accounts().AddCredits(payment); for(const auto &it : events) player.AddEvent(*GameData::Events().Get(it.first), player.GetDate() + it.second); conditions.Apply(player.Conditions()); }
void MissionAction::Do(PlayerInfo &player, UI *ui, const System *destination) const { bool isOffer = (trigger == "offer"); if(!conversation.IsEmpty() && ui) { ConversationPanel *panel = new ConversationPanel(player, conversation, destination); if(isOffer) panel->SetCallback(&player, &PlayerInfo::MissionCallback); ui->Push(panel); } else if(!dialogText.empty() && ui) { map<string, string> subs; subs["<first>"] = player.FirstName(); subs["<last>"] = player.LastName(); if(player.Flagship()) subs["<ship>"] = player.Flagship()->Name(); string text = Format::Replace(dialogText, subs); if(isOffer) ui->Push(new Dialog(text, player, destination)); else ui->Push(new Dialog(text)); } else if(isOffer && ui) player.MissionCallback(Conversation::ACCEPT); // If multiple outfits are being transferred, first remove them before // adding any new ones. for(const auto &it : gifts) if(it.second < 0) DoGift(player, it.first, it.second, ui); for(const auto &it : gifts) if(it.second > 0) DoGift(player, it.first, it.second, ui); if(payment) player.Accounts().AddCredits(payment); for(const auto &it : events) player.AddEvent(*GameData::Events().Get(it.first), player.GetDate() + it.second); if(!fail.empty()) { // Failing missions invalidates iterators into the player's mission list, // but does not immediately delete those missions. So, the safe way to // iterate over all missions is to make a copy of the list before we // begin to remove items from it. vector<const Mission *> failedMissions; for(const Mission &mission : player.Missions()) if(fail.count(mission.Identifier())) failedMissions.push_back(&mission); for(const Mission *mission : failedMissions) player.RemoveMission(Mission::FAIL, *mission, ui); } // Check if applying the conditions changes the player's reputations. player.SetReputationConditions(); conditions.Apply(player.Conditions()); player.CheckReputationConditions(); }