Exemple #1
0
void AI::DoScatter(Ship &ship, Command &command, const list<shared_ptr<Ship>> &ships)
{
	if(!command.Has(Command::FORWARD))
		return;
	
	double turnRate = ship.TurnRate();
	double acceleration = ship.Acceleration();
	for(const shared_ptr<Ship> &other : ships)
	{
		if(other.get() == &ship)
			continue;
		
		// Check for any ships that have nearly the same movement profile as
		// this ship and are in nearly the same location.
		Point offset = other->Position() - ship.Position();
		if(offset.LengthSquared() > 400.)
			continue;
		if(fabs(other->TurnRate() / turnRate - 1.) > .05)
			continue;
		if(fabs(other->Acceleration() / acceleration - 1.) > .05)
			continue;
		
		// Move away from this ship. What side of me is it on?
		command.SetTurn(offset.Cross(ship.Facing().Unit()) > 0. ? 1. : -1.);
		return;
	}
}
Exemple #2
0
bool MenuPanel::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command)
{
	if(!isReady)
		return false;
	
	if(player.IsLoaded() && (key == 'e' || command.Has(Command::MENU)))
		GetUI()->Pop(this);
	else if(key == 'p')
		GetUI()->Push(new PreferencesPanel());
	else if(key == 'l')
		GetUI()->Push(new LoadPanel(player, gamePanels));
	else if(key == 'n' && (!player.IsLoaded() || player.IsDead()))
	{
		// If no player is loaded, the "Enter Ship" button becomes "New Pilot."
		player.New();
		
		ConversationPanel *panel = new ConversationPanel(
			player, *GameData::Conversations().Get("intro"));
		GetUI()->Push(panel);
		panel->SetCallback(this, &MenuPanel::OnCallback);
	}
	else if(key == 'q')
		GetUI()->Quit();
	else
		return false;
	
	return true;
}
bool MapSalesPanel::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command)
{
	if(command.Has(Command::MAP) || key == 'd' || key == SDLK_ESCAPE || (key == 'w' && (mod & (KMOD_CTRL | KMOD_GUI))))
		GetUI()->Pop(this);
	else if(key == 's' && isOutfitters)
	{
		GetUI()->Pop(this);
		GetUI()->Push(new MapShipyardPanel(*this));
	}
	else if(key == 'o' && !isOutfitters)
	{
		GetUI()->Pop(this);
		GetUI()->Push(new MapOutfitterPanel(*this));
	}
	else if(key == 'i')
	{
		GetUI()->Pop(this);
		GetUI()->Push(new MissionPanel(*this));
	}
	else if(key == 'p')
	{
		GetUI()->Pop(this);
		GetUI()->Push(new MapDetailPanel(*this));
	}
	else if(key == SDLK_PAGEUP || key == SDLK_PAGEDOWN)
	{
		scroll += (Screen::Height() - 100) * ((key == SDLK_PAGEUP) - (key == SDLK_PAGEDOWN));
		scroll = min(0, max(-maxScroll, scroll));
	}
	else if((key == SDLK_DOWN || key == SDLK_UP) && !zones.empty())
	{
		selected += (key == SDLK_DOWN) - (key == SDLK_UP);
		if(selected < 0)
			selected = zones.size() - 1;
		else if(selected > static_cast<int>(zones.size() - 1))
			selected = 0;
		
		const ClickZone<int> &it = zones[selected];
		double top = (it.Center() - it.Size()).Y();
		double bottom = (it.Center() + it.Size()).Y();
		if(bottom > Screen::Bottom())
			scroll += Screen::Bottom() - bottom;
		if(top < Screen::Top())
			scroll += Screen::Top() - top;
		
		Compare(compare = -1);
		Select(selected);
	}
	else if(key == '+' || key == '=')
		ZoomMap();
	else if(key == '-')
		UnzoomMap();
	else
		return false;
	
	return true;
}
// Only override the ones you need; the default action is to return false.
bool MainPanel::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command)
{
	if(command.Has(Command::MAP | Command::INFO | Command::HAIL))
		show = command;
	else
		return false;
	
	return true;
}
Exemple #5
0
bool InfoPanel::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command)
{
	if(key == 'd' || key == SDLK_ESCAPE || (key == 'w' && (mod & (KMOD_CTRL | KMOD_GUI))))
		GetUI()->Pop(this);
	else if(showShip && !player.Ships().empty() && (key == 'p' || key == SDLK_LEFT || key == SDLK_UP))
	{
		if(shipIt == player.Ships().begin())
			shipIt = player.Ships().end();
		--shipIt;
		UpdateInfo();
	}
	else if(showShip && !player.Ships().empty() && (key == 'n' || key == SDLK_RIGHT || key == SDLK_DOWN))
	{
		++shipIt;
		if(shipIt == player.Ships().end())
			shipIt = player.Ships().begin();
		UpdateInfo();
	}
	else if(key == 'i' && showShip)
	{
		selected = -1;
		hover = -1;
		showShip = false;
	}
	else if(key == 's')
	{
		showShip = true;
		UpdateInfo();
	}
	else if(key == SDLK_PAGEUP || key == SDLK_PAGEDOWN)
		Scroll(0, 6 * ((key == SDLK_PAGEUP) - (key == SDLK_PAGEDOWN)));
	else if(showShip && key == 'R')
		GetUI()->Push(new Dialog(this, &InfoPanel::Rename, "Change this ship's name?"));
	else if(canEdit && showShip && key == 'P' && shipIt != player.Ships().begin())
		player.ParkShip(shipIt->get(), !(*shipIt)->IsParked());
	else if(canEdit && !showShip && key == 'n' && player.Ships().size() > 1)
	{
		bool allParked = true;
		auto it = player.Ships().begin() + 1;
		for( ; it != player.Ships().end(); ++it)
			if(!(*it)->IsDisabled())
				allParked &= (*it)->IsParked();
		
		it = player.Ships().begin() + !allParked;
		for( ; it != player.Ships().end(); ++it)
			if(!(*it)->IsDisabled())
				player.ParkShip(it->get(), !allParked);
	}
	else if(command.Has(Command::INFO | Command::MAP) || key == 'm')
		GetUI()->Push(new MissionPanel(player));
	else
		return false;
	
	return true;
}
Exemple #6
0
// Only override the ones you need; the default action is to return false.
bool MainPanel::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command, bool isNewPress)
{
	if(command.Has(Command::MAP | Command::INFO | Command::HAIL))
		show = command;
	else if(command.Has(Command::AMMO))
	{
		Preferences::ToggleAmmoUsage();
		Messages::Add("Your escorts will now expend ammo: " + Preferences::AmmoUsage() + ".");
	}
	else if(key == '-' && !command)
		Preferences::ZoomViewOut();
	else if(key == '=' && !command)
		Preferences::ZoomViewIn();
	else if(key >= '0' && key <= '9' && !command)
		engine.SelectGroup(key - '0', mod & KMOD_SHIFT, mod & (KMOD_CTRL | KMOD_GUI));
	else
		return false;
	
	return true;
}
bool MapSalesPanel::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command)
{
	if(command.Has(Command::MAP) || key == 'd' || key == SDLK_ESCAPE || (key == 'w' && (mod & (KMOD_CTRL | KMOD_GUI))))
		GetUI()->Pop(this);
	else if(key == 's' && isOutfitters)
	{
		GetUI()->Pop(this);
		GetUI()->Push(new MapShipyardPanel(*this));
	}
	else if(key == 'o' && !isOutfitters)
	{
		GetUI()->Pop(this);
		GetUI()->Push(new MapOutfitterPanel(*this));
	}
	else if(key == 'i')
	{
		GetUI()->Pop(this);
		GetUI()->Push(new MissionPanel(*this));
	}
	else if(key == 'p')
	{
		GetUI()->Pop(this);
		GetUI()->Push(new MapDetailPanel(*this));
	}
	else if(key == SDLK_PAGEUP || key == SDLK_PAGEDOWN)
	{
		scroll += static_cast<double>((Screen::Height() - 100) * ((key == SDLK_PAGEUP) - (key == SDLK_PAGEDOWN)));
		scroll = min(0., max(-maxScroll, scroll));
	}
	else if((key == SDLK_DOWN || key == SDLK_UP) && !zones.empty())
	{
		selected += (key == SDLK_DOWN) - (key == SDLK_UP);
		if(selected < 0)
			selected = zones.size() - 1;
		else if(selected > static_cast<int>(zones.size() - 1))
			selected = 0;
		
		Compare(compare = -1);
		Select(selected);
		ScrollTo(selected);
	}
	else if(key == 'f')
		GetUI()->Push(new Dialog(
			this, &MapSalesPanel::DoFind, "Search for:"));
	else if(key == '+' || key == '=')
		ZoomMap();
	else if(key == '-')
		UnzoomMap();
	else
		return false;
	
	return true;
}
// Only override the ones you need; the default action is to return false.
bool TradingPanel::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command)
{
	if(key == SDLK_UP)
		player.SetMapColoring(max(0, player.MapColoring() - 1));
	else if(key == SDLK_DOWN)
		player.SetMapColoring(max(0, min(COMMODITY_COUNT - 1, player.MapColoring() + 1)));
	else if(key == '=' || key == SDLK_RETURN || key == SDLK_SPACE)
		Buy(1);
	else if(key == '-' || key == SDLK_BACKSPACE || key == SDLK_DELETE)
		Buy(-1);
	else if(key == 'B' || (key == 'b' && (mod & KMOD_SHIFT)))
		Buy(1000000000);
	else if(key == 'S' || (key == 's' && (mod & KMOD_SHIFT)))
	{
		for(const auto &it : GameData::Commodities())
		{
			int64_t amount = player.Cargo().Get(it.name);
			int64_t price = system.Trade(it.name);
			if(!price || !amount)
				continue;
			
			int64_t basis = player.GetBasis(it.name, -amount);
			player.AdjustBasis(it.name, basis);
			profit += amount * price + basis;
			tonsSold += amount;
			
			player.Cargo().Remove(it.name, amount);
			player.Accounts().AddCredits(amount * price);
			GameData::AddPurchase(system, it.name, -amount);
		}
		for(const auto &it : player.Cargo().Outfits())
		{
			if(it.first->Get("installable") >= 0. && !sellOutfits)
				continue;
			
			profit += it.second * it.first->Cost();
			tonsSold += it.second * static_cast<int>(it.first->Get("mass"));
			
			player.SoldOutfits()[it.first] += it.second;
			player.Accounts().AddCredits(it.second * it.first->Cost());
			player.Cargo().Remove(it.first, it.second);
		}
	}
	else if(command.Has(Command::MAP))
		GetUI()->Push(new MapDetailPanel(player));
	else
		return false;
	
	return true;
}
Exemple #9
0
bool Dialog::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command, bool isNewPress)
{
	auto it = KEY_MAP.find(key);
	bool isCloseRequest = key == SDLK_ESCAPE || (key == 'w' && (mod & (KMOD_CTRL | KMOD_GUI)));
	if((it != KEY_MAP.end() || (key >= ' ' && key <= '~')) && !isMission && (intFun || stringFun) && !isCloseRequest)
	{
		int ascii = (it != KEY_MAP.end()) ? it->second : key;
		char c = ((mod & KMOD_SHIFT) ? SHIFT[ascii] : ascii);
		// Caps lock should shift letters, but not any other keys.
		if((mod & KMOD_CAPS) && c >= 'a' && c <= 'z')
			c += 'A' - 'a';
		
		if(stringFun)
			input += c;
		// Integer input should not allow leading zeros.
		else if(intFun && c == '0' && !input.empty())
			input += c;
		else if(intFun && c >= '1' && c <= '9')
			input += c;
	}
	else if((key == SDLK_DELETE || key == SDLK_BACKSPACE) && !input.empty())
		input.erase(input.length() - 1);
	else if(key == SDLK_TAB && canCancel)
		okIsActive = !okIsActive;
	else if(key == SDLK_LEFT)
		okIsActive = !canCancel;
	else if(key == SDLK_RIGHT)
		okIsActive = true;
	else if(key == SDLK_RETURN || key == SDLK_KP_ENTER || isCloseRequest
			|| (isMission && (key == 'a' || key == 'd')))
	{
		// Shortcuts for "accept" and "decline."
		if(key == 'a' || (!canCancel && isCloseRequest))
			okIsActive = true;
		if(key == 'd' || (canCancel && isCloseRequest))
			okIsActive = false;
		if(okIsActive || isMission)
			DoCallback();
		
		GetUI()->Pop(this);
	}
	else if((key == 'm' || command.Has(Command::MAP)) && system && player)
		GetUI()->Push(new MapDetailPanel(*player, system));
	else
		return false;
	
	return true;
}
bool PreferencesPanel::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command)
{
	if(static_cast<unsigned>(editing) < zones.size())
	{
		Command::SetKey(zones[editing].Value(), key);
		EndEditing();
		return true;
	}
	
	if(key == SDLK_DOWN && static_cast<unsigned>(selected + 1) < zones.size())
		++selected;
	else if(key == SDLK_UP && selected > 0)
		--selected;
	else if(key == SDLK_RETURN)
		editing = selected;
	else if(key == 'b' || command.Has(Command::MENU) || (key == 'w' && (mod & (KMOD_CTRL | KMOD_GUI))))
		Exit();
	else if(key == 'c' || key == 's' || key == 'p')
		page = key;
	else
		return false;
	
	return true;
}
// Only override the ones you need; the default action is to return false.
bool MapShipyardPanel::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command)
{
	if(command.Has(Command::MAP) || key == 'd' || (key == 'w' && (mod & (KMOD_CTRL | KMOD_GUI))))
		GetUI()->Pop(this);
	else if(key == 'o')
	{
		GetUI()->Pop(this);
		GetUI()->Push(new MapOutfitterPanel(*this));
	}
	else if(key == 'i')
	{
		GetUI()->Pop(this);
		GetUI()->Push(new MissionPanel(*this));
	}
	else if(key == 'p')
	{
		GetUI()->Pop(this);
		GetUI()->Push(new MapDetailPanel(*this));
	}
	else
		return false;
	
	return true;
}
// Only override the ones you need; the default action is to return false.
bool ConversationPanel::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command)
{
	// Map popup happens when you press the map key, unless the name text entry
	// fields are currently active.
	if(command.Has(Command::MAP) && !choices.empty())
		GetUI()->Push(new MapDetailPanel(player, -4, system));
	if(node < 0)
	{
		if(key == SDLK_RETURN)
		{
			GetUI()->Pop(this);
			if(callback)
			{
				if(player.BoardingShip())
				{
					if(node == Conversation::LAUNCH || node == Conversation::FLEE)
						player.BoardingShip()->Destroy();
					else if(player.BoardingShip()->GetGovernment()->IsEnemy())
					{
						if(node != Conversation::ACCEPT)
							GetUI()->Push(new BoardingPanel(player, player.BoardingShip()));
					}
				}
				callback(node);
			}
			return true;
		}
		
		return false;
	}
	if(choices.empty())
	{
		string &name = (choice ? lastName : firstName);
		string &otherName = (choice ? firstName : lastName);
		if(key >= ' ' && key <= '~')
			name += ((mod & (KMOD_SHIFT | KMOD_CAPS)) ? SHIFT[key] : key);
		else if((key == SDLK_DELETE || key == SDLK_BACKSPACE) && name.size())
			name.erase(name.size() - 1);
		else if(key == '\t' || (key == SDLK_RETURN && otherName.empty()))
			choice = !choice;
		else if(key == SDLK_RETURN && !firstName.empty() && !lastName.empty())
		{
			for(char &c : firstName)
				if(c == '~')
					c = '-';
			for(char &c : lastName)
				if(c == '~')
					c = '-';
			
			string name = "\t\tName: " + firstName + " " + lastName + ".\n";
			text.emplace_back(name);
			
			player.SetName(firstName, lastName);
			subs["<first>"] = player.FirstName();
			subs["<last>"] = player.LastName();
			
			Goto(node + 1);
		}
		else
			return false;
		
		return true;
	}
	
	if(key == SDLK_UP && choice > 0)
		--choice;
	else if(key == SDLK_DOWN && choice < conversation.Choices(node) - 1)
		++choice;
	else if(key == SDLK_RETURN && choice < conversation.Choices(node))
		Goto(conversation.NextNode(node, choice), choice);
	else if(key > '0' && key <= static_cast<SDL_Keycode>('0' + choices.size()))
		Goto(conversation.NextNode(node, key - '1'), key - '1');
	else
		return false;
	
	return true;
}
// Only override the ones you need; the default action is to return false.
bool MapDetailPanel::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command)
{
	if(command.Has(Command::MAP) || key == 'd' || key == SDLK_ESCAPE || (key == 'w' && (mod & (KMOD_CTRL | KMOD_GUI))))
		GetUI()->Pop(this);
	else if(key == SDLK_PAGEUP || key == SDLK_PAGEDOWN || key == 'i')
	{
		GetUI()->Pop(this);
		GetUI()->Push(new MissionPanel(*this));
	}
	else if(key == 'o')
	{
		GetUI()->Pop(this);
		GetUI()->Push(new MapOutfitterPanel(*this));
	}
	else if(key == 's')
	{
		GetUI()->Pop(this);
		GetUI()->Push(new MapShipyardPanel(*this));
	}
	else if((key == SDLK_TAB || command.Has(Command::JUMP)) && player.Flagship())
	{
		bool hasJumpDrive = player.Flagship()->Attributes().Get("jump drive");
		const vector<const System *> &links =
			hasJumpDrive ? player.GetSystem()->Neighbors() : player.GetSystem()->Links();
		
		if(!player.HasTravelPlan() && !links.empty())
			Select(links.front());
		else if(player.TravelPlan().size() == 1 && !links.empty())
		{
			auto it = links.begin();
			for( ; it != links.end(); ++it)
				if(*it == player.TravelPlan().front())
					break;
			
			if(it != links.end())
				++it;
			if(it == links.end())
				it = links.begin();
			
			Select(*it);
		}
	}
	else if(key == SDLK_DOWN)
	{
		if(commodity < 0 || commodity == 9)
			commodity = 0;
		else
			++commodity;
	}
	else if(key == SDLK_UP)
	{
		if(commodity <= 0)
			commodity = 9;
		else
			--commodity;
	}
	else if(key == 'f')
		GetUI()->Push(new Dialog(
			this, &MapDetailPanel::DoFind, "Search for:"));
	else if(key == '+' || key == '=')
		ZoomMap();
	else if(key == '-')
		UnzoomMap();
	else
		return false;
	
	return true;
}
Exemple #14
0
bool InfoPanel::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command)
{
	if(key == 'd' || key == SDLK_ESCAPE || (key == 'w' && (mod & (KMOD_CTRL | KMOD_GUI))))
		GetUI()->Pop(this);
	else if(showShip && !player.Ships().empty() && (key == 'p' || key == SDLK_LEFT || key == SDLK_UP))
	{
		if(shipIt == player.Ships().begin())
			shipIt = player.Ships().end();
		--shipIt;
		UpdateInfo();
	}
	else if(showShip && !player.Ships().empty() && (key == 'n' || key == SDLK_RIGHT || key == SDLK_DOWN))
	{
		++shipIt;
		if(shipIt == player.Ships().end())
			shipIt = player.Ships().begin();
		UpdateInfo();
	}
	else if(key == 'i' && showShip)
	{
		selected = -1;
		hover = -1;
		showShip = false;
	}
	else if(key == 's')
	{
		showShip = true;
		UpdateInfo();
	}
	else if(key == SDLK_PAGEUP || key == SDLK_PAGEDOWN)
		Scroll(0, 6 * ((key == SDLK_PAGEUP) - (key == SDLK_PAGEDOWN)));
	else if(showShip && key == 'R')
		GetUI()->Push(new Dialog(this, &InfoPanel::Rename, "Change this ship's name?"));
	else if(canEdit && showShip && key == 'P')
	{
		if(shipIt->get() != player.Flagship() || (*shipIt)->IsParked())
			player.ParkShip(shipIt->get(), !(*shipIt)->IsParked());
	}
	else if((key == 'P' || key == 'c') && showShip && !canEdit)
	{
		if(CanDump())
		{
			int amount = (*shipIt)->Cargo().Get(selectedCommodity);
			int plunderAmount = (*shipIt)->Cargo().Get(selectedPlunder);
			if(amount)
			{
				GetUI()->Push(new Dialog(this, &InfoPanel::Dump,
					"Are you sure you want to jettison "
						+ (amount == 1 ? "a ton" : Format::Number(amount) + " tons")
						+ " of " + Format::LowerCase(selectedCommodity) + " cargo?"));
			}
			else if(plunderAmount == 1)
			{
				GetUI()->Push(new Dialog(this, &InfoPanel::Dump,
					"Are you sure you want to jettison a " + selectedPlunder->Name() + "?"));
			}
			else if(plunderAmount > 1)
			{
				GetUI()->Push(new Dialog(this, &InfoPanel::DumpPlunder,
					"How many of the " + selectedPlunder->Name() + " outfits to you want to jettison?",
					plunderAmount));
			}
			else
			{
				GetUI()->Push(new Dialog(this, &InfoPanel::Dump,
					"Are you sure you want to jettison all this ship's regular cargo?"));
			}
		}
	}
	else if(canEdit && !showShip && key == 'n' && player.Ships().size() > 1)
	{
		bool allParked = true;
		const Ship *flagship = player.Flagship();
		for(const auto &it : player.Ships())
			if(!it->IsDisabled() && it.get() != flagship)
				allParked &= it->IsParked();
		
		for(const auto &it : player.Ships())
			if(!it->IsDisabled() && (allParked || it.get() != flagship))
				player.ParkShip(it.get(), !allParked);
	}
	else if(command.Has(Command::INFO | Command::MAP) || key == 'm')
		GetUI()->Push(new MissionPanel(player));
	else
		return false;
	
	return true;
}
Exemple #15
0
// Only override the ones you need; the default action is to return false.
bool PlanetPanel::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command)
{
	Panel *oldPanel = selectedPanel;
	const Ship *flagship = player.Flagship();
	
	bool hasAccess = planet.CanUseServices();
	if(key == 'd' && flagship && flagship->CanBeFlagship())
	{
		// Check whether the player should be warned before taking off.
		if(player.ShouldLaunch())
			TakeOff();
		else
		{
			// Will you have to sell something other than regular cargo?
			int cargoToSell = -(player.Cargo().Free() + player.Cargo().CommoditiesSize());
			int droneCount = 0;
			int fighterCount = 0;
			for(const auto &it : player.Ships())
				if(!it->IsParked() && !it->IsDisabled() && it->GetSystem() == player.GetSystem())
				{
					const string &category = it->Attributes().Category();
					droneCount += (category == "Drone") - it->BaysFree(false);
					fighterCount += (category == "Fighter") - it->BaysFree(true);
				}
			
			if(fighterCount > 0 || droneCount > 0 || cargoToSell > 0)
			{
				ostringstream out;
				out << "If you take off now you will have to sell ";
				bool triple = (fighterCount > 0 && droneCount > 0 && cargoToSell > 0);
			
				if(fighterCount == 1)
					out << "a fighter";
				else if(fighterCount > 0)
					out << fighterCount << " fighters";
				if(fighterCount > 0 && (droneCount > 0 || cargoToSell > 0))
					out << (triple ? ", " : " and ");
			
				if(droneCount == 1)
					out << "a drone";
				else if(droneCount > 0)
					out << droneCount << " drones";
				if(droneCount > 0 && cargoToSell > 0)
					out << (triple ? ", and " : " and ");
			
				if(cargoToSell == 1)
					out << "a ton of cargo";
				else if(cargoToSell > 0)
					out << cargoToSell << " tons of cargo";
				out << " that you do not have space for. Are you sure you want to continue?";
				
				GetUI()->Push(new Dialog(this, &PlanetPanel::TakeOff, out.str()));
				return true;
			}
			else
				TakeOff();
		}
	}
	else if(key == 'l')
	{
		selectedPanel = nullptr;
	}
	else if(key == 't' && flagship && planet.IsInhabited() && hasAccess)
	{
		selectedPanel = trading.get();
		GetUI()->Push(trading);
	}
	else if(key == 'b' && planet.IsInhabited() && hasAccess)
	{
		selectedPanel = bank.get();
		GetUI()->Push(bank);
	}
	else if(key == 'p' && flagship && planet.HasSpaceport() && hasAccess)
	{
		selectedPanel = spaceport.get();
		GetUI()->Push(spaceport);
	}
	else if(key == 's' && planet.HasShipyard() && hasAccess)
	{
		GetUI()->Push(new ShipyardPanel(player));
		return true;
	}
	else if(key == 'o' && planet.HasOutfitter() && hasAccess)
	{
		bool hasShip = false;
		for(const auto &it : player.Ships())
			if(it->GetSystem() == player.GetSystem() && !it->IsDisabled())
			{
				hasShip = true;
				break;
			}
		if(hasShip)
			GetUI()->Push(new OutfitterPanel(player));
		return true;
	}
	else if(key == 'j' && flagship && planet.IsInhabited() && hasAccess)
	{
		GetUI()->Push(new MissionPanel(player));
		return true;
	}
	else if(key == 'h' && flagship && planet.IsInhabited() && hasAccess)
	{
		selectedPanel = hiring.get();
		GetUI()->Push(hiring);
	}
	else if(command.Has(Command::MAP))
	{
		GetUI()->Push(new MapDetailPanel(player));
		return true;
	}
	else if(command.Has(Command::INFO))
	{
		GetUI()->Push(new InfoPanel(player));
		return true;
	}
	else
		return false;
	
	// If we are here, it is because something happened to change the selected
	// panel. So, we need to pop the old selected panel:
	if(oldPanel)
		GetUI()->Pop(oldPanel);
	
	return true;
}
Exemple #16
0
void AI::Step(const list<shared_ptr<Ship>> &ships, const PlayerInfo &player)
{
	const Ship *flagship = player.Flagship();
	
	step = (step + 1) & 31;
	int targetTurn = 0;
	for(const auto &it : ships)
	{
		// Skip any carried fighters or drones that are somehow in the list.
		if(!it->GetSystem())
			continue;
		
		if(it.get() == flagship)
		{
			MovePlayer(*it, player, ships);
			continue;
		}
		
		bool isPresent = (it->GetSystem() == player.GetSystem());
		bool isStranded = !it->JumpsRemaining() && it->Attributes().Get("fuel capacity")
			&& !it->GetSystem()->IsInhabited();
		if(isStranded || it->IsDisabled())
		{
			if(it->IsDestroyed() || (it->IsDisabled() && it->IsYours()) || it->GetPersonality().IsDerelict())
				continue;
			
			bool hasEnemy = false;
			Ship *firstAlly = nullptr;
			bool selectNext = false;
			Ship *nextAlly = nullptr;
			const Government *gov = it->GetGovernment();
			for(const auto &ship : ships)
			{
				if(ship->IsDisabled() || !ship->IsTargetable() || ship->GetSystem() != it->GetSystem())
					continue;
				
				const Government *otherGov = ship->GetGovernment();
				// If any enemies of this ship are in system, it cannot call for help.
				if(otherGov->IsEnemy(gov) && isPresent)
				{
					hasEnemy = true;
					break;
				}
				if((otherGov->IsPlayer() && !gov->IsPlayer()) || ship.get() == flagship)
					continue;
				
				if(it->IsDisabled() ? (otherGov == gov) : (!otherGov->IsEnemy(gov)))
				{
					if(isStranded && !ship->CanRefuel(*it))
						continue;
					
					if(!firstAlly)
						firstAlly = &*ship;
					else if(ship == it)
						selectNext = true;
					else if(selectNext && !nextAlly)
						nextAlly = &*ship;
				}
			}
			
			isStranded = false;
			if(!hasEnemy)
			{
				if(!nextAlly)
					nextAlly = firstAlly;
				if(nextAlly)
				{
					nextAlly->SetShipToAssist(it);
					isStranded = true;
				}
			}
			if(it->IsDisabled())
				continue;
		}
		
		Command command;
		if(it->IsYours())
		{
			if(isLaunching)
				command |= Command::DEPLOY;
			if(isCloaking)
				command |= Command::CLOAK;
		}
		
		const Personality &personality = it->GetPersonality();
		shared_ptr<Ship> parent = it->GetParent();
		
		if(isPresent && personality.IsSurveillance())
		{
			DoSurveillance(*it, command, ships);
			it->SetCommands(command);
			continue;
		}
		
		// Fire any weapons that will hit the target. Only ships that are in
		// the current system can fire.
		shared_ptr<const Ship> target = it->GetTargetShip();
		if(isPresent)
		{
			command |= AutoFire(*it, ships);
			
			// Each ship only switches targets twice a second, so that it can
			// focus on damaging one particular ship.
			targetTurn = (targetTurn + 1) & 31;
			if(targetTurn == step || !target || !target->IsTargetable()
					|| (target->IsDisabled() && personality.Disables()))
				it->SetTargetShip(FindTarget(*it, ships));
		}
		
		double targetDistance = numeric_limits<double>::infinity();
		target = it->GetTargetShip();
		if(target)
			targetDistance = target->Position().Distance(it->Position());
		
		// Handle fighters:
		const string &category = it->Attributes().Category();
		bool isDrone = (category == "Drone");
		bool isFighter = (category == "Fighter");
		if(isDrone || isFighter)
		{
			bool hasSpace = true;
			hasSpace &= parent && (!isDrone || parent->DroneBaysFree());
			hasSpace &= parent && (!isFighter || parent->FighterBaysFree());
			if(!hasSpace || parent->IsDestroyed() || parent->GetSystem() != it->GetSystem())
			{
				// Handle orphaned fighters and drones.
				for(const auto &other : ships)
					if(other->GetGovernment() == it->GetGovernment() && !other->IsDisabled()
							&& other->GetSystem() == it->GetSystem())
						if((isDrone && other->DroneBaysFree()) || (isFighter && other->FighterBaysFree()))
						{
							it->SetParent(other);
							break;
						}
			}
			else if(parent && !(it->IsYours() ? isLaunching : parent->Commands().Has(Command::DEPLOY)))
			{
				it->SetTargetShip(parent);
				MoveTo(*it, command, parent->Position(), 40., .8);
				command |= Command::BOARD;
				it->SetCommands(command);
				continue;
			}
		}
		bool mustRecall = false;
		if(it->HasBays() && !(it->IsYours() ? isLaunching : it->Commands().Has(Command::DEPLOY)) && !target)
			for(const weak_ptr<const Ship> &ptr : it->GetEscorts())
			{
				shared_ptr<const Ship> escort = ptr.lock();
				if(escort && escort->CanBeCarried() && escort->GetSystem() == it->GetSystem()
						&& !escort->IsDisabled())
				{
					mustRecall = true;
					break;
				}
			}
		
		shared_ptr<Ship> shipToAssist = it->GetShipToAssist();
		if(shipToAssist)
		{
			it->SetTargetShip(shipToAssist);
			if(shipToAssist->IsDestroyed() || shipToAssist->GetSystem() != it->GetSystem())
				it->SetShipToAssist(shared_ptr<Ship>());
			else if(!it->IsBoarding())
			{
				MoveTo(*it, command, shipToAssist->Position(), 40., .8);
				command |= Command::BOARD;
			}
			it->SetCommands(command);
			continue;
		}
		
		bool isPlayerEscort = it->IsYours();
		if((isPlayerEscort && holdPosition) || mustRecall || isStranded)
		{
			if(it->Velocity().Length() > .2 || !target)
				Stop(*it, command);
			else
				command.SetTurn(TurnToward(*it, TargetAim(*it)));
		}
		// Hostile "escorts" (i.e. NPCs that are trailing you) only revert to
		// escort behavior when in a different system from you. Otherwise,
		// the behavior depends on what the parent is doing, whether there
		// are hostile targets nearby, and whether the escort has any
		// immediate needs (like refueling).
		else if(!parent || parent->IsDestroyed() || (parent->IsDisabled() && !isPlayerEscort))
			MoveIndependent(*it, command);
		else if(parent->GetSystem() != it->GetSystem())
		{
			if(personality.IsStaying())
				MoveIndependent(*it, command);
			else
				MoveEscort(*it, command);
		}
		// From here down, we're only dealing with ships that have a "parent"
		// which is in the same system as them. If you're an enemy of your
		// "parent," you don't take orders from them.
		else if(personality.IsStaying() || parent->GetGovernment()->IsEnemy(it->GetGovernment()))
			MoveIndependent(*it, command);
		// This is a friendly escort. If the parent is getting ready to
		// jump, always follow.
		else if(parent->Commands().Has(Command::JUMP))
			MoveEscort(*it, command);
		// If the player is ordering escorts to gather, don't go off to fight.
		else if(isPlayerEscort && moveToMe)
			MoveEscort(*it, command);
		// On the other hand, if the player ordered you to attack, do so even
		// if you're usually more timid than that.
		else if(isPlayerEscort && sharedTarget.lock())
			MoveIndependent(*it, command);
		// Timid ships always stay near their parent.
		else if(personality.IsTimid() && parent->Position().Distance(it->Position()) > 500.)
			MoveEscort(*it, command);
		// Otherwise, attack targets depending on how heroic you are.
		else if(target && (targetDistance < 2000. || personality.IsHeroic()))
			MoveIndependent(*it, command);
		// This ship does not feel like fighting.
		else
			MoveEscort(*it, command);
		
		// Apply the afterburner if you're in a heated battle and it will not
		// use up your last jump worth of fuel.
		if(it->Attributes().Get("afterburner thrust") && target && !target->IsDisabled()
				&& target->IsTargetable() && target->GetSystem() == it->GetSystem())
		{
			double fuel = it->Fuel() * it->Attributes().Get("fuel capacity");
			if(fuel - it->Attributes().Get("afterburner fuel") >= it->JumpFuel())
				if(command.Has(Command::FORWARD) && targetDistance < 1000.)
					command |= Command::AFTERBURNER;
		}
		// Your own ships cloak on your command; all others do it when the
		// AI considers it appropriate.
		if(!it->IsYours())
			DoCloak(*it, command, ships);
		
		// Force ships that are overlapping each other to "scatter":
		DoScatter(*it, command, ships);
		
		it->SetCommands(command);
	}
}
bool LoadPanel::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command)
{
	if(key == 'n')
	{
		GameData::Revert();
		player.New();
		
		Messages::Reset();
		ConversationPanel *panel = new ConversationPanel(
			player, *GameData::Conversations().Get("intro"));
		GetUI()->Push(panel);
		panel->SetCallback(this, &LoadPanel::OnCallback);
	}
	else if(key == 'd' && !selectedPilot.empty())
	{
		GetUI()->Push(new Dialog(this, &LoadPanel::DeletePilot,
			"Are you sure you want to delete the selected pilot, \""
				+ selectedPilot + "\", and all their saved games?"));
	}
	else if(key == 'a')
	{
		string wasSelected = selectedPilot;
		auto it = files.find(selectedPilot);
		if(it == files.end() || it->second.empty() || it->second.front().size() < 4)
			return false;
		
		GetUI()->Push(new Dialog(this, &LoadPanel::SnapshotCallback,
			"Enter a name for this snapshot, or leave the name empty to use the current date:"));
	}
	else if(key == 'r' && !selectedFile.empty())
	{
		GetUI()->Push(new Dialog(this, &LoadPanel::DeleteSave,
			"Are you sure you want to delete the selected saved game file, \""
				+ selectedFile + "\"?"));
	}
	else if(key == 'e')
	{
		// First, make sure the previous MainPanel has been deleted, so
		// its background thread is no longer running.
		gamePanels.Reset();
		
		GameData::Revert();
		player.Load(loadedInfo.Path());
		player.ApplyChanges();
		
		Messages::Reset();
		GetUI()->Pop(this);
		GetUI()->Pop(GetUI()->Root().get());
		gamePanels.Push(new MainPanel(player));
	}
	else if(key == 'b' || command.Has(Command::MENU) || (key == 'w' && (mod & (KMOD_CTRL | KMOD_GUI))))
		GetUI()->Pop(this);
	else if((key == SDLK_DOWN || key == SDLK_UP) && !files.empty())
	{
		auto pit = files.find(selectedPilot);
		if(sideHasFocus)
		{
			auto it = files.begin();
			for( ; it != files.end(); ++it)
				if(it->first == selectedPilot)
					break;
			
			if(key == SDLK_DOWN)
			{
				++it;
				if(it == files.end())
					it = files.begin();
			}
			else
			{
				if(it == files.begin())
					it = files.end();
				--it;
			}
			selectedPilot = it->first;
			selectedFile = it->second.front();
		}
		else if(pit != files.end())
		{
			auto it = pit->second.begin();
			for( ; it != pit->second.end(); ++it)
				if(*it == selectedFile)
					break;
			
			if(key == SDLK_DOWN)
			{
				++it;
				if(it == pit->second.end())
					it = pit->second.begin();
			}
			else
			{
				if(it == pit->second.begin())
					it = pit->second.end();
				--it;
			}
			selectedFile = *it;
		}
		loadedInfo.Load(Files::Saves() + selectedFile);
	}
	else if(key == SDLK_LEFT)
		sideHasFocus = true;
	else if(key == SDLK_RIGHT)
		sideHasFocus = false;
	else
		return false;
	
	return true;
}
Exemple #18
0
// Handle key presses or button clicks that were mapped to key presses.
bool BoardingPanel::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command)
{
	if((key == 'd' || key == 'x' || key == SDLK_ESCAPE || (key == 'w' && (mod & (KMOD_CTRL | KMOD_GUI)))) && CanExit())
	{
		// When closing the panel, mark the player dead if their ship was captured.
		if(playerDied)
			player.Die(true);
		// Handle any death benefits that are owed.
		if(deathBenefits)
		{
			Messages::Add(("You must pay " + Format::Number(deathBenefits)
				+ " credits in death benefits for the ")
				+ ((casualties > 1) ? "families of your dead crew members."
					: "family of your dead crew member."));
			player.Accounts().AddDeathBenefits(deathBenefits);
		}
		GetUI()->Pop(this);
	}
	else if(playerDied)
		return false;
	else if(key == 't' && CanTake())
	{
		CargoHold &cargo = you->Cargo();
		int count = plunder[selected].Count();
		
		const Outfit *outfit = plunder[selected].GetOutfit();
		if(outfit)
		{
			// Check if this outfit is ammo for one of your weapons. If so, use
			// it to refill your ammo rather than putting it in cargo.
			for(const auto &it : you->Outfits())
				if(it.first != outfit && it.first->Ammo() == outfit)
				{
					for( ; count && you->Attributes().CanAdd(*outfit); --count)
					{
						you->AddOutfit(outfit, 1);
						victim->AddOutfit(outfit, -1);
					}
					break;
				}
			// Transfer as many as possible of these outfits to your cargo hold.
			count = -cargo.Transfer(outfit, -count);
			victim->AddOutfit(outfit, -count);
		}
		else
			count = victim->Cargo().Transfer(plunder[selected].Name(), count, &cargo);
		
		// If all of the plunder of this type was taken, remove it from the list.
		// Otherwise, just update the count in the list item.
		if(count == plunder[selected].Count())
		{
			plunder.erase(plunder.begin() + selected);
			selected = min(selected, static_cast<int>(plunder.size()));
		}
		else
			plunder[selected].Take(count);
	}
	else if((key == SDLK_UP || key == SDLK_DOWN || key == SDLK_PAGEUP || key == SDLK_PAGEDOWN) && !isCapturing)
	{
		// Scrolling the list of plunder.
		if(key == SDLK_PAGEUP || key == SDLK_PAGEDOWN)
			Drag(0, 200 * ((key == SDLK_PAGEDOWN) - (key == SDLK_PAGEUP)));
		else
		{
			if(key == SDLK_UP && selected)
				--selected;
			else if(key == SDLK_DOWN && selected < static_cast<int>(plunder.size() - 1))
				++selected;
			
			// Scroll down at least far enough to view the current item.
			double minimumScroll = max(0., 20. * selected - 200.);
			double maximumScroll = 20. * selected;
			scroll = max(minimumScroll, min(maximumScroll, scroll));
		}
	}
	else if(key == 'c' && CanCapture())
	{
		// A ship that self-destructs checks once when you board it, and again
		// when you try to capture it, to see if it will self-destruct. This is
		// so that capturing will be harder than plundering.
		if(Random::Real() < victim->Attributes().Get("self destruct"))
		{
			victim->SelfDestruct();
			GetUI()->Pop(this);
			GetUI()->Push(new Dialog("The moment you blast through the airlock, a series of explosions rocks the enemy ship. They appear to have set off their self-destruct sequence..."));
			return true;
		}
		isCapturing = true;
		messages.push_back("The airlock blasts open. Combat has begun!");
		messages.push_back("(It will end if you both choose to \"defend.\")");
	}
	else if((key == 'a' || key == 'd') && CanAttack())
	{
		int yourStartCrew = you->Crew();
		int enemyStartCrew = victim->Crew();
		
		// Figure out what action the other ship will take. As a special case,
		// if you board them but immediately "defend" they will let you return
		// to your ship in peace. That is to allow the player to "cancel" if
		// they did not really mean to try to capture the ship.
		bool youAttack = (key == 'a' && (yourStartCrew > 1 || !victim->RequiredCrew()));
		bool enemyAttacks = defenseOdds.Odds(enemyStartCrew, yourStartCrew) > .5;
		if(isFirstCaptureAction && !youAttack)
			enemyAttacks = false;
		isFirstCaptureAction = false;
		
		// If neither side attacks, combat ends.
		if(!youAttack && !enemyAttacks)
		{
			messages.push_back("You retreat to your ships. Combat ends.");
			isCapturing = false;
		}
		else
		{
			if(youAttack)
				messages.push_back("You attack. ");
			else if(enemyAttacks)
				messages.push_back("You defend. ");
			
			// To speed things up, have multiple rounds of combat each time you
			// click the button, if you started with a lot of crew.
			int rounds = max(1, yourStartCrew / 5);
			for(int round = 0; round < rounds; ++round)
			{
				int yourCrew = you->Crew();
				int enemyCrew = victim->Crew();
				if(!yourCrew || !enemyCrew)
					break;
				
				// Your chance of winning this round is equal to the ratio of
				// your power to the enemy's power.
				double yourPower = (youAttack ?
					attackOdds.AttackerPower(yourCrew) : defenseOdds.DefenderPower(yourCrew));
				double enemyPower = (enemyAttacks ?
					defenseOdds.AttackerPower(enemyCrew) : attackOdds.DefenderPower(enemyCrew));
				
				double total = yourPower + enemyPower;
				if(!total)
					break;
				
				if(Random::Real() * total >= yourPower)
					you->AddCrew(-1);
				else
					victim->AddCrew(-1);
			}
			
			// Report how many casualties each side suffered.
			int yourCasualties = yourStartCrew - you->Crew();
			int enemyCasualties = enemyStartCrew - victim->Crew();
			if(yourCasualties && enemyCasualties)
				messages.back() += "You lose " + to_string(yourCasualties)
					+ " crew; they lose " + to_string(enemyCasualties) + ".";
			else if(yourCasualties)
				messages.back() += "You lose " + to_string(yourCasualties) + " crew.";
			else if(enemyCasualties)
				messages.back() += "They lose " + to_string(enemyCasualties) + " crew.";
			
			// Check if either ship has been captured.
			if(!you->Crew())
			{
				messages.push_back("You have been killed. Your ship is lost.");
				you->WasCaptured(victim);
				you->SetIsYours(false);
				playerDied = true;
				isCapturing = false;
			}
			else if(!victim->Crew())
			{
				casualties = initialCrew - you->Crew();
				messages.push_back("You have succeeded in capturing this ship.");
				victim->WasCaptured(you);
				if(!victim->JumpsRemaining() && you->CanRefuel(*victim))
					you->TransferFuel(victim->JumpFuel(), &*victim);
				player.AddShip(victim);
				// If you capture a fighter, find one of your ships that can
				// carry it. Otherwise, it will follow your flagship.
				victim->SetParent(you);
				if(victim->CanBeCarried())
					for(const shared_ptr<Ship> &ship : player.Ships())
						if(ship->CanCarry(*victim))
						{
							victim->SetParent(ship);
							break;
						}
				isCapturing = false;
				
				// If you suffered any casualties, you need to split the value
				// of the ship with their bereaved families. You get two shares,
				// and each dead crew member gets one.
				int64_t bonus = (victim->Cost() * casualties) / (casualties + 2);
				deathBenefits += bonus;
				
				// Report this ship as captured in case any missions care.
				ShipEvent event(you, victim, ShipEvent::CAPTURE);
				player.HandleEvent(event, GetUI());
			}
		}
	}
	else if(command.Has(Command::INFO))
		GetUI()->Push(new InfoPanel(player, true));
	
	// Trim the list of status messages.
	while(messages.size() > 5)
		messages.erase(messages.begin());
	
	return true;
}
Exemple #19
0
// Only override the ones you need; the default action is to return false.
bool PlanetPanel::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command, bool isNewPress)
{
	Panel *oldPanel = selectedPanel;
	const Ship *flagship = player.Flagship();
	
	bool hasAccess = planet.CanUseServices();
	if(key == 'd' && flagship && flagship->CanBeFlagship())
		requestedLaunch = true;
	else if(key == 'l')
	{
		selectedPanel = nullptr;
	}
	else if(key == 't' && hasAccess && flagship && planet.IsInhabited() && system.HasTrade())
	{
		selectedPanel = trading.get();
		GetUI()->Push(trading);
	}
	else if(key == 'b' && hasAccess && planet.IsInhabited())
	{
		selectedPanel = bank.get();
		GetUI()->Push(bank);
	}
	else if(key == 'p' && hasAccess && flagship && planet.HasSpaceport())
	{
		selectedPanel = spaceport.get();
		if(isNewPress)
			spaceport->UpdateNews();
		GetUI()->Push(spaceport);
	}
	else if(key == 's' && hasAccess && planet.HasShipyard())
	{
		GetUI()->Push(new ShipyardPanel(player));
		return true;
	}
	else if(key == 'o' && hasAccess && planet.HasOutfitter())
	{
		for(const auto &it : player.Ships())
			if(it->GetSystem() == &system && !it->IsDisabled())
			{
				GetUI()->Push(new OutfitterPanel(player));
				return true;
			}
	}
	else if(key == 'j' && hasAccess && flagship && planet.IsInhabited())
	{
		GetUI()->Push(new MissionPanel(player));
		return true;
	}
	else if(key == 'h' && hasAccess && flagship && planet.IsInhabited())
	{
		selectedPanel = hiring.get();
		GetUI()->Push(hiring);
	}
	else if(command.Has(Command::MAP))
	{
		GetUI()->Push(new MapDetailPanel(player));
		return true;
	}
	else if(command.Has(Command::INFO))
	{
		GetUI()->Push(new PlayerInfoPanel(player));
		return true;
	}
	else
		return false;
	
	// If we are here, it is because something happened to change the selected
	// planet UI panel. So, we need to pop the old selected panel:
	if(oldPanel)
		GetUI()->Pop(oldPanel);
	
	return true;
}
bool BoardingPanel::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command)
{
	if((key == 'd' || key == 'x' || key == SDLK_ESCAPE || (key == 'w' && (mod & (KMOD_CTRL | KMOD_GUI)))) && CanExit())
	{
		if(playerDied)
			player.Die(true);
		if(crewBonus)
		{
			Messages::Add(("You must pay " + Format::Number(crewBonus)
				+ " credits in death benefits for the ")
				+ ((casualties > 1) ? "families of your dead crew members."
					: "family of your dead crew member."));
			player.Accounts().AddBonus(crewBonus);
		}
		GetUI()->Pop(this);
	}
	else if(playerDied)
		return false;
	else if(key == 't' && CanTake())
	{
		CargoHold &cargo = you->Cargo();
		int count = plunder[selected].CanTake(cargo.Free());
		
		const Outfit *outfit = plunder[selected].GetOutfit();
		if(outfit)
		{
			// Check if this outfit is ammo for one of your weapons. If so, use
			// it to refill your ammo rather than putting it in cargo.
			int taken = 0;
			for(const auto &it : you->Outfits())
				if(it.first != outfit && it.first->Ammo() == outfit)
				{
					for( ; taken < count && you->Attributes().CanAdd(*outfit); ++taken)
					{
						you->AddOutfit(outfit, 1);
						victim->AddOutfit(outfit, -1);
					}
					break;
				}
			cargo.Transfer(outfit, -(count - taken));
			victim->AddOutfit(outfit, -(count - taken));
		}
		else
			victim->Cargo().Transfer(plunder[selected].Name(), count, &cargo);
		
		if(count == plunder[selected].Count())
		{
			plunder.erase(plunder.begin() + selected);
			selected = min(selected, static_cast<int>(plunder.size()));
		}
		else
			plunder[selected].Take(count);
	}
	else if((key == SDLK_UP || key == SDLK_DOWN || key == SDLK_PAGEUP || key == SDLK_PAGEDOWN) && !isCapturing)
	{
		if(key == SDLK_PAGEUP || key == SDLK_PAGEDOWN)
			Drag(0, 200 * ((key == SDLK_PAGEDOWN) - (key == SDLK_PAGEUP)));
		else
		{
			if(key == SDLK_UP && selected)
				--selected;
			else if(key == SDLK_DOWN && selected < static_cast<int>(plunder.size() - 1))
				++selected;
			
			// Scroll down at least far enough to view the current item.
			double minimumScroll = max(0., 20. * selected - 200.);
			double maximumScroll = 20. * selected;
			scroll = max(minimumScroll, min(maximumScroll, scroll));
		}
	}
	else if(key == 'c' && CanCapture())
	{
		if(Random::Real() < victim->Attributes().Get("self destruct"))
		{
			victim->SelfDestruct();
			GetUI()->Pop(this);
			GetUI()->Push(new Dialog("The moment you blast through the airlock, a series of explosions rocks the enemy ship. They appear to have set off their self-destruct sequence..."));
			return true;
		}
		isCapturing = true;
		messages.push_back("The airlock blasts open. Combat has begun!");
		messages.push_back("(It will end if you both choose to \"defend.\")");
	}
	else if((key == 'a' || key == 'd') && CanAttack())
	{
		int yourStartCrew = you->Crew();
		int enemyStartCrew = victim->Crew();
		
		// Figure out what action the other ship will take.
		bool youAttack = (key == 'a' && yourStartCrew > 1);
		bool enemyAttacks = defenseOdds.Odds(enemyStartCrew, yourStartCrew) > .5;
		
		if(!youAttack && !enemyAttacks)
		{
			messages.push_back("You retreat to your ships. Combat ends.");
			isCapturing = false;
		}
		else
		{
			if(youAttack)
				messages.push_back("You attack. ");
			else if(enemyAttacks)
				messages.push_back("You defend. ");
			
			int rounds = max(1, yourStartCrew / 5);
			for(int round = 0; round < rounds; ++round)
			{
				int yourCrew = you->Crew();
				int enemyCrew = victim->Crew();
				if(!yourCrew || !enemyCrew)
					break;
				
				unsigned yourPower = static_cast<unsigned>(1000. * (youAttack ?
					attackOdds.AttackerPower(yourCrew) : defenseOdds.DefenderPower(yourCrew)));
				unsigned enemyPower = static_cast<unsigned>(1000. * (enemyAttacks ?
					defenseOdds.AttackerPower(enemyCrew) : attackOdds.DefenderPower(enemyCrew)));
				
				unsigned total = yourPower + enemyPower;
				if(!total)
					break;
				
				if(Random::Int(total) >= yourPower)
					you->AddCrew(-1);
				else
					victim->AddCrew(-1);
			}
			
			int yourCasualties = yourStartCrew - you->Crew();
			int enemyCasualties = enemyStartCrew - victim->Crew();
			if(yourCasualties && enemyCasualties)
				messages.back() += "You lose " + to_string(yourCasualties)
					+ " crew; they lose " + to_string(enemyCasualties) + ".";
			else if(yourCasualties)
				messages.back() += "You lose " + to_string(yourCasualties) + " crew.";
			else if(enemyCasualties)
				messages.back() += "They lose " + to_string(enemyCasualties) + " crew.";
			
			if(!you->Crew())
			{
				messages.push_back("You have been killed. Your ship is lost.");
				you->WasCaptured(victim);
				you->SetIsYours(false);
				playerDied = true;
				isCapturing = false;
			}
			else if(!victim->Crew())
			{
				casualties = initialCrew - you->Crew();
				messages.push_back("You have succeeded in capturing this ship.");
				victim->WasCaptured(you);
				if(!victim->JumpsRemaining() && you->CanRefuel(*victim))
					you->TransferFuel(victim->JumpFuel(), &*victim);
				player.AddShip(victim);
				if(!victim->CanBeCarried())
					victim->SetParent(you);
				else
					for(const shared_ptr<Ship> &ship : player.Ships())
						if(ship->CanHoldFighter(*victim))
						{
							victim->SetParent(ship);
							break;
						}
				isCapturing = false;
				
				int64_t bonus = (victim->Cost() * casualties) / (casualties + 2);
				crewBonus += bonus;
				
				ShipEvent event(you, victim, ShipEvent::CAPTURE);
				player.HandleEvent(event, GetUI());
			}
		}
	}
	else if(command.Has(Command::INFO))
		GetUI()->Push(new InfoPanel(player, true));
	
	// Trim the list of status messages.
	while(messages.size() > 5)
		messages.erase(messages.begin());
	
	return true;
}
// Handle key presses.
bool ConversationPanel::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command)
{
	// Map popup happens when you press the map key, unless the name text entry
	// fields are currently active.
	if(command.Has(Command::MAP) && !choices.empty())
		GetUI()->Push(new MapDetailPanel(player, system));
	if(node < 0)
	{
		// If the conversation has ended, the only possible action is to exit.
		if(key == SDLK_RETURN)
		{
			Exit();
			return true;
		}
		return false;
	}
	if(choices.empty())
	{
		// Right now we're asking the player to enter their name.
		string &name = (choice ? lastName : firstName);
		string &otherName = (choice ? firstName : lastName);
		// Allow editing the text. The tab key toggles to the other entry field,
		// as does the return key if the other field is still empty.
		if(key >= ' ' && key <= '~')
		{
			// Apply the shift or caps lock key.
			char c = ((mod & (KMOD_SHIFT | KMOD_CAPS)) ? SHIFT[key] : key);
			// Don't allow characters that can't be used in a file name.
			static const string FORBIDDEN = "/\\?*:|\"<>~";
			if(FORBIDDEN.find(c) == string::npos)
				name += c;
		}
		else if((key == SDLK_DELETE || key == SDLK_BACKSPACE) && name.size())
			name.erase(name.size() - 1);
		else if(key == '\t' || (key == SDLK_RETURN && otherName.empty()))
			choice = !choice;
		else if(key == SDLK_RETURN && !firstName.empty() && !lastName.empty())
		{
			// Display the name the player entered.
			string name = "\t\tName: " + firstName + " " + lastName + ".\n";
			text.emplace_back(name);
			
			player.SetName(firstName, lastName);
			subs["<first>"] = player.FirstName();
			subs["<last>"] = player.LastName();
			
			Goto(node + 1);
		}
		else
			return false;
		
		return true;
	}
	
	// Let the player select choices by using the arrow keys and then pressing
	// return, or by pressing a number key.
	if(key == SDLK_UP && choice > 0)
		--choice;
	else if(key == SDLK_DOWN && choice < conversation.Choices(node) - 1)
		++choice;
	else if(key == SDLK_RETURN && choice < conversation.Choices(node))
		Goto(conversation.NextNode(node, choice), choice);
	else if(key > '0' && key <= static_cast<SDL_Keycode>('0' + choices.size()))
		Goto(conversation.NextNode(node, key - '1'), key - '1');
	else
		return false;
	
	return true;
}
// Only override the ones you need; the default action is to return false.
bool MapDetailPanel::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command)
{
	if(command.Has(Command::MAP) || key == 'd' || key == SDLK_ESCAPE || (key == 'w' && (mod & (KMOD_CTRL | KMOD_GUI))))
		GetUI()->Pop(this);
	else if(key == SDLK_PAGEUP || key == SDLK_PAGEDOWN || key == 'i')
	{
		GetUI()->Pop(this);
		GetUI()->Push(new MissionPanel(*this));
	}
	else if(key == 'o')
	{
		GetUI()->Pop(this);
		GetUI()->Push(new MapOutfitterPanel(*this));
	}
	else if(key == 's')
	{
		GetUI()->Pop(this);
		GetUI()->Push(new MapShipyardPanel(*this));
	}
	else if((key == SDLK_TAB || command.Has(Command::JUMP)) && player.Flagship())
	{
		// Toggle to the next link connected to the "source" system. If the
		// shift key is down, the source is the end of the travel plan; otherwise
		// it is one step before the end.
		vector<const System *> &plan = player.TravelPlan();
		const System *source = plan.empty() ? player.GetSystem() : plan.front();
		const System *next = nullptr;
		Point previousUnit = Point(0., -1.);
		if(!plan.empty() && !(mod & KMOD_SHIFT))
		{
			previousUnit = plan.front()->Position();
			plan.erase(plan.begin());
			next = source;
			source = plan.empty() ? player.GetSystem() : plan.front();
			previousUnit = (previousUnit - source->Position()).Unit();
		}
		Point here = source->Position();
		const System *original = next;
		
		// Depending on whether the flagship has a jump drive, the possible links
		// we can travel along are different:
		bool hasJumpDrive = player.Flagship()->Attributes().Get("jump drive");
		const vector<const System *> &links = hasJumpDrive ? source->Neighbors() : source->Links();
		
		// For each link we can travel from this system, check whether the link
		// is closer to the current angle (while still being larger) than any
		// link we have seen so far.
		auto bestAngle = make_pair(4., 0.);
		for(const System *it : links)
		{
			// Skip the currently selected link, if any. Also skip links to
			// systems the player has not seen, and skip hyperspace links if the
			// player has not visited either end of them.
			if(it == original)
				continue;
			if(!player.HasSeen(it))
				continue;
			if(!(hasJumpDrive || player.HasVisited(it) || player.HasVisited(source)))
				continue;
			
			// Generate a sortable angle with vector length as a tiebreaker.
			// Otherwise if two systems are in exactly the same direction it is
			// not well defined which one comes first.
			auto angle = SortAngle(previousUnit, it->Position() - here);
			if(angle < bestAngle)
			{
				next = it;
				bestAngle = angle;
			}
		}
		if(next)
		{
			plan.insert(plan.begin(), next);
			Select(next);
		}
	}
	else if((key == SDLK_DELETE || key == SDLK_BACKSPACE) && player.HasTravelPlan())
	{
		vector<const System *> &plan = player.TravelPlan();
		plan.erase(plan.begin());
		Select(plan.empty() ? player.GetSystem() : plan.front());
	}
	else if(key == SDLK_DOWN)
	{
		if(commodity < 0 || commodity == 9)
			SetCommodity(0);
		else
			SetCommodity(commodity + 1);
	}
	else if(key == SDLK_UP)
	{
		if(commodity <= 0)
			SetCommodity(9);
		else
			SetCommodity(commodity - 1);
	}
	else if(key == 'f')
		GetUI()->Push(new Dialog(
			this, &MapDetailPanel::DoFind, "Search for:"));
	else if(key == '+' || key == '=')
		ZoomMap();
	else if(key == '-')
		UnzoomMap();
	else
		return false;
	
	return true;
}
// Only override the ones you need; the default action is to return false.
bool MapOutfitterPanel::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command)
{
	if(command.Has(Command::MAP) || key == 'd' || key == SDLK_ESCAPE || (key == 'w' && (mod & (KMOD_CTRL | KMOD_GUI))))
		GetUI()->Pop(this);
	else if(key == 's')
	{
		GetUI()->Pop(this);
		GetUI()->Push(new MapShipyardPanel(*this));
	}
	else if(key == 'i')
	{
		GetUI()->Pop(this);
		GetUI()->Push(new MissionPanel(*this));
	}
	else if(key == 'p')
	{
		GetUI()->Pop(this);
		GetUI()->Push(new MapDetailPanel(*this));
	}
	else if(key == 'c')
	{
		if(!compare)
			compare = selected;
		else if (compare!=selected)
			compare = selected;
		else
			compare = nullptr;
	}
	else if((key == SDLK_DOWN || key == SDLK_UP) && !zones.empty())
	{
		// First, find the currently selected item, if any
		auto it = zones.begin();
		if(!selected)
		{
			if(key == SDLK_DOWN)
				it = --zones.end();
		}
		else
		{
			for( ; it != zones.end() - 1; ++it)
				if(it->Value() == selected)
					break;
		}
		if(key == SDLK_DOWN)
		{
			++it;
			if(it == zones.end())
				it = zones.begin();
		}
		else
		{
			if(it == zones.begin())
				it = zones.end();
			--it;
		}
		double top = (it->Center() - it->Size()).Y();
		double bottom = (it->Center() + it->Size()).Y();
		if(bottom > Screen::Bottom())
			scroll += Screen::Bottom() - bottom;
		if(top < Screen::Top())
			scroll += Screen::Top() - top;
		selected = it->Value();
	}
	else if(key == SDLK_PAGEUP || key == SDLK_PAGEDOWN)
	{
		scroll += (Screen::Height() - 100) * ((key == SDLK_PAGEUP) - (key == SDLK_PAGEDOWN));
		scroll = min(0, max(-maxScroll, scroll));
	}
	else
		return false;
	
	return true;
}
Exemple #24
0
bool LoadPanel::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command)
{
	if(key == 'n')
	{
		GameData::Revert();
		player.New();
		
		Messages::Reset();
		ConversationPanel *panel = new ConversationPanel(
			player, *GameData::Conversations().Get("intro"));
		GetUI()->Push(panel);
		panel->SetCallback(this, &LoadPanel::OnCallback);
	}
	else if(key == 'd' && !selectedPilot.empty())
	{
		GetUI()->Push(new Dialog(this, &LoadPanel::DeletePilot,
			"Are you sure you want to delete the selected pilot, \""
				+ selectedPilot + "\", and all their saved games?"));
	}
	else if(key == 'a')
	{
		string wasSelected = selectedPilot;
		auto it = files.find(selectedPilot);
		if(it == files.end() || it->second.empty() || it->second.front().size() < 4)
			return false;
		
		GetUI()->Push(new Dialog(this, &LoadPanel::SnapshotCallback,
			"Enter a name for this snapshot, or leave the name empty to use the current date:"));
	}
	else if(key == 'r' && !selectedFile.empty())
	{
		GetUI()->Push(new Dialog(this, &LoadPanel::DeleteSave,
			"Are you sure you want to delete the selected saved game file, \""
				+ selectedFile + "\"?"));
	}
	else if(key == 'l' || key == 'e')
	{
		// Is the selected file a snapshot or the pilot's main file?
		string fileName = selectedFile.substr(selectedFile.rfind('/') + 1);
		if(fileName == selectedPilot + ".txt")
			LoadCallback();
		else
			GetUI()->Push(new Dialog(this, &LoadPanel::LoadCallback,
				"If you load this snapshot, it will overwrite your current game. "
				"Any progress will be lost, unless you have saved other snapshots. "
				"Are you sure you want to do that?"));
	}
	else if(key == 'b' || command.Has(Command::MENU) || (key == 'w' && (mod & (KMOD_CTRL | KMOD_GUI))))
		GetUI()->Pop(this);
	else if((key == SDLK_DOWN || key == SDLK_UP) && !files.empty())
	{
		auto pit = files.find(selectedPilot);
		if(sideHasFocus)
		{
			auto it = files.begin();
			for( ; it != files.end(); ++it)
				if(it->first == selectedPilot)
					break;
			
			if(key == SDLK_DOWN)
			{
				++it;
				if(it == files.end())
					it = files.begin();
			}
			else
			{
				if(it == files.begin())
					it = files.end();
				--it;
			}
			selectedPilot = it->first;
			selectedFile = it->second.front();
		}
		else if(pit != files.end())
		{
			auto it = pit->second.begin();
			for( ; it != pit->second.end(); ++it)
				if(*it == selectedFile)
					break;
			
			if(key == SDLK_DOWN)
			{
				++it;
				if(it == pit->second.end())
					it = pit->second.begin();
			}
			else
			{
				if(it == pit->second.begin())
					it = pit->second.end();
				--it;
			}
			selectedFile = *it;
		}
		loadedInfo.Load(Files::Saves() + selectedFile);
	}
	else if(key == SDLK_LEFT)
		sideHasFocus = true;
	else if(key == SDLK_RIGHT)
		sideHasFocus = false;
	else
		return false;
	
	return true;
}
Exemple #25
0
bool LoadPanel::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command)
{
	if(key == 'n')
	{
		GameData::Revert();
		player.New();
		
		Messages::Reset();
		ConversationPanel *panel = new ConversationPanel(
			player, *GameData::Conversations().Get("intro"));
		GetUI()->Push(panel);
		panel->SetCallback(this, &LoadPanel::OnCallback);
	}
	else if(key == 'd' && !selectedPilot.empty())
	{
		GetUI()->Push(new Dialog(this, &LoadPanel::DeletePilot,
			"Are you sure you want to delete the selected pilot, \""
				+ selectedPilot + "\", and all their saved games?"));
	}
	else if(key == 'a')
	{
		string wasSelected = selectedPilot;
		auto it = files.find(selectedPilot);
		if(it == files.end() || it->second.empty() || it->second.front().size() < 4)
			return false;
		
		// Extract the date from this pilot's most recent save.
		string date = "~0000-00-00.txt";
		string from = Files::Saves() + it->second.front();
		DataFile file(from);
		for(const DataNode &node : file)
			if(node.Token(0) == "date")
			{
				int year = node.Value(3);
				int month = node.Value(2);
				int day = node.Value(1);
				date[1] += (year / 1000) % 10;
				date[2] += (year / 100) % 10;
				date[3] += (year / 10) % 10;
				date[4] += year % 10;
				date[6] += (month / 10) % 10;
				date[7] += month % 10;
				date[9] += (day / 10) % 10;
				date[10] += day % 10;
			}
		
		// Copy the autosave to a new, named file.
		string to = from.substr(0, from.size() - 4) + date;
		Files::Copy(from, to);
		UpdateLists();
		
		selectedPilot = wasSelected;
		selectedFile = Files::Name(to);
		loadedInfo.Load(Files::Saves() + selectedFile);
	}
	else if(key == 'r' && !selectedFile.empty())
	{
		GetUI()->Push(new Dialog(this, &LoadPanel::DeleteSave,
			"Are you sure you want to delete the selected saved game file, \""
				+ selectedFile + "\"?"));
	}
	else if(key == 'e')
	{
		// First, make sure the previous MainPanel has been deleted, so
		// its background thread is no longer running.
		gamePanels.Reset();
		
		GameData::Revert();
		player.Load(loadedInfo.Path());
		player.ApplyChanges();
		
		Messages::Reset();
		GetUI()->Pop(this);
		GetUI()->Pop(GetUI()->Root().get());
		gamePanels.Push(new MainPanel(player));
	}
	else if(key == 'b' || command.Has(Command::MENU))
		GetUI()->Pop(this);
	else if((key == SDLK_DOWN || key == SDLK_UP) && !files.empty())
	{
		auto pit = files.find(selectedPilot);
		if(sideHasFocus)
		{
			auto it = files.begin();
			for( ; it != files.end(); ++it)
				if(it->first == selectedPilot)
					break;
			
			if(key == SDLK_DOWN)
			{
				++it;
				if(it == files.end())
					it = files.begin();
			}
			else
			{
				if(it == files.begin())
					it = files.end();
				--it;
			}
			selectedPilot = it->first;
			selectedFile = it->second.front();
		}
		else if(pit != files.end())
		{
			auto it = pit->second.begin();
			for( ; it != pit->second.end(); ++it)
				if(*it == selectedFile)
					break;
			
			if(key == SDLK_DOWN)
			{
				++it;
				if(it == pit->second.end())
					it = pit->second.begin();
			}
			else
			{
				if(it == pit->second.begin())
					it = pit->second.end();
				--it;
			}
			selectedFile = *it;
		}
		loadedInfo.Load(Files::Saves() + selectedFile);
	}
	else if(key == SDLK_LEFT)
		sideHasFocus = true;
	else if(key == SDLK_RIGHT)
		sideHasFocus = false;
	else
		return false;
	
	return true;
}
Exemple #26
0
// Only override the ones you need; the default action is to return false.
bool PlanetPanel::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command)
{
	Panel *oldPanel = selectedPanel;
	const Ship *flagship = player.Flagship();
	
	bool hasAccess = planet.CanUseServices();
	if(key == 'd' && flagship && flagship->CanBeFlagship())
	{
		player.Save();
		if(player.TakeOff(GetUI()))
		{
			if(callback)
				callback();
			GetUI()->Pop(this);
		}
	}
	else if(key == 'l')
	{
		selectedPanel = nullptr;
	}
	else if(key == 't' && flagship && planet.IsInhabited() && hasAccess)
	{
		selectedPanel = trading.get();
		GetUI()->Push(trading);
	}
	else if(key == 'b' && planet.IsInhabited() && hasAccess)
	{
		selectedPanel = bank.get();
		GetUI()->Push(bank);
	}
	else if(key == 'p' && flagship && planet.HasSpaceport() && hasAccess)
	{
		selectedPanel = spaceport.get();
		GetUI()->Push(spaceport);
	}
	else if(key == 's' && planet.HasShipyard() && hasAccess)
	{
		GetUI()->Push(new ShipyardPanel(player));
		return true;
	}
	else if(key == 'o' && planet.HasOutfitter() && hasAccess)
	{
		bool hasShip = false;
		for(const auto &it : player.Ships())
			if(it->GetSystem() == player.GetSystem() && !it->IsDisabled())
			{
				hasShip = true;
				break;
			}
		if(hasShip)
			GetUI()->Push(new OutfitterPanel(player));
		return true;
	}
	else if(key == 'j' && flagship && planet.IsInhabited() && hasAccess)
	{
		GetUI()->Push(new MissionPanel(player));
		return true;
	}
	else if(key == 'h' && flagship && planet.IsInhabited() && hasAccess)
	{
		selectedPanel = hiring.get();
		GetUI()->Push(hiring);
	}
	else if(command.Has(Command::MAP))
	{
		GetUI()->Push(new MapDetailPanel(player));
		return true;
	}
	else if(command.Has(Command::INFO))
	{
		GetUI()->Push(new InfoPanel(player));
		return true;
	}
	else
		return false;
	
	// If we are here, it is because something happened to change the selected
	// panel. So, we need to pop the old selected panel:
	if(oldPanel)
		GetUI()->Pop(oldPanel);
	
	return true;
}
// Only override the ones you need; the default action is to return false.
bool MissionPanel::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command)
{
	if(key == 'd' || key == SDLK_ESCAPE || (key == 'w' && (mod & (KMOD_CTRL | KMOD_GUI))))
	{
		GetUI()->Pop(this);
		return true;
	}
	else if(key == 'a' && CanAccept())
	{
		Accept();
		return true;
	}
	else if(key == 'A' || (key == 'a' && (mod & KMOD_SHIFT)))
	{
		if(acceptedIt != accepted.end() && acceptedIt->IsVisible())
			GetUI()->Push(new Dialog(this, &MissionPanel::AbortMission,
				"Abort mission \"" + acceptedIt->Name() + "\"?"));
		return true;
	}
	else if(key == 'p' || key == SDLK_PAGEUP || key == SDLK_PAGEDOWN)
	{
		GetUI()->Pop(this);
		GetUI()->Push(new MapDetailPanel(*this));
	}
	else if(key == 'o')
	{
		GetUI()->Pop(this);
		GetUI()->Push(new MapOutfitterPanel(*this));
	}
	else if(key == 's')
	{
		GetUI()->Pop(this);
		GetUI()->Push(new MapShipyardPanel(*this));
	}
	else if(key == SDLK_LEFT && availableIt == available.end())
	{
		acceptedIt = accepted.end();
		availableIt = available.begin();
	}
	else if(key == SDLK_RIGHT && acceptedIt == accepted.end() && AcceptedVisible())
	{
		availableIt = available.end();
		acceptedIt = accepted.begin();
		while(acceptedIt != accepted.end() && !acceptedIt->IsVisible())
			++acceptedIt;
	}
	else if(key == SDLK_UP)
	{
		SelectAnyMission();
		if(availableIt != available.end())
		{
			if(availableIt == available.begin())
				availableIt = available.end();
			--availableIt;
		}
		else if(acceptedIt != accepted.end())
		{
			do {
				if(acceptedIt == accepted.begin())
					acceptedIt = accepted.end();
				--acceptedIt;
			} while(!acceptedIt->IsVisible());
		}
	}
	else if(key == SDLK_DOWN && !SelectAnyMission())
	{
		if(availableIt != available.end())
		{
			++availableIt;
			if(availableIt == available.end())
				availableIt = available.begin();
		}
		else if(acceptedIt != accepted.end())
		{
			do {
				++acceptedIt;
				if(acceptedIt == accepted.end())
					acceptedIt = accepted.begin();
			} while(!acceptedIt->IsVisible());
		}
	}
	else if(command.Has(Command::MAP))
	{
		GetUI()->Pop(this);
		GetUI()->Push(new MapDetailPanel(*this));
		return true;
	}
	else if(key == 'f')
	{
		GetUI()->Push(new Dialog(
			this, &MissionPanel::DoFind, "Search for:"));
		return true;
	}
	else if(key == '+' || key == '=')
		ZoomMap();
	else if(key == '-')
		UnzoomMap();
	else
		return false;
	
	if(availableIt != available.end())
		selectedSystem = availableIt->Destination()->GetSystem();
	else if(acceptedIt != accepted.end())
		selectedSystem = acceptedIt->Destination()->GetSystem();
	if(selectedSystem)
		center = Point(0., -80.) - selectedSystem->Position();
	
	return true;
}
// Only override the ones you need; the default action is to return false.
bool MapDetailPanel::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command)
{
	if(command.Has(Command::MAP) || key == 'd' || key == SDLK_ESCAPE || (key == 'w' && (mod & (KMOD_CTRL | KMOD_GUI))))
		GetUI()->Pop(this);
	else if(key == SDLK_PAGEUP || key == SDLK_PAGEDOWN || key == 'i')
	{
		GetUI()->Pop(this);
		GetUI()->Push(new MissionPanel(*this));
	}
	else if(key == 'o')
	{
		GetUI()->Pop(this);
		GetUI()->Push(new MapOutfitterPanel(*this));
	}
	else if(key == 's')
	{
		GetUI()->Pop(this);
		GetUI()->Push(new MapShipyardPanel(*this));
	}
	else if((key == SDLK_TAB || command.Has(Command::JUMP)) && player.Flagship())
	{
		// Toggle to the next link connected to the "source" system. If the
		// shift key is down, the source is the end of the travel plan; otherwise
		// it is one step before the end.
		vector<const System *> &plan = player.TravelPlan();
		const System *source = plan.empty() ? player.GetSystem() : plan.front();
		const System *next = nullptr;
		Point previousUnit = Point(0., -1.);
		if(!plan.empty() && !(mod & KMOD_SHIFT))
		{
			previousUnit = plan.front()->Position();
			plan.erase(plan.begin());
			next = source;
			source = plan.empty() ? player.GetSystem() : plan.front();
			previousUnit = (previousUnit - source->Position()).Unit();
		}
		Point here = source->Position();
		
		// Depending on whether the flagship has a jump drive, the possible links
		// we can travel along are different:
		bool hasJumpDrive = player.Flagship()->Attributes().Get("jump drive");
		const vector<const System *> &links = hasJumpDrive ? source->Neighbors() : source->Links();
		
		double bestAngle = 2. * PI;
		for(const System *it : links)
		{
			if(!player.HasSeen(it))
				continue;
			if(!(hasJumpDrive || player.HasVisited(it) || player.HasVisited(source)))
				continue;
			
			Point unit = (it->Position() - here).Unit();
			double angle = acos(unit.Dot(previousUnit));
			if(unit.Cross(previousUnit) >= 0.)
				angle = 2. * PI - angle;
			
			if(angle <= bestAngle)
			{
				next = it;
				bestAngle = angle;
			}
		}
		if(next)
		{
			plan.insert(plan.begin(), next);
			Select(next);
		}
	}
	else if((key == SDLK_DELETE || key == SDLK_BACKSPACE) && player.HasTravelPlan())
	{
		vector<const System *> &plan = player.TravelPlan();
		plan.erase(plan.begin());
		Select(plan.empty() ? player.GetSystem() : plan.front());
	}
	else if(key == SDLK_DOWN)
	{
		if(commodity < 0 || commodity == 9)
			commodity = 0;
		else
			++commodity;
	}
	else if(key == SDLK_UP)
	{
		if(commodity <= 0)
			commodity = 9;
		else
			--commodity;
	}
	else if(key == 'f')
		GetUI()->Push(new Dialog(
			this, &MapDetailPanel::DoFind, "Search for:"));
	else if(key == '+' || key == '=')
		ZoomMap();
	else if(key == '-')
		UnzoomMap();
	else
		return false;
	
	return true;
}
// Only override the ones you need; the default action is to return false.
bool PlanetPanel::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command)
{
	Panel *oldPanel = selectedPanel;
	const Ship *flagship = player.Flagship();
	
	bool hasAccess = planet.CanUseServices();
	if(key == 'd' && flagship && flagship->CanBeFlagship())
	{
		// Check whether the player should be warned before taking off.
		if(player.ShouldLaunch())
			TakeOff();
		else
		{
			// The checks that follow are typically cause by parking or selling
			// ships or changing outfits.

			// Are you overbooked? Don't count fireable flagship crew.
			const CargoHold &cargo = player.Cargo();
			int overbooked = -cargo.Bunks() - (flagship->Crew() - flagship->RequiredCrew());
			int missionCargoToSell = cargo.MissionCargoSize() - cargo.Size();
			// Will you have to sell something other than regular cargo?
			int cargoToSell = -(cargo.Free() + cargo.CommoditiesSize());
			int droneCount = 0;
			int fighterCount = 0;
			for(const auto &it : player.Ships())
				if(!it->IsParked() && !it->IsDisabled() && it->GetSystem() == player.GetSystem())
				{
					const string &category = it->Attributes().Category();
					droneCount += (category == "Drone") - it->BaysFree(false);
					fighterCount += (category == "Fighter") - it->BaysFree(true);
				}
			
			if(fighterCount > 0 || droneCount > 0 || cargoToSell > 0 || overbooked > 0)
			{
				ostringstream out;
				if(missionCargoToSell > 0 || overbooked > 0)
				{
					bool both = ((cargoToSell > 0 && cargo.MissionCargoSize()) && overbooked > 0);
					out << "If you take off now you will fail a mission due to not having enough ";

					if(overbooked > 0)
					{
						out << "bunks available for " << overbooked;
						out << (overbooked > 1 ? " of the passengers" : " passenger");
						out << (both ? " and not having enough " : ".");
					}

					if(missionCargoToSell > 0)
					{
						out << "cargo space to hold " << missionCargoToSell;
						out << (missionCargoToSell > 1 ? " tons" : " ton");
						out << " of your mission cargo.";
					}
				}
				else
				{
					out << "If you take off now you will have to sell ";
					bool triple = (fighterCount > 0 && droneCount > 0 && cargoToSell > 0);

					if(fighterCount == 1)
						out << "a fighter";
					else if(fighterCount > 0)
						out << fighterCount << " fighters";
					if(fighterCount > 0 && (droneCount > 0 || cargoToSell > 0))
						out << (triple ? ", " : " and ");
				
					if(droneCount == 1)
						out << "a drone";
					else if(droneCount > 0)
						out << droneCount << " drones";
					if(droneCount > 0 && cargoToSell > 0)
						out << (triple ? ", and " : " and ");

					if(cargoToSell == 1)
						out << "a ton of cargo";
					else if(cargoToSell > 0)
						out << cargoToSell << " tons of cargo";
					out << " that you do not have space for.";
				}
				out << " Are you sure you want to continue?";
				GetUI()->Push(new Dialog(this, &PlanetPanel::TakeOff, out.str()));
				return true;
			}
			else
				TakeOff();
		}
	}
	else if(key == 'l')
	{
		selectedPanel = nullptr;
	}
	else if(key == 't' && flagship && planet.IsInhabited() && hasAccess)
	{
		selectedPanel = trading.get();
		GetUI()->Push(trading);
	}
	else if(key == 'b' && planet.IsInhabited() && hasAccess)
	{
		selectedPanel = bank.get();
		GetUI()->Push(bank);
	}
	else if(key == 'p' && flagship && planet.HasSpaceport() && hasAccess)
	{
		selectedPanel = spaceport.get();
		GetUI()->Push(spaceport);
	}
	else if(key == 's' && planet.HasShipyard() && hasAccess)
	{
		GetUI()->Push(new ShipyardPanel(player));
		return true;
	}
	else if(key == 'o' && planet.HasOutfitter() && hasAccess)
	{
		bool hasShip = false;
		for(const auto &it : player.Ships())
			if(it->GetSystem() == player.GetSystem() && !it->IsDisabled())
			{
				hasShip = true;
				break;
			}
		if(hasShip)
			GetUI()->Push(new OutfitterPanel(player));
		return true;
	}
	else if(key == 'j' && flagship && planet.IsInhabited() && hasAccess)
	{
		GetUI()->Push(new MissionPanel(player));
		return true;
	}
	else if(key == 'h' && flagship && planet.IsInhabited() && hasAccess)
	{
		selectedPanel = hiring.get();
		GetUI()->Push(hiring);
	}
	else if(command.Has(Command::MAP))
	{
		GetUI()->Push(new MapDetailPanel(player));
		return true;
	}
	else if(command.Has(Command::INFO))
	{
		GetUI()->Push(new InfoPanel(player));
		return true;
	}
	else
		return false;
	
	// If we are here, it is because something happened to change the selected
	// panel. So, we need to pop the old selected panel:
	if(oldPanel)
		GetUI()->Pop(oldPanel);
	
	return true;
}
bool ShipInfoPanel::KeyDown(SDL_Keycode key, Uint16 mod, const Command &command, bool isNewPress)
{
	bool shift = (mod & KMOD_SHIFT);
	if(key == 'd' || key == SDLK_ESCAPE || (key == 'w' && (mod & (KMOD_CTRL | KMOD_GUI))))
		GetUI()->Pop(this);
	else if(!player.Ships().empty() && ((key == 'p' && !shift) || key == SDLK_LEFT || key == SDLK_UP))
	{
		if(shipIt == player.Ships().begin())
			shipIt = player.Ships().end();
		--shipIt;
		UpdateInfo();
	}
	else if(!player.Ships().empty() && (key == 'n' || key == SDLK_RIGHT || key == SDLK_DOWN))
	{
		++shipIt;
		if(shipIt == player.Ships().end())
			shipIt = player.Ships().begin();
		UpdateInfo();
	}
	else if(key == 'i' || command.Has(Command::INFO))
	{
		GetUI()->Pop(this);
		GetUI()->Push(new PlayerInfoPanel(player));
	}
	else if(key == 'R' || (key == 'r' && shift))
		GetUI()->Push(new Dialog(this, &ShipInfoPanel::Rename, "Change this ship's name?"));
	else if(canEdit && (key == 'P' || (key == 'p' && shift)))
	{
		if(shipIt->get() != player.Flagship() || (*shipIt)->IsParked())
			player.ParkShip(shipIt->get(), !(*shipIt)->IsParked());
	}
	else if(canEdit && key == 'D')
	{
		if(shipIt->get() != player.Flagship())
			GetUI()->Push(new Dialog(this, &ShipInfoPanel::Disown, "Are you sure you want to disown \""
				+ shipIt->get()->Name()
				+ "\"? Disowning a ship rather than selling it means you will not get any money for it."));
	}
	else if(key == 'c' && CanDump())
	{
		int commodities = (*shipIt)->Cargo().CommoditiesSize();
		int amount = (*shipIt)->Cargo().Get(selectedCommodity);
		int plunderAmount = (*shipIt)->Cargo().Get(selectedPlunder);
		if(amount)
		{
			GetUI()->Push(new Dialog(this, &ShipInfoPanel::DumpCommodities,
				"How many tons of " + Format::LowerCase(selectedCommodity)
					+ " do you want to jettison?", amount));
		}
		else if(plunderAmount > 0 && selectedPlunder->Get("installable") < 0.)
		{
			GetUI()->Push(new Dialog(this, &ShipInfoPanel::DumpPlunder,
				"How many tons of " + Format::LowerCase(selectedPlunder->Name())
					+ " do you want to jettison?", plunderAmount));
		}
		else if(plunderAmount == 1)
		{
			GetUI()->Push(new Dialog(this, &ShipInfoPanel::Dump,
				"Are you sure you want to jettison a " + selectedPlunder->Name() + "?"));
		}
		else if(plunderAmount > 1)
		{
			GetUI()->Push(new Dialog(this, &ShipInfoPanel::DumpPlunder,
				"How many " + selectedPlunder->PluralName() + " do you want to jettison?",
				plunderAmount));
		}
		else if(commodities)
		{
			GetUI()->Push(new Dialog(this, &ShipInfoPanel::Dump,
				"Are you sure you want to jettison all of this ship's regular cargo?"));
		}
		else
		{
			GetUI()->Push(new Dialog(this, &ShipInfoPanel::Dump,
				"Are you sure you want to jettison all of this ship's cargo?"));
		}
	}
	else if(command.Has(Command::MAP) || key == 'm')
		GetUI()->Push(new MissionPanel(player));
	else if(key == 'l' && player.HasLogs())
		GetUI()->Push(new LogbookPanel(player));
	else
		return false;
	
	return true;
}