Index: binaries/data/mods/public/simulation/components/UnitMotionFlying.js =================================================================== --- binaries/data/mods/public/simulation/components/UnitMotionFlying.js +++ /dev/null @@ -1,341 +0,0 @@ -// (A serious implementation of this might want to use C++ instead of JS -// for performance; this is just for fun.) -const SHORT_FINAL = 2.5; -function UnitMotionFlying() {} - -UnitMotionFlying.prototype.Schema = - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - ""; - -UnitMotionFlying.prototype.Init = function() -{ - this.hasTarget = false; - this.reachedTarget = false; - this.targetX = 0; - this.targetZ = 0; - this.targetMinRange = 0; - this.targetMaxRange = 0; - this.speed = 0; - this.landing = false; - this.onGround = true; - this.pitch = 0; - this.roll = 0; - this.waterDeath = false; - this.passabilityClass = Engine.QueryInterface(SYSTEM_ENTITY, IID_Pathfinder).GetPassabilityClass(this.template.PassabilityClass); -}; - -UnitMotionFlying.prototype.OnUpdate = function(msg) -{ - var turnLength = msg.turnLength; - if (!this.hasTarget) - return; - var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder); - var cmpHealth = Engine.QueryInterface(this.entity, IID_Health); - var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); - var pos = cmpPosition.GetPosition(); - var angle = cmpPosition.GetRotation().y; - var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain); - var cmpWaterManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_WaterManager); - var ground = Math.max(cmpTerrain.GetGroundLevel(pos.x, pos.z), cmpWaterManager.GetWaterLevel(pos.x, pos.z)); - var newangle = angle; - var canTurn = true; - if (this.landing) - { - if (this.speed > 0 && this.onGround) - { - if (pos.y <= cmpWaterManager.GetWaterLevel(pos.x, pos.z) && this.template.DiesInWater == "true") - this.waterDeath = true; - this.pitch = 0; - // Deaccelerate forwards...at a very reduced pace. - if (this.waterDeath) - this.speed = Math.max(0, this.speed - turnLength * this.template.BrakingRate * 10); - else - this.speed = Math.max(0, this.speed - turnLength * this.template.BrakingRate); - canTurn = false; - // Clamp to ground if below it, or descend if above - if (pos.y < ground) - pos.y = ground; - else if (pos.y > ground) - pos.y = Math.max(ground, pos.y - turnLength * this.template.ClimbRate); - } - else if (this.speed == 0 && this.onGround) - { - if (this.waterDeath && cmpHealth) - cmpHealth.Kill(); - else - { - this.pitch = 0; - // We've stopped. - if (cmpGarrisonHolder) - cmpGarrisonHolder.AllowGarrisoning(true,"UnitMotionFlying"); - canTurn = false; - this.hasTarget = false; - this.landing = false; - // summon planes back from the edge of the map - var terrainSize = cmpTerrain.GetMapSize(); - var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); - if (cmpRangeManager.GetLosCircular()) - { - var mapRadius = terrainSize/2; - var x = pos.x - mapRadius; - var z = pos.z - mapRadius; - var div = (mapRadius - 12) / Math.sqrt(x*x + z*z); - if (div < 1) - { - pos.x = mapRadius + x*div; - pos.z = mapRadius + z*div; - newangle += Math.PI; - } - } - else - { - pos.x = Math.max(Math.min(pos.x, terrainSize - 12), 12); - pos.z = Math.max(Math.min(pos.z, terrainSize - 12), 12); - newangle += Math.PI; - } - } - } - else - { - // Final Approach - // We need to slow down to land! - this.speed = Math.max(this.template.LandingSpeed, this.speed - turnLength * this.template.SlowingRate); - canTurn = false; - var targetHeight = ground; - // Steep, then gradual descent. - if ((pos.y - targetHeight) / this.template.FlyingHeight > 1 / SHORT_FINAL) - this.pitch = - Math.PI / 18; - else - this.pitch = Math.PI / 18; - var descentRate = ((pos.y - targetHeight) / this.template.FlyingHeight * this.template.ClimbRate + SHORT_FINAL) * SHORT_FINAL; - if (pos.y < targetHeight) - pos.y = Math.max(targetHeight, pos.y + turnLength * descentRate); - else if (pos.y > targetHeight) - pos.y = Math.max(targetHeight, pos.y - turnLength * descentRate); - if (targetHeight == pos.y) - { - this.onGround = true; - if (targetHeight == cmpWaterManager.GetWaterLevel(pos.x, pos.z) && this.template.DiesInWater) - this.waterDeath = true; - } - } - } - else - { - // If we haven't reached max speed yet then we're still on the ground; - // otherwise we're taking off or flying - // this.onGround in case of a go-around after landing (but not fully stopped) - - if (this.speed < this.template.TakeoffSpeed && this.onGround) - { - if (cmpGarrisonHolder) - cmpGarrisonHolder.AllowGarrisoning(false,"UnitMotionFlying"); - this.pitch = 0; - // Accelerate forwards - this.speed = Math.min(this.template.MaxSpeed, this.speed + turnLength * this.template.AccelRate); - canTurn = false; - // Clamp to ground if below it, or descend if above - if (pos.y < ground) - pos.y = ground; - else if (pos.y > ground) - pos.y = Math.max(ground, pos.y - turnLength * this.template.ClimbRate); - } - else - { - this.onGround = false; - // Climb/sink to max height above ground - this.speed = Math.min(this.template.MaxSpeed, this.speed + turnLength * this.template.AccelRate); - var targetHeight = ground + (+this.template.FlyingHeight); - if (Math.abs(pos.y-targetHeight) > this.template.FlyingHeight/5) - { - this.pitch = Math.PI / 9; - canTurn = false; - } - else - this.pitch = 0; - if (pos.y < targetHeight) - pos.y = Math.min(targetHeight, pos.y + turnLength * this.template.ClimbRate); - else if (pos.y > targetHeight) - { - pos.y = Math.max(targetHeight, pos.y - turnLength * this.template.ClimbRate); - this.pitch = -1 * this.pitch; - } - } - } - - // If we're in range of the target then tell people that we've reached it - // (TODO: quantisation breaks this) - var distFromTarget = Math.euclidDistance2D(pos.x, pos.z, this.targetX, this.targetZ); - if (!this.reachedTarget && this.targetMinRange <= distFromTarget && distFromTarget <= this.targetMaxRange) - { - this.reachedTarget = true; - Engine.PostMessage(this.entity, MT_MotionUpdate, { "updateString": "likelySuccess" }); - } - - // If we're facing away from the target, and are still fairly close to it, - // then carry on going straight so we overshoot in a straight line - var isBehindTarget = ((this.targetX - pos.x) * Math.sin(angle) + (this.targetZ - pos.z) * Math.cos(angle) < 0); - // Overshoot the target: carry on straight - if (isBehindTarget && distFromTarget < this.template.MaxSpeed * this.template.OvershootTime) - canTurn = false; - - if (canTurn) - { - // Turn towards the target - var targetAngle = Math.atan2(this.targetX - pos.x, this.targetZ - pos.z); - var delta = targetAngle - angle; - // Wrap delta to -pi..pi - delta = (delta + Math.PI) % (2*Math.PI); // range -2pi..2pi - if (delta < 0) delta += 2*Math.PI; // range 0..2pi - delta -= Math.PI; // range -pi..pi - // Clamp to max rate - var deltaClamped = Math.min(Math.max(delta, -this.template.TurnRate * turnLength), this.template.TurnRate * turnLength); - // Calculate new orientation, in a peculiar way in order to make sure the - // result gets close to targetAngle (rather than being n*2*pi out) - newangle = targetAngle + deltaClamped - delta; - if (newangle - angle > Math.PI / 18) - this.roll = Math.PI / 9; - else if (newangle - angle < -Math.PI / 18) - this.roll = - Math.PI / 9; - else - this.roll = newangle - angle; - } - else - this.roll = 0; - - pos.x += this.speed * turnLength * Math.sin(angle); - pos.z += this.speed * turnLength * Math.cos(angle); - cmpPosition.SetHeightFixed(pos.y); - cmpPosition.TurnTo(newangle); - cmpPosition.SetXZRotation(this.pitch, this.roll); - cmpPosition.MoveTo(pos.x, pos.z); -}; - -UnitMotionFlying.prototype.MoveToPointRange = function(x, z, minRange, maxRange) -{ - this.hasTarget = true; - this.landing = false; - this.reachedTarget = false; - this.targetX = x; - this.targetZ = z; - this.targetMinRange = minRange; - this.targetMaxRange = maxRange; - - return true; -}; - -UnitMotionFlying.prototype.MoveToTargetRange = function(target, minRange, maxRange) -{ - var cmpTargetPosition = Engine.QueryInterface(target, IID_Position); - if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) - return false; - - var targetPos = cmpTargetPosition.GetPosition2D(); - - this.hasTarget = true; - this.reachedTarget = false; - this.targetX = targetPos.x; - this.targetZ = targetPos.y; - this.targetMinRange = minRange; - this.targetMaxRange = maxRange; - - return true; -}; - -UnitMotionFlying.prototype.GetWalkSpeed = function() -{ - return +this.template.MaxSpeed; -}; - -UnitMotionFlying.prototype.SetSpeedMultiplier = function() -{ - // ignore this, the speed is always the walk speed -}; - -UnitMotionFlying.prototype.GetRunMultiplier = function() -{ - return 1; -}; - -UnitMotionFlying.prototype.IsMoveRequested = function() -{ - return this.hasTarget; -}; - -UnitMotionFlying.prototype.GetCurrentSpeed = function() -{ - return this.speed; -}; - -UnitMotionFlying.prototype.GetSpeedMultiplier = function() -{ - return this.GetCurrentSpeed() / this.GetWalkSpeed(); -}; - -UnitMotionFlying.prototype.GetPassabilityClassName = function() -{ - return this.template.PassabilityClass; -}; - -UnitMotionFlying.prototype.GetPassabilityClass = function() -{ - return this.passabilityClass; -}; - -UnitMotionFlying.prototype.FaceTowardsPoint = function(x, z) -{ - // Ignore this - angle is controlled by the target-seeking code instead -}; - -UnitMotionFlying.prototype.SetFacePointAfterMove = function() -{ - // Ignore this - angle is controlled by the target-seeking code instead -}; - -UnitMotionFlying.prototype.StopMoving = function() -{ - //Invert - if (!this.waterDeath) - this.landing = !this.landing; - -}; - -UnitMotionFlying.prototype.SetDebugOverlay = function(enabled) -{ -}; - -Engine.RegisterComponentType(IID_UnitMotion, "UnitMotionFlying", UnitMotionFlying); Index: source/simulation2/TypeList.h =================================================================== --- source/simulation2/TypeList.h +++ source/simulation2/TypeList.h @@ -187,6 +187,9 @@ COMPONENT(UnitMotion) // must be after Obstruction COMPONENT(UnitMotionScripted) +//INTERFACE(UnitMotionFlying) +COMPONENT(UnitMotionFlying) // must be after Obstruction + INTERFACE(UnitRenderer) COMPONENT(UnitRenderer) Index: source/simulation2/components/CCmpUnitMotionFlying.h =================================================================== --- /dev/null +++ source/simulation2/components/CCmpUnitMotionFlying.h @@ -0,0 +1,94 @@ +/* Copyright (C) 2019 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_CCMPUNITMOTIONFLYING +#define INCLUDED_CCMPUNITMOTIONFLYING + +#include "ICmpUnitMotion.h" +#include "simulation2/system/Component.h" +#include "simulation2/components/ICmpPosition.h" +#include "simulation2/components/ICmpTerrain.h" +#include "simulation2/components/ICmpWaterManager.h" +#include "simulation2/components/ICmpRangeManager.h" +#include "simulation2/MessageTypes.h" +#include "ps/CLogger.h" + +class CCmpUnitMotionFlying : public ICmpUnitMotion +{ +public: + DEFAULT_COMPONENT_ALLOCATOR(UnitMotionFlying) + static void ClassInit(CComponentManager& componentManager); + static std::string GetSchema(); + virtual void Serialize(ISerializer& serializer); + virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserializer); + virtual void Init(const CParamNode& paramNode); + virtual void Deinit(); + virtual void HandleMessage(const CMessage& msg, bool UNUSED(global)); + bool Move(fixed turnLength); + fixed EuclidDistance(fixed x1, fixed z1, fixed x2, fixed z2); + void StopMoving(); + void SetDebugOverlay(bool enabled); + void SetFacePointAfterMove(); + void SetSpeedMultiplier(fixed UNUSED(multiplier)); + void FaceTowardsPoint(fixed x, fixed z); + bool MoveToTargetRange(entity_id_t target, fixed minRange, fixed maxRange); + bool MoveToPointRange(fixed x, fixed z, fixed minRange, fixed maxRange); + pass_class_t GetPassabilityClass() const; + std::string GetPassabilityClassName() const; + fixed GetSpeedMultiplier() const; + fixed GetCurrentSpeed() const; + fixed GetSpeed() const; + fixed GetRunMultiplier() const; + fixed GetWalkSpeed() const; + bool IsMoveRequested() const; + entity_pos_t GetUnitClearance() const; + void SetFacePointAfterMove(bool facePointAfterMove); + void MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z); + +private: + bool m_HasTarget; + bool m_ReachedTarget; + fixed m_TargetX; + fixed m_TargetZ; + fixed m_TargetMinRange; + fixed m_StationaryDistance; + fixed m_TargetMaxRange; + fixed m_Speed; + fixed m_MaxSpeed; + fixed m_TurnRate; + fixed m_BrakingRate; + fixed m_ClimbRate; + fixed m_FlyingHeight; + fixed m_LandingSpeed; + fixed m_SlowingRate; + fixed m_OvershootTime; + fixed m_TakeOffSpeed; + fixed m_AccelRate; + bool m_DiesInWater; + bool m_Landing; + bool m_OnGround; + fixed m_Pitch; + fixed m_Roll; + bool m_WaterDeath; + std::string m_PassabilityClassName; + pass_class_t m_PassabilityClass; +}; + + +REGISTER_COMPONENT_TYPE(UnitMotionFlying) + +#endif // INCLUDED_CCMPUNITMOTIONFLYING Index: source/simulation2/components/CCmpUnitMotionFlying.cpp =================================================================== --- /dev/null +++ source/simulation2/components/CCmpUnitMotionFlying.cpp @@ -0,0 +1,465 @@ +/* Copyright (C) 2019 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "CCmpUnitMotionFlying.h" + +constexpr float SHORT_FINAL = 2.5f; +constexpr double M_PI_NINTH = M_PI / 9.0; +constexpr double M_PI_EIGHTEENTH = M_PI / 18.0; + +std::string CCmpUnitMotionFlying::GetSchema() +{ + return + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + ""; +} + +void CCmpUnitMotionFlying::Serialize(ISerializer& serializer) +{ +} + +void CCmpUnitMotionFlying::Deserialize(const CParamNode& paramNode, IDeserializer& deserializer) +{ +} + + +void CCmpUnitMotionFlying::ClassInit(CComponentManager& componentManager) +{ + componentManager.SubscribeToMessageType(MT_Update); +} + +void CCmpUnitMotionFlying::Init(const CParamNode ¶mNode) +{ + m_HasTarget = false; + m_ReachedTarget = false; + m_TargetX = fixed::Zero(); + m_TargetZ = fixed::Zero(); + m_TargetMinRange = fixed::Zero(); + m_TargetMaxRange = fixed::Zero(); + m_Speed = fixed::Zero(); + m_Landing = false; + m_OnGround = true; + m_Pitch = fixed::Zero(); + m_Roll = fixed::Zero(); + m_WaterDeath = false; + m_BrakingRate = paramNode.GetChild("BrakingRate").ToFixed(); + m_ClimbRate = paramNode.GetChild("ClimbRate").ToFixed(); + m_TurnRate = paramNode.GetChild("TurnRate").ToFixed(); + m_MaxSpeed = paramNode.GetChild("MaxSpeed").ToFixed(); + m_DiesInWater = paramNode.GetChild("MaxSpeed").ToBool(); + m_FlyingHeight =paramNode.GetChild("FlyingHeight ").ToFixed(); + m_LandingSpeed = paramNode.GetChild("LandingSpeed ").ToFixed(); + m_SlowingRate = paramNode.GetChild("SlowingRate ").ToFixed(); + m_OvershootTime = paramNode.GetChild("OvershootTime ").ToFixed(); + m_TakeOffSpeed = paramNode.GetChild("TakeoffSpeed ").ToFixed(); + m_AccelRate = paramNode.GetChild("AccelRate ").ToFixed(); + m_PassabilityClassName = paramNode.GetChild("PassabilityClass").ToUTF8(); + + if (paramNode.GetChild("StationaryDistance").IsOk()) + m_StationaryDistance = paramNode.GetChild("StationaryDistance").ToFixed(); + else + m_StationaryDistance = fixed::FromInt(-1); + + CmpPtr cmpPathfinder(GetSystemEntity()); + if (cmpPathfinder) + m_PassabilityClass = cmpPathfinder->GetPassabilityClass(m_PassabilityClassName); +} + +bool CCmpUnitMotionFlying::MoveToPointRange(fixed x, fixed z, fixed minRange, fixed maxRange) +{ + m_HasTarget = true; + m_Landing = false; + m_ReachedTarget = false; + m_TargetX = x; + m_TargetZ = z; + m_TargetMinRange = minRange; + m_TargetMaxRange = maxRange; + + return true; +}; + +bool CCmpUnitMotionFlying::MoveToTargetRange(entity_id_t target, fixed minRange, fixed maxRange) +{ + CmpPtr cmpTargetPosition(GetEntityHandle()); + if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld()) + return false; + + CFixedVector2D targetPos = cmpTargetPosition->GetPosition2D(); + m_HasTarget = true; + m_ReachedTarget = false; + m_TargetX = targetPos.X; + m_TargetZ = targetPos.Y; + m_TargetMinRange = minRange; + m_TargetMaxRange = maxRange; + + return true; +} + +fixed CCmpUnitMotionFlying::GetWalkSpeed() const +{ + return m_MaxSpeed; +} + +void CCmpUnitMotionFlying::SetSpeedMultiplier(fixed UNUSED(multiplier)) +{ + // Ignore this, the speed is always the walk speed. +} + +fixed CCmpUnitMotionFlying::GetRunMultiplier() const +{ + return fixed::FromInt(1); +} + +bool CCmpUnitMotionFlying::IsMoveRequested() const +{ + return m_HasTarget; +} + +entity_pos_t CCmpUnitMotionFlying::GetUnitClearance() const +{ + return entity_pos_t(); +} + +void CCmpUnitMotionFlying::SetFacePointAfterMove(bool facePointAfterMove) +{ +} + +void CCmpUnitMotionFlying::MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z) +{ +} + +fixed CCmpUnitMotionFlying::GetCurrentSpeed() const +{ + return m_Speed; +} + +fixed CCmpUnitMotionFlying::GetSpeed() const +{ + return GetCurrentSpeed(); +} + +fixed CCmpUnitMotionFlying::GetSpeedMultiplier() const +{ + return m_Speed / m_MaxSpeed; +} + +std::string CCmpUnitMotionFlying::GetPassabilityClassName() const +{ + return m_PassabilityClassName; +} + +pass_class_t CCmpUnitMotionFlying::GetPassabilityClass() const +{ + return m_PassabilityClass; +} + +void CCmpUnitMotionFlying::FaceTowardsPoint(fixed x, fixed z) +{ + // Ignore this - angle is controlled by the target-seeking code instead +} + +void CCmpUnitMotionFlying::SetFacePointAfterMove() +{ + // Ignore this - angle is controlled by the target-seeking code instead +} + +void CCmpUnitMotionFlying::StopMoving() +{ + // Invert. + if (!m_WaterDeath) + m_Landing = !m_Landing; +} + +void CCmpUnitMotionFlying::SetDebugOverlay(bool enabled) +{ + // TODO: Implement m_ +} + +void CCmpUnitMotionFlying::Deinit() +{ +} + +bool CCmpUnitMotionFlying::Move(fixed turnLength) +{ + if (!m_HasTarget) + return false; + + CmpPtr cmpTerrain(GetSystemEntity()); + if (!cmpTerrain) + return false; + + CmpPtr cmpWaterManager(GetSystemEntity()); + if (!cmpWaterManager) + return false; + + CmpPtr cmpPosition(GetEntityHandle()); + if (!cmpPosition || !cmpPosition->IsInWorld()) + return false; + CFixedVector3D pos = cmpPosition->GetPosition(); + fixed angle = cmpPosition->GetRotation().Y; + fixed sinus = fixed::Zero(); + fixed cosinus = fixed::Zero(); + // TODO: Create interface. + // auto cmpGarrisonHolder = Engine.QueryInterface(m_entity, IID_GarrisonHolder); + fixed ground = std::max(cmpTerrain->GetGroundLevel(pos.X, pos.Z), cmpWaterManager->GetWaterLevel(pos.X, pos.Z)); + fixed newangle = angle; + bool canTurn = true; + if (m_Landing) + { + if (m_Speed > fixed::Zero() && m_OnGround) + { + if (pos.Y <= cmpWaterManager->GetWaterLevel(pos.X, pos.Z) && m_DiesInWater) + m_WaterDeath = true; + m_Pitch = fixed::Zero(); + // Deaccelerate forwards...at a very reduced pace. + if (m_WaterDeath) + m_Speed = std::max(fixed::Zero(), m_Speed - turnLength.Multiply(m_BrakingRate.Multiply( fixed::FromInt(10)))); + else + m_Speed = std::max(fixed::Zero(), m_Speed - turnLength.Multiply(m_BrakingRate)); + canTurn = false; + // Clamp to ground if below it, or descend if above + if (pos.Y < ground) + pos.Y = ground; + else if (pos.Y > ground) + pos.Y = std::max(ground, pos.Y - turnLength.Multiply(m_ClimbRate)); + } + else if (m_Speed == fixed::Zero() && m_OnGround) + { + // TODO: Create interface. + // auto cmpHealth = Engine.QueryInterface(m_entity, IID_Health); + //auto cmpHealth = nullptr; + if (m_WaterDeath /* && cmpHealth */) + // cmpHealth.Kill(); + LOGERROR("Can't access cmpHealth in cpp!"); + else + { + m_Pitch = fixed::Zero(); + // We've stopped. + //if (cmpGarrisonHolder) + // cmpGarrisonHolder.AllowGarrisoning(true,"UnitMotionFlying"); + canTurn = false; + m_HasTarget = false; + m_Landing = false; + // summon planes back from the edge of the map + u32 terrainSize = cmpTerrain->GetMapSize(); + + CmpPtr cmpRangeManager(GetEntityHandle()); + if (cmpRangeManager->GetLosCircular()) + { + fixed mapRadius = fixed::FromDouble(terrainSize / 2.0); + fixed x = pos.X - mapRadius; + fixed z = pos.Z - mapRadius; + fixed div = (mapRadius - fixed::FromInt(12)) / (x.Square() + z.Square()).Sqrt(); + if (div < fixed::FromInt(1)) + { + pos.X = mapRadius + x.Multiply(div); + pos.Z = mapRadius + z.Multiply(div); + newangle += fixed::Pi(); + } + } + else + { + pos.X = std::max(std::min(pos.X, fixed::FromInt(terrainSize - 12)), fixed::FromInt(12)); + pos.Z = std::max(std::min(pos.Z, fixed::FromInt(terrainSize - 12)), fixed::FromInt(12)); + newangle += fixed::Pi(); + } + } + } + else + { + // Final Approach + // We need to slow down to land! + m_Speed = std::max(m_LandingSpeed, m_Speed - turnLength.Multiply(m_SlowingRate)); + canTurn = false; + fixed targetHeight = ground; + // Steep, then gradual descent. + if ((pos.Y - targetHeight) / m_FlyingHeight > fixed::FromInt(1) / fixed::FromDouble(SHORT_FINAL)) + m_Pitch = - fixed::FromDouble(M_PI_EIGHTEENTH); + else + m_Pitch = fixed::FromDouble(M_PI_EIGHTEENTH); + fixed descentRate = (((pos.Y - targetHeight) / m_FlyingHeight).Multiply(m_ClimbRate) + fixed::FromDouble(SHORT_FINAL)).Multiply(fixed::FromDouble(SHORT_FINAL)); + if (pos.Y < targetHeight) + pos.Y = std::max(targetHeight, pos.Y + turnLength.Multiply(descentRate)); + else if (pos.Y > targetHeight) + pos.Y = std::max(targetHeight, pos.Y - turnLength.Multiply(descentRate)); + if (targetHeight == pos.Y) + { + m_OnGround = true; + if (targetHeight == cmpWaterManager->GetWaterLevel(pos.X, pos.Z) && m_DiesInWater) + m_WaterDeath = true; + } + } + } + else + { + if (m_StationaryDistance > fixed::Zero() && EuclidDistance(pos.X, pos.Z, m_TargetX, m_TargetZ) <= m_StationaryDistance) + return true; + // If we haven't reached max speed yet then we're still on the ground; + // otherwise we're taking off or flying + // m_onGround in case of a go-around after landing (but not fully stopped) + + if (m_Speed < m_TakeOffSpeed && m_OnGround) + { + // if (cmpGarrisonHolder) + // cmpGarrisonHolder.AllowGarrisoning(false,"UnitMotionFlying"); + m_Pitch = fixed::Zero(); + // Accelerate forwards + m_Speed = std::min(m_MaxSpeed, m_Speed + turnLength.Multiply(m_AccelRate)); + canTurn = false; + // Clamp to ground if below it, or descend if above + if (pos.Y < ground) + pos.Y = ground; + else if (pos.Y > ground) + pos.Y = std::max(ground, pos.Y - turnLength.Multiply(m_ClimbRate)); + } + else + { + m_OnGround = false; + // Climb/sink to max height above ground + m_Speed = std::min(m_MaxSpeed, m_Speed + turnLength.Multiply(m_AccelRate)); + fixed targetHeight = ground + m_FlyingHeight; + if ((pos.Y-targetHeight).Absolute() > m_FlyingHeight / 5) + { + m_Pitch = fixed::FromDouble(M_PI_NINTH); + canTurn = false; + } + else + m_Pitch = fixed::Zero(); + if (pos.Y < targetHeight) + pos.Y = std::min(targetHeight, pos.Y + turnLength.Multiply(m_ClimbRate)); + else if (pos.Y > targetHeight) + { + pos.Y = std::max(targetHeight, pos.Y - turnLength.Multiply(m_ClimbRate)); + m_Pitch = fixed::FromInt(-1).Multiply(m_Pitch); + } + } + return true; + } + + // If we're in range of the target then tell people that we've reached it + // (TODO: quantisation breaks this) + fixed distFromTarget = EuclidDistance(pos.X, pos.Z, m_TargetX, m_TargetZ); + if (!m_ReachedTarget && m_TargetMinRange <= distFromTarget && distFromTarget <= m_TargetMaxRange) + { + m_ReachedTarget = true; + CMessageMotionUpdate msg(CMessageMotionUpdate::LIKELY_SUCCESS); + GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg); + } + + + + sincos_approx(angle, sinus, cosinus); + // If we're facing away from the target, and are still fairly close to it, + // then carry on going straight so we overshoot in a straight line + bool isBehindTarget = ((m_TargetX - pos.X).Multiply(sinus) + (m_TargetZ - pos.Z).Multiply(cosinus) < fixed::Zero()); + // Overshoot the target: carry on straight + if (isBehindTarget && distFromTarget < m_MaxSpeed.Multiply(m_OvershootTime)) + canTurn = false; + + if (canTurn) + { + // Turn towards the target + fixed targetAngle = atan2_approx(m_TargetX - pos.X, m_TargetZ - pos.Z); + fixed delta = targetAngle - angle; + // Wrap delta to -pi..pi + delta = (delta + fixed::FromDouble(M_PI)) % fixed::FromDouble(2.0*M_PI); // range -2pi..2pi + if (delta < fixed::Zero()) delta += fixed::FromDouble(2.0 * M_PI); // range 0..2pi + delta -= fixed::FromDouble(M_PI); // range -pi..pi + // Clamp to max rate + fixed deltaClamped = std::min(std::max(delta, - m_TurnRate.Multiply(turnLength)), m_TurnRate.Multiply(turnLength)); + // Calculate new orientation, in a peculiar way in order to make sure the + // result gets close to targetAngle (rather than being n*2*pi out) + newangle = targetAngle + deltaClamped - delta; + if (newangle - angle > fixed::FromDouble(M_PI_EIGHTEENTH)) + m_Roll = fixed::FromDouble(M_PI_NINTH); + else if (newangle - angle < - fixed::FromDouble(M_PI_EIGHTEENTH)) + m_Roll = -fixed::FromDouble(M_PI_NINTH); + else + m_Roll = newangle - angle; + } + else + m_Roll = fixed::Zero(); + + sincos_approx(angle, sinus, cosinus); + pos.X += m_Speed.Multiply(turnLength).Multiply(sinus); + pos.Z += m_Speed.Multiply(turnLength).Multiply(cosinus); + cmpPosition->SetHeightFixed(pos.Y); + cmpPosition->TurnTo(newangle); + cmpPosition->SetXZRotation(m_Pitch, m_Roll); + cmpPosition->MoveTo(pos.X, pos.Z); +} + +fixed CCmpUnitMotionFlying::EuclidDistance(fixed x1, fixed z1, fixed x2, fixed z2) +{ + fixed dx = x1 - x2; + fixed dz = z1 - z2; + return (dx.Square() + dz.Square()).Sqrt(); +} + + +void CCmpUnitMotionFlying::HandleMessage(const CMessage& msg, bool UNUSED(global)) +{ + switch (msg.GetType()) + { + case MT_Update: + { + Move(static_cast (msg).turnLength); + break; + } + } +} Index: source/simulation2/components/ICmpUnitMotionFlying.h =================================================================== --- /dev/null +++ source/simulation2/components/ICmpUnitMotionFlying.h @@ -0,0 +1,22 @@ +/* Copyright (C) 2019 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_ICMPUNITMOTIONFLYING +#define INCLUDED_ICMPUNITMOTIONFLYING + + +#endif // INCLUDED_ICMPUNITMOTIONFLYING Index: source/simulation2/components/ICmpUnitMotionFlying.cpp =================================================================== --- /dev/null +++ source/simulation2/components/ICmpUnitMotionFlying.cpp @@ -0,0 +1,20 @@ +/* Copyright (C) 2019 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "ICmpUnitMotionFlying.h"