예제 #1
0
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();
}
예제 #2
0
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();
}
예제 #3
0
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());
}
예제 #4
0
InfoPanel::InfoPanel(PlayerInfo &player)
	: player(player), shipIt(player.Ships().begin()), showShip(false), canEdit(player.GetPlanet())
{
	SetInterruptible(false);
	
	UpdateInfo();
}
예제 #5
0
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);
}
예제 #6
0
// 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;
}
예제 #7
0
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();
}
예제 #8
0
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());
}
예제 #9
0
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();
}
예제 #10
0
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();
}
예제 #11
0
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();
}
예제 #12
0
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();
}
예제 #13
0
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;
}
예제 #14
0
// 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;
}
예제 #15
0
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;
}
예제 #16
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;
}
예제 #17
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);
	
	// 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;
}