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