Ticket #3280: formation_pathfinding.diff

File formation_pathfinding.diff, 25.6 KB (added by sanderd17, 9 years ago)
  • binaries/data/mods/public/simulation/components/Formation.js

    diff --git a/binaries/data/mods/public/simulation/components/Formation.js b/binaries/data/mods/public/simulation/components/Formation.js
    index 6bdc3b6..0a125be 100644
    a b Formation.prototype.MoveMembersIntoFormation = function(moveCenter, force)  
    548548        xMin = Math.min(xMin, offset.x);
    549549        yMin = Math.min(yMin, offset.y);
    550550    }
    551     this.width = xMax - xMin;
    552     this.depth = yMax - yMin;
     551    this.width = xMax - xMin + 4;// TODO take formation width + passClass clearance
     552    this.depth = yMax - yMin + 4;
     553
     554    // set an obstruction size to calculate collisions
     555    var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
     556    if (cmpObstruction)
     557        cmpObstruction.SetSize(this.depth, this.width);
     558    var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
     559    if (cmpUnitMotion)
     560        cmpUnitMotion.SetAllowedDeviation(this.width);
    553561};
    554562
    555563Formation.prototype.MoveToMembersCenter = function()
  • binaries/data/mods/public/simulation/components/UnitMotionFormation.js

    diff --git a/binaries/data/mods/public/simulation/components/UnitMotionFormation.js b/binaries/data/mods/public/simulation/components/UnitMotionFormation.js
    index e69de29..3c3c35c 100644
    a b  
     1function UnitMotionFormation() {}
     2
     3UnitMotionFormation.prototype.Schema =
     4    "<empty/>";
     5
     6UnitMotionFormation.prototype.Init = function()
     7{
     8    this.goal = null;
     9    this.pathTicket = 0;
     10    this.passClass = 0;
     11    this.path = [];
     12    this.speed = 1;
     13    this.pathIdx = -1;
     14    this.state = "IDLE";
     15    this.allowedDeviation = 8;
     16
     17    var cmpPathfinder = Engine.QueryInterface(SYSTEM_ENTITY, IID_Pathfinder);
     18    this.passClass = cmpPathfinder.GetPassabilityClass("default");
     19};
     20
     21UnitMotionFormation.prototype.SetPassabilityClassName = function(name)
     22{
     23    var cmpPathfinder = Engine.QueryInterface(SYSTEM_ENTITY, IID_Pathfinder);
     24    this.passClass = cmpPathfinder.GetPassabilityClass(name);
     25};
     26
     27// Ignore, unit radius is not important anymore, only the passability class is
     28UnitMotionFormation.prototype.SetUnitRadius = function() {};
     29
     30UnitMotionFormation.prototype.SetAllowedDeviation = function(allowedDeviation)
     31{
     32    this.allowedDeviation = allowedDeviation;
     33};
     34
     35UnitMotionFormation.prototype.IsMoving = function()
     36{
     37    return this.pathIdx >= 0;
     38};
     39UnitMotionFormation.prototype.SetSpeed = function(speed)
     40{
     41    this.speed = speed;
     42};
     43
     44UnitMotionFormation.prototype.MoveToPointRange = function(x, z, minRange, maxRange)
     45{
     46    this.goal = {
     47        "type": 0,
     48        "x": x, // target x
     49        "z": z, // target z
     50        "u": {"x": 1, "y": 0}, // first unit axis
     51        "v": {"x": 0, "y": 1}, // second unit axis
     52        "hw": maxRange, // for a circle, hw is range
     53        "hh": maxRange,
     54        "maxdist": 0 // maxdist between waypoints, 0 -> unlimited
     55    }
     56    var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     57    if (!cmpPosition || !cmpPosition.IsInWorld())
     58        return false;
     59
     60    var pos = cmpPosition.GetPosition2D();
     61
     62    var cmpPathfinder = Engine.QueryInterface(SYSTEM_ENTITY, IID_Pathfinder);
     63    this.pathTicket = cmpPathfinder.ComputePathAsync(pos.x, pos.y, this.goal, this.passClass, this.entity);
     64
     65    this.state = "PATH_REQUESTED";
     66    return true;
     67};
     68
     69UnitMotionFormation.prototype.OnPathResult = function(msg)
     70{
     71    if (msg.ticket != this.pathTicket)
     72        return;
     73
     74    var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     75    if (!cmpPosition || !cmpPosition.IsInWorld())
     76        return;
     77
     78    // TODO path generalisation
     79    this.path = msg.path;
     80    this.pathIdx = this.path.length -1;
     81    // add the current position for reference
     82    var pos = cmpPosition.GetPosition2D();
     83    this.path.push({"x" : pos.x, "y": pos.y});
     84
     85    var direction = Vector2D.clone(this.path[this.pathIdx]).sub(pos);
     86    cmpPosition.TurnTo(Math.atan2(direction.x, direction.y));
     87
     88    this.state = "FOLLOW_LINE";
     89    Engine.PostMessage(this.entity, MT_MotionChanged, { "starting": true, "error": false });
     90};
     91
     92UnitMotionFormation.prototype.OnUpdate = function(msg)
     93{
     94    if (!this.IsMoving())
     95        return;
     96    if (this.state == "FOLLOW_LINE")
     97        this.FollowLine(msg.turnLength);
     98    else if (this.state == "TAKE_TURN")
     99        this.TakeTurn(msg.turnLength);
     100};
     101
     102UnitMotionFormation.prototype.FollowLine = function(turnLength)
     103{
     104    var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     105    if (!cmpPosition || !cmpPosition.IsInWorld())
     106        return;
     107
     108    var pos = cmpPosition.GetPosition2D();
     109
     110    // project current position to original path
     111    var target = this.path[this.pathIdx];
     112    var source = this.path[this.pathIdx + 1];
     113    var direction = Vector2D.clone(target).sub(source);
     114    var posVec = Vector2D.clone(pos).sub(source);
     115    var pathPosition = Vector2D.clone(direction).mult(posVec.dot(direction) / direction.lengthSquared());
     116
     117    // Get the distance on the original path
     118    var pathToWalk = Vector2D.clone(direction).sub(pathPosition);
     119    var distanceToTarget = pathToWalk.length();
     120    var distanceToWalk = this.speed * turnLength;
     121
     122    if (distanceToTarget < distanceToWalk) // just walk straight on, and start turning
     123    {
     124        pos.add(pathToWalk);
     125        cmpPosition.MoveTo(pos.x, pos.y);
     126        // if this is the last target, stop moving
     127        if (this.pathIdx)
     128        {
     129            // else, take a turn
     130            var usedTime = distanceToTarget / this.speed;
     131            this.state = "TAKE_TURN";
     132            this.TakeTurn(turnLength - usedTime);
     133        }
     134        else
     135        {
     136            this.pathIdx--;
     137            Engine.PostMessage(this.entity, MT_MotionChanged, { "starting": false, "error": false });
     138            this.state = "IDLE";
     139        }
     140        return;
     141    }
     142
     143    var currentDeviation = posVec.distanceTo(pathPosition);
     144    if (this.pathIdx == 0 && distanceToTarget <= currentDeviation)
     145    {
     146        var pathToTarget = Vector2D.clone(target).sub(pos).normalize().mult(distanceToWalk);
     147        pos.add(pathToTarget);
     148        cmpPosition.MoveTo(pos.x, pos.y);
     149        return;
     150    }
     151
     152    // Find the next position
     153    // scale the path to walk to include the walkable distance
     154    var centerPathToWalk = Vector2D.clone(pathToWalk).mult(distanceToWalk / distanceToTarget);
     155    // expand the rotation matrix for the 45deg rotations
     156    var leftPathToWalk = new Vector2D(centerPathToWalk.x - centerPathToWalk.y, centerPathToWalk.x + centerPathToWalk.y);
     157    var rightPathToWalk = new Vector2D(centerPathToWalk.x + centerPathToWalk.y, -centerPathToWalk.x + centerPathToWalk.y);
     158   
     159    var newCenterPosVec = Vector2D.clone(posVec).add(centerPathToWalk);
     160    var newLeftPosVec = Vector2D.clone(posVec).add(leftPathToWalk);
     161    var newRightPosVec = Vector2D.clone(posVec).add(rightPathToWalk);
     162
     163    // project the center to the original path
     164    var centerPathPosition = Vector2D.clone(direction).mult(newCenterPosVec.dot(direction) / direction.lengthSquared());
     165
     166    var allowLeft = newLeftPosVec.distanceToSquared(centerPathPosition) < this.allowedDeviation * this.allowedDeviation;
     167    var allowRight = newRightPosVec.distanceToSquared(centerPathPosition) < this.allowedDeviation * this.allowedDeviation;
     168
     169    // compare the obstructions
     170    var cmpPathfinder = Engine.QueryInterface(SYSTEM_ENTITY, IID_Pathfinder);
     171    var centerObstructedTiles = cmpPathfinder.GetObstructedTiles(centerPathToWalk, this.entity, this.passClass);
     172    var leftObstructedTiles = Infinity;
     173    var rightObstructedTiles = Infinity;
     174    if (allowLeft)
     175        leftObstructedTiles = cmpPathfinder.GetObstructedTiles(leftPathToWalk, this.entity, this.passClass);
     176    if (allowRight)
     177        rightObstructedTiles = cmpPathfinder.GetObstructedTiles(rightPathToWalk, this.entity, this.passClass);
     178
     179    // choose the best of the three positions
     180    var min = Math.min(centerObstructedTiles, leftObstructedTiles, rightObstructedTiles);
     181    if (min == centerObstructedTiles)
     182        pos.add(centerPathToWalk);
     183    else if (min == leftObstructedTiles)
     184        pos.add(leftPathToWalk);
     185    else if (min == rightObstructedTiles)
     186        pos.add(rightPathToWalk);
     187
     188    cmpPosition.MoveTo(pos.x, pos.y);
     189};
     190
     191UnitMotionFormation.prototype.TakeTurn = function(turnLength)
     192{
     193    // TODO take time to make a nice turn
     194    var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     195    if (!cmpPosition || !cmpPosition.IsInWorld())
     196        return;
     197    var newSource = Vector2D.clone(this.path[this.pathIdx]);
     198    var newTarget = Vector2D.clone(this.path[--this.pathIdx]);
     199    var newDirection = Vector2D.clone(newTarget).sub(newSource);
     200
     201    cmpPosition.TurnTo(Math.atan2(newDirection.x, newDirection.y));
     202    this.state = "FOLLOW_LINE";
     203    this.FollowLine(turnLength);
     204};
     205
     206Engine.RegisterComponentType(IID_UnitMotion, "UnitMotionFormation", UnitMotionFormation);
  • binaries/data/mods/public/simulation/templates/template_formation.xml

    diff --git a/binaries/data/mods/public/simulation/templates/template_formation.xml b/binaries/data/mods/public/simulation/templates/template_formation.xml
    index c17f0e7..cc151b3 100644
    a b  
    11<?xml version="1.0" encoding="utf-8"?>
    22<Entity>
    3   <!--
     3  <!---->
    44  <VisualActor>
    55    <Actor>props/special/common/waypoint_flag.xml</Actor>
     6    <SilhouetteDisplay>false</SilhouetteDisplay>
     7    <SilhouetteOccluder>true</SilhouetteOccluder>
     8    <VisibleInAtlasOnly>false</VisibleInAtlasOnly>
    69  </VisualActor>
    7   -->
     10  <!---->
    811  <Formation>
    912    <RequiredMemberCount>1</RequiredMemberCount>
    1013    <DisabledTooltip></DisabledTooltip>
     
    2124  <FormationAttack>
    2225    <CanAttackAsFormation>false</CanAttackAsFormation>
    2326  </FormationAttack>
     27  <Obstruction>
     28    <Active>false</Active>
     29    <BlockMovement>false</BlockMovement>
     30    <BlockPathfinding>false</BlockPathfinding>
     31    <BlockFoundation>false</BlockFoundation>
     32    <BlockConstruction>false</BlockConstruction>
     33    <DisableBlockMovement>true</DisableBlockMovement>
     34    <DisableBlockPathfinding>true</DisableBlockPathfinding>
     35    <Static width="30.0" depth="30.0"/>
     36  </Obstruction>
    2437  <Ownership/>
    2538  <Position>
    2639    <Altitude>0</Altitude>
     
    3548    <FormationController>true</FormationController>
    3649    <CanGuard>true</CanGuard>
    3750  </UnitAI>
    38   <UnitMotion>
    39     <FormationController>true</FormationController>
    40     <WalkSpeed>1.0</WalkSpeed>
    41     <PassabilityClass>siege-large</PassabilityClass>
    42   </UnitMotion>
     51  <UnitMotionFormation/>
    4352  <Trader>
    4453    <GainMultiplier>1.0</GainMultiplier>
    4554  </Trader>
  • binaries/data/mods/public/simulation/templates/template_unit.xml

    diff --git a/binaries/data/mods/public/simulation/templates/template_unit.xml b/binaries/data/mods/public/simulation/templates/template_unit.xml
    index 761c00a..ed23f03 100644
    a b  
    3737  <Identity>
    3838    <GenericName>Unit</GenericName>
    3939    <Classes datatype="tokens">Unit ConquestCritical</Classes>
    40         <Formations datatype="tokens">
    41             formations/null
    42         </Formations>
     40    <Formations datatype="tokens">
     41      formations/null
     42      formations/box
     43      formations/column_closed
     44      formations/line_closed
     45      formations/column_open
     46      formations/line_open
     47      formations/flank
     48      formations/battle_line
     49    </Formations>
    4350  </Identity>
    4451  <Looter/>
    4552  <Minimap>
  • source/scriptinterface/ScriptConversions.cpp

    diff --git a/source/scriptinterface/ScriptConversions.cpp b/source/scriptinterface/ScriptConversions.cpp
    index 8ea9b51..dc53121 100644
    a b  
    2020#include "ScriptInterface.h"
    2121
    2222#include "graphics/Entity.h"
     23#include "simulation2/helpers/PathGoal.h" // to convert path goals
    2324#include "ps/utf16string.h"
    2425#include "ps/CLogger.h"
    2526#include "ps/CStr.h"
    template<> bool ScriptInterface::FromJSVal<Entity>(JSContext* cx, JS::HandleValu  
    205206    JS::RootedValue position(cx);
    206207    JS::RootedValue rotation(cx);
    207208
    208     // TODO: Report type errors
     209    // TODO: Report type errors when loading attributes
    209210    if (!JS_GetProperty(cx, obj, "player", &player) || !FromJSVal(cx, player, out.playerID))
    210211        FAIL("Failed to read Entity.player property");
    211212    if (!JS_GetProperty(cx, obj, "templateName", &templateName) || !FromJSVal(cx, templateName, out.templateName))
    template<> bool ScriptInterface::FromJSVal<Entity>(JSContext* cx, JS::HandleValu  
    220221    return true;
    221222}
    222223
     224// TODO this doesn't work (probably always uses POINT)
     225template<> bool ScriptInterface::FromJSVal<PathGoal::Type>(JSContext* cx, JS::HandleValue v, PathGoal::Type& out)
     226{
     227    std::string strVal;
     228    ScriptInterface::FromJSVal(cx, v, static_cast<std::string&>(strVal));
     229    if (strVal == "CIRCLE")
     230        out = PathGoal::CIRCLE;
     231    if (strVal == "INVERTED_CIRCLE")
     232        out = PathGoal::INVERTED_CIRCLE;
     233    else if (strVal == "SQUARE")
     234        out = PathGoal::SQUARE;
     235    else if (strVal == "INVERTED_SQUARE")
     236        out = PathGoal::INVERTED_SQUARE;
     237    else
     238        out = PathGoal::POINT;
     239    return true;
     240}
     241
     242template<> bool ScriptInterface::FromJSVal<PathGoal>(JSContext* cx, JS::HandleValue val, PathGoal& out)
     243{
     244    JSAutoRequest rq(cx);
     245    if (!val.isObject())
     246        FAIL("Argument must be an object");
     247
     248    JS::RootedObject obj(cx, &val.toObject());
     249    JS::RootedValue type(cx);
     250    JS::RootedValue x(cx);
     251    JS::RootedValue z(cx);
     252    JS::RootedValue u(cx);
     253    JS::RootedValue v(cx);
     254    JS::RootedValue hw(cx);
     255    JS::RootedValue hh(cx);
     256
     257   
     258    // TODO: Report type errors
     259    if (!JS_GetProperty(cx, obj, "type", &type) || !FromJSVal(cx, type, out.type))
     260        FAIL("Failed to read goal.type property");
     261    if (!JS_GetProperty(cx, obj, "x", &x) || !FromJSVal(cx, x, out.x))
     262        FAIL("Failed to read goal.x property");
     263    if (!JS_GetProperty(cx, obj, "z", &z) || !FromJSVal(cx, z, out.z))
     264        FAIL("Failed to read goal.z property");
     265    if (!JS_GetProperty(cx, obj, "u", &u) || !FromJSVal(cx, u, out.u))
     266        FAIL("Failed to read goal.u property");
     267    if (!JS_GetProperty(cx, obj, "v", &v) || !FromJSVal(cx, v, out.v))
     268        FAIL("Failed to read goal.v property");
     269    if (!JS_GetProperty(cx, obj, "hw", &hw) || !FromJSVal(cx, hw, out.hw))
     270        FAIL("Failed to read goal.hw property");
     271    if (!JS_GetProperty(cx, obj, "hh", &hh) || !FromJSVal(cx, hh, out.hh))
     272        FAIL("Failed to read goal.hh property");
     273    return true;
     274}
     275
    223276////////////////////////////////////////////////////////////////
    224277// Primitive types:
    225278
  • source/simulation2/components/CCmpObstruction.cpp

    diff --git a/source/simulation2/components/CCmpObstruction.cpp b/source/simulation2/components/CCmpObstruction.cpp
    index 40c6963..4e4d897 100644
    a b public:  
    493493            return CFixedVector2D(m_Size0 / 2, m_Size1 / 2).Length();
    494494    }
    495495
     496    virtual void SetSize(entity_pos_t width, entity_pos_t height)
     497    {
     498        // require this to be inactive, so we don't have to force grid updates
     499        ENSURE(m_Active == false);
     500        m_Size0 = width;
     501        m_Size0 = height;
     502    }
     503
    496504    virtual bool IsControlPersistent()
    497505    {
    498506        return m_ControlPersist;
  • source/simulation2/components/CCmpPathfinder.cpp

    diff --git a/source/simulation2/components/CCmpPathfinder.cpp b/source/simulation2/components/CCmpPathfinder.cpp
    index f31c303..12ced80 100644
    a b void CCmpPathfinder::RenderSubmit(SceneCollector& collector)  
    188188}
    189189
    190190
    191 pass_class_t CCmpPathfinder::GetPassabilityClass(const std::string& name)
     191pass_class_t CCmpPathfinder::GetPassabilityClass(std::string name)
    192192{
    193193    if (m_PassClassMasks.find(name) == m_PassClassMasks.end())
    194194    {
    bool CCmpPathfinder::GetDirtinessData(Grid<u8>& dirtinessGrid, bool& globalUpdat  
    611611
    612612// Async path requests:
    613613
    614 u32 CCmpPathfinder::ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass, entity_id_t notify)
     614u32 CCmpPathfinder::ComputePathAsync(entity_pos_t x0, entity_pos_t z0, PathGoal goal, pass_class_t passClass, entity_id_t notify)
    615615{
    616616    AsyncLongPathRequest req = { m_NextAsyncTicket++, x0, z0, goal, passClass, notify };
    617617    m_AsyncLongPathRequests.push_back(req);
    ICmpObstruction::EFoundationCheck CCmpPathfinder::CheckBuildingPlacement(const I  
    807807    return ICmpObstruction::FOUNDATION_CHECK_SUCCESS;
    808808}
    809809
     810u32 CCmpPathfinder::GetObstructedTiles(CFixedVector2D offset, entity_id_t id, pass_class_t passClass)
     811{
     812    // Test against terrain:
     813    ICmpObstructionManager::ObstructionSquare square;
     814    CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), id);
     815    if (!cmpObstruction || !cmpObstruction->GetObstructionSquare(square))
     816        return 0;
     817
     818    square.x += offset.X;
     819    square.z += offset.Y;
     820
     821    UpdateGrid();
     822
     823    entity_pos_t expand;
     824    const PathfinderPassability* passability = GetPassabilityFromMask(passClass);
     825    if (passability && passability->m_HasClearance)
     826        expand = passability->m_Clearance;
     827
     828    SimRasterize::Spans spans;
     829    SimRasterize::RasterizeRectWithClearance(spans, square, expand, Pathfinding::NAVCELL_SIZE);
     830    u32 obstructedTiles = 0;
     831    for (SimRasterize::Span& span : spans)
     832    {
     833        i16 i0 = span.i0;
     834        i16 i1 = span.i1;
     835        i16 j = span.j;
     836
     837        // Fail if any span includes an impassable tile
     838        for (i16 i = i0; i < i1; ++i)
     839            if (i < 0 || i > m_Grid->m_W || j < 0 || j > m_Grid->m_H || !IS_PASSABLE(m_Grid->get(i, j), passClass))
     840                ++obstructedTiles;
     841    }
     842
     843    return obstructedTiles;
     844}
  • source/simulation2/components/CCmpPathfinder_Common.h

    diff --git a/source/simulation2/components/CCmpPathfinder_Common.h b/source/simulation2/components/CCmpPathfinder_Common.h
    index f83b43c..6cb8ee3 100644
    a b public:  
    139139
    140140    virtual void HandleMessage(const CMessage& msg, bool global);
    141141
    142     virtual pass_class_t GetPassabilityClass(const std::string& name);
     142    virtual pass_class_t GetPassabilityClass(std::string name);
    143143
    144144    virtual std::map<std::string, pass_class_t> GetPassabilityClasses();
    145145
    public:  
    156156        m_LongPathfinder.ComputePath(x0, z0, goal, passClass, ret);
    157157    }
    158158
    159     virtual u32 ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass, entity_id_t notify);
     159    virtual u32 ComputePathAsync(entity_pos_t x0, entity_pos_t z0, PathGoal goal, pass_class_t passClass, entity_id_t notify);
    160160
    161161    virtual void ComputeShortPath(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const PathGoal& goal, pass_class_t passClass, WaypointPath& ret);
    162162
    public:  
    191191
    192192    virtual ICmpObstruction::EFoundationCheck CheckBuildingPlacement(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w, entity_pos_t h, entity_id_t id, pass_class_t passClass, bool onlyCenterPoint);
    193193
     194    virtual u32 GetObstructedTiles(CFixedVector2D offset, entity_id_t id, pass_class_t passClass);
     195
    194196    virtual void FinishAsyncRequests();
    195197
    196198    void ProcessLongRequests(const std::vector<AsyncLongPathRequest>& longRequests);
  • source/simulation2/components/ICmpObstruction.cpp

    diff --git a/source/simulation2/components/ICmpObstruction.cpp b/source/simulation2/components/ICmpObstruction.cpp
    index 33373f9..7a2245c 100644
    a b std::string ICmpObstruction::CheckFoundation_wrapper(std::string className, bool  
    4747
    4848BEGIN_INTERFACE_WRAPPER(Obstruction)
    4949DEFINE_INTERFACE_METHOD_0("GetUnitRadius", entity_pos_t, ICmpObstruction, GetUnitRadius)
     50DEFINE_INTERFACE_METHOD_2("SetSize", void, ICmpObstruction, SetSize, entity_pos_t, entity_pos_t)
    5051DEFINE_INTERFACE_METHOD_2("CheckFoundation", std::string, ICmpObstruction, CheckFoundation_wrapper, std::string, bool)
    5152DEFINE_INTERFACE_METHOD_0("CheckDuplicateFoundation", bool, ICmpObstruction, CheckDuplicateFoundation)
    5253DEFINE_INTERFACE_METHOD_2("GetEntityCollisions", std::vector<entity_id_t>, ICmpObstruction, GetEntityCollisions, bool, bool)
  • source/simulation2/components/ICmpObstruction.h

    diff --git a/source/simulation2/components/ICmpObstruction.h b/source/simulation2/components/ICmpObstruction.h
    index 1b289da..7d0fd18 100644
    a b public:  
    5454
    5555    virtual entity_pos_t GetSize() = 0;
    5656
     57    /**
     58     * Passively set the size of this obstruction
     59     * This must only be used on inactive obstructions, as it won't update any grids
     60     */
     61    virtual void SetSize(entity_pos_t width, entity_pos_t height) = 0;
     62
    5763    virtual entity_pos_t GetUnitRadius() = 0;
    5864
    5965    virtual bool IsControlPersistent() = 0;
  • source/simulation2/components/ICmpPathfinder.cpp

    diff --git a/source/simulation2/components/ICmpPathfinder.cpp b/source/simulation2/components/ICmpPathfinder.cpp
    index df01d65..a85a6d4 100644
    a b  
    2424BEGIN_INTERFACE_WRAPPER(Pathfinder)
    2525DEFINE_INTERFACE_METHOD_1("SetDebugOverlay", void, ICmpPathfinder, SetDebugOverlay, bool)
    2626DEFINE_INTERFACE_METHOD_1("SetHierDebugOverlay", void, ICmpPathfinder, SetHierDebugOverlay, bool)
     27DEFINE_INTERFACE_METHOD_1("GetPassabilityClass", pass_class_t, ICmpPathfinder, GetPassabilityClass, std::string)
     28DEFINE_INTERFACE_METHOD_3("GetObstructedTiles", u32, ICmpPathfinder, GetObstructedTiles, CFixedVector2D, entity_id_t, pass_class_t)
     29DEFINE_INTERFACE_METHOD_5("ComputePathAsync", u32, ICmpPathfinder, ComputePathAsync, entity_pos_t, entity_pos_t, PathGoal, pass_class_t, entity_id_t)
    2730END_INTERFACE_WRAPPER(Pathfinder)
  • source/simulation2/components/ICmpPathfinder.h

    diff --git a/source/simulation2/components/ICmpPathfinder.h b/source/simulation2/components/ICmpPathfinder.h
    index 70d818f..f57cab2 100644
    a b public:  
    5959     * Get the tag for a given passability class name.
    6060     * Logs an error and returns something acceptable if the name is unrecognised.
    6161     */
    62     virtual pass_class_t GetPassabilityClass(const std::string& name) = 0;
     62    virtual pass_class_t GetPassabilityClass(std::string name) = 0;
    6363
    6464    virtual const Grid<u16>& GetPassabilityGrid() = 0;
    6565
    public:  
    8787     * Returns a unique non-zero number, which will match the 'ticket' in the result,
    8888     * so callers can recognise each individual request they make.
    8989     */
    90     virtual u32 ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass, entity_id_t notify) = 0;
     90    virtual u32 ComputePathAsync(entity_pos_t x0, entity_pos_t z0, PathGoal goal, pass_class_t passClass, entity_id_t notify) = 0;
    9191
    9292    /**
    9393     * If the debug overlay is enabled, render the path that will computed by ComputePath.
    public:  
    143143     */
    144144    virtual ICmpObstruction::EFoundationCheck CheckBuildingPlacement(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w, entity_pos_t h, entity_id_t id, pass_class_t passClass, bool onlyCenterPoint) = 0;
    145145
     146    /**
     147     * Get the number of obstructed tiles that are under an obstruction of a
     148     * certain entity. Allow an offset to easily test alternative positions.
     149     */
     150    virtual u32 GetObstructedTiles(CFixedVector2D offset, entity_id_t id, pass_class_t passClass) = 0;
    146151
    147152    /**
    148153     * Toggle the storage and rendering of debug info.
  • source/simulation2/scripting/EngineScriptConversions.cpp

    diff --git a/source/simulation2/scripting/EngineScriptConversions.cpp b/source/simulation2/scripting/EngineScriptConversions.cpp
    index bfbeba6..c0deaeb 100644
    a b  
    2727#include "ps/Shapes.h"
    2828#include "ps/utf16string.h"
    2929#include "simulation2/helpers/Grid.h"
     30#include "simulation2/helpers/Pathfinding.h"
    3031#include "simulation2/system/IComponent.h"
    3132#include "simulation2/system/ParamNode.h"
    3233
    template<> void ScriptInterface::ToJSVal<CFixedVector2D>(JSContext* cx, JS::Muta  
    247248    ret.setObject(*obj);
    248249}
    249250
     251template<> void ScriptInterface::ToJSVal<Waypoint>(JSContext* cx, JS::MutableHandleValue ret, const Waypoint& val)
     252{
     253    JSAutoRequest rq(cx);
     254
     255    // apply the Vector2D prototype to the return value
     256    ScriptInterface::CxPrivate* pCxPrivate = ScriptInterface::GetScriptInterfaceAndCBData(cx);
     257    JS::RootedObject proto(cx, &pCxPrivate->pScriptInterface->GetCachedValue(ScriptInterface::CACHE_VECTOR2DPROTO).toObject());
     258    JS::RootedObject obj(cx, JS_NewObject(cx, nullptr, proto, JS::NullPtr()));
     259    if (!obj)
     260    {
     261        ret.setUndefined();
     262        return;
     263    }
     264
     265    JS::RootedValue x(cx);
     266    JS::RootedValue y(cx);
     267    ToJSVal(cx, &x, val.x);
     268    ToJSVal(cx, &y, val.z);
     269
     270    JS_SetProperty(cx, obj, "x", x);
     271    JS_SetProperty(cx, obj, "y", y);
     272
     273    ret.setObject(*obj);
     274}
     275
    250276template<> void ScriptInterface::ToJSVal<Grid<u8> >(JSContext* cx, JS::MutableHandleValue ret, const Grid<u8>& val)
    251277{
    252278    JSAutoRequest rq(cx);
    template<> void ScriptInterface::ToJSVal<std::vector<CFixedVector2D> >(JSContext  
    309335    }
    310336    ret.setObject(*obj);
    311337}
     338
     339template<> void ScriptInterface::ToJSVal<WaypointPath>(JSContext* cx, JS::MutableHandleValue ret, const WaypointPath& val)
     340{
     341    JSAutoRequest rq(cx);
     342    JS::RootedObject obj(cx, JS_NewArrayObject(cx, 0));
     343    if (!obj)
     344    {
     345        ret.setUndefined();
     346        return;
     347    }
     348    const std::vector<Waypoint>& waypointList = val.m_Waypoints;
     349    for (size_t i = 0; i < waypointList.size(); ++i)
     350    {
     351        JS::RootedValue el(cx);
     352        ScriptInterface::ToJSVal<Waypoint>(cx, &el, waypointList[i]);
     353        JS_SetElement(cx, obj, i, el);
     354    }
     355    ret.setObject(*obj);
     356}
  • source/simulation2/scripting/MessageTypeConversions.cpp

    diff --git a/source/simulation2/scripting/MessageTypeConversions.cpp b/source/simulation2/scripting/MessageTypeConversions.cpp
    index 1b4ea26..f5ce641 100644
    a b CMessage* CMessageRangeUpdate::FromJSVal(ScriptInterface& UNUSED(scriptInterface  
    385385
    386386////////////////////////////////
    387387
    388 JS::Value CMessagePathResult::ToJSVal(ScriptInterface& UNUSED(scriptInterface)) const
     388JS::Value CMessagePathResult::ToJSVal(ScriptInterface& scriptInterface) const
    389389{
    390     LOGWARNING("CMessagePathResult::ToJSVal not implemented");
    391     return JS::UndefinedValue();
     390    TOJSVAL_SETUP();
     391    SET_MSG_PROPERTY(ticket);
     392    SET_MSG_PROPERTY(path);
     393    return JS::ObjectValue(*obj);
    392394}
    393395
    394396CMessage* CMessagePathResult::FromJSVal(ScriptInterface& UNUSED(scriptInterface), JS::HandleValue UNUSED(val))