void Player::InitCockpit() { m_cockpit.release(); if (!Pi::config->Int("EnableCockpit")) return; // XXX select a cockpit model. this is all quite skanky because we want a // fallback if the name is not found, which means having to actually try to // load the model. but ModelBody (on which ShipCockpit is currently based) // requires a model name, not a model object. it won't hurt much because it // all stays in the model cache anyway, its just awkward. the fix is to fix // ShipCockpit so its not a ModelBody and thus does its model work // directly, but we're not there yet std::string cockpitModelName; if (!GetShipType()->cockpitName.empty()) { if (Pi::FindModel(GetShipType()->cockpitName, false)) cockpitModelName = GetShipType()->cockpitName; } if (cockpitModelName.empty()) { if (Pi::FindModel("default_cockpit", false)) cockpitModelName = "default_cockpit"; } if (!cockpitModelName.empty()) m_cockpit.reset(new ShipCockpit(cockpitModelName)); }
void Ship::UpdateFuel(const float timeStep, const vector3d &thrust) { const double fuelUseRate = GetShipType()->GetFuelUseRate() * 0.01; double totalThrust = (fabs(thrust.x) + fabs(thrust.y) + fabs(thrust.z)) / -GetShipType()->linThrust[ShipType::THRUSTER_FORWARD]; FuelState lastState = GetFuelState(); SetFuel(GetFuel() - timeStep * (totalThrust * fuelUseRate)); FuelState currentState = GetFuelState(); UpdateFuelStats(); if (currentState != lastState) LuaEvent::Queue("onShipFuelChanged", this, EnumStrings::GetString("ShipFuelStatus", currentState)); }
double Ship::AIFaceOrient(const vector3d &dir, const vector3d &updir) { double timeStep = Pi::GetTimeStep(); matrix4x4d rot; GetRotMatrix(rot); double maxAccel = GetShipType().angThrust / GetAngularInertia(); // should probably be in stats anyway double frameAccel = maxAccel * timeStep; if (dir.Dot(vector3d(rot[8], rot[9], rot[10])) > -0.999999) { AIFaceDirection(dir); return false; } vector3d uphead = (updir * rot).Normalized(); // create desired object-space updir vector3d dav(0.0, 0.0, 0.0); // desired angular velocity double ang = 0.0; if (uphead.y < 0.999999) { ang = acos(Clamp(uphead.y, -1.0, 1.0)); // scalar angle from head to curhead double iangvel = sqrt(2.0 * maxAccel * ang); // ideal angvel at current time double frameEndAV = iangvel - frameAccel; if (frameEndAV <= 0.0) iangvel = ang / timeStep; // last frame discrete correction else iangvel = (iangvel + frameEndAV) * 0.5; // discrete overshoot correction dav.z = -iangvel; } vector3d cav = (GetAngVelocity() - GetFrame()->GetAngVelocity()) * rot; // current obj-rel angvel // vector3d cav = GetAngVelocity() * rot; // current obj-rel angvel vector3d diff = (dav - cav) / frameAccel; // find diff between current & desired angvel SetAngThrusterState(diff); return ang; // if (diff.x*diff.x > 1.0 || diff.y*diff.y > 1.0 || diff.z*diff.z > 1.0) return false; // else return true; }
void Ship::Init() { // XXX the animation namespace must match that in LuaConstants // note: this must be set before generating the collision mesh // (which happens in SetModel()) and before rendering GetLmrObjParams().animationNamespace = "ShipAnimation"; GetLmrObjParams().equipment = &m_equipment; const ShipType &stype = GetShipType(); // Dirty hack: Always use gear-down mesh for collision detection // necessary because some ships have non-docking meshes too large for docking float temp = GetLmrObjParams().animValues[ANIM_WHEEL_STATE]; GetLmrObjParams().animValues[ANIM_WHEEL_STATE] = 1.0f; SetModel(stype.lmrModelName.c_str()); GetLmrObjParams().animValues[ANIM_WHEEL_STATE] = temp; SetMassDistributionFromModel(); UpdateStats(); m_stats.hull_mass_left = float(stype.hullMass); m_stats.shield_mass_left = 0; m_hyperspace.now = false; // TODO: move this on next savegame change, maybe m_hyperspaceCloud = 0; m_landingGearAnimation = 0; SceneGraph::Model *nmodel = dynamic_cast<SceneGraph::Model*>(GetModel()); if (nmodel) { m_landingGearAnimation = nmodel->FindAnimation("gear_down"); } }
/* Assumed to be at model coords */ void Ship::RenderLaserfire() { const ShipType &stype = GetShipType(); glDisable(GL_LIGHTING); for (int i=0; i<ShipType::GUNMOUNT_MAX; i++) { if (!m_gunState[i]) continue; glPushAttrib(GL_CURRENT_BIT | GL_LINE_BIT); switch (m_equipment.Get(Equip::SLOT_LASER, i)) { case Equip::LASER_2MW_BEAM: glColor3f(1,.5,0); break; case Equip::LASER_4MW_BEAM: glColor3f(1,1,0); break; default: case Equip::LASER_1MW_BEAM: glColor3f(1,0,0); break; } glLineWidth(2.0f); glBegin(GL_LINES); vector3f pos = stype.gunMount[i].pos; glVertex3f(pos.x, pos.y, pos.z); glVertex3fv(&((10000)*stype.gunMount[i].dir)[0]); glEnd(); glPopAttrib(); } glEnable(GL_LIGHTING); }
double Ship::GetAccelMin() const { const ShipType &stype = GetShipType(); float val = stype.linThrust[ShipType::THRUSTER_UP]; val = std::min(val, stype.linThrust[ShipType::THRUSTER_RIGHT]); val = std::min(val, -stype.linThrust[ShipType::THRUSTER_LEFT]); return val / GetMass(); }
float Ship::GetWeakestThrustersForce() const { const ShipType &type = GetShipType(); float val = FLT_MAX; for (int i=0; i<ShipType::THRUSTER_MAX; i++) { val = std::min(val, fabsf(type.linThrust[i])); } return val; }
void Ship::Init() { const ShipType &stype = GetShipType(); SetModel(stype.lmrModelName.c_str()); SetMassDistributionFromModel(); UpdateMass(); m_stats.hull_mass_left = float(stype.hullMass); m_stats.shield_mass_left = 0; }
// Input in object space void Ship::AIMatchAngVelObjSpace(const vector3d &angvel) { double maxAccel = GetShipType().angThrust / GetAngularInertia(); double invFrameAccel = 1.0 / (maxAccel * Pi::GetTimeStep()); matrix4x4d rot; GetRotMatrix(rot); vector3d diff = angvel - GetAngVelocity() * rot; // find diff between current & desired angvel SetAngThrusterState(diff * invFrameAccel); }
void Ship::UpdateFuelStats() { const ShipType &stype = GetShipType(); m_stats.fuel_tank_mass = stype.fuelTankMass; m_stats.fuel_use = GetFuelUseRate(); m_stats.fuel_tank_mass_left = m_stats.fuel_tank_mass * GetFuel(); UpdateMass(); }
void Ship::InitEquipSet() { lua_State * l = Lua::manager->GetLuaState(); PropertyMap & p = Properties(); LUA_DEBUG_START(l); pi_lua_import(l, "EquipSet"); LuaTable es_class(l, -1); LuaTable slots = LuaTable(l).LoadMap(GetShipType()->slots.begin(), GetShipType()->slots.end()); m_equipSet = es_class.Call<LuaRef>("New", slots); p.Set("equipSet", ScopedTable(m_equipSet)); UpdateEquipStats(); { ScopedTable es(m_equipSet); int usedCargo = es.CallMethod<int>("OccupiedSpace", "cargo"); int totalCargo = std::min(m_stats.free_capacity + usedCargo, es.CallMethod<int>("SlotSize", "cargo")); p.Set("usedCargo", usedCargo); p.Set("totalCargo", totalCargo); } lua_pop(l, 2); LUA_DEBUG_END(l, 0); }
vector3d Ship::GetMaxThrust(const vector3d &dir) { const ShipType &stype = GetShipType(); vector3d maxThrust; maxThrust.x = (dir.x > 0) ? stype.linThrust[ShipType::THRUSTER_RIGHT] : -stype.linThrust[ShipType::THRUSTER_LEFT]; maxThrust.y = (dir.y > 0) ? stype.linThrust[ShipType::THRUSTER_UP] : -stype.linThrust[ShipType::THRUSTER_DOWN]; maxThrust.z = (dir.z > 0) ? stype.linThrust[ShipType::THRUSTER_REVERSE] : -stype.linThrust[ShipType::THRUSTER_FORWARD]; return maxThrust; }
bool Ship::OnDamage(Object *attacker, float kgDamage, const CollisionContact& contactData) { if (m_invulnerable) { Sound::BodyMakeNoise(this, "Hull_hit_Small", 0.5f); return true; } if (!IsDead()) { float dam = kgDamage*0.001f; if (m_stats.shield_mass_left > 0.0f) { if (m_stats.shield_mass_left > dam) { m_stats.shield_mass_left -= dam; dam = 0; } else { dam -= m_stats.shield_mass_left; m_stats.shield_mass_left = 0; } Properties().Set("shieldMassLeft", m_stats.shield_mass_left); } m_shieldCooldown = DEFAULT_SHIELD_COOLDOWN_TIME; // transform the collision location into the models local space (from world space) and add it as a hit. matrix4x4d mtx = GetOrient(); mtx.SetTranslate( GetPosition() ); const matrix4x4d invmtx = mtx.Inverse(); const vector3d localPos = invmtx * contactData.pos; GetShields()->AddHit(localPos); m_stats.hull_mass_left -= dam; Properties().Set("hullMassLeft", m_stats.hull_mass_left); Properties().Set("hullPercent", 100.0f * (m_stats.hull_mass_left / float(m_type->hullMass))); if (m_stats.hull_mass_left < 0) { if (attacker) { if (attacker->IsType(Object::BODY)) LuaEvent::Queue("onShipDestroyed", this, dynamic_cast<Body*>(attacker)); } Explode(); } else { if (Pi::rng.Double() < kgDamage) SfxManager::Add(this, TYPE_DAMAGE); if (dam < 0.01 * float(GetShipType()->hullMass)) Sound::BodyMakeNoise(this, "Hull_hit_Small", 1.0f); else Sound::BodyMakeNoise(this, "Hull_Hit_Medium", 1.0f); } } //Output("Ouch! %s took %.1f kilos of damage from %s! (%.1f t hull left)\n", GetLabel().c_str(), kgDamage, attacker->GetLabel().c_str(), m_stats.hull_mass_left); return true; }
void Ship::TimeStepUpdate(const float timeStep) { // XXX why not just f*****g do this rather than track down the // reason that ship geoms are being collision tested during launch if (m_flightState == FLYING) Enable(); else Disable(); vector3d maxThrust = GetMaxThrust(m_thrusters); AddRelForce(vector3d(maxThrust.x*m_thrusters.x, maxThrust.y*m_thrusters.y, maxThrust.z*m_thrusters.z)); AddRelTorque(GetShipType().angThrust * m_angThrusters); DynamicBody::TimeStepUpdate(timeStep); }
void Ship::Load(Serializer::Reader &rd, Space *space) { DynamicBody::Load(rd, space); // needs fixups m_angThrusters = rd.Vector3d(); m_thrusters = rd.Vector3d(); m_wheelTransition = rd.Int32(); m_wheelState = rd.Float(); m_launchLockTimeout = rd.Float(); m_testLanded = rd.Bool(); m_flightState = FlightState(rd.Int32()); m_alertState = AlertState(rd.Int32()); m_lastFiringAlert = rd.Double(); m_hyperspace.dest = SystemPath::Unserialize(rd); m_hyperspace.countdown = rd.Float(); for (int i=0; i<ShipType::GUNMOUNT_MAX; i++) { m_gunState[i] = rd.Int32(); m_gunRecharge[i] = rd.Float(); m_gunTemperature[i] = rd.Float(); } m_ecmRecharge = rd.Float(); m_shipFlavour.Load(rd); m_type = &ShipType::types[m_shipFlavour.id]; m_dockedWithPort = rd.Int32(); m_dockedWithIndex = rd.Int32(); m_equipment.InitSlotSizes(m_shipFlavour.id); m_equipment.Load(rd); Init(); m_stats.hull_mass_left = rd.Float(); // must be after Init()... m_stats.shield_mass_left = rd.Float(); if(rd.Int32()) m_curAICmd = AICommand::Load(rd); else m_curAICmd = 0; m_aiMessage = AIError(rd.Int32()); SetFuel(rd.Double()); m_stats.fuel_tank_mass_left = GetShipType().fuelTankMass * GetFuel(); m_reserveFuel = rd.Double(); UpdateStats(); // this is necessary, UpdateStats() in Ship::Init has wrong values of m_thrusterFuel after Load m_controller = 0; const ShipController::Type ctype = static_cast<ShipController::Type>(rd.Int32()); if (ctype == ShipController::PLAYER) SetController(new PlayerShipController()); else SetController(new ShipController()); m_controller->Load(rd); m_equipment.onChange.connect(sigc::mem_fun(this, &Ship::OnEquipmentChange)); }
bool Ship::OnDamage(Object *attacker, float kgDamage) { if (!IsDead()) { float dam = kgDamage*0.001f; if (m_stats.shield_mass_left > 0.0f) { if (m_stats.shield_mass_left > dam) { m_stats.shield_mass_left -= dam; dam = 0; } else { dam -= m_stats.shield_mass_left; m_stats.shield_mass_left = 0; } } m_stats.hull_mass_left -= dam; if (m_stats.hull_mass_left < 0) { if (attacker) { if (attacker->IsType(Object::BODY)) { // XXX remove this call. kill stuff (including elite rating) should be in a script static_cast<Body*>(attacker)->OnHaveKilled(this); Pi::luaOnShipDestroyed->Queue(this, dynamic_cast<Body*>(attacker)); } if (attacker->IsType(Object::SHIP)) Polit::NotifyOfCrime(static_cast<Ship*>(attacker), Polit::CRIME_MURDER); } Space::KillBody(this); Sfx::Add(this, Sfx::TYPE_EXPLOSION); Sound::BodyMakeNoise(this, "Explosion_1", 1.0f); } else { if (attacker && attacker->IsType(Object::SHIP)) Polit::NotifyOfCrime(static_cast<Ship*>(attacker), Polit::CRIME_PIRACY); if (Pi::rng.Double() < kgDamage) Sfx::Add(this, Sfx::TYPE_DAMAGE); if (dam < 0.01 * float(GetShipType().hullMass)) Sound::BodyMakeNoise(this, "Hull_hit_Small", 1.0f); else Sound::BodyMakeNoise(this, "Hull_Hit_Medium", 1.0f); } } //printf("Ouch! %s took %.1f kilos of damage from %s! (%.1f t hull left)\n", GetLabel().c_str(), kgDamage, attacker->GetLabel().c_str(), m_stats.hull_mass_left); return true; }
void Ship::Init() { const ShipType &stype = GetShipType(); SetModel(stype.modelName.c_str()); SetMassDistributionFromModel(); UpdateStats(); m_stats.hull_mass_left = float(stype.hullMass); m_stats.shield_mass_left = 0; m_hyperspace.now = false; // TODO: move this on next savegame change, maybe m_hyperspaceCloud = 0; m_landingGearAnimation = GetModel()->FindAnimation("gear_down"); }
bool Ship::OnDamage(Object *attacker, float kgDamage) { if (!IsDead()) { float dam = kgDamage*0.001f; if (m_stats.shield_mass_left > 0.0f) { if (m_stats.shield_mass_left > dam) { m_stats.shield_mass_left -= dam; dam = 0; } else { dam -= m_stats.shield_mass_left; m_stats.shield_mass_left = 0; } } m_stats.hull_mass_left -= dam; if (m_stats.hull_mass_left < 0) { if (attacker) { if (attacker->IsType(Object::BODY)) LuaEvent::Queue("onShipDestroyed", this, dynamic_cast<Body*>(attacker)); if (attacker->IsType(Object::SHIP)) Polit::NotifyOfCrime(static_cast<Ship*>(attacker), Polit::CRIME_MURDER); } Explode(); } else { if (attacker && attacker->IsType(Object::SHIP)) Polit::NotifyOfCrime(static_cast<Ship*>(attacker), Polit::CRIME_PIRACY); if (Pi::rng.Double() < kgDamage) Sfx::Add(this, Sfx::TYPE_DAMAGE); if (dam < 0.01 * float(GetShipType().hullMass)) Sound::BodyMakeNoise(this, "Hull_hit_Small", 1.0f); else Sound::BodyMakeNoise(this, "Hull_Hit_Medium", 1.0f); } } //printf("Ouch! %s took %.1f kilos of damage from %s! (%.1f t hull left)\n", GetLabel().c_str(), kgDamage, attacker->GetLabel().c_str(), m_stats.hull_mass_left); return true; }
void Ship::FireWeapon(int num) { const ShipType &stype = GetShipType(); if (m_flightState != FLYING) return; const matrix3x3d &m = GetOrient(); const vector3d dir = m * vector3d(stype.gunMount[num].dir); const vector3d pos = m * vector3d(stype.gunMount[num].pos) + GetPosition(); m_gunTemperature[num] += 0.01f; Equip::Type t = m_equipment.Get(Equip::SLOT_LASER, num); const LaserType < = Equip::lasers[Equip::types[t].tableIndex]; m_gunRecharge[num] = lt.rechargeTime; vector3d baseVel = GetVelocity(); vector3d dirVel = lt.speed * dir.Normalized(); if (lt.flags & Equip::LASER_DUAL) { const ShipType::DualLaserOrientation orient = stype.gunMount[num].orient; const vector3d orient_norm = (orient == ShipType::DUAL_LASERS_VERTICAL) ? m.VectorX() : m.VectorY(); const vector3d sep = stype.gunMount[num].sep * dir.Cross(orient_norm).NormalizedSafe(); Projectile::Add(this, t, pos + sep, baseVel, dirVel); Projectile::Add(this, t, pos - sep, baseVel, dirVel); } else Projectile::Add(this, t, pos, baseVel, dirVel); /* // trace laser beam through frame to see who it hits CollisionContact c; GetFrame()->GetCollisionSpace()->TraceRay(pos, dir, 10000.0, &c, this->GetGeom()); if (c.userData1) { Body *hit = static_cast<Body*>(c.userData1); hit->OnDamage(this, damage); } */ Polit::NotifyOfCrime(this, Polit::CRIME_WEAPON_DISCHARGE); Sound::BodyMakeNoise(this, "Pulse_Laser", 1.0f); }
void Ship::TimeStepUpdate(const float timeStep) { // If docked, station is responsible for updating position/orient of ship // but we call this crap anyway and hope it doesn't do anything bad vector3d maxThrust = GetMaxThrust(m_thrusters); vector3d thrust = vector3d(maxThrust.x*m_thrusters.x, maxThrust.y*m_thrusters.y, maxThrust.z*m_thrusters.z); AddRelForce(thrust); AddRelTorque(GetShipType().angThrust * m_angThrusters); DynamicBody::TimeStepUpdate(timeStep); // fuel use decreases mass, so do this as the last thing in the frame UpdateFuel(timeStep, thrust); if (m_landingGearAnimation) static_cast<SceneGraph::Model*>(GetModel())->UpdateAnimations(); }
void Ship::FireWeapon(int num) { const ShipType &stype = GetShipType(); if (m_flightState != FLYING) return; matrix4x4d m; GetRotMatrix(m); vector3d dir = vector3d(stype.gunMount[num].dir); vector3d pos = vector3d(stype.gunMount[num].pos); m_gunTemperature[num] += 0.01f; dir = m.ApplyRotationOnly(dir); pos = m.ApplyRotationOnly(pos); pos += GetPosition(); Equip::Type t = m_equipment.Get(Equip::SLOT_LASER, num); const LaserType < = Equip::lasers[Equip::types[t].tableIndex]; m_gunRecharge[num] = lt.rechargeTime; vector3d baseVel = GetVelocity(); vector3d dirVel = lt.speed * dir.Normalized(); if (lt.flags & Equip::LASER_DUAL) { vector3d sep = dir.Cross(vector3d(m[4],m[5],m[6])).Normalized(); Projectile::Add(this, t, pos+5.0*sep, baseVel, dirVel); Projectile::Add(this, t, pos-5.0*sep, baseVel, dirVel); } else Projectile::Add(this, t, pos, baseVel, dirVel); /* // trace laser beam through frame to see who it hits CollisionContact c; GetFrame()->GetCollisionSpace()->TraceRay(pos, dir, 10000.0, &c, this->GetGeom()); if (c.userData1) { Body *hit = static_cast<Body*>(c.userData1); hit->OnDamage(this, damage); } */ Polit::NotifyOfCrime(this, Polit::CRIME_WEAPON_DISCHARGE); Sound::BodyMakeNoise(this, "Pulse_Laser", 1.0f); }
// Input: direction in ship's frame, doesn't need to be normalized // Approximate positive angular velocity at match point // Applies thrust directly // old: returns whether it can reach that direction in this frame // returns angle to target double Ship::AIFaceDirection(const vector3d &dir, double av) { double timeStep = Pi::game->GetTimeStep(); double maxAccel = GetShipType().angThrust / GetAngularInertia(); // should probably be in stats anyway if (maxAccel <= 0.0) return 0.0; double frameAccel = maxAccel * timeStep; matrix4x4d rot; GetRotMatrix(rot); vector3d head = (dir * rot).Normalized(); // create desired object-space heading vector3d dav(0.0, 0.0, 0.0); // desired angular velocity double ang = 0.0; if (head.z > -0.99999999) { ang = acos (Clamp(-head.z, -1.0, 1.0)); // scalar angle from head to curhead double iangvel = av + sqrt (2.0 * maxAccel * ang); // ideal angvel at current time double frameEndAV = iangvel - frameAccel; if (frameEndAV <= 0.0) iangvel = ang / timeStep; // last frame discrete correction else iangvel = (iangvel + frameEndAV) * 0.5; // discrete overshoot correction // Normalize (head.x, head.y) to give desired angvel direction if (head.z > 0.999999) head.x = 1.0; double head2dnorm = 1.0 / sqrt(head.x*head.x + head.y*head.y); // NAN fix shouldn't be necessary if inputs are normalized dav.x = head.y * head2dnorm * iangvel; dav.y = -head.x * head2dnorm * iangvel; } vector3d cav = (GetAngVelocity() - GetFrame()->GetAngVelocity()) * rot; // current obj-rel angvel // vector3d cav = GetAngVelocity() * rot; // current obj-rel angvel vector3d diff = (dav - cav) / frameAccel; // find diff between current & desired angvel // If the player is pressing a roll key, don't override roll. // XXX this really shouldn't be here. a better way would be to have a // field in Ship describing the wanted angvel adjustment from input. the // baseclass version in Ship would always be 0. the version in Player // would be constructed from user input. that adjustment could then be // considered by this method when computing the required change if (IsType(Object::PLAYER) && (KeyBindings::rollLeft.IsActive() || KeyBindings::rollRight.IsActive())) diff.z = m_angThrusters.z; SetAngThrusterState(diff); return ang; }
void Ship::AIModelCoordsMatchAngVel(vector3d desiredAngVel, double softness) { const ShipType &stype = GetShipType(); double angAccel = stype.angThrust / GetAngularInertia(); const double softTimeStep = Pi::GetTimeStep() * softness; matrix4x4d rot; GetRotMatrix(rot); vector3d angVel = desiredAngVel - rot.InverseOf() * GetAngVelocity(); vector3d thrust; for (int axis=0; axis<3; axis++) { if (angAccel * softTimeStep >= fabs(angVel[axis])) { thrust[axis] = angVel[axis] / (softTimeStep * angAccel); } else { thrust[axis] = (angVel[axis] > 0.0 ? 1.0 : -1.0); } } SetAngThrusterState(thrust); }
void Ship::TimeStepUpdate(const float timeStep) { // If docked, station is responsible for updating position/orient of ship // but we call this crap anyway and hope it doesn't do anything bad const vector3d thrust(m_thrusters * GetMaxThrust(m_thrusters)); AddRelForce(thrust); AddRelTorque(GetShipType()->angThrust * m_angThrusters); if (m_landingGearAnimation) m_landingGearAnimation->SetProgress(m_wheelState); m_dragCoeff = DynamicBody::DEFAULT_DRAG_COEFF * (1.0 + 0.25 * m_wheelState); DynamicBody::TimeStepUpdate(timeStep); // fuel use decreases mass, so do this as the last thing in the frame UpdateFuel(timeStep, thrust); m_navLights->SetEnabled(m_wheelState > 0.01f); m_navLights->Update(timeStep); if (m_sensors.get()) m_sensors->Update(timeStep); }
// Input: direction in ship's frame, doesn't need to be normalized // Approximate positive angular velocity at match point // Applies thrust directly // old: returns whether it can reach that direction in this frame // returns angle to target double Ship::AIFaceDirection(const vector3d &dir, double av) { double timeStep = Pi::GetTimeStep(); double maxAccel = GetShipType().angThrust / GetAngularInertia(); // should probably be in stats anyway if (maxAccel <= 0.0) // happens if no angular thrust is set for the model eg MISSILE_UNGUIDED return 0.0; double frameAccel = maxAccel * timeStep; matrix4x4d rot; GetRotMatrix(rot); vector3d head = (dir * rot).Normalized(); // create desired object-space heading vector3d dav(0.0, 0.0, 0.0); // desired angular velocity double ang = 0.0; if (head.z > -0.99999999) { ang = acos (Clamp(-head.z, -1.0, 1.0)); // scalar angle from head to curhead double iangvel = av + sqrt (2.0 * maxAccel * ang); // ideal angvel at current time double frameEndAV = iangvel - frameAccel; if (frameEndAV <= 0.0) iangvel = ang / timeStep; // last frame discrete correction else iangvel = (iangvel + frameEndAV) * 0.5; // discrete overshoot correction // Normalize (head.x, head.y) to give desired angvel direction if (head.z > 0.9999) head.x = 1.0; double head2dnorm = 1.0 / sqrt(head.x*head.x + head.y*head.y); // NAN fix shouldn't be necessary if inputs are normalized dav.x = head.y * head2dnorm * iangvel; dav.y = -head.x * head2dnorm * iangvel; } vector3d cav = (GetAngVelocity() - GetFrame()->GetAngVelocity()) * rot; // current obj-rel angvel // vector3d cav = GetAngVelocity() * rot; // current obj-rel angvel vector3d diff = (dav - cav) / frameAccel; // find diff between current & desired angvel SetAngThrusterState(diff); return ang; //if (diff.x*diff.x > 1.0 || diff.y*diff.y > 1.0 || diff.z*diff.z > 1.0) return false; // else return true; }
void Ship::UpdateEquipStats() { const ShipType &stype = GetShipType(); m_stats.max_capacity = stype.capacity; m_stats.used_capacity = 0; m_stats.used_cargo = 0; for (int i=0; i<Equip::SLOT_MAX; i++) { for (int j=0; j<stype.equipSlotCapacity[i]; j++) { Equip::Type t = m_equipment.Get(Equip::Slot(i), j); if (t) m_stats.used_capacity += Equip::types[t].mass; if (Equip::Slot(i) == Equip::SLOT_CARGO) m_stats.used_cargo += Equip::types[t].mass; } } m_stats.free_capacity = m_stats.max_capacity - m_stats.used_capacity; m_stats.total_mass = m_stats.used_capacity + stype.hullMass; m_stats.shield_mass = TONS_HULL_PER_SHIELD * float(m_equipment.Count(Equip::SLOT_SHIELD, Equip::SHIELD_GENERATOR)); UpdateMass(); UpdateFuelStats(); Equip::Type fuelType = GetHyperdriveFuelType(); if (stype.equipSlotCapacity[Equip::SLOT_ENGINE]) { Equip::Type t = m_equipment.Get(Equip::SLOT_ENGINE); int hyperclass = Equip::types[t].pval; if (!hyperclass) { // no drive m_stats.hyperspace_range = m_stats.hyperspace_range_max = 0; } else { m_stats.hyperspace_range_max = Pi::CalcHyperspaceRangeMax(hyperclass, GetMass()/1000); m_stats.hyperspace_range = Pi::CalcHyperspaceRange(hyperclass, GetMass()/1000, m_equipment.Count(Equip::SLOT_CARGO, fuelType)); } } else { m_stats.hyperspace_range = m_stats.hyperspace_range_max = 0; } }
const shipstats_t *Ship::CalcStats() { const ShipType &stype = GetShipType(); m_stats.max_capacity = stype.capacity; m_stats.used_capacity = 0; m_stats.used_cargo = 0; Equip::Type fuelType = GetHyperdriveFuelType(); for (int i=0; i<Equip::SLOT_MAX; i++) { for (int j=0; j<stype.equipSlotCapacity[i]; j++) { Equip::Type t = m_equipment.Get(Equip::Slot(i), j); if (t) m_stats.used_capacity += EquipType::types[t].mass; if (Equip::Slot(i) == Equip::SLOT_CARGO) m_stats.used_cargo += EquipType::types[t].mass; } } m_stats.free_capacity = m_stats.max_capacity - m_stats.used_capacity; m_stats.total_mass = m_stats.used_capacity + stype.hullMass; m_stats.shield_mass = TONS_HULL_PER_SHIELD * float(m_equipment.Count(Equip::SLOT_SHIELD, Equip::SHIELD_GENERATOR)); if (stype.equipSlotCapacity[Equip::SLOT_ENGINE]) { Equip::Type t = m_equipment.Get(Equip::SLOT_ENGINE); int hyperclass = EquipType::types[t].pval; if (!hyperclass) { // no drive m_stats.hyperspace_range = m_stats.hyperspace_range_max = 0; } else { // for the sake of hyperspace range, we count ships mass as 60% of original. m_stats.hyperspace_range_max = Pi::CalcHyperspaceRange(hyperclass, m_stats.total_mass); m_stats.hyperspace_range = std::min(m_stats.hyperspace_range_max, m_stats.hyperspace_range_max * m_equipment.Count(Equip::SLOT_CARGO, fuelType) / (hyperclass * hyperclass)); } } else { m_stats.hyperspace_range = m_stats.hyperspace_range_max = 0; } return &m_stats; }
void Ship::LoadFromJson(const Json::Value &jsonObj, Space *space) { DynamicBody::LoadFromJson(jsonObj, space); if (!jsonObj.isMember("ship")) throw SavedGameCorruptException(); Json::Value shipObj = jsonObj["ship"]; if (!shipObj.isMember("ang_thrusters")) throw SavedGameCorruptException(); if (!shipObj.isMember("thrusters")) throw SavedGameCorruptException(); if (!shipObj.isMember("wheel_transition")) throw SavedGameCorruptException(); if (!shipObj.isMember("wheel_state")) throw SavedGameCorruptException(); if (!shipObj.isMember("launch_lock_timeout")) throw SavedGameCorruptException(); if (!shipObj.isMember("test_landed")) throw SavedGameCorruptException(); if (!shipObj.isMember("flight_state")) throw SavedGameCorruptException(); if (!shipObj.isMember("alert_state")) throw SavedGameCorruptException(); if (!shipObj.isMember("last_firing_alert")) throw SavedGameCorruptException(); if (!shipObj.isMember("hyperspace_destination")) throw SavedGameCorruptException(); if (!shipObj.isMember("hyperspace_countdown")) throw SavedGameCorruptException(); if (!shipObj.isMember("guns")) throw SavedGameCorruptException(); if (!shipObj.isMember("ecm_recharge")) throw SavedGameCorruptException(); if (!shipObj.isMember("ship_type_id")) throw SavedGameCorruptException(); if (!shipObj.isMember("docked_with_port")) throw SavedGameCorruptException(); if (!shipObj.isMember("index_for_body_docked_with")) throw SavedGameCorruptException(); if (!shipObj.isMember("hull_mass_left")) throw SavedGameCorruptException(); if (!shipObj.isMember("shield_mass_left")) throw SavedGameCorruptException(); if (!shipObj.isMember("shield_cooldown")) throw SavedGameCorruptException(); if (!shipObj.isMember("ai_message")) throw SavedGameCorruptException(); if (!shipObj.isMember("thruster_fuel")) throw SavedGameCorruptException(); if (!shipObj.isMember("reserve_fuel")) throw SavedGameCorruptException(); if (!shipObj.isMember("controller_type")) throw SavedGameCorruptException(); if (!shipObj.isMember("name")) throw SavedGameCorruptException(); m_skin.LoadFromJson(shipObj); m_skin.Apply(GetModel()); // needs fixups JsonToVector(&m_angThrusters, shipObj, "ang_thrusters"); JsonToVector(&m_thrusters, shipObj, "thrusters"); m_wheelTransition = shipObj["wheel_transition"].asInt(); m_wheelState = StrToFloat(shipObj["wheel_state"].asString()); m_launchLockTimeout = StrToFloat(shipObj["launch_lock_timeout"].asString()); m_testLanded = shipObj["test_landed"].asBool(); m_flightState = static_cast<FlightState>(shipObj["flight_state"].asInt()); m_alertState = static_cast<AlertState>(shipObj["alert_state"].asInt()); Properties().Set("flightState", EnumStrings::GetString("ShipFlightState", m_flightState)); Properties().Set("alertStatus", EnumStrings::GetString("ShipAlertStatus", m_alertState)); m_lastFiringAlert = StrToDouble(shipObj["last_firing_alert"].asString()); Json::Value hyperspaceDestObj = shipObj["hyperspace_destination"]; m_hyperspace.dest = SystemPath::FromJson(hyperspaceDestObj); m_hyperspace.countdown = StrToFloat(shipObj["hyperspace_countdown"].asString()); m_hyperspace.duration = 0; Json::Value gunArray = shipObj["guns"]; if (!gunArray.isArray()) throw SavedGameCorruptException(); assert(ShipType::GUNMOUNT_MAX == gunArray.size()); for (unsigned int i = 0; i < ShipType::GUNMOUNT_MAX; i++) { Json::Value gunArrayEl = gunArray[i]; if (!gunArrayEl.isMember("state")) throw SavedGameCorruptException(); if (!gunArrayEl.isMember("recharge")) throw SavedGameCorruptException(); if (!gunArrayEl.isMember("temperature")) throw SavedGameCorruptException(); m_gun[i].state = gunArrayEl["state"].asUInt(); m_gun[i].recharge = StrToFloat(gunArrayEl["recharge"].asString()); m_gun[i].temperature = StrToFloat(gunArrayEl["temperature"].asString()); } m_ecmRecharge = StrToFloat(shipObj["ecm_recharge"].asString()); SetShipId(shipObj["ship_type_id"].asString()); // XXX handle missing thirdparty ship m_dockedWithPort = shipObj["docked_with_port"].asInt(); m_dockedWithIndex = shipObj["index_for_body_docked_with"].asUInt(); Init(); m_stats.hull_mass_left = StrToFloat(shipObj["hull_mass_left"].asString()); // must be after Init()... m_stats.shield_mass_left = StrToFloat(shipObj["shield_mass_left"].asString()); m_shieldCooldown = StrToFloat(shipObj["shield_cooldown"].asString()); m_curAICmd = 0; m_curAICmd = AICommand::LoadFromJson(shipObj); m_aiMessage = AIError(shipObj["ai_message"].asInt()); SetFuel(StrToDouble(shipObj["thruster_fuel"].asString())); m_stats.fuel_tank_mass_left = GetShipType()->fuelTankMass * GetFuel(); m_reserveFuel = StrToDouble(shipObj["reserve_fuel"].asString()); PropertyMap &p = Properties(); p.Set("hullMassLeft", m_stats.hull_mass_left); p.Set("hullPercent", 100.0f * (m_stats.hull_mass_left / float(m_type->hullMass))); p.Set("shieldMassLeft", m_stats.shield_mass_left); p.Set("fuelMassLeft", m_stats.fuel_tank_mass_left); p.PushLuaTable(); lua_State *l = Lua::manager->GetLuaState(); lua_getfield(l, -1, "equipSet"); m_equipSet = LuaRef(l, -1); lua_pop(l, 2); UpdateLuaStats(); m_controller = 0; const ShipController::Type ctype = static_cast<ShipController::Type>(shipObj["controller_type"].asInt()); if (ctype == ShipController::PLAYER) SetController(new PlayerShipController()); else SetController(new ShipController()); m_controller->LoadFromJson(shipObj); m_navLights->LoadFromJson(shipObj); m_shipName = shipObj["name"].asString(); Properties().Set("shipName", m_shipName); }
// returns speed that can be reached using fuel minus reserve according to the Tsiolkovsky equation double Ship::GetSpeedReachedWithFuel() const { const double fuelmass = 1000*GetShipType()->fuelTankMass * (m_thrusterFuel - m_reserveFuel); if (fuelmass < 0) return 0.0; return GetShipType()->effectiveExhaustVelocity * log(GetMass()/(GetMass()-fuelmass)); }
void Ship::UpdateMass() { SetMass((m_stats.static_mass + GetFuel()*GetShipType()->fuelTankMass)*1000); }