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: binaries/data/mods/public/simulation/components/interfaces/GarrisonHolder.js
===================================================================
--- binaries/data/mods/public/simulation/components/interfaces/GarrisonHolder.js
+++ binaries/data/mods/public/simulation/components/interfaces/GarrisonHolder.js
@@ -1,5 +1,3 @@
-Engine.RegisterInterface("GarrisonHolder");
-
/**
* Message of the form { "added": number[], "removed": number[] }
* sent from the GarrisonHolder component to the current entity whenever the garrisoned units change.
Index: binaries/data/mods/public/simulation/components/interfaces/Health.js
===================================================================
--- binaries/data/mods/public/simulation/components/interfaces/Health.js
+++ binaries/data/mods/public/simulation/components/interfaces/Health.js
@@ -1,5 +1,3 @@
-Engine.RegisterInterface("Health");
-
/**
* Message of the form { "from": number, "to": number }
* sent from Health component whenever health changes.
Index: binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js
+++ binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js
@@ -1,9 +1,11 @@
Engine.LoadHelperScript("ValueModification.js");
Engine.LoadHelperScript("Player.js");
+Engine.RegisterInterface("GarrisonHolder"); // Declaration is in C++.
Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
Engine.LoadComponentScript("interfaces/Garrisonable.js");
Engine.LoadComponentScript("GarrisonHolder.js");
Engine.LoadComponentScript("interfaces/Auras.js");
+Engine.RegisterInterface("Health"); // Declaration is in C++.
Engine.LoadComponentScript("interfaces/Health.js");
Engine.LoadComponentScript("interfaces/ProductionQueue.js");
Engine.LoadComponentScript("interfaces/ModifiersManager.js");
Index: binaries/data/mods/public/simulation/components/tests/test_Health.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_Health.js
+++ binaries/data/mods/public/simulation/components/tests/test_Health.js
@@ -4,7 +4,7 @@
Engine.LoadHelperScript("Sound.js");
Engine.LoadComponentScript("interfaces/DeathDamage.js");
-
+Engine.RegisterInterface("Health"); // Declaration is in C++.
Engine.LoadComponentScript("interfaces/Health.js");
Engine.LoadComponentScript("Health.js");
Index: binaries/data/mods/public/simulation/components/tests/test_UnitMotionFlying.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_UnitMotionFlying.js
+++ /dev/null
@@ -1,143 +0,0 @@
-Engine.LoadComponentScript("UnitMotionFlying.js");
-Engine.LoadComponentScript("interfaces/Health.js");
-Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
-
-let entity = 1;
-let target = 2;
-
-let height = 5;
-
-AddMock(SYSTEM_ENTITY, IID_Pathfinder, {
- GetPassabilityClass: (name) => 1 << 8
-});
-
-let cmpUnitMotionFlying = ConstructComponent(entity, "UnitMotionFlying", {
- "MaxSpeed": 1.0,
- "TakeoffSpeed": 0.5,
- "LandingSpeed": 0.5,
- "AccelRate": 0.0005,
- "SlowingRate": 0.001,
- "BrakingRate": 0.0005,
- "TurnRate": 0.1,
- "OvershootTime": 10,
- "FlyingHeight": 100,
- "ClimbRate": 0.1,
- "DiesInWater": false,
- "PassabilityClass": "unrestricted"
-});
-
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.GetSpeedMultiplier(), 0);
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.GetRunMultiplier(), 1);
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.GetCurrentSpeed(), 0);
-cmpUnitMotionFlying.SetSpeedMultiplier(2);
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.GetSpeedMultiplier(), 0);
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.GetRunMultiplier(), 1);
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.GetCurrentSpeed(), 0);
-
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.GetPassabilityClassName(), "unrestricted");
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.GetPassabilityClass(), 1 << 8);
-
-AddMock(entity, IID_Position, {
- "IsInWorld": () => true,
- "GetPosition2D": () => { return { "x": 50, "y": 100 }; },
- "GetPosition": () => { return { "x": 50, "y": height, "z": 100 }; },
- "GetRotation": () => { return { "y": 3.14 }; },
- "SetHeightFixed": (y) => height = y,
- "TurnTo": () => {},
- "SetXZRotation": () => {},
- "MoveTo": () => {}
-});
-
-AddMock(target, IID_Position, {
- "IsInWorld": () => true,
- "GetPosition2D": () => { return { "x": 100, "y": 200 }; }
-});
-
-AddMock(entity, IID_GarrisonHolder, {
- "AllowGarrisoning": () => {}
-});
-
-AddMock(entity, IID_Health, {
-});
-
-AddMock(entity, IID_RangeManager, {
- "GetLosCircular": () => true
-});
-
-AddMock(entity, IID_Terrain, {
- "GetGroundLevel": () => 4,
- "GetMapSize": () => 20
-});
-
-AddMock(entity, IID_WaterManager, {
- "GetWaterLevel": () => 5
-});
-
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.GetCurrentSpeed(), 0);
-cmpUnitMotionFlying.OnUpdate({ "turnLength": 500 });
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.GetCurrentSpeed(), 0);
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.GetSpeedMultiplier(), 0);
-
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.MoveToTargetRange(target, 0, 10), true);
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.MoveToPointRange(100, 200, 0, 20), true);
-
-// Take Off
-cmpUnitMotionFlying.OnUpdate({ "turnLength": 500 });
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.GetCurrentSpeed(), 0.25);
-TS_ASSERT_EQUALS(height, 5);
-cmpUnitMotionFlying.OnUpdate({ "turnLength": 500 });
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.GetCurrentSpeed(), 0.5);
-TS_ASSERT_EQUALS(height, 5);
-cmpUnitMotionFlying.OnUpdate({ "turnLength": 0 });
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.GetCurrentSpeed(), 0.5);
-TS_ASSERT_EQUALS(height, 5);
-cmpUnitMotionFlying.OnUpdate({ "turnLength": 500 });
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.GetCurrentSpeed(), 0.75);
-TS_ASSERT_EQUALS(height, 55);
-cmpUnitMotionFlying.OnUpdate({ "turnLength": 500 });
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.GetCurrentSpeed(), 1);
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.GetSpeedMultiplier(), 1);
-TS_ASSERT_EQUALS(height, 105);
-
-// Fly
-cmpUnitMotionFlying.OnUpdate({ "turnLength": 100 });
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.GetCurrentSpeed(), 1);
-TS_ASSERT_EQUALS(height, 105);
-cmpUnitMotionFlying.OnUpdate({ "turnLength": 500 });
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.GetCurrentSpeed(), 1);
-TS_ASSERT_EQUALS(height, 105);
-cmpUnitMotionFlying.OnUpdate({ "turnLength": 0 });
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.GetCurrentSpeed(), 1);
-TS_ASSERT_EQUALS(height, 105);
-
-// Land
-cmpUnitMotionFlying.StopMoving();
-cmpUnitMotionFlying.OnUpdate({ "turnLength": 0 });
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.GetCurrentSpeed(), 1);
-TS_ASSERT_EQUALS(height, 105);
-cmpUnitMotionFlying.OnUpdate({ "turnLength": 500 });
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.GetCurrentSpeed(), 0.5);
-TS_ASSERT_EQUALS(height, 5);
-
-// Slide
-cmpUnitMotionFlying.OnUpdate({ "turnLength": 500 });
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.GetCurrentSpeed(), 0.25);
-TS_ASSERT_EQUALS(height, 5);
-cmpUnitMotionFlying.OnUpdate({ "turnLength": 0 });
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.GetCurrentSpeed(), 0.25);
-TS_ASSERT_EQUALS(height, 5);
-cmpUnitMotionFlying.OnUpdate({ "turnLength": 500 });
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.GetCurrentSpeed(), 0);
-TS_ASSERT_EQUALS(height, 5);
-
-// Stay
-cmpUnitMotionFlying.OnUpdate({ "turnLength": 300 });
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.GetCurrentSpeed(), 0);
-TS_ASSERT_EQUALS(height, 5);
-cmpUnitMotionFlying.OnUpdate({ "turnLength": 0 });
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.GetCurrentSpeed(), 0);
-TS_ASSERT_EQUALS(height, 5);
-cmpUnitMotionFlying.OnUpdate({ "turnLength": 900 });
-TS_ASSERT_EQUALS(cmpUnitMotionFlying.GetCurrentSpeed(), 0);
-TS_ASSERT_EQUALS(height, 5);
-
Index: source/simulation2/TypeList.h
===================================================================
--- source/simulation2/TypeList.h
+++ source/simulation2/TypeList.h
@@ -98,9 +98,15 @@
INTERFACE(Footprint)
COMPONENT(Footprint)
+INTERFACE(GarrisonHolder)
+COMPONENT(GarrisonHolderScripted)
+
INTERFACE(GuiInterface)
COMPONENT(GuiInterfaceScripted)
+INTERFACE(Health)
+COMPONENT(HealthScripted)
+
INTERFACE(Identity)
COMPONENT(IdentityScripted)
@@ -187,6 +193,8 @@
COMPONENT(UnitMotion) // must be after Obstruction
COMPONENT(UnitMotionScripted)
+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,95 @@
+/* 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/ICmpGarrisonHolder.h"
+#include "simulation2/components/ICmpHealth.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));
+ void Move(fixed turnLength);
+ virtual void StopMoving();
+ virtual void SetSpeedMultiplier(fixed UNUSED(multiplier));
+ virtual void FaceTowardsPoint(fixed x, fixed z);
+ virtual void SetDebugOverlay(bool enabled);
+ virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange);
+ virtual bool MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange);
+ virtual void MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z);
+ virtual pass_class_t GetPassabilityClass() const;
+ virtual std::string GetPassabilityClassName() const;
+ virtual fixed GetSpeedMultiplier() const;
+ virtual fixed GetCurrentSpeed() const;
+ virtual fixed GetSpeed() const;
+ virtual fixed GetRunMultiplier() const;
+ virtual fixed GetWalkSpeed() const;
+ virtual bool IsMoveRequested() const;
+ virtual entity_pos_t GetUnitClearance() const;
+ virtual void SetFacePointAfterMove(bool facePointAfterMove);
+
+private:
+ 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;
+ fixed m_Pitch;
+ fixed m_Roll;
+ bool m_HasTarget;
+ bool m_ReachedTarget;
+ bool m_DiesInWater;
+ bool m_Landing;
+ bool m_OnGround;
+ bool m_WaterDeath;
+ std::string m_PassabilityClassName;
+ pass_class_t m_PassabilityClass;
+ fixed EuclidDistance(fixed x1, fixed z1, fixed x2, fixed z2);
+};
+
+
+REGISTER_COMPONENT_TYPE(UnitMotionFlying)
+
+#endif // INCLUDED_CCMPUNITMOTIONFLYING
Index: source/simulation2/components/CCmpUnitMotionFlying.cpp
===================================================================
--- /dev/null
+++ source/simulation2/components/CCmpUnitMotionFlying.cpp
@@ -0,0 +1,455 @@
+/* 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_TWO_PI = M_PI * 2.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(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t 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, entity_pos_t minRange, entity_pos_t maxRange)
+{
+ CmpPtr cmpTargetPosition(GetEntityHandle());
+ if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld())
+ return false;
+
+ CFixedVector2D targetPos = cmpTargetPosition->GetPosition2D();
+ return MoveToPointRange(targetPos.X, targetPos.Y, minRange, maxRange);
+}
+
+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::Zero();
+}
+
+
+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(bool facePointAfterMove)
+{
+ // 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.
+}
+
+void CCmpUnitMotionFlying::Deinit()
+{
+}
+
+void CCmpUnitMotionFlying::Move(fixed turnLength)
+{
+ if (!m_HasTarget)
+ return;
+
+ CmpPtr cmpTerrain(GetSystemEntity());
+ if (!cmpTerrain)
+ return;
+
+ CmpPtr cmpWaterManager(GetSystemEntity());
+ if (!cmpWaterManager)
+ return;
+
+ CmpPtr cmpPosition(GetEntityHandle());
+ if (!cmpPosition || !cmpPosition->IsInWorld())
+ return;
+
+ fixed sinus = fixed::Zero();
+ fixed cosinus = fixed::Zero();
+ CFixedVector3D pos = cmpPosition->GetPosition();
+ fixed ground = std::max(cmpTerrain->GetGroundLevel(pos.X, pos.Z), cmpWaterManager->GetWaterLevel(pos.X, pos.Z));
+ fixed angle = cmpPosition->GetRotation().Y;
+ 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.IsZero() && m_OnGround)
+ {
+ CmpPtr cmpHealth(GetEntityHandle());
+ if (m_WaterDeath && cmpHealth)
+ cmpHealth->Kill();
+ else
+ {
+ m_Pitch = fixed::Zero();
+ // We've stopped.
+ CmpPtr cmpGarrisonHolder(GetEntityHandle());
+ 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(GetSystemEntity());
+ 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;
+ auto heightRatio = (pos.Y - targetHeight) / m_FlyingHeight;
+ // Steep, then gradual descent.
+ if (heightRatio > fixed::FromInt(1) / fixed::FromDouble(SHORT_FINAL))
+ m_Pitch = - fixed::FromDouble(M_PI_EIGHTEENTH);
+ else
+ m_Pitch = fixed::FromDouble(M_PI_EIGHTEENTH);
+ fixed descentRate = (heightRatio.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.Square())
+ return;
+
+ // 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)
+ {
+ CmpPtr cmpGarrisonHolder(GetEntityHandle());
+ 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 / fixed::FromInt(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);
+ }
+ }
+ }
+
+ // 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.Square() <= distFromTarget && distFromTarget <= m_TargetMaxRange.Square())
+ {
+ 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).Square())
+ 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::Pi()) % fixed::FromDouble(M_TWO_PI); // range -2pi..2pi
+ if (delta < fixed::Zero())
+ delta += fixed::FromDouble(M_TWO_PI); // range 0..2pi
+ delta -= fixed::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);
+ fixed temp = m_Speed.Multiply(turnLength);
+ pos.X += temp.Multiply(sinus);
+ pos.Z += temp.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();
+}
+
+
+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/ICmpGarrisonHolder.h
===================================================================
--- /dev/null
+++ source/simulation2/components/ICmpGarrisonHolder.h
@@ -0,0 +1,30 @@
+/* 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_ICMPGARRISONHOLDER
+#define INCLUDED_ICMPGARRISONHOLDER
+
+#include "simulation2/system/Interface.h"
+
+class ICmpGarrisonHolder : public IComponent
+{
+public:
+ virtual void AllowGarrisoning(bool allow, std::string callerId) = 0;
+ DECLARE_INTERFACE_TYPE(GarrisonHolder)
+};
+
+#endif // INCLUDED_ICMPGARRISONHOLDER
Index: source/simulation2/components/ICmpGarrisonHolder.cpp
===================================================================
--- /dev/null
+++ source/simulation2/components/ICmpGarrisonHolder.cpp
@@ -0,0 +1,40 @@
+/* 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 "ICmpGarrisonHolder.h"
+
+#include "simulation2/system/InterfaceScripted.h"
+#include "simulation2/scripting/ScriptComponent.h"
+
+
+BEGIN_INTERFACE_WRAPPER(GarrisonHolder)
+END_INTERFACE_WRAPPER(GarrisonHolder)
+
+class CCmpGarrisonHolderScripted : public ICmpGarrisonHolder
+{
+public:
+ DEFAULT_SCRIPT_WRAPPER(GarrisonHolderScripted)
+
+ virtual void AllowGarrisoning(bool allow, std::string callerId)
+ {
+ m_Script.CallVoid("AllowGarrisoning", allow, callerId);
+ }
+};
+
+REGISTER_COMPONENT_SCRIPT_WRAPPER(GarrisonHolderScripted)
Index: source/simulation2/components/ICmpHealth.h
===================================================================
--- /dev/null
+++ source/simulation2/components/ICmpHealth.h
@@ -0,0 +1,30 @@
+/* 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_ICMPHEALTH
+#define INCLUDED_ICMPHEALTH
+
+#include "simulation2/system/Interface.h"
+
+class ICmpHealth : public IComponent
+{
+public:
+ virtual void Kill() = 0;
+ DECLARE_INTERFACE_TYPE(Health)
+};
+
+#endif // INCLUDED_ICMPHEALTH
Index: source/simulation2/components/ICmpHealth.cpp
===================================================================
--- /dev/null
+++ source/simulation2/components/ICmpHealth.cpp
@@ -0,0 +1,40 @@
+/* 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 "ICmpHealth.h"
+
+#include "simulation2/system/InterfaceScripted.h"
+#include "simulation2/scripting/ScriptComponent.h"
+
+
+BEGIN_INTERFACE_WRAPPER(Health)
+END_INTERFACE_WRAPPER(Health)
+
+class CCmpHealthScripted: public ICmpHealth
+{
+public:
+ DEFAULT_SCRIPT_WRAPPER(HealthScripted)
+
+ virtual void Kill()
+ {
+ m_Script.CallVoid("Kill");
+ }
+};
+
+REGISTER_COMPONENT_SCRIPT_WRAPPER(HealthScripted)