Index: binaries/data/mods/public/simulation/components/Formation.js =================================================================== --- binaries/data/mods/public/simulation/components/Formation.js +++ binaries/data/mods/public/simulation/components/Formation.js @@ -349,7 +349,7 @@ this.MoveToMembersCenter(); // Compute the speed etc. of the formation. - this.ComputeMotionParameters(); + this.ComputeSpeed(); }; /** @@ -391,13 +391,11 @@ return; } - this.ComputeMotionParameters(); - - if (!this.rearrange) - return; + this.ComputeSpeed(); // Rearrange the remaining members. - this.MoveMembersIntoFormation(true, true, this.lastOrderVariant); + if (this.rearrange) + this.MoveMembersIntoFormation(true, true, this.lastOrderVariant); }; Formation.prototype.AddMembers = function(ents) @@ -427,12 +425,10 @@ } } - this.ComputeMotionParameters(); - - if (!this.rearrange) - return; + this.ComputeSpeed(); - this.MoveMembersIntoFormation(true, true, this.lastOrderVariant); + if (this.rearrange) + this.MoveMembersIntoFormation(true, true, this.lastOrderVariant); }; /** @@ -555,6 +551,10 @@ } this.width = xMax - xMin; this.depth = yMax - yMin; + + // The formation turn rate depends on the positioning of the members. + if (offsetsChanged) + this.ComputeTurnRate(); }; Formation.prototype.MoveToMembersCenter = function() @@ -873,23 +873,42 @@ /** * Set formation controller's speed based on its current members. */ -Formation.prototype.ComputeMotionParameters = function() +Formation.prototype.ComputeSpeed = function() { - let maxRadius = 0; let minSpeed = Infinity; - for (let ent of this.members) + for (const ent of this.members) { - let cmpUnitMotion = Engine.QueryInterface(ent, IID_UnitMotion); + const cmpUnitMotion = Engine.QueryInterface(ent, IID_UnitMotion); if (cmpUnitMotion) minSpeed = Math.min(minSpeed, cmpUnitMotion.GetWalkSpeed()); } minSpeed *= this.GetSpeedMultiplier(); - - let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); + const cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); cmpUnitMotion.SetSpeedMultiplier(minSpeed / cmpUnitMotion.GetWalkSpeed()); }; +/** + * Set formation controller's turn rate based on its current shape. + */ +Formation.prototype.ComputeTurnRate = function() +{ + if (!this.offsets || !this.offsets.length) + return; + + let minTurnRateSq = Infinity; + const speedMultiplier = this.GetSpeedMultiplier(); + for (const offset of this.offsets) + { + const cmpUnitMotion = Engine.QueryInterface(offset.ent, IID_UnitMotion); + if (cmpUnitMotion) + minTurnRateSq = Math.min( + minTurnRateSq, + Math.square(cmpUnitMotion.GetWalkSpeed() * cmpUnitMotion.GetRunMultiplier() * speedMultiplier) / (offset.x * offset.x + offset.y * offset.y)); + } + Engine.QueryInterface(this.entity, IID_Position).SetTurnRate(Math.sqrt(minTurnRateSq)); +}; + Formation.prototype.ShapeUpdate = function() { if (!this.rearrange) Index: binaries/data/mods/public/simulation/components/tests/test_UnitAI.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_UnitAI.js +++ binaries/data/mods/public/simulation/components/tests/test_UnitAI.js @@ -168,6 +168,7 @@ AddMock(unit, IID_UnitMotion, { "GetWalkSpeed": () => 1, + "GetRunMultiplier": () => 1, "MoveToFormationOffset": (target, x, z) => {}, "MoveToTargetRange": (target, min, max) => true, "SetMemberOfFormation": () => {}, @@ -235,6 +236,7 @@ "GetPosition": function() { return new Vector3D(this.x, 0, this.z); }, "GetPosition2D": function() { return new Vector2D(this.x, this.z); }, "GetRotation": function() { return { "y": 0 }; }, + "SetTurnRate": () => {}, "IsInWorld": function() { return true; }, "MoveOutOfWorld": () => {} }); @@ -348,6 +350,7 @@ AddMock(unit + i, IID_UnitMotion, { "GetWalkSpeed": () => 1, + "GetRunMultiplier": () => 1, "MoveToFormationOffset": (target, x, z) => {}, "MoveToTargetRange": (target, min, max) => true, "SetMemberOfFormation": () => {}, @@ -407,6 +410,7 @@ "GetPosition": function(){ return new Vector3D(this.x, 0, this.z); }, "GetPosition2D": function(){ return new Vector2D(this.x, this.z); }, "GetRotation": () => ({ "y": 0 }), + "SetTurnRate": () => {}, "IsInWorld": () => true, "MoveOutOfWorld": () => {}, }); Index: binaries/data/mods/public/simulation/templates/template_formation.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_formation.xml +++ binaries/data/mods/public/simulation/templates/template_formation.xml @@ -54,7 +54,7 @@ upright false 0 - 10 + 0 0.75 Index: source/simulation2/components/CCmpPosition.cpp =================================================================== --- source/simulation2/components/CCmpPosition.cpp +++ source/simulation2/components/CCmpPosition.cpp @@ -132,7 +132,7 @@ "" "" "" - "" + "" ""; } @@ -552,6 +552,11 @@ return m_RotYSpeed; } + virtual void SetTurnRate(fixed turnRate) + { + m_RotYSpeed = turnRate; + } + virtual void TurnTo(entity_angle_t y) { if (m_TurretParent != INVALID_ENTITY) Index: source/simulation2/components/ICmpPosition.h =================================================================== --- source/simulation2/components/ICmpPosition.h +++ source/simulation2/components/ICmpPosition.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -71,6 +71,11 @@ virtual entity_id_t GetTurretParent() const = 0; /** + * Set the turn rate in radians per second. + */ + virtual void SetTurnRate(fixed turnRate) = 0; + + /** * Has to be called to update the simulation position of the turret */ virtual void UpdateTurretPosition() = 0; Index: source/simulation2/components/ICmpPosition.cpp =================================================================== --- source/simulation2/components/ICmpPosition.cpp +++ source/simulation2/components/ICmpPosition.cpp @@ -43,6 +43,7 @@ DEFINE_INTERFACE_METHOD("GetPreviousPosition", ICmpPosition, GetPreviousPosition) DEFINE_INTERFACE_METHOD("GetPreviousPosition2D", ICmpPosition, GetPreviousPosition2D) DEFINE_INTERFACE_METHOD("GetTurnRate", ICmpPosition, GetTurnRate) +DEFINE_INTERFACE_METHOD("SetTurnRate", ICmpPosition, SetTurnRate) DEFINE_INTERFACE_METHOD("TurnTo", ICmpPosition, TurnTo) DEFINE_INTERFACE_METHOD("SetYRotation", ICmpPosition, SetYRotation) DEFINE_INTERFACE_METHOD("SetXZRotation", ICmpPosition, SetXZRotation) Index: source/simulation2/components/tests/test_RangeManager.h =================================================================== --- source/simulation2/components/tests/test_RangeManager.h +++ source/simulation2/components/tests/test_RangeManager.h @@ -64,6 +64,7 @@ virtual CFixedVector3D GetPreviousPosition() const { return CFixedVector3D(); } virtual CFixedVector2D GetPreviousPosition2D() const { return CFixedVector2D(); } virtual fixed GetTurnRate() const { return fixed::Zero(); } + virtual void SetTurnRate(fixed UNUSED(turnRate)) { } virtual void TurnTo(entity_angle_t UNUSED(y)) { } virtual void SetYRotation(entity_angle_t UNUSED(y)) { } virtual void SetXZRotation(entity_angle_t UNUSED(x), entity_angle_t UNUSED(z)) { }