// 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; }
// "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; }
// "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; }