337 lines
8.8 KiB
C++
337 lines
8.8 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 "Player.h"
|
|
|
|
#include "FixedGuns.h"
|
|
#include "Frame.h"
|
|
#include "Game.h"
|
|
#include "GameConfig.h"
|
|
#include "GameLog.h"
|
|
#include "HyperspaceCloud.h"
|
|
#include "Lang.h"
|
|
#include "Pi.h"
|
|
#include "SectorView.h"
|
|
#include "Sfx.h"
|
|
#include "SpaceStation.h"
|
|
#include "StringF.h"
|
|
#include "SystemView.h" // for the transfer planner
|
|
#include "WorldView.h"
|
|
#include "lua/LuaObject.h"
|
|
#include "ship/PlayerShipController.h"
|
|
#include "sound/Sound.h"
|
|
|
|
//Some player specific sounds
|
|
static Sound::Event s_soundUndercarriage;
|
|
static Sound::Event s_soundHyperdrive;
|
|
|
|
static int onEquipChangeListener(lua_State *l)
|
|
{
|
|
Player *p = LuaObject<Player>::GetFromLua(lua_upvalueindex(1));
|
|
p->onChangeEquipment.emit();
|
|
return 0;
|
|
}
|
|
|
|
static void registerEquipChangeListener(Player *player)
|
|
{
|
|
lua_State *l = Lua::manager->GetLuaState();
|
|
LUA_DEBUG_START(l);
|
|
|
|
LuaObject<Player>::PushToLua(player);
|
|
lua_pushcclosure(l, onEquipChangeListener, 1);
|
|
LuaRef lr(Lua::manager->GetLuaState(), -1);
|
|
ScopedTable(player->GetEquipSet()).CallMethod("AddListener", lr);
|
|
lua_pop(l, 1);
|
|
|
|
LUA_DEBUG_END(l, 0);
|
|
}
|
|
|
|
Player::Player(const ShipType::Id &shipId) :
|
|
Ship(shipId)
|
|
{
|
|
SetController(new PlayerShipController());
|
|
InitCockpit();
|
|
GetFixedGuns()->SetShouldUseLeadCalc(true);
|
|
registerEquipChangeListener(this);
|
|
}
|
|
|
|
Player::Player(const Json &jsonObj, Space *space) :
|
|
Ship(jsonObj, space)
|
|
{
|
|
InitCockpit();
|
|
GetFixedGuns()->SetShouldUseLeadCalc(true);
|
|
registerEquipChangeListener(this);
|
|
}
|
|
|
|
void Player::SetShipType(const ShipType::Id &shipId)
|
|
{
|
|
Ship::SetShipType(shipId);
|
|
registerEquipChangeListener(this);
|
|
InitCockpit();
|
|
}
|
|
|
|
void Player::SaveToJson(Json &jsonObj, Space *space)
|
|
{
|
|
Ship::SaveToJson(jsonObj, space);
|
|
}
|
|
|
|
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));
|
|
|
|
OnCockpitActivated();
|
|
}
|
|
|
|
bool Player::DoDamage(float kgDamage)
|
|
{
|
|
bool r = Ship::DoDamage(kgDamage);
|
|
|
|
// Don't fire audio on EVERY iteration (aka every 16ms, or 60fps), only when exceeds a value randomly
|
|
const float dam = kgDamage * 0.01f;
|
|
if (Pi::rng.Double() < dam) {
|
|
if (!IsDead() && (GetPercentHull() < 25.0f)) {
|
|
Sound::BodyMakeNoise(this, "warning", .5f);
|
|
}
|
|
if (dam < (0.01 * float(GetShipType()->hullMass)))
|
|
Sound::BodyMakeNoise(this, "Hull_hit_Small", 1.0f);
|
|
else
|
|
Sound::BodyMakeNoise(this, "Hull_Hit_Medium", 1.0f);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
//XXX perhaps remove this, the sound is very annoying
|
|
bool Player::OnDamage(Body *attacker, float kgDamage, const CollisionContact &contactData)
|
|
{
|
|
bool r = Ship::OnDamage(attacker, kgDamage, contactData);
|
|
if (!IsDead() && (GetPercentHull() < 25.0f)) {
|
|
Sound::BodyMakeNoise(this, "warning", .5f);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
//XXX handle killcounts in lua
|
|
void Player::SetDockedWith(SpaceStation *s, int port)
|
|
{
|
|
Ship::SetDockedWith(s, port);
|
|
}
|
|
|
|
//XXX all ships should make this sound
|
|
bool Player::SetWheelState(bool down)
|
|
{
|
|
bool did = Ship::SetWheelState(down);
|
|
if (did) {
|
|
s_soundUndercarriage.Play(down ? "UC_out" : "UC_in", 1.0f, 1.0f, 0);
|
|
}
|
|
return did;
|
|
}
|
|
|
|
//XXX all ships should make this sound
|
|
Missile *Player::SpawnMissile(ShipType::Id missile_type, int power)
|
|
{
|
|
Missile *m = Ship::SpawnMissile(missile_type, power);
|
|
if (m)
|
|
Sound::PlaySfx("Missile_launch", 1.0f, 1.0f, 0);
|
|
return m;
|
|
}
|
|
|
|
//XXX do in lua, or use the alert concept for all ships
|
|
void Player::SetAlertState(Ship::AlertState as)
|
|
{
|
|
Ship::AlertState prev = GetAlertState();
|
|
|
|
switch (as) {
|
|
case ALERT_NONE:
|
|
if (prev != ALERT_NONE)
|
|
Pi::game->log->Add(Lang::ALERT_CANCELLED);
|
|
break;
|
|
|
|
case ALERT_SHIP_NEARBY:
|
|
if (prev == ALERT_NONE)
|
|
Pi::game->log->Add(Lang::SHIP_DETECTED_NEARBY);
|
|
else
|
|
Pi::game->log->Add(Lang::DOWNGRADING_ALERT_STATUS);
|
|
Sound::PlaySfx("OK");
|
|
break;
|
|
|
|
case ALERT_SHIP_FIRING:
|
|
Pi::game->log->Add(Lang::LASER_FIRE_DETECTED);
|
|
Sound::PlaySfx("warning", 0.2f, 0.2f, 0);
|
|
break;
|
|
|
|
case ALERT_MISSILE_DETECTED:
|
|
Pi::game->log->Add(Lang::MISSILE_DETECTED);
|
|
Sound::PlaySfx("warning", 0.2f, 0.2f, 0);
|
|
break;
|
|
}
|
|
|
|
Ship::SetAlertState(as);
|
|
}
|
|
|
|
void Player::NotifyRemoved(const Body *const removedBody)
|
|
{
|
|
if (GetNavTarget() == removedBody)
|
|
SetNavTarget(0);
|
|
|
|
if (GetCombatTarget() == removedBody) {
|
|
SetCombatTarget(0);
|
|
|
|
if (!GetNavTarget() && removedBody->IsType(ObjectType::SHIP))
|
|
SetNavTarget(static_cast<const Ship *>(removedBody)->GetHyperspaceCloud());
|
|
}
|
|
|
|
if (GetSetSpeedTarget() == removedBody)
|
|
SetSetSpeedTarget(0);
|
|
|
|
Ship::NotifyRemoved(removedBody);
|
|
}
|
|
|
|
//XXX ui stuff
|
|
void Player::OnEnterHyperspace()
|
|
{
|
|
s_soundHyperdrive.Play(m_hyperspace.sounds.jump_sound.c_str());
|
|
SetNavTarget(0);
|
|
SetCombatTarget(0);
|
|
SetSetSpeedTarget(0);
|
|
|
|
m_controller->SetFlightControlState(CONTROL_MANUAL); //could set CONTROL_HYPERDRIVE
|
|
ClearThrusterState();
|
|
Pi::game->WantHyperspace();
|
|
}
|
|
|
|
void Player::OnEnterSystem()
|
|
{
|
|
m_controller->SetFlightControlState(CONTROL_MANUAL);
|
|
//XXX don't call sectorview from here, use signals instead
|
|
Pi::game->GetSectorView()->ResetHyperspaceTarget();
|
|
}
|
|
|
|
//temporary targeting stuff
|
|
PlayerShipController *Player::GetPlayerController() const
|
|
{
|
|
return static_cast<PlayerShipController *>(GetController());
|
|
}
|
|
|
|
Body *Player::GetCombatTarget() const
|
|
{
|
|
return static_cast<PlayerShipController *>(m_controller)->GetCombatTarget();
|
|
}
|
|
|
|
Body *Player::GetNavTarget() const
|
|
{
|
|
return static_cast<PlayerShipController *>(m_controller)->GetNavTarget();
|
|
}
|
|
|
|
Body *Player::GetSetSpeedTarget() const
|
|
{
|
|
return static_cast<PlayerShipController *>(m_controller)->GetSetSpeedTarget();
|
|
}
|
|
|
|
void Player::SetCombatTarget(Body *const target, bool setSpeedTo)
|
|
{
|
|
static_cast<PlayerShipController *>(m_controller)->SetCombatTarget(target, setSpeedTo);
|
|
}
|
|
|
|
void Player::SetNavTarget(Body *const target)
|
|
{
|
|
static_cast<PlayerShipController *>(m_controller)->SetNavTarget(target);
|
|
}
|
|
|
|
void Player::SetSetSpeedTarget(Body *const target)
|
|
{
|
|
static_cast<PlayerShipController *>(m_controller)->SetSetSpeedTarget(target);
|
|
}
|
|
|
|
void Player::ChangeSetSpeed(double delta)
|
|
{
|
|
static_cast<PlayerShipController *>(m_controller)->ChangeSetSpeed(delta);
|
|
}
|
|
|
|
//temporary targeting stuff ends
|
|
|
|
Ship::HyperjumpStatus Player::InitiateHyperjumpTo(const SystemPath &dest, int warmup_time, double duration, const HyperdriveSoundsTable &sounds, LuaRef checks)
|
|
{
|
|
HyperjumpStatus status = Ship::InitiateHyperjumpTo(dest, warmup_time, duration, sounds, checks);
|
|
|
|
if (status == HYPERJUMP_OK)
|
|
s_soundHyperdrive.Play(m_hyperspace.sounds.warmup_sound.c_str());
|
|
|
|
return status;
|
|
}
|
|
|
|
void Player::AbortHyperjump()
|
|
{
|
|
s_soundHyperdrive.Play(m_hyperspace.sounds.abort_sound.c_str());
|
|
Ship::AbortHyperjump();
|
|
}
|
|
|
|
void Player::OnCockpitActivated()
|
|
{
|
|
if (m_cockpit)
|
|
m_cockpit->OnActivated(this);
|
|
}
|
|
|
|
void Player::StaticUpdate(const float timeStep)
|
|
{
|
|
Ship::StaticUpdate(timeStep);
|
|
|
|
for (size_t i = 0; i < GUNMOUNT_MAX; i++)
|
|
if (GetFixedGuns()->IsGunMounted(i))
|
|
GetFixedGuns()->UpdateLead(timeStep, i, this, GetCombatTarget());
|
|
|
|
// XXX even when not on screen. hacky, but really cockpit shouldn't be here
|
|
// anyway so this will do for now
|
|
if (m_cockpit)
|
|
m_cockpit->Update(this, timeStep);
|
|
}
|
|
|
|
int Player::GetManeuverTime() const
|
|
{
|
|
if (Pi::planner->GetOffsetVel().ExactlyEqual(vector3d(0, 0, 0))) {
|
|
return 0;
|
|
}
|
|
return Pi::planner->GetStartTime();
|
|
}
|
|
|
|
vector3d Player::GetManeuverVelocity() const
|
|
{
|
|
Frame *frame = Frame::GetFrame(GetFrame());
|
|
if (frame->IsRotFrame())
|
|
frame = Frame::GetFrame(frame->GetNonRotFrame());
|
|
const SystemBody *systemBody = frame->GetSystemBody();
|
|
|
|
if (Pi::planner->GetOffsetVel().ExactlyEqual(vector3d(0, 0, 0))) {
|
|
return vector3d(0, 0, 0);
|
|
} else if (systemBody) {
|
|
Orbit playerOrbit = ComputeOrbit();
|
|
if (!is_zero_exact(playerOrbit.GetSemiMajorAxis())) {
|
|
double mass = systemBody->GetMass();
|
|
// XXX The best solution would be to store the mass(es) on Orbit
|
|
const vector3d velocity = (Pi::planner->GetVel() - playerOrbit.OrbitalVelocityAtTime(mass, playerOrbit.OrbitalTimeAtPos(Pi::planner->GetPosition(), mass)));
|
|
return velocity;
|
|
}
|
|
}
|
|
return vector3d(0, 0, 0);
|
|
}
|