// Fire an anti-missile. Returns true if the missile should be killed. bool Armament::Weapon::FireAntiMissile(Ship &ship, const Projectile &projectile, list<Effect> &effects) { int strength = outfit->AntiMissile(); if(!strength) return false; double range = outfit->Velocity(); // Check if the missile is in range. Point start = ship.Position() + ship.Facing().Rotate(point); Point offset = projectile.Position() - start; if(offset.Length() > range) return false; // Figure out where the effect should be placed. Anti-missiles do not create // projectiles; they just create a blast animation. start += (.5 * range) * offset.Unit(); Angle aim = TO_DEG * atan2(offset.X(), -offset.Y()); for(const auto &eit : outfit->HitEffects()) for(int i = 0; i < eit.second; ++i) { effects.push_back(*eit.first); effects.back().Place(start, ship.Velocity(), aim); } Fire(ship); return (Random::Int(strength) > Random::Int(projectile.MissileStrength())); }
void AI::CircleAround(Ship &ship, Command &command, const Ship &target) { // This is not the behavior I want, but it's reasonable. Point direction = target.Position() - ship.Position(); command.SetTurn(TurnToward(ship, direction)); if(ship.Facing().Unit().Dot(direction) >= 0. && direction.Length() > 200.) command |= Command::FORWARD; }
// Check whether the mask contains the given point. bool Mask::Contains(Point point, Angle facing) const { if(outline.empty() || point.Length() > radius) return false; // Rotate into the mask's frame of reference. return Contains((-facing).Rotate(point)); }
void Invert::Transform (real &w, Point& p) { const Point diff = p-C_ptr->Center(); real r=diff.Length(); real ratio=RadiusSq/(r*r); p = C_ptr->Center()+diff*ratio; w *= ratio*ratio; }
void E2toIS::Transform(real& w, Point& p) { InfiniteStrip& s = *IS_ptr; Point D = s.B()- s.A(); Point C(-D.Y(),D.X()); C = C/C.Length(); w *= D.Length()*.5*(1-tanh(p.X())*tanh(p.X())); p = s.A() + p.Y()*C + (.5*(1+tanh(p.X())))*D; }
// Check if the given ship is within this projectile's blast radius. (The // projectile will not explode unless it is also within the trigger radius.) bool Projectile::InBlastRadius(const Ship &ship, int step, double closestHit) const { // "Invisible" ships can be killed by weapons with blast radii. Point offset = position + closestHit * velocity - ship.Position(); if(offset.Length() <= weapon->BlastRadius()) return true; const Mask &mask = ship.GetMask(step); return mask.WithinRange(offset, ship.Facing(), weapon->BlastRadius()); }
void AI::Attack(Ship &ship, Command &command, const Ship &target) { Point d = target.Position() - ship.Position(); // First, figure out what your shortest-range weapon is. double shortestRange = 4000.; for(const Armament::Weapon &weapon : ship.Weapons()) { const Outfit *outfit = weapon.GetOutfit(); if(outfit && !weapon.IsAntiMissile()) shortestRange = min(outfit->Range(), shortestRange); } // Deploy any fighters you are carrying. if(!ship.IsYours()) command |= Command::DEPLOY; // If this ship only has long-range weapons, it should keep its distance // instead of trying to close with the target ship. if(shortestRange > 1000. && d.Length() < .5 * shortestRange) { command.SetTurn(TurnToward(ship, -d)); if(ship.Facing().Unit().Dot(d) <= 0.) command |= Command::FORWARD; return; } // First of all, aim in the direction that will hit this target. command.SetTurn(TurnToward(ship, TargetAim(ship))); // Calculate this ship's "turning radius; that is, the smallest circle it // can make while at full speed. double stepsInFullTurn = 360. / ship.TurnRate(); double circumference = stepsInFullTurn * ship.Velocity().Length(); double diameter = max(200., circumference / PI); // This isn't perfect, but it works well enough. if((ship.Facing().Unit().Dot(d) >= 0. && d.Length() > diameter) || (ship.Velocity().Dot(d) < 0. && ship.Facing().Unit().Dot(d.Unit()) >= .9)) command |= Command::FORWARD; }
// This ship just got hit by the given projectile. Take damage according to // what sort of weapon the projectile it. int Ship::TakeDamage(const Projectile &projectile, bool isBlast) { int type = 0; const Outfit &weapon = projectile.GetWeapon(); double shieldDamage = weapon.ShieldDamage(); double hullDamage = weapon.HullDamage(); double hitForce = weapon.HitForce(); double heatDamage = weapon.HeatDamage(); double ionDamage = weapon.IonDamage(); bool wasDisabled = IsDisabled(); bool wasDestroyed = IsDestroyed(); if(shields > shieldDamage) { shields -= shieldDamage; heat += .5 * heatDamage; ionization += .5 * ionDamage; } else if(!shields || shieldDamage) { if(shieldDamage) { hullDamage *= (1. - (shields / shieldDamage)); shields = 0.; } hull -= hullDamage; heat += heatDamage; ionization += ionDamage; } if(hitForce && !IsHyperspacing()) { Point d = position - projectile.Position(); double distance = d.Length(); if(distance) ApplyForce((hitForce / distance) * d); } if(!wasDisabled && IsDisabled()) type |= ShipEvent::DISABLE; if(!wasDestroyed && IsDestroyed()) type |= ShipEvent::DESTROY; // If this ship was hit directly and did not consider itself an enemy of the // ship that hit it, it is now "provoked" against that government. if(!isBlast && projectile.GetGovernment() && !projectile.GetGovernment()->IsEnemy(government) && (Shields() < .9 || Hull() < .9 || !personality.IsForbearing()) && !personality.IsPacifist()) type |= ShipEvent::PROVOKE; return type; }
// Check if this ship is currently able to begin landing on its target. bool Ship::CanLand() const { if(!GetTargetPlanet() || !GetTargetPlanet()->GetPlanet() || isDisabled || IsDestroyed()) return false; if(!GetTargetPlanet()->GetPlanet()->CanLand(*this)) return false; Point distance = GetTargetPlanet()->Position() - position; double speed = velocity.Length(); return (speed < 1. && distance.Length() < GetTargetPlanet()->Radius()); }
// Check if this mask intersects the given line segment (from sA to vA). If // it does, return the fraction of the way along the segment where the // intersection occurs. The sA should be relative to this object's center. // If this object contains the given point, the return value is 0. If there // is no collision, the return value is 1. double Mask::Collide(Point sA, Point vA, Angle facing) const { // Bail out if we're too far away to possibly be touching. double distance = sA.Length(); if(outline.empty() || distance > radius + vA.Length()) return 1.; // Rotate into the mask's frame of reference. sA = (-facing).Rotate(sA); vA = (-facing).Rotate(vA); // If this point is contained within the mask, a ray drawn out from it will // intersect the mask an even number of times. If that ray coincides with an // edge, ignore that edge, and count all segments as closed at the start and // open at the end to avoid double-counting. // For simplicity, use a ray pointing straight downwards. A segment then // intersects only if its x coordinates span the point's coordinates. if(distance <= radius && Contains(sA)) return 0.; return Intersection(sA, vA); }
double AI::TurnToward(const Ship &ship, const Point &vector) { static const double RAD_TO_DEG = 180. / 3.14159265358979; Point facing = ship.Facing().Unit(); double cross = vector.Cross(facing); if(vector.Dot(facing) > 0.) { double angle = asin(cross / vector.Length()) * RAD_TO_DEG; if(fabs(angle) <= ship.TurnRate()) return -angle / ship.TurnRate(); } bool left = cross < 0.; return left - !left; }
// Find out how close this mask is to the given point. Again, the mask is // assumed to be rotated and scaled according to the given unit vector. bool Mask::WithinRange(Point point, Angle facing, double range) const { // Bail out if the object is too far away to possible be touched. if(outline.empty() || range < point.Length() - radius) return false; // Rotate into the mask's frame of reference. point = (-facing).Rotate(point); // For efficiency, compare to range^2 instead of range. range *= range; for(const Point &p : outline) if(p.DistanceSquared(point) < range) return true; return false; }
// Draw the radar display at the given coordinates. void Radar::Draw(const Point ¢er, double scale, double radius, double pointerRadius) const { RingShader::Bind(); for(const Object &object : objects) { Point position = object.position * scale; double length = position.Length(); if(length > radius) position *= radius / length; position += center; RingShader::Add(position, object.outer, object.inner, object.color); } RingShader::Unbind(); PointerShader::Bind(); for(const Pointer &pointer : pointers) PointerShader::Add(center, pointer.unit, 10., 10., pointerRadius, pointer.color); PointerShader::Unbind(); }
bool AI::MoveTo(Ship &ship, Command &command, const Point &target, double radius, double slow) { const Point &position = ship.Position(); const Point &velocity = ship.Velocity(); const Angle &angle = ship.Facing(); Point distance = target - position; double speed = velocity.Length(); bool isClose = (distance.Length() < radius); if(isClose && speed < slow) return true; distance = target - StoppingPoint(ship); bool isFacing = (distance.Unit().Dot(angle.Unit()) > .8); if(!isClose || !isFacing) command.SetTurn(TurnToward(ship, distance)); if(isFacing) command |= Command::FORWARD; return false; }
void OutlineShader::Draw(const Sprite *sprite, const Point &pos, const Point &size, const Color &color, const Point &unit, float frame) { glUseProgram(shader.Object()); glBindVertexArray(vao); GLfloat scale[2] = {2.f / Screen::Width(), -2.f / Screen::Height()}; glUniform2fv(scaleI, 1, scale); GLfloat off[2] = { static_cast<float>(.5 / size.X()), static_cast<float>(.5 / size.Y())}; glUniform2fv(offI, 1, off); glUniform1f(frameI, frame); glUniform1f(frameCountI, sprite->Frames()); Point uw = unit * size.X(); Point uh = unit * size.Y(); GLfloat transform[4] = { static_cast<float>(-uw.Y()), static_cast<float>(uw.X()), static_cast<float>(-uh.X()), static_cast<float>(-uh.Y()) }; glUniformMatrix2fv(transformI, 1, false, transform); GLfloat position[2] = { static_cast<float>(pos.X()), static_cast<float>(pos.Y())}; glUniform2fv(positionI, 1, position); glUniform4fv(colorI, 1, color.Get()); glBindTexture(GL_TEXTURE_2D_ARRAY, sprite->Texture(unit.Length() * Screen::Zoom() > 50.)); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glBindVertexArray(0); glUseProgram(0); }
void Gao(Point r1[3],Point r2[3],Point v1,Point v2,bool &flag,double &res) { v.x = v1.x-v2.x; v.y = v1.y-v2.y; res = 1e100; flag = false; double speed = v.Length(); if (cmp(speed,0.0) > 0) { for (int i = 0; i < 3; i++) { l1 = Line(r1[i],Point(r1[i].x+v.x,r1[i].y+v.y)); double curmin = 1e100; bool tflag = false; for (int j = 0; j < 3; j++) { Point tmp = Point(r2[j],r2[(j+1)%3]); l2 = Line(r2[j],Point(r2[j].x+tmp.x,r2[j].y+tmp.y)); if (cmp(xmult(l1,l2),0.0) == 0) continue; xp = LineToLine(l1,l2); if (OnSeg(xp,l2) == false) continue; if (cmp(v.x,0.0) != 0) if (cmp(Point(r1[i],xp).x/v.x,0.0) < 0) continue; if (cmp(v.y,0.0) != 0) if (cmp(Point(r1[i],xp).y/v.y,0.0) < 0) continue; curmin = min(curmin,Point(r1[i],xp).Length()); tflag = true; } if (tflag == true) { res = min(res,curmin); flag = true; } } } if (flag == true) res = sqrt(res/speed); }
void InscribedCircle() { for (int i = 0; i < 3; i++) scanf("%lf%lf",&p[i].x,&p[i].y); if (xmult(Point(p[0],p[1]),Point(p[0],p[2])) < 0) swap(p[1],p[2]); for (int i = 0; i < 3; i++) len[i] = Point(p[i],p[(i+1)%3]).Length(); tr = (len[0]+len[1]+len[2])/2; r = sqrt((tr-len[0])*(tr-len[1])*(tr-len[2])/tr); for (int i = 0; i < 2; i++) { v = Point(p[i],p[i+1]); tv = Point(-v.y,v.x); tr = tv.Length(); tv = Point(tv.x*r/tr,tv.y*r/tr); tp = Point(p[i].x+tv.x,p[i].y+tv.y); l[i].s = tp; tp = Point(p[i+1].x+tv.x,p[i+1].y+tv.y); l[i].e = tp; } tp = LineToLine(l[0],l[1]); printf("(%.6f,%.6f,%.6f)\n",tp.x,tp.y,r); }
// Begin the next step of calculations. void Engine::Step(bool isActive) { events.swap(eventQueue); eventQueue.clear(); // The calculation thread is now paused, so it is safe to access things. const shared_ptr<Ship> flagship = player.FlagshipPtr(); const StellarObject *object = player.GetStellarObject(); if(object) { center = object->Position(); centerVelocity = Point(); } else if(flagship) { center = flagship->Position(); centerVelocity = flagship->Velocity(); if(doEnter && flagship->Zoom() == 1. && !flagship->IsHyperspacing()) { doEnter = false; events.emplace_back(flagship, flagship, ShipEvent::JUMP); } if(flagship->IsEnteringHyperspace() || (flagship->Commands().Has(Command::WAIT) && !flagship->IsHyperspacing())) { if(jumpCount < 100) ++jumpCount; const System *from = flagship->GetSystem(); const System *to = flagship->GetTargetSystem(); if(from && to && from != to) { jumpInProgress[0] = from; jumpInProgress[1] = to; } } else if(jumpCount > 0) --jumpCount; } ai.UpdateEvents(events); ai.UpdateKeys(player, clickCommands, isActive && wasActive); wasActive = isActive; Audio::Update(center); // Any of the player's ships that are in system are assumed to have // landed along with the player. if(flagship && flagship->GetPlanet() && isActive) player.SetPlanet(flagship->GetPlanet()); const System *currentSystem = player.GetSystem(); // Update this here, for thread safety. if(!player.HasTravelPlan() && flagship && flagship->GetTargetSystem()) player.TravelPlan().push_back(flagship->GetTargetSystem()); if(player.HasTravelPlan() && currentSystem == player.TravelPlan().back()) player.PopTravel(); if(doFlash) { flash = .4; doFlash = false; } else if(flash) flash = max(0., flash * .99 - .002); targets.clear(); // Update the player's ammo amounts. ammo.clear(); if(flagship) for(const auto &it : flagship->Outfits()) { if(!it.first->Icon()) continue; if(it.first->Ammo()) ammo.emplace_back(it.first, flagship->OutfitCount(it.first->Ammo())); else if(it.first->FiringFuel()) { double remaining = flagship->Fuel() * flagship->Attributes().Get("fuel capacity"); ammo.emplace_back(it.first, remaining / it.first->FiringFuel()); } else ammo.emplace_back(it.first, -1); } // Display escort information for all ships of the "Escort" government, // and all ships with the "escort" personality, except for fighters that // are not owned by the player. escorts.Clear(); bool fleetIsJumping = (flagship && flagship->Commands().Has(Command::JUMP)); for(const auto &it : ships) if(it->GetGovernment()->IsPlayer() || it->GetPersonality().IsEscort()) if(!it->IsYours() && !it->CanBeCarried()) escorts.Add(*it, it->GetSystem() == currentSystem, fleetIsJumping); for(const shared_ptr<Ship> &escort : player.Ships()) if(!escort->IsParked() && escort != flagship) escorts.Add(*escort, escort->GetSystem() == currentSystem, fleetIsJumping); // Create the status overlays. statuses.clear(); if(isActive && Preferences::Has("Show status overlays")) for(const auto &it : ships) { if(!it->GetGovernment() || it->GetSystem() != currentSystem || it->Cloaking() == 1.) continue; bool isEnemy = it->GetGovernment()->IsEnemy(); if(isEnemy || it->GetGovernment()->IsPlayer() || it->GetPersonality().IsEscort()) { double width = min(it->Width(), it->Height()); statuses.emplace_back(it->Position() - center, it->Shields(), it->Hull(), max(20., width * .5), isEnemy); } } // Create the planet labels. labels.clear(); if(currentSystem && Preferences::Has("Show planet labels")) { for(const StellarObject &object : currentSystem->Objects()) { if(!object.GetPlanet()) continue; Point pos = object.Position() - center; if(pos.Length() < 600. + object.Radius()) labels.emplace_back(pos, object, currentSystem); } } if(flagship && flagship->IsOverheated()) Messages::Add("Your ship has overheated."); if(flagship && flagship->Hull()) info.SetSprite("player sprite", flagship->GetSprite()); else info.SetSprite("player sprite", nullptr); if(currentSystem) info.SetString("location", currentSystem->Name()); info.SetString("date", player.GetDate().ToString()); if(flagship) { info.SetBar("fuel", flagship->Fuel(), flagship->Attributes().Get("fuel capacity") * .01); info.SetBar("energy", flagship->Energy()); info.SetBar("heat", flagship->Heat()); info.SetBar("shields", flagship->Shields()); info.SetBar("hull", flagship->Hull(), 20.); } else { info.SetBar("fuel", 0.); info.SetBar("energy", 0.); info.SetBar("heat", 0.); info.SetBar("shields", 0.); info.SetBar("hull", 0.); } info.SetString("credits", Format::Number(player.Accounts().Credits()) + " credits"); if(flagship && flagship->GetTargetPlanet() && !flagship->Commands().Has(Command::JUMP)) { const StellarObject *object = flagship->GetTargetPlanet(); info.SetString("navigation mode", "Landing on:"); const string &name = object->Name(); info.SetString("destination", name); targets.push_back({ object->Position() - center, Angle(45.), object->Radius(), object->GetPlanet()->CanLand() ? Radar::FRIENDLY : Radar::HOSTILE}); } else if(flagship && flagship->GetTargetSystem()) { info.SetString("navigation mode", "Hyperspace:"); if(player.HasVisited(flagship->GetTargetSystem())) info.SetString("destination", flagship->GetTargetSystem()->Name()); else info.SetString("destination", "unexplored system"); } else { info.SetString("navigation mode", "Navigation:"); info.SetString("destination", "no destination"); } // Use the radar that was just populated. (The draw tick-tock has not // yet been toggled, but it will be at the end of this function.) shared_ptr<const Ship> target; targetAngle = Point(); if(flagship) target = flagship->GetTargetShip(); if(!target) { info.SetSprite("target sprite", nullptr); info.SetString("target name", "no target"); info.SetString("target type", ""); info.SetString("target government", ""); info.SetBar("target shields", 0.); info.SetBar("target hull", 0.); } else { if(target->GetSystem() == player.GetSystem() && target->Cloaking() < 1.) targetUnit = target->Facing().Unit(); info.SetSprite("target sprite", target->GetSprite(), targetUnit); info.SetString("target name", target->Name()); info.SetString("target type", target->ModelName()); if(!target->GetGovernment()) info.SetString("target government", "No Government"); else info.SetString("target government", target->GetGovernment()->GetName()); int targetType = RadarType(*target); info.SetOutlineColor(Radar::GetColor(targetType)); if(target->GetSystem() == player.GetSystem() && target->IsTargetable()) { info.SetBar("target shields", target->Shields()); info.SetBar("target hull", target->Hull(), 20.); // The target area will be a square, with sides proportional to the average // of the width and the height of the sprite. double size = (target->Width() + target->Height()) * .35; targets.push_back({ target->Position() - center, Angle(45.) + target->Facing(), size, targetType}); // Don't show the angle to the target if it is very close. targetAngle = target->Position() - center; double length = targetAngle.Length(); if(length > 20.) targetAngle /= length; else targetAngle = Point(); } else { info.SetBar("target shields", 0.); info.SetBar("target hull", 0.); } } }
// This returns false if it is time to delete this projectile. bool Projectile::Move(list<Effect> &effects) { if(--lifetime <= 0) { if(lifetime > -100) for(const auto &it : weapon->DieEffects()) for(int i = 0; i < it.second; ++i) { effects.push_back(*it.first); effects.back().Place(position, velocity, angle); } return false; } for(const auto &it : weapon->LiveEffects()) if(!Random::Int(it.second)) { effects.push_back(*it.first); effects.back().Place(position, velocity, angle); } // If the target has left the system, stop following it. Also stop if the // target has been captured by a different government. const Ship *target = cachedTarget; if(target) { target = targetShip.lock().get(); if(!target || !target->IsTargetable() || target->GetGovernment() != targetGovernment) { targetShip.reset(); cachedTarget = nullptr; target = nullptr; } } double turn = weapon->Turn(); double accel = weapon->Acceleration(); int homing = weapon->Homing(); if(target && homing && !Random::Int(60)) CheckLock(*target); if(target && homing && hasLock) { Point d = position - target->Position(); double drag = weapon->Drag(); double trueVelocity = drag ? accel / drag : velocity.Length(); double stepsToReach = d.Length() / trueVelocity; bool isFacingAway = d.Dot(angle.Unit()) > 0.; // At the highest homing level, compensate for target motion. if(homing >= 4) { // Adjust the target's position based on where it will be when we // reach it (assuming we're pointed right towards it). d -= stepsToReach * target->Velocity(); stepsToReach = d.Length() / trueVelocity; } double cross = d.Unit().Cross(angle.Unit()); // The very dumbest of homing missiles lose their target if pointed // away from it. if(isFacingAway && homing == 1) targetShip.reset(); else { double desiredTurn = TO_DEG * asin(cross); if(fabs(desiredTurn) > turn) turn = copysign(turn, desiredTurn); else turn = desiredTurn; // Levels 3 and 4 stop accelerating when facing away. if(homing >= 3) { double stepsToFace = desiredTurn / turn; // If you are facing away from the target, stop accelerating. if(stepsToFace * 1.5 > stepsToReach) accel = 0.; } } } // If a weapon is homing but has no target, do not turn it. else if(homing) turn = 0.; if(turn) angle += Angle(turn); if(accel) { velocity += accel * angle.Unit(); velocity *= 1. - weapon->Drag(); } position += velocity; if(target && (position - target->Position()).Length() < weapon->SplitRange() && !Random::Int(10)) lifetime = 0; return true; }
PlanetLabel::PlanetLabel(const Point &position, const StellarObject &object, const System *system) : position(position), radius(object.Radius()) { const Planet &planet = *object.GetPlanet(); color = object.TargetColor(); name = planet.Name(); if(!planet.IsWormhole()) { if(planet.GetGovernment()) { government = "(" + planet.GetGovernment()->GetName() + ")"; if(planet.CanLand()) { color = planet.GetGovernment()->GetColor(); color = Color(color.Get()[0] * .5 + .3, color.Get()[1] * .5 + .3, color.Get()[2] * .5 + .3); } else hostility = 3 + 2 * planet.GetGovernment()->IsEnemy(); } else government = "(No government)"; } double alpha = min(.5, max(0., .6 - (position.Length() - radius) * .001)); color = Color(color.Get()[0] * alpha, color.Get()[1] * alpha, color.Get()[2] * alpha, 0.); if(!system) return; // Figure out how big the label has to be. double width = max(FontSet::Get(18).Width(name), FontSet::Get(14).Width(government)) + 8.; for(int d = 0; d < 4; ++d) { bool overlaps = false; Point start = object.Position() + (radius + INNER_SPACE + LINE_GAP + LINE_LENGTH) * Angle(LINE_ANGLE[d]).Unit(); Point unit(LINE_ANGLE[d] > 180. ? -1. : 1., 0.); Point end = start + unit * width; for(const StellarObject &other : system->Objects()) { if(&other == &object) continue; double minDistance = other.Radius() + MIN_DISTANCE; double startDistance = other.Position().Distance(start); double endDistance = other.Position().Distance(end); overlaps |= (startDistance < minDistance || endDistance < minDistance); double projection = (other.Position() - start).Dot(unit); if(projection > 0. && projection < width) { double distance = sqrt(startDistance * startDistance - projection * projection); overlaps |= (distance < minDistance); } if(overlaps) break; } if(!overlaps) { direction = d; break; } } }
// get/update camera from values in node GvBool GvViewpoint::getCamera(GCamera &camera, BBox &bbox, float visibilityLimitNear,float visibilityLimit, Matrix *cameraTransform) { Matrix m; float viewAngle = 0.785398; float aspectRatio = 1.0f; orientation.get(m); camera.position=position; viewAngle = (float) fieldOfView; Point dir(0,0,-1.0); Point up(0,1.0,0); // apply orientation to standard dir and up vectors dir *= m; up *= m; up.Normalize(); if (cameraTransform) { camera.position *= *cameraTransform; dir = RotateOnly(*cameraTransform,dir); up = RotateOnly(*cameraTransform,up); // near far focalDistance ?????????? } dir.Normalize(); up.Normalize(); Point size = bbox.Size(); // computed bounding box float field = max(max(fabs(size.x),fabs(size.y)),fabs(size.z)); int positionInBox = bbox.Inside(camera.position); if (bbox.IsEmpty() || (field<=1E-20f)) field = 2.0f; // no bounding box yet, bad // compute distance to target point //xx float targetDistance = field*2.0f; float targetDistance = field*1.0f; // viewpoint inside scene if (positionInBox) targetDistance = 0.2 * field; camera.targetDistanceIsDefault=1; camera.zrangeIsDefault=1; // compute a reasonable z-range if (visibilityLimit >0.0f) { camera.zfar = visibilityLimit; camera.zrangeIsDefault=0; } else { if (positionInBox) camera.zfar = field*1.5; else camera.zfar = field*3.0f; Point center = bbox.Center(); Point d = camera.position - center; float dist = d.Length(); // make shure object is visible from viewpoint if ((dist+field) > camera.zfar) camera.zfar = dist + field; } if (visibilityLimitNear > 0.0f) camera.znear = visibilityLimitNear; else camera.znear = camera.zfar * camera.znearFactor; // compute target camera.target = camera.position + targetDistance*dir; camera.up = up; // field of view camera.height = 2.0 * tan(viewAngle * 0.5)*targetDistance; camera.width = camera.height * aspectRatio; if (!bbox.IsEmpty()) camera.SetWorldReference(bbox); camera.ComputeWorldUpFromUp(); camera.OnChanged(); return gtrue; }
Float DottedCornerFinder::FindNext(Float overlap) { Float lower = mLastT; Float upper = 1.0f; Float t; Point C = mLastC; Float r = 0.0f; Float factor = (1.0f - overlap); Float circlesDist = 0.0f; Float expectedDist = 0.0f; const Float DIST_MARGIN = 0.1f; if (mType == SINGLE_CURVE_AND_RADIUS) { r = mR0; expectedDist = (r + mLastR) * factor; // Find C_i on the center curve. for (size_t i = 0; i < MAX_LOOP; i++) { t = (upper + lower) / 2.0f; C = GetBezierPoint(mCenterBezier, t); // Check overlap along arc. circlesDist = GetBezierLength(mCenterBezier, mLastT, t); if (circlesDist < expectedDist - DIST_MARGIN) { lower = t; } else if (circlesDist > expectedDist + DIST_MARGIN) { upper = t; } else { break; } } } else if (mType == SINGLE_CURVE) { // Find C_i on the center curve, and calculate r_i. for (size_t i = 0; i < MAX_LOOP; i++) { t = (upper + lower) / 2.0f; C = GetBezierPoint(mCenterBezier, t); Point Diff = GetBezierDifferential(mCenterBezier, t); Float DiffLength = Diff.Length(); if (DiffLength == 0.0f) { // Basically this shouldn't happen. // If differential is 0, we cannot calculate tangent circle, // skip this point. t = (t + upper) / 2.0f; continue; } Point normal = PointRotateCCW90(Diff / DiffLength) * (-mNormalSign); r = CalculateDistanceToEllipticArc(C, normal, mInnerCurveOrigin, mInnerWidth, mInnerHeight); // Check overlap along arc. circlesDist = GetBezierLength(mCenterBezier, mLastT, t); expectedDist = (r + mLastR) * factor; if (circlesDist < expectedDist - DIST_MARGIN) { lower = t; } else if (circlesDist > expectedDist + DIST_MARGIN) { upper = t; } else { break; } } } else { Float distSquareMax = Square(mMaxR * 3.0f); Float circlesDistSquare = 0.0f; // Find C_i and r_i. for (size_t i = 0; i < MAX_LOOP; i++) { t = (upper + lower) / 2.0f; Point innerTangent = GetBezierPoint(mInnerBezier, t); if ((innerTangent - mLastC).LengthSquare() > distSquareMax) { // It's clear that this tangent point is too far, skip it. upper = t; continue; } Point Diff = GetBezierDifferential(mInnerBezier, t); Float DiffLength = Diff.Length(); if (DiffLength == 0.0f) { // Basically this shouldn't happen. // If differential is 0, we cannot calculate tangent circle, // skip this point. t = (t + upper) / 2.0f; continue; } Point normal = PointRotateCCW90(Diff / DiffLength) * mNormalSign; FindPointAndRadius(C, r, innerTangent, normal, t); // Check overlap with direct distance. circlesDistSquare = (C - mLastC).LengthSquare(); expectedDist = (r + mLastR) * factor; if (circlesDistSquare < Square(expectedDist - DIST_MARGIN)) { lower = t; } else if (circlesDistSquare > Square(expectedDist + DIST_MARGIN)) { upper = t; } else { break; } } circlesDist = sqrt(circlesDistSquare); } if (mHasZeroBorderWidth) { // When calculating circle around r=0, it may result in wrong radius that // is bigger than previous circle. Detect it and stop calculating. const Float R_MARGIN = 0.1f; if (mLastR < R_MARGIN && r > mLastR) { mHasMore = false; mLastR = 0.0f; return 0.0f; } } mLastT = t; mLastC = C; mLastR = r; if (mHasZeroBorderWidth) { const Float T_MARGIN = 0.001f; if (mLastT >= 1.0f - T_MARGIN || (mLastC - mCn).LengthSquare() < Square(mLastR)) { mHasMore = false; } } if (expectedDist == 0.0f) { return 0.0f; } return 1.0f - circlesDist * factor / expectedDist; }
void Pony48Engine::drawBoard() { float fTotalWidth = BOARD_WIDTH * TILE_WIDTH + (BOARD_WIDTH + 1) * TILE_SPACING; float fTotalHeight = BOARD_HEIGHT * TILE_HEIGHT + (BOARD_HEIGHT + 1) * TILE_SPACING; //Fill in bg fillRect(Point(-fTotalWidth/2.0, fTotalHeight/2.0), Point(fTotalWidth/2.0, -fTotalHeight/2.0), m_BoardBg); //Draw at z = 0 //Fill in bg for individual tiles for(int i = 0; i < BOARD_HEIGHT; i++) { for(int j = 0; j < BOARD_WIDTH; j++) { //Draw tile bg glPushMatrix(); glTranslatef(0, 0, TILEBG_DRAWZ); Point ptDrawPos(-fTotalWidth/2.0 + TILE_SPACING + (TILE_SPACING + TILE_WIDTH) * j, fTotalHeight/2.0 - TILE_SPACING - (TILE_SPACING + TILE_HEIGHT) * i); fillRect(ptDrawPos, Point(ptDrawPos.x + TILE_WIDTH, ptDrawPos.y - TILE_HEIGHT), m_TileBg[j][i]); glPopMatrix(); } } //Draw joining-tile animations for(list<TilePiece*>::iterator i = m_lSlideJoinAnimations.begin(); i != m_lSlideJoinAnimations.end(); i++) { Point ptDrawPos(-fTotalWidth/2.0 + TILE_SPACING + (TILE_SPACING + TILE_WIDTH) * (*i)->destx, fTotalHeight/2.0 - TILE_SPACING - (TILE_SPACING + TILE_HEIGHT) * (*i)->desty); glPushMatrix(); glTranslatef(ptDrawPos.x+TILE_WIDTH/2.0+(*i)->drawSlide.x, ptDrawPos.y-TILE_HEIGHT/2.0+(*i)->drawSlide.y, JOINANIM_DRAWZ); (*i)->draw(); glPopMatrix(); } //Draw tiles themselves (separate loop because z-order alpha issues with animations) for(int i = 0; i < BOARD_HEIGHT; i++) { for(int j = 0; j < BOARD_WIDTH; j++) { Point ptDrawPos(-fTotalWidth/2.0 + TILE_SPACING + (TILE_SPACING + TILE_WIDTH) * j, fTotalHeight/2.0 - TILE_SPACING - (TILE_SPACING + TILE_HEIGHT) * i); //Draw tile if(m_Board[j][i] != NULL) { glPushMatrix(); glTranslatef(ptDrawPos.x+TILE_WIDTH/2.0+m_Board[j][i]->drawSlide.x, ptDrawPos.y-TILE_HEIGHT/2.0+m_Board[j][i]->drawSlide.y, TILE_DRAWZ); m_Board[j][i]->draw(); glPopMatrix(); } } } //Draw particle fx for highest tile if(m_highestTile != NULL) { for(int i = 0; i < BOARD_HEIGHT; i++) { for(int j = 0; j < BOARD_WIDTH; j++) { //Draw tile if(m_Board[j][i] == m_highestTile) { Point ptDrawPos(-fTotalWidth/2.0 + TILE_SPACING + (TILE_SPACING + TILE_WIDTH) * j, fTotalHeight/2.0 - TILE_SPACING - (TILE_SPACING + TILE_HEIGHT) * i); glPushMatrix(); glTranslatef(ptDrawPos.x+TILE_WIDTH/2.0+m_Board[j][i]->drawSlide.x, ptDrawPos.y-TILE_HEIGHT/2.0+m_Board[j][i]->drawSlide.y, TILE_DRAWZ + 0.1); m_newHighTile->img = m_highestTile->bg->img; m_newHighTile->draw(); m_newHighTile->img = m_highestTile->seg->img; m_newHighTile->draw(); glPopMatrix(); break; } } } } //Draw arrows for direction the mouse will move the board if(m_iMouseControl >= MOUSE_MOVE_TRIP_AMT && m_iCurMode == PLAYING) { glPushMatrix(); Point ptMoveDir = worldPosFromCursor(getCursorPos()); //Rotate first to simplify logic. Hooray! switch(getDirOfVec2(ptMoveDir)) { case UP: glRotatef(90, 0, 0, 1); break; case DOWN: glRotatef(-90, 0, 0, 1); break; case LEFT: glRotatef(180, 0, 0, 1); break; } //Determine the drawing alpha based on how far away from the center the mouse is float32 fDestAlpha = min(fabs(ptMoveDir.Length() / (getCameraView().height() / 2.0)) - 0.4, 0.4); //Draw 16 arrows pointing in the direction we'll move for(int y = 0; y < BOARD_HEIGHT; y++) { for(int x = 0; x < BOARD_WIDTH; x++) { //Position to draw this arrow at Point ptDrawPos(-fTotalWidth/2.0 + (TILE_SPACING + TILE_WIDTH) * x + TILE_WIDTH / 2.0 + TILE_SPACING + m_fArrowAdd, fTotalHeight/2.0 - (TILE_SPACING + TILE_WIDTH) * y - TILE_HEIGHT / 2.0 - TILE_SPACING); //If this arrow is reaching the end of its lifespan, fade out float32 fDrawAlpha = fDestAlpha; if(m_fArrowAdd >= MOVEARROW_FADEOUTDIST && x == BOARD_WIDTH - 1) fDrawAlpha *= 1.0f - ((m_fArrowAdd - MOVEARROW_FADEOUTDIST) / MOVEARROW_FADEOUTDIST); fDrawAlpha = min(fDrawAlpha, 1.0f); fDrawAlpha = max(fDrawAlpha, 0.0f); //Now draw glColor4f(1,1,1,fDrawAlpha); glPushMatrix(); glTranslatef(ptDrawPos.x, ptDrawPos.y, MOVEARROW_DRAWZ); m_imgMouseMoveArrow->render(Point(1,1)); glPopMatrix(); //See if we should draw new arrow spawning if(!x && (ARROW_RESET - m_fArrowAdd) <= MOVEARROW_FADEINDIST) { fDrawAlpha = fDestAlpha; fDrawAlpha *= 1.0 - (ARROW_RESET - m_fArrowAdd) / MOVEARROW_FADEINDIST; fDrawAlpha = min(fDrawAlpha, 1.0f); fDrawAlpha = max(fDrawAlpha, 0.0f); glColor4f(1,1,1,fDrawAlpha); glPushMatrix(); glTranslatef(ptDrawPos.x - (TILE_SPACING + TILE_WIDTH), ptDrawPos.y, MOVEARROW_DRAWZ); m_imgMouseMoveArrow->render(Point(1,1)); glPopMatrix(); } } } glPopMatrix(); } }
int main() { while (scanf("%d",&n) != EOF) { for (int i = 0;i < n;i++) scanf("%lf%lf%lf",&c[i].c.x,&c[i].c.y,&c[i].r); for (int i = 1;i <= n;i++) ans[i] = 0.0; for (int i = 0;i < n;i++) { tote = 0; e[tote++] = Event(-pi,1); e[tote++] = Event(pi,-1); for (int j = 0;j < n;j++) if (j != i) { lab = Point(c[j].c.x-c[i].c.x,c[j].c.y-c[i].c.y); AB = lab.Length(); AC = c[i].r; BC = c[j].r; if (cmp(AB+AC,BC) <= 0) { e[tote++] = Event(-pi,1); e[tote++] = Event(pi,-1); continue; } if (cmp(AB+BC,AC) <= 0) continue; if (cmp(AB,AC+BC) > 0) continue; theta = atan2(lab.y,lab.x); fai = acos((AC*AC+AB*AB-BC*BC)/(2.0*AC*AB)); a0 = theta-fai; if (cmp(a0,-pi) < 0) a0 += 2*pi; a1 = theta+fai; if (cmp(a1,pi) > 0) a1 -= 2*pi; if (cmp(a0,a1) > 0) { e[tote++] = Event(a0,1); e[tote++] = Event(pi,-1); e[tote++] = Event(-pi,1); e[tote++] = Event(a1,-1); } else { e[tote++] = Event(a0,1); e[tote++] = Event(a1,-1); } } sort(e,e+tote,Eventcmp); cur = 0; for (int j = 0;j < tote;j++) { if (cur != 0 && cmp(e[j].tim,pre[cur]) != 0) { ans[cur] += Area(e[j].tim-pre[cur],c[i].r); ans[cur] += xmult(Point(c[i].c.x+c[i].r*cos(pre[cur]),c[i].c.y+c[i].r*sin(pre[cur])), Point(c[i].c.x+c[i].r*cos(e[j].tim),c[i].c.y+c[i].r*sin(e[j].tim)))/2.0; } cur += e[j].typ; pre[cur] = e[j].tim; } } for (int i = 1;i < n;i++) ans[i] -= ans[i+1]; for (int i = 1;i <= n;i++) printf("[%d] = %.3f\n",i,ans[i]); } return 0; }
float Point::Distance(const Point& p0, const Point& p1) { Point diff = p1 - p0; return diff.Length(); }
// 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; }
// Fire whichever of the given ship's weapons can hit a hostile target. Command AI::AutoFire(const Ship &ship, const list<shared_ptr<Ship>> &ships, bool secondary) const { Command command; int index = -1; // Special case: your target is not your enemy. Do not fire, because you do // not want to risk damaging that target. The only time a ship other than // the player will target a friendly ship is if the player has asked a ship // for assistance. shared_ptr<Ship> currentTarget = ship.GetTargetShip(); const Government *gov = ship.GetGovernment(); bool isSharingTarget = ship.IsYours() && currentTarget == sharedTarget.lock(); bool currentIsEnemy = currentTarget && currentTarget->GetGovernment()->IsEnemy(gov) && currentTarget->GetSystem() == ship.GetSystem(); if(currentTarget && !(currentIsEnemy || isSharingTarget)) currentTarget.reset(); // Only fire on disabled targets if you don't want to plunder them. bool spareDisabled = (ship.GetPersonality().Disables() || ship.GetPersonality().Plunders()); // Find the longest range of any of your non-homing weapons. double maxRange = 0.; for(const Armament::Weapon &weapon : ship.Weapons()) if(weapon.IsReady() && !weapon.IsHoming() && (secondary || !weapon.GetOutfit()->Icon())) maxRange = max(maxRange, weapon.GetOutfit()->Range()); // Extend the weapon range slightly to account for velocity differences. maxRange *= 1.5; // Find all enemy ships within range of at least one weapon. vector<shared_ptr<const Ship>> enemies; if(currentTarget) enemies.push_back(currentTarget); for(auto target : ships) if(target->IsTargetable() && gov->IsEnemy(target->GetGovernment()) && target->Velocity().Length() < 20. && target->GetSystem() == ship.GetSystem() && target->Position().Distance(ship.Position()) < maxRange && target != currentTarget) enemies.push_back(target); for(const Armament::Weapon &weapon : ship.Weapons()) { ++index; // Skip weapons that are not ready to fire. Also skip homing weapons if // no target is selected, and secondary weapons if only firing primaries. if(!weapon.IsReady() || (!currentTarget && weapon.IsHoming())) continue; if(!secondary && weapon.GetOutfit()->Icon()) continue; // Special case: if the weapon uses fuel, be careful not to spend so much // fuel that you cannot leave the system if necessary. if(weapon.GetOutfit()->FiringFuel()) { double fuel = ship.Fuel() * ship.Attributes().Get("fuel capacity"); fuel -= weapon.GetOutfit()->FiringFuel(); // If the ship is not ever leaving this system, it does not need to // reserve any fuel. bool isStaying = ship.GetPersonality().IsStaying(); if(!secondary || fuel < (isStaying ? 0. : ship.JumpFuel())) continue; } // Figure out where this weapon will fire from, but add some randomness // depending on how accurate this ship's pilot is. Point start = ship.Position() + ship.Facing().Rotate(weapon.GetPoint()); start += ship.GetPersonality().Confusion(); const Outfit *outfit = weapon.GetOutfit(); double vp = outfit->Velocity(); double lifetime = outfit->TotalLifetime(); if(currentTarget && (weapon.IsHoming() || weapon.IsTurret())) { bool hasBoarded = Has(ship, currentTarget, ShipEvent::BOARD); if(currentTarget->IsDisabled() && spareDisabled && !hasBoarded) continue; Point p = currentTarget->Position() - start; Point v = currentTarget->Velocity() - ship.Velocity(); // By the time this action is performed, the ships will have moved // forward one time step. p += v; if(p.Length() < outfit->BlastRadius()) continue; double steps = Armament::RendevousTime(p, v, vp); if(steps == steps && steps <= lifetime) { command.SetFire(index); continue; } } // Don't fire homing weapons with no target. if(weapon.IsHoming()) continue; for(const shared_ptr<const Ship> &target : enemies) { if(!target->IsTargetable() || target->Velocity().Length() > 20. || target->GetSystem() != ship.GetSystem()) continue; // Don't shoot ships we want to plunder. bool hasBoarded = Has(ship, target, ShipEvent::BOARD); if(target->IsDisabled() && spareDisabled && !hasBoarded) continue; Point p = target->Position() - start; Point v = target->Velocity() - ship.Velocity(); // By the time this action is performed, the ships will have moved // forward one time step. p += v; // Get the vector the weapon will travel along. v = (ship.Facing() + weapon.GetAngle()).Unit() * vp - v; // Extrapolate over the lifetime of the projectile. v *= lifetime; const Mask &mask = target->GetSprite().GetMask(step); if(mask.Collide(-p, v, target->Facing()) < 1.) { command.SetFire(index); break; } } } return command; }
void MapPanel::DrawSystems() const { if(commodity == SHOW_GOVERNMENT) closeGovernments.clear(); // Draw the circles for the systems, colored based on the selected criterion, // which may be government, services, or commodity prices. for(const auto &it : GameData::Systems()) { const System &system = it.second; // Referring to a non-existent system in a mission can create a spurious // system record. Ignore those. if(system.Name().empty()) continue; if(!player.HasSeen(&system) && &system != specialSystem) continue; Point pos = Zoom() * (system.Position() + center); Color color = UninhabitedColor(); if(!player.HasVisited(&system)) color = UnexploredColor(); else if(system.IsInhabited()) { if(commodity >= SHOW_SPECIAL) { double value = 0.; bool showUninhabited = false; if(commodity >= 0) { const Trade::Commodity &com = GameData::Commodities()[commodity]; double price = system.Trade(com.name); showUninhabited = !price; value = (2. * (price - com.low)) / (com.high - com.low) - 1.; } else if(commodity == SHOW_SHIPYARD) { double size = 0; for(const StellarObject &object : system.Objects()) if(object.GetPlanet()) size += object.GetPlanet()->Shipyard().size(); value = size ? min(10., size) / 10. : -1.; } else if(commodity == SHOW_OUTFITTER) { double size = 0; for(const StellarObject &object : system.Objects()) if(object.GetPlanet()) size += object.GetPlanet()->Outfitter().size(); value = size ? min(60., size) / 60. : -1.; } else if(commodity == SHOW_VISITED) { bool all = true; bool some = false; for(const StellarObject &object : system.Objects()) if(object.GetPlanet()) { bool visited = player.HasVisited(object.GetPlanet()); all &= visited; some |= visited; } value = -1 + some + all; } else value = SystemValue(&system); color = (showUninhabited ? UninhabitedColor() : MapColor(value)); } else if(commodity == SHOW_GOVERNMENT) { const Government *gov = system.GetGovernment(); color = GovernmentColor(gov); // For every government that is draw, keep track of how close it // is to the center of the view. The four closest governments // will be displayed in the key. double distance = pos.Length(); auto it = closeGovernments.find(gov); if(it == closeGovernments.end()) closeGovernments[gov] = distance; else it->second = min(it->second, distance); } else { double reputation = system.GetGovernment()->Reputation(); bool hasDominated = true; bool isInhabited = false; bool canLand = false; for(const StellarObject &object : system.Objects()) if(object.GetPlanet() && object.GetPlanet()->HasSpaceport()) { canLand |= object.GetPlanet()->CanLand(); isInhabited |= object.GetPlanet()->IsInhabited(); hasDominated &= (!object.GetPlanet()->IsInhabited() || GameData::GetPolitics().HasDominated(object.GetPlanet())); } hasDominated &= isInhabited; color = ReputationColor(reputation, canLand, canLand && hasDominated); } } RingShader::Draw(pos, OUTER, INNER, color); } }
void CircleCurve2d :: NormalVector (const Point<2> & p, Vec<2> & n) const { n = p - center; n /= n.Length(); }