void OutfitInfoDisplay::UpdateRequirements(const Outfit &outfit, const PlayerInfo &player, bool canSell) { requirementLabels.clear(); requirementValues.clear(); requirementsHeight = 20; int day = player.GetDate().DaysSinceEpoch(); int64_t cost = outfit.Cost(); int64_t buyValue = player.StockDepreciation().Value(&outfit, day); int64_t sellValue = player.FleetDepreciation().Value(&outfit, day); if(buyValue == cost) requirementLabels.push_back("cost:"); else { ostringstream out; out << "cost (" << (100 * buyValue) / cost << "%):"; requirementLabels.push_back(out.str()); } requirementValues.push_back(Format::Number(buyValue)); requirementsHeight += 20; if(canSell && sellValue != buyValue) { if(sellValue == cost) requirementLabels.push_back("sells for:"); else { ostringstream out; out << "sells for (" << (100 * sellValue) / cost << "%):"; requirementLabels.push_back(out.str()); } requirementValues.push_back(Format::Number(sellValue)); requirementsHeight += 20; } static const string names[] = { "outfit space needed:", "outfit space", "weapon capacity needed:", "weapon capacity", "engine capacity needed:", "engine capacity", "gun ports needed:", "gun ports", "turret mounts needed:", "turret mounts" }; static const int NAMES = sizeof(names) / sizeof(names[0]); for(int i = 0; i + 1 < NAMES; i += 2) if(outfit.Get(names[i + 1])) { requirementLabels.push_back(string()); requirementValues.push_back(string()); requirementsHeight += 10; requirementLabels.push_back(names[i]); requirementValues.push_back(Format::Number(-outfit.Get(names[i + 1]))); requirementsHeight += 20; } }
// "Instantiate" a mission by replacing randomly selected values and places // with a single choice, and then replacing any wildcard text as well. Mission Mission::Instantiate(const PlayerInfo &player) const { Mission result; // If anything goes wrong below, this mission should not be offered. result.hasFailed = true; result.isVisible = isVisible; result.hasPriority = hasPriority; result.isMinor = isMinor; result.autosave = autosave; result.location = location; result.repeat = repeat; result.name = name; result.waypoints = waypoints; // If one of the waypoints is the current system, it is already visited. auto it = result.waypoints.find(player.GetSystem()); if(it != result.waypoints.end()) result.waypoints.erase(it); // First, pick values for all the variables. // If a specific destination is not specified in the mission, pick a random // one out of all the destinations that satisfy the mission requirements. result.destination = destination; if(!result.destination && !destinationFilter.IsEmpty()) { // Find a destination that satisfies the filter. vector<const Planet *> options; for(const auto &it : GameData::Planets()) { // Skip entries with incomplete data. if(it.second.Name().empty() || (clearance.empty() && !it.second.CanLand())) continue; if(it.second.IsWormhole() || !it.second.HasSpaceport()) continue; if(destinationFilter.Matches(&it.second, player.GetSystem())) options.push_back(&it.second); } if(!options.empty()) result.destination = options[Random::Int(options.size())]; else return result; } // If no destination is specified, it is the same as the source planet. Also // use the source planet if the given destination is not a valid planet name. if(!result.destination || !result.destination->GetSystem()) { if(player.GetPlanet()) result.destination = player.GetPlanet(); else return result; } // If cargo is being carried, see if we are supposed to replace a generic // cargo name with something more specific. if(!cargo.empty()) { const Trade::Commodity *commodity = nullptr; if(cargo == "random") commodity = PickCommodity(*player.GetSystem(), *result.destination->GetSystem()); else { for(const Trade::Commodity &option : GameData::Commodities()) if(option.name == cargo) { commodity = &option; break; } } if(commodity) result.cargo = commodity->items[Random::Int(commodity->items.size())]; else result.cargo = cargo; } // Pick a random cargo amount, if requested. if(cargoSize || cargoLimit) { if(cargoProb) result.cargoSize = Random::Polya(cargoLimit, cargoProb) + cargoSize; else if(cargoLimit > cargoSize) result.cargoSize = cargoSize + Random::Int(cargoLimit - cargoSize + 1); else result.cargoSize = cargoSize; } // Pick a random passenger count, if requested. if(passengers | passengerLimit) { if(passengerProb) result.passengers = Random::Polya(passengerLimit, passengerProb) + passengers; else if(passengerLimit > passengers) result.passengers = passengers + Random::Int(passengerLimit - passengers + 1); else result.passengers = passengers; } result.illegalCargoFine = illegalCargoFine; // How far is it to the destination? DistanceMap distance(player.GetSystem()); int jumps = distance.Distance(result.destination->GetSystem()); int defaultPayment = (jumps + 1) * (150 * result.cargoSize + 1500 * result.passengers); int defaultDeadline = doDefaultDeadline ? (2 * jumps) : 0; // Set the deadline, if requested. if(daysToDeadline || defaultDeadline) { result.hasDeadline = true; result.deadline = player.GetDate() + (defaultDeadline + daysToDeadline); } // Copy the completion conditions. No need to copy the offer conditions, // because they have already been checked. result.toComplete = toComplete; result.toFail = toFail; // Generate the substitutions map. map<string, string> subs; subs["<commodity>"] = result.cargo; subs["<tons>"] = to_string(result.cargoSize) + (result.cargoSize == 1 ? " ton" : " tons"); subs["<cargo>"] = subs["<tons>"] + " of " + subs["<commodity>"]; subs["<bunks>"] = to_string(result.passengers); subs["<passengers>"] = (result.passengers == 1) ? "passenger" : "passengers"; subs["<fare>"] = (result.passengers == 1) ? "a passenger" : (subs["<bunks>"] + " passengers"); if(player.GetPlanet()) subs["<origin>"] = player.GetPlanet()->Name(); else if(player.BoardingShip()) subs["<origin>"] = player.BoardingShip()->Name(); subs["<planet>"] = result.destination ? result.destination->Name() : ""; subs["<system>"] = result.destination ? result.destination->GetSystem()->Name() : ""; subs["<destination>"] = subs["<planet>"] + " in the " + subs["<system>"] + " system"; subs["<date>"] = result.deadline.ToString(); subs["<day>"] = result.deadline.LongString(); // Instantiate the NPCs. This also fills in the "<npc>" substitution. for(const NPC &npc : npcs) result.npcs.push_back(npc.Instantiate(subs, player.GetSystem())); // Instantiate the actions. The "complete" action is always first so that // the "<payment>" substitution can be filled in. for(const auto &it : actions) result.actions[it.first] = it.second.Instantiate(subs, defaultPayment); for(const auto &it : onEnter) result.onEnter[it.first] = it.second.Instantiate(subs, defaultPayment); // Perform substitution in the name and description. result.displayName = Format::Replace(displayName, subs); result.description = Format::Replace(description, subs); result.clearance = Format::Replace(clearance, subs); result.blocked = Format::Replace(blocked, subs); result.clearanceFilter = clearanceFilter; result.hasFullClearance = hasFullClearance; result.hasFailed = false; return result; }
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()); }
// "Instantiate" a mission by replacing randomly selected values and places // with a single choice, and then replacing any wildcard text as well. Mission Mission::Instantiate(const PlayerInfo &player) const { Mission result; // If anything goes wrong below, this mission should not be offered. result.hasFailed = true; result.isVisible = isVisible; result.hasPriority = hasPriority; result.isMinor = isMinor; result.autosave = autosave; result.location = location; result.repeat = repeat; result.name = name; result.waypoints = waypoints; // If one of the waypoints is the current system, it is already visited. auto it = result.waypoints.find(player.GetSystem()); if(it != result.waypoints.end()) result.waypoints.erase(it); // Copy the stopover planet list, and populate the list based on the filters // that were given. result.stopovers = stopovers; // Make sure they all exist in a valid system. for(auto it = result.stopovers.begin(); it != result.stopovers.end(); ) { if((*it)->GetSystem()) ++it; else it = result.stopovers.erase(it); } for(const LocationFilter &filter : stopoverFilters) { const Planet *planet = PickPlanet(filter, player); if(!planet) return result; result.stopovers.insert(planet); } // First, pick values for all the variables. // If a specific destination is not specified in the mission, pick a random // one out of all the destinations that satisfy the mission requirements. result.destination = destination; if(!result.destination && !destinationFilter.IsEmpty()) { result.destination = PickPlanet(destinationFilter, player); if(!result.destination) return result; } // If no destination is specified, it is the same as the source planet. Also // use the source planet if the given destination is not a valid planet name. if(!result.destination || !result.destination->GetSystem()) { if(player.GetPlanet()) result.destination = player.GetPlanet(); else return result; } // If cargo is being carried, see if we are supposed to replace a generic // cargo name with something more specific. if(!cargo.empty()) { const Trade::Commodity *commodity = nullptr; if(cargo == "random") commodity = PickCommodity(*player.GetSystem(), *result.destination->GetSystem()); else { for(const Trade::Commodity &option : GameData::Commodities()) if(option.name == cargo) { commodity = &option; break; } for(const Trade::Commodity &option : GameData::SpecialCommodities()) if(option.name == cargo) { commodity = &option; break; } } if(commodity) result.cargo = commodity->items[Random::Int(commodity->items.size())]; else result.cargo = cargo; } // Pick a random cargo amount, if requested. if(cargoSize || cargoLimit) { if(cargoProb) result.cargoSize = Random::Polya(cargoLimit, cargoProb) + cargoSize; else if(cargoLimit > cargoSize) result.cargoSize = cargoSize + Random::Int(cargoLimit - cargoSize + 1); else result.cargoSize = cargoSize; } // Pick a random passenger count, if requested. if(passengers | passengerLimit) { if(passengerProb) result.passengers = Random::Polya(passengerLimit, passengerProb) + passengers; else if(passengerLimit > passengers) result.passengers = passengers + Random::Int(passengerLimit - passengers + 1); else result.passengers = passengers; } result.illegalCargoFine = illegalCargoFine; // How far is it to the destination? DistanceMap distance(player.GetSystem()); int jumps = distance.Distance(result.destination->GetSystem()); int payload = result.cargoSize + 10 * result.passengers; // Set the deadline, if requested. if(deadlineBase || deadlineMultiplier) result.deadline = player.GetDate() + deadlineBase + deadlineMultiplier * jumps; // Copy the conditions. The offer conditions must be copied too, because they // may depend on a condition that other mission offers might change. result.toOffer = toOffer; result.toComplete = toComplete; result.toFail = toFail; // Generate the substitutions map. map<string, string> subs; subs["<commodity>"] = result.cargo; subs["<tons>"] = to_string(result.cargoSize) + (result.cargoSize == 1 ? " ton" : " tons"); subs["<cargo>"] = subs["<tons>"] + " of " + subs["<commodity>"]; subs["<bunks>"] = to_string(result.passengers); subs["<passengers>"] = (result.passengers == 1) ? "passenger" : "passengers"; subs["<fare>"] = (result.passengers == 1) ? "a passenger" : (subs["<bunks>"] + " passengers"); if(player.GetPlanet()) subs["<origin>"] = player.GetPlanet()->Name(); else if(player.BoardingShip()) subs["<origin>"] = player.BoardingShip()->Name(); subs["<planet>"] = result.destination ? result.destination->Name() : ""; subs["<system>"] = result.destination ? result.destination->GetSystem()->Name() : ""; subs["<destination>"] = subs["<planet>"] + " in the " + subs["<system>"] + " system"; subs["<date>"] = result.deadline.ToString(); subs["<day>"] = result.deadline.LongString(); if(!result.stopovers.empty()) { string planets; const Planet * const *last = &*--result.stopovers.end(); int count = 0; // Iterate by reference to the pointers so we can check when we're at // the very last one in the set. for(const Planet * const &planet : result.stopovers) { if(count++) planets += (&planet != last) ? ", " : (count > 2 ? ", and " : " and "); planets += planet->Name() + " in the " + planet->GetSystem()->Name() + " system"; } subs["<stopovers>"] = planets; } // Instantiate the NPCs. This also fills in the "<npc>" substitution. for(const NPC &npc : npcs) result.npcs.push_back(npc.Instantiate(subs, player.GetSystem())); // Instantiate the actions. The "complete" action is always first so that // the "<payment>" substitution can be filled in. for(const auto &it : actions) result.actions[it.first] = it.second.Instantiate(subs, jumps, payload); for(const auto &it : onEnter) result.onEnter[it.first] = it.second.Instantiate(subs, jumps, payload); // Perform substitution in the name and description. result.displayName = Format::Replace(displayName, subs); result.description = Format::Replace(description, subs); result.clearance = Format::Replace(clearance, subs); result.blocked = Format::Replace(blocked, subs); result.clearanceFilter = clearanceFilter; result.hasFullClearance = hasFullClearance; result.hasFailed = false; return result; }
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(); }