279 lines
8.3 KiB
C++
279 lines
8.3 KiB
C++
// Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details
|
|
// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
|
|
|
|
#include "FixedGuns.h"
|
|
#include "Beam.h"
|
|
#include "DynamicBody.h"
|
|
#include "GameSaveError.h"
|
|
#include "Projectile.h"
|
|
#include "Quaternion.h"
|
|
#include "StringF.h"
|
|
#include "libs.h"
|
|
#include "scenegraph/MatrixTransform.h"
|
|
#include "vector3.h"
|
|
|
|
FixedGuns::FixedGuns()
|
|
{
|
|
}
|
|
|
|
FixedGuns::~FixedGuns()
|
|
{
|
|
}
|
|
|
|
bool FixedGuns::IsFiring()
|
|
{
|
|
bool gunstate = false;
|
|
for (int j = 0; j < Guns::GUNMOUNT_MAX; j++)
|
|
gunstate |= m_is_firing[j];
|
|
return gunstate;
|
|
}
|
|
|
|
bool FixedGuns::IsFiring(const int num)
|
|
{
|
|
return m_is_firing[num];
|
|
}
|
|
|
|
bool FixedGuns::IsBeam(const int num)
|
|
{
|
|
return m_gun[num].projData.beam;
|
|
}
|
|
|
|
void FixedGuns::Init(DynamicBody *b)
|
|
{
|
|
for (int i = 0; i < Guns::GUNMOUNT_MAX; i++) {
|
|
// Initialize structs
|
|
m_is_firing[i] = false;
|
|
m_gun[i].recharge = 0;
|
|
m_gun[i].temp_heat_rate = 0;
|
|
m_gun[i].temp_cool_rate = 0;
|
|
m_gun[i].projData.lifespan = 0;
|
|
m_gun[i].projData.damage = 0;
|
|
m_gun[i].projData.length = 0;
|
|
m_gun[i].projData.width = 0;
|
|
m_gun[i].projData.mining = false;
|
|
m_gun[i].projData.speed = 0;
|
|
m_gun[i].projData.color = Color::BLACK;
|
|
// Set there's no guns:
|
|
m_gun_present[i] = false;
|
|
m_recharge_stat[i] = 0.0;
|
|
m_temperature_stat[i] = 0.0;
|
|
}
|
|
b->AddFeature(DynamicBody::FIXED_GUNS);
|
|
}
|
|
|
|
void FixedGuns::SaveToJson(Json &jsonObj, Space *space)
|
|
{
|
|
|
|
Json gunArray = Json::array(); // Create JSON array to contain gun data.
|
|
|
|
for (int i = 0; i < Guns::GUNMOUNT_MAX; i++) {
|
|
Json gunArrayEl = Json::object(); // Create JSON object to contain gun.
|
|
gunArrayEl["state"] = m_is_firing[i];
|
|
gunArrayEl["recharge"] = m_recharge_stat[i];
|
|
gunArrayEl["temperature"] = m_temperature_stat[i];
|
|
gunArray.push_back(gunArrayEl); // Append gun object to array.
|
|
}
|
|
jsonObj["guns"] = gunArray; // Add gun array to ship object.
|
|
}
|
|
|
|
void FixedGuns::LoadFromJson(const Json &jsonObj, Space *space)
|
|
{
|
|
Json gunArray = jsonObj["guns"].get<Json::array_t>();
|
|
assert(Guns::GUNMOUNT_MAX == gunArray.size());
|
|
|
|
try {
|
|
for (unsigned int i = 0; i < Guns::GUNMOUNT_MAX; i++) {
|
|
Json gunArrayEl = gunArray[i];
|
|
|
|
m_is_firing[i] = gunArrayEl["state"];
|
|
m_recharge_stat[i] = gunArrayEl["recharge"];
|
|
m_temperature_stat[i] = gunArrayEl["temperature"];
|
|
}
|
|
} catch (Json::type_error &) {
|
|
throw SavedGameCorruptException();
|
|
}
|
|
}
|
|
|
|
void FixedGuns::InitGuns(SceneGraph::Model *m)
|
|
{
|
|
for (int num = 0; num < Guns::GUNMOUNT_MAX; num++) {
|
|
int found = 0;
|
|
// probably 4 is fine 99% of the time (X-Wings)
|
|
m_gun[num].locs.reserve(4);
|
|
// 32 is a crazy number...
|
|
for (int gun = 0; gun < 32; gun++) {
|
|
const std::string tag = stringf("tag_gunmount_%0{d}_multi_%1{d}", num, gun); //"gunmount_0_multi_0";
|
|
const SceneGraph::MatrixTransform *mt = m->FindTagByName(tag);
|
|
if (mt) {
|
|
++found;
|
|
const matrix4x4f &trans = mt->GetTransform();
|
|
GunData::GunLoc loc;
|
|
loc.pos = vector3d(trans.GetTranslate());
|
|
loc.dir = vector3d(trans.GetOrient().VectorZ());
|
|
m_gun[num].locs.push_back(loc);
|
|
} else if (found == 0) {
|
|
// look for legacy "tag_gunmount_0" or "tag_gunmount_1" tags
|
|
const std::string tag = stringf("tag_gunmount_%0{d}", num); //"gunmount_0";
|
|
const SceneGraph::MatrixTransform *mt = m->FindTagByName(tag);
|
|
if (mt) {
|
|
++found;
|
|
const matrix4x4f &trans = mt->GetTransform();
|
|
GunData::GunLoc loc;
|
|
loc.pos = vector3d(trans.GetTranslate());
|
|
loc.dir = vector3d(trans.GetOrient().VectorZ());
|
|
m_gun[num].locs.push_back(loc);
|
|
}
|
|
break; // definitely no more "gun"s for this "num" if we've come down this path
|
|
} else
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FixedGuns::MountGun(const int num, const float recharge, const float lifespan, const float damage, const float length,
|
|
const float width, const bool mining, const Color &color, const float speed, const bool beam, const float heatrate, const float coolrate)
|
|
{
|
|
if (num >= Guns::GUNMOUNT_MAX)
|
|
return;
|
|
// Here we have projectile data MORE recharge time
|
|
m_is_firing[num] = false;
|
|
m_gun[num].recharge = recharge;
|
|
m_gun[num].temp_heat_rate = heatrate; // TODO: More fun if you have a variable "speed" for temperature
|
|
m_gun[num].temp_cool_rate = coolrate;
|
|
m_gun[num].projData.lifespan = lifespan;
|
|
m_gun[num].projData.damage = damage;
|
|
m_gun[num].projData.length = length;
|
|
m_gun[num].projData.width = width;
|
|
m_gun[num].projData.mining = mining;
|
|
m_gun[num].projData.color = color;
|
|
m_gun[num].projData.speed = speed;
|
|
m_gun[num].projData.beam = beam;
|
|
m_gun_present[num] = true;
|
|
}
|
|
|
|
void FixedGuns::UnMountGun(int num)
|
|
{
|
|
if (num >= Guns::GUNMOUNT_MAX)
|
|
return;
|
|
if (!m_gun_present[num])
|
|
return;
|
|
m_is_firing[num] = false;
|
|
m_gun[num].recharge = 0;
|
|
m_gun[num].temp_heat_rate = 0;
|
|
m_gun[num].temp_cool_rate = 0;
|
|
m_gun[num].projData.lifespan = 0;
|
|
m_gun[num].projData.damage = 0;
|
|
m_gun[num].projData.length = 0;
|
|
m_gun[num].projData.width = 0;
|
|
m_gun[num].projData.mining = false;
|
|
m_gun[num].projData.speed = 0;
|
|
m_gun[num].projData.color = Color::BLACK;
|
|
m_gun_present[num] = false;
|
|
}
|
|
|
|
void FixedGuns::SetGunFiringState(int idx, int s)
|
|
{
|
|
if (m_gun_present[idx])
|
|
m_is_firing[idx] = s;
|
|
}
|
|
|
|
bool FixedGuns::Fire(int num, Body *b)
|
|
{
|
|
if (!m_gun_present[num]) return false;
|
|
if (!m_is_firing[num]) return false;
|
|
// Output("Firing gun %i, present\n", num);
|
|
// Output(" is firing\n");
|
|
if (m_recharge_stat[num] > 0) return false;
|
|
// Output(" recharge stat <= 0\n");
|
|
if (m_temperature_stat[num] > 1.0) return false;
|
|
// Output(" temperature stat <= 1.0\n");
|
|
|
|
m_temperature_stat[num] += m_gun[num].temp_heat_rate;
|
|
m_recharge_stat[num] = m_gun[num].recharge;
|
|
|
|
const matrix3x3d leadRot = m_shouldUseLeadCalc ? Quaterniond(m_currentLeadDir).ToMatrix3x3<double>() : matrix3x3d::Identity();
|
|
|
|
const int maxBarrels = std::min(size_t(m_gun[num].dual ? 2 : 1), m_gun[num].locs.size());
|
|
|
|
for (int iBarrel = 0; iBarrel < maxBarrels; iBarrel++) {
|
|
const vector3d dir = (b->GetOrient() * leadRot * vector3d(m_gun[num].locs[iBarrel].dir)).Normalized();
|
|
const vector3d pos = b->GetOrient() * vector3d(m_gun[num].locs[iBarrel].pos) + b->GetPosition();
|
|
|
|
if (m_gun[num].projData.beam) {
|
|
Beam::Add(b, m_gun[num].projData, pos, b->GetVelocity(), dir);
|
|
} else {
|
|
const vector3d dirVel = m_gun[num].projData.speed * dir;
|
|
Projectile::Add(b, m_gun[num].projData, pos, b->GetVelocity(), dirVel);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void FixedGuns::UpdateGuns(float timeStep)
|
|
{
|
|
for (int i = 0; i < Guns::GUNMOUNT_MAX; i++) {
|
|
if (!m_gun_present[i])
|
|
continue;
|
|
|
|
m_recharge_stat[i] -= timeStep;
|
|
|
|
float rateCooling = m_gun[i].temp_cool_rate;
|
|
rateCooling *= m_cooler_boost;
|
|
m_temperature_stat[i] -= rateCooling * timeStep;
|
|
|
|
if (m_temperature_stat[i] < 0.0f)
|
|
m_temperature_stat[i] = 0;
|
|
else if (m_temperature_stat[i] > 1.0f)
|
|
m_is_firing[i] = false;
|
|
|
|
if (m_recharge_stat[i] < 0.0f)
|
|
m_recharge_stat[i] = 0;
|
|
}
|
|
}
|
|
|
|
static constexpr double MAX_LEAD_ANGLE = DEG2RAD(1.5);
|
|
|
|
void FixedGuns::UpdateLead(float timeStep, int num, Body *ship, Body *target)
|
|
{
|
|
assert(num < GUNMOUNT_MAX);
|
|
const vector3d forwardVector = num == GUN_REAR ? vector3d(0, 0, 1) : vector3d(0, 0, -1);
|
|
m_targetLeadPos = forwardVector;
|
|
|
|
if (!target) {
|
|
m_currentLeadDir = forwardVector;
|
|
return;
|
|
}
|
|
|
|
const vector3d targpos = target->GetPositionRelTo(ship) * ship->GetOrient();
|
|
|
|
// calculate firing solution and relative velocity along our z axis
|
|
double projspeed = m_gun[num].projData.speed;
|
|
|
|
// don't calculate lead if there's no gun there
|
|
if (m_gun_present[num] && projspeed > 0) {
|
|
const vector3d targvel = target->GetVelocityRelTo(ship) * ship->GetOrient();
|
|
vector3d leadpos = targpos + targvel * (targpos.Length() / projspeed);
|
|
leadpos = targpos + targvel * (leadpos.Length() / projspeed); // second order approx
|
|
|
|
m_targetLeadPos = leadpos;
|
|
}
|
|
|
|
const vector3d targetDir = m_targetLeadPos.Normalized();
|
|
const vector3d gunLeadTarget = (targetDir.Dot(forwardVector) >= cos(MAX_LEAD_ANGLE)) ? targetDir : forwardVector;
|
|
// FIXME: for some unearthly reason, pointing directly at the lead target causes projectiles to overshoot by 2x
|
|
// So we just interpolate by exactly half and it works perfectly. This is a pretty benign hack, all considered.
|
|
Quaterniond interpTarget = Quaterniond::Slerp(Quaterniond(gunLeadTarget), Quaterniond(forwardVector), 0.5);
|
|
|
|
double angle;
|
|
interpTarget.GetAxisAngle(angle, m_currentLeadDir);
|
|
}
|
|
|
|
float FixedGuns::GetGunTemperature(int idx) const
|
|
{
|
|
if (m_gun_present[idx])
|
|
return m_temperature_stat[idx];
|
|
else
|
|
return 0.0f;
|
|
}
|