Ejemplo n.º 1
0
void MapPanel::DrawMissions() const
{
	// Draw a pointer for each active or available mission.
	map<const System *, Angle> angle;
	Color black(0., 1.);
	Color white(1., 1.);
	
	const Set<Color> &colors = GameData::Colors();
	const Color &availableColor = *colors.Get("available job");
	const Color &unavailableColor = *colors.Get("unavailable job");
	const Color &currentColor = *colors.Get("active mission");
	const Color &blockedColor = *colors.Get("blocked mission");
	const Color &waypointColor = *colors.Get("waypoint");
	for(const Mission &mission : player.AvailableJobs())
	{
		const System *system = mission.Destination()->GetSystem();
		Angle a = (angle[system] += Angle(30.));
		Point pos = system->Position() + center;
		PointerShader::Draw(pos, a.Unit(), 14., 19., -4., black);
		PointerShader::Draw(pos, a.Unit(), 8., 15., -6.,
			mission.HasSpace(player) ? availableColor : unavailableColor);
	}
	++step;
	for(const Mission &mission : player.Missions())
	{
		if(!mission.IsVisible())
			continue;
		
		const System *system = mission.Destination()->GetSystem();
		Angle a = (angle[system] += Angle(30.));
		
		bool blink = false;
		if(mission.HasDeadline())
		{
			int days = min(5, mission.Deadline() - player.GetDate()) + 1;
			if(days > 0)
				blink = (step % (10 * days) > 5 * days);
		}
		Point pos = system->Position() + center;
		PointerShader::Draw(pos, a.Unit(), 14., 19., -4., black);
		if(!blink)
			PointerShader::Draw(pos, a.Unit(), 8., 15., -6.,
				IsSatisfied(mission) ? currentColor : blockedColor);
		
		for(const System *waypoint : mission.Waypoints())
		{
			Angle a = (angle[waypoint] += Angle(30.));
			Point pos = waypoint->Position() + center;
			PointerShader::Draw(pos, a.Unit(), 14., 19., -4., black);
			PointerShader::Draw(pos, a.Unit(), 8., 15., -6., waypointColor);
		}
	}
	if(specialSystem)
	{
		Angle a = (angle[specialSystem] += Angle(30.));
		Point pos = specialSystem->Position() + center;
		PointerShader::Draw(pos, a.Unit(), 20., 27., -4., black);
		PointerShader::Draw(pos, a.Unit(), 11.5, 21.5, -6., white);
	}
}
Ejemplo n.º 2
0
double AI::TurnBackward(const Ship &ship)
{
	Angle angle = ship.Facing();
	bool left = ship.Velocity().Cross(angle.Unit()) > 0.;
	double turn = left - !left;
	
	// Check if the ship will still be pointing to the same side of the target
	// angle if it turns by this amount.
	angle += ship.TurnRate() * turn;
	bool stillLeft = ship.Velocity().Cross(angle.Unit()) > 0.;
	if(left == stillLeft)
		return turn;
	
	// If we're within one step of the correct direction, stop turning.
	return 0.;
}
Ejemplo n.º 3
0
// Do the randomization to make a ship enter or be in the given system.
void Fleet::Enter(const System &system, Ship &ship)
{
	if(!system.Links().size())
	{
		Place(system, ship);
		return;
	}
	
	const System *source = system.Links()[Random::Int(system.Links().size())];
	Angle angle = Angle::Random();
	Point pos = angle.Unit() * Random::Real() * 1000.;
	
	ship.Place(pos, angle.Unit(), angle);
	ship.SetSystem(source);
	ship.SetTargetSystem(&system);
}
Ejemplo n.º 4
0
void Fleet::Place(const System &system, Ship &ship)
{
	// Count the inhabited planets in this system.
	int planets = 0;
	for(const StellarObject &object : system.Objects())
		if(object.GetPlanet() && object.GetPlanet()->HasSpaceport())
			++planets;
	
	// Determine where the fleet is going to or coming from.
	Point center;
	if(planets)
	{
		int index = Random::Int(planets);
		for(const StellarObject &object : system.Objects())
			if(object.GetPlanet() && object.GetPlanet()->HasSpaceport())
				if(!index--)
					center = object.Position();
	}
	
	// Move out a random distance from that object, facing toward it or away.
	center += Angle::Random().Unit() * (Random::Real() * 2. - 1.);
	Point pos = center + Angle::Random().Unit() * Random::Real() * 400.;
	
	double velocity = Random::Real() * ship.MaxVelocity();
	
	ship.SetSystem(&system);
	Angle angle = Angle::Random();
	ship.Place(pos, velocity * angle.Unit(), angle);
}
Ejemplo n.º 5
0
void MapPanel::DrawPointer(const System *system, Angle &angle, const Color &color) const
{
	static const Color black(0., 1.);
	
	angle += Angle(30.);
	Point pos = Zoom() * (system->Position() + center);
	PointerShader::Draw(pos, angle.Unit(), 14., 19., -4., black);
	PointerShader::Draw(pos, angle.Unit(), 8., 15., -6., color);
}
Ejemplo n.º 6
0
void Fleet::Place(const System &system, Ship &ship)
{
	// Move out a random distance from that object, facing toward it or away.
	Point pos = ChooseCenter(system) + Angle::Random().Unit() * Random::Real() * 400.;
	
	double velocity = Random::Real() * ship.MaxVelocity();
	
	ship.SetSystem(&system);
	Angle angle = Angle::Random();
	ship.Place(pos, velocity * angle.Unit(), angle);
}
Ejemplo n.º 7
0
// Fire this weapon. If it is a turret, it automatically points toward
// the given ship's target. If the weapon requires ammunition, it will
// be subtracted from the given ship.
void Armament::Weapon::Fire(Ship &ship, list<Projectile> &projectiles, list<Effect> &effects)
{
	// Since this is only called internally by Armament (no one else has non-
	// const access), assume Armament checked that this is a valid call.
	Angle aim = ship.Facing();
	
	// Get projectiles to start at the right position. They are drawn at an
	// offset of (.5 * velocity) and that velocity includes the velocity of the
	// ship that fired them.
	Point start = ship.Position() + aim.Rotate(point) - .5 * ship.Velocity();
	
	shared_ptr<const Ship> target = ship.GetTargetShip();
	// If you are boarding your target, do not fire on it.
	if(ship.IsBoarding() || ship.Commands().Has(Command::BOARD))
		target.reset();
	
	if(!isTurret || !target || target->GetSystem() != ship.GetSystem())
		aim += angle;
	else
	{
		Point p = target->Position() - start + ship.GetPersonality().Confusion();
		Point v = target->Velocity() - ship.Velocity();
		double steps = RendezvousTime(p, v, outfit->Velocity());
		
		// Special case: RendezvousTime() may return NaN. But in that case, this
		// comparison will return false.
		if(!(steps < outfit->TotalLifetime()))
			steps = outfit->TotalLifetime();
		
		p += steps * v;
		
		aim = Angle(TO_DEG * atan2(p.X(), -p.Y()));
	}
	
	projectiles.emplace_back(ship, start, aim, outfit);
	if(outfit->WeaponSound())
		Audio::Play(outfit->WeaponSound(), start);
	double force = outfit->FiringForce();
	if(force)
		ship.ApplyForce(aim.Unit() * -force);
	
	for(const auto &eit : outfit->FireEffects())
		for(int i = 0; i < eit.second; ++i)
		{
			effects.push_back(*eit.first);
			effects.back().Place(start, ship.Velocity(), aim);
		}
	
	Fire(ship);
}
Ejemplo n.º 8
0
// Check if this ship is currently able to enter hyperspace to it target.
int Ship::CheckHyperspace() const
{
	if(commands.Has(Command::WAIT))
		return 0;
	
	// Find out where we're going and how we're getting there,
	const System *destination = GetTargetSystem();
	int type = HyperspaceType();
	
	// You can't jump to a system with no link to it.
	if(!type)
		return 0;
	
	if(fuel < type)
		return 0;
	
	Point direction = destination->Position() - currentSystem->Position();
	
	// The ship can only enter hyperspace if it is traveling slowly enough
	// and pointed in the right direction.
	if(type == 150)
	{
		double deviation = fabs(direction.Unit().Cross(velocity));
		if(deviation > attributes.Get("scram drive"))
			return 0;
	}
	else if(velocity.Length() > attributes.Get("jump speed"))
		return 0;
	
	if(type != 200)
	{
		// Figure out if we're within one turn step of facing this system.
		bool left = direction.Cross(angle.Unit()) < 0.;
		Angle turned = angle + TurnRate() * (left - !left);
		bool stillLeft = direction.Cross(turned.Unit()) < 0.;
	
		if(left == stillLeft)
			return 0;
	}
	
	return type;
}
Ejemplo n.º 9
0
// Place a fleet in the given system, already "in action."
void Fleet::Place(const System &system, list<shared_ptr<Ship>> &ships, bool carried) const
{
	if(!total || !government || variants.empty())
		return;
	
	// Pick a fleet variant to instantiate.
	const Variant &variant = ChooseVariant();
	if(variant.ships.empty())
		return;
	
	// Determine where the fleet is going to or coming from.
	Point center = ChooseCenter(system);
	
	// Place all the ships in the chosen fleet variant.
	shared_ptr<Ship> flagship;
	vector<shared_ptr<Ship>> placed = Instantiate(variant);
	for(shared_ptr<Ship> &ship : placed)
	{
		// If this is a fighter and someone can carry it, no need to position it.
		if(carried && PlaceFighter(ship, placed))
			continue;
		
		Angle angle = Angle::Random();
		Point pos = center + Angle::Random().Unit() * Random::Real() * 400.;
		double velocity = Random::Real() * ship->MaxVelocity();
		
		ships.push_front(ship);
		ship->SetSystem(&system);
		ship->Place(pos, velocity * angle.Unit(), angle);
		
		if(flagship)
			ship->SetParent(flagship);
		else
			flagship = ship;
		
		SetCargo(&*ship);
	}
}
Ejemplo n.º 10
0
void Fleet::Enter(const System &system, list<shared_ptr<Ship>> &ships, const Planet *planet) const
{
	if(!total || !government)
		return;
	
	// Pick a random variant based on the weights.
	unsigned index = 0;
	for(int choice = Random::Int(total); choice >= variants[index].weight; ++index)
		choice -= variants[index].weight;
	
	if(variants[index].ships.empty())
		return;
	
	bool isEnemy = system.GetGovernment()->IsEnemy(government);
	bool hasJump = variants[index].ships.front()->Attributes().Get("jump drive");
	const vector<const System *> &linkVector = hasJump ? system.Neighbors() : system.Links();
	int links = linkVector.size();
	// Count the inhabited planets in this system.
	int planets = 0;
	if(!isEnemy && !personality.IsSurveillance())
		for(const StellarObject &object : system.Objects())
			if(object.GetPlanet() && object.GetPlanet()->HasSpaceport())
				++planets;
	
	if(!(links + planets))
		return;
	
	int choice = Random::Int(links + planets);
	
	const System *source = &system;
	const System *target = &system;
	Point position;
	unsigned radius = 0;
	
	if(planet)
	{
		for(const StellarObject &object : system.Objects())
			if(object.GetPlanet() == planet)
			{
				position = object.Position();
				radius = max(0, static_cast<int>(object.Radius()));
				break;
			}
	}
	else if(choice >= links)
	{
		choice -= links;
		
		planets = 0;
		for(const StellarObject &object : system.Objects())
			if(object.GetPlanet() && object.GetPlanet()->HasSpaceport())
			{
				if(planets++ != choice)
					continue;
				
				position = object.Position();
				planet = object.GetPlanet();
				radius = max(0, static_cast<int>(object.Radius()));
				break;
			}
		if(links)
			target = linkVector[Random::Int(links)];
	}
	else
	{
		radius = 1000;
		source = linkVector[choice];
	}
	
	vector<shared_ptr<Ship>> placed;
	for(const Ship *ship : variants[index].ships)
	{
		if(ship->CanBeCarried())
		{
			shared_ptr<Ship> fighter(new Ship(*ship));
			fighter->SetGovernment(government);
			fighter->SetName((fighterNames ? fighterNames : names)->Get());
			fighter->SetPersonality(personality);
			for(const shared_ptr<Ship> &parent : placed)
				if(parent->AddFighter(fighter))
					break;
			continue;
		}
		Angle angle = Angle::Random(360.);
		Point pos = position + angle.Unit() * (Random::Int(radius + 1));
		
		ships.push_front(shared_ptr<Ship>(new Ship(*ship)));
		ships.front()->SetSystem(source);
		ships.front()->SetPlanet(planet);
		if(source == &system)
			ships.front()->Place(pos, angle.Unit(), angle);
		else
		{
			Point offset = system.Position() - source->Position();
			angle = TO_DEG * atan2(offset.X(), -offset.Y());
			ships.front()->Place(pos, Point(), angle);
		}
		ships.front()->SetTargetSystem(target);
		ships.front()->SetGovernment(government);
		ships.front()->SetName(names->Get());
		ships.front()->SetPersonality(personality);
		
		if(!placed.empty())
			ships.front()->SetParent(placed.front());
		
		placed.push_back(ships.front());
		
		SetCargo(&*ships.front());
	}
}
Ejemplo n.º 11
0
// Move this ship. A ship may create effects as it moves, in particular if
// it is in the process of blowing up. If this returns false, the ship
// should be deleted.
bool Ship::Move(list<Effect> &effects)
{
	// Check if this ship has been in a different system from the player for so
	// long that it should be "forgotten." Also eliminate ships that have no
	// system set because they just entered a fighter bay.
	forget += !isInSystem;
	if((!isSpecial && forget >= 1000) || !currentSystem)
		return false;
	isInSystem = false;
	if(!fuel || !(attributes.Get("hyperdrive") || attributes.Get("jump drive")))
		hyperspaceSystem = nullptr;
	
	// Handle ionization effects.
	if(ionization)
	{
		ionization *= .99;
		
		const Effect *effect = GameData::Effects().Get("ion spark");
		double ion = ionization * .1;
		while(!forget)
		{
			ion -= Random::Real();
			if(ion <= 0.)
				break;
			
			Point point((Random::Real() - .5) * .5 * sprite.Width(),
				(Random::Real() - .5) * .5 * sprite.Height());
			if(sprite.GetMask(0).Contains(point, Angle()))
			{
				effects.push_back(*effect);
				effects.back().Place(angle.Rotate(point) + position, velocity, angle);
			}
		}
	}
	// Jettisoned cargo effects.
	static const int JETTISON_BOX = 5;
	if(jettisoned >= JETTISON_BOX)
	{
		jettisoned -= JETTISON_BOX;
		effects.push_back(*GameData::Effects().Get("box"));
		effects.back().Place(position, velocity, angle);
	}
	
	// When ships recharge, what actually happens is that they can exceed their
	// maximum capacity for the rest of the turn, but must be clamped to the
	// maximum here before they gain more. This is so that, for example, a ship
	// with no batteries but a good generator can still move.
	energy = min(energy, attributes.Get("energy capacity"));
	
	heat *= heatDissipation;
	if(heat > Mass() * 100.)
		isOverheated = true;
	else if(heat < Mass() * 90.)
		isOverheated = false;
	
	double maxShields = attributes.Get("shields");
	shields = min(shields, maxShields);
	double maxHull = attributes.Get("hull");
	hull = min(hull, maxHull);
	isDisabled = isOverheated || IsDisabled();
	
	// Update ship supply levels.
	if(!isDisabled)
	{
		// If you have a ramscoop, you recharge enough fuel to make one jump in
		// a little less than a minute - enough to be an inconvenience without
		// being totally aggravating.
		if(attributes.Get("ramscoop"))
			TransferFuel(-.03 * sqrt(attributes.Get("ramscoop")), nullptr);
		
		energy += attributes.Get("energy generation") - ionization;
		energy = max(0., energy);
		heat += attributes.Get("heat generation");
		heat -= attributes.Get("cooling");
		heat = max(0., heat);
		
		// Hull repair.
		double oldHull = hull;
		hull = min(hull + attributes.Get("hull repair rate"), maxHull);
		static const double HULL_EXCHANGE_RATE = 1.;
		energy -= HULL_EXCHANGE_RATE * (hull - oldHull);
		
		// Recharge shields, but only up to the max. If there is extra shield
		// energy, use it to recharge fighters and drones.
		shields += attributes.Get("shield generation");
		static const double SHIELD_EXCHANGE_RATE = 1.;
		energy -= SHIELD_EXCHANGE_RATE * attributes.Get("shield generation");
		double excessShields = max(0., shields - maxShields);
		shields -= excessShields;
		
		for(Bay &bay : fighterBays)
		{
			if(!bay.ship)
				continue;
			
			double myGen = bay.ship->Attributes().Get("shield generation");
			double myMax = bay.ship->Attributes().Get("shields");
			bay.ship->shields = min(myMax, bay.ship->shields + myGen);
			if(excessShields > 0. && bay.ship->shields < myMax)
			{
				double extra = min(myMax - bay.ship->shields, excessShields);
				bay.ship->shields += extra;
				excessShields -= extra;
			}
		}
		for(Bay &bay : droneBays)
		{
			if(!bay.ship)
				continue;
			
			double myGen = bay.ship->Attributes().Get("shield generation");
			double myMax = bay.ship->Attributes().Get("shields");
			bay.ship->shields = min(myMax, bay.ship->shields + myGen);
			if(excessShields > 0. && bay.ship->shields < myMax)
			{
				double extra = min(myMax - bay.ship->shields, excessShields);
				bay.ship->shields += extra;
				excessShields -= extra;
			}
		}
		// If you do not need the shield generation, apply the extra back to
		// your energy. On the other hand, if recharging shields drives your
		// energy negative, undo that part of the recharge.
		energy += SHIELD_EXCHANGE_RATE * excessShields;
		if(energy < 0.)
		{
			shields += energy / SHIELD_EXCHANGE_RATE;
			energy = 0.;
		}
	}
	
	
	if(IsDestroyed())
	{
		// Once we've created enough little explosions, die.
		if(explosionCount == explosionTotal || forget)
		{
			if(!forget)
			{
				const Effect *effect = GameData::Effects().Get("smoke");
				double scale = .015 * (sprite.Width() + sprite.Height()) + .5;
				double radius = .1 * (sprite.Width() + sprite.Height());
				int debrisCount = attributes.Get("mass") * .07;
				for(int i = 0; i < debrisCount; ++i)
				{
					effects.push_back(*effect);
					
					Angle angle = Angle::Random();
					Point effectVelocity = velocity + angle.Unit() * (scale * Random::Real());
					Point effectPosition = position + radius * angle.Unit();
					effects.back().Place(effectPosition, effectVelocity, angle);
				}
					
				for(unsigned i = 0; i < explosionTotal / 2; ++i)
					CreateExplosion(effects, true);
			}
			energy = 0.;
			heat = 0.;
			ionization = 0.;
			fuel = 0.;
			return false;
		}
		
		// If the ship is dead, it first creates explosions at an increasing
		// rate, then disappears in one big explosion.
		++explosionRate;
		if(Random::Int(1024) < explosionRate)
			CreateExplosion(effects);
	}
	else if(hyperspaceSystem || hyperspaceCount)
	{
		fuel -= (hyperspaceSystem != nullptr);
		
		// Enter hyperspace.
		int direction = (hyperspaceSystem != nullptr) - (hyperspaceSystem == nullptr);
		hyperspaceCount += direction;
		static const int HYPER_C = 100;
		static const double HYPER_A = 2.;
		static const double HYPER_D = 1000.;
		bool hasJumpDrive = (hyperspaceType == 200);
		
		// Create the particle effects for the jump drive. This may create 100
		// or more particles per ship per turn at the peak of the jump.
		if(hasJumpDrive && !forget)
		{
			int count = hyperspaceCount;
			count *= sprite.Width() * sprite.Height();
			count /= 160000;
			const Effect *effect = GameData::Effects().Get("jump drive");
			while(--count >= 0)
			{
				Point point((Random::Real() - .5) * .5 * sprite.Width(),
					(Random::Real() - .5) * .5 * sprite.Height());
				if(sprite.GetMask(0).Contains(point, Angle()))
				{
					effects.push_back(*effect);
					Point vel = velocity + 5. * Angle::Random(360.).Unit();
					effects.back().Place(angle.Rotate(point) + position, vel, angle);
				}
			}
		}
		
		if(hyperspaceCount == HYPER_C)
		{
			currentSystem = hyperspaceSystem;
			// If the jump fuel is higher than 100, expend extra fuel now.
			fuel -= hyperspaceType - HYPER_C;
			hyperspaceSystem = nullptr;
			SetTargetSystem(nullptr);
			SetTargetPlanet(nullptr);
			direction = -1;
			
			Point target;
			for(const StellarObject &object : currentSystem->Objects())
				if(object.GetPlanet() && object.GetPlanet()->HasSpaceport())
				{
					target = object.Position();
					break;
				}
			if(GetDestination())
				for(const StellarObject &object : currentSystem->Objects())
					if(object.GetPlanet() == GetDestination())
					{
						target = object.Position();
						break;
					}
			
			if(hasJumpDrive)
			{
				position = target + Angle::Random().Unit() * 300. * (Random::Real() + 1.);
				return true;
			}
			
			// Have all ships exit hyperspace at the same distance so that
			// your escorts always stay with you.
			double distance = (HYPER_C * HYPER_C) * .5 * HYPER_A + HYPER_D;
			position = (target - distance * angle.Unit());
			position += hyperspaceOffset;
			// Make sure your velocity is in exactly the direction you are
			// traveling in, so that when you decelerate there will not be a
			// sudden shift in direction at the end.
			velocity = velocity.Length() * angle.Unit();
		}
		if(!hasJumpDrive)
		{
			velocity += (HYPER_A * direction) * angle.Unit();
			if(!hyperspaceSystem)
			{
				// Exit hyperspace far enough from the planet to be able to land.
				// This does not take drag into account, so it is always an over-
				// estimate of how long it will take to stop.
				// We start decellerating after rotating about 150 degrees (that
				// is, about acos(.8) from the proper angle). So:
				// Stopping distance = .5*a*(v/a)^2 + (150/turn)*v.
				// Exit distance = HYPER_D + .25 * v^2 = stopping distance.
				double exitV = MaxVelocity();
				double a = (.5 / Acceleration() - .25);
				double b = 150. / TurnRate();
				double discriminant = b * b - 4. * a * -HYPER_D;
				if(discriminant > 0.)
				{
					double altV = (-b + sqrt(discriminant)) / (2. * a);
					if(altV > 0. && altV < exitV)
						exitV = altV;
				}
				if(velocity.Length() <= exitV)
				{
					velocity = angle.Unit() * exitV;
					hyperspaceCount = 0;
				}
			}
		}
		position += velocity;
		if(GetParent() && GetParent()->currentSystem == currentSystem)
		{
			hyperspaceOffset = position - GetParent()->position;
			double length = hyperspaceOffset.Length();
			if(length > 1000.)
				hyperspaceOffset *= 1000. / length;
		}
		
		return true;
	}
	else if(landingPlanet || zoom < 1.)
	{
		// Special ships do not disappear forever when they land; they
		// just slowly refuel.
		if(landingPlanet && zoom)
		{
			// Move the ship toward the center of the planet while landing.
			if(GetTargetPlanet())
				position = .97 * position + .03 * GetTargetPlanet()->Position();
			zoom -= .02;
			if(zoom < 0.)
			{
				// If this is not a special ship, it ceases to exist when it
				// lands on a true planet. If this is a wormhole, the ship is
				// instantly transported.
				if(landingPlanet->IsWormhole())
				{
					currentSystem = landingPlanet->WormholeDestination(currentSystem);
					for(const StellarObject &object : currentSystem->Objects())
						if(object.GetPlanet() == landingPlanet)
							position = object.Position();
					SetTargetPlanet(nullptr);
					landingPlanet = nullptr;
				}
				else if(!isSpecial || personality.IsFleeing())
					return false;
				
				zoom = 0.;
			}
		}
		// Only refuel if this planet has a spaceport.
		else if(fuel == attributes.Get("fuel capacity")
				|| !landingPlanet || !landingPlanet->HasSpaceport())
		{
			zoom = min(1., zoom + .02);
			landingPlanet = nullptr;
		}
		else
			fuel = min(fuel + 1., attributes.Get("fuel capacity"));
		
		// Move the ship at the velocity it had when it began landing, but
		// scaled based on how small it is now.
		position += velocity * zoom;
		
		return true;
	}
	if(commands.Has(Command::LAND) && CanLand())
		landingPlanet = GetTargetPlanet()->GetPlanet();
	else if(commands.Has(Command::JUMP))
	{
		hyperspaceType = CheckHyperspace();
		if(hyperspaceType)
			hyperspaceSystem = GetTargetSystem();
	}
	
	double cloakingSpeed = attributes.Get("cloak");
	bool canCloak = (zoom == 1. && !isDisabled && !hyperspaceCount && cloakingSpeed
		&& fuel >= attributes.Get("cloaking fuel")
		&& energy >= attributes.Get("cloaking energy"));
	if(commands.Has(Command::CLOAK) && canCloak)
	{
		cloak = min(1., cloak + cloakingSpeed);
		fuel -= attributes.Get("cloaking fuel");
		energy -= attributes.Get("cloaking energy");
	}
	else if(cloakingSpeed)
		cloak = max(0., cloak - cloakingSpeed);
	else
		cloak = 0.;
	
	int requiredCrew = RequiredCrew();
	if(pilotError)
		--pilotError;
	else if(pilotOkay)
		--pilotOkay;
	else if(requiredCrew && static_cast<int>(Random::Int(requiredCrew)) >= Crew())
	{
		pilotError = 30;
		Messages::Add("Your ship is moving erratically because you do not have enough crew to pilot it.");
	}
	else
		pilotOkay = 30;
	
	// This ship is not landing or entering hyperspace. So, move it. If it is
	// disabled, all it can do is slow down to a stop.
	double mass = Mass();
	if(isDisabled)
		velocity *= 1. - attributes.Get("drag") / mass;
	else if(!pilotError)
	{
		double thrustCommand = commands.Has(Command::FORWARD) - commands.Has(Command::BACK);
		Point acceleration;
		if(thrustCommand)
		{
			// Check if we are able to apply this thrust.
			double cost = attributes.Get((thrustCommand > 0.) ?
				"thrusting energy" : "reverse thrusting energy");
			if(energy < cost)
				thrustCommand = 0.;
			else
			{
				// If a reverse thrust is commanded and the capability does not
				// exist, ignore it (do not even slow under drag).
				double thrust = attributes.Get((thrustCommand > 0.) ?
					"thrust" : "reverse thrust");
				if(!thrust)
					thrustCommand = 0.;
				else
				{
					energy -= cost;
					heat += attributes.Get((thrustCommand > 0.) ?
						"thrusting heat" : "reverse thrusting heat");
					acceleration += angle.Unit() * (thrustCommand * thrust / mass);
				}
			}
		}
		bool applyAfterburner = commands.Has(Command::AFTERBURNER) && !CannotAct();
		if(applyAfterburner)
		{
			double thrust = attributes.Get("afterburner thrust");
			double cost = attributes.Get("afterburner fuel");
			double energyCost = attributes.Get("afterburner energy");
			if(!thrust || fuel < cost || energy < energyCost)
				applyAfterburner = false;
			else
			{
				heat += attributes.Get("afterburner heat");
				fuel -= cost;
				energy -= energyCost;
				acceleration += angle.Unit() * thrust / mass;
				
				if(!forget)
					for(const Point &point : enginePoints)
					{
						Point pos = angle.Rotate(point) * .5 * Zoom() + position;
						for(const auto &it : attributes.AfterburnerEffects())
							for(int i = 0; i < it.second; ++i)
							{
								effects.push_back(*it.first);
								effects.back().Place(pos + velocity, velocity - 6. * angle.Unit(), angle);
							}
					}
			}
		}
		if(acceleration)
		{
			Point dragAcceleration = acceleration - velocity * (attributes.Get("drag") / mass);
			// Make sure dragAcceleration has nonzero length, to avoid divide by zero.
			if(dragAcceleration)
			{
				// What direction will the net acceleration be if this drag is applied?
				// If the net acceleration will be opposite the thrust, do not apply drag.
				dragAcceleration *= .5 * (acceleration.Unit().Dot(dragAcceleration.Unit()) + 1.);
				velocity += dragAcceleration;
			}
		}
		if(commands.Turn())
		{
			// Check if we are able to turn.
			double cost = attributes.Get("turning energy");
			if(energy < cost)
				commands.SetTurn(0.);
			else
			{
				energy -= cost;
				heat += attributes.Get("turning heat");
				angle += commands.Turn() * TurnRate();
			}
		}
	}
	
	// Boarding:
	if(isBoarding && (commands.Has(Command::FORWARD | Command::BACK) || commands.Turn()))
		isBoarding = false;
	shared_ptr<const Ship> target = (CanBeCarried() ? GetParent() : GetTargetShip());
	if(target && !isDisabled)
	{
		Point dp = (target->position - position);
		double distance = dp.Length();
		Point dv = (target->velocity - velocity);
		double speed = dv.Length();
		isBoarding |= (distance < 50. && speed < 1. && commands.Has(Command::BOARD));
		if(isBoarding && !CanBeCarried())
		{
			if(!target->IsDisabled() && government->IsEnemy(target->government))
				isBoarding = false;
			else if(target->IsDestroyed() || target->IsLanding() || target->IsHyperspacing()
					|| target->GetSystem() != GetSystem())
				isBoarding = false;
		}
		if(isBoarding && !pilotError)
		{
			Angle facing = angle;
			bool left = target->Unit().Cross(facing.Unit()) < 0.;
			double turn = left - !left;
			
			// Check if the ship will still be pointing to the same side of the target
			// angle if it turns by this amount.
			facing += TurnRate() * turn;
			bool stillLeft = target->Unit().Cross(facing.Unit()) < 0.;
			if(left != stillLeft)
				turn = 0.;
			angle += TurnRate() * turn;
			
			velocity += dv.Unit() * .1;
			position += dp.Unit() * .5;
			
			if(distance < 10. && speed < 1. && (CanBeCarried() || !turn))
			{
				isBoarding = false;
				if(government->IsEnemy(target->government) && target->Attributes().Get("self destruct"))
				{
					Messages::Add("The " + target->ModelName() + " \"" + target->Name()
						+ "\" has activated its self-destruct mechanism.");
					shared_ptr<Ship> victim = targetShip.lock();
					victim->hull = -1.;
					victim->explosionRate = 1024;
				}
				else
					hasBoarded = true;
			}
		}
	}
	
	// And finally: move the ship!
	position += velocity;
	
	return true;
}
Ejemplo n.º 12
0
void Fleet::Enter(const System &system, list<shared_ptr<Ship>> &ships, const Planet *planet) const
{
	if(!total || !government || variants.empty())
		return;
	
	// Pick a fleet variant to instantiate.
	const Variant &variant = ChooseVariant();
	if(variant.ships.empty())
		return;
	
	// Where this fleet can come from depends on whether it is friendly to any
	// planets in this system and whether it has jump drives.
	vector<const System *> linkVector;
	// Find out what the "best" jump method the fleet has is. Assume that if the
	// others don't have that jump method, they are being carried as fighters.
	// That is, content creators should avoid creating fleets with a mix of jump
	// drives and hyperdrives.
	int jumpType = 0;
	for(const Ship *ship : variant.ships)
		jumpType = max(jumpType,
			 ship->Attributes().Get("jump drive") ? 200 :
			 ship->Attributes().Get("hyperdrive") ? 100 : 0);
	if(jumpType)
	{
		// Don't try to make a fleet "enter" from another system if none of the
		// ships have jump drives.
		bool isWelcomeHere = !system.GetGovernment()->IsEnemy(government);
		for(const System *neighbor : (jumpType == 200 ? system.Neighbors() : system.Links()))
		{
			// If this ship is not "welcome" in the current system, prefer to have
			// it enter from a system that is friendly to it. (This is for realism,
			// so attack fleets don't come from what ought to be a safe direction.)
			if(isWelcomeHere || neighbor->GetGovernment()->IsEnemy(government))
				linkVector.push_back(neighbor);
			else
				linkVector.insert(linkVector.end(), 4, neighbor);
		}
	}
	
	// Find all the inhabited planets this fleet could take off from.
	vector<const Planet *> planetVector;
	if(!personality.IsSurveillance())
		for(const StellarObject &object : system.Objects())
			if(object.GetPlanet() && object.GetPlanet()->HasSpaceport()
					&& !object.GetPlanet()->GetGovernment()->IsEnemy(government))
				planetVector.push_back(object.GetPlanet());
	
	// If there is nowhere for this fleet to come from, don't create it.
	size_t options = linkVector.size() + planetVector.size();
	if(!options)
		return;
	
	// Choose a random planet or star system to come from.
	size_t choice = Random::Int(options);
	
	// Figure out what system the ship is starting in, where it is going, and
	// what position it should start from in the system.
	const System *source = &system;
	const System *target = &system;
	Point position;
	double radius = 0.;
	
	// If a planet is chosen, also pick a system to travel to after taking off.
	if(choice >= linkVector.size())
	{
		planet = planetVector[choice - linkVector.size()];
		if(!linkVector.empty())
			target = linkVector[Random::Int(linkVector.size())];
	}
	
	// Find the stellar object for the given planet, and place the ships there.
	if(planet)
	{
		for(const StellarObject &object : system.Objects())
			if(object.GetPlanet() == planet)
			{
				position = object.Position();
				radius = object.Radius();
				break;
			}
	}
	else if(choice < linkVector.size())
	{
		// We are entering this system via hyperspace, not taking off from a planet.
		radius = 1000.;
		source = linkVector[choice];
	}
	
	// Place all the ships in the chosen fleet variant.
	shared_ptr<Ship> flagship;
	vector<shared_ptr<Ship>> placed = Instantiate(variant);
	for(shared_ptr<Ship> &ship : placed)
	{
		// If this is a fighter and someone can carry it, no need to position it.
		if(PlaceFighter(ship, placed))
			continue;
		
		Angle angle = Angle::Random(360.);
		Point pos = position + angle.Unit() * (Random::Real() * radius);
		
		ships.push_front(ship);
		ship->SetSystem(source);
		ship->SetPlanet(planet);
		if(source == &system)
			ship->Place(pos, angle.Unit(), angle);
		else
		{
			// Place the ship stationary and pointed in the right direction.
			angle = Angle(system.Position() - source->Position());
			ship->Place(pos, Point(), angle);
		}
		if(target != source)
			ship->SetTargetSystem(target);
		
		if(flagship)
			ship->SetParent(flagship);
		else
			flagship = ship;
		
		SetCargo(&*ship);
	}
}
Ejemplo n.º 13
0
// Place a fleet in the given system, already "in action."
void Fleet::Place(const System &system, list<shared_ptr<Ship>> &ships, bool carried) const
{
	if(!total || !government)
		return;
	
	// Pick a random variant based on the weights.
	unsigned index = 0;
	for(int choice = Random::Int(total); choice >= variants[index].weight; ++index)
		choice -= variants[index].weight;
	
	// Count the inhabited planets in this system.
	int planets = 0;
	for(const StellarObject &object : system.Objects())
		if(object.GetPlanet() && object.GetPlanet()->HasSpaceport())
			++planets;
	
	// Determine where the fleet is going to or coming from.
	Point center;
	if(planets)
	{
		int index = Random::Int(planets);
		for(const StellarObject &object : system.Objects())
			if(object.GetPlanet() && object.GetPlanet()->HasSpaceport())
				if(!index--)
					center = object.Position();
	}
	
	// Move out a random distance from that object, facing toward it or away.
	Angle angle = Angle::Random();
	center += angle.Unit() * (Random::Real() * 2. - 1.);
	
	vector<shared_ptr<Ship>> placed;
	for(const Ship *ship : variants[index].ships)
	{
		if(carried && ship->CanBeCarried())
		{
			shared_ptr<Ship> fighter(new Ship(*ship));
			fighter->SetGovernment(government);
			fighter->SetName((fighterNames ? fighterNames : names)->Get());
			fighter->SetPersonality(personality);
			for(const shared_ptr<Ship> &parent : placed)
				if(parent->AddFighter(fighter))
					break;
			continue;
		}
		Angle angle = Angle::Random();
		Point pos = center + Angle::Random().Unit() * Random::Real() * 400.;
		
		double velocity = Random::Real() * ship->MaxVelocity();
		
		ships.push_front(shared_ptr<Ship>(new Ship(*ship)));
		ships.front()->SetSystem(&system);
		ships.front()->Place(pos, velocity * angle.Unit(), angle);
		ships.front()->SetGovernment(government);
		ships.front()->SetName(names->Get());
		ships.front()->SetPersonality(personality);
		
		if(!placed.empty())
			ships.front()->SetParent(placed.front());
		
		placed.push_back(ships.front());
		
		SetCargo(&*ships.front());
	}
}
void Engine::Place()
{
	ships.clear();
	
	EnterSystem();
	auto it = ships.end();
	
	// Add the player's flagship and escorts to the list of ships. The TakeOff()
	// code already took care of loading up fighters and assigning parents.
	for(const shared_ptr<Ship> &ship : player.Ships())
		if(!ship->IsParked() && ship->GetSystem())
		{
			ships.push_back(ship);
			if(it == ships.end())
				--it;
		}
	
	// Add NPCs to the list of ships. Fighters have to be assigned to carriers,
	// and all but "uninterested" ships should follow the player.
	shared_ptr<Ship> flagship = player.FlagshipPtr();
	for(const Mission &mission : player.Missions())
		for(const NPC &npc : mission.NPCs())
		{
			map<Ship *, int> droneCarriers;
			map<Ship *, int> fighterCarriers;
			for(const shared_ptr<Ship> &ship : npc.Ships())
			{
				// Skip ships that have been destroyed.
				if(ship->IsDestroyed() || ship->IsDisabled())
					continue;
				
				if(ship->BaysFree(false))
					droneCarriers[&*ship] = ship->BaysFree(false);
				if(ship->BaysFree(true))
					fighterCarriers[&*ship] = ship->BaysFree(true);
				// Redo the loading up of fighters.
				ship->UnloadBays();
			}
			
			for(const shared_ptr<Ship> &ship : npc.Ships())
			{
				// Skip ships that have been destroyed.
				if(ship->IsDestroyed())
					continue;
				
				if(!ship->IsDisabled())
					ship->Recharge();
				
				if(ship->CanBeCarried())
				{
					bool docked = false;
					map<Ship *, int> &carriers = (ship->Attributes().Category() == "Drone") ?
						droneCarriers : fighterCarriers;
					for(auto &it : carriers)
						if(it.second && it.first->Carry(ship))
						{
							--it.second;
							docked = true;
							break;
						}
					if(docked)
						continue;
				}
				
				ships.push_back(ship);
				if(!ship->GetPersonality().IsUninterested())
					ship->SetParent(flagship);
			}
		}
	
	// Get the coordinates of the planet the player is leaving.
	Point planetPos;
	double planetRadius = 0.;
	const StellarObject *object = player.GetStellarObject();
	if(object)
	{
		planetPos = object->Position();
		planetRadius = object->Radius();
	}
	
	// Give each ship a random heading and position. The iterator points to the
	// first ship that was an escort or NPC (i.e. the first ship after any
	// fleets that were placed starting out in this system).
	while(it != ships.end())
	{
		const shared_ptr<Ship> &ship = *it++;
		
		Point pos;
		Angle angle = Angle::Random(360.);
		Point velocity = angle.Unit();
		// Any ships in the same system as the player should be either
		// taking off from the player's planet or nearby.
		bool isHere = (ship->GetSystem() == player.GetSystem());
		if(isHere)
			pos = planetPos;
		// Check whether this ship should take off with you.
		if(isHere && !ship->IsDisabled()
				&& (player.GetPlanet()->CanLand(*ship) || ship->GetGovernment()->IsPlayer())
				&& !(ship->GetPersonality().IsStaying() || ship->GetPersonality().IsWaiting()))
		{
			if(player.GetPlanet())
				ship->SetPlanet(player.GetPlanet());
			pos += angle.Unit() * Random::Real() * planetRadius;
		}
		else
		{
			ship->SetPlanet(nullptr);
			pos = planetPos + Angle::Random().Unit() * ((Random::Real() + 1.) * 400. + 2. * planetRadius);
			velocity *= Random::Real() * ship->MaxVelocity();
		}
		
		ship->Place(pos, ship->IsDisabled() ? Point() : velocity, angle);
	}
	
	player.SetPlanet(nullptr);
}
Ejemplo n.º 15
0
// Draw a frame.
void Engine::Draw() const
{
	GameData::Background().Draw(position, velocity);
	draw[drawTickTock].Draw();
	
	for(const auto &it : statuses)
	{
		if(it.hull <= 0.)
			continue;
		
		static const Color color[4] = {
			Color(0., .5, 0., .25),
			Color(.5, .15, 0., .25),
			Color(.45, .5, 0., .25),
			Color(.5, .3, 0., .25)
		};
		RingShader::Draw(it.position, it.radius + 3., 1.5, it.shields, color[it.isEnemy]);
		RingShader::Draw(it.position, it.radius, 1.5, it.hull, color[2 + it.isEnemy], 20.);
	}
	
	if(flash)
		FillShader::Fill(Point(), Point(Screen::Width(), Screen::Height()), Color(flash, flash));
	
	// Draw messages.
	const Font &font = FontSet::Get(14);
	const vector<Messages::Entry> &messages = Messages::Get(step);
	Point messagePoint(
		Screen::Left() + 120.,
		Screen::Bottom() - 20. * messages.size());
	for(const auto &it : messages)
	{
		float alpha = (it.step + 1000 - step) * .001f;
		Color color(alpha, 0.);
		font.Draw(it.message, messagePoint, color);
		messagePoint.Y() += 20.;
	}
	
	// Draw crosshairs around anything that is targeted.
	for(const Target &target : targets)
	{
		Angle a = target.angle;
		Angle da(90.);
		
		for(int i = 0; i < 4; ++i)
		{
			PointerShader::Draw(target.center, a.Unit(), 10., 10., -target.radius,
				Radar::GetColor(target.type));
			a += da;
		}
	}
	
	GameData::Interfaces().Get("status")->Draw(info);
	GameData::Interfaces().Get("targets")->Draw(info);
	
	// Draw ammo status.
	Point pos(Screen::Right() - 80, Screen::Bottom());
	const Sprite *selectedSprite = SpriteSet::Get("ui/ammo selected");
	const Sprite *unselectedSprite = SpriteSet::Get("ui/ammo unselected");
	Color selectedColor = *GameData::Colors().Get("bright");
	Color unselectedColor = *GameData::Colors().Get("dim");
	for(const pair<const Outfit *, int> &it : ammo)
	{
		pos.Y() -= 30.;
		
		bool isSelected = it.first == player.SelectedWeapon();
		
		SpriteShader::Draw(it.first->Icon(), pos);
		SpriteShader::Draw(
			isSelected ? selectedSprite : unselectedSprite, pos + Point(35., 0.));
		
		string amount = to_string(it.second);
		Point textPos = pos + Point(55 - font.Width(amount), -(30 - font.Height()) / 2);
		font.Draw(amount, textPos, isSelected ? selectedColor : unselectedColor);
	}
	
	// Draw escort status.
	escorts.Draw();
	
	if(Preferences::Has("Show CPU / GPU load"))
	{
		string loadString = to_string(static_cast<int>(load * 100. + .5)) + "% CPU";
		Color color = *GameData::Colors().Get("medium");
		FontSet::Get(14).Draw(loadString,
			Point(-10 - font.Width(loadString), Screen::Height() * -.5 + 5.), color);
	}
}
Ejemplo n.º 16
0
void Engine::Place()
{
	ships.clear();
	
	EnterSystem();
	for(const shared_ptr<Ship> &ship : player.Ships())
	{
		if(ship->IsParked())
			continue;
		
		ships.push_back(ship);
		Point pos;
		Angle angle = Angle::Random(360.);
		// All your ships that are in system with the player act as if they are
		// leaving the planet along with you.
		if(player.GetPlanet() && ship->GetSystem() == player.GetSystem() && !ship->IsDisabled())
		{
			ship->SetPlanet(player.GetPlanet());
			for(const StellarObject &object : ship->GetSystem()->Objects())
				if(object.GetPlanet() == player.GetPlanet())
				{
					pos = object.Position() + angle.Unit() * Random::Real() * object.Radius();
				}
		}
		else if(ship->GetSystem())
		{
			pos = Angle::Random().Unit() * ((Random::Real() + 1.) * 600.);
			for(const StellarObject &object : ship->GetSystem()->Objects())
				if(object.GetPlanet() == player.GetPlanet())
					pos += object.Position();
		}
		ship->Place(pos, angle.Unit(), angle);
	}
	shared_ptr<Ship> flagship;
	if(!player.Ships().empty())
		flagship = player.Ships().front();
	for(const Mission &mission : player.Missions())
		for(const NPC &npc : mission.NPCs())
		{
			map<Ship *, int> droneCarriers;
			map<Ship *, int> fighterCarriers;
			for(const shared_ptr<Ship> &ship : npc.Ships())
			{
				// Skip ships that have been destroyed.
				if(ship->IsDestroyed() || ship->IsDisabled())
					continue;
				
				if(ship->DroneBaysFree())
					droneCarriers[&*ship] = ship->DroneBaysFree();
				if(ship->FighterBaysFree())
					fighterCarriers[&*ship] = ship->FighterBaysFree();
				// Redo the loading up of fighters.
				ship->UnloadFighters();
			}
			
			for(const shared_ptr<Ship> &ship : npc.Ships())
			{
				// Skip ships that have been destroyed.
				if(ship->IsDestroyed())
					continue;
				
				if(!ship->IsDisabled())
					ship->Recharge();
				
				if(ship->CanBeCarried())
				{
					bool docked = false;
					if(ship->Attributes().Category() == "Drone")
					{
						for(auto &it : droneCarriers)
							if(it.second)
							{
								it.first->AddFighter(ship);
								--it.second;
								docked = true;
								break;
							}
					}
					else if(ship->Attributes().Category() == "Fighter")
					{
						for(auto &it : fighterCarriers)
							if(it.second)
							{
								it.first->AddFighter(ship);
								--it.second;
								docked = true;
								break;
							}
					}
					if(docked)
						continue;
				}
				
				ships.push_back(ship);
				if(!ship->GetPersonality().IsUninterested())
					ship->SetParent(flagship);
				
				Point pos;
				Angle angle = Angle::Random(360.);
				// All your ships that are in system with the player act as if they are
				// leaving the planet along with you.
				if(player.GetPlanet() && ship->GetSystem() == player.GetSystem() && !ship->IsDisabled()
						&& (player.GetPlanet()->CanLand(*ship) || ship->GetGovernment()->IsPlayer())
						&& !(ship->GetPersonality().IsStaying() || ship->GetPersonality().IsWaiting()))
				{
					ship->SetPlanet(player.GetPlanet());
					for(const StellarObject &object : ship->GetSystem()->Objects())
						if(object.GetPlanet() == player.GetPlanet())
							pos = object.Position() + angle.Unit() * Random::Real() * object.Radius();
				}
				else if(ship->GetSystem())
				{
					pos = Angle::Random().Unit() * ((Random::Real() + 1.) * 600.);
					for(const StellarObject &object : ship->GetSystem()->Objects())
						if(object.GetPlanet() == player.GetPlanet())
							pos += object.Position();
				}
				ship->Place(pos, angle.Unit(), angle);
			}
		}
	
	player.SetPlanet(nullptr);
}
// Draw a frame.
void Engine::Draw() const
{
	GameData::Background().Draw(center, centerVelocity);
	
	// Draw any active planet labels.
	for(const PlanetLabel &label : labels)
		label.Draw();
	
	draw[drawTickTock].Draw();
	
	for(const auto &it : statuses)
	{
		if(it.hull <= 0.)
			continue;
		
		static const Color color[4] = {
			Color(0., .5, 0., .25),
			Color(.5, .15, 0., .25),
			Color(.45, .5, 0., .25),
			Color(.5, .3, 0., .25)
		};
		RingShader::Draw(it.position, it.radius + 3., 1.5, it.shields, color[it.isEnemy]);
		RingShader::Draw(it.position, it.radius, 1.5, it.hull, color[2 + it.isEnemy], 20.);
	}
	
	if(flash)
		FillShader::Fill(Point(), Point(Screen::Width(), Screen::Height()), Color(flash, flash));
	
	// Draw messages.
	const Font &font = FontSet::Get(14);
	const vector<Messages::Entry> &messages = Messages::Get(step);
	Point messagePoint(
		Screen::Left() + 120.,
		Screen::Bottom() - 20. * messages.size());
	auto it = messages.begin();
	double firstY = Screen::Top() - font.Height();
	if(messagePoint.Y() < firstY)
	{
		int skip = (firstY - messagePoint.Y()) / 20.;
		it += skip;
		messagePoint.Y() += 20. * skip;
	}
	for( ; it != messages.end(); ++it)
	{
		float alpha = (it->step + 1000 - step) * .001f;
		Color color(alpha, 0.);
		font.Draw(it->message, messagePoint, color);
		messagePoint.Y() += 20.;
	}
	
	// Draw crosshairs around anything that is targeted.
	for(const Target &target : targets)
	{
		Angle a = target.angle;
		Angle da(90.);
		
		for(int i = 0; i < 4; ++i)
		{
			PointerShader::Draw(target.center, a.Unit(), 12., 14., -target.radius,
				Radar::GetColor(target.type));
			a += da;
		}
	}
	
	const Interface *interfaces[2] = {
		GameData::Interfaces().Get("status"),
		GameData::Interfaces().Get("targets")
	};
	for(const Interface *interface : interfaces)
	{
		interface->Draw(info);
		if(interface->HasPoint("radar"))
		{
			radar[drawTickTock].Draw(
				interface->GetPoint("radar"),
				.025,
				interface->GetSize("radar").X(),
				interface->GetSize("radar").Y());
		}
		if(interface->HasPoint("target") && targetAngle)
		{
			Point center = interface->GetPoint("target");
			double radius = interface->GetSize("target").X();
			PointerShader::Draw(center, targetAngle, 10., 10., radius, Color(1.));
		}
	}
	if(jumpCount && Preferences::Has("Show mini-map"))
		MapPanel::DrawMiniMap(player, .5 * min(1., jumpCount / 30.), jumpInProgress, step);
	
	// Draw ammo status.
	Point pos(Screen::Right() - 80, Screen::Bottom());
	const Sprite *selectedSprite = SpriteSet::Get("ui/ammo selected");
	const Sprite *unselectedSprite = SpriteSet::Get("ui/ammo unselected");
	Color selectedColor = *GameData::Colors().Get("bright");
	Color unselectedColor = *GameData::Colors().Get("dim");
	for(const pair<const Outfit *, int> &it : ammo)
	{
		pos.Y() -= 30.;
		
		bool isSelected = it.first == player.SelectedWeapon();
		
		SpriteShader::Draw(it.first->Icon(), pos);
		SpriteShader::Draw(
			isSelected ? selectedSprite : unselectedSprite, pos + Point(35., 0.));
		
		// Some secondary weapons may not have limited ammo. In that case, just
		// show the icon without a number.
		if(it.second < 0)
			continue;
		
		string amount = to_string(it.second);
		Point textPos = pos + Point(55 - font.Width(amount), -(30 - font.Height()) / 2);
		font.Draw(amount, textPos, isSelected ? selectedColor : unselectedColor);
	}
	
	// Draw escort status.
	escorts.Draw();
	
	if(Preferences::Has("Show CPU / GPU load"))
	{
		string loadString = to_string(static_cast<int>(load * 100. + .5)) + "% CPU";
		Color color = *GameData::Colors().Get("medium");
		font.Draw(loadString,
			Point(-10 - font.Width(loadString), Screen::Height() * -.5 + 5.), color);
	}
}