Index: ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js (revision 13693)
+++ ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js (revision 13694)
@@ -1,510 +1,540 @@
function GarrisonHolder() {}
GarrisonHolder.prototype.Schema =
"" +
"" +
"" +
"" +
"" +
"tokens" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"";
/**
* Initialize GarrisonHolder Component
*/
GarrisonHolder.prototype.Init = function()
{
// Garrisoned Units
this.entities = [];
this.spaceOccupied = 0;
this.timer = undefined;
+ this.allowGarrisoning = [];
};
/**
* Return range at which entities can garrison here
*/
GarrisonHolder.prototype.GetLoadingRange = function()
{
var max = +this.template.LoadingRange;
return { "max": max, "min": 0 };
};
/**
* Return the list of entities garrisoned inside
*/
GarrisonHolder.prototype.GetEntities = function()
{
return this.entities;
};
/**
* Returns an array of unit classes which can be garrisoned inside this
* particualar entity. Obtained from the entity's template
*/
GarrisonHolder.prototype.GetAllowedClassesList = function()
{
var classes = this.template.List._string;
return classes ? classes.split(/\s+/) : [];
};
/**
* Get Maximum pop which can be garrisoned
*/
GarrisonHolder.prototype.GetCapacity = function()
{
return ApplyTechModificationsToEntity("GarrisonHolder/Max", +this.template.Max, this.entity);
};
/**
* Get the heal rate with which garrisoned units will be healed
*/
GarrisonHolder.prototype.GetHealRate = function()
{
return ApplyTechModificationsToEntity("GarrisonHolder/BuffHeal", +this.template.BuffHeal, this.entity);
};
GarrisonHolder.prototype.EjectEntitiesOnDestroy = function()
{
if (this.template.EjectEntitiesOnDestroy == "true")
return true;
return false;
-}
+};
+
+/**
+ * Set this entity to allow or disallow garrisoning in
+ * Every component calling this function should do it with its own ID, and as long as one
+ * component doesn't allow this entity to garrison, it can't be garrisoned
+ * When this entity already contains garrisoned soldiers,
+ * these will not be able to ungarrison until the flag is set to true again.
+ *
+ * This more useful for modern-day features. For example you can't garrison or ungarrison
+ * a driving vehicle or plane.
+ */
+GarrisonHolder.prototype.AllowGarrisoning = function(allow, callerID)
+{
+ this.allowGarrisoning[callerID] = allow;
+};
+
+/**
+ * Check if no component of this entity blocks garrisoning
+ * (f.e. because the vehicle is moving too fast)
+ */
+GarrisonHolder.prototype.IsGarrisoningAllowed = function()
+{
+ return this.allowGarrisoning.every(function (x) x);
+};
/**
* Get number of garrisoned units capable of shooting arrows
* Not necessarily archers
*/
GarrisonHolder.prototype.GetGarrisonedArcherCount = function(garrisonArrowClasses)
{
var count = 0;
for each (var entity in this.entities)
{
var cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
var classes = cmpIdentity.GetClassesList();
if (classes.some(function(c){return garrisonArrowClasses.indexOf(c) > -1;}))
count++;
}
return count;
};
/**
* Checks if an entity can be allowed to garrison in the building
* based on its class
*/
GarrisonHolder.prototype.AllowedToGarrison = function(entity)
{
+ if (!this.IsGarrisoningAllowed())
+ return false;
+
var allowedClasses = this.GetAllowedClassesList();
var entityClasses = (Engine.QueryInterface(entity, IID_Identity)).GetClassesList();
// Check if the unit is allowed to be garrisoned inside the building
for each (var allowedClass in allowedClasses)
{
if (entityClasses.indexOf(allowedClass) != -1)
{
return true;
}
}
return false;
};
/**
* Garrison a unit inside.
* Returns true if successful, false if not
* The timer for AutoHeal is started here
*/
GarrisonHolder.prototype.Garrison = function(entity)
{
if (!this.HasEnoughHealth())
return false;
// Check if the unit is allowed to be garrisoned inside the building
if(!this.AllowedToGarrison(entity))
return false;
if (this.GetCapacity() < this.spaceOccupied + 1)
return false;
var cmpPosition = Engine.QueryInterface(entity, IID_Position);
if (!cmpPosition)
return false;
if (!this.timer && this.GetHealRate() > 0)
{
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
this.timer = cmpTimer.SetTimeout(this.entity, IID_GarrisonHolder, "HealTimeout", 1000, {});
}
// Actual garrisoning happens here
this.entities.push(entity);
this.spaceOccupied += 1;
cmpPosition.MoveOutOfWorld();
this.UpdateGarrisonFlag();
var cmpProductionQueue = Engine.QueryInterface(entity, IID_ProductionQueue);
if (cmpProductionQueue)
cmpProductionQueue.PauseProduction();
Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, {});
return true;
};
/**
* Simply eject the unit from the garrisoning entity without
* moving it
* Returns true if successful, false if not
*/
GarrisonHolder.prototype.Eject = function(entity, forced)
{
+
var entityIndex = this.entities.indexOf(entity);
+ // Error: invalid entity ID, usually it's already been ejected
if (entityIndex == -1)
- { // Error: invalid entity ID, usually it's already been ejected
return false; // Fail
- }
// Find spawning location
var cmpFootprint = Engine.QueryInterface(this.entity, IID_Footprint);
var pos = cmpFootprint.PickSpawnPoint(entity);
if (pos.y < 0)
{
// Error: couldn't find space satisfying the unit's passability criteria
if (forced)
{ // If ejection is forced, we need to continue, so use center of the building
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
pos = cmpPosition.GetPosition();
}
else
{ // Fail
return false;
}
}
this.spaceOccupied -= 1;
this.entities.splice(entityIndex, 1);
var cmpUnitAI = Engine.QueryInterface(entity, IID_UnitAI);
if (cmpUnitAI)
cmpUnitAI.Ungarrison();
var cmpProductionQueue = Engine.QueryInterface(entity, IID_ProductionQueue);
if (cmpProductionQueue)
cmpProductionQueue.UnpauseProduction();
var cmpNewPosition = Engine.QueryInterface(entity, IID_Position);
cmpNewPosition.JumpTo(pos.x, pos.z);
// TODO: what direction should they face in?
Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, {});
return true;
};
/**
* Order entities to walk to the Rally Point
*/
GarrisonHolder.prototype.OrderWalkToRallyPoint = function(entities)
{
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
var cmpRallyPoint = Engine.QueryInterface(this.entity, IID_RallyPoint);
if (cmpRallyPoint)
{
var rallyPos = cmpRallyPoint.GetPositions()[0];
if (rallyPos)
{
var commands = GetRallyPointCommands(cmpRallyPoint, entities);
for each (var com in commands)
{
ProcessCommand(cmpOwnership.GetOwner(), com);
}
}
}
};
/**
* Ejects units and orders them to move to the Rally Point.
* Returns true if successful, false if not
*/
GarrisonHolder.prototype.PerformEject = function(entities, forced)
{
+ if (!this.IsGarrisoningAllowed() && !forced)
+ return false
var ejectedEntities = [];
var success = true;
for each (var entity in entities)
{
if (this.Eject(entity, forced))
ejectedEntities.push(entity);
else
success = false;
}
this.OrderWalkToRallyPoint(ejectedEntities);
this.UpdateGarrisonFlag();
return success;
};
/**
* Unload unit from the garrisoning entity and order them
* to move to the Rally Point
* Returns true if successful, false if not
*/
GarrisonHolder.prototype.Unload = function(entity, forced)
{
return this.PerformEject([entity], forced);
};
/**
* Unload one or all units that match a template and owner from
* the garrisoning entity and order them to move to the Rally Point
* Returns true if successful, false if not
*
* extendedTemplate has the format "p"+ownerid+"&"+template
*/
GarrisonHolder.prototype.UnloadTemplate = function(extendedTemplate, all, forced)
{
var index = extendedTemplate.indexOf("&");
if (index == -1)
return false;
var owner = +extendedTemplate.slice(1,index);
var template = extendedTemplate.slice(index+1);
var entities = [];
var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
for each (var entity in this.entities)
{
var cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
// Units with multiple ranks are grouped together.
var name = cmpIdentity.GetSelectionGroupName()
|| cmpTemplateManager.GetCurrentTemplateName(entity);
if (name != template)
continue;
if (owner != Engine.QueryInterface(entity, IID_Ownership).GetOwner())
continue;
entities.push(entity);
// If 'all' is false, only ungarrison the first matched unit.
if (!all)
break;
}
return this.PerformEject(entities, forced);
};
/**
* Unload all units with same owner as the entity
* and order them to move to the Rally Point
* Returns true if all successful, false if not
*/
GarrisonHolder.prototype.UnloadAllOwn = function(forced)
{
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (!cmpOwnership)
return false;
var owner = cmpOwnership.GetOwner();
// Make copy of entity list
var entities = [];
for each (var entity in this.entities)
{
var cmpOwnership = Engine.QueryInterface(entity, IID_Ownership);
if (cmpOwnership && cmpOwnership.GetOwner() == owner)
entities.push(entity);
}
return this.PerformEject(entities, forced);
};
/**
* Unload all units from the entity
* and order them to move to the Rally Point
* Returns true if all successful, false if not
*/
GarrisonHolder.prototype.UnloadAll = function(forced)
{
var entities = this.entities.slice(0);
return this.PerformEject(entities, forced);
};
/**
* Used to check if the garrisoning entity's health has fallen below
* a certain limit after which all garrisoned units are unloaded
*/
GarrisonHolder.prototype.OnHealthChanged = function(msg)
{
if (!this.HasEnoughHealth())
{
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
// Destroy the garrisoned units if the holder kill his entities on destroy or
// is not in the world (generally means this holder is inside
// a holder which kills its entities which has sunk).
if (!this.EjectEntitiesOnDestroy() || !cmpPosition.IsInWorld())
{
for each (var entity in this.entities)
{
var cmpHealth = Engine.QueryInterface(entity, IID_Health);
if (cmpHealth)
{
cmpHealth.Kill();
}
}
this.entities = [];
Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, {});
}
else
{ // Building - force ejection
this.UnloadAll(true);
}
}
};
/**
* Check if this entity has enough health to garrison units inside it
*/
GarrisonHolder.prototype.HasEnoughHealth = function()
{
var cmpHealth = Engine.QueryInterface(this.entity, IID_Health)
var hitpoints = cmpHealth.GetHitpoints();
var maxHitpoints = cmpHealth.GetMaxHitpoints();
var ejectHitpoints = Math.floor((+this.template.EjectHealth) * maxHitpoints);
return hitpoints > ejectHitpoints;
};
/**
* Called every second. Heals garrisoned units
*/
GarrisonHolder.prototype.HealTimeout = function(data)
{
if (this.entities.length == 0)
{
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
cmpTimer.CancelTimer(this.timer);
this.timer = undefined;
}
else
{
for each (var entity in this.entities)
{
var cmpHealth = Engine.QueryInterface(entity, IID_Health);
if (cmpHealth)
{
// We do not want to heal unhealable units
if (!cmpHealth.IsUnhealable())
cmpHealth.Increase(this.GetHealRate());
}
}
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
this.timer = cmpTimer.SetTimeout(this.entity, IID_GarrisonHolder, "HealTimeout", 1000, {});
}
};
GarrisonHolder.prototype.UpdateGarrisonFlag = function()
{
var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
if (!cmpVisual)
return;
cmpVisual.SelectAnimation("garrisoned", true, 0, "");
// TODO: ought to extend ICmpVisual to let us just select variant
// keywords without changing the animation too
if (this.entities.length)
cmpVisual.SelectAnimation("garrisoned", false, 1.0, "");
else
cmpVisual.SelectAnimation("idle", false, 1.0, "");
};
/**
* Cancel timer when destroyed
*/
GarrisonHolder.prototype.OnDestroy = function()
{
if (this.timer)
{
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
cmpTimer.CancelTimer(this.timer);
}
};
/**
* If a garrisoned entity is captured, or about to be killed (so its owner
* changes to '-1'), remove it from the building so we only ever contain valid
* entities
*/
GarrisonHolder.prototype.OnGlobalOwnershipChanged = function(msg)
{
var entityIndex = this.entities.indexOf(msg.entity);
if (entityIndex != -1)
{
// If the entity is dead, remove it directly instead of ejecting the corpse
var cmpHealth = Engine.QueryInterface(msg.entity, IID_Health);
if (cmpHealth && cmpHealth.GetHitpoints() == 0)
{
this.entities.splice(entityIndex, 1);
Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, {});
}
else
{
// We have to be careful of our passability
// ships: not land passable, assume unit was thrown overboard or something
// building: land passable, unit can be ejected freely
var classes = (Engine.QueryInterface(this.entity, IID_Identity)).GetClassesList();
if (classes.indexOf("Ship") != -1)
{ // Ship - kill unit
var cmpHealth = Engine.QueryInterface(msg.entity, IID_Health);
if (cmpHealth)
{
cmpHealth.Kill();
}
this.entities.splice(entityIndex, 1);
Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, {});
}
else
{ // Building - force ejection
this.Eject(msg.entity, true);
}
}
}
};
/**
* Update list of garrisoned entities if one gets renamed (e.g. by promotion)
*/
GarrisonHolder.prototype.OnGlobalEntityRenamed = function(msg)
{
var entityIndex = this.entities.indexOf(msg.entity);
if (entityIndex != -1)
{
this.entities[entityIndex] = msg.newentity;
Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, {});
}
};
/**
* Eject all foreign garrisoned entities which are no more allied
*/
GarrisonHolder.prototype.OnDiplomacyChanged = function()
{
for (var i = this.entities.length; i > 0; --i)
{
if (!IsOwnedByMutualAllyOfEntity(this.entity, this.entities[i-1]))
this.Eject(this.entities[i-1], true);
}
this.UpdateGarrisonFlag();
};
Engine.RegisterComponentType(IID_GarrisonHolder, "GarrisonHolder", GarrisonHolder);
Index: ps/trunk/binaries/data/mods/public/simulation/components/UnitMotionFlying.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/UnitMotionFlying.js (revision 13693)
+++ ps/trunk/binaries/data/mods/public/simulation/components/UnitMotionFlying.js (revision 13694)
@@ -1,261 +1,300 @@
// (A serious implementation of this might want to use C++ instead of JS
// for performance; this is just for fun.)
-const shortFinal = 2.5;
+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;
};
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 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 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)
- var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
- var ground = cmpTerrain.GetGroundLevel(pos.x, pos.z);
- if (this.speed < this.template.TakeoffSpeed && this.onGround)
- {
- // 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 (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);
- }
- cmpPosition.SetHeightFixed(pos.y);
- }
- else
+ if (this.landing)
{
if (this.speed > 0 && this.onGround)
- {
+ {
+ this.pitch = 0;
// Deaccelerate forwards...at a very reduced pace.
- this.speed = Math.max(0, this.speed - turnLength * this.template.BrakingRate);
+ 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
- var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
- var ground = cmpTerrain.GetGroundLevel(pos.x, pos.z);
+ // 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);
- cmpPosition.SetHeightFixed(pos.y);
+ pos.y = Math.max(ground, pos.y - turnLength * this.template.ClimbRate);
}
- else if (this.speed == 0)
+ else if (this.speed == 0 && this.onGround)
{
+ if (this.waterDeath)
+ cmpHealth.Kill();
+ this.pitch = 0;
// We've stopped.
+ cmpGarrisonHolder.AllowGarrisoning(true,"UnitMotionFlying")
canTurn = false;
this.hasTarget = false;
this.landing = false;
}
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 cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
- var ground = cmpTerrain.GetGroundLevel(pos.x, pos.z);
+ canTurn = false;
var targetHeight = ground;
// Steep, then gradual descent.
- var descentRate = ((pos.y - targetHeight) / this.template.FlyingHeight * this.template.ClimbRate + shortFinal) * shortFinal;
+ 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;
- cmpPosition.SetHeightFixed(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)
+ {
+ 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.sqrt(Math.pow(this.targetX - pos.x, 2) + Math.pow(this.targetZ - pos.z, 2));
if (!this.reachedTarget && this.targetMinRange <= distFromTarget && distFromTarget <= this.targetMaxRange)
{
this.reachedTarget = true;
Engine.PostMessage(this.entity, MT_MotionChanged, { "starting": false, "error": false });
}
// 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)
- angle = targetAngle + deltaClamped - delta;
+ 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.TurnTo(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.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.IsInPointRange = function(x, y, minRange, maxRange)
{
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
var pos = cmpPosition.GetPosition2D();
var distFromTarget = Math.sqrt(Math.pow(x - pos.x, 2) + Math.pow(y - pos.y, 2));
if (minRange <= distFromTarget && distFromTarget <= maxRange)
return true;
return false;
};
UnitMotionFlying.prototype.IsInTargetRange = function(target, minRange, maxRange)
{
var cmpTargetPosition = Engine.QueryInterface(target, IID_Position);
if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld())
return false;
var targetPos = cmpTargetPosition.GetPosition2D();
return this.IsInPointRange(targetPos.x, targetPos.y, minRange, maxRange);
};
UnitMotionFlying.prototype.GetWalkSpeed = function()
{
return +this.template.MaxSpeed;
};
UnitMotionFlying.prototype.GetRunSpeed = function()
{
return this.GetWalkSpeed();
};
UnitMotionFlying.prototype.GetCurrentSpeed = function()
{
return this.speed;
}
UnitMotionFlying.prototype.FaceTowardsPoint = function(x, z)
{
// Ignore this - angle is controlled by the target-seeking code instead
};
UnitMotionFlying.prototype.StopMoving = function()
{
//Invert
- this.landing = !this.landing;
+ if (!this.waterDeath)
+ this.landing = !this.landing;
+
};
UnitMotionFlying.prototype.SetDebugOverlay = function(enabled)
{
};
Engine.RegisterComponentType(IID_UnitMotion, "UnitMotionFlying", UnitMotionFlying);
Index: ps/trunk/binaries/data/mods/public/simulation/templates/other/plane.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/other/plane.xml (revision 13693)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/other/plane.xml (revision 13694)
@@ -1,47 +1,74 @@
0.0
100.0
25.0
48
24
60.0
0
2000
1.5
+
+ 3
+ 1
+ Infantry
+
+
+ 1
+ 0
+ Support Infantry
+ 1
+ 5
+ false
+
+
+
+
+ 0.0
+ 3.0
+ 7.0
+
hele
P-51 Mustang
This may be anachronistic.
A World War 2 American fighter plane.
units/global_mustang.png
+
+ 100
+ true
+ true
+
1.0
+ true
60.0
50.0
40.0
25.0
5.0
10.0
1.0
2.0
50.0
- 5.0
+ 15.0
+ true
100
units/global/plane.xml