OutfitterPanel::OutfitterPanel(PlayerInfo &player) : ShopPanel(player, Outfit::CATEGORIES), available(player.SoldOutfits()) { for(const pair<string, Outfit> &it : GameData::Outfits()) catalog[it.second.Category()].insert(it.first); if(player.GetPlanet()) outfitter = player.GetPlanet()->Outfitter(); }
ShipyardPanel::ShipyardPanel(PlayerInfo &player) : ShopPanel(player, false), modifier(0) { for(const auto &it : GameData::Ships()) catalog[it.second.Attributes().Category()].insert(it.first); if(player.GetPlanet()) shipyard = player.GetPlanet()->Shipyard(); }
Engine::Engine(PlayerInfo &player) : player(player), calcTickTock(false), drawTickTock(false), terminate(false), step(0), flash(0.), doFlash(false), wasLeavingHyperspace(false), load(0.), loadCount(0), loadSum(0.) { // Start the thread for doing calculations. calcThread = thread(&Engine::ThreadEntryPoint, this); if(!player.IsLoaded() || !player.GetSystem()) return; // Preload any landscapes for this system. for(const StellarObject &object : player.GetSystem()->Objects()) if(object.GetPlanet()) GameData::Preload(object.GetPlanet()->Landscape()); // Now we know the player's current position. Draw the planets. Point center; if(player.GetPlanet()) { for(const StellarObject &object : player.GetSystem()->Objects()) if(object.GetPlanet() == player.GetPlanet()) center = object.Position(); } for(const StellarObject &object : player.GetSystem()->Objects()) if(!object.GetSprite().IsEmpty()) { Point position = object.Position(); Point unit = object.Unit(); position -= center; int type = object.IsStar() ? Radar::SPECIAL : !object.GetPlanet() ? Radar::INACTIVE : object.GetPlanet()->IsWormhole() ? Radar::ANOMALOUS : GameData::GetPolitics().HasDominated(object.GetPlanet()) ? Radar::PLAYER : object.GetPlanet()->CanLand() ? Radar::FRIENDLY : Radar::HOSTILE; double r = max(2., object.Radius() * .03 + .5); draw[calcTickTock].Add(object.GetSprite(), position, unit); radar[calcTickTock].Add(type, position, r, r - 1.); } // Add all neighboring systems to the radar. const Ship *flagship = player.Flagship(); const System *targetSystem = flagship ? flagship->GetTargetSystem() : nullptr; const vector<const System *> &links = (flagship && flagship->Attributes().Get("jump drive")) ? player.GetSystem()->Neighbors() : player.GetSystem()->Links(); for(const System *system : links) radar[calcTickTock].AddPointer( (system == targetSystem) ? Radar::SPECIAL : Radar::INACTIVE, system->Position() - player.GetSystem()->Position()); }
InfoPanel::InfoPanel(PlayerInfo &player) : player(player), shipIt(player.Ships().begin()), showShip(false), canEdit(player.GetPlanet()) { SetInterruptible(false); UpdateInfo(); }
ShopPanel::ShopPanel(PlayerInfo &player, const vector<string> &categories) : player(player), planet(player.GetPlanet()), playerShip(player.Flagship()), categories(categories) { if(playerShip) playerShips.insert(playerShip); SetIsFullScreen(true); }
// 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; }
LoadPanel::LoadPanel(PlayerInfo &player, UI &gamePanels) : player(player), gamePanels(gamePanels), selectedPilot(player.Identifier()) { // If you have a player loaded, and the player is on a planet, makes sure // the player is saved so that any snapshot you create will be of the // player's current state, rather than one planet ago. if(player.GetPlanet() && !player.IsDead()) player.Save(); UpdateLists(); }
SpaceportPanel::SpaceportPanel(PlayerInfo &player) : player(player) { SetTrapAllEvents(false); text.SetFont(FontSet::Get(14)); text.SetAlignment(WrappedText::JUSTIFIED); text.SetWrapWidth(480); text.Wrap(player.GetPlanet()->SpaceportDescription()); }
LoadPanel::LoadPanel(PlayerInfo &player, UI &gamePanels) : player(player), gamePanels(gamePanels), selectedPilot(player.Identifier()) { // If you have a player loaded, and the player is on a planet, makes sure // the player is saved so that any snapshot you create will be of the // player's current state, rather than one planet ago. Only do this if the // game is paused, i.e. the "main panel" is not on top: if(player.GetPlanet() && !player.IsDead() && !gamePanels.IsTop(&*gamePanels.Root())) player.Save(); UpdateLists(); }
InfoPanel::InfoPanel(PlayerInfo &player, bool showFlagship) : player(player), shipIt(player.Ships().begin()), showShip(showFlagship), canEdit(player.GetPlanet()) { SetInterruptible(false); if(showFlagship) while(shipIt != player.Ships().end() && shipIt->get() != player.Flagship()) ++shipIt; UpdateInfo(); }
ShipInfoPanel::ShipInfoPanel(PlayerInfo &player, int index) : player(player), shipIt(player.Ships().begin()), canEdit(player.GetPlanet()) { SetInterruptible(false); // If a valid ship index was given, show that ship. if(static_cast<unsigned>(index) < player.Ships().size()) shipIt += index; else if(player.Flagship()) { // Find the player's flagship. It may not be first in the list, if the // first item in the list cannot be a flagship. while(shipIt != player.Ships().end() && shipIt->get() != player.Flagship()) ++shipIt; } UpdateInfo(); }
PlanetPanel::PlanetPanel(PlayerInfo &player, function<void()> callback) : player(player), callback(callback), planet(*player.GetPlanet()), system(*player.GetSystem()), ui(*GameData::Interfaces().Get("planet")) { trading.reset(new TradingPanel(player)); bank.reset(new BankPanel(player)); spaceport.reset(new SpaceportPanel(player)); hiring.reset(new HiringPanel(player)); text.SetFont(FontSet::Get(14)); text.SetAlignment(WrappedText::JUSTIFIED); text.SetWrapWidth(480); text.Wrap(planet.Description()); // Since the loading of landscape images is deferred, make sure that the // landscapes for this system are loaded before showing the planet panel. GameData::Preload(planet.Landscape()); GameData::FinishLoading(); }
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; }
int main(int argc, char *argv[]) { Conversation conversation; bool debugMode = false; for(const char *const *it = argv + 1; *it; ++it) { string arg = *it; if(arg == "-h" || arg == "--help") { PrintHelp(); return 0; } else if(arg == "-v" || arg == "--version") { PrintVersion(); return 0; } else if(arg == "-t" || arg == "--talk") conversation = LoadConversation(); else if(arg == "-d" || arg == "--debug") debugMode = true; } PlayerInfo player; try { SDL_Init(SDL_INIT_VIDEO); // Begin loading the game data. GameData::BeginLoad(argv); Audio::Init(GameData::Sources()); // On Windows, make sure that the sleep timer has at least 1 ms resolution // to avoid irregular frame rates. #ifdef _WIN32 timeBeginPeriod(1); #endif player.LoadRecent(); player.ApplyChanges(); // Check how big the window can be. SDL_DisplayMode mode; if(SDL_GetCurrentDisplayMode(0, &mode)) return DoError("Unable to query monitor resolution!"); Preferences::Load(); Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI; if(Preferences::Has("fullscreen")) flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; // Make the window just slightly smaller than the monitor resolution. int maxWidth = mode.w; int maxHeight = mode.h; // Restore this after toggling fullscreen. int restoreWidth = 0; int restoreHeight = 0; if(maxWidth < 640 || maxHeight < 480) return DoError("Monitor resolution is too small!"); if(Screen::RawWidth() && Screen::RawHeight()) { // Never allow the saved screen width to be leaving less than 100 // pixels free around the window. This avoids the problem where you // maximize without going full-screen, and next time the window pops // up you can't access the resize control because it is offscreen. Screen::SetRaw( min(Screen::RawWidth(), (maxWidth - 100)), min(Screen::RawHeight(), (maxHeight - 100))); if(flags & SDL_WINDOW_FULLSCREEN_DESKTOP) { restoreWidth = Screen::RawWidth(); restoreHeight = Screen::RawHeight(); Screen::SetRaw(maxWidth, maxHeight); } } else Screen::SetRaw(maxWidth - 100, maxHeight - 100); // Make sure the zoom factor is not set too high for the full UI to fit. if(Screen::Height() < 700) Screen::SetZoom(100); // Create the window. SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); #ifdef _WIN32 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); #endif SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_Window *window = SDL_CreateWindow("Endless Sky", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, Screen::RawWidth(), Screen::RawHeight(), flags); if(!window) return DoError("Unable to create window!"); SDL_GLContext context = SDL_GL_CreateContext(window); if(!context) return DoError("Unable to create OpenGL context! Check if your system supports OpenGL 3.0.", window); if(SDL_GL_MakeCurrent(window, context)) return DoError("Unable to set the current OpenGL context!", window, context); SDL_GL_SetSwapInterval(1); // Initialize GLEW. #ifndef __APPLE__ glewExperimental = GL_TRUE; if(glewInit() != GLEW_OK) return DoError("Unable to initialize GLEW!", window, context); #endif // Check that the OpenGL version is high enough. const char *glVersion = reinterpret_cast<const char *>(glGetString(GL_VERSION)); if(!glVersion || !*glVersion) return DoError("Unable to query the OpenGL version!", window, context); const char *glslVersion = reinterpret_cast<const char *>(glGetString(GL_SHADING_LANGUAGE_VERSION)); if(!glslVersion || !*glslVersion) { ostringstream out; out << "Unable to query the GLSL version. OpenGL version is " << glVersion << "."; return DoError(out.str(), window, context); } if(*glVersion < '3') { ostringstream out; out << "Endless Sky requires OpenGL version 3.0 or higher." << endl; out << "Your OpenGL version is " << glVersion << ", GLSL version " << glslVersion << "." << endl; out << "Please update your graphics drivers."; return DoError(out.str(), window, context); } glClearColor(0.f, 0.f, 0.0f, 1.f); glEnable(GL_BLEND); glDisable(GL_DEPTH_TEST); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); GameData::LoadShaders(); { // Check whether this is a high-DPI window. int width = 0; int height = 0; SDL_GL_GetDrawableSize(window, &width, &height); Screen::SetHighDPI(width > Screen::RawWidth() && height > Screen::RawHeight()); // Fix a possible race condition leading to the wrong window dimensions. glViewport(0, 0, width, height); } UI gamePanels; UI menuPanels; menuPanels.Push(new MenuPanel(player, gamePanels)); if(!conversation.IsEmpty()) menuPanels.Push(new ConversationPanel(player, conversation)); string swizzleName = "_texture_swizzle"; #ifndef __APPLE__ const char *extensions = reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)); if(!strstr(extensions, swizzleName.c_str())) #else bool hasSwizzle = false; GLint extensionCount; glGetIntegerv(GL_NUM_EXTENSIONS, &extensionCount); for(GLint i = 0; i < extensionCount && !hasSwizzle; ++i) { const char *extension = reinterpret_cast<const char *>(glGetStringi(GL_EXTENSIONS, i)); hasSwizzle = (extension && strstr(extension, swizzleName.c_str())); } if(!hasSwizzle) #endif menuPanels.Push(new Dialog( "Note: your computer does not support the \"texture swizzling\" OpenGL feature, " "which Endless Sky uses to draw ships in different colors depending on which " "government they belong to. So, all human ships will be the same color, which " "may be confusing. Consider upgrading your graphics driver (or your OS).")); FrameTimer timer(60); bool isPaused = false; while(!menuPanels.IsDone()) { // Handle any events that occurred in this frame. SDL_Event event; while(SDL_PollEvent(&event)) { UI &activeUI = (menuPanels.IsEmpty() ? gamePanels : menuPanels); // The caps lock key slows the game down (to make it easier to // see and debug things that are happening quickly). if(debugMode && (event.type == SDL_KEYDOWN || event.type == SDL_KEYUP) && event.key.keysym.sym == SDLK_CAPSLOCK) { timer.SetFrameRate((event.key.keysym.mod & KMOD_CAPS) ? 10 : 60); } else if(debugMode && event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_BACKQUOTE) { isPaused = !isPaused; } else if(event.type == SDL_KEYDOWN && menuPanels.IsEmpty() && Command(event.key.keysym.sym).Has(Command::MENU) && !gamePanels.IsEmpty() && gamePanels.Top()->IsInterruptible()) { menuPanels.Push(shared_ptr<Panel>( new MenuPanel(player, gamePanels))); } else if(event.type == SDL_QUIT) { menuPanels.Quit(); } else if(event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { int width = event.window.data1 & ~1; int height = event.window.data2 & ~1; if(width != Screen::RawWidth() || height != Screen::RawHeight()) { Screen::SetRaw(width, height); if((event.window.data1 | event.window.data2) & 1) SDL_SetWindowSize(window, Screen::RawWidth(), Screen::RawHeight()); SDL_GL_GetDrawableSize(window, &width, &height); glViewport(0, 0, width, height); } } else if(event.type == SDL_KEYDOWN && (Command(event.key.keysym.sym).Has(Command::FULLSCREEN) || (event.key.keysym.sym == SDLK_RETURN && event.key.keysym.mod & KMOD_ALT))) { if(restoreWidth) { SDL_SetWindowFullscreen(window, 0); Screen::SetRaw(restoreWidth, restoreHeight); SDL_SetWindowSize(window, Screen::RawWidth(), Screen::RawHeight()); restoreWidth = 0; restoreHeight = 0; } else { restoreWidth = Screen::RawWidth(); restoreHeight = Screen::RawHeight(); Screen::SetRaw(maxWidth, maxHeight); SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP); } int width, height; SDL_GL_GetDrawableSize(window, &width, &height); glViewport(0, 0, width, height); } else if(activeUI.Handle(event)) { // No need to do anything more! } } Font::ShowUnderlines(SDL_GetModState() & KMOD_ALT); // Tell all the panels to step forward, then draw them. ((!isPaused && menuPanels.IsEmpty()) ? gamePanels : menuPanels).StepAll(); Audio::Step(); // That may have cleared out the menu, in which case we should draw // the game panels instead: (menuPanels.IsEmpty() ? gamePanels : menuPanels).DrawAll(); SDL_GL_SwapWindow(window); timer.Wait(); } // If you quit while landed on a planet, save the game. if(player.GetPlanet()) player.Save(); // The Preferences class reads the screen dimensions, so update them if // the window is full screen: bool isFullscreen = (restoreWidth != 0); Preferences::Set("fullscreen", isFullscreen); if(isFullscreen) Screen::SetRaw(restoreWidth, restoreHeight); Preferences::Save(); Cleanup(window, context); } catch(const runtime_error &error) { DoError(error.what()); } return 0; }
// "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; }
// "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; }