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 @@ -903,21 +903,32 @@ { let minSpeed = Infinity; let minAcceleration = Infinity; + let maxClearance = 0; + let maxPassClass; + const cmpPathfinder = Engine.QueryInterface(SYSTEM_ENTITY, IID_Pathfinder); for (let ent of this.members) { - let cmpUnitMotion = Engine.QueryInterface(ent, IID_UnitMotion); - if (cmpUnitMotion) + const cmpUnitMotion = Engine.QueryInterface(ent, IID_UnitMotion); + if (!cmpUnitMotion) + continue; + minSpeed = Math.min(minSpeed, cmpUnitMotion.GetWalkSpeed()); + minAcceleration = Math.min(minAcceleration, cmpUnitMotion.GetAcceleration()); + + const passClass = cmpUnitMotion.GetPassabilityClassName(); + const clearance = cmpPathfinder.GetClearance(cmpPathfinder.GetPassabilityClass(passClass)); + if (clearance > maxClearance) { - minSpeed = Math.min(minSpeed, cmpUnitMotion.GetWalkSpeed()); - minAcceleration = Math.min(minAcceleration, cmpUnitMotion.GetAcceleration()); + maxClearance = clearance; + maxPassClass = passClass; } } minSpeed *= this.GetSpeedMultiplier(); - let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); + const cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); cmpUnitMotion.SetSpeedMultiplier(minSpeed / cmpUnitMotion.GetWalkSpeed()); cmpUnitMotion.SetAcceleration(minAcceleration); + cmpUnitMotion.SetPassabilityClassName(maxPassClass); }; Formation.prototype.ShapeUpdate = function() 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 @@ -247,12 +247,18 @@ "StopMoving": () => {}, "SetSpeedMultiplier": () => {}, "SetAcceleration": (accel) => {}, + "SetPassabilityClassName": (name) => {}, "MoveToPointRange": () => true, "SetFacePointAfterMove": () => {}, "GetFacePointAfterMove": () => true, "GetPassabilityClassName": () => "default" }); + AddMock(SYSTEM_ENTITY, IID_Pathfinder, { + "GetClearance": () => 1, + "GetPassabilityClass": () => 16 + }); + controllerAI.OnCreate(); @@ -420,6 +426,7 @@ "GetWalkSpeed": () => 1, "SetSpeedMultiplier": (speed) => {}, "SetAcceleration": (accel) => {}, + "SetPassabilityClassName": (name) => {}, "MoveToPointRange": (x, z, minRange, maxRange) => {}, "StopMoving": () => {}, "SetFacePointAfterMove": () => {}, @@ -427,6 +434,11 @@ "GetPassabilityClassName": () => "default" }); + AddMock(SYSTEM_ENTITY, IID_Pathfinder, { + "GetClearance": () => 1, + "GetPassabilityClass": () => 16 + }); + AddMock(controller, IID_Attack, { "GetRange": function() { return { "max": 10, "min": 0 }; }, "CanAttackAsFormation": function() { return false; }, Index: source/simulation2/components/ICmpPathfinder.cpp =================================================================== --- source/simulation2/components/ICmpPathfinder.cpp +++ source/simulation2/components/ICmpPathfinder.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -24,6 +24,7 @@ BEGIN_INTERFACE_WRAPPER(Pathfinder) DEFINE_INTERFACE_METHOD("SetDebugOverlay", ICmpPathfinder, SetDebugOverlay) DEFINE_INTERFACE_METHOD("SetHierDebugOverlay", ICmpPathfinder, SetHierDebugOverlay) +DEFINE_INTERFACE_METHOD("GetClearance", ICmpPathfinder, GetClearance) DEFINE_INTERFACE_METHOD("GetPassabilityClass", ICmpPathfinder, GetPassabilityClass) DEFINE_INTERFACE_METHOD("UpdateGrid", ICmpPathfinder, UpdateGrid) END_INTERFACE_WRAPPER(Pathfinder)