// Check if this action can be completed right now. It cannot be completed // if it takes away money or outfits that the player does not have. bool MissionAction::CanBeDone(const PlayerInfo &player) const { if(player.Accounts().Credits() < -payment) return false; const Ship *flagship = player.Flagship(); for(const auto &it : gifts) { if(it.second > 0) continue; // The outfit can be taken from the player's cargo or from the flagship. int available = player.Cargo().Get(it.first); for(const auto &ship : player.Ships()) available += ship->Cargo().Get(it.first); if(flagship) available += flagship->OutfitCount(it.first); // If the gift "count" is 0, that means to check that the player has at // least one of these items. if(available < -it.second + !it.second) 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) 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; }
BankPanel::BankPanel(PlayerInfo &player) : player(player), qualify(player.Accounts().Prequalify()), selectedRow(0) { SetTrapAllEvents(false); }
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()); }
// Check to see if the player has done anything they should be fined for. string Politics::Fine(PlayerInfo &player, const Government *gov, int scan, const Ship *target, double security) { // Do nothing if you have already been fined today, or if you evade // detection. auto it = fined.find(gov); if(it != fined.end() || Random::Real() > security || !gov->GetFineFraction()) return ""; string reason; int64_t maxFine = 0; for(const shared_ptr<Ship> &ship : player.Ships()) { // Check if the ship evades being scanned due to interference plating. if(Random::Real() > 1. / (1. + ship->Attributes().Get("scan interference"))) continue; if(target && target != &*ship) continue; if(ship->GetSystem() != player.GetSystem()) continue; if(!scan || (scan & ShipEvent::SCAN_CARGO)) { int64_t fine = ship->Cargo().IllegalCargoFine(); if((fine > maxFine && maxFine >= 0) || fine < 0) { maxFine = fine; reason = "carrying illegal cargo."; } } if(!scan || (scan & ShipEvent::SCAN_OUTFITS)) { for(const auto &it : ship->Outfits()) if(it.second) { int64_t fine = it.first->Get("illegal"); if((fine > maxFine && maxFine >= 0) || fine < 0) { maxFine = fine; reason = "having illegal outfits installed on your ship."; } } } } if(maxFine < 0) { gov->Offend(ShipEvent::ATROCITY); if(!scan) reason = "atrocity"; else reason = "After scanning your ship, the " + gov->GetName() + " captain hails you with a grim expression on his face. He says, \"You are guilty of " + reason + " The penalty for your actions is death. Goodbye.\""; } else if(maxFine > 0) { // Scale the fine based on how lenient this government is. maxFine = maxFine * gov->GetFineFraction() + .5; reason = "The " + gov->GetName() + " fines you " + Format::Number(maxFine) + " credits for " + reason; player.Accounts().AddFine(maxFine); fined.insert(gov); } return reason; }
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(); }